@isograph/react 0.4.3 → 0.5.0

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 (137) hide show
  1. package/.turbo/turbo-compile-libs.log +2 -2
  2. package/dist/core/FragmentReference.d.ts +4 -2
  3. package/dist/core/FragmentReference.d.ts.map +1 -1
  4. package/dist/core/FragmentReference.js +2 -2
  5. package/dist/core/IsographEnvironment.d.ts +19 -11
  6. package/dist/core/IsographEnvironment.d.ts.map +1 -1
  7. package/dist/core/IsographEnvironment.js +27 -2
  8. package/dist/core/PromiseWrapper.d.ts +13 -7
  9. package/dist/core/PromiseWrapper.d.ts.map +1 -1
  10. package/dist/core/brand.d.ts +17 -0
  11. package/dist/core/brand.d.ts.map +1 -1
  12. package/dist/core/cache.d.ts +10 -7
  13. package/dist/core/cache.d.ts.map +1 -1
  14. package/dist/core/cache.js +102 -74
  15. package/dist/core/check.d.ts +8 -4
  16. package/dist/core/check.d.ts.map +1 -1
  17. package/dist/core/check.js +10 -7
  18. package/dist/core/componentCache.d.ts +1 -1
  19. package/dist/core/componentCache.d.ts.map +1 -1
  20. package/dist/core/componentCache.js +6 -4
  21. package/dist/core/entrypoint.d.ts +17 -7
  22. package/dist/core/entrypoint.d.ts.map +1 -1
  23. package/dist/core/garbageCollection.d.ts +8 -2
  24. package/dist/core/garbageCollection.d.ts.map +1 -1
  25. package/dist/core/garbageCollection.js +36 -14
  26. package/dist/core/logging.d.ts +16 -3
  27. package/dist/core/logging.d.ts.map +1 -1
  28. package/dist/core/makeNetworkRequest.d.ts +4 -2
  29. package/dist/core/makeNetworkRequest.d.ts.map +1 -1
  30. package/dist/core/makeNetworkRequest.js +115 -38
  31. package/dist/core/optimisticProxy.d.ts +59 -0
  32. package/dist/core/optimisticProxy.d.ts.map +1 -0
  33. package/dist/core/optimisticProxy.js +399 -0
  34. package/dist/core/read.d.ts +3 -2
  35. package/dist/core/read.d.ts.map +1 -1
  36. package/dist/core/read.js +158 -123
  37. package/dist/core/reader.d.ts +7 -4
  38. package/dist/core/reader.d.ts.map +1 -1
  39. package/dist/core/startUpdate.d.ts +3 -2
  40. package/dist/core/startUpdate.d.ts.map +1 -1
  41. package/dist/core/startUpdate.js +33 -34
  42. package/dist/index.d.ts +2 -2
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +2 -1
  45. package/dist/loadable-hooks/useClientSideDefer.d.ts +9 -4
  46. package/dist/loadable-hooks/useClientSideDefer.d.ts.map +1 -1
  47. package/dist/loadable-hooks/useClientSideDefer.js +34 -1
  48. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts +5 -3
  49. package/dist/loadable-hooks/useConnectionSpecPagination.d.ts.map +1 -1
  50. package/dist/loadable-hooks/useConnectionSpecPagination.js +27 -13
  51. package/dist/loadable-hooks/useImperativeLoadableField.d.ts +1 -1
  52. package/dist/loadable-hooks/useImperativeLoadableField.d.ts.map +1 -1
  53. package/dist/loadable-hooks/useSkipLimitPagination.d.ts +1 -1
  54. package/dist/loadable-hooks/useSkipLimitPagination.d.ts.map +1 -1
  55. package/dist/loadable-hooks/useSkipLimitPagination.js +1 -1
  56. package/dist/react/FragmentReader.d.ts +2 -1
  57. package/dist/react/FragmentReader.d.ts.map +1 -1
  58. package/dist/react/FragmentRenderer.d.ts +2 -1
  59. package/dist/react/FragmentRenderer.d.ts.map +1 -1
  60. package/dist/react/LoadableFieldReader.d.ts +9 -3
  61. package/dist/react/LoadableFieldReader.d.ts.map +1 -1
  62. package/dist/react/LoadableFieldReader.js +40 -1
  63. package/dist/react/LoadableFieldRenderer.d.ts +9 -3
  64. package/dist/react/LoadableFieldRenderer.d.ts.map +1 -1
  65. package/dist/react/LoadableFieldRenderer.js +36 -1
  66. package/dist/react/useImperativeReference.d.ts +4 -3
  67. package/dist/react/useImperativeReference.d.ts.map +1 -1
  68. package/dist/react/useImperativeReference.js +3 -5
  69. package/dist/react/useLazyReference.d.ts +2 -1
  70. package/dist/react/useLazyReference.d.ts.map +1 -1
  71. package/dist/react/useReadAndSubscribe.d.ts.map +1 -1
  72. package/dist/react/useReadAndSubscribe.js +1 -3
  73. package/dist/react/useResult.d.ts.map +1 -1
  74. package/dist/react/useResult.js +6 -5
  75. package/package.json +16 -17
  76. package/src/core/FragmentReference.ts +10 -4
  77. package/src/core/IsographEnvironment.ts +59 -13
  78. package/src/core/PromiseWrapper.ts +14 -7
  79. package/src/core/brand.ts +18 -0
  80. package/src/core/cache.ts +186 -91
  81. package/src/core/check.ts +21 -10
  82. package/src/core/componentCache.ts +8 -4
  83. package/src/core/entrypoint.ts +35 -6
  84. package/src/core/garbageCollection.ts +61 -19
  85. package/src/core/logging.ts +15 -3
  86. package/src/core/makeNetworkRequest.ts +307 -74
  87. package/src/core/optimisticProxy.ts +563 -0
  88. package/src/core/read.ts +233 -163
  89. package/src/core/reader.ts +10 -6
  90. package/src/core/startUpdate.ts +45 -30
  91. package/src/index.ts +2 -1
  92. package/src/loadable-hooks/useClientSideDefer.ts +76 -26
  93. package/src/loadable-hooks/useConnectionSpecPagination.ts +34 -17
  94. package/src/loadable-hooks/useImperativeLoadableField.ts +2 -2
  95. package/src/loadable-hooks/useSkipLimitPagination.ts +2 -3
  96. package/src/react/FragmentReader.tsx +3 -1
  97. package/src/react/FragmentRenderer.tsx +8 -1
  98. package/src/react/LoadableFieldReader.tsx +123 -12
  99. package/src/react/LoadableFieldRenderer.tsx +122 -12
  100. package/src/react/useImperativeReference.ts +20 -11
  101. package/src/react/useLazyReference.ts +17 -6
  102. package/src/react/useReadAndSubscribe.ts +1 -8
  103. package/src/react/useResult.ts +9 -11
  104. package/src/tests/__isograph/Node/asEconomist/resolver_reader.ts +1 -1
  105. package/src/tests/__isograph/Query/linkedUpdate/entrypoint.ts +3 -1
  106. package/src/tests/__isograph/Query/linkedUpdate/raw_response_type.ts +13 -0
  107. package/src/tests/__isograph/Query/linkedUpdate/resolver_reader.ts +1 -1
  108. package/src/tests/__isograph/Query/meName/entrypoint.ts +3 -1
  109. package/src/tests/__isograph/Query/meName/raw_response_type.ts +7 -0
  110. package/src/tests/__isograph/Query/meName/resolver_reader.ts +1 -1
  111. package/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +3 -1
  112. package/src/tests/__isograph/Query/meNameSuccessor/raw_response_type.ts +14 -0
  113. package/src/tests/__isograph/Query/meNameSuccessor/resolver_reader.ts +1 -1
  114. package/src/tests/__isograph/Query/nodeField/entrypoint.ts +3 -1
  115. package/src/tests/__isograph/Query/nodeField/raw_response_type.ts +7 -0
  116. package/src/tests/__isograph/Query/nodeField/resolver_reader.ts +1 -1
  117. package/src/tests/__isograph/Query/normalizeUndefinedField/entrypoint.ts +33 -0
  118. package/src/tests/__isograph/Query/normalizeUndefinedField/normalization_ast.ts +25 -0
  119. package/src/tests/__isograph/Query/normalizeUndefinedField/output_type.ts +3 -0
  120. package/src/tests/__isograph/Query/normalizeUndefinedField/param_type.ts +9 -0
  121. package/src/tests/__isograph/Query/normalizeUndefinedField/query_text.ts +6 -0
  122. package/src/tests/__isograph/Query/normalizeUndefinedField/raw_response_type.ts +7 -0
  123. package/src/tests/__isograph/Query/normalizeUndefinedField/resolver_reader.ts +38 -0
  124. package/src/tests/__isograph/Query/startUpdate/entrypoint.ts +3 -1
  125. package/src/tests/__isograph/Query/startUpdate/raw_response_type.ts +8 -0
  126. package/src/tests/__isograph/Query/startUpdate/resolver_reader.ts +1 -1
  127. package/src/tests/__isograph/Query/subquery/entrypoint.ts +3 -1
  128. package/src/tests/__isograph/Query/subquery/raw_response_type.ts +9 -0
  129. package/src/tests/__isograph/Query/subquery/resolver_reader.ts +1 -1
  130. package/src/tests/__isograph/iso.ts +11 -1
  131. package/src/tests/garbageCollection.test.ts +8 -5
  132. package/src/tests/meNameSuccessor.ts +6 -3
  133. package/src/tests/nodeQuery.ts +4 -2
  134. package/src/tests/normalizeData.test.ts +89 -15
  135. package/src/tests/optimisticProxy.test.ts +860 -0
  136. package/src/tests/startUpdate.test.ts +7 -5
  137. package/src/tests/__isograph/Economist/__link/output_type.ts +0 -2
