@dxos/graph 0.8.4-main.ae835ea → 0.8.4-main.bc2380dfbc

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.
@@ -1,33 +1,131 @@
1
- // src/model.ts
2
- import { effect } from "@preact/signals-core";
3
- import { inspectCustom } from "@dxos/debug";
4
- import { failedInvariant, invariant as invariant2 } from "@dxos/invariant";
5
- import { live } from "@dxos/live-object";
6
- import { isTruthy, removeBy } from "@dxos/util";
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
7
6
 
8
- // src/util.ts
7
+ // src/selection.ts
8
+ import { Atom, Registry } from "@effect-atom/atom-react";
9
9
  import { invariant } from "@dxos/invariant";
10
- var __dxlog_file = "/__w/dxos/dxos/packages/common/graph/src/util.ts";
10
+ var __dxlog_file = "/__w/dxos/dxos/packages/common/graph/src/selection.ts";
11
+ var SelectionModel = class {
12
+ _registry;
13
+ _selected;
14
+ _mode;
15
+ constructor({ mode = "multi" } = {}) {
16
+ this._registry = Registry.make();
17
+ this._selected = Atom.make(/* @__PURE__ */ new Set());
18
+ this._mode = mode;
19
+ }
20
+ /**
21
+ * Returns the selected IDs atom for subscription.
22
+ */
23
+ get selected() {
24
+ return this._selected;
25
+ }
26
+ /**
27
+ * Gets the current selected IDs as a Set.
28
+ */
29
+ getSelected() {
30
+ return this._registry.get(this._selected);
31
+ }
32
+ /**
33
+ * Gets the current selected IDs as an array.
34
+ */
35
+ getSelectedIds() {
36
+ return Array.from(this._registry.get(this._selected).values());
37
+ }
38
+ /**
39
+ * Subscribe to selection changes.
40
+ */
41
+ subscribe(cb) {
42
+ this._registry.get(this._selected);
43
+ return this._registry.subscribe(this._selected, () => {
44
+ cb(this._registry.get(this._selected));
45
+ });
46
+ }
47
+ toJSON() {
48
+ return {
49
+ selected: this.getSelectedIds()
50
+ };
51
+ }
52
+ /**
53
+ * Gets the current selection size.
54
+ */
55
+ getSize() {
56
+ return this._registry.get(this._selected).size;
57
+ }
58
+ contains(id) {
59
+ return this._registry.get(this._selected).has(id);
60
+ }
61
+ clear() {
62
+ this._registry.set(this._selected, /* @__PURE__ */ new Set());
63
+ }
64
+ add(id) {
65
+ invariant(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 59, S: this, A: ["id", ""] });
66
+ const current = this._registry.get(this._selected);
67
+ this._registry.set(this._selected, new Set(this._mode === "single" ? [
68
+ id
69
+ ] : [
70
+ ...Array.from(current.values()),
71
+ id
72
+ ]));
73
+ }
74
+ remove(id) {
75
+ invariant(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 69, S: this, A: ["id", ""] });
76
+ const current = this._registry.get(this._selected);
77
+ this._registry.set(this._selected, new Set(Array.from(current.values()).filter((_id) => _id !== id)));
78
+ }
79
+ // TODO(burdon): Handle single select.
80
+ setSelected(ids, subtract = false) {
81
+ const current = this._registry.get(this._selected);
82
+ this._registry.set(this._selected, /* @__PURE__ */ new Set([
83
+ ...subtract ? Array.from(current.values()) : [],
84
+ ...ids
85
+ ]));
86
+ }
87
+ toggleSelected(ids, subtract = false) {
88
+ const current = this._registry.get(this._selected);
89
+ const set = new Set(subtract ? Array.from(current.values()) : void 0);
90
+ ids.forEach((id) => {
91
+ if (current.has(id)) {
92
+ set.delete(id);
93
+ } else {
94
+ set.add(id);
95
+ }
96
+ });
97
+ this._registry.set(this._selected, set);
98
+ }
99
+ };
100
+
101
+ // src/Graph.ts
102
+ var Graph_exports = {};
103
+ __export(Graph_exports, {
104
+ Edge: () => Edge,
105
+ Graph: () => Graph,
106
+ Node: () => Node,
107
+ createEdgeId: () => createEdgeId,
108
+ parseEdgeId: () => parseEdgeId
109
+ });
110
+ import * as Schema from "effect/Schema";
111
+ import { invariant as invariant2 } from "@dxos/invariant";
112
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/common/graph/src/Graph.ts";
113
+ var Node = Schema.Struct({
114
+ id: Schema.String,
115
+ type: Schema.optional(Schema.String),
116
+ data: Schema.optional(Schema.Any)
117
+ });
118
+ var Edge = Schema.Struct({
119
+ id: Schema.String,
120
+ type: Schema.optional(Schema.String),
121
+ source: Schema.String,
122
+ target: Schema.String,
123
+ data: Schema.optional(Schema.Any)
124
+ });
11
125
  var KEY_REGEX = /\w+/;
