@zenstackhq/tanstack-query 1.3.2 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/generator.js.map +1 -1
  2. package/package.json +5 -4
  3. package/runtime/common-mqucVdqq.d.mts +43 -0
  4. package/runtime/common-mqucVdqq.d.ts +43 -0
  5. package/runtime/index.d.mts +2 -2
  6. package/runtime/index.d.ts +2 -2
  7. package/runtime/index.js.map +1 -1
  8. package/runtime/index.mjs.map +1 -1
  9. package/runtime/react.d.mts +1 -1
  10. package/runtime/react.d.ts +1 -1
  11. package/runtime/react.js +6 -0
  12. package/runtime/react.js.map +1 -1
  13. package/runtime/react.mjs +6 -0
  14. package/runtime/react.mjs.map +1 -1
  15. package/runtime/svelte.d.mts +1 -1
  16. package/runtime/svelte.d.ts +1 -1
  17. package/runtime/svelte.js +6 -0
  18. package/runtime/svelte.js.map +1 -1
  19. package/runtime/svelte.mjs +6 -0
  20. package/runtime/svelte.mjs.map +1 -1
  21. package/runtime/vue.d.mts +1 -1
  22. package/runtime/vue.d.ts +1 -1
  23. package/runtime/vue.js +6 -0
  24. package/runtime/vue.js.map +1 -1
  25. package/runtime/vue.mjs +6 -0
  26. package/runtime/vue.mjs.map +1 -1
  27. package/runtime-v5/{common-82ef6965.d.ts → common-mqucVdqq.d.mts} +1 -1
  28. package/{runtime/common-82ef6965.d.ts → runtime-v5/common-mqucVdqq.d.ts} +1 -1
  29. package/runtime-v5/index.d.mts +2 -2
  30. package/runtime-v5/index.d.ts +2 -2
  31. package/runtime-v5/index.js.map +1 -1
  32. package/runtime-v5/index.mjs.map +1 -1
  33. package/runtime-v5/react.d.mts +2 -2
  34. package/runtime-v5/react.d.ts +2 -2
  35. package/runtime-v5/react.js +10 -1
  36. package/runtime-v5/react.js.map +1 -1
  37. package/runtime-v5/react.mjs +10 -1
  38. package/runtime-v5/react.mjs.map +1 -1
  39. package/runtime-v5/svelte.d.mts +1 -1
  40. package/runtime-v5/svelte.d.ts +1 -1
  41. package/runtime-v5/svelte.js +6 -0
  42. package/runtime-v5/svelte.js.map +1 -1
  43. package/runtime-v5/svelte.mjs +6 -0
  44. package/runtime-v5/svelte.mjs.map +1 -1
  45. package/runtime-v5/vue.d.mts +1 -1
  46. package/runtime-v5/vue.d.ts +1 -1
  47. package/runtime-v5/vue.js +6 -0
  48. package/runtime-v5/vue.js.map +1 -1
  49. package/runtime-v5/vue.mjs +6 -0
  50. package/runtime-v5/vue.mjs.map +1 -1
@@ -1 +1 @@
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';\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":";AAEA,SAAS,aAAa,iBAAiB;AACvC;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OAGG;AACP,YAAY,gBAAgB;AAUrB,IAAM,mBAAmB;AAmFzB,SAAS,YACZ,OACA,gBACA,MACA,WAAW,OACX,mBAAmB,OACX;AACR,MAAI,CAAC,gBAAgB;AACjB,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC5C;AACA,QAAM,YAAY,eAAe,MAAM,GAAG,EAAE,IAAI;AAEhD,SAAO,CAAC,kBAAkB,OAAO,WAAY,MAAM,EAAE,UAAU,iBAAiB,CAAC;AACrF;","names":[]}
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';\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 // 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 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 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 if (logging) {\n console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);\n }\n continue;\n }\n\n const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate }] = queryKey as QueryKey;\n if (!optimisticUpdate) {\n if (logging) {\n console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);\n }\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":";AAEA,SAAS,aAAa,iBAAiB;AACvC;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OAGG;AACP,YAAY,gBAAgB;AAUrB,IAAM,mBAAmB;AAmFzB,SAAS,YACZ,OACA,gBACA,MACA,WAAW,OACX,mBAAmB,OACX;AACR,MAAI,CAAC,gBAAgB;AACjB,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC5C;AACA,QAAM,YAAY,eAAe,MAAM,GAAG,EAAE,IAAI;AAEhD,SAAO,CAAC,kBAAkB,OAAO,WAAY,MAAM,EAAE,UAAU,iBAAiB,CAAC;AACrF;","names":[]}
@@ -1,8 +1,8 @@
1
- import * as _tanstack_react_query_v5_build_legacy_types from '@tanstack/react-query/build/legacy/types';
1
+ import * as _tanstack_react_query_v5_build_legacy_types from '@tanstack/react-query';
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-82ef6965.js';
5
+ import { A as APIContext, F as FetchFn } from './common-mqucVdqq.mjs';
6
6
 
