stitchkit 0.110.5 → 0.111.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/agent-runtime/branch-observations.d.ts +16 -16
  3. package/dist/agent-runtime/sandbox-broker.d.ts.map +1 -1
  4. package/dist/browser/stream.d.ts +3 -2
  5. package/dist/browser/stream.d.ts.map +1 -1
  6. package/dist/contract/runtime-context.d.ts +3 -1
  7. package/dist/contract/runtime-context.d.ts.map +1 -1
  8. package/dist/entrypoints/agent-runtime/coding-tools.js +2 -2
  9. package/dist/entrypoints/agent-runtime/sandbox.js +15 -29
  10. package/dist/entrypoints/agent-runtime/sqlite/bun.js +4 -4
  11. package/dist/entrypoints/agent-runtime/sqlite/node.js +4 -4
  12. package/dist/entrypoints/agent-runtime/testing.js +4 -4
  13. package/dist/entrypoints/agent-runtime.js +4 -4
  14. package/dist/entrypoints/bin/upgrade-cli.d.ts.map +1 -1
  15. package/dist/entrypoints/bin/upgrade-cli.js +123 -29
  16. package/dist/entrypoints/cli/publish.js +1 -1
  17. package/dist/entrypoints/cli.js +2 -2
  18. package/dist/entrypoints/http.d.ts +2 -0
  19. package/dist/entrypoints/http.d.ts.map +1 -1
  20. package/dist/entrypoints/http.js +5 -1
  21. package/dist/entrypoints/index.d.ts +1 -0
  22. package/dist/entrypoints/index.d.ts.map +1 -1
  23. package/dist/entrypoints/index.js +5 -1
  24. package/dist/entrypoints/node.d.ts +1 -0
  25. package/dist/entrypoints/node.d.ts.map +1 -1
  26. package/dist/entrypoints/node.js +3 -1
  27. package/dist/entrypoints/observability.js +24 -9
  28. package/dist/entrypoints/process.js +1 -1
  29. package/dist/entrypoints/remote.js +1 -1
  30. package/dist/entrypoints/server.d.ts +2 -0
  31. package/dist/entrypoints/server.d.ts.map +1 -1
  32. package/dist/entrypoints/server.js +79 -6
  33. package/dist/entrypoints/telegram.js +3 -3
  34. package/dist/entrypoints/testing.js +1 -1
  35. package/dist/entrypoints/tools/connections.js +16 -31
  36. package/dist/entrypoints/tools.js +39 -7
  37. package/dist/{index-mpg1t7nv.js → index-0z3kfphz.js} +1 -1
  38. package/dist/{index-jk44mbqb.js → index-1y0arq4z.js} +3 -57
  39. package/dist/{index-5pbtbce0.js → index-7c957abz.js} +1 -1
  40. package/dist/{index-e1sjcjfy.js → index-7r4knwf8.js} +94 -23
  41. package/dist/index-cjbn98zt.js +68 -0
  42. package/dist/{index-bm180f2d.js → index-ey15wx6j.js} +1 -1
  43. package/dist/{index-s0xbnpfh.js → index-kdnfe0sr.js} +1 -1
  44. package/dist/{index-s6d8zsha.js → index-krng2a5j.js} +64 -110
  45. package/dist/{index-9ket8wzx.js → index-mnk5vgdr.js} +1 -1
  46. package/dist/index-qndsr7f0.js +55 -0
  47. package/dist/{index-n7hrpngg.js → index-syzngt7v.js} +1 -1
  48. package/dist/{index-3h0ztg0z.js → index-t65erk4z.js} +5 -21
  49. package/dist/internal/bounded-body.d.ts +44 -0
  50. package/dist/internal/bounded-body.d.ts.map +1 -0
  51. package/dist/internal/bounded-lines.d.ts +1 -2
  52. package/dist/internal/bounded-lines.d.ts.map +1 -1
  53. package/dist/internal/secure-fetch.d.ts.map +1 -1
  54. package/dist/internal/stream-chunks.d.ts +13 -0
  55. package/dist/internal/stream-chunks.d.ts.map +1 -0
  56. package/dist/internal/upgrade-affects.d.ts +5 -0
  57. package/dist/internal/upgrade-affects.d.ts.map +1 -1
  58. package/dist/internal/upgrade-plan.d.ts +18 -3
  59. package/dist/internal/upgrade-plan.d.ts.map +1 -1
  60. package/dist/internal/upgrade-usage.d.ts +21 -5
  61. package/dist/internal/upgrade-usage.d.ts.map +1 -1
  62. package/dist/observability/audit.d.ts +10 -0
  63. package/dist/observability/audit.d.ts.map +1 -1
  64. package/dist/observability/event.d.ts +5 -0
  65. package/dist/observability/event.d.ts.map +1 -1
  66. package/dist/process/group-diagnostics.d.ts +20 -0
  67. package/dist/process/group-diagnostics.d.ts.map +1 -0
  68. package/dist/process/group-settlement.d.ts +1 -1
  69. package/dist/process/group-settlement.d.ts.map +1 -1
  70. package/dist/process/group.d.ts.map +1 -1
  71. package/dist/server/bun.d.ts +9 -0
  72. package/dist/server/bun.d.ts.map +1 -1
  73. package/dist/server/multipart.d.ts.map +1 -1
  74. package/dist/server/oauth/endpoints.d.ts.map +1 -1
  75. package/dist/server/oauth/provider.d.ts +5 -0
  76. package/dist/server/oauth/provider.d.ts.map +1 -1
  77. package/dist/server/proxy-origin.d.ts +12 -0
  78. package/dist/server/proxy-origin.d.ts.map +1 -0
  79. package/dist/server/raw.d.ts +6 -3
  80. package/dist/server/raw.d.ts.map +1 -1
  81. package/dist/server/request-body.d.ts.map +1 -1
  82. package/dist/server/request-completion.d.ts.map +1 -1
  83. package/dist/server/request-ingress.d.ts +31 -0
  84. package/dist/server/request-ingress.d.ts.map +1 -0
  85. package/dist/server/unix-client-body.d.ts.map +1 -1
  86. package/dist/tools/connections/limits.d.ts.map +1 -1
  87. package/llms/http.txt +3 -0
  88. package/llms/observability.txt +19 -2
  89. package/llms/server.2.txt +135 -0
  90. package/llms/server.5.txt +3 -1
  91. package/llms/server.txt +97 -131
  92. package/llms/stitchkit.2.txt +28 -0
  93. package/llms/stitchkit.txt +3 -3
  94. package/llms/tools.3.txt +1 -1
  95. package/llms/tools.txt +4 -0
  96. package/llms/{upgrading-0.101.0-0.110.0.txt → upgrading-0.101.0-0.111.0.txt} +29 -3
  97. package/llms/upgrading.txt +8 -2
  98. package/llms-full.txt +192 -8
  99. package/llms.txt +6 -6
  100. package/package.json +1 -1