12
126
  var createEdgeId = ({ source, target, relation }) => {
13
- invariant(source.match(KEY_REGEX), `invalid source: ${source}`, {
14
- F: __dxlog_file,
15
- L: 13,
16
- S: void 0,
17
- A: [
18
- "source.match(KEY_REGEX)",
19
- "`invalid source: ${source}`"
20
- ]
21
- });
22
- invariant(target.match(KEY_REGEX), `invalid target: ${target}`, {
23
- F: __dxlog_file,
24
- L: 14,
25
- S: void 0,
26
- A: [
27
- "target.match(KEY_REGEX)",
28
- "`invalid target: ${target}`"
29
- ]
30
- });
127
+ invariant2(source.match(KEY_REGEX), `invalid source: ${source}`, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 27, S: void 0, A: ["source.match(KEY_REGEX)", "`invalid source: ${source}`"] });
128
+ invariant2(target.match(KEY_REGEX), `invalid target: ${target}`, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 28, S: void 0, A: ["target.match(KEY_REGEX)", "`invalid target: ${target}`"] });
31
129
  return [
32
130
  source,
33
131
  relation,
@@ -36,34 +134,52 @@ var createEdgeId = ({ source, target, relation }) => {
36
134
  };
37
135
  var parseEdgeId = (id) => {
38
136
  const [source, relation, target] = id.split("_");
39
- invariant(source.length && target.length, void 0, {
40
- F: __dxlog_file,
41
- L: 20,
42
- S: void 0,
43
- A: [
44
- "source.length && target.length",
45
- ""
46
- ]
47
- });
137
+ invariant2(source.length && target.length, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 37, S: void 0, A: ["source.length && target.length", ""] });
48
138
  return {
49
139
  source,
50
140
  relation: relation.length ? relation : void 0,
51
141
  target
52
142
  };
53
143
  };
144
+ var Graph = Schema.Struct({
145
+ id: Schema.optional(Schema.String),
146
+ nodes: Schema.mutable(Schema.Array(Node)),
147
+ edges: Schema.mutable(Schema.Array(Edge))
148
+ });
54
149
 
