stitchkit 0.46.0 → 0.48.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 +13 -3
- package/dist/browser/cancellation.d.ts +14 -0
- package/dist/browser/cancellation.d.ts.map +1 -0
- package/dist/browser/client-multipart.d.ts +3 -1
- package/dist/browser/client-multipart.d.ts.map +1 -1
- package/dist/browser/client.d.ts +1 -0
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +1 -0
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/contract/define.d.ts +67 -15
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/index.d.ts +1 -1
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/{index-zwqty9zf.js → index-44xysy8r.js} +465 -49
- package/dist/{index-pwyedf7b.js → index-45dz4m51.js} +46 -0
- package/dist/{index-c40tkxcd.js → index-8ekq6res.js} +1 -1
- package/dist/{index-5s8b7z6q.js → index-ee621cmy.js} +9 -9
- package/dist/{index-62pqb23z.js → index-kp8xamqp.js} +1 -1
- package/dist/{index-w1s873ng.js → index-nrytvb30.js} +4 -3
- package/dist/index.js +195 -94
- package/dist/node.js +2 -2
- package/dist/observability/audit.d.ts +22 -3
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +82 -15
- package/dist/server/context.d.ts +8 -5
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/implement.d.ts +33 -2
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +28 -6
- package/dist/server/middleware/auth.d.ts +7 -4
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/multipart.d.ts +13 -18
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/types.d.ts +48 -15
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools.js +171 -74
- package/llms-full.txt +411 -67
- package/package.json +1 -1
package/llms-full.txt
CHANGED
|
@@ -253,7 +253,7 @@ export const users = defineContract({ prefix: 'users' }, {
|
|
|
253
253
|
| `scope` | no | access scope for this endpoint — see [Auth & errors](./auth-and-errors.md) |
|
|
254
254
|
| `expose` | no | which transports carry this endpoint — see [below](#transports) |
|
|
255
255
|
| `toolName` | no | explicit MCP / agent tool name (default: a verb-aware derivation, see below — not a literal `prefix_key`) |
|
|
256
|
-
| `multipart` | no |
|
|
256
|
+
| `multipart` | no | typed file fields, cardinality, delivery and upload policy — see [below](#file-uploads) |
|
|
257
257
|
| `maxJsonBodyBytes` | no | per-route JSON body ceiling; overrides the server default |
|
|
258
258
|
| `timeout` | no | per-endpoint client timeout in ms, for slow endpoints |
|
|
259
259
|
| `idempotent` | no | safe to call twice with the same input (like `PUT`/`DELETE`); a retrying transport reads it — see [Realtime](./realtime.md#bring-your-own-transport) |
|
|
@@ -474,21 +474,61 @@ it to curate a public spec (e.g. `meta: { public: true }`), without ever emittin
|
|
|
474
474
|
|
|
475
475
|
## File uploads
|
|
476
476
|
|
|
477
|
-
`multipart`
|
|
478
|
-
|
|
477
|
+
`multipart` is the single source of truth for file fields, cardinality and
|
|
478
|
+
transport-level upload policy. Buffered delivery (the default) gives the
|
|
479
|
+
handler a typed `ctx.files` map:
|
|
479
480
|
|
|
480
481
|
```ts
|
|
481
|
-
|
|
482
|
+
uploadAttachments: {
|
|
482
483
|
method: 'POST',
|
|
483
|
-
path: '/
|
|
484
|
-
desc: 'Upload
|
|
485
|
-
|
|
486
|
-
|
|
484
|
+
path: '/:answerId/attachments',
|
|
485
|
+
desc: 'Upload attachments',
|
|
486
|
+
params: AnswerIdParamsSchema,
|
|
487
|
+
input: UploadMetadataSchema,
|
|
488
|
+
output: UploadedAttachmentsSchema,
|
|
489
|
+
multipart: {
|
|
490
|
+
maxRequestBytes: 120 * 1024 * 1024,
|
|
491
|
+
maxFieldBytes: 64 * 1024,
|
|
492
|
+
files: {
|
|
493
|
+
cover: {
|
|
494
|
+
required: false,
|
|
495
|
+
maxBytes: 10 * 1024 * 1024,
|
|
496
|
+
contentTypes: ['image/*'],
|
|
497
|
+
},
|
|
498
|
+
attachments: {
|
|
499
|
+
multiple: true,
|
|
500
|
+
maxFiles: 8,
|
|
501
|
+
maxBytes: 20 * 1024 * 1024,
|
|
502
|
+
contentTypes: ['image/*', 'application/pdf'],
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
},
|
|
487
506
|
}
|
|
488
507
|
```
|
|
489
508
|
|
|
490
|
-
|
|
491
|
-
|
|
509
|
+
```ts
|
|
510
|
+
uploadAttachments: ({ params, input, files }) => {
|
|
511
|
+
files.cover // File | undefined
|
|
512
|
+
files.attachments // File[] in multipart order
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
`required` defaults to `true`; `multiple` defaults to `false`. `maxFiles` is
|
|
517
|
+
valid only for a multiple field. `contentTypes` accepts exact media types and
|
|
518
|
+
validated type wildcards such as `image/*`; it checks the declared multipart
|
|
519
|
+
header, not file contents. Content sniffing, antivirus and storage policy stay
|
|
520
|
+
in the application.
|
|
521
|
+
|
|
522
|
+
The typed client accepts a web `Blob`/`File` or React Native
|
|
523
|
+
`{ uri, name, type }` descriptor for each single value, and arrays for multiple
|
|
524
|
+
fields. It appends repeated multipart field names in stable order. Undeclared
|
|
525
|
+
file fields, duplicate single fields, missing required fields and wrong part
|
|
526
|
+
kinds fail before the handler. Multipart endpoints are HTTP-only.
|
|
527
|
+
|
|
528
|
+
Set `delivery: 'stream'` when files must go directly to consumer-owned storage
|
|
529
|
+
without becoming `File` objects in framework memory. The contract descriptor
|
|
530
|
+
stays the same; the implementation supplies receivers with Web streams. See
|
|
531
|
+
[HTTP server → multipart](./server.md#multipart).
|
|
492
532
|
|
|
493
533
|
### Multipart text fields
|
|
494
534
|
|
|
@@ -622,7 +662,7 @@ Every handler receives one `ctx` argument:
|
|
|
622
662
|
|-------------|------|--------|
|
|
623
663
|
| `params` | inferred from `params` schema | parsed path params |
|
|
624
664
|
| `input` | inferred from `input` schema | parsed body / query |
|
|
625
|
-
| `
|
|
665
|
+
| `files` | inferred `File` map or receiver values | the endpoint's typed multipart fields |
|
|
626
666
|
| `source` | `'http' \| 'mcp' \| 'agent'` | the transport that invoked the handler |
|
|
627
667
|
| `traceId` | `string` | per-request trace id |
|
|
628
668
|
| `ipAddress` | `string` | caller IP |
|
|
@@ -674,7 +714,6 @@ server. See [Testing & deployment](./testing-and-deployment.md).
|
|
|
674
714
|
| `groups` | route groups — a shared path prefix and hooks (see below) |
|
|
675
715
|
| `scopePrefixes` | `scope → path prefix` map — mount `services` by `service.scope` (see below) |
|
|
676
716
|
| `rawRoutes` | non-contract routes (see below) |
|
|
677
|
-
| `maxUploadBytes` | default multipart upload cap (bytes); per-route `EndpointDef.maxUploadBytes` overrides |
|
|
678
717
|
| `maxJsonBodyBytes` | optional JSON body cap (bytes); per-route value overrides; unset preserves existing behaviour |
|
|
679
718
|
| `port` / `hostname` | listen address — port defaults to `3000` |
|
|
680
719
|
| `cors` | CORS policy — `{ origin, credentials, methods, headers, exposeHeaders }`. `origin` is **required** when `cors` is present: pass an explicit origin (or list), or `'*'` to deliberately allow every origin — an origin-less config is a construction error, never a silent wildcard. Omit `cors` entirely to emit no CORS headers. |
|
|
@@ -794,7 +833,7 @@ A group gives a set of services a shared path prefix and its own hooks:
|
|
|
794
833
|
createServer({
|
|
795
834
|
groups: [
|
|
796
835
|
{ pathPrefix: '/api', services: [usersService, postsService] },
|
|
797
|
-
{ pathPrefix: '/api/admin', services: [adminService], hooks: {
|
|
836
|
+
{ pathPrefix: '/api/admin', services: [adminService], hooks: { authorize: adminAuth } },
|
|
798
837
|
],
|
|
799
838
|
})
|
|
800
839
|
```
|
|
@@ -810,7 +849,7 @@ or resource-scoped API:
|
|
|
810
849
|
```ts
|
|
811
850
|
createServer({
|
|
812
851
|
groups: [
|
|
813
|
-
{ pathPrefix: '/tenants/:tenantId', services: [widgetsService], hooks: {
|
|
852
|
+
{ pathPrefix: '/tenants/:tenantId', services: [widgetsService], hooks: { authorize: auth } },
|
|
814
853
|
],
|
|
815
854
|
})
|
|
816
855
|
// widgetsService (prefix 'widgets') → /tenants/:tenantId/widgets/...
|
|
@@ -819,8 +858,8 @@ createServer({
|
|
|
819
858
|
**Where the prefix param lands.** The router matches the *full* path (group
|
|
820
859
|
prefix + service prefix + endpoint path) and collects every `:param` — from the
|
|
821
860
|
prefix and from the endpoint alike — into one set. Each is spread onto the
|
|
822
|
-
context root, so it is available as **`ctx.tenantId`** (a raw `string`) in
|
|
823
|
-
|
|
861
|
+
context root, so it is available as **`ctx.tenantId`** (a raw `string`) in the
|
|
862
|
+
`authorize` hook, handler and later lifecycle hooks:
|
|
824
863
|
|
|
825
864
|
```ts
|
|
826
865
|
beforeHandle: (ctx) => {
|
|
@@ -884,14 +923,17 @@ Scope stays a free string; the core attaches no meaning beyond this lookup
|
|
|
884
923
|
|
|
885
924
|
## Lifecycle hooks
|
|
886
925
|
|
|
887
|
-
|
|
926
|
+
Five hooks wrap every contract request. Route matching and path-param
|
|
927
|
+
validation happen before authorization; body/query parsing happens only after
|
|
928
|
+
authorization succeeds:
|
|
888
929
|
|
|
889
930
|
```ts
|
|
890
931
|
createServer({
|
|
891
932
|
services,
|
|
892
933
|
hooks: {
|
|
893
934
|
onRequest(req) { /* logging, global rate limit — may return a Response to short-circuit */ },
|
|
894
|
-
|
|
935
|
+
authorize(ctx, endpoint) { /* identity + scope, before body reads — throw to reject */ },
|
|
936
|
+
beforeHandle(ctx, endpoint) { /* validated-input preconditions */ },
|
|
895
937
|
afterHandle(ctx, result, ep) { /* transform the result data */ },
|
|
896
938
|
onError(ctx, error, ep) { /* custom error response — return a Response */ },
|
|
897
939
|
},
|
|
@@ -900,9 +942,13 @@ createServer({
|
|
|
900
942
|
|
|
901
943
|
- **`onRequest`** — runs first, with the raw `Request`. Return a `Response` to
|
|
902
944
|
short-circuit (a rate-limit 429, a redirect); return nothing to continue.
|
|
903
|
-
- **`
|
|
904
|
-
|
|
905
|
-
|
|
945
|
+
- **`authorize`** — runs after route matching and validated path params, but
|
|
946
|
+
before query, JSON or multipart parsing. It receives request metadata and
|
|
947
|
+
params, with `input: undefined` and no files. This is the HTTP home of
|
|
948
|
+
[`createAuthHook`](./auth-and-errors.md#createauthhook).
|
|
949
|
+
- **`beforeHandle`** — runs after the complete context has been parsed and
|
|
950
|
+
validated, immediately before the handler. Put input-dependent application
|
|
951
|
+
preconditions here, not authentication.
|
|
906
952
|
- **`afterHandle`** — receives the handler result; return a replacement to
|
|
907
953
|
transform it.
|
|
908
954
|
- **`onError`** — receives any thrown error; return a `Response` to customise
|
|
@@ -1205,7 +1251,7 @@ focused helper — not a sub-framework.
|
|
|
1205
1251
|
|--------|------|
|
|
1206
1252
|
| `serveFile()` | serve a file with `Range` / `304` / `HEAD` (media seeking) |
|
|
1207
1253
|
| `streamSSE()` | turn an `AsyncGenerator` into a Server-Sent-Events `Response` |
|
|
1208
|
-
| `parseMultipart()` | parse a
|
|
1254
|
+
| `parseMultipart()` | parse a typed buffered/streaming multipart descriptor |
|
|
1209
1255
|
| `createRateLimiter()` | per-key token-bucket rate limiting |
|
|
1210
1256
|
| `createCache()` + `cacheHeaders()` | in-memory TTL cache; `Cache-Control` builder |
|
|
1211
1257
|
| `createEventBus<EventMap>()` | typed in-process pub/sub |
|
|
@@ -1232,29 +1278,85 @@ The client side is [`parseSSE`](./client.md#sse).
|
|
|
1232
1278
|
|
|
1233
1279
|
### Multipart
|
|
1234
1280
|
|
|
1235
|
-
|
|
1236
|
-
import { parseMultipart } from 'stitchkit/server'
|
|
1281
|
+
The contract owns one descriptor for buffered and streaming delivery:
|
|
1237
1282
|
|
|
1238
|
-
|
|
1283
|
+
```ts
|
|
1284
|
+
const uploads = defineContract({ prefix: 'uploads' }, {
|
|
1285
|
+
create: {
|
|
1286
|
+
method: 'POST', path: '/', desc: 'Upload media',
|
|
1287
|
+
input: UploadMetadataSchema,
|
|
1288
|
+
output: UploadedMediaSchema,
|
|
1289
|
+
multipart: {
|
|
1290
|
+
maxRequestBytes: 220 * 1024 * 1024,
|
|
1291
|
+
maxFieldBytes: 64 * 1024,
|
|
1292
|
+
files: {
|
|
1293
|
+
cover: { required: false, maxBytes: 10 * 1024 * 1024, contentTypes: ['image/*'] },
|
|
1294
|
+
media: { multiple: true, maxFiles: 4, maxBytes: 50 * 1024 * 1024 },
|
|
1295
|
+
},
|
|
1296
|
+
},
|
|
1297
|
+
},
|
|
1298
|
+
})
|
|
1239
1299
|
```
|
|
1240
1300
|
|
|
1241
|
-
|
|
1242
|
-
and
|
|
1243
|
-
|
|
1301
|
+
Buffered delivery is the default. Each file becomes a Web `File`; single,
|
|
1302
|
+
optional and multiple cardinality is inferred on `ctx.files`. The request cap
|
|
1303
|
+
defaults to **25 MB** when omitted. The parser measures actual bytes including
|
|
1304
|
+
boundaries and headers instead of trusting `Content-Length`; per-file bytes,
|
|
1305
|
+
file count, text-field bytes and declared MIME policy are enforced while
|
|
1306
|
+
reading. Text fields remain strings until the endpoint's Zod `input` parses
|
|
1307
|
+
them.
|
|
1244
1308
|
|
|
1245
|
-
|
|
1246
|
-
`
|
|
1247
|
-
|
|
1309
|
+
For large files, set `delivery: 'stream'` and define receivers. A receiver gets
|
|
1310
|
+
a Web `ReadableStream<Uint8Array>` and writes directly to consumer-owned
|
|
1311
|
+
storage; Stitchkit holds only bounded parser state:
|
|
1248
1312
|
|
|
1249
1313
|
```ts
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1314
|
+
const streamingUploads = defineContract({ prefix: 'uploads' }, {
|
|
1315
|
+
create: {
|
|
1316
|
+
method: 'POST', path: '/', desc: 'Upload media',
|
|
1317
|
+
input: UploadMetadataSchema,
|
|
1318
|
+
output: UploadedMediaSchema,
|
|
1319
|
+
multipart: {
|
|
1320
|
+
delivery: 'stream',
|
|
1321
|
+
maxRequestBytes: 260 * 1024 * 1024,
|
|
1322
|
+
files: { media: { maxBytes: 250 * 1024 * 1024, contentTypes: ['video/*'] } },
|
|
1323
|
+
},
|
|
1324
|
+
},
|
|
1325
|
+
})
|
|
1253
1326
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1327
|
+
const service = implement(streamingUploads, {
|
|
1328
|
+
create: defineMultipartStream(streamingUploads.endpoints.create, {
|
|
1329
|
+
files: {
|
|
1330
|
+
media: async ({ metadata, stream, signal }) => {
|
|
1331
|
+
const stored = await storage.write({ metadata, stream, signal })
|
|
1332
|
+
return {
|
|
1333
|
+
value: stored,
|
|
1334
|
+
cleanup: () => storage.remove(stored.key),
|
|
1335
|
+
}
|
|
1336
|
+
},
|
|
1337
|
+
},
|
|
1338
|
+
handler: ({ input, files }) => mediaService.attach(input, files.media),
|
|
1339
|
+
}),
|
|
1340
|
+
})
|
|
1256
1341
|
```
|
|
1257
1342
|
|
|
1343
|
+
Receivers run sequentially in multipart order. A multiple receiver runs once
|
|
1344
|
+
per part and the handler gets an ordered value array. `cleanup` is registered
|
|
1345
|
+
as soon as a receiver materialises external state. Disconnect, size/policy
|
|
1346
|
+
failure, a later receiver/text validation failure or handler failure rolls
|
|
1347
|
+
accepted handles back exactly once in reverse order. After handler success,
|
|
1348
|
+
ownership transfers to the application.
|
|
1349
|
+
|
|
1350
|
+
Authorization always completes before the multipart parser or any receiver is
|
|
1351
|
+
started. The receiver signal is tied to the request abort. The core does not
|
|
1352
|
+
provide filesystem/S3 adapters, retries, antivirus or a distributed
|
|
1353
|
+
storage/database transaction; those policies belong to the application.
|
|
1354
|
+
|
|
1355
|
+
`parseMultipart(req, descriptor, fieldsSchema?, receivers?)` is also exported
|
|
1356
|
+
for a custom raw transport. It uses the same descriptor and returns
|
|
1357
|
+
`{ files, fields, rollback }`; contract endpoints should prefer the automatic
|
|
1358
|
+
dispatcher path.
|
|
1359
|
+
|
|
1258
1360
|
### Rate limiting
|
|
1259
1361
|
|
|
1260
1362
|
```ts
|
|
@@ -1450,10 +1552,50 @@ contract:
|
|
|
1450
1552
|
- for `GET` / `DELETE`, the remaining fields become the **query string**
|
|
1451
1553
|
(arrays become repeated keys),
|
|
1452
1554
|
- for `POST` / `PUT` / `PATCH`, they become the **JSON body**,
|
|
1453
|
-
- a `multipart` field is a `Blob` (web / Bun) or
|
|
1454
|
-
(`{ uri, name, type }`,
|
|
1455
|
-
|
|
1456
|
-
|
|
1555
|
+
- a single `multipart.files` field is a `Blob` (web / Bun) or platform
|
|
1556
|
+
`FileDescriptor` (`{ uri, name, type }`, React Native / Expo); a multiple
|
|
1557
|
+
field is an array and is appended under the same form field name in order.
|
|
1558
|
+
The exported `MultipartFile` / `FileDescriptor` types let you annotate your
|
|
1559
|
+
own upload helpers.
|
|
1560
|
+
|
|
1561
|
+
### Per-call cancellation
|
|
1562
|
+
|
|
1563
|
+
Every endpoint callable exposes a `withOptions` method accepting required
|
|
1564
|
+
`ClientRequestOptions`. The ordinary callable contains only contract arguments,
|
|
1565
|
+
so it can be passed directly to callback APIs such as `react-query-kit` without
|
|
1566
|
+
mistaking their callback context for Stitchkit transport options:
|
|
1567
|
+
|
|
1568
|
+
```ts
|
|
1569
|
+
const controller = new AbortController()
|
|
1570
|
+
|
|
1571
|
+
const pending = api.upload.withOptions(
|
|
1572
|
+
{ file: selectedFile, title: 'Draft' },
|
|
1573
|
+
{ signal: controller.signal },
|
|
1574
|
+
)
|
|
1575
|
+
|
|
1576
|
+
controller.abort()
|
|
1577
|
+
await pending
|
|
1578
|
+
```
|
|
1579
|
+
|
|
1580
|
+
Caller cancellation and the endpoint/client timeout are composed; whichever
|
|
1581
|
+
fires first owns the result. Both the bare-fetch and Ky-backed clients expose
|
|
1582
|
+
the same client-only errors:
|
|
1583
|
+
|
|
1584
|
+
| Failure | `ApiError.code` | `status` |
|
|
1585
|
+
|---------|-----------------|----------|
|
|
1586
|
+
| caller `AbortSignal` | `REQUEST_ABORTED` | `0` |
|
|
1587
|
+
| endpoint/client timeout | `REQUEST_TIMEOUT` | `0` |
|
|
1588
|
+
| other transport failure | `UNKNOWN_ERROR` | `0` |
|
|
1589
|
+
|
|
1590
|
+
Abort and timeout do not emit `network_error` and are not retried. The same
|
|
1591
|
+
options work for query, JSON, multipart and raw-response calls. Stitchkit does
|
|
1592
|
+
not expose upload progress: Fetch has no portable upload-progress primitive.
|
|
1593
|
+
|
|
1594
|
+
For an endpoint without contract arguments, pass only the options object:
|
|
1595
|
+
|
|
1596
|
+
```ts
|
|
1597
|
+
await api.health.withOptions({ signal: controller.signal })
|
|
1598
|
+
```
|
|
1457
1599
|
|
|
1458
1600
|
### Many contracts at once
|
|
1459
1601
|
|
|
@@ -1688,6 +1830,10 @@ export const useUsers = createQuery({ queryKey: ['users'], fetcher: () => a
|
|
|
1688
1830
|
export const useCreateUser = createMutation({ mutationFn: api.create })
|
|
1689
1831
|
```
|
|
1690
1832
|
|
|
1833
|
+
Generated methods intentionally keep their ordinary call signature limited to
|
|
1834
|
+
contract variables. Use `api.create.withOptions(variables, { signal })` only for
|
|
1835
|
+
an imperative call that needs per-request cancellation.
|
|
1836
|
+
|
|
1691
1837
|
### Cursor pagination
|
|
1692
1838
|
|
|
1693
1839
|
For a cursor-paginated list, `createCursorQuery` is the canonical helper:
|
|
@@ -2030,15 +2176,15 @@ handlers must still be idempotent where retries matter.
|
|
|
2030
2176
|
|
|
2031
2177
|
A tool call runs the same handler an HTTP request would. `lifecycle` makes it
|
|
2032
2178
|
run the same gate: a `beforeHandle` (throw to reject) and an `afterHandle`
|
|
2033
|
-
(transform the result)
|
|
2034
|
-
|
|
2035
|
-
|
|
2179
|
+
(transform the result). Pass the **same**
|
|
2180
|
+
[`createAuthHook`](./auth-and-errors.md#createauthhook) result used as the HTTP
|
|
2181
|
+
server's `authorize` hook and tool calls are scope-checked by identical rules:
|
|
2036
2182
|
|
|
2037
2183
|
```ts
|
|
2038
2184
|
createMcpHandler({ serverInfo, auth, services, lifecycle: { beforeHandle: authHook } })
|
|
2039
2185
|
```
|
|
2040
2186
|
|
|
2041
|
-
Without it, a tool call bypasses the HTTP `
|
|
2187
|
+
Without it, a tool call bypasses the HTTP `authorize` gate — the contract's
|
|
2042
2188
|
`scope` is not enforced on the MCP / agent surface. `mountMcp`, `mountAgent` and
|
|
2043
2189
|
`buildMcpServer` take `lifecycle` too.
|
|
2044
2190
|
|
|
@@ -2400,7 +2546,7 @@ the merged `params` + `input`. `context` is merged into every tool handler's
|
|
|
2400
2546
|
| `runtimeTools` | framework-managed pathless operations from `defineRuntimeTool` |
|
|
2401
2547
|
|
|
2402
2548
|
`lifecycle` works the same as on the MCP server — without it an agent tool call
|
|
2403
|
-
bypasses the HTTP `
|
|
2549
|
+
bypasses the HTTP `authorize` gate. Pass your `createAuthHook` result.
|
|
2404
2550
|
|
|
2405
2551
|
### Adding tool-only args — `extend`
|
|
2406
2552
|
|
|
@@ -2795,7 +2941,7 @@ await createCli({
|
|
|
2795
2941
|
version: '1.0.0',
|
|
2796
2942
|
auth: await resolveIdentityFromToken(process.env.MYAPP_TOKEN),
|
|
2797
2943
|
context: (identity) => ({ user: identity }), // resolveFromContext reads this
|
|
2798
|
-
lifecycle: { beforeHandle: authHook }, // same
|
|
2944
|
+
lifecycle: { beforeHandle: authHook }, // same policy; HTTP wires it as authorize
|
|
2799
2945
|
services,
|
|
2800
2946
|
})
|
|
2801
2947
|
```
|
|
@@ -3348,10 +3494,12 @@ The framework attaches no meaning to the strings — `'public'`, `'user'`,
|
|
|
3348
3494
|
|
|
3349
3495
|
## `createAuthHook`
|
|
3350
3496
|
|
|
3351
|
-
`createAuthHook` builds
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3497
|
+
`createAuthHook` builds one scope gate that can run in both HTTP and tool
|
|
3498
|
+
lifecycles. On HTTP it belongs in `hooks.authorize`: Stitchkit validates path
|
|
3499
|
+
params, resolves identity and scope, and rejects `401`/`403` **before reading a
|
|
3500
|
+
JSON or multipart body**. On MCP, agent and CLI surfaces it belongs in
|
|
3501
|
+
`lifecycle.beforeHandle`, because those transports have already received their
|
|
3502
|
+
arguments before the common tool runner starts.
|
|
3355
3503
|
|
|
3356
3504
|
```ts
|
|
3357
3505
|
import { createAuthHook, createServer } from 'stitchkit/server'
|
|
@@ -3366,7 +3514,7 @@ const authHook = createAuthHook<User>({
|
|
|
3366
3514
|
inject: (ctx, user) => { ctx.user = user },
|
|
3367
3515
|
})
|
|
3368
3516
|
|
|
3369
|
-
createServer({ services, hooks: {
|
|
3517
|
+
createServer({ services, hooks: { authorize: authHook } })
|
|
3370
3518
|
```
|
|
3371
3519
|
|
|
3372
3520
|
### `AuthRule`
|
|
@@ -3376,8 +3524,9 @@ The value of each `rules` entry, keyed by scope:
|
|
|
3376
3524
|
- **`'public'`** — always passes; the identity is attached if present.
|
|
3377
3525
|
- **`'authenticated'`** — any resolved identity passes; no identity ⇒ 401.
|
|
3378
3526
|
- **a function** `(identity, ctx) => boolean | Promise<boolean>` — a custom
|
|
3379
|
-
check. It receives
|
|
3380
|
-
|
|
3527
|
+
check. It receives request metadata and validated path params, so a
|
|
3528
|
+
resource-scoped rule can do a DB lookup. It cannot read `input` or files: the
|
|
3529
|
+
body has deliberately not been consumed yet. May be async.
|
|
3381
3530
|
|
|
3382
3531
|
#### Resource-scoped rule — reading a path/prefix param
|
|
3383
3532
|
|
|
@@ -3424,14 +3573,15 @@ catches a scope you forgot to cover.
|
|
|
3424
3573
|
|
|
3425
3574
|
### Auth on the tool surface — `resolveFromContext`
|
|
3426
3575
|
|
|
3427
|
-
The hook
|
|
3428
|
-
|
|
3429
|
-
surface:
|
|
3576
|
+
The same hook guards every transport, but the lifecycle slot and identity
|
|
3577
|
+
source differ:
|
|
3430
3578
|
|
|
3431
|
-
- **HTTP** — `resolve(ctx)`
|
|
3579
|
+
- **HTTP** — `hooks.authorize` calls `resolve(ctx)` from `ctx.req` (a cookie or
|
|
3580
|
+
bearer token) before body parsing.
|
|
3432
3581
|
- **Tool calls (MCP / agent)** — there is no `req`. The transport authenticated
|
|
3433
3582
|
the caller (an MCP API key) and `buildMcpServer`'s `context` injected the
|
|
3434
|
-
identity into `ctx`. `
|
|
3583
|
+
identity into `ctx`. `lifecycle.beforeHandle` calls
|
|
3584
|
+
`resolveFromContext(ctx)` to locate it.
|
|
3435
3585
|
|
|
3436
3586
|
```ts
|
|
3437
3587
|
const authHook = createAuthHook<User>({
|
|
@@ -3446,6 +3596,11 @@ differs. If you omit `resolveFromContext`, a scoped tool call has no identity
|
|
|
3446
3596
|
and **fails closed** (rejected by `onAnonymous`) — the hook never silently
|
|
3447
3597
|
passes a tool call it cannot authenticate. → [ADR 0014](../decisions/0014-tool-http-parity.md)
|
|
3448
3598
|
|
|
3599
|
+
```ts
|
|
3600
|
+
createServer({ services, hooks: { authorize: authHook } })
|
|
3601
|
+
createMcpHandler({ services, lifecycle: { beforeHandle: authHook } })
|
|
3602
|
+
```
|
|
3603
|
+
|
|
3449
3604
|
## `createBearerResolver`
|
|
3450
3605
|
|
|
3451
3606
|
For API-key or bearer-token auth (the usual MCP case), `createBearerResolver`
|
|
@@ -3806,6 +3961,66 @@ Each sink runs fire-and-forget and fails independently: a slow or broken request
|
|
|
3806
3961
|
sink cannot block the response, suppress operational logging or break the tool
|
|
3807
3962
|
sink.
|
|
3808
3963
|
|
|
3964
|
+
The fire-and-forget work has an explicit bounded lifecycle:
|
|
3965
|
+
|
|
3966
|
+
```ts
|
|
3967
|
+
export const observability = createObservability({
|
|
3968
|
+
request: {
|
|
3969
|
+
maxPending: 1_000,
|
|
3970
|
+
write: persistRequestEvent,
|
|
3971
|
+
onSinkError: ({ error, event }) => reportAuditFailure(error, event),
|
|
3972
|
+
onDrop: ({ reason, event, pending }) => {
|
|
3973
|
+
reportAuditDrop({ reason, traceId: event.traceId, pending })
|
|
3974
|
+
},
|
|
3975
|
+
},
|
|
3976
|
+
tools: {
|
|
3977
|
+
maxPending: 500,
|
|
3978
|
+
write: persistToolEvent,
|
|
3979
|
+
onSinkError: ({ error, event }) => reportToolAuditFailure(error, event),
|
|
3980
|
+
onDrop: reportToolAuditDrop,
|
|
3981
|
+
},
|
|
3982
|
+
})
|
|
3983
|
+
|
|
3984
|
+
await observability.flush()
|
|
3985
|
+
await observability.close()
|
|
3986
|
+
```
|
|
3987
|
+
|
|
3988
|
+
`maxPending` defaults to `1000` per sink and must be a positive safe integer.
|
|
3989
|
+
Filtering and sanitisation happen before a write occupies a pending slot. At
|
|
3990
|
+
capacity, a new event is rejected with `onDrop({ reason: 'capacity', ... })`;
|
|
3991
|
+
after close, admission reports `reason: 'closed'`. Existing writes are never
|
|
3992
|
+
cancelled. Sink, filter and diagnostic-callback failures remain isolated from
|
|
3993
|
+
the observed request or tool call and are reported through `onSinkError` when
|
|
3994
|
+
configured; `onSinkError`/`onDrop` cannot create unhandled rejections.
|
|
3995
|
+
|
|
3996
|
+
`flush()` snapshots the current generation and waits only for events admitted
|
|
3997
|
+
up to that call. `close()` atomically stops admission, drains every accepted
|
|
3998
|
+
generation and is idempotent. Graceful shutdown order is therefore:
|
|
3999
|
+
|
|
4000
|
+
1. stop HTTP/MCP admission;
|
|
4001
|
+
2. wait for active requests and tool calls;
|
|
4002
|
+
3. `await observability.close()`;
|
|
4003
|
+
4. close the database/storage connection used by the sinks.
|
|
4004
|
+
|
|
4005
|
+
Stitchkit manages only in-process delivery. If process-crash durability matters,
|
|
4006
|
+
make `write` enqueue into a consumer-owned durable outbox and let that adapter
|
|
4007
|
+
own retry, replay and storage policy:
|
|
4008
|
+
|
|
4009
|
+
```ts
|
|
4010
|
+
interface AuditOutbox {
|
|
4011
|
+
enqueue(event: RequestEvent): Promise<void>
|
|
4012
|
+
}
|
|
4013
|
+
|
|
4014
|
+
const observability = createObservability({
|
|
4015
|
+
request: { write: (event) => outbox.enqueue(event) },
|
|
4016
|
+
tools: { write: (event) => outbox.enqueue(event) },
|
|
4017
|
+
})
|
|
4018
|
+
```
|
|
4019
|
+
|
|
4020
|
+
The core intentionally contains no retry scheduler, database dependency or
|
|
4021
|
+
disk queue. A durable adapter can use `(traceId, spanId, source, toolPhase)` as
|
|
4022
|
+
its idempotency identity.
|
|
4023
|
+
|
|
3809
4024
|
### RequestEvent
|
|
3810
4025
|
|
|
3811
4026
|
Every surface produces the same shape — so a single audit table stays
|
|
@@ -4337,8 +4552,9 @@ Notes for a Node host:
|
|
|
4337
4552
|
to join the two logs.
|
|
4338
4553
|
- **Rate limiting** — `createRateLimiter` in `onRequest` for a global limit;
|
|
4339
4554
|
per-route limits belong in `beforeHandle`.
|
|
4340
|
-
- **Auth** —
|
|
4341
|
-
do not re-check auth per handler.
|
|
4555
|
+
- **Auth** — wire one `createAuthHook` as HTTP `hooks.authorize` and tool
|
|
4556
|
+
`lifecycle.beforeHandle`; do not re-check auth per handler. HTTP authorization
|
|
4557
|
+
runs before JSON or multipart body reads.
|
|
4342
4558
|
- **Errors** — handlers throw `AppError`; let the standard envelope render them.
|
|
4343
4559
|
Add an `onError` hook only to integrate an error tracker.
|
|
4344
4560
|
- **Secrets** — read them from the environment; never commit them.
|
|
@@ -4397,7 +4613,7 @@ matched value is on the context root as `ctx.tenantId`
|
|
|
4397
4613
|
```ts
|
|
4398
4614
|
createServer({
|
|
4399
4615
|
groups: [
|
|
4400
|
-
{ pathPrefix: '/tenants/:tenantId', services: [widgetsService], hooks: {
|
|
4616
|
+
{ pathPrefix: '/tenants/:tenantId', services: [widgetsService], hooks: { authorize: authHook } },
|
|
4401
4617
|
],
|
|
4402
4618
|
})
|
|
4403
4619
|
// → /tenants/:tenantId/widgets
|
|
@@ -4411,7 +4627,7 @@ the flat `services` list — no hand-partitioning, the mapping lives in one plac
|
|
|
4411
4627
|
createServer({
|
|
4412
4628
|
services, // mixed scopes, listed once
|
|
4413
4629
|
scopePrefixes: { tenant: 'tenants/:tenantId', project: 'projects/:projectId' },
|
|
4414
|
-
hooks: {
|
|
4630
|
+
hooks: { authorize: authHook },
|
|
4415
4631
|
})
|
|
4416
4632
|
// `tenant`-scoped → /tenants/:tenantId/..., `project` → /projects/:projectId/..., the rest flat
|
|
4417
4633
|
```
|
|
@@ -4612,6 +4828,119 @@ current one *up to* your target, and apply each snippet.
|
|
|
4612
4828
|
runtime): bootstrap the server, one HTTP request, and any feature you rely on
|
|
4613
4829
|
(Socket.IO connect, an MCP tool call, a multipart upload, …).
|
|
4614
4830
|
|
|
4831
|
+
## Released migration: 0.48.0
|
|
4832
|
+
|
|
4833
|
+
### Typed-client request options move to `.withOptions`
|
|
4834
|
+
|
|
4835
|
+
Generated endpoint methods reserve their ordinary call signature for contract
|
|
4836
|
+
variables. This keeps them directly assignable to callback APIs whose runtime
|
|
4837
|
+
supplies its own second context argument, including `react-query-kit` and
|
|
4838
|
+
TanStack Query. Move imperative cancellation to the callable's explicit method:
|
|
4839
|
+
|
|
4840
|
+
```ts
|
|
4841
|
+
// before — endpoint with arguments
|
|
4842
|
+
await api.create({ name: 'Max' }, { signal })
|
|
4843
|
+
|
|
4844
|
+
// after
|
|
4845
|
+
await api.create.withOptions({ name: 'Max' }, { signal })
|
|
4846
|
+
|
|
4847
|
+
// before — endpoint without arguments
|
|
4848
|
+
await api.health({ signal })
|
|
4849
|
+
|
|
4850
|
+
// after
|
|
4851
|
+
await api.health.withOptions({ signal })
|
|
4852
|
+
```
|
|
4853
|
+
|
|
4854
|
+
Direct query and mutation composition remains unchanged:
|
|
4855
|
+
|
|
4856
|
+
```ts
|
|
4857
|
+
createMutation({ mutationFn: api.create })
|
|
4858
|
+
createQuery({ queryKey: ['search'], fetcher: api.search })
|
|
4859
|
+
```
|
|
4860
|
+
|
|
4861
|
+
There is no positional-options alias. Ordinary generated methods ignore extra
|
|
4862
|
+
runtime callback arguments; only `.withOptions` reads `ClientRequestOptions`.
|
|
4863
|
+
|
|
4864
|
+
## Released migration: 0.47.0
|
|
4865
|
+
|
|
4866
|
+
### HTTP auth moves to the pre-body `authorize` phase
|
|
4867
|
+
|
|
4868
|
+
Move the HTTP wiring of `createAuthHook` from `beforeHandle` to `authorize`.
|
|
4869
|
+
This lets Stitchkit reject an unauthorized JSON or multipart request after path
|
|
4870
|
+
parameter validation but before reading a body chunk. Keep application
|
|
4871
|
+
preconditions that depend on validated input in `beforeHandle`.
|
|
4872
|
+
|
|
4873
|
+
```ts
|
|
4874
|
+
const auth = createAuthHook({ authenticate, authorize })
|
|
4875
|
+
|
|
4876
|
+
// before
|
|
4877
|
+
createServer({ services, hooks: { beforeHandle: auth } })
|
|
4878
|
+
|
|
4879
|
+
// after
|
|
4880
|
+
createServer({ services, hooks: { authorize: auth } })
|
|
4881
|
+
```
|
|
4882
|
+
|
|
4883
|
+
Tool transports already receive parsed input, so their wiring does not move:
|
|
4884
|
+
|
|
4885
|
+
```ts
|
|
4886
|
+
createMcpHandler({ services, lifecycle: { beforeHandle: auth } })
|
|
4887
|
+
```
|
|
4888
|
+
|
|
4889
|
+
If a custom HTTP authorization hook read `ctx.input`, `ctx.files` or raw body
|
|
4890
|
+
state, split it: identity/scope checks belong in `authorize`; validated payload
|
|
4891
|
+
preconditions belong in `beforeHandle` or the domain service.
|
|
4892
|
+
|
|
4893
|
+
### Multipart uses a typed descriptor and `ctx.files`
|
|
4894
|
+
|
|
4895
|
+
Replace every string multipart declaration, top-level `maxUploadBytes` and
|
|
4896
|
+
`ctx.file`. The descriptor is now the only source of request, per-file,
|
|
4897
|
+
cardinality and declared media-type policy.
|
|
4898
|
+
|
|
4899
|
+
```ts
|
|
4900
|
+
// before
|
|
4901
|
+
upload: {
|
|
4902
|
+
method: 'POST',
|
|
4903
|
+
path: '/',
|
|
4904
|
+
multipart: 'file',
|
|
4905
|
+
maxUploadBytes: 25 * 1024 * 1024,
|
|
4906
|
+
}
|
|
4907
|
+
upload: ({ file, input }) => store(file, input)
|
|
4908
|
+
|
|
4909
|
+
// after
|
|
4910
|
+
upload: {
|
|
4911
|
+
method: 'POST',
|
|
4912
|
+
path: '/',
|
|
4913
|
+
multipart: {
|
|
4914
|
+
maxRequestBytes: 25 * 1024 * 1024,
|
|
4915
|
+
files: {
|
|
4916
|
+
file: {
|
|
4917
|
+
maxBytes: 20 * 1024 * 1024,
|
|
4918
|
+
contentTypes: ['image/*', 'application/pdf'],
|
|
4919
|
+
},
|
|
4920
|
+
},
|
|
4921
|
+
},
|
|
4922
|
+
}
|
|
4923
|
+
upload: ({ files, input }) => store(files.file, input)
|
|
4924
|
+
```
|
|
4925
|
+
|
|
4926
|
+
Multiple files are repeated under one multipart field name and arrive in the
|
|
4927
|
+
same order:
|
|
4928
|
+
|
|
4929
|
+
```ts
|
|
4930
|
+
files: {
|
|
4931
|
+
attachments: { multiple: true, maxFiles: 8 },
|
|
4932
|
+
}
|
|
4933
|
+
|
|
4934
|
+
await api.upload({ attachments: [firstFile, secondFile] })
|
|
4935
|
+
// handler: files.attachments is File[]
|
|
4936
|
+
```
|
|
4937
|
+
|
|
4938
|
+
For direct-to-storage delivery, set `delivery: 'stream'` and implement the
|
|
4939
|
+
endpoint with `defineMultipartStream`. A receiver must consume its Web Stream
|
|
4940
|
+
and return `{ value, cleanup }`; the final handler sees only receiver values.
|
|
4941
|
+
There is no deprecated overload or buffered compatibility path under the old
|
|
4942
|
+
contract shape.
|
|
4943
|
+
|
|
4615
4944
|
## Worked example — frozen on 0.3, jumping to 0.7
|
|
4616
4945
|
|
|
4617
4946
|
1. `bun.lock` → consumer resolves `stitchkit@0.3.x`.
|
|
@@ -5299,6 +5628,7 @@ The browser-and-server entrypoint. Re-exports everything from
|
|
|
5299
5628
|
| `createUrlBuilders` | function | build one exact URL builder per contract in a registry |
|
|
5300
5629
|
| `UrlBuilderConfig` | _type_ | explicit `{ baseUrl }` source for a URL builder |
|
|
5301
5630
|
| `ClientConfig` | _type_ | config for `createClient`'s bare-fetch mode (2nd arg, no `HttpClient`) |
|
|
5631
|
+
| `ClientRequestOptions` | _type_ | per-call `{ signal?: AbortSignal }` passed through an endpoint callable's `.withOptions(...)`; caller abort is distinct from timeout — [guide](../guide/client.md#per-call-cancellation) |
|
|
5302
5632
|
| `ContractClientConfig` | _type_ | per-tenant / resource-scoped client config — dynamic `pathPrefix` + `stripPrefixKeys` ([guide](../guide/client.md#contractclientconfig--per-tenant--resource-scoped-clients)) |
|
|
5303
5633
|
| `contractEndpointMatchers` | function | compile exact pathname matchers for selected HTTP contract operations and expected-401 policy |
|
|
5304
5634
|
| `PathPrefixArgs` | _type_ | required string-valued keys exposed to a typed dynamic `pathPrefix` callback |
|
|
@@ -5388,6 +5718,7 @@ from the root `stitchkit`.
|
|
|
5388
5718
|
| `TransportSource` | _type_ | `http \| mcp \| agent \| cli` — the value of `ctx.source` |
|
|
5389
5719
|
| `RuntimeContext` | _type_ | the loose context seen by transport and hooks |
|
|
5390
5720
|
| `HandlerContext` | _type_ | the typed context seen by a handler |
|
|
5721
|
+
| `EndpointHandlerContext` | _type_ | one endpoint handler's fully inferred params, input, files and runtime context |
|
|
5391
5722
|
| `EndpointFn` | _type_ | the call signature of one client method |
|
|
5392
5723
|
| `TypedClient` | _type_ | the full typed client for a contract |
|
|
5393
5724
|
| `TypedHttpClient` | _type_ | the typed client, HTTP endpoints only (`= ScopedHttpClient<C, unknown>`) |
|
|
@@ -5398,6 +5729,9 @@ from the root `stitchkit`.
|
|
|
5398
5729
|
| `ScopedUrlFn` | _type_ | one URL method's signature with scoped-prefix keys folded in |
|
|
5399
5730
|
| `MultipartFile` | _type_ | a `multipart` file field — `Blob \| FileDescriptor` |
|
|
5400
5731
|
| `FileDescriptor` | _type_ | a React Native / Expo file — `{ uri, name, type }` |
|
|
5732
|
+
| `MultipartDescriptor` | _type_ | file fields, cardinality, delivery and request/text limits |
|
|
5733
|
+
| `MultipartFilePolicy` | _type_ | required/multiple, per-file bytes/count and declared MIME policy |
|
|
5734
|
+
| `MultipartBufferedFiles` | _type_ | `File` map inferred from a multipart descriptor |
|
|
5401
5735
|
| `EndpointToolAnnotations` | _type_ | MCP behavioural hints on an endpoint (`readOnlyHint` / `destructiveHint` / `title`) |
|
|
5402
5736
|
| `EndpointUiMeta` | _type_ | MCP Apps widget metadata on an endpoint |
|
|
5403
5737
|
| `EndpointMcpInputRequired` | _type_ | typed MCP multi-round input request (`key`, message and Zod object schema) |
|
|
@@ -5476,7 +5810,8 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
5476
5810
|
| `MethodDef` | _type_ | one resolved endpoint inside a service |
|
|
5477
5811
|
| `OperationIdentity` | _type_ | path-free service/action/scope/method identity shared by contract and native tool operations |
|
|
5478
5812
|
| `Handlers` | _type_ | the typed handler map `implement` expects |
|
|
5479
|
-
| `LifecycleHooks` | _type_ | `onRequest` / `beforeHandle` / `afterHandle` / `onError` |
|
|
5813
|
+
| `LifecycleHooks` | _type_ | `onRequest` / pre-body `authorize` / `beforeHandle` / `afterHandle` / `onError` |
|
|
5814
|
+
| `AuthorizationContext` | _type_ | HTTP pre-body context with validated params, `input: undefined` and no files |
|
|
5480
5815
|
| `RouteGroup` | _type_ | a prefixed group of services with its own hooks |
|
|
5481
5816
|
| `RawRoute` | _type_ | a non-contract `Request → Response` route with a concrete `BunServer` context |
|
|
5482
5817
|
| `RawRouteContext` | _type_ | the Bun-bound routing context a raw handler receives |
|
|
@@ -5493,7 +5828,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
5493
5828
|
|
|
5494
5829
|
| Export | Kind | Summary |
|
|
5495
5830
|
|--------|------|---------|
|
|
5496
|
-
| `createAuthHook` | function |
|
|
5831
|
+
| `createAuthHook` | function | one scope gate for HTTP `authorize` and tool `beforeHandle` — [guide](../guide/auth-and-errors.md#createauthhook) |
|
|
5497
5832
|
| `createErrorHook` | function | an async-capable, endpoint-aware `onError` hook from a code map + envelope renderer — [guide](../guide/auth-and-errors.md#createerrorhook) |
|
|
5498
5833
|
| `ErrorHookConfig` | _type_ | async observer/renderer config for `createErrorHook` |
|
|
5499
5834
|
| `ResolvedError` | _type_ | the normalised error handed to `createErrorHook`'s `render` |
|
|
@@ -5551,8 +5886,14 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
5551
5886
|
|--------|------|---------|
|
|
5552
5887
|
| `streamSSE` | function | an async generator → SSE `Response` — [guide](../guide/server.md#sse-streaming) |
|
|
5553
5888
|
| `parseSSE` | function | parse an SSE `Response` (also on the root entrypoint) |
|
|
5889
|
+
| `MultipartLifecycle` | _type_ | request-scoped rollback ownership for accepted streamed handles |
|
|
5554
5890
|
| `MultipartResult` | _type_ | what `parseMultipart` returns |
|
|
5555
|
-
| `parseMultipart` | function | parse a
|
|
5891
|
+
| `parseMultipart` | function | parse a typed buffered/streaming multipart descriptor — [guide](../guide/server.md#multipart) |
|
|
5892
|
+
| `defineMultipartStream` | function | bind typed streaming file receivers and a final endpoint handler |
|
|
5893
|
+
| `MultipartFileMetadata` | _type_ | field, filename, declared media type and optional declared size |
|
|
5894
|
+
| `MultipartReceiver` | _type_ | consumer-owned Web-stream storage receiver |
|
|
5895
|
+
| `MultipartReceiverResult` | _type_ | receiver value plus rollback cleanup |
|
|
5896
|
+
| `StreamingMultipartImplementation` | _type_ | receiver registry and handler shape inferred by `defineMultipartStream` |
|
|
5556
5897
|
| `createRateLimiter` | function | token-bucket rate limiting — [guide](../guide/server.md#rate-limiting) |
|
|
5557
5898
|
| `createCache` | function | an in-memory TTL cache |
|
|
5558
5899
|
| `CacheOptions` | _type_ | bounded-cache options, including the maximum retained entry count |
|
|
@@ -5597,9 +5938,12 @@ audit event. See the [Observability guide](../guide/observability.md).
|
|
|
5597
5938
|
| `createObservability` | function | configure framework-owned request completion and canonical tool event sinks — [guide](../guide/observability.md#createobservability) |
|
|
5598
5939
|
| `RequestEvent` | _type_ | the normalised audit event handed to the sink |
|
|
5599
5940
|
| `ObservabilityConfig` | _type_ | independent request and tool sink configuration |
|
|
5600
|
-
| `Observability` | _type_ |
|
|
5601
|
-
| `RequestEventSinkConfig` | _type_ | `write`,
|
|
5941
|
+
| `Observability` | _type_ | `{ request?, toolCall, flush(), close() }` with bounded sink lifecycle |
|
|
5942
|
+
| `RequestEventSinkConfig` | _type_ | `write`, filter/sanitisation, `maxPending`, `onSinkError` and `onDrop` |
|
|
5602
5943
|
| `RequestObservabilityConfig` | _type_ | request sink plus opt-in payload capture |
|
|
5944
|
+
| `SinkDropReason` | _type_ | `'capacity' \| 'closed'` |
|
|
5945
|
+
| `SinkError` | _type_ | isolated sink/projection failure and optional event |
|
|
5946
|
+
| `SinkDrop` | _type_ | rejected event, reason and current pending count |
|
|
5603
5947
|
| `HttpRequestCompletion` | _type_ | the single framework-owned HTTP outcome projected to logging and request events |
|
|
5604
5948
|
| `HttpRequestObserver` | _type_ | server-facing projection consumed by `HandlerConfig.observability` |
|
|
5605
5949
|
|