@laboverwire/stitch 0.4.3 → 0.5.1
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/CHANGELOG.md +46 -0
- package/README.md +8 -5
- package/dist/index.d.ts +1 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -7
- package/dist/index.js.map +1 -1
- package/dist/react/provider.d.ts +2 -2
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/store.d.ts +2 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +190 -724
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +23 -161
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -6
- package/dist/types.js.map +1 -1
- package/package.json +23 -14
- package/dist/internal-utils.d.ts +0 -4
- package/dist/internal-utils.d.ts.map +0 -1
- package/dist/internal-utils.js +0 -20
- package/dist/internal-utils.js.map +0 -1
- package/dist/internal-wasm-error.d.ts +0 -9
- package/dist/internal-wasm-error.d.ts.map +0 -1
- package/dist/internal-wasm-error.js +0 -48
- package/dist/internal-wasm-error.js.map +0 -1
- package/dist/memory-store.d.ts +0 -3
- package/dist/memory-store.d.ts.map +0 -1
- package/dist/memory-store.js +0 -572
- package/dist/memory-store.js.map +0 -1
- package/dist/offline-queue.d.ts +0 -6
- package/dist/offline-queue.d.ts.map +0 -1
- package/dist/offline-queue.js +0 -363
- package/dist/offline-queue.js.map +0 -1
- package/dist/persistence-layer.d.ts +0 -7
- package/dist/persistence-layer.d.ts.map +0 -1
- package/dist/persistence-layer.js +0 -409
- package/dist/persistence-layer.js.map +0 -1
- package/dist/remote-sync-layer.d.ts +0 -8
- package/dist/remote-sync-layer.d.ts.map +0 -1
- package/dist/remote-sync-layer.js +0 -439
- package/dist/remote-sync-layer.js.map +0 -1
- package/dist/sync-engine.d.ts +0 -3
- package/dist/sync-engine.d.ts.map +0 -1
- package/dist/sync-engine.js +0 -643
- package/dist/sync-engine.js.map +0 -1
package/dist/store.js
CHANGED
|
@@ -1,779 +1,245 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
_connectionStatus = 'offline';
|
|
17
|
-
statusListeners = new Set();
|
|
18
|
-
currentScopeId = null;
|
|
19
|
-
initialSyncDone = false;
|
|
20
|
-
_authenticatedUser = null;
|
|
21
|
-
_earlySubscribers = [];
|
|
22
|
-
constructor(config, options) {
|
|
23
|
-
this.config = config;
|
|
24
|
-
this.options = options;
|
|
25
|
-
this.memory = createMemoryStore(config);
|
|
1
|
+
import { createStore as createWasmStore } from '@laboverwire/stitch-wasm';
|
|
2
|
+
const EMPTY_ARRAY = [];
|
|
3
|
+
const EMPTY_MAP = {};
|
|
4
|
+
function normalizeStatus(raw) {
|
|
5
|
+
switch (raw) {
|
|
6
|
+
case 'Connected':
|
|
7
|
+
return 'connected';
|
|
8
|
+
case 'Connecting':
|
|
9
|
+
return 'connecting';
|
|
10
|
+
case 'Disconnected':
|
|
11
|
+
return 'disconnected';
|
|
12
|
+
case 'Error':
|
|
13
|
+
return 'error';
|
|
14
|
+
default:
|
|
15
|
+
return 'offline';
|
|
26
16
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
}
|
|
18
|
+
function cacheKey(scopeId, entity) {
|
|
19
|
+
return `${scopeId} ${entity}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Wire a subscription now if the store is ready, otherwise defer wiring until
|
|
23
|
+
* `whenReady` resolves. Returns an unsubscribe that cancels the pending wire or
|
|
24
|
+
* the live subscription, whichever exists. `poke` fires once the deferred
|
|
25
|
+
* subscription attaches so `useSyncExternalStore`-style consumers re-read.
|
|
26
|
+
*/
|
|
27
|
+
function deferrableSubscribe(isReady, whenReady, subscribeNow, poke) {
|
|
28
|
+
if (isReady())
|
|
29
|
+
return subscribeNow();
|
|
30
|
+
let live = null;
|
|
31
|
+
let cancelled = false;
|
|
32
|
+
void whenReady().then(() => {
|
|
33
|
+
if (cancelled)
|
|
34
|
+
return;
|
|
35
|
+
live = subscribeNow();
|
|
36
|
+
poke();
|
|
37
|
+
});
|
|
38
|
+
return () => {
|
|
39
|
+
cancelled = true;
|
|
40
|
+
if (live) {
|
|
41
|
+
live();
|
|
42
|
+
live = null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
class MemoryView {
|
|
47
|
+
#inner;
|
|
48
|
+
#whenReady;
|
|
49
|
+
#arrays = new Map();
|
|
50
|
+
#maps = new Map();
|
|
51
|
+
constructor(inner, whenReady) {
|
|
52
|
+
this.#inner = inner;
|
|
53
|
+
this.#whenReady = whenReady;
|
|
34
54
|
}
|
|
35
|
-
|
|
36
|
-
|
|
55
|
+
getSnapshot(entity, scopeId) {
|
|
56
|
+
if (!this.#inner.ready())
|
|
57
|
+
return EMPTY_ARRAY;
|
|
58
|
+
const version = this.#inner.getVersion(scopeId, entity);
|
|
59
|
+
const key = cacheKey(scopeId, entity);
|
|
60
|
+
const hit = this.#arrays.get(key);
|
|
61
|
+
if (hit && hit.version === version)
|
|
62
|
+
return hit.data;
|
|
63
|
+
const data = this.#inner.getSnapshot(entity, scopeId);
|
|
64
|
+
this.#arrays.set(key, { version, data });
|
|
65
|
+
return data;
|
|
37
66
|
}
|
|
38
|
-
|
|
39
|
-
|
|
67
|
+
getSnapshotAsMap(entity, scopeId) {
|
|
68
|
+
if (!this.#inner.ready())
|
|
69
|
+
return EMPTY_MAP;
|
|
70
|
+
const version = this.#inner.getVersion(scopeId, entity);
|
|
71
|
+
const key = cacheKey(scopeId, entity);
|
|
72
|
+
const hit = this.#maps.get(key);
|
|
73
|
+
if (hit && hit.version === version)
|
|
74
|
+
return hit.data;
|
|
75
|
+
const data = this.#inner.getSnapshotAsMap(entity, scopeId);
|
|
76
|
+
this.#maps.set(key, { version, data });
|
|
77
|
+
return data;
|
|
40
78
|
}
|
|
41
|
-
|
|
42
|
-
return this.
|
|
79
|
+
subscribeToScope(scopeId, entity, callback) {
|
|
80
|
+
return deferrableSubscribe(() => this.#inner.ready(), this.#whenReady, () => this.#inner.subscribeToScope(scopeId, entity, callback), callback);
|
|
43
81
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
82
|
+
}
|
|
83
|
+
class StitchStore {
|
|
84
|
+
config;
|
|
85
|
+
memory;
|
|
86
|
+
#inner;
|
|
87
|
+
#memory;
|
|
88
|
+
#hasPersistence;
|
|
89
|
+
#hasRemote;
|
|
90
|
+
#reconnectValidator = null;
|
|
91
|
+
#readyPromise;
|
|
92
|
+
#resolveReady;
|
|
93
|
+
constructor(inner, config, options) {
|
|
94
|
+
this.#inner = inner;
|
|
95
|
+
this.config = config;
|
|
96
|
+
this.#hasPersistence = options.persistence != null;
|
|
97
|
+
this.#hasRemote = options.remote != null;
|
|
98
|
+
this.#readyPromise = new Promise((resolve) => {
|
|
99
|
+
this.#resolveReady = resolve;
|
|
51
100
|
});
|
|
52
|
-
|
|
101
|
+
this.#memory = new MemoryView(inner, () => this.#readyPromise);
|
|
102
|
+
this.memory = this.#memory;
|
|
53
103
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this._persistence = persistence;
|
|
61
|
-
await persistence.open(this.options.persistence.dbName);
|
|
62
|
-
this.memory.onCorruption(() => {
|
|
63
|
-
persistence.notifyCorruption();
|
|
64
|
-
});
|
|
65
|
-
for (const entry of this._earlySubscribers) {
|
|
66
|
-
entry.persistenceUnsub = persistence.subscribe(entry.entity, entry.callback);
|
|
67
|
-
}
|
|
68
|
-
this._earlySubscribers = [];
|
|
69
|
-
}
|
|
70
|
-
if (this.options.remote) {
|
|
71
|
-
const remote = createRemoteSyncLayer(this.config);
|
|
72
|
-
this._remote = remote;
|
|
73
|
-
if (this._authenticatedUser) {
|
|
74
|
-
remote.setAuthenticatedUser(this._authenticatedUser);
|
|
75
|
-
}
|
|
76
|
-
if (this._persistence) {
|
|
77
|
-
const queue = createPersistentOfflineQueue(this._persistence, this.config.scope.rootEntity);
|
|
78
|
-
this._queue = queue;
|
|
79
|
-
if (this._authenticatedUser && 'setAuthenticatedUser' in queue) {
|
|
80
|
-
queue.setAuthenticatedUser(this._authenticatedUser);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
this._queue = createInMemoryOfflineQueue(this.config.scope.rootEntity);
|
|
85
|
-
}
|
|
86
|
-
remote.setMutationCallback((mutation) => {
|
|
87
|
-
this.handleRemoteMutation(mutation);
|
|
88
|
-
});
|
|
89
|
-
remote.setConnectionChangeCallback((status) => {
|
|
90
|
-
this._connectionStatus = status;
|
|
91
|
-
for (const listener of this.statusListeners) {
|
|
92
|
-
listener(status);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
remote.setOnConnectedCallback(() => {
|
|
96
|
-
this.onConnected();
|
|
97
|
-
});
|
|
98
|
-
await remote.connect(this.options.remote.serverUrl, this.options.remote.getTicket);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
this._connectionStatus = 'offline';
|
|
102
|
-
this.initialSyncDone = true;
|
|
103
|
-
}
|
|
104
|
-
if (this._persistence) {
|
|
105
|
-
this.setupPersistenceSubscriptions();
|
|
106
|
-
}
|
|
107
|
-
this._ready = true;
|
|
108
|
-
if (this._persistence) {
|
|
109
|
-
this._persistence.notifyAllEntitySubscribers();
|
|
110
|
-
}
|
|
104
|
+
#afterReady(fn) {
|
|
105
|
+
return this.#inner.ready() ? fn() : this.#readyPromise.then(fn);
|
|
106
|
+
}
|
|
107
|
+
async initialize() {
|
|
108
|
+
await this.#inner.initialize();
|
|
109
|
+
this.#resolveReady();
|
|
111
110
|
}
|
|
112
111
|
destroy() {
|
|
113
|
-
this.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.
|
|
112
|
+
return this.#inner.ready() ? this.#inner.destroy() : Promise.resolve();
|
|
113
|
+
}
|
|
114
|
+
get ready() {
|
|
115
|
+
return this.#inner.ready();
|
|
117
116
|
}
|
|
118
117
|
read(entity, id) {
|
|
119
|
-
|
|
118
|
+
if (!this.#inner.ready())
|
|
119
|
+
return null;
|
|
120
|
+
return (this.#inner.read(entity, id) ?? null);
|
|
120
121
|
}
|
|
121
122
|
getSnapshot(entity, scopeId) {
|
|
122
|
-
return this
|
|
123
|
+
return this.#memory.getSnapshot(entity, scopeId);
|
|
123
124
|
}
|
|
124
125
|
getSnapshotAsMap(entity, scopeId) {
|
|
125
|
-
return this
|
|
126
|
-
}
|
|
127
|
-
async list(entity, filter) {
|
|
128
|
-
if (this._persistence) {
|
|
129
|
-
const { scopeField } = this.config.scope;
|
|
130
|
-
const options = {};
|
|
131
|
-
if (filter?.scopeId) {
|
|
132
|
-
options.filters = [{ field: scopeField, op: 'eq', value: filter.scopeId }];
|
|
133
|
-
}
|
|
134
|
-
if (filter?.sort && filter.sort.length > 0) {
|
|
135
|
-
options.sort = filter.sort;
|
|
136
|
-
}
|
|
137
|
-
if (filter?.projection && filter.projection.length > 0) {
|
|
138
|
-
options.projection = filter.projection;
|
|
139
|
-
}
|
|
140
|
-
return this._persistence.list(entity, options);
|
|
141
|
-
}
|
|
142
|
-
const scopeId = filter?.scopeId ?? '';
|
|
143
|
-
return Promise.resolve(this.memory.list(entity, scopeId));
|
|
144
|
-
}
|
|
145
|
-
async listRootEntities(sort) {
|
|
146
|
-
if (!this.initialSyncDone && this._remote)
|
|
147
|
-
return [];
|
|
148
|
-
if (this._persistence) {
|
|
149
|
-
const { rootEntity } = this.config.scope;
|
|
150
|
-
const options = {};
|
|
151
|
-
if (sort && sort.length > 0) {
|
|
152
|
-
options.sort = sort;
|
|
153
|
-
}
|
|
154
|
-
return this._persistence.list(rootEntity, options);
|
|
155
|
-
}
|
|
156
|
-
return Promise.resolve(this.memory.list(this.config.scope.rootEntity, ''));
|
|
157
|
-
}
|
|
158
|
-
async getChildCount(entity, scopeId) {
|
|
159
|
-
if (this._persistence) {
|
|
160
|
-
const { scopeField } = this.config.scope;
|
|
161
|
-
return this._persistence.count(entity, {
|
|
162
|
-
filters: [{ field: scopeField, op: 'eq', value: scopeId }],
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
return this.memory.list(entity, scopeId).length;
|
|
166
|
-
}
|
|
167
|
-
async create(entity, scopeId, data, tag) {
|
|
168
|
-
const { rootEntity } = this.config.scope;
|
|
169
|
-
const id = data.id || crypto.randomUUID();
|
|
170
|
-
const record = stripNulls({ ...data, id });
|
|
171
|
-
const effectiveScopeId = entity === rootEntity ? id : scopeId;
|
|
172
|
-
this.memory.create(entity, effectiveScopeId, record, tag);
|
|
173
|
-
if (this._persistence) {
|
|
174
|
-
try {
|
|
175
|
-
await this._persistence.create(entity, record);
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
console.error(`[Stitch] Persistence create ${entity} failed:`, err);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (this._queue && effectiveScopeId) {
|
|
182
|
-
await this._queue.queue({
|
|
183
|
-
op: 'insert',
|
|
184
|
-
entity,
|
|
185
|
-
id,
|
|
186
|
-
scopeId: effectiveScopeId,
|
|
187
|
-
data: record,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
if (this._remote && this._remote.connectionStatus === 'connected' && effectiveScopeId) {
|
|
191
|
-
try {
|
|
192
|
-
await this._remote.syncCreate(entity, effectiveScopeId, record);
|
|
193
|
-
await this._queue?.remove(entity, id, effectiveScopeId, 'insert');
|
|
194
|
-
}
|
|
195
|
-
catch (err) {
|
|
196
|
-
if (err instanceof OwnershipError) {
|
|
197
|
-
await this._queue?.remove(entity, id, effectiveScopeId, 'insert');
|
|
198
|
-
console.warn(`[Stitch] OwnershipError on create ${entity}/${id}:`, err.message);
|
|
199
|
-
return id;
|
|
200
|
-
}
|
|
201
|
-
if (!isTransientSyncError(err)) {
|
|
202
|
-
console.error(`[Stitch] Failed to sync create ${entity}:`, err);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return id;
|
|
207
|
-
}
|
|
208
|
-
async update(entity, id, fields, tag) {
|
|
209
|
-
const { rootEntity, scopeField } = this.config.scope;
|
|
210
|
-
const memExisting = this.memory.read(entity, id);
|
|
211
|
-
let scopeId;
|
|
212
|
-
if (memExisting) {
|
|
213
|
-
scopeId = entity === rootEntity ? id : memExisting[scopeField];
|
|
214
|
-
if (scopeId) {
|
|
215
|
-
this.memory.update(entity, id, fields, tag);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (this._persistence) {
|
|
219
|
-
if (!scopeId) {
|
|
220
|
-
let existing;
|
|
221
|
-
try {
|
|
222
|
-
existing = await this._persistence.read(entity, id);
|
|
223
|
-
}
|
|
224
|
-
catch (readErr) {
|
|
225
|
-
const msg = readErr instanceof Error ? readErr.message : String(readErr);
|
|
226
|
-
if (/not found/i.test(msg))
|
|
227
|
-
return;
|
|
228
|
-
throw readErr;
|
|
229
|
-
}
|
|
230
|
-
scopeId = entity === rootEntity ? id : existing[scopeField];
|
|
231
|
-
if (scopeId && !memExisting) {
|
|
232
|
-
this.memory.update(entity, id, fields, tag);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
try {
|
|
236
|
-
await this._persistence.update(entity, id, fields);
|
|
237
|
-
}
|
|
238
|
-
catch (updErr) {
|
|
239
|
-
console.error(`[Stitch] Persistence update ${entity} failed:`, updErr);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
else if (!memExisting) {
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
if (this._queue && scopeId) {
|
|
246
|
-
await this._queue.queue({ op: 'update', entity, id, scopeId, data: fields });
|
|
247
|
-
}
|
|
248
|
-
if (this._remote && this._remote.connectionStatus === 'connected' && scopeId) {
|
|
249
|
-
try {
|
|
250
|
-
await this._remote.syncUpdate(entity, scopeId, id, fields);
|
|
251
|
-
await this._queue?.remove(entity, id, scopeId, 'update');
|
|
252
|
-
}
|
|
253
|
-
catch (err) {
|
|
254
|
-
if (err instanceof OwnershipError) {
|
|
255
|
-
await this._queue?.remove(entity, id, scopeId, 'update');
|
|
256
|
-
console.warn(`[Stitch] OwnershipError on update ${entity}/${id}:`, err.message);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
const isNotFound = err instanceof Error && /not found/i.test(err.message);
|
|
260
|
-
if (isNotFound && entity === rootEntity) {
|
|
261
|
-
try {
|
|
262
|
-
if (this._persistence) {
|
|
263
|
-
await this._persistence.delete(rootEntity, id);
|
|
264
|
-
}
|
|
265
|
-
this.memory.delete(rootEntity, id);
|
|
266
|
-
}
|
|
267
|
-
catch (cleanupErr) {
|
|
268
|
-
console.error(`[Stitch] Cleanup delete ${rootEntity}/${id} failed:`, cleanupErr);
|
|
269
|
-
}
|
|
270
|
-
await this._queue?.remove(entity, id, scopeId, 'update');
|
|
271
|
-
}
|
|
272
|
-
else if (isNotFound) {
|
|
273
|
-
try {
|
|
274
|
-
let full;
|
|
275
|
-
if (this._persistence) {
|
|
276
|
-
full = await this._persistence.read(entity, id);
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
full = this.memory.read(entity, id) ?? {};
|
|
280
|
-
}
|
|
281
|
-
await this._remote.syncCreate(entity, scopeId, full);
|
|
282
|
-
}
|
|
283
|
-
catch (createErr) {
|
|
284
|
-
if (createErr instanceof OwnershipError) {
|
|
285
|
-
await this._queue?.remove(entity, id, scopeId, 'update');
|
|
286
|
-
console.warn(`[Stitch] OwnershipError on upsert ${entity}/${id}:`, createErr.message);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
if (!isTransientSyncError(createErr)) {
|
|
290
|
-
console.error(`[Stitch] Failed to sync upsert ${entity}:`, createErr);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
else if (!isTransientSyncError(err)) {
|
|
295
|
-
console.error(`[Stitch] Failed to sync update ${entity}:`, err);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
126
|
+
return this.#memory.getSnapshotAsMap(entity, scopeId);
|
|
299
127
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
this.memory.delete(entity, id, tag);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
try {
|
|
328
|
-
await this._persistence.delete(entity, id);
|
|
329
|
-
}
|
|
330
|
-
catch (delErr) {
|
|
331
|
-
const msg = delErr instanceof Error ? delErr.message : String(delErr);
|
|
332
|
-
if (/not found/i.test(msg))
|
|
333
|
-
return;
|
|
334
|
-
console.error(`[Stitch] Persistence delete ${entity} failed:`, delErr);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
else if (!memExisting) {
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
if (this._queue && scopeId) {
|
|
341
|
-
await this._queue.queue({ op: 'delete', entity, id, scopeId, data: null });
|
|
342
|
-
}
|
|
343
|
-
if (this._remote && this._remote.connectionStatus === 'connected' && scopeId) {
|
|
344
|
-
try {
|
|
345
|
-
await this._remote.syncDelete(entity, scopeId, id);
|
|
346
|
-
await this._queue?.remove(entity, id, scopeId, 'delete');
|
|
347
|
-
}
|
|
348
|
-
catch (err) {
|
|
349
|
-
if (err instanceof OwnershipError) {
|
|
350
|
-
await this._queue?.remove(entity, id, scopeId, 'delete');
|
|
351
|
-
console.warn(`[Stitch] OwnershipError on delete ${entity}/${id}:`, err.message);
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
const isNotFound = err instanceof Error && /not found/i.test(err.message);
|
|
355
|
-
if (!isNotFound && !isTransientSyncError(err)) {
|
|
356
|
-
console.error(`[Stitch] Failed to sync delete ${entity}:`, err);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
128
|
+
list(entity, filter) {
|
|
129
|
+
return this.#afterReady(() => this.#inner.list(entity, filter ?? {}));
|
|
130
|
+
}
|
|
131
|
+
listRootEntities(sort) {
|
|
132
|
+
return this.#afterReady(() => this.#inner.listRootEntities(sort ?? []));
|
|
133
|
+
}
|
|
134
|
+
getChildCount(entity, scopeId) {
|
|
135
|
+
if (!this.#inner.ready())
|
|
136
|
+
return 0;
|
|
137
|
+
return this.#inner.getChildCount(entity, scopeId);
|
|
138
|
+
}
|
|
139
|
+
getVersion(scopeId, entity) {
|
|
140
|
+
if (!this.#inner.ready())
|
|
141
|
+
return 0;
|
|
142
|
+
return this.#inner.getVersion(scopeId, entity);
|
|
143
|
+
}
|
|
144
|
+
create(entity, scopeId, data, tag) {
|
|
145
|
+
return this.#afterReady(() => this.#inner.create(entity, scopeId, data, tag ?? undefined));
|
|
146
|
+
}
|
|
147
|
+
update(entity, id, fields, tag) {
|
|
148
|
+
return this.#afterReady(() => this.#inner.update(entity, id, fields, tag ?? undefined));
|
|
149
|
+
}
|
|
150
|
+
delete(entity, id, tag) {
|
|
151
|
+
return this.#afterReady(() => this.#inner.delete(entity, id, tag ?? undefined));
|
|
360
152
|
}
|
|
361
153
|
subscribeToScope(scopeId, entity, callback) {
|
|
362
|
-
return this.
|
|
154
|
+
return deferrableSubscribe(() => this.#inner.ready(), () => this.#readyPromise, () => this.#inner.subscribeToScope(scopeId, entity, callback), callback);
|
|
363
155
|
}
|
|
364
156
|
subscribeToEntity(entity, callback) {
|
|
365
|
-
|
|
366
|
-
if (event.entity !== entity)
|
|
367
|
-
return;
|
|
368
|
-
if (this._persistence && event.originTag !== 'load' && event.originTag !== 'clear')
|
|
369
|
-
return;
|
|
370
|
-
const op = event.operation === 'create' ? 'insert' : event.operation;
|
|
371
|
-
callback(event.data, op);
|
|
372
|
-
});
|
|
373
|
-
const entry = {
|
|
374
|
-
entity,
|
|
375
|
-
callback: (data, op) => {
|
|
376
|
-
callback((data ?? null), op);
|
|
377
|
-
},
|
|
378
|
-
memoryUnsub: memUnsub,
|
|
379
|
-
persistenceUnsub: null,
|
|
380
|
-
};
|
|
381
|
-
if (this._persistence) {
|
|
382
|
-
entry.persistenceUnsub = this._persistence.subscribe(entity, entry.callback);
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
this._earlySubscribers.push(entry);
|
|
386
|
-
}
|
|
387
|
-
return () => {
|
|
388
|
-
entry.memoryUnsub();
|
|
389
|
-
entry.persistenceUnsub?.();
|
|
390
|
-
const idx = this._earlySubscribers.indexOf(entry);
|
|
391
|
-
if (idx >= 0)
|
|
392
|
-
this._earlySubscribers.splice(idx, 1);
|
|
393
|
-
};
|
|
157
|
+
return deferrableSubscribe(() => this.#inner.ready(), () => this.#readyPromise, () => this.#inner.subscribeToEntity(entity, (data, op) => callback((data ?? null), op)), () => callback(null, 'update'));
|
|
394
158
|
}
|
|
395
159
|
beginBatch() {
|
|
396
|
-
this.
|
|
160
|
+
if (this.#inner.ready())
|
|
161
|
+
this.#inner.beginBatch();
|
|
397
162
|
}
|
|
398
163
|
endBatch() {
|
|
399
|
-
this.
|
|
164
|
+
if (this.#inner.ready())
|
|
165
|
+
this.#inner.endBatch();
|
|
400
166
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return;
|
|
404
|
-
const { rootEntity, childEntities } = this.config.scope;
|
|
405
|
-
if (this.currentScopeId && this.currentScopeId !== scopeId) {
|
|
406
|
-
const prevScope = this.currentScopeId;
|
|
407
|
-
this.currentScopeId = scopeId;
|
|
408
|
-
this.closeScope(prevScope).catch(() => { });
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
this.currentScopeId = scopeId;
|
|
412
|
-
}
|
|
413
|
-
if (this._remote && this._remote.connectionStatus === 'connected') {
|
|
414
|
-
if (this._persistence) {
|
|
415
|
-
this._persistence.setSuppressNotifications(true);
|
|
416
|
-
}
|
|
417
|
-
try {
|
|
418
|
-
const state = await this._remote.openScope(scopeId);
|
|
419
|
-
if (this._persistence) {
|
|
420
|
-
const localAccessor = this.createLocalAccessor();
|
|
421
|
-
const versionField = this.config.versionField ?? 'version';
|
|
422
|
-
if (state.root && state.root.id) {
|
|
423
|
-
if (state.root[versionField] != null && typeof state.root[versionField] !== 'number') {
|
|
424
|
-
state.root[versionField] = Number(state.root[versionField]) || 1;
|
|
425
|
-
}
|
|
426
|
-
try {
|
|
427
|
-
await localAccessor.read(rootEntity, state.root.id);
|
|
428
|
-
await localAccessor.update(rootEntity, state.root.id, state.root);
|
|
429
|
-
}
|
|
430
|
-
catch {
|
|
431
|
-
await localAccessor.create(rootEntity, state.root);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
else {
|
|
435
|
-
try {
|
|
436
|
-
await localAccessor.read(rootEntity, scopeId);
|
|
437
|
-
}
|
|
438
|
-
catch {
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
for (const childEntity of childEntities) {
|
|
443
|
-
const serverChildren = state.children[childEntity] ?? [];
|
|
444
|
-
await this._remote.reconcileChildren(scopeId, childEntity, serverChildren, localAccessor, this._queue);
|
|
445
|
-
}
|
|
446
|
-
for (const mutation of state.bufferedMutations) {
|
|
447
|
-
await this._remote.applyMutationToDb(mutation, localAccessor);
|
|
448
|
-
}
|
|
449
|
-
const bundle = await this.loadScopeFromPersistence(scopeId);
|
|
450
|
-
if (bundle) {
|
|
451
|
-
this.memory.loadScope(scopeId, bundle, 'load');
|
|
452
|
-
}
|
|
453
|
-
await this.loadRootIntoMemory(rootEntity, scopeId);
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
this.memory.loadScope(scopeId, state.children, 'load');
|
|
457
|
-
if (state.root && state.root.id) {
|
|
458
|
-
this.memory.create(rootEntity, scopeId, state.root, 'load');
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
catch (err) {
|
|
463
|
-
console.error('[Stitch] Failed to open scope from server:', err);
|
|
464
|
-
if (this._persistence) {
|
|
465
|
-
const bundle = await this.loadScopeFromPersistence(scopeId);
|
|
466
|
-
if (bundle) {
|
|
467
|
-
this.memory.loadScope(scopeId, bundle, 'load');
|
|
468
|
-
}
|
|
469
|
-
await this.loadRootIntoMemory(rootEntity, scopeId);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
finally {
|
|
473
|
-
if (this._persistence) {
|
|
474
|
-
this._persistence.setSuppressNotifications(false);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
else if (this._persistence) {
|
|
479
|
-
const bundle = await this.loadScopeFromPersistence(scopeId);
|
|
480
|
-
if (bundle) {
|
|
481
|
-
this.memory.loadScope(scopeId, bundle, 'load');
|
|
482
|
-
}
|
|
483
|
-
await this.loadRootIntoMemory(rootEntity, scopeId);
|
|
484
|
-
}
|
|
167
|
+
replaceScope(scopeId) {
|
|
168
|
+
return this.#afterReady(() => this.#inner.replaceScope(scopeId));
|
|
485
169
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
await this._remote.closeScope(scopeId);
|
|
489
|
-
}
|
|
490
|
-
if (this.currentScopeId === scopeId) {
|
|
491
|
-
this.currentScopeId = null;
|
|
492
|
-
}
|
|
170
|
+
closeScope(scopeId) {
|
|
171
|
+
return this.#afterReady(() => this.#inner.closeScope(scopeId));
|
|
493
172
|
}
|
|
494
173
|
loadScope(scopeId, data) {
|
|
495
|
-
this.
|
|
174
|
+
return this.#afterReady(() => this.#inner.loadScope(scopeId, data));
|
|
496
175
|
}
|
|
497
176
|
clearScope(scopeId) {
|
|
498
|
-
this.
|
|
177
|
+
return this.#afterReady(() => this.#inner.clearScope(scopeId));
|
|
178
|
+
}
|
|
179
|
+
get connectionStatus() {
|
|
180
|
+
if (!this.#inner.ready())
|
|
181
|
+
return 'offline';
|
|
182
|
+
return normalizeStatus(this.#inner.connectionStatus());
|
|
499
183
|
}
|
|
500
184
|
subscribeToConnectionStatus(cb) {
|
|
501
|
-
|
|
502
|
-
return this._remote.subscribeToConnectionStatus(cb);
|
|
503
|
-
}
|
|
504
|
-
this.statusListeners.add(cb);
|
|
505
|
-
cb(this._connectionStatus);
|
|
506
|
-
return () => this.statusListeners.delete(cb);
|
|
185
|
+
return deferrableSubscribe(() => this.#inner.ready(), () => this.#readyPromise, () => this.#inner.subscribeToConnectionStatus((raw) => cb(normalizeStatus(raw))), () => cb(normalizeStatus(this.#inner.connectionStatus())));
|
|
507
186
|
}
|
|
508
187
|
disconnect() {
|
|
509
|
-
this.
|
|
510
|
-
this._connectionStatus = 'offline';
|
|
188
|
+
return this.#inner.ready() ? this.#inner.disconnect() : Promise.resolve();
|
|
511
189
|
}
|
|
512
190
|
async reconnect(serverUrl, getTicket) {
|
|
513
|
-
if (this
|
|
514
|
-
await this
|
|
515
|
-
|
|
191
|
+
if (this.#reconnectValidator)
|
|
192
|
+
await this.#reconnectValidator();
|
|
193
|
+
const ticket = getTicket ? await getTicket() : undefined;
|
|
194
|
+
await this.#inner.reconnect(serverUrl, ticket ?? undefined);
|
|
195
|
+
}
|
|
196
|
+
get isReconnecting() {
|
|
197
|
+
if (!this.#inner.ready())
|
|
198
|
+
return false;
|
|
199
|
+
return this.#inner.isReconnecting();
|
|
516
200
|
}
|
|
517
201
|
setAuthenticatedUser(userId) {
|
|
518
|
-
this.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
this.
|
|
522
|
-
}
|
|
202
|
+
if (this.#inner.ready())
|
|
203
|
+
this.#inner.setAuthenticatedUser(userId);
|
|
204
|
+
else
|
|
205
|
+
void this.#readyPromise.then(() => this.#inner.setAuthenticatedUser(userId));
|
|
523
206
|
}
|
|
524
207
|
setSessionInvalidHandler(handler) {
|
|
525
|
-
this.
|
|
208
|
+
if (this.#inner.ready())
|
|
209
|
+
this.#inner.setSessionInvalidHandler(handler);
|
|
210
|
+
else
|
|
211
|
+
void this.#readyPromise.then(() => this.#inner.setSessionInvalidHandler(handler));
|
|
526
212
|
}
|
|
527
213
|
setReconnectValidator(validator) {
|
|
528
|
-
this
|
|
214
|
+
this.#reconnectValidator = validator;
|
|
529
215
|
}
|
|
530
216
|
resetForLogout() {
|
|
531
|
-
this.
|
|
532
|
-
this._persistence?.close();
|
|
533
|
-
this._persistence = null;
|
|
534
|
-
this._queue = null;
|
|
535
|
-
this._ready = false;
|
|
536
|
-
this._initPromise = null;
|
|
537
|
-
this._earlySubscribers = [];
|
|
538
|
-
this.currentScopeId = null;
|
|
539
|
-
this._authenticatedUser = null;
|
|
540
|
-
this.initialSyncDone = false;
|
|
541
|
-
this._connectionStatus = 'offline';
|
|
542
|
-
sessionStorage.removeItem('stitch_cached_user');
|
|
543
|
-
sessionStorage.removeItem('stitch_pending_logout');
|
|
544
|
-
}
|
|
545
|
-
async request(topic, payload) {
|
|
546
|
-
if (!this._remote)
|
|
547
|
-
throw new Error('No remote connection');
|
|
548
|
-
return this._remote.request(topic, payload);
|
|
549
|
-
}
|
|
550
|
-
async readLocalState(entity, id) {
|
|
551
|
-
if (this._persistence) {
|
|
552
|
-
return this._persistence.readLocalState(entity, id);
|
|
553
|
-
}
|
|
554
|
-
return this.memory.read(entity, id);
|
|
555
|
-
}
|
|
556
|
-
async updateLocalState(entity, id, fields) {
|
|
557
|
-
if (this._persistence) {
|
|
558
|
-
await this._persistence.updateLocalState(entity, id, fields);
|
|
559
|
-
}
|
|
560
|
-
else {
|
|
561
|
-
const existing = this.memory.read(entity, id);
|
|
562
|
-
if (existing) {
|
|
563
|
-
this.memory.update(entity, id, fields);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
217
|
+
return this.#inner.ready() ? this.#inner.resetForLogout() : Promise.resolve();
|
|
566
218
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
return null;
|
|
572
|
-
const envelope = JSON.parse(raw);
|
|
573
|
-
if (typeof envelope !== 'object' || envelope === null)
|
|
574
|
-
return null;
|
|
575
|
-
const { user, cachedAt } = envelope;
|
|
576
|
-
const MAX_AGE_MS = 15 * 60 * 1000;
|
|
577
|
-
if (typeof cachedAt !== 'number' || Date.now() - cachedAt > MAX_AGE_MS) {
|
|
578
|
-
sessionStorage.removeItem('stitch_cached_user');
|
|
579
|
-
return null;
|
|
580
|
-
}
|
|
581
|
-
if (typeof user !== 'object' || user === null)
|
|
582
|
-
return null;
|
|
583
|
-
return user;
|
|
584
|
-
}
|
|
585
|
-
catch {
|
|
586
|
-
return null;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
async setCachedUser(user) {
|
|
590
|
-
sessionStorage.setItem('stitch_cached_user', JSON.stringify({ user, cachedAt: Date.now() }));
|
|
591
|
-
sessionStorage.removeItem('stitch_pending_logout');
|
|
219
|
+
readLocalState(entity, id) {
|
|
220
|
+
return this.#afterReady(() => this.#inner
|
|
221
|
+
.readLocalState(entity, id)
|
|
222
|
+
.then((r) => (r ?? null)));
|
|
592
223
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
async hasPendingLogout() {
|
|
597
|
-
return sessionStorage.getItem('stitch_pending_logout') === 'true';
|
|
598
|
-
}
|
|
599
|
-
async setPendingLogout(pending) {
|
|
600
|
-
if (pending) {
|
|
601
|
-
sessionStorage.setItem('stitch_pending_logout', 'true');
|
|
602
|
-
}
|
|
603
|
-
else {
|
|
604
|
-
sessionStorage.removeItem('stitch_pending_logout');
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
async flushPendingLogout(logoutFn) {
|
|
608
|
-
const pending = await this.hasPendingLogout();
|
|
609
|
-
if (!pending)
|
|
610
|
-
return;
|
|
611
|
-
try {
|
|
612
|
-
await logoutFn();
|
|
613
|
-
await this.setPendingLogout(false);
|
|
614
|
-
}
|
|
615
|
-
catch { }
|
|
616
|
-
}
|
|
617
|
-
async handleRemoteMutation(mutation) {
|
|
618
|
-
const localAccessor = this.createLocalAccessor();
|
|
619
|
-
if (this._remote) {
|
|
620
|
-
await this._remote.applyMutationToDb(mutation, localAccessor);
|
|
621
|
-
}
|
|
622
|
-
if (!this._persistence) {
|
|
623
|
-
const { scopeField } = this.config.scope;
|
|
624
|
-
switch (mutation.op) {
|
|
625
|
-
case 'insert':
|
|
626
|
-
if (mutation.data) {
|
|
627
|
-
const sid = mutation.data[scopeField];
|
|
628
|
-
if (sid) {
|
|
629
|
-
this.memory.create(mutation.entity, sid, { ...mutation.data, id: mutation.id }, 'remote');
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
break;
|
|
633
|
-
case 'update':
|
|
634
|
-
if (mutation.data) {
|
|
635
|
-
this.memory.update(mutation.entity, mutation.id, mutation.data, 'remote');
|
|
636
|
-
}
|
|
637
|
-
break;
|
|
638
|
-
case 'delete':
|
|
639
|
-
this.memory.delete(mutation.entity, mutation.id, 'remote');
|
|
640
|
-
break;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
224
|
+
updateLocalState(entity, id, fields) {
|
|
225
|
+
return this.#afterReady(() => this.#inner.updateLocalState(entity, id, fields));
|
|
643
226
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (this._queue && this._remote) {
|
|
647
|
-
const sender = this.createMutationSender();
|
|
648
|
-
await this._queue.flush(sender);
|
|
649
|
-
await this._queue.flush(sender);
|
|
650
|
-
}
|
|
651
|
-
if (this._remote) {
|
|
652
|
-
const localAccessor = this.createLocalAccessor();
|
|
653
|
-
this.initialSyncDone = false;
|
|
654
|
-
await this._remote.syncRootEntityList(localAccessor, this._queue);
|
|
655
|
-
this.initialSyncDone = true;
|
|
656
|
-
}
|
|
657
|
-
this._persistence?.notifyAllEntitySubscribers();
|
|
658
|
-
}
|
|
659
|
-
catch (err) {
|
|
660
|
-
void err;
|
|
661
|
-
this.initialSyncDone = true;
|
|
662
|
-
}
|
|
227
|
+
pendingMutationCount(scopeId) {
|
|
228
|
+
return this.#afterReady(() => this.#inner.pendingMutationCount(scopeId));
|
|
663
229
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return {
|
|
667
|
-
read: (entity, id) => this._persistence.read(entity, id),
|
|
668
|
-
list: (entity, options) => this._persistence.list(entity, options),
|
|
669
|
-
create: (entity, data) => this._persistence.create(entity, data),
|
|
670
|
-
update: (entity, id, data) => this._persistence.update(entity, id, data),
|
|
671
|
-
delete: (entity, id) => this._persistence.delete(entity, id),
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
return {
|
|
675
|
-
read: async (entity, id) => {
|
|
676
|
-
const result = this.memory.read(entity, id);
|
|
677
|
-
if (!result)
|
|
678
|
-
throw new Error(`${entity} ${id} not found`);
|
|
679
|
-
return result;
|
|
680
|
-
},
|
|
681
|
-
list: async (entity, options) => {
|
|
682
|
-
const filters = options.filters;
|
|
683
|
-
if (filters && filters.length > 0) {
|
|
684
|
-
const scopeFilter = filters.find((f) => f.field === this.config.scope.scopeField);
|
|
685
|
-
if (scopeFilter) {
|
|
686
|
-
return this.memory.list(entity, scopeFilter.value);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
return this.memory.list(entity, '');
|
|
690
|
-
},
|
|
691
|
-
create: async (entity, data) => {
|
|
692
|
-
const { scopeField } = this.config.scope;
|
|
693
|
-
const scopeId = data[scopeField];
|
|
694
|
-
this.memory.create(entity, scopeId ?? '', data, 'remote');
|
|
695
|
-
},
|
|
696
|
-
update: async (entity, id, data) => {
|
|
697
|
-
this.memory.update(entity, id, data, 'remote');
|
|
698
|
-
},
|
|
699
|
-
delete: async (entity, id) => {
|
|
700
|
-
this.memory.delete(entity, id, 'remote');
|
|
701
|
-
},
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
createMutationSender() {
|
|
705
|
-
return {
|
|
706
|
-
syncCreate: (entity, scopeId, data) => this._remote.syncCreate(entity, scopeId, data),
|
|
707
|
-
syncUpdate: (entity, scopeId, id, data) => this._remote.syncUpdate(entity, scopeId, id, data),
|
|
708
|
-
syncDelete: (entity, scopeId, id) => this._remote.syncDelete(entity, scopeId, id),
|
|
709
|
-
readEntity: async (entity, id) => {
|
|
710
|
-
if (this._persistence) {
|
|
711
|
-
return this._persistence.read(entity, id);
|
|
712
|
-
}
|
|
713
|
-
const result = this.memory.read(entity, id);
|
|
714
|
-
if (!result)
|
|
715
|
-
throw new Error(`${entity} ${id} not found`);
|
|
716
|
-
return result;
|
|
717
|
-
},
|
|
718
|
-
deleteEntity: async (entity, id) => {
|
|
719
|
-
if (this._persistence) {
|
|
720
|
-
await this._persistence.delete(entity, id);
|
|
721
|
-
}
|
|
722
|
-
this.memory.delete(entity, id);
|
|
723
|
-
},
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
async loadRootIntoMemory(rootEntity, scopeId) {
|
|
727
|
-
if (!this._persistence)
|
|
728
|
-
return;
|
|
729
|
-
try {
|
|
730
|
-
const rootRecord = await this._persistence.read(rootEntity, scopeId);
|
|
731
|
-
this.memory.create(rootEntity, scopeId, rootRecord, 'load');
|
|
732
|
-
}
|
|
733
|
-
catch (readErr) {
|
|
734
|
-
console.debug(`[Stitch] Root entity ${rootEntity}/${scopeId} not in persistence:`, readErr);
|
|
735
|
-
}
|
|
230
|
+
request(topic, payload) {
|
|
231
|
+
return this.#afterReady(() => this.#inner.request(topic, payload));
|
|
736
232
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
return null;
|
|
740
|
-
const { childEntities, scopeField } = this.config.scope;
|
|
741
|
-
const children = {};
|
|
742
|
-
for (const entity of childEntities) {
|
|
743
|
-
children[entity] = await this._persistence.list(entity, {
|
|
744
|
-
filters: [{ field: scopeField, op: 'eq', value: scopeId }],
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
return children;
|
|
233
|
+
get hasPersistence() {
|
|
234
|
+
return this.#hasPersistence;
|
|
748
235
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
return;
|
|
752
|
-
const { childEntities, scopeField } = this.config.scope;
|
|
753
|
-
for (const entity of childEntities) {
|
|
754
|
-
this._persistence.subscribe(entity, (entityData, op) => {
|
|
755
|
-
if (entityData === null || typeof entityData !== 'object')
|
|
756
|
-
return;
|
|
757
|
-
const record = entityData;
|
|
758
|
-
const sid = record[scopeField];
|
|
759
|
-
if (!sid || sid !== this.currentScopeId)
|
|
760
|
-
return;
|
|
761
|
-
switch (op) {
|
|
762
|
-
case 'insert':
|
|
763
|
-
this.memory.create(entity, sid, record, 'remote');
|
|
764
|
-
break;
|
|
765
|
-
case 'update':
|
|
766
|
-
this.memory.update(entity, record.id, record, 'remote');
|
|
767
|
-
break;
|
|
768
|
-
case 'delete':
|
|
769
|
-
this.memory.delete(entity, record.id, 'remote');
|
|
770
|
-
break;
|
|
771
|
-
}
|
|
772
|
-
});
|
|
773
|
-
}
|
|
236
|
+
get hasRemote() {
|
|
237
|
+
return this.#hasRemote;
|
|
774
238
|
}
|
|
775
239
|
}
|
|
776
240
|
export function createStore(config, options) {
|
|
777
|
-
|
|
241
|
+
const resolvedOptions = options ?? {};
|
|
242
|
+
const inner = createWasmStore(config, resolvedOptions);
|
|
243
|
+
return new StitchStore(inner, config, resolvedOptions);
|
|
778
244
|
}
|
|
779
245
|
//# sourceMappingURL=store.js.map
|