ts-procedures 5.16.0 → 6.0.1

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 (147) hide show
  1. package/README.md +2 -0
  2. package/agent_config/claude-code/agents/ts-procedures-architect.md +13 -6
  3. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +26 -4
  4. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +87 -19
  5. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +162 -16
  6. package/agent_config/claude-code/skills/ts-procedures/patterns.md +179 -16
  7. package/agent_config/claude-code/skills/ts-procedures-review/SKILL.md +1 -1
  8. package/agent_config/claude-code/skills/ts-procedures-review/checklist.md +20 -12
  9. package/agent_config/claude-code/skills/ts-procedures-scaffold/SKILL.md +2 -1
  10. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/client.md +22 -15
  11. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/express-rpc.md +20 -17
  12. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-api.md +20 -16
  13. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-rpc.md +20 -17
  14. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-stream.md +16 -3
  15. package/agent_config/copilot/copilot-instructions.md +78 -12
  16. package/agent_config/cursor/cursorrules +78 -12
  17. package/build/client/call.d.ts +2 -1
  18. package/build/client/call.js +9 -1
  19. package/build/client/call.js.map +1 -1
  20. package/build/client/error-dispatch.d.ts +13 -0
  21. package/build/client/error-dispatch.js +26 -0
  22. package/build/client/error-dispatch.js.map +1 -0
  23. package/build/client/error-dispatch.test.d.ts +1 -0
  24. package/build/client/error-dispatch.test.js +56 -0
  25. package/build/client/error-dispatch.test.js.map +1 -0
  26. package/build/client/fetch-adapter.js +10 -4
  27. package/build/client/fetch-adapter.js.map +1 -1
  28. package/build/client/index.d.ts +2 -1
  29. package/build/client/index.js +5 -1
  30. package/build/client/index.js.map +1 -1
  31. package/build/client/stream.d.ts +2 -1
  32. package/build/client/stream.js +13 -3
  33. package/build/client/stream.js.map +1 -1
  34. package/build/client/typed-error-dispatch.test.d.ts +1 -0
  35. package/build/client/typed-error-dispatch.test.js +168 -0
  36. package/build/client/typed-error-dispatch.test.js.map +1 -0
  37. package/build/client/types.d.ts +37 -0
  38. package/build/codegen/e2e.test.js +9 -4
  39. package/build/codegen/e2e.test.js.map +1 -1
  40. package/build/codegen/emit-client-runtime.js +4 -0
  41. package/build/codegen/emit-client-runtime.js.map +1 -1
  42. package/build/codegen/emit-errors.d.ts +17 -6
  43. package/build/codegen/emit-errors.integration.test.d.ts +1 -0
  44. package/build/codegen/emit-errors.integration.test.js +162 -0
  45. package/build/codegen/emit-errors.integration.test.js.map +1 -0
  46. package/build/codegen/emit-errors.js +50 -39
  47. package/build/codegen/emit-errors.js.map +1 -1
  48. package/build/codegen/emit-errors.test.js +75 -78
  49. package/build/codegen/emit-errors.test.js.map +1 -1
  50. package/build/codegen/emit-index.d.ts +7 -0
  51. package/build/codegen/emit-index.js +26 -4
  52. package/build/codegen/emit-index.js.map +1 -1
  53. package/build/codegen/emit-index.test.js +55 -23
  54. package/build/codegen/emit-index.test.js.map +1 -1
  55. package/build/codegen/emit-scope.d.ts +8 -0
  56. package/build/codegen/emit-scope.js +82 -7
  57. package/build/codegen/emit-scope.js.map +1 -1
  58. package/build/codegen/pipeline.js +22 -2
  59. package/build/codegen/pipeline.js.map +1 -1
  60. package/build/implementations/http/doc-registry.d.ts +17 -1
  61. package/build/implementations/http/doc-registry.js +47 -79
  62. package/build/implementations/http/doc-registry.js.map +1 -1
  63. package/build/implementations/http/doc-registry.test.js +149 -16
  64. package/build/implementations/http/doc-registry.test.js.map +1 -1
  65. package/build/implementations/http/error-taxonomy.d.ts +249 -0
  66. package/build/implementations/http/error-taxonomy.js +252 -0
  67. package/build/implementations/http/error-taxonomy.js.map +1 -0
  68. package/build/implementations/http/error-taxonomy.test.d.ts +1 -0
  69. package/build/implementations/http/error-taxonomy.test.js +399 -0
  70. package/build/implementations/http/error-taxonomy.test.js.map +1 -0
  71. package/build/implementations/http/express-rpc/error-taxonomy.test.d.ts +1 -0
  72. package/build/implementations/http/express-rpc/error-taxonomy.test.js +83 -0
  73. package/build/implementations/http/express-rpc/error-taxonomy.test.js.map +1 -0
  74. package/build/implementations/http/express-rpc/index.d.ts +39 -8
  75. package/build/implementations/http/express-rpc/index.js +39 -8
  76. package/build/implementations/http/express-rpc/index.js.map +1 -1
  77. package/build/implementations/http/hono-api/error-taxonomy.test.d.ts +1 -0
  78. package/build/implementations/http/hono-api/error-taxonomy.test.js +137 -0
  79. package/build/implementations/http/hono-api/error-taxonomy.test.js.map +1 -0
  80. package/build/implementations/http/hono-api/index.d.ts +38 -1
  81. package/build/implementations/http/hono-api/index.js +32 -0
  82. package/build/implementations/http/hono-api/index.js.map +1 -1
  83. package/build/implementations/http/hono-rpc/error-taxonomy.test.d.ts +1 -0
  84. package/build/implementations/http/hono-rpc/error-taxonomy.test.js +64 -0
  85. package/build/implementations/http/hono-rpc/error-taxonomy.test.js.map +1 -0
  86. package/build/implementations/http/hono-rpc/index.d.ts +34 -7
  87. package/build/implementations/http/hono-rpc/index.js +31 -4
  88. package/build/implementations/http/hono-rpc/index.js.map +1 -1
  89. package/build/implementations/http/hono-stream/error-taxonomy.test.d.ts +1 -0
  90. package/build/implementations/http/hono-stream/error-taxonomy.test.js +87 -0
  91. package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +1 -0
  92. package/build/implementations/http/hono-stream/index.d.ts +40 -3
  93. package/build/implementations/http/hono-stream/index.js +37 -10
  94. package/build/implementations/http/hono-stream/index.js.map +1 -1
  95. package/build/implementations/http/hono-stream/index.test.js +45 -18
  96. package/build/implementations/http/hono-stream/index.test.js.map +1 -1
  97. package/build/implementations/http/on-request-error.test.d.ts +1 -0
  98. package/build/implementations/http/on-request-error.test.js +173 -0
  99. package/build/implementations/http/on-request-error.test.js.map +1 -0
  100. package/build/implementations/http/route-errors.test.d.ts +1 -0
  101. package/build/implementations/http/route-errors.test.js +139 -0
  102. package/build/implementations/http/route-errors.test.js.map +1 -0
  103. package/build/implementations/types.d.ts +43 -3
  104. package/docs/client-and-codegen.md +105 -12
  105. package/docs/core.md +14 -5
  106. package/docs/http-integrations.md +138 -5
  107. package/docs/streaming.md +3 -1
  108. package/docs/superpowers/plans/2026-04-24-doc-registry-simplification.md +886 -0
  109. package/package.json +7 -2
  110. package/src/client/call.ts +10 -1
  111. package/src/client/error-dispatch.test.ts +72 -0
  112. package/src/client/error-dispatch.ts +27 -0
  113. package/src/client/fetch-adapter.ts +11 -5
  114. package/src/client/index.ts +9 -0
  115. package/src/client/stream.ts +14 -3
  116. package/src/client/typed-error-dispatch.test.ts +211 -0
  117. package/src/client/types.ts +42 -0
  118. package/src/codegen/e2e.test.ts +9 -4
  119. package/src/codegen/emit-client-runtime.ts +4 -0
  120. package/src/codegen/emit-errors.integration.test.ts +183 -0
  121. package/src/codegen/emit-errors.test.ts +91 -87
  122. package/src/codegen/emit-errors.ts +123 -41
  123. package/src/codegen/emit-index.test.ts +68 -24
  124. package/src/codegen/emit-index.ts +66 -4
  125. package/src/codegen/emit-scope.ts +124 -7
  126. package/src/codegen/pipeline.ts +25 -2
  127. package/src/implementations/http/README.md +21 -7
  128. package/src/implementations/http/doc-registry.test.ts +164 -16
  129. package/src/implementations/http/doc-registry.ts +58 -82
  130. package/src/implementations/http/error-taxonomy.test.ts +438 -0
  131. package/src/implementations/http/error-taxonomy.ts +361 -0
  132. package/src/implementations/http/express-rpc/README.md +23 -24
  133. package/src/implementations/http/express-rpc/error-taxonomy.test.ts +103 -0
  134. package/src/implementations/http/express-rpc/index.ts +75 -14
  135. package/src/implementations/http/hono-api/README.md +284 -0
  136. package/src/implementations/http/hono-api/error-taxonomy.test.ts +179 -0
  137. package/src/implementations/http/hono-api/index.ts +76 -1
  138. package/src/implementations/http/hono-rpc/README.md +20 -21
  139. package/src/implementations/http/hono-rpc/error-taxonomy.test.ts +82 -0
  140. package/src/implementations/http/hono-rpc/index.ts +65 -9
  141. package/src/implementations/http/hono-stream/README.md +44 -25
  142. package/src/implementations/http/hono-stream/error-taxonomy.test.ts +98 -0
  143. package/src/implementations/http/hono-stream/index.test.ts +54 -18
  144. package/src/implementations/http/hono-stream/index.ts +83 -13
  145. package/src/implementations/http/on-request-error.test.ts +201 -0
  146. package/src/implementations/http/route-errors.test.ts +176 -0
  147. package/src/implementations/types.ts +43 -3
