stitchkit 0.42.0 → 0.43.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 +22 -5
- 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 +1 -1
- 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 +39 -1
- package/llms-full.txt +260 -84
- 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
|
|
|
@@ -1664,6 +1681,11 @@ The server side is [`streamSSE`](./server.md#sse-streaming).
|
|
|
1664
1681
|
|
|
1665
1682
|
# MCP & AI agents
|
|
1666
1683
|
|
|
1684
|
+
The application generated by `bun create stitchkit` includes a working
|
|
1685
|
+
stateless MCP endpoint and a separate `bun run tools` manifest command. Both
|
|
1686
|
+
are assembled from the same implemented project contracts as HTTP and CLI, so
|
|
1687
|
+
the generated project is the canonical end-to-end example for this chapter.
|
|
1688
|
+
|
|
1667
1689
|
The same contract that drives the HTTP API also drives AI tooling. An endpoint
|
|
1668
1690
|
exposed on `MCP` becomes a [Model Context Protocol](https://modelcontextprotocol.io)
|
|
1669
1691
|
tool — callable from Claude, Cursor and other MCP clients. An endpoint exposed on
|
|
@@ -1978,8 +2000,14 @@ as one thing. Where they agree, that is what you get; where they disagree — a
|
|
|
1978
2000
|
`.refine()` only one of them carries, two different bounds on the same number, an
|
|
1979
2001
|
enum against a free string — the *constraint* is dropped and the **type** is not.
|
|
1980
2002
|
A field that is a number in every variant is advertised as a number, not as a
|
|
1981
|
-
bare description.
|
|
1982
|
-
|
|
2003
|
+
bare description. When the kinds genuinely differ, the flat projection keeps
|
|
2004
|
+
every provable kind in a deterministic JSON Schema type array — for example
|
|
2005
|
+
`type: ['string', 'array']` — while dropping constraints that are not sound for
|
|
2006
|
+
all branches. Nested `oneOf` / `anyOf` values still contribute their provable
|
|
2007
|
+
base kinds without reintroducing union keywords. Only a branch whose kind is
|
|
2008
|
+
actually unknowable, such as a free-form schema or unresolved reference, leaves
|
|
2009
|
+
the collision unconstrained. → ADRs [0044](../decisions/0044-a-collided-field-keeps-its-type.md)
|
|
2010
|
+
and [0065](../decisions/0065-flat-collisions-preserve-every-known-kind.md)
|
|
1983
2011
|
|
|
1984
2012
|
It is **deep** because the projection walks the generated JSON Schema document,
|
|
1985
2013
|
including objects, arrays, tuples and schema-definition nodes. Structurally
|
|
@@ -1987,6 +2015,10 @@ identifiable discriminated object unions are flattened wherever they occur.
|
|
|
1987
2015
|
Plain unions and unions hidden behind unresolved external references remain
|
|
1988
2016
|
unions because Stitchkit cannot soundly invent a discriminator.
|
|
1989
2017
|
|
|
2018
|
+
Use the default `flattenUnionInput: false` when the model must see the exact
|
|
2019
|
+
relationship between a discriminator and each branch. The flat type array is a
|
|
2020
|
+
sound set of possible JSON kinds, not a reconstruction of those correlations.
|
|
2021
|
+
|
|
1990
2022
|
The flattened form is **lossy but never executable**. Per-variant refinements
|
|
1991
2023
|
and incompatible constraints are widened in the presentation document; the
|
|
1992
2024
|
original Zod contract enforces them exactly once inside `executeToolMethod`.
|
|
@@ -2287,6 +2319,33 @@ const agentTools = mountAgent([service], {
|
|
|
2287
2319
|
})
|
|
2288
2320
|
```
|
|
2289
2321
|
|
|
2322
|
+
When several runtime tools share application context and identity, bind both
|
|
2323
|
+
once with `createRuntimeToolFactory`. Its Zod context schema is parsed once per
|
|
2324
|
+
call inside the same runner; the authored handler receives validated context and
|
|
2325
|
+
parsed `input`, while lifecycle, hooks, output validation and presenters remain
|
|
2326
|
+
unchanged:
|
|
2327
|
+
|
|
2328
|
+
```ts
|
|
2329
|
+
const knowledgeTools = createRuntimeToolFactory({
|
|
2330
|
+
serviceName: 'agentKnowledge',
|
|
2331
|
+
scope: 'user',
|
|
2332
|
+
context: z.object({ userId: z.string(), tz: z.string() }),
|
|
2333
|
+
})
|
|
2334
|
+
|
|
2335
|
+
const countRecords = knowledgeTools.define({
|
|
2336
|
+
name: 'count_records',
|
|
2337
|
+
action: 'countRecords',
|
|
2338
|
+
method: 'GET',
|
|
2339
|
+
description: 'Count records',
|
|
2340
|
+
input: z.object({ kind: z.string() }),
|
|
2341
|
+
output: z.object({ count: z.number() }),
|
|
2342
|
+
handler: async ({ userId, tz, input }) => countFor(userId, tz, input.kind),
|
|
2343
|
+
})
|
|
2344
|
+
```
|
|
2345
|
+
|
|
2346
|
+
`serviceName` and `scope` cannot be overridden by one definition. Use standalone
|
|
2347
|
+
`defineRuntimeTool` when tools do not share a context schema or identity.
|
|
2348
|
+
|
|
2290
2349
|
`transports` defaults to `['MCP', 'AGENT']`; set an explicit subset when an
|
|
2291
2350
|
operation belongs on only one surface. The configured identity becomes the
|
|
2292
2351
|
hook/lifecycle `OperationIdentity` and the tool `RequestEvent`
|
|
@@ -3443,10 +3502,10 @@ stitchkit answers this at two levels.
|
|
|
3443
3502
|
- **The raw hooks** — `LifecycleHooks` and `ToolCallHooks`. Every request and
|
|
3444
3503
|
every tool call passes through a point you can observe. The lowest level;
|
|
3445
3504
|
always available. [Jump ↓](#the-raw-hooks)
|
|
3446
|
-
- **`stitchkit/observability`** —
|
|
3447
|
-
trace context, an `AsyncLocalStorage` request context, payload
|
|
3448
|
-
and `
|
|
3449
|
-
|
|
3505
|
+
- **`stitchkit/observability`** — framework-owned HTTP completion plus canonical
|
|
3506
|
+
tool hooks: W3C trace context, an `AsyncLocalStorage` request context, payload
|
|
3507
|
+
sanitisation and `createObservability` with independent request/tool sinks.
|
|
3508
|
+
[Start here ↓](#the-observability-module)
|
|
3450
3509
|
|
|
3451
3510
|
stitchkit still ships no logger and no audit store — those are the app's choice.
|
|
3452
3511
|
What it ships is the machinery that turns a completed call into a clean,
|
|
@@ -3455,52 +3514,55 @@ normalised record.
|
|
|
3455
3514
|
## The observability module
|
|
3456
3515
|
|
|
3457
3516
|
`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
|
-
|
|
3517
|
+
a request context, and event projections — and you usually touch only the last.
|
|
3518
|
+
|
|
3519
|
+
### createObservability
|
|
3520
|
+
|
|
3521
|
+
`createObservability` configures request and tool projections independently.
|
|
3522
|
+
Every completed call is normalised into one `RequestEvent`; HTTP completion is
|
|
3523
|
+
owned directly by `createHandler`, while MCP/Agent completion uses the canonical
|
|
3524
|
+
`ToolCallHooks` runner. There is no nested HTTP audit wrapper.
|
|
3525
|
+
|
|
3526
|
+
```ts
|
|
3527
|
+
import { createObservability } from 'stitchkit/observability'
|
|
3528
|
+
|
|
3529
|
+
const write = (event) => db.auditLog.create({ data: {
|
|
3530
|
+
traceId: event.traceId,
|
|
3531
|
+
source: event.source,
|
|
3532
|
+
method: event.method,
|
|
3533
|
+
path: event.path,
|
|
3534
|
+
ok: event.ok,
|
|
3535
|
+
statusCode: event.statusCode,
|
|
3536
|
+
durationMs: event.durationMs,
|
|
3537
|
+
userId: event.userId,
|
|
3538
|
+
payload: event.payload,
|
|
3539
|
+
}})
|
|
3540
|
+
|
|
3541
|
+
export const observability = createObservability({
|
|
3542
|
+
request: {
|
|
3543
|
+
write,
|
|
3544
|
+
includePayload: false, // default: no Request.clone(), payload is null
|
|
3545
|
+
filter: (event) => event.method !== 'GET',
|
|
3546
|
+
},
|
|
3547
|
+
tools: {
|
|
3548
|
+
write,
|
|
3549
|
+
filter: (event) => event.source === 'mcp' || event.source === 'agent',
|
|
3484
3550
|
},
|
|
3485
|
-
// Optional — keep only the events you care about.
|
|
3486
|
-
filter: (event) => event.source !== 'http' || event.method !== 'GET',
|
|
3487
3551
|
})
|
|
3488
3552
|
```
|
|
3489
3553
|
|
|
3490
|
-
|
|
3554
|
+
Wire each projection where its completion is owned:
|
|
3491
3555
|
|
|
3492
3556
|
```ts
|
|
3493
|
-
|
|
3494
|
-
Bun.serve({ fetch: wrapInRequestContext(audit.http(handler)) })
|
|
3557
|
+
createServer({ services, observability: observability.request })
|
|
3495
3558
|
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
mountAgent(service, { hooks: audit.toolCall })
|
|
3559
|
+
createMcpHandler({ /* … */ hooks: observability.toolCall })
|
|
3560
|
+
mountAgent(service, { hooks: observability.toolCall })
|
|
3499
3561
|
```
|
|
3500
3562
|
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3563
|
+
Each sink runs fire-and-forget and fails independently: a slow or broken request
|
|
3564
|
+
sink cannot block the response, suppress operational logging or break the tool
|
|
3565
|
+
sink.
|
|
3504
3566
|
|
|
3505
3567
|
### RequestEvent
|
|
3506
3568
|
|
|
@@ -3519,32 +3581,26 @@ queryable across all three:
|
|
|
3519
3581
|
| `ok` / `statusCode` | outcome — real HTTP status, or `200`/`400` for a tool |
|
|
3520
3582
|
| `durationMs` / `startedAt` | timing |
|
|
3521
3583
|
| `errorCode` / `errorMessage` / `errorDetail` | failures only — `errorDetail` carries the structure the message flattens (e.g. Zod issues) |
|
|
3522
|
-
| `payload` |
|
|
3584
|
+
| `payload` | sanitised tool arguments; HTTP is `null` unless request `includePayload` is enabled |
|
|
3523
3585
|
| `resultSize` / `responseBytes` | result item count + serialised size |
|
|
3524
3586
|
| `userId` / `ipAddress` / `userAgent` | identity |
|
|
3525
3587
|
|
|
3526
3588
|
### Request context
|
|
3527
3589
|
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3590
|
+
When request observability is configured, `createHandler` establishes the
|
|
3591
|
+
`AsyncLocalStorage` request context itself and uses the same completion snapshot
|
|
3592
|
+
for operational logging and `RequestEvent`. No `wrapFetch` composition is
|
|
3593
|
+
needed:
|
|
3531
3594
|
|
|
3532
3595
|
```ts
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
Bun.serve({
|
|
3536
|
-
fetch: wrapInRequestContext(audit.http(handler)),
|
|
3537
|
-
})
|
|
3596
|
+
createServer({ services, logging, observability: observability.request })
|
|
3538
3597
|
```
|
|
3539
3598
|
|
|
3540
|
-
`
|
|
3541
|
-
|
|
3599
|
+
`wrapInRequestContext` remains available for a custom fetch pipeline that does
|
|
3600
|
+
not use `createHandler`; it is no longer part of built-in HTTP audit wiring:
|
|
3542
3601
|
|
|
3543
3602
|
```ts
|
|
3544
|
-
|
|
3545
|
-
services,
|
|
3546
|
-
wrapFetch: (fetch) => wrapInRequestContext(audit.http(fetch)),
|
|
3547
|
-
})
|
|
3603
|
+
Bun.serve({ fetch: wrapInRequestContext(customFetch) })
|
|
3548
3604
|
```
|
|
3549
3605
|
|
|
3550
3606
|
Some fields are filled in late. Set them from the hooks that know:
|
|
@@ -3679,8 +3735,8 @@ every tool call underneath it. With no inbound header a fresh root trace is
|
|
|
3679
3735
|
minted. Each tool call opens a [`childSpan`](#trace-context) of the request it
|
|
3680
3736
|
runs in.
|
|
3681
3737
|
|
|
3682
|
-
You rarely call the trace functions directly — `
|
|
3683
|
-
`
|
|
3738
|
+
You rarely call the trace functions directly — `createHandler` request
|
|
3739
|
+
observability and `wrapInRequestContext` use them for you. They are exported (`resolveTraceContext`,
|
|
3684
3740
|
`parseTraceparent`, `formatTraceparent`, `childSpan`) for when you need to
|
|
3685
3741
|
propagate a `traceparent` onward to another service.
|
|
3686
3742
|
|
|
@@ -3715,12 +3771,19 @@ A payload goes into an audit row only after `sanitizePayload`:
|
|
|
3715
3771
|
never the bytes;
|
|
3716
3772
|
- the result is **capped** — anything over the byte limit becomes a preview.
|
|
3717
3773
|
|
|
3718
|
-
`
|
|
3774
|
+
`createObservability` runs it on every emitted event; tune each sink separately:
|
|
3719
3775
|
|
|
3720
3776
|
```ts
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3777
|
+
createObservability({
|
|
3778
|
+
request: {
|
|
3779
|
+
write,
|
|
3780
|
+
includePayload: true,
|
|
3781
|
+
sanitize: { maxBytes: 8_000, sensitiveKeys: /password|token|pin/i },
|
|
3782
|
+
},
|
|
3783
|
+
tools: {
|
|
3784
|
+
write,
|
|
3785
|
+
sanitize: { maxBytes: 8_000, sensitiveKeys: /password|token|pin/i },
|
|
3786
|
+
},
|
|
3724
3787
|
})
|
|
3725
3788
|
```
|
|
3726
3789
|
|
|
@@ -3729,7 +3792,7 @@ need to sanitise something outside the audit path.
|
|
|
3729
3792
|
|
|
3730
3793
|
## The raw hooks
|
|
3731
3794
|
|
|
3732
|
-
|
|
3795
|
+
Tool observability is built on hooks you can also use directly — for a one-off
|
|
3733
3796
|
metric, a custom log line, anything that is not a full audit row.
|
|
3734
3797
|
|
|
3735
3798
|
| Surface | Hook | Fires |
|
|
@@ -3803,7 +3866,7 @@ observe. (This is also why it lives on `ToolCallHooks` rather than being an
|
|
|
3803
3866
|
object must stay assignable to `ToolLifecycle`.)
|
|
3804
3867
|
|
|
3805
3868
|
**Do not reach for `setRequestError` here.** It writes to the *request* context,
|
|
3806
|
-
which
|
|
3869
|
+
which the built-in **tool** row does not read: a tool event takes
|
|
3807
3870
|
`errorCode` / `errorMessage` / `errorDetail` from the `ToolResult`, and only
|
|
3808
3871
|
identity and `dimensions` from the context. Calling it in `onToolError` would
|
|
3809
3872
|
leave the tool row exactly as scrubbed as before. It is right for the **HTTP**
|
|
@@ -3829,7 +3892,7 @@ validation failure or a `beforeToolCall` rejection leaves it `undefined`, becaus
|
|
|
3829
3892
|
neither ever had a raw value to lose. Consumers destructure only the fields they
|
|
3830
3893
|
use; future optional fields do not change callback arity.
|
|
3831
3894
|
|
|
3832
|
-
`
|
|
3895
|
+
`createObservability({ tools })` uses it already. Where the envelope was scrubbed to
|
|
3833
3896
|
`INTERNAL_SERVER_ERROR`, the row's `errorMessage` becomes the real message
|
|
3834
3897
|
instead of the placeholder; a truthful envelope (a thrown `AppError`, a
|
|
3835
3898
|
`ZodError`) is left alone, `errorCode` and `errorDetail` are untouched, and the
|
|
@@ -3842,7 +3905,7 @@ sink of your own (a tracker, a stack, an alert), `afterToolCall` for the record.
|
|
|
3842
3905
|
|
|
3843
3906
|
### Keying a row on (service, action)
|
|
3844
3907
|
|
|
3845
|
-
|
|
3908
|
+
Built-in observability keys every event by **service** and **action**
|
|
3846
3909
|
(`event.serviceName` / `event.action`, → ADR 0029) — reach for the raw hook only
|
|
3847
3910
|
when you also need the handler **output**, which the audit wrapper never sees. For
|
|
3848
3911
|
that, read the endpoint identity off the `OperationIdentity` the tool hook
|
|
@@ -3870,11 +3933,11 @@ hooks: {
|
|
|
3870
3933
|
}
|
|
3871
3934
|
```
|
|
3872
3935
|
|
|
3873
|
-
> **Why
|
|
3874
|
-
> has a single `onError
|
|
3875
|
-
>
|
|
3876
|
-
>
|
|
3877
|
-
>
|
|
3936
|
+
> **Why HTTP observability is framework-owned, not a lifecycle hook.**
|
|
3937
|
+
> `LifecycleHooks` has a single `onError`; an audit built on it would compete
|
|
3938
|
+
> with the app's error renderer and miss raw/unmatched exits. `createHandler`
|
|
3939
|
+
> sees the final response on every path and emits one completion without
|
|
3940
|
+
> consuming an application hook.
|
|
3878
3941
|
|
|
3879
3942
|
Keep any sink **asynchronous and self-contained**: a slow or failing write must
|
|
3880
3943
|
never block or break the request. Swallow the sink's own errors.
|
|
@@ -4023,7 +4086,8 @@ Notes for a Node host:
|
|
|
4023
4086
|
stitchkit serves the API. A SPA front-end is built and hosted separately — a
|
|
4024
4087
|
static host or CDN in production, its own dev server in development. The backend
|
|
4025
4088
|
does not serve static files (`staticRoute` exists for the occasional asset, not
|
|
4026
|
-
a whole app).
|
|
4089
|
+
a whole app). `bun create stitchkit my-app` demonstrates the supported split:
|
|
4090
|
+
an independently built Next.js frontend and Bun/Stitchkit API.
|
|
4027
4091
|
|
|
4028
4092
|
### MCP
|
|
4029
4093
|
|
|
@@ -4160,6 +4224,76 @@ rule read it the same way on both surfaces — one contract, every surface, no
|
|
|
4160
4224
|
per-transport tenant plumbing.
|
|
4161
4225
|
|
|
4162
4226
|
|
|
4227
|
+
==============================================================================
|
|
4228
|
+
# Guide: Frontend integrations (docs/guide/frontend-integrations.md)
|
|
4229
|
+
==============================================================================
|
|
4230
|
+
|
|
4231
|
+
---
|
|
4232
|
+
title: Frontend integrations
|
|
4233
|
+
description: Compose Stitchkit with Next.js, React Router or a separate Vite development server
|
|
4234
|
+
type: architecture
|
|
4235
|
+
status: active
|
|
4236
|
+
created: 2026-08-08
|
|
4237
|
+
updated: 2026-08-08
|
|
4238
|
+
---
|
|
4239
|
+
|
|
4240
|
+
# Frontend integrations
|
|
4241
|
+
|
|
4242
|
+
The official `bun create stitchkit` application uses Next.js with a separate
|
|
4243
|
+
Bun API. Stitchkit remains a Fetch-native backend and does not own frontend
|
|
4244
|
+
routing, SSR or HMR.
|
|
4245
|
+
|
|
4246
|
+
## Theme boundary in the official starter
|
|
4247
|
+
|
|
4248
|
+
The generated Next.js application uses `@wrksz/themes`, not a Stitchkit-owned
|
|
4249
|
+
theme abstraction. Its root `ThemeProvider` comes from `@wrksz/themes/next` and
|
|
4250
|
+
lives directly in the server layout so Next 16 can inject the first-paint script
|
|
4251
|
+
through `useServerInsertedHTML`. The default `hybrid` storage reads a cookie
|
|
4252
|
+
during SSR and mirrors changes to localStorage for cross-tab synchronization.
|
|
4253
|
+
|
|
4254
|
+
Client components import typed hooks from the fine-grained
|
|
4255
|
+
`@wrksz/themes/client/*` entrypoints. Nested visual examples use
|
|
4256
|
+
`ClientThemeProvider` with a scoped target and `storage="none"`; they never
|
|
4257
|
+
become a second global provider. Applications may add account-backed theme
|
|
4258
|
+
preferences, CSP nonces or consent-aware storage, but those policies remain
|
|
4259
|
+
application concerns.
|
|
4260
|
+
|
|
4261
|
+
Theme state and theme animation are intentionally separate. `@wrksz/themes`
|
|
4262
|
+
owns selection, resolution, SSR prepaint and persistence. The generated app's
|
|
4263
|
+
`theme/transition.ts` wraps an interactive `setTheme` call with the native View
|
|
4264
|
+
Transition API and exposes typed style, duration, easing and origin settings.
|
|
4265
|
+
The default 250 ms crossfade matches the starter's visual language; the
|
|
4266
|
+
catalogue also demonstrates a radial reveal. The runner bypasses animation when
|
|
4267
|
+
the browser lacks the API or `prefers-reduced-motion: reduce` is active.
|
|
4268
|
+
|
|
4269
|
+
## React Router
|
|
4270
|
+
|
|
4271
|
+
Mount a `createHandler()` result in a catch-all resource route and pass the
|
|
4272
|
+
incoming `Request` through unchanged. Mount MCP as a second resource route. An
|
|
4273
|
+
SSR request creates its own typed client using that request's origin and auth;
|
|
4274
|
+
do not share request identity in a module singleton.
|
|
4275
|
+
|
|
4276
|
+
```ts
|
|
4277
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
4278
|
+
return apiHandler(request);
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
4282
|
+
return apiHandler(request);
|
|
4283
|
+
}
|
|
4284
|
+
```
|
|
4285
|
+
|
|
4286
|
+
## Vite
|
|
4287
|
+
|
|
4288
|
+
Run Vite and the Stitchkit API as separate development processes. Declare one
|
|
4289
|
+
proxy for `/api`, `/mcp` and `/socket.io`; browser code still calls the typed
|
|
4290
|
+
client with same-origin paths. Production serves the static Vite output from a
|
|
4291
|
+
static host or reverse proxy and routes those backend paths to Stitchkit.
|
|
4292
|
+
|
|
4293
|
+
Do not duplicate DTOs or handwritten API wrappers in either integration. The
|
|
4294
|
+
shared contract remains the only transport schema source.
|
|
4295
|
+
|
|
4296
|
+
|
|
4163
4297
|
==============================================================================
|
|
4164
4298
|
# Guide: Upgrading (docs/guide/upgrading.md)
|
|
4165
4299
|
==============================================================================
|
|
@@ -4228,6 +4362,32 @@ current one *up to* your target, and apply each snippet.
|
|
|
4228
4362
|
|
|
4229
4363
|
## Unreleased breaking migrations
|
|
4230
4364
|
|
|
4365
|
+
HTTP observability now completes inside the framework handler instead of a
|
|
4366
|
+
nested fetch wrapper. Configure request and tool sinks explicitly:
|
|
4367
|
+
|
|
4368
|
+
```ts
|
|
4369
|
+
// before
|
|
4370
|
+
const audit = createAuditHook({ write })
|
|
4371
|
+
createServer({
|
|
4372
|
+
services,
|
|
4373
|
+
wrapFetch: (handler) => wrapInRequestContext(audit.http(handler)),
|
|
4374
|
+
})
|
|
4375
|
+
mountAgent(services, { hooks: audit.toolCall })
|
|
4376
|
+
|
|
4377
|
+
// after
|
|
4378
|
+
const observability = createObservability({
|
|
4379
|
+
request: { write, includePayload: true },
|
|
4380
|
+
tools: { write },
|
|
4381
|
+
})
|
|
4382
|
+
createServer({ services, observability: observability.request })
|
|
4383
|
+
mountAgent(services, { hooks: observability.toolCall })
|
|
4384
|
+
```
|
|
4385
|
+
|
|
4386
|
+
Body capture changed from always-on for body methods to opt-in. Set
|
|
4387
|
+
`includePayload: true` only when the request sink needs the sanitized JSON body.
|
|
4388
|
+
There is no `createAuditHook` or `audit.http` compatibility path;
|
|
4389
|
+
`wrapInRequestContext` remains only for custom fetch pipelines.
|
|
4390
|
+
|
|
4231
4391
|
Tool introspection now accepts one object-shaped contract/runtime surface. Stop
|
|
4232
4392
|
calling the internal contract collector or merging a locally converted runtime
|
|
4233
4393
|
manifest:
|
|
@@ -4729,6 +4889,11 @@ from the root `stitchkit`.
|
|
|
4729
4889
|
|--------|------|---------|
|
|
4730
4890
|
| `defineContract` | function | declare a contract — [guide](../guide/contracts.md#definecontract) |
|
|
4731
4891
|
| `createContractFactory` | function | a `defineContract` with a required allowed scope that retains each concrete literal — [guide](../guide/contracts.md#scope) |
|
|
4892
|
+
| `ContractFactoryConfig` | _type_ | optional scoped-factory policy, including explicit tool exposure |
|
|
4893
|
+
| `ContractFactoryToolExposure` | _type_ | `'explicit'` — omitted endpoint exposure materializes as HTTP-only |
|
|
4894
|
+
| `ExplicitScopedDefineContract` | _type_ | scoped factory authoring with explicit tool opt-in |
|
|
4895
|
+
| `ExplicitToolExposureEndpoints` | _type_ | endpoint map after missing exposure is materialized as `['HTTP']` |
|
|
4896
|
+
| `ScopedContractDef` | _type_ | a factory-defined contract whose `meta.scope` is the required concrete literal |
|
|
4732
4897
|
| `ScopedDefineContract` | _type_ | the `defineContract` `createContractFactory` returns |
|
|
4733
4898
|
| `ALL_TRANSPORTS` | constant | `['HTTP', 'MCP', 'AGENT', 'CLI']` |
|
|
4734
4899
|
| `ContractDef` | _type_ | a defined contract |
|
|
@@ -4939,14 +5104,18 @@ Server-only. The audit layer one level above the raw hooks — W3C trace context
|
|
|
4939
5104
|
an `AsyncLocalStorage` request context, payload sanitisation and a normalised
|
|
4940
5105
|
audit event. See the [Observability guide](../guide/observability.md).
|
|
4941
5106
|
|
|
4942
|
-
###
|
|
5107
|
+
### Events
|
|
4943
5108
|
|
|
4944
5109
|
| Export | Kind | Summary |
|
|
4945
5110
|
|--------|------|---------|
|
|
4946
|
-
| `
|
|
5111
|
+
| `createObservability` | function | configure framework-owned request completion and canonical tool event sinks — [guide](../guide/observability.md#createobservability) |
|
|
4947
5112
|
| `RequestEvent` | _type_ | the normalised audit event handed to the sink |
|
|
4948
|
-
| `
|
|
4949
|
-
| `
|
|
5113
|
+
| `ObservabilityConfig` | _type_ | independent request and tool sink configuration |
|
|
5114
|
+
| `Observability` | _type_ | the `{ request?, toolCall }` wiring result |
|
|
5115
|
+
| `RequestEventSinkConfig` | _type_ | `write`, `filter` and sanitisation for one event surface |
|
|
5116
|
+
| `RequestObservabilityConfig` | _type_ | request sink plus opt-in payload capture |
|
|
5117
|
+
| `HttpRequestCompletion` | _type_ | the single framework-owned HTTP outcome projected to logging and request events |
|
|
5118
|
+
| `HttpRequestObserver` | _type_ | server-facing projection consumed by `HandlerConfig.observability` |
|
|
4950
5119
|
|
|
4951
5120
|
### Request context
|
|
4952
5121
|
|
|
@@ -5003,6 +5172,7 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
5003
5172
|
| `implementRemote` | function | bind a contract to a remote HTTP API — [guide](../guide/mcp-and-agents.md#proxying-a-remote-api--implementremote) |
|
|
5004
5173
|
| `mountAgent` | function | a Vercel AI SDK `ToolSet` from a service — [guide](../guide/mcp-and-agents.md#ai-agents--mountagent) |
|
|
5005
5174
|
| `defineRuntimeTool` | function | define one validated pathless operation for MCP, Agent or both — [guide](../guide/mcp-and-agents.md#pathless-runtime-tools-and-multimodal-results) |
|
|
5175
|
+
| `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
5176
|
| `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
5177
|
| `createCli` | function | a command-line program from contracts — [guide](../guide/cli.md) (also on `stitchkit/cli`) |
|
|
5008
5178
|
| `createToolkit` | function | context-typed tool mounts — [guide](../guide/cli.md#typed-context) |
|
|
@@ -5029,6 +5199,12 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
5029
5199
|
| `RuntimeToolDefinitionBase` | _type_ | common name, identity, input, exposure and MCP metadata fields |
|
|
5030
5200
|
| `RuntimeToolDefinitionWithOutput` | _type_ | runtime definition whose handler and presenters share a validated output type |
|
|
5031
5201
|
| `RuntimeToolDefinitionWithoutOutput` | _type_ | runtime definition with a void handler and no presentation callbacks |
|
|
5202
|
+
| `RuntimeToolFactory` | _type_ | identity/context-bound runtime-tool definition factory |
|
|
5203
|
+
| `RuntimeToolFactoryConfig` | _type_ | factory service identity and context schema |
|
|
5204
|
+
| `RuntimeToolFactoryDefinitionWithOutput` | _type_ | factory-authored runtime tool with a validated output schema |
|
|
5205
|
+
| `RuntimeToolFactoryDefinitionWithoutOutput` | _type_ | factory-authored void runtime tool without presenters |
|
|
5206
|
+
| `RuntimeToolFactoryHandlerContext` | _type_ | parsed factory context plus parsed tool input |
|
|
5207
|
+
| `RuntimeToolFactoryIdentityFields` | _type_ | per-tool action, semantic method and optional identity metadata |
|
|
5032
5208
|
| `RuntimeToolIdentity` | _type_ | `{ serviceName, action, scope?, method, meta? }` for runtime lifecycle/audit |
|
|
5033
5209
|
| `RuntimeToolHandlerContext` | _type_ | runtime context with the definition's parsed input |
|
|
5034
5210
|
| `RuntimeToolOutput` | _type_ | output inferred from a runtime tool's optional Zod schema |
|
|
@@ -5133,7 +5309,7 @@ Advanced building blocks — the shared machinery the mounts are built on.
|
|
|
5133
5309
|
| `ToolSurfaceTransport` | _type_ | tool collector transport: `'MCP' \| 'AGENT' \| 'CLI'` |
|
|
5134
5310
|
| `ToolManifestConfig` | _type_ | mixed surface plus required model-facing `transport` and presentation options |
|
|
5135
5311
|
| `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
|
|
5136
|
-
| `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions in a
|
|
5312
|
+
| `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
5313
|
| `ToolPresentationSchema` | _type_ | immutable model-facing JSON Schema document shared by tool transports |
|
|
5138
5314
|
| `MountableTool` | _type_ | one operation with separate executable CLI argument schema and model-facing presentation schema |
|
|
5139
5315
|
| `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.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",
|
|
@@ -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",
|