footprintjs 9.28.0 → 9.30.0
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/CLAUDE.md +8 -6
- package/dist/esm/lib/engine/handlers/SubflowExecutor.js +41 -2
- package/dist/esm/lib/memory/EventLog.d.ts +6 -0
- package/dist/esm/lib/memory/EventLog.js +10 -4
- package/dist/esm/lib/memory/SharedMemory.d.ts +18 -4
- package/dist/esm/lib/memory/SharedMemory.js +42 -11
- package/dist/esm/lib/memory/StageContext.d.ts +45 -18
- package/dist/esm/lib/memory/StageContext.js +82 -27
- package/dist/esm/lib/memory/TransactionBuffer.d.ts +256 -86
- package/dist/esm/lib/memory/TransactionBuffer.js +497 -273
- package/dist/esm/lib/memory/admission.d.ts +81 -0
- package/dist/esm/lib/memory/admission.js +152 -0
- package/dist/esm/lib/memory/borrowedMutation.d.ts +9 -0
- package/dist/esm/lib/memory/borrowedMutation.js +18 -1
- package/dist/esm/lib/memory/deltaEncoding.d.ts +66 -0
- package/dist/esm/lib/memory/deltaEncoding.js +122 -0
- package/dist/esm/lib/memory/pathOps.d.ts +63 -0
- package/dist/esm/lib/memory/pathOps.js +150 -1
- package/dist/esm/lib/memory/utils.d.ts +47 -16
- package/dist/esm/lib/memory/utils.js +119 -24
- package/dist/esm/lib/runner/FlowChartExecutor.d.ts +7 -5
- package/dist/esm/lib/runner/FlowChartExecutor.js +8 -6
- package/dist/lib/engine/handlers/SubflowExecutor.js +41 -2
- package/dist/lib/memory/EventLog.js +9 -3
- package/dist/lib/memory/SharedMemory.js +40 -9
- package/dist/lib/memory/StageContext.js +81 -26
- package/dist/lib/memory/TransactionBuffer.js +495 -271
- package/dist/lib/memory/admission.js +158 -0
- package/dist/lib/memory/borrowedMutation.js +20 -2
- package/dist/lib/memory/deltaEncoding.js +128 -0
- package/dist/lib/memory/pathOps.js +159 -2
- package/dist/lib/memory/utils.js +122 -25
- package/dist/lib/runner/FlowChartExecutor.js +8 -6
- package/dist/types/lib/memory/EventLog.d.ts +6 -0
- package/dist/types/lib/memory/SharedMemory.d.ts +18 -4
- package/dist/types/lib/memory/StageContext.d.ts +45 -18
- package/dist/types/lib/memory/TransactionBuffer.d.ts +256 -86
- package/dist/types/lib/memory/admission.d.ts +81 -0
- package/dist/types/lib/memory/borrowedMutation.d.ts +9 -0
- package/dist/types/lib/memory/deltaEncoding.d.ts +66 -0
- package/dist/types/lib/memory/pathOps.d.ts +63 -0
- package/dist/types/lib/memory/utils.d.ts +47 -16
- package/dist/types/lib/runner/FlowChartExecutor.d.ts +7 -5
- package/package.json +2 -1
|
@@ -5,15 +5,31 @@
|
|
|
5
5
|
* - Each run gets its own namespace (runs/{id}/)
|
|
6
6
|
* - Default values can be initialised and preserved
|
|
7
7
|
* - Accepts commit bundles from TransactionBuffer
|
|
8
|
+
*
|
|
9
|
+
* COPY-ON-WRITE (9.29.0 — docs/design/2026-10-copy-on-write-commit.md):
|
|
10
|
+
* `context` is a GENERATION, and a generation is never edited. Every write —
|
|
11
|
+
* `applyPatch` (a stage commit), `setValue`, `updateValue` — builds the next
|
|
12
|
+
* generation by copying the root and the containers on each written path,
|
|
13
|
+
* shares every other subtree with the generation before it, and swaps. So a
|
|
14
|
+
* generation a stage captured (its first-touch view, its transaction
|
|
15
|
+
* buffer's diff base) stays exactly what it was, and a write costs O(what it
|
|
16
|
+
* wrote), not O(state).
|
|
8
17
|
*/
|
|
9
|
-
import { mergeContextWins } from './pathOps.js';
|
|
10
|
-
import {
|
|
18
|
+
import { mergeContextWins, ownedRootOf, ownSpine } from './pathOps.js';
|
|
19
|
+
import { getNestedValue, getRunAndGlobalPaths, nextGeneration, setNestedValue, updateNestedValue } from './utils.js';
|
|
11
20
|
export class SharedMemory {
|
|
12
21
|
context = {};
|
|
13
22
|
_defaultValues;
|
|
14
23
|
constructor(defaultValues, initialContext) {
|
|
15
24
|
this._defaultValues = defaultValues;
|
|
16
|
-
|
|
25
|
+
const seed = mergeContextWins(initialContext || {}, defaultValues || {});
|
|
26
|
+
// Detached ONCE, here: `mergeContextWins` copies only the top level, so
|
|
27
|
+
// the seed's nested values are the caller's own objects. Before 9.29.0
|
|
28
|
+
// the first commit's whole-state clone detached them (and until then a
|
|
29
|
+
// caller mutating its `initialContext` changed live state with no row);
|
|
30
|
+
// nothing re-clones the state any more, so the seed is detached at
|
|
31
|
+
// construction — one clone per runtime, not one per commit.
|
|
32
|
+
this.context = Object.keys(seed).length > 0 ? structuredClone(seed) : seed;
|
|
17
33
|
}
|
|
18
34
|
/** Gets a clone of the default values. */
|
|
19
35
|
getDefaultValues() {
|
|
@@ -23,13 +39,28 @@ export class SharedMemory {
|
|
|
23
39
|
getRuns() {
|
|
24
40
|
return this.context.runs;
|
|
25
41
|
}
|
|
26
|
-
/** Updates a value using merge semantics. */
|
|
42
|
+
/** Updates a value using merge semantics, as a new generation (path copy + swap). */
|
|
27
43
|
updateValue(runId, path, key, value) {
|
|
28
|
-
|
|
44
|
+
const next = this.ownedPathTo(runId, path, key);
|
|
45
|
+
updateNestedValue(next, runId, path, key, value, this.getDefaultValues());
|
|
46
|
+
this.context = next;
|
|
29
47
|
}
|
|
30
|
-
/** Sets a value using overwrite semantics. */
|
|
48
|
+
/** Sets a value using overwrite semantics, as a new generation (path copy + swap). */
|
|
31
49
|
setValue(runId, path, key, value) {
|
|
32
|
-
|
|
50
|
+
const next = this.ownedPathTo(runId, path, key);
|
|
51
|
+
setNestedValue(next, runId, path, key, value, this.getDefaultValues());
|
|
52
|
+
this.context = next;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A copy of the current generation's root whose containers on the way to
|
|
56
|
+
* `key` are owned — the in-place helpers above then edit only copies.
|
|
57
|
+
*/
|
|
58
|
+
ownedPathTo(runId, path, key) {
|
|
59
|
+
const owned = new WeakSet();
|
|
60
|
+
const root = ownedRootOf(this.context, owned);
|
|
61
|
+
const { runPath, globalPath } = getRunAndGlobalPaths(runId, path);
|
|
62
|
+
ownSpine(root, [...(runPath || globalPath), key], owned);
|
|
63
|
+
return root;
|
|
33
64
|
}
|
|
34
65
|
/**
|
|
35
66
|
* Reads a value from the store.
|
|
@@ -40,13 +71,13 @@ export class SharedMemory {
|
|
|
40
71
|
const value = runPath ? getNestedValue(this.context, runPath, key) : undefined;
|
|
41
72
|
return typeof value !== 'undefined' ? value : getNestedValue(this.context, globalPath, key);
|
|
42
73
|
}
|
|
43
|
-
/** Gets the entire state as a JSON object. */
|
|
74
|
+
/** Gets the entire state as a JSON object — the current generation, by reference. Never mutate it. */
|
|
44
75
|
getState() {
|
|
45
76
|
return this.context;
|
|
46
77
|
}
|
|
47
|
-
/** Applies a commit bundle from TransactionBuffer. */
|
|
78
|
+
/** Applies a commit bundle from TransactionBuffer: builds the next generation and swaps it in. */
|
|
48
79
|
applyPatch(overwrite, updates, trace) {
|
|
49
|
-
this.context =
|
|
80
|
+
this.context = nextGeneration(this.context, updates, overwrite, trace);
|
|
50
81
|
}
|
|
51
82
|
}
|
|
52
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
83
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2hhcmVkTWVtb3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9tZW1vcnkvU2hhcmVkTWVtb3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7O0dBZ0JHO0FBRUgsT0FBTyxFQUFFLGdCQUFnQixFQUFFLFdBQVcsRUFBRSxRQUFRLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFdkUsT0FBTyxFQUFFLGNBQWMsRUFBRSxvQkFBb0IsRUFBRSxjQUFjLEVBQUUsY0FBYyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRXJILE1BQU0sT0FBTyxZQUFZO0lBQ2YsT0FBTyxHQUEyQixFQUFFLENBQUM7SUFDckMsY0FBYyxDQUFXO0lBRWpDLFlBQVksYUFBdUIsRUFBRSxjQUF3QjtRQUMzRCxJQUFJLENBQUMsY0FBYyxHQUFHLGFBQWEsQ0FBQztRQUNwQyxNQUFNLElBQUksR0FBRyxnQkFBZ0IsQ0FBQyxjQUFjLElBQUksRUFBRSxFQUFFLGFBQWEsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUN6RSx3RUFBd0U7UUFDeEUsdUVBQXVFO1FBQ3ZFLHVFQUF1RTtRQUN2RSx3RUFBd0U7UUFDeEUsbUVBQW1FO1FBQ25FLDREQUE0RDtRQUM1RCxJQUFJLENBQUMsT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7SUFDN0UsQ0FBQztJQUVELDBDQUEwQztJQUMxQyxnQkFBZ0I7UUFDZCxPQUFPLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztJQUNoRixDQUFDO0lBRUQsK0JBQStCO0lBQy9CLE9BQU87UUFDTCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDO0lBQzNCLENBQUM7SUFFRCxxRkFBcUY7SUFDckYsV0FBVyxDQUFDLEtBQWEsRUFBRSxJQUFjLEVBQUUsR0FBVyxFQUFFLEtBQWM7UUFDcEUsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQ2hELGlCQUFpQixDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUMsQ0FBQztRQUMxRSxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQztJQUN0QixDQUFDO0lBRUQsc0ZBQXNGO0lBQ3RGLFFBQVEsQ0FBQyxLQUFhLEVBQUUsSUFBYyxFQUFFLEdBQVcsRUFBRSxLQUFjO1FBQ2pFLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQztRQUNoRCxjQUFjLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQyxDQUFDO1FBQ3ZFLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDO0lBQ3RCLENBQUM7SUFFRDs7O09BR0c7SUFDSyxXQUFXLENBQUMsS0FBYSxFQUFFLElBQWMsRUFBRSxHQUFXO1FBQzVELE1BQU0sS0FBSyxHQUFHLElBQUksT0FBTyxFQUFVLENBQUM7UUFDcEMsTUFBTSxJQUFJLEdBQUcsV0FBVyxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDOUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsR0FBRyxvQkFBb0IsQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDbEUsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLEdBQUcsQ0FBQyxPQUFPLElBQUksVUFBVSxDQUFDLEVBQUUsR0FBRyxDQUFDLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDekQsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsUUFBUSxDQUFDLEtBQWMsRUFBRSxJQUFlLEVBQUUsR0FBWTtRQUNwRCxNQUFNLEVBQUUsVUFBVSxFQUFFLE9BQU8sRUFBRSxHQUFHLG9CQUFvQixDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQztRQUNsRSxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLE9BQU8sRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO1FBQy9FLE9BQU8sT0FBTyxLQUFLLEtBQUssV0FBVyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUM5RixDQUFDO0lBRUQsc0dBQXNHO0lBQ3RHLFFBQVE7UUFDTixPQUFPLElBQUksQ0FBQyxPQUFPLENBQUM7SUFDdEIsQ0FBQztJQUVELGtHQUFrRztJQUNsRyxVQUFVLENBQUMsU0FBc0IsRUFBRSxPQUFvQixFQUFFLEtBQW1CO1FBQzFFLElBQUksQ0FBQyxPQUFPLEdBQUcsY0FBYyxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUN6RSxDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFNoYXJlZE1lbW9yeSDigJQgVGhlIHNoYXJlZCBzdGF0ZSBjb250YWluZXIgZm9yIGFsbCBmbG93Y2hhcnQgZXhlY3V0aW9uXG4gKlxuICogTGlrZSBhIHJ1bnRpbWUgaGVhcCB3aXRoIG5hbWVzcGFjZSBpc29sYXRpb246XG4gKiAtIEVhY2ggcnVuIGdldHMgaXRzIG93biBuYW1lc3BhY2UgKHJ1bnMve2lkfS8pXG4gKiAtIERlZmF1bHQgdmFsdWVzIGNhbiBiZSBpbml0aWFsaXNlZCBhbmQgcHJlc2VydmVkXG4gKiAtIEFjY2VwdHMgY29tbWl0IGJ1bmRsZXMgZnJvbSBUcmFuc2FjdGlvbkJ1ZmZlclxuICpcbiAqIENPUFktT04tV1JJVEUgKDkuMjkuMCDigJQgZG9jcy9kZXNpZ24vMjAyNi0xMC1jb3B5LW9uLXdyaXRlLWNvbW1pdC5tZCk6XG4gKiBgY29udGV4dGAgaXMgYSBHRU5FUkFUSU9OLCBhbmQgYSBnZW5lcmF0aW9uIGlzIG5ldmVyIGVkaXRlZC4gRXZlcnkgd3JpdGUg4oCUXG4gKiBgYXBwbHlQYXRjaGAgKGEgc3RhZ2UgY29tbWl0KSwgYHNldFZhbHVlYCwgYHVwZGF0ZVZhbHVlYCDigJQgYnVpbGRzIHRoZSBuZXh0XG4gKiBnZW5lcmF0aW9uIGJ5IGNvcHlpbmcgdGhlIHJvb3QgYW5kIHRoZSBjb250YWluZXJzIG9uIGVhY2ggd3JpdHRlbiBwYXRoLFxuICogc2hhcmVzIGV2ZXJ5IG90aGVyIHN1YnRyZWUgd2l0aCB0aGUgZ2VuZXJhdGlvbiBiZWZvcmUgaXQsIGFuZCBzd2Fwcy4gU28gYVxuICogZ2VuZXJhdGlvbiBhIHN0YWdlIGNhcHR1cmVkIChpdHMgZmlyc3QtdG91Y2ggdmlldywgaXRzIHRyYW5zYWN0aW9uXG4gKiBidWZmZXIncyBkaWZmIGJhc2UpIHN0YXlzIGV4YWN0bHkgd2hhdCBpdCB3YXMsIGFuZCBhIHdyaXRlIGNvc3RzIE8od2hhdCBpdFxuICogd3JvdGUpLCBub3QgTyhzdGF0ZSkuXG4gKi9cblxuaW1wb3J0IHsgbWVyZ2VDb250ZXh0V2lucywgb3duZWRSb290T2YsIG93blNwaW5lIH0gZnJvbSAnLi9wYXRoT3BzLmpzJztcbmltcG9ydCB0eXBlIHsgTWVtb3J5UGF0Y2gsIFRyYWNlRW50cnkgfSBmcm9tICcuL3R5cGVzLmpzJztcbmltcG9ydCB7IGdldE5lc3RlZFZhbHVlLCBnZXRSdW5BbmRHbG9iYWxQYXRocywgbmV4dEdlbmVyYXRpb24sIHNldE5lc3RlZFZhbHVlLCB1cGRhdGVOZXN0ZWRWYWx1ZSB9IGZyb20gJy4vdXRpbHMuanMnO1xuXG5leHBvcnQgY2xhc3MgU2hhcmVkTWVtb3J5IHtcbiAgcHJpdmF0ZSBjb250ZXh0OiB7IFtrZXk6IHN0cmluZ106IGFueSB9ID0ge307XG4gIHByaXZhdGUgX2RlZmF1bHRWYWx1ZXM/OiB1bmtub3duO1xuXG4gIGNvbnN0cnVjdG9yKGRlZmF1bHRWYWx1ZXM/OiB1bmtub3duLCBpbml0aWFsQ29udGV4dD86IHVua25vd24pIHtcbiAgICB0aGlzLl9kZWZhdWx0VmFsdWVzID0gZGVmYXVsdFZhbHVlcztcbiAgICBjb25zdCBzZWVkID0gbWVyZ2VDb250ZXh0V2lucyhpbml0aWFsQ29udGV4dCB8fCB7fSwgZGVmYXVsdFZhbHVlcyB8fCB7fSk7XG4gICAgLy8gRGV0YWNoZWQgT05DRSwgaGVyZTogYG1lcmdlQ29udGV4dFdpbnNgIGNvcGllcyBvbmx5IHRoZSB0b3AgbGV2ZWwsIHNvXG4gICAgLy8gdGhlIHNlZWQncyBuZXN0ZWQgdmFsdWVzIGFyZSB0aGUgY2FsbGVyJ3Mgb3duIG9iamVjdHMuIEJlZm9yZSA5LjI5LjBcbiAgICAvLyB0aGUgZmlyc3QgY29tbWl0J3Mgd2hvbGUtc3RhdGUgY2xvbmUgZGV0YWNoZWQgdGhlbSAoYW5kIHVudGlsIHRoZW4gYVxuICAgIC8vIGNhbGxlciBtdXRhdGluZyBpdHMgYGluaXRpYWxDb250ZXh0YCBjaGFuZ2VkIGxpdmUgc3RhdGUgd2l0aCBubyByb3cpO1xuICAgIC8vIG5vdGhpbmcgcmUtY2xvbmVzIHRoZSBzdGF0ZSBhbnkgbW9yZSwgc28gdGhlIHNlZWQgaXMgZGV0YWNoZWQgYXRcbiAgICAvLyBjb25zdHJ1Y3Rpb24g4oCUIG9uZSBjbG9uZSBwZXIgcnVudGltZSwgbm90IG9uZSBwZXIgY29tbWl0LlxuICAgIHRoaXMuY29udGV4dCA9IE9iamVjdC5rZXlzKHNlZWQpLmxlbmd0aCA+IDAgPyBzdHJ1Y3R1cmVkQ2xvbmUoc2VlZCkgOiBzZWVkO1xuICB9XG5cbiAgLyoqIEdldHMgYSBjbG9uZSBvZiB0aGUgZGVmYXVsdCB2YWx1ZXMuICovXG4gIGdldERlZmF1bHRWYWx1ZXMoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RlZmF1bHRWYWx1ZXMgPyBzdHJ1Y3R1cmVkQ2xvbmUodGhpcy5fZGVmYXVsdFZhbHVlcykgOiB1bmRlZmluZWQ7XG4gIH1cblxuICAvKiogR2V0cyBhbGwgcnVuIG5hbWVzcGFjZXMuICovXG4gIGdldFJ1bnMoKSB7XG4gICAgcmV0dXJuIHRoaXMuY29udGV4dC5ydW5zO1xuICB9XG5cbiAgLyoqIFVwZGF0ZXMgYSB2YWx1ZSB1c2luZyBtZXJnZSBzZW1hbnRpY3MsIGFzIGEgbmV3IGdlbmVyYXRpb24gKHBhdGggY29weSArIHN3YXApLiAqL1xuICB1cGRhdGVWYWx1ZShydW5JZDogc3RyaW5nLCBwYXRoOiBzdHJpbmdbXSwga2V5OiBzdHJpbmcsIHZhbHVlOiB1bmtub3duKSB7XG4gICAgY29uc3QgbmV4dCA9IHRoaXMub3duZWRQYXRoVG8ocnVuSWQsIHBhdGgsIGtleSk7XG4gICAgdXBkYXRlTmVzdGVkVmFsdWUobmV4dCwgcnVuSWQsIHBhdGgsIGtleSwgdmFsdWUsIHRoaXMuZ2V0RGVmYXVsdFZhbHVlcygpKTtcbiAgICB0aGlzLmNvbnRleHQgPSBuZXh0O1xuICB9XG5cbiAgLyoqIFNldHMgYSB2YWx1ZSB1c2luZyBvdmVyd3JpdGUgc2VtYW50aWNzLCBhcyBhIG5ldyBnZW5lcmF0aW9uIChwYXRoIGNvcHkgKyBzd2FwKS4gKi9cbiAgc2V0VmFsdWUocnVuSWQ6IHN0cmluZywgcGF0aDogc3RyaW5nW10sIGtleTogc3RyaW5nLCB2YWx1ZTogdW5rbm93bikge1xuICAgIGNvbnN0IG5leHQgPSB0aGlzLm93bmVkUGF0aFRvKHJ1bklkLCBwYXRoLCBrZXkpO1xuICAgIHNldE5lc3RlZFZhbHVlKG5leHQsIHJ1bklkLCBwYXRoLCBrZXksIHZhbHVlLCB0aGlzLmdldERlZmF1bHRWYWx1ZXMoKSk7XG4gICAgdGhpcy5jb250ZXh0ID0gbmV4dDtcbiAgfVxuXG4gIC8qKlxuICAgKiBBIGNvcHkgb2YgdGhlIGN1cnJlbnQgZ2VuZXJhdGlvbidzIHJvb3Qgd2hvc2UgY29udGFpbmVycyBvbiB0aGUgd2F5IHRvXG4gICAqIGBrZXlgIGFyZSBvd25lZCDigJQgdGhlIGluLXBsYWNlIGhlbHBlcnMgYWJvdmUgdGhlbiBlZGl0IG9ubHkgY29waWVzLlxuICAgKi9cbiAgcHJpdmF0ZSBvd25lZFBhdGhUbyhydW5JZDogc3RyaW5nLCBwYXRoOiBzdHJpbmdbXSwga2V5OiBzdHJpbmcpOiB7IFtrZXk6IHN0cmluZ106IGFueSB9IHtcbiAgICBjb25zdCBvd25lZCA9IG5ldyBXZWFrU2V0PG9iamVjdD4oKTtcbiAgICBjb25zdCByb290ID0gb3duZWRSb290T2YodGhpcy5jb250ZXh0LCBvd25lZCk7XG4gICAgY29uc3QgeyBydW5QYXRoLCBnbG9iYWxQYXRoIH0gPSBnZXRSdW5BbmRHbG9iYWxQYXRocyhydW5JZCwgcGF0aCk7XG4gICAgb3duU3BpbmUocm9vdCwgWy4uLihydW5QYXRoIHx8IGdsb2JhbFBhdGgpLCBrZXldLCBvd25lZCk7XG4gICAgcmV0dXJuIHJvb3Q7XG4gIH1cblxuICAvKipcbiAgICogUmVhZHMgYSB2YWx1ZSBmcm9tIHRoZSBzdG9yZS5cbiAgICogTG9va3MgdXAgaW4gcnVuIG5hbWVzcGFjZSBmaXJzdCwgZmFsbHMgYmFjayB0byBnbG9iYWwuXG4gICAqL1xuICBnZXRWYWx1ZShydW5JZD86IHN0cmluZywgcGF0aD86IHN0cmluZ1tdLCBrZXk/OiBzdHJpbmcpOiBhbnkge1xuICAgIGNvbnN0IHsgZ2xvYmFsUGF0aCwgcnVuUGF0aCB9ID0gZ2V0UnVuQW5kR2xvYmFsUGF0aHMocnVuSWQsIHBhdGgpO1xuICAgIGNvbnN0IHZhbHVlID0gcnVuUGF0aCA/IGdldE5lc3RlZFZhbHVlKHRoaXMuY29udGV4dCwgcnVuUGF0aCwga2V5KSA6IHVuZGVmaW5lZDtcbiAgICByZXR1cm4gdHlwZW9mIHZhbHVlICE9PSAndW5kZWZpbmVkJyA/IHZhbHVlIDogZ2V0TmVzdGVkVmFsdWUodGhpcy5jb250ZXh0LCBnbG9iYWxQYXRoLCBrZXkpO1xuICB9XG5cbiAgLyoqIEdldHMgdGhlIGVudGlyZSBzdGF0ZSBhcyBhIEpTT04gb2JqZWN0IOKAlCB0aGUgY3VycmVudCBnZW5lcmF0aW9uLCBieSByZWZlcmVuY2UuIE5ldmVyIG11dGF0ZSBpdC4gKi9cbiAgZ2V0U3RhdGUoKTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4ge1xuICAgIHJldHVybiB0aGlzLmNvbnRleHQ7XG4gIH1cblxuICAvKiogQXBwbGllcyBhIGNvbW1pdCBidW5kbGUgZnJvbSBUcmFuc2FjdGlvbkJ1ZmZlcjogYnVpbGRzIHRoZSBuZXh0IGdlbmVyYXRpb24gYW5kIHN3YXBzIGl0IGluLiAqL1xuICBhcHBseVBhdGNoKG92ZXJ3cml0ZTogTWVtb3J5UGF0Y2gsIHVwZGF0ZXM6IE1lbW9yeVBhdGNoLCB0cmFjZTogVHJhY2VFbnRyeVtdKTogdm9pZCB7XG4gICAgdGhpcy5jb250ZXh0ID0gbmV4dEdlbmVyYXRpb24odGhpcy5jb250ZXh0LCB1cGRhdGVzLCBvdmVyd3JpdGUsIHRyYWNlKTtcbiAgfVxufVxuIl19
|
|
@@ -91,6 +91,15 @@ export declare class StageContext {
|
|
|
91
91
|
* facade path never allocates it.
|
|
92
92
|
*/
|
|
93
93
|
private _nestedReads?;
|
|
94
|
+
/**
|
|
95
|
+
* Dev mode only: the root keys whose LAST tracked read was served from
|
|
96
|
+
* committed state — before this stage's first write, when no transaction
|
|
97
|
+
* buffer exists yet. Such a read is the committed object itself, so an
|
|
98
|
+
* in-place edit of it moves the buffer's diff base too (copy-on-write,
|
|
99
|
+
* 9.29.0); {@link warnOnBorrowedMutation} checks these keys even when the
|
|
100
|
+
* stage wrote them. Never allocated outside `enableDevMode()`.
|
|
101
|
+
*/
|
|
102
|
+
private _viewReads?;
|
|
94
103
|
/**
|
|
95
104
|
* Has this frame committed at least once? The borrowed-read guard runs on
|
|
96
105
|
* the FIRST commit only — see {@link warnOnBorrowedMutation}. The frame
|
|
@@ -304,14 +313,14 @@ export declare class StageContext {
|
|
|
304
313
|
* and the transaction buffer's diff base ({@link getTransactionBuffer}).
|
|
305
314
|
*
|
|
306
315
|
* WHY A BARE REFERENCE IS SAFE — the invariant this rests on: committed
|
|
307
|
-
* state is immutable-after-swap. `SharedMemory
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
316
|
+
* state is immutable-after-swap. Every write to `SharedMemory` builds the
|
|
317
|
+
* NEXT generation and swaps it in (copy-on-write, 9.29.0: `applyPatch` via
|
|
318
|
+
* `nextGeneration`, and `setValue`/`updateValue` too) — it copies the root
|
|
319
|
+
* and the containers on each written path, shares the rest, and never
|
|
320
|
+
* edits a container of the generation a stage captured here. Holding the
|
|
321
|
+
* reference therefore gives this stage a stable snapshot at zero cost — no
|
|
322
|
+
* clone, which is the entire point of #13. The transaction buffer's
|
|
323
|
+
* net-change diff base rests on the same guarantee: it IS this view.
|
|
315
324
|
*
|
|
316
325
|
* WHY FIRST TOUCH, not first write: the pre-#13 eager engine cloned the
|
|
317
326
|
* state into the buffer at the stage's first ACCESS, anchoring both its
|
|
@@ -371,7 +380,13 @@ export declare class StageContext {
|
|
|
371
380
|
* 2. LIVE state via `sharedMemory.getValue` for keys absent from the
|
|
372
381
|
* snapshot — including its run→global namespace fallback. The eager
|
|
373
382
|
* engine had this exact live fallback for snapshot-missing keys;
|
|
374
|
-
* byte-identity over purity.
|
|
383
|
+
* byte-identity over purity. After the stage's first write such a
|
|
384
|
+
* value can be the very container the buffer's diff base holds at
|
|
385
|
+
* the path (the stage deleted or unset it, or replaced a container
|
|
386
|
+
* above it), so the base is detached there first
|
|
387
|
+
* (`TransactionBuffer · detachBase`): an in-place edit of the value
|
|
388
|
+
* (out of contract) written back is recorded, as on 9.28.0, whose
|
|
389
|
+
* base was a clone taken at the first write.
|
|
375
390
|
*
|
|
376
391
|
* Reads never construct the buffer (#13): a stage that never writes
|
|
377
392
|
* performs zero clones of the shared state. */
|
|
@@ -447,22 +462,34 @@ export declare class StageContext {
|
|
|
447
462
|
* subflow merge-back into a committed branch parent) other stages have
|
|
448
463
|
* legitimately moved the state they were compared against.
|
|
449
464
|
*
|
|
465
|
+
* A key the stage DID stage is still checked when its last read came from
|
|
466
|
+
* committed state ({@link _viewReads} — read before the first write):
|
|
467
|
+
* that read was the committed object itself, and the buffer's diff base IS
|
|
468
|
+
* that object (copy-on-write, 9.29.0), so an in-place edit followed by a
|
|
469
|
+
* write of the key commits NO change — live state keeps the edit, the log
|
|
470
|
+
* does not. Compared against committed state, not the written value, so a
|
|
471
|
+
* legitimate write never trips it.
|
|
472
|
+
*
|
|
450
473
|
* Costs nothing outside `enableDevMode()`.
|
|
451
474
|
*/
|
|
452
475
|
private warnOnBorrowedMutation;
|
|
476
|
+
/** The staged-key half of {@link warnOnBorrowedMutation}: did committed state itself move under a read? */
|
|
477
|
+
private warnOnCommittedMutation;
|
|
453
478
|
/**
|
|
454
479
|
* Flush staged writes to shared memory and RELEASE the per-stage staging
|
|
455
480
|
* state (#13b).
|
|
456
481
|
*
|
|
457
|
-
* Commit is the stage's lifecycle end: `buffer` (
|
|
458
|
-
* `stateView` (a reference that
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
* the
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
* 500-iteration agent OOMed a default Node heap (backlog #18).
|
|
482
|
+
* Commit is the stage's lifecycle end: `buffer` (its working copy and
|
|
483
|
+
* whatever private copies its reads took) and `stateView` (a reference that
|
|
484
|
+
* pins one committed-state GENERATION) are only needed DURING execution,
|
|
485
|
+
* as the read snapshot + net-change diff base. The execution tree retains
|
|
486
|
+
* every StageContext for the lifetime of the run, so WITHOUT the release a
|
|
487
|
+
* long loop retains one state generation per executed stage — measured
|
|
488
|
+
* O(N²) before copy-on-write (9.29.0), when each generation and each
|
|
489
|
+
* buffer was a whole-state clone: 563.8MB at N=200 on an agent-style
|
|
490
|
+
* chart; a 500-iteration agent OOMed a default Node heap (backlog #18).
|
|
491
|
+
* Generations now share every unchanged subtree, but a pinned one still
|
|
492
|
+
* keeps the containers later commits replaced alive.
|
|
466
493
|
*
|
|
467
494
|
* RE-USE AFTER COMMIT stays correct because both fields re-create lazily:
|
|
468
495
|
* - a later READ re-anchors via {@link firstTouchState} on the CURRENT
|