@markless/runtime 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -161,9 +161,7 @@ function createRuntimeAsyncComputedNodes(asyncComputedInput) {
161
161
  };
162
162
  asyncComputedNodes.set(asyncComputed.graphNodeId, {
163
163
  ...asyncComputed,
164
- demanded: initialSnapshot.status !== "idle",
165
- keyValue: "key" in initialSnapshot ? initialSnapshot.key : void 0,
166
- pendingSnapshotNeedsRunner: initialSnapshot.status === "pending",
164
+ gate: initialSnapshot.status === "pending" ? 1 : 0,
167
165
  snapshot: initialSnapshot,
168
166
  version: initialSnapshot.version
169
167
  });
@@ -177,41 +175,75 @@ function readAsyncComputedNode(node, path, demand) {
177
175
  return readPath(node.snapshot.value, path);
178
176
  }
179
177
  function demandAsyncComputed(input) {
180
- if (!input.node.pendingSnapshotNeedsRunner && input.node.snapshot.status !== "idle") return;
181
- input.node.demanded = true;
182
- startAsyncComputed({
183
- ...input,
184
- key: input.node.key(input.readGraph)
185
- });
178
+ if (input.node.gate !== 1 && input.node.snapshot.status !== "idle") return;
179
+ advanceAsyncComputed(input);
186
180
  }
187
181
  function invalidateAsyncComputed(input) {
188
- if (!input.node.demanded) return;
182
+ if (input.node.snapshot.status === "idle") return;
183
+ advanceAsyncComputed(input);
184
+ }
185
+ function advanceAsyncComputed(input) {
186
+ const visited = new Set([input.node.graphNodeId]);
187
+ let blocked;
188
+ const visit = (graphNodeId) => {
189
+ if (visited.has(graphNodeId)) return;
190
+ visited.add(graphNodeId);
191
+ const dependency = input.asyncComputedNodes.get(graphNodeId);
192
+ if (dependency) {
193
+ if (dependency.snapshot.status === "idle" || dependency.snapshot.status === "pending") {
194
+ blocked = dependency;
195
+ input.demandAsyncComputed(dependency);
196
+ } else if (!blocked && dependency.snapshot.status === "rejected") blocked = dependency;
197
+ return;
198
+ }
199
+ for (const dependency of input.computedNodes.get(graphNodeId)?.dependencies ?? []) visit(dependency.graphNodeId);
200
+ };
201
+ for (const dependency of input.node.dependencies) visit(dependency.graphNodeId);
202
+ if (blocked) {
203
+ commitDependencyBlock(input, blocked.snapshot.status === "rejected" ? "rejected" : "pending", blocked.snapshot.error);
204
+ return;
205
+ }
189
206
  const nextKey = input.node.key(input.readGraph);
190
- if (input.node.snapshot.status !== "idle" && Object.is(input.node.keyValue, nextKey)) return;
207
+ if (!input.node.gate && input.node.snapshot.status !== "idle" && Object.is(input.node.snapshot.key, nextKey)) return;
191
208
  startAsyncComputed({
192
209
  ...input,
193
210
  key: nextKey
194
211
  });
195
212
  }
