@mmstack/mesh 21.0.0 → 21.1.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 CHANGED
@@ -67,6 +67,133 @@ meshSync(board, {
67
67
  lost and a resolution is just a later write. See `@mmstack/primitives` for the full set of
68
68
  merge policies; they are the same ones the store uses for forks and tabs.
69
69
 
70
+ The default is last-writer-wins on a leaf, which drops one side of a true clash on the same field.
71
+ For a field where losing a write matters, set `preserve` on its path and resolve the `Conflicted`
72
+ value in the UI.
73
+
74
+ ## Interop with other CRDTs
75
+
76
+ A merge policy is a plain function, so one path can hold another CRDT and merge through that
77
+ library. This is the path for rich text or a list where order matters, which this package does not
78
+ model itself. Store the encoded document state at the path, and let the policy merge two states:
79
+
80
+ ```ts
81
+ import * as Y from 'yjs';
82
+ import type { MergeFn } from '@mmstack/primitives';
83
+
84
+ // the value at `doc` is a base64-encoded Y.Doc state. Use your own base64 helpers,
85
+ // since a JSON transport cannot carry a raw Uint8Array.
86
+ const yjsDoc: MergeFn = (_ancestor, mine, theirs) => {
87
+ const merged = new Y.Doc();
88
+ Y.applyUpdate(merged, fromBase64(mine as string));
89
+ Y.applyUpdate(merged, fromBase64(theirs as string));
90
+ return toBase64(Y.encodeStateAsUpdate(merged));
91
+ };
92
+
93
+ meshSync(store, {
94
+ room, writer, transport,
95
+ policies: [{ path: 'doc', merge: yjsDoc }],
96
+ });
97
+ ```
98
+
99
+ A concurrent edit runs the merge, so two people typing at once combine into one document with no
100
+ lost characters. A sequential edit already contains the earlier one, so it is taken whole.
101
+
102
+ The state travels as one value, so the wire cost grows with the document. For a large document, keep
103
+ the `Y.Doc` as an opaque leaf your store does not diff, and sync it with the library's own
104
+ incremental updates over the same transport. This package syncs the rest of the app state, the
105
+ library syncs the document, and the two stay independent.
106
+
107
+ ## Offline and durable outbox
108
+
109
+ Writes made while disconnected are held locally and sent on reconnect. That queue lives in memory
110
+ by default, so a full reload loses any write the room never acknowledged. Pass `outbox` to persist
111
+ it:
112
+
113
+ ```ts
114
+ import * as idbKeyval from 'idb-keyval';
115
+
116
+ meshSync(board, {
117
+ room: 'board-42',
118
+ writer: currentUserId,
119
+ transport,
120
+ outbox: { key: 'board-42', store: idbKeyval },
121
+ });
122
+ ```
123
+
124
+ `store` is any `AsyncStore` (`get`, `set`, `del`), the same interface `persist` takes. On boot the
125
+ client restores the saved queue, adopts the origin it used before the reload, and resends the
126
+ unacknowledged writes when it reconnects. Those offline edits then rebase onto whatever the room
127
+ moved to while the tab was gone. The queue is saved on a 300ms debounce; set `debounceMs` to change
128
+ it, or `0` to write on every change.
129
+
130
+ One origin is driven by one tab at a time. `crossTab` sets what a second tab on the same key does:
131
+
132
+ - `'queue'` (default) takes a Web Lock on the key. The second tab waits, with `status()` reading
133
+ `'connecting'`, until the first tab closes, then takes over. Exactly one durable writer holds the
134
+ key at a time.
135
+ - `'off'` skips the lock. Use it when you coordinate ownership yourself, for example a distinct key
136
+ per tab, or leader election over `tabSync`.
137
+
138
+ When the Web Locks API is unavailable, `'queue'` logs a development warning and runs without the
139
+ lock.
140
+
141
+ The outbox persists your unacknowledged writes, not a full snapshot. For a meshed store, use it in
142
+ place of wrapping the store in `persist`. The two race on boot, and the outbox is the one that
143
+ rebases offline edits onto the room. `persist` stays the tool for a store that is only ever local. A
144
+ cold offline boot shows the store's initial value plus your restored writes until a welcome arrives.
145
+ If you also need to read the last room state while fully offline, assemble it as a base first, below.
146
+
147
+ ## Assemble a base before connecting
148
+
149
+ Pass `whenReady` to hold the connection until a local base is in place. `meshSync` awaits it before
150
+ it connects and before it restores the outbox, so a store filled from another source is ready when
151
+ the room welcome arrives and rebases your pending writes on top. The status reads `connecting` while
152
+ it waits, so a boundary shows the store as pending.
153
+
154
+ ```ts
155
+ meshSync(graph, {
156
+ room: 'graph-7',
157
+ writer: currentUserId,
158
+ transport,
159
+ outbox: { key: 'graph-7', store: idbKeyval },
160
+ whenReady: () => baseReady, // a promise that resolves once the base is filled
161
+ });
162
+ ```
163
+
164
+ This is the boot order for a worker-owned, meshed, persisted graph. The worker hydrates the base, the
165
+ outbox restores this device's offline writes, then the room welcome supersedes the base and rebases
166
+ those writes on top. Each source runs in turn instead of racing, so the result does not depend on
167
+ which one happened to finish first. A rejected `whenReady` is treated as ready, so a base that fails
168
+ to load never holds the connection open.
169
+
170
+ ## Multiple tabs
171
+
172
+ Run `tabSync` and `meshSync` on the same store to share it across a user's tabs while one connection
173
+ carries it to the room. The outbox lock elects the leader, so only one tab holds the relay
174
+ connection and the others share state over `tabSync`. A write in any tab reaches the room through the
175
+ leader, and a room write reaches every tab through `tabSync`. When the leader tab closes, another
176
+ tab acquires the lock and takes over, adopting the persisted origin.
177
+
178
+ ```ts
179
+ import { store, tabSync } from '@mmstack/primitives';
180
+ import { meshSync, webSocketTransport } from '@mmstack/mesh';
181
+
182
+ const board = store<Board>(initialBoard());
183
+ tabSync(board, { id: 'board-42' }); // share across this user's tabs
184
+ meshSync(board, {
185
+ room: 'board-42',
186
+ writer: currentUserId,
187
+ transport: webSocketTransport('wss://sync.example.com'),
188
+ outbox: { key: 'board-42', store: idbKeyval }, // crossTab:'queue' elects one leader
189
+ });
190
+ ```
191
+
192
+ Each layer is a separate reader on the store's op stream, so they compose without knowing about each
193
+ other. A follower tab's `meshSync` stays idle until it holds the lock, so it never opens a second
194
+ connection. `tabSync` also takes a `bus` if you want to route over a channel other than the default
195
+ `BroadcastChannel`.
196
+
70
197
  ## Presence
71
198
 
72
199
  ```ts
@@ -96,6 +223,88 @@ meshSync(store, {
96
223
  });
97
224
  ```
98
225
 
226
+ ## Agents
227
+
228
+ An agent acts under the same protocol as a person: the same envelopes, attribution, ACLs, and undo.
229
+ There are two ways to give it write access, for two levels of trust.
230
+
231
+ ### Review a branch
232
+
233
+ Give the agent a fork of the synced store. Its writes stay on the fork, so nothing reaches the room
234
+ until a person approves. `ops()` is the staged change as data, ready to render for review. `commit()`
235
+ applies it onto the synced store, which then emits to the room. `discard()` drops it.
236
+
237
+ ```ts
238
+ import { forkStore } from '@mmstack/primitives';
239
+
240
+ const board = store<Board>(initialBoard());
241
+ meshSync(board, { room: 'board-42', writer: userId, transport });
242
+
243
+ const proposal = forkStore(board); // the agent's isolated branch, off the room
244
+ runAgent(proposal.store); // it writes here
245
+
246
+ const changes = proposal.ops(); // StoreOp[] for the reviewer to see
247
+ proposal.commit(); // approve: merges onto board, which syncs
248
+ // proposal.discard(); // reject: drops the staged writes
249
+ ```
250
+
251
+ The fork reconciles as the base moves, so a proposal stays current while a person looks at it. The
252
+ reviewer reads and writes normal store values, and the agent never touches the room directly. This
253
+ is the fit when a write should be seen before it lands.
254
+
255
+ ### Write as a peer
256
+
257
+ An agent can also join the room directly, scoped by the relay ACL. Give it a narrower `ctx` and a
258
+ `policy`, and the relay ejects any write outside its scope (see [Trust](#trust)).
259
+
260
+ ```ts
261
+ meshSync(board, {
262
+ room: 'board-42',
263
+ writer: agentId,
264
+ transport,
265
+ ctx: { kind: 'agent', claims: { scope: 'pricing' } },
266
+ policy: pricingScopeOnly,
267
+ });
268
+ ```
269
+
270
+ A live agent inherits the same conflict rules as everyone else, so a fast agent can win a
271
+ last-writer-wins race on a shared field. Reach for the branch when a write should be reviewed, or
272
+ when the field carries real weight.
273
+
274
+ ## Health
275
+
276
+ `meshSync` returns a `health` signal alongside `status`. It composes the connection state and any
277
+ reject reason into one value you can render:
278
+
279
+ ```ts
280
+ const mesh = meshSync(store, { room, writer, transport });
281
+ // mesh.health() -> { status, reason?, lastSyncedAt? }
282
+ ```
283
+
284
+ `status` is one of `live`, `offline`, `outdated`, `ejected`, or `degraded`. The useful distinction
285
+ is `outdated` versus `ejected`. A versioned reject (the client's `proto`, `policyVersion`, or
286
+ `schemaVersion` is behind the room) reports `outdated` with the reason, so you can show an update or
287
+ reload prompt instead of a dead connection. A policy tripwire reports `ejected`. `degraded` is the
288
+ slot for local problems such as a full storage quota or a dead worker; those are your own signals to
289
+ fold in, since `meshSync` only owns the connection side.
290
+
291
+ ## Schema versions
292
+
293
+ The data shape a room holds is a third version axis next to `proto` and `policyVersion`. Additive
294
+ changes need no version at all: new fields fold in, and a client ignores fields it does not render.
295
+ For a breaking change, pass `schemaVersion` and migrate through the log.
296
+
297
+ ```ts
298
+ meshSync(store, { room, writer, transport, schemaVersion: 2 });
299
+ ```
300
+
301
+ A migration is an envelope: a privileged writer (run from your deploy) emits a root set carrying the
302
+ new `schemaVersion`. The relay bumps the room's schema and its epoch, so every watermark dies and
303
+ clients re-hydrate into the new shape. A client older than the room is rejected with reason
304
+ `schema`, and a client already connected when the migration lands stops applying and reports
305
+ `outdated`. Because the migration rides the log, a compacted snapshot and `relay.hydrate` are
306
+ post-migration by construction, and journal replay stays correct forever.
307
+
99
308
  ## Transports
100
309
 
101
310
  - `webSocketTransport(url)` for a relay over WebSocket.
@@ -2,26 +2,48 @@ import { inject, Injector, signal, computed, isDevMode, runInInjectionContext, D
2
2
  import { MESH_PROTO_VERSION, checkEnvelope } from '@mmstack/mesh-protocol';
3
3
  import { opSync, registerResource } from '@mmstack/primitives';
4
4
 
5
+ // a versioned reject means "your build is behind" → prompt an update, not a dead socket
6
+ const OUTDATED_REASONS = new Set([
7
+ 'proto',
8
+ 'policy-version',
9
+ 'schema',
10
+ ]);
5
11
  const RECONNECT_BASE_MS = 500;
12
+ const OUTBOX_DEBOUNCE_MS = 300;
6
13
  /**
7
14
  * Replicates a signal store across clients through a relay room: local writes emit stamped
8
15
  * envelopes, remote envelopes fold in convergently, reconnects resume via delta or snapshot
9
16
  * with unacknowledged local writes rebased on top, and presence rides an ephemeral channel.
10
17
  * A synced store reads exactly like a local one — connection state surfaces only through
11
- * `status` and the transition scope (op-protocol RFC §10).
18
+ * `status` and the transition scope.
12
19
  */
13
20
  function meshSync(source, opt) {
14
21
  const injector = opt.injector ?? inject(Injector);
15
22
  const status = signal('connecting', ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
23
+ const lastReason = signal(undefined, ...(ngDevMode ? [{ debugName: "lastReason" }] : /* istanbul ignore next */ []));
24
+ const lastSyncedAt = signal(undefined, ...(ngDevMode ? [{ debugName: "lastSyncedAt" }] : /* istanbul ignore next */ []));
16
25
  const peerMap = signal(new Map(), ...(ngDevMode ? [{ debugName: "peerMap" }] : /* istanbul ignore next */ []));
17
26
  const peers = computed(() => [...peerMap().values()], ...(ngDevMode ? [{ debugName: "peers" }] : /* istanbul ignore next */ []));
18
27
  const policyVersion = opt.policyVersion ?? 0;
19
- const sync = opSync(source, {
20
- writer: opt.writer,
21
- policies: opt.policies,
22
- policyVersion,
23
- injector,
24
- });
28
+ const health = computed(() => {
29
+ const at = lastSyncedAt();
30
+ switch (status()) {
31
+ case 'live':
32
+ return { status: 'live', lastSyncedAt: at };
33
+ case 'ejected': {
34
+ const reason = lastReason();
35
+ return reason && OUTDATED_REASONS.has(reason)
36
+ ? { status: 'outdated', reason, lastSyncedAt: at }
37
+ : { status: 'ejected', reason, lastSyncedAt: at };
38
+ }
39
+ default: // connecting / reconnecting / closed — not reachable
40
+ return { status: 'offline', lastSyncedAt: at };
41
+ }
42
+ }, ...(ngDevMode ? [{ debugName: "health" }] : /* istanbul ignore next */ []));
43
+ // Created lazily: with a persisted outbox we must adopt the stored origin BEFORE minting anything
44
+ let sync;
45
+ let started = false;
46
+ let unsubLocal = () => undefined;
25
47
  const unacked = new Map();
26
48
  let highestAcked = 0;
27
49
  let lastSeq = 0;
@@ -33,17 +55,55 @@ function meshSync(source, opt) {
33
55
  let reconnectTimer;
34
56
  let unsubs = [];
35
57
  let closed = false;
58
+ let persistTimer;
59
+ // single-writer Web Lock (crossTab:'queue'): `release` frees it for the next tab once acquired;
60
+ // `cancel` aborts a still-queued request if we tear down before the lock is ever granted.
61
+ let releaseLock;
62
+ let cancelLock;
63
+ const doPersist = () => {
64
+ if (!opt.outbox || !started)
65
+ return;
66
+ const payload = {
67
+ origin: sync.origin,
68
+ version: sync.watermark()[sync.origin] ?? 0,
69
+ envs: [...unacked.values()],
70
+ };
71
+ void Promise.resolve(opt.outbox.store.set(opt.outbox.key, payload));
72
+ };
73
+ // coalesce outbox writes; `immediate` forces a synchronous-path write (first boot, teardown)
74
+ const persistOutbox = (immediate = false) => {
75
+ if (!opt.outbox)
76
+ return;
77
+ if (immediate) {
78
+ if (persistTimer !== undefined)
79
+ clearTimeout(persistTimer);
80
+ persistTimer = undefined;
81
+ doPersist();
82
+ return;
83
+ }
84
+ if (persistTimer !== undefined)
85
+ return;
86
+ persistTimer = setTimeout(() => {
87
+ persistTimer = undefined;
88
+ doPersist();
89
+ }, opt.outbox.debounceMs ?? OUTBOX_DEBOUNCE_MS);
90
+ };
36
91
  const terminal = (state, reason) => {
37
92
  closed = true;
93
+ cancelLock?.(); // drop a still-queued lock request so we never steal it after teardown
94
+ releaseLock?.(); // free a held lock for the next waiting tab
95
+ cancelLock = releaseLock = undefined;
38
96
  if (reconnectTimer !== undefined)
39
97
  clearTimeout(reconnectTimer);
40
98
  for (const unsub of unsubs.splice(0))
41
99
  unsub();
42
100
  unsubLocal();
101
+ persistOutbox(true); // save the still-unacked tail for the next boot before dropping it
43
102
  unacked.clear();
44
103
  transport?.close();
45
104
  transport = null;
46
105
  peerMap.set(new Map());
106
+ lastReason.set(reason);
47
107
  status.set(state);
48
108
  if (reason !== undefined)
49
109
  opt.onEject?.(reason);
@@ -78,10 +138,15 @@ function meshSync(source, opt) {
78
138
  }
79
139
  lastSeq = Math.max(lastSeq, msg.seq);
80
140
  attempts = 0;
141
+ lastSyncedAt.set(Date.now());
81
142
  status.set('live');
82
143
  flushUnacked();
83
144
  if (hasPresence)
84
- transport?.send({ t: 'presence', room: opt.room, data: presenceData });
145
+ transport?.send({
146
+ t: 'presence',
147
+ room: opt.room,
148
+ data: presenceData,
149
+ });
85
150
  return;
86
151
  }
87
152
  case 'env':
@@ -107,9 +172,18 @@ function meshSync(source, opt) {
107
172
  };
108
173
  const applyRemote = (env) => {
109
174
  lastSeq = Math.max(lastSeq, env.seq);
175
+ lastSyncedAt.set(Date.now());
176
+ // a migration to a shape newer than ours: stop applying + surface 'outdated' (never downgrade-interpret newer data)
177
+ if (opt.schemaVersion !== undefined &&
178
+ env.schemaVersion !== undefined &&
179
+ env.schemaVersion > opt.schemaVersion) {
180
+ terminal('ejected', 'schema');
181
+ return;
182
+ }
110
183
  if (env.origin === sync.origin) {
111
184
  unacked.delete(env.version);
112
185
  highestAcked = Math.max(highestAcked, env.version);
186
+ persistOutbox();
113
187
  return;
114
188
  }
115
189
  sync.receive(env);
@@ -142,20 +216,45 @@ function meshSync(source, opt) {
142
216
  proto: MESH_PROTO_VERSION,
143
217
  policyVersion,
144
218
  seq: lastSeq > 0 ? lastSeq : undefined,
219
+ schemaVersion: opt.schemaVersion,
145
220
  });
146
221
  };
147
- const unsubLocal = sync.subscribe((env) => {
148
- const violation = checkEnvelope(opt.policy, env, { ...opt.ctx, writer: opt.writer });
149
- if (violation) {
150
- if (isDevMode()) {
151
- console.warn('[@mmstack/mesh] local write violates the room policy — not sent', violation);
222
+ const wireLocal = () => {
223
+ unsubLocal = sync.subscribe((env) => {
224
+ const violation = checkEnvelope(opt.policy, env, {
225
+ ...opt.ctx,
226
+ writer: opt.writer,
227
+ });
228
+ if (violation) {
229
+ if (isDevMode()) {
230
+ console.warn('[@mmstack/mesh] local write violates the room policy — not sent', violation);
231
+ }
232
+ return;
152
233
  }
234
+ unacked.set(env.version, env);
235
+ persistOutbox();
236
+ if (status() === 'live')
237
+ sendEnv(env);
238
+ });
239
+ };
240
+ const initCore = (restore) => {
241
+ if (closed)
153
242
  return;
243
+ sync = opSync(source, {
244
+ writer: opt.writer,
245
+ policies: opt.policies,
246
+ policyVersion,
247
+ injector,
248
+ origin: restore?.origin,
249
+ });
250
+ started = true;
251
+ wireLocal();
252
+ if (restore && (restore.envs.length > 0 || restore.version > 0)) {
253
+ sync.restore(restore.envs, restore.version); // → subscribe repopulates `unacked` for resend
154
254
  }
155
- unacked.set(env.version, env);
156
- if (status() === 'live')
157
- sendEnv(env);
158
- });
255
+ persistOutbox(true); // pin the (possibly freshly minted) origin so later boots reuse it
256
+ connect();
257
+ };
159
258
  if (opt.register) {
160
259
  const connection = {
161
260
  status: computed(() => {
@@ -177,11 +276,83 @@ function meshSync(source, opt) {
177
276
  if (!closed)
178
277
  terminal('closed');
179
278
  unsubLocal();
180
- sync.destroy();
279
+ if (started)
280
+ sync.destroy();
181
281
  });
182
- connect();
282
+ // Load the persisted outbox, then boot with the adopted origin. A fresh/unreadable slot boots clean.
283
+ const bootFromDisk = async () => {
284
+ if (closed || !opt.outbox)
285
+ return;
286
+ let saved;
287
+ try {
288
+ const raw = await opt.outbox.store.get(opt.outbox.key);
289
+ saved =
290
+ raw && typeof raw === 'object' && 'origin' in raw
291
+ ? raw
292
+ : undefined;
293
+ }
294
+ catch {
295
+ saved = undefined; // an unreadable slot must not wedge the boot
296
+ }
297
+ initCore(saved);
298
+ };
299
+ const beginConnect = () => {
300
+ if (closed)
301
+ return;
302
+ if (!opt.outbox) {
303
+ initCore();
304
+ }
305
+ else if ((opt.outbox.crossTab ?? 'queue') === 'off') {
306
+ void bootFromDisk(); // no single-writer lock — the app coordinates ownership
307
+ }
308
+ else {
309
+ // crossTab:'queue' — hold an exclusive Web Lock on the key for this tab's lifetime, so a
310
+ // second tab sharing the key WAITS (stays 'connecting') instead of restoring the same origin
311
+ // and colliding on version mints. The lock auto-releases if the tab crashes.
312
+ const locks = globalThis.navigator?.locks;
313
+ if (!locks) {
314
+ if (isDevMode()) {
315
+ console.warn('[@mmstack/mesh] outbox crossTab:"queue" needs the Web Locks API (navigator.locks), unavailable here — running WITHOUT a single-writer lock. Two tabs sharing this key can diverge; coordinate ownership yourself, or set crossTab:"off" to silence this.');
316
+ }
317
+ void bootFromDisk();
318
+ }
319
+ else {
320
+ const abort = new AbortController();
321
+ cancelLock = () => abort.abort();
322
+ void locks
323
+ .request(`@mmstack/mesh:outbox:${opt.outbox.key}`, { mode: 'exclusive', signal: abort.signal }, () => {
324
+ cancelLock = undefined; // granted — no longer abortable, only releasable
325
+ if (closed)
326
+ return Promise.resolve();
327
+ // hold the lock until teardown resolves this promise
328
+ return new Promise((release) => {
329
+ releaseLock = release;
330
+ void bootFromDisk();
331
+ });
332
+ })
333
+ .catch((e) => {
334
+ // an aborted request is our own teardown; any other failure → degrade to no-lock
335
+ if (!closed && e?.name !== 'AbortError') {
336
+ void bootFromDisk();
337
+ }
338
+ });
339
+ }
340
+ }
341
+ };
342
+ // Assemble the local base first (worker hydration, disk snapshot), THEN connect, so the room
343
+ // welcome rebases pending onto a populated store instead of racing it. A rejection is treated as
344
+ // ready so a failed base load never wedges the connection.
345
+ if (opt.whenReady) {
346
+ void Promise.resolve()
347
+ .then(() => opt.whenReady?.())
348
+ .then(() => beginConnect(), () => beginConnect());
349
+ }
350
+ else {
351
+ beginConnect();
352
+ }
183
353
  return {
184
354
  status: status.asReadonly(),
355
+ health,
185
356
  peers,
186
357
  setPresence: (data) => {
187
358
  presenceData = data;
@@ -194,7 +365,8 @@ function meshSync(source, opt) {
194
365
  if (!closed)
195
366
  terminal('closed');
196
367
  unsubLocal();
197
- sync.destroy();
368
+ if (started)
369
+ sync.destroy();
198
370
  },
199
371
  };
200
372
  }
@@ -392,7 +564,7 @@ function rtcPeerConnector(config) {
392
564
  };
393
565
  }
394
566
  /**
395
- * Peer-to-peer mesh sync (unsequenced topology, op-protocol RFC §4): the relay only signals
567
+ * Peer-to-peer mesh sync: the relay only signals
396
568
  * and tracks membership; envelopes flow over WebRTC data channels and converge via the
397
569
  * per-path register map. Catch-up is pairwise: on channel open both sides exchange
398
570
  * watermarks; a side whose state is strictly covered hydrates from the other. Two peers that
@@ -1 +1 @@
1
- {"version":3,"file":"mmstack-mesh.mjs","sources":["../../../../../packages/mesh/client/src/lib/mesh-sync.ts","../../../../../packages/mesh/client/src/lib/transport.ts","../../../../../packages/mesh/client/src/lib/webrtc-mesh.ts","../../../../../packages/mesh/client/src/mmstack-mesh.ts"],"sourcesContent":["import {\n computed,\n DestroyRef,\n inject,\n Injector,\n isDevMode,\n runInInjectionContext,\n signal,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport {\n checkEnvelope,\n MESH_PROTO_VERSION,\n type OpPolicy,\n type PresenceState,\n type PrincipalCtx,\n type SeqEnvelope,\n type ServerMsg,\n} from '@mmstack/mesh-protocol';\nimport {\n opSync,\n registerResource,\n type MergePolicyEntry,\n type OpEnvelope,\n type ResourceLike,\n} from '@mmstack/primitives';\nimport type { MeshTransport, MeshTransportFactory } from './transport';\n\nexport type MeshStatus =\n | 'connecting'\n | 'live'\n | 'reconnecting'\n | 'ejected'\n | 'closed';\n\nexport type MeshPeer = PresenceState;\n\nexport type MeshSyncOptions = {\n readonly room: string;\n /** Opaque principal pseudonym — provided, never minted (op-protocol RFC §3). */\n readonly writer: string;\n readonly transport: MeshTransportFactory;\n /** Per-path merge policies for rebase/convergence (`lww` default). */\n readonly policies?: readonly MergePolicyEntry[];\n /** Emit-side validation, symmetric with the relay's (tripwire honesty). */\n readonly policy?: OpPolicy;\n /** kind/claims of this principal, so a shared policy evaluates identically on both sides. */\n readonly ctx?: Omit<PrincipalCtx, 'writer'>;\n readonly policyVersion?: number;\n /** Exponential backoff cap for reconnects (default 15s; base 500ms + jitter). */\n readonly reconnect?: { readonly maxDelayMs?: number };\n readonly injector?: Injector;\n /** Register with the nearest transition scope so (re)connection surfaces as `pending`. */\n readonly register?: 'track' | 'suspend';\n readonly onEject?: (reason: string) => void;\n};\n\nexport type MeshSyncRef = {\n readonly status: Signal<MeshStatus>;\n readonly peers: Signal<readonly MeshPeer[]>;\n /** Publish this client's ephemeral presence payload (cursor, section, activity…). */\n setPresence(data: unknown): void;\n close(): void;\n};\n\nconst RECONNECT_BASE_MS = 500;\n\n/**\n * Replicates a signal store across clients through a relay room: local writes emit stamped\n * envelopes, remote envelopes fold in convergently, reconnects resume via delta or snapshot\n * with unacknowledged local writes rebased on top, and presence rides an ephemeral channel.\n * A synced store reads exactly like a local one — connection state surfaces only through\n * `status` and the transition scope (op-protocol RFC §10).\n */\nexport function meshSync<T extends object>(\n source: WritableSignal<T>,\n opt: MeshSyncOptions,\n): MeshSyncRef {\n const injector = opt.injector ?? inject(Injector);\n const status = signal<MeshStatus>('connecting');\n const peerMap = signal<ReadonlyMap<string, MeshPeer>>(new Map());\n const peers = computed(() => [...peerMap().values()]);\n const policyVersion = opt.policyVersion ?? 0;\n\n const sync = opSync(source, {\n writer: opt.writer,\n policies: opt.policies,\n policyVersion,\n injector,\n });\n\n const unacked = new Map<number, OpEnvelope>();\n let highestAcked = 0;\n let lastSeq = 0;\n let epoch: string | undefined;\n let presenceData: unknown;\n let hasPresence = false;\n let transport: MeshTransport | null = null;\n let attempts = 0;\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n let unsubs: (() => void)[] = [];\n let closed = false;\n\n const terminal = (state: 'ejected' | 'closed', reason?: string): void => {\n closed = true;\n if (reconnectTimer !== undefined) clearTimeout(reconnectTimer);\n for (const unsub of unsubs.splice(0)) unsub();\n unsubLocal();\n unacked.clear();\n transport?.close();\n transport = null;\n peerMap.set(new Map());\n status.set(state);\n if (reason !== undefined) opt.onEject?.(reason);\n };\n\n const sendEnv = (env: OpEnvelope): void => {\n transport?.send({ t: 'env', room: opt.room, env });\n };\n\n const flushUnacked = (): void => {\n for (const env of [...unacked.values()].sort((a, b) => a.version - b.version)) {\n sendEnv(env);\n }\n };\n\n const handle = (msg: ServerMsg): void => {\n if (msg.room !== opt.room) return;\n switch (msg.t) {\n case 'welcome': {\n if (epoch !== undefined && msg.epoch !== epoch) lastSeq = 0;\n epoch = msg.epoch;\n peerMap.set(new Map(msg.peers.map((p) => [p.origin, p])));\n if (msg.mode === 'delta') {\n for (const env of msg.envs) applyRemote(env);\n } else if (msg.mode === 'snapshot') {\n sync.hydrate(msg.root as T, { [sync.origin]: highestAcked });\n lastSeq = msg.seq;\n } else if (msg.seq === 0 && lastSeq === 0) {\n sync.seed();\n }\n lastSeq = Math.max(lastSeq, msg.seq);\n attempts = 0;\n status.set('live');\n flushUnacked();\n if (hasPresence) transport?.send({ t: 'presence', room: opt.room, data: presenceData });\n return;\n }\n case 'env':\n applyRemote(msg.env);\n return;\n case 'presence': {\n const next = new Map(peerMap());\n if (msg.gone) next.delete(msg.peer.origin);\n else next.set(msg.peer.origin, msg.peer);\n peerMap.set(next);\n return;\n }\n case 'eject':\n if (msg.writer === opt.writer) terminal('ejected', msg.reason);\n return;\n case 'reject':\n terminal('ejected', msg.reason);\n return;\n }\n };\n\n const applyRemote = (env: SeqEnvelope): void => {\n lastSeq = Math.max(lastSeq, env.seq);\n if (env.origin === sync.origin) {\n unacked.delete(env.version);\n highestAcked = Math.max(highestAcked, env.version);\n return;\n }\n sync.receive(env);\n };\n\n const connect = (): void => {\n if (closed) return;\n for (const unsub of unsubs.splice(0)) unsub();\n const t = opt.transport();\n transport = t;\n unsubs = [\n t.onMessage(handle),\n t.onClose(() => {\n if (closed || transport !== t) return;\n transport = null;\n status.set('reconnecting');\n const delay = Math.min(\n opt.reconnect?.maxDelayMs ?? 15_000,\n RECONNECT_BASE_MS * 2 ** attempts++,\n );\n reconnectTimer = setTimeout(() => {\n reconnectTimer = undefined;\n connect();\n }, delay + Math.random() * 100);\n }),\n ];\n t.send({\n t: 'hello',\n room: opt.room,\n origin: sync.origin,\n proto: MESH_PROTO_VERSION,\n policyVersion,\n seq: lastSeq > 0 ? lastSeq : undefined,\n });\n };\n\n const unsubLocal = sync.subscribe((env) => {\n const violation = checkEnvelope(opt.policy, env, { ...opt.ctx, writer: opt.writer });\n if (violation) {\n if (isDevMode()) {\n console.warn('[@mmstack/mesh] local write violates the room policy — not sent', violation);\n }\n return;\n }\n unacked.set(env.version, env);\n if (status() === 'live') sendEnv(env);\n });\n\n if (opt.register) {\n const connection: ResourceLike = {\n status: computed(() => {\n const s = status();\n return s === 'connecting'\n ? 'loading'\n : s === 'reconnecting'\n ? 'reloading'\n : s === 'ejected'\n ? 'error'\n : 'resolved';\n }),\n isLoading: computed(\n () => status() === 'connecting' || status() === 'reconnecting',\n ),\n hasValue: () => true,\n };\n runInInjectionContext(injector, () =>\n registerResource(connection, { suspends: opt.register === 'suspend' }),\n );\n }\n\n injector.get(DestroyRef).onDestroy(() => {\n if (!closed) terminal('closed');\n unsubLocal();\n sync.destroy();\n });\n\n connect();\n\n return {\n status: status.asReadonly(),\n peers,\n setPresence: (data) => {\n presenceData = data;\n hasPresence = true;\n if (status() === 'live') {\n transport?.send({ t: 'presence', room: opt.room, data });\n }\n },\n close: () => {\n if (!closed) terminal('closed');\n unsubLocal();\n sync.destroy();\n },\n };\n}\n","import { isDevMode } from '@angular/core';\nimport type {\n ClientMsg,\n PrincipalCtx,\n Relay,\n ServerMsg,\n} from '@mmstack/mesh-protocol';\n\n/**\n * One live connection to a relay. `meshSync` calls the factory per (re)connect; a transport\n * represents a single connection and never reconnects itself.\n */\nexport type MeshTransport = {\n send(msg: ClientMsg): void;\n onMessage(cb: (msg: ServerMsg) => void): () => void;\n onClose(cb: () => void): () => void;\n close(): void;\n};\n\nexport type MeshTransportFactory = () => MeshTransport;\n\n/**\n * JSON-over-WebSocket transport. Messages sent before the socket opens are buffered and\n * flushed on open; malformed inbound frames are dropped.\n */\nexport function webSocketTransport(\n url: string,\n protocols?: string | string[],\n): MeshTransportFactory {\n return () => {\n const ws = new WebSocket(url, protocols);\n const messageCbs = new Set<(msg: ServerMsg) => void>();\n const closeCbs = new Set<() => void>();\n const pending: string[] = [];\n\n ws.onopen = () => {\n for (const frame of pending.splice(0)) ws.send(frame);\n };\n ws.onmessage = (event) => {\n let msg: ServerMsg;\n try {\n msg = JSON.parse(String(event.data)) as ServerMsg;\n } catch {\n if (isDevMode()) {\n console.warn('[@mmstack/mesh] dropped unparseable frame (expected JSON text)');\n }\n return;\n }\n for (const cb of [...messageCbs]) cb(msg);\n };\n ws.onclose = () => {\n for (const cb of [...closeCbs]) cb();\n };\n\n return {\n send: (msg) => {\n const frame = JSON.stringify(msg);\n if (ws.readyState === WebSocket.OPEN) ws.send(frame);\n else if (ws.readyState === WebSocket.CONNECTING) pending.push(frame);\n },\n onMessage: (cb) => {\n messageCbs.add(cb);\n return () => messageCbs.delete(cb);\n },\n onClose: (cb) => {\n closeCbs.add(cb);\n return () => closeCbs.delete(cb);\n },\n close: () => ws.close(),\n };\n };\n}\n\n/**\n * Connects directly to an in-process `createRelay` — no network. The backbone of full-loop\n * tests, and handy for single-process demos or an SSR-side room.\n */\nexport function directTransport(\n relay: Relay,\n ctx: PrincipalCtx,\n): MeshTransportFactory {\n return () => {\n const messageCbs = new Set<(msg: ServerMsg) => void>();\n const closeCbs = new Set<() => void>();\n let open = true;\n\n const connection = relay.connect(\n {\n send: (msg) => {\n if (!open) return;\n for (const cb of [...messageCbs]) cb(msg);\n },\n close: () => {\n if (!open) return;\n open = false;\n connection.disconnect();\n for (const cb of [...closeCbs]) cb();\n },\n },\n ctx,\n );\n\n return {\n send: (msg) => {\n if (open) connection.receive(msg);\n },\n onMessage: (cb) => {\n messageCbs.add(cb);\n return () => messageCbs.delete(cb);\n },\n onClose: (cb) => {\n closeCbs.add(cb);\n return () => closeCbs.delete(cb);\n },\n close: () => {\n if (!open) return;\n open = false;\n connection.disconnect();\n for (const cb of [...closeCbs]) cb();\n },\n };\n };\n}\n","import {\n computed,\n DestroyRef,\n inject,\n Injector,\n signal,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport { MESH_PROTO_VERSION, type ServerMsg } from '@mmstack/mesh-protocol';\nimport {\n opSync,\n type MergePolicyEntry,\n type OpEnvelope,\n} from '@mmstack/primitives';\nimport type { MeshTransport, MeshTransportFactory } from './transport';\n\n/** A data channel as the P2P engine needs it; opens later, buffers nothing itself. */\nexport type DataChannelLike = {\n send(frame: string): void;\n onMessage(cb: (frame: string) => void): () => void;\n onOpen(cb: () => void): () => void;\n onClose(cb: () => void): () => void;\n close(): void;\n};\n\n/**\n * Creates one peer link. `polite` assigns the perfect-negotiation role (derived\n * deterministically from origin ordering); `sendSignal` routes offer/answer/ICE payloads\n * through the relay; `signal` delivers the remote side's payloads.\n */\nexport type PeerConnector = (opt: {\n readonly remote: string;\n readonly polite: boolean;\n readonly sendSignal: (data: unknown) => void;\n}) => {\n readonly channel: DataChannelLike;\n signal(data: unknown): void;\n close(): void;\n};\n\n/** WebRTC `PeerConnector` implementing the perfect-negotiation pattern. Browser-only. */\nexport function rtcPeerConnector(config?: RTCConfiguration): PeerConnector {\n return ({ polite, sendSignal }) => {\n const pc = new RTCPeerConnection(config);\n const messageCbs = new Set<(frame: string) => void>();\n const openCbs = new Set<() => void>();\n const closeCbs = new Set<() => void>();\n const pending: string[] = [];\n let dc: RTCDataChannel | null = null;\n let makingOffer = false;\n let ignoreOffer = false;\n\n const attach = (channel: RTCDataChannel): void => {\n dc = channel;\n channel.onmessage = (e) => {\n for (const cb of [...messageCbs]) cb(String(e.data));\n };\n channel.onopen = () => {\n for (const frame of pending.splice(0)) channel.send(frame);\n for (const cb of [...openCbs]) cb();\n };\n channel.onclose = () => {\n for (const cb of [...closeCbs]) cb();\n };\n };\n\n if (!polite) attach(pc.createDataChannel('mmstack-mesh'));\n else pc.ondatachannel = (e) => attach(e.channel);\n\n pc.onicecandidate = (e) => {\n if (e.candidate) sendSignal({ ice: e.candidate.toJSON() });\n };\n pc.onnegotiationneeded = async () => {\n try {\n makingOffer = true;\n await pc.setLocalDescription();\n sendSignal({ description: pc.localDescription });\n } finally {\n makingOffer = false;\n }\n };\n\n return {\n channel: {\n send: (frame) => {\n if (dc && dc.readyState === 'open') dc.send(frame);\n else pending.push(frame);\n },\n onMessage: (cb) => (messageCbs.add(cb), () => messageCbs.delete(cb)),\n onOpen: (cb) => (openCbs.add(cb), () => openCbs.delete(cb)),\n onClose: (cb) => (closeCbs.add(cb), () => closeCbs.delete(cb)),\n close: () => dc?.close(),\n },\n signal: async (data) => {\n const { description, ice } = (data ?? {}) as {\n description?: RTCSessionDescriptionInit;\n ice?: RTCIceCandidateInit;\n };\n if (description) {\n const collision =\n description.type === 'offer' &&\n (makingOffer || pc.signalingState !== 'stable');\n ignoreOffer = !polite && collision;\n if (ignoreOffer) return;\n await pc.setRemoteDescription(description);\n if (description.type === 'offer') {\n await pc.setLocalDescription();\n sendSignal({ description: pc.localDescription });\n }\n } else if (ice) {\n try {\n await pc.addIceCandidate(ice);\n } catch (err) {\n if (!ignoreOffer) throw err;\n }\n }\n },\n close: () => {\n dc?.close();\n pc.close();\n },\n };\n };\n}\n\ntype P2PMsg =\n | { t: 'hello'; wm: Record<string, number> }\n | { t: 'state'; root: unknown; wm: Record<string, number> }\n | { t: 'uptodate' }\n | { t: 'env'; env: OpEnvelope };\n\nexport type WebRtcMeshOptions = {\n readonly room: string;\n readonly writer: string;\n /** Signaling path to the relay (ws or direct) — data flows peer-to-peer. */\n readonly signaling: MeshTransportFactory;\n /** Peer link factory; defaults to {@link rtcPeerConnector}. Injectable for tests. */\n readonly connector?: PeerConnector;\n readonly policies?: readonly MergePolicyEntry[];\n readonly policyVersion?: number;\n readonly injector?: Injector;\n};\n\nexport type WebRtcMeshRef = {\n readonly status: Signal<'connecting' | 'live'>;\n /** Origins with an OPEN data channel. */\n readonly peers: Signal<readonly string[]>;\n close(): void;\n};\n\ntype Peer = {\n link: ReturnType<PeerConnector>;\n open: boolean;\n unsubs: (() => void)[];\n};\n\n/**\n * Peer-to-peer mesh sync (unsequenced topology, op-protocol RFC §4): the relay only signals\n * and tracks membership; envelopes flow over WebRTC data channels and converge via the\n * per-path register map. Catch-up is pairwise: on channel open both sides exchange\n * watermarks; a side whose state is strictly covered hydrates from the other. Two peers that\n * diverged while BOTH held state keep their convergent go-forward guarantees but do not\n * retroactively merge history (same contract as the tab rung).\n */\nexport function webRtcMesh<T extends object>(\n source: WritableSignal<T>,\n opt: WebRtcMeshOptions,\n): WebRtcMeshRef {\n const injector = opt.injector ?? inject(Injector);\n const connector = opt.connector ?? rtcPeerConnector();\n const status = signal<'connecting' | 'live'>('connecting');\n const openPeers = signal<ReadonlySet<string>>(new Set());\n const peers = new Map<string, Peer>();\n let signalingUnsubs: (() => void)[] = [];\n let transport: MeshTransport | null = null;\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n let closed = false;\n\n const sync = opSync(source, {\n writer: opt.writer,\n policies: opt.policies,\n policyVersion: opt.policyVersion,\n injector,\n });\n\n const covered = (\n mine: Record<string, number>,\n theirs: Record<string, number>,\n ): boolean => Object.entries(mine).every(([o, v]) => (theirs[o] ?? 0) >= v);\n\n const sendTo = (peer: Peer, msg: P2PMsg): void => {\n peer.link.channel.send(JSON.stringify(msg));\n };\n\n const broadcast = (msg: P2PMsg): void => {\n for (const peer of peers.values()) {\n if (peer.open) sendTo(peer, msg);\n }\n };\n\n const dropPeer = (origin: string): void => {\n const peer = peers.get(origin);\n if (!peer) return;\n peers.delete(origin);\n for (const unsub of peer.unsubs) unsub();\n peer.link.close();\n openPeers.update((set) => {\n const next = new Set(set);\n next.delete(origin);\n return next;\n });\n };\n\n const ensurePeer = (origin: string): Peer => {\n let peer = peers.get(origin);\n if (peer) return peer;\n const link = connector({\n remote: origin,\n polite: sync.origin > origin,\n sendSignal: (data) =>\n transport?.send({ t: 'signal', room: opt.room, to: origin, data }),\n });\n peer = { link, open: false, unsubs: [] };\n peers.set(origin, peer);\n peer.unsubs.push(\n link.channel.onOpen(() => {\n peer!.open = true;\n openPeers.update((set) => new Set(set).add(origin));\n sendTo(peer!, { t: 'hello', wm: sync.watermark() });\n }),\n link.channel.onMessage((frame) => {\n let msg: P2PMsg;\n try {\n msg = JSON.parse(frame) as P2PMsg;\n } catch {\n return;\n }\n handlePeerMsg(peer!, msg);\n }),\n link.channel.onClose(() => dropPeer(origin)),\n );\n return peer;\n };\n\n const handlePeerMsg = (peer: Peer, msg: P2PMsg): void => {\n switch (msg.t) {\n case 'hello': {\n const snap = sync.snapshot();\n if (covered(snap.wm, msg.wm)) sendTo(peer, { t: 'uptodate' });\n else sendTo(peer, { t: 'state', root: snap.root, wm: snap.wm });\n return;\n }\n case 'state': {\n if (covered(sync.watermark(), msg.wm)) {\n sync.hydrate(msg.root as T, msg.wm);\n }\n return;\n }\n case 'uptodate':\n return;\n case 'env':\n sync.receive(msg.env);\n return;\n }\n };\n\n const handleSignaling = (msg: ServerMsg): void => {\n if (msg.room !== opt.room) return;\n switch (msg.t) {\n case 'welcome':\n for (const origin of msg.members) ensurePeer(origin);\n status.set('live');\n return;\n case 'member':\n if (msg.gone) dropPeer(msg.origin);\n else ensurePeer(msg.origin);\n return;\n case 'signal':\n ensurePeer(msg.from).link.signal(msg.data);\n return;\n case 'reject':\n close();\n return;\n }\n };\n\n const connectSignaling = (): void => {\n if (closed) return;\n for (const unsub of signalingUnsubs.splice(0)) unsub();\n const t = opt.signaling();\n transport = t;\n signalingUnsubs = [\n t.onMessage(handleSignaling),\n t.onClose(() => {\n if (closed || transport !== t) return;\n transport = null;\n reconnectTimer = setTimeout(connectSignaling, 1000);\n }),\n ];\n t.send({\n t: 'hello',\n room: opt.room,\n origin: sync.origin,\n proto: MESH_PROTO_VERSION,\n policyVersion: opt.policyVersion ?? 0,\n });\n };\n\n const unsubLocal = sync.subscribe((env) => broadcast({ t: 'env', env }));\n\n const close = (): void => {\n if (closed) return;\n closed = true;\n if (reconnectTimer !== undefined) clearTimeout(reconnectTimer);\n unsubLocal();\n for (const origin of [...peers.keys()]) dropPeer(origin);\n for (const unsub of signalingUnsubs.splice(0)) unsub();\n transport?.close();\n transport = null;\n sync.destroy();\n status.set('connecting');\n };\n\n injector.get(DestroyRef).onDestroy(close);\n connectSignaling();\n\n return {\n status: status.asReadonly(),\n peers: computed(() => [...openPeers()]),\n close,\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mmstack-mesh.mjs","sources":["../../../../../packages/mesh/client/src/lib/mesh-sync.ts","../../../../../packages/mesh/client/src/lib/transport.ts","../../../../../packages/mesh/client/src/lib/webrtc-mesh.ts","../../../../../packages/mesh/client/src/mmstack-mesh.ts"],"sourcesContent":["import {\n computed,\n DestroyRef,\n inject,\n Injector,\n isDevMode,\n runInInjectionContext,\n signal,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport {\n checkEnvelope,\n MESH_PROTO_VERSION,\n type OpPolicy,\n type PresenceState,\n type PrincipalCtx,\n type SeqEnvelope,\n type ServerMsg,\n} from '@mmstack/mesh-protocol';\nimport {\n opSync,\n registerResource,\n type AsyncStore,\n type MergePolicyEntry,\n type OpEnvelope,\n type OpSync,\n type ResourceLike,\n} from '@mmstack/primitives';\nimport type { MeshTransport, MeshTransportFactory } from './transport';\n\nexport type MeshStatus =\n | 'connecting'\n | 'live'\n | 'reconnecting'\n | 'ejected'\n | 'closed';\n\nexport type MeshPeer = PresenceState;\n\n/**\n * A composed, user-facing health status for a synced store the surface that\n * turns a versioned reject from a dead socket into a speakable 'outdated' banner.\n */\nexport type SyncHealthStatus =\n | 'live'\n | 'offline'\n | 'outdated'\n | 'ejected'\n | 'degraded';\n\nexport type SyncHealth = {\n readonly status: SyncHealthStatus;\n /** Why, when the status is `outdated`/`ejected`/`degraded`. */\n readonly reason?:\n | 'proto'\n | 'policy-version'\n | 'schema'\n | 'quota'\n | 'worker'\n | (string & {});\n /** `Date.now()` of the last successful sync (welcome or applied env). */\n readonly lastSyncedAt?: number;\n};\n\n// a versioned reject means \"your build is behind\" → prompt an update, not a dead socket\nconst OUTDATED_REASONS: ReadonlySet<string> = new Set([\n 'proto',\n 'policy-version',\n 'schema',\n]);\n\nexport type MeshSyncOptions = {\n readonly room: string;\n /** Opaque principal pseudonym — provided, never minted. */\n readonly writer: string;\n readonly transport: MeshTransportFactory;\n /** Per-path merge policies for rebase/convergence (`lww` default). */\n readonly policies?: readonly MergePolicyEntry[];\n /** Emit-side validation, symmetric with the relay's (tripwire honesty). */\n readonly policy?: OpPolicy;\n /** kind/claims of this principal, so a shared policy evaluates identically on both sides. */\n readonly ctx?: Omit<PrincipalCtx, 'writer'>;\n readonly policyVersion?: number;\n /** The data shape this client speaks. Older-than-room → `outdated`, and a\n * newer-schema migration envelope arriving mid-session flips this client to `outdated` too. */\n readonly schemaVersion?: number;\n /** Exponential backoff cap for reconnects (default 15s; base 500ms + jitter). */\n readonly reconnect?: { readonly maxDelayMs?: number };\n readonly injector?: Injector;\n /** Register with the nearest transition scope so (re)connection surfaces as `pending`. */\n readonly register?: 'track' | 'suspend';\n readonly onEject?: (reason: string) => void;\n /**\n * Hold the connection until the local base is assembled. `meshSync` awaits this before it connects\n * (and before it restores an `outbox`), so a store hydrated from another source first (a worker\n * graph, a disk snapshot) is in place when the room welcome arrives and rebases pending on top.\n * The status stays `connecting` while it is pending. A rejection is treated as ready, so a base\n * that fails to load never wedges the connection.\n */\n readonly whenReady?: () => PromiseLike<void> | void;\n /**\n * Persist the unacknowledged local outbox (and this client's stable origin) so offline writes\n * survive a REBOOT, not just a live reconnect: on boot they are restored and rebased onto the room\n * on the next welcome, instead of being lost with in-memory state. The payload is written to\n * `store` under `key`, debounced.\n *\n * By default (`crossTab: 'queue'`) a Web Lock on `key` makes this a single-writer-per-key resource:\n * a second tab sharing the key WAITS (stays `connecting`, surfaced via `status`/`health`) until the\n * first releases, rather than restoring the same origin and colliding on version mints. Set\n * `crossTab: 'off'` to skip the lock and coordinate ownership yourself (e.g. a per-tab key, or\n * leader election over `tabSync`). A debounced write means a hard crash within the debounce window\n * can drop the very last mint — a small, best-effort gap; tune it with `debounceMs`.\n */\n readonly outbox?: {\n readonly key: string;\n readonly store: AsyncStore;\n /** Coalesce outbox writes by this many ms (default 300; `0` = write on every change). */\n readonly debounceMs?: number;\n /**\n * Cross-tab contention for the shared `key`. `'queue'` (default) holds a Web Lock so only one tab\n * owns the durable outbox at a time; others wait. `'off'` skips the lock (you coordinate).\n * (A future `'ephemeral'` — non-leaders run live with a throwaway origin — is planned.)\n */\n readonly crossTab?: 'queue' | 'off';\n };\n};\n\n/** The shape persisted under `outbox.key`: the stable origin, the emit high-water, and the tail. */\ntype PersistedOutbox = {\n readonly origin: string;\n readonly version: number;\n readonly envs: readonly OpEnvelope[];\n};\n\nexport type MeshSyncRef = {\n readonly status: Signal<MeshStatus>;\n /** Composed sync-health for a user-facing surface. */\n readonly health: Signal<SyncHealth>;\n readonly peers: Signal<readonly MeshPeer[]>;\n /** Publish this client's ephemeral presence payload (cursor, section, activity…). */\n setPresence(data: unknown): void;\n close(): void;\n};\n\nconst RECONNECT_BASE_MS = 500;\nconst OUTBOX_DEBOUNCE_MS = 300;\n\n/**\n * Replicates a signal store across clients through a relay room: local writes emit stamped\n * envelopes, remote envelopes fold in convergently, reconnects resume via delta or snapshot\n * with unacknowledged local writes rebased on top, and presence rides an ephemeral channel.\n * A synced store reads exactly like a local one — connection state surfaces only through\n * `status` and the transition scope.\n */\nexport function meshSync<T extends object>(\n source: WritableSignal<T>,\n opt: MeshSyncOptions,\n): MeshSyncRef {\n const injector = opt.injector ?? inject(Injector);\n const status = signal<MeshStatus>('connecting');\n const lastReason = signal<string | undefined>(undefined);\n const lastSyncedAt = signal<number | undefined>(undefined);\n const peerMap = signal<ReadonlyMap<string, MeshPeer>>(new Map());\n const peers = computed(() => [...peerMap().values()]);\n const policyVersion = opt.policyVersion ?? 0;\n\n const health = computed<SyncHealth>(() => {\n const at = lastSyncedAt();\n switch (status()) {\n case 'live':\n return { status: 'live', lastSyncedAt: at };\n case 'ejected': {\n const reason = lastReason();\n return reason && OUTDATED_REASONS.has(reason)\n ? { status: 'outdated', reason, lastSyncedAt: at }\n : { status: 'ejected', reason, lastSyncedAt: at };\n }\n default: // connecting / reconnecting / closed — not reachable\n return { status: 'offline', lastSyncedAt: at };\n }\n });\n\n // Created lazily: with a persisted outbox we must adopt the stored origin BEFORE minting anything\n let sync!: OpSync<T>;\n let started = false;\n let unsubLocal: () => void = () => undefined;\n\n const unacked = new Map<number, OpEnvelope>();\n let highestAcked = 0;\n let lastSeq = 0;\n let epoch: string | undefined;\n let presenceData: unknown;\n let hasPresence = false;\n let transport: MeshTransport | null = null;\n let attempts = 0;\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n let unsubs: (() => void)[] = [];\n let closed = false;\n let persistTimer: ReturnType<typeof setTimeout> | undefined;\n // single-writer Web Lock (crossTab:'queue'): `release` frees it for the next tab once acquired;\n // `cancel` aborts a still-queued request if we tear down before the lock is ever granted.\n let releaseLock: (() => void) | undefined;\n let cancelLock: (() => void) | undefined;\n\n const doPersist = (): void => {\n if (!opt.outbox || !started) return;\n const payload: PersistedOutbox = {\n origin: sync.origin,\n version: sync.watermark()[sync.origin] ?? 0,\n envs: [...unacked.values()],\n };\n void Promise.resolve(opt.outbox.store.set(opt.outbox.key, payload));\n };\n // coalesce outbox writes; `immediate` forces a synchronous-path write (first boot, teardown)\n const persistOutbox = (immediate = false): void => {\n if (!opt.outbox) return;\n if (immediate) {\n if (persistTimer !== undefined) clearTimeout(persistTimer);\n persistTimer = undefined;\n doPersist();\n return;\n }\n if (persistTimer !== undefined) return;\n persistTimer = setTimeout(() => {\n persistTimer = undefined;\n doPersist();\n }, opt.outbox.debounceMs ?? OUTBOX_DEBOUNCE_MS);\n };\n\n const terminal = (state: 'ejected' | 'closed', reason?: string): void => {\n closed = true;\n cancelLock?.(); // drop a still-queued lock request so we never steal it after teardown\n releaseLock?.(); // free a held lock for the next waiting tab\n cancelLock = releaseLock = undefined;\n if (reconnectTimer !== undefined) clearTimeout(reconnectTimer);\n for (const unsub of unsubs.splice(0)) unsub();\n unsubLocal();\n persistOutbox(true); // save the still-unacked tail for the next boot before dropping it\n unacked.clear();\n transport?.close();\n transport = null;\n peerMap.set(new Map());\n lastReason.set(reason);\n status.set(state);\n if (reason !== undefined) opt.onEject?.(reason);\n };\n\n const sendEnv = (env: OpEnvelope): void => {\n transport?.send({ t: 'env', room: opt.room, env });\n };\n\n const flushUnacked = (): void => {\n for (const env of [...unacked.values()].sort(\n (a, b) => a.version - b.version,\n )) {\n sendEnv(env);\n }\n };\n\n const handle = (msg: ServerMsg): void => {\n if (msg.room !== opt.room) return;\n switch (msg.t) {\n case 'welcome': {\n if (epoch !== undefined && msg.epoch !== epoch) lastSeq = 0;\n epoch = msg.epoch;\n peerMap.set(new Map(msg.peers.map((p) => [p.origin, p])));\n if (msg.mode === 'delta') {\n for (const env of msg.envs) applyRemote(env);\n } else if (msg.mode === 'snapshot') {\n sync.hydrate(msg.root as T, { [sync.origin]: highestAcked });\n lastSeq = msg.seq;\n } else if (msg.seq === 0 && lastSeq === 0) {\n sync.seed();\n }\n lastSeq = Math.max(lastSeq, msg.seq);\n attempts = 0;\n lastSyncedAt.set(Date.now());\n status.set('live');\n flushUnacked();\n if (hasPresence)\n transport?.send({\n t: 'presence',\n room: opt.room,\n data: presenceData,\n });\n return;\n }\n case 'env':\n applyRemote(msg.env);\n return;\n case 'presence': {\n const next = new Map(peerMap());\n if (msg.gone) next.delete(msg.peer.origin);\n else next.set(msg.peer.origin, msg.peer);\n peerMap.set(next);\n return;\n }\n case 'eject':\n if (msg.writer === opt.writer) terminal('ejected', msg.reason);\n return;\n case 'reject':\n terminal('ejected', msg.reason);\n return;\n }\n };\n\n const applyRemote = (env: SeqEnvelope): void => {\n lastSeq = Math.max(lastSeq, env.seq);\n lastSyncedAt.set(Date.now());\n // a migration to a shape newer than ours: stop applying + surface 'outdated' (never downgrade-interpret newer data)\n if (\n opt.schemaVersion !== undefined &&\n env.schemaVersion !== undefined &&\n env.schemaVersion > opt.schemaVersion\n ) {\n terminal('ejected', 'schema');\n return;\n }\n if (env.origin === sync.origin) {\n unacked.delete(env.version);\n highestAcked = Math.max(highestAcked, env.version);\n persistOutbox();\n return;\n }\n sync.receive(env);\n };\n\n const connect = (): void => {\n if (closed) return;\n for (const unsub of unsubs.splice(0)) unsub();\n const t = opt.transport();\n transport = t;\n unsubs = [\n t.onMessage(handle),\n t.onClose(() => {\n if (closed || transport !== t) return;\n transport = null;\n status.set('reconnecting');\n const delay = Math.min(\n opt.reconnect?.maxDelayMs ?? 15_000,\n RECONNECT_BASE_MS * 2 ** attempts++,\n );\n reconnectTimer = setTimeout(\n () => {\n reconnectTimer = undefined;\n connect();\n },\n delay + Math.random() * 100,\n );\n }),\n ];\n t.send({\n t: 'hello',\n room: opt.room,\n origin: sync.origin,\n proto: MESH_PROTO_VERSION,\n policyVersion,\n seq: lastSeq > 0 ? lastSeq : undefined,\n schemaVersion: opt.schemaVersion,\n });\n };\n\n const wireLocal = (): void => {\n unsubLocal = sync.subscribe((env) => {\n const violation = checkEnvelope(opt.policy, env, {\n ...opt.ctx,\n writer: opt.writer,\n });\n if (violation) {\n if (isDevMode()) {\n console.warn(\n '[@mmstack/mesh] local write violates the room policy — not sent',\n violation,\n );\n }\n return;\n }\n unacked.set(env.version, env);\n persistOutbox();\n if (status() === 'live') sendEnv(env);\n });\n };\n\n const initCore = (restore?: PersistedOutbox): void => {\n if (closed) return;\n sync = opSync(source, {\n writer: opt.writer,\n policies: opt.policies,\n policyVersion,\n injector,\n origin: restore?.origin,\n });\n started = true;\n wireLocal();\n if (restore && (restore.envs.length > 0 || restore.version > 0)) {\n sync.restore(restore.envs, restore.version); // → subscribe repopulates `unacked` for resend\n }\n persistOutbox(true); // pin the (possibly freshly minted) origin so later boots reuse it\n connect();\n };\n\n if (opt.register) {\n const connection: ResourceLike = {\n status: computed(() => {\n const s = status();\n return s === 'connecting'\n ? 'loading'\n : s === 'reconnecting'\n ? 'reloading'\n : s === 'ejected'\n ? 'error'\n : 'resolved';\n }),\n isLoading: computed(\n () => status() === 'connecting' || status() === 'reconnecting',\n ),\n hasValue: () => true,\n };\n runInInjectionContext(injector, () =>\n registerResource(connection, { suspends: opt.register === 'suspend' }),\n );\n }\n\n injector.get(DestroyRef).onDestroy(() => {\n if (!closed) terminal('closed');\n unsubLocal();\n if (started) sync.destroy();\n });\n\n // Load the persisted outbox, then boot with the adopted origin. A fresh/unreadable slot boots clean.\n const bootFromDisk = async (): Promise<void> => {\n if (closed || !opt.outbox) return;\n let saved: PersistedOutbox | undefined;\n try {\n const raw = await opt.outbox.store.get(opt.outbox.key);\n saved =\n raw && typeof raw === 'object' && 'origin' in raw\n ? (raw as PersistedOutbox)\n : undefined;\n } catch {\n saved = undefined; // an unreadable slot must not wedge the boot\n }\n initCore(saved);\n };\n\n const beginConnect = (): void => {\n if (closed) return;\n if (!opt.outbox) {\n initCore();\n } else if ((opt.outbox.crossTab ?? 'queue') === 'off') {\n void bootFromDisk(); // no single-writer lock — the app coordinates ownership\n } else {\n // crossTab:'queue' — hold an exclusive Web Lock on the key for this tab's lifetime, so a\n // second tab sharing the key WAITS (stays 'connecting') instead of restoring the same origin\n // and colliding on version mints. The lock auto-releases if the tab crashes.\n const locks = globalThis.navigator?.locks;\n if (!locks) {\n if (isDevMode()) {\n console.warn(\n '[@mmstack/mesh] outbox crossTab:\"queue\" needs the Web Locks API (navigator.locks), unavailable here — running WITHOUT a single-writer lock. Two tabs sharing this key can diverge; coordinate ownership yourself, or set crossTab:\"off\" to silence this.',\n );\n }\n void bootFromDisk();\n } else {\n const abort = new AbortController();\n cancelLock = () => abort.abort();\n void locks\n .request(\n `@mmstack/mesh:outbox:${opt.outbox.key}`,\n { mode: 'exclusive', signal: abort.signal },\n () => {\n cancelLock = undefined; // granted — no longer abortable, only releasable\n if (closed) return Promise.resolve();\n // hold the lock until teardown resolves this promise\n return new Promise<void>((release) => {\n releaseLock = release;\n void bootFromDisk();\n });\n },\n )\n .catch((e: unknown) => {\n // an aborted request is our own teardown; any other failure → degrade to no-lock\n if (!closed && (e as { name?: string })?.name !== 'AbortError') {\n void bootFromDisk();\n }\n });\n }\n }\n };\n\n // Assemble the local base first (worker hydration, disk snapshot), THEN connect, so the room\n // welcome rebases pending onto a populated store instead of racing it. A rejection is treated as\n // ready so a failed base load never wedges the connection.\n if (opt.whenReady) {\n void Promise.resolve()\n .then(() => opt.whenReady?.())\n .then(\n () => beginConnect(),\n () => beginConnect(),\n );\n } else {\n beginConnect();\n }\n\n return {\n status: status.asReadonly(),\n health,\n peers,\n setPresence: (data) => {\n presenceData = data;\n hasPresence = true;\n if (status() === 'live') {\n transport?.send({ t: 'presence', room: opt.room, data });\n }\n },\n close: () => {\n if (!closed) terminal('closed');\n unsubLocal();\n if (started) sync.destroy();\n },\n };\n}\n","import { isDevMode } from '@angular/core';\nimport type {\n ClientMsg,\n PrincipalCtx,\n Relay,\n ServerMsg,\n} from '@mmstack/mesh-protocol';\n\n/**\n * One live connection to a relay. `meshSync` calls the factory per (re)connect; a transport\n * represents a single connection and never reconnects itself.\n */\nexport type MeshTransport = {\n send(msg: ClientMsg): void;\n onMessage(cb: (msg: ServerMsg) => void): () => void;\n onClose(cb: () => void): () => void;\n close(): void;\n};\n\nexport type MeshTransportFactory = () => MeshTransport;\n\n/**\n * JSON-over-WebSocket transport. Messages sent before the socket opens are buffered and\n * flushed on open; malformed inbound frames are dropped.\n */\nexport function webSocketTransport(\n url: string,\n protocols?: string | string[],\n): MeshTransportFactory {\n return () => {\n const ws = new WebSocket(url, protocols);\n const messageCbs = new Set<(msg: ServerMsg) => void>();\n const closeCbs = new Set<() => void>();\n const pending: string[] = [];\n\n ws.onopen = () => {\n for (const frame of pending.splice(0)) ws.send(frame);\n };\n ws.onmessage = (event) => {\n let msg: ServerMsg;\n try {\n msg = JSON.parse(String(event.data)) as ServerMsg;\n } catch {\n if (isDevMode()) {\n console.warn('[@mmstack/mesh] dropped unparseable frame (expected JSON text)');\n }\n return;\n }\n for (const cb of [...messageCbs]) cb(msg);\n };\n ws.onclose = () => {\n for (const cb of [...closeCbs]) cb();\n };\n\n return {\n send: (msg) => {\n const frame = JSON.stringify(msg);\n if (ws.readyState === WebSocket.OPEN) ws.send(frame);\n else if (ws.readyState === WebSocket.CONNECTING) pending.push(frame);\n },\n onMessage: (cb) => {\n messageCbs.add(cb);\n return () => messageCbs.delete(cb);\n },\n onClose: (cb) => {\n closeCbs.add(cb);\n return () => closeCbs.delete(cb);\n },\n close: () => ws.close(),\n };\n };\n}\n\n/**\n * Connects directly to an in-process `createRelay` — no network. The backbone of full-loop\n * tests, and handy for single-process demos or an SSR-side room.\n */\nexport function directTransport(\n relay: Relay,\n ctx: PrincipalCtx,\n): MeshTransportFactory {\n return () => {\n const messageCbs = new Set<(msg: ServerMsg) => void>();\n const closeCbs = new Set<() => void>();\n let open = true;\n\n const connection = relay.connect(\n {\n send: (msg) => {\n if (!open) return;\n for (const cb of [...messageCbs]) cb(msg);\n },\n close: () => {\n if (!open) return;\n open = false;\n connection.disconnect();\n for (const cb of [...closeCbs]) cb();\n },\n },\n ctx,\n );\n\n return {\n send: (msg) => {\n if (open) connection.receive(msg);\n },\n onMessage: (cb) => {\n messageCbs.add(cb);\n return () => messageCbs.delete(cb);\n },\n onClose: (cb) => {\n closeCbs.add(cb);\n return () => closeCbs.delete(cb);\n },\n close: () => {\n if (!open) return;\n open = false;\n connection.disconnect();\n for (const cb of [...closeCbs]) cb();\n },\n };\n };\n}\n","import {\n computed,\n DestroyRef,\n inject,\n Injector,\n signal,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport { MESH_PROTO_VERSION, type ServerMsg } from '@mmstack/mesh-protocol';\nimport {\n opSync,\n type MergePolicyEntry,\n type OpEnvelope,\n} from '@mmstack/primitives';\nimport type { MeshTransport, MeshTransportFactory } from './transport';\n\n/** A data channel as the P2P engine needs it; opens later, buffers nothing itself. */\nexport type DataChannelLike = {\n send(frame: string): void;\n onMessage(cb: (frame: string) => void): () => void;\n onOpen(cb: () => void): () => void;\n onClose(cb: () => void): () => void;\n close(): void;\n};\n\n/**\n * Creates one peer link. `polite` assigns the perfect-negotiation role (derived\n * deterministically from origin ordering); `sendSignal` routes offer/answer/ICE payloads\n * through the relay; `signal` delivers the remote side's payloads.\n */\nexport type PeerConnector = (opt: {\n readonly remote: string;\n readonly polite: boolean;\n readonly sendSignal: (data: unknown) => void;\n}) => {\n readonly channel: DataChannelLike;\n signal(data: unknown): void;\n close(): void;\n};\n\n/** WebRTC `PeerConnector` implementing the perfect-negotiation pattern. Browser-only. */\nexport function rtcPeerConnector(config?: RTCConfiguration): PeerConnector {\n return ({ polite, sendSignal }) => {\n const pc = new RTCPeerConnection(config);\n const messageCbs = new Set<(frame: string) => void>();\n const openCbs = new Set<() => void>();\n const closeCbs = new Set<() => void>();\n const pending: string[] = [];\n let dc: RTCDataChannel | null = null;\n let makingOffer = false;\n let ignoreOffer = false;\n\n const attach = (channel: RTCDataChannel): void => {\n dc = channel;\n channel.onmessage = (e) => {\n for (const cb of [...messageCbs]) cb(String(e.data));\n };\n channel.onopen = () => {\n for (const frame of pending.splice(0)) channel.send(frame);\n for (const cb of [...openCbs]) cb();\n };\n channel.onclose = () => {\n for (const cb of [...closeCbs]) cb();\n };\n };\n\n if (!polite) attach(pc.createDataChannel('mmstack-mesh'));\n else pc.ondatachannel = (e) => attach(e.channel);\n\n pc.onicecandidate = (e) => {\n if (e.candidate) sendSignal({ ice: e.candidate.toJSON() });\n };\n pc.onnegotiationneeded = async () => {\n try {\n makingOffer = true;\n await pc.setLocalDescription();\n sendSignal({ description: pc.localDescription });\n } finally {\n makingOffer = false;\n }\n };\n\n return {\n channel: {\n send: (frame) => {\n if (dc && dc.readyState === 'open') dc.send(frame);\n else pending.push(frame);\n },\n onMessage: (cb) => (messageCbs.add(cb), () => messageCbs.delete(cb)),\n onOpen: (cb) => (openCbs.add(cb), () => openCbs.delete(cb)),\n onClose: (cb) => (closeCbs.add(cb), () => closeCbs.delete(cb)),\n close: () => dc?.close(),\n },\n signal: async (data) => {\n const { description, ice } = (data ?? {}) as {\n description?: RTCSessionDescriptionInit;\n ice?: RTCIceCandidateInit;\n };\n if (description) {\n const collision =\n description.type === 'offer' &&\n (makingOffer || pc.signalingState !== 'stable');\n ignoreOffer = !polite && collision;\n if (ignoreOffer) return;\n await pc.setRemoteDescription(description);\n if (description.type === 'offer') {\n await pc.setLocalDescription();\n sendSignal({ description: pc.localDescription });\n }\n } else if (ice) {\n try {\n await pc.addIceCandidate(ice);\n } catch (err) {\n if (!ignoreOffer) throw err;\n }\n }\n },\n close: () => {\n dc?.close();\n pc.close();\n },\n };\n };\n}\n\ntype P2PMsg =\n | { t: 'hello'; wm: Record<string, number> }\n | { t: 'state'; root: unknown; wm: Record<string, number> }\n | { t: 'uptodate' }\n | { t: 'env'; env: OpEnvelope };\n\nexport type WebRtcMeshOptions = {\n readonly room: string;\n readonly writer: string;\n /** Signaling path to the relay (ws or direct) — data flows peer-to-peer. */\n readonly signaling: MeshTransportFactory;\n /** Peer link factory; defaults to {@link rtcPeerConnector}. Injectable for tests. */\n readonly connector?: PeerConnector;\n readonly policies?: readonly MergePolicyEntry[];\n readonly policyVersion?: number;\n readonly injector?: Injector;\n};\n\nexport type WebRtcMeshRef = {\n readonly status: Signal<'connecting' | 'live'>;\n /** Origins with an OPEN data channel. */\n readonly peers: Signal<readonly string[]>;\n close(): void;\n};\n\ntype Peer = {\n link: ReturnType<PeerConnector>;\n open: boolean;\n unsubs: (() => void)[];\n};\n\n/**\n * Peer-to-peer mesh sync: the relay only signals\n * and tracks membership; envelopes flow over WebRTC data channels and converge via the\n * per-path register map. Catch-up is pairwise: on channel open both sides exchange\n * watermarks; a side whose state is strictly covered hydrates from the other. Two peers that\n * diverged while BOTH held state keep their convergent go-forward guarantees but do not\n * retroactively merge history (same contract as the tab rung).\n */\nexport function webRtcMesh<T extends object>(\n source: WritableSignal<T>,\n opt: WebRtcMeshOptions,\n): WebRtcMeshRef {\n const injector = opt.injector ?? inject(Injector);\n const connector = opt.connector ?? rtcPeerConnector();\n const status = signal<'connecting' | 'live'>('connecting');\n const openPeers = signal<ReadonlySet<string>>(new Set());\n const peers = new Map<string, Peer>();\n let signalingUnsubs: (() => void)[] = [];\n let transport: MeshTransport | null = null;\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n let closed = false;\n\n const sync = opSync(source, {\n writer: opt.writer,\n policies: opt.policies,\n policyVersion: opt.policyVersion,\n injector,\n });\n\n const covered = (\n mine: Record<string, number>,\n theirs: Record<string, number>,\n ): boolean => Object.entries(mine).every(([o, v]) => (theirs[o] ?? 0) >= v);\n\n const sendTo = (peer: Peer, msg: P2PMsg): void => {\n peer.link.channel.send(JSON.stringify(msg));\n };\n\n const broadcast = (msg: P2PMsg): void => {\n for (const peer of peers.values()) {\n if (peer.open) sendTo(peer, msg);\n }\n };\n\n const dropPeer = (origin: string): void => {\n const peer = peers.get(origin);\n if (!peer) return;\n peers.delete(origin);\n for (const unsub of peer.unsubs) unsub();\n peer.link.close();\n openPeers.update((set) => {\n const next = new Set(set);\n next.delete(origin);\n return next;\n });\n };\n\n const ensurePeer = (origin: string): Peer => {\n let peer = peers.get(origin);\n if (peer) return peer;\n const link = connector({\n remote: origin,\n polite: sync.origin > origin,\n sendSignal: (data) =>\n transport?.send({ t: 'signal', room: opt.room, to: origin, data }),\n });\n peer = { link, open: false, unsubs: [] };\n peers.set(origin, peer);\n peer.unsubs.push(\n link.channel.onOpen(() => {\n peer!.open = true;\n openPeers.update((set) => new Set(set).add(origin));\n sendTo(peer!, { t: 'hello', wm: sync.watermark() });\n }),\n link.channel.onMessage((frame) => {\n let msg: P2PMsg;\n try {\n msg = JSON.parse(frame) as P2PMsg;\n } catch {\n return;\n }\n handlePeerMsg(peer!, msg);\n }),\n link.channel.onClose(() => dropPeer(origin)),\n );\n return peer;\n };\n\n const handlePeerMsg = (peer: Peer, msg: P2PMsg): void => {\n switch (msg.t) {\n case 'hello': {\n const snap = sync.snapshot();\n if (covered(snap.wm, msg.wm)) sendTo(peer, { t: 'uptodate' });\n else sendTo(peer, { t: 'state', root: snap.root, wm: snap.wm });\n return;\n }\n case 'state': {\n if (covered(sync.watermark(), msg.wm)) {\n sync.hydrate(msg.root as T, msg.wm);\n }\n return;\n }\n case 'uptodate':\n return;\n case 'env':\n sync.receive(msg.env);\n return;\n }\n };\n\n const handleSignaling = (msg: ServerMsg): void => {\n if (msg.room !== opt.room) return;\n switch (msg.t) {\n case 'welcome':\n for (const origin of msg.members) ensurePeer(origin);\n status.set('live');\n return;\n case 'member':\n if (msg.gone) dropPeer(msg.origin);\n else ensurePeer(msg.origin);\n return;\n case 'signal':\n ensurePeer(msg.from).link.signal(msg.data);\n return;\n case 'reject':\n close();\n return;\n }\n };\n\n const connectSignaling = (): void => {\n if (closed) return;\n for (const unsub of signalingUnsubs.splice(0)) unsub();\n const t = opt.signaling();\n transport = t;\n signalingUnsubs = [\n t.onMessage(handleSignaling),\n t.onClose(() => {\n if (closed || transport !== t) return;\n transport = null;\n reconnectTimer = setTimeout(connectSignaling, 1000);\n }),\n ];\n t.send({\n t: 'hello',\n room: opt.room,\n origin: sync.origin,\n proto: MESH_PROTO_VERSION,\n policyVersion: opt.policyVersion ?? 0,\n });\n };\n\n const unsubLocal = sync.subscribe((env) => broadcast({ t: 'env', env }));\n\n const close = (): void => {\n if (closed) return;\n closed = true;\n if (reconnectTimer !== undefined) clearTimeout(reconnectTimer);\n unsubLocal();\n for (const origin of [...peers.keys()]) dropPeer(origin);\n for (const unsub of signalingUnsubs.splice(0)) unsub();\n transport?.close();\n transport = null;\n sync.destroy();\n status.set('connecting');\n };\n\n injector.get(DestroyRef).onDestroy(close);\n connectSignaling();\n\n return {\n status: status.asReadonly(),\n peers: computed(() => [...openPeers()]),\n close,\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAiEA;AACA,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAC;IACpD,OAAO;IACP,gBAAgB;IAChB,QAAQ;AACT,CAAA,CAAC;AA2EF,MAAM,iBAAiB,GAAG,GAAG;AAC7B,MAAM,kBAAkB,GAAG,GAAG;AAE9B;;;;;;AAMG;AACG,SAAU,QAAQ,CACtB,MAAyB,EACzB,GAAoB,EAAA;IAEpB,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC;AACjD,IAAA,MAAM,MAAM,GAAG,MAAM,CAAa,YAAY,6EAAC;AAC/C,IAAA,MAAM,UAAU,GAAG,MAAM,CAAqB,SAAS,iFAAC;AACxD,IAAA,MAAM,YAAY,GAAG,MAAM,CAAqB,SAAS,mFAAC;IAC1D,MAAM,OAAO,GAAG,MAAM,CAAgC,IAAI,GAAG,EAAE,8EAAC;AAChE,IAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,4EAAC;AACrD,IAAA,MAAM,aAAa,GAAG,GAAG,CAAC,aAAa,IAAI,CAAC;AAE5C,IAAA,MAAM,MAAM,GAAG,QAAQ,CAAa,MAAK;AACvC,QAAA,MAAM,EAAE,GAAG,YAAY,EAAE;QACzB,QAAQ,MAAM,EAAE;AACd,YAAA,KAAK,MAAM;gBACT,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE;YAC7C,KAAK,SAAS,EAAE;AACd,gBAAA,MAAM,MAAM,GAAG,UAAU,EAAE;AAC3B,gBAAA,OAAO,MAAM,IAAI,gBAAgB,CAAC,GAAG,CAAC,MAAM;sBACxC,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;AAChD,sBAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE;YACrD;AACA,YAAA;gBACE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,EAAE;;AAEpD,IAAA,CAAC,6EAAC;;AAGF,IAAA,IAAI,IAAgB;IACpB,IAAI,OAAO,GAAG,KAAK;AACnB,IAAA,IAAI,UAAU,GAAe,MAAM,SAAS;AAE5C,IAAA,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB;IAC7C,IAAI,YAAY,GAAG,CAAC;IACpB,IAAI,OAAO,GAAG,CAAC;AACf,IAAA,IAAI,KAAyB;AAC7B,IAAA,IAAI,YAAqB;IACzB,IAAI,WAAW,GAAG,KAAK;IACvB,IAAI,SAAS,GAAyB,IAAI;IAC1C,IAAI,QAAQ,GAAG,CAAC;AAChB,IAAA,IAAI,cAAyD;IAC7D,IAAI,MAAM,GAAmB,EAAE;IAC/B,IAAI,MAAM,GAAG,KAAK;AAClB,IAAA,IAAI,YAAuD;;;AAG3D,IAAA,IAAI,WAAqC;AACzC,IAAA,IAAI,UAAoC;IAExC,MAAM,SAAS,GAAG,MAAW;AAC3B,QAAA,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO;YAAE;AAC7B,QAAA,MAAM,OAAO,GAAoB;YAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AAC3C,YAAA,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;SAC5B;QACD,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrE,IAAA,CAAC;;AAED,IAAA,MAAM,aAAa,GAAG,CAAC,SAAS,GAAG,KAAK,KAAU;QAChD,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE;QACjB,IAAI,SAAS,EAAE;YACb,IAAI,YAAY,KAAK,SAAS;gBAAE,YAAY,CAAC,YAAY,CAAC;YAC1D,YAAY,GAAG,SAAS;AACxB,YAAA,SAAS,EAAE;YACX;QACF;QACA,IAAI,YAAY,KAAK,SAAS;YAAE;AAChC,QAAA,YAAY,GAAG,UAAU,CAAC,MAAK;YAC7B,YAAY,GAAG,SAAS;AACxB,YAAA,SAAS,EAAE;QACb,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,kBAAkB,CAAC;AACjD,IAAA,CAAC;AAED,IAAA,MAAM,QAAQ,GAAG,CAAC,KAA2B,EAAE,MAAe,KAAU;QACtE,MAAM,GAAG,IAAI;AACb,QAAA,UAAU,IAAI,CAAC;AACf,QAAA,WAAW,IAAI,CAAC;AAChB,QAAA,UAAU,GAAG,WAAW,GAAG,SAAS;QACpC,IAAI,cAAc,KAAK,SAAS;YAAE,YAAY,CAAC,cAAc,CAAC;QAC9D,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAAE,YAAA,KAAK,EAAE;AAC7C,QAAA,UAAU,EAAE;AACZ,QAAA,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,CAAC,KAAK,EAAE;QACf,SAAS,EAAE,KAAK,EAAE;QAClB,SAAS,GAAG,IAAI;AAChB,QAAA,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;AACtB,QAAA,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;AACtB,QAAA,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;QACjB,IAAI,MAAM,KAAK,SAAS;AAAE,YAAA,GAAG,CAAC,OAAO,GAAG,MAAM,CAAC;AACjD,IAAA,CAAC;AAED,IAAA,MAAM,OAAO,GAAG,CAAC,GAAe,KAAU;AACxC,QAAA,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;AACpD,IAAA,CAAC;IAED,MAAM,YAAY,GAAG,MAAW;AAC9B,QAAA,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAC1C,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAChC,EAAE;YACD,OAAO,CAAC,GAAG,CAAC;QACd;AACF,IAAA,CAAC;AAED,IAAA,MAAM,MAAM,GAAG,CAAC,GAAc,KAAU;AACtC,QAAA,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI;YAAE;AAC3B,QAAA,QAAQ,GAAG,CAAC,CAAC;YACX,KAAK,SAAS,EAAE;gBACd,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK;oBAAE,OAAO,GAAG,CAAC;AAC3D,gBAAA,KAAK,GAAG,GAAG,CAAC,KAAK;gBACjB,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACzD,gBAAA,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE;AACxB,oBAAA,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI;wBAAE,WAAW,CAAC,GAAG,CAAC;gBAC9C;AAAO,qBAAA,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE;AAClC,oBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;AAC5D,oBAAA,OAAO,GAAG,GAAG,CAAC,GAAG;gBACnB;qBAAO,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,EAAE;oBACzC,IAAI,CAAC,IAAI,EAAE;gBACb;gBACA,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC;gBACpC,QAAQ,GAAG,CAAC;gBACZ,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;AAC5B,gBAAA,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;AAClB,gBAAA,YAAY,EAAE;AACd,gBAAA,IAAI,WAAW;oBACb,SAAS,EAAE,IAAI,CAAC;AACd,wBAAA,CAAC,EAAE,UAAU;wBACb,IAAI,EAAE,GAAG,CAAC,IAAI;AACd,wBAAA,IAAI,EAAE,YAAY;AACnB,qBAAA,CAAC;gBACJ;YACF;AACA,YAAA,KAAK,KAAK;AACR,gBAAA,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;gBACpB;YACF,KAAK,UAAU,EAAE;gBACf,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAC/B,IAAI,GAAG,CAAC,IAAI;oBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;;AACrC,oBAAA,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC;AACxC,gBAAA,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;gBACjB;YACF;AACA,YAAA,KAAK,OAAO;AACV,gBAAA,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM;AAAE,oBAAA,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC;gBAC9D;AACF,YAAA,KAAK,QAAQ;AACX,gBAAA,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC;gBAC/B;;AAEN,IAAA,CAAC;AAED,IAAA,MAAM,WAAW,GAAG,CAAC,GAAgB,KAAU;QAC7C,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC;QACpC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;;AAE5B,QAAA,IACE,GAAG,CAAC,aAAa,KAAK,SAAS;YAC/B,GAAG,CAAC,aAAa,KAAK,SAAS;AAC/B,YAAA,GAAG,CAAC,aAAa,GAAG,GAAG,CAAC,aAAa,EACrC;AACA,YAAA,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC;YAC7B;QACF;QACA,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE;AAC9B,YAAA,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;YAC3B,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC;AAClD,YAAA,aAAa,EAAE;YACf;QACF;AACA,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;AACnB,IAAA,CAAC;IAED,MAAM,OAAO,GAAG,MAAW;AACzB,QAAA,IAAI,MAAM;YAAE;QACZ,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAAE,YAAA,KAAK,EAAE;AAC7C,QAAA,MAAM,CAAC,GAAG,GAAG,CAAC,SAAS,EAAE;QACzB,SAAS,GAAG,CAAC;AACb,QAAA,MAAM,GAAG;AACP,YAAA,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC;AACnB,YAAA,CAAC,CAAC,OAAO,CAAC,MAAK;AACb,gBAAA,IAAI,MAAM,IAAI,SAAS,KAAK,CAAC;oBAAE;gBAC/B,SAAS,GAAG,IAAI;AAChB,gBAAA,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC;gBAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,GAAG,CAAC,SAAS,EAAE,UAAU,IAAI,MAAM,EACnC,iBAAiB,GAAG,CAAC,IAAI,QAAQ,EAAE,CACpC;AACD,gBAAA,cAAc,GAAG,UAAU,CACzB,MAAK;oBACH,cAAc,GAAG,SAAS;AAC1B,oBAAA,OAAO,EAAE;gBACX,CAAC,EACD,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAC5B;AACH,YAAA,CAAC,CAAC;SACH;QACD,CAAC,CAAC,IAAI,CAAC;AACL,YAAA,CAAC,EAAE,OAAO;YACV,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,MAAM,EAAE,IAAI,CAAC,MAAM;AACnB,YAAA,KAAK,EAAE,kBAAkB;YACzB,aAAa;YACb,GAAG,EAAE,OAAO,GAAG,CAAC,GAAG,OAAO,GAAG,SAAS;YACtC,aAAa,EAAE,GAAG,CAAC,aAAa;AACjC,SAAA,CAAC;AACJ,IAAA,CAAC;IAED,MAAM,SAAS,GAAG,MAAW;QAC3B,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,KAAI;YAClC,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE;gBAC/C,GAAG,GAAG,CAAC,GAAG;gBACV,MAAM,EAAE,GAAG,CAAC,MAAM;AACnB,aAAA,CAAC;YACF,IAAI,SAAS,EAAE;gBACb,IAAI,SAAS,EAAE,EAAE;AACf,oBAAA,OAAO,CAAC,IAAI,CACV,iEAAiE,EACjE,SAAS,CACV;gBACH;gBACA;YACF;YACA,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC;AAC7B,YAAA,aAAa,EAAE;YACf,IAAI,MAAM,EAAE,KAAK,MAAM;gBAAE,OAAO,CAAC,GAAG,CAAC;AACvC,QAAA,CAAC,CAAC;AACJ,IAAA,CAAC;AAED,IAAA,MAAM,QAAQ,GAAG,CAAC,OAAyB,KAAU;AACnD,QAAA,IAAI,MAAM;YAAE;AACZ,QAAA,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE;YACpB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,aAAa;YACb,QAAQ;YACR,MAAM,EAAE,OAAO,EAAE,MAAM;AACxB,SAAA,CAAC;QACF,OAAO,GAAG,IAAI;AACd,QAAA,SAAS,EAAE;AACX,QAAA,IAAI,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE;AAC/D,YAAA,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9C;AACA,QAAA,aAAa,CAAC,IAAI,CAAC,CAAC;AACpB,QAAA,OAAO,EAAE;AACX,IAAA,CAAC;AAED,IAAA,IAAI,GAAG,CAAC,QAAQ,EAAE;AAChB,QAAA,MAAM,UAAU,GAAiB;AAC/B,YAAA,MAAM,EAAE,QAAQ,CAAC,MAAK;AACpB,gBAAA,MAAM,CAAC,GAAG,MAAM,EAAE;gBAClB,OAAO,CAAC,KAAK;AACX,sBAAE;sBACA,CAAC,KAAK;AACN,0BAAE;0BACA,CAAC,KAAK;AACN,8BAAE;8BACA,UAAU;AACpB,YAAA,CAAC,CAAC;AACF,YAAA,SAAS,EAAE,QAAQ,CACjB,MAAM,MAAM,EAAE,KAAK,YAAY,IAAI,MAAM,EAAE,KAAK,cAAc,CAC/D;AACD,YAAA,QAAQ,EAAE,MAAM,IAAI;SACrB;QACD,qBAAqB,CAAC,QAAQ,EAAE,MAC9B,gBAAgB,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC,CACvE;IACH;IAEA,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAK;AACtC,QAAA,IAAI,CAAC,MAAM;YAAE,QAAQ,CAAC,QAAQ,CAAC;AAC/B,QAAA,UAAU,EAAE;AACZ,QAAA,IAAI,OAAO;YAAE,IAAI,CAAC,OAAO,EAAE;AAC7B,IAAA,CAAC,CAAC;;AAGF,IAAA,MAAM,YAAY,GAAG,YAA0B;AAC7C,QAAA,IAAI,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE;AAC3B,QAAA,IAAI,KAAkC;AACtC,QAAA,IAAI;AACF,YAAA,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;YACtD,KAAK;gBACH,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,QAAQ,IAAI;AAC5C,sBAAG;sBACD,SAAS;QACjB;AAAE,QAAA,MAAM;AACN,YAAA,KAAK,GAAG,SAAS,CAAC;QACpB;QACA,QAAQ,CAAC,KAAK,CAAC;AACjB,IAAA,CAAC;IAED,MAAM,YAAY,GAAG,MAAW;AAC9B,QAAA,IAAI,MAAM;YAAE;AACZ,QAAA,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE;AACf,YAAA,QAAQ,EAAE;QACZ;AAAO,aAAA,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,OAAO,MAAM,KAAK,EAAE;AACrD,YAAA,KAAK,YAAY,EAAE,CAAC;QACtB;aAAO;;;;AAIL,YAAA,MAAM,KAAK,GAAG,UAAU,CAAC,SAAS,EAAE,KAAK;YACzC,IAAI,CAAC,KAAK,EAAE;gBACV,IAAI,SAAS,EAAE,EAAE;AACf,oBAAA,OAAO,CAAC,IAAI,CACV,0PAA0P,CAC3P;gBACH;gBACA,KAAK,YAAY,EAAE;YACrB;iBAAO;AACL,gBAAA,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE;gBACnC,UAAU,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE;AAChC,gBAAA,KAAK;qBACF,OAAO,CACN,wBAAwB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAA,CAAE,EACxC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,EAC3C,MAAK;AACH,oBAAA,UAAU,GAAG,SAAS,CAAC;AACvB,oBAAA,IAAI,MAAM;AAAE,wBAAA,OAAO,OAAO,CAAC,OAAO,EAAE;;AAEpC,oBAAA,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,KAAI;wBACnC,WAAW,GAAG,OAAO;wBACrB,KAAK,YAAY,EAAE;AACrB,oBAAA,CAAC,CAAC;AACJ,gBAAA,CAAC;AAEF,qBAAA,KAAK,CAAC,CAAC,CAAU,KAAI;;oBAEpB,IAAI,CAAC,MAAM,IAAK,CAAuB,EAAE,IAAI,KAAK,YAAY,EAAE;wBAC9D,KAAK,YAAY,EAAE;oBACrB;AACF,gBAAA,CAAC,CAAC;YACN;QACF;AACF,IAAA,CAAC;;;;AAKD,IAAA,IAAI,GAAG,CAAC,SAAS,EAAE;QACjB,KAAK,OAAO,CAAC,OAAO;aACjB,IAAI,CAAC,MAAM,GAAG,CAAC,SAAS,IAAI;AAC5B,aAAA,IAAI,CACH,MAAM,YAAY,EAAE,EACpB,MAAM,YAAY,EAAE,CACrB;IACL;SAAO;AACL,QAAA,YAAY,EAAE;IAChB;IAEA,OAAO;AACL,QAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;QAC3B,MAAM;QACN,KAAK;AACL,QAAA,WAAW,EAAE,CAAC,IAAI,KAAI;YACpB,YAAY,GAAG,IAAI;YACnB,WAAW,GAAG,IAAI;AAClB,YAAA,IAAI,MAAM,EAAE,KAAK,MAAM,EAAE;AACvB,gBAAA,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;YAC1D;QACF,CAAC;QACD,KAAK,EAAE,MAAK;AACV,YAAA,IAAI,CAAC,MAAM;gBAAE,QAAQ,CAAC,QAAQ,CAAC;AAC/B,YAAA,UAAU,EAAE;AACZ,YAAA,IAAI,OAAO;gBAAE,IAAI,CAAC,OAAO,EAAE;QAC7B,CAAC;KACF;AACH;;ACrfA;;;AAGG;AACG,SAAU,kBAAkB,CAChC,GAAW,EACX,SAA6B,EAAA;AAE7B,IAAA,OAAO,MAAK;QACV,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC;AACxC,QAAA,MAAM,UAAU,GAAG,IAAI,GAAG,EAA4B;AACtD,QAAA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAc;QACtC,MAAM,OAAO,GAAa,EAAE;AAE5B,QAAA,EAAE,CAAC,MAAM,GAAG,MAAK;YACf,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AAAE,gBAAA,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;AACvD,QAAA,CAAC;AACD,QAAA,EAAE,CAAC,SAAS,GAAG,CAAC,KAAK,KAAI;AACvB,YAAA,IAAI,GAAc;AAClB,YAAA,IAAI;AACF,gBAAA,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAc;YACnD;AAAE,YAAA,MAAM;gBACN,IAAI,SAAS,EAAE,EAAE;AACf,oBAAA,OAAO,CAAC,IAAI,CAAC,gEAAgE,CAAC;gBAChF;gBACA;YACF;AACA,YAAA,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC;gBAAE,EAAE,CAAC,GAAG,CAAC;AAC3C,QAAA,CAAC;AACD,QAAA,EAAE,CAAC,OAAO,GAAG,MAAK;AAChB,YAAA,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC;AAAE,gBAAA,EAAE,EAAE;AACtC,QAAA,CAAC;QAED,OAAO;AACL,YAAA,IAAI,EAAE,CAAC,GAAG,KAAI;gBACZ,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;AACjC,gBAAA,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;AAAE,oBAAA,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;AAC/C,qBAAA,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU;AAAE,oBAAA,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;YACtE,CAAC;AACD,YAAA,SAAS,EAAE,CAAC,EAAE,KAAI;AAChB,gBAAA,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAClB,OAAO,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,CAAC;AACD,YAAA,OAAO,EAAE,CAAC,EAAE,KAAI;AACd,gBAAA,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChB,OAAO,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAClC,CAAC;AACD,YAAA,KAAK,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE;SACxB;AACH,IAAA,CAAC;AACH;AAEA;;;AAGG;AACG,SAAU,eAAe,CAC7B,KAAY,EACZ,GAAiB,EAAA;AAEjB,IAAA,OAAO,MAAK;AACV,QAAA,MAAM,UAAU,GAAG,IAAI,GAAG,EAA4B;AACtD,QAAA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAc;QACtC,IAAI,IAAI,GAAG,IAAI;AAEf,QAAA,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAC9B;AACE,YAAA,IAAI,EAAE,CAAC,GAAG,KAAI;AACZ,gBAAA,IAAI,CAAC,IAAI;oBAAE;AACX,gBAAA,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC;oBAAE,EAAE,CAAC,GAAG,CAAC;YAC3C,CAAC;YACD,KAAK,EAAE,MAAK;AACV,gBAAA,IAAI,CAAC,IAAI;oBAAE;gBACX,IAAI,GAAG,KAAK;gBACZ,UAAU,CAAC,UAAU,EAAE;AACvB,gBAAA,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC;AAAE,oBAAA,EAAE,EAAE;YACtC,CAAC;SACF,EACD,GAAG,CACJ;QAED,OAAO;AACL,YAAA,IAAI,EAAE,CAAC,GAAG,KAAI;AACZ,gBAAA,IAAI,IAAI;AAAE,oBAAA,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC;YACnC,CAAC;AACD,YAAA,SAAS,EAAE,CAAC,EAAE,KAAI;AAChB,gBAAA,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAClB,OAAO,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,CAAC;AACD,YAAA,OAAO,EAAE,CAAC,EAAE,KAAI;AACd,gBAAA,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChB,OAAO,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAClC,CAAC;YACD,KAAK,EAAE,MAAK;AACV,gBAAA,IAAI,CAAC,IAAI;oBAAE;gBACX,IAAI,GAAG,KAAK;gBACZ,UAAU,CAAC,UAAU,EAAE;AACvB,gBAAA,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC;AAAE,oBAAA,EAAE,EAAE;YACtC,CAAC;SACF;AACH,IAAA,CAAC;AACH;;ACjFA;AACM,SAAU,gBAAgB,CAAC,MAAyB,EAAA;AACxD,IAAA,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,KAAI;AAChC,QAAA,MAAM,EAAE,GAAG,IAAI,iBAAiB,CAAC,MAAM,CAAC;AACxC,QAAA,MAAM,UAAU,GAAG,IAAI,GAAG,EAA2B;AACrD,QAAA,MAAM,OAAO,GAAG,IAAI,GAAG,EAAc;AACrC,QAAA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAc;QACtC,MAAM,OAAO,GAAa,EAAE;QAC5B,IAAI,EAAE,GAA0B,IAAI;QACpC,IAAI,WAAW,GAAG,KAAK;QACvB,IAAI,WAAW,GAAG,KAAK;AAEvB,QAAA,MAAM,MAAM,GAAG,CAAC,OAAuB,KAAU;YAC/C,EAAE,GAAG,OAAO;AACZ,YAAA,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC,KAAI;AACxB,gBAAA,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC;oBAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACtD,YAAA,CAAC;AACD,YAAA,OAAO,CAAC,MAAM,GAAG,MAAK;gBACpB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AAAE,oBAAA,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;AAC1D,gBAAA,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC;AAAE,oBAAA,EAAE,EAAE;AACrC,YAAA,CAAC;AACD,YAAA,OAAO,CAAC,OAAO,GAAG,MAAK;AACrB,gBAAA,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC;AAAE,oBAAA,EAAE,EAAE;AACtC,YAAA,CAAC;AACH,QAAA,CAAC;AAED,QAAA,IAAI,CAAC,MAAM;YAAE,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;;AACpD,YAAA,EAAE,CAAC,aAAa,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;AAEhD,QAAA,EAAE,CAAC,cAAc,GAAG,CAAC,CAAC,KAAI;YACxB,IAAI,CAAC,CAAC,SAAS;AAAE,gBAAA,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;AAC5D,QAAA,CAAC;AACD,QAAA,EAAE,CAAC,mBAAmB,GAAG,YAAW;AAClC,YAAA,IAAI;gBACF,WAAW,GAAG,IAAI;AAClB,gBAAA,MAAM,EAAE,CAAC,mBAAmB,EAAE;gBAC9B,UAAU,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC,gBAAgB,EAAE,CAAC;YAClD;oBAAU;gBACR,WAAW,GAAG,KAAK;YACrB;AACF,QAAA,CAAC;QAED,OAAO;AACL,YAAA,OAAO,EAAE;AACP,gBAAA,IAAI,EAAE,CAAC,KAAK,KAAI;AACd,oBAAA,IAAI,EAAE,IAAI,EAAE,CAAC,UAAU,KAAK,MAAM;AAAE,wBAAA,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;;AAC7C,wBAAA,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;gBAC1B,CAAC;gBACD,SAAS,EAAE,CAAC,EAAE,MAAM,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACpE,MAAM,EAAE,CAAC,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBAC3D,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAC9D,gBAAA,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE;AACzB,aAAA;AACD,YAAA,MAAM,EAAE,OAAO,IAAI,KAAI;gBACrB,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,IAAI,IAAI,EAAE,CAGvC;gBACD,IAAI,WAAW,EAAE;AACf,oBAAA,MAAM,SAAS,GACb,WAAW,CAAC,IAAI,KAAK,OAAO;yBAC3B,WAAW,IAAI,EAAE,CAAC,cAAc,KAAK,QAAQ,CAAC;AACjD,oBAAA,WAAW,GAAG,CAAC,MAAM,IAAI,SAAS;AAClC,oBAAA,IAAI,WAAW;wBAAE;AACjB,oBAAA,MAAM,EAAE,CAAC,oBAAoB,CAAC,WAAW,CAAC;AAC1C,oBAAA,IAAI,WAAW,CAAC,IAAI,KAAK,OAAO,EAAE;AAChC,wBAAA,MAAM,EAAE,CAAC,mBAAmB,EAAE;wBAC9B,UAAU,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC,gBAAgB,EAAE,CAAC;oBAClD;gBACF;qBAAO,IAAI,GAAG,EAAE;AACd,oBAAA,IAAI;AACF,wBAAA,MAAM,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC;oBAC/B;oBAAE,OAAO,GAAG,EAAE;AACZ,wBAAA,IAAI,CAAC,WAAW;AAAE,4BAAA,MAAM,GAAG;oBAC7B;gBACF;YACF,CAAC;YACD,KAAK,EAAE,MAAK;gBACV,EAAE,EAAE,KAAK,EAAE;gBACX,EAAE,CAAC,KAAK,EAAE;YACZ,CAAC;SACF;AACH,IAAA,CAAC;AACH;AAiCA;;;;;;;AAOG;AACG,SAAU,UAAU,CACxB,MAAyB,EACzB,GAAsB,EAAA;IAEtB,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC;IACjD,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,IAAI,gBAAgB,EAAE;AACrD,IAAA,MAAM,MAAM,GAAG,MAAM,CAAwB,YAAY,6EAAC;IAC1D,MAAM,SAAS,GAAG,MAAM,CAAsB,IAAI,GAAG,EAAE,gFAAC;AACxD,IAAA,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgB;IACrC,IAAI,eAAe,GAAmB,EAAE;IACxC,IAAI,SAAS,GAAyB,IAAI;AAC1C,IAAA,IAAI,cAAyD;IAC7D,IAAI,MAAM,GAAG,KAAK;AAElB,IAAA,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE;QAC1B,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,QAAQ;AACT,KAAA,CAAC;AAEF,IAAA,MAAM,OAAO,GAAG,CACd,IAA4B,EAC5B,MAA8B,KAClB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAE3E,IAAA,MAAM,MAAM,GAAG,CAAC,IAAU,EAAE,GAAW,KAAU;AAC/C,QAAA,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AAC7C,IAAA,CAAC;AAED,IAAA,MAAM,SAAS,GAAG,CAAC,GAAW,KAAU;QACtC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE;YACjC,IAAI,IAAI,CAAC,IAAI;AAAE,gBAAA,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC;QAClC;AACF,IAAA,CAAC;AAED,IAAA,MAAM,QAAQ,GAAG,CAAC,MAAc,KAAU;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;AAC9B,QAAA,IAAI,CAAC,IAAI;YAAE;AACX,QAAA,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;AACpB,QAAA,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM;AAAE,YAAA,KAAK,EAAE;AACxC,QAAA,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;AACjB,QAAA,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,KAAI;AACvB,YAAA,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC;AACzB,YAAA,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;AACnB,YAAA,OAAO,IAAI;AACb,QAAA,CAAC,CAAC;AACJ,IAAA,CAAC;AAED,IAAA,MAAM,UAAU,GAAG,CAAC,MAAc,KAAU;QAC1C,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;AAC5B,QAAA,IAAI,IAAI;AAAE,YAAA,OAAO,IAAI;QACrB,MAAM,IAAI,GAAG,SAAS,CAAC;AACrB,YAAA,MAAM,EAAE,MAAM;AACd,YAAA,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,MAAM;YAC5B,UAAU,EAAE,CAAC,IAAI,KACf,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACrE,SAAA,CAAC;AACF,QAAA,IAAI,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;AACxC,QAAA,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC;AACvB,QAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAK;AACvB,YAAA,IAAK,CAAC,IAAI,GAAG,IAAI;AACjB,YAAA,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACnD,YAAA,MAAM,CAAC,IAAK,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;QACrD,CAAC,CAAC,EACF,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,KAAI;AAC/B,YAAA,IAAI,GAAW;AACf,YAAA,IAAI;AACF,gBAAA,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAW;YACnC;AAAE,YAAA,MAAM;gBACN;YACF;AACA,YAAA,aAAa,CAAC,IAAK,EAAE,GAAG,CAAC;AAC3B,QAAA,CAAC,CAAC,EACF,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,CAC7C;AACD,QAAA,OAAO,IAAI;AACb,IAAA,CAAC;AAED,IAAA,MAAM,aAAa,GAAG,CAAC,IAAU,EAAE,GAAW,KAAU;AACtD,QAAA,QAAQ,GAAG,CAAC,CAAC;YACX,KAAK,OAAO,EAAE;AACZ,gBAAA,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE;gBAC5B,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC;oBAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;;oBACxD,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;gBAC/D;YACF;YACA,KAAK,OAAO,EAAE;AACZ,gBAAA,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE;oBACrC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAS,EAAE,GAAG,CAAC,EAAE,CAAC;gBACrC;gBACA;YACF;AACA,YAAA,KAAK,UAAU;gBACb;AACF,YAAA,KAAK,KAAK;AACR,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;gBACrB;;AAEN,IAAA,CAAC;AAED,IAAA,MAAM,eAAe,GAAG,CAAC,GAAc,KAAU;AAC/C,QAAA,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI;YAAE;AAC3B,QAAA,QAAQ,GAAG,CAAC,CAAC;AACX,YAAA,KAAK,SAAS;AACZ,gBAAA,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO;oBAAE,UAAU,CAAC,MAAM,CAAC;AACpD,gBAAA,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;gBAClB;AACF,YAAA,KAAK,QAAQ;gBACX,IAAI,GAAG,CAAC,IAAI;AAAE,oBAAA,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;;AAC7B,oBAAA,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;gBAC3B;AACF,YAAA,KAAK,QAAQ;AACX,gBAAA,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;gBAC1C;AACF,YAAA,KAAK,QAAQ;AACX,gBAAA,KAAK,EAAE;gBACP;;AAEN,IAAA,CAAC;IAED,MAAM,gBAAgB,GAAG,MAAW;AAClC,QAAA,IAAI,MAAM;YAAE;QACZ,KAAK,MAAM,KAAK,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;AAAE,YAAA,KAAK,EAAE;AACtD,QAAA,MAAM,CAAC,GAAG,GAAG,CAAC,SAAS,EAAE;QACzB,SAAS,GAAG,CAAC;AACb,QAAA,eAAe,GAAG;AAChB,YAAA,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC;AAC5B,YAAA,CAAC,CAAC,OAAO,CAAC,MAAK;AACb,gBAAA,IAAI,MAAM,IAAI,SAAS,KAAK,CAAC;oBAAE;gBAC/B,SAAS,GAAG,IAAI;AAChB,gBAAA,cAAc,GAAG,UAAU,CAAC,gBAAgB,EAAE,IAAI,CAAC;AACrD,YAAA,CAAC,CAAC;SACH;QACD,CAAC,CAAC,IAAI,CAAC;AACL,YAAA,CAAC,EAAE,OAAO;YACV,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,MAAM,EAAE,IAAI,CAAC,MAAM;AACnB,YAAA,KAAK,EAAE,kBAAkB;AACzB,YAAA,aAAa,EAAE,GAAG,CAAC,aAAa,IAAI,CAAC;AACtC,SAAA,CAAC;AACJ,IAAA,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAExE,MAAM,KAAK,GAAG,MAAW;AACvB,QAAA,IAAI,MAAM;YAAE;QACZ,MAAM,GAAG,IAAI;QACb,IAAI,cAAc,KAAK,SAAS;YAAE,YAAY,CAAC,cAAc,CAAC;AAC9D,QAAA,UAAU,EAAE;QACZ,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;YAAE,QAAQ,CAAC,MAAM,CAAC;QACxD,KAAK,MAAM,KAAK,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;AAAE,YAAA,KAAK,EAAE;QACtD,SAAS,EAAE,KAAK,EAAE;QAClB,SAAS,GAAG,IAAI;QAChB,IAAI,CAAC,OAAO,EAAE;AACd,QAAA,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC;AAC1B,IAAA,CAAC;IAED,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC;AACzC,IAAA,gBAAgB,EAAE;IAElB,OAAO;AACL,QAAA,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE;QAC3B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,CAAC,CAAC;QACvC,KAAK;KACN;AACH;;AC5UA;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/mesh",
3
- "version": "21.0.0",
3
+ "version": "21.1.0",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",
@@ -20,8 +20,8 @@
20
20
  "homepage": "https://github.com/mihajm/mmstack/blob/master/packages/mesh/client",
21
21
  "peerDependencies": {
22
22
  "@angular/core": ">=21 <22",
23
- "@mmstack/primitives": ">=21.6 <22",
24
- "@mmstack/mesh-protocol": ">=0.1.0 <1"
23
+ "@mmstack/primitives": ">=21.7 <22",
24
+ "@mmstack/mesh-protocol": ">=0.2 <1"
25
25
  },
26
26
  "sideEffects": false,
27
27
  "module": "fesm2022/mmstack-mesh.mjs",
@@ -1,6 +1,6 @@
1
1
  import { Injector, Signal, WritableSignal } from '@angular/core';
2
2
  import { ClientMsg, ServerMsg, Relay, PrincipalCtx, PresenceState, OpPolicy } from '@mmstack/mesh-protocol';
3
- import { MergePolicyEntry } from '@mmstack/primitives';
3
+ import { MergePolicyEntry, AsyncStore } from '@mmstack/primitives';
4
4
 
5
5
  /**
6
6
  * One live connection to a relay. `meshSync` calls the factory per (re)connect; a transport
@@ -26,9 +26,21 @@ declare function directTransport(relay: Relay, ctx: PrincipalCtx): MeshTransport
26
26
 
27
27
  type MeshStatus = 'connecting' | 'live' | 'reconnecting' | 'ejected' | 'closed';
28
28
  type MeshPeer = PresenceState;
29
+ /**
30
+ * A composed, user-facing health status for a synced store the surface that
31
+ * turns a versioned reject from a dead socket into a speakable 'outdated' banner.
32
+ */
33
+ type SyncHealthStatus = 'live' | 'offline' | 'outdated' | 'ejected' | 'degraded';
34
+ type SyncHealth = {
35
+ readonly status: SyncHealthStatus;
36
+ /** Why, when the status is `outdated`/`ejected`/`degraded`. */
37
+ readonly reason?: 'proto' | 'policy-version' | 'schema' | 'quota' | 'worker' | (string & {});
38
+ /** `Date.now()` of the last successful sync (welcome or applied env). */
39
+ readonly lastSyncedAt?: number;
40
+ };
29
41
  type MeshSyncOptions = {
30
42
  readonly room: string;
31
- /** Opaque principal pseudonym — provided, never minted (op-protocol RFC §3). */
43
+ /** Opaque principal pseudonym — provided, never minted. */
32
44
  readonly writer: string;
33
45
  readonly transport: MeshTransportFactory;
34
46
  /** Per-path merge policies for rebase/convergence (`lww` default). */
@@ -38,6 +50,9 @@ type MeshSyncOptions = {
38
50
  /** kind/claims of this principal, so a shared policy evaluates identically on both sides. */
39
51
  readonly ctx?: Omit<PrincipalCtx, 'writer'>;
40
52
  readonly policyVersion?: number;
53
+ /** The data shape this client speaks. Older-than-room → `outdated`, and a
54
+ * newer-schema migration envelope arriving mid-session flips this client to `outdated` too. */
55
+ readonly schemaVersion?: number;
41
56
  /** Exponential backoff cap for reconnects (default 15s; base 500ms + jitter). */
42
57
  readonly reconnect?: {
43
58
  readonly maxDelayMs?: number;
@@ -46,9 +61,44 @@ type MeshSyncOptions = {
46
61
  /** Register with the nearest transition scope so (re)connection surfaces as `pending`. */
47
62
  readonly register?: 'track' | 'suspend';
48
63
  readonly onEject?: (reason: string) => void;
64
+ /**
65
+ * Hold the connection until the local base is assembled. `meshSync` awaits this before it connects
66
+ * (and before it restores an `outbox`), so a store hydrated from another source first (a worker
67
+ * graph, a disk snapshot) is in place when the room welcome arrives and rebases pending on top.
68
+ * The status stays `connecting` while it is pending. A rejection is treated as ready, so a base
69
+ * that fails to load never wedges the connection.
70
+ */
71
+ readonly whenReady?: () => PromiseLike<void> | void;
72
+ /**
73
+ * Persist the unacknowledged local outbox (and this client's stable origin) so offline writes
74
+ * survive a REBOOT, not just a live reconnect: on boot they are restored and rebased onto the room
75
+ * on the next welcome, instead of being lost with in-memory state. The payload is written to
76
+ * `store` under `key`, debounced.
77
+ *
78
+ * By default (`crossTab: 'queue'`) a Web Lock on `key` makes this a single-writer-per-key resource:
79
+ * a second tab sharing the key WAITS (stays `connecting`, surfaced via `status`/`health`) until the
80
+ * first releases, rather than restoring the same origin and colliding on version mints. Set
81
+ * `crossTab: 'off'` to skip the lock and coordinate ownership yourself (e.g. a per-tab key, or
82
+ * leader election over `tabSync`). A debounced write means a hard crash within the debounce window
83
+ * can drop the very last mint — a small, best-effort gap; tune it with `debounceMs`.
84
+ */
85
+ readonly outbox?: {
86
+ readonly key: string;
87
+ readonly store: AsyncStore;
88
+ /** Coalesce outbox writes by this many ms (default 300; `0` = write on every change). */
89
+ readonly debounceMs?: number;
90
+ /**
91
+ * Cross-tab contention for the shared `key`. `'queue'` (default) holds a Web Lock so only one tab
92
+ * owns the durable outbox at a time; others wait. `'off'` skips the lock (you coordinate).
93
+ * (A future `'ephemeral'` — non-leaders run live with a throwaway origin — is planned.)
94
+ */
95
+ readonly crossTab?: 'queue' | 'off';
96
+ };
49
97
  };
50
98
  type MeshSyncRef = {
51
99
  readonly status: Signal<MeshStatus>;
100
+ /** Composed sync-health for a user-facing surface. */
101
+ readonly health: Signal<SyncHealth>;
52
102
  readonly peers: Signal<readonly MeshPeer[]>;
53
103
  /** Publish this client's ephemeral presence payload (cursor, section, activity…). */
54
104
  setPresence(data: unknown): void;
@@ -59,7 +109,7 @@ type MeshSyncRef = {
59
109
  * envelopes, remote envelopes fold in convergently, reconnects resume via delta or snapshot
60
110
  * with unacknowledged local writes rebased on top, and presence rides an ephemeral channel.
61
111
  * A synced store reads exactly like a local one — connection state surfaces only through
62
- * `status` and the transition scope (op-protocol RFC §10).
112
+ * `status` and the transition scope.
63
113
  */
64
114
  declare function meshSync<T extends object>(source: WritableSignal<T>, opt: MeshSyncOptions): MeshSyncRef;
65
115
 
@@ -105,7 +155,7 @@ type WebRtcMeshRef = {
105
155
  close(): void;
106
156
  };
107
157
  /**
108
- * Peer-to-peer mesh sync (unsequenced topology, op-protocol RFC §4): the relay only signals
158
+ * Peer-to-peer mesh sync: the relay only signals
109
159
  * and tracks membership; envelopes flow over WebRTC data channels and converge via the
110
160
  * per-path register map. Catch-up is pairwise: on channel open both sides exchange
111
161
  * watermarks; a side whose state is strictly covered hydrates from the other. Two peers that
@@ -115,4 +165,4 @@ type WebRtcMeshRef = {
115
165
  declare function webRtcMesh<T extends object>(source: WritableSignal<T>, opt: WebRtcMeshOptions): WebRtcMeshRef;
116
166
 
117
167
  export { directTransport, meshSync, rtcPeerConnector, webRtcMesh, webSocketTransport };
118
- export type { DataChannelLike, MeshPeer, MeshStatus, MeshSyncOptions, MeshSyncRef, MeshTransport, MeshTransportFactory, PeerConnector, WebRtcMeshOptions, WebRtcMeshRef };
168
+ export type { DataChannelLike, MeshPeer, MeshStatus, MeshSyncOptions, MeshSyncRef, MeshTransport, MeshTransportFactory, PeerConnector, SyncHealth, SyncHealthStatus, WebRtcMeshOptions, WebRtcMeshRef };