@mmstack/primitives 22.7.0 → 22.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/primitives",
3
- "version": "22.7.0",
3
+ "version": "22.8.1",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",
@@ -2813,6 +2813,11 @@ type Key$2 = string | number;
2813
2813
  * One structural operation. `set` on a key that did not previously exist carries NO `prev`
2814
2814
  * property (an absent key is not the same as a key holding `undefined` — the merge3 lesson),
2815
2815
  * which is what lets {@link invertBatch} invert an add into a delete.
2816
+ *
2817
+ * `clear` is a sync-layer intent, not a value change: it retires a per-path register (the
2818
+ * observed-remove half of a subtree replace) and contributes NOTHING to a value: {@link applyOps}
2819
+ * treats it as a no-op and the structural diff ({@link diffOps}) never emits one. Only the sync
2820
+ * emission layer produces clears; they ride batches so undo/rebase plumbing can pass them through.
2816
2821
  */
2817
2822
  type StoreOp = {
2818
2823
  kind: 'set';
@@ -2823,6 +2828,9 @@ type StoreOp = {
2823
2828
  kind: 'delete';
2824
2829
  path: readonly Key$2[];
2825
2830
  prev: unknown;
2831
+ } | {
2832
+ kind: 'clear';
2833
+ path: readonly Key$2[];
2826
2834
  };
2827
2835
  /** One emission: every op derived from one commit window (a tick), in path order. */
2828
2836
  type OpBatch = {
@@ -2895,14 +2903,17 @@ declare function applyOps<T>(root: T, ops: OpBatch | readonly StoreOp[]): T;
2895
2903
  * Pure reference-pruned structural diff of two roots into minimal ops (the emission core of
2896
2904
  * {@link opLog}, exported so code outside a log can produce a batch — e.g. diffing a scratch
2897
2905
  * draft against a replica's current value to route a write to its owner). Trusts the
2898
- * copy-on-write contract: an untouched subtree that kept its reference is skipped.
2906
+ * copy-on-write contract: an untouched subtree that kept its reference is skipped. Emits only
2907
+ * `set` and `delete`; `clear` is an emission-layer intent, never a diff product.
2899
2908
  */
2900
2909
  declare function diffOps(prev: unknown, next: unknown): StoreOp[];
2901
2910
  /**
2902
2911
  * Inverts a batch for undo: reversed order, `set`↔its own inverse (an add — a `set` with no
2903
2912
  * `prev` — inverts to a `delete`; a `delete` inverts to a `set` restoring `prev`). Feed the
2904
2913
  * result to {@link OpLog.apply}. Requires the ops' `prev`s, which in-memory batches always
2905
- * carry a wire-serialized batch that stripped them is not invertible.
2914
+ * carry (a wire-serialized batch that stripped them is not invertible). A `clear` is skipped:
2915
+ * it never changed a value, so it has no independent inverse (the accompanying subtree `set`'s
2916
+ * `prev` subsumes restoration).
2906
2917
  */
2907
2918
  declare function invertBatch(batch: OpBatch | readonly StoreOp[]): StoreOp[];
2908
2919
  /**
@@ -3310,11 +3321,51 @@ type HlcClock = {
3310
3321
  */
3311
3322
  declare function createHlcClock(now?: () => number): HlcClock;
3312
3323
 
3313
- declare const OP_PROTO_VERSION = 1;
3324
+ /**
3325
+ * Wire protocol version. Version 2 ops carry `cites` + `epoch` (the dot-citation register);
3326
+ * envelopes from other versions are dropped loudly: an op without citations cannot be merged
3327
+ * soundly (it would supersede nothing and its siblings would accumulate forever), so versions
3328
+ * are never silently mixed.
3329
+ */
3330
+ declare const OP_PROTO_VERSION = 2;
3314
3331
  type Key = string | number;
3315
3332
  /**
3316
- * The wire/journal recor. `writer` is an opaque principal pseudonym
3317
- * natural identity never enters the envelope; `origin` identifies the emitting log instance.
3333
+ * A dot: the globally-unique identity of one op at one path, the emitting replica (`origin`)
3334
+ * plus its clock stamp. Origins are unique per replica and their clocks are monotone, so a dot
3335
+ * never collides. Citing a dot means "I observed this write and am replacing it".
3336
+ */
3337
+ type Dot = {
3338
+ readonly origin: string;
3339
+ readonly hlc: Hlc;
3340
+ };
3341
+ /**
3342
+ * A frozen observation point: the set of writes a replica had seen at a moment in time, captured
3343
+ * cheaply as a monotone sequence marker. Stamping emission against a frontier makes an op cite the
3344
+ * siblings that were live THEN, not the ones live now, so an edit made against stale knowledge (a
3345
+ * fork committed after the base moved on) stays a concurrent sibling instead of superseding writes
3346
+ * it never observed.
3347
+ */
3348
+ type DotFrontier = {
3349
+ readonly seq: number;
3350
+ };
3351
+ /**
3352
+ * A wire op: a structural {@link StoreOp} plus the causal metadata the register needs.
3353
+ * `cites` lists the sibling dot(s) the writer observed at the op's path when it wrote;
3354
+ * exactly those get superseded; a write nobody cited stays live as a concurrent sibling.
3355
+ * `epoch` is the op's precedence term: stamped at emission as the max of the cited dots'
3356
+ * epochs and the writer's own prior epoch at this path (monotone per writer per path), +1
3357
+ * for an authority-bumped write. `prev` (on the underlying op) stays purely an inversion
3358
+ * hint for undo/rebase and plays no role in convergence.
3359
+ */
3360
+ type SyncOp = StoreOp & {
3361
+ readonly cites: readonly Dot[];
3362
+ readonly epoch: number;
3363
+ };
3364
+ /**
3365
+ * The wire/journal record. `writer` is an opaque principal pseudonym (natural identity never
3366
+ * enters the envelope); `origin` identifies the emitting replica. All ops in one envelope share
3367
+ * the envelope stamp, and an envelope carries at most one op per path, so `(origin, hlc)` is a
3368
+ * unique dot per path register.
3318
3369
  */
3319
3370
  type OpEnvelope = {
3320
3371
  readonly proto: number;
@@ -3323,20 +3374,35 @@ type OpEnvelope = {
3323
3374
  readonly version: number;
3324
3375
  readonly hlc: Hlc;
3325
3376
  readonly policyVersion: number;
3326
- readonly ops: readonly StoreOp[];
3377
+ readonly ops: readonly SyncOp[];
3327
3378
  };
3328
3379
  declare const CONFLICT_BRAND = "~mmstackConflict";
3329
3380
  /**
3330
- * A preserved (jj-style) conflict: both sides survive as data, sync never blocks, and
3331
- * resolution is just a later write. String-branded so it survives structured clone.
3381
+ * A preserved (jj-style) conflict: every concurrent write survives as data, sync never blocks,
3382
+ * and resolution is just a later write (which cites all surviving dots and collapses the set).
3383
+ * `siblings` holds every live top-precedence value, winner first; a concurrent delete surfaces
3384
+ * as `undefined`. `mine`/`theirs` alias the first two entries, the shape two-sided reconcile
3385
+ * seams (fork rebase) produce and consume. String-branded so it survives structured clone.
3332
3386
  */
3333
3387
  type Conflicted<T = unknown> = {
3334
3388
  readonly [CONFLICT_BRAND]: true;
3389
+ readonly siblings: readonly T[];
3335
3390
  readonly mine: T;
3336
3391
  readonly theirs: T;
3337
3392
  readonly ancestor?: T;
3338
3393
  };
3339
3394
  declare function isConflicted<T = unknown>(value: unknown): value is Conflicted<T>;
3395
+ /**
3396
+ * Deterministic, total well-formedness check for a received envelope. Returns a short reason
3397
+ * string when the envelope must be rejected WHOLE, or `null` when it is well-formed. It reads only
3398
+ * the envelope (no clock, no local state), so every replica accepts or rejects a given envelope
3399
+ * identically. This validates SHAPE, not authority: it closes malformed input (control characters
3400
+ * in an id or path segment that could forge a path-key separator, a non-integer version, an unknown
3401
+ * op kind, a negative epoch, forged cites, a root delete, two ops racing on one path). Authority and
3402
+ * access control stay at the relay; direct peer-to-peer rooms are trust-full for authority, so this
3403
+ * shape check is a peer's only line against a malformed neighbor.
3404
+ */
3405
+ declare function validateEnvelope(env: OpEnvelope): string | null;
3340
3406
  type MergeContext = {
3341
3407
  readonly path: readonly Key[];
3342
3408
  };
@@ -3359,32 +3425,146 @@ declare const preserve: MergeFn;
3359
3425
  * array. Items are matched by key; per-item fields merge via `merge3` against the ancestor
3360
3426
  * item; items added on either side survive; an item removed on either side and unedited on
3361
3427
  * the other stays removed. Item ORDER follows `mine` (the total-order winner), with `theirs`-
3362
- * only additions appended positional merging is out of scope (fractional indexing is the
3363
- * known upgrade if dogfooding demands it). Arrays still TRAVEL as whole-value sets; identity
3364
- * only shapes conflict resolution, so the wire format is untouched.
3428
+ * only additions appended, and arrays still TRAVEL as whole-value sets. For a list whose elements
3429
+ * move and edit concurrently, model it as a keyed container (a record of elements ordered by
3430
+ * `posBetween`) instead: `insertElement`/`moveElement`/`removeElement` write per element, so a
3431
+ * reorder and a concurrent edit both survive and elements travel one at a time.
3365
3432
  */
3366
3433
  declare function keyedArray(identity: (item: unknown) => unknown, opt?: {
3367
3434
  item?: MergeFn;
3368
3435
  }): MergeFn;
3436
+ /**
3437
+ * One retained concurrent write at a path. The register keeps at most one sibling per origin
3438
+ * (a replica's newer op replaces its own older one) plus a per-origin supersession watermark,
3439
+ * so state stays bounded by the concurrent-writer count, not the op count.
3440
+ */
3441
+ type SyncSibling = {
3442
+ readonly kind: 'set' | 'delete' | 'clear';
3443
+ /** The written value for a `set`; absent for `delete`/`clear`. */
3444
+ readonly value?: unknown;
3445
+ /** The emitter's inversion hint, kept for value-merging folds; irrelevant to convergence. */
3446
+ readonly prev?: unknown;
3447
+ readonly writer: string;
3448
+ readonly origin: string;
3449
+ readonly hlc: Hlc;
3450
+ readonly epoch: number;
3451
+ };
3452
+ /**
3453
+ * What a fold decided for a path: a value to graft, a key removal, or `clear` (the register
3454
+ * abstains and the value at that path comes from the nearest ancestor write instead).
3455
+ */
3456
+ type FoldResult = {
3457
+ readonly kind: 'set';
3458
+ readonly value: unknown;
3459
+ } | {
3460
+ readonly kind: 'delete';
3461
+ } | {
3462
+ readonly kind: 'clear';
3463
+ };
3464
+ /**
3465
+ * A conflict-resolution fold over the live sibling set of one path register. Called with the
3466
+ * full set of causally-maximal concurrent writes; must be a pure function of that SET (never
3467
+ * of arrival order); then the materialized value converges on every peer by construction.
3468
+ *
3469
+ * A custom fold MUST keep `epoch` as its outermost comparison (prefer max-epoch siblings
3470
+ * unconditionally, as {@link compareSiblings} does). The epoch exists to retire stale values: a
3471
+ * long-partitioned replica can resurface an old write that nothing ever cited, and without the
3472
+ * epoch gate a fold that ranks it high would snap the value backwards. Folding a lower-epoch
3473
+ * sibling's content into the result reopens exactly that hazard.
3474
+ */
3475
+ type FoldFn = (siblings: readonly SyncSibling[], ctx: MergeContext) => FoldResult;
3476
+ type FoldPolicyEntry = {
3477
+ /** `'todos.*.title'` or a segment array; `'*'` matches exactly one segment. */
3478
+ readonly path: string | readonly Key[];
3479
+ readonly fold: FoldFn;
3480
+ };
3481
+ /**
3482
+ * The register's total order: max by `(epoch, kind-class, hlc, writer, origin)`, where `set`
3483
+ * and `delete` outrank `clear` at equal epoch. Epoch first makes an authority bump decisive
3484
+ * regardless of clocks (and closes stale-value resurrection); the kind-class tier makes a
3485
+ * concurrent edit's survival of a subtree replace categorical rather than a clock race; origin
3486
+ * last keeps the order strict when two replicas share a writer and a stamp.
3487
+ */
3488
+ declare function compareSiblings(a: SyncSibling, b: SyncSibling): number;
3489
+ /** Last-writer-wins over the live sibling set: the {@link compareSiblings} maximum, as-is. */
3490
+ declare const defaultFold: FoldFn;
3491
+ /**
3492
+ * Per-path register state, serializable: the live + superseded siblings and the per-origin
3493
+ * supersession watermarks. This is what a snapshot ships, never a folded value: a
3494
+ * joiner seeded with only a value cannot supersede or be superseded correctly afterwards.
3495
+ */
3496
+ type RegisterCheckpoint = {
3497
+ readonly path: readonly Key[];
3498
+ readonly siblings: readonly SyncSibling[];
3499
+ readonly water: Readonly<Record<string, Hlc>>;
3500
+ };
3369
3501
  type ConvergingApply = {
3370
3502
  /**
3371
- * Fold an envelope into the register map and return the ops the local store must apply
3372
- * (post-dominance, post-policy, including replays of newer descendant winners). Pass
3373
- * `local: true` for envelopes this peer emitted itself: registered, nothing returned.
3503
+ * Fold an envelope into the per-path registers and return the materialization deltas the
3504
+ * local store must apply: plain `set`/`delete` ops (never `clear`), `[]` when no fold
3505
+ * winner changed. Pass `local: true` for envelopes this peer emitted itself: registered,
3506
+ * nothing returned, unless `reconcile: true` is also set, which registers the op locally AND
3507
+ * returns the fold delta (a fork commit lands as a concurrent sibling, so the store must move to
3508
+ * the fold winner rather than the raw committed value). Pass `frontier` to reject ops at or below
3509
+ * a pruned horizon, so a straggler older than compacted state can never resurrect.
3374
3510
  */
3375
3511
  ingest(env: OpEnvelope, opt?: {
3376
3512
  local?: boolean;
3513
+ reconcile?: boolean;
3514
+ frontier?: Hlc;
3377
3515
  }): StoreOp[];
3378
- /** Drop all registers (snapshot compaction / rehydration boundary). */
3516
+ /**
3517
+ * Stamp locally-diffed ops for emission: each op cites the live dots at its path and adopts
3518
+ * `max(observed epoch, own prior epoch at the path)`, plus 1 per path when `bump` is set (an
3519
+ * authority override). A `set`/`delete` at a path additionally expands into one `clear` per
3520
+ * live descendant register (the observed-remove half of a subtree replace), so a concurrent
3521
+ * descendant edit this replica never saw stays live and survives the replace. Pass `frontier` to
3522
+ * cite only the siblings observed as of that point (a fork committing what it saw when it forked),
3523
+ * so mid-flight writes stay concurrent instead of being superseded.
3524
+ */
3525
+ stamp(ops: readonly StoreOp[], opt?: {
3526
+ bump?: boolean;
3527
+ frontier?: DotFrontier;
3528
+ }): SyncOp[];
3529
+ /** Capture the current observation frontier, for later scoped emission; O(1). */
3530
+ captureFrontier(): DotFrontier;
3531
+ /** The live (causally-maximal) siblings at a path: the emission-frontier read. */
3532
+ liveAt(path: readonly Key[]): readonly SyncSibling[];
3533
+ /**
3534
+ * Deepest-live-wins materialization of the whole tree from the current register state: the
3535
+ * root register's fold value with every live descendant fold grafted on. This is what a
3536
+ * replica's root reads after loading a checkpoint, so a joiner given only register state
3537
+ * (never a folded value) can derive its root through its OWN fold configuration.
3538
+ */
3539
+ materialize(): unknown;
3540
+ /** Serializable register state for a snapshot/seed checkpoint. */
3541
+ checkpoint(): RegisterCheckpoint[];
3542
+ /** Merge checkpointed register state in (idempotent; call after `reset()` on hydrate). */
3543
+ load(registers: readonly RegisterCheckpoint[]): void;
3544
+ /**
3545
+ * Drop settled state at or below the stability frontier: superseded siblings and their
3546
+ * watermarks, plus a register whose only live winner is a below-frontier tombstone once nothing
3547
+ * else still materializes its key (no live descendant register, no live ancestor `set` value
3548
+ * holding it), so state stays bounded under key churn. Never changes a fold above the frontier;
3549
+ * pair with `ingest`'s `frontier` so pruned ops are rejected on re-delivery.
3550
+ */
3551
+ prune(frontier: Hlc): void;
3552
+ /** Drop all registers (snapshot compaction / rehydration boundary). Emission epoch floors survive. */
3379
3553
  reset(): void;
3380
3554
  };
3381
3555
  /**
3382
- * The unsequenced-topology convergence core: a per-path last-writer-wins
3383
- * register map over the total order (hlc, writer), with subtree dominance. Order-independent:
3384
- * any arrival order of the same envelope set yields the same state.
3556
+ * The unsequenced-topology convergence core: a dot-citation multi-value register per path.
3557
+ * An op supersedes exactly the sibling dots it cites; uncited concurrent writes stay live; a
3558
+ * pluggable fold resolves the live set at read. Both the live set and any pure fold over it
3559
+ * are functions of the delivered op SET, so any arrival order of the same envelopes (split,
3560
+ * duplicated, cites-before-ops) yields the same state.
3385
3561
  */
3386
3562
  declare function createConvergingApply(opt?: {
3387
3563
  policies?: readonly MergePolicyEntry[];
3564
+ /** Per-path custom folds; take precedence over the `policies` mapping. */
3565
+ folds?: readonly FoldPolicyEntry[];
3566
+ /** The local replica id: lets loaded checkpoints restore this replica's emission epoch floors. */
3567
+ origin?: string;
3388
3568
  }): ConvergingApply;
3389
3569
  type RebaseResult<T = unknown> = {
3390
3570
  root: T;
@@ -3411,11 +3591,41 @@ type OpSyncOptions = {
3411
3591
  readonly origin?: string;
3412
3592
  readonly policyVersion?: number;
3413
3593
  readonly policies?: readonly MergePolicyEntry[];
3594
+ /** Per-path custom register folds; take precedence over the `policies` mapping. */
3595
+ readonly folds?: readonly FoldPolicyEntry[];
3414
3596
  readonly clock?: HlcClock;
3415
3597
  readonly injector?: Injector;
3416
3598
  readonly driver?: OpLogDriver;
3417
3599
  /** A version gap from a known origin (missed envelopes) — the resync hook. */
3418
3600
  readonly onGap?: (origin: string, expected: number, got: number) => void;
3601
+ /** A received envelope rejected as malformed by {@link validateEnvelope}, with the reason. */
3602
+ readonly onReject?: (env: OpEnvelope, reason: string) => void;
3603
+ };
3604
+ /**
3605
+ * A peer-state checkpoint: the current root as the materialization base, the per-path register
3606
+ * state (siblings + watermarks), and the per-origin version watermark. Register state rides
3607
+ * along because a value alone is not enough to join a room: a peer hydrated from a bare value
3608
+ * cannot tell a late straggler from a live concurrent write, so an already-superseded op would
3609
+ * resurrect on it while every established peer keeps ignoring it.
3610
+ */
3611
+ type OpSyncCheckpoint<T = unknown> = {
3612
+ readonly root: T;
3613
+ readonly registers: readonly RegisterCheckpoint[];
3614
+ readonly wm: Readonly<Record<string, number>>;
3615
+ };
3616
+ /**
3617
+ * A {@link Fork} of a synced store. Everything a plain fork does, plus `rebase()`. Committing it
3618
+ * cites only the dots the fork observed when it was created (or last rebased), so an edit that
3619
+ * landed on the base mid-flight stays a concurrent sibling and the configured fold decides between
3620
+ * them; an approval click never silently discards a concurrent write.
3621
+ */
3622
+ type SyncedFork<T> = Fork<T> & {
3623
+ /**
3624
+ * Re-observe the base: a following `commit()` cites the dots visible NOW, so the fork's edits
3625
+ * supersede everything currently on the base (the reviewed-and-apply step). Keeps the staged
3626
+ * edits; only advances what the next commit claims to have seen.
3627
+ */
3628
+ rebase(): void;
3419
3629
  };
3420
3630
  type OpSync<T = unknown> = {
3421
3631
  readonly origin: string;
@@ -3425,37 +3635,137 @@ type OpSync<T = unknown> = {
3425
3635
  receive(env: OpEnvelope): void;
3426
3636
  /** Synchronously emit any pending local delta now. */
3427
3637
  flush(): void;
3638
+ /**
3639
+ * Run `fn` and emit the writes it makes as authority-bumped ops: each written path gets
3640
+ * epoch `max(observed at that path) + 1`, and a bumped subtree replace bumps every path it
3641
+ * clears, so concurrent edits under it lose the fold instead of surviving. Scoped and
3642
+ * synchronous: writes before/after emit normally. WHO may bump is admission policy at the
3643
+ * transport/relay, not merge semantics; the register accepts any well-formed epoch.
3644
+ */
3645
+ override(fn: () => void): void;
3646
+ /**
3647
+ * Capture the current observation frontier: the set of writes this peer has seen right now, as a
3648
+ * cheap marker. Pair with {@link commitScope} to emit later against what was observed then (the
3649
+ * fork-commit seam); {@link syncedFork} wires both together.
3650
+ */
3651
+ captureFrontier(): DotFrontier;
3652
+ /**
3653
+ * Run `fn` and stamp the writes it makes against `frontier` rather than the current register
3654
+ * state, so they cite only the siblings observed as of that frontier. A write against a stale
3655
+ * frontier lands as a concurrent sibling (the fold decides) instead of superseding writes it
3656
+ * never saw. Scoped and synchronous, like {@link override}.
3657
+ */
3658
+ commitScope(frontier: DotFrontier, fn: () => void): void;
3428
3659
  /** Per-origin latest versions — the handshake watermark. */
3429
3660
  watermark(): Record<string, number>;
3430
- /** The current root + watermark, for answering a peer's hello. */
3431
- snapshot(): {
3432
- root: T;
3433
- wm: Record<string, number>;
3434
- };
3661
+ /** The full checkpoint (root + register state + watermark), for answering a peer's hello. */
3662
+ snapshot(): OpSyncCheckpoint<T>;
3435
3663
  /**
3436
- * Emit the CURRENT root as a root-set envelope the fresh-room seed of the relay
3437
- * contract (a room's snapshot root becomes complete once seeded).
3664
+ * Emit the CURRENT root as a root-set envelope: the fresh-room seed of the relay contract.
3665
+ * In a fresh room nothing was observed, so the op carries no cites and no clears; on a
3666
+ * non-fresh instance it behaves as a whole-root replace (cites the root register's live
3667
+ * dots and clears observed descendant registers).
3438
3668
  */
3439
3669
  seed(): void;
3440
3670
  /**
3441
- * Replace local state with a peer's snapshot, atomically (one notification wave).
3442
- * Local envelopes the snapshot doesn't cover (per its watermark) are re-applied on
3443
- * top, so writes made before hydration are never silently lost.
3671
+ * Replace local state with a peer's checkpoint, atomically (one notification wave): adopt
3672
+ * its root, load its register state, and fold its watermark. Local envelopes the checkpoint
3673
+ * doesn't cover (per its watermark) are re-applied on top, so writes made before hydration
3674
+ * are never silently lost; the next emitted version continues past the folded watermark.
3675
+ * Pass `pending` (a durable outbox) to rebase from it instead of the bounded in-memory recent
3676
+ * ring, so an offline burst larger than that ring is not dropped from the rebase.
3444
3677
  */
3445
- hydrate(root: T, wm?: Record<string, number>): void;
3678
+ hydrate(state: OpSyncCheckpoint<T>, pending?: readonly OpEnvelope[]): void;
3446
3679
  /**
3447
- * Re-inject this origin's persisted local envelopes on boot (a durable outbox), WITHOUT minting
3448
- * new versions: each is applied to the store (echo-free), registered as a local winner, and handed
3449
- * to subscribers so a transport can resend the unacknowledged tail. `highWater` is the highest
3450
- * version this origin ever emitted (>= every `env.version`); the next mint continues past it, so a
3451
- * version acked before the reboot but dropped from a debounced outbox never collides. Call on a
3452
- * FRESH instance, before any `receive`/`hydrate` restoring onto already-ingested remote winners
3453
- * would wrongly let a stale local op override them.
3680
+ * Re-inject a persisted local outbox on boot, WITHOUT minting new versions: each envelope is
3681
+ * applied to the store (echo-free), registered as a local winner, and handed to subscribers so a
3682
+ * transport can resend the unacknowledged tail VERBATIM under its recorded origin (idempotent at
3683
+ * receivers). Envelopes are accepted regardless of origin; their versions track per recorded
3684
+ * origin. `highWater` is that origin's emit high-water (>= every `env.version`, including any acked
3685
+ * then dropped from a debounced outbox), so a resent tail never re-mints below what the room may
3686
+ * have sequenced. This instance mints on its OWN origin: pass a fresh per-boot origin and new
3687
+ * writes start clean, so two clones of one outbox resend an identical tail (a duplicate) yet their
3688
+ * new writes land on distinct origins and never collide. Call on a FRESH instance, before any
3689
+ * `receive`/`hydrate` — restoring onto already-ingested remote winners would wrongly let a stale
3690
+ * local op override them.
3454
3691
  */
3455
3692
  restore(envs: readonly OpEnvelope[], highWater?: number): void;
3693
+ /**
3694
+ * Reclaim settled register state at or below a stability frontier: superseded siblings, their
3695
+ * watermarks, and lone tombstones nothing still materializes. Never changes the current value, so
3696
+ * it is safe to call whenever a transport learns the frontier has advanced (a straggler below it
3697
+ * is rejected at ingest, so nothing can resurrect). Without it, per-path register state grows with
3698
+ * every path ever written; with it, state stays bounded by what is live above the frontier.
3699
+ */
3700
+ prune(frontier: Hlc): void;
3456
3701
  destroy(): void;
3457
3702
  };
3458
3703
  declare function opSync<T extends object>(source: WritableSignal<T>, opt: OpSyncOptions): OpSync<T>;
3704
+ /**
3705
+ * Fork a synced store for isolated edits (an agent branch, a staged review), keeping the correct
3706
+ * emission semantics on commit. The fork observes the base as it was when this call ran; committing
3707
+ * emits its diff citing only those observed dots, so an edit that landed on the base mid-flight
3708
+ * stays a concurrent sibling and the configured fold decides between them, rather than the commit
3709
+ * overwriting a write it never saw. `rebase()` re-observes the base (a following commit then
3710
+ * supersedes what is visible now, the reviewed-and-apply step). Pass the same `store` and `sync`
3711
+ * that are wired together; the fork is a plain {@link Fork} otherwise, so `forkStore` itself stays
3712
+ * sync-agnostic.
3713
+ */
3714
+ declare function syncedFork<T extends Record<string, any>>(sync: OpSync, store: WritableSignalStore<T>, opt?: ForkStoreOptions<T>): SyncedFork<T>;
3715
+
3716
+ /**
3717
+ * Reserved key holding an element's fractional position inside a keyed container. It lives INSIDE
3718
+ * the element (at `[container, elementKey, '~pos']`), so a reorder is a one-field write that never
3719
+ * collides with a concurrent edit to the element's data. It stays visible on the materialized
3720
+ * element value; do not read, write, or strip it by hand, use the helpers in this file.
3721
+ */
3722
+ declare const POS_SEGMENT = "~pos";
3723
+ /**
3724
+ * A compact position string strictly between `before` and `after`, ordered by plain string
3725
+ * comparison. Pass `undefined` for an open end: `posBetween()` seeds the first element,
3726
+ * `posBetween(last)` appends, `posBetween(undefined, first)` prepends. Repeated inserts into the
3727
+ * same gap grow the string one digit at a time rather than colliding, and the result is never equal
3728
+ * to either neighbor. `before` must sort before `after`.
3729
+ */
3730
+ declare function posBetween(before?: string, after?: string): string;
3731
+ /** An element of a keyed container in reading order: its key, its position, and its value. */
3732
+ type OrderedEntry<T> = {
3733
+ readonly key: string;
3734
+ readonly pos: string;
3735
+ readonly value: T;
3736
+ };
3737
+ /**
3738
+ * A keyed container's elements in reading order. Order is a pure function of the materialized
3739
+ * value: elements sort by their `~pos` string, ties broken by key. An element whose `~pos` is
3740
+ * missing or not a string is ordered as if its position were the empty string (it sorts first,
3741
+ * key breaking the tie), so a peer that dropped the position field still lands somewhere
3742
+ * deterministic on every replica.
3743
+ */
3744
+ declare function orderedEntries<T>(container: Record<string, T>): OrderedEntry<T>[];
3745
+ /** A store node (or plain writable signal) holding a keyed container: a RECORD keyed by element id. */
3746
+ type ContainerNode<T> = WritableSignal<Record<string, T>>;
3747
+ /**
3748
+ * Insert `value` under `key` at `index` in reading order (default: append). The position is
3749
+ * computed from the neighbors at that index, so the element lands where asked without renumbering
3750
+ * any sibling. A keyed container is a RECORD, never an array, so this is a per-key write the sync
3751
+ * layer diffs on its own. Returns the assigned position. Re-inserting an existing key overwrites it.
3752
+ */
3753
+ declare function insertElement<T extends object>(container: ContainerNode<T>, key: string, value: T, index?: number): string;
3754
+ /**
3755
+ * Move the element at `key` to `index` in reading order. This writes ONLY the element's `~pos`
3756
+ * field, so it never conflicts with a concurrent edit to the same element's data (they land on
3757
+ * different paths and both survive). Returns the new position, or `undefined` if `key` is absent.
3758
+ */
3759
+ declare function moveElement<T extends object>(container: ContainerNode<T>, key: string, index: number): string | undefined;
3760
+ /** Remove the element at `key`. Deletes the whole element (a per-key delete the sync layer folds). */
3761
+ declare function removeElement<T>(container: ContainerNode<T>, key: string): void;
3762
+ /**
3763
+ * Reassign every element's position to a fresh, evenly spaced sequence, as an authority write:
3764
+ * each `~pos` set is epoch-bumped so it wins the merge against any concurrent move, while leaving
3765
+ * concurrent edits to element DATA untouched (only the `~pos` fields are written). Use this to
3766
+ * reclaim precision after many same-gap inserts. Existing reading order is preserved.
3767
+ */
3768
+ declare function rebalanceContainer<T extends object>(sync: Pick<OpSync, 'override'>, container: ContainerNode<T>): void;
3459
3769
 
3460
3770
  type StoreHistory = {
3461
3771
  readonly canUndo: Signal<boolean>;
@@ -3464,6 +3774,12 @@ type StoreHistory = {
3464
3774
  undo(): void;
3465
3775
  /** Re-apply the most recently undone change. */
3466
3776
  redo(): void;
3777
+ /**
3778
+ * Close the current coalescing run: the next tracked change starts a NEW undo entry even if
3779
+ * it lands inside the `coalesce` window. Call it on the boundaries your UX considers an
3780
+ * action — a field blur, a selection change, a drag drop. A no-op without `coalesce`.
3781
+ */
3782
+ checkpoint(): void;
3467
3783
  /** Forget all tracked history (e.g. after a save boundary). */
3468
3784
  clear(): void;
3469
3785
  destroy(): void;
@@ -3475,11 +3791,29 @@ type StoreHistoryOptions = CreateOpLogOptions & {
3475
3791
  * The change stream to track. Defaults to self-diffing `source` (every change to the store
3476
3792
  * becomes undoable). For collaborative-safe undo, pass a sync client's LOCAL envelope stream
3477
3793
  * (e.g. an `opSync`'s `subscribe`, which fires only for this peer's own writes) — remote
3478
- * peers' changes then never land on your undo stack.
3794
+ * peers' changes then never land on your undo stack. When the stream exposes `flush` (an
3795
+ * `opSync` does), undo/redo drain it synchronously so their own emissions never echo back
3796
+ * onto the stack as fresh entries.
3479
3797
  */
3480
3798
  readonly track?: {
3481
3799
  subscribe(cb: (batch: OpBatch) => void): () => void;
3800
+ flush?(): void;
3801
+ };
3802
+ /**
3803
+ * Merge rapid consecutive edits into ONE undo entry, so a typing run undoes as a unit
3804
+ * instead of per keystroke. A tracked change arriving within `ms` of the previous one AND
3805
+ * touching the same paths with the same op kinds extends the previous entry (set
3806
+ * `samePath: false` to merge on time alone); anything else — a different field, a kind
3807
+ * change, a pause longer than `ms`, a `checkpoint()`, an undo/redo — starts a new entry.
3808
+ * The window is measured between consecutive changes, so an unbroken run keeps merging.
3809
+ * Undoing a merged entry is exactly equivalent to undoing its changes one by one.
3810
+ */
3811
+ readonly coalesce?: {
3812
+ readonly ms: number;
3813
+ readonly samePath?: boolean;
3482
3814
  };
3815
+ /** Clock for the coalescing window (injectable for tests; default `Date.now`). */
3816
+ readonly now?: () => number;
3483
3817
  };
3484
3818
  /**
3485
3819
  * Undo/redo for a copy-on-write store, built on the op-log: each tracked change is stored as
@@ -3489,7 +3823,8 @@ type StoreHistoryOptions = CreateOpLogOptions & {
3489
3823
  *
3490
3824
  * Composes with sync for collaborative undo: pass `track: syncClient` so only YOUR writes are
3491
3825
  * undoable, while `undo()` emits a normal op that propagates to peers (it writes through the
3492
- * store, which the sync client picks up).
3826
+ * store, which the sync client picks up). Coalescing groups only this stack's entries — what
3827
+ * goes over the wire is untouched.
3493
3828
  */
3494
3829
  declare function storeHistory<T extends object>(source: WritableSignal<T>, opt?: StoreHistoryOptions): StoreHistory;
3495
3830
 
@@ -4153,5 +4488,5 @@ type CreateHistoryOptions<T> = Omit<CreateSignalOptions<T[]>, 'equal'> & {
4153
4488
  */
4154
4489
  declare function withHistory<T>(sourceOrValue: WritableSignal<T> | T, opt?: CreateHistoryOptions<T>): SignalWithHistory<T>;
4155
4490
 
4156
- export { CONCURRENCY_INSTRUMENTATION, MmActivity, MmTransition, MmViewTransitionName, OP_PROTO_VERSION, PAUSABLE_OPTIONS, PERSISTED_STORE_OPTIONS, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, applyOps, batteryStatus, bridgeScopeToPendingTasks, chunked, clipboard, combineWith, compareHlc, compareTotal, createAttributedPending, createConvergingApply, createForwardingScope, createHlcClock, createStoreContext, createTransaction, createTransitionScope, debounce, debounced, deferredValue, derived, diffOps, distinct, elementSize, elementVisibility, extendStore, filter, filterWith, focusWithin, forkStore, geolocation, getTransitionScope, holdUntilReady, idle, indexArray, injectPaused, injectRegisterResource, injectStartTransaction, injectStartTransition, injectTransitionScope, invertBatch, isConflicted, isDerivation, isLeaf, isMutable, isOpaque, isStore, keepPrevious, keyArray, keyedArray, latest, lww, map, mapArray, mapObject, mediaQuery, merge3, mergeThree, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, opLog, opSync, opaque, orientation, pageVisibility, pairwise, pausableComputed, pausableEffect, pausableSignal, perfCustomTracks, persist, persistedStore, pipeable, piped, pointerDrag, policyStrategy, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, preserve, projection, provideConcurrencyInstrumentation, provideForwardingTransitionScope, providePausableOptions, providePaused, providePersistedStoreOptions, provideTransitionScope, rebaseOps, reconcile, registerResource, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, storeHistory, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, use, windowSize, withHistory };
4157
- export type { AsyncStore, BatteryStatus, ClipboardSignal, Computation, ConcurrencyInstrumentation, Conflicted, ConvergingApply, CreateChunkedOptions, CreateDebouncedOptions, CreateHistoryOptions, CreateLatestOptions, CreateOpLogOptions, CreatePooledOptions, CreateProvidedPooledOptions, CreateStoredOptions, CreateThrottledOptions, CreateTransitionScopeOptions, DebouncedSignal, DeferStrategy, DeferredSignal, DeferredValueOptions, DerivedSignal, ElementSize, ElementSizeOptions, ElementSizeSignal, ElementVisibilityOptions, ElementVisibilitySignal, ExtendStoreOptions, Fork, ForkStoreOptions, ForkStrategy, ForwardingTransitionScope, Frame, GeolocationOptions, GeolocationSignal, Hlc, HlcClock, IdleOptions, IdleSignal, LatestSignal, MergeContext, MergeFn, MergePolicyEntry, MmTransitionContext, MousePositionOptions, MousePositionSignal, MutableSignal, MutableSignalStore, NetworkStatusSignal, OpBatch, OpEnvelope, OpLog, OpLogDriver, OpSync, OpSyncOptions, Opaque, PausableOptions, PauseOption, PersistHandle, PersistOptions, PersistedStore, PersistedStoreDefaults, PersistedStoreOptions, PipeableSignal, PointerDragOptions, PointerDragSignal, PointerDragState, PointerModifiers, PointerPoint, ProjectionOptions, RebaseResult, ReconcileFn, ReconcileKey, RegisterOptions, ResourceLike, ScreenOrientation, ScreenOrientationState, ScrollPosition, ScrollPositionOptions, ScrollPositionSignal, SensorRunOptions, SignalFromEventOptions, SignalStore, SignalWithHistory, StoreHistory, StoreHistoryOptions, StoreOp, StoreOptions, StoreTabSyncOptions, StoredSignal, SuspendType, SyncSignalOptions, TabSyncBus, ThrottledSignal, Transaction, TransactionRef, TransitionRef, TransitionScope, UntilOptions, UseSource, Vivify, WindowSize, WindowSizeOptions, WindowSizeSignal, WithVivify, WritableSignalStore, toStoreOptions };
4491
+ export { CONCURRENCY_INSTRUMENTATION, MmActivity, MmTransition, MmViewTransitionName, OP_PROTO_VERSION, PAUSABLE_OPTIONS, PERSISTED_STORE_OPTIONS, POS_SEGMENT, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, applyOps, batteryStatus, bridgeScopeToPendingTasks, chunked, clipboard, combineWith, compareHlc, compareSiblings, compareTotal, createAttributedPending, createConvergingApply, createForwardingScope, createHlcClock, createStoreContext, createTransaction, createTransitionScope, debounce, debounced, defaultFold, deferredValue, derived, diffOps, distinct, elementSize, elementVisibility, extendStore, filter, filterWith, focusWithin, forkStore, geolocation, getTransitionScope, holdUntilReady, idle, indexArray, injectPaused, injectRegisterResource, injectStartTransaction, injectStartTransition, injectTransitionScope, insertElement, invertBatch, isConflicted, isDerivation, isLeaf, isMutable, isOpaque, isStore, keepPrevious, keyArray, keyedArray, latest, lww, map, mapArray, mapObject, mediaQuery, merge3, mergeThree, mousePosition, moveElement, mutable, mutableStore, nestedEffect, networkStatus, opLog, opSync, opaque, orderedEntries, orientation, pageVisibility, pairwise, pausableComputed, pausableEffect, pausableSignal, perfCustomTracks, persist, persistedStore, pipeable, piped, pointerDrag, policyStrategy, pooled, pooledArray, pooledMap, pooledSet, posBetween, prefersDarkMode, prefersReducedMotion, preserve, projection, provideConcurrencyInstrumentation, provideForwardingTransitionScope, providePausableOptions, providePaused, providePersistedStoreOptions, provideTransitionScope, rebalanceContainer, rebaseOps, reconcile, registerResource, removeElement, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, storeHistory, stored, syncedFork, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, use, validateEnvelope, windowSize, withHistory };
4492
+ export type { AsyncStore, BatteryStatus, ClipboardSignal, Computation, ConcurrencyInstrumentation, Conflicted, ContainerNode, ConvergingApply, CreateChunkedOptions, CreateDebouncedOptions, CreateHistoryOptions, CreateLatestOptions, CreateOpLogOptions, CreatePooledOptions, CreateProvidedPooledOptions, CreateStoredOptions, CreateThrottledOptions, CreateTransitionScopeOptions, DebouncedSignal, DeferStrategy, DeferredSignal, DeferredValueOptions, DerivedSignal, Dot, DotFrontier, ElementSize, ElementSizeOptions, ElementSizeSignal, ElementVisibilityOptions, ElementVisibilitySignal, ExtendStoreOptions, FoldFn, FoldPolicyEntry, FoldResult, Fork, ForkStoreOptions, ForkStrategy, ForwardingTransitionScope, Frame, GeolocationOptions, GeolocationSignal, Hlc, HlcClock, IdleOptions, IdleSignal, LatestSignal, MergeContext, MergeFn, MergePolicyEntry, MmTransitionContext, MousePositionOptions, MousePositionSignal, MutableSignal, MutableSignalStore, NetworkStatusSignal, OpBatch, OpEnvelope, OpLog, OpLogDriver, OpSync, OpSyncCheckpoint, OpSyncOptions, Opaque, OrderedEntry, PausableOptions, PauseOption, PersistHandle, PersistOptions, PersistedStore, PersistedStoreDefaults, PersistedStoreOptions, PipeableSignal, PointerDragOptions, PointerDragSignal, PointerDragState, PointerModifiers, PointerPoint, ProjectionOptions, RebaseResult, ReconcileFn, ReconcileKey, RegisterCheckpoint, RegisterOptions, ResourceLike, ScreenOrientation, ScreenOrientationState, ScrollPosition, ScrollPositionOptions, ScrollPositionSignal, SensorRunOptions, SignalFromEventOptions, SignalStore, SignalWithHistory, StoreHistory, StoreHistoryOptions, StoreOp, StoreOptions, StoreTabSyncOptions, StoredSignal, SuspendType, SyncOp, SyncSibling, SyncSignalOptions, SyncedFork, TabSyncBus, ThrottledSignal, Transaction, TransactionRef, TransitionRef, TransitionScope, UntilOptions, UseSource, Vivify, WindowSize, WindowSizeOptions, WindowSizeSignal, WithVivify, WritableSignalStore, toStoreOptions };