213
+ function commitDependencyBlock(input, status, error) {
214
+ if (input.node.gate === 2 && (status === "pending" || input.node.snapshot.status === "rejected" && Object.is(input.node.snapshot.error, error))) return;
215
+ input.node.controller?.abort();
216
+ input.node.controller = void 0;
217
+ input.node.gate = 2;
218
+ const version = ++input.node.version;
219
+ const key = input.node.snapshot.key;
220
+ publish(input, status === "pending" ? {
221
+ status,
222
+ version,
223
+ key,
224
+ value: input.node.snapshot.value
225
+ } : {
226
+ status,
227
+ version,
228
+ key,
229
+ error
230
+ });
231
+ }
196
232
  function startAsyncComputed(input) {
197
233
  input.node.controller?.abort();
198
- input.node.pendingSnapshotNeedsRunner = false;
234
+ input.node.gate = 0;
199
235
  const controller = new AbortController();
200
- const version = input.node.version + 1;
236
+ const version = ++input.node.version;
201
237
  input.node.controller = controller;
202
- input.node.keyValue = input.key;
203
- input.node.version = version;
204
- input.node.snapshot = {
238
+ publish(input, {
205
239
  status: "pending",
206
240
  version,
207
241
  key: input.key,
208
242
  value: input.node.snapshot.value
209
- };
243
+ });
210
244
  const commit = (snapshot) => {
211
- if (input.node.version !== version || controller.signal.aborted) return;
212
- input.node.snapshot = snapshot;
213
- input.markDirtyPath(input.node.graphNodeId, []);
214
- input.scheduleFlush();
245
+ if (input.node.version !== version) return;
246
+ publish(input, snapshot);
215
247
  };
216
248
  const commitRejected = (error) => commit({
217
249
  status: "rejected",
@@ -233,6 +265,9 @@ function startAsyncComputed(input) {
233
265
  } catch (error) {
234
266
  commitRejected(error);
235
267
  }
268
+ }
269
+ function publish(input, snapshot) {
270
+ input.node.snapshot = snapshot;
236
271
  input.markDirtyPath(input.node.graphNodeId, []);
237
272
  input.scheduleFlush();
238
273
  }
@@ -258,19 +293,20 @@ function markComputedDirty(input) {
258
293
  if (input.visited.has(input.graphNodeId)) return;
259
294
  input.visited.add(input.graphNodeId);
260
295
  const computed = input.computedNodes.get(input.graphNodeId);
261
- if (computed) computed.dirty = true;
296
+ if (!computed?.compute) return;
297
+ computed.dirty = true;
262
298
  input.dirtyPaths.push({
263
299
  graphNodeId: input.graphNodeId,
264
300
  path: []
265
301
  });
266
- for (const dependent of input.computedNodes.values()) if (dependent.dependencies.some((dependency) => dependency.graphNodeId === input.graphNodeId)) markComputedDirty({
302
+ for (const dependent of input.computedNodes.values()) if (dependent.dependencies.some((dependency) => dependency.graphNodeId === input.graphNodeId) && dependent.compute) markComputedDirty({
267
303
  ...input,
268
304
  graphNodeId: dependent.graphNodeId
269
305
  });
270
306
  for (const dependent of input.asyncComputedNodes.values()) if (dependent.dependencies.some((dependency) => dependency.graphNodeId === input.graphNodeId)) input.invalidateAsyncComputed(dependent);
271
307
  }
272
308
  function markDirtyComputedDependencies(input) {
273
- for (const computed of input.computedNodes.values()) if (computed.dependencies.some((dependency) => dependency.graphNodeId === input.graphNodeId && pathsIntersect(input.path, dependency.path ?? []))) markComputedDirty({
309
+ for (const computed of input.computedNodes.values()) if (computed.dependencies.some((dependency) => dependency.graphNodeId === input.graphNodeId && pathsIntersect(input.path, dependency.path ?? [])) && computed.compute) markComputedDirty({
274
310
  ...input,
275
311
  graphNodeId: computed.graphNodeId,
276
312
  visited: /* @__PURE__ */ new Set()
@@ -394,6 +430,7 @@ function createSharedGraphPlane(input) {
394
430
  //#region src/graph.ts
395
431
  function createRuntimeGraph(input) {
396
432
  const cells = /* @__PURE__ */ new Map();
433
+ const readInitializers = /* @__PURE__ */ new Map();
397
434
  const computedNodes = createRuntimeComputedNodes(input.computed);
398
435
  const asyncComputedNodes = createRuntimeAsyncComputedNodes(input.asyncComputed);
399
436
  const subscriptions = [];
@@ -403,12 +440,28 @@ function createRuntimeGraph(input) {
403
440
  let flushScheduled = false;
404
441
  let flushing = false;
405
442
  let activeFlush;
406
- for (const cell of input.cells) cells.set(cell.graphNodeId, cell.value);
443
+ for (const cell of input.cells) {
444
+ cells.set(cell.graphNodeId, cell.value);
445
+ if (cell.readInitializer) readInitializers.set(cell.graphNodeId, cell.readInitializer);
446
+ }
407
447
  const readGraph = (graphNodeId, path = []) => {
408
448
  const computed = computedNodes.get(graphNodeId);
409
- if (computed) return readComputedNode(computed, readGraph, path);
449
+ if (computed?.compute) return readComputedNode(computed, readGraph, path);
410
450
  const asyncComputed = asyncComputedNodes.get(graphNodeId);
411
451
  if (asyncComputed) return readAsyncComputedNode(asyncComputed, path, () => demandAsyncComputed$1(asyncComputed));
452
+ const initialize = readInitializers.get(graphNodeId);
453
+ if (initialize) {
454
+ readInitializers.delete(graphNodeId);
455
+ const current = cells.get(graphNodeId);
456
+ try {
457
+ const value = initialize();
458
+ if (!Object.is(current, value)) {
459
+ cells.set(graphNodeId, value);
460
+ markDirtyPath(graphNodeId, dirtyPathForGraphWrite(current, []));
461
+ scheduleFlush();
462
+ }
463
+ } catch {}
464
+ }
412
465
  return readPath(cells.get(graphNodeId), path);
413
466
  };
414
467
  const markDirtyPath = (graphNodeId, path) => {
@@ -432,22 +485,22 @@ function createRuntimeGraph(input) {
432
485
  flush();
433
486
  });
434
487
  };
435
- const demandAsyncComputed$1 = (node) => {
436
- demandAsyncComputed({
437
- node,
438
- readGraph,
439
- markDirtyPath,
440
- scheduleFlush
441
- });
442
- };
443
- const invalidateAsyncComputed$1 = (node) => {
444
- invalidateAsyncComputed({
445
- node,
446
- readGraph,
447
- markDirtyPath,
448
- scheduleFlush
449
- });
450
- };
488
+ const asyncComputedInput = () => ({
489
+ computedNodes,
490
+ asyncComputedNodes,
491
+ demandAsyncComputed: demandAsyncComputed$1,
492
+ readGraph,
493
+ markDirtyPath,
494
+ scheduleFlush
495
+ });
496
+ const demandAsyncComputed$1 = (node) => demandAsyncComputed({
497
+ node,
498
+ ...asyncComputedInput()
499
+ });
500
+ const invalidateAsyncComputed$1 = (node) => invalidateAsyncComputed({
501
+ node,
502
+ ...asyncComputedInput()
503
+ });
451
504
  const sharedGraph = createSharedGraphPlane({
452
505
  cells,
453
506
  sharedDefinitionsInput: input.sharedDefinitions,
@@ -491,6 +544,7 @@ function createRuntimeGraph(input) {
491
544
  };
492
545
  return {
493
546
  read: readGraph,
547
+ ...typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__ ? { peekAsyncSnapshot: (graphNodeId) => asyncComputedNodes.get(graphNodeId)?.snapshot } : {},
494
548
  readShared: sharedGraph.readShared,
495
549
  writeShared: sharedGraph.writeShared,
496
550
  getSharedDefinition: sharedGraph.getSharedDefinition,
package/dist/graph.d.ts CHANGED
@@ -4,6 +4,7 @@ import { ProtocolStatePayload } from "@markless/serializer";
4
4
  type RuntimeGraphCell = {
5
5
  readonly graphNodeId: string;
6
6
  readonly value: unknown;
7
+ readonly readInitializer?: () => unknown;
7
8
  };
8
9
  type RuntimeGraphRead = (graphNodeId: string, path?: ReadonlyArray<string>) => unknown;
9
10
  type RuntimeGraphComputedDependency = {
@@ -15,6 +16,9 @@ type RuntimeGraphComputed = {
15
16
  readonly dependencies: ReadonlyArray<RuntimeGraphComputedDependency>;
16
17
  readonly compute: (read: RuntimeGraphRead) => unknown;
17
18
  };
19
+ type RuntimeGraphComputedDependencyNode = Omit<RuntimeGraphComputed, 'compute'> & {
20
+ readonly compute?: never;
21
+ };
18
22
  type RuntimeGraphAsyncSnapshot = {
19
23
  readonly status: 'idle';
20
24
  readonly version: 0;
@@ -73,7 +77,7 @@ type DomJournalResult = DomJournalEntry | ReadonlyArray<DomJournalEntry>;
73
77
  type DomJournalListener = (entries: ReadonlyArray<DomJournalEntry>) => void | Promise<void>;
74
78
  type RuntimeGraphInput = {
75
79
  readonly cells: ReadonlyArray<RuntimeGraphCell>;
76
- readonly computed?: ReadonlyArray<RuntimeGraphComputed>;
80
+ readonly computed?: ReadonlyArray<RuntimeGraphComputed | RuntimeGraphComputedDependencyNode>;
77
81
  readonly asyncComputed?: ReadonlyArray<RuntimeGraphAsyncComputed>;
78
82
  readonly sharedDefinitions?: ProtocolStatePayload['sharedDefinitions'];
79
83
  };
@@ -118,7 +122,8 @@ type RuntimeGraphSubscription = {
118
122
  readonly run: (value: unknown) => DomJournalResult | void | Promise<DomJournalResult | void>;
119
123
  };
120
124
  type RuntimeGraph = {
121
- readonly read: (graphNodeId: string, path?: ReadonlyArray<string>) => unknown;
125
+ readonly read: (graphNodeId: string, path?: ReadonlyArray<string>) => unknown; /** Available only in debug-enabled builds (`__MARKLESS_DEBUG_ENABLED__`). */
126
+ readonly peekAsyncSnapshot?: (graphNodeId: string) => RuntimeGraphAsyncSnapshot | undefined;
122
127
  readonly readShared: (definitionId: string, propertyName: string, path?: ReadonlyArray<string>) => unknown;
123
128
  readonly writeShared: (write: RuntimeGraphSharedWrite) => boolean;
124
129
  readonly getSharedDefinition: (definitionId: string) => NonNullable<ProtocolStatePayload['sharedDefinitions']>[number] | undefined;
@@ -137,4 +142,4 @@ type RuntimeGraph = {
137
142
  type RuntimeSharedDefinition = NonNullable<ProtocolStatePayload['sharedDefinitions']>[number];
138
143
  declare function createRuntimeGraph(input: RuntimeGraphInput): RuntimeGraph;
139
144
  //#endregion
140
- export { DomJournalEntry, DomJournalListener, DomJournalResult, RuntimeGraph, RuntimeGraphAsyncComputed, RuntimeGraphAsyncSnapshot, RuntimeGraphCall, RuntimeGraphCell, RuntimeGraphComputed, RuntimeGraphComputedDependency, RuntimeGraphDelete, RuntimeGraphInput, RuntimeGraphRead, RuntimeGraphSharedPatch, RuntimeGraphSharedPatchOperation, RuntimeGraphSharedWrite, RuntimeGraphSubscription, RuntimeGraphUpdate, RuntimeGraphWrite, createRuntimeGraph };
145
+ export { DomJournalEntry, DomJournalListener, DomJournalResult, RuntimeGraph, RuntimeGraphAsyncComputed, RuntimeGraphAsyncSnapshot, RuntimeGraphCall, RuntimeGraphCell, RuntimeGraphComputed, RuntimeGraphComputedDependency, RuntimeGraphComputedDependencyNode, RuntimeGraphDelete, RuntimeGraphInput, RuntimeGraphRead, RuntimeGraphSharedPatch, RuntimeGraphSharedPatchOperation, RuntimeGraphSharedWrite, RuntimeGraphSubscription, RuntimeGraphUpdate, RuntimeGraphWrite, createRuntimeGraph };
package/dist/graph.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as createRuntimeGraph } from "./graph-B7awrnzg.js";
1
+ import { t as createRuntimeGraph } from "./graph-P12yh-2u.js";
2
2
  export { createRuntimeGraph };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { DomJournalEntry, DomJournalListener, DomJournalResult, RuntimeGraph, RuntimeGraphAsyncComputed, RuntimeGraphAsyncSnapshot, RuntimeGraphCall, RuntimeGraphCell, RuntimeGraphComputed, RuntimeGraphComputedDependency, RuntimeGraphDelete, RuntimeGraphInput, RuntimeGraphRead, RuntimeGraphSharedPatch, RuntimeGraphSharedPatchOperation, RuntimeGraphSharedWrite, RuntimeGraphSubscription, RuntimeGraphUpdate, RuntimeGraphWrite, createRuntimeGraph } from "./graph.js";
2
- export { DomJournalEntry, DomJournalListener, DomJournalResult, RuntimeGraph, RuntimeGraphAsyncComputed, RuntimeGraphAsyncSnapshot, RuntimeGraphCall, RuntimeGraphCell, RuntimeGraphComputed, RuntimeGraphComputedDependency, RuntimeGraphDelete, RuntimeGraphInput, RuntimeGraphRead, RuntimeGraphSharedPatch, RuntimeGraphSharedPatchOperation, RuntimeGraphSharedWrite, RuntimeGraphSubscription, RuntimeGraphUpdate, RuntimeGraphWrite, createRuntimeGraph };
1
+ import { DomJournalEntry, DomJournalListener, DomJournalResult, RuntimeGraph, RuntimeGraphAsyncComputed, RuntimeGraphAsyncSnapshot, RuntimeGraphCall, RuntimeGraphCell, RuntimeGraphComputed, RuntimeGraphComputedDependency, RuntimeGraphComputedDependencyNode, RuntimeGraphDelete, RuntimeGraphInput, RuntimeGraphRead, RuntimeGraphSharedPatch, RuntimeGraphSharedPatchOperation, RuntimeGraphSharedWrite, RuntimeGraphSubscription, RuntimeGraphUpdate, RuntimeGraphWrite, createRuntimeGraph } from "./graph.js";
2
+ export { DomJournalEntry, DomJournalListener, DomJournalResult, RuntimeGraph, RuntimeGraphAsyncComputed, RuntimeGraphAsyncSnapshot, RuntimeGraphCall, RuntimeGraphCell, RuntimeGraphComputed, RuntimeGraphComputedDependency, RuntimeGraphComputedDependencyNode, RuntimeGraphDelete, RuntimeGraphInput, RuntimeGraphRead, RuntimeGraphSharedPatch, RuntimeGraphSharedPatchOperation, RuntimeGraphSharedWrite, RuntimeGraphSubscription, RuntimeGraphUpdate, RuntimeGraphWrite, createRuntimeGraph };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as createRuntimeGraph } from "./graph-B7awrnzg.js";
1
+ import { t as createRuntimeGraph } from "./graph-P12yh-2u.js";
2
2
  export { createRuntimeGraph };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markless/runtime",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Markless state graph runtime: reads, writes, computed invalidation, flush journal.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,7 +9,8 @@
9
9
  "directory": "packages/runtime"
10
10
  },
11
11
  "publishConfig": {
12
- "access": "public"
12
+ "access": "public",
13
+ "provenance": true
13
14
  },
14
15
  "type": "module",
15
16
  "sideEffects": false,
@@ -24,7 +25,7 @@
24
25
  }
25
26
  },
26
27
  "dependencies": {
27
- "@markless/serializer": "0.1.1"
28
+ "@markless/serializer": "0.2.0"
28
29
  },
29
30
  "files": [
30
31
  "dist"