@zenstackhq/tanstack-query 2.21.0 → 3.0.0-beta.16

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 (94) hide show
  1. package/.turbo/turbo-build.log +32 -0
  2. package/.turbo/turbo-lint.log +5 -0
  3. package/LICENSE +1 -1
  4. package/dist/react.cjs +1238 -0
  5. package/dist/react.cjs.map +1 -0
  6. package/dist/react.d.cts +696 -0
  7. package/dist/react.d.ts +696 -0
  8. package/dist/react.js +1195 -0
  9. package/dist/react.js.map +1 -0
  10. package/eslint.config.js +4 -0
  11. package/package.json +56 -109
  12. package/scripts/generate.ts +27 -0
  13. package/src/react.ts +531 -0
  14. package/src/utils/common.ts +457 -0
  15. package/src/utils/mutator.ts +441 -0
  16. package/src/utils/nested-read-visitor.ts +61 -0
  17. package/src/utils/nested-write-visitor.ts +359 -0
  18. package/src/utils/query-analysis.ts +116 -0
  19. package/src/utils/serialization.ts +39 -0
  20. package/src/utils/types.ts +19 -0
  21. package/test/react-query.test.tsx +1787 -0
  22. package/test/schemas/basic/input.ts +70 -0
  23. package/test/schemas/basic/models.ts +12 -0
  24. package/test/schemas/basic/schema-lite.ts +124 -0
  25. package/test/schemas/basic/schema.zmodel +25 -0
  26. package/tsconfig.json +7 -0
  27. package/tsconfig.test.json +8 -0
  28. package/tsup.config.ts +13 -0
  29. package/vitest.config.ts +11 -0
  30. package/README.md +0 -5
  31. package/generator.d.ts +0 -6
  32. package/generator.js +0 -578
  33. package/generator.js.map +0 -1
  34. package/index.d.ts +0 -4
  35. package/index.js +0 -22
  36. package/index.js.map +0 -1
  37. package/runtime/common-CXlL7vTW.d.mts +0 -121
  38. package/runtime/common-CXlL7vTW.d.ts +0 -121
  39. package/runtime/index.d.mts +0 -20
  40. package/runtime/index.d.ts +0 -20
  41. package/runtime/index.js +0 -44
  42. package/runtime/index.js.map +0 -1
  43. package/runtime/index.mjs +0 -21
  44. package/runtime/index.mjs.map +0 -1
  45. package/runtime/react.d.mts +0 -322
  46. package/runtime/react.d.ts +0 -322
  47. package/runtime/react.js +0 -408
  48. package/runtime/react.js.map +0 -1
  49. package/runtime/react.mjs +0 -380
  50. package/runtime/react.mjs.map +0 -1
  51. package/runtime/svelte.d.mts +0 -322
  52. package/runtime/svelte.d.ts +0 -322
  53. package/runtime/svelte.js +0 -407
  54. package/runtime/svelte.js.map +0 -1
  55. package/runtime/svelte.mjs +0 -379
  56. package/runtime/svelte.mjs.map +0 -1
  57. package/runtime/vue.d.mts +0 -330
  58. package/runtime/vue.d.ts +0 -330
  59. package/runtime/vue.js +0 -418
  60. package/runtime/vue.js.map +0 -1
  61. package/runtime/vue.mjs +0 -390
  62. package/runtime/vue.mjs.map +0 -1
  63. package/runtime-v5/angular.d.mts +0 -59
  64. package/runtime-v5/angular.d.ts +0 -59
  65. package/runtime-v5/angular.js +0 -425
  66. package/runtime-v5/angular.js.map +0 -1
  67. package/runtime-v5/angular.mjs +0 -397
  68. package/runtime-v5/angular.mjs.map +0 -1
  69. package/runtime-v5/common-CXlL7vTW.d.mts +0 -121
  70. package/runtime-v5/common-CXlL7vTW.d.ts +0 -121
  71. package/runtime-v5/index.d.mts +0 -20
  72. package/runtime-v5/index.d.ts +0 -20
  73. package/runtime-v5/index.js +0 -44
  74. package/runtime-v5/index.js.map +0 -1
  75. package/runtime-v5/index.mjs +0 -21
  76. package/runtime-v5/index.mjs.map +0 -1
  77. package/runtime-v5/react.d.mts +0 -474
  78. package/runtime-v5/react.d.ts +0 -474
  79. package/runtime-v5/react.js +0 -440
  80. package/runtime-v5/react.js.map +0 -1
  81. package/runtime-v5/react.mjs +0 -412
  82. package/runtime-v5/react.mjs.map +0 -1
  83. package/runtime-v5/svelte.d.mts +0 -386
  84. package/runtime-v5/svelte.d.ts +0 -386
  85. package/runtime-v5/svelte.js +0 -436
  86. package/runtime-v5/svelte.js.map +0 -1
  87. package/runtime-v5/svelte.mjs +0 -408
  88. package/runtime-v5/svelte.mjs.map +0 -1
  89. package/runtime-v5/vue.d.mts +0 -330
  90. package/runtime-v5/vue.d.ts +0 -330
  91. package/runtime-v5/vue.js +0 -420
  92. package/runtime-v5/vue.js.map +0 -1
  93. package/runtime-v5/vue.mjs +0 -392
  94. package/runtime-v5/vue.mjs.map +0 -1