7
7
  /**
8
8
  * Context for configuring react hooks.
@@ -1,8 +1,8 @@
1
- import * as _tanstack_react_query_v5_build_legacy_types from '@tanstack/react-query/build/legacy/types';
1
+ import * as _tanstack_react_query_v5_build_legacy_types from '@tanstack/react-query';
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-82ef6965.js';
5
+ import { A as APIContext, F as FetchFn } from './common-mqucVdqq.js';
6
6
 
7
7
  /**
8
8
  * Context for configuring react hooks.
@@ -241,10 +241,16 @@ function optimisticUpdate(mutationModel, mutationOp, mutationArgs, modelMeta, qu
241
241
  state: { data, error }
242
242
  } = cacheItem;
243
243
  if (error) {
244
+ if (logging) {
245
+ console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);
246
+ }
244
247
  continue;
245
248
  }
246
249
  const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate: optimisticUpdate2 }] = queryKey;
247
250
  if (!optimisticUpdate2) {
251
+ if (logging) {
252
+ console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);
253
+ }
248
254
  continue;
249
255
  }
250
256
  const mutatedData = yield (0, import_cross.applyMutation)(
@@ -331,7 +337,10 @@ function useModelMutation(model, method, url, modelMeta, options, fetch2, invali
331
337
  modelMeta,
332
338
  finalOptions,
333
339
  queryClient.getQueryCache().getAll(),
334
- (queryKey, data) => queryClient.setQueryData(queryKey, data),
340
+ (queryKey, data) => {
341
+ queryClient.setQueryData(queryKey, data);
342
+ queryClient.cancelQueries({ queryKey }, { revert: false, silent: true });
343
+ },
335
344
  invalidateQueries ? (predicate) => queryClient.invalidateQueries({ predicate }) : void 0,
336
345
  logging
337
346
  );
@@ -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 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"]}
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) => {\n // update query cache\n queryClient.setQueryData<unknown>(queryKey, data);\n // cancel on-flight queries to avoid redundant cache updates,\n // the settlement of the current mutation will trigger a new revalidation\n queryClient.cancelQueries({ queryKey }, { revert: false, silent: true });\n },\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 // 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 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 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 if (logging) {\n console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);\n }\n continue;\n }\n\n const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate }] = queryKey as QueryKey;\n if (!optimisticUpdate) {\n if (logging) {\n console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);\n }\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"]}
@@ -214,10 +214,16 @@ function optimisticUpdate(mutationModel, mutationOp, mutationArgs, modelMeta, qu
214
214
  state: { data, error }
215
215
  } = cacheItem;
216
216
  if (error) {
217
+ if (logging) {
218
+ console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);
219
+ }
217
220
  continue;
218
221
  }
219
222
  const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate: optimisticUpdate2 }] = queryKey;
220
223
  if (!optimisticUpdate2) {
224
+ if (logging) {
225
+ console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);
226
+ }
221
227
  continue;
222
228
  }
223
229
  const mutatedData = yield applyMutation(
@@ -304,7 +310,10 @@ function useModelMutation(model, method, url, modelMeta, options, fetch2, invali
304
310
  modelMeta,
305
311
  finalOptions,
306
312
  queryClient.getQueryCache().getAll(),
307
- (queryKey, data) => queryClient.setQueryData(queryKey, data),
313
+ (queryKey, data) => {
314
+ queryClient.setQueryData(queryKey, data);
315
+ queryClient.cancelQueries({ queryKey }, { revert: false, silent: true });
316
+ },
308
317
  invalidateQueries ? (predicate) => queryClient.invalidateQueries({ predicate }) : void 0,
309
318
  logging
310
319
  );
@@ -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 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"]}
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) => {\n // update query cache\n queryClient.setQueryData<unknown>(queryKey, data);\n // cancel on-flight queries to avoid redundant cache updates,\n // the settlement of the current mutation will trigger a new revalidation\n queryClient.cancelQueries({ queryKey }, { revert: false, silent: true });\n },\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 // 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 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 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 if (logging) {\n console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);\n }\n continue;\n }\n\n const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate }] = queryKey as QueryKey;\n if (!optimisticUpdate) {\n if (logging) {\n console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);\n }\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-82ef6965.js';
5
+ import { A as APIContext, F as FetchFn } from './common-mqucVdqq.mjs';
6
6
 
7
7
  /**
8
8
  * Key for setting and getting the global query context.
@@ -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-82ef6965.js';
5
+ import { A as APIContext, F as FetchFn } from './common-mqucVdqq.js';
6
6
 
7
7
  /**
8
8
  * Key for setting and getting the global query context.
@@ -242,10 +242,16 @@ function optimisticUpdate(mutationModel, mutationOp, mutationArgs, modelMeta, qu
242
242
  state: { data, error }
243
243
  } = cacheItem;
244
244
  if (error) {
245
+ if (logging) {
246
+ console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);
247
+ }
245
248
  continue;
246
249
  }
247
250
  const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate: optimisticUpdate2 }] = queryKey;
248
251
  if (!optimisticUpdate2) {
252
+ if (logging) {
253
+ console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);
254
+ }
249
255
  continue;
250
256
  }
251
257
  const mutatedData = yield (0, import_cross.applyMutation)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/runtime-v5/svelte.ts","../../src/runtime/common.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n createInfiniteQuery,\n createMutation,\n createQuery,\n useQueryClient,\n type CreateInfiniteQueryOptions,\n type InfiniteData,\n type MutationOptions,\n type StoreOrVal,\n} from '@tanstack/svelte-query-v5';\nimport { QueryOptions } from '@tanstack/vue-query';\nimport { ModelMeta } from '@zenstackhq/runtime/cross';\nimport { getContext, setContext } from 'svelte';\nimport { Readable, derived } from 'svelte/store';\nimport {\n APIContext,\n DEFAULT_QUERY_ENDPOINT,\n FetchFn,\n fetcher,\n getQueryKey,\n makeUrl,\n marshal,\n setupInvalidation,\n setupOptimisticUpdate,\n} from '../runtime/common';\n\nexport { APIContext as RequestHandlerContext } from '../runtime/common';\n\n/**\n * Key for setting and getting the global query context.\n */\nexport const SvelteQueryContextKey = 'zenstack-svelte-query-context';\n\n/**\n * Set context for the generated TanStack Query hooks.\n */\nexport function setHooksContext(context: APIContext) {\n setContext(SvelteQueryContextKey, context);\n}\n\n/**\n * Hooks context.\n */\nexport function getHooksContext() {\n const { endpoint, ...rest } = getContext<APIContext>(SvelteQueryContextKey);\n return { endpoint: endpoint ?? DEFAULT_QUERY_ENDPOINT, ...rest };\n}\n\n/**\n * Creates a svelte-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 svelte-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?: StoreOrVal<Omit<QueryOptions<R>, 'queryKey'>>,\n fetch?: FetchFn,\n optimisticUpdate = false\n) {\n const reqUrl = makeUrl(url, args);\n const queryKey = getQueryKey(model, url, args, false, optimisticUpdate);\n const queryFn = () => fetcher<R, false>(reqUrl, undefined, fetch, false);\n\n let mergedOpt: any;\n if (isStore(options)) {\n // options is store\n mergedOpt = derived([options], ([$opt]) => {\n return {\n queryKey,\n queryFn,\n ...($opt as object),\n };\n });\n } else {\n // options is value\n mergedOpt = {\n queryKey,\n queryFn,\n ...options,\n };\n }\n return createQuery(mergedOpt);\n}\n\n/**\n * Creates a svelte-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 svelte-query infinite query options object\n * @returns useQuery hook\n */\nexport function useInfiniteModelQuery<R>(\n model: string,\n url: string,\n args: unknown,\n options: StoreOrVal<Omit<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>>,\n fetch?: FetchFn\n) {\n const queryKey = getQueryKey(model, url, args, true);\n const queryFn = ({ pageParam }: { pageParam: unknown }) =>\n fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);\n\n let mergedOpt: StoreOrVal<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>>;\n if (isStore<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>>(options)) {\n // options is store\n mergedOpt = derived([options], ([$opt]) => {\n return {\n queryKey,\n queryFn,\n ...$opt,\n };\n });\n } else {\n // options is value\n mergedOpt = {\n queryKey,\n queryFn,\n ...options,\n };\n }\n return createInfiniteQuery<R, unknown, InfiniteData<R>>(mergedOpt);\n}\n\nfunction isStore<T>(opt: unknown): opt is Readable<T> {\n return typeof (opt as any)?.subscribe === 'function';\n}\n\n/**\n * Creates a POST mutation with svelte-query.\n *\n * @param model The name of the model under mutation.\n * @param method The HTTP method.\n * @param modelMeta The model metadata.\n * @param url The request URL.\n * @param options The svelte-query options.\n * @param invalidateQueries Whether to invalidate queries after mutation.\n * @returns useMutation hooks\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<MutationOptions<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 } = getContext<APIContext>(SvelteQueryContextKey);\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 createMutation(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"]}
1
+ {"version":3,"sources":["../../src/runtime-v5/svelte.ts","../../src/runtime/common.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n createInfiniteQuery,\n createMutation,\n createQuery,\n useQueryClient,\n type CreateInfiniteQueryOptions,\n type InfiniteData,\n type MutationOptions,\n type StoreOrVal,\n} from '@tanstack/svelte-query-v5';\nimport { QueryOptions } from '@tanstack/vue-query';\nimport { ModelMeta } from '@zenstackhq/runtime/cross';\nimport { getContext, setContext } from 'svelte';\nimport { Readable, derived } from 'svelte/store';\nimport {\n APIContext,\n DEFAULT_QUERY_ENDPOINT,\n FetchFn,\n fetcher,\n getQueryKey,\n makeUrl,\n marshal,\n setupInvalidation,\n setupOptimisticUpdate,\n} from '../runtime/common';\n\nexport { APIContext as RequestHandlerContext } from '../runtime/common';\n\n/**\n * Key for setting and getting the global query context.\n */\nexport const SvelteQueryContextKey = 'zenstack-svelte-query-context';\n\n/**\n * Set context for the generated TanStack Query hooks.\n */\nexport function setHooksContext(context: APIContext) {\n setContext(SvelteQueryContextKey, context);\n}\n\n/**\n * Hooks context.\n */\nexport function getHooksContext() {\n const { endpoint, ...rest } = getContext<APIContext>(SvelteQueryContextKey);\n return { endpoint: endpoint ?? DEFAULT_QUERY_ENDPOINT, ...rest };\n}\n\n/**\n * Creates a svelte-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 svelte-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?: StoreOrVal<Omit<QueryOptions<R>, 'queryKey'>>,\n fetch?: FetchFn,\n optimisticUpdate = false\n) {\n const reqUrl = makeUrl(url, args);\n const queryKey = getQueryKey(model, url, args, false, optimisticUpdate);\n const queryFn = () => fetcher<R, false>(reqUrl, undefined, fetch, false);\n\n let mergedOpt: any;\n if (isStore(options)) {\n // options is store\n mergedOpt = derived([options], ([$opt]) => {\n return {\n queryKey,\n queryFn,\n ...($opt as object),\n };\n });\n } else {\n // options is value\n mergedOpt = {\n queryKey,\n queryFn,\n ...options,\n };\n }\n return createQuery(mergedOpt);\n}\n\n/**\n * Creates a svelte-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 svelte-query infinite query options object\n * @returns useQuery hook\n */\nexport function useInfiniteModelQuery<R>(\n model: string,\n url: string,\n args: unknown,\n options: StoreOrVal<Omit<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>>,\n fetch?: FetchFn\n) {\n const queryKey = getQueryKey(model, url, args, true);\n const queryFn = ({ pageParam }: { pageParam: unknown }) =>\n fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);\n\n let mergedOpt: StoreOrVal<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>>;\n if (isStore<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>>(options)) {\n // options is store\n mergedOpt = derived([options], ([$opt]) => {\n return {\n queryKey,\n queryFn,\n ...$opt,\n };\n });\n } else {\n // options is value\n mergedOpt = {\n queryKey,\n queryFn,\n ...options,\n };\n }\n return createInfiniteQuery<R, unknown, InfiniteData<R>>(mergedOpt);\n}\n\nfunction isStore<T>(opt: unknown): opt is Readable<T> {\n return typeof (opt as any)?.subscribe === 'function';\n}\n\n/**\n * Creates a POST mutation with svelte-query.\n *\n * @param model The name of the model under mutation.\n * @param method The HTTP method.\n * @param modelMeta The model metadata.\n * @param url The request URL.\n * @param options The svelte-query options.\n * @param invalidateQueries Whether to invalidate queries after mutation.\n * @returns useMutation hooks\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<MutationOptions<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 } = getContext<APIContext>(SvelteQueryContextKey);\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 createMutation(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 // 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 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 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 if (logging) {\n console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);\n }\n continue;\n }\n\n const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate }] = queryKey as QueryKey;\n if (!optimisticUpdate) {\n if (logging) {\n console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);\n }\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"]}
@@ -215,10 +215,16 @@ function optimisticUpdate(mutationModel, mutationOp, mutationArgs, modelMeta, qu
215
215
  state: { data, error }
216
216
  } = cacheItem;
217
217
  if (error) {
218
+ if (logging) {
219
+ console.warn(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to error:`, error);
220
+ }
218
221
  continue;
219
222
  }
220
223
  const [_, queryModel, queryOp, _queryArgs, { optimisticUpdate: optimisticUpdate2 }] = queryKey;
221
224
  if (!optimisticUpdate2) {
225
+ if (logging) {
226
+ console.log(`Skipping optimistic update for ${JSON.stringify(queryKey)} due to opt-out`);
227
+ }
222
228
  continue;
223
229
  }
224
230
  const mutatedData = yield applyMutation(