@zenstackhq/client-helpers 3.1.1 → 3.2.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/package.json +12 -9
- package/.turbo/turbo-build.log +0 -24
- package/eslint.config.js +0 -4
- package/src/constants.ts +0 -4
- package/src/fetch.ts +0 -107
- package/src/index.ts +0 -9
- package/src/invalidation.ts +0 -89
- package/src/logging.ts +0 -15
- package/src/mutator.ts +0 -449
- package/src/nested-read-visitor.ts +0 -68
- package/src/nested-write-visitor.ts +0 -359
- package/src/optimistic.ts +0 -139
- package/src/query-analysis.ts +0 -111
- package/src/types.ts +0 -82
- package/test/fetch.test.ts +0 -423
- package/test/invalidation.test.ts +0 -602
- package/test/mutator.test.ts +0 -1533
- package/test/nested-read-visitor.test.ts +0 -949
- package/test/nested-write-visitor.test.ts +0 -1244
- package/test/optimistic.test.ts +0 -743
- package/test/query-analysis.test.ts +0 -1399
- package/test/test-helpers.ts +0 -37
- package/tsconfig.json +0 -4
- package/tsconfig.test.json +0 -7
- package/tsup.config.ts +0 -14
- package/vitest.config.ts +0 -4
package/test/optimistic.test.ts
DELETED
|
@@ -1,743 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import type { Logger } from '../src/logging';
|
|
3
|
-
import { createOptimisticUpdater } from '../src/optimistic';
|
|
4
|
-
import type { QueryInfo } from '../src/types';
|
|
5
|
-
import { createField, createRelationField, createSchema } from './test-helpers';
|
|
6
|
-
|
|
7
|
-
describe('Optimistic update tests', () => {
|
|
8
|
-
describe('createOptimisticUpdater', () => {
|
|
9
|
-
it('applies default optimistic update to matching queries', async () => {
|
|
10
|
-
const schema = createSchema({
|
|
11
|
-
User: {
|
|
12
|
-
name: 'User',
|
|
13
|
-
fields: {
|
|
14
|
-
id: createField('id', 'String'),
|
|
15
|
-
name: createField('name', 'String'),
|
|
16
|
-
},
|
|
17
|
-
uniqueFields: {},
|
|
18
|
-
idFields: ['id'],
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const updateDataMock = vi.fn();
|
|
23
|
-
const queries: QueryInfo[] = [
|
|
24
|
-
{
|
|
25
|
-
model: 'User',
|
|
26
|
-
operation: 'findMany',
|
|
27
|
-
args: {},
|
|
28
|
-
data: [
|
|
29
|
-
{ id: '1', name: 'John' },
|
|
30
|
-
{ id: '2', name: 'Jane' },
|
|
31
|
-
],
|
|
32
|
-
optimisticUpdate: true,
|
|
33
|
-
updateData: updateDataMock,
|
|
34
|
-
},
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const updater = createOptimisticUpdater('User', 'update', schema, {}, () => queries, undefined);
|
|
38
|
-
|
|
39
|
-
await updater({ where: { id: '1' }, data: { name: 'Johnny' } });
|
|
40
|
-
|
|
41
|
-
// Should update the cache with the optimistic data
|
|
42
|
-
expect(updateDataMock).toHaveBeenCalledTimes(1);
|
|
43
|
-
const updatedData = updateDataMock.mock.calls[0]?.[0];
|
|
44
|
-
expect(updatedData).toBeDefined();
|
|
45
|
-
expect(Array.isArray(updatedData)).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('skips queries with optimisticUpdate set to false', async () => {
|
|
49
|
-
const schema = createSchema({
|
|
50
|
-
User: {
|
|
51
|
-
name: 'User',
|
|
52
|
-
fields: {
|
|
53
|
-
id: createField('id', 'String'),
|
|
54
|
-
name: createField('name', 'String'),
|
|
55
|
-
},
|
|
56
|
-
uniqueFields: {},
|
|
57
|
-
idFields: ['id'],
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const updateDataMock = vi.fn();
|
|
62
|
-
const queries: QueryInfo[] = [
|
|
63
|
-
{
|
|
64
|
-
model: 'User',
|
|
65
|
-
operation: 'findMany',
|
|
66
|
-
args: {},
|
|
67
|
-
data: [{ id: '1', name: 'John' }],
|
|
68
|
-
optimisticUpdate: false, // opted out
|
|
69
|
-
updateData: updateDataMock,
|
|
70
|
-
},
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
const updater = createOptimisticUpdater('User', 'update', schema, {}, () => queries, undefined);
|
|
74
|
-
|
|
75
|
-
await updater({ where: { id: '1' }, data: { name: 'Johnny' } });
|
|
76
|
-
|
|
77
|
-
// Should not update the cache
|
|
78
|
-
expect(updateDataMock).not.toHaveBeenCalled();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('uses custom optimisticDataProvider when provided', async () => {
|
|
82
|
-
const schema = createSchema({
|
|
83
|
-
User: {
|
|
84
|
-
name: 'User',
|
|
85
|
-
fields: {
|
|
86
|
-
id: createField('id', 'String'),
|
|
87
|
-
name: createField('name', 'String'),
|
|
88
|
-
},
|
|
89
|
-
uniqueFields: {},
|
|
90
|
-
idFields: ['id'],
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const customData = [{ id: '1', name: 'Custom', $optimistic: true }];
|
|
95
|
-
const optimisticDataProvider = vi.fn(() => ({
|
|
96
|
-
kind: 'Update' as const,
|
|
97
|
-
data: customData,
|
|
98
|
-
}));
|
|
99
|
-
|
|
100
|
-
const updateDataMock = vi.fn();
|
|
101
|
-
const queries: QueryInfo[] = [
|
|
102
|
-
{
|
|
103
|
-
model: 'User',
|
|
104
|
-
operation: 'findMany',
|
|
105
|
-
args: {},
|
|
106
|
-
data: [{ id: '1', name: 'John' }],
|
|
107
|
-
optimisticUpdate: true,
|
|
108
|
-
updateData: updateDataMock,
|
|
109
|
-
},
|
|
110
|
-
];
|
|
111
|
-
|
|
112
|
-
const updater = createOptimisticUpdater(
|
|
113
|
-
'User',
|
|
114
|
-
'update',
|
|
115
|
-
schema,
|
|
116
|
-
{ optimisticDataProvider },
|
|
117
|
-
() => queries,
|
|
118
|
-
undefined,
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
await updater({ where: { id: '1' }, data: { name: 'Johnny' } });
|
|
122
|
-
|
|
123
|
-
// Provider should be called
|
|
124
|
-
expect(optimisticDataProvider).toHaveBeenCalledWith({
|
|
125
|
-
queryModel: 'User',
|
|
126
|
-
queryOperation: 'findMany',
|
|
127
|
-
queryArgs: {},
|
|
128
|
-
currentData: [{ id: '1', name: 'John' }],
|
|
129
|
-
mutationArgs: { where: { id: '1' }, data: { name: 'Johnny' } },
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Should update with custom data
|
|
133
|
-
expect(updateDataMock).toHaveBeenCalledWith(customData, true);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('skips update when provider returns Skip', async () => {
|
|
137
|
-
const schema = createSchema({
|
|
138
|
-
User: {
|
|
139
|
-
name: 'User',
|
|
140
|
-
fields: {
|
|
141
|
-
id: createField('id', 'String'),
|
|
142
|
-
},
|
|
143
|
-
uniqueFields: {},
|
|
144
|
-
idFields: ['id'],
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const optimisticDataProvider = vi.fn(() => ({
|
|
149
|
-
kind: 'Skip' as const,
|
|
150
|
-
}));
|
|
151
|
-
|
|
152
|
-
const updateDataMock = vi.fn();
|
|
153
|
-
const queries: QueryInfo[] = [
|
|
154
|
-
{
|
|
155
|
-
model: 'User',
|
|
156
|
-
operation: 'findMany',
|
|
157
|
-
args: {},
|
|
158
|
-
data: [{ id: '1' }],
|
|
159
|
-
optimisticUpdate: true,
|
|
160
|
-
updateData: updateDataMock,
|
|
161
|
-
},
|
|
162
|
-
];
|
|
163
|
-
|
|
164
|
-
const updater = createOptimisticUpdater(
|
|
165
|
-
'User',
|
|
166
|
-
'update',
|
|
167
|
-
schema,
|
|
168
|
-
{ optimisticDataProvider },
|
|
169
|
-
() => queries,
|
|
170
|
-
undefined,
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
await updater({ where: { id: '1' }, data: {} });
|
|
174
|
-
|
|
175
|
-
// Provider should be called
|
|
176
|
-
expect(optimisticDataProvider).toHaveBeenCalled();
|
|
177
|
-
|
|
178
|
-
// Should not update
|
|
179
|
-
expect(updateDataMock).not.toHaveBeenCalled();
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('proceeds with default update when provider returns ProceedDefault', async () => {
|
|
183
|
-
const schema = createSchema({
|
|
184
|
-
User: {
|
|
185
|
-
name: 'User',
|
|
186
|
-
fields: {
|
|
187
|
-
id: createField('id', 'String'),
|
|
188
|
-
name: createField('name', 'String'),
|
|
189
|
-
},
|
|
190
|
-
uniqueFields: {},
|
|
191
|
-
idFields: ['id'],
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const optimisticDataProvider = vi.fn(() => ({
|
|
196
|
-
kind: 'ProceedDefault' as const,
|
|
197
|
-
}));
|
|
198
|
-
|
|
199
|
-
const updateDataMock = vi.fn();
|
|
200
|
-
const queries: QueryInfo[] = [
|
|
201
|
-
{
|
|
202
|
-
model: 'User',
|
|
203
|
-
operation: 'findMany',
|
|
204
|
-
args: {},
|
|
205
|
-
data: [{ id: '1', name: 'John' }],
|
|
206
|
-
optimisticUpdate: true,
|
|
207
|
-
updateData: updateDataMock,
|
|
208
|
-
},
|
|
209
|
-
];
|
|
210
|
-
|
|
211
|
-
const updater = createOptimisticUpdater(
|
|
212
|
-
'User',
|
|
213
|
-
'update',
|
|
214
|
-
schema,
|
|
215
|
-
{ optimisticDataProvider },
|
|
216
|
-
() => queries,
|
|
217
|
-
undefined,
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
await updater({ where: { id: '1' }, data: { name: 'Johnny' } });
|
|
221
|
-
|
|
222
|
-
// Provider should be called
|
|
223
|
-
expect(optimisticDataProvider).toHaveBeenCalled();
|
|
224
|
-
|
|
225
|
-
// Should proceed with default update
|
|
226
|
-
expect(updateDataMock).toHaveBeenCalled();
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('handles async optimisticDataProvider', async () => {
|
|
230
|
-
const schema = createSchema({
|
|
231
|
-
User: {
|
|
232
|
-
name: 'User',
|
|
233
|
-
fields: {
|
|
234
|
-
id: createField('id', 'String'),
|
|
235
|
-
},
|
|
236
|
-
uniqueFields: {},
|
|
237
|
-
idFields: ['id'],
|
|
238
|
-
},
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
const optimisticDataProvider = vi.fn(async () => {
|
|
242
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
243
|
-
return {
|
|
244
|
-
kind: 'Update' as const,
|
|
245
|
-
data: [{ id: '1', $optimistic: true }],
|
|
246
|
-
};
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
const updateDataMock = vi.fn();
|
|
250
|
-
const queries: QueryInfo[] = [
|
|
251
|
-
{
|
|
252
|
-
model: 'User',
|
|
253
|
-
operation: 'findMany',
|
|
254
|
-
args: {},
|
|
255
|
-
data: [],
|
|
256
|
-
optimisticUpdate: true,
|
|
257
|
-
updateData: updateDataMock,
|
|
258
|
-
},
|
|
259
|
-
];
|
|
260
|
-
|
|
261
|
-
const updater = createOptimisticUpdater(
|
|
262
|
-
'User',
|
|
263
|
-
'update',
|
|
264
|
-
schema,
|
|
265
|
-
{ optimisticDataProvider },
|
|
266
|
-
() => queries,
|
|
267
|
-
undefined,
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
await updater({ where: { id: '1' }, data: {} });
|
|
271
|
-
|
|
272
|
-
expect(optimisticDataProvider).toHaveBeenCalled();
|
|
273
|
-
expect(updateDataMock).toHaveBeenCalled();
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('processes multiple queries', async () => {
|
|
277
|
-
const schema = createSchema({
|
|
278
|
-
User: {
|
|
279
|
-
name: 'User',
|
|
280
|
-
fields: {
|
|
281
|
-
id: createField('id', 'String'),
|
|
282
|
-
name: createField('name', 'String'),
|
|
283
|
-
},
|
|
284
|
-
uniqueFields: {},
|
|
285
|
-
idFields: ['id'],
|
|
286
|
-
},
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
const updateData1 = vi.fn();
|
|
290
|
-
const updateData2 = vi.fn();
|
|
291
|
-
const queries: QueryInfo[] = [
|
|
292
|
-
{
|
|
293
|
-
model: 'User',
|
|
294
|
-
operation: 'findMany',
|
|
295
|
-
args: {},
|
|
296
|
-
data: [{ id: '1', name: 'John' }],
|
|
297
|
-
optimisticUpdate: true,
|
|
298
|
-
updateData: updateData1,
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
model: 'User',
|
|
302
|
-
operation: 'findUnique',
|
|
303
|
-
args: { where: { id: '1' } },
|
|
304
|
-
data: { id: '1', name: 'John' },
|
|
305
|
-
optimisticUpdate: true,
|
|
306
|
-
updateData: updateData2,
|
|
307
|
-
},
|
|
308
|
-
];
|
|
309
|
-
|
|
310
|
-
const updater = createOptimisticUpdater('User', 'update', schema, {}, () => queries, undefined);
|
|
311
|
-
|
|
312
|
-
await updater({ where: { id: '1' }, data: { name: 'Johnny' } });
|
|
313
|
-
|
|
314
|
-
// Both queries should be updated
|
|
315
|
-
expect(updateData1).toHaveBeenCalled();
|
|
316
|
-
expect(updateData2).toHaveBeenCalled();
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it('logs when logging is enabled', async () => {
|
|
320
|
-
const schema = createSchema({
|
|
321
|
-
User: {
|
|
322
|
-
name: 'User',
|
|
323
|
-
fields: {
|
|
324
|
-
id: createField('id', 'String'),
|
|
325
|
-
name: createField('name', 'String'),
|
|
326
|
-
},
|
|
327
|
-
uniqueFields: {},
|
|
328
|
-
idFields: ['id'],
|
|
329
|
-
},
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
const logger = vi.fn() as Logger;
|
|
333
|
-
const updateDataMock = vi.fn();
|
|
334
|
-
const queries: QueryInfo[] = [
|
|
335
|
-
{
|
|
336
|
-
model: 'User',
|
|
337
|
-
operation: 'findMany',
|
|
338
|
-
args: {},
|
|
339
|
-
data: [{ id: '1', name: 'John' }],
|
|
340
|
-
optimisticUpdate: true,
|
|
341
|
-
updateData: updateDataMock,
|
|
342
|
-
},
|
|
343
|
-
];
|
|
344
|
-
|
|
345
|
-
const updater = createOptimisticUpdater('User', 'update', schema, {}, () => queries, logger);
|
|
346
|
-
|
|
347
|
-
await updater({ where: { id: '1' }, data: { name: 'Johnny' } });
|
|
348
|
-
|
|
349
|
-
// Logger should be called
|
|
350
|
-
expect(logger).toHaveBeenCalled();
|
|
351
|
-
expect(logger).toHaveBeenCalledWith(expect.stringContaining('Optimistically updating'));
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('logs when skipping due to opt-out', async () => {
|
|
355
|
-
const schema = createSchema({
|
|
356
|
-
User: {
|
|
357
|
-
name: 'User',
|
|
358
|
-
fields: {
|
|
359
|
-
id: createField('id', 'String'),
|
|
360
|
-
},
|
|
361
|
-
uniqueFields: {},
|
|
362
|
-
idFields: ['id'],
|
|
363
|
-
},
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
const logger = vi.fn() as Logger;
|
|
367
|
-
const updateDataMock = vi.fn();
|
|
368
|
-
const queries: QueryInfo[] = [
|
|
369
|
-
{
|
|
370
|
-
model: 'User',
|
|
371
|
-
operation: 'findMany',
|
|
372
|
-
args: {},
|
|
373
|
-
data: [],
|
|
374
|
-
optimisticUpdate: false,
|
|
375
|
-
updateData: updateDataMock,
|
|
376
|
-
},
|
|
377
|
-
];
|
|
378
|
-
|
|
379
|
-
const updater = createOptimisticUpdater('User', 'update', schema, {}, () => queries, logger);
|
|
380
|
-
|
|
381
|
-
await updater({ where: { id: '1' }, data: {} });
|
|
382
|
-
|
|
383
|
-
// Logger should be called with skip message
|
|
384
|
-
expect(logger).toHaveBeenCalledWith(expect.stringContaining('Skipping optimistic update'));
|
|
385
|
-
expect(logger).toHaveBeenCalledWith(expect.stringContaining('opt-out'));
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it('logs when skipping due to provider', async () => {
|
|
389
|
-
const schema = createSchema({
|
|
390
|
-
User: {
|
|
391
|
-
name: 'User',
|
|
392
|
-
fields: {
|
|
393
|
-
id: createField('id', 'String'),
|
|
394
|
-
},
|
|
395
|
-
uniqueFields: {},
|
|
396
|
-
idFields: ['id'],
|
|
397
|
-
},
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
const logger = vi.fn() as Logger;
|
|
401
|
-
const optimisticDataProvider = vi.fn(() => ({
|
|
402
|
-
kind: 'Skip' as const,
|
|
403
|
-
}));
|
|
404
|
-
|
|
405
|
-
const updateDataMock = vi.fn();
|
|
406
|
-
const queries: QueryInfo[] = [
|
|
407
|
-
{
|
|
408
|
-
model: 'User',
|
|
409
|
-
operation: 'findMany',
|
|
410
|
-
args: {},
|
|
411
|
-
data: [],
|
|
412
|
-
optimisticUpdate: true,
|
|
413
|
-
updateData: updateDataMock,
|
|
414
|
-
},
|
|
415
|
-
];
|
|
416
|
-
|
|
417
|
-
const updater = createOptimisticUpdater(
|
|
418
|
-
'User',
|
|
419
|
-
'update',
|
|
420
|
-
schema,
|
|
421
|
-
{ optimisticDataProvider },
|
|
422
|
-
() => queries,
|
|
423
|
-
logger,
|
|
424
|
-
);
|
|
425
|
-
|
|
426
|
-
await updater({ where: { id: '1' }, data: {} });
|
|
427
|
-
|
|
428
|
-
// Logger should be called with skip message
|
|
429
|
-
expect(logger).toHaveBeenCalledWith(expect.stringContaining('Skipping optimistic updating'));
|
|
430
|
-
expect(logger).toHaveBeenCalledWith(expect.stringContaining('provider'));
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
it('logs when updating due to provider', async () => {
|
|
434
|
-
const schema = createSchema({
|
|
435
|
-
User: {
|
|
436
|
-
name: 'User',
|
|
437
|
-
fields: {
|
|
438
|
-
id: createField('id', 'String'),
|
|
439
|
-
},
|
|
440
|
-
uniqueFields: {},
|
|
441
|
-
idFields: ['id'],
|
|
442
|
-
},
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
const logger = vi.fn() as Logger;
|
|
446
|
-
const optimisticDataProvider = vi.fn(() => ({
|
|
447
|
-
kind: 'Update' as const,
|
|
448
|
-
data: [],
|
|
449
|
-
}));
|
|
450
|
-
|
|
451
|
-
const updateDataMock = vi.fn();
|
|
452
|
-
const queries: QueryInfo[] = [
|
|
453
|
-
{
|
|
454
|
-
model: 'User',
|
|
455
|
-
operation: 'findMany',
|
|
456
|
-
args: {},
|
|
457
|
-
data: [],
|
|
458
|
-
optimisticUpdate: true,
|
|
459
|
-
updateData: updateDataMock,
|
|
460
|
-
},
|
|
461
|
-
];
|
|
462
|
-
|
|
463
|
-
const updater = createOptimisticUpdater(
|
|
464
|
-
'User',
|
|
465
|
-
'update',
|
|
466
|
-
schema,
|
|
467
|
-
{ optimisticDataProvider },
|
|
468
|
-
() => queries,
|
|
469
|
-
logger,
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
await updater({ where: { id: '1' }, data: {} });
|
|
473
|
-
|
|
474
|
-
// Logger should be called with update message
|
|
475
|
-
expect(logger).toHaveBeenCalledWith(expect.stringContaining('Optimistically updating'));
|
|
476
|
-
expect(logger).toHaveBeenCalledWith(expect.stringContaining('provider'));
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
it('handles empty query list', async () => {
|
|
480
|
-
const schema = createSchema({
|
|
481
|
-
User: {
|
|
482
|
-
name: 'User',
|
|
483
|
-
fields: {
|
|
484
|
-
id: createField('id', 'String'),
|
|
485
|
-
},
|
|
486
|
-
uniqueFields: {},
|
|
487
|
-
idFields: ['id'],
|
|
488
|
-
},
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
const queries: QueryInfo[] = [];
|
|
492
|
-
|
|
493
|
-
const updater = createOptimisticUpdater('User', 'update', schema, {}, () => queries, undefined);
|
|
494
|
-
|
|
495
|
-
// Should not throw
|
|
496
|
-
await expect(updater({ where: { id: '1' }, data: {} })).resolves.toBeUndefined();
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
it('handles mutations on related models', async () => {
|
|
500
|
-
const schema = createSchema({
|
|
501
|
-
User: {
|
|
502
|
-
name: 'User',
|
|
503
|
-
fields: {
|
|
504
|
-
id: createField('id', 'String'),
|
|
505
|
-
posts: createRelationField('posts', 'Post'),
|
|
506
|
-
},
|
|
507
|
-
uniqueFields: {},
|
|
508
|
-
idFields: ['id'],
|
|
509
|
-
},
|
|
510
|
-
Post: {
|
|
511
|
-
name: 'Post',
|
|
512
|
-
fields: {
|
|
513
|
-
id: createField('id', 'String'),
|
|
514
|
-
title: createField('title', 'String'),
|
|
515
|
-
userId: createField('userId', 'String'),
|
|
516
|
-
},
|
|
517
|
-
uniqueFields: {},
|
|
518
|
-
idFields: ['id'],
|
|
519
|
-
},
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
const updateDataMock = vi.fn();
|
|
523
|
-
const queries: QueryInfo[] = [
|
|
524
|
-
{
|
|
525
|
-
model: 'Post',
|
|
526
|
-
operation: 'findMany',
|
|
527
|
-
args: {},
|
|
528
|
-
data: [
|
|
529
|
-
{ id: '1', title: 'Post 1', userId: '1' },
|
|
530
|
-
{ id: '2', title: 'Post 2', userId: '2' },
|
|
531
|
-
],
|
|
532
|
-
optimisticUpdate: true,
|
|
533
|
-
updateData: updateDataMock,
|
|
534
|
-
},
|
|
535
|
-
];
|
|
536
|
-
|
|
537
|
-
const updater = createOptimisticUpdater('Post', 'update', schema, {}, () => queries, undefined);
|
|
538
|
-
|
|
539
|
-
await updater({ where: { id: '1' }, data: { title: 'Updated Post 1' } });
|
|
540
|
-
|
|
541
|
-
// Should update the cache
|
|
542
|
-
expect(updateDataMock).toHaveBeenCalled();
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
it('extracts mutation args from first argument', async () => {
|
|
546
|
-
const schema = createSchema({
|
|
547
|
-
User: {
|
|
548
|
-
name: 'User',
|
|
549
|
-
fields: {
|
|
550
|
-
id: createField('id', 'String'),
|
|
551
|
-
name: createField('name', 'String'),
|
|
552
|
-
},
|
|
553
|
-
uniqueFields: {},
|
|
554
|
-
idFields: ['id'],
|
|
555
|
-
},
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
let capturedMutationArgs: any;
|
|
559
|
-
const optimisticDataProvider = vi.fn((args) => {
|
|
560
|
-
capturedMutationArgs = args.mutationArgs;
|
|
561
|
-
return { kind: 'Skip' as const };
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const queries: QueryInfo[] = [
|
|
565
|
-
{
|
|
566
|
-
model: 'User',
|
|
567
|
-
operation: 'findMany',
|
|
568
|
-
args: {},
|
|
569
|
-
data: [],
|
|
570
|
-
optimisticUpdate: true,
|
|
571
|
-
updateData: vi.fn(),
|
|
572
|
-
},
|
|
573
|
-
];
|
|
574
|
-
|
|
575
|
-
const updater = createOptimisticUpdater(
|
|
576
|
-
'User',
|
|
577
|
-
'update',
|
|
578
|
-
schema,
|
|
579
|
-
{ optimisticDataProvider },
|
|
580
|
-
() => queries,
|
|
581
|
-
undefined,
|
|
582
|
-
);
|
|
583
|
-
|
|
584
|
-
const mutationArgs = { where: { id: '1' }, data: { name: 'Test' } };
|
|
585
|
-
await updater(mutationArgs);
|
|
586
|
-
|
|
587
|
-
// Should extract mutation args from first argument
|
|
588
|
-
expect(capturedMutationArgs).toEqual(mutationArgs);
|
|
589
|
-
});
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
describe('real-world scenarios', () => {
|
|
593
|
-
it('handles user list update optimistically', async () => {
|
|
594
|
-
const schema = createSchema({
|
|
595
|
-
User: {
|
|
596
|
-
name: 'User',
|
|
597
|
-
fields: {
|
|
598
|
-
id: createField('id', 'String'),
|
|
599
|
-
name: createField('name', 'String'),
|
|
600
|
-
email: createField('email', 'String'),
|
|
601
|
-
},
|
|
602
|
-
uniqueFields: {},
|
|
603
|
-
idFields: ['id'],
|
|
604
|
-
},
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
const updateDataMock = vi.fn();
|
|
608
|
-
const queries: QueryInfo[] = [
|
|
609
|
-
{
|
|
610
|
-
model: 'User',
|
|
611
|
-
operation: 'findMany',
|
|
612
|
-
args: {},
|
|
613
|
-
data: [
|
|
614
|
-
{ id: '1', name: 'John', email: 'john@example.com' },
|
|
615
|
-
{ id: '2', name: 'Jane', email: 'jane@example.com' },
|
|
616
|
-
],
|
|
617
|
-
optimisticUpdate: true,
|
|
618
|
-
updateData: updateDataMock,
|
|
619
|
-
},
|
|
620
|
-
];
|
|
621
|
-
|
|
622
|
-
const updater = createOptimisticUpdater('User', 'update', schema, {}, () => queries, undefined);
|
|
623
|
-
|
|
624
|
-
await updater({ where: { id: '1' }, data: { name: 'Johnny' } });
|
|
625
|
-
|
|
626
|
-
expect(updateDataMock).toHaveBeenCalled();
|
|
627
|
-
const updatedData = updateDataMock.mock.calls[0]?.[0];
|
|
628
|
-
expect(Array.isArray(updatedData)).toBe(true);
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
it('handles custom provider for complex business logic', async () => {
|
|
632
|
-
const schema = createSchema({
|
|
633
|
-
Post: {
|
|
634
|
-
name: 'Post',
|
|
635
|
-
fields: {
|
|
636
|
-
id: createField('id', 'String'),
|
|
637
|
-
title: createField('title', 'String'),
|
|
638
|
-
published: createField('published', 'Boolean'),
|
|
639
|
-
},
|
|
640
|
-
uniqueFields: {},
|
|
641
|
-
idFields: ['id'],
|
|
642
|
-
},
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
// Custom provider that only updates published posts
|
|
646
|
-
const optimisticDataProvider = vi.fn(({ currentData, mutationArgs }) => {
|
|
647
|
-
const posts = currentData as any[];
|
|
648
|
-
const updatedPosts = posts.map((post: any) => {
|
|
649
|
-
if (post.id === mutationArgs.where.id && post.published) {
|
|
650
|
-
return { ...post, ...mutationArgs.data, $optimistic: true };
|
|
651
|
-
}
|
|
652
|
-
return post;
|
|
653
|
-
});
|
|
654
|
-
return { kind: 'Update' as const, data: updatedPosts };
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
const updateDataMock = vi.fn();
|
|
658
|
-
const queries: QueryInfo[] = [
|
|
659
|
-
{
|
|
660
|
-
model: 'Post',
|
|
661
|
-
operation: 'findMany',
|
|
662
|
-
args: { where: { published: true } },
|
|
663
|
-
data: [
|
|
664
|
-
{ id: '1', title: 'Post 1', published: true },
|
|
665
|
-
{ id: '2', title: 'Post 2', published: true },
|
|
666
|
-
],
|
|
667
|
-
optimisticUpdate: true,
|
|
668
|
-
updateData: updateDataMock,
|
|
669
|
-
},
|
|
670
|
-
];
|
|
671
|
-
|
|
672
|
-
const updater = createOptimisticUpdater(
|
|
673
|
-
'Post',
|
|
674
|
-
'update',
|
|
675
|
-
schema,
|
|
676
|
-
{ optimisticDataProvider },
|
|
677
|
-
() => queries,
|
|
678
|
-
undefined,
|
|
679
|
-
);
|
|
680
|
-
|
|
681
|
-
await updater({ where: { id: '1' }, data: { title: 'Updated Post 1' } });
|
|
682
|
-
|
|
683
|
-
expect(optimisticDataProvider).toHaveBeenCalled();
|
|
684
|
-
expect(updateDataMock).toHaveBeenCalled();
|
|
685
|
-
const updatedData = updateDataMock.mock.calls[0]?.[0];
|
|
686
|
-
expect(updatedData[0]).toHaveProperty('$optimistic', true);
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
it('handles mixed queries with different opt-in settings', async () => {
|
|
690
|
-
const schema = createSchema({
|
|
691
|
-
User: {
|
|
692
|
-
name: 'User',
|
|
693
|
-
fields: {
|
|
694
|
-
id: createField('id', 'String'),
|
|
695
|
-
name: createField('name', 'String'),
|
|
696
|
-
},
|
|
697
|
-
uniqueFields: {},
|
|
698
|
-
idFields: ['id'],
|
|
699
|
-
},
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
const updateData1 = vi.fn();
|
|
703
|
-
const updateData2 = vi.fn();
|
|
704
|
-
const updateData3 = vi.fn();
|
|
705
|
-
|
|
706
|
-
const queries: QueryInfo[] = [
|
|
707
|
-
{
|
|
708
|
-
model: 'User',
|
|
709
|
-
operation: 'findMany',
|
|
710
|
-
args: {},
|
|
711
|
-
data: [{ id: '1', name: 'John' }],
|
|
712
|
-
optimisticUpdate: true, // opted in
|
|
713
|
-
updateData: updateData1,
|
|
714
|
-
},
|
|
715
|
-
{
|
|
716
|
-
model: 'User',
|
|
717
|
-
operation: 'findUnique',
|
|
718
|
-
args: { where: { id: '1' } },
|
|
719
|
-
data: { id: '1', name: 'John' },
|
|
720
|
-
optimisticUpdate: false, // opted out
|
|
721
|
-
updateData: updateData2,
|
|
722
|
-
},
|
|
723
|
-
{
|
|
724
|
-
model: 'User',
|
|
725
|
-
operation: 'findUnique',
|
|
726
|
-
args: { where: { id: '2' } },
|
|
727
|
-
data: { id: '2', name: 'Jane' },
|
|
728
|
-
optimisticUpdate: true, // opted in but different ID so won't be updated
|
|
729
|
-
updateData: updateData3,
|
|
730
|
-
},
|
|
731
|
-
];
|
|
732
|
-
|
|
733
|
-
const updater = createOptimisticUpdater('User', 'update', schema, {}, () => queries, undefined);
|
|
734
|
-
|
|
735
|
-
await updater({ where: { id: '1' }, data: { name: 'Johnny' } });
|
|
736
|
-
|
|
737
|
-
// Only opted-in queries matching the mutation should be updated
|
|
738
|
-
expect(updateData1).toHaveBeenCalled(); // opted in and matches
|
|
739
|
-
expect(updateData2).not.toHaveBeenCalled(); // opted out
|
|
740
|
-
expect(updateData3).not.toHaveBeenCalled(); // opted in but different ID
|
|
741
|
-
});
|
|
742
|
-
});
|
|
743
|
-
});
|