@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/echo-solid",
3
- "version": "0.8.4-main.d05539e30a",
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.1",
28
+ "@effect-atom/atom": "^0.5.3",
29
29
  "@solid-primitives/utils": "^6.3.2",
30
- "@dxos/echo-atom": "0.8.4-main.d05539e30a",
31
- "@dxos/echo": "0.8.4-main.d05539e30a",
32
- "@dxos/effect-atom-solid": "0.8.4-main.d05539e30a"
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.6",
39
- "@dxos/echo-db": "0.8.4-main.d05539e30a"
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
@@ -4,4 +4,4 @@
4
4
 
5
5
  export * from './useObject';
6
6
  export * from './useQuery';
7
- export * from './useSchema';
7
+ export * from './useType';
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.dxn.toString();
305
+ const key = ref.uri;
306
306
  if (!targetUnsubscribes.has(key)) {
307
307
  targetUnsubscribes.set(key, Obj.subscribe(target, triggerUpdate));
308
308
  }
@@ -148,7 +148,7 @@ describe('useQuery', () => {
148
148
 
149
149
  test('accepts Filter directly', async () => {
150
150
  // Register schema first
151
- await db.graph.schemaRegistry.register([TestSchema.Person]);
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
- await db.graph.schemaRegistry.register([TestSchema.Person]);
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 { useSchema } from './useSchema';
13
+ import { useType } from './useType';
14
14
 
15
- describe('useSchema', () => {
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 = useSchema(undefined, 'dxos.test.Person');
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 = useSchema(db, undefined);
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 = useSchema(db, 'dxos.test.NonExistent');
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 [registeredSchema] = await db.schemaRegistry.register([TestSchema.Person]);
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 = useSchema(db, registeredSchema.typename);
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.typename);
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?.typename).toBe(registeredSchema.typename);
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.schemaRegistry.schemas (runtime registry), but when a schema is registered
99
- // via db.schemaRegistry.register(), it's added to the database registry, not the runtime registry.
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 = useSchema(db, typename);
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 [registeredSchema] = await db.schemaRegistry.register([TestSchema.Person]);
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
- // It reads from db.graph.schemaRegistry.schemas which should include the newly registered schema
124
+ // db.graph.registry.types should include the newly registered schema
127
125
  await waitFor(() => {
128
- expect(getByTestId('typename').textContent).toBe(registeredSchema.typename);
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?.typename).toBe(registeredSchema.typename);
132
+ expect(Type.getTypename(result!)).toBe(Type.getTypename(registeredSchema)!);
135
133
  });
136
134
 
137
135
  test('accepts reactive database accessor', async () => {
138
- const [registeredSchema] = await db.schemaRegistry.register([TestSchema.Person]);
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 = useSchema(() => dbAccessor, registeredSchema.typename);
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.typename);
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 [registeredSchema] = await db.schemaRegistry.register([TestSchema.Person]);
170
+ const registeredSchema = await db.addType(TestSchema.Person);
173
171
 
174
172
  let schemaAccessor: (() => any) | undefined;
175
- let typename: string | undefined = registeredSchema.typename;
173
+ let typename: string | undefined = Type.getTypename(registeredSchema)!;
176
174
 
177
175
  function TestComponent() {
178
- const schema = useSchema(db, () => typename);
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.typename);
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?.typename).toBe(registeredSchema.typename);
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.typename);
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,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=useSchema.test.d.ts.map
@@ -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
- };