ts-procedures 5.7.2 → 5.9.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.
Files changed (50) hide show
  1. package/README.md +7 -1051
  2. package/agent_config/claude-code/skills/guide/api-reference.md +21 -16
  3. package/agent_config/claude-code/skills/guide/patterns.md +3 -1
  4. package/agent_config/copilot/copilot-instructions.md +7 -5
  5. package/agent_config/cursor/cursorrules +7 -5
  6. package/build/codegen/bin/cli.d.ts +2 -0
  7. package/build/codegen/bin/cli.js +21 -10
  8. package/build/codegen/bin/cli.js.map +1 -1
  9. package/build/codegen/bin/cli.test.js +44 -2
  10. package/build/codegen/bin/cli.test.js.map +1 -1
  11. package/build/codegen/emit-errors.d.ts +4 -1
  12. package/build/codegen/emit-errors.js +11 -5
  13. package/build/codegen/emit-errors.js.map +1 -1
  14. package/build/codegen/emit-errors.test.js +37 -0
  15. package/build/codegen/emit-errors.test.js.map +1 -1
  16. package/build/codegen/emit-index.d.ts +3 -1
  17. package/build/codegen/emit-index.js +6 -13
  18. package/build/codegen/emit-index.js.map +1 -1
  19. package/build/codegen/emit-index.test.js +23 -0
  20. package/build/codegen/emit-index.test.js.map +1 -1
  21. package/build/codegen/emit-scope.js +17 -13
  22. package/build/codegen/emit-scope.js.map +1 -1
  23. package/build/codegen/emit-scope.test.js +166 -0
  24. package/build/codegen/emit-scope.test.js.map +1 -1
  25. package/build/codegen/index.d.ts +1 -0
  26. package/build/codegen/index.js +1 -0
  27. package/build/codegen/index.js.map +1 -1
  28. package/build/codegen/naming.d.ts +7 -0
  29. package/build/codegen/naming.js +21 -0
  30. package/build/codegen/naming.js.map +1 -0
  31. package/build/codegen/naming.test.d.ts +1 -0
  32. package/build/codegen/naming.test.js +40 -0
  33. package/build/codegen/naming.test.js.map +1 -0
  34. package/build/codegen/pipeline.d.ts +1 -0
  35. package/build/codegen/pipeline.js +7 -3
  36. package/build/codegen/pipeline.js.map +1 -1
  37. package/build/codegen/pipeline.test.js +60 -0
  38. package/build/codegen/pipeline.test.js.map +1 -1
  39. package/docs/ai-agent-setup.md +61 -0
  40. package/docs/client-and-codegen.md +193 -0
  41. package/docs/core.md +473 -0
  42. package/docs/http-integrations.md +183 -0
  43. package/docs/streaming.md +199 -0
  44. package/docs/superpowers/plans/2026-03-30-client-codegen.md +2833 -0
  45. package/docs/superpowers/specs/2026-03-30-client-codegen-design.md +632 -0
  46. package/package.json +6 -1
  47. package/src/implementations/http/README.md +324 -0
  48. package/src/implementations/http/express-rpc/README.md +281 -0
  49. package/src/implementations/http/hono-rpc/README.md +358 -0
  50. package/src/implementations/http/hono-stream/README.md +525 -0