package/llms-full.txt CHANGED
@@ -77,12 +77,12 @@ own, recorded as an ADR.
77
77
  | `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
78
78
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
79
79
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
80
- | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 55 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
80
+ | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 56 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
81
81
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, the managed-resource conformance kit and manual time for schedules and retries |
82
82
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
83
83
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
84
84
  | `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
85
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 23 of the 55 minors since 0.56.2, most recently 0.105.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
85
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 23 of the 56 minors since 0.56.2, most recently 0.105.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
86
86
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
87
87
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
88
88
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -94,7 +94,7 @@ own, recorded as an ADR.
94
94
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
95
95
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
96
96
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
97
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 55 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
97
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 56 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
98
98
  | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
99
99
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
100
100
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -1162,6 +1162,48 @@ Every handler receives one `ctx` argument:
1162
1162
  The same handler runs for HTTP, MCP and agent calls — `ctx.source` tells you
1163
1163
  which. Anything an auth hook attaches (e.g. the resolved user) is also on `ctx`.
1164
1164
 
1165
+ ### Request attributes from the ingress
1166
+
1167
+ When your own code builds the `Request` — a local socket ingress, a relay, a test harness —
1168
+ and calls the fetch handler of the core, it often knows something an endpoint needs and the
1169
+ request cannot be allowed to say for itself: which authenticated peer sent it. A header can be
1170
+ forged by any client. `defineRequestIngress` is the channel the request cannot carry:
1171
+
1172
+ ```ts
1173
+ import { createHandler, defineRequestIngress, implement } from 'stitchkit/server'
1174
+
1175
+ interface Peer {
1176
+ node: string
1177
+ }
1178
+ export const peer = defineRequestIngress<Peer>('peer')
1179
+
1180
+ // The ingress, after it has authenticated the peer and built the request:
1181
+ peer.attach(request, { node: 'node-7' })
1182
+ const response = await handler(request)
1183
+
1184
+ // An endpoint, `authorize`, `beforeHandle` or `onError` — `ctx` is the source:
1185
+ implement(contract, {
1186
+ place: (ctx) => ({ node: peer.from(ctx)?.node ?? null }),
1187
+ })
1188
+ ```
1189
+
1190
+ - The value belongs to the key. A header, a body, `request.clone()` and `new Request(request)`
1191
+ carry nothing; a request from another ingress has no value, and `from` answers `undefined`.
1192
+ Two keys never meet, even under one name, and a second `attach` of the same key to the same
1193
+ request is an error.
1194
+ - `ctx.req` is the `Request` the fetch handler of the core received, on every HTTP context: an
1195
+ endpoint, `authorize`, `beforeHandle`, and the `onError` context of a validation failure, an
1196
+ unknown path, a refused method or an `onRequest` that threw. That is the guarantee the lookup
1197
+ stands on, and the ingress tests hold it.
1198
+ - A raw route, `streamingRoute` and `onRequest` receive the request without a context: read with
1199
+ `peer.from({ req })`. The MCP, agent and CLI lanes carry no `Request`, so there is nothing to
1200
+ read.
1201
+ - With `proxyOrigin` configured, the request is replaced before `wrapFetch`: attach in
1202
+ `wrapFetch` or later, to the request you are handed.
1203
+ - A Socket.IO handshake is routed before `wrapFetch` on Bun and rebuilt as a new `Request` on
1204
+ Node: it never carries an attached value.
1205
+ - `stitchkit/node` exports the same names.
1206
+
1165
1207
  ### `createImplement` — a fixed context type
