@zenstackhq/tanstack-query 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/generator.js +29 -8
  2. package/generator.js.map +1 -1
  3. package/package.json +5 -5
  4. package/runtime/{common-5e18c135.d.ts → common-82ef6965.d.ts} +13 -2
  5. package/runtime/index.d.mts +1 -1
  6. package/runtime/index.d.ts +1 -1
  7. package/runtime/index.js +2 -2
  8. package/runtime/index.js.map +1 -1
  9. package/runtime/index.mjs +7 -3
  10. package/runtime/index.mjs.map +1 -1
  11. package/runtime/react.d.mts +8 -3
  12. package/runtime/react.d.ts +8 -3
  13. package/runtime/react.js +94 -12
  14. package/runtime/react.js.map +1 -1
  15. package/runtime/react.mjs +99 -13
  16. package/runtime/react.mjs.map +1 -1
  17. package/runtime/svelte.d.mts +8 -3
  18. package/runtime/svelte.d.ts +8 -3
  19. package/runtime/svelte.js +99 -10
  20. package/runtime/svelte.js.map +1 -1
  21. package/runtime/svelte.mjs +104 -11
  22. package/runtime/svelte.mjs.map +1 -1
  23. package/runtime/vue.d.mts +9 -3
  24. package/runtime/vue.d.ts +9 -3
  25. package/runtime/vue.js +94 -12
  26. package/runtime/vue.js.map +1 -1
  27. package/runtime/vue.mjs +99 -13
  28. package/runtime/vue.mjs.map +1 -1
  29. package/runtime-v5/{common-5e18c135.d.ts → common-82ef6965.d.ts} +13 -2
  30. package/runtime-v5/index.d.mts +1 -1
  31. package/runtime-v5/index.d.ts +1 -1
  32. package/runtime-v5/index.js +2 -2
  33. package/runtime-v5/index.js.map +1 -1
  34. package/runtime-v5/index.mjs +7 -3
  35. package/runtime-v5/index.mjs.map +1 -1
  36. package/runtime-v5/react.d.mts +19 -3
  37. package/runtime-v5/react.d.ts +19 -3
  38. package/runtime-v5/react.js +94 -12
  39. package/runtime-v5/react.js.map +1 -1
  40. package/runtime-v5/react.mjs +99 -13
  41. package/runtime-v5/react.mjs.map +1 -1
  42. package/runtime-v5/svelte.d.mts +5 -3
  43. package/runtime-v5/svelte.d.ts +5 -3
  44. package/runtime-v5/svelte.js +94 -12
  45. package/runtime-v5/svelte.js.map +1 -1
  46. package/runtime-v5/svelte.mjs +99 -13
  47. package/runtime-v5/svelte.mjs.map +1 -1
  48. package/runtime-v5/vue.d.mts +9 -3
  49. package/runtime-v5/vue.d.ts +9 -3
  50. package/runtime-v5/vue.js +94 -12
  51. package/runtime-v5/vue.js.map +1 -1
  52. package/runtime-v5/vue.mjs +99 -13
  53. package/runtime-v5/vue.mjs.map +1 -1
@@ -2,7 +2,7 @@ import * as _tanstack_react_query_v5_build_legacy_types from '@tanstack/react-qu
2
2
  import * as react from 'react';
3
3
  import { UseQueryOptions, UseInfiniteQueryOptions, InfiniteData, UseMutationOptions } from '@tanstack/react-query';
4
4
  import { ModelMeta } from '@zenstackhq/runtime/cross';
5
- import { A as APIContext, F as FetchFn } from './common-5e18c135.js';
5
+ import { A as APIContext, F as FetchFn } from './common-82ef6965.js';
6
6
 
7
7
  /**
8
8
  * Context for configuring react hooks.
@@ -27,9 +27,11 @@ declare const Provider: react.Provider<APIContext>;
27
27
  * @param url The request URL.
28
28
  * @param args The request args object, URL-encoded and appended as "?q=" parameter
29
29
  * @param options The react-query options object
30
+ * @param fetch The fetch function to use for sending the HTTP request
31
+ * @param optimisticUpdate Whether to enable automatic optimistic update
30
32
  * @returns useQuery hook
31
33
  */
32
- declare function useModelQuery<R>(model: string, url: string, args?: unknown, options?: Omit<UseQueryOptions<R>, 'queryKey'>, fetch?: FetchFn): _tanstack_react_query_v5_build_legacy_types.UseQueryResult<R, Error>;
34
+ declare function useModelQuery<R>(model: string, url: string, args?: unknown, options?: Omit<UseQueryOptions<R>, 'queryKey'>, fetch?: FetchFn, optimisticUpdate?: boolean): _tanstack_react_query_v5_build_legacy_types.UseQueryResult<R, Error>;
33
35
  /**
34
36
  * Creates a react-query infinite query.
35
37
  *
@@ -37,9 +39,23 @@ declare function useModelQuery<R>(model: string, url: string, args?: unknown, op
37
39
  * @param url The request URL.
38
40
  * @param args The initial request args object, URL-encoded and appended as "?q=" parameter
39
41
  * @param options The react-query infinite query options object
42
+ * @param fetch The fetch function to use for sending the HTTP request
40
43
  * @returns useInfiniteQuery hook
41
44
  */
42
45
  declare function useInfiniteModelQuery<R>(model: string, url: string, args: unknown, options: Omit<UseInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>, fetch?: FetchFn): _tanstack_react_query_v5_build_legacy_types.UseInfiniteQueryResult<InfiniteData<R, unknown>, unknown>;
43
- declare function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(model: string, method: 'POST' | 'PUT' | 'DELETE', url: string, modelMeta: ModelMeta, options?: Omit<UseMutationOptions<Result, unknown, T>, 'mutationFn'>, fetch?: FetchFn, invalidateQueries?: boolean, checkReadBack?: C): _tanstack_react_query_v5_build_legacy_types.UseMutationResult<Result, unknown, T, unknown>;
46
+ /**
47
+ * Creates a react-query mutation
48
+ *
49
+ * @param model The name of the model under mutation.
50
+ * @param method The HTTP method.
51
+ * @param url The request URL.
52
+ * @param modelMeta The model metadata.
53
+ * @param options The react-query options.
54
+ * @param fetch The fetch function to use for sending the HTTP request
55
+ * @param invalidateQueries Whether to invalidate queries after mutation.
56
+ * @param checkReadBack Whether to check for read back errors and return undefined if found.
57
+ * @param optimisticUpdate Whether to enable automatic optimistic update
58
+ */
59
+ declare function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(model: string, method: 'POST' | 'PUT' | 'DELETE', url: string, modelMeta: ModelMeta, options?: Omit<UseMutationOptions<Result, unknown, T>, 'mutationFn'>, fetch?: FetchFn, invalidateQueries?: boolean, checkReadBack?: C, optimisticUpdate?: boolean): _tanstack_react_query_v5_build_legacy_types.UseMutationResult<Result, unknown, T, unknown>;
44
60
 
45
61
  export { Provider, RequestHandlerContext, getHooksContext, useInfiniteModelQuery, useModelMutation, useModelQuery };
@@ -122,12 +122,12 @@ function fetcher(url, options, fetch2, checkReadBack) {
122
122
  }
123
123
  });
124
124
  }
