@zenstackhq/tanstack-query 2.21.0 → 3.0.0-beta.17
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 +31 -0
- package/LICENSE +1 -1
- package/dist/react.cjs +1238 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +696 -0
- package/dist/react.d.ts +696 -0
- package/dist/react.js +1195 -0
- package/dist/react.js.map +1 -0
- package/eslint.config.js +4 -0
- package/package.json +56 -109
- package/scripts/generate.ts +27 -0
- package/src/react.ts +531 -0
- package/src/utils/common.ts +457 -0
- package/src/utils/mutator.ts +441 -0
- package/src/utils/nested-read-visitor.ts +61 -0
- package/src/utils/nested-write-visitor.ts +359 -0
- package/src/utils/query-analysis.ts +116 -0
- package/src/utils/serialization.ts +39 -0
- package/src/utils/types.ts +19 -0
- package/test/react-query.test.tsx +1787 -0
- package/test/schemas/basic/input.ts +70 -0
- package/test/schemas/basic/models.ts +12 -0
- package/test/schemas/basic/schema-lite.ts +124 -0
- package/test/schemas/basic/schema.zmodel +25 -0
- package/tsconfig.json +7 -0
- package/tsconfig.test.json +8 -0
- package/tsup.config.ts +13 -0
- package/vitest.config.ts +11 -0
- package/README.md +0 -5
- package/generator.d.ts +0 -6
- package/generator.js +0 -578
- package/generator.js.map +0 -1
- package/index.d.ts +0 -4
- package/index.js +0 -22
- package/index.js.map +0 -1
- package/runtime/common-CXlL7vTW.d.mts +0 -121
- package/runtime/common-CXlL7vTW.d.ts +0 -121
- package/runtime/index.d.mts +0 -20
- package/runtime/index.d.ts +0 -20
- package/runtime/index.js +0 -44
- package/runtime/index.js.map +0 -1
- package/runtime/index.mjs +0 -21
- package/runtime/index.mjs.map +0 -1
- package/runtime/react.d.mts +0 -322
- package/runtime/react.d.ts +0 -322
- package/runtime/react.js +0 -408
- package/runtime/react.js.map +0 -1
- package/runtime/react.mjs +0 -380
- package/runtime/react.mjs.map +0 -1
- package/runtime/svelte.d.mts +0 -322
- package/runtime/svelte.d.ts +0 -322
- package/runtime/svelte.js +0 -407
- package/runtime/svelte.js.map +0 -1
- package/runtime/svelte.mjs +0 -379
- package/runtime/svelte.mjs.map +0 -1
- package/runtime/vue.d.mts +0 -330
- package/runtime/vue.d.ts +0 -330
- package/runtime/vue.js +0 -418
- package/runtime/vue.js.map +0 -1
- package/runtime/vue.mjs +0 -390
- package/runtime/vue.mjs.map +0 -1
- package/runtime-v5/angular.d.mts +0 -59
- package/runtime-v5/angular.d.ts +0 -59
- package/runtime-v5/angular.js +0 -425
- package/runtime-v5/angular.js.map +0 -1
- package/runtime-v5/angular.mjs +0 -397
- package/runtime-v5/angular.mjs.map +0 -1
- package/runtime-v5/common-CXlL7vTW.d.mts +0 -121
- package/runtime-v5/common-CXlL7vTW.d.ts +0 -121
- package/runtime-v5/index.d.mts +0 -20
- package/runtime-v5/index.d.ts +0 -20
- package/runtime-v5/index.js +0 -44
- package/runtime-v5/index.js.map +0 -1
- package/runtime-v5/index.mjs +0 -21
- package/runtime-v5/index.mjs.map +0 -1
- package/runtime-v5/react.d.mts +0 -474
- package/runtime-v5/react.d.ts +0 -474
- package/runtime-v5/react.js +0 -440
- package/runtime-v5/react.js.map +0 -1
- package/runtime-v5/react.mjs +0 -412
- package/runtime-v5/react.mjs.map +0 -1
- package/runtime-v5/svelte.d.mts +0 -386
- package/runtime-v5/svelte.d.ts +0 -386
- package/runtime-v5/svelte.js +0 -436
- package/runtime-v5/svelte.js.map +0 -1
- package/runtime-v5/svelte.mjs +0 -408
- package/runtime-v5/svelte.mjs.map +0 -1
- package/runtime-v5/vue.d.mts +0 -330
- package/runtime-v5/vue.d.ts +0 -330
- package/runtime-v5/vue.js +0 -420
- package/runtime-v5/vue.js.map +0 -1
- package/runtime-v5/vue.mjs +0 -392
- package/runtime-v5/vue.mjs.map +0 -1
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { clone, enumerate, invariant, zip } from '@zenstackhq/common-helpers';
|
|
2
|
+
import type { FieldDef, SchemaDef } from '@zenstackhq/schema';
|
|
3
|
+
import { NestedWriteVisitor } from './nested-write-visitor';
|
|
4
|
+
import type { ORMWriteActionType } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Tries to apply a mutation to a query result.
|
|
8
|
+
*
|
|
9
|
+
* @param queryModel the model of the query
|
|
10
|
+
* @param queryOp the operation of the query
|
|
11
|
+
* @param queryData the result data of the query
|
|
12
|
+
* @param mutationModel the model of the mutation
|
|
13
|
+
* @param mutationOp the operation of the mutation
|
|
14
|
+
* @param mutationArgs the arguments of the mutation
|
|
15
|
+
* @param schema the model metadata
|
|
16
|
+
* @param logging whether to log the mutation application
|
|
17
|
+
* @returns the updated query data if the mutation is applicable, otherwise undefined
|
|
18
|
+
*/
|
|
19
|
+
export async function applyMutation(
|
|
20
|
+
queryModel: string,
|
|
21
|
+
queryOp: string,
|
|
22
|
+
queryData: any,
|
|
23
|
+
mutationModel: string,
|
|
24
|
+
mutationOp: ORMWriteActionType,
|
|
25
|
+
mutationArgs: any,
|
|
26
|
+
schema: SchemaDef,
|
|
27
|
+
logging: boolean,
|
|
28
|
+
) {
|
|
29
|
+
if (!queryData || (typeof queryData !== 'object' && !Array.isArray(queryData))) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!queryOp.startsWith('find')) {
|
|
34
|
+
// only findXXX results are applicable
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return await doApplyMutation(queryModel, queryData, mutationModel, mutationOp, mutationArgs, schema, logging);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function doApplyMutation(
|
|
42
|
+
queryModel: string,
|
|
43
|
+
queryData: any,
|
|
44
|
+
mutationModel: string,
|
|
45
|
+
mutationOp: ORMWriteActionType,
|
|
46
|
+
mutationArgs: any,
|
|
47
|
+
schema: SchemaDef,
|
|
48
|
+
logging: boolean,
|
|
49
|
+
) {
|
|
50
|
+
let resultData = queryData;
|
|
51
|
+
let updated = false;
|
|
52
|
+
|
|
53
|
+
const visitor = new NestedWriteVisitor(schema, {
|
|
54
|
+
create: (model, args) => {
|
|
55
|
+
if (
|
|
56
|
+
model === queryModel &&
|
|
57
|
+
Array.isArray(resultData) // "create" mutation is only relevant for arrays
|
|
58
|
+
) {
|
|
59
|
+
const r = createMutate(queryModel, resultData, args, schema, logging);
|
|
60
|
+
if (r) {
|
|
61
|
+
resultData = r;
|
|
62
|
+
updated = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
createMany: (model, args) => {
|
|
68
|
+
if (
|
|
69
|
+
model === queryModel &&
|
|
70
|
+
args?.data &&
|
|
71
|
+
Array.isArray(resultData) // "createMany" mutation is only relevant for arrays
|
|
72
|
+
) {
|
|
73
|
+
for (const oneArg of enumerate(args.data)) {
|
|
74
|
+
const r = createMutate(queryModel, resultData, oneArg, schema, logging);
|
|
75
|
+
if (r) {
|
|
76
|
+
resultData = r;
|
|
77
|
+
updated = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
update: (model, args) => {
|
|
84
|
+
if (
|
|
85
|
+
model === queryModel &&
|
|
86
|
+
!Array.isArray(resultData) // array elements will be handled with recursion
|
|
87
|
+
) {
|
|
88
|
+
const r = updateMutate(queryModel, resultData, model, args, schema, logging);
|
|
89
|
+
if (r) {
|
|
90
|
+
resultData = r;
|
|
91
|
+
updated = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
upsert: (model, args) => {
|
|
97
|
+
if (model === queryModel && args?.where && args?.create && args?.update) {
|
|
98
|
+
const r = upsertMutate(queryModel, resultData, model, args, schema, logging);
|
|
99
|
+
if (r) {
|
|
100
|
+
resultData = r;
|
|
101
|
+
updated = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
delete: (model, args) => {
|
|
107
|
+
if (model === queryModel) {
|
|
108
|
+
const r = deleteMutate(queryModel, resultData, model, args, schema, logging);
|
|
109
|
+
if (r) {
|
|
110
|
+
resultData = r;
|
|
111
|
+
updated = true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await visitor.visit(mutationModel, mutationOp, mutationArgs);
|
|
118
|
+
|
|
119
|
+
const modelFields = schema.models[queryModel]?.fields;
|
|
120
|
+
invariant(modelFields, `Model ${queryModel} not found in schema`);
|
|
121
|
+
|
|
122
|
+
if (Array.isArray(resultData)) {
|
|
123
|
+
// try to apply mutation to each item in the array, replicate the entire
|
|
124
|
+
// array if any item is updated
|
|
125
|
+
|
|
126
|
+
let arrayCloned = false;
|
|
127
|
+
for (let i = 0; i < resultData.length; i++) {
|
|
128
|
+
const item = resultData[i];
|
|
129
|
+
if (
|
|
130
|
+
!item ||
|
|
131
|
+
typeof item !== 'object' ||
|
|
132
|
+
item.$optimistic // skip items already optimistically updated
|
|
133
|
+
) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const r = await doApplyMutation(queryModel, item, mutationModel, mutationOp, mutationArgs, schema, logging);
|
|
138
|
+
|
|
139
|
+
if (r && typeof r === 'object') {
|
|
140
|
+
if (!arrayCloned) {
|
|
141
|
+
resultData = [...resultData];
|
|
142
|
+
arrayCloned = true;
|
|
143
|
+
}
|
|
144
|
+
resultData[i] = r;
|
|
145
|
+
updated = true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} else if (resultData !== null && typeof resultData === 'object') {
|
|
149
|
+
// Clone resultData to prevent mutations affecting the loop
|
|
150
|
+
const currentData = { ...resultData };
|
|
151
|
+
|
|
152
|
+
// iterate over each field and apply mutation to nested data models
|
|
153
|
+
for (const [key, value] of Object.entries(currentData)) {
|
|
154
|
+
const fieldDef = modelFields[key];
|
|
155
|
+
if (!fieldDef?.relation) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const r = await doApplyMutation(
|
|
160
|
+
fieldDef.type,
|
|
161
|
+
value,
|
|
162
|
+
mutationModel,
|
|
163
|
+
mutationOp,
|
|
164
|
+
mutationArgs,
|
|
165
|
+
schema,
|
|
166
|
+
logging,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (r && typeof r === 'object') {
|
|
170
|
+
resultData = { ...resultData, [key]: r };
|
|
171
|
+
updated = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return updated ? resultData : undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function createMutate(queryModel: string, currentData: any, newData: any, schema: SchemaDef, logging: boolean) {
|
|
180
|
+
if (!newData) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const modelFields = schema.models[queryModel]?.fields;
|
|
185
|
+
if (!modelFields) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const insert: any = {};
|
|
190
|
+
const newDataFields = Object.keys(newData);
|
|
191
|
+
|
|
192
|
+
Object.entries(modelFields).forEach(([name, field]) => {
|
|
193
|
+
if (field.relation && newData[name]) {
|
|
194
|
+
// deal with "connect"
|
|
195
|
+
assignForeignKeyFields(field, insert, newData[name]);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (newDataFields.includes(name)) {
|
|
200
|
+
insert[name] = clone(newData[name]);
|
|
201
|
+
} else {
|
|
202
|
+
const defaultAttr = field.attributes?.find((attr) => attr.name === '@default');
|
|
203
|
+
if (field.type === 'DateTime') {
|
|
204
|
+
// default value for DateTime field
|
|
205
|
+
if (defaultAttr || field.attributes?.some((attr) => attr.name === '@updatedAt')) {
|
|
206
|
+
insert[name] = new Date();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const defaultArg = defaultAttr?.args?.[0]?.value;
|
|
212
|
+
if (defaultArg?.kind === 'literal') {
|
|
213
|
+
// other default value
|
|
214
|
+
insert[name] = defaultArg.value;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// add temp id value
|
|
220
|
+
const idFields = getIdFields(schema, queryModel);
|
|
221
|
+
idFields.forEach((f) => {
|
|
222
|
+
if (insert[f.name] === undefined) {
|
|
223
|
+
if (f.type === 'Int' || f.type === 'BigInt') {
|
|
224
|
+
const currMax = Array.isArray(currentData)
|
|
225
|
+
? Math.max(
|
|
226
|
+
...[...currentData].map((item) => {
|
|
227
|
+
const idv = parseInt(item[f.name]);
|
|
228
|
+
return isNaN(idv) ? 0 : idv;
|
|
229
|
+
}),
|
|
230
|
+
)
|
|
231
|
+
: 0;
|
|
232
|
+
insert[f.name] = currMax + 1;
|
|
233
|
+
} else {
|
|
234
|
+
insert[f.name] = crypto.randomUUID();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
insert.$optimistic = true;
|
|
240
|
+
|
|
241
|
+
if (logging) {
|
|
242
|
+
console.log(`Optimistic create for ${queryModel}:`, insert);
|
|
243
|
+
}
|
|
244
|
+
return [insert, ...(Array.isArray(currentData) ? currentData : [])];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function updateMutate(
|
|
248
|
+
queryModel: string,
|
|
249
|
+
currentData: any,
|
|
250
|
+
mutateModel: string,
|
|
251
|
+
mutateArgs: any,
|
|
252
|
+
schema: SchemaDef,
|
|
253
|
+
logging: boolean,
|
|
254
|
+
) {
|
|
255
|
+
if (!currentData || typeof currentData !== 'object') {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!mutateArgs?.where || typeof mutateArgs.where !== 'object') {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!mutateArgs?.data || typeof mutateArgs.data !== 'object') {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!idFieldsMatch(mutateModel, currentData, mutateArgs.where, schema)) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const modelFields = schema.models[queryModel]?.fields;
|
|
272
|
+
if (!modelFields) {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
let updated = false;
|
|
277
|
+
let resultData = currentData;
|
|
278
|
+
|
|
279
|
+
for (const [key, value] of Object.entries<any>(mutateArgs.data)) {
|
|
280
|
+
const fieldInfo = modelFields[key];
|
|
281
|
+
if (!fieldInfo) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (fieldInfo.relation && !value?.connect) {
|
|
286
|
+
// relation field but without "connect"
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!updated) {
|
|
291
|
+
// clone
|
|
292
|
+
resultData = { ...currentData };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (fieldInfo.relation) {
|
|
296
|
+
// deal with "connect"
|
|
297
|
+
assignForeignKeyFields(fieldInfo, resultData, value);
|
|
298
|
+
} else {
|
|
299
|
+
resultData[key] = clone(value);
|
|
300
|
+
}
|
|
301
|
+
resultData.$optimistic = true;
|
|
302
|
+
updated = true;
|
|
303
|
+
|
|
304
|
+
if (logging) {
|
|
305
|
+
console.log(`Optimistic update for ${queryModel}:`, resultData);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return updated ? resultData : undefined;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function upsertMutate(
|
|
313
|
+
queryModel: string,
|
|
314
|
+
currentData: any,
|
|
315
|
+
model: string,
|
|
316
|
+
args: { where: object; create: any; update: any },
|
|
317
|
+
schema: SchemaDef,
|
|
318
|
+
logging: boolean,
|
|
319
|
+
) {
|
|
320
|
+
let updated = false;
|
|
321
|
+
let resultData = currentData;
|
|
322
|
+
|
|
323
|
+
if (Array.isArray(resultData)) {
|
|
324
|
+
// check if we should create or update
|
|
325
|
+
const foundIndex = resultData.findIndex((x) => idFieldsMatch(model, x, args.where, schema));
|
|
326
|
+
if (foundIndex >= 0) {
|
|
327
|
+
const updateResult = updateMutate(
|
|
328
|
+
queryModel,
|
|
329
|
+
resultData[foundIndex],
|
|
330
|
+
model,
|
|
331
|
+
{ where: args.where, data: args.update },
|
|
332
|
+
schema,
|
|
333
|
+
logging,
|
|
334
|
+
);
|
|
335
|
+
if (updateResult) {
|
|
336
|
+
// replace the found item with updated item
|
|
337
|
+
resultData = [...resultData.slice(0, foundIndex), updateResult, ...resultData.slice(foundIndex + 1)];
|
|
338
|
+
updated = true;
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
const createResult = createMutate(queryModel, resultData, args.create, schema, logging);
|
|
342
|
+
if (createResult) {
|
|
343
|
+
resultData = createResult;
|
|
344
|
+
updated = true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
// try update only
|
|
349
|
+
const updateResult = updateMutate(
|
|
350
|
+
queryModel,
|
|
351
|
+
resultData,
|
|
352
|
+
model,
|
|
353
|
+
{ where: args.where, data: args.update },
|
|
354
|
+
schema,
|
|
355
|
+
logging,
|
|
356
|
+
);
|
|
357
|
+
if (updateResult) {
|
|
358
|
+
resultData = updateResult;
|
|
359
|
+
updated = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return updated ? resultData : undefined;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function deleteMutate(
|
|
367
|
+
queryModel: string,
|
|
368
|
+
currentData: any,
|
|
369
|
+
mutateModel: string,
|
|
370
|
+
mutateArgs: any,
|
|
371
|
+
schema: SchemaDef,
|
|
372
|
+
logging: boolean,
|
|
373
|
+
) {
|
|
374
|
+
// TODO: handle mutation of nested reads?
|
|
375
|
+
|
|
376
|
+
if (!currentData || !mutateArgs) {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (queryModel !== mutateModel) {
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
let updated = false;
|
|
385
|
+
let result = currentData;
|
|
386
|
+
|
|
387
|
+
if (Array.isArray(currentData)) {
|
|
388
|
+
for (const item of currentData) {
|
|
389
|
+
if (idFieldsMatch(mutateModel, item, mutateArgs, schema)) {
|
|
390
|
+
result = (result as unknown[]).filter((x) => x !== item);
|
|
391
|
+
updated = true;
|
|
392
|
+
if (logging) {
|
|
393
|
+
console.log(`Optimistic delete for ${queryModel}:`, item);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
if (idFieldsMatch(mutateModel, currentData, mutateArgs, schema)) {
|
|
399
|
+
result = null;
|
|
400
|
+
updated = true;
|
|
401
|
+
if (logging) {
|
|
402
|
+
console.log(`Optimistic delete for ${queryModel}:`, currentData);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return updated ? result : undefined;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function idFieldsMatch(model: string, x: any, y: any, schema: SchemaDef) {
|
|
411
|
+
if (!x || !y || typeof x !== 'object' || typeof y !== 'object') {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
const idFields = getIdFields(schema, model);
|
|
415
|
+
if (idFields.length === 0) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
return idFields.every((f) => x[f.name] === y[f.name]);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function assignForeignKeyFields(field: FieldDef, resultData: any, mutationData: any) {
|
|
422
|
+
// convert "connect" like `{ connect: { id: '...' } }` to foreign key fields
|
|
423
|
+
// assignment: `{ userId: '...' }`
|
|
424
|
+
if (!mutationData?.connect) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (!field.relation?.fields || !field.relation.references) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
for (const [idField, fkField] of zip(field.relation.references, field.relation.fields)) {
|
|
433
|
+
if (idField in mutationData.connect) {
|
|
434
|
+
resultData[fkField] = mutationData.connect[idField];
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function getIdFields(schema: SchemaDef, model: string) {
|
|
440
|
+
return (schema.models[model]?.idFields ?? []).map((f) => schema.models[model]!.fields[f]!);
|
|
441
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { FieldDef, SchemaDef } from '@zenstackhq/schema';
|
|
2
|
+
|
|
3
|
+
export type NestedReadVisitorCallback = {
|
|
4
|
+
field?: (
|
|
5
|
+
model: string,
|
|
6
|
+
field: FieldDef | undefined,
|
|
7
|
+
kind: 'include' | 'select' | undefined,
|
|
8
|
+
args: unknown,
|
|
9
|
+
) => void | boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Visitor for nested read payload.
|
|
14
|
+
*/
|
|
15
|
+
export class NestedReadVisitor {
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly schema: SchemaDef,
|
|
18
|
+
private readonly callback: NestedReadVisitorCallback,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
doVisit(model: string, field: FieldDef | undefined, kind: 'include' | 'select' | undefined, args: unknown) {
|
|
22
|
+
if (this.callback.field) {
|
|
23
|
+
const r = this.callback.field(model, field, kind, args);
|
|
24
|
+
if (r === false) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!args || typeof args !== 'object') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let selectInclude: any;
|
|
34
|
+
let nextKind: 'select' | 'include' | undefined;
|
|
35
|
+
if ((args as any).select) {
|
|
36
|
+
selectInclude = (args as any).select;
|
|
37
|
+
nextKind = 'select';
|
|
38
|
+
} else if ((args as any).include) {
|
|
39
|
+
selectInclude = (args as any).include;
|
|
40
|
+
nextKind = 'include';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (selectInclude && typeof selectInclude === 'object') {
|
|
44
|
+
for (const [k, v] of Object.entries(selectInclude)) {
|
|
45
|
+
if (k === '_count' && typeof v === 'object' && v) {
|
|
46
|
+
// recurse into { _count: { ... } }
|
|
47
|
+
this.doVisit(model, field, kind, v);
|
|
48
|
+
} else {
|
|
49
|
+
const field = this.schema.models[model]?.fields[k];
|
|
50
|
+
if (field) {
|
|
51
|
+
this.doVisit(field.type, field, nextKind, v);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
visit(model: string, args: unknown) {
|
|
59
|
+
this.doVisit(model, undefined, undefined, args);
|
|
60
|
+
}
|
|
61
|
+
}
|