ts-procedures 8.5.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +35 -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-index.js +13 -0
- package/build/codegen/emit-index.js.map +1 -1
- package/build/codegen/emit-index.test.js +25 -0
- package/build/codegen/emit-index.test.js.map +1 -1
- package/build/codegen/emit-scope.d.ts +13 -30
- package/build/codegen/emit-scope.js +15 -807
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/emit-scope.test.js +86 -4
- 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} +13 -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 +182 -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 +4 -4
- package/docs/client-error-handling.md +92 -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 +83 -6
- package/docs/migration-v8-to-v9.md +192 -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 +23 -47
- 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/__fixtures__/users-envelope.json +9 -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 +102 -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-index.test.ts +34 -0
- package/src/codegen/emit-index.ts +19 -0
- package/src/codegen/emit-scope.test.ts +96 -6
- package/src/codegen/emit-scope.ts +15 -1003
- 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/__fixtures__/users-golden.kt +6 -0
- 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/__fixtures__/users-golden.swift +6 -0
- 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} +15 -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 +195 -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/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/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/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,80 +0,0 @@
|
|
|
1
|
-
# Decision: keep ts-procedures as a single package (monorepo split declined)
|
|
2
|
-
|
|
3
|
-
- **Status:** Accepted — 2026-06-02
|
|
4
|
-
- **Supersedes:** the draft [`docs/npm-workspaces-migration-plan.md`](../npm-workspaces-migration-plan.md) (retained for the revisit scenario below)
|
|
5
|
-
|
|
6
|
-
## Context
|
|
7
|
-
|
|
8
|
-
A plan was drafted to split `ts-procedures` into a 7-package npm-workspaces
|
|
9
|
-
monorepo under the `@ts-procedures/*` scope (core, client, http, codegen, hono,
|
|
10
|
-
astro, setup), published fresh at `v1.0.0`. The stated motivation was **consumer
|
|
11
|
-
install surface**: "a codegen-CLI user shouldn't pull the Hono server runtime; an
|
|
12
|
-
Astro user shouldn't pull `ajsc`."
|
|
13
|
-
|
|
14
|
-
The overriding project goal is **a codebase that is VERY easy to maintain with
|
|
15
|
-
great DX — no magic, no incidental complexity.** Before committing to the split,
|
|
16
|
-
we verified the motivation line-by-line against the source.
|
|
17
|
-
|
|
18
|
-
## Decision
|
|
19
|
-
|
|
20
|
-
**Do not split.** Keep `ts-procedures` as a single package and capture the real,
|
|
21
|
-
residual benefit cheaply. The split's cost (7 packages, Changesets, an import
|
|
22
|
-
codemod, an OIDC release pipeline, an org claim, a hard import cutover, and
|
|
23
|
-
cross-package devDependency test edges) is a large, permanent increase in
|
|
24
|
-
*maintainer* surface that directly conflicts with the project goal — and it
|
|
25
|
-
solves a problem that is mostly already solved.
|
|
26
|
-
|
|
27
|
-
## Rationale (verified evidence)
|
|
28
|
-
|
|
29
|
-
| Claim | Evidence |
|
|
30
|
-
|---|---|
|
|
31
|
-
| The install-surface problem is ~80% already solved | `hono`, `astro`, `ajsc` are `optionalDependencies` (`package.json`) → never installed on a normal `npm install ts-procedures`. |
|
|
32
|
-
| `ajsc` is never loaded unless used | Consumed via dynamic `await import('ajsc')` (`src/codegen/emit-types.ts`). |
|
|
33
|
-
| Codegen does not pull the server runtime | Codegen's only edge into core is `import type` (erased at compile); zero value imports of `hono`/`astro`. |
|
|
34
|
-
| The pain is not real (yet) | No doc, commit, or `CLAUDE.md` line records install surface / unwanted deps as a felt problem. |
|
|
35
|
-
| The split would not even shrink the base floor | `core` must keep `suretype`+`typebox` (then) as regular deps, so every consumer pulls them regardless of the split. |
|
|
36
|
-
| The one concrete, cheaply-fixable gap | No `sideEffects: false` field → bundlers couldn't tree-shake unused subpaths. Fixed (see below). |
|
|
37
|
-
|
|
38
|
-
A split is uniquely justified **only** by benefits the single package cannot
|
|
39
|
-
deliver: independent per-concern **versioning/publishing cadence**, or
|
|
40
|
-
`@ts-procedures` **org branding/discoverability**. Neither was the actual driver,
|
|
41
|
-
so the split was declined.
|
|
42
|
-
|
|
43
|
-
## Consequences — what we did instead (the cheap wins)
|
|
44
|
-
|
|
45
|
-
Implemented on the single package, 2026-06-02:
|
|
46
|
-
|
|
47
|
-
1. **`"sideEffects": false`** in `package.json` — enables bundler tree-shaking of
|
|
48
|
-
any subpath a consumer doesn't import. Verified safe: no side-effect imports or
|
|
49
|
-
global mutation in production code.
|
|
50
|
-
2. **`import type` everywhere it belongs** — converted the schema layer
|
|
51
|
-
(`resolve-schema-lib.ts`, `schema/types.ts`) to type-only imports. Result:
|
|
52
|
-
`typebox` is no longer a runtime value import anywhere in shipped code (lib
|
|
53
|
-
detection is duck-typed); only `suretype`'s `extractSingleJsonSchema` remains a
|
|
54
|
-
runtime value import, in one branch.
|
|
55
|
-
3. **`verbatimModuleSyntax: true`** (`tsconfig.json`) — makes value-vs-type import
|
|
56
|
-
intent explicit and catches the whole bug class (this is the flag that would
|
|
57
|
-
have caught the draft plan's own `import { Procedures }` value-vs-type slip).
|
|
58
|
-
4. **`@typescript-eslint/consistent-type-imports`** rule (`error`) — enforces the
|
|
59
|
-
above going forward; the one-time autofix split mixed imports and surfaced 3
|
|
60
|
-
genuinely dead imports, which were removed.
|
|
61
|
-
5. **Optional-integrations table** in the README — documents the optional-peer
|
|
62
|
-
story (install `hono` for `ts-procedures/hono`, `astro` for the adapter, `ajsc`
|
|
63
|
-
for Kotlin/Swift codegen).
|
|
64
|
-
|
|
65
|
-
All changes verified: `npm run lint` (0 errors), `npm run build` (clean),
|
|
66
|
-
`npm test` (983 passing, 0 type errors).
|
|
67
|
-
|
|
68
|
-
## When to revisit
|
|
69
|
-
|
|
70
|
-
Revive the split — and the retained
|
|
71
|
-
[`npm-workspaces-migration-plan.md`](../npm-workspaces-migration-plan.md) (with
|
|
72
|
-
its corrections: codegen→core is a **normal** dependency because the public API
|
|
73
|
-
leaks `DocEnvelope`; expose the wire contract via a named `core/contract` seam;
|
|
74
|
-
move spanning tests to a private integration-tests package) — **only if** one of
|
|
75
|
-
these becomes a real, stated requirement:
|
|
76
|
-
|
|
77
|
-
- per-concern packages need to ship on **independent version cadences**, or
|
|
78
|
-
- the `@ts-procedures` **scoped-org identity** is wanted for branding/discovery.
|
|
79
|
-
|
|
80
|
-
Until then, "install surface" alone does not justify the maintenance cost.
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# Handoff: x-named-type reference should win over a same-named structural extraction
|
|
2
|
-
|
|
3
|
-
> **STATUS: OPEN REQUEST — not yet shipped in ajsc.** ts-procedures currently DETECTS this collision and throws a route-qualified error, rejecting an otherwise-valid schema. We'd retire that detect-and-error once ajsc disambiguates per the proposal below.
|
|
4
|
-
|
|
5
|
-
**To:** ajsc maintainer
|
|
6
|
-
**From:** ts-procedures codegen (consumer of ajsc as the JSON-Schema → TS/Kotlin/Swift emitter)
|
|
7
|
-
**Date:** 2026-06-06
|
|
8
|
-
**ajsc version inspected:** 7.3.0
|
|
9
|
-
**Priority:** medium — removes a DX wart in ts-procedures (a valid schema is rejected); no current miscompilation, because we detect-and-error rather than emit wrong code
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## TL;DR
|
|
14
|
-
|
|
15
|
-
`x-named-type` (shipped in 7.3.0, thank you) lets ts-procedures reference a shared model `Message` instead of inlining it. But when a route schema *also* has a property whose ajsc-derived extraction name equals that model name, ajsc **silently merges** the two: the external reference resolves to the unrelated structural sub-type. Your README already documents this caveat and the detection signal (`extractedTypeNames` ∩ `referencedNamedTypes`).
|
|
16
|
-
|
|
17
|
-
The ask: make an `x-named-type` reference **take precedence** over a would-be structural extraction of the same name — rename the *extraction*, keep the *reference* verbatim. The reference is an explicit author intent ("this is the external `Message`"); the extraction name is an ajsc-derived convenience. Intent should win.
|
|
18
|
-
|
|
19
|
-
Additive and opt-in in effect (only changes output for schemas that currently collide, which today produce silently-wrong code). Nothing changes when no collision exists.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Why this happens
|
|
24
|
-
|
|
25
|
-
ts-procedures hoists every `$id`-bearing subschema into a shared `_models.ts` and rewrites each route schema's occurrence into `{ "x-named-type": "<Name>" }` so routes *reference* the model rather than inline it (the integration contract from the prior `x-named-type` handoff, now live).
|
|
26
|
-
|
|
27
|
-
Separately, with `inlineTypes: false`, ajsc extracts inline nested objects into sub-types **named after the property** (PascalCased). So a route schema can independently produce an extracted sub-type whose name happens to equal a model's title.
|
|
28
|
-
|
|
29
|
-
When those two names coincide, ajsc's `$ref`/extraction merge collapses them. Because the merge happens **inside ajsc, before codegen sees any output**, a post-hoc rename downstream can't fix it — renaming the emitted text would rewrite the model reference too, yielding a silently-wrong type. The merge is lossy at the source.
|
|
30
|
-
|
|
31
|
-
## What ajsc 7.3.0 does today (the blocker)
|
|
32
|
-
|
|
33
|
-
A route property named `message` (an inline object) PascalCases to an extracted sub-type `Message`. A sibling property `latest` references the external model via `{ "x-named-type": "Message" }`. The reference and the extraction share the name `Message`, and ajsc merges them — `latest` resolves to the structural `{ unread }`, **not** the external `Message`.
|
|
34
|
-
|
|
35
|
-
```jsonc
|
|
36
|
-
// input route schema (inlineTypes: false)
|
|
37
|
-
{ "type": "object", "properties": {
|
|
38
|
-
"latest": { "x-named-type": "Message" }, // external ref → shared _models.ts Message
|
|
39
|
-
"message": { "type": "object", "title": "Message", // inline → ajsc extracts a sub-type "Message"
|
|
40
|
-
"properties": { "unread": { "type": "boolean" } } } } }
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
// ajsc 7.3.0 output — the extracted structural type wins; the external reference is lost
|
|
45
|
-
export type Message = { unread?: boolean }; // structural extraction (from `message`)
|
|
46
|
-
export type Root = { latest?: Message; message?: Message };
|
|
47
|
-
// ^^^^^^^ WRONG — `latest` now points at { unread } instead of the
|
|
48
|
-
// real shared Message model. The x-named-type intent was silently merged away.
|
|
49
|
-
// converter.referencedNamedTypes === ['Message'] AND converter.extractedTypeNames === ['Message', …]
|
|
50
|
-
// → both lists contain 'Message': the documented collision signal.
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
// desired output — the reference wins, the extraction is renamed
|
|
55
|
-
export type MessageRef = { unread?: boolean }; // structural extraction, disambiguated
|
|
56
|
-
export type Root = { latest?: Message; message?: MessageRef };
|
|
57
|
-
// ^^^^^^^ correct — external Message preserved (imported from _models)
|
|
58
|
-
// ^^^^^^^^^^ structural sub-type disambiguated
|
|
59
|
-
// converter.extractedTypeNames === ['MessageRef', …] // the rename is reported back
|
|
60
|
-
// converter.referencedNamedTypes === ['Message'] // reference untouched
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
The reference is what the author explicitly asked for; the extraction name is incidental. The reference should be the one preserved verbatim.
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## Requested feature
|
|
68
|
-
|
|
69
|
-
When a name referenced via `x-named-type` would collide with a name ajsc is about to assign to an *extracted* structural sub-type, **the reference wins**: keep the referenced name verbatim and disambiguate the extraction. Pick whichever of the following best fits ajsc's internals — listed in our order of preference:
|
|
70
|
-
|
|
71
|
-
### Option (a) — x-named-type references win (recommended)
|
|
72
|
-
|
|
73
|
-
When a referenced name collides with a would-be extraction, **rename the extraction** (append a suffix — `Ref`, `Inner`, or a numeric tail like ajsc already uses elsewhere) and keep the reference verbatim. Surface the *final* extraction name in `extractedTypeNames` so the caller sees the rename. The referenced name in `referencedNamedTypes` is unchanged.
|
|
74
|
-
|
|
75
|
-
This is the most "just works" outcome: the schema author gets a correct external reference and a (renamed) structural type, with zero new API surface. It mirrors ajsc's existing sub-type-naming disambiguation, just seeded by the set of referenced names.
|
|
76
|
-
|
|
77
|
-
### Option (b) — a `reservedExtractionNames` converter option
|
|
78
|
-
|
|
79
|
-
Add a converter option — `reservedExtractionNames?: Set<string>` (or `reservedNames`) — so the caller can hand ajsc the set of model names up front and guarantee extractions avoid them (renaming on conflict). This puts the caller in control and is explicit, at the cost of one new option. ts-procedures would pass the full set of `_models.ts` names. Functionally equivalent to (a) for our case; (a) is preferable because it needs no caller wiring and protects every consumer by default.
|
|
80
|
-
|
|
81
|
-
### Option (c) — explicit no-op acknowledgement
|
|
82
|
-
|
|
83
|
-
If you prefer to keep disambiguation as the caller's responsibility, a short note in the README confirming that — and that the documented `extractedTypeNames` ∩ `referencedNamedTypes` signal is the intended detection mechanism — lets us keep our detect-and-error in good conscience. Least preferred: it leaves a valid schema rejectable downstream.
|
|
84
|
-
|
|
85
|
-
**Recommendation: (a).** It removes the failure mode for every consumer with no new API and matches the principle that an explicit reference outranks a derived extraction name.
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## Current downstream interim (the DX wart)
|
|
90
|
-
|
|
91
|
-
ts-procedures DETECTS the collision and throws rather than emit wrong code. The check (`assertNoModelNameCollision` in `src/codegen/emit-scope.ts`) is the **set intersection** of the converter's two reported lists — ajsc's documented signal:
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
collision = referencedNamedTypes ∩ extractedTypeNames
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
A non-empty intersection means a referenced model name also appears as an ajsc-extracted declaration — i.e. the silent merge happened. We throw a clear, route-qualified error telling the author to rename the colliding property or change the model's `$id`/`title`. This is correct but unfortunate: the schema is *valid*, and the author is forced to rename something to satisfy a code-generator limitation. We'd retire this the moment ajsc disambiguates.
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## How ts-procedures will consume it (integration contract)
|
|
102
|
-
|
|
103
|
-
Once ajsc disambiguates (option (a) or (b)):
|
|
104
|
-
|
|
105
|
-
1. We **drop the detect-and-error** in `emit-scope.ts` — or downgrade it to a defensive `assert` (a non-empty intersection should then be impossible, so a remaining one would indicate an ajsc regression rather than a user error).
|
|
106
|
-
2. The colliding schema **just works**: `latest: Message` resolves to the shared `_models.ts` model; the structural sibling emits under its disambiguated name (e.g. `MessageRef`), referenced correctly from the route type.
|
|
107
|
-
3. We continue to read `referencedNamedTypes` for imports (unchanged) and `extractedTypeNames` for the route's local type set — which, under option (a), now carries the renamed extraction automatically.
|
|
108
|
-
|
|
109
|
-
The contract we depend on: **a referenced `x-named-type` name is never overwritten by an extraction — the extraction yields**, and the final extraction name is reported in `extractedTypeNames`.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## Tests worth adding
|
|
114
|
-
|
|
115
|
-
1. `x-named-type: "Message"` sibling to an inline object property `message` (PascalCases to `Message`) ⇒ reference resolves to external `Message`; extraction renamed (e.g. `MessageRef`); `extractedTypeNames` carries the renamed name; `referencedNamedTypes === ['Message']`; the two lists no longer intersect.
|
|
116
|
-
2. Same model referenced at N sites alongside one colliding extraction ⇒ all references stay verbatim; only the extraction is renamed.
|
|
117
|
-
3. Multiple distinct collisions in one schema ⇒ each extraction independently disambiguated; references all preserved.
|
|
118
|
-
4. No collision (referenced name ≠ any extraction name) ⇒ byte-identical to current 7.3.0 output (regression guard).
|
|
119
|
-
5. Option (b) only: `reservedExtractionNames` containing a name that *would not* otherwise collide ⇒ extraction proactively avoids it; references unaffected.
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## Versioning
|
|
124
|
-
|
|
125
|
-
Additive in effect — it only changes output for schemas that **currently miscompile** (a silent merge). For every non-colliding schema the output is byte-identical. A **minor** bump (e.g. 7.4.0). ts-procedures will gate the removal of its detect-and-error on the ajsc version that introduces the precedence rule, keeping the guard as a fallback for one release if you'd prefer a soft cutover.
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## Contact / context
|
|
130
|
-
|
|
131
|
-
- `src/codegen/emit-scope.ts` — `assertNoModelNameCollision` (the detect-and-error we want to retire)
|
|
132
|
-
- `src/codegen/emit-types.ts` — `referencedNamedTypes` already surfaced (`ExtractedTypeOutput.referencedNamedTypes`, `jsonSchemaToTypeBodyWithRefs`); `extractedTypeNames` not yet surfaced, will be wired when (a)/(b) lands
|
|
133
|
-
- `src/codegen/collect-models.ts`, `emit-models.ts` — `$id` model collection / `_models.ts` emission (unaffected)
|
|
134
|
-
- `docs/handoffs/ajsc-named-type-support.md` — the prior (shipped) `x-named-type` handoff this builds on
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
# Handoff: named-type / verbatim-type support in ajsc
|
|
2
|
-
|
|
3
|
-
> **STATUS: Shipped in ajsc 7.3.0 and adopted in ts-procedures (this cutover).** The placeholder-token workaround described below has been deleted; `substituteModelRefs` now emits `{ 'x-named-type': '<Name>' }` and the codegen reads the converter's `referencedNamedTypes`. The rest of this doc is retained for historical context.
|
|
4
|
-
|
|
5
|
-
**To:** ajsc maintainer
|
|
6
|
-
**From:** ts-procedures codegen (consumer of ajsc as the JSON-Schema → TS/Kotlin/Swift emitter)
|
|
7
|
-
**Date:** 2026-06-05
|
|
8
|
-
**ajsc version inspected:** 7.2.0
|
|
9
|
-
**Priority:** medium — unblocks removing a workaround in ts-procedures, no current breakage
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## TL;DR
|
|
14
|
-
|
|
15
|
-
ts-procedures needs a way to tell ajsc: *"this subschema is an already-defined named type called `Message` — emit a reference to it (`Message`), don't convert/inline it, and tell me you referenced it so I can add an import."*
|
|
16
|
-
|
|
17
|
-
ajsc 7.2.0 has no such mechanism: a `$ref` to a `$defs` entry is **inlined and re-extracted under the property name** (with no dedup), and there is no verbatim-type escape hatch. We've worked around this downstream with a placeholder-token hack (described below) that we'd like to delete once ajsc supports this directly.
|
|
18
|
-
|
|
19
|
-
The ask is small and **additive** (a new opt-in schema keyword + one new field on the emit result). Nothing changes when the keyword is absent.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Why we need it
|
|
24
|
-
|
|
25
|
-
ts-procedures generates a typed API client from a server's schema. The same domain entity (e.g. `Message`) appears in many route schemas. Today ajsc inlines a fresh structural literal at every site, so `Message` is emitted ~4–6 times under different names in one file, disconnected from each other and from the author's `Message` type. We want **one** `Message` type that every route references.
|
|
26
|
-
|
|
27
|
-
We already know the identity: each such schema carries a stable `$id` (e.g. `urn:msg`) and a `title` (`Message`). So at codegen time we hoist every `$id`-bearing schema into a shared `_models.ts` (emitted via `ajsc.emitTypescript(messageSchema, { rootTypeName: 'Message' })` — which works great), and we want each *route* schema to **reference** `Message` rather than inline it.
|
|
28
|
-
|
|
29
|
-
## What ajsc 7.2.0 does today (the blocker)
|
|
30
|
-
|
|
31
|
-
Based on inspecting the installed `dist/`:
|
|
32
|
-
|
|
33
|
-
1. **`$ref` is inlined, not referenced.** The IR layer (`ir/JSONSchemaConverter`) resolves `$ref` against `$defs`/`definitions` by inlining, and rejects non-local refs (`"Only local references are supported"`). With `inlineTypes: false`, the inlined object is then re-extracted **named after the property** — so a schema with `author`, `lastReply`, and `all` all `$ref`-ing the same `Message` emits three duplicate types `Author`, `LastReply`, `All`, with **no deduplication** and no way to make them one `Message`.
|
|
34
|
-
|
|
35
|
-
```jsonc
|
|
36
|
-
// input
|
|
37
|
-
{ "type": "object", "properties": {
|
|
38
|
-
"author": { "$ref": "#/$defs/Message" },
|
|
39
|
-
"lastReply": { "$ref": "#/$defs/Message" } },
|
|
40
|
-
"$defs": { "Message": { "type": "object", "title": "Message",
|
|
41
|
-
"properties": { "id": { "type": "string" } } } } }
|
|
42
|
-
```
|
|
43
|
-
```ts
|
|
44
|
-
// ajsc 7.2.0 output (inlineTypes:false) — duplicated, property-named, no dedup
|
|
45
|
-
export type Author = { id?: string };
|
|
46
|
-
export type LastReply = { id?: string };
|
|
47
|
-
export type Root = { author?: Author; lastReply?: LastReply };
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
2. **No verbatim-type escape hatch.** Grepping `dist/` for `tsType | x-ts | verbatim | existingType | customType | rawType` returns nothing. We tried `{ tsType: 'Message' }`, `{ "x-tsType": 'Message' }`, `{ $ref: 'Message' }` (bare), `{ type:'object', tsType:'Message' }` — all are ignored (emit `any` / `{ [key:string]: unknown }`) or throw.
|
|
51
|
-
|
|
52
|
-
3. **`EmitResult.imports` is empty for TypeScript.** The function-form `emitTypescript(schema, opts)` returns `{ code, rootTypeName, extractedTypeNames, imports }`, but `imports` is documented (README ~line 110) as Kotlin/Swift-only — there's no channel to surface "this TS output references external type `Message`."
|
|
53
|
-
|
|
54
|
-
### Our current workaround (what we want to delete)
|
|
55
|
-
|
|
56
|
-
Because ajsc can't reference a named type, ts-procedures smuggles a sentinel through ajsc and scrapes it back out:
|
|
57
|
-
|
|
58
|
-
1. Before calling ajsc, replace each `$id`-bearing subschema with `{ const: '__MODELREF__Message__' }`. ajsc emits that verbatim as a string-literal type (`author?: "__MODELREF__Message__"`), and — usefully — never extracts it as a sub-type, and survives inside `Array<…>`.
|
|
59
|
-
2. After ajsc, a global regex `/["']__MODELREF__([A-Za-z_$][\w$]*)__["']/g` rewrites the tokens to bare names (`author?: Message`) and collects the referenced names so we can emit `import type { Message } from './_models'`.
|
|
60
|
-
|
|
61
|
-
It works and is well-tested, but it encodes a *type identity* as schema *data*, runs it through a code generator, and recovers it from generated *text* with a regex. We'd much rather express the intent directly to ajsc.
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Requested feature
|
|
66
|
-
|
|
67
|
-
A new **opt-in schema keyword** that marks a subschema as an already-defined named type. When present, ajsc:
|
|
68
|
-
|
|
69
|
-
1. emits a **reference** to that name (does not convert/inline the subschema, does not extract a sub-type), and
|
|
70
|
-
2. **reports** the referenced name on the emit result so the caller can wire an import.
|
|
71
|
-
|
|
72
|
-
### Keyword name & shape
|
|
73
|
-
|
|
74
|
-
ajsc is multi-target (TS/Kotlin/Swift), so prefer a target-agnostic keyword over a TS-specific one. Suggested:
|
|
75
|
-
|
|
76
|
-
```jsonc
|
|
77
|
-
{ "x-named-type": "Message" }
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
`x-`-prefixed so it's unambiguously an annotation and ignored by standard JSON-Schema validators. (If you'd rather not use `x-`, `namedType` or `typeRef` are fine — your call on convention.)
|
|
81
|
-
|
|
82
|
-
- **Value = string:** the identifier emitted verbatim in **every** target (`Message` in TS, Kotlin, and Swift). This matches our usage — we use the same PascalCase name across targets.
|
|
83
|
-
- **Optional future extension:** allow an object for per-language names, e.g. `{ "x-named-type": { "ts": "Message", "kotlin": "Message", "swift": "Message" } }`. Not needed by us now; mentioning so the string form can widen later without a breaking change.
|
|
84
|
-
|
|
85
|
-
### Semantics
|
|
86
|
-
|
|
87
|
-
- The keyword **short-circuits conversion** of that subschema node: emit the bare identifier as the type, in all the positions ajsc already handles a leaf type (direct property, `Array<…>` items, union member, map value, etc.).
|
|
88
|
-
- With `inlineTypes: false`, the named type is **not** added to `extractedTypeNames` (it's external — defined elsewhere).
|
|
89
|
-
- The node is **not** recursed into (its `properties`/`items` are irrelevant once it's a named reference). A `type`/`properties` sitting alongside `x-named-type` should be ignored (or, if you prefer strictness, validated to match — but ignore is simpler and matches our need; we strip the body anyway).
|
|
90
|
-
- **Absent keyword ⇒ zero behaviour change.** Fully backward compatible.
|
|
91
|
-
|
|
92
|
-
### Reporting referenced names
|
|
93
|
-
|
|
94
|
-
Add referenced external names to the emit result so the caller can build imports. Two acceptable shapes:
|
|
95
|
-
|
|
96
|
-
- **Preferred:** a new field `referencedNamedTypes: string[]` on `EmitResult` (deduped, sorted or insertion-order — either is fine; we sort downstream).
|
|
97
|
-
- **Or:** populate the existing (currently-empty-for-TS) `imports` field with the bare names.
|
|
98
|
-
|
|
99
|
-
A new dedicated field is cleaner than overloading `imports` (which means module paths for Kotlin/Swift).
|
|
100
|
-
|
|
101
|
-
### Worked example
|
|
102
|
-
|
|
103
|
-
```jsonc
|
|
104
|
-
// input
|
|
105
|
-
{ "type": "object", "properties": {
|
|
106
|
-
"author": { "x-named-type": "Message" },
|
|
107
|
-
"replies": { "type": "array", "items": { "x-named-type": "Message" } },
|
|
108
|
-
"id": { "type": "string" } } }
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
// desired TS output (inlineTypes:false)
|
|
113
|
-
export type Root = { author?: Message; replies?: Array<Message>; id?: string };
|
|
114
|
-
// emitResult.referencedNamedTypes === ['Message'] // Root is NOT polluted with a Message decl
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
```kotlin
|
|
118
|
-
// desired Kotlin output
|
|
119
|
-
data class Root(val author: Message? = null, val replies: List<Message>? = null, val id: String? = null)
|
|
120
|
-
// referencedNamedTypes === ['Message']
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
```swift
|
|
124
|
-
// desired Swift output
|
|
125
|
-
struct Root: Codable { let author: Message?; let replies: [Message]?; let id: String? }
|
|
126
|
-
// referencedNamedTypes === ['Message']
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
In every case ajsc does **not** define `Message` — the caller (ts-procedures) defines it once elsewhere and adds the import/reference.
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## Where this likely lives in ajsc
|
|
134
|
-
|
|
135
|
-
From the `dist/` surface (you know the real source layout):
|
|
136
|
-
|
|
137
|
-
- **IR conversion** (`ir/JSONSchemaConverter` or equivalent): detect `x-named-type` on a node early and lower it to a new IR node kind — e.g. `NamedTypeRef(name)` — instead of converting the object. This is the one place that needs to intercept before the existing `$ref`-inline / object-extract logic runs.
|
|
138
|
-
- **Each language emitter** (`typescript/`, `kotlin/`, `swift/`): render `NamedTypeRef(name)` as the bare identifier wherever a leaf type is rendered, and **collect** the name into the result.
|
|
139
|
-
- **`EmitResult` type** (`index.d.ts`): add `referencedNamedTypes?: string[]` (or document `imports` carrying them for TS).
|
|
140
|
-
- **`BaseConverterOpts`:** no new option needed — the keyword lives in the schema, not the options. (You *could* add a `validateNamedTypes` strictness flag later; not required.)
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Tests worth adding (mirror our spike findings)
|
|
145
|
-
|
|
146
|
-
1. `x-named-type` on a direct property ⇒ `prop?: Message`, `referencedNamedTypes` includes `Message`, no `Message` extracted.
|
|
147
|
-
2. Inside `array.items` ⇒ `Array<Message>` (TS) / `List<Message>` (Kotlin) / `[Message]` (Swift).
|
|
148
|
-
3. Same name referenced by N properties ⇒ deduped to a single `Message` reference, `referencedNamedTypes === ['Message']` (the dedup we can't get today).
|
|
149
|
-
4. As a whole root schema (`{ "x-named-type": "Message" }` at top level) ⇒ output is just `Message` (our route-response-is-exactly-a-model case).
|
|
150
|
-
5. Both `inlineTypes: true` and `inlineTypes: false` behave identically for the referenced name (it's a leaf either way).
|
|
151
|
-
6. Absent keyword ⇒ byte-identical to current output (regression guard).
|
|
152
|
-
7. Coexists with genuine sibling extraction: a schema with one `x-named-type` property and one ordinary nested object still extracts the ordinary object normally under `inlineTypes:false`.
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## How ts-procedures will consume it (integration contract)
|
|
157
|
-
|
|
158
|
-
So you can sanity-check the design against the real caller:
|
|
159
|
-
|
|
160
|
-
1. We already collect every `$id`-bearing subschema into a registry `{ $id, name, schema }` and emit each one standalone via `emitTypescript(schema, { rootTypeName: name })` into `_models.ts`. **No change needed there.**
|
|
161
|
-
2. For each *route* schema, instead of our placeholder-token substitution, we'll walk the schema and replace each `$id`-bearing subschema with `{ "x-named-type": <name> }` before calling ajsc.
|
|
162
|
-
3. We read `emitResult.referencedNamedTypes` to emit `import type { Message, … } from './_models'` per scope file.
|
|
163
|
-
4. We delete `model-refs.ts` (the placeholder/regex module) entirely and the two wrapper functions in `emit-scope.ts`.
|
|
164
|
-
|
|
165
|
-
The contract we depend on: **(a)** a referenced `x-named-type` is never inlined and never extracted, and **(b)** its name is reported back. Those two guarantees are the whole feature.
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## Versioning
|
|
170
|
-
|
|
171
|
-
Additive and opt-in ⇒ a **minor** bump (e.g. 7.3.0). No migration for existing consumers. ts-procedures will gate on the ajsc version that introduces `referencedNamedTypes` and keep the placeholder-token path as a fallback for one release if you'd like a soft cutover, or hard-switch if you prefer.
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## Contact / context
|
|
176
|
-
|
|
177
|
-
The placeholder-token workaround, the design rationale, and the empirical ajsc probes live in the ts-procedures repo:
|
|
178
|
-
- `src/codegen/model-refs.ts` — the current substitution we want to remove
|
|
179
|
-
- `src/codegen/emit-models.ts`, `collect-models.ts` — the model collection/emission (unaffected)
|
|
180
|
-
- `docs/superpowers/specs/2026-06-05-dx-feedback-round-design.md` — design (see finding #3)
|
|
181
|
-
- commit `483185e` — the spike that established ajsc's inlining behaviour and chose the workaround
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
# Response: shared-models auto-resolve (re: `--shared-types-from` request)
|
|
2
|
-
|
|
3
|
-
> **STATUS: SHIPPED (v8.5.0).** Three targeted improvements shipped in place of source-file scanning. See below for what landed and why the requested mechanism was declined.
|
|
4
|
-
|
|
5
|
-
**To:** downstream developer
|
|
6
|
-
**From:** ts-procedures codegen maintainers
|
|
7
|
-
**Date:** 2026-06-06
|
|
8
|
-
**Reference:** feedback on `sharedTypesImport` DX — pain points 1–3
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## TL;DR
|
|
13
|
-
|
|
14
|
-
The goal — "define it once, no second table, no silent drift" — was right. We shipped three targeted fixes that deliver those ergonomics from the DocEnvelope side rather than by scanning TypeScript source files, which would introduce a worse class of silent failure. The per-`$id` `sharedTypesImport` map is unchanged and remains available as a precision escape hatch.
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## What shipped
|
|
19
|
-
|
|
20
|
-
### 1. `sharedModelsModule` — one line replaces the entire map
|
|
21
|
-
|
|
22
|
-
A single module path tells codegen: *"re-export every `$id` model from this module under its derived name."*
|
|
23
|
-
|
|
24
|
-
```jsonc
|
|
25
|
-
// ts-procedures-codegen.config.json
|
|
26
|
-
{ "sharedModelsModule": "@app/schemas" }
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
# CLI
|
|
31
|
-
npx ts-procedures-codegen --url http://localhost:3000/doc --out ./generated --shared-models-module @app/schemas
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
// generateClient() API
|
|
36
|
-
await generateClient({ url, outDir, sharedModelsModule: '@app/schemas' })
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Convention: the derived name is the model's `$id`/`title` (PascalCase), which is the same identifier your schema file already exports. If the names align — they usually do — the entire `sharedTypesImport` map collapses to this single value.
|
|
40
|
-
|
|
41
|
-
Precedence: explicit `sharedTypesImport[$id]` entry → `sharedModelsModule` convention → generate locally. The map remains available to override individual entries (rename, different package, multi-consumer split).
|
|
42
|
-
|
|
43
|
-
### 2. Summary log (CLI)
|
|
44
|
-
|
|
45
|
-
Every CLI run that finds `$id`-bearing models now prints:
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
[ts-procedures-codegen] Shared models: 15 total — 15 re-exported, 0 generated locally.
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
This makes silent generated-twin drift visible without requiring any additional flags or contract tests. (Calling `generateClient` programmatically is silent by default — pass `logger: console.log` to opt in — so the summary never leaks into your own build-script output.)
|
|
52
|
-
|
|
53
|
-
### 3. `--strict-shared-models` — hard-fail on local twins
|
|
54
|
-
|
|
55
|
-
Fails the run and lists every `$id` that would be generated as a local structural twin instead of re-exported:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
npx ts-procedures-codegen ... --strict-shared-models
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
```jsonc
|
|
62
|
-
{ "strictSharedModels": true }
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
await generateClient({ url, outDir, sharedModelsModule: '@app/schemas', strictSharedModels: true })
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
This is a **CI flag, not a development default** — run it in your pipeline and codegen becomes the drift gate. It replaces a hand-written `toEqualTypeOf` contract test.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## How this maps to your three pain points
|
|
74
|
-
|
|
75
|
-
### Pain 1: Two places to keep in lockstep (`$id` and the map entry)
|
|
76
|
-
|
|
77
|
-
`sharedModelsModule` derives the import from the model's own `$id`/`title` — you define the convention once and it applies to every model. There is no second table to maintain.
|
|
78
|
-
|
|
79
|
-
### Pain 2: Silent fallback — a generated structural twin that typechecks but isn't the real type
|
|
80
|
-
|
|
81
|
-
Two layers now make this visible:
|
|
82
|
-
|
|
83
|
-
- The CLI summary log surfaces the "generated locally" count on every codegen run.
|
|
84
|
-
- `--strict-shared-models` promotes that to a hard failure, listing the specific `$id`s. Add it to your CI pipeline and the generated-twin state becomes impossible to ship.
|
|
85
|
-
|
|
86
|
-
This replaces your hand-written `toEqualTypeOf` contract test with a single flag — the codegen itself owns the invariant.
|
|
87
|
-
|
|
88
|
-
### Pain 3: The map is redundant — `module` is "where the schema lives" and `name` is usually the `$id`
|
|
89
|
-
|
|
90
|
-
`sharedModelsModule` makes that redundancy disappear. `name` is derived from the model's `$id`/`title`; `module` is supplied once for all models. The 15-entry map becomes one line.
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## Why `--shared-types-from <dir|glob>` was declined
|
|
95
|
-
|
|
96
|
-
The goal was right. The mechanism is what we changed.
|
|
97
|
-
|
|
98
|
-
ts-procedures codegen has one explicit input contract: a **DocEnvelope** obtained via `--url`, `--file`, or a passed object. The envelope is a self-contained JSON document — it contains schemas, routes, and error shapes. Codegen never reads the consumer's TypeScript source tree. That decoupling is what makes codegen work against a live remote server, a serialized offline file, or a published package with no local source.
|
|
99
|
-
|
|
100
|
-
Source-file scanning would break that contract in four concrete ways:
|
|
101
|
-
|
|
102
|
-
**(a) Silent desync in `--url` / offline `--file` cases.** The live server's envelope and the local source tree can disagree at any point in time — a schema field renamed in the server but not yet deployed, an `$id` changed in source but not rebuilt. Source scanning would produce a "shared" reference that names a type the server actually never returns. The result typechecks and the single-source-of-truth claim is restored, but the types are silently WRONG — a worse outcome than today's duplicate-but-correct structural twin.
|
|
103
|
-
|
|
104
|
-
**(b) Fails for published-package consumers.** If `@app/schemas` lives in `node_modules`, there is no TypeScript source tree to scan — only compiled `.d.ts` files (if any) or nothing. The scan would silently skip models that are genuinely shared from a published package and generate local twins for them anyway, with no signal.
|
|
105
|
-
|
|
106
|
-
**(c) Requires a TS parser, glob runner, and export-resolution heuristics in codegen.** This is a substantial dependency and surface area. It introduces failure modes (barrel re-exports, `export * from`, conditional exports, `.d.ts` vs. `.ts`) whose edge cases are hard to exhaustively enumerate and maintain. The DocEnvelope contract specifically exists to avoid this category of tooling dependency.
|
|
107
|
-
|
|
108
|
-
**(d) Does not solve module-path correctness.** A file path discovered by glob scanning (`src/schemas/message.ts`) is not a valid TypeScript import specifier for a consumer who imports `@app/schemas`. Path-to-module-alias resolution requires TypeScript's full compiler configuration. Without it, any path the scan produces must be corrected by hand — which brings back a second table.
|
|
109
|
-
|
|
110
|
-
The `sharedModelsModule` convention solves the same "define once, no second table" problem. The module specifier (`@app/schemas`) is typed once; names are derived from the model's own `$id`/`title`. It works in all deployment topologies: live server, offline file, published package.
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Escape hatch: `sharedTypesImport` unchanged
|
|
115
|
-
|
|
116
|
-
The per-`$id` map is unchanged and remains available for cases where `sharedModelsModule` is not enough:
|
|
117
|
-
|
|
118
|
-
- A model whose TypeScript name differs from its `$id` (rename case).
|
|
119
|
-
- Different models imported from different packages (`@app/schemas` and `@app/internal-schemas`).
|
|
120
|
-
- Multi-consumer split where a subset of models come from a shared package and others are generated locally.
|
|
121
|
-
|
|
122
|
-
```jsonc
|
|
123
|
-
{
|
|
124
|
-
"sharedModelsModule": "@app/schemas",
|
|
125
|
-
"sharedTypesImport": {
|
|
126
|
-
"urn:internal-msg": { "module": "@app/internal-schemas", "name": "InternalMessage" }
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
`sharedTypesImport` entries take precedence over `sharedModelsModule`, so you can cover the general case with the convention and override individual outliers explicitly.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Migration example
|
|
136
|
-
|
|
137
|
-
**Before (v8.3 and earlier) — 15-entry hand-maintained map + contract test:**
|
|
138
|
-
|
|
139
|
-
```jsonc
|
|
140
|
-
// ts-procedures-codegen.config.json
|
|
141
|
-
{
|
|
142
|
-
"shareModels": true,
|
|
143
|
-
"sharedTypesImport": {
|
|
144
|
-
"urn:message": { "module": "@app/schemas", "name": "Message" },
|
|
145
|
-
"urn:user": { "module": "@app/schemas", "name": "User" },
|
|
146
|
-
"urn:thread": { "module": "@app/schemas", "name": "Thread" },
|
|
147
|
-
"urn:attachment": { "module": "@app/schemas", "name": "Attachment" },
|
|
148
|
-
"urn:reaction": { "module": "@app/schemas", "name": "Reaction" },
|
|
149
|
-
"urn:channel": { "module": "@app/schemas", "name": "Channel" },
|
|
150
|
-
"urn:workspace": { "module": "@app/schemas", "name": "Workspace" },
|
|
151
|
-
"urn:notification": { "module": "@app/schemas", "name": "Notification" },
|
|
152
|
-
"urn:role": { "module": "@app/schemas", "name": "Role" },
|
|
153
|
-
"urn:permission": { "module": "@app/schemas", "name": "Permission" },
|
|
154
|
-
"urn:bot": { "module": "@app/schemas", "name": "Bot" },
|
|
155
|
-
"urn:webhook": { "module": "@app/schemas", "name": "Webhook" },
|
|
156
|
-
"urn:app": { "module": "@app/schemas", "name": "App" },
|
|
157
|
-
"urn:token": { "module": "@app/schemas", "name": "Token" },
|
|
158
|
-
"urn:audit-log": { "module": "@app/schemas", "name": "AuditLog" }
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
```ts
|
|
164
|
-
// contract test to catch silent twins
|
|
165
|
-
expect(generatedMessage).toEqualTypeOf<Message>()
|
|
166
|
-
expect(generatedUser).toEqualTypeOf<User>()
|
|
167
|
-
// ... × 15
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
**After (v8.5.0) — one config line + one CI flag:**
|
|
171
|
-
|
|
172
|
-
```jsonc
|
|
173
|
-
// ts-procedures-codegen.config.json
|
|
174
|
-
{
|
|
175
|
-
"shareModels": true,
|
|
176
|
-
"sharedModelsModule": "@app/schemas",
|
|
177
|
-
"strictSharedModels": true
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
No map. No contract tests. `--strict-shared-models` (or `"strictSharedModels": true`) in CI is the drift gate: codegen fails and names the offender if any `$id` model would silently generate as a local twin.
|