1166
1208
 
1167
1209
  To type `ctx` with your app's extras (the user an auth hook injects), fix the
@@ -1369,6 +1411,7 @@ server. See [Testing & deployment](./testing-and-deployment.md).
1369
1411
  | `logging` | `true` for built-in request logs, or a `LoggingConfig` (see below) |
1370
1412
  | `traceId` | override per-request trace-id resolution — may return `undefined` to fall back |
1371
1413
  | `wrapFetch` | compose wrappers around the finished handler (request context, audit) |
1414
+ | `proxyOrigin` | Bun TCP ingress policy: `{ trustedPeer(socketIp) }` restores the external scheme before all composition; independent of client-IP `trustProxy` |
1372
1415
  | `socket` | full Stitchkit Socket.IO handle; route, default WebSocket handler and shutdown are mounted once |
1373
1416
  | `websocket` | custom Bun WebSocket handler; with `socket`, this is the explicit composed handler |
1374
1417
  | `development` / `bun` | passthrough to `Bun.serve` |
@@ -1377,6 +1420,60 @@ Native Bun `routes` are intentionally not accepted: Bun matches them before
1377
1420
  `fetch`, so they could bypass shutdown admission. Use `rawRoutes`; they retain
1378
1421
  the Fetch `Request → Response` model and participate in lifecycle tracking.
1379
1422
 
1423
+ ### External origin behind a trusted proxy
1424
+
1425
+ An HTTPS ingress often forwards plain HTTP to Bun. `trustProxy` trusts client-IP
1426
+ headers only; it does not change `Request.url`. Opt into the separate URL policy:
1427
+
1428
+ ```ts
1429
+ import { createServer, isLoopbackAddress } from 'stitchkit/server'
1430
+
1431
+ const server = createServer({
1432
+ hostname: '127.0.0.1',
1433
+ port: 3000,
1434
+ proxyOrigin: { trustedPeer: isLoopbackAddress },
1435
+ services,
1436
+ })
1437
+ ```
1438
+
1439
+ The predicate receives the **real TCP peer**, never `X-Forwarded-For` or
1440
+ `X-Real-IP`. Match only the ingress you control. Loopback is appropriate only
1441
+ when other local processes are trusted. Unmatched peers, missing peer metadata
1442
+ and the default configuration ignore URL forwarding headers. Unix listeners have
1443
+ no TCP peer and do not use this policy. This option belongs to Bun `createServer`,
1444
+ not the Fetch-clean `createHandler` or Node `serveNode`.
1445
+
1446
+ The trusted ingress must overwrite `Host` with the external authority and
1447
+ `X-Forwarded-Proto` with exactly `http` or `https`. It must remove `Forwarded`,
1448
+ `X-Forwarded-Host` and `X-Forwarded-Port`; their presence, a missing/invalid
1449
+ proto, a list of values or a malformed Host returns HTTP 400 before application
1450
+ code. Header names are case-insensitive; proto values are lowercase. Multiple
1451
+ ingress chains require a single sanitizing ingress at this boundary.
1452
+
1453
+ For an nginx HTTPS terminator, the relevant forwarding settings are:
1454
+
1455
+ ```nginx
1456
+ proxy_set_header Host $http_host;
1457
+ proxy_set_header X-Forwarded-Proto $scheme;
1458
+ proxy_set_header Forwarded "";
1459
+ proxy_set_header X-Forwarded-Host "";
1460
+ proxy_set_header X-Forwarded-Port "";
1461
+ ```
1462
+
1463
+ The normalized `Request.url` reaches `wrapFetch`, hooks, contracts, raw routes,
1464
+ and the managed Socket.IO route. It preserves the path/query, wire authority
1465
+ (including explicit ports), method, body, headers and linked cancellation signal.
1466
+ Request cloning uses the normalized URL. The same runtime handle resolves
1467
+ `requestIP`, `timeout` and `upgrade` against the original native request, retaining
1468
+ WebSocket and shutdown behavior.
1469
+
1470
+ This is a scheme normalization policy, **not host authorization**. Applications
1471
+ must retain their hostname/tenant allowlist and exact same-origin checks for
1472
+ Origin and Referer. The TLS terminator must reject SNI/Host mismatches before
1473
+ forwarding; a plain-HTTP Bun listener cannot observe the TLS SNI. The normalizer
1474
+ does not accept `X-Forwarded-Host`, authorize a foreign Host, or treat HTTP and
1475
+ HTTPS as aliases.
1476
+
1380
1477
  ### Local daemon over a unix socket
