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
package/src/index.test.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import { describe, expect, it, test } from 'vitest'
|
|
3
3
|
import { Procedures } from './index.js'
|
|
4
4
|
import { v } from 'suretype'
|
|
5
5
|
import { Type } from 'typebox'
|
|
6
|
-
import { ProcedureError, ProcedureValidationError } from './errors.js'
|
|
7
6
|
|
|
8
7
|
describe('Procedures', () => {
|
|
9
8
|
test('Procedures', () => {
|
|
@@ -16,265 +15,6 @@ describe('Procedures', () => {
|
|
|
16
15
|
expect(result).toHaveProperty('Create')
|
|
17
16
|
})
|
|
18
17
|
|
|
19
|
-
test('Procedures generic context & extended config', () => {
|
|
20
|
-
interface CustomContext {
|
|
21
|
-
authToken: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface ExtendedConfig {
|
|
25
|
-
customProp: string
|
|
26
|
-
optionalProp?: number
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const { Create } = Procedures<CustomContext, ExtendedConfig>()
|
|
30
|
-
|
|
31
|
-
const { info } = Create(
|
|
32
|
-
'TestProcedure',
|
|
33
|
-
{
|
|
34
|
-
// should not throw type errors
|
|
35
|
-
customProp: 'customProp',
|
|
36
|
-
},
|
|
37
|
-
async (ctx) => {
|
|
38
|
-
// should not throw type errors
|
|
39
|
-
return ctx.authToken
|
|
40
|
-
}
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
expect(info.customProp).toEqual('customProp')
|
|
44
|
-
expect(info.optionalProp).toEqual(undefined)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('Create Single Procedures', () => {
|
|
48
|
-
const { procedure: procedure1, info: info1 } = Procedures().Create('test1', {}, async () => {
|
|
49
|
-
return '1'
|
|
50
|
-
})
|
|
51
|
-
const { procedure: procedure2, info: info2 } = Procedures().Create('test2', {}, async () => {
|
|
52
|
-
return '2'
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
expect(procedure1).toBeDefined()
|
|
56
|
-
expect(info1).toBeDefined()
|
|
57
|
-
expect(procedure2).toBeDefined()
|
|
58
|
-
expect(info2).toBeDefined()
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('Procedures - Create call', () =>
|
|
62
|
-
new Promise<void>((done) => {
|
|
63
|
-
let mockHttpCall: any
|
|
64
|
-
|
|
65
|
-
const { Create } = Procedures({
|
|
66
|
-
onCreate: ({ handler }) => {
|
|
67
|
-
mockHttpCall = handler
|
|
68
|
-
},
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
Create(
|
|
72
|
-
'Handler',
|
|
73
|
-
{
|
|
74
|
-
schema: {
|
|
75
|
-
params: v.object({ name: v.string() }),
|
|
76
|
-
returnType: v.string(),
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
async (ctx, params) => {
|
|
80
|
-
expect(params).toEqual({ name: 'name' })
|
|
81
|
-
done()
|
|
82
|
-
return 'name'
|
|
83
|
-
}
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
mockHttpCall({}, { name: 'name' })
|
|
87
|
-
}))
|
|
88
|
-
|
|
89
|
-
test('Procedures - Create call w/ Typebox', () =>
|
|
90
|
-
new Promise<void>((done) => {
|
|
91
|
-
let mockHttpCall: any
|
|
92
|
-
|
|
93
|
-
const { Create } = Procedures({
|
|
94
|
-
onCreate: ({ handler, config, name }) => {
|
|
95
|
-
mockHttpCall = handler
|
|
96
|
-
},
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
Create(
|
|
100
|
-
'Handler',
|
|
101
|
-
{
|
|
102
|
-
schema: {
|
|
103
|
-
params: Type.Object({ name: Type.Optional(Type.String()) }),
|
|
104
|
-
returnType: Type.String(),
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
async (ctx, params) => {
|
|
108
|
-
expect(params).toEqual({ name: 'name' })
|
|
109
|
-
done()
|
|
110
|
-
return 'name'
|
|
111
|
-
}
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
mockHttpCall({}, { name: 'name' })
|
|
115
|
-
}))
|
|
116
|
-
|
|
117
|
-
test('Procedures - Create returns a handler to call/test the Procedure registration', async () => {
|
|
118
|
-
let ProcedureRegisteredCbHandler: any
|
|
119
|
-
|
|
120
|
-
const { Create } = Procedures({
|
|
121
|
-
onCreate: (Procedure) => {
|
|
122
|
-
ProcedureRegisteredCbHandler = Procedure.handler
|
|
123
|
-
},
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
const { NamedExportHandler, procedure, info } = Create(
|
|
127
|
-
'NamedExportHandler',
|
|
128
|
-
{
|
|
129
|
-
description: 'Handler description',
|
|
130
|
-
schema: {
|
|
131
|
-
params: v.object({ number: v.number() }),
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
async (ctx, params) => {
|
|
135
|
-
return params.number
|
|
136
|
-
}
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
expect(NamedExportHandler).toBeDefined()
|
|
140
|
-
expect(procedure).toBeDefined()
|
|
141
|
-
expect(ProcedureRegisteredCbHandler).toEqual(NamedExportHandler)
|
|
142
|
-
expect(ProcedureRegisteredCbHandler).toEqual(procedure)
|
|
143
|
-
|
|
144
|
-
const result = NamedExportHandler({}, { number: 1 })
|
|
145
|
-
|
|
146
|
-
expect(result).toBeDefined()
|
|
147
|
-
expect(result).toBeInstanceOf(Promise)
|
|
148
|
-
await expect(result).resolves.toEqual(1)
|
|
149
|
-
|
|
150
|
-
expect(info).toBeDefined()
|
|
151
|
-
expect(info).toBeInstanceOf(Object)
|
|
152
|
-
expect(info.schema).toHaveProperty('params')
|
|
153
|
-
expect(info.schema.params).toEqual({
|
|
154
|
-
type: 'object',
|
|
155
|
-
properties: { number: { type: 'number' } },
|
|
156
|
-
})
|
|
157
|
-
expect(info).toHaveProperty('description')
|
|
158
|
-
expect(info.description).toEqual('Handler description')
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
test('Procedures - Create params validation w/ no params provided', () =>
|
|
162
|
-
new Promise<void>((done) => {
|
|
163
|
-
let mockHttpCall: any
|
|
164
|
-
|
|
165
|
-
const { Create } = Procedures({
|
|
166
|
-
onCreate: ({ handler, config, name }) => {
|
|
167
|
-
mockHttpCall = (callParams: any) => {
|
|
168
|
-
if (config.validation?.params) {
|
|
169
|
-
const { errors } = config.validation.params(callParams)
|
|
170
|
-
|
|
171
|
-
if (errors && 'message' in errors[0]) {
|
|
172
|
-
expect(errors[0].message).toEqual('must be object')
|
|
173
|
-
done()
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
handler(callParams, {})
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
Create(
|
|
184
|
-
'test',
|
|
185
|
-
{
|
|
186
|
-
schema: {
|
|
187
|
-
params: v.object({}),
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
async () => {
|
|
191
|
-
done()
|
|
192
|
-
}
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
mockHttpCall()
|
|
196
|
-
}))
|
|
197
|
-
|
|
198
|
-
test('Procedures - Create params validation w/ missing params', async () =>
|
|
199
|
-
new Promise<void>((done) => {
|
|
200
|
-
let mockHttpCall: any
|
|
201
|
-
|
|
202
|
-
const { Create } = Procedures({
|
|
203
|
-
onCreate: async ({ handler, config, name }) => {
|
|
204
|
-
mockHttpCall = async (callParams: any) => {
|
|
205
|
-
if (config.validation?.params) {
|
|
206
|
-
const { errors } = config.validation.params(callParams)
|
|
207
|
-
expect(errors).toBeDefined()
|
|
208
|
-
expect(errors?.length).toEqual(2)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
await handler(callParams, {})
|
|
213
|
-
} catch (e: any) {
|
|
214
|
-
expect(e).instanceof(ProcedureValidationError)
|
|
215
|
-
expect(e.errors.length).toEqual(2)
|
|
216
|
-
done()
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
Create(
|
|
223
|
-
'test',
|
|
224
|
-
{
|
|
225
|
-
schema: {
|
|
226
|
-
params: v.object({
|
|
227
|
-
name: v.string().required(),
|
|
228
|
-
id: v.number().required(),
|
|
229
|
-
email: v.string(),
|
|
230
|
-
}),
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
async () => {
|
|
234
|
-
return
|
|
235
|
-
}
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
mockHttpCall({})
|
|
239
|
-
}))
|
|
240
|
-
|
|
241
|
-
test('Procedures - Create call provides ctx to handler', () =>
|
|
242
|
-
new Promise<void>((done) => {
|
|
243
|
-
let mockHttpCall: any
|
|
244
|
-
|
|
245
|
-
const { Create } = Procedures<{
|
|
246
|
-
testCtx: string
|
|
247
|
-
}>({
|
|
248
|
-
onCreate: ({ handler }) => {
|
|
249
|
-
mockHttpCall = () => handler({ testCtx: 'testCtx' })
|
|
250
|
-
},
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
Create('test', {}, async (ctx, params) => {
|
|
254
|
-
expect(ctx.testCtx).toEqual('testCtx')
|
|
255
|
-
done()
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
mockHttpCall()
|
|
259
|
-
}))
|
|
260
|
-
|
|
261
|
-
test('Procedure handler can throw local ctx error and is caught', async () => {
|
|
262
|
-
const { Create } = Procedures()
|
|
263
|
-
|
|
264
|
-
const { TestProcedureHandlerError } = Create('TestProcedureHandlerError', {}, async (ctx) => {
|
|
265
|
-
throw ctx.error('Local context error')
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
await TestProcedureHandlerError({}, {})
|
|
270
|
-
} catch (e: any) {
|
|
271
|
-
expect(e).toBeInstanceOf(ProcedureError)
|
|
272
|
-
|
|
273
|
-
expect(e.message).toEqual('Local context error')
|
|
274
|
-
expect(e.procedureName).toEqual('TestProcedureHandlerError')
|
|
275
|
-
}
|
|
276
|
-
})
|
|
277
|
-
|
|
278
18
|
test('Procedures - getRegisteredProcedures', () => {
|
|
279
19
|
const { Create, getProcedures } = Procedures({
|
|
280
20
|
onCreate: () => {
|
|
@@ -314,41 +54,6 @@ describe('Procedures', () => {
|
|
|
314
54
|
})
|
|
315
55
|
})
|
|
316
56
|
|
|
317
|
-
test('Procedures - context() throws', async () => {
|
|
318
|
-
interface CustomContext {
|
|
319
|
-
authToken: string
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const { Create } = Procedures<CustomContext>()
|
|
323
|
-
|
|
324
|
-
function validateAuthToken(token: string) {
|
|
325
|
-
return token === 'valid-token'
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const { CheckIsAuthenticated } = Create(
|
|
329
|
-
'CheckIsAuthenticated',
|
|
330
|
-
{
|
|
331
|
-
schema: {
|
|
332
|
-
returnType: v.string(),
|
|
333
|
-
},
|
|
334
|
-
},
|
|
335
|
-
async (ctx) => {
|
|
336
|
-
if (!validateAuthToken(ctx.authToken)) {
|
|
337
|
-
throw ctx.error('Invalid auth token')
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return 'User authentication is valid'
|
|
341
|
-
}
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
await expect(CheckIsAuthenticated({ authToken: 'valid-token' }, {})).resolves.toEqual(
|
|
345
|
-
'User authentication is valid'
|
|
346
|
-
)
|
|
347
|
-
await expect(CheckIsAuthenticated({ authToken: 'not-valid-token' }, {})).rejects.toThrowError(
|
|
348
|
-
ProcedureError
|
|
349
|
-
)
|
|
350
|
-
})
|
|
351
|
-
|
|
352
57
|
test('Procedures - duplicate registration throws before schema computation', () => {
|
|
353
58
|
const { Create } = Procedures()
|
|
354
59
|
|
|
@@ -359,24 +64,6 @@ describe('Procedures', () => {
|
|
|
359
64
|
}).toThrow('Procedure with name DuplicateTest is already registered')
|
|
360
65
|
})
|
|
361
66
|
|
|
362
|
-
test('Procedures - wrapped errors preserve cause', async () => {
|
|
363
|
-
const { Create } = Procedures()
|
|
364
|
-
const originalError = new Error('Database connection failed')
|
|
365
|
-
;(originalError as any).code = 'ECONNREFUSED'
|
|
366
|
-
|
|
367
|
-
const { TestCause } = Create('TestCause', {}, async () => {
|
|
368
|
-
throw originalError
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
try {
|
|
372
|
-
await TestCause({}, {})
|
|
373
|
-
} catch (e: any) {
|
|
374
|
-
expect(e).toBeInstanceOf(ProcedureError)
|
|
375
|
-
expect(e.cause).toBe(originalError)
|
|
376
|
-
expect(e.cause.code).toBe('ECONNREFUSED')
|
|
377
|
-
}
|
|
378
|
-
})
|
|
379
|
-
|
|
380
67
|
test('Procedures - getProcedure returns specific procedure', () => {
|
|
381
68
|
const { Create, getProcedure } = Procedures()
|
|
382
69
|
|
|
@@ -415,377 +102,7 @@ describe('Procedures', () => {
|
|
|
415
102
|
expect(getProcedures().length).toBe(0)
|
|
416
103
|
})
|
|
417
104
|
|
|
418
|
-
test('
|
|
419
|
-
const { Create } = Procedures()
|
|
420
|
-
|
|
421
|
-
const { ErrorTest } = Create('ErrorTest', {}, async (ctx) => {
|
|
422
|
-
throw ctx.error('Custom error message', { code: 'ERR_001' })
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
try {
|
|
426
|
-
await ErrorTest({}, {})
|
|
427
|
-
} catch (e: any) {
|
|
428
|
-
expect(e).toBeInstanceOf(ProcedureError)
|
|
429
|
-
expect(e.message).toBe('Custom error message')
|
|
430
|
-
expect(e.procedureName).toBe('ErrorTest')
|
|
431
|
-
expect(e.meta).toEqual({ code: 'ERR_001' })
|
|
432
|
-
}
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
test('Create passes through external signal from context', async () => {
|
|
436
|
-
const { Create } = Procedures<{ signal: AbortSignal }>()
|
|
437
|
-
const externalAc = new AbortController()
|
|
438
|
-
let capturedSignal: AbortSignal | null = null
|
|
439
|
-
|
|
440
|
-
const { WithSignal } = Create('WithSignal', {}, async (ctx) => {
|
|
441
|
-
capturedSignal = ctx.signal!
|
|
442
|
-
return 'done'
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
await WithSignal({ signal: externalAc.signal }, {})
|
|
446
|
-
expect(capturedSignal).toBe(externalAc.signal)
|
|
447
|
-
expect(capturedSignal!.aborted).toBe(false)
|
|
448
|
-
})
|
|
449
|
-
|
|
450
|
-
test('Create external signal reflects abort from caller', async () => {
|
|
451
|
-
const { Create } = Procedures<{ signal: AbortSignal }>()
|
|
452
|
-
const externalAc = new AbortController()
|
|
453
|
-
let capturedSignal: AbortSignal | null = null
|
|
454
|
-
|
|
455
|
-
const { AbortedSignal } = Create('AbortedSignal', {}, async (ctx) => {
|
|
456
|
-
capturedSignal = ctx.signal!
|
|
457
|
-
expect(ctx.signal!.aborted).toBe(true)
|
|
458
|
-
return 'done'
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
externalAc.abort()
|
|
462
|
-
await AbortedSignal({ signal: externalAc.signal }, {})
|
|
463
|
-
expect(capturedSignal!.aborted).toBe(true)
|
|
464
|
-
})
|
|
465
|
-
|
|
466
|
-
test('Create external signal cancels in-flight async work', async () => {
|
|
467
|
-
const { Create } = Procedures<{ signal: AbortSignal }>()
|
|
468
|
-
const externalAc = new AbortController()
|
|
469
|
-
let wasAbortedDuringWork = false
|
|
470
|
-
const ready = Promise.withResolvers<void>()
|
|
471
|
-
|
|
472
|
-
const { LongWork } = Create('LongWork', {}, async (ctx) => {
|
|
473
|
-
ready.resolve()
|
|
474
|
-
await new Promise<void>((resolve) => {
|
|
475
|
-
ctx.signal!.addEventListener('abort', () => {
|
|
476
|
-
wasAbortedDuringWork = true
|
|
477
|
-
resolve()
|
|
478
|
-
})
|
|
479
|
-
})
|
|
480
|
-
return 'done'
|
|
481
|
-
})
|
|
482
|
-
|
|
483
|
-
const p = LongWork({ signal: externalAc.signal }, {})
|
|
484
|
-
await ready.promise
|
|
485
|
-
externalAc.abort()
|
|
486
|
-
await p
|
|
487
|
-
expect(wasAbortedDuringWork).toBe(true)
|
|
488
|
-
})
|
|
489
|
-
})
|
|
490
|
-
|
|
491
|
-
describe('Procedures - Definition Location in Errors', () => {
|
|
492
|
-
test('ProcedureValidationError includes definition location', async () => {
|
|
493
|
-
const { Create } = Procedures()
|
|
494
|
-
|
|
495
|
-
const { TestValidation } = Create(
|
|
496
|
-
'TestValidation',
|
|
497
|
-
{
|
|
498
|
-
schema: {
|
|
499
|
-
params: v.object({ name: v.string().required() }),
|
|
500
|
-
},
|
|
501
|
-
},
|
|
502
|
-
async (ctx, params) => {
|
|
503
|
-
return params.name
|
|
504
|
-
}
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
try {
|
|
508
|
-
// @ts-expect-error - intentionally passing invalid params
|
|
509
|
-
await TestValidation({}, {}) // Missing required 'name' param
|
|
510
|
-
} catch (e: any) {
|
|
511
|
-
expect(e).toBeInstanceOf(ProcedureValidationError)
|
|
512
|
-
expect(e.definedAt).toBeDefined()
|
|
513
|
-
expect(e.definedAt.file).toContain('index.test.ts')
|
|
514
|
-
expect(e.definedAt.line).toBeGreaterThan(0)
|
|
515
|
-
expect(e.definedAt.column).toBeGreaterThan(0)
|
|
516
|
-
expect(e.stack).toContain('--- Procedure "TestValidation" defined at ---')
|
|
517
|
-
}
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
test('ctx.error() includes definition location', async () => {
|
|
521
|
-
const { Create } = Procedures()
|
|
522
|
-
|
|
523
|
-
const { TestCtxError } = Create('TestCtxError', {}, async (ctx) => {
|
|
524
|
-
throw ctx.error('Custom error')
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
await TestCtxError({}, {})
|
|
529
|
-
} catch (e: any) {
|
|
530
|
-
expect(e).toBeInstanceOf(ProcedureError)
|
|
531
|
-
expect(e.definedAt).toBeDefined()
|
|
532
|
-
expect(e.definedAt.file).toContain('index.test.ts')
|
|
533
|
-
expect(e.getDefinitionLocation()).toBeDefined()
|
|
534
|
-
expect(e.stack).toContain('--- Procedure "TestCtxError" defined at ---')
|
|
535
|
-
}
|
|
536
|
-
})
|
|
537
|
-
|
|
538
|
-
test('wrapped errors include definition location', async () => {
|
|
539
|
-
const { Create } = Procedures()
|
|
540
|
-
|
|
541
|
-
const { TestWrappedError } = Create('TestWrappedError', {}, async () => {
|
|
542
|
-
throw new Error('Original error')
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
try {
|
|
546
|
-
await TestWrappedError({}, {})
|
|
547
|
-
} catch (e: any) {
|
|
548
|
-
expect(e).toBeInstanceOf(ProcedureError)
|
|
549
|
-
expect(e.definedAt).toBeDefined()
|
|
550
|
-
expect(e.definedAt.file).toContain('index.test.ts')
|
|
551
|
-
expect(e.message).toContain('Error in handler for TestWrappedError')
|
|
552
|
-
expect(e.cause).toBeInstanceOf(Error)
|
|
553
|
-
expect(e.cause.message).toBe('Original error')
|
|
554
|
-
}
|
|
555
|
-
})
|
|
556
|
-
|
|
557
|
-
test('getDefinitionLocation returns formatted string', async () => {
|
|
558
|
-
const { Create } = Procedures()
|
|
559
|
-
|
|
560
|
-
const { TestGetLocation } = Create(
|
|
561
|
-
'TestGetLocation',
|
|
562
|
-
{
|
|
563
|
-
schema: {
|
|
564
|
-
params: v.object({ id: v.number().required() }),
|
|
565
|
-
},
|
|
566
|
-
},
|
|
567
|
-
async (ctx, params) => {
|
|
568
|
-
return params.id
|
|
569
|
-
}
|
|
570
|
-
)
|
|
571
|
-
|
|
572
|
-
try {
|
|
573
|
-
// @ts-expect-error - intentionally passing invalid params
|
|
574
|
-
await TestGetLocation({}, {}) // Missing required 'id' param
|
|
575
|
-
} catch (e: any) {
|
|
576
|
-
const location = e.getDefinitionLocation()
|
|
577
|
-
expect(location).toBeDefined()
|
|
578
|
-
expect(location).toMatch(/index\.test\.ts:\d+:\d+/)
|
|
579
|
-
}
|
|
580
|
-
})
|
|
581
|
-
|
|
582
|
-
test('error stack shows procedure definition location at the end', async () => {
|
|
583
|
-
const { Create } = Procedures()
|
|
584
|
-
|
|
585
|
-
const { TestStackFormat } = Create(
|
|
586
|
-
'TestStackFormat',
|
|
587
|
-
{
|
|
588
|
-
schema: {
|
|
589
|
-
params: v.object({ value: v.string().required() }),
|
|
590
|
-
},
|
|
591
|
-
},
|
|
592
|
-
async (ctx, params) => {
|
|
593
|
-
return params.value
|
|
594
|
-
}
|
|
595
|
-
)
|
|
596
|
-
|
|
597
|
-
try {
|
|
598
|
-
// @ts-expect-error - intentionally passing invalid params
|
|
599
|
-
await TestStackFormat({}, {})
|
|
600
|
-
} catch (e: any) {
|
|
601
|
-
// Verify it's a validation error
|
|
602
|
-
expect(e.name).toBe('ProcedureValidationError')
|
|
603
|
-
expect(e).toBeInstanceOf(ProcedureValidationError)
|
|
604
|
-
// Stack should contain the error message and definition location
|
|
605
|
-
expect(e.stack).toContain('Validation error for TestStackFormat')
|
|
606
|
-
expect(e.stack).toContain('--- Procedure "TestStackFormat" defined at ---')
|
|
607
|
-
// The definition section should be at the end of the stack
|
|
608
|
-
const stackLines = e.stack.split('\n')
|
|
609
|
-
const definitionIndex = stackLines.findIndex((line: string) =>
|
|
610
|
-
line.includes('--- Procedure "TestStackFormat" defined at ---')
|
|
611
|
-
)
|
|
612
|
-
expect(definitionIndex).toBeGreaterThan(0)
|
|
613
|
-
}
|
|
614
|
-
})
|
|
615
|
-
})
|
|
616
|
-
|
|
617
|
-
describe('Streaming Procedures - CreateStream', () => {
|
|
618
|
-
test('CreateStream creates a streaming procedure', async () => {
|
|
619
|
-
const { CreateStream } = Procedures()
|
|
620
|
-
|
|
621
|
-
const { StreamTest, procedure, info } = CreateStream(
|
|
622
|
-
'StreamTest',
|
|
623
|
-
{
|
|
624
|
-
description: 'Test streaming procedure',
|
|
625
|
-
schema: {
|
|
626
|
-
params: v.object({ count: v.number().required() }),
|
|
627
|
-
yieldType: v.object({ value: v.number().required() }),
|
|
628
|
-
},
|
|
629
|
-
},
|
|
630
|
-
async function* (ctx, params) {
|
|
631
|
-
for (let i = 0; i < params.count; i++) {
|
|
632
|
-
yield { value: i }
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
expect(StreamTest).toBeDefined()
|
|
638
|
-
expect(procedure).toBeDefined()
|
|
639
|
-
expect(info.isStream).toBe(true)
|
|
640
|
-
expect(info.name).toBe('StreamTest')
|
|
641
|
-
expect(info.description).toBe('Test streaming procedure')
|
|
642
|
-
expect(info.schema.params).toEqual({
|
|
643
|
-
type: 'object',
|
|
644
|
-
properties: { count: { type: 'number' } },
|
|
645
|
-
required: ['count'],
|
|
646
|
-
})
|
|
647
|
-
expect(info.schema.yieldType).toEqual({
|
|
648
|
-
type: 'object',
|
|
649
|
-
properties: { value: { type: 'number' } },
|
|
650
|
-
required: ['value'],
|
|
651
|
-
})
|
|
652
|
-
|
|
653
|
-
// Collect yielded values
|
|
654
|
-
const values: { value: number }[] = []
|
|
655
|
-
for await (const val of StreamTest({}, { count: 3 })) {
|
|
656
|
-
values.push(val)
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
expect(values).toEqual([{ value: 0 }, { value: 1 }, { value: 2 }])
|
|
660
|
-
})
|
|
661
|
-
|
|
662
|
-
test('CreateStream validates params', async () => {
|
|
663
|
-
const { CreateStream } = Procedures()
|
|
664
|
-
|
|
665
|
-
const { StreamWithParams } = CreateStream(
|
|
666
|
-
'StreamWithParams',
|
|
667
|
-
{
|
|
668
|
-
schema: {
|
|
669
|
-
params: v.object({ name: v.string().required() }),
|
|
670
|
-
yieldType: v.string(),
|
|
671
|
-
},
|
|
672
|
-
},
|
|
673
|
-
async function* (ctx, params) {
|
|
674
|
-
yield params.name
|
|
675
|
-
}
|
|
676
|
-
)
|
|
677
|
-
|
|
678
|
-
// Missing required param should throw ProcedureValidationError
|
|
679
|
-
try {
|
|
680
|
-
// @ts-expect-error - intentionally passing invalid params
|
|
681
|
-
for await (const _val of StreamWithParams({}, {})) {
|
|
682
|
-
// Should not reach here
|
|
683
|
-
}
|
|
684
|
-
expect.fail('Should have thrown')
|
|
685
|
-
} catch (e: any) {
|
|
686
|
-
expect(e).toBeInstanceOf(ProcedureValidationError)
|
|
687
|
-
expect(e.message).toContain('Validation error for StreamWithParams')
|
|
688
|
-
}
|
|
689
|
-
})
|
|
690
|
-
|
|
691
|
-
test('CreateStream with validateYields validates each yielded value', async () => {
|
|
692
|
-
const { CreateStream } = Procedures()
|
|
693
|
-
const { ProcedureYieldValidationError } = await import('./errors.js')
|
|
694
|
-
|
|
695
|
-
const { StreamValidateYields } = CreateStream(
|
|
696
|
-
'StreamValidateYields',
|
|
697
|
-
{
|
|
698
|
-
schema: {
|
|
699
|
-
yieldType: v.object({ id: v.number().required() }),
|
|
700
|
-
},
|
|
701
|
-
validateYields: true,
|
|
702
|
-
},
|
|
703
|
-
async function* () {
|
|
704
|
-
yield { id: 1 } // Valid
|
|
705
|
-
yield { id: 'not-a-number' } as any // Invalid - should throw
|
|
706
|
-
}
|
|
707
|
-
)
|
|
708
|
-
|
|
709
|
-
const values: any[] = []
|
|
710
|
-
try {
|
|
711
|
-
for await (const val of StreamValidateYields({}, {})) {
|
|
712
|
-
values.push(val)
|
|
713
|
-
}
|
|
714
|
-
expect.fail('Should have thrown on invalid yield')
|
|
715
|
-
} catch (e: any) {
|
|
716
|
-
expect(e).toBeInstanceOf(ProcedureYieldValidationError)
|
|
717
|
-
expect(e.message).toContain('Yield validation error for StreamValidateYields')
|
|
718
|
-
expect(values).toEqual([{ id: 1 }]) // First value was valid
|
|
719
|
-
}
|
|
720
|
-
})
|
|
721
|
-
|
|
722
|
-
test('CreateStream without validateYields does not validate yields', async () => {
|
|
723
|
-
const { CreateStream } = Procedures()
|
|
724
|
-
|
|
725
|
-
const { StreamNoValidate } = CreateStream(
|
|
726
|
-
'StreamNoValidate',
|
|
727
|
-
{
|
|
728
|
-
schema: {
|
|
729
|
-
yieldType: v.object({ id: v.number().required() }),
|
|
730
|
-
},
|
|
731
|
-
// validateYields defaults to false
|
|
732
|
-
},
|
|
733
|
-
async function* () {
|
|
734
|
-
yield { id: 1 }
|
|
735
|
-
yield { id: 'not-a-number' } as any // Invalid but won't throw
|
|
736
|
-
}
|
|
737
|
-
)
|
|
738
|
-
|
|
739
|
-
const values: any[] = []
|
|
740
|
-
for await (const val of StreamNoValidate({}, {})) {
|
|
741
|
-
values.push(val)
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
expect(values).toEqual([{ id: 1 }, { id: 'not-a-number' }])
|
|
745
|
-
})
|
|
746
|
-
|
|
747
|
-
test('CreateStream ctx.error throws ProcedureError', async () => {
|
|
748
|
-
const { CreateStream } = Procedures()
|
|
749
|
-
|
|
750
|
-
const { StreamError } = CreateStream('StreamError', {}, async function* (ctx) {
|
|
751
|
-
yield 'first'
|
|
752
|
-
throw ctx.error('Custom stream error', { code: 'STREAM_ERR' })
|
|
753
|
-
})
|
|
754
|
-
|
|
755
|
-
const values: any[] = []
|
|
756
|
-
try {
|
|
757
|
-
for await (const val of StreamError({}, {})) {
|
|
758
|
-
values.push(val)
|
|
759
|
-
}
|
|
760
|
-
expect.fail('Should have thrown')
|
|
761
|
-
} catch (e: any) {
|
|
762
|
-
expect(e).toBeInstanceOf(ProcedureError)
|
|
763
|
-
expect(e.message).toBe('Custom stream error')
|
|
764
|
-
expect(e.procedureName).toBe('StreamError')
|
|
765
|
-
expect(e.meta).toEqual({ code: 'STREAM_ERR' })
|
|
766
|
-
expect(values).toEqual(['first'])
|
|
767
|
-
}
|
|
768
|
-
})
|
|
769
|
-
|
|
770
|
-
test('CreateStream provides ctx.signal for abort handling', async () => {
|
|
771
|
-
const { CreateStream } = Procedures()
|
|
772
|
-
let signalReceived = false
|
|
773
|
-
|
|
774
|
-
const { StreamWithSignal } = CreateStream('StreamWithSignal', {}, async function* (ctx) {
|
|
775
|
-
expect(ctx.signal).toBeDefined()
|
|
776
|
-
expect(ctx.signal).toBeInstanceOf(AbortSignal)
|
|
777
|
-
signalReceived = true
|
|
778
|
-
yield 'value'
|
|
779
|
-
})
|
|
780
|
-
|
|
781
|
-
for await (const _val of StreamWithSignal({}, {})) {
|
|
782
|
-
// consume
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
expect(signalReceived).toBe(true)
|
|
786
|
-
})
|
|
787
|
-
|
|
788
|
-
test('CreateStream appears in getProcedures with isStream flag', () => {
|
|
105
|
+
test('CreateStream appears in getProcedures with kind: rpc-stream', () => {
|
|
789
106
|
const { Create, CreateStream, getProcedures } = Procedures()
|
|
790
107
|
|
|
791
108
|
Create('RegularProc', {}, async () => 'regular')
|
|
@@ -800,423 +117,50 @@ describe('Streaming Procedures - CreateStream', () => {
|
|
|
800
117
|
const regular = procs.find((p) => p.name === 'RegularProc')
|
|
801
118
|
const stream = procs.find((p) => p.name === 'StreamProc')
|
|
802
119
|
|
|
803
|
-
expect(regular?.
|
|
804
|
-
expect(stream?.
|
|
805
|
-
})
|
|
806
|
-
|
|
807
|
-
test('CreateStream onCreate callback receives isStream flag', () =>
|
|
808
|
-
new Promise<void>((done) => {
|
|
809
|
-
let receivedProc: any
|
|
810
|
-
|
|
811
|
-
const { CreateStream } = Procedures({
|
|
812
|
-
onCreate: (proc) => {
|
|
813
|
-
receivedProc = proc
|
|
814
|
-
},
|
|
815
|
-
})
|
|
816
|
-
|
|
817
|
-
CreateStream('OnCreateStream', {}, async function* () {
|
|
818
|
-
yield 'test'
|
|
819
|
-
})
|
|
820
|
-
|
|
821
|
-
expect(receivedProc).toBeDefined()
|
|
822
|
-
expect(receivedProc.isStream).toBe(true)
|
|
823
|
-
expect(receivedProc.name).toBe('OnCreateStream')
|
|
824
|
-
done()
|
|
825
|
-
}))
|
|
826
|
-
|
|
827
|
-
test('CreateStream duplicate registration throws', () => {
|
|
828
|
-
const { CreateStream } = Procedures()
|
|
829
|
-
|
|
830
|
-
CreateStream('DuplicateStream', {}, async function* () {
|
|
831
|
-
yield 'first'
|
|
832
|
-
})
|
|
833
|
-
|
|
834
|
-
expect(() => {
|
|
835
|
-
CreateStream('DuplicateStream', {}, async function* () {
|
|
836
|
-
yield 'second'
|
|
837
|
-
})
|
|
838
|
-
}).toThrow('Procedure with name DuplicateStream is already registered')
|
|
839
|
-
})
|
|
840
|
-
|
|
841
|
-
test('CreateStream error includes definition location', async () => {
|
|
842
|
-
const { CreateStream } = Procedures()
|
|
843
|
-
|
|
844
|
-
const { StreamErrorLocation } = CreateStream(
|
|
845
|
-
'StreamErrorLocation',
|
|
846
|
-
{
|
|
847
|
-
schema: {
|
|
848
|
-
params: v.object({ id: v.number().required() }),
|
|
849
|
-
},
|
|
850
|
-
},
|
|
851
|
-
async function* () {
|
|
852
|
-
yield 'test'
|
|
853
|
-
}
|
|
854
|
-
)
|
|
855
|
-
|
|
856
|
-
try {
|
|
857
|
-
// @ts-expect-error - intentionally passing invalid params
|
|
858
|
-
for await (const _val of StreamErrorLocation({}, {})) {
|
|
859
|
-
// consume
|
|
860
|
-
}
|
|
861
|
-
expect.fail('Should have thrown')
|
|
862
|
-
} catch (e: any) {
|
|
863
|
-
expect(e.definedAt).toBeDefined()
|
|
864
|
-
expect(e.definedAt.file).toContain('index.test.ts')
|
|
865
|
-
expect(e.stack).toContain('--- Procedure "StreamErrorLocation" defined at ---')
|
|
866
|
-
}
|
|
120
|
+
expect(regular?.kind).toBe('rpc')
|
|
121
|
+
expect(stream?.kind).toBe('rpc-stream')
|
|
867
122
|
})
|
|
123
|
+
})
|
|
868
124
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
const {
|
|
873
|
-
|
|
874
|
-
{
|
|
875
|
-
|
|
876
|
-
params: Type.Object({ limit: Type.Number() }),
|
|
877
|
-
yieldType: Type.Object({ data: Type.String() }),
|
|
878
|
-
},
|
|
125
|
+
describe('builder.config.noRuntimeValidation', () => {
|
|
126
|
+
test('noRuntimeValidation does not interfere with onCreate callback', async () => {
|
|
127
|
+
const seen: string[] = []
|
|
128
|
+
const { Create } = Procedures({
|
|
129
|
+
config: { noRuntimeValidation: true },
|
|
130
|
+
onCreate: (proc) => {
|
|
131
|
+
seen.push(proc.name)
|
|
879
132
|
},
|
|
880
|
-
async function* (ctx, params) {
|
|
881
|
-
for (let i = 0; i < params.limit; i++) {
|
|
882
|
-
yield { data: `item-${i}` }
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
)
|
|
886
|
-
|
|
887
|
-
const values: any[] = []
|
|
888
|
-
for await (const val of TypeboxStream({}, { limit: 2 })) {
|
|
889
|
-
values.push(val)
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
expect(values).toEqual([{ data: 'item-0' }, { data: 'item-1' }])
|
|
893
|
-
})
|
|
894
|
-
|
|
895
|
-
test('CreateStream with context type', async () => {
|
|
896
|
-
interface StreamContext {
|
|
897
|
-
userId: string
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
const { CreateStream } = Procedures<StreamContext>()
|
|
901
|
-
|
|
902
|
-
const { ContextStream } = CreateStream('ContextStream', {}, async function* (ctx) {
|
|
903
|
-
// ctx should have both userId and error
|
|
904
|
-
expect(ctx.userId).toBe('user-123')
|
|
905
|
-
expect(ctx.error).toBeDefined()
|
|
906
|
-
expect(ctx.signal).toBeDefined()
|
|
907
|
-
yield ctx.userId
|
|
908
133
|
})
|
|
909
134
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
expect(values).toEqual(['user-123'])
|
|
916
|
-
})
|
|
917
|
-
|
|
918
|
-
test('CreateStream rethrows the original error preserving class identity', async () => {
|
|
919
|
-
// The streaming wrapper must NOT box user errors inside ProcedureError —
|
|
920
|
-
// doing so would defeat route-declared typed-error dispatch on the client
|
|
921
|
-
// (the HTTP builder's taxonomy would see `ProcedureError` instead of the
|
|
922
|
-
// user's class). Stack annotation is added in place; class identity and
|
|
923
|
-
// custom properties are preserved.
|
|
924
|
-
class MyDomainError extends Error {
|
|
925
|
-
readonly name = 'MyDomainError'
|
|
926
|
-
readonly code = 'STREAM_FAIL'
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
const { CreateStream } = Procedures()
|
|
930
|
-
const originalError = new MyDomainError('Stream underlying error')
|
|
931
|
-
|
|
932
|
-
const { StreamCause } = CreateStream(
|
|
933
|
-
'StreamCause',
|
|
934
|
-
{},
|
|
935
|
-
// eslint-disable-next-line require-yield
|
|
936
|
-
async function* () {
|
|
937
|
-
throw originalError
|
|
938
|
-
}
|
|
939
|
-
)
|
|
940
|
-
|
|
941
|
-
try {
|
|
942
|
-
for await (const _val of StreamCause({}, {})) {
|
|
943
|
-
// consume
|
|
944
|
-
}
|
|
945
|
-
expect.fail('Should have thrown')
|
|
946
|
-
} catch (e: any) {
|
|
947
|
-
expect(e).toBe(originalError)
|
|
948
|
-
expect(e).toBeInstanceOf(MyDomainError)
|
|
949
|
-
expect(e.code).toBe('STREAM_FAIL')
|
|
950
|
-
}
|
|
951
|
-
})
|
|
952
|
-
|
|
953
|
-
test('CreateStream propagates .return() to the user generator', async () => {
|
|
954
|
-
// Consumers that close a stream early (via `iterator.return()` or breaking
|
|
955
|
-
// out of for-await) must trigger the user generator's `finally` block so
|
|
956
|
-
// cleanup (db handles, subscriptions, signal-driven teardown) runs.
|
|
957
|
-
const { CreateStream } = Procedures()
|
|
958
|
-
let finallyRan = false
|
|
959
|
-
|
|
960
|
-
const { EarlyClose } = CreateStream(
|
|
961
|
-
'EarlyClose',
|
|
962
|
-
{},
|
|
963
|
-
async function* () {
|
|
964
|
-
try {
|
|
965
|
-
yield 1
|
|
966
|
-
yield 2
|
|
967
|
-
yield 3
|
|
968
|
-
} finally {
|
|
969
|
-
finallyRan = true
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
)
|
|
973
|
-
|
|
974
|
-
const iter = EarlyClose({}, {})
|
|
975
|
-
const first = await iter.next()
|
|
976
|
-
expect(first.value).toBe(1)
|
|
977
|
-
await iter.return!(undefined)
|
|
978
|
-
expect(finallyRan).toBe(true)
|
|
979
|
-
})
|
|
980
|
-
|
|
981
|
-
test('CreateStream with extended config', () => {
|
|
982
|
-
interface ExtConfig {
|
|
983
|
-
scope: string
|
|
984
|
-
version: number
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
const { CreateStream } = Procedures<unknown, ExtConfig>()
|
|
988
|
-
|
|
989
|
-
const { info } = CreateStream(
|
|
990
|
-
'ExtendedStream',
|
|
991
|
-
{
|
|
992
|
-
scope: 'api',
|
|
993
|
-
version: 1,
|
|
994
|
-
description: 'Extended config stream',
|
|
995
|
-
},
|
|
996
|
-
async function* () {
|
|
997
|
-
yield 'data'
|
|
998
|
-
}
|
|
135
|
+
Create(
|
|
136
|
+
'Registered',
|
|
137
|
+
{ schema: { params: Type.Object({ id: Type.String() }) } },
|
|
138
|
+
async (_ctx, params) => params
|
|
999
139
|
)
|
|
1000
140
|
|
|
1001
|
-
expect(
|
|
1002
|
-
expect(info.version).toBe(1)
|
|
1003
|
-
expect(info.description).toBe('Extended config stream')
|
|
1004
|
-
expect(info.isStream).toBe(true)
|
|
1005
|
-
})
|
|
1006
|
-
|
|
1007
|
-
test('CreateStream signal.aborted becomes true after generator completes', async () => {
|
|
1008
|
-
const { CreateStream } = Procedures()
|
|
1009
|
-
let capturedSignal: AbortSignal | null = null
|
|
1010
|
-
|
|
1011
|
-
const { AbortStream } = CreateStream('AbortStream', {}, async function* (ctx) {
|
|
1012
|
-
capturedSignal = ctx.signal
|
|
1013
|
-
yield 'value'
|
|
1014
|
-
})
|
|
1015
|
-
|
|
1016
|
-
// Consume the generator
|
|
1017
|
-
for await (const _val of AbortStream({}, {})) {
|
|
1018
|
-
// consume
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// After generator completes, signal should be aborted
|
|
1022
|
-
expect(capturedSignal).not.toBeNull()
|
|
1023
|
-
expect(capturedSignal!.aborted).toBe(true)
|
|
1024
|
-
})
|
|
1025
|
-
|
|
1026
|
-
test('CreateStream signal.reason is stream-completed after normal completion', async () => {
|
|
1027
|
-
const { CreateStream } = Procedures()
|
|
1028
|
-
let capturedSignal: AbortSignal | null = null
|
|
1029
|
-
|
|
1030
|
-
const { ReasonStream } = CreateStream('ReasonStream', {}, async function* (ctx) {
|
|
1031
|
-
capturedSignal = ctx.signal
|
|
1032
|
-
yield 'value'
|
|
1033
|
-
})
|
|
1034
|
-
|
|
1035
|
-
for await (const _val of ReasonStream({}, {})) {
|
|
1036
|
-
// consume
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
expect(capturedSignal!.reason).toBe('stream-completed')
|
|
141
|
+
expect(seen).toEqual(['Registered'])
|
|
1040
142
|
})
|
|
1041
143
|
|
|
1042
|
-
test('
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
let capturedSignal: AbortSignal | null = null
|
|
1046
|
-
|
|
1047
|
-
const { CombinedStream } = CreateStream('CombinedStream', {}, async function* (ctx) {
|
|
1048
|
-
capturedSignal = ctx.signal
|
|
1049
|
-
// Combined signal is a new object, not the raw external signal
|
|
1050
|
-
expect(ctx.signal).not.toBe(externalAc.signal)
|
|
1051
|
-
yield 'value'
|
|
1052
|
-
})
|
|
1053
|
-
|
|
1054
|
-
// Abort external before consuming — combined signal should reflect it
|
|
1055
|
-
externalAc.abort('client-disconnected')
|
|
1056
|
-
|
|
1057
|
-
for await (const _val of CombinedStream({ signal: externalAc.signal }, {})) {
|
|
1058
|
-
// consume
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
expect(capturedSignal!.aborted).toBe(true)
|
|
1062
|
-
// Reason comes from external abort, not internal 'stream-completed'
|
|
1063
|
-
expect(capturedSignal!.reason).toBe('client-disconnected')
|
|
144
|
+
test('noRuntimeValidation: false is rejected by the type system', () => {
|
|
145
|
+
// @ts-expect-error - only `true` (or omitted) is allowed for noRuntimeValidation
|
|
146
|
+
void Procedures({ config: { noRuntimeValidation: false } })
|
|
1064
147
|
})
|
|
1065
148
|
})
|
|
1066
149
|
|
|
1067
|
-
describe('
|
|
1068
|
-
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
)
|
|
1082
|
-
|
|
1083
|
-
// Without isPrevalidated, missing required param would throw
|
|
1084
|
-
// With isPrevalidated: true, validation is skipped
|
|
1085
|
-
const result = await SkipValidation({ isPrevalidated: true }, {} as any)
|
|
1086
|
-
expect(result).toEqual({})
|
|
1087
|
-
})
|
|
1088
|
-
|
|
1089
|
-
test('Create validates when ctx.isPrevalidated is false', async () => {
|
|
1090
|
-
const { Create } = Procedures()
|
|
1091
|
-
|
|
1092
|
-
const { ValidateParams } = Create(
|
|
1093
|
-
'ValidateParams',
|
|
1094
|
-
{
|
|
1095
|
-
schema: {
|
|
1096
|
-
params: v.object({ name: v.string().required() }),
|
|
1097
|
-
},
|
|
1098
|
-
},
|
|
1099
|
-
async (ctx, params) => {
|
|
1100
|
-
return params
|
|
1101
|
-
}
|
|
1102
|
-
)
|
|
1103
|
-
|
|
1104
|
-
// With isPrevalidated: false, validation should still run
|
|
1105
|
-
try {
|
|
1106
|
-
await ValidateParams({ isPrevalidated: false }, {} as any)
|
|
1107
|
-
expect.fail('Should have thrown validation error')
|
|
1108
|
-
} catch (e: any) {
|
|
1109
|
-
expect(e).toBeInstanceOf(ProcedureValidationError)
|
|
1110
|
-
}
|
|
1111
|
-
})
|
|
1112
|
-
|
|
1113
|
-
test('Create validates when ctx.isPrevalidated is undefined', async () => {
|
|
1114
|
-
const { Create } = Procedures()
|
|
1115
|
-
|
|
1116
|
-
const { ValidateUndefined } = Create(
|
|
1117
|
-
'ValidateUndefined',
|
|
1118
|
-
{
|
|
1119
|
-
schema: {
|
|
1120
|
-
params: v.object({ id: v.number().required() }),
|
|
1121
|
-
},
|
|
1122
|
-
},
|
|
1123
|
-
async (ctx, params) => {
|
|
1124
|
-
return params
|
|
1125
|
-
}
|
|
1126
|
-
)
|
|
1127
|
-
|
|
1128
|
-
// Without isPrevalidated property, validation should run
|
|
1129
|
-
try {
|
|
1130
|
-
await ValidateUndefined({}, {} as any)
|
|
1131
|
-
expect.fail('Should have thrown validation error')
|
|
1132
|
-
} catch (e: any) {
|
|
1133
|
-
expect(e).toBeInstanceOf(ProcedureValidationError)
|
|
1134
|
-
}
|
|
1135
|
-
})
|
|
1136
|
-
|
|
1137
|
-
test('CreateStream skips validation when ctx.isPrevalidated is true', async () => {
|
|
1138
|
-
const { CreateStream } = Procedures()
|
|
1139
|
-
|
|
1140
|
-
const { StreamSkipValidation } = CreateStream(
|
|
1141
|
-
'StreamSkipValidation',
|
|
1142
|
-
{
|
|
1143
|
-
schema: {
|
|
1144
|
-
params: v.object({ count: v.number().required() }),
|
|
1145
|
-
},
|
|
1146
|
-
},
|
|
1147
|
-
async function* (ctx, params) {
|
|
1148
|
-
yield { received: params }
|
|
1149
|
-
}
|
|
1150
|
-
)
|
|
1151
|
-
|
|
1152
|
-
// With isPrevalidated: true, validation is skipped even with invalid params
|
|
1153
|
-
const values: any[] = []
|
|
1154
|
-
for await (const val of StreamSkipValidation({ isPrevalidated: true }, {} as any)) {
|
|
1155
|
-
values.push(val)
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
expect(values).toEqual([{ received: {} }])
|
|
1159
|
-
})
|
|
1160
|
-
|
|
1161
|
-
test('CreateStream validates when ctx.isPrevalidated is false', async () => {
|
|
1162
|
-
const { CreateStream } = Procedures()
|
|
1163
|
-
|
|
1164
|
-
const { StreamValidate } = CreateStream(
|
|
1165
|
-
'StreamValidate',
|
|
1166
|
-
{
|
|
1167
|
-
schema: {
|
|
1168
|
-
params: v.object({ count: v.number().required() }),
|
|
1169
|
-
},
|
|
1170
|
-
},
|
|
1171
|
-
async function* (ctx, params) {
|
|
1172
|
-
yield { received: params }
|
|
1173
|
-
}
|
|
1174
|
-
)
|
|
1175
|
-
|
|
1176
|
-
// With isPrevalidated: false, validation should run
|
|
1177
|
-
try {
|
|
1178
|
-
for await (const _val of StreamValidate({ isPrevalidated: false }, {} as any)) {
|
|
1179
|
-
// consume
|
|
1180
|
-
}
|
|
1181
|
-
expect.fail('Should have thrown validation error')
|
|
1182
|
-
} catch (e: any) {
|
|
1183
|
-
expect(e).toBeInstanceOf(ProcedureValidationError)
|
|
1184
|
-
}
|
|
1185
|
-
})
|
|
1186
|
-
|
|
1187
|
-
test('CreateStream validates when ctx.isPrevalidated is undefined', async () => {
|
|
1188
|
-
const { CreateStream } = Procedures()
|
|
1189
|
-
|
|
1190
|
-
const { StreamValidateUndefined } = CreateStream(
|
|
1191
|
-
'StreamValidateUndefined',
|
|
1192
|
-
{
|
|
1193
|
-
schema: {
|
|
1194
|
-
params: v.object({ value: v.string().required() }),
|
|
1195
|
-
},
|
|
1196
|
-
},
|
|
1197
|
-
async function* (ctx, params) {
|
|
1198
|
-
yield params
|
|
1199
|
-
}
|
|
1200
|
-
)
|
|
1201
|
-
|
|
1202
|
-
// Without isPrevalidated property, validation should run
|
|
1203
|
-
try {
|
|
1204
|
-
for await (const _val of StreamValidateUndefined({}, {} as any)) {
|
|
1205
|
-
// consume
|
|
1206
|
-
}
|
|
1207
|
-
expect.fail('Should have thrown validation error')
|
|
1208
|
-
} catch (e: any) {
|
|
1209
|
-
expect(e).toBeInstanceOf(ProcedureValidationError)
|
|
1210
|
-
}
|
|
1211
|
-
})
|
|
1212
|
-
|
|
1213
|
-
test('isPrevalidated is not exposed in handler context type', async () => {
|
|
1214
|
-
const { Create } = Procedures()
|
|
1215
|
-
|
|
1216
|
-
Create('CheckCtxType', {}, async (ctx) => {
|
|
1217
|
-
// @ts-expect-error - isPrevalidated should not be on handler context type
|
|
1218
|
-
void ctx.isPrevalidated
|
|
1219
|
-
return 'done'
|
|
1220
|
-
})
|
|
150
|
+
describe('Procedure registration kind discriminant', () => {
|
|
151
|
+
it('Create produces kind: rpc', () => {
|
|
152
|
+
const procs = Procedures()
|
|
153
|
+
procs.Create('Foo', { schema: { params: Type.Object({}) } }, async () => undefined)
|
|
154
|
+
const reg = procs.getProcedure('Foo')
|
|
155
|
+
expect(reg?.kind).toBe('rpc')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('CreateStream produces kind: rpc-stream', () => {
|
|
159
|
+
const procs = Procedures()
|
|
160
|
+
procs.CreateStream('Bar', {
|
|
161
|
+
schema: { params: Type.Object({}), yieldType: Type.Number() },
|
|
162
|
+
}, async function* () {})
|
|
163
|
+
const reg = procs.getProcedure('Bar')
|
|
164
|
+
expect(reg?.kind).toBe('rpc-stream')
|
|
1221
165
|
})
|
|
1222
166
|
})
|