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
|
@@ -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:
|
|
@@ -169,6 +292,14 @@ app.get('/docs', (c) => c.json(docs.toJSON()))
|
|
|
169
292
|
|
|
170
293
|
`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
294
|
|
|
295
|
+
`DocRegistry.fromTaxonomy(taxonomy, config?)` is a convenience constructor that seeds `envelope.errors` from your taxonomy plus framework defaults in one call (deduped — your entries win when keys overlap):
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
const docs = DocRegistry.fromTaxonomy(appErrors, { basePath: '/api' })
|
|
299
|
+
.from(rpcBuilder)
|
|
300
|
+
.from(apiBuilder)
|
|
301
|
+
```
|
|
302
|
+
|
|
172
303
|
The `DocRegistry` output is the input for [Client Code Generation](./client-and-codegen.md).
|
|
173
304
|
|
|
174
305
|
## Type Exports
|
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(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-procedures",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.",
|
|
5
5
|
"main": "build/exports.js",
|
|
6
6
|
"types": "build/exports.d.ts",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc",
|
|
14
14
|
"lint": "npx eslint src/ --quiet",
|
|
15
|
-
"
|
|
15
|
+
"check-docs": "bash scripts/check-docs-consistency.sh",
|
|
16
|
+
"prepublishOnly": "npm run lint && npm run build && npm run check-docs",
|
|
16
17
|
"postinstall": "node ./agent_config/bin/postinstall.mjs",
|
|
17
18
|
"test": "vitest run"
|
|
18
19
|
},
|
|
@@ -45,6 +46,10 @@
|
|
|
45
46
|
"types": "./build/implementations/http/doc-registry.d.ts",
|
|
46
47
|
"import": "./build/implementations/http/doc-registry.js"
|
|
47
48
|
},
|
|
49
|
+
"./http-errors": {
|
|
50
|
+
"types": "./build/implementations/http/error-taxonomy.d.ts",
|
|
51
|
+
"import": "./build/implementations/http/error-taxonomy.js"
|
|
52
|
+
},
|
|
48
53
|
"./client": {
|
|
49
54
|
"types": "./build/client/index.d.ts",
|
|
50
55
|
"import": "./build/client/index.js"
|
package/src/client/call.test.ts
CHANGED
|
@@ -7,6 +7,8 @@ import type {
|
|
|
7
7
|
AdapterResponse,
|
|
8
8
|
ClientHooks,
|
|
9
9
|
CallDescriptor,
|
|
10
|
+
ProcedureCallDefaults,
|
|
11
|
+
ProcedureCallOptions,
|
|
10
12
|
} from './types.js'
|
|
11
13
|
|
|
12
14
|
// ── helpers ───────────────────────────────────────────────
|
|
@@ -37,43 +39,55 @@ function makeAdapter(response?: Partial<AdapterResponse>): ClientAdapter {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
interface RunConfig {
|
|
43
|
+
adapter: ClientAdapter
|
|
44
|
+
hooks?: ClientHooks
|
|
45
|
+
defaults?: ProcedureCallDefaults
|
|
46
|
+
options?: ProcedureCallOptions
|
|
47
|
+
descriptor?: CallDescriptor
|
|
48
|
+
basePath?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function run<T>({
|
|
52
|
+
adapter,
|
|
53
|
+
hooks = {},
|
|
54
|
+
defaults,
|
|
55
|
+
options,
|
|
56
|
+
descriptor = makeDescriptor(),
|
|
57
|
+
basePath = 'https://api.example.com',
|
|
58
|
+
}: RunConfig): Promise<T> {
|
|
59
|
+
return executeCall<T>({ descriptor, basePath, adapter, hooks, defaults, options })
|
|
60
|
+
}
|
|
61
|
+
|
|
40
62
|
// ── executeCall ───────────────────────────────────────────
|
|
41
63
|
|
|
42
64
|
describe('executeCall', () => {
|
|
43
65
|
it('calls adapter.request and returns body', async () => {
|
|
44
66
|
const adapter = makeAdapter({ body: { id: '1', name: 'Bob' } })
|
|
45
|
-
const result = await
|
|
67
|
+
const result = await run({ adapter })
|
|
46
68
|
expect(adapter.request).toHaveBeenCalledOnce()
|
|
47
69
|
expect(result).toEqual({ id: '1', name: 'Bob' })
|
|
48
70
|
})
|
|
49
71
|
|
|
50
72
|
it('throws ClientRequestError on 4xx response', async () => {
|
|
51
73
|
const adapter = makeAdapter({ status: 404, body: { message: 'Not Found' } })
|
|
52
|
-
await expect(
|
|
53
|
-
executeCall(makeDescriptor(), 'https://api.example.com', adapter, {}, undefined)
|
|
54
|
-
).rejects.toThrow(ClientRequestError)
|
|
74
|
+
await expect(run({ adapter })).rejects.toThrow(ClientRequestError)
|
|
55
75
|
})
|
|
56
76
|
|
|
57
77
|
it('throws ClientRequestError on 5xx response', async () => {
|
|
58
78
|
const adapter = makeAdapter({ status: 500, body: { message: 'Server Error' } })
|
|
59
|
-
await expect(
|
|
60
|
-
executeCall(makeDescriptor(), 'https://api.example.com', adapter, {}, undefined)
|
|
61
|
-
).rejects.toThrow(ClientRequestError)
|
|
79
|
+
await expect(run({ adapter })).rejects.toThrow(ClientRequestError)
|
|
62
80
|
})
|
|
63
81
|
|
|
64
82
|
it('throws ClientRequestError on 199 response (below 200)', async () => {
|
|
65
83
|
const adapter = makeAdapter({ status: 199, body: null })
|
|
66
|
-
await expect(
|
|
67
|
-
executeCall(makeDescriptor(), 'https://api.example.com', adapter, {}, undefined)
|
|
68
|
-
).rejects.toThrow(ClientRequestError)
|
|
84
|
+
await expect(run({ adapter })).rejects.toThrow(ClientRequestError)
|
|
69
85
|
})
|
|
70
86
|
|
|
71
87
|
it('does not throw on 2xx boundary responses (200, 201, 299)', async () => {
|
|
72
88
|
for (const status of [200, 201, 204, 299]) {
|
|
73
89
|
const adapter = makeAdapter({ status, body: null })
|
|
74
|
-
await expect(
|
|
75
|
-
executeCall(makeDescriptor(), 'https://api.example.com', adapter, {}, undefined)
|
|
76
|
-
).resolves.not.toThrow()
|
|
90
|
+
await expect(run({ adapter })).resolves.not.toThrow()
|
|
77
91
|
}
|
|
78
92
|
})
|
|
79
93
|
|
|
@@ -87,7 +101,7 @@ describe('executeCall', () => {
|
|
|
87
101
|
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
88
102
|
}
|
|
89
103
|
|
|
90
|
-
const
|
|
104
|
+
const hooks: ClientHooks = {
|
|
91
105
|
onBeforeRequest: (ctx) => ({
|
|
92
106
|
...ctx,
|
|
93
107
|
request: {
|
|
@@ -97,7 +111,7 @@ describe('executeCall', () => {
|
|
|
97
111
|
}),
|
|
98
112
|
}
|
|
99
113
|
|
|
100
|
-
await
|
|
114
|
+
await run({ adapter, hooks })
|
|
101
115
|
expect(capturedHeaders[0]?.['x-auth']).toBe('token-123')
|
|
102
116
|
})
|
|
103
117
|
|
|
@@ -110,26 +124,23 @@ describe('executeCall', () => {
|
|
|
110
124
|
}),
|
|
111
125
|
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
112
126
|
}
|
|
113
|
-
const
|
|
127
|
+
const hooks: ClientHooks = {
|
|
114
128
|
onAfterResponse: () => { order.push('afterResponse') },
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
await
|
|
131
|
+
await run({ adapter, hooks })
|
|
118
132
|
expect(order).toEqual(['adapter', 'afterResponse'])
|
|
119
133
|
})
|
|
120
134
|
|
|
121
135
|
it('does not throw when onAfterResponse swallows non-2xx by mutating status', async () => {
|
|
122
136
|
const adapter = makeAdapter({ status: 401, body: { message: 'Unauthorized' } })
|
|
123
|
-
const
|
|
137
|
+
const hooks: ClientHooks = {
|
|
124
138
|
onAfterResponse: (ctx) => {
|
|
125
|
-
// Swallow the error by setting status to 200
|
|
126
139
|
ctx.response.status = 200
|
|
127
140
|
},
|
|
128
141
|
}
|
|
129
142
|
|
|
130
|
-
await expect(
|
|
131
|
-
executeCall(makeDescriptor(), 'https://api.example.com', adapter, globalHooks, undefined)
|
|
132
|
-
).resolves.not.toThrow()
|
|
143
|
+
await expect(run({ adapter, hooks })).resolves.not.toThrow()
|
|
133
144
|
})
|
|
134
145
|
|
|
135
146
|
it('runs onError on adapter failure and re-throws', async () => {
|
|
@@ -139,24 +150,186 @@ describe('executeCall', () => {
|
|
|
139
150
|
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
140
151
|
}
|
|
141
152
|
const receivedErrors: unknown[] = []
|
|
142
|
-
const
|
|
153
|
+
const hooks: ClientHooks = {
|
|
143
154
|
onError: (ctx) => { receivedErrors.push(ctx.error) },
|
|
144
155
|
}
|
|
145
156
|
|
|
146
|
-
await expect(
|
|
147
|
-
executeCall(makeDescriptor(), 'https://api.example.com', adapter, globalHooks, undefined)
|
|
148
|
-
).rejects.toThrow('Network failure')
|
|
157
|
+
await expect(run({ adapter, hooks })).rejects.toThrow('Network failure')
|
|
149
158
|
expect(receivedErrors[0]).toBe(adapterError)
|
|
150
159
|
})
|
|
151
160
|
|
|
152
|
-
it('passes per-procedure hooks as local
|
|
161
|
+
it('passes per-procedure hooks as local options', async () => {
|
|
153
162
|
const adapter = makeAdapter()
|
|
154
163
|
const localOrder: string[] = []
|
|
155
|
-
const
|
|
164
|
+
const options: ProcedureCallOptions = {
|
|
156
165
|
onBeforeRequest: (ctx) => { localOrder.push('local-before'); return ctx },
|
|
157
166
|
}
|
|
158
167
|
|
|
159
|
-
await
|
|
168
|
+
await run({ adapter, options })
|
|
160
169
|
expect(localOrder).toContain('local-before')
|
|
161
170
|
})
|
|
171
|
+
|
|
172
|
+
// ── Per-call options: signal / timeout / headers / basePath / meta ──
|
|
173
|
+
|
|
174
|
+
it('per-call timeout attaches a signal via AbortSignal.timeout', async () => {
|
|
175
|
+
const spy = vi.spyOn(AbortSignal, 'timeout')
|
|
176
|
+
try {
|
|
177
|
+
let observedSignal: AbortSignal | undefined
|
|
178
|
+
const adapter: ClientAdapter = {
|
|
179
|
+
request: vi.fn(async (req: AdapterRequest): Promise<AdapterResponse> => {
|
|
180
|
+
observedSignal = req.signal
|
|
181
|
+
return { status: 200, headers: {}, body: {} }
|
|
182
|
+
}),
|
|
183
|
+
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await run({ adapter, options: { timeout: 5000 } })
|
|
187
|
+
expect(spy).toHaveBeenCalledWith(5000)
|
|
188
|
+
expect(observedSignal).toBeDefined()
|
|
189
|
+
} finally {
|
|
190
|
+
spy.mockRestore()
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('adapter receives a signal that reflects abort when the caller cancels', async () => {
|
|
195
|
+
const controller = new AbortController()
|
|
196
|
+
const adapter: ClientAdapter = {
|
|
197
|
+
request: vi.fn(async (req: AdapterRequest): Promise<AdapterResponse> => {
|
|
198
|
+
return new Promise((_resolve, reject) => {
|
|
199
|
+
const abort = () => reject(new Error('aborted'))
|
|
200
|
+
if (req.signal?.aborted) abort()
|
|
201
|
+
else req.signal?.addEventListener('abort', abort, { once: true })
|
|
202
|
+
})
|
|
203
|
+
}),
|
|
204
|
+
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const promise = run({ adapter, options: { signal: controller.signal } })
|
|
208
|
+
// Let executeCall reach the adapter before aborting
|
|
209
|
+
await Promise.resolve()
|
|
210
|
+
controller.abort()
|
|
211
|
+
await expect(promise).rejects.toThrow('aborted')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('per-call signal is forwarded to the adapter', async () => {
|
|
215
|
+
const controller = new AbortController()
|
|
216
|
+
let observedSignal: AbortSignal | undefined
|
|
217
|
+
const adapter: ClientAdapter = {
|
|
218
|
+
request: vi.fn(async (req: AdapterRequest): Promise<AdapterResponse> => {
|
|
219
|
+
observedSignal = req.signal
|
|
220
|
+
return { status: 200, headers: {}, body: {} }
|
|
221
|
+
}),
|
|
222
|
+
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await run({ adapter, options: { signal: controller.signal } })
|
|
226
|
+
expect(observedSignal).toBe(controller.signal)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('per-call headers are merged into the request before hooks', async () => {
|
|
230
|
+
const capturedHeaders: Record<string, string>[] = []
|
|
231
|
+
const seenByHook: Record<string, string>[] = []
|
|
232
|
+
const adapter: ClientAdapter = {
|
|
233
|
+
request: vi.fn(async (req: AdapterRequest): Promise<AdapterResponse> => {
|
|
234
|
+
capturedHeaders.push(req.headers ?? {})
|
|
235
|
+
return { status: 200, headers: {}, body: {} }
|
|
236
|
+
}),
|
|
237
|
+
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const hooks: ClientHooks = {
|
|
241
|
+
onBeforeRequest: (ctx) => {
|
|
242
|
+
seenByHook.push({ ...ctx.request.headers })
|
|
243
|
+
return ctx
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
await run({ adapter, hooks, options: { headers: { 'x-request-id': 'req-123' } } })
|
|
248
|
+
expect(seenByHook[0]?.['x-request-id']).toBe('req-123')
|
|
249
|
+
expect(capturedHeaders[0]?.['x-request-id']).toBe('req-123')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('per-call basePath overrides the client base path', async () => {
|
|
253
|
+
const capturedUrls: string[] = []
|
|
254
|
+
const adapter: ClientAdapter = {
|
|
255
|
+
request: vi.fn(async (req: AdapterRequest): Promise<AdapterResponse> => {
|
|
256
|
+
capturedUrls.push(req.url)
|
|
257
|
+
return { status: 200, headers: {}, body: {} }
|
|
258
|
+
}),
|
|
259
|
+
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
await run({
|
|
263
|
+
adapter,
|
|
264
|
+
descriptor: makeDescriptor({ path: '/users' }),
|
|
265
|
+
basePath: 'https://default.example.com',
|
|
266
|
+
options: { basePath: 'https://override.example.com' },
|
|
267
|
+
})
|
|
268
|
+
expect(capturedUrls[0]).toBe('https://override.example.com/users')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('global defaults apply when no per-call options', async () => {
|
|
272
|
+
const capturedHeaders: Record<string, string>[] = []
|
|
273
|
+
const capturedUrls: string[] = []
|
|
274
|
+
const adapter: ClientAdapter = {
|
|
275
|
+
request: vi.fn(async (req: AdapterRequest): Promise<AdapterResponse> => {
|
|
276
|
+
capturedHeaders.push(req.headers ?? {})
|
|
277
|
+
capturedUrls.push(req.url)
|
|
278
|
+
return { status: 200, headers: {}, body: {} }
|
|
279
|
+
}),
|
|
280
|
+
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
await run({
|
|
284
|
+
adapter,
|
|
285
|
+
descriptor: makeDescriptor({ path: '/users' }),
|
|
286
|
+
basePath: 'https://default.example.com',
|
|
287
|
+
defaults: {
|
|
288
|
+
headers: { 'x-client-version': '1.0' },
|
|
289
|
+
basePath: 'https://region-us.example.com',
|
|
290
|
+
},
|
|
291
|
+
})
|
|
292
|
+
expect(capturedHeaders[0]?.['x-client-version']).toBe('1.0')
|
|
293
|
+
expect(capturedUrls[0]).toBe('https://region-us.example.com/users')
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('onBeforeRequest can still override resolved signal/headers', async () => {
|
|
297
|
+
let observedSignal: AbortSignal | undefined
|
|
298
|
+
const hookController = new AbortController()
|
|
299
|
+
const adapter: ClientAdapter = {
|
|
300
|
+
request: vi.fn(async (req: AdapterRequest): Promise<AdapterResponse> => {
|
|
301
|
+
observedSignal = req.signal
|
|
302
|
+
return { status: 200, headers: {}, body: {} }
|
|
303
|
+
}),
|
|
304
|
+
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const hooks: ClientHooks = {
|
|
308
|
+
onBeforeRequest: (ctx) => ({
|
|
309
|
+
...ctx,
|
|
310
|
+
request: { ...ctx.request, signal: hookController.signal },
|
|
311
|
+
}),
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
await run({ adapter, hooks, options: { timeout: 10_000 } })
|
|
315
|
+
expect(observedSignal).toBe(hookController.signal)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('merges default + per-call meta; per-call keys win', async () => {
|
|
319
|
+
let observedMeta: unknown
|
|
320
|
+
const adapter: ClientAdapter = {
|
|
321
|
+
request: vi.fn(async (req: AdapterRequest): Promise<AdapterResponse> => {
|
|
322
|
+
observedMeta = req.meta
|
|
323
|
+
return { status: 200, headers: {}, body: {} }
|
|
324
|
+
}),
|
|
325
|
+
stream: vi.fn(async () => { throw new Error('not expected') }),
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
await run({
|
|
329
|
+
adapter,
|
|
330
|
+
defaults: { meta: { traceId: 'default-trace', attempt: 1 } as never },
|
|
331
|
+
options: { meta: { traceId: 'override' } as never },
|
|
332
|
+
})
|
|
333
|
+
expect(observedMeta).toEqual({ traceId: 'override', attempt: 1 })
|
|
334
|
+
})
|
|
162
335
|
})
|
package/src/client/call.ts
CHANGED
|
@@ -1,65 +1,87 @@
|
|
|
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
|
import type {
|
|
5
7
|
ClientAdapter,
|
|
6
8
|
ClientHooks,
|
|
7
9
|
CallDescriptor,
|
|
10
|
+
ErrorRegistry,
|
|
11
|
+
ProcedureCallDefaults,
|
|
12
|
+
ProcedureCallOptions,
|
|
8
13
|
} from './types.js'
|
|
9
14
|
|
|
15
|
+
export interface ExecuteCallConfig {
|
|
16
|
+
descriptor: CallDescriptor
|
|
17
|
+
basePath: string
|
|
18
|
+
adapter: ClientAdapter
|
|
19
|
+
hooks: ClientHooks
|
|
20
|
+
defaults?: ProcedureCallDefaults
|
|
21
|
+
options?: ProcedureCallOptions
|
|
22
|
+
errorRegistry?: ErrorRegistry
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
/**
|
|
11
26
|
* Executes a single procedure call through the adapter.
|
|
12
27
|
*
|
|
13
28
|
* Flow:
|
|
14
|
-
* 1.
|
|
15
|
-
* 2.
|
|
16
|
-
* 3.
|
|
17
|
-
* 4.
|
|
18
|
-
* 5.
|
|
19
|
-
* 6.
|
|
20
|
-
* 7.
|
|
29
|
+
* 1. Resolve base path (per-call > defaults > config) and build AdapterRequest
|
|
30
|
+
* 2. Apply request options (headers, signal, timeout, meta) from defaults + per-call
|
|
31
|
+
* 3. Run onBeforeRequest hooks (global then local) — may further mutate request
|
|
32
|
+
* 4. Call adapter.request()
|
|
33
|
+
* 5. On adapter error: run onError hooks, re-throw
|
|
34
|
+
* 6. Run onAfterResponse hooks (may mutate response.status to swallow errors)
|
|
35
|
+
* 7. If response status is non-2xx: throw ClientRequestError
|
|
36
|
+
* 8. Return response.body as TResponse
|
|
21
37
|
*/
|
|
22
|
-
export async function executeCall<TResponse>(
|
|
23
|
-
descriptor
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
|
|
38
|
+
export async function executeCall<TResponse>(config: ExecuteCallConfig): Promise<TResponse> {
|
|
39
|
+
const { descriptor, basePath, adapter, hooks, defaults, options, errorRegistry } = config
|
|
40
|
+
|
|
41
|
+
// 1. Build the initial request (path/query/body from descriptor)
|
|
42
|
+
const resolvedBasePath = resolveBasePath(defaults, options, basePath)
|
|
43
|
+
let request = buildAdapterRequest(descriptor, resolvedBasePath)
|
|
44
|
+
|
|
45
|
+
// 2. Apply request-level options (headers, signal, timeout, meta)
|
|
46
|
+
request = applyRequestOptions(request, defaults, options)
|
|
31
47
|
|
|
32
|
-
//
|
|
48
|
+
// 3. Run before-request hooks — they may further mutate the request
|
|
33
49
|
const beforeCtx = await runBeforeRequest(
|
|
34
50
|
{ procedureName: descriptor.name, scope: descriptor.scope, request },
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
hooks,
|
|
52
|
+
options,
|
|
37
53
|
)
|
|
38
54
|
request = beforeCtx.request
|
|
39
55
|
|
|
40
|
-
//
|
|
56
|
+
// 4. Call the adapter
|
|
41
57
|
let response
|
|
42
58
|
try {
|
|
43
59
|
response = await adapter.request(request)
|
|
44
60
|
} catch (err) {
|
|
45
|
-
//
|
|
61
|
+
// 5. On adapter error: run error hooks, re-throw
|
|
46
62
|
await runOnError(
|
|
47
63
|
{ procedureName: descriptor.name, scope: descriptor.scope, request, error: err },
|
|
48
|
-
|
|
49
|
-
|
|
64
|
+
hooks,
|
|
65
|
+
options,
|
|
50
66
|
)
|
|
51
67
|
throw err
|
|
52
68
|
}
|
|
53
69
|
|
|
54
|
-
//
|
|
70
|
+
// 6. Run after-response hooks — they may mutate response.status to swallow errors
|
|
55
71
|
await runAfterResponse(
|
|
56
72
|
{ procedureName: descriptor.name, scope: descriptor.scope, request, response },
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
hooks,
|
|
74
|
+
options,
|
|
59
75
|
)
|
|
60
76
|
|
|
61
|
-
//
|
|
77
|
+
// 7. Check status AFTER hooks (hooks may have swallowed the error by mutating status)
|
|
62
78
|
if (response.status < 200 || response.status >= 300) {
|
|
79
|
+
const typed = dispatchTypedError(errorRegistry, response.body, {
|
|
80
|
+
status: response.status,
|
|
81
|
+
procedureName: descriptor.name,
|
|
82
|
+
scope: descriptor.scope,
|
|
83
|
+
})
|
|
84
|
+
if (typed) throw typed
|
|
63
85
|
throw new ClientRequestError({
|
|
64
86
|
status: response.status,
|
|
65
87
|
headers: response.headers,
|
|
@@ -69,6 +91,6 @@ export async function executeCall<TResponse>(
|
|
|
69
91
|
})
|
|
70
92
|
}
|
|
71
93
|
|
|
72
|
-
//
|
|
94
|
+
// 8. Return the body
|
|
73
95
|
return response.body as TResponse
|
|
74
96
|
}
|