@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/dist/lib/neutral/index.mjs +210 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/useObject.d.ts +101 -0
- package/dist/types/src/useObject.d.ts.map +1 -0
- package/dist/types/src/useObject.test.d.ts +2 -0
- package/dist/types/src/useObject.test.d.ts.map +1 -0
- package/dist/types/src/useQuery.d.ts +14 -0
- package/dist/types/src/useQuery.d.ts.map +1 -0
- package/dist/types/src/useQuery.test.d.ts +2 -0
- package/dist/types/src/useQuery.test.d.ts.map +1 -0
- package/dist/types/src/useSchema.d.ts +14 -0
- package/dist/types/src/useSchema.d.ts.map +1 -0
- package/dist/types/src/useSchema.test.d.ts +2 -0
- package/dist/types/src/useSchema.test.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +15 -14
- package/src/index.ts +0 -1
- package/src/useObject.test.tsx +12 -12
- package/src/useObject.ts +222 -76
- package/src/useQuery.test.tsx +18 -18
- package/src/useSchema.test.tsx +2 -2
- package/src/useSchema.ts +1 -1
- package/src/useRef.test.tsx +0 -247
- package/src/useRef.ts +0 -48
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo-solid",
|
|
3
|
-
"version": "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
|
-
"
|
|
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/
|
|
30
|
-
"@dxos/echo-atom": "0.
|
|
31
|
-
"@dxos/
|
|
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.
|
|
36
|
-
"vite-plugin-solid": "^2.11.
|
|
37
|
-
"vitest": "
|
|
38
|
-
"@dxos/echo-db": "0.8.
|
|
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.
|
|
42
|
+
"solid-js": "^1.9.11"
|
|
42
43
|
},
|
|
43
44
|
"publishConfig": {
|
|
44
45
|
"access": "public"
|
package/src/index.ts
CHANGED
package/src/useObject.test.tsx
CHANGED
|
@@ -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.
|
|
90
|
-
Obj.
|
|
91
|
-
|
|
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.
|
|
122
|
-
Obj.
|
|
123
|
-
|
|
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.
|
|
153
|
-
Obj.
|
|
154
|
-
|
|
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:
|
|
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 {
|
|
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>
|
|
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
|
|
33
|
-
*
|
|
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
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
61
|
+
export function useObject<T extends Obj.Unknown>(
|
|
52
62
|
obj: MaybeAccessor<T>,
|
|
53
63
|
): [Accessor<ConditionalUndefined<T, T>>, ObjectUpdateCallback<T>];
|
|
54
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
67
|
-
|
|
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
|
|
87
|
-
const
|
|
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
|
-
|
|
92
|
-
|
|
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.
|
|
163
|
+
Obj.update(obj, (obj: any) => {
|
|
97
164
|
if (typeof updateOrValue === 'function') {
|
|
98
|
-
const returnValue = (updateOrValue as (obj: unknown) => unknown)(property !== undefined ?
|
|
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
|
-
|
|
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
|
-
|
|
176
|
+
obj[property] = updateOrValue;
|
|
110
177
|
}
|
|
111
178
|
});
|
|
112
179
|
};
|
|
113
180
|
|
|
114
181
|
if (property !== undefined) {
|
|
115
|
-
|
|
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
|
|
122
|
-
*
|
|
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
|
|
194
|
+
function useObjectValue<T extends Obj.Unknown>(
|
|
125
195
|
registry: Registry.Registry,
|
|
126
|
-
|
|
127
|
-
): Accessor<
|
|
128
|
-
// Memoize the resolved
|
|
129
|
-
const
|
|
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
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
const [value, setValue] = createSignal<T | undefined>(
|
|
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
|
|
208
|
+
const input = resolvedInput();
|
|
139
209
|
|
|
140
|
-
if (!
|
|
210
|
+
if (!input) {
|
|
141
211
|
setValue(() => undefined);
|
|
142
212
|
return;
|
|
143
213
|
}
|
|
144
214
|
|
|
145
|
-
const atom = AtomObj.make(
|
|
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
|
|
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
|
|
236
|
+
function useObjectProperty<T extends Obj.Unknown, K extends keyof T>(
|
|
167
237
|
registry: Registry.Registry,
|
|
168
|
-
obj:
|
|
238
|
+
obj: Accessor<T | undefined>,
|
|
169
239
|
property: K,
|
|
170
|
-
): Accessor<
|
|
171
|
-
//
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
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 =
|
|
248
|
+
const currentObj = obj();
|
|
181
249
|
|
|
182
250
|
if (!currentObj) {
|
|
183
251
|
setValue(() => undefined);
|
|
184
252
|
return;
|
|
185
253
|
}
|
|
186
254
|
|
|
187
|
-
|
|
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
|
|
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
|
|
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
|
+
};
|