@dxos/echo-solid 0.8.4-main.fcfe5033a5 → 0.9.0

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.fcfe5033a5",
3
+ "version": "0.9.0",
4
4
  "description": "Solid.js integration for ECHO.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -8,7 +8,7 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/dxos/dxos"
10
10
  },
11
- "license": "MIT",
11
+ "license": "FSL-1.1-Apache-2.0",
12
12
  "author": "DXOS.org",
13
13
  "sideEffects": false,
14
14
  "type": "module",
@@ -20,26 +20,22 @@
20
20
  }
21
21
  },
22
22
  "types": "dist/types/src/index.d.ts",
23
- "typesVersions": {
24
- "*": {}
25
- },
26
23
  "files": [
27
24
  "dist",
28
25
  "src"
29
26
  ],
30
27
  "dependencies": {
31
- "@effect-atom/atom": "^0.5.1",
28
+ "@effect-atom/atom": "^0.5.3",
32
29
  "@solid-primitives/utils": "^6.3.2",
33
- "@dxos/echo": "0.8.4-main.fcfe5033a5",
34
- "@dxos/echo-atom": "0.8.4-main.fcfe5033a5",
35
- "@dxos/effect-atom-solid": "0.8.4-main.fcfe5033a5"
30
+ "@dxos/echo": "0.9.0",
31
+ "@dxos/effect-atom-solid": "0.9.0"
36
32
  },
37
33
  "devDependencies": {
38
34
  "@solidjs/testing-library": "^0.8.10",
39
35
  "solid-js": "^1.9.11",
40
- "vite-plugin-solid": "^2.11.10",
41
- "vitest": "3.2.4",
42
- "@dxos/echo-db": "0.8.4-main.fcfe5033a5"
36
+ "vite-plugin-solid": "^2.11.12",
37
+ "vitest": "4.1.7",
38
+ "@dxos/echo-client": "0.9.0"
43
39
  },
