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
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
ProcedureError,
|
|
4
4
|
ProcedureValidationError,
|
|
5
5
|
ProcedureYieldValidationError,
|
|
6
|
-
} from '../../errors.js'
|
|
7
|
-
import type { TProcedureRegistration } from '../../
|
|
8
|
-
import
|
|
6
|
+
} from '../../core/errors.js'
|
|
7
|
+
import type { TProcedureRegistration } from '../../core/types.js'
|
|
8
|
+
import { Ajv } from 'ajv'
|
|
9
9
|
import {
|
|
10
10
|
defineErrorTaxonomy,
|
|
11
11
|
resolveErrorResponse,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
taxonomyToErrorDocs,
|
|
14
14
|
defaultErrorSchema,
|
|
15
15
|
defaultErrorBody,
|
|
16
|
-
} from './
|
|
16
|
+
} from './taxonomy.js'
|
|
17
17
|
|
|
18
18
|
class UseCaseError extends Error {
|
|
19
19
|
constructor(
|
|
@@ -34,7 +34,12 @@ class AuthError extends Error {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const fakeProcedure = {
|
|
37
|
+
const fakeProcedure = {
|
|
38
|
+
name: 'Test',
|
|
39
|
+
kind: 'rpc',
|
|
40
|
+
config: {},
|
|
41
|
+
handler: async () => {},
|
|
42
|
+
} as unknown as TProcedureRegistration
|
|
38
43
|
|
|
39
44
|
describe('defineErrorTaxonomy', () => {
|
|
40
45
|
test('validates exactly one discriminator per entry', () => {
|
|
@@ -78,8 +83,8 @@ describe('resolveErrorResponse', () => {
|
|
|
78
83
|
procedure: fakeProcedure,
|
|
79
84
|
raw: {},
|
|
80
85
|
})
|
|
81
|
-
expect(resolved
|
|
82
|
-
expect(resolved
|
|
86
|
+
expect(resolved!.statusCode).toBe(422)
|
|
87
|
+
expect(resolved!.body).toEqual({ name: 'UseCaseError', message: 'external' })
|
|
83
88
|
})
|
|
84
89
|
|
|
85
90
|
test('match predicate catches 3rd-party errors', () => {
|
|
@@ -97,8 +102,8 @@ describe('resolveErrorResponse', () => {
|
|
|
97
102
|
procedure: fakeProcedure,
|
|
98
103
|
raw: {},
|
|
99
104
|
})
|
|
100
|
-
expect(resolved
|
|
101
|
-
expect(resolved
|
|
105
|
+
expect(resolved!.statusCode).toBe(409)
|
|
106
|
+
expect(resolved!.body).toEqual({ name: 'Conflict' })
|
|
102
107
|
})
|
|
103
108
|
|
|
104
109
|
test('default toResponse emits { name, message } from entry key', () => {
|
|
@@ -111,7 +116,7 @@ describe('resolveErrorResponse', () => {
|
|
|
111
116
|
procedure: fakeProcedure,
|
|
112
117
|
raw: {},
|
|
113
118
|
})
|
|
114
|
-
expect(resolved
|
|
119
|
+
expect(resolved!.body).toEqual({ name: 'AuthError', message: 'unauthenticated' })
|
|
115
120
|
})
|
|
116
121
|
|
|
117
122
|
test('first matching entry wins — subclass declared before base', () => {
|
|
@@ -134,8 +139,8 @@ describe('resolveErrorResponse', () => {
|
|
|
134
139
|
includeDefaults: false,
|
|
135
140
|
raw: {},
|
|
136
141
|
})
|
|
137
|
-
expect(resolved
|
|
138
|
-
expect(resolved
|
|
142
|
+
expect(resolved!.statusCode).toBe(400)
|
|
143
|
+
expect(resolved!.body).toEqual({ name: 'ProcedureValidationError', kind: 'validation' })
|
|
139
144
|
})
|
|
140
145
|
|
|
141
146
|
test('topological sort fixes a subclass that was declared after its base', () => {
|
|
@@ -160,8 +165,8 @@ describe('resolveErrorResponse', () => {
|
|
|
160
165
|
includeDefaults: false,
|
|
161
166
|
raw: {},
|
|
162
167
|
})
|
|
163
|
-
expect(resolved
|
|
164
|
-
expect(resolved
|
|
168
|
+
expect(resolved!.statusCode).toBe(400)
|
|
169
|
+
expect(resolved!.body).toEqual({ name: 'ProcedureValidationError', kind: 'validation' })
|
|
165
170
|
})
|
|
166
171
|
|
|
167
172
|
test('falls through to unknownError when nothing matches', () => {
|
|
@@ -178,8 +183,8 @@ describe('resolveErrorResponse', () => {
|
|
|
178
183
|
procedure: fakeProcedure,
|
|
179
184
|
raw: {},
|
|
180
185
|
})
|
|
181
|
-
expect(resolved
|
|
182
|
-
expect(resolved
|
|
186
|
+
expect(resolved!.statusCode).toBe(500)
|
|
187
|
+
expect(resolved!.body).toEqual({ name: 'InternalServerError' })
|
|
183
188
|
})
|
|
184
189
|
|
|
185
190
|
test('returns null when nothing matches and no unknownError', () => {
|
|
@@ -201,8 +206,8 @@ describe('resolveErrorResponse', () => {
|
|
|
201
206
|
procedure: fakeProcedure,
|
|
202
207
|
raw: {},
|
|
203
208
|
})
|
|
204
|
-
expect(resolved
|
|
205
|
-
expect((resolved
|
|
209
|
+
expect(resolved!.statusCode).toBe(400)
|
|
210
|
+
expect((resolved!.body as any).name).toBe('ProcedureValidationError')
|
|
206
211
|
})
|
|
207
212
|
|
|
208
213
|
test('default taxonomy catches ProcedureYieldValidationError with status 500', () => {
|
|
@@ -211,8 +216,8 @@ describe('resolveErrorResponse', () => {
|
|
|
211
216
|
procedure: fakeProcedure,
|
|
212
217
|
raw: {},
|
|
213
218
|
})
|
|
214
|
-
expect(resolved
|
|
215
|
-
expect((resolved
|
|
219
|
+
expect(resolved!.statusCode).toBe(500)
|
|
220
|
+
expect((resolved!.body as any).name).toBe('ProcedureYieldValidationError')
|
|
216
221
|
})
|
|
217
222
|
|
|
218
223
|
test('includeDefaults: false disables the default taxonomy', () => {
|
|
@@ -238,8 +243,8 @@ describe('resolveErrorResponse', () => {
|
|
|
238
243
|
procedure: fakeProcedure,
|
|
239
244
|
raw: {},
|
|
240
245
|
})
|
|
241
|
-
expect(resolved
|
|
242
|
-
expect(resolved
|
|
246
|
+
expect(resolved!.statusCode).toBe(418)
|
|
247
|
+
expect(resolved!.body).toEqual({ name: 'ProcedureValidationError', overridden: true })
|
|
243
248
|
})
|
|
244
249
|
|
|
245
250
|
test('onCatch is awaited via runOnCatch', async () => {
|
|
@@ -300,15 +305,15 @@ describe('resolveErrorResponse', () => {
|
|
|
300
305
|
err: new AuthError('forbidden'),
|
|
301
306
|
userTaxonomy: taxonomy,
|
|
302
307
|
procedure: fakeProcedure,
|
|
303
|
-
raw: { marker: '
|
|
308
|
+
raw: { marker: 'adapter-context' },
|
|
304
309
|
})
|
|
305
310
|
await resolved!.runOnCatch()
|
|
306
311
|
expect(received.procedure).toBe(fakeProcedure)
|
|
307
312
|
expect(received.key).toBe('AuthError')
|
|
308
|
-
expect(received.raw).toEqual({ marker: '
|
|
313
|
+
expect(received.raw).toEqual({ marker: 'adapter-context' })
|
|
309
314
|
})
|
|
310
315
|
|
|
311
|
-
test('defaultErrorTaxonomy exposes
|
|
316
|
+
test('defaultErrorTaxonomy exposes the framework error mappings', () => {
|
|
312
317
|
expect(defaultErrorTaxonomy.ProcedureValidationError.statusCode).toBe(400)
|
|
313
318
|
expect(defaultErrorTaxonomy.ProcedureYieldValidationError.statusCode).toBe(500)
|
|
314
319
|
expect(defaultErrorTaxonomy.ProcedureError.statusCode).toBe(500)
|
|
@@ -335,8 +340,8 @@ describe('resolveErrorResponse', () => {
|
|
|
335
340
|
procedure: fakeProcedure,
|
|
336
341
|
raw: {},
|
|
337
342
|
})
|
|
338
|
-
expect(resolved
|
|
339
|
-
expect(resolved
|
|
343
|
+
expect(resolved!.statusCode).toBe(422)
|
|
344
|
+
expect(resolved!.body).toEqual({ name: 'UseCaseError', message: 'public' })
|
|
340
345
|
})
|
|
341
346
|
|
|
342
347
|
test('wrapped ProcedureError falls through default taxonomy to unknownError', () => {
|
|
@@ -353,8 +358,8 @@ describe('resolveErrorResponse', () => {
|
|
|
353
358
|
procedure: fakeProcedure,
|
|
354
359
|
raw: {},
|
|
355
360
|
})
|
|
356
|
-
expect(resolved
|
|
357
|
-
expect(resolved
|
|
361
|
+
expect(resolved!.statusCode).toBe(500)
|
|
362
|
+
expect(resolved!.body).toEqual({ name: 'InternalServerError', type: 'TypeError' })
|
|
358
363
|
})
|
|
359
364
|
|
|
360
365
|
test('direct ProcedureError (no cause) still caught by default entry', () => {
|
|
@@ -364,8 +369,8 @@ describe('resolveErrorResponse', () => {
|
|
|
364
369
|
procedure: fakeProcedure,
|
|
365
370
|
raw: {},
|
|
366
371
|
})
|
|
367
|
-
expect(resolved
|
|
368
|
-
expect((resolved
|
|
372
|
+
expect(resolved!.statusCode).toBe(500)
|
|
373
|
+
expect((resolved!.body as any).name).toBe('ProcedureError')
|
|
369
374
|
})
|
|
370
375
|
|
|
371
376
|
test('auto-injects name when toResponse omits it', () => {
|
|
@@ -383,7 +388,7 @@ describe('resolveErrorResponse', () => {
|
|
|
383
388
|
procedure: fakeProcedure,
|
|
384
389
|
raw: {},
|
|
385
390
|
})
|
|
386
|
-
expect(resolved
|
|
391
|
+
expect(resolved!.body).toEqual({ name: 'UseCaseError', message: 'ext', detail: 'int' })
|
|
387
392
|
})
|
|
388
393
|
|
|
389
394
|
test('preserves explicit name in toResponse output', () => {
|
|
@@ -400,7 +405,7 @@ describe('resolveErrorResponse', () => {
|
|
|
400
405
|
procedure: fakeProcedure,
|
|
401
406
|
raw: {},
|
|
402
407
|
})
|
|
403
|
-
expect((resolved
|
|
408
|
+
expect((resolved!.body as any).name).toBe('CustomAlias')
|
|
404
409
|
})
|
|
405
410
|
|
|
406
411
|
test('defineErrorTaxonomy topologically sorts class entries (subclass before base)', () => {
|
|
@@ -427,7 +432,7 @@ describe('resolveErrorResponse', () => {
|
|
|
427
432
|
procedure: fakeProcedure,
|
|
428
433
|
raw: {},
|
|
429
434
|
})
|
|
430
|
-
expect(resolved
|
|
435
|
+
expect(resolved!.statusCode).toBe(400)
|
|
431
436
|
})
|
|
432
437
|
|
|
433
438
|
test('topological sort preserves declared order for unrelated classes', () => {
|
|
@@ -448,8 +453,9 @@ describe('taxonomyToErrorDocs', () => {
|
|
|
448
453
|
})
|
|
449
454
|
const docs = taxonomyToErrorDocs(taxonomy)
|
|
450
455
|
const auth = docs.find((d) => d.name === 'AuthError')
|
|
451
|
-
expect(auth
|
|
452
|
-
expect(auth
|
|
456
|
+
expect(auth).toBeDefined()
|
|
457
|
+
expect(auth!.statusCode).toBe(401)
|
|
458
|
+
expect(auth!.schema).toEqual({
|
|
453
459
|
type: 'object',
|
|
454
460
|
properties: {
|
|
455
461
|
name: { type: 'string', const: 'AuthError' },
|
|
@@ -469,7 +475,7 @@ describe('taxonomyToErrorDocs', () => {
|
|
|
469
475
|
})
|
|
470
476
|
const docs = taxonomyToErrorDocs(taxonomy)
|
|
471
477
|
const useCase = docs.find((d) => d.name === 'UseCaseError')
|
|
472
|
-
expect(useCase
|
|
478
|
+
expect(useCase!.schema).toBeUndefined()
|
|
473
479
|
})
|
|
474
480
|
|
|
475
481
|
test('preserves an explicit schema untouched', () => {
|
|
@@ -490,7 +496,7 @@ describe('taxonomyToErrorDocs', () => {
|
|
|
490
496
|
})
|
|
491
497
|
const docs = taxonomyToErrorDocs(taxonomy)
|
|
492
498
|
const useCase = docs.find((d) => d.name === 'UseCaseError')
|
|
493
|
-
expect(useCase
|
|
499
|
+
expect(useCase!.schema).toBe(explicitSchema)
|
|
494
500
|
})
|
|
495
501
|
})
|
|
496
502
|
|
|
@@ -529,7 +535,7 @@ describe('defaultErrorSchema', () => {
|
|
|
529
535
|
// against the schema codegen turns into the client error class. If either side
|
|
530
536
|
// changes shape, this fails before consumers see a mismatch.
|
|
531
537
|
describe('defaultErrorBody / defaultErrorSchema invariant', () => {
|
|
532
|
-
const ajv = new
|
|
538
|
+
const ajv = new Ajv()
|
|
533
539
|
|
|
534
540
|
test('default body validates against the synthesized schema', () => {
|
|
535
541
|
const schema = defaultErrorSchema('AuthError', { class: AuthError, statusCode: 401 })
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
ProcedureError,
|
|
3
3
|
ProcedureValidationError,
|
|
4
4
|
ProcedureYieldValidationError,
|
|
5
|
-
} from '../../errors.js'
|
|
6
|
-
import type {
|
|
5
|
+
} from '../../core/errors.js'
|
|
6
|
+
import type { AnyProcedureRegistration } from '../../core/types.js'
|
|
7
7
|
import type { ErrorDoc } from '../types.js'
|
|
8
8
|
|
|
9
9
|
/** Any procedure registration — accepted by taxonomy callbacks. */
|
|
10
|
-
export type TAnyProcedureRegistration =
|
|
10
|
+
export type TAnyProcedureRegistration = AnyProcedureRegistration<any, any>
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* An entry in an {@link ErrorTaxonomy}. Describes how to recognize a thrown
|
|
@@ -35,7 +35,7 @@ export type ErrorTaxonomyEntry<TError = any, TBody = unknown> = {
|
|
|
35
35
|
toResponse?: (err: TError, meta: { key: string }) => TBody
|
|
36
36
|
/**
|
|
37
37
|
* Side effect on catch (logging, metrics). Awaited before the response is sent.
|
|
38
|
-
* `raw` is the
|
|
38
|
+
* `raw` is the server-adapter-specific request context (e.g., Hono `Context`) —
|
|
39
39
|
* cast as needed.
|
|
40
40
|
*/
|
|
41
41
|
onCatch?: (
|
|
@@ -222,24 +222,15 @@ export function defaultErrorBody(key: string, err: unknown): { name: string; mes
|
|
|
222
222
|
* Synthesizes the response-body JSON Schema for a taxonomy entry that ships
|
|
223
223
|
* neither an explicit `schema` nor a custom `toResponse`.
|
|
224
224
|
*
|
|
225
|
-
* The common case for `defineErrorTaxonomy` is `{ class, statusCode }` only.
|
|
226
|
-
* For those entries the default `toResponse` (see `resolveErrorResponse`) emits
|
|
227
|
-
* exactly `{ name: <key>, message }`. Without a schema, `taxonomyToErrorDocs`
|
|
228
|
-
* produced a schema-less `ErrorDoc`, and codegen (`emit-errors.ts`) only emits a
|
|
229
|
-
* typed client error class for docs that carry a schema — so these entries
|
|
230
|
-
* silently fell back to the untyped `ClientHttpError`, while framework errors
|
|
231
|
-
* (which ship schemas) worked. That mismatch was confusing.
|
|
232
|
-
*
|
|
233
|
-
* By describing the default envelope here, the entry becomes self-describing and
|
|
234
|
-
* codegen emits a typed client error class with zero ceremony from the consumer.
|
|
235
|
-
*
|
|
236
225
|
* Rules:
|
|
237
|
-
* - Entry has an explicit `schema` → caller keeps it
|
|
226
|
+
* - Entry has an explicit `schema` → caller keeps it.
|
|
238
227
|
* - Entry has a custom `toResponse` but no `schema` → returns `undefined`; the
|
|
239
228
|
* body shape is unknown and we never guess it.
|
|
240
229
|
* - Entry has neither → returns the `{ name: const <key>, message }` schema that
|
|
241
230
|
* describes {@link defaultErrorBody} — the exact body the runtime serializes.
|
|
242
|
-
* (An invariant test keeps the two from drifting.)
|
|
231
|
+
* (An invariant test keeps the two from drifting.) This makes bare
|
|
232
|
+
* `{ class, statusCode }` entries self-describing so codegen emits a typed
|
|
233
|
+
* client error class with zero ceremony from the consumer.
|
|
243
234
|
*/
|
|
244
235
|
export function defaultErrorSchema(
|
|
245
236
|
key: string,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-agnostic HTTP server layer.
|
|
3
|
+
*
|
|
4
|
+
* Everything a server adapter (Hono, Fastify, Express, ...) needs to serve the
|
|
5
|
+
* four procedure kinds without each adapter re-implementing the framework:
|
|
6
|
+
* route docs, error taxonomy + dispatch, request channel extraction, SSE
|
|
7
|
+
* metadata, path resolution, and the doc registry. Adapters are thin glue:
|
|
8
|
+
* read the request via their framework, call into this layer, write the
|
|
9
|
+
* response via their framework.
|
|
10
|
+
*
|
|
11
|
+
* No framework imports are allowed in this subtree (enforced by test).
|
|
12
|
+
*/
|
|
13
|
+
export * from './types.js'
|
|
14
|
+
export * from './paths.js'
|
|
15
|
+
export * from './context.js'
|
|
16
|
+
export * from './sse.js'
|
|
17
|
+
export { BODY_METHODS, defaultSuccessStatus, extractReqChannels } from './request/params.js'
|
|
18
|
+
export type { RequestSource } from './request/params.js'
|
|
19
|
+
export { parseQueryNative, extractQuery } from './request/query.js'
|
|
20
|
+
export type { QueryParser } from './request/query.js'
|
|
21
|
+
export * from './errors/taxonomy.js'
|
|
22
|
+
export * from './errors/dispatch.js'
|
|
23
|
+
export { buildRpcRouteDoc } from './docs/rpc-doc.js'
|
|
24
|
+
export { buildStreamRouteDoc } from './docs/stream-doc.js'
|
|
25
|
+
export { buildHttpRouteDoc } from './docs/http-doc.js'
|
|
26
|
+
export { buildHttpStreamRouteDoc } from './docs/http-stream-doc.js'
|
|
27
|
+
export { DocRegistry } from './doc-registry.js'
|
|
28
|
+
export { writeDocEnvelope } from './doc-envelope.js'
|
|
29
|
+
export type { DocEnvelopeSource } from './doc-envelope.js'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFile, readdir } from 'node:fs/promises'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { describe, expect, it } from 'vitest'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Enforces the transport-agnostic guarantee: nothing under src/server may
|
|
8
|
+
* import a web framework. Adapters (src/adapters/*) are the only place
|
|
9
|
+
* framework imports belong.
|
|
10
|
+
*/
|
|
11
|
+
const FRAMEWORKS = ['hono', 'astro', 'express', 'fastify']
|
|
12
|
+
const IMPORT_RE = new RegExp(
|
|
13
|
+
`(?:from\\s+|import\\s*\\()\\s*['"](?:${FRAMEWORKS.join('|')})(?:/|['"])`,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
async function collectSourceFiles(dir: string): Promise<string[]> {
|
|
17
|
+
const entries = await readdir(dir, { withFileTypes: true })
|
|
18
|
+
const files: string[] = []
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const full = join(dir, entry.name)
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
files.push(...(await collectSourceFiles(full)))
|
|
23
|
+
} else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.test.ts')) {
|
|
24
|
+
files.push(full)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return files
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('server layer is framework-free', () => {
|
|
31
|
+
it('no file under src/server imports hono/astro/express/fastify', async () => {
|
|
32
|
+
const serverDir = dirname(fileURLToPath(import.meta.url))
|
|
33
|
+
const files = await collectSourceFiles(serverDir)
|
|
34
|
+
expect(files.length).toBeGreaterThan(10)
|
|
35
|
+
|
|
36
|
+
const offenders: string[] = []
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
const content = await readFile(file, 'utf-8')
|
|
39
|
+
if (IMPORT_RE.test(content)) offenders.push(file)
|
|
40
|
+
}
|
|
41
|
+
expect(offenders, 'framework imports found in server layer').toEqual([])
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import type { AnyProcedureRegistration, ProcedureKind } from '../core/types.js'
|
|
3
|
+
import { makeRoutePath, resolveFullPath } from './paths.js'
|
|
4
|
+
|
|
5
|
+
function rpcLike(
|
|
6
|
+
kind: 'rpc' | 'rpc-stream',
|
|
7
|
+
name: string,
|
|
8
|
+
scope: string | string[],
|
|
9
|
+
version: number,
|
|
10
|
+
): AnyProcedureRegistration<any, any> {
|
|
11
|
+
return {
|
|
12
|
+
name,
|
|
13
|
+
kind,
|
|
14
|
+
config: { scope, version },
|
|
15
|
+
handler: async () => {},
|
|
16
|
+
} as unknown as AnyProcedureRegistration<any, any>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function httpLike(
|
|
20
|
+
kind: 'http' | 'http-stream',
|
|
21
|
+
name: string,
|
|
22
|
+
path: string,
|
|
23
|
+
): AnyProcedureRegistration<any, any> {
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
kind,
|
|
27
|
+
config: { path, method: 'get' },
|
|
28
|
+
handler: async () => {},
|
|
29
|
+
} as unknown as AnyProcedureRegistration<any, any>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('makeRoutePath', () => {
|
|
33
|
+
describe('rpc / rpc-stream kinds', () => {
|
|
34
|
+
test('builds /{kebab(scope)}/{kebab(name)}/{version}', () => {
|
|
35
|
+
const path = makeRoutePath({
|
|
36
|
+
procedure: rpcLike('rpc', 'GetUser', 'userAccounts', 1),
|
|
37
|
+
prefix: undefined,
|
|
38
|
+
})
|
|
39
|
+
expect(path).toBe('/user-accounts/get-user/1')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test.each(['rpc', 'rpc-stream'] as ProcedureKind[])(
|
|
43
|
+
'%s kind uses the rpc path shape',
|
|
44
|
+
(kind) => {
|
|
45
|
+
const path = makeRoutePath({
|
|
46
|
+
procedure: rpcLike(kind as 'rpc' | 'rpc-stream', 'Echo', 'echo', 2),
|
|
47
|
+
prefix: undefined,
|
|
48
|
+
})
|
|
49
|
+
expect(path).toBe('/echo/echo/2')
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
test('prefix without a leading slash is normalized', () => {
|
|
54
|
+
const path = makeRoutePath({
|
|
55
|
+
procedure: rpcLike('rpc', 'GetUser', 'users', 1),
|
|
56
|
+
prefix: 'api',
|
|
57
|
+
})
|
|
58
|
+
expect(path).toBe('/api/users/get-user/1')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('prefix with a leading slash is kept as-is', () => {
|
|
62
|
+
const path = makeRoutePath({
|
|
63
|
+
procedure: rpcLike('rpc', 'GetUser', 'users', 1),
|
|
64
|
+
prefix: '/api',
|
|
65
|
+
})
|
|
66
|
+
expect(path).toBe('/api/users/get-user/1')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('empty prefix adds nothing', () => {
|
|
70
|
+
const path = makeRoutePath({
|
|
71
|
+
procedure: rpcLike('rpc', 'GetUser', 'users', 1),
|
|
72
|
+
prefix: '',
|
|
73
|
+
})
|
|
74
|
+
expect(path).toBe('/users/get-user/1')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('array scope joins segments with "/" (each kebab-cased)', () => {
|
|
78
|
+
const path = makeRoutePath({
|
|
79
|
+
procedure: rpcLike('rpc-stream', 'WatchOrders', ['admin', 'orderEvents'], 3),
|
|
80
|
+
prefix: '/api',
|
|
81
|
+
})
|
|
82
|
+
expect(path).toBe('/api/admin/order-events/watch-orders/3')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('http / http-stream kinds', () => {
|
|
87
|
+
test('http kind resolves prefix + config.path', () => {
|
|
88
|
+
const path = makeRoutePath({
|
|
89
|
+
procedure: httpLike('http', 'GetUser', '/users/:id'),
|
|
90
|
+
prefix: '/api',
|
|
91
|
+
})
|
|
92
|
+
expect(path).toBe('/api/users/:id')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('http-stream kind resolves prefix + config.path', () => {
|
|
96
|
+
const path = makeRoutePath({
|
|
97
|
+
procedure: httpLike('http-stream', 'Tail', '/streams/logs'),
|
|
98
|
+
prefix: 'api',
|
|
99
|
+
})
|
|
100
|
+
expect(path).toBe('/api/streams/logs')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('path is used as-given when there is no prefix', () => {
|
|
104
|
+
const path = makeRoutePath({
|
|
105
|
+
procedure: httpLike('http', 'GetUser', '/users/:id'),
|
|
106
|
+
prefix: undefined,
|
|
107
|
+
})
|
|
108
|
+
expect(path).toBe('/users/:id')
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('resolveFullPath', () => {
|
|
114
|
+
test('path without a leading slash gets one', () => {
|
|
115
|
+
expect(resolveFullPath('users')).toBe('/users')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('path with a leading slash is unchanged', () => {
|
|
119
|
+
expect(resolveFullPath('/users')).toBe('/users')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('prefix without a leading slash is normalized', () => {
|
|
123
|
+
expect(resolveFullPath('/users', 'api')).toBe('/api/users')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('prefix with a leading slash is kept', () => {
|
|
127
|
+
expect(resolveFullPath('/users', '/api')).toBe('/api/users')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('both unslashed → single slashes everywhere', () => {
|
|
131
|
+
expect(resolveFullPath('users', 'api')).toBe('/api/users')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('empty prefix adds nothing', () => {
|
|
135
|
+
expect(resolveFullPath('/users', '')).toBe('/users')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('undefined prefix adds nothing', () => {
|
|
139
|
+
expect(resolveFullPath('users', undefined)).toBe('/users')
|
|
140
|
+
})
|
|
141
|
+
})
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import { kebabCase } from 'es-toolkit/string'
|
|
2
2
|
import { castArray } from 'es-toolkit/compat'
|
|
3
|
-
import type {
|
|
4
|
-
TProcedureRegistration,
|
|
5
|
-
TStreamProcedureRegistration,
|
|
6
|
-
THttpProcedureRegistration,
|
|
7
|
-
THttpStreamProcedureRegistration,
|
|
8
|
-
} from '../../../types.js'
|
|
9
|
-
|
|
10
|
-
export type AnyProcedureRegistration =
|
|
11
|
-
| TProcedureRegistration
|
|
12
|
-
| TStreamProcedureRegistration
|
|
13
|
-
| THttpProcedureRegistration<any>
|
|
14
|
-
| THttpStreamProcedureRegistration<any>
|
|
3
|
+
import type { AnyProcedureRegistration } from '../core/types.js'
|
|
15
4
|
|
|
16
5
|
function normalizePrefix(prefix?: string): string {
|
|
17
6
|
if (!prefix) return ''
|
|
@@ -28,7 +17,7 @@ export function makeRoutePath({
|
|
|
28
17
|
procedure,
|
|
29
18
|
prefix,
|
|
30
19
|
}: {
|
|
31
|
-
procedure: AnyProcedureRegistration
|
|
20
|
+
procedure: AnyProcedureRegistration<any, any>
|
|
32
21
|
prefix?: string
|
|
33
22
|
}): string {
|
|
34
23
|
const normalizedPrefix = normalizePrefix(prefix)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import type { HttpMethod } from '../types.js'
|
|
3
|
+
import type { RequestSource } from './params.js'
|
|
4
|
+
import { BODY_METHODS, defaultSuccessStatus, extractReqChannels } from './params.js'
|
|
5
|
+
import { parseQueryNative } from './query.js'
|
|
6
|
+
|
|
7
|
+
function makeSource(overrides?: Partial<RequestSource>): RequestSource {
|
|
8
|
+
return {
|
|
9
|
+
pathParams: () => ({ id: '42' }),
|
|
10
|
+
url: () => 'http://localhost/users/42?limit=10&tag=a&tag=b',
|
|
11
|
+
json: async () => ({ name: 'Ada' }),
|
|
12
|
+
headers: () => ({ 'x-api-key': 'secret' }),
|
|
13
|
+
...overrides,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('extractReqChannels', () => {
|
|
18
|
+
test('extracts only the declared channels', async () => {
|
|
19
|
+
const params = await extractReqChannels(
|
|
20
|
+
makeSource(),
|
|
21
|
+
'get',
|
|
22
|
+
{ query: {} },
|
|
23
|
+
parseQueryNative,
|
|
24
|
+
)
|
|
25
|
+
expect(Object.keys(params)).toEqual(['query'])
|
|
26
|
+
expect(params.query).toEqual({ limit: '10', tag: ['a', 'b'] })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('extracts pathParams channel', async () => {
|
|
30
|
+
const params = await extractReqChannels(
|
|
31
|
+
makeSource(),
|
|
32
|
+
'get',
|
|
33
|
+
{ pathParams: {} },
|
|
34
|
+
parseQueryNative,
|
|
35
|
+
)
|
|
36
|
+
expect(params).toEqual({ pathParams: { id: '42' } })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('extracts headers channel (lowercased keys come from the source)', async () => {
|
|
40
|
+
const params = await extractReqChannels(
|
|
41
|
+
makeSource(),
|
|
42
|
+
'get',
|
|
43
|
+
{ headers: {} },
|
|
44
|
+
parseQueryNative,
|
|
45
|
+
)
|
|
46
|
+
expect(params).toEqual({ headers: { 'x-api-key': 'secret' } })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('extracts all four channels for a body-carrying method', async () => {
|
|
50
|
+
const params = await extractReqChannels(
|
|
51
|
+
makeSource(),
|
|
52
|
+
'post',
|
|
53
|
+
{ pathParams: {}, query: {}, body: {}, headers: {} },
|
|
54
|
+
parseQueryNative,
|
|
55
|
+
)
|
|
56
|
+
expect(params).toEqual({
|
|
57
|
+
pathParams: { id: '42' },
|
|
58
|
+
query: { limit: '10', tag: ['a', 'b'] },
|
|
59
|
+
body: { name: 'Ada' },
|
|
60
|
+
headers: { 'x-api-key': 'secret' },
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test.each(['post', 'put', 'patch'] as HttpMethod[])(
|
|
65
|
+
'parses the body for %s',
|
|
66
|
+
async (method) => {
|
|
67
|
+
const params = await extractReqChannels(makeSource(), method, { body: {} }, parseQueryNative)
|
|
68
|
+
expect(params.body).toEqual({ name: 'Ada' })
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
test.each(['get', 'head', 'delete'] as HttpMethod[])(
|
|
73
|
+
'skips the body for %s (never calls source.json)',
|
|
74
|
+
async (method) => {
|
|
75
|
+
let jsonCalled = false
|
|
76
|
+
const source = makeSource({
|
|
77
|
+
json: async () => {
|
|
78
|
+
jsonCalled = true
|
|
79
|
+
return {}
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
const params = await extractReqChannels(source, method, { body: {} }, parseQueryNative)
|
|
83
|
+
expect(jsonCalled).toBe(false)
|
|
84
|
+
expect('body' in params).toBe(false)
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
test('JSON parse failure yields {} so validation reports the missing fields', async () => {
|
|
89
|
+
const source = makeSource({
|
|
90
|
+
json: async () => {
|
|
91
|
+
throw new SyntaxError('Unexpected token')
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
const params = await extractReqChannels(source, 'post', { body: {} }, parseQueryNative)
|
|
95
|
+
expect(params.body).toEqual({})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('unknown channel is set to undefined (key present, no extraction)', async () => {
|
|
99
|
+
const params = await extractReqChannels(
|
|
100
|
+
makeSource(),
|
|
101
|
+
'get',
|
|
102
|
+
{ cookies: {} },
|
|
103
|
+
parseQueryNative,
|
|
104
|
+
)
|
|
105
|
+
expect('cookies' in params).toBe(true)
|
|
106
|
+
expect(params.cookies).toBeUndefined()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('empty schema extracts nothing', async () => {
|
|
110
|
+
const params = await extractReqChannels(makeSource(), 'post', {}, parseQueryNative)
|
|
111
|
+
expect(params).toEqual({})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('custom query parser is used for the query channel', async () => {
|
|
115
|
+
const params = await extractReqChannels(
|
|
116
|
+
makeSource(),
|
|
117
|
+
'get',
|
|
118
|
+
{ query: {} },
|
|
119
|
+
(raw) => ({ raw }),
|
|
120
|
+
)
|
|
121
|
+
expect(params.query).toEqual({ raw: 'limit=10&tag=a&tag=b' })
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('BODY_METHODS', () => {
|
|
126
|
+
test('contains exactly post, put, patch', () => {
|
|
127
|
+
expect(BODY_METHODS).toEqual(['post', 'put', 'patch'])
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe('defaultSuccessStatus', () => {
|
|
132
|
+
test('post → 201', () => {
|
|
133
|
+
expect(defaultSuccessStatus('post')).toBe(201)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('delete → 204', () => {
|
|
137
|
+
expect(defaultSuccessStatus('delete')).toBe(204)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test.each(['get', 'put', 'patch', 'head'] as HttpMethod[])('%s → 200', (method) => {
|
|
141
|
+
expect(defaultSuccessStatus(method)).toBe(200)
|
|
142
|
+
})
|
|
143
|
+
})
|