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/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,62 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
|
|
|
15
15
|
|
|
16
16
|
## [Unreleased]
|
|
17
17
|
|
|
18
|
+
## [0.90.0] — 2026-09-15
|
|
19
|
+
|
|
20
|
+
### ⚠️ Breaking changes
|
|
21
|
+
|
|
22
|
+
**Who must act:** applications that bind the watch protocol themselves or read
|
|
23
|
+
`WatchValueFrame.value` directly — a hand-written subscriber, a test double, a custom
|
|
24
|
+
server binding. Applications using `createWatchClient` and the starter's binding need
|
|
25
|
+
no code change. **Both ends must come from the same major:** a client older than 0.90.0
|
|
26
|
+
reads a `delta` frame as a value of `undefined`, silently.
|
|
27
|
+
|
|
28
|
+
- **`stitchkit.watch.value` is a discriminated union on `kind`.** `full` carries the value,
|
|
29
|
+
`delta` carries a structural difference against a `base` revision, `unchanged` says the
|
|
30
|
+
value held is still current. Every frame carries `fingerprint`, the order-independent
|
|
31
|
+
digest of the value the receiver holds after applying it.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// before: frame.value
|
|
35
|
+
// after:
|
|
36
|
+
if (frame.kind === 'full') hold(frame.value);
|
|
37
|
+
else if (frame.kind === 'delta') hold(applyWatchDelta(held, frame.delta));
|
|
38
|
+
// 'unchanged' leaves what you hold standing
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- **`AttachedWatcher.open` takes an optional third argument, `have`.** A custom server
|
|
42
|
+
binding forwarding `stitchkit.watch.open` should pass the payload's `have` through;
|
|
43
|
+
omitting it costs a whole value on every reconnection and is otherwise harmless.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **Watched reads cross as differences.** The hub chooses per subscriber, from the revision
|
|
48
|
+
it last delivered to that subscriber, and sends a difference only when it is strictly
|
|
49
|
+
smaller than the value. Measured: a ~75 KB list answer with one field changed crosses as a
|
|
50
|
+
frame under 1 KB. `deltaMemoryBytes` (default 262144, `0` disables) bounds the superseded
|
|
51
|
+
values kept per key. → ADR 0183.
|
|
52
|
+
- **A reconnection offers what it holds.** `stitchkit.watch.open` may carry
|
|
53
|
+
`have: { revision, fingerprint }`; a subscriber returning to an answer that has not moved
|
|
54
|
+
is told `unchanged` instead of being sent the value again. Requires the key to still exist
|
|
55
|
+
on the hub — set `holdMs` past your reconnect delay.
|
|
56
|
+
- **Reassembly is checked.** The client verifies the rebuilt value against the server's
|
|
57
|
+
fingerprint on every frame and resynchronises **that one key** on any mismatch, publishing
|
|
58
|
+
`resync-required`. Other keys on the same socket are untouched.
|
|
59
|
+
- **`watchDiff` / `applyWatchDelta` / `watchDeltaWins`** and the `WatchDelta` shapes are
|
|
60
|
+
exported from `stitchkit/live` for applications building their own binding.
|
|
61
|
+
- **`auditChanges`** — the sink `filter` most projects were writing themselves: drops `GET`,
|
|
62
|
+
`HEAD` and `OPTIONS`, keeps `401` and `403` whatever the verb was, keeps an unrecognised
|
|
63
|
+
verb. One predicate across HTTP, MCP and agent calls. → ADR 0184.
|
|
64
|
+
- **`createSpooledSink`** — writes the audit row to a local append-only file before offering
|
|
65
|
+
it to the store and replays what a previous process left undelivered. At least once; the
|
|
66
|
+
store must be idempotent on the record key (`spanId` by default).
|
|
67
|
+
|
|
68
|
+
### Unchanged on purpose
|
|
69
|
+
|
|
70
|
+
- **Auditing stays opt-in.** `createHandler` and the tool mounts are untouched. Making a
|
|
71
|
+
decision about audit mandatory at startup was considered and rejected — the measurement it
|
|
72
|
+
rested on counted projects without an HTTP surface as projects that had forgotten. → ADR 0184.
|
|
73
|
+
|
|
18
74
|
## [0.89.0] — 2026-09-12
|
|
19
75
|
|
|
20
76
|
### ⚠️ Breaking changes
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* → ADR 0153.
|
|
39
39
|
*/
|
|
40
40
|
import { type BackoffPolicy } from '../browser/resumable.js';
|
|
41
|
-
import { type WatchKey, type WatchStateFrame, type WatchValueFrame } from '../live/watch-contract.js';
|
|
41
|
+
import { type WatchHave, type WatchKey, type WatchStateFrame, type WatchValueFrame } from '../live/watch-contract.js';
|
|
42
42
|
import type { StitchLogger } from '../logger.js';
|
|
43
43
|
/** The operation a watched read runs — `OperationIdentity`'s two stable halves. */
|
|
44
44
|
export interface WatchOperation {
|
|
@@ -52,7 +52,15 @@ export interface WatchSubscriber {
|
|
|
52
52
|
state(frame: WatchStateFrame): void;
|
|
53
53
|
}
|
|
54
54
|
export interface AttachedWatcher {
|
|
55
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Start watching a key.
|
|
57
|
+
*
|
|
58
|
+
* `have` is what the caller already holds from an earlier connection. Offered,
|
|
59
|
+
* it turns a reconnection into a difference — or into nothing at all when the
|
|
60
|
+
* answer has not moved — instead of the whole value again. Omitted, the caller
|
|
61
|
+
* is treated as holding nothing, which is what a first subscription is.
|
|
62
|
+
*/
|
|
63
|
+
open(key: WatchKey, args: unknown, have?: WatchHave): {
|
|
56
64
|
accepted: boolean;
|
|
57
65
|
reason?: string;
|
|
58
66
|
};
|
|
@@ -105,6 +113,20 @@ export interface WatchHubConfig {
|
|
|
105
113
|
holdMs?: number;
|
|
106
114
|
/** Retry pacing after a failed read. */
|
|
107
115
|
backoff?: BackoffPolicy;
|
|
116
|
+
/**
|
|
117
|
+
* How many bytes of superseded values one key may keep, for differences.
|
|
118
|
+
*
|
|
119
|
+
* A difference needs the value the receiver actually holds, which is no longer
|
|
120
|
+
* the current one. This is the ceiling on that memory, per key, and it is a
|
|
121
|
+
* byte budget rather than a count of revisions because the thing being
|
|
122
|
+
* protected is the process's heap and revisions have no size.
|
|
123
|
+
*
|
|
124
|
+
* Zero disables differences entirely: every frame carries the whole value,
|
|
125
|
+
* which is what this hub did before they existed. Default 262144 (256 KiB),
|
|
126
|
+
* which holds several revisions of the large answers differences are for and
|
|
127
|
+
* a great many small ones.
|
|
128
|
+
*/
|
|
129
|
+
deltaMemoryBytes?: number;
|
|
108
130
|
/** Whether two answers are the same. Defaults to key-order-independent JSON equality. */
|
|
109
131
|
same?(previous: unknown, next: unknown): boolean;
|
|
110
132
|
logger?: StitchLogger;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch-hub.d.ts","sourceRoot":"","sources":["../../src/application/watch-hub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,sBAAsB,CAAC;AAEzE,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"watch-hub.d.ts","sourceRoot":"","sources":["../../src/application/watch-hub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,sBAAsB,CAAC;AAEzE,OAAO,EACL,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,KAAK,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,oDAAoD;IACpD,KAAK,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7F,KAAK,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,yEAAyE;IACzE,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;;;;;;OAeG;IACH,IAAI,CAAC,SAAS,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,gFAAgF;IAChF,SAAS,CAAC,SAAS,EAAE,cAAc,GAAG,OAAO,CAAC;IAC9C;;;;;;;;;OASG;IACH,aAAa,CAAC,SAAS,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,CAAC;IAC3E,mFAAmF;IACnF,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC3D,kEAAkE;IAClE,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yFAAyF;IACzF,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACjD,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,UAAU,EAAE,eAAe,GAAG,eAAe,CAAC;IACrD;;;;;;OAMG;IACH,SAAS,IAAI,MAAM,CAAC;IACpB,qEAAqE;IACrE,IAAI,IAAI,MAAM,CAAC;IACf,KAAK,IAAI,IAAI,CAAC;CACf;AA2CD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,GAAG,QAAQ,CAU3E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,QAAQ,CA4Y/D"}
|
package/dist/application.js
CHANGED
|
@@ -66,8 +66,10 @@ import {
|
|
|
66
66
|
createBackoff
|
|
67
67
|
} from "./index-s8cnx02s.js";
|
|
68
68
|
import {
|
|
69
|
+
deltaWins,
|
|
70
|
+
diff,
|
|
69
71
|
watchKeyString
|
|
70
|
-
} from "./index-
|
|
72
|
+
} from "./index-2na0qrfn.js";
|
|
71
73
|
import"./index-ywd0y6m1.js";
|
|
72
74
|
import {
|
|
73
75
|
BoundedChannelPolicySchema,
|
|
@@ -1563,17 +1565,20 @@ function createWatchHub(config) {
|
|
|
1563
1565
|
const held = new Map;
|
|
1564
1566
|
const maxWatches = config.maxWatchesPerSubscriber ?? 64;
|
|
1565
1567
|
const holdMs = config.holdMs ?? 0;
|
|
1568
|
+
const deltaMemoryBytes = config.deltaMemoryBytes ?? 262144;
|
|
1566
1569
|
const same = config.same ?? ((previous, next) => JSON.stringify(stableValue(previous)) === JSON.stringify(stableValue(next)));
|
|
1567
1570
|
let reads = 0;
|
|
1568
1571
|
let closed = false;
|
|
1569
|
-
function tell(key,
|
|
1572
|
+
function tell(key, deliver2) {
|
|
1570
1573
|
try {
|
|
1571
|
-
|
|
1574
|
+
deliver2();
|
|
1575
|
+
return true;
|
|
1572
1576
|
} catch (error) {
|
|
1573
1577
|
config.logger?.warn?.("[stitchkit] watch subscriber threw", {
|
|
1574
1578
|
key: watchKeyString(key),
|
|
1575
1579
|
error: error instanceof Error ? error.message : String(error)
|
|
1576
1580
|
});
|
|
1581
|
+
return false;
|
|
1577
1582
|
}
|
|
1578
1583
|
}
|
|
1579
1584
|
function announceState(source, state) {
|
|
@@ -1582,13 +1587,85 @@ function createWatchHub(config) {
|
|
|
1582
1587
|
tell(source.key, () => subscriber.state(state));
|
|
1583
1588
|
}
|
|
1584
1589
|
}
|
|
1585
|
-
function
|
|
1590
|
+
function remember(source, revision, value, fingerprint) {
|
|
1591
|
+
if (deltaMemoryBytes === 0)
|
|
1592
|
+
return;
|
|
1593
|
+
const bytes = (JSON.stringify(value) ?? "null").length;
|
|
1594
|
+
if (bytes > deltaMemoryBytes)
|
|
1595
|
+
return;
|
|
1596
|
+
source.history.set(revision, { value, fingerprint, bytes });
|
|
1597
|
+
source.historyBytes += bytes;
|
|
1598
|
+
for (const [oldest, entry] of source.history) {
|
|
1599
|
+
if (source.historyBytes <= deltaMemoryBytes)
|
|
1600
|
+
break;
|
|
1601
|
+
source.history.delete(oldest);
|
|
1602
|
+
source.historyBytes -= entry.bytes;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
function frameFor(source, subscriber) {
|
|
1606
|
+
const value = source.value;
|
|
1607
|
+
const fingerprint = source.fingerprint ?? valueFingerprint(value);
|
|
1608
|
+
const full = {
|
|
1609
|
+
kind: "full",
|
|
1610
|
+
key: source.key,
|
|
1611
|
+
revision: source.revision,
|
|
1612
|
+
fingerprint,
|
|
1613
|
+
value
|
|
1614
|
+
};
|
|
1615
|
+
const base = source.baselines.get(subscriber);
|
|
1616
|
+
if (base === undefined)
|
|
1617
|
+
return full;
|
|
1618
|
+
if (base === source.revision) {
|
|
1619
|
+
return { kind: "unchanged", key: source.key, revision: source.revision, fingerprint };
|
|
1620
|
+
}
|
|
1621
|
+
const held2 = source.history.get(base);
|
|
1622
|
+
if (!held2)
|
|
1623
|
+
return full;
|
|
1624
|
+
const delta = diff(held2.value, value);
|
|
1625
|
+
if (delta === undefined || !deltaWins(delta, value))
|
|
1626
|
+
return full;
|
|
1627
|
+
return {
|
|
1628
|
+
kind: "delta",
|
|
1629
|
+
key: source.key,
|
|
1630
|
+
revision: source.revision,
|
|
1631
|
+
fingerprint,
|
|
1632
|
+
base,
|
|
1633
|
+
delta
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function adoptBaseline(source, subscriber, have) {
|
|
1637
|
+
if (source.fingerprint === have.fingerprint) {
|
|
1638
|
+
source.baselines.set(subscriber, source.revision);
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
const named = source.history.get(have.revision);
|
|
1642
|
+
if (named?.fingerprint === have.fingerprint) {
|
|
1643
|
+
source.baselines.set(subscriber, have.revision);
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
for (const [revision, entry] of source.history) {
|
|
1647
|
+
if (entry.fingerprint !== have.fingerprint)
|
|
1648
|
+
continue;
|
|
1649
|
+
source.baselines.set(subscriber, revision);
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
function deliver(source, subscriber) {
|
|
1654
|
+
const frame = frameFor(source, subscriber);
|
|
1655
|
+
const delivered = tell(source.key, () => subscriber.value(frame));
|
|
1656
|
+
if (delivered)
|
|
1657
|
+
source.baselines.set(subscriber, source.revision);
|
|
1658
|
+
return delivered;
|
|
1659
|
+
}
|
|
1660
|
+
function publish(source, value, fingerprint) {
|
|
1661
|
+
if (source.fingerprint !== undefined) {
|
|
1662
|
+
remember(source, source.revision, source.value, source.fingerprint);
|
|
1663
|
+
}
|
|
1586
1664
|
source.revision += 1;
|
|
1587
1665
|
source.value = value;
|
|
1588
|
-
|
|
1589
|
-
for (const subscriber of source.subscribers)
|
|
1590
|
-
|
|
1591
|
-
}
|
|
1666
|
+
source.fingerprint = fingerprint;
|
|
1667
|
+
for (const subscriber of source.subscribers)
|
|
1668
|
+
deliver(source, subscriber);
|
|
1592
1669
|
}
|
|
1593
1670
|
async function pump(source) {
|
|
1594
1671
|
if (source.reading || closed)
|
|
@@ -1605,7 +1682,7 @@ function createWatchHub(config) {
|
|
|
1605
1682
|
const unchanged = source.signature !== undefined && same(source.value, value);
|
|
1606
1683
|
source.signature = signature;
|
|
1607
1684
|
if (!unchanged)
|
|
1608
|
-
publish(source, value);
|
|
1685
|
+
publish(source, value, valueFingerprint(value));
|
|
1609
1686
|
if (source.state.phase !== "live") {
|
|
1610
1687
|
announceState(source, { key: source.key, phase: "live" });
|
|
1611
1688
|
}
|
|
@@ -1655,6 +1732,9 @@ function createWatchHub(config) {
|
|
|
1655
1732
|
subscribers: new Set,
|
|
1656
1733
|
unsubscribes: [],
|
|
1657
1734
|
revision: 0,
|
|
1735
|
+
history: new Map,
|
|
1736
|
+
historyBytes: 0,
|
|
1737
|
+
baselines: new Map,
|
|
1658
1738
|
reading: false,
|
|
1659
1739
|
dirty: true,
|
|
1660
1740
|
state: { key, phase: "opening" },
|
|
@@ -1696,7 +1776,7 @@ function createWatchHub(config) {
|
|
|
1696
1776
|
const keys = new Set;
|
|
1697
1777
|
held.set(subscriber, keys);
|
|
1698
1778
|
return {
|
|
1699
|
-
open(key, args) {
|
|
1779
|
+
open(key, args, have) {
|
|
1700
1780
|
if (closed)
|
|
1701
1781
|
return { accepted: false, reason: "the watch hub is closed" };
|
|
1702
1782
|
const operation = { service: key.service, action: key.action };
|
|
@@ -1718,13 +1798,18 @@ function createWatchHub(config) {
|
|
|
1718
1798
|
const source = acquire(operation, key, args);
|
|
1719
1799
|
source.subscribers.add(subscriber);
|
|
1720
1800
|
keys.add(id);
|
|
1801
|
+
if (have)
|
|
1802
|
+
adoptBaseline(source, subscriber, have);
|
|
1721
1803
|
try {
|
|
1722
1804
|
if (source.signature !== undefined) {
|
|
1723
|
-
|
|
1805
|
+
const frame = frameFor(source, subscriber);
|
|
1806
|
+
subscriber.value(frame);
|
|
1807
|
+
source.baselines.set(subscriber, source.revision);
|
|
1724
1808
|
}
|
|
1725
1809
|
subscriber.state(source.state);
|
|
1726
1810
|
} catch (error) {
|
|
1727
1811
|
source.subscribers.delete(subscriber);
|
|
1812
|
+
source.baselines.delete(subscriber);
|
|
1728
1813
|
keys.delete(id);
|
|
1729
1814
|
release(source);
|
|
1730
1815
|
return {
|
|
@@ -1743,6 +1828,7 @@ function createWatchHub(config) {
|
|
|
1743
1828
|
if (!source)
|
|
1744
1829
|
return;
|
|
1745
1830
|
source.subscribers.delete(subscriber);
|
|
1831
|
+
source.baselines.delete(subscriber);
|
|
1746
1832
|
release(source);
|
|
1747
1833
|
},
|
|
1748
1834
|
detach() {
|
|
@@ -1751,6 +1837,7 @@ function createWatchHub(config) {
|
|
|
1751
1837
|
if (!source)
|
|
1752
1838
|
continue;
|
|
1753
1839
|
source.subscribers.delete(subscriber);
|
|
1840
|
+
source.baselines.delete(subscriber);
|
|
1754
1841
|
release(source);
|
|
1755
1842
|
}
|
|
1756
1843
|
keys.clear();
|
|
@@ -1779,6 +1866,9 @@ function createWatchHub(config) {
|
|
|
1779
1866
|
}
|
|
1780
1867
|
};
|
|
1781
1868
|
}
|
|
1869
|
+
function valueFingerprint(value) {
|
|
1870
|
+
return argumentsDigest({ value });
|
|
1871
|
+
}
|
|
1782
1872
|
function readErrorCode(error) {
|
|
1783
1873
|
if (typeof error !== "object" || error === null)
|
|
1784
1874
|
return;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LiveStatePhaseSchema,
|
|
3
|
+
LiveStateStopReasonSchema
|
|
4
|
+
} from "./index-ywd0y6m1.js";
|
|
5
|
+
import {
|
|
6
|
+
stableValue
|
|
7
|
+
} from "./index-a916km3r.js";
|
|
8
|
+
|
|
9
|
+
// src/live/watch-delta.ts
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
var WatchDeltaSchema = z.lazy(() => z.union([
|
|
12
|
+
z.object({ t: z.literal("set"), v: z.unknown() }),
|
|
13
|
+
z.object({
|
|
14
|
+
t: z.literal("obj"),
|
|
15
|
+
set: z.record(z.string(), WatchDeltaSchema).optional(),
|
|
16
|
+
drop: z.array(z.string()).optional()
|
|
17
|
+
}),
|
|
18
|
+
z.object({ t: z.literal("arr"), ops: z.array(WatchArrayOpSchema) })
|
|
19
|
+
]));
|
|
20
|
+
var WatchArrayOpSchema = z.lazy(() => z.union([
|
|
21
|
+
z.object({
|
|
22
|
+
o: z.literal("copy"),
|
|
23
|
+
from: z.number().int().nonnegative(),
|
|
24
|
+
count: z.number().int().positive()
|
|
25
|
+
}),
|
|
26
|
+
z.object({
|
|
27
|
+
o: z.literal("patch"),
|
|
28
|
+
from: z.number().int().nonnegative(),
|
|
29
|
+
d: WatchDeltaSchema
|
|
30
|
+
}),
|
|
31
|
+
z.object({ o: z.literal("put"), v: z.unknown() })
|
|
32
|
+
]));
|
|
33
|
+
function isRecord(value) {
|
|
34
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35
|
+
}
|
|
36
|
+
function signature(value) {
|
|
37
|
+
return JSON.stringify(stableValue(value)) ?? "undefined";
|
|
38
|
+
}
|
|
39
|
+
function diff(previous, next) {
|
|
40
|
+
if (signature(previous) === signature(next))
|
|
41
|
+
return;
|
|
42
|
+
if (isRecord(previous) && isRecord(next))
|
|
43
|
+
return objectDiff(previous, next);
|
|
44
|
+
if (Array.isArray(previous) && Array.isArray(next))
|
|
45
|
+
return arrayDiff(previous, next);
|
|
46
|
+
return { t: "set", v: next };
|
|
47
|
+
}
|
|
48
|
+
function objectDiff(previous, next) {
|
|
49
|
+
const set = {};
|
|
50
|
+
const drop = [];
|
|
51
|
+
for (const key of Object.keys(next)) {
|
|
52
|
+
if (!Object.hasOwn(previous, key)) {
|
|
53
|
+
set[key] = { t: "set", v: next[key] };
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const nested = diff(previous[key], next[key]);
|
|
57
|
+
if (nested !== undefined)
|
|
58
|
+
set[key] = nested;
|
|
59
|
+
}
|
|
60
|
+
for (const key of Object.keys(previous)) {
|
|
61
|
+
if (!Object.hasOwn(next, key))
|
|
62
|
+
drop.push(key);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
t: "obj",
|
|
66
|
+
...Object.keys(set).length > 0 && { set },
|
|
67
|
+
...drop.length > 0 && { drop }
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function arrayDiff(previous, next) {
|
|
71
|
+
const previousSignatures = previous.map(signature);
|
|
72
|
+
const positions = new Map;
|
|
73
|
+
previousSignatures.forEach((value, index) => {
|
|
74
|
+
const bucket = positions.get(value);
|
|
75
|
+
if (bucket)
|
|
76
|
+
bucket.push(index);
|
|
77
|
+
else
|
|
78
|
+
positions.set(value, [index]);
|
|
79
|
+
});
|
|
80
|
+
const taken = new Set;
|
|
81
|
+
const ops = [];
|
|
82
|
+
let cursor = 0;
|
|
83
|
+
const extend = (from) => {
|
|
84
|
+
const last = ops.at(-1);
|
|
85
|
+
if (last?.o !== "copy" || last.from + last.count !== from)
|
|
86
|
+
return false;
|
|
87
|
+
ops[ops.length - 1] = { o: "copy", from: last.from, count: last.count + 1 };
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
for (const element of next) {
|
|
91
|
+
const wanted = signature(element);
|
|
92
|
+
const last = ops.at(-1);
|
|
93
|
+
const continuation = last?.o === "copy" ? last.from + last.count : undefined;
|
|
94
|
+
if (continuation !== undefined && continuation < previous.length && previousSignatures[continuation] === wanted && !taken.has(continuation)) {
|
|
95
|
+
taken.add(continuation);
|
|
96
|
+
extend(continuation);
|
|
97
|
+
cursor = continuation + 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const bucket = positions.get(wanted);
|
|
101
|
+
const found = bucket?.find((index) => !taken.has(index));
|
|
102
|
+
if (found !== undefined) {
|
|
103
|
+
taken.add(found);
|
|
104
|
+
if (!extend(found))
|
|
105
|
+
ops.push({ o: "copy", from: found, count: 1 });
|
|
106
|
+
cursor = found + 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const counterpart = cursor < previous.length && !taken.has(cursor) ? cursor : undefined;
|
|
110
|
+
const nested = counterpart === undefined ? undefined : diff(previous[counterpart], element);
|
|
111
|
+
if (counterpart !== undefined && nested !== undefined) {
|
|
112
|
+
taken.add(counterpart);
|
|
113
|
+
ops.push({ o: "patch", from: counterpart, d: nested });
|
|
114
|
+
cursor = counterpart + 1;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
ops.push({ o: "put", v: element });
|
|
118
|
+
}
|
|
119
|
+
return { t: "arr", ops };
|
|
120
|
+
}
|
|
121
|
+
function apply(previous, delta) {
|
|
122
|
+
if (delta.t === "set")
|
|
123
|
+
return delta.v;
|
|
124
|
+
if (delta.t === "obj") {
|
|
125
|
+
if (!isRecord(previous))
|
|
126
|
+
throw new Error("an object difference needs an object to apply to");
|
|
127
|
+
const result2 = { ...previous };
|
|
128
|
+
for (const key of delta.drop ?? [])
|
|
129
|
+
delete result2[key];
|
|
130
|
+
for (const [key, nested] of Object.entries(delta.set ?? {})) {
|
|
131
|
+
result2[key] = apply(Object.hasOwn(result2, key) ? result2[key] : undefined, nested);
|
|
132
|
+
}
|
|
133
|
+
return result2;
|
|
134
|
+
}
|
|
135
|
+
if (!Array.isArray(previous))
|
|
136
|
+
throw new Error("an array difference needs an array to apply to");
|
|
137
|
+
const result = [];
|
|
138
|
+
for (const op of delta.ops) {
|
|
139
|
+
if (op.o === "put") {
|
|
140
|
+
result.push(op.v);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (op.from >= previous.length) {
|
|
144
|
+
throw new Error(`the difference reads element ${op.from} of an array of ${previous.length}`);
|
|
145
|
+
}
|
|
146
|
+
if (op.o === "patch") {
|
|
147
|
+
result.push(apply(previous[op.from], op.d));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (op.from + op.count > previous.length) {
|
|
151
|
+
throw new Error(`the difference copies ${op.count} from ${op.from} of an array of ${previous.length}`);
|
|
152
|
+
}
|
|
153
|
+
result.push(...previous.slice(op.from, op.from + op.count));
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
function deltaWins(delta, value) {
|
|
158
|
+
return JSON.stringify(delta).length < (JSON.stringify(value) ?? "null").length;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/live/watch-contract.ts
|
|
162
|
+
import { z as z2 } from "zod";
|
|
163
|
+
var WatchKeySchema = z2.object({
|
|
164
|
+
service: z2.string().min(1),
|
|
165
|
+
action: z2.string().min(1),
|
|
166
|
+
digest: z2.string().min(1)
|
|
167
|
+
}).strict().readonly();
|
|
168
|
+
var WatchHaveSchema = z2.object({ revision: z2.number().int().nonnegative(), fingerprint: z2.string().min(1) }).strict().readonly();
|
|
169
|
+
var WatchOpenSchema = z2.object({ key: WatchKeySchema, args: z2.unknown(), have: WatchHaveSchema.optional() }).readonly();
|
|
170
|
+
var WatchAcceptedSchema = z2.object({
|
|
171
|
+
accepted: z2.boolean(),
|
|
172
|
+
reason: z2.string().optional()
|
|
173
|
+
}).strict().readonly();
|
|
174
|
+
var watchValueParts = {
|
|
175
|
+
key: WatchKeySchema,
|
|
176
|
+
revision: z2.number().int().nonnegative(),
|
|
177
|
+
fingerprint: z2.string().min(1)
|
|
178
|
+
};
|
|
179
|
+
var WatchValueSchema = z2.discriminatedUnion("kind", [
|
|
180
|
+
z2.object({ kind: z2.literal("full"), ...watchValueParts, value: z2.unknown() }).readonly(),
|
|
181
|
+
z2.object({
|
|
182
|
+
kind: z2.literal("delta"),
|
|
183
|
+
...watchValueParts,
|
|
184
|
+
base: z2.number().int().nonnegative(),
|
|
185
|
+
delta: WatchDeltaSchema
|
|
186
|
+
}).readonly(),
|
|
187
|
+
z2.object({ kind: z2.literal("unchanged"), ...watchValueParts }).readonly()
|
|
188
|
+
]);
|
|
189
|
+
var WatchStateSchema = z2.object({
|
|
190
|
+
key: WatchKeySchema,
|
|
191
|
+
phase: LiveStatePhaseSchema,
|
|
192
|
+
reason: LiveStateStopReasonSchema.optional(),
|
|
193
|
+
code: z2.string().optional(),
|
|
194
|
+
message: z2.string().optional()
|
|
195
|
+
}).readonly();
|
|
196
|
+
var WATCH_OPEN = "stitchkit.watch.open";
|
|
197
|
+
var WATCH_CLOSE = "stitchkit.watch.close";
|
|
198
|
+
var WATCH_VALUE = "stitchkit.watch.value";
|
|
199
|
+
var WATCH_STATE = "stitchkit.watch.state";
|
|
200
|
+
var watchContract = {
|
|
201
|
+
serverToClient: {
|
|
202
|
+
[WATCH_VALUE]: { args: z2.tuple([WatchValueSchema]) },
|
|
203
|
+
[WATCH_STATE]: { args: z2.tuple([WatchStateSchema]) }
|
|
204
|
+
},
|
|
205
|
+
clientToServer: {
|
|
206
|
+
[WATCH_OPEN]: { args: z2.tuple([WatchOpenSchema]), ack: WatchAcceptedSchema },
|
|
207
|
+
[WATCH_CLOSE]: { args: z2.tuple([z2.object({ key: WatchKeySchema }).readonly()]) }
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
function watchKeyString(key) {
|
|
211
|
+
return `${key.service}/${key.action}/${key.digest}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { WatchDeltaSchema, WatchArrayOpSchema, diff, apply, deltaWins, WatchKeySchema, WatchHaveSchema, WatchValueSchema, WatchStateSchema, WATCH_OPEN, WATCH_CLOSE, WATCH_VALUE, WATCH_STATE, watchContract, watchKeyString };
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* sense a rendering component can use.
|
|
45
45
|
*/
|
|
46
46
|
import type { ContractDef, EndpointDef } from '../contract/define.js';
|
|
47
|
-
import { WATCH_CLOSE, WATCH_OPEN, WATCH_STATE, WATCH_VALUE, type WatchKey, type WatchStateFrame, type WatchValueFrame } from './watch-contract.js';
|
|
47
|
+
import { WATCH_CLOSE, WATCH_OPEN, WATCH_STATE, WATCH_VALUE, type WatchHave, type WatchKey, type WatchStateFrame, type WatchValueFrame } from './watch-contract.js';
|
|
48
48
|
export interface WatchListeners<TValue> {
|
|
49
49
|
value(value: TValue): void;
|
|
50
50
|
/** Phase and, when unhealthy, the read's own code and message. */
|
|
@@ -107,6 +107,7 @@ export interface WatchTransport {
|
|
|
107
107
|
request(event: typeof WATCH_OPEN, payload: {
|
|
108
108
|
key: WatchKey;
|
|
109
109
|
args: unknown;
|
|
110
|
+
have?: WatchHave;
|
|
110
111
|
}, options: {
|
|
111
112
|
timeoutMs: number;
|
|
112
113
|
}): Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch-client.d.ts","sourceRoot":"","sources":["../../src/live/watch-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,WAAW,EACX,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,eAAe,EAErB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"watch-client.d.ts","sourceRoot":"","sources":["../../src/live/watch-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,6 +26,20 @@ export declare const WatchKeySchema: z.ZodReadonly<z.ZodObject<{
|
|
|
26
26
|
digest: z.ZodString;
|
|
27
27
|
}, z.core.$strict>>;
|
|
28
28
|
export type WatchKey = z.infer<typeof WatchKeySchema>;
|
|
29
|
+
/**
|
|
30
|
+
* What a subscriber already holds, offered when it opens a key.
|
|
31
|
+
*
|
|
32
|
+
* Both halves are needed and neither is redundant. The revision says *which*
|
|
33
|
+
* answer it has; the fingerprint proves it is that answer and not a different
|
|
34
|
+
* one wearing the same number — revisions restart at zero when a hub restarts or
|
|
35
|
+
* a key is released and re-acquired, so a bare revision would let a client keep
|
|
36
|
+
* a value from a previous life of the source and be told it was current.
|
|
37
|
+
*/
|
|
38
|
+
export declare const WatchHaveSchema: z.ZodReadonly<z.ZodObject<{
|
|
39
|
+
revision: z.ZodNumber;
|
|
40
|
+
fingerprint: z.ZodString;
|
|
41
|
+
}, z.core.$strict>>;
|
|
42
|
+
export type WatchHave = z.infer<typeof WatchHaveSchema>;
|
|
29
43
|
export declare const WatchOpenSchema: z.ZodReadonly<z.ZodObject<{
|
|
30
44
|
key: z.ZodReadonly<z.ZodObject<{
|
|
31
45
|
service: z.ZodString;
|
|
@@ -33,20 +47,46 @@ export declare const WatchOpenSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
33
47
|
digest: z.ZodString;
|
|
34
48
|
}, z.core.$strict>>;
|
|
35
49
|
args: z.ZodUnknown;
|
|
50
|
+
have: z.ZodOptional<z.ZodReadonly<z.ZodObject<{
|
|
51
|
+
revision: z.ZodNumber;
|
|
52
|
+
fingerprint: z.ZodString;
|
|
53
|
+
}, z.core.$strict>>>;
|
|
36
54
|
}, z.core.$strip>>;
|
|
37
55
|
export declare const WatchAcceptedSchema: z.ZodReadonly<z.ZodObject<{
|
|
38
56
|
accepted: z.ZodBoolean;
|
|
39
57
|
reason: z.ZodOptional<z.ZodString>;
|
|
40
58
|
}, z.core.$strict>>;
|
|
41
|
-
export declare const WatchValueSchema: z.ZodReadonly<z.ZodObject<{
|
|
59
|
+
export declare const WatchValueSchema: z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodObject<{
|
|
42
60
|
key: z.ZodReadonly<z.ZodObject<{
|
|
43
61
|
service: z.ZodString;
|
|
44
62
|
action: z.ZodString;
|
|
45
63
|
digest: z.ZodString;
|
|
46
64
|
}, z.core.$strict>>;
|
|
47
65
|
revision: z.ZodNumber;
|
|
66
|
+
fingerprint: z.ZodString;
|
|
67
|
+
kind: z.ZodLiteral<"full">;
|
|
48
68
|
value: z.ZodUnknown;
|
|
49
|
-
}, z.core.$strip
|
|
69
|
+
}, z.core.$strip>>, z.ZodReadonly<z.ZodObject<{
|
|
70
|
+
key: z.ZodReadonly<z.ZodObject<{
|
|
71
|
+
service: z.ZodString;
|
|
72
|
+
action: z.ZodString;
|
|
73
|
+
digest: z.ZodString;
|
|
74
|
+
}, z.core.$strict>>;
|
|
75
|
+
revision: z.ZodNumber;
|
|
76
|
+
fingerprint: z.ZodString;
|
|
77
|
+
kind: z.ZodLiteral<"delta">;
|
|
78
|
+
base: z.ZodNumber;
|
|
79
|
+
delta: z.ZodType<import("./watch-delta.js").WatchDelta, unknown, z.core.$ZodTypeInternals<import("./watch-delta.js").WatchDelta, unknown>>;
|
|
80
|
+
}, z.core.$strip>>, z.ZodReadonly<z.ZodObject<{
|
|
81
|
+
key: z.ZodReadonly<z.ZodObject<{
|
|
82
|
+
service: z.ZodString;
|
|
83
|
+
action: z.ZodString;
|
|
84
|
+
digest: z.ZodString;
|
|
85
|
+
}, z.core.$strict>>;
|
|
86
|
+
revision: z.ZodNumber;
|
|
87
|
+
fingerprint: z.ZodString;
|
|
88
|
+
kind: z.ZodLiteral<"unchanged">;
|
|
89
|
+
}, z.core.$strip>>], "kind">;
|
|
50
90
|
export declare const WatchStateSchema: z.ZodReadonly<z.ZodObject<{
|
|
51
91
|
key: z.ZodReadonly<z.ZodObject<{
|
|
52
92
|
service: z.ZodString;
|
|
@@ -88,15 +128,37 @@ export declare const WATCH_STATE = "stitchkit.watch.state";
|
|
|
88
128
|
export declare const watchContract: {
|
|
89
129
|
readonly serverToClient: {
|
|
90
130
|
readonly "stitchkit.watch.value": {
|
|
91
|
-
readonly args: z.ZodTuple<[z.ZodReadonly<z.ZodObject<{
|
|
131
|
+
readonly args: z.ZodTuple<[z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodObject<{
|
|
92
132
|
key: z.ZodReadonly<z.ZodObject<{
|
|
93
133
|
service: z.ZodString;
|
|
94
134
|
action: z.ZodString;
|
|
95
135
|
digest: z.ZodString;
|
|
96
136
|
}, z.core.$strict>>;
|
|
97
137
|
revision: z.ZodNumber;
|
|
138
|
+
fingerprint: z.ZodString;
|
|
139
|
+
kind: z.ZodLiteral<"full">;
|
|
98
140
|
value: z.ZodUnknown;
|
|
99
|
-
}, z.core.$strip
|
|
141
|
+
}, z.core.$strip>>, z.ZodReadonly<z.ZodObject<{
|
|
142
|
+
key: z.ZodReadonly<z.ZodObject<{
|
|
143
|
+
service: z.ZodString;
|
|
144
|
+
action: z.ZodString;
|
|
145
|
+
digest: z.ZodString;
|
|
146
|
+
}, z.core.$strict>>;
|
|
147
|
+
revision: z.ZodNumber;
|
|
148
|
+
fingerprint: z.ZodString;
|
|
149
|
+
kind: z.ZodLiteral<"delta">;
|
|
150
|
+
base: z.ZodNumber;
|
|
151
|
+
delta: z.ZodType<import("./watch-delta.js").WatchDelta, unknown, z.core.$ZodTypeInternals<import("./watch-delta.js").WatchDelta, unknown>>;
|
|
152
|
+
}, z.core.$strip>>, z.ZodReadonly<z.ZodObject<{
|
|
153
|
+
key: z.ZodReadonly<z.ZodObject<{
|
|
154
|
+
service: z.ZodString;
|
|
155
|
+
action: z.ZodString;
|
|
156
|
+
digest: z.ZodString;
|
|
157
|
+
}, z.core.$strict>>;
|
|
158
|
+
revision: z.ZodNumber;
|
|
159
|
+
fingerprint: z.ZodString;
|
|
160
|
+
kind: z.ZodLiteral<"unchanged">;
|
|
161
|
+
}, z.core.$strip>>], "kind">], null>;
|
|
100
162
|
};
|
|
101
163
|
readonly "stitchkit.watch.state": {
|
|
102
164
|
readonly args: z.ZodTuple<[z.ZodReadonly<z.ZodObject<{
|
|
@@ -135,6 +197,10 @@ export declare const watchContract: {
|
|
|
135
197
|
digest: z.ZodString;
|
|
136
198
|
}, z.core.$strict>>;
|
|
137
199
|
args: z.ZodUnknown;
|
|
200
|
+
have: z.ZodOptional<z.ZodReadonly<z.ZodObject<{
|
|
201
|
+
revision: z.ZodNumber;
|
|
202
|
+
fingerprint: z.ZodString;
|
|
203
|
+
}, z.core.$strict>>>;
|
|
138
204
|
}, z.core.$strip>>], null>;
|
|
139
205
|
readonly ack: z.ZodReadonly<z.ZodObject<{
|
|
140
206
|
accepted: z.ZodBoolean;
|