@dxos/app-graph 0.8.4-main.84f28bd → 0.8.4-main.937b3ca

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 (44) hide show
  1. package/dist/lib/browser/index.mjs +1131 -612
  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 +1130 -612
  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 +113 -60
  10. package/dist/types/src/graph-builder.d.ts.map +1 -1
  11. package/dist/types/src/graph.d.ts +183 -209
  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 +32 -3
  20. package/dist/types/src/node.d.ts.map +1 -1
  21. package/dist/types/src/stories/EchoGraph.stories.d.ts +8 -10
  22. package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
  23. package/dist/types/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +37 -37
  25. package/src/atoms.ts +25 -0
  26. package/src/graph-builder.test.ts +571 -97
  27. package/src/graph-builder.ts +600 -258
  28. package/src/graph.test.ts +300 -107
  29. package/src/graph.ts +971 -400
  30. package/src/index.ts +9 -3
  31. package/src/node-matcher.test.ts +301 -0
  32. package/src/node-matcher.ts +284 -0
  33. package/src/node.ts +40 -5
  34. package/src/stories/EchoGraph.stories.tsx +126 -114
  35. package/src/stories/Tree.tsx +2 -2
  36. package/dist/types/src/experimental/graph-projections.test.d.ts +0 -25
  37. package/dist/types/src/experimental/graph-projections.test.d.ts.map +0 -1
  38. package/dist/types/src/signals-integration.test.d.ts +0 -2
  39. package/dist/types/src/signals-integration.test.d.ts.map +0 -1
  40. package/dist/types/src/testing.d.ts +0 -5
  41. package/dist/types/src/testing.d.ts.map +0 -1
  42. package/src/experimental/graph-projections.test.ts +0 -56
  43. package/src/signals-integration.test.ts +0 -218
  44. package/src/testing.ts +0 -20
