@dxos/echo-solid 0.0.0 → 0.8.4-main.1c7ec43d41

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,9 +1,13 @@
1
1
  {
2
2
  "name": "@dxos/echo-solid",
3
- "version": "0.0.0",
3
+ "version": "0.8.4-main.1c7ec43d41",
4
4
  "description": "Solid.js integration for ECHO.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "DXOS.org",
9
13
  "sideEffects": false,
@@ -12,33 +16,30 @@
12
16
  ".": {
13
17
  "source": "./src/index.ts",
14
18
  "types": "./dist/types/src/index.d.ts",
15
- "browser": "./dist/lib/browser/index.mjs",
16
- "node": "./dist/lib/node-esm/index.mjs"
19
+ "default": "./dist/lib/neutral/index.mjs"
17
20
  }
18
21
  },
19
22
  "types": "dist/types/src/index.d.ts",
20
- "typesVersions": {
21
- "*": {}
22
- },
23
23
  "files": [
24
24
  "dist",
25
25
  "src"
26
26
  ],
27
27
  "dependencies": {
28
+ "@effect-atom/atom": "^0.5.1",
28
29
  "@solid-primitives/utils": "^6.3.2",
29
- "@dxos/effect-atom-solid": "0.0.0",
30
- "@dxos/echo-atom": "0.0.0",
31
- "@dxos/echo": "0.8.3"
30
+ "@dxos/echo": "0.8.4-main.1c7ec43d41",
31
+ "@dxos/echo-atom": "0.8.4-main.1c7ec43d41",
32
+ "@dxos/effect-atom-solid": "0.8.4-main.1c7ec43d41"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@solidjs/testing-library": "^0.8.10",
35
- "solid-js": "^1.9.9",
36
- "vite-plugin-solid": "^2.11.10",
37
- "vitest": "3.2.4",
38
- "@dxos/echo-db": "0.8.3"
36
+ "solid-js": "^1.9.11",
37
+ "vite-plugin-solid": "^2.11.12",
38
+ "vitest": "4.1.5",
39
+ "@dxos/echo-db": "0.8.4-main.1c7ec43d41"
39
40
  },
40
41
  "peerDependencies": {
41
- "solid-js": "^1.9.9"
42
+ "solid-js": "^1.9.11"
42
43
  },