1381
1478
 
1382
1479
  A local daemon whose door is a socket file needs no TCP port at all:
@@ -2032,6 +2129,10 @@ handler: async (req) => {
2032
2129
  }
2033
2130
  ```
2034
2131
 
2132
+ `parseBody` reads the body up to `{ maxBytes }` — 1 MiB when you pass none, as the third
2133
+ argument — and returns `null` for a larger one without reading further, so a raw route
2134
+ never holds an unbounded body in memory.
2135
+
2035
2136
  The error helpers (`badRequest`, `notFound`, …) **throw** an `AppError`, so raise
2036
2137
  them inside the `try` and let `errorResponse(err)` render it — that runs any
2037
2138
  thrown value through the framework's `normalizeError`, so a raw route returns the
@@ -3454,6 +3555,33 @@ that ends inside a line throws `StreamTruncatedLineError` (`lineBytes`) instead
3454
3555
  fragment as a value — the right policy for a socket or a child process, where a missing newline
3455
3556
  means the writer died. Both errors extend the `RangeError` / `SyntaxError` they replace.
3456
3557
 
3558
+ ## Reading a body up to a byte limit
3559
+
3560
+ `readBoundedBody` (from `stitchkit` or `stitchkit/http`) reads a request or response body, a
3561
+ `ReadableStream<Uint8Array>` or any `AsyncIterable<Uint8Array>`, and never reads past
3562
+ `maxBytes` plus one chunk. Whoever owns the boundary picks the two errors, so a body that is too
3563
+ large and a read that was cancelled stay different outcomes:
3564
+
3565
+ ```ts
3566
+ import { readBoundedBody } from 'stitchkit'
3567
+
3568
+ const bytes = await readBoundedBody(request.body, {
3569
+ maxBytes: 64 * 1024,
3570
+ signal: request.signal,
3571
+ limitError: (observedBytes) => new PayloadTooLarge(observedBytes),
3572
+ abortError: (reason) => new ClientGone({ cause: reason }),
3573
+ })
3574
+ ```
3575
+
3576
+ - The source is cancelled when the limit is crossed and when `signal` aborts. A read that never
3577
+ settles does not hold the call after an abort, and an abort is honoured before the first read
3578
+ (even for a `null` body).
3579
+ - A body of exactly `maxBytes` is read; one more byte calls `limitError` with the bytes seen so far
3580
+ (the chunk that crossed the limit included).
3581
+ - A `null` body, such as a request without one, is an empty result.
3582
+ - Without `abortError` the signal's own reason is thrown.
3583
+ - `maxBytes` is a positive safe integer.
3584
+
3457
3585
  ## Resumable streams
3458
3586
 
3459
3587
  A stream that survives a dropped connection needs four things beyond opening it:
@@ -4456,6 +4584,10 @@ default; DCR is off unless `clientRegistration.dcr` supplies both `register` and
4456
4584
  exactly equals its URL, explicit redirect URIs and
4457
4585
  `token_endpoint_auth_method: 'none'`.
4458
4586
 
4587
+ The token endpoint and `/register` read at most 64 KiB of a request body, or
4588
+ `maxRequestBodyBytes` when you set it; a larger one is answered `413` with
4589
+ `invalid_request` and the rest is not read.
4590
+
4459
4591
  The production fetcher rejects credentials, fragments, non-HTTPS URLs and
4460
4592
  private/reserved targets. Every redirect is DNS-resolved and IP-pinned again;
4461
4593
  timeouts, redirect count and body size are bounded. Its bounded cache respects
@@ -13372,7 +13504,7 @@ queryable across all three:
13372
13504
  | `ok` / `statusCode` | legacy success bit plus real HTTP status, or `200`/`400` for a tool; an opted-in cancellation is `false` / `499`. `statusCode` is **absent** on a job — it had no transport to have a status on, and inventing one would be the same defect as the `method: 'AGENT'` this replaced. A job's outcome is `ok`, plus `errorCode` when it failed |
13373
13505
  | `durationMs` / `startedAt` | timing |
13374
13506
  | `errorCode` / `errorMessage` / `errorDetail` | failures only — `errorDetail` carries the structure the message flattens (e.g. Zod issues) |
13375
- | `payload` | sanitised tool arguments; HTTP is `null` unless request `includePayload` is enabled |
13507
+ | `payload` | sanitised tool arguments; HTTP is `null` unless request `includePayload` is enabled, or when the body was larger than `payloadMaxBytes` (then `payloadTruncated: true`) |
13376
13508
  | `resultSize` / `responseBytes` | result item count + serialised size |
13377
13509
  | `userId` / `ipAddress` / `userAgent` | identity |
13378
13510
 
@@ -13744,6 +13876,23 @@ createObservability({
13744
13876
  `redact`, `truncatePreview` and `measureSize` are exported on their own if you
13745
13877
  need to sanitise something outside the audit path.
13746
13878
 
13879
+ ### How much of a body the audit row reads
13880
+
13881
+ With `includePayload: true` the row reads a clone of the request body, and it reads at most
13882
+ `payloadMaxBytes` of it. Unset, the limit is the handler's `maxJsonBodyBytes`, or 1 MiB when the
13883
+ handler has none, so one number bounds both. The clone is taken when the request opens, before
13884
+ a route is matched, so an endpoint's own `maxJsonBodyBytes` is not known to it: set
13885
+ `payloadMaxBytes` when endpoints accept more than the handler default. A body that is larger
13886
+ is cancelled at the limit, its `payload` is `null`, and the row carries `payloadTruncated: true` — a `null` payload with that flag
13887
+ means "too large to record", not "no body". Set `payloadMaxBytes` (a positive integer) to record
13888
+ more or less than the handler accepts:
13889
+
13890
+ ```ts
13891
+ createObservability({
13892
+ request: { write, includePayload: true, payloadMaxBytes: 256 * 1024 },
13893
+ })
13894
+ ```
13895
+
13747
13896
  ## The raw hooks
13748
13897
 
13749
13898
  Tool observability is built on hooks you can also use directly — for a one-off
@@ -15998,6 +16147,32 @@ analytics policy belong to the application.
15998
16147
 
15999
16148
  # Upgrading stitchkit
16000
16149
 
16150
+ ## Released migration: 0.111.0
16151
+
16152
+ **Who must act:** applications whose raw routes call `parseBody` on JSON larger than 1 MiB, and applications whose OAuth clients send `/register` or `/token` bodies larger than 64 KiB.
16153
+
16154
+ ### Bounded raw and OAuth bodies
16155
+
16156
+ `parseBody` reads at most 1 MiB and returns `null` for a larger body, where it used to read any size. A route that takes larger JSON states its limit:
16157
+
16158
+ ```ts
16159
+ // before
16160
+ const body = await parseBody(req, Schema)
16161
+ // after
16162
+ const body = await parseBody(req, Schema, { maxBytes: 8 * 1024 * 1024 })
16163
+ ```
16164
+
16165
+ `mountOAuthProvider` reads at most 64 KiB of a `/token` or `/register` body and answers `413` with `invalid_request` for a larger one. A deployment whose clients register larger metadata raises the limit:
16166
+
16167
+ ```ts
16168
+ // before
16169
+ mountOAuthProvider({ issuer, signingSecret, resource, codes, authorizeUser })
16170
+ // after
16171
+ mountOAuthProvider({ issuer, signingSecret, resource, codes, authorizeUser, maxRequestBodyBytes: 256 * 1024 })
16172
+ ```
16173
+
16174
+ Both limits must be a positive safe integer; anything else is refused when the route is built or called.
16175
+
16001
16176
  ## Released migration: 0.110.0
16002
16177
 
16003
16178
  ### Interrupted process drains
@@ -17375,7 +17550,7 @@ Run it inside the project that depends on stitchkit. It reads your installed
17375
17550
  version out of `node_modules`, reads the changelog **shipped inside the
17376
17551
  package**, and prints every breaking section your range crosses, oldest first,
17377
17552
  each with its **Who must act** line — the list this guide's migrations expand
17378
- on. Nothing to clone, nothing to be told: `--from` / `--to` override either end
17553
+ on — and then the fixes of that range that name what they touch. Nothing to clone, nothing to be told: `--from` / `--to` override either end
17379
17554
  of the range if you want a different one.
17380
17555
 
17381
17556
  So upgrading is: read the `### ⚠️ Breaking changes` of every version *above* your