package/docs/core.md ADDED
@@ -0,0 +1,473 @@
1
+ [Home](../README.md) | **Core** | [Streaming](./streaming.md) | [HTTP Integrations](./http-integrations.md) | [Client & Codegen](./client-and-codegen.md) | [AI Agent Setup](./ai-agent-setup.md)
2
+
3
+ # Core Procedures
4
+
5
+ ts-procedures creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and procedure documentation/configuration.
6
+
7
+ ## Procedures Factory
8
+
9
+ The `Procedures()` function creates a factory for defining procedures. It accepts two generic type parameters:
10
+
11
+ ```typescript
12
+ Procedures<TContext, TExtendedConfig>(builder?: {
13
+ onCreate?: (procedure: TProcedureRegistration<TContext, TExtendedConfig>) => void
14
+ })
15
+ ```
16
+
17
+ | Parameter | Description |
18
+ |-----------|----------------------------------------------------------------------------|
19
+ | `TContext` | The base context type passed to all handlers as the first parameter |
20
+ | `TExtendedConfig` | Additional configuration properties for all procedures `config` properties |
21
+ | `builder.onCreate` | Optional callback invoked when each procedure is registered (runtime) |
22
+
23
+ ## Create Function
24
+
25
+ The `Create` function defines individual procedures:
26
+
27
+ ```typescript
28
+ Create(name, config, handler)
29
+ ```
30
+
31
+ **Returns:**
32
+ - `{ [name]: handler }` - Named export for the handler
33
+ - `procedure` - Generic reference to the handler
34
+ - `info` - Procedure meta (name, description, schema, `TExtendedConfig` properties, etc.)
35
+
36
+ ## Structured Input with schema.input
37
+
38
+ For HTTP APIs and other multi-channel transports, `schema.input` provides per-channel type safety. Each key is an independently validated input channel:
39
+
40
+ ```typescript
41
+ const { Create } = Procedures<AppContext, APIConfig>()
42
+
43
+ const { UpdateUser } = Create(
44
+ 'UpdateUser',
45
+ {
46
+ path: '/users/:id',
47
+ method: 'put',
48
+ schema: {
49
+ input: {
50
+ pathParams: Type.Object({ id: Type.String() }),
51
+ query: Type.Object({ notify: Type.Optional(Type.Boolean()) }),
52
+ body: Type.Object({ name: Type.String(), email: Type.String() }),
53
+ },
54
+ returnType: Type.Object({ ok: Type.Boolean() }),
55
+ },
56
+ },
57
+ async (ctx, { pathParams, query, body }) => {
58
+ // Each channel is independently typed and validated
59
+ await updateUser(pathParams.id, body)
60
+ if (query.notify) await sendNotification(pathParams.id)
61
+ return { ok: true }
62
+ }
63
+ )
64
+ ```
65
+
66
+ **Rules:**
67
+ - `schema.input` and `schema.params` are **mutually exclusive** — defining both throws `ProcedureRegistrationError`
68
+ - Each channel is validated independently with per-channel error messages
69
+ - Works with both `Create` and `CreateStream`
70
+
71
+ ## CreateStream Function
72
+
73
+ The `CreateStream` function defines streaming procedures that yield values over time using async generators:
74
+
75
+ ```typescript
76
+ CreateStream(name, config, handler)
77
+ ```
78
+
79
+ **Config Options:**
80
+ - `schema.params` - Input parameter schema (validated at runtime)
81
+ - `schema.yieldType` - Schema for each yielded value (validated if `validateYields: true`)
82
+ - `schema.returnType` - Schema for final return value (documentation only)
83
+ - `validateYields` - Enable runtime validation of yielded values (default: `false`)
84
+
85
+ **Handler Signature:**
86
+ ```typescript
87
+ async function* (ctx, params) => AsyncGenerator<TYield, TReturn | void>
88
+ ```
89
+
90
+ **Context Extensions (all handlers):**
91
+ - `ctx.error(message, meta?)` - Create a ProcedureError
92
+ - `ctx.signal?` - AbortSignal for cancellation support (optional for `Create`, always present for `CreateStream`)
93
+
94
+ When using the built-in HTTP implementations (Hono, Express), `ctx.signal` is automatically injected from the HTTP request, so handlers can detect client disconnection. For direct usage without a server, `signal` is `undefined` unless you pass one in context.
95
+
96
+ **Returns:**
97
+ - `{ [name]: handler }` - Named generator export
98
+ - `procedure` - Generic reference to the generator
99
+ - `info` - Procedure meta with `isStream: true`
100
+
101
+ For detailed streaming documentation (yield validation, abort signals, SSE integration), see [Streaming Procedures](./streaming.md).
102
+
103
+ ## Using Generics
104
+
105
+ ### Base Context
106
+
107
+ Define a shared context type for all procedures in your application:
108
+
109
+ ```typescript
110
+ interface AppContext {
111
+ authToken: string
112
+ requestId: string
113
+ logger: Logger
114
+ }
115
+
116
+ const { Create } = Procedures<AppContext>()
117
+
118
+ const { SecureEndpoint } = Create(
119
+ 'SecureEndpoint',
120
+ {},
121
+ async (ctx, params) => {
122
+ // ctx.authToken is typed as string
123
+ // ctx.requestId is typed as string
124
+ // ctx.logger is typed as Logger
125
+ return { token: ctx.authToken }
126
+ },
127
+ )
128
+
129
+ // When calling, you must provide the context
130
+ await SecureEndpoint({ authToken: 'abc', requestId: '123', logger: myLogger }, {})
131
+ ```
132
+
133
+ ### Extended Configuration
134
+
135
+ Add custom properties to all procedure configs:
136
+
137
+ ```typescript
138
+ interface ExtendedConfig {
139
+ permissions: string[]
140
+ rateLimit?: number
141
+ cacheTTL?: number
142
+ }
143
+
144
+ const { Create } = Procedures<AppContext, ExtendedConfig>()
145
+
146
+ const { AdminOnly } = Create(
147
+ 'AdminOnly',
148
+ {
149
+ permissions: ['admin'], // Required by ExtendedConfig
150
+ rateLimit: 100, // Optional
151
+ description: 'Admin-only endpoint',
152
+ },
153
+ async (ctx, params) => {
154
+ return { admin: true }
155
+ },
156
+ )
157
+
158
+ // Access extended config via info
159
+ console.log(AdminOnly.info.permissions) // ['admin']
160
+ ```
161
+
162
+ ### Combined Example
163
+
164
+ ```typescript
165
+ interface CustomContext {
166
+ authToken: string
167
+ tenantId: string
168
+ }
169
+
170
+ interface ExtendedConfig {
171
+ requiresAuth: boolean
172
+ auditLog?: boolean
173
+ }
174
+
175
+ const { Create, getProcedures } = Procedures<CustomContext, ExtendedConfig>({
176
+ onCreate: (procedure) => {
177
+ // Register with your framework
178
+ console.log(`Registered: ${procedure.name}`)
179
+ console.log(`Requires Auth: ${procedure.config.requiresAuth}`)
180
+ },
181
+ })
182
+
183
+ const { CreateUser } = Create(
184
+ 'CreateUser',
185
+ {
186
+ requiresAuth: true,
187
+ auditLog: true,
188
+ description: 'Creates a new user',
189
+ schema: {
190
+ params: Type.Object({
191
+ email: Type.String(),
192
+ name: Type.String(),
193
+ }),
194
+ returnType: Type.Object({ id: Type.String() }),
195
+ },
196
+ },
197
+ async (ctx, params) => {
198
+ // Both context and params are fully typed
199
+ return { id: 'user-123' }
200
+ },
201
+ )
202
+ ```
203
+
204
+ ## Schema Validation
205
+
206
+ ts-procedures supports two schema libraries — **TypeBox** (recommended) and **Suretype**. The library is auto-detected from the schema object. Both work identically at runtime; choose whichever you prefer:
207
+
208
+ ```typescript
209
+ // TypeBox
210
+ import { Type } from 'typebox'
211
+ schema: { params: Type.Object({ title: Type.String() }) }
212
+
213
+ // Suretype
214
+ import { v } from 'suretype'
215
+ schema: { params: v.object({ title: v.string().required() }) }
216
+ ```
217
+
218
+ ### Validation Behavior
219
+
220
+ AJV is configured with:
221
+ - `allErrors: true` - Report all validation errors
222
+ - `coerceTypes: true` - Automatically coerce types when possible
223
+ - `removeAdditional: true` - Strip properties not in schema
224
+
225
+ **Note:** `schema.params` is validated at runtime. `schema.returnType` is for documentation/introspection only.
226
+
227
+ ## Error Handling
228
+
229
+ ### Using ctx.error()
230
+
231
+ The `error()` function is injected into both hooks and handlers:
232
+
233
+ ```typescript
234
+ Create(
235
+ 'GetResource',
236
+ {},
237
+ async (ctx, params) => {
238
+ const resource = await db.find(params.id)
239
+ if (!resource) {
240
+ throw ctx.error(404, 'Resource not found', { id: params.id })
241
+ }
242
+ return resource
243
+ },
244
+ )
245
+ ```
246
+
247
+ ### Error Classes
248
+
249
+ | Error Class | Trigger |
250
+ |-------------|---------|
251
+ | ProcedureError | `ctx.error()` in handlers |
252
+ | ProcedureValidationError | Schema validation failure (params) |
253
+ | ProcedureYieldValidationError | Yield validation failure (streaming with `validateYields: true`) |
254
+ | ProcedureRegistrationError | Invalid schema at registration |
255
+
256
+ ### Error Properties
257
+
258
+ ```typescript
259
+ try {
260
+ await MyProcedure(ctx, params)
261
+ } catch (e) {
262
+ if (e instanceof ProcedureError) {
263
+ console.log(e.procedureName) // 'MyProcedure'
264
+ console.log(e.message) // 'Resource not found'
265
+ console.log(e.meta) // { id: '123' }
266
+ }
267
+ }
268
+ ```
269
+
270
+ ## Abort Signal
271
+
272
+ For regular procedures, `ctx.signal` is available when the server implementation provides it. The built-in HTTP integrations (Hono RPC, Express RPC) inject the request's abort signal automatically:
273
+
274
+ ```typescript
275
+ const { Create } = Procedures<{ signal: AbortSignal }>()
276
+
277
+ const { LongQuery } = Create(
278
+ 'LongQuery',
279
+ {},
280
+ async (ctx, params) => {
281
+ // Pass signal to downstream operations
282
+ const result = await fetch('https://api.example.com/data', {
283
+ signal: ctx.signal,
284
+ })
285
+ return result.json()
286
+ },
287
+ )
288
+ ```
289
+
290
+ When using the Hono or Express implementations, `ctx.signal` aborts when the client disconnects, automatically cancelling in-flight `fetch()` calls, database queries, or any other signal-aware operation.
291
+
292
+ For streaming abort signal behavior (including `signal.reason` and consumer break detection), see [Streaming Procedures](./streaming.md#abort-signal-integration).
293
+
294
+ ## Framework Integration
295
+
296
+ ### onCreate Callback
297
+
298
+ Register procedures with your framework (Express, Fastify, etc.):
299
+
300
+ ```typescript
301
+ import express from 'express'
302
+
303
+ const app = express()
304
+ const routes: Map<string, Function> = new Map()
305
+
306
+ const { Create } = Procedures<{ req: Request; res: Response }>({
307
+ onCreate: ({ name, handler, config }) => {
308
+ // Register as Express route
309
+ app.post(`/rpc/${name}`, async (req, res) => {
310
+ try {
311
+ const result = await handler({ req, res }, req.body)
312
+ res.json(result)
313
+ } catch (e) {
314
+ if (e instanceof ProcedureError) {
315
+ res.status(500).json({ error: e.message })
316
+ } else {
317
+ res.status(500).json({ error: 'Internal error' })
318
+ }
319
+ }
320
+ })
321
+ },
322
+ })
323
+
324
+ // Procedures are automatically registered as /rpc/GetUser, /rpc/CreateUser, etc.
325
+ ```
326
+
327
+ For built-in HTTP framework integrations (Express RPC, Hono RPC, Hono API, Hono Stream), see [HTTP Integrations](./http-integrations.md).
328
+
329
+ ### Introspection with getProcedures()
330
+
331
+ Access all registered procedures for documentation or routing:
332
+
333
+ ```typescript
334
+ const { Create, getProcedures } = Procedures()
335
+
336
+ Create('GetUser', { schema: { params: Type.Object({ id: Type.String() }) } }, async () => {})
337
+ Create('ListUsers', { schema: { params: Type.Object({}) } }, async () => {})
338
+
339
+ // Get all registered procedures
340
+ const procedures = getProcedures()
341
+
342
+ // Generate OpenAPI spec
343
+ for (const config of procedures) {
344
+ console.log(`${config.name}:`, config.schema)
345
+ }
346
+ ```
347
+
348
+ ## Testing
349
+
350
+ Procedures return handlers that can be called directly in tests:
351
+
352
+ ```typescript
353
+ import { describe, test, expect } from 'vitest'
354
+ import { Procedures } from 'ts-procedures'
355
+ import { Type } from 'typebox'
356
+
357
+ interface MyCustomContext {
358
+ userId?: string
359
+ userName?: string
360
+ }
361
+
362
+ const { Create } = Procedures<MyCustomContext>()
363
+
364
+ const { GetUser, info } = Create(
365
+ 'GetUser',
366
+ {
367
+ schema: {
368
+ params: Type.Object({ hideName: Type.Optional(Type.Boolean()) }),
369
+ returnType: Type.Object({ id: Type.String(), name: Type.String() }),
370
+ },
371
+ },
372
+ async (ctx, params) => {
373
+ if (!params.userName || !ctx.userId) {
374
+ throw ctx.error('User is not authenticated')
375
+ }
376
+
377
+ return {
378
+ id: params.userId,
379
+ name: params?.hideName ? '*******' : params.userName
380
+ }
381
+ },
382
+ )
383
+
384
+ describe('GetUser', () => {
385
+ test('returns user', async () => {
386
+ const result = await GetUser({userId:'123',userName:'Ray'}, { hideName: false })
387
+ expect(result).toEqual({ id: '123', name: 'Ray' })
388
+ })
389
+
390
+ test('hides user name', async () => {
391
+ const result = await GetUser({userId:'123',userName:'Ray'}, { hideName: true })
392
+ expect(result).toEqual({ id: '123', name: '*******' })
393
+ })
394
+
395
+ test('validates params', async () => {
396
+ await expect(GetUser({}, {})).rejects.toThrow(ProcedureValidationError)
397
+ })
398
+
399
+ test('has correct schema', () => {
400
+ expect(info.schema.params).toEqual({
401
+ type: 'object',
402
+ properties: { id: { type: 'string' } },
403
+ required: ['id'],
404
+ })
405
+ })
406
+ })
407
+ ```
408
+
409
+ ## API Reference
410
+
411
+ ### Procedures(builder?)
412
+
413
+ Creates a procedure factory.
414
+
415
+ **Parameters:**
416
+ - `builder.onCreate` - Callback invoked when each procedure is registered
417
+
418
+ **Returns:**
419
+ - `Create` - Function to define procedures
420
+ - `getProcedures()` - Returns `Array` of all registered procedures
421
+
422
+ ### Create(name, config, handler)
423
+
424
+ Defines a procedure.
425
+
426
+ **Parameters:**
427
+ - `name` - Unique procedure name (becomes named export)
428
+ - `config.description` - Optional description
429
+ - `config.schema.params` - Suretype or TypeBox schema for params (validated at runtime)
430
+ - `config.schema.returnType` - Suretype or TypeBox schema for return returnType (documentation only)
431
+ - Additional properties from `TExtendedConfig`
432
+ - `handler` - Async function `(ctx, params) => Promise<returnType>`
433
+
434
+ **Returns:**
435
+ - `{ [name]: handler }` - Named handler export
436
+ - `procedure` - Generic handler reference
437
+ - `info` - Procedure metareturnType
438
+
439
+ ### Type Exports
440
+
441
+ ```typescript
442
+ import {
443
+ // Core
444
+ Procedures,
445
+
446
+ // Errors
447
+ ProcedureError,
448
+ ProcedureValidationError,
449
+ ProcedureRegistrationError,
450
+ ProcedureYieldValidationError, // For streaming yield validation
451
+
452
+ // Types
453
+ TLocalContext,
454
+ TStreamContext, // Streaming context (AbortSignal always present)
455
+ TProcedureRegistration,
456
+ TStreamProcedureRegistration, // Streaming procedure registration
457
+ TNoContextProvided,
458
+
459
+ // Schema utilities
460
+ extractJsonSchema,
461
+ schemaParser,
462
+ isTypeboxSchema,
463
+ isSuretypeSchema,
464
+
465
+ // Schema types
466
+ TJSONSchema,
467
+ TSchemaLib,
468
+ TSchemaLibGenerator, // AsyncGenerator type utility
469
+ TSchemaParsed,
470
+ TSchemaValidationError,
471
+ Prettify,
472
+ } from 'ts-procedures'
473
+ ```
@@ -0,0 +1,183 @@
1
+ [Home](../README.md) | [Core](./core.md) | [Streaming](./streaming.md) | **HTTP Integrations** | [Client & Codegen](./client-and-codegen.md) | [AI Agent Setup](./ai-agent-setup.md)
2
+
3
+ # HTTP Framework Integrations
4
+
5
+ ts-procedures includes built-in HTTP builders for Express and Hono that handle routing, context resolution, lifecycle hooks, abort signals, and automatic route documentation. Each builder uses the `onCreate` callback internally to register procedures as HTTP endpoints.
6
+
7
+ For a full cross-framework comparison (config interfaces, path generation, context resolution, abort signal, lifecycle hooks, route documentation), see the [HTTP implementations overview](../src/implementations/http/README.md).
8
+
9
+ ## Integrations at a Glance
10
+
11
+ | Integration | Export | Style | Guide |
12
+ |-------------|--------|-------|-------|
13
+ | Express RPC | `ts-procedures/express-rpc` | RPC (POST routes) | [README](../src/implementations/http/express-rpc/README.md) |
14
+ | Hono RPC | `ts-procedures/hono-rpc` | RPC (POST routes) | [README](../src/implementations/http/hono-rpc/README.md) |
15
+ | Hono Stream | `ts-procedures/hono-stream` | SSE/text streaming | [README](../src/implementations/http/hono-stream/README.md) |
16
+ | Hono API | `ts-procedures/hono-api` | REST (method-based) | [Below](#hono-api-integration) |
17
+
18
+ ## Express RPC
19
+
20
+ RPC-style HTTP integration for Express that creates POST routes at `/rpc/{name}/{version}` paths with automatic JSON schema documentation.
21
+
22
+ ```typescript
23
+ import { ExpressRPCAppBuilder, RPCConfig } from 'ts-procedures/express-rpc'
24
+
25
+ const RPC = Procedures<AppContext, RPCConfig>()
26
+
27
+ RPC.Create(
28
+ 'GetUser',
29
+ {
30
+ name: ['users', 'get'],
31
+ version: 1,
32
+ schema: {
33
+ params: Type.Object({ id: Type.String() }),
34
+ returnType: Type.Object({ id: Type.String(), name: Type.String() }),
35
+ },
36
+ },
37
+ async (ctx, params) => {
38
+ return { id: params.id, name: 'John Doe' }
39
+ }
40
+ )
41
+
42
+ const app = new ExpressRPCAppBuilder()
43
+ .register(RPC, (req) => ({ userId: req.headers['x-user-id'] as string }))
44
+ .build()
45
+
46
+ app.listen(3000)
47
+ // Route created: POST /rpc/users/get/1
48
+ ```
49
+
50
+ See the [Express RPC Integration Guide](../src/implementations/http/express-rpc/README.md) for complete setup including lifecycle hooks, error handling, and route documentation.
51
+
52
+ ## Hono RPC
53
+
54
+ RPC-style HTTP integration for Hono with the same routing pattern as Express RPC. Works with Bun, Deno, Cloudflare Workers, and Node.js.
55
+
56
+ ```typescript
57
+ import { HonoRPCAppBuilder, RPCConfig } from 'ts-procedures/hono-rpc'
58
+
59
+ const RPC = Procedures<AppContext, RPCConfig>()
60
+
61
+ RPC.Create('GetUser', {
62
+ scope: ['users', 'profile'],
63
+ version: 1,
64
+ schema: { params: Type.Object({ id: Type.String() }) },
65
+ }, async (ctx, params) => {
66
+ return { id: params.id, name: 'John Doe' }
67
+ })
68
+
69
+ const app = new HonoRPCAppBuilder({ pathPrefix: '/rpc' })
70
+ .register(RPC, (c) => ({ userId: c.req.header('x-user-id') || 'anonymous' }))
71
+ .build()
72
+
73
+ // POST /rpc/users/profile/get-user/1
74
+ ```
75
+
76
+ See the [Hono RPC Integration Guide](../src/implementations/http/hono-rpc/README.md) for complete setup including context resolution, doc extension, abort signal, error handling, and runtime compatibility.
77
+
78
+ ## Hono Stream
79
+
80
+ Streaming/SSE integration for Hono that supports both SSE and text streaming modes with yield validation, lifecycle hooks, and client disconnect detection.
81
+
82
+ ```typescript
83
+ import { HonoStreamAppBuilder } from 'ts-procedures/hono-stream'
84
+
85
+ const StreamRPC = Procedures<AppContext, RPCConfig>()
86
+
87
+ StreamRPC.CreateStream('WatchNotifications', {
88
+ scope: ['user', 'notifications'],
89
+ version: 1,
90
+ schema: { yieldType: Type.Object({ id: Type.Number(), message: Type.String() }) },
91
+ }, async function* (ctx) {
92
+ for (let i = 1; i <= 10; i++) {
93
+ yield { id: i, message: `Notification ${i}` }
94
+ await new Promise((r) => setTimeout(r, 1000))
95
+ }
96
+ })
97
+
98
+ const app = new HonoStreamAppBuilder()
99
+ .register(StreamRPC, (c) => ({ userId: c.req.header('x-user-id') || 'anonymous' }))
100
+ .build()
101
+ ```
102
+
103
+ See the [Hono Stream Integration Guide](../src/implementations/http/hono-stream/README.md) for complete setup including SSE/text modes, the `sse()` helper, validation, error handling, and migration notes.
104
+
105
+ ## Hono API Integration
106
+
107
+ REST-style HTTP integration for Hono that routes by HTTP method with per-channel input validation via `schema.input`.
108
+
109
+ ```typescript
110
+ import { Procedures } from 'ts-procedures'
111
+ import { HonoAPIAppBuilder } from 'ts-procedures/hono-api'
112
+ import type { APIConfig } from 'ts-procedures/http'
113
+ import { Type } from 'typebox'
114
+
115
+ const API = Procedures<{ userId: string }, APIConfig>()
116
+
117
+ API.Create('GetUser', {
118
+ path: '/users/:id',
119
+ method: 'get',
120
+ schema: {
121
+ input: {
122
+ pathParams: Type.Object({ id: Type.String() }),
123
+ },
124
+ returnType: Type.Object({ id: Type.String(), name: Type.String() }),
125
+ },
126
+ }, async (ctx, { pathParams }) => {
127
+ return await fetchUser(pathParams.id)
128
+ })
129
+
130
+ API.Create('CreateUser', {
131
+ path: '/users',
132
+ method: 'post',
133
+ schema: {
134
+ input: {
135
+ body: Type.Object({ name: Type.String(), email: Type.String() }),
136
+ },
137
+ },
138
+ }, async (ctx, { body }) => {
139
+ return await createUser(body)
140
+ })
141
+
142
+ const app = await new HonoAPIAppBuilder({ pathPrefix: '/api' })
143
+ .register(API, (c) => ({ userId: c.req.header('x-user-id') || 'anonymous' }))
144
+ .build()
145
+
146
+ // Routes:
147
+ // GET /api/users/:id -> 200
148
+ // POST /api/users -> 201
149
+ ```
150
+
151
+ ## DocRegistry — Composing Docs from Multiple Builders
152
+
153
+ Use `DocRegistry` to compose route documentation from any combination of HTTP builders into a typed envelope:
154
+
155
+ ```typescript
156
+ import { DocRegistry } from 'ts-procedures/http-docs'
157
+
158
+ const docs = new DocRegistry({
159
+ basePath: '/api',
160
+ headers: [{ name: 'Authorization', description: 'Bearer token', required: false }],
161
+ errors: DocRegistry.defaultErrors(),
162
+ })
163
+ .from(rpcBuilder)
164
+ .from(apiBuilder)
165
+ .from(streamBuilder)
166
+
167
+ app.get('/docs', (c) => c.json(docs.toJSON()))
168
+ ```
169
+
170
+ `from()` stores a reference — routes are read lazily at `toJSON()` time, so builders can be registered before or after `.build()`. Supports optional `filter` and `transform` options for customizing output.
171
+
172
+ The `DocRegistry` output is the input for [Client Code Generation](./client-and-codegen.md).
173
+
174
+ ## Type Exports
175
+
176
+ ```typescript
177
+ // HTTP types
178
+ import type { RPCConfig, RPCHttpRouteDoc, StreamHttpRouteDoc, StreamMode, APIConfig, APIHttpRouteDoc, APIInput, HttpMethod } from 'ts-procedures/http'
179
+
180
+ // Hono API (REST-style)
181
+ import { HonoAPIAppBuilder } from 'ts-procedures/hono-api'
182
+ import type { APIConfig, APIHttpRouteDoc, APIInput, HttpMethod, QueryParser } from 'ts-procedures/hono-api'
183
+ ```