43
44
  "publishConfig": {
44
45
  "access": "public"
package/src/index.ts CHANGED
@@ -4,5 +4,4 @@
4
4
 
5
5
  export * from './useObject';
6
6
  export * from './useQuery';
7
- export * from './useRef';
8
7
  export * from './useSchema';
@@ -8,12 +8,12 @@ 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 { TestSchema } from '@dxos/echo/testing';
12
11
  import { createObject } from '@dxos/echo-db';
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';
15
15
 
16
- import { useObject } from './useObject';
16
+ import { type ObjectUpdateCallback, useObject } from './useObject';
17
17
 
18
18
  const createWrapper = (registry: Registry.Registry) => {
19
19
  return (props: { children: JSX.Element }) => (
@@ -86,9 +86,9 @@ 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, (o) => {
91
- o.name = 'Updated';
89
+ // Update the property via Obj.update.
90
+ Obj.update(obj, (obj) => {
91
+ obj.name = 'Updated';
92
92
  });
93
93
 
94
94
  // Wait for reactivity to update.
@@ -118,9 +118,9 @@ 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, (o) => {
123
- o.name = 'Updated';
121
+ // Update a property via Obj.update.
122
+ Obj.update(obj, (obj) => {
123
+ obj.name = 'Updated';
124
124
  });
125
125
 
126
126
  // Wait for reactivity to update.
@@ -149,9 +149,9 @@ describe('useObject', () => {
149
149
 
150
150
  expect(result).toBe('Test');
151
151
 
152
- // Update a different property via Obj.change.
153
- Obj.change(obj, (o) => {
154
- o.email = 'newemail@example.com';
152
+ // Update a different property via Obj.update.
153
+ Obj.update(obj, (obj) => {
154
+ obj.email = 'newemail@example.com';
155
155
  });
156
156
 
157
157
  // Name should still be 'Test'.
@@ -334,7 +334,7 @@ describe('useObject', () => {
334
334
  const registry = Registry.make();
335
335
  const Wrapper = createWrapper(registry);
336
336
 
337
- let updatePerson: ((updater: (p: Entity.Entity<TestSchema.Person>) => void) => void) | undefined;
337
+ let updatePerson: ObjectUpdateCallback<TestSchema.Person> | undefined;
338
338
  const { getByTestId } = render(
339
339
  () => {
340
340
  const [value, update] = useObject(obj);
package/src/useObject.ts CHANGED
@@ -5,16 +5,18 @@
5
5
  import { type MaybeAccessor, access } from '@solid-primitives/utils';
6
6
  import { type Accessor, createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
7
7
 
8
- import { type Entity, Obj } from '@dxos/echo';
8
+ import { Obj, Ref } from '@dxos/echo';
9
9
  import { AtomObj } from '@dxos/echo-atom';
10
10
  import { type Registry, useRegistry } from '@dxos/effect-atom-solid';
11
11
 
12
12
  export interface ObjectUpdateCallback<T> {
13
- (update: (obj: T) => void): void;
14
- (update: (obj: T) => T): void;
13
+ (update: (obj: Obj.Mutable<T>) => void): void;
14
+ (update: (obj: Obj.Mutable<T>) => Obj.Mutable<T>): void;
15
15
  }
16
16
 
17
- export interface ObjectPropUpdateCallback<T> extends ObjectUpdateCallback<T> {
17
+ export interface ObjectPropUpdateCallback<T> {
18
+ (update: (value: Obj.Mutable<T>) => void): void;
19
+ (update: (value: Obj.Mutable<T>) => Obj.Mutable<T>): void;
18
20
  (newValue: T): void;
19
21
  }
20
22
 
@@ -29,17 +31,25 @@ type ConditionalUndefined<T, R> = [T] extends [Exclude<T, undefined>]
29
31
  : R; // Both T and R include undefined, return R as-is (no double undefined)
30
32
 
31
33
  /**
32
- * Subscribe to a specific property of an Echo object.
33
- * Returns the current property value accessor and an update callback.
34
+ * Subscribe to a Ref's target object.
35
+ * Automatically dereferences the ref and handles async loading.
36
+ * Returns undefined if the ref hasn't loaded yet.
34
37
  *
35
- * @param obj - The Echo object to subscribe to (can be reactive)
36
- * @param property - Property key to subscribe to
38
+ * @param ref - The Ref to dereference and subscribe to (can be reactive)
37
39
  * @returns A tuple of [accessor, updateCallback]
38
40
  */
39
- export function useObject<T extends Entity.Unknown | undefined, K extends keyof Exclude<T, undefined>>(
40
- obj: MaybeAccessor<T>,
41
- property: K,
42
- ): [Accessor<ConditionalUndefined<T, Exclude<T, undefined>[K]>>, ObjectPropUpdateCallback<Exclude<T, undefined>[K]>];
41
+ export function useObject<T>(ref: MaybeAccessor<Ref.Ref<T>>): [Accessor<T | undefined>, ObjectUpdateCallback<T>];
42
+
43
+ /**
44
+ * Subscribe to a Ref's target object that may be undefined.
45
+ * Returns undefined if the ref is undefined or hasn't loaded yet.
46
+ *
47
+ * @param ref - The Ref to dereference and subscribe to (can be undefined/reactive)
48
+ * @returns A tuple of [accessor, updateCallback]
49
+ */
50
+ export function useObject<T>(
51
+ ref: MaybeAccessor<Ref.Ref<T> | undefined>,
52
+ ): [Accessor<T | undefined>, ObjectUpdateCallback<T>];
43
53
 
44
54
  /**
45
55
  * Subscribe to an entire Echo object.
@@ -48,108 +58,168 @@ export function useObject<T extends Entity.Unknown | undefined, K extends keyof
48
58
  * @param obj - The Echo object to subscribe to (can be reactive)
49
59
  * @returns A tuple of [accessor, updateCallback]
50
60
  */
51
- export function useObject<T extends Entity.Unknown>(
61
+ export function useObject<T extends Obj.Unknown>(
52
62
  obj: MaybeAccessor<T>,
53
63
  ): [Accessor<ConditionalUndefined<T, T>>, ObjectUpdateCallback<T>];
54
- export function useObject<T extends Entity.Unknown | undefined>(
64
+
65
+ /**
66
+ * Subscribe to an entire Echo object that may be undefined.
67
+ * Returns undefined if the object is undefined.
68
+ *
69
+ * @param obj - The Echo object to subscribe to (can be undefined/reactive)
70
+ * @returns A tuple of [accessor, updateCallback]
71
+ */
72
+ export function useObject<T extends Obj.Unknown>(
73
+ obj: MaybeAccessor<T | undefined>,
74
+ ): [Accessor<ConditionalUndefined<T, T>>, ObjectUpdateCallback<T>];
75
+
76
+ /**
77
+ * Subscribe to a specific property of an Echo object.
78
+ * Returns the current property value accessor and an update callback.
79
+ *
80
+ * @param obj - The Echo object to subscribe to (can be reactive)
81
+ * @param property - Property key to subscribe to
82
+ * @returns A tuple of [accessor, updateCallback]
83
+ */
84
+ export function useObject<T extends Obj.Unknown, K extends keyof T>(
55
85
  obj: MaybeAccessor<T>,
56
- ): [Accessor<ConditionalUndefined<T, Exclude<T, undefined>>>, ObjectUpdateCallback<Exclude<T, undefined>>];
86
+ property: K,
87
+ ): [Accessor<T[K]>, ObjectPropUpdateCallback<T[K]>];
88
+
89
+ /**
90
+ * Subscribe to a specific property of an Echo object that may be undefined.
91
+ * Returns undefined if the object is undefined.
92
+ *
93
+ * @param obj - The Echo object to subscribe to (can be undefined/reactive)
94
+ * @param property - Property key to subscribe to
95
+ * @returns A tuple of [accessor, updateCallback]
96
+ */
97
+ export function useObject<T extends Obj.Unknown, K extends keyof T>(
98
+ obj: MaybeAccessor<T | undefined>,
99
+ property: K,
100
+ ): [Accessor<T[K] | undefined>, ObjectPropUpdateCallback<T[K]>];
101
+
102
+ /**
103
+ * Subscribe to a specific property of a Ref's target object.
104
+ * Automatically dereferences the ref and handles async loading.
105
+ * Returns undefined if the ref hasn't loaded yet.
106
+ *
107
+ * @param ref - The Ref to dereference and subscribe to (can be reactive)
108
+ * @param property - Property key to subscribe to
109
+ * @returns A tuple of [accessor, updateCallback]
110
+ */
111
+ export function useObject<T, K extends keyof T>(
112
+ ref: MaybeAccessor<Ref.Ref<T>>,
113
+ property: K,
114
+ ): [Accessor<T[K] | undefined>, ObjectPropUpdateCallback<T[K]>];
115
+
116
+ /**
117
+ * Subscribe to a specific property of a Ref's target object that may be undefined.
118
+ * Returns undefined if the ref is undefined or hasn't loaded yet.
119
+ *
120
+ * @param ref - The Ref to dereference and subscribe to (can be undefined/reactive)
121
+ * @param property - Property key to subscribe to
122
+ * @returns A tuple of [accessor, updateCallback]
123
+ */
124
+ export function useObject<T, K extends keyof T>(
125
+ ref: MaybeAccessor<Ref.Ref<T> | undefined>,
126
+ property: K,
127
+ ): [Accessor<T[K] | undefined>, ObjectPropUpdateCallback<T[K]>];
57
128
 
58
129
  /**
59
- * Subscribe to an Echo object (entire object or specific property).
130
+ * Subscribe to an Echo object or Ref (entire object or specific property).
60
131
  * Returns the current value accessor and an update callback.
61
132
  *
62
- * @param obj - The Echo object to subscribe to (can be reactive)
133
+ * @param objOrRef - The Echo object or Ref to subscribe to (can be reactive)
63
134
  * @param property - Optional property key to subscribe to a specific property
64
135
  * @returns A tuple of [accessor, updateCallback]
65
136
  */
66
- export function useObject<T extends Entity.Unknown | undefined, K extends keyof Exclude<T, undefined>>(
67
- obj: MaybeAccessor<T>,
68
- property?: K,
69
- ):
70
- | [Accessor<ConditionalUndefined<T, Exclude<T, undefined>>>, ObjectUpdateCallback<Exclude<T, undefined>>]
71
- | [Accessor<ConditionalUndefined<T, Exclude<T, undefined>[K]>>, ObjectPropUpdateCallback<Exclude<T, undefined>[K]>];
72
- export function useObject<T extends Entity.Unknown, K extends keyof T>(
73
- obj: MaybeAccessor<T>,
74
- property?: K,
75
- ):
76
- | [Accessor<ConditionalUndefined<T, T>>, ObjectUpdateCallback<T>]
77
- | [Accessor<ConditionalUndefined<T, T[K]>>, ObjectPropUpdateCallback<T[K]>];
78
- export function useObject<T extends Entity.Unknown | undefined, K extends keyof Exclude<T, undefined>>(
79
- obj: MaybeAccessor<T>,
137
+ export function useObject<T extends Obj.Unknown, K extends keyof T>(
138
+ objOrRef: MaybeAccessor<T | Ref.Ref<T> | undefined>,
80
139
  property?: K,
81
- ):
82
- | [Accessor<ConditionalUndefined<T, Exclude<T, undefined>>>, ObjectUpdateCallback<Exclude<T, undefined>>]
83
- | [Accessor<ConditionalUndefined<T, Exclude<T, undefined>[K]>>, ObjectPropUpdateCallback<Exclude<T, undefined>[K]>] {
140
+ ): [Accessor<any>, ObjectUpdateCallback<T> | ObjectPropUpdateCallback<T[K]>] {
84
141
  const registry = useRegistry();
85
142
 
86
- // Memoize the resolved object to track changes.
87
- const resolvedObj = createMemo(() => access(obj));
143
+ // Memoize the resolved input to track changes.
144
+ const resolvedInput = createMemo(() => access(objOrRef));
145
+
146
+ // Determine if input is a ref.
147
+ const isRef = createMemo(() => Ref.isRef(resolvedInput()));
148
+
149
+ // Get the live object for the callback (refs need to dereference).
150
+ const liveObj = createMemo(() => {
151
+ const input = resolvedInput();
152
+ return isRef() ? (input as Ref.Ref<T>)?.target : (input as T | undefined);
153
+ });
88
154
 
89
155
  // Create a stable callback that handles both object and property updates.
90
156
  const callback = (updateOrValue: unknown | ((obj: unknown) => unknown)) => {
91
- const currentObj = resolvedObj();
92
- if (!currentObj) {
157
+ // Get current target for refs (may have loaded since render).
158
+ const obj = isRef() ? (resolvedInput() as Ref.Ref<T>)?.target : liveObj();
159
+ if (!obj) {
93
160
  return;
94
161
  }
95
162
 
96
- Obj.change(currentObj, (o: any) => {
163
+ Obj.update(obj, (obj: any) => {
97
164
  if (typeof updateOrValue === 'function') {
98
- const returnValue = (updateOrValue as (obj: unknown) => unknown)(property !== undefined ? o[property] : o);
165
+ const returnValue = (updateOrValue as (obj: unknown) => unknown)(property !== undefined ? obj[property] : obj);
99
166
  if (returnValue !== undefined) {
100
167
  if (property === undefined) {
101
168
  throw new Error('Cannot re-assign the entire object');
102
169
  }
103
- o[property] = returnValue;
170
+ obj[property] = returnValue;
104
171
  }
105
172
  } else {
106
173
  if (property === undefined) {
107
174
  throw new Error('Cannot re-assign the entire object');
108
175
  }
109
- o[property] = updateOrValue;
176
+ obj[property] = updateOrValue;
110
177
  }
111
178
  });
112
179
  };
113
180
 
114
181
  if (property !== undefined) {
115
- return [useObjectProperty(registry, obj, property), callback as ObjectPropUpdateCallback<Exclude<T, undefined>[K]>];
182
+ // For property subscriptions on refs, we subscribe to trigger re-render on load.
183
+ useObjectValue(registry, objOrRef);
184
+ return [useObjectProperty(registry, liveObj, property), callback as ObjectPropUpdateCallback<T[K]>];
185
+ } else {
186
+ return [useObjectValue(registry, objOrRef), callback as ObjectUpdateCallback<T>];
116
187
  }
117
- return [useObjectValue(registry, obj), callback as ObjectUpdateCallback<Exclude<T, undefined>>];
118
188
  }
119
189
 
120
190
  /**
121
- * Internal function for subscribing to an entire Echo object.
122
- * Uses snapshots from AtomObj.make() which return new object references on each change.
191
+ * Internal function for subscribing to an Echo object or Ref.
192
+ * AtomObj.make handles both objects and refs, returning snapshots.
123
193
  */
124
- function useObjectValue<T extends Entity.Unknown | undefined>(
194
+ function useObjectValue<T extends Obj.Unknown>(
125
195
  registry: Registry.Registry,
126
- obj: MaybeAccessor<T>,
127
- ): Accessor<ConditionalUndefined<T, Exclude<T, undefined>>> {
128
- // Memoize the resolved object to track changes.
129
- const resolvedObj = createMemo(() => access(obj));
196
+ objOrRef: MaybeAccessor<T | Ref.Ref<T> | undefined>,
197
+ ): Accessor<T | undefined> {
198
+ // Memoize the resolved input to track changes.
199
+ const resolvedInput = createMemo(() => access(objOrRef));
130
200
 
131
- // Initialize with snapshot of the current object (if available).
132
- const initialObj = resolvedObj();
133
- const initialSnapshot = initialObj ? (Obj.getSnapshot(initialObj as Obj.Any) as T) : undefined;
134
- const [value, setValue] = createSignal<T | undefined>(initialSnapshot);
201
+ // Initialize with the current value (if available).
202
+ const initialInput = resolvedInput();
203
+ const initialValue = initialInput ? registry.get(AtomObj.make(initialInput)) : undefined;
204
+ const [value, setValue] = createSignal<T | undefined>(initialValue as T | undefined);
135
205
 
136
206
  // Subscribe to atom updates.
137
207
  createEffect(() => {
138
- const currentObj = resolvedObj();
208
+ const input = resolvedInput();
139
209
 
140
- if (!currentObj) {
210
+ if (!input) {
141
211
  setValue(() => undefined);
142
212
  return;
143
213
  }
144
214
 
145
- const atom = AtomObj.make(currentObj);
215
+ const atom = AtomObj.make(input);
146
216
  const currentValue = registry.get(atom);
147
- setValue(() => currentValue as T);
217
+ setValue(() => currentValue as unknown as T);
148
218
 
149
219
  const unsubscribe = registry.subscribe(
150
220
  atom,
151
221
  () => {
152
- setValue(() => registry.get(atom) as T);
222
+ setValue(() => registry.get(atom) as unknown as T);
153
223
  },
154
224
  { immediate: true },
155
225
  );
@@ -157,43 +227,39 @@ function useObjectValue<T extends Entity.Unknown | undefined>(
157
227
  onCleanup(unsubscribe);
158
228
  });
159
229
 
160
- return value as Accessor<ConditionalUndefined<T, Exclude<T, undefined>>>;
230
+ return value;
161
231
  }
162
232
 
163
233
  /**
164
234
  * Internal function for subscribing to a specific property of an Echo object.
165
235
  */
166
- function useObjectProperty<T extends Entity.Unknown | undefined, K extends keyof Exclude<T, undefined>>(
236
+ function useObjectProperty<T extends Obj.Unknown, K extends keyof T>(
167
237
  registry: Registry.Registry,
168
- obj: MaybeAccessor<T>,
238
+ obj: Accessor<T | undefined>,
169
239
  property: K,
170
- ): Accessor<ConditionalUndefined<T, Exclude<T, undefined>[K]>> {
171
- // Memoize the resolved object to track changes.
172
- const resolvedObj = createMemo(() => access(obj));
173
-
174
- type NonUndefinedT = Exclude<T, undefined>;
175
- const initialValue = resolvedObj() ? (resolvedObj() as NonUndefinedT)[property] : undefined;
176
- const [value, setValue] = createSignal<NonUndefinedT[K] | undefined>(initialValue);
240
+ ): Accessor<T[K] | undefined> {
241
+ // Initialize with the current value (if available).
242
+ const initialObj = obj();
243
+ const initialValue = initialObj ? registry.get(AtomObj.makeProperty(initialObj, property)) : undefined;
244
+ const [value, setValue] = createSignal<T[K] | undefined>(initialValue);
177
245
 
178
246
  // Subscribe to atom updates.
179
247
  createEffect(() => {
180
- const currentObj = resolvedObj();
248
+ const currentObj = obj();
181
249
 
182
250
  if (!currentObj) {
183
251
  setValue(() => undefined);
184
252
  return;
185
253
  }
186
254
 
187
- type NonUndefinedT = Exclude<T, undefined>;
188
- const echoObj = currentObj as NonUndefinedT;
189
- const atom = AtomObj.makeProperty(echoObj, property);
255
+ const atom = AtomObj.makeProperty(currentObj, property);
190
256
  const currentValue = registry.get(atom);
191
257
  setValue(() => currentValue);
192
258
 
193
259
  const unsubscribe = registry.subscribe(
194
260
  atom,
195
261
  () => {
196
- setValue(() => registry.get(atom) as NonUndefinedT[K]);
262
+ setValue(() => registry.get(atom) as T[K]);
197
263
  },
198
264
  { immediate: true },
199
265
  );
@@ -201,5 +267,85 @@ function useObjectProperty<T extends Entity.Unknown | undefined, K extends keyof
201
267
  onCleanup(unsubscribe);
202
268
  });
203
269
 
204
- return value as Accessor<ConditionalUndefined<T, Exclude<T, undefined>[K]>>;
270
+ return value;
205
271
  }
272
+
273
+ /**
274
+ * Subscribe to multiple Refs' target objects.
275
+ * Automatically dereferences each ref and handles async loading.
276
+ * Returns an accessor to an array of loaded snapshots (filtering out undefined values).
277
+ *
278
+ * This hook is useful for aggregate computations like counts or filtering
279
+ * across multiple refs without using .target directly.
280
+ *
281
+ * @param refs - Array of Refs to dereference and subscribe to (can be reactive)
282
+ * @returns Accessor to array of loaded target snapshots (excludes unloaded refs)
283
+ */
284
+ export const useObjects = <T extends Obj.Unknown>(refs: MaybeAccessor<readonly Ref.Ref<T>[]>): Accessor<T[]> => {
285
+ // Track version to trigger re-renders when any ref or target changes.
286
+ const [version, setVersion] = createSignal(0);
287
+
288
+ // Memoize the refs array to track changes.
289
+ const resolvedRefs = createMemo(() => access(refs));
290
+
291
+ // Subscribe to all refs and their targets.
292
+ createEffect(() => {
293
+ const currentRefs = resolvedRefs();
294
+ const targetUnsubscribes = new Map<string, () => void>();
295
+
296
+ // Function to trigger re-render.
297
+ const triggerUpdate = () => {
298
+ setVersion((v) => v + 1);
299
+ };
300
+
301
+ // Function to set up subscription for a target.
302
+ const subscribeToTarget = (ref: Ref.Ref<T>) => {
303
+ const target = ref.target;
304
+ if (target) {
305
+ const key = ref.dxn.toString();
306
+ if (!targetUnsubscribes.has(key)) {
307
+ targetUnsubscribes.set(key, Obj.subscribe(target, triggerUpdate));
308
+ }
309
+ }
310
+ };
311
+
312
+ // Try to load all refs and subscribe to targets.
313
+ for (const ref of currentRefs) {
314
+ // Subscribe to existing target if available.
315
+ subscribeToTarget(ref);
316
+
317
+ // Trigger async load if not already loaded.
318
+ if (!ref.target) {
319
+ void ref
320
+ .load()
321
+ .then(() => {
322
+ subscribeToTarget(ref);
323
+ triggerUpdate();
324
+ })
325
+ .catch(() => {
326
+ // Ignore load errors.
327
+ });
328
+ }
329
+ }
330
+
331
+ onCleanup(() => {
332
+ targetUnsubscribes.forEach((u) => u());
333
+ });
334
+ });
335
+
336
+ // Compute current snapshots by reading each ref's target.
337
+ return createMemo(() => {
338
+ // Depend on version to re-compute when targets change.
339
+ version();
340
+
341
+ const currentRefs = resolvedRefs();
342
+ const snapshots: T[] = [];
343
+ for (const ref of currentRefs) {
344
+ const target = ref.target;
345
+ if (target !== undefined) {
346
+ snapshots.push(target as T);
347
+ }
348
+ }
349
+ return snapshots;
350
+ });
351
+ };