@osdk/client 2.2.0-beta.3 → 2.2.0-beta.4
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 +12 -0
- package/build/browser/Logger.js.map +1 -1
- package/build/browser/observable/ObservableClient.js.map +1 -1
- package/build/browser/observable/internal/ActionApplication.js +102 -0
- package/build/browser/observable/internal/ActionApplication.js.map +1 -0
- package/build/browser/observable/internal/CacheKey.js +38 -1
- package/build/browser/observable/internal/CacheKey.js.map +1 -1
- package/build/browser/observable/internal/CacheKeys.js +4 -4
- package/build/browser/observable/internal/CacheKeys.js.map +1 -1
- package/build/browser/observable/internal/ChangedObjects.js +24 -1
- package/build/browser/observable/internal/ChangedObjects.js.map +1 -1
- package/build/browser/observable/internal/Layer.js +3 -3
- package/build/browser/observable/internal/Layer.js.map +1 -1
- package/build/browser/observable/internal/ListQuery.js +188 -66
- package/build/browser/observable/internal/ListQuery.js.map +1 -1
- package/build/browser/observable/internal/ObjectQuery.js +16 -3
- package/build/browser/observable/internal/ObjectQuery.js.map +1 -1
- package/build/browser/observable/internal/ObservableClientImpl.js +2 -2
- package/build/browser/observable/internal/ObservableClientImpl.js.map +1 -1
- package/build/browser/observable/internal/OptimisticJob.js +30 -29
- package/build/browser/observable/internal/OptimisticJob.js.map +1 -1
- package/build/browser/observable/internal/Query.js +42 -2
- package/build/browser/observable/internal/Query.js.map +1 -1
- package/build/browser/observable/internal/Store.js +259 -126
- package/build/browser/observable/internal/Store.js.map +1 -1
- package/build/browser/observable/internal/Store.test.js +416 -76
- package/build/browser/observable/internal/Store.test.js.map +1 -1
- package/build/browser/observable/internal/testUtils.js +142 -6
- package/build/browser/observable/internal/testUtils.js.map +1 -1
- package/build/browser/util/UserAgent.js +1 -1
- package/build/cjs/index.cjs +1 -1
- package/build/cjs/public/unstable-do-not-use.cjs +657 -268
- package/build/cjs/public/unstable-do-not-use.cjs.map +1 -1
- package/build/cjs/public/unstable-do-not-use.d.cts +13 -4
- package/build/esm/Logger.js.map +1 -1
- package/build/esm/observable/ObservableClient.js.map +1 -1
- package/build/esm/observable/internal/ActionApplication.js +102 -0
- package/build/esm/observable/internal/ActionApplication.js.map +1 -0
- package/build/esm/observable/internal/CacheKey.js +38 -1
- package/build/esm/observable/internal/CacheKey.js.map +1 -1
- package/build/esm/observable/internal/CacheKeys.js +4 -4
- package/build/esm/observable/internal/CacheKeys.js.map +1 -1
- package/build/esm/observable/internal/ChangedObjects.js +24 -1
- package/build/esm/observable/internal/ChangedObjects.js.map +1 -1
- package/build/esm/observable/internal/Layer.js +3 -3
- package/build/esm/observable/internal/Layer.js.map +1 -1
- package/build/esm/observable/internal/ListQuery.js +188 -66
- package/build/esm/observable/internal/ListQuery.js.map +1 -1
- package/build/esm/observable/internal/ObjectQuery.js +16 -3
- package/build/esm/observable/internal/ObjectQuery.js.map +1 -1
- package/build/esm/observable/internal/ObservableClientImpl.js +2 -2
- package/build/esm/observable/internal/ObservableClientImpl.js.map +1 -1
- package/build/esm/observable/internal/OptimisticJob.js +30 -29
- package/build/esm/observable/internal/OptimisticJob.js.map +1 -1
- package/build/esm/observable/internal/Query.js +42 -2
- package/build/esm/observable/internal/Query.js.map +1 -1
- package/build/esm/observable/internal/Store.js +259 -126
- package/build/esm/observable/internal/Store.js.map +1 -1
- package/build/esm/observable/internal/Store.test.js +416 -76
- package/build/esm/observable/internal/Store.test.js.map +1 -1
- package/build/esm/observable/internal/testUtils.js +142 -6
- package/build/esm/observable/internal/testUtils.js.map +1 -1
- package/build/esm/util/UserAgent.js +1 -1
- package/build/types/Logger.d.ts +1 -2
- package/build/types/Logger.d.ts.map +1 -1
- package/build/types/observable/ObservableClient.d.ts +10 -3
- package/build/types/observable/ObservableClient.d.ts.map +1 -1
- package/build/types/observable/internal/ActionApplication.d.ts +9 -0
- package/build/types/observable/internal/ActionApplication.d.ts.map +1 -0
- package/build/types/observable/internal/CacheKeys.d.ts +2 -1
- package/build/types/observable/internal/CacheKeys.d.ts.map +1 -1
- package/build/types/observable/internal/ChangedObjects.d.ts +6 -2
- package/build/types/observable/internal/ChangedObjects.d.ts.map +1 -1
- package/build/types/observable/internal/Layer.d.ts +1 -1
- package/build/types/observable/internal/Layer.d.ts.map +1 -1
- package/build/types/observable/internal/ListQuery.d.ts +18 -17
- package/build/types/observable/internal/ListQuery.d.ts.map +1 -1
- package/build/types/observable/internal/ObjectQuery.d.ts +3 -3
- package/build/types/observable/internal/ObjectQuery.d.ts.map +1 -1
- package/build/types/observable/internal/OptimisticJob.d.ts +2 -2
- package/build/types/observable/internal/OptimisticJob.d.ts.map +1 -1
- package/build/types/observable/internal/Query.d.ts +6 -5
- package/build/types/observable/internal/Query.d.ts.map +1 -1
- package/build/types/observable/internal/Store.d.ts +47 -19
- package/build/types/observable/internal/Store.d.ts.map +1 -1
- package/build/types/observable/internal/testUtils.d.ts +13 -3
- package/build/types/observable/internal/testUtils.d.ts.map +1 -1
- package/package.json +8 -7
|
@@ -14,19 +14,19 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { delay } from "msw";
|
|
18
17
|
import { BehaviorSubject } from "rxjs";
|
|
19
18
|
import invariant from "tiny-invariant";
|
|
19
|
+
import { additionalContext } from "../../Client.js";
|
|
20
20
|
import { DEBUG_REFCOUNTS } from "../DebugFlags.js";
|
|
21
|
+
import { ActionApplication } from "./ActionApplication.js";
|
|
21
22
|
import { CacheKeys } from "./CacheKeys.js";
|
|
22
|
-
import { createChangedObjects } from "./ChangedObjects.js";
|
|
23
|
-
import { Layer } from "./Layer.js";
|
|
23
|
+
import { createChangedObjects, DEBUG_ONLY__changesToString } from "./ChangedObjects.js";
|
|
24
|
+
import { Entry, Layer } from "./Layer.js";
|
|
24
25
|
import { isListCacheKey, ListQuery } from "./ListQuery.js";
|
|
25
26
|
import { ObjectQuery } from "./ObjectQuery.js";
|
|
26
|
-
import { runOptimisticJob } from "./OptimisticJob.js";
|
|
27
27
|
import { RefCounts } from "./RefCounts.js";
|
|
28
|
+
import { WeakMapWithEntries } from "./WeakMapWithEntries.js";
|
|
28
29
|
import { WhereClauseCanonicalizer } from "./WhereClauseCanonicalizer.js";
|
|
29
|
-
const ACTION_DELAY = process.env.NODE_ENV === "production" ? 0 : 1000;
|
|
30
30
|
|
|
31
31
|
/*
|
|
32
32
|
Work still to do:
|
|
@@ -35,9 +35,9 @@ const ACTION_DELAY = process.env.NODE_ENV === "production" ? 0 : 1000;
|
|
|
35
35
|
- [x] automatic optimistic list updates
|
|
36
36
|
- [x] useOsdkObjects
|
|
37
37
|
- [x] imply offline for objects passed directly
|
|
38
|
-
- [
|
|
38
|
+
- [x] websocket subscriptions
|
|
39
39
|
- [ ] links
|
|
40
|
-
- [
|
|
40
|
+
- [x] add pagination
|
|
41
41
|
- [ ] sub-selection support
|
|
42
42
|
- [ ] interfaces
|
|
43
43
|
- [ ] setup defaults
|
|
@@ -59,19 +59,38 @@ function createInitEntry(cacheKey) {
|
|
|
59
59
|
- Data is one per layer per cache key
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
|
+
export class OrderByCanonicalizer {
|
|
63
|
+
// crappy version
|
|
64
|
+
#map = new Map();
|
|
65
|
+
canonicalize = orderBy => {
|
|
66
|
+
if (this.#map.has(JSON.stringify(orderBy))) {
|
|
67
|
+
return this.#map.get(JSON.stringify(orderBy));
|
|
68
|
+
} else {
|
|
69
|
+
this.#map.set(JSON.stringify(orderBy), orderBy);
|
|
70
|
+
return orderBy;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
62
74
|
export class Store {
|
|
63
75
|
whereCanonicalizer = new WhereClauseCanonicalizer();
|
|
76
|
+
orderByCanonicalizer = new OrderByCanonicalizer();
|
|
64
77
|
#truthLayer = new Layer(undefined, undefined);
|
|
65
78
|
#topLayer;
|
|
66
|
-
|
|
79
|
+
|
|
80
|
+
/** @internal */
|
|
81
|
+
|
|
82
|
+
#queries = new WeakMapWithEntries();
|
|
67
83
|
#cacheKeyToSubject = new WeakMap();
|
|
68
84
|
#cacheKeys;
|
|
69
85
|
#refCounts = new RefCounts(DEBUG_REFCOUNTS ? 15_000 : 60_000, k => this.#cleanupCacheKey(k));
|
|
70
86
|
#finalizationRegistry;
|
|
71
87
|
constructor(client) {
|
|
72
88
|
this.client = client;
|
|
89
|
+
this.logger = client[additionalContext].logger?.child({}, {
|
|
90
|
+
msgPrefix: "Store"
|
|
91
|
+
});
|
|
73
92
|
this.#topLayer = this.#truthLayer;
|
|
74
|
-
this.#cacheKeys = new CacheKeys(this.whereCanonicalizer, k => {
|
|
93
|
+
this.#cacheKeys = new CacheKeys(this.whereCanonicalizer, this.orderByCanonicalizer, k => {
|
|
75
94
|
if (DEBUG_REFCOUNTS) {
|
|
76
95
|
const cacheKeyType = k.type;
|
|
77
96
|
const otherKeys = k.otherKeys;
|
|
@@ -185,7 +204,16 @@ export class Store {
|
|
|
185
204
|
const query = this.getObjectQuery(apiName, pk);
|
|
186
205
|
this.#refCounts.retain(query.cacheKey);
|
|
187
206
|
if (options.mode !== "offline") {
|
|
188
|
-
|
|
207
|
+
query.revalidate(options.mode === "force").catch(e => {
|
|
208
|
+
// we don't want observeObject() to return a promise,
|
|
209
|
+
// so we settle for logging an error here instead of
|
|
210
|
+
// dropping it on the floor.
|
|
211
|
+
if (this.logger) {
|
|
212
|
+
this.logger.error("Unhandled error in observeObject", e);
|
|
213
|
+
} else {
|
|
214
|
+
throw e;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
189
217
|
}
|
|
190
218
|
const sub = query.subscribe({
|
|
191
219
|
next: subFn
|
|
@@ -197,11 +225,9 @@ export class Store {
|
|
|
197
225
|
}
|
|
198
226
|
};
|
|
199
227
|
}
|
|
200
|
-
observeList(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
const query = this.getListQuery(apiName, where, options);
|
|
228
|
+
observeList(options, subFn) {
|
|
229
|
+
// the ListQuery represents the shared state of the list
|
|
230
|
+
const query = this.getListQuery(options.objectType, options.where ?? {}, options.orderBy ?? {}, options);
|
|
205
231
|
this.#refCounts.retain(query.cacheKey);
|
|
206
232
|
if (options.mode !== "offline") {
|
|
207
233
|
void query.revalidate(options.mode === "force");
|
|
@@ -209,6 +235,109 @@ export class Store {
|
|
|
209
235
|
const sub = query.subscribe({
|
|
210
236
|
next: subFn
|
|
211
237
|
});
|
|
238
|
+
if (options.streamUpdates) {
|
|
239
|
+
const miniDef = {
|
|
240
|
+
type: "object",
|
|
241
|
+
apiName: typeof options.objectType === "string" ? options.objectType : options.objectType.apiName
|
|
242
|
+
};
|
|
243
|
+
let objectSet = this.client(miniDef);
|
|
244
|
+
if (options.where) {
|
|
245
|
+
objectSet = objectSet.where(options.where ?? {});
|
|
246
|
+
}
|
|
247
|
+
const store = this;
|
|
248
|
+
const websocketSubscription = objectSet.subscribe({
|
|
249
|
+
onChange({
|
|
250
|
+
object,
|
|
251
|
+
state
|
|
252
|
+
}) {
|
|
253
|
+
if (process.env.NODE_ENV !== "production") {
|
|
254
|
+
store.logger?.debug({
|
|
255
|
+
methodName: "onError"
|
|
256
|
+
}, "updates", state, object);
|
|
257
|
+
}
|
|
258
|
+
const cacheKey = store.getCacheKey("object", object.$objectType, object.$primaryKey);
|
|
259
|
+
const type = store.#peekQuery(cacheKey) == null ? "addedObjects" : "modifiedObjects";
|
|
260
|
+
const changes = createChangedObjects();
|
|
261
|
+
changes[type].set(object.$objectType, object);
|
|
262
|
+
if (state === "ADDED_OR_UPDATED") {
|
|
263
|
+
// todo, can we do the update without
|
|
264
|
+
// the extra invalidation? maybe a flag to updateObject
|
|
265
|
+
store.updateObject(object.$objectType, object);
|
|
266
|
+
store.maybeRevalidateLists(changes).catch(err => {
|
|
267
|
+
// eslint-disable-next-line no-console
|
|
268
|
+
console.error("Unhandled error in maybeRevalidateLists", err);
|
|
269
|
+
});
|
|
270
|
+
} else if (state === "REMOVED") {
|
|
271
|
+
const changes = createChangedObjects();
|
|
272
|
+
store.batch({
|
|
273
|
+
changes
|
|
274
|
+
}, batch => {
|
|
275
|
+
const existing = batch.read(query.cacheKey);
|
|
276
|
+
const cacheKeyToRemove = store.getCacheKey("object", object.$objectType, object.$primaryKey);
|
|
277
|
+
if (existing?.status === "loaded") {
|
|
278
|
+
const newObjects = existing.value?.data.filter(o => o !== cacheKeyToRemove);
|
|
279
|
+
if (newObjects?.length !== existing.value?.data.length) {
|
|
280
|
+
batch.changes.modifiedLists.add(query.cacheKey);
|
|
281
|
+
batch.write(query.cacheKey, {
|
|
282
|
+
data: newObjects ?? []
|
|
283
|
+
}, "loaded");
|
|
284
|
+
// Should there be an else for this case? Do we need to invalidate
|
|
285
|
+
// the paging tokens we may have?
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
// There may be a tiny race here where OSW tells us the object has
|
|
289
|
+
// been removed but an outstanding invalidation of this query is
|
|
290
|
+
// about to return. In this case, its possible that we remove this item
|
|
291
|
+
// from the list and then the returned list load re-adds it.
|
|
292
|
+
// To avoid this, we will just force reload the query to be sure
|
|
293
|
+
// we don't leave things in a bad state.
|
|
294
|
+
|
|
295
|
+
if (process.env.NODE_ENV !== "production") {
|
|
296
|
+
store.logger?.info("Removing an object from an object list that is in the middle of being loaded.", existing);
|
|
297
|
+
}
|
|
298
|
+
query.revalidate(/* force */true).catch(e => {
|
|
299
|
+
if (store.logger) {
|
|
300
|
+
store.logger?.error("Uncaught error while revalidating list", e);
|
|
301
|
+
} else {
|
|
302
|
+
// eslint-disable-next-line no-console
|
|
303
|
+
console.error("Uncaught error while revalidating list", e);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
onError(errors) {
|
|
311
|
+
if (process.env.NODE_ENV !== "production") {
|
|
312
|
+
store.logger?.info({
|
|
313
|
+
methodName: "onError"
|
|
314
|
+
}, "subscription errors", errors);
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
onOutOfDate() {
|
|
318
|
+
if (process.env.NODE_ENV !== "production") {
|
|
319
|
+
store.logger?.info({
|
|
320
|
+
methodName: "onOutOfDate"
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
onSuccessfulSubscription() {
|
|
325
|
+
if (process.env.NODE_ENV !== "production") {
|
|
326
|
+
store.logger?.info({
|
|
327
|
+
methodName: "onSuccessfulSubscription"
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
sub.add(() => {
|
|
333
|
+
if (process.env.NODE_ENV !== "production") {
|
|
334
|
+
store.logger?.info({
|
|
335
|
+
methodName: "observeList"
|
|
336
|
+
}, "Unsubscribing from websocket");
|
|
337
|
+
}
|
|
338
|
+
websocketSubscription.unsubscribe();
|
|
339
|
+
});
|
|
340
|
+
}
|
|
212
341
|
return {
|
|
213
342
|
unsubscribe: () => {
|
|
214
343
|
sub.unsubscribe();
|
|
@@ -227,14 +356,15 @@ export class Store {
|
|
|
227
356
|
}
|
|
228
357
|
return query;
|
|
229
358
|
}
|
|
230
|
-
getListQuery(apiName, where,
|
|
359
|
+
getListQuery(apiName, where, orderBy, opts) {
|
|
231
360
|
if (typeof apiName !== "string") {
|
|
232
361
|
apiName = apiName.apiName;
|
|
233
362
|
}
|
|
234
363
|
const canonWhere = this.whereCanonicalizer.canonicalize(where);
|
|
235
|
-
const
|
|
364
|
+
const canonOrderBy = this.orderByCanonicalizer.canonicalize(orderBy);
|
|
365
|
+
const listCacheKey = this.getCacheKey("list", apiName, canonWhere, canonOrderBy);
|
|
236
366
|
return this.#getQuery(listCacheKey, () => {
|
|
237
|
-
return new ListQuery(this, this.getSubject(listCacheKey), apiName, canonWhere, listCacheKey, opts);
|
|
367
|
+
return new ListQuery(this, this.getSubject(listCacheKey), apiName, canonWhere, canonOrderBy, listCacheKey, opts);
|
|
238
368
|
});
|
|
239
369
|
}
|
|
240
370
|
getObjectQuery(apiName, pk) {
|
|
@@ -255,14 +385,13 @@ export class Store {
|
|
|
255
385
|
return objEntry?.value;
|
|
256
386
|
}
|
|
257
387
|
batch = ({
|
|
258
|
-
optimisticId
|
|
388
|
+
optimisticId,
|
|
389
|
+
changes = createChangedObjects()
|
|
259
390
|
}, batchFn) => {
|
|
260
391
|
!(optimisticId === undefined || !!optimisticId) ? process.env.NODE_ENV !== "production" ? invariant(false, "optimistic must be undefined or not falsy") : invariant(false) : void 0;
|
|
261
392
|
let needsLayer = optimisticId !== undefined;
|
|
262
393
|
const batchContext = {
|
|
263
|
-
|
|
264
|
-
modifiedObjects: new Set(),
|
|
265
|
-
modifiedLists: new Set(),
|
|
394
|
+
changes,
|
|
266
395
|
createLayerIfNeeded: () => {
|
|
267
396
|
if (needsLayer) {
|
|
268
397
|
this.#topLayer = this.#topLayer.addLayer(optimisticId);
|
|
@@ -274,16 +403,12 @@ export class Store {
|
|
|
274
403
|
const oldTopValue = this.#topLayer.get(cacheKey);
|
|
275
404
|
if (optimisticId) batchContext.createLayerIfNeeded();
|
|
276
405
|
const writeLayer = optimisticId ? this.#topLayer : this.#truthLayer;
|
|
277
|
-
const newValue =
|
|
278
|
-
cacheKey,
|
|
279
|
-
value,
|
|
280
|
-
lastUpdated: Date.now(),
|
|
281
|
-
status
|
|
282
|
-
};
|
|
406
|
+
const newValue = new Entry(cacheKey, value, Date.now(), status);
|
|
283
407
|
writeLayer.set(cacheKey, newValue);
|
|
284
408
|
const newTopValue = this.#topLayer.get(cacheKey);
|
|
285
409
|
if (oldTopValue !== newTopValue) {
|
|
286
410
|
this.#cacheKeyToSubject.get(cacheKey)?.next({
|
|
411
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-spread
|
|
287
412
|
...newValue,
|
|
288
413
|
isOptimistic: newTopValue?.value !== this.#truthLayer.get(cacheKey)?.value
|
|
289
414
|
});
|
|
@@ -295,6 +420,7 @@ export class Store {
|
|
|
295
420
|
}
|
|
296
421
|
};
|
|
297
422
|
const retVal = batchFn(batchContext);
|
|
423
|
+
void this.maybeUpdateLists(changes, optimisticId);
|
|
298
424
|
return {
|
|
299
425
|
batchResult: batchContext,
|
|
300
426
|
retVal: retVal
|
|
@@ -315,37 +441,93 @@ export class Store {
|
|
|
315
441
|
// TODO
|
|
316
442
|
// could we detect that a list WOULD include it?
|
|
317
443
|
}
|
|
318
|
-
maybeRevalidateLists(changes) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
444
|
+
async maybeRevalidateLists(changes) {
|
|
445
|
+
if (process.env.NODE_ENV !== "production") {
|
|
446
|
+
// todo
|
|
447
|
+
this.logger?.trace({
|
|
448
|
+
methodName: "maybeRevalidateList"
|
|
449
|
+
}, DEBUG_ONLY__changesToString(changes));
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
const promises = [];
|
|
453
|
+
for (const [cacheKey, v] of this.#truthLayer.entries()) {
|
|
454
|
+
if (isListCacheKey(cacheKey)) {
|
|
455
|
+
const promise = this.#peekQuery(cacheKey)?.maybeUpdateAndRevalidate(changes, undefined);
|
|
456
|
+
if (promise) promises.push(promise);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
await Promise.all(promises);
|
|
460
|
+
} finally {
|
|
461
|
+
if (process.env.NODE_ENV !== "production") {
|
|
462
|
+
// todo
|
|
463
|
+
this.logger?.trace({
|
|
464
|
+
methodName: "maybeRevalidateList"
|
|
465
|
+
}, "in finally", DEBUG_ONLY__changesToString(changes));
|
|
323
466
|
}
|
|
324
467
|
}
|
|
325
468
|
}
|
|
326
469
|
maybeUpdateLists(changes, optimisticId) {
|
|
327
|
-
|
|
470
|
+
if (process.env.NODE_ENV !== "production") {
|
|
471
|
+
this.logger?.trace({
|
|
472
|
+
methodName: "maybeUpdateLists"
|
|
473
|
+
}, DEBUG_ONLY__changesToString(changes), {
|
|
474
|
+
optimisticId
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
if (changes.addedObjects.size === 0 && changes.modifiedObjects.size === 0) {
|
|
478
|
+
return Promise.resolve([]);
|
|
479
|
+
}
|
|
480
|
+
const promises = [];
|
|
481
|
+
for (const cacheKey of this.#queries.keys()) {
|
|
328
482
|
if (isListCacheKey(cacheKey)) {
|
|
329
|
-
|
|
330
|
-
|
|
483
|
+
if (!changes.modifiedLists.has(cacheKey)) {
|
|
484
|
+
const promise = this.#peekQuery(cacheKey)?.maybeUpdateAndRevalidate(changes, optimisticId);
|
|
485
|
+
if (promise) promises.push(promise);
|
|
486
|
+
}
|
|
331
487
|
}
|
|
332
488
|
}
|
|
489
|
+
return Promise.all(promises);
|
|
333
490
|
}
|
|
334
|
-
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* @param apiName
|
|
494
|
+
* @param changes The changes we know about / to update
|
|
495
|
+
* @returns
|
|
496
|
+
*/
|
|
497
|
+
invalidateObjectType(apiName, changes) {
|
|
335
498
|
if (typeof apiName !== "string") {
|
|
336
499
|
apiName = apiName.apiName;
|
|
337
500
|
}
|
|
501
|
+
if (process.env.NODE_ENV !== "production") {
|
|
502
|
+
this.logger?.info({
|
|
503
|
+
methodName: "invalidateObjectType"
|
|
504
|
+
}, changes ? DEBUG_ONLY__changesToString(changes) : void 0);
|
|
505
|
+
}
|
|
506
|
+
const promises = [];
|
|
338
507
|
for (const [cacheKey, v] of this.#truthLayer.entries()) {
|
|
339
508
|
if (isListCacheKey(cacheKey, apiName)) {
|
|
340
|
-
|
|
509
|
+
if (!changes || !changes.modifiedLists.has(cacheKey)) {
|
|
510
|
+
const promise = this.#peekQuery(cacheKey)?.revalidate(true);
|
|
511
|
+
if (promise) {
|
|
512
|
+
promises.push(promise);
|
|
513
|
+
changes?.modifiedLists.add(cacheKey);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
341
516
|
}
|
|
342
517
|
}
|
|
518
|
+
return Promise.all(promises);
|
|
343
519
|
}
|
|
344
|
-
invalidateList(
|
|
345
|
-
|
|
346
|
-
|
|
520
|
+
invalidateList({
|
|
521
|
+
objectType,
|
|
522
|
+
where,
|
|
523
|
+
orderBy
|
|
524
|
+
}) {
|
|
525
|
+
if (typeof objectType !== "string") {
|
|
526
|
+
objectType = objectType.apiName;
|
|
347
527
|
}
|
|
348
|
-
|
|
528
|
+
where = this.whereCanonicalizer.canonicalize(where ?? {});
|
|
529
|
+
orderBy = this.orderByCanonicalizer.canonicalize(orderBy ?? {});
|
|
530
|
+
const cacheKey = this.getCacheKey("list", objectType, where, orderBy);
|
|
349
531
|
void this.#peekQuery(cacheKey)?.revalidate(true);
|
|
350
532
|
}
|
|
351
533
|
updateObject(apiName, value, {
|
|
@@ -361,19 +543,48 @@ export class Store {
|
|
|
361
543
|
return query.writeToStore(value, "loaded", batch);
|
|
362
544
|
}).retVal.value;
|
|
363
545
|
}
|
|
364
|
-
|
|
546
|
+
updateObjects(values, batch) {
|
|
547
|
+
// update the cache for any object that has changed
|
|
548
|
+
// and save the mapped values to return
|
|
549
|
+
return values.map(v => {
|
|
550
|
+
return this.getObjectQuery(v.$apiName, v.$primaryKey).writeToStore(v, "loaded", batch).cacheKey;
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Updates the internal state of a list and will create a new internal query if needed.
|
|
556
|
+
*
|
|
557
|
+
* Helper method only for tests right now. May be removed later.
|
|
558
|
+
*
|
|
559
|
+
* @param apiName
|
|
560
|
+
* @param where
|
|
561
|
+
* @param orderBy
|
|
562
|
+
* @param objects
|
|
563
|
+
* @param param4
|
|
564
|
+
* @param opts
|
|
565
|
+
*/
|
|
566
|
+
updateList({
|
|
567
|
+
objectType: apiName,
|
|
568
|
+
where,
|
|
569
|
+
orderBy
|
|
570
|
+
}, objects, {
|
|
365
571
|
optimisticId
|
|
366
572
|
} = {}, opts = {
|
|
367
573
|
dedupeInterval: 0
|
|
368
574
|
}) {
|
|
369
|
-
if (
|
|
370
|
-
|
|
575
|
+
if (process.env.NODE_ENV !== "production") {
|
|
576
|
+
this.logger?.info({
|
|
577
|
+
methodName: "updateList"
|
|
578
|
+
}, "", {
|
|
579
|
+
optimisticId
|
|
580
|
+
});
|
|
371
581
|
}
|
|
372
|
-
const query = this.getListQuery(apiName, where, opts);
|
|
582
|
+
const query = this.getListQuery(apiName, where ?? {}, orderBy ?? {}, opts);
|
|
373
583
|
this.batch({
|
|
374
584
|
optimisticId
|
|
375
|
-
},
|
|
376
|
-
|
|
585
|
+
}, batch => {
|
|
586
|
+
const objectCacheKeys = this.updateObjects(objects, batch);
|
|
587
|
+
query.updateList(objectCacheKeys, false, "loaded", batch);
|
|
377
588
|
});
|
|
378
589
|
}
|
|
379
590
|
retain(cacheKey) {
|
|
@@ -383,82 +594,4 @@ export class Store {
|
|
|
383
594
|
this.#refCounts.release(cacheKey);
|
|
384
595
|
}
|
|
385
596
|
}
|
|
386
|
-
class ActionApplication {
|
|
387
|
-
constructor(store) {
|
|
388
|
-
this.store = store;
|
|
389
|
-
}
|
|
390
|
-
applyAction = (action, args, {
|
|
391
|
-
optimisticUpdate
|
|
392
|
-
} = {}) => {
|
|
393
|
-
const removeOptimisticResult = runOptimisticJob(this.store, optimisticUpdate);
|
|
394
|
-
return (async () => {
|
|
395
|
-
try {
|
|
396
|
-
// The types for client get confused when we dynamically applyAction so we
|
|
397
|
-
// have to deal with the `any` here and force cast it to what it should be.
|
|
398
|
-
// TODO: Update the types so this doesn't happen!
|
|
399
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
400
|
-
const actionResults = await this.store.client(action).applyAction(args, {
|
|
401
|
-
$returnEdits: true
|
|
402
|
-
});
|
|
403
|
-
if (ACTION_DELAY > 0) {
|
|
404
|
-
// eslint-disable-next-line no-console
|
|
405
|
-
console.log("action done, pausing");
|
|
406
|
-
await delay(ACTION_DELAY);
|
|
407
|
-
// eslint-disable-next-line no-console
|
|
408
|
-
console.log("action done, pausing done");
|
|
409
|
-
}
|
|
410
|
-
await this.#invalidateActionEditResponse(actionResults);
|
|
411
|
-
return actionResults;
|
|
412
|
-
} finally {
|
|
413
|
-
// make sure this happens even if the action fails
|
|
414
|
-
await removeOptimisticResult();
|
|
415
|
-
}
|
|
416
|
-
})();
|
|
417
|
-
};
|
|
418
|
-
#invalidateActionEditResponse = value => {
|
|
419
|
-
const typesToInvalidate = new Set();
|
|
420
|
-
let promisesToWait = [];
|
|
421
|
-
if (value.type === "edits") {
|
|
422
|
-
// TODO we need an backend update for deletes
|
|
423
|
-
for (const obj of value.modifiedObjects) {
|
|
424
|
-
promisesToWait.push(this.store.invalidateObject(obj.objectType, obj.primaryKey));
|
|
425
|
-
}
|
|
426
|
-
for (const obj of value.addedObjects) {
|
|
427
|
-
promisesToWait.push(this.store.invalidateObject(obj.objectType, obj.primaryKey));
|
|
428
|
-
typesToInvalidate.add(obj.objectType);
|
|
429
|
-
}
|
|
430
|
-
promisesToWait = [Promise.allSettled(promisesToWait).then(() => {
|
|
431
|
-
const changes2 = this.#changesFromActionEditResponse(value);
|
|
432
|
-
this.store.maybeRevalidateLists(changes2);
|
|
433
|
-
})];
|
|
434
|
-
} else {
|
|
435
|
-
for (const apiName of value.editedObjectTypes) {
|
|
436
|
-
typesToInvalidate.add(apiName.toString());
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
return Promise.allSettled(promisesToWait).then(() => {
|
|
440
|
-
// after the single object invalidations are done we can decide if we need to updates any lists
|
|
441
|
-
for (const objectType of typesToInvalidate) {
|
|
442
|
-
// TODO make sure this covers individual object loads too
|
|
443
|
-
this.store.invalidateObjectType(objectType);
|
|
444
|
-
}
|
|
445
|
-
return value;
|
|
446
|
-
});
|
|
447
|
-
};
|
|
448
|
-
#changesFromActionEditResponse = value => {
|
|
449
|
-
const changes = createChangedObjects();
|
|
450
|
-
for (const changeType of ["addedObjects", "modifiedObjects"]) {
|
|
451
|
-
for (const {
|
|
452
|
-
objectType,
|
|
453
|
-
primaryKey
|
|
454
|
-
} of value[changeType] ?? []) {
|
|
455
|
-
const obj = this.store.getObject(objectType, primaryKey);
|
|
456
|
-
if (obj) {
|
|
457
|
-
changes[changeType].set(objectType, obj);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return changes;
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
597
|
//# sourceMappingURL=Store.js.map
|