@dxos/echo-atom 0.8.4-main.ef1bc66f44 → 0.8.4-main.fcfe5033a5
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/README.md +28 -18
- package/dist/lib/neutral/index.mjs +43 -11
- package/dist/lib/neutral/index.mjs.map +3 -3
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/types/src/atom.d.ts +19 -6
- package/dist/types/src/atom.d.ts.map +1 -1
- package/dist/types/src/query-atom.d.ts +2 -2
- package/dist/types/src/query-atom.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -9
- package/src/atom.test.ts +102 -12
- package/src/atom.ts +85 -18
- package/src/batching.test.ts +15 -15
- package/src/query-atom.test.ts +138 -12
- package/src/query-atom.ts +5 -10
- package/src/reactivity.test.ts +45 -18
- package/src/ref-atom.test.ts +10 -10
package/src/query-atom.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { DXN, Database, type Entity, type Filter, Query, type QueryResult } from
|
|
|
8
8
|
import { WeakDictionary } from '@dxos/util';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Create a self-updating atom from
|
|
11
|
+
* Create a self-updating atom from any QueryResult (e.g. schema registry queries).
|
|
12
12
|
* Internally subscribes to queryResult and uses get.setSelf to update.
|
|
13
13
|
* Cleanup is handled via get.addFinalizer.
|
|
14
14
|
*
|
|
@@ -17,18 +17,13 @@ import { WeakDictionary } from '@dxos/util';
|
|
|
17
17
|
* @param queryResult - The QueryResult to wrap.
|
|
18
18
|
* @returns An atom that automatically updates when query results change.
|
|
19
19
|
*/
|
|
20
|
-
export const fromQuery = <T
|
|
20
|
+
export const fromQuery = <T>(queryResult: QueryResult.QueryResult<T>): Atom.Atom<T[]> =>
|
|
21
21
|
Atom.make((get) => {
|
|
22
|
-
// TODO(wittjosiah): Consider subscribing to individual objects here as well, and grabbing their snapshots.
|
|
23
|
-
// Subscribe to QueryResult changes.
|
|
24
22
|
const unsubscribe = queryResult.subscribe(() => {
|
|
25
|
-
get.setSelf(queryResult.
|
|
23
|
+
get.setSelf(queryResult.runSync());
|
|
26
24
|
});
|
|
27
|
-
|
|
28
|
-
// Register cleanup for when atom is no longer used.
|
|
29
25
|
get.addFinalizer(unsubscribe);
|
|
30
|
-
|
|
31
|
-
return queryResult.results;
|
|
26
|
+
return queryResult.runSync();
|
|
32
27
|
});
|
|
33
28
|
|
|
34
29
|
// Registry: key → Queryable (WeakRef with auto-cleanup when GC'd).
|
|
@@ -37,7 +32,7 @@ const queryableRegistry = new WeakDictionary<string, Database.Queryable>();
|
|
|
37
32
|
// Key separator that won't appear in identifiers (DXN strings use colons).
|
|
38
33
|
const KEY_SEPARATOR = '~';
|
|
39
34
|
|
|
40
|
-
// Atom.family keyed by "identifier
|
|
35
|
+
// Atom.family keyed by "identifier~serializedAST".
|
|
41
36
|
const queryFamily = Atom.family((key: string) => {
|
|
42
37
|
// Parse key outside Atom.make - runs once per key.
|
|
43
38
|
const separatorIndex = key.indexOf(KEY_SEPARATOR);
|
package/src/reactivity.test.ts
CHANGED
|
@@ -6,8 +6,9 @@ import * as Registry from '@effect-atom/atom/Registry';
|
|
|
6
6
|
import { describe, expect, test } from 'vitest';
|
|
7
7
|
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
|
-
import { TestSchema } from '@dxos/echo/testing';
|
|
10
9
|
import { createObject } from '@dxos/echo-db';
|
|
10
|
+
import { TestSchema } from '@dxos/echo/testing';
|
|
11
|
+
import { arrayMove } from '@dxos/util';
|
|
11
12
|
|
|
12
13
|
import * as AtomObj from './atom';
|
|
13
14
|
|
|
@@ -28,8 +29,8 @@ describe('Echo Atom - Reactivity', () => {
|
|
|
28
29
|
registry.subscribe(atom, () => {});
|
|
29
30
|
|
|
30
31
|
// Update the object via Obj.change.
|
|
31
|
-
Obj.change(obj, (
|
|
32
|
-
|
|
32
|
+
Obj.change(obj, (obj) => {
|
|
33
|
+
obj.name = 'Updated';
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
const updatedSnapshot = registry.get(atom);
|
|
@@ -50,8 +51,8 @@ describe('Echo Atom - Reactivity', () => {
|
|
|
50
51
|
registry.subscribe(atom, () => {});
|
|
51
52
|
|
|
52
53
|
// Update the property via Obj.change.
|
|
53
|
-
Obj.change(obj, (
|
|
54
|
-
|
|
54
|
+
Obj.change(obj, (obj) => {
|
|
55
|
+
obj.name = 'Updated';
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
expect(registry.get(atom)).toBe('Updated');
|
|
@@ -82,8 +83,8 @@ describe('Echo Atom - Reactivity', () => {
|
|
|
82
83
|
});
|
|
83
84
|
|
|
84
85
|
// Update only email property via Obj.change.
|
|
85
|
-
Obj.change(obj, (
|
|
86
|
-
|
|
86
|
+
Obj.change(obj, (obj) => {
|
|
87
|
+
obj.email = 'updated@example.com';
|
|
87
88
|
});
|
|
88
89
|
|
|
89
90
|
// Name atom should NOT have changed.
|
|
@@ -109,9 +110,9 @@ describe('Echo Atom - Reactivity', () => {
|
|
|
109
110
|
registry.subscribe(emailAtom, () => {});
|
|
110
111
|
|
|
111
112
|
// Update multiple properties via Obj.change.
|
|
112
|
-
Obj.change(obj, (
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
Obj.change(obj, (obj) => {
|
|
114
|
+
obj.name = 'Updated';
|
|
115
|
+
obj.email = 'updated@example.com';
|
|
115
116
|
});
|
|
116
117
|
|
|
117
118
|
expect(registry.get(nameAtom)).toBe('Updated');
|
|
@@ -140,11 +141,11 @@ describe('Echo Atom - Reactivity', () => {
|
|
|
140
141
|
expect(initialCount).toBe(1);
|
|
141
142
|
|
|
142
143
|
// Update object via Obj.change.
|
|
143
|
-
Obj.change(obj, (
|
|
144
|
-
|
|
144
|
+
Obj.change(obj, (obj) => {
|
|
145
|
+
obj.name = 'Updated';
|
|
145
146
|
});
|
|
146
|
-
Obj.change(obj, (
|
|
147
|
-
|
|
147
|
+
Obj.change(obj, (obj) => {
|
|
148
|
+
obj.email = 'updated@example.com';
|
|
148
149
|
});
|
|
149
150
|
|
|
150
151
|
// Updates fire through Obj.subscribe (one per Obj.change call).
|
|
@@ -166,8 +167,8 @@ describe('Echo Atom - Reactivity', () => {
|
|
|
166
167
|
});
|
|
167
168
|
|
|
168
169
|
actions.push('before');
|
|
169
|
-
Obj.change(obj, (
|
|
170
|
-
|
|
170
|
+
Obj.change(obj, (obj) => {
|
|
171
|
+
obj.name = 'Updated';
|
|
171
172
|
});
|
|
172
173
|
actions.push('after');
|
|
173
174
|
|
|
@@ -190,8 +191,8 @@ describe('Echo Atom - Reactivity', () => {
|
|
|
190
191
|
});
|
|
191
192
|
|
|
192
193
|
actions.push('before');
|
|
193
|
-
Obj.change(obj, (
|
|
194
|
-
|
|
194
|
+
Obj.change(obj, (obj) => {
|
|
195
|
+
obj.stringArray!.splice(1, 1);
|
|
195
196
|
});
|
|
196
197
|
actions.push('after');
|
|
197
198
|
|
|
@@ -203,4 +204,30 @@ describe('Echo Atom - Reactivity', () => {
|
|
|
203
204
|
|
|
204
205
|
unsubscribe();
|
|
205
206
|
});
|
|
207
|
+
|
|
208
|
+
test('property atom for array property updates when array is reordered in place', () => {
|
|
209
|
+
// Verifies that makeProperty(obj, 'columns')-style atoms subscribe to in-place
|
|
210
|
+
// array mutations (e.g. arrayMove), so UI stays in sync after column reorder.
|
|
211
|
+
const obj = createObject(Obj.make(TestSchema.Example, { stringArray: ['a', 'b', 'c'] }));
|
|
212
|
+
|
|
213
|
+
const registry = Registry.make();
|
|
214
|
+
const atom = AtomObj.makeProperty(obj, 'stringArray');
|
|
215
|
+
|
|
216
|
+
const initial = registry.get(atom);
|
|
217
|
+
expect(initial).toEqual(['a', 'b', 'c']);
|
|
218
|
+
|
|
219
|
+
let updateCount = 0;
|
|
220
|
+
registry.subscribe(atom, () => {
|
|
221
|
+
updateCount++;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Reorder in place (e.g. move first to last).
|
|
225
|
+
Obj.change(obj, (obj) => {
|
|
226
|
+
arrayMove(obj.stringArray!, 0, 2);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(updateCount).toBe(1);
|
|
230
|
+
const afterReorder = registry.get(atom);
|
|
231
|
+
expect(afterReorder).toEqual(['b', 'c', 'a']);
|
|
232
|
+
});
|
|
206
233
|
});
|
package/src/ref-atom.test.ts
CHANGED
|
@@ -6,9 +6,9 @@ import * as Registry from '@effect-atom/atom/Registry';
|
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
7
7
|
|
|
8
8
|
import { Obj, Ref } from '@dxos/echo';
|
|
9
|
-
import { TestSchema } from '@dxos/echo/testing';
|
|
10
9
|
import { type EchoDatabase } from '@dxos/echo-db';
|
|
11
10
|
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
11
|
+
import { TestSchema } from '@dxos/echo/testing';
|
|
12
12
|
|
|
13
13
|
import * as AtomRef from './ref-atom';
|
|
14
14
|
|
|
@@ -33,7 +33,7 @@ describe('AtomRef - Basic Functionality', () => {
|
|
|
33
33
|
|
|
34
34
|
const targetObj = Obj.make(TestSchema.Person, { name: 'Target', username: 'target', email: 'target@example.com' });
|
|
35
35
|
db.add(targetObj);
|
|
36
|
-
await db.flush(
|
|
36
|
+
await db.flush();
|
|
37
37
|
|
|
38
38
|
const ref = Ref.make(targetObj);
|
|
39
39
|
const atom = AtomRef.make(ref);
|
|
@@ -48,7 +48,7 @@ describe('AtomRef - Basic Functionality', () => {
|
|
|
48
48
|
|
|
49
49
|
const targetObj = Obj.make(TestSchema.Person, { name: 'Target', username: 'target', email: 'target@example.com' });
|
|
50
50
|
db.add(targetObj);
|
|
51
|
-
await db.flush(
|
|
51
|
+
await db.flush();
|
|
52
52
|
|
|
53
53
|
const ref = Ref.make(targetObj);
|
|
54
54
|
const atom = AtomRef.make(ref);
|
|
@@ -60,8 +60,8 @@ describe('AtomRef - Basic Functionality', () => {
|
|
|
60
60
|
expect(updateCount).toBe(1);
|
|
61
61
|
|
|
62
62
|
// Mutate target - ref atom does NOT react to this.
|
|
63
|
-
Obj.change(targetObj, (
|
|
64
|
-
|
|
63
|
+
Obj.change(targetObj, (targetObj) => {
|
|
64
|
+
targetObj.name = 'Updated';
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
// Update count should still be 1 - ref atom doesn't subscribe to target changes.
|
|
@@ -90,7 +90,7 @@ describe('AtomRef - Referential Equality', () => {
|
|
|
90
90
|
|
|
91
91
|
const targetObj = Obj.make(TestSchema.Person, { name: 'Target', username: 'target', email: 'target@example.com' });
|
|
92
92
|
db.add(targetObj);
|
|
93
|
-
await db.flush(
|
|
93
|
+
await db.flush();
|
|
94
94
|
|
|
95
95
|
const ref = Ref.make(targetObj);
|
|
96
96
|
|
|
@@ -116,7 +116,7 @@ describe('AtomRef - Referential Equality', () => {
|
|
|
116
116
|
});
|
|
117
117
|
db.add(targetObj1);
|
|
118
118
|
db.add(targetObj2);
|
|
119
|
-
await db.flush(
|
|
119
|
+
await db.flush();
|
|
120
120
|
|
|
121
121
|
const ref1 = Ref.make(targetObj1);
|
|
122
122
|
const ref2 = Ref.make(targetObj2);
|
|
@@ -133,7 +133,7 @@ describe('AtomRef - Referential Equality', () => {
|
|
|
133
133
|
|
|
134
134
|
const targetObj = Obj.make(TestSchema.Person, { name: 'Target', username: 'target', email: 'target@example.com' });
|
|
135
135
|
db.add(targetObj);
|
|
136
|
-
await db.flush(
|
|
136
|
+
await db.flush();
|
|
137
137
|
|
|
138
138
|
// Create two separate refs to the same target.
|
|
139
139
|
const ref1 = Ref.make(targetObj);
|
|
@@ -158,7 +158,7 @@ describe('AtomRef - Referential Equality', () => {
|
|
|
158
158
|
|
|
159
159
|
const targetObj = Obj.make(TestSchema.Person, { name: 'Target', username: 'target', email: 'target@example.com' });
|
|
160
160
|
db.add(targetObj);
|
|
161
|
-
await db.flush(
|
|
161
|
+
await db.flush();
|
|
162
162
|
|
|
163
163
|
const ref = Ref.make(targetObj);
|
|
164
164
|
|
|
@@ -197,7 +197,7 @@ describe('AtomRef - Expando Objects', () => {
|
|
|
197
197
|
test('works with Expando objects', async () => {
|
|
198
198
|
const targetObj = Obj.make(TestSchema.Expando, { name: 'Expando Target', value: 42 });
|
|
199
199
|
db.add(targetObj);
|
|
200
|
-
await db.flush(
|
|
200
|
+
await db.flush();
|
|
201
201
|
|
|
202
202
|
const ref = Ref.make(targetObj);
|
|
203
203
|
const atom = AtomRef.make(ref);
|