@@ -13,7 +13,7 @@ For a full cross-framework comparison (config interfaces, path generation, conte
13
13
  | Express RPC | `ts-procedures/express-rpc` | RPC (POST routes) | [README](../src/implementations/http/express-rpc/README.md) |
14
14
  | Hono RPC | `ts-procedures/hono-rpc` | RPC (POST routes) | [README](../src/implementations/http/hono-rpc/README.md) |
15
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) |
16
+ | Hono API | `ts-procedures/hono-api` | REST (method-based) | [README](../src/implementations/http/hono-api/README.md) |
17
17
 
18
18
  ## Express RPC
19
19
 
@@ -27,7 +27,7 @@ const RPC = Procedures<AppContext, RPCConfig>()
27
27
  RPC.Create(
28
28
  'GetUser',
29
29
  {
30
- name: ['users', 'get'],
30
+ scope: ['users', 'get'],
31
31
  version: 1,
32
32
  schema: {
33
33
  params: Type.Object({ id: Type.String() }),
@@ -44,7 +44,7 @@ const app = new ExpressRPCAppBuilder()
44
44
  .build()
45
45
 
46
46
  app.listen(3000)
47
- // Route created: POST /rpc/users/get/1
47
+ // Route created: POST /users/get/get-user/1
48
48
  ```
49
49
 
50
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.
@@ -139,7 +139,7 @@ API.Create('CreateUser', {
139
139
  return await createUser(body)
140
140
  })
141
141
 
142
- const app = await new HonoAPIAppBuilder({ pathPrefix: '/api' })
142
+ const app = new HonoAPIAppBuilder({ pathPrefix: '/api' })
143
143
  .register(API, (c) => ({ userId: c.req.header('x-user-id') || 'anonymous' }))
144
144
  .build()
145
145
 
@@ -148,6 +148,129 @@ const app = await new HonoAPIAppBuilder({ pathPrefix: '/api' })
148
148
  // POST /api/users -> 201
149
149
  ```
150
150
 
151
+ See the [Hono API Integration Guide](../src/implementations/http/hono-api/README.md) for the full surface: `schema.input` channels, query parsing, success-status defaults, and error handling.
152
+
153
+ ## Error Handling
154
+
155
+ Every HTTP builder supports **two peer error-handling modes** — neither is "primary" or "fallback". Pick whichever fits your app, or combine them.
156
+
157
+ | Mode | When to pick | What you configure |
158
+ |---|---|---|
159
+ | **Declarative — the taxonomy** | Structured errors, typed client dispatch, DocEnvelope integration | `errors` (taxonomy) + optional `unknownError` |
160
+ | **Imperative — the `onError` callback** | Simple apps, gradual migration, full response control, no typed client contract | `onError` only |
161
+
162
+ Both modes also expose `onRequestError` — a cross-cutting observer for logging, tracing, and metrics that fires for every error regardless of dispatch outcome.
163
+
164
+ ### Declarative — the taxonomy
165
+
166
+ ```typescript
167
+ import { defineErrorTaxonomy } from 'ts-procedures/http-errors'
168
+ import { HonoAPIAppBuilder } from 'ts-procedures/hono-api'
169
+
170
+ class UseCaseError extends Error {
171
+ constructor(readonly externalMsg: string, readonly internalMsg: string) {
172
+ super(externalMsg); this.name = 'UseCaseError'
173
+ Object.setPrototypeOf(this, UseCaseError.prototype)
174
+ }
175
+ }
176
+
177
+ const appErrors = defineErrorTaxonomy({
178
+ UseCaseError: {
179
+ class: UseCaseError,
180
+ statusCode: 422,
181
+ toResponse: (err) => ({ message: err.externalMsg }), // `name: 'UseCaseError'` auto-injected
182
+ onCatch: (err) => logger.error(err.internalMsg),
183
+ },
184
+ // 3rd-party errors without a subclassable type use `match:` instead of `class:`.
185
+ MongoDuplicateKey: {
186
+ match: (err): err is Error =>
187
+ err instanceof Error && err.name === 'MongoServerError' && (err as any).code === 11000,
188
+ statusCode: 409,
189
+ toResponse: () => ({ message: 'Resource already exists' }),
190
+ },
191
+ })
192
+
193
+ new HonoAPIAppBuilder({
194
+ errors: appErrors,
195
+ unknownError: {
196
+ statusCode: 500,
197
+ toResponse: () => ({ name: 'InternalServerError', message: 'Unexpected error' }),
198
+ onCatch: (err, { procedure }) => logger.error({ procedure: procedure.name, err }),
199
+ },
200
+ }).register(API, /* ... */).build()
201
+ ```
202
+
203
+ The identical `errors` + `unknownError` shape plugs into `HonoRPCAppBuilder`, `ExpressRPCAppBuilder`, and `HonoStreamAppBuilder` (pre-stream only — mid-stream uses `onMidStreamError`).
204
+
205
+ ### Imperative — the `onError` callback
206
+
207
+ For apps that don't need typed client dispatch or declarative docs, configure `onError` directly and handle every error in one place:
208
+
209
+ ```typescript
210
+ new HonoAPIAppBuilder({
211
+ onError: (procedure, c, error) => {
212
+ logger.error(`[${procedure.name}]`, error)
213
+ return c.json({ error: error.message ?? 'unknown error' }, 500)
214
+ },
215
+ }).register(API, /* ... */).build()
216
+ ```
217
+
218
+ (Need to route different error classes to different status codes? Use the taxonomy — that's exactly what it's designed for. Hand-writing `instanceof` ladders inside `onError` is [anti-pattern #20](../agent_config/claude-code/skills/ts-procedures/anti-patterns.md).)
219
+
220
+ Signatures (differ by framework):
221
+
222
+ | Builder | `onError` signature |
223
+ |---|---|
224
+ | `HonoAPIAppBuilder`, `HonoRPCAppBuilder` | `(procedure, c: Context, error) => Response \| Promise<Response>` |
225
+ | `ExpressRPCAppBuilder` | `(procedure, req, res, error) => void` (write to `res`) |
226
+ | `HonoStreamAppBuilder` | `(procedure, c: Context, error) => Response \| Promise<Response>` — pre-stream only |
227
+
228
+ Picking between modes isn't irreversible: you can start with `onError`, migrate chunks to the taxonomy as your error model stabilizes, and leave the rest in `onError`. Both modes coexist in the dispatch order below.
229
+
230
+ ### Per-route error narrowing (taxonomy mode)
231
+
232
+ `APIConfig` and `RPCConfig` are generic over a `TErrorKey extends string` parameter so you can declare which errors a specific route may emit:
233
+
234
+ ```typescript
235
+ import type { APIConfig } from 'ts-procedures/http'
236
+
237
+ type MyAPIConfig = APIConfig<keyof typeof appErrors & string>
238
+ const API = Procedures<Ctx, MyAPIConfig>()
239
+
240
+ API.Create('GetUser', {
241
+ path: '/users/:id',
242
+ method: 'get',
243
+ errors: ['UseCaseError'], // typo-checked against the taxonomy keys
244
+ schema: { /* ... */ },
245
+ }, /* handler */)
246
+ ```
247
+
248
+ These per-route declarations flow into the DocEnvelope and drive typed `catch` blocks on generated clients — see [Client & Codegen → Typed Error Handling](./client-and-codegen.md#typed-error-handling).
249
+
250
+ ### Cross-cutting observability — `onRequestError`
251
+
252
+ `onRequestError` fires for every caught error, **before** dispatch. It's an observer — it can't mutate the response. Use it for APM, distributed tracing, custom logging, or metrics where you want one hook that sees every error regardless of which mode dispatched it.
253
+
254
+ ```typescript
255
+ new HonoAPIAppBuilder({
256
+ errors: appErrors,
257
+ onRequestError: async ({ err, procedure, raw }) => {
258
+ sentry.captureException(err, { procedure: procedure.name })
259
+ },
260
+ })
261
+ ```
262
+
263
+ The observer is awaited before the response is sent, and any error it throws is swallowed (logged to the console) so a broken instrumentation hook can't corrupt the primary flow.
264
+
265
+ ### Dispatch order inside each builder's catch block
266
+
267
+ 1. **`onRequestError`** (observer) — awaited, can't alter dispatch or response
268
+ 2. **`errors` taxonomy** — user entries checked, then framework defaults, then `unknownError`
269
+ 3. **`onError` callback** — imperative handler receives anything the taxonomy didn't match
270
+ 4. **Hard default** — `{ error: message }` at status 500 (produced only when nothing above handled the error)
271
+
272
+ Configuring only the taxonomy, only `onError`, both, or neither are all valid. When neither is configured the builder goes straight from step 1 to step 4.
273
+
151
274
  ## DocRegistry — Composing Docs from Multiple Builders
152
275
 
153
276
  Use `DocRegistry` to compose route documentation from any combination of HTTP builders into a typed envelope:
@@ -158,7 +281,7 @@ import { DocRegistry } from 'ts-procedures/http-docs'
158
281
  const docs = new DocRegistry({
159
282
  basePath: '/api',
160
283
  headers: [{ name: 'Authorization', description: 'Bearer token', required: false }],
161
- errors: DocRegistry.defaultErrors(),
284
+ errors: appErrors, // your ErrorTaxonomy — auto-converted to ErrorDocs, framework defaults merged
162
285
  })
163
286
  .from(rpcBuilder)
164
287
  .from(apiBuilder)
@@ -167,6 +290,16 @@ const docs = new DocRegistry({
167
290
  app.get('/docs', (c) => c.json(docs.toJSON()))
168
291
  ```
169
292
 
293
+ `errors` accepts either your runtime `ErrorTaxonomy` (the common case) or a raw `ErrorDoc[]`. Framework defaults (`ProcedureError`, `ProcedureValidationError`, `ProcedureYieldValidationError`, `ProcedureRegistrationError`) are merged in automatically and deduped — user entries win when keys overlap. Pass `includeDefaults: false` to opt out.
294
+
295
+ For errors that aren't in your taxonomy (middleware-level, infrastructure, doc-only meta errors), use the fluent `.documentError()` method:
296
+
297
+ ```typescript
298
+ const docs = new DocRegistry({ errors: appErrors, basePath: '/api' })
299
+ .from(rpcBuilder)
300
+ .documentError({ name: 'RateLimitExceeded', statusCode: 429, description: 'too many requests' })
301
+ ```
302
+
170
303
  `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
304
 
172
305
  The `DocRegistry` output is the input for [Client Code Generation](./client-and-codegen.md).
package/docs/streaming.md CHANGED
@@ -168,7 +168,9 @@ For the built-in Hono streaming integration, see the [Hono Stream README](../src
168
168
 
169
169
  ## Stream Errors
170
170
 
171
- Streaming procedures support the same error handling as regular procedures (see [Error Handling](./core.md#error-handling)):
171
+ Streaming procedures support the same error handling as regular procedures (see [Error Handling](./core.md#error-handling)).
172
+
173
+ For HTTP stream endpoints, pre-stream errors (validation, context resolution, anything thrown before the first yield) go through the same two peer error-handling modes as every other HTTP builder — either the declarative `errors` / `unknownError` taxonomy (from `defineErrorTaxonomy`) or the imperative `onError` callback on `HonoStreamAppBuilder`. Cross-cutting `onRequestError` observer fires for every pre-stream error. See [HTTP Integrations → Error Handling](./http-integrations.md#error-handling) for the full contract. Mid-stream errors (thrown after the first yield) still flow through `onMidStreamError` and are surfaced to the client as SSE error events — the HTTP status is already committed once streaming begins, so mid-stream is outside the peer error modes.
172
174
 
173
175
  ```typescript
174
176
  const { StreamWithErrors } = CreateStream(