@mmstack/primitives 22.6.1 → 22.8.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 +3 -1
- package/fesm2022/mmstack-primitives.mjs +1022 -137
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mmstack-primitives.d.ts +369 -35
package/package.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
*
|
|
3317
|
-
*
|
|
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
|
|
3377
|
+
readonly ops: readonly SyncOp[];
|
|
3327
3378
|
};
|
|
3328
3379
|
declare const CONFLICT_BRAND = "~mmstackConflict";
|
|
3329
3380
|
/**
|
|
3330
|
-
* A preserved (jj-style) conflict:
|
|
3331
|
-
* resolution is just a later write
|
|
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
|
};
|
|
@@ -3354,37 +3420,151 @@ declare const lww: MergeFn;
|
|
|
3354
3420
|
declare const mergeThree: MergeFn;
|
|
3355
3421
|
declare const preserve: MergeFn;
|
|
3356
3422
|
/**
|
|
3357
|
-
* Identity-aware array merge
|
|
3423
|
+
* Identity-aware array merge: reconciles two concurrent versions of
|
|
3358
3424
|
* an array item-wise by a user-provided identity, instead of last-writer-wins on the whole
|
|
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
|
|
3363
|
-
*
|
|
3364
|
-
*
|
|
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
|
|
3372
|
-
*
|
|
3373
|
-
* `local: true` for envelopes this peer emitted itself: registered,
|
|
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
|
-
/**
|
|
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
|
|
3383
|
-
*
|
|
3384
|
-
*
|
|
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;
|
|
@@ -3392,7 +3572,7 @@ type RebaseResult<T = unknown> = {
|
|
|
3392
3572
|
pending: StoreOp[][];
|
|
3393
3573
|
};
|
|
3394
3574
|
/**
|
|
3395
|
-
* The shared rebase routine
|
|
3575
|
+
* The shared rebase routine: invert pending, apply remote, re-apply
|
|
3396
3576
|
* pending through the merge policies. Pure — branching's `rebase()` and the sequenced relay
|
|
3397
3577
|
* client both call this.
|
|
3398
3578
|
*/
|
|
@@ -3406,16 +3586,46 @@ declare function rebaseOps<T>(root: T, pending: readonly (readonly StoreOp[])[],
|
|
|
3406
3586
|
*/
|
|
3407
3587
|
declare function policyStrategy<T>(policies: readonly MergePolicyEntry[]): (ancestor: T, mine: T, theirs: T) => T;
|
|
3408
3588
|
type OpSyncOptions = {
|
|
3409
|
-
/** Opaque principal pseudonym — provided by the app, never minted here
|
|
3589
|
+
/** Opaque principal pseudonym — provided by the app, never minted here. */
|
|
3410
3590
|
readonly writer: string;
|
|
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,27 +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
|
|
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
|
|
3437
|
-
*
|
|
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
|
|
3442
|
-
*
|
|
3443
|
-
* top, so writes made before hydration
|
|
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.
|
|
3677
|
+
*/
|
|
3678
|
+
hydrate(state: OpSyncCheckpoint<T>, pending?: readonly OpEnvelope[]): void;
|
|
3679
|
+
/**
|
|
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.
|
|
3691
|
+
*/
|
|
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.
|
|
3444
3699
|
*/
|
|
3445
|
-
|
|
3700
|
+
prune(frontier: Hlc): void;
|
|
3446
3701
|
destroy(): void;
|
|
3447
3702
|
};
|
|
3448
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;
|
|
3449
3769
|
|
|
3450
3770
|
type StoreHistory = {
|
|
3451
3771
|
readonly canUndo: Signal<boolean>;
|
|
@@ -3807,6 +4127,18 @@ type StoredSignal<T> = WritableSignal<T> & {
|
|
|
3807
4127
|
*/
|
|
3808
4128
|
declare function stored<T>(fallback: T, { key, store: providedStore, serialize, deserialize, syncTabs, equal, onKeyChange, cleanupOldKey, validate, pause, injector: providedInjector, ...rest }: CreateStoredOptions<T>): StoredSignal<T>;
|
|
3809
4129
|
|
|
4130
|
+
/**
|
|
4131
|
+
* The cross-tab transport `tabSync` rides. The default is {@link MessageBus} (a `BroadcastChannel`);
|
|
4132
|
+
* pass a custom one through `tabSync`'s `bus` option to route over a different channel, or to drive
|
|
4133
|
+
* tabs deterministically in a test. `subscribe` returns an unsubscribe handle plus a `post` that
|
|
4134
|
+
* fans the value to every OTHER tab on the same `id`.
|
|
4135
|
+
*/
|
|
4136
|
+
type TabSyncBus = {
|
|
4137
|
+
subscribe<T>(id: string, listener: (data: T) => void): {
|
|
4138
|
+
unsub: () => void;
|
|
4139
|
+
post: (value: T) => void;
|
|
4140
|
+
};
|
|
4141
|
+
};
|
|
3810
4142
|
type LegacySyncSignalOptions = {
|
|
3811
4143
|
id?: string;
|
|
3812
4144
|
};
|
|
@@ -3820,11 +4152,13 @@ type SyncSignalOptions = {
|
|
|
3820
4152
|
* it — a cross-tab consistency gap not worth the negligible saving. The channel stays live.
|
|
3821
4153
|
*/
|
|
3822
4154
|
injector?: Injector;
|
|
4155
|
+
/** Cross-tab transport. Defaults to the injected {@link MessageBus} (a `BroadcastChannel`). */
|
|
4156
|
+
bus?: TabSyncBus;
|
|
3823
4157
|
};
|
|
3824
4158
|
/**
|
|
3825
4159
|
* Store mode (`tabSync(store, …)`): syncs structural OPS instead of whole values — concurrent
|
|
3826
4160
|
* edits to different leaves merge instead of clobbering, and a joining tab hydrates from a
|
|
3827
|
-
* peer via the hello exchange
|
|
4161
|
+
* peer via the hello exchange.
|
|
3828
4162
|
*/
|
|
3829
4163
|
type StoreTabSyncOptions = SyncSignalOptions & {
|
|
3830
4164
|
/** Principal pseudonym on emitted envelopes. Tabs share one user, so a default is fine. */
|
|
@@ -4129,5 +4463,5 @@ type CreateHistoryOptions<T> = Omit<CreateSignalOptions<T[]>, 'equal'> & {
|
|
|
4129
4463
|
*/
|
|
4130
4464
|
declare function withHistory<T>(sourceOrValue: WritableSignal<T> | T, opt?: CreateHistoryOptions<T>): SignalWithHistory<T>;
|
|
4131
4465
|
|
|
4132
|
-
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 };
|
|
4133
|
-
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, ThrottledSignal, Transaction, TransactionRef, TransitionRef, TransitionScope, UntilOptions, UseSource, Vivify, WindowSize, WindowSizeOptions, WindowSizeSignal, WithVivify, WritableSignalStore, toStoreOptions };
|
|
4466
|
+
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 };
|
|
4467
|
+
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 };
|