@zenstackhq/tanstack-query 3.0.0 → 3.1.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.
Files changed (56) hide show
  1. package/dist/common/client.d.ts +4 -0
  2. package/dist/common/client.js +38 -0
  3. package/dist/common/client.js.map +1 -0
  4. package/dist/common/query-key.d.ts +39 -0
  5. package/dist/common/query-key.js +38 -0
  6. package/dist/common/query-key.js.map +1 -0
  7. package/dist/common/types.d.ts +60 -0
  8. package/dist/common/types.js +2 -0
  9. package/dist/common/types.js.map +1 -0
  10. package/dist/react.d.ts +99 -150
  11. package/dist/react.js +248 -1178
  12. package/dist/react.js.map +1 -1
  13. package/dist/svelte/index.svelte.d.ts +79 -0
  14. package/dist/svelte/index.svelte.js +245 -0
  15. package/dist/vue.d.ts +313 -342
  16. package/dist/vue.js +224 -1138
  17. package/dist/vue.js.map +1 -1
  18. package/package.json +36 -48
  19. package/.turbo/turbo-build.log +0 -47
  20. package/dist/react.cjs +0 -1240
  21. package/dist/react.cjs.map +0 -1
  22. package/dist/react.d.cts +0 -616
  23. package/dist/svelte.cjs +0 -1224
  24. package/dist/svelte.cjs.map +0 -1
  25. package/dist/svelte.d.cts +0 -381
  26. package/dist/svelte.d.ts +0 -381
  27. package/dist/svelte.js +0 -1183
  28. package/dist/svelte.js.map +0 -1
  29. package/dist/types-C8iIZD-7.d.cts +0 -99
  30. package/dist/types-C8iIZD-7.d.ts +0 -99
  31. package/dist/vue.cjs +0 -1192
  32. package/dist/vue.cjs.map +0 -1
  33. package/dist/vue.d.cts +0 -382
  34. package/eslint.config.js +0 -4
  35. package/src/react.ts +0 -562
  36. package/src/svelte.ts +0 -502
  37. package/src/utils/common.ts +0 -448
  38. package/src/utils/mutator.ts +0 -441
  39. package/src/utils/nested-read-visitor.ts +0 -61
  40. package/src/utils/nested-write-visitor.ts +0 -359
  41. package/src/utils/query-analysis.ts +0 -116
  42. package/src/utils/serialization.ts +0 -39
  43. package/src/utils/types.ts +0 -43
  44. package/src/vue.ts +0 -448
  45. package/test/react-query.test.tsx +0 -1787
  46. package/test/react-typing-test.ts +0 -113
  47. package/test/schemas/basic/input.ts +0 -110
  48. package/test/schemas/basic/models.ts +0 -14
  49. package/test/schemas/basic/schema-lite.ts +0 -172
  50. package/test/schemas/basic/schema.zmodel +0 -35
  51. package/test/svelte-typing-test.ts +0 -111
  52. package/test/vue-typing-test.ts +0 -111
  53. package/tsconfig.json +0 -7
  54. package/tsconfig.test.json +0 -8
  55. package/tsup.config.ts +0 -15
  56. package/vitest.config.ts +0 -11
@@ -1,441 +0,0 @@
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
- }
@@ -1,61 +0,0 @@
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
- }