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
|
@@ -2,54 +2,137 @@ import { describe, expect, it } from 'vitest'
|
|
|
2
2
|
import { extractRouteSlots } from './route-slots.js'
|
|
3
3
|
import type { AnyHttpRouteDoc } from '../../../implementations/types.js'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
// Helpers for constructing minimal route docs per kind
|
|
6
|
+
function apiRoute(jsonSchema: Record<string, unknown>): AnyHttpRouteDoc {
|
|
7
|
+
return { name: 'X', kind: 'api', method: 'get', path: '/x', fullPath: '/x', jsonSchema } as unknown as AnyHttpRouteDoc
|
|
8
|
+
}
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
function httpStreamRoute(jsonSchema: Record<string, unknown>): AnyHttpRouteDoc {
|
|
11
|
+
return { name: 'X', kind: 'http-stream', method: 'get', path: '/x', fullPath: '/x', streamMode: 'sse', jsonSchema } as unknown as AnyHttpRouteDoc
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function rpcRoute(jsonSchema: Record<string, unknown>): AnyHttpRouteDoc {
|
|
15
|
+
return { name: 'X', kind: 'rpc', method: 'post', path: '/x', scope: 'x', version: 1, jsonSchema } as unknown as AnyHttpRouteDoc
|
|
16
|
+
}
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
function streamRoute(jsonSchema: Record<string, unknown>): AnyHttpRouteDoc {
|
|
19
|
+
return { name: 'X', kind: 'stream', path: '/x', scope: 'x', version: 1, methods: ['get'], streamMode: 'sse', jsonSchema } as unknown as AnyHttpRouteDoc
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('extractRouteSlots — api kind', () => {
|
|
23
|
+
it('returns no slots when jsonSchema is empty', () => {
|
|
24
|
+
expect(extractRouteSlots(apiRoute({}))).toEqual([])
|
|
15
25
|
})
|
|
16
26
|
|
|
17
|
-
it('returns the deterministic slot order: PathParams, Query, Body, Response', () => {
|
|
27
|
+
it('returns the deterministic slot order: PathParams, Query, Body, Headers, Response, ResponseHeaders', () => {
|
|
18
28
|
const slots = extractRouteSlots(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
body: { type: 'object',
|
|
22
|
-
query: { type: 'object',
|
|
23
|
-
pathParams: { type: 'object',
|
|
29
|
+
apiRoute({
|
|
30
|
+
req: {
|
|
31
|
+
body: { type: 'object', tag: 'body' },
|
|
32
|
+
query: { type: 'object', tag: 'query' },
|
|
33
|
+
pathParams: { type: 'object', tag: 'path' },
|
|
34
|
+
headers: { type: 'object', tag: 'headers' },
|
|
35
|
+
},
|
|
36
|
+
res: {
|
|
37
|
+
body: { type: 'object', tag: 'response' },
|
|
38
|
+
headers: { type: 'object', tag: 'resHeaders' },
|
|
24
39
|
},
|
|
25
|
-
returnType: { type: 'object', x: 'response' },
|
|
26
40
|
}),
|
|
27
41
|
)
|
|
28
|
-
expect(slots.map((s) => s.rootName)).toEqual(['PathParams', 'Query', 'Body', 'Response'])
|
|
42
|
+
expect(slots.map((s) => s.rootName)).toEqual(['PathParams', 'Query', 'Body', 'Headers', 'Response', 'ResponseHeaders'])
|
|
29
43
|
})
|
|
30
44
|
|
|
31
45
|
it('omits slots whose source is null or undefined', () => {
|
|
32
46
|
const slots = extractRouteSlots(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// returnType missing
|
|
47
|
+
apiRoute({
|
|
48
|
+
req: { pathParams: { type: 'object' }, query: null, body: undefined },
|
|
36
49
|
}),
|
|
37
50
|
)
|
|
38
51
|
expect(slots.map((s) => s.rootName)).toEqual(['PathParams'])
|
|
39
52
|
})
|
|
40
53
|
|
|
41
|
-
it('attaches
|
|
54
|
+
it('attaches source schemas verbatim', () => {
|
|
42
55
|
const path = { type: 'object', tag: 'p' }
|
|
43
|
-
const
|
|
44
|
-
const slots = extractRouteSlots(
|
|
56
|
+
const body = { type: 'object', tag: 'b' }
|
|
57
|
+
const slots = extractRouteSlots(apiRoute({ req: { pathParams: path, body }, res: {} }))
|
|
45
58
|
expect(slots).toEqual([
|
|
46
59
|
{ rootName: 'PathParams', source: path },
|
|
47
|
-
{ rootName: '
|
|
60
|
+
{ rootName: 'Body', source: body },
|
|
61
|
+
])
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('reads responseHeaders from res.headers', () => {
|
|
65
|
+
const resHeaders = { type: 'object', tag: 'rh' }
|
|
66
|
+
const slots = extractRouteSlots(apiRoute({ res: { headers: resHeaders } }))
|
|
67
|
+
expect(slots).toEqual([{ rootName: 'ResponseHeaders', source: resHeaders }])
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('extractRouteSlots — http-stream kind', () => {
|
|
72
|
+
it('returns no slots when jsonSchema is empty', () => {
|
|
73
|
+
expect(extractRouteSlots(httpStreamRoute({}))).toEqual([])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('returns the deterministic slot order: PathParams, Query, Body, Headers, ResponseHeaders, Yield, ReturnType', () => {
|
|
77
|
+
const slots = extractRouteSlots(
|
|
78
|
+
httpStreamRoute({
|
|
79
|
+
req: {
|
|
80
|
+
pathParams: { type: 'object', tag: 'path' },
|
|
81
|
+
query: { type: 'object', tag: 'query' },
|
|
82
|
+
body: { type: 'object', tag: 'body' },
|
|
83
|
+
headers: { type: 'object', tag: 'headers' },
|
|
84
|
+
},
|
|
85
|
+
res: { headers: { type: 'object', tag: 'resHeaders' } },
|
|
86
|
+
yield: { type: 'object', tag: 'yield' },
|
|
87
|
+
returnType: { type: 'object', tag: 'return' },
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
90
|
+
expect(slots.map((s) => s.rootName)).toEqual([
|
|
91
|
+
'PathParams', 'Query', 'Body', 'Headers', 'ResponseHeaders', 'Yield', 'ReturnType',
|
|
48
92
|
])
|
|
49
93
|
})
|
|
50
94
|
|
|
51
|
-
it('
|
|
52
|
-
const
|
|
53
|
-
|
|
95
|
+
it('attaches yield and returnType sources correctly', () => {
|
|
96
|
+
const yieldSchema = { type: 'object', tag: 'y' }
|
|
97
|
+
const returnSchema = { type: 'object', tag: 'r' }
|
|
98
|
+
const slots = extractRouteSlots(httpStreamRoute({ yield: yieldSchema, returnType: returnSchema }))
|
|
99
|
+
expect(slots).toEqual([
|
|
100
|
+
{ rootName: 'Yield', source: yieldSchema },
|
|
101
|
+
{ rootName: 'ReturnType', source: returnSchema },
|
|
102
|
+
])
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('extractRouteSlots — rpc kind', () => {
|
|
107
|
+
it('returns no slots when jsonSchema is empty', () => {
|
|
108
|
+
expect(extractRouteSlots(rpcRoute({}))).toEqual([])
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('maps body and response slots', () => {
|
|
112
|
+
const body = { type: 'object', tag: 'b' }
|
|
113
|
+
const response = { type: 'object', tag: 'r' }
|
|
114
|
+
const slots = extractRouteSlots(rpcRoute({ body, response }))
|
|
115
|
+
expect(slots).toEqual([
|
|
116
|
+
{ rootName: 'Body', source: body },
|
|
117
|
+
{ rootName: 'Response', source: response },
|
|
118
|
+
])
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('extractRouteSlots — stream kind', () => {
|
|
123
|
+
it('returns no slots when jsonSchema is empty', () => {
|
|
124
|
+
expect(extractRouteSlots(streamRoute({}))).toEqual([])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('maps params→Body, yieldType→Yield, returnType→ReturnType', () => {
|
|
128
|
+
const params = { type: 'object', tag: 'p' }
|
|
129
|
+
const yieldType = { type: 'object', tag: 'y' }
|
|
130
|
+
const returnType = { type: 'object', tag: 'r' }
|
|
131
|
+
const slots = extractRouteSlots(streamRoute({ params, yieldType, returnType }))
|
|
132
|
+
expect(slots).toEqual([
|
|
133
|
+
{ rootName: 'Body', source: params },
|
|
134
|
+
{ rootName: 'Yield', source: yieldType },
|
|
135
|
+
{ rootName: 'ReturnType', source: returnType },
|
|
136
|
+
])
|
|
54
137
|
})
|
|
55
138
|
})
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { AnyHttpRouteDoc } from '../../../implementations/types.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A "slot" is one of the deterministic schema sources a route exposes
|
|
5
|
-
*
|
|
6
|
-
* (`schema.returnType`). Targets emit one type per non-null slot.
|
|
4
|
+
* A "slot" is one of the deterministic schema sources a route exposes.
|
|
5
|
+
* Targets emit one type per non-null slot.
|
|
7
6
|
*/
|
|
8
7
|
export interface RouteSlot {
|
|
9
8
|
/** Stable identifier used as the emitted type's `rootTypeName`. */
|
|
@@ -15,18 +14,56 @@ export interface RouteSlot {
|
|
|
15
14
|
/**
|
|
16
15
|
* Returns the deterministic ordered slot list for a route, filtered to slots
|
|
17
16
|
* with non-null sources. Order is fixed at the module level for stable output.
|
|
17
|
+
*
|
|
18
|
+
* Kind dispatch:
|
|
19
|
+
* - `'api'` — reads from `jsonSchema.req` / `jsonSchema.res` (new envelope shape)
|
|
20
|
+
* - `'http-stream'` — reads from `jsonSchema.req` / `jsonSchema.res` / `jsonSchema.yield` / `jsonSchema.returnType`
|
|
21
|
+
* - `'rpc'` — reads from `jsonSchema.body` / `jsonSchema.response`
|
|
22
|
+
* - `'stream'` — reads from `jsonSchema.params` / `jsonSchema.yieldType` / `jsonSchema.returnType`
|
|
18
23
|
*/
|
|
19
24
|
export function extractRouteSlots(route: AnyHttpRouteDoc): RouteSlot[] {
|
|
20
|
-
const
|
|
21
|
-
const input = (schema.input ?? {}) as Record<string, unknown>
|
|
25
|
+
const kind = route.kind
|
|
22
26
|
|
|
23
27
|
// Order is load-bearing: targets emit slots in this sequence.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
let slots: RouteSlot[]
|
|
29
|
+
|
|
30
|
+
if (kind === 'api') {
|
|
31
|
+
const req = route.jsonSchema.req ?? {}
|
|
32
|
+
const res = route.jsonSchema.res ?? {}
|
|
33
|
+
slots = [
|
|
34
|
+
{ rootName: 'PathParams', source: req.pathParams },
|
|
35
|
+
{ rootName: 'Query', source: req.query },
|
|
36
|
+
{ rootName: 'Body', source: req.body },
|
|
37
|
+
{ rootName: 'Headers', source: req.headers },
|
|
38
|
+
{ rootName: 'Response', source: res.body },
|
|
39
|
+
{ rootName: 'ResponseHeaders', source: res.headers },
|
|
40
|
+
]
|
|
41
|
+
} else if (kind === 'http-stream') {
|
|
42
|
+
const req = route.jsonSchema.req ?? {}
|
|
43
|
+
const res = route.jsonSchema.res ?? {}
|
|
44
|
+
slots = [
|
|
45
|
+
{ rootName: 'PathParams', source: req.pathParams },
|
|
46
|
+
{ rootName: 'Query', source: req.query },
|
|
47
|
+
{ rootName: 'Body', source: req.body },
|
|
48
|
+
{ rootName: 'Headers', source: req.headers },
|
|
49
|
+
{ rootName: 'ResponseHeaders', source: res.headers },
|
|
50
|
+
{ rootName: 'Yield', source: route.jsonSchema.yield },
|
|
51
|
+
{ rootName: 'ReturnType', source: route.jsonSchema.returnType },
|
|
52
|
+
]
|
|
53
|
+
} else if (kind === 'rpc') {
|
|
54
|
+
slots = [
|
|
55
|
+
{ rootName: 'Body', source: route.jsonSchema.body },
|
|
56
|
+
{ rootName: 'Response', source: route.jsonSchema.response },
|
|
57
|
+
]
|
|
58
|
+
} else if (kind === 'stream') {
|
|
59
|
+
slots = [
|
|
60
|
+
{ rootName: 'Body', source: route.jsonSchema.params },
|
|
61
|
+
{ rootName: 'Yield', source: route.jsonSchema.yieldType },
|
|
62
|
+
{ rootName: 'ReturnType', source: route.jsonSchema.returnType },
|
|
63
|
+
]
|
|
64
|
+
} else {
|
|
65
|
+
slots = []
|
|
66
|
+
}
|
|
30
67
|
|
|
31
68
|
return slots.filter((s) => s.source != null)
|
|
32
69
|
}
|
|
@@ -118,4 +118,77 @@ object Users {
|
|
|
118
118
|
)
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
+
|
|
122
|
+
object DownloadUser {
|
|
123
|
+
const val method = "GET"
|
|
124
|
+
const val pathTemplate = "/users/{id}/download"
|
|
125
|
+
fun path(p: PathParams): String = "/users/${p.id}/download"
|
|
126
|
+
|
|
127
|
+
@Serializable
|
|
128
|
+
data class PathParams(
|
|
129
|
+
val id: String,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
@Serializable
|
|
133
|
+
data class Response(
|
|
134
|
+
val id: String,
|
|
135
|
+
val name: String,
|
|
136
|
+
@SerialName("created-at") @Contextual val createdAt: java.time.Instant,
|
|
137
|
+
val address: Address,
|
|
138
|
+
) {
|
|
139
|
+
@Serializable
|
|
140
|
+
data class Address(
|
|
141
|
+
val street: String,
|
|
142
|
+
val city: String,
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@Serializable
|
|
147
|
+
data class ResponseHeaders(
|
|
148
|
+
@SerialName("x-download-token") val xDownloadToken: String,
|
|
149
|
+
@SerialName("content-disposition") val contentDisposition: String? = null,
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
object WatchUsers {
|
|
154
|
+
const val method = "GET"
|
|
155
|
+
const val pathTemplate = "/users/watch"
|
|
156
|
+
const val path = "/users/watch"
|
|
157
|
+
|
|
158
|
+
@Serializable
|
|
159
|
+
data class Query(
|
|
160
|
+
val status: Status? = null,
|
|
161
|
+
val limit: Long? = null,
|
|
162
|
+
) {
|
|
163
|
+
@Serializable
|
|
164
|
+
enum class Status {
|
|
165
|
+
@SerialName("active") ACTIVE,
|
|
166
|
+
@SerialName("inactive") INACTIVE,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@Serializable
|
|
171
|
+
data class ResponseHeaders(
|
|
172
|
+
@SerialName("x-download-token") val xDownloadToken: String,
|
|
173
|
+
@SerialName("content-disposition") val contentDisposition: String? = null,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@Serializable
|
|
177
|
+
data class Yield(
|
|
178
|
+
val id: String,
|
|
179
|
+
val event: Event,
|
|
180
|
+
) {
|
|
181
|
+
@Serializable
|
|
182
|
+
enum class Event {
|
|
183
|
+
@SerialName("created") CREATED,
|
|
184
|
+
@SerialName("updated") UPDATED,
|
|
185
|
+
@SerialName("deleted") DELETED,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@Serializable
|
|
190
|
+
data class ReturnType(
|
|
191
|
+
val count: Long,
|
|
192
|
+
)
|
|
193
|
+
}
|
|
121
194
|
}
|
|
@@ -34,11 +34,9 @@ describe('emitKotlinRoute', () => {
|
|
|
34
34
|
name: 'GetUser',
|
|
35
35
|
method: 'GET',
|
|
36
36
|
fullPath: '/users/:id',
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
},
|
|
41
|
-
returnType: { type: 'object' },
|
|
37
|
+
jsonSchema: {
|
|
38
|
+
req: { pathParams: { type: 'object' } },
|
|
39
|
+
res: { body: { type: 'object' } },
|
|
42
40
|
},
|
|
43
41
|
errors: [],
|
|
44
42
|
} as unknown as AnyHttpRouteDoc
|
|
@@ -64,7 +62,10 @@ describe('emitKotlinRoute', () => {
|
|
|
64
62
|
name: 'CreateUser',
|
|
65
63
|
method: 'POST',
|
|
66
64
|
fullPath: '/users',
|
|
67
|
-
|
|
65
|
+
jsonSchema: {
|
|
66
|
+
req: { body: { type: 'object' } },
|
|
67
|
+
res: { body: { type: 'object' } },
|
|
68
|
+
},
|
|
68
69
|
errors: [],
|
|
69
70
|
} as unknown as AnyHttpRouteDoc
|
|
70
71
|
|
|
@@ -84,7 +85,10 @@ describe('emitKotlinRoute', () => {
|
|
|
84
85
|
name: 'GetUser',
|
|
85
86
|
method: 'GET',
|
|
86
87
|
fullPath: '/users/:id',
|
|
87
|
-
|
|
88
|
+
jsonSchema: {
|
|
89
|
+
req: { pathParams: { type: 'object' } },
|
|
90
|
+
res: { body: { type: 'object' } },
|
|
91
|
+
},
|
|
88
92
|
errors: ['NotFound'],
|
|
89
93
|
} as unknown as AnyHttpRouteDoc
|
|
90
94
|
|
|
@@ -103,14 +107,14 @@ describe('emitKotlinRoute', () => {
|
|
|
103
107
|
it('silently skips error keys with no schema in the envelope map', () => {
|
|
104
108
|
const route = {
|
|
105
109
|
kind: 'api', name: 'GetUser', method: 'GET', fullPath: '/users',
|
|
106
|
-
|
|
110
|
+
jsonSchema: {}, errors: ['UnknownTaxonomyKey'],
|
|
107
111
|
} as unknown as AnyHttpRouteDoc
|
|
108
112
|
const result = emitKotlinRoute(route, createStubKotlinEmitter({}), new Map())
|
|
109
113
|
expect(result.code).not.toContain('object Errors {')
|
|
110
114
|
})
|
|
111
115
|
|
|
112
116
|
it('returns skipped:true for stream routes', () => {
|
|
113
|
-
const route = { kind: 'stream', name: 'WatchUsers', method: 'GET', path: '/users/stream',
|
|
117
|
+
const route = { kind: 'stream', name: 'WatchUsers', method: 'GET', path: '/users/stream', jsonSchema: {}, errors: [] } as unknown as AnyHttpRouteDoc
|
|
114
118
|
const result = emitKotlinRoute(route, createStubKotlinEmitter({}), noErrors)
|
|
115
119
|
expect(result.code).toBe('')
|
|
116
120
|
expect(result.skipped).toBe(true)
|
|
@@ -122,9 +126,9 @@ describe('emitKotlinRoute', () => {
|
|
|
122
126
|
name: 'GetUser',
|
|
123
127
|
method: 'GET',
|
|
124
128
|
fullPath: '/users/:id',
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
jsonSchema: {
|
|
130
|
+
req: { pathParams: { type: 'object' } },
|
|
131
|
+
res: { body: { type: 'object' } },
|
|
128
132
|
},
|
|
129
133
|
errors: ['NotFound'],
|
|
130
134
|
} as unknown as AnyHttpRouteDoc
|
|
@@ -155,13 +159,13 @@ describe('emitKotlinRoute', () => {
|
|
|
155
159
|
name: 'X',
|
|
156
160
|
method: 'POST',
|
|
157
161
|
fullPath: '/x/:id',
|
|
158
|
-
|
|
159
|
-
|
|
162
|
+
jsonSchema: {
|
|
163
|
+
req: {
|
|
160
164
|
pathParams: { type: 'object' },
|
|
161
165
|
query: { type: 'object' },
|
|
162
166
|
body: { type: 'object' },
|
|
163
167
|
},
|
|
164
|
-
|
|
168
|
+
res: { body: { type: 'object' } },
|
|
165
169
|
},
|
|
166
170
|
errors: ['Z'],
|
|
167
171
|
} as unknown as AnyHttpRouteDoc
|
|
@@ -187,7 +191,7 @@ describe('emitKotlinRoute', () => {
|
|
|
187
191
|
name: 'X',
|
|
188
192
|
method: 'GET',
|
|
189
193
|
fullPath: '/x',
|
|
190
|
-
|
|
194
|
+
jsonSchema: { res: { body: { type: 'object' } } },
|
|
191
195
|
errors: [],
|
|
192
196
|
} as unknown as AnyHttpRouteDoc
|
|
193
197
|
|
|
@@ -207,13 +211,92 @@ describe('emitKotlinRoute', () => {
|
|
|
207
211
|
expect(calls[0]!.opts.uncountableWords).toEqual(['data', 'metadata'])
|
|
208
212
|
})
|
|
209
213
|
|
|
214
|
+
it('emits ResponseHeaders slot when res.headers is declared on an api route', () => {
|
|
215
|
+
const route = {
|
|
216
|
+
kind: 'api',
|
|
217
|
+
name: 'DownloadUser',
|
|
218
|
+
method: 'GET',
|
|
219
|
+
fullPath: '/users/:id/download',
|
|
220
|
+
jsonSchema: {
|
|
221
|
+
req: { pathParams: { type: 'object' } },
|
|
222
|
+
res: {
|
|
223
|
+
body: { type: 'object' },
|
|
224
|
+
headers: { type: 'object' },
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
errors: [],
|
|
228
|
+
} as unknown as AnyHttpRouteDoc
|
|
229
|
+
|
|
230
|
+
const { emitter, calls } = makeSpyEmitter({
|
|
231
|
+
PathParams: ok('@Serializable data class PathParams(val id: String)', 'PathParams'),
|
|
232
|
+
Response: ok('@Serializable data class Response(val url: String)', 'Response'),
|
|
233
|
+
ResponseHeaders: ok(
|
|
234
|
+
'@Serializable\ndata class ResponseHeaders(\n @SerialName("x-download-token") val xDownloadToken: String,\n)',
|
|
235
|
+
'ResponseHeaders',
|
|
236
|
+
),
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const result = emitKotlinRoute(route, emitter, noErrors)
|
|
240
|
+
expect(calls.map((c) => c.opts.rootTypeName)).toEqual(['PathParams', 'Response', 'ResponseHeaders'])
|
|
241
|
+
expect(result.code).toContain('@SerialName("x-download-token") val xDownloadToken: String')
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('does not emit ResponseHeaders when res.headers is absent', () => {
|
|
245
|
+
const route = {
|
|
246
|
+
kind: 'api',
|
|
247
|
+
name: 'GetUser',
|
|
248
|
+
method: 'GET',
|
|
249
|
+
fullPath: '/users/:id',
|
|
250
|
+
jsonSchema: {
|
|
251
|
+
req: { pathParams: { type: 'object' } },
|
|
252
|
+
res: { body: { type: 'object' } },
|
|
253
|
+
},
|
|
254
|
+
errors: [],
|
|
255
|
+
} as unknown as AnyHttpRouteDoc
|
|
256
|
+
|
|
257
|
+
const { emitter, calls } = makeSpyEmitter({
|
|
258
|
+
PathParams: ok('@Serializable data class PathParams(val id: String)', 'PathParams'),
|
|
259
|
+
Response: ok('@Serializable data class Response(val id: String)', 'Response'),
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
emitKotlinRoute(route, emitter, noErrors)
|
|
263
|
+
expect(calls.map((c) => c.opts.rootTypeName)).not.toContain('ResponseHeaders')
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('processes http-stream routes (not skipped) and emits Yield/ReturnType slots', () => {
|
|
267
|
+
const route = {
|
|
268
|
+
kind: 'http-stream',
|
|
269
|
+
name: 'TailLogs',
|
|
270
|
+
method: 'GET',
|
|
271
|
+
fullPath: '/logs/tail',
|
|
272
|
+
jsonSchema: {
|
|
273
|
+
req: { query: { type: 'object' } },
|
|
274
|
+
yield: { type: 'object' },
|
|
275
|
+
returnType: { type: 'object' },
|
|
276
|
+
},
|
|
277
|
+
errors: [],
|
|
278
|
+
} as unknown as AnyHttpRouteDoc
|
|
279
|
+
|
|
280
|
+
const { emitter, calls } = makeSpyEmitter({
|
|
281
|
+
Query: ok('@Serializable data class Query(val filter: String? = null)', 'Query'),
|
|
282
|
+
Yield: ok('@Serializable data class Yield(val line: String)', 'Yield'),
|
|
283
|
+
ReturnType: ok('@Serializable data class ReturnType(val count: Long)', 'ReturnType'),
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
const result = emitKotlinRoute(route, emitter, noErrors)
|
|
287
|
+
expect(result.skipped).toBeFalsy()
|
|
288
|
+
expect(calls.map((c) => c.opts.rootTypeName)).toEqual(['Query', 'Yield', 'ReturnType'])
|
|
289
|
+
expect(result.code).toContain('const val method = "GET"')
|
|
290
|
+
expect(result.code).toContain('const val path = "/logs/tail"')
|
|
291
|
+
})
|
|
292
|
+
|
|
210
293
|
it('does not include passthrough keys when caller omits them', () => {
|
|
211
294
|
const route = {
|
|
212
295
|
kind: 'api',
|
|
213
296
|
name: 'X',
|
|
214
297
|
method: 'GET',
|
|
215
298
|
fullPath: '/x',
|
|
216
|
-
|
|
299
|
+
jsonSchema: { res: { body: { type: 'object' } } },
|
|
217
300
|
errors: [],
|
|
218
301
|
} as unknown as AnyHttpRouteDoc
|
|
219
302
|
|
|
@@ -18,7 +18,10 @@ describe('emitKotlinScope', () => {
|
|
|
18
18
|
name: 'GetUser',
|
|
19
19
|
method: 'GET',
|
|
20
20
|
fullPath: '/users/:id',
|
|
21
|
-
|
|
21
|
+
jsonSchema: {
|
|
22
|
+
req: { pathParams: { type: 'object' } },
|
|
23
|
+
res: { body: { type: 'object' } },
|
|
24
|
+
},
|
|
22
25
|
errors: [],
|
|
23
26
|
} as unknown as AnyHttpRouteDoc
|
|
24
27
|
|
|
@@ -40,8 +43,8 @@ describe('emitKotlinScope', () => {
|
|
|
40
43
|
})
|
|
41
44
|
|
|
42
45
|
it('joins multiple routes inside one scope object', () => {
|
|
43
|
-
const route1 = { kind: 'api', name: 'GetUser', method: 'GET', fullPath: '/users/:id',
|
|
44
|
-
const route2 = { kind: 'api', name: 'CreateUser', method: 'POST', fullPath: '/users',
|
|
46
|
+
const route1 = { kind: 'api', name: 'GetUser', method: 'GET', fullPath: '/users/:id', jsonSchema: {}, errors: [] } as unknown as AnyHttpRouteDoc
|
|
47
|
+
const route2 = { kind: 'api', name: 'CreateUser', method: 'POST', fullPath: '/users', jsonSchema: {}, errors: [] } as unknown as AnyHttpRouteDoc
|
|
45
48
|
const group: ScopeGroup = { scopeKey: 'users', camelCase: 'users', routes: [route1, route2] }
|
|
46
49
|
const emitter = createStubKotlinEmitter({})
|
|
47
50
|
|
|
@@ -60,7 +63,7 @@ describe('emitKotlinScope', () => {
|
|
|
60
63
|
})
|
|
61
64
|
|
|
62
65
|
it('threads all 5 passthrough opts to every emitter call', () => {
|
|
63
|
-
const route = { kind: 'api', name: 'X', method: 'GET', fullPath: '/x',
|
|
66
|
+
const route = { kind: 'api', name: 'X', method: 'GET', fullPath: '/x', jsonSchema: { res: { body: { type: 'object' } } }, errors: [] } as unknown as AnyHttpRouteDoc
|
|
64
67
|
const group: ScopeGroup = { scopeKey: 'x', camelCase: 'x', routes: [route] }
|
|
65
68
|
|
|
66
69
|
const calls: KotlinEmitOptions[] = []
|
|
@@ -96,8 +99,8 @@ describe('emitKotlinScope', () => {
|
|
|
96
99
|
})
|
|
97
100
|
|
|
98
101
|
it('collects skipped stream-route names', () => {
|
|
99
|
-
const stream = { kind: 'stream', name: 'WatchUsers', method: 'GET', path: '/u/stream',
|
|
100
|
-
const api = { kind: 'api', name: 'GetUser', method: 'GET', fullPath: '/u',
|
|
102
|
+
const stream = { kind: 'stream', name: 'WatchUsers', method: 'GET', path: '/u/stream', jsonSchema: {}, errors: [] } as unknown as AnyHttpRouteDoc
|
|
103
|
+
const api = { kind: 'api', name: 'GetUser', method: 'GET', fullPath: '/u', jsonSchema: { res: { body: { type: 'object' } } }, errors: [] } as unknown as AnyHttpRouteDoc
|
|
101
104
|
const group: ScopeGroup = { scopeKey: 'u', camelCase: 'u', routes: [stream, api] }
|
|
102
105
|
|
|
103
106
|
const emitter = createStubKotlinEmitter({
|
|
@@ -61,6 +61,25 @@ describe('kotlin codegen — integration', () => {
|
|
|
61
61
|
'Query',
|
|
62
62
|
['kotlinx.serialization.Serializable', 'kotlinx.serialization.SerialName'],
|
|
63
63
|
),
|
|
64
|
+
|
|
65
|
+
// DownloadUser
|
|
66
|
+
ResponseHeaders: ok(
|
|
67
|
+
'@Serializable\ndata class ResponseHeaders(\n @SerialName("x-download-token") val xDownloadToken: String,\n @SerialName("content-disposition") val contentDisposition: String? = null,\n)',
|
|
68
|
+
'ResponseHeaders',
|
|
69
|
+
['kotlinx.serialization.Serializable', 'kotlinx.serialization.SerialName'],
|
|
70
|
+
),
|
|
71
|
+
|
|
72
|
+
// WatchUsers (http-stream) — reuses Query and ResponseHeaders stubs from above;
|
|
73
|
+
// adds Yield and ReturnType which are unique to the stream route.
|
|
74
|
+
Yield: ok(
|
|
75
|
+
'@Serializable\ndata class Yield(\n val id: String,\n val event: Event,\n) {\n @Serializable\n enum class Event {\n @SerialName("created") CREATED,\n @SerialName("updated") UPDATED,\n @SerialName("deleted") DELETED,\n }\n}',
|
|
76
|
+
'Yield',
|
|
77
|
+
['kotlinx.serialization.Serializable', 'kotlinx.serialization.SerialName'],
|
|
78
|
+
),
|
|
79
|
+
ReturnType: ok(
|
|
80
|
+
'@Serializable\ndata class ReturnType(\n val count: Long,\n)',
|
|
81
|
+
'ReturnType',
|
|
82
|
+
),
|
|
64
83
|
})
|
|
65
84
|
|
|
66
85
|
const files = await runPipeline({
|
|
@@ -120,4 +120,83 @@ public enum Users {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
|
|
124
|
+
public enum DownloadUser {
|
|
125
|
+
public static let method = "GET"
|
|
126
|
+
public static let pathTemplate = "/users/{id}/download"
|
|
127
|
+
public static func path(_ p: PathParams) -> String { return "/users/\(p.id)/download" }
|
|
128
|
+
|
|
129
|
+
public struct PathParams: Codable {
|
|
130
|
+
public let id: String
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public struct Response: Codable {
|
|
134
|
+
public let id: String
|
|
135
|
+
public let name: String
|
|
136
|
+
public let createdAt: Date
|
|
137
|
+
public let address: Address
|
|
138
|
+
|
|
139
|
+
enum CodingKeys: String, CodingKey {
|
|
140
|
+
case id, name
|
|
141
|
+
case createdAt = "created-at"
|
|
142
|
+
case address
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public struct Address: Codable {
|
|
146
|
+
public let street: String
|
|
147
|
+
public let city: String
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public struct ResponseHeaders: Codable {
|
|
152
|
+
public let xDownloadToken: String
|
|
153
|
+
public let contentDisposition: String?
|
|
154
|
+
|
|
155
|
+
enum CodingKeys: String, CodingKey {
|
|
156
|
+
case xDownloadToken = "x-download-token"
|
|
157
|
+
case contentDisposition = "content-disposition"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public enum WatchUsers {
|
|
163
|
+
public static let method = "GET"
|
|
164
|
+
public static let pathTemplate = "/users/watch"
|
|
165
|
+
public static let path = "/users/watch"
|
|
166
|
+
|
|
167
|
+
public struct Query: Codable {
|
|
168
|
+
public let status: Status?
|
|
169
|
+
public let limit: Int64?
|
|
170
|
+
|
|
171
|
+
public enum Status: String, Codable {
|
|
172
|
+
case active
|
|
173
|
+
case inactive
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public struct ResponseHeaders: Codable {
|
|
178
|
+
public let xDownloadToken: String
|
|
179
|
+
public let contentDisposition: String?
|
|
180
|
+
|
|
181
|
+
enum CodingKeys: String, CodingKey {
|
|
182
|
+
case xDownloadToken = "x-download-token"
|
|
183
|
+
case contentDisposition = "content-disposition"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public struct Yield: Codable {
|
|
188
|
+
public let id: String
|
|
189
|
+
public let event: Event
|
|
190
|
+
|
|
191
|
+
public enum Event: String, Codable {
|
|
192
|
+
case created
|
|
193
|
+
case updated
|
|
194
|
+
case deleted
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public struct ReturnType: Codable {
|
|
199
|
+
public let count: Int64
|
|
200
|
+
}
|
|
201
|
+
}
|
|
123
202
|
}
|