@rotorsoft/act-http 1.10.17 → 1.10.18
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 +17 -161
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/trpc/index.d.ts.map +1 -1
- package/dist/trpc/index.cjs.map +1 -1
- package/dist/trpc/index.js.map +1 -1
- package/package.json +5 -8
package/dist/trpc/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/trpc/index.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act-http/trpc\n *\n * tRPC subpath for the auto-generated API epic (#835). Walks the\n * registry of a built `Act` once at function call and emits a typed\n * router that exposes every registered action as a flat `mutation`,\n * matching the shape hand-written tRPC routers in the Act ecosystem\n * already use.\n *\n * Usage:\n *\n * ```ts\n * import { trpc } from \"@rotorsoft/act-http/trpc\";\n *\n * const router = trpc(app, {\n * actor: (ctx) => ({ id: ctx.user.id, name: ctx.user.name }),\n * stream: (action, input, ctx) => input.stream ?? ctx.tenant,\n * });\n *\n * // Client sees:\n * // appRouter.PressKey.mutate({ key: \"5\" })\n * // appRouter.OpenTicket.mutate({ title: \"...\" })\n * ```\n *\n * The route shape is flat: one procedure per registered action at\n * the top level. Action names are the unique identifiers across\n * the registry (the framework already enforces no duplicates), so\n * an extra namespace tier would be overhead in the common case.\n * Pure mutations for this slice; query/projection wiring lives in\n * a follow-up. See `libs/act-http/README.md` for the full surface\n * walkthrough.\n *\n * **Actor lives on the context.** The generator wires an internal\n * tRPC middleware that runs the host's\n * {@link TrpcOptions.actor | actor extractor} once per call and\n * injects the resolved {@link Actor} onto the downstream context as\n * `ctx.actor`. Every generated mutation reads from there — auth\n * resolves at the procedure boundary, not inside each action body,\n * and any composed middleware (logging, tracing) sees the same\n * `ctx.actor`. The middleware factory is also exported as\n * {@link authenticated} for hosts that need to compose it into\n * their own procedure chain.\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 (this one, Hono,\n * OpenAPI) ride the same utilities so a client talking to two\n * transports never sees two error shapes for the same framework\n * error.\n */\nimport { type Actor, type Target, validate } from \"@rotorsoft/act\";\nimport type { IdempotencyStore } from \"@rotorsoft/act-ops/idempotency\";\nimport {\n type AnyTRPCMiddlewareFunction,\n type AnyTRPCMutationProcedure,\n type AnyTRPCRootTypes,\n initTRPC,\n type TRPCBuiltRouter,\n TRPCError,\n} from \"@trpc/server\";\nimport { z } from \"zod\";\nimport { track_in_flight } from \"../api/in-flight.js\";\nimport {\n type ActorExtractor,\n resolveSseConfig,\n runSseSubscription,\n SseConnectionCounter,\n type SseOptions,\n toApiError,\n withIdempotency,\n} from \"../api/index.js\";\n\n/**\n * Per-call options for {@link trpc}. The host supplies the three\n * seams the package can't make decisions on:\n *\n * - `actor` — auth resolver. Receives the tRPC context, returns the\n * `Actor` that flows onto `ctx.actor` for every generated\n * procedure (via an internal {@link authenticated}).\n * - `stream` — stream-id resolver. Receives the action name, the\n * validated action input, and the original tRPC context; returns\n * the target stream. Singleton aggregates return a constant;\n * per-tenant aggregates pull from input or context.\n * - `expectedVersion` (optional) — optimistic-concurrency resolver.\n * When set, the procedure threads the resolved value through\n * `target.expectedVersion` so `app.do` refuses to commit if the\n * stream has moved. Hosts typically read it from an `If-Match`\n * header on the underlying HTTP request, or pull it from the\n * client's last-known snapshot. Returning `undefined` skips the\n * check for that call — handy when only some actions are\n * concurrency-sensitive.\n * - `idempotency` (optional) — when set, the procedure honors\n * `Idempotency-Key` via the shared\n * {@link withIdempotency} helper. The host supplies the\n * `IdempotencyStore` and a `keyFrom` extractor that reads the key\n * out of the tRPC context (typically a header). A duplicate of a key\n * that already succeeded throws a `CONFLICT` `TRPCError` — the\n * receiver-side convention of \"ack the duplicate\" doesn't carry\n * over because the contract intentionally does not cache the\n * original handler's result. A duplicate that arrives while the\n * original is still running throws a retryable\n * `SERVICE_UNAVAILABLE` (503) instead: the original may yet fail and\n * release the key, and a client told \"already used\" would never retry.\n *\n * @template Ctx The host's tRPC context shape. Flows end-to-end —\n * procedures see this exact context type (plus `actor`) at\n * runtime.\n */\nexport type TrpcOptions<Ctx> = {\n readonly actor: ActorExtractor;\n readonly stream: (\n action_name: string,\n input: unknown,\n ctx: Ctx\n ) => string | Promise<string>;\n readonly expectedVersion?: (\n action_name: string,\n input: unknown,\n ctx: Ctx\n ) => number | undefined | Promise<number | undefined>;\n readonly idempotency?: {\n readonly store: IdempotencyStore;\n readonly keyFrom: (ctx: Ctx) => string | undefined;\n };\n /**\n * Optional SSE wiring. When set, the generator emits one\n * subscription per unique state name in the registry, grouped on\n * the returned router as\n * `router.subscribe.<stateName>.useSubscription({ stream })`.\n * Each subscription yields `{ kind: \"state\", data }` once with\n * the cached state (when present) and then `{ kind: \"patch\",\n * data }` for every patch the shared\n * {@link SseOptions.channel} publishes. The per-process\n * connection cap surfaces as a `TOO_MANY_REQUESTS` `TRPCError`.\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};\n\n/**\n * Build a tRPC middleware that runs an {@link ActorExtractor} once\n * per call and forwards the resolved {@link Actor} on the\n * downstream context as `ctx.actor`. The generator at\n * {@link trpc} uses this internally; it's also exported so hosts\n * that want to compose their own procedure chain (logging,\n * tracing, custom auth flavors) can wire it directly:\n *\n * ```ts\n * import { initTRPC } from \"@trpc/server\";\n * import { authenticated } from \"@rotorsoft/act-http/trpc\";\n *\n * type Ctx = { user?: { id: string; name: string } };\n * const t = initTRPC.context<Ctx>().create();\n *\n * const authed = t.procedure.use(\n * authenticated((ctx) => {\n * if (!ctx.user) throw new Error(\"not authenticated\");\n * return { id: ctx.user.id, name: ctx.user.name };\n * })\n * );\n *\n * // Inside any procedure built off `authed`, `ctx.actor: Actor`.\n * ```\n *\n * The middleware is unaware of the host's `t` instance — it returns\n * a structurally-typed function that any tRPC `procedure.use(...)`\n * accepts. Errors thrown by the extractor surface unchanged\n * (typically wrap them as `TRPCError({ code: \"UNAUTHORIZED\" })`\n * in the extractor body).\n */\nexport function authenticated(\n extractor: ActorExtractor\n): AnyTRPCMiddlewareFunction {\n // tRPC's middleware function type lives behind\n // `unstable-core-do-not-import`; we satisfy it structurally with the\n // shape `procedure.use(...)` actually requires (an `{ ctx, next }`\n // bag whose `next` callback is invoked once with the augmented ctx)\n // and cast the structurally-typed result up to the named export. The\n // type cast carries no `any`; both sides are structurally compatible\n // at the call site.\n const middleware = async function actor_middleware({\n ctx,\n next,\n }: {\n ctx: object;\n next: (opts: { ctx: object }) => Promise<unknown>;\n }) {\n const actor = await extractor(ctx);\n return next({ ctx: { ...ctx, actor } });\n };\n return middleware as unknown as AnyTRPCMiddlewareFunction;\n}\n\n/**\n * Map a thrown framework error onto a `TRPCError`. Known errors get\n * their conventional tRPC code (`ConcurrencyError` → `CONFLICT`,\n * `ValidationError` → `UNPROCESSABLE_CONTENT`, etc.); unknown throws\n * surface as `INTERNAL_SERVER_ERROR`. Every transport in the\n * act-http epic passes through the same {@link toApiError} table,\n * so a client talking to both this subpath and the REST sibling\n * sees one envelope per framework error.\n *\n * @internal\n */\nfunction to_trpc_error(err: unknown): TRPCError {\n const { status, body } = toApiError(err);\n const code = status_to_trpc_code(status);\n return new TRPCError({\n code,\n message: body.detail ?? body.error,\n cause: err instanceof Error ? err : undefined,\n });\n}\n\n/**\n * Minimal status → tRPC code map. Only the statuses\n * {@link ERROR_MAP} produces (plus 500) appear here; anything else\n * collapses to `INTERNAL_SERVER_ERROR`.\n *\n * @internal\n */\nfunction status_to_trpc_code(status: number): TRPCError[\"code\"] {\n switch (status) {\n case 400:\n return \"BAD_REQUEST\";\n case 409:\n return \"CONFLICT\";\n case 410:\n // StreamClosedError is HTTP 410 Gone. tRPC has no 410 code, so map to\n // NOT_FOUND (404) — its closest \"resource is gone\" semantics. This is\n // the one unavoidable cross-transport status difference (Hono/OpenAPI\n // keep 410); see auto-generated-api.md.\n return \"NOT_FOUND\";\n case 412:\n // ConcurrencyError is HTTP 412 Precondition Failed. Map by error\n // identity, not by round-tripping the status: PRECONDITION_FAILED\n // serializes back to 412, matching Hono/OpenAPI (#1280).\n return \"PRECONDITION_FAILED\";\n case 422:\n return \"UNPROCESSABLE_CONTENT\";\n default:\n return \"INTERNAL_SERVER_ERROR\";\n }\n}\n\n/**\n * Build a typed tRPC router from a built `Act` instance.\n *\n * Walks `app.registry.actions` once and emits one flat mutation per\n * action under `router.<actionName>`. The framework already enforces\n * no duplicate action names across states, so a flat tier reads\n * cleanly at the client and matches the hand-written router shape\n * adopters already have. Each mutation:\n *\n * 1. Runs the internal {@link authenticated} — `options.actor`\n * resolves the {@link Actor} once and injects it onto\n * `ctx.actor`.\n * 2. Resolves the target stream via `options.stream(name, input, ctx)`.\n * 3. (Optionally) claims the `Idempotency-Key` via\n * {@link withIdempotency} — throws `CONFLICT` on a duplicate of a\n * committed key, `SERVICE_UNAVAILABLE` on one still in flight.\n * 4. Calls `app.do(name, { stream, actor: ctx.actor }, input)`.\n * 5. Maps any thrown framework error onto a `TRPCError` via\n * {@link toApiError}.\n *\n * The returned router's procedure inputs come straight from each\n * action's Zod schema; outputs are the framework's\n * `Snapshot<TState, TEvents>[]` shape. Strong typing flows\n * end-to-end via tRPC's normal inference path — the client sees\n * `appRouter.Calculator.PressKey.mutate({ key: \"5\" })` as a typed\n * call without any codegen.\n *\n * @template Ctx The tRPC context shape the host's runtime feeds in.\n * Flows through `actor`, `stream`, and `idempotency.keyFrom` so\n * the operator's existing context types just work. The internal\n * middleware augments `Ctx` with `{ actor: Actor }` on the\n * downstream procedure context.\n *\n * @param app A built `Act` orchestrator.\n * @param options Auth, stream, and (optional) idempotency seams.\n * @returns A tRPC router covering every registered action, grouped\n * by owning state.\n */\n/**\n * Structural shape of the Act surface this generator walks at\n * runtime — the registry's action-name → owning-state map plus the\n * `do(...)` dispatch. Letting TApp infer to the caller's concrete\n * `Act<...>` against this structural bound (instead of forcing it\n * to fit a narrow framework-typed upper bound) keeps the variance\n * of nested types like `PatchHandler` from leaking — and avoids\n * `any` in the signature.\n *\n * @internal\n */\ntype ActSurface = {\n readonly registry: {\n actions: Record<\n string,\n {\n readonly name: string;\n readonly actions: Record<string, unknown>;\n }\n >;\n };\n do(action: string, target: Target, payload: unknown): Promise<unknown>;\n};\n\n/**\n * Generated router type — one mutation procedure per action name in\n * `TApp`'s registry. Mapped over the concrete action keys so\n * `createTRPCReact<typeof router>()` sees specific procedure names\n * (`PressKey`, `Clear`, …) instead of a wide `Record<string, ...>`\n * index signature — which is what trips tRPC React's\n * \"useContext / Provider / useUtils collides\" check on widely-typed\n * routers.\n *\n * @internal\n */\ntype GeneratedRouter<TApp> = TRPCBuiltRouter<\n AnyTRPCRootTypes,\n TApp extends { registry: { actions: infer TActions } }\n ? { [K in keyof TActions]: AnyTRPCMutationProcedure }\n : Record<string, AnyTRPCMutationProcedure>\n>;\n\nexport function trpc<\n Ctx extends object = object,\n TApp extends ActSurface = ActSurface,\n>(app: TApp, options: TrpcOptions<Ctx>): GeneratedRouter<TApp> {\n const t = initTRPC.context<Ctx>().create();\n // Counts claims per key so a duplicate of a still-running action can be\n // told apart from a duplicate of a committed one (see `track_in_flight`).\n const tracker = options.idempotency\n ? track_in_flight(options.idempotency.store)\n : undefined;\n\n // Resolve the actor inline per mutation instead of via\n // `t.procedure.use(authenticated(...))`. The middleware path threads\n // tRPC's internal `Unwrap` type into each procedure's inferred shape,\n // which the d.ts emitter can't name portably (`unstable-core-do-not-\n // import` has a hashed file name that varies per build). Inlining\n // sidesteps the issue entirely — the standalone `authenticated`\n // export still works for hosts who want middleware composition.\n /**\n * Run the actor extractor for a generated procedure, mapping a\n * \"throw to deny\" onto `UNAUTHORIZED` rather than a framework 500.\n *\n * Shared by mutations and subscriptions on purpose. Each used to carry\n * its own copy of this block, and the subscription copy was simply\n * missing — a denied caller got a live subscription and the cached\n * state frame with it (#1620). One helper is what keeps the two paths\n * from drifting again.\n */\n const resolve_actor = async (host_ctx: Ctx): Promise<Actor> => {\n try {\n return await options.actor(host_ctx);\n } catch (err) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: err instanceof Error ? err.message : undefined,\n cause: err instanceof Error ? err : undefined,\n });\n }\n };\n\n const handlers: Record<\n string,\n ReturnType<ReturnType<typeof t.procedure.input>[\"mutation\"]>\n > = {};\n for (const [action_name, state] of Object.entries(app.registry.actions)) {\n const action_schema = state.actions[action_name];\n handlers[action_name] = t.procedure\n // Passthrough parser: tRPC's own `.input()` validation is deliberately\n // bypassed because a Zod failure there surfaces as a hardcoded\n // BAD_REQUEST (400) that never reaches `to_trpc_error`. Validation runs\n // in the resolver (below) instead, so a malformed body maps to the SAME\n // 422 / ApiError a `ValidationError` thrown inside `app.do` produces —\n // cross-transport parity with Hono/OpenAPI (#1295).\n .input((raw: unknown) => raw)\n .mutation(async ({ input, ctx }) => {\n const host_ctx = ctx as unknown as Ctx;\n // Resolve the actor first, mirroring Hono's `authenticated` middleware:\n // a \"throw to deny\" from the extractor is 401 UNAUTHORIZED, not a 500.\n // The actor step is auth — distinct from a framework error inside\n // `app.do`, which the main try below maps via `to_trpc_error` (#1286).\n const actor: Actor = await resolve_actor(host_ctx);\n try {\n // Validate the payload before the stream / expectedVersion resolvers\n // see it, mirroring Hono's zValidator-then-handler order. `validate`\n // throws the framework `ValidationError`, which `to_trpc_error` maps\n // to 422 / UNPROCESSABLE_CONTENT with the shared ApiError envelope\n // (#1295). The resolvers get the parsed value, but `app.do` gets the\n // raw `input`: it parses again itself, and a second parse of the\n // output would run every `.transform()` twice.\n const validated = validate(\n action_name,\n input as never,\n action_schema as never\n );\n const stream = await options.stream(action_name, validated, host_ctx);\n const expected_version = options.expectedVersion\n ? await options.expectedVersion(action_name, validated, host_ctx)\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 = options.idempotency.keyFrom(host_ctx);\n if (!key) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Idempotency-Key required\",\n });\n }\n const outcome = await withIdempotency(tracker!.store, key, () =>\n app.do(action_name as never, target as never, input as never)\n );\n if (outcome.deduped && tracker!.in_flight(key)) {\n throw new TRPCError({\n code: \"SERVICE_UNAVAILABLE\",\n message:\n \"Idempotency-Key in flight; the original request has not finished, retry later\",\n });\n }\n if (outcome.deduped) {\n throw new TRPCError({\n code: \"CONFLICT\",\n message:\n \"Idempotency-Key already used; original result not cached\",\n });\n }\n return outcome.result;\n }\n\n return await app.do(\n action_name as never,\n target as never,\n input as never\n );\n } catch (err) {\n if (err instanceof TRPCError) throw err;\n throw to_trpc_error(err);\n }\n });\n }\n\n if (options.sse) {\n // SSE subscriptions mount under the `subscribe` key of the same router\n // as the action mutations, so an action named `subscribe` would be\n // silently replaced. Refuse it at generation instead.\n if (\"subscribe\" in app.registry.actions)\n throw new Error(\n 'trpc: an action named \"subscribe\" collides with the SSE subscription router (router.subscribe.<stateName>); rename the action or generate the router without `sse`.'\n );\n const sse_config = resolveSseConfig(options.sse);\n const sse_counter = new SseConnectionCounter(sse_config.maxConnections);\n const state_names = new Set<string>();\n for (const action of Object.values(app.registry.actions)) {\n state_names.add(action.name);\n }\n const sse_handlers: Record<string, unknown> = {};\n for (const state_name of state_names) {\n sse_handlers[state_name] = t.procedure\n .input(z.object({ stream: z.string().min(1) }))\n .subscription(async function* ({ input, ctx, signal }) {\n // Step 1 of the documented subscription contract: the actor\n // extractor runs before a slot is taken or a frame is yielded,\n // so a denying extractor denies the open. Hono gets this from\n // the `authenticated` middleware it mounts across `/api/*`;\n // this generator is where the same rule lives for tRPC, which\n // resolves the actor inline rather than through middleware.\n await resolve_actor(ctx as unknown as Ctx);\n yield* runSseSubscription(\n sse_config.channel,\n input.stream,\n sse_counter,\n signal,\n {\n maxPending: sse_config.maxPendingPerConnection,\n on_cap_exceeded: () => {\n throw new TRPCError({\n code: \"TOO_MANY_REQUESTS\",\n message: \"max concurrent SSE subscriptions reached\",\n });\n },\n }\n );\n });\n }\n handlers.subscribe = t.router(sse_handlers as never) as never;\n }\n\n // The runtime router IS structurally compatible with\n // `GeneratedRouter<TApp>` (one mutation per action key), but tRPC's\n // `BuiltRouter` carries an internal `Unwrap<Ctx>` that the d.ts\n // emitter can't name portably. Bridging through `unknown` keeps the\n // caller-visible type clean (specific procedure names, no\n // index-signature widening that trips React tRPC's collision check).\n return t.router(handlers) as unknown as GeneratedRouter<TApp>;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/trpc/index.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act-http/trpc\n *\n * tRPC subpath for the auto-generated API epic (#835). Walks the\n * registry of a built `Act` once at function call and emits a typed\n * router that exposes every registered action as a flat `mutation`,\n * matching the shape hand-written tRPC routers in the Act ecosystem\n * already use.\n *\n * Usage:\n *\n * ```ts\n * import { trpc } from \"@rotorsoft/act-http/trpc\";\n *\n * const router = trpc(app, {\n * actor: (ctx) => ({ id: ctx.user.id, name: ctx.user.name }),\n * stream: (action, input, ctx) => input.stream ?? ctx.tenant,\n * });\n *\n * // Client sees:\n * // appRouter.PressKey.mutate({ key: \"5\" })\n * // appRouter.OpenTicket.mutate({ title: \"...\" })\n * ```\n *\n * The route shape is flat: one procedure per registered action at\n * the top level. Action names are the unique identifiers across\n * the registry (the framework already enforces no duplicates), so\n * an extra namespace tier would be overhead in the common case.\n * Pure mutations for this slice; query/projection wiring lives in\n * a follow-up. See `libs/act-http/README.md` for the full surface\n * walkthrough.\n *\n * **Actor lives on the context.** The generator wires an internal\n * tRPC middleware that runs the host's\n * {@link TrpcOptions.actor | actor extractor} once per call and\n * injects the resolved {@link Actor} onto the downstream context as\n * `ctx.actor`. Every generated mutation reads from there — auth\n * resolves at the procedure boundary, not inside each action body,\n * and any composed middleware (logging, tracing) sees the same\n * `ctx.actor`. The middleware factory is also exported as\n * {@link authenticated} for hosts that need to compose it into\n * their own procedure chain.\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 (this one, Hono,\n * OpenAPI) ride the same utilities so a client talking to two\n * transports never sees two error shapes for the same framework\n * error.\n */\nimport { type Actor, type Target, validate } from \"@rotorsoft/act\";\nimport type { IdempotencyStore } from \"@rotorsoft/act-ops/idempotency\";\nimport {\n type AnyTRPCMiddlewareFunction,\n type AnyTRPCMutationProcedure,\n type AnyTRPCRootTypes,\n initTRPC,\n type TRPCBuiltRouter,\n TRPCError,\n} from \"@trpc/server\";\nimport { z } from \"zod\";\nimport { track_in_flight } from \"../api/in-flight.js\";\nimport {\n type ActorExtractor,\n resolveSseConfig,\n runSseSubscription,\n SseConnectionCounter,\n type SseOptions,\n toApiError,\n withIdempotency,\n} from \"../api/index.js\";\n\n/**\n * Per-call options for {@link trpc}. The host supplies the three\n * seams the package can't make decisions on:\n *\n * - `actor` — auth resolver. Receives the tRPC context, returns the\n * `Actor` that flows onto `ctx.actor` for every generated\n * procedure (via an internal {@link authenticated}).\n * - `stream` — stream-id resolver. Receives the action name, the\n * validated action input, and the original tRPC context; returns\n * the target stream. Singleton aggregates return a constant;\n * per-tenant aggregates pull from input or context.\n * - `expectedVersion` (optional) — optimistic-concurrency resolver.\n * When set, the procedure threads the resolved value through\n * `target.expectedVersion` so `app.do` refuses to commit if the\n * stream has moved. Hosts typically read it from an `If-Match`\n * header on the underlying HTTP request, or pull it from the\n * client's last-known snapshot. Returning `undefined` skips the\n * check for that call — handy when only some actions are\n * concurrency-sensitive.\n * - `idempotency` (optional) — when set, the procedure honors\n * `Idempotency-Key` via the shared\n * {@link withIdempotency} helper. The host supplies the\n * `IdempotencyStore` and a `keyFrom` extractor that reads the key\n * out of the tRPC context (typically a header). A duplicate of a key\n * that already succeeded throws a `CONFLICT` `TRPCError` — the\n * receiver-side convention of \"ack the duplicate\" doesn't carry\n * over because the contract intentionally does not cache the\n * original handler's result. A duplicate that arrives while the\n * original is still running throws a retryable\n * `SERVICE_UNAVAILABLE` (503) instead: the original may yet fail and\n * release the key, and a client told \"already used\" would never retry.\n *\n * @template Ctx The host's tRPC context shape. Flows end-to-end —\n * procedures see this exact context type (plus `actor`) at\n * runtime.\n */\nexport type TrpcOptions<Ctx> = {\n readonly actor: ActorExtractor;\n readonly stream: (\n action_name: string,\n input: unknown,\n ctx: Ctx\n ) => string | Promise<string>;\n readonly expectedVersion?: (\n action_name: string,\n input: unknown,\n ctx: Ctx\n ) => number | undefined | Promise<number | undefined>;\n readonly idempotency?: {\n readonly store: IdempotencyStore;\n readonly keyFrom: (ctx: Ctx) => string | undefined;\n };\n /**\n * Optional SSE wiring. When set, the generator emits one\n * subscription per unique state name in the registry, grouped on\n * the returned router as\n * `router.subscribe.<stateName>.useSubscription({ stream })`.\n * Each subscription yields `{ kind: \"state\", data }` once with\n * the cached state (when present) and then `{ kind: \"patch\",\n * data }` for every patch the shared\n * {@link SseOptions.channel} publishes. The per-process\n * connection cap surfaces as a `TOO_MANY_REQUESTS` `TRPCError`.\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};\n\n/**\n * Build a tRPC middleware that runs an {@link ActorExtractor} once\n * per call and forwards the resolved {@link Actor} on the\n * downstream context as `ctx.actor`. The generator at\n * {@link trpc} uses this internally; it's also exported so hosts\n * that want to compose their own procedure chain (logging,\n * tracing, custom auth flavors) can wire it directly:\n *\n * ```ts\n * import { initTRPC } from \"@trpc/server\";\n * import { authenticated } from \"@rotorsoft/act-http/trpc\";\n *\n * type Ctx = { user?: { id: string; name: string } };\n * const t = initTRPC.context<Ctx>().create();\n *\n * const authed = t.procedure.use(\n * authenticated((ctx) => {\n * if (!ctx.user) throw new Error(\"not authenticated\");\n * return { id: ctx.user.id, name: ctx.user.name };\n * })\n * );\n *\n * // Inside any procedure built off `authed`, `ctx.actor: Actor`.\n * ```\n *\n * The middleware is unaware of the host's `t` instance — it returns\n * a structurally-typed function that any tRPC `procedure.use(...)`\n * accepts. Errors thrown by the extractor surface unchanged\n * (typically wrap them as `TRPCError({ code: \"UNAUTHORIZED\" })`\n * in the extractor body).\n */\nexport function authenticated(\n extractor: ActorExtractor\n): AnyTRPCMiddlewareFunction {\n // tRPC's middleware function type lives behind\n // `unstable-core-do-not-import`; we satisfy it structurally with the\n // shape `procedure.use(...)` actually requires (an `{ ctx, next }`\n // bag whose `next` callback is invoked once with the augmented ctx)\n // and cast the structurally-typed result up to the named export. The\n // type cast carries no `any`; both sides are structurally compatible\n // at the call site.\n const middleware = async function actor_middleware({\n ctx,\n next,\n }: {\n ctx: object;\n next: (opts: { ctx: object }) => Promise<unknown>;\n }) {\n const actor = await extractor(ctx);\n return next({ ctx: { ...ctx, actor } });\n };\n return middleware as unknown as AnyTRPCMiddlewareFunction;\n}\n\n/**\n * Map a thrown framework error onto a `TRPCError`. Known errors get\n * their conventional tRPC code (`ConcurrencyError` → `PRECONDITION_FAILED`,\n * `StreamClosedError` → `NOT_FOUND`, `ValidationError` →\n * `UNPROCESSABLE_CONTENT`, etc.); unknown throws\n * surface as `INTERNAL_SERVER_ERROR`. Every transport in the\n * act-http epic passes through the same {@link toApiError} table,\n * so a client talking to both this subpath and the REST sibling\n * sees one envelope per framework error.\n *\n * @internal\n */\nfunction to_trpc_error(err: unknown): TRPCError {\n const { status, body } = toApiError(err);\n const code = status_to_trpc_code(status);\n return new TRPCError({\n code,\n message: body.detail ?? body.error,\n cause: err instanceof Error ? err : undefined,\n });\n}\n\n/**\n * Minimal status → tRPC code map. Only the statuses\n * {@link ERROR_MAP} produces (plus 500) appear here; anything else\n * collapses to `INTERNAL_SERVER_ERROR`.\n *\n * @internal\n */\nfunction status_to_trpc_code(status: number): TRPCError[\"code\"] {\n switch (status) {\n case 400:\n return \"BAD_REQUEST\";\n case 409:\n return \"CONFLICT\";\n case 410:\n // StreamClosedError is HTTP 410 Gone. tRPC has no 410 code, so map to\n // NOT_FOUND (404) — its closest \"resource is gone\" semantics. This is\n // the one unavoidable cross-transport status difference (Hono/OpenAPI\n // keep 410); see auto-generated-api.md.\n return \"NOT_FOUND\";\n case 412:\n // ConcurrencyError is HTTP 412 Precondition Failed. Map by error\n // identity, not by round-tripping the status: PRECONDITION_FAILED\n // serializes back to 412, matching Hono/OpenAPI (#1280).\n return \"PRECONDITION_FAILED\";\n case 422:\n return \"UNPROCESSABLE_CONTENT\";\n default:\n return \"INTERNAL_SERVER_ERROR\";\n }\n}\n\n/**\n * Build a typed tRPC router from a built `Act` instance.\n *\n * Walks `app.registry.actions` once and emits one flat mutation per\n * action under `router.<actionName>`. The framework already enforces\n * no duplicate action names across states, so a flat tier reads\n * cleanly at the client and matches the hand-written router shape\n * adopters already have. Each mutation:\n *\n * 1. Runs the internal {@link authenticated} — `options.actor`\n * resolves the {@link Actor} once and injects it onto\n * `ctx.actor`.\n * 2. Resolves the target stream via `options.stream(name, input, ctx)`.\n * 3. (Optionally) claims the `Idempotency-Key` via\n * {@link withIdempotency} — throws `CONFLICT` on a duplicate of a\n * committed key, `SERVICE_UNAVAILABLE` on one still in flight.\n * 4. Calls `app.do(name, { stream, actor: ctx.actor }, input)`.\n * 5. Maps any thrown framework error onto a `TRPCError` via\n * {@link toApiError}.\n *\n * The returned router's procedure inputs come straight from each\n * action's Zod schema; outputs are the framework's\n * `Snapshot<TState, TEvents>[]` shape. Strong typing flows\n * end-to-end via tRPC's normal inference path — the client sees\n * `appRouter.Calculator.PressKey.mutate({ key: \"5\" })` as a typed\n * call without any codegen.\n *\n * @template Ctx The tRPC context shape the host's runtime feeds in.\n * Flows through `actor`, `stream`, and `idempotency.keyFrom` so\n * the operator's existing context types just work. The internal\n * middleware augments `Ctx` with `{ actor: Actor }` on the\n * downstream procedure context.\n *\n * @param app A built `Act` orchestrator.\n * @param options Auth, stream, and (optional) idempotency seams.\n * @returns A tRPC router covering every registered action, grouped\n * by owning state.\n */\n/**\n * Structural shape of the Act surface this generator walks at\n * runtime — the registry's action-name → owning-state map plus the\n * `do(...)` dispatch. Letting TApp infer to the caller's concrete\n * `Act<...>` against this structural bound (instead of forcing it\n * to fit a narrow framework-typed upper bound) keeps the variance\n * of nested types like `PatchHandler` from leaking — and avoids\n * `any` in the signature.\n *\n * @internal\n */\ntype ActSurface = {\n readonly registry: {\n actions: Record<\n string,\n {\n readonly name: string;\n readonly actions: Record<string, unknown>;\n }\n >;\n };\n do(action: string, target: Target, payload: unknown): Promise<unknown>;\n};\n\n/**\n * Generated router type — one mutation procedure per action name in\n * `TApp`'s registry. Mapped over the concrete action keys so\n * `createTRPCReact<typeof router>()` sees specific procedure names\n * (`PressKey`, `Clear`, …) instead of a wide `Record<string, ...>`\n * index signature — which is what trips tRPC React's\n * \"useContext / Provider / useUtils collides\" check on widely-typed\n * routers.\n *\n * @internal\n */\ntype GeneratedRouter<TApp> = TRPCBuiltRouter<\n AnyTRPCRootTypes,\n TApp extends { registry: { actions: infer TActions } }\n ? { [K in keyof TActions]: AnyTRPCMutationProcedure }\n : Record<string, AnyTRPCMutationProcedure>\n>;\n\nexport function trpc<\n Ctx extends object = object,\n TApp extends ActSurface = ActSurface,\n>(app: TApp, options: TrpcOptions<Ctx>): GeneratedRouter<TApp> {\n const t = initTRPC.context<Ctx>().create();\n // Counts claims per key so a duplicate of a still-running action can be\n // told apart from a duplicate of a committed one (see `track_in_flight`).\n const tracker = options.idempotency\n ? track_in_flight(options.idempotency.store)\n : undefined;\n\n // Resolve the actor inline per mutation instead of via\n // `t.procedure.use(authenticated(...))`. The middleware path threads\n // tRPC's internal `Unwrap` type into each procedure's inferred shape,\n // which the d.ts emitter can't name portably (`unstable-core-do-not-\n // import` has a hashed file name that varies per build). Inlining\n // sidesteps the issue entirely — the standalone `authenticated`\n // export still works for hosts who want middleware composition.\n /**\n * Run the actor extractor for a generated procedure, mapping a\n * \"throw to deny\" onto `UNAUTHORIZED` rather than a framework 500.\n *\n * Shared by mutations and subscriptions on purpose. Each used to carry\n * its own copy of this block, and the subscription copy was simply\n * missing — a denied caller got a live subscription and the cached\n * state frame with it (#1620). One helper is what keeps the two paths\n * from drifting again.\n */\n const resolve_actor = async (host_ctx: Ctx): Promise<Actor> => {\n try {\n return await options.actor(host_ctx);\n } catch (err) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: err instanceof Error ? err.message : undefined,\n cause: err instanceof Error ? err : undefined,\n });\n }\n };\n\n const handlers: Record<\n string,\n ReturnType<ReturnType<typeof t.procedure.input>[\"mutation\"]>\n > = {};\n for (const [action_name, state] of Object.entries(app.registry.actions)) {\n const action_schema = state.actions[action_name];\n handlers[action_name] = t.procedure\n // Passthrough parser: tRPC's own `.input()` validation is deliberately\n // bypassed because a Zod failure there surfaces as a hardcoded\n // BAD_REQUEST (400) that never reaches `to_trpc_error`. Validation runs\n // in the resolver (below) instead, so a malformed body maps to the SAME\n // 422 / ApiError a `ValidationError` thrown inside `app.do` produces —\n // cross-transport parity with Hono/OpenAPI (#1295).\n .input((raw: unknown) => raw)\n .mutation(async ({ input, ctx }) => {\n const host_ctx = ctx as unknown as Ctx;\n // Resolve the actor first, mirroring Hono's `authenticated` middleware:\n // a \"throw to deny\" from the extractor is 401 UNAUTHORIZED, not a 500.\n // The actor step is auth — distinct from a framework error inside\n // `app.do`, which the main try below maps via `to_trpc_error` (#1286).\n const actor: Actor = await resolve_actor(host_ctx);\n try {\n // Validate the payload before the stream / expectedVersion resolvers\n // see it, mirroring Hono's zValidator-then-handler order. `validate`\n // throws the framework `ValidationError`, which `to_trpc_error` maps\n // to 422 / UNPROCESSABLE_CONTENT with the shared ApiError envelope\n // (#1295). The resolvers get the parsed value, but `app.do` gets the\n // raw `input`: it parses again itself, and a second parse of the\n // output would run every `.transform()` twice.\n const validated = validate(\n action_name,\n input as never,\n action_schema as never\n );\n const stream = await options.stream(action_name, validated, host_ctx);\n const expected_version = options.expectedVersion\n ? await options.expectedVersion(action_name, validated, host_ctx)\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 = options.idempotency.keyFrom(host_ctx);\n if (!key) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Idempotency-Key required\",\n });\n }\n const outcome = await withIdempotency(tracker!.store, key, () =>\n app.do(action_name as never, target as never, input as never)\n );\n if (outcome.deduped && tracker!.in_flight(key)) {\n throw new TRPCError({\n code: \"SERVICE_UNAVAILABLE\",\n message:\n \"Idempotency-Key in flight; the original request has not finished, retry later\",\n });\n }\n if (outcome.deduped) {\n throw new TRPCError({\n code: \"CONFLICT\",\n message:\n \"Idempotency-Key already used; original result not cached\",\n });\n }\n return outcome.result;\n }\n\n return await app.do(\n action_name as never,\n target as never,\n input as never\n );\n } catch (err) {\n if (err instanceof TRPCError) throw err;\n throw to_trpc_error(err);\n }\n });\n }\n\n if (options.sse) {\n // SSE subscriptions mount under the `subscribe` key of the same router\n // as the action mutations, so an action named `subscribe` would be\n // silently replaced. Refuse it at generation instead.\n if (\"subscribe\" in app.registry.actions)\n throw new Error(\n 'trpc: an action named \"subscribe\" collides with the SSE subscription router (router.subscribe.<stateName>); rename the action or generate the router without `sse`.'\n );\n const sse_config = resolveSseConfig(options.sse);\n const sse_counter = new SseConnectionCounter(sse_config.maxConnections);\n const state_names = new Set<string>();\n for (const action of Object.values(app.registry.actions)) {\n state_names.add(action.name);\n }\n const sse_handlers: Record<string, unknown> = {};\n for (const state_name of state_names) {\n sse_handlers[state_name] = t.procedure\n .input(z.object({ stream: z.string().min(1) }))\n .subscription(async function* ({ input, ctx, signal }) {\n // Step 1 of the documented subscription contract: the actor\n // extractor runs before a slot is taken or a frame is yielded,\n // so a denying extractor denies the open. Hono gets this from\n // the `authenticated` middleware it mounts across `/api/*`;\n // this generator is where the same rule lives for tRPC, which\n // resolves the actor inline rather than through middleware.\n await resolve_actor(ctx as unknown as Ctx);\n yield* runSseSubscription(\n sse_config.channel,\n input.stream,\n sse_counter,\n signal,\n {\n maxPending: sse_config.maxPendingPerConnection,\n on_cap_exceeded: () => {\n throw new TRPCError({\n code: \"TOO_MANY_REQUESTS\",\n message: \"max concurrent SSE subscriptions reached\",\n });\n },\n }\n );\n });\n }\n handlers.subscribe = t.router(sse_handlers as never) as never;\n }\n\n // The runtime router IS structurally compatible with\n // `GeneratedRouter<TApp>` (one mutation per action key), but tRPC's\n // `BuiltRouter` carries an internal `Unwrap<Ctx>` that the d.ts\n // emitter can't name portably. Bridging through `unknown` keeps the\n // caller-visible type clean (specific procedure names, no\n // index-signature widening that trips React tRPC's collision check).\n return t.router(handlers) as unknown as GeneratedRouter<TApp>;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rotorsoft/act-http",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.10.
|
|
4
|
+
"version": "1.10.18",
|
|
5
5
|
"description": "HTTP integrations for act apps — webhooks and SSE",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"typescript",
|
|
@@ -90,8 +90,8 @@
|
|
|
90
90
|
"access": "public"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
|
-
"@hono/node-server": "^2.1.
|
|
94
|
-
"@rotorsoft/act": "^1.32.
|
|
93
|
+
"@hono/node-server": "^2.1.4",
|
|
94
|
+
"@rotorsoft/act": "^1.32.20",
|
|
95
95
|
"@rotorsoft/act-ops": "^0.2.10",
|
|
96
96
|
"@trpc/server": ">=11.19.0",
|
|
97
97
|
"express": "^5.2.1",
|
|
@@ -122,11 +122,9 @@
|
|
|
122
122
|
"@rotorsoft/act-patch": "^1.2.11"
|
|
123
123
|
},
|
|
124
124
|
"devDependencies": {
|
|
125
|
-
"@hono/node-server": "^2.1.
|
|
125
|
+
"@hono/node-server": "^2.1.4",
|
|
126
126
|
"@hono/zod-validator": "^0.9.1",
|
|
127
127
|
"@rotorsoft/act-ops": "^0.2.10",
|
|
128
|
-
"@stryker-mutator/core": "^10.0.0",
|
|
129
|
-
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
130
128
|
"@trpc/server": "^11.19.0",
|
|
131
129
|
"@types/express": "^5.0.6",
|
|
132
130
|
"express": "^5.2.1",
|
|
@@ -137,7 +135,6 @@
|
|
|
137
135
|
"scripts": {
|
|
138
136
|
"clean": "rm -rf dist",
|
|
139
137
|
"types": "tsc --build tsconfig.build.json --emitDeclarationOnly",
|
|
140
|
-
"build": "pnpm clean && tsup && pnpm types"
|
|
141
|
-
"mutation": "stryker run"
|
|
138
|
+
"build": "pnpm clean && tsup && pnpm types"
|
|
142
139
|
}
|
|
143
140
|
}
|