@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.
Files changed (93) hide show
  1. package/.turbo/turbo-build.log +31 -0
  2. package/LICENSE +1 -1
  3. package/dist/react.cjs +1238 -0
  4. package/dist/react.cjs.map +1 -0
  5. package/dist/react.d.cts +696 -0
  6. package/dist/react.d.ts +696 -0
  7. package/dist/react.js +1195 -0
  8. package/dist/react.js.map +1 -0
  9. package/eslint.config.js +4 -0
  10. package/package.json +56 -109
  11. package/scripts/generate.ts +27 -0
  12. package/src/react.ts +531 -0
  13. package/src/utils/common.ts +457 -0
  14. package/src/utils/mutator.ts +441 -0
  15. package/src/utils/nested-read-visitor.ts +61 -0
  16. package/src/utils/nested-write-visitor.ts +359 -0
  17. package/src/utils/query-analysis.ts +116 -0
  18. package/src/utils/serialization.ts +39 -0
  19. package/src/utils/types.ts +19 -0
  20. package/test/react-query.test.tsx +1787 -0
  21. package/test/schemas/basic/input.ts +70 -0
  22. package/test/schemas/basic/models.ts +12 -0
  23. package/test/schemas/basic/schema-lite.ts +124 -0
  24. package/test/schemas/basic/schema.zmodel +25 -0
  25. package/tsconfig.json +7 -0
  26. package/tsconfig.test.json +8 -0
  27. package/tsup.config.ts +13 -0
  28. package/vitest.config.ts +11 -0
  29. package/README.md +0 -5
  30. package/generator.d.ts +0 -6
  31. package/generator.js +0 -578
  32. package/generator.js.map +0 -1
  33. package/index.d.ts +0 -4
  34. package/index.js +0 -22
  35. package/index.js.map +0 -1
  36. package/runtime/common-CXlL7vTW.d.mts +0 -121
  37. package/runtime/common-CXlL7vTW.d.ts +0 -121
  38. package/runtime/index.d.mts +0 -20
  39. package/runtime/index.d.ts +0 -20
  40. package/runtime/index.js +0 -44
  41. package/runtime/index.js.map +0 -1
  42. package/runtime/index.mjs +0 -21
  43. package/runtime/index.mjs.map +0 -1
  44. package/runtime/react.d.mts +0 -322
  45. package/runtime/react.d.ts +0 -322
  46. package/runtime/react.js +0 -408
  47. package/runtime/react.js.map +0 -1
  48. package/runtime/react.mjs +0 -380
  49. package/runtime/react.mjs.map +0 -1
  50. package/runtime/svelte.d.mts +0 -322
  51. package/runtime/svelte.d.ts +0 -322
  52. package/runtime/svelte.js +0 -407
  53. package/runtime/svelte.js.map +0 -1
  54. package/runtime/svelte.mjs +0 -379
  55. package/runtime/svelte.mjs.map +0 -1
  56. package/runtime/vue.d.mts +0 -330
  57. package/runtime/vue.d.ts +0 -330
  58. package/runtime/vue.js +0 -418
  59. package/runtime/vue.js.map +0 -1
  60. package/runtime/vue.mjs +0 -390
  61. package/runtime/vue.mjs.map +0 -1
  62. package/runtime-v5/angular.d.mts +0 -59
  63. package/runtime-v5/angular.d.ts +0 -59
  64. package/runtime-v5/angular.js +0 -425
  65. package/runtime-v5/angular.js.map +0 -1
  66. package/runtime-v5/angular.mjs +0 -397
  67. package/runtime-v5/angular.mjs.map +0 -1
  68. package/runtime-v5/common-CXlL7vTW.d.mts +0 -121
  69. package/runtime-v5/common-CXlL7vTW.d.ts +0 -121
  70. package/runtime-v5/index.d.mts +0 -20
  71. package/runtime-v5/index.d.ts +0 -20
  72. package/runtime-v5/index.js +0 -44
  73. package/runtime-v5/index.js.map +0 -1
  74. package/runtime-v5/index.mjs +0 -21
  75. package/runtime-v5/index.mjs.map +0 -1
  76. package/runtime-v5/react.d.mts +0 -474
  77. package/runtime-v5/react.d.ts +0 -474
  78. package/runtime-v5/react.js +0 -440
  79. package/runtime-v5/react.js.map +0 -1
  80. package/runtime-v5/react.mjs +0 -412
  81. package/runtime-v5/react.mjs.map +0 -1
  82. package/runtime-v5/svelte.d.mts +0 -386
  83. package/runtime-v5/svelte.d.ts +0 -386
  84. package/runtime-v5/svelte.js +0 -436
  85. package/runtime-v5/svelte.js.map +0 -1
  86. package/runtime-v5/svelte.mjs +0 -408
  87. package/runtime-v5/svelte.mjs.map +0 -1
  88. package/runtime-v5/vue.d.mts +0 -330
  89. package/runtime-v5/vue.d.ts +0 -330
  90. package/runtime-v5/vue.js +0 -420
  91. package/runtime-v5/vue.js.map +0 -1
  92. package/runtime-v5/vue.mjs +0 -392
  93. package/runtime-v5/vue.mjs.map +0 -1