@@ -17406,6 +17581,12 @@ beside a `package.json`, which is build output) and gives each item one verdict:
17406
17581
  - **not declared — check by hand** — an item from before the line existed, with the files that
17407
17582
  import the entrypoints it leads with.
17408
17583
 
17584
+ A fix that names what it touches gets the same verdicts. Since 0.110.7 every `### Fixed` item that
17585
+ leads with an entrypoint carries the `**Affects:**` line, and `upgrade` prints them in a second
17586
+ section, **Fixes that touch this project** (`fixes` in `--json`): a fix to `runNativeCommand` is
17587
+ listed with the `path:line` that imports it, or as *not used here*. Fixes of earlier releases that
17588
+ carry no line are not judged; one line counts them (`fixesWithoutAffects` in `--json`).
17589
+
17409
17590
  `--json` prints the same verdicts per item for a CI step. The scan reads static `import` and
17410
17591
  `export … from` declarations, bare imports and `import()` / `require()` of a literal specifier; a
17411
17592
  specifier built at run time is invisible to it, and so is a file of another kind (`.astro`,
@@ -20903,6 +21084,7 @@ The browser-and-server entrypoint. Re-exports everything from
20903
21084
  | `parseSSE` | function | parse SSE from a `Response`, a web stream or an async byte iterable into an async generator — [guide](../guide/client.md#sse) |
20904
21085
  | `parseNDJSON` | function | parse bounded fatal-UTF-8 NDJSON from a `Response`, a `ReadableStream<Uint8Array>` or an `AsyncIterable<Uint8Array>` (a child's stdout); blank keep-alives are skipped, `finalLine: 'require-newline'` makes the delimiter mandatory and `signal` cancels the source — [guide](../guide/client.md#any-byte-source) |
20905
21086
  | `createNDJSONDecoder` / `NDJSONDecoder` | function / _type_ | the same bounded NDJSON reading driven by `push(chunk)` / `end()`, for a socket `data` callback |
21087
+ | `readBoundedBody` / `BoundedBodyOptions` | function / _type_ | the whole body of a `ReadableStream<Uint8Array>` or async byte iterable as bytes, never reading past `maxBytes` plus one chunk; `limitError(observedBytes)` and `abortError(reason)` choose the two errors, the source is cancelled on both, a `null` body is empty — [guide](../guide/client.md#reading-a-body-up-to-a-byte-limit) |
20906
21088
  | `StreamByteSource` | _type_ | `Response \| ReadableStream<Uint8Array> \| AsyncIterable<Uint8Array>`, what the NDJSON and SSE readers accept |
20907
21089
  | `StreamLineLimitError` / `StreamTruncatedLineError` | class | a line past `maxLineBytes` (`limitBytes`, `lineBytes`; a `RangeError`) and a source ended inside a line under `require-newline` (`lineBytes`; a `SyntaxError`) |
20908
21090
  | `resumableIterator` | function | re-open a long-lived stream from the last delivered cursor, with jittered backoff, a caller-owned terminal item and prompt abort — [guide](../guide/client.md#resumable-streams) |
@@ -21242,9 +21424,11 @@ Also re-exports the error helpers from `stitchkit/contract`.
21242
21424
  | `formatZodError` | function | a `ZodError` → a readable, field-summarised string |
21243
21425
  | `zodIssues` | function | a `ZodError` → structured `{ path, code, message }[]` — the machine-readable sibling of `formatZodError` |
21244
21426
  | `ZodIssueSummary` | _type_ | one structured validation issue (`{ path, code, message }`) |
21245
- | `parseBody` | function | parse + Zod-validate a JSON body → `data` or `null` (no throw) |
21427
+ | `parseBody` | function | parse + Zod-validate a JSON body → `data` or `null` (no throw); reads at most `options.maxBytes` (1 MiB by default) and is `null` for a larger body |
21246
21428
  | `HandlerConfig` | _type_ | config for `createHandler`, including optional `maxJsonBodyBytes`; bound to `BunServer` on this entrypoint |
21247
21429
  | `BunServerConfig` | _type_ | config for `createServer` (Bun); `unix` listens on a unix domain socket (mutually exclusive with `port`/`hostname`) — [details](../guide/server.md#local-daemon-over-a-unix-socket) |
21430
+ | `defineRequestIngress` / `RequestIngress` / `RequestIngressSource` | function / _type_ | a typed key the code that builds a `Request` attaches a value to (`attach(request, value)`) and an endpoint, `authorize`, `beforeHandle` or `onError` reads from its `ctx` (`from(ctx)`); a header, `clone()` and `new Request(request)` carry nothing, a second `attach` is refused, and `ctx.req` is the very request the handler received — [guide](../guide/server.md#request-attributes-from-the-ingress) |
21431
+ | `ProxyOriginConfig` | _type_ | explicit real-peer trust for Bun `createServer({ proxyOrigin })`; strict external-scheme normalization before composition — [details](../guide/server.md#external-origin-behind-a-trusted-proxy) |
21248
21432
  | `UnixListenConfig` | _type_ | unix listener — a socket path, or `{ path, mode }` to tighten the file mode after listen |
21249
21433
  | `BunServerHandle` | _type_ | managed Bun handle (`url`, `port`, `runtime`, `status`, `shutdown`) |
21250
21434
  | `ManagedServerHandle` | _type_ | shared lifecycle shape generic over the runtime escape hatch |
@@ -22333,7 +22517,7 @@ audit event. See the [Observability guide](../guide/observability.md).
22333
22517
  | `ObservabilityDrainReport` | _type_ | final closed/drained snapshot, `durationMs` (the shared drain's age, not this call's wait), and `drained` — read from the counters, `false` only when `total.pending + total.preparing` is nonzero |
22334
22518
  | `ObservabilitySinkStatusSchema` / `ObservabilityStatusSchema` / `ObservabilityDrainReportSchema` | schema | runtime schemas for status/report integration boundaries |
22335
22519
  | `RequestEventSinkConfig` | _type_ | `write`, filter/sanitisation, `maxPending`, `onSinkError` and `onDrop` |
22336
- | `RequestObservabilityConfig` | _type_ | request sink plus opt-in payload capture and default-off `includeCancelled` rows |
22520
+ | `RequestObservabilityConfig` | _type_ | request sink plus opt-in payload capture (`payloadMaxBytes` bounds the body read, `payloadTruncated` marks a cut one) and default-off `includeCancelled` rows |
22337
22521
  | `SinkDropReason` | _type_ | `'capacity' \| 'closed'` |
22338
22522
  | `SinkError` | _type_ | isolated sink/projection failure and optional event |
22339
22523
  | `SinkDrop` | _type_ | rejected event, reason and current pending count |
@@ -22578,7 +22762,7 @@ A native remote-connector auth surface for MCP — [guide](../guide/mcp-and-agen
22578
22762
  | `protectedResourceMetadataUrl` | function | build the metadata URL for a resource |
22579
22763
  | `wwwAuthenticateHeader` | function | build the `WWW-Authenticate` challenge header |
22580
22764
  | `PROTECTED_RESOURCE_PATH` | const | the well-known metadata path |
22581
- | `OAuthProviderConfig` | _type_ | config for `mountOAuthProvider` |
22765
+ | `OAuthProviderConfig` | _type_ | config for `mountOAuthProvider`; `maxRequestBodyBytes` bounds the `/token` and `/register` bodies (64 KiB by default) |
22582
22766
  | `ProtectedResourceConfig` | _type_ | config for `oauthProtectedResourceRoute` |
22583
22767
  | `ApplicationType` | _type_ | DCR `application_type` — `'native'` (loopback allowed) \| `'web'` (https only) |
22584
22768
  | `AuthCodeData` | _type_ | a stored authorization-code record |
package/llms.txt CHANGED
@@ -9,11 +9,11 @@ Build with stitchkit: define a contract once, then `implement` it and serve it (
9
9
  Load the slice for each entrypoint your code imports — `import … from 'stitchkit/server'` → `llms/server.txt`; the root `stitchkit` import → `llms/stitchkit.txt`. A slice holds that entrypoint's API reference section and the guide pages whose home it is; every guide lives in exactly one slice, and a slice that needs another's guide names it under "Also read". Each file is at most 50.0 KB; a longer one continues in numbered parts, each naming the next at its top. Moving across versions: load `llms/upgrading.txt` and the range slices it lists between your version and the target. `llms-full.txt` inlines everything; it is far larger than a context window, so do not read it whole.
10
10
 
11
11
  ## Entrypoint slices
12
- - **stitchkit**: [llms/stitchkit.txt](llms/stitchkit.txt) 47.5 KB → [llms/stitchkit.2.txt](llms/stitchkit.2.txt) 36.5 KB
12
+ - **stitchkit**: [llms/stitchkit.txt](llms/stitchkit.txt) 47.5 KB → [llms/stitchkit.2.txt](llms/stitchkit.2.txt) 38.1 KB
13
13
  - **stitchkit/live**: [llms/live.txt](llms/live.txt) 21.2 KB
14
14
  - **stitchkit/react**: [llms/react.txt](llms/react.txt) 8.6 KB
15
15
  - **stitchkit/react/keyboard**: [llms/react-keyboard.txt](llms/react-keyboard.txt) 9.2 KB
16
- - **stitchkit/tools**: [llms/tools.txt](llms/tools.txt) 46.2 KB → [llms/tools.2.txt](llms/tools.2.txt) 30.4 KB → [llms/tools.3.txt](llms/tools.3.txt) 26.2 KB
16
+ - **stitchkit/tools**: [llms/tools.txt](llms/tools.txt) 46.4 KB → [llms/tools.2.txt](llms/tools.2.txt) 30.4 KB → [llms/tools.3.txt](llms/tools.3.txt) 26.3 KB
17
17
  - **stitchkit/tools/mcp**: [llms/tools-mcp.txt](llms/tools-mcp.txt) 1.8 KB
18
18
  - **stitchkit/tools/contract**: [llms/tools-contract.txt](llms/tools-contract.txt) 0.2 KB
19
19
  - **stitchkit/tools/invoker**: [llms/tools-invoker.txt](llms/tools-invoker.txt) 0.2 KB
@@ -23,8 +23,8 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
23
23
  - **stitchkit/remote**: [llms/remote.txt](llms/remote.txt) 1.0 KB
24
24
  - **stitchkit/contract**: [llms/contract.txt](llms/contract.txt) 31.9 KB
25
25
  - **stitchkit/primitives**: [llms/primitives.txt](llms/primitives.txt) 11.7 KB
26
- - **stitchkit/server**: [llms/server.txt](llms/server.txt) 45.3 KB → [llms/server.2.txt](llms/server.2.txt) 14.6 KB → [llms/server.3.txt](llms/server.3.txt) 45.6 KB → [llms/server.4.txt](llms/server.4.txt) 38.3 KB → [llms/server.5.txt](llms/server.5.txt) 22.1 KB
27
- - **stitchkit/observability**: [llms/observability.txt](llms/observability.txt) 45.9 KB
26
+ - **stitchkit/server**: [llms/server.txt](llms/server.txt) 44.2 KB → [llms/server.2.txt](llms/server.2.txt) 20.5 KB → [llms/server.3.txt](llms/server.3.txt) 45.6 KB → [llms/server.4.txt](llms/server.4.txt) 38.3 KB → [llms/server.5.txt](llms/server.5.txt) 22.9 KB
27
+ - **stitchkit/observability**: [llms/observability.txt](llms/observability.txt) 46.9 KB
28
28
  - **stitchkit/agent-runtime**: [llms/agent-runtime.txt](llms/agent-runtime.txt) 42.7 KB → [llms/agent-runtime.2.txt](llms/agent-runtime.2.txt) 46.7 KB → [llms/agent-runtime.3.txt](llms/agent-runtime.3.txt) 35.6 KB
29
29
  - **stitchkit/agent-runtime/testing**: [llms/agent-runtime-testing.txt](llms/agent-runtime-testing.txt) 2.6 KB
30
30
  - **stitchkit/agent-runtime/harness**: [llms/agent-runtime-harness.txt](llms/agent-runtime-harness.txt) 11.0 KB
@@ -63,8 +63,8 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
63
63
  - **stitchkit/realtime**: [llms/realtime.txt](llms/realtime.txt) 1.4 KB
64
64
 
65
65
  ## Upgrading slices
66
- - **Upgrading — how to move across versions**: [llms/upgrading.txt](llms/upgrading.txt) 12.2 KB
67
- - **Upgrading 0.101.0 → 0.110.0**: [llms/upgrading-0.101.0-0.110.0.txt](llms/upgrading-0.101.0-0.110.0.txt) 45.8 KB
66
+ - **Upgrading — how to move across versions**: [llms/upgrading.txt](llms/upgrading.txt) 12.7 KB
67
+ - **Upgrading 0.101.0 → 0.111.0**: [llms/upgrading-0.101.0-0.111.0.txt](llms/upgrading-0.101.0-0.111.0.txt) 46.9 KB
68
68
  - **Upgrading 0.74.0 → 0.100.0**: [llms/upgrading-0.74.0-0.100.0.txt](llms/upgrading-0.74.0-0.100.0.txt) 45.1 KB
69
69
  - **Upgrading 0.61.0 → 0.73.0**: [llms/upgrading-0.61.0-0.73.0.txt](llms/upgrading-0.61.0-0.73.0.txt) 41.2 KB
70
70
  - **Upgrading 0.44.0 → 0.60.0**: [llms/upgrading-0.44.0-0.60.0.txt](llms/upgrading-0.44.0-0.60.0.txt) 43.9 KB
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.110.5",
3
+ "version": "0.111.0",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",