@mmstack/primitives 21.10.4 → 21.11.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.
package/README.md
CHANGED
|
@@ -369,8 +369,11 @@ The foundation of stale-while-revalidate. Wraps a signal so it **holds its last
|
|
|
369
369
|
import { keepPrevious } from '@mmstack/primitives';
|
|
370
370
|
|
|
371
371
|
const held = keepPrevious(resource.value); // drops to undefined mid-reload → keeps last value
|
|
372
|
+
const rows = keepPrevious(resource.value, { fallback: [] }); // [] only until the first value lands
|
|
372
373
|
```
|
|
373
374
|
|
|
375
|
+
`fallback` is yielded only while nothing has ever been defined; after the first defined value the previous value covers every gap, never the fallback. Like any linked signal the hold is lazy: it carries a value it has computed with, so place it over the value your readers read — reading is what feeds it. `@mmstack/resource` does exactly that for its `keepPrevious` option.
|
|
376
|
+
|
|
374
377
|
If the source is writable, `set` / `update` / `asReadonly` (and `mutate` / `inline` / `from` for mutable / derived sources) are forwarded through, so it stays a drop-in replacement. `@mmstack/resource` uses it under the hood for its `keepPrevious` option.
|
|
375
378
|
|
|
376
379
|
### Keep-alive — `MmActivity` / `injectPaused` / `providePaused`
|
|
@@ -1871,14 +1871,19 @@ function isDerivation(sig) {
|
|
|
1871
1871
|
|
|
1872
1872
|
function keepPrevious(src, opt) {
|
|
1873
1873
|
const mutableSrc = isWritableSignal$2(src) && isMutable(src);
|
|
1874
|
+
const { fallback, ...signalOpt } = opt ?? {};
|
|
1874
1875
|
let cnt = 0;
|
|
1875
1876
|
const baseEqual = opt?.equal;
|
|
1876
1877
|
const equal = mutableSrc
|
|
1877
1878
|
? (a, b) => cnt > 0 ? false : baseEqual ? baseEqual(a, b) : Object.is(a, b)
|
|
1878
1879
|
: baseEqual;
|
|
1879
|
-
const persisted = linkedSignal({ ...(ngDevMode ? { debugName: "persisted" } : /* istanbul ignore next */ {}), ...
|
|
1880
|
+
const persisted = linkedSignal({ ...(ngDevMode ? { debugName: "persisted" } : /* istanbul ignore next */ {}), ...signalOpt,
|
|
1880
1881
|
source: () => src(),
|
|
1881
|
-
computation: (next, prev) =>
|
|
1882
|
+
computation: (next, prev) => {
|
|
1883
|
+
if (next !== undefined)
|
|
1884
|
+
return next;
|
|
1885
|
+
return prev !== undefined ? prev.value : fallback;
|
|
1886
|
+
},
|
|
1882
1887
|
equal });
|
|
1883
1888
|
if (isWritableSignal$2(src)) {
|
|
1884
1889
|
persisted.set = src.set;
|
|
@@ -5112,6 +5117,81 @@ function validateEnvelope(env) {
|
|
|
5112
5117
|
}
|
|
5113
5118
|
return null;
|
|
5114
5119
|
}
|
|
5120
|
+
function wireValueViolation(value) {
|
|
5121
|
+
return violationAt(value, false, []);
|
|
5122
|
+
}
|
|
5123
|
+
function violationAt(value, nested, ancestors) {
|
|
5124
|
+
if (value === undefined)
|
|
5125
|
+
return nested ? 'undefined-in-container' : null;
|
|
5126
|
+
if (value === null)
|
|
5127
|
+
return null;
|
|
5128
|
+
const t = typeof value;
|
|
5129
|
+
if (t === 'boolean' || t === 'string')
|
|
5130
|
+
return null;
|
|
5131
|
+
if (t === 'number') {
|
|
5132
|
+
if (!Number.isFinite(value))
|
|
5133
|
+
return 'non-finite-number';
|
|
5134
|
+
return Object.is(value, -0) ? 'negative-zero' : null;
|
|
5135
|
+
}
|
|
5136
|
+
if (t === 'bigint')
|
|
5137
|
+
return 'bigint';
|
|
5138
|
+
if (t === 'function')
|
|
5139
|
+
return 'function';
|
|
5140
|
+
if (t === 'symbol')
|
|
5141
|
+
return 'symbol';
|
|
5142
|
+
if (ancestors.includes(value))
|
|
5143
|
+
return 'cycle';
|
|
5144
|
+
ancestors.push(value);
|
|
5145
|
+
try {
|
|
5146
|
+
if (Array.isArray(value)) {
|
|
5147
|
+
for (let i = 0; i < value.length; i++) {
|
|
5148
|
+
if (!Object.hasOwn(value, i))
|
|
5149
|
+
return 'sparse-array';
|
|
5150
|
+
const reason = violationAt(value[i], true, ancestors);
|
|
5151
|
+
if (reason)
|
|
5152
|
+
return reason;
|
|
5153
|
+
}
|
|
5154
|
+
const keys = Object.keys(value);
|
|
5155
|
+
if (keys.length !== value.length)
|
|
5156
|
+
return 'array-named-property';
|
|
5157
|
+
for (const key of keys) {
|
|
5158
|
+
const idx = Number(key);
|
|
5159
|
+
if (String(idx) !== key ||
|
|
5160
|
+
!Number.isInteger(idx) ||
|
|
5161
|
+
idx < 0 ||
|
|
5162
|
+
idx >= value.length) {
|
|
5163
|
+
return 'array-named-property';
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
return null;
|
|
5167
|
+
}
|
|
5168
|
+
const proto = Object.getPrototypeOf(value);
|
|
5169
|
+
if (proto !== Object.prototype && proto !== null)
|
|
5170
|
+
return 'non-plain-object';
|
|
5171
|
+
for (const key of Object.keys(value)) {
|
|
5172
|
+
const reason = violationAt(value[key], true, ancestors);
|
|
5173
|
+
if (reason)
|
|
5174
|
+
return reason;
|
|
5175
|
+
}
|
|
5176
|
+
return null;
|
|
5177
|
+
}
|
|
5178
|
+
finally {
|
|
5179
|
+
ancestors.pop();
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5182
|
+
// dev-only: one warning per offending batch names the first violating op, then stops
|
|
5183
|
+
const lintWireValues = (ops) => {
|
|
5184
|
+
for (const op of ops) {
|
|
5185
|
+
const reason = (op.kind === 'set' ? wireValueViolation(op.next) : null) ??
|
|
5186
|
+
(op.kind !== 'clear' && Object.hasOwn(op, 'prev')
|
|
5187
|
+
? wireValueViolation(op.prev)
|
|
5188
|
+
: null);
|
|
5189
|
+
if (!reason)
|
|
5190
|
+
continue;
|
|
5191
|
+
console.warn(`[@mmstack/primitives] op value at "${op.path.join('.')}" will not survive a JSON transport (${reason}): peers materialize a different value than this emitter, and the room can diverge. Op values must round-trip JSON unchanged; encode rich leaves as strings.`);
|
|
5192
|
+
return;
|
|
5193
|
+
}
|
|
5194
|
+
};
|
|
5115
5195
|
const lww = (_ancestor, mine) => mine;
|
|
5116
5196
|
const mergeThree = (ancestor, mine, theirs) => merge3(ancestor, mine, theirs);
|
|
5117
5197
|
const preserve = (ancestor, mine, theirs) => ({
|
|
@@ -5660,6 +5740,17 @@ function createConvergingApply(opt) {
|
|
|
5660
5740
|
const reg = registers.get(keyOf$1(path));
|
|
5661
5741
|
return reg ? liveOf(reg) : [];
|
|
5662
5742
|
},
|
|
5743
|
+
liveUnder: (path) => {
|
|
5744
|
+
const key = keyOf$1(path);
|
|
5745
|
+
const out = [];
|
|
5746
|
+
const at = registers.get(key);
|
|
5747
|
+
for (const reg of at ? [at, ...descendantsOf(key)] : descendantsOf(key)) {
|
|
5748
|
+
const siblings = liveOf(reg);
|
|
5749
|
+
if (siblings.length)
|
|
5750
|
+
out.push({ path: reg.path, siblings });
|
|
5751
|
+
}
|
|
5752
|
+
return out;
|
|
5753
|
+
},
|
|
5663
5754
|
materialize: () => {
|
|
5664
5755
|
const root = registers.get('');
|
|
5665
5756
|
const res = root?.result;
|
|
@@ -5865,6 +5956,8 @@ function opSync(source, opt) {
|
|
|
5865
5956
|
const emitLocal = (ops) => {
|
|
5866
5957
|
const frontier = scopeFrontier;
|
|
5867
5958
|
const stamped = conv.stamp(ops, { bump: bumping, frontier });
|
|
5959
|
+
if (isDevMode())
|
|
5960
|
+
lintWireValues(stamped);
|
|
5868
5961
|
const nextVersion = (versions.get(origin) ?? 0) + 1;
|
|
5869
5962
|
const env = {
|
|
5870
5963
|
proto: OP_PROTO_VERSION,
|
|
@@ -5969,6 +6062,7 @@ function opSync(source, opt) {
|
|
|
5969
6062
|
log.flush(); // fold pending base writes in first, so they count as observed
|
|
5970
6063
|
return conv.captureFrontier();
|
|
5971
6064
|
},
|
|
6065
|
+
liveUnder: (path) => conv.liveUnder(path),
|
|
5972
6066
|
commitScope: (frontier, fn) => {
|
|
5973
6067
|
log.flush(); // earlier pending writes emit against the live frontier, not this one
|
|
5974
6068
|
scopeFrontier = frontier;
|