@@ -0,0 +1,359 @@
1
+ import { enumerate } from '@zenstackhq/common-helpers';
2
+ import type { FieldDef, SchemaDef } from '@zenstackhq/schema';
3
+ import { ORMWriteActions, type MaybePromise, type ORMWriteActionType } from './types';
4
+
5
+ type NestingPathItem = { field?: FieldDef; model: string; where: any; unique: boolean };
6
+
7
+ /**
8
+ * Context for visiting
9
+ */
10
+ export type NestedWriteVisitorContext = {
11
+ /**
12
+ * Parent data, can be used to replace fields
13
+ */
14
+ parent: any;
15
+
16
+ /**
17
+ * Current field, undefined if toplevel
18
+ */
19
+ field?: FieldDef;
20
+
21
+ /**
22
+ * A top-down path of all nested update conditions and corresponding field till now
23
+ */
24
+ nestingPath: NestingPathItem[];
25
+ };
26
+
27
+ /**
28
+ * NestedWriteVisitor's callback actions. A call back function should return true or void to indicate
29
+ * that the visitor should continue traversing its children, or false to stop. It can also return an object
30
+ * to let the visitor traverse it instead of its original children.
31
+ */
32
+ export type NestedWriterVisitorCallback = {
33
+ create?: (model: string, data: any, context: NestedWriteVisitorContext) => MaybePromise<boolean | object | void>;
34
+
35
+ createMany?: (
36
+ model: string,
37
+ args: { data: any; skipDuplicates?: boolean },
38
+ context: NestedWriteVisitorContext,
39
+ ) => MaybePromise<boolean | object | void>;
40
+
41
+ connectOrCreate?: (
42
+ model: string,
43
+ args: { where: object; create: any },
44
+ context: NestedWriteVisitorContext,
45
+ ) => MaybePromise<boolean | object | void>;
46
+
47
+ connect?: (
48
+ model: string,
49
+ args: object,
50
+ context: NestedWriteVisitorContext,
51
+ ) => MaybePromise<boolean | object | void>;
52
+
53
+ disconnect?: (
54
+ model: string,
55
+ args: object,
56
+ context: NestedWriteVisitorContext,
57
+ ) => MaybePromise<boolean | object | void>;
58
+
59
+ set?: (model: string, args: object, context: NestedWriteVisitorContext) => MaybePromise<boolean | object | void>;
60
+
61
+ update?: (model: string, args: object, context: NestedWriteVisitorContext) => MaybePromise<boolean | object | void>;
62
+
63
+ updateMany?: (
64
+ model: string,
65
+ args: { where?: object; data: any },
66
+ context: NestedWriteVisitorContext,
67
+ ) => MaybePromise<boolean | object | void>;
68
+
69
+ upsert?: (
70
+ model: string,
71
+ args: { where: object; create: any; update: any },
72
+ context: NestedWriteVisitorContext,
73
+ ) => MaybePromise<boolean | object | void>;
74
+
75
+ delete?: (
76
+ model: string,
77
+ args: object | boolean,
78
+ context: NestedWriteVisitorContext,
79
+ ) => MaybePromise<boolean | object | void>;
80
+
81
+ deleteMany?: (
82
+ model: string,
83
+ args: any | object,
84
+ context: NestedWriteVisitorContext,
85
+ ) => MaybePromise<boolean | object | void>;
86
+
87
+ field?: (
88
+ field: FieldDef,
89
+ action: ORMWriteActionType,
90
+ data: any,
91
+ context: NestedWriteVisitorContext,
92
+ ) => MaybePromise<void>;
93
+ };
94
+
95
+ /**
96
+ * Recursive visitor for nested write (create/update) payload.
97
+ */
98
+ export class NestedWriteVisitor {
99
+ constructor(
100
+ private readonly schema: SchemaDef,
101
+ private readonly callback: NestedWriterVisitorCallback,
102
+ ) {}
103
+
104
+ private isWriteAction(value: string): value is ORMWriteActionType {
105
+ return ORMWriteActions.includes(value as ORMWriteActionType);
106
+ }
107
+
108
+ /**
109
+ * Start visiting
110
+ *
111
+ * @see NestedWriterVisitorCallback
112
+ */
113
+ async visit(model: string, action: ORMWriteActionType, args: any): Promise<void> {
114
+ if (!args) {
115
+ return;
116
+ }
117
+
118
+ let topData = args;
119
+
120
+ switch (action) {
121
+ // create has its data wrapped in 'data' field
122
+ case 'create':
123
+ topData = topData.data;
124
+ break;
125
+
126
+ case 'delete':
127
+ case 'deleteMany':
128
+ topData = topData.where;
129
+ break;
130
+ }
131
+
132
+ await this.doVisit(model, action, topData, undefined, undefined, []);
133
+ }
134
+
135
+ private async doVisit(
136
+ model: string,
137
+ action: ORMWriteActionType,
138
+ data: any,
139
+ parent: any,
140
+ field: FieldDef | undefined,
141
+ nestingPath: NestingPathItem[],
142
+ ): Promise<void> {
143
+ if (!data) {
144
+ return;
145
+ }
146
+
147
+ const toplevel = field == undefined;
148
+
149
+ const context = { parent, field, nestingPath: [...nestingPath] };
150
+ const pushNewContext = (field: FieldDef | undefined, model: string, where: any, unique = false) => {
151
+ return { ...context, nestingPath: [...context.nestingPath, { field, model, where, unique }] };
152
+ };
153
+
154
+ // visit payload
155
+ switch (action) {
156
+ case 'create':
157
+ for (const item of this.enumerateReverse(data)) {
158
+ const newContext = pushNewContext(field, model, {});
159
+ let callbackResult: any;
160
+ if (this.callback.create) {
161
+ callbackResult = await this.callback.create(model, item, newContext);
162
+ }
163
+ if (callbackResult !== false) {
164
+ const subPayload = typeof callbackResult === 'object' ? callbackResult : item;
165
+ await this.visitSubPayload(model, action, subPayload, newContext.nestingPath);
166
+ }
167
+ }
168
+ break;
169
+
170
+ case 'createMany':
171
+ case 'createManyAndReturn':
172
+ {
173
+ const newContext = pushNewContext(field, model, {});
174
+ let callbackResult: any;
175
+ if (this.callback.createMany) {
176
+ callbackResult = await this.callback.createMany(model, data, newContext);
177
+ }
178
+ if (callbackResult !== false) {
179
+ const subPayload = typeof callbackResult === 'object' ? callbackResult : data.data;
180
+ await this.visitSubPayload(model, action, subPayload, newContext.nestingPath);
181
+ }
182
+ }
183
+ break;
184
+
185
+ case 'connectOrCreate':
186
+ for (const item of this.enumerateReverse(data)) {
187
+ const newContext = pushNewContext(field, model, item.where);
188
+ let callbackResult: any;
189
+ if (this.callback.connectOrCreate) {
190
+ callbackResult = await this.callback.connectOrCreate(model, item, newContext);
191
+ }
192
+ if (callbackResult !== false) {
193
+ const subPayload = typeof callbackResult === 'object' ? callbackResult : item.create;
194
+ await this.visitSubPayload(model, action, subPayload, newContext.nestingPath);
195
+ }
196
+ }
197
+ break;
198
+
199
+ case 'connect':
200
+ if (this.callback.connect) {
201
+ for (const item of this.enumerateReverse(data)) {
202
+ const newContext = pushNewContext(field, model, item, true);
203
+ await this.callback.connect(model, item, newContext);
204
+ }
205
+ }
206
+ break;
207
+
208
+ case 'disconnect':
209
+ // disconnect has two forms:
210
+ // if relation is to-many, the payload is a unique filter object
211
+ // if relation is to-one, the payload can only be boolean `true`
212
+ if (this.callback.disconnect) {
213
+ for (const item of this.enumerateReverse(data)) {
214
+ const newContext = pushNewContext(field, model, item, typeof item === 'object');
215
+ await this.callback.disconnect(model, item, newContext);
216
+ }
217
+ }
218
+ break;
219
+
220
+ case 'set':
221
+ if (this.callback.set) {
222
+ for (const item of this.enumerateReverse(data)) {
223
+ const newContext = pushNewContext(field, model, item, true);
224
+ await this.callback.set(model, item, newContext);
225
+ }
226
+ }
227
+ break;
228
+
229
+ case 'update':
230
+ for (const item of this.enumerateReverse(data)) {
231
+ const newContext = pushNewContext(field, model, item.where);
232
+ let callbackResult: any;
233
+ if (this.callback.update) {
234
+ callbackResult = await this.callback.update(model, item, newContext);
235
+ }
236
+ if (callbackResult !== false) {
237
+ const subPayload =
238
+ typeof callbackResult === 'object'
239
+ ? callbackResult
240
+ : typeof item.data === 'object'
241
+ ? item.data
242
+ : item;
243
+ await this.visitSubPayload(model, action, subPayload, newContext.nestingPath);
244
+ }
245
+ }
246
+ break;
247
+
248
+ case 'updateMany':
249
+ case 'updateManyAndReturn':
250
+ for (const item of this.enumerateReverse(data)) {
251
+ const newContext = pushNewContext(field, model, item.where);
252
+ let callbackResult: any;
253
+ if (this.callback.updateMany) {
254
+ callbackResult = await this.callback.updateMany(model, item, newContext);
255
+ }
256
+ if (callbackResult !== false) {
257
+ const subPayload = typeof callbackResult === 'object' ? callbackResult : item;
258
+ await this.visitSubPayload(model, action, subPayload, newContext.nestingPath);
259
+ }
260
+ }
261
+ break;
262
+
263
+ case 'upsert': {
264
+ for (const item of this.enumerateReverse(data)) {
265
+ const newContext = pushNewContext(field, model, item.where);
266
+ let callbackResult: any;
267
+ if (this.callback.upsert) {
268
+ callbackResult = await this.callback.upsert(model, item, newContext);
269
+ }
270
+ if (callbackResult !== false) {
271
+ if (typeof callbackResult === 'object') {
272
+ await this.visitSubPayload(model, action, callbackResult, newContext.nestingPath);
273
+ } else {
274
+ await this.visitSubPayload(model, action, item.create, newContext.nestingPath);
275
+ await this.visitSubPayload(model, action, item.update, newContext.nestingPath);
276
+ }
277
+ }
278
+ }
279
+ break;
280
+ }
281
+
282
+ case 'delete': {
283
+ if (this.callback.delete) {
284
+ for (const item of this.enumerateReverse(data)) {
285
+ const newContext = pushNewContext(field, model, toplevel ? item.where : item);
286
+ await this.callback.delete(model, item, newContext);
287
+ }
288
+ }
289
+ break;
290
+ }
291
+
292
+ case 'deleteMany':
293
+ if (this.callback.deleteMany) {
294
+ for (const item of this.enumerateReverse(data)) {
295
+ const newContext = pushNewContext(field, model, toplevel ? item.where : item);
296
+ await this.callback.deleteMany(model, item, newContext);
297
+ }
298
+ }
299
+ break;
300
+
301
+ default: {
302
+ throw new Error(`unhandled action type ${action}`);
303
+ }
304
+ }
305
+ }
306
+
307
+ private async visitSubPayload(
308
+ model: string,
309
+ action: ORMWriteActionType,
310
+ payload: any,
311
+ nestingPath: NestingPathItem[],
312
+ ) {
313
+ for (const item of enumerate(payload)) {
314
+ if (!item || typeof item !== 'object') {
315
+ continue;
316
+ }
317
+ for (const field of Object.keys(item)) {
318
+ const fieldDef = this.schema.models[model]?.fields[field];
319
+ if (!fieldDef) {
320
+ continue;
321
+ }
322
+
323
+ if (fieldDef.relation) {
324
+ if (item[field]) {
325
+ // recurse into nested payloads
326
+ for (const [subAction, subData] of Object.entries<any>(item[field])) {
327
+ if (this.isWriteAction(subAction) && subData) {
328
+ await this.doVisit(fieldDef.type, subAction, subData, item[field], fieldDef, [
329
+ ...nestingPath,
330
+ ]);
331
+ }
332
+ }
333
+ }
334
+ } else {
335
+ // visit plain field
336
+ if (this.callback.field) {
337
+ await this.callback.field(fieldDef, action, item[field], {
338
+ parent: item,
339
+ nestingPath,
340
+ field: fieldDef,
341
+ });
342
+ }
343
+ }
344
+ }
345
+ }
346
+ }
347
+
348
+ // enumerate a (possible) array in reverse order, so that the enumeration
349
+ // callback can safely delete the current item
350
+ private *enumerateReverse(data: any) {
351
+ if (Array.isArray(data)) {
352
+ for (let i = data.length - 1; i >= 0; i--) {
353
+ yield data[i];
354
+ }
355
+ } else {
356
+ yield data;
357
+ }
358
+ }
359
+ }
@@ -0,0 +1,116 @@
1
+ import type { SchemaDef } from '@zenstackhq/schema';
2
+ import { NestedReadVisitor } from './nested-read-visitor';
3
+ import { NestedWriteVisitor } from './nested-write-visitor';
4
+ import type { ORMWriteActionType } from './types';
5
+
6
+ /**
7
+ * Gets models read (including nested ones) given a query args.
8
+ * @param model
9
+ * @param targetModels
10
+ * @param schema
11
+ * @param args
12
+ * @returns
13
+ */
14
+ export function getReadModels(model: string, schema: SchemaDef, args: any) {
15
+ const result = new Set<string>();
16
+ result.add(model);
17
+ const visitor = new NestedReadVisitor(schema, {
18
+ field: (model) => {
19
+ result.add(model);
20
+ return true;
21
+ },
22
+ });
23
+ visitor.visit(model, args);
24
+ return [...result];
25
+ }
26
+
27
+ /**
28
+ * Gets mutated models (including nested ones) given a mutation args.
29
+ */
30
+ export async function getMutatedModels(
31
+ model: string,
32
+ operation: ORMWriteActionType,
33
+ mutationArgs: any,
34
+ schema: SchemaDef,
35
+ ) {
36
+ const result = new Set<string>();
37
+ result.add(model);
38
+
39
+ if (mutationArgs) {
40
+ const addModel = (model: string) => void result.add(model);
41
+
42
+ // add models that are cascaded deleted recursively
43
+ const addCascades = (model: string) => {
44
+ const cascades = new Set<string>();
45
+ const visited = new Set<string>();
46
+ collectDeleteCascades(model, schema, cascades, visited);
47
+ cascades.forEach((m) => addModel(m));
48
+ };
49
+
50
+ const visitor = new NestedWriteVisitor(schema, {
51
+ create: addModel,
52
+ createMany: addModel,
53
+ connectOrCreate: addModel,
54
+ connect: addModel,
55
+ disconnect: addModel,
56
+ set: addModel,
57
+ update: addModel,
58
+ updateMany: addModel,
59
+ upsert: addModel,
60
+ delete: (model) => {
61
+ addModel(model);
62
+ addCascades(model);
63
+ },
64
+ deleteMany: (model) => {
65
+ addModel(model);
66
+ addCascades(model);
67
+ },
68
+ });
69
+ await visitor.visit(model, operation, mutationArgs);
70
+ }
71
+
72
+ // include delegate base models recursively
73
+ result.forEach((m) => {
74
+ getBaseRecursively(m, schema, result);
75
+ });
76
+
77
+ return [...result];
78
+ }
79
+
80
+ function collectDeleteCascades(model: string, schema: SchemaDef, result: Set<string>, visited: Set<string>) {
81
+ if (visited.has(model)) {
82
+ // break circle
83
+ return;
84
+ }
85
+ visited.add(model);
86
+
87
+ const modelDef = schema.models[model];
88
+ if (!modelDef) {
89
+ return;
90
+ }
91
+
92
+ for (const [modelName, modelDef] of Object.entries(schema.models)) {
93
+ if (!modelDef) {
94
+ continue;
95
+ }
96
+ for (const fieldDef of Object.values(modelDef.fields)) {
97
+ if (fieldDef.relation?.onDelete === 'Cascade' && fieldDef.type === model) {
98
+ if (!result.has(modelName)) {
99
+ result.add(modelName);
100
+ }
101
+ collectDeleteCascades(modelName, schema, result, visited);
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ function getBaseRecursively(model: string, schema: SchemaDef, result: Set<string>) {
108
+ const modelDef = schema.models[model];
109
+ if (!modelDef) {
110
+ return;
111
+ }
112
+ if (modelDef.baseModel) {
113
+ result.add(modelDef.baseModel);
114
+ getBaseRecursively(modelDef.baseModel, schema, result);
115
+ }
116
+ }
@@ -0,0 +1,39 @@
1
+ import { Buffer } from 'buffer';
2
+ import Decimal from 'decimal.js';
3
+ import SuperJSON from 'superjson';
4
+
5
+ SuperJSON.registerCustom<Decimal, string>(
6
+ {
7
+ isApplicable: (v): v is Decimal =>
8
+ v instanceof Decimal ||
9
+ // interop with decimal.js
10
+ v?.toStringTag === '[object Decimal]',
11
+ serialize: (v) => v.toJSON(),
12
+ deserialize: (v) => new Decimal(v),
13
+ },
14
+ 'Decimal'
15
+ );
16
+
17
+ SuperJSON.registerCustom<Buffer, string>(
18
+ {
19
+ isApplicable: (v): v is Buffer => Buffer.isBuffer(v),
20
+ serialize: (v) => v.toString('base64'),
21
+ deserialize: (v) => Buffer.from(v, 'base64'),
22
+ },
23
+ 'Bytes'
24
+ );
25
+
26
+ /**
27
+ * Serialize the given value with superjson
28
+ */
29
+ export function serialize(value: unknown): { data: unknown; meta: unknown } {
30
+ const { json, meta } = SuperJSON.serialize(value);
31
+ return { data: json, meta };
32
+ }
33
+
34
+ /**
35
+ * Deserialize the given value with superjson using the given metadata
36
+ */
37
+ export function deserialize(value: unknown, meta: any): unknown {
38
+ return SuperJSON.deserialize({ json: value as any, meta });
39
+ }
@@ -0,0 +1,19 @@
1
+ export type MaybePromise<T> = T | Promise<T> | PromiseLike<T>;
2
+
3
+ export const ORMWriteActions = [
4
+ 'create',
5
+ 'createMany',
6
+ 'createManyAndReturn',
7
+ 'connectOrCreate',
8
+ 'update',
9
+ 'updateMany',
10
+ 'updateManyAndReturn',
11
+ 'upsert',
12
+ 'connect',
13
+ 'disconnect',
14
+ 'set',
15
+ 'delete',
16
+ 'deleteMany',
17
+ ] as const;
18
+
19
+ export type ORMWriteActionType = (typeof ORMWriteActions)[number];