@dxos/app-graph 0.8.4-main.406dc2a → 0.8.4-main.548089c
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 +173 -195
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +173 -195
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/graph-builder.d.ts +13 -13
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +18 -18
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/dist/types/src/testing.d.ts +2 -2
- package/dist/types/src/testing.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +22 -22
- package/src/graph-builder.test.ts +26 -26
- package/src/graph-builder.ts +42 -40
- package/src/graph.test.ts +2 -2
- package/src/graph.ts +89 -49
- package/src/signals-integration.test.ts +4 -4
- package/src/stories/EchoGraph.stories.tsx +15 -8
- package/src/stories/Tree.tsx +1 -1
- package/src/testing.ts +3 -3
package/src/graph.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom, Registry } from '@effect-atom/atom-react';
|
|
6
6
|
import * as Function from 'effect/Function';
|
|
7
7
|
import * as Option from 'effect/Option';
|
|
8
8
|
import * as Record from 'effect/Record';
|
|
@@ -16,7 +16,9 @@ import { type MakeOptional, isNonNullable } from '@dxos/util';
|
|
|
16
16
|
import { type Action, type ActionGroup, type Node, type NodeArg, type Relation } from './node';
|
|
17
17
|
|
|
18
18
|
const graphSymbol = Symbol('graph');
|
|
19
|
-
type DeepWriteable<T> = {
|
|
19
|
+
type DeepWriteable<T> = {
|
|
20
|
+
-readonly [K in keyof T]: T[K] extends object ? DeepWriteable<T[K]> : T[K];
|
|
21
|
+
};
|
|
20
22
|
type NodeInternal = DeepWriteable<Node> & { [graphSymbol]: Graph };
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -79,32 +81,32 @@ export interface ReadableGraph {
|
|
|
79
81
|
*/
|
|
80
82
|
toJSON(id?: string): object;
|
|
81
83
|
|
|
82
|
-
json(id?: string):
|
|
84
|
+
json(id?: string): Atom.Atom<any>;
|
|
83
85
|
|
|
84
86
|
/**
|
|
85
|
-
* Get the
|
|
87
|
+
* Get the atom key for the node with the given id.
|
|
86
88
|
*/
|
|
87
|
-
node(id: string):
|
|
89
|
+
node(id: string): Atom.Atom<Option.Option<Node>>;
|
|
88
90
|
|
|
89
91
|
/**
|
|
90
|
-
* Get the
|
|
92
|
+
* Get the atom key for the node with the given id.
|
|
91
93
|
*/
|
|
92
|
-
nodeOrThrow(id: string):
|
|
94
|
+
nodeOrThrow(id: string): Atom.Atom<Node>;
|
|
93
95
|
|
|
94
96
|
/**
|
|
95
|
-
* Get the
|
|
97
|
+
* Get the atom key for the connections of the node with the given id.
|
|
96
98
|
*/
|
|
97
|
-
connections(id: string, relation?: Relation):
|
|
99
|
+
connections(id: string, relation?: Relation): Atom.Atom<Node[]>;
|
|
98
100
|
|
|
99
101
|
/**
|
|
100
|
-
* Get the
|
|
102
|
+
* Get the atom key for the actions of the node with the given id.
|
|
101
103
|
*/
|
|
102
|
-
actions(id: string):
|
|
104
|
+
actions(id: string): Atom.Atom<(Action | ActionGroup)[]>;
|
|
103
105
|
|
|
104
106
|
/**
|
|
105
|
-
* Get the
|
|
107
|
+
* Get the atom key for the edges of the node with the given id.
|
|
106
108
|
*/
|
|
107
|
-
edges(id: string):
|
|
109
|
+
edges(id: string): Atom.Atom<Edges>;
|
|
108
110
|
|
|
109
111
|
/**
|
|
110
112
|
* Alias for `getNodeOrThrow(ROOT_ID)`.
|
|
@@ -228,7 +230,10 @@ export interface WritableGraph extends ExpandableGraph {
|
|
|
228
230
|
* The Graph represents the user interface information architecture of the application constructed via plugins.
|
|
229
231
|
*/
|
|
230
232
|
export class Graph implements WritableGraph {
|
|
231
|
-
readonly onNodeChanged = new Event<{
|
|
233
|
+
readonly onNodeChanged = new Event<{
|
|
234
|
+
id: string;
|
|
235
|
+
node: Option.Option<Node>;
|
|
236
|
+
}>();
|
|
232
237
|
|
|
233
238
|
private readonly _onExpand?: (id: string, relation: Relation) => void;
|
|
234
239
|
private readonly _onInitialize?: (id: string) => Promise<void>;
|
|
@@ -239,51 +244,59 @@ export class Graph implements WritableGraph {
|
|
|
239
244
|
private readonly _initialized = Record.empty<string, boolean>();
|
|
240
245
|
private readonly _initialEdges = Record.empty<string, Edges>();
|
|
241
246
|
private readonly _initialNodes = Record.fromEntries([
|
|
242
|
-
[
|
|
247
|
+
[
|
|
248
|
+
ROOT_ID,
|
|
249
|
+
this._constructNode({
|
|
250
|
+
id: ROOT_ID,
|
|
251
|
+
type: ROOT_TYPE,
|
|
252
|
+
data: null,
|
|
253
|
+
properties: {},
|
|
254
|
+
}),
|
|
255
|
+
],
|
|
243
256
|
]);
|
|
244
257
|
|
|
245
258
|
/** @internal */
|
|
246
|
-
readonly _node =
|
|
259
|
+
readonly _node = Atom.family<string, Atom.Writable<Option.Option<Node>>>((id) => {
|
|
247
260
|
const initial = Option.flatten(Record.get(this._initialNodes, id));
|
|
248
|
-
return
|
|
261
|
+
return Atom.make<Option.Option<Node>>(initial).pipe(Atom.keepAlive, Atom.withLabel(`graph:node:${id}`));
|
|
249
262
|
});
|
|
250
263
|
|
|
251
|
-
private readonly _nodeOrThrow =
|
|
252
|
-
return
|
|
264
|
+
private readonly _nodeOrThrow = Atom.family<string, Atom.Atom<Node>>((id) => {
|
|
265
|
+
return Atom.make((get) => {
|
|
253
266
|
const node = get(this._node(id));
|
|
254
267
|
invariant(Option.isSome(node), `Node not available: ${id}`);
|
|
255
268
|
return node.value;
|
|
256
269
|
});
|
|
257
270
|
});
|
|
258
271
|
|
|
259
|
-
private readonly _edges =
|
|
272
|
+
private readonly _edges = Atom.family<string, Atom.Writable<Edges>>((id) => {
|
|
260
273
|
const initial = Record.get(this._initialEdges, id).pipe(Option.getOrElse(() => ({ inbound: [], outbound: [] })));
|
|
261
|
-
return
|
|
274
|
+
return Atom.make<Edges>(initial).pipe(Atom.keepAlive, Atom.withLabel(`graph:edges:${id}`));
|
|
262
275
|
});
|
|
263
276
|
|
|
264
|
-
// NOTE: Currently the argument to the family needs to be referentially stable for the
|
|
265
|
-
// TODO(wittjosiah):
|
|
266
|
-
private readonly _connections =
|
|
267
|
-
return
|
|
277
|
+
// NOTE: Currently the argument to the family needs to be referentially stable for the atom to be referentially stable.
|
|
278
|
+
// TODO(wittjosiah): Atom feature request, support for something akin to `ComplexMap` to allow for complex arguments.
|
|
279
|
+
private readonly _connections = Atom.family<string, Atom.Atom<Node[]>>((key) => {
|
|
280
|
+
return Atom.make((get) => {
|
|
268
281
|
const [id, relation] = key.split('$');
|
|
269
282
|
const edges = get(this._edges(id));
|
|
270
283
|
return edges[relation as Relation]
|
|
271
284
|
.map((id) => get(this._node(id)))
|
|
272
285
|
.filter(Option.isSome)
|
|
273
286
|
.map((o) => o.value);
|
|
274
|
-
}).pipe(
|
|
287
|
+
}).pipe(Atom.withLabel(`graph:connections:${key}`));
|
|
275
288
|
});
|
|
276
289
|
|
|
277
|
-
private readonly _actions =
|
|
278
|
-
return
|
|
290
|
+
private readonly _actions = Atom.family<string, Atom.Atom<(Action | ActionGroup)[]>>((id) => {
|
|
291
|
+
return Atom.make((get) => {
|
|
279
292
|
return get(this._connections(`${id}$outbound`)).filter(
|
|
280
293
|
(node) => node.type === ACTION_TYPE || node.type === ACTION_GROUP_TYPE,
|
|
281
294
|
);
|
|
282
|
-
}).pipe(
|
|
295
|
+
}).pipe(Atom.withLabel(`graph:actions:${id}`));
|
|
283
296
|
});
|
|
284
297
|
|
|
285
|
-
private readonly _json =
|
|
286
|
-
return
|
|
298
|
+
private readonly _json = Atom.family<string, Atom.Atom<any>>((id) => {
|
|
299
|
+
return Atom.make((get) => {
|
|
287
300
|
const toJSON = (node: Node, seen: string[] = []): any => {
|
|
288
301
|
const nodes = get(this.connections(node.id));
|
|
289
302
|
const obj: Record<string, any> = {
|
|
@@ -307,7 +320,7 @@ export class Graph implements WritableGraph {
|
|
|
307
320
|
|
|
308
321
|
const root = get(this.nodeOrThrow(id));
|
|
309
322
|
return toJSON(root);
|
|
310
|
-
}).pipe(
|
|
323
|
+
}).pipe(Atom.withLabel(`graph:json:${id}`));
|
|
311
324
|
});
|
|
312
325
|
|
|
313
326
|
constructor({ registry, nodes, edges, onInitialize, onExpand, onRemoveNode }: GraphParams = {}) {
|
|
@@ -337,15 +350,15 @@ export class Graph implements WritableGraph {
|
|
|
337
350
|
return this._json(id);
|
|
338
351
|
}
|
|
339
352
|
|
|
340
|
-
node(id: string):
|
|
353
|
+
node(id: string): Atom.Atom<Option.Option<Node>> {
|
|
341
354
|
return this._node(id);
|
|
342
355
|
}
|
|
343
356
|
|
|
344
|
-
nodeOrThrow(id: string):
|
|
357
|
+
nodeOrThrow(id: string): Atom.Atom<Node> {
|
|
345
358
|
return this._nodeOrThrow(id);
|
|
346
359
|
}
|
|
347
360
|
|
|
348
|
-
connections(id: string, relation: Relation = 'outbound'):
|
|
361
|
+
connections(id: string, relation: Relation = 'outbound'): Atom.Atom<Node[]> {
|
|
349
362
|
return this._connections(`${id}$${relation}`);
|
|
350
363
|
}
|
|
351
364
|
|
|
@@ -353,7 +366,7 @@ export class Graph implements WritableGraph {
|
|
|
353
366
|
return this._actions(id);
|
|
354
367
|
}
|
|
355
368
|
|
|
356
|
-
edges(id: string):
|
|
369
|
+
edges(id: string): Atom.Atom<Edges> {
|
|
357
370
|
return this._edges(id);
|
|
358
371
|
}
|
|
359
372
|
|
|
@@ -401,7 +414,7 @@ export class Graph implements WritableGraph {
|
|
|
401
414
|
}
|
|
402
415
|
|
|
403
416
|
addNodes(nodes: NodeArg<any, Record<string, any>>[]): void {
|
|
404
|
-
|
|
417
|
+
Atom.batch(() => {
|
|
405
418
|
nodes.map((node) => this.addNode(node));
|
|
406
419
|
});
|
|
407
420
|
}
|
|
@@ -415,10 +428,20 @@ export class Graph implements WritableGraph {
|
|
|
415
428
|
const typeChanged = node.type !== type;
|
|
416
429
|
const dataChanged = node.data !== data;
|
|
417
430
|
const propertiesChanged = Object.keys(properties).some((key) => node.properties[key] !== properties[key]);
|
|
418
|
-
log('existing node', {
|
|
431
|
+
log('existing node', {
|
|
432
|
+
id,
|
|
433
|
+
typeChanged,
|
|
434
|
+
dataChanged,
|
|
435
|
+
propertiesChanged,
|
|
436
|
+
});
|
|
419
437
|
if (typeChanged || dataChanged || propertiesChanged) {
|
|
420
438
|
log('updating node', { id, type, data, properties });
|
|
421
|
-
const newNode = Option.some({
|
|
439
|
+
const newNode = Option.some({
|
|
440
|
+
...node,
|
|
441
|
+
type,
|
|
442
|
+
data,
|
|
443
|
+
properties: { ...node.properties, ...properties },
|
|
444
|
+
});
|
|
422
445
|
this._registry.set(nodeRx, newNode);
|
|
423
446
|
this.onNodeChanged.emit({ id, node: newNode });
|
|
424
447
|
}
|
|
@@ -432,7 +455,7 @@ export class Graph implements WritableGraph {
|
|
|
432
455
|
});
|
|
433
456
|
|
|
434
457
|
if (nodes) {
|
|
435
|
-
//
|
|
458
|
+
// Atom.batch(() => {
|
|
436
459
|
this.addNodes(nodes);
|
|
437
460
|
const _edges = nodes.map((node) => ({ source: id, target: node.id }));
|
|
438
461
|
this.addEdges(_edges);
|
|
@@ -445,14 +468,14 @@ export class Graph implements WritableGraph {
|
|
|
445
468
|
}
|
|
446
469
|
|
|
447
470
|
removeNodes(ids: string[], edges = false): void {
|
|
448
|
-
|
|
471
|
+
Atom.batch(() => {
|
|
449
472
|
ids.map((id) => this.removeNode(id, edges));
|
|
450
473
|
});
|
|
451
474
|
}
|
|
452
475
|
|
|
453
476
|
removeNode(id: string, edges = false): void {
|
|
454
477
|
const nodeRx = this._node(id);
|
|
455
|
-
// TODO(wittjosiah): Is there a way to mark these
|
|
478
|
+
// TODO(wittjosiah): Is there a way to mark these atom values for garbage collection?
|
|
456
479
|
this._registry.set(nodeRx, Option.none());
|
|
457
480
|
this.onNodeChanged.emit({ id, node: Option.none() });
|
|
458
481
|
// TODO(wittjosiah): Reset expanded and initialized flags?
|
|
@@ -470,7 +493,7 @@ export class Graph implements WritableGraph {
|
|
|
470
493
|
}
|
|
471
494
|
|
|
472
495
|
addEdges(edges: Edge[]): void {
|
|
473
|
-
|
|
496
|
+
Atom.batch(() => {
|
|
474
497
|
edges.map((edge) => this.addEdge(edge));
|
|
475
498
|
});
|
|
476
499
|
}
|
|
@@ -479,20 +502,32 @@ export class Graph implements WritableGraph {
|
|
|
479
502
|
const sourceRx = this._edges(edgeArg.source);
|
|
480
503
|
const source = this._registry.get(sourceRx);
|
|
481
504
|
if (!source.outbound.includes(edgeArg.target)) {
|
|
482
|
-
log('add outbound edge', {
|
|
483
|
-
|
|
505
|
+
log('add outbound edge', {
|
|
506
|
+
source: edgeArg.source,
|
|
507
|
+
target: edgeArg.target,
|
|
508
|
+
});
|
|
509
|
+
this._registry.set(sourceRx, {
|
|
510
|
+
inbound: source.inbound,
|
|
511
|
+
outbound: [...source.outbound, edgeArg.target],
|
|
512
|
+
});
|
|
484
513
|
}
|
|
485
514
|
|
|
486
515
|
const targetRx = this._edges(edgeArg.target);
|
|
487
516
|
const target = this._registry.get(targetRx);
|
|
488
517
|
if (!target.inbound.includes(edgeArg.source)) {
|
|
489
|
-
log('add inbound edge', {
|
|
490
|
-
|
|
518
|
+
log('add inbound edge', {
|
|
519
|
+
source: edgeArg.source,
|
|
520
|
+
target: edgeArg.target,
|
|
521
|
+
});
|
|
522
|
+
this._registry.set(targetRx, {
|
|
523
|
+
inbound: [...target.inbound, edgeArg.source],
|
|
524
|
+
outbound: target.outbound,
|
|
525
|
+
});
|
|
491
526
|
}
|
|
492
527
|
}
|
|
493
528
|
|
|
494
529
|
removeEdges(edges: Edge[], removeOrphans = false): void {
|
|
495
|
-
|
|
530
|
+
Atom.batch(() => {
|
|
496
531
|
edges.map((edge) => this.removeEdge(edge, removeOrphans));
|
|
497
532
|
});
|
|
498
533
|
}
|
|
@@ -599,6 +634,11 @@ export class Graph implements WritableGraph {
|
|
|
599
634
|
|
|
600
635
|
/** @internal */
|
|
601
636
|
_constructNode(node: NodeArg<any>): Option.Option<Node> {
|
|
602
|
-
return Option.some({
|
|
637
|
+
return Option.some({
|
|
638
|
+
[graphSymbol]: this,
|
|
639
|
+
data: null,
|
|
640
|
+
properties: {},
|
|
641
|
+
...node,
|
|
642
|
+
});
|
|
603
643
|
}
|
|
604
644
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom, Registry } from '@effect-atom/atom-react';
|
|
6
6
|
import { signal } from '@preact/signals-core';
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, onTestFinished, test } from 'vitest';
|
|
8
8
|
|
|
@@ -26,7 +26,7 @@ describe('signals integration', () => {
|
|
|
26
26
|
const registry = Registry.make();
|
|
27
27
|
const state = signal<number>(0);
|
|
28
28
|
const value = rxFromSignal(() => state.value);
|
|
29
|
-
const inline =
|
|
29
|
+
const inline = Atom.make((get) => {
|
|
30
30
|
// NOTE: This will create a new rx instance each time.
|
|
31
31
|
// This test is verifying that this behaves the same as using a stable rx instance.
|
|
32
32
|
// The parent will remain subscribed to one instance until the new one is created.
|
|
@@ -138,7 +138,7 @@ describe('signals integration', () => {
|
|
|
138
138
|
createExtension({
|
|
139
139
|
id: 'outbound-connector',
|
|
140
140
|
connector: () =>
|
|
141
|
-
|
|
141
|
+
Atom.make((get) => {
|
|
142
142
|
const inner = get(innerRx) as any;
|
|
143
143
|
return inner ? [{ id: inner.id, type: EXAMPLE_TYPE, data: inner.name }] : [];
|
|
144
144
|
}),
|
|
@@ -184,7 +184,7 @@ describe('signals integration', () => {
|
|
|
184
184
|
connector: () => {
|
|
185
185
|
const query = db.query(Filter.type(Type.Expando));
|
|
186
186
|
|
|
187
|
-
return
|
|
187
|
+
return Atom.make((get) => {
|
|
188
188
|
const objects = get(rxFromQuery(query));
|
|
189
189
|
return objects.map((object) => ({ id: object.id, type: EXAMPLE_TYPE, data: object.name }));
|
|
190
190
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Registry, RegistryContext,
|
|
5
|
+
import { Atom, type Registry, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
|
|
6
6
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
7
|
import * as Function from 'effect/Function';
|
|
8
8
|
import * as Option from 'effect/Option';
|
|
@@ -60,7 +60,7 @@ const createGraph = (client: Client, registry: Registry.Registry): ExpandableGra
|
|
|
60
60
|
const spaceBuilderExtension = createExtension({
|
|
61
61
|
id: 'space',
|
|
62
62
|
connector: (node) =>
|
|
63
|
-
|
|
63
|
+
Atom.make((get) =>
|
|
64
64
|
Function.pipe(
|
|
65
65
|
get(node),
|
|
66
66
|
Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
|
|
@@ -71,7 +71,9 @@ const createGraph = (client: Client, registry: Registry.Registry): ExpandableGra
|
|
|
71
71
|
.map((space) => ({
|
|
72
72
|
id: space.id,
|
|
73
73
|
type: 'dxos.org/type/Space',
|
|
74
|
-
properties: {
|
|
74
|
+
properties: {
|
|
75
|
+
label: get(rxFromSignal(() => space.properties.name)),
|
|
76
|
+
},
|
|
75
77
|
data: space,
|
|
76
78
|
}));
|
|
77
79
|
}),
|
|
@@ -84,7 +86,7 @@ const createGraph = (client: Client, registry: Registry.Registry): ExpandableGra
|
|
|
84
86
|
id: 'object',
|
|
85
87
|
connector: (node) => {
|
|
86
88
|
let query: QueryResult<Live<Expando>> | undefined;
|
|
87
|
-
return
|
|
89
|
+
return Atom.make((get) =>
|
|
88
90
|
Function.pipe(
|
|
89
91
|
get(node),
|
|
90
92
|
Option.flatMap((node) => (isSpace(node.data) ? Option.some(node.data) : Option.none())),
|
|
@@ -159,7 +161,12 @@ const runAction = async (client: Client, action: Action) => {
|
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
case Action.ADD_OBJECT:
|
|
162
|
-
getRandomSpace(client)?.db.add(
|
|
164
|
+
getRandomSpace(client)?.db.add(
|
|
165
|
+
Obj.make(Type.Expando, {
|
|
166
|
+
type: 'test',
|
|
167
|
+
name: faker.commerce.productName(),
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
163
170
|
break;
|
|
164
171
|
|
|
165
172
|
case Action.REMOVE_OBJECT: {
|
|
@@ -258,7 +265,7 @@ const meta = {
|
|
|
258
265
|
},
|
|
259
266
|
}),
|
|
260
267
|
],
|
|
261
|
-
} satisfies Meta
|
|
268
|
+
} satisfies Meta;
|
|
262
269
|
|
|
263
270
|
export default meta;
|
|
264
271
|
|
|
@@ -269,7 +276,7 @@ export const JsonView: Story = {
|
|
|
269
276
|
const client = useClient();
|
|
270
277
|
const registry = useContext(RegistryContext);
|
|
271
278
|
const graph = useMemo(() => createGraph(client, registry), [client, registry]);
|
|
272
|
-
const data =
|
|
279
|
+
const data = useAtomValue(graph.json());
|
|
273
280
|
|
|
274
281
|
return (
|
|
275
282
|
<>
|
|
@@ -289,7 +296,7 @@ export const TreeView: Story = {
|
|
|
289
296
|
|
|
290
297
|
const useItems = useCallback(
|
|
291
298
|
(node?: Node, options?: { disposition?: string; sort?: boolean }) => {
|
|
292
|
-
const connections =
|
|
299
|
+
const connections = useAtomValue(graph.connections(node?.id ?? ROOT_ID));
|
|
293
300
|
return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
|
|
294
301
|
},
|
|
295
302
|
[graph],
|
package/src/stories/Tree.tsx
CHANGED
|
@@ -72,7 +72,7 @@ const Scalar: FC<{ value: any }> = ({ value }) => {
|
|
|
72
72
|
|
|
73
73
|
const Box: FC<HTMLAttributes<HTMLDivElement>> = ({ children, className, ...props }) => {
|
|
74
74
|
return (
|
|
75
|
-
<div className={mx('flex
|
|
75
|
+
<div className={mx('flex pli-2 border border-l-0 font-mono truncate', className)} {...props}>
|
|
76
76
|
{children}
|
|
77
77
|
</div>
|
|
78
78
|
);
|
package/src/testing.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
6
6
|
|
|
7
7
|
import { type AnyEchoObject } from '@dxos/echo/internal';
|
|
8
8
|
import { type QueryResult } from '@dxos/echo-db';
|
|
9
9
|
|
|
10
|
-
export const rxFromQuery = <T extends AnyEchoObject>(query: QueryResult<T>):
|
|
11
|
-
return
|
|
10
|
+
export const rxFromQuery = <T extends AnyEchoObject>(query: QueryResult<T>): Atom.Atom<T[]> => {
|
|
11
|
+
return Atom.make((get) => {
|
|
12
12
|
const unsubscribe = query.subscribe((result) => {
|
|
13
13
|
get.setSelf(result.objects);
|
|
14
14
|
});
|