doxum 0.1.32 → 0.1.34
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 +33 -1
- package/dist/advanced.cjs +1 -1
- package/dist/advanced.d.cts +1 -1
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.js +1 -1
- package/dist/{dependency-B1qIJJBL.d.cts → dependency-Cbf3C8eJ.d.cts} +2 -2
- package/dist/{dependency-BRgPvyN6.d.ts → dependency-DzxXGYe1.d.ts} +2 -2
- package/dist/index.cjs +580 -212
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -12
- package/dist/index.d.ts +19 -12
- package/dist/index.js +580 -212
- package/dist/index.js.map +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js.map +1 -1
- package/dist/{transform-CENhTaVN.cjs → transform-Bt1Mwgrj.cjs} +310 -280
- package/dist/transform-Bt1Mwgrj.cjs.map +1 -0
- package/dist/{transform-gOtqcdHe.js → transform-RghG_Qxs.js} +305 -275
- package/dist/transform-RghG_Qxs.js.map +1 -0
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +4 -2
- package/skills/doxum-runtime/references/integrations.md +2 -2
- package/skills/doxum-runtime/references/invariants.md +6 -3
- package/skills/doxum-runtime/references/projections.md +62 -3
- package/skills/doxum-runtime/references/public-api.md +14 -4
- package/skills/doxum-runtime/references/recipes.md +32 -0
- package/dist/transform-CENhTaVN.cjs.map +0 -1
- package/dist/transform-gOtqcdHe.js.map +0 -1
|
@@ -119,22 +119,40 @@ const mapRead = (value) => {
|
|
|
119
119
|
/** Immutable map-like view over a CollectionRead. */
|
|
120
120
|
const collectionView = (read, knownIds) => {
|
|
121
121
|
const ids = knownIds ?? read.ids();
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
const iterate = (select) => {
|
|
123
|
+
read.ids();
|
|
124
|
+
let index = 0;
|
|
125
|
+
return {
|
|
126
|
+
next() {
|
|
127
|
+
read.ids();
|
|
128
|
+
if (index === ids.length) return {
|
|
129
|
+
done: true,
|
|
130
|
+
value: void 0
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
done: false,
|
|
134
|
+
value: select(ids[index++])
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
[Symbol.iterator]() {
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
127
141
|
};
|
|
142
|
+
const entries = () => iterate((key) => [key, read.get(key)]);
|
|
143
|
+
const values = () => iterate((key) => read.get(key));
|
|
128
144
|
const view = {
|
|
129
145
|
get: (key) => read.get(key),
|
|
130
146
|
has: (key) => read.has(key),
|
|
131
147
|
get size() {
|
|
148
|
+
read.ids();
|
|
132
149
|
return ids.length;
|
|
133
150
|
},
|
|
134
|
-
keys: () =>
|
|
151
|
+
keys: () => iterate((key) => key),
|
|
135
152
|
values,
|
|
136
153
|
entries,
|
|
137
154
|
forEach: (callback, thisArg) => {
|
|
155
|
+
read.ids();
|
|
138
156
|
for (const key of ids) callback.call(thisArg, read.get(key), key, view);
|
|
139
157
|
},
|
|
140
158
|
[Symbol.iterator]: entries
|
|
@@ -183,141 +201,36 @@ const compileProjectionDependencies = (dependencies, label) => {
|
|
|
183
201
|
});
|
|
184
202
|
};
|
|
185
203
|
//#endregion
|
|
186
|
-
//#region core/src/
|
|
187
|
-
/**
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
let
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
//#endregion
|
|
206
|
-
//#region core/src/projection/collection/change.ts
|
|
207
|
-
const transitionContains = (entries, key) => {
|
|
208
|
-
for (const entry of entries) if (entry.key === key) return true;
|
|
209
|
-
return false;
|
|
210
|
-
};
|
|
211
|
-
const transitionSet = (entries) => {
|
|
212
|
-
if (entries.length <= 4) return void 0;
|
|
213
|
-
const keys = /* @__PURE__ */ new Set();
|
|
214
|
-
for (const entry of entries) keys.add(entry.key);
|
|
215
|
-
return keys;
|
|
216
|
-
};
|
|
217
|
-
const nextExcluding = (iterator, excluded, index) => {
|
|
218
|
-
while (true) {
|
|
219
|
-
const next = iterator.next();
|
|
220
|
-
if (next.done || !(index ? index.has(next.value) : transitionContains(excluded, next.value))) return next;
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
/** Compares common-member order from known transitions without materializing common arrays. */
|
|
224
|
-
const transitionOrderChanged = (before, after, removed, added) => {
|
|
225
|
-
const removedKeys = transitionSet(removed);
|
|
226
|
-
const addedKeys = transitionSet(added);
|
|
227
|
-
const beforeIterator = before[Symbol.iterator]();
|
|
228
|
-
const afterIterator = after[Symbol.iterator]();
|
|
229
|
-
while (true) {
|
|
230
|
-
const left = nextExcluding(beforeIterator, removed, removedKeys);
|
|
231
|
-
const right = nextExcluding(afterIterator, added, addedKeys);
|
|
232
|
-
if (left.done || right.done) return left.done !== right.done;
|
|
233
|
-
if (left.value !== right.value) return true;
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
const nextPresentIn = (iterator, membership) => {
|
|
237
|
-
while (true) {
|
|
238
|
-
const next = iterator.next();
|
|
239
|
-
if (next.done || membership.has(next.value)) return next;
|
|
204
|
+
//#region core/src/projection/keyed/invalidation.ts
|
|
205
|
+
/** Shared driver/dependency invalidation policy; owns no revisions or member cache. */
|
|
206
|
+
const collectKeyedInvalidation = (driver, sources, dependencies, reset) => {
|
|
207
|
+
const changed = dependencies.driverChanged(driver);
|
|
208
|
+
const change = changed ? driver.change : void 0;
|
|
209
|
+
if (reset || changed && (!change || change.kind === "reset")) return {
|
|
210
|
+
rebuild: true,
|
|
211
|
+
keys: driver.read.ids(),
|
|
212
|
+
removed: [],
|
|
213
|
+
structural: true
|
|
214
|
+
};
|
|
215
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
216
|
+
let removed = [];
|
|
217
|
+
let structural = false;
|
|
218
|
+
if (change?.kind === "incremental") {
|
|
219
|
+
removed = change.removed;
|
|
220
|
+
for (const entry of change.added) dirty.add(entry.key);
|
|
221
|
+
for (const entry of change.updated) dirty.add(entry.key);
|
|
222
|
+
structural = Boolean(change.added.length || change.removed.length || change.order);
|
|
240
223
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
const beforeIterator = before.keys();
|
|
245
|
-
const afterIterator = after.keys();
|
|
246
|
-
while (true) {
|
|
247
|
-
const left = nextPresentIn(beforeIterator, after);
|
|
248
|
-
const right = nextPresentIn(afterIterator, before);
|
|
249
|
-
if (left.done || right.done) return left.done !== right.done;
|
|
250
|
-
if (left.value !== right.value) return true;
|
|
224
|
+
const all = dependencies.collectInvalidated(sources, dirty);
|
|
225
|
+
if (!all) {
|
|
226
|
+
for (const key of dirty) if (!driver.read.has(key)) dirty.delete(key);
|
|
251
227
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
updated: Object.freeze(updated),
|
|
259
|
-
removed: Object.freeze(removed),
|
|
260
|
-
...order ? { order: Object.freeze({
|
|
261
|
-
before: snapshotArray(order.before),
|
|
262
|
-
after: snapshotArray(order.after)
|
|
263
|
-
}) } : {}
|
|
264
|
-
});
|
|
265
|
-
};
|
|
266
|
-
/** Builds the canonical incremental CollectionChange from already-known net transitions. */
|
|
267
|
-
const createCollectionChange = (input) => {
|
|
268
|
-
let orderChanged = false;
|
|
269
|
-
if (input.beforeOrder !== input.afterOrder) if (!input.added.length && !input.removed.length) orderChanged = !sameArray(input.beforeOrder, input.afterOrder);
|
|
270
|
-
else orderChanged = transitionOrderChanged(input.beforeOrder, input.afterOrder, input.removed, input.added);
|
|
271
|
-
return sealIncrementalChange(input.added, input.updated, input.removed, orderChanged ? {
|
|
272
|
-
before: input.beforeOrder,
|
|
273
|
-
after: input.afterOrder
|
|
274
|
-
} : void 0);
|
|
275
|
-
};
|
|
276
|
-
/** Exact fallback diff between two published map-like snapshots. */
|
|
277
|
-
const diffCollection = (previous, current, isEqual = Object.is) => {
|
|
278
|
-
const added = [];
|
|
279
|
-
const updated = [];
|
|
280
|
-
const removed = [];
|
|
281
|
-
for (const [key, before] of previous) if (!current.has(key)) removed.push({
|
|
282
|
-
key,
|
|
283
|
-
before
|
|
284
|
-
});
|
|
285
|
-
else {
|
|
286
|
-
const after = current.get(key);
|
|
287
|
-
if (!isEqual(before, after)) updated.push({
|
|
288
|
-
key,
|
|
289
|
-
before,
|
|
290
|
-
after
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
for (const [key, after] of current) if (!previous.has(key)) added.push({
|
|
294
|
-
key,
|
|
295
|
-
after
|
|
296
|
-
});
|
|
297
|
-
return sealIncrementalChange(added, updated, removed, mapOrderChanged(previous, current) ? {
|
|
298
|
-
before: Object.freeze([...previous.keys()]),
|
|
299
|
-
after: Object.freeze([...current.keys()])
|
|
300
|
-
} : void 0);
|
|
301
|
-
};
|
|
302
|
-
const changedKeys = function* (change) {
|
|
303
|
-
if (change.kind !== "incremental") throw new TypeError("collectionChange.keys requires an incremental CollectionChange.");
|
|
304
|
-
for (const entry of change.added) yield entry.key;
|
|
305
|
-
for (const entry of change.updated) yield entry.key;
|
|
306
|
-
for (const entry of change.removed) yield entry.key;
|
|
307
|
-
};
|
|
308
|
-
const collectionChange = Object.freeze({ keys: changedKeys });
|
|
309
|
-
const collectionHasStructuralChange = (change) => change.kind === "reset" || Boolean(change.added.length || change.removed.length || change.order);
|
|
310
|
-
const collectionChangeTouchesKey = (change, key) => {
|
|
311
|
-
for (const entry of change.added) if (entry.key === key) return true;
|
|
312
|
-
for (const entry of change.updated) if (entry.key === key) return true;
|
|
313
|
-
for (const entry of change.removed) if (entry.key === key) return true;
|
|
314
|
-
return false;
|
|
315
|
-
};
|
|
316
|
-
const collectionChangeIntersects = (change, keys) => {
|
|
317
|
-
for (const entry of change.added) if (keys.has(entry.key)) return true;
|
|
318
|
-
for (const entry of change.updated) if (keys.has(entry.key)) return true;
|
|
319
|
-
for (const entry of change.removed) if (keys.has(entry.key)) return true;
|
|
320
|
-
return false;
|
|
228
|
+
return {
|
|
229
|
+
rebuild: false,
|
|
230
|
+
keys: all ? driver.read.ids() : dirty,
|
|
231
|
+
removed,
|
|
232
|
+
structural
|
|
233
|
+
};
|
|
321
234
|
};
|
|
322
235
|
//#endregion
|
|
323
236
|
//#region core/src/projection/contract.ts
|
|
@@ -348,10 +261,11 @@ const createScheduler = (onError) => {
|
|
|
348
261
|
const sources = /* @__PURE__ */ new Set();
|
|
349
262
|
const processors = /* @__PURE__ */ new Set();
|
|
350
263
|
const pendingSources = /* @__PURE__ */ new Set();
|
|
351
|
-
const
|
|
264
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
352
265
|
const queued = /* @__PURE__ */ new Set();
|
|
353
266
|
const completed = /* @__PURE__ */ new Set();
|
|
354
267
|
const emissions = /* @__PURE__ */ new Set();
|
|
268
|
+
const forced = /* @__PURE__ */ new Set();
|
|
355
269
|
const errors = [];
|
|
356
270
|
let reportingFailures = [];
|
|
357
271
|
const guards = /* @__PURE__ */ new Set();
|
|
@@ -360,38 +274,22 @@ const createScheduler = (onError) => {
|
|
|
360
274
|
};
|
|
361
275
|
const assertIdle = () => {
|
|
362
276
|
assertActive();
|
|
363
|
-
if (phase !== "idle") throw new Error("Projection cannot be re-entered during processing or notification.");
|
|
277
|
+
if (phase !== "idle") throw new Error("Projection cannot be re-entered during input callbacks, processing or notification.");
|
|
364
278
|
};
|
|
365
279
|
const lock = (value) => guards.forEach((guard) => guard(value));
|
|
280
|
+
const markDirty = (producer) => {
|
|
281
|
+
const pending = [producer];
|
|
282
|
+
for (let index = 0; index < pending.length; index++) {
|
|
283
|
+
const current = pending[index];
|
|
284
|
+
if (current.disposed || dirty.has(current)) continue;
|
|
285
|
+
dirty.add(current);
|
|
286
|
+
for (const output of current.outputs) output.forEachConsumer((consumer) => pending.push(consumer));
|
|
287
|
+
}
|
|
288
|
+
};
|
|
366
289
|
const enqueue = (processor) => {
|
|
367
290
|
if (processor.disposed || queued.has(processor)) return;
|
|
368
291
|
queued.add(processor);
|
|
369
292
|
profile.projection("scheduledNodes");
|
|
370
|
-
let index = heap.length;
|
|
371
|
-
heap.push(processor);
|
|
372
|
-
while (index > 0) {
|
|
373
|
-
const parent = index - 1 >> 1;
|
|
374
|
-
if (heap[parent].order < processor.order) break;
|
|
375
|
-
heap[index] = heap[parent];
|
|
376
|
-
index = parent;
|
|
377
|
-
}
|
|
378
|
-
heap[index] = processor;
|
|
379
|
-
};
|
|
380
|
-
const pop = () => {
|
|
381
|
-
const first = heap[0];
|
|
382
|
-
const last = heap.pop();
|
|
383
|
-
if (heap.length) {
|
|
384
|
-
let index = 0;
|
|
385
|
-
while (index * 2 + 1 < heap.length) {
|
|
386
|
-
let child = index * 2 + 1;
|
|
387
|
-
if (child + 1 < heap.length && heap[child + 1].order < heap[child].order) child++;
|
|
388
|
-
if (last.order < heap[child].order) break;
|
|
389
|
-
heap[index] = heap[child];
|
|
390
|
-
index = child;
|
|
391
|
-
}
|
|
392
|
-
heap[index] = last;
|
|
393
|
-
}
|
|
394
|
-
return first;
|
|
395
293
|
};
|
|
396
294
|
const errorFor = (producer, cause, kind = producer.kind === "source" ? "source" : "processor") => new ProjectionError(kind, cause);
|
|
397
295
|
const capture = (source) => {
|
|
@@ -402,6 +300,7 @@ const createScheduler = (onError) => {
|
|
|
402
300
|
throw source.fault;
|
|
403
301
|
}
|
|
404
302
|
pendingSources.add(source);
|
|
303
|
+
markDirty(source);
|
|
405
304
|
profile.projection("sourceEvents");
|
|
406
305
|
if (source.fault) errors.push(source.fault);
|
|
407
306
|
};
|
|
@@ -412,88 +311,137 @@ const createScheduler = (onError) => {
|
|
|
412
311
|
if (cause !== void 0) return cause;
|
|
413
312
|
}
|
|
414
313
|
};
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
314
|
+
const propagate = (output, force = false) => {
|
|
315
|
+
emissions.add(output);
|
|
316
|
+
if (force) forced.add(output);
|
|
317
|
+
output.forEachConsumer(enqueue);
|
|
318
|
+
};
|
|
319
|
+
const advanceSource = (source, cause) => {
|
|
320
|
+
pendingSources.delete(source);
|
|
321
|
+
const wasFaulted = source.fault !== void 0;
|
|
322
|
+
let changed = false;
|
|
323
|
+
let force = false;
|
|
324
|
+
if (!source.fault || source.recovering()) try {
|
|
325
|
+
source.fault = void 0;
|
|
326
|
+
const prepared = source.prepare(cause);
|
|
327
|
+
changed = prepared.changed;
|
|
328
|
+
force = prepared.force;
|
|
329
|
+
source.fault = void 0;
|
|
330
|
+
} catch (failure) {
|
|
331
|
+
source.fault = errorFor(source, failure, "source");
|
|
332
|
+
errors.push(source.fault);
|
|
333
|
+
}
|
|
334
|
+
if (changed || force) source.publish();
|
|
335
|
+
if (source.fault || changed || force || wasFaulted) propagate(source.outputs[0], force);
|
|
336
|
+
source.clear();
|
|
337
|
+
};
|
|
338
|
+
const advanceProcessor = (processor, cause) => {
|
|
339
|
+
queued.delete(processor);
|
|
340
|
+
const wasFaulted = processor.fault !== void 0;
|
|
341
|
+
profile.projection("processedNodes");
|
|
342
|
+
let changedOutputs = [];
|
|
343
|
+
const blocked = processor.dependencies.find((output) => output.owner.fault || output.owner.disposed);
|
|
344
|
+
if (blocked) processor.fault = errorFor(processor, blocked.owner.fault ?? new ProjectionDisposedError(), "blocked");
|
|
345
|
+
else {
|
|
346
|
+
const reset = wasFaulted || processor.dependencies.some((output) => output.reset(processor));
|
|
347
|
+
try {
|
|
348
|
+
changedOutputs = processor.evaluate(reset, cause, wasFaulted);
|
|
349
|
+
const failure = processor.dependencies.find((output) => output.owner.fault)?.owner.fault;
|
|
350
|
+
if (failure) throw failure;
|
|
351
|
+
processor.fault = void 0;
|
|
352
|
+
} catch (failure) {
|
|
353
|
+
const error = errorFor(processor, failure);
|
|
354
|
+
processor.fault = error;
|
|
355
|
+
if (!reset) try {
|
|
356
|
+
changedOutputs = processor.evaluate(true, cause, true);
|
|
357
|
+
const dependencyFailure = processor.dependencies.find((output) => output.owner.fault)?.owner.fault;
|
|
358
|
+
if (dependencyFailure) throw dependencyFailure;
|
|
359
|
+
processor.fault = void 0;
|
|
360
|
+
errors.push(error);
|
|
361
|
+
} catch (rebuildFailure) {
|
|
362
|
+
processor.fault = errorFor(processor, rebuildFailure);
|
|
363
|
+
errors.push(processor.fault);
|
|
441
364
|
}
|
|
365
|
+
else errors.push(error);
|
|
442
366
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
367
|
+
}
|
|
368
|
+
completed.add(processor);
|
|
369
|
+
if (processor.fault) processor.outputs.forEach((output) => propagate(output));
|
|
370
|
+
else {
|
|
371
|
+
if (changedOutputs.length) processor.publish();
|
|
372
|
+
(wasFaulted && !processor.fault ? processor.outputs : changedOutputs).forEach((output) => propagate(output));
|
|
373
|
+
}
|
|
374
|
+
if (processor.fault || changedOutputs.length) profile.projection("publishedNodes");
|
|
375
|
+
};
|
|
376
|
+
const advance = (target, cause) => {
|
|
377
|
+
const stack = [{
|
|
378
|
+
producer: target,
|
|
379
|
+
dependency: 0
|
|
380
|
+
}];
|
|
381
|
+
while (stack.length) {
|
|
382
|
+
const frame = stack[stack.length - 1];
|
|
383
|
+
const { producer } = frame;
|
|
384
|
+
if (producer.disposed || !dirty.has(producer)) {
|
|
385
|
+
dirty.delete(producer);
|
|
386
|
+
stack.pop();
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (producer.kind === "processor") {
|
|
390
|
+
let dependency;
|
|
391
|
+
while (frame.dependency < producer.dependencies.length) {
|
|
392
|
+
const candidate = producer.dependencies[frame.dependency++].owner;
|
|
393
|
+
if (dirty.has(candidate)) {
|
|
394
|
+
dependency = candidate;
|
|
395
|
+
break;
|
|
472
396
|
}
|
|
473
397
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
else {
|
|
481
|
-
if (changedOutputs.length) {
|
|
482
|
-
processor.publish();
|
|
483
|
-
changedOutputs.forEach((output) => emissions.add(output));
|
|
484
|
-
}
|
|
485
|
-
(recovered ? processor.outputs : changedOutputs).forEach((output) => output.forEachConsumer(enqueue));
|
|
398
|
+
if (dependency) {
|
|
399
|
+
stack.push({
|
|
400
|
+
producer: dependency,
|
|
401
|
+
dependency: 0
|
|
402
|
+
});
|
|
403
|
+
continue;
|
|
486
404
|
}
|
|
487
|
-
if (processor.fault || changedOutputs.length) profile.projection("publishedNodes");
|
|
488
405
|
}
|
|
406
|
+
stack.pop();
|
|
407
|
+
dirty.delete(producer);
|
|
408
|
+
if (producer.kind === "source") advanceSource(producer, cause);
|
|
409
|
+
else if (queued.has(producer)) if (producer.fault || producer.dependencies.some((output) => output.owner.fault || output.pending(producer))) advanceProcessor(producer, cause);
|
|
410
|
+
else {
|
|
411
|
+
queued.delete(producer);
|
|
412
|
+
producer.dependencies.forEach((output) => output.acknowledge(producer));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
const compute = (run) => {
|
|
417
|
+
phase = "compute";
|
|
418
|
+
lock(true);
|
|
419
|
+
try {
|
|
420
|
+
return run();
|
|
489
421
|
} finally {
|
|
490
422
|
lock(false);
|
|
491
423
|
phase = "idle";
|
|
492
424
|
}
|
|
493
425
|
};
|
|
426
|
+
const settle = () => {
|
|
427
|
+
if (disposed || depth || phase !== "idle") return;
|
|
428
|
+
const cause = settleCause();
|
|
429
|
+
compute(() => {
|
|
430
|
+
for (const producer of dirty) advance(producer, cause);
|
|
431
|
+
});
|
|
432
|
+
};
|
|
433
|
+
const ensureCurrent = (output) => {
|
|
434
|
+
assertActive();
|
|
435
|
+
if (phase === "notify") return;
|
|
436
|
+
assertIdle();
|
|
437
|
+
if (dirty.has(output.owner)) {
|
|
438
|
+
const cause = settleCause();
|
|
439
|
+
compute(() => advance(output.owner, cause));
|
|
440
|
+
}
|
|
441
|
+
};
|
|
494
442
|
const flush = () => {
|
|
495
443
|
if (disposed || depth) return [];
|
|
496
|
-
if (
|
|
444
|
+
if (emissions.size || completed.size) profile.projection("flushes");
|
|
497
445
|
phase = "notify";
|
|
498
446
|
lock(true);
|
|
499
447
|
const reportFailures = [];
|
|
@@ -504,7 +452,7 @@ const createScheduler = (onError) => {
|
|
|
504
452
|
} catch (cause) {
|
|
505
453
|
errors.push(errorFor(output.owner, cause, "listener"));
|
|
506
454
|
}
|
|
507
|
-
}));
|
|
455
|
+
}, forced.has(output) || Boolean(output.owner.fault)));
|
|
508
456
|
for (const error of errors.slice()) try {
|
|
509
457
|
onError(error);
|
|
510
458
|
} catch (cause) {
|
|
@@ -518,12 +466,13 @@ const createScheduler = (onError) => {
|
|
|
518
466
|
error
|
|
519
467
|
}))]);
|
|
520
468
|
} finally {
|
|
521
|
-
|
|
469
|
+
emissions.forEach((output) => output.finish());
|
|
522
470
|
completed.forEach((processor) => processor.clear());
|
|
523
471
|
pendingSources.clear();
|
|
524
472
|
queued.clear();
|
|
525
473
|
completed.clear();
|
|
526
474
|
emissions.clear();
|
|
475
|
+
forced.clear();
|
|
527
476
|
errors.length = 0;
|
|
528
477
|
reportingFailures = reportFailures;
|
|
529
478
|
lock(false);
|
|
@@ -569,6 +518,7 @@ const createScheduler = (onError) => {
|
|
|
569
518
|
assertIdle();
|
|
570
519
|
if (producer.outputs.some((output) => output.hasConsumers())) throw new Error("Projection producer is still used by another processor.");
|
|
571
520
|
producer.disposed = true;
|
|
521
|
+
dirty.delete(producer);
|
|
572
522
|
if (producer.kind === "processor") {
|
|
573
523
|
producer.dependencies.forEach((output) => output.detachConsumer(producer));
|
|
574
524
|
processors.delete(producer);
|
|
@@ -597,6 +547,7 @@ const createScheduler = (onError) => {
|
|
|
597
547
|
assertIdle,
|
|
598
548
|
capture,
|
|
599
549
|
settle,
|
|
550
|
+
ensureCurrent,
|
|
600
551
|
flush,
|
|
601
552
|
run,
|
|
602
553
|
registerGuard(guard) {
|
|
@@ -622,10 +573,10 @@ const createScheduler = (onError) => {
|
|
|
622
573
|
processor.dependencies.forEach((output) => output.attachConsumer(processor));
|
|
623
574
|
},
|
|
624
575
|
releaseProducer,
|
|
625
|
-
|
|
626
|
-
|
|
576
|
+
nextId: () => sequence++,
|
|
577
|
+
acceptInput(callback) {
|
|
627
578
|
assertIdle();
|
|
628
|
-
phase = "
|
|
579
|
+
phase = "input";
|
|
629
580
|
lock(true);
|
|
630
581
|
try {
|
|
631
582
|
return callback();
|
|
@@ -634,6 +585,10 @@ const createScheduler = (onError) => {
|
|
|
634
585
|
phase = "idle";
|
|
635
586
|
}
|
|
636
587
|
},
|
|
588
|
+
initialize(callback) {
|
|
589
|
+
assertIdle();
|
|
590
|
+
return compute(callback);
|
|
591
|
+
},
|
|
637
592
|
batch,
|
|
638
593
|
batchContext: () => activeBatch,
|
|
639
594
|
dispose() {
|
|
@@ -660,10 +615,11 @@ const createScheduler = (onError) => {
|
|
|
660
615
|
processors.clear();
|
|
661
616
|
sources.clear();
|
|
662
617
|
pendingSources.clear();
|
|
663
|
-
|
|
618
|
+
dirty.clear();
|
|
664
619
|
queued.clear();
|
|
665
620
|
completed.clear();
|
|
666
621
|
emissions.clear();
|
|
622
|
+
forced.clear();
|
|
667
623
|
guards.clear();
|
|
668
624
|
errors.length = 0;
|
|
669
625
|
reportingFailures = [];
|
|
@@ -678,6 +634,26 @@ const assertScope = (active) => {
|
|
|
678
634
|
if (!active()) throw new Error("Projection reader or writer is no longer active.");
|
|
679
635
|
};
|
|
680
636
|
//#endregion
|
|
637
|
+
//#region core/src/value/array.ts
|
|
638
|
+
/** Allocation-free strict equality for ordered readonly arrays. */
|
|
639
|
+
const sameArray = (left, right) => {
|
|
640
|
+
if (left === right) return true;
|
|
641
|
+
if (left.length !== right.length) return false;
|
|
642
|
+
for (let index = 0; index < left.length; index++) if (left[index] !== right[index]) return false;
|
|
643
|
+
return true;
|
|
644
|
+
};
|
|
645
|
+
/** Owns an immutable shallow snapshot while reusing arrays that are already frozen. */
|
|
646
|
+
const snapshotArray = (values) => Object.isFrozen(values) ? values : Object.freeze([...values]);
|
|
647
|
+
/** Compares an iterable with an ordered array without materializing the iterable. */
|
|
648
|
+
const iterableMatchesArray = (values, expected) => {
|
|
649
|
+
let index = 0;
|
|
650
|
+
for (const value of values) {
|
|
651
|
+
if (index >= expected.length || value !== expected[index]) return false;
|
|
652
|
+
index++;
|
|
653
|
+
}
|
|
654
|
+
return index === expected.length;
|
|
655
|
+
};
|
|
656
|
+
//#endregion
|
|
681
657
|
//#region core/src/projection/keyed/relation.ts
|
|
682
658
|
/** Runtime-local domain-neutral many-to-many keyed relation. */
|
|
683
659
|
const createKeyRelation = () => {
|
|
@@ -906,7 +882,6 @@ const createKeyedDependencyRuntime = (compiled) => {
|
|
|
906
882
|
for (const state of states) {
|
|
907
883
|
const source = sources[state.sourceIndex];
|
|
908
884
|
if (state.revision === source.revision) continue;
|
|
909
|
-
state.entry;
|
|
910
885
|
if (state.kind === "global") {
|
|
911
886
|
all = true;
|
|
912
887
|
continue;
|
|
@@ -948,6 +923,80 @@ const createKeyedDependencyRuntime = (compiled) => {
|
|
|
948
923
|
};
|
|
949
924
|
};
|
|
950
925
|
//#endregion
|
|
926
|
+
//#region core/src/projection/collection/change.ts
|
|
927
|
+
const transitionContains = (entries, key) => {
|
|
928
|
+
for (const entry of entries) if (entry.key === key) return true;
|
|
929
|
+
return false;
|
|
930
|
+
};
|
|
931
|
+
const transitionSet = (entries) => {
|
|
932
|
+
if (entries.length <= 4) return void 0;
|
|
933
|
+
const keys = /* @__PURE__ */ new Set();
|
|
934
|
+
for (const entry of entries) keys.add(entry.key);
|
|
935
|
+
return keys;
|
|
936
|
+
};
|
|
937
|
+
const nextExcluding = (iterator, excluded, index) => {
|
|
938
|
+
while (true) {
|
|
939
|
+
const next = iterator.next();
|
|
940
|
+
if (next.done || !(index ? index.has(next.value) : transitionContains(excluded, next.value))) return next;
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
/** Compares common-member order from known transitions without materializing common arrays. */
|
|
944
|
+
const transitionOrderChanged = (before, after, removed, added) => {
|
|
945
|
+
const removedKeys = transitionSet(removed);
|
|
946
|
+
const addedKeys = transitionSet(added);
|
|
947
|
+
const beforeIterator = before[Symbol.iterator]();
|
|
948
|
+
const afterIterator = after[Symbol.iterator]();
|
|
949
|
+
while (true) {
|
|
950
|
+
const left = nextExcluding(beforeIterator, removed, removedKeys);
|
|
951
|
+
const right = nextExcluding(afterIterator, added, addedKeys);
|
|
952
|
+
if (left.done || right.done) return left.done !== right.done;
|
|
953
|
+
if (left.value !== right.value) return true;
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
const sealIncrementalChange = (added, updated, removed, order) => {
|
|
957
|
+
if (!added.length && !updated.length && !removed.length && !order) return void 0;
|
|
958
|
+
return Object.freeze({
|
|
959
|
+
kind: "incremental",
|
|
960
|
+
added: Object.freeze(added),
|
|
961
|
+
updated: Object.freeze(updated),
|
|
962
|
+
removed: Object.freeze(removed),
|
|
963
|
+
...order ? { order: Object.freeze({
|
|
964
|
+
before: snapshotArray(order.before),
|
|
965
|
+
after: snapshotArray(order.after)
|
|
966
|
+
}) } : {}
|
|
967
|
+
});
|
|
968
|
+
};
|
|
969
|
+
/** Builds the canonical incremental CollectionChange from already-known net transitions. */
|
|
970
|
+
const createCollectionChange = (input) => {
|
|
971
|
+
let orderChanged = false;
|
|
972
|
+
if (input.beforeOrder !== input.afterOrder) if (!input.added.length && !input.removed.length) orderChanged = !sameArray(input.beforeOrder, input.afterOrder);
|
|
973
|
+
else orderChanged = transitionOrderChanged(input.beforeOrder, input.afterOrder, input.removed, input.added);
|
|
974
|
+
return sealIncrementalChange(input.added, input.updated, input.removed, orderChanged ? {
|
|
975
|
+
before: input.beforeOrder,
|
|
976
|
+
after: input.afterOrder
|
|
977
|
+
} : void 0);
|
|
978
|
+
};
|
|
979
|
+
const changedKeys = function* (change) {
|
|
980
|
+
if (change.kind !== "incremental") throw new TypeError("collectionChange.keys requires an incremental CollectionChange.");
|
|
981
|
+
for (const entry of change.added) yield entry.key;
|
|
982
|
+
for (const entry of change.updated) yield entry.key;
|
|
983
|
+
for (const entry of change.removed) yield entry.key;
|
|
984
|
+
};
|
|
985
|
+
const collectionChange = Object.freeze({ keys: changedKeys });
|
|
986
|
+
const collectionHasStructuralChange = (change) => change.kind === "reset" || Boolean(change.added.length || change.removed.length || change.order);
|
|
987
|
+
const collectionChangeTouchesKey = (change, key) => {
|
|
988
|
+
for (const entry of change.added) if (entry.key === key) return true;
|
|
989
|
+
for (const entry of change.updated) if (entry.key === key) return true;
|
|
990
|
+
for (const entry of change.removed) if (entry.key === key) return true;
|
|
991
|
+
return false;
|
|
992
|
+
};
|
|
993
|
+
const collectionChangeIntersects = (change, keys) => {
|
|
994
|
+
for (const entry of change.added) if (keys.has(entry.key)) return true;
|
|
995
|
+
for (const entry of change.updated) if (keys.has(entry.key)) return true;
|
|
996
|
+
for (const entry of change.removed) if (keys.has(entry.key)) return true;
|
|
997
|
+
return false;
|
|
998
|
+
};
|
|
999
|
+
//#endregion
|
|
951
1000
|
//#region core/src/projection/keyed/transform.ts
|
|
952
1001
|
const keyedAbsent = Symbol("keyed-transform-absent");
|
|
953
1002
|
/** Shared driver-keyed lifecycle for pure and retained keyed transforms. */
|
|
@@ -972,7 +1021,8 @@ const createKeyedTransform = (definition) => {
|
|
|
972
1021
|
const driver = evaluation.sources[0];
|
|
973
1022
|
const output = evaluation.outputs[0];
|
|
974
1023
|
if (driver.kind !== "collection" || output.kind !== "collection") throw new Error(`${definition.name} requires collection input and output.`);
|
|
975
|
-
|
|
1024
|
+
const invalidation = collectKeyedInvalidation(driver, evaluation.sources, dependencies, evaluation.reset);
|
|
1025
|
+
dependencies.prepare(evaluation.sources, invalidation.rebuild);
|
|
976
1026
|
const stateFor = (key, value) => {
|
|
977
1027
|
if (states.has(key)) return states.get(key);
|
|
978
1028
|
const state = definition.state ? definition.state(value, key) : void 0;
|
|
@@ -997,49 +1047,29 @@ const createKeyedTransform = (definition) => {
|
|
|
997
1047
|
else output.output.set(key, next);
|
|
998
1048
|
return beforePresent !== output.next.has(key);
|
|
999
1049
|
};
|
|
1000
|
-
if (
|
|
1050
|
+
if (invalidation.rebuild) {
|
|
1001
1051
|
dependencies.clearBindings();
|
|
1002
1052
|
const ids = driver.read.ids();
|
|
1003
1053
|
for (const key of states.keys()) if (!driver.read.has(key)) states.delete(key);
|
|
1054
|
+
if (!evaluation.reset) {
|
|
1055
|
+
for (const key of output.previous.ids()) if (!driver.read.has(key)) output.output.remove(key);
|
|
1056
|
+
}
|
|
1004
1057
|
for (const key of ids) project(key);
|
|
1005
1058
|
output.output.order(ids.filter((key) => output.next.has(key)));
|
|
1006
1059
|
dependencies.remember(evaluation.sources);
|
|
1007
1060
|
return;
|
|
1008
1061
|
}
|
|
1009
|
-
const dirty = /* @__PURE__ */ new Set();
|
|
1010
|
-
let all = false;
|
|
1011
|
-
let updateOrder = false;
|
|
1012
1062
|
let membershipChanged = false;
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
updateOrder = true;
|
|
1018
|
-
for (const key of output.previous.ids()) {
|
|
1019
|
-
if (driver.read.has(key)) continue;
|
|
1020
|
-
output.output.remove(key);
|
|
1021
|
-
dropState(key);
|
|
1022
|
-
membershipChanged = true;
|
|
1023
|
-
}
|
|
1024
|
-
} else {
|
|
1025
|
-
for (const entry of change.removed) {
|
|
1026
|
-
membershipChanged ||= output.previous.has(entry.key);
|
|
1027
|
-
output.output.remove(entry.key);
|
|
1028
|
-
dropState(entry.key);
|
|
1029
|
-
dirty.delete(entry.key);
|
|
1030
|
-
}
|
|
1031
|
-
for (const entry of change.added) dirty.add(entry.key);
|
|
1032
|
-
for (const entry of change.updated) dirty.add(entry.key);
|
|
1033
|
-
updateOrder = Boolean(change.order);
|
|
1034
|
-
}
|
|
1063
|
+
for (const entry of invalidation.removed) {
|
|
1064
|
+
membershipChanged ||= output.previous.has(entry.key);
|
|
1065
|
+
output.output.remove(entry.key);
|
|
1066
|
+
dropState(entry.key);
|
|
1035
1067
|
}
|
|
1036
|
-
|
|
1037
|
-
if (all) for (const key of driver.read.ids()) dirty.add(key);
|
|
1038
|
-
for (const key of dirty) {
|
|
1068
|
+
for (const key of invalidation.keys) {
|
|
1039
1069
|
const changedMembership = project(key);
|
|
1040
1070
|
membershipChanged ||= changedMembership;
|
|
1041
1071
|
}
|
|
1042
|
-
if (
|
|
1072
|
+
if (invalidation.structural || membershipChanged) output.output.order(driver.read.ids().filter((key) => output.next.has(key)));
|
|
1043
1073
|
dependencies.remember(evaluation.sources);
|
|
1044
1074
|
},
|
|
1045
1075
|
release: () => {
|
|
@@ -1053,6 +1083,6 @@ const createKeyedTransform = (definition) => {
|
|
|
1053
1083
|
return projection;
|
|
1054
1084
|
};
|
|
1055
1085
|
//#endregion
|
|
1056
|
-
export { ownProjections as A, snapshotDependencyValue as C, defineProcessor as D, snapshotCollectionView as E, projectionRef as M, profile as N, defineSource as O, compileProjectionDependencies as S, mapRead as T,
|
|
1086
|
+
export { ownProjections as A, snapshotDependencyValue as C, defineProcessor as D, snapshotCollectionView as E, projectionRef as M, profile as N, defineSource as O, compileProjectionDependencies as S, mapRead as T, assertSynchronous as _, collectionChangeTouchesKey as a, ProjectionError as b, assertKeyedProjection as c, outputKind as d, createKeyRelation as f, assertScope as g, snapshotArray as h, collectionChangeIntersects as i, producerOf as j, isProjection as k, compileKeyedDependencies as l, sameArray as m, keyedAbsent as n, collectionHasStructuralChange as o, iterableMatchesArray as p, collectionChange as r, createCollectionChange as s, createKeyedTransform as t, createKeyedDependencyRuntime as u, createScheduler as v, collectionView as w, collectKeyedInvalidation as x, ProjectionDisposedError as y };
|
|
1057
1087
|
|
|
1058
|
-
//# sourceMappingURL=transform-
|
|
1088
|
+
//# sourceMappingURL=transform-RghG_Qxs.js.map
|