@dxos/echo-solid 0.8.4-main.d05539e30a → 0.8.4-main.d9fc60f731
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/neutral/index.mjs +19 -22
- package/dist/lib/neutral/index.mjs.map +4 -4
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/useType.d.ts +18 -0
- package/dist/types/src/useType.d.ts.map +1 -0
- package/dist/types/src/useType.test.d.ts +2 -0
- package/dist/types/src/useType.test.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/index.ts +1 -1
- package/src/useObject.ts +1 -1
- package/src/useQuery.test.tsx +2 -2
- package/src/{useSchema.test.tsx → useType.test.tsx} +25 -27
- package/src/useType.ts +61 -0
- package/dist/types/src/useSchema.d.ts +0 -14
- package/dist/types/src/useSchema.d.ts.map +0 -1
- package/dist/types/src/useSchema.test.d.ts +0 -2
- package/dist/types/src/useSchema.test.d.ts.map +0 -1
- package/src/useSchema.ts +0 -59
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo-solid",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.d9fc60f731",
|
|
4
4
|
"description": "Solid.js integration for ECHO.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -25,18 +25,18 @@
|
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@effect-atom/atom": "^0.5.
|
|
28
|
+
"@effect-atom/atom": "^0.5.3",
|
|
29
29
|
"@solid-primitives/utils": "^6.3.2",
|
|
30
|
-
"@dxos/echo
|
|
31
|
-
"@dxos/echo": "0.8.4-main.
|
|
32
|
-
"@dxos/effect-atom-solid": "0.8.4-main.
|
|
30
|
+
"@dxos/echo": "0.8.4-main.d9fc60f731",
|
|
31
|
+
"@dxos/echo-atom": "0.8.4-main.d9fc60f731",
|
|
32
|
+
"@dxos/effect-atom-solid": "0.8.4-main.d9fc60f731"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@solidjs/testing-library": "^0.8.10",
|
|
36
36
|
"solid-js": "^1.9.11",
|
|
37
37
|
"vite-plugin-solid": "^2.11.12",
|
|
38
|
-
"vitest": "4.1.
|
|
39
|
-
"@dxos/echo-db": "0.8.4-main.
|
|
38
|
+
"vitest": "4.1.7",
|
|
39
|
+
"@dxos/echo-db": "0.8.4-main.d9fc60f731"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"solid-js": "^1.9.11"
|
package/src/index.ts
CHANGED
package/src/useObject.ts
CHANGED
|
@@ -302,7 +302,7 @@ export const useObjects = <T extends Obj.Unknown>(refs: MaybeAccessor<readonly R
|
|
|
302
302
|
const subscribeToTarget = (ref: Ref.Ref<T>) => {
|
|
303
303
|
const target = ref.target;
|
|
304
304
|
if (target) {
|
|
305
|
-
const key = ref.
|
|
305
|
+
const key = ref.uri;
|
|
306
306
|
if (!targetUnsubscribes.has(key)) {
|
|
307
307
|
targetUnsubscribes.set(key, Obj.subscribe(target, triggerUpdate));
|
|
308
308
|
}
|
package/src/useQuery.test.tsx
CHANGED
|
@@ -148,7 +148,7 @@ describe('useQuery', () => {
|
|
|
148
148
|
|
|
149
149
|
test('accepts Filter directly', async () => {
|
|
150
150
|
// Register schema first
|
|
151
|
-
|
|
151
|
+
db.graph.registry.add([TestSchema.Person]);
|
|
152
152
|
|
|
153
153
|
const obj = Obj.make(TestSchema.Person, { name: 'Test', username: 'test', email: 'test@example.com' });
|
|
154
154
|
db.add(obj);
|
|
@@ -233,7 +233,7 @@ describe('useQuery', () => {
|
|
|
233
233
|
|
|
234
234
|
test('accepts reactive query accessor', async () => {
|
|
235
235
|
// Register schema first
|
|
236
|
-
|
|
236
|
+
db.graph.registry.add([TestSchema.Person]);
|
|
237
237
|
|
|
238
238
|
const obj1 = Obj.make(TestSchema.Expando, { name: 'Test1' });
|
|
239
239
|
const obj2 = Obj.make(TestSchema.Person, { name: 'Test2', username: 'test', email: 'test@example.com' });
|
|
@@ -10,9 +10,9 @@ import { Type } from '@dxos/echo';
|
|
|
10
10
|
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
11
11
|
import { TestSchema } from '@dxos/echo/testing';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { useType } from './useType';
|
|
14
14
|
|
|
15
|
-
describe('
|
|
15
|
+
describe('useType', () => {
|
|
16
16
|
let testBuilder: EchoTestBuilder;
|
|
17
17
|
let db: any;
|
|
18
18
|
|
|
@@ -30,7 +30,7 @@ describe('useSchema', () => {
|
|
|
30
30
|
let result: any;
|
|
31
31
|
|
|
32
32
|
render(() => {
|
|
33
|
-
const schema =
|
|
33
|
+
const schema = useType(undefined, 'dxos.test.Person');
|
|
34
34
|
result = schema();
|
|
35
35
|
return (<div>test</div>) as JSX.Element;
|
|
36
36
|
});
|
|
@@ -42,7 +42,7 @@ describe('useSchema', () => {
|
|
|
42
42
|
let result: any;
|
|
43
43
|
|
|
44
44
|
render(() => {
|
|
45
|
-
const schema =
|
|
45
|
+
const schema = useType(db, undefined);
|
|
46
46
|
result = schema();
|
|
47
47
|
return (<div>test</div>) as JSX.Element;
|
|
48
48
|
});
|
|
@@ -54,7 +54,7 @@ describe('useSchema', () => {
|
|
|
54
54
|
let result: any;
|
|
55
55
|
|
|
56
56
|
render(() => {
|
|
57
|
-
const schema =
|
|
57
|
+
const schema = useType(db, 'dxos.test.NonExistent');
|
|
58
58
|
result = schema();
|
|
59
59
|
return (<div>test</div>) as JSX.Element;
|
|
60
60
|
});
|
|
@@ -66,13 +66,13 @@ describe('useSchema', () => {
|
|
|
66
66
|
|
|
67
67
|
test('returns schema when it exists', async () => {
|
|
68
68
|
// Register a schema to the database
|
|
69
|
-
const
|
|
69
|
+
const registeredSchema = await db.addType(TestSchema.Person);
|
|
70
70
|
await db.flush({ indexes: true });
|
|
71
71
|
|
|
72
72
|
let schemaAccessor: (() => any) | undefined;
|
|
73
73
|
|
|
74
74
|
function TestComponent() {
|
|
75
|
-
const schema =
|
|
75
|
+
const schema = useType(db, Type.getTypename(registeredSchema)!);
|
|
76
76
|
schemaAccessor = schema;
|
|
77
77
|
const t = createMemo(() => {
|
|
78
78
|
const s = schema();
|
|
@@ -84,26 +84,24 @@ describe('useSchema', () => {
|
|
|
84
84
|
const { getByTestId } = render(() => <TestComponent />);
|
|
85
85
|
|
|
86
86
|
await waitFor(() => {
|
|
87
|
-
expect(getByTestId('typename').textContent).toBe(registeredSchema
|
|
87
|
+
expect(getByTestId('typename').textContent).toBe(Type.getTypename(registeredSchema)!);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
// Get the actual result from the accessor
|
|
91
91
|
const result = schemaAccessor?.();
|
|
92
92
|
expect(result).toBeDefined();
|
|
93
|
-
expect(result
|
|
93
|
+
expect(Type.getTypename(result!)).toBe(Type.getTypename(registeredSchema)!);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
test.skip('updates when schema is added', async () => {
|
|
97
97
|
// TODO: This test is skipped because runtime schema registry is not reactive to newly registered schemas.
|
|
98
|
-
// The query reads from db.graph.
|
|
99
|
-
// via db.
|
|
100
|
-
// The runtime registry query subscription doesn't fire when schemas are registered.
|
|
101
|
-
// See: packages/core/echo/echo-db/src/proxy-db/runtime-schema-registry.ts:57
|
|
98
|
+
// The query reads from db.graph.registry.types (shared registry), but when a schema is added
|
|
99
|
+
// via db.add(), the runtime registry query subscription doesn't fire reactively.
|
|
102
100
|
let schemaAccessor: (() => any) | undefined;
|
|
103
101
|
const typename = 'com.example.type.person';
|
|
104
102
|
|
|
105
103
|
function TestComponent() {
|
|
106
|
-
const schema =
|
|
104
|
+
const schema = useType(db, typename);
|
|
107
105
|
schemaAccessor = schema;
|
|
108
106
|
const t = createMemo(() => {
|
|
109
107
|
const s = schema();
|
|
@@ -119,29 +117,29 @@ describe('useSchema', () => {
|
|
|
119
117
|
});
|
|
120
118
|
|
|
121
119
|
// Register the schema
|
|
122
|
-
const
|
|
120
|
+
const registeredSchema = await db.addType(TestSchema.Person);
|
|
123
121
|
await db.flush({ indexes: true });
|
|
124
122
|
|
|
125
123
|
// The schema registry query should pick up the new schema
|
|
126
|
-
//
|
|
124
|
+
// db.graph.registry.types should include the newly registered schema
|
|
127
125
|
await waitFor(() => {
|
|
128
|
-
expect(getByTestId('typename').textContent).toBe(registeredSchema
|
|
126
|
+
expect(getByTestId('typename').textContent).toBe(Type.getTypename(registeredSchema)!);
|
|
129
127
|
});
|
|
130
128
|
|
|
131
129
|
// Get the actual result from the accessor
|
|
132
130
|
const result = schemaAccessor?.();
|
|
133
131
|
expect(result).toBeDefined();
|
|
134
|
-
expect(result
|
|
132
|
+
expect(Type.getTypename(result!)).toBe(Type.getTypename(registeredSchema)!);
|
|
135
133
|
});
|
|
136
134
|
|
|
137
135
|
test('accepts reactive database accessor', async () => {
|
|
138
|
-
const
|
|
136
|
+
const registeredSchema = await db.addType(TestSchema.Person);
|
|
139
137
|
|
|
140
138
|
let schemaAccessor: (() => any) | undefined;
|
|
141
139
|
let dbAccessor: any = db;
|
|
142
140
|
|
|
143
141
|
function TestComponent() {
|
|
144
|
-
const schema =
|
|
142
|
+
const schema = useType(() => dbAccessor, Type.getTypename(registeredSchema)!);
|
|
145
143
|
schemaAccessor = schema;
|
|
146
144
|
const t = createMemo(() => {
|
|
147
145
|
const s = schema();
|
|
@@ -153,7 +151,7 @@ describe('useSchema', () => {
|
|
|
153
151
|
const { getByTestId } = render(() => <TestComponent />);
|
|
154
152
|
|
|
155
153
|
await waitFor(() => {
|
|
156
|
-
expect(getByTestId('typename').textContent).toBe(registeredSchema
|
|
154
|
+
expect(getByTestId('typename').textContent).toBe(Type.getTypename(registeredSchema)!);
|
|
157
155
|
});
|
|
158
156
|
|
|
159
157
|
// Get the actual result from the accessor
|
|
@@ -169,13 +167,13 @@ describe('useSchema', () => {
|
|
|
169
167
|
});
|
|
170
168
|
|
|
171
169
|
test('accepts reactive typename accessor', async () => {
|
|
172
|
-
const
|
|
170
|
+
const registeredSchema = await db.addType(TestSchema.Person);
|
|
173
171
|
|
|
174
172
|
let schemaAccessor: (() => any) | undefined;
|
|
175
|
-
let typename: string | undefined = registeredSchema
|
|
173
|
+
let typename: string | undefined = Type.getTypename(registeredSchema)!;
|
|
176
174
|
|
|
177
175
|
function TestComponent() {
|
|
178
|
-
const schema =
|
|
176
|
+
const schema = useType(db, () => typename);
|
|
179
177
|
schemaAccessor = schema;
|
|
180
178
|
const t = createMemo(() => {
|
|
181
179
|
const s = schema();
|
|
@@ -187,20 +185,20 @@ describe('useSchema', () => {
|
|
|
187
185
|
const { getByTestId } = render(() => <TestComponent />);
|
|
188
186
|
|
|
189
187
|
await waitFor(() => {
|
|
190
|
-
expect(getByTestId('typename').textContent).toBe(registeredSchema
|
|
188
|
+
expect(getByTestId('typename').textContent).toBe(Type.getTypename(registeredSchema)!);
|
|
191
189
|
});
|
|
192
190
|
|
|
193
191
|
// Get the actual result from the accessor
|
|
194
192
|
let result = schemaAccessor?.();
|
|
195
193
|
expect(result).toBeDefined();
|
|
196
|
-
expect(result
|
|
194
|
+
expect(Type.getTypename(result!)).toBe(Type.getTypename(registeredSchema)!);
|
|
197
195
|
|
|
198
196
|
// Change typename
|
|
199
197
|
typename = undefined;
|
|
200
198
|
|
|
201
199
|
await waitFor(() => {
|
|
202
200
|
// Should keep previous value when typename becomes undefined
|
|
203
|
-
expect(getByTestId('typename').textContent).toBe(registeredSchema
|
|
201
|
+
expect(getByTestId('typename').textContent).toBe(Type.getTypename(registeredSchema)!);
|
|
204
202
|
});
|
|
205
203
|
result = schemaAccessor?.();
|
|
206
204
|
expect(result).toBeDefined();
|
package/src/useType.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Accessor, createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
|
|
6
|
+
|
|
7
|
+
import { type Database, Filter, Query, Scope, Type } from '@dxos/echo';
|
|
8
|
+
|
|
9
|
+
type MaybeAccessor<T> = T | Accessor<T>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Subscribe to and retrieve a type by typename from a space.
|
|
13
|
+
* Accepts either values or accessors for db and typename.
|
|
14
|
+
*
|
|
15
|
+
* Fans across the owning space db (persisted custom types) and the shared
|
|
16
|
+
* registry (static/runtime plugin types). Persisted types live only in the db,
|
|
17
|
+
* so a registry-only lookup misses them.
|
|
18
|
+
*
|
|
19
|
+
* @param db - The database instance (can be reactive)
|
|
20
|
+
* @param typename - The typename to query (can be reactive)
|
|
21
|
+
* @returns An accessor that returns the current type or undefined
|
|
22
|
+
*/
|
|
23
|
+
export const useType = (
|
|
24
|
+
db?: MaybeAccessor<Database.Database | undefined>,
|
|
25
|
+
typename?: MaybeAccessor<string | undefined>,
|
|
26
|
+
): Accessor<Type.AnyEntity | undefined> => {
|
|
27
|
+
// Derive resolved values reactively.
|
|
28
|
+
const resolved = createMemo(() => {
|
|
29
|
+
const resolvedDb = typeof db === 'function' ? db() : db;
|
|
30
|
+
const resolvedTypename = typeof typename === 'function' ? typename() : typename;
|
|
31
|
+
if (!resolvedTypename || !resolvedDb) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return { db: resolvedDb, typename: resolvedTypename };
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Store the current type in a signal.
|
|
38
|
+
const [type, setType] = createSignal<Type.AnyEntity | undefined>(undefined);
|
|
39
|
+
|
|
40
|
+
// Subscribe to registry changes.
|
|
41
|
+
createEffect(() => {
|
|
42
|
+
const r = resolved();
|
|
43
|
+
if (!r) {
|
|
44
|
+
// Keep previous value during transitions to prevent flickering.
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { db: resolvedDb, typename: resolvedTypename } = r;
|
|
49
|
+
|
|
50
|
+
const queryResult = resolvedDb.query(Query.select(Filter.type(Type.Type)).from(Scope.space(), Scope.registry()));
|
|
51
|
+
const update = () => setType(() => queryResult.results.find((type) => Type.getTypename(type) === resolvedTypename));
|
|
52
|
+
|
|
53
|
+
// Subscribe before reading `.results` — the query requires at least one subscriber.
|
|
54
|
+
const unsubscribe = queryResult.subscribe(update);
|
|
55
|
+
update();
|
|
56
|
+
|
|
57
|
+
onCleanup(unsubscribe);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return type;
|
|
61
|
+
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { type Accessor } from 'solid-js';
|
|
2
|
-
import { type Database, type Type } from '@dxos/echo';
|
|
3
|
-
type MaybeAccessor<T> = T | Accessor<T>;
|
|
4
|
-
/**
|
|
5
|
-
* Subscribe to and retrieve schema changes from a database's schema registry.
|
|
6
|
-
* Accepts either values or accessors for db and typename.
|
|
7
|
-
*
|
|
8
|
-
* @param db - The database instance (can be reactive)
|
|
9
|
-
* @param typename - The schema typename to query (can be reactive)
|
|
10
|
-
* @returns An accessor that returns the current schema or undefined
|
|
11
|
-
*/
|
|
12
|
-
export declare const useSchema: <T extends Type.AnyEntity = Type.AnyEntity>(db?: MaybeAccessor<Database.Database | undefined>, typename?: MaybeAccessor<string | undefined>) => Accessor<T | undefined>;
|
|
13
|
-
export {};
|
|
14
|
-
//# sourceMappingURL=useSchema.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useSchema.d.ts","sourceRoot":"","sources":["../../../src/useSchema.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,QAAQ,EAAqD,MAAM,UAAU,CAAC;AAE5F,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAEtD,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,OAC5D,aAAa,CAAC,QAAQ,CAAC,QAAQ,GAAG,SAAS,CAAC,aACtC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC,KAC3C,QAAQ,CAAC,CAAC,GAAG,SAAS,CAqCxB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useSchema.test.d.ts","sourceRoot":"","sources":["../../../src/useSchema.test.tsx"],"names":[],"mappings":""}
|
package/src/useSchema.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { type Accessor, createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
|
|
6
|
-
|
|
7
|
-
import { type Database, type Type } from '@dxos/echo';
|
|
8
|
-
|
|
9
|
-
type MaybeAccessor<T> = T | Accessor<T>;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Subscribe to and retrieve schema changes from a database's schema registry.
|
|
13
|
-
* Accepts either values or accessors for db and typename.
|
|
14
|
-
*
|
|
15
|
-
* @param db - The database instance (can be reactive)
|
|
16
|
-
* @param typename - The schema typename to query (can be reactive)
|
|
17
|
-
* @returns An accessor that returns the current schema or undefined
|
|
18
|
-
*/
|
|
19
|
-
export const useSchema = <T extends Type.AnyEntity = Type.AnyEntity>(
|
|
20
|
-
db?: MaybeAccessor<Database.Database | undefined>,
|
|
21
|
-
typename?: MaybeAccessor<string | undefined>,
|
|
22
|
-
): Accessor<T | undefined> => {
|
|
23
|
-
// Derive the schema query reactively
|
|
24
|
-
const query = createMemo(() => {
|
|
25
|
-
const resolvedDb = typeof db === 'function' ? db() : db;
|
|
26
|
-
const resolvedTypename = typeof typename === 'function' ? typename() : typename;
|
|
27
|
-
if (!resolvedTypename || !resolvedDb) {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
return resolvedDb.schemaRegistry.query({ typename: resolvedTypename, location: ['database', 'runtime'] });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
// Store the current schema in a signal
|
|
34
|
-
const [schema, setSchema] = createSignal<T | undefined>(undefined);
|
|
35
|
-
|
|
36
|
-
// Subscribe to query changes
|
|
37
|
-
createEffect(() => {
|
|
38
|
-
const q = query();
|
|
39
|
-
if (!q) {
|
|
40
|
-
// Keep previous value during transitions to prevent flickering
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Subscribe to updates with immediate fire to get initial result and track changes
|
|
45
|
-
// The subscription will automatically start the reactive query
|
|
46
|
-
const unsubscribe = q.subscribe(
|
|
47
|
-
() => {
|
|
48
|
-
// Access results inside the callback to ensure query is running
|
|
49
|
-
const results = q.results;
|
|
50
|
-
setSchema(() => results[0] as T | undefined);
|
|
51
|
-
},
|
|
52
|
-
{ fire: true },
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
onCleanup(unsubscribe);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
return schema;
|
|
59
|
-
};
|