@@ -0,0 +1,860 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { callSubscriptions, type EncounteredIds } from '../core/cache';
3
+ import {
4
+ createIsographEnvironment,
5
+ createIsographStore,
6
+ type IsographEnvironment,
7
+ type StoreLayerData,
8
+ } from '../core/IsographEnvironment';
9
+ import {
10
+ addNetworkResponseStoreLayer as addNetworkResponseStoreLayerInner,
11
+ addOptimisticUpdaterStoreLayer as addOptimisticStoreLayerInner,
12
+ addStartUpdateStoreLayer as addStartUpdateStoreLayerInner,
13
+ getStoreRecordProxy,
14
+ revertOptimisticStoreLayerAndMaybeReplace,
15
+ type OptimisticStoreLayer,
16
+ type StoreLayer,
17
+ } from '../core/optimisticProxy';
18
+
19
+ vi.mock(import('../core/cache'), { spy: true });
20
+
21
+ const CHANGES = new Map([['Query', new Set(['__ROOT'])]]);
22
+ const NO_CHANGES = new Map();
23
+
24
+ describe('optimisticLayer', () => {
25
+ let environment: ReturnType<typeof createIsographEnvironment>;
26
+
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ const networkFunction = vi
30
+ .fn()
31
+ .mockRejectedValue(new Error('Fetch failed'));
32
+ environment = createIsographEnvironment(
33
+ createIsographStore(),
34
+ networkFunction,
35
+ );
36
+ addNetworkResponseStoreLayer(environment, 0);
37
+ });
38
+
39
+ describe('addNetworkResponseStoreLayer', () => {
40
+ test('has parent BaseStoreLayer', () => {
41
+ expect(environment.store).toMatchObject({
42
+ kind: 'BaseStoreLayer',
43
+ });
44
+ });
45
+
46
+ test('merge', () => {
47
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
48
+ addNetworkResponseStoreLayer(environment, 3);
49
+ addNetworkResponseStoreLayer(environment, 4);
50
+
51
+ expect(environment.store).toMatchObject({
52
+ kind: 'NetworkResponseStoreLayer',
53
+ parentStoreLayer: {
54
+ kind: 'OptimisticUpdaterStoreLayer',
55
+ parentStoreLayer: {
56
+ kind: 'BaseStoreLayer',
57
+ },
58
+ },
59
+ });
60
+
61
+ expect(
62
+ getStoreRecordProxy(environment.store, {
63
+ __link: '__ROOT',
64
+ __typename: 'Query',
65
+ })?.counter,
66
+ ).toBe(4);
67
+ });
68
+ });
69
+
70
+ describe('addStartUpdateStoreLayer', () => {
71
+ test('merge', () => {
72
+ addOptimisticStoreLayer(environment, () => 1);
73
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
74
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
75
+
76
+ expect(environment.store).toMatchObject({
77
+ kind: 'StartUpdateStoreLayer',
78
+ parentStoreLayer: {
79
+ kind: 'OptimisticUpdaterStoreLayer',
80
+ parentStoreLayer: {
81
+ kind: 'BaseStoreLayer',
82
+ },
83
+ },
84
+ });
85
+ expect(
86
+ getStoreRecordProxy(environment.store, {
87
+ __link: '__ROOT',
88
+ __typename: 'Query',
89
+ })?.counter,
90
+ ).toBe(3);
91
+ });
92
+ });
93
+
94
+ describe('addOptimisticStoreLayer', () => {
95
+ test('add tree nodes', () => {
96
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
97
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
98
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
99
+
100
+ expect(
101
+ getStoreRecordProxy(environment.store, {
102
+ __link: '__ROOT',
103
+ __typename: 'Query',
104
+ })?.counter,
105
+ ).toBe(3);
106
+ });
107
+ });
108
+
109
+ describe('replaceOptimisticStoreLayerWithNetworkResponseStoreLayer', () => {
110
+ describe('subscriptions', () => {
111
+ test('calls if network response differs', () => {
112
+ const revert = addOptimisticStoreLayer(environment, () => 1);
113
+
114
+ revert(2);
115
+
116
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
117
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
118
+ 1,
119
+ expect.anything(),
120
+ CHANGES,
121
+ );
122
+ });
123
+
124
+ test("doesn't call subscriptions if no changes", () => {
125
+ const revert = addOptimisticStoreLayer(environment, () => 1);
126
+
127
+ revert(1);
128
+
129
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
130
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
131
+ 1,
132
+ expect.anything(),
133
+ NO_CHANGES,
134
+ );
135
+ });
136
+
137
+ test('calls subscriptions if nodes update different fields', () => {
138
+ const node = addOptimisticStoreLayerInner(
139
+ environment.store,
140
+ (storeLayer) => {
141
+ ignoreReadonly(storeLayer).data = {
142
+ Query: {
143
+ __ROOT: {
144
+ surname: 'foo',
145
+ },
146
+ },
147
+ };
148
+ return CHANGES;
149
+ },
150
+ );
151
+ environment.store = node;
152
+ environment.store = addNetworkResponseStoreLayerInner(
153
+ environment.store,
154
+ );
155
+ ignoreReadonly(environment.store).data = {
156
+ Query: {
157
+ __ROOT: {
158
+ name: 'foo',
159
+ },
160
+ },
161
+ };
162
+
163
+ revertOptimisticStoreLayerAndMaybeReplace(
164
+ environment,
165
+ node,
166
+ (storeLayer) => {
167
+ ignoreReadonly(storeLayer).data = {
168
+ Query: { __ROOT: { surname: 'bar' } },
169
+ };
170
+ return CHANGES;
171
+ },
172
+ );
173
+
174
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
175
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
176
+ 1,
177
+ expect.anything(),
178
+ CHANGES,
179
+ );
180
+ });
181
+
182
+ test('calls subscriptions if reverted unrelated field', () => {
183
+ const node = addOptimisticStoreLayerInner(
184
+ environment.store,
185
+ (storeLayer) => {
186
+ ignoreReadonly(storeLayer).data = {
187
+ Pet: { 1: { surname: 'foo' } },
188
+ };
189
+ return new Map([['Pet', new Set(['1'])]]);
190
+ },
191
+ );
192
+ environment.store = node;
193
+
194
+ environment.store = addNetworkResponseStoreLayerInner(
195
+ environment.store,
196
+ );
197
+ ignoreReadonly(environment.store).data = {
198
+ Query: { __ROOT: { name: 'foo' } },
199
+ };
200
+
201
+ revertOptimisticStoreLayerAndMaybeReplace(
202
+ environment,
203
+ node,
204
+ (storeLayer) => {
205
+ ignoreReadonly(storeLayer).data = {};
206
+ return new Map();
207
+ },
208
+ );
209
+
210
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
211
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
212
+ 1,
213
+ expect.anything(),
214
+ new Map([['Pet', new Set(['1'])]]),
215
+ );
216
+ });
217
+ });
218
+
219
+ describe('with parent BaseStoreLayer', () => {
220
+ test('merges ', () => {
221
+ const revert = addOptimisticStoreLayer(
222
+ environment,
223
+ (counter) => counter + 1,
224
+ );
225
+
226
+ revert(5);
227
+
228
+ expect(environment.store).toMatchObject({
229
+ kind: 'BaseStoreLayer',
230
+ });
231
+
232
+ expect(
233
+ getStoreRecordProxy(environment.store, {
234
+ __link: '__ROOT',
235
+ __typename: 'Query',
236
+ })?.counter,
237
+ ).toBe(5);
238
+ });
239
+
240
+ test('merges children', () => {
241
+ const revert = addOptimisticStoreLayer(
242
+ environment,
243
+ (counter) => counter + 1,
244
+ );
245
+ addNetworkResponseStoreLayer(environment, 12);
246
+
247
+ revert(5);
248
+
249
+ expect(environment.store).toMatchObject({
250
+ kind: 'BaseStoreLayer',
251
+ });
252
+
253
+ expect(
254
+ getStoreRecordProxy(environment.store, {
255
+ __link: '__ROOT',
256
+ __typename: 'Query',
257
+ })?.counter,
258
+ ).toBe(12);
259
+ });
260
+
261
+ test('merges children and stops at optimistic child node', () => {
262
+ const revert = addOptimisticStoreLayer(
263
+ environment,
264
+ (counter) => counter + 1,
265
+ );
266
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
267
+ addNetworkResponseStoreLayer(environment, 12);
268
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
269
+
270
+ revert(4);
271
+
272
+ expect(environment.store).toMatchObject({
273
+ kind: 'OptimisticUpdaterStoreLayer',
274
+ parentStoreLayer: {
275
+ kind: 'BaseStoreLayer',
276
+ },
277
+ });
278
+ expect(
279
+ getStoreRecordProxy(environment.store, {
280
+ __link: '__ROOT',
281
+ __typename: 'Query',
282
+ })?.counter,
283
+ ).toBe(13);
284
+ });
285
+
286
+ test('reexecutes updates while merging children', () => {
287
+ const revert = addOptimisticStoreLayer(
288
+ environment,
289
+ (counter) => counter + 1,
290
+ );
291
+ addStartUpdateStoreLayer(environment, (counter) => counter * 2);
292
+ addStartUpdateStoreLayer(environment, (counter) => counter + 7);
293
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
294
+
295
+ revert(4);
296
+
297
+ expect(environment.store).toMatchObject({
298
+ kind: 'OptimisticUpdaterStoreLayer',
299
+ parentStoreLayer: {
300
+ kind: 'BaseStoreLayer',
301
+ },
302
+ });
303
+ expect(
304
+ getStoreRecordProxy(environment.store, {
305
+ __link: '__ROOT',
306
+ __typename: 'Query',
307
+ })?.counter,
308
+ ).toBe(16);
309
+ });
310
+
311
+ test('stops at optimistic child node', () => {
312
+ const revert = addOptimisticStoreLayer(
313
+ environment,
314
+ (counter) => counter + 1,
315
+ );
316
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
317
+
318
+ revert(5);
319
+
320
+ expect(environment.store).toMatchObject({
321
+ kind: 'OptimisticUpdaterStoreLayer',
322
+ parentStoreLayer: {
323
+ kind: 'BaseStoreLayer',
324
+ },
325
+ });
326
+
327
+ expect(
328
+ getStoreRecordProxy(environment.store, {
329
+ __link: '__ROOT',
330
+ __typename: 'Query',
331
+ })?.counter,
332
+ ).toBe(6);
333
+ });
334
+ });
335
+
336
+ describe('adjacent with NetworkResponseStoreLayer', () => {
337
+ test('merges with parent NetworkResponseStoreLayer', () => {
338
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
339
+ addNetworkResponseStoreLayer(environment, 10);
340
+ const revert = addOptimisticStoreLayer(
341
+ environment,
342
+ (counter) => counter + 1,
343
+ );
344
+ addNetworkResponseStoreLayer(environment, 12);
345
+ revert(5);
346
+
347
+ expect(environment.store).toMatchObject({
348
+ kind: 'NetworkResponseStoreLayer',
349
+ parentStoreLayer: {
350
+ kind: 'OptimisticUpdaterStoreLayer',
351
+ parentStoreLayer: {
352
+ kind: 'BaseStoreLayer',
353
+ },
354
+ },
355
+ });
356
+
357
+ expect(
358
+ getStoreRecordProxy(environment.store, {
359
+ __link: '__ROOT',
360
+ __typename: 'Query',
361
+ })?.counter,
362
+ ).toBe(12);
363
+ });
364
+
365
+ test('merges child NetworkResponseStoreLayer', () => {
366
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
367
+ const revert = addOptimisticStoreLayer(
368
+ environment,
369
+ (counter) => counter + 1,
370
+ );
371
+ addNetworkResponseStoreLayer(environment, 12);
372
+ revert(5);
373
+
374
+ expect(environment.store).toMatchObject({
375
+ kind: 'NetworkResponseStoreLayer',
376
+ parentStoreLayer: {
377
+ kind: 'OptimisticUpdaterStoreLayer',
378
+ parentStoreLayer: {
379
+ kind: 'BaseStoreLayer',
380
+ },
381
+ },
382
+ });
383
+
384
+ expect(
385
+ getStoreRecordProxy(environment.store, {
386
+ __link: '__ROOT',
387
+ __typename: 'Query',
388
+ })?.counter,
389
+ ).toBe(12);
390
+ });
391
+
392
+ test('merges between two NetworkResponseStoreLayers', () => {
393
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
394
+ addNetworkResponseStoreLayer(environment, 10);
395
+ const revert = addOptimisticStoreLayer(
396
+ environment,
397
+ (counter) => counter + 1,
398
+ );
399
+ addNetworkResponseStoreLayer(environment, 12);
400
+ revert(5);
401
+
402
+ expect(environment.store).toMatchObject({
403
+ kind: 'NetworkResponseStoreLayer',
404
+ parentStoreLayer: {
405
+ kind: 'OptimisticUpdaterStoreLayer',
406
+ parentStoreLayer: {
407
+ kind: 'BaseStoreLayer',
408
+ },
409
+ },
410
+ });
411
+
412
+ expect(
413
+ getStoreRecordProxy(environment.store, {
414
+ __link: '__ROOT',
415
+ __typename: 'Query',
416
+ })?.counter,
417
+ ).toBe(12);
418
+ });
419
+ });
420
+
421
+ describe('has parent OptimisticStoreLayer', () => {
422
+ test('replaces self with network response', () => {
423
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
424
+ const revert = addOptimisticStoreLayer(
425
+ environment,
426
+ (counter) => counter + 1,
427
+ );
428
+
429
+ revert(5);
430
+
431
+ expect(environment.store).toMatchObject({
432
+ kind: 'NetworkResponseStoreLayer',
433
+ parentStoreLayer: {
434
+ kind: 'OptimisticUpdaterStoreLayer',
435
+ parentStoreLayer: {
436
+ kind: 'BaseStoreLayer',
437
+ },
438
+ },
439
+ });
440
+
441
+ expect(
442
+ getStoreRecordProxy(environment.store, {
443
+ __link: '__ROOT',
444
+ __typename: 'Query',
445
+ })?.counter,
446
+ ).toBe(5);
447
+ });
448
+ test("doesn't merge child nodes if has parent nodes", () => {
449
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
450
+ const revert = addOptimisticStoreLayer(
451
+ environment,
452
+ (counter) => counter + 1,
453
+ );
454
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
455
+
456
+ revert(5);
457
+
458
+ expect(environment.store).toMatchObject({
459
+ kind: 'StartUpdateStoreLayer',
460
+ parentStoreLayer: {
461
+ kind: 'NetworkResponseStoreLayer',
462
+ parentStoreLayer: {
463
+ kind: 'OptimisticUpdaterStoreLayer',
464
+ parentStoreLayer: {
465
+ kind: 'BaseStoreLayer',
466
+ },
467
+ },
468
+ },
469
+ });
470
+
471
+ expect(
472
+ getStoreRecordProxy(environment.store, {
473
+ __link: '__ROOT',
474
+ __typename: 'Query',
475
+ })?.counter,
476
+ ).toBe(6);
477
+ });
478
+ });
479
+
480
+ describe('network error', () => {
481
+ describe('subscriptions', () => {
482
+ test('calls if network response differs', () => {
483
+ const revert = addOptimisticStoreLayer(environment, () => 1);
484
+
485
+ revert(null);
486
+
487
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
488
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
489
+ 1,
490
+ expect.anything(),
491
+ CHANGES,
492
+ );
493
+ });
494
+
495
+ test('calls subscriptions if reverted unrelated field', () => {
496
+ const node = addOptimisticStoreLayerInner(
497
+ environment.store,
498
+ (storeLayer) => {
499
+ ignoreReadonly(storeLayer).data = {
500
+ Pet: { 1: { surname: 'foo' } },
501
+ };
502
+ return new Map([['Pet', new Set(['1'])]]);
503
+ },
504
+ );
505
+ environment.store = node;
506
+
507
+ environment.store = addNetworkResponseStoreLayerInner(
508
+ environment.store,
509
+ );
510
+ ignoreReadonly(environment.store).data = {
511
+ Query: { __ROOT: { name: 'foo' } },
512
+ };
513
+
514
+ revert(environment, node, null);
515
+
516
+ expect(callSubscriptions).toHaveBeenCalledTimes(1);
517
+ expect(callSubscriptions).toHaveBeenNthCalledWith(
518
+ 1,
519
+ expect.anything(),
520
+ new Map([['Pet', new Set(['1'])]]),
521
+ );
522
+ });
523
+ });
524
+
525
+ describe('with parent BaseStoreLayer', () => {
526
+ test('removes self ', () => {
527
+ const revert = addOptimisticStoreLayer(
528
+ environment,
529
+ (counter) => counter + 1,
530
+ );
531
+
532
+ revert(null);
533
+
534
+ expect(environment.store).toMatchObject({
535
+ kind: 'BaseStoreLayer',
536
+ });
537
+
538
+ expect(
539
+ getStoreRecordProxy(environment.store, {
540
+ __link: '__ROOT',
541
+ __typename: 'Query',
542
+ })?.counter,
543
+ ).toBe(0);
544
+ });
545
+
546
+ test('merges children', () => {
547
+ const revert = addOptimisticStoreLayer(
548
+ environment,
549
+ (counter) => counter + 1,
550
+ );
551
+ addNetworkResponseStoreLayer(environment, 12);
552
+
553
+ revert(null);
554
+
555
+ expect(environment.store).toMatchObject({
556
+ kind: 'BaseStoreLayer',
557
+ });
558
+
559
+ expect(
560
+ getStoreRecordProxy(environment.store, {
561
+ __link: '__ROOT',
562
+ __typename: 'Query',
563
+ })?.counter,
564
+ ).toBe(12);
565
+ });
566
+
567
+ test('merges children and stops at optimistic child node', () => {
568
+ const revert = addOptimisticStoreLayer(
569
+ environment,
570
+ (counter) => counter + 1,
571
+ );
572
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
573
+ addNetworkResponseStoreLayer(environment, 12);
574
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
575
+
576
+ revert(null);
577
+
578
+ expect(environment.store).toMatchObject({
579
+ kind: 'OptimisticUpdaterStoreLayer',
580
+ parentStoreLayer: {
581
+ kind: 'BaseStoreLayer',
582
+ },
583
+ });
584
+ expect(
585
+ getStoreRecordProxy(environment.store, {
586
+ __link: '__ROOT',
587
+ __typename: 'Query',
588
+ })?.counter,
589
+ ).toBe(13);
590
+ });
591
+
592
+ test('reexecutes updates while merging children', () => {
593
+ const revert = addOptimisticStoreLayer(
594
+ environment,
595
+ (counter) => counter + 1,
596
+ );
597
+ addStartUpdateStoreLayer(environment, (counter) => counter + 2);
598
+ addStartUpdateStoreLayer(environment, (counter) => counter * 7);
599
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
600
+
601
+ revert(null);
602
+
603
+ expect(environment.store).toMatchObject({
604
+ kind: 'OptimisticUpdaterStoreLayer',
605
+ parentStoreLayer: {
606
+ kind: 'BaseStoreLayer',
607
+ },
608
+ });
609
+ expect(
610
+ getStoreRecordProxy(environment.store, {
611
+ __link: '__ROOT',
612
+ __typename: 'Query',
613
+ })?.counter,
614
+ ).toBe(15);
615
+ });
616
+
617
+ test('stops at optimistic child node', () => {
618
+ const revert = addOptimisticStoreLayer(
619
+ environment,
620
+ (counter) => counter + 1,
621
+ );
622
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
623
+
624
+ revert(null);
625
+
626
+ expect(environment.store).toMatchObject({
627
+ kind: 'OptimisticUpdaterStoreLayer',
628
+ parentStoreLayer: {
629
+ kind: 'BaseStoreLayer',
630
+ },
631
+ });
632
+
633
+ expect(
634
+ getStoreRecordProxy(environment.store, {
635
+ __link: '__ROOT',
636
+ __typename: 'Query',
637
+ })?.counter,
638
+ ).toBe(1);
639
+ });
640
+ });
641
+
642
+ describe('adjacent with NetworkResponseStoreLayer', () => {
643
+ test('merges with parent NetworkResponseStoreLayer', () => {
644
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
645
+ addNetworkResponseStoreLayer(environment, 10);
646
+ const revert = addOptimisticStoreLayer(
647
+ environment,
648
+ (counter) => counter + 1,
649
+ );
650
+ addNetworkResponseStoreLayer(environment, 12);
651
+ revert(null);
652
+
653
+ expect(environment.store).toMatchObject({
654
+ kind: 'NetworkResponseStoreLayer',
655
+ parentStoreLayer: {
656
+ kind: 'OptimisticUpdaterStoreLayer',
657
+ parentStoreLayer: {
658
+ kind: 'BaseStoreLayer',
659
+ },
660
+ },
661
+ });
662
+
663
+ expect(
664
+ getStoreRecordProxy(environment.store, {
665
+ __link: '__ROOT',
666
+ __typename: 'Query',
667
+ })?.counter,
668
+ ).toBe(12);
669
+ });
670
+
671
+ test('merges child NetworkResponseStoreLayer', () => {
672
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
673
+ const revert = addOptimisticStoreLayer(
674
+ environment,
675
+ (counter) => counter + 1,
676
+ );
677
+ addNetworkResponseStoreLayer(environment, 12);
678
+ revert(null);
679
+
680
+ expect(environment.store).toMatchObject({
681
+ kind: 'NetworkResponseStoreLayer',
682
+ parentStoreLayer: {
683
+ kind: 'OptimisticUpdaterStoreLayer',
684
+ parentStoreLayer: {
685
+ kind: 'BaseStoreLayer',
686
+ },
687
+ },
688
+ });
689
+
690
+ expect(
691
+ getStoreRecordProxy(environment.store, {
692
+ __link: '__ROOT',
693
+ __typename: 'Query',
694
+ })?.counter,
695
+ ).toBe(12);
696
+ });
697
+
698
+ test('removes self and merges two adjacent NetworkResponseStoreLayers', () => {
699
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
700
+ addNetworkResponseStoreLayer(environment, 10);
701
+ const revert = addOptimisticStoreLayer(
702
+ environment,
703
+ (counter) => counter + 1,
704
+ );
705
+ addNetworkResponseStoreLayer(environment, 12);
706
+ revert(null);
707
+
708
+ expect(environment.store).toMatchObject({
709
+ kind: 'NetworkResponseStoreLayer',
710
+ parentStoreLayer: {
711
+ kind: 'OptimisticUpdaterStoreLayer',
712
+ parentStoreLayer: {
713
+ kind: 'BaseStoreLayer',
714
+ },
715
+ },
716
+ });
717
+
718
+ expect(
719
+ getStoreRecordProxy(environment.store, {
720
+ __link: '__ROOT',
721
+ __typename: 'Query',
722
+ })?.counter,
723
+ ).toBe(12);
724
+ });
725
+ });
726
+
727
+ describe('has parent OptimisticStoreLayer', () => {
728
+ test('removes self', () => {
729
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
730
+ const revert = addOptimisticStoreLayer(
731
+ environment,
732
+ (counter) => counter + 1,
733
+ );
734
+
735
+ revert(null);
736
+
737
+ expect(environment.store).toMatchObject({
738
+ kind: 'OptimisticUpdaterStoreLayer',
739
+ parentStoreLayer: {
740
+ kind: 'BaseStoreLayer',
741
+ },
742
+ });
743
+
744
+ expect(
745
+ getStoreRecordProxy(environment.store, {
746
+ __link: '__ROOT',
747
+ __typename: 'Query',
748
+ })?.counter,
749
+ ).toBe(1);
750
+ });
751
+
752
+ test("doesn't merge child nodes if has parent nodes", () => {
753
+ addOptimisticStoreLayer(environment, (counter) => counter + 1);
754
+ const revert = addOptimisticStoreLayer(
755
+ environment,
756
+ (counter) => counter + 1,
757
+ );
758
+ addStartUpdateStoreLayer(environment, (counter) => counter + 1);
759
+
760
+ revert(null);
761
+
762
+ expect(environment.store).toMatchObject({
763
+ kind: 'StartUpdateStoreLayer',
764
+ parentStoreLayer: {
765
+ kind: 'OptimisticUpdaterStoreLayer',
766
+ parentStoreLayer: {
767
+ kind: 'BaseStoreLayer',
768
+ },
769
+ },
770
+ });
771
+
772
+ expect(
773
+ getStoreRecordProxy(environment.store, {
774
+ __link: '__ROOT',
775
+ __typename: 'Query',
776
+ })?.counter,
777
+ ).toBe(2);
778
+ });
779
+ });
780
+ });
781
+ });
782
+
783
+ // utils
784
+ function addNetworkResponseStoreLayer(
785
+ environment: IsographEnvironment,
786
+ counter: number,
787
+ ) {
788
+ environment.store = addNetworkResponseStoreLayerInner(environment.store);
789
+ update(environment.store, () => counter);
790
+ }
791
+
792
+ function addOptimisticStoreLayer(
793
+ environment: IsographEnvironment,
794
+ updater: (prev: number) => number,
795
+ ) {
796
+ const node = addOptimisticStoreLayerInner(
797
+ environment.store,
798
+ (storeLayer) => {
799
+ return update(storeLayer, updater);
800
+ },
801
+ );
802
+ environment.store = node;
803
+ return (counter: null | number) => {
804
+ revert(environment, node, counter);
805
+ };
806
+ }
807
+
808
+ function revert(
809
+ environment: IsographEnvironment,
810
+ node: OptimisticStoreLayer,
811
+ counter: null | number,
812
+ ) {
813
+ return revertOptimisticStoreLayerAndMaybeReplace(
814
+ environment,
815
+ node,
816
+ counter === null
817
+ ? counter
818
+ : (storeLayer) => update(storeLayer, () => counter),
819
+ );
820
+ }
821
+
822
+ function addStartUpdateStoreLayer(
823
+ environment: IsographEnvironment,
824
+ updater: (prev: number) => number,
825
+ ) {
826
+ const node = addStartUpdateStoreLayerInner(
827
+ environment.store,
828
+ (storeLayer) => {
829
+ return update(storeLayer, updater);
830
+ },
831
+ );
832
+ environment.store = node;
833
+ }
834
+
835
+ const update = (
836
+ storeLayer: StoreLayer,
837
+ value: (counter: number) => number,
838
+ ): EncounteredIds => {
839
+ const { counter } =
840
+ getStoreRecordProxy(storeLayer, {
841
+ __link: '__ROOT',
842
+ __typename: 'Query',
843
+ }) ?? {};
844
+ const nextCounter = value(Number(counter));
845
+
846
+ ignoreReadonly(storeLayer).data = {
847
+ Query: {
848
+ __ROOT: {
849
+ counter: nextCounter,
850
+ },
851
+ },
852
+ };
853
+
854
+ return counter != nextCounter ? CHANGES : NO_CHANGES;
855
+ };
856
+
857
+ function ignoreReadonly(value: StoreLayer): { data: StoreLayerData } {
858
+ return value;
859
+ }
860
+ });