stitchkit 0.42.0 → 0.43.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -5
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +2 -1
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/request-id.d.ts +3 -0
- package/dist/browser/request-id.d.ts.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/contract/factory.d.ts +29 -2
- package/dist/contract/factory.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-181aebw8.js → index-esqmem78.js} +100 -66
- package/dist/{index-p9vkwns1.js → index-h2wdcsby.js} +93 -15
- package/dist/{index-11x5dts2.js → index-r6czv7yt.js} +14 -9
- package/dist/index.js +16 -7
- package/dist/node.js +1 -1
- package/dist/observability/audit.d.ts +27 -15
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/event.d.ts +2 -2
- package/dist/observability/index.d.ts +4 -4
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +69 -58
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/logger.d.ts +0 -1
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/types.d.ts +8 -13
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/flatten-join.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +31 -0
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +52 -4
- package/llms-full.txt +272 -88
- package/llms.txt +1 -0
- package/package.json +2 -2
package/llms-full.txt
CHANGED
|
@@ -44,7 +44,7 @@ keeping server-only code (`Bun.serve`, the MCP SDK) out of browser bundles.
|
|
|
44
44
|
| `stitchkit/node` | server (Node ≥ 22) | `serveNode` + the runtime-agnostic core — the Node mirror of `/server` |
|
|
45
45
|
| `stitchkit/tools` | server | `createMcpHandler`, `mountMcp`, `mountAgent`, the OAuth provider, native tools |
|
|
46
46
|
| `stitchkit/cli` | server | `createCli` — the CLI transport, light (no MCP SDK / `ai`) |
|
|
47
|
-
| `stitchkit/observability` | server |
|
|
47
|
+
| `stitchkit/observability` | server | request/tool event projections — `createObservability`, trace context, sanitisation |
|
|
48
48
|
| `stitchkit/react` | browser | `createCursorQuery`, `createCacheBridge` |
|
|
49
49
|
|
|
50
50
|
Rule of thumb: browser code imports `stitchkit` and `stitchkit/react`; server
|
|
@@ -168,7 +168,9 @@ actionable error naming the package and the install command — not a bare
|
|
|
168
168
|
- [Testing & deployment](./testing-and-deployment.md).
|
|
169
169
|
- [API reference](../api/reference.md) — every export, by entrypoint.
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
For a complete production-shaped app, run `bun create stitchkit my-app`. The
|
|
172
|
+
canonical generated topology is maintained in
|
|
173
|
+
[`packages/create-stitchkit/template`](../../packages/create-stitchkit/template).
|
|
172
174
|
|
|
173
175
|
|
|
174
176
|
==============================================================================
|
|
@@ -339,6 +341,19 @@ tools. Narrow it with `expose`:
|
|
|
339
341
|
- `expose: ['MCP', 'AGENT']` — a tool only; no HTTP route.
|
|
340
342
|
- omit `expose` — all transports.
|
|
341
343
|
|
|
344
|
+
For a curated or security-sensitive tool surface, opt into explicit tool
|
|
345
|
+
exposure at the scoped factory:
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
const { defineContract } = createContractFactory<AppScope>({
|
|
349
|
+
toolExposure: 'explicit',
|
|
350
|
+
})
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
With this policy, omitting `expose` materializes `['HTTP']` on the returned
|
|
354
|
+
endpoint. MCP, Agent and CLI then require an explicit endpoint array. The plain
|
|
355
|
+
factory and `defineContract` keep the default-on behaviour above.
|
|
356
|
+
|
|
342
357
|
Tool transports (`MCP`, `AGENT`) skip three kinds of endpoint automatically:
|
|
343
358
|
`multipart` (a file upload is not a tool call) and
|
|
344
359
|
[`rawResponse`](./server.md#raw-response-endpoints) (its answer is bytes, which
|
|
@@ -542,9 +557,11 @@ export const { defineContract } = createContractFactory<'public' | 'user' | 'adm
|
|
|
542
557
|
export const users = defineContract({ prefix: 'users', scope: 'user' }, { … })
|
|
543
558
|
```
|
|
544
559
|
|
|
545
|
-
The vocabulary is yours; each returned `
|
|
546
|
-
literal, so scope-aware registries select
|
|
547
|
-
wrapper.
|
|
560
|
+
The vocabulary is yours; each returned `ScopedContractDef` retains its concrete
|
|
561
|
+
scope literal as a required `meta.scope` field, so scope-aware registries select
|
|
562
|
+
the exact config without `NonNullable` or another wrapper. Plain `ContractDef`
|
|
563
|
+
keeps optional scope because ordinary `defineContract` still supports its
|
|
564
|
+
default-public overload.
|
|
548
565
|
|
|
549
566
|
## One source of truth
|
|
550
567
|
|
|
@@ -1529,10 +1546,16 @@ try {
|
|
|
1529
1546
|
err.message // 'Note not found'
|
|
1530
1547
|
err.details // structured details, if any
|
|
1531
1548
|
err.hint // optional hint
|
|
1549
|
+
err.traceId // x-request-id — correlate this failure with backend logs
|
|
1532
1550
|
}
|
|
1533
1551
|
}
|
|
1534
1552
|
```
|
|
1535
1553
|
|
|
1554
|
+
`traceId` is present when the server returned `x-request-id`. A network error,
|
|
1555
|
+
timeout or abort without an HTTP response cannot carry one. Cross-origin
|
|
1556
|
+
browser code can read it through Stitchkit's default CORS expose list; a custom
|
|
1557
|
+
`cors.exposeHeaders` policy must keep `x-request-id` exposed.
|
|
1558
|
+
|
|
1536
1559
|
The error model is shared with the server — see [Auth & errors](./auth-and-errors.md).
|
|
1537
1560
|
|
|
1538
1561
|
## Auth events
|
|
@@ -1664,6 +1687,11 @@ The server side is [`streamSSE`](./server.md#sse-streaming).
|
|
|
1664
1687
|
|
|
1665
1688
|
# MCP & AI agents
|
|
1666
1689
|
|
|
1690
|
+
The application generated by `bun create stitchkit` includes a working
|
|
1691
|
+
stateless MCP endpoint and a separate `bun run tools` manifest command. Both
|
|
1692
|
+
are assembled from the same implemented project contracts as HTTP and CLI, so
|
|
1693
|
+
the generated project is the canonical end-to-end example for this chapter.
|
|
1694
|
+
|
|
1667
1695
|
The same contract that drives the HTTP API also drives AI tooling. An endpoint
|
|
1668
1696
|
exposed on `MCP` becomes a [Model Context Protocol](https://modelcontextprotocol.io)
|
|
1669
1697
|
tool — callable from Claude, Cursor and other MCP clients. An endpoint exposed on
|
|
@@ -1978,8 +2006,14 @@ as one thing. Where they agree, that is what you get; where they disagree — a
|
|
|
1978
2006
|
`.refine()` only one of them carries, two different bounds on the same number, an
|
|
1979
2007
|
enum against a free string — the *constraint* is dropped and the **type** is not.
|
|
1980
2008
|
A field that is a number in every variant is advertised as a number, not as a
|
|
1981
|
-
bare description.
|
|
1982
|
-
|
|
2009
|
+
bare description. When the kinds genuinely differ, the flat projection keeps
|
|
2010
|
+
every provable kind in a deterministic JSON Schema type array — for example
|
|
2011
|
+
`type: ['string', 'array']` — while dropping constraints that are not sound for
|
|
2012
|
+
all branches. Nested `oneOf` / `anyOf` values still contribute their provable
|
|
2013
|
+
base kinds without reintroducing union keywords. Only a branch whose kind is
|
|
2014
|
+
actually unknowable, such as a free-form schema or unresolved reference, leaves
|
|
2015
|
+
the collision unconstrained. → ADRs [0044](../decisions/0044-a-collided-field-keeps-its-type.md)
|
|
2016
|
+
and [0065](../decisions/0065-flat-collisions-preserve-every-known-kind.md)
|
|
1983
2017
|
|
|
1984
2018
|
It is **deep** because the projection walks the generated JSON Schema document,
|
|
1985
2019
|
including objects, arrays, tuples and schema-definition nodes. Structurally
|
|
@@ -1987,6 +2021,10 @@ identifiable discriminated object unions are flattened wherever they occur.
|
|
|
1987
2021
|
Plain unions and unions hidden behind unresolved external references remain
|
|
1988
2022
|
unions because Stitchkit cannot soundly invent a discriminator.
|
|
1989
2023
|
|
|
2024
|
+
Use the default `flattenUnionInput: false` when the model must see the exact
|
|
2025
|
+
relationship between a discriminator and each branch. The flat type array is a
|
|
2026
|
+
sound set of possible JSON kinds, not a reconstruction of those correlations.
|
|
2027
|
+
|
|
1990
2028
|
The flattened form is **lossy but never executable**. Per-variant refinements
|
|
1991
2029
|
and incompatible constraints are widened in the presentation document; the
|
|
1992
2030
|
original Zod contract enforces them exactly once inside `executeToolMethod`.
|
|
@@ -2287,6 +2325,33 @@ const agentTools = mountAgent([service], {
|
|
|
2287
2325
|
})
|
|
2288
2326
|
```
|
|
2289
2327
|
|
|
2328
|
+
When several runtime tools share application context and identity, bind both
|
|
2329
|
+
once with `createRuntimeToolFactory`. Its Zod context schema is parsed once per
|
|
2330
|
+
call inside the same runner; the authored handler receives validated context and
|
|
2331
|
+
parsed `input`, while lifecycle, hooks, output validation and presenters remain
|
|
2332
|
+
unchanged:
|
|
2333
|
+
|
|
2334
|
+
```ts
|
|
2335
|
+
const knowledgeTools = createRuntimeToolFactory({
|
|
2336
|
+
serviceName: 'agentKnowledge',
|
|
2337
|
+
scope: 'user',
|
|
2338
|
+
context: z.object({ userId: z.string(), tz: z.string() }),
|
|
2339
|
+
})
|
|
2340
|
+
|
|
2341
|
+
const countRecords = knowledgeTools.define({
|
|
2342
|
+
name: 'count_records',
|
|
2343
|
+
action: 'countRecords',
|
|
2344
|
+
method: 'GET',
|
|
2345
|
+
description: 'Count records',
|
|
2346
|
+
input: z.object({ kind: z.string() }),
|
|
2347
|
+
output: z.object({ count: z.number() }),
|
|
2348
|
+
handler: async ({ userId, tz, input }) => countFor(userId, tz, input.kind),
|
|
2349
|
+
})
|
|
2350
|
+
```
|
|
2351
|
+
|
|
2352
|
+
`serviceName` and `scope` cannot be overridden by one definition. Use standalone
|
|
2353
|
+
`defineRuntimeTool` when tools do not share a context schema or identity.
|
|
2354
|
+
|
|
2290
2355
|
`transports` defaults to `['MCP', 'AGENT']`; set an explicit subset when an
|
|
2291
2356
|
operation belongs on only one surface. The configured identity becomes the
|
|
2292
2357
|
hook/lifecycle `OperationIdentity` and the tool `RequestEvent`
|
|
@@ -3283,8 +3348,9 @@ Use the exported `zodIssues(error)` to build the same structured list from a
|
|
|
3283
3348
|
### On the client
|
|
3284
3349
|
|
|
3285
3350
|
The client parses that envelope back into an `ApiError` with the same `code`,
|
|
3286
|
-
`status`, `details` and `hint
|
|
3287
|
-
|
|
3351
|
+
`status`, `details` and `hint`, plus the response `x-request-id` as optional
|
|
3352
|
+
readonly `traceId` — see [Typed client → ApiError](./client.md#apierror). The
|
|
3353
|
+
error round-trips without adding correlation data to the response body.
|
|
3288
3354
|
|
|
3289
3355
|
### Stitch codes vs your codes
|
|
3290
3356
|
|
|
@@ -3443,10 +3509,10 @@ stitchkit answers this at two levels.
|
|
|
3443
3509
|
- **The raw hooks** — `LifecycleHooks` and `ToolCallHooks`. Every request and
|
|
3444
3510
|
every tool call passes through a point you can observe. The lowest level;
|
|
3445
3511
|
always available. [Jump ↓](#the-raw-hooks)
|
|
3446
|
-
- **`stitchkit/observability`** —
|
|
3447
|
-
trace context, an `AsyncLocalStorage` request context, payload
|
|
3448
|
-
and `
|
|
3449
|
-
|
|
3512
|
+
- **`stitchkit/observability`** — framework-owned HTTP completion plus canonical
|
|
3513
|
+
tool hooks: W3C trace context, an `AsyncLocalStorage` request context, payload
|
|
3514
|
+
sanitisation and `createObservability` with independent request/tool sinks.
|
|
3515
|
+
[Start here ↓](#the-observability-module)
|
|
3450
3516
|
|
|
3451
3517
|
stitchkit still ships no logger and no audit store — those are the app's choice.
|
|
3452
3518
|
What it ships is the machinery that turns a completed call into a clean,
|
|
@@ -3455,52 +3521,55 @@ normalised record.
|
|
|
3455
3521
|
## The observability module
|
|
3456
3522
|
|
|
3457
3523
|
`stitchkit/observability` is server-only. It has three parts — a trace context,
|
|
3458
|
-
a request context, and
|
|
3459
|
-
|
|
3460
|
-
###
|
|
3461
|
-
|
|
3462
|
-
`
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
```ts
|
|
3468
|
-
import {
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3524
|
+
a request context, and event projections — and you usually touch only the last.
|
|
3525
|
+
|
|
3526
|
+
### createObservability
|
|
3527
|
+
|
|
3528
|
+
`createObservability` configures request and tool projections independently.
|
|
3529
|
+
Every completed call is normalised into one `RequestEvent`; HTTP completion is
|
|
3530
|
+
owned directly by `createHandler`, while MCP/Agent completion uses the canonical
|
|
3531
|
+
`ToolCallHooks` runner. There is no nested HTTP audit wrapper.
|
|
3532
|
+
|
|
3533
|
+
```ts
|
|
3534
|
+
import { createObservability } from 'stitchkit/observability'
|
|
3535
|
+
|
|
3536
|
+
const write = (event) => db.auditLog.create({ data: {
|
|
3537
|
+
traceId: event.traceId,
|
|
3538
|
+
source: event.source,
|
|
3539
|
+
method: event.method,
|
|
3540
|
+
path: event.path,
|
|
3541
|
+
ok: event.ok,
|
|
3542
|
+
statusCode: event.statusCode,
|
|
3543
|
+
durationMs: event.durationMs,
|
|
3544
|
+
userId: event.userId,
|
|
3545
|
+
payload: event.payload,
|
|
3546
|
+
}})
|
|
3547
|
+
|
|
3548
|
+
export const observability = createObservability({
|
|
3549
|
+
request: {
|
|
3550
|
+
write,
|
|
3551
|
+
includePayload: false, // default: no Request.clone(), payload is null
|
|
3552
|
+
filter: (event) => event.method !== 'GET',
|
|
3553
|
+
},
|
|
3554
|
+
tools: {
|
|
3555
|
+
write,
|
|
3556
|
+
filter: (event) => event.source === 'mcp' || event.source === 'agent',
|
|
3484
3557
|
},
|
|
3485
|
-
// Optional — keep only the events you care about.
|
|
3486
|
-
filter: (event) => event.source !== 'http' || event.method !== 'GET',
|
|
3487
3558
|
})
|
|
3488
3559
|
```
|
|
3489
3560
|
|
|
3490
|
-
|
|
3561
|
+
Wire each projection where its completion is owned:
|
|
3491
3562
|
|
|
3492
3563
|
```ts
|
|
3493
|
-
|
|
3494
|
-
Bun.serve({ fetch: wrapInRequestContext(audit.http(handler)) })
|
|
3564
|
+
createServer({ services, observability: observability.request })
|
|
3495
3565
|
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
mountAgent(service, { hooks: audit.toolCall })
|
|
3566
|
+
createMcpHandler({ /* … */ hooks: observability.toolCall })
|
|
3567
|
+
mountAgent(service, { hooks: observability.toolCall })
|
|
3499
3568
|
```
|
|
3500
3569
|
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3570
|
+
Each sink runs fire-and-forget and fails independently: a slow or broken request
|
|
3571
|
+
sink cannot block the response, suppress operational logging or break the tool
|
|
3572
|
+
sink.
|
|
3504
3573
|
|
|
3505
3574
|
### RequestEvent
|
|
3506
3575
|
|
|
@@ -3519,32 +3588,26 @@ queryable across all three:
|
|
|
3519
3588
|
| `ok` / `statusCode` | outcome — real HTTP status, or `200`/`400` for a tool |
|
|
3520
3589
|
| `durationMs` / `startedAt` | timing |
|
|
3521
3590
|
| `errorCode` / `errorMessage` / `errorDetail` | failures only — `errorDetail` carries the structure the message flattens (e.g. Zod issues) |
|
|
3522
|
-
| `payload` |
|
|
3591
|
+
| `payload` | sanitised tool arguments; HTTP is `null` unless request `includePayload` is enabled |
|
|
3523
3592
|
| `resultSize` / `responseBytes` | result item count + serialised size |
|
|
3524
3593
|
| `userId` / `ipAddress` / `userAgent` | identity |
|
|
3525
3594
|
|
|
3526
3595
|
### Request context
|
|
3527
3596
|
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3597
|
+
When request observability is configured, `createHandler` establishes the
|
|
3598
|
+
`AsyncLocalStorage` request context itself and uses the same completion snapshot
|
|
3599
|
+
for operational logging and `RequestEvent`. No `wrapFetch` composition is
|
|
3600
|
+
needed:
|
|
3531
3601
|
|
|
3532
3602
|
```ts
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
Bun.serve({
|
|
3536
|
-
fetch: wrapInRequestContext(audit.http(handler)),
|
|
3537
|
-
})
|
|
3603
|
+
createServer({ services, logging, observability: observability.request })
|
|
3538
3604
|
```
|
|
3539
3605
|
|
|
3540
|
-
`
|
|
3541
|
-
|
|
3606
|
+
`wrapInRequestContext` remains available for a custom fetch pipeline that does
|
|
3607
|
+
not use `createHandler`; it is no longer part of built-in HTTP audit wiring:
|
|
3542
3608
|
|
|
3543
3609
|
```ts
|
|
3544
|
-
|
|
3545
|
-
services,
|
|
3546
|
-
wrapFetch: (fetch) => wrapInRequestContext(audit.http(fetch)),
|
|
3547
|
-
})
|
|
3610
|
+
Bun.serve({ fetch: wrapInRequestContext(customFetch) })
|
|
3548
3611
|
```
|
|
3549
3612
|
|
|
3550
3613
|
Some fields are filled in late. Set them from the hooks that know:
|
|
@@ -3645,7 +3708,8 @@ locally; you do not need to change `NODE_ENV` or deploy.
|
|
|
3645
3708
|
Every response the stitchkit handler produces carries the resolved id as
|
|
3646
3709
|
**`x-request-id`**. With `cors` configured it is in the default
|
|
3647
3710
|
`Access-Control-Expose-Headers`, so a browser client can read it and quote it in
|
|
3648
|
-
a bug report.
|
|
3711
|
+
a bug report. Stitchkit browser clients retain it as `ApiError.traceId` on
|
|
3712
|
+
response-backed failures. Note the deliberate asymmetry: inbound the id may arrive as
|
|
3649
3713
|
`X-Trace-Id` *or* `X-Request-Id`; outbound there is one name and no alias.
|
|
3650
3714
|
|
|
3651
3715
|
Log the same id from nginx and the two logs join on one key. `log_format` and
|
|
@@ -3679,8 +3743,8 @@ every tool call underneath it. With no inbound header a fresh root trace is
|
|
|
3679
3743
|
minted. Each tool call opens a [`childSpan`](#trace-context) of the request it
|
|
3680
3744
|
runs in.
|
|
3681
3745
|
|
|
3682
|
-
You rarely call the trace functions directly — `
|
|
3683
|
-
`
|
|
3746
|
+
You rarely call the trace functions directly — `createHandler` request
|
|
3747
|
+
observability and `wrapInRequestContext` use them for you. They are exported (`resolveTraceContext`,
|
|
3684
3748
|
`parseTraceparent`, `formatTraceparent`, `childSpan`) for when you need to
|
|
3685
3749
|
propagate a `traceparent` onward to another service.
|
|
3686
3750
|
|
|
@@ -3715,12 +3779,19 @@ A payload goes into an audit row only after `sanitizePayload`:
|
|
|
3715
3779
|
never the bytes;
|
|
3716
3780
|
- the result is **capped** — anything over the byte limit becomes a preview.
|
|
3717
3781
|
|
|
3718
|
-
`
|
|
3782
|
+
`createObservability` runs it on every emitted event; tune each sink separately:
|
|
3719
3783
|
|
|
3720
3784
|
```ts
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3785
|
+
createObservability({
|
|
3786
|
+
request: {
|
|
3787
|
+
write,
|
|
3788
|
+
includePayload: true,
|
|
3789
|
+
sanitize: { maxBytes: 8_000, sensitiveKeys: /password|token|pin/i },
|
|
3790
|
+
},
|
|
3791
|
+
tools: {
|
|
3792
|
+
write,
|
|
3793
|
+
sanitize: { maxBytes: 8_000, sensitiveKeys: /password|token|pin/i },
|
|
3794
|
+
},
|
|
3724
3795
|
})
|
|
3725
3796
|
```
|
|
3726
3797
|
|
|
@@ -3729,7 +3800,7 @@ need to sanitise something outside the audit path.
|
|
|
3729
3800
|
|
|
3730
3801
|
## The raw hooks
|
|
3731
3802
|
|
|
3732
|
-
|
|
3803
|
+
Tool observability is built on hooks you can also use directly — for a one-off
|
|
3733
3804
|
metric, a custom log line, anything that is not a full audit row.
|
|
3734
3805
|
|
|
3735
3806
|
| Surface | Hook | Fires |
|
|
@@ -3803,7 +3874,7 @@ observe. (This is also why it lives on `ToolCallHooks` rather than being an
|
|
|
3803
3874
|
object must stay assignable to `ToolLifecycle`.)
|
|
3804
3875
|
|
|
3805
3876
|
**Do not reach for `setRequestError` here.** It writes to the *request* context,
|
|
3806
|
-
which
|
|
3877
|
+
which the built-in **tool** row does not read: a tool event takes
|
|
3807
3878
|
`errorCode` / `errorMessage` / `errorDetail` from the `ToolResult`, and only
|
|
3808
3879
|
identity and `dimensions` from the context. Calling it in `onToolError` would
|
|
3809
3880
|
leave the tool row exactly as scrubbed as before. It is right for the **HTTP**
|
|
@@ -3829,7 +3900,7 @@ validation failure or a `beforeToolCall` rejection leaves it `undefined`, becaus
|
|
|
3829
3900
|
neither ever had a raw value to lose. Consumers destructure only the fields they
|
|
3830
3901
|
use; future optional fields do not change callback arity.
|
|
3831
3902
|
|
|
3832
|
-
`
|
|
3903
|
+
`createObservability({ tools })` uses it already. Where the envelope was scrubbed to
|
|
3833
3904
|
`INTERNAL_SERVER_ERROR`, the row's `errorMessage` becomes the real message
|
|
3834
3905
|
instead of the placeholder; a truthful envelope (a thrown `AppError`, a
|
|
3835
3906
|
`ZodError`) is left alone, `errorCode` and `errorDetail` are untouched, and the
|
|
@@ -3842,7 +3913,7 @@ sink of your own (a tracker, a stack, an alert), `afterToolCall` for the record.
|
|
|
3842
3913
|
|
|
3843
3914
|
### Keying a row on (service, action)
|
|
3844
3915
|
|
|
3845
|
-
|
|
3916
|
+
Built-in observability keys every event by **service** and **action**
|
|
3846
3917
|
(`event.serviceName` / `event.action`, → ADR 0029) — reach for the raw hook only
|
|
3847
3918
|
when you also need the handler **output**, which the audit wrapper never sees. For
|
|
3848
3919
|
that, read the endpoint identity off the `OperationIdentity` the tool hook
|
|
@@ -3870,11 +3941,11 @@ hooks: {
|
|
|
3870
3941
|
}
|
|
3871
3942
|
```
|
|
3872
3943
|
|
|
3873
|
-
> **Why
|
|
3874
|
-
> has a single `onError
|
|
3875
|
-
>
|
|
3876
|
-
>
|
|
3877
|
-
>
|
|
3944
|
+
> **Why HTTP observability is framework-owned, not a lifecycle hook.**
|
|
3945
|
+
> `LifecycleHooks` has a single `onError`; an audit built on it would compete
|
|
3946
|
+
> with the app's error renderer and miss raw/unmatched exits. `createHandler`
|
|
3947
|
+
> sees the final response on every path and emits one completion without
|
|
3948
|
+
> consuming an application hook.
|
|
3878
3949
|
|
|
3879
3950
|
Keep any sink **asynchronous and self-contained**: a slow or failing write must
|
|
3880
3951
|
never block or break the request. Swallow the sink's own errors.
|
|
@@ -4023,7 +4094,8 @@ Notes for a Node host:
|
|
|
4023
4094
|
stitchkit serves the API. A SPA front-end is built and hosted separately — a
|
|
4024
4095
|
static host or CDN in production, its own dev server in development. The backend
|
|
4025
4096
|
does not serve static files (`staticRoute` exists for the occasional asset, not
|
|
4026
|
-
a whole app).
|
|
4097
|
+
a whole app). `bun create stitchkit my-app` demonstrates the supported split:
|
|
4098
|
+
an independently built Next.js frontend and Bun/Stitchkit API.
|
|
4027
4099
|
|
|
4028
4100
|
### MCP
|
|
4029
4101
|
|
|
@@ -4160,6 +4232,76 @@ rule read it the same way on both surfaces — one contract, every surface, no
|
|
|
4160
4232
|
per-transport tenant plumbing.
|
|
4161
4233
|
|
|
4162
4234
|
|
|
4235
|
+
==============================================================================
|
|
4236
|
+
# Guide: Frontend integrations (docs/guide/frontend-integrations.md)
|
|
4237
|
+
==============================================================================
|
|
4238
|
+
|
|
4239
|
+
---
|
|
4240
|
+
title: Frontend integrations
|
|
4241
|
+
description: Compose Stitchkit with Next.js, React Router or a separate Vite development server
|
|
4242
|
+
type: architecture
|
|
4243
|
+
status: active
|
|
4244
|
+
created: 2026-08-08
|
|
4245
|
+
updated: 2026-08-08
|
|
4246
|
+
---
|
|
4247
|
+
|
|
4248
|
+
# Frontend integrations
|
|
4249
|
+
|
|
4250
|
+
The official `bun create stitchkit` application uses Next.js with a separate
|
|
4251
|
+
Bun API. Stitchkit remains a Fetch-native backend and does not own frontend
|
|
4252
|
+
routing, SSR or HMR.
|
|
4253
|
+
|
|
4254
|
+
## Theme boundary in the official starter
|
|
4255
|
+
|
|
4256
|
+
The generated Next.js application uses `@wrksz/themes`, not a Stitchkit-owned
|
|
4257
|
+
theme abstraction. Its root `ThemeProvider` comes from `@wrksz/themes/next` and
|
|
4258
|
+
lives directly in the server layout so Next 16 can inject the first-paint script
|
|
4259
|
+
through `useServerInsertedHTML`. The default `hybrid` storage reads a cookie
|
|
4260
|
+
during SSR and mirrors changes to localStorage for cross-tab synchronization.
|
|
4261
|
+
|
|
4262
|
+
Client components import typed hooks from the fine-grained
|
|
4263
|
+
`@wrksz/themes/client/*` entrypoints. Nested visual examples use
|
|
4264
|
+
`ClientThemeProvider` with a scoped target and `storage="none"`; they never
|
|
4265
|
+
become a second global provider. Applications may add account-backed theme
|
|
4266
|
+
preferences, CSP nonces or consent-aware storage, but those policies remain
|
|
4267
|
+
application concerns.
|
|
4268
|
+
|
|
4269
|
+
Theme state and theme animation are intentionally separate. `@wrksz/themes`
|
|
4270
|
+
owns selection, resolution, SSR prepaint and persistence. The generated app's
|
|
4271
|
+
`theme/transition.ts` wraps an interactive `setTheme` call with the native View
|
|
4272
|
+
Transition API and exposes typed style, duration, easing and origin settings.
|
|
4273
|
+
The default 250 ms crossfade matches the starter's visual language; the
|
|
4274
|
+
catalogue also demonstrates a radial reveal. The runner bypasses animation when
|
|
4275
|
+
the browser lacks the API or `prefers-reduced-motion: reduce` is active.
|
|
4276
|
+
|
|
4277
|
+
## React Router
|
|
4278
|
+
|
|
4279
|
+
Mount a `createHandler()` result in a catch-all resource route and pass the
|
|
4280
|
+
incoming `Request` through unchanged. Mount MCP as a second resource route. An
|
|
4281
|
+
SSR request creates its own typed client using that request's origin and auth;
|
|
4282
|
+
do not share request identity in a module singleton.
|
|
4283
|
+
|
|
4284
|
+
```ts
|
|
4285
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
4286
|
+
return apiHandler(request);
|
|
4287
|
+
}
|
|
4288
|
+
|
|
4289
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
4290
|
+
return apiHandler(request);
|
|
4291
|
+
}
|
|
4292
|
+
```
|
|
4293
|
+
|
|
4294
|
+
## Vite
|
|
4295
|
+
|
|
4296
|
+
Run Vite and the Stitchkit API as separate development processes. Declare one
|
|
4297
|
+
proxy for `/api`, `/mcp` and `/socket.io`; browser code still calls the typed
|
|
4298
|
+
client with same-origin paths. Production serves the static Vite output from a
|
|
4299
|
+
static host or reverse proxy and routes those backend paths to Stitchkit.
|
|
4300
|
+
|
|
4301
|
+
Do not duplicate DTOs or handwritten API wrappers in either integration. The
|
|
4302
|
+
shared contract remains the only transport schema source.
|
|
4303
|
+
|
|
4304
|
+
|
|
4163
4305
|
==============================================================================
|
|
4164
4306
|
# Guide: Upgrading (docs/guide/upgrading.md)
|
|
4165
4307
|
==============================================================================
|
|
@@ -4228,6 +4370,32 @@ current one *up to* your target, and apply each snippet.
|
|
|
4228
4370
|
|
|
4229
4371
|
## Unreleased breaking migrations
|
|
4230
4372
|
|
|
4373
|
+
HTTP observability now completes inside the framework handler instead of a
|
|
4374
|
+
nested fetch wrapper. Configure request and tool sinks explicitly:
|
|
4375
|
+
|
|
4376
|
+
```ts
|
|
4377
|
+
// before
|
|
4378
|
+
const audit = createAuditHook({ write })
|
|
4379
|
+
createServer({
|
|
4380
|
+
services,
|
|
4381
|
+
wrapFetch: (handler) => wrapInRequestContext(audit.http(handler)),
|
|
4382
|
+
})
|
|
4383
|
+
mountAgent(services, { hooks: audit.toolCall })
|
|
4384
|
+
|
|
4385
|
+
// after
|
|
4386
|
+
const observability = createObservability({
|
|
4387
|
+
request: { write, includePayload: true },
|
|
4388
|
+
tools: { write },
|
|
4389
|
+
})
|
|
4390
|
+
createServer({ services, observability: observability.request })
|
|
4391
|
+
mountAgent(services, { hooks: observability.toolCall })
|
|
4392
|
+
```
|
|
4393
|
+
|
|
4394
|
+
Body capture changed from always-on for body methods to opt-in. Set
|
|
4395
|
+
`includePayload: true` only when the request sink needs the sanitized JSON body.
|
|
4396
|
+
There is no `createAuditHook` or `audit.http` compatibility path;
|
|
4397
|
+
`wrapInRequestContext` remains only for custom fetch pipelines.
|
|
4398
|
+
|
|
4231
4399
|
Tool introspection now accepts one object-shaped contract/runtime surface. Stop
|
|
4232
4400
|
calling the internal contract collector or merging a locally converted runtime
|
|
4233
4401
|
manifest:
|
|
@@ -4679,7 +4847,7 @@ The browser-and-server entrypoint. Re-exports everything from
|
|
|
4679
4847
|
| `contractEndpointMatchers` | function | compile exact pathname matchers for selected HTTP contract operations and expected-401 policy |
|
|
4680
4848
|
| `PathPrefixArgs` | _type_ | required string-valued keys exposed to a typed dynamic `pathPrefix` callback |
|
|
4681
4849
|
| `createHttpClient` | function | the Ky-based HTTP transport — [guide](../guide/client.md#createhttpclient) |
|
|
4682
|
-
| `ApiError` | class | a non-2xx response, with `code` / `status` / `details` / `hint` |
|
|
4850
|
+
| `ApiError` | class | a non-2xx response, with `code` / `status` / `details` / `hint` and optional readonly `traceId` from `x-request-id` |
|
|
4683
4851
|
| `HttpClient` | _type_ | the transport interface `createClient` builds on |
|
|
4684
4852
|
| `ConfiguredHttpClient` | _type_ | a framework-created `HttpClient` carrying its readonly `baseUrl` for URL builders |
|
|
4685
4853
|
| `HttpClientConfig` | _type_ | config for `createHttpClient` |
|
|
@@ -4729,6 +4897,11 @@ from the root `stitchkit`.
|
|
|
4729
4897
|
|--------|------|---------|
|
|
4730
4898
|
| `defineContract` | function | declare a contract — [guide](../guide/contracts.md#definecontract) |
|
|
4731
4899
|
| `createContractFactory` | function | a `defineContract` with a required allowed scope that retains each concrete literal — [guide](../guide/contracts.md#scope) |
|
|
4900
|
+
| `ContractFactoryConfig` | _type_ | optional scoped-factory policy, including explicit tool exposure |
|
|
4901
|
+
| `ContractFactoryToolExposure` | _type_ | `'explicit'` — omitted endpoint exposure materializes as HTTP-only |
|
|
4902
|
+
| `ExplicitScopedDefineContract` | _type_ | scoped factory authoring with explicit tool opt-in |
|
|
4903
|
+
| `ExplicitToolExposureEndpoints` | _type_ | endpoint map after missing exposure is materialized as `['HTTP']` |
|
|
4904
|
+
| `ScopedContractDef` | _type_ | a factory-defined contract whose `meta.scope` is the required concrete literal |
|
|
4732
4905
|
| `ScopedDefineContract` | _type_ | the `defineContract` `createContractFactory` returns |
|
|
4733
4906
|
| `ALL_TRANSPORTS` | constant | `['HTTP', 'MCP', 'AGENT', 'CLI']` |
|
|
4734
4907
|
| `ContractDef` | _type_ | a defined contract |
|
|
@@ -4939,14 +5112,18 @@ Server-only. The audit layer one level above the raw hooks — W3C trace context
|
|
|
4939
5112
|
an `AsyncLocalStorage` request context, payload sanitisation and a normalised
|
|
4940
5113
|
audit event. See the [Observability guide](../guide/observability.md).
|
|
4941
5114
|
|
|
4942
|
-
###
|
|
5115
|
+
### Events
|
|
4943
5116
|
|
|
4944
5117
|
| Export | Kind | Summary |
|
|
4945
5118
|
|--------|------|---------|
|
|
4946
|
-
| `
|
|
5119
|
+
| `createObservability` | function | configure framework-owned request completion and canonical tool event sinks — [guide](../guide/observability.md#createobservability) |
|
|
4947
5120
|
| `RequestEvent` | _type_ | the normalised audit event handed to the sink |
|
|
4948
|
-
| `
|
|
4949
|
-
| `
|
|
5121
|
+
| `ObservabilityConfig` | _type_ | independent request and tool sink configuration |
|
|
5122
|
+
| `Observability` | _type_ | the `{ request?, toolCall }` wiring result |
|
|
5123
|
+
| `RequestEventSinkConfig` | _type_ | `write`, `filter` and sanitisation for one event surface |
|
|
5124
|
+
| `RequestObservabilityConfig` | _type_ | request sink plus opt-in payload capture |
|
|
5125
|
+
| `HttpRequestCompletion` | _type_ | the single framework-owned HTTP outcome projected to logging and request events |
|
|
5126
|
+
| `HttpRequestObserver` | _type_ | server-facing projection consumed by `HandlerConfig.observability` |
|
|
4950
5127
|
|
|
4951
5128
|
### Request context
|
|
4952
5129
|
|
|
@@ -5003,6 +5180,7 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
5003
5180
|
| `implementRemote` | function | bind a contract to a remote HTTP API — [guide](../guide/mcp-and-agents.md#proxying-a-remote-api--implementremote) |
|
|
5004
5181
|
| `mountAgent` | function | a Vercel AI SDK `ToolSet` from a service — [guide](../guide/mcp-and-agents.md#ai-agents--mountagent) |
|
|
5005
5182
|
| `defineRuntimeTool` | function | define one validated pathless operation for MCP, Agent or both — [guide](../guide/mcp-and-agents.md#pathless-runtime-tools-and-multimodal-results) |
|
|
5183
|
+
| `createRuntimeToolFactory` | function | bind shared identity and Zod-validated per-call context for runtime tools — [guide](../guide/mcp-and-agents.md#pathless-runtime-tools-and-multimodal-results) |
|
|
5006
5184
|
| `createToolInvoker` | function | compile an exposure-aware in-process dispatcher over the canonical tool runner — [guide](../guide/mcp-and-agents.md#in-process-calls--createtoolinvoker) |
|
|
5007
5185
|
| `createCli` | function | a command-line program from contracts — [guide](../guide/cli.md) (also on `stitchkit/cli`) |
|
|
5008
5186
|
| `createToolkit` | function | context-typed tool mounts — [guide](../guide/cli.md#typed-context) |
|
|
@@ -5029,6 +5207,12 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
5029
5207
|
| `RuntimeToolDefinitionBase` | _type_ | common name, identity, input, exposure and MCP metadata fields |
|
|
5030
5208
|
| `RuntimeToolDefinitionWithOutput` | _type_ | runtime definition whose handler and presenters share a validated output type |
|
|
5031
5209
|
| `RuntimeToolDefinitionWithoutOutput` | _type_ | runtime definition with a void handler and no presentation callbacks |
|
|
5210
|
+
| `RuntimeToolFactory` | _type_ | identity/context-bound runtime-tool definition factory |
|
|
5211
|
+
| `RuntimeToolFactoryConfig` | _type_ | factory service identity and context schema |
|
|
5212
|
+
| `RuntimeToolFactoryDefinitionWithOutput` | _type_ | factory-authored runtime tool with a validated output schema |
|
|
5213
|
+
| `RuntimeToolFactoryDefinitionWithoutOutput` | _type_ | factory-authored void runtime tool without presenters |
|
|
5214
|
+
| `RuntimeToolFactoryHandlerContext` | _type_ | parsed factory context plus parsed tool input |
|
|
5215
|
+
| `RuntimeToolFactoryIdentityFields` | _type_ | per-tool action, semantic method and optional identity metadata |
|
|
5032
5216
|
| `RuntimeToolIdentity` | _type_ | `{ serviceName, action, scope?, method, meta? }` for runtime lifecycle/audit |
|
|
5033
5217
|
| `RuntimeToolHandlerContext` | _type_ | runtime context with the definition's parsed input |
|
|
5034
5218
|
| `RuntimeToolOutput` | _type_ | output inferred from a runtime tool's optional Zod schema |
|
|
@@ -5133,7 +5317,7 @@ Advanced building blocks — the shared machinery the mounts are built on.
|
|
|
5133
5317
|
| `ToolSurfaceTransport` | _type_ | tool collector transport: `'MCP' \| 'AGENT' \| 'CLI'` |
|
|
5134
5318
|
| `ToolManifestConfig` | _type_ | mixed surface plus required model-facing `transport` and presentation options |
|
|
5135
5319
|
| `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
|
|
5136
|
-
| `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions in a
|
|
5320
|
+
| `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions into conservative object joins; divergent fields retain every provable base kind in a deterministic `type` array, and the projection never executes validation |
|
|
5137
5321
|
| `ToolPresentationSchema` | _type_ | immutable model-facing JSON Schema document shared by tool transports |
|
|
5138
5322
|
| `MountableTool` | _type_ | one operation with separate executable CLI argument schema and model-facing presentation schema |
|
|
5139
5323
|
| `ToolManifestEntry` | _type_ | one `buildToolManifest` row |
|
package/llms.txt
CHANGED
|
@@ -16,6 +16,7 @@ Build with stitchkit: define a contract once, then `implement` it and serve it (
|
|
|
16
16
|
- [Observability](https://github.com/max-listov/stitchkit/blob/master/docs/guide/observability.md): request and tool-call logging via hooks, W3C trace context, createAuditHook
|
|
17
17
|
- [Testing & deployment](https://github.com/max-listov/stitchkit/blob/master/docs/guide/testing-and-deployment.md): in-process testing; deploying on Bun and on Node (serveNode)
|
|
18
18
|
- [Multi-tenant](https://github.com/max-listov/stitchkit/blob/master/docs/guide/multi-tenant.md): a /tenants/:id/… scenario end-to-end — scopePrefixes, scoped client, extend
|
|
19
|
+
- [Frontend integrations](https://github.com/max-listov/stitchkit/blob/master/docs/guide/frontend-integrations.md): React Router resource routes and a separate Vite development proxy
|
|
19
20
|
- [Upgrading](https://github.com/max-listov/stitchkit/blob/master/docs/guide/upgrading.md): moving a project across stitchkit versions; how breaking changes are marked
|
|
20
21
|
|
|
21
22
|
## Reference
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stitchkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.1",
|
|
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",
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"@types/json-schema": "^7.0.15",
|
|
161
161
|
"@types/react": "^19.2.18",
|
|
162
162
|
"@typescript/typescript6": "^6.0.2",
|
|
163
|
-
"ai": "^7.0.
|
|
163
|
+
"ai": "^7.0.58",
|
|
164
164
|
"react": "^19.2.8",
|
|
165
165
|
"react-query-kit": "^3.3.4",
|
|
166
166
|
"socket.io": "^4.8.3",
|