@mmstack/primitives 20.15.3 → 20.15.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 (`orderedEntries`, `insertElement`, `moveElement`, `rebalanceContainer`), `rebaseOps`, `policyStrategy`, `syncedFork`
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.
@@ -5121,11 +5121,14 @@ const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
5121
5121
  * deterministic on every replica.
5122
5122
  */
5123
5123
  function orderedEntries(container) {
5124
+ return entriesOf(container, (element) => element);
5125
+ }
5126
+ function entriesOf(container, payloadOf) {
5124
5127
  const entries = [];
5125
5128
  for (const key of Object.keys(container)) {
5126
- const value = container[key];
5127
- const raw = isRecord(value) ? value[POS_SEGMENT] : undefined;
5128
- entries.push({ key, pos: typeof raw === 'string' ? raw : '', value });
5129
+ const element = container[key];
5130
+ const raw = isRecord(element) ? element[POS_SEGMENT] : undefined;
5131
+ entries.push({ key, pos: typeof raw === 'string' ? raw : '', value: payloadOf(element) });
5129
5132
  }
5130
5133
  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);
5131
5134
  return entries;
@@ -5145,12 +5148,18 @@ const neighborPositions = (entries, index) => {
5145
5148
  * layer diffs on its own. Returns the assigned position. Re-inserting an existing key overwrites it.
5146
5149
  */
5147
5150
  function insertElement(container, key, value, index) {
5151
+ return insertInto(container, key, value, index, inlineElement);
5152
+ }
5153
+ const inlineElement = (value, pos) => {
5148
5154
  if (POS_SEGMENT in value)
5149
- devError(`insertElement: '${POS_SEGMENT}' is managed, drop it from the value`);
5155
+ devError(`insert: '${POS_SEGMENT}' is managed, drop it from the value`);
5156
+ return { ...value, [POS_SEGMENT]: pos };
5157
+ };
5158
+ function insertInto(container, key, value, index, elementOf) {
5150
5159
  const entries = orderedEntries(container()).filter((e) => e.key !== key);
5151
5160
  const [before, after] = neighborPositions(entries, index ?? entries.length);
5152
5161
  const pos = posBetween(before, after);
5153
- container.update((c) => ({ ...c, [key]: { ...value, [POS_SEGMENT]: pos } }));
5162
+ container.update((c) => ({ ...c, [key]: elementOf(value, pos) }));
5154
5163
  return pos;
5155
5164
  }
5156
5165
  /**
@@ -5217,6 +5226,23 @@ function evenPositions(n) {
5217
5226
  }
5218
5227
  return out;
5219
5228
  }
5229
+ function keyedContainer(config = {}) {
5230
+ return helpersFor(config.key, inlineElement, (element) => element);
5231
+ }
5232
+ function wrappedContainer(config = {}) {
5233
+ return helpersFor(config.key, (value, pos) => ({ [POS_SEGMENT]: pos, value }), (element) => element.value);
5234
+ }
5235
+ function helpersFor(extract, elementOf, payloadOf) {
5236
+ return {
5237
+ entries: (container) => entriesOf(container, payloadOf),
5238
+ insert: (container, a, b, c) => extract
5239
+ ? insertInto(container, extract(a), a, b, elementOf)
5240
+ : insertInto(container, a, b, c, elementOf),
5241
+ move: (container, key, index) => moveElement(container, key, index),
5242
+ remove: (container, key) => removeElement(container, key),
5243
+ rebalance: (sync, container) => rebalanceContainer(sync, container),
5244
+ };
5245
+ }
5220
5246
 
5221
5247
  /**
5222
5248
  * Wire protocol version. Version 2 ops carry `cites` + `epoch` (the dot-citation register).
@@ -7447,5 +7473,5 @@ function withHistory(sourceOrValue, opt) {
7447
7473
  * Generated bundle index. Do not edit.
7448
7474
  */
7449
7475
 
7450
- 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 };
7476
+ 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 };
7451
7477
  //# sourceMappingURL=mmstack-primitives.mjs.map