stitchkit 0.89.0 → 0.90.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/CHANGELOG.md +56 -0
- package/dist/application/watch-hub.d.ts +24 -2
- package/dist/application/watch-hub.d.ts.map +1 -1
- package/dist/application.js +101 -11
- package/dist/index-2na0qrfn.js +214 -0
- package/dist/live/watch-client.d.ts +2 -1
- package/dist/live/watch-client.d.ts.map +1 -1
- package/dist/live/watch-contract.d.ts +70 -4
- package/dist/live/watch-contract.d.ts.map +1 -1
- package/dist/live/watch-delta.d.ts +110 -0
- package/dist/live/watch-delta.d.ts.map +1 -0
- package/dist/live.d.ts +2 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +56 -4
- package/dist/observability/changes.d.ts +55 -0
- package/dist/observability/changes.d.ts.map +1 -0
- package/dist/observability/index.d.ts +2 -0
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +127 -0
- package/dist/observability/spool.d.ts +53 -0
- package/dist/observability/spool.d.ts.map +1 -0
- package/llms-full.txt +143 -12
- package/package.json +1 -1
- package/dist/index-dre2ywck.js +0 -48
package/llms-full.txt
CHANGED
|
@@ -48,7 +48,7 @@ own, recorded as an ADR.
|
|
|
48
48
|
|--------|--------|----------|-------|
|
|
49
49
|
| `stitchkit` | browser **and** server | stable | `defineContract`, `createClient`, `createHttpClient`, `createSocketIOClient`, `parseSSE`, the error model |
|
|
50
50
|
| `stitchkit/contract` | browser **and** server | stable | the contract layer alone — `defineContract`, errors, pagination |
|
|
51
|
-
| `stitchkit/live` | browser **and** server | evolving | `defineEvents` — topic declarations beside the operation contract
|
|
51
|
+
| `stitchkit/live` | browser **and** server | evolving | `defineEvents` — topic declarations beside the operation contract — and watched reads: `createWatchClient`, the value/difference frame and its reassembly |
|
|
52
52
|
| `stitchkit/primitives` | browser **and** server | evolving | generic values and declarations for lifecycle, access, audit, delivery and exports |
|
|
53
53
|
| `stitchkit/server` | server (Bun) | stable | `createServer`, `implement`, hooks, auth, Socket.IO server, server primitives |
|
|
54
54
|
| `stitchkit/node` | server (Node ≥ 22) | stable | `serveNode` + the runtime-agnostic core — the Node mirror of `/server` |
|
|
@@ -64,11 +64,11 @@ own, recorded as an ADR.
|
|
|
64
64
|
| `stitchkit/tracking/server` | server (Bun or Node) | evolving | the decisions a tracking backend makes — dispositions, visit lease over an application-owned store, active intervals, presence; no database |
|
|
65
65
|
| `stitchkit/release` | browser **and** server | evolving | a page follows the release it was built for — `createReleaseMarker` on the server, `createReleaseWatcher` in the browser, the `X-Build-Id` header and a socket event between them |
|
|
66
66
|
| `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
|
|
67
|
-
| `stitchkit/observability` | server | stable<br>_redefined in 1 of the
|
|
67
|
+
| `stitchkit/observability` | server | stable<br>_redefined in 1 of the 35 minors since 0.56.2, most recently 0.83.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
|
|
68
68
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
|
|
69
69
|
| `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
70
70
|
| `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
|
|
71
|
-
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 20 of the
|
|
71
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 20 of the 35 minors since 0.56.2, most recently 0.89.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
72
72
|
| `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
|
|
73
73
|
| `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults and deterministic race controls |
|
|
74
74
|
| `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
|
|
@@ -78,7 +78,7 @@ own, recorded as an ADR.
|
|
|
78
78
|
| `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
|
|
79
79
|
| `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
|
|
80
80
|
| `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
|
|
81
|
-
| `stitchkit/application` | browser + server | evolving<br>_redefined in 7 of the
|
|
81
|
+
| `stitchkit/application` | browser + server | evolving<br>_redefined in 7 of the 35 minors since 0.56.2, most recently 0.83.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
|
|
82
82
|
| `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
|
|
83
83
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
84
84
|
| `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
|
|
@@ -8810,13 +8810,49 @@ Nothing an open can fail with escapes as a rejected promise: a disconnected
|
|
|
8810
8810
|
socket, a timeout, a refusal all arrive as `unavailable` carrying the error's own
|
|
8811
8811
|
code and message, and the next connection retries.
|
|
8812
8812
|
|
|
8813
|
+
The re-open carries what the client still holds, so a reconnection is usually
|
|
8814
|
+
cheap: the hub answers with a difference, or with nothing at all when the answer
|
|
8815
|
+
has not moved. That needs the key to still exist on the server — set `holdMs`
|
|
8816
|
+
past your reconnect delay, or the last detach releases the source and the page
|
|
8817
|
+
pays the whole value once.
|
|
8818
|
+
|
|
8819
|
+
### Large answers cross as differences
|
|
8820
|
+
|
|
8821
|
+
A frame carries the value, a **difference** to a revision this subscriber already
|
|
8822
|
+
holds, or `unchanged`. The hub chooses per subscriber and sends a difference only
|
|
8823
|
+
when it is genuinely smaller; your `value` listener sees the rebuilt value either
|
|
8824
|
+
way and needs no code for this.
|
|
8825
|
+
|
|
8826
|
+
It matters when an answer is large and moves a little: a ~75 KB list in which two
|
|
8827
|
+
timestamps change, republished every fifteen seconds, was a megabyte per
|
|
8828
|
+
subscriber per minute before and is a frame under a kilobyte now.
|
|
8829
|
+
|
|
8830
|
+
```ts
|
|
8831
|
+
createWatchHub({
|
|
8832
|
+
// Superseded values kept per key, so a difference has something to be taken
|
|
8833
|
+
// against. Default 262144. Set 0 to send whole values only.
|
|
8834
|
+
deltaMemoryBytes: 256 * 1024,
|
|
8835
|
+
holdMs: 30_000,
|
|
8836
|
+
// …
|
|
8837
|
+
});
|
|
8838
|
+
```
|
|
8839
|
+
|
|
8840
|
+
Reassembly is checked against the server's fingerprint on every frame. If a
|
|
8841
|
+
difference will not apply — a client that missed a revision, a hub that restarted
|
|
8842
|
+
— that **one key** resynchronises: your `state` listener sees `resync-required`
|
|
8843
|
+
and the whole value follows. Other keys on the same socket are untouched.
|
|
8844
|
+
|
|
8845
|
+
Both ends must come from the same major release: a client older than 0.90 reads a
|
|
8846
|
+
difference frame as a value of `undefined`.
|
|
8847
|
+
|
|
8813
8848
|
### `watch` or `createLiveStateController`?
|
|
8814
8849
|
|
|
8815
8850
|
> **If you would have written `applyEvent` as `(_, next) => next`, you want
|
|
8816
|
-
> `watch`:
|
|
8817
|
-
>
|
|
8818
|
-
>
|
|
8819
|
-
> applying both to one value is always a
|
|
8851
|
+
> `watch`: whatever crosses the wire, what your `value` listener receives is the
|
|
8852
|
+
> whole answer.** `createLiveStateController` is for a server whose *protocol* is
|
|
8853
|
+
> deltas that your code folds. `watch` may send a difference too, but it folds it
|
|
8854
|
+
> for you and hands you the value — so applying both to one value is always a
|
|
8855
|
+
> mistake.
|
|
8820
8856
|
|
|
8821
8857
|
### What it cannot promise
|
|
8822
8858
|
|
|
@@ -9746,9 +9782,10 @@ stitchkit answers this at two levels.
|
|
|
9746
9782
|
sanitisation and `createObservability` with independent request/tool sinks.
|
|
9747
9783
|
[Start here ↓](#the-observability-module)
|
|
9748
9784
|
|
|
9749
|
-
stitchkit still ships no logger and no audit store
|
|
9750
|
-
|
|
9751
|
-
normalised record
|
|
9785
|
+
stitchkit still ships no logger and no audit store, and auditing stays opt-in —
|
|
9786
|
+
those are the app's choice (→ ADR 0012, reaffirmed in ADR 0184). What it ships is
|
|
9787
|
+
the machinery that turns a completed call into a clean, normalised record, keeps
|
|
9788
|
+
the ones worth keeping, and does not lose them when the store is down.
|
|
9752
9789
|
|
|
9753
9790
|
## The observability module
|
|
9754
9791
|
|
|
@@ -9985,6 +10022,69 @@ queryable across all three:
|
|
|
9985
10022
|
| `resultSize` / `responseBytes` | result item count + serialised size |
|
|
9986
10023
|
| `userId` / `ipAddress` / `userAgent` | identity |
|
|
9987
10024
|
|
|
10025
|
+
### Write the calls that changed something
|
|
10026
|
+
|
|
10027
|
+
`auditChanges` is the filter most projects end up writing, shipped so they do not
|
|
10028
|
+
have to write it six ways:
|
|
10029
|
+
|
|
10030
|
+
```ts
|
|
10031
|
+
import { auditChanges, createObservability } from 'stitchkit/observability';
|
|
10032
|
+
|
|
10033
|
+
createObservability({
|
|
10034
|
+
request: { write: saveAuditRow, filter: auditChanges },
|
|
10035
|
+
tools: { write: saveAuditRow, filter: auditChanges },
|
|
10036
|
+
});
|
|
10037
|
+
```
|
|
10038
|
+
|
|
10039
|
+
It drops `GET`, `HEAD` and `OPTIONS`, keeps everything else, and keeps `401` and
|
|
10040
|
+
`403` **whatever the verb was** — a refused read is the row an audit exists to
|
|
10041
|
+
hold, and a filter that drops every `GET` drops exactly that. An unrecognised
|
|
10042
|
+
verb is kept: an extra row costs bytes, a missing one costs the answer to "who
|
|
10043
|
+
changed this", silently and only later.
|
|
10044
|
+
|
|
10045
|
+
One filter across HTTP, MCP and agent calls, because a tool call carries its
|
|
10046
|
+
contract verb in `httpMethod` while its `method` is the literal `TOOL`.
|
|
10047
|
+
|
|
10048
|
+
Narrower policy stays yours — compose it:
|
|
10049
|
+
|
|
10050
|
+
```ts
|
|
10051
|
+
filter: (event) => auditChanges(event) && event.serviceName !== 'health',
|
|
10052
|
+
```
|
|
10053
|
+
|
|
10054
|
+
### Keeping the row when the store is down
|
|
10055
|
+
|
|
10056
|
+
A sink is fire-and-forget, so an unreachable store means the event is simply
|
|
10057
|
+
gone — and a store is most likely to be unreachable during an incident, which is
|
|
10058
|
+
the window whose rows someone will later want most.
|
|
10059
|
+
|
|
10060
|
+
`createSpooledSink` writes the row to a local append-only file first, offers it
|
|
10061
|
+
to the store second, and marks it delivered third. What a previous process left
|
|
10062
|
+
unmarked is replayed:
|
|
10063
|
+
|
|
10064
|
+
```ts
|
|
10065
|
+
import { createSpooledSink } from 'stitchkit/observability';
|
|
10066
|
+
|
|
10067
|
+
const audit = createSpooledSink({
|
|
10068
|
+
path: '/var/lib/app/audit.ndjson',
|
|
10069
|
+
write: saveAuditRow,
|
|
10070
|
+
});
|
|
10071
|
+
|
|
10072
|
+
// Once, at startup, before the sink is wired — and await it.
|
|
10073
|
+
const { replayed, failed } = await audit.recover();
|
|
10074
|
+
|
|
10075
|
+
createObservability({ request: { write: audit.write, filter: auditChanges } });
|
|
10076
|
+
```
|
|
10077
|
+
|
|
10078
|
+
The guarantee is **at least once**. A crash between the store accepting a row and
|
|
10079
|
+
the file recording that it did replays the row, so **the store must be idempotent
|
|
10080
|
+
on the record key** — `event.spanId` by default, unique per call. A unique index
|
|
10081
|
+
on it turns the duplicate into a no-op; without one, replay writes the row twice.
|
|
10082
|
+
Exactly-once would need the file and the database to share a transaction. They do
|
|
10083
|
+
not.
|
|
10084
|
+
|
|
10085
|
+
One process, one path. Two processes pointed at the same file replay each other's
|
|
10086
|
+
records: harmless against an idempotent store, wasteful always.
|
|
10087
|
+
|
|
9988
10088
|
### Request context
|
|
9989
10089
|
|
|
9990
10090
|
When request observability is configured, `createHandler` establishes the
|
|
@@ -11382,6 +11482,32 @@ analytics policy belong to the application.
|
|
|
11382
11482
|
|
|
11383
11483
|
# Upgrading stitchkit
|
|
11384
11484
|
|
|
11485
|
+
## Released migration: 0.90.0
|
|
11486
|
+
|
|
11487
|
+
1. `stitchkit.watch.value` is a discriminated union on `kind` — `full`, `delta`,
|
|
11488
|
+
`unchanged` — and every frame carries `fingerprint`. **Both ends must come from
|
|
11489
|
+
the same major.** A client older than this release reads a `delta` frame as a
|
|
11490
|
+
value of `undefined`, silently; upgrade the hub and the pages that talk to it
|
|
11491
|
+
together, or set `deltaMemoryBytes: 0` on the hub until they are.
|
|
11492
|
+
|
|
11493
|
+
Applications using `createWatchClient` need no code change: it rebuilds the
|
|
11494
|
+
value and hands the listener the whole answer as before. Code that reads
|
|
11495
|
+
`WatchValueFrame.value` directly — a hand-written subscriber, a test double —
|
|
11496
|
+
narrows on `kind` first.
|
|
11497
|
+
|
|
11498
|
+
```ts
|
|
11499
|
+
// before: frame.value
|
|
11500
|
+
// after:
|
|
11501
|
+
if (frame.kind === 'full') hold(frame.value);
|
|
11502
|
+
else if (frame.kind === 'delta') hold(applyWatchDelta(held, frame.delta));
|
|
11503
|
+
// 'unchanged' leaves what you hold standing
|
|
11504
|
+
```
|
|
11505
|
+
|
|
11506
|
+
2. `AttachedWatcher.open` takes an optional third argument, `have`. A custom
|
|
11507
|
+
server binding that forwards `stitchkit.watch.open` should pass the payload's
|
|
11508
|
+
`have` through; omitting it costs a whole value on every reconnection and is
|
|
11509
|
+
otherwise harmless.
|
|
11510
|
+
|
|
11385
11511
|
## Released migration: 0.89.0
|
|
11386
11512
|
|
|
11387
11513
|
1. Custom `AgentRuntimeStore` adapters implement `seedConversationInput`; custom
|
|
@@ -15121,7 +15247,10 @@ realtime contract from `stitchkit`, and the server halves live in `stitchkit/app
|
|
|
15121
15247
|
| `watchContract` | const | the four-event realtime contract a watched read travels on |
|
|
15122
15248
|
| `WATCH_OPEN` / `WATCH_CLOSE` / `WATCH_VALUE` / `WATCH_STATE` | const | the event names of that contract |
|
|
15123
15249
|
| `WatchKey` / `WatchKeySchema` / `watchKeyString` | type / schema / function | `(service, action, arguments digest)` — the identity both ends compute the same way |
|
|
15124
|
-
| `WatchValueFrame` / `WatchValueSchema` / `WatchStateFrame` / `WatchStateSchema` | _types_ |
|
|
15250
|
+
| `WatchValueFrame` / `WatchValueSchema` / `WatchStateFrame` / `WatchStateSchema` | _types_ | one answer — `full`, `delta` or `unchanged`, with its monotonic revision and fingerprint — and a phase from `LiveStatePhase` with the read's own code and message |
|
|
15251
|
+
| `WatchHave` / `WatchHaveSchema` | type / schema | what a reconnecting subscriber already holds, offered on `open`: a revision and the fingerprint that proves it |
|
|
15252
|
+
| `WatchDelta` / `WatchDeltaSchema` / `WatchArrayOp` / `WatchArrayOpSchema` | _types_ / schemas | a structural difference over parsed JSON — objects by changed and dropped key, arrays by runs copied from the previous value |
|
|
15253
|
+
| `watchDiff` / `applyWatchDelta` / `watchDeltaWins` | functions | build a difference (`undefined` when equal), rebuild the value from one (throws rather than inventing), and whether it is actually smaller than the value |
|
|
15125
15254
|
|
|
15126
15255
|
---
|
|
15127
15256
|
|
|
@@ -16142,6 +16271,8 @@ audit event. See the [Observability guide](../guide/observability.md).
|
|
|
16142
16271
|
| Export | Kind | Summary |
|
|
16143
16272
|
|--------|------|---------|
|
|
16144
16273
|
| `createObservability` | function | configure framework-owned request completion and canonical tool event sinks — [guide](../guide/observability.md#createobservability) |
|
|
16274
|
+
| `auditChanges` | function | the sink `filter` most projects write themselves — drops `GET`/`HEAD`/`OPTIONS`, keeps `401`/`403` whatever the verb, keeps an unrecognised verb |
|
|
16275
|
+
| `createSpooledSink` / `SpooledSink` / `SpooledSinkConfig` / `SpoolRecovery` | function / _types_ | spool the row to an append-only file before the store and replay what a previous process left undelivered — at least once, keyed on `spanId` |
|
|
16145
16276
|
| `createDimensionsProjector` / `DimensionsProjector` / `DimensionsProjectorConfig` / `ProjectedDimensions` | function / _type_ | typed request/result/error attribution projected into the existing request context |
|
|
16146
16277
|
| `DimensionCollision` / `SetRequestDimensionsOptions` | _type_ | explicit overwrite, preserve or error policy for dimension keys |
|
|
16147
16278
|
| `createBoundedLogger` | function | decorate a `StitchLogger` with request context, shared sanitisation, redaction and total bounds |
|
package/package.json
CHANGED
package/dist/index-dre2ywck.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LiveStatePhaseSchema,
|
|
3
|
-
LiveStateStopReasonSchema
|
|
4
|
-
} from "./index-ywd0y6m1.js";
|
|
5
|
-
|
|
6
|
-
// src/live/watch-contract.ts
|
|
7
|
-
import { z } from "zod";
|
|
8
|
-
var WatchKeySchema = z.object({
|
|
9
|
-
service: z.string().min(1),
|
|
10
|
-
action: z.string().min(1),
|
|
11
|
-
digest: z.string().min(1)
|
|
12
|
-
}).strict().readonly();
|
|
13
|
-
var WatchOpenSchema = z.object({ key: WatchKeySchema, args: z.unknown() }).readonly();
|
|
14
|
-
var WatchAcceptedSchema = z.object({
|
|
15
|
-
accepted: z.boolean(),
|
|
16
|
-
reason: z.string().optional()
|
|
17
|
-
}).strict().readonly();
|
|
18
|
-
var WatchValueSchema = z.object({
|
|
19
|
-
key: WatchKeySchema,
|
|
20
|
-
revision: z.number().int().nonnegative(),
|
|
21
|
-
value: z.unknown()
|
|
22
|
-
}).readonly();
|
|
23
|
-
var WatchStateSchema = z.object({
|
|
24
|
-
key: WatchKeySchema,
|
|
25
|
-
phase: LiveStatePhaseSchema,
|
|
26
|
-
reason: LiveStateStopReasonSchema.optional(),
|
|
27
|
-
code: z.string().optional(),
|
|
28
|
-
message: z.string().optional()
|
|
29
|
-
}).readonly();
|
|
30
|
-
var WATCH_OPEN = "stitchkit.watch.open";
|
|
31
|
-
var WATCH_CLOSE = "stitchkit.watch.close";
|
|
32
|
-
var WATCH_VALUE = "stitchkit.watch.value";
|
|
33
|
-
var WATCH_STATE = "stitchkit.watch.state";
|
|
34
|
-
var watchContract = {
|
|
35
|
-
serverToClient: {
|
|
36
|
-
[WATCH_VALUE]: { args: z.tuple([WatchValueSchema]) },
|
|
37
|
-
[WATCH_STATE]: { args: z.tuple([WatchStateSchema]) }
|
|
38
|
-
},
|
|
39
|
-
clientToServer: {
|
|
40
|
-
[WATCH_OPEN]: { args: z.tuple([WatchOpenSchema]), ack: WatchAcceptedSchema },
|
|
41
|
-
[WATCH_CLOSE]: { args: z.tuple([z.object({ key: WatchKeySchema }).readonly()]) }
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
function watchKeyString(key) {
|
|
45
|
-
return `${key.service}/${key.action}/${key.digest}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export { WatchKeySchema, WatchValueSchema, WatchStateSchema, WATCH_OPEN, WATCH_CLOSE, WATCH_VALUE, WATCH_STATE, watchContract, watchKeyString };
|