@rotorsoft/act-http 1.11.1 → 1.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/api/actor.d.ts +4 -6
- package/dist/@types/api/actor.d.ts.map +1 -1
- package/dist/@types/api/dispatch.d.ts +46 -1
- package/dist/@types/api/dispatch.d.ts.map +1 -1
- package/dist/@types/api/index.d.ts +3 -3
- package/dist/@types/api/sse-wiring.d.ts +4 -4
- package/dist/@types/api/sse-wiring.d.ts.map +1 -1
- package/dist/@types/hono/index.d.ts +80 -13
- package/dist/@types/hono/index.d.ts.map +1 -1
- package/dist/@types/openapi/index.d.ts.map +1 -1
- package/dist/@types/receiver/extract.d.ts +4 -4
- package/dist/@types/receiver/hono/index.d.ts +1 -1
- package/dist/@types/receiver/verify.d.ts +2 -2
- package/dist/@types/sse/apply-patch.d.ts.map +1 -1
- package/dist/@types/sse/broadcast.d.ts +6 -24
- package/dist/@types/sse/broadcast.d.ts.map +1 -1
- package/dist/@types/sse/state-cache.d.ts +1 -1
- package/dist/@types/sse/types.d.ts +4 -5
- package/dist/@types/sse/types.d.ts.map +1 -1
- package/dist/@types/trpc/index.d.ts +107 -35
- package/dist/@types/trpc/index.d.ts.map +1 -1
- package/dist/@types/webhook/index.d.ts +1 -1
- package/dist/@types/webhook/index.d.ts.map +1 -1
- package/dist/@types/webhook/types.d.ts +1 -1
- package/dist/api/index.cjs +1 -3
- package/dist/api/index.cjs.map +1 -1
- package/dist/api/index.js +2 -2
- package/dist/{chunk-MKD3SM3N.js → chunk-BOJG4555.js} +1 -1
- package/dist/chunk-BOJG4555.js.map +1 -0
- package/dist/{chunk-EERALS5E.js → chunk-CVCTDLJA.js} +1 -1
- package/dist/chunk-CVCTDLJA.js.map +1 -0
- package/dist/{chunk-G5FNOSDX.js → chunk-ET2PACPO.js} +1 -1
- package/dist/chunk-ET2PACPO.js.map +1 -0
- package/dist/{chunk-TTVMQYNN.js → chunk-OEMQP5ZM.js} +2 -2
- package/dist/chunk-OEMQP5ZM.js.map +1 -0
- package/dist/{chunk-5KZI37IT.js → chunk-QSH2Y3YR.js} +3 -3
- package/dist/chunk-QSH2Y3YR.js.map +1 -0
- package/dist/{chunk-FSN52BXM.js → chunk-YEDAY2PC.js} +3 -5
- package/dist/chunk-YEDAY2PC.js.map +1 -0
- package/dist/hono/index.cjs +1 -3
- package/dist/hono/index.cjs.map +1 -1
- package/dist/hono/index.js +4 -4
- package/dist/hono/index.js.map +1 -1
- package/dist/openapi/index.cjs +103 -35
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.js +104 -36
- package/dist/openapi/index.js.map +1 -1
- package/dist/receiver/express/index.cjs.map +1 -1
- package/dist/receiver/express/index.js +1 -1
- package/dist/receiver/fastify/index.cjs.map +1 -1
- package/dist/receiver/fastify/index.js +1 -1
- package/dist/receiver/hono/index.cjs.map +1 -1
- package/dist/receiver/hono/index.js +2 -2
- package/dist/receiver/index.cjs.map +1 -1
- package/dist/receiver/index.js +3 -3
- package/dist/receiver/index.js.map +1 -1
- package/dist/receiver/trpc/index.cjs.map +1 -1
- package/dist/receiver/trpc/index.js +1 -1
- package/dist/sse/index.cjs +7 -25
- package/dist/sse/index.cjs.map +1 -1
- package/dist/sse/index.js +8 -26
- package/dist/sse/index.js.map +1 -1
- package/dist/trpc/index.cjs +2 -8
- package/dist/trpc/index.cjs.map +1 -1
- package/dist/trpc/index.js +5 -9
- package/dist/trpc/index.js.map +1 -1
- package/dist/webhook/index.cjs.map +1 -1
- package/dist/webhook/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-5KZI37IT.js.map +0 -1
- package/dist/chunk-EERALS5E.js.map +0 -1
- package/dist/chunk-FSN52BXM.js.map +0 -1
- package/dist/chunk-G5FNOSDX.js.map +0 -1
- package/dist/chunk-MKD3SM3N.js.map +0 -1
- package/dist/chunk-TTVMQYNN.js.map +0 -1
package/dist/hono/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
make_dispatch
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-QSH2Y3YR.js";
|
|
4
4
|
import {
|
|
5
5
|
SseConnectionCounter,
|
|
6
6
|
fireAndForget,
|
|
7
7
|
resolveSseConfig,
|
|
8
8
|
runSseSubscription,
|
|
9
9
|
toApiError
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-YEDAY2PC.js";
|
|
11
11
|
import "../chunk-WZ6QI2OI.js";
|
|
12
|
-
import "../chunk-
|
|
13
|
-
import "../chunk-
|
|
12
|
+
import "../chunk-CVCTDLJA.js";
|
|
13
|
+
import "../chunk-BOJG4555.js";
|
|
14
14
|
import "../chunk-PZ5AY32C.js";
|
|
15
15
|
|
|
16
16
|
// ../../node_modules/.pnpm/@hono+zod-validator@0.9.1_hono@4.13.13_zod@4.6.7/node_modules/@hono/zod-validator/dist/index.mjs
|
package/dist/hono/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../node_modules/.pnpm/@hono+zod-validator@0.9.1_hono@4.13.13_zod@4.6.7/node_modules/@hono/zod-validator/src/index.ts","../../src/hono/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'\nimport { validator } from 'hono/validator'\nimport type { InferInput } from 'hono/validator'\nimport type * as v3 from 'zod/v3'\nimport type { ZodSafeParseResult as v4ZodSafeParseResult } from 'zod/v4'\nimport type * as v4 from 'zod/v4/core'\n\ntype ZodSchema = v3.ZodType | v4.$ZodType\ntype ZodError<T extends ZodSchema> = T extends v4.$ZodType\n ? v4.$ZodError<v4.output<T>>\n : v3.ZodError\ntype ZodSafeParseResult<T, T2, T3 extends ZodSchema> = T3 extends v4.$ZodType\n ? v4ZodSafeParseResult<T>\n : v3.SafeParseReturnType<T, T2>\ntype zInput<T> = T extends v3.ZodType ? v3.input<T> : T extends v4.$ZodType ? v4.input<T> : never\ntype zOutput<T> = T extends v3.ZodType ? v3.output<T> : T extends v4.$ZodType ? v4.output<T> : never\ntype zInfer<T> = T extends v3.ZodType ? v3.infer<T> : T extends v4.$ZodType ? v4.infer<T> : never\n\n// Failure variant of `safeParseAsync`'s return value.\n// Mirrors the runtime body returned by `c.json(result, 400)` on validation failure.\ntype ZodValidatorFailureBody<T extends ZodSchema> = T extends v4.$ZodType\n ? Extract<v4ZodSafeParseResult<v4.output<T>>, { success: false }>\n : T extends v3.ZodType\n ? Extract<v3.SafeParseReturnType<v3.input<T>, v3.output<T>>, { success: false }>\n : never\n\ntype ZodValidatorFailureResponse<T extends ZodSchema> = Response &\n TypedResponse<ZodValidatorFailureBody<T>, 400, 'json'>\n\nexport type Hook<\n T,\n E extends Env,\n P extends string,\n Target extends keyof ValidationTargets = keyof ValidationTargets,\n O = {},\n Schema extends ZodSchema = any,\n> = (\n result: ({ success: true; data: T } | { success: false; error: ZodError<Schema>; data: T }) & {\n target: Target\n },\n c: Context<E, P>\n) => Response | void | TypedResponse<O> | Promise<Response | void | TypedResponse<O>>\n\ntype HasUndefined<T> = undefined extends T ? true : false\n\ntype ExtractValidationResponse<VF> = VF extends (value: any, c: any) => infer R\n ? R extends Promise<infer PR>\n ? PR extends TypedResponse<infer T, infer S, infer F>\n ? Response & TypedResponse<T, S, F>\n : PR extends Response\n ? PR\n : PR extends undefined\n ? never\n : never\n : R extends TypedResponse<infer T, infer S, infer F>\n ? Response & TypedResponse<T, S, F>\n : R extends Response\n ? R\n : R extends undefined\n ? never\n : never\n : never\n\ntype ValidationFunctionOption<T extends ZodSchema, Target extends keyof ValidationTargets> = {\n validationFunction: (\n schema: T,\n value: ValidationTargets[Target]\n ) => ZodSafeParseResult<any, any, T> | Promise<ZodSafeParseResult<any, any, T>>\n}\n\ntype DefaultInput<Target extends keyof ValidationTargets, In, Out> = {\n in: HasUndefined<In> extends true\n ? {\n [K in Target]?: [In] extends [ValidationTargets[K]] ? In : InferInput<In, K>\n }\n : {\n [K in Target]: [In] extends [ValidationTargets[K]] ? In : InferInput<In, K>\n }\n out: { [K in Target]: Out }\n}\n\n// without hook (with or without options)\nfunction zValidatorFunction<\n T extends ZodSchema,\n Target extends keyof ValidationTargets,\n E extends Env,\n P extends string,\n In = zInput<T>,\n Out = zOutput<T>,\n I extends Input = DefaultInput<Target, In, Out>,\n V extends I = I,\n>(\n target: Target,\n schema: T,\n hook?: undefined,\n options?: ValidationFunctionOption<T, Target>\n): MiddlewareHandler<E, P, V, ZodValidatorFailureResponse<T>>\n\n// with hook and options\nfunction zValidatorFunction<\n T extends ZodSchema,\n Target extends keyof ValidationTargets,\n E extends Env,\n P extends string,\n HookFn extends Hook<InferredValue, E, P, Target, {}, T>,\n In = zInput<T>,\n Out = zOutput<T>,\n I extends Input = DefaultInput<Target, In, Out>,\n V extends I = I,\n InferredValue = zInfer<T>,\n>(\n target: Target,\n schema: T,\n hook?: HookFn,\n options?: ValidationFunctionOption<T, Target>\n): MiddlewareHandler<E, P, V, ExtractValidationResponse<HookFn>>\n\n// implementation\nfunction zValidatorFunction<\n T extends ZodSchema,\n Target extends keyof ValidationTargets,\n E extends Env,\n P extends string,\n HookFn extends Hook<InferredValue, E, P, Target, {}, T>,\n In = zInput<T>,\n Out = zOutput<T>,\n I extends Input = DefaultInput<Target, In, Out>,\n V extends I = I,\n InferredValue = zInfer<T>,\n>(\n target: Target,\n schema: T,\n hook?: HookFn,\n options?: ValidationFunctionOption<T, Target>\n):\n | MiddlewareHandler<E, P, V, ZodValidatorFailureResponse<T>>\n | MiddlewareHandler<E, P, V, ExtractValidationResponse<HookFn>> {\n const caseInsensitiveKeymap =\n target === 'header' && ('_def' in schema || '_zod' in schema)\n ? Object.fromEntries(\n // @ts-expect-error the schema is a Zod Schema\n Object.keys('in' in schema ? schema.in.shape : schema.shape).map((key) => [\n key.toLowerCase(),\n key,\n ])\n )\n : undefined\n\n // @ts-expect-error not typed well\n return validator(target, async (value: ValidationTargets[Target], c) => {\n let validatorValue = value\n\n // in case where our `target` === `header`, Hono parses all of the headers into lowercase.\n // this might not match the Zod schema, so we want to make sure that we account for that when parsing the schema.\n if (caseInsensitiveKeymap) {\n validatorValue = Object.fromEntries(\n Object.entries(value).map(([key, value]) => [caseInsensitiveKeymap[key] || key, value])\n )\n }\n\n const result =\n options && options.validationFunction\n ? await options.validationFunction(schema, validatorValue)\n : // @ts-expect-error z4.$ZodType has safeParseAsync\n await schema.safeParseAsync(validatorValue)\n\n if (hook) {\n const hookResult = await hook({ data: validatorValue, ...result, target }, c)\n if (hookResult) {\n if (hookResult instanceof Response) {\n return hookResult\n }\n\n if ('response' in hookResult) {\n return hookResult.response\n }\n }\n }\n\n if (!result.success) {\n return c.json(result, 400)\n }\n\n return result.data as zInfer<T>\n })\n}\n\nexport const zValidator: typeof zValidatorFunction = zValidatorFunction\n","/**\n * @packageDocumentation\n * @module act-http/hono\n *\n * Hono subpath for the auto-generated API epic (#835). Walks the\n * registry of a built `Act` once at function call and emits a Hono\n * app with one `POST /actions/<name>` route per registered action,\n * Zod-validated bodies, and the shared error envelope.\n *\n * Usage:\n *\n * ```ts\n * import { hono } from \"@rotorsoft/act-http/hono\";\n *\n * const api = hono(app, {\n * // the extractor receives `unknown`; it is the Hono Context\n * actor: (c) => ({ id: (c as Context).req.header(\"x-user-id\")!, name: \"...\" }),\n * stream: (action, input, c) => `tenant-${c.req.header(\"x-tenant\")}`,\n * });\n *\n * // Mounts at /api by default — pass `basePath` to override.\n * // POST /api/actions/OpenTicket body: { title } → 200 Snapshot[] | 4xx ApiError\n * ```\n *\n * The route shape matches the flat tRPC sibling at\n * `@rotorsoft/act-http/trpc`: one endpoint per action, no\n * state-name tier. Action names are unique across the registry —\n * the framework enforces no duplicates — so an extra namespace\n * level would be overhead. Queries live in a follow-up; this\n * slice is mutations only.\n *\n * **Actor lives on the context.** The generator wires an internal\n * middleware that runs the host's\n * {@link HonoOptions.actor | actor extractor} once per call and\n * stashes the resolved {@link Actor} under `c.get(\"actor\")`. Every\n * generated route reads from there — auth resolves at one seam,\n * downstream middleware (logging, tracing) sees the same actor.\n * The middleware factory is also exported as {@link authenticated}\n * for hosts that want to compose it into their own Hono chain\n * alongside hand-written routes.\n *\n * Composes the shared utilities at `@rotorsoft/act-http/api`:\n * {@link ActorExtractor} for the auth seam, {@link toApiError} for\n * the uniform error → status / code mapping, {@link withIdempotency}\n * for `Idempotency-Key` dedup. Three transports (tRPC, this one,\n * OpenAPI) ride the same utilities so a client speaking two of\n * them never sees two shapes for the same framework error.\n */\nimport { zValidator } from \"@hono/zod-validator\";\nimport type { Actor } from \"@rotorsoft/act\";\nimport type { IdempotencyStore } from \"@rotorsoft/act-ops/idempotency\";\nimport type { Context, MiddlewareHandler } from \"hono\";\nimport { Hono } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport { type ActSurface, make_dispatch } from \"../api/dispatch.js\";\nimport {\n type ActorExtractor,\n type ApiError,\n fireAndForget,\n resolveSseConfig,\n runSseSubscription,\n SseConnectionCounter,\n type SseOptions,\n toApiError,\n} from \"../api/index.js\";\n\n/**\n * Hono context variables this subpath contributes. The generic on\n * the returned middleware threads it through so routes downstream\n * of {@link authenticated} see `c.get(\"actor\")` typed without a\n * manual cast.\n */\nexport type ActMiddlewareVariables = {\n actor: Actor;\n};\n\n/**\n * Per-call options for {@link hono}. The host supplies the four\n * seams the package can't make decisions on:\n *\n * - `actor` — auth resolver. Receives the Hono context, returns the\n * `Actor` that flows onto `c.get(\"actor\")` for every generated\n * route (via an internal {@link authenticated}).\n * - `stream` — stream-id resolver. Receives the action name, the\n * validated body, and the Hono context; returns the target\n * stream. Singleton aggregates return a constant; per-tenant\n * aggregates pull from headers, path, or body.\n * - `expectedVersion` (optional) — optimistic-concurrency resolver.\n * Hosts typically read it from an `If-Match` header on the\n * request, or from the client's last-known snapshot. Returning\n * `undefined` skips the check for that call — handy when only\n * some actions are concurrency-sensitive.\n * - `idempotency` (optional) — when set, the route honors\n * `Idempotency-Key` via {@link withIdempotency}. The host\n * supplies the `IdempotencyStore`; `keyFrom` defaults to reading\n * the `Idempotency-Key` header. A duplicate of a key that already\n * succeeded gets `409 Conflict` — the contract intentionally doesn't\n * cache the original handler's result. A duplicate that arrives\n * while the original is still running gets a retryable\n * `503` + `Retry-After` instead, because the original may yet fail\n * and release the key, and a client told \"already used\" would never\n * retry.\n * - `basePath` (optional, default `\"/api\"`) — Hono basePath the\n * routes mount under.\n */\nexport type HonoOptions = {\n readonly actor: ActorExtractor;\n readonly stream: (\n action_name: string,\n input: unknown,\n c: Context\n ) => string | Promise<string>;\n readonly expectedVersion?: (\n action_name: string,\n input: unknown,\n c: Context\n ) => number | undefined | Promise<number | undefined>;\n readonly idempotency?: {\n readonly store: IdempotencyStore;\n readonly keyFrom?: (c: Context) => string | undefined;\n };\n /**\n * Optional SSE wiring. When set, the generator emits one\n * `GET <basePath>/sse/<stateName>?stream=<streamId>` per unique\n * state name in the registry. The endpoint runs the host\n * {@link actor} extractor, looks up the streamId from\n * `?stream=...`, opens a `text/event-stream`, yields the cached\n * state (if any), and forwards every patch published to the\n * shared {@link SseOptions.channel}. A heartbeat keeps proxies\n * from idling the connection out; the per-process connection cap\n * returns `503 Service Unavailable` (with `Retry-After: 1`) when\n * full so operators never see silent stalls.\n *\n * Off by default — most APIs don't expose live state to every\n * client, and opening one is widening both the auth and cost\n * surface.\n */\n readonly sse?: SseOptions;\n readonly basePath?: string;\n};\n\n/**\n * Build a Hono middleware that runs an {@link ActorExtractor} once\n * per call and stashes the resolved {@link Actor} under\n * `c.set(\"actor\", ...)` so downstream routes can read it via\n * `c.get(\"actor\")`. The generator at {@link hono} uses this\n * internally; it's also exported so hosts that want to compose\n * their own route chain (logging, tracing, custom auth flavors)\n * can wire it directly:\n *\n * ```ts\n * import { Hono } from \"hono\";\n * import { authenticated, type ActMiddlewareVariables } from \"@rotorsoft/act-http/hono\";\n *\n * const api = new Hono<{ Variables: ActMiddlewareVariables }>();\n * api.use(\"*\", authenticated((c) => resolveUserFromJwt(c)));\n * api.get(\"/me\", (c) => c.json(c.get(\"actor\"))); // typed\n * ```\n *\n * Errors thrown by the extractor surface as `401 Unauthorized` with\n * the shared {@link ApiError} envelope.\n */\nexport function authenticated(\n extractor: ActorExtractor\n): MiddlewareHandler<{ Variables: ActMiddlewareVariables }> {\n return async (c, next) => {\n try {\n const actor = await extractor(c);\n c.set(\"actor\", actor);\n await next();\n } catch (err) {\n const body: ApiError = {\n error: \"Unauthorized\",\n detail: err instanceof Error ? err.message : undefined,\n code: \"UNAUTHORIZED\",\n };\n return c.json(body, 401);\n }\n };\n}\n\nconst default_key_from = (c: Context): string | undefined =>\n c.req.header(\"idempotency-key\");\n\n/**\n * Build a typed Hono REST surface from a built `Act` instance.\n *\n * Walks `app.registry.actions` once and emits one\n * `POST /actions/<actionName>` per action under the configured\n * `basePath` (default `/api`). Each route:\n *\n * 1. Runs the internal {@link authenticated} middleware —\n * `options.actor` resolves the {@link Actor} once and stashes\n * it under `c.get(\"actor\")`.\n * 2. Validates the JSON body against the action's Zod schema via\n * `@hono/zod-validator`. Failures short-circuit with `422`.\n * 3. Resolves the target stream via\n * `options.stream(name, input, c)`.\n * 4. (Optionally) resolves `expectedVersion` for optimistic\n * concurrency.\n * 5. (Optionally) claims the `Idempotency-Key` via\n * {@link withIdempotency} — responds `409` on a duplicate of a\n * committed key, `503` on a duplicate of one still in flight.\n * 6. Calls\n * `app.do(name, { stream, actor, expectedVersion? }, input)`.\n * 7. Maps any thrown framework error onto the shared\n * {@link ApiError} envelope via {@link toApiError}, returning\n * the conventional HTTP status (`409` / `412` for concurrency,\n * `422` for validation, `400` for non-retryable, etc.).\n *\n * @param app A built `Act` orchestrator.\n * @param options Auth, stream, expected-version, idempotency, and\n * base-path seams.\n * @returns A Hono app covering every registered action under\n * `<basePath>/actions/<name>`.\n */\nexport function hono<TApp extends ActSurface = ActSurface>(\n app: TApp,\n options: HonoOptions\n): Hono<{ Variables: ActMiddlewareVariables }> {\n const base_path = options.basePath ?? \"/api\";\n const api = new Hono<{ Variables: ActMiddlewareVariables }>().basePath(\n base_path\n );\n api.use(\"*\", authenticated(options.actor));\n\n const key_from = options.idempotency?.keyFrom ?? default_key_from;\n const dispatch = make_dispatch<Context>(\n app,\n options,\n options.idempotency?.store\n );\n\n // Wire SSE subscriptions before the mutation routes so the\n // `/sse/*` paths sit alongside `/actions/*`. The cap counter is\n // shared by every state-name's GET route on this generator\n // instance so a noisy room on one state doesn't get a free pass\n // on another.\n if (options.sse) {\n const sse_config = resolveSseConfig(options.sse);\n const sse_counter = new SseConnectionCounter(sse_config.maxConnections);\n const state_names = new Set(\n Object.values(app.registry.actions).map((a) => a.name)\n );\n for (const state_name of state_names) {\n api.get(`/sse/${state_name}`, async (c) => {\n const stream_id = c.req.query(\"stream\");\n if (!stream_id) {\n const body: ApiError = {\n error: \"BadRequest\",\n detail: \"stream query parameter is required\",\n code: \"BAD_REQUEST\",\n };\n return c.json(body, 400);\n }\n if (!sse_counter.acquire()) {\n c.header(\"Retry-After\", \"1\");\n const body: ApiError = {\n error: \"ServiceUnavailable\",\n detail: \"max concurrent SSE connections reached\",\n code: \"SSE_BUSY\",\n };\n return c.json(body, 503);\n }\n // The route already acquired the slot above so streamSSE\n // could return `503` before writing headers if the cap was\n // full. The shared loop runs without accounting; the route\n // releases the slot in `finally`.\n const controller = new AbortController();\n return streamSSE(c, async (sse_stream) => {\n sse_stream.onAbort(() => controller.abort());\n const heartbeat = setInterval(() => {\n fireAndForget(() =>\n sse_stream.writeSSE({ event: \"ping\", data: \"\" })\n );\n }, sse_config.heartbeatMs);\n try {\n for await (const frame of runSseSubscription(\n sse_config.channel,\n stream_id,\n undefined,\n controller.signal,\n { maxPending: sse_config.maxPendingPerConnection }\n )) {\n await sse_stream.writeSSE({\n event: frame.kind,\n data: JSON.stringify(frame.data),\n });\n }\n } finally {\n clearInterval(heartbeat);\n sse_counter.release();\n }\n });\n });\n }\n }\n\n for (const [action_name, state] of Object.entries(app.registry.actions)) {\n const schema = state.actions[action_name];\n api.post(\n `/actions/${action_name}`,\n // Custom hook funnels body-schema failures through the same\n // ApiError envelope + 422 status an in-`do` ValidationError maps\n // to (ERROR_MAP.ValidationError). Without it, @hono/zod-validator\n // short-circuits with its own 400 + raw body — two statuses and\n // two shapes for the same \"malformed input\" failure class on one\n // endpoint. The OpenAPI doc only documents the 422/ApiError path.\n // A JSON body that doesn't parse gets the same envelope: the\n // validator would throw its own plain-text 400 before the hook runs.\n async (c, next) => {\n if (is_json(c.req.header(\"content-type\")))\n try {\n await c.req.json();\n } catch {\n return c.json(\n validation_error(\"Malformed JSON in request body\"),\n 422\n );\n }\n await next();\n },\n zValidator(\"json\", schema as never, (result, c) => {\n if (!result.success)\n // `result.error` is a ZodError; the schema's `never` typing\n // erases its shape, so read `.message` through a narrow cast.\n return c.json(\n validation_error((result.error as { message?: string }).message),\n 422\n );\n }),\n async (c) => {\n try {\n // The resolvers get the validated value; `app.do` gets the raw\n // body, because it parses again itself and a second parse of the\n // validator's output would run every `.transform()` twice. The\n // json validator only reads a JSON body (no or non-JSON\n // Content-Type validates `{}`), so take the raw value from the same\n // place it did: re-reading the body regardless would throw on an\n // empty request and hand `app.do` a body the resolvers never saw\n // (#1777). Hono caches the body, so reading it again is free.\n const input = c.req.valid(\"json\" as never) as unknown;\n const raw: unknown = is_json(c.req.header(\"content-type\"))\n ? await c.req.json()\n : {};\n const outcome = await dispatch(\n action_name,\n input,\n raw,\n c,\n c.get(\"actor\"),\n () => key_from(c)\n );\n if (outcome.kind === \"done\") return c.json(outcome.result);\n if (outcome.kind === \"missing-key\") {\n const body: ApiError = {\n error: \"BadRequest\",\n detail: \"Idempotency-Key required\",\n code: \"BAD_REQUEST\",\n };\n return c.json(body, 400);\n }\n if (outcome.kind === \"in-flight\") {\n const body: ApiError = {\n error: \"ServiceUnavailable\",\n detail:\n \"Idempotency-Key in flight; the original request has not finished, retry later\",\n code: \"IN_FLIGHT\",\n };\n c.header(\"Retry-After\", \"1\");\n return c.json(body, 503);\n }\n const body: ApiError = {\n error: \"Conflict\",\n detail: \"Idempotency-Key already used; original result not cached\",\n code: \"CONFLICT\",\n };\n return c.json(body, 409);\n } catch (err) {\n const { status, body } = toApiError(err);\n // Hono types narrow `status` to its known status-number union;\n // `toApiError` returns a numeric status from a closed set that\n // matches but TypeScript can't prove it through the union.\n return c.json(body, status as 400 | 409 | 410 | 412 | 422 | 500);\n }\n }\n );\n }\n\n return api;\n}\n\n/**\n * Hono's json validator reads the body only for a JSON Content-Type\n * (`application/json`, or a `+json` suffix, optionally with parameters);\n * anything else validates `{}`. Same test, so the route reads the raw body\n * exactly when the validator did.\n */\nconst JSON_CONTENT_TYPE =\n /^application\\/([a-z-.]+\\+)?json(;\\s*[a-zA-Z0-9-]+=([^;]+))*$/i;\nfunction validation_error(detail: string | undefined): ApiError {\n return { error: \"ValidationError\", detail, code: \"VALIDATION\" };\n}\n\nfunction is_json(content_type: string | undefined): boolean {\n return !!content_type && JSON_CONTENT_TYPE.test(content_type);\n}\n"],"mappings":"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","names":["value","body"]}
|
|
1
|
+
{"version":3,"sources":["../../../../node_modules/.pnpm/@hono+zod-validator@0.9.1_hono@4.13.13_zod@4.6.7/node_modules/@hono/zod-validator/src/index.ts","../../src/hono/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'\nimport { validator } from 'hono/validator'\nimport type { InferInput } from 'hono/validator'\nimport type * as v3 from 'zod/v3'\nimport type { ZodSafeParseResult as v4ZodSafeParseResult } from 'zod/v4'\nimport type * as v4 from 'zod/v4/core'\n\ntype ZodSchema = v3.ZodType | v4.$ZodType\ntype ZodError<T extends ZodSchema> = T extends v4.$ZodType\n ? v4.$ZodError<v4.output<T>>\n : v3.ZodError\ntype ZodSafeParseResult<T, T2, T3 extends ZodSchema> = T3 extends v4.$ZodType\n ? v4ZodSafeParseResult<T>\n : v3.SafeParseReturnType<T, T2>\ntype zInput<T> = T extends v3.ZodType ? v3.input<T> : T extends v4.$ZodType ? v4.input<T> : never\ntype zOutput<T> = T extends v3.ZodType ? v3.output<T> : T extends v4.$ZodType ? v4.output<T> : never\ntype zInfer<T> = T extends v3.ZodType ? v3.infer<T> : T extends v4.$ZodType ? v4.infer<T> : never\n\n// Failure variant of `safeParseAsync`'s return value.\n// Mirrors the runtime body returned by `c.json(result, 400)` on validation failure.\ntype ZodValidatorFailureBody<T extends ZodSchema> = T extends v4.$ZodType\n ? Extract<v4ZodSafeParseResult<v4.output<T>>, { success: false }>\n : T extends v3.ZodType\n ? Extract<v3.SafeParseReturnType<v3.input<T>, v3.output<T>>, { success: false }>\n : never\n\ntype ZodValidatorFailureResponse<T extends ZodSchema> = Response &\n TypedResponse<ZodValidatorFailureBody<T>, 400, 'json'>\n\nexport type Hook<\n T,\n E extends Env,\n P extends string,\n Target extends keyof ValidationTargets = keyof ValidationTargets,\n O = {},\n Schema extends ZodSchema = any,\n> = (\n result: ({ success: true; data: T } | { success: false; error: ZodError<Schema>; data: T }) & {\n target: Target\n },\n c: Context<E, P>\n) => Response | void | TypedResponse<O> | Promise<Response | void | TypedResponse<O>>\n\ntype HasUndefined<T> = undefined extends T ? true : false\n\ntype ExtractValidationResponse<VF> = VF extends (value: any, c: any) => infer R\n ? R extends Promise<infer PR>\n ? PR extends TypedResponse<infer T, infer S, infer F>\n ? Response & TypedResponse<T, S, F>\n : PR extends Response\n ? PR\n : PR extends undefined\n ? never\n : never\n : R extends TypedResponse<infer T, infer S, infer F>\n ? Response & TypedResponse<T, S, F>\n : R extends Response\n ? R\n : R extends undefined\n ? never\n : never\n : never\n\ntype ValidationFunctionOption<T extends ZodSchema, Target extends keyof ValidationTargets> = {\n validationFunction: (\n schema: T,\n value: ValidationTargets[Target]\n ) => ZodSafeParseResult<any, any, T> | Promise<ZodSafeParseResult<any, any, T>>\n}\n\ntype DefaultInput<Target extends keyof ValidationTargets, In, Out> = {\n in: HasUndefined<In> extends true\n ? {\n [K in Target]?: [In] extends [ValidationTargets[K]] ? In : InferInput<In, K>\n }\n : {\n [K in Target]: [In] extends [ValidationTargets[K]] ? In : InferInput<In, K>\n }\n out: { [K in Target]: Out }\n}\n\n// without hook (with or without options)\nfunction zValidatorFunction<\n T extends ZodSchema,\n Target extends keyof ValidationTargets,\n E extends Env,\n P extends string,\n In = zInput<T>,\n Out = zOutput<T>,\n I extends Input = DefaultInput<Target, In, Out>,\n V extends I = I,\n>(\n target: Target,\n schema: T,\n hook?: undefined,\n options?: ValidationFunctionOption<T, Target>\n): MiddlewareHandler<E, P, V, ZodValidatorFailureResponse<T>>\n\n// with hook and options\nfunction zValidatorFunction<\n T extends ZodSchema,\n Target extends keyof ValidationTargets,\n E extends Env,\n P extends string,\n HookFn extends Hook<InferredValue, E, P, Target, {}, T>,\n In = zInput<T>,\n Out = zOutput<T>,\n I extends Input = DefaultInput<Target, In, Out>,\n V extends I = I,\n InferredValue = zInfer<T>,\n>(\n target: Target,\n schema: T,\n hook?: HookFn,\n options?: ValidationFunctionOption<T, Target>\n): MiddlewareHandler<E, P, V, ExtractValidationResponse<HookFn>>\n\n// implementation\nfunction zValidatorFunction<\n T extends ZodSchema,\n Target extends keyof ValidationTargets,\n E extends Env,\n P extends string,\n HookFn extends Hook<InferredValue, E, P, Target, {}, T>,\n In = zInput<T>,\n Out = zOutput<T>,\n I extends Input = DefaultInput<Target, In, Out>,\n V extends I = I,\n InferredValue = zInfer<T>,\n>(\n target: Target,\n schema: T,\n hook?: HookFn,\n options?: ValidationFunctionOption<T, Target>\n):\n | MiddlewareHandler<E, P, V, ZodValidatorFailureResponse<T>>\n | MiddlewareHandler<E, P, V, ExtractValidationResponse<HookFn>> {\n const caseInsensitiveKeymap =\n target === 'header' && ('_def' in schema || '_zod' in schema)\n ? Object.fromEntries(\n // @ts-expect-error the schema is a Zod Schema\n Object.keys('in' in schema ? schema.in.shape : schema.shape).map((key) => [\n key.toLowerCase(),\n key,\n ])\n )\n : undefined\n\n // @ts-expect-error not typed well\n return validator(target, async (value: ValidationTargets[Target], c) => {\n let validatorValue = value\n\n // in case where our `target` === `header`, Hono parses all of the headers into lowercase.\n // this might not match the Zod schema, so we want to make sure that we account for that when parsing the schema.\n if (caseInsensitiveKeymap) {\n validatorValue = Object.fromEntries(\n Object.entries(value).map(([key, value]) => [caseInsensitiveKeymap[key] || key, value])\n )\n }\n\n const result =\n options && options.validationFunction\n ? await options.validationFunction(schema, validatorValue)\n : // @ts-expect-error z4.$ZodType has safeParseAsync\n await schema.safeParseAsync(validatorValue)\n\n if (hook) {\n const hookResult = await hook({ data: validatorValue, ...result, target }, c)\n if (hookResult) {\n if (hookResult instanceof Response) {\n return hookResult\n }\n\n if ('response' in hookResult) {\n return hookResult.response\n }\n }\n }\n\n if (!result.success) {\n return c.json(result, 400)\n }\n\n return result.data as zInfer<T>\n })\n}\n\nexport const zValidator: typeof zValidatorFunction = zValidatorFunction\n","/**\n * @packageDocumentation\n * @module act-http/hono\n *\n * Hono subpath of the auto-generated API. Walks the\n * registry of a built `Act` once at function call and emits a Hono\n * app with one `POST /actions/<name>` route per registered action,\n * Zod-validated bodies, and the shared error envelope.\n *\n * Usage:\n *\n * ```ts\n * import { hono } from \"@rotorsoft/act-http/hono\";\n *\n * const api = hono(app, {\n * // the extractor receives `unknown`; it is the Hono Context\n * actor: (c) => ({ id: (c as Context).req.header(\"x-user-id\")!, name: \"...\" }),\n * stream: (action, input, c) => `tenant-${c.req.header(\"x-tenant\")}`,\n * });\n *\n * // Mounts at /api by default — pass `basePath` to override.\n * // POST /api/actions/OpenTicket body: { title } → 200 Snapshot[] | 4xx ApiError\n * ```\n *\n * The route shape matches the flat tRPC sibling at\n * `@rotorsoft/act-http/trpc`: one endpoint per action, no\n * state-name tier. Action names are unique across the registry —\n * the framework enforces no duplicates — so an extra namespace\n * level would be overhead. Queries live in a follow-up; this\n * slice is mutations only.\n *\n * **Actor lives on the context.** The generator wires an internal\n * middleware that runs the host's\n * {@link HonoOptions.actor | actor extractor} once per call and\n * stashes the resolved {@link Actor} under `c.get(\"actor\")`. Every\n * generated route reads from there — auth resolves at one seam,\n * downstream middleware (logging, tracing) sees the same actor.\n * The middleware factory is also exported as {@link authenticated}\n * for hosts that want to compose it into their own Hono chain\n * alongside hand-written routes.\n *\n * Composes the shared utilities at `@rotorsoft/act-http/api`:\n * {@link ActorExtractor} for the auth seam, {@link toApiError} for\n * the uniform error → status / code mapping, {@link withIdempotency}\n * for `Idempotency-Key` dedup. Three transports (tRPC, this one,\n * OpenAPI) ride the same utilities so a client speaking two of\n * them never sees two shapes for the same framework error.\n */\nimport { zValidator } from \"@hono/zod-validator\";\nimport type { Actor } from \"@rotorsoft/act\";\nimport type { IdempotencyStore } from \"@rotorsoft/act-ops/idempotency\";\nimport type { Context, MiddlewareHandler } from \"hono\";\nimport { Hono } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport type { MergePath, TypedResponse } from \"hono/types\";\nimport type { JSONParsed } from \"hono/utils/types\";\nimport {\n type ActionResult,\n type ActSurface,\n type AppActions,\n type AppStateNames,\n make_dispatch,\n type ResolverArgs,\n} from \"../api/dispatch.js\";\nimport {\n type ActorExtractor,\n type ApiError,\n fireAndForget,\n resolveSseConfig,\n runSseSubscription,\n SseConnectionCounter,\n type SseOptions,\n toApiError,\n} from \"../api/index.js\";\n\n/**\n * Hono context variables this subpath contributes. The generic on\n * the returned middleware threads it through so routes downstream\n * of {@link authenticated} see `c.get(\"actor\")` typed without a\n * manual cast.\n */\nexport type ActMiddlewareVariables = {\n actor: Actor;\n};\n\n/**\n * Per-call options for {@link hono}. The host supplies the four\n * seams the package can't make decisions on:\n *\n * - `actor` — auth resolver. Receives the Hono context, returns the\n * `Actor` that flows onto `c.get(\"actor\")` for every generated\n * route (via an internal {@link authenticated}).\n * - `stream` — stream-id resolver. Receives the action name, the\n * validated body, and the Hono context; returns the target\n * stream. Singleton aggregates return a constant; per-tenant\n * aggregates pull from headers, path, or body.\n * - `expectedVersion` (optional) — optimistic-concurrency resolver.\n * Hosts typically read it from an `If-Match` header on the\n * request, or from the client's last-known snapshot. Returning\n * `undefined` leaves the call to the version the action loads; a\n * number pins it, and a pinned conflict is never retried.\n * - `idempotency` (optional) — when set, the route honors\n * `Idempotency-Key` via {@link withIdempotency}. The host\n * supplies the `IdempotencyStore`; `keyFrom` defaults to reading\n * the `Idempotency-Key` header. A duplicate of a key that already\n * succeeded gets `409 Conflict` — the contract intentionally doesn't\n * cache the original handler's result. A duplicate that arrives\n * while the original is still running gets a retryable\n * `503` + `Retry-After` instead, because the original may yet fail\n * and release the key, and a client told \"already used\" would never\n * retry.\n * - `basePath` (optional, default `\"/api\"`) — Hono basePath the\n * routes mount under.\n */\nexport type HonoOptions<TApp = ActSurface> = {\n readonly actor: ActorExtractor<Context>;\n readonly stream: (\n ...args: ResolverArgs<TApp, Context>\n ) => string | Promise<string>;\n readonly expectedVersion?: (\n ...args: ResolverArgs<TApp, Context>\n ) => number | undefined | Promise<number | undefined>;\n readonly idempotency?: {\n readonly store: IdempotencyStore;\n readonly keyFrom?: (c: Context) => string | undefined;\n };\n /**\n * Optional SSE wiring. When set, the generator emits one\n * `GET <basePath>/sse/<stateName>?stream=<streamId>` per unique\n * state name in the registry. The endpoint runs the host\n * {@link actor} extractor, looks up the streamId from\n * `?stream=...`, opens a `text/event-stream`, yields the cached\n * state (if any), and forwards every patch published to the\n * shared {@link SseOptions.channel}. A heartbeat keeps proxies\n * from idling the connection out; the per-process connection cap\n * returns `503 Service Unavailable` (with `Retry-After: 1`) when\n * full so operators never see silent stalls.\n *\n * Off by default — most APIs don't expose live state to every\n * client, and opening one is widening both the auth and cost\n * surface.\n */\n readonly sse?: SseOptions;\n readonly basePath?: string;\n};\n\n/**\n * Build a Hono middleware that runs an {@link ActorExtractor} once\n * per call and stashes the resolved {@link Actor} under\n * `c.set(\"actor\", ...)` so downstream routes can read it via\n * `c.get(\"actor\")`. The generator at {@link hono} uses this\n * internally; it's also exported so hosts that want to compose\n * their own route chain (logging, tracing, custom auth flavors)\n * can wire it directly:\n *\n * ```ts\n * import { Hono } from \"hono\";\n * import { authenticated, type ActMiddlewareVariables } from \"@rotorsoft/act-http/hono\";\n *\n * const api = new Hono<{ Variables: ActMiddlewareVariables }>();\n * api.use(\"*\", authenticated((c) => resolveUserFromJwt(c)));\n * api.get(\"/me\", (c) => c.json(c.get(\"actor\"))); // typed\n * ```\n *\n * Errors thrown by the extractor surface as `401 Unauthorized` with\n * the shared {@link ApiError} envelope. Mounted on a route\n * (`api.get(\"/me\", authenticated(extract), handler)`), the `401` is in\n * the route's type, so `hc` and `testClient` see it.\n */\nexport function authenticated(\n extractor: ActorExtractor<Context>\n): MiddlewareHandler<\n { Variables: ActMiddlewareVariables },\n string,\n {},\n Response & TypedResponse<JSONParsed<ApiError>, 401, \"json\">\n> {\n return async (c, next) => {\n try {\n const actor = await extractor(c);\n c.set(\"actor\", actor);\n await next();\n } catch (err) {\n const body: ApiError = {\n error: \"Unauthorized\",\n detail: err instanceof Error ? err.message : undefined,\n code: \"UNAUTHORIZED\",\n };\n return c.json(body, 401);\n }\n };\n}\n\nconst default_key_from = (c: Context): string | undefined =>\n c.req.header(\"idempotency-key\");\n\n/**\n * Build a typed Hono REST surface from a built `Act` instance.\n *\n * Walks `app.registry.actions` once and emits one\n * `POST /actions/<actionName>` per action under the configured\n * `basePath` (default `/api`). Each route:\n *\n * 1. Runs the internal {@link authenticated} middleware —\n * `options.actor` resolves the {@link Actor} once and stashes\n * it under `c.get(\"actor\")`.\n * 2. Validates the JSON body against the action's Zod schema via\n * `@hono/zod-validator`. Failures short-circuit with `422`.\n * 3. Resolves the target stream via\n * `options.stream(name, input, c)`.\n * 4. (Optionally) resolves `expectedVersion` for optimistic\n * concurrency.\n * 5. (Optionally) claims the `Idempotency-Key` via\n * {@link withIdempotency} — responds `409` on a duplicate of a\n * committed key, `503` on a duplicate of one still in flight.\n * 6. Calls\n * `app.do(name, { stream, actor, expectedVersion? }, input)`.\n * 7. Maps any thrown framework error onto the shared\n * {@link ApiError} envelope via {@link toApiError}, returning\n * the conventional HTTP status (`409` / `412` for concurrency,\n * `422` for validation, `400` for non-retryable, etc.).\n *\n * @param app A built `Act` orchestrator.\n * @param options Auth, stream, expected-version, idempotency, and\n * base-path seams.\n * @returns A Hono app covering every registered action under\n * `<basePath>/actions/<name>`.\n */\nexport function hono<\n TApp extends ActSurface = ActSurface,\n TBase extends string = \"/api\",\n>(\n app: TApp,\n options: HonoOptions<TApp> & {\n readonly basePath?: TBase;\n readonly sse: NonNullable<HonoOptions[\"sse\"]>;\n }\n): Hono<\n { Variables: ActMiddlewareVariables },\n ActionRoutes<TApp, TBase> & SseRoutes<TApp, TBase>,\n TBase\n>;\nexport function hono<\n TApp extends ActSurface = ActSurface,\n TBase extends string = \"/api\",\n>(\n app: TApp,\n options: HonoOptions<TApp> & { readonly basePath?: TBase }\n): Hono<\n { Variables: ActMiddlewareVariables },\n ActionRoutes<TApp, TBase>,\n TBase\n>;\nexport function hono<TApp extends ActSurface>(\n app: TApp,\n options: HonoOptions\n): unknown {\n const base_path = options.basePath ?? \"/api\";\n const api = new Hono<{ Variables: ActMiddlewareVariables }>().basePath(\n base_path\n );\n api.use(\"*\", authenticated(options.actor));\n\n const key_from = options.idempotency?.keyFrom ?? default_key_from;\n const dispatch = make_dispatch<Context>(\n app,\n options,\n options.idempotency?.store\n );\n\n // Wire SSE subscriptions before the mutation routes so the\n // `/sse/*` paths sit alongside `/actions/*`. The cap counter is\n // shared by every state-name's GET route on this generator\n // instance so a noisy room on one state doesn't get a free pass\n // on another.\n if (options.sse) {\n const sse_config = resolveSseConfig(options.sse);\n const sse_counter = new SseConnectionCounter(sse_config.maxConnections);\n const state_names = new Set(\n Object.values(app.registry.actions).map((a) => a.name)\n );\n for (const state_name of state_names) {\n api.get(`/sse/${state_name}`, async (c) => {\n const stream_id = c.req.query(\"stream\");\n if (!stream_id) {\n const body: ApiError = {\n error: \"BadRequest\",\n detail: \"stream query parameter is required\",\n code: \"BAD_REQUEST\",\n };\n return c.json(body, 400);\n }\n if (!sse_counter.acquire()) {\n c.header(\"Retry-After\", \"1\");\n const body: ApiError = {\n error: \"ServiceUnavailable\",\n detail: \"max concurrent SSE connections reached\",\n code: \"SSE_BUSY\",\n };\n return c.json(body, 503);\n }\n // The route already acquired the slot above so streamSSE\n // could return `503` before writing headers if the cap was\n // full. The shared loop runs without accounting; the route\n // releases the slot in `finally`.\n const controller = new AbortController();\n return streamSSE(c, async (sse_stream) => {\n sse_stream.onAbort(() => controller.abort());\n const heartbeat = setInterval(() => {\n fireAndForget(() =>\n sse_stream.writeSSE({ event: \"ping\", data: \"\" })\n );\n }, sse_config.heartbeatMs);\n try {\n for await (const frame of runSseSubscription(\n sse_config.channel,\n stream_id,\n undefined,\n controller.signal,\n { maxPending: sse_config.maxPendingPerConnection }\n )) {\n await sse_stream.writeSSE({\n event: frame.kind,\n data: JSON.stringify(frame.data),\n });\n }\n } finally {\n clearInterval(heartbeat);\n sse_counter.release();\n }\n });\n });\n }\n }\n\n for (const [action_name, state] of Object.entries(app.registry.actions)) {\n const schema = state.actions[action_name];\n api.post(\n `/actions/${action_name}`,\n // Custom hook funnels body-schema failures through the same\n // ApiError envelope + 422 status an in-`do` ValidationError maps\n // to (ERROR_MAP.ValidationError). Without it, @hono/zod-validator\n // short-circuits with its own 400 + raw body — two statuses and\n // two shapes for the same \"malformed input\" failure class on one\n // endpoint. The OpenAPI doc only documents the 422/ApiError path.\n // A JSON body that doesn't parse gets the same envelope: the\n // validator would throw its own plain-text 400 before the hook runs.\n async (c, next) => {\n if (is_json(c.req.header(\"content-type\")))\n try {\n await c.req.json();\n } catch {\n return c.json(\n validation_error(\"Malformed JSON in request body\"),\n 422\n );\n }\n await next();\n },\n zValidator(\"json\", schema as never, (result, c) => {\n if (!result.success)\n // `result.error` is a ZodError; the schema's `never` typing\n // erases its shape, so read `.message` through a narrow cast.\n return c.json(\n validation_error((result.error as { message?: string }).message),\n 422\n );\n }),\n async (c) => {\n try {\n // The resolvers get the validated value; `app.do` gets the raw\n // body, because it parses again itself and a second parse of the\n // validator's output would run every `.transform()` twice. The\n // json validator only reads a JSON body (no or non-JSON\n // Content-Type validates `{}`), so take the raw value from the same\n // place it did: re-reading the body regardless would throw on an\n // empty request and hand `app.do` a body the resolvers never saw.\n // Hono caches the body, so reading it again is free.\n const input = c.req.valid(\"json\" as never) as unknown;\n const raw: unknown = is_json(c.req.header(\"content-type\"))\n ? await c.req.json()\n : {};\n const outcome = await dispatch(\n action_name,\n input,\n raw,\n c,\n c.get(\"actor\"),\n () => key_from(c)\n );\n if (outcome.kind === \"done\") return c.json(outcome.result);\n if (outcome.kind === \"missing-key\") {\n const body: ApiError = {\n error: \"BadRequest\",\n detail: \"Idempotency-Key required\",\n code: \"BAD_REQUEST\",\n };\n return c.json(body, 400);\n }\n if (outcome.kind === \"in-flight\") {\n const body: ApiError = {\n error: \"ServiceUnavailable\",\n detail:\n \"Idempotency-Key in flight; the original request has not finished, retry later\",\n code: \"IN_FLIGHT\",\n };\n c.header(\"Retry-After\", \"1\");\n return c.json(body, 503);\n }\n const body: ApiError = {\n error: \"Conflict\",\n detail: \"Idempotency-Key already used; original result not cached\",\n code: \"CONFLICT\",\n };\n return c.json(body, 409);\n } catch (err) {\n const { status, body } = toApiError(err);\n // Hono types narrow `status` to its known status-number union;\n // `toApiError` returns a numeric status from a closed set that\n // matches but TypeScript can't prove it through the union.\n return c.json(body, status as 400 | 409 | 410 | 412 | 422 | 500);\n }\n }\n );\n }\n\n // Built in a loop over the registry, so its type is the overloads'.\n return api;\n}\n\n/**\n * One `GET <basePath>/sse/<state>?stream=<id>` per state of `TApp`, with\n * `sse`: an event stream on `200`, {@link ApiError} on an error status.\n */\nexport type SseRoutes<TApp, TBase extends string> = {\n [N in AppStateNames<TApp> as MergePath<TBase, `/sse/${N}`>]: {\n $get:\n | {\n input: { query: { stream: string } };\n output: {};\n outputFormat: \"body\";\n status: 200;\n }\n | {\n input: { query: { stream: string } };\n output: ApiError;\n outputFormat: \"json\";\n status: 400 | 401 | 503;\n };\n };\n};\n\n/** Every status a generated action route answers with the error envelope. */\ntype ErrorStatus = 400 | 401 | 409 | 410 | 412 | 422 | 500 | 503;\n\n/**\n * The routes {@link hono} builds, one `POST <basePath>/actions/<name>` per\n * action of `TApp`: the action's payload as the JSON body, the snapshots\n * `app.do` returns (as JSON) on `200`, and {@link ApiError} on an error\n * status. `hc<typeof api>` and `testClient(api)` read it.\n */\nexport type ActionRoutes<TApp, TBase extends string> = {\n [K in keyof AppActions<TApp> & string as MergePath<TBase, `/actions/${K}`>]: {\n $post:\n | {\n input: { json: AppActions<TApp>[K] };\n output: JSONParsed<ActionResult<TApp, K>>;\n outputFormat: \"json\";\n status: 200;\n }\n | {\n input: { json: AppActions<TApp>[K] };\n output: ApiError;\n outputFormat: \"json\";\n status: ErrorStatus;\n };\n };\n};\n\n/**\n * Hono's json validator reads the body only for a JSON Content-Type\n * (`application/json`, or a `+json` suffix, optionally with parameters);\n * anything else validates `{}`. Same test, so the route reads the raw body\n * exactly when the validator did.\n */\nconst JSON_CONTENT_TYPE =\n /^application\\/([a-z-.]+\\+)?json(;\\s*[a-zA-Z0-9-]+=([^;]+))*$/i;\nfunction validation_error(detail: string | undefined): ApiError {\n return { error: \"ValidationError\", detail, code: \"VALIDATION\" };\n}\n\nfunction is_json(content_type: string | undefined): boolean {\n return !!content_type && JSON_CONTENT_TYPE.test(content_type);\n}\n"],"mappings":"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","names":["value","body"]}
|
package/dist/openapi/index.cjs
CHANGED
|
@@ -19753,22 +19753,53 @@ var API_ERROR_SCHEMA = {
|
|
|
19753
19753
|
},
|
|
19754
19754
|
required: ["error"]
|
|
19755
19755
|
};
|
|
19756
|
-
|
|
19757
|
-
|
|
19758
|
-
|
|
19759
|
-
|
|
19760
|
-
|
|
19761
|
-
|
|
19762
|
-
|
|
19763
|
-
|
|
19764
|
-
|
|
19765
|
-
|
|
19766
|
-
|
|
19767
|
-
|
|
19768
|
-
|
|
19769
|
-
|
|
19770
|
-
|
|
19771
|
-
}
|
|
19756
|
+
function snapshot_array(state, event) {
|
|
19757
|
+
return {
|
|
19758
|
+
type: "array",
|
|
19759
|
+
items: {
|
|
19760
|
+
type: "object",
|
|
19761
|
+
properties: {
|
|
19762
|
+
state,
|
|
19763
|
+
event,
|
|
19764
|
+
patch: patch_of(state),
|
|
19765
|
+
id: { type: "integer" },
|
|
19766
|
+
version: { type: "integer" },
|
|
19767
|
+
patches: { type: "integer" },
|
|
19768
|
+
snaps: { type: "integer" },
|
|
19769
|
+
cache_hit: { type: "boolean" },
|
|
19770
|
+
replayed: { type: "integer" }
|
|
19771
|
+
},
|
|
19772
|
+
required: [
|
|
19773
|
+
"state",
|
|
19774
|
+
"id",
|
|
19775
|
+
"version",
|
|
19776
|
+
"patches",
|
|
19777
|
+
"snaps",
|
|
19778
|
+
"cache_hit",
|
|
19779
|
+
"replayed"
|
|
19780
|
+
]
|
|
19781
|
+
}
|
|
19782
|
+
};
|
|
19783
|
+
}
|
|
19784
|
+
var SNAPSHOT_ARRAY_SCHEMA = snapshot_array(
|
|
19785
|
+
{ type: "object" },
|
|
19786
|
+
{ type: "object" }
|
|
19787
|
+
);
|
|
19788
|
+
function patch_of(schema) {
|
|
19789
|
+
const properties = schema.properties;
|
|
19790
|
+
if (!properties) return schema;
|
|
19791
|
+
const { required: _, ...rest } = schema;
|
|
19792
|
+
return {
|
|
19793
|
+
...rest,
|
|
19794
|
+
properties: Object.fromEntries(
|
|
19795
|
+
Object.entries(properties).map(([key, value]) => [
|
|
19796
|
+
key,
|
|
19797
|
+
{ anyOf: [patch_of(value), { type: "null" }] }
|
|
19798
|
+
])
|
|
19799
|
+
)
|
|
19800
|
+
};
|
|
19801
|
+
}
|
|
19802
|
+
var PII_MARKERS = { type: "string", enum: [import_act.REDACTED, import_act.SHREDDED] };
|
|
19772
19803
|
var API_ERROR_RESPONSE = {
|
|
19773
19804
|
description: "Error envelope per @rotorsoft/act-http/api's toApiError.",
|
|
19774
19805
|
content: {
|
|
@@ -19826,7 +19857,51 @@ function mark_sensitive_fields(body_schema, zod_schema) {
|
|
|
19826
19857
|
}
|
|
19827
19858
|
return { ...body_schema, properties: next_properties };
|
|
19828
19859
|
}
|
|
19829
|
-
function
|
|
19860
|
+
function json_schema(schema, io) {
|
|
19861
|
+
return strip_json_schema_meta(
|
|
19862
|
+
external_exports.toJSONSchema(schema, { unrepresentable: "any", io })
|
|
19863
|
+
);
|
|
19864
|
+
}
|
|
19865
|
+
function allow_markers(schema, fields) {
|
|
19866
|
+
const properties = schema.properties;
|
|
19867
|
+
const present = fields.filter((f) => properties?.[f]);
|
|
19868
|
+
if (!properties || present.length === 0) return schema;
|
|
19869
|
+
const next = { ...properties };
|
|
19870
|
+
for (const f of present) next[f] = { anyOf: [properties[f], PII_MARKERS] };
|
|
19871
|
+
return { ...schema, properties: next };
|
|
19872
|
+
}
|
|
19873
|
+
function state_snapshots(state, events) {
|
|
19874
|
+
const sensitive = /* @__PURE__ */ new Set();
|
|
19875
|
+
const variants = Object.entries(events).map(([name, data]) => {
|
|
19876
|
+
const fields = (0, import_act.pii_fields)(data);
|
|
19877
|
+
for (const f of fields) sensitive.add(f);
|
|
19878
|
+
return {
|
|
19879
|
+
type: "object",
|
|
19880
|
+
properties: {
|
|
19881
|
+
id: { type: "integer" },
|
|
19882
|
+
stream: { type: "string" },
|
|
19883
|
+
version: { type: "integer" },
|
|
19884
|
+
created: { type: "string", format: "date-time" },
|
|
19885
|
+
name: { const: name },
|
|
19886
|
+
data: allow_markers(json_schema(data, "output"), fields),
|
|
19887
|
+
meta: {
|
|
19888
|
+
type: "object",
|
|
19889
|
+
properties: {
|
|
19890
|
+
correlation: { type: "string" },
|
|
19891
|
+
causation: { type: "object" }
|
|
19892
|
+
},
|
|
19893
|
+
required: ["correlation", "causation"]
|
|
19894
|
+
}
|
|
19895
|
+
},
|
|
19896
|
+
required: ["id", "stream", "version", "created", "name", "data", "meta"]
|
|
19897
|
+
};
|
|
19898
|
+
});
|
|
19899
|
+
return snapshot_array(
|
|
19900
|
+
allow_markers(json_schema(state, "output"), [...sensitive]),
|
|
19901
|
+
{ oneOf: variants }
|
|
19902
|
+
);
|
|
19903
|
+
}
|
|
19904
|
+
function build_operation(action_name, body_schema, response_ref, options) {
|
|
19830
19905
|
const parameters = [];
|
|
19831
19906
|
if (options.idempotency) {
|
|
19832
19907
|
parameters.push({
|
|
@@ -19834,7 +19909,7 @@ function build_operation(action_name, body_schema, options) {
|
|
|
19834
19909
|
in: "header",
|
|
19835
19910
|
// Required: the generated route fail-closes with 400 when idempotency is
|
|
19836
19911
|
// enabled and the header is absent, so the doc must not advertise it as
|
|
19837
|
-
// optional
|
|
19912
|
+
// optional.
|
|
19838
19913
|
required: true,
|
|
19839
19914
|
description: "Required idempotency token. A duplicate of a request that already succeeded produces a 409 Conflict (the first result is not cached on the server). A duplicate that arrives while the original is still running produces a retryable 503 with Retry-After, because the original may yet fail.",
|
|
19840
19915
|
schema: { type: "string" }
|
|
@@ -19865,7 +19940,7 @@ function build_operation(action_name, body_schema, options) {
|
|
|
19865
19940
|
description: "Action committed; returns the snapshot array.",
|
|
19866
19941
|
content: {
|
|
19867
19942
|
"application/json": {
|
|
19868
|
-
schema: { $ref:
|
|
19943
|
+
schema: { $ref: response_ref }
|
|
19869
19944
|
}
|
|
19870
19945
|
}
|
|
19871
19946
|
},
|
|
@@ -19883,27 +19958,19 @@ function openapi(app, options) {
|
|
|
19883
19958
|
validate_options(options);
|
|
19884
19959
|
const base_path = options.basePath ?? DEFAULT_BASE_PATH;
|
|
19885
19960
|
const paths = {};
|
|
19961
|
+
const state_schemas = {};
|
|
19886
19962
|
for (const [action_name, state] of Object.entries(app.registry.actions)) {
|
|
19887
19963
|
const zod_schema = state.actions[action_name];
|
|
19888
19964
|
const body_schema = mark_sensitive_fields(
|
|
19889
|
-
|
|
19890
|
-
// `unrepresentable: "any"` emits an open (`{}`) schema for a Zod type
|
|
19891
|
-
// with no JSON-Schema form (z.date/z.bigint/z.map/z.set/...) instead of
|
|
19892
|
-
// Zod 4's default `"throw"`, which would abort the WHOLE document over
|
|
19893
|
-
// one such field — while hono() serves the same action fine (#1328).
|
|
19894
|
-
// `io: "input"` describes what a client must SEND: a `.default()`
|
|
19895
|
-
// field is optional, extra keys are not rejected (Zod strips them),
|
|
19896
|
-
// and a `.pipe()`/`.transform()` documents its input type. Zod's
|
|
19897
|
-
// default, `"output"`, describes the parsed result instead.
|
|
19898
|
-
external_exports.toJSONSchema(zod_schema, {
|
|
19899
|
-
unrepresentable: "any",
|
|
19900
|
-
io: "input"
|
|
19901
|
-
})
|
|
19902
|
-
),
|
|
19965
|
+
json_schema(zod_schema, "input"),
|
|
19903
19966
|
zod_schema
|
|
19904
19967
|
);
|
|
19968
|
+
const snapshots = `${state.name}Snapshots`;
|
|
19969
|
+
if (state.state && state.events && !state_schemas[snapshots])
|
|
19970
|
+
state_schemas[snapshots] = state_snapshots(state.state, state.events);
|
|
19971
|
+
const response_ref = state_schemas[snapshots] ? `#/components/schemas/${snapshots}` : "#/components/schemas/SnapshotArray";
|
|
19905
19972
|
paths[`${base_path}/actions/${action_name}`] = {
|
|
19906
|
-
post: build_operation(action_name, body_schema, options)
|
|
19973
|
+
post: build_operation(action_name, body_schema, response_ref, options)
|
|
19907
19974
|
};
|
|
19908
19975
|
}
|
|
19909
19976
|
return {
|
|
@@ -19914,7 +19981,8 @@ function openapi(app, options) {
|
|
|
19914
19981
|
components: {
|
|
19915
19982
|
schemas: {
|
|
19916
19983
|
ApiError: API_ERROR_SCHEMA,
|
|
19917
|
-
SnapshotArray: SNAPSHOT_ARRAY_SCHEMA
|
|
19984
|
+
SnapshotArray: SNAPSHOT_ARRAY_SCHEMA,
|
|
19985
|
+
...state_schemas
|
|
19918
19986
|
},
|
|
19919
19987
|
responses: {
|
|
19920
19988
|
ApiError: API_ERROR_RESPONSE
|