@vielzeug/courier 1.1.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +10 -12
  2. package/dist/_dev.cjs +1 -1
  3. package/dist/_dev.cjs.map +1 -1
  4. package/dist/_dev.js +1 -1
  5. package/dist/_dev.js.map +1 -1
  6. package/dist/api.cjs +1 -1
  7. package/dist/api.cjs.map +1 -1
  8. package/dist/api.d.ts +4 -7
  9. package/dist/api.d.ts.map +1 -1
  10. package/dist/api.js +1 -1
  11. package/dist/api.js.map +1 -1
  12. package/dist/courier.cjs +5 -6
  13. package/dist/courier.cjs.map +1 -1
  14. package/dist/courier.d.ts +20 -142
  15. package/dist/courier.d.ts.map +1 -1
  16. package/dist/courier.iife.js +5 -6
  17. package/dist/courier.iife.js.map +1 -1
  18. package/dist/courier.js +5 -6
  19. package/dist/courier.js.map +1 -1
  20. package/dist/devtools.cjs.map +1 -1
  21. package/dist/devtools.d.ts +0 -28
  22. package/dist/devtools.d.ts.map +1 -1
  23. package/dist/devtools.js.map +1 -1
  24. package/dist/errors.cjs +1 -1
  25. package/dist/errors.cjs.map +1 -1
  26. package/dist/errors.d.ts +0 -3
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/errors.js +1 -1
  29. package/dist/errors.js.map +1 -1
  30. package/dist/index.cjs +1 -1
  31. package/dist/index.d.ts +6 -22
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/interceptors.cjs +1 -1
  35. package/dist/interceptors.cjs.map +1 -1
  36. package/dist/interceptors.js +1 -1
  37. package/dist/interceptors.js.map +1 -1
  38. package/dist/query.cjs +1 -1
  39. package/dist/query.cjs.map +1 -1
  40. package/dist/query.d.ts +8 -101
  41. package/dist/query.d.ts.map +1 -1
  42. package/dist/query.js +1 -1
  43. package/dist/query.js.map +1 -1
  44. package/dist/serialize.cjs +1 -1
  45. package/dist/serialize.cjs.map +1 -1
  46. package/dist/serialize.d.ts +1 -1
  47. package/dist/serialize.d.ts.map +1 -1
  48. package/dist/serialize.js +1 -1
  49. package/dist/serialize.js.map +1 -1
  50. package/dist/stream.cjs +5 -1
  51. package/dist/stream.cjs.map +1 -1
  52. package/dist/stream.d.ts +22 -39
  53. package/dist/stream.d.ts.map +1 -1
  54. package/dist/stream.js +5 -1
  55. package/dist/stream.js.map +1 -1
  56. package/dist/transport.cjs.map +1 -1
  57. package/dist/transport.d.ts +1 -1
  58. package/dist/transport.js.map +1 -1
  59. package/dist/types.d.ts +38 -46
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/url.cjs.map +1 -1
  62. package/dist/url.d.ts +0 -9
  63. package/dist/url.d.ts.map +1 -1
  64. package/dist/url.js.map +1 -1
  65. package/package.json +4 -4
  66. package/dist/batcher.cjs +0 -2
  67. package/dist/batcher.cjs.map +0 -1
  68. package/dist/batcher.d.ts +0 -66
  69. package/dist/batcher.d.ts.map +0 -1
  70. package/dist/batcher.js +0 -2
  71. package/dist/batcher.js.map +0 -1
  72. package/dist/focus.cjs +0 -2
  73. package/dist/focus.cjs.map +0 -1
  74. package/dist/focus.d.ts +0 -25
  75. package/dist/focus.d.ts.map +0 -1
  76. package/dist/focus.js +0 -2
  77. package/dist/focus.js.map +0 -1
  78. package/dist/mutation.cjs +0 -2
  79. package/dist/mutation.cjs.map +0 -1
  80. package/dist/mutation.d.ts +0 -81
  81. package/dist/mutation.d.ts.map +0 -1
  82. package/dist/mutation.js +0 -2
  83. package/dist/mutation.js.map +0 -1
  84. package/dist/persist.cjs +0 -2
  85. package/dist/persist.cjs.map +0 -1
  86. package/dist/persist.d.ts +0 -86
  87. package/dist/persist.d.ts.map +0 -1
  88. package/dist/persist.js +0 -2
  89. package/dist/persist.js.map +0 -1
  90. package/dist/query-cache.cjs +0 -2
  91. package/dist/query-cache.cjs.map +0 -1
  92. package/dist/query-cache.d.ts +0 -88
  93. package/dist/query-cache.d.ts.map +0 -1
  94. package/dist/query-cache.js +0 -2
  95. package/dist/query-cache.js.map +0 -1
  96. package/dist/query-observe.cjs +0 -2
  97. package/dist/query-observe.cjs.map +0 -1
  98. package/dist/query-observe.d.ts +0 -17
  99. package/dist/query-observe.d.ts.map +0 -1
  100. package/dist/query-observe.js +0 -2
  101. package/dist/query-observe.js.map +0 -1
  102. package/dist/retry.cjs +0 -2
  103. package/dist/retry.cjs.map +0 -1
  104. package/dist/retry.d.ts +0 -34
  105. package/dist/retry.d.ts.map +0 -1
  106. package/dist/retry.js +0 -2
  107. package/dist/retry.js.map +0 -1
  108. package/dist/stream-readable.cjs +0 -3
  109. package/dist/stream-readable.cjs.map +0 -1
  110. package/dist/stream-readable.d.ts +0 -46
  111. package/dist/stream-readable.d.ts.map +0 -1
  112. package/dist/stream-readable.js +0 -3
  113. package/dist/stream-readable.js.map +0 -1
  114. package/dist/stream-shared.cjs +0 -2
  115. package/dist/stream-shared.cjs.map +0 -1
  116. package/dist/stream-shared.d.ts +0 -41
  117. package/dist/stream-shared.d.ts.map +0 -1
  118. package/dist/stream-shared.js +0 -2
  119. package/dist/stream-shared.js.map +0 -1
  120. package/dist/stream-sse.cjs +0 -6
  121. package/dist/stream-sse.cjs.map +0 -1
  122. package/dist/stream-sse.d.ts +0 -59
  123. package/dist/stream-sse.d.ts.map +0 -1
  124. package/dist/stream-sse.js +0 -6
  125. package/dist/stream-sse.js.map +0 -1
  126. package/dist/sync-store.cjs +0 -2
  127. package/dist/sync-store.cjs.map +0 -1
  128. package/dist/sync-store.d.ts +0 -20
  129. package/dist/sync-store.d.ts.map +0 -1
  130. package/dist/sync-store.js +0 -2
  131. package/dist/sync-store.js.map +0 -1
package/dist/url.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"url.js","names":[],"sources":["../src/url.ts"],"sourcesContent":["import type { ResponseType } from './response';\n\nimport { CourierParseError } from './errors';\n\ntype QueryScalar = string | number | boolean | null;\n\nexport type ParamValue = QueryScalar | readonly QueryScalar[] | undefined;\nexport type Params = Record<string, ParamValue>;\n\n// Type-safe path params: extracts {param} placeholders from a path string\ntype ExtractPathParams<P extends string> = P extends `${string}{${infer K}}${infer R}`\n ? K | ExtractPathParams<R>\n : never;\n\nexport type PathConfig<P extends string> = [ExtractPathParams<P>] extends [never]\n ? { params?: never }\n : { params: Record<ExtractPathParams<P>, string | number | boolean> };\n\nexport type CourierRequestConfig<P extends string = string, T = unknown> = PathConfig<P> & {\n /** Request body. Plain objects → JSON. BodyInit values passed as-is. */\n body?: unknown;\n /**\n * Set to `false` to bypass in-flight deduplication for this request.\n * Safe + idempotent methods (GET, HEAD, OPTIONS) are deduplicated by default.\n * DELETE is idempotent but has side-effects, so it does not auto-dedupe;\n * pass an explicit `dedupeKey` to opt in.\n */\n dedupe?: boolean;\n /** Explicit deduplication key for non-idempotent requests. */\n dedupeKey?: unknown;\n /** Query string parameters. */\n query?: Params;\n /** Response parsing strategy. */\n responseType?: ResponseType;\n /**\n * Optional schema for response validation. Any object with a `parse(data)` method\n * works — e.g. a `@vielzeug/spell` schema or a plain validator function wrapper.\n * Called after the response body is parsed; throws if validation fails.\n * The schema's return type must be assignable to `T`.\n */\n schema?: { parse(data: unknown): T };\n /** Request timeout in ms. Overrides client default. */\n timeout?: number;\n};\n\nexport type HttpRequestConfig<P extends string = string, T = unknown> = CourierRequestConfig<P, T> & {\n /** Raw fetch options for advanced use (credentials, cache, mode, referrer, etc.). */\n fetchInit?: Omit<RequestInit, 'body' | 'headers' | 'method' | 'signal'>;\n /** Per-request headers merged with (and overriding) global client headers. */\n headers?: Record<string, string>;\n /** External abort signal merged with internal cancellation. */\n signal?: AbortSignal;\n};\n\nexport function buildUrl(base: string, path: string, params?: Params, query?: Params): string {\n const baseClean = base.replace(/\\/+$/, '');\n let pathClean = path.replace(/^\\/+/, '');\n\n pathClean = pathClean.replace(/\\{([^}]+)\\}/g, (_, key: string) => {\n const val = params?.[key];\n\n if (val === undefined || val === null) throw new CourierParseError(`unresolved path param {${key}} in '${path}'`);\n\n return encodeURIComponent(String(val));\n });\n\n const url = baseClean && pathClean ? `${baseClean}/${pathClean}` : baseClean || pathClean;\n\n if (!query) return url;\n\n const qs = new URLSearchParams();\n\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) {\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const entry of value) {\n if (entry !== undefined) {\n qs.append(key, entry === null ? '' : String(entry));\n }\n }\n\n continue;\n }\n\n qs.append(key, value === null ? '' : String(value));\n }\n\n const queryString = qs.toString();\n\n return queryString ? `${url}${url.includes('?') ? '&' : '?'}${queryString}` : url;\n}\n"],"mappings":"gDAsDA,SAAgB,EAAS,EAAc,EAAc,EAAiB,EAAwB,CAC5F,IAAM,EAAY,EAAK,QAAQ,OAAQ,EAAE,EACrC,EAAY,EAAK,QAAQ,OAAQ,EAAE,EAEvC,EAAY,EAAU,QAAQ,gBAAiB,EAAG,IAAgB,CAChE,IAAM,EAAM,IAAS,GAErB,GAAI,GAA6B,KAAM,MAAM,IAAI,EAAkB,0BAA0B,EAAI,QAAQ,EAAK,EAAE,EAEhH,OAAO,mBAAmB,OAAO,CAAG,CAAC,CACvC,CAAC,EAED,IAAM,EAAM,GAAa,EAAY,GAAG,EAAU,GAAG,IAAc,GAAa,EAEhF,GAAI,CAAC,EAAO,OAAO,EAEnB,IAAM,EAAK,IAAI,gBAEf,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAK,EACzC,OAAU,IAAA,GAId,IAAI,MAAM,QAAQ,CAAK,EAAG,CACxB,IAAK,IAAM,KAAS,EACd,IAAU,IAAA,IACZ,EAAG,OAAO,EAAK,IAAU,KAAO,GAAK,OAAO,CAAK,CAAC,EAItD,QACF,CAEA,EAAG,OAAO,EAAK,IAAU,KAAO,GAAK,OAAO,CAAK,CAAC,CAFlD,CAKF,IAAM,EAAc,EAAG,SAAS,EAEhC,OAAO,EAAc,GAAG,IAAM,EAAI,SAAS,GAAG,EAAI,IAAM,MAAM,IAAgB,CAChF"}
1
+ {"version":3,"file":"url.js","names":[],"sources":["../src/url.ts"],"sourcesContent":["import type { ResponseType } from './response';\n\nimport { CourierParseError } from './errors';\n\ntype QueryScalar = string | number | boolean | null;\n\nexport type ParamValue = QueryScalar | readonly QueryScalar[] | undefined;\nexport type Params = Record<string, ParamValue>;\n\n// Type-safe path params: extracts {param} placeholders from a path string\ntype ExtractPathParams<P extends string> = P extends `${string}{${infer K}}${infer R}`\n ? K | ExtractPathParams<R>\n : never;\n\nexport type PathConfig<P extends string> = [ExtractPathParams<P>] extends [never]\n ? { params?: never }\n : { params: Record<ExtractPathParams<P>, string | number | boolean> };\n\nexport type CourierRequestConfig<P extends string = string, T = unknown> = PathConfig<P> & {\n /** Request body. Plain objects → JSON. BodyInit values passed as-is. */\n body?: unknown;\n /** Query string parameters. */\n query?: Params;\n /** Response parsing strategy. */\n responseType?: ResponseType;\n /**\n * Optional schema for response validation. Any object with a `parse(data)` method\n * works — e.g. a `@vielzeug/spell` schema or a plain validator function wrapper.\n * Called after the response body is parsed; throws if validation fails.\n * The schema's return type must be assignable to `T`.\n */\n schema?: { parse(data: unknown): T };\n /** Request timeout in ms. Overrides client default. */\n timeout?: number;\n};\n\nexport type HttpRequestConfig<P extends string = string, T = unknown> = CourierRequestConfig<P, T> & {\n /** Raw fetch options for advanced use (credentials, cache, mode, referrer, etc.). */\n fetchInit?: Omit<RequestInit, 'body' | 'headers' | 'method' | 'signal'>;\n /** Per-request headers merged with (and overriding) global client headers. */\n headers?: Record<string, string>;\n /** External abort signal merged with internal cancellation. */\n signal?: AbortSignal;\n};\n\nexport function buildUrl(base: string, path: string, params?: Params, query?: Params): string {\n const baseClean = base.replace(/\\/+$/, '');\n let pathClean = path.replace(/^\\/+/, '');\n\n pathClean = pathClean.replace(/\\{([^}]+)\\}/g, (_, key: string) => {\n const val = params?.[key];\n\n if (val === undefined || val === null) throw new CourierParseError(`unresolved path param {${key}} in '${path}'`);\n\n return encodeURIComponent(String(val));\n });\n\n const url = baseClean && pathClean ? `${baseClean}/${pathClean}` : baseClean || pathClean;\n\n if (!query) return url;\n\n const qs = new URLSearchParams();\n\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) {\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const entry of value) {\n if (entry !== undefined) {\n qs.append(key, entry === null ? '' : String(entry));\n }\n }\n\n continue;\n }\n\n qs.append(key, value === null ? '' : String(value));\n }\n\n const queryString = qs.toString();\n\n return queryString ? `${url}${url.includes('?') ? '&' : '?'}${queryString}` : url;\n}\n"],"mappings":"gDA6CA,SAAgB,EAAS,EAAc,EAAc,EAAiB,EAAwB,CAC5F,IAAM,EAAY,EAAK,QAAQ,OAAQ,EAAE,EACrC,EAAY,EAAK,QAAQ,OAAQ,EAAE,EAEvC,EAAY,EAAU,QAAQ,gBAAiB,EAAG,IAAgB,CAChE,IAAM,EAAM,IAAS,GAErB,GAAI,GAA6B,KAAM,MAAM,IAAI,EAAkB,0BAA0B,EAAI,QAAQ,EAAK,EAAE,EAEhH,OAAO,mBAAmB,OAAO,CAAG,CAAC,CACvC,CAAC,EAED,IAAM,EAAM,GAAa,EAAY,GAAG,EAAU,GAAG,IAAc,GAAa,EAEhF,GAAI,CAAC,EAAO,OAAO,EAEnB,IAAM,EAAK,IAAI,gBAEf,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAK,EACzC,OAAU,IAAA,GAId,IAAI,MAAM,QAAQ,CAAK,EAAG,CACxB,IAAK,IAAM,KAAS,EACd,IAAU,IAAA,IACZ,EAAG,OAAO,EAAK,IAAU,KAAO,GAAK,OAAO,CAAK,CAAC,EAItD,QACF,CAEA,EAAG,OAAO,EAAK,IAAU,KAAO,GAAK,OAAO,CAAK,CAAC,CAFlD,CAKF,IAAM,EAAc,EAAG,SAAS,EAEhC,OAAO,EAAc,GAAG,IAAM,EAAI,SAAS,GAAG,EAAI,IAAM,MAAM,IAAgB,CAChF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/courier",
3
- "version": "1.1.4",
3
+ "version": "2.0.0",
4
4
  "description": "HTTP client with caching, request deduplication, typed mutations, SSE streaming, and interceptors",
5
5
  "type": "module",
6
6
  "files": [
@@ -41,12 +41,12 @@
41
41
  "node": ">=22"
42
42
  },
43
43
  "dependencies": {
44
- "@vielzeug/arsenal": "1.1.2"
44
+ "@vielzeug/arsenal": "2.0.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@types/node": "^26.1.0",
47
+ "@types/node": "^26.1.2",
48
48
  "typescript": "^6.0.3",
49
- "vite": "^8.1.3",
49
+ "vite": "^8.2.0",
50
50
  "vitest": "^4.1.10"
51
51
  }
52
52
  }