@@ -2,40 +2,31 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
- import { type Registry, RegistryContext, Rx, useRxValue } from '@effect-rx/rx-react';
8
- import { Pause, Play, Plus, Timer } from '@phosphor-icons/react';
9
- import { type Meta } from '@storybook/react-vite';
10
- import { Option, pipe } from 'effect';
11
- import React, { type PropsWithChildren, useCallback, useContext, useEffect, useMemo, useState } from 'react';
12
-
13
- import {
14
- live,
15
- isSpace,
16
- Query,
17
- type QueryResult,
18
- type Space,
19
- SpaceState,
20
- Expando,
21
- type Live,
22
- Filter,
23
- } from '@dxos/client/echo';
24
- import { Obj, Type } from '@dxos/echo';
5
+ import { Atom, type Registry, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
6
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
7
+ import * as Function from 'effect/Function';
8
+ import * as Option from 'effect/Option';
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';
25
15
  import { faker } from '@dxos/random';
26
16
  import { type Client, useClient } from '@dxos/react-client';
27
17
  import { withClientProvider } from '@dxos/react-client/testing';
28
- import { Button, Input, Select } from '@dxos/react-ui';
18
+ import { Icon, IconButton, Input, Select } from '@dxos/react-ui';
19
+ import { withTheme } from '@dxos/react-ui/testing';
29
20
  import { Path, Tree } from '@dxos/react-ui-list';
30
- import { getSize, mx } from '@dxos/react-ui-theme';
31
- import { withTheme } from '@dxos/storybook-utils';
21
+ import { getSize, mx } from '@dxos/ui-theme';
32
22
  import { byPosition, isNonNullable, safeParseInt } from '@dxos/util';
33
23
 
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';
28
+
34
29
  import { JsonTree } from './Tree';
35
- import { type ExpandableGraph, ROOT_ID } from '../graph';
36
- import { GraphBuilder, createExtension, rxFromObservable, rxFromSignal } from '../graph-builder';
37
- import { type Node } from '../node';
38
- import { rxFromQuery } from '../testing';
39
30
 
40
31
  const DEFAULT_PERIOD = 500;
41
32
 
@@ -57,43 +48,44 @@ const actionWeights = {
57
48
  [Action.RENAME_OBJECT]: 4,
58
49
  };
59
50
 
60
- const createGraph = (client: Client, registry: Registry.Registry): ExpandableGraph => {
61
- const spaceBuilderExtension = createExtension({
51
+ const createGraph = (client: Client, registry: Registry.Registry): Graph.ExpandableGraph => {
52
+ const spaceBuilderExtension = GraphBuilder.createExtensionRaw({
62
53
  id: 'space',
63
54
  connector: (node) =>
64
- Rx.make((get) =>
65
- pipe(
55
+ Atom.make((get) =>
56
+ Function.pipe(
66
57
  get(node),
67
- 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())),
68
59
  Option.map(() => {
69
- const spaces = get(rxFromObservable(client.spaces)) ?? [];
60
+ const spaces = get(CreateAtom.fromObservable(client.spaces)) ?? [];
70
61
  return spaces
71
- .filter((space) => get(rxFromObservable(space.state)) === SpaceState.SPACE_READY)
72
- .map((space) => ({
73
- id: space.id,
74
- type: 'dxos.org/type/Space',
75
- properties: { label: get(rxFromSignal(() => space.properties.name)) },
76
- data: space,
77
- }));
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
+ });
78
74
  }),
79
75
  Option.getOrElse(() => []),
80
76
  ),
81
77
  ),
82
78
  });
83
79
 
84
- const objectBuilderExtension = createExtension({
80
+ const objectBuilderExtension = GraphBuilder.createExtensionRaw({
85
81
  id: 'object',
86
82
  connector: (node) => {
87
- let query: QueryResult<Live<Expando>> | undefined;
88
- return Rx.make((get) =>
89
- pipe(
83
+ return Atom.make((get) =>
84
+ Function.pipe(
90
85
  get(node),
91
86
  Option.flatMap((node) => (isSpace(node.data) ? Option.some(node.data) : Option.none())),
92
87
  Option.map((space) => {
93
- if (!query) {
94
- query = space.db.query(Query.type(Expando, { type: 'test' }));
95
- }
96
- const objects = get(rxFromQuery(query));
88
+ const objects = get(AtomQuery.make(space.db, Query.type(TestSchema.Expando, { type: 'test' })));
97
89
  return objects.map((object) => ({
98
90
  id: object.id,
99
91
  type: 'dxos.org/type/test',
@@ -107,15 +99,15 @@ const createGraph = (client: Client, registry: Registry.Registry): ExpandableGra
107
99
  },
108
100
  });
109
101
 
110
- const graph = new GraphBuilder({ registry })
111
- .addExtension(spaceBuilderExtension)
112
- .addExtension(objectBuilderExtension).graph;
102
+ const builder = GraphBuilder.make({ registry });
103
+ GraphBuilder.addExtension(builder, spaceBuilderExtension);
104
+ GraphBuilder.addExtension(builder, objectBuilderExtension);
105
+ const graph = builder.graph;
113
106
  graph.onNodeChanged.on(({ id }) => {
114
- graph.expand(id);
107
+ Graph.expand(graph, id);
115
108
  });
116
- graph.expand(ROOT_ID);
109
+ Graph.expand(graph, Node.RootId);
117
110
  (window as any).graph = graph;
118
-
119
111
  return graph;
120
112
  };
121
113
 
@@ -135,9 +127,9 @@ const getRandomSpace = (client: Client): Space | undefined => {
135
127
  const getSpaceWithObjects = async (client: Client): Promise<Space | undefined> => {
136
128
  const readySpaces = client.spaces.get().filter((space) => space.state.get() === SpaceState.SPACE_READY);
137
129
  const spaceQueries = await Promise.all(
138
- 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()),
139
131
  );
140
- const spaces = readySpaces.filter((space, index) => spaceQueries[index].objects.length > 0);
132
+ const spaces = readySpaces.filter((space, index) => spaceQueries[index].length > 0);
141
133
  return spaces[Math.floor(Math.random() * spaces.length)];
142
134
  };
143
135
 
@@ -154,19 +146,26 @@ const runAction = async (client: Client, action: Action) => {
154
146
  case Action.RENAME_SPACE: {
155
147
  const space = getRandomSpace(client);
156
148
  if (space) {
157
- space.properties.name = faker.commerce.productName();
149
+ Obj.change(space.properties, (p) => {
150
+ p.name = faker.commerce.productName();
151
+ });
158
152
  }
159
153
  break;
160
154
  }
161
155
 
162
156
  case Action.ADD_OBJECT:
163
- 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
+ );
164
163
  break;
165
164
 
166
165
  case Action.REMOVE_OBJECT: {
167
166
  const space = await getSpaceWithObjects(client);
168
167
  if (space) {
169
- 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();
170
169
  space.db.remove(objects[Math.floor(Math.random() * objects.length)]);
171
170
  }
172
171
  break;
@@ -175,8 +174,11 @@ const runAction = async (client: Client, action: Action) => {
175
174
  case Action.RENAME_OBJECT: {
176
175
  const space = await getSpaceWithObjects(client);
177
176
  if (space) {
178
- const { objects } = await space.db.query(Filter.type(Expando, { type: 'test' })).run();
179
- 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
+ });
180
182
  }
181
183
  break;
182
184
  }
@@ -205,23 +207,25 @@ const Controls = ({ children }: PropsWithChildren) => {
205
207
  return (
206
208
  <>
207
209
  <div className='flex shrink-0 p-2 space-x-2'>
208
- <Button onClick={() => setGenerating((generating) => !generating)}>{generating ? <Pause /> : <Play />}</Button>
210
+ <IconButton
211
+ icon={generating ? 'ph--pause--regular' : 'ph--play--regular'}
212
+ label={generating ? 'Pause' : 'Play'}
213
+ onClick={() => setGenerating((generating) => !generating)}
214
+ />
209
215
  <div className='relative' title='mutation period'>
210
216
  <Input.Root>
211
217
  <Input.TextInput
212
218
  autoComplete='off'
213
219
  size={5}
214
- classNames='w-[100px] text-right pie-[22px]'
220
+ classNames='is-[100px] text-right pie-[22px]'
215
221
  placeholder='Interval'
216
222
  value={actionInterval}
217
223
  onChange={({ target: { value } }) => setActionInterval(value)}
218
224
  />
219
225
  </Input.Root>
220
- <Timer className={mx('absolute inline-end-1 block-start-1 mt-[6px]', getSize(3))} />
226
+ <Icon icon='ph--timer--regular' classNames={mx('absolute inline-end-1 block-start-1 mt-[6px]', getSize(3))} />
221
227
  </div>
222
- <Button onClick={() => action && runAction(client, action)}>
223
- <Plus />
224
- </Button>
228
+ <IconButton icon='ph--plus--regular' label='Add' onClick={() => action && runAction(client, action)} />
225
229
  <Select.Root value={action?.toString()} onValueChange={(action) => setAction(action as unknown as Action)}>
226
230
  <Select.TriggerButton placeholder='Select value' />
227
231
  <Select.Portal>
@@ -245,28 +249,30 @@ const Controls = ({ children }: PropsWithChildren) => {
245
249
  );
246
250
  };
247
251
 
248
- const meta: Meta = {
252
+ const meta = {
249
253
  title: 'sdk/app-graph/EchoGraph',
250
254
  decorators: [
255
+ withTheme,
251
256
  withClientProvider({
252
257
  createIdentity: true,
253
- onIdentityCreated: async ({ client }) => {
258
+ onCreateIdentity: async ({ client }) => {
254
259
  await client.spaces.create();
255
260
  await client.spaces.create();
256
261
  },
257
262
  }),
258
- withTheme,
259
263
  ],
260
- };
264
+ } satisfies Meta;
261
265
 
262
266
  export default meta;
263
267
 
264
- export const JsonView = {
268
+ type Story = StoryObj<typeof meta>;
269
+
270
+ export const JsonView: Story = {
265
271
  render: () => {
266
272
  const client = useClient();
267
273
  const registry = useContext(RegistryContext);
268
274
  const graph = useMemo(() => createGraph(client, registry), [client, registry]);
269
- const data = useRxValue(graph.json());
275
+ const data = useAtomValue(graph.json());
270
276
 
271
277
  return (
272
278
  <>
@@ -277,31 +283,42 @@ export const JsonView = {
277
283
  },
278
284
  };
279
285
 
280
- export const TreeView = {
286
+ export const TreeView: Story = {
281
287
  render: () => {
282
288
  const client = useClient();
283
289
  const registry = useContext(RegistryContext);
284
290
  const graph = useMemo(() => createGraph(client, registry), [client, registry]);
285
- const state = useMemo(() => new Map<string, Live<{ open: boolean; current: boolean }>>(), []);
291
+ const stateRef = useRef(new Map<string, Atom.Writable<{ open: boolean; current: boolean }>>());
292
+
293
+ const getOrCreateState = useMemo(
294
+ () => (path: string) => {
295
+ let atom = stateRef.current.get(path);
296
+ if (!atom) {
297
+ atom = Atom.make({ open: true, current: false }).pipe(Atom.keepAlive);
298
+ stateRef.current.set(path, atom);
299
+ }
300
+ return atom;
301
+ },
302
+ [],
303
+ );
286
304
 
287
305
  const useItems = useCallback(
288
- (node?: Node, options?: { disposition?: string; sort?: boolean }) => {
289
- const connections = useRxValue(graph.connections(node?.id ?? ROOT_ID));
306
+ (node?: Node.Node, options?: { disposition?: string; sort?: boolean }) => {
307
+ const connections = useAtomValue(graph.connections(node?.id ?? Node.RootId));
290
308
  return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
291
309
  },
292
310
  [graph],
293
311
  );
294
312
 
295
313
  const getProps = useCallback(
296
- (node: Node, path: string[]) => {
297
- const children = graph
298
- .getConnections(node.id, 'outbound')
314
+ (node: Node.Node, path: string[]) => {
315
+ const children = Graph.getConnections(graph, node.id, 'outbound')
299
316
  .map((n) => {
300
317
  // Break cycles.
301
318
  const nextPath = [...path, node.id];
302
- return nextPath.includes(n.id) ? undefined : (n as Node);
319
+ return nextPath.includes(n.id) ? undefined : (n as Node.Node);
303
320
  })
304
- .filter(isNonNullable) as Node[];
321
+ .filter(isNonNullable) as Node.Node[];
305
322
  const parentOf =
306
323
  children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
307
324
  return {
@@ -314,59 +331,54 @@ export const TreeView = {
314
331
  [graph],
315
332
  );
316
333
 
317
- const isOpen = useCallback(
318
- (_path: string[]) => {
319
- const path = Path.create(..._path);
320
- const object = state.get(path) ?? live({ open: true, current: false });
321
- if (!state.has(path)) {
322
- state.set(path, object);
323
- }
334
+ // Hook that subscribes to item state via Atom.
335
+ const useItemState = (_path: string[]) => {
336
+ const path = useMemo(() => Path.create(..._path), [_path.join('~')]);
337
+ const atom = getOrCreateState(path);
338
+ return useAtomValue(atom);
339
+ };
324
340
 
325
- return object.open;
326
- },
327
- [state],
328
- );
341
+ const useIsOpen = (_path: string[]) => {
342
+ return useItemState(_path).open;
343
+ };
329
344
 
330
- const isCurrent = useCallback(
331
- (_path: string[]) => {
332
- const path = Path.create(..._path);
333
- const object = state.get(path) ?? live({ open: false, current: false });
334
- if (!state.has(path)) {
335
- state.set(path, object);
336
- }
337
-
338
- return object.current;
339
- },
340
- [state],
341
- );
345
+ const useIsCurrent = (_path: string[]) => {
346
+ return useItemState(_path).current;
347
+ };
342
348
 
343
349
  const onOpenChange = useCallback(
344
350
  ({ path: _path, open }: { path: string[]; open: boolean }) => {
345
351
  const path = Path.create(..._path);
346
- const object = state.get(path);
347
- object!.open = open;
352
+ const atom = stateRef.current.get(path);
353
+ if (atom) {
354
+ const prev = registry.get(atom);
355
+ registry.set(atom, { ...prev, open });
356
+ }
348
357
  },
349
- [state],
358
+ [registry],
350
359
  );
351
360
 
352
361
  const onSelect = useCallback(
353
362
  ({ path: _path, current }: { path: string[]; current: boolean }) => {
354
363
  const path = Path.create(..._path);
355
- const object = state.get(path);
356
- object!.current = current;
364
+ const atom = stateRef.current.get(path);
365
+ if (atom) {
366
+ const prev = registry.get(atom);
367
+ registry.set(atom, { ...prev, current });
368
+ }
357
369
  },
358
- [state],
370
+ [registry],
359
371
  );
360
372
 
361
373
  return (
362
374
  <>
363
375
  <Controls />
364
376
  <Tree
365
- id={ROOT_ID}
377
+ id={Node.RootId}
366
378
  useItems={useItems}
367
379
  getProps={getProps}
368
- isOpen={isOpen}
369
- isCurrent={isCurrent}
380
+ useIsOpen={useIsOpen}
381
+ useIsCurrent={useIsCurrent}
370
382
  onOpenChange={onOpenChange}
371
383
  onSelect={onSelect}
372
384
  />
@@ -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
 
@@ -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 px-2 border border-l-0 font-mono truncate', className)} {...props}>
75
+ <div className={mx('flex pli-2 border border-l-0 font-mono truncate', className)} {...props}>
76
76
  {children}
77
77
  </div>
78
78
  );
@@ -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 QueryResult } from '@dxos/echo-db';
3
- import { type AnyEchoObject } from '@dxos/echo-schema';
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,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,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
- }