@dxos/app-graph 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae

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.
Files changed (46) hide show
  1. package/dist/lib/browser/index.mjs +1350 -686
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +1349 -686
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/atoms.d.ts +8 -0
  8. package/dist/types/src/atoms.d.ts.map +1 -0
  9. package/dist/types/src/graph-builder.d.ts +112 -66
  10. package/dist/types/src/graph-builder.d.ts.map +1 -1
  11. package/dist/types/src/graph.d.ts +187 -221
  12. package/dist/types/src/graph.d.ts.map +1 -1
  13. package/dist/types/src/index.d.ts +6 -3
  14. package/dist/types/src/index.d.ts.map +1 -1
  15. package/dist/types/src/node-matcher.d.ts +218 -0
  16. package/dist/types/src/node-matcher.d.ts.map +1 -0
  17. package/dist/types/src/node-matcher.test.d.ts +2 -0
  18. package/dist/types/src/node-matcher.test.d.ts.map +1 -0
  19. package/dist/types/src/node.d.ts +42 -5
  20. package/dist/types/src/node.d.ts.map +1 -1
  21. package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
  22. package/dist/types/src/util.d.ts +24 -0
  23. package/dist/types/src/util.d.ts.map +1 -0
  24. package/dist/types/tsconfig.tsbuildinfo +1 -1
  25. package/package.json +36 -34
  26. package/src/atoms.ts +25 -0
  27. package/src/graph-builder.test.ts +626 -119
  28. package/src/graph-builder.ts +667 -288
  29. package/src/graph.test.ts +429 -121
  30. package/src/graph.ts +1041 -403
  31. package/src/index.ts +9 -3
  32. package/src/node-matcher.test.ts +301 -0
  33. package/src/node-matcher.ts +282 -0
  34. package/src/node.ts +53 -8
  35. package/src/stories/EchoGraph.stories.tsx +158 -119
  36. package/src/stories/Tree.tsx +1 -1
  37. package/src/util.ts +55 -0
  38. package/dist/types/src/experimental/graph-projections.test.d.ts +0 -25
  39. package/dist/types/src/experimental/graph-projections.test.d.ts.map +0 -1
  40. package/dist/types/src/signals-integration.test.d.ts +0 -2
  41. package/dist/types/src/signals-integration.test.d.ts.map +0 -1
  42. package/dist/types/src/testing.d.ts +0 -5
  43. package/dist/types/src/testing.d.ts.map +0 -1
  44. package/src/experimental/graph-projections.test.ts +0 -56
  45. package/src/signals-integration.test.ts +0 -218
  46. package/src/testing.ts +0 -20
