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
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# HonoAppBuilder Convergence — Design
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-05-08
|
|
4
|
+
**Status:** Draft for review
|
|
5
|
+
**Scope:** Hono only
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
`ts-procedures` currently exposes three Hono builders — `HonoRPCAppBuilder`, `HonoAPIAppBuilder`, `HonoStreamAppBuilder` — totaling ~1,580 lines of near-parallel code (constructor, `register()`, `build()`, error dispatch, factory iteration, doc emission). Each filters procedures by `kind` and warn-skips mismatches.
|
|
10
|
+
|
|
11
|
+
Since v8, a single `Procedures<TContext>()` factory produces all four procedure kinds (`rpc`, `rpc-stream`, `http`, `http-stream`) from one call. Splitting registration across three builders is no longer a structural necessity — it's friction. A user wiring a typical app today does:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const rpc = new HonoRPCAppBuilder({ app, errors }).register(F1, ctx1)
|
|
15
|
+
const api = new HonoAPIAppBuilder({ app, errors }).register(F2, ctx2)
|
|
16
|
+
const stream = new HonoStreamAppBuilder({ app, errors, onMidStreamError }).register(F3, ctx3)
|
|
17
|
+
rpc.build(); api.build(); stream.build()
|
|
18
|
+
new DocRegistry().from(rpc).from(api).from(stream).toJSON()
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
…three constructors, three `errors` config objects, three `build()` calls, an explicit shared `app`, and a DocRegistry assembly step — to do what is conceptually one thing: "mount these procedures on this Hono server."
|
|
22
|
+
|
|
23
|
+
## Goal
|
|
24
|
+
|
|
25
|
+
A single `HonoAppBuilder` that accepts any `Procedures<>()` factory and mounts every procedure kind correctly, with the appropriate documentation surfaced for the codegen pipeline. The current four-builder surface is removed.
|
|
26
|
+
|
|
27
|
+
## Non-Goals
|
|
28
|
+
|
|
29
|
+
- **Removing or rewriting `DocRegistry`.** It remains the multi-app aggregation primitive.
|
|
30
|
+
- **Changing per-procedure config.** `RPCConfig`, `APIConfig`, the `CreateHttp`/`CreateStream`/`CreateHttpStream` shapes are unchanged. The convergence is at the *builder* layer, not the procedure-definition layer.
|
|
31
|
+
- **Path generation rule changes.** RPC/rpc-stream still use `pathPrefix + kebab(scope)/kebab(name)/version`. HTTP/http-stream still use `pathPrefix + config.path`.
|
|
32
|
+
- **Error taxonomy semantics.** `defineErrorTaxonomy`, `resolveErrorResponse`, mid-stream error wire shape, and the `onRequestError` observer all keep their current behavior.
|
|
33
|
+
|
|
34
|
+
## Decisions
|
|
35
|
+
|
|
36
|
+
| Decision | Choice | Rationale |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| Convergence scope | Full collapse, remove old builders, no deprecation period | v8 has not been published; v8 is already a breaking release from v7. Bundling this convergence with the v8 break avoids a second migration. |
|
|
39
|
+
| Config shape for kind-specific knobs | Stratified — top-level cross-cutting + nested `rpc`/`api`/`stream` blocks | Type-safe, discoverable; users who only register one kind don't see clutter from other kinds. |
|
|
40
|
+
| Documentation | Builder gains `toDocEnvelope()` for single-app; `DocRegistry` stays for multi-app aggregation | Common case is one builder per app — `toDocEnvelope()` removes the registry boilerplate. Multi-app users keep the existing primitive. |
|
|
41
|
+
| Builder name | `HonoAppBuilder` | Drops the kind suffix (RPC/API/Stream) since it now serves all kinds. |
|
|
42
|
+
| Non-streaming hooks (`onSuccess`) placement | Inside stratified blocks (`rpc.onSuccess`, `api.onSuccess`) | Strict typing per kind; users wiring a unified metrics callback assign the same function to both blocks. Non-breaking to add a top-level `onSuccess` later if duplication is a real pain point. |
|
|
43
|
+
| Shared error-dispatch module location | `src/implementations/http/error-dispatch.ts` (one level up from `hono/`) | Lives outside the framework-specific subdir so it can be reused by future framework adapters. |
|
|
44
|
+
| Path-builder static method | `HonoAppBuilder.makeRoutePath({ procedure, prefix })` — handles all four kinds | Tooling consumers get one entry point regardless of kind. |
|
|
45
|
+
|
|
46
|
+
## Public API
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { HonoAppBuilder } from 'ts-procedures/hono'
|
|
50
|
+
|
|
51
|
+
const { Create, CreateStream, CreateHttp, CreateHttpStream } =
|
|
52
|
+
Procedures<AppCtx>()
|
|
53
|
+
|
|
54
|
+
// ... define procedures with any combination of the four creators ...
|
|
55
|
+
|
|
56
|
+
const builder = new HonoAppBuilder({
|
|
57
|
+
app, // optional pre-existing Hono
|
|
58
|
+
pathPrefix: '/api/v1',
|
|
59
|
+
errors: appErrors, // ErrorTaxonomy, cross-cutting
|
|
60
|
+
unknownError,
|
|
61
|
+
onError, // imperative peer of `errors`
|
|
62
|
+
onRequestError, // observer
|
|
63
|
+
onRequestStart,
|
|
64
|
+
onRequestEnd,
|
|
65
|
+
rpc: {
|
|
66
|
+
onSuccess: (proc, c) => {},
|
|
67
|
+
},
|
|
68
|
+
api: {
|
|
69
|
+
queryParser: qs.parse,
|
|
70
|
+
onSuccess: (proc, c) => {},
|
|
71
|
+
},
|
|
72
|
+
stream: {
|
|
73
|
+
defaultStreamMode: 'sse', // applies to rpc-stream + http-stream
|
|
74
|
+
onStreamStart, onStreamEnd,
|
|
75
|
+
onMidStreamError,
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
builder
|
|
80
|
+
.register(AppFactory, ctxResolver)
|
|
81
|
+
.register(InternalFactory, internalCtxResolver, {
|
|
82
|
+
streamMode: 'text', // optional per-factory override
|
|
83
|
+
extendProcedureDoc: ({ base }) => {
|
|
84
|
+
switch (base.kind) {
|
|
85
|
+
case 'rpc': return { internal: true }
|
|
86
|
+
case 'api': return { internal: true, group: 'admin' }
|
|
87
|
+
case 'stream': return { internal: true }
|
|
88
|
+
case 'http-stream': return { internal: true }
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const honoApp = builder.build() // returns Hono
|
|
94
|
+
const docs = builder.docs // AnyHttpRouteDoc[] (lazy-cached)
|
|
95
|
+
const envelope = builder.toDocEnvelope() // DocEnvelope (single-app)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Config Type
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
export type HonoAppBuilderConfig<TStreamErrorData = unknown> = {
|
|
102
|
+
// Cross-cutting
|
|
103
|
+
app?: Hono
|
|
104
|
+
pathPrefix?: string
|
|
105
|
+
|
|
106
|
+
errors?: ErrorTaxonomy
|
|
107
|
+
unknownError?: UnknownErrorConfig
|
|
108
|
+
onError?: (
|
|
109
|
+
procedure: AnyProcedureRegistration,
|
|
110
|
+
c: Context,
|
|
111
|
+
error: Error,
|
|
112
|
+
) => Response | Promise<Response>
|
|
113
|
+
onRequestError?: (ctx: OnRequestErrorContext) => void | Promise<void>
|
|
114
|
+
|
|
115
|
+
onRequestStart?: (c: Context) => void
|
|
116
|
+
onRequestEnd?: (c: Context) => void
|
|
117
|
+
|
|
118
|
+
// Kind-specific
|
|
119
|
+
rpc?: {
|
|
120
|
+
onSuccess?: (procedure: TProcedureRegistration, c: Context) => void
|
|
121
|
+
}
|
|
122
|
+
api?: {
|
|
123
|
+
queryParser?: QueryParser
|
|
124
|
+
onSuccess?: (
|
|
125
|
+
procedure: THttpProcedureRegistration<any>,
|
|
126
|
+
c: Context,
|
|
127
|
+
) => void
|
|
128
|
+
}
|
|
129
|
+
stream?: {
|
|
130
|
+
defaultStreamMode?: StreamMode
|
|
131
|
+
onStreamStart?: (
|
|
132
|
+
procedure: TStreamProcedureRegistration | THttpStreamProcedureRegistration<any>,
|
|
133
|
+
c: Context,
|
|
134
|
+
streamMode: StreamMode,
|
|
135
|
+
) => void
|
|
136
|
+
onStreamEnd?: (
|
|
137
|
+
procedure: TStreamProcedureRegistration | THttpStreamProcedureRegistration<any>,
|
|
138
|
+
c: Context,
|
|
139
|
+
streamMode: StreamMode,
|
|
140
|
+
) => void
|
|
141
|
+
onMidStreamError?: (
|
|
142
|
+
procedure: TStreamProcedureRegistration | THttpStreamProcedureRegistration<any>,
|
|
143
|
+
c: Context,
|
|
144
|
+
error: Error,
|
|
145
|
+
) => MidStreamErrorResult<TStreamErrorData> | undefined
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type AnyProcedureRegistration =
|
|
150
|
+
| TProcedureRegistration
|
|
151
|
+
| TStreamProcedureRegistration
|
|
152
|
+
| THttpProcedureRegistration<any>
|
|
153
|
+
| THttpStreamProcedureRegistration<any>
|
|
154
|
+
|
|
155
|
+
export type OnRequestErrorContext = {
|
|
156
|
+
err: unknown
|
|
157
|
+
procedure: AnyProcedureRegistration
|
|
158
|
+
raw: Context
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### `register()`
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
register<TFactory extends ProceduresFactory>(
|
|
166
|
+
factory: TFactory,
|
|
167
|
+
factoryContext:
|
|
168
|
+
| ExtractContext<TFactory>
|
|
169
|
+
| ((c: Context) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>),
|
|
170
|
+
options?: {
|
|
171
|
+
/** Per-factory override for stream.defaultStreamMode. */
|
|
172
|
+
streamMode?: StreamMode
|
|
173
|
+
/** Extend route docs. base is a kind-discriminated union. */
|
|
174
|
+
extendProcedureDoc?: (params: {
|
|
175
|
+
base: RPCHttpRouteDoc | APIHttpRouteDoc | StreamHttpRouteDoc | HttpStreamRouteDoc
|
|
176
|
+
procedure: AnyProcedureRegistration
|
|
177
|
+
}) => Record<string, unknown>
|
|
178
|
+
},
|
|
179
|
+
): this
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `toDocEnvelope()`
|
|
183
|
+
|
|
184
|
+
Mirrors `DocRegistry.toJSON()`'s option surface so the produced envelope is identical and the codegen pipeline reads either interchangeably:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
toDocEnvelope(options?: {
|
|
188
|
+
basePath?: string
|
|
189
|
+
errors?: ErrorTaxonomy | ErrorDoc[]
|
|
190
|
+
includeDefaults?: boolean
|
|
191
|
+
filter?: (route: AnyHttpRouteDoc) => boolean
|
|
192
|
+
transform?: (route: AnyHttpRouteDoc) => AnyHttpRouteDoc
|
|
193
|
+
}): DocEnvelope
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`builder.docs` exposes the underlying array (lazy-computed on first read or on `build()`, whichever happens first; cached thereafter).
|
|
197
|
+
|
|
198
|
+
### Static Path Builder
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
HonoAppBuilder.makeRoutePath({
|
|
202
|
+
procedure: AnyProcedureRegistration,
|
|
203
|
+
prefix?: string,
|
|
204
|
+
}): string
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- For `rpc` / `rpc-stream`: `{prefix}/{kebab(scope)}/{kebab(name)}/{version}`
|
|
208
|
+
- For `http` / `http-stream`: `{prefix}{config.path}` (prefix normalized to leading `/`)
|
|
209
|
+
|
|
210
|
+
## Internal Architecture
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
src/implementations/http/
|
|
214
|
+
├── error-dispatch.ts # SHARED — pre-stream + mid-stream dispatchers
|
|
215
|
+
├── error-taxonomy.ts # unchanged
|
|
216
|
+
├── doc-registry.ts # unchanged
|
|
217
|
+
├── hono/
|
|
218
|
+
│ ├── index.ts # HonoAppBuilder (~200 lines)
|
|
219
|
+
│ ├── path.ts # makeRoutePath, resolveFullPath
|
|
220
|
+
│ ├── types.ts # HonoAppBuilderConfig, factory item
|
|
221
|
+
│ ├── handlers/
|
|
222
|
+
│ │ ├── rpc.ts # installRpcRoute(...)
|
|
223
|
+
│ │ ├── http.ts # installHttpRoute(...)
|
|
224
|
+
│ │ ├── stream.ts # installRpcStreamRoute(...)
|
|
225
|
+
│ │ └── http-stream.ts # installHttpStreamRoute(...)
|
|
226
|
+
│ └── docs/
|
|
227
|
+
│ ├── rpc-doc.ts # buildRpcRouteDoc
|
|
228
|
+
│ ├── http-doc.ts # buildHttpRouteDoc
|
|
229
|
+
│ ├── stream-doc.ts # buildStreamRouteDoc
|
|
230
|
+
│ └── http-stream-doc.ts # buildHttpStreamRouteDoc
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### `build()` Dispatch Loop
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
build(): Hono {
|
|
237
|
+
for (const item of this._factories) {
|
|
238
|
+
for (const procedure of item.factory.getProcedures().values()) {
|
|
239
|
+
switch (procedure.kind) {
|
|
240
|
+
case 'rpc':
|
|
241
|
+
installRpcRoute(this._app, procedure, item, this.config, this._docs)
|
|
242
|
+
break
|
|
243
|
+
case 'rpc-stream':
|
|
244
|
+
installRpcStreamRoute(this._app, procedure, item, this.config, this._docs)
|
|
245
|
+
break
|
|
246
|
+
case 'http':
|
|
247
|
+
installHttpRoute(this._app, procedure, item, this.config, this._docs)
|
|
248
|
+
break
|
|
249
|
+
case 'http-stream':
|
|
250
|
+
installHttpStreamRoute(this._app, procedure, item, this.config, this._docs)
|
|
251
|
+
break
|
|
252
|
+
default: {
|
|
253
|
+
const reason = `Unknown procedure kind "${(procedure as any).kind}"`
|
|
254
|
+
this._skipped.push({ name: procedure.name, reason })
|
|
255
|
+
console.warn(`[ts-procedures hono] ${reason}`)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return this._app
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Shared Error Dispatch
|
|
265
|
+
|
|
266
|
+
`src/implementations/http/error-dispatch.ts` exports two functions that consolidate the three current near-identical implementations:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
// Used by rpc, http, and the pre-stream guards in stream / http-stream.
|
|
270
|
+
// Returns a Response.
|
|
271
|
+
export async function dispatchPreStreamError(args: {
|
|
272
|
+
err: unknown
|
|
273
|
+
procedure: AnyProcedureRegistration
|
|
274
|
+
raw: Context
|
|
275
|
+
cfg: {
|
|
276
|
+
errors?: ErrorTaxonomy
|
|
277
|
+
unknownError?: UnknownErrorConfig
|
|
278
|
+
onError?: HonoAppBuilderConfig['onError']
|
|
279
|
+
onRequestError?: HonoAppBuilderConfig['onRequestError']
|
|
280
|
+
}
|
|
281
|
+
}): Promise<Response>
|
|
282
|
+
|
|
283
|
+
// Used inside stream generator catch blocks. Returns the data to write
|
|
284
|
+
// (plus optional SSE metadata + onCatch hook), not a full Response — the
|
|
285
|
+
// HTTP status is already committed once streaming starts.
|
|
286
|
+
export async function dispatchMidStreamError<TData = unknown>(args: {
|
|
287
|
+
err: unknown
|
|
288
|
+
procedure: TStreamProcedureRegistration | THttpStreamProcedureRegistration<any>
|
|
289
|
+
raw: Context
|
|
290
|
+
cfg: {
|
|
291
|
+
errors?: ErrorTaxonomy
|
|
292
|
+
unknownError?: UnknownErrorConfig
|
|
293
|
+
onMidStreamError?: HonoAppBuilderConfig<TData>['stream']['onMidStreamError']
|
|
294
|
+
onRequestError?: HonoAppBuilderConfig['onRequestError']
|
|
295
|
+
}
|
|
296
|
+
}): Promise<{
|
|
297
|
+
data: unknown
|
|
298
|
+
sseEvent?: string
|
|
299
|
+
sseId?: string
|
|
300
|
+
sseRetry?: number
|
|
301
|
+
runOnCatch?: () => Promise<void>
|
|
302
|
+
}>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Both call the `onRequestError` observer first (swallowing observer throws) and then dispatch through taxonomy → onError/onMidStreamError → hard default — same order as today.
|
|
306
|
+
|
|
307
|
+
### Per-Kind Handler Modules
|
|
308
|
+
|
|
309
|
+
Each handler module is a small file (~80–180 lines) exporting one `install*Route` function. Their job:
|
|
310
|
+
|
|
311
|
+
1. Build the route doc (`docs/<kind>-doc.ts`) and push to `_docs`.
|
|
312
|
+
2. Resolve the route path (`path.ts`).
|
|
313
|
+
3. Register the Hono handler that:
|
|
314
|
+
- Resolves the factory context (sync object | sync fn | async fn).
|
|
315
|
+
- Extracts and assembles params from the request (per kind: body for rpc; structured req for http/http-stream; query-or-body for rpc-stream).
|
|
316
|
+
- Invokes `procedure.handler({ ...ctx, signal: c.req.raw.signal }, params)`.
|
|
317
|
+
- For streams: forwards `isPrevalidated: true` after the pre-stream validation so core doesn't re-validate.
|
|
318
|
+
- Catches and routes through `dispatchPreStreamError`.
|
|
319
|
+
- For streams: forwards mid-stream throws through `dispatchMidStreamError` and writes the result via `streamSSE`/`streamText`.
|
|
320
|
+
- Calls the right kind-specific lifecycle hook (`rpc.onSuccess`, `api.onSuccess`, `stream.onStreamStart`/`onStreamEnd`).
|
|
321
|
+
|
|
322
|
+
Module-level coupling: each handler imports only the slice of config it needs (e.g., `handlers/stream.ts` imports `stream.*` and the cross-cutting error config). The full `HonoAppBuilderConfig` type lives in `hono/types.ts`.
|
|
323
|
+
|
|
324
|
+
### Lazy Doc Computation
|
|
325
|
+
|
|
326
|
+
Today `_docs` is populated during `build()` — calling `builder.docs` before `build()` returns an empty array. New behavior: `docs` is a getter that runs the per-kind doc-builders (`docs/<kind>-doc.ts`) without touching route registration; the result is cached. `build()` reuses the cached array. This makes `toDocEnvelope()` usable in tests without spinning up handlers.
|
|
327
|
+
|
|
328
|
+
## Migration Impact
|
|
329
|
+
|
|
330
|
+
Since v8 is unpublished, there's no in-the-wild deprecation cycle — we update docs, templates, and tests and ship.
|
|
331
|
+
|
|
332
|
+
### Deletions
|
|
333
|
+
|
|
334
|
+
- `src/implementations/http/hono-rpc/` (entire directory)
|
|
335
|
+
- `src/implementations/http/hono-api/` (entire directory)
|
|
336
|
+
- `src/implementations/http/hono-stream/` (entire directory)
|
|
337
|
+
- The corresponding `package.json#exports` entries: `./hono-rpc`, `./hono-api`, `./hono-stream`
|
|
338
|
+
|
|
339
|
+
### Additions
|
|
340
|
+
|
|
341
|
+
- `src/implementations/http/hono/` (per the layout above)
|
|
342
|
+
- `package.json#exports` entry: `./hono`
|
|
343
|
+
- `src/implementations/http/error-dispatch.ts` — shared dispatchers (replacing inline copies in three builders)
|
|
344
|
+
|
|
345
|
+
### Re-exports From `ts-procedures/hono`
|
|
346
|
+
|
|
347
|
+
`HonoAppBuilder`, `HonoAppBuilderConfig`, `OnRequestErrorContext`, `AnyProcedureRegistration`, `RPCConfig`, `RPCHttpRouteDoc`, `APIConfig`, `APIHttpRouteDoc`, `APIInput`, `StreamHttpRouteDoc`, `HttpStreamRouteDoc`, `StreamMode`, `HttpMethod`, `QueryParser`, `MidStreamErrorResult`, `sse`, `defineErrorTaxonomy`, `ErrorTaxonomy`, `ErrorTaxonomyEntry`, `UnknownErrorConfig`.
|
|
348
|
+
|
|
349
|
+
`./http-errors` and `./http-docs` (DocRegistry) keep their current subpaths.
|
|
350
|
+
|
|
351
|
+
### User-Facing Migration Table
|
|
352
|
+
|
|
353
|
+
| Before (in-flight v8) | After |
|
|
354
|
+
|---|---|
|
|
355
|
+
| `import { HonoRPCAppBuilder } from 'ts-procedures/hono-rpc'` | `import { HonoAppBuilder } from 'ts-procedures/hono'` |
|
|
356
|
+
| `import { HonoAPIAppBuilder } from 'ts-procedures/hono-api'` | same |
|
|
357
|
+
| `import { HonoStreamAppBuilder } from 'ts-procedures/hono-stream'` | same |
|
|
358
|
+
| `new HonoRPCAppBuilder({ errors, onSuccess })` | `new HonoAppBuilder({ errors, rpc: { onSuccess } })` |
|
|
359
|
+
| `new HonoAPIAppBuilder({ errors, queryParser })` | `new HonoAppBuilder({ errors, api: { queryParser } })` |
|
|
360
|
+
| `new HonoStreamAppBuilder({ errors, onMidStreamError, defaultStreamMode })` | `new HonoAppBuilder({ errors, stream: { onMidStreamError, defaultStreamMode } })` |
|
|
361
|
+
| Three builders sharing `app: Hono` | One builder, no shared-`app` plumbing |
|
|
362
|
+
| `new DocRegistry().from(rpc).from(api).from(stream).toJSON()` | `builder.toDocEnvelope()` for single-app; DocRegistry only for multi-app aggregation |
|
|
363
|
+
|
|
364
|
+
### Edge Cases & Behavior Preservation
|
|
365
|
+
|
|
366
|
+
1. **Mixed-kind factories work natively.** A single `Procedures<>()` factory with `Create`/`CreateStream`/`CreateHttp`/`CreateHttpStream` calls dispatches correctly under one builder.
|
|
367
|
+
2. **`pathPrefix` is global to the builder.** Users who genuinely need different prefixes per kind (rare) instantiate two `HonoAppBuilder`s sharing the same `app`.
|
|
368
|
+
3. **`extendProcedureDoc`** receives a discriminated union — switch on `base.kind`.
|
|
369
|
+
4. **Per-factory `streamMode` override** stays via `register(factory, ctx, { streamMode })`. Applies to both rpc-stream and http-stream procedures within that factory.
|
|
370
|
+
5. **`AbortSignal` injection, SSE `event: 'return'`, `isPrevalidated`, `noRuntimeValidation`** — all preserved.
|
|
371
|
+
6. **`skippedProcedures`** stays on the public surface but is expected to always be empty post-convergence (defensive logging for future kinds).
|
|
372
|
+
7. **`HonoAppBuilder` satisfies `DocSource`** — multi-app users continue to assemble via `DocRegistry`.
|
|
373
|
+
|
|
374
|
+
### Tests
|
|
375
|
+
|
|
376
|
+
Consolidated under `src/implementations/http/hono/`:
|
|
377
|
+
|
|
378
|
+
- `index.test.ts` — public surface (constructor, register, build, docs, toDocEnvelope, makeRoutePath)
|
|
379
|
+
- `handlers/rpc.test.ts`, `handlers/http.test.ts`, `handlers/stream.test.ts`, `handlers/http-stream.test.ts` — per-kind handler behavior, taking the place of the three separate `index.test.ts` files
|
|
380
|
+
- `../error-dispatch.test.ts` — shared error dispatch (folds in `error-taxonomy.test.ts` content from each old builder)
|
|
381
|
+
|
|
382
|
+
Top-level shared tests at `src/implementations/http/` stay: `error-taxonomy.test.ts`, `doc-registry.test.ts`, `on-request-error.test.ts`, `route-errors.test.ts`.
|
|
383
|
+
|
|
384
|
+
Codegen e2e fixtures consume `DocEnvelope` JSON — unaffected.
|
|
385
|
+
|
|
386
|
+
### Templates & Docs
|
|
387
|
+
|
|
388
|
+
- `agent_config/claude-code/skills/ts-procedures-scaffold/templates/` — delete `hono-rpc.ts`, `hono-stream.ts`; rewrite `hono-api.ts` (or rename to `hono.ts`) demonstrating one builder serving all kinds.
|
|
389
|
+
- `agent_config/claude-code/skills/ts-procedures/{SKILL.md, patterns.md, anti-patterns.md}` — replace per-builder examples with single-builder examples.
|
|
390
|
+
- `agent_config/copilot/copilot-instructions.md` and `agent_config/cursor/cursorrules` — same updates (these mirror the Claude skill content).
|
|
391
|
+
- `src/implementations/http/README.md` — rewrite around `HonoAppBuilder`. Drop the "RPC vs Streaming vs API builder" sectioning. Replace with one section + a "Which procedure kind to use" table.
|
|
392
|
+
- `docs/http-integrations.md` — rewrite around the unified builder. Delete the "One Hono Server, Multiple Builders" section (no longer relevant).
|
|
393
|
+
- README v8 changelog — update the convergence entry.
|
|
394
|
+
|
|
395
|
+
## Test Plan
|
|
396
|
+
|
|
397
|
+
1. **Public-surface contract** — constructor stores config; `register()` is chainable; `build()` returns `Hono`; `docs` is lazy and stable across multiple reads; `toDocEnvelope()` produces a DocEnvelope identical to a `DocRegistry({ basePath, errors }).from(builder).toJSON()` output for the same builder.
|
|
398
|
+
2. **Per-kind dispatch** — register a single factory containing one procedure of each of the four kinds; verify each is mounted at the correct path with the correct method(s) and produces the correct doc shape.
|
|
399
|
+
3. **Mixed factories** — register two factories with different `factoryContext` resolvers; verify each procedure receives the correct context and that the resolver runs once per request.
|
|
400
|
+
4. **Stratified config** — `rpc.onSuccess` fires only for rpc; `api.onSuccess` fires only for http; `stream.onStreamStart`/`onStreamEnd`/`onMidStreamError` fire only for streaming kinds. Cross-cutting hooks (`onRequestStart`/`onRequestEnd`/`onRequestError`) fire for every kind.
|
|
401
|
+
5. **Error dispatch parity** — port the existing taxonomy tests from each old builder. Pre-stream errors flow through `dispatchPreStreamError`; mid-stream errors flow through `dispatchMidStreamError` with status already committed. `onRequestError` observer fires before dispatch in both paths and its throws are swallowed.
|
|
402
|
+
6. **Path generation** — `makeRoutePath` returns the expected URL for each kind; with and without `prefix`; with array vs. string `scope`.
|
|
403
|
+
7. **Per-factory `streamMode` override** — registering with `{ streamMode: 'text' }` overrides `stream.defaultStreamMode` for that factory's stream procedures only.
|
|
404
|
+
8. **`extendProcedureDoc`** — receives a kind-discriminated `base`; returned fields are merged onto the route doc; `kind` cannot be overwritten.
|
|
405
|
+
9. **DocSource compatibility** — `new DocRegistry().from(builder).toJSON()` produces a valid envelope matching `builder.toDocEnvelope()` (modulo `basePath` if not specified).
|
|
406
|
+
10. **Codegen end-to-end** — feed `builder.toDocEnvelope()` into `generateClient()` for the TS / Kotlin / Swift targets and verify the generated output compiles. Use the existing fixture envelope structure.
|
|
407
|
+
|
|
408
|
+
## Out of Scope (Follow-ups)
|
|
409
|
+
|
|
410
|
+
- **Top-level `onSuccess` shortcut** — if the duplicated `rpc.onSuccess` + `api.onSuccess` pattern shows up in real apps, add a top-level `onSuccess` that fans out to both. Non-breaking when added.
|
|
411
|
+
- **Different `pathPrefix` per kind** — if users actually need this, add `rpc.pathPrefix` / `api.pathPrefix` / `stream.pathPrefix` overrides. Today the workaround (two builders sharing one `app`) is acceptable.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-procedures",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.",
|
|
5
5
|
"main": "build/exports.js",
|
|
6
6
|
"types": "build/exports.d.ts",
|
|
@@ -30,21 +30,9 @@
|
|
|
30
30
|
"./http": {
|
|
31
31
|
"types": "./build/implementations/types.d.ts"
|
|
32
32
|
},
|
|
33
|
-
"./
|
|
34
|
-
"types": "./build/implementations/http/
|
|
35
|
-
"import": "./build/implementations/http/
|
|
36
|
-
},
|
|
37
|
-
"./hono-rpc": {
|
|
38
|
-
"types": "./build/implementations/http/hono-rpc/index.d.ts",
|
|
39
|
-
"import": "./build/implementations/http/hono-rpc/index.js"
|
|
40
|
-
},
|
|
41
|
-
"./hono-stream": {
|
|
42
|
-
"types": "./build/implementations/http/hono-stream/index.d.ts",
|
|
43
|
-
"import": "./build/implementations/http/hono-stream/index.js"
|
|
44
|
-
},
|
|
45
|
-
"./hono-api": {
|
|
46
|
-
"types": "./build/implementations/http/hono-api/index.d.ts",
|
|
47
|
-
"import": "./build/implementations/http/hono-api/index.js"
|
|
33
|
+
"./hono": {
|
|
34
|
+
"types": "./build/implementations/http/hono/index.d.ts",
|
|
35
|
+
"import": "./build/implementations/http/hono/index.js"
|
|
48
36
|
},
|
|
49
37
|
"./http-docs": {
|
|
50
38
|
"types": "./build/implementations/http/doc-registry.d.ts",
|
|
@@ -81,7 +69,6 @@
|
|
|
81
69
|
"api",
|
|
82
70
|
"framework",
|
|
83
71
|
"hono",
|
|
84
|
-
"express",
|
|
85
72
|
"http",
|
|
86
73
|
"rest",
|
|
87
74
|
"schema",
|
|
@@ -102,7 +89,6 @@
|
|
|
102
89
|
"optionalDependencies": {
|
|
103
90
|
"ajsc": "^7.2.0",
|
|
104
91
|
"astro": "6.x.x || 7.x.x",
|
|
105
|
-
"express": "^5.2.1",
|
|
106
92
|
"hono": "^4.7.4"
|
|
107
93
|
},
|
|
108
94
|
"dependencies": {
|
|
@@ -114,14 +100,10 @@
|
|
|
114
100
|
},
|
|
115
101
|
"devDependencies": {
|
|
116
102
|
"@eslint/js": "^9.17.0",
|
|
117
|
-
"@types/express": "^5.0.6",
|
|
118
|
-
"@types/supertest": "^7.2.0",
|
|
119
103
|
"astro": "^6.3.1",
|
|
120
104
|
"eslint": "^9.17.0",
|
|
121
|
-
"express": "^5.2.1",
|
|
122
105
|
"hono": "^4.7.4",
|
|
123
106
|
"prettier": "^3.8.1",
|
|
124
|
-
"supertest": "^7.2.2",
|
|
125
107
|
"suretype": "^3.3.1",
|
|
126
108
|
"typebox": "^1.0.77",
|
|
127
109
|
"typescript-eslint": "^8.53.0",
|
package/src/client/call.test.ts
CHANGED
|
@@ -338,6 +338,32 @@ describe('executeCall', () => {
|
|
|
338
338
|
})
|
|
339
339
|
expect(observedMeta).toEqual({ traceId: 'override', attempt: 1 })
|
|
340
340
|
})
|
|
341
|
+
|
|
342
|
+
// ── responseHeadersDeclared ──
|
|
343
|
+
|
|
344
|
+
it('returns bare body when responseHeadersDeclared is not set', async () => {
|
|
345
|
+
const adapter = makeAdapter({ body: { id: 'abc' } })
|
|
346
|
+
const result = await run<{ id: string }>({ adapter, descriptor: makeDescriptor() })
|
|
347
|
+
expect(result).toEqual({ id: 'abc' })
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('returns { body, headers } when responseHeadersDeclared is true', async () => {
|
|
351
|
+
const adapter: ClientAdapter = {
|
|
352
|
+
request: vi.fn(async (): Promise<AdapterResponse> => ({
|
|
353
|
+
status: 200,
|
|
354
|
+
headers: { 'x-rate-limit': '99' },
|
|
355
|
+
body: { id: 'abc' },
|
|
356
|
+
})),
|
|
357
|
+
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
358
|
+
}
|
|
359
|
+
const descriptor = makeDescriptor({ responseHeadersDeclared: true })
|
|
360
|
+
const result = await run<{ body: { id: string }; headers: Record<string, string> }>({
|
|
361
|
+
adapter,
|
|
362
|
+
descriptor,
|
|
363
|
+
})
|
|
364
|
+
expect(result).toEqual({ body: { id: 'abc' }, headers: { 'x-rate-limit': '99' } })
|
|
365
|
+
expect(result.headers['x-rate-limit']).toBe('99')
|
|
366
|
+
})
|
|
341
367
|
})
|
|
342
368
|
|
|
343
369
|
describe('executeCall classifier integration', () => {
|
package/src/client/call.ts
CHANGED
|
@@ -132,7 +132,10 @@ export async function executeCall<TResponse>(config: ExecuteCallConfig): Promise
|
|
|
132
132
|
})
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
// 8. Return the body
|
|
135
|
+
// 8. Return the body (or { body, headers } when the route declares res.headers)
|
|
136
|
+
if (descriptor.responseHeadersDeclared) {
|
|
137
|
+
return { body: response.body, headers: response.headers } as TResponse
|
|
138
|
+
}
|
|
136
139
|
return response.body as TResponse
|
|
137
140
|
}
|
|
138
141
|
|
|
@@ -302,7 +302,20 @@ describe('createFetchAdapter — stream()', () => {
|
|
|
302
302
|
method: 'GET',
|
|
303
303
|
})
|
|
304
304
|
|
|
305
|
-
expect(response.headers
|
|
305
|
+
expect(response.headers.get('content-type')).toContain('text/event-stream')
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('propagates response headers to AdapterStreamResponse.headers', async () => {
|
|
309
|
+
const mockFetch = vi.mocked(globalThis.fetch)
|
|
310
|
+
mockFetch.mockResolvedValueOnce(
|
|
311
|
+
new Response('data: x\n\n', {
|
|
312
|
+
status: 200,
|
|
313
|
+
headers: { 'x-stream-id': 'abc', 'content-type': 'text/event-stream' },
|
|
314
|
+
})
|
|
315
|
+
)
|
|
316
|
+
const adapter = createFetchAdapter()
|
|
317
|
+
const result = await adapter.stream({ url: 'https://x', method: 'GET', headers: {} })
|
|
318
|
+
expect(result.headers.get('x-stream-id')).toBe('abc')
|
|
306
319
|
})
|
|
307
320
|
|
|
308
321
|
it('passes signal to fetch for streams', async () => {
|
|
@@ -174,7 +174,9 @@ export function createFetchAdapter(config?: FetchAdapterConfig): ClientAdapter {
|
|
|
174
174
|
signal: req.signal,
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
// Expose the platform Headers object directly — callers that declared
|
|
178
|
+
// res.headers receive it on TypedStream.headers without any conversion.
|
|
179
|
+
const { headers } = response
|
|
178
180
|
const emptyBody: AsyncIterable<unknown> = {
|
|
179
181
|
[Symbol.asyncIterator]: async function* () {},
|
|
180
182
|
}
|
package/src/client/index.test.ts
CHANGED
|
@@ -32,7 +32,7 @@ function makeAdapter(
|
|
|
32
32
|
})),
|
|
33
33
|
stream: vi.fn(async (_req: AdapterRequest): Promise<AdapterStreamResponse> => ({
|
|
34
34
|
status: 200,
|
|
35
|
-
headers:
|
|
35
|
+
headers: new Headers(),
|
|
36
36
|
body: makeAsyncIterable(streamItems),
|
|
37
37
|
})),
|
|
38
38
|
}
|
|
@@ -117,7 +117,7 @@ describe('createClient', () => {
|
|
|
117
117
|
})),
|
|
118
118
|
stream: vi.fn(async (): Promise<AdapterStreamResponse> => ({
|
|
119
119
|
status: 200,
|
|
120
|
-
headers:
|
|
120
|
+
headers: new Headers(),
|
|
121
121
|
body: makeAsyncIterable([]),
|
|
122
122
|
})),
|
|
123
123
|
}
|
|
@@ -145,7 +145,7 @@ describe('createClient', () => {
|
|
|
145
145
|
}),
|
|
146
146
|
stream: vi.fn(async (): Promise<AdapterStreamResponse> => ({
|
|
147
147
|
status: 200,
|
|
148
|
-
headers:
|
|
148
|
+
headers: new Headers(),
|
|
149
149
|
body: makeAsyncIterable([]),
|
|
150
150
|
})),
|
|
151
151
|
}
|
|
@@ -254,7 +254,7 @@ describe('createClient', () => {
|
|
|
254
254
|
request: vi.fn(async (): Promise<never> => { throw new Error('not expected') }),
|
|
255
255
|
stream: vi.fn(async (req: AdapterRequest): Promise<AdapterStreamResponse> => {
|
|
256
256
|
capturedHeaders.push(req.headers ?? {})
|
|
257
|
-
return { status: 200, headers:
|
|
257
|
+
return { status: 200, headers: new Headers(), body: makeAsyncIterable([]) }
|
|
258
258
|
}),
|
|
259
259
|
}
|
|
260
260
|
|
|
@@ -299,7 +299,7 @@ describe('createClient', () => {
|
|
|
299
299
|
}),
|
|
300
300
|
stream: vi.fn(async (): Promise<AdapterStreamResponse> => ({
|
|
301
301
|
status: 200,
|
|
302
|
-
headers:
|
|
302
|
+
headers: new Headers(),
|
|
303
303
|
body: makeAsyncIterable([]),
|
|
304
304
|
})),
|
|
305
305
|
}
|
|
@@ -328,7 +328,7 @@ describe('createClient', () => {
|
|
|
328
328
|
}),
|
|
329
329
|
stream: vi.fn(async (): Promise<AdapterStreamResponse> => ({
|
|
330
330
|
status: 200,
|
|
331
|
-
headers:
|
|
331
|
+
headers: new Headers(),
|
|
332
332
|
body: makeAsyncIterable([]),
|
|
333
333
|
})),
|
|
334
334
|
}
|
|
@@ -364,7 +364,7 @@ describe('createClient', () => {
|
|
|
364
364
|
}),
|
|
365
365
|
stream: vi.fn(async (): Promise<AdapterStreamResponse> => ({
|
|
366
366
|
status: 200,
|
|
367
|
-
headers:
|
|
367
|
+
headers: new Headers(),
|
|
368
368
|
body: makeAsyncIterable([]),
|
|
369
369
|
})),
|
|
370
370
|
}
|
|
@@ -23,7 +23,7 @@ export function interpolatePath(
|
|
|
23
23
|
* Builds an `AdapterRequest` from a `CallDescriptor` and a base URL.
|
|
24
24
|
*
|
|
25
25
|
* - `kind === 'rpc'` or `kind === 'stream'`: params are flat — sent as the JSON body.
|
|
26
|
-
* - `kind === 'api'`: params are structured channels — `pathParams`, `query`, `body`, `headers`.
|
|
26
|
+
* - `kind === 'api'` or `kind === 'http-stream'`: params are structured channels — `pathParams`, `query`, `body`, `headers`.
|
|
27
27
|
*/
|
|
28
28
|
export function buildAdapterRequest(descriptor: CallDescriptor, basePath: string): AdapterRequest {
|
|
29
29
|
const { name, path, method, kind, params } = descriptor
|
|
@@ -36,7 +36,7 @@ export function buildAdapterRequest(descriptor: CallDescriptor, basePath: string
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// kind === 'api' — params are structured channels
|
|
39
|
+
// kind === 'api' | 'http-stream' — params are structured channels
|
|
40
40
|
const structured = (params ?? {}) as {
|
|
41
41
|
pathParams?: Record<string, unknown>
|
|
42
42
|
query?: Record<string, unknown>
|