@dxos/app-graph 0.8.4-main.5ea62a8 → 0.8.4-main.66e292d
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 +216 -233
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +216 -233
- 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 +16 -16
- package/dist/types/src/graph-builder.d.ts.map +1 -1
- package/dist/types/src/graph.d.ts +19 -19
- package/dist/types/src/graph.d.ts.map +1 -1
- package/dist/types/src/node.d.ts +1 -1
- package/dist/types/src/node.d.ts.map +1 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts +0 -1
- package/dist/types/src/stories/EchoGraph.stories.d.ts.map +1 -1
- package/dist/types/src/testing.d.ts +3 -4
- package/dist/types/src/testing.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -32
- package/src/graph-builder.test.ts +34 -33
- package/src/graph-builder.ts +50 -45
- package/src/graph.test.ts +3 -3
- package/src/graph.ts +115 -73
- package/src/node.ts +1 -1
- package/src/signals-integration.test.ts +39 -34
- package/src/stories/EchoGraph.stories.tsx +39 -41
- package/src/stories/Tree.tsx +1 -1
- package/src/testing.ts +6 -5
- package/dist/types/src/experimental/graph-projections.test.d.ts +0 -25
- package/dist/types/src/experimental/graph-projections.test.d.ts.map +0 -1
- package/src/experimental/graph-projections.test.ts +0 -56
|
@@ -2,38 +2,28 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
7
|
-
import { type Registry, RegistryContext, Rx, useRxValue } from '@effect-rx/rx-react';
|
|
5
|
+
import { Atom, type Registry, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
|
|
8
6
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
9
|
-
import
|
|
7
|
+
import * as Function from 'effect/Function';
|
|
8
|
+
import * as Option from 'effect/Option';
|
|
9
|
+
import type * as Schema from 'effect/Schema';
|
|
10
10
|
import React, { type PropsWithChildren, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
Filter,
|
|
15
|
-
type Live,
|
|
16
|
-
Query,
|
|
17
|
-
type QueryResult,
|
|
18
|
-
type Space,
|
|
19
|
-
SpaceState,
|
|
20
|
-
isSpace,
|
|
21
|
-
live,
|
|
22
|
-
} from '@dxos/client/echo';
|
|
23
|
-
import { Obj, Type } from '@dxos/echo';
|
|
12
|
+
import { Filter, type Live, Query, type Space, SpaceState, isSpace, live } from '@dxos/client/echo';
|
|
13
|
+
import { type Database, Obj, Type } from '@dxos/echo';
|
|
24
14
|
import { faker } from '@dxos/random';
|
|
25
15
|
import { type Client, useClient } from '@dxos/react-client';
|
|
26
16
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
27
17
|
import { Icon, IconButton, Input, Select } from '@dxos/react-ui';
|
|
18
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
28
19
|
import { Path, Tree } from '@dxos/react-ui-list';
|
|
29
20
|
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
30
|
-
import { withTheme } from '@dxos/storybook-utils';
|
|
31
21
|
import { byPosition, isNonNullable, safeParseInt } from '@dxos/util';
|
|
32
22
|
|
|
33
23
|
import { type ExpandableGraph, ROOT_ID } from '../graph';
|
|
34
|
-
import { GraphBuilder,
|
|
24
|
+
import { GraphBuilder, atomFromObservable, atomFromSignal, createExtension } from '../graph-builder';
|
|
35
25
|
import { type Node } from '../node';
|
|
36
|
-
import {
|
|
26
|
+
import { atomFromQuery } from '../testing';
|
|
37
27
|
|
|
38
28
|
import { JsonTree } from './Tree';
|
|
39
29
|
|
|
@@ -61,18 +51,20 @@ const createGraph = (client: Client, registry: Registry.Registry): ExpandableGra
|
|
|
61
51
|
const spaceBuilderExtension = createExtension({
|
|
62
52
|
id: 'space',
|
|
63
53
|
connector: (node) =>
|
|
64
|
-
|
|
65
|
-
pipe(
|
|
54
|
+
Atom.make((get) =>
|
|
55
|
+
Function.pipe(
|
|
66
56
|
get(node),
|
|
67
57
|
Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
|
|
68
58
|
Option.map(() => {
|
|
69
|
-
const spaces = get(
|
|
59
|
+
const spaces = get(atomFromObservable(client.spaces)) ?? [];
|
|
70
60
|
return spaces
|
|
71
|
-
.filter((space) => get(
|
|
61
|
+
.filter((space) => get(atomFromObservable(space.state)) === SpaceState.SPACE_READY)
|
|
72
62
|
.map((space) => ({
|
|
73
63
|
id: space.id,
|
|
74
64
|
type: 'dxos.org/type/Space',
|
|
75
|
-
properties: {
|
|
65
|
+
properties: {
|
|
66
|
+
label: get(atomFromSignal(() => space.properties.name)),
|
|
67
|
+
},
|
|
76
68
|
data: space,
|
|
77
69
|
}));
|
|
78
70
|
}),
|
|
@@ -84,16 +76,18 @@ const createGraph = (client: Client, registry: Registry.Registry): ExpandableGra
|
|
|
84
76
|
const objectBuilderExtension = createExtension({
|
|
85
77
|
id: 'object',
|
|
86
78
|
connector: (node) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
// TODO(wittjosiah): Find a simpler way to define this type.
|
|
80
|
+
let result: Database.QueryResult<Schema.Schema.Type<typeof Type.Expando>> | undefined;
|
|
81
|
+
return Atom.make((get) =>
|
|
82
|
+
Function.pipe(
|
|
90
83
|
get(node),
|
|
91
84
|
Option.flatMap((node) => (isSpace(node.data) ? Option.some(node.data) : Option.none())),
|
|
92
85
|
Option.map((space) => {
|
|
93
|
-
if (!
|
|
94
|
-
|
|
86
|
+
if (!result) {
|
|
87
|
+
result = space.db.query(Query.type(Type.Expando, { type: 'test' }));
|
|
95
88
|
}
|
|
96
|
-
|
|
89
|
+
|
|
90
|
+
const objects = get(atomFromQuery(result));
|
|
97
91
|
return objects.map((object) => ({
|
|
98
92
|
id: object.id,
|
|
99
93
|
type: 'dxos.org/type/test',
|
|
@@ -115,7 +109,6 @@ const createGraph = (client: Client, registry: Registry.Registry): ExpandableGra
|
|
|
115
109
|
});
|
|
116
110
|
graph.expand(ROOT_ID);
|
|
117
111
|
(window as any).graph = graph;
|
|
118
|
-
|
|
119
112
|
return graph;
|
|
120
113
|
};
|
|
121
114
|
|
|
@@ -135,7 +128,7 @@ const getRandomSpace = (client: Client): Space | undefined => {
|
|
|
135
128
|
const getSpaceWithObjects = async (client: Client): Promise<Space | undefined> => {
|
|
136
129
|
const readySpaces = client.spaces.get().filter((space) => space.state.get() === SpaceState.SPACE_READY);
|
|
137
130
|
const spaceQueries = await Promise.all(
|
|
138
|
-
readySpaces.map((space) => space.db.query(Filter.type(Expando, { type: 'test' })).run()),
|
|
131
|
+
readySpaces.map((space) => space.db.query(Filter.type(Type.Expando, { type: 'test' })).run()),
|
|
139
132
|
);
|
|
140
133
|
const spaces = readySpaces.filter((space, index) => spaceQueries[index].objects.length > 0);
|
|
141
134
|
return spaces[Math.floor(Math.random() * spaces.length)];
|
|
@@ -160,13 +153,18 @@ const runAction = async (client: Client, action: Action) => {
|
|
|
160
153
|
}
|
|
161
154
|
|
|
162
155
|
case Action.ADD_OBJECT:
|
|
163
|
-
getRandomSpace(client)?.db.add(
|
|
156
|
+
getRandomSpace(client)?.db.add(
|
|
157
|
+
Obj.make(Type.Expando, {
|
|
158
|
+
type: 'test',
|
|
159
|
+
name: faker.commerce.productName(),
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
164
162
|
break;
|
|
165
163
|
|
|
166
164
|
case Action.REMOVE_OBJECT: {
|
|
167
165
|
const space = await getSpaceWithObjects(client);
|
|
168
166
|
if (space) {
|
|
169
|
-
const { objects } = await space.db.query(Filter.type(Expando, { type: 'test' })).run();
|
|
167
|
+
const { objects } = await space.db.query(Filter.type(Type.Expando, { type: 'test' })).run();
|
|
170
168
|
space.db.remove(objects[Math.floor(Math.random() * objects.length)]);
|
|
171
169
|
}
|
|
172
170
|
break;
|
|
@@ -175,7 +173,7 @@ const runAction = async (client: Client, action: Action) => {
|
|
|
175
173
|
case Action.RENAME_OBJECT: {
|
|
176
174
|
const space = await getSpaceWithObjects(client);
|
|
177
175
|
if (space) {
|
|
178
|
-
const { objects } = await space.db.query(Filter.type(Expando, { type: 'test' })).run();
|
|
176
|
+
const { objects } = await space.db.query(Filter.type(Type.Expando, { type: 'test' })).run();
|
|
179
177
|
objects[Math.floor(Math.random() * objects.length)].name = faker.commerce.productName();
|
|
180
178
|
}
|
|
181
179
|
break;
|
|
@@ -215,7 +213,7 @@ const Controls = ({ children }: PropsWithChildren) => {
|
|
|
215
213
|
<Input.TextInput
|
|
216
214
|
autoComplete='off'
|
|
217
215
|
size={5}
|
|
218
|
-
classNames='
|
|
216
|
+
classNames='is-[100px] text-right pie-[22px]'
|
|
219
217
|
placeholder='Interval'
|
|
220
218
|
value={actionInterval}
|
|
221
219
|
onChange={({ target: { value } }) => setActionInterval(value)}
|
|
@@ -250,16 +248,16 @@ const Controls = ({ children }: PropsWithChildren) => {
|
|
|
250
248
|
const meta = {
|
|
251
249
|
title: 'sdk/app-graph/EchoGraph',
|
|
252
250
|
decorators: [
|
|
251
|
+
withTheme,
|
|
253
252
|
withClientProvider({
|
|
254
253
|
createIdentity: true,
|
|
255
|
-
|
|
254
|
+
onCreateIdentity: async ({ client }) => {
|
|
256
255
|
await client.spaces.create();
|
|
257
256
|
await client.spaces.create();
|
|
258
257
|
},
|
|
259
258
|
}),
|
|
260
|
-
withTheme,
|
|
261
259
|
],
|
|
262
|
-
} satisfies Meta
|
|
260
|
+
} satisfies Meta;
|
|
263
261
|
|
|
264
262
|
export default meta;
|
|
265
263
|
|
|
@@ -270,7 +268,7 @@ export const JsonView: Story = {
|
|
|
270
268
|
const client = useClient();
|
|
271
269
|
const registry = useContext(RegistryContext);
|
|
272
270
|
const graph = useMemo(() => createGraph(client, registry), [client, registry]);
|
|
273
|
-
const data =
|
|
271
|
+
const data = useAtomValue(graph.json());
|
|
274
272
|
|
|
275
273
|
return (
|
|
276
274
|
<>
|
|
@@ -290,7 +288,7 @@ export const TreeView: Story = {
|
|
|
290
288
|
|
|
291
289
|
const useItems = useCallback(
|
|
292
290
|
(node?: Node, options?: { disposition?: string; sort?: boolean }) => {
|
|
293
|
-
const connections =
|
|
291
|
+
const connections = useAtomValue(graph.connections(node?.id ?? ROOT_ID));
|
|
294
292
|
return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
|
|
295
293
|
},
|
|
296
294
|
[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,14 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
8
|
-
import { type AnyEchoObject } from '@dxos/echo-schema';
|
|
7
|
+
import { type Database, type Entity } from '@dxos/echo';
|
|
9
8
|
|
|
10
|
-
export const
|
|
11
|
-
|
|
9
|
+
export const atomFromQuery = <T extends Entity.Unknown = Entity.Unknown>(
|
|
10
|
+
query: Database.QueryResult<T>,
|
|
11
|
+
): Atom.Atom<T[]> => {
|
|
12
|
+
return Atom.make((get) => {
|
|
12
13
|
const unsubscribe = query.subscribe((result) => {
|
|
13
14
|
get.setSelf(result.objects);
|
|
14
15
|
});
|
|
@@ -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,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
|
-
}
|