@dxos/echo-atom 0.8.4-main.c85a9c8dae → 0.8.4-main.e00bdcdb52
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 +12 -4
- package/dist/lib/neutral/index.mjs.map +3 -3
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/types/src/atom.d.ts +1 -1
- 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/src/ref-utils.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -12
- package/src/atom.test.ts +18 -18
- package/src/atom.ts +4 -3
- package/src/batching.test.ts +24 -24
- package/src/query-atom.test.ts +137 -12
- package/src/query-atom.ts +4 -9
- package/src/reactivity.test.ts +25 -25
- package/src/ref-atom.test.ts +45 -11
- package/src/ref-utils.ts +17 -2
package/src/ref-atom.test.ts
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
import * as Registry from '@effect-atom/atom/Registry';
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import { Obj, Ref } from '@dxos/echo';
|
|
9
|
-
import { TestSchema } from '@dxos/echo/testing';
|
|
8
|
+
import { Filter, Obj, Ref } from '@dxos/echo';
|
|
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
|
+
import { PublicKey } from '@dxos/keys';
|
|
12
13
|
|
|
14
|
+
import * as AtomObj from './atom';
|
|
13
15
|
import * as AtomRef from './ref-atom';
|
|
14
16
|
|
|
15
17
|
describe('AtomRef - Basic Functionality', () => {
|
|
@@ -33,7 +35,7 @@ describe('AtomRef - Basic Functionality', () => {
|
|
|
33
35
|
|
|
34
36
|
const targetObj = Obj.make(TestSchema.Person, { name: 'Target', username: 'target', email: 'target@example.com' });
|
|
35
37
|
db.add(targetObj);
|
|
36
|
-
await db.flush(
|
|
38
|
+
await db.flush();
|
|
37
39
|
|
|
38
40
|
const ref = Ref.make(targetObj);
|
|
39
41
|
const atom = AtomRef.make(ref);
|
|
@@ -48,7 +50,7 @@ describe('AtomRef - Basic Functionality', () => {
|
|
|
48
50
|
|
|
49
51
|
const targetObj = Obj.make(TestSchema.Person, { name: 'Target', username: 'target', email: 'target@example.com' });
|
|
50
52
|
db.add(targetObj);
|
|
51
|
-
await db.flush(
|
|
53
|
+
await db.flush();
|
|
52
54
|
|
|
53
55
|
const ref = Ref.make(targetObj);
|
|
54
56
|
const atom = AtomRef.make(ref);
|
|
@@ -60,13 +62,45 @@ describe('AtomRef - Basic Functionality', () => {
|
|
|
60
62
|
expect(updateCount).toBe(1);
|
|
61
63
|
|
|
62
64
|
// Mutate target - ref atom does NOT react to this.
|
|
63
|
-
Obj.
|
|
64
|
-
|
|
65
|
+
Obj.update(targetObj, (targetObj) => {
|
|
66
|
+
targetObj.name = 'Updated';
|
|
65
67
|
});
|
|
66
68
|
|
|
67
69
|
// Update count should still be 1 - ref atom doesn't subscribe to target changes.
|
|
68
70
|
expect(updateCount).toBe(1);
|
|
69
71
|
});
|
|
72
|
+
|
|
73
|
+
// Sibling client (sharing services) creates a brand-new ref target. The atom
|
|
74
|
+
// must update once the target's document propagates and resolves.
|
|
75
|
+
test('atom resolves target created by sibling client', async () => {
|
|
76
|
+
const [spaceKey] = PublicKey.randomSequence();
|
|
77
|
+
await using peer = await testBuilder.createPeer({
|
|
78
|
+
types: [TestSchema.Person, TestSchema.Container],
|
|
79
|
+
});
|
|
80
|
+
await using db1 = await peer.createDatabase(spaceKey);
|
|
81
|
+
const parent1 = db1.add(Obj.make(TestSchema.Container, { objects: [] }));
|
|
82
|
+
await db1.flush();
|
|
83
|
+
|
|
84
|
+
await using client2 = await peer.createClient();
|
|
85
|
+
await using db2 = await peer.openDatabase(spaceKey, db1.rootUrl!, { client: client2 });
|
|
86
|
+
const [parent2] = await db2.query(Filter.id(parent1.id)).run();
|
|
87
|
+
|
|
88
|
+
const newPerson = db2.add(
|
|
89
|
+
Obj.make(TestSchema.Person, { name: 'Alice', username: 'alice', email: 'alice@example.com' }),
|
|
90
|
+
);
|
|
91
|
+
Obj.update(parent2, (parent2) => {
|
|
92
|
+
parent2.objects = [...(parent2.objects ?? []), Ref.make(newPerson)];
|
|
93
|
+
});
|
|
94
|
+
await db2.flush();
|
|
95
|
+
|
|
96
|
+
await expect.poll(() => (parent1.objects ?? []).length).toBeGreaterThan(0);
|
|
97
|
+
const atom = AtomObj.make(parent1.objects![0]);
|
|
98
|
+
|
|
99
|
+
let lastValue: any;
|
|
100
|
+
registry.subscribe(atom, (value) => (lastValue = value), { immediate: true });
|
|
101
|
+
|
|
102
|
+
await expect.poll(() => lastValue?.name).toBe('Alice');
|
|
103
|
+
});
|
|
70
104
|
});
|
|
71
105
|
|
|
72
106
|
describe('AtomRef - Referential Equality', () => {
|
|
@@ -90,7 +124,7 @@ describe('AtomRef - Referential Equality', () => {
|
|
|
90
124
|
|
|
91
125
|
const targetObj = Obj.make(TestSchema.Person, { name: 'Target', username: 'target', email: 'target@example.com' });
|
|
92
126
|
db.add(targetObj);
|
|
93
|
-
await db.flush(
|
|
127
|
+
await db.flush();
|
|
94
128
|
|
|
95
129
|
const ref = Ref.make(targetObj);
|
|
96
130
|
|
|
@@ -116,7 +150,7 @@ describe('AtomRef - Referential Equality', () => {
|
|
|
116
150
|
});
|
|
117
151
|
db.add(targetObj1);
|
|
118
152
|
db.add(targetObj2);
|
|
119
|
-
await db.flush(
|
|
153
|
+
await db.flush();
|
|
120
154
|
|
|
121
155
|
const ref1 = Ref.make(targetObj1);
|
|
122
156
|
const ref2 = Ref.make(targetObj2);
|
|
@@ -133,7 +167,7 @@ describe('AtomRef - Referential Equality', () => {
|
|
|
133
167
|
|
|
134
168
|
const targetObj = Obj.make(TestSchema.Person, { name: 'Target', username: 'target', email: 'target@example.com' });
|
|
135
169
|
db.add(targetObj);
|
|
136
|
-
await db.flush(
|
|
170
|
+
await db.flush();
|
|
137
171
|
|
|
138
172
|
// Create two separate refs to the same target.
|
|
139
173
|
const ref1 = Ref.make(targetObj);
|
|
@@ -158,7 +192,7 @@ describe('AtomRef - Referential Equality', () => {
|
|
|
158
192
|
|
|
159
193
|
const targetObj = Obj.make(TestSchema.Person, { name: 'Target', username: 'target', email: 'target@example.com' });
|
|
160
194
|
db.add(targetObj);
|
|
161
|
-
await db.flush(
|
|
195
|
+
await db.flush();
|
|
162
196
|
|
|
163
197
|
const ref = Ref.make(targetObj);
|
|
164
198
|
|
|
@@ -197,7 +231,7 @@ describe('AtomRef - Expando Objects', () => {
|
|
|
197
231
|
test('works with Expando objects', async () => {
|
|
198
232
|
const targetObj = Obj.make(TestSchema.Expando, { name: 'Expando Target', value: 42 });
|
|
199
233
|
db.add(targetObj);
|
|
200
|
-
await db.flush(
|
|
234
|
+
await db.flush();
|
|
201
235
|
|
|
202
236
|
const ref = Ref.make(targetObj);
|
|
203
237
|
const atom = AtomRef.make(ref);
|
package/src/ref-utils.ts
CHANGED
|
@@ -21,19 +21,34 @@ export const loadRefTarget = <T, R>(
|
|
|
21
21
|
get: Atom.Context,
|
|
22
22
|
onTargetAvailable: (target: T) => R,
|
|
23
23
|
): R | undefined => {
|
|
24
|
+
// Accessing `ref.target` registers a resolution callback when the target is
|
|
25
|
+
// not yet loaded, so resolution can be observed via `ref.onResolved` below.
|
|
24
26
|
const currentTarget = ref.target;
|
|
25
27
|
if (currentTarget) {
|
|
26
28
|
return onTargetAvailable(currentTarget);
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
//
|
|
31
|
+
// Subscribe to the ref's resolution event in case the target loads later
|
|
32
|
+
// (e.g. when a sibling client creates the linked object). Without this,
|
|
33
|
+
// a one-shot async load that fails because the document hasn't propagated
|
|
34
|
+
// would leave the atom permanently undefined.
|
|
35
|
+
const unsubscribe = ref.onResolved(() => {
|
|
36
|
+
const target = ref.target;
|
|
37
|
+
if (target) {
|
|
38
|
+
get.setSelf(onTargetAvailable(target));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
get.addFinalizer(unsubscribe);
|
|
42
|
+
|
|
43
|
+
// Also try async load (e.g. for objects that need disk loading).
|
|
30
44
|
void ref
|
|
31
45
|
.load()
|
|
32
46
|
.then((loadedTarget) => {
|
|
33
47
|
get.setSelf(onTargetAvailable(loadedTarget));
|
|
34
48
|
})
|
|
35
49
|
.catch(() => {
|
|
36
|
-
// Loading failed
|
|
50
|
+
// Loading failed; the resolution subscription above will pick up
|
|
51
|
+
// cross-client updates when they arrive.
|
|
37
52
|
});
|
|
38
53
|
|
|
39
54
|
return undefined;
|