package/dist/batcher.cjs DELETED
@@ -1,2 +0,0 @@
1
- const e=require("./errors.cjs");function t(t){let{maxSize:n=25,resolve:r,resolveSettled:i,window:a=0}=t,o=[],s=new AbortController,c=!1,l=null,u=!1;function d(){if(o.length===0)return;let t=o.splice(0,n);o.length>0&&f();let a=t.map(e=>e.key);try{i?i(a).then(n=>{if(n.length!==t.length){let r=new e.CourierBatcherError(`Batcher: resolveSettled() returned ${n.length} results for ${t.length} keys`);for(let e of t)e.reject(r);return}t.forEach((e,t)=>{let r=n[t];r.status===`fulfilled`?e.resolve(r.value):e.reject(r.reason)})}).catch(e=>{for(let n of t)n.reject(e)}):r(a).then(n=>{if(n.length!==t.length){let r=new e.CourierBatcherError(`Batcher: resolve() returned ${n.length} results for ${t.length} keys`);for(let e of t)e.reject(r);return}t.forEach((e,t)=>e.resolve(n[t]))}).catch(e=>{for(let n of t)n.reject(e)})}catch(e){for(let n of t)n.reject(e)}}function f(){c||(c=!0,a===0?queueMicrotask(()=>{c=!1,d()}):l=setTimeout(()=>{l=null,c=!1,d()},a))}return{get disposalSignal(){return s.signal},dispose(){if(u)return;u=!0,l!==null&&(clearTimeout(l),l=null);let t=new e.CourierDisposedError(`Batcher`);for(let e of o.splice(0))e.reject(t);c=!1,s.abort()},get disposed(){return u},load(t){return u?Promise.reject(new e.CourierDisposedError(`Batcher`)):new Promise((e,r)=>{o.push({key:t,reject:r,resolve:e}),o.length>=n?(l!==null&&(clearTimeout(l),l=null),c=!1,d()):f()})},[Symbol.dispose](){this.dispose()}}}exports.createBatcher=t;
2
- //# sourceMappingURL=batcher.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"batcher.cjs","names":[],"sources":["../src/batcher.ts"],"sourcesContent":["import { CourierBatcherError, CourierDisposedError } from './errors';\n\ntype BatcherBase = {\n /** Maximum batch size before a flush is forced. Defaults to `25`. */\n maxSize?: number;\n /**\n * Scheduling window in ms. Defaults to `0` (next microtask), which coalesces\n * all `load()` calls within the same synchronous block into one batch.\n * Set a positive value (e.g. `16`) to coalesce across async boundaries.\n */\n window?: number;\n};\n\n/**\n * Batcher options. Exactly one of `resolve` or `resolveSettled` must be provided — they are mutually exclusive.\n *\n * - **`resolve`**: execute a batch and return results in the same order. All succeed or all fail.\n * - **`resolveSettled`**: per-key error isolation — each `load()` fulfills or rejects independently.\n */\nexport type BatcherOptions<K, V> =\n | (BatcherBase & {\n /**\n * Execute a batch of keys and return results in the same order.\n * The returned array must have the same length as `keys`.\n */\n resolve: (keys: K[]) => Promise<V[]>;\n resolveSettled?: never;\n })\n | (BatcherBase & {\n resolve?: never;\n /**\n * Per-key error isolation. Receives the same key array but must return a\n * `PromiseSettledResult<V>[]` of equal length. Each `load()` promise fulfills\n * or rejects independently based on its corresponding settled result.\n */\n resolveSettled: (keys: K[]) => Promise<PromiseSettledResult<V>[]>;\n });\n\ntype PendingItem<K, V> = {\n key: K;\n reject: (reason: unknown) => void;\n resolve: (value: V) => void;\n};\n\n/**\n * DataLoader-style request batcher. Coalesces individual `load()` calls made\n * within the same scheduling window into a single `resolve()` call.\n *\n * Typical use: N+1 query elimination, request fan-out coalescing.\n *\n * @example\n * ```ts\n * const userLoader = createBatcher({\n * resolve: async (ids) => api.post<User[]>('/users/batch', { body: { ids } }),\n * });\n *\n * // These 3 calls collapse into 1 POST /users/batch { ids: [1, 2, 3] }\n * const [alice, bob, carol] = await Promise.all([\n * userLoader.load(1),\n * userLoader.load(2),\n * userLoader.load(3),\n * ]);\n * ```\n */\nexport function createBatcher<K, V>(opts: BatcherOptions<K, V>) {\n const { maxSize = 25, resolve, resolveSettled, window: windowMs = 0 } = opts;\n\n const queue: PendingItem<K, V>[] = [];\n const controller = new AbortController();\n let scheduled = false;\n let pendingTimer: ReturnType<typeof setTimeout> | null = null;\n let disposed = false;\n\n function flush(): void {\n if (queue.length === 0) return;\n\n const batch = queue.splice(0, maxSize);\n\n // If more items remain after taking maxSize, schedule another flush immediately.\n if (queue.length > 0) schedule();\n\n const keys = batch.map((item) => item.key);\n\n // resolve()/resolveSettled() are called directly (not just chained with .then/.catch) so a\n // consumer function that throws synchronously — rather than returning a rejected promise —\n // still rejects every pending load() instead of leaving them hanging forever.\n try {\n if (resolveSettled) {\n void resolveSettled(keys)\n .then((results) => {\n if (results.length !== batch.length) {\n const err = new CourierBatcherError(\n `Batcher: resolveSettled() returned ${results.length} results for ${batch.length} keys`,\n );\n\n for (const item of batch) item.reject(err);\n\n return;\n }\n\n batch.forEach((item, i) => {\n const r = results[i];\n\n if (r.status === 'fulfilled') {\n item.resolve(r.value);\n } else {\n item.reject(r.reason);\n }\n });\n })\n .catch((err: unknown) => {\n for (const item of batch) item.reject(err);\n });\n } else {\n void resolve(keys)\n .then((results) => {\n if (results.length !== batch.length) {\n const err = new CourierBatcherError(\n `Batcher: resolve() returned ${results.length} results for ${batch.length} keys`,\n );\n\n for (const item of batch) item.reject(err);\n\n return;\n }\n\n batch.forEach((item, i) => item.resolve(results[i]));\n })\n .catch((err: unknown) => {\n for (const item of batch) item.reject(err);\n });\n }\n } catch (err) {\n for (const item of batch) item.reject(err);\n }\n }\n\n function schedule(): void {\n if (scheduled) return;\n\n scheduled = true;\n\n if (windowMs === 0) {\n queueMicrotask(() => {\n scheduled = false;\n flush();\n });\n } else {\n pendingTimer = setTimeout(() => {\n pendingTimer = null;\n scheduled = false;\n flush();\n }, windowMs);\n }\n }\n\n return {\n get disposalSignal(): AbortSignal {\n return controller.signal;\n },\n\n /**\n * Cancels all pending `load()` promises with a disposal error and stops\n * any scheduled flushes. Safe to call multiple times.\n */\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n\n if (pendingTimer !== null) {\n clearTimeout(pendingTimer);\n pendingTimer = null;\n }\n\n const err = new CourierDisposedError('Batcher');\n\n for (const item of queue.splice(0)) item.reject(err);\n\n scheduled = false;\n controller.abort();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n load(key: K): Promise<V> {\n if (disposed) return Promise.reject(new CourierDisposedError('Batcher'));\n\n return new Promise<V>((res, rej) => {\n queue.push({ key, reject: rej, resolve: res });\n\n if (queue.length >= maxSize) {\n // Force-flush immediately when the batch is full.\n // Cancel any pending timer first so it cannot fire a second flush.\n if (pendingTimer !== null) {\n clearTimeout(pendingTimer);\n pendingTimer = null;\n }\n\n scheduled = false;\n flush();\n } else {\n schedule();\n }\n });\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n };\n}\n\nexport type Batcher<K, V> = ReturnType<typeof createBatcher<K, V>>;\n"],"mappings":"gCAgEA,SAAgB,EAAoB,EAA4B,CAC9D,GAAM,CAAE,UAAU,GAAI,UAAS,iBAAgB,OAAQ,EAAW,GAAM,EAElE,EAA6B,CAAC,EAC9B,EAAa,IAAI,gBACnB,EAAY,GACZ,EAAqD,KACrD,EAAW,GAEf,SAAS,GAAc,CACrB,GAAI,EAAM,SAAW,EAAG,OAExB,IAAM,EAAQ,EAAM,OAAO,EAAG,CAAO,EAGjC,EAAM,OAAS,GAAG,EAAS,EAE/B,IAAM,EAAO,EAAM,IAAK,GAAS,EAAK,GAAG,EAKzC,GAAI,CACE,EACF,EAAoB,CAAI,CAAC,CACtB,KAAM,GAAY,CACjB,GAAI,EAAQ,SAAW,EAAM,OAAQ,CACnC,IAAM,EAAM,IAAI,EAAA,oBACd,sCAAsC,EAAQ,OAAO,eAAe,EAAM,OAAO,MACnF,EAEA,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,EAEzC,MACF,CAEA,EAAM,SAAS,EAAM,IAAM,CACzB,IAAM,EAAI,EAAQ,GAEd,EAAE,SAAW,YACf,EAAK,QAAQ,EAAE,KAAK,EAEpB,EAAK,OAAO,EAAE,MAAM,CAExB,CAAC,CACH,CAAC,CAAC,CACD,MAAO,GAAiB,CACvB,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CAAC,EAEH,EAAa,CAAI,CAAC,CACf,KAAM,GAAY,CACjB,GAAI,EAAQ,SAAW,EAAM,OAAQ,CACnC,IAAM,EAAM,IAAI,EAAA,oBACd,+BAA+B,EAAQ,OAAO,eAAe,EAAM,OAAO,MAC5E,EAEA,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,EAEzC,MACF,CAEA,EAAM,SAAS,EAAM,IAAM,EAAK,QAAQ,EAAQ,EAAE,CAAC,CACrD,CAAC,CAAC,CACD,MAAO,GAAiB,CACvB,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CAAC,CAEP,OAAS,EAAK,CACZ,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CACF,CAEA,SAAS,GAAiB,CACpB,IAEJ,EAAY,GAER,IAAa,EACf,mBAAqB,CACnB,EAAY,GACZ,EAAM,CACR,CAAC,EAED,EAAe,eAAiB,CAC9B,EAAe,KACf,EAAY,GACZ,EAAM,CACR,EAAG,CAAQ,EAEf,CAEA,MAAO,CACL,IAAI,gBAA8B,CAChC,OAAO,EAAW,MACpB,EAMA,SAAgB,CACd,GAAI,EAAU,OAEd,EAAW,GAEP,IAAiB,OACnB,aAAa,CAAY,EACzB,EAAe,MAGjB,IAAM,EAAM,IAAI,EAAA,qBAAqB,SAAS,EAE9C,IAAK,IAAM,KAAQ,EAAM,OAAO,CAAC,EAAG,EAAK,OAAO,CAAG,EAEnD,EAAY,GACZ,EAAW,MAAM,CACnB,EAEA,IAAI,UAAoB,CACtB,OAAO,CACT,EAEA,KAAK,EAAoB,CAGvB,OAFI,EAAiB,QAAQ,OAAO,IAAI,EAAA,qBAAqB,SAAS,CAAC,EAEhE,IAAI,SAAY,EAAK,IAAQ,CAClC,EAAM,KAAK,CAAE,MAAK,OAAQ,EAAK,QAAS,CAAI,CAAC,EAEzC,EAAM,QAAU,GAGd,IAAiB,OACnB,aAAa,CAAY,EACzB,EAAe,MAGjB,EAAY,GACZ,EAAM,GAEN,EAAS,CAEb,CAAC,CACH,EAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF,CACF"}
package/dist/batcher.d.ts DELETED
@@ -1,66 +0,0 @@
1
- type BatcherBase = {
2
- /** Maximum batch size before a flush is forced. Defaults to `25`. */
3
- maxSize?: number;
4
- /**
5
- * Scheduling window in ms. Defaults to `0` (next microtask), which coalesces
6
- * all `load()` calls within the same synchronous block into one batch.
7
- * Set a positive value (e.g. `16`) to coalesce across async boundaries.
8
- */
9
- window?: number;
10
- };
11
- /**
12
- * Batcher options. Exactly one of `resolve` or `resolveSettled` must be provided — they are mutually exclusive.
13
- *
14
- * - **`resolve`**: execute a batch and return results in the same order. All succeed or all fail.
15
- * - **`resolveSettled`**: per-key error isolation — each `load()` fulfills or rejects independently.
16
- */
17
- export type BatcherOptions<K, V> = (BatcherBase & {
18
- /**
19
- * Execute a batch of keys and return results in the same order.
20
- * The returned array must have the same length as `keys`.
21
- */
22
- resolve: (keys: K[]) => Promise<V[]>;
23
- resolveSettled?: never;
24
- }) | (BatcherBase & {
25
- resolve?: never;
26
- /**
27
- * Per-key error isolation. Receives the same key array but must return a
28
- * `PromiseSettledResult<V>[]` of equal length. Each `load()` promise fulfills
29
- * or rejects independently based on its corresponding settled result.
30
- */
31
- resolveSettled: (keys: K[]) => Promise<PromiseSettledResult<V>[]>;
32
- });
33
- /**
34
- * DataLoader-style request batcher. Coalesces individual `load()` calls made
35
- * within the same scheduling window into a single `resolve()` call.
36
- *
37
- * Typical use: N+1 query elimination, request fan-out coalescing.
38
- *
39
- * @example
40
- * ```ts
41
- * const userLoader = createBatcher({
42
- * resolve: async (ids) => api.post<User[]>('/users/batch', { body: { ids } }),
43
- * });
44
- *
45
- * // These 3 calls collapse into 1 POST /users/batch { ids: [1, 2, 3] }
46
- * const [alice, bob, carol] = await Promise.all([
47
- * userLoader.load(1),
48
- * userLoader.load(2),
49
- * userLoader.load(3),
50
- * ]);
51
- * ```
52
- */
53
- export declare function createBatcher<K, V>(opts: BatcherOptions<K, V>): {
54
- readonly disposalSignal: AbortSignal;
55
- /**
56
- * Cancels all pending `load()` promises with a disposal error and stops
57
- * any scheduled flushes. Safe to call multiple times.
58
- */
59
- dispose(): void;
60
- readonly disposed: boolean;
61
- load(key: K): Promise<V>;
62
- [Symbol.dispose](): void;
63
- };
64
- export type Batcher<K, V> = ReturnType<typeof createBatcher<K, V>>;
65
- export {};
66
- //# sourceMappingURL=batcher.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"batcher.d.ts","sourceRoot":"","sources":["../src/batcher.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG;IACjB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,EAAE,CAAC,IAC3B,CAAC,WAAW,GAAG;IACb;;;OAGG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,KAAK,CAAC;CACxB,CAAC,GACF,CAAC,WAAW,GAAG;IACb,OAAO,CAAC,EAAE,KAAK,CAAC;IAChB;;;;OAIG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;CACnE,CAAC,CAAC;AAQP;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;6BA6FpC,WAAW;IAIjC;;;OAGG;eACQ,IAAI;uBAkBC,OAAO;cAIb,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;wBAsBJ,IAAI;EAI3B;AAED,MAAM,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC"}
package/dist/batcher.js DELETED
@@ -1,2 +0,0 @@
1
- import{CourierBatcherError as e,CourierDisposedError as t}from"./errors.js";function n(n){let{maxSize:r=25,resolve:i,resolveSettled:a,window:o=0}=n,s=[],c=new AbortController,l=!1,u=null,d=!1;function f(){if(s.length===0)return;let t=s.splice(0,r);s.length>0&&p();let n=t.map(e=>e.key);try{a?a(n).then(n=>{if(n.length!==t.length){let r=new e(`Batcher: resolveSettled() returned ${n.length} results for ${t.length} keys`);for(let e of t)e.reject(r);return}t.forEach((e,t)=>{let r=n[t];r.status===`fulfilled`?e.resolve(r.value):e.reject(r.reason)})}).catch(e=>{for(let n of t)n.reject(e)}):i(n).then(n=>{if(n.length!==t.length){let r=new e(`Batcher: resolve() returned ${n.length} results for ${t.length} keys`);for(let e of t)e.reject(r);return}t.forEach((e,t)=>e.resolve(n[t]))}).catch(e=>{for(let n of t)n.reject(e)})}catch(e){for(let n of t)n.reject(e)}}function p(){l||(l=!0,o===0?queueMicrotask(()=>{l=!1,f()}):u=setTimeout(()=>{u=null,l=!1,f()},o))}return{get disposalSignal(){return c.signal},dispose(){if(d)return;d=!0,u!==null&&(clearTimeout(u),u=null);let e=new t(`Batcher`);for(let t of s.splice(0))t.reject(e);l=!1,c.abort()},get disposed(){return d},load(e){return d?Promise.reject(new t(`Batcher`)):new Promise((t,n)=>{s.push({key:e,reject:n,resolve:t}),s.length>=r?(u!==null&&(clearTimeout(u),u=null),l=!1,f()):p()})},[Symbol.dispose](){this.dispose()}}}export{n as createBatcher};
2
- //# sourceMappingURL=batcher.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"batcher.js","names":[],"sources":["../src/batcher.ts"],"sourcesContent":["import { CourierBatcherError, CourierDisposedError } from './errors';\n\ntype BatcherBase = {\n /** Maximum batch size before a flush is forced. Defaults to `25`. */\n maxSize?: number;\n /**\n * Scheduling window in ms. Defaults to `0` (next microtask), which coalesces\n * all `load()` calls within the same synchronous block into one batch.\n * Set a positive value (e.g. `16`) to coalesce across async boundaries.\n */\n window?: number;\n};\n\n/**\n * Batcher options. Exactly one of `resolve` or `resolveSettled` must be provided — they are mutually exclusive.\n *\n * - **`resolve`**: execute a batch and return results in the same order. All succeed or all fail.\n * - **`resolveSettled`**: per-key error isolation — each `load()` fulfills or rejects independently.\n */\nexport type BatcherOptions<K, V> =\n | (BatcherBase & {\n /**\n * Execute a batch of keys and return results in the same order.\n * The returned array must have the same length as `keys`.\n */\n resolve: (keys: K[]) => Promise<V[]>;\n resolveSettled?: never;\n })\n | (BatcherBase & {\n resolve?: never;\n /**\n * Per-key error isolation. Receives the same key array but must return a\n * `PromiseSettledResult<V>[]` of equal length. Each `load()` promise fulfills\n * or rejects independently based on its corresponding settled result.\n */\n resolveSettled: (keys: K[]) => Promise<PromiseSettledResult<V>[]>;\n });\n\ntype PendingItem<K, V> = {\n key: K;\n reject: (reason: unknown) => void;\n resolve: (value: V) => void;\n};\n\n/**\n * DataLoader-style request batcher. Coalesces individual `load()` calls made\n * within the same scheduling window into a single `resolve()` call.\n *\n * Typical use: N+1 query elimination, request fan-out coalescing.\n *\n * @example\n * ```ts\n * const userLoader = createBatcher({\n * resolve: async (ids) => api.post<User[]>('/users/batch', { body: { ids } }),\n * });\n *\n * // These 3 calls collapse into 1 POST /users/batch { ids: [1, 2, 3] }\n * const [alice, bob, carol] = await Promise.all([\n * userLoader.load(1),\n * userLoader.load(2),\n * userLoader.load(3),\n * ]);\n * ```\n */\nexport function createBatcher<K, V>(opts: BatcherOptions<K, V>) {\n const { maxSize = 25, resolve, resolveSettled, window: windowMs = 0 } = opts;\n\n const queue: PendingItem<K, V>[] = [];\n const controller = new AbortController();\n let scheduled = false;\n let pendingTimer: ReturnType<typeof setTimeout> | null = null;\n let disposed = false;\n\n function flush(): void {\n if (queue.length === 0) return;\n\n const batch = queue.splice(0, maxSize);\n\n // If more items remain after taking maxSize, schedule another flush immediately.\n if (queue.length > 0) schedule();\n\n const keys = batch.map((item) => item.key);\n\n // resolve()/resolveSettled() are called directly (not just chained with .then/.catch) so a\n // consumer function that throws synchronously — rather than returning a rejected promise —\n // still rejects every pending load() instead of leaving them hanging forever.\n try {\n if (resolveSettled) {\n void resolveSettled(keys)\n .then((results) => {\n if (results.length !== batch.length) {\n const err = new CourierBatcherError(\n `Batcher: resolveSettled() returned ${results.length} results for ${batch.length} keys`,\n );\n\n for (const item of batch) item.reject(err);\n\n return;\n }\n\n batch.forEach((item, i) => {\n const r = results[i];\n\n if (r.status === 'fulfilled') {\n item.resolve(r.value);\n } else {\n item.reject(r.reason);\n }\n });\n })\n .catch((err: unknown) => {\n for (const item of batch) item.reject(err);\n });\n } else {\n void resolve(keys)\n .then((results) => {\n if (results.length !== batch.length) {\n const err = new CourierBatcherError(\n `Batcher: resolve() returned ${results.length} results for ${batch.length} keys`,\n );\n\n for (const item of batch) item.reject(err);\n\n return;\n }\n\n batch.forEach((item, i) => item.resolve(results[i]));\n })\n .catch((err: unknown) => {\n for (const item of batch) item.reject(err);\n });\n }\n } catch (err) {\n for (const item of batch) item.reject(err);\n }\n }\n\n function schedule(): void {\n if (scheduled) return;\n\n scheduled = true;\n\n if (windowMs === 0) {\n queueMicrotask(() => {\n scheduled = false;\n flush();\n });\n } else {\n pendingTimer = setTimeout(() => {\n pendingTimer = null;\n scheduled = false;\n flush();\n }, windowMs);\n }\n }\n\n return {\n get disposalSignal(): AbortSignal {\n return controller.signal;\n },\n\n /**\n * Cancels all pending `load()` promises with a disposal error and stops\n * any scheduled flushes. Safe to call multiple times.\n */\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n\n if (pendingTimer !== null) {\n clearTimeout(pendingTimer);\n pendingTimer = null;\n }\n\n const err = new CourierDisposedError('Batcher');\n\n for (const item of queue.splice(0)) item.reject(err);\n\n scheduled = false;\n controller.abort();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n load(key: K): Promise<V> {\n if (disposed) return Promise.reject(new CourierDisposedError('Batcher'));\n\n return new Promise<V>((res, rej) => {\n queue.push({ key, reject: rej, resolve: res });\n\n if (queue.length >= maxSize) {\n // Force-flush immediately when the batch is full.\n // Cancel any pending timer first so it cannot fire a second flush.\n if (pendingTimer !== null) {\n clearTimeout(pendingTimer);\n pendingTimer = null;\n }\n\n scheduled = false;\n flush();\n } else {\n schedule();\n }\n });\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n };\n}\n\nexport type Batcher<K, V> = ReturnType<typeof createBatcher<K, V>>;\n"],"mappings":"4EAgEA,SAAgB,EAAoB,EAA4B,CAC9D,GAAM,CAAE,UAAU,GAAI,UAAS,iBAAgB,OAAQ,EAAW,GAAM,EAElE,EAA6B,CAAC,EAC9B,EAAa,IAAI,gBACnB,EAAY,GACZ,EAAqD,KACrD,EAAW,GAEf,SAAS,GAAc,CACrB,GAAI,EAAM,SAAW,EAAG,OAExB,IAAM,EAAQ,EAAM,OAAO,EAAG,CAAO,EAGjC,EAAM,OAAS,GAAG,EAAS,EAE/B,IAAM,EAAO,EAAM,IAAK,GAAS,EAAK,GAAG,EAKzC,GAAI,CACE,EACF,EAAoB,CAAI,CAAC,CACtB,KAAM,GAAY,CACjB,GAAI,EAAQ,SAAW,EAAM,OAAQ,CACnC,IAAM,EAAM,IAAI,EACd,sCAAsC,EAAQ,OAAO,eAAe,EAAM,OAAO,MACnF,EAEA,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,EAEzC,MACF,CAEA,EAAM,SAAS,EAAM,IAAM,CACzB,IAAM,EAAI,EAAQ,GAEd,EAAE,SAAW,YACf,EAAK,QAAQ,EAAE,KAAK,EAEpB,EAAK,OAAO,EAAE,MAAM,CAExB,CAAC,CACH,CAAC,CAAC,CACD,MAAO,GAAiB,CACvB,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CAAC,EAEH,EAAa,CAAI,CAAC,CACf,KAAM,GAAY,CACjB,GAAI,EAAQ,SAAW,EAAM,OAAQ,CACnC,IAAM,EAAM,IAAI,EACd,+BAA+B,EAAQ,OAAO,eAAe,EAAM,OAAO,MAC5E,EAEA,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,EAEzC,MACF,CAEA,EAAM,SAAS,EAAM,IAAM,EAAK,QAAQ,EAAQ,EAAE,CAAC,CACrD,CAAC,CAAC,CACD,MAAO,GAAiB,CACvB,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CAAC,CAEP,OAAS,EAAK,CACZ,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CACF,CAEA,SAAS,GAAiB,CACpB,IAEJ,EAAY,GAER,IAAa,EACf,mBAAqB,CACnB,EAAY,GACZ,EAAM,CACR,CAAC,EAED,EAAe,eAAiB,CAC9B,EAAe,KACf,EAAY,GACZ,EAAM,CACR,EAAG,CAAQ,EAEf,CAEA,MAAO,CACL,IAAI,gBAA8B,CAChC,OAAO,EAAW,MACpB,EAMA,SAAgB,CACd,GAAI,EAAU,OAEd,EAAW,GAEP,IAAiB,OACnB,aAAa,CAAY,EACzB,EAAe,MAGjB,IAAM,EAAM,IAAI,EAAqB,SAAS,EAE9C,IAAK,IAAM,KAAQ,EAAM,OAAO,CAAC,EAAG,EAAK,OAAO,CAAG,EAEnD,EAAY,GACZ,EAAW,MAAM,CACnB,EAEA,IAAI,UAAoB,CACtB,OAAO,CACT,EAEA,KAAK,EAAoB,CAGvB,OAFI,EAAiB,QAAQ,OAAO,IAAI,EAAqB,SAAS,CAAC,EAEhE,IAAI,SAAY,EAAK,IAAQ,CAClC,EAAM,KAAK,CAAE,MAAK,OAAQ,EAAK,QAAS,CAAI,CAAC,EAEzC,EAAM,QAAU,GAGd,IAAiB,OACnB,aAAa,CAAY,EACzB,EAAe,MAGjB,EAAY,GACZ,EAAM,GAEN,EAAS,CAEb,CAAC,CACH,EAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF,CACF"}
package/dist/focus.cjs DELETED
@@ -1,2 +0,0 @@
1
- const e=require("./_dev.cjs");function t(t,n){let r=[],i=typeof document<`u`,a=typeof window<`u`;if(!i&&!a&&e.warn(`bindRefetch() called with no document/window in scope — no listeners attached`),i){let e=()=>{document.visibilityState===`visible`&&t.refetchStale()};document.addEventListener(`visibilitychange`,e),r.push(()=>document.removeEventListener(`visibilitychange`,e))}if(a){let e=()=>t.refetchStale();window.addEventListener(`online`,e),r.push(()=>window.removeEventListener(`online`,e))}let o=()=>{for(let e of r)e();r.length=0};return n?.signal?.addEventListener(`abort`,o,{once:!0}),o}exports.bindRefetch=t;
2
- //# sourceMappingURL=focus.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"focus.cjs","names":[],"sources":["../src/focus.ts"],"sourcesContent":["import { warn } from './_dev';\n\n/**\n * Binds a query client's refetchStale() method to browser lifecycle events\n * (document visibility and network reconnect), returning a disposer.\n *\n * This is the explicit opt-in replacement for the removed `refetchOnFocus` and\n * `refetchOnReconnect` QueryClientOptions, giving full control over when and\n * where listeners are attached without hidden global side-effects.\n *\n * In a non-browser environment (no `document`/`window`, e.g. SSR) this is a no-op — a dev\n * warning is logged and the returned disposer does nothing when called.\n *\n * @example\n * ```ts\n * const qc = createQuery({ staleTime: 30_000 });\n * const unbind = bindRefetch(qc);\n * // later — e.g. in a component teardown or route leave:\n * unbind();\n * ```\n */\nexport function bindRefetch(qc: { refetchStale(): void }, opts?: { signal?: AbortSignal }): () => void {\n const disposers: Array<() => void> = [];\n const hasDocument = typeof document !== 'undefined';\n const hasWindow = typeof window !== 'undefined';\n\n if (!hasDocument && !hasWindow) {\n warn('bindRefetch() called with no document/window in scope — no listeners attached');\n }\n\n if (hasDocument) {\n const handler = () => {\n if (document.visibilityState === 'visible') qc.refetchStale();\n };\n\n document.addEventListener('visibilitychange', handler);\n disposers.push(() => document.removeEventListener('visibilitychange', handler));\n }\n\n if (hasWindow) {\n const handler = () => qc.refetchStale();\n\n window.addEventListener('online', handler);\n disposers.push(() => window.removeEventListener('online', handler));\n }\n\n const unbind = () => {\n for (const dispose of disposers) dispose();\n disposers.length = 0;\n };\n\n opts?.signal?.addEventListener('abort', unbind, { once: true });\n\n return unbind;\n}\n"],"mappings":"8BAqBA,SAAgB,EAAY,EAA8B,EAA6C,CACrG,IAAM,EAA+B,CAAC,EAChC,EAAc,OAAO,SAAa,IAClC,EAAY,OAAO,OAAW,IAMpC,GAJI,CAAC,GAAe,CAAC,GACnB,EAAA,KAAK,+EAA+E,EAGlF,EAAa,CACf,IAAM,MAAgB,CAChB,SAAS,kBAAoB,WAAW,EAAG,aAAa,CAC9D,EAEA,SAAS,iBAAiB,mBAAoB,CAAO,EACrD,EAAU,SAAW,SAAS,oBAAoB,mBAAoB,CAAO,CAAC,CAChF,CAEA,GAAI,EAAW,CACb,IAAM,MAAgB,EAAG,aAAa,EAEtC,OAAO,iBAAiB,SAAU,CAAO,EACzC,EAAU,SAAW,OAAO,oBAAoB,SAAU,CAAO,CAAC,CACpE,CAEA,IAAM,MAAe,CACnB,IAAK,IAAM,KAAW,EAAW,EAAQ,EACzC,EAAU,OAAS,CACrB,EAIA,OAFA,GAAM,QAAQ,iBAAiB,QAAS,EAAQ,CAAE,KAAM,EAAK,CAAC,EAEvD,CACT"}
package/dist/focus.d.ts DELETED
@@ -1,25 +0,0 @@
1
- /**
2
- * Binds a query client's refetchStale() method to browser lifecycle events
3
- * (document visibility and network reconnect), returning a disposer.
4
- *
5
- * This is the explicit opt-in replacement for the removed `refetchOnFocus` and
6
- * `refetchOnReconnect` QueryClientOptions, giving full control over when and
7
- * where listeners are attached without hidden global side-effects.
8
- *
9
- * In a non-browser environment (no `document`/`window`, e.g. SSR) this is a no-op — a dev
10
- * warning is logged and the returned disposer does nothing when called.
11
- *
12
- * @example
13
- * ```ts
14
- * const qc = createQuery({ staleTime: 30_000 });
15
- * const unbind = bindRefetch(qc);
16
- * // later — e.g. in a component teardown or route leave:
17
- * unbind();
18
- * ```
19
- */
20
- export declare function bindRefetch(qc: {
21
- refetchStale(): void;
22
- }, opts?: {
23
- signal?: AbortSignal;
24
- }): () => void;
25
- //# sourceMappingURL=focus.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"focus.d.ts","sourceRoot":"","sources":["../src/focus.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE;IAAE,YAAY,IAAI,IAAI,CAAA;CAAE,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAAG,MAAM,IAAI,CAiCrG"}
package/dist/focus.js DELETED
@@ -1,2 +0,0 @@
1
- import{warn as e}from"./_dev.js";function t(t,n){let r=[],i=typeof document<`u`,a=typeof window<`u`;if(!i&&!a&&e(`bindRefetch() called with no document/window in scope — no listeners attached`),i){let e=()=>{document.visibilityState===`visible`&&t.refetchStale()};document.addEventListener(`visibilitychange`,e),r.push(()=>document.removeEventListener(`visibilitychange`,e))}if(a){let e=()=>t.refetchStale();window.addEventListener(`online`,e),r.push(()=>window.removeEventListener(`online`,e))}let o=()=>{for(let e of r)e();r.length=0};return n?.signal?.addEventListener(`abort`,o,{once:!0}),o}export{t as bindRefetch};
2
- //# sourceMappingURL=focus.js.map
package/dist/focus.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"focus.js","names":[],"sources":["../src/focus.ts"],"sourcesContent":["import { warn } from './_dev';\n\n/**\n * Binds a query client's refetchStale() method to browser lifecycle events\n * (document visibility and network reconnect), returning a disposer.\n *\n * This is the explicit opt-in replacement for the removed `refetchOnFocus` and\n * `refetchOnReconnect` QueryClientOptions, giving full control over when and\n * where listeners are attached without hidden global side-effects.\n *\n * In a non-browser environment (no `document`/`window`, e.g. SSR) this is a no-op — a dev\n * warning is logged and the returned disposer does nothing when called.\n *\n * @example\n * ```ts\n * const qc = createQuery({ staleTime: 30_000 });\n * const unbind = bindRefetch(qc);\n * // later — e.g. in a component teardown or route leave:\n * unbind();\n * ```\n */\nexport function bindRefetch(qc: { refetchStale(): void }, opts?: { signal?: AbortSignal }): () => void {\n const disposers: Array<() => void> = [];\n const hasDocument = typeof document !== 'undefined';\n const hasWindow = typeof window !== 'undefined';\n\n if (!hasDocument && !hasWindow) {\n warn('bindRefetch() called with no document/window in scope — no listeners attached');\n }\n\n if (hasDocument) {\n const handler = () => {\n if (document.visibilityState === 'visible') qc.refetchStale();\n };\n\n document.addEventListener('visibilitychange', handler);\n disposers.push(() => document.removeEventListener('visibilitychange', handler));\n }\n\n if (hasWindow) {\n const handler = () => qc.refetchStale();\n\n window.addEventListener('online', handler);\n disposers.push(() => window.removeEventListener('online', handler));\n }\n\n const unbind = () => {\n for (const dispose of disposers) dispose();\n disposers.length = 0;\n };\n\n opts?.signal?.addEventListener('abort', unbind, { once: true });\n\n return unbind;\n}\n"],"mappings":"iCAqBA,SAAgB,EAAY,EAA8B,EAA6C,CACrG,IAAM,EAA+B,CAAC,EAChC,EAAc,OAAO,SAAa,IAClC,EAAY,OAAO,OAAW,IAMpC,GAJI,CAAC,GAAe,CAAC,GACnB,EAAK,+EAA+E,EAGlF,EAAa,CACf,IAAM,MAAgB,CAChB,SAAS,kBAAoB,WAAW,EAAG,aAAa,CAC9D,EAEA,SAAS,iBAAiB,mBAAoB,CAAO,EACrD,EAAU,SAAW,SAAS,oBAAoB,mBAAoB,CAAO,CAAC,CAChF,CAEA,GAAI,EAAW,CACb,IAAM,MAAgB,EAAG,aAAa,EAEtC,OAAO,iBAAiB,SAAU,CAAO,EACzC,EAAU,SAAW,OAAO,oBAAoB,SAAU,CAAO,CAAC,CACpE,CAEA,IAAM,MAAe,CACnB,IAAK,IAAM,KAAW,EAAW,EAAQ,EACzC,EAAU,OAAS,CACrB,EAIA,OAFA,GAAM,QAAQ,iBAAiB,QAAS,EAAQ,CAAE,KAAM,EAAK,CAAC,EAEvD,CACT"}
package/dist/mutation.cjs DELETED
@@ -1,2 +0,0 @@
1
- const e=require("./errors.cjs"),t=require("./transport.cjs"),n=require("./retry.cjs");let r=require("@vielzeug/arsenal");var i=Object.freeze({data:void 0,error:null,isFetching:!1,isLoading:!0,status:`loading`,updatedAt:void 0});function a(a,o){let s=i,c=0,l=null,u=new Set,d=!1,f=null;function p(){u.forEach(e=>e())}async function m(e){try{await e?.()}catch(e){if(o?.onCallbackError)try{o.onCallbackError(e instanceof Error?e:Error(String(e)))}catch{}}}return{async cancel(){let e=l;e&&(e.controller.abort(),await e.promise.catch(()=>{}))},dispose(){d||(d=!0,l?.controller.abort(),l=null,u.clear())},get disposed(){return d},async mutate(u,f){if(d)throw new e.CourierDisposedError(`Mutation`);let h=new AbortController,g=f?.signal?t.anySignal(f.signal,h.signal):h.signal,_=++c;s={data:void 0,error:null,isFetching:!0,isLoading:!0,status:`loading`,updatedAt:void 0},p();let v={controller:h,promise:null};l=v;let y=(async()=>{try{let e=await(0,r.retry)(()=>a(u,g),{delay:e=>n.resolveRetryDelay(e,o?.delay),shouldRetry:o?.shouldRetry,signal:g,times:o?.times??1});return _===c&&(s={data:e,error:null,isFetching:!1,isLoading:!1,status:`success`,updatedAt:Date.now()},p()),await m(()=>o?.onSuccess?.(e,u)),await m(()=>o?.onFinally?.(u)),await m(()=>o?.onSettled?.({data:e,status:`success`,variables:u})),e}catch(e){let t=e instanceof Error?e:Error(String(e)),n=g.aborted||(0,r.isAbortError)(t);throw _===c&&(s=n?i:{data:void 0,error:t,isFetching:!1,isLoading:!1,status:`error`,updatedAt:Date.now()},p()),n||await m(()=>o?.onError?.(t,u)),await m(()=>o?.onFinally?.(u)),await m(()=>o?.onSettled?.(n?{status:`aborted`,variables:u}:{error:t,status:`error`,variables:u})),t}finally{l?.controller===h&&(l=null)}})();return v.promise=y,y},peek(){return s},reset(){s=i,p()},get store(){return f??={peek:()=>s,subscribe:e=>d?()=>{}:(u.add(e),()=>u.delete(e))}},subscribe(e){return d?()=>{}:(u.add(e),()=>u.delete(e))},[Symbol.dispose](){this.dispose()}}}exports.createMutation=a;
2
- //# sourceMappingURL=mutation.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mutation.cjs","names":[],"sources":["../src/mutation.ts"],"sourcesContent":["import { isAbortError, retry } from '@vielzeug/arsenal';\n\nimport type { RetryOptions } from './retry';\nimport type { MutationState, SyncStore, Unsubscribe } from './types';\n\nimport { CourierDisposedError } from './errors';\nimport { resolveRetryDelay } from './retry';\nimport { anySignal } from './transport';\n\nconst IDLE_STATE: MutationState<unknown> = Object.freeze({\n data: undefined,\n error: null,\n isFetching: false,\n isLoading: true,\n status: 'loading',\n updatedAt: undefined,\n});\n\n/**\n * Discriminated union result passed to `onSettled`.\n * Switch on `result.status` to handle each outcome exhaustively.\n */\nexport type SettledResult<TData, TVariables> =\n | { readonly data: TData; readonly status: 'success'; readonly variables: TVariables }\n | { readonly error: Error; readonly status: 'error'; readonly variables: TVariables }\n | { readonly status: 'aborted'; readonly variables: TVariables };\n\nexport type MutationOptions<TData = unknown, TVariables = void> = RetryOptions & {\n /**\n * Called when a lifecycle callback (`onSuccess`, `onError`, `onSettled`, or `onFinally`) throws.\n * Does not affect the `mutate()` result or re-throw. Optional — for development/debugging.\n */\n onCallbackError?: (error: Error) => void;\n /**\n * Called after a failed run, before `mutate()` rejects.\n * Not called when the mutation was aborted (use `onSettled` if you need abort awareness).\n */\n onError?: (error: Error, variables: TVariables) => void | Promise<void>;\n /**\n * Called after every run regardless of outcome, including aborts.\n * Use for cleanup patterns that do not need to inspect the result.\n * Runs after `onSuccess` / `onError` and before `onSettled`.\n */\n onFinally?: (variables: TVariables) => void | Promise<void>;\n /**\n * Called after every run regardless of outcome, including aborts.\n * Switch on `result.status` to handle `'success'`, `'error'`, or `'aborted'` exhaustively.\n */\n onSettled?: (result: SettledResult<TData, TVariables>) => void | Promise<void>;\n /** Called after a successful run, before `mutate()` resolves. */\n onSuccess?: (data: TData, variables: TVariables) => void | Promise<void>;\n};\n\nexport type MutationFn<TData, TVariables = void> = (input: TVariables, signal: AbortSignal) => Promise<TData>;\n\n/**\n * Wraps an async function (typically a write operation — create/update/delete) with\n * loading/success/error state tracking, retries, cancellation, and lifecycle callbacks.\n *\n * @example\n * ```ts\n * const createUser = createMutation(\n * (input: NewUser, signal) => api.post<User>('/users', { body: input, signal }),\n * { onSuccess: (user) => console.log('created', user.id) },\n * );\n *\n * const unsub = createUser.subscribe(() => console.log(createUser.peek()));\n * const user = await createUser.mutate({ name: 'Ada' });\n *\n * // later:\n * createUser.dispose();\n * ```\n */\nexport function createMutation<TData, TVariables = void>(\n mutationFn: MutationFn<TData, TVariables>,\n mutOpts?: MutationOptions<TData, TVariables>,\n): Mutation<TData, TVariables> {\n let snap: MutationState<TData> = IDLE_STATE as MutationState<TData>;\n let currentRun = 0;\n let activeRun: { controller: AbortController; promise: Promise<unknown> } | null = null;\n const observers = new Set<() => void>();\n let disposed = false;\n let cachedStore: SyncStore<MutationState<TData>> | null = null;\n\n function notifyObservers(): void {\n observers.forEach((l) => l());\n }\n\n async function safeCall(fn: (() => void | Promise<void>) | undefined): Promise<void> {\n try {\n await fn?.();\n } catch (err) {\n if (mutOpts?.onCallbackError) {\n try {\n mutOpts.onCallbackError(err instanceof Error ? err : new Error(String(err)));\n } catch {\n // Silently ignore errors in error handler itself.\n }\n }\n }\n }\n\n return {\n async cancel(): Promise<void> {\n const run = activeRun;\n\n if (!run) return;\n\n run.controller.abort();\n await run.promise.catch(() => {});\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n activeRun?.controller.abort();\n activeRun = null;\n observers.clear();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n /**\n * Execute the mutation with the given variables.\n *\n * Concurrent calls are allowed. State updates (`isFetching`, `data`, `error`) always\n * reflect the **latest** `mutate()` call. Lifecycle callbacks (`onSuccess`, `onError`,\n * `onSettled`) fire for **every** call independently — not just the last one.\n * Cancel earlier calls via `mutation.cancel()` if you need only-last-wins callback semantics.\n *\n * @throws {CourierDisposedError} If called after `dispose()`.\n */\n async mutate(variables: TVariables, callOpts?: { signal?: AbortSignal }): Promise<TData> {\n if (disposed) throw new CourierDisposedError('Mutation');\n\n const localController = new AbortController();\n\n const signal = callOpts?.signal ? anySignal(callOpts.signal, localController.signal)! : localController.signal;\n const run = ++currentRun;\n\n snap = {\n data: undefined,\n error: null,\n isFetching: true,\n isLoading: true,\n status: 'loading',\n updatedAt: undefined,\n };\n notifyObservers();\n\n const pendingRun = { controller: localController, promise: null as unknown as Promise<TData> };\n\n activeRun = pendingRun;\n\n const operation = (async () => {\n try {\n const data = await retry(() => mutationFn(variables, signal), {\n delay: (attempt) => resolveRetryDelay(attempt, mutOpts?.delay),\n shouldRetry: mutOpts?.shouldRetry,\n signal,\n times: mutOpts?.times ?? 1,\n });\n\n if (run === currentRun) {\n snap = { data, error: null, isFetching: false, isLoading: false, status: 'success', updatedAt: Date.now() };\n notifyObservers();\n }\n\n await safeCall(() => mutOpts?.onSuccess?.(data, variables));\n await safeCall(() => mutOpts?.onFinally?.(variables));\n await safeCall(() => mutOpts?.onSettled?.({ data, status: 'success', variables }));\n\n return data;\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n const isAborted = signal.aborted || isAbortError(error);\n\n if (run === currentRun) {\n snap = isAborted\n ? (IDLE_STATE as MutationState<TData>)\n : { data: undefined, error, isFetching: false, isLoading: false, status: 'error', updatedAt: Date.now() };\n notifyObservers();\n }\n\n if (!isAborted) {\n await safeCall(() => mutOpts?.onError?.(error, variables));\n }\n\n await safeCall(() => mutOpts?.onFinally?.(variables));\n await safeCall(() =>\n mutOpts?.onSettled?.(isAborted ? { status: 'aborted', variables } : { error, status: 'error', variables }),\n );\n\n throw error;\n } finally {\n if (activeRun?.controller === localController) {\n activeRun = null;\n }\n }\n })();\n\n pendingRun.promise = operation;\n\n return operation;\n },\n\n /**\n * Read the current mutation state snapshot.\n * For framework integrations prefer `peek()` + `subscribe()` directly.\n */\n peek(): MutationState<TData> {\n return snap;\n },\n\n reset(): void {\n snap = IDLE_STATE as MutationState<TData>;\n notifyObservers();\n },\n\n /**\n * A `SyncStore` view of the mutation state — identical to `{ peek, subscribe }` on this object.\n * Provided for framework integrations that accept a `SyncStore` directly:\n * - React: `useSyncExternalStore(mutation.subscribe, mutation.peek)`\n * - Svelte: `readable(mutation.peek(), mutation.subscribe)`\n */\n get store(): SyncStore<MutationState<TData>> {\n return (cachedStore ??= {\n peek: () => snap,\n subscribe: (cb) => {\n if (disposed) return () => {};\n\n observers.add(cb);\n\n return () => observers.delete(cb);\n },\n });\n },\n\n subscribe(onStoreChange: () => void): Unsubscribe {\n if (disposed) return () => {};\n\n observers.add(onStoreChange);\n\n return () => observers.delete(onStoreChange);\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n };\n}\n\nexport interface Mutation<TData, TVariables = void> {\n [Symbol.dispose](): void;\n cancel(): Promise<void>;\n dispose(): void;\n get disposed(): boolean;\n mutate(variables: TVariables, callOpts?: { signal?: AbortSignal }): Promise<TData>;\n peek(): MutationState<TData>;\n reset(): void;\n /**\n * `SyncStore` adapter — equivalent to `{ peek, subscribe }` on this object.\n * For React: `useSyncExternalStore(mutation.subscribe, mutation.peek)`\n */\n get store(): SyncStore<MutationState<TData>>;\n subscribe(onStoreChange: () => void): Unsubscribe;\n}\n"],"mappings":"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"}
@@ -1,81 +0,0 @@
1
- import type { RetryOptions } from './retry';
2
- import type { MutationState, SyncStore, Unsubscribe } from './types';
3
- /**
4
- * Discriminated union result passed to `onSettled`.
5
- * Switch on `result.status` to handle each outcome exhaustively.
6
- */
7
- export type SettledResult<TData, TVariables> = {
8
- readonly data: TData;
9
- readonly status: 'success';
10
- readonly variables: TVariables;
11
- } | {
12
- readonly error: Error;
13
- readonly status: 'error';
14
- readonly variables: TVariables;
15
- } | {
16
- readonly status: 'aborted';
17
- readonly variables: TVariables;
18
- };
19
- export type MutationOptions<TData = unknown, TVariables = void> = RetryOptions & {
20
- /**
21
- * Called when a lifecycle callback (`onSuccess`, `onError`, `onSettled`, or `onFinally`) throws.
22
- * Does not affect the `mutate()` result or re-throw. Optional — for development/debugging.
23
- */
24
- onCallbackError?: (error: Error) => void;
25
- /**
26
- * Called after a failed run, before `mutate()` rejects.
27
- * Not called when the mutation was aborted (use `onSettled` if you need abort awareness).
28
- */
29
- onError?: (error: Error, variables: TVariables) => void | Promise<void>;
30
- /**
31
- * Called after every run regardless of outcome, including aborts.
32
- * Use for cleanup patterns that do not need to inspect the result.
33
- * Runs after `onSuccess` / `onError` and before `onSettled`.
34
- */
35
- onFinally?: (variables: TVariables) => void | Promise<void>;
36
- /**
37
- * Called after every run regardless of outcome, including aborts.
38
- * Switch on `result.status` to handle `'success'`, `'error'`, or `'aborted'` exhaustively.
39
- */
40
- onSettled?: (result: SettledResult<TData, TVariables>) => void | Promise<void>;
41
- /** Called after a successful run, before `mutate()` resolves. */
42
- onSuccess?: (data: TData, variables: TVariables) => void | Promise<void>;
43
- };
44
- export type MutationFn<TData, TVariables = void> = (input: TVariables, signal: AbortSignal) => Promise<TData>;
45
- /**
46
- * Wraps an async function (typically a write operation — create/update/delete) with
47
- * loading/success/error state tracking, retries, cancellation, and lifecycle callbacks.
48
- *
49
- * @example
50
- * ```ts
51
- * const createUser = createMutation(
52
- * (input: NewUser, signal) => api.post<User>('/users', { body: input, signal }),
53
- * { onSuccess: (user) => console.log('created', user.id) },
54
- * );
55
- *
56
- * const unsub = createUser.subscribe(() => console.log(createUser.peek()));
57
- * const user = await createUser.mutate({ name: 'Ada' });
58
- *
59
- * // later:
60
- * createUser.dispose();
61
- * ```
62
- */
63
- export declare function createMutation<TData, TVariables = void>(mutationFn: MutationFn<TData, TVariables>, mutOpts?: MutationOptions<TData, TVariables>): Mutation<TData, TVariables>;
64
- export interface Mutation<TData, TVariables = void> {
65
- [Symbol.dispose](): void;
66
- cancel(): Promise<void>;
67
- dispose(): void;
68
- get disposed(): boolean;
69
- mutate(variables: TVariables, callOpts?: {
70
- signal?: AbortSignal;
71
- }): Promise<TData>;
72
- peek(): MutationState<TData>;
73
- reset(): void;
74
- /**
75
- * `SyncStore` adapter — equivalent to `{ peek, subscribe }` on this object.
76
- * For React: `useSyncExternalStore(mutation.subscribe, mutation.peek)`
77
- */
78
- get store(): SyncStore<MutationState<TData>>;
79
- subscribe(onStoreChange: () => void): Unsubscribe;
80
- }
81
- //# sourceMappingURL=mutation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mutation.d.ts","sourceRoot":"","sources":["../src/mutation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAerE;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,KAAK,EAAE,UAAU,IACvC;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAA;CAAE,GACpF;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAA;CAAE,GACnF;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAA;CAAE,CAAC;AAEnE,MAAM,MAAM,eAAe,CAAC,KAAK,GAAG,OAAO,EAAE,UAAU,GAAG,IAAI,IAAI,YAAY,GAAG;IAC/E;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACzC;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,iEAAiE;IACjE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1E,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;AAE9G;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,EACrD,UAAU,EAAE,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,EACzC,OAAO,CAAC,EAAE,eAAe,CAAC,KAAK,EAAE,UAAU,CAAC,GAC3C,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAiL7B;AAED,MAAM,WAAW,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAChD,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,OAAO,IAAI,IAAI,CAAC;IAChB,IAAI,QAAQ,IAAI,OAAO,CAAC;IACxB,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACnF,IAAI,IAAI,aAAa,CAAC,KAAK,CAAC,CAAC;IAC7B,KAAK,IAAI,IAAI,CAAC;IACd;;;OAGG;IACH,IAAI,KAAK,IAAI,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7C,SAAS,CAAC,aAAa,EAAE,MAAM,IAAI,GAAG,WAAW,CAAC;CACnD"}
package/dist/mutation.js DELETED
@@ -1,2 +0,0 @@
1
- import{CourierDisposedError as e}from"./errors.js";import{anySignal as t}from"./transport.js";import{resolveRetryDelay as n}from"./retry.js";import{isAbortError as r,retry as i}from"@vielzeug/arsenal";var a=Object.freeze({data:void 0,error:null,isFetching:!1,isLoading:!0,status:`loading`,updatedAt:void 0});function o(o,s){let c=a,l=0,u=null,d=new Set,f=!1,p=null;function m(){d.forEach(e=>e())}async function h(e){try{await e?.()}catch(e){if(s?.onCallbackError)try{s.onCallbackError(e instanceof Error?e:Error(String(e)))}catch{}}}return{async cancel(){let e=u;e&&(e.controller.abort(),await e.promise.catch(()=>{}))},dispose(){f||(f=!0,u?.controller.abort(),u=null,d.clear())},get disposed(){return f},async mutate(d,p){if(f)throw new e(`Mutation`);let g=new AbortController,_=p?.signal?t(p.signal,g.signal):g.signal,v=++l;c={data:void 0,error:null,isFetching:!0,isLoading:!0,status:`loading`,updatedAt:void 0},m();let y={controller:g,promise:null};u=y;let b=(async()=>{try{let e=await i(()=>o(d,_),{delay:e=>n(e,s?.delay),shouldRetry:s?.shouldRetry,signal:_,times:s?.times??1});return v===l&&(c={data:e,error:null,isFetching:!1,isLoading:!1,status:`success`,updatedAt:Date.now()},m()),await h(()=>s?.onSuccess?.(e,d)),await h(()=>s?.onFinally?.(d)),await h(()=>s?.onSettled?.({data:e,status:`success`,variables:d})),e}catch(e){let t=e instanceof Error?e:Error(String(e)),n=_.aborted||r(t);throw v===l&&(c=n?a:{data:void 0,error:t,isFetching:!1,isLoading:!1,status:`error`,updatedAt:Date.now()},m()),n||await h(()=>s?.onError?.(t,d)),await h(()=>s?.onFinally?.(d)),await h(()=>s?.onSettled?.(n?{status:`aborted`,variables:d}:{error:t,status:`error`,variables:d})),t}finally{u?.controller===g&&(u=null)}})();return y.promise=b,b},peek(){return c},reset(){c=a,m()},get store(){return p??={peek:()=>c,subscribe:e=>f?()=>{}:(d.add(e),()=>d.delete(e))}},subscribe(e){return f?()=>{}:(d.add(e),()=>d.delete(e))},[Symbol.dispose](){this.dispose()}}}export{o as createMutation};
2
- //# sourceMappingURL=mutation.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mutation.js","names":[],"sources":["../src/mutation.ts"],"sourcesContent":["import { isAbortError, retry } from '@vielzeug/arsenal';\n\nimport type { RetryOptions } from './retry';\nimport type { MutationState, SyncStore, Unsubscribe } from './types';\n\nimport { CourierDisposedError } from './errors';\nimport { resolveRetryDelay } from './retry';\nimport { anySignal } from './transport';\n\nconst IDLE_STATE: MutationState<unknown> = Object.freeze({\n data: undefined,\n error: null,\n isFetching: false,\n isLoading: true,\n status: 'loading',\n updatedAt: undefined,\n});\n\n/**\n * Discriminated union result passed to `onSettled`.\n * Switch on `result.status` to handle each outcome exhaustively.\n */\nexport type SettledResult<TData, TVariables> =\n | { readonly data: TData; readonly status: 'success'; readonly variables: TVariables }\n | { readonly error: Error; readonly status: 'error'; readonly variables: TVariables }\n | { readonly status: 'aborted'; readonly variables: TVariables };\n\nexport type MutationOptions<TData = unknown, TVariables = void> = RetryOptions & {\n /**\n * Called when a lifecycle callback (`onSuccess`, `onError`, `onSettled`, or `onFinally`) throws.\n * Does not affect the `mutate()` result or re-throw. Optional — for development/debugging.\n */\n onCallbackError?: (error: Error) => void;\n /**\n * Called after a failed run, before `mutate()` rejects.\n * Not called when the mutation was aborted (use `onSettled` if you need abort awareness).\n */\n onError?: (error: Error, variables: TVariables) => void | Promise<void>;\n /**\n * Called after every run regardless of outcome, including aborts.\n * Use for cleanup patterns that do not need to inspect the result.\n * Runs after `onSuccess` / `onError` and before `onSettled`.\n */\n onFinally?: (variables: TVariables) => void | Promise<void>;\n /**\n * Called after every run regardless of outcome, including aborts.\n * Switch on `result.status` to handle `'success'`, `'error'`, or `'aborted'` exhaustively.\n */\n onSettled?: (result: SettledResult<TData, TVariables>) => void | Promise<void>;\n /** Called after a successful run, before `mutate()` resolves. */\n onSuccess?: (data: TData, variables: TVariables) => void | Promise<void>;\n};\n\nexport type MutationFn<TData, TVariables = void> = (input: TVariables, signal: AbortSignal) => Promise<TData>;\n\n/**\n * Wraps an async function (typically a write operation — create/update/delete) with\n * loading/success/error state tracking, retries, cancellation, and lifecycle callbacks.\n *\n * @example\n * ```ts\n * const createUser = createMutation(\n * (input: NewUser, signal) => api.post<User>('/users', { body: input, signal }),\n * { onSuccess: (user) => console.log('created', user.id) },\n * );\n *\n * const unsub = createUser.subscribe(() => console.log(createUser.peek()));\n * const user = await createUser.mutate({ name: 'Ada' });\n *\n * // later:\n * createUser.dispose();\n * ```\n */\nexport function createMutation<TData, TVariables = void>(\n mutationFn: MutationFn<TData, TVariables>,\n mutOpts?: MutationOptions<TData, TVariables>,\n): Mutation<TData, TVariables> {\n let snap: MutationState<TData> = IDLE_STATE as MutationState<TData>;\n let currentRun = 0;\n let activeRun: { controller: AbortController; promise: Promise<unknown> } | null = null;\n const observers = new Set<() => void>();\n let disposed = false;\n let cachedStore: SyncStore<MutationState<TData>> | null = null;\n\n function notifyObservers(): void {\n observers.forEach((l) => l());\n }\n\n async function safeCall(fn: (() => void | Promise<void>) | undefined): Promise<void> {\n try {\n await fn?.();\n } catch (err) {\n if (mutOpts?.onCallbackError) {\n try {\n mutOpts.onCallbackError(err instanceof Error ? err : new Error(String(err)));\n } catch {\n // Silently ignore errors in error handler itself.\n }\n }\n }\n }\n\n return {\n async cancel(): Promise<void> {\n const run = activeRun;\n\n if (!run) return;\n\n run.controller.abort();\n await run.promise.catch(() => {});\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n activeRun?.controller.abort();\n activeRun = null;\n observers.clear();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n /**\n * Execute the mutation with the given variables.\n *\n * Concurrent calls are allowed. State updates (`isFetching`, `data`, `error`) always\n * reflect the **latest** `mutate()` call. Lifecycle callbacks (`onSuccess`, `onError`,\n * `onSettled`) fire for **every** call independently — not just the last one.\n * Cancel earlier calls via `mutation.cancel()` if you need only-last-wins callback semantics.\n *\n * @throws {CourierDisposedError} If called after `dispose()`.\n */\n async mutate(variables: TVariables, callOpts?: { signal?: AbortSignal }): Promise<TData> {\n if (disposed) throw new CourierDisposedError('Mutation');\n\n const localController = new AbortController();\n\n const signal = callOpts?.signal ? anySignal(callOpts.signal, localController.signal)! : localController.signal;\n const run = ++currentRun;\n\n snap = {\n data: undefined,\n error: null,\n isFetching: true,\n isLoading: true,\n status: 'loading',\n updatedAt: undefined,\n };\n notifyObservers();\n\n const pendingRun = { controller: localController, promise: null as unknown as Promise<TData> };\n\n activeRun = pendingRun;\n\n const operation = (async () => {\n try {\n const data = await retry(() => mutationFn(variables, signal), {\n delay: (attempt) => resolveRetryDelay(attempt, mutOpts?.delay),\n shouldRetry: mutOpts?.shouldRetry,\n signal,\n times: mutOpts?.times ?? 1,\n });\n\n if (run === currentRun) {\n snap = { data, error: null, isFetching: false, isLoading: false, status: 'success', updatedAt: Date.now() };\n notifyObservers();\n }\n\n await safeCall(() => mutOpts?.onSuccess?.(data, variables));\n await safeCall(() => mutOpts?.onFinally?.(variables));\n await safeCall(() => mutOpts?.onSettled?.({ data, status: 'success', variables }));\n\n return data;\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n const isAborted = signal.aborted || isAbortError(error);\n\n if (run === currentRun) {\n snap = isAborted\n ? (IDLE_STATE as MutationState<TData>)\n : { data: undefined, error, isFetching: false, isLoading: false, status: 'error', updatedAt: Date.now() };\n notifyObservers();\n }\n\n if (!isAborted) {\n await safeCall(() => mutOpts?.onError?.(error, variables));\n }\n\n await safeCall(() => mutOpts?.onFinally?.(variables));\n await safeCall(() =>\n mutOpts?.onSettled?.(isAborted ? { status: 'aborted', variables } : { error, status: 'error', variables }),\n );\n\n throw error;\n } finally {\n if (activeRun?.controller === localController) {\n activeRun = null;\n }\n }\n })();\n\n pendingRun.promise = operation;\n\n return operation;\n },\n\n /**\n * Read the current mutation state snapshot.\n * For framework integrations prefer `peek()` + `subscribe()` directly.\n */\n peek(): MutationState<TData> {\n return snap;\n },\n\n reset(): void {\n snap = IDLE_STATE as MutationState<TData>;\n notifyObservers();\n },\n\n /**\n * A `SyncStore` view of the mutation state — identical to `{ peek, subscribe }` on this object.\n * Provided for framework integrations that accept a `SyncStore` directly:\n * - React: `useSyncExternalStore(mutation.subscribe, mutation.peek)`\n * - Svelte: `readable(mutation.peek(), mutation.subscribe)`\n */\n get store(): SyncStore<MutationState<TData>> {\n return (cachedStore ??= {\n peek: () => snap,\n subscribe: (cb) => {\n if (disposed) return () => {};\n\n observers.add(cb);\n\n return () => observers.delete(cb);\n },\n });\n },\n\n subscribe(onStoreChange: () => void): Unsubscribe {\n if (disposed) return () => {};\n\n observers.add(onStoreChange);\n\n return () => observers.delete(onStoreChange);\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n };\n}\n\nexport interface Mutation<TData, TVariables = void> {\n [Symbol.dispose](): void;\n cancel(): Promise<void>;\n dispose(): void;\n get disposed(): boolean;\n mutate(variables: TVariables, callOpts?: { signal?: AbortSignal }): Promise<TData>;\n peek(): MutationState<TData>;\n reset(): void;\n /**\n * `SyncStore` adapter — equivalent to `{ peek, subscribe }` on this object.\n * For React: `useSyncExternalStore(mutation.subscribe, mutation.peek)`\n */\n get store(): SyncStore<MutationState<TData>>;\n subscribe(onStoreChange: () => void): Unsubscribe;\n}\n"],"mappings":"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"}
package/dist/persist.cjs DELETED
@@ -1,2 +0,0 @@
1
- require("./serialize.cjs");let e=require("@vielzeug/arsenal");function t(t,n){let{keys:r,onError:i,prefix:a=`courier:`,storage:o}=n,s=t.keys(),c=typeof r==`function`?s.filter(r):r,l=[];function u(t){return`${a}${(0,e.hash)(t)}`}function d(e,t){if(t.status!==`success`)return;let n={data:t.data,updatedAt:t.updatedAt};try{let t=o.setItem(u(e),JSON.stringify(n));t instanceof Promise&&t.catch(t=>i?.(t,e))}catch(t){i?.(t,e)}}for(let e of c){let n=t.getState(e);n&&d(e,n);let r=t.observe({fetch:!1,key:e}),i=r.subscribe(()=>d(e,r.peek()));l.push(i)}return()=>{for(let e of l)e();l.length=0}}async function n(t,n){let{keys:r,maxAge:i,onError:a,prefix:o=`courier:`,storage:s}=n,c=t.keys(),l=typeof r==`function`?c.filter(r):r;await Promise.all(l.map(async n=>{try{let r=await s.getItem(`${o}${(0,e.hash)(n)}`);if(!r)return;let c=JSON.parse(r);if(typeof c!=`object`||!c||!(`data`in c)||typeof c.updatedAt!=`number`){a?.(Error(`[courier] Malformed persisted entry`),n);return}let l=c;if(i!==void 0&&Date.now()-l.updatedAt>i)return;t.set(n,l.data,{updatedAt:l.updatedAt})}catch(e){a?.(e,n)}}))}exports.hydrateQueryCache=n,exports.persistQueryCache=t;
2
- //# sourceMappingURL=persist.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"persist.cjs","names":[],"sources":["../src/persist.ts"],"sourcesContent":["import type { QueryKey, QueryState } from './types';\n\nimport { hash } from './serialize';\n\n/**\n * A sync-or-async key-value storage backend. Compatible with `localStorage`,\n * `sessionStorage`, and any async adapter (IndexedDB, filesystem, etc.).\n */\nexport interface PersistStorage {\n getItem(key: string): Promise<string | null> | string | null;\n setItem(key: string, value: string): Promise<void> | void;\n}\n\nexport interface PersistOptions {\n /**\n * The query keys to persist or hydrate. Accepts either:\n * - `QueryKey[]` — an explicit list of keys to observe/restore.\n * - `(key: QueryKey) => boolean` — a predicate applied to all currently cached keys.\n *\n * Both `persistQueryCache` and `hydrateQueryCache` evaluate this consistently.\n */\n keys: QueryKey[] | ((key: QueryKey) => boolean);\n /**\n * Maximum age in ms for a persisted entry to be eligible for hydration.\n * Entries whose `updatedAt` timestamp is older than `Date.now() - maxAge` are\n * skipped during hydration and left for a fresh network fetch.\n * When omitted, all entries are hydrated regardless of age.\n */\n maxAge?: number;\n /**\n * Called when a storage operation (read or write) throws.\n * If omitted, storage errors are silently swallowed.\n */\n onError?: (err: unknown, key: QueryKey) => void;\n /**\n * Storage key prefix to namespace entries and avoid collisions.\n * Defaults to `'courier:'`.\n */\n prefix?: string;\n /** Storage backend. */\n storage: PersistStorage;\n}\n\ntype SerializedEntry = {\n data: unknown;\n updatedAt: number;\n};\n\n/**\n * Subscribes a query client to state changes and writes successful entries to\n * a storage backend on every update. Returns a disposer that stops persistence.\n *\n * Pair with `hydrateQueryCache` to restore entries on page load.\n *\n * @example\n * ```ts\n * const qc = createQuery({ staleTime: 60_000 });\n * const stop = persistQueryCache(qc, {\n * keys: [['users', 1], ['settings']],\n * storage: localStorage,\n * });\n *\n * // On next page load:\n * await hydrateQueryCache(qc, { keys: [['users', 1], ['settings']], storage: localStorage });\n * ```\n */\nexport function persistQueryCache(\n qc: {\n getState(key: QueryKey): QueryState | null;\n keys(): QueryKey[];\n observe(opts: { fetch: false; key: QueryKey }): { peek(): QueryState; subscribe(listener: () => void): () => void };\n },\n opts: PersistOptions,\n): () => void {\n const { keys: keysOpt, onError, prefix = 'courier:', storage } = opts;\n const allKeys = qc.keys();\n const keys = typeof keysOpt === 'function' ? allKeys.filter(keysOpt) : keysOpt;\n const unsubs: Array<() => void> = [];\n\n function storageKey(key: QueryKey): string {\n return `${prefix}${hash(key)}`;\n }\n\n function persistState(key: QueryKey, state: QueryState): void {\n if (state.status !== 'success') return;\n\n const entry: SerializedEntry = { data: state.data, updatedAt: state.updatedAt };\n\n try {\n const result = storage.setItem(storageKey(key), JSON.stringify(entry));\n\n if (result instanceof Promise) {\n result.catch((err: unknown) => onError?.(err, key));\n }\n } catch (err) {\n onError?.(err, key);\n }\n }\n\n for (const key of keys) {\n const current = qc.getState(key);\n\n if (current) persistState(key, current);\n\n const store = qc.observe({ fetch: false, key });\n const unsub = store.subscribe(() => persistState(key, store.peek()));\n\n unsubs.push(unsub);\n }\n\n return () => {\n for (const unsub of unsubs) unsub();\n unsubs.length = 0;\n };\n}\n\n/**\n * Hydrates a query client from persisted storage before any fetches are made.\n * Entries are written via `qc.set()` so observers are notified immediately.\n *\n * @example\n * ```ts\n * await hydrateQueryCache(qc, {\n * keys: [['users', 1], ['settings']],\n * storage: localStorage,\n * });\n * ```\n */\nexport async function hydrateQueryCache(\n qc: { keys(): QueryKey[]; set(key: QueryKey, data: unknown, opts?: { updatedAt?: number }): void },\n opts: PersistOptions,\n): Promise<void> {\n const { keys: keysOpt, maxAge, onError, prefix = 'courier:', storage } = opts;\n const allKeys = qc.keys();\n const keys = typeof keysOpt === 'function' ? allKeys.filter(keysOpt) : keysOpt;\n\n await Promise.all(\n keys.map(async (key) => {\n try {\n const raw = await storage.getItem(`${prefix}${hash(key)}`);\n\n if (!raw) return;\n\n // Use the caller's key — not any key embedded in stored JSON — so the correct\n // cache entry is populated regardless of JSON serialisation edge cases.\n const parsed: unknown = JSON.parse(raw);\n\n // Validate structure before use to guard against schema migrations,\n // corrupt storage, or entries written by older versions of the app.\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n !('data' in parsed) ||\n typeof (parsed as { updatedAt?: unknown }).updatedAt !== 'number'\n ) {\n onError?.(new Error('[courier] Malformed persisted entry'), key);\n\n return;\n }\n\n const entry = parsed as SerializedEntry;\n\n // Skip entries that exceed the caller's maximum age threshold.\n if (maxAge !== undefined && Date.now() - entry.updatedAt > maxAge) return;\n\n // Restore the original updatedAt so staleTime checks after hydration are\n // accurate — data hydrated from a 55-second-old entry should be considered\n // almost stale, not brand-new.\n qc.set(key, entry.data, { updatedAt: entry.updatedAt });\n } catch (err) {\n onError?.(err, key);\n }\n }),\n );\n}\n"],"mappings":"8DAkEA,SAAgB,EACd,EAKA,EACY,CACZ,GAAM,CAAE,KAAM,EAAS,UAAS,SAAS,WAAY,WAAY,EAC3D,EAAU,EAAG,KAAK,EAClB,EAAO,OAAO,GAAY,WAAa,EAAQ,OAAO,CAAO,EAAI,EACjE,EAA4B,CAAC,EAEnC,SAAS,EAAW,EAAuB,CACzC,MAAO,GAAG,KAAA,EAAA,EAAA,KAAA,CAAc,CAAG,GAC7B,CAEA,SAAS,EAAa,EAAe,EAAyB,CAC5D,GAAI,EAAM,SAAW,UAAW,OAEhC,IAAM,EAAyB,CAAE,KAAM,EAAM,KAAM,UAAW,EAAM,SAAU,EAE9E,GAAI,CACF,IAAM,EAAS,EAAQ,QAAQ,EAAW,CAAG,EAAG,KAAK,UAAU,CAAK,CAAC,EAEjE,aAAkB,SACpB,EAAO,MAAO,GAAiB,IAAU,EAAK,CAAG,CAAC,CAEtD,OAAS,EAAK,CACZ,IAAU,EAAK,CAAG,CACpB,CACF,CAEA,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAU,EAAG,SAAS,CAAG,EAE3B,GAAS,EAAa,EAAK,CAAO,EAEtC,IAAM,EAAQ,EAAG,QAAQ,CAAE,MAAO,GAAO,KAAI,CAAC,EACxC,EAAQ,EAAM,cAAgB,EAAa,EAAK,EAAM,KAAK,CAAC,CAAC,EAEnE,EAAO,KAAK,CAAK,CACnB,CAEA,UAAa,CACX,IAAK,IAAM,KAAS,EAAQ,EAAM,EAClC,EAAO,OAAS,CAClB,CACF,CAcA,eAAsB,EACpB,EACA,EACe,CACf,GAAM,CAAE,KAAM,EAAS,SAAQ,UAAS,SAAS,WAAY,WAAY,EACnE,EAAU,EAAG,KAAK,EAClB,EAAO,OAAO,GAAY,WAAa,EAAQ,OAAO,CAAO,EAAI,EAEvE,MAAM,QAAQ,IACZ,EAAK,IAAI,KAAO,IAAQ,CACtB,GAAI,CACF,IAAM,EAAM,MAAM,EAAQ,QAAQ,GAAG,KAAA,EAAA,EAAA,KAAA,CAAc,CAAG,GAAG,EAEzD,GAAI,CAAC,EAAK,OAIV,IAAM,EAAkB,KAAK,MAAM,CAAG,EAItC,GACE,OAAO,GAAW,WAClB,GACA,EAAE,SAAU,IACZ,OAAQ,EAAmC,WAAc,SACzD,CACA,IAAc,MAAM,qCAAqC,EAAG,CAAG,EAE/D,MACF,CAEA,IAAM,EAAQ,EAGd,GAAI,IAAW,IAAA,IAAa,KAAK,IAAI,EAAI,EAAM,UAAY,EAAQ,OAKnE,EAAG,IAAI,EAAK,EAAM,KAAM,CAAE,UAAW,EAAM,SAAU,CAAC,CACxD,OAAS,EAAK,CACZ,IAAU,EAAK,CAAG,CACpB,CACF,CAAC,CACH,CACF"}
package/dist/persist.d.ts DELETED
@@ -1,86 +0,0 @@
1
- import type { QueryKey, QueryState } from './types';
2
- /**
3
- * A sync-or-async key-value storage backend. Compatible with `localStorage`,
4
- * `sessionStorage`, and any async adapter (IndexedDB, filesystem, etc.).
5
- */
6
- export interface PersistStorage {
7
- getItem(key: string): Promise<string | null> | string | null;
8
- setItem(key: string, value: string): Promise<void> | void;
9
- }
10
- export interface PersistOptions {
11
- /**
12
- * The query keys to persist or hydrate. Accepts either:
13
- * - `QueryKey[]` — an explicit list of keys to observe/restore.
14
- * - `(key: QueryKey) => boolean` — a predicate applied to all currently cached keys.
15
- *
16
- * Both `persistQueryCache` and `hydrateQueryCache` evaluate this consistently.
17
- */
18
- keys: QueryKey[] | ((key: QueryKey) => boolean);
19
- /**
20
- * Maximum age in ms for a persisted entry to be eligible for hydration.
21
- * Entries whose `updatedAt` timestamp is older than `Date.now() - maxAge` are
22
- * skipped during hydration and left for a fresh network fetch.
23
- * When omitted, all entries are hydrated regardless of age.
24
- */
25
- maxAge?: number;
26
- /**
27
- * Called when a storage operation (read or write) throws.
28
- * If omitted, storage errors are silently swallowed.
29
- */
30
- onError?: (err: unknown, key: QueryKey) => void;
31
- /**
32
- * Storage key prefix to namespace entries and avoid collisions.
33
- * Defaults to `'courier:'`.
34
- */
35
- prefix?: string;
36
- /** Storage backend. */
37
- storage: PersistStorage;
38
- }
39
- /**
40
- * Subscribes a query client to state changes and writes successful entries to
41
- * a storage backend on every update. Returns a disposer that stops persistence.
42
- *
43
- * Pair with `hydrateQueryCache` to restore entries on page load.
44
- *
45
- * @example
46
- * ```ts
47
- * const qc = createQuery({ staleTime: 60_000 });
48
- * const stop = persistQueryCache(qc, {
49
- * keys: [['users', 1], ['settings']],
50
- * storage: localStorage,
51
- * });
52
- *
53
- * // On next page load:
54
- * await hydrateQueryCache(qc, { keys: [['users', 1], ['settings']], storage: localStorage });
55
- * ```
56
- */
57
- export declare function persistQueryCache(qc: {
58
- getState(key: QueryKey): QueryState | null;
59
- keys(): QueryKey[];
60
- observe(opts: {
61
- fetch: false;
62
- key: QueryKey;
63
- }): {
64
- peek(): QueryState;
65
- subscribe(listener: () => void): () => void;
66
- };
67
- }, opts: PersistOptions): () => void;
68
- /**
69
- * Hydrates a query client from persisted storage before any fetches are made.
70
- * Entries are written via `qc.set()` so observers are notified immediately.
71
- *
72
- * @example
73
- * ```ts
74
- * await hydrateQueryCache(qc, {
75
- * keys: [['users', 1], ['settings']],
76
- * storage: localStorage,
77
- * });
78
- * ```
79
- */
80
- export declare function hydrateQueryCache(qc: {
81
- keys(): QueryKey[];
82
- set(key: QueryKey, data: unknown, opts?: {
83
- updatedAt?: number;
84
- }): void;
85
- }, opts: PersistOptions): Promise<void>;
86
- //# sourceMappingURL=persist.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../src/persist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAIpD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IAC7D,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3D;AAED,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC;IAChD;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC;IAChD;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,OAAO,EAAE,cAAc,CAAC;CACzB;AAOD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE;IACF,QAAQ,CAAC,GAAG,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI,CAAC;IAC3C,IAAI,IAAI,QAAQ,EAAE,CAAC;IACnB,OAAO,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,GAAG;QAAE,IAAI,IAAI,UAAU,CAAC;QAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;KAAE,CAAC;CACrH,EACD,IAAI,EAAE,cAAc,GACnB,MAAM,IAAI,CAyCZ;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE;IAAE,IAAI,IAAI,QAAQ,EAAE,CAAC;IAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAAE,EAClG,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,IAAI,CAAC,CA2Cf"}
package/dist/persist.js DELETED
@@ -1,2 +0,0 @@
1
- import{hash as e}from"./serialize.js";function t(t,n){let{keys:r,onError:i,prefix:a=`courier:`,storage:o}=n,s=t.keys(),c=typeof r==`function`?s.filter(r):r,l=[];function u(t){return`${a}${e(t)}`}function d(e,t){if(t.status!==`success`)return;let n={data:t.data,updatedAt:t.updatedAt};try{let t=o.setItem(u(e),JSON.stringify(n));t instanceof Promise&&t.catch(t=>i?.(t,e))}catch(t){i?.(t,e)}}for(let e of c){let n=t.getState(e);n&&d(e,n);let r=t.observe({fetch:!1,key:e}),i=r.subscribe(()=>d(e,r.peek()));l.push(i)}return()=>{for(let e of l)e();l.length=0}}async function n(t,n){let{keys:r,maxAge:i,onError:a,prefix:o=`courier:`,storage:s}=n,c=t.keys(),l=typeof r==`function`?c.filter(r):r;await Promise.all(l.map(async n=>{try{let r=await s.getItem(`${o}${e(n)}`);if(!r)return;let c=JSON.parse(r);if(typeof c!=`object`||!c||!(`data`in c)||typeof c.updatedAt!=`number`){a?.(Error(`[courier] Malformed persisted entry`),n);return}let l=c;if(i!==void 0&&Date.now()-l.updatedAt>i)return;t.set(n,l.data,{updatedAt:l.updatedAt})}catch(e){a?.(e,n)}}))}export{n as hydrateQueryCache,t as persistQueryCache};
2
- //# sourceMappingURL=persist.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"persist.js","names":[],"sources":["../src/persist.ts"],"sourcesContent":["import type { QueryKey, QueryState } from './types';\n\nimport { hash } from './serialize';\n\n/**\n * A sync-or-async key-value storage backend. Compatible with `localStorage`,\n * `sessionStorage`, and any async adapter (IndexedDB, filesystem, etc.).\n */\nexport interface PersistStorage {\n getItem(key: string): Promise<string | null> | string | null;\n setItem(key: string, value: string): Promise<void> | void;\n}\n\nexport interface PersistOptions {\n /**\n * The query keys to persist or hydrate. Accepts either:\n * - `QueryKey[]` — an explicit list of keys to observe/restore.\n * - `(key: QueryKey) => boolean` — a predicate applied to all currently cached keys.\n *\n * Both `persistQueryCache` and `hydrateQueryCache` evaluate this consistently.\n */\n keys: QueryKey[] | ((key: QueryKey) => boolean);\n /**\n * Maximum age in ms for a persisted entry to be eligible for hydration.\n * Entries whose `updatedAt` timestamp is older than `Date.now() - maxAge` are\n * skipped during hydration and left for a fresh network fetch.\n * When omitted, all entries are hydrated regardless of age.\n */\n maxAge?: number;\n /**\n * Called when a storage operation (read or write) throws.\n * If omitted, storage errors are silently swallowed.\n */\n onError?: (err: unknown, key: QueryKey) => void;\n /**\n * Storage key prefix to namespace entries and avoid collisions.\n * Defaults to `'courier:'`.\n */\n prefix?: string;\n /** Storage backend. */\n storage: PersistStorage;\n}\n\ntype SerializedEntry = {\n data: unknown;\n updatedAt: number;\n};\n\n/**\n * Subscribes a query client to state changes and writes successful entries to\n * a storage backend on every update. Returns a disposer that stops persistence.\n *\n * Pair with `hydrateQueryCache` to restore entries on page load.\n *\n * @example\n * ```ts\n * const qc = createQuery({ staleTime: 60_000 });\n * const stop = persistQueryCache(qc, {\n * keys: [['users', 1], ['settings']],\n * storage: localStorage,\n * });\n *\n * // On next page load:\n * await hydrateQueryCache(qc, { keys: [['users', 1], ['settings']], storage: localStorage });\n * ```\n */\nexport function persistQueryCache(\n qc: {\n getState(key: QueryKey): QueryState | null;\n keys(): QueryKey[];\n observe(opts: { fetch: false; key: QueryKey }): { peek(): QueryState; subscribe(listener: () => void): () => void };\n },\n opts: PersistOptions,\n): () => void {\n const { keys: keysOpt, onError, prefix = 'courier:', storage } = opts;\n const allKeys = qc.keys();\n const keys = typeof keysOpt === 'function' ? allKeys.filter(keysOpt) : keysOpt;\n const unsubs: Array<() => void> = [];\n\n function storageKey(key: QueryKey): string {\n return `${prefix}${hash(key)}`;\n }\n\n function persistState(key: QueryKey, state: QueryState): void {\n if (state.status !== 'success') return;\n\n const entry: SerializedEntry = { data: state.data, updatedAt: state.updatedAt };\n\n try {\n const result = storage.setItem(storageKey(key), JSON.stringify(entry));\n\n if (result instanceof Promise) {\n result.catch((err: unknown) => onError?.(err, key));\n }\n } catch (err) {\n onError?.(err, key);\n }\n }\n\n for (const key of keys) {\n const current = qc.getState(key);\n\n if (current) persistState(key, current);\n\n const store = qc.observe({ fetch: false, key });\n const unsub = store.subscribe(() => persistState(key, store.peek()));\n\n unsubs.push(unsub);\n }\n\n return () => {\n for (const unsub of unsubs) unsub();\n unsubs.length = 0;\n };\n}\n\n/**\n * Hydrates a query client from persisted storage before any fetches are made.\n * Entries are written via `qc.set()` so observers are notified immediately.\n *\n * @example\n * ```ts\n * await hydrateQueryCache(qc, {\n * keys: [['users', 1], ['settings']],\n * storage: localStorage,\n * });\n * ```\n */\nexport async function hydrateQueryCache(\n qc: { keys(): QueryKey[]; set(key: QueryKey, data: unknown, opts?: { updatedAt?: number }): void },\n opts: PersistOptions,\n): Promise<void> {\n const { keys: keysOpt, maxAge, onError, prefix = 'courier:', storage } = opts;\n const allKeys = qc.keys();\n const keys = typeof keysOpt === 'function' ? allKeys.filter(keysOpt) : keysOpt;\n\n await Promise.all(\n keys.map(async (key) => {\n try {\n const raw = await storage.getItem(`${prefix}${hash(key)}`);\n\n if (!raw) return;\n\n // Use the caller's key — not any key embedded in stored JSON — so the correct\n // cache entry is populated regardless of JSON serialisation edge cases.\n const parsed: unknown = JSON.parse(raw);\n\n // Validate structure before use to guard against schema migrations,\n // corrupt storage, or entries written by older versions of the app.\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n !('data' in parsed) ||\n typeof (parsed as { updatedAt?: unknown }).updatedAt !== 'number'\n ) {\n onError?.(new Error('[courier] Malformed persisted entry'), key);\n\n return;\n }\n\n const entry = parsed as SerializedEntry;\n\n // Skip entries that exceed the caller's maximum age threshold.\n if (maxAge !== undefined && Date.now() - entry.updatedAt > maxAge) return;\n\n // Restore the original updatedAt so staleTime checks after hydration are\n // accurate — data hydrated from a 55-second-old entry should be considered\n // almost stale, not brand-new.\n qc.set(key, entry.data, { updatedAt: entry.updatedAt });\n } catch (err) {\n onError?.(err, key);\n }\n }),\n );\n}\n"],"mappings":"sCAkEA,SAAgB,EACd,EAKA,EACY,CACZ,GAAM,CAAE,KAAM,EAAS,UAAS,SAAS,WAAY,WAAY,EAC3D,EAAU,EAAG,KAAK,EAClB,EAAO,OAAO,GAAY,WAAa,EAAQ,OAAO,CAAO,EAAI,EACjE,EAA4B,CAAC,EAEnC,SAAS,EAAW,EAAuB,CACzC,MAAO,GAAG,IAAS,EAAK,CAAG,GAC7B,CAEA,SAAS,EAAa,EAAe,EAAyB,CAC5D,GAAI,EAAM,SAAW,UAAW,OAEhC,IAAM,EAAyB,CAAE,KAAM,EAAM,KAAM,UAAW,EAAM,SAAU,EAE9E,GAAI,CACF,IAAM,EAAS,EAAQ,QAAQ,EAAW,CAAG,EAAG,KAAK,UAAU,CAAK,CAAC,EAEjE,aAAkB,SACpB,EAAO,MAAO,GAAiB,IAAU,EAAK,CAAG,CAAC,CAEtD,OAAS,EAAK,CACZ,IAAU,EAAK,CAAG,CACpB,CACF,CAEA,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAU,EAAG,SAAS,CAAG,EAE3B,GAAS,EAAa,EAAK,CAAO,EAEtC,IAAM,EAAQ,EAAG,QAAQ,CAAE,MAAO,GAAO,KAAI,CAAC,EACxC,EAAQ,EAAM,cAAgB,EAAa,EAAK,EAAM,KAAK,CAAC,CAAC,EAEnE,EAAO,KAAK,CAAK,CACnB,CAEA,UAAa,CACX,IAAK,IAAM,KAAS,EAAQ,EAAM,EAClC,EAAO,OAAS,CAClB,CACF,CAcA,eAAsB,EACpB,EACA,EACe,CACf,GAAM,CAAE,KAAM,EAAS,SAAQ,UAAS,SAAS,WAAY,WAAY,EACnE,EAAU,EAAG,KAAK,EAClB,EAAO,OAAO,GAAY,WAAa,EAAQ,OAAO,CAAO,EAAI,EAEvE,MAAM,QAAQ,IACZ,EAAK,IAAI,KAAO,IAAQ,CACtB,GAAI,CACF,IAAM,EAAM,MAAM,EAAQ,QAAQ,GAAG,IAAS,EAAK,CAAG,GAAG,EAEzD,GAAI,CAAC,EAAK,OAIV,IAAM,EAAkB,KAAK,MAAM,CAAG,EAItC,GACE,OAAO,GAAW,WAClB,GACA,EAAE,SAAU,IACZ,OAAQ,EAAmC,WAAc,SACzD,CACA,IAAc,MAAM,qCAAqC,EAAG,CAAG,EAE/D,MACF,CAEA,IAAM,EAAQ,EAGd,GAAI,IAAW,IAAA,IAAa,KAAK,IAAI,EAAI,EAAM,UAAY,EAAQ,OAKnE,EAAG,IAAI,EAAK,EAAM,KAAM,CAAE,UAAW,EAAM,SAAU,CAAC,CACxD,OAAS,EAAK,CACZ,IAAU,EAAK,CAAG,CACpB,CACF,CAAC,CACH,CACF"}
@@ -1,2 +0,0 @@
1
- require("./serialize.cjs");const e=require("./retry.cjs");let t=require("@vielzeug/arsenal");var n=5*6e4;function r(e){return typeof e==`function`?e():e}function i(e){return(0,t.hash)(e)}function a(e){return(0,t.hash)(e)}function o(e,n){let r=(0,t.hash)(n),i=e.entries.get(r);return i||(i={data:void 0,error:null,hash:r,inflight:null,isFetching:!1,key:n,lastConfig:void 0,observers:new Set,segmentHashes:n.map(e=>(0,t.hash)(e)),status:`loading`,updatedAt:void 0},e.entries.set(r,i)),i}function s(e,t){let n=e.gcTimers.get(t);n&&(clearTimeout(n),e.gcTimers.delete(t))}function c(e,t,n){if(e.disposed||(s(e,t.hash),t.observers.size>0))return;if(n===0){e.entries.delete(t.hash);return}let r=setTimeout(()=>{let n=e.entries.get(t.hash);!n||n.observers.size>0||n.isFetching||(e.entries.delete(t.hash),e.gcTimers.delete(t.hash))},n);e.gcTimers.set(t.hash,r)}function l(e,t){t.inflight?.controller.abort(),s(e,t.hash),e.entries.delete(t.hash)}function u(e,t){return t.length>e.segmentHashes.length?!1:t.every((t,n)=>t===e.segmentHashes[n])}function d(e,t){t.isFetching=!0,t.status=`loading`,t.error=null,e.notify(t)}function f(e,t,n,r){t.data=n,t.error=null,t.status=`success`,t.updatedAt=Date.now(),t.isFetching=!1,t.inflight=null,c(e,t,r),e.notify(t)}function p(e,t,n,r){t.error=n,t.status=`error`,t.updatedAt=Date.now(),t.isFetching=!1,t.inflight=null,c(e,t,r),e.notify(t)}function m(e,t,n,r){if(t.data=n.data,t.error=n.error,t.status=n.status,t.updatedAt=n.updatedAt,t.isFetching=!1,t.inflight=null,t.observers.size===0&&t.status===`loading`&&t.data===void 0){l(e,t);return}c(e,t,r),e.notify(t)}function h(e,t){if(t.inflight?.controller.abort(),t.observers.size>0){t.inflight=null,t.isFetching=!1,t.status=`loading`,t.data=void 0,t.error=null,t.updatedAt=void 0,e.notify(t);return}l(e,t)}function g(n,r,i){if(r.inflight)return r.inflight.promise;let{delay:a,fn:o,gcTime:s,shouldRetry:c,times:l}=i,u=new AbortController,h={data:r.data,error:r.error,status:r.status,updatedAt:r.updatedAt},g=(async()=>{try{let i=await(0,t.retry)(()=>o({key:r.key,signal:u.signal}),{delay:t=>e.resolveRetryDelay(t,a),shouldRetry:c,signal:u.signal,times:l});return f(n,r,i,s),i}catch(e){let i=e instanceof Error?e:Error(String(e));throw u.signal.aborted||(0,t.isAbortError)(i)?m(n,r,h,s):p(n,r,i,s),i}})();return r.inflight={controller:u,promise:g},d(n,r),g}function _(e,t){return{delay:t.delay??e.delayDefault,fn:t.fn,gcTime:t.gcTime??e.gcTimeDefault,shouldRetry:t.shouldRetry??e.shouldRetryDefault,staleTime:t.staleTime??e.staleTimeDefault,times:t.times??e.timesDefault}}function v(e,t,n,r){return{delayDefault:e.delay,disposed:!1,entries:t,gcTimeDefault:e.gcTime??3e5,gcTimers:n,notify:r,shouldRetryDefault:e.shouldRetry,staleTimeDefault:e.staleTime??0,timesDefault:e.times??1}}exports.DEFAULT_GC=n,exports.buildCacheContext=v,exports.cancelGc=s,exports.commitError=p,exports.commitSuccess=f,exports.deleteEntry=l,exports.ensureEntry=o,exports.evictEntry=h,exports.hashAtom=a,exports.hashKey=i,exports.isKeyOrPrefix=u,exports.makeFetchConfig=_,exports.markFetching=d,exports.resolveValue=r,exports.rollback=m,exports.scheduleGc=c,exports.startFetch=g;
2
- //# sourceMappingURL=query-cache.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"query-cache.cjs","names":[],"sources":["../src/query-cache.ts"],"sourcesContent":["import { isAbortError, retry } from '@vielzeug/arsenal';\n\nimport type { RetryOptions } from './retry';\nimport type { QueryKey } from './types';\n\nimport { DEFAULT_TIMES, resolveRetryDelay } from './retry';\nimport { hash } from './serialize';\n\nexport const DEFAULT_GC = 5 * 60_000;\n\nexport type QueryFn<T> = (ctx: QueryFnContext) => Promise<T>;\n\nexport type QueryFnContext = {\n key: QueryKey;\n signal: AbortSignal;\n};\n\nexport type FetchConfig<T = unknown> = {\n delay: RetryOptions['delay'];\n fn: QueryFn<T>;\n gcTime: number;\n shouldRetry: RetryOptions['shouldRetry'];\n staleTime: number;\n times: number;\n};\n\nexport type EntryStatus = 'loading' | 'success' | 'error';\n\nexport type CacheEntry<T = unknown> = {\n data: T | undefined;\n error: Error | null;\n hash: string;\n inflight: { controller: AbortController; promise: Promise<T> } | null;\n isFetching: boolean;\n key: QueryKey;\n lastConfig: FetchConfig<T> | undefined;\n observers: Set<import('./query-observe').QueryObserver<unknown, unknown>>;\n segmentHashes: readonly string[];\n status: EntryStatus;\n updatedAt: number | undefined;\n};\n\nexport type EntrySnapshot<T> = {\n data: T | undefined;\n error: Error | null;\n status: EntryStatus;\n updatedAt: number | undefined;\n};\n\nexport type CacheContext = {\n delayDefault: RetryOptions['delay'];\n disposed: boolean;\n entries: Map<string, CacheEntry>;\n gcTimeDefault: number;\n gcTimers: Map<string, ReturnType<typeof setTimeout>>;\n notify: (entry: CacheEntry) => void;\n shouldRetryDefault: RetryOptions['shouldRetry'];\n staleTimeDefault: number;\n timesDefault: number;\n};\n\nexport function makeLoadingState(): {\n data: undefined;\n error: null;\n isFetching: true;\n isLoading: true;\n status: 'loading';\n updatedAt: undefined;\n} {\n return {\n data: undefined,\n error: null,\n isFetching: true,\n isLoading: true,\n status: 'loading',\n updatedAt: undefined,\n };\n}\n\nexport function resolveValue<T>(v: T | (() => T | undefined) | undefined): T | undefined {\n return typeof v === 'function' ? (v as () => T | undefined)() : v;\n}\n\nexport function hashKey(key: QueryKey): string {\n return hash(key);\n}\n\nexport function hashAtom(atom: QueryKey[number]): string {\n return hash(atom);\n}\n\nexport function ensureEntry<T>(ctx: CacheContext, key: QueryKey): CacheEntry<T> {\n const entryHash = hash(key);\n let entry = ctx.entries.get(entryHash) as CacheEntry<T> | undefined;\n\n if (!entry) {\n entry = {\n data: undefined,\n error: null,\n hash: entryHash,\n inflight: null,\n isFetching: false,\n key,\n lastConfig: undefined,\n observers: new Set(),\n segmentHashes: key.map((k) => hash(k)),\n status: 'loading',\n updatedAt: undefined,\n };\n ctx.entries.set(entryHash, entry as CacheEntry<unknown>);\n }\n\n return entry;\n}\n\nexport function cancelGc(ctx: CacheContext, entryHash: string): void {\n const timer = ctx.gcTimers.get(entryHash);\n\n if (!timer) return;\n\n clearTimeout(timer);\n ctx.gcTimers.delete(entryHash);\n}\n\nexport function scheduleGc<T>(ctx: CacheContext, entry: CacheEntry<T>, gcTime: number): void {\n if (ctx.disposed) return;\n\n cancelGc(ctx, entry.hash);\n\n if (entry.observers.size > 0) return;\n\n if (gcTime === 0) {\n ctx.entries.delete(entry.hash);\n\n return;\n }\n\n const timer = setTimeout(() => {\n const current = ctx.entries.get(entry.hash);\n\n if (!current || current.observers.size > 0 || current.isFetching) return;\n\n ctx.entries.delete(entry.hash);\n ctx.gcTimers.delete(entry.hash);\n }, gcTime);\n\n ctx.gcTimers.set(entry.hash, timer);\n}\n\nexport function deleteEntry<T>(ctx: CacheContext, entry: CacheEntry<T>): void {\n entry.inflight?.controller.abort();\n cancelGc(ctx, entry.hash);\n ctx.entries.delete(entry.hash);\n}\n\nexport function isKeyOrPrefix(entry: CacheEntry, prefixHash: readonly string[]): boolean {\n if (prefixHash.length > entry.segmentHashes.length) return false;\n\n return prefixHash.every((seg, i) => seg === entry.segmentHashes[i]);\n}\n\nexport function markFetching<T>(ctx: CacheContext, entry: CacheEntry<T>): void {\n entry.isFetching = true;\n entry.status = 'loading';\n entry.error = null;\n ctx.notify(entry);\n}\n\nexport function commitSuccess<T>(ctx: CacheContext, entry: CacheEntry<T>, data: T, gcTime: number): void {\n entry.data = data;\n entry.error = null;\n entry.status = 'success';\n entry.updatedAt = Date.now();\n entry.isFetching = false;\n entry.inflight = null;\n scheduleGc(ctx, entry, gcTime);\n ctx.notify(entry);\n}\n\nexport function commitError<T>(ctx: CacheContext, entry: CacheEntry<T>, error: Error, gcTime: number): void {\n entry.error = error;\n entry.status = 'error';\n entry.updatedAt = Date.now();\n entry.isFetching = false;\n entry.inflight = null;\n scheduleGc(ctx, entry, gcTime);\n ctx.notify(entry);\n}\n\nexport function rollback<T>(ctx: CacheContext, entry: CacheEntry<T>, previous: EntrySnapshot<T>, gcTime: number): void {\n entry.data = previous.data;\n entry.error = previous.error;\n entry.status = previous.status;\n entry.updatedAt = previous.updatedAt;\n entry.isFetching = false;\n entry.inflight = null;\n\n if (entry.observers.size === 0 && entry.status === 'loading' && entry.data === undefined) {\n deleteEntry(ctx, entry);\n\n return;\n }\n\n scheduleGc(ctx, entry, gcTime);\n ctx.notify(entry);\n}\n\nexport function evictEntry<T>(ctx: CacheContext, entry: CacheEntry<T>): void {\n entry.inflight?.controller.abort();\n\n if (entry.observers.size > 0) {\n entry.inflight = null;\n entry.isFetching = false;\n entry.status = 'loading';\n entry.data = undefined;\n entry.error = null;\n entry.updatedAt = undefined;\n ctx.notify(entry);\n\n return;\n }\n\n deleteEntry(ctx, entry);\n}\n\nexport function startFetch<T>(ctx: CacheContext, entry: CacheEntry<T>, config: FetchConfig<T>): Promise<T> {\n if (entry.inflight) return entry.inflight.promise;\n\n const { delay, fn, gcTime, shouldRetry, times } = config;\n const controller = new AbortController();\n const prev: EntrySnapshot<T> = {\n data: entry.data,\n error: entry.error,\n status: entry.status,\n updatedAt: entry.updatedAt,\n };\n\n const promise = (async () => {\n try {\n const data = await retry(() => fn({ key: entry.key, signal: controller.signal }), {\n delay: (attempt) => resolveRetryDelay(attempt, delay),\n shouldRetry,\n signal: controller.signal,\n times,\n });\n\n commitSuccess(ctx, entry, data, gcTime);\n\n return data;\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n const isAborted = controller.signal.aborted || isAbortError(error);\n\n if (isAborted) {\n rollback(ctx, entry, prev, gcTime);\n } else {\n commitError(ctx, entry, error, gcTime);\n }\n\n throw error;\n }\n })();\n\n entry.inflight = { controller, promise };\n markFetching(ctx, entry);\n\n return promise;\n}\n\nexport function makeFetchConfig<T>(\n ctx: CacheContext,\n options: {\n delay?: RetryOptions['delay'];\n fn: QueryFn<T>;\n gcTime?: number;\n shouldRetry?: RetryOptions['shouldRetry'];\n staleTime?: number;\n times?: number;\n },\n): FetchConfig<T> {\n return {\n delay: options.delay ?? ctx.delayDefault,\n fn: options.fn,\n gcTime: options.gcTime ?? ctx.gcTimeDefault,\n shouldRetry: options.shouldRetry ?? ctx.shouldRetryDefault,\n staleTime: options.staleTime ?? ctx.staleTimeDefault,\n times: options.times ?? ctx.timesDefault,\n };\n}\n\nexport function buildCacheContext(\n opts: {\n delay?: RetryOptions['delay'];\n gcTime?: number;\n shouldRetry?: RetryOptions['shouldRetry'];\n staleTime?: number;\n times?: number;\n },\n entries: Map<string, CacheEntry>,\n gcTimers: Map<string, ReturnType<typeof setTimeout>>,\n notify: (entry: CacheEntry) => void,\n): CacheContext {\n return {\n delayDefault: opts.delay,\n disposed: false,\n entries,\n gcTimeDefault: opts.gcTime ?? DEFAULT_GC,\n gcTimers,\n notify,\n shouldRetryDefault: opts.shouldRetry,\n staleTimeDefault: opts.staleTime ?? 0,\n timesDefault: opts.times ?? DEFAULT_TIMES,\n };\n}\n"],"mappings":"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"}