@zenstackhq/client-helpers 3.1.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.
- package/.turbo/turbo-build.log +24 -0
- package/LICENSE +21 -0
- package/dist/fetch.d.ts +35 -0
- package/dist/fetch.js +101 -0
- package/dist/fetch.js.map +1 -0
- package/dist/index.d.ts +278 -0
- package/dist/index.js +822 -0
- package/dist/index.js.map +1 -0
- package/eslint.config.js +4 -0
- package/package.json +40 -0
- package/src/constants.ts +4 -0
- package/src/fetch.ts +107 -0
- package/src/index.ts +9 -0
- package/src/invalidation.ts +89 -0
- package/src/logging.ts +15 -0
- package/src/mutator.ts +449 -0
- package/src/nested-read-visitor.ts +68 -0
- package/src/nested-write-visitor.ts +359 -0
- package/src/optimistic.ts +139 -0
- package/src/query-analysis.ts +111 -0
- package/src/types.ts +82 -0
- package/test/fetch.test.ts +423 -0
- package/test/invalidation.test.ts +602 -0
- package/test/mutator.test.ts +1533 -0
- package/test/nested-read-visitor.test.ts +949 -0
- package/test/nested-write-visitor.test.ts +1244 -0
- package/test/optimistic.test.ts +743 -0
- package/test/query-analysis.test.ts +1399 -0
- package/test/test-helpers.ts +37 -0
- package/tsconfig.json +4 -0
- package/tsconfig.test.json +7 -0
- package/tsup.config.ts +14 -0
- package/vitest.config.ts +4 -0
|
@@ -0,0 +1,1244 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { NestedWriteVisitor, type NestedWriteVisitorContext } from '../src/nested-write-visitor';
|
|
3
|
+
import { createField, createRelationField, createSchema } from './test-helpers';
|
|
4
|
+
|
|
5
|
+
describe('NestedWriteVisitor tests', () => {
|
|
6
|
+
describe('create action', () => {
|
|
7
|
+
it('visits create with simple data', async () => {
|
|
8
|
+
const schema = createSchema({
|
|
9
|
+
User: {
|
|
10
|
+
name: 'User',
|
|
11
|
+
fields: {
|
|
12
|
+
id: createField('id', 'String'),
|
|
13
|
+
name: createField('name', 'String'),
|
|
14
|
+
},
|
|
15
|
+
uniqueFields: {},
|
|
16
|
+
idFields: ['id'],
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const createCallback = vi.fn();
|
|
21
|
+
const visitor = new NestedWriteVisitor(schema, { create: createCallback });
|
|
22
|
+
|
|
23
|
+
await visitor.visit('User', 'create', {
|
|
24
|
+
data: { name: 'Alice' },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(createCallback).toHaveBeenCalledTimes(1);
|
|
28
|
+
expect(createCallback).toHaveBeenCalledWith(
|
|
29
|
+
'User',
|
|
30
|
+
{ name: 'Alice' },
|
|
31
|
+
expect.objectContaining({
|
|
32
|
+
parent: undefined,
|
|
33
|
+
field: undefined,
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('visits nested create in relation', async () => {
|
|
39
|
+
const schema = createSchema({
|
|
40
|
+
User: {
|
|
41
|
+
name: 'User',
|
|
42
|
+
fields: {
|
|
43
|
+
id: createField('id', 'String'),
|
|
44
|
+
posts: createRelationField('posts', 'Post'),
|
|
45
|
+
},
|
|
46
|
+
uniqueFields: {},
|
|
47
|
+
idFields: ['id'],
|
|
48
|
+
},
|
|
49
|
+
Post: {
|
|
50
|
+
name: 'Post',
|
|
51
|
+
fields: {
|
|
52
|
+
id: createField('id', 'String'),
|
|
53
|
+
title: createField('title', 'String'),
|
|
54
|
+
},
|
|
55
|
+
uniqueFields: {},
|
|
56
|
+
idFields: ['id'],
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const createCallback = vi.fn();
|
|
61
|
+
const visitor = new NestedWriteVisitor(schema, { create: createCallback });
|
|
62
|
+
|
|
63
|
+
await visitor.visit('User', 'create', {
|
|
64
|
+
data: {
|
|
65
|
+
name: 'Alice',
|
|
66
|
+
posts: {
|
|
67
|
+
create: { title: 'First Post' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(createCallback).toHaveBeenCalledTimes(2);
|
|
73
|
+
expect(createCallback).toHaveBeenNthCalledWith(1, 'User', expect.any(Object), expect.any(Object));
|
|
74
|
+
expect(createCallback).toHaveBeenNthCalledWith(2, 'Post', { title: 'First Post' }, expect.any(Object));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('visits create with array data', async () => {
|
|
78
|
+
const schema = createSchema({
|
|
79
|
+
Post: {
|
|
80
|
+
name: 'Post',
|
|
81
|
+
fields: {
|
|
82
|
+
id: createField('id', 'String'),
|
|
83
|
+
comments: createRelationField('comments', 'Comment'),
|
|
84
|
+
},
|
|
85
|
+
uniqueFields: {},
|
|
86
|
+
idFields: ['id'],
|
|
87
|
+
},
|
|
88
|
+
Comment: {
|
|
89
|
+
name: 'Comment',
|
|
90
|
+
fields: {
|
|
91
|
+
id: createField('id', 'String'),
|
|
92
|
+
text: createField('text', 'String'),
|
|
93
|
+
},
|
|
94
|
+
uniqueFields: {},
|
|
95
|
+
idFields: ['id'],
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const createCallback = vi.fn();
|
|
100
|
+
const visitor = new NestedWriteVisitor(schema, { create: createCallback });
|
|
101
|
+
|
|
102
|
+
await visitor.visit('Post', 'create', {
|
|
103
|
+
data: {
|
|
104
|
+
title: 'My Post',
|
|
105
|
+
comments: {
|
|
106
|
+
create: [{ text: 'Comment 1' }, { text: 'Comment 2' }],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(createCallback).toHaveBeenCalledTimes(3); // 1 post + 2 comments
|
|
112
|
+
expect(createCallback).toHaveBeenNthCalledWith(3, 'Comment', { text: 'Comment 1' }, expect.any(Object));
|
|
113
|
+
expect(createCallback).toHaveBeenNthCalledWith(2, 'Comment', { text: 'Comment 2' }, expect.any(Object));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('stops visiting when callback returns false', async () => {
|
|
117
|
+
const schema = createSchema({
|
|
118
|
+
User: {
|
|
119
|
+
name: 'User',
|
|
120
|
+
fields: {
|
|
121
|
+
id: createField('id', 'String'),
|
|
122
|
+
posts: createRelationField('posts', 'Post'),
|
|
123
|
+
},
|
|
124
|
+
uniqueFields: {},
|
|
125
|
+
idFields: ['id'],
|
|
126
|
+
},
|
|
127
|
+
Post: {
|
|
128
|
+
name: 'Post',
|
|
129
|
+
fields: {
|
|
130
|
+
id: createField('id', 'String'),
|
|
131
|
+
title: createField('title', 'String'),
|
|
132
|
+
},
|
|
133
|
+
uniqueFields: {},
|
|
134
|
+
idFields: ['id'],
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const createCallback = vi.fn(() => false);
|
|
139
|
+
const visitor = new NestedWriteVisitor(schema, { create: createCallback });
|
|
140
|
+
|
|
141
|
+
await visitor.visit('User', 'create', {
|
|
142
|
+
data: {
|
|
143
|
+
name: 'Alice',
|
|
144
|
+
posts: {
|
|
145
|
+
create: { title: 'First Post' },
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Should only visit User, not the nested post
|
|
151
|
+
expect(createCallback).toHaveBeenCalledTimes(1);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('allows callback to replace payload', async () => {
|
|
155
|
+
const schema = createSchema({
|
|
156
|
+
User: {
|
|
157
|
+
name: 'User',
|
|
158
|
+
fields: {
|
|
159
|
+
id: createField('id', 'String'),
|
|
160
|
+
name: createField('name', 'String'),
|
|
161
|
+
},
|
|
162
|
+
uniqueFields: {},
|
|
163
|
+
idFields: ['id'],
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const fieldCallback = vi.fn();
|
|
168
|
+
const createCallback = vi.fn(() => ({ name: 'Bob' }));
|
|
169
|
+
const visitor = new NestedWriteVisitor(schema, { create: createCallback, field: fieldCallback });
|
|
170
|
+
|
|
171
|
+
await visitor.visit('User', 'create', {
|
|
172
|
+
data: { name: 'Alice' },
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Field callback should see the replaced payload
|
|
176
|
+
expect(fieldCallback).toHaveBeenCalledWith(
|
|
177
|
+
expect.objectContaining({ name: 'name' }),
|
|
178
|
+
'create',
|
|
179
|
+
'Bob',
|
|
180
|
+
expect.any(Object),
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('createMany action', () => {
|
|
186
|
+
it('visits createMany with data array', async () => {
|
|
187
|
+
const schema = createSchema({
|
|
188
|
+
User: {
|
|
189
|
+
name: 'User',
|
|
190
|
+
fields: {
|
|
191
|
+
id: createField('id', 'String'),
|
|
192
|
+
name: createField('name', 'String'),
|
|
193
|
+
},
|
|
194
|
+
uniqueFields: {},
|
|
195
|
+
idFields: ['id'],
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const createManyCallback = vi.fn();
|
|
200
|
+
const visitor = new NestedWriteVisitor(schema, { createMany: createManyCallback });
|
|
201
|
+
|
|
202
|
+
await visitor.visit('User', 'createMany', {
|
|
203
|
+
data: [{ name: 'Alice' }, { name: 'Bob' }],
|
|
204
|
+
skipDuplicates: true,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(createManyCallback).toHaveBeenCalledTimes(1);
|
|
208
|
+
expect(createManyCallback).toHaveBeenCalledWith(
|
|
209
|
+
'User',
|
|
210
|
+
{ data: [{ name: 'Alice' }, { name: 'Bob' }], skipDuplicates: true },
|
|
211
|
+
expect.any(Object),
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('update action', () => {
|
|
217
|
+
it('visits update with simple data', async () => {
|
|
218
|
+
const schema = createSchema({
|
|
219
|
+
User: {
|
|
220
|
+
name: 'User',
|
|
221
|
+
fields: {
|
|
222
|
+
id: createField('id', 'String'),
|
|
223
|
+
name: createField('name', 'String'),
|
|
224
|
+
},
|
|
225
|
+
uniqueFields: {},
|
|
226
|
+
idFields: ['id'],
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const updateCallback = vi.fn();
|
|
231
|
+
const visitor = new NestedWriteVisitor(schema, { update: updateCallback });
|
|
232
|
+
|
|
233
|
+
await visitor.visit('User', 'update', {
|
|
234
|
+
where: { id: '1' },
|
|
235
|
+
data: { name: 'Updated' },
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(updateCallback).toHaveBeenCalledTimes(1);
|
|
239
|
+
expect(updateCallback).toHaveBeenCalledWith(
|
|
240
|
+
'User',
|
|
241
|
+
{ where: { id: '1' }, data: { name: 'Updated' } },
|
|
242
|
+
expect.any(Object),
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('visits nested update in relation', async () => {
|
|
247
|
+
const schema = createSchema({
|
|
248
|
+
User: {
|
|
249
|
+
name: 'User',
|
|
250
|
+
fields: {
|
|
251
|
+
id: createField('id', 'String'),
|
|
252
|
+
posts: createRelationField('posts', 'Post'),
|
|
253
|
+
},
|
|
254
|
+
uniqueFields: {},
|
|
255
|
+
idFields: ['id'],
|
|
256
|
+
},
|
|
257
|
+
Post: {
|
|
258
|
+
name: 'Post',
|
|
259
|
+
fields: {
|
|
260
|
+
id: createField('id', 'String'),
|
|
261
|
+
title: createField('title', 'String'),
|
|
262
|
+
},
|
|
263
|
+
uniqueFields: {},
|
|
264
|
+
idFields: ['id'],
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const updateCallback = vi.fn();
|
|
269
|
+
const visitor = new NestedWriteVisitor(schema, { update: updateCallback });
|
|
270
|
+
|
|
271
|
+
await visitor.visit('User', 'update', {
|
|
272
|
+
where: { id: '1' },
|
|
273
|
+
data: {
|
|
274
|
+
posts: {
|
|
275
|
+
update: {
|
|
276
|
+
where: { id: 'p1' },
|
|
277
|
+
data: { title: 'Updated Title' },
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(updateCallback).toHaveBeenCalledTimes(2);
|
|
284
|
+
expect(updateCallback).toHaveBeenNthCalledWith(2, 'Post', expect.any(Object), expect.any(Object));
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('visits update with array', async () => {
|
|
288
|
+
const schema = createSchema({
|
|
289
|
+
User: {
|
|
290
|
+
name: 'User',
|
|
291
|
+
fields: {
|
|
292
|
+
id: createField('id', 'String'),
|
|
293
|
+
posts: createRelationField('posts', 'Post'),
|
|
294
|
+
},
|
|
295
|
+
uniqueFields: {},
|
|
296
|
+
idFields: ['id'],
|
|
297
|
+
},
|
|
298
|
+
Post: {
|
|
299
|
+
name: 'Post',
|
|
300
|
+
fields: {
|
|
301
|
+
id: createField('id', 'String'),
|
|
302
|
+
title: createField('title', 'String'),
|
|
303
|
+
},
|
|
304
|
+
uniqueFields: {},
|
|
305
|
+
idFields: ['id'],
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const updateCallback = vi.fn();
|
|
310
|
+
const visitor = new NestedWriteVisitor(schema, { update: updateCallback });
|
|
311
|
+
|
|
312
|
+
await visitor.visit('User', 'update', {
|
|
313
|
+
where: { id: '1' },
|
|
314
|
+
data: {
|
|
315
|
+
posts: {
|
|
316
|
+
update: [
|
|
317
|
+
{ where: { id: 'p1' }, data: { title: 'Title 1' } },
|
|
318
|
+
{ where: { id: 'p2' }, data: { title: 'Title 2' } },
|
|
319
|
+
],
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
expect(updateCallback).toHaveBeenCalledTimes(3); // 1 user + 2 posts
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('updateMany action', () => {
|
|
329
|
+
it('visits updateMany', async () => {
|
|
330
|
+
const schema = createSchema({
|
|
331
|
+
User: {
|
|
332
|
+
name: 'User',
|
|
333
|
+
fields: {
|
|
334
|
+
id: createField('id', 'String'),
|
|
335
|
+
active: createField('active', 'Boolean'),
|
|
336
|
+
},
|
|
337
|
+
uniqueFields: {},
|
|
338
|
+
idFields: ['id'],
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const updateManyCallback = vi.fn();
|
|
343
|
+
const visitor = new NestedWriteVisitor(schema, { updateMany: updateManyCallback });
|
|
344
|
+
|
|
345
|
+
await visitor.visit('User', 'updateMany', {
|
|
346
|
+
where: { active: false },
|
|
347
|
+
data: { active: true },
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect(updateManyCallback).toHaveBeenCalledTimes(1);
|
|
351
|
+
expect(updateManyCallback).toHaveBeenCalledWith(
|
|
352
|
+
'User',
|
|
353
|
+
{ where: { active: false }, data: { active: true } },
|
|
354
|
+
expect.any(Object),
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe('upsert action', () => {
|
|
360
|
+
it('visits upsert with create and update', async () => {
|
|
361
|
+
const schema = createSchema({
|
|
362
|
+
User: {
|
|
363
|
+
name: 'User',
|
|
364
|
+
fields: {
|
|
365
|
+
id: createField('id', 'String'),
|
|
366
|
+
name: createField('name', 'String'),
|
|
367
|
+
},
|
|
368
|
+
uniqueFields: {},
|
|
369
|
+
idFields: ['id'],
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const upsertCallback = vi.fn();
|
|
374
|
+
const visitor = new NestedWriteVisitor(schema, { upsert: upsertCallback });
|
|
375
|
+
|
|
376
|
+
await visitor.visit('User', 'upsert', {
|
|
377
|
+
where: { id: '1' },
|
|
378
|
+
create: { name: 'Alice' },
|
|
379
|
+
update: { name: 'Updated Alice' },
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
expect(upsertCallback).toHaveBeenCalledTimes(1);
|
|
383
|
+
expect(upsertCallback).toHaveBeenCalledWith(
|
|
384
|
+
'User',
|
|
385
|
+
{
|
|
386
|
+
where: { id: '1' },
|
|
387
|
+
create: { name: 'Alice' },
|
|
388
|
+
update: { name: 'Updated Alice' },
|
|
389
|
+
},
|
|
390
|
+
expect.any(Object),
|
|
391
|
+
);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('visits nested upsert in relation', async () => {
|
|
395
|
+
const schema = createSchema({
|
|
396
|
+
User: {
|
|
397
|
+
name: 'User',
|
|
398
|
+
fields: {
|
|
399
|
+
id: createField('id', 'String'),
|
|
400
|
+
profile: createRelationField('profile', 'Profile'),
|
|
401
|
+
},
|
|
402
|
+
uniqueFields: {},
|
|
403
|
+
idFields: ['id'],
|
|
404
|
+
},
|
|
405
|
+
Profile: {
|
|
406
|
+
name: 'Profile',
|
|
407
|
+
fields: {
|
|
408
|
+
id: createField('id', 'String'),
|
|
409
|
+
bio: createField('bio', 'String'),
|
|
410
|
+
},
|
|
411
|
+
uniqueFields: {},
|
|
412
|
+
idFields: ['id'],
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const upsertCallback = vi.fn();
|
|
417
|
+
const visitor = new NestedWriteVisitor(schema, { upsert: upsertCallback });
|
|
418
|
+
|
|
419
|
+
await visitor.visit('User', 'update', {
|
|
420
|
+
where: { id: '1' },
|
|
421
|
+
data: {
|
|
422
|
+
profile: {
|
|
423
|
+
upsert: {
|
|
424
|
+
where: { id: 'p1' },
|
|
425
|
+
create: { bio: 'New bio' },
|
|
426
|
+
update: { bio: 'Updated bio' },
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
expect(upsertCallback).toHaveBeenCalledWith('Profile', expect.any(Object), expect.any(Object));
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
describe('connect action', () => {
|
|
437
|
+
it('visits connect with unique filter', async () => {
|
|
438
|
+
const schema = createSchema({
|
|
439
|
+
User: {
|
|
440
|
+
name: 'User',
|
|
441
|
+
fields: {
|
|
442
|
+
id: createField('id', 'String'),
|
|
443
|
+
posts: createRelationField('posts', 'Post'),
|
|
444
|
+
},
|
|
445
|
+
uniqueFields: {},
|
|
446
|
+
idFields: ['id'],
|
|
447
|
+
},
|
|
448
|
+
Post: {
|
|
449
|
+
name: 'Post',
|
|
450
|
+
fields: {
|
|
451
|
+
id: createField('id', 'String'),
|
|
452
|
+
},
|
|
453
|
+
uniqueFields: {},
|
|
454
|
+
idFields: ['id'],
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const connectCallback = vi.fn();
|
|
459
|
+
const visitor = new NestedWriteVisitor(schema, { connect: connectCallback });
|
|
460
|
+
|
|
461
|
+
await visitor.visit('User', 'update', {
|
|
462
|
+
where: { id: '1' },
|
|
463
|
+
data: {
|
|
464
|
+
posts: {
|
|
465
|
+
connect: { id: 'p1' },
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
expect(connectCallback).toHaveBeenCalledTimes(1);
|
|
471
|
+
expect(connectCallback).toHaveBeenCalledWith('Post', { id: 'p1' }, expect.any(Object));
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('visits connect with array', async () => {
|
|
475
|
+
const schema = createSchema({
|
|
476
|
+
User: {
|
|
477
|
+
name: 'User',
|
|
478
|
+
fields: {
|
|
479
|
+
id: createField('id', 'String'),
|
|
480
|
+
posts: createRelationField('posts', 'Post'),
|
|
481
|
+
},
|
|
482
|
+
uniqueFields: {},
|
|
483
|
+
idFields: ['id'],
|
|
484
|
+
},
|
|
485
|
+
Post: {
|
|
486
|
+
name: 'Post',
|
|
487
|
+
fields: {
|
|
488
|
+
id: createField('id', 'String'),
|
|
489
|
+
},
|
|
490
|
+
uniqueFields: {},
|
|
491
|
+
idFields: ['id'],
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const connectCallback = vi.fn();
|
|
496
|
+
const visitor = new NestedWriteVisitor(schema, { connect: connectCallback });
|
|
497
|
+
|
|
498
|
+
await visitor.visit('User', 'update', {
|
|
499
|
+
where: { id: '1' },
|
|
500
|
+
data: {
|
|
501
|
+
posts: {
|
|
502
|
+
connect: [{ id: 'p1' }, { id: 'p2' }],
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
expect(connectCallback).toHaveBeenCalledTimes(2);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe('disconnect action', () => {
|
|
512
|
+
it('visits disconnect with unique filter (to-many)', async () => {
|
|
513
|
+
const schema = createSchema({
|
|
514
|
+
User: {
|
|
515
|
+
name: 'User',
|
|
516
|
+
fields: {
|
|
517
|
+
id: createField('id', 'String'),
|
|
518
|
+
posts: createRelationField('posts', 'Post'),
|
|
519
|
+
},
|
|
520
|
+
uniqueFields: {},
|
|
521
|
+
idFields: ['id'],
|
|
522
|
+
},
|
|
523
|
+
Post: {
|
|
524
|
+
name: 'Post',
|
|
525
|
+
fields: {
|
|
526
|
+
id: createField('id', 'String'),
|
|
527
|
+
},
|
|
528
|
+
uniqueFields: {},
|
|
529
|
+
idFields: ['id'],
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const disconnectCallback = vi.fn();
|
|
534
|
+
const visitor = new NestedWriteVisitor(schema, { disconnect: disconnectCallback });
|
|
535
|
+
|
|
536
|
+
await visitor.visit('User', 'update', {
|
|
537
|
+
where: { id: '1' },
|
|
538
|
+
data: {
|
|
539
|
+
posts: {
|
|
540
|
+
disconnect: { id: 'p1' },
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
expect(disconnectCallback).toHaveBeenCalledTimes(1);
|
|
546
|
+
expect(disconnectCallback).toHaveBeenCalledWith('Post', { id: 'p1' }, expect.any(Object));
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('visits disconnect with boolean (to-one)', async () => {
|
|
550
|
+
const schema = createSchema({
|
|
551
|
+
User: {
|
|
552
|
+
name: 'User',
|
|
553
|
+
fields: {
|
|
554
|
+
id: createField('id', 'String'),
|
|
555
|
+
profile: createRelationField('profile', 'Profile', true),
|
|
556
|
+
},
|
|
557
|
+
uniqueFields: {},
|
|
558
|
+
idFields: ['id'],
|
|
559
|
+
},
|
|
560
|
+
Profile: {
|
|
561
|
+
name: 'Profile',
|
|
562
|
+
fields: {
|
|
563
|
+
id: createField('id', 'String'),
|
|
564
|
+
},
|
|
565
|
+
uniqueFields: {},
|
|
566
|
+
idFields: ['id'],
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const disconnectCallback = vi.fn();
|
|
571
|
+
const visitor = new NestedWriteVisitor(schema, { disconnect: disconnectCallback });
|
|
572
|
+
|
|
573
|
+
await visitor.visit('User', 'update', {
|
|
574
|
+
where: { id: '1' },
|
|
575
|
+
data: {
|
|
576
|
+
profile: {
|
|
577
|
+
disconnect: true,
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
expect(disconnectCallback).toHaveBeenCalledTimes(1);
|
|
583
|
+
expect(disconnectCallback).toHaveBeenCalledWith('Profile', true, expect.any(Object));
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
describe('set action', () => {
|
|
588
|
+
it('visits set with unique filters', async () => {
|
|
589
|
+
const schema = createSchema({
|
|
590
|
+
User: {
|
|
591
|
+
name: 'User',
|
|
592
|
+
fields: {
|
|
593
|
+
id: createField('id', 'String'),
|
|
594
|
+
posts: createRelationField('posts', 'Post'),
|
|
595
|
+
},
|
|
596
|
+
uniqueFields: {},
|
|
597
|
+
idFields: ['id'],
|
|
598
|
+
},
|
|
599
|
+
Post: {
|
|
600
|
+
name: 'Post',
|
|
601
|
+
fields: {
|
|
602
|
+
id: createField('id', 'String'),
|
|
603
|
+
},
|
|
604
|
+
uniqueFields: {},
|
|
605
|
+
idFields: ['id'],
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const setCallback = vi.fn();
|
|
610
|
+
const visitor = new NestedWriteVisitor(schema, { set: setCallback });
|
|
611
|
+
|
|
612
|
+
await visitor.visit('User', 'update', {
|
|
613
|
+
where: { id: '1' },
|
|
614
|
+
data: {
|
|
615
|
+
posts: {
|
|
616
|
+
set: [{ id: 'p1' }, { id: 'p2' }],
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
expect(setCallback).toHaveBeenCalledTimes(2);
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
describe('delete action', () => {
|
|
626
|
+
it('visits delete with where clause', async () => {
|
|
627
|
+
const schema = createSchema({
|
|
628
|
+
User: {
|
|
629
|
+
name: 'User',
|
|
630
|
+
fields: {
|
|
631
|
+
id: createField('id', 'String'),
|
|
632
|
+
},
|
|
633
|
+
uniqueFields: {},
|
|
634
|
+
idFields: ['id'],
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
const deleteCallback = vi.fn();
|
|
639
|
+
const visitor = new NestedWriteVisitor(schema, { delete: deleteCallback });
|
|
640
|
+
|
|
641
|
+
await visitor.visit('User', 'delete', {
|
|
642
|
+
where: { id: '1' },
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
expect(deleteCallback).toHaveBeenCalledTimes(1);
|
|
646
|
+
// For top-level delete, the callback receives the full args object including where
|
|
647
|
+
expect(deleteCallback).toHaveBeenCalledWith('User', { id: '1' }, expect.any(Object));
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it('visits nested delete in relation', async () => {
|
|
651
|
+
const schema = createSchema({
|
|
652
|
+
User: {
|
|
653
|
+
name: 'User',
|
|
654
|
+
fields: {
|
|
655
|
+
id: createField('id', 'String'),
|
|
656
|
+
posts: createRelationField('posts', 'Post'),
|
|
657
|
+
},
|
|
658
|
+
uniqueFields: {},
|
|
659
|
+
idFields: ['id'],
|
|
660
|
+
},
|
|
661
|
+
Post: {
|
|
662
|
+
name: 'Post',
|
|
663
|
+
fields: {
|
|
664
|
+
id: createField('id', 'String'),
|
|
665
|
+
},
|
|
666
|
+
uniqueFields: {},
|
|
667
|
+
idFields: ['id'],
|
|
668
|
+
},
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
const deleteCallback = vi.fn();
|
|
672
|
+
const visitor = new NestedWriteVisitor(schema, { delete: deleteCallback });
|
|
673
|
+
|
|
674
|
+
await visitor.visit('User', 'update', {
|
|
675
|
+
where: { id: '1' },
|
|
676
|
+
data: {
|
|
677
|
+
posts: {
|
|
678
|
+
delete: { id: 'p1' },
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
expect(deleteCallback).toHaveBeenCalledTimes(1);
|
|
684
|
+
expect(deleteCallback).toHaveBeenCalledWith('Post', { id: 'p1' }, expect.any(Object));
|
|
685
|
+
});
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
describe('deleteMany action', () => {
|
|
689
|
+
it('visits deleteMany with where clause', async () => {
|
|
690
|
+
const schema = createSchema({
|
|
691
|
+
User: {
|
|
692
|
+
name: 'User',
|
|
693
|
+
fields: {
|
|
694
|
+
id: createField('id', 'String'),
|
|
695
|
+
active: createField('active', 'Boolean'),
|
|
696
|
+
},
|
|
697
|
+
uniqueFields: {},
|
|
698
|
+
idFields: ['id'],
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
const deleteManyCallback = vi.fn();
|
|
703
|
+
const visitor = new NestedWriteVisitor(schema, { deleteMany: deleteManyCallback });
|
|
704
|
+
|
|
705
|
+
await visitor.visit('User', 'deleteMany', {
|
|
706
|
+
where: { active: false },
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
expect(deleteManyCallback).toHaveBeenCalledTimes(1);
|
|
710
|
+
// For top-level deleteMany, the callback receives the where clause directly
|
|
711
|
+
expect(deleteManyCallback).toHaveBeenCalledWith('User', { active: false }, expect.any(Object));
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
describe('connectOrCreate action', () => {
|
|
716
|
+
it('visits connectOrCreate with where and create', async () => {
|
|
717
|
+
const schema = createSchema({
|
|
718
|
+
User: {
|
|
719
|
+
name: 'User',
|
|
720
|
+
fields: {
|
|
721
|
+
id: createField('id', 'String'),
|
|
722
|
+
posts: createRelationField('posts', 'Post'),
|
|
723
|
+
},
|
|
724
|
+
uniqueFields: {},
|
|
725
|
+
idFields: ['id'],
|
|
726
|
+
},
|
|
727
|
+
Post: {
|
|
728
|
+
name: 'Post',
|
|
729
|
+
fields: {
|
|
730
|
+
id: createField('id', 'String'),
|
|
731
|
+
title: createField('title', 'String'),
|
|
732
|
+
},
|
|
733
|
+
uniqueFields: {},
|
|
734
|
+
idFields: ['id'],
|
|
735
|
+
},
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
const connectOrCreateCallback = vi.fn();
|
|
739
|
+
const visitor = new NestedWriteVisitor(schema, { connectOrCreate: connectOrCreateCallback });
|
|
740
|
+
|
|
741
|
+
await visitor.visit('User', 'update', {
|
|
742
|
+
where: { id: '1' },
|
|
743
|
+
data: {
|
|
744
|
+
posts: {
|
|
745
|
+
connectOrCreate: {
|
|
746
|
+
where: { id: 'p1' },
|
|
747
|
+
create: { title: 'New Post' },
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
expect(connectOrCreateCallback).toHaveBeenCalledTimes(1);
|
|
754
|
+
expect(connectOrCreateCallback).toHaveBeenCalledWith(
|
|
755
|
+
'Post',
|
|
756
|
+
{ where: { id: 'p1' }, create: { title: 'New Post' } },
|
|
757
|
+
expect.any(Object),
|
|
758
|
+
);
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
describe('field callback', () => {
|
|
763
|
+
it('visits scalar fields during create', async () => {
|
|
764
|
+
const schema = createSchema({
|
|
765
|
+
User: {
|
|
766
|
+
name: 'User',
|
|
767
|
+
fields: {
|
|
768
|
+
id: createField('id', 'String'),
|
|
769
|
+
name: createField('name', 'String'),
|
|
770
|
+
email: createField('email', 'String'),
|
|
771
|
+
},
|
|
772
|
+
uniqueFields: {},
|
|
773
|
+
idFields: ['id'],
|
|
774
|
+
},
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
const fieldCallback = vi.fn();
|
|
778
|
+
const visitor = new NestedWriteVisitor(schema, { field: fieldCallback });
|
|
779
|
+
|
|
780
|
+
await visitor.visit('User', 'create', {
|
|
781
|
+
data: {
|
|
782
|
+
name: 'Alice',
|
|
783
|
+
email: 'alice@example.com',
|
|
784
|
+
},
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
expect(fieldCallback).toHaveBeenCalledTimes(2);
|
|
788
|
+
expect(fieldCallback).toHaveBeenCalledWith(
|
|
789
|
+
expect.objectContaining({ name: 'name' }),
|
|
790
|
+
'create',
|
|
791
|
+
'Alice',
|
|
792
|
+
expect.any(Object),
|
|
793
|
+
);
|
|
794
|
+
expect(fieldCallback).toHaveBeenCalledWith(
|
|
795
|
+
expect.objectContaining({ name: 'email' }),
|
|
796
|
+
'create',
|
|
797
|
+
'alice@example.com',
|
|
798
|
+
expect.any(Object),
|
|
799
|
+
);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it('visits scalar fields during update', async () => {
|
|
803
|
+
const schema = createSchema({
|
|
804
|
+
User: {
|
|
805
|
+
name: 'User',
|
|
806
|
+
fields: {
|
|
807
|
+
id: createField('id', 'String'),
|
|
808
|
+
name: createField('name', 'String'),
|
|
809
|
+
},
|
|
810
|
+
uniqueFields: {},
|
|
811
|
+
idFields: ['id'],
|
|
812
|
+
},
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
const fieldCallback = vi.fn();
|
|
816
|
+
const visitor = new NestedWriteVisitor(schema, { field: fieldCallback });
|
|
817
|
+
|
|
818
|
+
await visitor.visit('User', 'update', {
|
|
819
|
+
where: { id: '1' },
|
|
820
|
+
data: { name: 'Updated Name' },
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
expect(fieldCallback).toHaveBeenCalledWith(
|
|
824
|
+
expect.objectContaining({ name: 'name' }),
|
|
825
|
+
'update',
|
|
826
|
+
'Updated Name',
|
|
827
|
+
expect.any(Object),
|
|
828
|
+
);
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it('provides correct parent in context', async () => {
|
|
832
|
+
const schema = createSchema({
|
|
833
|
+
User: {
|
|
834
|
+
name: 'User',
|
|
835
|
+
fields: {
|
|
836
|
+
id: createField('id', 'String'),
|
|
837
|
+
name: createField('name', 'String'),
|
|
838
|
+
},
|
|
839
|
+
uniqueFields: {},
|
|
840
|
+
idFields: ['id'],
|
|
841
|
+
},
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
const fieldCallback = vi.fn();
|
|
845
|
+
const visitor = new NestedWriteVisitor(schema, { field: fieldCallback });
|
|
846
|
+
|
|
847
|
+
const data = { name: 'Alice' };
|
|
848
|
+
await visitor.visit('User', 'create', { data });
|
|
849
|
+
|
|
850
|
+
expect(fieldCallback).toHaveBeenCalledWith(
|
|
851
|
+
expect.anything(),
|
|
852
|
+
'create',
|
|
853
|
+
'Alice',
|
|
854
|
+
expect.objectContaining({ parent: data }),
|
|
855
|
+
);
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
describe('context and nesting path', () => {
|
|
860
|
+
it('builds nesting path correctly', async () => {
|
|
861
|
+
const schema = createSchema({
|
|
862
|
+
User: {
|
|
863
|
+
name: 'User',
|
|
864
|
+
fields: {
|
|
865
|
+
id: createField('id', 'String'),
|
|
866
|
+
posts: createRelationField('posts', 'Post'),
|
|
867
|
+
},
|
|
868
|
+
uniqueFields: {},
|
|
869
|
+
idFields: ['id'],
|
|
870
|
+
},
|
|
871
|
+
Post: {
|
|
872
|
+
name: 'Post',
|
|
873
|
+
fields: {
|
|
874
|
+
id: createField('id', 'String'),
|
|
875
|
+
comments: createRelationField('comments', 'Comment'),
|
|
876
|
+
},
|
|
877
|
+
uniqueFields: {},
|
|
878
|
+
idFields: ['id'],
|
|
879
|
+
},
|
|
880
|
+
Comment: {
|
|
881
|
+
name: 'Comment',
|
|
882
|
+
fields: {
|
|
883
|
+
id: createField('id', 'String'),
|
|
884
|
+
text: createField('text', 'String'),
|
|
885
|
+
},
|
|
886
|
+
uniqueFields: {},
|
|
887
|
+
idFields: ['id'],
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
let commentContext: NestedWriteVisitorContext | undefined;
|
|
892
|
+
const createCallback = vi.fn((model, _data, context) => {
|
|
893
|
+
if (model === 'Comment') {
|
|
894
|
+
commentContext = context;
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
const visitor = new NestedWriteVisitor(schema, { create: createCallback });
|
|
899
|
+
|
|
900
|
+
await visitor.visit('User', 'create', {
|
|
901
|
+
data: {
|
|
902
|
+
name: 'Alice',
|
|
903
|
+
posts: {
|
|
904
|
+
create: {
|
|
905
|
+
title: 'Post',
|
|
906
|
+
comments: {
|
|
907
|
+
create: { text: 'Comment' },
|
|
908
|
+
},
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
expect(commentContext).toBeDefined();
|
|
915
|
+
expect(commentContext!.nestingPath).toHaveLength(3);
|
|
916
|
+
expect(commentContext!.nestingPath[0]?.model).toBe('User');
|
|
917
|
+
expect(commentContext!.nestingPath[1]?.model).toBe('Post');
|
|
918
|
+
expect(commentContext!.nestingPath[2]?.model).toBe('Comment');
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
it('includes field in nesting path', async () => {
|
|
922
|
+
const schema = createSchema({
|
|
923
|
+
User: {
|
|
924
|
+
name: 'User',
|
|
925
|
+
fields: {
|
|
926
|
+
id: createField('id', 'String'),
|
|
927
|
+
posts: createRelationField('posts', 'Post'),
|
|
928
|
+
},
|
|
929
|
+
uniqueFields: {},
|
|
930
|
+
idFields: ['id'],
|
|
931
|
+
},
|
|
932
|
+
Post: {
|
|
933
|
+
name: 'Post',
|
|
934
|
+
fields: {
|
|
935
|
+
id: createField('id', 'String'),
|
|
936
|
+
title: createField('title', 'String'),
|
|
937
|
+
},
|
|
938
|
+
uniqueFields: {},
|
|
939
|
+
idFields: ['id'],
|
|
940
|
+
},
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
let postContext: NestedWriteVisitorContext | undefined;
|
|
944
|
+
const createCallback = vi.fn((model, _data, context) => {
|
|
945
|
+
if (model === 'Post') {
|
|
946
|
+
postContext = context;
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
const visitor = new NestedWriteVisitor(schema, { create: createCallback });
|
|
951
|
+
|
|
952
|
+
await visitor.visit('User', 'create', {
|
|
953
|
+
data: {
|
|
954
|
+
posts: {
|
|
955
|
+
create: { title: 'Post' },
|
|
956
|
+
},
|
|
957
|
+
},
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
expect(postContext).toBeDefined();
|
|
961
|
+
expect(postContext!.field).toBeDefined();
|
|
962
|
+
expect(postContext!.field?.name).toBe('posts');
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
it('includes where clause in nesting path for update', async () => {
|
|
966
|
+
const schema = createSchema({
|
|
967
|
+
User: {
|
|
968
|
+
name: 'User',
|
|
969
|
+
fields: {
|
|
970
|
+
id: createField('id', 'String'),
|
|
971
|
+
posts: createRelationField('posts', 'Post'),
|
|
972
|
+
},
|
|
973
|
+
uniqueFields: {},
|
|
974
|
+
idFields: ['id'],
|
|
975
|
+
},
|
|
976
|
+
Post: {
|
|
977
|
+
name: 'Post',
|
|
978
|
+
fields: {
|
|
979
|
+
id: createField('id', 'String'),
|
|
980
|
+
title: createField('title', 'String'),
|
|
981
|
+
},
|
|
982
|
+
uniqueFields: {},
|
|
983
|
+
idFields: ['id'],
|
|
984
|
+
},
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
let postContext: NestedWriteVisitorContext | undefined;
|
|
988
|
+
const updateCallback = vi.fn((model, _args, context) => {
|
|
989
|
+
if (model === 'Post') {
|
|
990
|
+
postContext = context;
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
const visitor = new NestedWriteVisitor(schema, { update: updateCallback });
|
|
995
|
+
|
|
996
|
+
await visitor.visit('User', 'update', {
|
|
997
|
+
where: { id: '1' },
|
|
998
|
+
data: {
|
|
999
|
+
posts: {
|
|
1000
|
+
update: {
|
|
1001
|
+
where: { id: 'p1' },
|
|
1002
|
+
data: { title: 'Updated' },
|
|
1003
|
+
},
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
expect(postContext).toBeDefined();
|
|
1009
|
+
expect(postContext!.nestingPath).toHaveLength(2);
|
|
1010
|
+
expect(postContext!.nestingPath[1]?.where).toEqual({ id: 'p1' });
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
describe('edge cases', () => {
|
|
1015
|
+
it('handles null args gracefully', async () => {
|
|
1016
|
+
const schema = createSchema({
|
|
1017
|
+
User: {
|
|
1018
|
+
name: 'User',
|
|
1019
|
+
fields: {
|
|
1020
|
+
id: createField('id', 'String'),
|
|
1021
|
+
},
|
|
1022
|
+
uniqueFields: {},
|
|
1023
|
+
idFields: ['id'],
|
|
1024
|
+
},
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
const createCallback = vi.fn();
|
|
1028
|
+
const visitor = new NestedWriteVisitor(schema, { create: createCallback });
|
|
1029
|
+
|
|
1030
|
+
await visitor.visit('User', 'create', null);
|
|
1031
|
+
|
|
1032
|
+
expect(createCallback).not.toHaveBeenCalled();
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
it('handles undefined args gracefully', async () => {
|
|
1036
|
+
const schema = createSchema({
|
|
1037
|
+
User: {
|
|
1038
|
+
name: 'User',
|
|
1039
|
+
fields: {
|
|
1040
|
+
id: createField('id', 'String'),
|
|
1041
|
+
},
|
|
1042
|
+
uniqueFields: {},
|
|
1043
|
+
idFields: ['id'],
|
|
1044
|
+
},
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
const createCallback = vi.fn();
|
|
1048
|
+
const visitor = new NestedWriteVisitor(schema, { create: createCallback });
|
|
1049
|
+
|
|
1050
|
+
await visitor.visit('User', 'create', undefined);
|
|
1051
|
+
|
|
1052
|
+
expect(createCallback).not.toHaveBeenCalled();
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
it('handles fields not in schema gracefully', async () => {
|
|
1056
|
+
const schema = createSchema({
|
|
1057
|
+
User: {
|
|
1058
|
+
name: 'User',
|
|
1059
|
+
fields: {
|
|
1060
|
+
id: createField('id', 'String'),
|
|
1061
|
+
},
|
|
1062
|
+
uniqueFields: {},
|
|
1063
|
+
idFields: ['id'],
|
|
1064
|
+
},
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
const fieldCallback = vi.fn();
|
|
1068
|
+
const visitor = new NestedWriteVisitor(schema, { field: fieldCallback });
|
|
1069
|
+
|
|
1070
|
+
await visitor.visit('User', 'create', {
|
|
1071
|
+
data: {
|
|
1072
|
+
nonExistentField: 'value',
|
|
1073
|
+
},
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
// Should not visit non-existent field
|
|
1077
|
+
expect(fieldCallback).not.toHaveBeenCalled();
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
it('handles visitor with no callbacks', async () => {
|
|
1081
|
+
const schema = createSchema({
|
|
1082
|
+
User: {
|
|
1083
|
+
name: 'User',
|
|
1084
|
+
fields: {
|
|
1085
|
+
id: createField('id', 'String'),
|
|
1086
|
+
},
|
|
1087
|
+
uniqueFields: {},
|
|
1088
|
+
idFields: ['id'],
|
|
1089
|
+
},
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
const visitor = new NestedWriteVisitor(schema, {});
|
|
1093
|
+
|
|
1094
|
+
await expect(
|
|
1095
|
+
visitor.visit('User', 'create', {
|
|
1096
|
+
data: { name: 'Alice' },
|
|
1097
|
+
}),
|
|
1098
|
+
).resolves.not.toThrow();
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
it('throws error for unhandled action type', async () => {
|
|
1102
|
+
const schema = createSchema({
|
|
1103
|
+
User: {
|
|
1104
|
+
name: 'User',
|
|
1105
|
+
fields: {
|
|
1106
|
+
id: createField('id', 'String'),
|
|
1107
|
+
},
|
|
1108
|
+
uniqueFields: {},
|
|
1109
|
+
idFields: ['id'],
|
|
1110
|
+
},
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
const visitor = new NestedWriteVisitor(schema, {});
|
|
1114
|
+
|
|
1115
|
+
await expect(visitor.visit('User', 'invalidAction' as any, { data: {} })).rejects.toThrow(
|
|
1116
|
+
'unhandled action type',
|
|
1117
|
+
);
|
|
1118
|
+
});
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
describe('complex real-world scenarios', () => {
|
|
1122
|
+
it('handles deeply nested create operations', async () => {
|
|
1123
|
+
const schema = createSchema({
|
|
1124
|
+
User: {
|
|
1125
|
+
name: 'User',
|
|
1126
|
+
fields: {
|
|
1127
|
+
id: createField('id', 'String'),
|
|
1128
|
+
name: createField('name', 'String'),
|
|
1129
|
+
posts: createRelationField('posts', 'Post'),
|
|
1130
|
+
},
|
|
1131
|
+
uniqueFields: {},
|
|
1132
|
+
idFields: ['id'],
|
|
1133
|
+
},
|
|
1134
|
+
Post: {
|
|
1135
|
+
name: 'Post',
|
|
1136
|
+
fields: {
|
|
1137
|
+
id: createField('id', 'String'),
|
|
1138
|
+
title: createField('title', 'String'),
|
|
1139
|
+
comments: createRelationField('comments', 'Comment'),
|
|
1140
|
+
},
|
|
1141
|
+
uniqueFields: {},
|
|
1142
|
+
idFields: ['id'],
|
|
1143
|
+
},
|
|
1144
|
+
Comment: {
|
|
1145
|
+
name: 'Comment',
|
|
1146
|
+
fields: {
|
|
1147
|
+
id: createField('id', 'String'),
|
|
1148
|
+
text: createField('text', 'String'),
|
|
1149
|
+
author: createRelationField('author', 'User'),
|
|
1150
|
+
},
|
|
1151
|
+
uniqueFields: {},
|
|
1152
|
+
idFields: ['id'],
|
|
1153
|
+
},
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
const visitedModels: string[] = [];
|
|
1157
|
+
const createCallback = vi.fn((model) => {
|
|
1158
|
+
visitedModels.push(model);
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
const visitor = new NestedWriteVisitor(schema, { create: createCallback });
|
|
1162
|
+
|
|
1163
|
+
await visitor.visit('User', 'create', {
|
|
1164
|
+
data: {
|
|
1165
|
+
name: 'Alice',
|
|
1166
|
+
posts: {
|
|
1167
|
+
create: {
|
|
1168
|
+
title: 'Post',
|
|
1169
|
+
comments: {
|
|
1170
|
+
create: {
|
|
1171
|
+
text: 'Comment',
|
|
1172
|
+
author: {
|
|
1173
|
+
create: { name: 'Bob' },
|
|
1174
|
+
},
|
|
1175
|
+
},
|
|
1176
|
+
},
|
|
1177
|
+
},
|
|
1178
|
+
},
|
|
1179
|
+
},
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
expect(visitedModels).toContain('User');
|
|
1183
|
+
expect(visitedModels).toContain('Post');
|
|
1184
|
+
expect(visitedModels).toContain('Comment');
|
|
1185
|
+
expect(visitedModels.filter((m) => m === 'User').length).toBe(2); // Alice and Bob
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
it('handles mixed operations in update', async () => {
|
|
1189
|
+
const schema = createSchema({
|
|
1190
|
+
User: {
|
|
1191
|
+
name: 'User',
|
|
1192
|
+
fields: {
|
|
1193
|
+
id: createField('id', 'String'),
|
|
1194
|
+
posts: createRelationField('posts', 'Post'),
|
|
1195
|
+
},
|
|
1196
|
+
uniqueFields: {},
|
|
1197
|
+
idFields: ['id'],
|
|
1198
|
+
},
|
|
1199
|
+
Post: {
|
|
1200
|
+
name: 'Post',
|
|
1201
|
+
fields: {
|
|
1202
|
+
id: createField('id', 'String'),
|
|
1203
|
+
title: createField('title', 'String'),
|
|
1204
|
+
},
|
|
1205
|
+
uniqueFields: {},
|
|
1206
|
+
idFields: ['id'],
|
|
1207
|
+
},
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
const createCallback = vi.fn();
|
|
1211
|
+
const updateCallback = vi.fn();
|
|
1212
|
+
const deleteCallback = vi.fn();
|
|
1213
|
+
const connectCallback = vi.fn();
|
|
1214
|
+
|
|
1215
|
+
const visitor = new NestedWriteVisitor(schema, {
|
|
1216
|
+
create: createCallback,
|
|
1217
|
+
update: updateCallback,
|
|
1218
|
+
delete: deleteCallback,
|
|
1219
|
+
connect: connectCallback,
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
await visitor.visit('User', 'update', {
|
|
1223
|
+
where: { id: '1' },
|
|
1224
|
+
data: {
|
|
1225
|
+
posts: {
|
|
1226
|
+
create: { title: 'New Post' },
|
|
1227
|
+
update: { where: { id: 'p1' }, data: { title: 'Updated' } },
|
|
1228
|
+
delete: { id: 'p2' },
|
|
1229
|
+
connect: { id: 'p3' },
|
|
1230
|
+
},
|
|
1231
|
+
},
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
expect(createCallback).toHaveBeenCalledWith('Post', { title: 'New Post' }, expect.any(Object));
|
|
1235
|
+
expect(updateCallback).toHaveBeenCalledWith(
|
|
1236
|
+
'Post',
|
|
1237
|
+
{ where: { id: 'p1' }, data: { title: 'Updated' } },
|
|
1238
|
+
expect.any(Object),
|
|
1239
|
+
);
|
|
1240
|
+
expect(deleteCallback).toHaveBeenCalledWith('Post', { id: 'p2' }, expect.any(Object));
|
|
1241
|
+
expect(connectCallback).toHaveBeenCalledWith('Post', { id: 'p3' }, expect.any(Object));
|
|
1242
|
+
});
|
|
1243
|
+
});
|
|
1244
|
+
});
|