ts-procedures 5.15.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +85 -17
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +220 -9
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +271 -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 +53 -18
- 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 +132 -19
- package/agent_config/cursor/cursorrules +132 -19
- package/build/client/call.d.ts +19 -9
- package/build/client/call.js +33 -19
- package/build/client/call.js.map +1 -1
- package/build/client/call.test.js +167 -17
- package/build/client/call.test.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 +22 -3
- package/build/client/index.js.map +1 -1
- package/build/client/index.test.js +104 -0
- package/build/client/index.test.js.map +1 -1
- package/build/client/resolve-options.d.ts +45 -0
- package/build/client/resolve-options.js +82 -0
- package/build/client/resolve-options.js.map +1 -0
- package/build/client/resolve-options.test.d.ts +1 -0
- package/build/client/resolve-options.test.js +158 -0
- package/build/client/resolve-options.test.js.map +1 -0
- package/build/client/stream.d.ts +19 -9
- package/build/client/stream.js +36 -21
- package/build/client/stream.js.map +1 -1
- package/build/client/stream.test.js +102 -46
- package/build/client/stream.test.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 +105 -1
- package/build/client/types.js +1 -1
- package/build/codegen/e2e.test.js +150 -4
- package/build/codegen/e2e.test.js.map +1 -1
- package/build/codegen/emit-client-runtime.js +7 -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 +21 -0
- package/build/implementations/http/doc-registry.js +51 -78
- package/build/implementations/http/doc-registry.js.map +1 -1
- package/build/implementations/http/doc-registry.test.js +8 -6
- package/build/implementations/http/doc-registry.test.js.map +1 -1
- package/build/implementations/http/error-taxonomy.d.ts +240 -0
- package/build/implementations/http/error-taxonomy.js +230 -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 +140 -0
- package/build/implementations/http/route-errors.test.js.map +1 -0
- package/build/implementations/types.d.ts +30 -2
- package/docs/client-and-codegen.md +228 -14
- package/docs/core.md +14 -5
- package/docs/http-integrations.md +135 -4
- package/docs/streaming.md +3 -1
- package/package.json +7 -2
- package/src/client/call.test.ts +202 -29
- package/src/client/call.ts +50 -28
- 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.test.ts +117 -0
- package/src/client/index.ts +34 -8
- package/src/client/resolve-options.test.ts +205 -0
- package/src/client/resolve-options.ts +113 -0
- package/src/client/stream.test.ts +132 -107
- package/src/client/stream.ts +53 -27
- package/src/client/typed-error-dispatch.test.ts +211 -0
- package/src/client/types.ts +116 -2
- package/src/codegen/e2e.test.ts +160 -4
- package/src/codegen/emit-client-runtime.ts +7 -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 +28 -5
- package/src/implementations/http/doc-registry.test.ts +10 -6
- package/src/implementations/http/doc-registry.ts +63 -80
- package/src/implementations/http/error-taxonomy.test.ts +438 -0
- package/src/implementations/http/error-taxonomy.ts +337 -0
- package/src/implementations/http/express-rpc/README.md +21 -22
- 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 +18 -19
- 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 +177 -0
- package/src/implementations/types.ts +30 -2
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
## Implementation — `{{Name}}.rpc.ts`
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
|
-
import { Procedures
|
|
7
|
-
import { HonoRPCAppBuilder } from 'ts-procedures/hono-rpc'
|
|
6
|
+
import { Procedures } from 'ts-procedures'
|
|
7
|
+
import { HonoRPCAppBuilder, defineErrorTaxonomy } from 'ts-procedures/hono-rpc'
|
|
8
8
|
import type { RPCConfig } from 'ts-procedures/http'
|
|
9
9
|
import { Type } from 'typebox'
|
|
10
10
|
|
|
@@ -60,25 +60,28 @@ export const { ListItems } = RPC.Create(
|
|
|
60
60
|
}
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
+
// ─── Error Taxonomy ───────────────────────────────────────
|
|
64
|
+
// Declare the error classes this service throws. Framework errors
|
|
65
|
+
// (ProcedureValidationError → 400, ctx.error() → 500) are caught by the
|
|
66
|
+
// default taxonomy automatically. Add your own classes here — handlers just
|
|
67
|
+
// `throw` them and the builder serializes via this map. See
|
|
68
|
+
// docs/http-integrations.md#error-handling for the full contract.
|
|
69
|
+
|
|
70
|
+
const {{name}}Errors = defineErrorTaxonomy({
|
|
71
|
+
// Example — replace with your app's error classes:
|
|
72
|
+
// NotFoundError: { class: NotFoundError, statusCode: 404 },
|
|
73
|
+
// AuthError: { class: AuthError, statusCode: 401 },
|
|
74
|
+
})
|
|
75
|
+
|
|
63
76
|
// ─── Hono App Builder ─────────────────────────────────────
|
|
64
77
|
|
|
65
78
|
export const {{name}}App = new HonoRPCAppBuilder({
|
|
66
79
|
pathPrefix: '/api',
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
procedure: error.procedureName,
|
|
73
|
-
}, 400)
|
|
74
|
-
} else if (error instanceof ProcedureError) {
|
|
75
|
-
return c.json({
|
|
76
|
-
error: error.message,
|
|
77
|
-
meta: error.meta,
|
|
78
|
-
procedure: error.procedureName,
|
|
79
|
-
}, 422)
|
|
80
|
-
}
|
|
81
|
-
return c.json({ error: 'Internal server error' }, 500)
|
|
80
|
+
errors: {{name}}Errors,
|
|
81
|
+
unknownError: {
|
|
82
|
+
statusCode: 500,
|
|
83
|
+
toResponse: () => ({ name: 'InternalServerError', message: 'Unexpected error' }),
|
|
84
|
+
onCatch: (err, { procedure }) => console.error(`[${procedure.name}]`, err),
|
|
82
85
|
},
|
|
83
86
|
})
|
|
84
87
|
.register(RPC, (c) => ({
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
6
|
import { Procedures } from 'ts-procedures'
|
|
7
|
-
import { HonoStreamAppBuilder, sse } from 'ts-procedures/hono-stream'
|
|
7
|
+
import { HonoStreamAppBuilder, sse, defineErrorTaxonomy } from 'ts-procedures/hono-stream'
|
|
8
8
|
import type { RPCConfig } from 'ts-procedures/http'
|
|
9
9
|
import { Type } from 'typebox'
|
|
10
10
|
|
|
@@ -60,13 +60,26 @@ async function pollForEvents(channel: string, opts?: { signal?: AbortSignal }) {
|
|
|
60
60
|
return { type: 'update', data: { value: Math.random() } }
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// ─── Error Taxonomy (pre-stream) ──────────────────────────
|
|
64
|
+
// Pre-stream errors (validation, context resolution) flow through the
|
|
65
|
+
// taxonomy — framework errors are caught by the default taxonomy automatically.
|
|
66
|
+
// Mid-stream errors (thrown after the first yield) still go through
|
|
67
|
+
// onMidStreamError since the HTTP status is already committed.
|
|
68
|
+
|
|
69
|
+
const {{name}}Errors = defineErrorTaxonomy({
|
|
70
|
+
// Example — replace with your app's error classes:
|
|
71
|
+
// AuthError: { class: AuthError, statusCode: 401 },
|
|
72
|
+
})
|
|
73
|
+
|
|
63
74
|
// ─── Hono Stream App Builder ──────────────────────────────
|
|
64
75
|
|
|
65
76
|
export const {{name}}StreamApp = new HonoStreamAppBuilder({
|
|
66
77
|
pathPrefix: '/api',
|
|
67
78
|
defaultStreamMode: 'sse', // or 'text' for newline-delimited JSON
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
errors: {{name}}Errors,
|
|
80
|
+
unknownError: {
|
|
81
|
+
statusCode: 500,
|
|
82
|
+
toResponse: () => ({ name: 'InternalServerError', message: 'Unexpected error' }),
|
|
70
83
|
},
|
|
71
84
|
onMidStreamError: (procedure, c, error) => ({
|
|
72
85
|
data: { type: 'error', payload: { message: error.message }, timestamp: Date.now() },
|
|
@@ -196,24 +196,89 @@ const app = new HonoAPIAppBuilder({ pathPrefix: '/api' })
|
|
|
196
196
|
|
|
197
197
|
| Error Class | Trigger | HTTP Status |
|
|
198
198
|
|-------------|---------|-------------|
|
|
199
|
-
| `ProcedureValidationError` | Schema params validation failure | 400 |
|
|
200
|
-
| `ProcedureError` | `ctx.error()` or unhandled handler exception |
|
|
199
|
+
| `ProcedureValidationError` | Schema params validation failure | 400 (auto — default taxonomy) |
|
|
200
|
+
| `ProcedureError` | `ctx.error()` or unhandled handler exception | 500 (auto — default taxonomy) |
|
|
201
201
|
| `ProcedureYieldValidationError` | Yield validation failure (validateYields: true) | N/A (mid-stream) |
|
|
202
202
|
| `ProcedureRegistrationError` | Invalid schema or duplicate name at registration | N/A (startup) |
|
|
203
203
|
|
|
204
|
+
### Error Taxonomy (required for custom errors)
|
|
205
|
+
|
|
206
|
+
`defineErrorTaxonomy` maps error classes to HTTP responses declaratively. Works with every HTTP builder (`hono-api`, `hono-rpc`, `express-rpc`, `hono-stream` pre-stream).
|
|
207
|
+
|
|
208
|
+
Do NOT write `onError` `instanceof` ladders — that's anti-pattern #20. Use the taxonomy instead:
|
|
209
|
+
|
|
204
210
|
```typescript
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
} else {
|
|
212
|
-
res.status(500).json({ error: 'Internal server error' })
|
|
211
|
+
import { defineErrorTaxonomy } from 'ts-procedures/http-errors'
|
|
212
|
+
|
|
213
|
+
class UseCaseError extends Error {
|
|
214
|
+
constructor(readonly externalMsg: string, readonly internalMsg: string) {
|
|
215
|
+
super(externalMsg); this.name = 'UseCaseError'
|
|
216
|
+
Object.setPrototypeOf(this, UseCaseError.prototype)
|
|
213
217
|
}
|
|
214
218
|
}
|
|
219
|
+
|
|
220
|
+
const appErrors = defineErrorTaxonomy({
|
|
221
|
+
// First-match wins — declare subclasses before base classes
|
|
222
|
+
UseCaseError: {
|
|
223
|
+
class: UseCaseError,
|
|
224
|
+
statusCode: 422,
|
|
225
|
+
toResponse: (err) => ({ name: 'UseCaseError', message: err.externalMsg }),
|
|
226
|
+
onCatch: (err) => logger.error(err.internalMsg), // internal logs only
|
|
227
|
+
},
|
|
228
|
+
MongoDuplicateKey: {
|
|
229
|
+
match: (err): err is Error => err instanceof Error && (err as any).code === 11000,
|
|
230
|
+
statusCode: 409,
|
|
231
|
+
toResponse: () => ({ name: 'Conflict', message: 'Resource already exists' }),
|
|
232
|
+
},
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
new HonoAPIAppBuilder({
|
|
236
|
+
errors: appErrors,
|
|
237
|
+
unknownError: { statusCode: 500, toResponse: () => ({ name: 'InternalServerError' }) },
|
|
238
|
+
})
|
|
215
239
|
```
|
|
216
240
|
|
|
241
|
+
Handlers throw the error classes directly — the builder auto-serializes. `onError` is the first-class imperative peer when you want to handle errors in one callback instead (both modes coexist). In v6 `HonoStreamAppBuilder.onPreStreamError` was renamed to `onError`. Cross-cutting `onRequestError` observer fires for every caught error.
|
|
242
|
+
|
|
243
|
+
### Per-route errors (typed)
|
|
244
|
+
|
|
245
|
+
`APIConfig` and `RPCConfig` are generic over `TErrorKey extends string = string`. Narrow to your taxonomy for compile-time typo protection and route-level error doc entries:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { APIConfig } from 'ts-procedures/http'
|
|
249
|
+
import { DocRegistry } from 'ts-procedures/http-docs'
|
|
250
|
+
|
|
251
|
+
type MyAPIConfig = APIConfig<keyof typeof appErrors & string>
|
|
252
|
+
const API = Procedures<Ctx, MyAPIConfig>()
|
|
253
|
+
|
|
254
|
+
API.Create('GetUser', { path: '/users/:id', method: 'get', errors: ['UseCaseError'], /* ... */ }, ...)
|
|
255
|
+
|
|
256
|
+
// Seed envelope errors from the taxonomy + framework defaults in one call
|
|
257
|
+
const envelope = DocRegistry.fromTaxonomy(appErrors, { basePath: '/api' }).from(apiApp).toJSON()
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Client-side typed catch blocks (via codegen)
|
|
261
|
+
|
|
262
|
+
The generated `_errors.ts` emits real runtime classes extending a shared `${Service}ProcedureError` base. The generated `createApiClient` wires an error registry so non-2xx responses arrive as typed class instances:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { createApiClient, ApiErrors, createFetchAdapter } from './generated'
|
|
266
|
+
|
|
267
|
+
const api = createApiClient({ adapter: createFetchAdapter({ /* ... */ }), basePath: '...' })
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
await api.users.getUser({ pathParams: { id: 'x' } })
|
|
271
|
+
} catch (err) {
|
|
272
|
+
if (err instanceof ApiErrors.UseCaseError) {
|
|
273
|
+
// err.body typed; err.status, err.procedureName, err.scope available
|
|
274
|
+
} else if (err instanceof ApiErrors.ApiProcedureError) {
|
|
275
|
+
// Catch-all for any service error
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Per-route error unions: routes with `errors: [...]` get an `Errors` type in their namespace (e.g. `Users.GetUser.Errors = ApiErrors.UseCaseError | ApiErrors.AuthError`).
|
|
281
|
+
|
|
217
282
|
## Lifecycle Hook Order
|
|
218
283
|
|
|
219
284
|
### Standard RPC
|
|
@@ -225,7 +290,7 @@ onRequestStart → factoryContext() → handler() → onSuccess → onRequestEnd
|
|
|
225
290
|
### Streaming
|
|
226
291
|
```
|
|
227
292
|
onRequestStart → factoryContext() → validation → onStreamStart → handler yields → onStreamEnd → onRequestEnd
|
|
228
|
-
→
|
|
293
|
+
→ onError (or taxonomy) → onRequestEnd
|
|
229
294
|
→ onMidStreamError → onStreamEnd → onRequestEnd
|
|
230
295
|
```
|
|
231
296
|
|
|
@@ -320,7 +385,7 @@ npx ts-procedures-codegen --url http://localhost:3000/docs --out ./src/generated
|
|
|
320
385
|
# --clean-out-dir # recursively wipe --out before writing (prunes stale scope files)
|
|
321
386
|
```
|
|
322
387
|
|
|
323
|
-
Generates one `.ts` file per scope plus a root `index.ts` that imports each scope as a namespace and exports a `create${ServiceName}Bindings` factory (defaults to `createApiBindings`; pass `--service-name <Name>` to rename). When namespace mode is on (the default), `index.ts` also wraps every scope namespace in an outer `export namespace ${ServiceName} { ... }` block so types are reachable as `Api.Users.GetUser.Params`, `Api.Errors.
|
|
388
|
+
Generates one `.ts` file per scope plus a root `index.ts` that imports each scope as a namespace and exports a `create${ServiceName}Bindings(client)` factory AND a `create${ServiceName}Client(config)` convenience factory that pre-wires the error registry (defaults to `createApiBindings` / `createApiClient`; pass `--service-name <Name>` to rename). When namespace mode is on (the default), `index.ts` also wraps every scope namespace in an outer `export namespace ${ServiceName} { ... }` block so types are reachable as `Api.Users.GetUser.Params`, `Api.Errors.UseCaseError`, etc. The errors file (`_errors.ts`) emits runtime error classes extending a shared `${ServiceName}ProcedureError` base, each with `static fromResponse(body, meta)`, plus `${ServiceName}ErrorRegistry` (runtime dispatch map) and `${ServiceName}ProcedureErrorUnion` (type union). Defaults: `ApiErrors`, `ApiProcedureError`, `ApiErrorRegistry`, `ApiProcedureErrorUnion`.
|
|
324
389
|
By default, types are wrapped in nested TS namespaces (`Scope.Route.Params`), JSDoc comments are emitted, and output is self-contained (no runtime dependency on `ts-procedures`). Use `--no-namespace-types` to revert to flat type names (`RouteParams`); this also disables the outer service namespace in `index.ts` and skips importing `_errors` from there.
|
|
325
390
|
Note: ajsc formatting options (`--enum-style enum`, `--depluralize`, etc.) only take effect in namespace mode (the default). They are ignored with `--no-namespace-types`.
|
|
326
391
|
Supports config file: `ts-procedures-codegen.config.json` (auto-loaded from CWD) or `--config <path>`.
|
|
@@ -336,6 +401,7 @@ const client = createClient({
|
|
|
336
401
|
adapter: createFetchAdapter(),
|
|
337
402
|
basePath: 'http://localhost:3000',
|
|
338
403
|
scopes: createApiBindings,
|
|
404
|
+
defaults: { timeout: 30_000, headers: { 'X-Client-Version': '1.0.0' } },
|
|
339
405
|
hooks: {
|
|
340
406
|
onBeforeRequest(ctx) {
|
|
341
407
|
ctx.request.headers = { ...ctx.request.headers, Authorization: `Bearer ${getToken()}` }
|
|
@@ -350,20 +416,67 @@ const client = createClient({
|
|
|
350
416
|
// Fully typed — params and response inferred from server schemas; type aliases live under Api.<Scope>.<Route>.
|
|
351
417
|
const user = await client.users.GetUser({ pathParams: { id: '123' } })
|
|
352
418
|
|
|
353
|
-
// Per-call
|
|
354
|
-
await client.users.GetUser(
|
|
355
|
-
|
|
356
|
-
|
|
419
|
+
// Per-call options — timeout, signal, headers, basePath, and hooks all share one bag
|
|
420
|
+
await client.users.GetUser(
|
|
421
|
+
{ pathParams: { id: '123' } },
|
|
422
|
+
{
|
|
423
|
+
timeout: 5000,
|
|
424
|
+
headers: { 'X-Request-Id': crypto.randomUUID() },
|
|
425
|
+
onAfterResponse(ctx) {
|
|
426
|
+
console.log(ctx.response.headers['x-rate-limit-remaining'])
|
|
427
|
+
},
|
|
357
428
|
},
|
|
358
|
-
|
|
429
|
+
)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Per-Call Options & Defaults
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
interface ProcedureCallDefaults {
|
|
436
|
+
signal?: AbortSignal // cancellation (combined with per-call via AbortSignal.any)
|
|
437
|
+
timeout?: number // ms — per-call timeout:0 disables inherited default
|
|
438
|
+
headers?: Record<string, string> // merged, per-call keys win
|
|
439
|
+
basePath?: string // per-call > default > config.basePath
|
|
440
|
+
meta?: RequestMeta // typed per-request metadata (declaration-mergeable)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
interface ProcedureCallOptions extends ProcedureCallDefaults, ClientHooks {}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
- Defaults are set via `config.defaults`; per-call options are passed as the 2nd argument to any generated callable.
|
|
447
|
+
- Header precedence (low → high): adapter config < `defaults.headers` < per-call `headers` < route-declared `schema.input.headers` < `onBeforeRequest` mutations.
|
|
448
|
+
|
|
449
|
+
### Typed RequestMeta (Declaration Merging)
|
|
450
|
+
|
|
451
|
+
Augment `RequestMeta` to type `meta` end-to-end (options, hooks, adapter):
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// For code-generated self-contained clients
|
|
455
|
+
declare module './generated/_types' {
|
|
456
|
+
interface RequestMeta {
|
|
457
|
+
traceId: string
|
|
458
|
+
priority?: 'high' | 'low'
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Or for direct ts-procedures/client usage
|
|
463
|
+
declare module 'ts-procedures/client' {
|
|
464
|
+
interface RequestMeta { traceId: string }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
await client.users.GetUser(
|
|
468
|
+
{ pathParams: { id: '1' } },
|
|
469
|
+
{ meta: { traceId: 'req-abc', priority: 'high' } }, // fully typed
|
|
470
|
+
)
|
|
359
471
|
```
|
|
360
472
|
|
|
361
473
|
### Hook Types
|
|
362
474
|
|
|
363
475
|
```typescript
|
|
364
476
|
interface ClientHooks {
|
|
365
|
-
onBeforeRequest?: (ctx:
|
|
366
|
-
onAfterResponse?: (ctx:
|
|
477
|
+
onBeforeRequest?: (ctx: BeforeRequestContext) => BeforeRequestContext | Promise<BeforeRequestContext>
|
|
478
|
+
onAfterResponse?: (ctx: AfterResponseContext) => void | Promise<void>
|
|
479
|
+
onError?: (ctx: ErrorContext) => void | Promise<void>
|
|
367
480
|
}
|
|
368
481
|
```
|
|
369
482
|
|
|
@@ -196,24 +196,89 @@ const app = new HonoAPIAppBuilder({ pathPrefix: '/api' })
|
|
|
196
196
|
|
|
197
197
|
| Error Class | Trigger | HTTP Status |
|
|
198
198
|
|-------------|---------|-------------|
|
|
199
|
-
| `ProcedureValidationError` | Schema params validation failure | 400 |
|
|
200
|
-
| `ProcedureError` | `ctx.error()` or unhandled handler exception |
|
|
199
|
+
| `ProcedureValidationError` | Schema params validation failure | 400 (auto — default taxonomy) |
|
|
200
|
+
| `ProcedureError` | `ctx.error()` or unhandled handler exception | 500 (auto — default taxonomy) |
|
|
201
201
|
| `ProcedureYieldValidationError` | Yield validation failure (validateYields: true) | N/A (mid-stream) |
|
|
202
202
|
| `ProcedureRegistrationError` | Invalid schema or duplicate name at registration | N/A (startup) |
|
|
203
203
|
|
|
204
|
+
### Error Taxonomy (required for custom errors)
|
|
205
|
+
|
|
206
|
+
`defineErrorTaxonomy` maps error classes to HTTP responses declaratively. Works with every HTTP builder (`hono-api`, `hono-rpc`, `express-rpc`, `hono-stream` pre-stream).
|
|
207
|
+
|
|
208
|
+
Do NOT write `onError` `instanceof` ladders — that's anti-pattern #20. Use the taxonomy instead:
|
|
209
|
+
|
|
204
210
|
```typescript
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
} else {
|
|
212
|
-
res.status(500).json({ error: 'Internal server error' })
|
|
211
|
+
import { defineErrorTaxonomy } from 'ts-procedures/http-errors'
|
|
212
|
+
|
|
213
|
+
class UseCaseError extends Error {
|
|
214
|
+
constructor(readonly externalMsg: string, readonly internalMsg: string) {
|
|
215
|
+
super(externalMsg); this.name = 'UseCaseError'
|
|
216
|
+
Object.setPrototypeOf(this, UseCaseError.prototype)
|
|
213
217
|
}
|
|
214
218
|
}
|
|
219
|
+
|
|
220
|
+
const appErrors = defineErrorTaxonomy({
|
|
221
|
+
// First-match wins — declare subclasses before base classes
|
|
222
|
+
UseCaseError: {
|
|
223
|
+
class: UseCaseError,
|
|
224
|
+
statusCode: 422,
|
|
225
|
+
toResponse: (err) => ({ name: 'UseCaseError', message: err.externalMsg }),
|
|
226
|
+
onCatch: (err) => logger.error(err.internalMsg), // internal logs only
|
|
227
|
+
},
|
|
228
|
+
MongoDuplicateKey: {
|
|
229
|
+
match: (err): err is Error => err instanceof Error && (err as any).code === 11000,
|
|
230
|
+
statusCode: 409,
|
|
231
|
+
toResponse: () => ({ name: 'Conflict', message: 'Resource already exists' }),
|
|
232
|
+
},
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
new HonoAPIAppBuilder({
|
|
236
|
+
errors: appErrors,
|
|
237
|
+
unknownError: { statusCode: 500, toResponse: () => ({ name: 'InternalServerError' }) },
|
|
238
|
+
})
|
|
215
239
|
```
|
|
216
240
|
|
|
241
|
+
Handlers throw the error classes directly — the builder auto-serializes. `onError` is the first-class imperative peer when you want to handle errors in one callback instead (both modes coexist). In v6 `HonoStreamAppBuilder.onPreStreamError` was renamed to `onError`. Cross-cutting `onRequestError` observer fires for every caught error.
|
|
242
|
+
|
|
243
|
+
### Per-route errors (typed)
|
|
244
|
+
|
|
245
|
+
`APIConfig` and `RPCConfig` are generic over `TErrorKey extends string = string`. Narrow to your taxonomy for compile-time typo protection and route-level error doc entries:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { APIConfig } from 'ts-procedures/http'
|
|
249
|
+
import { DocRegistry } from 'ts-procedures/http-docs'
|
|
250
|
+
|
|
251
|
+
type MyAPIConfig = APIConfig<keyof typeof appErrors & string>
|
|
252
|
+
const API = Procedures<Ctx, MyAPIConfig>()
|
|
253
|
+
|
|
254
|
+
API.Create('GetUser', { path: '/users/:id', method: 'get', errors: ['UseCaseError'], /* ... */ }, ...)
|
|
255
|
+
|
|
256
|
+
// Seed envelope errors from the taxonomy + framework defaults in one call
|
|
257
|
+
const envelope = DocRegistry.fromTaxonomy(appErrors, { basePath: '/api' }).from(apiApp).toJSON()
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Client-side typed catch blocks (via codegen)
|
|
261
|
+
|
|
262
|
+
The generated `_errors.ts` emits real runtime classes extending a shared `${Service}ProcedureError` base. The generated `createApiClient` wires an error registry so non-2xx responses arrive as typed class instances:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { createApiClient, ApiErrors, createFetchAdapter } from './generated'
|
|
266
|
+
|
|
267
|
+
const api = createApiClient({ adapter: createFetchAdapter({ /* ... */ }), basePath: '...' })
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
await api.users.getUser({ pathParams: { id: 'x' } })
|
|
271
|
+
} catch (err) {
|
|
272
|
+
if (err instanceof ApiErrors.UseCaseError) {
|
|
273
|
+
// err.body typed; err.status, err.procedureName, err.scope available
|
|
274
|
+
} else if (err instanceof ApiErrors.ApiProcedureError) {
|
|
275
|
+
// Catch-all for any service error
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Per-route error unions: routes with `errors: [...]` get an `Errors` type in their namespace (e.g. `Users.GetUser.Errors = ApiErrors.UseCaseError | ApiErrors.AuthError`).
|
|
281
|
+
|
|
217
282
|
## Lifecycle Hook Order
|
|
218
283
|
|
|
219
284
|
### Standard RPC
|
|
@@ -225,7 +290,7 @@ onRequestStart → factoryContext() → handler() → onSuccess → onRequestEnd
|
|
|
225
290
|
### Streaming
|
|
226
291
|
```
|
|
227
292
|
onRequestStart → factoryContext() → validation → onStreamStart → handler yields → onStreamEnd → onRequestEnd
|
|
228
|
-
→
|
|
293
|
+
→ onError (or taxonomy) → onRequestEnd
|
|
229
294
|
→ onMidStreamError → onStreamEnd → onRequestEnd
|
|
230
295
|
```
|
|
231
296
|
|
|
@@ -320,7 +385,7 @@ npx ts-procedures-codegen --url http://localhost:3000/docs --out ./src/generated
|
|
|
320
385
|
# --clean-out-dir # recursively wipe --out before writing (prunes stale scope files)
|
|
321
386
|
```
|
|
322
387
|
|
|
323
|
-
Generates one `.ts` file per scope plus a root `index.ts` that imports each scope as a namespace and exports a `create${ServiceName}Bindings` factory (defaults to `createApiBindings`; pass `--service-name <Name>` to rename). When namespace mode is on (the default), `index.ts` also wraps every scope namespace in an outer `export namespace ${ServiceName} { ... }` block so types are reachable as `Api.Users.GetUser.Params`, `Api.Errors.
|
|
388
|
+
Generates one `.ts` file per scope plus a root `index.ts` that imports each scope as a namespace and exports a `create${ServiceName}Bindings(client)` factory AND a `create${ServiceName}Client(config)` convenience factory that pre-wires the error registry (defaults to `createApiBindings` / `createApiClient`; pass `--service-name <Name>` to rename). When namespace mode is on (the default), `index.ts` also wraps every scope namespace in an outer `export namespace ${ServiceName} { ... }` block so types are reachable as `Api.Users.GetUser.Params`, `Api.Errors.UseCaseError`, etc. The errors file (`_errors.ts`) emits runtime error classes extending a shared `${ServiceName}ProcedureError` base, each with `static fromResponse(body, meta)`, plus `${ServiceName}ErrorRegistry` (runtime dispatch map) and `${ServiceName}ProcedureErrorUnion` (type union). Defaults: `ApiErrors`, `ApiProcedureError`, `ApiErrorRegistry`, `ApiProcedureErrorUnion`.
|
|
324
389
|
By default, types are wrapped in nested TS namespaces (`Scope.Route.Params`), JSDoc comments are emitted, and output is self-contained (no runtime dependency on `ts-procedures`). Use `--no-namespace-types` to revert to flat type names (`RouteParams`); this also disables the outer service namespace in `index.ts` and skips importing `_errors` from there.
|
|
325
390
|
Note: ajsc formatting options (`--enum-style enum`, `--depluralize`, etc.) only take effect in namespace mode (the default). They are ignored with `--no-namespace-types`.
|
|
326
391
|
Supports config file: `ts-procedures-codegen.config.json` (auto-loaded from CWD) or `--config <path>`.
|
|
@@ -336,6 +401,7 @@ const client = createClient({
|
|
|
336
401
|
adapter: createFetchAdapter(),
|
|
337
402
|
basePath: 'http://localhost:3000',
|
|
338
403
|
scopes: createApiBindings,
|
|
404
|
+
defaults: { timeout: 30_000, headers: { 'X-Client-Version': '1.0.0' } },
|
|
339
405
|
hooks: {
|
|
340
406
|
onBeforeRequest(ctx) {
|
|
341
407
|
ctx.request.headers = { ...ctx.request.headers, Authorization: `Bearer ${getToken()}` }
|
|
@@ -350,20 +416,67 @@ const client = createClient({
|
|
|
350
416
|
// Fully typed — params and response inferred from server schemas; type aliases live under Api.<Scope>.<Route>.
|
|
351
417
|
const user = await client.users.GetUser({ pathParams: { id: '123' } })
|
|
352
418
|
|
|
353
|
-
// Per-call
|
|
354
|
-
await client.users.GetUser(
|
|
355
|
-
|
|
356
|
-
|
|
419
|
+
// Per-call options — timeout, signal, headers, basePath, and hooks all share one bag
|
|
420
|
+
await client.users.GetUser(
|
|
421
|
+
{ pathParams: { id: '123' } },
|
|
422
|
+
{
|
|
423
|
+
timeout: 5000,
|
|
424
|
+
headers: { 'X-Request-Id': crypto.randomUUID() },
|
|
425
|
+
onAfterResponse(ctx) {
|
|
426
|
+
console.log(ctx.response.headers['x-rate-limit-remaining'])
|
|
427
|
+
},
|
|
357
428
|
},
|
|
358
|
-
|
|
429
|
+
)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Per-Call Options & Defaults
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
interface ProcedureCallDefaults {
|
|
436
|
+
signal?: AbortSignal // cancellation (combined with per-call via AbortSignal.any)
|
|
437
|
+
timeout?: number // ms — per-call timeout:0 disables inherited default
|
|
438
|
+
headers?: Record<string, string> // merged, per-call keys win
|
|
439
|
+
basePath?: string // per-call > default > config.basePath
|
|
440
|
+
meta?: RequestMeta // typed per-request metadata (declaration-mergeable)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
interface ProcedureCallOptions extends ProcedureCallDefaults, ClientHooks {}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
- Defaults are set via `config.defaults`; per-call options are passed as the 2nd argument to any generated callable.
|
|
447
|
+
- Header precedence (low → high): adapter config < `defaults.headers` < per-call `headers` < route-declared `schema.input.headers` < `onBeforeRequest` mutations.
|
|
448
|
+
|
|
449
|
+
### Typed RequestMeta (Declaration Merging)
|
|
450
|
+
|
|
451
|
+
Augment `RequestMeta` to type `meta` end-to-end (options, hooks, adapter):
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
// For code-generated self-contained clients
|
|
455
|
+
declare module './generated/_types' {
|
|
456
|
+
interface RequestMeta {
|
|
457
|
+
traceId: string
|
|
458
|
+
priority?: 'high' | 'low'
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Or for direct ts-procedures/client usage
|
|
463
|
+
declare module 'ts-procedures/client' {
|
|
464
|
+
interface RequestMeta { traceId: string }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
await client.users.GetUser(
|
|
468
|
+
{ pathParams: { id: '1' } },
|
|
469
|
+
{ meta: { traceId: 'req-abc', priority: 'high' } }, // fully typed
|
|
470
|
+
)
|
|
359
471
|
```
|
|
360
472
|
|
|
361
473
|
### Hook Types
|
|
362
474
|
|
|
363
475
|
```typescript
|
|
364
476
|
interface ClientHooks {
|
|
365
|
-
onBeforeRequest?: (ctx:
|
|
366
|
-
onAfterResponse?: (ctx:
|
|
477
|
+
onBeforeRequest?: (ctx: BeforeRequestContext) => BeforeRequestContext | Promise<BeforeRequestContext>
|
|
478
|
+
onAfterResponse?: (ctx: AfterResponseContext) => void | Promise<void>
|
|
479
|
+
onError?: (ctx: ErrorContext) => void | Promise<void>
|
|
367
480
|
}
|
|
368
481
|
```
|
|
369
482
|
|
package/build/client/call.d.ts
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import type { ClientAdapter, ClientHooks, CallDescriptor } from './types.js';
|
|
1
|
+
import type { ClientAdapter, ClientHooks, CallDescriptor, ErrorRegistry, ProcedureCallDefaults, ProcedureCallOptions } from './types.js';
|
|
2
|
+
export interface ExecuteCallConfig {
|
|
3
|
+
descriptor: CallDescriptor;
|
|
4
|
+
basePath: string;
|
|
5
|
+
adapter: ClientAdapter;
|
|
6
|
+
hooks: ClientHooks;
|
|
7
|
+
defaults?: ProcedureCallDefaults;
|
|
8
|
+
options?: ProcedureCallOptions;
|
|
9
|
+
errorRegistry?: ErrorRegistry;
|
|
10
|
+
}
|
|
2
11
|
/**
|
|
3
12
|
* Executes a single procedure call through the adapter.
|
|
4
13
|
*
|
|
5
14
|
* Flow:
|
|
6
|
-
* 1.
|
|
7
|
-
* 2.
|
|
8
|
-
* 3.
|
|
9
|
-
* 4.
|
|
10
|
-
* 5.
|
|
11
|
-
* 6.
|
|
12
|
-
* 7.
|
|
15
|
+
* 1. Resolve base path (per-call > defaults > config) and build AdapterRequest
|
|
16
|
+
* 2. Apply request options (headers, signal, timeout, meta) from defaults + per-call
|
|
17
|
+
* 3. Run onBeforeRequest hooks (global then local) — may further mutate request
|
|
18
|
+
* 4. Call adapter.request()
|
|
19
|
+
* 5. On adapter error: run onError hooks, re-throw
|
|
20
|
+
* 6. Run onAfterResponse hooks (may mutate response.status to swallow errors)
|
|
21
|
+
* 7. If response status is non-2xx: throw ClientRequestError
|
|
22
|
+
* 8. Return response.body as TResponse
|
|
13
23
|
*/
|
|
14
|
-
export declare function executeCall<TResponse>(
|
|
24
|
+
export declare function executeCall<TResponse>(config: ExecuteCallConfig): Promise<TResponse>;
|
package/build/client/call.js
CHANGED
|
@@ -1,38 +1,52 @@
|
|
|
1
1
|
import { buildAdapterRequest } from './request-builder.js';
|
|
2
2
|
import { runBeforeRequest, runAfterResponse, runOnError } from './hooks.js';
|
|
3
|
+
import { applyRequestOptions, resolveBasePath } from './resolve-options.js';
|
|
3
4
|
import { ClientRequestError } from './errors.js';
|
|
5
|
+
import { dispatchTypedError } from './error-dispatch.js';
|
|
4
6
|
/**
|
|
5
7
|
* Executes a single procedure call through the adapter.
|
|
6
8
|
*
|
|
7
9
|
* Flow:
|
|
8
|
-
* 1.
|
|
9
|
-
* 2.
|
|
10
|
-
* 3.
|
|
11
|
-
* 4.
|
|
12
|
-
* 5.
|
|
13
|
-
* 6.
|
|
14
|
-
* 7.
|
|
10
|
+
* 1. Resolve base path (per-call > defaults > config) and build AdapterRequest
|
|
11
|
+
* 2. Apply request options (headers, signal, timeout, meta) from defaults + per-call
|
|
12
|
+
* 3. Run onBeforeRequest hooks (global then local) — may further mutate request
|
|
13
|
+
* 4. Call adapter.request()
|
|
14
|
+
* 5. On adapter error: run onError hooks, re-throw
|
|
15
|
+
* 6. Run onAfterResponse hooks (may mutate response.status to swallow errors)
|
|
16
|
+
* 7. If response status is non-2xx: throw ClientRequestError
|
|
17
|
+
* 8. Return response.body as TResponse
|
|
15
18
|
*/
|
|
16
|
-
export async function executeCall(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
export async function executeCall(config) {
|
|
20
|
+
const { descriptor, basePath, adapter, hooks, defaults, options, errorRegistry } = config;
|
|
21
|
+
// 1. Build the initial request (path/query/body from descriptor)
|
|
22
|
+
const resolvedBasePath = resolveBasePath(defaults, options, basePath);
|
|
23
|
+
let request = buildAdapterRequest(descriptor, resolvedBasePath);
|
|
24
|
+
// 2. Apply request-level options (headers, signal, timeout, meta)
|
|
25
|
+
request = applyRequestOptions(request, defaults, options);
|
|
26
|
+
// 3. Run before-request hooks — they may further mutate the request
|
|
27
|
+
const beforeCtx = await runBeforeRequest({ procedureName: descriptor.name, scope: descriptor.scope, request }, hooks, options);
|
|
21
28
|
request = beforeCtx.request;
|
|
22
|
-
//
|
|
29
|
+
// 4. Call the adapter
|
|
23
30
|
let response;
|
|
24
31
|
try {
|
|
25
32
|
response = await adapter.request(request);
|
|
26
33
|
}
|
|
27
34
|
catch (err) {
|
|
28
|
-
//
|
|
29
|
-
await runOnError({ procedureName: descriptor.name, scope: descriptor.scope, request, error: err },
|
|
35
|
+
// 5. On adapter error: run error hooks, re-throw
|
|
36
|
+
await runOnError({ procedureName: descriptor.name, scope: descriptor.scope, request, error: err }, hooks, options);
|
|
30
37
|
throw err;
|
|
31
38
|
}
|
|
32
|
-
//
|
|
33
|
-
await runAfterResponse({ procedureName: descriptor.name, scope: descriptor.scope, request, response },
|
|
34
|
-
//
|
|
39
|
+
// 6. Run after-response hooks — they may mutate response.status to swallow errors
|
|
40
|
+
await runAfterResponse({ procedureName: descriptor.name, scope: descriptor.scope, request, response }, hooks, options);
|
|
41
|
+
// 7. Check status AFTER hooks (hooks may have swallowed the error by mutating status)
|
|
35
42
|
if (response.status < 200 || response.status >= 300) {
|
|
43
|
+
const typed = dispatchTypedError(errorRegistry, response.body, {
|
|
44
|
+
status: response.status,
|
|
45
|
+
procedureName: descriptor.name,
|
|
46
|
+
scope: descriptor.scope,
|
|
47
|
+
});
|
|
48
|
+
if (typed)
|
|
49
|
+
throw typed;
|
|
36
50
|
throw new ClientRequestError({
|
|
37
51
|
status: response.status,
|
|
38
52
|
headers: response.headers,
|
|
@@ -41,7 +55,7 @@ export async function executeCall(descriptor, basePath, adapter, globalHooks, lo
|
|
|
41
55
|
scope: descriptor.scope,
|
|
42
56
|
});
|
|
43
57
|
}
|
|
44
|
-
//
|
|
58
|
+
// 8. Return the body
|
|
45
59
|
return response.body;
|
|
46
60
|
}
|
|
47
61
|
//# sourceMappingURL=call.js.map
|
package/build/client/call.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call.js","sourceRoot":"","sources":["../../src/client/call.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"call.js","sourceRoot":"","sources":["../../src/client/call.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC3E,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAoBxD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAY,MAAyB;IACpE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CAAA;IAEzF,iEAAiE;IACjE,MAAM,gBAAgB,GAAG,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAA;IACrE,IAAI,OAAO,GAAG,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAA;IAE/D,kEAAkE;IAClE,OAAO,GAAG,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IAEzD,oEAAoE;IACpE,MAAM,SAAS,GAAG,MAAM,gBAAgB,CACtC,EAAE,aAAa,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,EACpE,KAAK,EACL,OAAO,CACR,CAAA;IACD,OAAO,GAAG,SAAS,CAAC,OAAO,CAAA;IAE3B,sBAAsB;IACtB,IAAI,QAAQ,CAAA;IACZ,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,iDAAiD;QACjD,MAAM,UAAU,CACd,EAAE,aAAa,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,EAChF,KAAK,EACL,OAAO,CACR,CAAA;QACD,MAAM,GAAG,CAAA;IACX,CAAC;IAED,kFAAkF;IAClF,MAAM,gBAAgB,CACpB,EAAE,aAAa,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAC9E,KAAK,EACL,OAAO,CACR,CAAA;IAED,sFAAsF;IACtF,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,kBAAkB,CAAC,aAAa,EAAE,QAAQ,CAAC,IAAI,EAAE;YAC7D,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,aAAa,EAAE,UAAU,CAAC,IAAI;YAC9B,KAAK,EAAE,UAAU,CAAC,KAAK;SACxB,CAAC,CAAA;QACF,IAAI,KAAK;YAAE,MAAM,KAAK,CAAA;QACtB,MAAM,IAAI,kBAAkB,CAAC;YAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,aAAa,EAAE,UAAU,CAAC,IAAI;YAC9B,KAAK,EAAE,UAAU,CAAC,KAAK;SACxB,CAAC,CAAA;IACJ,CAAC;IAED,qBAAqB;IACrB,OAAO,QAAQ,CAAC,IAAiB,CAAA;AACnC,CAAC"}
|