@mmstack/primitives 22.10.3 → 22.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ npm install @mmstack/primitives
|
|
|
21
21
|
- [Effects](#effects) — `nestedEffect`
|
|
22
22
|
- [Concurrency & transitions](#concurrency--transitions) — `keepPrevious`, keep-alive (`MmActivity`), `pausable*` / `providePausableOptions`, Suspense (`mm-suspense`), hold-and-swap (`*mmTransition`), per-element morphs (`mmViewTransitionName`), async derivations (`latest` / `use`), `deferredValue`, `startTransition` / `startTransaction`, `holdUntilReady`
|
|
23
23
|
- [History & persistence](#history--persistence) — `withHistory`, `storeHistory`, `stored`, `persistedStore`, `tabSync`, `opLog`
|
|
24
|
-
- [Sync & convergence](#sync--convergence) — `opSync`, `tabSync(store)`, merge policies (`lww`, `mergeThree`, `keyedArray`, `preserve`), `Conflicted`, keyed containers (`
|
|
24
|
+
- [Sync & convergence](#sync--convergence) — `opSync`, `tabSync(store)`, merge policies (`lww`, `mergeThree`, `keyedArray`, `preserve`), `Conflicted`, keyed containers (`keyedContainer`, `wrappedContainer`, `orderedEntries`, `posBetween`), `rebaseOps`, `policyStrategy`, `syncedFork`
|
|
25
25
|
- [Observability](#observability) — `provideConcurrencyInstrumentation`, `perfCustomTracks`
|
|
26
26
|
- [Performance helpers](#performance-helpers) — `chunked`, `pooled` / `pooledArray` / `pooledMap` / `pooledSet`
|
|
27
27
|
- [Sensors](#sensors) — `sensor()` facade + browser-state signals
|
|
@@ -792,6 +792,24 @@ const board = tabSync(store({ title: 'Board', todos: [] }), {
|
|
|
792
792
|
|
|
793
793
|
A **merge policy** decides the result when two peers change one path at once: `lww` (default), `mergeThree` (three-way against the common ancestor), `keyedArray(idFn)` (list reconcile by identity), or `preserve` (both sides survive as a `Conflicted` value; `isConflicted(v)` narrows it, resolution is a later write). `rebaseOps(root, pending, remote, policies)` is the pure invert-apply-reapply routine behind optimistic updates and offline queues, and `policyStrategy(policies)` gives a `forkStore` the same per-path resolution. This is what [`@mmstack/mesh`](https://www.npmjs.com/package/@mmstack/mesh) wraps for multiplayer.
|
|
794
794
|
|
|
795
|
+
### Keyed containers
|
|
796
|
+
|
|
797
|
+
**A list several peers reorder is a record keyed by element id, never an array.** Each element carries a fractional position at `~pos`, so an insert is one write at `[list, id]` and a move is one write at `[list, id, '~pos']` — two peers inserting into the same list at once keep both elements, where one whole-array write would have folded over the other.
|
|
798
|
+
|
|
799
|
+
```typescript
|
|
800
|
+
import { keyedContainer } from '@mmstack/primitives';
|
|
801
|
+
|
|
802
|
+
const board = store<{ todos: Record<string, Todo> }>({ todos: {} });
|
|
803
|
+
const todos = keyedContainer({ key: (t: Todo) => t.id }); // or pass the key to insert
|
|
804
|
+
|
|
805
|
+
todos.insert(board.todos, { id: 't1', title: 'Ship it' }, 0);
|
|
806
|
+
todos.move(board.todos, 't1', 3); // writes the position and nothing else
|
|
807
|
+
todos.entries(board.todos()); // reading order: by ~pos, key breaking ties
|
|
808
|
+
todos.rebalance(sync, board.todos); // authority sweep when positions grow long
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
Reading order is a pure function of the materialized value, so every replica agrees without consulting the op log. `wrappedContainer` stores elements as `{ '~pos', value }` instead, keeping the payload a closed record a schema can validate; the choice is fixed when the container is created and never inferred from data, so peers of a synced container must agree on it. `posBetween(before, after)` is the fractional index underneath.
|
|
812
|
+
|
|
795
813
|
## Observability
|
|
796
814
|
|
|
797
815
|
An optional listener seam on the concurrency layer. `provideConcurrencyInstrumentation(listener)` receives events as transition scopes coordinate pending, suspense, and transaction windows; with no listener the taps are no-ops. `perfCustomTracks()` is a ready listener that writes each window to a Chrome DevTools Performance track, and the window hooks are span-shaped, so forwarding to [`@mmstack/telemetry-core`](https://www.npmjs.com/package/@mmstack/telemetry-core) is a direct mapping.
|
|
@@ -6177,11 +6177,14 @@ const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
|
|
|
6177
6177
|
* deterministic on every replica.
|
|
6178
6178
|
*/
|
|
6179
6179
|
function orderedEntries(container) {
|
|
6180
|
+
return entriesOf(container, (element) => element);
|
|
6181
|
+
}
|
|
6182
|
+
function entriesOf(container, payloadOf) {
|
|
6180
6183
|
const entries = [];
|
|
6181
6184
|
for (const key of Object.keys(container)) {
|
|
6182
|
-
const
|
|
6183
|
-
const raw = isRecord(
|
|
6184
|
-
entries.push({ key, pos: typeof raw === 'string' ? raw : '', value });
|
|
6185
|
+
const element = container[key];
|
|
6186
|
+
const raw = isRecord(element) ? element[POS_SEGMENT] : undefined;
|
|
6187
|
+
entries.push({ key, pos: typeof raw === 'string' ? raw : '', value: payloadOf(element) });
|
|
6185
6188
|
}
|
|
6186
6189
|
entries.sort((a, b) => a.pos < b.pos ? -1 : a.pos > b.pos ? 1 : a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
6187
6190
|
return entries;
|
|
@@ -6201,12 +6204,18 @@ const neighborPositions = (entries, index) => {
|
|
|
6201
6204
|
* layer diffs on its own. Returns the assigned position. Re-inserting an existing key overwrites it.
|
|
6202
6205
|
*/
|
|
6203
6206
|
function insertElement(container, key, value, index) {
|
|
6207
|
+
return insertInto(container, key, value, index, inlineElement);
|
|
6208
|
+
}
|
|
6209
|
+
const inlineElement = (value, pos) => {
|
|
6204
6210
|
if (POS_SEGMENT in value)
|
|
6205
|
-
devError(`
|
|
6211
|
+
devError(`insert: '${POS_SEGMENT}' is managed, drop it from the value`);
|
|
6212
|
+
return { ...value, [POS_SEGMENT]: pos };
|
|
6213
|
+
};
|
|
6214
|
+
function insertInto(container, key, value, index, elementOf) {
|
|
6206
6215
|
const entries = orderedEntries(container()).filter((e) => e.key !== key);
|
|
6207
6216
|
const [before, after] = neighborPositions(entries, index ?? entries.length);
|
|
6208
6217
|
const pos = posBetween(before, after);
|
|
6209
|
-
container.update((c) => ({ ...c, [key]:
|
|
6218
|
+
container.update((c) => ({ ...c, [key]: elementOf(value, pos) }));
|
|
6210
6219
|
return pos;
|
|
6211
6220
|
}
|
|
6212
6221
|
/**
|
|
@@ -6273,6 +6282,23 @@ function evenPositions(n) {
|
|
|
6273
6282
|
}
|
|
6274
6283
|
return out;
|
|
6275
6284
|
}
|
|
6285
|
+
function keyedContainer(config = {}) {
|
|
6286
|
+
return helpersFor(config.key, inlineElement, (element) => element);
|
|
6287
|
+
}
|
|
6288
|
+
function wrappedContainer(config = {}) {
|
|
6289
|
+
return helpersFor(config.key, (value, pos) => ({ [POS_SEGMENT]: pos, value }), (element) => element.value);
|
|
6290
|
+
}
|
|
6291
|
+
function helpersFor(extract, elementOf, payloadOf) {
|
|
6292
|
+
return {
|
|
6293
|
+
entries: (container) => entriesOf(container, payloadOf),
|
|
6294
|
+
insert: (container, a, b, c) => extract
|
|
6295
|
+
? insertInto(container, extract(a), a, b, elementOf)
|
|
6296
|
+
: insertInto(container, a, b, c, elementOf),
|
|
6297
|
+
move: (container, key, index) => moveElement(container, key, index),
|
|
6298
|
+
remove: (container, key) => removeElement(container, key),
|
|
6299
|
+
rebalance: (sync, container) => rebalanceContainer(sync, container),
|
|
6300
|
+
};
|
|
6301
|
+
}
|
|
6276
6302
|
|
|
6277
6303
|
const PATH_SEP = '';
|
|
6278
6304
|
const OP_SEP = '';
|
|
@@ -7448,5 +7474,5 @@ function withHistory(sourceOrValue, opt) {
|
|
|
7448
7474
|
* Generated bundle index. Do not edit.
|
|
7449
7475
|
*/
|
|
7450
7476
|
|
|
7451
|
-
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, isStored, 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, pooledKeys, 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, traced, until, use, validateEnvelope, windowSize, withHistory };
|
|
7477
|
+
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, isStored, keepPrevious, keyArray, keyedArray, keyedContainer, 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, pooledKeys, 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, traced, until, use, validateEnvelope, windowSize, withHistory, wrappedContainer };
|
|
7452
7478
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|