44
40
  "peerDependencies": {
45
41
  "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';
@@ -8,7 +8,7 @@ import { describe, expect, test } from 'vitest';
8
8
 
9
9
  import type { Entity } from '@dxos/echo';
10
10
  import { Obj } from '@dxos/echo';
11
- import { createObject } from '@dxos/echo-db';
11
+ import { createObject } from '@dxos/echo-client';
12
12
  import { TestSchema } from '@dxos/echo/testing';
13
13
  import { Registry } from '@dxos/effect-atom-solid';
14
14
  import { RegistryProvider } from '@dxos/effect-atom-solid';
@@ -86,8 +86,8 @@ describe('useObject', () => {
86
86
  expect(result).toBe('Test');
87
87
  expect(getByTestId('value').textContent).toBe('Test');
88
88
 
89
- // Update the property via Obj.change.
90
- Obj.change(obj, (obj) => {
89
+ // Update the property via Obj.update.
90
+ Obj.update(obj, (obj) => {
91
91
  obj.name = 'Updated';
92
92
  });
93
93
 
@@ -118,8 +118,8 @@ describe('useObject', () => {
118
118
  expect(valueAccessor?.()?.name).toBe('Test');
119
119
  expect(getByTestId('name').textContent).toBe('Test');
120
120
 
121
- // Update a property via Obj.change.
122
- Obj.change(obj, (obj) => {
121
+ // Update a property via Obj.update.
122
+ Obj.update(obj, (obj) => {
123
123
  obj.name = 'Updated';
124
124
  });
125
125
 
@@ -149,8 +149,8 @@ describe('useObject', () => {
149
149
 
150
150
  expect(result).toBe('Test');
151
151
 
152
- // Update a different property via Obj.change.
153
- Obj.change(obj, (obj) => {
152
+ // Update a different property via Obj.update.
153
+ Obj.update(obj, (obj) => {
154
154
  obj.email = 'newemail@example.com';
155
155
  });
156
156
 
package/src/useObject.ts CHANGED
@@ -6,7 +6,6 @@ import { type MaybeAccessor, access } from '@solid-primitives/utils';
6
6
  import { type Accessor, createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
7
7
 
8
8
  import { Obj, Ref } from '@dxos/echo';
9
- import { AtomObj } from '@dxos/echo-atom';
10
9
  import { type Registry, useRegistry } from '@dxos/effect-atom-solid';
11
10
 
12
11
  export interface ObjectUpdateCallback<T> {
@@ -146,10 +145,36 @@ export function useObject<T extends Obj.Unknown, K extends keyof T>(
146
145
  // Determine if input is a ref.
147
146
  const isRef = createMemo(() => Ref.isRef(resolvedInput()));
148
147
 
148
+ // Signal to notify when a ref's target loads.
149
+ // Incremented by the ref.atom subscription below so that liveObj re-evaluates.
150
+ const [refLoadVersion, setRefLoadVersion] = createSignal(0, { equals: false });
151
+
152
+ // Subscribe to ref.atom (load-only) to detect when the ref target becomes available.
153
+ // This drives liveObj reactivity for the property-subscription case.
154
+ createEffect(() => {
155
+ const input = resolvedInput();
156
+ if (!input || !Ref.isRef(input)) {
157
+ return;
158
+ }
159
+ const unsubscribe = registry.subscribe(
160
+ input.atom,
161
+ () => {
162
+ setRefLoadVersion((v) => v + 1);
163
+ },
164
+ { immediate: false },
165
+ );
166
+ onCleanup(unsubscribe);
167
+ });
168
+
149
169
  // Get the live object for the callback (refs need to dereference).
170
+ // For refs, depends on refLoadVersion so it re-evaluates when the target loads.
150
171
  const liveObj = createMemo(() => {
151
172
  const input = resolvedInput();
152
- return isRef() ? (input as Ref.Ref<T>)?.target : (input as T | undefined);
173
+ if (!isRef()) {
174
+ return input as T | undefined;
175
+ }
176
+ refLoadVersion();
177
+ return (input as Ref.Ref<T>)?.target;
153
178
  });
154
179
 
155
180
  // Create a stable callback that handles both object and property updates.
@@ -160,7 +185,7 @@ export function useObject<T extends Obj.Unknown, K extends keyof T>(
160
185
  return;
161
186
  }
162
187
 
163
- Obj.change(obj, (obj: any) => {
188
+ Obj.update(obj, (obj: any) => {
164
189
  if (typeof updateOrValue === 'function') {
165
190
  const returnValue = (updateOrValue as (obj: unknown) => unknown)(property !== undefined ? obj[property] : obj);
166
191
  if (returnValue !== undefined) {
@@ -179,16 +204,15 @@ export function useObject<T extends Obj.Unknown, K extends keyof T>(
179
204
  };
180
205
 
181
206
  if (property !== undefined) {
182
- // For property subscriptions on refs, we subscribe to trigger re-render on load.
183
- useObjectValue(registry, objOrRef);
184
207
  return [useObjectProperty(registry, liveObj, property), callback as ObjectPropUpdateCallback<T[K]>];
208
+ } else {
209
+ return [useObjectValue(registry, objOrRef), callback as ObjectUpdateCallback<T>];
185
210
  }
186
- return [useObjectValue(registry, objOrRef), callback as ObjectUpdateCallback<T>];
187
211
  }
188
212
 
189
213
  /**
190
214
  * Internal function for subscribing to an Echo object or Ref.
191
- * AtomObj.make handles both objects and refs, returning snapshots.
215
+ * Obj.atom handles both objects and refs, returning snapshots.
192
216
  */
193
217
  function useObjectValue<T extends Obj.Unknown>(
194
218
  registry: Registry.Registry,
@@ -199,7 +223,9 @@ function useObjectValue<T extends Obj.Unknown>(
199
223
 
200
224
  // Initialize with the current value (if available).
201
225
  const initialInput = resolvedInput();
202
- const initialValue = initialInput ? registry.get(AtomObj.make(initialInput)) : undefined;
226
+ const initialValue = initialInput
227
+ ? registry.get(Ref.isRef(initialInput) ? Obj.atom(initialInput) : Obj.atom(initialInput as T))
228
+ : undefined;
203
229
  const [value, setValue] = createSignal<T | undefined>(initialValue as T | undefined);
204
230
 
205
231
  // Subscribe to atom updates.
@@ -211,7 +237,7 @@ function useObjectValue<T extends Obj.Unknown>(
211
237
  return;
212
238
  }
213
239
 
214
- const atom = AtomObj.make(input);
240
+ const atom = Ref.isRef(input) ? Obj.atom(input) : Obj.atom(input as T);
215
241
  const currentValue = registry.get(atom);
216
242
  setValue(() => currentValue as unknown as T);
217
243
 
@@ -239,7 +265,7 @@ function useObjectProperty<T extends Obj.Unknown, K extends keyof T>(
239
265
  ): Accessor<T[K] | undefined> {
240
266
  // Initialize with the current value (if available).
241
267
  const initialObj = obj();
242
- const initialValue = initialObj ? registry.get(AtomObj.makeProperty(initialObj, property)) : undefined;
268
+ const initialValue = initialObj ? registry.get(Obj.atomProperty(initialObj, property)) : undefined;
243
269
  const [value, setValue] = createSignal<T[K] | undefined>(initialValue);
244
270
 
245
271
  // Subscribe to atom updates.
@@ -251,7 +277,7 @@ function useObjectProperty<T extends Obj.Unknown, K extends keyof T>(
251
277
  return;
252
278
  }
253
279
 
254
- const atom = AtomObj.makeProperty(currentObj, property);
280
+ const atom = Obj.atomProperty(currentObj, property);
255
281
  const currentValue = registry.get(atom);
256
282
  setValue(() => currentValue);
257
283
 
@@ -301,7 +327,7 @@ export const useObjects = <T extends Obj.Unknown>(refs: MaybeAccessor<readonly R
301
327
  const subscribeToTarget = (ref: Ref.Ref<T>) => {
302
328
  const target = ref.target;
303
329
  if (target) {
304
- const key = ref.dxn.toString();
330
+ const key = ref.uri;
305
331
  if (!targetUnsubscribes.has(key)) {
306
332
  targetUnsubscribes.set(key, Obj.subscribe(target, triggerUpdate));
307
333
  }
@@ -7,7 +7,7 @@ import { type JSX, createSignal } from 'solid-js';
7
7
  import { afterEach, beforeEach, describe, expect, test } from 'vitest';
8
8
 
9
9
  import { Filter, Obj, Query } from '@dxos/echo';
10
- import { EchoTestBuilder } from '@dxos/echo-db/testing';
10
+ import { EchoTestBuilder } from '@dxos/echo-client/testing';
11
11
  import { TestSchema } from '@dxos/echo/testing';
12
12
 
13
13
  import { useQuery } from './useQuery';
@@ -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' });
@@ -6,13 +6,13 @@ import { render, waitFor } from '@solidjs/testing-library';
6
6
  import { type JSX, createMemo } from 'solid-js';
7
7
  import { afterEach, beforeEach, describe, expect, test } from 'vitest';
8
8
 
9
- import { Type } from '@dxos/echo';
10
- import { EchoTestBuilder } from '@dxos/echo-db/testing';
9
+ import { DXN, Type, URI } from '@dxos/echo';
10
+ import { EchoTestBuilder } from '@dxos/echo-client/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, DXN.make('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, DXN.make('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.getURI(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
- const typename = 'com.example.type.person';
101
+ const typename = DXN.make('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.getURI(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 typeUri: URI.URI | undefined = Type.getURI(registeredSchema);
176
174
 
177
175
  function TestComponent() {
178
- const schema = useSchema(db, () => typename);
176
+ const schema = useType(db, () => typeUri);
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
- // Change typename
199
- typename = undefined;
196
+ // Change typeUri
197
+ typeUri = undefined;
200
198
 
201
199
  await waitFor(() => {
202
- // Should keep previous value when typename becomes undefined
203
- expect(getByTestId('typename').textContent).toBe(registeredSchema.typename);
200
+ // Should keep previous value when typeUri becomes undefined
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,83 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Accessor, createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
6
+
7
+ import { DXN, EID, URI, 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 its URI from a space: a static schema's typename DXN, or a
13
+ * persisted (database) schema's `echo:` EID (what `Type.getURI` / `getTypeURIFromQuery` produce).
14
+ * Accepts either values or accessors for db and typeUri.
15
+ *
16
+ * Fans across the owning space db (persisted custom types) and the shared
17
+ * registry (static/runtime plugin types). Persisted types live only in the db,
18
+ * so a registry-only lookup misses them.
19
+ *
20
+ * @param db - The database instance (can be reactive)
21
+ * @param typeUri - The type URI to query (can be reactive)
22
+ * @returns An accessor that returns the current type or undefined
23
+ */
24
+ export const useType = (
25
+ db?: MaybeAccessor<Database.Database | undefined>,
26
+ typeUri?: MaybeAccessor<URI.URI | undefined>,
27
+ ): Accessor<Type.AnyEntity | undefined> => {
28
+ // Derive resolved values reactively.
29
+ const resolved = createMemo(() => {
30
+ const resolvedDb = typeof db === 'function' ? db() : db;
31
+ const resolvedTypeUri = typeof typeUri === 'function' ? typeUri() : typeUri;
32
+ if (!resolvedTypeUri || !resolvedDb) {
33
+ return undefined;
34
+ }
35
+ return { db: resolvedDb, typeUri: resolvedTypeUri };
36
+ });
37
+
38
+ // Store the current type in a signal.
39
+ const [type, setType] = createSignal<Type.AnyEntity | undefined>(undefined);
40
+
41
+ // Subscribe to registry changes.
42
+ createEffect(() => {
43
+ const r = resolved();
44
+ if (!r) {
45
+ setType(() => undefined);
46
+ return;
47
+ }
48
+
49
+ const { db: resolvedDb, typeUri: resolvedTypeUri } = r;
50
+
51
+ const searchEid = EID.isEID(resolvedTypeUri) ? EID.tryParse(resolvedTypeUri) : undefined;
52
+ const searchDxn = DXN.isDXN(resolvedTypeUri) ? DXN.tryMake(resolvedTypeUri) : undefined;
53
+ const queryResult = resolvedDb.query(Query.select(Filter.type(Type.Type)).from(Scope.space(), Scope.registry()));
54
+ const update = () =>
55
+ setType(() =>
56
+ queryResult.results.find((type) => {
57
+ const uri = Type.getURI(type);
58
+ if (uri === resolvedTypeUri) {
59
+ return true;
60
+ }
61
+ // EID matching is space-agnostic: echo:/<id> matches echo://<space>/<id>.
62
+ if (searchEid && EID.isEID(uri)) {
63
+ const typeEid = EID.tryParse(uri);
64
+ return typeEid != null && EID.getEntityId(typeEid) === EID.getEntityId(searchEid);
65
+ }
66
+ // DXN matching is version-agnostic so callers may pass an unversioned DXN.
67
+ if (searchDxn && DXN.isDXN(uri)) {
68
+ const typeDxn = DXN.tryMake(uri);
69
+ return typeDxn != null && DXN.getName(typeDxn) === DXN.getName(searchDxn);
70
+ }
71
+ return false;
72
+ }),
73
+ );
74
+
75
+ // Subscribe before reading `.results` — the query requires at least one subscriber.
76
+ const unsubscribe = queryResult.subscribe(update);
77
+ update();
78
+
79
+ onCleanup(unsubscribe);
80
+ });
81
+
82
+ return type;
83
+ };
@@ -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,EACjE,KAAK,aAAa,CAAC,QAAQ,CAAC,QAAQ,GAAG,SAAS,CAAC,EACjD,WAAW,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
- };