@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.
Files changed (88) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/browser/Logger.js.map +1 -1
  3. package/build/browser/observable/ObservableClient.js.map +1 -1
  4. package/build/browser/observable/internal/ActionApplication.js +102 -0
  5. package/build/browser/observable/internal/ActionApplication.js.map +1 -0
  6. package/build/browser/observable/internal/CacheKey.js +38 -1
  7. package/build/browser/observable/internal/CacheKey.js.map +1 -1
  8. package/build/browser/observable/internal/CacheKeys.js +4 -4
  9. package/build/browser/observable/internal/CacheKeys.js.map +1 -1
  10. package/build/browser/observable/internal/ChangedObjects.js +24 -1
  11. package/build/browser/observable/internal/ChangedObjects.js.map +1 -1
  12. package/build/browser/observable/internal/Layer.js +3 -3
  13. package/build/browser/observable/internal/Layer.js.map +1 -1
  14. package/build/browser/observable/internal/ListQuery.js +188 -66
  15. package/build/browser/observable/internal/ListQuery.js.map +1 -1
  16. package/build/browser/observable/internal/ObjectQuery.js +16 -3
  17. package/build/browser/observable/internal/ObjectQuery.js.map +1 -1
  18. package/build/browser/observable/internal/ObservableClientImpl.js +2 -2
  19. package/build/browser/observable/internal/ObservableClientImpl.js.map +1 -1
  20. package/build/browser/observable/internal/OptimisticJob.js +30 -29
  21. package/build/browser/observable/internal/OptimisticJob.js.map +1 -1
  22. package/build/browser/observable/internal/Query.js +42 -2
  23. package/build/browser/observable/internal/Query.js.map +1 -1
  24. package/build/browser/observable/internal/Store.js +259 -126
  25. package/build/browser/observable/internal/Store.js.map +1 -1
  26. package/build/browser/observable/internal/Store.test.js +416 -76
  27. package/build/browser/observable/internal/Store.test.js.map +1 -1
  28. package/build/browser/observable/internal/testUtils.js +142 -6
  29. package/build/browser/observable/internal/testUtils.js.map +1 -1
  30. package/build/browser/util/UserAgent.js +1 -1
  31. package/build/cjs/index.cjs +1 -1
  32. package/build/cjs/public/unstable-do-not-use.cjs +657 -268
  33. package/build/cjs/public/unstable-do-not-use.cjs.map +1 -1
  34. package/build/cjs/public/unstable-do-not-use.d.cts +13 -4
  35. package/build/esm/Logger.js.map +1 -1
  36. package/build/esm/observable/ObservableClient.js.map +1 -1
  37. package/build/esm/observable/internal/ActionApplication.js +102 -0
  38. package/build/esm/observable/internal/ActionApplication.js.map +1 -0
  39. package/build/esm/observable/internal/CacheKey.js +38 -1
  40. package/build/esm/observable/internal/CacheKey.js.map +1 -1
  41. package/build/esm/observable/internal/CacheKeys.js +4 -4
  42. package/build/esm/observable/internal/CacheKeys.js.map +1 -1
  43. package/build/esm/observable/internal/ChangedObjects.js +24 -1
  44. package/build/esm/observable/internal/ChangedObjects.js.map +1 -1
  45. package/build/esm/observable/internal/Layer.js +3 -3
  46. package/build/esm/observable/internal/Layer.js.map +1 -1
  47. package/build/esm/observable/internal/ListQuery.js +188 -66
  48. package/build/esm/observable/internal/ListQuery.js.map +1 -1
  49. package/build/esm/observable/internal/ObjectQuery.js +16 -3
  50. package/build/esm/observable/internal/ObjectQuery.js.map +1 -1
  51. package/build/esm/observable/internal/ObservableClientImpl.js +2 -2
  52. package/build/esm/observable/internal/ObservableClientImpl.js.map +1 -1
  53. package/build/esm/observable/internal/OptimisticJob.js +30 -29
  54. package/build/esm/observable/internal/OptimisticJob.js.map +1 -1
  55. package/build/esm/observable/internal/Query.js +42 -2
  56. package/build/esm/observable/internal/Query.js.map +1 -1
  57. package/build/esm/observable/internal/Store.js +259 -126
  58. package/build/esm/observable/internal/Store.js.map +1 -1
  59. package/build/esm/observable/internal/Store.test.js +416 -76
  60. package/build/esm/observable/internal/Store.test.js.map +1 -1
  61. package/build/esm/observable/internal/testUtils.js +142 -6
  62. package/build/esm/observable/internal/testUtils.js.map +1 -1
  63. package/build/esm/util/UserAgent.js +1 -1
  64. package/build/types/Logger.d.ts +1 -2
  65. package/build/types/Logger.d.ts.map +1 -1
  66. package/build/types/observable/ObservableClient.d.ts +10 -3
  67. package/build/types/observable/ObservableClient.d.ts.map +1 -1
  68. package/build/types/observable/internal/ActionApplication.d.ts +9 -0
  69. package/build/types/observable/internal/ActionApplication.d.ts.map +1 -0
  70. package/build/types/observable/internal/CacheKeys.d.ts +2 -1
  71. package/build/types/observable/internal/CacheKeys.d.ts.map +1 -1
  72. package/build/types/observable/internal/ChangedObjects.d.ts +6 -2
  73. package/build/types/observable/internal/ChangedObjects.d.ts.map +1 -1
  74. package/build/types/observable/internal/Layer.d.ts +1 -1
  75. package/build/types/observable/internal/Layer.d.ts.map +1 -1
  76. package/build/types/observable/internal/ListQuery.d.ts +18 -17
  77. package/build/types/observable/internal/ListQuery.d.ts.map +1 -1
  78. package/build/types/observable/internal/ObjectQuery.d.ts +3 -3
  79. package/build/types/observable/internal/ObjectQuery.d.ts.map +1 -1
  80. package/build/types/observable/internal/OptimisticJob.d.ts +2 -2
  81. package/build/types/observable/internal/OptimisticJob.d.ts.map +1 -1
  82. package/build/types/observable/internal/Query.d.ts +6 -5
  83. package/build/types/observable/internal/Query.d.ts.map +1 -1
  84. package/build/types/observable/internal/Store.d.ts +47 -19
  85. package/build/types/observable/internal/Store.d.ts.map +1 -1
  86. package/build/types/observable/internal/testUtils.d.ts +13 -3
  87. package/build/types/observable/internal/testUtils.d.ts.map +1 -1
  88. package/package.json +8 -7
