ts-procedures 10.1.1 → 10.2.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/CHANGELOG.md +26 -65
- package/README.md +2 -0
- package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +8 -0
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +25 -22
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +24 -13
- package/agent_config/copilot/copilot-instructions.md +1 -1
- package/agent_config/cursor/cursorrules +1 -1
- package/build/adapters/hono/envelope-parity.test.js +6 -17
- package/build/adapters/hono/envelope-parity.test.js.map +1 -1
- package/build/adapters/hono/handlers/http-stream.d.ts +2 -1
- package/build/adapters/hono/handlers/http-stream.js +9 -2
- package/build/adapters/hono/handlers/http-stream.js.map +1 -1
- package/build/adapters/hono/handlers/http.d.ts +2 -1
- package/build/adapters/hono/handlers/http.js +9 -2
- package/build/adapters/hono/handlers/http.js.map +1 -1
- package/build/adapters/hono/handlers/rpc.d.ts +2 -1
- package/build/adapters/hono/handlers/rpc.js +9 -2
- package/build/adapters/hono/handlers/rpc.js.map +1 -1
- package/build/adapters/hono/handlers/stream.d.ts +2 -1
- package/build/adapters/hono/handlers/stream.js +9 -2
- package/build/adapters/hono/handlers/stream.js.map +1 -1
- package/build/adapters/hono/index.d.ts +14 -7
- package/build/adapters/hono/index.js +45 -5
- package/build/adapters/hono/index.js.map +1 -1
- package/build/adapters/hono/index.test.js +55 -0
- package/build/adapters/hono/index.test.js.map +1 -1
- package/build/adapters/hono/request.d.ts +7 -0
- package/build/adapters/hono/request.js +15 -0
- package/build/adapters/hono/request.js.map +1 -1
- package/build/adapters/hono/types.d.ts +31 -0
- package/build/adapters/hono/types.js.map +1 -1
- package/build/codegen/bin/cli.js +1 -1
- package/build/codegen/bin/cli.js.map +1 -1
- package/build/codegen/constants.js +2 -2
- package/build/codegen/constants.js.map +1 -1
- package/build/codegen/emit/api-route.js +1 -1
- package/build/codegen/emit/api-route.js.map +1 -1
- package/build/codegen/emit/scope-file.js +1 -1
- package/build/codegen/emit/scope-file.js.map +1 -1
- package/build/codegen/emit-errors.integration.test.js +2 -2
- package/build/codegen/emit-errors.integration.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/test-helpers/run-tsc.js +1 -1
- package/build/codegen/test-helpers/run-tsc.js.map +1 -1
- package/build/core/create-http-stream.d.ts +3 -14
- package/build/core/create-http-stream.js +4 -2
- package/build/core/create-http-stream.js.map +1 -1
- package/build/core/create-http.d.ts +2 -9
- package/build/core/create-http.js +3 -2
- package/build/core/create-http.js.map +1 -1
- package/build/core/create-http.test-d.d.ts +1 -0
- package/build/core/create-http.test-d.js +104 -0
- package/build/core/create-http.test-d.js.map +1 -0
- package/build/core/create-stream.js +6 -2
- package/build/core/create-stream.js.map +1 -1
- package/build/core/create.js +5 -4
- package/build/core/create.js.map +1 -1
- package/build/core/create.test.js +63 -0
- package/build/core/create.test.js.map +1 -1
- package/build/core/internal.d.ts +16 -0
- package/build/core/internal.js +22 -0
- package/build/core/internal.js.map +1 -1
- package/build/core/procedures.d.ts +9 -16
- package/build/core/procedures.js +4 -0
- package/build/core/procedures.js.map +1 -1
- package/build/core/types.d.ts +85 -4
- package/build/errors.d.ts +33 -0
- package/build/errors.js +91 -0
- package/build/errors.js.map +1 -0
- package/build/errors.test.d.ts +1 -0
- package/build/errors.test.js +122 -0
- package/build/errors.test.js.map +1 -0
- package/build/exports.d.ts +1 -2
- package/build/exports.js +0 -4
- package/build/exports.js.map +1 -1
- package/build/implementations/http/doc-registry.d.ts +28 -0
- package/build/implementations/http/doc-registry.js +96 -0
- package/build/implementations/http/doc-registry.js.map +1 -0
- package/build/implementations/http/doc-registry.test.d.ts +1 -0
- package/build/implementations/http/doc-registry.test.js +516 -0
- package/build/implementations/http/doc-registry.test.js.map +1 -0
- package/build/implementations/http/error-taxonomy.d.ts +249 -0
- package/build/implementations/http/error-taxonomy.js +252 -0
- package/build/implementations/http/error-taxonomy.js.map +1 -0
- package/build/implementations/http/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/error-taxonomy.test.js +399 -0
- package/build/implementations/http/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/express-rpc/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/express-rpc/error-taxonomy.test.js +83 -0
- package/build/implementations/http/express-rpc/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/express-rpc/index.d.ts +125 -0
- package/build/implementations/http/express-rpc/index.js +216 -0
- package/build/implementations/http/express-rpc/index.js.map +1 -0
- package/build/implementations/http/express-rpc/index.test.d.ts +1 -0
- package/build/implementations/http/express-rpc/index.test.js +684 -0
- package/build/implementations/http/express-rpc/index.test.js.map +1 -0
- package/build/implementations/http/express-rpc/types.d.ts +11 -0
- package/build/implementations/http/express-rpc/types.js +2 -0
- package/build/implementations/http/express-rpc/types.js.map +1 -0
- package/build/implementations/http/hono-api/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/hono-api/error-taxonomy.test.js +137 -0
- package/build/implementations/http/hono-api/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/hono-api/index.d.ts +151 -0
- package/build/implementations/http/hono-api/index.js +344 -0
- package/build/implementations/http/hono-api/index.js.map +1 -0
- package/build/implementations/http/hono-api/index.test.d.ts +1 -0
- package/build/implementations/http/hono-api/index.test.js +992 -0
- package/build/implementations/http/hono-api/index.test.js.map +1 -0
- package/build/implementations/http/hono-api/types.d.ts +13 -0
- package/build/implementations/http/hono-api/types.js +2 -0
- package/build/implementations/http/hono-api/types.js.map +1 -0
- package/build/implementations/http/hono-rpc/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/hono-rpc/error-taxonomy.test.js +64 -0
- package/build/implementations/http/hono-rpc/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/hono-rpc/index.d.ts +130 -0
- package/build/implementations/http/hono-rpc/index.js +209 -0
- package/build/implementations/http/hono-rpc/index.js.map +1 -0
- package/build/implementations/http/hono-rpc/index.test.d.ts +1 -0
- package/build/implementations/http/hono-rpc/index.test.js +828 -0
- package/build/implementations/http/hono-rpc/index.test.js.map +1 -0
- package/build/implementations/http/hono-rpc/types.d.ts +11 -0
- package/build/implementations/http/hono-rpc/types.js +2 -0
- package/build/implementations/http/hono-rpc/types.js.map +1 -0
- package/build/implementations/http/hono-stream/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/hono-stream/error-taxonomy.test.js +159 -0
- package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/hono-stream/index.d.ts +171 -0
- package/build/implementations/http/hono-stream/index.js +415 -0
- package/build/implementations/http/hono-stream/index.js.map +1 -0
- package/build/implementations/http/hono-stream/index.test.d.ts +1 -0
- package/build/implementations/http/hono-stream/index.test.js +1383 -0
- package/build/implementations/http/hono-stream/index.test.js.map +1 -0
- package/build/implementations/http/hono-stream/types.d.ts +15 -0
- package/build/implementations/http/hono-stream/types.js +2 -0
- package/build/implementations/http/hono-stream/types.js.map +1 -0
- package/build/implementations/http/on-request-error.test.d.ts +1 -0
- package/build/implementations/http/on-request-error.test.js +173 -0
- package/build/implementations/http/on-request-error.test.js.map +1 -0
- package/build/implementations/http/route-errors.test.d.ts +1 -0
- package/build/implementations/http/route-errors.test.js +139 -0
- package/build/implementations/http/route-errors.test.js.map +1 -0
- package/build/implementations/types.d.ts +192 -0
- package/build/implementations/types.js +2 -0
- package/build/implementations/types.js.map +1 -0
- package/build/index.d.ts +165 -0
- package/build/index.js +258 -0
- package/build/index.js.map +1 -0
- package/build/index.test.d.ts +1 -0
- package/build/index.test.js +920 -0
- package/build/index.test.js.map +1 -0
- package/build/schema/extract-json-schema.d.ts +2 -0
- package/build/schema/extract-json-schema.js +12 -0
- package/build/schema/extract-json-schema.js.map +1 -0
- package/build/schema/extract-json-schema.test.d.ts +1 -0
- package/build/schema/extract-json-schema.test.js +23 -0
- package/build/schema/extract-json-schema.test.js.map +1 -0
- package/build/schema/parser.d.ts +28 -0
- package/build/schema/parser.js +170 -0
- package/build/schema/parser.js.map +1 -0
- package/build/schema/parser.test.d.ts +1 -0
- package/build/schema/parser.test.js +120 -0
- package/build/schema/parser.test.js.map +1 -0
- package/build/schema/resolve-schema-lib.d.ts +12 -0
- package/build/schema/resolve-schema-lib.js +11 -0
- package/build/schema/resolve-schema-lib.js.map +1 -0
- package/build/schema/resolve-schema-lib.test.d.ts +1 -0
- package/build/schema/resolve-schema-lib.test.js +17 -0
- package/build/schema/resolve-schema-lib.test.js.map +1 -0
- package/build/schema/typebox.d.ts +1 -1
- package/build/schema/typebox.js +2 -20
- package/build/schema/typebox.js.map +1 -1
- package/build/schema/typebox.test.js +0 -10
- package/build/schema/typebox.test.js.map +1 -1
- package/build/schema/types.d.ts +8 -0
- package/build/schema/types.js +2 -0
- package/build/schema/types.js.map +1 -0
- package/build/server/context.d.ts +12 -2
- package/build/server/context.js +49 -4
- package/build/server/context.js.map +1 -1
- package/build/server/context.test.js +32 -1
- package/build/server/context.test.js.map +1 -1
- package/build/server/doc-registry.js +0 -3
- package/build/server/doc-registry.js.map +1 -1
- package/build/server/doc-registry.test.js +0 -31
- package/build/server/doc-registry.test.js.map +1 -1
- package/build/server/index.d.ts +0 -1
- package/build/server/index.js +0 -1
- package/build/server/index.js.map +1 -1
- package/build/server/types.d.ts +6 -27
- package/build/stack-utils.d.ts +25 -0
- package/build/stack-utils.js +95 -0
- package/build/stack-utils.js.map +1 -0
- package/build/stack-utils.test.d.ts +1 -0
- package/build/stack-utils.test.js +80 -0
- package/build/stack-utils.test.js.map +1 -0
- package/docs/core.md +8 -0
- package/docs/http-integrations.md +41 -27
- package/package.json +8 -8
- package/src/adapters/astro/README.md +2 -0
- package/src/adapters/hono/envelope-parity.test.ts +6 -18
- package/src/adapters/hono/handlers/http-stream.ts +12 -3
- package/src/adapters/hono/handlers/http.ts +12 -3
- package/src/adapters/hono/handlers/rpc.ts +12 -3
- package/src/adapters/hono/handlers/stream.ts +12 -3
- package/src/adapters/hono/index.test.ts +70 -0
- package/src/adapters/hono/index.ts +53 -11
- package/src/adapters/hono/request.ts +18 -0
- package/src/adapters/hono/types.ts +37 -0
- package/src/codegen/bin/cli.ts +1 -1
- package/src/codegen/constants.ts +2 -2
- package/src/codegen/emit/api-route.ts +1 -1
- package/src/codegen/emit/scope-file.ts +1 -2
- package/src/codegen/emit-errors.integration.test.ts +2 -2
- package/src/codegen/resolve-envelope.ts +1 -1
- package/src/codegen/test-helpers/run-tsc.ts +0 -1
- package/src/core/create-http-stream.ts +20 -13
- package/src/core/create-http.test-d.ts +154 -0
- package/src/core/create-http.ts +19 -8
- package/src/core/create-stream.ts +12 -4
- package/src/core/create.test.ts +74 -0
- package/src/core/create.ts +10 -5
- package/src/core/internal.ts +35 -0
- package/src/core/procedures.ts +4 -0
- package/src/core/types.ts +99 -4
- package/src/exports.ts +6 -4
- package/src/schema/typebox.test.ts +0 -12
- package/src/schema/typebox.ts +2 -21
- package/src/server/context.test.ts +39 -1
- package/src/server/context.ts +54 -4
- package/src/server/doc-registry.test.ts +0 -34
- package/src/server/doc-registry.ts +0 -3
- package/src/server/index.ts +0 -1
- package/src/server/types.ts +8 -27
- package/build/server/spec-version.d.ts +0 -24
- package/build/server/spec-version.js +0 -26
- package/build/server/spec-version.js.map +0 -1
- package/build/version.d.ts +0 -9
- package/build/version.js +0 -11
- package/build/version.js.map +0 -1
- package/docs/doc-envelope-spec-v1.md +0 -296
- package/docs/doc-envelope-v1.schema.json +0 -253
- package/src/server/spec-version.ts +0 -27
- package/src/version.ts +0 -11
package/src/core/create.test.ts
CHANGED
|
@@ -364,6 +364,80 @@ describe('Procedures', () => {
|
|
|
364
364
|
})
|
|
365
365
|
})
|
|
366
366
|
|
|
367
|
+
describe('Procedures({ middleware }) - factory level, ctx + input aware', () => {
|
|
368
|
+
test('middleware runs with same ctx the handler sees and can inspect input', async () => {
|
|
369
|
+
const calls: any[] = []
|
|
370
|
+
|
|
371
|
+
const { Create } = Procedures<{ userId: string }>({
|
|
372
|
+
middleware: [
|
|
373
|
+
({ ctx, input, next }) => {
|
|
374
|
+
calls.push({ userId: ctx.userId, input })
|
|
375
|
+
// "parse the incoming call" using ctx + input
|
|
376
|
+
if ((input as any)?.orgId === 'evil') {
|
|
377
|
+
throw ctx.error('tenant violation')
|
|
378
|
+
}
|
|
379
|
+
return next()
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
const { GetThing } = Create('GetThing', { schema: { params: Type.Object({ orgId: Type.String() }) } }, async (ctx, p) => {
|
|
385
|
+
return { seenByHandler: ctx.userId, org: (p as any).orgId }
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
await GetThing({ userId: 'u1' }, { orgId: 'acme' })
|
|
389
|
+
expect(calls).toHaveLength(1)
|
|
390
|
+
expect(calls[0]).toEqual({ userId: 'u1', input: { orgId: 'acme' } })
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
test('middleware can augment ctx and transform input for the handler', async () => {
|
|
394
|
+
const { Create } = Procedures<{ base: string }>({
|
|
395
|
+
middleware: [
|
|
396
|
+
({ ctx, input, next }) => {
|
|
397
|
+
return next({
|
|
398
|
+
ctx: { ...ctx, extra: 'added' },
|
|
399
|
+
input: { ...(input as any), injected: true },
|
|
400
|
+
})
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
const { Echo } = Create('Echo', { schema: { params: Type.Object({ n: Type.Number() }) } }, async (ctx, p: any) => {
|
|
406
|
+
return { base: ctx.base, extra: (ctx as any).extra, received: p }
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
const result = await Echo({ base: 'b' }, { n: 42 })
|
|
410
|
+
expect(result).toEqual({
|
|
411
|
+
base: 'b',
|
|
412
|
+
extra: 'added',
|
|
413
|
+
received: { n: 42, injected: true },
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
test('middleware error uses the factory ctx.error and is wrapped as ProcedureError', async () => {
|
|
418
|
+
const { Create } = Procedures<{}>({
|
|
419
|
+
middleware: [({ ctx }) => { throw ctx.error('from factory mw') }],
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
const { Fail } = Create('Fail', {}, async () => 'no')
|
|
423
|
+
await expect(Fail({}, {})).rejects.toThrow('from factory mw')
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
test('multiple factory middlewares compose (order + next)', async () => {
|
|
427
|
+
const order: string[] = []
|
|
428
|
+
const { Create } = Procedures({
|
|
429
|
+
middleware: [
|
|
430
|
+
async ({ next }) => { order.push('mw1'); return next() },
|
|
431
|
+
async ({ next }) => { order.push('mw2'); return next() },
|
|
432
|
+
],
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
const { Ok } = Create('Ok', {}, async () => 'ok')
|
|
436
|
+
await Ok({}, {})
|
|
437
|
+
expect(order).toEqual(['mw1', 'mw2'])
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
|
|
367
441
|
describe('Procedures - Definition Location in Errors', () => {
|
|
368
442
|
test('ProcedureValidationError includes definition location', async () => {
|
|
369
443
|
const { Create } = Procedures()
|
package/src/core/create.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { captureDefinitionInfo } from './definition-site.js'
|
|
|
5
5
|
import {
|
|
6
6
|
assertNoHttpFields,
|
|
7
7
|
assertNotDuplicate,
|
|
8
|
+
runWithMiddlewares,
|
|
8
9
|
shouldSkipValidation,
|
|
9
10
|
toProcedureError,
|
|
10
11
|
validateParams,
|
|
@@ -76,11 +77,15 @@ export function makeCreate<TContext, TExtendedConfig>(runtime: FactoryRuntime<TC
|
|
|
76
77
|
error: errorFactory,
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
const fullCtx = { ...ctx, ...localCtx } as Prettify<TContext & TLocalContext>
|
|
81
|
+
|
|
82
|
+
// Factory middleware sees the exact same ctx + validated input as the
|
|
83
|
+
// user handler. Supports next({ ctx?, input? }) augmentation.
|
|
84
|
+
return await runWithMiddlewares(
|
|
85
|
+
runtime.middlewares,
|
|
86
|
+
fullCtx,
|
|
87
|
+
params,
|
|
88
|
+
(c, p) => handler(c as any, p as any)
|
|
84
89
|
)
|
|
85
90
|
} catch (error: any) {
|
|
86
91
|
throw toProcedureError(name, definitionInfo, error)
|
package/src/core/internal.ts
CHANGED
|
@@ -20,6 +20,8 @@ export type FactoryRuntime<TContext, TExtendedConfig> = {
|
|
|
20
20
|
adapters: readonly SchemaAdapter[]
|
|
21
21
|
compile: ValidatorCompiler
|
|
22
22
|
httpDefaults?: { pathPrefix?: string; scope?: string }
|
|
23
|
+
/** Factory-level middleware applied to every procedure from this factory. */
|
|
24
|
+
middlewares: Array<(opts: { ctx: any; input: unknown; next: (o?: any) => any }) => any>
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
const HTTP_FIELDS = ['path', 'method', 'req', 'res', 'successStatus'] as const
|
|
@@ -142,6 +144,39 @@ export function combineSignals(incoming: AbortSignal | undefined, own: AbortSign
|
|
|
142
144
|
return incoming ? AbortSignal.any([incoming, own]) : own
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Apply factory-defined middlewares around a core invocation.
|
|
149
|
+
* Middlewares run from first to last; each sees the (possibly augmented) ctx + input.
|
|
150
|
+
* Supports both unary results and async generators (for streams).
|
|
151
|
+
*/
|
|
152
|
+
export function runWithMiddlewares(
|
|
153
|
+
middlewares: Array<(opts: { ctx: any; input: unknown; next: (o?: any) => any }) => any>,
|
|
154
|
+
ctx: any,
|
|
155
|
+
input: unknown,
|
|
156
|
+
core: (c: any, i: unknown) => any
|
|
157
|
+
): any {
|
|
158
|
+
if (!middlewares.length) {
|
|
159
|
+
return core(ctx, input)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Build the chain from the inside out
|
|
163
|
+
let current = core
|
|
164
|
+
|
|
165
|
+
for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
166
|
+
const mw = middlewares[i]!
|
|
167
|
+
const previous = current
|
|
168
|
+
current = (c: any, i: unknown) =>
|
|
169
|
+
mw({
|
|
170
|
+
ctx: c,
|
|
171
|
+
input: i,
|
|
172
|
+
next: (o: any = {}) => previous(o.ctx ?? c, o.input ?? i),
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return current(ctx, input)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
145
180
|
/**
|
|
146
181
|
* Drives a user stream iterator with the framework guarantees shared by
|
|
147
182
|
* CreateStream and CreateHttpStream:
|
package/src/core/procedures.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type { AnyProcedureRegistration, ProceduresOptions, TNoContextProvided }
|
|
|
18
18
|
* schema: { adapters: [myZodAdapter] }, // TypeBox is built in
|
|
19
19
|
* http: { pathPrefix: '/v1', scope: 'billing' }, // CreateHttp* defaults
|
|
20
20
|
* onCreate: (procedure) => log(procedure.kind, procedure.name),
|
|
21
|
+
* // middleware: [ myFactoryGuard ], // runs with final ctx + input for every proc
|
|
21
22
|
* })
|
|
22
23
|
* ```
|
|
23
24
|
*
|
|
@@ -26,6 +27,8 @@ import type { AnyProcedureRegistration, ProceduresOptions, TNoContextProvided }
|
|
|
26
27
|
* - `TExtendedConfig` — extra per-procedure config fields (e.g. `scope`,
|
|
27
28
|
* `version`, auth flags) carried through to registrations, `info`, and
|
|
28
29
|
* `onCreate`.
|
|
30
|
+
* - `middleware` — factory-level middleware with access to the final ctx + input
|
|
31
|
+
* ("parse the incoming call") for authz, enrichment, etc. See `FactoryMiddleware`.
|
|
29
32
|
*/
|
|
30
33
|
export function Procedures<TContext = TNoContextProvided, TExtendedConfig = unknown>(
|
|
31
34
|
options?: ProceduresOptions<TContext, TExtendedConfig>,
|
|
@@ -39,6 +42,7 @@ export function Procedures<TContext = TNoContextProvided, TExtendedConfig = unkn
|
|
|
39
42
|
adapters: [...(options?.schema?.adapters ?? []), typeboxAdapter],
|
|
40
43
|
compile: createValidatorCompiler(options?.validation === false ? undefined : options?.validation),
|
|
41
44
|
httpDefaults: options?.http,
|
|
45
|
+
middlewares: options?.middleware ?? [],
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
const Create = makeCreate<TContext, TExtendedConfig>(runtime)
|
package/src/core/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as AJV from 'ajv'
|
|
2
2
|
import type { ProcedureError } from './errors.js'
|
|
3
3
|
import type { SchemaAdapter } from '../schema/adapter.js'
|
|
4
|
-
import type { TJSONSchema } from '../schema/json-schema.js'
|
|
4
|
+
import type { Infer, Prettify, TJSONSchema } from '../schema/json-schema.js'
|
|
5
5
|
import type { Validate } from '../schema/compile.js'
|
|
6
6
|
|
|
7
7
|
export type TNoContextProvided = unknown
|
|
@@ -69,7 +69,64 @@ export type TStreamProcedureRegistration<TContext = unknown, TExtendedConfig = u
|
|
|
69
69
|
|
|
70
70
|
export type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Canonical shape for `schema.req` channels.
|
|
74
|
+
* Used both to constrain generics and to provide autocomplete when writing
|
|
75
|
+
* `CreateHttp` / `CreateHttpStream` config objects.
|
|
76
|
+
*
|
|
77
|
+
* Note: RPC `Create` / `CreateStream` use a flatter `schema: { params?, yieldType?, returnType? }`
|
|
78
|
+
* directly (no extra container generic), so they don't need an equivalent named input shape.
|
|
79
|
+
*/
|
|
80
|
+
export type THttpReqInput = {
|
|
81
|
+
pathParams?: unknown
|
|
82
|
+
query?: unknown
|
|
83
|
+
body?: unknown
|
|
84
|
+
headers?: unknown
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Canonical shape for `schema.res` on CreateHttp (CreateHttpStream only supports `headers`).
|
|
89
|
+
*/
|
|
90
|
+
export type THttpResInput = {
|
|
91
|
+
body?: unknown
|
|
92
|
+
headers?: unknown
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @internal Helper to surface literal channel keys for autocomplete
|
|
97
|
+
* while still threading the user's concrete generic schemas through for inference.
|
|
98
|
+
*/
|
|
99
|
+
export type HttpSchemaInput<Base, T> = Base & (T extends undefined ? {} : T)
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @internal Shared helper for typing the destructured `req` argument passed to
|
|
103
|
+
* CreateHttp / CreateHttpStream handlers. Mirrors the per-channel Infer.
|
|
104
|
+
*/
|
|
105
|
+
export type HttpReq<TReq> = TReq extends Record<string, unknown>
|
|
106
|
+
? Prettify<{ [K in keyof TReq]: Infer<TReq[K]> }>
|
|
107
|
+
: undefined
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Shape used for the `schema` option of `CreateHttpStream`.
|
|
111
|
+
* Reuses the same req input shape as CreateHttp for autocomplete consistency.
|
|
112
|
+
*/
|
|
113
|
+
export type THttpStreamSchema<
|
|
114
|
+
TReq extends THttpReqInput | undefined = undefined,
|
|
115
|
+
TYield = unknown,
|
|
116
|
+
TReturn = void,
|
|
117
|
+
TResHeaders = undefined,
|
|
118
|
+
> = {
|
|
119
|
+
req?: HttpSchemaInput<THttpReqInput, TReq>
|
|
120
|
+
yield?: TYield
|
|
121
|
+
returnType?: TReturn
|
|
122
|
+
res?: TResHeaders extends undefined ? undefined : { headers: TResHeaders }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type TCreateHttpConfig<
|
|
126
|
+
TReq extends THttpReqInput | undefined = undefined,
|
|
127
|
+
TRes extends THttpResInput | undefined = undefined,
|
|
128
|
+
TErrorKey extends string = string,
|
|
129
|
+
> = {
|
|
73
130
|
path: string
|
|
74
131
|
method: HttpMethod
|
|
75
132
|
successStatus?: number
|
|
@@ -78,8 +135,10 @@ export type TCreateHttpConfig<TReq, TRes, TErrorKey extends string = string> = {
|
|
|
78
135
|
description?: string
|
|
79
136
|
/** Optional — a no-input, no-output route (e.g. a 204 logout) may omit it. */
|
|
80
137
|
schema?: {
|
|
81
|
-
|
|
82
|
-
|
|
138
|
+
// HttpSchemaInput surfaces the channel keys for IDE autocomplete / excess-property checking
|
|
139
|
+
// while the generic part carries the caller's concrete schema (for Infer + handler typing).
|
|
140
|
+
req?: HttpSchemaInput<THttpReqInput, TReq>
|
|
141
|
+
res?: HttpSchemaInput<THttpResInput, TRes>
|
|
83
142
|
}
|
|
84
143
|
}
|
|
85
144
|
|
|
@@ -193,4 +252,40 @@ export type ProceduresOptions<TContext = unknown, TExtendedConfig = unknown> = {
|
|
|
193
252
|
pathPrefix?: string
|
|
194
253
|
scope?: string
|
|
195
254
|
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Optional middleware that runs for *every* procedure defined via this factory.
|
|
258
|
+
* Runs after schema validation but before the user handler, with the *same*
|
|
259
|
+
* context the handler receives (including injected `error()` and `signal`).
|
|
260
|
+
*
|
|
261
|
+
* Use this for cross-cutting logic that needs the resolved `TContext` + the
|
|
262
|
+
* incoming call data ("parse the incoming call"):
|
|
263
|
+
* - Authorization / tenancy checks using `ctx.user` + `input`
|
|
264
|
+
* - Enriching ctx for all procedures on the factory
|
|
265
|
+
* - Auditing, feature flags, etc.
|
|
266
|
+
*
|
|
267
|
+
* The middleware receives a `next` function. Call `await next({ ctx?, input? })`
|
|
268
|
+
* to continue (optionally with augmented ctx or transformed input). You may
|
|
269
|
+
* also return/throw early.
|
|
270
|
+
*
|
|
271
|
+
* Works for both direct calls and all adapters. Complements (runs after)
|
|
272
|
+
* builder-level middleware on HonoAppBuilder.
|
|
273
|
+
*/
|
|
274
|
+
middleware?: FactoryMiddleware<TContext>[]
|
|
196
275
|
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Middleware defined on a Procedures factory.
|
|
279
|
+
*
|
|
280
|
+
* - `ctx` is the fully resolved context (your TContext + `error()` + `signal`).
|
|
281
|
+
* - `input` is the (already schema-validated) call argument:
|
|
282
|
+
* - For Create / CreateStream: the `params` object
|
|
283
|
+
* - For CreateHttp / CreateHttpStream: the `{ pathParams?, query?, body?, headers? }`
|
|
284
|
+
* - Call `next()` (or `next({ ctx: augmented, input: transformed })`) to proceed.
|
|
285
|
+
*/
|
|
286
|
+
export type FactoryMiddleware<TContext = unknown> = (opts: {
|
|
287
|
+
ctx: Prettify<TContext & TLocalContext>
|
|
288
|
+
input: unknown
|
|
289
|
+
next: (opts?: { ctx?: any; input?: unknown }) => Promise<any> | AsyncGenerator<any, any, unknown>
|
|
290
|
+
}) => Promise<any> | AsyncGenerator<any, any, unknown> | void
|
|
291
|
+
|
package/src/exports.ts
CHANGED
|
@@ -23,10 +23,16 @@ export type {
|
|
|
23
23
|
THttpProcedureRegistration,
|
|
24
24
|
THttpStreamProcedureRegistration,
|
|
25
25
|
TCreateHttpConfig,
|
|
26
|
+
THttpReqInput,
|
|
27
|
+
THttpResInput,
|
|
28
|
+
THttpStreamSchema,
|
|
29
|
+
HttpSchemaInput,
|
|
30
|
+
HttpReq,
|
|
26
31
|
TLocalContext,
|
|
27
32
|
TStreamContext,
|
|
28
33
|
TNoContextProvided,
|
|
29
34
|
HttpMethod,
|
|
35
|
+
FactoryMiddleware,
|
|
30
36
|
} from './core/types.js'
|
|
31
37
|
export type { HttpReturn } from './core/create-http.js'
|
|
32
38
|
export {
|
|
@@ -56,9 +62,5 @@ export type { Infer, TSchemaLib, TJSONSchema, Prettify } from './schema/json-sch
|
|
|
56
62
|
|
|
57
63
|
// Doc envelope — offline codegen input
|
|
58
64
|
export { writeDocEnvelope } from './server/doc-envelope.js'
|
|
59
|
-
// `generatorVersion` metadata lives on `ts-procedures/server`
|
|
60
|
-
// (DOC_ENVELOPE_GENERATOR_VERSION) — it's niche telemetry. The spec version is
|
|
61
|
-
// the one consumers branch on, so it gets the prominent top-level export.
|
|
62
|
-
export { DOC_ENVELOPE_SPEC_VERSION } from './server/spec-version.js'
|
|
63
65
|
export type { DocEnvelopeSource } from './server/doc-envelope.js'
|
|
64
66
|
export type { DocEnvelope } from './server/types.js'
|
|
@@ -9,18 +9,6 @@ describe('typebox schema detection', () => {
|
|
|
9
9
|
expect(isTypeboxSchema(typebox)).toBe(true)
|
|
10
10
|
})
|
|
11
11
|
|
|
12
|
-
test('it recognizes a top-level Type.Unsafe schema (~unsafe marker, no ~kind)', () => {
|
|
13
|
-
// typebox >= 1.2.0: Type.Unsafe carries `~unsafe` INSTEAD of `~kind`.
|
|
14
|
-
expect(isTypeboxSchema(Type.Unsafe({ type: 'string' }))).toBe(true)
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test('it recognizes any future ~-prefixed marker (convention, not a name list)', () => {
|
|
18
|
-
// Detection keys off TypeBox's `~` namespace convention, so a marker that
|
|
19
|
-
// doesn't exist yet is still recognized without a code change.
|
|
20
|
-
const futureMarker = { '~somethingNew': true, type: 'object' }
|
|
21
|
-
expect(isTypeboxSchema(futureMarker)).toBe(true)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
12
|
test('it rejects non-TypeBox values', () => {
|
|
25
13
|
expect(isTypeboxSchema({ type: 'object' })).toBe(false)
|
|
26
14
|
expect(isTypeboxSchema('string value')).toBe(false)
|
package/src/schema/typebox.ts
CHANGED
|
@@ -3,29 +3,10 @@ import type { TJSONSchema } from './json-schema.js'
|
|
|
3
3
|
|
|
4
4
|
const TYPEBOX_KIND = Symbol.for('TypeBox.Kind')
|
|
5
5
|
|
|
6
|
-
/** TypeBox v1 prefixes every internal marker key with `~`. */
|
|
7
|
-
const TILDE = '~'.charCodeAt(0)
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* TypeBox v1 namespaces all of its internal markers under a `~` prefix
|
|
11
|
-
* (`~kind`, `~unsafe`, `~optional`, ...) — non-enumerable own properties that
|
|
12
|
-
* stay out of the serialized JSON Schema. Detecting the *convention* rather
|
|
13
|
-
* than specific marker names means new markers (e.g. `~unsafe`, added in
|
|
14
|
-
* typebox 1.2.0 and used by `Type.Unsafe(...)` *instead of* `~kind`) are
|
|
15
|
-
* recognized without a code change. Reflect.ownKeys is required because these
|
|
16
|
-
* markers are non-enumerable.
|
|
17
|
-
*/
|
|
18
|
-
function hasTypeBoxV1Marker(schema: object): boolean {
|
|
19
|
-
for (const key of Reflect.ownKeys(schema)) {
|
|
20
|
-
if (typeof key === 'string' && key.charCodeAt(0) === TILDE) return true
|
|
21
|
-
}
|
|
22
|
-
return false
|
|
23
|
-
}
|
|
24
|
-
|
|
25
6
|
/**
|
|
26
7
|
* Built-in adapter for TypeBox (`import { Type } from 'typebox'`).
|
|
27
8
|
*
|
|
28
|
-
* Detection covers both typebox v1 (
|
|
9
|
+
* Detection covers both typebox v1 (`~kind` property) and legacy
|
|
29
10
|
* `@sinclair/typebox` 0.3x (`Symbol.for('TypeBox.Kind')`). TypeBox schemas
|
|
30
11
|
* already ARE JSON Schema, so conversion is the identity.
|
|
31
12
|
*/
|
|
@@ -33,7 +14,7 @@ export const typeboxAdapter: SchemaAdapter = {
|
|
|
33
14
|
name: 'typebox',
|
|
34
15
|
detect(schema: unknown): boolean {
|
|
35
16
|
if (typeof schema !== 'object' || schema === null) return false
|
|
36
|
-
return
|
|
17
|
+
return '~kind' in schema || TYPEBOX_KIND in schema
|
|
37
18
|
},
|
|
38
19
|
toJsonSchema(schema: unknown): TJSONSchema {
|
|
39
20
|
return schema as TJSONSchema
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { resolveFactoryContext } from './context.js'
|
|
2
|
+
import { resolveFactoryContext, composeContextInits } from './context.js'
|
|
3
3
|
|
|
4
4
|
describe('resolveFactoryContext', () => {
|
|
5
5
|
it('returns a plain value as-is', async () => {
|
|
@@ -19,4 +19,42 @@ describe('resolveFactoryContext', () => {
|
|
|
19
19
|
resolveFactoryContext(async () => ({ ready: true }), null),
|
|
20
20
|
).resolves.toEqual({ ready: true })
|
|
21
21
|
})
|
|
22
|
+
|
|
23
|
+
it('resolves an array of inits with shallow merge (later wins)', async () => {
|
|
24
|
+
const raw = { id: 'r1' }
|
|
25
|
+
const composed = [
|
|
26
|
+
{ a: 1 },
|
|
27
|
+
(r: any) => ({ b: r.id }),
|
|
28
|
+
async () => ({ c: 3 }),
|
|
29
|
+
]
|
|
30
|
+
await expect(resolveFactoryContext(composed as any, raw)).resolves.toEqual({
|
|
31
|
+
a: 1,
|
|
32
|
+
b: 'r1',
|
|
33
|
+
c: 3,
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('later layers override earlier keys', async () => {
|
|
38
|
+
const layers = [{ tenant: 't1' }, { tenant: 't2', extra: true }]
|
|
39
|
+
await expect(resolveFactoryContext(layers as any, {})).resolves.toEqual({
|
|
40
|
+
tenant: 't2',
|
|
41
|
+
extra: true,
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('composeContextInits', () => {
|
|
47
|
+
it('returns a reusable composed initializer', async () => {
|
|
48
|
+
const init = composeContextInits<{ token: string | undefined }, { token?: string }>(
|
|
49
|
+
{ base: true } as any,
|
|
50
|
+
(r: { token?: string }) => ({ token: r.token }),
|
|
51
|
+
)
|
|
52
|
+
const out = await resolveFactoryContext(init, { token: 'abc' })
|
|
53
|
+
expect(out).toEqual({ base: true, token: 'abc' })
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('handles empty compose as empty object', async () => {
|
|
57
|
+
const init = composeContextInits()
|
|
58
|
+
await expect(resolveFactoryContext(init, {})).resolves.toEqual({})
|
|
59
|
+
})
|
|
22
60
|
})
|
package/src/server/context.ts
CHANGED
|
@@ -2,17 +2,67 @@
|
|
|
2
2
|
* A factory context as accepted by server adapters' `register()`: either the
|
|
3
3
|
* context value itself, or a function deriving it from the adapter's request
|
|
4
4
|
* context per request (sync or async).
|
|
5
|
+
*
|
|
6
|
+
* Arrays are supported for composition (resolved left-to-right with shallow
|
|
7
|
+
* merge of object results). Use `composeContextInits` for a reusable composed
|
|
8
|
+
* initializer.
|
|
5
9
|
*/
|
|
6
10
|
export type FactoryContextInit<TContext, TRaw> =
|
|
7
11
|
| TContext
|
|
8
12
|
| ((raw: TRaw) => TContext | Promise<TContext>)
|
|
13
|
+
| Array<FactoryContextInit<TContext, TRaw>>
|
|
9
14
|
|
|
10
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Compose multiple context initializers (values or functions) into one.
|
|
17
|
+
* Later initializers shallow-merge over earlier results (object case).
|
|
18
|
+
* Functions are resolved in order against the raw + the accumulating context.
|
|
19
|
+
*/
|
|
20
|
+
export function composeContextInits<TContext, TRaw>(
|
|
21
|
+
...inits: FactoryContextInit<TContext, TRaw>[]
|
|
22
|
+
): FactoryContextInit<TContext, TRaw> {
|
|
23
|
+
const flat = inits.flat() as FactoryContextInit<TContext, TRaw>[]
|
|
24
|
+
if (flat.length === 0) return {} as TContext
|
|
25
|
+
if (flat.length === 1) return flat[0]!
|
|
26
|
+
|
|
27
|
+
return async (raw: TRaw): Promise<TContext> => {
|
|
28
|
+
let acc: any = {}
|
|
29
|
+
for (const init of flat) {
|
|
30
|
+
const part = typeof init === 'function'
|
|
31
|
+
? await (init as (r: TRaw) => TContext | Promise<TContext>)(raw)
|
|
32
|
+
: init
|
|
33
|
+
if (part && typeof part === 'object') {
|
|
34
|
+
acc = { ...acc, ...part }
|
|
35
|
+
} else if (part !== undefined) {
|
|
36
|
+
// Non-object last-wins (rare, but supported)
|
|
37
|
+
acc = part
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return acc as TContext
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Resolves a {@link FactoryContextInit} (or array) for one request. */
|
|
11
45
|
export async function resolveFactoryContext<TContext, TRaw>(
|
|
12
46
|
init: FactoryContextInit<TContext, TRaw>,
|
|
13
47
|
raw: TRaw,
|
|
14
48
|
): Promise<TContext> {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
:
|
|
49
|
+
if (Array.isArray(init)) {
|
|
50
|
+
// Use the same merge loop here to avoid calling a potentially non-fn value.
|
|
51
|
+
let acc: any = {}
|
|
52
|
+
for (const i of init) {
|
|
53
|
+
const part = typeof i === 'function'
|
|
54
|
+
? await (i as (r: TRaw) => TContext | Promise<TContext>)(raw)
|
|
55
|
+
: i
|
|
56
|
+
if (part && typeof part === 'object') {
|
|
57
|
+
acc = { ...acc, ...part }
|
|
58
|
+
} else if (part !== undefined) {
|
|
59
|
+
acc = part
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return acc as TContext
|
|
63
|
+
}
|
|
64
|
+
if (typeof init === 'function') {
|
|
65
|
+
return await (init as (raw: TRaw) => TContext | Promise<TContext>)(raw)
|
|
66
|
+
}
|
|
67
|
+
return init
|
|
18
68
|
}
|
|
@@ -5,9 +5,7 @@ import type {
|
|
|
5
5
|
THttpStreamProcedureRegistration,
|
|
6
6
|
TProcedureRegistration,
|
|
7
7
|
} from '../core/types.js'
|
|
8
|
-
import pkg from '../../package.json' with { type: 'json' }
|
|
9
8
|
import { DocRegistry } from './doc-registry.js'
|
|
10
|
-
import { DOC_ENVELOPE_SPEC_VERSION, DOC_ENVELOPE_GENERATOR_VERSION } from './spec-version.js'
|
|
11
9
|
import { defineErrorTaxonomy } from './errors/taxonomy.js'
|
|
12
10
|
import { buildRpcRouteDoc } from './docs/rpc-doc.js'
|
|
13
11
|
import { buildHttpStreamRouteDoc } from './docs/http-stream-doc.js'
|
|
@@ -203,38 +201,6 @@ describe('DocRegistry', () => {
|
|
|
203
201
|
})
|
|
204
202
|
})
|
|
205
203
|
|
|
206
|
-
// --------------------------------------------------------------------------
|
|
207
|
-
// toJSON() spec metadata
|
|
208
|
-
// --------------------------------------------------------------------------
|
|
209
|
-
describe('toJSON() spec metadata', () => {
|
|
210
|
-
test('emits specVersion (the constant, a number) and generatorVersion (the package version)', () => {
|
|
211
|
-
const out = new DocRegistry().toJSON()
|
|
212
|
-
expect(out.specVersion).toBe(DOC_ENVELOPE_SPEC_VERSION)
|
|
213
|
-
expect(typeof out.specVersion).toBe('number')
|
|
214
|
-
expect(out.generatorVersion).toBe(pkg.version)
|
|
215
|
-
// the exported constant must track the real package version
|
|
216
|
-
expect(DOC_ENVELOPE_GENERATOR_VERSION).toBe(pkg.version)
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
test('metadata is present even on an otherwise-empty envelope', () => {
|
|
220
|
-
const out = new DocRegistry({ includeDefaults: false }).toJSON()
|
|
221
|
-
expect(out).toMatchObject({
|
|
222
|
-
specVersion: DOC_ENVELOPE_SPEC_VERSION,
|
|
223
|
-
generatorVersion: pkg.version,
|
|
224
|
-
})
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
test('transform receives the metadata on its envelope argument', () => {
|
|
228
|
-
const seen = new DocRegistry().toJSON({
|
|
229
|
-
transform: ({ specVersion, generatorVersion }) => ({ specVersion, generatorVersion }),
|
|
230
|
-
})
|
|
231
|
-
expect(seen).toEqual({
|
|
232
|
-
specVersion: DOC_ENVELOPE_SPEC_VERSION,
|
|
233
|
-
generatorVersion: pkg.version,
|
|
234
|
-
})
|
|
235
|
-
})
|
|
236
|
-
})
|
|
237
|
-
|
|
238
204
|
// --------------------------------------------------------------------------
|
|
239
205
|
// toJSON() filter
|
|
240
206
|
// --------------------------------------------------------------------------
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
taxonomyToErrorDocs,
|
|
14
14
|
type ErrorTaxonomy,
|
|
15
15
|
} from './errors/taxonomy.js'
|
|
16
|
-
import { DOC_ENVELOPE_SPEC_VERSION, DOC_ENVELOPE_GENERATOR_VERSION } from './spec-version.js'
|
|
17
16
|
|
|
18
17
|
export type {
|
|
19
18
|
AnyHttpRouteDoc,
|
|
@@ -113,8 +112,6 @@ export class DocRegistry {
|
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
const envelope: DocEnvelope = {
|
|
116
|
-
specVersion: DOC_ENVELOPE_SPEC_VERSION,
|
|
117
|
-
generatorVersion: DOC_ENVELOPE_GENERATOR_VERSION,
|
|
118
115
|
basePath: this.basePath,
|
|
119
116
|
headers: [...this.headers],
|
|
120
117
|
errors: [...this.errors],
|
package/src/server/index.ts
CHANGED
|
@@ -25,6 +25,5 @@ export { buildStreamRouteDoc } from './docs/stream-doc.js'
|
|
|
25
25
|
export { buildHttpRouteDoc } from './docs/http-doc.js'
|
|
26
26
|
export { buildHttpStreamRouteDoc } from './docs/http-stream-doc.js'
|
|
27
27
|
export { DocRegistry } from './doc-registry.js'
|
|
28
|
-
export { DOC_ENVELOPE_SPEC_VERSION, DOC_ENVELOPE_GENERATOR_VERSION } from './spec-version.js'
|
|
29
28
|
export { writeDocEnvelope } from './doc-envelope.js'
|
|
30
29
|
export type { DocEnvelopeSource } from './doc-envelope.js'
|
package/src/server/types.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* guarantees byte-identical generated output vs v8. Do not change field names,
|
|
7
7
|
* optionality, or nesting without re-validating the codegen goldens.
|
|
8
8
|
*/
|
|
9
|
-
import type { ProcedureKind } from '../core/types.js'
|
|
9
|
+
import type { ProcedureKind, THttpReqInput } from '../core/types.js'
|
|
10
10
|
import type { ErrorTaxonomy } from './errors/taxonomy.js'
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -97,6 +97,9 @@ export interface APIHttpRouteDoc {
|
|
|
97
97
|
* Constrains `schema.req` channel names to valid HTTP input sources.
|
|
98
98
|
* Use with `satisfies` to catch typos at compile time:
|
|
99
99
|
*
|
|
100
|
+
* The same shape also powers autocomplete inside `schema.req` when using
|
|
101
|
+
* CreateHttp / CreateHttpStream.
|
|
102
|
+
*
|
|
100
103
|
* @example
|
|
101
104
|
* schema: {
|
|
102
105
|
* req: {
|
|
@@ -105,17 +108,10 @@ export interface APIHttpRouteDoc {
|
|
|
105
108
|
* } satisfies APIInput
|
|
106
109
|
* }
|
|
107
110
|
*/
|
|
108
|
-
export type APIInput<T extends
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
headers?: unknown
|
|
113
|
-
} = {
|
|
114
|
-
pathParams?: unknown
|
|
115
|
-
query?: unknown
|
|
116
|
-
body?: unknown
|
|
117
|
-
headers?: unknown
|
|
118
|
-
}> = T
|
|
111
|
+
export type APIInput<T extends THttpReqInput = THttpReqInput> = T
|
|
112
|
+
|
|
113
|
+
// Re-export the canonical input shapes (useful for advanced typing or custom wrappers)
|
|
114
|
+
export type { THttpReqInput, THttpResInput } from '../core/types.js'
|
|
119
115
|
|
|
120
116
|
export interface StreamHttpRouteDoc extends RPCConfig {
|
|
121
117
|
kind: 'stream'
|
|
@@ -250,21 +246,6 @@ export interface DocRegistryOutputOptions<TEnvelope = DocEnvelope> {
|
|
|
250
246
|
}
|
|
251
247
|
|
|
252
248
|
export interface DocEnvelope {
|
|
253
|
-
/**
|
|
254
|
-
* Wire-format version of this envelope. Every envelope produced by
|
|
255
|
-
* `DocRegistry.toJSON()` / `builder.toDocEnvelope()` carries it. Downstream
|
|
256
|
-
* consumers branch on it to pick a parser; bumped only on envelope format
|
|
257
|
-
* changes, NOT on package releases. Optional on the type so hand-built
|
|
258
|
-
* envelopes and pre-existing documents remain valid — an absent value MUST be
|
|
259
|
-
* treated as spec version 1. See {@link DOC_ENVELOPE_SPEC_VERSION}.
|
|
260
|
-
*/
|
|
261
|
-
specVersion?: number
|
|
262
|
-
/**
|
|
263
|
-
* ts-procedures package version that produced this envelope (e.g. `"10.0.0"`).
|
|
264
|
-
* Informational only — do not use for parse-compatibility decisions (use
|
|
265
|
-
* {@link specVersion} for that). See {@link DOC_ENVELOPE_GENERATOR_VERSION}.
|
|
266
|
-
*/
|
|
267
|
-
generatorVersion?: string
|
|
268
249
|
basePath: string
|
|
269
250
|
headers: HeaderDoc[]
|
|
270
251
|
errors: ErrorDoc[]
|