@@ -1,121 +0,0 @@
1
- /**
2
- * Function signature for `fetch`.
3
- */
4
- type FetchFn = (url: string, options?: RequestInit) => Promise<Response>;
5
- /**
6
- * Type for query and mutation errors.
7
- */
8
- type QueryError = Error & {
9
- /**
10
- * Additional error information.
11
- */
12
- info?: unknown;
13
- /**
14
- * HTTP status code.
15
- */
16
- status?: number;
17
- };
18
- /**
19
- * Result of optimistic data provider.
20
- */
21
- type OptimisticDataProviderResult = {
22
- /**
23
- * Kind of the result.
24
- * - Update: use the `data` field to update the query cache.
25
- * - Skip: skip the optimistic update for this query.
26
- * - ProceedDefault: proceed with the default optimistic update.
27
- */
28
- kind: 'Update' | 'Skip' | 'ProceedDefault';
29
- /**
30
- * Data to update the query cache. Only applicable if `kind` is 'Update'.
31
- *
32
- * If the data is an object with fields updated, it should have a `$optimistic`
33
- * field set to `true`. If it's an array and an element object is created or updated,
34
- * the element should have a `$optimistic` field set to `true`.
35
- */
36
- data?: any;
37
- };
38
- /**
39
- * Optimistic data provider.
40
- *
41
- * @param args Arguments.
42
- * @param args.queryModel The model of the query.
43
- * @param args.queryOperation The operation of the query, `findMany`, `count`, etc.
44
- * @param args.queryArgs The arguments of the query.
45
- * @param args.currentData The current cache data for the query.
46
- * @param args.mutationArgs The arguments of the mutation.
47
- */
48
- type OptimisticDataProvider = (args: {
49
- queryModel: string;
50
- queryOperation: string;
51
- queryArgs: any;
52
- currentData: any;
53
- mutationArgs: any;
54
- }) => OptimisticDataProviderResult | Promise<OptimisticDataProviderResult>;
55
- /**
56
- * Extra mutation options.
57
- */
58
- type ExtraMutationOptions = {
59
- /**
60
- * Whether to automatically invalidate queries potentially affected by the mutation. Defaults to `true`.
61
- */
62
- invalidateQueries?: boolean;
63
- /**
64
- * Whether to optimistically update queries potentially affected by the mutation. Defaults to `false`.
65
- */
66
- optimisticUpdate?: boolean;
67
- /**
68
- * A callback for computing optimistic update data for each query cache entry.
69
- */
70
- optimisticDataProvider?: OptimisticDataProvider;
71
- };
72
- /**
73
- * Extra query options.
74
- */
75
- type ExtraQueryOptions = {
76
- /**
77
- * Whether to opt-in to optimistic updates for this query. Defaults to `true`.
78
- */
79
- optimisticUpdate?: boolean;
80
- };
81
- /**
82
- * Context type for configuring the hooks.
83
- */
84
- type APIContext = {
85
- /**
86
- * The endpoint to use for the queries.
87
- */
88
- endpoint?: string;
89
- /**
90
- * A custom fetch function for sending the HTTP requests.
91
- */
92
- fetch?: FetchFn;
93
- /**
94
- * If logging is enabled.
95
- */
96
- logging?: boolean;
97
- };
98
- type QueryKey = [
99
- string,
100
- string,
101
- string,
102
- unknown,
103
- {
104
- infinite: boolean;
105
- optimisticUpdate: boolean;
106
- }
107
- ];
108
- /**
109
- * Computes query key for the given model, operation and query args.
110
- * @param model Model name.
111
- * @param urlOrOperation Prisma operation (e.g, `findMany`) or request URL. If it's a URL, the last path segment will be used as the operation name.
112
- * @param args Prisma query arguments.
113
- * @param options Query options, including `infinite` indicating if it's an infinite query (defaults to false), and `optimisticUpdate` indicating if optimistic updates are enabled (defaults to true).
114
- * @returns Query key
115
- */
116
- declare function getQueryKey(model: string, urlOrOperation: string, args: unknown, options?: {
117
- infinite: boolean;
118
- optimisticUpdate: boolean;
119
- }): QueryKey;
120
-
121
- export { type APIContext as A, type ExtraMutationOptions as E, type FetchFn as F, type QueryError as Q, type ExtraQueryOptions as a, getQueryKey as g };
@@ -1,20 +0,0 @@
1
- export { E as ExtraMutationOptions, a as ExtraQueryOptions, F as FetchFn, Q as QueryError, g as getQueryKey } from './common-CXlL7vTW.mjs';
2
-
3
- type Enumerable<T> = T | Array<T>;
4
- type _TupleToUnion<T> = T extends (infer E)[] ? E : never;
5
- type TupleToUnion<K extends readonly any[]> = _TupleToUnion<K>;
6
- type MaybeTupleToUnion<T> = T extends any[] ? TupleToUnion<T> : T;
7
- type PickEnumerable<T, K extends Enumerable<keyof T> | keyof T> = Pick<T, MaybeTupleToUnion<K>>;
8
- type SelectAndInclude = {
9
- select: any;
10
- include: any;
11
- };
12
- type HasSelect = {
13
- select: any;
14
- };
15
- type HasInclude = {
16
- include: any;
17
- };
18
- type CheckSelect<T, S, U> = T extends SelectAndInclude ? 'Please either choose `select` or `include`' : T extends HasSelect ? U : T extends HasInclude ? U : S;
19
-
20
- export type { CheckSelect, Enumerable, MaybeTupleToUnion, PickEnumerable, TupleToUnion };
@@ -1,20 +0,0 @@
1
- export { E as ExtraMutationOptions, a as ExtraQueryOptions, F as FetchFn, Q as QueryError, g as getQueryKey } from './common-CXlL7vTW.js';
2
-
3
- type Enumerable<T> = T | Array<T>;
4
- type _TupleToUnion<T> = T extends (infer E)[] ? E : never;
5
- type TupleToUnion<K extends readonly any[]> = _TupleToUnion<K>;
6
- type MaybeTupleToUnion<T> = T extends any[] ? TupleToUnion<T> : T;
7
- type PickEnumerable<T, K extends Enumerable<keyof T> | keyof T> = Pick<T, MaybeTupleToUnion<K>>;
8
- type SelectAndInclude = {
9
- select: any;
10
- include: any;
11
- };
12
- type HasSelect = {
13
- select: any;
14
- };
15
- type HasInclude = {
16
- include: any;
17
- };
18
- type CheckSelect<T, S, U> = T extends SelectAndInclude ? 'Please either choose `select` or `include`' : T extends HasSelect ? U : T extends HasInclude ? U : S;
19
-
20
- export type { CheckSelect, Enumerable, MaybeTupleToUnion, PickEnumerable, TupleToUnion };
@@ -1,44 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/runtime-v5/index.ts
21
- var runtime_v5_exports = {};
22
- __export(runtime_v5_exports, {
23
- getQueryKey: () => getQueryKey
24
- });
25
- module.exports = __toCommonJS(runtime_v5_exports);
26
-
27
- // src/runtime/common.ts
28
- var import_browser = require("@zenstackhq/runtime/browser");
29
- var import_cross = require("@zenstackhq/runtime/cross");
30
- var QUERY_KEY_PREFIX = "zenstack";
31
- function getQueryKey(model, urlOrOperation, args, options = { infinite: false, optimisticUpdate: true }) {
32
- if (!urlOrOperation) {
33
- throw new Error("Invalid urlOrOperation");
34
- }
35
- const operation = urlOrOperation.split("/").pop();
36
- const infinite = options.infinite;
37
- const optimisticUpdate = options.infinite ? false : options.optimisticUpdate;
38
- return [QUERY_KEY_PREFIX, model, operation, args, { infinite, optimisticUpdate }];
39
- }
40
- // Annotate the CommonJS export names for ESM import in node:
41
- 0 && (module.exports = {
42
- getQueryKey
43
- });
44
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/runtime-v5/index.ts","../../src/runtime/common.ts"],"sourcesContent":["export {\n getQueryKey,\n type ExtraMutationOptions,\n type ExtraQueryOptions,\n type FetchFn,\n type QueryError,\n} from '../runtime/common';\nexport * from '../runtime/prisma-types';\n","/* eslint-disable @typescript-eslint/no-unused-vars */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { deserialize, serialize } from '@zenstackhq/runtime/browser';\nimport {\n applyMutation,\n getMutatedModels,\n getReadModels,\n type ModelMeta,\n type PrismaWriteActionType,\n} from '@zenstackhq/runtime/cross';\n\n/**\n * The default query endpoint.\n */\nexport const DEFAULT_QUERY_ENDPOINT = '/api/model';\n\n/**\n * Prefix for react-query keys.\n */\nexport const QUERY_KEY_PREFIX = 'zenstack';\n\n/**\n * Function signature for `fetch`.\n */\nexport type FetchFn = (url: string, options?: RequestInit) => Promise<Response>;\n\n/**\n * Type for query and mutation errors.\n */\nexport type QueryError = Error & {\n /**\n * Additional error information.\n */\n info?: unknown;\n\n /**\n * HTTP status code.\n */\n status?: number;\n};\n\n/**\n * Result of optimistic data provider.\n */\nexport type OptimisticDataProviderResult = {\n /**\n * Kind of the result.\n * - Update: use the `data` field to update the query cache.\n * - Skip: skip the optimistic update for this query.\n * - ProceedDefault: proceed with the default optimistic update.\n */\n kind: 'Update' | 'Skip' | 'ProceedDefault';\n\n /**\n * Data to update the query cache. Only applicable if `kind` is 'Update'.\n *\n * If the data is an object with fields updated, it should have a `$optimistic`\n * field set to `true`. If it's an array and an element object is created or updated,\n * the element should have a `$optimistic` field set to `true`.\n */\n data?: any;\n};\n\n/**\n * Optimistic data provider.\n *\n * @param args Arguments.\n * @param args.queryModel The model of the query.\n * @param args.queryOperation The operation of the query, `findMany`, `count`, etc.\n * @param args.queryArgs The arguments of the query.\n * @param args.currentData The current cache data for the query.\n * @param args.mutationArgs The arguments of the mutation.\n */\nexport type OptimisticDataProvider = (args: {\n queryModel: string;\n queryOperation: string;\n queryArgs: any;\n currentData: any;\n mutationArgs: any;\n}) => OptimisticDataProviderResult | Promise<OptimisticDataProviderResult>;\n\n/**\n * Extra mutation options.\n */\nexport type ExtraMutationOptions = {\n /**\n * Whether to automatically invalidate queries potentially affected by the mutation. Defaults to `true`.\n */\n invalidateQueries?: boolean;\n\n /**\n * Whether to optimistically update queries potentially affected by the mutation. Defaults to `false`.\n */\n optimisticUpdate?: boolean;\n\n /**\n * A callback for computing optimistic update data for each query cache entry.\n */\n optimisticDataProvider?: OptimisticDataProvider;\n};\n\n/**\n * Extra query options.\n */\nexport type ExtraQueryOptions = {\n /**\n * Whether to opt-in to optimistic updates for this query. Defaults to `true`.\n */\n optimisticUpdate?: boolean;\n};\n\n/**\n * Context type for configuring the hooks.\n */\nexport type APIContext = {\n /**\n * The endpoint to use for the queries.\n */\n endpoint?: string;\n\n /**\n * A custom fetch function for sending the HTTP requests.\n */\n fetch?: FetchFn;\n\n /**\n * If logging is enabled.\n */\n logging?: boolean;\n};\n\nexport async function fetcher<R, C extends boolean>(\n url: string,\n options?: RequestInit,\n customFetch?: FetchFn,\n checkReadBack?: C\n): Promise<C extends true ? R | undefined : R> {\n // Note: 'cross-fetch' is supposed to handle fetch compatibility\n // but it doesn't work for cloudflare workers\n const _fetch =\n customFetch ??\n // check if fetch is available globally\n (typeof fetch === 'function'\n ? fetch\n : // fallback to 'cross-fetch' if otherwise\n (await import('cross-fetch')).default);\n\n const res = await _fetch(url, options);\n if (!res.ok) {\n const errData = unmarshal(await res.text());\n if (\n checkReadBack !== false &&\n errData.error?.prisma &&\n errData.error?.code === 'P2004' &&\n errData.error?.reason === 'RESULT_NOT_READABLE'\n ) {\n // policy doesn't allow mutation result to be read back, just return undefined\n return undefined as any;\n }\n const error: QueryError = new Error('An error occurred while fetching the data.');\n error.info = errData.error;\n error.status = res.status;\n throw error;\n }\n\n const textResult = await res.text();\n try {\n return unmarshal(textResult).data as R;\n } catch (err) {\n console.error(`Unable to deserialize data:`, textResult);\n throw err;\n }\n}\n\ntype QueryKey = [\n string /* prefix */,\n string /* model */,\n string /* operation */,\n unknown /* args */,\n {\n infinite: boolean;\n optimisticUpdate: boolean;\n } /* flags */\n];\n\n/**\n * Computes query key for the given model, operation and query args.\n * @param model Model name.\n * @param urlOrOperation Prisma operation (e.g, `findMany`) or request URL. If it's a URL, the last path segment will be used as the operation name.\n * @param args Prisma query arguments.\n * @param options Query options, including `infinite` indicating if it's an infinite query (defaults to false), and `optimisticUpdate` indicating if optimistic updates are enabled (defaults to true).\n * @returns Query key\n */\nexport function getQueryKey(\n model: string,\n urlOrOperation: string,\n args: unknown,\n options: { infinite: boolean; optimisticUpdate: boolean } = { infinite: false, optimisticUpdate: true }\n): QueryKey {\n if (!urlOrOperation) {\n throw new Error('Invalid urlOrOperation');\n }\n const operation = urlOrOperation.split('/').pop();\n\n const infinite = options.infinite;\n // infinite query doesn't support optimistic updates\n const optimisticUpdate = options.infinite ? false : options.optimisticUpdate;\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return [QUERY_KEY_PREFIX, model, operation!, args, { infinite, optimisticUpdate }];\n}\n\nexport function marshal(value: unknown) {\n const { data, meta } = serialize(value);\n if (meta) {\n return JSON.stringify({ ...(data as any), meta: { serialization: meta } });\n } else {\n return JSON.stringify(data);\n }\n}\n\nexport function unmarshal(value: string) {\n const parsed = JSON.parse(value);\n if (typeof parsed === 'object' && parsed?.data && parsed?.meta?.serialization) {\n const deserializedData = deserialize(parsed.data, parsed.meta.serialization);\n return { ...parsed, data: deserializedData };\n } else {\n return parsed;\n }\n}\n\nexport function makeUrl(url: string, args: unknown) {\n if (!args) {\n return url;\n }\n\n const { data, meta } = serialize(args);\n let result = `${url}?q=${encodeURIComponent(JSON.stringify(data))}`;\n if (meta) {\n result += `&meta=${encodeURIComponent(JSON.stringify({ serialization: meta }))}`;\n }\n return result;\n}\n\ntype InvalidationPredicate = ({ queryKey }: { queryKey: readonly unknown[] }) => boolean;\ntype InvalidateFunc = (predicate: InvalidationPredicate) => Promise<void>;\ntype MutationOptions = {\n onMutate?: (...args: any[]) => any;\n onSuccess?: (...args: any[]) => any;\n onSettled?: (...args: any[]) => any;\n};\n\n// sets up invalidation hook for a mutation\nexport function setupInvalidation(\n model: string,\n operation: string,\n modelMeta: ModelMeta,\n options: MutationOptions,\n invalidate: InvalidateFunc,\n logging = false\n) {\n const origOnSuccess = options?.onSuccess;\n options.onSuccess = async (...args: unknown[]) => {\n const [_, variables] = args;\n const predicate = await getInvalidationPredicate(\n model,\n operation as PrismaWriteActionType,\n variables,\n modelMeta,\n logging\n );\n await invalidate(predicate);\n return origOnSuccess?.(...args);\n };\n}\n\n// gets a predicate for evaluating whether a query should be invalidated\nasync function getInvalidationPredicate(\n model: string,\n operation: PrismaWriteActionType,\n mutationArgs: any,\n modelMeta: ModelMeta,\n logging = false\n) {\n const mutatedModels = await getMutatedModels(model, operation, mutationArgs, modelMeta);\n\n return ({ queryKey }: { queryKey: readonly unknown[] }) => {\n const [_, queryModel, , args] = queryKey as QueryKey;\n\n if (mutatedModels.includes(queryModel)) {\n // direct match\n if (logging) {\n console.log(`Invalidating query ${JSON.stringify(queryKey)} due to mutation \"${model}.${operation}\"`);\n }\n return true;\n }\n\n if (args) {\n // traverse query args to find nested reads that match the model under mutation\n if (findNestedRead(queryModel, mutatedModels, modelMeta, args)) {\n if (logging) {\n console.log(\n `Invalidating query ${JSON.stringify(queryKey)} due to mutation \"${model}.${operation}\"`\n );\n }\n return true;\n }\n }\n\n return false;\n };\n}\n\n// find nested reads that match the given models\nfunction findNestedRead(visitingModel: string, targetModels: string[], modelMeta: ModelMeta, args: any) {\n const modelsRead = getReadModels(visitingModel, modelMeta, args);\n return targetModels.some((m) => modelsRead.includes(m));\n}\n\ntype QueryCache = {\n queryKey: readonly unknown[];\n state: {\n data: unknown;\n error: unknown;\n };\n}[];\n\ntype SetCacheFunc = (queryKey: readonly unknown[], data: unknown) => void;\n\n/**\n * Sets up optimistic update and invalidation (after settled) for a mutation.\n */\nexport function setupOptimisticUpdate(\n model: string,\n operation: string,\n modelMeta: ModelMeta,\n options: MutationOptions & ExtraMutationOptions,\n queryCache: QueryCache,\n setCache: SetCacheFunc,\n invalidate?: InvalidateFunc,\n logging = false\n) {\n const origOnMutate = options?.onMutate;\n const origOnSettled = options?.onSettled;\n\n // optimistic update on mutate\n options.onMutate = async (...args: unknown[]) => {\n const [variables] = args;\n await optimisticUpdate(\n model,\n operation as PrismaWriteActionType,\n variables,\n options,\n modelMeta,\n queryCache,\n setCache,\n logging\n );\n return origOnMutate?.(...args);\n };\n\n // invalidate on settled\n options.onSettled = async (...args: unknown[]) => {\n if (invalidate) {\n const [, , variables] = args;\n const predicate = await getInvalidationPredicate(\n model,\n operation as PrismaWriteActionType,\n variables,\n modelMeta,\n logging\n );\n await invalidate(predicate);\n }\n return origOnSettled?.(...args);\n };\n}\n\n// optimistically updates query cache\nasync function optimisticUpdate(\n mutationModel: string,\n mutationOp: string,\n mutationArgs: any,\n options: MutationOptions & ExtraMutationOptions,\n modelMeta: ModelMeta,\n queryCache: QueryCache,\n setCache: SetCacheFunc,\n logging = false\n) {\n for (const cacheItem of queryCache) {\n const {\n queryKey,\n state: { data, error },\n } = cacheItem;\n\n if (!isZenStackQueryKey(queryKey)) {\n // skip non-zenstack queries\n continue;\n }\n\n if (error) {\n if (logging) {\n console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);\n }\n continue;\n }\n\n const [_, queryModel, queryOperation, queryArgs, queryOptions] = queryKey;\n if (!queryOptions?.optimisticUpdate) {\n if (logging) {\n console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);\n }\n continue;\n }\n\n if (options.optimisticDataProvider) {\n const providerResult = await options.optimisticDataProvider({\n queryModel,\n queryOperation,\n queryArgs,\n currentData: data,\n mutationArgs,\n });\n\n if (providerResult?.kind === 'Skip') {\n // skip\n if (logging) {\n console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to provider`);\n }\n continue;\n } else if (providerResult?.kind === 'Update') {\n // update cache\n if (logging) {\n console.log(`Optimistically updating query ${JSON.stringify(queryKey)} due to provider`);\n }\n setCache(queryKey, providerResult.data);\n continue;\n }\n }\n\n // proceed with default optimistic update\n const mutatedData = await applyMutation(\n queryModel,\n queryOperation,\n data,\n mutationModel,\n mutationOp as PrismaWriteActionType,\n mutationArgs,\n modelMeta,\n logging\n );\n\n if (mutatedData !== undefined) {\n // mutation applicable to this query, update cache\n if (logging) {\n console.log(\n `Optimistically updating query ${JSON.stringify(\n queryKey\n )} due to mutation \"${mutationModel}.${mutationOp}\"`\n );\n }\n setCache(queryKey, mutatedData);\n }\n }\n}\n\nfunction isZenStackQueryKey(queryKey: readonly unknown[]): queryKey is QueryKey {\n if (queryKey.length < 5) {\n return false;\n }\n\n if (queryKey[0] !== QUERY_KEY_PREFIX) {\n return false;\n }\n\n return true;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,qBAAuC;AACvC,mBAMO;AAUA,IAAM,mBAAmB;AA8KzB,SAAS,YACZ,OACA,gBACA,MACA,UAA4D,EAAE,UAAU,OAAO,kBAAkB,KAAK,GAC9F;AACR,MAAI,CAAC,gBAAgB;AACjB,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC5C;AACA,QAAM,YAAY,eAAe,MAAM,GAAG,EAAE,IAAI;AAEhD,QAAM,WAAW,QAAQ;AAEzB,QAAM,mBAAmB,QAAQ,WAAW,QAAQ,QAAQ;AAG5D,SAAO,CAAC,kBAAkB,OAAO,WAAY,MAAM,EAAE,UAAU,iBAAiB,CAAC;AACrF;","names":[]}
@@ -1,21 +0,0 @@
1
- // src/runtime/common.ts
2
- import { deserialize, serialize } from "@zenstackhq/runtime/browser";
3
- import {
4
- applyMutation,
5
- getMutatedModels,
6
- getReadModels
7
- } from "@zenstackhq/runtime/cross";
8
- var QUERY_KEY_PREFIX = "zenstack";
9
- function getQueryKey(model, urlOrOperation, args, options = { infinite: false, optimisticUpdate: true }) {
10
- if (!urlOrOperation) {
11
- throw new Error("Invalid urlOrOperation");
12
- }
13
- const operation = urlOrOperation.split("/").pop();
14
- const infinite = options.infinite;
15
- const optimisticUpdate = options.infinite ? false : options.optimisticUpdate;
16
- return [QUERY_KEY_PREFIX, model, operation, args, { infinite, optimisticUpdate }];
17
- }
18
- export {
19
- getQueryKey
20
- };
21
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/runtime/common.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { deserialize, serialize } from '@zenstackhq/runtime/browser';\nimport {\n applyMutation,\n getMutatedModels,\n getReadModels,\n type ModelMeta,\n type PrismaWriteActionType,\n} from '@zenstackhq/runtime/cross';\n\n/**\n * The default query endpoint.\n */\nexport const DEFAULT_QUERY_ENDPOINT = '/api/model';\n\n/**\n * Prefix for react-query keys.\n */\nexport const QUERY_KEY_PREFIX = 'zenstack';\n\n/**\n * Function signature for `fetch`.\n */\nexport type FetchFn = (url: string, options?: RequestInit) => Promise<Response>;\n\n/**\n * Type for query and mutation errors.\n */\nexport type QueryError = Error & {\n /**\n * Additional error information.\n */\n info?: unknown;\n\n /**\n * HTTP status code.\n */\n status?: number;\n};\n\n/**\n * Result of optimistic data provider.\n */\nexport type OptimisticDataProviderResult = {\n /**\n * Kind of the result.\n * - Update: use the `data` field to update the query cache.\n * - Skip: skip the optimistic update for this query.\n * - ProceedDefault: proceed with the default optimistic update.\n */\n kind: 'Update' | 'Skip' | 'ProceedDefault';\n\n /**\n * Data to update the query cache. Only applicable if `kind` is 'Update'.\n *\n * If the data is an object with fields updated, it should have a `$optimistic`\n * field set to `true`. If it's an array and an element object is created or updated,\n * the element should have a `$optimistic` field set to `true`.\n */\n data?: any;\n};\n\n/**\n * Optimistic data provider.\n *\n * @param args Arguments.\n * @param args.queryModel The model of the query.\n * @param args.queryOperation The operation of the query, `findMany`, `count`, etc.\n * @param args.queryArgs The arguments of the query.\n * @param args.currentData The current cache data for the query.\n * @param args.mutationArgs The arguments of the mutation.\n */\nexport type OptimisticDataProvider = (args: {\n queryModel: string;\n queryOperation: string;\n queryArgs: any;\n currentData: any;\n mutationArgs: any;\n}) => OptimisticDataProviderResult | Promise<OptimisticDataProviderResult>;\n\n/**\n * Extra mutation options.\n */\nexport type ExtraMutationOptions = {\n /**\n * Whether to automatically invalidate queries potentially affected by the mutation. Defaults to `true`.\n */\n invalidateQueries?: boolean;\n\n /**\n * Whether to optimistically update queries potentially affected by the mutation. Defaults to `false`.\n */\n optimisticUpdate?: boolean;\n\n /**\n * A callback for computing optimistic update data for each query cache entry.\n */\n optimisticDataProvider?: OptimisticDataProvider;\n};\n\n/**\n * Extra query options.\n */\nexport type ExtraQueryOptions = {\n /**\n * Whether to opt-in to optimistic updates for this query. Defaults to `true`.\n */\n optimisticUpdate?: boolean;\n};\n\n/**\n * Context type for configuring the hooks.\n */\nexport type APIContext = {\n /**\n * The endpoint to use for the queries.\n */\n endpoint?: string;\n\n /**\n * A custom fetch function for sending the HTTP requests.\n */\n fetch?: FetchFn;\n\n /**\n * If logging is enabled.\n */\n logging?: boolean;\n};\n\nexport async function fetcher<R, C extends boolean>(\n url: string,\n options?: RequestInit,\n customFetch?: FetchFn,\n checkReadBack?: C\n): Promise<C extends true ? R | undefined : R> {\n // Note: 'cross-fetch' is supposed to handle fetch compatibility\n // but it doesn't work for cloudflare workers\n const _fetch =\n customFetch ??\n // check if fetch is available globally\n (typeof fetch === 'function'\n ? fetch\n : // fallback to 'cross-fetch' if otherwise\n (await import('cross-fetch')).default);\n\n const res = await _fetch(url, options);\n if (!res.ok) {\n const errData = unmarshal(await res.text());\n if (\n checkReadBack !== false &&\n errData.error?.prisma &&\n errData.error?.code === 'P2004' &&\n errData.error?.reason === 'RESULT_NOT_READABLE'\n ) {\n // policy doesn't allow mutation result to be read back, just return undefined\n return undefined as any;\n }\n const error: QueryError = new Error('An error occurred while fetching the data.');\n error.info = errData.error;\n error.status = res.status;\n throw error;\n }\n\n const textResult = await res.text();\n try {\n return unmarshal(textResult).data as R;\n } catch (err) {\n console.error(`Unable to deserialize data:`, textResult);\n throw err;\n }\n}\n\ntype QueryKey = [\n string /* prefix */,\n string /* model */,\n string /* operation */,\n unknown /* args */,\n {\n infinite: boolean;\n optimisticUpdate: boolean;\n } /* flags */\n];\n\n/**\n * Computes query key for the given model, operation and query args.\n * @param model Model name.\n * @param urlOrOperation Prisma operation (e.g, `findMany`) or request URL. If it's a URL, the last path segment will be used as the operation name.\n * @param args Prisma query arguments.\n * @param options Query options, including `infinite` indicating if it's an infinite query (defaults to false), and `optimisticUpdate` indicating if optimistic updates are enabled (defaults to true).\n * @returns Query key\n */\nexport function getQueryKey(\n model: string,\n urlOrOperation: string,\n args: unknown,\n options: { infinite: boolean; optimisticUpdate: boolean } = { infinite: false, optimisticUpdate: true }\n): QueryKey {\n if (!urlOrOperation) {\n throw new Error('Invalid urlOrOperation');\n }\n const operation = urlOrOperation.split('/').pop();\n\n const infinite = options.infinite;\n // infinite query doesn't support optimistic updates\n const optimisticUpdate = options.infinite ? false : options.optimisticUpdate;\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return [QUERY_KEY_PREFIX, model, operation!, args, { infinite, optimisticUpdate }];\n}\n\nexport function marshal(value: unknown) {\n const { data, meta } = serialize(value);\n if (meta) {\n return JSON.stringify({ ...(data as any), meta: { serialization: meta } });\n } else {\n return JSON.stringify(data);\n }\n}\n\nexport function unmarshal(value: string) {\n const parsed = JSON.parse(value);\n if (typeof parsed === 'object' && parsed?.data && parsed?.meta?.serialization) {\n const deserializedData = deserialize(parsed.data, parsed.meta.serialization);\n return { ...parsed, data: deserializedData };\n } else {\n return parsed;\n }\n}\n\nexport function makeUrl(url: string, args: unknown) {\n if (!args) {\n return url;\n }\n\n const { data, meta } = serialize(args);\n let result = `${url}?q=${encodeURIComponent(JSON.stringify(data))}`;\n if (meta) {\n result += `&meta=${encodeURIComponent(JSON.stringify({ serialization: meta }))}`;\n }\n return result;\n}\n\ntype InvalidationPredicate = ({ queryKey }: { queryKey: readonly unknown[] }) => boolean;\ntype InvalidateFunc = (predicate: InvalidationPredicate) => Promise<void>;\ntype MutationOptions = {\n onMutate?: (...args: any[]) => any;\n onSuccess?: (...args: any[]) => any;\n onSettled?: (...args: any[]) => any;\n};\n\n// sets up invalidation hook for a mutation\nexport function setupInvalidation(\n model: string,\n operation: string,\n modelMeta: ModelMeta,\n options: MutationOptions,\n invalidate: InvalidateFunc,\n logging = false\n) {\n const origOnSuccess = options?.onSuccess;\n options.onSuccess = async (...args: unknown[]) => {\n const [_, variables] = args;\n const predicate = await getInvalidationPredicate(\n model,\n operation as PrismaWriteActionType,\n variables,\n modelMeta,\n logging\n );\n await invalidate(predicate);\n return origOnSuccess?.(...args);\n };\n}\n\n// gets a predicate for evaluating whether a query should be invalidated\nasync function getInvalidationPredicate(\n model: string,\n operation: PrismaWriteActionType,\n mutationArgs: any,\n modelMeta: ModelMeta,\n logging = false\n) {\n const mutatedModels = await getMutatedModels(model, operation, mutationArgs, modelMeta);\n\n return ({ queryKey }: { queryKey: readonly unknown[] }) => {\n const [_, queryModel, , args] = queryKey as QueryKey;\n\n if (mutatedModels.includes(queryModel)) {\n // direct match\n if (logging) {\n console.log(`Invalidating query ${JSON.stringify(queryKey)} due to mutation \"${model}.${operation}\"`);\n }\n return true;\n }\n\n if (args) {\n // traverse query args to find nested reads that match the model under mutation\n if (findNestedRead(queryModel, mutatedModels, modelMeta, args)) {\n if (logging) {\n console.log(\n `Invalidating query ${JSON.stringify(queryKey)} due to mutation \"${model}.${operation}\"`\n );\n }\n return true;\n }\n }\n\n return false;\n };\n}\n\n// find nested reads that match the given models\nfunction findNestedRead(visitingModel: string, targetModels: string[], modelMeta: ModelMeta, args: any) {\n const modelsRead = getReadModels(visitingModel, modelMeta, args);\n return targetModels.some((m) => modelsRead.includes(m));\n}\n\ntype QueryCache = {\n queryKey: readonly unknown[];\n state: {\n data: unknown;\n error: unknown;\n };\n}[];\n\ntype SetCacheFunc = (queryKey: readonly unknown[], data: unknown) => void;\n\n/**\n * Sets up optimistic update and invalidation (after settled) for a mutation.\n */\nexport function setupOptimisticUpdate(\n model: string,\n operation: string,\n modelMeta: ModelMeta,\n options: MutationOptions & ExtraMutationOptions,\n queryCache: QueryCache,\n setCache: SetCacheFunc,\n invalidate?: InvalidateFunc,\n logging = false\n) {\n const origOnMutate = options?.onMutate;\n const origOnSettled = options?.onSettled;\n\n // optimistic update on mutate\n options.onMutate = async (...args: unknown[]) => {\n const [variables] = args;\n await optimisticUpdate(\n model,\n operation as PrismaWriteActionType,\n variables,\n options,\n modelMeta,\n queryCache,\n setCache,\n logging\n );\n return origOnMutate?.(...args);\n };\n\n // invalidate on settled\n options.onSettled = async (...args: unknown[]) => {\n if (invalidate) {\n const [, , variables] = args;\n const predicate = await getInvalidationPredicate(\n model,\n operation as PrismaWriteActionType,\n variables,\n modelMeta,\n logging\n );\n await invalidate(predicate);\n }\n return origOnSettled?.(...args);\n };\n}\n\n// optimistically updates query cache\nasync function optimisticUpdate(\n mutationModel: string,\n mutationOp: string,\n mutationArgs: any,\n options: MutationOptions & ExtraMutationOptions,\n modelMeta: ModelMeta,\n queryCache: QueryCache,\n setCache: SetCacheFunc,\n logging = false\n) {\n for (const cacheItem of queryCache) {\n const {\n queryKey,\n state: { data, error },\n } = cacheItem;\n\n if (!isZenStackQueryKey(queryKey)) {\n // skip non-zenstack queries\n continue;\n }\n\n if (error) {\n if (logging) {\n console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);\n }\n continue;\n }\n\n const [_, queryModel, queryOperation, queryArgs, queryOptions] = queryKey;\n if (!queryOptions?.optimisticUpdate) {\n if (logging) {\n console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);\n }\n continue;\n }\n\n if (options.optimisticDataProvider) {\n const providerResult = await options.optimisticDataProvider({\n queryModel,\n queryOperation,\n queryArgs,\n currentData: data,\n mutationArgs,\n });\n\n if (providerResult?.kind === 'Skip') {\n // skip\n if (logging) {\n console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to provider`);\n }\n continue;\n } else if (providerResult?.kind === 'Update') {\n // update cache\n if (logging) {\n console.log(`Optimistically updating query ${JSON.stringify(queryKey)} due to provider`);\n }\n setCache(queryKey, providerResult.data);\n continue;\n }\n }\n\n // proceed with default optimistic update\n const mutatedData = await applyMutation(\n queryModel,\n queryOperation,\n data,\n mutationModel,\n mutationOp as PrismaWriteActionType,\n mutationArgs,\n modelMeta,\n logging\n );\n\n if (mutatedData !== undefined) {\n // mutation applicable to this query, update cache\n if (logging) {\n console.log(\n `Optimistically updating query ${JSON.stringify(\n queryKey\n )} due to mutation \"${mutationModel}.${mutationOp}\"`\n );\n }\n setCache(queryKey, mutatedData);\n }\n }\n}\n\nfunction isZenStackQueryKey(queryKey: readonly unknown[]): queryKey is QueryKey {\n if (queryKey.length < 5) {\n return false;\n }\n\n if (queryKey[0] !== QUERY_KEY_PREFIX) {\n return false;\n }\n\n return true;\n}\n"],"mappings":";AAEA,SAAS,aAAa,iBAAiB;AACvC;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OAGG;AAUA,IAAM,mBAAmB;AA8KzB,SAAS,YACZ,OACA,gBACA,MACA,UAA4D,EAAE,UAAU,OAAO,kBAAkB,KAAK,GAC9F;AACR,MAAI,CAAC,gBAAgB;AACjB,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC5C;AACA,QAAM,YAAY,eAAe,MAAM,GAAG,EAAE,IAAI;AAEhD,QAAM,WAAW,QAAQ;AAEzB,QAAM,mBAAmB,QAAQ,WAAW,QAAQ,QAAQ;AAG5D,SAAO,CAAC,kBAAkB,OAAO,WAAY,MAAM,EAAE,UAAU,iBAAiB,CAAC;AACrF;","names":[]}