@@ -16,11 +16,13 @@
16
16
 
17
17
  import { $ontologyRid, createOffice, Employee, Todo } from "@osdk/client.test.ontology";
18
18
  import { apiServer } from "@osdk/shared.test";
19
+ import chalk from "chalk";
19
20
  import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi, vitest } from "vitest";
20
21
  import { createClient } from "../../createClient.js";
21
22
  import { createOptimisticId } from "./OptimisticId.js";
23
+ import { runOptimisticJob } from "./OptimisticJob.js";
22
24
  import { Store } from "./Store.js";
23
- import { applyCustomMatchers, createClientMockHelper, createDefer, expectSingleListCallAndClear, expectSingleObjectCallAndClear, listPayloadContaining, mockListSubCallback, mockSingleSubCallback, objectPayloadContaining, waitForCall } from "./testUtils.js";
25
+ import { applyCustomMatchers, createClientMockHelper, createDefer, createTestLogger, expectSingleListCallAndClear, expectSingleObjectCallAndClear, listPayloadContaining, mockListSubCallback, mockSingleSubCallback, objectPayloadContaining, waitForCall } from "./testUtils.js";
24
26
  const defer = createDefer();
25
27
  beforeAll(() => {
26
28
  vi.setConfig({
@@ -38,6 +40,19 @@ const ANY_INIT_ENTRY = {
38
40
  lastUpdated: 0,
39
41
  status: "init"
40
42
  };
43
+
44
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
45
+ function fullTaskName(task) {
46
+ return task ? `${fullTaskName(task.suite)} > ${task.name}` : "";
47
+ }
48
+ beforeEach(x => {
49
+ console.log(chalk.bgRed(chalk.white(fullTaskName(x.task))));
50
+ });
51
+
52
+ // helper method to make debugging tests easier
53
+ function testStage(s) {
54
+ console.log(chalk.bgYellow(`Test Stage: ${s}`));
55
+ }
41
56
  applyCustomMatchers();
42
57
  describe(Store, () => {
43
58
  describe("with mock server", () => {
@@ -47,7 +62,9 @@ describe(Store, () => {
47
62
  let mutatedEmployees;
48
63
  beforeAll(async () => {
49
64
  apiServer.listen();
50
- client = createClient("https://stack.palantir.com", $ontologyRid, async () => "myAccessToken");
65
+ client = createClient("https://stack.palantir.com", $ontologyRid, async () => "myAccessToken", {
66
+ logger: createTestLogger({})
67
+ });
51
68
  employeesAsServerReturns = (await client(Employee).fetchPage()).data;
52
69
  mutatedEmployees = [employeesAsServerReturns[0], employeesAsServerReturns[1].$clone({
53
70
  fullName: "foo"
@@ -106,10 +123,12 @@ describe(Store, () => {
106
123
  expectSingleObjectCallAndClear(subFn, emp, "loaded");
107
124
  });
108
125
  it("rolls back to an updated real value", async () => {
109
- vi.useFakeTimers();
110
-
111
126
  // pre-seed the cache with the "real" value
112
- cache.updateList(Employee, {}, employeesAsServerReturns);
127
+ cache.updateList({
128
+ objectType: Employee,
129
+ where: {},
130
+ orderBy: {}
131
+ }, employeesAsServerReturns);
113
132
  const emp = employeesAsServerReturns[0];
114
133
  const empSubFn = mockSingleSubCallback();
115
134
  defer(cache.observeObject(Employee, emp.$primaryKey, {
@@ -117,47 +136,58 @@ describe(Store, () => {
117
136
  }, empSubFn));
118
137
  expectSingleObjectCallAndClear(empSubFn, emp, "loaded");
119
138
  const listSubFn = mockListSubCallback();
120
- defer(cache.observeList(Employee, {}, {
139
+ defer(cache.observeList({
140
+ objectType: Employee,
121
141
  mode: "offline"
122
142
  }, listSubFn));
143
+ await waitForCall(listSubFn, 1);
123
144
  expectSingleListCallAndClear(listSubFn, employeesAsServerReturns);
124
145
  const optimisticEmployee = emp.$clone({
125
146
  fullName: "new name"
126
147
  });
127
148
  const optimisticId = createOptimisticId();
149
+ testStage("optimistic update");
150
+ expect(listSubFn).not.toHaveBeenCalled();
128
151
 
129
152
  // update with an optimistic write
130
153
  cache.updateObject(Employee, optimisticEmployee, {
131
154
  optimisticId
132
155
  });
156
+ testStage("after optimistic update");
133
157
 
134
158
  // expect optimistic write
135
159
  expectSingleObjectCallAndClear(empSubFn, optimisticEmployee);
136
160
 
137
161
  // expect optimistic write to the list
138
- expectSingleListCallAndClear(listSubFn, [optimisticEmployee, ...employeesAsServerReturns.slice(1)]);
162
+ await waitForCall(listSubFn, 1);
163
+ expectSingleListCallAndClear(listSubFn, [optimisticEmployee, ...employeesAsServerReturns.slice(1)], {
164
+ isOptimistic: true,
165
+ status: "loading"
166
+ });
139
167
 
140
168
  // write the real update, via the earlier list definition
141
169
  const truthUpdatedEmployee = emp.$clone({
142
170
  fullName: "real update"
143
171
  });
144
- cache.updateList(Employee, {}, [truthUpdatedEmployee]);
145
-
146
- // we shouldn't expect an update because the top layer has a value
147
- expect(empSubFn).not.toHaveBeenCalled();
148
-
149
- // we do get an update to the list but we still see the optimistic value
150
- // for the object
151
- expectSingleListCallAndClear(listSubFn, [optimisticEmployee]);
172
+ testStage("write real update");
173
+ cache.updateList({
174
+ objectType: Employee,
175
+ where: {},
176
+ orderBy: {}
177
+ }, [truthUpdatedEmployee]);
152
178
 
153
179
  // remove the optimistic write
154
180
  cache.removeLayer(optimisticId);
155
181
 
156
182
  // see the object observation get updated
157
- expectSingleObjectCallAndClear(empSubFn, truthUpdatedEmployee);
183
+ expectSingleObjectCallAndClear(empSubFn, truthUpdatedEmployee, "loaded");
158
184
 
159
185
  // see the list get updated
160
- expectSingleListCallAndClear(listSubFn, [truthUpdatedEmployee]);
186
+ await waitForCall(listSubFn, 1);
187
+ expectSingleListCallAndClear(listSubFn, [truthUpdatedEmployee], {
188
+ status: "loaded",
189
+ isOptimistic: false
190
+ });
161
191
  vi.useRealTimers();
162
192
  });
163
193
  it("rolls back to an updated real value via list", async () => {
@@ -244,39 +274,40 @@ describe(Store, () => {
244
274
  const staleEmp = emp.$clone({
245
275
  fullName: "stale"
246
276
  });
247
- cache.updateList(Employee, {}, [staleEmp]);
277
+ cache.updateList({
278
+ objectType: Employee,
279
+ where: {},
280
+ orderBy: {}
281
+ }, [staleEmp]);
248
282
  const subFn = mockSingleSubCallback();
249
283
  defer(cache.observeObject(Employee, emp.$primaryKey, {
250
284
  mode: "offline"
251
285
  }, subFn));
252
286
  expectSingleObjectCallAndClear(subFn, staleEmp);
253
287
  const subListFn = mockListSubCallback();
254
- defer(cache.observeList(Employee, {}, {
288
+ defer(cache.observeList({
289
+ objectType: Employee,
255
290
  mode: "offline"
256
291
  }, subListFn));
257
- await vi.waitFor(() => expect(subListFn).toHaveBeenCalled());
258
- expect(subListFn).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
259
- resolvedList: [staleEmp],
292
+ await waitForCall(subListFn, 1);
293
+ expectSingleListCallAndClear(subListFn, [staleEmp], {
260
294
  status: "loaded"
261
- }));
262
- subListFn.mockClear();
263
- cache.invalidateList(Employee, {});
264
- await vi.waitFor(() => expect(subListFn).toHaveBeenCalled());
265
- expect(subListFn).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
266
- resolvedList: [staleEmp],
295
+ });
296
+ testStage("invalidate");
297
+ cache.invalidateList({
298
+ objectType: Employee
299
+ });
300
+ testStage("check invalidate");
301
+ await waitForCall(subListFn, 1);
302
+ expectSingleListCallAndClear(subListFn, [staleEmp], {
267
303
  status: "loading"
268
- }));
269
- subListFn.mockClear();
270
- await vi.waitFor(() => expect(subListFn).toHaveBeenCalled());
271
- expect(subListFn).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
272
- resolvedList: employeesAsServerReturns
273
- }));
274
- subListFn.mockClear();
275
- await vi.waitFor(() => expect(subFn).toHaveBeenCalled());
276
- expect(subFn).toHaveBeenCalledExactlyOnceWith(objectPayloadContaining({
277
- status: "loaded",
278
- object: emp
279
- }));
304
+ });
305
+ await waitForCall(subListFn, 1);
306
+ expectSingleListCallAndClear(subListFn, employeesAsServerReturns, {
307
+ status: "loaded"
308
+ });
309
+ await waitForCall(subFn, 1);
310
+ expectSingleObjectCallAndClear(subFn, emp, "loaded");
280
311
  });
281
312
  });
282
313
  describe(".invalidateObjectType", () => {
@@ -291,24 +322,30 @@ describe(Store, () => {
291
322
  const staleEmp = emp.$clone({
292
323
  fullName: "stale"
293
324
  });
294
- cache.updateList(Employee, {}, [staleEmp]);
325
+ cache.updateList({
326
+ objectType: Employee,
327
+ where: {},
328
+ orderBy: {}
329
+ }, [staleEmp]);
295
330
  const subFn = mockSingleSubCallback();
296
331
  defer(cache.observeObject(Employee, emp.$primaryKey, {
297
332
  mode: "offline"
298
333
  }, subFn));
299
334
  expectSingleObjectCallAndClear(subFn, staleEmp);
300
335
  const subListFn = mockListSubCallback();
301
- defer(cache.observeList(Employee, {}, {
336
+ defer(cache.observeList({
337
+ objectType: Employee,
338
+ where: {},
339
+ orderBy: {},
302
340
  mode: "offline"
303
341
  }, subListFn));
304
- await vi.waitFor(() => expect(subListFn).toHaveBeenCalled());
305
- expect(subListFn).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
306
- resolvedList: [staleEmp],
342
+ await waitForCall(subListFn, 1);
343
+ expectSingleListCallAndClear(subListFn, [staleEmp], {
307
344
  status: "loaded"
308
- }));
309
- subListFn.mockClear();
310
- cache.invalidateObjectType(Employee);
311
- await vi.waitFor(() => expect(subListFn).toHaveBeenCalled());
345
+ });
346
+ testStage("invalidate");
347
+ const pInvalidateComplete = cache.invalidateObjectType(Employee, undefined);
348
+ await waitForCall(subListFn, 1);
312
349
  expect(subListFn).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
313
350
  resolvedList: [staleEmp],
314
351
  status: "loading"
@@ -324,6 +361,10 @@ describe(Store, () => {
324
361
  status: "loaded",
325
362
  object: emp
326
363
  }));
364
+
365
+ // we don't need this value to control the test but we want to make sure we don't have
366
+ // any unhandled exceptions upon test completion
367
+ await pInvalidateComplete;
327
368
  });
328
369
  });
329
370
  describe(".observeObject (force)", () => {
@@ -430,7 +471,11 @@ describe(Store, () => {
430
471
  fullName: "not the name"
431
472
  }));
432
473
  expect(subFn).toHaveBeenCalledTimes(1);
433
- cache.updateList(Employee, {}, employeesAsServerReturns);
474
+ cache.updateList({
475
+ objectType: Employee,
476
+ where: {},
477
+ orderBy: {}
478
+ }, employeesAsServerReturns);
434
479
  expect(subFn).toHaveBeenCalledTimes(2);
435
480
  expect(subFn.mock.calls[1][0]).toEqual(objectPayloadContaining({
436
481
  object: emp
@@ -448,7 +493,10 @@ describe(Store, () => {
448
493
  });
449
494
  describe("mode=force", () => {
450
495
  it("initial load", async () => {
451
- defer(cache.observeList(Employee, {}, {
496
+ defer(cache.observeList({
497
+ objectType: Employee,
498
+ where: {},
499
+ orderBy: {},
452
500
  mode: "force"
453
501
  }, listSub1));
454
502
  vitest.runOnlyPendingTimers();
@@ -466,17 +514,20 @@ describe(Store, () => {
466
514
  });
467
515
  it("subsequent load", async () => {
468
516
  // Pre-seed with data the server doesn't return
469
- cache.updateList(Employee, {}, mutatedEmployees);
470
- defer(cache.observeList(Employee, {}, {
517
+ cache.updateList({
518
+ objectType: Employee,
519
+ where: {},
520
+ orderBy: {}
521
+ }, mutatedEmployees);
522
+ defer(cache.observeList({
523
+ objectType: Employee,
471
524
  mode: "force"
472
525
  }, listSub1));
473
- await vi.waitFor(() => expect(listSub1).toHaveBeenCalled());
474
- expect(listSub1).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
475
- resolvedList: mutatedEmployees,
476
- status: "loading"
477
- }));
526
+ await waitForCall(listSub1, 1);
478
527
  const firstLoad = listSub1.mock.calls[0][0];
479
- listSub1.mockClear();
528
+ expectSingleListCallAndClear(listSub1, mutatedEmployees, {
529
+ status: "loading"
530
+ });
480
531
  await vi.waitFor(() => expect(listSub1).toHaveBeenCalled());
481
532
  expect(listSub1).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
482
533
  resolvedList: employeesAsServerReturns,
@@ -488,11 +539,18 @@ describe(Store, () => {
488
539
  });
489
540
  describe("mode = offline", () => {
490
541
  it("updates with list updates", async () => {
491
- defer(cache.observeList(Employee, {}, {
542
+ defer(cache.observeList({
543
+ objectType: Employee,
544
+ where: {},
545
+ orderBy: {},
492
546
  mode: "offline"
493
547
  }, listSub1));
494
548
  expect(listSub1).toHaveBeenCalledTimes(0);
495
- cache.updateList(Employee, {}, employeesAsServerReturns);
549
+ cache.updateList({
550
+ objectType: Employee,
551
+ where: {},
552
+ orderBy: {}
553
+ }, employeesAsServerReturns);
496
554
  vitest.runOnlyPendingTimers();
497
555
  expect(listSub1).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
498
556
  resolvedList: employeesAsServerReturns
@@ -500,18 +558,29 @@ describe(Store, () => {
500
558
  listSub1.mockClear();
501
559
 
502
560
  // list is just now one object
503
- cache.updateList(Employee, {}, [employeesAsServerReturns[0]]);
561
+ cache.updateList({
562
+ objectType: Employee,
563
+ where: {},
564
+ orderBy: {}
565
+ }, [employeesAsServerReturns[0]]);
504
566
  vitest.runOnlyPendingTimers();
505
567
  expect(listSub1).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
506
568
  resolvedList: [employeesAsServerReturns[0]]
507
569
  }));
508
570
  });
509
571
  it("updates with different list updates", async () => {
510
- defer(cache.observeList(Employee, {}, {
572
+ defer(cache.observeList({
573
+ objectType: Employee,
574
+ where: {},
575
+ orderBy: {},
511
576
  mode: "offline"
512
577
  }, listSub1));
513
578
  expect(listSub1).toHaveBeenCalledTimes(0);
514
- cache.updateList(Employee, {}, employeesAsServerReturns);
579
+ cache.updateList({
580
+ objectType: Employee,
581
+ where: {},
582
+ orderBy: {}
583
+ }, employeesAsServerReturns);
515
584
  vitest.runOnlyPendingTimers();
516
585
  expect(listSub1).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
517
586
  resolvedList: employeesAsServerReturns
@@ -519,10 +588,14 @@ describe(Store, () => {
519
588
  listSub1.mockClear();
520
589
 
521
590
  // new where === different list
522
- cache.updateList(Employee, {
523
- employeeId: {
524
- $gt: 0
525
- }
591
+ cache.updateList({
592
+ objectType: Employee,
593
+ where: {
594
+ employeeId: {
595
+ $gt: 0
596
+ }
597
+ },
598
+ orderBy: {}
526
599
  }, mutatedEmployees);
527
600
  vitest.runOnlyPendingTimers();
528
601
 
@@ -542,7 +615,10 @@ describe(Store, () => {
542
615
  });
543
616
  it("works in the solo case", async () => {
544
617
  const listSub = mockListSubCallback();
545
- defer(cache.observeList(Employee, {}, {
618
+ defer(cache.observeList({
619
+ objectType: Employee,
620
+ where: {},
621
+ orderBy: {},
546
622
  mode: "force",
547
623
  pageSize: 1
548
624
  }, listSub));
@@ -579,11 +655,11 @@ describe(Store, () => {
579
655
  describe("with mock client", () => {
580
656
  let client;
581
657
  let mockClient;
582
- let cache;
658
+ let store;
583
659
  beforeEach(async () => {
584
660
  mockClient = createClientMockHelper();
585
661
  client = mockClient.client;
586
- cache = new Store(client);
662
+ store = new Store(client);
587
663
  });
588
664
  describe("actions", () => {
589
665
  it("properly invalidates objects", async () => {
@@ -592,7 +668,7 @@ describe(Store, () => {
592
668
  $apiName: "Todo"
593
669
  });
594
670
  const todoSubFn = mockSingleSubCallback();
595
- defer(cache.observeObject(Todo, 0, {}, todoSubFn));
671
+ defer(store.observeObject(Todo, 0, {}, todoSubFn));
596
672
  await todoSubFn.expectLoadingAndLoaded({
597
673
  loading: objectPayloadContaining({
598
674
  status: "loading",
@@ -619,7 +695,7 @@ describe(Store, () => {
619
695
  $apiName: "Todo",
620
696
  text: "hello there kind sir"
621
697
  });
622
- const actionPromise = cache.applyAction(createOffice, {
698
+ const actionPromise = store.applyAction(createOffice, {
623
699
  officeId: "whatever"
624
700
  });
625
701
  await actionPromise;
@@ -645,7 +721,7 @@ describe(Store, () => {
645
721
  // after the below `observeObject`, the cache will need to load from the server
646
722
  mockClient.mockFetchOneOnce().resolve(fauxObject);
647
723
  const todoSubFn = mockSingleSubCallback();
648
- defer(cache.observeObject(Todo, 0, {}, todoSubFn));
724
+ defer(store.observeObject(Todo, 0, {}, todoSubFn));
649
725
  await todoSubFn.expectLoadingAndLoaded({
650
726
  loading: objectPayloadContaining({
651
727
  status: "loading",
@@ -661,7 +737,7 @@ describe(Store, () => {
661
737
 
662
738
  // at this point we have an observation properly set up
663
739
  const applyActionResult = mockClient.mockApplyActionOnce();
664
- const actionPromise = cache.applyAction(createOffice, {
740
+ const actionPromise = store.applyAction(createOffice, {
665
741
  officeId: "whatever"
666
742
  }, {
667
743
  optimisticUpdate: ctx => {
@@ -695,10 +771,274 @@ describe(Store, () => {
695
771
  }));
696
772
  });
697
773
  });
774
+ describe("orderBy", () => {
775
+ let nextPk = 0;
776
+ const fauxObjectA = {
777
+ $apiName: "Todo",
778
+ $objectType: "Todo",
779
+ $primaryKey: nextPk++,
780
+ $title: "a",
781
+ text: "a"
782
+ };
783
+ const fauxObjectB = {
784
+ $apiName: "Todo",
785
+ $objectType: "Todo",
786
+ $primaryKey: nextPk++,
787
+ $title: "b",
788
+ text: "b"
789
+ };
790
+ const fauxObjectC = {
791
+ $apiName: "Todo",
792
+ $objectType: "Todo",
793
+ $primaryKey: nextPk++,
794
+ $title: "c",
795
+ text: "c"
796
+ };
797
+ const noWhereNoOrderBy = {
798
+ objectType: Todo,
799
+ where: {},
800
+ orderBy: {}
801
+ };
802
+ const noWhereOrderByText = {
803
+ objectType: Todo,
804
+ where: {},
805
+ orderBy: {
806
+ text: "asc"
807
+ }
808
+ };
809
+ const subListUnordered = mockListSubCallback();
810
+ const subListOrdered = mockListSubCallback();
811
+ beforeEach(() => {
812
+ defer(store.observeList({
813
+ ...noWhereNoOrderBy,
814
+ mode: "offline"
815
+ }, subListUnordered));
816
+ expect(subListUnordered).toHaveBeenCalledTimes(0);
817
+ defer(store.observeList({
818
+ ...noWhereOrderByText,
819
+ mode: "offline"
820
+ }, subListOrdered));
821
+ expect(subListOrdered).toHaveBeenCalledTimes(0);
822
+ });
823
+ it("invalidates the correct lists", async () => {
824
+ // for whatever reason, the first list is loaded as [B, A]
825
+ store.updateList(noWhereNoOrderBy, [fauxObjectB, fauxObjectA]);
826
+ await waitForCall(subListUnordered, 1);
827
+ expectSingleListCallAndClear(subListUnordered, [fauxObjectB, fauxObjectA]);
828
+
829
+ // The other list definitely matches on the where clause and we can insert
830
+ // orderBy properly. So this is [A, B]
831
+ await waitForCall(subListOrdered, 1);
832
+ expectSingleListCallAndClear(subListOrdered, [fauxObjectA, fauxObjectB]);
833
+
834
+ // For whatever reason, object B is no longer in the first set (use your imagination)
835
+ // but we have added a C before A. So the first list is [C, A]
836
+ store.updateList({
837
+ objectType: Todo,
838
+ where: {},
839
+ orderBy: {}
840
+ }, [fauxObjectC, fauxObjectA]);
841
+ await waitForCall(subListUnordered, 1);
842
+ expectSingleListCallAndClear(subListUnordered, [fauxObjectC, fauxObjectA]);
843
+
844
+ // Nothing told the system that B was deleted so we can presume it still exists
845
+ // and therefore the second list is now [A, B, C]
846
+ await waitForCall(subListOrdered, 1);
847
+ expectSingleListCallAndClear(subListOrdered, [fauxObjectA, fauxObjectB, fauxObjectC]);
848
+ });
849
+ it("produces proper results with optimistic updates and successful action", async () => {
850
+ const optimisticallyMutatedA = {
851
+ ...fauxObjectA,
852
+ text: "optimistic"
853
+ };
854
+ const pkForOptimistic = nextPk++;
855
+ const optimisticallyCreatedObjectD = {
856
+ "$apiName": "Todo",
857
+ "$objectType": "Todo",
858
+ "$primaryKey": pkForOptimistic,
859
+ "$title": "d",
860
+ "text": "d",
861
+ id: pkForOptimistic
862
+ };
863
+
864
+ // for whatever reason, the first list is loaded as [B, A]
865
+ store.updateList(noWhereNoOrderBy, [fauxObjectB, fauxObjectA]);
866
+ await waitForCall(subListUnordered, 1);
867
+ expectSingleListCallAndClear(subListUnordered, [fauxObjectB, fauxObjectA]);
868
+
869
+ // The other list definitely matches on the where clause and we can insert
870
+ // orderBy properly. So this is [A, B]
871
+ await waitForCall(subListOrdered, 1);
872
+ expectSingleListCallAndClear(subListOrdered, [fauxObjectA, fauxObjectB]);
873
+ testStage("Start");
874
+
875
+ // the optimistic job will call createObject which triggers the `objectFactory2` of the
876
+ // cache context.
877
+ mockClient.mockObjectFactory2Once().resolve([optimisticallyCreatedObjectD]);
878
+
879
+ // Perform something optimistic.
880
+ const removeOptimisticResult = runOptimisticJob(store, b => {
881
+ b.createObject(Todo, pkForOptimistic, {
882
+ id: pkForOptimistic,
883
+ text: "d"
884
+ });
885
+ b.updateObject(optimisticallyMutatedA);
886
+ });
887
+
888
+ // The first list is now [B, A, optimistic]
889
+ await waitForCall(subListUnordered, 1);
890
+ expectSingleListCallAndClear(subListUnordered, [fauxObjectB, optimisticallyMutatedA,
891
+ // same position, new values
892
+ optimisticallyCreatedObjectD], {
893
+ isOptimistic: true
894
+ });
895
+
896
+ // the second list is now [A, B, optimistic]
897
+ await waitForCall(subListOrdered, 1);
898
+ expectSingleListCallAndClear(subListOrdered, [fauxObjectB, optimisticallyCreatedObjectD, optimisticallyMutatedA], {
899
+ isOptimistic: true
900
+ });
901
+
902
+ // Roll back the optimistic update
903
+ await removeOptimisticResult();
904
+
905
+ // The first list is now [B, A]
906
+ await waitForCall(subListUnordered, 1);
907
+ expectSingleListCallAndClear(subListUnordered, [fauxObjectB, fauxObjectA], {
908
+ isOptimistic: false
909
+ });
910
+
911
+ // the second list is now [A, B]
912
+ await waitForCall(subListOrdered, 1);
913
+ expectSingleListCallAndClear(subListOrdered, [fauxObjectA, fauxObjectB], {
914
+ isOptimistic: false
915
+ });
916
+ });
917
+ // I think these are named backwards
918
+ it("produces proper results with optimistic updates and rollback", async () => {
919
+ const pkForOptimistic = nextPk++;
920
+ const optimisticallyCreatedObjectD = {
921
+ "$apiName": "Todo",
922
+ "$objectType": "Todo",
923
+ "$primaryKey": pkForOptimistic,
924
+ "$title": "d",
925
+ "text": "d",
926
+ id: pkForOptimistic
927
+ };
928
+ const optimisticallyMutatedA = {
929
+ ...fauxObjectA,
930
+ text: "optimistic"
931
+ };
932
+ testStage("Initial Setup");
933
+
934
+ // later we will "create" this object
935
+ const createdObjectD = {
936
+ "$apiName": "Todo",
937
+ "$objectType": "Todo",
938
+ "$primaryKey": 9000,
939
+ "$title": "d prime",
940
+ "text": "d prime",
941
+ id: 9000
942
+ };
943
+
944
+ // for whatever reason, the first list is loaded as [B, A]
945
+ store.updateList(noWhereNoOrderBy, [fauxObjectB, fauxObjectA]);
946
+ await waitForCall(subListUnordered, 1);
947
+ expectSingleListCallAndClear(subListUnordered, [fauxObjectB, fauxObjectA]);
948
+
949
+ // The other list definitely matches on the where clause and we can insert
950
+ // orderBy properly. So this is [A, B]
951
+ expectSingleListCallAndClear(subListOrdered, [fauxObjectA, fauxObjectB]);
952
+ testStage("Optimistic Creation");
953
+
954
+ // the optimistic job will call createObject which triggers the `objectFactory2` of the
955
+ // cache context.
956
+
957
+ mockClient.mockObjectFactory2Once().resolve([optimisticallyCreatedObjectD]);
958
+ const mockedApplyAction = mockClient.mockApplyActionOnce();
959
+ testStage("Apply Action");
960
+ const actionPromise = store.applyAction(createOffice, {
961
+ officeId: "5"
962
+ }, {
963
+ optimisticUpdate: b => {
964
+ b.createObject(Todo, pkForOptimistic, {
965
+ id: pkForOptimistic,
966
+ text: "d"
967
+ });
968
+ b.updateObject(optimisticallyMutatedA);
969
+ }
970
+ });
971
+ testStage("Optimistic Updates");
972
+
973
+ // The first list is now [B, A, optimistic]
974
+ await waitForCall(subListUnordered, 1);
975
+ expectSingleListCallAndClear(subListUnordered, [fauxObjectB, optimisticallyMutatedA,
976
+ // same position, new values
977
+ optimisticallyCreatedObjectD], {
978
+ isOptimistic: true
979
+ });
980
+
981
+ // the second list is now [A, B, optimistic]
982
+ await waitForCall(subListOrdered, 1);
983
+ expectSingleListCallAndClear(subListOrdered, [fauxObjectB, optimisticallyCreatedObjectD, optimisticallyMutatedA], {
984
+ isOptimistic: true
985
+ });
986
+ testStage("Resolve Action");
987
+ const modifiedObjectA = {
988
+ ...fauxObjectA,
989
+ text: "a prime"
990
+ };
991
+
992
+ // The action will complete and then revalidate in order...
993
+ mockClient.mockFetchOneOnce(modifiedObjectA.$primaryKey).resolve(modifiedObjectA);
994
+ mockClient.mockFetchOneOnce(createdObjectD.$primaryKey).resolve(createdObjectD);
995
+
996
+ // this order matters!
997
+ // but now we don't need them because we just update lists instead of revalidate when we can
998
+ // const plainList = mockClient.mockFetchPageOnce<Todo>();
999
+ // const orderedList = mockClient.mockFetchPageOnce<Todo>();
1000
+
1001
+ mockedApplyAction.resolve({
1002
+ addedObjects: [{
1003
+ objectType: "Todo",
1004
+ primaryKey: createdObjectD.id
1005
+ }],
1006
+ modifiedObjects: [{
1007
+ objectType: "Todo",
1008
+ primaryKey: fauxObjectA.$primaryKey
1009
+ }]
1010
+ });
1011
+
1012
+ // plainList.resolve({
1013
+ // nextPageToken: undefined,
1014
+ // totalCount: "4",
1015
+ // data: [fauxObjectB, fauxObjectC, modifiedObjectA, createdObjectD],
1016
+ // });
1017
+
1018
+ // orderedList.resolve({
1019
+ // nextPageToken: undefined,
1020
+ // totalCount: "4",
1021
+ // data: [modifiedObjectA, fauxObjectC, createdObjectD],
1022
+ // });
1023
+
1024
+ await actionPromise;
1025
+ await waitForCall(subListUnordered, 1);
1026
+ expectSingleListCallAndClear(subListUnordered, [fauxObjectB,
1027
+ // fauxObjectC,
1028
+ modifiedObjectA, createdObjectD], {
1029
+ isOptimistic: false
1030
+ });
1031
+ await waitForCall(subListOrdered, 1);
1032
+ expectSingleListCallAndClear(subListOrdered, [modifiedObjectA, fauxObjectB, createdObjectD], {
1033
+ isOptimistic: false
1034
+ });
1035
+ });
1036
+ });
698
1037
  });
699
1038
  describe("layers", () => {
700
1039
  it("properly remove", () => {
701
- const store = new Store({});
1040
+ const clientHelper = createClientMockHelper();
1041
+ const store = new Store(clientHelper.client);
702
1042
  const baseObjects = [1, 2].map(i => ({
703
1043
  $primaryKey: i,
704
1044
  $objectType: "Employee",