{"version":3,"sources":["../../src/createPersister.ts"],"sourcesContent":["import {\n  hashKey,\n  matchQuery,\n  notifyManager,\n  partialMatchKey,\n} from '@tanstack/query-core'\nimport type {\n  Query,\n  QueryClient,\n  QueryFilters,\n  QueryFunctionContext,\n  QueryKey,\n  QueryState,\n} from '@tanstack/query-core'\n\nexport interface PersistedQuery {\n  buster: string\n  queryHash: string\n  queryKey: QueryKey\n  state: QueryState\n}\n\nexport type MaybePromise<T> = T | Promise<T>\n\nexport interface AsyncStorage<TStorageValue = string> {\n  getItem: (key: string) => MaybePromise<TStorageValue | undefined | null>\n  setItem: (key: string, value: TStorageValue) => MaybePromise<unknown>\n  removeItem: (key: string) => MaybePromise<void>\n  entries?: () => MaybePromise<Array<[key: string, value: TStorageValue]>>\n}\n\nexport interface StoragePersisterOptions<TStorageValue = string> {\n  /** The storage client used for setting and retrieving items from cache.\n   * For SSR pass in `undefined`.\n   */\n  storage: AsyncStorage<TStorageValue> | undefined | null\n  /**\n   * How to serialize the data to storage.\n   * @default `JSON.stringify`\n   */\n  serialize?: (persistedQuery: PersistedQuery) => MaybePromise<TStorageValue>\n  /**\n   * How to deserialize the data from storage.\n   * @default `JSON.parse`\n   */\n  deserialize?: (cachedString: TStorageValue) => MaybePromise<PersistedQuery>\n  /**\n   * A unique string that can be used to forcefully invalidate existing caches,\n   * if they do not share the same buster string\n   */\n  buster?: string\n  /**\n   * The max-allowed age of the cache in milliseconds.\n   * If a persisted cache is found that is older than this\n   * time, it will be discarded\n   * @default 24 hours\n   */\n  maxAge?: number\n  /**\n   * Prefix to be used for storage key.\n   * Storage key is a combination of prefix and query hash in a form of `prefix-queryHash`.\n   * @default 'tanstack-query'\n   */\n  prefix?: string\n  /**\n   * If set to `true`, the query will refetch on successful query restoration if the data is stale.\n   * If set to `false`, the query will not refetch on successful query restoration.\n   * If set to `'always'`, the query will always refetch on successful query restoration.\n   * Defaults to `true`.\n   */\n  refetchOnRestore?: boolean | 'always'\n  /**\n   * Filters to narrow down which Queries should be persisted.\n   */\n  filters?: QueryFilters\n}\n\nexport const PERSISTER_KEY_PREFIX = 'tanstack-query'\n\n/**\n * Warning: experimental feature.\n * This utility function enables fine-grained query persistence.\n * Simple add it as a `persister` parameter to `useQuery` or `defaultOptions` on `queryClient`.\n *\n * ```\n * useQuery({\n     queryKey: ['myKey'],\n     queryFn: fetcher,\n     persister: createPersister({\n       storage: localStorage,\n     }),\n   })\n   ```\n */\nexport function experimental_createQueryPersister<TStorageValue = string>({\n  storage,\n  buster = '',\n  maxAge = 1000 * 60 * 60 * 24,\n  serialize = JSON.stringify as Required<\n    StoragePersisterOptions<TStorageValue>\n  >['serialize'],\n  deserialize = JSON.parse as Required<\n    StoragePersisterOptions<TStorageValue>\n  >['deserialize'],\n  prefix = PERSISTER_KEY_PREFIX,\n  refetchOnRestore = true,\n  filters,\n}: StoragePersisterOptions<TStorageValue>) {\n  function isExpiredOrBusted(persistedQuery: PersistedQuery) {\n    if (persistedQuery.state.dataUpdatedAt) {\n      const queryAge = Date.now() - persistedQuery.state.dataUpdatedAt\n      const expired = queryAge > maxAge\n      const busted = persistedQuery.buster !== buster\n\n      if (expired || busted) {\n        return true\n      }\n\n      return false\n    }\n\n    return true\n  }\n\n  async function retrieveQuery<T>(\n    queryHash: string,\n    afterRestoreMacroTask?: (persistedQuery: PersistedQuery) => void,\n  ) {\n    if (storage != null) {\n      const storageKey = `${prefix}-${queryHash}`\n      try {\n        const storedData = await storage.getItem(storageKey)\n        if (storedData) {\n          let persistedQuery: PersistedQuery\n          try {\n            persistedQuery = await deserialize(storedData)\n          } catch {\n            await storage.removeItem(storageKey)\n            return\n          }\n\n          if (isExpiredOrBusted(persistedQuery)) {\n            await storage.removeItem(storageKey)\n          } else {\n            if (afterRestoreMacroTask) {\n              // Just after restoring we want to get fresh data from the server if it's stale\n              notifyManager.schedule(() =>\n                afterRestoreMacroTask(persistedQuery),\n              )\n            }\n            // We must resolve the promise here, as otherwise we will have `loading` state in the app until `queryFn` resolves\n            return persistedQuery.state.data as T\n          }\n        }\n      } catch (err) {\n        if (process.env.NODE_ENV === 'development') {\n          console.error(err)\n          console.warn(\n            'Encountered an error attempting to restore query cache from persisted location.',\n          )\n        }\n        await storage.removeItem(storageKey)\n      }\n    }\n\n    return\n  }\n\n  async function persistQueryByKey(\n    queryKey: QueryKey,\n    queryClient: QueryClient,\n  ) {\n    if (storage != null) {\n      const query = queryClient.getQueryCache().find({ queryKey })\n      if (query) {\n        await persistQuery(query)\n      } else {\n        if (process.env.NODE_ENV === 'development') {\n          console.warn(\n            'Could not find query to be persisted. QueryKey:',\n            JSON.stringify(queryKey),\n          )\n        }\n      }\n    }\n  }\n\n  async function persistQuery(query: Query) {\n    if (storage != null) {\n      const storageKey = `${prefix}-${query.queryHash}`\n      storage.setItem(\n        storageKey,\n        await serialize({\n          state: query.state,\n          queryKey: query.queryKey,\n          queryHash: query.queryHash,\n          buster: buster,\n        }),\n      )\n    }\n  }\n\n  async function persisterFn<T, TQueryKey extends QueryKey>(\n    queryFn: (context: QueryFunctionContext<TQueryKey>) => T | Promise<T>,\n    ctx: QueryFunctionContext<TQueryKey>,\n    query: Query,\n  ) {\n    const matchesFilter = filters ? matchQuery(filters, query) : true\n\n    // Try to restore only if we do not have any data in the cache and we have persister defined\n    if (matchesFilter && query.state.data === undefined && storage != null) {\n      const restoredData = await retrieveQuery(\n        query.queryHash,\n        (persistedQuery: PersistedQuery) => {\n          // Set proper updatedAt, since resolving in the first pass overrides those values\n          query.setState({\n            dataUpdatedAt: persistedQuery.state.dataUpdatedAt,\n            errorUpdatedAt: persistedQuery.state.errorUpdatedAt,\n          })\n\n          if (\n            refetchOnRestore === 'always' ||\n            (refetchOnRestore === true && query.isStale())\n          ) {\n            query.fetch()\n          }\n        },\n      )\n\n      if (restoredData !== undefined) {\n        return Promise.resolve(restoredData as T)\n      }\n    }\n\n    // If we did not restore, or restoration failed - fetch\n    const queryFnResult = await queryFn(ctx)\n\n    if (matchesFilter && storage != null) {\n      // Persist if we have storage defined, we use timeout to get proper state to be persisted\n      notifyManager.schedule(() => {\n        persistQuery(query)\n      })\n    }\n\n    return Promise.resolve(queryFnResult)\n  }\n\n  async function persisterGc() {\n    if (storage?.entries) {\n      const storageKeyPrefix = `${prefix}-`\n      const entries = await storage.entries()\n      for (const [key, value] of entries) {\n        if (key.startsWith(storageKeyPrefix)) {\n          let persistedQuery: PersistedQuery\n          try {\n            persistedQuery = await deserialize(value)\n          } catch {\n            await storage.removeItem(key)\n            continue\n          }\n          if (isExpiredOrBusted(persistedQuery)) {\n            await storage.removeItem(key)\n          }\n        }\n      }\n    } else if (process.env.NODE_ENV === 'development') {\n      throw new Error(\n        'Provided storage does not implement `entries` method. Garbage collection is not possible without ability to iterate over storage items.',\n      )\n    }\n  }\n\n  async function restoreQueries(\n    queryClient: QueryClient,\n    filters: Pick<QueryFilters, 'queryKey' | 'exact'> = {},\n  ): Promise<void> {\n    const { exact, queryKey } = filters\n\n    if (storage?.entries) {\n      const storageKeyPrefix = `${prefix}-`\n      const entries = await storage.entries()\n      for (const [key, value] of entries) {\n        if (key.startsWith(storageKeyPrefix)) {\n          let persistedQuery: PersistedQuery\n          try {\n            persistedQuery = await deserialize(value)\n          } catch {\n            await storage.removeItem(key)\n            continue\n          }\n          if (isExpiredOrBusted(persistedQuery)) {\n            await storage.removeItem(key)\n            continue\n          }\n\n          if (queryKey) {\n            if (exact) {\n              if (persistedQuery.queryHash !== hashKey(queryKey)) {\n                continue\n              }\n            } else if (!partialMatchKey(persistedQuery.queryKey, queryKey)) {\n              continue\n            }\n          }\n\n          queryClient.setQueryData(\n            persistedQuery.queryKey,\n            persistedQuery.state.data,\n            {\n              updatedAt: persistedQuery.state.dataUpdatedAt,\n            },\n          )\n        }\n      }\n    } else if (process.env.NODE_ENV === 'development') {\n      throw new Error(\n        'Provided storage does not implement `entries` method. Restoration of all stored entries is not possible without ability to iterate over storage items.',\n      )\n    }\n  }\n\n  async function removeQueries(\n    filters: Pick<QueryFilters, 'queryKey' | 'exact'> = {},\n  ): Promise<void> {\n    const { exact, queryKey } = filters\n\n    if (storage?.entries) {\n      const entries = await storage.entries()\n      const storageKeyPrefix = `${prefix}-`\n      for (const [key, value] of entries) {\n        if (key.startsWith(storageKeyPrefix)) {\n          if (!queryKey) {\n            await storage.removeItem(key)\n            continue\n          }\n\n          let persistedQuery: PersistedQuery\n          try {\n            persistedQuery = await deserialize(value)\n          } catch {\n            await storage.removeItem(key)\n            continue\n          }\n\n          if (exact) {\n            if (persistedQuery.queryHash !== hashKey(queryKey)) {\n              continue\n            }\n          } else if (!partialMatchKey(persistedQuery.queryKey, queryKey)) {\n            continue\n          }\n\n          await storage.removeItem(key)\n        }\n      }\n    } else if (process.env.NODE_ENV === 'development') {\n      throw new Error(\n        'Provided storage does not implement `entries` method. Removal of stored entries is not possible without ability to iterate over storage items.',\n      )\n    }\n  }\n\n  return {\n    persisterFn,\n    persistQuery,\n    persistQueryByKey,\n    retrieveQuery,\n    persisterGc,\n    restoreQueries,\n    removeQueries,\n  }\n}\n"],"mappings":"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","names":["filters"]}