@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":"query-cache.js","names":[],"sources":["../src/query-cache.ts"],"sourcesContent":["import { isAbortError, retry } from '@vielzeug/arsenal';\n\nimport type { RetryOptions } from './retry';\nimport type { QueryKey } from './types';\n\nimport { DEFAULT_TIMES, resolveRetryDelay } from './retry';\nimport { hash } from './serialize';\n\nexport const DEFAULT_GC = 5 * 60_000;\n\nexport type QueryFn<T> = (ctx: QueryFnContext) => Promise<T>;\n\nexport type QueryFnContext = {\n key: QueryKey;\n signal: AbortSignal;\n};\n\nexport type FetchConfig<T = unknown> = {\n delay: RetryOptions['delay'];\n fn: QueryFn<T>;\n gcTime: number;\n shouldRetry: RetryOptions['shouldRetry'];\n staleTime: number;\n times: number;\n};\n\nexport type EntryStatus = 'loading' | 'success' | 'error';\n\nexport type CacheEntry<T = unknown> = {\n data: T | undefined;\n error: Error | null;\n hash: string;\n inflight: { controller: AbortController; promise: Promise<T> } | null;\n isFetching: boolean;\n key: QueryKey;\n lastConfig: FetchConfig<T> | undefined;\n observers: Set<import('./query-observe').QueryObserver<unknown, unknown>>;\n segmentHashes: readonly string[];\n status: EntryStatus;\n updatedAt: number | undefined;\n};\n\nexport type EntrySnapshot<T> = {\n data: T | undefined;\n error: Error | null;\n status: EntryStatus;\n updatedAt: number | undefined;\n};\n\nexport type CacheContext = {\n delayDefault: RetryOptions['delay'];\n disposed: boolean;\n entries: Map<string, CacheEntry>;\n gcTimeDefault: number;\n gcTimers: Map<string, ReturnType<typeof setTimeout>>;\n notify: (entry: CacheEntry) => void;\n shouldRetryDefault: RetryOptions['shouldRetry'];\n staleTimeDefault: number;\n timesDefault: number;\n};\n\nexport function makeLoadingState(): {\n data: undefined;\n error: null;\n isFetching: true;\n isLoading: true;\n status: 'loading';\n updatedAt: undefined;\n} {\n return {\n data: undefined,\n error: null,\n isFetching: true,\n isLoading: true,\n status: 'loading',\n updatedAt: undefined,\n };\n}\n\nexport function resolveValue<T>(v: T | (() => T | undefined) | undefined): T | undefined {\n return typeof v === 'function' ? (v as () => T | undefined)() : v;\n}\n\nexport function hashKey(key: QueryKey): string {\n return hash(key);\n}\n\nexport function hashAtom(atom: QueryKey[number]): string {\n return hash(atom);\n}\n\nexport function ensureEntry<T>(ctx: CacheContext, key: QueryKey): CacheEntry<T> {\n const entryHash = hash(key);\n let entry = ctx.entries.get(entryHash) as CacheEntry<T> | undefined;\n\n if (!entry) {\n entry = {\n data: undefined,\n error: null,\n hash: entryHash,\n inflight: null,\n isFetching: false,\n key,\n lastConfig: undefined,\n observers: new Set(),\n segmentHashes: key.map((k) => hash(k)),\n status: 'loading',\n updatedAt: undefined,\n };\n ctx.entries.set(entryHash, entry as CacheEntry<unknown>);\n }\n\n return entry;\n}\n\nexport function cancelGc(ctx: CacheContext, entryHash: string): void {\n const timer = ctx.gcTimers.get(entryHash);\n\n if (!timer) return;\n\n clearTimeout(timer);\n ctx.gcTimers.delete(entryHash);\n}\n\nexport function scheduleGc<T>(ctx: CacheContext, entry: CacheEntry<T>, gcTime: number): void {\n if (ctx.disposed) return;\n\n cancelGc(ctx, entry.hash);\n\n if (entry.observers.size > 0) return;\n\n if (gcTime === 0) {\n ctx.entries.delete(entry.hash);\n\n return;\n }\n\n const timer = setTimeout(() => {\n const current = ctx.entries.get(entry.hash);\n\n if (!current || current.observers.size > 0 || current.isFetching) return;\n\n ctx.entries.delete(entry.hash);\n ctx.gcTimers.delete(entry.hash);\n }, gcTime);\n\n ctx.gcTimers.set(entry.hash, timer);\n}\n\nexport function deleteEntry<T>(ctx: CacheContext, entry: CacheEntry<T>): void {\n entry.inflight?.controller.abort();\n cancelGc(ctx, entry.hash);\n ctx.entries.delete(entry.hash);\n}\n\nexport function isKeyOrPrefix(entry: CacheEntry, prefixHash: readonly string[]): boolean {\n if (prefixHash.length > entry.segmentHashes.length) return false;\n\n return prefixHash.every((seg, i) => seg === entry.segmentHashes[i]);\n}\n\nexport function markFetching<T>(ctx: CacheContext, entry: CacheEntry<T>): void {\n entry.isFetching = true;\n entry.status = 'loading';\n entry.error = null;\n ctx.notify(entry);\n}\n\nexport function commitSuccess<T>(ctx: CacheContext, entry: CacheEntry<T>, data: T, gcTime: number): void {\n entry.data = data;\n entry.error = null;\n entry.status = 'success';\n entry.updatedAt = Date.now();\n entry.isFetching = false;\n entry.inflight = null;\n scheduleGc(ctx, entry, gcTime);\n ctx.notify(entry);\n}\n\nexport function commitError<T>(ctx: CacheContext, entry: CacheEntry<T>, error: Error, gcTime: number): void {\n entry.error = error;\n entry.status = 'error';\n entry.updatedAt = Date.now();\n entry.isFetching = false;\n entry.inflight = null;\n scheduleGc(ctx, entry, gcTime);\n ctx.notify(entry);\n}\n\nexport function rollback<T>(ctx: CacheContext, entry: CacheEntry<T>, previous: EntrySnapshot<T>, gcTime: number): void {\n entry.data = previous.data;\n entry.error = previous.error;\n entry.status = previous.status;\n entry.updatedAt = previous.updatedAt;\n entry.isFetching = false;\n entry.inflight = null;\n\n if (entry.observers.size === 0 && entry.status === 'loading' && entry.data === undefined) {\n deleteEntry(ctx, entry);\n\n return;\n }\n\n scheduleGc(ctx, entry, gcTime);\n ctx.notify(entry);\n}\n\nexport function evictEntry<T>(ctx: CacheContext, entry: CacheEntry<T>): void {\n entry.inflight?.controller.abort();\n\n if (entry.observers.size > 0) {\n entry.inflight = null;\n entry.isFetching = false;\n entry.status = 'loading';\n entry.data = undefined;\n entry.error = null;\n entry.updatedAt = undefined;\n ctx.notify(entry);\n\n return;\n }\n\n deleteEntry(ctx, entry);\n}\n\nexport function startFetch<T>(ctx: CacheContext, entry: CacheEntry<T>, config: FetchConfig<T>): Promise<T> {\n if (entry.inflight) return entry.inflight.promise;\n\n const { delay, fn, gcTime, shouldRetry, times } = config;\n const controller = new AbortController();\n const prev: EntrySnapshot<T> = {\n data: entry.data,\n error: entry.error,\n status: entry.status,\n updatedAt: entry.updatedAt,\n };\n\n const promise = (async () => {\n try {\n const data = await retry(() => fn({ key: entry.key, signal: controller.signal }), {\n delay: (attempt) => resolveRetryDelay(attempt, delay),\n shouldRetry,\n signal: controller.signal,\n times,\n });\n\n commitSuccess(ctx, entry, data, gcTime);\n\n return data;\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n const isAborted = controller.signal.aborted || isAbortError(error);\n\n if (isAborted) {\n rollback(ctx, entry, prev, gcTime);\n } else {\n commitError(ctx, entry, error, gcTime);\n }\n\n throw error;\n }\n })();\n\n entry.inflight = { controller, promise };\n markFetching(ctx, entry);\n\n return promise;\n}\n\nexport function makeFetchConfig<T>(\n ctx: CacheContext,\n options: {\n delay?: RetryOptions['delay'];\n fn: QueryFn<T>;\n gcTime?: number;\n shouldRetry?: RetryOptions['shouldRetry'];\n staleTime?: number;\n times?: number;\n },\n): FetchConfig<T> {\n return {\n delay: options.delay ?? ctx.delayDefault,\n fn: options.fn,\n gcTime: options.gcTime ?? ctx.gcTimeDefault,\n shouldRetry: options.shouldRetry ?? ctx.shouldRetryDefault,\n staleTime: options.staleTime ?? ctx.staleTimeDefault,\n times: options.times ?? ctx.timesDefault,\n };\n}\n\nexport function buildCacheContext(\n opts: {\n delay?: RetryOptions['delay'];\n gcTime?: number;\n shouldRetry?: RetryOptions['shouldRetry'];\n staleTime?: number;\n times?: number;\n },\n entries: Map<string, CacheEntry>,\n gcTimers: Map<string, ReturnType<typeof setTimeout>>,\n notify: (entry: CacheEntry) => void,\n): CacheContext {\n return {\n delayDefault: opts.delay,\n disposed: false,\n entries,\n gcTimeDefault: opts.gcTime ?? DEFAULT_GC,\n gcTimers,\n notify,\n shouldRetryDefault: opts.shouldRetry,\n staleTimeDefault: opts.staleTime ?? 0,\n timesDefault: opts.times ?? DEFAULT_TIMES,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./query-cache.cjs");var t=Object.freeze({data:void 0,error:null,isFetching:!1,isLoading:!0,status:`loading`,updatedAt:void 0});function n(e){return e.status===`loading`?{data:void 0,error:null,isFetching:e.isFetching,isLoading:!0,status:`loading`,updatedAt:void 0}:e.status===`success`?{data:e.data,error:null,isFetching:e.isFetching,isLoading:!1,status:`success`,updatedAt:e.updatedAt}:{data:e.data,error:e.error,isFetching:e.isFetching,isLoading:!1,status:`error`,updatedAt:e.updatedAt}}function r(t,r){let i=n(t);if(i.status===`loading`){let t=e.resolveValue(r.placeholderData);return{...i,data:t}}let a=r.select?r.select(i.data):i.data;return{...i,data:a}}function i(e){for(let t of e.observers){let n=t,i=r(e,n),a=n.previous,o=Object.is(a?.data,i.data),s=!!a&&a.status===i.status&&a.isFetching===i.isFetching&&Object.is(a.error,i.error);(n.select?s&&o:s&&a?.updatedAt===i.updatedAt&&o)||(n.previous=i,n.listener())}}function a(n,i,a){let o={listener:()=>{},...a};return{peek(){let a=n.entries.get(e.hashKey(i));return a?r(a,o):t},subscribe(t){let s=e.ensureEntry(n,i);e.cancelGc(n,s.hash);let c={...a,listener:t,previous:r(s,o)};return s.observers.add(c),()=>{s.observers.delete(c),s.observers.size===0&&e.scheduleGc(n,s,s.lastConfig?.gcTime??n.gcTimeDefault)}}}}exports.notify=i,exports.toBaseState=n,exports.toObserverState=r,exports.watchInternal=a;
|
|
2
|
+
//# sourceMappingURL=query-observe.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-observe.cjs","names":[],"sources":["../src/query-observe.ts"],"sourcesContent":["import type { CacheContext, CacheEntry } from './query-cache';\nimport type { AsyncState, QueryState, SyncStore, Unsubscribe } from './types';\n\nimport { cancelGc, ensureEntry, hashKey, resolveValue, scheduleGc } from './query-cache';\n\nexport type QueryObserver<T, S> = {\n listener: () => void;\n placeholderData?: S | (() => S | undefined);\n previous?: QueryState<S>;\n select?: (data: T | undefined) => S | undefined;\n};\n\nconst LOADING_STATE: AsyncState<unknown> = Object.freeze({\n data: undefined,\n error: null,\n isFetching: false,\n isLoading: true,\n status: 'loading',\n updatedAt: undefined,\n});\n\nexport function toBaseState<T>(entry: CacheEntry<T>): QueryState<T> {\n if (entry.status === 'loading') {\n return {\n data: undefined,\n error: null,\n isFetching: entry.isFetching,\n isLoading: true,\n status: 'loading',\n updatedAt: undefined,\n } as QueryState<T>;\n }\n\n if (entry.status === 'success') {\n return {\n data: entry.data as T,\n error: null,\n isFetching: entry.isFetching,\n isLoading: false,\n status: 'success',\n updatedAt: entry.updatedAt as number,\n };\n }\n\n return {\n data: entry.data,\n error: entry.error as Error,\n isFetching: entry.isFetching,\n isLoading: false,\n status: 'error',\n updatedAt: entry.updatedAt as number,\n };\n}\n\nexport function toObserverState<T, S>(entry: CacheEntry<T>, observer: QueryObserver<T, S>): QueryState<S> {\n const base = toBaseState(entry);\n\n if (base.status === 'loading') {\n const placeholder = resolveValue(observer.placeholderData as S | (() => S | undefined) | undefined);\n\n return { ...base, data: placeholder } as QueryState<S>;\n }\n\n const selected = observer.select ? observer.select(base.data as T | undefined) : (base.data as S | undefined);\n\n return { ...base, data: selected } as QueryState<S>;\n}\n\nexport function notify<T>(entry: CacheEntry<T>): void {\n for (const observer of entry.observers) {\n const typed = observer as QueryObserver<T, unknown>;\n const next = toObserverState(entry, typed);\n const prev = typed.previous;\n\n const dataUnchanged = Object.is(prev?.data, next.data);\n const metaUnchanged =\n !!prev && prev.status === next.status && prev.isFetching === next.isFetching && Object.is(prev.error, next.error);\n\n const shouldSkip = typed.select\n ? metaUnchanged && dataUnchanged\n : metaUnchanged && prev?.updatedAt === next.updatedAt && dataUnchanged;\n\n if (shouldSkip) continue;\n\n typed.previous = next;\n typed.listener();\n }\n}\n\nexport function watchInternal<T = unknown, S = T>(\n ctx: CacheContext,\n key: Parameters<typeof ensureEntry>[1],\n opts?: { placeholderData?: S | (() => S | undefined); select?: (data: T | undefined) => S | undefined },\n): SyncStore<QueryState<S>> {\n const peekObserver: QueryObserver<T, S> = { listener: () => {}, ...opts };\n\n return {\n peek(): QueryState<S> {\n const entry = ctx.entries.get(hashKey(key)) as CacheEntry<T> | undefined;\n\n if (!entry) return LOADING_STATE as QueryState<S>;\n\n return toObserverState(entry, peekObserver);\n },\n\n subscribe(onStoreChange: () => void): Unsubscribe {\n const entry = ensureEntry<T>(ctx, key);\n\n cancelGc(ctx, entry.hash);\n\n const observer: QueryObserver<T, S> = {\n ...opts,\n listener: onStoreChange,\n previous: toObserverState(entry, peekObserver),\n };\n\n entry.observers.add(observer as QueryObserver<unknown, unknown>);\n\n return () => {\n entry.observers.delete(observer as QueryObserver<unknown, unknown>);\n\n if (entry.observers.size === 0) {\n scheduleGc(ctx, entry, entry.lastConfig?.gcTime ?? ctx.gcTimeDefault);\n }\n };\n },\n };\n}\n"],"mappings":"qCAYA,IAAM,EAAqC,OAAO,OAAO,CACvD,KAAM,IAAA,GACN,MAAO,KACP,WAAY,GACZ,UAAW,GACX,OAAQ,UACR,UAAW,IAAA,EACb,CAAC,EAED,SAAgB,EAAe,EAAqC,CAuBlE,OAtBI,EAAM,SAAW,UACZ,CACL,KAAM,IAAA,GACN,MAAO,KACP,WAAY,EAAM,WAClB,UAAW,GACX,OAAQ,UACR,UAAW,IAAA,EACb,EAGE,EAAM,SAAW,UACZ,CACL,KAAM,EAAM,KACZ,MAAO,KACP,WAAY,EAAM,WAClB,UAAW,GACX,OAAQ,UACR,UAAW,EAAM,SACnB,EAGK,CACL,KAAM,EAAM,KACZ,MAAO,EAAM,MACb,WAAY,EAAM,WAClB,UAAW,GACX,OAAQ,QACR,UAAW,EAAM,SACnB,CACF,CAEA,SAAgB,EAAsB,EAAsB,EAA8C,CACxG,IAAM,EAAO,EAAY,CAAK,EAE9B,GAAI,EAAK,SAAW,UAAW,CAC7B,IAAM,EAAc,EAAA,aAAa,EAAS,eAAwD,EAElG,MAAO,CAAE,GAAG,EAAM,KAAM,CAAY,CACtC,CAEA,IAAM,EAAW,EAAS,OAAS,EAAS,OAAO,EAAK,IAAqB,EAAK,EAAK,KAEvF,MAAO,CAAE,GAAG,EAAM,KAAM,CAAS,CACnC,CAEA,SAAgB,EAAU,EAA4B,CACpD,IAAK,IAAM,KAAY,EAAM,UAAW,CACtC,IAAM,EAAQ,EACR,EAAO,EAAgB,EAAO,CAAK,EACnC,EAAO,EAAM,SAEb,EAAgB,OAAO,GAAG,GAAM,KAAM,EAAK,IAAI,EAC/C,EACJ,CAAC,CAAC,GAAQ,EAAK,SAAW,EAAK,QAAU,EAAK,aAAe,EAAK,YAAc,OAAO,GAAG,EAAK,MAAO,EAAK,KAAK,GAE/F,EAAM,OACrB,GAAiB,EACjB,GAAiB,GAAM,YAAc,EAAK,WAAa,KAI3D,EAAM,SAAW,EACjB,EAAM,SAAS,EACjB,CACF,CAEA,SAAgB,EACd,EACA,EACA,EAC0B,CAC1B,IAAM,EAAoC,CAAE,aAAgB,CAAC,EAAG,GAAG,CAAK,EAExE,MAAO,CACL,MAAsB,CACpB,IAAM,EAAQ,EAAI,QAAQ,IAAI,EAAA,QAAQ,CAAG,CAAC,EAI1C,OAFK,EAEE,EAAgB,EAAO,CAAY,EAFvB,CAGrB,EAEA,UAAU,EAAwC,CAChD,IAAM,EAAQ,EAAA,YAAe,EAAK,CAAG,EAErC,EAAA,SAAS,EAAK,EAAM,IAAI,EAExB,IAAM,EAAgC,CACpC,GAAG,EACH,SAAU,EACV,SAAU,EAAgB,EAAO,CAAY,CAC/C,EAIA,OAFA,EAAM,UAAU,IAAI,CAA2C,MAElD,CACX,EAAM,UAAU,OAAO,CAA2C,EAE9D,EAAM,UAAU,OAAS,GAC3B,EAAA,WAAW,EAAK,EAAO,EAAM,YAAY,QAAU,EAAI,aAAa,CAExE,CACF,CACF,CACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CacheContext, CacheEntry } from './query-cache';
|
|
2
|
+
import type { QueryState, SyncStore } from './types';
|
|
3
|
+
import { ensureEntry } from './query-cache';
|
|
4
|
+
export type QueryObserver<T, S> = {
|
|
5
|
+
listener: () => void;
|
|
6
|
+
placeholderData?: S | (() => S | undefined);
|
|
7
|
+
previous?: QueryState<S>;
|
|
8
|
+
select?: (data: T | undefined) => S | undefined;
|
|
9
|
+
};
|
|
10
|
+
export declare function toBaseState<T>(entry: CacheEntry<T>): QueryState<T>;
|
|
11
|
+
export declare function toObserverState<T, S>(entry: CacheEntry<T>, observer: QueryObserver<T, S>): QueryState<S>;
|
|
12
|
+
export declare function notify<T>(entry: CacheEntry<T>): void;
|
|
13
|
+
export declare function watchInternal<T = unknown, S = T>(ctx: CacheContext, key: Parameters<typeof ensureEntry>[1], opts?: {
|
|
14
|
+
placeholderData?: S | (() => S | undefined);
|
|
15
|
+
select?: (data: T | undefined) => S | undefined;
|
|
16
|
+
}): SyncStore<QueryState<S>>;
|
|
17
|
+
//# sourceMappingURL=query-observe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-observe.d.ts","sourceRoot":"","sources":["../src/query-observe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,KAAK,EAAc,UAAU,EAAE,SAAS,EAAe,MAAM,SAAS,CAAC;AAE9E,OAAO,EAAY,WAAW,EAAqC,MAAM,eAAe,CAAC;AAEzF,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,IAAI;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,eAAe,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,SAAS,CAAC;CACjD,CAAC;AAWF,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CA+BlE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAYxG;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAmBpD;AAED,wBAAgB,aAAa,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,EAC9C,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,EACtC,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,SAAS,CAAA;CAAE,GACtG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAkC1B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{cancelGc as e,ensureEntry as t,hashKey as n,resolveValue as r,scheduleGc as i}from"./query-cache.js";var a=Object.freeze({data:void 0,error:null,isFetching:!1,isLoading:!0,status:`loading`,updatedAt:void 0});function o(e){return e.status===`loading`?{data:void 0,error:null,isFetching:e.isFetching,isLoading:!0,status:`loading`,updatedAt:void 0}:e.status===`success`?{data:e.data,error:null,isFetching:e.isFetching,isLoading:!1,status:`success`,updatedAt:e.updatedAt}:{data:e.data,error:e.error,isFetching:e.isFetching,isLoading:!1,status:`error`,updatedAt:e.updatedAt}}function s(e,t){let n=o(e);if(n.status===`loading`){let e=r(t.placeholderData);return{...n,data:e}}let i=t.select?t.select(n.data):n.data;return{...n,data:i}}function c(e){for(let t of e.observers){let n=t,r=s(e,n),i=n.previous,a=Object.is(i?.data,r.data),o=!!i&&i.status===r.status&&i.isFetching===r.isFetching&&Object.is(i.error,r.error);(n.select?o&&a:o&&i?.updatedAt===r.updatedAt&&a)||(n.previous=r,n.listener())}}function l(r,o,c){let l={listener:()=>{},...c};return{peek(){let e=r.entries.get(n(o));return e?s(e,l):a},subscribe(n){let a=t(r,o);e(r,a.hash);let u={...c,listener:n,previous:s(a,l)};return a.observers.add(u),()=>{a.observers.delete(u),a.observers.size===0&&i(r,a,a.lastConfig?.gcTime??r.gcTimeDefault)}}}}export{c as notify,o as toBaseState,s as toObserverState,l as watchInternal};
|
|
2
|
+
//# sourceMappingURL=query-observe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-observe.js","names":[],"sources":["../src/query-observe.ts"],"sourcesContent":["import type { CacheContext, CacheEntry } from './query-cache';\nimport type { AsyncState, QueryState, SyncStore, Unsubscribe } from './types';\n\nimport { cancelGc, ensureEntry, hashKey, resolveValue, scheduleGc } from './query-cache';\n\nexport type QueryObserver<T, S> = {\n listener: () => void;\n placeholderData?: S | (() => S | undefined);\n previous?: QueryState<S>;\n select?: (data: T | undefined) => S | undefined;\n};\n\nconst LOADING_STATE: AsyncState<unknown> = Object.freeze({\n data: undefined,\n error: null,\n isFetching: false,\n isLoading: true,\n status: 'loading',\n updatedAt: undefined,\n});\n\nexport function toBaseState<T>(entry: CacheEntry<T>): QueryState<T> {\n if (entry.status === 'loading') {\n return {\n data: undefined,\n error: null,\n isFetching: entry.isFetching,\n isLoading: true,\n status: 'loading',\n updatedAt: undefined,\n } as QueryState<T>;\n }\n\n if (entry.status === 'success') {\n return {\n data: entry.data as T,\n error: null,\n isFetching: entry.isFetching,\n isLoading: false,\n status: 'success',\n updatedAt: entry.updatedAt as number,\n };\n }\n\n return {\n data: entry.data,\n error: entry.error as Error,\n isFetching: entry.isFetching,\n isLoading: false,\n status: 'error',\n updatedAt: entry.updatedAt as number,\n };\n}\n\nexport function toObserverState<T, S>(entry: CacheEntry<T>, observer: QueryObserver<T, S>): QueryState<S> {\n const base = toBaseState(entry);\n\n if (base.status === 'loading') {\n const placeholder = resolveValue(observer.placeholderData as S | (() => S | undefined) | undefined);\n\n return { ...base, data: placeholder } as QueryState<S>;\n }\n\n const selected = observer.select ? observer.select(base.data as T | undefined) : (base.data as S | undefined);\n\n return { ...base, data: selected } as QueryState<S>;\n}\n\nexport function notify<T>(entry: CacheEntry<T>): void {\n for (const observer of entry.observers) {\n const typed = observer as QueryObserver<T, unknown>;\n const next = toObserverState(entry, typed);\n const prev = typed.previous;\n\n const dataUnchanged = Object.is(prev?.data, next.data);\n const metaUnchanged =\n !!prev && prev.status === next.status && prev.isFetching === next.isFetching && Object.is(prev.error, next.error);\n\n const shouldSkip = typed.select\n ? metaUnchanged && dataUnchanged\n : metaUnchanged && prev?.updatedAt === next.updatedAt && dataUnchanged;\n\n if (shouldSkip) continue;\n\n typed.previous = next;\n typed.listener();\n }\n}\n\nexport function watchInternal<T = unknown, S = T>(\n ctx: CacheContext,\n key: Parameters<typeof ensureEntry>[1],\n opts?: { placeholderData?: S | (() => S | undefined); select?: (data: T | undefined) => S | undefined },\n): SyncStore<QueryState<S>> {\n const peekObserver: QueryObserver<T, S> = { listener: () => {}, ...opts };\n\n return {\n peek(): QueryState<S> {\n const entry = ctx.entries.get(hashKey(key)) as CacheEntry<T> | undefined;\n\n if (!entry) return LOADING_STATE as QueryState<S>;\n\n return toObserverState(entry, peekObserver);\n },\n\n subscribe(onStoreChange: () => void): Unsubscribe {\n const entry = ensureEntry<T>(ctx, key);\n\n cancelGc(ctx, entry.hash);\n\n const observer: QueryObserver<T, S> = {\n ...opts,\n listener: onStoreChange,\n previous: toObserverState(entry, peekObserver),\n };\n\n entry.observers.add(observer as QueryObserver<unknown, unknown>);\n\n return () => {\n entry.observers.delete(observer as QueryObserver<unknown, unknown>);\n\n if (entry.observers.size === 0) {\n scheduleGc(ctx, entry, entry.lastConfig?.gcTime ?? ctx.gcTimeDefault);\n }\n };\n },\n };\n}\n"],"mappings":"4GAYA,IAAM,EAAqC,OAAO,OAAO,CACvD,KAAM,IAAA,GACN,MAAO,KACP,WAAY,GACZ,UAAW,GACX,OAAQ,UACR,UAAW,IAAA,EACb,CAAC,EAED,SAAgB,EAAe,EAAqC,CAuBlE,OAtBI,EAAM,SAAW,UACZ,CACL,KAAM,IAAA,GACN,MAAO,KACP,WAAY,EAAM,WAClB,UAAW,GACX,OAAQ,UACR,UAAW,IAAA,EACb,EAGE,EAAM,SAAW,UACZ,CACL,KAAM,EAAM,KACZ,MAAO,KACP,WAAY,EAAM,WAClB,UAAW,GACX,OAAQ,UACR,UAAW,EAAM,SACnB,EAGK,CACL,KAAM,EAAM,KACZ,MAAO,EAAM,MACb,WAAY,EAAM,WAClB,UAAW,GACX,OAAQ,QACR,UAAW,EAAM,SACnB,CACF,CAEA,SAAgB,EAAsB,EAAsB,EAA8C,CACxG,IAAM,EAAO,EAAY,CAAK,EAE9B,GAAI,EAAK,SAAW,UAAW,CAC7B,IAAM,EAAc,EAAa,EAAS,eAAwD,EAElG,MAAO,CAAE,GAAG,EAAM,KAAM,CAAY,CACtC,CAEA,IAAM,EAAW,EAAS,OAAS,EAAS,OAAO,EAAK,IAAqB,EAAK,EAAK,KAEvF,MAAO,CAAE,GAAG,EAAM,KAAM,CAAS,CACnC,CAEA,SAAgB,EAAU,EAA4B,CACpD,IAAK,IAAM,KAAY,EAAM,UAAW,CACtC,IAAM,EAAQ,EACR,EAAO,EAAgB,EAAO,CAAK,EACnC,EAAO,EAAM,SAEb,EAAgB,OAAO,GAAG,GAAM,KAAM,EAAK,IAAI,EAC/C,EACJ,CAAC,CAAC,GAAQ,EAAK,SAAW,EAAK,QAAU,EAAK,aAAe,EAAK,YAAc,OAAO,GAAG,EAAK,MAAO,EAAK,KAAK,GAE/F,EAAM,OACrB,GAAiB,EACjB,GAAiB,GAAM,YAAc,EAAK,WAAa,KAI3D,EAAM,SAAW,EACjB,EAAM,SAAS,EACjB,CACF,CAEA,SAAgB,EACd,EACA,EACA,EAC0B,CAC1B,IAAM,EAAoC,CAAE,aAAgB,CAAC,EAAG,GAAG,CAAK,EAExE,MAAO,CACL,MAAsB,CACpB,IAAM,EAAQ,EAAI,QAAQ,IAAI,EAAQ,CAAG,CAAC,EAI1C,OAFK,EAEE,EAAgB,EAAO,CAAY,EAFvB,CAGrB,EAEA,UAAU,EAAwC,CAChD,IAAM,EAAQ,EAAe,EAAK,CAAG,EAErC,EAAS,EAAK,EAAM,IAAI,EAExB,IAAM,EAAgC,CACpC,GAAG,EACH,SAAU,EACV,SAAU,EAAgB,EAAO,CAAY,CAC/C,EAIA,OAFA,EAAM,UAAU,IAAI,CAA2C,MAElD,CACX,EAAM,UAAU,OAAO,CAA2C,EAE9D,EAAM,UAAU,OAAS,GAC3B,EAAW,EAAK,EAAO,EAAM,YAAY,QAAU,EAAI,aAAa,CAExE,CACF,CACF,CACF"}
|
package/dist/query.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./query-cache.cjs"),n=require("./query-observe.cjs");function r(r){let i=!1,a=new AbortController,o=new Map,s=new Map,c=t.buildCacheContext({delay:r?.delay,gcTime:r?.gcTime,shouldRetry:r?.shouldRetry,staleTime:r?.staleTime,times:r?.times},o,s,n.notify);async function l(n){if(i)throw new e.CourierDisposedError(`QueryClient`);let{enabled:r=!0,fn:a,gcTime:s,initialData:l,key:u,staleTime:d,...f}=n;if(!r){let e=t.hashKey(u);return o.get(e)?.data??void 0}let p=t.ensureEntry(c,u),m=t.makeFetchConfig(c,{fn:a,gcTime:s,staleTime:d,...f});if(l!==void 0&&p.status===`loading`&&p.data===void 0){let e=t.resolveValue(l);e!==void 0&&(p.data=e,p.error=null,p.status=`success`,p.updatedAt=Date.now())}return p.status===`success`&&p.updatedAt!==void 0&&Date.now()-p.updatedAt<m.staleTime&&!p.isFetching?(p.lastConfig=m,p.data):(p.lastConfig=m,p.inflight?p.inflight.promise:t.startFetch(c,p,m))}function u(e){if(!e.lastConfig){t.evictEntry(c,e);return}e.isFetching||t.startFetch(c,e,e.lastConfig).catch(()=>{})}function d(e){let n=e.map(e=>t.hashAtom(e));for(let e of[...o.values()])if(t.isKeyOrPrefix(e,n)){if(e.observers.size>0){u(e);continue}t.evictEntry(c,e)}}function f(e,r,i){let a=t.ensureEntry(c,e),o=i?.gcTime??a.lastConfig?.gcTime??c.gcTimeDefault;a.data=typeof r==`function`?r(a.data):r,a.error=null,a.status=`success`,a.updatedAt=i?.updatedAt??Date.now(),a.isFetching=!1,a.inflight=null,n.notify(a),t.scheduleGc(c,a,o)}function p(e){return e.observers.size===0||!e.lastConfig||e.isFetching?!1:e.status===`success`&&e.updatedAt!==void 0||e.status===`error`&&e.data!==void 0&&e.updatedAt!==void 0?Date.now()-e.updatedAt>=e.lastConfig.staleTime:!1}function m(e){let{fetch:t=!0,key:r,placeholderData:i,select:a}=e,o=n.watchInternal(c,r,{placeholderData:i,select:a});return t&&e.fn&&l({...e}).catch(()=>{}),o}return{cancel(e){let n=o.get(t.hashKey(e));n?.inflight&&n.inflight.controller.abort()},cancelAll(){for(let e of o.values())e.inflight?.controller.abort()},clear(){for(let e of[...o.values()])t.evictEntry(c,e)},get disposalSignal(){return a.signal},dispose(){if(!i){i=!0,c.disposed=!0,a.abort();for(let[,e]of o)e.observers.clear(),e.inflight?.controller.abort();for(let[,e]of s)clearTimeout(e);s.clear(),o.clear()}},get disposed(){return i},fetch:l,fetchMany(e){return Promise.all(e.map(e=>l(e)))},get(e){let n=o.get(t.hashKey(e));return n?n.data:void 0},getState(e){let r=o.get(t.hashKey(e));return r?n.toBaseState(r):null},invalidate:d,keys(){return[...o.values()].map(e=>e.key)},observe:m,observeMany(e){let t=e.map(e=>n.watchInternal(c,e));return{peek(){return t.map(e=>e.peek())},subscribe(e){let n=t.map(t=>t.subscribe(e));return()=>{for(let e of n)e()}}}},refetchStale(){for(let e of[...o.values()])p(e)&&t.startFetch(c,e,e.lastConfig).catch(()=>{})},remove(e){let n=o.get(t.hashKey(e));n&&t.evictEntry(c,n)},set:f,get size(){return o.size},[Symbol.dispose](){this.dispose()}}}exports.createQuery=r;
|
|
2
|
+
//# sourceMappingURL=query.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.cjs","names":[],"sources":["../src/query.ts"],"sourcesContent":["import type { RetryOptions } from './retry';\nimport type { QueryKey, QueryState, SyncStore, Unsubscribe } from './types';\n\nimport { CourierDisposedError } from './errors';\nimport {\n buildCacheContext,\n ensureEntry,\n evictEntry,\n hashAtom,\n hashKey,\n isKeyOrPrefix,\n makeFetchConfig,\n resolveValue,\n scheduleGc,\n startFetch,\n type CacheContext,\n type CacheEntry,\n type FetchConfig,\n type QueryFn,\n type QueryFnContext,\n} from './query-cache';\nimport { notify, toBaseState, watchInternal } from './query-observe';\n\nexport type { QueryFn, QueryFnContext };\n\n/**\n * Options for `qc.fetch()`. Always throws on error and returns `T`.\n * Use `observe()` for reactive subscriptions with select/placeholder support.\n */\nexport type QueryOptions<T> = {\n /**\n * When `false` the fetch is skipped and the current cached value is returned.\n * If no cached data exists yet, the return value is `undefined` — use `getState()`\n * to inspect entry status in that case.\n * Defaults to `true`.\n */\n enabled?: boolean;\n fn: QueryFn<T>;\n gcTime?: number;\n /** Pre-seed the cache as a successful entry when no data exists. Subject to normal staleTime checks. */\n initialData?: T | (() => T | undefined);\n key: QueryKey;\n staleTime?: number;\n} & RetryOptions;\n\n/**\n * Shared reactive fields for `observe()`.\n */\ntype ObserveExtras<T, S> = {\n /**\n * Temporary value shown while a fetch is in-flight and no cached data exists.\n * Does not affect cache state — only the value returned by `store.peek()`.\n */\n placeholderData?: S | (() => S | undefined);\n /** Transform the cached data before it is delivered to store subscribers. */\n select?: (data: T | undefined) => S | undefined;\n};\n\n/**\n * Options for `qc.observe()`. Two forms:\n *\n * - `fetch?: true` (default) — triggers a background fetch; `fn` is required.\n * - `fetch: false` — read-only store; `fn` is not required or called.\n *\n * Errors surface via `store.peek().status === 'error'`, not via Promise rejection.\n */\nexport type ObserveOptions<T, S = T> =\n | ({ fetch?: true } & QueryOptions<T> & ObserveExtras<T, S>)\n | ({ fetch: false; key: QueryKey } & Partial<QueryOptions<T>> & ObserveExtras<T, S>);\n\nexport type QueryClientOptions = {\n /**\n * Optional fetch implementation injected by `createCourier` so that\n * query fetches flow through the shared interceptor pipeline (auth, logging, etc.).\n * When omitted `globalThis.fetch` is used directly.\n */\n fetch?: typeof globalThis.fetch;\n gcTime?: number;\n staleTime?: number;\n} & RetryOptions;\n\n/**\n * Creates a key-based query cache with request deduplication, retries, stale-time/gc-time\n * eviction, and `observe()`-based reactive subscriptions (`SyncStore`-compatible).\n *\n * @example\n * ```ts\n * const qc = createQuery({ staleTime: 30_000 });\n *\n * // One-shot fetch, always resolves or throws\n * const user = await qc.fetch({ key: ['users', 1], fn: ({ signal }) => fetchUser(1, signal) });\n *\n * // Reactive subscription — errors surface via store.peek().status, not rejection\n * const store = qc.observe({ key: ['users', 1], fn: ({ signal }) => fetchUser(1, signal) });\n * const unsub = store.subscribe(() => console.log(store.peek()));\n *\n * // later:\n * qc.dispose();\n * ```\n */\nexport function createQuery(opts?: QueryClientOptions) {\n let disposed = false;\n const disposeController = new AbortController();\n const entries = new Map<string, CacheEntry>();\n const gcTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n const ctx: CacheContext = buildCacheContext(\n {\n delay: opts?.delay,\n gcTime: opts?.gcTime,\n shouldRetry: opts?.shouldRetry,\n staleTime: opts?.staleTime,\n times: opts?.times,\n },\n entries,\n gcTimers,\n notify,\n );\n\n async function fetchQuery<T>(options: QueryOptions<T>): Promise<T> {\n if (disposed) throw new CourierDisposedError('QueryClient');\n\n const { enabled = true, fn, gcTime, initialData, key, staleTime, ...retryOpts } = options;\n\n // When disabled and no existing entry, return undefined without creating a cache entry.\n if (!enabled) {\n const h = hashKey(key);\n const existing = entries.get(h);\n\n return (existing?.data as T | undefined) ?? (undefined as unknown as T);\n }\n\n const entry = ensureEntry<T>(ctx, key);\n const config = makeFetchConfig<T>(ctx, { fn, gcTime, staleTime, ...retryOpts });\n\n if (initialData !== undefined && entry.status === 'loading' && entry.data === undefined) {\n const initVal = resolveValue(initialData);\n\n if (initVal !== undefined) {\n entry.data = initVal;\n entry.error = null;\n entry.status = 'success';\n entry.updatedAt = Date.now();\n }\n }\n\n if (\n entry.status === 'success' &&\n entry.updatedAt !== undefined &&\n Date.now() - entry.updatedAt < config.staleTime &&\n !entry.isFetching\n ) {\n entry.lastConfig = config;\n\n return entry.data as T;\n }\n\n entry.lastConfig = config;\n\n if (entry.inflight) return entry.inflight.promise;\n\n return startFetch(ctx, entry, config);\n }\n\n function revalidateObservedEntry<T>(entry: CacheEntry<T>): void {\n if (!entry.lastConfig) {\n evictEntry(ctx, entry);\n\n return;\n }\n\n if (entry.isFetching) return;\n\n startFetch(ctx, entry, entry.lastConfig as FetchConfig<T>).catch(() => {});\n }\n\n function invalidate(key: QueryKey): void {\n const prefixHash = key.map((k) => hashAtom(k));\n\n for (const entry of [...entries.values()]) {\n if (isKeyOrPrefix(entry, prefixHash)) {\n if (entry.observers.size > 0) {\n revalidateObservedEntry(entry);\n continue;\n }\n\n evictEntry(ctx, entry);\n }\n }\n }\n\n function set<T>(key: QueryKey, data: T, opts?: { gcTime?: number; updatedAt?: number }): void;\n function set<T>(\n key: QueryKey,\n updater: (old: T | undefined) => T,\n opts?: { gcTime?: number; updatedAt?: number },\n ): void;\n function set<T>(\n key: QueryKey,\n dataOrUpdater: T | ((old: T | undefined) => T),\n opts?: { gcTime?: number; updatedAt?: number },\n ): void {\n const entry = ensureEntry<T>(ctx, key);\n const gcTime = opts?.gcTime ?? entry.lastConfig?.gcTime ?? ctx.gcTimeDefault;\n\n entry.data =\n typeof dataOrUpdater === 'function' ? (dataOrUpdater as (old: T | undefined) => T)(entry.data) : dataOrUpdater;\n entry.error = null;\n entry.status = 'success';\n entry.updatedAt = opts?.updatedAt ?? Date.now();\n entry.isFetching = false;\n entry.inflight = null;\n notify(entry);\n scheduleGc(ctx, entry, gcTime);\n }\n\n function isStaleAndRevalidatable(entry: CacheEntry): boolean {\n if (entry.observers.size === 0 || !entry.lastConfig || entry.isFetching) return false;\n\n if (entry.status === 'success' && entry.updatedAt !== undefined) {\n return Date.now() - entry.updatedAt >= entry.lastConfig.staleTime;\n }\n\n if (entry.status === 'error' && entry.data !== undefined && entry.updatedAt !== undefined) {\n return Date.now() - entry.updatedAt >= entry.lastConfig.staleTime;\n }\n\n return false;\n }\n\n /**\n * Returns a `SyncStore` for `key` and optionally triggers a background fetch.\n *\n * **With fetch (default):** pass `fn` and the store triggers a background fetch on creation.\n * Errors surface via `store.peek().status === 'error'`, not via Promise rejection.\n *\n * **Without fetch:** pass `fetch: false` — `fn` is not required. The store reflects\n * whatever is in the cache. Useful when another path is responsible for populating the entry.\n *\n * @example\n * ```ts\n * // Fetch form\n * const store = qc.observe({ key: ['users', 1], fn: ({ signal }) => fetchUser(1, signal) });\n *\n * // Read-only form — no fn required\n * const store = qc.observe({ key: ['users', 1], fetch: false });\n * ```\n */\n function observe<T = unknown, S = T>(options: ObserveOptions<T, S>): SyncStore<QueryState<S>> {\n const {\n fetch: shouldFetch = true,\n key,\n placeholderData,\n select,\n } = options as {\n fetch?: boolean;\n key: QueryKey;\n placeholderData?: S | (() => S | undefined);\n select?: (data: T | undefined) => S | undefined;\n };\n\n const store = watchInternal<T, S>(ctx, key, { placeholderData, select });\n\n if (shouldFetch && (options as { fn?: QueryFn<T> }).fn) {\n fetchQuery({ ...(options as QueryOptions<T>) }).catch(() => {});\n }\n\n return store;\n }\n\n return {\n cancel(key: QueryKey): void {\n const entry = entries.get(hashKey(key));\n\n if (!entry?.inflight) return;\n\n entry.inflight.controller.abort();\n },\n\n cancelAll(): void {\n for (const entry of entries.values()) {\n entry.inflight?.controller.abort();\n }\n },\n\n clear(): void {\n for (const entry of [...entries.values()]) {\n evictEntry(ctx, entry);\n }\n },\n\n get disposalSignal(): AbortSignal {\n return disposeController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n ctx.disposed = true;\n disposeController.abort();\n\n for (const [, entry] of entries) {\n entry.observers.clear();\n entry.inflight?.controller.abort();\n }\n\n for (const [, timer] of gcTimers) {\n clearTimeout(timer);\n }\n\n gcTimers.clear();\n entries.clear();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n fetch: fetchQuery,\n\n fetchMany<T = unknown>(queries: QueryOptions<T>[]): Promise<T[]> {\n return Promise.all(queries.map((q) => fetchQuery(q)));\n },\n\n get<T>(key: QueryKey): T | undefined {\n const entry = entries.get(hashKey(key));\n\n return entry ? (entry.data as T | undefined) : undefined;\n },\n\n getState<T>(key: QueryKey): QueryState<T> | null {\n const entry = entries.get(hashKey(key)) as CacheEntry<T> | undefined;\n\n if (!entry) return null;\n\n return toBaseState(entry);\n },\n\n invalidate,\n\n keys(): QueryKey[] {\n return [...entries.values()].map((e) => e.key);\n },\n\n observe,\n\n observeMany<T = unknown>(keys: QueryKey[]): SyncStore<QueryState<T>[]> {\n const stores = keys.map((k) => watchInternal<T>(ctx, k));\n\n return {\n peek(): QueryState<T>[] {\n return stores.map((s) => s.peek());\n },\n\n subscribe(onStoreChange: () => void): Unsubscribe {\n const unsubs = stores.map((s) => s.subscribe(onStoreChange));\n\n return () => {\n for (const u of unsubs) u();\n };\n },\n };\n },\n\n refetchStale(): void {\n for (const entry of [...entries.values()]) {\n if (isStaleAndRevalidatable(entry)) {\n startFetch(ctx, entry, entry.lastConfig as FetchConfig<unknown>).catch(() => {});\n }\n }\n },\n\n remove(key: QueryKey): void {\n const entry = entries.get(hashKey(key));\n\n if (entry) evictEntry(ctx, entry);\n },\n\n set,\n\n get size(): number {\n return entries.size;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n };\n}\n\nexport type QueryClient = ReturnType<typeof createQuery>;\n"],"mappings":"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"}
|
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { RetryOptions } from './retry';
|
|
2
|
+
import type { QueryKey, QueryState, SyncStore } from './types';
|
|
3
|
+
import { type QueryFn, type QueryFnContext } from './query-cache';
|
|
4
|
+
export type { QueryFn, QueryFnContext };
|
|
5
|
+
/**
|
|
6
|
+
* Options for `qc.fetch()`. Always throws on error and returns `T`.
|
|
7
|
+
* Use `observe()` for reactive subscriptions with select/placeholder support.
|
|
8
|
+
*/
|
|
9
|
+
export type QueryOptions<T> = {
|
|
10
|
+
/**
|
|
11
|
+
* When `false` the fetch is skipped and the current cached value is returned.
|
|
12
|
+
* If no cached data exists yet, the return value is `undefined` — use `getState()`
|
|
13
|
+
* to inspect entry status in that case.
|
|
14
|
+
* Defaults to `true`.
|
|
15
|
+
*/
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
fn: QueryFn<T>;
|
|
18
|
+
gcTime?: number;
|
|
19
|
+
/** Pre-seed the cache as a successful entry when no data exists. Subject to normal staleTime checks. */
|
|
20
|
+
initialData?: T | (() => T | undefined);
|
|
21
|
+
key: QueryKey;
|
|
22
|
+
staleTime?: number;
|
|
23
|
+
} & RetryOptions;
|
|
24
|
+
/**
|
|
25
|
+
* Shared reactive fields for `observe()`.
|
|
26
|
+
*/
|
|
27
|
+
type ObserveExtras<T, S> = {
|
|
28
|
+
/**
|
|
29
|
+
* Temporary value shown while a fetch is in-flight and no cached data exists.
|
|
30
|
+
* Does not affect cache state — only the value returned by `store.peek()`.
|
|
31
|
+
*/
|
|
32
|
+
placeholderData?: S | (() => S | undefined);
|
|
33
|
+
/** Transform the cached data before it is delivered to store subscribers. */
|
|
34
|
+
select?: (data: T | undefined) => S | undefined;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Options for `qc.observe()`. Two forms:
|
|
38
|
+
*
|
|
39
|
+
* - `fetch?: true` (default) — triggers a background fetch; `fn` is required.
|
|
40
|
+
* - `fetch: false` — read-only store; `fn` is not required or called.
|
|
41
|
+
*
|
|
42
|
+
* Errors surface via `store.peek().status === 'error'`, not via Promise rejection.
|
|
43
|
+
*/
|
|
44
|
+
export type ObserveOptions<T, S = T> = ({
|
|
45
|
+
fetch?: true;
|
|
46
|
+
} & QueryOptions<T> & ObserveExtras<T, S>) | ({
|
|
47
|
+
fetch: false;
|
|
48
|
+
key: QueryKey;
|
|
49
|
+
} & Partial<QueryOptions<T>> & ObserveExtras<T, S>);
|
|
50
|
+
export type QueryClientOptions = {
|
|
51
|
+
/**
|
|
52
|
+
* Optional fetch implementation injected by `createCourier` so that
|
|
53
|
+
* query fetches flow through the shared interceptor pipeline (auth, logging, etc.).
|
|
54
|
+
* When omitted `globalThis.fetch` is used directly.
|
|
55
|
+
*/
|
|
56
|
+
fetch?: typeof globalThis.fetch;
|
|
57
|
+
gcTime?: number;
|
|
58
|
+
staleTime?: number;
|
|
59
|
+
} & RetryOptions;
|
|
60
|
+
/**
|
|
61
|
+
* Creates a key-based query cache with request deduplication, retries, stale-time/gc-time
|
|
62
|
+
* eviction, and `observe()`-based reactive subscriptions (`SyncStore`-compatible).
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const qc = createQuery({ staleTime: 30_000 });
|
|
67
|
+
*
|
|
68
|
+
* // One-shot fetch, always resolves or throws
|
|
69
|
+
* const user = await qc.fetch({ key: ['users', 1], fn: ({ signal }) => fetchUser(1, signal) });
|
|
70
|
+
*
|
|
71
|
+
* // Reactive subscription — errors surface via store.peek().status, not rejection
|
|
72
|
+
* const store = qc.observe({ key: ['users', 1], fn: ({ signal }) => fetchUser(1, signal) });
|
|
73
|
+
* const unsub = store.subscribe(() => console.log(store.peek()));
|
|
74
|
+
*
|
|
75
|
+
* // later:
|
|
76
|
+
* qc.dispose();
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function createQuery(opts?: QueryClientOptions): {
|
|
80
|
+
cancel(key: QueryKey): void;
|
|
81
|
+
cancelAll(): void;
|
|
82
|
+
clear(): void;
|
|
83
|
+
readonly disposalSignal: AbortSignal;
|
|
84
|
+
dispose(): void;
|
|
85
|
+
readonly disposed: boolean;
|
|
86
|
+
fetch: <T>(options: QueryOptions<T>) => Promise<T>;
|
|
87
|
+
fetchMany<T = unknown>(queries: QueryOptions<T>[]): Promise<T[]>;
|
|
88
|
+
get<T>(key: QueryKey): T | undefined;
|
|
89
|
+
getState<T>(key: QueryKey): QueryState<T> | null;
|
|
90
|
+
invalidate: (key: QueryKey) => void;
|
|
91
|
+
keys(): QueryKey[];
|
|
92
|
+
observe: <T = unknown, S = T>(options: ObserveOptions<T, S>) => SyncStore<QueryState<S>>;
|
|
93
|
+
observeMany<T = unknown>(keys: QueryKey[]): SyncStore<QueryState<T>[]>;
|
|
94
|
+
refetchStale(): void;
|
|
95
|
+
remove(key: QueryKey): void;
|
|
96
|
+
set: {
|
|
97
|
+
<T>(key: QueryKey, data: T, opts?: {
|
|
98
|
+
gcTime?: number;
|
|
99
|
+
updatedAt?: number;
|
|
100
|
+
}): void;
|
|
101
|
+
<T>(key: QueryKey, updater: (old: T | undefined) => T, opts?: {
|
|
102
|
+
gcTime?: number;
|
|
103
|
+
updatedAt?: number;
|
|
104
|
+
}): void;
|
|
105
|
+
};
|
|
106
|
+
readonly size: number;
|
|
107
|
+
[Symbol.dispose](): void;
|
|
108
|
+
};
|
|
109
|
+
export type QueryClient = ReturnType<typeof createQuery>;
|
|
110
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|
package/dist/query.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{CourierDisposedError as e}from"./errors.js";import{buildCacheContext as t,ensureEntry as n,evictEntry as r,hashAtom as i,hashKey as a,isKeyOrPrefix as o,makeFetchConfig as s,resolveValue as c,scheduleGc as l,startFetch as u}from"./query-cache.js";import{notify as d,toBaseState as f,watchInternal as p}from"./query-observe.js";function m(m){let h=!1,g=new AbortController,_=new Map,v=new Map,y=t({delay:m?.delay,gcTime:m?.gcTime,shouldRetry:m?.shouldRetry,staleTime:m?.staleTime,times:m?.times},_,v,d);async function b(t){if(h)throw new e(`QueryClient`);let{enabled:r=!0,fn:i,gcTime:o,initialData:l,key:d,staleTime:f,...p}=t;if(!r){let e=a(d);return _.get(e)?.data??void 0}let m=n(y,d),g=s(y,{fn:i,gcTime:o,staleTime:f,...p});if(l!==void 0&&m.status===`loading`&&m.data===void 0){let e=c(l);e!==void 0&&(m.data=e,m.error=null,m.status=`success`,m.updatedAt=Date.now())}return m.status===`success`&&m.updatedAt!==void 0&&Date.now()-m.updatedAt<g.staleTime&&!m.isFetching?(m.lastConfig=g,m.data):(m.lastConfig=g,m.inflight?m.inflight.promise:u(y,m,g))}function x(e){if(!e.lastConfig){r(y,e);return}e.isFetching||u(y,e,e.lastConfig).catch(()=>{})}function S(e){let t=e.map(e=>i(e));for(let e of[..._.values()])if(o(e,t)){if(e.observers.size>0){x(e);continue}r(y,e)}}function C(e,t,r){let i=n(y,e),a=r?.gcTime??i.lastConfig?.gcTime??y.gcTimeDefault;i.data=typeof t==`function`?t(i.data):t,i.error=null,i.status=`success`,i.updatedAt=r?.updatedAt??Date.now(),i.isFetching=!1,i.inflight=null,d(i),l(y,i,a)}function w(e){return e.observers.size===0||!e.lastConfig||e.isFetching?!1:e.status===`success`&&e.updatedAt!==void 0||e.status===`error`&&e.data!==void 0&&e.updatedAt!==void 0?Date.now()-e.updatedAt>=e.lastConfig.staleTime:!1}function T(e){let{fetch:t=!0,key:n,placeholderData:r,select:i}=e,a=p(y,n,{placeholderData:r,select:i});return t&&e.fn&&b({...e}).catch(()=>{}),a}return{cancel(e){let t=_.get(a(e));t?.inflight&&t.inflight.controller.abort()},cancelAll(){for(let e of _.values())e.inflight?.controller.abort()},clear(){for(let e of[..._.values()])r(y,e)},get disposalSignal(){return g.signal},dispose(){if(!h){h=!0,y.disposed=!0,g.abort();for(let[,e]of _)e.observers.clear(),e.inflight?.controller.abort();for(let[,e]of v)clearTimeout(e);v.clear(),_.clear()}},get disposed(){return h},fetch:b,fetchMany(e){return Promise.all(e.map(e=>b(e)))},get(e){let t=_.get(a(e));return t?t.data:void 0},getState(e){let t=_.get(a(e));return t?f(t):null},invalidate:S,keys(){return[..._.values()].map(e=>e.key)},observe:T,observeMany(e){let t=e.map(e=>p(y,e));return{peek(){return t.map(e=>e.peek())},subscribe(e){let n=t.map(t=>t.subscribe(e));return()=>{for(let e of n)e()}}}},refetchStale(){for(let e of[..._.values()])w(e)&&u(y,e,e.lastConfig).catch(()=>{})},remove(e){let t=_.get(a(e));t&&r(y,t)},set:C,get size(){return _.size},[Symbol.dispose](){this.dispose()}}}export{m as createQuery};
|
|
2
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","names":[],"sources":["../src/query.ts"],"sourcesContent":["import type { RetryOptions } from './retry';\nimport type { QueryKey, QueryState, SyncStore, Unsubscribe } from './types';\n\nimport { CourierDisposedError } from './errors';\nimport {\n buildCacheContext,\n ensureEntry,\n evictEntry,\n hashAtom,\n hashKey,\n isKeyOrPrefix,\n makeFetchConfig,\n resolveValue,\n scheduleGc,\n startFetch,\n type CacheContext,\n type CacheEntry,\n type FetchConfig,\n type QueryFn,\n type QueryFnContext,\n} from './query-cache';\nimport { notify, toBaseState, watchInternal } from './query-observe';\n\nexport type { QueryFn, QueryFnContext };\n\n/**\n * Options for `qc.fetch()`. Always throws on error and returns `T`.\n * Use `observe()` for reactive subscriptions with select/placeholder support.\n */\nexport type QueryOptions<T> = {\n /**\n * When `false` the fetch is skipped and the current cached value is returned.\n * If no cached data exists yet, the return value is `undefined` — use `getState()`\n * to inspect entry status in that case.\n * Defaults to `true`.\n */\n enabled?: boolean;\n fn: QueryFn<T>;\n gcTime?: number;\n /** Pre-seed the cache as a successful entry when no data exists. Subject to normal staleTime checks. */\n initialData?: T | (() => T | undefined);\n key: QueryKey;\n staleTime?: number;\n} & RetryOptions;\n\n/**\n * Shared reactive fields for `observe()`.\n */\ntype ObserveExtras<T, S> = {\n /**\n * Temporary value shown while a fetch is in-flight and no cached data exists.\n * Does not affect cache state — only the value returned by `store.peek()`.\n */\n placeholderData?: S | (() => S | undefined);\n /** Transform the cached data before it is delivered to store subscribers. */\n select?: (data: T | undefined) => S | undefined;\n};\n\n/**\n * Options for `qc.observe()`. Two forms:\n *\n * - `fetch?: true` (default) — triggers a background fetch; `fn` is required.\n * - `fetch: false` — read-only store; `fn` is not required or called.\n *\n * Errors surface via `store.peek().status === 'error'`, not via Promise rejection.\n */\nexport type ObserveOptions<T, S = T> =\n | ({ fetch?: true } & QueryOptions<T> & ObserveExtras<T, S>)\n | ({ fetch: false; key: QueryKey } & Partial<QueryOptions<T>> & ObserveExtras<T, S>);\n\nexport type QueryClientOptions = {\n /**\n * Optional fetch implementation injected by `createCourier` so that\n * query fetches flow through the shared interceptor pipeline (auth, logging, etc.).\n * When omitted `globalThis.fetch` is used directly.\n */\n fetch?: typeof globalThis.fetch;\n gcTime?: number;\n staleTime?: number;\n} & RetryOptions;\n\n/**\n * Creates a key-based query cache with request deduplication, retries, stale-time/gc-time\n * eviction, and `observe()`-based reactive subscriptions (`SyncStore`-compatible).\n *\n * @example\n * ```ts\n * const qc = createQuery({ staleTime: 30_000 });\n *\n * // One-shot fetch, always resolves or throws\n * const user = await qc.fetch({ key: ['users', 1], fn: ({ signal }) => fetchUser(1, signal) });\n *\n * // Reactive subscription — errors surface via store.peek().status, not rejection\n * const store = qc.observe({ key: ['users', 1], fn: ({ signal }) => fetchUser(1, signal) });\n * const unsub = store.subscribe(() => console.log(store.peek()));\n *\n * // later:\n * qc.dispose();\n * ```\n */\nexport function createQuery(opts?: QueryClientOptions) {\n let disposed = false;\n const disposeController = new AbortController();\n const entries = new Map<string, CacheEntry>();\n const gcTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n const ctx: CacheContext = buildCacheContext(\n {\n delay: opts?.delay,\n gcTime: opts?.gcTime,\n shouldRetry: opts?.shouldRetry,\n staleTime: opts?.staleTime,\n times: opts?.times,\n },\n entries,\n gcTimers,\n notify,\n );\n\n async function fetchQuery<T>(options: QueryOptions<T>): Promise<T> {\n if (disposed) throw new CourierDisposedError('QueryClient');\n\n const { enabled = true, fn, gcTime, initialData, key, staleTime, ...retryOpts } = options;\n\n // When disabled and no existing entry, return undefined without creating a cache entry.\n if (!enabled) {\n const h = hashKey(key);\n const existing = entries.get(h);\n\n return (existing?.data as T | undefined) ?? (undefined as unknown as T);\n }\n\n const entry = ensureEntry<T>(ctx, key);\n const config = makeFetchConfig<T>(ctx, { fn, gcTime, staleTime, ...retryOpts });\n\n if (initialData !== undefined && entry.status === 'loading' && entry.data === undefined) {\n const initVal = resolveValue(initialData);\n\n if (initVal !== undefined) {\n entry.data = initVal;\n entry.error = null;\n entry.status = 'success';\n entry.updatedAt = Date.now();\n }\n }\n\n if (\n entry.status === 'success' &&\n entry.updatedAt !== undefined &&\n Date.now() - entry.updatedAt < config.staleTime &&\n !entry.isFetching\n ) {\n entry.lastConfig = config;\n\n return entry.data as T;\n }\n\n entry.lastConfig = config;\n\n if (entry.inflight) return entry.inflight.promise;\n\n return startFetch(ctx, entry, config);\n }\n\n function revalidateObservedEntry<T>(entry: CacheEntry<T>): void {\n if (!entry.lastConfig) {\n evictEntry(ctx, entry);\n\n return;\n }\n\n if (entry.isFetching) return;\n\n startFetch(ctx, entry, entry.lastConfig as FetchConfig<T>).catch(() => {});\n }\n\n function invalidate(key: QueryKey): void {\n const prefixHash = key.map((k) => hashAtom(k));\n\n for (const entry of [...entries.values()]) {\n if (isKeyOrPrefix(entry, prefixHash)) {\n if (entry.observers.size > 0) {\n revalidateObservedEntry(entry);\n continue;\n }\n\n evictEntry(ctx, entry);\n }\n }\n }\n\n function set<T>(key: QueryKey, data: T, opts?: { gcTime?: number; updatedAt?: number }): void;\n function set<T>(\n key: QueryKey,\n updater: (old: T | undefined) => T,\n opts?: { gcTime?: number; updatedAt?: number },\n ): void;\n function set<T>(\n key: QueryKey,\n dataOrUpdater: T | ((old: T | undefined) => T),\n opts?: { gcTime?: number; updatedAt?: number },\n ): void {\n const entry = ensureEntry<T>(ctx, key);\n const gcTime = opts?.gcTime ?? entry.lastConfig?.gcTime ?? ctx.gcTimeDefault;\n\n entry.data =\n typeof dataOrUpdater === 'function' ? (dataOrUpdater as (old: T | undefined) => T)(entry.data) : dataOrUpdater;\n entry.error = null;\n entry.status = 'success';\n entry.updatedAt = opts?.updatedAt ?? Date.now();\n entry.isFetching = false;\n entry.inflight = null;\n notify(entry);\n scheduleGc(ctx, entry, gcTime);\n }\n\n function isStaleAndRevalidatable(entry: CacheEntry): boolean {\n if (entry.observers.size === 0 || !entry.lastConfig || entry.isFetching) return false;\n\n if (entry.status === 'success' && entry.updatedAt !== undefined) {\n return Date.now() - entry.updatedAt >= entry.lastConfig.staleTime;\n }\n\n if (entry.status === 'error' && entry.data !== undefined && entry.updatedAt !== undefined) {\n return Date.now() - entry.updatedAt >= entry.lastConfig.staleTime;\n }\n\n return false;\n }\n\n /**\n * Returns a `SyncStore` for `key` and optionally triggers a background fetch.\n *\n * **With fetch (default):** pass `fn` and the store triggers a background fetch on creation.\n * Errors surface via `store.peek().status === 'error'`, not via Promise rejection.\n *\n * **Without fetch:** pass `fetch: false` — `fn` is not required. The store reflects\n * whatever is in the cache. Useful when another path is responsible for populating the entry.\n *\n * @example\n * ```ts\n * // Fetch form\n * const store = qc.observe({ key: ['users', 1], fn: ({ signal }) => fetchUser(1, signal) });\n *\n * // Read-only form — no fn required\n * const store = qc.observe({ key: ['users', 1], fetch: false });\n * ```\n */\n function observe<T = unknown, S = T>(options: ObserveOptions<T, S>): SyncStore<QueryState<S>> {\n const {\n fetch: shouldFetch = true,\n key,\n placeholderData,\n select,\n } = options as {\n fetch?: boolean;\n key: QueryKey;\n placeholderData?: S | (() => S | undefined);\n select?: (data: T | undefined) => S | undefined;\n };\n\n const store = watchInternal<T, S>(ctx, key, { placeholderData, select });\n\n if (shouldFetch && (options as { fn?: QueryFn<T> }).fn) {\n fetchQuery({ ...(options as QueryOptions<T>) }).catch(() => {});\n }\n\n return store;\n }\n\n return {\n cancel(key: QueryKey): void {\n const entry = entries.get(hashKey(key));\n\n if (!entry?.inflight) return;\n\n entry.inflight.controller.abort();\n },\n\n cancelAll(): void {\n for (const entry of entries.values()) {\n entry.inflight?.controller.abort();\n }\n },\n\n clear(): void {\n for (const entry of [...entries.values()]) {\n evictEntry(ctx, entry);\n }\n },\n\n get disposalSignal(): AbortSignal {\n return disposeController.signal;\n },\n\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n ctx.disposed = true;\n disposeController.abort();\n\n for (const [, entry] of entries) {\n entry.observers.clear();\n entry.inflight?.controller.abort();\n }\n\n for (const [, timer] of gcTimers) {\n clearTimeout(timer);\n }\n\n gcTimers.clear();\n entries.clear();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n fetch: fetchQuery,\n\n fetchMany<T = unknown>(queries: QueryOptions<T>[]): Promise<T[]> {\n return Promise.all(queries.map((q) => fetchQuery(q)));\n },\n\n get<T>(key: QueryKey): T | undefined {\n const entry = entries.get(hashKey(key));\n\n return entry ? (entry.data as T | undefined) : undefined;\n },\n\n getState<T>(key: QueryKey): QueryState<T> | null {\n const entry = entries.get(hashKey(key)) as CacheEntry<T> | undefined;\n\n if (!entry) return null;\n\n return toBaseState(entry);\n },\n\n invalidate,\n\n keys(): QueryKey[] {\n return [...entries.values()].map((e) => e.key);\n },\n\n observe,\n\n observeMany<T = unknown>(keys: QueryKey[]): SyncStore<QueryState<T>[]> {\n const stores = keys.map((k) => watchInternal<T>(ctx, k));\n\n return {\n peek(): QueryState<T>[] {\n return stores.map((s) => s.peek());\n },\n\n subscribe(onStoreChange: () => void): Unsubscribe {\n const unsubs = stores.map((s) => s.subscribe(onStoreChange));\n\n return () => {\n for (const u of unsubs) u();\n };\n },\n };\n },\n\n refetchStale(): void {\n for (const entry of [...entries.values()]) {\n if (isStaleAndRevalidatable(entry)) {\n startFetch(ctx, entry, entry.lastConfig as FetchConfig<unknown>).catch(() => {});\n }\n }\n },\n\n remove(key: QueryKey): void {\n const entry = entries.get(hashKey(key));\n\n if (entry) evictEntry(ctx, entry);\n },\n\n set,\n\n get size(): number {\n return entries.size;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n };\n}\n\nexport type QueryClient = ReturnType<typeof createQuery>;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
async function e(e,t=`auto`){if(t===`raw`)return e;if(e.status===204||e.status===205||e.status===304)return;if(t===`json`)return e.json();if(t===`text`)return e.text();if(t===`blob`)return e.blob();if(t===`arrayBuffer`)return e.arrayBuffer();let n=e.headers.get(`content-type`)??``;return n.includes(`application/json`)||n.includes(`+json`)?e.json():n.startsWith(`text/`)?e.text():n.startsWith(`image/`)||n.startsWith(`audio/`)||n.startsWith(`video/`)?e.blob():e.text()}exports.parseResponse=e;
|
|
2
|
+
//# sourceMappingURL=response.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.cjs","names":[],"sources":["../src/response.ts"],"sourcesContent":["/** Response parsing strategy. 'raw' returns the Response object directly. */\nexport type ResponseType = 'auto' | 'json' | 'text' | 'blob' | 'arrayBuffer' | 'raw';\n\nexport async function parseResponse(res: Response, responseType: ResponseType = 'auto'): Promise<unknown> {\n if (responseType === 'raw') return res;\n\n if (res.status === 204 || res.status === 205 || res.status === 304) return;\n\n if (responseType === 'json') return res.json();\n\n if (responseType === 'text') return res.text();\n\n if (responseType === 'blob') return res.blob();\n\n if (responseType === 'arrayBuffer') return res.arrayBuffer();\n\n const contentType = res.headers.get('content-type') ?? '';\n\n if (contentType.includes('application/json') || contentType.includes('+json')) return res.json();\n\n if (contentType.startsWith('text/')) return res.text();\n\n if (contentType.startsWith('image/') || contentType.startsWith('audio/') || contentType.startsWith('video/')) {\n return res.blob();\n }\n\n // Unknown content-type — fall back to text, which is always safe to read and debug.\n return res.text();\n}\n"],"mappings":"AAGA,eAAsB,EAAc,EAAe,EAA6B,OAA0B,CACxG,GAAI,IAAiB,MAAO,OAAO,EAEnC,GAAI,EAAI,SAAW,KAAO,EAAI,SAAW,KAAO,EAAI,SAAW,IAAK,OAEpE,GAAI,IAAiB,OAAQ,OAAO,EAAI,KAAK,EAE7C,GAAI,IAAiB,OAAQ,OAAO,EAAI,KAAK,EAE7C,GAAI,IAAiB,OAAQ,OAAO,EAAI,KAAK,EAE7C,GAAI,IAAiB,cAAe,OAAO,EAAI,YAAY,EAE3D,IAAM,EAAc,EAAI,QAAQ,IAAI,cAAc,GAAK,GAWvD,OATI,EAAY,SAAS,kBAAkB,GAAK,EAAY,SAAS,OAAO,EAAU,EAAI,KAAK,EAE3F,EAAY,WAAW,OAAO,EAAU,EAAI,KAAK,EAEjD,EAAY,WAAW,QAAQ,GAAK,EAAY,WAAW,QAAQ,GAAK,EAAY,WAAW,QAAQ,EAClG,EAAI,KAAK,EAIX,EAAI,KAAK,CAClB"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Response parsing strategy. 'raw' returns the Response object directly. */
|
|
2
|
+
export type ResponseType = 'auto' | 'json' | 'text' | 'blob' | 'arrayBuffer' | 'raw';
|
|
3
|
+
export declare function parseResponse(res: Response, responseType?: ResponseType): Promise<unknown>;
|
|
4
|
+
//# sourceMappingURL=response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,KAAK,CAAC;AAErF,wBAAsB,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,YAAY,GAAE,YAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,CAyBxG"}
|
package/dist/response.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
async function e(e,t=`auto`){if(t===`raw`)return e;if(e.status===204||e.status===205||e.status===304)return;if(t===`json`)return e.json();if(t===`text`)return e.text();if(t===`blob`)return e.blob();if(t===`arrayBuffer`)return e.arrayBuffer();let n=e.headers.get(`content-type`)??``;return n.includes(`application/json`)||n.includes(`+json`)?e.json():n.startsWith(`text/`)?e.text():n.startsWith(`image/`)||n.startsWith(`audio/`)||n.startsWith(`video/`)?e.blob():e.text()}export{e as parseResponse};
|
|
2
|
+
//# sourceMappingURL=response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.js","names":[],"sources":["../src/response.ts"],"sourcesContent":["/** Response parsing strategy. 'raw' returns the Response object directly. */\nexport type ResponseType = 'auto' | 'json' | 'text' | 'blob' | 'arrayBuffer' | 'raw';\n\nexport async function parseResponse(res: Response, responseType: ResponseType = 'auto'): Promise<unknown> {\n if (responseType === 'raw') return res;\n\n if (res.status === 204 || res.status === 205 || res.status === 304) return;\n\n if (responseType === 'json') return res.json();\n\n if (responseType === 'text') return res.text();\n\n if (responseType === 'blob') return res.blob();\n\n if (responseType === 'arrayBuffer') return res.arrayBuffer();\n\n const contentType = res.headers.get('content-type') ?? '';\n\n if (contentType.includes('application/json') || contentType.includes('+json')) return res.json();\n\n if (contentType.startsWith('text/')) return res.text();\n\n if (contentType.startsWith('image/') || contentType.startsWith('audio/') || contentType.startsWith('video/')) {\n return res.blob();\n }\n\n // Unknown content-type — fall back to text, which is always safe to read and debug.\n return res.text();\n}\n"],"mappings":"AAGA,eAAsB,EAAc,EAAe,EAA6B,OAA0B,CACxG,GAAI,IAAiB,MAAO,OAAO,EAEnC,GAAI,EAAI,SAAW,KAAO,EAAI,SAAW,KAAO,EAAI,SAAW,IAAK,OAEpE,GAAI,IAAiB,OAAQ,OAAO,EAAI,KAAK,EAE7C,GAAI,IAAiB,OAAQ,OAAO,EAAI,KAAK,EAE7C,GAAI,IAAiB,OAAQ,OAAO,EAAI,KAAK,EAE7C,GAAI,IAAiB,cAAe,OAAO,EAAI,YAAY,EAE3D,IAAM,EAAc,EAAI,QAAQ,IAAI,cAAc,GAAK,GAWvD,OATI,EAAY,SAAS,kBAAkB,GAAK,EAAY,SAAS,OAAO,EAAU,EAAI,KAAK,EAE3F,EAAY,WAAW,OAAO,EAAU,EAAI,KAAK,EAEjD,EAAY,WAAW,QAAQ,GAAK,EAAY,WAAW,QAAQ,GAAK,EAAY,WAAW,QAAQ,EAClG,EAAI,KAAK,EAIX,EAAI,KAAK,CAClB"}
|
package/dist/retry.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
let e=require("@vielzeug/arsenal");function t(t){return Math.random()*(0,e.backoff)(t)}function n(e,n){if(typeof n==`function`){let t=n(e);return Number.isFinite(t)?Math.max(0,t):0}return typeof n==`number`?Number.isFinite(n)?Math.max(0,n):0:t(e)}exports.fullJitterDelay=t,exports.resolveRetryDelay=n;
|
|
2
|
+
//# sourceMappingURL=retry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.cjs","names":[],"sources":["../src/retry.ts"],"sourcesContent":["import { backoff } from '@vielzeug/arsenal';\n\n/** Internal default: one attempt, no retries. */\nexport const DEFAULT_TIMES = 1;\n\nexport type RetryOptions = {\n /**\n * Delay between retry attempts in ms, or a function receiving the zero-based\n * attempt index (`attempt = 0` means the wait before the 2nd try, after the\n * 1st failure). Defaults to full-jitter exponential backoff: a random delay\n * in `[0, min(1 s × 2ⁿ, 30 s)]` where `n` is the zero-based attempt index.\n *\n * @example\n * ```ts\n * // Linear backoff: 500 ms, 1 000 ms, 1 500 ms, …\n * delay: (attempt) => (attempt + 1) * 500\n * ```\n */\n delay?: number | ((attempt: number) => number);\n /**\n * Return `false` to skip retrying for a specific error (e.g. 4xx HTTP errors).\n * `attempt` is zero-based: `0` means \"deciding whether to retry after the 1st\n * failure\", `1` means \"after the 2nd failure\", and so on.\n */\n shouldRetry?: (error: unknown, attempt: number) => boolean;\n /** Total number of attempts including the first. `1` means a single try with no retries. Defaults to `1`. */\n times?: number;\n};\n\n/**\n * Full-jitter exponential backoff: a random delay in `[0, min(1s × 2ⁿ, 30s)]` where `n` is the\n * zero-based attempt index. Shared default delay for both retries (`retry()`/mutations/queries)\n * and stream reconnects (`sse()`/`readable()`).\n */\nexport function fullJitterDelay(attempt: number): number {\n return Math.random() * backoff(attempt);\n}\n\n/** Compute the inter-attempt delay from a courier RetryOptions configuration. */\nexport function resolveRetryDelay(attempt: number, userDelay?: number | ((attempt: number) => number)): number {\n if (typeof userDelay === 'function') {\n const ms = userDelay(attempt);\n\n return Number.isFinite(ms) ? Math.max(0, ms) : 0;\n }\n\n if (typeof userDelay === 'number') {\n return Number.isFinite(userDelay) ? Math.max(0, userDelay) : 0;\n }\n\n return fullJitterDelay(attempt);\n}\n"],"mappings":"mCAkCA,SAAgB,EAAgB,EAAyB,CACvD,OAAO,KAAK,OAAO,GAAA,EAAA,EAAA,QAAA,CAAY,CAAO,CACxC,CAGA,SAAgB,EAAkB,EAAiB,EAA4D,CAC7G,GAAI,OAAO,GAAc,WAAY,CACnC,IAAM,EAAK,EAAU,CAAO,EAE5B,OAAO,OAAO,SAAS,CAAE,EAAI,KAAK,IAAI,EAAG,CAAE,EAAI,CACjD,CAMA,OAJI,OAAO,GAAc,SAChB,OAAO,SAAS,CAAS,EAAI,KAAK,IAAI,EAAG,CAAS,EAAI,EAGxD,EAAgB,CAAO,CAChC"}
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Internal default: one attempt, no retries. */
|
|
2
|
+
export declare const DEFAULT_TIMES = 1;
|
|
3
|
+
export type RetryOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Delay between retry attempts in ms, or a function receiving the zero-based
|
|
6
|
+
* attempt index (`attempt = 0` means the wait before the 2nd try, after the
|
|
7
|
+
* 1st failure). Defaults to full-jitter exponential backoff: a random delay
|
|
8
|
+
* in `[0, min(1 s × 2ⁿ, 30 s)]` where `n` is the zero-based attempt index.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // Linear backoff: 500 ms, 1 000 ms, 1 500 ms, …
|
|
13
|
+
* delay: (attempt) => (attempt + 1) * 500
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
delay?: number | ((attempt: number) => number);
|
|
17
|
+
/**
|
|
18
|
+
* Return `false` to skip retrying for a specific error (e.g. 4xx HTTP errors).
|
|
19
|
+
* `attempt` is zero-based: `0` means "deciding whether to retry after the 1st
|
|
20
|
+
* failure", `1` means "after the 2nd failure", and so on.
|
|
21
|
+
*/
|
|
22
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
23
|
+
/** Total number of attempts including the first. `1` means a single try with no retries. Defaults to `1`. */
|
|
24
|
+
times?: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Full-jitter exponential backoff: a random delay in `[0, min(1s × 2ⁿ, 30s)]` where `n` is the
|
|
28
|
+
* zero-based attempt index. Shared default delay for both retries (`retry()`/mutations/queries)
|
|
29
|
+
* and stream reconnects (`sse()`/`readable()`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function fullJitterDelay(attempt: number): number;
|
|
32
|
+
/** Compute the inter-attempt delay from a courier RetryOptions configuration. */
|
|
33
|
+
export declare function resolveRetryDelay(attempt: number, userDelay?: number | ((attempt: number) => number)): number;
|
|
34
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAEA,iDAAiD;AACjD,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAC/C;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3D,6GAA6G;IAC7G,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,MAAM,CAY7G"}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{backoff as e}from"@vielzeug/arsenal";function t(t){return Math.random()*e(t)}function n(e,n){if(typeof n==`function`){let t=n(e);return Number.isFinite(t)?Math.max(0,t):0}return typeof n==`number`?Number.isFinite(n)?Math.max(0,n):0:t(e)}export{t as fullJitterDelay,n as resolveRetryDelay};
|
|
2
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","names":[],"sources":["../src/retry.ts"],"sourcesContent":["import { backoff } from '@vielzeug/arsenal';\n\n/** Internal default: one attempt, no retries. */\nexport const DEFAULT_TIMES = 1;\n\nexport type RetryOptions = {\n /**\n * Delay between retry attempts in ms, or a function receiving the zero-based\n * attempt index (`attempt = 0` means the wait before the 2nd try, after the\n * 1st failure). Defaults to full-jitter exponential backoff: a random delay\n * in `[0, min(1 s × 2ⁿ, 30 s)]` where `n` is the zero-based attempt index.\n *\n * @example\n * ```ts\n * // Linear backoff: 500 ms, 1 000 ms, 1 500 ms, …\n * delay: (attempt) => (attempt + 1) * 500\n * ```\n */\n delay?: number | ((attempt: number) => number);\n /**\n * Return `false` to skip retrying for a specific error (e.g. 4xx HTTP errors).\n * `attempt` is zero-based: `0` means \"deciding whether to retry after the 1st\n * failure\", `1` means \"after the 2nd failure\", and so on.\n */\n shouldRetry?: (error: unknown, attempt: number) => boolean;\n /** Total number of attempts including the first. `1` means a single try with no retries. Defaults to `1`. */\n times?: number;\n};\n\n/**\n * Full-jitter exponential backoff: a random delay in `[0, min(1s × 2ⁿ, 30s)]` where `n` is the\n * zero-based attempt index. Shared default delay for both retries (`retry()`/mutations/queries)\n * and stream reconnects (`sse()`/`readable()`).\n */\nexport function fullJitterDelay(attempt: number): number {\n return Math.random() * backoff(attempt);\n}\n\n/** Compute the inter-attempt delay from a courier RetryOptions configuration. */\nexport function resolveRetryDelay(attempt: number, userDelay?: number | ((attempt: number) => number)): number {\n if (typeof userDelay === 'function') {\n const ms = userDelay(attempt);\n\n return Number.isFinite(ms) ? Math.max(0, ms) : 0;\n }\n\n if (typeof userDelay === 'number') {\n return Number.isFinite(userDelay) ? Math.max(0, userDelay) : 0;\n }\n\n return fullJitterDelay(attempt);\n}\n"],"mappings":"4CAkCA,SAAgB,EAAgB,EAAyB,CACvD,OAAO,KAAK,OAAO,EAAI,EAAQ,CAAO,CACxC,CAGA,SAAgB,EAAkB,EAAiB,EAA4D,CAC7G,GAAI,OAAO,GAAc,WAAY,CACnC,IAAM,EAAK,EAAU,CAAO,EAE5B,OAAO,OAAO,SAAS,CAAE,EAAI,KAAK,IAAI,EAAG,CAAE,EAAI,CACjD,CAMA,OAJI,OAAO,GAAc,SAChB,OAAO,SAAS,CAAS,EAAI,KAAK,IAAI,EAAG,CAAS,EAAI,EAGxD,EAAgB,CAAO,CAChC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
require("@vielzeug/arsenal");function e(e,n,r,i,a){return r!==void 0&&!t(r)?{...a,body:JSON.stringify(r),headers:{"content-type":`application/json`,...n},method:e.toUpperCase(),signal:i}:{...a,...r!==void 0&&{body:r},headers:n,method:e.toUpperCase(),signal:i}}function t(e){return e instanceof FormData||e instanceof Blob||e instanceof URLSearchParams||typeof e==`string`||e instanceof ArrayBuffer||ArrayBuffer.isView(e)||typeof ReadableStream<`u`&&e instanceof ReadableStream}exports.buildRequestInit=e,exports.isBodyInit=t;
|
|
2
|
+
//# sourceMappingURL=serialize.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.cjs","names":[],"sources":["../src/serialize.ts"],"sourcesContent":["import { hash } from '@vielzeug/arsenal';\n\nexport { hash };\n\n/**\n * Build a `RequestInit` object from the request components, handling body serialisation.\n * Plain objects/arrays are JSON-serialised and `content-type: application/json` is added.\n * BodyInit values (FormData, Blob, etc.) are forwarded as-is.\n */\nexport function buildRequestInit(\n method: string,\n headers: Record<string, string>,\n body: unknown,\n signal: AbortSignal | undefined,\n rest: Omit<RequestInit, 'body' | 'headers' | 'method' | 'signal'>,\n): RequestInit {\n if (body !== undefined && !isBodyInit(body)) {\n return {\n ...rest,\n body: JSON.stringify(body),\n headers: { 'content-type': 'application/json', ...headers },\n method: method.toUpperCase(),\n signal,\n };\n }\n\n return {\n ...rest,\n ...(body !== undefined && { body: body as BodyInit }),\n headers,\n method: method.toUpperCase(),\n signal,\n };\n}\n\nexport function isBodyInit(value: unknown): value is BodyInit {\n return (\n value instanceof FormData ||\n value instanceof Blob ||\n value instanceof URLSearchParams ||\n typeof value === 'string' ||\n value instanceof ArrayBuffer ||\n ArrayBuffer.isView(value) ||\n (typeof ReadableStream !== 'undefined' && value instanceof ReadableStream)\n );\n}\n"],"mappings":"6BASA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACa,CAWb,OAVI,IAAS,IAAA,IAAa,CAAC,EAAW,CAAI,EACjC,CACL,GAAG,EACH,KAAM,KAAK,UAAU,CAAI,EACzB,QAAS,CAAE,eAAgB,mBAAoB,GAAG,CAAQ,EAC1D,OAAQ,EAAO,YAAY,EAC3B,QACF,EAGK,CACL,GAAG,EACH,GAAI,IAAS,IAAA,IAAa,CAAQ,MAAiB,EACnD,UACA,OAAQ,EAAO,YAAY,EAC3B,QACF,CACF,CAEA,SAAgB,EAAW,EAAmC,CAC5D,OACE,aAAiB,UACjB,aAAiB,MACjB,aAAiB,iBACjB,OAAO,GAAU,UACjB,aAAiB,aACjB,YAAY,OAAO,CAAK,GACvB,OAAO,eAAmB,KAAe,aAAiB,cAE/D"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { hash } from '@vielzeug/arsenal';
|
|
2
|
+
export { hash };
|
|
3
|
+
/**
|
|
4
|
+
* Build a `RequestInit` object from the request components, handling body serialisation.
|
|
5
|
+
* Plain objects/arrays are JSON-serialised and `content-type: application/json` is added.
|
|
6
|
+
* BodyInit values (FormData, Blob, etc.) are forwarded as-is.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildRequestInit(method: string, headers: Record<string, string>, body: unknown, signal: AbortSignal | undefined, rest: Omit<RequestInit, 'body' | 'headers' | 'method' | 'signal'>): RequestInit;
|
|
9
|
+
export declare function isBodyInit(value: unknown): value is BodyInit;
|
|
10
|
+
//# sourceMappingURL=serialize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhB;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC,GAChE,WAAW,CAkBb;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAU5D"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{hash as e}from"@vielzeug/arsenal";function t(e,t,r,i,a){return r!==void 0&&!n(r)?{...a,body:JSON.stringify(r),headers:{"content-type":`application/json`,...t},method:e.toUpperCase(),signal:i}:{...a,...r!==void 0&&{body:r},headers:t,method:e.toUpperCase(),signal:i}}function n(e){return e instanceof FormData||e instanceof Blob||e instanceof URLSearchParams||typeof e==`string`||e instanceof ArrayBuffer||ArrayBuffer.isView(e)||typeof ReadableStream<`u`&&e instanceof ReadableStream}export{t as buildRequestInit,e as hash,n as isBodyInit};
|
|
2
|
+
//# sourceMappingURL=serialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.js","names":[],"sources":["../src/serialize.ts"],"sourcesContent":["import { hash } from '@vielzeug/arsenal';\n\nexport { hash };\n\n/**\n * Build a `RequestInit` object from the request components, handling body serialisation.\n * Plain objects/arrays are JSON-serialised and `content-type: application/json` is added.\n * BodyInit values (FormData, Blob, etc.) are forwarded as-is.\n */\nexport function buildRequestInit(\n method: string,\n headers: Record<string, string>,\n body: unknown,\n signal: AbortSignal | undefined,\n rest: Omit<RequestInit, 'body' | 'headers' | 'method' | 'signal'>,\n): RequestInit {\n if (body !== undefined && !isBodyInit(body)) {\n return {\n ...rest,\n body: JSON.stringify(body),\n headers: { 'content-type': 'application/json', ...headers },\n method: method.toUpperCase(),\n signal,\n };\n }\n\n return {\n ...rest,\n ...(body !== undefined && { body: body as BodyInit }),\n headers,\n method: method.toUpperCase(),\n signal,\n };\n}\n\nexport function isBodyInit(value: unknown): value is BodyInit {\n return (\n value instanceof FormData ||\n value instanceof Blob ||\n value instanceof URLSearchParams ||\n typeof value === 'string' ||\n value instanceof ArrayBuffer ||\n ArrayBuffer.isView(value) ||\n (typeof ReadableStream !== 'undefined' && value instanceof ReadableStream)\n );\n}\n"],"mappings":"yCASA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACa,CAWb,OAVI,IAAS,IAAA,IAAa,CAAC,EAAW,CAAI,EACjC,CACL,GAAG,EACH,KAAM,KAAK,UAAU,CAAI,EACzB,QAAS,CAAE,eAAgB,mBAAoB,GAAG,CAAQ,EAC1D,OAAQ,EAAO,YAAY,EAC3B,QACF,EAGK,CACL,GAAG,EACH,GAAI,IAAS,IAAA,IAAa,CAAQ,MAAiB,EACnD,UACA,OAAQ,EAAO,YAAY,EAC3B,QACF,CACF,CAEA,SAAgB,EAAW,EAAmC,CAC5D,OACE,aAAiB,UACjB,aAAiB,MACjB,aAAiB,iBACjB,OAAO,GAAU,UACjB,aAAiB,aACjB,YAAY,OAAO,CAAK,GACvB,OAAO,eAAmB,KAAe,aAAiB,cAE/D"}
|
package/dist/stream.cjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./serialize.cjs"),n=require("./transport.cjs"),r=require("./url.cjs"),i=require("./retry.cjs");let a=require("@vielzeug/arsenal");function o(e){try{return JSON.parse(e)}catch{return e}}async function s(i,a,o,s,c){let l=s.method.toUpperCase(),u=r.buildUrl(i.baseUrl,a,s.params,s.query),d=n.anySignal(s.extSignal,o.signal)??o.signal,f=n.buildTimeoutSignal(s.timeout??1/0,d),{headers:p,...m}=t.buildRequestInit(l,i.mergeHeaders(s.headers,c),s.body,f,s.fetchInit??{}),h;try{h=await i.dispatch({headers:p,init:m,url:u})}catch(t){throw e.classifyRequestError(t,l,u,f??d)}if(!h.ok){let t=await h.text().catch(()=>``);throw e.CourierHttpError.fromResponse(h,t,l,u)}if(!h.body)throw new e.CourierParseError(`Response has no body`);return h}function c(t){let{transport:r,...c}=t??{},l=!r,u=r??n.createTransportCore(c);function d(t,r={}){if(u.disposed)throw new e.CourierDisposedError(`StreamClient`);let{body:c,fetchInit:l,headers:d,method:f=c===void 0?`GET`:`POST`,onError:p,params:m,query:h,reconnect:g=!1,signal:_,timeout:v}=r;v!==void 0&&n.validateTimeout(v);let y=new Map,b=new AbortController,x=u.track(b),S=!1,C=`connecting`,w=``,T=g===!0?{}:g===!1?{times:0}:g,E=T.times??(g?5:0),D=T.delay??i.fullJitterDelay;function O(e,t){let n=y.get(e);if(!n?.size)return;let r=o(t);for(let e of n)e(r)}async function k(){let e=(await s(u,t,b,{body:c,extSignal:_,fetchInit:l,headers:d,method:f,params:m,query:h,timeout:v},{accept:`text/event-stream`,"cache-control":`no-cache`,...w?{"last-event-id":w}:{}})).body.getReader(),n=new TextDecoder,r=``,i=`message`,a=``,o=``;try{for(;;){let{done:t,value:s}=await e.read();if(t)break;r+=n.decode(s,{stream:!0}).replace(/\r\n/g,`
|
|
2
|
+
`).replace(/\r/g,`
|
|
3
|
+
`);let c;for(;(c=r.indexOf(`
|
|
4
|
+
`))!==-1;){let e=r.slice(0,c);if(r=r.slice(c+1),e===``)a!==``&&(o&&(w=o),O(i,a.replace(/\n$/,``))),i=`message`,a=``,o=``;else if(!e.startsWith(`:`)){let t=e.indexOf(`:`),n=t===-1?e:e.slice(0,t),r=t===-1?``:e.slice(t+1).replace(/^ /,``);if(n===`event`)i=r;else if(n===`data`)a+=(a?`
|
|
5
|
+
`:``)+r;else if(n===`id`&&!r.includes(`\0`))o=r;else if(n===`retry`){let e=parseInt(r,10);Number.isNaN(e)||(D=e)}}}}}finally{e.releaseLock()}}async function A(){let e=0;try{for(;!S&&!u.disposed&&!b.signal.aborted;){let t;C=`connecting`;try{C=`open`,await k()}catch(e){if(S||u.disposed||b.signal.aborted)break;t=e instanceof Error?e:Error(String(e))}if(S||u.disposed||b.signal.aborted)break;if(E===0){t&&p?.(t);break}if(e>=E){p?.(t??Error(`[courier] SSE reconnect budget exhausted`));break}C=`reconnecting`;let n=typeof D==`function`?D(e):D;e++,await(0,a.sleep)(n,b.signal).catch(()=>{})}}finally{C=`closed`,x()}}return A().catch(e=>{!S&&!u.disposed&&p?.(e instanceof Error?e:Error(String(e)))}),{get closed(){return S},dispose(){S=!0,b.abort(),y.clear()},on(e,t){if(S)return()=>{};let n=y.get(e);return n||(n=new Set,y.set(e,n)),n.add(t),()=>n.delete(t)},get status(){return C},[Symbol.dispose](){this.dispose()}}}async function*f(t,r={}){if(u.disposed)throw new e.CourierDisposedError(`StreamClient`);let{body:o,fetchInit:c,headers:l,method:d=o===void 0?`GET`:`POST`,onError:f,params:p,parse:m=`text`,query:h,reconnect:g=!1,signal:_,timeout:v}=r;v!==void 0&&n.validateTimeout(v);let y=g===!0?{}:g===!1?{times:0}:g,b=y.times??(g?5:0),x=y.delay??i.fullJitterDelay,S=new AbortController,C=u.track(S),w=0,T=!1;try{for(;!u.disposed&&!S.signal.aborted;){let n,r=!1;try{let n=(await s(u,t,S,{body:o,extSignal:_,fetchInit:c,headers:l,method:d,params:p,query:h,timeout:v})).body.getReader(),i=new TextDecoder;try{if(m===`ndjson`){let t=``;for(;;){let{done:a,value:o}=await n.read();if(a){let n=t.trim();if(n)try{yield JSON.parse(n)}catch{throw new e.CourierParseError(`NDJSON: failed to parse line: ${n.slice(0,200)}`)}r=!0;break}t+=i.decode(o,{stream:!0});let s;for(;(s=t.indexOf(`
|
|
6
|
+
`))!==-1;){let n=t.slice(0,s).trim();if(t=t.slice(s+1),n)try{yield JSON.parse(n)}catch{throw new e.CourierParseError(`NDJSON: failed to parse line: ${n.slice(0,200)}`)}}}}else for(;;){let{done:e,value:t}=await n.read();if(e){r=!0;break}yield i.decode(t,{stream:!0})}}finally{n.releaseLock()}}catch(e){if(u.disposed||S.signal.aborted)break;n=e instanceof Error?e:Error(String(e))}if(u.disposed||S.signal.aborted)break;if(b===0){if(n)if(f)f(n);else throw n;else T=r;break}if(w>=b){let e=n??Error(`[courier] readable() reconnect budget exhausted`);if(f)f(e);else if(n)throw n;break}if(r&&!n){T=!0;break}let i=typeof x==`function`?x(w):x;w++,await(0,a.sleep)(i,S.signal).catch(()=>{})}}finally{T||S.abort(),C()}}return{cancelAll(){u.cancelAll()},get disposalSignal(){return u.disposalSignal},dispose(){l&&u.dispose()},get disposed(){return u.disposed},getHeaders:u.getHeaders,headers:u.headers,readable:f,sse:d,[Symbol.dispose](){this.dispose()},use:u.use}}exports.createStream=c;
|
|
7
|
+
//# sourceMappingURL=stream.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.cjs","names":[],"sources":["../src/stream.ts"],"sourcesContent":["import { sleep } from '@vielzeug/arsenal';\n\nimport type { Params } from './url';\n\nimport { classifyRequestError, CourierDisposedError, CourierHttpError, CourierParseError } from './errors';\nimport { fullJitterDelay } from './retry';\nimport { buildRequestInit } from './serialize';\nimport {\n anySignal,\n buildTimeoutSignal,\n createTransportCore,\n type TransportCore,\n type TransportOptions,\n validateTimeout,\n} from './transport';\nimport { buildUrl } from './url';\n\nexport type StreamRequestConfig<P extends string = string> = {\n body?: unknown;\n /** Raw fetch options for advanced use (credentials, cache, mode, referrer, etc.). */\n fetchInit?: Omit<RequestInit, 'body' | 'headers' | 'method' | 'signal'>;\n headers?: Record<string, string>;\n method?: string;\n params?: P extends string ? Record<string, string | number | boolean> : never;\n query?: Params;\n /** External abort signal. Merged with internal cancellation. */\n signal?: AbortSignal;\n /** Request timeout in ms. Overrides client default (which is `Infinity` for streaming). */\n timeout?: number;\n};\n\nexport type ReconnectOptions = {\n /**\n * Delay between reconnect attempts in ms, or a zero-based function where\n * `attempt` is the number of failures so far (0 = waiting before the 2nd try).\n * Defaults to full-jitter exponential backoff: `[0, min(1s × 2ⁿ, 30s)]`.\n */\n delay?: number | ((attempt: number) => number);\n /** Total reconnect attempts after the first failure. Defaults to `5`. */\n times?: number;\n};\n\nexport type ReadableConfig<P extends string = string> = StreamRequestConfig<P> & {\n /**\n * Called when the reconnect budget is exhausted or a non-retriable error\n * occurs. Not called when the generator is aborted via signal or dispose.\n */\n onError?: (error: Error) => void;\n /**\n * Response body parsing mode.\n * - `'text'` (default) — yields raw decoded string chunks as they arrive.\n * - `'ndjson'` — splits by newline and JSON-parses each complete line; use `T` to type the values.\n */\n parse?: 'ndjson' | 'text';\n /**\n * Auto-reconnect on connection loss. Uses the same backoff semantics as\n * `sse()` reconnect. `true` uses defaults (5 attempts). Defaults to `false`.\n */\n reconnect?: boolean | ReconnectOptions;\n};\n\nexport type SseOptions<P extends string = string> = StreamRequestConfig<P> & {\n /**\n * Called when the connection is permanently lost (reconnect budget exhausted due to error).\n * Not called on `dispose()`.\n */\n onError?: (error: Error) => void;\n /**\n * Auto-reconnect on connection loss with exponential backoff.\n * `true` uses defaults (5 attempts). Defaults to `false`.\n */\n reconnect?: boolean | ReconnectOptions;\n};\n\n/** Connection lifecycle status for an SSE source. */\nexport type SseStatus = 'connecting' | 'open' | 'reconnecting' | 'closed';\n\n/**\n * Typed SSE event source. `TEvents` maps event name → data type.\n * Non-string data values are automatically JSON-parsed from the raw `data:` field.\n * If parsing fails the raw string is delivered instead.\n */\nexport type SseSource<TEvents extends Record<string, unknown> = Record<string, string>> = {\n [Symbol.dispose](): void;\n /** `true` after `dispose()` is called or the reconnect budget is exhausted. */\n readonly closed: boolean;\n /** Permanently closes the SSE connection. No further events will be dispatched. */\n dispose(): void;\n /**\n * Registers a handler for a named SSE event. Returns an unsubscribe function.\n * The special name `'message'` matches events without an explicit `event:` field.\n * Calling `on()` after `dispose()` returns a no-op unsubscribe.\n */\n on<K extends keyof TEvents & string>(event: K, handler: (data: TEvents[K]) => void): () => void;\n /** Current connection lifecycle status. Useful for displaying a live-connection indicator. */\n readonly status: SseStatus;\n};\n\n/** Parse a raw SSE data string: try JSON, fall back to raw string. */\nfunction parseEventData(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\n/**\n * Shared stream setup: build URL, combine signals, merge headers, dispatch through\n * transport. Returns the Response so both sse() and readable() can focus on parsing.\n * The caller owns the AbortController lifecycle (track + untrack).\n */\nasync function openStreamWith(\n transport: TransportCore,\n url: string,\n ac: AbortController,\n config: {\n body?: unknown;\n extSignal?: AbortSignal;\n fetchInit?: Omit<RequestInit, 'body' | 'headers' | 'method' | 'signal'>;\n headers?: Record<string, string>;\n method: string;\n params?: Params;\n query?: Params;\n timeout?: number;\n },\n extraHeaders?: Record<string, string>,\n): Promise<Response> {\n // Normalize once — error paths and RequestInit both receive the canonical form.\n const method = config.method.toUpperCase();\n const full = buildUrl(transport.baseUrl, url, config.params, config.query);\n const combined = anySignal(config.extSignal, ac.signal) ?? ac.signal;\n // Streaming connections are long-lived — default to Infinity so the transport's\n // REST default (30s) does not cut off streams prematurely.\n const signal = buildTimeoutSignal(config.timeout ?? Number.POSITIVE_INFINITY, combined);\n const requestHeaders = transport.mergeHeaders(config.headers, extraHeaders);\n const { headers: initHeaders, ...restInit } = buildRequestInit(\n method,\n requestHeaders,\n config.body,\n signal,\n config.fetchInit ?? {},\n );\n\n let res: Response;\n\n try {\n res = await transport.dispatch({ headers: initHeaders as Record<string, string>, init: restInit, url: full });\n } catch (err) {\n throw classifyRequestError(err, method, full, signal ?? combined);\n }\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n\n throw CourierHttpError.fromResponse(res, text, method, full);\n }\n\n if (!res.body) throw new CourierParseError('Response has no body');\n\n return res;\n}\n\n/**\n * Creates a streaming client exposing `sse()` (Server-Sent Events) and `readable()`\n * (`ReadableStream`/NDJSON). Both share the same interceptor pipeline, header management,\n * and `AbortController` lifecycle as `createApi`.\n *\n * @example\n * ```ts\n * const stream = createStream({ baseUrl: 'https://api.example.com' });\n *\n * const src = stream.sse<{ message: { text: string } }>('/events', { reconnect: true });\n * src.on('message', (data) => console.log(data.text));\n *\n * for await (const chunk of stream.readable('/completions', { body: { prompt } })) {\n * process.stdout.write(chunk);\n * }\n *\n * // later:\n * stream.dispose();\n * ```\n */\nexport function createStream(opts?: TransportOptions & { transport?: TransportCore }) {\n const { transport: sharedTransport, ...transportOpts } = opts ?? {};\n const ownTransport = !sharedTransport;\n const transport = sharedTransport ?? createTransportCore(transportOpts);\n\n /**\n * Opens a Server-Sent Events connection and returns a typed source.\n *\n * - Intercepts request through the same pipeline as `createApi` (auth, logging, etc.)\n * - Auto-reconnects with exponential backoff when `reconnect` is set\n * - Sends `Last-Event-ID` header on reconnect to resume from last received event\n * - Non-string event data is automatically JSON-parsed\n *\n * @example\n * ```ts\n * const src = stream.sse<{ message: { text: string }; ping: null }>('/events', {\n * reconnect: true,\n * onError: (err) => console.error(err),\n * });\n *\n * src.on('message', (data) => console.log(data.text));\n * src.on('ping', () => {});\n * // later:\n * src.dispose();\n * ```\n */\n function sse<TEvents extends Record<string, unknown> = Record<string, string>, P extends string = string>(\n url: P,\n config: SseOptions<P> = {} as SseOptions<P>,\n ): SseSource<TEvents> {\n if (transport.disposed) throw new CourierDisposedError('StreamClient');\n\n const {\n body,\n fetchInit,\n headers: perRequestHeaders,\n method = body !== undefined ? 'POST' : 'GET',\n onError,\n params,\n query,\n reconnect = false,\n signal: extSignal,\n timeout: cfgTimeout,\n } = config as SseOptions;\n\n if (cfgTimeout !== undefined) validateTimeout(cfgTimeout);\n\n const listeners = new Map<string, Set<(data: unknown) => void>>();\n const ac = new AbortController();\n // Track `ac` once for the entire SSE lifetime — covers active connections AND\n // reconnect sleep windows, so cancelAll()/dispose() always reach it.\n const untrack = transport.track(ac);\n let closed = false;\n let sseStatus: SseStatus = 'connecting';\n let lastEventId = '';\n\n const reconnectOpts: ReconnectOptions = reconnect === true ? {} : reconnect === false ? { times: 0 } : reconnect;\n const maxReconnects = reconnectOpts.times ?? (reconnect ? 5 : 0);\n // `activeReconnectDelay` is mutable so the server's `retry:` field can update\n // the reconnection interval without the caller needing to handle it manually.\n let activeReconnectDelay: number | ((attempt: number) => number) = reconnectOpts.delay ?? fullJitterDelay;\n\n function dispatchEvent(event: string, rawData: string): void {\n const handlers = listeners.get(event);\n\n if (!handlers?.size) return;\n\n const data = parseEventData(rawData);\n\n for (const handler of handlers) handler(data);\n }\n\n async function connect(): Promise<void> {\n const res = await openStreamWith(\n transport,\n url,\n ac,\n {\n body,\n extSignal,\n fetchInit,\n headers: perRequestHeaders as Record<string, string> | undefined,\n method,\n params: params as Params | undefined,\n query,\n timeout: cfgTimeout,\n },\n {\n accept: 'text/event-stream',\n 'cache-control': 'no-cache',\n ...(lastEventId ? { 'last-event-id': lastEventId } : {}),\n },\n );\n\n const reader = res.body!.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let currentEvent = 'message';\n let currentData = '';\n let currentId = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n // Normalise \\r\\n and \\r to \\n before processing\n buffer += decoder.decode(value, { stream: true }).replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n\n let nlIdx: number;\n\n while ((nlIdx = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, nlIdx);\n\n buffer = buffer.slice(nlIdx + 1);\n\n if (line === '') {\n // Empty line → dispatch event\n if (currentData !== '') {\n if (currentId) lastEventId = currentId;\n\n dispatchEvent(currentEvent, currentData.replace(/\\n$/, ''));\n }\n\n currentEvent = 'message';\n currentData = '';\n currentId = '';\n } else if (line.startsWith(':')) {\n // Comment — ignore\n } else {\n const colonIdx = line.indexOf(':');\n const field = colonIdx === -1 ? line : line.slice(0, colonIdx);\n const value = colonIdx === -1 ? '' : line.slice(colonIdx + 1).replace(/^ /, '');\n\n if (field === 'event') currentEvent = value;\n else if (field === 'data') currentData += (currentData ? '\\n' : '') + value;\n else if (field === 'id' && !value.includes('\\0')) currentId = value;\n else if (field === 'retry') {\n // Honor the server's advised reconnect interval (SSE spec §9.2.6).\n const ms = parseInt(value, 10);\n\n if (!Number.isNaN(ms)) activeReconnectDelay = ms;\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n async function run(): Promise<void> {\n let attempt = 0;\n\n try {\n while (!closed && !transport.disposed && !ac.signal.aborted) {\n let connectError: Error | undefined;\n\n sseStatus = 'connecting';\n\n try {\n sseStatus = 'open';\n await connect();\n // Clean server-side close — falls through to reconnect logic below.\n // The attempt counter is intentionally NOT reset here: `times`\n // bounds total reconnects regardless of whether the close was clean or an\n // error, preventing infinite reconnection against servers that always close\n // after a response window (e.g. SSE keepalive patterns).\n } catch (err) {\n if (closed || transport.disposed || ac.signal.aborted) break;\n\n connectError = err instanceof Error ? err : new Error(String(err));\n }\n\n if (closed || transport.disposed || ac.signal.aborted) break;\n\n // No reconnect budget → fire onError for errors, stop either way\n if (maxReconnects === 0) {\n if (connectError) onError?.(connectError);\n\n break;\n }\n\n // Budget exhausted → always signal via onError so the caller knows\n // the reconnect cycle has ended (whether the close was clean or not).\n if (attempt >= maxReconnects) {\n onError?.(connectError ?? new Error('[courier] SSE reconnect budget exhausted'));\n\n break;\n }\n\n // Budget available → sleep and retry\n sseStatus = 'reconnecting';\n\n const delay =\n typeof activeReconnectDelay === 'function' ? activeReconnectDelay(attempt) : activeReconnectDelay;\n\n attempt++;\n await sleep(delay, ac.signal).catch(() => {});\n }\n } finally {\n sseStatus = 'closed';\n untrack();\n }\n }\n\n // Surface unexpected programming errors (not network failures) via onError\n run().catch((err) => {\n if (!closed && !transport.disposed) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n });\n\n return {\n get closed(): boolean {\n return closed;\n },\n\n dispose(): void {\n closed = true;\n ac.abort();\n listeners.clear();\n },\n\n on<K extends keyof TEvents & string>(event: K, handler: (data: TEvents[K]) => void): () => void {\n // After dispose(), return a no-op to avoid silent dead-listener bugs\n if (closed) return () => {};\n\n let set = listeners.get(event);\n\n if (!set) {\n set = new Set<(data: unknown) => void>();\n listeners.set(event, set);\n }\n\n set.add(handler as (data: unknown) => void);\n\n return () => set.delete(handler as (data: unknown) => void);\n },\n\n get status(): SseStatus {\n return sseStatus;\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n };\n }\n\n /**\n * Opens an HTTP streaming connection and returns an async generator yielding\n * response body chunks as they arrive.\n *\n * - `parse: 'text'` (default) — yields raw decoded string chunks\n * - `parse: 'ndjson'` — splits by newline and JSON-parses each line; use `T` to\n * type the parsed values\n *\n * @example\n * ```ts\n * // Raw text stream\n * for await (const chunk of stream.readable('/completions', { body: { prompt } })) {\n * process.stdout.write(chunk);\n * }\n *\n * // NDJSON (e.g. AI token stream, log stream)\n * for await (const msg of stream.readable<ChatMessage>('/chat', {\n * body: { prompt },\n * parse: 'ndjson',\n * })) {\n * console.log(msg.content);\n * }\n * ```\n */\n async function* readable<T = string, P extends string = string>(\n url: P,\n config: ReadableConfig<P> = {} as ReadableConfig<P>,\n ): AsyncGenerator<T> {\n if (transport.disposed) throw new CourierDisposedError('StreamClient');\n\n const {\n body,\n fetchInit,\n headers: perRequestHeaders,\n method = body !== undefined ? 'POST' : 'GET',\n onError,\n params,\n parse = 'text',\n query,\n reconnect = false,\n signal: extSignal,\n timeout: cfgTimeout,\n } = config as ReadableConfig;\n\n if (cfgTimeout !== undefined) validateTimeout(cfgTimeout);\n\n const reconnectOpts: ReconnectOptions = reconnect === true ? {} : reconnect === false ? { times: 0 } : reconnect;\n const maxReconnects = reconnectOpts.times ?? (reconnect ? 5 : 0);\n const reconnectDelay: number | ((attempt: number) => number) = reconnectOpts.delay ?? fullJitterDelay;\n\n const ac = new AbortController();\n const untrack = transport.track(ac);\n let attempt = 0;\n let overallNaturalEnd = false;\n\n try {\n while (!transport.disposed && !ac.signal.aborted) {\n let connectError: Error | undefined;\n let connectionNaturalEnd = false;\n\n try {\n const res = await openStreamWith(transport, url, ac, {\n body,\n extSignal,\n fetchInit,\n headers: perRequestHeaders as Record<string, string> | undefined,\n method,\n params: params as Params | undefined,\n query,\n timeout: cfgTimeout,\n });\n\n const reader = res.body!.getReader();\n const decoder = new TextDecoder();\n\n try {\n if (parse === 'ndjson') {\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n const remaining = buffer.trim();\n\n if (remaining) {\n try {\n yield JSON.parse(remaining) as T;\n } catch {\n throw new CourierParseError(`NDJSON: failed to parse line: ${remaining.slice(0, 200)}`);\n }\n }\n\n connectionNaturalEnd = true;\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n let nlIdx: number;\n\n while ((nlIdx = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, nlIdx).trim();\n\n buffer = buffer.slice(nlIdx + 1);\n\n if (line) {\n try {\n yield JSON.parse(line) as T;\n } catch {\n throw new CourierParseError(`NDJSON: failed to parse line: ${line.slice(0, 200)}`);\n }\n }\n }\n }\n } else {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n connectionNaturalEnd = true;\n break;\n }\n\n yield decoder.decode(value, { stream: true }) as unknown as T;\n }\n }\n } finally {\n reader.releaseLock();\n }\n } catch (err) {\n if (transport.disposed || ac.signal.aborted) break;\n\n connectError = err instanceof Error ? err : new Error(String(err));\n }\n\n if (transport.disposed || ac.signal.aborted) break;\n\n // No reconnect budget — throw or call onError on errors, stop either way\n if (maxReconnects === 0) {\n if (connectError) {\n if (onError) {\n onError(connectError);\n } else {\n throw connectError;\n }\n } else {\n overallNaturalEnd = connectionNaturalEnd;\n }\n\n break;\n }\n\n // Budget exhausted\n if (attempt >= maxReconnects) {\n const budgetError = connectError ?? new Error('[courier] readable() reconnect budget exhausted');\n\n if (onError) {\n onError(budgetError);\n } else if (connectError) {\n throw connectError;\n }\n\n break;\n }\n\n // Clean server close with no reconnect error — done\n if (connectionNaturalEnd && !connectError) {\n overallNaturalEnd = true;\n break;\n }\n\n // Sleep before reconnecting\n const delay = typeof reconnectDelay === 'function' ? reconnectDelay(attempt) : reconnectDelay;\n\n attempt++;\n await sleep(delay, ac.signal).catch(() => {});\n }\n } finally {\n if (!overallNaturalEnd) ac.abort();\n\n untrack();\n }\n }\n\n return {\n cancelAll(): void {\n transport.cancelAll();\n },\n get disposalSignal() {\n return transport.disposalSignal;\n },\n dispose(): void {\n if (ownTransport) transport.dispose();\n },\n get disposed() {\n return transport.disposed;\n },\n getHeaders: transport.getHeaders,\n headers: transport.headers,\n readable,\n sse,\n [Symbol.dispose](): void {\n this.dispose();\n },\n use: transport.use,\n };\n}\n\nexport type StreamClient = ReturnType<typeof createStream>;\n"],"mappings":"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"}
|