@mmstack/primitives 22.8.4 → 22.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4661,8 +4661,11 @@ function toStore(source, { injector, vivify = false, noUnionLeaves = false, ...r
4661
4661
  return idx >= 0 && idx < v.length;
4662
4662
  }
4663
4663
  }
4664
- // nullish node values are routinely descended with vivify on `in` must not throw
4665
- return v == null ? false : Reflect.has(v, prop);
4664
+ // nullish/primitive node values are routinely descended with vivify on or probed
4665
+ // with `in` (e.g. isMutable) `in` must not throw
4666
+ return v == null || (typeof v !== 'object' && typeof v !== 'function')
4667
+ ? false
4668
+ : Reflect.has(v, prop);
4666
4669
  },
4667
4670
  ownKeys() {
4668
4671
  const v = untracked(source);
@@ -6867,6 +6870,9 @@ function stored(fallback, { key, store: providedStore, serialize = JSON.stringif
6867
6870
  writable.key = keySig;
6868
6871
  return writable;
6869
6872
  }
6873
+ function isStored(value) {
6874
+ return 'clear' in value && typeof value.clear === 'function' && 'key' in value;
6875
+ }
6870
6876
 
6871
6877
  /** Op-mode sync for a writable store: hello exchange, then live envelopes. */
6872
6878
  function storeTabSync(sig, opt, bus, injector) {
@@ -7104,6 +7110,125 @@ function tabSync(sig, opt) {
7104
7110
  return sig;
7105
7111
  }
7106
7112
 
7113
+ /**
7114
+ * Wraps any member of the writable-signal family so that every synchronous write
7115
+ * first records an ambient "cause" (via the caller-provided `capture` function)
7116
+ * and then delegates to the original write, preserving the wrapped signal's exact
7117
+ * write semantics. The recorded cause is exposed through a plain {@link Traced.causedBy}
7118
+ * read, letting a downstream consumer attribute a change to whatever caused it —
7119
+ * without any dependency on a particular telemetry system.
7120
+ *
7121
+ * Capture is **synchronous-only** and last-writer-wins: the cause is whatever
7122
+ * `capture()` returns during the `set`/`update`/`mutate`/`inline` call. A write
7123
+ * performed after an `await` (once the ambient cause has been cleared) records
7124
+ * `undefined`; a write while `capture()` returns `undefined` clears the cause.
7125
+ * `causedBy()` is a plain read, not a signal — reading it never subscribes, and
7126
+ * a cause-only change (a write with a different cause but no dependent-visible
7127
+ * value change) never invalidates a consumer.
7128
+ *
7129
+ * The traced twin behaves like the original except for `causedBy`: it carries the
7130
+ * wrapped signal's surface — `asReadonly`, the `mutate`/`inline` methods of a
7131
+ * mutable, and the `from` of a derived — and the underlying write semantics survive
7132
+ * (a traced `derived` still writes through to its source; a traced `toWritable` still
7133
+ * runs its custom `set`). Reads through the twin stay tracked — consumers subscribe
7134
+ * exactly as they would to the source.
7135
+ *
7136
+ * @typeParam S - The wrapped writable-signal type.
7137
+ * @typeParam C - The type of the captured cause.
7138
+ *
7139
+ * @param sig - The writable signal to trace. Any family member is accepted
7140
+ * (`signal`, `mutable`, `derived`, `toWritable`, `stored`, ...).
7141
+ * @param capture - A function invoked synchronously at the start of every write.
7142
+ * Its return value becomes the current cause; returning `undefined`
7143
+ * clears it.
7144
+ * @param opt - Optional configuration.
7145
+ * @param opt.pure - If `true` (the default), the returned twin is a **new** signal
7146
+ * that reads through to `sig`; `sig` itself is left untouched, so
7147
+ * tracing a shared signal never makes the original writable or
7148
+ * alters its behaviour.
7149
+ *
7150
+ * CAUTION: with `pure: false` the write methods are patched
7151
+ * directly onto the `sig` object you passed in — every other
7152
+ * holder of that signal now records causes on write. Only use it
7153
+ * with a signal you created and own exclusively.
7154
+ *
7155
+ * @returns A {@link Traced} twin of `sig` exposing `causedBy()`.
7156
+ *
7157
+ * @example
7158
+ * // A refetch attributing itself to whatever wrote the query.
7159
+ * let activeCause: string | undefined;
7160
+ * const query = traced(signal(''), () => activeCause);
7161
+ *
7162
+ * activeCause = 'user-typed';
7163
+ * query.set('hello');
7164
+ * query.causedBy(); // 'user-typed'
7165
+ *
7166
+ * @example
7167
+ * // Tracing a shared, read-only-exposed signal without making it writable.
7168
+ * const shared = signal(0);
7169
+ * const twin = traced(shared, () => currentInteraction());
7170
+ * twin === shared; // false — the original is untouched
7171
+ */
7172
+ function traced(sig, capture, opt) {
7173
+ const twin = (opt?.pure === false ? sig : facadeOf(sig));
7174
+ let cause;
7175
+ const setOriginal = sig.set.bind(sig);
7176
+ twin.set = (value) => {
7177
+ cause = capture();
7178
+ setOriginal(value);
7179
+ };
7180
+ const updateOriginal = sig.update.bind(sig);
7181
+ twin.update = (updater) => {
7182
+ cause = capture();
7183
+ updateOriginal(updater);
7184
+ };
7185
+ if (isMutable(sig)) {
7186
+ const source = sig;
7187
+ const mutableTwin = twin;
7188
+ const mutateOriginal = source.mutate.bind(source);
7189
+ mutableTwin.mutate = (updater) => {
7190
+ cause = capture();
7191
+ mutateOriginal(updater);
7192
+ };
7193
+ const inlineOriginal = source.inline.bind(source);
7194
+ mutableTwin.inline = (updater) => {
7195
+ cause = capture();
7196
+ inlineOriginal(updater);
7197
+ };
7198
+ }
7199
+ if (isStored(sig)) {
7200
+ const source = sig;
7201
+ const storedTwin = twin;
7202
+ const clearOriginal = source.clear.bind(source);
7203
+ storedTwin.clear = () => {
7204
+ cause = capture();
7205
+ clearOriginal();
7206
+ };
7207
+ }
7208
+ twin.causedBy = () => cause;
7209
+ return twin;
7210
+ }
7211
+ /**
7212
+ * Builds a read-through facade over `sig`: a fresh signal whose reads track `sig`,
7213
+ * carrying `sig`'s non-write surface (`asReadonly`, and `from` on a derived signal)
7214
+ * by explicit delegation. The write methods are attached by {@link traced}. The
7215
+ * facade's `equal` always reports "changed", so it forwards every notification `sig`
7216
+ * emits — including the same-reference `mutate`/`inline` force-notifies that a default
7217
+ * `computed` would swallow (see `mutable`'s docs). The change decision is thereby
7218
+ * delegated entirely to `sig`.
7219
+ */
7220
+ function facadeOf(sig) {
7221
+ const facade = computed(sig, { equal: () => false });
7222
+ facade.asReadonly = sig.asReadonly.bind(sig);
7223
+ if (isDerivation(sig)) {
7224
+ facade.from = sig.from;
7225
+ }
7226
+ if (isStored(sig)) {
7227
+ facade.key = sig.key;
7228
+ }
7229
+ return facade;
7230
+ }
7231
+
7107
7232
  function until(sourceSignal, predicate, options = {}) {
7108
7233
  return new Promise((resolve, reject) => {
7109
7234
  let effectRef;
@@ -7304,5 +7429,5 @@ function withHistory(sourceOrValue, opt) {
7304
7429
  * Generated bundle index. Do not edit.
7305
7430
  */
7306
7431
 
7307
- 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 };
7432
+ 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, 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 };
7308
7433
  //# sourceMappingURL=mmstack-primitives.mjs.map