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.
- package/README.md +2 -0
- package/agent_config/claude-code/agents/ts-procedures-architect.md +13 -6
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +26 -4
- package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +87 -19
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +162 -16
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +179 -16
- package/agent_config/claude-code/skills/ts-procedures-review/SKILL.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures-review/checklist.md +20 -12
- package/agent_config/claude-code/skills/ts-procedures-scaffold/SKILL.md +2 -1
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/client.md +22 -15
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/express-rpc.md +20 -17
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-api.md +20 -16
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-rpc.md +20 -17
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/hono-stream.md +16 -3
- package/agent_config/copilot/copilot-instructions.md +78 -12
- package/agent_config/cursor/cursorrules +78 -12
- package/build/client/call.d.ts +2 -1
- package/build/client/call.js +9 -1
- package/build/client/call.js.map +1 -1
- package/build/client/error-dispatch.d.ts +13 -0
- package/build/client/error-dispatch.js +26 -0
- package/build/client/error-dispatch.js.map +1 -0
- package/build/client/error-dispatch.test.d.ts +1 -0
- package/build/client/error-dispatch.test.js +56 -0
- package/build/client/error-dispatch.test.js.map +1 -0
- package/build/client/fetch-adapter.js +10 -4
- package/build/client/fetch-adapter.js.map +1 -1
- package/build/client/index.d.ts +2 -1
- package/build/client/index.js +5 -1
- package/build/client/index.js.map +1 -1
- package/build/client/stream.d.ts +2 -1
- package/build/client/stream.js +13 -3
- package/build/client/stream.js.map +1 -1
- package/build/client/typed-error-dispatch.test.d.ts +1 -0
- package/build/client/typed-error-dispatch.test.js +168 -0
- package/build/client/typed-error-dispatch.test.js.map +1 -0
- package/build/client/types.d.ts +37 -0
- package/build/codegen/e2e.test.js +9 -4
- package/build/codegen/e2e.test.js.map +1 -1
- package/build/codegen/emit-client-runtime.js +4 -0
- package/build/codegen/emit-client-runtime.js.map +1 -1
- package/build/codegen/emit-errors.d.ts +17 -6
- package/build/codegen/emit-errors.integration.test.d.ts +1 -0
- package/build/codegen/emit-errors.integration.test.js +162 -0
- package/build/codegen/emit-errors.integration.test.js.map +1 -0
- package/build/codegen/emit-errors.js +50 -39
- package/build/codegen/emit-errors.js.map +1 -1
- package/build/codegen/emit-errors.test.js +75 -78
- package/build/codegen/emit-errors.test.js.map +1 -1
- package/build/codegen/emit-index.d.ts +7 -0
- package/build/codegen/emit-index.js +26 -4
- package/build/codegen/emit-index.js.map +1 -1
- package/build/codegen/emit-index.test.js +55 -23
- package/build/codegen/emit-index.test.js.map +1 -1
- package/build/codegen/emit-scope.d.ts +8 -0
- package/build/codegen/emit-scope.js +82 -7
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/pipeline.js +22 -2
- package/build/codegen/pipeline.js.map +1 -1
- package/build/implementations/http/doc-registry.d.ts +17 -1
- package/build/implementations/http/doc-registry.js +47 -79
- package/build/implementations/http/doc-registry.js.map +1 -1
- package/build/implementations/http/doc-registry.test.js +149 -16
- package/build/implementations/http/doc-registry.test.js.map +1 -1
- 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 +39 -8
- package/build/implementations/http/express-rpc/index.js +39 -8
- package/build/implementations/http/express-rpc/index.js.map +1 -1
- 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 +38 -1
- package/build/implementations/http/hono-api/index.js +32 -0
- package/build/implementations/http/hono-api/index.js.map +1 -1
- 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 +34 -7
- package/build/implementations/http/hono-rpc/index.js +31 -4
- package/build/implementations/http/hono-rpc/index.js.map +1 -1
- package/build/implementations/http/hono-stream/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/hono-stream/error-taxonomy.test.js +87 -0
- package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/hono-stream/index.d.ts +40 -3
- package/build/implementations/http/hono-stream/index.js +37 -10
- package/build/implementations/http/hono-stream/index.js.map +1 -1
- package/build/implementations/http/hono-stream/index.test.js +45 -18
- package/build/implementations/http/hono-stream/index.test.js.map +1 -1
- 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 +43 -3
- package/docs/client-and-codegen.md +105 -12
- package/docs/core.md +14 -5
- package/docs/http-integrations.md +138 -5
- package/docs/streaming.md +3 -1
- package/docs/superpowers/plans/2026-04-24-doc-registry-simplification.md +886 -0
- package/package.json +7 -2
- package/src/client/call.ts +10 -1
- package/src/client/error-dispatch.test.ts +72 -0
- package/src/client/error-dispatch.ts +27 -0
- package/src/client/fetch-adapter.ts +11 -5
- package/src/client/index.ts +9 -0
- package/src/client/stream.ts +14 -3
- package/src/client/typed-error-dispatch.test.ts +211 -0
- package/src/client/types.ts +42 -0
- package/src/codegen/e2e.test.ts +9 -4
- package/src/codegen/emit-client-runtime.ts +4 -0
- package/src/codegen/emit-errors.integration.test.ts +183 -0
- package/src/codegen/emit-errors.test.ts +91 -87
- package/src/codegen/emit-errors.ts +123 -41
- package/src/codegen/emit-index.test.ts +68 -24
- package/src/codegen/emit-index.ts +66 -4
- package/src/codegen/emit-scope.ts +124 -7
- package/src/codegen/pipeline.ts +25 -2
- package/src/implementations/http/README.md +21 -7
- package/src/implementations/http/doc-registry.test.ts +164 -16
- package/src/implementations/http/doc-registry.ts +58 -82
- package/src/implementations/http/error-taxonomy.test.ts +438 -0
- package/src/implementations/http/error-taxonomy.ts +361 -0
- package/src/implementations/http/express-rpc/README.md +23 -24
- package/src/implementations/http/express-rpc/error-taxonomy.test.ts +103 -0
- package/src/implementations/http/express-rpc/index.ts +75 -14
- package/src/implementations/http/hono-api/README.md +284 -0
- package/src/implementations/http/hono-api/error-taxonomy.test.ts +179 -0
- package/src/implementations/http/hono-api/index.ts +76 -1
- package/src/implementations/http/hono-rpc/README.md +20 -21
- package/src/implementations/http/hono-rpc/error-taxonomy.test.ts +82 -0
- package/src/implementations/http/hono-rpc/index.ts +65 -9
- package/src/implementations/http/hono-stream/README.md +44 -25
- package/src/implementations/http/hono-stream/error-taxonomy.test.ts +98 -0
- package/src/implementations/http/hono-stream/index.test.ts +54 -18
- package/src/implementations/http/hono-stream/index.ts +83 -13
- package/src/implementations/http/on-request-error.test.ts +201 -0
- package/src/implementations/http/route-errors.test.ts +176 -0
- 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) | [
|
|
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
|
-
|
|
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 /
|
|
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 =
|
|
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:
|
|
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(
|