@mark1russell7/render-node 0.1.1 → 0.2.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 -1
- package/build/index.d.ts +25 -13
- package/build/index.d.ts.map +1 -1
- package/build/index.js +122 -39
- package/build/index.js.map +1 -1
- package/package.json +3 -2
- package/src/engine.ts +128 -40
- package/src/index.ts +2 -2
- package/src/types.ts +7 -4
- package/src/writes.ts +19 -14
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@mark1russell7/render-node) [](https://github.com/mark1russell7/render/actions/workflows/ci.yml) [](https://github.com/mark1russell7/render/blob/main/LICENSE)
|
|
4
4
|
|
|
5
|
-
The reactive store of render. Each value is a node with an expression. One write starts one epoch. The store evaluates each reader of the node in topological order, and it stops at each value that does not change.
|
|
5
|
+
The reactive store of render. Each value is a node with an expression, a coeval document. One write starts one epoch. The store evaluates each reader of the node in topological order, and it stops at each value that does not change.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
npm i @mark1russell7/render-node @mark1russell7/render-dsl @mark1russell7/render-splay
|
|
@@ -31,6 +31,8 @@ batch(store, () => {
|
|
|
31
31
|
|
|
32
32
|
A node can be a container of named slots, and a reference reads through the slots. The `NodeOps` of a store (`splash`, `flow`, `deref` and `targets`) are its extension points.
|
|
33
33
|
|
|
34
|
+
The kernel of [coeval](https://github.com/mark1russell7/coeval) compiles the expression of a node one time, at each write. Each epoch evaluates the plan. The store is the host thing of the root `world`. The kernel gives it each path in one call, and the `deref` op of the store answers. The reads of a node are its free `world` references (`refsOf` of the kernel, `readsOf`). A store value is data, thus a value with the shape of a reference stays a value.
|
|
35
|
+
|
|
34
36
|
## More
|
|
35
37
|
|
|
36
38
|
- [The site](https://mark1russell7.github.io/render/): the guide, live demos and the viewer.
|
package/build/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Optional } from "@mark1russell7/render-optional";
|
|
2
|
-
import {
|
|
2
|
+
import { Expr, Ops, Resolver } from "@mark1russell7/render-dsl";
|
|
3
|
+
import "@mark1russell7/coeval";
|
|
3
4
|
//#region src/types.d.ts
|
|
5
|
+
/** A read path of an expression: the segments of a `world` reference. The first segment is a node ID. */
|
|
6
|
+
type DepPath = readonly string[];
|
|
4
7
|
/** The identity of a node in its store. */
|
|
5
8
|
type NodeId = string;
|
|
6
9
|
/**
|
|
@@ -12,13 +15,13 @@ type NodeId = string;
|
|
|
12
15
|
type Node = {
|
|
13
16
|
readonly id: NodeId;
|
|
14
17
|
/**
|
|
15
|
-
* The expression is the source of truth. The value is a cache of its evaluation.
|
|
18
|
+
* The expression is the source of truth: a coeval document. The value is a cache of its evaluation.
|
|
16
19
|
* A write changes the expression, thus a later evaluation cannot revert the write.
|
|
17
20
|
*/
|
|
18
21
|
readonly expr: Expr;
|
|
19
22
|
/** The value of the expression after the last epoch. `none` is a value: the expression has no result. */
|
|
20
23
|
readonly value: Optional<unknown>;
|
|
21
|
-
/** The
|
|
24
|
+
/** The `world` reference paths of `expr`, without duplicates: the free references that the kernel finds (`refsOf`). */
|
|
22
25
|
readonly reads: readonly DepPath[];
|
|
23
26
|
/** The value readers: the nodes whose read path ends on this node. */
|
|
24
27
|
readonly seats: ReadonlySet<NodeId>;
|
|
@@ -32,7 +35,7 @@ type Node = {
|
|
|
32
35
|
readonly heldBy: ReadonlySet<NodeId>;
|
|
33
36
|
/**
|
|
34
37
|
* The named slots of a container. A node with slots is a container: its expression is a `record`
|
|
35
|
-
*
|
|
38
|
+
* construct over its slots, thus its value is the record of their values.
|
|
36
39
|
*/
|
|
37
40
|
readonly slots: ReadonlyMap<string, NodeId>;
|
|
38
41
|
};
|
|
@@ -129,9 +132,10 @@ export declare const defaultOps: NodeOps;
|
|
|
129
132
|
export declare const nodeStore: (options?: StoreOptions) => NodeStore;
|
|
130
133
|
/**
|
|
131
134
|
* This function gives an independent copy of a store: the same nodes, values, slots and seats, and the same
|
|
132
|
-
* counters. A write to one store does not change the other. The expressions and the values are
|
|
133
|
-
* thus the copy shares them. The options replace the semantics or the op registry of the copy, for
|
|
134
|
-
* class semantics of a copied biblo.
|
|
135
|
+
* counters. A write to one store does not change the other. The expressions, their plans and the values are
|
|
136
|
+
* immutable, thus the copy shares them. The options replace the semantics or the op registry of the copy, for
|
|
137
|
+
* example the class semantics of a copied biblo. A new op registry compiles the plans again. The function throws
|
|
138
|
+
* inside a batch, because a batch has pending work.
|
|
135
139
|
*/
|
|
136
140
|
export declare const forkStore: (store: NodeStore, options?: StoreOptions) => NodeStore;
|
|
137
141
|
/** This function gives a node of the store, or `undefined`. */
|
|
@@ -146,16 +150,16 @@ export declare const batch: <T>(store: NodeStore, fn: () => T) => T;
|
|
|
146
150
|
/**
|
|
147
151
|
* This function adds a node with an expression and evaluates it. It gives the ID of the node.
|
|
148
152
|
* The ID is the hint when no node has it, otherwise a fresh ID. A reader that waited for this ID gets rewired.
|
|
149
|
-
* The
|
|
153
|
+
* The kernel compiles the expression one time, and each epoch evaluates the plan.
|
|
150
154
|
*/
|
|
151
|
-
export declare const addNode: (store: NodeStore, expr: Expr
|
|
155
|
+
export declare const addNode: (store: NodeStore, expr: Expr, idHint?: string) => NodeId;
|
|
152
156
|
/**
|
|
153
157
|
* This function sets the expression of a node. This is the fundamental write.
|
|
154
158
|
* The engine rewires the reads of the node, evaluates it and propagates the change.
|
|
155
159
|
* A write to a container collapses it first: its own slots are removed, and the expression replaces the record.
|
|
156
|
-
* The
|
|
160
|
+
* The kernel compiles the new expression one time.
|
|
157
161
|
*/
|
|
158
|
-
export declare const setExpr: (store: NodeStore, id: NodeId, expr: Expr
|
|
162
|
+
export declare const setExpr: (store: NodeStore, id: NodeId, expr: Expr) => void;
|
|
159
163
|
/**
|
|
160
164
|
* This function writes a literal value: it is `setExpr` with `lit(value)`.
|
|
161
165
|
* A write to a derived node makes it an input node, like a value that a person types over a formula.
|
|
@@ -202,13 +206,21 @@ export declare const removeNode: (store: NodeStore, id: NodeId) => void;
|
|
|
202
206
|
export declare const resolveAll: (store: NodeStore) => void;
|
|
203
207
|
//#endregion
|
|
204
208
|
//#region src/engine.d.ts
|
|
209
|
+
/**
|
|
210
|
+
* This function gives the read paths of an expression: the `world` references that the kernel finds (`refsOf`).
|
|
211
|
+
* The paths have no duplicates and keep the order of the first read. A parameter of a `fn` is not a read.
|
|
212
|
+
*/
|
|
213
|
+
export declare const readsOf: (expr: Expr) => readonly DepPath[];
|
|
205
214
|
/**
|
|
206
215
|
* This function gives the chain of nodes that a read path goes through. The chain starts at the root
|
|
207
216
|
* (`path[0]`, a node ID) and follows slots as far as they exist. The last node is the terminal: the path
|
|
208
217
|
* reads its value. The function gives an empty chain when the root is not in the store.
|
|
209
218
|
*/
|
|
210
219
|
export declare const readTargets: (nodes: ReadonlyMap<NodeId, Node>, path: DepPath) => Node[];
|
|
211
|
-
/**
|
|
220
|
+
/**
|
|
221
|
+
* This function makes the resolver of a store: `path[0]` is a node ID. The expressions of the store read the same
|
|
222
|
+
* paths through the host thing of the store.
|
|
223
|
+
*/
|
|
212
224
|
declare const resolverOf: (store: NodeStore) => Resolver;
|
|
213
225
|
/**
|
|
214
226
|
* The limit of the rounds of a cycle in one epoch. Like the iterative calculation of a spreadsheet, the members
|
|
@@ -236,5 +248,5 @@ export declare const isPlainObject: (v: unknown) => v is Record<string, unknown>
|
|
|
236
248
|
/** This function compares two node values structurally. The module comment gives the rules. */
|
|
237
249
|
export declare const valueEquals: (a: unknown, b: unknown) => boolean;
|
|
238
250
|
//#endregion
|
|
239
|
-
export { type DerefFn, type EpochStats, type FlowFn, type Node, type NodeId, type NodeOps, type NodeStore, type ReadTargets, type SlotOptions, type SplashFn, type SplashTarget, type StoreOptions, type TargetsFn, resolverOf as storeResolver };
|
|
251
|
+
export { type DepPath, type DerefFn, type EpochStats, type FlowFn, type Node, type NodeId, type NodeOps, type NodeStore, type ReadTargets, type SlotOptions, type SplashFn, type SplashTarget, type StoreOptions, type TargetsFn, resolverOf as storeResolver };
|
|
240
252
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/ops.ts","../src/writes.ts","../src/engine.ts","../src/toposort.ts","../src/equality.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/ops.ts","../src/writes.ts","../src/engine.ts","../src/toposort.ts","../src/equality.ts"],"mappings":";;;;;KAIY;;KAGA;;;;;;;KAQA;WACD,IAAI;;;;;WAKJ,MAAM;;WAEN,OAAO;;WAEP,gBAAgB;;WAEhB,OAAO,YAAY;;WAEnB,iBAAiB,YAAY;;WAE7B,UAAU,YAAY;;WAEtB,QAAQ;;WAER,QAAQ,YAAY;;;;;WAKpB,OAAO,oBAAoB;;;;;;KAO1B;;WAED,QAAQ;;WAER,MAAM;;WAEN,OAAO;;;;;WAKP,SAAS;;;KAIR,eAAe,KAAK;EAAmB,OAAO;;;KAG9C,YAAY,OAAO,mBAAmB,QAAQ,cAAc,OAAO;;KAGnE,UAAU,QAAQ,MAAM,OAAO,cAAc,YAAY;;KAGzD,WAAW,MAAM,MAAM,yBAAyB,OAAO,cAAc;;;;;;KAOrE;WAAyB,kBAAkB;WAAiB,UAAU;;;KAGtE,aAAa,MAAM,MAAM,yBAAyB,OAAO,cAAc;;KAGvE;;WAED,WAAW,YAAY;;WAEvB,SAAS,YAAY;;WAErB,QAAQ,YAAY;;WAEpB;;;KAIC;WACD,OAAO,YAAY,QAAQ;WAC3B,SAAS;WACT,KAAK;;WAEL,YAAY;;;KAIX;;WAED,UAAU;;WAEV,MAAM;;;;;;;;qBCnGJ,eAAe;;qBAQf,aAAa;;;;;;qBAOb,cAAc;;;;;;qBA2Bd,gBAAgB;;qBAgBhB,YAAY;;;;qBClDZ,YAAa,UAAS,iBAAoB;;;;;;;;qBAU1C,YAAa,OAAO,WAAW,UAAS,iBAAoB;;qBAwC5D,UAAW,OAAO,WAAW,IAAI,WAAS;;qBAG1C,YAAa,OAAO,WAAW,IAAI,WAAS;;;;;qBAM5C,QAAS,GAAG,OAAO,WAAW,UAAU,MAAI;;;;;;qBAO5C,UAAW,OAAO,WAAW,MAAM,MAAM,oBAAkB;;;;;;;qBAc3D,UAAW,OAAO,WAAW,IAAI,QAAQ,MAAM;;;;;qBAe/C,WAAY,OAAO,WAAW,IAAI,QAAQ;;qBAK1C,WAAY,OAAO,WAAW,QAAQ,mBAAmB;;KAO1D;;;;;WAKD;;;;;;;;qBASE,UACX,OAAO,WACP,UAAU,QACV,cACA,SAAS,oBACT,UAAS;;;;;;;;;;qBA6BE,aAAc,OAAO,WAAW,IAAI;;;;;;qBAYpC,aAAc,OAAO,WAAW,IAAI;;;;;;qBAYpC,aAAc,OAAO;;;;;;;qBCxDrB,UAAW,MAAM,kBAAgB;;;;;;qBAsGjC,cAAe,OAAO,YAAY,QAAQ,OAAO,MAAM,YAAU;;;;;cA+QjE,aAAc,OAAO,cAAY;;;;;qBAMjC;;;;;;;;;;;;;;qBC1fA,WAAY,OAAO,cAAY;;;;qBCG/B,gBAAiB,eAAa,KAAK;;qBA6BnC,cAAe,YAAY"}
|
package/build/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isSome, none, some } from "@mark1russell7/render-optional";
|
|
2
|
-
import {
|
|
2
|
+
import { NO_VALUE, isLiteral, lit, machineOf, renderWorld } from "@mark1russell7/render-dsl";
|
|
3
|
+
import { THING, context, isRecord, prepare, refsOf } from "@mark1russell7/coeval";
|
|
3
4
|
//#region src/equality.ts
|
|
4
5
|
/**
|
|
5
6
|
* Structural equality for node values.
|
|
@@ -116,11 +117,51 @@ const defaultOps = {
|
|
|
116
117
|
const state = (store) => store;
|
|
117
118
|
/** This function gives the public view of a state. */
|
|
118
119
|
const publicStore = (s) => s;
|
|
120
|
+
/** This function gives the keys of a path as texts. A document from another source can have a number key. */
|
|
121
|
+
const texts = (keys) => keys.every((k) => typeof k === "string") ? keys : keys.map(String);
|
|
122
|
+
/** This function reads a path of a store: `path[0]` is a node ID, and the `deref` op of the store reads the rest. */
|
|
123
|
+
const readPath = (s, path) => {
|
|
124
|
+
const rootId = path[0];
|
|
125
|
+
const root = rootId === void 0 ? void 0 : s.nodes.get(String(rootId));
|
|
126
|
+
return root ? s.nodeOps.deref(root, path.length === 1 ? [] : texts(path.slice(1)), publicStore(s)) : none;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* This function makes the host thing of a store: the root `world` of its expressions. The kernel gives it each
|
|
130
|
+
* `world` path in one call (`resolve`). The store has no value of its own (`NO_VALUE`).
|
|
131
|
+
*/
|
|
132
|
+
const storeThing = (s) => ({
|
|
133
|
+
[THING]: true,
|
|
134
|
+
label: "node store",
|
|
135
|
+
deref: (key) => {
|
|
136
|
+
const found = readPath(s, [key]);
|
|
137
|
+
return isSome(found) ? found.value : void 0;
|
|
138
|
+
},
|
|
139
|
+
toJson: () => NO_VALUE,
|
|
140
|
+
resolve: (keys) => {
|
|
141
|
+
const found = readPath(s, keys);
|
|
142
|
+
return isSome(found) ? { thing: found.value } : void 0;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
/** This function gives a state its context: the store is the root `world`. */
|
|
146
|
+
const withContext = (s) => {
|
|
147
|
+
s.ctx = context({ world: storeThing(s) });
|
|
148
|
+
return s;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* This function gives the compiler of an op registry. A store compiles each new expression one time, without a
|
|
152
|
+
* cache. Most expressions of a store are new objects, for example the literal of each write.
|
|
153
|
+
*/
|
|
154
|
+
const compilerOf = (ops) => {
|
|
155
|
+
const machine = machineOf(ops);
|
|
156
|
+
return (doc) => prepare(machine, doc);
|
|
157
|
+
};
|
|
119
158
|
/** This function makes the state of an empty store. */
|
|
120
|
-
const makeState = (nodeOps, ops) => ({
|
|
159
|
+
const makeState = (nodeOps, ops) => withContext({
|
|
121
160
|
nodes: /* @__PURE__ */ new Map(),
|
|
122
161
|
nodeOps,
|
|
123
162
|
ops,
|
|
163
|
+
plan: compilerOf(ops),
|
|
164
|
+
ctx: context(),
|
|
124
165
|
epochStats: null,
|
|
125
166
|
nextId: 0,
|
|
126
167
|
dangling: /* @__PURE__ */ new Map(),
|
|
@@ -128,12 +169,32 @@ const makeState = (nodeOps, ops) => ({
|
|
|
128
169
|
batchDepth: 0,
|
|
129
170
|
epoch: 0
|
|
130
171
|
});
|
|
131
|
-
|
|
132
|
-
|
|
172
|
+
const NO_READS = Object.freeze([]);
|
|
173
|
+
/**
|
|
174
|
+
* This function gives the read paths of an expression: the `world` references that the kernel finds (`refsOf`).
|
|
175
|
+
* The paths have no duplicates and keep the order of the first read. A parameter of a `fn` is not a read.
|
|
176
|
+
*/
|
|
177
|
+
const readsOf = (expr) => {
|
|
178
|
+
if (isLiteral(expr)) return NO_READS;
|
|
179
|
+
const out = [];
|
|
180
|
+
const seen = /* @__PURE__ */ new Set();
|
|
181
|
+
for (const use of refsOf(renderWorld, expr)) {
|
|
182
|
+
if (!use.free || use.root !== "world") continue;
|
|
183
|
+
const path = use.path.slice(1).map(String);
|
|
184
|
+
const key = JSON.stringify(path);
|
|
185
|
+
if (seen.has(key)) continue;
|
|
186
|
+
seen.add(key);
|
|
187
|
+
out.push(path);
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
};
|
|
191
|
+
/** This function makes a node that is not in a store yet. The kernel compiles its expression one time. */
|
|
192
|
+
const makeNode = (s, id, expr) => ({
|
|
133
193
|
id,
|
|
134
194
|
expr,
|
|
195
|
+
plan: s.plan(expr),
|
|
135
196
|
value: none,
|
|
136
|
-
reads:
|
|
197
|
+
reads: readsOf(expr),
|
|
137
198
|
seats: /* @__PURE__ */ new Set(),
|
|
138
199
|
seatsStructural: /* @__PURE__ */ new Set(),
|
|
139
200
|
seatedOn: /* @__PURE__ */ new Set(),
|
|
@@ -160,26 +221,40 @@ const freshId = (s, hint) => {
|
|
|
160
221
|
if (!s.nodes.has(id)) return id;
|
|
161
222
|
}
|
|
162
223
|
};
|
|
163
|
-
/** This function makes the `record`
|
|
224
|
+
/** This function makes the `record` construct of a container: one field for each slot, a `world` reference to the slot. */
|
|
164
225
|
const recordOf = (n) => {
|
|
165
|
-
const
|
|
166
|
-
for (const name of n.slots.keys())
|
|
226
|
+
const fields = {};
|
|
227
|
+
for (const name of n.slots.keys()) Object.defineProperty(fields, name, {
|
|
228
|
+
value: {
|
|
229
|
+
type: "ref",
|
|
230
|
+
value: [
|
|
231
|
+
"world",
|
|
232
|
+
n.id,
|
|
233
|
+
name
|
|
234
|
+
]
|
|
235
|
+
},
|
|
236
|
+
enumerable: true,
|
|
237
|
+
writable: true,
|
|
238
|
+
configurable: true
|
|
239
|
+
});
|
|
167
240
|
return {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
args
|
|
241
|
+
type: "record",
|
|
242
|
+
value: fields
|
|
171
243
|
};
|
|
172
244
|
};
|
|
173
|
-
/** This function tells if the expression of a container is the record of its current slots. */
|
|
245
|
+
/** This function tells if the expression of a container is the record of its current slots, in their order. */
|
|
174
246
|
const isRecordOf = (n) => {
|
|
175
247
|
const e = n.expr;
|
|
176
|
-
if (e.
|
|
248
|
+
if (e.type !== "record" || !isRecord(e.value)) return false;
|
|
249
|
+
const fields = e.value;
|
|
250
|
+
const names = Object.keys(fields);
|
|
251
|
+
if (names.length !== n.slots.size) return false;
|
|
177
252
|
let i = 0;
|
|
178
253
|
for (const name of n.slots.keys()) {
|
|
179
|
-
|
|
180
|
-
const r =
|
|
181
|
-
|
|
182
|
-
if (
|
|
254
|
+
if (names[i++] !== name) return false;
|
|
255
|
+
const r = fields[name];
|
|
256
|
+
const path = r?.type === "ref" && Array.isArray(r.value) ? r.value : void 0;
|
|
257
|
+
if (path?.length !== 3 || path[0] !== "world" || path[1] !== n.id || path[2] !== name) return false;
|
|
183
258
|
}
|
|
184
259
|
return true;
|
|
185
260
|
};
|
|
@@ -274,11 +349,14 @@ const unwireNode = (s, n) => {
|
|
|
274
349
|
}
|
|
275
350
|
};
|
|
276
351
|
/** This function changes the expression of a node and keeps its reads correct. The caller touches the node. */
|
|
277
|
-
const replaceExpr = (s, n, expr) => {
|
|
352
|
+
const replaceExpr = (s, n, expr, reads = readsOf(expr)) => {
|
|
278
353
|
unwireNode(s, n);
|
|
279
354
|
n.expr = expr;
|
|
280
|
-
n.
|
|
355
|
+
n.plan = s.plan(expr);
|
|
356
|
+
n.reads = reads;
|
|
281
357
|
};
|
|
358
|
+
/** This function gives the reads of the record of a container: one path to each slot. */
|
|
359
|
+
const recordReads = (n) => Array.from(n.slots.keys(), (name) => [n.id, name]);
|
|
282
360
|
const pending = (s) => s.pending ??= {
|
|
283
361
|
touched: /* @__PURE__ */ new Set(),
|
|
284
362
|
records: /* @__PURE__ */ new Set(),
|
|
@@ -332,7 +410,7 @@ const expandNow = (s, id) => {
|
|
|
332
410
|
continue;
|
|
333
411
|
}
|
|
334
412
|
const [key, fieldValue] = field;
|
|
335
|
-
const sub = makeNode(freshId(s, `${frame.owner.id}.${key}`), lit(fieldValue));
|
|
413
|
+
const sub = makeNode(s, freshId(s, `${frame.owner.id}.${key}`), lit(fieldValue));
|
|
336
414
|
sub.parent = frame.owner.id;
|
|
337
415
|
sub.value = some(fieldValue);
|
|
338
416
|
insertNode(s, sub);
|
|
@@ -410,7 +488,7 @@ const flush = (s) => {
|
|
|
410
488
|
for (const id of p.records) {
|
|
411
489
|
const n = s.nodes.get(id);
|
|
412
490
|
if (!n) continue;
|
|
413
|
-
if (!isRecordOf(n)) replaceExpr(s, n, recordOf(n));
|
|
491
|
+
if (!isRecordOf(n)) replaceExpr(s, n, recordOf(n), recordReads(n));
|
|
414
492
|
p.touched.add(id);
|
|
415
493
|
}
|
|
416
494
|
for (const id of p.touched) {
|
|
@@ -449,12 +527,11 @@ const flush = (s) => {
|
|
|
449
527
|
total: s.nodes.size
|
|
450
528
|
};
|
|
451
529
|
};
|
|
452
|
-
/**
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
};
|
|
530
|
+
/**
|
|
531
|
+
* This function makes the resolver of a store: `path[0]` is a node ID. The expressions of the store read the same
|
|
532
|
+
* paths through the host thing of the store.
|
|
533
|
+
*/
|
|
534
|
+
const resolverOf = (store) => (path) => readPath(state(store), path);
|
|
458
535
|
/**
|
|
459
536
|
* The limit of the rounds of a cycle in one epoch. Like the iterative calculation of a spreadsheet, the members
|
|
460
537
|
* of a cycle evaluate again until their values are stable, or until this limit.
|
|
@@ -551,14 +628,15 @@ const runEpoch = (s, frontier) => {
|
|
|
551
628
|
n.next = next;
|
|
552
629
|
}
|
|
553
630
|
const order = components(closure);
|
|
554
|
-
const
|
|
631
|
+
const ctx = s.ctx;
|
|
555
632
|
const evaluated = /* @__PURE__ */ new Set();
|
|
556
633
|
const changed = /* @__PURE__ */ new Set();
|
|
557
634
|
const cyclic = /* @__PURE__ */ new Set();
|
|
558
635
|
const evaluateNode = (n) => {
|
|
559
636
|
n.dirty = 0;
|
|
560
637
|
evaluated.add(n.id);
|
|
561
|
-
|
|
638
|
+
const result = n.plan.evaluate(ctx);
|
|
639
|
+
if (!s.nodeOps.splash(result.ok ? some(result.value) : none, n, store)) return false;
|
|
562
640
|
changed.add(n.id);
|
|
563
641
|
for (const m of n.next) m.dirty = epoch;
|
|
564
642
|
return true;
|
|
@@ -598,17 +676,21 @@ const runEpoch = (s, frontier) => {
|
|
|
598
676
|
const nodeStore = (options = {}) => publicStore(makeState(options.nodeOps ?? defaultOps, options.ops ?? {}));
|
|
599
677
|
/**
|
|
600
678
|
* This function gives an independent copy of a store: the same nodes, values, slots and seats, and the same
|
|
601
|
-
* counters. A write to one store does not change the other. The expressions and the values are
|
|
602
|
-
* thus the copy shares them. The options replace the semantics or the op registry of the copy, for
|
|
603
|
-
* class semantics of a copied biblo.
|
|
679
|
+
* counters. A write to one store does not change the other. The expressions, their plans and the values are
|
|
680
|
+
* immutable, thus the copy shares them. The options replace the semantics or the op registry of the copy, for
|
|
681
|
+
* example the class semantics of a copied biblo. A new op registry compiles the plans again. The function throws
|
|
682
|
+
* inside a batch, because a batch has pending work.
|
|
604
683
|
*/
|
|
605
684
|
const forkStore = (store, options = {}) => {
|
|
606
685
|
const s = state(store);
|
|
607
686
|
if (s.pending !== null || s.batchDepth > 0) throw new Error("forkStore: the store is in a batch");
|
|
687
|
+
const ops = options.ops ?? s.ops;
|
|
608
688
|
const copy = {
|
|
609
689
|
nodes: /* @__PURE__ */ new Map(),
|
|
610
690
|
nodeOps: options.nodeOps ?? s.nodeOps,
|
|
611
|
-
ops
|
|
691
|
+
ops,
|
|
692
|
+
plan: ops === s.ops ? s.plan : compilerOf(ops),
|
|
693
|
+
ctx: s.ctx,
|
|
612
694
|
epochStats: s.epochStats,
|
|
613
695
|
nextId: s.nextId,
|
|
614
696
|
dangling: new Map([...s.dangling].map(([id, readers]) => [id, new Set(readers)])),
|
|
@@ -619,6 +701,7 @@ const forkStore = (store, options = {}) => {
|
|
|
619
701
|
for (const [id, n] of s.nodes) {
|
|
620
702
|
const node = {
|
|
621
703
|
...n,
|
|
704
|
+
plan: ops === s.ops ? n.plan : copy.plan(n.expr),
|
|
622
705
|
seats: new Set(n.seats),
|
|
623
706
|
seatsStructural: new Set(n.seatsStructural),
|
|
624
707
|
seatedOn: new Set(n.seatedOn),
|
|
@@ -633,7 +716,7 @@ const forkStore = (store, options = {}) => {
|
|
|
633
716
|
};
|
|
634
717
|
copy.nodes.set(id, node);
|
|
635
718
|
}
|
|
636
|
-
return publicStore(copy);
|
|
719
|
+
return publicStore(withContext(copy));
|
|
637
720
|
};
|
|
638
721
|
/** This function gives a node of the store, or `undefined`. */
|
|
639
722
|
const getNode = (store, id) => store.nodes.get(id);
|
|
@@ -647,11 +730,11 @@ const batch = (store, fn) => runBatch(state(store), fn);
|
|
|
647
730
|
/**
|
|
648
731
|
* This function adds a node with an expression and evaluates it. It gives the ID of the node.
|
|
649
732
|
* The ID is the hint when no node has it, otherwise a fresh ID. A reader that waited for this ID gets rewired.
|
|
650
|
-
* The
|
|
733
|
+
* The kernel compiles the expression one time, and each epoch evaluates the plan.
|
|
651
734
|
*/
|
|
652
735
|
const addNode = (store, expr, idHint) => {
|
|
653
736
|
const s = state(store);
|
|
654
|
-
const n = makeNode(freshId(s, idHint),
|
|
737
|
+
const n = makeNode(s, freshId(s, idHint), expr);
|
|
655
738
|
insertNode(s, n);
|
|
656
739
|
flushUnlessBatched(s);
|
|
657
740
|
return n.id;
|
|
@@ -660,14 +743,14 @@ const addNode = (store, expr, idHint) => {
|
|
|
660
743
|
* This function sets the expression of a node. This is the fundamental write.
|
|
661
744
|
* The engine rewires the reads of the node, evaluates it and propagates the change.
|
|
662
745
|
* A write to a container collapses it first: its own slots are removed, and the expression replaces the record.
|
|
663
|
-
* The
|
|
746
|
+
* The kernel compiles the new expression one time.
|
|
664
747
|
*/
|
|
665
748
|
const setExpr = (store, id, expr) => {
|
|
666
749
|
const s = state(store);
|
|
667
750
|
const n = s.nodes.get(id);
|
|
668
751
|
if (n) {
|
|
669
752
|
if (n.slots.size > 0) collapse(s, n);
|
|
670
|
-
replaceExpr(s, n,
|
|
753
|
+
replaceExpr(s, n, expr);
|
|
671
754
|
touch(s, id);
|
|
672
755
|
}
|
|
673
756
|
flushUnlessBatched(s);
|
|
@@ -843,6 +926,6 @@ const toposort = (store) => {
|
|
|
843
926
|
return order;
|
|
844
927
|
};
|
|
845
928
|
//#endregion
|
|
846
|
-
export { MAX_CYCLE_ROUNDS, addNode, batch, defaultDeref, defaultFlow, defaultOps, defaultSplash, defaultTargets, expandNode, fillMany, forkStore, getNode, isPlainObject, nodeStore, readTargets, readValue, removeNode, resolveAll, setExpr, setSlot, setValue, resolverOf as storeResolver, toposort, valueEquals };
|
|
929
|
+
export { MAX_CYCLE_ROUNDS, addNode, batch, defaultDeref, defaultFlow, defaultOps, defaultSplash, defaultTargets, expandNode, fillMany, forkStore, getNode, isPlainObject, nodeStore, readTargets, readValue, readsOf, removeNode, resolveAll, setExpr, setSlot, setValue, resolverOf as storeResolver, toposort, valueEquals };
|
|
847
930
|
|
|
848
931
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["hasOwn"],"sources":["../src/equality.ts","../src/ops.ts","../src/engine.ts","../src/writes.ts","../src/toposort.ts"],"sourcesContent":["/**\n * Structural equality for node values.\n *\n * With reference equality only, each expression that makes an object is always dirty: each evaluation\n * gives a new object, and each epoch propagates it. Node values are plain data, thus the default is structural:\n *\n * - `Object.is` for primitives, thus `NaN` is equal to `NaN`.\n * - Element by element for arrays, own key by own key for plain objects.\n * - An exotic object (a class instance, a function, a React element) is equal only to itself.\n * - Below `MAX_DEPTH` levels, two values are not equal. This limit also stops a cyclic value.\n */\n\nconst MAX_DEPTH = 32;\n\nconst hasOwn = (o: object, key: string): boolean => Object.prototype.hasOwnProperty.call(o, key);\n\n/** This function tells if a value is a plain object: its prototype is `Object.prototype` or `null`. */\nexport const isPlainObject = (v: unknown): v is Record<string, unknown> => {\n if (v === null || typeof v !== \"object\") return false;\n const proto: unknown = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n};\n\nconst eq = (a: unknown, b: unknown, depth: number): boolean => {\n if (Object.is(a, b)) return true;\n if (depth >= MAX_DEPTH) return false;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!eq(a[i], b[i], depth + 1)) return false;\n }\n return true;\n }\n\n if (!isPlainObject(a) || !isPlainObject(b)) return false;\n const keysA = Object.keys(a);\n if (keysA.length !== Object.keys(b).length) return false;\n for (const k of keysA) {\n if (!hasOwn(b, k) || !eq(a[k], b[k], depth + 1)) return false;\n }\n return true;\n};\n\n/** This function compares two node values structurally. The module comment gives the rules. */\nexport const valueEquals = (a: unknown, b: unknown): boolean => eq(a, b, 0);\n","import { some, none, isSome } from \"@mark1russell7/render-optional\";\nimport type { DerefFn, FlowFn, Node, NodeOps, SplashFn, TargetsFn } from \"./types.ts\";\nimport { valueEquals } from \"./equality.ts\";\n\nconst hasOwn = (o: object, key: string): boolean => Object.prototype.hasOwnProperty.call(o, key);\n\n/**\n * The default write. It keeps the old value when the new result is structurally equal, thus an equal\n * object keeps its old reference. A change from a value to `none`, or from `none` to a value, is a change.\n */\nexport const defaultSplash: SplashFn = (value, target) => {\n const prev = target.value;\n if (isSome(prev) ? isSome(value) && valueEquals(prev.value, value.value) : !isSome(value)) return false;\n target.value = value;\n return true;\n};\n\n/** The default propagation. A change of a node makes its value readers dirty. */\nexport const defaultFlow: FlowFn = (target) => target.seats;\n\n/**\n * The default resolution. It goes through slots as far as they exist, then through the own fields of the value.\n * A path that ends on a node gives the value of that node. For a container, this value is the record of its slots.\n * A path that stops at a container before its end reads a missing slot, thus it gives `none`.\n */\nexport const defaultDeref: DerefFn = (root, path, store) => {\n let current = root;\n let i = 0;\n while (i < path.length) {\n const slotId = current.slots.get(path[i]!);\n if (slotId === undefined) break;\n const next = store.nodes.get(slotId);\n if (!next) break;\n current = next;\n i++;\n }\n if (i < path.length && current.slots.size > 0) return none;\n if (!isSome(current.value)) return none;\n let value: unknown = current.value.value;\n for (; i < path.length; i++) {\n const segment = path[i]!;\n if (value === null || typeof value !== \"object\" || !hasOwn(value, segment)) return none;\n value = (value as Record<string, unknown>)[segment];\n }\n return some(value);\n};\n\n/**\n * The default dependencies of a path, which agree with `defaultDeref`. The nodes that the path goes through are\n * structural targets, and the node where it ends is the terminal. A path that stops at a container before its end\n * reads a missing slot: it has no terminal, and the container is a structural target.\n */\nexport const defaultTargets: TargetsFn = (root, path, store) => {\n const through: Node[] = [];\n let current = root;\n let i = 0;\n for (; i < path.length; i++) {\n const slotId = current.slots.get(path[i]!);\n const next = slotId === undefined ? undefined : store.nodes.get(slotId);\n if (!next) break;\n through.push(current);\n current = next;\n }\n if (i < path.length && current.slots.size > 0) return { through: [...through, current], terminal: undefined };\n return { through, terminal: current };\n};\n\n/** The default semantics of a store. */\nexport const defaultOps: NodeOps = {\n splash: defaultSplash,\n flow: defaultFlow,\n deref: defaultDeref,\n targets: defaultTargets,\n};\n","/**\n * The internal state of a store and the epoch machine. The public operations are in `writes.ts`.\n *\n * Each write changes the structure at once and marks the nodes that it touched. A flush then rewires\n * the touched nodes and starts one epoch over them. Outside a batch, each write flushes at its end.\n */\nimport type { DepPath, Expr, Ops, Resolver } from \"@mark1russell7/render-dsl\";\nimport { deps, evaluate, lit, ref } from \"@mark1russell7/render-dsl\";\nimport type { Optional } from \"@mark1russell7/render-optional\";\nimport { isSome, none, some } from \"@mark1russell7/render-optional\";\nimport type { EpochStats, Node, NodeId, NodeOps, NodeStore } from \"./types.ts\";\nimport { isPlainObject } from \"./equality.ts\";\n\n/** The mutable form of a node. Only the engine sees it. */\nexport type MutableNode = {\n readonly id: NodeId;\n expr: Expr;\n value: Optional<unknown>;\n reads: readonly DepPath[];\n readonly seats: Set<NodeId>;\n readonly seatsStructural: Set<NodeId>;\n readonly seatedOn: Set<NodeId>;\n parent: NodeId | undefined;\n readonly heldBy: Set<NodeId>;\n readonly slots: Map<string, NodeId>;\n /** True when a read of this node waits in `dangling`. */\n dangles: boolean;\n // The scratch fields of the epoch machine. They are valid only when `mark` is the current epoch.\n mark: number;\n dirty: number;\n index: number;\n low: number;\n onStack: boolean;\n next: MutableNode[] | null;\n};\n\n/** The work that the next flush does. */\ntype Pending = {\n /** The nodes to rewire and to evaluate. */\n readonly touched: Set<NodeId>;\n /** The containers whose record expression to make again from their slots. */\n readonly records: Set<NodeId>;\n /** The nodes to expand after the epoch, because a batch asked for an expansion of a value that was not settled. */\n readonly expansions: Set<NodeId>;\n};\n\n/** The full state of a store. `nodeStore` makes it, and the public type `NodeStore` shows a part of it. */\nexport type StoreState = {\n readonly nodes: Map<NodeId, MutableNode>;\n readonly nodeOps: NodeOps;\n readonly ops: Ops;\n epochStats: EpochStats | null;\n nextId: number;\n /** For each node ID that is not in the store: the readers whose path starts at it. */\n readonly dangling: Map<NodeId, Set<NodeId>>;\n pending: Pending | null;\n batchDepth: number;\n /** The counter of epochs. The scratch fields of a node use it as a stamp. */\n epoch: number;\n};\n\n/** This function gives the internal state of a store. All stores come from `makeState`. */\nexport const state = (store: NodeStore): StoreState => store as unknown as StoreState;\n\n/** This function gives the public view of a state. */\nexport const publicStore = (s: StoreState): NodeStore => s;\n\n/** This function makes the state of an empty store. */\nexport const makeState = (nodeOps: NodeOps, ops: Ops): StoreState => ({\n nodes: new Map(),\n nodeOps,\n ops,\n epochStats: null,\n nextId: 0,\n dangling: new Map(),\n pending: null,\n batchDepth: 0,\n epoch: 0,\n});\n\n/** This function makes a node that is not in a store yet. */\nexport const makeNode = (id: NodeId, expr: Expr): MutableNode => ({\n id,\n expr,\n value: none,\n reads: deps(expr),\n seats: new Set(),\n seatsStructural: new Set(),\n seatedOn: new Set(),\n parent: undefined,\n heldBy: new Set(),\n slots: new Map(),\n dangles: false,\n mark: 0,\n dirty: 0,\n index: -1,\n low: -1,\n onStack: false,\n next: null,\n});\n\n/**\n * This function gives an ID that no node of the store has. It uses the hint when the hint is free.\n * Without a hint, the IDs have the form `n_<number>`, with a counter for each store.\n */\nexport const freshId = (s: StoreState, hint?: string): NodeId => {\n if (hint !== undefined && !s.nodes.has(hint)) return hint;\n const base = hint ?? \"n\";\n for (;;) {\n const id = `${base}${hint === undefined ? \"_\" : \"~\"}${String(s.nextId++)}`;\n if (!s.nodes.has(id)) return id;\n }\n};\n\n/** This function makes the `record` expression of a container: one field for each slot. */\nexport const recordOf = (n: MutableNode): Expr => {\n const args: Expr[] = [];\n for (const name of n.slots.keys()) args.push(lit(name), ref(n.id, name));\n return { tag: \"app\", op: \"record\", args };\n};\n\n/** This function tells if the expression of a container is the record of its current slots. */\nconst isRecordOf = (n: MutableNode): boolean => {\n const e = n.expr;\n if (e.tag !== \"app\" || e.op !== \"record\" || e.args.length !== 2 * n.slots.size) return false;\n let i = 0;\n for (const name of n.slots.keys()) {\n const k = e.args[i++]!;\n const r = e.args[i++]!;\n if (k.tag !== \"lit\" || k.value !== name) return false;\n if (r.tag !== \"ref\" || r.path.length !== 2 || r.path[0] !== n.id || r.path[1] !== name) return false;\n }\n return true;\n};\n\n// === Slots ===\n\n/** This function points a slot at a node and records the container in `heldBy` of the node. */\nexport const linkSlot = (container: MutableNode, name: string, child: MutableNode): void => {\n container.slots.set(name, child.id);\n child.heldBy.add(container.id);\n};\n\n/** This function removes a slot. The target forgets the container when no other slot of the container points at it. */\nexport const unlinkSlot = (s: StoreState, container: MutableNode, name: string): void => {\n const target = container.slots.get(name);\n container.slots.delete(name);\n if (target === undefined) return;\n for (const other of container.slots.values()) if (other === target) return;\n s.nodes.get(target)?.heldBy.delete(container.id);\n};\n\n// === Wiring ===\n\n/**\n * This function gives the chain of nodes that a read path goes through. The chain starts at the root\n * (`path[0]`, a node ID) and follows slots as far as they exist. The last node is the terminal: the path\n * reads its value. The function gives an empty chain when the root is not in the store.\n */\nexport const readTargets = (nodes: ReadonlyMap<NodeId, Node>, path: DepPath): Node[] => {\n const rootId = path[0];\n const root = rootId === undefined ? undefined : nodes.get(rootId);\n if (!root) return [];\n const out: Node[] = [root];\n let current = root;\n for (let i = 1; i < path.length; i++) {\n const slotId = current.slots.get(path[i]!);\n const next = slotId === undefined ? undefined : nodes.get(slotId);\n if (!next) break;\n current = next;\n out.push(current);\n }\n return out;\n};\n\n/**\n * This function seats a node on the targets of its reads (the `targets` op of the store). The terminal gets a\n * value seat, and each node that the path goes through gets a structural seat. A read whose root is not in the\n * store waits in `dangling`. A missing slot of a container has no terminal. Thus a container cannot be on a\n * false cycle with a reader of one of its missing slots.\n */\nexport const wireNode = (s: StoreState, n: MutableNode): void => {\n for (const path of n.reads) {\n const rootId = path[0];\n const root = rootId === undefined ? undefined : s.nodes.get(rootId);\n if (!root) {\n if (rootId !== undefined) {\n let waiting = s.dangling.get(rootId);\n if (!waiting) s.dangling.set(rootId, (waiting = new Set()));\n waiting.add(n.id);\n n.dangles = true;\n }\n continue;\n }\n const targets = s.nodeOps.targets(root, path.slice(1), publicStore(s));\n for (const t of targets.through) {\n const m = s.nodes.get(t.id);\n if (!m || m === n) continue;\n m.seatsStructural.add(n.id);\n n.seatedOn.add(m.id);\n }\n const terminal = targets.terminal === undefined ? undefined : s.nodes.get(targets.terminal.id);\n if (terminal && terminal !== n) {\n terminal.seats.add(n.id);\n n.seatedOn.add(terminal.id);\n }\n }\n};\n\n/** This function removes a node from each seat set that holds it, and from `dangling`. */\nexport const unwireNode = (s: StoreState, n: MutableNode): void => {\n if (n.seatedOn.size > 0) {\n for (const otherId of n.seatedOn) {\n const other = s.nodes.get(otherId);\n if (other) {\n other.seats.delete(n.id);\n other.seatsStructural.delete(n.id);\n }\n }\n n.seatedOn.clear();\n }\n if (n.dangles) {\n for (const path of n.reads) {\n const head = path[0];\n const waiting = head === undefined ? undefined : s.dangling.get(head);\n if (waiting) {\n waiting.delete(n.id);\n if (waiting.size === 0) s.dangling.delete(head!);\n }\n }\n n.dangles = false;\n }\n};\n\n/** This function changes the expression of a node and keeps its reads correct. The caller touches the node. */\nexport const replaceExpr = (s: StoreState, n: MutableNode, expr: Expr): void => {\n unwireNode(s, n);\n n.expr = expr;\n n.reads = deps(expr);\n};\n\n// === Pending work, batches and the flush ===\n\nconst pending = (s: StoreState): Pending =>\n (s.pending ??= { touched: new Set(), records: new Set(), expansions: new Set() });\n\n/** This function marks a node for the next flush: rewire and evaluate. */\nexport const touch = (s: StoreState, id: NodeId): void => {\n pending(s).touched.add(id);\n};\n\n/** This function marks a container: the next flush makes its record expression again. */\nexport const touchRecord = (s: StoreState, id: NodeId): void => {\n pending(s).records.add(id);\n};\n\n/** This function marks a node for an expansion after the next epoch, when its value is settled. */\nexport const touchExpansion = (s: StoreState, id: NodeId): void => {\n pending(s).expansions.add(id);\n};\n\n/** This function adds a node to the store. Readers that waited for its ID get rewired. */\nexport const insertNode = (s: StoreState, n: MutableNode): void => {\n s.nodes.set(n.id, n);\n touch(s, n.id);\n const waiting = s.dangling.get(n.id);\n if (waiting) {\n for (const r of waiting) touch(s, r);\n s.dangling.delete(n.id);\n }\n};\n\n/**\n * This function expands a node with a settled value. Each field of a plain object becomes an owned\n * slot, and a field that is a plain object becomes a container in turn. A field that holds one of its own\n * ancestors stays a leaf, thus a cyclic object gives a finite tree. The caller flushes.\n * The build uses its own stack, not the call stack, thus a deep value does not overflow it.\n */\nexport const expandNow = (s: StoreState, id: NodeId): void => {\n const n = s.nodes.get(id);\n const value = n && isSome(n.value) ? n.value.value : undefined;\n if (!n || n.slots.size > 0 || !isPlainObject(value)) return;\n type Frame = { readonly owner: MutableNode; readonly value: object; readonly fields: [string, unknown][]; next: number };\n // The objects on the path from the root to the current field\n const ancestors = new Set<object>([value]);\n const frames: Frame[] = [{ owner: n, value, fields: Object.entries(value), next: 0 }];\n for (let frame = frames.at(-1); frame !== undefined; frame = frames.at(-1)) {\n const field = frame.fields[frame.next++];\n if (field === undefined) {\n frames.pop();\n ancestors.delete(frame.value);\n // A container gets its record after its fields, thus the records go from the leaves to the root\n if (frame.owner !== n) touchRecord(s, frame.owner.id);\n continue;\n }\n const [key, fieldValue] = field;\n const sub = makeNode(freshId(s, `${frame.owner.id}.${key}`), lit(fieldValue));\n sub.parent = frame.owner.id;\n sub.value = some(fieldValue);\n insertNode(s, sub);\n linkSlot(frame.owner, key, sub);\n if (isPlainObject(fieldValue) && !ancestors.has(fieldValue)) {\n ancestors.add(fieldValue);\n frames.push({ owner: sub, value: fieldValue, fields: Object.entries(fieldValue), next: 0 });\n }\n }\n touchRecord(s, n.id);\n for (const r of n.seats) touch(s, r);\n for (const r of n.seatsStructural) touch(s, r);\n};\n\n/** This function does the pending work at once, except inside a batch. */\nexport const flushUnlessBatched = (s: StoreState): void => {\n if (s.batchDepth === 0) flush(s);\n};\n\n/**\n * This function makes `fn` one transaction: the pending work of all its writes becomes one epoch at the end.\n * When `fn` throws, the flush still does the pending work, and the error of `fn` is the error of the batch.\n */\nexport const runBatch = <T>(s: StoreState, fn: () => T): T => {\n s.batchDepth++;\n let result: T;\n try {\n result = fn();\n } catch (error) {\n s.batchDepth--;\n if (s.batchDepth === 0) {\n try {\n flush(s);\n } catch {\n // The error of fn is the cause. An error of the flush comes after it, thus the batch keeps the first one.\n }\n }\n throw error;\n }\n s.batchDepth--;\n if (s.batchDepth === 0) flush(s);\n return result;\n};\n\n/** The limit of follow-up epochs in one flush. Only a custom op that writes on each epoch reaches it. */\nconst MAX_FOLLOW_UPS = 1000;\n\n/** The error of an op during an epoch, with the dirty nodes that the epoch did not evaluate. */\nclass EpochFailure {\n readonly retry: ReadonlySet<NodeId>;\n readonly cause: unknown;\n constructor(retry: ReadonlySet<NodeId>, cause: unknown) {\n this.retry = retry;\n this.cause = cause;\n }\n}\n\n/**\n * This function does the pending work. A custom op can write during an epoch. That write does not go into\n * the epoch: it becomes pending, and the flush does it in a follow-up epoch. The stats join all epochs.\n *\n * When an op throws, the dirty nodes that the epoch did not evaluate stay pending. The next flush evaluates them,\n * and the error goes to the caller. A throw before the evaluation, for example of a custom `targets` or `flow`\n * op, keeps all the work of the round pending.\n */\nconst flush = (s: StoreState): void => {\n let stats: { evaluated: Set<NodeId>; changed: Set<NodeId>; cyclic: Set<NodeId> } | null = null;\n s.batchDepth++;\n try {\n for (let round = 0; s.pending; round++) {\n if (round > MAX_FOLLOW_UPS) {\n s.pending = null;\n throw new Error(`The epochs did not settle after ${String(MAX_FOLLOW_UPS)} follow-ups: an op writes on each epoch.`);\n }\n const p = s.pending;\n s.pending = null;\n let epoch: { evaluated: Set<NodeId>; changed: Set<NodeId>; cyclic: Set<NodeId> };\n try {\n for (const id of p.records) {\n const n = s.nodes.get(id);\n if (!n) continue;\n if (!isRecordOf(n)) replaceExpr(s, n, recordOf(n));\n p.touched.add(id);\n }\n for (const id of p.touched) {\n const n = s.nodes.get(id);\n if (!n) continue;\n unwireNode(s, n);\n wireNode(s, n);\n }\n epoch = runEpoch(s, p.touched);\n } catch (failure) {\n if (failure instanceof EpochFailure) {\n for (const id of failure.retry) touch(s, id);\n for (const id of p.expansions) touchExpansion(s, id);\n throw failure.cause;\n }\n // The wiring or the closure failed, thus no node evaluated: the full round stays pending\n for (const id of p.records) touchRecord(s, id);\n for (const id of p.touched) touch(s, id);\n for (const id of p.expansions) touchExpansion(s, id);\n throw failure;\n }\n if (!stats) stats = epoch;\n else {\n for (const id of epoch.evaluated) stats.evaluated.add(id);\n for (const id of epoch.changed) stats.changed.add(id);\n for (const id of epoch.cyclic) stats.cyclic.add(id);\n }\n // An expansion inside a batch waits for the settled value. It makes new pending work: a follow-up epoch.\n for (const id of p.expansions) expandNow(s, id);\n }\n } finally {\n s.batchDepth--;\n }\n s.epochStats = {\n evaluated: stats?.evaluated ?? new Set(),\n changed: stats?.changed ?? new Set(),\n cyclic: stats?.cyclic ?? new Set(),\n total: s.nodes.size,\n };\n};\n\n// === The epoch ===\n\n/** This function makes the resolver that the expressions of a store use: `path[0]` is a node ID. */\nexport const resolverOf = (store: NodeStore): Resolver => (path) => {\n const rootId = path[0];\n const root = rootId === undefined ? undefined : store.nodes.get(rootId);\n return root ? store.nodeOps.deref(root, path.length === 1 ? [] : path.slice(1), store) : none;\n};\n\n/**\n * The limit of the rounds of a cycle in one epoch. Like the iterative calculation of a spreadsheet, the members\n * of a cycle evaluate again until their values are stable, or until this limit.\n */\nexport const MAX_CYCLE_ROUNDS = 100;\n\n/**\n * This function gives the strongly connected components of the closure (Tarjan, without recursion).\n * Tarjan gives each component after all components that it reaches. Thus the reverse list is a topological\n * order of the components, and each node comes after its inputs.\n */\nconst components = (closure: readonly MutableNode[]): MutableNode[][] => {\n const out: MutableNode[][] = [];\n const stack: MutableNode[] = [];\n let counter = 0;\n for (const start of closure) {\n if (start.index !== -1) continue;\n const frames: { readonly node: MutableNode; next: number }[] = [];\n const open = (v: MutableNode): void => {\n v.index = v.low = counter++;\n v.onStack = true;\n stack.push(v);\n frames.push({ node: v, next: 0 });\n };\n open(start);\n while (frames.length > 0) {\n const frame = frames[frames.length - 1]!;\n const v = frame.node;\n const successors = v.next!;\n if (frame.next < successors.length) {\n const w = successors[frame.next++]!;\n if (w.index === -1) open(w);\n else if (w.onStack) v.low = Math.min(v.low, w.index);\n continue;\n }\n frames.pop();\n const parent = frames[frames.length - 1];\n if (parent) parent.node.low = Math.min(parent.node.low, v.low);\n if (v.low === v.index) {\n const component: MutableNode[] = [];\n let w: MutableNode;\n do {\n w = stack.pop()!;\n w.onStack = false;\n component.push(w);\n } while (w !== v);\n out.push(component);\n }\n }\n }\n return out.toReversed();\n};\n\n/**\n * One epoch. The engine finds the closure of the frontier through `flow`, and it orders the strongly connected\n * components of the closure topologically. Then it evaluates each dirty node in that order, thus each node\n * reads settled values. A node is dirty when it is in the frontier or when one of its inputs changed.\n *\n * A node outside a cycle evaluates at most one time. The members of a cycle (a component with more than one\n * node) evaluate again while one of them changes, up to `MAX_CYCLE_ROUNDS` rounds. The stats report them.\n * The machine keeps its state in the scratch fields of the nodes, with the epoch number as a stamp.\n */\nconst runEpoch = (\n s: StoreState,\n frontier: ReadonlySet<NodeId>,\n): { evaluated: Set<NodeId>; changed: Set<NodeId>; cyclic: Set<NodeId> } => {\n const store = publicStore(s);\n const epoch = ++s.epoch;\n\n // 1. The closure: the nodes that the frontier can reach. Each node gets its successors one time.\n const closure: MutableNode[] = [];\n const stack: MutableNode[] = [];\n const enter = (n: MutableNode): void => {\n n.mark = epoch;\n n.index = -1;\n n.low = -1;\n n.onStack = false;\n n.next = null;\n stack.push(n);\n };\n for (const id of frontier) {\n const n = s.nodes.get(id);\n if (n && n.mark !== epoch) {\n enter(n);\n n.dirty = epoch;\n }\n }\n while (stack.length > 0) {\n const n = stack.pop()!;\n closure.push(n);\n const next: MutableNode[] = [];\n for (const id of s.nodeOps.flow(n, store)) {\n const m = s.nodes.get(id);\n if (!m || m === n) continue;\n next.push(m);\n if (m.mark !== epoch) enter(m);\n }\n n.next = next;\n }\n\n // 2. The order: the components, with each component after its inputs\n const order = components(closure);\n\n // 3. The evaluation of the dirty nodes, in order\n const resolver = resolverOf(store);\n const evaluated = new Set<NodeId>();\n const changed = new Set<NodeId>();\n const cyclic = new Set<NodeId>();\n const evaluateNode = (n: MutableNode): boolean => {\n n.dirty = 0;\n evaluated.add(n.id);\n if (!s.nodeOps.splash(evaluate(n.expr, resolver, s.ops), n, store)) return false;\n changed.add(n.id);\n for (const m of n.next!) m.dirty = epoch;\n return true;\n };\n let position = 0;\n try {\n for (; position < order.length; position++) {\n const component = order[position]!;\n if (component.length === 1) {\n const n = component[0]!;\n if (n.dirty === epoch) evaluateNode(n);\n continue;\n }\n for (const n of component) cyclic.add(n.id);\n for (let round = 0; round < MAX_CYCLE_ROUNDS; round++) {\n let any = false;\n for (const n of component) if (n.dirty === epoch && evaluateNode(n)) any = true;\n if (!any) break;\n }\n }\n } catch (cause) {\n const retry = new Set<NodeId>();\n for (let i = position; i < order.length; i++) {\n for (const n of order[i]!) if (n.dirty === epoch || i === position) retry.add(n.id);\n }\n for (const n of closure) n.next = null;\n throw new EpochFailure(retry, cause);\n }\n for (const n of closure) n.next = null;\n return { evaluated, changed, cyclic };\n};\n","/**\n * The public operations of a store. Each operation keeps the store consistent. At the end of the operation, or after\n * the end of the enclosing batch, the value of each node agrees with its expression.\n * The seats agree with the reads too. `store.epochStats` records the epoch of the last operation.\n */\nimport type { Expr, ExprDocument } from \"@mark1russell7/render-dsl\";\nimport { asExpr, lit } from \"@mark1russell7/render-dsl\";\nimport type { Optional } from \"@mark1russell7/render-optional\";\nimport { none } from \"@mark1russell7/render-optional\";\nimport type { Node, NodeId, NodeStore, StoreOptions } from \"./types.ts\";\nimport type { MutableNode, StoreState } from \"./engine.ts\";\nimport {\n expandNow, flushUnlessBatched, freshId, insertNode, linkSlot, makeNode, makeState, publicStore, replaceExpr,\n runBatch, state, touch, touchExpansion, touchRecord, unlinkSlot, unwireNode,\n} from \"./engine.ts\";\nimport { defaultOps } from \"./ops.ts\";\n\n/** This function makes an empty store. The options give its reactive semantics and its op registry. */\nexport const nodeStore = (options: StoreOptions = {}): NodeStore =>\n publicStore(makeState(options.nodeOps ?? defaultOps, options.ops ?? {}));\n\n/**\n * This function gives an independent copy of a store: the same nodes, values, slots and seats, and the same\n * counters. A write to one store does not change the other. The expressions and the values are immutable,\n * thus the copy shares them. The options replace the semantics or the op registry of the copy, for example the\n * class semantics of a copied biblo. The function throws inside a batch, because a batch has pending work.\n */\nexport const forkStore = (store: NodeStore, options: StoreOptions = {}): NodeStore => {\n const s = state(store);\n if (s.pending !== null || s.batchDepth > 0) throw new Error(\"forkStore: the store is in a batch\");\n const copy: StoreState = {\n nodes: new Map(),\n nodeOps: options.nodeOps ?? s.nodeOps,\n ops: options.ops ?? s.ops,\n epochStats: s.epochStats,\n nextId: s.nextId,\n dangling: new Map([...s.dangling].map(([id, readers]) => [id, new Set(readers)])),\n pending: null,\n batchDepth: 0,\n epoch: s.epoch,\n };\n for (const [id, n] of s.nodes) {\n const node: MutableNode = {\n ...n,\n seats: new Set(n.seats),\n seatsStructural: new Set(n.seatsStructural),\n seatedOn: new Set(n.seatedOn),\n heldBy: new Set(n.heldBy),\n slots: new Map(n.slots),\n // The scratch fields of the epoch machine start again: no epoch is in progress\n mark: 0,\n dirty: 0,\n index: -1,\n low: -1,\n onStack: false,\n next: null,\n };\n copy.nodes.set(id, node);\n }\n return publicStore(copy);\n};\n\n/** This function gives a node of the store, or `undefined`. */\nexport const getNode = (store: NodeStore, id: NodeId): Node | undefined => store.nodes.get(id);\n\n/** This function gives the value of a node, or `none` when the store has no such node. */\nexport const readValue = (store: NodeStore, id: NodeId): Optional<unknown> => store.nodes.get(id)?.value ?? none;\n\n/**\n * This function makes `fn` one transaction. The writes in `fn` change the structure at once, and one epoch\n * at the end evaluates all their effects. A batch inside a batch joins the outer batch.\n */\nexport const batch = <T>(store: NodeStore, fn: () => T): T => runBatch(state(store), fn);\n\n/**\n * This function adds a node with an expression and evaluates it. It gives the ID of the node.\n * The ID is the hint when no node has it, otherwise a fresh ID. A reader that waited for this ID gets rewired.\n * The expression can be a document (`ExprDocument`): the node keeps its tree of the IR (`asExpr`).\n */\nexport const addNode = (store: NodeStore, expr: Expr | ExprDocument, idHint?: string): NodeId => {\n const s = state(store);\n const n = makeNode(freshId(s, idHint), asExpr(expr));\n insertNode(s, n);\n flushUnlessBatched(s);\n return n.id;\n};\n\n/**\n * This function sets the expression of a node. This is the fundamental write.\n * The engine rewires the reads of the node, evaluates it and propagates the change.\n * A write to a container collapses it first: its own slots are removed, and the expression replaces the record.\n * The expression can be a document (`ExprDocument`): the node keeps its tree of the IR (`asExpr`).\n */\nexport const setExpr = (store: NodeStore, id: NodeId, expr: Expr | ExprDocument): void => {\n const s = state(store);\n const n = s.nodes.get(id);\n if (n) {\n if (n.slots.size > 0) collapse(s, n);\n replaceExpr(s, n, asExpr(expr));\n touch(s, id);\n }\n flushUnlessBatched(s);\n};\n\n/**\n * This function writes a literal value: it is `setExpr` with `lit(value)`.\n * A write to a derived node makes it an input node, like a value that a person types over a formula.\n */\nexport const setValue = (store: NodeStore, id: NodeId, value: unknown): void => {\n setExpr(store, id, lit(value));\n};\n\n/** This function writes many literal values in one batch, thus in one epoch. */\nexport const fillMany = (store: NodeStore, writes: Iterable<readonly [NodeId, unknown]>): void => {\n batch(store, () => {\n for (const [id, value] of writes) setValue(store, id, value);\n });\n};\n\n/** The options of `setSlot`. */\nexport type SlotOptions = {\n /**\n * With `own`, the container becomes the owner of the child when the child has no owner. The removal of the\n * owner removes the child, and so does a slot change that drops the child. Without `own`, the child is shared.\n */\n readonly own?: boolean;\n};\n\n/**\n * This function points a slot of a container at a node, or removes the slot when `childId` is `undefined`.\n * This is the structural write: the container gets a new record expression, and each reader whose path goes\n * through the container resolves its path again. A node that the container owns and drops is removed.\n * The function does nothing when a node is missing or when the child is the container itself.\n */\nexport const setSlot = (\n store: NodeStore,\n parentId: NodeId,\n name: string,\n childId: NodeId | undefined,\n options: SlotOptions = {},\n): void => {\n const s = state(store);\n const parent = s.nodes.get(parentId);\n const child = childId === undefined ? undefined : s.nodes.get(childId);\n if (parent && (childId === undefined || (child && childId !== parentId)) && parent.slots.get(name) !== childId) {\n const previous = parent.slots.get(name);\n if (previous !== undefined) unlinkSlot(s, parent, name);\n if (child) {\n linkSlot(parent, name, child);\n if (options.own === true && (child.parent === undefined || !s.nodes.has(child.parent))) child.parent = parent.id;\n }\n if (previous !== undefined) dropIfOwned(s, parent, previous);\n touchRecord(s, parent.id);\n for (const r of parent.seatsStructural) touch(s, r);\n for (const r of parent.seats) touch(s, r);\n }\n flushUnlessBatched(s);\n};\n\n/**\n * This function expands a node whose value is a plain object. Each field becomes a slot, and\n * a field that is a plain object becomes a container in turn. The node becomes a container, and its slots\n * are from then on the source of truth of its fields. A reader of a field gets a seat on the field node,\n * thus an edit of one field makes only its own readers dirty. An expansion of a derived node keeps the current fields.\n *\n * Inside a batch, the expansion waits for the epoch of the batch, thus it uses the settled value.\n * The function does nothing for a container or for a value that is not a plain object.\n */\nexport const expandNode = (store: NodeStore, id: NodeId): void => {\n const s = state(store);\n if (s.batchDepth > 0) touchExpansion(s, id);\n else expandNow(s, id);\n flushUnlessBatched(s);\n};\n\n/**\n * This function removes a node and the slots that it owns. The containers that hold it lose the slot.\n * Its readers resolve their paths again: a path to a removed node gives `none`. If a node with the same ID\n * comes back later, the readers find it.\n */\nexport const removeNode = (store: NodeStore, id: NodeId): void => {\n const s = state(store);\n const n = s.nodes.get(id);\n if (n) removeOwned(s, n);\n flushUnlessBatched(s);\n};\n\n/**\n * This function evaluates the full store again. It rewires each node, makes each record expression again,\n * and starts one epoch in which each node evaluates one time in topological order. The operations keep the\n * store consistent, thus this function is a repair tool. It does not revert a write.\n */\nexport const resolveAll = (store: NodeStore): void => {\n const s = state(store);\n for (const n of s.nodes.values()) {\n touch(s, n.id);\n if (n.slots.size > 0) touchRecord(s, n.id);\n }\n flushUnlessBatched(s);\n};\n\n// === Internal ===\n\n/** A node that the container owns and no longer holds is removed: it was a part of the container. */\nconst dropIfOwned = (s: StoreState, container: MutableNode, childId: NodeId): void => {\n const former = s.nodes.get(childId);\n if (!former || former.parent !== container.id) return;\n for (const target of container.slots.values()) if (target === childId) return;\n removeOwned(s, former);\n};\n\n/** A container gives up its slots: the owned slot nodes are removed, and the shared ones are only released. */\nconst collapse = (s: StoreState, n: MutableNode): void => {\n const slots = [...n.slots.values()];\n for (const name of Array.from(n.slots.keys())) unlinkSlot(s, n, name);\n for (const childId of slots) {\n const child = s.nodes.get(childId);\n if (child && child.parent === n.id) removeOwned(s, child);\n }\n for (const r of n.seatsStructural) touch(s, r);\n};\n\n/** This function removes a node and its owned slot subtree, and touches each reader that stays. */\nconst removeOwned = (s: StoreState, root: MutableNode): void => {\n const doomed: MutableNode[] = [];\n const doomedIds = new Set<NodeId>();\n // The owned subtree, with a stack of its own: a deep tree does not overflow the call stack\n for (const todo = [root]; todo.length > 0;) {\n const n = todo.pop();\n if (n === undefined || doomedIds.has(n.id)) continue;\n doomedIds.add(n.id);\n doomed.push(n);\n for (const childId of n.slots.values()) {\n const child = s.nodes.get(childId);\n if (child && child.parent === n.id) todo.push(child);\n }\n }\n\n // The owner and each container that shares a removed node lose the slot\n for (const n of doomed) {\n for (const r of n.seatsStructural) touch(s, r);\n for (const r of n.seats) touch(s, r);\n for (const holderId of Array.from(n.heldBy)) {\n const holder = doomedIds.has(holderId) ? undefined : s.nodes.get(holderId);\n if (!holder) continue;\n for (const [name, target] of Array.from(holder.slots)) {\n if (target === n.id) unlinkSlot(s, holder, name);\n }\n touchRecord(s, holder.id);\n for (const r of holder.seatsStructural) touch(s, r);\n }\n }\n // A removed container no longer holds the nodes that it shares\n for (const n of doomed) {\n for (const target of n.slots.values()) if (!doomedIds.has(target)) s.nodes.get(target)?.heldBy.delete(n.id);\n }\n\n for (const n of doomed) {\n for (const readerId of n.seats) s.nodes.get(readerId)?.seatedOn.delete(n.id);\n for (const readerId of n.seatsStructural) s.nodes.get(readerId)?.seatedOn.delete(n.id);\n n.seats.clear();\n n.seatsStructural.clear();\n }\n for (const n of doomed) {\n unwireNode(s, n);\n s.nodes.delete(n.id);\n }\n};\n","import type { NodeId, NodeStore } from \"./types.ts\";\n\n\n/**\n * This function sorts the nodes of a store by their static dependencies (Kahn's algorithm).\n *\n * The edges come from the `targets` op of the store, like the seat wiring.\n * A reader depends on the terminal of each path and on each node that the path goes through.\n * A path to a node that is not in the store adds no edge.\n *\n * The result has the dependencies before their readers. A node on a cycle of two or more nodes is not in the\n * result. A reference of a node to itself adds no edge. The epochs of the engine use the edges of `flow`\n * instead, thus this function is a tool for analysis.\n */\nexport const toposort = (store: NodeStore): NodeId[] => {\n const dependedBy = new Map<NodeId, Set<NodeId>>();\n const inDegree = new Map<NodeId, number>();\n for (const id of store.nodes.keys()) {\n dependedBy.set(id, new Set());\n inDegree.set(id, 0);\n }\n for (const [id, n] of store.nodes) {\n for (const path of n.reads) {\n const root = path[0] === undefined ? undefined : store.nodes.get(path[0]);\n if (!root) continue;\n const { through, terminal } = store.nodeOps.targets(root, path.slice(1), store);\n for (const target of terminal === undefined ? through : [...through, terminal]) {\n const readers = dependedBy.get(target.id)!;\n if (target.id === id || readers.has(id)) continue;\n readers.add(id);\n inDegree.set(id, inDegree.get(id)! + 1);\n }\n }\n }\n\n const order: NodeId[] = [];\n for (const [id, d] of inDegree) if (d === 0) order.push(id);\n for (let head = 0; head < order.length; head++) {\n for (const readerId of dependedBy.get(order[head]!)!) {\n const d = inDegree.get(readerId)! - 1;\n inDegree.set(readerId, d);\n if (d === 0) order.push(readerId);\n }\n }\n return order;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["hasOwn"],"sources":["../src/equality.ts","../src/ops.ts","../src/engine.ts","../src/writes.ts","../src/toposort.ts"],"sourcesContent":["/**\n * Structural equality for node values.\n *\n * With reference equality only, each expression that makes an object is always dirty: each evaluation\n * gives a new object, and each epoch propagates it. Node values are plain data, thus the default is structural:\n *\n * - `Object.is` for primitives, thus `NaN` is equal to `NaN`.\n * - Element by element for arrays, own key by own key for plain objects.\n * - An exotic object (a class instance, a function, a React element) is equal only to itself.\n * - Below `MAX_DEPTH` levels, two values are not equal. This limit also stops a cyclic value.\n */\n\nconst MAX_DEPTH = 32;\n\nconst hasOwn = (o: object, key: string): boolean => Object.prototype.hasOwnProperty.call(o, key);\n\n/** This function tells if a value is a plain object: its prototype is `Object.prototype` or `null`. */\nexport const isPlainObject = (v: unknown): v is Record<string, unknown> => {\n if (v === null || typeof v !== \"object\") return false;\n const proto: unknown = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n};\n\nconst eq = (a: unknown, b: unknown, depth: number): boolean => {\n if (Object.is(a, b)) return true;\n if (depth >= MAX_DEPTH) return false;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!eq(a[i], b[i], depth + 1)) return false;\n }\n return true;\n }\n\n if (!isPlainObject(a) || !isPlainObject(b)) return false;\n const keysA = Object.keys(a);\n if (keysA.length !== Object.keys(b).length) return false;\n for (const k of keysA) {\n if (!hasOwn(b, k) || !eq(a[k], b[k], depth + 1)) return false;\n }\n return true;\n};\n\n/** This function compares two node values structurally. The module comment gives the rules. */\nexport const valueEquals = (a: unknown, b: unknown): boolean => eq(a, b, 0);\n","import { some, none, isSome } from \"@mark1russell7/render-optional\";\nimport type { DerefFn, FlowFn, Node, NodeOps, SplashFn, TargetsFn } from \"./types.ts\";\nimport { valueEquals } from \"./equality.ts\";\n\nconst hasOwn = (o: object, key: string): boolean => Object.prototype.hasOwnProperty.call(o, key);\n\n/**\n * The default write. It keeps the old value when the new result is structurally equal, thus an equal\n * object keeps its old reference. A change from a value to `none`, or from `none` to a value, is a change.\n */\nexport const defaultSplash: SplashFn = (value, target) => {\n const prev = target.value;\n if (isSome(prev) ? isSome(value) && valueEquals(prev.value, value.value) : !isSome(value)) return false;\n target.value = value;\n return true;\n};\n\n/** The default propagation. A change of a node makes its value readers dirty. */\nexport const defaultFlow: FlowFn = (target) => target.seats;\n\n/**\n * The default resolution. It goes through slots as far as they exist, then through the own fields of the value.\n * A path that ends on a node gives the value of that node. For a container, this value is the record of its slots.\n * A path that stops at a container before its end reads a missing slot, thus it gives `none`.\n */\nexport const defaultDeref: DerefFn = (root, path, store) => {\n let current = root;\n let i = 0;\n while (i < path.length) {\n const slotId = current.slots.get(path[i]!);\n if (slotId === undefined) break;\n const next = store.nodes.get(slotId);\n if (!next) break;\n current = next;\n i++;\n }\n if (i < path.length && current.slots.size > 0) return none;\n if (!isSome(current.value)) return none;\n let value: unknown = current.value.value;\n for (; i < path.length; i++) {\n const segment = path[i]!;\n if (value === null || typeof value !== \"object\" || !hasOwn(value, segment)) return none;\n value = (value as Record<string, unknown>)[segment];\n }\n return some(value);\n};\n\n/**\n * The default dependencies of a path, which agree with `defaultDeref`. The nodes that the path goes through are\n * structural targets, and the node where it ends is the terminal. A path that stops at a container before its end\n * reads a missing slot: it has no terminal, and the container is a structural target.\n */\nexport const defaultTargets: TargetsFn = (root, path, store) => {\n const through: Node[] = [];\n let current = root;\n let i = 0;\n for (; i < path.length; i++) {\n const slotId = current.slots.get(path[i]!);\n const next = slotId === undefined ? undefined : store.nodes.get(slotId);\n if (!next) break;\n through.push(current);\n current = next;\n }\n if (i < path.length && current.slots.size > 0) return { through: [...through, current], terminal: undefined };\n return { through, terminal: current };\n};\n\n/** The default semantics of a store. */\nexport const defaultOps: NodeOps = {\n splash: defaultSplash,\n flow: defaultFlow,\n deref: defaultDeref,\n targets: defaultTargets,\n};\n","/**\n * The internal state of a store and the epoch machine. The public operations are in `writes.ts`.\n *\n * Each write changes the structure at once and marks the nodes that it touched. A flush then rewires\n * the touched nodes and starts one epoch over them. Outside a batch, each write flushes at its end.\n */\nimport type { Context, HostThing, Json, Key, Plan } from \"@mark1russell7/coeval\";\nimport { THING, context, isRecord, prepare, refsOf } from \"@mark1russell7/coeval\";\nimport type { Expr, Ops, Resolver } from \"@mark1russell7/render-dsl\";\nimport { NO_VALUE, isLiteral, lit, machineOf, renderWorld } from \"@mark1russell7/render-dsl\";\nimport type { Optional } from \"@mark1russell7/render-optional\";\nimport { isSome, none, some } from \"@mark1russell7/render-optional\";\nimport type { DepPath, EpochStats, Node, NodeId, NodeOps, NodeStore } from \"./types.ts\";\nimport { isPlainObject } from \"./equality.ts\";\n\n/** The mutable form of a node. Only the engine sees it. */\nexport type MutableNode = {\n readonly id: NodeId;\n expr: Expr;\n /** The plan of `expr`, which the kernel compiled one time. Each epoch evaluates it. */\n plan: Plan;\n value: Optional<unknown>;\n reads: readonly DepPath[];\n readonly seats: Set<NodeId>;\n readonly seatsStructural: Set<NodeId>;\n readonly seatedOn: Set<NodeId>;\n parent: NodeId | undefined;\n readonly heldBy: Set<NodeId>;\n readonly slots: Map<string, NodeId>;\n /** True when a read of this node waits in `dangling`. */\n dangles: boolean;\n // The scratch fields of the epoch machine. They are valid only when `mark` is the current epoch.\n mark: number;\n dirty: number;\n index: number;\n low: number;\n onStack: boolean;\n next: MutableNode[] | null;\n};\n\n/** The work that the next flush does. */\ntype Pending = {\n /** The nodes to rewire and to evaluate. */\n readonly touched: Set<NodeId>;\n /** The containers whose record expression to make again from their slots. */\n readonly records: Set<NodeId>;\n /** The nodes to expand after the epoch, because a batch asked for an expansion of a value that was not settled. */\n readonly expansions: Set<NodeId>;\n};\n\n/** The full state of a store. `nodeStore` makes it, and the public type `NodeStore` shows a part of it. */\nexport type StoreState = {\n readonly nodes: Map<NodeId, MutableNode>;\n readonly nodeOps: NodeOps;\n readonly ops: Ops;\n /** This function compiles an expression with the machine of the op registry. Each write compiles its new expression one time. */\n readonly plan: (doc: unknown) => Plan;\n /** The context of each evaluation: the store is the host thing of the root `world`. */\n ctx: Context;\n epochStats: EpochStats | null;\n nextId: number;\n /** For each node ID that is not in the store: the readers whose path starts at it. */\n readonly dangling: Map<NodeId, Set<NodeId>>;\n pending: Pending | null;\n batchDepth: number;\n /** The counter of epochs. The scratch fields of a node use it as a stamp. */\n epoch: number;\n};\n\n/** This function gives the internal state of a store. All stores come from `makeState`. */\nexport const state = (store: NodeStore): StoreState => store as unknown as StoreState;\n\n/** This function gives the public view of a state. */\nexport const publicStore = (s: StoreState): NodeStore => s;\n\n/** This function gives the keys of a path as texts. A document from another source can have a number key. */\nconst texts = (keys: readonly Key[]): readonly string[] =>\n keys.every((k): k is string => typeof k === \"string\") ? keys : keys.map(String);\n\n/** This function reads a path of a store: `path[0]` is a node ID, and the `deref` op of the store reads the rest. */\nconst readPath = (s: StoreState, path: readonly Key[]): Optional<unknown> => {\n const rootId = path[0];\n const root = rootId === undefined ? undefined : s.nodes.get(String(rootId));\n return root ? s.nodeOps.deref(root, path.length === 1 ? [] : texts(path.slice(1)), publicStore(s)) : none;\n};\n\n/**\n * This function makes the host thing of a store: the root `world` of its expressions. The kernel gives it each\n * `world` path in one call (`resolve`). The store has no value of its own (`NO_VALUE`).\n */\nconst storeThing = (s: StoreState): HostThing => ({\n [THING]: true,\n label: \"node store\",\n deref: (key) => {\n const found = readPath(s, [key]);\n return isSome(found) ? found.value : undefined;\n },\n toJson: () => NO_VALUE,\n resolve: (keys) => {\n const found = readPath(s, keys);\n return isSome(found) ? { thing: found.value } : undefined;\n },\n});\n\n/** This function gives a state its context: the store is the root `world`. */\nexport const withContext = (s: StoreState): StoreState => {\n s.ctx = context({ world: storeThing(s) });\n return s;\n};\n\n/**\n * This function gives the compiler of an op registry. A store compiles each new expression one time, without a\n * cache. Most expressions of a store are new objects, for example the literal of each write.\n */\nexport const compilerOf = (ops: Ops): ((doc: unknown) => Plan) => {\n const machine = machineOf(ops);\n return (doc) => prepare(machine, doc);\n};\n\n/** This function makes the state of an empty store. */\nexport const makeState = (nodeOps: NodeOps, ops: Ops): StoreState =>\n withContext({\n nodes: new Map(),\n nodeOps,\n ops,\n plan: compilerOf(ops),\n ctx: context(),\n epochStats: null,\n nextId: 0,\n dangling: new Map(),\n pending: null,\n batchDepth: 0,\n epoch: 0,\n });\n\nconst NO_READS: readonly DepPath[] = Object.freeze([]);\n\n/**\n * This function gives the read paths of an expression: the `world` references that the kernel finds (`refsOf`).\n * The paths have no duplicates and keep the order of the first read. A parameter of a `fn` is not a read.\n */\nexport const readsOf = (expr: Expr): readonly DepPath[] => {\n if (isLiteral(expr)) return NO_READS;\n const out: DepPath[] = [];\n const seen = new Set<string>();\n for (const use of refsOf(renderWorld, expr)) {\n if (!use.free || use.root !== \"world\") continue;\n // The JSON form of a path is a key without ambiguity: [\"a b\"] and [\"a\", \"b\"] stay separate\n const path = use.path.slice(1).map(String);\n const key = JSON.stringify(path);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(path);\n }\n return out;\n};\n\n/** This function makes a node that is not in a store yet. The kernel compiles its expression one time. */\nexport const makeNode = (s: StoreState, id: NodeId, expr: Expr): MutableNode => ({\n id,\n expr,\n plan: s.plan(expr),\n value: none,\n reads: readsOf(expr),\n seats: new Set(),\n seatsStructural: new Set(),\n seatedOn: new Set(),\n parent: undefined,\n heldBy: new Set(),\n slots: new Map(),\n dangles: false,\n mark: 0,\n dirty: 0,\n index: -1,\n low: -1,\n onStack: false,\n next: null,\n});\n\n/**\n * This function gives an ID that no node of the store has. It uses the hint when the hint is free.\n * Without a hint, the IDs have the form `n_<number>`, with a counter for each store.\n */\nexport const freshId = (s: StoreState, hint?: string): NodeId => {\n if (hint !== undefined && !s.nodes.has(hint)) return hint;\n const base = hint ?? \"n\";\n for (;;) {\n const id = `${base}${hint === undefined ? \"_\" : \"~\"}${String(s.nextId++)}`;\n if (!s.nodes.has(id)) return id;\n }\n};\n\n/** This function makes the `record` construct of a container: one field for each slot, a `world` reference to the slot. */\nexport const recordOf = (n: MutableNode): Expr => {\n const fields: Record<string, Json> = {};\n for (const name of n.slots.keys()) {\n // An own data property, thus a slot \"__proto__\" stays a field\n Object.defineProperty(fields, name, { value: { type: \"ref\", value: [\"world\", n.id, name] }, enumerable: true, writable: true, configurable: true });\n }\n return { type: \"record\", value: fields };\n};\n\n/** This function tells if the expression of a container is the record of its current slots, in their order. */\nconst isRecordOf = (n: MutableNode): boolean => {\n const e = n.expr;\n if (e.type !== \"record\" || !isRecord(e.value)) return false;\n const fields = e.value;\n const names = Object.keys(fields);\n if (names.length !== n.slots.size) return false;\n let i = 0;\n for (const name of n.slots.keys()) {\n if (names[i++] !== name) return false;\n const r = fields[name] as { readonly type?: unknown; readonly value?: unknown } | null;\n const path = r?.type === \"ref\" && Array.isArray(r.value) ? (r.value as readonly unknown[]) : undefined;\n if (path?.length !== 3 || path[0] !== \"world\" || path[1] !== n.id || path[2] !== name) return false;\n }\n return true;\n};\n\n// === Slots ===\n\n/** This function points a slot at a node and records the container in `heldBy` of the node. */\nexport const linkSlot = (container: MutableNode, name: string, child: MutableNode): void => {\n container.slots.set(name, child.id);\n child.heldBy.add(container.id);\n};\n\n/** This function removes a slot. The target forgets the container when no other slot of the container points at it. */\nexport const unlinkSlot = (s: StoreState, container: MutableNode, name: string): void => {\n const target = container.slots.get(name);\n container.slots.delete(name);\n if (target === undefined) return;\n for (const other of container.slots.values()) if (other === target) return;\n s.nodes.get(target)?.heldBy.delete(container.id);\n};\n\n// === Wiring ===\n\n/**\n * This function gives the chain of nodes that a read path goes through. The chain starts at the root\n * (`path[0]`, a node ID) and follows slots as far as they exist. The last node is the terminal: the path\n * reads its value. The function gives an empty chain when the root is not in the store.\n */\nexport const readTargets = (nodes: ReadonlyMap<NodeId, Node>, path: DepPath): Node[] => {\n const rootId = path[0];\n const root = rootId === undefined ? undefined : nodes.get(rootId);\n if (!root) return [];\n const out: Node[] = [root];\n let current = root;\n for (let i = 1; i < path.length; i++) {\n const slotId = current.slots.get(path[i]!);\n const next = slotId === undefined ? undefined : nodes.get(slotId);\n if (!next) break;\n current = next;\n out.push(current);\n }\n return out;\n};\n\n/**\n * This function seats a node on the targets of its reads (the `targets` op of the store). The terminal gets a\n * value seat, and each node that the path goes through gets a structural seat. A read whose root is not in the\n * store waits in `dangling`. A missing slot of a container has no terminal. Thus a container cannot be on a\n * false cycle with a reader of one of its missing slots.\n */\nexport const wireNode = (s: StoreState, n: MutableNode): void => {\n for (const path of n.reads) {\n const rootId = path[0];\n const root = rootId === undefined ? undefined : s.nodes.get(rootId);\n if (!root) {\n if (rootId !== undefined) {\n let waiting = s.dangling.get(rootId);\n if (!waiting) s.dangling.set(rootId, (waiting = new Set()));\n waiting.add(n.id);\n n.dangles = true;\n }\n continue;\n }\n const targets = s.nodeOps.targets(root, path.slice(1), publicStore(s));\n for (const t of targets.through) {\n const m = s.nodes.get(t.id);\n if (!m || m === n) continue;\n m.seatsStructural.add(n.id);\n n.seatedOn.add(m.id);\n }\n const terminal = targets.terminal === undefined ? undefined : s.nodes.get(targets.terminal.id);\n if (terminal && terminal !== n) {\n terminal.seats.add(n.id);\n n.seatedOn.add(terminal.id);\n }\n }\n};\n\n/** This function removes a node from each seat set that holds it, and from `dangling`. */\nexport const unwireNode = (s: StoreState, n: MutableNode): void => {\n if (n.seatedOn.size > 0) {\n for (const otherId of n.seatedOn) {\n const other = s.nodes.get(otherId);\n if (other) {\n other.seats.delete(n.id);\n other.seatsStructural.delete(n.id);\n }\n }\n n.seatedOn.clear();\n }\n if (n.dangles) {\n for (const path of n.reads) {\n const head = path[0];\n const waiting = head === undefined ? undefined : s.dangling.get(head);\n if (waiting) {\n waiting.delete(n.id);\n if (waiting.size === 0) s.dangling.delete(head!);\n }\n }\n n.dangles = false;\n }\n};\n\n/** This function changes the expression of a node and keeps its reads correct. The caller touches the node. */\nexport const replaceExpr = (s: StoreState, n: MutableNode, expr: Expr, reads: readonly DepPath[] = readsOf(expr)): void => {\n unwireNode(s, n);\n n.expr = expr;\n n.plan = s.plan(expr);\n n.reads = reads;\n};\n\n/** This function gives the reads of the record of a container: one path to each slot. */\nconst recordReads = (n: MutableNode): readonly DepPath[] => Array.from(n.slots.keys(), (name) => [n.id, name]);\n\n// === Pending work, batches and the flush ===\n\nconst pending = (s: StoreState): Pending =>\n (s.pending ??= { touched: new Set(), records: new Set(), expansions: new Set() });\n\n/** This function marks a node for the next flush: rewire and evaluate. */\nexport const touch = (s: StoreState, id: NodeId): void => {\n pending(s).touched.add(id);\n};\n\n/** This function marks a container: the next flush makes its record expression again. */\nexport const touchRecord = (s: StoreState, id: NodeId): void => {\n pending(s).records.add(id);\n};\n\n/** This function marks a node for an expansion after the next epoch, when its value is settled. */\nexport const touchExpansion = (s: StoreState, id: NodeId): void => {\n pending(s).expansions.add(id);\n};\n\n/** This function adds a node to the store. Readers that waited for its ID get rewired. */\nexport const insertNode = (s: StoreState, n: MutableNode): void => {\n s.nodes.set(n.id, n);\n touch(s, n.id);\n const waiting = s.dangling.get(n.id);\n if (waiting) {\n for (const r of waiting) touch(s, r);\n s.dangling.delete(n.id);\n }\n};\n\n/**\n * This function expands a node with a settled value. Each field of a plain object becomes an owned\n * slot, and a field that is a plain object becomes a container in turn. A field that holds one of its own\n * ancestors stays a leaf, thus a cyclic object gives a finite tree. The caller flushes.\n * The build uses its own stack, not the call stack, thus a deep value does not overflow it.\n */\nexport const expandNow = (s: StoreState, id: NodeId): void => {\n const n = s.nodes.get(id);\n const value = n && isSome(n.value) ? n.value.value : undefined;\n if (!n || n.slots.size > 0 || !isPlainObject(value)) return;\n type Frame = { readonly owner: MutableNode; readonly value: object; readonly fields: [string, unknown][]; next: number };\n // The objects on the path from the root to the current field\n const ancestors = new Set<object>([value]);\n const frames: Frame[] = [{ owner: n, value, fields: Object.entries(value), next: 0 }];\n for (let frame = frames.at(-1); frame !== undefined; frame = frames.at(-1)) {\n const field = frame.fields[frame.next++];\n if (field === undefined) {\n frames.pop();\n ancestors.delete(frame.value);\n // A container gets its record after its fields, thus the records go from the leaves to the root\n if (frame.owner !== n) touchRecord(s, frame.owner.id);\n continue;\n }\n const [key, fieldValue] = field;\n const sub = makeNode(s, freshId(s, `${frame.owner.id}.${key}`), lit(fieldValue));\n sub.parent = frame.owner.id;\n sub.value = some(fieldValue);\n insertNode(s, sub);\n linkSlot(frame.owner, key, sub);\n if (isPlainObject(fieldValue) && !ancestors.has(fieldValue)) {\n ancestors.add(fieldValue);\n frames.push({ owner: sub, value: fieldValue, fields: Object.entries(fieldValue), next: 0 });\n }\n }\n touchRecord(s, n.id);\n for (const r of n.seats) touch(s, r);\n for (const r of n.seatsStructural) touch(s, r);\n};\n\n/** This function does the pending work at once, except inside a batch. */\nexport const flushUnlessBatched = (s: StoreState): void => {\n if (s.batchDepth === 0) flush(s);\n};\n\n/**\n * This function makes `fn` one transaction: the pending work of all its writes becomes one epoch at the end.\n * When `fn` throws, the flush still does the pending work, and the error of `fn` is the error of the batch.\n */\nexport const runBatch = <T>(s: StoreState, fn: () => T): T => {\n s.batchDepth++;\n let result: T;\n try {\n result = fn();\n } catch (error) {\n s.batchDepth--;\n if (s.batchDepth === 0) {\n try {\n flush(s);\n } catch {\n // The error of fn is the cause. An error of the flush comes after it, thus the batch keeps the first one.\n }\n }\n throw error;\n }\n s.batchDepth--;\n if (s.batchDepth === 0) flush(s);\n return result;\n};\n\n/** The limit of follow-up epochs in one flush. Only a custom op that writes on each epoch reaches it. */\nconst MAX_FOLLOW_UPS = 1000;\n\n/** The error of an op during an epoch, with the dirty nodes that the epoch did not evaluate. */\nclass EpochFailure {\n readonly retry: ReadonlySet<NodeId>;\n readonly cause: unknown;\n constructor(retry: ReadonlySet<NodeId>, cause: unknown) {\n this.retry = retry;\n this.cause = cause;\n }\n}\n\n/**\n * This function does the pending work. A custom op can write during an epoch. That write does not go into\n * the epoch: it becomes pending, and the flush does it in a follow-up epoch. The stats join all epochs.\n *\n * When an op throws, the dirty nodes that the epoch did not evaluate stay pending. The next flush evaluates them,\n * and the error goes to the caller. A throw before the evaluation, for example of a custom `targets` or `flow`\n * op, keeps all the work of the round pending.\n */\nconst flush = (s: StoreState): void => {\n let stats: { evaluated: Set<NodeId>; changed: Set<NodeId>; cyclic: Set<NodeId> } | null = null;\n s.batchDepth++;\n try {\n for (let round = 0; s.pending; round++) {\n if (round > MAX_FOLLOW_UPS) {\n s.pending = null;\n throw new Error(`The epochs did not settle after ${String(MAX_FOLLOW_UPS)} follow-ups: an op writes on each epoch.`);\n }\n const p = s.pending;\n s.pending = null;\n let epoch: { evaluated: Set<NodeId>; changed: Set<NodeId>; cyclic: Set<NodeId> };\n try {\n for (const id of p.records) {\n const n = s.nodes.get(id);\n if (!n) continue;\n if (!isRecordOf(n)) replaceExpr(s, n, recordOf(n), recordReads(n));\n p.touched.add(id);\n }\n for (const id of p.touched) {\n const n = s.nodes.get(id);\n if (!n) continue;\n unwireNode(s, n);\n wireNode(s, n);\n }\n epoch = runEpoch(s, p.touched);\n } catch (failure) {\n if (failure instanceof EpochFailure) {\n for (const id of failure.retry) touch(s, id);\n for (const id of p.expansions) touchExpansion(s, id);\n throw failure.cause;\n }\n // The wiring or the closure failed, thus no node evaluated: the full round stays pending\n for (const id of p.records) touchRecord(s, id);\n for (const id of p.touched) touch(s, id);\n for (const id of p.expansions) touchExpansion(s, id);\n throw failure;\n }\n if (!stats) stats = epoch;\n else {\n for (const id of epoch.evaluated) stats.evaluated.add(id);\n for (const id of epoch.changed) stats.changed.add(id);\n for (const id of epoch.cyclic) stats.cyclic.add(id);\n }\n // An expansion inside a batch waits for the settled value. It makes new pending work: a follow-up epoch.\n for (const id of p.expansions) expandNow(s, id);\n }\n } finally {\n s.batchDepth--;\n }\n s.epochStats = {\n evaluated: stats?.evaluated ?? new Set(),\n changed: stats?.changed ?? new Set(),\n cyclic: stats?.cyclic ?? new Set(),\n total: s.nodes.size,\n };\n};\n\n// === The epoch ===\n\n/**\n * This function makes the resolver of a store: `path[0]` is a node ID. The expressions of the store read the same\n * paths through the host thing of the store.\n */\nexport const resolverOf = (store: NodeStore): Resolver => (path) => readPath(state(store), path);\n\n/**\n * The limit of the rounds of a cycle in one epoch. Like the iterative calculation of a spreadsheet, the members\n * of a cycle evaluate again until their values are stable, or until this limit.\n */\nexport const MAX_CYCLE_ROUNDS = 100;\n\n/**\n * This function gives the strongly connected components of the closure (Tarjan, without recursion).\n * Tarjan gives each component after all components that it reaches. Thus the reverse list is a topological\n * order of the components, and each node comes after its inputs.\n */\nconst components = (closure: readonly MutableNode[]): MutableNode[][] => {\n const out: MutableNode[][] = [];\n const stack: MutableNode[] = [];\n let counter = 0;\n for (const start of closure) {\n if (start.index !== -1) continue;\n const frames: { readonly node: MutableNode; next: number }[] = [];\n const open = (v: MutableNode): void => {\n v.index = v.low = counter++;\n v.onStack = true;\n stack.push(v);\n frames.push({ node: v, next: 0 });\n };\n open(start);\n while (frames.length > 0) {\n const frame = frames[frames.length - 1]!;\n const v = frame.node;\n const successors = v.next!;\n if (frame.next < successors.length) {\n const w = successors[frame.next++]!;\n if (w.index === -1) open(w);\n else if (w.onStack) v.low = Math.min(v.low, w.index);\n continue;\n }\n frames.pop();\n const parent = frames[frames.length - 1];\n if (parent) parent.node.low = Math.min(parent.node.low, v.low);\n if (v.low === v.index) {\n const component: MutableNode[] = [];\n let w: MutableNode;\n do {\n w = stack.pop()!;\n w.onStack = false;\n component.push(w);\n } while (w !== v);\n out.push(component);\n }\n }\n }\n return out.toReversed();\n};\n\n/**\n * One epoch. The engine finds the closure of the frontier through `flow`, and it orders the strongly connected\n * components of the closure topologically. Then it evaluates each dirty node in that order, thus each node\n * reads settled values. A node is dirty when it is in the frontier or when one of its inputs changed.\n *\n * A node outside a cycle evaluates at most one time. The members of a cycle (a component with more than one\n * node) evaluate again while one of them changes, up to `MAX_CYCLE_ROUNDS` rounds. The stats report them.\n * The machine keeps its state in the scratch fields of the nodes, with the epoch number as a stamp.\n */\nconst runEpoch = (\n s: StoreState,\n frontier: ReadonlySet<NodeId>,\n): { evaluated: Set<NodeId>; changed: Set<NodeId>; cyclic: Set<NodeId> } => {\n const store = publicStore(s);\n const epoch = ++s.epoch;\n\n // 1. The closure: the nodes that the frontier can reach. Each node gets its successors one time.\n const closure: MutableNode[] = [];\n const stack: MutableNode[] = [];\n const enter = (n: MutableNode): void => {\n n.mark = epoch;\n n.index = -1;\n n.low = -1;\n n.onStack = false;\n n.next = null;\n stack.push(n);\n };\n for (const id of frontier) {\n const n = s.nodes.get(id);\n if (n && n.mark !== epoch) {\n enter(n);\n n.dirty = epoch;\n }\n }\n while (stack.length > 0) {\n const n = stack.pop()!;\n closure.push(n);\n const next: MutableNode[] = [];\n for (const id of s.nodeOps.flow(n, store)) {\n const m = s.nodes.get(id);\n if (!m || m === n) continue;\n next.push(m);\n if (m.mark !== epoch) enter(m);\n }\n n.next = next;\n }\n\n // 2. The order: the components, with each component after its inputs\n const order = components(closure);\n\n // 3. The evaluation of the dirty nodes, in order. The kernel evaluates the plan of each node.\n const ctx = s.ctx;\n const evaluated = new Set<NodeId>();\n const changed = new Set<NodeId>();\n const cyclic = new Set<NodeId>();\n const evaluateNode = (n: MutableNode): boolean => {\n n.dirty = 0;\n evaluated.add(n.id);\n const result = n.plan.evaluate(ctx);\n if (!s.nodeOps.splash(result.ok ? some(result.value) : none, n, store)) return false;\n changed.add(n.id);\n for (const m of n.next!) m.dirty = epoch;\n return true;\n };\n let position = 0;\n try {\n for (; position < order.length; position++) {\n const component = order[position]!;\n if (component.length === 1) {\n const n = component[0]!;\n if (n.dirty === epoch) evaluateNode(n);\n continue;\n }\n for (const n of component) cyclic.add(n.id);\n for (let round = 0; round < MAX_CYCLE_ROUNDS; round++) {\n let any = false;\n for (const n of component) if (n.dirty === epoch && evaluateNode(n)) any = true;\n if (!any) break;\n }\n }\n } catch (cause) {\n const retry = new Set<NodeId>();\n for (let i = position; i < order.length; i++) {\n for (const n of order[i]!) if (n.dirty === epoch || i === position) retry.add(n.id);\n }\n for (const n of closure) n.next = null;\n throw new EpochFailure(retry, cause);\n }\n for (const n of closure) n.next = null;\n return { evaluated, changed, cyclic };\n};\n","/**\n * The public operations of a store. Each operation keeps the store consistent. At the end of the operation, or after\n * the end of the enclosing batch, the value of each node agrees with its expression.\n * The seats agree with the reads too. `store.epochStats` records the epoch of the last operation.\n */\nimport type { Expr } from \"@mark1russell7/render-dsl\";\nimport { lit } from \"@mark1russell7/render-dsl\";\nimport type { Optional } from \"@mark1russell7/render-optional\";\nimport { none } from \"@mark1russell7/render-optional\";\nimport type { Node, NodeId, NodeStore, StoreOptions } from \"./types.ts\";\nimport type { MutableNode, StoreState } from \"./engine.ts\";\nimport {\n expandNow, flushUnlessBatched, freshId, insertNode, linkSlot, makeNode, makeState, publicStore, replaceExpr,\n compilerOf, runBatch, state, touch, touchExpansion, touchRecord, unlinkSlot, unwireNode, withContext,\n} from \"./engine.ts\";\nimport { defaultOps } from \"./ops.ts\";\n\n/** This function makes an empty store. The options give its reactive semantics and its op registry. */\nexport const nodeStore = (options: StoreOptions = {}): NodeStore =>\n publicStore(makeState(options.nodeOps ?? defaultOps, options.ops ?? {}));\n\n/**\n * This function gives an independent copy of a store: the same nodes, values, slots and seats, and the same\n * counters. A write to one store does not change the other. The expressions, their plans and the values are\n * immutable, thus the copy shares them. The options replace the semantics or the op registry of the copy, for\n * example the class semantics of a copied biblo. A new op registry compiles the plans again. The function throws\n * inside a batch, because a batch has pending work.\n */\nexport const forkStore = (store: NodeStore, options: StoreOptions = {}): NodeStore => {\n const s = state(store);\n if (s.pending !== null || s.batchDepth > 0) throw new Error(\"forkStore: the store is in a batch\");\n const ops = options.ops ?? s.ops;\n const copy: StoreState = {\n nodes: new Map(),\n nodeOps: options.nodeOps ?? s.nodeOps,\n ops,\n plan: ops === s.ops ? s.plan : compilerOf(ops),\n ctx: s.ctx,\n epochStats: s.epochStats,\n nextId: s.nextId,\n dangling: new Map([...s.dangling].map(([id, readers]) => [id, new Set(readers)])),\n pending: null,\n batchDepth: 0,\n epoch: s.epoch,\n };\n for (const [id, n] of s.nodes) {\n const node: MutableNode = {\n ...n,\n plan: ops === s.ops ? n.plan : copy.plan(n.expr),\n seats: new Set(n.seats),\n seatsStructural: new Set(n.seatsStructural),\n seatedOn: new Set(n.seatedOn),\n heldBy: new Set(n.heldBy),\n slots: new Map(n.slots),\n // The scratch fields of the epoch machine start again: no epoch is in progress\n mark: 0,\n dirty: 0,\n index: -1,\n low: -1,\n onStack: false,\n next: null,\n };\n copy.nodes.set(id, node);\n }\n return publicStore(withContext(copy));\n};\n\n/** This function gives a node of the store, or `undefined`. */\nexport const getNode = (store: NodeStore, id: NodeId): Node | undefined => store.nodes.get(id);\n\n/** This function gives the value of a node, or `none` when the store has no such node. */\nexport const readValue = (store: NodeStore, id: NodeId): Optional<unknown> => store.nodes.get(id)?.value ?? none;\n\n/**\n * This function makes `fn` one transaction. The writes in `fn` change the structure at once, and one epoch\n * at the end evaluates all their effects. A batch inside a batch joins the outer batch.\n */\nexport const batch = <T>(store: NodeStore, fn: () => T): T => runBatch(state(store), fn);\n\n/**\n * This function adds a node with an expression and evaluates it. It gives the ID of the node.\n * The ID is the hint when no node has it, otherwise a fresh ID. A reader that waited for this ID gets rewired.\n * The kernel compiles the expression one time, and each epoch evaluates the plan.\n */\nexport const addNode = (store: NodeStore, expr: Expr, idHint?: string): NodeId => {\n const s = state(store);\n const n = makeNode(s, freshId(s, idHint), expr);\n insertNode(s, n);\n flushUnlessBatched(s);\n return n.id;\n};\n\n/**\n * This function sets the expression of a node. This is the fundamental write.\n * The engine rewires the reads of the node, evaluates it and propagates the change.\n * A write to a container collapses it first: its own slots are removed, and the expression replaces the record.\n * The kernel compiles the new expression one time.\n */\nexport const setExpr = (store: NodeStore, id: NodeId, expr: Expr): void => {\n const s = state(store);\n const n = s.nodes.get(id);\n if (n) {\n if (n.slots.size > 0) collapse(s, n);\n replaceExpr(s, n, expr);\n touch(s, id);\n }\n flushUnlessBatched(s);\n};\n\n/**\n * This function writes a literal value: it is `setExpr` with `lit(value)`.\n * A write to a derived node makes it an input node, like a value that a person types over a formula.\n */\nexport const setValue = (store: NodeStore, id: NodeId, value: unknown): void => {\n setExpr(store, id, lit(value));\n};\n\n/** This function writes many literal values in one batch, thus in one epoch. */\nexport const fillMany = (store: NodeStore, writes: Iterable<readonly [NodeId, unknown]>): void => {\n batch(store, () => {\n for (const [id, value] of writes) setValue(store, id, value);\n });\n};\n\n/** The options of `setSlot`. */\nexport type SlotOptions = {\n /**\n * With `own`, the container becomes the owner of the child when the child has no owner. The removal of the\n * owner removes the child, and so does a slot change that drops the child. Without `own`, the child is shared.\n */\n readonly own?: boolean;\n};\n\n/**\n * This function points a slot of a container at a node, or removes the slot when `childId` is `undefined`.\n * This is the structural write: the container gets a new record expression, and each reader whose path goes\n * through the container resolves its path again. A node that the container owns and drops is removed.\n * The function does nothing when a node is missing or when the child is the container itself.\n */\nexport const setSlot = (\n store: NodeStore,\n parentId: NodeId,\n name: string,\n childId: NodeId | undefined,\n options: SlotOptions = {},\n): void => {\n const s = state(store);\n const parent = s.nodes.get(parentId);\n const child = childId === undefined ? undefined : s.nodes.get(childId);\n if (parent && (childId === undefined || (child && childId !== parentId)) && parent.slots.get(name) !== childId) {\n const previous = parent.slots.get(name);\n if (previous !== undefined) unlinkSlot(s, parent, name);\n if (child) {\n linkSlot(parent, name, child);\n if (options.own === true && (child.parent === undefined || !s.nodes.has(child.parent))) child.parent = parent.id;\n }\n if (previous !== undefined) dropIfOwned(s, parent, previous);\n touchRecord(s, parent.id);\n for (const r of parent.seatsStructural) touch(s, r);\n for (const r of parent.seats) touch(s, r);\n }\n flushUnlessBatched(s);\n};\n\n/**\n * This function expands a node whose value is a plain object. Each field becomes a slot, and\n * a field that is a plain object becomes a container in turn. The node becomes a container, and its slots\n * are from then on the source of truth of its fields. A reader of a field gets a seat on the field node,\n * thus an edit of one field makes only its own readers dirty. An expansion of a derived node keeps the current fields.\n *\n * Inside a batch, the expansion waits for the epoch of the batch, thus it uses the settled value.\n * The function does nothing for a container or for a value that is not a plain object.\n */\nexport const expandNode = (store: NodeStore, id: NodeId): void => {\n const s = state(store);\n if (s.batchDepth > 0) touchExpansion(s, id);\n else expandNow(s, id);\n flushUnlessBatched(s);\n};\n\n/**\n * This function removes a node and the slots that it owns. The containers that hold it lose the slot.\n * Its readers resolve their paths again: a path to a removed node gives `none`. If a node with the same ID\n * comes back later, the readers find it.\n */\nexport const removeNode = (store: NodeStore, id: NodeId): void => {\n const s = state(store);\n const n = s.nodes.get(id);\n if (n) removeOwned(s, n);\n flushUnlessBatched(s);\n};\n\n/**\n * This function evaluates the full store again. It rewires each node, makes each record expression again,\n * and starts one epoch in which each node evaluates one time in topological order. The operations keep the\n * store consistent, thus this function is a repair tool. It does not revert a write.\n */\nexport const resolveAll = (store: NodeStore): void => {\n const s = state(store);\n for (const n of s.nodes.values()) {\n touch(s, n.id);\n if (n.slots.size > 0) touchRecord(s, n.id);\n }\n flushUnlessBatched(s);\n};\n\n// === Internal ===\n\n/** A node that the container owns and no longer holds is removed: it was a part of the container. */\nconst dropIfOwned = (s: StoreState, container: MutableNode, childId: NodeId): void => {\n const former = s.nodes.get(childId);\n if (!former || former.parent !== container.id) return;\n for (const target of container.slots.values()) if (target === childId) return;\n removeOwned(s, former);\n};\n\n/** A container gives up its slots: the owned slot nodes are removed, and the shared ones are only released. */\nconst collapse = (s: StoreState, n: MutableNode): void => {\n const slots = [...n.slots.values()];\n for (const name of Array.from(n.slots.keys())) unlinkSlot(s, n, name);\n for (const childId of slots) {\n const child = s.nodes.get(childId);\n if (child && child.parent === n.id) removeOwned(s, child);\n }\n for (const r of n.seatsStructural) touch(s, r);\n};\n\n/** This function removes a node and its owned slot subtree, and touches each reader that stays. */\nconst removeOwned = (s: StoreState, root: MutableNode): void => {\n const doomed: MutableNode[] = [];\n const doomedIds = new Set<NodeId>();\n // The owned subtree, with a stack of its own: a deep tree does not overflow the call stack\n for (const todo = [root]; todo.length > 0;) {\n const n = todo.pop();\n if (n === undefined || doomedIds.has(n.id)) continue;\n doomedIds.add(n.id);\n doomed.push(n);\n for (const childId of n.slots.values()) {\n const child = s.nodes.get(childId);\n if (child && child.parent === n.id) todo.push(child);\n }\n }\n\n // The owner and each container that shares a removed node lose the slot\n for (const n of doomed) {\n for (const r of n.seatsStructural) touch(s, r);\n for (const r of n.seats) touch(s, r);\n for (const holderId of Array.from(n.heldBy)) {\n const holder = doomedIds.has(holderId) ? undefined : s.nodes.get(holderId);\n if (!holder) continue;\n for (const [name, target] of Array.from(holder.slots)) {\n if (target === n.id) unlinkSlot(s, holder, name);\n }\n touchRecord(s, holder.id);\n for (const r of holder.seatsStructural) touch(s, r);\n }\n }\n // A removed container no longer holds the nodes that it shares\n for (const n of doomed) {\n for (const target of n.slots.values()) if (!doomedIds.has(target)) s.nodes.get(target)?.heldBy.delete(n.id);\n }\n\n for (const n of doomed) {\n for (const readerId of n.seats) s.nodes.get(readerId)?.seatedOn.delete(n.id);\n for (const readerId of n.seatsStructural) s.nodes.get(readerId)?.seatedOn.delete(n.id);\n n.seats.clear();\n n.seatsStructural.clear();\n }\n for (const n of doomed) {\n unwireNode(s, n);\n s.nodes.delete(n.id);\n }\n};\n","import type { NodeId, NodeStore } from \"./types.ts\";\n\n\n/**\n * This function sorts the nodes of a store by their static dependencies (Kahn's algorithm).\n *\n * The edges come from the `targets` op of the store, like the seat wiring.\n * A reader depends on the terminal of each path and on each node that the path goes through.\n * A path to a node that is not in the store adds no edge.\n *\n * The result has the dependencies before their readers. A node on a cycle of two or more nodes is not in the\n * result. A reference of a node to itself adds no edge. The epochs of the engine use the edges of `flow`\n * instead, thus this function is a tool for analysis.\n */\nexport const toposort = (store: NodeStore): NodeId[] => {\n const dependedBy = new Map<NodeId, Set<NodeId>>();\n const inDegree = new Map<NodeId, number>();\n for (const id of store.nodes.keys()) {\n dependedBy.set(id, new Set());\n inDegree.set(id, 0);\n }\n for (const [id, n] of store.nodes) {\n for (const path of n.reads) {\n const root = path[0] === undefined ? undefined : store.nodes.get(path[0]);\n if (!root) continue;\n const { through, terminal } = store.nodeOps.targets(root, path.slice(1), store);\n for (const target of terminal === undefined ? through : [...through, terminal]) {\n const readers = dependedBy.get(target.id)!;\n if (target.id === id || readers.has(id)) continue;\n readers.add(id);\n inDegree.set(id, inDegree.get(id)! + 1);\n }\n }\n }\n\n const order: NodeId[] = [];\n for (const [id, d] of inDegree) if (d === 0) order.push(id);\n for (let head = 0; head < order.length; head++) {\n for (const readerId of dependedBy.get(order[head]!)!) {\n const d = inDegree.get(readerId)! - 1;\n inDegree.set(readerId, d);\n if (d === 0) order.push(readerId);\n }\n }\n return order;\n};\n"],"mappings":";;;;;;;;;;;;;;;AAYA,MAAM,YAAY;AAElB,MAAMA,YAAU,GAAW,QAAyB,OAAO,UAAU,eAAe,KAAK,GAAG,GAAG;;AAG/F,MAAa,iBAAiB,MAA6C;CACzE,IAAI,MAAM,QAAQ,OAAO,MAAM,UAAU,OAAO;CAChD,MAAM,QAAiB,OAAO,eAAe,CAAC;CAC9C,OAAO,UAAU,OAAO,aAAa,UAAU;AACjD;AAEA,MAAM,MAAM,GAAY,GAAY,UAA2B;CAC7D,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,OAAO;CAC5B,IAAI,SAAS,WAAW,OAAO;CAC/B,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,MAAM,QAAQ,MAAM,MAAM,OAAO;CAEvF,IAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;EACxC,IAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,OAAO;EAC5E,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAC5B,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,GAAG,OAAO;EAEzC,OAAO;CACT;CAEA,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,OAAO;CACnD,MAAM,QAAQ,OAAO,KAAK,CAAC;CAC3B,IAAI,MAAM,WAAW,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,OAAO;CACnD,KAAK,MAAM,KAAK,OACd,IAAI,CAACA,SAAO,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,GAAG,OAAO;CAE1D,OAAO;AACT;;AAGA,MAAa,eAAe,GAAY,MAAwB,GAAG,GAAG,GAAG,CAAC;;;AC1C1E,MAAM,UAAU,GAAW,QAAyB,OAAO,UAAU,eAAe,KAAK,GAAG,GAAG;;;;;AAM/F,MAAa,iBAA2B,OAAO,WAAW;CACxD,MAAM,OAAO,OAAO;CACpB,IAAI,OAAO,IAAI,IAAI,OAAO,KAAK,KAAK,YAAY,KAAK,OAAO,MAAM,KAAK,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO;CAClG,OAAO,QAAQ;CACf,OAAO;AACT;;AAGA,MAAa,eAAuB,WAAW,OAAO;;;;;;AAOtD,MAAa,gBAAyB,MAAM,MAAM,UAAU;CAC1D,IAAI,UAAU;CACd,IAAI,IAAI;CACR,OAAO,IAAI,KAAK,QAAQ;EACtB,MAAM,SAAS,QAAQ,MAAM,IAAI,KAAK,EAAG;EACzC,IAAI,WAAW,KAAA,GAAW;EAC1B,MAAM,OAAO,MAAM,MAAM,IAAI,MAAM;EACnC,IAAI,CAAC,MAAM;EACX,UAAU;EACV;CACF;CACA,IAAI,IAAI,KAAK,UAAU,QAAQ,MAAM,OAAO,GAAG,OAAO;CACtD,IAAI,CAAC,OAAO,QAAQ,KAAK,GAAG,OAAO;CACnC,IAAI,QAAiB,QAAQ,MAAM;CACnC,OAAO,IAAI,KAAK,QAAQ,KAAK;EAC3B,MAAM,UAAU,KAAK;EACrB,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,OAAO,OAAO,OAAO,GAAG,OAAO;EACnF,QAAS,MAAkC;CAC7C;CACA,OAAO,KAAK,KAAK;AACnB;;;;;;AAOA,MAAa,kBAA6B,MAAM,MAAM,UAAU;CAC9D,MAAM,UAAkB,CAAC;CACzB,IAAI,UAAU;CACd,IAAI,IAAI;CACR,OAAO,IAAI,KAAK,QAAQ,KAAK;EAC3B,MAAM,SAAS,QAAQ,MAAM,IAAI,KAAK,EAAG;EACzC,MAAM,OAAO,WAAW,KAAA,IAAY,KAAA,IAAY,MAAM,MAAM,IAAI,MAAM;EACtE,IAAI,CAAC,MAAM;EACX,QAAQ,KAAK,OAAO;EACpB,UAAU;CACZ;CACA,IAAI,IAAI,KAAK,UAAU,QAAQ,MAAM,OAAO,GAAG,OAAO;EAAE,SAAS,CAAC,GAAG,SAAS,OAAO;EAAG,UAAU,KAAA;CAAU;CAC5G,OAAO;EAAE;EAAS,UAAU;CAAQ;AACtC;;AAGA,MAAa,aAAsB;CACjC,QAAQ;CACR,MAAM;CACN,OAAO;CACP,SAAS;AACX;;;;ACHA,MAAa,SAAS,UAAiC;;AAGvD,MAAa,eAAe,MAA6B;;AAGzD,MAAM,SAAS,SACb,KAAK,OAAO,MAAmB,OAAO,MAAM,QAAQ,IAAI,OAAO,KAAK,IAAI,MAAM;;AAGhF,MAAM,YAAY,GAAe,SAA4C;CAC3E,MAAM,SAAS,KAAK;CACpB,MAAM,OAAO,WAAW,KAAA,IAAY,KAAA,IAAY,EAAE,MAAM,IAAI,OAAO,MAAM,CAAC;CAC1E,OAAO,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,WAAW,IAAI,CAAC,IAAI,MAAM,KAAK,MAAM,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,IAAI;AACvG;;;;;AAMA,MAAM,cAAc,OAA8B;EAC/C,QAAQ;CACT,OAAO;CACP,QAAQ,QAAQ;EACd,MAAM,QAAQ,SAAS,GAAG,CAAC,GAAG,CAAC;EAC/B,OAAO,OAAO,KAAK,IAAI,MAAM,QAAQ,KAAA;CACvC;CACA,cAAc;CACd,UAAU,SAAS;EACjB,MAAM,QAAQ,SAAS,GAAG,IAAI;EAC9B,OAAO,OAAO,KAAK,IAAI,EAAE,OAAO,MAAM,MAAM,IAAI,KAAA;CAClD;AACF;;AAGA,MAAa,eAAe,MAA8B;CACxD,EAAE,MAAM,QAAQ,EAAE,OAAO,WAAW,CAAC,EAAE,CAAC;CACxC,OAAO;AACT;;;;;AAMA,MAAa,cAAc,QAAuC;CAChE,MAAM,UAAU,UAAU,GAAG;CAC7B,QAAQ,QAAQ,QAAQ,SAAS,GAAG;AACtC;;AAGA,MAAa,aAAa,SAAkB,QAC1C,YAAY;CACV,uBAAO,IAAI,IAAI;CACf;CACA;CACA,MAAM,WAAW,GAAG;CACpB,KAAK,QAAQ;CACb,YAAY;CACZ,QAAQ;CACR,0BAAU,IAAI,IAAI;CAClB,SAAS;CACT,YAAY;CACZ,OAAO;AACT,CAAC;AAEH,MAAM,WAA+B,OAAO,OAAO,CAAC,CAAC;;;;;AAMrD,MAAa,WAAW,SAAmC;CACzD,IAAI,UAAU,IAAI,GAAG,OAAO;CAC5B,MAAM,MAAiB,CAAC;CACxB,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,OAAO,OAAO,aAAa,IAAI,GAAG;EAC3C,IAAI,CAAC,IAAI,QAAQ,IAAI,SAAS,SAAS;EAEvC,MAAM,OAAO,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM;EACzC,MAAM,MAAM,KAAK,UAAU,IAAI;EAC/B,IAAI,KAAK,IAAI,GAAG,GAAG;EACnB,KAAK,IAAI,GAAG;EACZ,IAAI,KAAK,IAAI;CACf;CACA,OAAO;AACT;;AAGA,MAAa,YAAY,GAAe,IAAY,UAA6B;CAC/E;CACA;CACA,MAAM,EAAE,KAAK,IAAI;CACjB,OAAO;CACP,OAAO,QAAQ,IAAI;CACnB,uBAAO,IAAI,IAAI;CACf,iCAAiB,IAAI,IAAI;CACzB,0BAAU,IAAI,IAAI;CAClB,QAAQ,KAAA;CACR,wBAAQ,IAAI,IAAI;CAChB,uBAAO,IAAI,IAAI;CACf,SAAS;CACT,MAAM;CACN,OAAO;CACP,OAAO;CACP,KAAK;CACL,SAAS;CACT,MAAM;AACR;;;;;AAMA,MAAa,WAAW,GAAe,SAA0B;CAC/D,IAAI,SAAS,KAAA,KAAa,CAAC,EAAE,MAAM,IAAI,IAAI,GAAG,OAAO;CACrD,MAAM,OAAO,QAAQ;CACrB,SAAS;EACP,MAAM,KAAK,GAAG,OAAO,SAAS,KAAA,IAAY,MAAM,MAAM,OAAO,EAAE,QAAQ;EACvE,IAAI,CAAC,EAAE,MAAM,IAAI,EAAE,GAAG,OAAO;CAC/B;AACF;;AAGA,MAAa,YAAY,MAAyB;CAChD,MAAM,SAA+B,CAAC;CACtC,KAAK,MAAM,QAAQ,EAAE,MAAM,KAAK,GAE9B,OAAO,eAAe,QAAQ,MAAM;EAAE,OAAO;GAAE,MAAM;GAAO,OAAO;IAAC;IAAS,EAAE;IAAI;GAAI;EAAE;EAAG,YAAY;EAAM,UAAU;EAAM,cAAc;CAAK,CAAC;CAEpJ,OAAO;EAAE,MAAM;EAAU,OAAO;CAAO;AACzC;;AAGA,MAAM,cAAc,MAA4B;CAC9C,MAAM,IAAI,EAAE;CACZ,IAAI,EAAE,SAAS,YAAY,CAAC,SAAS,EAAE,KAAK,GAAG,OAAO;CACtD,MAAM,SAAS,EAAE;CACjB,MAAM,QAAQ,OAAO,KAAK,MAAM;CAChC,IAAI,MAAM,WAAW,EAAE,MAAM,MAAM,OAAO;CAC1C,IAAI,IAAI;CACR,KAAK,MAAM,QAAQ,EAAE,MAAM,KAAK,GAAG;EACjC,IAAI,MAAM,SAAS,MAAM,OAAO;EAChC,MAAM,IAAI,OAAO;EACjB,MAAM,OAAO,GAAG,SAAS,SAAS,MAAM,QAAQ,EAAE,KAAK,IAAK,EAAE,QAA+B,KAAA;EAC7F,IAAI,MAAM,WAAW,KAAK,KAAK,OAAO,WAAW,KAAK,OAAO,EAAE,MAAM,KAAK,OAAO,MAAM,OAAO;CAChG;CACA,OAAO;AACT;;AAKA,MAAa,YAAY,WAAwB,MAAc,UAA6B;CAC1F,UAAU,MAAM,IAAI,MAAM,MAAM,EAAE;CAClC,MAAM,OAAO,IAAI,UAAU,EAAE;AAC/B;;AAGA,MAAa,cAAc,GAAe,WAAwB,SAAuB;CACvF,MAAM,SAAS,UAAU,MAAM,IAAI,IAAI;CACvC,UAAU,MAAM,OAAO,IAAI;CAC3B,IAAI,WAAW,KAAA,GAAW;CAC1B,KAAK,MAAM,SAAS,UAAU,MAAM,OAAO,GAAG,IAAI,UAAU,QAAQ;CACpE,EAAE,MAAM,IAAI,MAAM,CAAC,EAAE,OAAO,OAAO,UAAU,EAAE;AACjD;;;;;;AASA,MAAa,eAAe,OAAkC,SAA0B;CACtF,MAAM,SAAS,KAAK;CACpB,MAAM,OAAO,WAAW,KAAA,IAAY,KAAA,IAAY,MAAM,IAAI,MAAM;CAChE,IAAI,CAAC,MAAM,OAAO,CAAC;CACnB,MAAM,MAAc,CAAC,IAAI;CACzB,IAAI,UAAU;CACd,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,SAAS,QAAQ,MAAM,IAAI,KAAK,EAAG;EACzC,MAAM,OAAO,WAAW,KAAA,IAAY,KAAA,IAAY,MAAM,IAAI,MAAM;EAChE,IAAI,CAAC,MAAM;EACX,UAAU;EACV,IAAI,KAAK,OAAO;CAClB;CACA,OAAO;AACT;;;;;;;AAQA,MAAa,YAAY,GAAe,MAAyB;CAC/D,KAAK,MAAM,QAAQ,EAAE,OAAO;EAC1B,MAAM,SAAS,KAAK;EACpB,MAAM,OAAO,WAAW,KAAA,IAAY,KAAA,IAAY,EAAE,MAAM,IAAI,MAAM;EAClE,IAAI,CAAC,MAAM;GACT,IAAI,WAAW,KAAA,GAAW;IACxB,IAAI,UAAU,EAAE,SAAS,IAAI,MAAM;IACnC,IAAI,CAAC,SAAS,EAAE,SAAS,IAAI,QAAS,0BAAU,IAAI,IAAI,CAAE;IAC1D,QAAQ,IAAI,EAAE,EAAE;IAChB,EAAE,UAAU;GACd;GACA;EACF;EACA,MAAM,UAAU,EAAE,QAAQ,QAAQ,MAAM,KAAK,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC;EACrE,KAAK,MAAM,KAAK,QAAQ,SAAS;GAC/B,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE,EAAE;GAC1B,IAAI,CAAC,KAAK,MAAM,GAAG;GACnB,EAAE,gBAAgB,IAAI,EAAE,EAAE;GAC1B,EAAE,SAAS,IAAI,EAAE,EAAE;EACrB;EACA,MAAM,WAAW,QAAQ,aAAa,KAAA,IAAY,KAAA,IAAY,EAAE,MAAM,IAAI,QAAQ,SAAS,EAAE;EAC7F,IAAI,YAAY,aAAa,GAAG;GAC9B,SAAS,MAAM,IAAI,EAAE,EAAE;GACvB,EAAE,SAAS,IAAI,SAAS,EAAE;EAC5B;CACF;AACF;;AAGA,MAAa,cAAc,GAAe,MAAyB;CACjE,IAAI,EAAE,SAAS,OAAO,GAAG;EACvB,KAAK,MAAM,WAAW,EAAE,UAAU;GAChC,MAAM,QAAQ,EAAE,MAAM,IAAI,OAAO;GACjC,IAAI,OAAO;IACT,MAAM,MAAM,OAAO,EAAE,EAAE;IACvB,MAAM,gBAAgB,OAAO,EAAE,EAAE;GACnC;EACF;EACA,EAAE,SAAS,MAAM;CACnB;CACA,IAAI,EAAE,SAAS;EACb,KAAK,MAAM,QAAQ,EAAE,OAAO;GAC1B,MAAM,OAAO,KAAK;GAClB,MAAM,UAAU,SAAS,KAAA,IAAY,KAAA,IAAY,EAAE,SAAS,IAAI,IAAI;GACpE,IAAI,SAAS;IACX,QAAQ,OAAO,EAAE,EAAE;IACnB,IAAI,QAAQ,SAAS,GAAG,EAAE,SAAS,OAAO,IAAK;GACjD;EACF;EACA,EAAE,UAAU;CACd;AACF;;AAGA,MAAa,eAAe,GAAe,GAAgB,MAAY,QAA4B,QAAQ,IAAI,MAAY;CACzH,WAAW,GAAG,CAAC;CACf,EAAE,OAAO;CACT,EAAE,OAAO,EAAE,KAAK,IAAI;CACpB,EAAE,QAAQ;AACZ;;AAGA,MAAM,eAAe,MAAuC,MAAM,KAAK,EAAE,MAAM,KAAK,IAAI,SAAS,CAAC,EAAE,IAAI,IAAI,CAAC;AAI7G,MAAM,WAAW,MACd,EAAE,YAAY;CAAE,yBAAS,IAAI,IAAI;CAAG,yBAAS,IAAI,IAAI;CAAG,4BAAY,IAAI,IAAI;AAAE;;AAGjF,MAAa,SAAS,GAAe,OAAqB;CACxD,QAAQ,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE;AAC3B;;AAGA,MAAa,eAAe,GAAe,OAAqB;CAC9D,QAAQ,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE;AAC3B;;AAGA,MAAa,kBAAkB,GAAe,OAAqB;CACjE,QAAQ,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE;AAC9B;;AAGA,MAAa,cAAc,GAAe,MAAyB;CACjE,EAAE,MAAM,IAAI,EAAE,IAAI,CAAC;CACnB,MAAM,GAAG,EAAE,EAAE;CACb,MAAM,UAAU,EAAE,SAAS,IAAI,EAAE,EAAE;CACnC,IAAI,SAAS;EACX,KAAK,MAAM,KAAK,SAAS,MAAM,GAAG,CAAC;EACnC,EAAE,SAAS,OAAO,EAAE,EAAE;CACxB;AACF;;;;;;;AAQA,MAAa,aAAa,GAAe,OAAqB;CAC5D,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE;CACxB,MAAM,QAAQ,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,QAAQ,KAAA;CACrD,IAAI,CAAC,KAAK,EAAE,MAAM,OAAO,KAAK,CAAC,cAAc,KAAK,GAAG;CAGrD,MAAM,4BAAY,IAAI,IAAY,CAAC,KAAK,CAAC;CACzC,MAAM,SAAkB,CAAC;EAAE,OAAO;EAAG;EAAO,QAAQ,OAAO,QAAQ,KAAK;EAAG,MAAM;CAAE,CAAC;CACpF,KAAK,IAAI,QAAQ,OAAO,GAAG,EAAE,GAAG,UAAU,KAAA,GAAW,QAAQ,OAAO,GAAG,EAAE,GAAG;EAC1E,MAAM,QAAQ,MAAM,OAAO,MAAM;EACjC,IAAI,UAAU,KAAA,GAAW;GACvB,OAAO,IAAI;GACX,UAAU,OAAO,MAAM,KAAK;GAE5B,IAAI,MAAM,UAAU,GAAG,YAAY,GAAG,MAAM,MAAM,EAAE;GACpD;EACF;EACA,MAAM,CAAC,KAAK,cAAc;EAC1B,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,GAAG,MAAM,MAAM,GAAG,GAAG,KAAK,GAAG,IAAI,UAAU,CAAC;EAC/E,IAAI,SAAS,MAAM,MAAM;EACzB,IAAI,QAAQ,KAAK,UAAU;EAC3B,WAAW,GAAG,GAAG;EACjB,SAAS,MAAM,OAAO,KAAK,GAAG;EAC9B,IAAI,cAAc,UAAU,KAAK,CAAC,UAAU,IAAI,UAAU,GAAG;GAC3D,UAAU,IAAI,UAAU;GACxB,OAAO,KAAK;IAAE,OAAO;IAAK,OAAO;IAAY,QAAQ,OAAO,QAAQ,UAAU;IAAG,MAAM;GAAE,CAAC;EAC5F;CACF;CACA,YAAY,GAAG,EAAE,EAAE;CACnB,KAAK,MAAM,KAAK,EAAE,OAAO,MAAM,GAAG,CAAC;CACnC,KAAK,MAAM,KAAK,EAAE,iBAAiB,MAAM,GAAG,CAAC;AAC/C;;AAGA,MAAa,sBAAsB,MAAwB;CACzD,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC;AACjC;;;;;AAMA,MAAa,YAAe,GAAe,OAAmB;CAC5D,EAAE;CACF,IAAI;CACJ,IAAI;EACF,SAAS,GAAG;CACd,SAAS,OAAO;EACd,EAAE;EACF,IAAI,EAAE,eAAe,GACnB,IAAI;GACF,MAAM,CAAC;EACT,QAAQ,CAER;EAEF,MAAM;CACR;CACA,EAAE;CACF,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC;CAC/B,OAAO;AACT;;AAGA,MAAM,iBAAiB;;AAGvB,IAAM,eAAN,MAAmB;CACjB;CACA;CACA,YAAY,OAA4B,OAAgB;EACtD,KAAK,QAAQ;EACb,KAAK,QAAQ;CACf;AACF;;;;;;;;;AAUA,MAAM,SAAS,MAAwB;CACrC,IAAI,QAAsF;CAC1F,EAAE;CACF,IAAI;EACF,KAAK,IAAI,QAAQ,GAAG,EAAE,SAAS,SAAS;GACtC,IAAI,QAAQ,gBAAgB;IAC1B,EAAE,UAAU;IACZ,MAAM,IAAI,MAAM,mCAAmC,OAAO,cAAc,EAAE,yCAAyC;GACrH;GACA,MAAM,IAAI,EAAE;GACZ,EAAE,UAAU;GACZ,IAAI;GACJ,IAAI;IACF,KAAK,MAAM,MAAM,EAAE,SAAS;KAC1B,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE;KACxB,IAAI,CAAC,GAAG;KACR,IAAI,CAAC,WAAW,CAAC,GAAG,YAAY,GAAG,GAAG,SAAS,CAAC,GAAG,YAAY,CAAC,CAAC;KACjE,EAAE,QAAQ,IAAI,EAAE;IAClB;IACA,KAAK,MAAM,MAAM,EAAE,SAAS;KAC1B,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE;KACxB,IAAI,CAAC,GAAG;KACR,WAAW,GAAG,CAAC;KACf,SAAS,GAAG,CAAC;IACf;IACA,QAAQ,SAAS,GAAG,EAAE,OAAO;GAC/B,SAAS,SAAS;IAChB,IAAI,mBAAmB,cAAc;KACnC,KAAK,MAAM,MAAM,QAAQ,OAAO,MAAM,GAAG,EAAE;KAC3C,KAAK,MAAM,MAAM,EAAE,YAAY,eAAe,GAAG,EAAE;KACnD,MAAM,QAAQ;IAChB;IAEA,KAAK,MAAM,MAAM,EAAE,SAAS,YAAY,GAAG,EAAE;IAC7C,KAAK,MAAM,MAAM,EAAE,SAAS,MAAM,GAAG,EAAE;IACvC,KAAK,MAAM,MAAM,EAAE,YAAY,eAAe,GAAG,EAAE;IACnD,MAAM;GACR;GACA,IAAI,CAAC,OAAO,QAAQ;QACf;IACH,KAAK,MAAM,MAAM,MAAM,WAAW,MAAM,UAAU,IAAI,EAAE;IACxD,KAAK,MAAM,MAAM,MAAM,SAAS,MAAM,QAAQ,IAAI,EAAE;IACpD,KAAK,MAAM,MAAM,MAAM,QAAQ,MAAM,OAAO,IAAI,EAAE;GACpD;GAEA,KAAK,MAAM,MAAM,EAAE,YAAY,UAAU,GAAG,EAAE;EAChD;CACF,UAAU;EACR,EAAE;CACJ;CACA,EAAE,aAAa;EACb,WAAW,OAAO,6BAAa,IAAI,IAAI;EACvC,SAAS,OAAO,2BAAW,IAAI,IAAI;EACnC,QAAQ,OAAO,0BAAU,IAAI,IAAI;EACjC,OAAO,EAAE,MAAM;CACjB;AACF;;;;;AAQA,MAAa,cAAc,WAAgC,SAAS,SAAS,MAAM,KAAK,GAAG,IAAI;;;;;AAM/F,MAAa,mBAAmB;;;;;;AAOhC,MAAM,cAAc,YAAqD;CACvE,MAAM,MAAuB,CAAC;CAC9B,MAAM,QAAuB,CAAC;CAC9B,IAAI,UAAU;CACd,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,MAAM,UAAU,IAAI;EACxB,MAAM,SAAyD,CAAC;EAChE,MAAM,QAAQ,MAAyB;GACrC,EAAE,QAAQ,EAAE,MAAM;GAClB,EAAE,UAAU;GACZ,MAAM,KAAK,CAAC;GACZ,OAAO,KAAK;IAAE,MAAM;IAAG,MAAM;GAAE,CAAC;EAClC;EACA,KAAK,KAAK;EACV,OAAO,OAAO,SAAS,GAAG;GACxB,MAAM,QAAQ,OAAO,OAAO,SAAS;GACrC,MAAM,IAAI,MAAM;GAChB,MAAM,aAAa,EAAE;GACrB,IAAI,MAAM,OAAO,WAAW,QAAQ;IAClC,MAAM,IAAI,WAAW,MAAM;IAC3B,IAAI,EAAE,UAAU,IAAI,KAAK,CAAC;SACrB,IAAI,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,EAAE,KAAK,EAAE,KAAK;IACnD;GACF;GACA,OAAO,IAAI;GACX,MAAM,SAAS,OAAO,OAAO,SAAS;GACtC,IAAI,QAAQ,OAAO,KAAK,MAAM,KAAK,IAAI,OAAO,KAAK,KAAK,EAAE,GAAG;GAC7D,IAAI,EAAE,QAAQ,EAAE,OAAO;IACrB,MAAM,YAA2B,CAAC;IAClC,IAAI;IACJ,GAAG;KACD,IAAI,MAAM,IAAI;KACd,EAAE,UAAU;KACZ,UAAU,KAAK,CAAC;IAClB,SAAS,MAAM;IACf,IAAI,KAAK,SAAS;GACpB;EACF;CACF;CACA,OAAO,IAAI,WAAW;AACxB;;;;;;;;;;AAWA,MAAM,YACJ,GACA,aAC0E;CAC1E,MAAM,QAAQ,YAAY,CAAC;CAC3B,MAAM,QAAQ,EAAE,EAAE;CAGlB,MAAM,UAAyB,CAAC;CAChC,MAAM,QAAuB,CAAC;CAC9B,MAAM,SAAS,MAAyB;EACtC,EAAE,OAAO;EACT,EAAE,QAAQ;EACV,EAAE,MAAM;EACR,EAAE,UAAU;EACZ,EAAE,OAAO;EACT,MAAM,KAAK,CAAC;CACd;CACA,KAAK,MAAM,MAAM,UAAU;EACzB,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE;EACxB,IAAI,KAAK,EAAE,SAAS,OAAO;GACzB,MAAM,CAAC;GACP,EAAE,QAAQ;EACZ;CACF;CACA,OAAO,MAAM,SAAS,GAAG;EACvB,MAAM,IAAI,MAAM,IAAI;EACpB,QAAQ,KAAK,CAAC;EACd,MAAM,OAAsB,CAAC;EAC7B,KAAK,MAAM,MAAM,EAAE,QAAQ,KAAK,GAAG,KAAK,GAAG;GACzC,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE;GACxB,IAAI,CAAC,KAAK,MAAM,GAAG;GACnB,KAAK,KAAK,CAAC;GACX,IAAI,EAAE,SAAS,OAAO,MAAM,CAAC;EAC/B;EACA,EAAE,OAAO;CACX;CAGA,MAAM,QAAQ,WAAW,OAAO;CAGhC,MAAM,MAAM,EAAE;CACd,MAAM,4BAAY,IAAI,IAAY;CAClC,MAAM,0BAAU,IAAI,IAAY;CAChC,MAAM,yBAAS,IAAI,IAAY;CAC/B,MAAM,gBAAgB,MAA4B;EAChD,EAAE,QAAQ;EACV,UAAU,IAAI,EAAE,EAAE;EAClB,MAAM,SAAS,EAAE,KAAK,SAAS,GAAG;EAClC,IAAI,CAAC,EAAE,QAAQ,OAAO,OAAO,KAAK,KAAK,OAAO,KAAK,IAAI,MAAM,GAAG,KAAK,GAAG,OAAO;EAC/E,QAAQ,IAAI,EAAE,EAAE;EAChB,KAAK,MAAM,KAAK,EAAE,MAAO,EAAE,QAAQ;EACnC,OAAO;CACT;CACA,IAAI,WAAW;CACf,IAAI;EACF,OAAO,WAAW,MAAM,QAAQ,YAAY;GAC1C,MAAM,YAAY,MAAM;GACxB,IAAI,UAAU,WAAW,GAAG;IAC1B,MAAM,IAAI,UAAU;IACpB,IAAI,EAAE,UAAU,OAAO,aAAa,CAAC;IACrC;GACF;GACA,KAAK,MAAM,KAAK,WAAW,OAAO,IAAI,EAAE,EAAE;GAC1C,KAAK,IAAI,QAAQ,GAAG,QAAA,KAA0B,SAAS;IACrD,IAAI,MAAM;IACV,KAAK,MAAM,KAAK,WAAW,IAAI,EAAE,UAAU,SAAS,aAAa,CAAC,GAAG,MAAM;IAC3E,IAAI,CAAC,KAAK;GACZ;EACF;CACF,SAAS,OAAO;EACd,MAAM,wBAAQ,IAAI,IAAY;EAC9B,KAAK,IAAI,IAAI,UAAU,IAAI,MAAM,QAAQ,KACvC,KAAK,MAAM,KAAK,MAAM,IAAK,IAAI,EAAE,UAAU,SAAS,MAAM,UAAU,MAAM,IAAI,EAAE,EAAE;EAEpF,KAAK,MAAM,KAAK,SAAS,EAAE,OAAO;EAClC,MAAM,IAAI,aAAa,OAAO,KAAK;CACrC;CACA,KAAK,MAAM,KAAK,SAAS,EAAE,OAAO;CAClC,OAAO;EAAE;EAAW;EAAS;CAAO;AACtC;;;;ACjoBA,MAAa,aAAa,UAAwB,CAAC,MACjD,YAAY,UAAU,QAAQ,WAAW,YAAY,QAAQ,OAAO,CAAC,CAAC,CAAC;;;;;;;;AASzE,MAAa,aAAa,OAAkB,UAAwB,CAAC,MAAiB;CACpF,MAAM,IAAI,MAAM,KAAK;CACrB,IAAI,EAAE,YAAY,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI,MAAM,oCAAoC;CAChG,MAAM,MAAM,QAAQ,OAAO,EAAE;CAC7B,MAAM,OAAmB;EACvB,uBAAO,IAAI,IAAI;EACf,SAAS,QAAQ,WAAW,EAAE;EAC9B;EACA,MAAM,QAAQ,EAAE,MAAM,EAAE,OAAO,WAAW,GAAG;EAC7C,KAAK,EAAE;EACP,YAAY,EAAE;EACd,QAAQ,EAAE;EACV,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC;EAChF,SAAS;EACT,YAAY;EACZ,OAAO,EAAE;CACX;CACA,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,OAAO;EAC7B,MAAM,OAAoB;GACxB,GAAG;GACH,MAAM,QAAQ,EAAE,MAAM,EAAE,OAAO,KAAK,KAAK,EAAE,IAAI;GAC/C,OAAO,IAAI,IAAI,EAAE,KAAK;GACtB,iBAAiB,IAAI,IAAI,EAAE,eAAe;GAC1C,UAAU,IAAI,IAAI,EAAE,QAAQ;GAC5B,QAAQ,IAAI,IAAI,EAAE,MAAM;GACxB,OAAO,IAAI,IAAI,EAAE,KAAK;GAEtB,MAAM;GACN,OAAO;GACP,OAAO;GACP,KAAK;GACL,SAAS;GACT,MAAM;EACR;EACA,KAAK,MAAM,IAAI,IAAI,IAAI;CACzB;CACA,OAAO,YAAY,YAAY,IAAI,CAAC;AACtC;;AAGA,MAAa,WAAW,OAAkB,OAAiC,MAAM,MAAM,IAAI,EAAE;;AAG7F,MAAa,aAAa,OAAkB,OAAkC,MAAM,MAAM,IAAI,EAAE,CAAC,EAAE,SAAS;;;;;AAM5G,MAAa,SAAY,OAAkB,OAAmB,SAAS,MAAM,KAAK,GAAG,EAAE;;;;;;AAOvF,MAAa,WAAW,OAAkB,MAAY,WAA4B;CAChF,MAAM,IAAI,MAAM,KAAK;CACrB,MAAM,IAAI,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI;CAC9C,WAAW,GAAG,CAAC;CACf,mBAAmB,CAAC;CACpB,OAAO,EAAE;AACX;;;;;;;AAQA,MAAa,WAAW,OAAkB,IAAY,SAAqB;CACzE,MAAM,IAAI,MAAM,KAAK;CACrB,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE;CACxB,IAAI,GAAG;EACL,IAAI,EAAE,MAAM,OAAO,GAAG,SAAS,GAAG,CAAC;EACnC,YAAY,GAAG,GAAG,IAAI;EACtB,MAAM,GAAG,EAAE;CACb;CACA,mBAAmB,CAAC;AACtB;;;;;AAMA,MAAa,YAAY,OAAkB,IAAY,UAAyB;CAC9E,QAAQ,OAAO,IAAI,IAAI,KAAK,CAAC;AAC/B;;AAGA,MAAa,YAAY,OAAkB,WAAuD;CAChG,MAAM,aAAa;EACjB,KAAK,MAAM,CAAC,IAAI,UAAU,QAAQ,SAAS,OAAO,IAAI,KAAK;CAC7D,CAAC;AACH;;;;;;;AAiBA,MAAa,WACX,OACA,UACA,MACA,SACA,UAAuB,CAAC,MACf;CACT,MAAM,IAAI,MAAM,KAAK;CACrB,MAAM,SAAS,EAAE,MAAM,IAAI,QAAQ;CACnC,MAAM,QAAQ,YAAY,KAAA,IAAY,KAAA,IAAY,EAAE,MAAM,IAAI,OAAO;CACrE,IAAI,WAAW,YAAY,KAAA,KAAc,SAAS,YAAY,aAAc,OAAO,MAAM,IAAI,IAAI,MAAM,SAAS;EAC9G,MAAM,WAAW,OAAO,MAAM,IAAI,IAAI;EACtC,IAAI,aAAa,KAAA,GAAW,WAAW,GAAG,QAAQ,IAAI;EACtD,IAAI,OAAO;GACT,SAAS,QAAQ,MAAM,KAAK;GAC5B,IAAI,QAAQ,QAAQ,SAAS,MAAM,WAAW,KAAA,KAAa,CAAC,EAAE,MAAM,IAAI,MAAM,MAAM,IAAI,MAAM,SAAS,OAAO;EAChH;EACA,IAAI,aAAa,KAAA,GAAW,YAAY,GAAG,QAAQ,QAAQ;EAC3D,YAAY,GAAG,OAAO,EAAE;EACxB,KAAK,MAAM,KAAK,OAAO,iBAAiB,MAAM,GAAG,CAAC;EAClD,KAAK,MAAM,KAAK,OAAO,OAAO,MAAM,GAAG,CAAC;CAC1C;CACA,mBAAmB,CAAC;AACtB;;;;;;;;;;AAWA,MAAa,cAAc,OAAkB,OAAqB;CAChE,MAAM,IAAI,MAAM,KAAK;CACrB,IAAI,EAAE,aAAa,GAAG,eAAe,GAAG,EAAE;MACrC,UAAU,GAAG,EAAE;CACpB,mBAAmB,CAAC;AACtB;;;;;;AAOA,MAAa,cAAc,OAAkB,OAAqB;CAChE,MAAM,IAAI,MAAM,KAAK;CACrB,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE;CACxB,IAAI,GAAG,YAAY,GAAG,CAAC;CACvB,mBAAmB,CAAC;AACtB;;;;;;AAOA,MAAa,cAAc,UAA2B;CACpD,MAAM,IAAI,MAAM,KAAK;CACrB,KAAK,MAAM,KAAK,EAAE,MAAM,OAAO,GAAG;EAChC,MAAM,GAAG,EAAE,EAAE;EACb,IAAI,EAAE,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,EAAE;CAC3C;CACA,mBAAmB,CAAC;AACtB;;AAKA,MAAM,eAAe,GAAe,WAAwB,YAA0B;CACpF,MAAM,SAAS,EAAE,MAAM,IAAI,OAAO;CAClC,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,IAAI;CAC/C,KAAK,MAAM,UAAU,UAAU,MAAM,OAAO,GAAG,IAAI,WAAW,SAAS;CACvE,YAAY,GAAG,MAAM;AACvB;;AAGA,MAAM,YAAY,GAAe,MAAyB;CACxD,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,OAAO,CAAC;CAClC,KAAK,MAAM,QAAQ,MAAM,KAAK,EAAE,MAAM,KAAK,CAAC,GAAG,WAAW,GAAG,GAAG,IAAI;CACpE,KAAK,MAAM,WAAW,OAAO;EAC3B,MAAM,QAAQ,EAAE,MAAM,IAAI,OAAO;EACjC,IAAI,SAAS,MAAM,WAAW,EAAE,IAAI,YAAY,GAAG,KAAK;CAC1D;CACA,KAAK,MAAM,KAAK,EAAE,iBAAiB,MAAM,GAAG,CAAC;AAC/C;;AAGA,MAAM,eAAe,GAAe,SAA4B;CAC9D,MAAM,SAAwB,CAAC;CAC/B,MAAM,4BAAY,IAAI,IAAY;CAElC,KAAK,MAAM,OAAO,CAAC,IAAI,GAAG,KAAK,SAAS,IAAI;EAC1C,MAAM,IAAI,KAAK,IAAI;EACnB,IAAI,MAAM,KAAA,KAAa,UAAU,IAAI,EAAE,EAAE,GAAG;EAC5C,UAAU,IAAI,EAAE,EAAE;EAClB,OAAO,KAAK,CAAC;EACb,KAAK,MAAM,WAAW,EAAE,MAAM,OAAO,GAAG;GACtC,MAAM,QAAQ,EAAE,MAAM,IAAI,OAAO;GACjC,IAAI,SAAS,MAAM,WAAW,EAAE,IAAI,KAAK,KAAK,KAAK;EACrD;CACF;CAGA,KAAK,MAAM,KAAK,QAAQ;EACtB,KAAK,MAAM,KAAK,EAAE,iBAAiB,MAAM,GAAG,CAAC;EAC7C,KAAK,MAAM,KAAK,EAAE,OAAO,MAAM,GAAG,CAAC;EACnC,KAAK,MAAM,YAAY,MAAM,KAAK,EAAE,MAAM,GAAG;GAC3C,MAAM,SAAS,UAAU,IAAI,QAAQ,IAAI,KAAA,IAAY,EAAE,MAAM,IAAI,QAAQ;GACzE,IAAI,CAAC,QAAQ;GACb,KAAK,MAAM,CAAC,MAAM,WAAW,MAAM,KAAK,OAAO,KAAK,GAClD,IAAI,WAAW,EAAE,IAAI,WAAW,GAAG,QAAQ,IAAI;GAEjD,YAAY,GAAG,OAAO,EAAE;GACxB,KAAK,MAAM,KAAK,OAAO,iBAAiB,MAAM,GAAG,CAAC;EACpD;CACF;CAEA,KAAK,MAAM,KAAK,QACd,KAAK,MAAM,UAAU,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,IAAI,MAAM,GAAG,EAAE,MAAM,IAAI,MAAM,CAAC,EAAE,OAAO,OAAO,EAAE,EAAE;CAG5G,KAAK,MAAM,KAAK,QAAQ;EACtB,KAAK,MAAM,YAAY,EAAE,OAAO,EAAE,MAAM,IAAI,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,EAAE;EAC3E,KAAK,MAAM,YAAY,EAAE,iBAAiB,EAAE,MAAM,IAAI,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,EAAE;EACrF,EAAE,MAAM,MAAM;EACd,EAAE,gBAAgB,MAAM;CAC1B;CACA,KAAK,MAAM,KAAK,QAAQ;EACtB,WAAW,GAAG,CAAC;EACf,EAAE,MAAM,OAAO,EAAE,EAAE;CACrB;AACF;;;;;;;;;;;;;;AClQA,MAAa,YAAY,UAA+B;CACtD,MAAM,6BAAa,IAAI,IAAyB;CAChD,MAAM,2BAAW,IAAI,IAAoB;CACzC,KAAK,MAAM,MAAM,MAAM,MAAM,KAAK,GAAG;EACnC,WAAW,IAAI,oBAAI,IAAI,IAAI,CAAC;EAC5B,SAAS,IAAI,IAAI,CAAC;CACpB;CACA,KAAK,MAAM,CAAC,IAAI,MAAM,MAAM,OAC1B,KAAK,MAAM,QAAQ,EAAE,OAAO;EAC1B,MAAM,OAAO,KAAK,OAAO,KAAA,IAAY,KAAA,IAAY,MAAM,MAAM,IAAI,KAAK,EAAE;EACxE,IAAI,CAAC,MAAM;EACX,MAAM,EAAE,SAAS,aAAa,MAAM,QAAQ,QAAQ,MAAM,KAAK,MAAM,CAAC,GAAG,KAAK;EAC9E,KAAK,MAAM,UAAU,aAAa,KAAA,IAAY,UAAU,CAAC,GAAG,SAAS,QAAQ,GAAG;GAC9E,MAAM,UAAU,WAAW,IAAI,OAAO,EAAE;GACxC,IAAI,OAAO,OAAO,MAAM,QAAQ,IAAI,EAAE,GAAG;GACzC,QAAQ,IAAI,EAAE;GACd,SAAS,IAAI,IAAI,SAAS,IAAI,EAAE,IAAK,CAAC;EACxC;CACF;CAGF,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,CAAC,IAAI,MAAM,UAAU,IAAI,MAAM,GAAG,MAAM,KAAK,EAAE;CAC1D,KAAK,IAAI,OAAO,GAAG,OAAO,MAAM,QAAQ,QACtC,KAAK,MAAM,YAAY,WAAW,IAAI,MAAM,KAAM,GAAI;EACpD,MAAM,IAAI,SAAS,IAAI,QAAQ,IAAK;EACpC,SAAS,IAAI,UAAU,CAAC;EACxB,IAAI,MAAM,GAAG,MAAM,KAAK,QAAQ;CAClC;CAEF,OAAO;AACT"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/render-node",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"description": "The reactive node store of render: seats, slots, ordered flow epochs and the NodeOps extension points.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"reactive",
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"node": ">=22.12"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@mark1russell7/
|
|
53
|
+
"@mark1russell7/coeval": "^0.4.3",
|
|
54
|
+
"@mark1russell7/render-dsl": "0.2.1",
|
|
54
55
|
"@mark1russell7/render-optional": "0.1.0"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
package/src/engine.ts
CHANGED
|
@@ -4,17 +4,21 @@
|
|
|
4
4
|
* Each write changes the structure at once and marks the nodes that it touched. A flush then rewires
|
|
5
5
|
* the touched nodes and starts one epoch over them. Outside a batch, each write flushes at its end.
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
8
|
-
import {
|
|
7
|
+
import type { Context, HostThing, Json, Key, Plan } from "@mark1russell7/coeval";
|
|
8
|
+
import { THING, context, isRecord, prepare, refsOf } from "@mark1russell7/coeval";
|
|
9
|
+
import type { Expr, Ops, Resolver } from "@mark1russell7/render-dsl";
|
|
10
|
+
import { NO_VALUE, isLiteral, lit, machineOf, renderWorld } from "@mark1russell7/render-dsl";
|
|
9
11
|
import type { Optional } from "@mark1russell7/render-optional";
|
|
10
12
|
import { isSome, none, some } from "@mark1russell7/render-optional";
|
|
11
|
-
import type { EpochStats, Node, NodeId, NodeOps, NodeStore } from "./types.ts";
|
|
13
|
+
import type { DepPath, EpochStats, Node, NodeId, NodeOps, NodeStore } from "./types.ts";
|
|
12
14
|
import { isPlainObject } from "./equality.ts";
|
|
13
15
|
|
|
14
16
|
/** The mutable form of a node. Only the engine sees it. */
|
|
15
17
|
export type MutableNode = {
|
|
16
18
|
readonly id: NodeId;
|
|
17
19
|
expr: Expr;
|
|
20
|
+
/** The plan of `expr`, which the kernel compiled one time. Each epoch evaluates it. */
|
|
21
|
+
plan: Plan;
|
|
18
22
|
value: Optional<unknown>;
|
|
19
23
|
reads: readonly DepPath[];
|
|
20
24
|
readonly seats: Set<NodeId>;
|
|
@@ -49,6 +53,10 @@ export type StoreState = {
|
|
|
49
53
|
readonly nodes: Map<NodeId, MutableNode>;
|
|
50
54
|
readonly nodeOps: NodeOps;
|
|
51
55
|
readonly ops: Ops;
|
|
56
|
+
/** This function compiles an expression with the machine of the op registry. Each write compiles its new expression one time. */
|
|
57
|
+
readonly plan: (doc: unknown) => Plan;
|
|
58
|
+
/** The context of each evaluation: the store is the host thing of the root `world`. */
|
|
59
|
+
ctx: Context;
|
|
52
60
|
epochStats: EpochStats | null;
|
|
53
61
|
nextId: number;
|
|
54
62
|
/** For each node ID that is not in the store: the readers whose path starts at it. */
|
|
@@ -65,25 +73,95 @@ export const state = (store: NodeStore): StoreState => store as unknown as Store
|
|
|
65
73
|
/** This function gives the public view of a state. */
|
|
66
74
|
export const publicStore = (s: StoreState): NodeStore => s;
|
|
67
75
|
|
|
68
|
-
/** This function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
/** This function gives the keys of a path as texts. A document from another source can have a number key. */
|
|
77
|
+
const texts = (keys: readonly Key[]): readonly string[] =>
|
|
78
|
+
keys.every((k): k is string => typeof k === "string") ? keys : keys.map(String);
|
|
79
|
+
|
|
80
|
+
/** This function reads a path of a store: `path[0]` is a node ID, and the `deref` op of the store reads the rest. */
|
|
81
|
+
const readPath = (s: StoreState, path: readonly Key[]): Optional<unknown> => {
|
|
82
|
+
const rootId = path[0];
|
|
83
|
+
const root = rootId === undefined ? undefined : s.nodes.get(String(rootId));
|
|
84
|
+
return root ? s.nodeOps.deref(root, path.length === 1 ? [] : texts(path.slice(1)), publicStore(s)) : none;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* This function makes the host thing of a store: the root `world` of its expressions. The kernel gives it each
|
|
89
|
+
* `world` path in one call (`resolve`). The store has no value of its own (`NO_VALUE`).
|
|
90
|
+
*/
|
|
91
|
+
const storeThing = (s: StoreState): HostThing => ({
|
|
92
|
+
[THING]: true,
|
|
93
|
+
label: "node store",
|
|
94
|
+
deref: (key) => {
|
|
95
|
+
const found = readPath(s, [key]);
|
|
96
|
+
return isSome(found) ? found.value : undefined;
|
|
97
|
+
},
|
|
98
|
+
toJson: () => NO_VALUE,
|
|
99
|
+
resolve: (keys) => {
|
|
100
|
+
const found = readPath(s, keys);
|
|
101
|
+
return isSome(found) ? { thing: found.value } : undefined;
|
|
102
|
+
},
|
|
79
103
|
});
|
|
80
104
|
|
|
81
|
-
/** This function
|
|
82
|
-
export const
|
|
105
|
+
/** This function gives a state its context: the store is the root `world`. */
|
|
106
|
+
export const withContext = (s: StoreState): StoreState => {
|
|
107
|
+
s.ctx = context({ world: storeThing(s) });
|
|
108
|
+
return s;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* This function gives the compiler of an op registry. A store compiles each new expression one time, without a
|
|
113
|
+
* cache. Most expressions of a store are new objects, for example the literal of each write.
|
|
114
|
+
*/
|
|
115
|
+
export const compilerOf = (ops: Ops): ((doc: unknown) => Plan) => {
|
|
116
|
+
const machine = machineOf(ops);
|
|
117
|
+
return (doc) => prepare(machine, doc);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** This function makes the state of an empty store. */
|
|
121
|
+
export const makeState = (nodeOps: NodeOps, ops: Ops): StoreState =>
|
|
122
|
+
withContext({
|
|
123
|
+
nodes: new Map(),
|
|
124
|
+
nodeOps,
|
|
125
|
+
ops,
|
|
126
|
+
plan: compilerOf(ops),
|
|
127
|
+
ctx: context(),
|
|
128
|
+
epochStats: null,
|
|
129
|
+
nextId: 0,
|
|
130
|
+
dangling: new Map(),
|
|
131
|
+
pending: null,
|
|
132
|
+
batchDepth: 0,
|
|
133
|
+
epoch: 0,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const NO_READS: readonly DepPath[] = Object.freeze([]);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* This function gives the read paths of an expression: the `world` references that the kernel finds (`refsOf`).
|
|
140
|
+
* The paths have no duplicates and keep the order of the first read. A parameter of a `fn` is not a read.
|
|
141
|
+
*/
|
|
142
|
+
export const readsOf = (expr: Expr): readonly DepPath[] => {
|
|
143
|
+
if (isLiteral(expr)) return NO_READS;
|
|
144
|
+
const out: DepPath[] = [];
|
|
145
|
+
const seen = new Set<string>();
|
|
146
|
+
for (const use of refsOf(renderWorld, expr)) {
|
|
147
|
+
if (!use.free || use.root !== "world") continue;
|
|
148
|
+
// The JSON form of a path is a key without ambiguity: ["a b"] and ["a", "b"] stay separate
|
|
149
|
+
const path = use.path.slice(1).map(String);
|
|
150
|
+
const key = JSON.stringify(path);
|
|
151
|
+
if (seen.has(key)) continue;
|
|
152
|
+
seen.add(key);
|
|
153
|
+
out.push(path);
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/** This function makes a node that is not in a store yet. The kernel compiles its expression one time. */
|
|
159
|
+
export const makeNode = (s: StoreState, id: NodeId, expr: Expr): MutableNode => ({
|
|
83
160
|
id,
|
|
84
161
|
expr,
|
|
162
|
+
plan: s.plan(expr),
|
|
85
163
|
value: none,
|
|
86
|
-
reads:
|
|
164
|
+
reads: readsOf(expr),
|
|
87
165
|
seats: new Set(),
|
|
88
166
|
seatsStructural: new Set(),
|
|
89
167
|
seatedOn: new Set(),
|
|
@@ -112,23 +190,29 @@ export const freshId = (s: StoreState, hint?: string): NodeId => {
|
|
|
112
190
|
}
|
|
113
191
|
};
|
|
114
192
|
|
|
115
|
-
/** This function makes the `record`
|
|
193
|
+
/** This function makes the `record` construct of a container: one field for each slot, a `world` reference to the slot. */
|
|
116
194
|
export const recordOf = (n: MutableNode): Expr => {
|
|
117
|
-
const
|
|
118
|
-
for (const name of n.slots.keys())
|
|
119
|
-
|
|
195
|
+
const fields: Record<string, Json> = {};
|
|
196
|
+
for (const name of n.slots.keys()) {
|
|
197
|
+
// An own data property, thus a slot "__proto__" stays a field
|
|
198
|
+
Object.defineProperty(fields, name, { value: { type: "ref", value: ["world", n.id, name] }, enumerable: true, writable: true, configurable: true });
|
|
199
|
+
}
|
|
200
|
+
return { type: "record", value: fields };
|
|
120
201
|
};
|
|
121
202
|
|
|
122
|
-
/** This function tells if the expression of a container is the record of its current slots. */
|
|
203
|
+
/** This function tells if the expression of a container is the record of its current slots, in their order. */
|
|
123
204
|
const isRecordOf = (n: MutableNode): boolean => {
|
|
124
205
|
const e = n.expr;
|
|
125
|
-
if (e.
|
|
206
|
+
if (e.type !== "record" || !isRecord(e.value)) return false;
|
|
207
|
+
const fields = e.value;
|
|
208
|
+
const names = Object.keys(fields);
|
|
209
|
+
if (names.length !== n.slots.size) return false;
|
|
126
210
|
let i = 0;
|
|
127
211
|
for (const name of n.slots.keys()) {
|
|
128
|
-
|
|
129
|
-
const r =
|
|
130
|
-
|
|
131
|
-
if (
|
|
212
|
+
if (names[i++] !== name) return false;
|
|
213
|
+
const r = fields[name] as { readonly type?: unknown; readonly value?: unknown } | null;
|
|
214
|
+
const path = r?.type === "ref" && Array.isArray(r.value) ? (r.value as readonly unknown[]) : undefined;
|
|
215
|
+
if (path?.length !== 3 || path[0] !== "world" || path[1] !== n.id || path[2] !== name) return false;
|
|
132
216
|
}
|
|
133
217
|
return true;
|
|
134
218
|
};
|
|
@@ -233,12 +317,16 @@ export const unwireNode = (s: StoreState, n: MutableNode): void => {
|
|
|
233
317
|
};
|
|
234
318
|
|
|
235
319
|
/** This function changes the expression of a node and keeps its reads correct. The caller touches the node. */
|
|
236
|
-
export const replaceExpr = (s: StoreState, n: MutableNode, expr: Expr): void => {
|
|
320
|
+
export const replaceExpr = (s: StoreState, n: MutableNode, expr: Expr, reads: readonly DepPath[] = readsOf(expr)): void => {
|
|
237
321
|
unwireNode(s, n);
|
|
238
322
|
n.expr = expr;
|
|
239
|
-
n.
|
|
323
|
+
n.plan = s.plan(expr);
|
|
324
|
+
n.reads = reads;
|
|
240
325
|
};
|
|
241
326
|
|
|
327
|
+
/** This function gives the reads of the record of a container: one path to each slot. */
|
|
328
|
+
const recordReads = (n: MutableNode): readonly DepPath[] => Array.from(n.slots.keys(), (name) => [n.id, name]);
|
|
329
|
+
|
|
242
330
|
// === Pending work, batches and the flush ===
|
|
243
331
|
|
|
244
332
|
const pending = (s: StoreState): Pending =>
|
|
@@ -294,7 +382,7 @@ export const expandNow = (s: StoreState, id: NodeId): void => {
|
|
|
294
382
|
continue;
|
|
295
383
|
}
|
|
296
384
|
const [key, fieldValue] = field;
|
|
297
|
-
const sub = makeNode(freshId(s, `${frame.owner.id}.${key}`), lit(fieldValue));
|
|
385
|
+
const sub = makeNode(s, freshId(s, `${frame.owner.id}.${key}`), lit(fieldValue));
|
|
298
386
|
sub.parent = frame.owner.id;
|
|
299
387
|
sub.value = some(fieldValue);
|
|
300
388
|
insertNode(s, sub);
|
|
@@ -376,7 +464,7 @@ const flush = (s: StoreState): void => {
|
|
|
376
464
|
for (const id of p.records) {
|
|
377
465
|
const n = s.nodes.get(id);
|
|
378
466
|
if (!n) continue;
|
|
379
|
-
if (!isRecordOf(n)) replaceExpr(s, n, recordOf(n));
|
|
467
|
+
if (!isRecordOf(n)) replaceExpr(s, n, recordOf(n), recordReads(n));
|
|
380
468
|
p.touched.add(id);
|
|
381
469
|
}
|
|
382
470
|
for (const id of p.touched) {
|
|
@@ -420,12 +508,11 @@ const flush = (s: StoreState): void => {
|
|
|
420
508
|
|
|
421
509
|
// === The epoch ===
|
|
422
510
|
|
|
423
|
-
/**
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
};
|
|
511
|
+
/**
|
|
512
|
+
* This function makes the resolver of a store: `path[0]` is a node ID. The expressions of the store read the same
|
|
513
|
+
* paths through the host thing of the store.
|
|
514
|
+
*/
|
|
515
|
+
export const resolverOf = (store: NodeStore): Resolver => (path) => readPath(state(store), path);
|
|
429
516
|
|
|
430
517
|
/**
|
|
431
518
|
* The limit of the rounds of a cycle in one epoch. Like the iterative calculation of a spreadsheet, the members
|
|
@@ -530,15 +617,16 @@ const runEpoch = (
|
|
|
530
617
|
// 2. The order: the components, with each component after its inputs
|
|
531
618
|
const order = components(closure);
|
|
532
619
|
|
|
533
|
-
// 3. The evaluation of the dirty nodes, in order
|
|
534
|
-
const
|
|
620
|
+
// 3. The evaluation of the dirty nodes, in order. The kernel evaluates the plan of each node.
|
|
621
|
+
const ctx = s.ctx;
|
|
535
622
|
const evaluated = new Set<NodeId>();
|
|
536
623
|
const changed = new Set<NodeId>();
|
|
537
624
|
const cyclic = new Set<NodeId>();
|
|
538
625
|
const evaluateNode = (n: MutableNode): boolean => {
|
|
539
626
|
n.dirty = 0;
|
|
540
627
|
evaluated.add(n.id);
|
|
541
|
-
|
|
628
|
+
const result = n.plan.evaluate(ctx);
|
|
629
|
+
if (!s.nodeOps.splash(result.ok ? some(result.value) : none, n, store)) return false;
|
|
542
630
|
changed.add(n.id);
|
|
543
631
|
for (const m of n.next!) m.dirty = epoch;
|
|
544
632
|
return true;
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type {
|
|
2
|
-
NodeId, Node, NodeOps, SplashFn, SplashTarget, FlowFn, DerefFn, TargetsFn, ReadTargets, NodeStore, EpochStats, StoreOptions,
|
|
2
|
+
DepPath, NodeId, Node, NodeOps, SplashFn, SplashTarget, FlowFn, DerefFn, TargetsFn, ReadTargets, NodeStore, EpochStats, StoreOptions,
|
|
3
3
|
} from "./types.ts";
|
|
4
4
|
export { defaultSplash, defaultFlow, defaultDeref, defaultTargets, defaultOps } from "./ops.ts";
|
|
5
5
|
export {
|
|
@@ -7,6 +7,6 @@ export {
|
|
|
7
7
|
setSlot, expandNode, removeNode, resolveAll,
|
|
8
8
|
} from "./writes.ts";
|
|
9
9
|
export { MAX_CYCLE_ROUNDS } from "./engine.ts";
|
|
10
|
-
export { readTargets, resolverOf as storeResolver } from "./engine.ts";
|
|
10
|
+
export { readTargets, readsOf, resolverOf as storeResolver } from "./engine.ts";
|
|
11
11
|
export { toposort } from "./toposort.ts";
|
|
12
12
|
export { valueEquals, isPlainObject } from "./equality.ts";
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Expr, Ops } from "@mark1russell7/render-dsl";
|
|
2
2
|
import type { Optional } from "@mark1russell7/render-optional";
|
|
3
3
|
|
|
4
|
+
/** A read path of an expression: the segments of a `world` reference. The first segment is a node ID. */
|
|
5
|
+
export type DepPath = readonly string[];
|
|
6
|
+
|
|
4
7
|
/** The identity of a node in its store. */
|
|
5
8
|
export type NodeId = string;
|
|
6
9
|
|
|
@@ -13,13 +16,13 @@ export type NodeId = string;
|
|
|
13
16
|
export type Node = {
|
|
14
17
|
readonly id: NodeId;
|
|
15
18
|
/**
|
|
16
|
-
* The expression is the source of truth. The value is a cache of its evaluation.
|
|
19
|
+
* The expression is the source of truth: a coeval document. The value is a cache of its evaluation.
|
|
17
20
|
* A write changes the expression, thus a later evaluation cannot revert the write.
|
|
18
21
|
*/
|
|
19
22
|
readonly expr: Expr;
|
|
20
23
|
/** The value of the expression after the last epoch. `none` is a value: the expression has no result. */
|
|
21
24
|
readonly value: Optional<unknown>;
|
|
22
|
-
/** The
|
|
25
|
+
/** The `world` reference paths of `expr`, without duplicates: the free references that the kernel finds (`refsOf`). */
|
|
23
26
|
readonly reads: readonly DepPath[];
|
|
24
27
|
/** The value readers: the nodes whose read path ends on this node. */
|
|
25
28
|
readonly seats: ReadonlySet<NodeId>;
|
|
@@ -33,7 +36,7 @@ export type Node = {
|
|
|
33
36
|
readonly heldBy: ReadonlySet<NodeId>;
|
|
34
37
|
/**
|
|
35
38
|
* The named slots of a container. A node with slots is a container: its expression is a `record`
|
|
36
|
-
*
|
|
39
|
+
* construct over its slots, thus its value is the record of their values.
|
|
37
40
|
*/
|
|
38
41
|
readonly slots: ReadonlyMap<string, NodeId>;
|
|
39
42
|
};
|
package/src/writes.ts
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
* the end of the enclosing batch, the value of each node agrees with its expression.
|
|
4
4
|
* The seats agree with the reads too. `store.epochStats` records the epoch of the last operation.
|
|
5
5
|
*/
|
|
6
|
-
import type { Expr
|
|
7
|
-
import {
|
|
6
|
+
import type { Expr } from "@mark1russell7/render-dsl";
|
|
7
|
+
import { lit } from "@mark1russell7/render-dsl";
|
|
8
8
|
import type { Optional } from "@mark1russell7/render-optional";
|
|
9
9
|
import { none } from "@mark1russell7/render-optional";
|
|
10
10
|
import type { Node, NodeId, NodeStore, StoreOptions } from "./types.ts";
|
|
11
11
|
import type { MutableNode, StoreState } from "./engine.ts";
|
|
12
12
|
import {
|
|
13
13
|
expandNow, flushUnlessBatched, freshId, insertNode, linkSlot, makeNode, makeState, publicStore, replaceExpr,
|
|
14
|
-
runBatch, state, touch, touchExpansion, touchRecord, unlinkSlot, unwireNode,
|
|
14
|
+
compilerOf, runBatch, state, touch, touchExpansion, touchRecord, unlinkSlot, unwireNode, withContext,
|
|
15
15
|
} from "./engine.ts";
|
|
16
16
|
import { defaultOps } from "./ops.ts";
|
|
17
17
|
|
|
@@ -21,17 +21,21 @@ export const nodeStore = (options: StoreOptions = {}): NodeStore =>
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* This function gives an independent copy of a store: the same nodes, values, slots and seats, and the same
|
|
24
|
-
* counters. A write to one store does not change the other. The expressions and the values are
|
|
25
|
-
* thus the copy shares them. The options replace the semantics or the op registry of the copy, for
|
|
26
|
-
* class semantics of a copied biblo.
|
|
24
|
+
* counters. A write to one store does not change the other. The expressions, their plans and the values are
|
|
25
|
+
* immutable, thus the copy shares them. The options replace the semantics or the op registry of the copy, for
|
|
26
|
+
* example the class semantics of a copied biblo. A new op registry compiles the plans again. The function throws
|
|
27
|
+
* inside a batch, because a batch has pending work.
|
|
27
28
|
*/
|
|
28
29
|
export const forkStore = (store: NodeStore, options: StoreOptions = {}): NodeStore => {
|
|
29
30
|
const s = state(store);
|
|
30
31
|
if (s.pending !== null || s.batchDepth > 0) throw new Error("forkStore: the store is in a batch");
|
|
32
|
+
const ops = options.ops ?? s.ops;
|
|
31
33
|
const copy: StoreState = {
|
|
32
34
|
nodes: new Map(),
|
|
33
35
|
nodeOps: options.nodeOps ?? s.nodeOps,
|
|
34
|
-
ops
|
|
36
|
+
ops,
|
|
37
|
+
plan: ops === s.ops ? s.plan : compilerOf(ops),
|
|
38
|
+
ctx: s.ctx,
|
|
35
39
|
epochStats: s.epochStats,
|
|
36
40
|
nextId: s.nextId,
|
|
37
41
|
dangling: new Map([...s.dangling].map(([id, readers]) => [id, new Set(readers)])),
|
|
@@ -42,6 +46,7 @@ export const forkStore = (store: NodeStore, options: StoreOptions = {}): NodeSto
|
|
|
42
46
|
for (const [id, n] of s.nodes) {
|
|
43
47
|
const node: MutableNode = {
|
|
44
48
|
...n,
|
|
49
|
+
plan: ops === s.ops ? n.plan : copy.plan(n.expr),
|
|
45
50
|
seats: new Set(n.seats),
|
|
46
51
|
seatsStructural: new Set(n.seatsStructural),
|
|
47
52
|
seatedOn: new Set(n.seatedOn),
|
|
@@ -57,7 +62,7 @@ export const forkStore = (store: NodeStore, options: StoreOptions = {}): NodeSto
|
|
|
57
62
|
};
|
|
58
63
|
copy.nodes.set(id, node);
|
|
59
64
|
}
|
|
60
|
-
return publicStore(copy);
|
|
65
|
+
return publicStore(withContext(copy));
|
|
61
66
|
};
|
|
62
67
|
|
|
63
68
|
/** This function gives a node of the store, or `undefined`. */
|
|
@@ -75,11 +80,11 @@ export const batch = <T>(store: NodeStore, fn: () => T): T => runBatch(state(sto
|
|
|
75
80
|
/**
|
|
76
81
|
* This function adds a node with an expression and evaluates it. It gives the ID of the node.
|
|
77
82
|
* The ID is the hint when no node has it, otherwise a fresh ID. A reader that waited for this ID gets rewired.
|
|
78
|
-
* The
|
|
83
|
+
* The kernel compiles the expression one time, and each epoch evaluates the plan.
|
|
79
84
|
*/
|
|
80
|
-
export const addNode = (store: NodeStore, expr: Expr
|
|
85
|
+
export const addNode = (store: NodeStore, expr: Expr, idHint?: string): NodeId => {
|
|
81
86
|
const s = state(store);
|
|
82
|
-
const n = makeNode(freshId(s, idHint),
|
|
87
|
+
const n = makeNode(s, freshId(s, idHint), expr);
|
|
83
88
|
insertNode(s, n);
|
|
84
89
|
flushUnlessBatched(s);
|
|
85
90
|
return n.id;
|
|
@@ -89,14 +94,14 @@ export const addNode = (store: NodeStore, expr: Expr | ExprDocument, idHint?: st
|
|
|
89
94
|
* This function sets the expression of a node. This is the fundamental write.
|
|
90
95
|
* The engine rewires the reads of the node, evaluates it and propagates the change.
|
|
91
96
|
* A write to a container collapses it first: its own slots are removed, and the expression replaces the record.
|
|
92
|
-
* The
|
|
97
|
+
* The kernel compiles the new expression one time.
|
|
93
98
|
*/
|
|
94
|
-
export const setExpr = (store: NodeStore, id: NodeId, expr: Expr
|
|
99
|
+
export const setExpr = (store: NodeStore, id: NodeId, expr: Expr): void => {
|
|
95
100
|
const s = state(store);
|
|
96
101
|
const n = s.nodes.get(id);
|
|
97
102
|
if (n) {
|
|
98
103
|
if (n.slots.size > 0) collapse(s, n);
|
|
99
|
-
replaceExpr(s, n,
|
|
104
|
+
replaceExpr(s, n, expr);
|
|
100
105
|
touch(s, id);
|
|
101
106
|
}
|
|
102
107
|
flushUnlessBatched(s);
|