ts-procedures 7.3.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 +104 -53
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +205 -232
- 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 +34 -48
- package/agent_config/cursor/cursorrules +34 -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 +124 -124
- package/build/index.js +10 -221
- package/build/index.js.map +1 -1
- package/build/index.test.js +20 -919
- 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 +15 -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 +22 -1249
- package/src/index.ts +49 -485
- 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
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import { describe, expect, it, test } from 'vitest'
|
|
3
|
+
import { Procedures } from './index.js'
|
|
4
|
+
import { v } from 'suretype'
|
|
5
|
+
import { Type } from 'typebox'
|
|
6
|
+
import { ProcedureError, ProcedureValidationError } from './errors.js'
|
|
7
|
+
|
|
8
|
+
describe('Streaming Procedures - CreateStream', () => {
|
|
9
|
+
test('CreateStream creates a streaming procedure', async () => {
|
|
10
|
+
const { CreateStream } = Procedures()
|
|
11
|
+
|
|
12
|
+
const { StreamTest, procedure, info } = CreateStream(
|
|
13
|
+
'StreamTest',
|
|
14
|
+
{
|
|
15
|
+
description: 'Test streaming procedure',
|
|
16
|
+
schema: {
|
|
17
|
+
params: v.object({ count: v.number().required() }),
|
|
18
|
+
yieldType: v.object({ value: v.number().required() }),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
async function* (ctx, params) {
|
|
22
|
+
for (let i = 0; i < params.count; i++) {
|
|
23
|
+
yield { value: i }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
expect(StreamTest).toBeDefined()
|
|
29
|
+
expect(procedure).toBeDefined()
|
|
30
|
+
expect(info.isStream).toBe(true)
|
|
31
|
+
expect(info.name).toBe('StreamTest')
|
|
32
|
+
expect(info.description).toBe('Test streaming procedure')
|
|
33
|
+
expect(info.schema.params).toEqual({
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: { count: { type: 'number' } },
|
|
36
|
+
required: ['count'],
|
|
37
|
+
})
|
|
38
|
+
expect(info.schema.yieldType).toEqual({
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: { value: { type: 'number' } },
|
|
41
|
+
required: ['value'],
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Collect yielded values
|
|
45
|
+
const values: { value: number }[] = []
|
|
46
|
+
for await (const val of StreamTest({}, { count: 3 })) {
|
|
47
|
+
values.push(val)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
expect(values).toEqual([{ value: 0 }, { value: 1 }, { value: 2 }])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('CreateStream validates params', async () => {
|
|
54
|
+
const { CreateStream } = Procedures()
|
|
55
|
+
|
|
56
|
+
const { StreamWithParams } = CreateStream(
|
|
57
|
+
'StreamWithParams',
|
|
58
|
+
{
|
|
59
|
+
schema: {
|
|
60
|
+
params: v.object({ name: v.string().required() }),
|
|
61
|
+
yieldType: v.string(),
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
async function* (ctx, params) {
|
|
65
|
+
yield params.name
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
// Missing required param should throw ProcedureValidationError
|
|
70
|
+
try {
|
|
71
|
+
// @ts-expect-error - intentionally passing invalid params
|
|
72
|
+
for await (const _val of StreamWithParams({}, {})) {
|
|
73
|
+
// Should not reach here
|
|
74
|
+
}
|
|
75
|
+
expect.fail('Should have thrown')
|
|
76
|
+
} catch (e: any) {
|
|
77
|
+
expect(e).toBeInstanceOf(ProcedureValidationError)
|
|
78
|
+
expect(e.message).toContain('Validation error for StreamWithParams')
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('CreateStream with validateYields validates each yielded value', async () => {
|
|
83
|
+
const { CreateStream } = Procedures()
|
|
84
|
+
const { ProcedureYieldValidationError } = await import('./errors.js')
|
|
85
|
+
|
|
86
|
+
const { StreamValidateYields } = CreateStream(
|
|
87
|
+
'StreamValidateYields',
|
|
88
|
+
{
|
|
89
|
+
schema: {
|
|
90
|
+
yieldType: v.object({ id: v.number().required() }),
|
|
91
|
+
},
|
|
92
|
+
validateYields: true,
|
|
93
|
+
},
|
|
94
|
+
async function* () {
|
|
95
|
+
yield { id: 1 } // Valid
|
|
96
|
+
yield { id: 'not-a-number' } as any // Invalid - should throw
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const values: any[] = []
|
|
101
|
+
try {
|
|
102
|
+
for await (const val of StreamValidateYields({}, {})) {
|
|
103
|
+
values.push(val)
|
|
104
|
+
}
|
|
105
|
+
expect.fail('Should have thrown on invalid yield')
|
|
106
|
+
} catch (e: any) {
|
|
107
|
+
expect(e).toBeInstanceOf(ProcedureYieldValidationError)
|
|
108
|
+
expect(e.message).toContain('Yield validation error for StreamValidateYields')
|
|
109
|
+
expect(values).toEqual([{ id: 1 }]) // First value was valid
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('CreateStream without validateYields does not validate yields', async () => {
|
|
114
|
+
const { CreateStream } = Procedures()
|
|
115
|
+
|
|
116
|
+
const { StreamNoValidate } = CreateStream(
|
|
117
|
+
'StreamNoValidate',
|
|
118
|
+
{
|
|
119
|
+
schema: {
|
|
120
|
+
yieldType: v.object({ id: v.number().required() }),
|
|
121
|
+
},
|
|
122
|
+
// validateYields defaults to false
|
|
123
|
+
},
|
|
124
|
+
async function* () {
|
|
125
|
+
yield { id: 1 }
|
|
126
|
+
yield { id: 'not-a-number' } as any // Invalid but won't throw
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const values: any[] = []
|
|
131
|
+
for await (const val of StreamNoValidate({}, {})) {
|
|
132
|
+
values.push(val)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
expect(values).toEqual([{ id: 1 }, { id: 'not-a-number' }])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('CreateStream ctx.error throws ProcedureError', async () => {
|
|
139
|
+
const { CreateStream } = Procedures()
|
|
140
|
+
|
|
141
|
+
const { StreamError } = CreateStream('StreamError', {}, async function* (ctx) {
|
|
142
|
+
yield 'first'
|
|
143
|
+
throw ctx.error('Custom stream error', { code: 'STREAM_ERR' })
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const values: any[] = []
|
|
147
|
+
try {
|
|
148
|
+
for await (const val of StreamError({}, {})) {
|
|
149
|
+
values.push(val)
|
|
150
|
+
}
|
|
151
|
+
expect.fail('Should have thrown')
|
|
152
|
+
} catch (e: any) {
|
|
153
|
+
expect(e).toBeInstanceOf(ProcedureError)
|
|
154
|
+
expect(e.message).toBe('Custom stream error')
|
|
155
|
+
expect(e.procedureName).toBe('StreamError')
|
|
156
|
+
expect(e.meta).toEqual({ code: 'STREAM_ERR' })
|
|
157
|
+
expect(values).toEqual(['first'])
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('CreateStream provides ctx.signal for abort handling', async () => {
|
|
162
|
+
const { CreateStream } = Procedures()
|
|
163
|
+
let signalReceived = false
|
|
164
|
+
|
|
165
|
+
const { StreamWithSignal } = CreateStream('StreamWithSignal', {}, async function* (ctx) {
|
|
166
|
+
expect(ctx.signal).toBeDefined()
|
|
167
|
+
expect(ctx.signal).toBeInstanceOf(AbortSignal)
|
|
168
|
+
signalReceived = true
|
|
169
|
+
yield 'value'
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
for await (const _val of StreamWithSignal({}, {})) {
|
|
173
|
+
// consume
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
expect(signalReceived).toBe(true)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test('CreateStream onCreate callback receives isStream flag', () =>
|
|
180
|
+
new Promise<void>((done) => {
|
|
181
|
+
let receivedProc: any
|
|
182
|
+
|
|
183
|
+
const { CreateStream } = Procedures({
|
|
184
|
+
onCreate: (proc) => {
|
|
185
|
+
receivedProc = proc
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
CreateStream('OnCreateStream', {}, async function* () {
|
|
190
|
+
yield 'test'
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
expect(receivedProc).toBeDefined()
|
|
194
|
+
expect(receivedProc.isStream).toBe(true)
|
|
195
|
+
expect(receivedProc.name).toBe('OnCreateStream')
|
|
196
|
+
done()
|
|
197
|
+
}))
|
|
198
|
+
|
|
199
|
+
test('CreateStream duplicate registration throws', () => {
|
|
200
|
+
const { CreateStream } = Procedures()
|
|
201
|
+
|
|
202
|
+
CreateStream('DuplicateStream', {}, async function* () {
|
|
203
|
+
yield 'first'
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
expect(() => {
|
|
207
|
+
CreateStream('DuplicateStream', {}, async function* () {
|
|
208
|
+
yield 'second'
|
|
209
|
+
})
|
|
210
|
+
}).toThrow('Procedure with name DuplicateStream is already registered')
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
test('CreateStream error includes definition location', async () => {
|
|
214
|
+
const { CreateStream } = Procedures()
|
|
215
|
+
|
|
216
|
+
const { StreamErrorLocation } = CreateStream(
|
|
217
|
+
'StreamErrorLocation',
|
|
218
|
+
{
|
|
219
|
+
schema: {
|
|
220
|
+
params: v.object({ id: v.number().required() }),
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
async function* () {
|
|
224
|
+
yield 'test'
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
// @ts-expect-error - intentionally passing invalid params
|
|
230
|
+
for await (const _val of StreamErrorLocation({}, {})) {
|
|
231
|
+
// consume
|
|
232
|
+
}
|
|
233
|
+
expect.fail('Should have thrown')
|
|
234
|
+
} catch (e: any) {
|
|
235
|
+
expect(e.definedAt).toBeDefined()
|
|
236
|
+
expect(e.definedAt.file).toContain('create-stream.test.ts')
|
|
237
|
+
expect(e.stack).toContain('--- Procedure "StreamErrorLocation" defined at ---')
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test('CreateStream with Typebox schema', async () => {
|
|
242
|
+
const { CreateStream } = Procedures()
|
|
243
|
+
|
|
244
|
+
const { TypeboxStream } = CreateStream(
|
|
245
|
+
'TypeboxStream',
|
|
246
|
+
{
|
|
247
|
+
schema: {
|
|
248
|
+
params: Type.Object({ limit: Type.Number() }),
|
|
249
|
+
yieldType: Type.Object({ data: Type.String() }),
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
async function* (ctx, params) {
|
|
253
|
+
for (let i = 0; i < params.limit; i++) {
|
|
254
|
+
yield { data: `item-${i}` }
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
const values: any[] = []
|
|
260
|
+
for await (const val of TypeboxStream({}, { limit: 2 })) {
|
|
261
|
+
values.push(val)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
expect(values).toEqual([{ data: 'item-0' }, { data: 'item-1' }])
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
test('CreateStream with context type', async () => {
|
|
268
|
+
interface StreamContext {
|
|
269
|
+
userId: string
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const { CreateStream } = Procedures<StreamContext>()
|
|
273
|
+
|
|
274
|
+
const { ContextStream } = CreateStream('ContextStream', {}, async function* (ctx) {
|
|
275
|
+
// ctx should have both userId and error
|
|
276
|
+
expect(ctx.userId).toBe('user-123')
|
|
277
|
+
expect(ctx.error).toBeDefined()
|
|
278
|
+
expect(ctx.signal).toBeDefined()
|
|
279
|
+
yield ctx.userId
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
const values: any[] = []
|
|
283
|
+
for await (const val of ContextStream({ userId: 'user-123' }, {})) {
|
|
284
|
+
values.push(val)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
expect(values).toEqual(['user-123'])
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
test('CreateStream rethrows the original error preserving class identity', async () => {
|
|
291
|
+
// The streaming wrapper must NOT box user errors inside ProcedureError —
|
|
292
|
+
// doing so would defeat route-declared typed-error dispatch on the client
|
|
293
|
+
// (the HTTP builder's taxonomy would see `ProcedureError` instead of the
|
|
294
|
+
// user's class). Stack annotation is added in place; class identity and
|
|
295
|
+
// custom properties are preserved.
|
|
296
|
+
class MyDomainError extends Error {
|
|
297
|
+
readonly name = 'MyDomainError'
|
|
298
|
+
readonly code = 'STREAM_FAIL'
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const { CreateStream } = Procedures()
|
|
302
|
+
const originalError = new MyDomainError('Stream underlying error')
|
|
303
|
+
|
|
304
|
+
const { StreamCause } = CreateStream(
|
|
305
|
+
'StreamCause',
|
|
306
|
+
{},
|
|
307
|
+
// eslint-disable-next-line require-yield
|
|
308
|
+
async function* () {
|
|
309
|
+
throw originalError
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
for await (const _val of StreamCause({}, {})) {
|
|
315
|
+
// consume
|
|
316
|
+
}
|
|
317
|
+
expect.fail('Should have thrown')
|
|
318
|
+
} catch (e: any) {
|
|
319
|
+
expect(e).toBe(originalError)
|
|
320
|
+
expect(e).toBeInstanceOf(MyDomainError)
|
|
321
|
+
expect(e.code).toBe('STREAM_FAIL')
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
test('CreateStream propagates .return() to the user generator', async () => {
|
|
326
|
+
// Consumers that close a stream early (via `iterator.return()` or breaking
|
|
327
|
+
// out of for-await) must trigger the user generator's `finally` block so
|
|
328
|
+
// cleanup (db handles, subscriptions, signal-driven teardown) runs.
|
|
329
|
+
const { CreateStream } = Procedures()
|
|
330
|
+
let finallyRan = false
|
|
331
|
+
|
|
332
|
+
const { EarlyClose } = CreateStream(
|
|
333
|
+
'EarlyClose',
|
|
334
|
+
{},
|
|
335
|
+
async function* () {
|
|
336
|
+
try {
|
|
337
|
+
yield 1
|
|
338
|
+
yield 2
|
|
339
|
+
yield 3
|
|
340
|
+
} finally {
|
|
341
|
+
finallyRan = true
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
const iter = EarlyClose({}, {})
|
|
347
|
+
const first = await iter.next()
|
|
348
|
+
expect(first.value).toBe(1)
|
|
349
|
+
await iter.return!(undefined)
|
|
350
|
+
expect(finallyRan).toBe(true)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
test('CreateStream with extended config', () => {
|
|
354
|
+
interface ExtConfig {
|
|
355
|
+
scope: string
|
|
356
|
+
version: number
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const { CreateStream } = Procedures<unknown, ExtConfig>()
|
|
360
|
+
|
|
361
|
+
const { info } = CreateStream(
|
|
362
|
+
'ExtendedStream',
|
|
363
|
+
{
|
|
364
|
+
scope: 'api',
|
|
365
|
+
version: 1,
|
|
366
|
+
description: 'Extended config stream',
|
|
367
|
+
},
|
|
368
|
+
async function* () {
|
|
369
|
+
yield 'data'
|
|
370
|
+
}
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
expect(info.scope).toBe('api')
|
|
374
|
+
expect(info.version).toBe(1)
|
|
375
|
+
expect(info.description).toBe('Extended config stream')
|
|
376
|
+
expect(info.isStream).toBe(true)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
test('CreateStream signal.aborted becomes true after generator completes', async () => {
|
|
380
|
+
const { CreateStream } = Procedures()
|
|
381
|
+
let capturedSignal: AbortSignal | null = null
|
|
382
|
+
|
|
383
|
+
const { AbortStream } = CreateStream('AbortStream', {}, async function* (ctx) {
|
|
384
|
+
capturedSignal = ctx.signal
|
|
385
|
+
yield 'value'
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// Consume the generator
|
|
389
|
+
for await (const _val of AbortStream({}, {})) {
|
|
390
|
+
// consume
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// After generator completes, signal should be aborted
|
|
394
|
+
expect(capturedSignal).not.toBeNull()
|
|
395
|
+
expect(capturedSignal!.aborted).toBe(true)
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
test('CreateStream signal.reason is stream-completed after normal completion', async () => {
|
|
399
|
+
const { CreateStream } = Procedures()
|
|
400
|
+
let capturedSignal: AbortSignal | null = null
|
|
401
|
+
|
|
402
|
+
const { ReasonStream } = CreateStream('ReasonStream', {}, async function* (ctx) {
|
|
403
|
+
capturedSignal = ctx.signal
|
|
404
|
+
yield 'value'
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
for await (const _val of ReasonStream({}, {})) {
|
|
408
|
+
// consume
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
expect(capturedSignal!.reason).toBe('stream-completed')
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
test('CreateStream combines external signal with internal signal', async () => {
|
|
415
|
+
const { CreateStream } = Procedures<{ signal: AbortSignal }>()
|
|
416
|
+
const externalAc = new AbortController()
|
|
417
|
+
let capturedSignal: AbortSignal | null = null
|
|
418
|
+
|
|
419
|
+
const { CombinedStream } = CreateStream('CombinedStream', {}, async function* (ctx) {
|
|
420
|
+
capturedSignal = ctx.signal
|
|
421
|
+
// Combined signal is a new object, not the raw external signal
|
|
422
|
+
expect(ctx.signal).not.toBe(externalAc.signal)
|
|
423
|
+
yield 'value'
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
// Abort external before consuming — combined signal should reflect it
|
|
427
|
+
externalAc.abort('client-disconnected')
|
|
428
|
+
|
|
429
|
+
for await (const _val of CombinedStream({ signal: externalAc.signal }, {})) {
|
|
430
|
+
// consume
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
expect(capturedSignal!.aborted).toBe(true)
|
|
434
|
+
// Reason comes from external abort, not internal 'stream-completed'
|
|
435
|
+
expect(capturedSignal!.reason).toBe('client-disconnected')
|
|
436
|
+
})
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
describe('isPrevalidated context property', () => {
|
|
440
|
+
test('CreateStream skips validation when ctx.isPrevalidated is true', async () => {
|
|
441
|
+
const { CreateStream } = Procedures()
|
|
442
|
+
|
|
443
|
+
const { StreamSkipValidation } = CreateStream(
|
|
444
|
+
'StreamSkipValidation',
|
|
445
|
+
{
|
|
446
|
+
schema: {
|
|
447
|
+
params: v.object({ count: v.number().required() }),
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
async function* (ctx, params) {
|
|
451
|
+
yield { received: params }
|
|
452
|
+
}
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
// With isPrevalidated: true, validation is skipped even with invalid params
|
|
456
|
+
const values: any[] = []
|
|
457
|
+
for await (const val of StreamSkipValidation({ isPrevalidated: true }, {} as any)) {
|
|
458
|
+
values.push(val)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
expect(values).toEqual([{ received: {} }])
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
test('CreateStream validates when ctx.isPrevalidated is false', async () => {
|
|
465
|
+
const { CreateStream } = Procedures()
|
|
466
|
+
|
|
467
|
+
const { StreamValidate } = CreateStream(
|
|
468
|
+
'StreamValidate',
|
|
469
|
+
{
|
|
470
|
+
schema: {
|
|
471
|
+
params: v.object({ count: v.number().required() }),
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
async function* (ctx, params) {
|
|
475
|
+
yield { received: params }
|
|
476
|
+
}
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
// With isPrevalidated: false, validation should run
|
|
480
|
+
try {
|
|
481
|
+
for await (const _val of StreamValidate({ isPrevalidated: false }, {} as any)) {
|
|
482
|
+
// consume
|
|
483
|
+
}
|
|
484
|
+
expect.fail('Should have thrown validation error')
|
|
485
|
+
} catch (e: any) {
|
|
486
|
+
expect(e).toBeInstanceOf(ProcedureValidationError)
|
|
487
|
+
}
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
test('CreateStream validates when ctx.isPrevalidated is undefined', async () => {
|
|
491
|
+
const { CreateStream } = Procedures()
|
|
492
|
+
|
|
493
|
+
const { StreamValidateUndefined } = CreateStream(
|
|
494
|
+
'StreamValidateUndefined',
|
|
495
|
+
{
|
|
496
|
+
schema: {
|
|
497
|
+
params: v.object({ value: v.string().required() }),
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
async function* (ctx, params) {
|
|
501
|
+
yield params
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
// Without isPrevalidated property, validation should run
|
|
506
|
+
try {
|
|
507
|
+
for await (const _val of StreamValidateUndefined({}, {} as any)) {
|
|
508
|
+
// consume
|
|
509
|
+
}
|
|
510
|
+
expect.fail('Should have thrown validation error')
|
|
511
|
+
} catch (e: any) {
|
|
512
|
+
expect(e).toBeInstanceOf(ProcedureValidationError)
|
|
513
|
+
}
|
|
514
|
+
})
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
describe('builder.config.noRuntimeValidation', () => {
|
|
518
|
+
test('CreateStream skips params validation when noRuntimeValidation is true', async () => {
|
|
519
|
+
const { CreateStream } = Procedures({ config: { noRuntimeValidation: true } })
|
|
520
|
+
|
|
521
|
+
const { StreamSkipParams } = CreateStream(
|
|
522
|
+
'StreamSkipParams',
|
|
523
|
+
{
|
|
524
|
+
schema: {
|
|
525
|
+
params: Type.Object({ count: Type.Number() }),
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
async function* (_ctx, params) {
|
|
529
|
+
yield { received: params }
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
const values: any[] = []
|
|
534
|
+
for await (const val of StreamSkipParams({}, {} as any)) {
|
|
535
|
+
values.push(val)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
expect(values).toEqual([{ received: {} }])
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
// migrated to schema.req in Phase 3+ (CreateHttpStream)
|
|
542
|
+
test.todo('CreateStream skips input channel validation when noRuntimeValidation is true')
|
|
543
|
+
|
|
544
|
+
test('CreateStream still validates when noRuntimeValidation is omitted', async () => {
|
|
545
|
+
const { CreateStream } = Procedures({ config: {} })
|
|
546
|
+
|
|
547
|
+
const { StreamValidateDefault } = CreateStream(
|
|
548
|
+
'StreamValidateDefault',
|
|
549
|
+
{
|
|
550
|
+
schema: {
|
|
551
|
+
params: Type.Object({ count: Type.Number() }),
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
async function* (_ctx, params) {
|
|
555
|
+
yield params
|
|
556
|
+
}
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
await expect(async () => {
|
|
560
|
+
for await (const _v of StreamValidateDefault({}, {} as any)) {
|
|
561
|
+
// consume
|
|
562
|
+
}
|
|
563
|
+
}).rejects.toBeInstanceOf(ProcedureValidationError)
|
|
564
|
+
})
|
|
565
|
+
})
|