@@ -2,37 +2,29 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Registry, RegistryContext, Rx, useRxValue } from '@effect-rx/rx-react';
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';
9
- import React, { type PropsWithChildren, useCallback, useContext, useEffect, useMemo, useState } from 'react';
10
-
11
- import {
12
- Expando,
13
- Filter,
14
- type Live,
15
- Query,
16
- type QueryResult,
17
- type Space,
18
- SpaceState,
19
- isSpace,
20
- live,
21
- } from '@dxos/client/echo';
22
- import { Obj, Type } from '@dxos/echo';
9
+ import React, { type PropsWithChildren, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
10
+
11
+ import { Filter, type Space, SpaceState, isSpace } from '@dxos/client/echo';
12
+ import { Obj, Query } from '@dxos/echo';
13
+ import { TestSchema } from '@dxos/echo/testing';
14
+ import { AtomObj, AtomQuery } from '@dxos/echo-atom';
23
15
  import { faker } from '@dxos/random';
24
16
  import { type Client, useClient } from '@dxos/react-client';
25
17
  import { withClientProvider } from '@dxos/react-client/testing';
26
18
  import { Icon, IconButton, Input, Select } from '@dxos/react-ui';
27
19
  import { withTheme } from '@dxos/react-ui/testing';
28
- import { Path, Tree } from '@dxos/react-ui-list';
29
- import { getSize, mx } from '@dxos/react-ui-theme';
30
- import { byPosition, isNonNullable, safeParseInt } from '@dxos/util';
20
+ import { Path, Tree, type TreeModel } from '@dxos/react-ui-list';
21
+ import { getSize, mx } from '@dxos/ui-theme';
22
+ import { safeParseInt } from '@dxos/util';
31
23
 
32
- import { type ExpandableGraph, ROOT_ID } from '../graph';
33
- import { GraphBuilder, createExtension, rxFromObservable, rxFromSignal } from '../graph-builder';
34
- import { type Node } from '../node';
35
- import { rxFromQuery } from '../testing';
24
+ import * as CreateAtom from '../atoms';
25
+ import * as Graph from '../graph';
26
+ import * as GraphBuilder from '../graph-builder';
27
+ import * as Node from '../node';
36
28
 
37
29
  import { JsonTree } from './Tree';
38
30
 
@@ -56,43 +48,44 @@ const actionWeights = {
56
48
  [Action.RENAME_OBJECT]: 4,
57
49
  };
58
50
 
59
- const createGraph = (client: Client, registry: Registry.Registry): ExpandableGraph => {
60
- const spaceBuilderExtension = createExtension({
51
+ const createGraph = (client: Client, registry: Registry.Registry): Graph.ExpandableGraph => {
52
+ const spaceBuilderExtension = GraphBuilder.createExtensionRaw({
61
53
  id: 'space',
62
54
  connector: (node) =>
63
- Rx.make((get) =>
55
+ Atom.make((get) =>
64
56
  Function.pipe(
65
57
  get(node),
66
- Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
58
+ Option.flatMap((node) => (node.id === Node.RootId ? Option.some(node) : Option.none())),
67
59
  Option.map(() => {
68
- const spaces = get(rxFromObservable(client.spaces)) ?? [];
60
+ const spaces = get(CreateAtom.fromObservable(client.spaces)) ?? [];
69
61
  return spaces
70
- .filter((space) => get(rxFromObservable(space.state)) === SpaceState.SPACE_READY)
71
- .map((space) => ({
72
- id: space.id,
73
- type: 'dxos.org/type/Space',
74
- properties: { label: get(rxFromSignal(() => space.properties.name)) },
75
- data: space,
76
- }));
62
+ .filter((space: any) => get(CreateAtom.fromObservable(space.state)) === SpaceState.SPACE_READY)
63
+ .map((space) => {
64
+ const propertiesSnapshot = get(AtomObj.make(space.properties));
65
+ return {
66
+ id: space.id,
67
+ type: 'dxos.org/type/Space',
68
+ properties: {
69
+ label: propertiesSnapshot.name,
70
+ },
71
+ data: space,
72
+ };
73
+ });
77
74
  }),
78
75
  Option.getOrElse(() => []),
79
76
  ),
80
77
  ),
81
78
  });
82
79
 
83
- const objectBuilderExtension = createExtension({
80
+ const objectBuilderExtension = GraphBuilder.createExtensionRaw({
84
81
  id: 'object',
85
82
  connector: (node) => {
86
- let query: QueryResult<Live<Expando>> | undefined;
87
- return Rx.make((get) =>
83
+ return Atom.make((get) =>
88
84
  Function.pipe(
89
85
  get(node),
90
86
  Option.flatMap((node) => (isSpace(node.data) ? Option.some(node.data) : Option.none())),
91
87
  Option.map((space) => {
92
- if (!query) {
93
- query = space.db.query(Query.type(Expando, { type: 'test' }));
94
- }
95
- const objects = get(rxFromQuery(query));
88
+ const objects = get(AtomQuery.make(space.db, Query.type(TestSchema.Expando, { type: 'test' })));
96
89
  return objects.map((object) => ({
97
90
  id: object.id,
98
91
  type: 'dxos.org/type/test',
@@ -106,15 +99,15 @@ const createGraph = (client: Client, registry: Registry.Registry): ExpandableGra
106
99
  },
107
100
  });
108
101
 
109
- const graph = new GraphBuilder({ registry })
110
- .addExtension(spaceBuilderExtension)
111
- .addExtension(objectBuilderExtension).graph;
102
+ const builder = GraphBuilder.make({ registry });
103
+ GraphBuilder.addExtension(builder, spaceBuilderExtension);
104
+ GraphBuilder.addExtension(builder, objectBuilderExtension);
105
+ const graph = builder.graph;
112
106
  graph.onNodeChanged.on(({ id }) => {
113
- graph.expand(id);
107
+ Graph.expand(graph, id, 'child');
114
108
  });
115
- graph.expand(ROOT_ID);
109
+ Graph.expand(graph, Node.RootId, 'child');
116
110
  (window as any).graph = graph;
117
-
118
111
  return graph;
119
112
  };
120
113
 
@@ -134,9 +127,9 @@ const getRandomSpace = (client: Client): Space | undefined => {
134
127
  const getSpaceWithObjects = async (client: Client): Promise<Space | undefined> => {
135
128
  const readySpaces = client.spaces.get().filter((space) => space.state.get() === SpaceState.SPACE_READY);
136
129
  const spaceQueries = await Promise.all(
137
- readySpaces.map((space) => space.db.query(Filter.type(Expando, { type: 'test' })).run()),
130
+ readySpaces.map((space) => space.db.query(Filter.type(TestSchema.Expando, { type: 'test' })).run()),
138
131
  );
139
- const spaces = readySpaces.filter((space, index) => spaceQueries[index].objects.length > 0);
132
+ const spaces = readySpaces.filter((space, index) => spaceQueries[index].length > 0);
140
133
  return spaces[Math.floor(Math.random() * spaces.length)];
141
134
  };
142
135
 
@@ -153,19 +146,26 @@ const runAction = async (client: Client, action: Action) => {
153
146
  case Action.RENAME_SPACE: {
154
147
  const space = getRandomSpace(client);
155
148
  if (space) {
156
- space.properties.name = faker.commerce.productName();
149
+ Obj.change(space.properties, (p) => {
150
+ p.name = faker.commerce.productName();
151
+ });
157
152
  }
158
153
  break;
159
154
  }
160
155
 
161
156
  case Action.ADD_OBJECT:
162
- getRandomSpace(client)?.db.add(Obj.make(Type.Expando, { type: 'test', name: faker.commerce.productName() }));
157
+ getRandomSpace(client)?.db.add(
158
+ Obj.make(TestSchema.Expando, {
159
+ type: 'test',
160
+ name: faker.commerce.productName(),
161
+ }),
162
+ );
163
163
  break;
164
164
 
165
165
  case Action.REMOVE_OBJECT: {
166
166
  const space = await getSpaceWithObjects(client);
167
167
  if (space) {
168
- const { objects } = await space.db.query(Filter.type(Expando, { type: 'test' })).run();
168
+ const objects = await space.db.query(Filter.type(TestSchema.Expando, { type: 'test' })).run();
169
169
  space.db.remove(objects[Math.floor(Math.random() * objects.length)]);
170
170
  }
171
171
  break;
@@ -174,8 +174,11 @@ const runAction = async (client: Client, action: Action) => {
174
174
  case Action.RENAME_OBJECT: {
175
175
  const space = await getSpaceWithObjects(client);
176
176
  if (space) {
177
- const { objects } = await space.db.query(Filter.type(Expando, { type: 'test' })).run();
178
- objects[Math.floor(Math.random() * objects.length)].name = faker.commerce.productName();
177
+ const objects = await space.db.query(Filter.type(TestSchema.Expando, { type: 'test' })).run();
178
+ const object = objects[Math.floor(Math.random() * objects.length)];
179
+ Obj.change(object, (o) => {
180
+ o.name = faker.commerce.productName();
181
+ });
179
182
  }
180
183
  break;
181
184
  }
@@ -214,13 +217,13 @@ const Controls = ({ children }: PropsWithChildren) => {
214
217
  <Input.TextInput
215
218
  autoComplete='off'
216
219
  size={5}
217
- classNames='w-[100px] text-right pie-[22px]'
220
+ classNames='w-[100px] text-right pe-[22px]'
218
221
  placeholder='Interval'
219
222
  value={actionInterval}
220
223
  onChange={({ target: { value } }) => setActionInterval(value)}
221
224
  />
222
225
  </Input.Root>
223
- <Icon icon='ph--timer--regular' classNames={mx('absolute inline-end-1 block-start-1 mt-[6px]', getSize(3))} />
226
+ <Icon icon='ph--timer--regular' classNames={mx('absolute right-1 top-1 mt-[6px]', getSize(3))} />
224
227
  </div>
225
228
  <IconButton icon='ph--plus--regular' label='Add' onClick={() => action && runAction(client, action)} />
226
229
  <Select.Root value={action?.toString()} onValueChange={(action) => setAction(action as unknown as Action)}>
@@ -249,16 +252,17 @@ const Controls = ({ children }: PropsWithChildren) => {
249
252
  const meta = {
250
253
  title: 'sdk/app-graph/EchoGraph',
251
254
  decorators: [
252
- withTheme,
255
+ withTheme(),
253
256
  withClientProvider({
254
257
  createIdentity: true,
258
+ types: [TestSchema.Expando],
255
259
  onCreateIdentity: async ({ client }) => {
256
260
  await client.spaces.create();
257
261
  await client.spaces.create();
258
262
  },
259
263
  }),
260
264
  ],
261
- } satisfies Meta<typeof Registry>;
265
+ } satisfies Meta;
262
266
 
263
267
  export default meta;
264
268
 
@@ -269,7 +273,7 @@ export const JsonView: Story = {
269
273
  const client = useClient();
270
274
  const registry = useContext(RegistryContext);
271
275
  const graph = useMemo(() => createGraph(client, registry), [client, registry]);
272
- const data = useRxValue(graph.json());
276
+ const data = useAtomValue(graph.json());
273
277
 
274
278
  return (
275
279
  <>
@@ -285,94 +289,129 @@ export const TreeView: Story = {
285
289
  const client = useClient();
286
290
  const registry = useContext(RegistryContext);
287
291
  const graph = useMemo(() => createGraph(client, registry), [client, registry]);
288
- const state = useMemo(() => new Map<string, Live<{ open: boolean; current: boolean }>>(), []);
289
-
290
- const useItems = useCallback(
291
- (node?: Node, options?: { disposition?: string; sort?: boolean }) => {
292
- const connections = useRxValue(graph.connections(node?.id ?? ROOT_ID));
293
- return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
292
+ const stateRef = useRef(new Map<string, Atom.Writable<{ open: boolean; current: boolean }>>());
293
+
294
+ const getOrCreateState = useMemo(
295
+ () => (pathKey: string) => {
296
+ let atom = stateRef.current.get(pathKey);
297
+ if (!atom) {
298
+ atom = Atom.make({ open: true, current: false }).pipe(Atom.keepAlive);
299
+ stateRef.current.set(pathKey, atom);
300
+ }
301
+ return atom;
294
302
  },
303
+ [],
304
+ );
305
+
306
+ const childIdsFamily = useMemo(
307
+ () =>
308
+ Atom.family((id: string) =>
309
+ Atom.make((get) => {
310
+ const connections = get(graph.connections(id, 'child'));
311
+ return connections.map((connection) => connection.id);
312
+ }),
313
+ ),
295
314
  [graph],
296
315
  );
297
316
 
298
- const getProps = useCallback(
299
- (node: Node, path: string[]) => {
300
- const children = graph
301
- .getConnections(node.id, 'outbound')
302
- .map((n) => {
303
- // Break cycles.
304
- const nextPath = [...path, node.id];
305
- return nextPath.includes(n.id) ? undefined : (n as Node);
306
- })
307
- .filter(isNonNullable) as Node[];
308
- const parentOf =
309
- children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
310
- return {
311
- id: node.id,
312
- label: node.id,
313
- icon: node.type === 'dxos.org/type/Space' ? 'ph--planet--regular' : 'ph--placeholder--regular',
314
- parentOf,
315
- };
316
- },
317
+ const itemFamily = useMemo(
318
+ () =>
319
+ Atom.family((id: string) =>
320
+ Atom.make((get) => {
321
+ const node = get(graph.node(id));
322
+ return Option.isSome(node) ? node.value : undefined;
323
+ }),
324
+ ),
317
325
  [graph],
318
326
  );
319
327
 
320
- const isOpen = useCallback(
321
- (_path: string[]) => {
322
- const path = Path.create(..._path);
323
- const object = state.get(path) ?? live({ open: true, current: false });
324
- if (!state.has(path)) {
325
- state.set(path, object);
326
- }
328
+ const itemPropsFamily = useMemo(
329
+ () =>
330
+ Atom.family((pathKey: string) => {
331
+ const path = pathKey.split('~');
332
+ const id = path[path.length - 1];
333
+ return Atom.make((get) => {
334
+ const nodeOpt = get(graph.node(id));
335
+ const node = Option.isSome(nodeOpt) ? nodeOpt.value : undefined;
336
+ if (!node) {
337
+ return { id, label: id };
338
+ }
339
+ const connections = get(graph.connections(node.id, 'child'));
340
+ const safeChildren = connections.filter((n) => !path.includes(n.id));
341
+ const parentOf =
342
+ safeChildren.length > 0
343
+ ? safeChildren.map(({ id }) => id)
344
+ : node.properties.role === 'branch'
345
+ ? []
346
+ : undefined;
347
+ return {
348
+ id: node.id,
349
+ label: node.id,
350
+ icon: node.type === 'dxos.org/type/Space' ? 'ph--planet--regular' : 'ph--placeholder--regular',
351
+ parentOf,
352
+ };
353
+ });
354
+ }),
355
+ [graph],
356
+ );
327
357
 
328
- return object.open;
329
- },
330
- [state],
358
+ const itemOpenFamily = useMemo(
359
+ () =>
360
+ Atom.family((pathKey: string) => {
361
+ const stateAtom = getOrCreateState(pathKey);
362
+ return Atom.make((get) => get(stateAtom).open);
363
+ }),
364
+ [getOrCreateState],
331
365
  );
332
366
 
333
- const isCurrent = useCallback(
334
- (_path: string[]) => {
335
- const path = Path.create(..._path);
336
- const object = state.get(path) ?? live({ open: false, current: false });
337
- if (!state.has(path)) {
338
- state.set(path, object);
339
- }
367
+ const itemCurrentFamily = useMemo(
368
+ () =>
369
+ Atom.family((pathKey: string) => {
370
+ const stateAtom = getOrCreateState(pathKey);
371
+ return Atom.make((get) => get(stateAtom).current);
372
+ }),
373
+ [getOrCreateState],
374
+ );
340
375
 
341
- return object.current;
342
- },
343
- [state],
376
+ const model: TreeModel<Node.Node> = useMemo(
377
+ () => ({
378
+ childIds: (parentId?: string) => childIdsFamily(parentId ?? Node.RootId),
379
+ item: (id: string) => itemFamily(id),
380
+ itemProps: (path: string[]) => itemPropsFamily(path.join('~')),
381
+ itemOpen: (path: string[]) => itemOpenFamily(Path.create(...path)),
382
+ itemCurrent: (path: string[]) => itemCurrentFamily(Path.create(...path)),
383
+ }),
384
+ [childIdsFamily, itemFamily, itemPropsFamily, itemOpenFamily, itemCurrentFamily],
344
385
  );
345
386
 
346
387
  const onOpenChange = useCallback(
347
388
  ({ path: _path, open }: { path: string[]; open: boolean }) => {
348
389
  const path = Path.create(..._path);
349
- const object = state.get(path);
350
- object!.open = open;
390
+ const atom = stateRef.current.get(path);
391
+ if (atom) {
392
+ const prev = registry.get(atom);
393
+ registry.set(atom, { ...prev, open });
394
+ }
351
395
  },
352
- [state],
396
+ [registry],
353
397
  );
354
398
 
355
399
  const onSelect = useCallback(
356
400
  ({ path: _path, current }: { path: string[]; current: boolean }) => {
357
401
  const path = Path.create(..._path);
358
- const object = state.get(path);
359
- object!.current = current;
402
+ const atom = stateRef.current.get(path);
403
+ if (atom) {
404
+ const prev = registry.get(atom);
405
+ registry.set(atom, { ...prev, current });
406
+ }
360
407
  },
361
- [state],
408
+ [registry],
362
409
  );
363
410
 
364
411
  return (
365
412
  <>
366
413
  <Controls />
367
- <Tree
368
- id={ROOT_ID}
369
- useItems={useItems}
370
- getProps={getProps}
371
- isOpen={isOpen}
372
- isCurrent={isCurrent}
373
- onOpenChange={onOpenChange}
374
- onSelect={onSelect}
375
- />
414
+ <Tree model={model} id={Node.RootId} onOpenChange={onOpenChange} onSelect={onSelect} />
376
415
  </>
377
416
  );
378
417
  },
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { type FC, type HTMLAttributes, useState } from 'react';
6
6
 
7
- import { mx } from '@dxos/react-ui-theme';
7
+ import { mx } from '@dxos/ui-theme';
8
8
 
9
9
  // TODO(burdon): Copied form devtools.
10
10
 
package/src/util.ts ADDED
@@ -0,0 +1,55 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Node from './node';
6
+
7
+ /**
8
+ * Key separators for compound string keys used across the app-graph package.
9
+ * `primary` separates top-level components (e.g., node ID from relation).
10
+ * `secondary` separates sub-components within an encoded value (e.g., relation kind from direction).
11
+ * Two distinct characters are needed because secondary separators appear inside primary-separated fields.
12
+ */
13
+ export const Separators = {
14
+ primary: '\u0001',
15
+ secondary: '\u0002',
16
+ } as const;
17
+
18
+ /**
19
+ * Normalize a relation input to a full Relation object.
20
+ */
21
+ export const normalizeRelation = (relation?: Node.RelationInput): Node.Relation =>
22
+ relation == null ? Node.childRelation() : typeof relation === 'string' ? Node.relation(relation) : relation;
23
+
24
+ /**
25
+ * Shallow-compare two values: same reference, or same own-keys with === values.
26
+ */
27
+ export const shallowEqual = (a: unknown, b: unknown): boolean => {
28
+ if (a === b) return true;
29
+ if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object') return false;
30
+ const keysA = Object.keys(a as Record<string, unknown>);
31
+ const keysB = Object.keys(b as Record<string, unknown>);
32
+ if (keysA.length !== keysB.length) {
33
+ return false;
34
+ }
35
+ return keysA.every((k) => (a as Record<string, unknown>)[k] === (b as Record<string, unknown>)[k]);
36
+ };
37
+
38
+ /**
39
+ * Returns true if two NodeArg arrays are semantically identical (same id, type, data, properties per index).
40
+ */
41
+ export const nodeArgsUnchanged = (prev: Node.NodeArg<any>[], next: Node.NodeArg<any>[]): boolean => {
42
+ if (prev.length !== next.length) {
43
+ return false;
44
+ }
45
+
46
+ return prev.every((prevNode, idx) => {
47
+ const nextNode = next[idx];
48
+ return (
49
+ prevNode.id === nextNode.id &&
50
+ prevNode.type === nextNode.type &&
51
+ shallowEqual(prevNode.data, nextNode.data) &&
52
+ shallowEqual(prevNode.properties, nextNode.properties)
53
+ );
54
+ });
55
+ };
@@ -1,25 +0,0 @@
1
- type Cb = () => void;
2
- interface Query {
3
- id?: string[];
4
- typename?: string[];
5
- }
6
- /**
7
- * Lazy query result that can be executed.
8
- * Does not run without the run function being called.
9
- */
10
- interface QueryResult<T> {
11
- /**
12
- * Execute the query and subscribe to the result.
13
- * @param next Called at least once with the first value (maybe synchronously) and then for every subsequent update.
14
- * @param error Called on error. `next` is never called after that.
15
- * @returns Function to dispose the query and unsubscribe.
16
- */
17
- run(onData: (value?: T[]) => void, onError: (err: Error) => void): Cb;
18
- }
19
- declare const QueryResult: Readonly<{
20
- fromPromise: <T>(run: (onDispose: (cb: Cb) => void) => Promise<T[]>) => QueryResult<T>;
21
- }>;
22
- interface _Resolver<T> {
23
- query(query: Query): QueryResult<T>;
24
- }
25
- //# sourceMappingURL=graph-projections.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"graph-projections.test.d.ts","sourceRoot":"","sources":["../../../../src/experimental/graph-projections.test.ts"],"names":[],"mappings":"AAIA,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC;AAErB,UAAU,KAAK;IACb,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;GAGG;AACH,UAAU,WAAW,CAAC,CAAC;IACrB;;;;;OAKG;IACH,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAAG,EAAE,CAAC;CACvE;AAED,QAAA,MAAM,WAAW;kBACD,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,KAAG,WAAW,CAAC,CAAC,CAAC;EAyBpF,CAAC;AAEH,UAAU,SAAS,CAAC,CAAC;IACnB,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;CACrC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=signals-integration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"signals-integration.test.d.ts","sourceRoot":"","sources":["../../../src/signals-integration.test.ts"],"names":[],"mappings":""}
@@ -1,5 +0,0 @@
1
- import { Rx } from '@effect-rx/rx-react';
2
- import { type AnyEchoObject } from '@dxos/echo/internal';
3
- import { type QueryResult } from '@dxos/echo-db';
4
- export declare const rxFromQuery: <T extends AnyEchoObject>(query: QueryResult<T>) => Rx.Rx<T[]>;
5
- //# sourceMappingURL=testing.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../../src/testing.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AAEzC,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,aAAa,EAAE,OAAO,WAAW,CAAC,CAAC,CAAC,KAAG,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAUrF,CAAC"}
@@ -1,56 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- type Cb = () => void;
6
-
7
- interface Query {
8
- id?: string[];
9
- typename?: string[];
10
- }
11
-
12
- /**
13
- * Lazy query result that can be executed.
14
- * Does not run without the run function being called.
15
- */
16
- interface QueryResult<T> {
17
- /**
18
- * Execute the query and subscribe to the result.
19
- * @param next Called at least once with the first value (maybe synchronously) and then for every subsequent update.
20
- * @param error Called on error. `next` is never called after that.
21
- * @returns Function to dispose the query and unsubscribe.
22
- */
23
- run(onData: (value?: T[]) => void, onError: (err: Error) => void): Cb;
24
- }
25
-
26
- const QueryResult = Object.freeze({
27
- fromPromise: <T>(run: (onDispose: (cb: Cb) => void) => Promise<T[]>): QueryResult<T> => {
28
- return {
29
- run: (onData, onError) => {
30
- const cbs: Cb[] = [];
31
- let disposed = false;
32
- const dispose = () => {
33
- cbs.forEach((cb) => cb());
34
- disposed = true;
35
- };
36
- run((cb) => (disposed ? cb() : cbs.push(cb))).then(
37
- (data) => {
38
- if (disposed) {
39
- return;
40
- }
41
- onData(data);
42
- },
43
- (err) => {
44
- dispose();
45
- onError(err);
46
- },
47
- );
48
- return dispose;
49
- },
50
- };
51
- },
52
- });
53
-
54
- interface _Resolver<T> {
55
- query(query: Query): QueryResult<T>;
56
- }