125
- function getQueryKey(model, urlOrOperation, args) {
125
+ function getQueryKey(model, urlOrOperation, args, infinite = false, optimisticUpdate2 = false) {
126
126
  if (!urlOrOperation) {
127
127
  throw new Error("Invalid urlOrOperation");
128
128
  }
129
129
  const operation = urlOrOperation.split("/").pop();
130
- return [QUERY_KEY_PREFIX, model, operation, args];
130
+ return [QUERY_KEY_PREFIX, model, operation, args, { infinite, optimisticUpdate: optimisticUpdate2 }];
131
131
  }
132
132
  function marshal(value) {
133
133
  const { data, meta } = (0, import_browser.serialize)(value);
@@ -177,17 +177,19 @@ function getInvalidationPredicate(model, operation, mutationArgs, modelMeta, log
177
177
  return __async(this, null, function* () {
178
178
  const mutatedModels = yield (0, import_cross.getMutatedModels)(model, operation, mutationArgs, modelMeta);
179
179
  return ({ queryKey }) => {
180
- const [_model, queryModel, queryOp, args] = queryKey;
180
+ const [_, queryModel, , args] = queryKey;
181
181
  if (mutatedModels.includes(queryModel)) {
182
182
  if (logging) {
183
- console.log(`Invalidating query [${queryKey}] due to mutation "${model}.${operation}"`);
183
+ console.log(`Invalidating query ${JSON.stringify(queryKey)} due to mutation "${model}.${operation}"`);
184
184
  }
185
185
  return true;
186
186
  }
187
187
  if (args) {
188
188
  if (findNestedRead(queryModel, mutatedModels, modelMeta, args)) {
189
189
  if (logging) {
190
- console.log(`Invalidating query [${queryKey}] due to mutation "${model}.${operation}"`);
190
+ console.log(
191
+ `Invalidating query ${JSON.stringify(queryKey)} due to mutation "${model}.${operation}"`
192
+ );
191
193
  }
192
194
  return true;
193
195
  }
@@ -200,6 +202,74 @@ function findNestedRead(visitingModel, targetModels, modelMeta, args) {
200
202
  const modelsRead = (0, import_cross.getReadModels)(visitingModel, modelMeta, args);
201
203
  return targetModels.some((m) => modelsRead.includes(m));
202
204
  }
205
+ function setupOptimisticUpdate(model, operation, modelMeta, options, queryCache, setCache, invalidate, logging = false) {
206
+ const origOnMutate = options == null ? void 0 : options.onMutate;
207
+ const origOnSettled = options == null ? void 0 : options.onSettled;
208
+ options.onMutate = (...args) => __async(this, null, function* () {
209
+ const [variables] = args;
210
+ yield optimisticUpdate(
211
+ model,
212
+ operation,
213
+ variables,
214
+ modelMeta,
215
+ queryCache,
216
+ setCache,
217
+ logging
218
+ );
219
+ return origOnMutate == null ? void 0 : origOnMutate(...args);
220
+ });
221
+ options.onSettled = (...args) => __async(this, null, function* () {
222
+ if (invalidate) {
223
+ const [, , variables] = args;
224
+ const predicate = yield getInvalidationPredicate(
225
+ model,
226
+ operation,
227
+ variables,
228
+ modelMeta,
229
+ logging
230
+ );
231
+ yield invalidate(predicate);
232
+ }
233
+ return origOnSettled == null ? void 0 : origOnSettled(...args);
234
+ });
235
+ }
236
+ function optimisticUpdate(mutationModel, mutationOp, mutationArgs, modelMeta, queryCache, setCache, logging = false) {
237
+ return __async(this, null, function* () {
238
+ for (const cacheItem of queryCache) {
239
+ const {
240
+ queryKey,
241
+ state: { data, error }
242
+ } = cacheItem;
243
+ if (error) {
244
+ continue;
245
+ }
246
+ const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate: optimisticUpdate2 }] = queryKey;
247
+ if (!optimisticUpdate2) {
248
+ continue;
249
+ }
250
+ const mutatedData = yield (0, import_cross.applyMutation)(
251
+ queryModel,
252
+ queryOp,
253
+ data,
254
+ mutationModel,
255
+ mutationOp,
256
+ mutationArgs,
257
+ modelMeta,
258
+ logging
259
+ );
260
+ if (mutatedData !== void 0) {
261
+ if (logging) {
262
+ console.log(
263
+ `Optimistically updating query ${JSON.stringify(
264
+ queryKey
265
+ )} due to mutation "${mutationModel}.${mutationOp}"`
266
+ );
267
+ }
268
+ setCache(queryKey, mutatedData);
269
+ }
270
+ }
271
+ });
272
+ }
203
273
 
204
274
  // src/runtime-v5/react.ts
205
275
  var RequestHandlerContext = (0, import_react.createContext)({
@@ -211,22 +281,22 @@ function getHooksContext() {
211
281
  return __spreadValues({ endpoint: endpoint != null ? endpoint : DEFAULT_QUERY_ENDPOINT }, rest);
212
282
  }
213
283
  var Provider = RequestHandlerContext.Provider;
214
- function useModelQuery(model, url, args, options, fetch2) {
284
+ function useModelQuery(model, url, args, options, fetch2, optimisticUpdate2 = false) {
215
285
  const reqUrl = makeUrl(url, args);
216
286
  return (0, import_react_query_v5.useQuery)(__spreadValues({
217
- queryKey: getQueryKey(model, url, args),
287
+ queryKey: getQueryKey(model, url, args, false, optimisticUpdate2),
218
288
  queryFn: () => fetcher(reqUrl, void 0, fetch2, false)
219
289
  }, options));
220
290
  }
221
291
  function useInfiniteModelQuery(model, url, args, options, fetch2) {
222
292
  return (0, import_react_query_v5.useInfiniteQuery)(__spreadValues({
223
- queryKey: getQueryKey(model, url, args),
293
+ queryKey: getQueryKey(model, url, args, true),
224
294
  queryFn: ({ pageParam }) => {
225
295
  return fetcher(makeUrl(url, pageParam != null ? pageParam : args), void 0, fetch2, false);
226
296
  }
227
297
  }, options));
228
298
  }
229
- function useModelMutation(model, method, url, modelMeta, options, fetch2, invalidateQueries = true, checkReadBack) {
299
+ function useModelMutation(model, method, url, modelMeta, options, fetch2, invalidateQueries = true, checkReadBack, optimisticUpdate2 = false) {
230
300
  const queryClient = (0, import_react_query_v5.useQueryClient)();
231
301
  const mutationFn = (data) => {
232
302
  const reqUrl = method === "DELETE" ? makeUrl(url, data) : url;
@@ -241,10 +311,10 @@ function useModelMutation(model, method, url, modelMeta, options, fetch2, invali
241
311
  return fetcher(reqUrl, fetchInit, fetch2, checkReadBack);
242
312
  };
243
313
  const finalOptions = __spreadProps(__spreadValues({}, options), { mutationFn });
244
- if (invalidateQueries) {
314
+ const operation = url.split("/").pop();
315
+ if (operation) {
245
316
  const { logging } = (0, import_react.useContext)(RequestHandlerContext);
246
- const operation = url.split("/").pop();
247
- if (operation) {
317
+ if (invalidateQueries) {
248
318
  setupInvalidation(
249
319
  model,
250
320
  operation,
@@ -254,6 +324,18 @@ function useModelMutation(model, method, url, modelMeta, options, fetch2, invali
254
324
  logging
255
325
  );
256
326
  }
327
+ if (optimisticUpdate2) {
328
+ setupOptimisticUpdate(
329
+ model,
330
+ operation,
331
+ modelMeta,
332
+ finalOptions,
333
+ queryClient.getQueryCache().getAll(),
334
+ (queryKey, data) => queryClient.setQueryData(queryKey, data),
335
+ invalidateQueries ? (predicate) => queryClient.invalidateQueries({ predicate }) : void 0,
336
+ logging
337
+ );
338
+ }
257
339
  }
258
340
  return (0, import_react_query_v5.useMutation)(finalOptions);
259
341
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/runtime-v5/react.ts","../../src/runtime/common.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n useInfiniteQuery,\n useMutation,\n useQuery,\n useQueryClient,\n type InfiniteData,\n type UseInfiniteQueryOptions,\n type UseMutationOptions,\n type UseQueryOptions,\n} from '@tanstack/react-query-v5';\nimport type { ModelMeta } from '@zenstackhq/runtime/cross';\nimport { createContext, useContext } from 'react';\nimport {\n DEFAULT_QUERY_ENDPOINT,\n FetchFn,\n fetcher,\n getQueryKey,\n makeUrl,\n marshal,\n setupInvalidation,\n type APIContext,\n} from '../runtime/common';\n\n/**\n * Context for configuring react hooks.\n */\nexport const RequestHandlerContext = createContext<APIContext>({\n endpoint: DEFAULT_QUERY_ENDPOINT,\n fetch: undefined,\n});\n\n/**\n * Hooks context.\n */\nexport function getHooksContext() {\n const { endpoint, ...rest } = useContext(RequestHandlerContext);\n return { endpoint: endpoint ?? DEFAULT_QUERY_ENDPOINT, ...rest };\n}\n\n/**\n * Context provider.\n */\nexport const Provider = RequestHandlerContext.Provider;\n\n/**\n * Creates a react-query query.\n *\n * @param model The name of the model under query.\n * @param url The request URL.\n * @param args The request args object, URL-encoded and appended as \"?q=\" parameter\n * @param options The react-query options object\n * @returns useQuery hook\n */\nexport function useModelQuery<R>(\n model: string,\n url: string,\n args?: unknown,\n options?: Omit<UseQueryOptions<R>, 'queryKey'>,\n fetch?: FetchFn\n) {\n const reqUrl = makeUrl(url, args);\n return useQuery({\n queryKey: getQueryKey(model, url, args),\n queryFn: () => fetcher<R, false>(reqUrl, undefined, fetch, false),\n ...options,\n });\n}\n\n/**\n * Creates a react-query infinite query.\n *\n * @param model The name of the model under query.\n * @param url The request URL.\n * @param args The initial request args object, URL-encoded and appended as \"?q=\" parameter\n * @param options The react-query infinite query options object\n * @returns useInfiniteQuery hook\n */\nexport function useInfiniteModelQuery<R>(\n model: string,\n url: string,\n args: unknown,\n options: Omit<UseInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>,\n fetch?: FetchFn\n) {\n return useInfiniteQuery({\n queryKey: getQueryKey(model, url, args),\n queryFn: ({ pageParam }) => {\n return fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);\n },\n ...options,\n });\n}\n\nexport function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(\n model: string,\n method: 'POST' | 'PUT' | 'DELETE',\n url: string,\n modelMeta: ModelMeta,\n options?: Omit<UseMutationOptions<Result, unknown, T>, 'mutationFn'>,\n fetch?: FetchFn,\n invalidateQueries = true,\n checkReadBack?: C\n) {\n const queryClient = useQueryClient();\n const mutationFn = (data: any) => {\n const reqUrl = method === 'DELETE' ? makeUrl(url, data) : url;\n const fetchInit: RequestInit = {\n method,\n ...(method !== 'DELETE' && {\n headers: {\n 'content-type': 'application/json',\n },\n body: marshal(data),\n }),\n };\n return fetcher<R, C>(reqUrl, fetchInit, fetch, checkReadBack) as Promise<Result>;\n };\n\n const finalOptions = { ...options, mutationFn };\n if (invalidateQueries) {\n const { logging } = useContext(RequestHandlerContext);\n const operation = url.split('/').pop();\n if (operation) {\n setupInvalidation(\n model,\n operation,\n modelMeta,\n finalOptions,\n (predicate) => queryClient.invalidateQueries({ predicate }),\n logging\n );\n }\n }\n\n return useMutation(finalOptions);\n}\n","/* eslint-disable @typescript-eslint/no-unused-vars */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { deserialize, serialize } from '@zenstackhq/runtime/browser';\nimport { getMutatedModels, getReadModels, type ModelMeta, type PrismaWriteActionType } from '@zenstackhq/runtime/cross';\nimport * as crossFetch from 'cross-fetch';\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 * 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 fetch?: FetchFn,\n checkReadBack?: C\n): Promise<C extends true ? R | undefined : R> {\n const _fetch = fetch ?? crossFetch.fetch;\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: Error & { info?: unknown; status?: number } = new Error(\n 'An error occurred while fetching the data.'\n );\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 = [string /* prefix */, string /* model */, string /* operation */, unknown /* args */];\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 * @returns Query key\n */\nexport function getQueryKey(model: string, urlOrOperation: string, args: unknown): QueryKey {\n if (!urlOrOperation) {\n throw new Error('Invalid urlOrOperation');\n }\n const operation = urlOrOperation.split('/').pop();\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return [QUERY_KEY_PREFIX, model, operation!, args];\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 (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;\n\n// sets up invalidation hook for a mutation\nexport function setupInvalidation(\n model: string,\n operation: string,\n modelMeta: ModelMeta,\n options: { onSuccess?: (...args: any[]) => any },\n invalidate: (predicate: InvalidationPredicate) => Promise<void>,\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 [_model, queryModel, queryOp, args] = queryKey as QueryKey;\n\n if (mutatedModels.includes(queryModel)) {\n // direct match\n if (logging) {\n console.log(`Invalidating query [${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(`Invalidating query [${queryKey}] due to mutation \"${model}.${operation}\"`);\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,4BASO;AAEP,mBAA0C;;;ACV1C,qBAAuC;AACvC,mBAA4F;AAC5F,iBAA4B;AAKrB,IAAM,yBAAyB;AAK/B,IAAM,mBAAmB;AA2BhC,SAAsB,QAClB,KACA,SACAA,QACA,eAC2C;AAAA;AA9C/C;AA+CI,UAAM,SAASA,UAAA,OAAAA,SAAoB;AACnC,UAAM,MAAM,MAAM,OAAO,KAAK,OAAO;AACrC,QAAI,CAAC,IAAI,IAAI;AACT,YAAM,UAAU,UAAU,MAAM,IAAI,KAAK,CAAC;AAC1C,UACI,kBAAkB,WAClB,aAAQ,UAAR,mBAAe,aACf,aAAQ,UAAR,mBAAe,UAAS,aACxB,aAAQ,UAAR,mBAAe,YAAW,uBAC5B;AAEE,eAAO;AAAA,MACX;AACA,YAAM,QAAqD,IAAI;AAAA,QAC3D;AAAA,MACJ;AACA,YAAM,OAAO,QAAQ;AACrB,YAAM,SAAS,IAAI;AACnB,YAAM;AAAA,IACV;AAEA,UAAM,aAAa,MAAM,IAAI,KAAK;AAClC,QAAI;AACA,aAAO,UAAU,UAAU,EAAE;AAAA,IACjC,SAAS,KAAK;AACV,cAAQ,MAAM,+BAA+B,UAAU;AACvD,YAAM;AAAA,IACV;AAAA,EACJ;AAAA;AAWO,SAAS,YAAY,OAAe,gBAAwB,MAAyB;AACxF,MAAI,CAAC,gBAAgB;AACjB,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC5C;AACA,QAAM,YAAY,eAAe,MAAM,GAAG,EAAE,IAAI;AAEhD,SAAO,CAAC,kBAAkB,OAAO,WAAY,IAAI;AACrD;AAEO,SAAS,QAAQ,OAAgB;AACpC,QAAM,EAAE,MAAM,KAAK,QAAI,0BAAU,KAAK;AACtC,MAAI,MAAM;AACN,WAAO,KAAK,UAAU,iCAAM,OAAN,EAAoB,MAAM,EAAE,eAAe,KAAK,EAAE,EAAC;AAAA,EAC7E,OAAO;AACH,WAAO,KAAK,UAAU,IAAI;AAAA,EAC9B;AACJ;AAEO,SAAS,UAAU,OAAe;AAxGzC;AAyGI,QAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,MAAI,OAAO,UAAQ,YAAO,SAAP,mBAAa,gBAAe;AAC3C,UAAM,uBAAmB,4BAAY,OAAO,MAAM,OAAO,KAAK,aAAa;AAC3E,WAAO,iCAAK,SAAL,EAAa,MAAM,iBAAiB;AAAA,EAC/C,OAAO;AACH,WAAO;AAAA,EACX;AACJ;AAEO,SAAS,QAAQ,KAAa,MAAe;AAChD,MAAI,CAAC,MAAM;AACP,WAAO;AAAA,EACX;AAEA,QAAM,EAAE,MAAM,KAAK,QAAI,0BAAU,IAAI;AACrC,MAAI,SAAS,GAAG,GAAG,MAAM,mBAAmB,KAAK,UAAU,IAAI,CAAC,CAAC;AACjE,MAAI,MAAM;AACN,cAAU,SAAS,mBAAmB,KAAK,UAAU,EAAE,eAAe,KAAK,CAAC,CAAC,CAAC;AAAA,EAClF;AACA,SAAO;AACX;AAKO,SAAS,kBACZ,OACA,WACA,WACA,SACA,YACA,UAAU,OACZ;AACE,QAAM,gBAAgB,mCAAS;AAC/B,UAAQ,YAAY,IAAU,SAAoB;AAC9C,UAAM,CAAC,GAAG,SAAS,IAAI;AACvB,UAAM,YAAY,MAAM;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AACA,UAAM,WAAW,SAAS;AAC1B,WAAO,+CAAgB,GAAG;AAAA,EAC9B;AACJ;AAGA,SAAe,yBACX,OACA,WACA,cACA,WACA,UAAU,OACZ;AAAA;AACE,UAAM,gBAAgB,UAAM,+BAAiB,OAAO,WAAW,cAAc,SAAS;AAEtF,WAAO,CAAC,EAAE,SAAS,MAAwC;AACvD,YAAM,CAAC,QAAQ,YAAY,SAAS,IAAI,IAAI;AAE5C,UAAI,cAAc,SAAS,UAAU,GAAG;AAEpC,YAAI,SAAS;AACT,kBAAQ,IAAI,uBAAuB,QAAQ,sBAAsB,KAAK,IAAI,SAAS,GAAG;AAAA,QAC1F;AACA,eAAO;AAAA,MACX;AAEA,UAAI,MAAM;AAEN,YAAI,eAAe,YAAY,eAAe,WAAW,IAAI,GAAG;AAC5D,cAAI,SAAS;AACT,oBAAQ,IAAI,uBAAuB,QAAQ,sBAAsB,KAAK,IAAI,SAAS,GAAG;AAAA,UAC1F;AACA,iBAAO;AAAA,QACX;AAAA,MACJ;AAEA,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;AAGA,SAAS,eAAe,eAAuB,cAAwB,WAAsB,MAAW;AACpG,QAAM,iBAAa,4BAAc,eAAe,WAAW,IAAI;AAC/D,SAAO,aAAa,KAAK,CAAC,MAAM,WAAW,SAAS,CAAC,CAAC;AAC1D;;;ADrKO,IAAM,4BAAwB,4BAA0B;AAAA,EAC3D,UAAU;AAAA,EACV,OAAO;AACX,CAAC;AAKM,SAAS,kBAAkB;AAC9B,QAA8B,kCAAW,qBAAqB,GAAtD,WApCZ,IAoCkC,IAAT,iBAAS,IAAT,CAAb;AACR,SAAO,iBAAE,UAAU,8BAAY,0BAA2B;AAC9D;AAKO,IAAM,WAAW,sBAAsB;AAWvC,SAAS,cACZ,OACA,KACA,MACA,SACAC,QACF;AACE,QAAM,SAAS,QAAQ,KAAK,IAAI;AAChC,aAAO,gCAAS;AAAA,IACZ,UAAU,YAAY,OAAO,KAAK,IAAI;AAAA,IACtC,SAAS,MAAM,QAAkB,QAAQ,QAAWA,QAAO,KAAK;AAAA,KAC7D,QACN;AACL;AAWO,SAAS,sBACZ,OACA,KACA,MACA,SACAA,QACF;AACE,aAAO,wCAAiB;AAAA,IACpB,UAAU,YAAY,OAAO,KAAK,IAAI;AAAA,IACtC,SAAS,CAAC,EAAE,UAAU,MAAM;AACxB,aAAO,QAAkB,QAAQ,KAAK,gCAAa,IAAI,GAAG,QAAWA,QAAO,KAAK;AAAA,IACrF;AAAA,KACG,QACN;AACL;AAEO,SAAS,iBACZ,OACA,QACA,KACA,WACA,SACAA,QACA,oBAAoB,MACpB,eACF;AACE,QAAM,kBAAc,sCAAe;AACnC,QAAM,aAAa,CAAC,SAAc;AAC9B,UAAM,SAAS,WAAW,WAAW,QAAQ,KAAK,IAAI,IAAI;AAC1D,UAAM,YAAyB;AAAA,MAC3B;AAAA,OACI,WAAW,YAAY;AAAA,MACvB,SAAS;AAAA,QACL,gBAAgB;AAAA,MACpB;AAAA,MACA,MAAM,QAAQ,IAAI;AAAA,IACtB;AAEJ,WAAO,QAAc,QAAQ,WAAWA,QAAO,aAAa;AAAA,EAChE;AAEA,QAAM,eAAe,iCAAK,UAAL,EAAc,WAAW;AAC9C,MAAI,mBAAmB;AACnB,UAAM,EAAE,QAAQ,QAAI,yBAAW,qBAAqB;AACpD,UAAM,YAAY,IAAI,MAAM,GAAG,EAAE,IAAI;AACrC,QAAI,WAAW;AACX;AAAA,QACI;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,CAAC,cAAc,YAAY,kBAAkB,EAAE,UAAU,CAAC;AAAA,QAC1D;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAEA,aAAO,mCAAY,YAAY;AACnC;","names":["fetch","fetch"]}
1
+ {"version":3,"sources":["../../src/runtime-v5/react.ts","../../src/runtime/common.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n useInfiniteQuery,\n useMutation,\n useQuery,\n useQueryClient,\n type InfiniteData,\n type UseInfiniteQueryOptions,\n type UseMutationOptions,\n type UseQueryOptions,\n} from '@tanstack/react-query-v5';\nimport type { ModelMeta } from '@zenstackhq/runtime/cross';\nimport { createContext, useContext } from 'react';\nimport {\n DEFAULT_QUERY_ENDPOINT,\n FetchFn,\n fetcher,\n getQueryKey,\n makeUrl,\n marshal,\n setupInvalidation,\n setupOptimisticUpdate,\n type APIContext,\n} from '../runtime/common';\n\n/**\n * Context for configuring react hooks.\n */\nexport const RequestHandlerContext = createContext<APIContext>({\n endpoint: DEFAULT_QUERY_ENDPOINT,\n fetch: undefined,\n});\n\n/**\n * Hooks context.\n */\nexport function getHooksContext() {\n const { endpoint, ...rest } = useContext(RequestHandlerContext);\n return { endpoint: endpoint ?? DEFAULT_QUERY_ENDPOINT, ...rest };\n}\n\n/**\n * Context provider.\n */\nexport const Provider = RequestHandlerContext.Provider;\n\n/**\n * Creates a react-query query.\n *\n * @param model The name of the model under query.\n * @param url The request URL.\n * @param args The request args object, URL-encoded and appended as \"?q=\" parameter\n * @param options The react-query options object\n * @param fetch The fetch function to use for sending the HTTP request\n * @param optimisticUpdate Whether to enable automatic optimistic update\n * @returns useQuery hook\n */\nexport function useModelQuery<R>(\n model: string,\n url: string,\n args?: unknown,\n options?: Omit<UseQueryOptions<R>, 'queryKey'>,\n fetch?: FetchFn,\n optimisticUpdate = false\n) {\n const reqUrl = makeUrl(url, args);\n return useQuery({\n queryKey: getQueryKey(model, url, args, false, optimisticUpdate),\n queryFn: () => fetcher<R, false>(reqUrl, undefined, fetch, false),\n ...options,\n });\n}\n\n/**\n * Creates a react-query infinite query.\n *\n * @param model The name of the model under query.\n * @param url The request URL.\n * @param args The initial request args object, URL-encoded and appended as \"?q=\" parameter\n * @param options The react-query infinite query options object\n * @param fetch The fetch function to use for sending the HTTP request\n * @returns useInfiniteQuery hook\n */\nexport function useInfiniteModelQuery<R>(\n model: string,\n url: string,\n args: unknown,\n options: Omit<UseInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>,\n fetch?: FetchFn\n) {\n return useInfiniteQuery({\n queryKey: getQueryKey(model, url, args, true),\n queryFn: ({ pageParam }) => {\n return fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);\n },\n ...options,\n });\n}\n\n/**\n * Creates a react-query mutation\n *\n * @param model The name of the model under mutation.\n * @param method The HTTP method.\n * @param url The request URL.\n * @param modelMeta The model metadata.\n * @param options The react-query options.\n * @param fetch The fetch function to use for sending the HTTP request\n * @param invalidateQueries Whether to invalidate queries after mutation.\n * @param checkReadBack Whether to check for read back errors and return undefined if found.\n * @param optimisticUpdate Whether to enable automatic optimistic update\n */\nexport function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(\n model: string,\n method: 'POST' | 'PUT' | 'DELETE',\n url: string,\n modelMeta: ModelMeta,\n options?: Omit<UseMutationOptions<Result, unknown, T>, 'mutationFn'>,\n fetch?: FetchFn,\n invalidateQueries = true,\n checkReadBack?: C,\n optimisticUpdate = false\n) {\n const queryClient = useQueryClient();\n const mutationFn = (data: any) => {\n const reqUrl = method === 'DELETE' ? makeUrl(url, data) : url;\n const fetchInit: RequestInit = {\n method,\n ...(method !== 'DELETE' && {\n headers: {\n 'content-type': 'application/json',\n },\n body: marshal(data),\n }),\n };\n return fetcher<R, C>(reqUrl, fetchInit, fetch, checkReadBack) as Promise<Result>;\n };\n\n const finalOptions = { ...options, mutationFn };\n const operation = url.split('/').pop();\n\n if (operation) {\n const { logging } = useContext(RequestHandlerContext);\n if (invalidateQueries) {\n setupInvalidation(\n model,\n operation,\n modelMeta,\n finalOptions,\n (predicate) => queryClient.invalidateQueries({ predicate }),\n logging\n );\n }\n\n if (optimisticUpdate) {\n setupOptimisticUpdate(\n model,\n operation,\n modelMeta,\n finalOptions,\n queryClient.getQueryCache().getAll(),\n (queryKey, data) => queryClient.setQueryData<unknown>(queryKey, data),\n invalidateQueries ? (predicate) => queryClient.invalidateQueries({ predicate }) : undefined,\n logging\n );\n }\n }\n\n return useMutation(finalOptions);\n}\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';\nimport * as crossFetch from 'cross-fetch';\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 * 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 fetch?: FetchFn,\n checkReadBack?: C\n): Promise<C extends true ? R | undefined : R> {\n const _fetch = fetch ?? crossFetch.fetch;\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: Error & { info?: unknown; status?: number } = new Error(\n 'An error occurred while fetching the data.'\n );\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 infinite Whether the query is infinite.\n * @param optimisticUpdate Whether the query is optimistically updated.\n * @returns Query key\n */\nexport function getQueryKey(\n model: string,\n urlOrOperation: string,\n args: unknown,\n infinite = false,\n optimisticUpdate = false\n): QueryKey {\n if (!urlOrOperation) {\n throw new Error('Invalid urlOrOperation');\n }\n const operation = urlOrOperation.split('/').pop();\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 (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\nexport function setupOptimisticUpdate(\n model: string,\n operation: string,\n modelMeta: ModelMeta,\n options: MutationOptions,\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 options.onMutate = async (...args: unknown[]) => {\n const [variables] = args;\n await optimisticUpdate(\n model,\n operation as PrismaWriteActionType,\n variables,\n modelMeta,\n queryCache,\n setCache,\n logging\n );\n return origOnMutate?.(...args);\n };\n\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 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 (error) {\n continue;\n }\n\n const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate }] = queryKey as QueryKey;\n if (!optimisticUpdate) {\n continue;\n }\n\n const mutatedData = await applyMutation(\n queryModel,\n queryOp,\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"],"mappings":"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","names":["fetch","optimisticUpdate","fetch","optimisticUpdate"]}
@@ -61,7 +61,11 @@ import { createContext, useContext } from "react";
61
61
 
62
62
  // src/runtime/common.ts
63
63
  import { deserialize, serialize } from "@zenstackhq/runtime/browser";
64
- import { getMutatedModels, getReadModels } from "@zenstackhq/runtime/cross";
64
+ import {
65
+ applyMutation,
66
+ getMutatedModels,
67
+ getReadModels
68
+ } from "@zenstackhq/runtime/cross";
65
69
  import * as crossFetch from "cross-fetch";
66
70
  var DEFAULT_QUERY_ENDPOINT = "/api/model";
67
71
  var QUERY_KEY_PREFIX = "zenstack";
@@ -91,12 +95,12 @@ function fetcher(url, options, fetch2, checkReadBack) {
91
95
  }
92
96
  });
93
97
  }
94
- function getQueryKey(model, urlOrOperation, args) {
98
+ function getQueryKey(model, urlOrOperation, args, infinite = false, optimisticUpdate2 = false) {
95
99
  if (!urlOrOperation) {
96
100
  throw new Error("Invalid urlOrOperation");
97
101
  }
98
102
  const operation = urlOrOperation.split("/").pop();
99
- return [QUERY_KEY_PREFIX, model, operation, args];
103
+ return [QUERY_KEY_PREFIX, model, operation, args, { infinite, optimisticUpdate: optimisticUpdate2 }];
100
104
  }
101
105
  function marshal(value) {
102
106
  const { data, meta } = serialize(value);
@@ -146,17 +150,19 @@ function getInvalidationPredicate(model, operation, mutationArgs, modelMeta, log
146
150
  return __async(this, null, function* () {
147
151
  const mutatedModels = yield getMutatedModels(model, operation, mutationArgs, modelMeta);
148
152
  return ({ queryKey }) => {
149
- const [_model, queryModel, queryOp, args] = queryKey;
153
+ const [_, queryModel, , args] = queryKey;
150
154
  if (mutatedModels.includes(queryModel)) {
151
155
  if (logging) {
152
- console.log(`Invalidating query [${queryKey}] due to mutation "${model}.${operation}"`);
156
+ console.log(`Invalidating query ${JSON.stringify(queryKey)} due to mutation "${model}.${operation}"`);
153
157
  }
154
158
  return true;
155
159
  }
156
160
  if (args) {
157
161
  if (findNestedRead(queryModel, mutatedModels, modelMeta, args)) {
158
162
  if (logging) {
159
- console.log(`Invalidating query [${queryKey}] due to mutation "${model}.${operation}"`);
163
+ console.log(
164
+ `Invalidating query ${JSON.stringify(queryKey)} due to mutation "${model}.${operation}"`
165
+ );
160
166
  }
161
167
  return true;
162
168
  }
@@ -169,6 +175,74 @@ function findNestedRead(visitingModel, targetModels, modelMeta, args) {
169
175
  const modelsRead = getReadModels(visitingModel, modelMeta, args);
170
176
  return targetModels.some((m) => modelsRead.includes(m));
171
177
  }
178
+ function setupOptimisticUpdate(model, operation, modelMeta, options, queryCache, setCache, invalidate, logging = false) {
179
+ const origOnMutate = options == null ? void 0 : options.onMutate;
180
+ const origOnSettled = options == null ? void 0 : options.onSettled;
181
+ options.onMutate = (...args) => __async(this, null, function* () {
182
+ const [variables] = args;
183
+ yield optimisticUpdate(
184
+ model,
185
+ operation,
186
+ variables,
187
+ modelMeta,
188
+ queryCache,
189
+ setCache,
190
+ logging
191
+ );
192
+ return origOnMutate == null ? void 0 : origOnMutate(...args);
193
+ });
194
+ options.onSettled = (...args) => __async(this, null, function* () {
195
+ if (invalidate) {
196
+ const [, , variables] = args;
197
+ const predicate = yield getInvalidationPredicate(
198
+ model,
199
+ operation,
200
+ variables,
201
+ modelMeta,
202
+ logging
203
+ );
204
+ yield invalidate(predicate);
205
+ }
206
+ return origOnSettled == null ? void 0 : origOnSettled(...args);
207
+ });
208
+ }
209
+ function optimisticUpdate(mutationModel, mutationOp, mutationArgs, modelMeta, queryCache, setCache, logging = false) {
210
+ return __async(this, null, function* () {
211
+ for (const cacheItem of queryCache) {
212
+ const {
213
+ queryKey,
214
+ state: { data, error }
215
+ } = cacheItem;
216
+ if (error) {
217
+ continue;
218
+ }
219
+ const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate: optimisticUpdate2 }] = queryKey;
220
+ if (!optimisticUpdate2) {
221
+ continue;
222
+ }
223
+ const mutatedData = yield applyMutation(
224
+ queryModel,
225
+ queryOp,
226
+ data,
227
+ mutationModel,
228
+ mutationOp,
229
+ mutationArgs,
230
+ modelMeta,
231
+ logging
232
+ );
233
+ if (mutatedData !== void 0) {
234
+ if (logging) {
235
+ console.log(
236
+ `Optimistically updating query ${JSON.stringify(
237
+ queryKey
238
+ )} due to mutation "${mutationModel}.${mutationOp}"`
239
+ );
240
+ }
241
+ setCache(queryKey, mutatedData);
242
+ }
243
+ }
244
+ });
245
+ }
172
246
 
173
247
  // src/runtime-v5/react.ts
174
248
  var RequestHandlerContext = createContext({
@@ -180,22 +254,22 @@ function getHooksContext() {
180
254
  return __spreadValues({ endpoint: endpoint != null ? endpoint : DEFAULT_QUERY_ENDPOINT }, rest);
181
255
  }
182
256
  var Provider = RequestHandlerContext.Provider;
183
- function useModelQuery(model, url, args, options, fetch2) {
257
+ function useModelQuery(model, url, args, options, fetch2, optimisticUpdate2 = false) {
184
258
  const reqUrl = makeUrl(url, args);
185
259
  return useQuery(__spreadValues({
186
- queryKey: getQueryKey(model, url, args),
260
+ queryKey: getQueryKey(model, url, args, false, optimisticUpdate2),
187
261
  queryFn: () => fetcher(reqUrl, void 0, fetch2, false)
188
262
  }, options));
189
263
  }
190
264
  function useInfiniteModelQuery(model, url, args, options, fetch2) {
191
265
  return useInfiniteQuery(__spreadValues({
192
- queryKey: getQueryKey(model, url, args),
266
+ queryKey: getQueryKey(model, url, args, true),
193
267
  queryFn: ({ pageParam }) => {
194
268
  return fetcher(makeUrl(url, pageParam != null ? pageParam : args), void 0, fetch2, false);
195
269
  }
196
270
  }, options));
197
271
  }
198
- function useModelMutation(model, method, url, modelMeta, options, fetch2, invalidateQueries = true, checkReadBack) {
272
+ function useModelMutation(model, method, url, modelMeta, options, fetch2, invalidateQueries = true, checkReadBack, optimisticUpdate2 = false) {
199
273
  const queryClient = useQueryClient();
200
274
  const mutationFn = (data) => {
201
275
  const reqUrl = method === "DELETE" ? makeUrl(url, data) : url;
@@ -210,10 +284,10 @@ function useModelMutation(model, method, url, modelMeta, options, fetch2, invali
210
284
  return fetcher(reqUrl, fetchInit, fetch2, checkReadBack);
211
285
  };
212
286
  const finalOptions = __spreadProps(__spreadValues({}, options), { mutationFn });
213
- if (invalidateQueries) {
287
+ const operation = url.split("/").pop();
288
+ if (operation) {
214
289
  const { logging } = useContext(RequestHandlerContext);
215
- const operation = url.split("/").pop();
216
- if (operation) {
290
+ if (invalidateQueries) {
217
291
  setupInvalidation(
218
292
  model,
219
293
  operation,
@@ -223,6 +297,18 @@ function useModelMutation(model, method, url, modelMeta, options, fetch2, invali
223
297
  logging
224
298
  );
225
299
  }
300
+ if (optimisticUpdate2) {
301
+ setupOptimisticUpdate(
302
+ model,
303
+ operation,
304
+ modelMeta,
305
+ finalOptions,
306
+ queryClient.getQueryCache().getAll(),
307
+ (queryKey, data) => queryClient.setQueryData(queryKey, data),
308
+ invalidateQueries ? (predicate) => queryClient.invalidateQueries({ predicate }) : void 0,
309
+ logging
310
+ );
311
+ }
226
312
  }
227
313
  return useMutation(finalOptions);
228
314
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/runtime-v5/react.ts","../../src/runtime/common.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n useInfiniteQuery,\n useMutation,\n useQuery,\n useQueryClient,\n type InfiniteData,\n type UseInfiniteQueryOptions,\n type UseMutationOptions,\n type UseQueryOptions,\n} from '@tanstack/react-query-v5';\nimport type { ModelMeta } from '@zenstackhq/runtime/cross';\nimport { createContext, useContext } from 'react';\nimport {\n DEFAULT_QUERY_ENDPOINT,\n FetchFn,\n fetcher,\n getQueryKey,\n makeUrl,\n marshal,\n setupInvalidation,\n type APIContext,\n} from '../runtime/common';\n\n/**\n * Context for configuring react hooks.\n */\nexport const RequestHandlerContext = createContext<APIContext>({\n endpoint: DEFAULT_QUERY_ENDPOINT,\n fetch: undefined,\n});\n\n/**\n * Hooks context.\n */\nexport function getHooksContext() {\n const { endpoint, ...rest } = useContext(RequestHandlerContext);\n return { endpoint: endpoint ?? DEFAULT_QUERY_ENDPOINT, ...rest };\n}\n\n/**\n * Context provider.\n */\nexport const Provider = RequestHandlerContext.Provider;\n\n/**\n * Creates a react-query query.\n *\n * @param model The name of the model under query.\n * @param url The request URL.\n * @param args The request args object, URL-encoded and appended as \"?q=\" parameter\n * @param options The react-query options object\n * @returns useQuery hook\n */\nexport function useModelQuery<R>(\n model: string,\n url: string,\n args?: unknown,\n options?: Omit<UseQueryOptions<R>, 'queryKey'>,\n fetch?: FetchFn\n) {\n const reqUrl = makeUrl(url, args);\n return useQuery({\n queryKey: getQueryKey(model, url, args),\n queryFn: () => fetcher<R, false>(reqUrl, undefined, fetch, false),\n ...options,\n });\n}\n\n/**\n * Creates a react-query infinite query.\n *\n * @param model The name of the model under query.\n * @param url The request URL.\n * @param args The initial request args object, URL-encoded and appended as \"?q=\" parameter\n * @param options The react-query infinite query options object\n * @returns useInfiniteQuery hook\n */\nexport function useInfiniteModelQuery<R>(\n model: string,\n url: string,\n args: unknown,\n options: Omit<UseInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>,\n fetch?: FetchFn\n) {\n return useInfiniteQuery({\n queryKey: getQueryKey(model, url, args),\n queryFn: ({ pageParam }) => {\n return fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);\n },\n ...options,\n });\n}\n\nexport function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(\n model: string,\n method: 'POST' | 'PUT' | 'DELETE',\n url: string,\n modelMeta: ModelMeta,\n options?: Omit<UseMutationOptions<Result, unknown, T>, 'mutationFn'>,\n fetch?: FetchFn,\n invalidateQueries = true,\n checkReadBack?: C\n) {\n const queryClient = useQueryClient();\n const mutationFn = (data: any) => {\n const reqUrl = method === 'DELETE' ? makeUrl(url, data) : url;\n const fetchInit: RequestInit = {\n method,\n ...(method !== 'DELETE' && {\n headers: {\n 'content-type': 'application/json',\n },\n body: marshal(data),\n }),\n };\n return fetcher<R, C>(reqUrl, fetchInit, fetch, checkReadBack) as Promise<Result>;\n };\n\n const finalOptions = { ...options, mutationFn };\n if (invalidateQueries) {\n const { logging } = useContext(RequestHandlerContext);\n const operation = url.split('/').pop();\n if (operation) {\n setupInvalidation(\n model,\n operation,\n modelMeta,\n finalOptions,\n (predicate) => queryClient.invalidateQueries({ predicate }),\n logging\n );\n }\n }\n\n return useMutation(finalOptions);\n}\n","/* eslint-disable @typescript-eslint/no-unused-vars */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { deserialize, serialize } from '@zenstackhq/runtime/browser';\nimport { getMutatedModels, getReadModels, type ModelMeta, type PrismaWriteActionType } from '@zenstackhq/runtime/cross';\nimport * as crossFetch from 'cross-fetch';\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 * 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 fetch?: FetchFn,\n checkReadBack?: C\n): Promise<C extends true ? R | undefined : R> {\n const _fetch = fetch ?? crossFetch.fetch;\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: Error & { info?: unknown; status?: number } = new Error(\n 'An error occurred while fetching the data.'\n );\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 = [string /* prefix */, string /* model */, string /* operation */, unknown /* args */];\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 * @returns Query key\n */\nexport function getQueryKey(model: string, urlOrOperation: string, args: unknown): QueryKey {\n if (!urlOrOperation) {\n throw new Error('Invalid urlOrOperation');\n }\n const operation = urlOrOperation.split('/').pop();\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return [QUERY_KEY_PREFIX, model, operation!, args];\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 (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;\n\n// sets up invalidation hook for a mutation\nexport function setupInvalidation(\n model: string,\n operation: string,\n modelMeta: ModelMeta,\n options: { onSuccess?: (...args: any[]) => any },\n invalidate: (predicate: InvalidationPredicate) => Promise<void>,\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 [_model, queryModel, queryOp, args] = queryKey as QueryKey;\n\n if (mutatedModels.includes(queryModel)) {\n // direct match\n if (logging) {\n console.log(`Invalidating query [${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(`Invalidating query [${queryKey}] due to mutation \"${model}.${operation}\"`);\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"],"mappings":"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","names":["fetch","fetch"]}
1
+ {"version":3,"sources":["../../src/runtime-v5/react.ts","../../src/runtime/common.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n useInfiniteQuery,\n useMutation,\n useQuery,\n useQueryClient,\n type InfiniteData,\n type UseInfiniteQueryOptions,\n type UseMutationOptions,\n type UseQueryOptions,\n} from '@tanstack/react-query-v5';\nimport type { ModelMeta } from '@zenstackhq/runtime/cross';\nimport { createContext, useContext } from 'react';\nimport {\n DEFAULT_QUERY_ENDPOINT,\n FetchFn,\n fetcher,\n getQueryKey,\n makeUrl,\n marshal,\n setupInvalidation,\n setupOptimisticUpdate,\n type APIContext,\n} from '../runtime/common';\n\n/**\n * Context for configuring react hooks.\n */\nexport const RequestHandlerContext = createContext<APIContext>({\n endpoint: DEFAULT_QUERY_ENDPOINT,\n fetch: undefined,\n});\n\n/**\n * Hooks context.\n */\nexport function getHooksContext() {\n const { endpoint, ...rest } = useContext(RequestHandlerContext);\n return { endpoint: endpoint ?? DEFAULT_QUERY_ENDPOINT, ...rest };\n}\n\n/**\n * Context provider.\n */\nexport const Provider = RequestHandlerContext.Provider;\n\n/**\n * Creates a react-query query.\n *\n * @param model The name of the model under query.\n * @param url The request URL.\n * @param args The request args object, URL-encoded and appended as \"?q=\" parameter\n * @param options The react-query options object\n * @param fetch The fetch function to use for sending the HTTP request\n * @param optimisticUpdate Whether to enable automatic optimistic update\n * @returns useQuery hook\n */\nexport function useModelQuery<R>(\n model: string,\n url: string,\n args?: unknown,\n options?: Omit<UseQueryOptions<R>, 'queryKey'>,\n fetch?: FetchFn,\n optimisticUpdate = false\n) {\n const reqUrl = makeUrl(url, args);\n return useQuery({\n queryKey: getQueryKey(model, url, args, false, optimisticUpdate),\n queryFn: () => fetcher<R, false>(reqUrl, undefined, fetch, false),\n ...options,\n });\n}\n\n/**\n * Creates a react-query infinite query.\n *\n * @param model The name of the model under query.\n * @param url The request URL.\n * @param args The initial request args object, URL-encoded and appended as \"?q=\" parameter\n * @param options The react-query infinite query options object\n * @param fetch The fetch function to use for sending the HTTP request\n * @returns useInfiniteQuery hook\n */\nexport function useInfiniteModelQuery<R>(\n model: string,\n url: string,\n args: unknown,\n options: Omit<UseInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>,\n fetch?: FetchFn\n) {\n return useInfiniteQuery({\n queryKey: getQueryKey(model, url, args, true),\n queryFn: ({ pageParam }) => {\n return fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);\n },\n ...options,\n });\n}\n\n/**\n * Creates a react-query mutation\n *\n * @param model The name of the model under mutation.\n * @param method The HTTP method.\n * @param url The request URL.\n * @param modelMeta The model metadata.\n * @param options The react-query options.\n * @param fetch The fetch function to use for sending the HTTP request\n * @param invalidateQueries Whether to invalidate queries after mutation.\n * @param checkReadBack Whether to check for read back errors and return undefined if found.\n * @param optimisticUpdate Whether to enable automatic optimistic update\n */\nexport function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(\n model: string,\n method: 'POST' | 'PUT' | 'DELETE',\n url: string,\n modelMeta: ModelMeta,\n options?: Omit<UseMutationOptions<Result, unknown, T>, 'mutationFn'>,\n fetch?: FetchFn,\n invalidateQueries = true,\n checkReadBack?: C,\n optimisticUpdate = false\n) {\n const queryClient = useQueryClient();\n const mutationFn = (data: any) => {\n const reqUrl = method === 'DELETE' ? makeUrl(url, data) : url;\n const fetchInit: RequestInit = {\n method,\n ...(method !== 'DELETE' && {\n headers: {\n 'content-type': 'application/json',\n },\n body: marshal(data),\n }),\n };\n return fetcher<R, C>(reqUrl, fetchInit, fetch, checkReadBack) as Promise<Result>;\n };\n\n const finalOptions = { ...options, mutationFn };\n const operation = url.split('/').pop();\n\n if (operation) {\n const { logging } = useContext(RequestHandlerContext);\n if (invalidateQueries) {\n setupInvalidation(\n model,\n operation,\n modelMeta,\n finalOptions,\n (predicate) => queryClient.invalidateQueries({ predicate }),\n logging\n );\n }\n\n if (optimisticUpdate) {\n setupOptimisticUpdate(\n model,\n operation,\n modelMeta,\n finalOptions,\n queryClient.getQueryCache().getAll(),\n (queryKey, data) => queryClient.setQueryData<unknown>(queryKey, data),\n invalidateQueries ? (predicate) => queryClient.invalidateQueries({ predicate }) : undefined,\n logging\n );\n }\n }\n\n return useMutation(finalOptions);\n}\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';\nimport * as crossFetch from 'cross-fetch';\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 * 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 fetch?: FetchFn,\n checkReadBack?: C\n): Promise<C extends true ? R | undefined : R> {\n const _fetch = fetch ?? crossFetch.fetch;\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: Error & { info?: unknown; status?: number } = new Error(\n 'An error occurred while fetching the data.'\n );\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 infinite Whether the query is infinite.\n * @param optimisticUpdate Whether the query is optimistically updated.\n * @returns Query key\n */\nexport function getQueryKey(\n model: string,\n urlOrOperation: string,\n args: unknown,\n infinite = false,\n optimisticUpdate = false\n): QueryKey {\n if (!urlOrOperation) {\n throw new Error('Invalid urlOrOperation');\n }\n const operation = urlOrOperation.split('/').pop();\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 (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\nexport function setupOptimisticUpdate(\n model: string,\n operation: string,\n modelMeta: ModelMeta,\n options: MutationOptions,\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 options.onMutate = async (...args: unknown[]) => {\n const [variables] = args;\n await optimisticUpdate(\n model,\n operation as PrismaWriteActionType,\n variables,\n modelMeta,\n queryCache,\n setCache,\n logging\n );\n return origOnMutate?.(...args);\n };\n\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 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 (error) {\n continue;\n }\n\n const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate }] = queryKey as QueryKey;\n if (!optimisticUpdate) {\n continue;\n }\n\n const mutatedData = await applyMutation(\n queryModel,\n queryOp,\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"],"mappings":"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","names":["fetch","optimisticUpdate","fetch","optimisticUpdate"]}
@@ -2,7 +2,7 @@ import * as _tanstack_svelte_query_v5 from '@tanstack/svelte-query';
2
2
  import { StoreOrVal, CreateInfiniteQueryOptions, InfiniteData, MutationOptions } from '@tanstack/svelte-query';
3
3
  import { QueryOptions } from '@tanstack/vue-query';
4
4
  import { ModelMeta } from '@zenstackhq/runtime/cross';
5
- import { A as APIContext, F as FetchFn } from './common-5e18c135.js';
5
+ import { A as APIContext, F as FetchFn } from './common-82ef6965.js';
6
6
 
7
7
  /**
8
8
  * Key for setting and getting the global query context.
@@ -27,9 +27,11 @@ declare function getHooksContext(): {
27
27
  * @param url The request URL.
28
28
  * @param args The request args object, URL-encoded and appended as "?q=" parameter
29
29
  * @param options The svelte-query options object
30
+ * @param fetch The fetch function to use for sending the HTTP request
31
+ * @param optimisticUpdate Whether to enable automatic optimistic update
30
32
  * @returns useQuery hook
31
33
  */
32
- declare function useModelQuery<R>(model: string, url: string, args?: unknown, options?: StoreOrVal<Omit<QueryOptions<R>, 'queryKey'>>, fetch?: FetchFn): _tanstack_svelte_query_v5.CreateQueryResult<unknown, Error>;
34
+ declare function useModelQuery<R>(model: string, url: string, args?: unknown, options?: StoreOrVal<Omit<QueryOptions<R>, 'queryKey'>>, fetch?: FetchFn, optimisticUpdate?: boolean): _tanstack_svelte_query_v5.CreateQueryResult<unknown, Error>;
33
35
  /**
34
36
  * Creates a svelte-query infinite query.
35
37
  *
@@ -51,6 +53,6 @@ declare function useInfiniteModelQuery<R>(model: string, url: string, args: unkn
51
53
  * @param invalidateQueries Whether to invalidate queries after mutation.
52
54
  * @returns useMutation hooks
53
55
  */
54
- declare function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(model: string, method: 'POST' | 'PUT' | 'DELETE', url: string, modelMeta: ModelMeta, options?: Omit<MutationOptions<Result, unknown, T>, 'mutationFn'>, fetch?: FetchFn, invalidateQueries?: boolean, checkReadBack?: C): _tanstack_svelte_query_v5.CreateMutationResult<Result, unknown, T, unknown>;
56
+ declare function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(model: string, method: 'POST' | 'PUT' | 'DELETE', url: string, modelMeta: ModelMeta, options?: Omit<MutationOptions<Result, unknown, T>, 'mutationFn'>, fetch?: FetchFn, invalidateQueries?: boolean, checkReadBack?: C, optimisticUpdate?: boolean): _tanstack_svelte_query_v5.CreateMutationResult<Result, unknown, T, unknown>;
55
57
 
56
58
  export { APIContext as RequestHandlerContext, SvelteQueryContextKey, getHooksContext, setHooksContext, useInfiniteModelQuery, useModelMutation, useModelQuery };
@@ -2,7 +2,7 @@ import * as _tanstack_svelte_query_v5 from '@tanstack/svelte-query';
2
2
  import { StoreOrVal, CreateInfiniteQueryOptions, InfiniteData, MutationOptions } from '@tanstack/svelte-query';
3
3
  import { QueryOptions } from '@tanstack/vue-query';
4
4
  import { ModelMeta } from '@zenstackhq/runtime/cross';
5
- import { A as APIContext, F as FetchFn } from './common-5e18c135.js';
5
+ import { A as APIContext, F as FetchFn } from './common-82ef6965.js';
6
6
 
7
7
  /**
8
8
  * Key for setting and getting the global query context.
@@ -27,9 +27,11 @@ declare function getHooksContext(): {
27
27
  * @param url The request URL.
28
28
  * @param args The request args object, URL-encoded and appended as "?q=" parameter
29
29
  * @param options The svelte-query options object
30
+ * @param fetch The fetch function to use for sending the HTTP request
31
+ * @param optimisticUpdate Whether to enable automatic optimistic update
30
32
  * @returns useQuery hook
31
33
  */
32
- declare function useModelQuery<R>(model: string, url: string, args?: unknown, options?: StoreOrVal<Omit<QueryOptions<R>, 'queryKey'>>, fetch?: FetchFn): _tanstack_svelte_query_v5.CreateQueryResult<unknown, Error>;
34
+ declare function useModelQuery<R>(model: string, url: string, args?: unknown, options?: StoreOrVal<Omit<QueryOptions<R>, 'queryKey'>>, fetch?: FetchFn, optimisticUpdate?: boolean): _tanstack_svelte_query_v5.CreateQueryResult<unknown, Error>;
33
35
  /**
34
36
  * Creates a svelte-query infinite query.
35
37
  *
@@ -51,6 +53,6 @@ declare function useInfiniteModelQuery<R>(model: string, url: string, args: unkn
51
53
  * @param invalidateQueries Whether to invalidate queries after mutation.
52
54
  * @returns useMutation hooks
53
55
  */
54
- declare function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(model: string, method: 'POST' | 'PUT' | 'DELETE', url: string, modelMeta: ModelMeta, options?: Omit<MutationOptions<Result, unknown, T>, 'mutationFn'>, fetch?: FetchFn, invalidateQueries?: boolean, checkReadBack?: C): _tanstack_svelte_query_v5.CreateMutationResult<Result, unknown, T, unknown>;
56
+ declare function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(model: string, method: 'POST' | 'PUT' | 'DELETE', url: string, modelMeta: ModelMeta, options?: Omit<MutationOptions<Result, unknown, T>, 'mutationFn'>, fetch?: FetchFn, invalidateQueries?: boolean, checkReadBack?: C, optimisticUpdate?: boolean): _tanstack_svelte_query_v5.CreateMutationResult<Result, unknown, T, unknown>;
55
57
 
56
58
  export { APIContext as RequestHandlerContext, SvelteQueryContextKey, getHooksContext, setHooksContext, useInfiniteModelQuery, useModelMutation, useModelQuery };