55
- // src/model.ts
56
- var __dxlog_file2 = "/__w/dxos/dxos/packages/common/graph/src/model.ts";
150
+ // src/GraphModel.ts
151
+ var GraphModel_exports = {};
152
+ __export(GraphModel_exports, {
153
+ AbstractBuilder: () => AbstractBuilder,
154
+ AbstractGraphModel: () => AbstractGraphModel,
155
+ Builder: () => Builder,
156
+ GraphModel: () => GraphModel,
157
+ ReactiveGraphModel: () => ReactiveGraphModel,
158
+ ReadonlyGraphModel: () => ReadonlyGraphModel
159
+ });
160
+ import { Atom as Atom2 } from "@effect-atom/atom-react";
161
+ import { inspectCustom } from "@dxos/debug";
162
+ import { failedInvariant, invariant as invariant3 } from "@dxos/invariant";
163
+ import { isTruthy, removeBy } from "@dxos/util";
164
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/common/graph/src/GraphModel.ts";
57
165
  var ReadonlyGraphModel = class {
58
166
  _graph;
59
167
  /**
168
+ * Optional function to wrap mutations.
169
+ * When set, all graph mutations are wrapped in this function.
170
+ */
171
+ _change;
172
+ /**
60
173
  * NOTE: Pass in simple Graph or Live.
174
+ * @param graph - The graph data.
175
+ * @param change - Optional function to wrap mutations (e.g., Obj.update for ECHO objects).
61
176
  */
62
- constructor(graph) {
177
+ constructor(graph, change) {
63
178
  this._graph = graph ?? {
64
179
  nodes: [],
65
180
  edges: []
66
181
  };
182
+ this._change = change;
67
183
  }
68
184
  [inspectCustom]() {
69
185
  return this.toJSON();
@@ -131,9 +247,22 @@ var ReadonlyGraphModel = class {
131
247
  }
132
248
  };
133
249
  var AbstractGraphModel = class extends ReadonlyGraphModel {
250
+ /**
251
+ * Execute a mutation on the graph.
252
+ * If a change function is set, wraps the mutation in it.
253
+ */
254
+ _mutate(fn) {
255
+ if (this._change != null) {
256
+ this._change(() => fn(this._graph));
257
+ } else {
258
+ fn(this._graph);
259
+ }
260
+ }
134
261
  clear() {
135
- this._graph.nodes.length = 0;
136
- this._graph.edges.length = 0;
262
+ this._mutate((graph) => {
263
+ graph.nodes.length = 0;
264
+ graph.edges.length = 0;
265
+ });
137
266
  return this;
138
267
  }
139
268
  addGraph(graph) {
@@ -149,76 +278,44 @@ var AbstractGraphModel = class extends ReadonlyGraphModel {
149
278
  return this;
150
279
  }
151
280
  addNode(node) {
152
- invariant2(node.id, "ID is required", {
153
- F: __dxlog_file2,
154
- L: 157,
155
- S: this,
156
- A: [
157
- "node.id",
158
- "'ID is required'"
159
- ]
160
- });
161
- invariant2(!this.findNode(node.id), `node already exists: ${node.id}`, {
162
- F: __dxlog_file2,
163
- L: 158,
164
- S: this,
165
- A: [
166
- "!this.findNode(node.id)",
167
- "`node already exists: ${node.id}`"
168
- ]
281
+ invariant3(node.id, "ID is required", { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 126, S: this, A: ["node.id", "'ID is required'"] });
282
+ invariant3(!this.findNode(node.id), `node already exists: ${node.id}`, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 127, S: this, A: ["!this.findNode(node.id)", "`node already exists: ${node.id}`"] });
283
+ this._mutate((graph) => {
284
+ graph.nodes.push(node);
169
285
  });
170
- this._graph.nodes.push(node);
171
286
  return node;
172
287
  }
173
288
  addNodes(nodes) {
174
289
  return nodes.map((node) => this.addNode(node));
175
290
  }
176
291
  addEdge(edge) {
177
- invariant2(edge.source, void 0, {
178
- F: __dxlog_file2,
179
- L: 168,
180
- S: this,
181
- A: [
182
- "edge.source",
183
- ""
184
- ]
185
- });
186
- invariant2(edge.target, void 0, {
187
- F: __dxlog_file2,
188
- L: 169,
189
- S: this,
190
- A: [
191
- "edge.target",
192
- ""
193
- ]
194
- });
292
+ invariant3(edge.source, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 137, S: this, A: ["edge.source", ""] });
293
+ invariant3(edge.target, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 138, S: this, A: ["edge.target", ""] });
195
294
  if (!edge.id) {
196
295
  edge = {
197
296
  id: createEdgeId(edge),
198
297
  ...edge
199
298
  };
200
299
  }
201
- invariant2(!this.findNode(edge.id), void 0, {
202
- F: __dxlog_file2,
203
- L: 174,
204
- S: this,
205
- A: [
206
- "!this.findNode(edge.id!)",
207
- ""
208
- ]
300
+ invariant3(!this.findNode(edge.id), void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 146, S: this, A: ["!this.findNode(edge.id)", ""] });
301
+ this._mutate((graph) => {
302
+ graph.edges.push(edge);
209
303
  });
210
- this._graph.edges.push(edge);
211
304
  return edge;
212
305
  }
213
306
  addEdges(edges) {
214
307
  return edges.map((edge) => this.addEdge(edge));
215
308
  }
216
309
  removeNode(id) {
217
- const edges = removeBy(this._graph.edges, (edge) => edge.source === id || edge.target === id);
218
- const nodes = removeBy(this._graph.nodes, (node) => node.id === id);
310
+ let removedEdges = [];
311
+ let removedNodes = [];
312
+ this._mutate((graph) => {
313
+ removedEdges = removeBy(graph.edges, (edge) => edge.source === id || edge.target === id);
314
+ removedNodes = removeBy(graph.nodes, (node) => node.id === id);
315
+ });
219
316
  return this.copy({
220
- nodes,
221
- edges
317
+ nodes: removedNodes,
318
+ edges: removedEdges
222
319
  });
223
320
  }
224
321
  removeNodes(ids) {
@@ -226,10 +323,13 @@ var AbstractGraphModel = class extends ReadonlyGraphModel {
226
323
  return this.copy().addGraphs(graphs);
227
324
  }
228
325
  removeEdge(id) {
229
- const edges = removeBy(this._graph.edges, (edge) => edge.id === id);
326
+ let removedEdges = [];
327
+ this._mutate((graph) => {
328
+ removedEdges = removeBy(graph.edges, (edge) => edge.id === id);
329
+ });
230
330
  return this.copy({
231
331
  nodes: [],
232
- edges
332
+ edges: removedEdges
233
333
  });
234
334
  }
235
335
  removeEdges(ids) {
@@ -237,7 +337,7 @@ var AbstractGraphModel = class extends ReadonlyGraphModel {
237
337
  return this.copy().addGraphs(graphs);
238
338
  }
239
339
  };
240
- var AbstractGraphBuilder = class {
340
+ var AbstractBuilder = class {
241
341
  _model;
242
342
  constructor(_model) {
243
343
  this._model = _model;
@@ -271,153 +371,138 @@ var AbstractGraphBuilder = class {
271
371
  };
272
372
  var GraphModel = class _GraphModel extends AbstractGraphModel {
273
373
  get builder() {
274
- return new GraphBuilder(this);
374
+ return new Builder(this);
275
375
  }
276
376
  copy(graph) {
277
- return new _GraphModel({
278
- nodes: graph?.nodes ?? [],
279
- edges: graph?.edges ?? []
280
- });
281
- }
282
- };
283
- var subscribe = (model, cb, fire = false) => {
284
- if (fire) {
285
- cb(model, model.graph);
377
+ return new _GraphModel(graph);
286
378
  }
287
- return effect(() => {
288
- cb(model, model.graph);
289
- });
290
379
  };
291
380
  var ReactiveGraphModel = class _ReactiveGraphModel extends GraphModel {
292
- constructor(graph) {
293
- super(live({
381
+ _registry;
382
+ _graphAtom;
383
+ constructor(_registry, graph) {
384
+ const initialGraph = {
294
385
  nodes: graph?.nodes ?? [],
295
386
  edges: graph?.edges ?? []
296
- }));
297
- }
298
- copy(graph) {
299
- return new _ReactiveGraphModel(graph);
300
- }
301
- subscribe(cb, fire = false) {
302
- return subscribe(this, cb, fire);
387
+ };
388
+ super(initialGraph), this._registry = _registry;
389
+ this._graphAtom = Atom2.make(initialGraph);
303
390
  }
304
- };
305
- var GraphBuilder = class extends AbstractGraphBuilder {
306
- call(cb) {
307
- cb(this);
308
- return this;
391
+ get registry() {
392
+ return this._registry;
309
393
  }
310
- };
311
-
312
- // src/selection.ts
313
- import { computed, signal } from "@preact/signals-core";
314
- import { invariant as invariant3 } from "@dxos/invariant";
315
- var __dxlog_file3 = "/__w/dxos/dxos/packages/common/graph/src/selection.ts";
316
- var SelectionModel = class {
317
- _singleSelect;
318
- _selected = signal(/* @__PURE__ */ new Set());
319
- _selectedIds = computed(() => Array.from(this._selected.value.values()));
320
- constructor(_singleSelect = false) {
321
- this._singleSelect = _singleSelect;
394
+ /**
395
+ * Get the graph atom for reactive subscriptions.
396
+ */
397
+ get graphAtom() {
398
+ return this._graphAtom;
322
399
  }
323
- toJSON() {
324
- return {
325
- selected: Array.from(this._selected.value.values())
326
- };
400
+ get graph() {
401
+ return this._registry.get(this._graphAtom);
327
402
  }
328
- get size() {
329
- return this._selected.value.size;
403
+ get nodes() {
404
+ return this.graph.nodes;
330
405
  }
331
- get selected() {
332
- return this._selectedIds;
406
+ get edges() {
407
+ return this.graph.edges;
333
408
  }
334
- contains(id) {
335
- return this._selected.value.has(id);
409
+ copy(graph) {
410
+ return new _ReactiveGraphModel(this._registry, graph);
336
411
  }
337
412
  clear() {
338
- this._selected.value = /* @__PURE__ */ new Set();
413
+ this._registry.set(this._graphAtom, {
414
+ nodes: [],
415
+ edges: []
416
+ });
417
+ return this;
339
418
  }
340
- add(id) {
341
- invariant3(id, void 0, {
342
- F: __dxlog_file3,
343
- L: 41,
344
- S: this,
345
- A: [
346
- "id",
347
- ""
419
+ /**
420
+ * Set the entire graph at once, triggering a single notification.
421
+ */
422
+ setGraph(graph) {
423
+ this._registry.set(this._graphAtom, graph);
424
+ return this;
425
+ }
426
+ addNode(node) {
427
+ invariant3(node.id, "ID is required", { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 280, S: this, A: ["node.id", "'ID is required'"] });
428
+ invariant3(!this.findNode(node.id), `node already exists: ${node.id}`, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 281, S: this, A: ["!this.findNode(node.id)", "`node already exists: ${node.id}`"] });
429
+ const current = this._registry.get(this._graphAtom);
430
+ this._registry.set(this._graphAtom, {
431
+ ...current,
432
+ nodes: [
433
+ ...current.nodes,
434
+ node
348
435
  ]
349
436
  });
350
- this._selected.value = new Set(this._singleSelect ? [
351
- id
352
- ] : [
353
- ...Array.from(this._selected.value.values()),
354
- id
355
- ]);
437
+ return node;
356
438
  }
357
- remove(id) {
358
- invariant3(id, void 0, {
359
- F: __dxlog_file3,
360
- L: 48,
361
- S: this,
362
- A: [
363
- "id",
364
- ""
439
+ addEdge(edge) {
440
+ invariant3(edge.source, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 293, S: this, A: ["edge.source", ""] });
441
+ invariant3(edge.target, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 294, S: this, A: ["edge.target", ""] });
442
+ if (!edge.id) {
443
+ edge = {
444
+ id: createEdgeId(edge),
445
+ ...edge
446
+ };
447
+ }
448
+ invariant3(!this.findNode(edge.id), void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 301, S: this, A: ["!this.findNode(edge.id)", ""] });
449
+ const current = this._registry.get(this._graphAtom);
450
+ this._registry.set(this._graphAtom, {
451
+ ...current,
452
+ edges: [
453
+ ...current.edges,
454
+ edge
365
455
  ]
366
456
  });
367
- this._selected.value = new Set(Array.from(this._selected.value.values()).filter((_id) => _id !== id));
457
+ return edge;
368
458
  }
369
- // TODO(burdon): Handle single select.
370
- setSelected(ids, shift = false) {
371
- this._selected.value = /* @__PURE__ */ new Set([
372
- ...shift ? Array.from(this._selected.value.values()) : [],
373
- ...ids
374
- ]);
459
+ removeNode(id) {
460
+ const current = this._registry.get(this._graphAtom);
461
+ const removedEdges = current.edges.filter((edge) => edge.source === id || edge.target === id);
462
+ const removedNodes = current.nodes.filter((node) => node.id === id);
463
+ this._registry.set(this._graphAtom, {
464
+ nodes: current.nodes.filter((node) => node.id !== id),
465
+ edges: current.edges.filter((edge) => edge.source !== id && edge.target !== id)
466
+ });
467
+ return this.copy({
468
+ nodes: removedNodes,
469
+ edges: removedEdges
470
+ });
375
471
  }
376
- toggleSelected(ids, shift = false) {
377
- const set = new Set(shift ? Array.from(this._selected.value.values()) : void 0);
378
- ids.forEach((id) => {
379
- if (this._selected.value.has(id)) {
380
- set.delete(id);
381
- } else {
382
- set.add(id);
383
- }
472
+ removeEdge(id) {
473
+ const current = this._registry.get(this._graphAtom);
474
+ const removedEdges = current.edges.filter((edge) => edge.id === id);
475
+ this._registry.set(this._graphAtom, {
476
+ ...current,
477
+ edges: current.edges.filter((edge) => edge.id !== id)
478
+ });
479
+ return this.copy({
480
+ nodes: [],
481
+ edges: removedEdges
482
+ });
483
+ }
484
+ /**
485
+ * Subscribe to graph changes.
486
+ */
487
+ subscribe(cb, fire = false) {
488
+ if (fire) {
489
+ cb(this, this.graph);
490
+ }
491
+ this._registry.get(this._graphAtom);
492
+ return this._registry.subscribe(this._graphAtom, () => {
493
+ cb(this, this.graph);
384
494
  });
385
- this._selected.value = set;
386
495
  }
387
496
  };
388
-
389
- // src/types.ts
390
- import * as Schema from "effect/Schema";
391
- var BaseGraphNode = Schema.Struct({
392
- id: Schema.String,
393
- type: Schema.optional(Schema.String),
394
- data: Schema.optional(Schema.Any)
395
- });
396
- var BaseGraphEdge = Schema.Struct({
397
- id: Schema.String,
398
- type: Schema.optional(Schema.String),
399
- source: Schema.String,
400
- target: Schema.String,
401
- data: Schema.optional(Schema.Any)
402
- });
403
- var Graph = Schema.Struct({
404
- id: Schema.optional(Schema.String),
405
- nodes: Schema.mutable(Schema.Array(BaseGraphNode)),
406
- edges: Schema.mutable(Schema.Array(BaseGraphEdge))
407
- });
497
+ var Builder = class extends AbstractBuilder {
498
+ call(cb) {
499
+ cb(this);
500
+ return this;
501
+ }
502
+ };
408
503
  export {
409
- AbstractGraphBuilder,
410
- AbstractGraphModel,
411
- BaseGraphEdge,
412
- BaseGraphNode,
413
- Graph,
414
- GraphBuilder,
415
- GraphModel,
416
- ReactiveGraphModel,
417
- ReadonlyGraphModel,
418
- SelectionModel,
419
- createEdgeId,
420
- parseEdgeId,
421
- subscribe
504
+ Graph_exports as Graph,
505
+ GraphModel_exports as GraphModel,
506
+ SelectionModel
422
507
  };
423
508
  //# sourceMappingURL=index.mjs.map