ts-procedures 7.3.0 → 8.0.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 +65 -3
- package/agent_config/claude-code/agents/ts-procedures-architect.md +6 -8
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +30 -33
- package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +104 -53
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +205 -232
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +80 -153
- package/agent_config/claude-code/skills/ts-procedures-review/SKILL.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures-review/checklist.md +4 -5
- package/agent_config/claude-code/skills/ts-procedures-scaffold/SKILL.md +4 -7
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono.md +223 -0
- package/agent_config/copilot/copilot-instructions.md +34 -48
- package/agent_config/cursor/cursorrules +34 -48
- package/build/client/call.js +4 -1
- package/build/client/call.js.map +1 -1
- package/build/client/call.test.js +23 -0
- package/build/client/call.test.js.map +1 -1
- package/build/client/fetch-adapter.js +3 -1
- package/build/client/fetch-adapter.js.map +1 -1
- package/build/client/fetch-adapter.test.js +11 -1
- package/build/client/fetch-adapter.test.js.map +1 -1
- package/build/client/index.test.js +7 -7
- package/build/client/index.test.js.map +1 -1
- package/build/client/request-builder.d.ts +1 -1
- package/build/client/request-builder.js +2 -2
- package/build/client/request-builder.js.map +1 -1
- package/build/client/stream.js +13 -2
- package/build/client/stream.js.map +1 -1
- package/build/client/stream.test.js +32 -7
- package/build/client/stream.test.js.map +1 -1
- package/build/client/typed-error-dispatch.test.js +8 -92
- package/build/client/typed-error-dispatch.test.js.map +1 -1
- package/build/client/types.d.ts +21 -3
- package/build/codegen/bin/cli.js +0 -0
- package/build/codegen/e2e.test.js +87 -23
- package/build/codegen/e2e.test.js.map +1 -1
- package/build/codegen/emit-errors.integration.test.js +1 -1
- package/build/codegen/emit-errors.integration.test.js.map +1 -1
- package/build/codegen/emit-scope.js +308 -47
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/emit-scope.test.js +363 -110
- package/build/codegen/emit-scope.test.js.map +1 -1
- package/build/codegen/pipeline.test.js +7 -7
- package/build/codegen/pipeline.test.js.map +1 -1
- package/build/codegen/resolve-envelope.js +1 -1
- package/build/codegen/resolve-envelope.js.map +1 -1
- package/build/codegen/resolve-envelope.test.js +5 -5
- package/build/codegen/resolve-envelope.test.js.map +1 -1
- package/build/codegen/targets/_shared/route-slots.d.ts +8 -3
- package/build/codegen/targets/_shared/route-slots.js +49 -8
- package/build/codegen/targets/_shared/route-slots.js.map +1 -1
- package/build/codegen/targets/_shared/route-slots.test.js +99 -26
- package/build/codegen/targets/_shared/route-slots.test.js.map +1 -1
- package/build/codegen/targets/kotlin/emit-route-kotlin.test.js +88 -17
- package/build/codegen/targets/kotlin/emit-route-kotlin.test.js.map +1 -1
- package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js +9 -6
- package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js.map +1 -1
- package/build/codegen/targets/kotlin/integration.test.js +6 -0
- package/build/codegen/targets/kotlin/integration.test.js.map +1 -1
- package/build/codegen/targets/swift/access-level.test.js +8 -11
- package/build/codegen/targets/swift/access-level.test.js.map +1 -1
- package/build/codegen/targets/swift/emit-route-swift.test.js +91 -20
- package/build/codegen/targets/swift/emit-route-swift.test.js.map +1 -1
- package/build/codegen/targets/swift/emit-scope-swift.test.js +12 -9
- package/build/codegen/targets/swift/emit-scope-swift.test.js.map +1 -1
- package/build/codegen/targets/swift/integration.test.js +6 -0
- package/build/codegen/targets/swift/integration.test.js.map +1 -1
- package/build/create-http-stream.d.ts +58 -0
- package/build/create-http-stream.js +122 -0
- package/build/create-http-stream.js.map +1 -0
- package/build/create-http-stream.test.js +88 -0
- package/build/create-http-stream.test.js.map +1 -0
- package/build/create-http.d.ts +49 -0
- package/build/create-http.js +108 -0
- package/build/create-http.js.map +1 -0
- package/build/create-http.test.js +137 -0
- package/build/create-http.test.js.map +1 -0
- package/build/create-stream.d.ts +35 -0
- package/build/create-stream.js +123 -0
- package/build/create-stream.js.map +1 -0
- package/build/create-stream.test.js +428 -0
- package/build/create-stream.test.js.map +1 -0
- package/build/create.d.ts +28 -0
- package/build/create.js +82 -0
- package/build/create.js.map +1 -0
- package/build/create.test.js +483 -0
- package/build/create.test.js.map +1 -0
- package/build/exports.d.ts +2 -0
- package/build/implementations/http/astro/index.test.js +20 -12
- package/build/implementations/http/astro/index.test.js.map +1 -1
- package/build/implementations/http/doc-registry.js +1 -1
- package/build/implementations/http/doc-registry.js.map +1 -1
- package/build/implementations/http/doc-registry.test.js +36 -5
- package/build/implementations/http/doc-registry.test.js.map +1 -1
- package/build/implementations/http/error-dispatch.d.ts +76 -0
- package/build/implementations/http/error-dispatch.js +77 -0
- package/build/implementations/http/error-dispatch.js.map +1 -0
- package/build/implementations/http/error-dispatch.test.js +254 -0
- package/build/implementations/http/error-dispatch.test.js.map +1 -0
- package/build/implementations/http/error-taxonomy.d.ts +5 -5
- package/build/implementations/http/hono/docs/http-doc.d.ts +6 -0
- package/build/implementations/http/hono/docs/http-doc.js +42 -0
- package/build/implementations/http/hono/docs/http-doc.js.map +1 -0
- package/build/implementations/http/hono/docs/http-stream-doc.d.ts +6 -0
- package/build/implementations/http/hono/docs/http-stream-doc.js +40 -0
- package/build/implementations/http/hono/docs/http-stream-doc.js.map +1 -0
- package/build/implementations/http/hono/docs/rpc-doc.d.ts +6 -0
- package/build/implementations/http/hono/docs/rpc-doc.js +24 -0
- package/build/implementations/http/hono/docs/rpc-doc.js.map +1 -0
- package/build/implementations/http/hono/docs/stream-doc.d.ts +6 -0
- package/build/implementations/http/hono/docs/stream-doc.js +42 -0
- package/build/implementations/http/hono/docs/stream-doc.js.map +1 -0
- package/build/implementations/http/hono/handlers/http-stream.d.ts +10 -0
- package/build/implementations/http/hono/handlers/http-stream.js +123 -0
- package/build/implementations/http/hono/handlers/http-stream.js.map +1 -0
- package/build/implementations/http/hono/handlers/http-stream.test.js +128 -0
- package/build/implementations/http/hono/handlers/http-stream.test.js.map +1 -0
- package/build/implementations/http/hono/handlers/http.d.ts +10 -0
- package/build/implementations/http/hono/handlers/http.js +115 -0
- package/build/implementations/http/hono/handlers/http.js.map +1 -0
- package/build/implementations/http/hono/handlers/http.test.js +118 -0
- package/build/implementations/http/hono/handlers/http.test.js.map +1 -0
- package/build/implementations/http/hono/handlers/rpc.d.ts +11 -0
- package/build/implementations/http/hono/handlers/rpc.js +32 -0
- package/build/implementations/http/hono/handlers/rpc.js.map +1 -0
- package/build/implementations/http/hono/handlers/rpc.test.js +73 -0
- package/build/implementations/http/hono/handlers/rpc.test.js.map +1 -0
- package/build/implementations/http/hono/handlers/stream.d.ts +23 -0
- package/build/implementations/http/hono/handlers/stream.js +147 -0
- package/build/implementations/http/hono/handlers/stream.js.map +1 -0
- package/build/implementations/http/hono/handlers/stream.test.d.ts +1 -0
- package/build/implementations/http/hono/handlers/stream.test.js +177 -0
- package/build/implementations/http/hono/handlers/stream.test.js.map +1 -0
- package/build/implementations/http/hono/index.d.ts +57 -0
- package/build/implementations/http/hono/index.js +149 -0
- package/build/implementations/http/hono/index.js.map +1 -0
- package/build/implementations/http/hono/index.test.d.ts +1 -0
- package/build/implementations/http/hono/index.test.js +274 -0
- package/build/implementations/http/hono/index.test.js.map +1 -0
- package/build/implementations/http/hono/path.d.ts +17 -0
- package/build/implementations/http/hono/path.js +39 -0
- package/build/implementations/http/hono/path.js.map +1 -0
- package/build/implementations/http/hono/path.test.d.ts +1 -0
- package/build/implementations/http/hono/path.test.js +83 -0
- package/build/implementations/http/hono/path.test.js.map +1 -0
- package/build/implementations/http/hono/types.d.ts +51 -0
- package/build/implementations/http/hono/types.js.map +1 -0
- package/build/implementations/http/on-request-error.test.js +6 -96
- package/build/implementations/http/on-request-error.test.js.map +1 -1
- package/build/implementations/http/route-errors.test.js +11 -59
- package/build/implementations/http/route-errors.test.js.map +1 -1
- package/build/implementations/types.d.ts +43 -9
- package/build/index.d.ts +124 -124
- package/build/index.js +10 -221
- package/build/index.js.map +1 -1
- package/build/index.test.js +20 -919
- package/build/index.test.js.map +1 -1
- package/build/migration.test.d.ts +1 -0
- package/build/migration.test.js +34 -0
- package/build/migration.test.js.map +1 -0
- package/build/schema/compute-schema.d.ts +11 -3
- package/build/schema/compute-schema.js +13 -7
- package/build/schema/compute-schema.js.map +1 -1
- package/build/schema/parser.d.ts +11 -3
- package/build/schema/parser.js +49 -9
- package/build/schema/parser.js.map +1 -1
- package/build/stack-utils.js +8 -0
- package/build/stack-utils.js.map +1 -1
- package/build/types.d.ts +142 -0
- package/build/types.js.map +1 -0
- package/docs/astro-adapter.md +5 -5
- package/docs/core.md +15 -17
- package/docs/http-integrations.md +83 -170
- package/docs/streaming.md +3 -60
- package/docs/superpowers/plans/2026-05-07-astro-adapter.md +2 -7
- package/docs/superpowers/plans/2026-05-08-create-http.md +3355 -0
- package/docs/superpowers/plans/2026-05-08-hono-app-builder-convergence.md +3365 -0
- package/docs/superpowers/specs/2026-05-07-astro-adapter-design.md +1 -3
- package/docs/superpowers/specs/2026-05-08-create-http-design.md +409 -0
- package/docs/superpowers/specs/2026-05-08-hono-app-builder-convergence-design.md +411 -0
- package/package.json +4 -22
- package/src/client/call.test.ts +26 -0
- package/src/client/call.ts +4 -1
- package/src/client/fetch-adapter.test.ts +14 -1
- package/src/client/fetch-adapter.ts +3 -1
- package/src/client/index.test.ts +7 -7
- package/src/client/request-builder.ts +2 -2
- package/src/client/stream.test.ts +39 -7
- package/src/client/stream.ts +16 -2
- package/src/client/typed-error-dispatch.test.ts +7 -97
- package/src/client/types.ts +21 -3
- package/src/codegen/__fixtures__/users-envelope.json +119 -38
- package/src/codegen/e2e.test.ts +98 -24
- package/src/codegen/emit-errors.integration.test.ts +1 -1
- package/src/codegen/emit-scope.test.ts +395 -110
- package/src/codegen/emit-scope.ts +350 -55
- package/src/codegen/pipeline.test.ts +7 -7
- package/src/codegen/resolve-envelope.test.ts +5 -5
- package/src/codegen/resolve-envelope.ts +1 -1
- package/src/codegen/targets/_shared/route-slots.test.ts +109 -26
- package/src/codegen/targets/_shared/route-slots.ts +48 -11
- package/src/codegen/targets/kotlin/__fixtures__/users-golden.kt +73 -0
- package/src/codegen/targets/kotlin/emit-route-kotlin.test.ts +100 -17
- package/src/codegen/targets/kotlin/emit-scope-kotlin.test.ts +9 -6
- package/src/codegen/targets/kotlin/integration.test.ts +19 -0
- package/src/codegen/targets/swift/__fixtures__/users-golden.swift +79 -0
- package/src/codegen/targets/swift/access-level.test.ts +8 -11
- package/src/codegen/targets/swift/emit-route-swift.test.ts +103 -20
- package/src/codegen/targets/swift/emit-scope-swift.test.ts +12 -9
- package/src/codegen/targets/swift/integration.test.ts +17 -0
- package/src/create-http-stream.test.ts +97 -0
- package/src/create-http-stream.ts +191 -0
- package/src/create-http.test.ts +163 -0
- package/src/create-http.ts +211 -0
- package/src/create-stream.test.ts +565 -0
- package/src/create-stream.ts +228 -0
- package/src/create.test.ts +658 -0
- package/src/create.ts +172 -0
- package/src/exports.ts +2 -0
- package/src/implementations/http/README.md +135 -95
- package/src/implementations/http/astro/README.md +4 -5
- package/src/implementations/http/astro/index.test.ts +25 -18
- package/src/implementations/http/doc-registry.test.ts +42 -5
- package/src/implementations/http/doc-registry.ts +1 -1
- package/src/implementations/http/error-dispatch.test.ts +283 -0
- package/src/implementations/http/error-dispatch.ts +176 -0
- package/src/implementations/http/error-taxonomy.ts +5 -5
- package/src/implementations/http/hono/docs/http-doc.ts +43 -0
- package/src/implementations/http/hono/docs/http-stream-doc.ts +44 -0
- package/src/implementations/http/hono/docs/rpc-doc.ts +34 -0
- package/src/implementations/http/hono/docs/stream-doc.ts +53 -0
- package/src/implementations/http/hono/handlers/http-stream.test.ts +150 -0
- package/src/implementations/http/hono/handlers/http-stream.ts +152 -0
- package/src/implementations/http/hono/handlers/http.test.ts +130 -0
- package/src/implementations/http/hono/handlers/http.ts +147 -0
- package/src/implementations/http/hono/handlers/rpc.test.ts +81 -0
- package/src/implementations/http/hono/handlers/rpc.ts +54 -0
- package/src/implementations/http/hono/handlers/stream.test.ts +198 -0
- package/src/implementations/http/hono/handlers/stream.ts +208 -0
- package/src/implementations/http/hono/index.test.ts +329 -0
- package/src/implementations/http/hono/index.ts +204 -0
- package/src/implementations/http/hono/path.test.ts +96 -0
- package/src/implementations/http/hono/path.ts +59 -0
- package/src/implementations/http/hono/types.ts +93 -0
- package/src/implementations/http/on-request-error.test.ts +10 -116
- package/src/implementations/http/route-errors.test.ts +11 -77
- package/src/implementations/types.ts +44 -9
- package/src/index.test.ts +22 -1249
- package/src/index.ts +49 -485
- package/src/migration.test.ts +48 -0
- package/src/schema/compute-schema.ts +26 -12
- package/src/schema/parser.ts +62 -12
- package/src/stack-utils.ts +8 -0
- package/src/types.ts +133 -0
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/express-rpc.md +0 -137
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-api.md +0 -173
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-rpc.md +0 -142
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-stream.md +0 -147
- package/build/implementations/http/express-rpc/error-taxonomy.test.js +0 -83
- package/build/implementations/http/express-rpc/error-taxonomy.test.js.map +0 -1
- package/build/implementations/http/express-rpc/index.d.ts +0 -125
- package/build/implementations/http/express-rpc/index.js +0 -216
- package/build/implementations/http/express-rpc/index.js.map +0 -1
- package/build/implementations/http/express-rpc/index.test.js +0 -684
- package/build/implementations/http/express-rpc/index.test.js.map +0 -1
- package/build/implementations/http/express-rpc/types.d.ts +0 -11
- package/build/implementations/http/express-rpc/types.js.map +0 -1
- package/build/implementations/http/hono-api/error-taxonomy.test.js +0 -137
- package/build/implementations/http/hono-api/error-taxonomy.test.js.map +0 -1
- package/build/implementations/http/hono-api/index.d.ts +0 -151
- package/build/implementations/http/hono-api/index.js +0 -344
- package/build/implementations/http/hono-api/index.js.map +0 -1
- package/build/implementations/http/hono-api/index.test.js +0 -992
- package/build/implementations/http/hono-api/index.test.js.map +0 -1
- package/build/implementations/http/hono-api/types.d.ts +0 -13
- package/build/implementations/http/hono-api/types.js.map +0 -1
- package/build/implementations/http/hono-rpc/error-taxonomy.test.js +0 -64
- package/build/implementations/http/hono-rpc/error-taxonomy.test.js.map +0 -1
- package/build/implementations/http/hono-rpc/index.d.ts +0 -130
- package/build/implementations/http/hono-rpc/index.js +0 -209
- package/build/implementations/http/hono-rpc/index.js.map +0 -1
- package/build/implementations/http/hono-rpc/index.test.js +0 -828
- package/build/implementations/http/hono-rpc/index.test.js.map +0 -1
- package/build/implementations/http/hono-rpc/types.d.ts +0 -11
- package/build/implementations/http/hono-rpc/types.js +0 -2
- package/build/implementations/http/hono-rpc/types.js.map +0 -1
- package/build/implementations/http/hono-stream/error-taxonomy.test.js +0 -159
- package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +0 -1
- package/build/implementations/http/hono-stream/index.d.ts +0 -171
- package/build/implementations/http/hono-stream/index.js +0 -415
- package/build/implementations/http/hono-stream/index.js.map +0 -1
- package/build/implementations/http/hono-stream/index.test.js +0 -1383
- package/build/implementations/http/hono-stream/index.test.js.map +0 -1
- package/build/implementations/http/hono-stream/types.d.ts +0 -15
- package/build/implementations/http/hono-stream/types.js +0 -2
- package/build/implementations/http/hono-stream/types.js.map +0 -1
- package/src/implementations/http/express-rpc/README.md +0 -280
- package/src/implementations/http/express-rpc/error-taxonomy.test.ts +0 -103
- package/src/implementations/http/express-rpc/index.test.ts +0 -957
- package/src/implementations/http/express-rpc/index.ts +0 -327
- package/src/implementations/http/express-rpc/types.ts +0 -16
- package/src/implementations/http/hono-api/README.md +0 -284
- package/src/implementations/http/hono-api/error-taxonomy.test.ts +0 -179
- package/src/implementations/http/hono-api/index.test.ts +0 -1341
- package/src/implementations/http/hono-api/index.ts +0 -519
- package/src/implementations/http/hono-api/types.ts +0 -16
- package/src/implementations/http/hono-rpc/README.md +0 -357
- package/src/implementations/http/hono-rpc/error-taxonomy.test.ts +0 -82
- package/src/implementations/http/hono-rpc/index.test.ts +0 -1107
- package/src/implementations/http/hono-rpc/index.ts +0 -320
- package/src/implementations/http/hono-rpc/types.ts +0 -16
- package/src/implementations/http/hono-stream/README.md +0 -559
- package/src/implementations/http/hono-stream/error-taxonomy.test.ts +0 -178
- package/src/implementations/http/hono-stream/index.test.ts +0 -1804
- package/src/implementations/http/hono-stream/index.ts +0 -622
- package/src/implementations/http/hono-stream/types.ts +0 -20
- /package/build/{implementations/http/express-rpc/error-taxonomy.test.d.ts → create-http-stream.test.d.ts} +0 -0
- /package/build/{implementations/http/express-rpc/index.test.d.ts → create-http.test.d.ts} +0 -0
- /package/build/{implementations/http/hono-api/error-taxonomy.test.d.ts → create-stream.test.d.ts} +0 -0
- /package/build/{implementations/http/hono-api/index.test.d.ts → create.test.d.ts} +0 -0
- /package/build/implementations/http/{hono-rpc/error-taxonomy.test.d.ts → error-dispatch.test.d.ts} +0 -0
- /package/build/implementations/http/{hono-rpc/index.test.d.ts → hono/handlers/http-stream.test.d.ts} +0 -0
- /package/build/implementations/http/{hono-stream/error-taxonomy.test.d.ts → hono/handlers/http.test.d.ts} +0 -0
- /package/build/implementations/http/{hono-stream/index.test.d.ts → hono/handlers/rpc.test.d.ts} +0 -0
- /package/build/implementations/http/{express-rpc → hono}/types.js +0 -0
- /package/build/{implementations/http/hono-api/types.js → types.js} +0 -0
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import express from 'express'
|
|
2
|
-
import { kebabCase } from 'es-toolkit/string'
|
|
3
|
-
import { Procedures, TProcedureRegistration } from '../../../index.js'
|
|
4
|
-
import {
|
|
5
|
-
ExtractConfig,
|
|
6
|
-
ExtractContext,
|
|
7
|
-
ProceduresFactory,
|
|
8
|
-
RPCConfig,
|
|
9
|
-
RPCHttpRouteDoc,
|
|
10
|
-
} from '../../types.js'
|
|
11
|
-
import {
|
|
12
|
-
ErrorTaxonomy,
|
|
13
|
-
ErrorTaxonomyEntry,
|
|
14
|
-
UnknownErrorConfig,
|
|
15
|
-
defineErrorTaxonomy,
|
|
16
|
-
resolveErrorResponse,
|
|
17
|
-
} from '../error-taxonomy.js'
|
|
18
|
-
import { castArray } from 'es-toolkit/compat'
|
|
19
|
-
import { ExpressFactoryItem } from './types.js'
|
|
20
|
-
|
|
21
|
-
export type { RPCConfig, RPCHttpRouteDoc }
|
|
22
|
-
export { defineErrorTaxonomy }
|
|
23
|
-
export type { ErrorTaxonomy, ErrorTaxonomyEntry, UnknownErrorConfig }
|
|
24
|
-
|
|
25
|
-
export type ExpressRPCAppBuilderConfig = {
|
|
26
|
-
/**
|
|
27
|
-
* An existing Express application instance to use.
|
|
28
|
-
* When provided, ensure to set up necessary middleware (e.g., json/body parser) beforehand.
|
|
29
|
-
* If not provided, a new instance will be created.
|
|
30
|
-
*/
|
|
31
|
-
app?: express.Express
|
|
32
|
-
/** Optional path prefix for all RPC routes. */
|
|
33
|
-
pathPrefix?: string
|
|
34
|
-
onRequestStart?: (req: express.Request) => void
|
|
35
|
-
onRequestEnd?: (req: express.Request, res: express.Response) => void
|
|
36
|
-
onSuccess?: (
|
|
37
|
-
procedure: TProcedureRegistration,
|
|
38
|
-
req: express.Request,
|
|
39
|
-
res: express.Response
|
|
40
|
-
) => void
|
|
41
|
-
/**
|
|
42
|
-
* Declarative error-to-response mapping (one of the two peer error modes).
|
|
43
|
-
* See hono-api for the full taxonomy contract. The `raw` field passed to
|
|
44
|
-
* taxonomy callbacks is `{ req, res }`.
|
|
45
|
-
*/
|
|
46
|
-
errors?: ErrorTaxonomy
|
|
47
|
-
/** Fallback serializer for errors not matched by the taxonomy. */
|
|
48
|
-
unknownError?: UnknownErrorConfig
|
|
49
|
-
/**
|
|
50
|
-
* Imperative error callback — the other peer error mode. Receives every
|
|
51
|
-
* error directly and writes the response via `res`. Use this when you want
|
|
52
|
-
* full control, or alongside `errors` for the tail of errors the taxonomy
|
|
53
|
-
* doesn't cover.
|
|
54
|
-
*/
|
|
55
|
-
onError?: (
|
|
56
|
-
procedure: TProcedureRegistration,
|
|
57
|
-
req: express.Request,
|
|
58
|
-
res: express.Response,
|
|
59
|
-
error: Error
|
|
60
|
-
) => void
|
|
61
|
-
/**
|
|
62
|
-
* Cross-cutting observer — fires for every caught error, BEFORE dispatch.
|
|
63
|
-
* Awaited. Cannot write to `res` (observer only — check `res.headersSent`
|
|
64
|
-
* if you must touch it). Thrown errors inside the observer are swallowed
|
|
65
|
-
* and logged.
|
|
66
|
-
*/
|
|
67
|
-
onRequestError?: (ctx: OnRequestErrorContext) => void | Promise<void>
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Context passed to the `onRequestError` observer. `raw` is `{ req, res }`
|
|
72
|
-
* for the in-flight request.
|
|
73
|
-
*/
|
|
74
|
-
export type OnRequestErrorContext = {
|
|
75
|
-
err: unknown
|
|
76
|
-
procedure: TProcedureRegistration
|
|
77
|
-
raw: { req: express.Request; res: express.Response }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Builder class for creating an Express application with RPC routes.
|
|
82
|
-
*
|
|
83
|
-
* Usage:
|
|
84
|
-
* const PublicRPC = Procedures<PublicRPCContext, RPCConfig>()
|
|
85
|
-
* const ProtectedRPC = Procedures<ProtectedRPCContext, RPCConfig>()
|
|
86
|
-
*
|
|
87
|
-
* const rpcApp = new ExpressRPCAppBuilder()
|
|
88
|
-
* .register(PublicRPC, (req): Promise<PublicRPCContext> => { /* context resolution logic * / })
|
|
89
|
-
* .register(ProtectedRPC, (req): Promise<ProtectedRPCContext> => { /* context resolution logic * / })
|
|
90
|
-
* .build();
|
|
91
|
-
*
|
|
92
|
-
* const app = rpcApp.app; // Express application
|
|
93
|
-
* const docs = rpcApp.docs; // RPC route documentation
|
|
94
|
-
*/
|
|
95
|
-
export class ExpressRPCAppBuilder {
|
|
96
|
-
/**
|
|
97
|
-
* Constructor for ExpressRPCAppBuilder.
|
|
98
|
-
*
|
|
99
|
-
* @param config
|
|
100
|
-
*/
|
|
101
|
-
constructor(readonly config?: ExpressRPCAppBuilderConfig) {
|
|
102
|
-
if (config?.app) {
|
|
103
|
-
this._app = config.app
|
|
104
|
-
} else {
|
|
105
|
-
// Default middleware if no app is provided
|
|
106
|
-
this._app.use(express.json())
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (config?.onRequestStart) {
|
|
110
|
-
this._app.use((req, res, next) => {
|
|
111
|
-
config.onRequestStart!(req)
|
|
112
|
-
next()
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (config?.onRequestEnd) {
|
|
117
|
-
this._app.use((req, res, next) => {
|
|
118
|
-
res.on('finish', () => {
|
|
119
|
-
config.onRequestEnd!(req, res)
|
|
120
|
-
})
|
|
121
|
-
next()
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Generates the RPC route path based on the RPC configuration.
|
|
128
|
-
* `RPCConfig.scope` can be a string or an array of strings to form nested paths.
|
|
129
|
-
*
|
|
130
|
-
* Example
|
|
131
|
-
* name: 'GetUser'
|
|
132
|
-
* scope: ['users', 'profile']
|
|
133
|
-
* version: 1
|
|
134
|
-
* path: /users/profile/get-user/1
|
|
135
|
-
* @param config
|
|
136
|
-
*/
|
|
137
|
-
static makeRPCHttpRoutePath({
|
|
138
|
-
name,
|
|
139
|
-
config,
|
|
140
|
-
prefix,
|
|
141
|
-
}: {
|
|
142
|
-
name: string
|
|
143
|
-
prefix?: string
|
|
144
|
-
config: RPCConfig
|
|
145
|
-
}) {
|
|
146
|
-
const normalizedPrefix = prefix ? (prefix.startsWith('/') ? prefix : `/${prefix}`) : ''
|
|
147
|
-
|
|
148
|
-
return `${normalizedPrefix}/${castArray(config.scope).map(kebabCase).join('/')}/${kebabCase(name)}/${String(config.version).trim()}`
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Instance method wrapper for makeRPCHttpRoutePath that uses the builder's pathPrefix.
|
|
153
|
-
* @param config - The RPC configuration
|
|
154
|
-
*/
|
|
155
|
-
makeRPCHttpRoutePath(name: string, config: RPCConfig): string {
|
|
156
|
-
return ExpressRPCAppBuilder.makeRPCHttpRoutePath({
|
|
157
|
-
name,
|
|
158
|
-
config,
|
|
159
|
-
prefix: this.config?.pathPrefix,
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private factories: ExpressFactoryItem<any>[] = []
|
|
164
|
-
|
|
165
|
-
private _app: express.Express = express()
|
|
166
|
-
private _docs: (RPCHttpRouteDoc & object)[] = []
|
|
167
|
-
|
|
168
|
-
get app(): express.Express {
|
|
169
|
-
return this._app
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
get docs(): RPCHttpRouteDoc[] {
|
|
173
|
-
return this._docs
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Registers a procedure factory with its context.
|
|
178
|
-
* @param factory - The procedure factory created by Procedures<Context, RPCConfig>()
|
|
179
|
-
* @param factoryContext - The context for procedure handlers. Can be a direct value,
|
|
180
|
-
* a sync function (req) => Context, or an async function (req) => Promise<Context>
|
|
181
|
-
* @param extendProcedureDoc - A custom function to extend the generated RPC route documentation for each procedure.
|
|
182
|
-
*/
|
|
183
|
-
register<TFactory extends ProceduresFactory>(
|
|
184
|
-
factory: TFactory,
|
|
185
|
-
factoryContext:
|
|
186
|
-
| ExtractContext<TFactory>
|
|
187
|
-
| ((req: express.Request) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>),
|
|
188
|
-
extendProcedureDoc?: (params: {
|
|
189
|
-
/* RPC App builder base http route doc */
|
|
190
|
-
base: RPCHttpRouteDoc
|
|
191
|
-
/* Procedure registration */
|
|
192
|
-
procedure: TProcedureRegistration<any, ExtractConfig<TFactory>>
|
|
193
|
-
}) => Record<string, any>
|
|
194
|
-
): this {
|
|
195
|
-
this.factories.push({ factory, factoryContext, extendProcedureDoc } as ExpressFactoryItem<any>)
|
|
196
|
-
return this
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Builds and returns the Express application with registered RPC routes.
|
|
201
|
-
* @return express.Application
|
|
202
|
-
*/
|
|
203
|
-
build(): express.Application {
|
|
204
|
-
this.factories.forEach(({ factory, factoryContext, extendProcedureDoc }) => {
|
|
205
|
-
factory.getProcedures().map((procedure: TProcedureRegistration<any, RPCConfig>) => {
|
|
206
|
-
const route = this.buildRpcHttpRouteDoc(procedure, extendProcedureDoc)
|
|
207
|
-
|
|
208
|
-
this._docs.push(route)
|
|
209
|
-
|
|
210
|
-
this._app[route.method](route.path, async (req, res) => {
|
|
211
|
-
try {
|
|
212
|
-
const context =
|
|
213
|
-
typeof factoryContext === 'function'
|
|
214
|
-
? await factoryContext(req)
|
|
215
|
-
: (factoryContext as ExtractContext<typeof factory>)
|
|
216
|
-
|
|
217
|
-
let ac: AbortController | undefined
|
|
218
|
-
const ctxWithSignal = Object.defineProperty({ ...context }, 'signal', {
|
|
219
|
-
get() {
|
|
220
|
-
if (!ac) {
|
|
221
|
-
ac = new AbortController()
|
|
222
|
-
req.on('close', () => { if (!res.writableFinished) ac!.abort() })
|
|
223
|
-
}
|
|
224
|
-
return ac.signal
|
|
225
|
-
},
|
|
226
|
-
enumerable: true,
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
res.json(await procedure.handler(ctxWithSignal, req.body))
|
|
230
|
-
if (this.config?.onSuccess) {
|
|
231
|
-
this.config.onSuccess(procedure, req, res)
|
|
232
|
-
}
|
|
233
|
-
// if status not set, set to 200
|
|
234
|
-
if (!res.status) {
|
|
235
|
-
res.status(200)
|
|
236
|
-
}
|
|
237
|
-
} catch (error) {
|
|
238
|
-
if (this.config?.onRequestError) {
|
|
239
|
-
try {
|
|
240
|
-
await this.config.onRequestError({ err: error, procedure, raw: { req, res } })
|
|
241
|
-
} catch (observerErr) {
|
|
242
|
-
console.error('[ts-procedures express-rpc] onRequestError threw — swallowed:', observerErr)
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if (this.config?.errors || this.config?.unknownError) {
|
|
246
|
-
const resolved = resolveErrorResponse({
|
|
247
|
-
err: error,
|
|
248
|
-
userTaxonomy: this.config.errors,
|
|
249
|
-
unknownError: this.config.unknownError,
|
|
250
|
-
procedure,
|
|
251
|
-
raw: { req, res },
|
|
252
|
-
})
|
|
253
|
-
if (resolved) {
|
|
254
|
-
await resolved.runOnCatch()
|
|
255
|
-
if (!res.headersSent) {
|
|
256
|
-
res.status(resolved.statusCode).json(resolved.body)
|
|
257
|
-
}
|
|
258
|
-
return
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
if (this.config?.onError) {
|
|
262
|
-
this.config.onError(procedure, req, res, error as Error)
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
// Hard default — `res.status` is always truthy (it's a method),
|
|
266
|
-
// so the previous `if (!res.status)` guard never ran. Set status
|
|
267
|
-
// and body together unconditionally, respecting an already-sent
|
|
268
|
-
// response.
|
|
269
|
-
if (!res.headersSent) {
|
|
270
|
-
res.status(500).json({ error: (error as Error).message })
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
})
|
|
274
|
-
})
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
return this._app
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Generates the RPC HTTP route for the given procedure.
|
|
282
|
-
* @param procedure
|
|
283
|
-
*/
|
|
284
|
-
private buildRpcHttpRouteDoc(
|
|
285
|
-
procedure: TProcedureRegistration<any, RPCConfig>,
|
|
286
|
-
extendProcedureDoc: ExpressFactoryItem['extendProcedureDoc']
|
|
287
|
-
): RPCHttpRouteDoc {
|
|
288
|
-
const { config } = procedure
|
|
289
|
-
const path = ExpressRPCAppBuilder.makeRPCHttpRoutePath({
|
|
290
|
-
name: procedure.name,
|
|
291
|
-
config,
|
|
292
|
-
prefix: this.config?.pathPrefix,
|
|
293
|
-
})
|
|
294
|
-
const method = 'post' as const // RPCs use POST method
|
|
295
|
-
const jsonSchema: { body?: Record<string, unknown>; response?: Record<string, unknown> } = {}
|
|
296
|
-
|
|
297
|
-
if (config.schema?.params) {
|
|
298
|
-
jsonSchema.body = config.schema.params
|
|
299
|
-
}
|
|
300
|
-
if (config.schema?.returnType) {
|
|
301
|
-
jsonSchema.response = config.schema.returnType
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const base: RPCHttpRouteDoc = {
|
|
305
|
-
kind: 'rpc' as const,
|
|
306
|
-
name: procedure.name,
|
|
307
|
-
version: config.version,
|
|
308
|
-
scope: config.scope,
|
|
309
|
-
path,
|
|
310
|
-
method,
|
|
311
|
-
jsonSchema,
|
|
312
|
-
}
|
|
313
|
-
if (config.errors && config.errors.length > 0) {
|
|
314
|
-
base.errors = [...config.errors]
|
|
315
|
-
}
|
|
316
|
-
let extendedDoc: object = {}
|
|
317
|
-
|
|
318
|
-
if (extendProcedureDoc) {
|
|
319
|
-
extendedDoc = extendProcedureDoc({ base, procedure })
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
...extendedDoc,
|
|
324
|
-
...base,
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { ExtractConfig, ExtractContext, RPCConfig, RPCHttpRouteDoc } from '../../types.js'
|
|
2
|
-
import { Procedures, TProcedureRegistration } from '../../../index.js'
|
|
3
|
-
import express from 'express'
|
|
4
|
-
|
|
5
|
-
export type ExpressFactoryItem<TFactory = ReturnType<typeof Procedures<any, RPCConfig>>> = {
|
|
6
|
-
factory: TFactory
|
|
7
|
-
factoryContext:
|
|
8
|
-
| ExtractContext<TFactory>
|
|
9
|
-
| ((req: express.Request) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>)
|
|
10
|
-
extendProcedureDoc?: (params: {
|
|
11
|
-
/* RPC App builder base http route doc */
|
|
12
|
-
base: RPCHttpRouteDoc
|
|
13
|
-
/* Procedure registration */
|
|
14
|
-
procedure: TProcedureRegistration<any, ExtractConfig<TFactory>>
|
|
15
|
-
}) => Record<string, any>
|
|
16
|
-
}
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
# Hono API (REST-style) Integration
|
|
2
|
-
|
|
3
|
-
REST-style HTTP integration for Hono — routes are dispatched by HTTP method with per-channel input validation (`schema.input.pathParams`, `query`, `body`, `headers`). Works with Bun, Deno, Cloudflare Workers, and Node.js.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install ts-procedures hono
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Quick Start
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
import { Procedures } from 'ts-procedures'
|
|
15
|
-
import { HonoAPIAppBuilder, defineErrorTaxonomy } from 'ts-procedures/hono-api'
|
|
16
|
-
import type { APIConfig } from 'ts-procedures/hono-api'
|
|
17
|
-
import { Type } from 'typebox'
|
|
18
|
-
|
|
19
|
-
// ─── Procedures ───────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
const API = Procedures<{ userId: string }, APIConfig>()
|
|
22
|
-
|
|
23
|
-
API.Create('GetUser', {
|
|
24
|
-
path: '/users/:id',
|
|
25
|
-
method: 'get',
|
|
26
|
-
schema: {
|
|
27
|
-
input: {
|
|
28
|
-
pathParams: Type.Object({ id: Type.String() }),
|
|
29
|
-
},
|
|
30
|
-
returnType: Type.Object({ id: Type.String(), name: Type.String() }),
|
|
31
|
-
},
|
|
32
|
-
}, async (ctx, { pathParams }) => {
|
|
33
|
-
return { id: pathParams.id, name: 'John Doe' }
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
API.Create('CreateUser', {
|
|
37
|
-
path: '/users',
|
|
38
|
-
method: 'post', // → 201 by default
|
|
39
|
-
schema: {
|
|
40
|
-
input: { body: Type.Object({ name: Type.String(), email: Type.String() }) },
|
|
41
|
-
returnType: Type.Object({ id: Type.String() }),
|
|
42
|
-
},
|
|
43
|
-
}, async (ctx, { body }) => {
|
|
44
|
-
return { id: await createUser(body) }
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
// ─── Build ────────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
const app = new HonoAPIAppBuilder({ pathPrefix: '/api' })
|
|
50
|
-
.register(API, (c) => ({ userId: c.req.header('x-user-id') || 'anonymous' }))
|
|
51
|
-
.build()
|
|
52
|
-
|
|
53
|
-
// Routes:
|
|
54
|
-
// GET /api/users/:id → 200
|
|
55
|
-
// POST /api/users → 201
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Configuration
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
export type HonoAPIAppBuilderConfig = {
|
|
62
|
-
app?: Hono // Reuse an existing Hono instance
|
|
63
|
-
pathPrefix?: string // Prepend to every route
|
|
64
|
-
queryParser?: QueryParser // Custom query-string parser
|
|
65
|
-
onRequestStart?: (c: Context) => void
|
|
66
|
-
onRequestEnd?: (c: Context) => void
|
|
67
|
-
onSuccess?: (procedure, c: Context) => void
|
|
68
|
-
errors?: ErrorTaxonomy // Declarative error-to-response mapping
|
|
69
|
-
unknownError?: UnknownErrorConfig // Fallback for unmatched errors
|
|
70
|
-
onError?: (procedure, c, error) => Response // Imperative error callback (peer of `errors` above)
|
|
71
|
-
onRequestError?: (ctx) => void | Promise<void> // Cross-cutting observer for logging/tracing
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
| Option | Description |
|
|
76
|
-
|---|---|
|
|
77
|
-
| `app` | Existing Hono instance to register routes on. If omitted, a new `Hono()` is created. |
|
|
78
|
-
| `pathPrefix` | Prefix applied to every route (e.g. `/api/v1`). Leading slash is optional. |
|
|
79
|
-
| `queryParser` | Override the default native `URLSearchParams` parser (see *Query Parsing* below). |
|
|
80
|
-
| `onRequestStart` / `onRequestEnd` | Global lifecycle hooks — wrap every registered route. |
|
|
81
|
-
| `onSuccess` | Called after a handler returns successfully, before the response is sent. |
|
|
82
|
-
| `errors` / `unknownError` | Declarative error handling — see *Error Handling* below. |
|
|
83
|
-
| `onError` | Imperative error callback — first-class peer of the declarative taxonomy. |
|
|
84
|
-
| `onRequestError` | Cross-cutting observer — fires for every caught error before dispatch. Awaited; can't mutate the response. For logging / tracing / metrics. |
|
|
85
|
-
|
|
86
|
-
## schema.input — Multi-Channel Structured Input
|
|
87
|
-
|
|
88
|
-
REST endpoints carry input via several transport channels. `schema.input` lets you type and validate each independently:
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
API.Create('UpdatePost', {
|
|
92
|
-
path: '/posts/:id',
|
|
93
|
-
method: 'put',
|
|
94
|
-
schema: {
|
|
95
|
-
input: {
|
|
96
|
-
pathParams: Type.Object({ id: Type.String() }),
|
|
97
|
-
query: Type.Object({ draft: Type.Optional(Type.Boolean()) }),
|
|
98
|
-
body: Type.Object({ title: Type.String(), body: Type.String() }),
|
|
99
|
-
headers: Type.Object({ 'if-match': Type.String() }),
|
|
100
|
-
},
|
|
101
|
-
returnType: Type.Object({ id: Type.String(), version: Type.Number() }),
|
|
102
|
-
},
|
|
103
|
-
}, async (ctx, { pathParams, query, body, headers }) => {
|
|
104
|
-
// All four channels typed independently.
|
|
105
|
-
// AJV validates each channel; `removeAdditional` strips undeclared keys from headers.
|
|
106
|
-
})
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Supported channels: `pathParams`, `query`, `body`, `headers`. `schema.input` is mutually exclusive with `schema.params` — defining both throws `ProcedureRegistrationError` at registration time.
|
|
110
|
-
|
|
111
|
-
`HonoAPIAppBuilder` performs build-time consistency checks: `:id` in the path template must match a `pathParams.id` entry in the schema, or the builder throws at `.build()`.
|
|
112
|
-
|
|
113
|
-
### APIInput helper
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
import type { APIInput } from 'ts-procedures/hono-api'
|
|
117
|
-
|
|
118
|
-
const schema = {
|
|
119
|
-
input: {
|
|
120
|
-
pathParams: Type.Object({ id: Type.String() }),
|
|
121
|
-
qurey: Type.Object({ /* ... */ }), // ← TS error: 'qurey' not in APIInput
|
|
122
|
-
} satisfies APIInput,
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
`APIInput` constrains channel names so typos become compile errors.
|
|
127
|
-
|
|
128
|
-
## Default Success Status
|
|
129
|
-
|
|
130
|
-
| Method | Default status |
|
|
131
|
-
|---|---|
|
|
132
|
-
| `post` | 201 |
|
|
133
|
-
| `delete` | 204 |
|
|
134
|
-
| `get`, `put`, `patch`, `head` | 200 |
|
|
135
|
-
|
|
136
|
-
Override via `successStatus` on the per-route config:
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
API.Create('RemoveUser', {
|
|
140
|
-
path: '/users/:id',
|
|
141
|
-
method: 'delete',
|
|
142
|
-
successStatus: 200, // Override the default 204
|
|
143
|
-
schema: { input: { pathParams: Type.Object({ id: Type.String() }) } },
|
|
144
|
-
}, async (ctx, { pathParams }) => { /* ... */ })
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## Query Parsing
|
|
148
|
-
|
|
149
|
-
Default: native `URLSearchParams`. Handles flat keys (`?page=2`) and repeated keys (`?tag=a&tag=b → { tag: ['a', 'b'] }`). It does **not** parse bracket objects, bracket arrays, dot paths, or comma-split arrays.
|
|
150
|
-
|
|
151
|
-
Opt in to richer parsing via `qs`:
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
import qs from 'qs'
|
|
155
|
-
|
|
156
|
-
new HonoAPIAppBuilder({
|
|
157
|
-
queryParser: (raw) => qs.parse(raw) as Record<string, unknown>,
|
|
158
|
-
})
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Context Resolution
|
|
162
|
-
|
|
163
|
-
The context resolver receives Hono's `Context` object:
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
builder.register(API, (c) => ({
|
|
167
|
-
userId: c.req.header('x-user-id') || 'anonymous',
|
|
168
|
-
requestId: c.req.header('x-request-id') ?? crypto.randomUUID(),
|
|
169
|
-
}))
|
|
170
|
-
|
|
171
|
-
// Async context resolution — authenticate per request
|
|
172
|
-
builder.register(API, async (c) => {
|
|
173
|
-
const token = c.req.header('authorization')?.replace('Bearer ', '')
|
|
174
|
-
const user = await verifyToken(token)
|
|
175
|
-
return { userId: user.id, roles: user.roles }
|
|
176
|
-
})
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## Abort Signal
|
|
180
|
-
|
|
181
|
-
`HonoAPIAppBuilder` injects `c.req.raw.signal` as `ctx.signal` in every handler so downstream async calls (fetch, DB queries) can cancel when the client disconnects.
|
|
182
|
-
|
|
183
|
-
```typescript
|
|
184
|
-
API.Create('StreamingQuery', { /* ... */ }, async (ctx) => {
|
|
185
|
-
const result = await db.query(sql, { signal: ctx.signal })
|
|
186
|
-
return result
|
|
187
|
-
})
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
## Error Handling
|
|
191
|
-
|
|
192
|
-
Declare error classes via `defineErrorTaxonomy` and pass them to the builder's `errors` option. Handlers `throw` their classes; the builder auto-serializes to the configured status + body.
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
import { defineErrorTaxonomy } from 'ts-procedures/hono-api'
|
|
196
|
-
|
|
197
|
-
const appErrors = defineErrorTaxonomy({
|
|
198
|
-
NotFoundError: { class: NotFoundError, statusCode: 404 },
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
new HonoAPIAppBuilder({
|
|
202
|
-
errors: appErrors,
|
|
203
|
-
unknownError: { toResponse: () => ({ error: 'Internal server error' }) },
|
|
204
|
-
})
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
Full contract (both peer error modes, `onRequestError` observer, per-route narrowing via `APIConfig<keyof typeof appErrors & string>`): see **[docs/http-integrations.md § Error Handling](../../../../docs/http-integrations.md#error-handling)** — the canonical explanation shared across all four HTTP builders.
|
|
208
|
-
|
|
209
|
-
## Extending Procedure Documentation
|
|
210
|
-
|
|
211
|
-
Like the other builders, `register()` accepts an optional third argument that extends each route's generated doc object:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
builder.register(API, ctxResolver, ({ base, procedure }) => ({
|
|
215
|
-
summary: procedure.config.description,
|
|
216
|
-
tags: [base.scope ?? 'default'],
|
|
217
|
-
deprecated: procedure.config.description?.toLowerCase().includes('deprecated') ?? false,
|
|
218
|
-
}))
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
The extended doc is spread onto the `APIHttpRouteDoc` before `base`, so base fields (`kind`, `name`, `method`, `path`, `fullPath`, `jsonSchema`, `errors`) always win.
|
|
222
|
-
|
|
223
|
-
## Using an Existing Hono App
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
const app = new Hono()
|
|
227
|
-
app.use('*', cors())
|
|
228
|
-
app.get('/health', (c) => c.json({ ok: true }))
|
|
229
|
-
|
|
230
|
-
new HonoAPIAppBuilder({ app, pathPrefix: '/api' })
|
|
231
|
-
.register(API, contextResolver)
|
|
232
|
-
.build()
|
|
233
|
-
|
|
234
|
-
// API routes added alongside your custom routes.
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
## Route Documentation
|
|
238
|
-
|
|
239
|
-
Each registered procedure generates an `APIHttpRouteDoc` accessible via `builder.docs`:
|
|
240
|
-
|
|
241
|
-
```typescript
|
|
242
|
-
interface APIHttpRouteDoc {
|
|
243
|
-
kind: 'api'
|
|
244
|
-
name: string
|
|
245
|
-
scope?: string
|
|
246
|
-
path: string
|
|
247
|
-
fullPath: string // path with pathPrefix applied
|
|
248
|
-
method: HttpMethod
|
|
249
|
-
successStatus?: number
|
|
250
|
-
jsonSchema: {
|
|
251
|
-
pathParams?: Record<string, unknown>
|
|
252
|
-
query?: Record<string, unknown>
|
|
253
|
-
body?: Record<string, unknown>
|
|
254
|
-
headers?: Record<string, unknown>
|
|
255
|
-
response?: Record<string, unknown>
|
|
256
|
-
}
|
|
257
|
-
errors?: string[] // Taxonomy keys this route may emit
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
Feed these into `DocRegistry` to compose a single `/docs` endpoint from multiple builders — see [docs/http-integrations.md § DocRegistry](../../../../docs/http-integrations.md#docregistry--composing-docs-from-multiple-builders).
|
|
262
|
-
|
|
263
|
-
## Runtime Compatibility
|
|
264
|
-
|
|
265
|
-
Runs wherever Hono runs: Bun, Deno, Cloudflare Workers, Node.js 18+. Uses standard Fetch API (`c.req.raw.signal`, `Request`, `Response`) — no Node-specific APIs.
|
|
266
|
-
|
|
267
|
-
## TypeScript Types
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
import type {
|
|
271
|
-
APIConfig,
|
|
272
|
-
APIHttpRouteDoc,
|
|
273
|
-
APIInput,
|
|
274
|
-
HttpMethod,
|
|
275
|
-
HonoAPIAppBuilderConfig,
|
|
276
|
-
QueryParser,
|
|
277
|
-
ErrorTaxonomy,
|
|
278
|
-
ErrorTaxonomyEntry,
|
|
279
|
-
UnknownErrorConfig,
|
|
280
|
-
OnRequestErrorContext,
|
|
281
|
-
} from 'ts-procedures/hono-api'
|
|
282
|
-
|
|
283
|
-
import { HonoAPIAppBuilder, defineErrorTaxonomy } from 'ts-procedures/hono-api'
|
|
284
|
-
```
|