@rotorsoft/act-http 1.4.0 → 1.5.0
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 +33 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/openapi/index.d.ts +191 -0
- package/dist/@types/openapi/index.d.ts.map +1 -0
- package/dist/api/index.js +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/hono/index.js +1 -0
- package/dist/hono/index.js.map +1 -1
- package/dist/openapi/index.cjs +14682 -0
- package/dist/openapi/index.cjs.map +1 -0
- package/dist/openapi/index.js +14659 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/receiver/express/index.js +1 -0
- package/dist/receiver/express/index.js.map +1 -1
- package/dist/receiver/fastify/index.js +1 -0
- package/dist/receiver/fastify/index.js.map +1 -1
- package/dist/receiver/hono/index.js +1 -0
- package/dist/receiver/index.js +1 -0
- package/dist/receiver/index.js.map +1 -1
- package/dist/receiver/trpc/index.js +1 -0
- package/dist/receiver/trpc/index.js.map +1 -1
- package/dist/sse/index.js +2 -0
- package/dist/sse/index.js.map +1 -1
- package/dist/trpc/index.js +1 -0
- package/dist/trpc/index.js.map +1 -1
- package/dist/webhook/index.js +2 -0
- package/dist/webhook/index.js.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* @module act-http/openapi
|
|
4
|
+
*
|
|
5
|
+
* OpenAPI subpath for the auto-generated API epic (#835). Walks the
|
|
6
|
+
* registry of a built `Act` once at function call and returns a
|
|
7
|
+
* valid OpenAPI 3.1 document describing the same action surface the
|
|
8
|
+
* Hono sibling at `@rotorsoft/act-http/hono` serves. The doc is
|
|
9
|
+
* suitable for cross-language client codegen (Go, Python, Swift,
|
|
10
|
+
* Kotlin, .NET), for serving over `/openapi.json` alongside a live
|
|
11
|
+
* API, and for the broader OpenAPI tooling ecosystem (Postman,
|
|
12
|
+
* Swagger UI, Redoc, schemathesis, API gateways).
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { openapi } from "@rotorsoft/act-http/openapi";
|
|
18
|
+
*
|
|
19
|
+
* const doc = openapi(app, {
|
|
20
|
+
* info: { title: "Wolfdesk API", version: "1.0.0" },
|
|
21
|
+
* servers: [{ url: "https://api.example.com" }],
|
|
22
|
+
* });
|
|
23
|
+
* // doc: OpenAPIDocument — valid OpenAPI 3.1 object
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* **Pure data emit.** No runtime dep on Hono or tRPC. The subpath is
|
|
27
|
+
* consumable standalone for hosts that serve their API some other
|
|
28
|
+
* way (Fastify, Lambda, edge) or that only want the doc for client
|
|
29
|
+
* codegen. The emit walks the registry once at function call; cost
|
|
30
|
+
* is linear in `N actions × M Zod fields each`, single-digit
|
|
31
|
+
* milliseconds for a 100-endpoint registry.
|
|
32
|
+
*
|
|
33
|
+
* **Determinism.** Output is stable across runs given the same
|
|
34
|
+
* registry — entries land in `Object.entries(app.registry.actions)`
|
|
35
|
+
* iteration order, schemas come from the action's Zod definition,
|
|
36
|
+
* and the rest of the doc is fixed content. CI can snapshot the
|
|
37
|
+
* result to catch unintended API surface changes.
|
|
38
|
+
*
|
|
39
|
+
* The same shared utilities at `@rotorsoft/act-http/api` underwrite
|
|
40
|
+
* cross-transport consistency: the `ApiError` envelope is referenced
|
|
41
|
+
* once from `components.schemas`, every error response points at it,
|
|
42
|
+
* and the same shape ships from the tRPC and Hono siblings at
|
|
43
|
+
* runtime. A client speaking the doc and the live REST API sees one
|
|
44
|
+
* envelope per framework error.
|
|
45
|
+
*
|
|
46
|
+
* **The doc describes the Hono surface, not tRPC.** tRPC's URL
|
|
47
|
+
* shape (`POST /trpc/<procedure>`, JSON-RPC-style body framing,
|
|
48
|
+
* batching) doesn't model cleanly as OpenAPI operations — and tRPC
|
|
49
|
+
* consumers already share types directly via `typeof router`, so
|
|
50
|
+
* OpenAPI buys them nothing. The path shape this emitter produces
|
|
51
|
+
* (`POST <basePath>/actions/<name>`) matches the
|
|
52
|
+
* `@rotorsoft/act-http/hono` sibling exactly: same default
|
|
53
|
+
* `basePath`, same request/response shapes, same envelope. If the
|
|
54
|
+
* operator overrides `basePath` on the Hono adapter, they pass the
|
|
55
|
+
* same override here so the doc keeps matching the live routes by
|
|
56
|
+
* construction. tRPC and Hono can run side-by-side at different
|
|
57
|
+
* mount points on the same Act instance; the OpenAPI doc covers
|
|
58
|
+
* the REST half only.
|
|
59
|
+
*/
|
|
60
|
+
import type { Act, Schema, Schemas } from "@rotorsoft/act";
|
|
61
|
+
/**
|
|
62
|
+
* Minimal OpenAPI 3.1 document type. The emitter targets the 3.1
|
|
63
|
+
* spec; the type is intentionally a structural subset rather than
|
|
64
|
+
* the full surface from `openapi-types` so callers can post-process
|
|
65
|
+
* without fighting the type system. Cast to a fuller type from
|
|
66
|
+
* `openapi-types` when downstream consumers need it.
|
|
67
|
+
*/
|
|
68
|
+
export type OpenAPIDocument = {
|
|
69
|
+
readonly openapi: "3.1.0";
|
|
70
|
+
readonly info: OpenAPIInfo;
|
|
71
|
+
readonly servers?: ReadonlyArray<OpenAPIServer>;
|
|
72
|
+
readonly paths: Record<string, OpenAPIPathItem>;
|
|
73
|
+
readonly components: OpenAPIComponents;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Per-call options for {@link openapi}. The host supplies the
|
|
77
|
+
* document-shape fields the registry can't derive (title, version,
|
|
78
|
+
* servers) and toggles for the cross-cutting headers that the live
|
|
79
|
+
* REST API may accept.
|
|
80
|
+
*
|
|
81
|
+
* - `info` — required. `title` and `version` must be non-empty;
|
|
82
|
+
* `info` may carry any other top-level OpenAPI info fields.
|
|
83
|
+
* - `servers` — optional. Each entry's `url` may contain
|
|
84
|
+
* `{variable}` template syntax; bare URLs are validated through
|
|
85
|
+
* `URL`'s parser and reject malformed inputs.
|
|
86
|
+
* - `basePath` — optional, default `/api`. Mirrors the Hono
|
|
87
|
+
* sibling's default so the doc describes the same paths the
|
|
88
|
+
* generated REST surface serves.
|
|
89
|
+
* - `idempotency` — optional, default `false`. When `true`, every
|
|
90
|
+
* mutation operation documents an optional `Idempotency-Key`
|
|
91
|
+
* request header.
|
|
92
|
+
* - `expectedVersion` — optional, default `false`. When `true`,
|
|
93
|
+
* every mutation operation documents an optional `If-Match`
|
|
94
|
+
* request header carrying the expected stream version.
|
|
95
|
+
*/
|
|
96
|
+
export type OpenAPIOptions = {
|
|
97
|
+
readonly info: OpenAPIInfo;
|
|
98
|
+
readonly servers?: ReadonlyArray<OpenAPIServer>;
|
|
99
|
+
readonly basePath?: string;
|
|
100
|
+
readonly idempotency?: boolean;
|
|
101
|
+
readonly expectedVersion?: boolean;
|
|
102
|
+
};
|
|
103
|
+
export type OpenAPIInfo = {
|
|
104
|
+
readonly title: string;
|
|
105
|
+
readonly version: string;
|
|
106
|
+
readonly description?: string;
|
|
107
|
+
readonly summary?: string;
|
|
108
|
+
readonly termsOfService?: string;
|
|
109
|
+
readonly contact?: {
|
|
110
|
+
readonly name?: string;
|
|
111
|
+
readonly url?: string;
|
|
112
|
+
readonly email?: string;
|
|
113
|
+
};
|
|
114
|
+
readonly license?: {
|
|
115
|
+
readonly name: string;
|
|
116
|
+
readonly identifier?: string;
|
|
117
|
+
readonly url?: string;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
export type OpenAPIServer = {
|
|
121
|
+
readonly url: string;
|
|
122
|
+
readonly description?: string;
|
|
123
|
+
readonly variables?: Record<string, {
|
|
124
|
+
readonly default: string;
|
|
125
|
+
readonly enum?: ReadonlyArray<string>;
|
|
126
|
+
readonly description?: string;
|
|
127
|
+
}>;
|
|
128
|
+
};
|
|
129
|
+
export type OpenAPIPathItem = {
|
|
130
|
+
readonly post?: OpenAPIOperation;
|
|
131
|
+
};
|
|
132
|
+
export type OpenAPIOperation = {
|
|
133
|
+
readonly operationId: string;
|
|
134
|
+
readonly summary: string;
|
|
135
|
+
readonly tags: ReadonlyArray<string>;
|
|
136
|
+
readonly parameters?: ReadonlyArray<OpenAPIParameter>;
|
|
137
|
+
readonly requestBody?: OpenAPIRequestBody;
|
|
138
|
+
readonly responses: Record<string, OpenAPIResponse>;
|
|
139
|
+
};
|
|
140
|
+
export type OpenAPIParameter = {
|
|
141
|
+
readonly name: string;
|
|
142
|
+
readonly in: "header" | "query" | "path" | "cookie";
|
|
143
|
+
readonly required?: boolean;
|
|
144
|
+
readonly description?: string;
|
|
145
|
+
readonly schema?: Record<string, unknown>;
|
|
146
|
+
};
|
|
147
|
+
export type OpenAPIRequestBody = {
|
|
148
|
+
readonly required?: boolean;
|
|
149
|
+
readonly content: Record<string, {
|
|
150
|
+
readonly schema: Record<string, unknown>;
|
|
151
|
+
}>;
|
|
152
|
+
};
|
|
153
|
+
export type OpenAPIResponse = {
|
|
154
|
+
readonly description: string;
|
|
155
|
+
readonly content?: Record<string, {
|
|
156
|
+
readonly schema: Record<string, unknown>;
|
|
157
|
+
}>;
|
|
158
|
+
} | {
|
|
159
|
+
readonly $ref: string;
|
|
160
|
+
};
|
|
161
|
+
export type OpenAPIComponents = {
|
|
162
|
+
readonly schemas: Record<string, Record<string, unknown>>;
|
|
163
|
+
readonly responses: Record<string, OpenAPIResponse>;
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Build an OpenAPI 3.1 document describing a built `Act` instance's
|
|
167
|
+
* action surface.
|
|
168
|
+
*
|
|
169
|
+
* Walks `app.registry.actions` once and emits a `POST
|
|
170
|
+
* <basePath>/actions/<actionName>` operation per action, deriving
|
|
171
|
+
* the request-body schema from each action's Zod definition via
|
|
172
|
+
* `z.toJSONSchema` (Zod 4's native JSON Schema 2020-12 emitter,
|
|
173
|
+
* which is the OpenAPI 3.1 schema dialect — no conversion layer
|
|
174
|
+
* needed). Error responses reference the shared
|
|
175
|
+
* `#/components/responses/ApiError` so a single envelope shape
|
|
176
|
+
* covers every error path.
|
|
177
|
+
*
|
|
178
|
+
* @param app A built `Act` orchestrator. Required.
|
|
179
|
+
* @param options Document-shape fields (`info.title`, `info.version`,
|
|
180
|
+
* servers, base path) and toggles for the cross-cutting headers
|
|
181
|
+
* the live REST API may accept (`Idempotency-Key`, `If-Match`).
|
|
182
|
+
* @returns A valid OpenAPI 3.1 document object — serve as
|
|
183
|
+
* `/openapi.json`, ship to a CDN, write to disk during CI, or
|
|
184
|
+
* pipe to a codegen tool.
|
|
185
|
+
*
|
|
186
|
+
* @throws Error if `info.title` / `info.version` is missing or
|
|
187
|
+
* empty, or if any `servers[].url` fails URL parsing after
|
|
188
|
+
* `{variable}` substitution.
|
|
189
|
+
*/
|
|
190
|
+
export declare function openapi(app: Act<Schemas, Schemas, Schemas, Record<string, Schema>>, options: OpenAPIOptions): OpenAPIDocument;
|
|
191
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/openapi/index.ts"],"names":[],"mappings":"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"}
|
package/dist/api/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/hono/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
toApiError,
|
|
3
3
|
withIdempotency
|
|
4
4
|
} from "../chunk-Z6FVXDDU.js";
|
|
5
|
+
import "../chunk-PZ5AY32C.js";
|
|
5
6
|
|
|
6
7
|
// ../../node_modules/.pnpm/@hono+zod-validator@0.8.0_hono@4.12.23_zod@4.4.3/node_modules/@hono/zod-validator/dist/index.js
|
|
7
8
|
import { validator } from "hono/validator";
|
package/dist/hono/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../node_modules/.pnpm/@hono+zod-validator@0.8.0_hono@4.12.23_zod@4.4.3/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 * as v3 from 'zod/v3'\nimport type { ZodSafeParseResult as v4ZodSafeParseResult } from 'zod/v4'\nimport type * as v4 from 'zod/v4/core'\nimport type { InferInput } from './utils'\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 // @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 ((target === 'header' && '_def' in schema) || (target === 'header' && '_zod' in schema)) {\n // create an object that maps lowercase schema keys to lowercase\n // @ts-expect-error the schema is a Zod Schema\n const schemaKeys = Object.keys('in' in schema ? schema.in.shape : schema.shape)\n const caseInsensitiveKeymap = Object.fromEntries(\n schemaKeys.map((key) => [key.toLowerCase(), key])\n )\n\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 * actor: (c) => ({ id: c.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 { Act, Actor, Schema, Schemas } from \"@rotorsoft/act\";\nimport type { IdempotencyStore } from \"@rotorsoft/act-ops/idempotency\";\nimport type { Context, MiddlewareHandler } from \"hono\";\nimport { Hono } from \"hono\";\nimport {\n type ActorExtractor,\n type ApiError,\n toApiError,\n withIdempotency,\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. On a duplicate claim, the route\n * responds `409 Conflict` — the contract intentionally doesn't\n * cache the original handler's result, matching the\n * receiver-side \"ack the duplicate\" semantics.\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 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 duplicate.\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 app: Act<Schemas, Schemas, Schemas, Record<string, Schema>>,\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\n for (const [action_name, state] of Object.entries(app.registry.actions)) {\n const schema = state.actions[action_name] as Schema;\n api.post(\n `/actions/${action_name}`,\n zValidator(\"json\", schema as never),\n async (c) => {\n try {\n const input = c.req.valid(\"json\" as never) as unknown;\n const actor = c.get(\"actor\");\n const stream = await options.stream(action_name, input, c);\n const expected_version = options.expectedVersion\n ? await options.expectedVersion(action_name, input, c)\n : undefined;\n const target =\n expected_version === undefined\n ? { stream, actor }\n : { stream, actor, expectedVersion: expected_version };\n\n if (options.idempotency) {\n const key = key_from(c);\n if (!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 const outcome = await withIdempotency(\n options.idempotency.store,\n key,\n () =>\n app.do(action_name as never, target as never, input as never)\n );\n if (outcome.deduped) {\n const body: ApiError = {\n error: \"Conflict\",\n detail:\n \"Idempotency-Key already used; original result not cached\",\n code: \"CONFLICT\",\n };\n return c.json(body, 409);\n }\n return c.json(outcome.result);\n }\n\n const snapshots = await app.do(\n action_name as never,\n target as never,\n input as never\n );\n return c.json(snapshots);\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"],"mappings":"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","names":["value","zValidator: typeof zValidatorFunction"]}
|
|
1
|
+
{"version":3,"sources":["../../../../node_modules/.pnpm/@hono+zod-validator@0.8.0_hono@4.12.23_zod@4.4.3/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 * as v3 from 'zod/v3'\nimport type { ZodSafeParseResult as v4ZodSafeParseResult } from 'zod/v4'\nimport type * as v4 from 'zod/v4/core'\nimport type { InferInput } from './utils'\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 // @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 ((target === 'header' && '_def' in schema) || (target === 'header' && '_zod' in schema)) {\n // create an object that maps lowercase schema keys to lowercase\n // @ts-expect-error the schema is a Zod Schema\n const schemaKeys = Object.keys('in' in schema ? schema.in.shape : schema.shape)\n const caseInsensitiveKeymap = Object.fromEntries(\n schemaKeys.map((key) => [key.toLowerCase(), key])\n )\n\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 * actor: (c) => ({ id: c.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 { Act, Actor, Schema, Schemas } from \"@rotorsoft/act\";\nimport type { IdempotencyStore } from \"@rotorsoft/act-ops/idempotency\";\nimport type { Context, MiddlewareHandler } from \"hono\";\nimport { Hono } from \"hono\";\nimport {\n type ActorExtractor,\n type ApiError,\n toApiError,\n withIdempotency,\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. On a duplicate claim, the route\n * responds `409 Conflict` — the contract intentionally doesn't\n * cache the original handler's result, matching the\n * receiver-side \"ack the duplicate\" semantics.\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 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 duplicate.\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 app: Act<Schemas, Schemas, Schemas, Record<string, Schema>>,\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\n for (const [action_name, state] of Object.entries(app.registry.actions)) {\n const schema = state.actions[action_name] as Schema;\n api.post(\n `/actions/${action_name}`,\n zValidator(\"json\", schema as never),\n async (c) => {\n try {\n const input = c.req.valid(\"json\" as never) as unknown;\n const actor = c.get(\"actor\");\n const stream = await options.stream(action_name, input, c);\n const expected_version = options.expectedVersion\n ? await options.expectedVersion(action_name, input, c)\n : undefined;\n const target =\n expected_version === undefined\n ? { stream, actor }\n : { stream, actor, expectedVersion: expected_version };\n\n if (options.idempotency) {\n const key = key_from(c);\n if (!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 const outcome = await withIdempotency(\n options.idempotency.store,\n key,\n () =>\n app.do(action_name as never, target as never, input as never)\n );\n if (outcome.deduped) {\n const body: ApiError = {\n error: \"Conflict\",\n detail:\n \"Idempotency-Key already used; original result not cached\",\n code: \"CONFLICT\",\n };\n return c.json(body, 409);\n }\n return c.json(outcome.result);\n }\n\n const snapshots = await app.do(\n action_name as never,\n target as never,\n input as never\n );\n return c.json(snapshots);\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"],"mappings":"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","names":["value","zValidator: typeof zValidatorFunction"]}
|