@mmstack/primitives 20.15.4 → 20.16.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 +3 -0
- package/fesm2022/mmstack-primitives.mjs +102 -4
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/index.d.ts +36 -7
- package/package.json +1 -1
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`
|
|
@@ -1917,18 +1917,27 @@ function createSetter(source) {
|
|
|
1917
1917
|
|
|
1918
1918
|
function keepPrevious(src, opt) {
|
|
1919
1919
|
const mutableSrc = isWritableSignal(src) && isMutable(src);
|
|
1920
|
+
const { fallback, ...signalOpt } = opt ?? {};
|
|
1920
1921
|
let cnt = 0;
|
|
1921
1922
|
const baseEqual = opt?.equal;
|
|
1922
1923
|
const equal = mutableSrc
|
|
1923
1924
|
? (a, b) => cnt > 0 ? false : baseEqual ? baseEqual(a, b) : Object.is(a, b)
|
|
1924
1925
|
: baseEqual;
|
|
1925
|
-
const persisted = linkedSignal(...(ngDevMode ? [{ debugName: "persisted", ...
|
|
1926
|
+
const persisted = linkedSignal(...(ngDevMode ? [{ debugName: "persisted", ...signalOpt,
|
|
1926
1927
|
source: () => src(),
|
|
1927
|
-
computation: (next, prev) =>
|
|
1928
|
+
computation: (next, prev) => {
|
|
1929
|
+
if (next !== undefined)
|
|
1930
|
+
return next;
|
|
1931
|
+
return prev !== undefined ? prev.value : fallback;
|
|
1932
|
+
},
|
|
1928
1933
|
equal }] : [{
|
|
1929
|
-
...
|
|
1934
|
+
...signalOpt,
|
|
1930
1935
|
source: () => src(),
|
|
1931
|
-
computation: (next, prev) =>
|
|
1936
|
+
computation: (next, prev) => {
|
|
1937
|
+
if (next !== undefined)
|
|
1938
|
+
return next;
|
|
1939
|
+
return prev !== undefined ? prev.value : fallback;
|
|
1940
|
+
},
|
|
1932
1941
|
equal,
|
|
1933
1942
|
}]));
|
|
1934
1943
|
if (isWritableSignal(src)) {
|
|
@@ -5332,6 +5341,81 @@ function validateEnvelope(env) {
|
|
|
5332
5341
|
}
|
|
5333
5342
|
return null;
|
|
5334
5343
|
}
|
|
5344
|
+
function wireValueViolation(value) {
|
|
5345
|
+
return violationAt(value, false, []);
|
|
5346
|
+
}
|
|
5347
|
+
function violationAt(value, nested, ancestors) {
|
|
5348
|
+
if (value === undefined)
|
|
5349
|
+
return nested ? 'undefined-in-container' : null;
|
|
5350
|
+
if (value === null)
|
|
5351
|
+
return null;
|
|
5352
|
+
const t = typeof value;
|
|
5353
|
+
if (t === 'boolean' || t === 'string')
|
|
5354
|
+
return null;
|
|
5355
|
+
if (t === 'number') {
|
|
5356
|
+
if (!Number.isFinite(value))
|
|
5357
|
+
return 'non-finite-number';
|
|
5358
|
+
return Object.is(value, -0) ? 'negative-zero' : null;
|
|
5359
|
+
}
|
|
5360
|
+
if (t === 'bigint')
|
|
5361
|
+
return 'bigint';
|
|
5362
|
+
if (t === 'function')
|
|
5363
|
+
return 'function';
|
|
5364
|
+
if (t === 'symbol')
|
|
5365
|
+
return 'symbol';
|
|
5366
|
+
if (ancestors.includes(value))
|
|
5367
|
+
return 'cycle';
|
|
5368
|
+
ancestors.push(value);
|
|
5369
|
+
try {
|
|
5370
|
+
if (Array.isArray(value)) {
|
|
5371
|
+
for (let i = 0; i < value.length; i++) {
|
|
5372
|
+
if (!Object.hasOwn(value, i))
|
|
5373
|
+
return 'sparse-array';
|
|
5374
|
+
const reason = violationAt(value[i], true, ancestors);
|
|
5375
|
+
if (reason)
|
|
5376
|
+
return reason;
|
|
5377
|
+
}
|
|
5378
|
+
const keys = Object.keys(value);
|
|
5379
|
+
if (keys.length !== value.length)
|
|
5380
|
+
return 'array-named-property';
|
|
5381
|
+
for (const key of keys) {
|
|
5382
|
+
const idx = Number(key);
|
|
5383
|
+
if (String(idx) !== key ||
|
|
5384
|
+
!Number.isInteger(idx) ||
|
|
5385
|
+
idx < 0 ||
|
|
5386
|
+
idx >= value.length) {
|
|
5387
|
+
return 'array-named-property';
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
return null;
|
|
5391
|
+
}
|
|
5392
|
+
const proto = Object.getPrototypeOf(value);
|
|
5393
|
+
if (proto !== Object.prototype && proto !== null)
|
|
5394
|
+
return 'non-plain-object';
|
|
5395
|
+
for (const key of Object.keys(value)) {
|
|
5396
|
+
const reason = violationAt(value[key], true, ancestors);
|
|
5397
|
+
if (reason)
|
|
5398
|
+
return reason;
|
|
5399
|
+
}
|
|
5400
|
+
return null;
|
|
5401
|
+
}
|
|
5402
|
+
finally {
|
|
5403
|
+
ancestors.pop();
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
// dev-only: one warning per offending batch names the first violating op, then stops
|
|
5407
|
+
const lintWireValues = (ops) => {
|
|
5408
|
+
for (const op of ops) {
|
|
5409
|
+
const reason = (op.kind === 'set' ? wireValueViolation(op.next) : null) ??
|
|
5410
|
+
(op.kind !== 'clear' && Object.hasOwn(op, 'prev')
|
|
5411
|
+
? wireValueViolation(op.prev)
|
|
5412
|
+
: null);
|
|
5413
|
+
if (!reason)
|
|
5414
|
+
continue;
|
|
5415
|
+
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.`);
|
|
5416
|
+
return;
|
|
5417
|
+
}
|
|
5418
|
+
};
|
|
5335
5419
|
const lww = (_ancestor, mine) => mine;
|
|
5336
5420
|
const mergeThree = (ancestor, mine, theirs) => merge3(ancestor, mine, theirs);
|
|
5337
5421
|
const preserve = (ancestor, mine, theirs) => ({
|
|
@@ -5880,6 +5964,17 @@ function createConvergingApply(opt) {
|
|
|
5880
5964
|
const reg = registers.get(keyOf$1(path));
|
|
5881
5965
|
return reg ? liveOf(reg) : [];
|
|
5882
5966
|
},
|
|
5967
|
+
liveUnder: (path) => {
|
|
5968
|
+
const key = keyOf$1(path);
|
|
5969
|
+
const out = [];
|
|
5970
|
+
const at = registers.get(key);
|
|
5971
|
+
for (const reg of at ? [at, ...descendantsOf(key)] : descendantsOf(key)) {
|
|
5972
|
+
const siblings = liveOf(reg);
|
|
5973
|
+
if (siblings.length)
|
|
5974
|
+
out.push({ path: reg.path, siblings });
|
|
5975
|
+
}
|
|
5976
|
+
return out;
|
|
5977
|
+
},
|
|
5883
5978
|
materialize: () => {
|
|
5884
5979
|
const root = registers.get('');
|
|
5885
5980
|
const res = root?.result;
|
|
@@ -6085,6 +6180,8 @@ function opSync(source, opt) {
|
|
|
6085
6180
|
const emitLocal = (ops) => {
|
|
6086
6181
|
const frontier = scopeFrontier;
|
|
6087
6182
|
const stamped = conv.stamp(ops, { bump: bumping, frontier });
|
|
6183
|
+
if (isDevMode())
|
|
6184
|
+
lintWireValues(stamped);
|
|
6088
6185
|
const nextVersion = (versions.get(origin) ?? 0) + 1;
|
|
6089
6186
|
const env = {
|
|
6090
6187
|
proto: OP_PROTO_VERSION,
|
|
@@ -6189,6 +6286,7 @@ function opSync(source, opt) {
|
|
|
6189
6286
|
log.flush(); // fold pending base writes in first, so they count as observed
|
|
6190
6287
|
return conv.captureFrontier();
|
|
6191
6288
|
},
|
|
6289
|
+
liveUnder: (path) => conv.liveUnder(path),
|
|
6192
6290
|
commitScope: (frontier, fn) => {
|
|
6193
6291
|
log.flush(); // earlier pending writes emit against the live frontier, not this one
|
|
6194
6292
|
scopeFrontier = frontier;
|