ts-procedures 7.2.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -3
- package/agent_config/claude-code/agents/ts-procedures-architect.md +6 -8
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +30 -33
- package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +139 -53
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +208 -231
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +80 -153
- package/agent_config/claude-code/skills/ts-procedures-review/SKILL.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures-review/checklist.md +4 -5
- package/agent_config/claude-code/skills/ts-procedures-scaffold/SKILL.md +4 -7
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono.md +223 -0
- package/agent_config/copilot/copilot-instructions.md +36 -48
- package/agent_config/cursor/cursorrules +36 -48
- package/build/client/call.js +4 -1
- package/build/client/call.js.map +1 -1
- package/build/client/call.test.js +23 -0
- package/build/client/call.test.js.map +1 -1
- package/build/client/fetch-adapter.js +3 -1
- package/build/client/fetch-adapter.js.map +1 -1
- package/build/client/fetch-adapter.test.js +11 -1
- package/build/client/fetch-adapter.test.js.map +1 -1
- package/build/client/index.test.js +7 -7
- package/build/client/index.test.js.map +1 -1
- package/build/client/request-builder.d.ts +1 -1
- package/build/client/request-builder.js +2 -2
- package/build/client/request-builder.js.map +1 -1
- package/build/client/stream.js +13 -2
- package/build/client/stream.js.map +1 -1
- package/build/client/stream.test.js +32 -7
- package/build/client/stream.test.js.map +1 -1
- package/build/client/typed-error-dispatch.test.js +8 -92
- package/build/client/typed-error-dispatch.test.js.map +1 -1
- package/build/client/types.d.ts +21 -3
- package/build/codegen/bin/cli.js +0 -0
- package/build/codegen/e2e.test.js +87 -23
- package/build/codegen/e2e.test.js.map +1 -1
- package/build/codegen/emit-errors.integration.test.js +1 -1
- package/build/codegen/emit-errors.integration.test.js.map +1 -1
- package/build/codegen/emit-scope.js +308 -47
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/emit-scope.test.js +363 -110
- package/build/codegen/emit-scope.test.js.map +1 -1
- package/build/codegen/pipeline.test.js +7 -7
- package/build/codegen/pipeline.test.js.map +1 -1
- package/build/codegen/resolve-envelope.js +1 -1
- package/build/codegen/resolve-envelope.js.map +1 -1
- package/build/codegen/resolve-envelope.test.js +5 -5
- package/build/codegen/resolve-envelope.test.js.map +1 -1
- package/build/codegen/targets/_shared/route-slots.d.ts +8 -3
- package/build/codegen/targets/_shared/route-slots.js +49 -8
- package/build/codegen/targets/_shared/route-slots.js.map +1 -1
- package/build/codegen/targets/_shared/route-slots.test.js +99 -26
- package/build/codegen/targets/_shared/route-slots.test.js.map +1 -1
- package/build/codegen/targets/kotlin/emit-route-kotlin.test.js +88 -17
- package/build/codegen/targets/kotlin/emit-route-kotlin.test.js.map +1 -1
- package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js +9 -6
- package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js.map +1 -1
- package/build/codegen/targets/kotlin/integration.test.js +6 -0
- package/build/codegen/targets/kotlin/integration.test.js.map +1 -1
- package/build/codegen/targets/swift/access-level.test.js +8 -11
- package/build/codegen/targets/swift/access-level.test.js.map +1 -1
- package/build/codegen/targets/swift/emit-route-swift.test.js +91 -20
- package/build/codegen/targets/swift/emit-route-swift.test.js.map +1 -1
- package/build/codegen/targets/swift/emit-scope-swift.test.js +12 -9
- package/build/codegen/targets/swift/emit-scope-swift.test.js.map +1 -1
- package/build/codegen/targets/swift/integration.test.js +6 -0
- package/build/codegen/targets/swift/integration.test.js.map +1 -1
- package/build/create-http-stream.d.ts +58 -0
- package/build/create-http-stream.js +122 -0
- package/build/create-http-stream.js.map +1 -0
- package/build/create-http-stream.test.js +88 -0
- package/build/create-http-stream.test.js.map +1 -0
- package/build/create-http.d.ts +49 -0
- package/build/create-http.js +108 -0
- package/build/create-http.js.map +1 -0
- package/build/create-http.test.js +137 -0
- package/build/create-http.test.js.map +1 -0
- package/build/create-stream.d.ts +35 -0
- package/build/create-stream.js +123 -0
- package/build/create-stream.js.map +1 -0
- package/build/create-stream.test.js +428 -0
- package/build/create-stream.test.js.map +1 -0
- package/build/create.d.ts +28 -0
- package/build/create.js +82 -0
- package/build/create.js.map +1 -0
- package/build/create.test.js +483 -0
- package/build/create.test.js.map +1 -0
- package/build/exports.d.ts +2 -0
- package/build/implementations/http/astro/index.test.js +20 -12
- package/build/implementations/http/astro/index.test.js.map +1 -1
- package/build/implementations/http/doc-registry.js +1 -1
- package/build/implementations/http/doc-registry.js.map +1 -1
- package/build/implementations/http/doc-registry.test.js +36 -5
- package/build/implementations/http/doc-registry.test.js.map +1 -1
- package/build/implementations/http/error-dispatch.d.ts +76 -0
- package/build/implementations/http/error-dispatch.js +77 -0
- package/build/implementations/http/error-dispatch.js.map +1 -0
- package/build/implementations/http/error-dispatch.test.js +254 -0
- package/build/implementations/http/error-dispatch.test.js.map +1 -0
- package/build/implementations/http/error-taxonomy.d.ts +5 -5
- package/build/implementations/http/hono/docs/http-doc.d.ts +6 -0
- package/build/implementations/http/hono/docs/http-doc.js +42 -0
- package/build/implementations/http/hono/docs/http-doc.js.map +1 -0
- package/build/implementations/http/hono/docs/http-stream-doc.d.ts +6 -0
- package/build/implementations/http/hono/docs/http-stream-doc.js +40 -0
- package/build/implementations/http/hono/docs/http-stream-doc.js.map +1 -0
- package/build/implementations/http/hono/docs/rpc-doc.d.ts +6 -0
- package/build/implementations/http/hono/docs/rpc-doc.js +24 -0
- package/build/implementations/http/hono/docs/rpc-doc.js.map +1 -0
- package/build/implementations/http/hono/docs/stream-doc.d.ts +6 -0
- package/build/implementations/http/hono/docs/stream-doc.js +42 -0
- package/build/implementations/http/hono/docs/stream-doc.js.map +1 -0
- package/build/implementations/http/hono/handlers/http-stream.d.ts +10 -0
- package/build/implementations/http/hono/handlers/http-stream.js +123 -0
- package/build/implementations/http/hono/handlers/http-stream.js.map +1 -0
- package/build/implementations/http/hono/handlers/http-stream.test.js +128 -0
- package/build/implementations/http/hono/handlers/http-stream.test.js.map +1 -0
- package/build/implementations/http/hono/handlers/http.d.ts +10 -0
- package/build/implementations/http/hono/handlers/http.js +115 -0
- package/build/implementations/http/hono/handlers/http.js.map +1 -0
- package/build/implementations/http/hono/handlers/http.test.js +118 -0
- package/build/implementations/http/hono/handlers/http.test.js.map +1 -0
- package/build/implementations/http/hono/handlers/rpc.d.ts +11 -0
- package/build/implementations/http/hono/handlers/rpc.js +32 -0
- package/build/implementations/http/hono/handlers/rpc.js.map +1 -0
- package/build/implementations/http/hono/handlers/rpc.test.js +73 -0
- package/build/implementations/http/hono/handlers/rpc.test.js.map +1 -0
- package/build/implementations/http/hono/handlers/stream.d.ts +23 -0
- package/build/implementations/http/hono/handlers/stream.js +147 -0
- package/build/implementations/http/hono/handlers/stream.js.map +1 -0
- package/build/implementations/http/hono/handlers/stream.test.d.ts +1 -0
- package/build/implementations/http/hono/handlers/stream.test.js +177 -0
- package/build/implementations/http/hono/handlers/stream.test.js.map +1 -0
- package/build/implementations/http/hono/index.d.ts +57 -0
- package/build/implementations/http/hono/index.js +149 -0
- package/build/implementations/http/hono/index.js.map +1 -0
- package/build/implementations/http/hono/index.test.d.ts +1 -0
- package/build/implementations/http/hono/index.test.js +274 -0
- package/build/implementations/http/hono/index.test.js.map +1 -0
- package/build/implementations/http/hono/path.d.ts +17 -0
- package/build/implementations/http/hono/path.js +39 -0
- package/build/implementations/http/hono/path.js.map +1 -0
- package/build/implementations/http/hono/path.test.d.ts +1 -0
- package/build/implementations/http/hono/path.test.js +83 -0
- package/build/implementations/http/hono/path.test.js.map +1 -0
- package/build/implementations/http/hono/types.d.ts +51 -0
- package/build/implementations/http/hono/types.js.map +1 -0
- package/build/implementations/http/on-request-error.test.js +6 -96
- package/build/implementations/http/on-request-error.test.js.map +1 -1
- package/build/implementations/http/route-errors.test.js +11 -59
- package/build/implementations/http/route-errors.test.js.map +1 -1
- package/build/implementations/types.d.ts +43 -9
- package/build/index.d.ts +125 -115
- package/build/index.js +10 -222
- package/build/index.js.map +1 -1
- package/build/index.test.js +30 -822
- package/build/index.test.js.map +1 -1
- package/build/migration.test.d.ts +1 -0
- package/build/migration.test.js +34 -0
- package/build/migration.test.js.map +1 -0
- package/build/schema/compute-schema.d.ts +11 -3
- package/build/schema/compute-schema.js +13 -7
- package/build/schema/compute-schema.js.map +1 -1
- package/build/schema/parser.d.ts +11 -3
- package/build/schema/parser.js +49 -9
- package/build/schema/parser.js.map +1 -1
- package/build/stack-utils.js +8 -0
- package/build/stack-utils.js.map +1 -1
- package/build/types.d.ts +142 -0
- package/build/types.js.map +1 -0
- package/docs/astro-adapter.md +5 -5
- package/docs/core.md +34 -17
- package/docs/http-integrations.md +83 -170
- package/docs/streaming.md +3 -60
- package/docs/superpowers/plans/2026-05-07-astro-adapter.md +2 -7
- package/docs/superpowers/plans/2026-05-08-create-http.md +3355 -0
- package/docs/superpowers/plans/2026-05-08-hono-app-builder-convergence.md +3365 -0
- package/docs/superpowers/specs/2026-05-07-astro-adapter-design.md +1 -3
- package/docs/superpowers/specs/2026-05-08-create-http-design.md +409 -0
- package/docs/superpowers/specs/2026-05-08-hono-app-builder-convergence-design.md +411 -0
- package/package.json +4 -22
- package/src/client/call.test.ts +26 -0
- package/src/client/call.ts +4 -1
- package/src/client/fetch-adapter.test.ts +14 -1
- package/src/client/fetch-adapter.ts +3 -1
- package/src/client/index.test.ts +7 -7
- package/src/client/request-builder.ts +2 -2
- package/src/client/stream.test.ts +39 -7
- package/src/client/stream.ts +16 -2
- package/src/client/typed-error-dispatch.test.ts +7 -97
- package/src/client/types.ts +21 -3
- package/src/codegen/__fixtures__/users-envelope.json +119 -38
- package/src/codegen/e2e.test.ts +98 -24
- package/src/codegen/emit-errors.integration.test.ts +1 -1
- package/src/codegen/emit-scope.test.ts +395 -110
- package/src/codegen/emit-scope.ts +350 -55
- package/src/codegen/pipeline.test.ts +7 -7
- package/src/codegen/resolve-envelope.test.ts +5 -5
- package/src/codegen/resolve-envelope.ts +1 -1
- package/src/codegen/targets/_shared/route-slots.test.ts +109 -26
- package/src/codegen/targets/_shared/route-slots.ts +48 -11
- package/src/codegen/targets/kotlin/__fixtures__/users-golden.kt +73 -0
- package/src/codegen/targets/kotlin/emit-route-kotlin.test.ts +100 -17
- package/src/codegen/targets/kotlin/emit-scope-kotlin.test.ts +9 -6
- package/src/codegen/targets/kotlin/integration.test.ts +19 -0
- package/src/codegen/targets/swift/__fixtures__/users-golden.swift +79 -0
- package/src/codegen/targets/swift/access-level.test.ts +8 -11
- package/src/codegen/targets/swift/emit-route-swift.test.ts +103 -20
- package/src/codegen/targets/swift/emit-scope-swift.test.ts +12 -9
- package/src/codegen/targets/swift/integration.test.ts +17 -0
- package/src/create-http-stream.test.ts +97 -0
- package/src/create-http-stream.ts +191 -0
- package/src/create-http.test.ts +163 -0
- package/src/create-http.ts +211 -0
- package/src/create-stream.test.ts +565 -0
- package/src/create-stream.ts +228 -0
- package/src/create.test.ts +658 -0
- package/src/create.ts +172 -0
- package/src/exports.ts +2 -0
- package/src/implementations/http/README.md +135 -95
- package/src/implementations/http/astro/README.md +4 -5
- package/src/implementations/http/astro/index.test.ts +25 -18
- package/src/implementations/http/doc-registry.test.ts +42 -5
- package/src/implementations/http/doc-registry.ts +1 -1
- package/src/implementations/http/error-dispatch.test.ts +283 -0
- package/src/implementations/http/error-dispatch.ts +176 -0
- package/src/implementations/http/error-taxonomy.ts +5 -5
- package/src/implementations/http/hono/docs/http-doc.ts +43 -0
- package/src/implementations/http/hono/docs/http-stream-doc.ts +44 -0
- package/src/implementations/http/hono/docs/rpc-doc.ts +34 -0
- package/src/implementations/http/hono/docs/stream-doc.ts +53 -0
- package/src/implementations/http/hono/handlers/http-stream.test.ts +150 -0
- package/src/implementations/http/hono/handlers/http-stream.ts +152 -0
- package/src/implementations/http/hono/handlers/http.test.ts +130 -0
- package/src/implementations/http/hono/handlers/http.ts +147 -0
- package/src/implementations/http/hono/handlers/rpc.test.ts +81 -0
- package/src/implementations/http/hono/handlers/rpc.ts +54 -0
- package/src/implementations/http/hono/handlers/stream.test.ts +198 -0
- package/src/implementations/http/hono/handlers/stream.ts +208 -0
- package/src/implementations/http/hono/index.test.ts +329 -0
- package/src/implementations/http/hono/index.ts +204 -0
- package/src/implementations/http/hono/path.test.ts +96 -0
- package/src/implementations/http/hono/path.ts +59 -0
- package/src/implementations/http/hono/types.ts +93 -0
- package/src/implementations/http/on-request-error.test.ts +10 -116
- package/src/implementations/http/route-errors.test.ts +11 -77
- package/src/implementations/types.ts +44 -9
- package/src/index.test.ts +35 -1091
- package/src/index.ts +50 -474
- package/src/migration.test.ts +48 -0
- package/src/schema/compute-schema.ts +26 -12
- package/src/schema/parser.ts +62 -12
- package/src/stack-utils.ts +8 -0
- package/src/types.ts +133 -0
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/express-rpc.md +0 -137
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-api.md +0 -173
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-rpc.md +0 -142
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-stream.md +0 -147
- package/build/implementations/http/express-rpc/error-taxonomy.test.js +0 -83
- package/build/implementations/http/express-rpc/error-taxonomy.test.js.map +0 -1
- package/build/implementations/http/express-rpc/index.d.ts +0 -125
- package/build/implementations/http/express-rpc/index.js +0 -216
- package/build/implementations/http/express-rpc/index.js.map +0 -1
- package/build/implementations/http/express-rpc/index.test.js +0 -684
- package/build/implementations/http/express-rpc/index.test.js.map +0 -1
- package/build/implementations/http/express-rpc/types.d.ts +0 -11
- package/build/implementations/http/express-rpc/types.js.map +0 -1
- package/build/implementations/http/hono-api/error-taxonomy.test.js +0 -137
- package/build/implementations/http/hono-api/error-taxonomy.test.js.map +0 -1
- package/build/implementations/http/hono-api/index.d.ts +0 -151
- package/build/implementations/http/hono-api/index.js +0 -344
- package/build/implementations/http/hono-api/index.js.map +0 -1
- package/build/implementations/http/hono-api/index.test.js +0 -992
- package/build/implementations/http/hono-api/index.test.js.map +0 -1
- package/build/implementations/http/hono-api/types.d.ts +0 -13
- package/build/implementations/http/hono-api/types.js.map +0 -1
- package/build/implementations/http/hono-rpc/error-taxonomy.test.js +0 -64
- package/build/implementations/http/hono-rpc/error-taxonomy.test.js.map +0 -1
- package/build/implementations/http/hono-rpc/index.d.ts +0 -130
- package/build/implementations/http/hono-rpc/index.js +0 -209
- package/build/implementations/http/hono-rpc/index.js.map +0 -1
- package/build/implementations/http/hono-rpc/index.test.js +0 -828
- package/build/implementations/http/hono-rpc/index.test.js.map +0 -1
- package/build/implementations/http/hono-rpc/types.d.ts +0 -11
- package/build/implementations/http/hono-rpc/types.js +0 -2
- package/build/implementations/http/hono-rpc/types.js.map +0 -1
- package/build/implementations/http/hono-stream/error-taxonomy.test.js +0 -159
- package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +0 -1
- package/build/implementations/http/hono-stream/index.d.ts +0 -171
- package/build/implementations/http/hono-stream/index.js +0 -415
- package/build/implementations/http/hono-stream/index.js.map +0 -1
- package/build/implementations/http/hono-stream/index.test.js +0 -1383
- package/build/implementations/http/hono-stream/index.test.js.map +0 -1
- package/build/implementations/http/hono-stream/types.d.ts +0 -15
- package/build/implementations/http/hono-stream/types.js +0 -2
- package/build/implementations/http/hono-stream/types.js.map +0 -1
- package/src/implementations/http/express-rpc/README.md +0 -280
- package/src/implementations/http/express-rpc/error-taxonomy.test.ts +0 -103
- package/src/implementations/http/express-rpc/index.test.ts +0 -957
- package/src/implementations/http/express-rpc/index.ts +0 -327
- package/src/implementations/http/express-rpc/types.ts +0 -16
- package/src/implementations/http/hono-api/README.md +0 -284
- package/src/implementations/http/hono-api/error-taxonomy.test.ts +0 -179
- package/src/implementations/http/hono-api/index.test.ts +0 -1341
- package/src/implementations/http/hono-api/index.ts +0 -519
- package/src/implementations/http/hono-api/types.ts +0 -16
- package/src/implementations/http/hono-rpc/README.md +0 -357
- package/src/implementations/http/hono-rpc/error-taxonomy.test.ts +0 -82
- package/src/implementations/http/hono-rpc/index.test.ts +0 -1107
- package/src/implementations/http/hono-rpc/index.ts +0 -320
- package/src/implementations/http/hono-rpc/types.ts +0 -16
- package/src/implementations/http/hono-stream/README.md +0 -559
- package/src/implementations/http/hono-stream/error-taxonomy.test.ts +0 -178
- package/src/implementations/http/hono-stream/index.test.ts +0 -1804
- package/src/implementations/http/hono-stream/index.ts +0 -622
- package/src/implementations/http/hono-stream/types.ts +0 -20
- /package/build/{implementations/http/express-rpc/error-taxonomy.test.d.ts → create-http-stream.test.d.ts} +0 -0
- /package/build/{implementations/http/express-rpc/index.test.d.ts → create-http.test.d.ts} +0 -0
- /package/build/{implementations/http/hono-api/error-taxonomy.test.d.ts → create-stream.test.d.ts} +0 -0
- /package/build/{implementations/http/hono-api/index.test.d.ts → create.test.d.ts} +0 -0
- /package/build/implementations/http/{hono-rpc/error-taxonomy.test.d.ts → error-dispatch.test.d.ts} +0 -0
- /package/build/implementations/http/{hono-rpc/index.test.d.ts → hono/handlers/http-stream.test.d.ts} +0 -0
- /package/build/implementations/http/{hono-stream/error-taxonomy.test.d.ts → hono/handlers/http.test.d.ts} +0 -0
- /package/build/implementations/http/{hono-stream/index.test.d.ts → hono/handlers/rpc.test.d.ts} +0 -0
- /package/build/implementations/http/{express-rpc → hono}/types.js +0 -0
- /package/build/{implementations/http/hono-api/types.js → types.js} +0 -0
|
@@ -46,20 +46,24 @@ const apiGroup: ScopeGroup = {
|
|
|
46
46
|
fullPath: '/api/posts/:id',
|
|
47
47
|
scope: 'posts',
|
|
48
48
|
jsonSchema: {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
req: {
|
|
50
|
+
pathParams: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: { id: { type: 'string' } },
|
|
53
|
+
required: ['id'],
|
|
54
|
+
},
|
|
55
|
+
body: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: { title: { type: 'string' } },
|
|
58
|
+
required: ['title'],
|
|
59
|
+
},
|
|
58
60
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
res: {
|
|
62
|
+
body: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: { id: { type: 'string' }, title: { type: 'string' } },
|
|
65
|
+
required: ['id', 'title'],
|
|
66
|
+
},
|
|
63
67
|
},
|
|
64
68
|
},
|
|
65
69
|
} satisfies APIHttpRouteDoc,
|
|
@@ -305,22 +309,22 @@ describe('emitScopeFile', () => {
|
|
|
305
309
|
})
|
|
306
310
|
|
|
307
311
|
describe('API scope', () => {
|
|
308
|
-
it('generates
|
|
312
|
+
it('generates Req-prefixed channel types in flat mode', async () => {
|
|
309
313
|
const output = await emitScopeFile(apiGroup)
|
|
310
|
-
expect(output).toContain('export type
|
|
311
|
-
expect(output).toContain('export type
|
|
314
|
+
expect(output).toContain('export type UpdatePostReqPathParams')
|
|
315
|
+
expect(output).toContain('export type UpdatePostReqBody')
|
|
312
316
|
})
|
|
313
317
|
|
|
314
|
-
it('composes structured
|
|
318
|
+
it('composes structured Req type in flat mode', async () => {
|
|
315
319
|
const output = await emitScopeFile(apiGroup)
|
|
316
|
-
expect(output).toContain('export type
|
|
317
|
-
expect(output).toContain('pathParams:
|
|
318
|
-
expect(output).toContain('body:
|
|
320
|
+
expect(output).toContain('export type UpdatePostReq =')
|
|
321
|
+
expect(output).toContain('pathParams: UpdatePostReqPathParams')
|
|
322
|
+
expect(output).toContain('body: UpdatePostReqBody')
|
|
319
323
|
})
|
|
320
324
|
|
|
321
|
-
it('exports response type', async () => {
|
|
325
|
+
it('exports response body type with Response prefix in flat mode', async () => {
|
|
322
326
|
const output = await emitScopeFile(apiGroup)
|
|
323
|
-
expect(output).toContain('export type
|
|
327
|
+
expect(output).toContain('export type UpdatePostResponseBody')
|
|
324
328
|
})
|
|
325
329
|
|
|
326
330
|
it('callable uses client.bindCallable with kind: api', async () => {
|
|
@@ -443,24 +447,27 @@ describe('emitScopeFile', () => {
|
|
|
443
447
|
})
|
|
444
448
|
|
|
445
449
|
describe('API scope', () => {
|
|
446
|
-
it('wraps channel types
|
|
450
|
+
it('wraps channel types in nested Req/Response sub-namespaces inside route namespace', async () => {
|
|
447
451
|
const output = await emitScopeFile(apiGroup, { namespaceTypes: true })
|
|
448
452
|
expect(output).toContain('export namespace Posts {')
|
|
449
|
-
expect(output).toContain('
|
|
453
|
+
expect(output).toContain('export namespace UpdatePost {')
|
|
454
|
+
expect(output).toContain('export namespace Req {')
|
|
455
|
+
expect(output).toContain('export namespace Response {')
|
|
450
456
|
expect(output).toContain('export type PathParams =')
|
|
451
457
|
expect(output).toContain('export type Body =')
|
|
452
|
-
|
|
458
|
+
// Response body is emitted inside Response sub-namespace
|
|
459
|
+
expect(output).toContain('export type Body =')
|
|
453
460
|
})
|
|
454
461
|
|
|
455
|
-
it('
|
|
462
|
+
it('callable uses Req sub-namespace as params type', async () => {
|
|
456
463
|
const output = await emitScopeFile(apiGroup, { namespaceTypes: true })
|
|
457
|
-
expect(output).toContain('
|
|
464
|
+
expect(output).toContain('Posts.UpdatePost.Req')
|
|
458
465
|
})
|
|
459
466
|
|
|
460
467
|
it('callable references fully qualified namespace types', async () => {
|
|
461
468
|
const output = await emitScopeFile(apiGroup, { namespaceTypes: true })
|
|
462
|
-
//
|
|
463
|
-
expect(output).toContain('client.bindCallable<Posts.UpdatePost.
|
|
469
|
+
// Params type is Posts.UpdatePost.Req; return is Posts.UpdatePost.Response.Body (body only)
|
|
470
|
+
expect(output).toContain('client.bindCallable<Posts.UpdatePost.Req, Posts.UpdatePost.Response.Body>')
|
|
464
471
|
})
|
|
465
472
|
})
|
|
466
473
|
|
|
@@ -778,20 +785,24 @@ const apiGroupWithErrors: ScopeGroup = {
|
|
|
778
785
|
scope: 'posts',
|
|
779
786
|
errors: ['NotFound'],
|
|
780
787
|
jsonSchema: {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
788
|
+
req: {
|
|
789
|
+
pathParams: {
|
|
790
|
+
type: 'object',
|
|
791
|
+
properties: { id: { type: 'string' } },
|
|
792
|
+
required: ['id'],
|
|
793
|
+
},
|
|
794
|
+
body: {
|
|
795
|
+
type: 'object',
|
|
796
|
+
properties: { title: { type: 'string' } },
|
|
797
|
+
required: ['title'],
|
|
798
|
+
},
|
|
790
799
|
},
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
800
|
+
res: {
|
|
801
|
+
body: {
|
|
802
|
+
type: 'object',
|
|
803
|
+
properties: { id: { type: 'string' }, title: { type: 'string' } },
|
|
804
|
+
required: ['id', 'title'],
|
|
805
|
+
},
|
|
795
806
|
},
|
|
796
807
|
},
|
|
797
808
|
} satisfies APIHttpRouteDoc,
|
|
@@ -810,20 +821,24 @@ const apiGroupNoErrors: ScopeGroup = {
|
|
|
810
821
|
fullPath: '/api/posts/:id',
|
|
811
822
|
scope: 'posts',
|
|
812
823
|
jsonSchema: {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
824
|
+
req: {
|
|
825
|
+
pathParams: {
|
|
826
|
+
type: 'object',
|
|
827
|
+
properties: { id: { type: 'string' } },
|
|
828
|
+
required: ['id'],
|
|
829
|
+
},
|
|
830
|
+
body: {
|
|
831
|
+
type: 'object',
|
|
832
|
+
properties: { title: { type: 'string' } },
|
|
833
|
+
required: ['title'],
|
|
834
|
+
},
|
|
822
835
|
},
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
836
|
+
res: {
|
|
837
|
+
body: {
|
|
838
|
+
type: 'object',
|
|
839
|
+
properties: { id: { type: 'string' }, title: { type: 'string' } },
|
|
840
|
+
required: ['id', 'title'],
|
|
841
|
+
},
|
|
827
842
|
},
|
|
828
843
|
},
|
|
829
844
|
} satisfies APIHttpRouteDoc,
|
|
@@ -837,7 +852,7 @@ describe('emitScopeFile .safe sibling on API', () => {
|
|
|
837
852
|
errorKeys: new Set(['NotFound']),
|
|
838
853
|
serviceName: 'Api',
|
|
839
854
|
})
|
|
840
|
-
// With errors: uses bindCallableTyped<
|
|
855
|
+
// With errors: uses bindCallableTyped<Req, ReturnType, Errors>
|
|
841
856
|
expect(out).toContain('client.bindCallableTyped<')
|
|
842
857
|
// No Object.assign in generated output
|
|
843
858
|
expect(out).not.toContain('Object.assign')
|
|
@@ -848,7 +863,7 @@ describe('emitScopeFile .safe sibling on API', () => {
|
|
|
848
863
|
namespaceTypes: true,
|
|
849
864
|
serviceName: 'Api',
|
|
850
865
|
})
|
|
851
|
-
// Without errors: uses bindCallable<
|
|
866
|
+
// Without errors: uses bindCallable<Req, ReturnType>
|
|
852
867
|
expect(out).toContain('client.bindCallable<')
|
|
853
868
|
// No Object.assign in generated output
|
|
854
869
|
expect(out).not.toContain('Object.assign')
|
|
@@ -861,7 +876,8 @@ describe('emitScopeFile .safe sibling on API', () => {
|
|
|
861
876
|
serviceName: 'Api',
|
|
862
877
|
})
|
|
863
878
|
// errorsRef in namespace mode is the route's Errors type alias: Scope.Route.Errors
|
|
864
|
-
|
|
879
|
+
// Params is Req sub-namespace; return is Response.Body (body-only)
|
|
880
|
+
expect(out).toContain('client.bindCallableTyped<Posts.UpdatePost.Req, Posts.UpdatePost.Response.Body, Posts.UpdatePost.Errors>')
|
|
865
881
|
})
|
|
866
882
|
|
|
867
883
|
it('flat mode: uses route Errors type alias as third type arg', async () => {
|
|
@@ -871,7 +887,8 @@ describe('emitScopeFile .safe sibling on API', () => {
|
|
|
871
887
|
serviceName: 'Api',
|
|
872
888
|
})
|
|
873
889
|
// errorsRef in flat mode is the injected UpdatePostErrors type alias
|
|
874
|
-
|
|
890
|
+
// Params is UpdatePostReq; return is UpdatePostResponseBody (body-only)
|
|
891
|
+
expect(out).toContain('client.bindCallableTyped<UpdatePostReq, UpdatePostResponseBody, UpdatePostErrors>')
|
|
875
892
|
})
|
|
876
893
|
|
|
877
894
|
it('route with errors uses bindCallableTyped', async () => {
|
|
@@ -965,39 +982,43 @@ const apiGroupScopeCollision: ScopeGroup = {
|
|
|
965
982
|
fullPath: '/api/keys',
|
|
966
983
|
scope: 'keys',
|
|
967
984
|
jsonSchema: {
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
985
|
+
req: {
|
|
986
|
+
// Body has a property literally named `scope` whose value is an object —
|
|
987
|
+
// ajsc with inlineTypes:false extracts `export type Scope = {...}`.
|
|
988
|
+
body: {
|
|
989
|
+
type: 'object',
|
|
990
|
+
properties: {
|
|
991
|
+
scope: {
|
|
992
|
+
type: 'object',
|
|
993
|
+
properties: { resource: { type: 'string' }, action: { type: 'string' } },
|
|
994
|
+
required: ['resource', 'action'],
|
|
995
|
+
},
|
|
977
996
|
},
|
|
997
|
+
required: ['scope'],
|
|
978
998
|
},
|
|
979
|
-
required: ['scope'],
|
|
980
999
|
},
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1000
|
+
res: {
|
|
1001
|
+
// Response also contains a property named `scope`, but with a DIFFERENT
|
|
1002
|
+
// shape (additional `grantedAt` field). ajsc extracts a second
|
|
1003
|
+
// `export type Scope = {...}` whose body string differs from the body's
|
|
1004
|
+
// `Scope`, so the dedup-by-string-equality in formatTypes does not
|
|
1005
|
+
// collapse them — both end up in the namespace.
|
|
1006
|
+
body: {
|
|
1007
|
+
type: 'object',
|
|
1008
|
+
properties: {
|
|
1009
|
+
id: { type: 'string' },
|
|
1010
|
+
scope: {
|
|
1011
|
+
type: 'object',
|
|
1012
|
+
properties: {
|
|
1013
|
+
resource: { type: 'string' },
|
|
1014
|
+
action: { type: 'string' },
|
|
1015
|
+
grantedAt: { type: 'string' },
|
|
1016
|
+
},
|
|
1017
|
+
required: ['resource', 'action', 'grantedAt'],
|
|
996
1018
|
},
|
|
997
|
-
required: ['resource', 'action', 'grantedAt'],
|
|
998
1019
|
},
|
|
1020
|
+
required: ['id', 'scope'],
|
|
999
1021
|
},
|
|
1000
|
-
required: ['id', 'scope'],
|
|
1001
1022
|
},
|
|
1002
1023
|
},
|
|
1003
1024
|
} satisfies APIHttpRouteDoc,
|
|
@@ -1016,26 +1037,30 @@ const apiGroupParamsCollision: ScopeGroup = {
|
|
|
1016
1037
|
fullPath: '/api/schemas',
|
|
1017
1038
|
scope: 'schemas',
|
|
1018
1039
|
jsonSchema: {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1040
|
+
req: {
|
|
1041
|
+
// Body has a property literally named `params` — ajsc extracts
|
|
1042
|
+
// `export type Params = {...}`. With the new nested Req namespace,
|
|
1043
|
+
// the `Params` sub-type lives inside `Req {}` and does not collide
|
|
1044
|
+
// with the route-level structured type (which no longer exists as `Params`).
|
|
1045
|
+
body: {
|
|
1046
|
+
type: 'object',
|
|
1047
|
+
properties: {
|
|
1048
|
+
name: { type: 'string' },
|
|
1049
|
+
params: {
|
|
1050
|
+
type: 'object',
|
|
1051
|
+
properties: { kind: { type: 'string' } },
|
|
1052
|
+
required: ['kind'],
|
|
1053
|
+
},
|
|
1031
1054
|
},
|
|
1055
|
+
required: ['name', 'params'],
|
|
1032
1056
|
},
|
|
1033
|
-
required: ['name', 'params'],
|
|
1034
1057
|
},
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1058
|
+
res: {
|
|
1059
|
+
body: {
|
|
1060
|
+
type: 'object',
|
|
1061
|
+
properties: { id: { type: 'string' } },
|
|
1062
|
+
required: ['id'],
|
|
1063
|
+
},
|
|
1039
1064
|
},
|
|
1040
1065
|
},
|
|
1041
1066
|
} satisfies APIHttpRouteDoc,
|
|
@@ -1056,17 +1081,23 @@ describe('emitScopeFile (bug repro: duplicate identifiers in namespace)', () =>
|
|
|
1056
1081
|
expect(countTypeDeclarations(out, 'Params')).toBe(1)
|
|
1057
1082
|
})
|
|
1058
1083
|
|
|
1059
|
-
it('does not emit two `export type Scope`
|
|
1084
|
+
it('does not emit two `export type Scope` for API routes with `scope` properties in both req and res (now in separate sub-namespaces)', async () => {
|
|
1060
1085
|
const out = await emitScopeFile(apiGroupScopeCollision, { namespaceTypes: true })
|
|
1061
|
-
//
|
|
1062
|
-
//
|
|
1063
|
-
|
|
1086
|
+
// With the new Req/Response sub-namespace structure, the body's `scope`
|
|
1087
|
+
// sub-type lives in `Req {}` and the response's `scope` sub-type lives in
|
|
1088
|
+
// `Response {}` — they're in separate namespaces and do not collide. Each
|
|
1089
|
+
// sub-namespace can have at most one `Scope` (the rename guard still applies
|
|
1090
|
+
// within each sub-namespace). Total count across the file can be 2 (one in
|
|
1091
|
+
// each sub-namespace).
|
|
1092
|
+
expect(countTypeDeclarations(out, 'Scope')).toBeGreaterThanOrEqual(1)
|
|
1093
|
+
expect(countTypeDeclarations(out, 'Scope')).toBeLessThanOrEqual(2)
|
|
1064
1094
|
})
|
|
1065
1095
|
|
|
1066
|
-
it('does not emit two `export type Params` inside
|
|
1096
|
+
it('does not emit two `export type Params` inside the Req sub-namespace when the body has a `params` property (API)', async () => {
|
|
1067
1097
|
const out = await emitScopeFile(apiGroupParamsCollision, { namespaceTypes: true })
|
|
1068
|
-
// One extracted `Params` (from body.params property)
|
|
1069
|
-
//
|
|
1098
|
+
// One extracted `Params` (from body.params property) — lives inside `Req {}`.
|
|
1099
|
+
// No separate structured Params injection at the route level any more.
|
|
1100
|
+
// The rename guard inside formatSubNamespace ensures at most 1 `Params` in Req.
|
|
1070
1101
|
expect(countTypeDeclarations(out, 'Params')).toBe(1)
|
|
1071
1102
|
})
|
|
1072
1103
|
})
|
|
@@ -1115,3 +1146,257 @@ describe('emitScopeFile streams omit .safe sibling', () => {
|
|
|
1115
1146
|
expect(out).not.toMatch(/WatchEvents\s*:\s*client\.bindCallable/)
|
|
1116
1147
|
})
|
|
1117
1148
|
})
|
|
1149
|
+
|
|
1150
|
+
// ---------------------------------------------------------------------------
|
|
1151
|
+
// API conditional return type (body-only, headers-only, both, void)
|
|
1152
|
+
// ---------------------------------------------------------------------------
|
|
1153
|
+
|
|
1154
|
+
import type { HttpStreamRouteDoc } from '../implementations/types.js'
|
|
1155
|
+
|
|
1156
|
+
const apiGroupBodyOnly: ScopeGroup = {
|
|
1157
|
+
scopeKey: 'reports',
|
|
1158
|
+
camelCase: 'reports',
|
|
1159
|
+
routes: [
|
|
1160
|
+
{
|
|
1161
|
+
kind: 'api',
|
|
1162
|
+
name: 'GetReport',
|
|
1163
|
+
path: '/reports/:id',
|
|
1164
|
+
method: 'get',
|
|
1165
|
+
fullPath: '/api/reports/:id',
|
|
1166
|
+
scope: 'reports',
|
|
1167
|
+
jsonSchema: {
|
|
1168
|
+
req: { pathParams: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
|
|
1169
|
+
res: { body: { type: 'object', properties: { data: { type: 'string' } }, required: ['data'] } },
|
|
1170
|
+
},
|
|
1171
|
+
} satisfies APIHttpRouteDoc,
|
|
1172
|
+
],
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
const apiGroupBothBodyAndHeaders: ScopeGroup = {
|
|
1176
|
+
scopeKey: 'downloads',
|
|
1177
|
+
camelCase: 'downloads',
|
|
1178
|
+
routes: [
|
|
1179
|
+
{
|
|
1180
|
+
kind: 'api',
|
|
1181
|
+
name: 'GetDownload',
|
|
1182
|
+
path: '/downloads/:id',
|
|
1183
|
+
method: 'get',
|
|
1184
|
+
fullPath: '/api/downloads/:id',
|
|
1185
|
+
scope: 'downloads',
|
|
1186
|
+
jsonSchema: {
|
|
1187
|
+
req: { pathParams: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
|
|
1188
|
+
res: {
|
|
1189
|
+
body: { type: 'object', properties: { url: { type: 'string' } }, required: ['url'] },
|
|
1190
|
+
headers: { type: 'object', properties: { 'x-token': { type: 'string' } }, required: ['x-token'] },
|
|
1191
|
+
},
|
|
1192
|
+
},
|
|
1193
|
+
} satisfies APIHttpRouteDoc,
|
|
1194
|
+
],
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const apiGroupHeadersOnly: ScopeGroup = {
|
|
1198
|
+
scopeKey: 'preflight',
|
|
1199
|
+
camelCase: 'preflight',
|
|
1200
|
+
routes: [
|
|
1201
|
+
{
|
|
1202
|
+
kind: 'api',
|
|
1203
|
+
name: 'GetPreflight',
|
|
1204
|
+
path: '/preflight',
|
|
1205
|
+
method: 'get',
|
|
1206
|
+
fullPath: '/api/preflight',
|
|
1207
|
+
scope: 'preflight',
|
|
1208
|
+
jsonSchema: {
|
|
1209
|
+
req: {},
|
|
1210
|
+
res: { headers: { type: 'object', properties: { 'x-allowed': { type: 'string' } }, required: ['x-allowed'] } },
|
|
1211
|
+
},
|
|
1212
|
+
} satisfies APIHttpRouteDoc,
|
|
1213
|
+
],
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
const apiGroupNoBody: ScopeGroup = {
|
|
1217
|
+
scopeKey: 'pings',
|
|
1218
|
+
camelCase: 'pings',
|
|
1219
|
+
routes: [
|
|
1220
|
+
{
|
|
1221
|
+
kind: 'api',
|
|
1222
|
+
name: 'Ping',
|
|
1223
|
+
path: '/ping',
|
|
1224
|
+
method: 'get',
|
|
1225
|
+
fullPath: '/api/ping',
|
|
1226
|
+
scope: 'pings',
|
|
1227
|
+
jsonSchema: { req: {}, res: {} },
|
|
1228
|
+
} satisfies APIHttpRouteDoc,
|
|
1229
|
+
],
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
describe('emitScopeFile API conditional return type', () => {
|
|
1233
|
+
describe('flat mode', () => {
|
|
1234
|
+
it('body-only: return type is the body type', async () => {
|
|
1235
|
+
const out = await emitScopeFile(apiGroupBodyOnly)
|
|
1236
|
+
expect(out).toContain('export type GetReportResponseBody =')
|
|
1237
|
+
expect(out).toContain('client.bindCallable<GetReportReq, GetReportResponseBody>')
|
|
1238
|
+
})
|
|
1239
|
+
|
|
1240
|
+
it('body + headers: return type is { body; headers }', async () => {
|
|
1241
|
+
const out = await emitScopeFile(apiGroupBothBodyAndHeaders)
|
|
1242
|
+
expect(out).toContain('export type GetDownloadResponseBody =')
|
|
1243
|
+
expect(out).toContain('export type GetDownloadResponseHeaders =')
|
|
1244
|
+
expect(out).toContain('{ body: GetDownloadResponseBody; headers: GetDownloadResponseHeaders }')
|
|
1245
|
+
})
|
|
1246
|
+
|
|
1247
|
+
it('headers-only: return type is { headers }', async () => {
|
|
1248
|
+
const out = await emitScopeFile(apiGroupHeadersOnly)
|
|
1249
|
+
expect(out).toContain('export type GetPreflightResponseHeaders =')
|
|
1250
|
+
expect(out).toContain('{ headers: GetPreflightResponseHeaders }')
|
|
1251
|
+
})
|
|
1252
|
+
|
|
1253
|
+
it('neither body nor headers: return type is void', async () => {
|
|
1254
|
+
const out = await emitScopeFile(apiGroupNoBody)
|
|
1255
|
+
expect(out).toContain('client.bindCallable<unknown, void>')
|
|
1256
|
+
})
|
|
1257
|
+
})
|
|
1258
|
+
|
|
1259
|
+
describe('namespace mode', () => {
|
|
1260
|
+
it('body-only: return type references Response.Body', async () => {
|
|
1261
|
+
const out = await emitScopeFile(apiGroupBodyOnly, { namespaceTypes: true })
|
|
1262
|
+
expect(out).toContain('export namespace Response {')
|
|
1263
|
+
expect(out).toContain('client.bindCallable<Reports.GetReport.Req, Reports.GetReport.Response.Body>')
|
|
1264
|
+
})
|
|
1265
|
+
|
|
1266
|
+
it('body + headers: return type is { body: Response.Body; headers: Response.Headers }', async () => {
|
|
1267
|
+
const out = await emitScopeFile(apiGroupBothBodyAndHeaders, { namespaceTypes: true })
|
|
1268
|
+
expect(out).toContain('export namespace Response {')
|
|
1269
|
+
expect(out).toContain('export type Body =')
|
|
1270
|
+
expect(out).toContain('export type Headers =')
|
|
1271
|
+
expect(out).toContain('{ body: Downloads.GetDownload.Response.Body; headers: Downloads.GetDownload.Response.Headers }')
|
|
1272
|
+
})
|
|
1273
|
+
|
|
1274
|
+
it('headers-only: return type is { headers: Response.Headers }', async () => {
|
|
1275
|
+
const out = await emitScopeFile(apiGroupHeadersOnly, { namespaceTypes: true })
|
|
1276
|
+
expect(out).toContain('{ headers: Preflight.GetPreflight.Response.Headers }')
|
|
1277
|
+
})
|
|
1278
|
+
|
|
1279
|
+
it('neither body nor headers: return type is void', async () => {
|
|
1280
|
+
const out = await emitScopeFile(apiGroupNoBody, { namespaceTypes: true })
|
|
1281
|
+
expect(out).toContain('client.bindCallable<unknown, void>')
|
|
1282
|
+
})
|
|
1283
|
+
})
|
|
1284
|
+
})
|
|
1285
|
+
|
|
1286
|
+
describe('emitScopeFile API responseHeadersDeclared descriptor field', () => {
|
|
1287
|
+
it('emits responseHeadersDeclared: true when res.headers is present', async () => {
|
|
1288
|
+
const out = await emitScopeFile(apiGroupBothBodyAndHeaders)
|
|
1289
|
+
expect(out).toContain('responseHeadersDeclared: true')
|
|
1290
|
+
})
|
|
1291
|
+
|
|
1292
|
+
it('does NOT emit responseHeadersDeclared when res.headers is absent', async () => {
|
|
1293
|
+
const out = await emitScopeFile(apiGroupBodyOnly)
|
|
1294
|
+
expect(out).not.toContain('responseHeadersDeclared')
|
|
1295
|
+
})
|
|
1296
|
+
})
|
|
1297
|
+
|
|
1298
|
+
// ---------------------------------------------------------------------------
|
|
1299
|
+
// http-stream kind emission
|
|
1300
|
+
// ---------------------------------------------------------------------------
|
|
1301
|
+
|
|
1302
|
+
const httpStreamGroup: ScopeGroup = {
|
|
1303
|
+
scopeKey: 'feed',
|
|
1304
|
+
camelCase: 'feed',
|
|
1305
|
+
routes: [
|
|
1306
|
+
{
|
|
1307
|
+
kind: 'http-stream',
|
|
1308
|
+
name: 'StreamFeed',
|
|
1309
|
+
path: '/feed/stream',
|
|
1310
|
+
method: 'get',
|
|
1311
|
+
fullPath: '/api/feed/stream',
|
|
1312
|
+
scope: 'feed',
|
|
1313
|
+
streamMode: 'sse',
|
|
1314
|
+
jsonSchema: {
|
|
1315
|
+
req: {
|
|
1316
|
+
query: {
|
|
1317
|
+
type: 'object',
|
|
1318
|
+
properties: { cursor: { type: 'string' } },
|
|
1319
|
+
},
|
|
1320
|
+
},
|
|
1321
|
+
res: {
|
|
1322
|
+
headers: {
|
|
1323
|
+
type: 'object',
|
|
1324
|
+
properties: { 'x-session': { type: 'string' } },
|
|
1325
|
+
required: ['x-session'],
|
|
1326
|
+
},
|
|
1327
|
+
},
|
|
1328
|
+
yield: {
|
|
1329
|
+
type: 'object',
|
|
1330
|
+
properties: { message: { type: 'string' } },
|
|
1331
|
+
required: ['message'],
|
|
1332
|
+
},
|
|
1333
|
+
returnType: {
|
|
1334
|
+
type: 'object',
|
|
1335
|
+
properties: { total: { type: 'number' } },
|
|
1336
|
+
required: ['total'],
|
|
1337
|
+
},
|
|
1338
|
+
},
|
|
1339
|
+
} satisfies HttpStreamRouteDoc,
|
|
1340
|
+
],
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
describe('emitScopeFile http-stream kind', () => {
|
|
1344
|
+
describe('flat mode', () => {
|
|
1345
|
+
it('emits Req-prefixed query type', async () => {
|
|
1346
|
+
const out = await emitScopeFile(httpStreamGroup)
|
|
1347
|
+
expect(out).toContain('export type StreamFeedReqQuery =')
|
|
1348
|
+
})
|
|
1349
|
+
|
|
1350
|
+
it('emits ResponseHeaders type', async () => {
|
|
1351
|
+
const out = await emitScopeFile(httpStreamGroup)
|
|
1352
|
+
expect(out).toContain('export type StreamFeedResponseHeaders =')
|
|
1353
|
+
})
|
|
1354
|
+
|
|
1355
|
+
it('emits Yield and ReturnType types', async () => {
|
|
1356
|
+
const out = await emitScopeFile(httpStreamGroup)
|
|
1357
|
+
expect(out).toContain('export type StreamFeedYield =')
|
|
1358
|
+
expect(out).toContain('export type StreamFeedReturnType =')
|
|
1359
|
+
})
|
|
1360
|
+
|
|
1361
|
+
it('callable returns TypedStream<Yield, ReturnType>', async () => {
|
|
1362
|
+
const out = await emitScopeFile(httpStreamGroup)
|
|
1363
|
+
expect(out).toContain('TypedStream<StreamFeedYield, StreamFeedReturnType>')
|
|
1364
|
+
})
|
|
1365
|
+
|
|
1366
|
+
it('callable uses kind: http-stream in descriptor', async () => {
|
|
1367
|
+
const out = await emitScopeFile(httpStreamGroup)
|
|
1368
|
+
expect(out).toContain("kind: 'http-stream'")
|
|
1369
|
+
})
|
|
1370
|
+
|
|
1371
|
+
it('imports TypedStream', async () => {
|
|
1372
|
+
const out = await emitScopeFile(httpStreamGroup)
|
|
1373
|
+
expect(out).toContain('TypedStream')
|
|
1374
|
+
})
|
|
1375
|
+
})
|
|
1376
|
+
|
|
1377
|
+
describe('namespace mode', () => {
|
|
1378
|
+
it('emits Req sub-namespace with query type', async () => {
|
|
1379
|
+
const out = await emitScopeFile(httpStreamGroup, { namespaceTypes: true })
|
|
1380
|
+
expect(out).toContain('export namespace Req {')
|
|
1381
|
+
expect(out).toContain('export type Query =')
|
|
1382
|
+
})
|
|
1383
|
+
|
|
1384
|
+
it('emits Response sub-namespace with Headers type', async () => {
|
|
1385
|
+
const out = await emitScopeFile(httpStreamGroup, { namespaceTypes: true })
|
|
1386
|
+
expect(out).toContain('export namespace Response {')
|
|
1387
|
+
expect(out).toContain('export type Headers =')
|
|
1388
|
+
})
|
|
1389
|
+
|
|
1390
|
+
it('emits Yield and ReturnType in route namespace', async () => {
|
|
1391
|
+
const out = await emitScopeFile(httpStreamGroup, { namespaceTypes: true })
|
|
1392
|
+
expect(out).toContain('export type Yield =')
|
|
1393
|
+
expect(out).toContain('export type ReturnType =')
|
|
1394
|
+
})
|
|
1395
|
+
|
|
1396
|
+
it('callable uses qualified Req and TypedStream types', async () => {
|
|
1397
|
+
const out = await emitScopeFile(httpStreamGroup, { namespaceTypes: true })
|
|
1398
|
+
expect(out).toContain('Feed.StreamFeed.Req')
|
|
1399
|
+
expect(out).toContain('TypedStream<Feed.StreamFeed.Yield, Feed.StreamFeed.ReturnType>')
|
|
1400
|
+
})
|
|
1401
|
+
})
|
|
1402
|
+
})
|