@vielzeug/courier 1.1.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.
- package/README.md +70 -0
- package/dist/_dev.cjs +2 -0
- package/dist/_dev.cjs.map +1 -0
- package/dist/_dev.d.ts +2 -0
- package/dist/_dev.d.ts.map +1 -0
- package/dist/_dev.js +2 -0
- package/dist/_dev.js.map +1 -0
- package/dist/api.cjs +2 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.ts +43 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +2 -0
- package/dist/api.js.map +1 -0
- package/dist/batcher.cjs +2 -0
- package/dist/batcher.cjs.map +1 -0
- package/dist/batcher.d.ts +66 -0
- package/dist/batcher.d.ts.map +1 -0
- package/dist/batcher.js +2 -0
- package/dist/batcher.js.map +1 -0
- package/dist/courier.cjs +7 -0
- package/dist/courier.cjs.map +1 -0
- package/dist/courier.d.ts +154 -0
- package/dist/courier.d.ts.map +1 -0
- package/dist/courier.iife.js +7 -0
- package/dist/courier.iife.js.map +1 -0
- package/dist/courier.js +7 -0
- package/dist/courier.js.map +1 -0
- package/dist/devtools.cjs +2 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.ts +31 -0
- package/dist/devtools.d.ts.map +1 -0
- package/dist/devtools.js +2 -0
- package/dist/devtools.js.map +1 -0
- package/dist/errors.cjs +2 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +105 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -0
- package/dist/focus.cjs +2 -0
- package/dist/focus.cjs.map +1 -0
- package/dist/focus.d.ts +25 -0
- package/dist/focus.d.ts.map +1 -0
- package/dist/focus.js +2 -0
- package/dist/focus.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/interceptors.cjs +2 -0
- package/dist/interceptors.cjs.map +1 -0
- package/dist/interceptors.d.ts +50 -0
- package/dist/interceptors.d.ts.map +1 -0
- package/dist/interceptors.js +2 -0
- package/dist/interceptors.js.map +1 -0
- package/dist/mutation.cjs +2 -0
- package/dist/mutation.cjs.map +1 -0
- package/dist/mutation.d.ts +81 -0
- package/dist/mutation.d.ts.map +1 -0
- package/dist/mutation.js +2 -0
- package/dist/mutation.js.map +1 -0
- package/dist/persist.cjs +2 -0
- package/dist/persist.cjs.map +1 -0
- package/dist/persist.d.ts +86 -0
- package/dist/persist.d.ts.map +1 -0
- package/dist/persist.js +2 -0
- package/dist/persist.js.map +1 -0
- package/dist/query-cache.cjs +2 -0
- package/dist/query-cache.cjs.map +1 -0
- package/dist/query-cache.d.ts +88 -0
- package/dist/query-cache.d.ts.map +1 -0
- package/dist/query-cache.js +2 -0
- package/dist/query-cache.js.map +1 -0
- package/dist/query-observe.cjs +2 -0
- package/dist/query-observe.cjs.map +1 -0
- package/dist/query-observe.d.ts +17 -0
- package/dist/query-observe.d.ts.map +1 -0
- package/dist/query-observe.js +2 -0
- package/dist/query-observe.js.map +1 -0
- package/dist/query.cjs +2 -0
- package/dist/query.cjs.map +1 -0
- package/dist/query.d.ts +110 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +2 -0
- package/dist/query.js.map +1 -0
- package/dist/response.cjs +2 -0
- package/dist/response.cjs.map +1 -0
- package/dist/response.d.ts +4 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +2 -0
- package/dist/response.js.map +1 -0
- package/dist/retry.cjs +2 -0
- package/dist/retry.cjs.map +1 -0
- package/dist/retry.d.ts +34 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +2 -0
- package/dist/retry.js.map +1 -0
- package/dist/serialize.cjs +2 -0
- package/dist/serialize.cjs.map +1 -0
- package/dist/serialize.d.ts +10 -0
- package/dist/serialize.d.ts.map +1 -0
- package/dist/serialize.js +2 -0
- package/dist/serialize.js.map +1 -0
- package/dist/stream.cjs +7 -0
- package/dist/stream.cjs.map +1 -0
- package/dist/stream.d.ts +113 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +7 -0
- package/dist/stream.js.map +1 -0
- package/dist/sync-store.cjs +2 -0
- package/dist/sync-store.cjs.map +1 -0
- package/dist/sync-store.d.ts +20 -0
- package/dist/sync-store.d.ts.map +1 -0
- package/dist/sync-store.js +2 -0
- package/dist/sync-store.js.map +1 -0
- package/dist/transport.cjs +2 -0
- package/dist/transport.cjs.map +1 -0
- package/dist/transport.d.ts +59 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +2 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/url.cjs +2 -0
- package/dist/url.cjs.map +1 -0
- package/dist/url.d.ts +49 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +2 -0
- package/dist/url.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./api.cjs"),n=require("./mutation.cjs"),r=require("./query.cjs"),i=require("./stream.cjs"),a=require("./courier.cjs"),o=require("./interceptors.cjs"),s=require("./batcher.cjs"),c=require("./focus.cjs"),l=require("./persist.cjs"),u=require("./sync-store.cjs");exports.CourierAbortError=e.CourierAbortError,exports.CourierBatcherError=e.CourierBatcherError,exports.CourierDisposedError=e.CourierDisposedError,exports.CourierError=e.CourierError,exports.CourierHttpError=e.CourierHttpError,exports.CourierNetworkError=e.CourierNetworkError,exports.CourierParseError=e.CourierParseError,exports.CourierSchemaValidationError=e.CourierSchemaValidationError,exports.CourierTimeoutError=e.CourierTimeoutError,exports.bindRefetch=c.bindRefetch,exports.createApi=t.createApi,exports.createBatcher=s.createBatcher,exports.createCourier=a.createCourier,exports.createMutation=n.createMutation,exports.createQuery=r.createQuery,exports.createStream=i.createStream,exports.hydrateQueryCache=l.hydrateQueryCache,exports.persistQueryCache=l.persistQueryCache,exports.toSyncStore=u.toSyncStore,exports.withBearerAuth=o.withBearerAuth,exports.withLogging=o.withLogging,exports.withRequestId=o.withRequestId;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { createApi } from './api';
|
|
2
|
+
export type { ApiClient, FetchContext, Interceptor } from './api';
|
|
3
|
+
export { createBatcher } from './batcher';
|
|
4
|
+
export type { Batcher, BatcherOptions } from './batcher';
|
|
5
|
+
export { createCourier } from './courier';
|
|
6
|
+
export type { Courier, CourierMutationOptions, CourierOptions } from './courier';
|
|
7
|
+
export { CourierAbortError, CourierBatcherError, CourierDisposedError, CourierError, CourierHttpError, CourierNetworkError, CourierParseError, CourierSchemaValidationError, CourierTimeoutError, } from './errors';
|
|
8
|
+
export { bindRefetch } from './focus';
|
|
9
|
+
export { withBearerAuth, withLogging, withRequestId } from './interceptors';
|
|
10
|
+
export { createMutation } from './mutation';
|
|
11
|
+
export type { Mutation, MutationFn, MutationOptions, SettledResult } from './mutation';
|
|
12
|
+
export { hydrateQueryCache, persistQueryCache } from './persist';
|
|
13
|
+
export type { PersistOptions, PersistStorage } from './persist';
|
|
14
|
+
export { createQuery } from './query';
|
|
15
|
+
export type { ObserveOptions, QueryClient, QueryClientOptions, QueryFn, QueryFnContext, QueryOptions } from './query';
|
|
16
|
+
export { toSyncStore } from './sync-store';
|
|
17
|
+
export type { ResponseType } from './response';
|
|
18
|
+
export type { RetryOptions } from './retry';
|
|
19
|
+
export { createStream } from './stream';
|
|
20
|
+
export type { ReadableConfig, ReconnectOptions, SseOptions, SseSource, SseStatus, StreamClient, StreamRequestConfig, } from './stream';
|
|
21
|
+
export type { TransportOptions } from './transport';
|
|
22
|
+
export type { AsyncState, AsyncStatus, MutationState, QueryKey, QueryKeyAtom, QueryState, SyncStore, Unsubscribe, } from './types';
|
|
23
|
+
export type { CourierRequestConfig as RequestConfig, HttpRequestConfig, Params } from './url';
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGzD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,YAAY,EAAE,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjF,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGtC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG5E,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGvF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACjE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhE,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGtH,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,SAAS,EACT,YAAY,EACZ,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAGlB,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,YAAY,EACV,UAAU,EACV,WAAW,EACX,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,GACZ,MAAM,SAAS,CAAC;AAGjB,YAAY,EAAE,oBAAoB,IAAI,aAAa,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CourierAbortError as e,CourierBatcherError as t,CourierDisposedError as n,CourierError as r,CourierHttpError as i,CourierNetworkError as a,CourierParseError as o,CourierSchemaValidationError as s,CourierTimeoutError as c}from"./errors.js";import{createApi as l}from"./api.js";import{createMutation as u}from"./mutation.js";import{createQuery as d}from"./query.js";import{createStream as f}from"./stream.js";import{createCourier as p}from"./courier.js";import{withBearerAuth as m,withLogging as h,withRequestId as g}from"./interceptors.js";import{createBatcher as _}from"./batcher.js";import{bindRefetch as v}from"./focus.js";import{hydrateQueryCache as y,persistQueryCache as b}from"./persist.js";import{toSyncStore as x}from"./sync-store.js";export{e as CourierAbortError,t as CourierBatcherError,n as CourierDisposedError,r as CourierError,i as CourierHttpError,a as CourierNetworkError,o as CourierParseError,s as CourierSchemaValidationError,c as CourierTimeoutError,v as bindRefetch,l as createApi,_ as createBatcher,p as createCourier,u as createMutation,d as createQuery,f as createStream,y as hydrateQueryCache,b as persistQueryCache,x as toSyncStore,m as withBearerAuth,h as withLogging,g as withRequestId};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let e=require("@vielzeug/arsenal");function t(e){return async(t,n)=>{let r=typeof e==`function`?await e():e;return n(t.withHeaders({authorization:`Bearer ${r}`}))}}function n(t){let n=t?.header??`x-request-id`,r=t?.generate??e.uuid;return(e,t)=>t(e.withHeaders({[n]:r()}))}function r(e){let t=e?.logger??((e,t)=>{console.debug(e,t)});return async(e,n)=>{let r=performance.now(),i=(e.init.method??`GET`).toUpperCase();try{let a=await n(e),o=Math.round(performance.now()-r);return t(`${i} ${e.url} ${a.status} (${o}ms)`,{duration:o,method:i,status:a.status,url:e.url}),a}catch(n){let a=Math.round(performance.now()-r);throw t(`${i} ${e.url} ERR (${a}ms)`,{duration:a,method:i,status:0,url:e.url}),n}}}exports.withBearerAuth=t,exports.withLogging=r,exports.withRequestId=n;
|
|
2
|
+
//# sourceMappingURL=interceptors.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interceptors.cjs","names":[],"sources":["../src/interceptors.ts"],"sourcesContent":["import { uuid } from '@vielzeug/arsenal';\n\nimport type { Interceptor } from './transport';\n\n/**\n * Attaches a Bearer token `Authorization` header to every request.\n * The token can be a static string or an async function (useful for refresh flows).\n *\n * @example\n * ```ts\n * client.use(withBearerAuth(() => tokenStore.getAccessToken()));\n * ```\n */\nexport function withBearerAuth(token: string | (() => string | Promise<string>)): Interceptor {\n return async (ctx, next) => {\n const t = typeof token === 'function' ? await token() : token;\n\n return next(ctx.withHeaders({ authorization: `Bearer ${t}` }));\n };\n}\n\n/**\n * Adds a unique request ID to every request as a header.\n *\n * Defaults to `x-request-id` populated with `crypto.randomUUID()`.\n *\n * @example\n * ```ts\n * client.use(withRequestId());\n * client.use(withRequestId({ header: 'x-trace-id' }));\n * ```\n */\nexport function withRequestId(opts?: { generate?: () => string; header?: string }): Interceptor {\n const header = opts?.header ?? 'x-request-id';\n const generate = opts?.generate ?? uuid;\n\n return (ctx, next) => next(ctx.withHeaders({ [header]: generate() }));\n}\n\n/**\n * Logs each request's method, URL, status code, and duration.\n *\n * Defaults to `console.debug`. Supply a custom `logger` for structured logging.\n *\n * **Security note:** The full URL is logged, including any query parameters.\n * If your URLs may contain sensitive data (tokens, PII), provide a custom\n * `logger` that sanitizes the URL before writing to persistent logs.\n *\n * @example\n * ```ts\n * client.use(withLogging());\n * client.use(withLogging({ logger: (msg, meta) => logger.info(meta) }));\n * ```\n */\nexport function withLogging(opts?: {\n logger?: (msg: string, meta: { duration: number; method: string; status: number; url: string }) => void;\n}): Interceptor {\n const log =\n opts?.logger ??\n ((msg, meta) => {\n console.debug(msg, meta);\n });\n\n return async (ctx, next) => {\n const start = performance.now();\n const method = ((ctx.init as RequestInit).method ?? 'GET').toUpperCase();\n\n try {\n const res = await next(ctx);\n const duration = Math.round(performance.now() - start);\n\n log(`${method} ${ctx.url} ${res.status} (${duration}ms)`, {\n duration,\n method,\n status: res.status,\n url: ctx.url,\n });\n\n return res;\n } catch (err) {\n const duration = Math.round(performance.now() - start);\n\n log(`${method} ${ctx.url} ERR (${duration}ms)`, {\n duration,\n method,\n status: 0,\n url: ctx.url,\n });\n\n throw err;\n }\n };\n}\n"],"mappings":"mCAaA,SAAgB,EAAe,EAA+D,CAC5F,OAAO,MAAO,EAAK,IAAS,CAC1B,IAAM,EAAI,OAAO,GAAU,WAAa,MAAM,EAAM,EAAI,EAExD,OAAO,EAAK,EAAI,YAAY,CAAE,cAAe,UAAU,GAAI,CAAC,CAAC,CAC/D,CACF,CAaA,SAAgB,EAAc,EAAkE,CAC9F,IAAM,EAAS,GAAM,QAAU,eACzB,EAAW,GAAM,UAAY,EAAA,KAEnC,OAAQ,EAAK,IAAS,EAAK,EAAI,YAAY,EAAG,GAAS,EAAS,CAAE,CAAC,CAAC,CACtE,CAiBA,SAAgB,EAAY,EAEZ,CACd,IAAM,EACJ,GAAM,UACJ,EAAK,IAAS,CACd,QAAQ,MAAM,EAAK,CAAI,CACzB,GAEF,OAAO,MAAO,EAAK,IAAS,CAC1B,IAAM,EAAQ,YAAY,IAAI,EACxB,GAAW,EAAI,KAAqB,QAAU,MAAA,CAAO,YAAY,EAEvE,GAAI,CACF,IAAM,EAAM,MAAM,EAAK,CAAG,EACpB,EAAW,KAAK,MAAM,YAAY,IAAI,EAAI,CAAK,EASrD,OAPA,EAAI,GAAG,EAAO,GAAG,EAAI,IAAI,GAAG,EAAI,OAAO,IAAI,EAAS,KAAM,CACxD,WACA,SACA,OAAQ,EAAI,OACZ,IAAK,EAAI,GACX,CAAC,EAEM,CACT,OAAS,EAAK,CACZ,IAAM,EAAW,KAAK,MAAM,YAAY,IAAI,EAAI,CAAK,EASrD,MAPA,EAAI,GAAG,EAAO,GAAG,EAAI,IAAI,QAAQ,EAAS,KAAM,CAC9C,WACA,SACA,OAAQ,EACR,IAAK,EAAI,GACX,CAAC,EAEK,CACR,CACF,CACF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Interceptor } from './transport';
|
|
2
|
+
/**
|
|
3
|
+
* Attaches a Bearer token `Authorization` header to every request.
|
|
4
|
+
* The token can be a static string or an async function (useful for refresh flows).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* client.use(withBearerAuth(() => tokenStore.getAccessToken()));
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare function withBearerAuth(token: string | (() => string | Promise<string>)): Interceptor;
|
|
12
|
+
/**
|
|
13
|
+
* Adds a unique request ID to every request as a header.
|
|
14
|
+
*
|
|
15
|
+
* Defaults to `x-request-id` populated with `crypto.randomUUID()`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* client.use(withRequestId());
|
|
20
|
+
* client.use(withRequestId({ header: 'x-trace-id' }));
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function withRequestId(opts?: {
|
|
24
|
+
generate?: () => string;
|
|
25
|
+
header?: string;
|
|
26
|
+
}): Interceptor;
|
|
27
|
+
/**
|
|
28
|
+
* Logs each request's method, URL, status code, and duration.
|
|
29
|
+
*
|
|
30
|
+
* Defaults to `console.debug`. Supply a custom `logger` for structured logging.
|
|
31
|
+
*
|
|
32
|
+
* **Security note:** The full URL is logged, including any query parameters.
|
|
33
|
+
* If your URLs may contain sensitive data (tokens, PII), provide a custom
|
|
34
|
+
* `logger` that sanitizes the URL before writing to persistent logs.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* client.use(withLogging());
|
|
39
|
+
* client.use(withLogging({ logger: (msg, meta) => logger.info(meta) }));
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function withLogging(opts?: {
|
|
43
|
+
logger?: (msg: string, meta: {
|
|
44
|
+
duration: number;
|
|
45
|
+
method: string;
|
|
46
|
+
status: number;
|
|
47
|
+
url: string;
|
|
48
|
+
}) => void;
|
|
49
|
+
}): Interceptor;
|
|
50
|
+
//# sourceMappingURL=interceptors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interceptors.d.ts","sourceRoot":"","sources":["../src/interceptors.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,WAAW,CAM5F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAK9F;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE;IACjC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACzG,GAAG,WAAW,CAoCd"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{uuid as e}from"@vielzeug/arsenal";function t(e){return async(t,n)=>{let r=typeof e==`function`?await e():e;return n(t.withHeaders({authorization:`Bearer ${r}`}))}}function n(t){let n=t?.header??`x-request-id`,r=t?.generate??e;return(e,t)=>t(e.withHeaders({[n]:r()}))}function r(e){let t=e?.logger??((e,t)=>{console.debug(e,t)});return async(e,n)=>{let r=performance.now(),i=(e.init.method??`GET`).toUpperCase();try{let a=await n(e),o=Math.round(performance.now()-r);return t(`${i} ${e.url} ${a.status} (${o}ms)`,{duration:o,method:i,status:a.status,url:e.url}),a}catch(n){let a=Math.round(performance.now()-r);throw t(`${i} ${e.url} ERR (${a}ms)`,{duration:a,method:i,status:0,url:e.url}),n}}}export{t as withBearerAuth,r as withLogging,n as withRequestId};
|
|
2
|
+
//# sourceMappingURL=interceptors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interceptors.js","names":[],"sources":["../src/interceptors.ts"],"sourcesContent":["import { uuid } from '@vielzeug/arsenal';\n\nimport type { Interceptor } from './transport';\n\n/**\n * Attaches a Bearer token `Authorization` header to every request.\n * The token can be a static string or an async function (useful for refresh flows).\n *\n * @example\n * ```ts\n * client.use(withBearerAuth(() => tokenStore.getAccessToken()));\n * ```\n */\nexport function withBearerAuth(token: string | (() => string | Promise<string>)): Interceptor {\n return async (ctx, next) => {\n const t = typeof token === 'function' ? await token() : token;\n\n return next(ctx.withHeaders({ authorization: `Bearer ${t}` }));\n };\n}\n\n/**\n * Adds a unique request ID to every request as a header.\n *\n * Defaults to `x-request-id` populated with `crypto.randomUUID()`.\n *\n * @example\n * ```ts\n * client.use(withRequestId());\n * client.use(withRequestId({ header: 'x-trace-id' }));\n * ```\n */\nexport function withRequestId(opts?: { generate?: () => string; header?: string }): Interceptor {\n const header = opts?.header ?? 'x-request-id';\n const generate = opts?.generate ?? uuid;\n\n return (ctx, next) => next(ctx.withHeaders({ [header]: generate() }));\n}\n\n/**\n * Logs each request's method, URL, status code, and duration.\n *\n * Defaults to `console.debug`. Supply a custom `logger` for structured logging.\n *\n * **Security note:** The full URL is logged, including any query parameters.\n * If your URLs may contain sensitive data (tokens, PII), provide a custom\n * `logger` that sanitizes the URL before writing to persistent logs.\n *\n * @example\n * ```ts\n * client.use(withLogging());\n * client.use(withLogging({ logger: (msg, meta) => logger.info(meta) }));\n * ```\n */\nexport function withLogging(opts?: {\n logger?: (msg: string, meta: { duration: number; method: string; status: number; url: string }) => void;\n}): Interceptor {\n const log =\n opts?.logger ??\n ((msg, meta) => {\n console.debug(msg, meta);\n });\n\n return async (ctx, next) => {\n const start = performance.now();\n const method = ((ctx.init as RequestInit).method ?? 'GET').toUpperCase();\n\n try {\n const res = await next(ctx);\n const duration = Math.round(performance.now() - start);\n\n log(`${method} ${ctx.url} ${res.status} (${duration}ms)`, {\n duration,\n method,\n status: res.status,\n url: ctx.url,\n });\n\n return res;\n } catch (err) {\n const duration = Math.round(performance.now() - start);\n\n log(`${method} ${ctx.url} ERR (${duration}ms)`, {\n duration,\n method,\n status: 0,\n url: ctx.url,\n });\n\n throw err;\n }\n };\n}\n"],"mappings":"yCAaA,SAAgB,EAAe,EAA+D,CAC5F,OAAO,MAAO,EAAK,IAAS,CAC1B,IAAM,EAAI,OAAO,GAAU,WAAa,MAAM,EAAM,EAAI,EAExD,OAAO,EAAK,EAAI,YAAY,CAAE,cAAe,UAAU,GAAI,CAAC,CAAC,CAC/D,CACF,CAaA,SAAgB,EAAc,EAAkE,CAC9F,IAAM,EAAS,GAAM,QAAU,eACzB,EAAW,GAAM,UAAY,EAEnC,OAAQ,EAAK,IAAS,EAAK,EAAI,YAAY,EAAG,GAAS,EAAS,CAAE,CAAC,CAAC,CACtE,CAiBA,SAAgB,EAAY,EAEZ,CACd,IAAM,EACJ,GAAM,UACJ,EAAK,IAAS,CACd,QAAQ,MAAM,EAAK,CAAI,CACzB,GAEF,OAAO,MAAO,EAAK,IAAS,CAC1B,IAAM,EAAQ,YAAY,IAAI,EACxB,GAAW,EAAI,KAAqB,QAAU,MAAA,CAAO,YAAY,EAEvE,GAAI,CACF,IAAM,EAAM,MAAM,EAAK,CAAG,EACpB,EAAW,KAAK,MAAM,YAAY,IAAI,EAAI,CAAK,EASrD,OAPA,EAAI,GAAG,EAAO,GAAG,EAAI,IAAI,GAAG,EAAI,OAAO,IAAI,EAAS,KAAM,CACxD,WACA,SACA,OAAQ,EAAI,OACZ,IAAK,EAAI,GACX,CAAC,EAEM,CACT,OAAS,EAAK,CACZ,IAAM,EAAW,KAAK,MAAM,YAAY,IAAI,EAAI,CAAK,EASrD,MAPA,EAAI,GAAG,EAAO,GAAG,EAAI,IAAI,QAAQ,EAAS,KAAM,CAC9C,WACA,SACA,OAAQ,EACR,IAAK,EAAI,GACX,CAAC,EAEK,CACR,CACF,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
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:!1,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=>(u.add(e),()=>u.delete(e))}},subscribe(e){return u.add(e),()=>u.delete(e)},[Symbol.dispose](){this.dispose()}}}exports.createMutation=a;
|
|
2
|
+
//# sourceMappingURL=mutation.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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: false,\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 observers.add(cb);\n\n return () => observers.delete(cb);\n },\n });\n },\n\n subscribe(onStoreChange: () => void): Unsubscribe {\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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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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,CA6K7B;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
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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:!1,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=>(d.add(e),()=>d.delete(e))}},subscribe(e){return d.add(e),()=>d.delete(e)},[Symbol.dispose](){this.dispose()}}}export{o as createMutation};
|
|
2
|
+
//# sourceMappingURL=mutation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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: false,\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 observers.add(cb);\n\n return () => observers.delete(cb);\n },\n });\n },\n\n subscribe(onStoreChange: () => void): Unsubscribe {\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
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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":"6FAQA,IAAa,EAAa,EAAI,IAuE9B,SAAgB,EAAgB,EAAyD,CACvF,OAAO,OAAO,GAAM,WAAc,EAA0B,EAAI,CAClE,CAEA,SAAgB,EAAQ,EAAuB,CAC7C,OAAA,EAAA,EAAA,KAAA,CAAY,CAAG,CACjB,CAEA,SAAgB,EAAS,EAAgC,CACvD,OAAA,EAAA,EAAA,KAAA,CAAY,CAAI,CAClB,CAEA,SAAgB,EAAe,EAAmB,EAA8B,CAC9E,IAAM,GAAA,EAAA,EAAA,KAAA,CAAiB,CAAG,EACtB,EAAQ,EAAI,QAAQ,IAAI,CAAS,EAmBrC,OAjBK,IACH,EAAQ,CACN,KAAM,IAAA,GACN,MAAO,KACP,KAAM,EACN,SAAU,KACV,WAAY,GACZ,MACA,WAAY,IAAA,GACZ,UAAW,IAAI,IACf,cAAe,EAAI,IAAK,IAAA,EAAA,EAAA,KAAA,CAAW,CAAC,CAAC,EACrC,OAAQ,UACR,UAAW,IAAA,EACb,EACA,EAAI,QAAQ,IAAI,EAAW,CAA4B,GAGlD,CACT,CAEA,SAAgB,EAAS,EAAmB,EAAyB,CACnE,IAAM,EAAQ,EAAI,SAAS,IAAI,CAAS,EAEnC,IAEL,aAAa,CAAK,EAClB,EAAI,SAAS,OAAO,CAAS,EAC/B,CAEA,SAAgB,EAAc,EAAmB,EAAsB,EAAsB,CAK3F,GAJI,EAAI,WAER,EAAS,EAAK,EAAM,IAAI,EAEpB,EAAM,UAAU,KAAO,GAAG,OAE9B,GAAI,IAAW,EAAG,CAChB,EAAI,QAAQ,OAAO,EAAM,IAAI,EAE7B,MACF,CAEA,IAAM,EAAQ,eAAiB,CAC7B,IAAM,EAAU,EAAI,QAAQ,IAAI,EAAM,IAAI,EAEtC,CAAC,GAAW,EAAQ,UAAU,KAAO,GAAK,EAAQ,aAEtD,EAAI,QAAQ,OAAO,EAAM,IAAI,EAC7B,EAAI,SAAS,OAAO,EAAM,IAAI,EAChC,EAAG,CAAM,EAET,EAAI,SAAS,IAAI,EAAM,KAAM,CAAK,CACpC,CAEA,SAAgB,EAAe,EAAmB,EAA4B,CAC5E,EAAM,UAAU,WAAW,MAAM,EACjC,EAAS,EAAK,EAAM,IAAI,EACxB,EAAI,QAAQ,OAAO,EAAM,IAAI,CAC/B,CAEA,SAAgB,EAAc,EAAmB,EAAwC,CAGvF,OAFI,EAAW,OAAS,EAAM,cAAc,OAAe,GAEpD,EAAW,OAAO,EAAK,IAAM,IAAQ,EAAM,cAAc,EAAE,CACpE,CAEA,SAAgB,EAAgB,EAAmB,EAA4B,CAC7E,EAAM,WAAa,GACnB,EAAM,OAAS,UACf,EAAM,MAAQ,KACd,EAAI,OAAO,CAAK,CAClB,CAEA,SAAgB,EAAiB,EAAmB,EAAsB,EAAS,EAAsB,CACvG,EAAM,KAAO,EACb,EAAM,MAAQ,KACd,EAAM,OAAS,UACf,EAAM,UAAY,KAAK,IAAI,EAC3B,EAAM,WAAa,GACnB,EAAM,SAAW,KACjB,EAAW,EAAK,EAAO,CAAM,EAC7B,EAAI,OAAO,CAAK,CAClB,CAEA,SAAgB,EAAe,EAAmB,EAAsB,EAAc,EAAsB,CAC1G,EAAM,MAAQ,EACd,EAAM,OAAS,QACf,EAAM,UAAY,KAAK,IAAI,EAC3B,EAAM,WAAa,GACnB,EAAM,SAAW,KACjB,EAAW,EAAK,EAAO,CAAM,EAC7B,EAAI,OAAO,CAAK,CAClB,CAEA,SAAgB,EAAY,EAAmB,EAAsB,EAA4B,EAAsB,CAQrH,GAPA,EAAM,KAAO,EAAS,KACtB,EAAM,MAAQ,EAAS,MACvB,EAAM,OAAS,EAAS,OACxB,EAAM,UAAY,EAAS,UAC3B,EAAM,WAAa,GACnB,EAAM,SAAW,KAEb,EAAM,UAAU,OAAS,GAAK,EAAM,SAAW,WAAa,EAAM,OAAS,IAAA,GAAW,CACxF,EAAY,EAAK,CAAK,EAEtB,MACF,CAEA,EAAW,EAAK,EAAO,CAAM,EAC7B,EAAI,OAAO,CAAK,CAClB,CAEA,SAAgB,EAAc,EAAmB,EAA4B,CAG3E,GAFA,EAAM,UAAU,WAAW,MAAM,EAE7B,EAAM,UAAU,KAAO,EAAG,CAC5B,EAAM,SAAW,KACjB,EAAM,WAAa,GACnB,EAAM,OAAS,UACf,EAAM,KAAO,IAAA,GACb,EAAM,MAAQ,KACd,EAAM,UAAY,IAAA,GAClB,EAAI,OAAO,CAAK,EAEhB,MACF,CAEA,EAAY,EAAK,CAAK,CACxB,CAEA,SAAgB,EAAc,EAAmB,EAAsB,EAAoC,CACzG,GAAI,EAAM,SAAU,OAAO,EAAM,SAAS,QAE1C,GAAM,CAAE,QAAO,KAAI,SAAQ,cAAa,SAAU,EAC5C,EAAa,IAAI,gBACjB,EAAyB,CAC7B,KAAM,EAAM,KACZ,MAAO,EAAM,MACb,OAAQ,EAAM,OACd,UAAW,EAAM,SACnB,EAEM,GAAW,SAAY,CAC3B,GAAI,CACF,IAAM,EAAO,MAAA,EAAA,EAAA,MAAA,KAAkB,EAAG,CAAE,IAAK,EAAM,IAAK,OAAQ,EAAW,MAAO,CAAC,EAAG,CAChF,MAAQ,GAAY,EAAA,kBAAkB,EAAS,CAAK,EACpD,cACA,OAAQ,EAAW,OACnB,OACF,CAAC,EAID,OAFA,EAAc,EAAK,EAAO,EAAM,CAAM,EAE/B,CACT,OAAS,EAAK,CACZ,IAAM,EAAQ,aAAe,MAAQ,EAAU,MAAM,OAAO,CAAG,CAAC,EAShE,MARkB,EAAW,OAAO,UAAA,EAAA,EAAA,aAAA,CAAwB,CAAK,EAG/D,EAAS,EAAK,EAAO,EAAM,CAAM,EAEjC,EAAY,EAAK,EAAO,EAAO,CAAM,EAGjC,CACR,CACF,EAAA,CAAG,EAKH,MAHA,GAAM,SAAW,CAAE,aAAY,SAAQ,EACvC,EAAa,EAAK,CAAK,EAEhB,CACT,CAEA,SAAgB,EACd,EACA,EAQgB,CAChB,MAAO,CACL,MAAO,EAAQ,OAAS,EAAI,aAC5B,GAAI,EAAQ,GACZ,OAAQ,EAAQ,QAAU,EAAI,cAC9B,YAAa,EAAQ,aAAe,EAAI,mBACxC,UAAW,EAAQ,WAAa,EAAI,iBACpC,MAAO,EAAQ,OAAS,EAAI,YAC9B,CACF,CAEA,SAAgB,EACd,EAOA,EACA,EACA,EACc,CACd,MAAO,CACL,aAAc,EAAK,MACnB,SAAU,GACV,UACA,cAAe,EAAK,QAAA,IACpB,WACA,SACA,mBAAoB,EAAK,YACzB,iBAAkB,EAAK,WAAa,EACpC,aAAc,EAAK,OAAA,CACrB,CACF"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { RetryOptions } from './retry';
|
|
2
|
+
import type { QueryKey } from './types';
|
|
3
|
+
export declare const DEFAULT_GC: number;
|
|
4
|
+
export type QueryFn<T> = (ctx: QueryFnContext) => Promise<T>;
|
|
5
|
+
export type QueryFnContext = {
|
|
6
|
+
key: QueryKey;
|
|
7
|
+
signal: AbortSignal;
|
|
8
|
+
};
|
|
9
|
+
export type FetchConfig<T = unknown> = {
|
|
10
|
+
delay: RetryOptions['delay'];
|
|
11
|
+
fn: QueryFn<T>;
|
|
12
|
+
gcTime: number;
|
|
13
|
+
shouldRetry: RetryOptions['shouldRetry'];
|
|
14
|
+
staleTime: number;
|
|
15
|
+
times: number;
|
|
16
|
+
};
|
|
17
|
+
export type EntryStatus = 'loading' | 'success' | 'error';
|
|
18
|
+
export type CacheEntry<T = unknown> = {
|
|
19
|
+
data: T | undefined;
|
|
20
|
+
error: Error | null;
|
|
21
|
+
hash: string;
|
|
22
|
+
inflight: {
|
|
23
|
+
controller: AbortController;
|
|
24
|
+
promise: Promise<T>;
|
|
25
|
+
} | null;
|
|
26
|
+
isFetching: boolean;
|
|
27
|
+
key: QueryKey;
|
|
28
|
+
lastConfig: FetchConfig<T> | undefined;
|
|
29
|
+
observers: Set<import('./query-observe').QueryObserver<unknown, unknown>>;
|
|
30
|
+
segmentHashes: readonly string[];
|
|
31
|
+
status: EntryStatus;
|
|
32
|
+
updatedAt: number | undefined;
|
|
33
|
+
};
|
|
34
|
+
export type EntrySnapshot<T> = {
|
|
35
|
+
data: T | undefined;
|
|
36
|
+
error: Error | null;
|
|
37
|
+
status: EntryStatus;
|
|
38
|
+
updatedAt: number | undefined;
|
|
39
|
+
};
|
|
40
|
+
export type CacheContext = {
|
|
41
|
+
delayDefault: RetryOptions['delay'];
|
|
42
|
+
disposed: boolean;
|
|
43
|
+
entries: Map<string, CacheEntry>;
|
|
44
|
+
gcTimeDefault: number;
|
|
45
|
+
gcTimers: Map<string, ReturnType<typeof setTimeout>>;
|
|
46
|
+
notify: (entry: CacheEntry) => void;
|
|
47
|
+
shouldRetryDefault: RetryOptions['shouldRetry'];
|
|
48
|
+
staleTimeDefault: number;
|
|
49
|
+
timesDefault: number;
|
|
50
|
+
};
|
|
51
|
+
export declare function makeLoadingState(): {
|
|
52
|
+
data: undefined;
|
|
53
|
+
error: null;
|
|
54
|
+
isFetching: true;
|
|
55
|
+
isLoading: true;
|
|
56
|
+
status: 'loading';
|
|
57
|
+
updatedAt: undefined;
|
|
58
|
+
};
|
|
59
|
+
export declare function resolveValue<T>(v: T | (() => T | undefined) | undefined): T | undefined;
|
|
60
|
+
export declare function hashKey(key: QueryKey): string;
|
|
61
|
+
export declare function hashAtom(atom: QueryKey[number]): string;
|
|
62
|
+
export declare function ensureEntry<T>(ctx: CacheContext, key: QueryKey): CacheEntry<T>;
|
|
63
|
+
export declare function cancelGc(ctx: CacheContext, entryHash: string): void;
|
|
64
|
+
export declare function scheduleGc<T>(ctx: CacheContext, entry: CacheEntry<T>, gcTime: number): void;
|
|
65
|
+
export declare function deleteEntry<T>(ctx: CacheContext, entry: CacheEntry<T>): void;
|
|
66
|
+
export declare function isKeyOrPrefix(entry: CacheEntry, prefixHash: readonly string[]): boolean;
|
|
67
|
+
export declare function markFetching<T>(ctx: CacheContext, entry: CacheEntry<T>): void;
|
|
68
|
+
export declare function commitSuccess<T>(ctx: CacheContext, entry: CacheEntry<T>, data: T, gcTime: number): void;
|
|
69
|
+
export declare function commitError<T>(ctx: CacheContext, entry: CacheEntry<T>, error: Error, gcTime: number): void;
|
|
70
|
+
export declare function rollback<T>(ctx: CacheContext, entry: CacheEntry<T>, previous: EntrySnapshot<T>, gcTime: number): void;
|
|
71
|
+
export declare function evictEntry<T>(ctx: CacheContext, entry: CacheEntry<T>): void;
|
|
72
|
+
export declare function startFetch<T>(ctx: CacheContext, entry: CacheEntry<T>, config: FetchConfig<T>): Promise<T>;
|
|
73
|
+
export declare function makeFetchConfig<T>(ctx: CacheContext, options: {
|
|
74
|
+
delay?: RetryOptions['delay'];
|
|
75
|
+
fn: QueryFn<T>;
|
|
76
|
+
gcTime?: number;
|
|
77
|
+
shouldRetry?: RetryOptions['shouldRetry'];
|
|
78
|
+
staleTime?: number;
|
|
79
|
+
times?: number;
|
|
80
|
+
}): FetchConfig<T>;
|
|
81
|
+
export declare function buildCacheContext(opts: {
|
|
82
|
+
delay?: RetryOptions['delay'];
|
|
83
|
+
gcTime?: number;
|
|
84
|
+
shouldRetry?: RetryOptions['shouldRetry'];
|
|
85
|
+
staleTime?: number;
|
|
86
|
+
times?: number;
|
|
87
|
+
}, entries: Map<string, CacheEntry>, gcTimers: Map<string, ReturnType<typeof setTimeout>>, notify: (entry: CacheEntry) => void): CacheContext;
|
|
88
|
+
//# sourceMappingURL=query-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-cache.d.ts","sourceRoot":"","sources":["../src/query-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{hash as e}from"./serialize.js";import{resolveRetryDelay as t}from"./retry.js";import{isAbortError as n,retry as r}from"@vielzeug/arsenal";var i=5*6e4;function a(e){return typeof e==`function`?e():e}function o(t){return e(t)}function s(t){return e(t)}function c(t,n){let r=e(n),i=t.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(t=>e(t)),status:`loading`,updatedAt:void 0},t.entries.set(r,i)),i}function l(e,t){let n=e.gcTimers.get(t);n&&(clearTimeout(n),e.gcTimers.delete(t))}function u(e,t,n){if(e.disposed||(l(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 d(e,t){t.inflight?.controller.abort(),l(e,t.hash),e.entries.delete(t.hash)}function f(e,t){return t.length>e.segmentHashes.length?!1:t.every((t,n)=>t===e.segmentHashes[n])}function p(e,t){t.isFetching=!0,t.status=`loading`,t.error=null,e.notify(t)}function m(e,t,n,r){t.data=n,t.error=null,t.status=`success`,t.updatedAt=Date.now(),t.isFetching=!1,t.inflight=null,u(e,t,r),e.notify(t)}function h(e,t,n,r){t.error=n,t.status=`error`,t.updatedAt=Date.now(),t.isFetching=!1,t.inflight=null,u(e,t,r),e.notify(t)}function g(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){d(e,t);return}u(e,t,r),e.notify(t)}function _(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}d(e,t)}function v(e,i,a){if(i.inflight)return i.inflight.promise;let{delay:o,fn:s,gcTime:c,shouldRetry:l,times:u}=a,d=new AbortController,f={data:i.data,error:i.error,status:i.status,updatedAt:i.updatedAt},_=(async()=>{try{let n=await r(()=>s({key:i.key,signal:d.signal}),{delay:e=>t(e,o),shouldRetry:l,signal:d.signal,times:u});return m(e,i,n,c),n}catch(t){let r=t instanceof Error?t:Error(String(t));throw d.signal.aborted||n(r)?g(e,i,f,c):h(e,i,r,c),r}})();return i.inflight={controller:d,promise:_},p(e,i),_}function y(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 b(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}}export{i as DEFAULT_GC,b as buildCacheContext,l as cancelGc,h as commitError,m as commitSuccess,d as deleteEntry,c as ensureEntry,_ as evictEntry,s as hashAtom,o as hashKey,f as isKeyOrPrefix,y as makeFetchConfig,p as markFetching,a as resolveValue,g as rollback,u as scheduleGc,v as startFetch};
|
|
2
|
+
//# sourceMappingURL=query-cache.js.map
|