@mmstack/primitives 20.10.0 → 20.11.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 +113 -2
- package/fesm2022/mmstack-primitives.mjs +1213 -230
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/index.d.ts +552 -79
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -224,6 +224,56 @@ type DeferredSignal<T> = Signal<T> & {
|
|
|
224
224
|
*/
|
|
225
225
|
declare function deferredValue<T>(source: Signal<T>, opt?: DeferredValueOptions<T>): DeferredSignal<T>;
|
|
226
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Optional observability seam for the concurrency layer (idea/concurrency-devtools.md). A
|
|
229
|
+
* listener, provided via {@link provideConcurrencyInstrumentation}, receives events as
|
|
230
|
+
* transition scopes coordinate pending/suspense/transaction windows and register resources.
|
|
231
|
+
* Zero-cost when absent: the taps are `listener?.hook(...)` behind a once-resolved optional
|
|
232
|
+
* inject, so nothing is allocated or measured unless a listener is installed.
|
|
233
|
+
*
|
|
234
|
+
* Span-shaped hooks (`*Start`) return an opaque handle passed back to their `*End`, and carry
|
|
235
|
+
* `at` epoch-ms stamps — deliberately isomorphic to the telemetry `startSpan`/`SpanHandle` SPI,
|
|
236
|
+
* so a telemetry consumer maps one-to-one.
|
|
237
|
+
*/
|
|
238
|
+
type ConcurrencyInstrumentation = {
|
|
239
|
+
pendingStart?(e: {
|
|
240
|
+
scope: string;
|
|
241
|
+
resources: number;
|
|
242
|
+
at: number;
|
|
243
|
+
}): unknown;
|
|
244
|
+
pendingEnd?(handle: unknown, e: {
|
|
245
|
+
at: number;
|
|
246
|
+
}): void;
|
|
247
|
+
transactionStart?(e: {
|
|
248
|
+
scope: string;
|
|
249
|
+
at: number;
|
|
250
|
+
}): unknown;
|
|
251
|
+
transactionEnd?(handle: unknown, e: {
|
|
252
|
+
at: number;
|
|
253
|
+
}): void;
|
|
254
|
+
resourceRegistered?(e: {
|
|
255
|
+
scope: string;
|
|
256
|
+
suspends: boolean;
|
|
257
|
+
}): void;
|
|
258
|
+
resourceRemoved?(e: {
|
|
259
|
+
scope: string;
|
|
260
|
+
}): void;
|
|
261
|
+
abortPending?(e: {
|
|
262
|
+
scope: string;
|
|
263
|
+
aborted: number;
|
|
264
|
+
at: number;
|
|
265
|
+
}): void;
|
|
266
|
+
};
|
|
267
|
+
declare const CONCURRENCY_INSTRUMENTATION: InjectionToken<ConcurrencyInstrumentation>;
|
|
268
|
+
declare function provideConcurrencyInstrumentation(listener: ConcurrencyInstrumentation): Provider;
|
|
269
|
+
/**
|
|
270
|
+
* Chrome DevTools "Performance" custom-tracks preset (idea/concurrency-devtools.md): writes a
|
|
271
|
+
* `performance.measure` for each pending/transaction window onto an "mmstack" extension track,
|
|
272
|
+
* so reactive coordination shows up on the Performance panel timeline. Dev-only, zero backend,
|
|
273
|
+
* no dependencies. Give each measure the scope name for readability.
|
|
274
|
+
*/
|
|
275
|
+
declare function perfCustomTracks(track?: string): ConcurrencyInstrumentation;
|
|
276
|
+
|
|
227
277
|
/**
|
|
228
278
|
* Structural hold-and-swap as a signal. Given a `target` (the desired value — e.g. the
|
|
229
279
|
* subtree/def/key you want to show) and a `ready` predicate, returns a signal that keeps
|
|
@@ -455,7 +505,13 @@ type TransitionScope = {
|
|
|
455
505
|
*/
|
|
456
506
|
hold<T>(value: Signal<T>): Signal<T>;
|
|
457
507
|
};
|
|
458
|
-
|
|
508
|
+
type CreateTransitionScopeOptions = {
|
|
509
|
+
/** Scope identity for instrumentation events (idea/concurrency-devtools.md). */
|
|
510
|
+
readonly name?: string;
|
|
511
|
+
/** Optional observability listener; taps are no-ops when omitted (zero cost). */
|
|
512
|
+
readonly instrumentation?: ConcurrencyInstrumentation;
|
|
513
|
+
};
|
|
514
|
+
declare function createTransitionScope(opt?: CreateTransitionScopeOptions): TransitionScope;
|
|
459
515
|
/**
|
|
460
516
|
* The scope→`PendingTasks` bridge: while `scope.pending()` is true, hold an Angular
|
|
461
517
|
* pending task so SSR serialization waits for the scope's in-flight loads — HTTP loads
|
|
@@ -470,7 +526,7 @@ declare function createTransitionScope(): TransitionScope;
|
|
|
470
526
|
*/
|
|
471
527
|
declare function bridgeScopeToPendingTasks(scope: TransitionScope, injector?: Injector): void;
|
|
472
528
|
/** Provide a fresh transition scope at a boundary so its subtree's resources are tracked independently. */
|
|
473
|
-
declare function provideTransitionScope(): Provider;
|
|
529
|
+
declare function provideTransitionScope(opt?: CreateTransitionScopeOptions): Provider;
|
|
474
530
|
declare function injectTransitionScope(): TransitionScope;
|
|
475
531
|
/**
|
|
476
532
|
* A transition scope that can be re-pointed at a delegate target at runtime. Reads and
|
|
@@ -837,23 +893,6 @@ type MutableSignal<T> = WritableSignal<T> & {
|
|
|
837
893
|
* This is because a `.mutate()` call notifies its dependents that it has changed, but if the
|
|
838
894
|
* reference to a derived object hasn't changed, the `computed` signal will not trigger its
|
|
839
895
|
* own dependents by default.
|
|
840
|
-
*
|
|
841
|
-
* @example
|
|
842
|
-
* ```ts
|
|
843
|
-
* const state = mutable({ user: { name: 'John' }, lastUpdated: new Date() });
|
|
844
|
-
*
|
|
845
|
-
* // ✅ CORRECT: Deriving a primitive value works as expected.
|
|
846
|
-
* const name = computed(() => state().user.name);
|
|
847
|
-
*
|
|
848
|
-
* // ❌ INCORRECT: This will not update reliably after the first change.
|
|
849
|
-
* const userObject = computed(() => state().user);
|
|
850
|
-
*
|
|
851
|
-
* // ✅ CORRECT: For object derivations, `equal: false` is required.
|
|
852
|
-
* const userObjectFixed = computed(() => state().user, { equal: false });
|
|
853
|
-
*
|
|
854
|
-
* // This mutation will now correctly trigger effects depending on `userObjectFixed`.
|
|
855
|
-
* state.mutate(s => s.lastUpdated = new Date());
|
|
856
|
-
* ```
|
|
857
896
|
*/
|
|
858
897
|
declare function mutable<T>(): MutableSignal<T | undefined>;
|
|
859
898
|
declare function mutable<T>(initial: T): MutableSignal<T>;
|
|
@@ -1800,10 +1839,10 @@ declare function clipboard(opt?: string | SensorRunOptions): ClipboardSignal;
|
|
|
1800
1839
|
/**
|
|
1801
1840
|
* Represents the size of an element.
|
|
1802
1841
|
*/
|
|
1803
|
-
|
|
1842
|
+
type ElementSize = {
|
|
1804
1843
|
width: number;
|
|
1805
1844
|
height: number;
|
|
1806
|
-
}
|
|
1845
|
+
};
|
|
1807
1846
|
/**
|
|
1808
1847
|
* Options for configuring the `elementSize` sensor.
|
|
1809
1848
|
*/
|
|
@@ -2762,6 +2801,128 @@ type ResolvableTarget = EventTargetLike | Signal<EventTargetLike | null>;
|
|
|
2762
2801
|
declare function signalFromEvent<TEvent extends Event>(target: ResolvableTarget, eventName: string, initial: TEvent | null, opt?: SignalFromEventOptions): Signal<TEvent | null>;
|
|
2763
2802
|
declare function signalFromEvent<TEvent extends Event, U>(target: ResolvableTarget, eventName: string, initial: U, project: (event: TEvent) => U, opt?: SignalFromEventOptions): Signal<U>;
|
|
2764
2803
|
|
|
2804
|
+
type Key$2 = string | number;
|
|
2805
|
+
/**
|
|
2806
|
+
* One structural operation. `set` on a key that did not previously exist carries NO `prev`
|
|
2807
|
+
* property (an absent key is not the same as a key holding `undefined` — the merge3 lesson),
|
|
2808
|
+
* which is what lets {@link invertBatch} invert an add into a delete.
|
|
2809
|
+
*/
|
|
2810
|
+
type StoreOp = {
|
|
2811
|
+
kind: 'set';
|
|
2812
|
+
path: readonly Key$2[];
|
|
2813
|
+
next: unknown;
|
|
2814
|
+
prev?: unknown;
|
|
2815
|
+
} | {
|
|
2816
|
+
kind: 'delete';
|
|
2817
|
+
path: readonly Key$2[];
|
|
2818
|
+
prev: unknown;
|
|
2819
|
+
};
|
|
2820
|
+
/** One emission: every op derived from one commit window (a tick), in path order. */
|
|
2821
|
+
type OpBatch = {
|
|
2822
|
+
/** Identifies the emitting log — filter your own batches on a shared transport. */
|
|
2823
|
+
readonly origin: string;
|
|
2824
|
+
/** Per-log monotonic batch counter. */
|
|
2825
|
+
readonly version: number;
|
|
2826
|
+
readonly ops: readonly StoreOp[];
|
|
2827
|
+
};
|
|
2828
|
+
/**
|
|
2829
|
+
* Drives an {@link opLog}'s emission reaction. Given the `run` closure (which reads the source in
|
|
2830
|
+
* a tracking context and flushes the delta), a driver arranges for `run` to execute now and again
|
|
2831
|
+
* on every subsequent change, returning a handle that stops it. The default driver is an Angular
|
|
2832
|
+
* `effect` (needs an injector). Supply a custom driver to run an opLog with NO injector; a
|
|
2833
|
+
* renderer-independent one built on `@angular/core/primitives/signals` `createWatch` ships as
|
|
2834
|
+
* `microtaskOpLogDriver` from `@mmstack/worker/host` (the Web Worker seam).
|
|
2835
|
+
*/
|
|
2836
|
+
type OpLogDriver = (run: () => void) => {
|
|
2837
|
+
destroy(): void;
|
|
2838
|
+
};
|
|
2839
|
+
type CreateOpLogOptions = {
|
|
2840
|
+
/** Transport identity for emitted batches. Defaults to a random id. */
|
|
2841
|
+
readonly origin?: string;
|
|
2842
|
+
/** Injection context for the default effect-based driver (required outside one). */
|
|
2843
|
+
readonly injector?: Injector;
|
|
2844
|
+
/**
|
|
2845
|
+
* Replaces the default Angular-`effect` emission driver. Supply a custom driver (e.g.
|
|
2846
|
+
* `microtaskOpLogDriver` from `@mmstack/worker/host`) to run an opLog with NO injector. When
|
|
2847
|
+
* given, `injector` is ignored and no injection context is required.
|
|
2848
|
+
*/
|
|
2849
|
+
readonly driver?: OpLogDriver;
|
|
2850
|
+
};
|
|
2851
|
+
type OpLog<T extends object> = {
|
|
2852
|
+
/**
|
|
2853
|
+
* Ordered, lossless delivery of every emitted batch. Synchronous — don't write back into
|
|
2854
|
+
* the observed source from inside a callback (route remote data through {@link OpLog.apply}).
|
|
2855
|
+
*/
|
|
2856
|
+
subscribe(cb: (batch: OpBatch) => void): () => void;
|
|
2857
|
+
/** The most recent batch — a lossy sampling view (devtools); use `subscribe` for transport. */
|
|
2858
|
+
readonly latest: Signal<OpBatch | null>;
|
|
2859
|
+
/**
|
|
2860
|
+
* Synchronously diff the source and emit any pending change NOW, rather than waiting for the
|
|
2861
|
+
* driver's scheduled run (an app tick, or a custom driver's microtask). Idempotent
|
|
2862
|
+
* and coalescing: writes since the last emission compose into one batch, and a `flush()` with
|
|
2863
|
+
* nothing pending is a no-op. Use it to make emission deterministic — the worker host calls it
|
|
2864
|
+
* to settle its mirror synchronously (tests), and it underpins the flush-before-apply honesty of
|
|
2865
|
+
* {@link OpLog.apply}. Independent of the driver: a later scheduled run simply finds no diff.
|
|
2866
|
+
*/
|
|
2867
|
+
flush(): void;
|
|
2868
|
+
/**
|
|
2869
|
+
* Applies ops (a remote batch, a persisted journal entry, an {@link invertBatch} result)
|
|
2870
|
+
* atomically: ONE `set`, one notification wave. Also advances this log's diff baseline in
|
|
2871
|
+
* the same step, so an applied batch produces NO echo emission — sync loops terminate by
|
|
2872
|
+
* construction. Local writes pending in the current tick are flushed (emitted) first, so
|
|
2873
|
+
* they are never silently folded into the applied baseline.
|
|
2874
|
+
*/
|
|
2875
|
+
apply(ops: OpBatch | readonly StoreOp[]): void;
|
|
2876
|
+
/** Stops observing and drops subscribers. Also happens when the injection context dies. */
|
|
2877
|
+
destroy(): void;
|
|
2878
|
+
};
|
|
2879
|
+
/**
|
|
2880
|
+
* Pure, store-free application of ops onto a plain root value, returning the next immutable root
|
|
2881
|
+
* (structural-sharing along op paths, missing containers vivified `'auto'`-style). This is the
|
|
2882
|
+
* same transform {@link OpLog.apply} runs, extracted so a replica can fold a received batch into
|
|
2883
|
+
* a value WITHOUT owning a diffing {@link opLog} — e.g. the worker-graph read-replica seam.
|
|
2884
|
+
* Accepts a batch or a bare op list.
|
|
2885
|
+
*/
|
|
2886
|
+
declare function applyOps<T>(root: T, ops: OpBatch | readonly StoreOp[]): T;
|
|
2887
|
+
/**
|
|
2888
|
+
* Pure reference-pruned structural diff of two roots into minimal ops (the emission core of
|
|
2889
|
+
* {@link opLog}, exported so code outside a log can produce a batch — e.g. diffing a scratch
|
|
2890
|
+
* draft against a replica's current value to route a write to its owner). Trusts the
|
|
2891
|
+
* copy-on-write contract: an untouched subtree that kept its reference is skipped.
|
|
2892
|
+
*/
|
|
2893
|
+
declare function diffOps(prev: unknown, next: unknown): StoreOp[];
|
|
2894
|
+
/**
|
|
2895
|
+
* Inverts a batch for undo: reversed order, `set`↔its own inverse (an add — a `set` with no
|
|
2896
|
+
* `prev` — inverts to a `delete`; a `delete` inverts to a `set` restoring `prev`). Feed the
|
|
2897
|
+
* result to {@link OpLog.apply}. Requires the ops' `prev`s, which in-memory batches always
|
|
2898
|
+
* carry — a wire-serialized batch that stripped them is not invertible.
|
|
2899
|
+
*/
|
|
2900
|
+
declare function invertBatch(batch: OpBatch | readonly StoreOp[]): StoreOp[];
|
|
2901
|
+
/**
|
|
2902
|
+
* Observes a copy-on-write signal (a `store`'s root, or any `WritableSignal` holding
|
|
2903
|
+
* immutably-updated objects) and emits its changes as minimal structural op batches — the
|
|
2904
|
+
* shared substrate for sync (ship batches, `apply` remote ones), persistence (journal
|
|
2905
|
+
* batches, replay on boot), undo ({@link invertBatch}), and devtools (`latest`).
|
|
2906
|
+
*
|
|
2907
|
+
* Zero store-core involvement and zero cost when unused: emission is a reference-pruned diff
|
|
2908
|
+
* of the root value per tick (structural sharing makes it O(changed paths)), driven by one
|
|
2909
|
+
* effect. A batch therefore coalesces everything written in one tick — for coarser,
|
|
2910
|
+
* intentional units, stage writes on a `forkStore` and `commit()` (one set → one batch).
|
|
2911
|
+
*
|
|
2912
|
+
* NOT supported on mutable stores/signals: in-place mutation keeps reference identity, which
|
|
2913
|
+
* defeats the diff (same reason `forkStore`'s `'fine'` strategy refuses them) — a dev-mode
|
|
2914
|
+
* warning fires and nothing emits.
|
|
2915
|
+
*
|
|
2916
|
+
* ```ts
|
|
2917
|
+
* const s = store({ todos: [{ done: false }] });
|
|
2918
|
+
* const log = opLog(s, { origin: 'tab-a' });
|
|
2919
|
+
* log.subscribe((b) => channel.postMessage(encode(b))); // ship
|
|
2920
|
+
* channel.onmessage = (m) => log.apply(decode(m.data)); // apply — echo-free
|
|
2921
|
+
* s.todos[0].done.set(true); // → { kind: 'set', path: ['todos', 0, 'done'], … }
|
|
2922
|
+
* ```
|
|
2923
|
+
*/
|
|
2924
|
+
declare function opLog<T extends object>(source: WritableSignal<T>, opt?: CreateOpLogOptions): OpLog<T>;
|
|
2925
|
+
|
|
2765
2926
|
/**
|
|
2766
2927
|
* @internal Runtime brand carrying a store node's lazily-built leaf probe. Exported (like
|
|
2767
2928
|
* {@link OPAQUE}) only so the `{ readonly [LEAF]: () => boolean }` brand on the store types is
|
|
@@ -3024,6 +3185,32 @@ declare function mutableStore<T extends AnyRecord>(value: T, opt?: CreateSignalO
|
|
|
3024
3185
|
*/
|
|
3025
3186
|
noUnionLeaves?: boolean;
|
|
3026
3187
|
}): MutableSignalStore<T>;
|
|
3188
|
+
/**
|
|
3189
|
+
* Builds a DI-less store context — the shared proxy-cache and cleanup registry that {@link toStore}
|
|
3190
|
+
* normally resolves from the injector — so a `store`/`toStore`/`opLog` graph can run with NO Angular
|
|
3191
|
+
* injection context. Spread the result into the options:
|
|
3192
|
+
*
|
|
3193
|
+
* ```ts
|
|
3194
|
+
* import { microtaskOpLogDriver } from '@mmstack/worker/host';
|
|
3195
|
+
* const ctx = createStoreContext();
|
|
3196
|
+
* const s = store({ todos: [] }, ctx);
|
|
3197
|
+
* const log = opLog(s, { driver: microtaskOpLogDriver(), origin: 'worker' }); // no injector anywhere
|
|
3198
|
+
* ```
|
|
3199
|
+
*
|
|
3200
|
+
* **This is a worker-only fallback — do NOT use it on the main thread.** DI is the default and
|
|
3201
|
+
* correct path in an app: the injector scopes the proxy-cache/cleanup singletons per app instance,
|
|
3202
|
+
* which on the SERVER keeps one request's store identity from bleeding into another's (the exact
|
|
3203
|
+
* hazard a module-scope singleton would reintroduce). A Web Worker is safe because it is a single
|
|
3204
|
+
* store graph per thread and never runs during SSR (spawn is a `PLATFORM_ID === 'server'` no-op),
|
|
3205
|
+
* so there is no cross-request scope to contaminate. Never hoist a `createStoreContext()` to module
|
|
3206
|
+
* scope on a shared/main thread.
|
|
3207
|
+
*
|
|
3208
|
+
* **Share ONE context across every store in a worker** — the same way `providedIn: 'root'` shares
|
|
3209
|
+
* one cache across all of an app's stores. `@mmstack/worker/host` memoizes this per worker
|
|
3210
|
+
* (`workerStoreContext()`); reach for `createStoreContext()` directly only in a bare
|
|
3211
|
+
* (non-worker-host) DI-less setup, and hold the single instance yourself.
|
|
3212
|
+
*/
|
|
3213
|
+
declare function createStoreContext(): toStoreOptions;
|
|
3027
3214
|
|
|
3028
3215
|
/**
|
|
3029
3216
|
* A 3-way merge of a forked value against a changed base: given the common `ancestor` (the base
|
|
@@ -3067,6 +3254,8 @@ type Fork<T> = {
|
|
|
3067
3254
|
commit(): void;
|
|
3068
3255
|
/** Drop staged writes — the fork reads through to the base again. */
|
|
3069
3256
|
discard(): void;
|
|
3257
|
+
/** The staged delta vs the CURRENT base, as structural ops (inspect, persist, invert). */
|
|
3258
|
+
ops(): StoreOp[];
|
|
3070
3259
|
};
|
|
3071
3260
|
/**
|
|
3072
3261
|
* Per-path 3-way merge. Reference-equality short-circuits do the work: a subtree the fork never
|
|
@@ -3093,86 +3282,355 @@ type ForkStoreOptions<T> = toStoreOptions & {
|
|
|
3093
3282
|
};
|
|
3094
3283
|
declare function forkStore<T extends Record<string, any>>(base: WritableSignalStore<T>, opt?: ForkStoreOptions<T>): Fork<T>;
|
|
3095
3284
|
|
|
3285
|
+
/** Hybrid logical clock stamp: physical epoch ms + logical counter for same-ms ordering. */
|
|
3286
|
+
type Hlc = {
|
|
3287
|
+
readonly p: number;
|
|
3288
|
+
readonly l: number;
|
|
3289
|
+
};
|
|
3290
|
+
/** Total order over stamps alone; ties break on `writer` via {@link compareTotal}. */
|
|
3291
|
+
declare function compareHlc(a: Hlc, b: Hlc): number;
|
|
3292
|
+
/** The protocol's total order: (hlc.p, hlc.l, writer). Never returns 0 for distinct writers. */
|
|
3293
|
+
declare function compareTotal(a: Hlc, writerA: string, b: Hlc, writerB: string): number;
|
|
3294
|
+
type HlcClock = {
|
|
3295
|
+
/** Stamp for a locally-emitted envelope: monotonic even when wall time stalls or rewinds. */
|
|
3296
|
+
next(): Hlc;
|
|
3297
|
+
/** Fold an observed remote stamp in, so subsequent local stamps sort after it. */
|
|
3298
|
+
observe(remote: Hlc): void;
|
|
3299
|
+
};
|
|
3300
|
+
/**
|
|
3301
|
+
* HLC per Kulkarni et al.: convergence never depends on wall clocks, but LWW fairness
|
|
3302
|
+
* degrades under large skew, so observing a remote clock far ahead warns in dev mode.
|
|
3303
|
+
*/
|
|
3304
|
+
declare function createHlcClock(now?: () => number): HlcClock;
|
|
3305
|
+
|
|
3306
|
+
declare const OP_PROTO_VERSION = 1;
|
|
3096
3307
|
type Key = string | number;
|
|
3097
3308
|
/**
|
|
3098
|
-
*
|
|
3099
|
-
*
|
|
3100
|
-
* which is what lets {@link invertBatch} invert an add into a delete.
|
|
3309
|
+
* The wire/journal record (op-protocol RFC §3). `writer` is an opaque principal pseudonym —
|
|
3310
|
+
* natural identity never enters the envelope; `origin` identifies the emitting log instance.
|
|
3101
3311
|
*/
|
|
3102
|
-
type
|
|
3103
|
-
|
|
3104
|
-
path: readonly Key[];
|
|
3105
|
-
next: unknown;
|
|
3106
|
-
prev?: unknown;
|
|
3107
|
-
} | {
|
|
3108
|
-
kind: 'delete';
|
|
3109
|
-
path: readonly Key[];
|
|
3110
|
-
prev: unknown;
|
|
3111
|
-
};
|
|
3112
|
-
/** One emission: every op derived from one commit window (a tick), in path order. */
|
|
3113
|
-
type OpBatch = {
|
|
3114
|
-
/** Identifies the emitting log — filter your own batches on a shared transport. */
|
|
3312
|
+
type OpEnvelope = {
|
|
3313
|
+
readonly proto: number;
|
|
3115
3314
|
readonly origin: string;
|
|
3116
|
-
|
|
3315
|
+
readonly writer: string;
|
|
3117
3316
|
readonly version: number;
|
|
3317
|
+
readonly hlc: Hlc;
|
|
3318
|
+
readonly policyVersion: number;
|
|
3118
3319
|
readonly ops: readonly StoreOp[];
|
|
3119
3320
|
};
|
|
3120
|
-
|
|
3121
|
-
|
|
3321
|
+
declare const CONFLICT_BRAND = "~mmstackConflict";
|
|
3322
|
+
/**
|
|
3323
|
+
* A preserved (jj-style) conflict: both sides survive as data, sync never blocks, and
|
|
3324
|
+
* resolution is just a later write. String-branded so it survives structured clone.
|
|
3325
|
+
*/
|
|
3326
|
+
type Conflicted<T = unknown> = {
|
|
3327
|
+
readonly [CONFLICT_BRAND]: true;
|
|
3328
|
+
readonly mine: T;
|
|
3329
|
+
readonly theirs: T;
|
|
3330
|
+
readonly ancestor?: T;
|
|
3331
|
+
};
|
|
3332
|
+
declare function isConflicted<T = unknown>(value: unknown): value is Conflicted<T>;
|
|
3333
|
+
type MergeContext = {
|
|
3334
|
+
readonly path: readonly Key[];
|
|
3335
|
+
};
|
|
3336
|
+
/**
|
|
3337
|
+
* Resolves a concurrent set-vs-set collision. Called with a deterministic argument order
|
|
3338
|
+
* (`mine` = the side winning the total order) so every peer computes the same value.
|
|
3339
|
+
*/
|
|
3340
|
+
type MergeFn = (ancestor: unknown, mine: unknown, theirs: unknown, ctx: MergeContext) => unknown;
|
|
3341
|
+
type MergePolicyEntry = {
|
|
3342
|
+
/** `'todos.*.title'` or a segment array; `'*'` matches exactly one segment. */
|
|
3343
|
+
readonly path: string | readonly Key[];
|
|
3344
|
+
readonly merge: MergeFn;
|
|
3345
|
+
};
|
|
3346
|
+
declare const lww: MergeFn;
|
|
3347
|
+
declare const mergeThree: MergeFn;
|
|
3348
|
+
declare const preserve: MergeFn;
|
|
3349
|
+
/**
|
|
3350
|
+
* Identity-aware array merge (op-protocol RFC §12 v0): reconciles two concurrent versions of
|
|
3351
|
+
* an array item-wise by a user-provided identity, instead of last-writer-wins on the whole
|
|
3352
|
+
* array. Items are matched by key; per-item fields merge via `merge3` against the ancestor
|
|
3353
|
+
* item; items added on either side survive; an item removed on either side and unedited on
|
|
3354
|
+
* the other stays removed. Item ORDER follows `mine` (the total-order winner), with `theirs`-
|
|
3355
|
+
* only additions appended — positional merging is out of scope (fractional indexing is the
|
|
3356
|
+
* known upgrade if dogfooding demands it). Arrays still TRAVEL as whole-value sets; identity
|
|
3357
|
+
* only shapes conflict resolution, so the wire format is untouched.
|
|
3358
|
+
*/
|
|
3359
|
+
declare function keyedArray(identity: (item: unknown) => unknown, opt?: {
|
|
3360
|
+
item?: MergeFn;
|
|
3361
|
+
}): MergeFn;
|
|
3362
|
+
type ConvergingApply = {
|
|
3363
|
+
/**
|
|
3364
|
+
* Fold an envelope into the register map and return the ops the local store must apply
|
|
3365
|
+
* (post-dominance, post-policy, including replays of newer descendant winners). Pass
|
|
3366
|
+
* `local: true` for envelopes this peer emitted itself: registered, nothing returned.
|
|
3367
|
+
*/
|
|
3368
|
+
ingest(env: OpEnvelope, opt?: {
|
|
3369
|
+
local?: boolean;
|
|
3370
|
+
}): StoreOp[];
|
|
3371
|
+
/** Drop all registers (snapshot compaction / rehydration boundary). */
|
|
3372
|
+
reset(): void;
|
|
3373
|
+
};
|
|
3374
|
+
/**
|
|
3375
|
+
* The unsequenced-topology convergence core (op-protocol RFC §4): a per-path last-writer-wins
|
|
3376
|
+
* register map over the total order (hlc, writer), with subtree dominance. Order-independent:
|
|
3377
|
+
* any arrival order of the same envelope set yields the same state.
|
|
3378
|
+
*/
|
|
3379
|
+
declare function createConvergingApply(opt?: {
|
|
3380
|
+
policies?: readonly MergePolicyEntry[];
|
|
3381
|
+
}): ConvergingApply;
|
|
3382
|
+
type RebaseResult<T = unknown> = {
|
|
3383
|
+
root: T;
|
|
3384
|
+
/** Pending batches re-based onto the remote state, `prev`s refreshed. */
|
|
3385
|
+
pending: StoreOp[][];
|
|
3386
|
+
};
|
|
3387
|
+
/**
|
|
3388
|
+
* The shared rebase routine (op-protocol RFC §5): invert pending, apply remote, re-apply
|
|
3389
|
+
* pending through the merge policies. Pure — branching's `rebase()` and the sequenced relay
|
|
3390
|
+
* client both call this.
|
|
3391
|
+
*/
|
|
3392
|
+
declare function rebaseOps<T>(root: T, pending: readonly (readonly StoreOp[])[], remote: readonly StoreOp[], policies?: readonly MergePolicyEntry[]): RebaseResult<T>;
|
|
3393
|
+
/**
|
|
3394
|
+
* A per-path-policy `ForkStrategy` for `forkStore`: a three-way reconcile built from the
|
|
3395
|
+
* shared rebase (invert mine → apply theirs' delta → re-apply mine through the policies).
|
|
3396
|
+
* Paths only one side touched resolve like `merge3`; paths BOTH touched go through the
|
|
3397
|
+
* matching {@link MergePolicyEntry} (`lww` default — fork wins, matching `'fine'`; or
|
|
3398
|
+
* `mergeThree` / `preserve` / custom). Same copy-on-write contract as `'fine'`.
|
|
3399
|
+
*/
|
|
3400
|
+
declare function policyStrategy<T>(policies: readonly MergePolicyEntry[]): (ancestor: T, mine: T, theirs: T) => T;
|
|
3401
|
+
type OpSyncOptions = {
|
|
3402
|
+
/** Opaque principal pseudonym — provided by the app, never minted here (RFC §3). */
|
|
3403
|
+
readonly writer: string;
|
|
3122
3404
|
readonly origin?: string;
|
|
3123
|
-
|
|
3405
|
+
readonly policyVersion?: number;
|
|
3406
|
+
readonly policies?: readonly MergePolicyEntry[];
|
|
3407
|
+
readonly clock?: HlcClock;
|
|
3124
3408
|
readonly injector?: Injector;
|
|
3409
|
+
readonly driver?: OpLogDriver;
|
|
3410
|
+
/** A version gap from a known origin (missed envelopes) — the resync hook. */
|
|
3411
|
+
readonly onGap?: (origin: string, expected: number, got: number) => void;
|
|
3125
3412
|
};
|
|
3126
|
-
type
|
|
3413
|
+
type OpSync<T = unknown> = {
|
|
3414
|
+
readonly origin: string;
|
|
3415
|
+
/** Locally-emitted envelopes, ready for a transport. */
|
|
3416
|
+
subscribe(cb: (env: OpEnvelope) => void): () => void;
|
|
3417
|
+
/** Converging apply of a remote envelope (echo-free; own-origin envelopes are ignored). */
|
|
3418
|
+
receive(env: OpEnvelope): void;
|
|
3419
|
+
/** Synchronously emit any pending local delta now. */
|
|
3420
|
+
flush(): void;
|
|
3421
|
+
/** Per-origin latest versions — the handshake watermark. */
|
|
3422
|
+
watermark(): Record<string, number>;
|
|
3423
|
+
/** The current root + watermark, for answering a peer's hello. */
|
|
3424
|
+
snapshot(): {
|
|
3425
|
+
root: T;
|
|
3426
|
+
wm: Record<string, number>;
|
|
3427
|
+
};
|
|
3127
3428
|
/**
|
|
3128
|
-
*
|
|
3129
|
-
*
|
|
3429
|
+
* Emit the CURRENT root as a root-set envelope — the fresh-room seed of the relay
|
|
3430
|
+
* contract (a room's snapshot root becomes complete once seeded).
|
|
3130
3431
|
*/
|
|
3131
|
-
|
|
3132
|
-
/** The most recent batch — a lossy sampling view (devtools); use `subscribe` for transport. */
|
|
3133
|
-
readonly latest: Signal<OpBatch | null>;
|
|
3432
|
+
seed(): void;
|
|
3134
3433
|
/**
|
|
3135
|
-
*
|
|
3136
|
-
*
|
|
3137
|
-
*
|
|
3138
|
-
* construction. Local writes pending in the current tick are flushed (emitted) first, so
|
|
3139
|
-
* they are never silently folded into the applied baseline.
|
|
3434
|
+
* Replace local state with a peer's snapshot, atomically (one notification wave).
|
|
3435
|
+
* Local envelopes the snapshot doesn't cover (per its watermark) are re-applied on
|
|
3436
|
+
* top, so writes made before hydration are never silently lost.
|
|
3140
3437
|
*/
|
|
3141
|
-
|
|
3142
|
-
|
|
3438
|
+
hydrate(root: T, wm?: Record<string, number>): void;
|
|
3439
|
+
destroy(): void;
|
|
3440
|
+
};
|
|
3441
|
+
declare function opSync<T extends object>(source: WritableSignal<T>, opt: OpSyncOptions): OpSync<T>;
|
|
3442
|
+
|
|
3443
|
+
type StoreHistory = {
|
|
3444
|
+
readonly canUndo: Signal<boolean>;
|
|
3445
|
+
readonly canRedo: Signal<boolean>;
|
|
3446
|
+
/** Revert the most recent tracked change; a no-op when nothing is undoable. */
|
|
3447
|
+
undo(): void;
|
|
3448
|
+
/** Re-apply the most recently undone change. */
|
|
3449
|
+
redo(): void;
|
|
3450
|
+
/** Forget all tracked history (e.g. after a save boundary). */
|
|
3451
|
+
clear(): void;
|
|
3143
3452
|
destroy(): void;
|
|
3144
3453
|
};
|
|
3454
|
+
type StoreHistoryOptions = CreateOpLogOptions & {
|
|
3455
|
+
/** Max entries kept per stack (default 100). */
|
|
3456
|
+
readonly limit?: number;
|
|
3457
|
+
/**
|
|
3458
|
+
* The change stream to track. Defaults to self-diffing `source` (every change to the store
|
|
3459
|
+
* becomes undoable). For collaborative-safe undo, pass a sync client's LOCAL envelope stream
|
|
3460
|
+
* (e.g. an `opSync`'s `subscribe`, which fires only for this peer's own writes) — remote
|
|
3461
|
+
* peers' changes then never land on your undo stack.
|
|
3462
|
+
*/
|
|
3463
|
+
readonly track?: {
|
|
3464
|
+
subscribe(cb: (batch: OpBatch) => void): () => void;
|
|
3465
|
+
};
|
|
3466
|
+
};
|
|
3145
3467
|
/**
|
|
3146
|
-
*
|
|
3147
|
-
*
|
|
3148
|
-
*
|
|
3149
|
-
*
|
|
3468
|
+
* Undo/redo for a copy-on-write store, built on the op-log: each tracked change is stored as
|
|
3469
|
+
* its inverse batch, so `undo()` is one `apply` and history costs only the diffs, not full
|
|
3470
|
+
* snapshots. Redoing is invert-of-the-inverse. A new edit made after an undo clears the redo
|
|
3471
|
+
* stack (linear history). Applying a redo/undo does not itself re-enter history.
|
|
3472
|
+
*
|
|
3473
|
+
* Composes with sync for collaborative undo: pass `track: syncClient` so only YOUR writes are
|
|
3474
|
+
* undoable, while `undo()` emits a normal op that propagates to peers (it writes through the
|
|
3475
|
+
* store, which the sync client picks up).
|
|
3150
3476
|
*/
|
|
3151
|
-
declare function
|
|
3477
|
+
declare function storeHistory<T extends object>(source: WritableSignal<T>, opt?: StoreHistoryOptions): StoreHistory;
|
|
3478
|
+
|
|
3479
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
3152
3480
|
/**
|
|
3153
|
-
*
|
|
3154
|
-
*
|
|
3155
|
-
*
|
|
3156
|
-
*
|
|
3481
|
+
* The minimal async key/value contract persistence needs. Deliberately matches `idb-keyval`'s
|
|
3482
|
+
* top-level `get`/`set`/`del` so its module drops in with no wrapper (`persist(s, { key, store:
|
|
3483
|
+
* idbKeyval })`). Any store backed by structured clone (idb-keyval, Dexie) can hold complex values
|
|
3484
|
+
* without a serialize hook. A Dexie table needs a tiny adapter because it names things differently:
|
|
3157
3485
|
*
|
|
3158
|
-
*
|
|
3159
|
-
*
|
|
3160
|
-
*
|
|
3161
|
-
*
|
|
3486
|
+
* ```ts
|
|
3487
|
+
* const table = db.table<{ key: string; value: unknown }>('kv');
|
|
3488
|
+
* const asyncStore: AsyncStore = {
|
|
3489
|
+
* get: (k) => table.get(k).then((r) => r?.value),
|
|
3490
|
+
* set: (k, v) => table.put({ key: k, value: v }).then(() => undefined),
|
|
3491
|
+
* del: (k) => table.delete(k),
|
|
3492
|
+
* };
|
|
3493
|
+
* ```
|
|
3494
|
+
*/
|
|
3495
|
+
type AsyncStore = {
|
|
3496
|
+
get(key: string): MaybePromise<unknown>;
|
|
3497
|
+
set(key: string, value: unknown): MaybePromise<void>;
|
|
3498
|
+
del(key: string): MaybePromise<void>;
|
|
3499
|
+
};
|
|
3500
|
+
/** Persistence options — the reader-side settings, independent of how the store was created. */
|
|
3501
|
+
type PersistOptions<T> = {
|
|
3502
|
+
/** Storage key for this store's snapshot. Required per call. */
|
|
3503
|
+
readonly key: string;
|
|
3504
|
+
/** The async backend. Falls back to the provided default (see {@link providePersistedStoreOptions}). */
|
|
3505
|
+
readonly store?: AsyncStore;
|
|
3506
|
+
/** Encode before writing. Default identity: structured-clone backends keep complex values. */
|
|
3507
|
+
readonly serialize?: (value: T) => unknown;
|
|
3508
|
+
/** Decode after reading. Default identity. */
|
|
3509
|
+
readonly deserialize?: (raw: unknown) => T;
|
|
3510
|
+
/**
|
|
3511
|
+
* Current schema version of the persisted value. When set, snapshots are written wrapped in a
|
|
3512
|
+
* small version envelope, and a snapshot stamped with an older version is passed through
|
|
3513
|
+
* {@link PersistOptions.migrate} on boot before it is adopted. A snapshot from a *newer* version
|
|
3514
|
+
* than this build is left untouched (a newer client wrote it).
|
|
3515
|
+
*/
|
|
3516
|
+
readonly version?: number;
|
|
3517
|
+
/**
|
|
3518
|
+
* Bring a snapshot from an older `version` up to the current shape. It runs during boot, which
|
|
3519
|
+
* is already async, so it may be async too: lazy-import the migration ladder here and only pay
|
|
3520
|
+
* for it when there is old data to migrate. Receives the decoded old value and the version it
|
|
3521
|
+
* was written with (`0` for a pre-versioning snapshot).
|
|
3522
|
+
*/
|
|
3523
|
+
readonly migrate?: (data: unknown, fromVersion: number) => MaybePromise<T>;
|
|
3524
|
+
/** Coalesce writes by this many ms (default 300). A flush/teardown always writes immediately. */
|
|
3525
|
+
readonly writeDebounceMs?: number;
|
|
3526
|
+
readonly injector?: Injector;
|
|
3527
|
+
};
|
|
3528
|
+
type PersistedStoreOptions<T extends object> = CreateSignalOptions<T> & toStoreOptions & PersistOptions<T>;
|
|
3529
|
+
/**
|
|
3530
|
+
* App-wide defaults for {@link persist} / {@link persistedStore}. Only cross-type settings live
|
|
3531
|
+
* here; `serialize`/`deserialize` are per-call because they depend on the store's value type.
|
|
3532
|
+
*/
|
|
3533
|
+
type PersistedStoreDefaults = {
|
|
3534
|
+
readonly store?: AsyncStore;
|
|
3535
|
+
readonly writeDebounceMs?: number;
|
|
3536
|
+
};
|
|
3537
|
+
declare const PERSISTED_STORE_OPTIONS: InjectionToken<PersistedStoreDefaults>;
|
|
3538
|
+
/**
|
|
3539
|
+
* Wire the {@link AsyncStore} backend (and any shared debounce) once, override per call. The
|
|
3540
|
+
* typical use is to install idb-keyval at bootstrap so every `persist`/`persistedStore` persists
|
|
3541
|
+
* without re-passing the backend.
|
|
3162
3542
|
*
|
|
3163
|
-
*
|
|
3164
|
-
*
|
|
3165
|
-
*
|
|
3543
|
+
* @example
|
|
3544
|
+
* import * as idbKeyval from 'idb-keyval';
|
|
3545
|
+
* providePersistedStoreOptions({ store: idbKeyval });
|
|
3546
|
+
*/
|
|
3547
|
+
declare function providePersistedStoreOptions(opt: PersistedStoreDefaults): Provider;
|
|
3548
|
+
/** Persistence controls for a store, from {@link persist}. */
|
|
3549
|
+
type PersistHandle = {
|
|
3550
|
+
/**
|
|
3551
|
+
* `false` until the first read from the backend settles (or immediately `true` on the server
|
|
3552
|
+
* and when no backend is configured). Gate first paint on it if a stale-flash matters.
|
|
3553
|
+
*/
|
|
3554
|
+
readonly hydrated: Signal<boolean>;
|
|
3555
|
+
/** Force any pending debounced write to the backend now. */
|
|
3556
|
+
flush(): Promise<void>;
|
|
3557
|
+
/** Remove the snapshot from the backend and reset the store to the value it held when attached. */
|
|
3558
|
+
clear(): Promise<void>;
|
|
3559
|
+
};
|
|
3560
|
+
/**
|
|
3561
|
+
* A store plus its persistence controls. Shaped like {@link Fork} (a `.store` field, not the
|
|
3562
|
+
* store itself) because the store is a proxy where any property access resolves a child path,
|
|
3563
|
+
* so controls cannot live on it directly.
|
|
3564
|
+
*/
|
|
3565
|
+
type PersistedStore<T extends object> = {
|
|
3566
|
+
/** The live store. Reads are synchronous; it holds the initial value until hydration lands. */
|
|
3567
|
+
readonly store: WritableSignalStore<T>;
|
|
3568
|
+
} & PersistHandle;
|
|
3569
|
+
/**
|
|
3570
|
+
* Attach durable local persistence to an EXISTING store: its whole-value snapshot is written to an
|
|
3571
|
+
* async backend (IndexedDB via idb-keyval or Dexie) and restored on boot. A reader over the store,
|
|
3572
|
+
* so it composes with the other op-log readers (`tabSync`, `@mmstack/mesh`) on the same store — a
|
|
3573
|
+
* persisted, synced graph is just two readers. Local durability, not sync.
|
|
3574
|
+
*
|
|
3575
|
+
* Because the backend is async, hydration cannot precede the first read: the store keeps its current
|
|
3576
|
+
* value, then adopts the persisted snapshot once the backend answers, UNLESS a write happened first
|
|
3577
|
+
* (an explicit boot-time write wins over stale disk). Writes are coalesced and flushed on teardown
|
|
3578
|
+
* and on page hide, so the last change is never lost. On the server it is a no-op.
|
|
3579
|
+
*
|
|
3580
|
+
* When the persisted shape evolves, pass `version` and a `migrate` hook: an older snapshot is
|
|
3581
|
+
* brought forward on boot before it is adopted, then re-persisted in the new shape. Because boot is
|
|
3582
|
+
* already async, `migrate` may be async, so the migration ladder can be lazy-imported.
|
|
3583
|
+
*/
|
|
3584
|
+
declare function persist<T extends object>(source: WritableSignalStore<T>, opt: PersistOptions<T>): PersistHandle;
|
|
3585
|
+
/**
|
|
3586
|
+
* A `store` with {@link persist} already attached: a whole-value snapshot persisted to an async
|
|
3587
|
+
* backend and restored on boot. Equivalent to `const s = store(initial); persist(s, opt)` — reach
|
|
3588
|
+
* for `persist` directly when you want persistence on a store you already have (e.g. to also
|
|
3589
|
+
* `meshSync` it).
|
|
3590
|
+
*/
|
|
3591
|
+
declare function persistedStore<T extends object>(initial: T, opt: PersistedStoreOptions<T>): PersistedStore<T>;
|
|
3592
|
+
|
|
3593
|
+
/** Identity selector for keyed array reconciliation: a property name, or a function per item. */
|
|
3594
|
+
type ReconcileKey = string | ((item: any) => unknown);
|
|
3595
|
+
/**
|
|
3596
|
+
* Produces a value equal to `next` but sharing as much of `prev`'s reference structure as possible:
|
|
3597
|
+
* an object subtree that did not change keeps its `prev` reference, and array items are matched by
|
|
3598
|
+
* `key` so a surviving item keeps its identity across a reorder/insert/remove (only added items are
|
|
3599
|
+
* new, only removed items are dropped). This is what lets a derived store recompute without tearing
|
|
3600
|
+
* down every downstream `computed` that reads an unchanged part of it.
|
|
3601
|
+
*/
|
|
3602
|
+
declare function reconcile<T>(prev: T, next: T, key?: ReconcileKey): T;
|
|
3603
|
+
type ProjectionOptions = toStoreOptions & {
|
|
3604
|
+
/** Identity key for reconciling array items (default `'id'`). */
|
|
3605
|
+
readonly key?: ReconcileKey;
|
|
3606
|
+
};
|
|
3607
|
+
/**
|
|
3608
|
+
* A derived STORE, the store-shaped counterpart to `computed`. `fn` receives a mutable draft seeded
|
|
3609
|
+
* with the current value and either mutates it in place or returns a new value; whichever it does,
|
|
3610
|
+
* the result is reconciled against the previous value (see {@link reconcile}) so unchanged subtrees
|
|
3611
|
+
* keep reference identity and keyed array items keep their proxy identity. Reading through the
|
|
3612
|
+
* returned store is fine-grained: a `computed` over one field only recomputes when that field
|
|
3613
|
+
* actually changes, even though the whole projection re-ran.
|
|
3614
|
+
*
|
|
3615
|
+
* Recompute is pull-based, exactly like `computed`: the projection is memoized and re-runs on the
|
|
3616
|
+
* first read after a signal `fn` depends on changes, so reads are always coherent (no waiting on an
|
|
3617
|
+
* effect flush) and nothing recomputes while nobody reads. `fn` must be pure, it runs inside the
|
|
3618
|
+
* reactive computation. Prefer `computed` for a plain value; reach for `projection` when you want
|
|
3619
|
+
* the per-property tracking of a store on top of a derivation.
|
|
3166
3620
|
*
|
|
3167
3621
|
* ```ts
|
|
3168
|
-
* const
|
|
3169
|
-
*
|
|
3170
|
-
* log.subscribe((b) => channel.postMessage(encode(b))); // ship
|
|
3171
|
-
* channel.onmessage = (m) => log.apply(decode(m.data)); // apply — echo-free
|
|
3172
|
-
* s.todos[0].done.set(true); // → { kind: 'set', path: ['todos', 0, 'done'], … }
|
|
3622
|
+
* const active = projection<User[]>(() => users().filter((u) => u.active), [], { key: 'id' });
|
|
3623
|
+
* // active[0].name(); — surviving users keep identity across recomputes
|
|
3173
3624
|
* ```
|
|
3625
|
+
*
|
|
3626
|
+
* Needs an injection context (or an explicit `injector`) for the store layer's cleanup on the main
|
|
3627
|
+
* thread; with an explicit store context (`createStoreContext()`) it is injector-free, so it also
|
|
3628
|
+
* runs on a worker host.
|
|
3629
|
+
*
|
|
3630
|
+
* @param fn receives the current draft; mutate it, or return new data.
|
|
3631
|
+
* @param seed the initial value, held before the first run.
|
|
3174
3632
|
*/
|
|
3175
|
-
declare function
|
|
3633
|
+
declare function projection<T extends object>(fn: (draft: T) => void | T, seed: T, opt?: ProjectionOptions): SignalStore<T>;
|
|
3176
3634
|
|
|
3177
3635
|
/**
|
|
3178
3636
|
* Interface for storage mechanisms compatible with the `stored` signal.
|
|
@@ -3339,10 +3797,25 @@ type SyncSignalOptions = {
|
|
|
3339
3797
|
*/
|
|
3340
3798
|
injector?: Injector;
|
|
3341
3799
|
};
|
|
3800
|
+
/**
|
|
3801
|
+
* Store mode (`tabSync(store, …)`): syncs structural OPS instead of whole values — concurrent
|
|
3802
|
+
* edits to different leaves merge instead of clobbering, and a joining tab hydrates from a
|
|
3803
|
+
* peer via the hello exchange (up-to-date / snapshot; op-protocol RFC §6).
|
|
3804
|
+
*/
|
|
3805
|
+
type StoreTabSyncOptions = SyncSignalOptions & {
|
|
3806
|
+
/** Principal pseudonym on emitted envelopes. Tabs share one user, so a default is fine. */
|
|
3807
|
+
writer?: string;
|
|
3808
|
+
/** Per-path merge policies (`lww` default; `mergeThree`, `preserve`, or custom). */
|
|
3809
|
+
policies?: readonly MergePolicyEntry[];
|
|
3810
|
+
/** How long a joining tab waits for a peer's answer before deciding it IS the base. */
|
|
3811
|
+
helloTimeoutMs?: number;
|
|
3812
|
+
/** Max response jitter — first responder wins, others cancel. */
|
|
3813
|
+
jitterMs?: number;
|
|
3814
|
+
};
|
|
3342
3815
|
/**
|
|
3343
3816
|
* @example tabSync(signal('dark'), { id: 'theme' })
|
|
3344
3817
|
*/
|
|
3345
|
-
declare function tabSync<T extends WritableSignal<any>>(sig: T, opt: SyncSignalOptions | string): T;
|
|
3818
|
+
declare function tabSync<T extends WritableSignal<any>>(sig: T, opt: StoreTabSyncOptions | SyncSignalOptions | string): T;
|
|
3346
3819
|
/**
|
|
3347
3820
|
* @deprecated Use `tabSync` with `SyncSignalOptions` instead and pass the options as the second argument
|
|
3348
3821
|
* @throws {Error} When deterministic ID generation fails and no explicit ID is provided
|
|
@@ -3632,5 +4105,5 @@ type CreateHistoryOptions<T> = Omit<CreateSignalOptions<T[]>, 'equal'> & {
|
|
|
3632
4105
|
*/
|
|
3633
4106
|
declare function withHistory<T>(sourceOrValue: WritableSignal<T> | T, opt?: CreateHistoryOptions<T>): SignalWithHistory<T>;
|
|
3634
4107
|
|
|
3635
|
-
export { MmActivity, MmTransition, MmViewTransitionName, PAUSABLE_OPTIONS, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, batteryStatus, bridgeScopeToPendingTasks, chunked, clipboard, combineWith, createAttributedPending, createForwardingScope, createTransaction, createTransitionScope, debounce, debounced, deferredValue, derived, distinct, elementSize, elementVisibility, extendStore, filter, filterWith, focusWithin, forkStore, geolocation, getTransitionScope, holdUntilReady, idle, indexArray, injectPaused, injectRegisterResource, injectStartTransaction, injectStartTransition, injectTransitionScope, invertBatch, isDerivation, isLeaf, isMutable, isOpaque, isStore, keepPrevious, keyArray, latest, map, mapArray, mapObject, mediaQuery, merge3, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, opLog, opaque, orientation, pageVisibility, pairwise, pausableComputed, pausableEffect, pausableSignal, pipeable, piped, pointerDrag, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, provideForwardingTransitionScope, providePausableOptions, providePaused, provideTransitionScope, registerResource, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, use, windowSize, withHistory };
|
|
3636
|
-
export type { BatteryStatus, ClipboardSignal, Computation, CreateChunkedOptions, CreateDebouncedOptions, CreateHistoryOptions, CreateLatestOptions, CreateOpLogOptions, CreatePooledOptions, CreateProvidedPooledOptions, CreateStoredOptions, CreateThrottledOptions, DebouncedSignal, DeferStrategy, DeferredSignal, DeferredValueOptions, DerivedSignal, ElementSize, ElementSizeOptions, ElementSizeSignal, ElementVisibilityOptions, ElementVisibilitySignal, ExtendStoreOptions, Fork, ForkStoreOptions, ForkStrategy, ForwardingTransitionScope, Frame, GeolocationOptions, GeolocationSignal, IdleOptions, IdleSignal, LatestSignal, MmTransitionContext, MousePositionOptions, MousePositionSignal, MutableSignal, MutableSignalStore, NetworkStatusSignal, OpBatch, OpLog, Opaque, PausableOptions, PauseOption, PipeableSignal, PointerDragOptions, PointerDragSignal, PointerDragState, PointerModifiers, PointerPoint, ReconcileFn, RegisterOptions, ResourceLike, ScreenOrientation, ScreenOrientationState, ScrollPosition, ScrollPositionOptions, ScrollPositionSignal, SensorRunOptions, SignalFromEventOptions, SignalStore, SignalWithHistory, StoreOp, StoreOptions, StoredSignal, SuspendType, ThrottledSignal, Transaction, TransactionRef, TransitionRef, TransitionScope, UntilOptions, UseSource, Vivify, WindowSize, WindowSizeOptions, WindowSizeSignal, WithVivify, WritableSignalStore, toStoreOptions };
|
|
4108
|
+
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 };
|
|
4109
|
+
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 };
|