import { z } from 'zod'; import { parseActionSuffix } from './action-suffix'; export interface ActionHandler { readonly body?: z.ZodTypeAny; handle(ctx: TExtra & { readonly id: string; readonly body: unknown }): Promise | void; } export type ActionTable = Readonly< Record> >; export function actionNames( actions: ActionTable, ): readonly TAction[] { return Object.keys(actions) as unknown as readonly TAction[]; } export function resolveActionTarget(opts: { readonly tail: string; readonly actions: ActionTable; 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(opts: { readonly action: string; readonly id: string; readonly actions: ActionTable; readonly extra: TExtra; readonly body?: unknown; }): Promise { 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(opts: { readonly tail: string; readonly actions: ActionTable; readonly resourceLabel: string; readonly extra: TExtra; readonly body?: unknown; readonly onUnsupported: (message: string) => void; }): Promise { 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, }); }