@dxos/app-graph 0.8.4-main.bc674ce → 0.8.4-main.c351d160a8
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/dist/lib/browser/index.mjs +480 -198
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +480 -198
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/graph-builder.d.ts +11 -7
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +13 -17
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/node-matcher.d.ts +4 -4
- package/dist/types/src/node-matcher.d.ts.map +1 -1
- package/dist/types/src/node.d.ts +15 -5
- package/dist/types/src/node.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +34 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -25
- package/src/graph-builder.test.ts +444 -102
- package/src/graph-builder.ts +191 -72
- package/src/graph.test.ts +187 -52
- package/src/graph.ts +179 -98
- package/src/node-matcher.ts +8 -10
- package/src/node.ts +20 -5
- package/src/stories/EchoGraph.stories.tsx +86 -56
- package/src/stories/Tree.tsx +1 -1
- package/src/util.ts +71 -0
|
@@ -41,6 +41,8 @@ __export(graph_exports, {
|
|
|
41
41
|
getRoot: () => getRoot,
|
|
42
42
|
initialize: () => initialize,
|
|
43
43
|
make: () => make,
|
|
44
|
+
relationFromKey: () => relationFromKey,
|
|
45
|
+
relationKey: () => relationKey,
|
|
44
46
|
removeEdge: () => removeEdge,
|
|
45
47
|
removeEdges: () => removeEdges,
|
|
46
48
|
removeNode: () => removeNode,
|
|
@@ -57,7 +59,7 @@ import * as Pipeable from "effect/Pipeable";
|
|
|
57
59
|
import * as Record from "effect/Record";
|
|
58
60
|
import { Event, Trigger } from "@dxos/async";
|
|
59
61
|
import { todo } from "@dxos/debug";
|
|
60
|
-
import { invariant } from "@dxos/invariant";
|
|
62
|
+
import { invariant as invariant2 } from "@dxos/invariant";
|
|
61
63
|
import { log } from "@dxos/log";
|
|
62
64
|
import { isNonNullable } from "@dxos/util";
|
|
63
65
|
|
|
@@ -69,29 +71,79 @@ __export(node_exports, {
|
|
|
69
71
|
RootId: () => RootId,
|
|
70
72
|
RootType: () => RootType,
|
|
71
73
|
actionGroupSymbol: () => actionGroupSymbol,
|
|
74
|
+
actionRelation: () => actionRelation,
|
|
75
|
+
childRelation: () => childRelation,
|
|
72
76
|
isAction: () => isAction,
|
|
73
77
|
isActionGroup: () => isActionGroup,
|
|
74
78
|
isActionLike: () => isActionLike,
|
|
75
|
-
isGraphNode: () => isGraphNode
|
|
79
|
+
isGraphNode: () => isGraphNode,
|
|
80
|
+
relation: () => relation
|
|
76
81
|
});
|
|
77
82
|
var RootId = "root";
|
|
78
|
-
var RootType = "dxos.
|
|
79
|
-
var ActionType = "dxos.
|
|
80
|
-
var ActionGroupType = "dxos.
|
|
83
|
+
var RootType = "org.dxos.type.graph-root";
|
|
84
|
+
var ActionType = "org.dxos.type.graph-action";
|
|
85
|
+
var ActionGroupType = "org.dxos.type.graph-action-group";
|
|
86
|
+
var relation = (kind, direction = "outbound") => ({
|
|
87
|
+
kind,
|
|
88
|
+
direction
|
|
89
|
+
});
|
|
90
|
+
var childRelation = (direction = "outbound") => relation("child", direction);
|
|
91
|
+
var actionRelation = (direction = "outbound") => relation("action", direction);
|
|
81
92
|
var isGraphNode = (data) => data && typeof data === "object" && "id" in data && "properties" in data && data.properties ? typeof data.properties === "object" && "data" in data : false;
|
|
82
93
|
var isAction = (data) => isGraphNode(data) ? typeof data.data === "function" && data.type === ActionType : false;
|
|
83
|
-
var actionGroupSymbol = Symbol("ActionGroup");
|
|
94
|
+
var actionGroupSymbol = /* @__PURE__ */ Symbol("ActionGroup");
|
|
84
95
|
var isActionGroup = (data) => isGraphNode(data) ? data.data === actionGroupSymbol && data.type === ActionGroupType : false;
|
|
85
96
|
var isActionLike = (data) => isAction(data) || isActionGroup(data);
|
|
86
97
|
|
|
98
|
+
// src/util.ts
|
|
99
|
+
import { invariant } from "@dxos/invariant";
|
|
100
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/sdk/app-graph/src/util.ts";
|
|
101
|
+
var Separators = {
|
|
102
|
+
primary: "",
|
|
103
|
+
secondary: "",
|
|
104
|
+
path: "/"
|
|
105
|
+
};
|
|
106
|
+
var normalizeRelation = (relation2) => relation2 == null ? childRelation() : typeof relation2 === "string" ? relation(relation2) : relation2;
|
|
107
|
+
var shallowEqual = (a, b) => {
|
|
108
|
+
if (a === b) return true;
|
|
109
|
+
if (a == null || b == null || typeof a !== "object" || typeof b !== "object") return false;
|
|
110
|
+
const keysA = Object.keys(a);
|
|
111
|
+
const keysB = Object.keys(b);
|
|
112
|
+
if (keysA.length !== keysB.length) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return keysA.every((k) => a[k] === b[k]);
|
|
116
|
+
};
|
|
117
|
+
var nodeArgsUnchanged = (prev, next) => {
|
|
118
|
+
if (prev.length !== next.length) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return prev.every((prevNode, idx) => {
|
|
122
|
+
const nextNode = next[idx];
|
|
123
|
+
return prevNode.id === nextNode.id && prevNode.type === nextNode.type && shallowEqual(prevNode.data, nextNode.data) && shallowEqual(prevNode.properties, nextNode.properties);
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
var qualifyId = (parentId, segmentId) => `${parentId}${Separators.path}${segmentId}`;
|
|
127
|
+
var validateSegmentId = (id) => {
|
|
128
|
+
invariant(!id.includes(Separators.path), `Node segment ID must not contain '${Separators.path}': ${id}`, {
|
|
129
|
+
F: __dxlog_file,
|
|
130
|
+
L: 70,
|
|
131
|
+
S: void 0,
|
|
132
|
+
A: [
|
|
133
|
+
"!id.includes(Separators.path)",
|
|
134
|
+
"`Node segment ID must not contain '${Separators.path}': ${id}`"
|
|
135
|
+
]
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
87
139
|
// src/graph.ts
|
|
88
|
-
var
|
|
89
|
-
var graphSymbol = Symbol("graph");
|
|
140
|
+
var __dxlog_file2 = "/__w/dxos/dxos/packages/sdk/app-graph/src/graph.ts";
|
|
141
|
+
var graphSymbol = /* @__PURE__ */ Symbol("graph");
|
|
90
142
|
var getGraph = (node) => {
|
|
91
143
|
const graph = node[graphSymbol];
|
|
92
|
-
|
|
93
|
-
F:
|
|
94
|
-
L:
|
|
144
|
+
invariant2(graph, "Node is not associated with a graph.", {
|
|
145
|
+
F: __dxlog_file2,
|
|
146
|
+
L: 33,
|
|
95
147
|
S: void 0,
|
|
96
148
|
A: [
|
|
97
149
|
"graph",
|
|
@@ -100,8 +152,8 @@ var getGraph = (node) => {
|
|
|
100
152
|
});
|
|
101
153
|
return graph;
|
|
102
154
|
};
|
|
103
|
-
var GraphTypeId = Symbol.for("@dxos/app-graph/Graph");
|
|
104
|
-
var GraphKind = Symbol.for("@dxos/app-graph/GraphKind");
|
|
155
|
+
var GraphTypeId = /* @__PURE__ */ Symbol.for("@dxos/app-graph/Graph");
|
|
156
|
+
var GraphKind = /* @__PURE__ */ Symbol.for("@dxos/app-graph/GraphKind");
|
|
105
157
|
var GraphImpl = class {
|
|
106
158
|
[GraphTypeId] = GraphTypeId;
|
|
107
159
|
[GraphKind] = "writable";
|
|
@@ -114,6 +166,7 @@ var GraphImpl = class {
|
|
|
114
166
|
_onRemoveNode;
|
|
115
167
|
_registry;
|
|
116
168
|
_expanded = Record.empty();
|
|
169
|
+
_pendingExpands = /* @__PURE__ */ new Set();
|
|
117
170
|
_initialized = Record.empty();
|
|
118
171
|
_initialEdges = Record.empty();
|
|
119
172
|
_initialNodes = Record.fromEntries([
|
|
@@ -135,9 +188,9 @@ var GraphImpl = class {
|
|
|
135
188
|
_nodeOrThrow = Atom2.family((id) => {
|
|
136
189
|
return Atom2.make((get2) => {
|
|
137
190
|
const node = get2(this._node(id));
|
|
138
|
-
|
|
139
|
-
F:
|
|
140
|
-
L:
|
|
191
|
+
invariant2(Option.isSome(node), `Node not available: ${id}`, {
|
|
192
|
+
F: __dxlog_file2,
|
|
193
|
+
L: 172,
|
|
141
194
|
S: this,
|
|
142
195
|
A: [
|
|
143
196
|
"Option.isSome(node)",
|
|
@@ -148,30 +201,33 @@ var GraphImpl = class {
|
|
|
148
201
|
});
|
|
149
202
|
});
|
|
150
203
|
_edges = Atom2.family((id) => {
|
|
151
|
-
const initial = Record.get(this._initialEdges, id).pipe(Option.getOrElse(() => ({
|
|
152
|
-
inbound: [],
|
|
153
|
-
outbound: []
|
|
154
|
-
})));
|
|
204
|
+
const initial = Record.get(this._initialEdges, id).pipe(Option.getOrElse(() => ({})));
|
|
155
205
|
return Atom2.make(initial).pipe(Atom2.keepAlive, Atom2.withLabel(`graph:edges:${id}`));
|
|
156
206
|
});
|
|
157
207
|
// NOTE: Currently the argument to the family needs to be referentially stable for the atom to be referentially stable.
|
|
158
208
|
// TODO(wittjosiah): Atom feature request, support for something akin to `ComplexMap` to allow for complex arguments.
|
|
159
209
|
_connections = Atom2.family((key) => {
|
|
160
210
|
return Atom2.make((get2) => {
|
|
161
|
-
|
|
211
|
+
if (!key || key.indexOf(Separators.primary) <= 0) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const { id, relation: relation2 } = relationFromConnectionKey(key);
|
|
162
215
|
const edges = get2(this._edges(id));
|
|
163
|
-
return edges[
|
|
216
|
+
return (edges[relationKey(relation2)] ?? []).map((id2) => get2(this._node(id2))).filter(Option.isSome).map((o) => o.value);
|
|
164
217
|
}).pipe(Atom2.withLabel(`graph:connections:${key}`));
|
|
165
218
|
});
|
|
166
219
|
_actions = Atom2.family((id) => {
|
|
167
220
|
return Atom2.make((get2) => {
|
|
168
|
-
|
|
221
|
+
if (!id) {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
return get2(this._connections(connectionKey(id, actionRelation())));
|
|
169
225
|
}).pipe(Atom2.withLabel(`graph:actions:${id}`));
|
|
170
226
|
});
|
|
171
227
|
_json = Atom2.family((id) => {
|
|
172
228
|
return Atom2.make((get2) => {
|
|
173
229
|
const toJSON2 = (node, seen = []) => {
|
|
174
|
-
const nodes = get2(this._connections(
|
|
230
|
+
const nodes = get2(this._connections(connectionKey(node.id, "child")));
|
|
175
231
|
const obj = {
|
|
176
232
|
id: node.id,
|
|
177
233
|
type: node.type
|
|
@@ -219,8 +275,8 @@ var GraphImpl = class {
|
|
|
219
275
|
nodeOrThrow(id) {
|
|
220
276
|
return nodeOrThrowImpl(this, id);
|
|
221
277
|
}
|
|
222
|
-
connections(id,
|
|
223
|
-
return connectionsImpl(this, id,
|
|
278
|
+
connections(id, relation2) {
|
|
279
|
+
return connectionsImpl(this, id, relation2);
|
|
224
280
|
}
|
|
225
281
|
actions(id) {
|
|
226
282
|
return actionsImpl(this, id);
|
|
@@ -257,9 +313,9 @@ var nodeOrThrowImpl = (graph, id) => {
|
|
|
257
313
|
const internal = getInternal(graph);
|
|
258
314
|
return internal._nodeOrThrow(id);
|
|
259
315
|
};
|
|
260
|
-
var connectionsImpl = (graph, id,
|
|
316
|
+
var connectionsImpl = (graph, id, relation2) => {
|
|
261
317
|
const internal = getInternal(graph);
|
|
262
|
-
return internal._connections(
|
|
318
|
+
return internal._connections(connectionKey(id, relation2));
|
|
263
319
|
};
|
|
264
320
|
var actionsImpl = (graph, id) => {
|
|
265
321
|
const internal = getInternal(graph);
|
|
@@ -298,19 +354,28 @@ function getNodeOrThrow(graphOrId, id) {
|
|
|
298
354
|
function getRoot(graph) {
|
|
299
355
|
return getNodeOrThrowImpl(graph, RootId);
|
|
300
356
|
}
|
|
301
|
-
var getConnectionsImpl = (graph, id,
|
|
357
|
+
var getConnectionsImpl = (graph, id, relation2) => {
|
|
302
358
|
const internal = getInternal(graph);
|
|
303
|
-
return internal._registry.get(connectionsImpl(graph, id,
|
|
359
|
+
return internal._registry.get(connectionsImpl(graph, id, relation2));
|
|
304
360
|
};
|
|
305
|
-
function getConnections(graphOrId, idOrRelation,
|
|
361
|
+
function getConnections(graphOrId, idOrRelation, relation2) {
|
|
306
362
|
if (typeof graphOrId === "string") {
|
|
307
363
|
const id = graphOrId;
|
|
308
|
-
const rel =
|
|
364
|
+
const rel = idOrRelation;
|
|
309
365
|
return (graph) => getConnectionsImpl(graph, id, rel);
|
|
310
366
|
} else {
|
|
311
367
|
const graph = graphOrId;
|
|
312
368
|
const id = idOrRelation;
|
|
313
|
-
|
|
369
|
+
invariant2(relation2 !== void 0, "Relation is required.", {
|
|
370
|
+
F: __dxlog_file2,
|
|
371
|
+
L: 446,
|
|
372
|
+
S: this,
|
|
373
|
+
A: [
|
|
374
|
+
"relation !== undefined",
|
|
375
|
+
"'Relation is required.'"
|
|
376
|
+
]
|
|
377
|
+
});
|
|
378
|
+
const rel = relation2;
|
|
314
379
|
return getConnectionsImpl(graph, id, rel);
|
|
315
380
|
}
|
|
316
381
|
}
|
|
@@ -341,7 +406,7 @@ function getEdges(graphOrId, id) {
|
|
|
341
406
|
}
|
|
342
407
|
}
|
|
343
408
|
var traverseImpl = (graph, options, path = []) => {
|
|
344
|
-
const { visitor, source = RootId, relation
|
|
409
|
+
const { visitor, source = RootId, relation: relation2 } = options;
|
|
345
410
|
if (path.includes(source)) {
|
|
346
411
|
return;
|
|
347
412
|
}
|
|
@@ -353,14 +418,25 @@ var traverseImpl = (graph, options, path = []) => {
|
|
|
353
418
|
if (shouldContinue === false) {
|
|
354
419
|
return;
|
|
355
420
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
421
|
+
const relations = Array.isArray(relation2) ? relation2 : [
|
|
422
|
+
relation2
|
|
423
|
+
];
|
|
424
|
+
const seen = /* @__PURE__ */ new Set();
|
|
425
|
+
for (const rel of relations) {
|
|
426
|
+
for (const connected of getConnections(graph, source, rel)) {
|
|
427
|
+
if (!seen.has(connected.id)) {
|
|
428
|
+
seen.add(connected.id);
|
|
429
|
+
traverseImpl(graph, {
|
|
430
|
+
source: connected.id,
|
|
431
|
+
relation: relation2,
|
|
432
|
+
visitor
|
|
433
|
+
}, [
|
|
434
|
+
...path,
|
|
435
|
+
source
|
|
436
|
+
]);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
364
440
|
};
|
|
365
441
|
function traverse(graphOrOptions, optionsOrPath, path) {
|
|
366
442
|
if (typeof graphOrOptions === "object" && "visitor" in graphOrOptions) {
|
|
@@ -379,6 +455,7 @@ var getPathImpl = (graph, params) => {
|
|
|
379
455
|
let found = Option.none();
|
|
380
456
|
traverseImpl(graph, {
|
|
381
457
|
source: node.id,
|
|
458
|
+
relation: "child",
|
|
382
459
|
visitor: (node2, path) => {
|
|
383
460
|
if (Option.isSome(found)) {
|
|
384
461
|
return false;
|
|
@@ -435,14 +512,14 @@ var initializeImpl = async (graph, id) => {
|
|
|
435
512
|
id,
|
|
436
513
|
initialized
|
|
437
514
|
}, {
|
|
438
|
-
F:
|
|
439
|
-
L:
|
|
515
|
+
F: __dxlog_file2,
|
|
516
|
+
L: 668,
|
|
440
517
|
S: void 0,
|
|
441
518
|
C: (f, a) => f(...a)
|
|
442
519
|
});
|
|
443
520
|
if (!initialized) {
|
|
444
|
-
await internal._onInitialize?.(id);
|
|
445
521
|
Record.set(internal._initialized, id, true);
|
|
522
|
+
await internal._onInitialize?.(id);
|
|
446
523
|
}
|
|
447
524
|
return graph;
|
|
448
525
|
};
|
|
@@ -455,61 +532,93 @@ function initialize(graphOrId, id) {
|
|
|
455
532
|
return initializeImpl(graph, id);
|
|
456
533
|
}
|
|
457
534
|
}
|
|
458
|
-
var expandImpl = (graph, id,
|
|
535
|
+
var expandImpl = (graph, id, relation2) => {
|
|
459
536
|
const internal = getInternal(graph);
|
|
460
|
-
const
|
|
537
|
+
const normalizedRelation = normalizeRelation(relation2);
|
|
538
|
+
const key = `${id}${Separators.primary}${relationKey(normalizedRelation)}`;
|
|
539
|
+
const nodeOpt = internal._registry.get(internal._node(id));
|
|
540
|
+
if (Option.isNone(nodeOpt)) {
|
|
541
|
+
internal._pendingExpands.add(key);
|
|
542
|
+
log("expand", {
|
|
543
|
+
key,
|
|
544
|
+
deferred: true
|
|
545
|
+
}, {
|
|
546
|
+
F: __dxlog_file2,
|
|
547
|
+
L: 714,
|
|
548
|
+
S: void 0,
|
|
549
|
+
C: (f, a) => f(...a)
|
|
550
|
+
});
|
|
551
|
+
return graph;
|
|
552
|
+
}
|
|
461
553
|
const expanded = Record.get(internal._expanded, key).pipe(Option.getOrElse(() => false));
|
|
462
554
|
log("expand", {
|
|
463
555
|
key,
|
|
464
556
|
expanded
|
|
465
557
|
}, {
|
|
466
|
-
F:
|
|
467
|
-
L:
|
|
558
|
+
F: __dxlog_file2,
|
|
559
|
+
L: 719,
|
|
468
560
|
S: void 0,
|
|
469
561
|
C: (f, a) => f(...a)
|
|
470
562
|
});
|
|
471
563
|
if (!expanded) {
|
|
472
|
-
internal._onExpand?.(id, relation);
|
|
473
564
|
Record.set(internal._expanded, key, true);
|
|
565
|
+
internal._onExpand?.(id, normalizedRelation);
|
|
474
566
|
}
|
|
475
567
|
return graph;
|
|
476
568
|
};
|
|
477
|
-
function expand(graphOrId, idOrRelation,
|
|
569
|
+
function expand(graphOrId, idOrRelation, relation2) {
|
|
478
570
|
if (typeof graphOrId === "string") {
|
|
479
571
|
const id = graphOrId;
|
|
480
|
-
const rel =
|
|
572
|
+
const rel = idOrRelation;
|
|
481
573
|
return (graph) => expandImpl(graph, id, rel);
|
|
482
574
|
} else {
|
|
483
575
|
const graph = graphOrId;
|
|
484
576
|
const id = idOrRelation;
|
|
485
|
-
|
|
577
|
+
invariant2(relation2 !== void 0, "Relation is required.", {
|
|
578
|
+
F: __dxlog_file2,
|
|
579
|
+
L: 755,
|
|
580
|
+
S: this,
|
|
581
|
+
A: [
|
|
582
|
+
"relation !== undefined",
|
|
583
|
+
"'Relation is required.'"
|
|
584
|
+
]
|
|
585
|
+
});
|
|
586
|
+
const rel = relation2;
|
|
486
587
|
return expandImpl(graph, id, rel);
|
|
487
588
|
}
|
|
488
589
|
}
|
|
489
|
-
var sortEdgesImpl = (graph, id,
|
|
590
|
+
var sortEdgesImpl = (graph, id, relation2, order) => {
|
|
490
591
|
const internal = getInternal(graph);
|
|
491
592
|
const edgesAtom = internal._edges(id);
|
|
492
593
|
const edges = internal._registry.get(edgesAtom);
|
|
493
|
-
const
|
|
494
|
-
const
|
|
495
|
-
|
|
594
|
+
const relationId = relationKey(relation2);
|
|
595
|
+
const current = edges[relationId] ?? [];
|
|
596
|
+
const unsorted = current.filter((id2) => !order.includes(id2));
|
|
597
|
+
const sorted = order.filter((id2) => current.includes(id2));
|
|
598
|
+
const newOrder = [
|
|
496
599
|
...sorted,
|
|
497
600
|
...unsorted
|
|
498
|
-
]
|
|
499
|
-
|
|
601
|
+
];
|
|
602
|
+
if (newOrder.length === current.length && newOrder.every((id2, i) => id2 === current[i])) {
|
|
603
|
+
return graph;
|
|
604
|
+
}
|
|
605
|
+
internal._registry.set(edgesAtom, {
|
|
606
|
+
...edges,
|
|
607
|
+
[relationId]: newOrder
|
|
608
|
+
});
|
|
500
609
|
return graph;
|
|
501
610
|
};
|
|
502
611
|
function sortEdges(graphOrId, idOrRelation, relationOrOrder, order) {
|
|
503
612
|
if (typeof graphOrId === "string") {
|
|
504
613
|
const id = graphOrId;
|
|
505
|
-
const
|
|
614
|
+
const relation2 = idOrRelation;
|
|
506
615
|
const order2 = relationOrOrder;
|
|
507
|
-
return (graph) => sortEdgesImpl(graph, id,
|
|
616
|
+
return (graph) => sortEdgesImpl(graph, id, relation2, order2);
|
|
508
617
|
} else {
|
|
509
618
|
const graph = graphOrId;
|
|
510
619
|
const id = idOrRelation;
|
|
511
|
-
const
|
|
512
|
-
return sortEdgesImpl(graph, id,
|
|
620
|
+
const relation2 = relationOrOrder;
|
|
621
|
+
return sortEdgesImpl(graph, id, relation2, order);
|
|
513
622
|
}
|
|
514
623
|
}
|
|
515
624
|
var addNodesImpl = (graph, nodes) => {
|
|
@@ -535,7 +644,7 @@ var addNodeImpl = (graph, nodeArg) => {
|
|
|
535
644
|
Option.match(existingNode, {
|
|
536
645
|
onSome: (existing) => {
|
|
537
646
|
const typeChanged = existing.type !== type;
|
|
538
|
-
const dataChanged = existing.data
|
|
647
|
+
const dataChanged = !shallowEqual(existing.data, data);
|
|
539
648
|
const propertiesChanged = Object.keys(properties).some((key) => existing.properties[key] !== properties[key]);
|
|
540
649
|
log("existing node", {
|
|
541
650
|
id,
|
|
@@ -543,8 +652,8 @@ var addNodeImpl = (graph, nodeArg) => {
|
|
|
543
652
|
dataChanged,
|
|
544
653
|
propertiesChanged
|
|
545
654
|
}, {
|
|
546
|
-
F:
|
|
547
|
-
L:
|
|
655
|
+
F: __dxlog_file2,
|
|
656
|
+
L: 877,
|
|
548
657
|
S: void 0,
|
|
549
658
|
C: (f, a) => f(...a)
|
|
550
659
|
});
|
|
@@ -555,8 +664,8 @@ var addNodeImpl = (graph, nodeArg) => {
|
|
|
555
664
|
data,
|
|
556
665
|
properties
|
|
557
666
|
}, {
|
|
558
|
-
F:
|
|
559
|
-
L:
|
|
667
|
+
F: __dxlog_file2,
|
|
668
|
+
L: 884,
|
|
560
669
|
S: void 0,
|
|
561
670
|
C: (f, a) => f(...a)
|
|
562
671
|
});
|
|
@@ -584,8 +693,8 @@ var addNodeImpl = (graph, nodeArg) => {
|
|
|
584
693
|
data,
|
|
585
694
|
properties
|
|
586
695
|
}, {
|
|
587
|
-
F:
|
|
588
|
-
L:
|
|
696
|
+
F: __dxlog_file2,
|
|
697
|
+
L: 897,
|
|
589
698
|
S: void 0,
|
|
590
699
|
C: (f, a) => f(...a)
|
|
591
700
|
});
|
|
@@ -601,13 +710,24 @@ var addNodeImpl = (graph, nodeArg) => {
|
|
|
601
710
|
id,
|
|
602
711
|
node: newNode
|
|
603
712
|
});
|
|
713
|
+
const prefix = `${id}${Separators.primary}`;
|
|
714
|
+
const toApply = [
|
|
715
|
+
...internal._pendingExpands
|
|
716
|
+
].filter((k) => k.startsWith(prefix));
|
|
717
|
+
for (const pendingKey of toApply) {
|
|
718
|
+
internal._pendingExpands.delete(pendingKey);
|
|
719
|
+
const relation2 = relationFromKey(pendingKey.slice(prefix.length));
|
|
720
|
+
Record.set(internal._expanded, pendingKey, true);
|
|
721
|
+
internal._onExpand?.(id, relation2);
|
|
722
|
+
}
|
|
604
723
|
}
|
|
605
724
|
});
|
|
606
725
|
if (nodes) {
|
|
607
726
|
addNodesImpl(graph, nodes);
|
|
608
727
|
const _edges = nodes.map((node) => ({
|
|
609
728
|
source: id,
|
|
610
|
-
target: node.id
|
|
729
|
+
target: node.id,
|
|
730
|
+
relation: "child"
|
|
611
731
|
}));
|
|
612
732
|
addEdgesImpl(graph, _edges);
|
|
613
733
|
}
|
|
@@ -652,17 +772,27 @@ var removeNodeImpl = (graph, id, edges = false) => {
|
|
|
652
772
|
node: Option.none()
|
|
653
773
|
});
|
|
654
774
|
if (edges) {
|
|
655
|
-
const
|
|
656
|
-
const edgesToRemove = [
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
775
|
+
const nodeEdges = internal._registry.get(internal._edges(id));
|
|
776
|
+
const edgesToRemove = [];
|
|
777
|
+
for (const [relationKeyValue, relatedIds] of Object.entries(nodeEdges)) {
|
|
778
|
+
const relation2 = relationFromKey(relationKeyValue);
|
|
779
|
+
const isInboundRelation = relation2.direction === "inbound";
|
|
780
|
+
for (const relatedId of relatedIds) {
|
|
781
|
+
if (isInboundRelation) {
|
|
782
|
+
edgesToRemove.push({
|
|
783
|
+
source: relatedId,
|
|
784
|
+
target: id,
|
|
785
|
+
relation: inverseRelation(relation2)
|
|
786
|
+
});
|
|
787
|
+
} else {
|
|
788
|
+
edgesToRemove.push({
|
|
789
|
+
source: id,
|
|
790
|
+
target: relatedId,
|
|
791
|
+
relation: relation2
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
666
796
|
removeEdgesImpl(graph, edgesToRemove);
|
|
667
797
|
}
|
|
668
798
|
internal._onRemoveNode?.(id);
|
|
@@ -696,45 +826,53 @@ function addEdges(graphOrEdges, edges) {
|
|
|
696
826
|
}
|
|
697
827
|
}
|
|
698
828
|
var addEdgeImpl = (graph, edgeArg) => {
|
|
829
|
+
const relation2 = normalizeRelation(edgeArg.relation);
|
|
830
|
+
const relationId = relationKey(relation2);
|
|
831
|
+
const inverse = inverseRelation(relation2);
|
|
832
|
+
const inverseId = relationKey(inverse);
|
|
699
833
|
const internal = getInternal(graph);
|
|
700
834
|
const sourceAtom = internal._edges(edgeArg.source);
|
|
701
835
|
const source = internal._registry.get(sourceAtom);
|
|
702
|
-
|
|
703
|
-
|
|
836
|
+
const sourceList = source[relationId] ?? [];
|
|
837
|
+
if (!sourceList.includes(edgeArg.target)) {
|
|
838
|
+
log("add edge", {
|
|
704
839
|
source: edgeArg.source,
|
|
705
|
-
target: edgeArg.target
|
|
840
|
+
target: edgeArg.target,
|
|
841
|
+
relation: relationId
|
|
706
842
|
}, {
|
|
707
|
-
F:
|
|
708
|
-
L:
|
|
843
|
+
F: __dxlog_file2,
|
|
844
|
+
L: 1081,
|
|
709
845
|
S: void 0,
|
|
710
846
|
C: (f, a) => f(...a)
|
|
711
847
|
});
|
|
712
848
|
internal._registry.set(sourceAtom, {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
...
|
|
849
|
+
...source,
|
|
850
|
+
[relationId]: [
|
|
851
|
+
...sourceList,
|
|
716
852
|
edgeArg.target
|
|
717
853
|
]
|
|
718
854
|
});
|
|
719
855
|
}
|
|
720
856
|
const targetAtom = internal._edges(edgeArg.target);
|
|
721
857
|
const target = internal._registry.get(targetAtom);
|
|
722
|
-
|
|
723
|
-
|
|
858
|
+
const targetList = target[inverseId] ?? [];
|
|
859
|
+
if (!targetList.includes(edgeArg.source)) {
|
|
860
|
+
log("add inverse edge", {
|
|
724
861
|
source: edgeArg.source,
|
|
725
|
-
target: edgeArg.target
|
|
862
|
+
target: edgeArg.target,
|
|
863
|
+
relation: inverseId
|
|
726
864
|
}, {
|
|
727
|
-
F:
|
|
728
|
-
L:
|
|
865
|
+
F: __dxlog_file2,
|
|
866
|
+
L: 1089,
|
|
729
867
|
S: void 0,
|
|
730
868
|
C: (f, a) => f(...a)
|
|
731
869
|
});
|
|
732
870
|
internal._registry.set(targetAtom, {
|
|
733
|
-
|
|
734
|
-
|
|
871
|
+
...target,
|
|
872
|
+
[inverseId]: [
|
|
873
|
+
...targetList,
|
|
735
874
|
edgeArg.source
|
|
736
|
-
]
|
|
737
|
-
outbound: target.outbound
|
|
875
|
+
]
|
|
738
876
|
});
|
|
739
877
|
}
|
|
740
878
|
return graph;
|
|
@@ -767,32 +905,39 @@ function removeEdges(graphOrEdges, edgesOrRemoveOrphans, removeOrphans) {
|
|
|
767
905
|
}
|
|
768
906
|
}
|
|
769
907
|
var removeEdgeImpl = (graph, edgeArg, removeOrphans = false) => {
|
|
908
|
+
const relation2 = normalizeRelation(edgeArg.relation);
|
|
909
|
+
const relationId = relationKey(relation2);
|
|
910
|
+
const inverse = inverseRelation(relation2);
|
|
911
|
+
const inverseId = relationKey(inverse);
|
|
770
912
|
const internal = getInternal(graph);
|
|
771
913
|
const sourceAtom = internal._edges(edgeArg.source);
|
|
772
914
|
const source = internal._registry.get(sourceAtom);
|
|
773
|
-
|
|
915
|
+
const sourceList = source[relationId] ?? [];
|
|
916
|
+
if (sourceList.includes(edgeArg.target)) {
|
|
774
917
|
internal._registry.set(sourceAtom, {
|
|
775
|
-
|
|
776
|
-
|
|
918
|
+
...source,
|
|
919
|
+
[relationId]: sourceList.filter((id) => id !== edgeArg.target)
|
|
777
920
|
});
|
|
778
921
|
}
|
|
779
922
|
const targetAtom = internal._edges(edgeArg.target);
|
|
780
923
|
const target = internal._registry.get(targetAtom);
|
|
781
|
-
|
|
924
|
+
const targetList = target[inverseId] ?? [];
|
|
925
|
+
if (targetList.includes(edgeArg.source)) {
|
|
782
926
|
internal._registry.set(targetAtom, {
|
|
783
|
-
|
|
784
|
-
|
|
927
|
+
...target,
|
|
928
|
+
[inverseId]: targetList.filter((id) => id !== edgeArg.source)
|
|
785
929
|
});
|
|
786
930
|
}
|
|
787
931
|
if (removeOrphans) {
|
|
788
|
-
const
|
|
789
|
-
const
|
|
790
|
-
|
|
932
|
+
const sourceAfter = internal._registry.get(sourceAtom);
|
|
933
|
+
const targetAfter = internal._registry.get(targetAtom);
|
|
934
|
+
const isEmpty = (edges) => Object.values(edges).every((ids) => ids.length === 0);
|
|
935
|
+
if (isEmpty(sourceAfter) && edgeArg.source !== RootId) {
|
|
791
936
|
removeNodesImpl(graph, [
|
|
792
937
|
edgeArg.source
|
|
793
938
|
]);
|
|
794
939
|
}
|
|
795
|
-
if (
|
|
940
|
+
if (isEmpty(targetAfter) && edgeArg.target !== RootId) {
|
|
796
941
|
removeNodesImpl(graph, [
|
|
797
942
|
edgeArg.target
|
|
798
943
|
]);
|
|
@@ -815,6 +960,57 @@ function removeEdge(graphOrEdgeArg, edgeArgOrRemoveOrphans, removeOrphans) {
|
|
|
815
960
|
var make = (params) => {
|
|
816
961
|
return new GraphImpl(params);
|
|
817
962
|
};
|
|
963
|
+
var relationKey = (relation2) => {
|
|
964
|
+
const normalized = normalizeRelation(relation2);
|
|
965
|
+
return `${normalized.kind}${Separators.secondary}${normalized.direction}`;
|
|
966
|
+
};
|
|
967
|
+
var relationFromKey = (encoded) => {
|
|
968
|
+
const separatorIndex = encoded.lastIndexOf(Separators.secondary);
|
|
969
|
+
invariant2(separatorIndex > 0 && separatorIndex < encoded.length - 1, `Invalid relation key: ${encoded}`, {
|
|
970
|
+
F: __dxlog_file2,
|
|
971
|
+
L: 1234,
|
|
972
|
+
S: void 0,
|
|
973
|
+
A: [
|
|
974
|
+
"separatorIndex > 0 && separatorIndex < encoded.length - 1",
|
|
975
|
+
"`Invalid relation key: ${encoded}`"
|
|
976
|
+
]
|
|
977
|
+
});
|
|
978
|
+
const kind = encoded.slice(0, separatorIndex);
|
|
979
|
+
const directionRaw = encoded.slice(separatorIndex + 1);
|
|
980
|
+
invariant2(directionRaw === "outbound" || directionRaw === "inbound", `Invalid relation direction: ${directionRaw}`, {
|
|
981
|
+
F: __dxlog_file2,
|
|
982
|
+
L: 1237,
|
|
983
|
+
S: void 0,
|
|
984
|
+
A: [
|
|
985
|
+
"directionRaw === 'outbound' || directionRaw === 'inbound'",
|
|
986
|
+
"`Invalid relation direction: ${directionRaw}`"
|
|
987
|
+
]
|
|
988
|
+
});
|
|
989
|
+
return relation(kind, directionRaw);
|
|
990
|
+
};
|
|
991
|
+
var connectionKey = (id, relation2) => `${id}${Separators.primary}${relationKey(relation2)}`;
|
|
992
|
+
var relationFromConnectionKey = (key) => {
|
|
993
|
+
const separatorIndex = key.indexOf(Separators.primary);
|
|
994
|
+
invariant2(separatorIndex > 0 && separatorIndex < key.length - 1, `Invalid connection key: ${key}`, {
|
|
995
|
+
F: __dxlog_file2,
|
|
996
|
+
L: 1246,
|
|
997
|
+
S: void 0,
|
|
998
|
+
A: [
|
|
999
|
+
"separatorIndex > 0 && separatorIndex < key.length - 1",
|
|
1000
|
+
"`Invalid connection key: ${key}`"
|
|
1001
|
+
]
|
|
1002
|
+
});
|
|
1003
|
+
const id = key.slice(0, separatorIndex);
|
|
1004
|
+
const encodedRelation = key.slice(separatorIndex + 1);
|
|
1005
|
+
return {
|
|
1006
|
+
id,
|
|
1007
|
+
relation: relationFromKey(encodedRelation)
|
|
1008
|
+
};
|
|
1009
|
+
};
|
|
1010
|
+
var inverseRelation = (relation2) => {
|
|
1011
|
+
const normalized = normalizeRelation(relation2);
|
|
1012
|
+
return relation(normalized.kind, normalized.direction === "outbound" ? "inbound" : "outbound");
|
|
1013
|
+
};
|
|
818
1014
|
|
|
819
1015
|
// src/graph-builder.ts
|
|
820
1016
|
var graph_builder_exports = {};
|
|
@@ -828,6 +1024,7 @@ __export(graph_builder_exports, {
|
|
|
828
1024
|
destroy: () => destroy,
|
|
829
1025
|
explore: () => explore,
|
|
830
1026
|
flattenExtensions: () => flattenExtensions,
|
|
1027
|
+
flush: () => flush,
|
|
831
1028
|
from: () => from,
|
|
832
1029
|
make: () => make2,
|
|
833
1030
|
removeExtension: () => removeExtension
|
|
@@ -839,8 +1036,9 @@ import * as Function2 from "effect/Function";
|
|
|
839
1036
|
import * as Option3 from "effect/Option";
|
|
840
1037
|
import * as Pipeable2 from "effect/Pipeable";
|
|
841
1038
|
import * as Record2 from "effect/Record";
|
|
1039
|
+
import { scheduleTask, yieldOrContinue } from "main-thread-scheduling";
|
|
842
1040
|
import { log as log2 } from "@dxos/log";
|
|
843
|
-
import { byPosition, getDebugName,
|
|
1041
|
+
import { byPosition, getDebugName, isNonNullable as isNonNullable2 } from "@dxos/util";
|
|
844
1042
|
|
|
845
1043
|
// src/node-matcher.ts
|
|
846
1044
|
var node_matcher_exports = {};
|
|
@@ -864,18 +1062,16 @@ var whenNodeType = (type) => (node) => node.type === type ? Option2.some(node) :
|
|
|
864
1062
|
var whenEchoType = (type) => (node) => Obj.instanceOf(type, node.data) ? Option2.some(node.data) : Option2.none();
|
|
865
1063
|
var whenEchoObject = (node) => Obj.isObject(node.data) ? Option2.some(node.data) : Option2.none();
|
|
866
1064
|
var whenAll = (...matchers) => (node) => {
|
|
867
|
-
for (const
|
|
868
|
-
|
|
869
|
-
if (Option2.isNone(result)) {
|
|
1065
|
+
for (const candidate of matchers) {
|
|
1066
|
+
if (Option2.isNone(candidate(node))) {
|
|
870
1067
|
return Option2.none();
|
|
871
1068
|
}
|
|
872
1069
|
}
|
|
873
1070
|
return Option2.some(node);
|
|
874
1071
|
};
|
|
875
1072
|
var whenAny = (...matchers) => (node) => {
|
|
876
|
-
for (const
|
|
877
|
-
|
|
878
|
-
if (Option2.isSome(result)) {
|
|
1073
|
+
for (const candidate of matchers) {
|
|
1074
|
+
if (Option2.isSome(candidate(node))) {
|
|
879
1075
|
return Option2.some(node);
|
|
880
1076
|
}
|
|
881
1077
|
}
|
|
@@ -886,25 +1082,40 @@ var whenEchoObjectMatches = (node) => Obj.isObject(node.data) ? Option2.some(nod
|
|
|
886
1082
|
var whenNot = (matcher) => (node) => Option2.isNone(matcher(node)) ? Option2.some(node) : Option2.none();
|
|
887
1083
|
|
|
888
1084
|
// src/graph-builder.ts
|
|
889
|
-
var
|
|
890
|
-
var GraphBuilderTypeId = Symbol.for("@dxos/app-graph/GraphBuilder");
|
|
1085
|
+
var __dxlog_file3 = "/__w/dxos/dxos/packages/sdk/app-graph/src/graph-builder.ts";
|
|
1086
|
+
var GraphBuilderTypeId = /* @__PURE__ */ Symbol.for("@dxos/app-graph/GraphBuilder");
|
|
891
1087
|
var GraphBuilderImpl = class {
|
|
892
1088
|
[GraphBuilderTypeId] = GraphBuilderTypeId;
|
|
893
1089
|
pipe() {
|
|
894
1090
|
return Pipeable2.pipeArguments(this, arguments);
|
|
895
1091
|
}
|
|
896
1092
|
// TODO(wittjosiah): Use Context.
|
|
1093
|
+
/** Active subscriptions keyed by composite ID, cleaned up on node removal. */
|
|
897
1094
|
_subscriptions = /* @__PURE__ */ new Map();
|
|
1095
|
+
/** Connector updates pending flush, keyed by connector key. */
|
|
1096
|
+
_dirtyConnectors = /* @__PURE__ */ new Map();
|
|
1097
|
+
/** Last-flushed node IDs per connector key, used for edge removal on update. */
|
|
1098
|
+
_connectorPrevious = /* @__PURE__ */ new Map();
|
|
1099
|
+
/** Last-flushed node args per connector key, used for change detection. */
|
|
1100
|
+
_connectorPreviousArgs = /* @__PURE__ */ new Map();
|
|
1101
|
+
/** Whether a dirty-flush task is already scheduled. */
|
|
1102
|
+
_flushScheduled = false;
|
|
1103
|
+
/** Resolves when the current flush completes. */
|
|
1104
|
+
_flushPromise = Promise.resolve();
|
|
1105
|
+
/** Registered builder extensions keyed by extension ID. */
|
|
898
1106
|
_extensions = Atom3.make(Record2.empty()).pipe(Atom3.keepAlive, Atom3.withLabel("graph-builder:extensions"));
|
|
1107
|
+
/** Triggers signalling that a node's resolver has fired at least once. */
|
|
899
1108
|
_initialized = {};
|
|
1109
|
+
/** Shared atom registry for reactive subscriptions. */
|
|
900
1110
|
_registry;
|
|
1111
|
+
/** Backing graph with internal accessors for node atoms and construction. */
|
|
901
1112
|
_graph;
|
|
902
1113
|
constructor({ registry, ...params } = {}) {
|
|
903
1114
|
this._registry = registry ?? Registry2.make();
|
|
904
1115
|
const graph = make({
|
|
905
1116
|
...params,
|
|
906
1117
|
registry: this._registry,
|
|
907
|
-
onExpand: (id,
|
|
1118
|
+
onExpand: (id, relation2) => this._onExpand(id, relation2),
|
|
908
1119
|
onInitialize: (id) => this._onInitialize(id),
|
|
909
1120
|
onRemoveNode: (id) => this._onRemoveNode(id)
|
|
910
1121
|
});
|
|
@@ -916,6 +1127,52 @@ var GraphBuilderImpl = class {
|
|
|
916
1127
|
get extensions() {
|
|
917
1128
|
return this._extensions;
|
|
918
1129
|
}
|
|
1130
|
+
/** Apply a set of node changes for a single connector key. */
|
|
1131
|
+
_applyConnectorUpdate(key, nodes, previous) {
|
|
1132
|
+
const { id, relation: relation2 } = relationFromConnectorKey(key);
|
|
1133
|
+
const ids = nodes.map((node) => node.id);
|
|
1134
|
+
const removed = previous.filter((pid) => !ids.includes(pid));
|
|
1135
|
+
this._connectorPrevious.set(key, ids);
|
|
1136
|
+
this._connectorPreviousArgs.set(key, nodes);
|
|
1137
|
+
removeEdges(this._graph, removed.map((target) => ({
|
|
1138
|
+
source: id,
|
|
1139
|
+
target,
|
|
1140
|
+
relation: relation2
|
|
1141
|
+
})), true);
|
|
1142
|
+
addNodes(this._graph, nodes);
|
|
1143
|
+
addEdges(this._graph, nodes.map((node) => ({
|
|
1144
|
+
source: id,
|
|
1145
|
+
target: node.id,
|
|
1146
|
+
relation: relation2
|
|
1147
|
+
})));
|
|
1148
|
+
if (ids.length > 0) {
|
|
1149
|
+
const sortedIds = [
|
|
1150
|
+
...nodes
|
|
1151
|
+
].sort((a, b) => byPosition(a.properties ?? {}, b.properties ?? {})).map((n) => n.id);
|
|
1152
|
+
sortEdges(this._graph, id, relation2, sortedIds);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
_scheduleDirtyFlush() {
|
|
1156
|
+
if (!this._flushScheduled) {
|
|
1157
|
+
this._flushScheduled = true;
|
|
1158
|
+
this._flushPromise = scheduleTask(() => {
|
|
1159
|
+
this._flushScheduled = false;
|
|
1160
|
+
while (this._dirtyConnectors.size > 0) {
|
|
1161
|
+
const entries = [
|
|
1162
|
+
...this._dirtyConnectors.entries()
|
|
1163
|
+
];
|
|
1164
|
+
this._dirtyConnectors.clear();
|
|
1165
|
+
Atom3.batch(() => {
|
|
1166
|
+
for (const [key, { nodes, previous }] of entries) {
|
|
1167
|
+
this._applyConnectorUpdate(key, nodes, previous);
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
}, {
|
|
1172
|
+
strategy: "smooth"
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
919
1176
|
_resolvers = Atom3.family((id) => {
|
|
920
1177
|
return Atom3.make((get2) => {
|
|
921
1178
|
return Function2.pipe(get2(this._extensions), Record2.values, Array2.sortBy(byPosition), Array2.map(({ resolver }) => resolver), Array2.filter(isNonNullable2), Array2.map((resolver) => get2(resolver(id))), Array2.filter(isNonNullable2), Array2.head);
|
|
@@ -923,82 +1180,77 @@ var GraphBuilderImpl = class {
|
|
|
923
1180
|
});
|
|
924
1181
|
_connectors = Atom3.family((key) => {
|
|
925
1182
|
return Atom3.make((get2) => {
|
|
926
|
-
const
|
|
1183
|
+
const { id, relation: relation2 } = relationFromConnectorKey(key);
|
|
927
1184
|
const node = this._graph.node(id);
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1185
|
+
const sourceNode = Option3.getOrElse(get2(node), () => void 0);
|
|
1186
|
+
if (!sourceNode) {
|
|
1187
|
+
return [];
|
|
1188
|
+
}
|
|
1189
|
+
const extensions = Function2.pipe(get2(this._extensions), Record2.values, Array2.sortBy(byPosition), Array2.filter((ext) => relationKey(ext.relation ?? "child") === relationKey(relation2) && ext.connector != null));
|
|
1190
|
+
const nodes = [];
|
|
1191
|
+
for (const ext of extensions) {
|
|
1192
|
+
const result = get2(ext.connector(node));
|
|
1193
|
+
nodes.push(...result);
|
|
1194
|
+
}
|
|
1195
|
+
return nodes;
|
|
938
1196
|
}).pipe(Atom3.withLabel(`graph-builder:connectors:${key}`));
|
|
939
1197
|
});
|
|
940
|
-
_onExpand(id,
|
|
1198
|
+
_onExpand(id, relation2) {
|
|
941
1199
|
log2("onExpand", {
|
|
942
1200
|
id,
|
|
943
|
-
relation,
|
|
1201
|
+
relation: relation2,
|
|
944
1202
|
registry: getDebugName(this._registry)
|
|
945
1203
|
}, {
|
|
946
|
-
F:
|
|
947
|
-
L:
|
|
1204
|
+
F: __dxlog_file3,
|
|
1205
|
+
L: 261,
|
|
948
1206
|
S: this,
|
|
949
1207
|
C: (f, a) => f(...a)
|
|
950
1208
|
});
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1209
|
+
this._expandRelation(id, relation2);
|
|
1210
|
+
if (relation2.kind === "child" && relation2.direction === "outbound") {
|
|
1211
|
+
expand(this._graph, id, "action");
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
_expandRelation(id, relation2) {
|
|
1215
|
+
const key = connectorKey(id, relation2);
|
|
1216
|
+
const connectors = this._connectors(key);
|
|
1217
|
+
const cancel = this._registry.subscribe(connectors, (rawNodes) => {
|
|
1218
|
+
const nodes = qualifyNodeArgs(id)(rawNodes);
|
|
1219
|
+
const previous = this._connectorPrevious.get(key) ?? [];
|
|
954
1220
|
const ids = nodes.map((n) => n.id);
|
|
955
|
-
|
|
956
|
-
|
|
1221
|
+
if (ids.length === previous.length && ids.every((nodeId, idx) => nodeId === previous[idx])) {
|
|
1222
|
+
const prevArgs = this._connectorPreviousArgs.get(key);
|
|
1223
|
+
if (prevArgs && nodeArgsUnchanged(prevArgs, nodes)) {
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
957
1227
|
log2("update", {
|
|
958
1228
|
id,
|
|
959
|
-
relation,
|
|
960
|
-
ids
|
|
961
|
-
removed
|
|
1229
|
+
relation: relation2,
|
|
1230
|
+
ids
|
|
962
1231
|
}, {
|
|
963
|
-
F:
|
|
964
|
-
L:
|
|
1232
|
+
F: __dxlog_file3,
|
|
1233
|
+
L: 288,
|
|
965
1234
|
S: this,
|
|
966
1235
|
C: (f, a) => f(...a)
|
|
967
1236
|
});
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
})), true);
|
|
974
|
-
addNodes(this._graph, nodes);
|
|
975
|
-
addEdges(this._graph, nodes.map((node) => relation === "outbound" ? {
|
|
976
|
-
source: id,
|
|
977
|
-
target: node.id
|
|
978
|
-
} : {
|
|
979
|
-
source: node.id,
|
|
980
|
-
target: id
|
|
981
|
-
}));
|
|
982
|
-
sortEdges(this._graph, id, relation, nodes.map(({ id: id2 }) => id2));
|
|
983
|
-
});
|
|
984
|
-
};
|
|
985
|
-
if (typeof requestAnimationFrame === "function") {
|
|
986
|
-
requestAnimationFrame(update);
|
|
987
|
-
} else {
|
|
988
|
-
update();
|
|
989
|
-
}
|
|
1237
|
+
this._dirtyConnectors.set(key, {
|
|
1238
|
+
nodes,
|
|
1239
|
+
previous
|
|
1240
|
+
});
|
|
1241
|
+
this._scheduleDirtyFlush();
|
|
990
1242
|
}, {
|
|
991
1243
|
immediate: true
|
|
992
1244
|
});
|
|
993
|
-
this._subscriptions.set(id, cancel);
|
|
1245
|
+
this._subscriptions.set(subscriptionKey(id, "expand", key), cancel);
|
|
994
1246
|
}
|
|
995
1247
|
// TODO(wittjosiah): If the same node is added by a connector, the resolver should probably cancel itself?
|
|
996
1248
|
async _onInitialize(id) {
|
|
997
1249
|
log2("onInitialize", {
|
|
998
1250
|
id
|
|
999
1251
|
}, {
|
|
1000
|
-
F:
|
|
1001
|
-
L:
|
|
1252
|
+
F: __dxlog_file3,
|
|
1253
|
+
L: 300,
|
|
1002
1254
|
S: this,
|
|
1003
1255
|
C: (f, a) => f(...a)
|
|
1004
1256
|
});
|
|
@@ -1007,9 +1259,14 @@ var GraphBuilderImpl = class {
|
|
|
1007
1259
|
const trigger = this._initialized[id];
|
|
1008
1260
|
Option3.match(node, {
|
|
1009
1261
|
onSome: (node2) => {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
]);
|
|
1262
|
+
const connectorOwned = [
|
|
1263
|
+
...this._connectorPrevious.values()
|
|
1264
|
+
].some((ids) => ids.includes(id));
|
|
1265
|
+
if (!connectorOwned) {
|
|
1266
|
+
addNodes(this._graph, [
|
|
1267
|
+
node2
|
|
1268
|
+
]);
|
|
1269
|
+
}
|
|
1013
1270
|
trigger?.wake();
|
|
1014
1271
|
},
|
|
1015
1272
|
onNone: () => {
|
|
@@ -1022,11 +1279,16 @@ var GraphBuilderImpl = class {
|
|
|
1022
1279
|
}, {
|
|
1023
1280
|
immediate: true
|
|
1024
1281
|
});
|
|
1025
|
-
this._subscriptions.set(id, cancel);
|
|
1282
|
+
this._subscriptions.set(subscriptionKey(id, "init"), cancel);
|
|
1026
1283
|
}
|
|
1027
1284
|
_onRemoveNode(id) {
|
|
1028
|
-
|
|
1029
|
-
this._subscriptions
|
|
1285
|
+
const prefix = `${id}${Separators.primary}`;
|
|
1286
|
+
for (const [key, cleanup] of this._subscriptions) {
|
|
1287
|
+
if (key.startsWith(prefix)) {
|
|
1288
|
+
cleanup();
|
|
1289
|
+
this._subscriptions.delete(key);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1030
1292
|
}
|
|
1031
1293
|
};
|
|
1032
1294
|
var make2 = (params) => {
|
|
@@ -1079,14 +1341,11 @@ function removeExtension(builderOrId, id) {
|
|
|
1079
1341
|
}
|
|
1080
1342
|
var exploreImpl = async (builder, options, path = []) => {
|
|
1081
1343
|
const internal = builder;
|
|
1082
|
-
const { registry = Registry2.make(), source = RootId, relation
|
|
1344
|
+
const { registry = Registry2.make(), source = RootId, relation: relation2, visitor } = options;
|
|
1083
1345
|
if (path.includes(source)) {
|
|
1084
1346
|
return;
|
|
1085
1347
|
}
|
|
1086
|
-
|
|
1087
|
-
const { yieldOrContinue } = await import("main-thread-scheduling");
|
|
1088
|
-
await yieldOrContinue("idle");
|
|
1089
|
-
}
|
|
1348
|
+
await yieldOrContinue("idle");
|
|
1090
1349
|
const node = registry.get(internal._graph.nodeOrThrow(source));
|
|
1091
1350
|
const shouldContinue = await visitor(node, [
|
|
1092
1351
|
...path,
|
|
@@ -1095,13 +1354,13 @@ var exploreImpl = async (builder, options, path = []) => {
|
|
|
1095
1354
|
if (shouldContinue === false) {
|
|
1096
1355
|
return;
|
|
1097
1356
|
}
|
|
1098
|
-
const nodes =
|
|
1357
|
+
const nodes = Function2.pipe(internal._registry.get(internal._extensions), Record2.values, Array2.map((extension) => extension.connector), Array2.filter(isNonNullable2), Array2.flatMap((connector) => registry.get(connector(internal._graph.node(source)))), qualifyNodeArgs(source));
|
|
1099
1358
|
await Promise.all(nodes.map((nodeArg) => {
|
|
1100
1359
|
registry.set(internal._graph._node(nodeArg.id), internal._graph._constructNode(nodeArg));
|
|
1101
1360
|
return exploreImpl(builder, {
|
|
1102
1361
|
registry,
|
|
1103
1362
|
source: nodeArg.id,
|
|
1104
|
-
relation,
|
|
1363
|
+
relation: relation2,
|
|
1105
1364
|
visitor
|
|
1106
1365
|
}, [
|
|
1107
1366
|
...path,
|
|
@@ -1137,8 +1396,12 @@ function destroy(builder) {
|
|
|
1137
1396
|
return destroyImpl(builder);
|
|
1138
1397
|
}
|
|
1139
1398
|
}
|
|
1399
|
+
var flush = (builder) => {
|
|
1400
|
+
return builder._flushPromise;
|
|
1401
|
+
};
|
|
1140
1402
|
var createExtensionRaw = (extension) => {
|
|
1141
|
-
const { id, position = "static", relation = "
|
|
1403
|
+
const { id, position = "static", relation: relation2 = "child", resolver: _resolver, connector: _connector, actions: _actions, actionGroups: _actionGroups } = extension;
|
|
1404
|
+
const normalizedRelation = normalizeRelation(relation2);
|
|
1142
1405
|
const getId = (key) => `${id}/${key}`;
|
|
1143
1406
|
const resolver = _resolver && Atom3.family((id2) => _resolver(id2).pipe(Atom3.withLabel(`graph-builder:_resolver:${id2}`)));
|
|
1144
1407
|
const connector = _connector && Atom3.family((node) => _connector(node).pipe(Atom3.withLabel(`graph-builder:_connector:${id}`)));
|
|
@@ -1153,7 +1416,7 @@ var createExtensionRaw = (extension) => {
|
|
|
1153
1416
|
connector ? {
|
|
1154
1417
|
id: getId("connector"),
|
|
1155
1418
|
position,
|
|
1156
|
-
relation,
|
|
1419
|
+
relation: normalizedRelation,
|
|
1157
1420
|
connector: Atom3.family((node) => Atom3.make((get2) => {
|
|
1158
1421
|
try {
|
|
1159
1422
|
return get2(connector(node));
|
|
@@ -1163,8 +1426,8 @@ var createExtensionRaw = (extension) => {
|
|
|
1163
1426
|
node,
|
|
1164
1427
|
error
|
|
1165
1428
|
}, {
|
|
1166
|
-
F:
|
|
1167
|
-
L:
|
|
1429
|
+
F: __dxlog_file3,
|
|
1430
|
+
L: 596,
|
|
1168
1431
|
S: void 0,
|
|
1169
1432
|
C: (f, a) => f(...a)
|
|
1170
1433
|
});
|
|
@@ -1175,7 +1438,7 @@ var createExtensionRaw = (extension) => {
|
|
|
1175
1438
|
actionGroups ? {
|
|
1176
1439
|
id: getId("actionGroups"),
|
|
1177
1440
|
position,
|
|
1178
|
-
relation:
|
|
1441
|
+
relation: actionRelation(),
|
|
1179
1442
|
connector: Atom3.family((node) => Atom3.make((get2) => {
|
|
1180
1443
|
try {
|
|
1181
1444
|
return get2(actionGroups(node)).map((arg) => ({
|
|
@@ -1189,8 +1452,8 @@ var createExtensionRaw = (extension) => {
|
|
|
1189
1452
|
node,
|
|
1190
1453
|
error
|
|
1191
1454
|
}, {
|
|
1192
|
-
F:
|
|
1193
|
-
L:
|
|
1455
|
+
F: __dxlog_file3,
|
|
1456
|
+
L: 617,
|
|
1194
1457
|
S: void 0,
|
|
1195
1458
|
C: (f, a) => f(...a)
|
|
1196
1459
|
});
|
|
@@ -1201,7 +1464,7 @@ var createExtensionRaw = (extension) => {
|
|
|
1201
1464
|
actions ? {
|
|
1202
1465
|
id: getId("actions"),
|
|
1203
1466
|
position,
|
|
1204
|
-
relation:
|
|
1467
|
+
relation: actionRelation(),
|
|
1205
1468
|
connector: Atom3.family((node) => Atom3.make((get2) => {
|
|
1206
1469
|
try {
|
|
1207
1470
|
return get2(actions(node)).map((arg) => ({
|
|
@@ -1214,8 +1477,8 @@ var createExtensionRaw = (extension) => {
|
|
|
1214
1477
|
node,
|
|
1215
1478
|
error
|
|
1216
1479
|
}, {
|
|
1217
|
-
F:
|
|
1218
|
-
L:
|
|
1480
|
+
F: __dxlog_file3,
|
|
1481
|
+
L: 634,
|
|
1219
1482
|
S: void 0,
|
|
1220
1483
|
C: (f, a) => f(...a)
|
|
1221
1484
|
});
|
|
@@ -1231,8 +1494,8 @@ var runEffectSyncWithFallback = (effect, context2, extensionId, fallback) => {
|
|
|
1231
1494
|
extension: extensionId,
|
|
1232
1495
|
error
|
|
1233
1496
|
}, {
|
|
1234
|
-
F:
|
|
1235
|
-
L:
|
|
1497
|
+
F: __dxlog_file3,
|
|
1498
|
+
L: 677,
|
|
1236
1499
|
S: void 0,
|
|
1237
1500
|
C: (f, a) => f(...a)
|
|
1238
1501
|
});
|
|
@@ -1240,7 +1503,7 @@ var runEffectSyncWithFallback = (effect, context2, extensionId, fallback) => {
|
|
|
1240
1503
|
})));
|
|
1241
1504
|
};
|
|
1242
1505
|
var createExtension = (options) => Effect.map(Effect.context(), (context2) => {
|
|
1243
|
-
const { id, match: match3, actions, connector, resolver, relation, position } = options;
|
|
1506
|
+
const { id, match: match3, actions, connector, resolver, relation: relation2, position } = options;
|
|
1244
1507
|
const connectorExtension = connector ? createConnectorWithRuntime(id, match3, connector, context2) : void 0;
|
|
1245
1508
|
const actionsExtension = actions ? (node) => Atom3.make((get2) => Function2.pipe(get2(node), Option3.flatMap(match3), Option3.map((matched) => runEffectSyncWithFallback(actions(matched, get2), context2, id, []).map((action) => ({
|
|
1246
1509
|
...action,
|
|
@@ -1250,7 +1513,7 @@ var createExtension = (options) => Effect.map(Effect.context(), (context2) => {
|
|
|
1250
1513
|
const resolverExtension = resolver ? (nodeId) => Atom3.make((get2) => runEffectSyncWithFallback(resolver(nodeId, get2), context2, id, null) ?? null) : void 0;
|
|
1251
1514
|
return createExtensionRaw({
|
|
1252
1515
|
id,
|
|
1253
|
-
relation,
|
|
1516
|
+
relation: relation2,
|
|
1254
1517
|
position,
|
|
1255
1518
|
connector: connectorExtension,
|
|
1256
1519
|
actions: actionsExtension,
|
|
@@ -1264,16 +1527,35 @@ var createConnectorWithRuntime = (extensionId, matcher, factory, context2) => {
|
|
|
1264
1527
|
return (node) => Atom3.make((get2) => Function2.pipe(get2(node), Option3.flatMap(matcher), Option3.map((data) => runEffectSyncWithFallback(factory(data, get2), context2, extensionId, [])), Option3.getOrElse(() => [])));
|
|
1265
1528
|
};
|
|
1266
1529
|
var createTypeExtension = (options) => {
|
|
1267
|
-
const { id, type, actions, connector, relation, position } = options;
|
|
1530
|
+
const { id, type, actions, connector, relation: relation2, position } = options;
|
|
1268
1531
|
return createExtension({
|
|
1269
1532
|
id,
|
|
1270
1533
|
match: whenEchoType(type),
|
|
1271
1534
|
actions,
|
|
1272
1535
|
connector,
|
|
1273
|
-
relation,
|
|
1536
|
+
relation: relation2,
|
|
1274
1537
|
position
|
|
1275
1538
|
});
|
|
1276
1539
|
};
|
|
1540
|
+
var qualifyNodeArgs = (parentId) => (nodes) => nodes.map((node) => {
|
|
1541
|
+
validateSegmentId(node.id);
|
|
1542
|
+
const qualified = qualifyId(parentId, node.id);
|
|
1543
|
+
return {
|
|
1544
|
+
...node,
|
|
1545
|
+
id: qualified,
|
|
1546
|
+
nodes: node.nodes ? qualifyNodeArgs(qualified)(node.nodes) : void 0
|
|
1547
|
+
};
|
|
1548
|
+
});
|
|
1549
|
+
var connectorKey = (id, relation2) => `${id}${Separators.primary}${relationKey(relation2)}`;
|
|
1550
|
+
var relationFromConnectorKey = (key) => {
|
|
1551
|
+
const separatorIndex = key.indexOf(Separators.primary);
|
|
1552
|
+
const id = key.slice(0, separatorIndex);
|
|
1553
|
+
return {
|
|
1554
|
+
id,
|
|
1555
|
+
relation: relationFromKey(key.slice(separatorIndex + 1))
|
|
1556
|
+
};
|
|
1557
|
+
};
|
|
1558
|
+
var subscriptionKey = (id, kind, detail) => detail != null ? `${id}${Separators.primary}${kind}${Separators.primary}${detail}` : `${id}${Separators.primary}${kind}`;
|
|
1277
1559
|
var flattenExtensions = (extension, acc = []) => {
|
|
1278
1560
|
if (Array2.isArray(extension)) {
|
|
1279
1561
|
return [
|