ts-procedures 8.6.0 → 9.1.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/CHANGELOG.md +540 -0
- package/README.md +166 -101
- package/agent_config/claude-code/.claude-plugin/plugin.json +1 -1
- package/agent_config/claude-code/agents/ts-procedures-architect.md +11 -10
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +25 -12
- package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +10 -12
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +141 -45
- package/agent_config/claude-code/skills/ts-procedures/checklist.md +7 -6
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +45 -6
- package/agent_config/claude-code/skills/ts-procedures/templates/client.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures/templates/hono.md +1 -1
- package/agent_config/copilot/copilot-instructions.md +50 -33
- package/agent_config/cursor/cursorrules +50 -33
- package/build/adapters/astro/astro-context.js.map +1 -0
- package/build/adapters/astro/create-handler.js.map +1 -0
- package/build/adapters/astro/index.js.map +1 -0
- package/build/{implementations/http → adapters}/astro/index.test.js +1 -1
- package/build/adapters/astro/index.test.js.map +1 -0
- package/build/adapters/astro/rewrite-request.js.map +1 -0
- package/build/adapters/hono/envelope-parity.test.js +98 -0
- package/build/adapters/hono/envelope-parity.test.js.map +1 -0
- package/build/{implementations/http → adapters}/hono/handlers/http-stream.d.ts +1 -1
- package/build/adapters/hono/handlers/http-stream.js +55 -0
- package/build/adapters/hono/handlers/http-stream.js.map +1 -0
- package/build/{implementations/http → adapters}/hono/handlers/http-stream.test.js +1 -1
- package/build/adapters/hono/handlers/http-stream.test.js.map +1 -0
- package/build/{implementations/http → adapters}/hono/handlers/http.d.ts +1 -1
- package/build/adapters/hono/handlers/http.js +50 -0
- package/build/adapters/hono/handlers/http.js.map +1 -0
- package/build/{implementations/http → adapters}/hono/handlers/http.test.js +1 -1
- package/build/adapters/hono/handlers/http.test.js.map +1 -0
- package/build/{implementations/http → adapters}/hono/handlers/rpc.d.ts +2 -2
- package/build/adapters/hono/handlers/rpc.js +23 -0
- package/build/adapters/hono/handlers/rpc.js.map +1 -0
- package/build/{implementations/http → adapters}/hono/handlers/rpc.test.js +1 -1
- package/build/adapters/hono/handlers/rpc.test.js.map +1 -0
- package/build/adapters/hono/handlers/stream.d.ts +12 -0
- package/build/adapters/hono/handlers/stream.js +89 -0
- package/build/adapters/hono/handlers/stream.js.map +1 -0
- package/build/{implementations/http → adapters}/hono/handlers/stream.test.js +3 -2
- package/build/adapters/hono/handlers/stream.test.js.map +1 -0
- package/build/{implementations/http → adapters}/hono/index.d.ts +24 -12
- package/build/{implementations/http → adapters}/hono/index.js +19 -8
- package/build/adapters/hono/index.js.map +1 -0
- package/build/{implementations/http → adapters}/hono/index.test.js +2 -4
- package/build/adapters/hono/index.test.js.map +1 -0
- package/build/{implementations/http → adapters/hono}/on-request-error.test.js +2 -2
- package/build/adapters/hono/on-request-error.test.js.map +1 -0
- package/build/adapters/hono/request.d.ts +7 -0
- package/build/adapters/hono/request.js +22 -0
- package/build/adapters/hono/request.js.map +1 -0
- package/build/{implementations/http → adapters/hono}/route-errors.test.js +4 -4
- package/build/adapters/hono/route-errors.test.js.map +1 -0
- package/build/adapters/hono/types.d.ts +55 -0
- package/build/adapters/hono/types.js +19 -0
- package/build/adapters/hono/types.js.map +1 -0
- package/build/client/freeze.test.js +39 -0
- package/build/client/freeze.test.js.map +1 -0
- package/build/client/typed-error-dispatch.test.js +2 -2
- package/build/client/typed-error-dispatch.test.js.map +1 -1
- package/build/codegen/__fixtures__/make-envelope.d.ts +1 -1
- package/build/codegen/bin/cli.d.ts +5 -0
- package/build/codegen/bin/cli.js +139 -182
- package/build/codegen/bin/cli.js.map +1 -1
- package/build/codegen/bin/cli.test.js +12 -2
- package/build/codegen/bin/cli.test.js.map +1 -1
- package/build/codegen/bin/flag-specs.d.ts +9 -0
- package/build/codegen/bin/flag-specs.js +33 -31
- package/build/codegen/bin/flag-specs.js.map +1 -1
- package/build/codegen/bin/flag-specs.test.js +14 -1
- package/build/codegen/bin/flag-specs.test.js.map +1 -1
- package/build/codegen/collect-models.d.ts +1 -1
- package/build/codegen/emit/api-route.d.ts +8 -0
- package/build/codegen/emit/api-route.js +156 -0
- package/build/codegen/emit/api-route.js.map +1 -0
- package/build/codegen/emit/context.d.ts +30 -0
- package/build/codegen/emit/context.js +2 -0
- package/build/codegen/emit/context.js.map +1 -0
- package/build/codegen/emit/declarations.d.ts +24 -0
- package/build/codegen/emit/declarations.js +48 -0
- package/build/codegen/emit/declarations.js.map +1 -0
- package/build/codegen/emit/format-types.d.ts +61 -0
- package/build/codegen/emit/format-types.js +188 -0
- package/build/codegen/emit/format-types.js.map +1 -0
- package/build/codegen/emit/http-stream-route.d.ts +7 -0
- package/build/codegen/emit/http-stream-route.js +138 -0
- package/build/codegen/emit/http-stream-route.js.map +1 -0
- package/build/codegen/emit/route-shared.d.ts +37 -0
- package/build/codegen/emit/route-shared.js +88 -0
- package/build/codegen/emit/route-shared.js.map +1 -0
- package/build/codegen/emit/rpc-route.d.ts +7 -0
- package/build/codegen/emit/rpc-route.js +37 -0
- package/build/codegen/emit/rpc-route.js.map +1 -0
- package/build/codegen/emit/scope-file.d.ts +39 -0
- package/build/codegen/emit/scope-file.js +166 -0
- package/build/codegen/emit/scope-file.js.map +1 -0
- package/build/codegen/emit/stream-route.d.ts +7 -0
- package/build/codegen/emit/stream-route.js +62 -0
- package/build/codegen/emit/stream-route.js.map +1 -0
- package/build/codegen/emit-errors.d.ts +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.d.ts +13 -30
- package/build/codegen/emit-scope.js +15 -844
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/emit-scope.test.js +67 -0
- package/build/codegen/emit-scope.test.js.map +1 -1
- package/build/codegen/goldens.test.js +69 -0
- package/build/codegen/goldens.test.js.map +1 -0
- package/build/codegen/group-routes.d.ts +1 -1
- package/build/codegen/pipeline.d.ts +1 -1
- package/build/codegen/resolve-envelope.d.ts +1 -1
- package/build/codegen/targets/_shared/error-schemas.d.ts +1 -1
- package/build/codegen/targets/_shared/route-slots.d.ts +1 -1
- package/build/codegen/targets/_shared/target-run.d.ts +1 -1
- package/build/codegen/targets/kotlin/emit-route-kotlin.d.ts +1 -1
- package/build/codegen/targets/swift/emit-route-swift.d.ts +1 -1
- package/build/core/create-http-stream.d.ts +50 -0
- package/build/core/create-http-stream.js +108 -0
- package/build/core/create-http-stream.js.map +1 -0
- package/build/{create-http-stream.test.js → core/create-http-stream.test.js} +1 -1
- package/build/core/create-http-stream.test.js.map +1 -0
- package/build/core/create-http.d.ts +51 -0
- package/build/core/create-http.js +65 -0
- package/build/core/create-http.js.map +1 -0
- package/build/{create-http.test.js → core/create-http.test.js} +27 -4
- package/build/core/create-http.test.js.map +1 -0
- package/build/core/create-stream.d.ts +26 -0
- package/build/core/create-stream.js +80 -0
- package/build/core/create-stream.js.map +1 -0
- package/build/{create-stream.test.js → core/create-stream.test.js} +23 -28
- package/build/core/create-stream.test.js.map +1 -0
- package/build/core/create.d.ts +22 -0
- package/build/core/create.js +71 -0
- package/build/core/create.js.map +1 -0
- package/build/{create.test.js → core/create.test.js} +25 -46
- package/build/core/create.test.js.map +1 -0
- package/build/core/definition-site.d.ts +24 -0
- package/build/{stack-utils.js → core/definition-site.js} +20 -20
- package/build/core/definition-site.js.map +1 -0
- package/build/{stack-utils.test.js → core/definition-site.test.js} +12 -3
- package/build/core/definition-site.test.js.map +1 -0
- package/build/{errors.d.ts → core/errors.d.ts} +19 -8
- package/build/{errors.js → core/errors.js} +21 -26
- package/build/core/errors.js.map +1 -0
- package/build/core/errors.test.js.map +1 -0
- package/build/core/factory-options.test.js +82 -0
- package/build/core/factory-options.test.js.map +1 -0
- package/build/core/http-route.d.ts +13 -0
- package/build/core/http-route.js +54 -0
- package/build/core/http-route.js.map +1 -0
- package/build/core/internal.d.ts +72 -0
- package/build/core/internal.js +128 -0
- package/build/core/internal.js.map +1 -0
- package/build/{migration.test.js → core/migration.test.js} +17 -1
- package/build/core/migration.test.js.map +1 -0
- package/build/core/procedures.d.ts +143 -0
- package/build/core/procedures.js +64 -0
- package/build/core/procedures.js.map +1 -0
- package/build/{index.test.js → core/procedures.test.js} +14 -11
- package/build/core/procedures.test.js.map +1 -0
- package/build/core/types.d.ts +183 -0
- package/build/{schema → core}/types.js.map +1 -1
- package/build/exports.d.ts +31 -11
- package/build/exports.js +23 -8
- package/build/exports.js.map +1 -1
- package/build/schema/adapter.d.ts +35 -0
- package/build/schema/adapter.js +13 -0
- package/build/schema/adapter.js.map +1 -0
- package/build/schema/adapter.test.js +53 -0
- package/build/schema/adapter.test.js.map +1 -0
- package/build/schema/compile.d.ts +37 -0
- package/build/schema/compile.js +38 -0
- package/build/schema/compile.js.map +1 -0
- package/build/schema/compile.test.js +78 -0
- package/build/schema/compile.test.js.map +1 -0
- package/build/schema/compute-schema.d.ts +47 -37
- package/build/schema/compute-schema.js +86 -29
- package/build/schema/compute-schema.js.map +1 -1
- package/build/schema/compute-schema.test.js +158 -40
- package/build/schema/compute-schema.test.js.map +1 -1
- package/build/schema/json-schema.d.ts +17 -0
- package/build/schema/json-schema.js +2 -0
- package/build/schema/json-schema.js.map +1 -0
- package/build/schema/typebox.d.ts +11 -0
- package/build/schema/typebox.js +24 -0
- package/build/schema/typebox.js.map +1 -0
- package/build/schema/typebox.test.js +34 -0
- package/build/schema/typebox.test.js.map +1 -0
- package/build/server/context.d.ts +8 -0
- package/build/server/context.js +7 -0
- package/build/server/context.js.map +1 -0
- package/build/server/context.test.js +16 -0
- package/build/server/context.test.js.map +1 -0
- package/build/{doc-envelope.d.ts → server/doc-envelope.d.ts} +1 -1
- package/build/server/doc-envelope.js.map +1 -0
- package/build/server/doc-envelope.test.d.ts +1 -0
- package/build/server/doc-envelope.test.js.map +1 -0
- package/build/{implementations/http → server}/doc-registry.d.ts +7 -2
- package/build/{implementations/http → server}/doc-registry.js +9 -5
- package/build/server/doc-registry.js.map +1 -0
- package/build/server/doc-registry.test.d.ts +1 -0
- package/build/{implementations/http → server}/doc-registry.test.js +27 -24
- package/build/server/doc-registry.test.js.map +1 -0
- package/build/server/docs/docs.test.d.ts +1 -0
- package/build/server/docs/docs.test.js +237 -0
- package/build/server/docs/docs.test.js.map +1 -0
- package/build/{implementations/http/hono → server}/docs/http-doc.d.ts +2 -2
- package/build/{implementations/http/hono → server}/docs/http-doc.js +1 -1
- package/build/server/docs/http-doc.js.map +1 -0
- package/build/{implementations/http/hono → server}/docs/http-stream-doc.d.ts +2 -2
- package/build/{implementations/http/hono → server}/docs/http-stream-doc.js +1 -1
- package/build/server/docs/http-stream-doc.js.map +1 -0
- package/build/{implementations/http/hono → server}/docs/rpc-doc.d.ts +2 -2
- package/build/{implementations/http/hono → server}/docs/rpc-doc.js +1 -1
- package/build/server/docs/rpc-doc.js.map +1 -0
- package/build/{implementations/http/hono → server}/docs/stream-doc.d.ts +2 -2
- package/build/{implementations/http/hono → server}/docs/stream-doc.js +1 -1
- package/build/server/docs/stream-doc.js.map +1 -0
- package/build/server/errors/dispatch.d.ts +96 -0
- package/build/{implementations/http/error-dispatch.js → server/errors/dispatch.js} +20 -10
- package/build/server/errors/dispatch.js.map +1 -0
- package/build/server/errors/dispatch.test.d.ts +1 -0
- package/build/server/errors/dispatch.test.js +418 -0
- package/build/server/errors/dispatch.test.js.map +1 -0
- package/build/{implementations/http/error-taxonomy.d.ts → server/errors/taxonomy.d.ts} +8 -17
- package/build/{implementations/http/error-taxonomy.js → server/errors/taxonomy.js} +6 -15
- package/build/server/errors/taxonomy.js.map +1 -0
- package/build/server/errors/taxonomy.test.d.ts +1 -0
- package/build/{implementations/http/error-taxonomy.test.js → server/errors/taxonomy.test.js} +45 -39
- package/build/server/errors/taxonomy.test.js.map +1 -0
- package/build/server/index.d.ts +29 -0
- package/build/server/index.js +27 -0
- package/build/server/index.js.map +1 -0
- package/build/server/no-framework-imports.test.d.ts +1 -0
- package/build/server/no-framework-imports.test.js +40 -0
- package/build/server/no-framework-imports.test.js.map +1 -0
- package/build/{implementations/http/hono/path.d.ts → server/paths.d.ts} +2 -3
- package/build/{implementations/http/hono/path.js → server/paths.js} +1 -1
- package/build/server/paths.js.map +1 -0
- package/build/server/paths.test.d.ts +1 -0
- package/build/server/paths.test.js +111 -0
- package/build/server/paths.test.js.map +1 -0
- package/build/server/request/params.d.ts +29 -0
- package/build/server/request/params.js +43 -0
- package/build/server/request/params.js.map +1 -0
- package/build/server/request/params.test.d.ts +1 -0
- package/build/server/request/params.test.js +91 -0
- package/build/server/request/params.test.js.map +1 -0
- package/build/server/request/query.d.ts +9 -0
- package/build/server/request/query.js +22 -0
- package/build/server/request/query.js.map +1 -0
- package/build/server/request/query.test.d.ts +1 -0
- package/build/server/request/query.test.js +60 -0
- package/build/server/request/query.test.js.map +1 -0
- package/build/server/sse.d.ts +70 -0
- package/build/server/sse.js +94 -0
- package/build/server/sse.js.map +1 -0
- package/build/server/sse.test.d.ts +1 -0
- package/build/server/sse.test.js +98 -0
- package/build/server/sse.test.js.map +1 -0
- package/build/{implementations → server}/types.d.ts +17 -15
- package/build/{implementations → server}/types.js.map +1 -1
- package/docs/astro-adapter.md +8 -9
- package/docs/client-and-codegen.md +10 -4
- package/docs/client-error-handling.md +5 -5
- package/docs/codegen-kotlin.md +2 -3
- package/docs/codegen-swift.md +1 -2
- package/docs/core.md +135 -54
- package/docs/http-integrations.md +58 -6
- package/docs/migration-v8-to-v9.md +200 -0
- package/docs/plans/2026-06-09-v9-rewrite.md +130 -0
- package/docs/specs/2026-06-09-v9-rewrite-design.md +221 -0
- package/docs/streaming.md +12 -0
- package/package.json +25 -48
- package/src/{implementations/http → adapters}/astro/index.test.ts +2 -2
- package/src/adapters/hono/__fixtures__/parity-envelope.json +389 -0
- package/src/adapters/hono/envelope-parity.test.ts +126 -0
- package/src/{implementations/http → adapters}/hono/handlers/http-stream.test.ts +1 -1
- package/src/adapters/hono/handlers/http-stream.ts +73 -0
- package/src/{implementations/http → adapters}/hono/handlers/http.test.ts +1 -1
- package/src/adapters/hono/handlers/http.ts +70 -0
- package/src/{implementations/http → adapters}/hono/handlers/rpc.test.ts +2 -2
- package/src/adapters/hono/handlers/rpc.ts +39 -0
- package/src/{implementations/http → adapters}/hono/handlers/stream.test.ts +4 -3
- package/src/{implementations/http → adapters}/hono/handlers/stream.ts +19 -92
- package/src/{implementations/http → adapters}/hono/index.test.ts +14 -16
- package/src/{implementations/http → adapters}/hono/index.ts +35 -30
- package/src/{implementations/http → adapters/hono}/on-request-error.test.ts +3 -3
- package/src/adapters/hono/request.ts +28 -0
- package/src/{implementations/http → adapters/hono}/route-errors.test.ts +5 -5
- package/src/{implementations/http → adapters}/hono/types.ts +43 -20
- package/src/client/freeze.test.ts +41 -0
- package/src/client/typed-error-dispatch.test.ts +3 -3
- package/src/codegen/__fixtures__/make-envelope.ts +1 -1
- package/src/codegen/__fixtures__/models-envelope.json +310 -0
- package/src/codegen/__goldens__/MANIFEST.json +85 -0
- package/src/codegen/__goldens__/kotlin-default--models/Billing.kt +112 -0
- package/src/codegen/__goldens__/kotlin-default--models/BillingReports.kt +26 -0
- package/src/codegen/__goldens__/kotlin-default--models/Orders.kt +88 -0
- package/src/codegen/__goldens__/kotlin-default--users/Users.kt +189 -0
- package/src/codegen/__goldens__/swift-default--models/Billing.swift +97 -0
- package/src/codegen/__goldens__/swift-default--models/BillingReports.swift +20 -0
- package/src/codegen/__goldens__/swift-default--models/Orders.swift +81 -0
- package/src/codegen/__goldens__/swift-default--users/Users.swift +204 -0
- package/src/codegen/__goldens__/ts-default--models/_client.ts +1319 -0
- package/src/codegen/__goldens__/ts-default--models/_errors.ts +90 -0
- package/src/codegen/__goldens__/ts-default--models/_models.ts +10 -0
- package/src/codegen/__goldens__/ts-default--models/_types.ts +502 -0
- package/src/codegen/__goldens__/ts-default--models/billing-reports.ts +29 -0
- package/src/codegen/__goldens__/ts-default--models/billing.ts +67 -0
- package/src/codegen/__goldens__/ts-default--models/index.ts +48 -0
- package/src/codegen/__goldens__/ts-default--models/orders.ts +80 -0
- package/src/codegen/__goldens__/ts-default--users/_client.ts +1319 -0
- package/src/codegen/__goldens__/ts-default--users/_errors.ts +90 -0
- package/src/codegen/__goldens__/ts-default--users/_types.ts +502 -0
- package/src/codegen/__goldens__/ts-default--users/index.ts +38 -0
- package/src/codegen/__goldens__/ts-default--users/users.ts +169 -0
- package/src/codegen/__goldens__/ts-external-runtime--models/_errors.ts +90 -0
- package/src/codegen/__goldens__/ts-external-runtime--models/_models.ts +10 -0
- package/src/codegen/__goldens__/ts-external-runtime--models/billing-reports.ts +29 -0
- package/src/codegen/__goldens__/ts-external-runtime--models/billing.ts +67 -0
- package/src/codegen/__goldens__/ts-external-runtime--models/index.ts +48 -0
- package/src/codegen/__goldens__/ts-external-runtime--models/orders.ts +80 -0
- package/src/codegen/__goldens__/ts-external-runtime--users/_errors.ts +90 -0
- package/src/codegen/__goldens__/ts-external-runtime--users/index.ts +38 -0
- package/src/codegen/__goldens__/ts-external-runtime--users/users.ts +169 -0
- package/src/codegen/__goldens__/ts-flat--models/_client.ts +1319 -0
- package/src/codegen/__goldens__/ts-flat--models/_errors.ts +87 -0
- package/src/codegen/__goldens__/ts-flat--models/_models.ts +10 -0
- package/src/codegen/__goldens__/ts-flat--models/_types.ts +502 -0
- package/src/codegen/__goldens__/ts-flat--models/billing-reports.ts +28 -0
- package/src/codegen/__goldens__/ts-flat--models/billing.ts +51 -0
- package/src/codegen/__goldens__/ts-flat--models/index.ts +42 -0
- package/src/codegen/__goldens__/ts-flat--models/orders.ts +73 -0
- package/src/codegen/__goldens__/ts-flat--users/_client.ts +1319 -0
- package/src/codegen/__goldens__/ts-flat--users/_errors.ts +87 -0
- package/src/codegen/__goldens__/ts-flat--users/_types.ts +502 -0
- package/src/codegen/__goldens__/ts-flat--users/index.ts +34 -0
- package/src/codegen/__goldens__/ts-flat--users/users.ts +126 -0
- package/src/codegen/__goldens__/ts-no-share-models--models/_client.ts +1319 -0
- package/src/codegen/__goldens__/ts-no-share-models--models/_errors.ts +90 -0
- package/src/codegen/__goldens__/ts-no-share-models--models/_types.ts +502 -0
- package/src/codegen/__goldens__/ts-no-share-models--models/billing-reports.ts +29 -0
- package/src/codegen/__goldens__/ts-no-share-models--models/billing.ts +111 -0
- package/src/codegen/__goldens__/ts-no-share-models--models/index.ts +48 -0
- package/src/codegen/__goldens__/ts-no-share-models--models/orders.ts +112 -0
- package/src/codegen/__goldens__/ts-no-share-models--users/_client.ts +1319 -0
- package/src/codegen/__goldens__/ts-no-share-models--users/_errors.ts +90 -0
- package/src/codegen/__goldens__/ts-no-share-models--users/_types.ts +502 -0
- package/src/codegen/__goldens__/ts-no-share-models--users/index.ts +38 -0
- package/src/codegen/__goldens__/ts-no-share-models--users/users.ts +169 -0
- package/src/codegen/__goldens__/ts-shared-models-module--models/_client.ts +1319 -0
- package/src/codegen/__goldens__/ts-shared-models-module--models/_errors.ts +90 -0
- package/src/codegen/__goldens__/ts-shared-models-module--models/_models.ts +7 -0
- package/src/codegen/__goldens__/ts-shared-models-module--models/_types.ts +502 -0
- package/src/codegen/__goldens__/ts-shared-models-module--models/billing-reports.ts +29 -0
- package/src/codegen/__goldens__/ts-shared-models-module--models/billing.ts +67 -0
- package/src/codegen/__goldens__/ts-shared-models-module--models/index.ts +48 -0
- package/src/codegen/__goldens__/ts-shared-models-module--models/orders.ts +80 -0
- package/src/codegen/bin/cli.test.ts +13 -2
- package/src/codegen/bin/cli.ts +181 -144
- package/src/codegen/bin/flag-specs.test.ts +16 -1
- package/src/codegen/bin/flag-specs.ts +43 -31
- package/src/codegen/bundle-size.test.ts +1 -1
- package/src/codegen/collect-models.ts +1 -1
- package/src/codegen/e2e.test.ts +1 -1
- package/src/codegen/emit/api-route.ts +184 -0
- package/src/codegen/emit/context.ts +32 -0
- package/src/codegen/emit/declarations.ts +49 -0
- package/src/codegen/emit/format-types.ts +232 -0
- package/src/codegen/emit/http-stream-route.ts +162 -0
- package/src/codegen/emit/route-shared.ts +104 -0
- package/src/codegen/emit/rpc-route.ts +49 -0
- package/src/codegen/emit/scope-file.ts +226 -0
- package/src/codegen/emit/stream-route.ts +81 -0
- package/src/codegen/emit-errors.integration.test.ts +2 -2
- package/src/codegen/emit-errors.test.ts +1 -1
- package/src/codegen/emit-errors.ts +1 -1
- package/src/codegen/emit-scope.test.ts +75 -2
- package/src/codegen/emit-scope.ts +15 -1048
- package/src/codegen/goldens.test.ts +89 -0
- package/src/codegen/group-routes.test.ts +1 -1
- package/src/codegen/group-routes.ts +1 -1
- package/src/codegen/pipeline.test.ts +1 -1
- package/src/codegen/pipeline.ts +1 -1
- package/src/codegen/resolve-envelope.test.ts +1 -1
- package/src/codegen/resolve-envelope.ts +1 -1
- package/src/codegen/targets/_shared/error-schemas.test.ts +1 -1
- package/src/codegen/targets/_shared/error-schemas.ts +1 -1
- package/src/codegen/targets/_shared/route-slots.test.ts +1 -1
- package/src/codegen/targets/_shared/route-slots.ts +1 -1
- package/src/codegen/targets/_shared/target-run.ts +1 -1
- package/src/codegen/targets/kotlin/emit-route-kotlin.test.ts +1 -1
- package/src/codegen/targets/kotlin/emit-route-kotlin.ts +1 -1
- package/src/codegen/targets/kotlin/emit-scope-kotlin.test.ts +1 -1
- package/src/codegen/targets/swift/access-level.test.ts +1 -1
- package/src/codegen/targets/swift/emit-route-swift.test.ts +1 -1
- package/src/codegen/targets/swift/emit-route-swift.ts +1 -1
- package/src/codegen/targets/swift/emit-scope-swift.test.ts +1 -1
- package/src/codegen/targets/ts/shared-models.test.ts +1 -1
- package/src/{create-http-stream.test.ts → core/create-http-stream.test.ts} +1 -1
- package/src/core/create-http-stream.ts +207 -0
- package/src/{create-http.test.ts → core/create-http.test.ts} +31 -4
- package/src/core/create-http.ts +126 -0
- package/src/{create-stream.test.ts → core/create-stream.test.ts} +28 -31
- package/src/core/create-stream.ts +142 -0
- package/src/{create.test.ts → core/create.test.ts} +25 -57
- package/src/core/create.ts +121 -0
- package/src/{stack-utils.test.ts → core/definition-site.test.ts} +14 -3
- package/src/{stack-utils.ts → core/definition-site.ts} +20 -23
- package/src/{errors.test.ts → core/errors.test.ts} +1 -1
- package/src/{errors.ts → core/errors.ts} +30 -28
- package/src/core/factory-options.test.ts +112 -0
- package/src/core/http-route.ts +73 -0
- package/src/core/internal.ts +203 -0
- package/src/{migration.test.ts → core/migration.test.ts} +23 -1
- package/src/{index.test.ts → core/procedures.test.ts} +13 -11
- package/src/core/procedures.ts +75 -0
- package/src/core/types.ts +196 -0
- package/src/exports.ts +60 -11
- package/src/schema/adapter.test.ts +58 -0
- package/src/schema/adapter.ts +45 -0
- package/src/schema/compile.test.ts +95 -0
- package/src/schema/compile.ts +64 -0
- package/src/schema/compute-schema.test.ts +222 -41
- package/src/schema/compute-schema.ts +145 -71
- package/src/schema/json-schema.ts +21 -0
- package/src/schema/typebox.test.ts +40 -0
- package/src/schema/typebox.ts +27 -0
- package/src/server/context.test.ts +22 -0
- package/src/server/context.ts +18 -0
- package/src/{doc-envelope.test.ts → server/doc-envelope.test.ts} +2 -2
- package/src/{doc-envelope.ts → server/doc-envelope.ts} +1 -1
- package/src/{implementations/http → server}/doc-registry.test.ts +32 -26
- package/src/{implementations/http → server}/doc-registry.ts +11 -7
- package/src/server/docs/docs.test.ts +287 -0
- package/src/{implementations/http/hono → server}/docs/http-doc.ts +3 -3
- package/src/{implementations/http/hono → server}/docs/http-stream-doc.ts +3 -3
- package/src/{implementations/http/hono → server}/docs/rpc-doc.ts +3 -3
- package/src/{implementations/http/hono → server}/docs/stream-doc.ts +3 -3
- package/src/server/errors/dispatch.test.ts +450 -0
- package/src/server/errors/dispatch.ts +189 -0
- package/src/{implementations/http/error-taxonomy.test.ts → server/errors/taxonomy.test.ts} +45 -39
- package/src/{implementations/http/error-taxonomy.ts → server/errors/taxonomy.ts} +8 -17
- package/src/server/index.ts +29 -0
- package/src/server/no-framework-imports.test.ts +43 -0
- package/src/server/paths.test.ts +141 -0
- package/src/{implementations/http/hono/path.ts → server/paths.ts} +2 -13
- package/src/server/request/params.test.ts +143 -0
- package/src/server/request/params.ts +68 -0
- package/src/server/request/query.test.ts +70 -0
- package/src/server/request/query.ts +24 -0
- package/src/server/sse.test.ts +113 -0
- package/src/server/sse.ts +117 -0
- package/src/{implementations → server}/types.ts +17 -16
- package/build/create-http-stream.d.ts +0 -58
- package/build/create-http-stream.js +0 -122
- package/build/create-http-stream.js.map +0 -1
- package/build/create-http-stream.test.js.map +0 -1
- package/build/create-http.d.ts +0 -49
- package/build/create-http.js +0 -108
- package/build/create-http.js.map +0 -1
- package/build/create-http.test.js.map +0 -1
- package/build/create-stream.d.ts +0 -35
- package/build/create-stream.js +0 -123
- package/build/create-stream.js.map +0 -1
- package/build/create-stream.test.js.map +0 -1
- package/build/create.d.ts +0 -28
- package/build/create.js +0 -82
- package/build/create.js.map +0 -1
- package/build/create.test.js.map +0 -1
- package/build/doc-envelope.js.map +0 -1
- package/build/doc-envelope.test.js.map +0 -1
- package/build/errors.js.map +0 -1
- package/build/errors.test.js.map +0 -1
- package/build/implementations/http/astro/astro-context.js.map +0 -1
- package/build/implementations/http/astro/create-handler.js.map +0 -1
- package/build/implementations/http/astro/index.js.map +0 -1
- package/build/implementations/http/astro/index.test.js.map +0 -1
- package/build/implementations/http/astro/rewrite-request.js.map +0 -1
- package/build/implementations/http/doc-registry.js.map +0 -1
- package/build/implementations/http/doc-registry.test.js.map +0 -1
- package/build/implementations/http/error-dispatch.d.ts +0 -76
- package/build/implementations/http/error-dispatch.js.map +0 -1
- package/build/implementations/http/error-dispatch.test.js +0 -254
- package/build/implementations/http/error-dispatch.test.js.map +0 -1
- package/build/implementations/http/error-taxonomy.js.map +0 -1
- package/build/implementations/http/error-taxonomy.test.js.map +0 -1
- package/build/implementations/http/hono/docs/http-doc.js.map +0 -1
- package/build/implementations/http/hono/docs/http-stream-doc.js.map +0 -1
- package/build/implementations/http/hono/docs/rpc-doc.js.map +0 -1
- package/build/implementations/http/hono/docs/stream-doc.js.map +0 -1
- package/build/implementations/http/hono/handlers/http-stream.js +0 -123
- package/build/implementations/http/hono/handlers/http-stream.js.map +0 -1
- package/build/implementations/http/hono/handlers/http-stream.test.js.map +0 -1
- package/build/implementations/http/hono/handlers/http.js +0 -110
- package/build/implementations/http/hono/handlers/http.js.map +0 -1
- package/build/implementations/http/hono/handlers/http.test.js.map +0 -1
- package/build/implementations/http/hono/handlers/rpc.js +0 -32
- package/build/implementations/http/hono/handlers/rpc.js.map +0 -1
- package/build/implementations/http/hono/handlers/rpc.test.js.map +0 -1
- package/build/implementations/http/hono/handlers/stream.d.ts +0 -23
- package/build/implementations/http/hono/handlers/stream.js +0 -147
- package/build/implementations/http/hono/handlers/stream.js.map +0 -1
- package/build/implementations/http/hono/handlers/stream.test.js.map +0 -1
- package/build/implementations/http/hono/index.js.map +0 -1
- package/build/implementations/http/hono/index.test.js.map +0 -1
- package/build/implementations/http/hono/path.js.map +0 -1
- package/build/implementations/http/hono/path.test.js +0 -83
- package/build/implementations/http/hono/path.test.js.map +0 -1
- package/build/implementations/http/hono/types.d.ts +0 -51
- package/build/implementations/http/hono/types.js.map +0 -1
- package/build/implementations/http/on-request-error.test.js.map +0 -1
- package/build/implementations/http/route-errors.test.js.map +0 -1
- package/build/index.d.ts +0 -175
- package/build/index.js +0 -47
- package/build/index.js.map +0 -1
- package/build/index.test.js.map +0 -1
- package/build/migration.test.js.map +0 -1
- package/build/schema/extract-json-schema.d.ts +0 -2
- package/build/schema/extract-json-schema.js +0 -12
- package/build/schema/extract-json-schema.js.map +0 -1
- package/build/schema/extract-json-schema.test.js +0 -23
- package/build/schema/extract-json-schema.test.js.map +0 -1
- package/build/schema/parser.d.ts +0 -36
- package/build/schema/parser.js +0 -210
- package/build/schema/parser.js.map +0 -1
- package/build/schema/parser.test.js +0 -120
- package/build/schema/parser.test.js.map +0 -1
- package/build/schema/resolve-schema-lib.d.ts +0 -12
- package/build/schema/resolve-schema-lib.js +0 -11
- package/build/schema/resolve-schema-lib.js.map +0 -1
- package/build/schema/resolve-schema-lib.test.js +0 -17
- package/build/schema/resolve-schema-lib.test.js.map +0 -1
- package/build/schema/types.d.ts +0 -8
- package/build/schema/types.js +0 -2
- package/build/stack-utils.d.ts +0 -25
- package/build/stack-utils.js.map +0 -1
- package/build/stack-utils.test.js.map +0 -1
- package/build/types.d.ts +0 -142
- package/build/types.js +0 -2
- package/build/types.js.map +0 -1
- package/docs/decisions/2026-06-02-monorepo-split-evaluation.md +0 -80
- package/docs/handoffs/2026-06-08-dx-round2-declines.md +0 -45
- package/docs/handoffs/ajsc-named-type-collision.md +0 -134
- package/docs/handoffs/ajsc-named-type-support.md +0 -181
- package/docs/handoffs/shared-models-auto-resolve-response.md +0 -181
- package/docs/npm-workspaces-migration-plan.md +0 -611
- package/docs/superpowers/plans/2026-04-24-doc-registry-simplification.md +0 -886
- package/docs/superpowers/plans/2026-04-24-kotlin-codegen-target.md +0 -1265
- package/docs/superpowers/plans/2026-04-25-ajsc-v7-kotlin-polish.md +0 -1993
- package/docs/superpowers/plans/2026-04-29-safe-result-api.md +0 -2293
- package/docs/superpowers/plans/2026-05-07-astro-adapter.md +0 -1391
- package/docs/superpowers/plans/2026-05-08-create-http.md +0 -3355
- package/docs/superpowers/plans/2026-05-08-hono-app-builder-convergence.md +0 -3365
- package/docs/superpowers/plans/2026-06-05-dx-feedback-round.md +0 -1292
- package/docs/superpowers/plans/2026-06-06-shared-models-convention-and-diagnostics.md +0 -659
- package/docs/superpowers/plans/2026-06-08-codegen-dx-surfacing.md +0 -428
- package/docs/superpowers/specs/2026-04-24-kotlin-swift-codegen-design.md +0 -401
- package/docs/superpowers/specs/2026-04-25-ajsc-v7-kotlin-polish-design.md +0 -314
- package/docs/superpowers/specs/2026-04-25-swift-codegen-design.md +0 -264
- package/docs/superpowers/specs/2026-04-29-safe-result-api-design.md +0 -324
- package/docs/superpowers/specs/2026-05-07-astro-adapter-design.md +0 -252
- package/docs/superpowers/specs/2026-05-08-create-http-design.md +0 -409
- package/docs/superpowers/specs/2026-05-08-hono-app-builder-convergence-design.md +0 -411
- package/docs/superpowers/specs/2026-06-05-dx-feedback-round-design.md +0 -285
- package/docs/superpowers/specs/2026-06-08-dx-feedback-round-2-design.md +0 -376
- package/src/create-http-stream.ts +0 -191
- package/src/create-http.ts +0 -210
- package/src/create-stream.ts +0 -228
- package/src/create.ts +0 -172
- package/src/implementations/http/README.md +0 -390
- package/src/implementations/http/error-dispatch.test.ts +0 -283
- package/src/implementations/http/error-dispatch.ts +0 -176
- package/src/implementations/http/hono/handlers/http-stream.ts +0 -152
- package/src/implementations/http/hono/handlers/http.ts +0 -145
- package/src/implementations/http/hono/handlers/rpc.ts +0 -54
- package/src/implementations/http/hono/path.test.ts +0 -96
- package/src/index.ts +0 -101
- package/src/schema/extract-json-schema.test.ts +0 -25
- package/src/schema/extract-json-schema.ts +0 -15
- package/src/schema/parser.test.ts +0 -182
- package/src/schema/parser.ts +0 -265
- package/src/schema/resolve-schema-lib.test.ts +0 -19
- package/src/schema/resolve-schema-lib.ts +0 -29
- package/src/schema/types.ts +0 -20
- package/src/types.ts +0 -133
- /package/build/{implementations/http → adapters}/astro/astro-context.d.ts +0 -0
- /package/build/{implementations/http → adapters}/astro/astro-context.js +0 -0
- /package/build/{implementations/http → adapters}/astro/create-handler.d.ts +0 -0
- /package/build/{implementations/http → adapters}/astro/create-handler.js +0 -0
- /package/build/{implementations/http → adapters}/astro/index.d.ts +0 -0
- /package/build/{implementations/http → adapters}/astro/index.js +0 -0
- /package/build/{implementations/http → adapters}/astro/index.test.d.ts +0 -0
- /package/build/{implementations/http → adapters}/astro/rewrite-request.d.ts +0 -0
- /package/build/{implementations/http → adapters}/astro/rewrite-request.js +0 -0
- /package/build/{create-http-stream.test.d.ts → adapters/hono/envelope-parity.test.d.ts} +0 -0
- /package/build/{implementations/http → adapters}/hono/handlers/http-stream.test.d.ts +0 -0
- /package/build/{implementations/http → adapters}/hono/handlers/http.test.d.ts +0 -0
- /package/build/{implementations/http → adapters}/hono/handlers/rpc.test.d.ts +0 -0
- /package/build/{implementations/http → adapters}/hono/handlers/stream.test.d.ts +0 -0
- /package/build/{implementations/http → adapters}/hono/index.test.d.ts +0 -0
- /package/build/{implementations/http → adapters/hono}/on-request-error.test.d.ts +0 -0
- /package/build/{implementations/http → adapters/hono}/route-errors.test.d.ts +0 -0
- /package/build/{create-http.test.d.ts → client/freeze.test.d.ts} +0 -0
- /package/build/{create-stream.test.d.ts → codegen/goldens.test.d.ts} +0 -0
- /package/build/{create.test.d.ts → core/create-http-stream.test.d.ts} +0 -0
- /package/build/{doc-envelope.test.d.ts → core/create-http.test.d.ts} +0 -0
- /package/build/{errors.test.d.ts → core/create-stream.test.d.ts} +0 -0
- /package/build/{implementations/http/doc-registry.test.d.ts → core/create.test.d.ts} +0 -0
- /package/build/{implementations/http/error-dispatch.test.d.ts → core/definition-site.test.d.ts} +0 -0
- /package/build/{implementations/http/error-taxonomy.test.d.ts → core/errors.test.d.ts} +0 -0
- /package/build/{errors.test.js → core/errors.test.js} +0 -0
- /package/build/{implementations/http/hono/path.test.d.ts → core/factory-options.test.d.ts} +0 -0
- /package/build/{migration.test.d.ts → core/migration.test.d.ts} +0 -0
- /package/build/{index.test.d.ts → core/procedures.test.d.ts} +0 -0
- /package/build/{implementations/http/hono → core}/types.js +0 -0
- /package/build/schema/{extract-json-schema.test.d.ts → adapter.test.d.ts} +0 -0
- /package/build/schema/{parser.test.d.ts → compile.test.d.ts} +0 -0
- /package/build/schema/{resolve-schema-lib.test.d.ts → typebox.test.d.ts} +0 -0
- /package/build/{stack-utils.test.d.ts → server/context.test.d.ts} +0 -0
- /package/build/{doc-envelope.js → server/doc-envelope.js} +0 -0
- /package/build/{doc-envelope.test.js → server/doc-envelope.test.js} +0 -0
- /package/build/{implementations → server}/types.js +0 -0
- /package/src/{implementations/http → adapters}/astro/README.md +0 -0
- /package/src/{implementations/http → adapters}/astro/astro-context.ts +0 -0
- /package/src/{implementations/http → adapters}/astro/create-handler.ts +0 -0
- /package/src/{implementations/http → adapters}/astro/index.ts +0 -0
- /package/src/{implementations/http → adapters}/astro/rewrite-request.ts +0 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Captures WHERE a procedure was defined (file:line:column) at registration
|
|
3
|
+
* time, so every error a procedure ever throws can point back to its
|
|
4
|
+
* definition site — not just the call site.
|
|
3
5
|
*/
|
|
6
|
+
|
|
7
|
+
/** A specific location in source code where a procedure was defined. */
|
|
4
8
|
export type DefinitionLocation = {
|
|
5
9
|
file: string
|
|
6
10
|
line: number
|
|
@@ -8,21 +12,19 @@ export type DefinitionLocation = {
|
|
|
8
12
|
raw: string
|
|
9
13
|
}
|
|
10
14
|
|
|
11
|
-
/**
|
|
12
|
-
* Contains information about where a procedure was defined.
|
|
13
|
-
*/
|
|
15
|
+
/** Definition-site info attached to registrations and errors. */
|
|
14
16
|
export type DefinitionInfo = {
|
|
15
17
|
definedAt?: DefinitionLocation
|
|
16
18
|
definitionStack?: string
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
|
-
*
|
|
21
|
-
* Only
|
|
22
|
+
* Library-internal files skipped when walking the stack for the user's frame.
|
|
23
|
+
* Only core library files — never test files or user code.
|
|
22
24
|
*/
|
|
23
25
|
const INTERNAL_FILES = [
|
|
24
|
-
'/
|
|
25
|
-
'/
|
|
26
|
+
'/procedures.ts',
|
|
27
|
+
'/procedures.js',
|
|
26
28
|
'/create.ts',
|
|
27
29
|
'/create.js',
|
|
28
30
|
'/create-stream.ts',
|
|
@@ -31,19 +33,21 @@ const INTERNAL_FILES = [
|
|
|
31
33
|
'/create-http.js',
|
|
32
34
|
'/create-http-stream.ts',
|
|
33
35
|
'/create-http-stream.js',
|
|
36
|
+
'/internal.ts',
|
|
37
|
+
'/internal.js',
|
|
34
38
|
'/errors.ts',
|
|
35
39
|
'/errors.js',
|
|
36
|
-
'/
|
|
37
|
-
'/
|
|
40
|
+
'/definition-site.ts',
|
|
41
|
+
'/definition-site.js',
|
|
38
42
|
'/compute-schema.ts',
|
|
39
43
|
'/compute-schema.js',
|
|
40
|
-
'/
|
|
41
|
-
'/
|
|
44
|
+
'/compile.ts',
|
|
45
|
+
'/compile.js',
|
|
42
46
|
]
|
|
43
47
|
|
|
44
48
|
/**
|
|
45
|
-
* Captures the stack trace at the call site and extracts the definition
|
|
46
|
-
*
|
|
49
|
+
* Captures the stack trace at the call site and extracts the definition
|
|
50
|
+
* location: the first stack frame outside ts-procedures internals.
|
|
47
51
|
*/
|
|
48
52
|
export function captureDefinitionInfo(): DefinitionInfo {
|
|
49
53
|
const err = new Error()
|
|
@@ -55,8 +59,6 @@ export function captureDefinitionInfo(): DefinitionInfo {
|
|
|
55
59
|
|
|
56
60
|
const lines = stack.split('\n')
|
|
57
61
|
|
|
58
|
-
// Find the first frame that's not from ts-procedures internals
|
|
59
|
-
// Skip the first line (Error message) and frames from this module
|
|
60
62
|
let userFrame: string | undefined
|
|
61
63
|
|
|
62
64
|
for (let i = 1; i < lines.length; i++) {
|
|
@@ -64,13 +66,11 @@ export function captureDefinitionInfo(): DefinitionInfo {
|
|
|
64
66
|
if (!rawLine) continue
|
|
65
67
|
const line = rawLine.trim()
|
|
66
68
|
|
|
67
|
-
// Skip empty or invalid frames
|
|
68
69
|
if (!line.startsWith('at ')) {
|
|
69
70
|
continue
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
const isInternalFile = INTERNAL_FILES.some(file => line.includes(file))
|
|
73
|
+
const isInternalFile = INTERNAL_FILES.some((file) => line.includes(file))
|
|
74
74
|
if (isInternalFile) {
|
|
75
75
|
continue
|
|
76
76
|
}
|
|
@@ -109,7 +109,6 @@ export function captureDefinitionInfo(): DefinitionInfo {
|
|
|
109
109
|
* - "at /path/to/file.ts:10:5"
|
|
110
110
|
*/
|
|
111
111
|
function parseStackFrame(frame: string): DefinitionLocation | undefined {
|
|
112
|
-
// Match patterns like "(path:line:column)" or just "path:line:column"
|
|
113
112
|
const match = frame.match(/\(([^)]+):(\d+):(\d+)\)$/) || frame.match(/at ([^:]+):(\d+):(\d+)$/)
|
|
114
113
|
|
|
115
114
|
if (match && match[1] && match[2] && match[3]) {
|
|
@@ -124,9 +123,7 @@ function parseStackFrame(frame: string): DefinitionLocation | undefined {
|
|
|
124
123
|
return undefined
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
/**
|
|
128
|
-
* Formats definition info for appending to error stacks.
|
|
129
|
-
*/
|
|
126
|
+
/** Formats definition info for appending to error stacks. */
|
|
130
127
|
export function formatDefinitionInfo(info: DefinitionInfo, procedureName: string): string | undefined {
|
|
131
128
|
if (!info.definedAt) {
|
|
132
129
|
return undefined
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
ProcedureValidationError,
|
|
5
5
|
ProcedureRegistrationError,
|
|
6
6
|
} from './errors.js'
|
|
7
|
-
import type { DefinitionInfo } from './
|
|
7
|
+
import type { DefinitionInfo } from './definition-site.js'
|
|
8
8
|
|
|
9
9
|
describe('Error Classes', () => {
|
|
10
10
|
test('ProcedureError has correct properties', () => {
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import type { TSchemaValidationError } from './schema/parser.js'
|
|
2
|
-
import type { DefinitionInfo, DefinitionLocation} from './stack-utils.js';
|
|
3
|
-
import { formatDefinitionInfo } from './stack-utils.js'
|
|
4
1
|
import { kebabCase } from 'es-toolkit/string'
|
|
2
|
+
import type { TSchemaValidationError } from '../schema/compile.js'
|
|
3
|
+
import type { DefinitionInfo, DefinitionLocation } from './definition-site.js'
|
|
4
|
+
import { formatDefinitionInfo } from './definition-site.js'
|
|
5
5
|
|
|
6
|
+
/** Discriminant carried by every framework error (alternative to `instanceof`). */
|
|
7
|
+
export type ProcedureErrorKind = 'procedure' | 'validation' | 'yield-validation' | 'registration'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base error for everything the framework throws. Produced by `ctx.error()`
|
|
11
|
+
* in handlers, and used to box non-ProcedureError throws (with the original
|
|
12
|
+
* error as `cause`). Carries the procedure's definition site so stack traces
|
|
13
|
+
* point at the definition, not just the call.
|
|
14
|
+
*/
|
|
6
15
|
export class ProcedureError extends Error {
|
|
16
|
+
readonly kind: ProcedureErrorKind = 'procedure'
|
|
7
17
|
cause?: unknown
|
|
8
18
|
readonly definedAt?: DefinitionLocation
|
|
9
19
|
readonly definitionStack?: string
|
|
@@ -12,8 +22,7 @@ export class ProcedureError extends Error {
|
|
|
12
22
|
readonly procedureName: string,
|
|
13
23
|
readonly message: string,
|
|
14
24
|
readonly meta?: object,
|
|
15
|
-
|
|
16
|
-
definitionInfo?: DefinitionInfo
|
|
25
|
+
definitionInfo?: DefinitionInfo,
|
|
17
26
|
) {
|
|
18
27
|
super(message)
|
|
19
28
|
this.name = 'ProcedureError'
|
|
@@ -25,12 +34,10 @@ export class ProcedureError extends Error {
|
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
// https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
|
|
28
|
-
Object.setPrototypeOf(this,
|
|
37
|
+
Object.setPrototypeOf(this, new.target.prototype)
|
|
29
38
|
}
|
|
30
39
|
|
|
31
|
-
/**
|
|
32
|
-
* Returns a formatted string showing where the procedure was defined.
|
|
33
|
-
*/
|
|
40
|
+
/** Returns `file:line:column` of the procedure definition, when captured. */
|
|
34
41
|
getDefinitionLocation(): string | undefined {
|
|
35
42
|
if (!this.definedAt) {
|
|
36
43
|
return undefined
|
|
@@ -38,9 +45,7 @@ export class ProcedureError extends Error {
|
|
|
38
45
|
return `${this.definedAt.file}:${this.definedAt.line}:${this.definedAt.column}`
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
/**
|
|
42
|
-
* Enhances the error stack with definition location information.
|
|
43
|
-
*/
|
|
48
|
+
/** Appends the definition-site section to the error stack. */
|
|
44
49
|
private enhanceStack(): void {
|
|
45
50
|
if (!this.stack || !this.definedAt) {
|
|
46
51
|
return
|
|
@@ -48,7 +53,7 @@ export class ProcedureError extends Error {
|
|
|
48
53
|
|
|
49
54
|
const definitionSection = formatDefinitionInfo(
|
|
50
55
|
{ definedAt: this.definedAt, definitionStack: this.definitionStack },
|
|
51
|
-
this.procedureName
|
|
56
|
+
this.procedureName,
|
|
52
57
|
)
|
|
53
58
|
|
|
54
59
|
if (definitionSection) {
|
|
@@ -57,52 +62,49 @@ export class ProcedureError extends Error {
|
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
64
|
|
|
65
|
+
/** Schema validation failure for params / req channels (400 in the default taxonomy). */
|
|
60
66
|
export class ProcedureValidationError extends ProcedureError {
|
|
67
|
+
override readonly kind = 'validation'
|
|
68
|
+
|
|
61
69
|
constructor(
|
|
62
70
|
readonly procedureName: string,
|
|
63
71
|
message: string,
|
|
64
72
|
readonly errors?: TSchemaValidationError[],
|
|
65
|
-
|
|
66
|
-
definitionInfo?: DefinitionInfo
|
|
73
|
+
definitionInfo?: DefinitionInfo,
|
|
67
74
|
) {
|
|
68
75
|
const readableErrors = errors
|
|
69
76
|
?.map((err) => `- ${kebabCase(err.instancePath).replace('-', '.')} ${err.message}`)
|
|
70
77
|
.join(', ')
|
|
71
78
|
super(procedureName, message + ' ' + readableErrors, { errors }, definitionInfo)
|
|
72
79
|
this.name = 'ProcedureValidationError'
|
|
73
|
-
|
|
74
|
-
// https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
|
|
75
|
-
Object.setPrototypeOf(this, ProcedureValidationError.prototype)
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
/** Invalid registration: bad schema, duplicate name, misplaced config fields. */
|
|
79
84
|
export class ProcedureRegistrationError extends ProcedureError {
|
|
85
|
+
override readonly kind = 'registration'
|
|
86
|
+
|
|
80
87
|
constructor(
|
|
81
88
|
readonly procedureName: string,
|
|
82
89
|
message: string,
|
|
83
|
-
|
|
84
|
-
definitionInfo?: DefinitionInfo
|
|
90
|
+
definitionInfo?: DefinitionInfo,
|
|
85
91
|
) {
|
|
86
92
|
super(procedureName, message, undefined, definitionInfo)
|
|
87
93
|
this.name = 'ProcedureRegistrationError'
|
|
88
|
-
|
|
89
|
-
// https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
|
|
90
|
-
Object.setPrototypeOf(this, ProcedureRegistrationError.prototype)
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
/** Stream yield failed schema validation (only with `validateYields: true`). */
|
|
94
98
|
export class ProcedureYieldValidationError extends ProcedureError {
|
|
99
|
+
override readonly kind = 'yield-validation'
|
|
100
|
+
|
|
95
101
|
constructor(
|
|
96
102
|
readonly procedureName: string,
|
|
97
103
|
message: string,
|
|
98
104
|
readonly errors?: TSchemaValidationError[],
|
|
99
|
-
|
|
100
|
-
definitionInfo?: DefinitionInfo
|
|
105
|
+
definitionInfo?: DefinitionInfo,
|
|
101
106
|
) {
|
|
102
107
|
super(procedureName, message, undefined, definitionInfo)
|
|
103
108
|
this.name = 'ProcedureYieldValidationError'
|
|
104
|
-
|
|
105
|
-
// https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
|
|
106
|
-
Object.setPrototypeOf(this, ProcedureYieldValidationError.prototype)
|
|
107
109
|
}
|
|
108
110
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { Type } from 'typebox'
|
|
3
|
+
import { Procedures } from './procedures.js'
|
|
4
|
+
import { ProcedureValidationError } from './errors.js'
|
|
5
|
+
import type { SchemaAdapter } from '../schema/adapter.js'
|
|
6
|
+
|
|
7
|
+
describe('Procedures factory options (new in v9)', () => {
|
|
8
|
+
describe('schema.adapters', () => {
|
|
9
|
+
// A minimal stand-in for a zod/valibot-style adapter: schema objects carry
|
|
10
|
+
// their JSON Schema under a custom marker key.
|
|
11
|
+
const fakeLibAdapter: SchemaAdapter = {
|
|
12
|
+
name: 'fake-lib',
|
|
13
|
+
detect: (s): boolean =>
|
|
14
|
+
typeof s === 'object' && s !== null && '__fakeJsonSchema' in s,
|
|
15
|
+
toJsonSchema: (s) => (s as { __fakeJsonSchema: Record<string, any> }).__fakeJsonSchema,
|
|
16
|
+
}
|
|
17
|
+
const fakeSchema = {
|
|
18
|
+
__fakeJsonSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: { id: { type: 'string' } },
|
|
21
|
+
required: ['id'],
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
it('registers and validates with a custom schema adapter', async () => {
|
|
26
|
+
const { Create } = Procedures({ schema: { adapters: [fakeLibAdapter] } })
|
|
27
|
+
const { GetThing } = Create('GetThing', { schema: { params: fakeSchema } }, async (_ctx, params) => params)
|
|
28
|
+
|
|
29
|
+
await expect(GetThing({}, { id: 'a' } as never)).resolves.toEqual({ id: 'a' })
|
|
30
|
+
await expect(GetThing({}, {} as never)).rejects.toThrow(ProcedureValidationError)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('custom adapters take precedence over the built-in TypeBox adapter', () => {
|
|
34
|
+
// An adapter that recognizes EVERYTHING and maps to a permissive schema —
|
|
35
|
+
// if it runs first, a TypeBox schema never reaches the typebox adapter.
|
|
36
|
+
const greedy: SchemaAdapter = {
|
|
37
|
+
name: 'greedy',
|
|
38
|
+
detect: () => true,
|
|
39
|
+
toJsonSchema: () => ({ type: 'object' }),
|
|
40
|
+
}
|
|
41
|
+
const { Create } = Procedures({ schema: { adapters: [greedy] } })
|
|
42
|
+
const { Info } = { Info: Create('Info', { schema: { params: Type.Object({ n: Type.Number() }) } }, async () => 'ok') }
|
|
43
|
+
expect(Info.info.schema.params).toEqual({ type: 'object' })
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('TypeBox keeps working with custom adapters present', async () => {
|
|
47
|
+
const { Create } = Procedures({ schema: { adapters: [fakeLibAdapter] } })
|
|
48
|
+
const { Echo } = Create('Echo', { schema: { params: Type.Object({ n: Type.Number() }) } }, async (_ctx, p) => p)
|
|
49
|
+
await expect(Echo({}, { n: 1 })).resolves.toEqual({ n: 1 })
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('validation.ajv', () => {
|
|
54
|
+
it('merges custom AJV options over the defaults', async () => {
|
|
55
|
+
const { Create } = Procedures({ validation: { ajv: { coerceTypes: false } } })
|
|
56
|
+
const { Strict } = Create('Strict', { schema: { params: Type.Object({ n: Type.Number() }) } }, async (_ctx, p) => p)
|
|
57
|
+
|
|
58
|
+
// Default config would coerce '1' → 1; with coerceTypes off it must fail.
|
|
59
|
+
await expect(Strict({}, { n: '1' } as never)).rejects.toThrow(ProcedureValidationError)
|
|
60
|
+
await expect(Strict({}, { n: 1 })).resolves.toEqual({ n: 1 })
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('http defaults', () => {
|
|
65
|
+
const reqId = { req: { pathParams: Type.Object({ id: Type.String() }) } }
|
|
66
|
+
|
|
67
|
+
it('applies pathPrefix and scope to CreateHttp routes', () => {
|
|
68
|
+
const { CreateHttp } = Procedures({ http: { pathPrefix: '/v1', scope: 'billing' } })
|
|
69
|
+
const { info } = CreateHttp(
|
|
70
|
+
'GetInvoice',
|
|
71
|
+
{ path: '/invoices/:id', method: 'get', schema: reqId },
|
|
72
|
+
async () => undefined,
|
|
73
|
+
)
|
|
74
|
+
expect(info.path).toBe('/v1/invoices/:id')
|
|
75
|
+
expect(info.scope).toBe('billing')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('per-route scope wins over the factory default', () => {
|
|
79
|
+
const { CreateHttp } = Procedures({ http: { scope: 'billing' } })
|
|
80
|
+
const { info } = CreateHttp(
|
|
81
|
+
'GetReport',
|
|
82
|
+
{ path: '/reports', method: 'get', scope: 'reports', schema: {} },
|
|
83
|
+
async () => undefined,
|
|
84
|
+
)
|
|
85
|
+
expect(info.scope).toBe('reports')
|
|
86
|
+
expect(info.path).toBe('/reports')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('applies pathPrefix to CreateHttpStream routes', () => {
|
|
90
|
+
const { CreateHttpStream } = Procedures({ http: { pathPrefix: 'v1' } })
|
|
91
|
+
const { info } = CreateHttpStream(
|
|
92
|
+
'Watch',
|
|
93
|
+
{ path: 'watch', method: 'get', schema: {} },
|
|
94
|
+
// eslint-disable-next-line require-yield
|
|
95
|
+
async function* () { return undefined },
|
|
96
|
+
)
|
|
97
|
+
// both prefix and path get normalized to a single joining slash
|
|
98
|
+
expect(info.path).toBe('/v1/watch')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('path-param consistency is checked against the prefixed path', () => {
|
|
102
|
+
const { CreateHttp } = Procedures({ http: { pathPrefix: '/v1' } })
|
|
103
|
+
expect(() =>
|
|
104
|
+
CreateHttp(
|
|
105
|
+
'Bad',
|
|
106
|
+
{ path: '/things/:id', method: 'get', schema: {} },
|
|
107
|
+
async () => undefined,
|
|
108
|
+
),
|
|
109
|
+
).toThrow(/path parameters \[id\] but schema\.req\.pathParams is not defined/)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-shape helpers shared by CreateHttp and CreateHttpStream.
|
|
3
|
+
*/
|
|
4
|
+
import { ProcedureRegistrationError } from './errors.js'
|
|
5
|
+
import type { DefinitionInfo } from './definition-site.js'
|
|
6
|
+
|
|
7
|
+
const PATH_PARAM_RE = /:([a-zA-Z_][a-zA-Z0-9_]*)/g
|
|
8
|
+
|
|
9
|
+
export function extractPathParamNames(path: string): string[] {
|
|
10
|
+
const matches = path.match(PATH_PARAM_RE)
|
|
11
|
+
return matches ? matches.map((m) => m.slice(1)) : []
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Applies the factory-level `http.pathPrefix` default to a route path.
|
|
16
|
+
* Both sides are normalized to exactly one joining `/`.
|
|
17
|
+
*/
|
|
18
|
+
export function applyPathPrefix(prefix: string | undefined, path: string): string {
|
|
19
|
+
if (!prefix) return path
|
|
20
|
+
const normalizedPrefix = prefix.startsWith('/') ? prefix : `/${prefix}`
|
|
21
|
+
const trimmedPrefix = normalizedPrefix.endsWith('/') ? normalizedPrefix.slice(0, -1) : normalizedPrefix
|
|
22
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`
|
|
23
|
+
return `${trimmedPrefix}${normalizedPath}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Registration-time guard: the `:params` in the path and the keys of
|
|
28
|
+
* `schema.req.pathParams` must agree exactly. Catches typos where a handler
|
|
29
|
+
* would silently receive `undefined` for a misspelled param.
|
|
30
|
+
*/
|
|
31
|
+
export function checkPathParamConsistency(
|
|
32
|
+
procedureName: string,
|
|
33
|
+
path: string,
|
|
34
|
+
pathParamsSchema: Record<string, unknown> | undefined,
|
|
35
|
+
definitionInfo: DefinitionInfo,
|
|
36
|
+
): void {
|
|
37
|
+
const pathParamNames = extractPathParamNames(path)
|
|
38
|
+
const hasPathParams = pathParamNames.length > 0
|
|
39
|
+
const hasSchema = pathParamsSchema !== undefined
|
|
40
|
+
|
|
41
|
+
if (hasPathParams && !hasSchema) {
|
|
42
|
+
throw new ProcedureRegistrationError(
|
|
43
|
+
procedureName,
|
|
44
|
+
`Path "${path}" has path parameters [${pathParamNames.join(', ')}] but schema.req.pathParams is not defined.`,
|
|
45
|
+
definitionInfo,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
if (!hasPathParams && hasSchema) {
|
|
49
|
+
throw new ProcedureRegistrationError(
|
|
50
|
+
procedureName,
|
|
51
|
+
`schema.req.pathParams is defined but path "${path}" has no path parameters.`,
|
|
52
|
+
definitionInfo,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
if (hasPathParams && hasSchema) {
|
|
56
|
+
const schemaProperties = (pathParamsSchema as { properties?: Record<string, unknown> }).properties
|
|
57
|
+
if (schemaProperties) {
|
|
58
|
+
const schemaKeys = Object.keys(schemaProperties)
|
|
59
|
+
const missing = pathParamNames.filter((p) => !schemaKeys.includes(p))
|
|
60
|
+
const extra = schemaKeys.filter((k) => !pathParamNames.includes(k))
|
|
61
|
+
if (missing.length > 0 || extra.length > 0) {
|
|
62
|
+
const parts: string[] = []
|
|
63
|
+
if (missing.length > 0) parts.push(`path has [${missing.join(', ')}] missing from schema`)
|
|
64
|
+
if (extra.length > 0) parts.push(`schema has [${extra.join(', ')}] not in path`)
|
|
65
|
+
throw new ProcedureRegistrationError(
|
|
66
|
+
procedureName,
|
|
67
|
+
`Path param mismatch for "${procedureName}": ${parts.join('; ')}.`,
|
|
68
|
+
definitionInfo,
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared machinery for the four creators. Everything here is package-private:
|
|
3
|
+
* the public API is `Procedures()` and the creators it returns.
|
|
4
|
+
*/
|
|
5
|
+
import { ProcedureError, ProcedureRegistrationError, ProcedureValidationError, ProcedureYieldValidationError } from './errors.js'
|
|
6
|
+
import type { DefinitionInfo } from './definition-site.js'
|
|
7
|
+
import type { AnyProcedureRegistration } from './types.js'
|
|
8
|
+
import type { SchemaAdapter } from '../schema/adapter.js'
|
|
9
|
+
import type { Validate, ValidatorCompiler } from '../schema/compile.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Everything a creator needs from its owning factory, resolved once in
|
|
13
|
+
* `Procedures()`.
|
|
14
|
+
*/
|
|
15
|
+
export type FactoryRuntime<TContext, TExtendedConfig> = {
|
|
16
|
+
registry: Map<string, AnyProcedureRegistration<TContext, TExtendedConfig>>
|
|
17
|
+
onCreate?: (procedure: AnyProcedureRegistration<TContext, TExtendedConfig>) => void
|
|
18
|
+
/** True when the factory was created with `validation: false`. */
|
|
19
|
+
skipValidation: boolean
|
|
20
|
+
adapters: readonly SchemaAdapter[]
|
|
21
|
+
compile: ValidatorCompiler
|
|
22
|
+
httpDefaults?: { pathPrefix?: string; scope?: string }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const HTTP_FIELDS = ['path', 'method', 'req', 'res', 'successStatus'] as const
|
|
26
|
+
|
|
27
|
+
/** Create/CreateStream reject HTTP-only config fields with a pointer to CreateHttp. */
|
|
28
|
+
export function assertNoHttpFields(
|
|
29
|
+
creatorName: 'Create' | 'CreateStream',
|
|
30
|
+
procedureName: string,
|
|
31
|
+
config: Record<string, unknown>,
|
|
32
|
+
definitionInfo: DefinitionInfo,
|
|
33
|
+
): void {
|
|
34
|
+
const presentHttpFields = HTTP_FIELDS.filter((f) => config[f] !== undefined)
|
|
35
|
+
if (presentHttpFields.length > 0) {
|
|
36
|
+
throw new ProcedureRegistrationError(
|
|
37
|
+
procedureName,
|
|
38
|
+
`HTTP fields require CreateHttp / CreateHttpStream. Procedure "${procedureName}" has [${presentHttpFields.join(', ')}] which are not valid on ${creatorName}.`,
|
|
39
|
+
definitionInfo,
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Duplicate names fail fast, BEFORE schema computation. */
|
|
45
|
+
export function assertNotDuplicate(
|
|
46
|
+
registry: Map<string, unknown>,
|
|
47
|
+
name: string,
|
|
48
|
+
definitionInfo: DefinitionInfo,
|
|
49
|
+
): void {
|
|
50
|
+
if (registry.has(name)) {
|
|
51
|
+
throw new ProcedureRegistrationError(
|
|
52
|
+
name,
|
|
53
|
+
`Procedure with name ${name} is already registered`,
|
|
54
|
+
definitionInfo,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Should this call skip runtime validation? Either the caller already
|
|
61
|
+
* validated (`ctx.isPrevalidated`, set by HTTP builders) or the whole factory
|
|
62
|
+
* opted out (`validation: false`).
|
|
63
|
+
*/
|
|
64
|
+
export function shouldSkipValidation(ctx: unknown, runtime: { skipValidation: boolean }): boolean {
|
|
65
|
+
return Boolean((ctx as { isPrevalidated?: boolean })?.isPrevalidated) || runtime.skipValidation
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Validates RPC params; throws `ProcedureValidationError` on failure. */
|
|
69
|
+
export function validateParams(
|
|
70
|
+
name: string,
|
|
71
|
+
validate: Validate | undefined,
|
|
72
|
+
params: unknown,
|
|
73
|
+
definitionInfo: DefinitionInfo,
|
|
74
|
+
): void {
|
|
75
|
+
if (!validate) return
|
|
76
|
+
const { errors } = validate(params)
|
|
77
|
+
if (errors) {
|
|
78
|
+
throw new ProcedureValidationError(name, `Validation error for ${name}`, errors, definitionInfo)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Validates each HTTP req channel independently; error messages name the channel. */
|
|
83
|
+
export function validateReqChannels(
|
|
84
|
+
name: string,
|
|
85
|
+
validators: Record<string, Validate> | undefined,
|
|
86
|
+
req: unknown,
|
|
87
|
+
definitionInfo: DefinitionInfo,
|
|
88
|
+
): void {
|
|
89
|
+
if (!validators) return
|
|
90
|
+
for (const [channel, validate] of Object.entries(validators)) {
|
|
91
|
+
const channelValue = (req as Record<string, unknown> | undefined)?.[channel]
|
|
92
|
+
const { errors } = validate(channelValue)
|
|
93
|
+
if (errors) {
|
|
94
|
+
throw new ProcedureValidationError(
|
|
95
|
+
name,
|
|
96
|
+
`Validation error for ${name} in req.${channel}`,
|
|
97
|
+
errors,
|
|
98
|
+
definitionInfo,
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Boxes a non-ProcedureError thrown by a unary handler into a ProcedureError
|
|
106
|
+
* (original error as `cause`), preserving the original stack and appending
|
|
107
|
+
* the definition site. ProcedureErrors pass through untouched.
|
|
108
|
+
*/
|
|
109
|
+
export function toProcedureError(
|
|
110
|
+
name: string,
|
|
111
|
+
definitionInfo: DefinitionInfo,
|
|
112
|
+
error: any,
|
|
113
|
+
): ProcedureError {
|
|
114
|
+
if (error instanceof ProcedureError) return error
|
|
115
|
+
const err = new ProcedureError(
|
|
116
|
+
name,
|
|
117
|
+
`Error in handler for ${name} - ${error?.message}`,
|
|
118
|
+
undefined,
|
|
119
|
+
definitionInfo,
|
|
120
|
+
)
|
|
121
|
+
err.cause = error
|
|
122
|
+
if (error?.stack && definitionInfo.definedAt) {
|
|
123
|
+
const { file, line, column } = definitionInfo.definedAt
|
|
124
|
+
err.stack =
|
|
125
|
+
error.stack + `\n--- Procedure "${name}" defined at ---\n at ${file}:${line}:${column}`
|
|
126
|
+
} else if (error?.stack) {
|
|
127
|
+
err.stack = error.stack
|
|
128
|
+
}
|
|
129
|
+
return err
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Appends the definition site to a thrown error's stack, in place. */
|
|
133
|
+
export function appendDefinitionSite(error: any, name: string, definitionInfo: DefinitionInfo): void {
|
|
134
|
+
if (definitionInfo.definedAt && error && typeof error.stack === 'string') {
|
|
135
|
+
const { file, line, column } = definitionInfo.definedAt
|
|
136
|
+
error.stack = `${error.stack}\n--- Procedure "${name}" defined at ---\n at ${file}:${line}:${column}`
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Combines an incoming (e.g. HTTP request) signal with the stream's own controller. */
|
|
141
|
+
export function combineSignals(incoming: AbortSignal | undefined, own: AbortSignal): AbortSignal {
|
|
142
|
+
return incoming ? AbortSignal.any([incoming, own]) : own
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Drives a user stream iterator with the framework guarantees shared by
|
|
147
|
+
* CreateStream and CreateHttpStream:
|
|
148
|
+
* - optional per-yield validation (`validateYields: true`)
|
|
149
|
+
* - errors get the definition site appended to their stack — but keep their
|
|
150
|
+
* original class, so HTTP taxonomies and typed-error dispatch see the real
|
|
151
|
+
* thrown value
|
|
152
|
+
* - on ANY exit, the user iterator's `return()` runs (its `finally` blocks
|
|
153
|
+
* fire on early consumer close) and the controller aborts with reason
|
|
154
|
+
* `'stream-completed'` so handlers can distinguish completion from client
|
|
155
|
+
* disconnect
|
|
156
|
+
* - the user generator's return value is propagated (becomes the
|
|
157
|
+
* `event: 'return'` SSE payload downstream)
|
|
158
|
+
*/
|
|
159
|
+
export async function* iterateWithGuards(
|
|
160
|
+
userIterator: AsyncIterator<any, any, unknown>,
|
|
161
|
+
options: {
|
|
162
|
+
name: string
|
|
163
|
+
validateYields: boolean
|
|
164
|
+
validateYield: Validate | undefined
|
|
165
|
+
abortController: AbortController
|
|
166
|
+
definitionInfo: DefinitionInfo
|
|
167
|
+
},
|
|
168
|
+
): AsyncGenerator<any, any, unknown> {
|
|
169
|
+
const { name, validateYields, validateYield, abortController, definitionInfo } = options
|
|
170
|
+
try {
|
|
171
|
+
let result = await userIterator.next()
|
|
172
|
+
while (!result.done) {
|
|
173
|
+
const value = result.value
|
|
174
|
+
if (validateYields && validateYield) {
|
|
175
|
+
const { errors } = validateYield(value)
|
|
176
|
+
if (errors) {
|
|
177
|
+
throw new ProcedureYieldValidationError(
|
|
178
|
+
name,
|
|
179
|
+
`Yield validation error for ${name}`,
|
|
180
|
+
errors,
|
|
181
|
+
definitionInfo,
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
yield value
|
|
186
|
+
result = await userIterator.next()
|
|
187
|
+
}
|
|
188
|
+
return result.value
|
|
189
|
+
} catch (error: any) {
|
|
190
|
+
appendDefinitionSite(error, name, definitionInfo)
|
|
191
|
+
throw error
|
|
192
|
+
} finally {
|
|
193
|
+
// Propagate `.return()` to the user generator so its `finally` blocks run
|
|
194
|
+
// when the consumer closes the stream early. No-op when iteration already
|
|
195
|
+
// completed.
|
|
196
|
+
try {
|
|
197
|
+
await userIterator.return?.(undefined)
|
|
198
|
+
} catch {
|
|
199
|
+
// Swallow — cleanup must not mask the primary error path
|
|
200
|
+
}
|
|
201
|
+
abortController.abort('stream-completed')
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { Procedures } from './
|
|
2
|
+
import { Procedures } from './procedures.js'
|
|
3
3
|
import { ProcedureRegistrationError } from './errors.js'
|
|
4
4
|
import { Type } from 'typebox'
|
|
5
5
|
|
|
@@ -24,6 +24,28 @@ describe('v7 → v8 migration errors', () => {
|
|
|
24
24
|
})
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
+
describe('schema.params rejected on HTTP creators', () => {
|
|
28
|
+
it('CreateHttp rejects schema.params with migration message', () => {
|
|
29
|
+
const procs = Procedures()
|
|
30
|
+
expect(() =>
|
|
31
|
+
procs.CreateHttp('LegacyHttp', {
|
|
32
|
+
path: '/x', method: 'get',
|
|
33
|
+
schema: { params: Type.Object({}) } as any,
|
|
34
|
+
}, async () => undefined)
|
|
35
|
+
).toThrow(/Use schema.req.body \(or schema.req.query\) instead of schema.params/)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('CreateHttpStream rejects schema.params with migration message', () => {
|
|
39
|
+
const procs = Procedures()
|
|
40
|
+
expect(() =>
|
|
41
|
+
procs.CreateHttpStream('LegacyHttpStream', {
|
|
42
|
+
path: '/x', method: 'get',
|
|
43
|
+
schema: { params: Type.Object({}), yield: Type.Number() } as any,
|
|
44
|
+
}, async function* () {})
|
|
45
|
+
).toThrow(/Use schema.req.body \(or schema.req.query\) instead of schema.params/)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
27
49
|
describe('HTTP fields rejected on RPC creators', () => {
|
|
28
50
|
const HTTP_FIELDS = ['path', 'method', 'req', 'res', 'successStatus'] as const
|
|
29
51
|
|