File size: 2,296 Bytes
4e23b01 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | import { z } from 'zod';
import { parseActionSuffix } from './action-suffix';
export interface ActionHandler<TExtra> {
readonly body?: z.ZodTypeAny;
handle(ctx: TExtra & { readonly id: string; readonly body: unknown }): Promise<void> | void;
}
export type ActionTable<TAction extends string, TExtra> = Readonly<
Record<TAction, ActionHandler<TExtra>>
>;
export function actionNames<TAction extends string, TExtra>(
actions: ActionTable<TAction, TExtra>,
): readonly TAction[] {
return Object.keys(actions) as unknown as readonly TAction[];
}
export function resolveActionTarget<TAction extends string, TExtra>(opts: {
readonly tail: string;
readonly actions: ActionTable<TAction, TExtra>;
readonly resourceLabel: string;
}): { readonly id: string; readonly action: TAction } | { readonly message: string } {
const parsed = parseActionSuffix({
tail: opts.tail,
allowedActions: actionNames(opts.actions),
resourceLabel: opts.resourceLabel,
});
if (parsed.kind !== 'action') {
return {
message: parsed.kind === 'invalid' ? parsed.reason : `unsupported action: ${opts.tail}`,
};
}
return { id: parsed.id, action: parsed.action };
}
export async function runAction<TAction extends string, TExtra>(opts: {
readonly action: string;
readonly id: string;
readonly actions: ActionTable<TAction, TExtra>;
readonly extra: TExtra;
readonly body?: unknown;
}): Promise<boolean> {
const entry = opts.actions[opts.action as TAction];
if (entry === undefined) {
return false;
}
const body = entry.body === undefined ? opts.body : entry.body.parse(opts.body);
await entry.handle({ ...opts.extra, id: opts.id, body });
return true;
}
export async function dispatchAction<TAction extends string, TExtra>(opts: {
readonly tail: string;
readonly actions: ActionTable<TAction, TExtra>;
readonly resourceLabel: string;
readonly extra: TExtra;
readonly body?: unknown;
readonly onUnsupported: (message: string) => void;
}): Promise<boolean> {
const target = resolveActionTarget(opts);
if ('message' in target) {
opts.onUnsupported(target.message);
return false;
}
return runAction({
action: target.action,
id: target.id,
actions: opts.actions,
extra: opts.extra,
body: opts.body,
});
}
|