@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
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @vielzeug/courier
|
|
2
|
+
|
|
3
|
+
> Type-safe HTTP, query cache, mutations, SSE, and streaming built on native fetch.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@vielzeug/courier) [](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
<details>
|
|
8
|
+
<summary>Quick Reference</summary>
|
|
9
|
+
|
|
10
|
+
**Package:** `@vielzeug/courier` · **Category:** Http
|
|
11
|
+
|
|
12
|
+
**Key exports:** `createApi`, `createCourier`, `createQuery`, `createMutation`, `createStream`, `CourierError`, `HttpError`, `NetworkError`, `TimeoutError`, `AbortError`
|
|
13
|
+
|
|
14
|
+
**When to use:** Typed HTTP, caching, mutations, SSE, and readable streaming with a shared interceptor pipeline.
|
|
15
|
+
|
|
16
|
+
**Related:** [@vielzeug/spell](https://vielzeug.dev/spell/) · [@vielzeug/ripple](https://vielzeug.dev/ripple/) · [@vielzeug/vault](https://vielzeug.dev/vault/)
|
|
17
|
+
|
|
18
|
+
</details>
|
|
19
|
+
|
|
20
|
+
`@vielzeug/courier` is part of Vielzeug and ships as a zero-dependency TypeScript package with ESM+CJS output.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pnpm add @vielzeug/courier
|
|
26
|
+
npm install @vielzeug/courier
|
|
27
|
+
yarn add @vielzeug/courier
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createCourier } from '@vielzeug/courier';
|
|
34
|
+
|
|
35
|
+
type NewUser = { name: string };
|
|
36
|
+
type User = { id: number; name: string };
|
|
37
|
+
|
|
38
|
+
const client = createCourier({
|
|
39
|
+
baseUrl: 'https://api.example.com',
|
|
40
|
+
query: { staleTime: 5_000 },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// observe() returns a SyncStore and triggers a background fetch if stale
|
|
44
|
+
const store = client.query.observe({
|
|
45
|
+
key: ['users', 1],
|
|
46
|
+
fn: ({ signal }) => client.api.get<User>('/users/{id}', { params: { id: 1 }, signal }),
|
|
47
|
+
staleTime: 5_000,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const createUser = client.mutation((input: NewUser, signal) =>
|
|
51
|
+
client.api.post<User>('/users', { body: input, signal }),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const nextUser = await createUser.mutate({ name: 'Alice' });
|
|
55
|
+
client.query.set(['users', nextUser.id], nextUser);
|
|
56
|
+
client.query.invalidate(['users']);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Documentation
|
|
60
|
+
|
|
61
|
+
Full docs: https://vielzeug.dev/courier/
|
|
62
|
+
|
|
63
|
+
- [Overview](https://vielzeug.dev/courier/)
|
|
64
|
+
- [Usage Guide](https://vielzeug.dev/courier/usage)
|
|
65
|
+
- [API Reference](https://vielzeug.dev/courier/api)
|
|
66
|
+
- [Examples](https://vielzeug.dev/courier/examples)
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
|
package/dist/_dev.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __COURIER_PROD__?: boolean }).__COURIER_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/courier] ${msg}`);\n}\n"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA8C,iBAG9D,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,uBAAuB,GAAK,CACtD"}
|
package/dist/_dev.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dev.d.ts","sourceRoot":"","sources":["../src/_dev.ts"],"names":[],"mappings":""}
|
package/dist/_dev.js
ADDED
package/dist/_dev.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __COURIER_PROD__?: boolean }).__COURIER_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/courier] ${msg}`);\n}\n"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA8C,iBAG9D,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,uBAAuB,GAAK,CACtD"}
|
package/dist/api.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./response.cjs"),n=require("./serialize.cjs"),r=require("./transport.cjs"),i=require("./url.cjs");let a=require("@vielzeug/arsenal");var o=new Set([`GET`,`HEAD`,`OPTIONS`]);function s(e,t,n,r){if(o.has(e))return`${e}:${t}:${n}`;if(r!==void 0)return`${e}:${t}:${(0,a.hash)(r)}`}function c(a){let{transport:o,...c}=a??{},l=o??r.createTransportCore(c),u=!o,d=new Map;async function f(n,r,i,a,o,s){let c=r.signal,u;try{u=await l.dispatch({headers:n,init:r,url:i})}catch(t){throw e.classifyRequestError(t,a,i,c)}if(!u.ok){let n;try{n=await t.parseResponse(u,o??`auto`)}catch{n=await u.text().catch(()=>``)}throw e.CourierHttpError.fromResponse(u,n,a,i)}let d;try{d=await t.parseResponse(u,o??`auto`)}catch(t){throw e.classifyRequestError(t,a,i,c)}if(s)try{return s.parse(d)}catch(t){throw new e.CourierSchemaValidationError(t,d)}return d}async function p(t,a,o={}){if(l.disposed)throw new e.CourierDisposedError(`ApiClient`);let c=t.toUpperCase(),u;try{u=i.buildUrl(l.baseUrl,a,o.params,o.query)}catch(t){throw e.classifyRequestError(t,c,a)}let{body:p,dedupe:m=!0,dedupeKey:h,fetchInit:g,headers:_,responseType:v,schema:y,signal:b,timeout:x}=o;x!==void 0&&r.validateTimeout(x);let S=l.mergeHeaders(_),C=m?s(c,u,v??`auto`,h):void 0;if(C){let e=d.get(C);if(e)return e}let w=new AbortController,T=l.track(w),E=r.anySignal(b,w.signal)??w.signal,{headers:D,...O}=n.buildRequestInit(c,S,p,r.buildTimeoutSignal(x??l.getTimeout(),E),g??{}),k=f(D,O,u,c,v,y);C&&d.set(C,k);try{return await k}finally{C&&d.delete(C),T()}}return{cancelAll(){l.cancelAll(),d.clear()},delete:(e,t)=>p(`DELETE`,e,t),get disposalSignal(){return l.disposalSignal},dispose(){d.clear(),u&&l.dispose()},get disposed(){return l.disposed},get:(e,t)=>p(`GET`,e,t),getHeaders:l.getHeaders,headers:l.headers,patch:(e,t)=>p(`PATCH`,e,t),post:(e,t)=>p(`POST`,e,t),put:(e,t)=>p(`PUT`,e,t),request:p,[Symbol.dispose](){this.dispose()},use:l.use}}exports.createApi=c;
|
|
2
|
+
//# sourceMappingURL=api.cjs.map
|
package/dist/api.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.cjs","names":[],"sources":["../src/api.ts"],"sourcesContent":["import type { HttpRequestConfig, Params } from './url';\n\nimport { classifyRequestError, CourierDisposedError, CourierHttpError, CourierSchemaValidationError } from './errors';\nimport { parseResponse } from './response';\nimport { buildRequestInit, hash } from './serialize';\nimport {\n anySignal,\n buildTimeoutSignal,\n createTransportCore,\n type FetchContext,\n type Interceptor,\n type TransportCore,\n type TransportOptions,\n validateTimeout,\n} from './transport';\nimport { buildUrl } from './url';\n\nexport type { FetchContext, Interceptor };\n\n// Only safe + idempotent methods are auto-deduplicated. DELETE is idempotent but\n// not safe (it produces a side-effect), so it must opt-in via explicit dedupeKey\n// to avoid silently coalescing concurrent destructive requests.\nconst IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);\n\nfunction getDedupeKey(\n method: string,\n url: string,\n responseType: string,\n dedupeKey: HttpRequestConfig['dedupeKey'],\n): string | undefined {\n if (IDEMPOTENT_METHODS.has(method)) {\n // Key on method + URL + responseType only. Headers are uniform within a\n // single client instance and should not prevent deduplication when a token\n // refresh updates globalHeaders mid-flight.\n return `${method}:${url}:${responseType}`;\n }\n\n if (dedupeKey === undefined) {\n return undefined;\n }\n\n return `${method}:${url}:${hash(dedupeKey)}`;\n}\n\n/**\n * Creates a typed REST client — `get`/`post`/`put`/`patch`/`delete`/`request`, all backed by a\n * shared interceptor pipeline, header management, and `AbortController` lifecycle.\n *\n * Safe + idempotent requests (`GET`/`HEAD`/`OPTIONS`) are deduplicated by default — concurrent\n * calls with the same method, URL, and response type share a single in-flight request.\n *\n * @example\n * ```ts\n * const api = createApi({ baseUrl: 'https://api.example.com', timeout: 10_000 });\n *\n * const user = await api.get<User>('/users/{id}', { params: { id: '1' } });\n * const created = await api.post<User>('/users', { body: { name: 'Ada' } });\n *\n * api.use(async (ctx, next) => next(ctx.withHeaders({ authorization: `Bearer ${token}` })));\n *\n * // later:\n * api.dispose();\n * ```\n */\nexport function createApi(opts?: TransportOptions & { transport?: TransportCore }) {\n const { transport: sharedTransport, ...transportOpts } = opts ?? {};\n const transport = sharedTransport ?? createTransportCore(transportOpts);\n const ownTransport = !sharedTransport;\n const inFlight = new Map<string, Promise<unknown>>();\n\n async function execute<T>(\n headers: Record<string, string>,\n init: Omit<RequestInit, 'headers'>,\n full: string,\n m: string,\n responseType: HttpRequestConfig['responseType'],\n schema?: { parse(data: unknown): T },\n ): Promise<T> {\n const signal = init.signal as AbortSignal | undefined;\n let res: Response;\n\n try {\n res = await transport.dispatch({ headers, init, url: full });\n } catch (err) {\n throw classifyRequestError(err, m, full, signal);\n }\n\n if (!res.ok) {\n // For error responses: parse body using normal content-type detection,\n // fall back to text for unknown content-types (better than blob for debugging).\n let body: unknown;\n\n try {\n body = await parseResponse(res, responseType ?? 'auto');\n } catch {\n body = await res.text().catch(() => '');\n }\n\n throw CourierHttpError.fromResponse(res, body, m, full);\n }\n\n let raw: unknown;\n\n try {\n raw = await parseResponse(res, responseType ?? 'auto');\n } catch (err) {\n throw classifyRequestError(err, m, full, signal);\n }\n\n if (schema) {\n try {\n return schema.parse(raw);\n } catch (err) {\n throw new CourierSchemaValidationError(err, raw);\n }\n }\n\n return raw as T;\n }\n\n async function request<T, P extends string = string>(\n method: string,\n url: P,\n config: HttpRequestConfig<P> = {} as HttpRequestConfig<P>,\n ) {\n if (transport.disposed) throw new CourierDisposedError('ApiClient');\n\n const m = method.toUpperCase();\n\n let full: string;\n\n try {\n full = buildUrl(transport.baseUrl, url, config.params as Params | undefined, config.query);\n } catch (err) {\n throw classifyRequestError(err, m, url);\n }\n\n const {\n body,\n dedupe = true,\n dedupeKey,\n fetchInit,\n headers,\n responseType,\n schema,\n signal: extSignal,\n timeout: cfgTimeout,\n } = config as HttpRequestConfig;\n\n if (cfgTimeout !== undefined) validateTimeout(cfgTimeout);\n\n const mergedHeaders = transport.mergeHeaders(headers);\n const requestDedupeKey = dedupe ? getDedupeKey(m, full, responseType ?? 'auto', dedupeKey) : undefined;\n\n if (requestDedupeKey) {\n const existing = inFlight.get(requestDedupeKey);\n\n if (existing) return existing as Promise<T>;\n }\n\n const requestAc = new AbortController();\n const untrack = transport.track(requestAc);\n const combinedExt = anySignal(extSignal, requestAc.signal) ?? requestAc.signal;\n const signal = buildTimeoutSignal(cfgTimeout ?? transport.getTimeout(), combinedExt);\n\n const { headers: initHeaders, ...restInit } = buildRequestInit(m, mergedHeaders, body, signal, fetchInit ?? {});\n const p = execute<T>(\n initHeaders as Record<string, string>,\n restInit,\n full,\n m,\n responseType,\n schema as { parse(data: unknown): T } | undefined,\n );\n\n if (requestDedupeKey) inFlight.set(requestDedupeKey, p);\n\n try {\n return (await p) as T;\n } finally {\n if (requestDedupeKey) inFlight.delete(requestDedupeKey);\n\n untrack();\n }\n }\n\n return {\n cancelAll(): void {\n // Note: narrow race — a request that starts after the abort signals fire but before\n // their in-flight promise settles may see an empty inFlight map and error a redundant\n // request. This is an accepted trade-off for the simplicity of a synchronous clear.\n transport.cancelAll();\n inFlight.clear();\n },\n delete: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => request<T, P>('DELETE', url, cfg),\n get disposalSignal() {\n return transport.disposalSignal;\n },\n dispose(): void {\n inFlight.clear();\n\n if (ownTransport) transport.dispose();\n },\n get disposed() {\n return transport.disposed;\n },\n get: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => request<T, P>('GET', url, cfg),\n getHeaders: transport.getHeaders,\n headers: transport.headers,\n patch: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => request<T, P>('PATCH', url, cfg),\n post: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => request<T, P>('POST', url, cfg),\n put: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => request<T, P>('PUT', url, cfg),\n request,\n [Symbol.dispose](): void {\n this.dispose();\n },\n use: transport.use,\n };\n}\n\nexport type ApiClient = ReturnType<typeof createApi>;\n"],"mappings":"gLAsBA,IAAM,EAAqB,IAAI,IAAI,CAAC,MAAO,OAAQ,SAAS,CAAC,EAE7D,SAAS,EACP,EACA,EACA,EACA,EACoB,CACpB,GAAI,EAAmB,IAAI,CAAM,EAI/B,MAAO,GAAG,EAAO,GAAG,EAAI,GAAG,IAGzB,OAAc,IAAA,GAIlB,MAAO,GAAG,EAAO,GAAG,EAAI,IAAA,EAAA,EAAA,KAAA,CAAQ,CAAS,GAC3C,CAsBA,SAAgB,EAAU,EAAyD,CACjF,GAAM,CAAE,UAAW,EAAiB,GAAG,GAAkB,GAAQ,CAAC,EAC5D,EAAY,GAAmB,EAAA,oBAAoB,CAAa,EAChE,EAAe,CAAC,EAChB,EAAW,IAAI,IAErB,eAAe,EACb,EACA,EACA,EACA,EACA,EACA,EACY,CACZ,IAAM,EAAS,EAAK,OAChB,EAEJ,GAAI,CACF,EAAM,MAAM,EAAU,SAAS,CAAE,UAAS,OAAM,IAAK,CAAK,CAAC,CAC7D,OAAS,EAAK,CACZ,MAAM,EAAA,qBAAqB,EAAK,EAAG,EAAM,CAAM,CACjD,CAEA,GAAI,CAAC,EAAI,GAAI,CAGX,IAAI,EAEJ,GAAI,CACF,EAAO,MAAM,EAAA,cAAc,EAAK,GAAgB,MAAM,CACxD,MAAQ,CACN,EAAO,MAAM,EAAI,KAAK,CAAC,CAAC,UAAY,EAAE,CACxC,CAEA,MAAM,EAAA,iBAAiB,aAAa,EAAK,EAAM,EAAG,CAAI,CACxD,CAEA,IAAI,EAEJ,GAAI,CACF,EAAM,MAAM,EAAA,cAAc,EAAK,GAAgB,MAAM,CACvD,OAAS,EAAK,CACZ,MAAM,EAAA,qBAAqB,EAAK,EAAG,EAAM,CAAM,CACjD,CAEA,GAAI,EACF,GAAI,CACF,OAAO,EAAO,MAAM,CAAG,CACzB,OAAS,EAAK,CACZ,MAAM,IAAI,EAAA,6BAA6B,EAAK,CAAG,CACjD,CAGF,OAAO,CACT,CAEA,eAAe,EACb,EACA,EACA,EAA+B,CAAC,EAChC,CACA,GAAI,EAAU,SAAU,MAAM,IAAI,EAAA,qBAAqB,WAAW,EAElE,IAAM,EAAI,EAAO,YAAY,EAEzB,EAEJ,GAAI,CACF,EAAO,EAAA,SAAS,EAAU,QAAS,EAAK,EAAO,OAA8B,EAAO,KAAK,CAC3F,OAAS,EAAK,CACZ,MAAM,EAAA,qBAAqB,EAAK,EAAG,CAAG,CACxC,CAEA,GAAM,CACJ,OACA,SAAS,GACT,YACA,YACA,UACA,eACA,SACA,OAAQ,EACR,QAAS,GACP,EAEA,IAAe,IAAA,IAAW,EAAA,gBAAgB,CAAU,EAExD,IAAM,EAAgB,EAAU,aAAa,CAAO,EAC9C,EAAmB,EAAS,EAAa,EAAG,EAAM,GAAgB,OAAQ,CAAS,EAAI,IAAA,GAE7F,GAAI,EAAkB,CACpB,IAAM,EAAW,EAAS,IAAI,CAAgB,EAE9C,GAAI,EAAU,OAAO,CACvB,CAEA,IAAM,EAAY,IAAI,gBAChB,EAAU,EAAU,MAAM,CAAS,EACnC,EAAc,EAAA,UAAU,EAAW,EAAU,MAAM,GAAK,EAAU,OAGlE,CAAE,QAAS,EAAa,GAAG,GAAa,EAAA,iBAAiB,EAAG,EAAe,EAFlE,EAAA,mBAAmB,GAAc,EAAU,WAAW,EAAG,CAEe,EAAQ,GAAa,CAAC,CAAC,EACxG,EAAI,EACR,EACA,EACA,EACA,EACA,EACA,CACF,EAEI,GAAkB,EAAS,IAAI,EAAkB,CAAC,EAEtD,GAAI,CACF,OAAQ,MAAM,CAChB,QAAU,CACJ,GAAkB,EAAS,OAAO,CAAgB,EAEtD,EAAQ,CACV,CACF,CAEA,MAAO,CACL,WAAkB,CAIhB,EAAU,UAAU,EACpB,EAAS,MAAM,CACjB,EACA,QAAuC,EAAQ,IAA+B,EAAc,SAAU,EAAK,CAAG,EAC9G,IAAI,gBAAiB,CACnB,OAAO,EAAU,cACnB,EACA,SAAgB,CACd,EAAS,MAAM,EAEX,GAAc,EAAU,QAAQ,CACtC,EACA,IAAI,UAAW,CACb,OAAO,EAAU,QACnB,EACA,KAAoC,EAAQ,IAA+B,EAAc,MAAO,EAAK,CAAG,EACxG,WAAY,EAAU,WACtB,QAAS,EAAU,QACnB,OAAsC,EAAQ,IAA+B,EAAc,QAAS,EAAK,CAAG,EAC5G,MAAqC,EAAQ,IAA+B,EAAc,OAAQ,EAAK,CAAG,EAC1G,KAAoC,EAAQ,IAA+B,EAAc,MAAO,EAAK,CAAG,EACxG,UACA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,EACA,IAAK,EAAU,GACjB,CACF"}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { HttpRequestConfig } from './url';
|
|
2
|
+
import { type FetchContext, type Interceptor, type TransportCore, type TransportOptions } from './transport';
|
|
3
|
+
export type { FetchContext, Interceptor };
|
|
4
|
+
/**
|
|
5
|
+
* Creates a typed REST client — `get`/`post`/`put`/`patch`/`delete`/`request`, all backed by a
|
|
6
|
+
* shared interceptor pipeline, header management, and `AbortController` lifecycle.
|
|
7
|
+
*
|
|
8
|
+
* Safe + idempotent requests (`GET`/`HEAD`/`OPTIONS`) are deduplicated by default — concurrent
|
|
9
|
+
* calls with the same method, URL, and response type share a single in-flight request.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const api = createApi({ baseUrl: 'https://api.example.com', timeout: 10_000 });
|
|
14
|
+
*
|
|
15
|
+
* const user = await api.get<User>('/users/{id}', { params: { id: '1' } });
|
|
16
|
+
* const created = await api.post<User>('/users', { body: { name: 'Ada' } });
|
|
17
|
+
*
|
|
18
|
+
* api.use(async (ctx, next) => next(ctx.withHeaders({ authorization: `Bearer ${token}` })));
|
|
19
|
+
*
|
|
20
|
+
* // later:
|
|
21
|
+
* api.dispose();
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function createApi(opts?: TransportOptions & {
|
|
25
|
+
transport?: TransportCore;
|
|
26
|
+
}): {
|
|
27
|
+
cancelAll(): void;
|
|
28
|
+
delete: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => Promise<T>;
|
|
29
|
+
readonly disposalSignal: AbortSignal;
|
|
30
|
+
dispose(): void;
|
|
31
|
+
readonly disposed: boolean;
|
|
32
|
+
get: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => Promise<T>;
|
|
33
|
+
getHeaders: () => Readonly<Record<string, string>>;
|
|
34
|
+
headers: (updates: Record<string, string | undefined>) => void;
|
|
35
|
+
patch: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => Promise<T>;
|
|
36
|
+
post: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => Promise<T>;
|
|
37
|
+
put: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => Promise<T>;
|
|
38
|
+
request: <T, P extends string = string>(method: string, url: P, config?: HttpRequestConfig<P>) => Promise<T>;
|
|
39
|
+
[Symbol.dispose](): void;
|
|
40
|
+
use: (interceptor: Interceptor) => () => void;
|
|
41
|
+
};
|
|
42
|
+
export type ApiClient = ReturnType<typeof createApi>;
|
|
43
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAU,MAAM,OAAO,CAAC;AAKvD,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EAEtB,MAAM,aAAa,CAAC;AAGrB,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;AA2B1C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,gBAAgB,GAAG;IAAE,SAAS,CAAC,EAAE,aAAa,CAAA;CAAE;iBA2HhE,IAAI;aAOR,CAAC,EAAE,CAAC,SAAS,MAAM,gBAAgB,CAAC,QAAQ,iBAAiB,CAAC,CAAC,CAAC;;eAI9D,IAAI;;UAQT,CAAC,EAAE,CAAC,SAAS,MAAM,gBAAgB,CAAC,QAAQ,iBAAiB,CAAC,CAAC,CAAC;;;YAG9D,CAAC,EAAE,CAAC,SAAS,MAAM,gBAAgB,CAAC,QAAQ,iBAAiB,CAAC,CAAC,CAAC;WACjE,CAAC,EAAE,CAAC,SAAS,MAAM,gBAAgB,CAAC,QAAQ,iBAAiB,CAAC,CAAC,CAAC;UACjE,CAAC,EAAE,CAAC,SAAS,MAAM,gBAAgB,CAAC,QAAQ,iBAAiB,CAAC,CAAC,CAAC;cA3FjD,CAAC,EAAE,CAAC,SAAS,MAAM,mBAChC,MAAM,OACT,CAAC,WACE,iBAAiB,CAAC,CAAC,CAAC;wBA0FR,IAAI;;EAK3B;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{CourierDisposedError as e,CourierHttpError as t,CourierSchemaValidationError as n,classifyRequestError as r}from"./errors.js";import{parseResponse as i}from"./response.js";import{buildRequestInit as a,hash as o}from"./serialize.js";import{anySignal as s,buildTimeoutSignal as c,createTransportCore as l,validateTimeout as u}from"./transport.js";import{buildUrl as d}from"./url.js";var f=new Set([`GET`,`HEAD`,`OPTIONS`]);function p(e,t,n,r){if(f.has(e))return`${e}:${t}:${n}`;if(r!==void 0)return`${e}:${t}:${o(r)}`}function m(o){let{transport:f,...m}=o??{},h=f??l(m),g=!f,_=new Map;async function v(e,a,o,s,c,l){let u=a.signal,d;try{d=await h.dispatch({headers:e,init:a,url:o})}catch(e){throw r(e,s,o,u)}if(!d.ok){let e;try{e=await i(d,c??`auto`)}catch{e=await d.text().catch(()=>``)}throw t.fromResponse(d,e,s,o)}let f;try{f=await i(d,c??`auto`)}catch(e){throw r(e,s,o,u)}if(l)try{return l.parse(f)}catch(e){throw new n(e,f)}return f}async function y(t,n,i={}){if(h.disposed)throw new e(`ApiClient`);let o=t.toUpperCase(),l;try{l=d(h.baseUrl,n,i.params,i.query)}catch(e){throw r(e,o,n)}let{body:f,dedupe:m=!0,dedupeKey:g,fetchInit:y,headers:b,responseType:x,schema:S,signal:C,timeout:w}=i;w!==void 0&&u(w);let T=h.mergeHeaders(b),E=m?p(o,l,x??`auto`,g):void 0;if(E){let e=_.get(E);if(e)return e}let D=new AbortController,O=h.track(D),k=s(C,D.signal)??D.signal,{headers:A,...j}=a(o,T,f,c(w??h.getTimeout(),k),y??{}),M=v(A,j,l,o,x,S);E&&_.set(E,M);try{return await M}finally{E&&_.delete(E),O()}}return{cancelAll(){h.cancelAll(),_.clear()},delete:(e,t)=>y(`DELETE`,e,t),get disposalSignal(){return h.disposalSignal},dispose(){_.clear(),g&&h.dispose()},get disposed(){return h.disposed},get:(e,t)=>y(`GET`,e,t),getHeaders:h.getHeaders,headers:h.headers,patch:(e,t)=>y(`PATCH`,e,t),post:(e,t)=>y(`POST`,e,t),put:(e,t)=>y(`PUT`,e,t),request:y,[Symbol.dispose](){this.dispose()},use:h.use}}export{m as createApi};
|
|
2
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","names":[],"sources":["../src/api.ts"],"sourcesContent":["import type { HttpRequestConfig, Params } from './url';\n\nimport { classifyRequestError, CourierDisposedError, CourierHttpError, CourierSchemaValidationError } from './errors';\nimport { parseResponse } from './response';\nimport { buildRequestInit, hash } from './serialize';\nimport {\n anySignal,\n buildTimeoutSignal,\n createTransportCore,\n type FetchContext,\n type Interceptor,\n type TransportCore,\n type TransportOptions,\n validateTimeout,\n} from './transport';\nimport { buildUrl } from './url';\n\nexport type { FetchContext, Interceptor };\n\n// Only safe + idempotent methods are auto-deduplicated. DELETE is idempotent but\n// not safe (it produces a side-effect), so it must opt-in via explicit dedupeKey\n// to avoid silently coalescing concurrent destructive requests.\nconst IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);\n\nfunction getDedupeKey(\n method: string,\n url: string,\n responseType: string,\n dedupeKey: HttpRequestConfig['dedupeKey'],\n): string | undefined {\n if (IDEMPOTENT_METHODS.has(method)) {\n // Key on method + URL + responseType only. Headers are uniform within a\n // single client instance and should not prevent deduplication when a token\n // refresh updates globalHeaders mid-flight.\n return `${method}:${url}:${responseType}`;\n }\n\n if (dedupeKey === undefined) {\n return undefined;\n }\n\n return `${method}:${url}:${hash(dedupeKey)}`;\n}\n\n/**\n * Creates a typed REST client — `get`/`post`/`put`/`patch`/`delete`/`request`, all backed by a\n * shared interceptor pipeline, header management, and `AbortController` lifecycle.\n *\n * Safe + idempotent requests (`GET`/`HEAD`/`OPTIONS`) are deduplicated by default — concurrent\n * calls with the same method, URL, and response type share a single in-flight request.\n *\n * @example\n * ```ts\n * const api = createApi({ baseUrl: 'https://api.example.com', timeout: 10_000 });\n *\n * const user = await api.get<User>('/users/{id}', { params: { id: '1' } });\n * const created = await api.post<User>('/users', { body: { name: 'Ada' } });\n *\n * api.use(async (ctx, next) => next(ctx.withHeaders({ authorization: `Bearer ${token}` })));\n *\n * // later:\n * api.dispose();\n * ```\n */\nexport function createApi(opts?: TransportOptions & { transport?: TransportCore }) {\n const { transport: sharedTransport, ...transportOpts } = opts ?? {};\n const transport = sharedTransport ?? createTransportCore(transportOpts);\n const ownTransport = !sharedTransport;\n const inFlight = new Map<string, Promise<unknown>>();\n\n async function execute<T>(\n headers: Record<string, string>,\n init: Omit<RequestInit, 'headers'>,\n full: string,\n m: string,\n responseType: HttpRequestConfig['responseType'],\n schema?: { parse(data: unknown): T },\n ): Promise<T> {\n const signal = init.signal as AbortSignal | undefined;\n let res: Response;\n\n try {\n res = await transport.dispatch({ headers, init, url: full });\n } catch (err) {\n throw classifyRequestError(err, m, full, signal);\n }\n\n if (!res.ok) {\n // For error responses: parse body using normal content-type detection,\n // fall back to text for unknown content-types (better than blob for debugging).\n let body: unknown;\n\n try {\n body = await parseResponse(res, responseType ?? 'auto');\n } catch {\n body = await res.text().catch(() => '');\n }\n\n throw CourierHttpError.fromResponse(res, body, m, full);\n }\n\n let raw: unknown;\n\n try {\n raw = await parseResponse(res, responseType ?? 'auto');\n } catch (err) {\n throw classifyRequestError(err, m, full, signal);\n }\n\n if (schema) {\n try {\n return schema.parse(raw);\n } catch (err) {\n throw new CourierSchemaValidationError(err, raw);\n }\n }\n\n return raw as T;\n }\n\n async function request<T, P extends string = string>(\n method: string,\n url: P,\n config: HttpRequestConfig<P> = {} as HttpRequestConfig<P>,\n ) {\n if (transport.disposed) throw new CourierDisposedError('ApiClient');\n\n const m = method.toUpperCase();\n\n let full: string;\n\n try {\n full = buildUrl(transport.baseUrl, url, config.params as Params | undefined, config.query);\n } catch (err) {\n throw classifyRequestError(err, m, url);\n }\n\n const {\n body,\n dedupe = true,\n dedupeKey,\n fetchInit,\n headers,\n responseType,\n schema,\n signal: extSignal,\n timeout: cfgTimeout,\n } = config as HttpRequestConfig;\n\n if (cfgTimeout !== undefined) validateTimeout(cfgTimeout);\n\n const mergedHeaders = transport.mergeHeaders(headers);\n const requestDedupeKey = dedupe ? getDedupeKey(m, full, responseType ?? 'auto', dedupeKey) : undefined;\n\n if (requestDedupeKey) {\n const existing = inFlight.get(requestDedupeKey);\n\n if (existing) return existing as Promise<T>;\n }\n\n const requestAc = new AbortController();\n const untrack = transport.track(requestAc);\n const combinedExt = anySignal(extSignal, requestAc.signal) ?? requestAc.signal;\n const signal = buildTimeoutSignal(cfgTimeout ?? transport.getTimeout(), combinedExt);\n\n const { headers: initHeaders, ...restInit } = buildRequestInit(m, mergedHeaders, body, signal, fetchInit ?? {});\n const p = execute<T>(\n initHeaders as Record<string, string>,\n restInit,\n full,\n m,\n responseType,\n schema as { parse(data: unknown): T } | undefined,\n );\n\n if (requestDedupeKey) inFlight.set(requestDedupeKey, p);\n\n try {\n return (await p) as T;\n } finally {\n if (requestDedupeKey) inFlight.delete(requestDedupeKey);\n\n untrack();\n }\n }\n\n return {\n cancelAll(): void {\n // Note: narrow race — a request that starts after the abort signals fire but before\n // their in-flight promise settles may see an empty inFlight map and error a redundant\n // request. This is an accepted trade-off for the simplicity of a synchronous clear.\n transport.cancelAll();\n inFlight.clear();\n },\n delete: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => request<T, P>('DELETE', url, cfg),\n get disposalSignal() {\n return transport.disposalSignal;\n },\n dispose(): void {\n inFlight.clear();\n\n if (ownTransport) transport.dispose();\n },\n get disposed() {\n return transport.disposed;\n },\n get: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => request<T, P>('GET', url, cfg),\n getHeaders: transport.getHeaders,\n headers: transport.headers,\n patch: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => request<T, P>('PATCH', url, cfg),\n post: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => request<T, P>('POST', url, cfg),\n put: <T, P extends string = string>(url: P, cfg?: HttpRequestConfig<P>) => request<T, P>('PUT', url, cfg),\n request,\n [Symbol.dispose](): void {\n this.dispose();\n },\n use: transport.use,\n };\n}\n\nexport type ApiClient = ReturnType<typeof createApi>;\n"],"mappings":"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"}
|
package/dist/batcher.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./errors.cjs");function t(t){let{maxSize:n=25,resolve:r,resolveSettled:i,window:a=0}=t,o=[],s=new AbortController,c=!1,l=null,u=!1;function d(){if(o.length===0)return;let t=o.splice(0,n);o.length>0&&f();let a=t.map(e=>e.key);try{i?i(a).then(n=>{if(n.length!==t.length){let r=new e.CourierBatcherError(`Batcher: resolveSettled() returned ${n.length} results for ${t.length} keys`);for(let e of t)e.reject(r);return}t.forEach((e,t)=>{let r=n[t];r.status===`fulfilled`?e.resolve(r.value):e.reject(r.reason)})}).catch(e=>{for(let n of t)n.reject(e)}):r(a).then(n=>{if(n.length!==t.length){let r=new e.CourierBatcherError(`Batcher: resolve() returned ${n.length} results for ${t.length} keys`);for(let e of t)e.reject(r);return}t.forEach((e,t)=>e.resolve(n[t]))}).catch(e=>{for(let n of t)n.reject(e)})}catch(e){for(let n of t)n.reject(e)}}function f(){c||(c=!0,a===0?queueMicrotask(()=>{c=!1,d()}):l=setTimeout(()=>{l=null,c=!1,d()},a))}return{get disposalSignal(){return s.signal},dispose(){if(u)return;u=!0,l!==null&&(clearTimeout(l),l=null);let t=new e.CourierDisposedError(`Batcher`);for(let e of o.splice(0))e.reject(t);c=!1,s.abort()},get disposed(){return u},load(t){return u?Promise.reject(new e.CourierDisposedError(`Batcher`)):new Promise((e,r)=>{o.push({key:t,reject:r,resolve:e}),o.length>=n?(l!==null&&(clearTimeout(l),l=null),c=!1,d()):f()})},[Symbol.dispose](){this.dispose()}}}exports.createBatcher=t;
|
|
2
|
+
//# sourceMappingURL=batcher.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batcher.cjs","names":[],"sources":["../src/batcher.ts"],"sourcesContent":["import { CourierBatcherError, CourierDisposedError } from './errors';\n\ntype BatcherBase = {\n /** Maximum batch size before a flush is forced. Defaults to `25`. */\n maxSize?: number;\n /**\n * Scheduling window in ms. Defaults to `0` (next microtask), which coalesces\n * all `load()` calls within the same synchronous block into one batch.\n * Set a positive value (e.g. `16`) to coalesce across async boundaries.\n */\n window?: number;\n};\n\n/**\n * Batcher options. Exactly one of `resolve` or `resolveSettled` must be provided — they are mutually exclusive.\n *\n * - **`resolve`**: execute a batch and return results in the same order. All succeed or all fail.\n * - **`resolveSettled`**: per-key error isolation — each `load()` fulfills or rejects independently.\n */\nexport type BatcherOptions<K, V> =\n | (BatcherBase & {\n /**\n * Execute a batch of keys and return results in the same order.\n * The returned array must have the same length as `keys`.\n */\n resolve: (keys: K[]) => Promise<V[]>;\n resolveSettled?: never;\n })\n | (BatcherBase & {\n resolve?: never;\n /**\n * Per-key error isolation. Receives the same key array but must return a\n * `PromiseSettledResult<V>[]` of equal length. Each `load()` promise fulfills\n * or rejects independently based on its corresponding settled result.\n */\n resolveSettled: (keys: K[]) => Promise<PromiseSettledResult<V>[]>;\n });\n\ntype PendingItem<K, V> = {\n key: K;\n reject: (reason: unknown) => void;\n resolve: (value: V) => void;\n};\n\n/**\n * DataLoader-style request batcher. Coalesces individual `load()` calls made\n * within the same scheduling window into a single `resolve()` call.\n *\n * Typical use: N+1 query elimination, request fan-out coalescing.\n *\n * @example\n * ```ts\n * const userLoader = createBatcher({\n * resolve: async (ids) => api.post<User[]>('/users/batch', { body: { ids } }),\n * });\n *\n * // These 3 calls collapse into 1 POST /users/batch { ids: [1, 2, 3] }\n * const [alice, bob, carol] = await Promise.all([\n * userLoader.load(1),\n * userLoader.load(2),\n * userLoader.load(3),\n * ]);\n * ```\n */\nexport function createBatcher<K, V>(opts: BatcherOptions<K, V>) {\n const { maxSize = 25, resolve, resolveSettled, window: windowMs = 0 } = opts;\n\n const queue: PendingItem<K, V>[] = [];\n const controller = new AbortController();\n let scheduled = false;\n let pendingTimer: ReturnType<typeof setTimeout> | null = null;\n let disposed = false;\n\n function flush(): void {\n if (queue.length === 0) return;\n\n const batch = queue.splice(0, maxSize);\n\n // If more items remain after taking maxSize, schedule another flush immediately.\n if (queue.length > 0) schedule();\n\n const keys = batch.map((item) => item.key);\n\n // resolve()/resolveSettled() are called directly (not just chained with .then/.catch) so a\n // consumer function that throws synchronously — rather than returning a rejected promise —\n // still rejects every pending load() instead of leaving them hanging forever.\n try {\n if (resolveSettled) {\n void resolveSettled(keys)\n .then((results) => {\n if (results.length !== batch.length) {\n const err = new CourierBatcherError(\n `Batcher: resolveSettled() returned ${results.length} results for ${batch.length} keys`,\n );\n\n for (const item of batch) item.reject(err);\n\n return;\n }\n\n batch.forEach((item, i) => {\n const r = results[i];\n\n if (r.status === 'fulfilled') {\n item.resolve(r.value);\n } else {\n item.reject(r.reason);\n }\n });\n })\n .catch((err: unknown) => {\n for (const item of batch) item.reject(err);\n });\n } else {\n void resolve(keys)\n .then((results) => {\n if (results.length !== batch.length) {\n const err = new CourierBatcherError(\n `Batcher: resolve() returned ${results.length} results for ${batch.length} keys`,\n );\n\n for (const item of batch) item.reject(err);\n\n return;\n }\n\n batch.forEach((item, i) => item.resolve(results[i]));\n })\n .catch((err: unknown) => {\n for (const item of batch) item.reject(err);\n });\n }\n } catch (err) {\n for (const item of batch) item.reject(err);\n }\n }\n\n function schedule(): void {\n if (scheduled) return;\n\n scheduled = true;\n\n if (windowMs === 0) {\n queueMicrotask(() => {\n scheduled = false;\n flush();\n });\n } else {\n pendingTimer = setTimeout(() => {\n pendingTimer = null;\n scheduled = false;\n flush();\n }, windowMs);\n }\n }\n\n return {\n get disposalSignal(): AbortSignal {\n return controller.signal;\n },\n\n /**\n * Cancels all pending `load()` promises with a disposal error and stops\n * any scheduled flushes. Safe to call multiple times.\n */\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n\n if (pendingTimer !== null) {\n clearTimeout(pendingTimer);\n pendingTimer = null;\n }\n\n const err = new CourierDisposedError('Batcher');\n\n for (const item of queue.splice(0)) item.reject(err);\n\n scheduled = false;\n controller.abort();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n load(key: K): Promise<V> {\n if (disposed) return Promise.reject(new CourierDisposedError('Batcher'));\n\n return new Promise<V>((res, rej) => {\n queue.push({ key, reject: rej, resolve: res });\n\n if (queue.length >= maxSize) {\n // Force-flush immediately when the batch is full.\n // Cancel any pending timer first so it cannot fire a second flush.\n if (pendingTimer !== null) {\n clearTimeout(pendingTimer);\n pendingTimer = null;\n }\n\n scheduled = false;\n flush();\n } else {\n schedule();\n }\n });\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n };\n}\n\nexport type Batcher<K, V> = ReturnType<typeof createBatcher<K, V>>;\n"],"mappings":"gCAgEA,SAAgB,EAAoB,EAA4B,CAC9D,GAAM,CAAE,UAAU,GAAI,UAAS,iBAAgB,OAAQ,EAAW,GAAM,EAElE,EAA6B,CAAC,EAC9B,EAAa,IAAI,gBACnB,EAAY,GACZ,EAAqD,KACrD,EAAW,GAEf,SAAS,GAAc,CACrB,GAAI,EAAM,SAAW,EAAG,OAExB,IAAM,EAAQ,EAAM,OAAO,EAAG,CAAO,EAGjC,EAAM,OAAS,GAAG,EAAS,EAE/B,IAAM,EAAO,EAAM,IAAK,GAAS,EAAK,GAAG,EAKzC,GAAI,CACE,EACF,EAAoB,CAAI,CAAC,CACtB,KAAM,GAAY,CACjB,GAAI,EAAQ,SAAW,EAAM,OAAQ,CACnC,IAAM,EAAM,IAAI,EAAA,oBACd,sCAAsC,EAAQ,OAAO,eAAe,EAAM,OAAO,MACnF,EAEA,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,EAEzC,MACF,CAEA,EAAM,SAAS,EAAM,IAAM,CACzB,IAAM,EAAI,EAAQ,GAEd,EAAE,SAAW,YACf,EAAK,QAAQ,EAAE,KAAK,EAEpB,EAAK,OAAO,EAAE,MAAM,CAExB,CAAC,CACH,CAAC,CAAC,CACD,MAAO,GAAiB,CACvB,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CAAC,EAEH,EAAa,CAAI,CAAC,CACf,KAAM,GAAY,CACjB,GAAI,EAAQ,SAAW,EAAM,OAAQ,CACnC,IAAM,EAAM,IAAI,EAAA,oBACd,+BAA+B,EAAQ,OAAO,eAAe,EAAM,OAAO,MAC5E,EAEA,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,EAEzC,MACF,CAEA,EAAM,SAAS,EAAM,IAAM,EAAK,QAAQ,EAAQ,EAAE,CAAC,CACrD,CAAC,CAAC,CACD,MAAO,GAAiB,CACvB,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CAAC,CAEP,OAAS,EAAK,CACZ,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CACF,CAEA,SAAS,GAAiB,CACpB,IAEJ,EAAY,GAER,IAAa,EACf,mBAAqB,CACnB,EAAY,GACZ,EAAM,CACR,CAAC,EAED,EAAe,eAAiB,CAC9B,EAAe,KACf,EAAY,GACZ,EAAM,CACR,EAAG,CAAQ,EAEf,CAEA,MAAO,CACL,IAAI,gBAA8B,CAChC,OAAO,EAAW,MACpB,EAMA,SAAgB,CACd,GAAI,EAAU,OAEd,EAAW,GAEP,IAAiB,OACnB,aAAa,CAAY,EACzB,EAAe,MAGjB,IAAM,EAAM,IAAI,EAAA,qBAAqB,SAAS,EAE9C,IAAK,IAAM,KAAQ,EAAM,OAAO,CAAC,EAAG,EAAK,OAAO,CAAG,EAEnD,EAAY,GACZ,EAAW,MAAM,CACnB,EAEA,IAAI,UAAoB,CACtB,OAAO,CACT,EAEA,KAAK,EAAoB,CAGvB,OAFI,EAAiB,QAAQ,OAAO,IAAI,EAAA,qBAAqB,SAAS,CAAC,EAEhE,IAAI,SAAY,EAAK,IAAQ,CAClC,EAAM,KAAK,CAAE,MAAK,OAAQ,EAAK,QAAS,CAAI,CAAC,EAEzC,EAAM,QAAU,GAGd,IAAiB,OACnB,aAAa,CAAY,EACzB,EAAe,MAGjB,EAAY,GACZ,EAAM,GAEN,EAAS,CAEb,CAAC,CACH,EAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF,CACF"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
type BatcherBase = {
|
|
2
|
+
/** Maximum batch size before a flush is forced. Defaults to `25`. */
|
|
3
|
+
maxSize?: number;
|
|
4
|
+
/**
|
|
5
|
+
* Scheduling window in ms. Defaults to `0` (next microtask), which coalesces
|
|
6
|
+
* all `load()` calls within the same synchronous block into one batch.
|
|
7
|
+
* Set a positive value (e.g. `16`) to coalesce across async boundaries.
|
|
8
|
+
*/
|
|
9
|
+
window?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Batcher options. Exactly one of `resolve` or `resolveSettled` must be provided — they are mutually exclusive.
|
|
13
|
+
*
|
|
14
|
+
* - **`resolve`**: execute a batch and return results in the same order. All succeed or all fail.
|
|
15
|
+
* - **`resolveSettled`**: per-key error isolation — each `load()` fulfills or rejects independently.
|
|
16
|
+
*/
|
|
17
|
+
export type BatcherOptions<K, V> = (BatcherBase & {
|
|
18
|
+
/**
|
|
19
|
+
* Execute a batch of keys and return results in the same order.
|
|
20
|
+
* The returned array must have the same length as `keys`.
|
|
21
|
+
*/
|
|
22
|
+
resolve: (keys: K[]) => Promise<V[]>;
|
|
23
|
+
resolveSettled?: never;
|
|
24
|
+
}) | (BatcherBase & {
|
|
25
|
+
resolve?: never;
|
|
26
|
+
/**
|
|
27
|
+
* Per-key error isolation. Receives the same key array but must return a
|
|
28
|
+
* `PromiseSettledResult<V>[]` of equal length. Each `load()` promise fulfills
|
|
29
|
+
* or rejects independently based on its corresponding settled result.
|
|
30
|
+
*/
|
|
31
|
+
resolveSettled: (keys: K[]) => Promise<PromiseSettledResult<V>[]>;
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* DataLoader-style request batcher. Coalesces individual `load()` calls made
|
|
35
|
+
* within the same scheduling window into a single `resolve()` call.
|
|
36
|
+
*
|
|
37
|
+
* Typical use: N+1 query elimination, request fan-out coalescing.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const userLoader = createBatcher({
|
|
42
|
+
* resolve: async (ids) => api.post<User[]>('/users/batch', { body: { ids } }),
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* // These 3 calls collapse into 1 POST /users/batch { ids: [1, 2, 3] }
|
|
46
|
+
* const [alice, bob, carol] = await Promise.all([
|
|
47
|
+
* userLoader.load(1),
|
|
48
|
+
* userLoader.load(2),
|
|
49
|
+
* userLoader.load(3),
|
|
50
|
+
* ]);
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function createBatcher<K, V>(opts: BatcherOptions<K, V>): {
|
|
54
|
+
readonly disposalSignal: AbortSignal;
|
|
55
|
+
/**
|
|
56
|
+
* Cancels all pending `load()` promises with a disposal error and stops
|
|
57
|
+
* any scheduled flushes. Safe to call multiple times.
|
|
58
|
+
*/
|
|
59
|
+
dispose(): void;
|
|
60
|
+
readonly disposed: boolean;
|
|
61
|
+
load(key: K): Promise<V>;
|
|
62
|
+
[Symbol.dispose](): void;
|
|
63
|
+
};
|
|
64
|
+
export type Batcher<K, V> = ReturnType<typeof createBatcher<K, V>>;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=batcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batcher.d.ts","sourceRoot":"","sources":["../src/batcher.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG;IACjB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,EAAE,CAAC,IAC3B,CAAC,WAAW,GAAG;IACb;;;OAGG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,KAAK,CAAC;CACxB,CAAC,GACF,CAAC,WAAW,GAAG;IACb,OAAO,CAAC,EAAE,KAAK,CAAC;IAChB;;;;OAIG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;CACnE,CAAC,CAAC;AAQP;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;6BA6FpC,WAAW;IAIjC;;;OAGG;eACQ,IAAI;uBAkBC,OAAO;cAIb,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;wBAsBJ,IAAI;EAI3B;AAED,MAAM,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC"}
|
package/dist/batcher.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{CourierBatcherError as e,CourierDisposedError as t}from"./errors.js";function n(n){let{maxSize:r=25,resolve:i,resolveSettled:a,window:o=0}=n,s=[],c=new AbortController,l=!1,u=null,d=!1;function f(){if(s.length===0)return;let t=s.splice(0,r);s.length>0&&p();let n=t.map(e=>e.key);try{a?a(n).then(n=>{if(n.length!==t.length){let r=new e(`Batcher: resolveSettled() returned ${n.length} results for ${t.length} keys`);for(let e of t)e.reject(r);return}t.forEach((e,t)=>{let r=n[t];r.status===`fulfilled`?e.resolve(r.value):e.reject(r.reason)})}).catch(e=>{for(let n of t)n.reject(e)}):i(n).then(n=>{if(n.length!==t.length){let r=new e(`Batcher: resolve() returned ${n.length} results for ${t.length} keys`);for(let e of t)e.reject(r);return}t.forEach((e,t)=>e.resolve(n[t]))}).catch(e=>{for(let n of t)n.reject(e)})}catch(e){for(let n of t)n.reject(e)}}function p(){l||(l=!0,o===0?queueMicrotask(()=>{l=!1,f()}):u=setTimeout(()=>{u=null,l=!1,f()},o))}return{get disposalSignal(){return c.signal},dispose(){if(d)return;d=!0,u!==null&&(clearTimeout(u),u=null);let e=new t(`Batcher`);for(let t of s.splice(0))t.reject(e);l=!1,c.abort()},get disposed(){return d},load(e){return d?Promise.reject(new t(`Batcher`)):new Promise((t,n)=>{s.push({key:e,reject:n,resolve:t}),s.length>=r?(u!==null&&(clearTimeout(u),u=null),l=!1,f()):p()})},[Symbol.dispose](){this.dispose()}}}export{n as createBatcher};
|
|
2
|
+
//# sourceMappingURL=batcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batcher.js","names":[],"sources":["../src/batcher.ts"],"sourcesContent":["import { CourierBatcherError, CourierDisposedError } from './errors';\n\ntype BatcherBase = {\n /** Maximum batch size before a flush is forced. Defaults to `25`. */\n maxSize?: number;\n /**\n * Scheduling window in ms. Defaults to `0` (next microtask), which coalesces\n * all `load()` calls within the same synchronous block into one batch.\n * Set a positive value (e.g. `16`) to coalesce across async boundaries.\n */\n window?: number;\n};\n\n/**\n * Batcher options. Exactly one of `resolve` or `resolveSettled` must be provided — they are mutually exclusive.\n *\n * - **`resolve`**: execute a batch and return results in the same order. All succeed or all fail.\n * - **`resolveSettled`**: per-key error isolation — each `load()` fulfills or rejects independently.\n */\nexport type BatcherOptions<K, V> =\n | (BatcherBase & {\n /**\n * Execute a batch of keys and return results in the same order.\n * The returned array must have the same length as `keys`.\n */\n resolve: (keys: K[]) => Promise<V[]>;\n resolveSettled?: never;\n })\n | (BatcherBase & {\n resolve?: never;\n /**\n * Per-key error isolation. Receives the same key array but must return a\n * `PromiseSettledResult<V>[]` of equal length. Each `load()` promise fulfills\n * or rejects independently based on its corresponding settled result.\n */\n resolveSettled: (keys: K[]) => Promise<PromiseSettledResult<V>[]>;\n });\n\ntype PendingItem<K, V> = {\n key: K;\n reject: (reason: unknown) => void;\n resolve: (value: V) => void;\n};\n\n/**\n * DataLoader-style request batcher. Coalesces individual `load()` calls made\n * within the same scheduling window into a single `resolve()` call.\n *\n * Typical use: N+1 query elimination, request fan-out coalescing.\n *\n * @example\n * ```ts\n * const userLoader = createBatcher({\n * resolve: async (ids) => api.post<User[]>('/users/batch', { body: { ids } }),\n * });\n *\n * // These 3 calls collapse into 1 POST /users/batch { ids: [1, 2, 3] }\n * const [alice, bob, carol] = await Promise.all([\n * userLoader.load(1),\n * userLoader.load(2),\n * userLoader.load(3),\n * ]);\n * ```\n */\nexport function createBatcher<K, V>(opts: BatcherOptions<K, V>) {\n const { maxSize = 25, resolve, resolveSettled, window: windowMs = 0 } = opts;\n\n const queue: PendingItem<K, V>[] = [];\n const controller = new AbortController();\n let scheduled = false;\n let pendingTimer: ReturnType<typeof setTimeout> | null = null;\n let disposed = false;\n\n function flush(): void {\n if (queue.length === 0) return;\n\n const batch = queue.splice(0, maxSize);\n\n // If more items remain after taking maxSize, schedule another flush immediately.\n if (queue.length > 0) schedule();\n\n const keys = batch.map((item) => item.key);\n\n // resolve()/resolveSettled() are called directly (not just chained with .then/.catch) so a\n // consumer function that throws synchronously — rather than returning a rejected promise —\n // still rejects every pending load() instead of leaving them hanging forever.\n try {\n if (resolveSettled) {\n void resolveSettled(keys)\n .then((results) => {\n if (results.length !== batch.length) {\n const err = new CourierBatcherError(\n `Batcher: resolveSettled() returned ${results.length} results for ${batch.length} keys`,\n );\n\n for (const item of batch) item.reject(err);\n\n return;\n }\n\n batch.forEach((item, i) => {\n const r = results[i];\n\n if (r.status === 'fulfilled') {\n item.resolve(r.value);\n } else {\n item.reject(r.reason);\n }\n });\n })\n .catch((err: unknown) => {\n for (const item of batch) item.reject(err);\n });\n } else {\n void resolve(keys)\n .then((results) => {\n if (results.length !== batch.length) {\n const err = new CourierBatcherError(\n `Batcher: resolve() returned ${results.length} results for ${batch.length} keys`,\n );\n\n for (const item of batch) item.reject(err);\n\n return;\n }\n\n batch.forEach((item, i) => item.resolve(results[i]));\n })\n .catch((err: unknown) => {\n for (const item of batch) item.reject(err);\n });\n }\n } catch (err) {\n for (const item of batch) item.reject(err);\n }\n }\n\n function schedule(): void {\n if (scheduled) return;\n\n scheduled = true;\n\n if (windowMs === 0) {\n queueMicrotask(() => {\n scheduled = false;\n flush();\n });\n } else {\n pendingTimer = setTimeout(() => {\n pendingTimer = null;\n scheduled = false;\n flush();\n }, windowMs);\n }\n }\n\n return {\n get disposalSignal(): AbortSignal {\n return controller.signal;\n },\n\n /**\n * Cancels all pending `load()` promises with a disposal error and stops\n * any scheduled flushes. Safe to call multiple times.\n */\n dispose(): void {\n if (disposed) return;\n\n disposed = true;\n\n if (pendingTimer !== null) {\n clearTimeout(pendingTimer);\n pendingTimer = null;\n }\n\n const err = new CourierDisposedError('Batcher');\n\n for (const item of queue.splice(0)) item.reject(err);\n\n scheduled = false;\n controller.abort();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n load(key: K): Promise<V> {\n if (disposed) return Promise.reject(new CourierDisposedError('Batcher'));\n\n return new Promise<V>((res, rej) => {\n queue.push({ key, reject: rej, resolve: res });\n\n if (queue.length >= maxSize) {\n // Force-flush immediately when the batch is full.\n // Cancel any pending timer first so it cannot fire a second flush.\n if (pendingTimer !== null) {\n clearTimeout(pendingTimer);\n pendingTimer = null;\n }\n\n scheduled = false;\n flush();\n } else {\n schedule();\n }\n });\n },\n\n [Symbol.dispose](): void {\n this.dispose();\n },\n };\n}\n\nexport type Batcher<K, V> = ReturnType<typeof createBatcher<K, V>>;\n"],"mappings":"4EAgEA,SAAgB,EAAoB,EAA4B,CAC9D,GAAM,CAAE,UAAU,GAAI,UAAS,iBAAgB,OAAQ,EAAW,GAAM,EAElE,EAA6B,CAAC,EAC9B,EAAa,IAAI,gBACnB,EAAY,GACZ,EAAqD,KACrD,EAAW,GAEf,SAAS,GAAc,CACrB,GAAI,EAAM,SAAW,EAAG,OAExB,IAAM,EAAQ,EAAM,OAAO,EAAG,CAAO,EAGjC,EAAM,OAAS,GAAG,EAAS,EAE/B,IAAM,EAAO,EAAM,IAAK,GAAS,EAAK,GAAG,EAKzC,GAAI,CACE,EACF,EAAoB,CAAI,CAAC,CACtB,KAAM,GAAY,CACjB,GAAI,EAAQ,SAAW,EAAM,OAAQ,CACnC,IAAM,EAAM,IAAI,EACd,sCAAsC,EAAQ,OAAO,eAAe,EAAM,OAAO,MACnF,EAEA,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,EAEzC,MACF,CAEA,EAAM,SAAS,EAAM,IAAM,CACzB,IAAM,EAAI,EAAQ,GAEd,EAAE,SAAW,YACf,EAAK,QAAQ,EAAE,KAAK,EAEpB,EAAK,OAAO,EAAE,MAAM,CAExB,CAAC,CACH,CAAC,CAAC,CACD,MAAO,GAAiB,CACvB,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CAAC,EAEH,EAAa,CAAI,CAAC,CACf,KAAM,GAAY,CACjB,GAAI,EAAQ,SAAW,EAAM,OAAQ,CACnC,IAAM,EAAM,IAAI,EACd,+BAA+B,EAAQ,OAAO,eAAe,EAAM,OAAO,MAC5E,EAEA,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,EAEzC,MACF,CAEA,EAAM,SAAS,EAAM,IAAM,EAAK,QAAQ,EAAQ,EAAE,CAAC,CACrD,CAAC,CAAC,CACD,MAAO,GAAiB,CACvB,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CAAC,CAEP,OAAS,EAAK,CACZ,IAAK,IAAM,KAAQ,EAAO,EAAK,OAAO,CAAG,CAC3C,CACF,CAEA,SAAS,GAAiB,CACpB,IAEJ,EAAY,GAER,IAAa,EACf,mBAAqB,CACnB,EAAY,GACZ,EAAM,CACR,CAAC,EAED,EAAe,eAAiB,CAC9B,EAAe,KACf,EAAY,GACZ,EAAM,CACR,EAAG,CAAQ,EAEf,CAEA,MAAO,CACL,IAAI,gBAA8B,CAChC,OAAO,EAAW,MACpB,EAMA,SAAgB,CACd,GAAI,EAAU,OAEd,EAAW,GAEP,IAAiB,OACnB,aAAa,CAAY,EACzB,EAAe,MAGjB,IAAM,EAAM,IAAI,EAAqB,SAAS,EAE9C,IAAK,IAAM,KAAQ,EAAM,OAAO,CAAC,EAAG,EAAK,OAAO,CAAG,EAEnD,EAAY,GACZ,EAAW,MAAM,CACnB,EAEA,IAAI,UAAoB,CACtB,OAAO,CACT,EAEA,KAAK,EAAoB,CAGvB,OAFI,EAAiB,QAAQ,OAAO,IAAI,EAAqB,SAAS,CAAC,EAEhE,IAAI,SAAY,EAAK,IAAQ,CAClC,EAAM,KAAK,CAAE,MAAK,OAAQ,EAAK,QAAS,CAAI,CAAC,EAEzC,EAAM,QAAU,GAGd,IAAiB,OACnB,aAAa,CAAY,EACzB,EAAe,MAGjB,EAAY,GACZ,EAAM,GAEN,EAAS,CAEb,CAAC,CACH,EAEA,CAAC,OAAO,UAAiB,CACvB,KAAK,QAAQ,CACf,CACF,CACF"}
|
package/dist/courier.cjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/arsenal");var t=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},n=class e extends t{url;method;status;data;headers;constructor(e){super(e.message),this.url=e.url,this.method=e.method,this.status=e.status,this.data=e.data,this.headers=e.headers}static fromResponse(t,n,r,i){return new e({data:n,headers:t.headers,message:t.statusText||`HTTP ${t.status}`,method:r,status:t.status,url:i})}static is(t,n){return t instanceof e&&(n===void 0||t.status===n)}},r=class extends t{url;method;constructor(e){super(e.message,{cause:e.cause}),this.url=e.url,this.method=e.method}},i=class extends t{url;method;constructor(e){super(e.message,{cause:e.cause}),this.url=e.url,this.method=e.method}},a=class extends t{url;method;constructor(e){super(e.message,{cause:e.cause}),this.url=e.url,this.method=e.method}},o=class extends t{data;constructor(e,t){super(e instanceof Error?e.message:String(e),{cause:e}),this.data=t}},s=class extends t{},c=class extends t{constructor(e){super(`[courier] ${e} disposed`)}},l=class extends t{};function u(e,t,n,o){let s=e instanceof Error?e.message:String(e),c=o?.reason;return c instanceof Error&&c.name===`TimeoutError`?new i({cause:e,message:s,method:t,url:n}):e instanceof DOMException?e.name===`TimeoutError`?new i({cause:e,message:s,method:t,url:n}):new a({cause:e,message:s,method:t,url:n}):e instanceof Error&&e.name===`AbortError`||o?.aborted?new a({cause:e,message:s,method:t,url:n}):new r({cause:e,message:s,method:t,url:n})}async function d(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()}function f(e,t,n,r,i){return n!==void 0&&!p(n)?{...i,body:JSON.stringify(n),headers:{"content-type":`application/json`,...t},method:e.toUpperCase(),signal:r}:{...i,...n!==void 0&&{body:n},headers:t,method:e.toUpperCase(),signal:r}}function p(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}function m(e,t,n){return{headers:t,init:n,url:e,withHeaders(r){let i={...t};for(let[e,t]of Object.entries(r))i[e.toLowerCase()]=t;return m(e,i,n)}}}function h(...e){let t=e.filter(e=>e!=null);if(t.length!==0)return t.length===1?t[0]:AbortSignal.any(t)}var g=3e4;function _(e){if((e<=0||!Number.isFinite(e))&&e!==1/0)throw new t(`[courier] timeout must be a positive number or Infinity`)}function v(e,t){return e===1/0?t??void 0:h(AbortSignal.timeout(e),t)}function y(e={}){let{baseUrl:t=``,fetch:n=globalThis.fetch,headers:r={},timeout:i=g}=e;_(i);let a=Object.fromEntries(Object.entries(r).map(([e,t])=>[e.toLowerCase(),t])),o=new Set,s=new AbortController,c=[],l=null,u=!1;function d(){if(l)return l;let e=e=>n(e.url,{...e.init,headers:e.headers});return l=c.length===0?e:c.reduceRight((e,t)=>n=>t(n,e),e),l}function f(e){return c.push(e),l=null,()=>{let t=c.indexOf(e);t!==-1&&(c.splice(t,1),l=null)}}function p(e){for(let[t,n]of Object.entries(e)){let e=t.toLowerCase();n===void 0?delete a[e]:a[e]=n}}function h(){return{...a}}function v(e,t){let n=e?Object.fromEntries(Object.entries(e).map(([e,t])=>[e.toLowerCase(),t])):void 0;return{...a,...n,...t}}function y(e){return o.add(e),()=>o.delete(e)}function b(e){return d()(m(e.url,e.headers,e.init))}function x(){for(let e of[...o])e.abort();o.clear()}function S(){if(!u){u=!0,s.abort();for(let e of o)e.abort();o.clear(),c.length=0,l=null}}return{baseUrl:t,cancelAll:x,dispatch:b,get disposalSignal(){return s.signal},dispose:S,get disposed(){return u},getHeaders:h,getTimeout(){return i},headers:p,mergeHeaders:v,track:y,use:f}}function b(e,t,n,r){let i=e.replace(/\/+$/,``),a=t.replace(/^\/+/,``);a=a.replace(/\{([^}]+)\}/g,(e,r)=>{let i=n?.[r];if(i==null)throw new l(`unresolved path param {${r}} in '${t}'`);return encodeURIComponent(String(i))});let o=i&&a?`${i}/${a}`:i||a;if(!r)return o;let s=new URLSearchParams;for(let[e,t]of Object.entries(r))if(t!==void 0){if(Array.isArray(t)){for(let n of t)n!==void 0&&s.append(e,n===null?``:String(n));continue}s.append(e,t===null?``:String(t))}let c=s.toString();return c?`${o}${o.includes(`?`)?`&`:`?`}${c}`:o}var x=new Set([`GET`,`HEAD`,`OPTIONS`]);function S(t,n,r,i){if(x.has(t))return`${t}:${n}:${r}`;if(i!==void 0)return`${t}:${n}:${(0,e.hash)(i)}`}function C(e){let{transport:t,...r}=e??{},i=t??y(r),a=!t,s=new Map;async function l(e,t,r,a,s,c){let l=t.signal,f;try{f=await i.dispatch({headers:e,init:t,url:r})}catch(e){throw u(e,a,r,l)}if(!f.ok){let e;try{e=await d(f,s??`auto`)}catch{e=await f.text().catch(()=>``)}throw n.fromResponse(f,e,a,r)}let p;try{p=await d(f,s??`auto`)}catch(e){throw u(e,a,r,l)}if(c)try{return c.parse(p)}catch(e){throw new o(e,p)}return p}async function p(e,t,n={}){if(i.disposed)throw new c(`ApiClient`);let r=e.toUpperCase(),a;try{a=b(i.baseUrl,t,n.params,n.query)}catch(e){throw u(e,r,t)}let{body:o,dedupe:d=!0,dedupeKey:p,fetchInit:m,headers:g,responseType:y,schema:x,signal:C,timeout:w}=n;w!==void 0&&_(w);let T=i.mergeHeaders(g),E=d?S(r,a,y??`auto`,p):void 0;if(E){let e=s.get(E);if(e)return e}let D=new AbortController,O=i.track(D),k=h(C,D.signal)??D.signal,{headers:A,...j}=f(r,T,o,v(w??i.getTimeout(),k),m??{}),M=l(A,j,a,r,y,x);E&&s.set(E,M);try{return await M}finally{E&&s.delete(E),O()}}return{cancelAll(){i.cancelAll(),s.clear()},delete:(e,t)=>p(`DELETE`,e,t),get disposalSignal(){return i.disposalSignal},dispose(){s.clear(),a&&i.dispose()},get disposed(){return i.disposed},get:(e,t)=>p(`GET`,e,t),getHeaders:i.getHeaders,headers:i.headers,patch:(e,t)=>p(`PATCH`,e,t),post:(e,t)=>p(`POST`,e,t),put:(e,t)=>p(`PUT`,e,t),request:p,[Symbol.dispose](){this.dispose()},use:i.use}}function w(e){let{maxSize:t=25,resolve:n,resolveSettled:r,window:i=0}=e,a=[],o=new AbortController,l=!1,u=null,d=!1;function f(){if(a.length===0)return;let e=a.splice(0,t);a.length>0&&p();let i=e.map(e=>e.key);try{r?r(i).then(t=>{if(t.length!==e.length){let n=new s(`Batcher: resolveSettled() returned ${t.length} results for ${e.length} keys`);for(let t of e)t.reject(n);return}e.forEach((e,n)=>{let r=t[n];r.status===`fulfilled`?e.resolve(r.value):e.reject(r.reason)})}).catch(t=>{for(let n of e)n.reject(t)}):n(i).then(t=>{if(t.length!==e.length){let n=new s(`Batcher: resolve() returned ${t.length} results for ${e.length} keys`);for(let t of e)t.reject(n);return}e.forEach((e,n)=>e.resolve(t[n]))}).catch(t=>{for(let n of e)n.reject(t)})}catch(t){for(let n of e)n.reject(t)}}function p(){l||(l=!0,i===0?queueMicrotask(()=>{l=!1,f()}):u=setTimeout(()=>{u=null,l=!1,f()},i))}return{get disposalSignal(){return o.signal},dispose(){if(d)return;d=!0,u!==null&&(clearTimeout(u),u=null);let e=new c(`Batcher`);for(let t of a.splice(0))t.reject(e);l=!1,o.abort()},get disposed(){return d},load(e){return d?Promise.reject(new c(`Batcher`)):new Promise((n,r)=>{a.push({key:e,reject:r,resolve:n}),a.length>=t?(u!==null&&(clearTimeout(u),u=null),l=!1,f()):p()})},[Symbol.dispose](){this.dispose()}}}function T(t){return Math.random()*(0,e.backoff)(t)}function E(e,t){if(typeof t==`function`){let n=t(e);return Number.isFinite(n)?Math.max(0,n):0}return typeof t==`number`?Number.isFinite(t)?Math.max(0,t):0:T(e)}var D=Object.freeze({data:void 0,error:null,isFetching:!1,isLoading:!1,status:`loading`,updatedAt:void 0});function O(t,n){let r=D,i=0,a=null,o=new Set,s=!1,l=null;function u(){o.forEach(e=>e())}async function d(e){try{await e?.()}catch(e){if(n?.onCallbackError)try{n.onCallbackError(e instanceof Error?e:Error(String(e)))}catch{}}}return{async cancel(){let e=a;e&&(e.controller.abort(),await e.promise.catch(()=>{}))},dispose(){s||(s=!0,a?.controller.abort(),a=null,o.clear())},get disposed(){return s},async mutate(o,l){if(s)throw new c(`Mutation`);let f=new AbortController,p=l?.signal?h(l.signal,f.signal):f.signal,m=++i;r={data:void 0,error:null,isFetching:!0,isLoading:!0,status:`loading`,updatedAt:void 0},u();let g={controller:f,promise:null};a=g;let _=(async()=>{try{let a=await(0,e.retry)(()=>t(o,p),{delay:e=>E(e,n?.delay),shouldRetry:n?.shouldRetry,signal:p,times:n?.times??1});return m===i&&(r={data:a,error:null,isFetching:!1,isLoading:!1,status:`success`,updatedAt:Date.now()},u()),await d(()=>n?.onSuccess?.(a,o)),await d(()=>n?.onFinally?.(o)),await d(()=>n?.onSettled?.({data:a,status:`success`,variables:o})),a}catch(t){let a=t instanceof Error?t:Error(String(t)),s=p.aborted||(0,e.isAbortError)(a);throw m===i&&(r=s?D:{data:void 0,error:a,isFetching:!1,isLoading:!1,status:`error`,updatedAt:Date.now()},u()),s||await d(()=>n?.onError?.(a,o)),await d(()=>n?.onFinally?.(o)),await d(()=>n?.onSettled?.(s?{status:`aborted`,variables:o}:{error:a,status:`error`,variables:o})),a}finally{a?.controller===f&&(a=null)}})();return g.promise=_,_},peek(){return r},reset(){r=D,u()},get store(){return l??={peek:()=>r,subscribe:e=>(o.add(e),()=>o.delete(e))}},subscribe(e){return o.add(e),()=>o.delete(e)},[Symbol.dispose](){this.dispose()}}}function k(e){return typeof e==`function`?e():e}function A(t){return(0,e.hash)(t)}function j(t){return(0,e.hash)(t)}function M(t,n){let r=(0,e.hash)(n),i=t.entries.get(r);return i||(i={data:void 0,error:null,hash:r,inflight:null,isFetching:!1,key:n,lastConfig:void 0,observers:new Set,segmentHashes:n.map(t=>(0,e.hash)(t)),status:`loading`,updatedAt:void 0},t.entries.set(r,i)),i}function N(e,t){let n=e.gcTimers.get(t);n&&(clearTimeout(n),e.gcTimers.delete(t))}function P(e,t,n){if(e.disposed||(N(e,t.hash),t.observers.size>0))return;if(n===0){e.entries.delete(t.hash);return}let r=setTimeout(()=>{let n=e.entries.get(t.hash);!n||n.observers.size>0||n.isFetching||(e.entries.delete(t.hash),e.gcTimers.delete(t.hash))},n);e.gcTimers.set(t.hash,r)}function F(e,t){t.inflight?.controller.abort(),N(e,t.hash),e.entries.delete(t.hash)}function I(e,t){return t.length>e.segmentHashes.length?!1:t.every((t,n)=>t===e.segmentHashes[n])}function L(e,t){t.isFetching=!0,t.status=`loading`,t.error=null,e.notify(t)}function R(e,t,n,r){t.data=n,t.error=null,t.status=`success`,t.updatedAt=Date.now(),t.isFetching=!1,t.inflight=null,P(e,t,r),e.notify(t)}function z(e,t,n,r){t.error=n,t.status=`error`,t.updatedAt=Date.now(),t.isFetching=!1,t.inflight=null,P(e,t,r),e.notify(t)}function B(e,t,n,r){if(t.data=n.data,t.error=n.error,t.status=n.status,t.updatedAt=n.updatedAt,t.isFetching=!1,t.inflight=null,t.observers.size===0&&t.status===`loading`&&t.data===void 0){F(e,t);return}P(e,t,r),e.notify(t)}function V(e,t){if(t.inflight?.controller.abort(),t.observers.size>0){t.inflight=null,t.isFetching=!1,t.status=`loading`,t.data=void 0,t.error=null,t.updatedAt=void 0,e.notify(t);return}F(e,t)}function H(t,n,r){if(n.inflight)return n.inflight.promise;let{delay:i,fn:a,gcTime:o,shouldRetry:s,times:c}=r,l=new AbortController,u={data:n.data,error:n.error,status:n.status,updatedAt:n.updatedAt},d=(async()=>{try{let r=await(0,e.retry)(()=>a({key:n.key,signal:l.signal}),{delay:e=>E(e,i),shouldRetry:s,signal:l.signal,times:c});return R(t,n,r,o),r}catch(r){let i=r instanceof Error?r:Error(String(r));throw l.signal.aborted||(0,e.isAbortError)(i)?B(t,n,u,o):z(t,n,i,o),i}})();return n.inflight={controller:l,promise:d},L(t,n),d}function U(e,t){return{delay:t.delay??e.delayDefault,fn:t.fn,gcTime:t.gcTime??e.gcTimeDefault,shouldRetry:t.shouldRetry??e.shouldRetryDefault,staleTime:t.staleTime??e.staleTimeDefault,times:t.times??e.timesDefault}}function W(e,t,n,r){return{delayDefault:e.delay,disposed:!1,entries:t,gcTimeDefault:e.gcTime??3e5,gcTimers:n,notify:r,shouldRetryDefault:e.shouldRetry,staleTimeDefault:e.staleTime??0,timesDefault:e.times??1}}var ee=Object.freeze({data:void 0,error:null,isFetching:!1,isLoading:!0,status:`loading`,updatedAt:void 0});function G(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 K(e,t){let n=G(e);if(n.status===`loading`){let e=k(t.placeholderData);return{...n,data:e}}let r=t.select?t.select(n.data):n.data;return{...n,data:r}}function q(e){for(let t of e.observers){let n=t,r=K(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 J(e,t,n){let r={listener:()=>{},...n};return{peek(){let n=e.entries.get(A(t));return n?K(n,r):ee},subscribe(i){let a=M(e,t);N(e,a.hash);let o={...n,listener:i,previous:K(a,r)};return a.observers.add(o),()=>{a.observers.delete(o),a.observers.size===0&&P(e,a,a.lastConfig?.gcTime??e.gcTimeDefault)}}}}function Y(e){let t=!1,n=new AbortController,r=new Map,i=new Map,a=W({delay:e?.delay,gcTime:e?.gcTime,shouldRetry:e?.shouldRetry,staleTime:e?.staleTime,times:e?.times},r,i,q);async function o(e){if(t)throw new c(`QueryClient`);let{enabled:n=!0,fn:i,gcTime:o,initialData:s,key:l,staleTime:u,...d}=e;if(!n){let e=A(l);return r.get(e)?.data??void 0}let f=M(a,l),p=U(a,{fn:i,gcTime:o,staleTime:u,...d});if(s!==void 0&&f.status===`loading`&&f.data===void 0){let e=k(s);e!==void 0&&(f.data=e,f.error=null,f.status=`success`,f.updatedAt=Date.now())}return f.status===`success`&&f.updatedAt!==void 0&&Date.now()-f.updatedAt<p.staleTime&&!f.isFetching?(f.lastConfig=p,f.data):(f.lastConfig=p,f.inflight?f.inflight.promise:H(a,f,p))}function s(e){if(!e.lastConfig){V(a,e);return}e.isFetching||H(a,e,e.lastConfig).catch(()=>{})}function l(e){let t=e.map(e=>j(e));for(let e of[...r.values()])if(I(e,t)){if(e.observers.size>0){s(e);continue}V(a,e)}}function u(e,t,n){let r=M(a,e),i=n?.gcTime??r.lastConfig?.gcTime??a.gcTimeDefault;r.data=typeof t==`function`?t(r.data):t,r.error=null,r.status=`success`,r.updatedAt=n?.updatedAt??Date.now(),r.isFetching=!1,r.inflight=null,q(r),P(a,r,i)}function d(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 f(e){let{fetch:t=!0,key:n,placeholderData:r,select:i}=e,s=J(a,n,{placeholderData:r,select:i});return t&&e.fn&&o({...e}).catch(()=>{}),s}return{cancel(e){let t=r.get(A(e));t?.inflight&&t.inflight.controller.abort()},cancelAll(){for(let e of r.values())e.inflight?.controller.abort()},clear(){for(let e of[...r.values()])V(a,e)},get disposalSignal(){return n.signal},dispose(){if(!t){t=!0,a.disposed=!0,n.abort();for(let[,e]of r)e.observers.clear(),e.inflight?.controller.abort();for(let[,e]of i)clearTimeout(e);i.clear(),r.clear()}},get disposed(){return t},fetch:o,fetchMany(e){return Promise.all(e.map(e=>o(e)))},get(e){let t=r.get(A(e));return t?t.data:void 0},getState(e){let t=r.get(A(e));return t?G(t):null},invalidate:l,keys(){return[...r.values()].map(e=>e.key)},observe:f,observeMany(e){let t=e.map(e=>J(a,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[...r.values()])d(e)&&H(a,e,e.lastConfig).catch(()=>{})},remove(e){let t=r.get(A(e));t&&V(a,t)},set:u,get size(){return r.size},[Symbol.dispose](){this.dispose()}}}function X(e){try{return JSON.parse(e)}catch{return e}}async function Z(e,t,r,i,a){let o=i.method.toUpperCase(),s=b(e.baseUrl,t,i.params,i.query),c=h(i.extSignal,r.signal)??r.signal,d=v(i.timeout??1/0,c),{headers:p,...m}=f(o,e.mergeHeaders(i.headers,a),i.body,d,i.fetchInit??{}),g;try{g=await e.dispatch({headers:p,init:m,url:s})}catch(e){throw u(e,o,s,d??c)}if(!g.ok){let e=await g.text().catch(()=>``);throw n.fromResponse(g,e,o,s)}if(!g.body)throw new l(`Response has no body`);return g}function Q(t){let{transport:n,...r}=t??{},i=!n,a=n??y(r);function o(t,n={}){if(a.disposed)throw new c(`StreamClient`);let{body:r,fetchInit:i,headers:o,method:s=r===void 0?`GET`:`POST`,onError:l,params:u,query:d,reconnect:f=!1,signal:p,timeout:m}=n;m!==void 0&&_(m);let h=new Map,g=new AbortController,v=a.track(g),y=!1,b=`connecting`,x=``,S=f===!0?{}:f===!1?{times:0}:f,C=S.times??(f?5:0),w=S.delay??T;function E(e,t){let n=h.get(e);if(!n?.size)return;let r=X(t);for(let e of n)e(r)}async function D(){let e=(await Z(a,t,g,{body:r,extSignal:p,fetchInit:i,headers:o,method:s,params:u,query:d,timeout:m},{accept:`text/event-stream`,"cache-control":`no-cache`,...x?{"last-event-id":x}:{}})).body.getReader(),n=new TextDecoder,c=``,l=`message`,f=``,h=``;try{for(;;){let{done:t,value:r}=await e.read();if(t)break;c+=n.decode(r,{stream:!0}).replace(/\r\n/g,`
|
|
2
|
+
`).replace(/\r/g,`
|
|
3
|
+
`);let i;for(;(i=c.indexOf(`
|
|
4
|
+
`))!==-1;){let e=c.slice(0,i);if(c=c.slice(i+1),e===``)f!==``&&(h&&(x=h),E(l,f.replace(/\n$/,``))),l=`message`,f=``,h=``;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`)l=r;else if(n===`data`)f+=(f?`
|
|
5
|
+
`:``)+r;else if(n===`id`&&!r.includes(`\0`))h=r;else if(n===`retry`){let e=parseInt(r,10);Number.isNaN(e)||(w=e)}}}}}finally{e.releaseLock()}}async function O(){let t=0;try{for(;!y&&!a.disposed&&!g.signal.aborted;){let n;b=`connecting`;try{b=`open`,await D()}catch(e){if(y||a.disposed||g.signal.aborted)break;n=e instanceof Error?e:Error(String(e))}if(y||a.disposed||g.signal.aborted)break;if(C===0){n&&l?.(n);break}if(t>=C){l?.(n??Error(`[courier] SSE reconnect budget exhausted`));break}b=`reconnecting`;let r=typeof w==`function`?w(t):w;t++,await(0,e.sleep)(r,g.signal).catch(()=>{})}}finally{b=`closed`,v()}}return O().catch(e=>{!y&&!a.disposed&&l?.(e instanceof Error?e:Error(String(e)))}),{get closed(){return y},dispose(){y=!0,g.abort(),h.clear()},on(e,t){if(y)return()=>{};let n=h.get(e);return n||(n=new Set,h.set(e,n)),n.add(t),()=>n.delete(t)},get status(){return b},[Symbol.dispose](){this.dispose()}}}async function*s(t,n={}){if(a.disposed)throw new c(`StreamClient`);let{body:r,fetchInit:i,headers:o,method:s=r===void 0?`GET`:`POST`,onError:u,params:d,parse:f=`text`,query:p,reconnect:m=!1,signal:h,timeout:g}=n;g!==void 0&&_(g);let v=m===!0?{}:m===!1?{times:0}:m,y=v.times??(m?5:0),b=v.delay??T,x=new AbortController,S=a.track(x),C=0,w=!1;try{for(;!a.disposed&&!x.signal.aborted;){let n,c=!1;try{let e=(await Z(a,t,x,{body:r,extSignal:h,fetchInit:i,headers:o,method:s,params:d,query:p,timeout:g})).body.getReader(),n=new TextDecoder;try{if(f===`ndjson`){let t=``;for(;;){let{done:r,value:i}=await e.read();if(r){let e=t.trim();if(e)try{yield JSON.parse(e)}catch{throw new l(`NDJSON: failed to parse line: ${e.slice(0,200)}`)}c=!0;break}t+=n.decode(i,{stream:!0});let a;for(;(a=t.indexOf(`
|
|
6
|
+
`))!==-1;){let e=t.slice(0,a).trim();if(t=t.slice(a+1),e)try{yield JSON.parse(e)}catch{throw new l(`NDJSON: failed to parse line: ${e.slice(0,200)}`)}}}}else for(;;){let{done:t,value:r}=await e.read();if(t){c=!0;break}yield n.decode(r,{stream:!0})}}finally{e.releaseLock()}}catch(e){if(a.disposed||x.signal.aborted)break;n=e instanceof Error?e:Error(String(e))}if(a.disposed||x.signal.aborted)break;if(y===0){if(n)if(u)u(n);else throw n;else w=c;break}if(C>=y){let e=n??Error(`[courier] readable() reconnect budget exhausted`);if(u)u(e);else if(n)throw n;break}if(c&&!n){w=!0;break}let m=typeof b==`function`?b(C):b;C++,await(0,e.sleep)(m,x.signal).catch(()=>{})}}finally{w||x.abort(),S()}}return{cancelAll(){a.cancelAll()},get disposalSignal(){return a.disposalSignal},dispose(){i&&a.dispose()},get disposed(){return a.disposed},getHeaders:a.getHeaders,headers:a.headers,readable:s,sse:o,[Symbol.dispose](){this.dispose()},use:a.use}}function te(e){let{mutationDefaults:t,query:n,...r}=e??{},i=y(r),a=C({transport:i}),o=Q({transport:i}),s=(e,t)=>{let n=typeof e==`string`?e:e instanceof URL?e.toString():e.url,{signal:r,...a}=t??{},o=Object.fromEntries(new Headers({...i.getHeaders(),...t?.headers??{}}).entries());return i.dispatch({headers:o,init:{...a,signal:r},url:n})},c=Y({...n,fetch:s});return{api:a,cancelAll(){i.cancelAll(),c.cancelAll()},get disposalSignal(){return i.disposalSignal},dispose(){i.dispose(),c.dispose()},get disposed(){return i.disposed},headers:i.headers,mutation(e,n){let{invalidates:r,sets:i,...a}=n??{},o=async(e,t)=>{if(i){let n=i(e,t);for(let[e,t]of n)c.set(e,t)}if(r)for(let e of r)c.invalidate(e);await a.onSuccess?.(e,t)};return O(e,{...t,...a,onSuccess:o})},query:c,stream:o,[Symbol.dispose](){this.dispose()},use:i.use}}var ne=!globalThis.__COURIER_PROD__;function re(e){ne&&console.warn(`[@vielzeug/courier] ${e}`)}function ie(e,t){let n=[],r=typeof document<`u`,i=typeof window<`u`;if(!r&&!i&&re(`bindRefetch() called with no document/window in scope — no listeners attached`),r){let t=()=>{document.visibilityState===`visible`&&e.refetchStale()};document.addEventListener(`visibilitychange`,t),n.push(()=>document.removeEventListener(`visibilitychange`,t))}if(i){let t=()=>e.refetchStale();window.addEventListener(`online`,t),n.push(()=>window.removeEventListener(`online`,t))}let a=()=>{for(let e of n)e();n.length=0};return t?.signal?.addEventListener(`abort`,a,{once:!0}),a}function ae(e){return async(t,n)=>{let r=typeof e==`function`?await e():e;return n(t.withHeaders({authorization:`Bearer ${r}`}))}}function oe(t){let n=t?.header??`x-request-id`,r=t?.generate??e.uuid;return(e,t)=>t(e.withHeaders({[n]:r()}))}function $(e){let t=e?.logger??((e,t)=>{console.debug(e,t)});return async(e,n)=>{let r=performance.now(),i=(e.init.method??`GET`).toUpperCase();try{let a=await n(e),o=Math.round(performance.now()-r);return t(`${i} ${e.url} ${a.status} (${o}ms)`,{duration:o,method:i,status:a.status,url:e.url}),a}catch(n){let a=Math.round(performance.now()-r);throw t(`${i} ${e.url} ERR (${a}ms)`,{duration:a,method:i,status:0,url:e.url}),n}}}function se(t,n){let{keys:r,onError:i,prefix:a=`courier:`,storage:o}=n,s=t.keys(),c=typeof r==`function`?s.filter(r):r,l=[];function u(t){return`${a}${(0,e.hash)(t)}`}function d(e,t){if(t.status!==`success`)return;let n={data:t.data,updatedAt:t.updatedAt};try{let t=o.setItem(u(e),JSON.stringify(n));t instanceof Promise&&t.catch(t=>i?.(t,e))}catch(t){i?.(t,e)}}for(let e of c){let n=t.getState(e);n&&d(e,n);let r=t.observe({fetch:!1,key:e}),i=r.subscribe(()=>d(e,r.peek()));l.push(i)}return()=>{for(let e of l)e();l.length=0}}async function ce(t,n){let{keys:r,maxAge:i,onError:a,prefix:o=`courier:`,storage:s}=n,c=t.keys(),l=typeof r==`function`?c.filter(r):r;await Promise.all(l.map(async n=>{try{let r=await s.getItem(`${o}${(0,e.hash)(n)}`);if(!r)return;let c=JSON.parse(r);if(typeof c!=`object`||!c||!(`data`in c)||typeof c.updatedAt!=`number`){a?.(Error(`[courier] Malformed persisted entry`),n);return}let l=c;if(i!==void 0&&Date.now()-l.updatedAt>i)return;t.set(n,l.data,{updatedAt:l.updatedAt})}catch(e){a?.(e,n)}}))}function le(e){return{peek:()=>e.peek(),subscribe:t=>e.subscribe(t)}}exports.CourierAbortError=a,exports.CourierBatcherError=s,exports.CourierDisposedError=c,exports.CourierError=t,exports.CourierHttpError=n,exports.CourierNetworkError=r,exports.CourierParseError=l,exports.CourierSchemaValidationError=o,exports.CourierTimeoutError=i,exports.bindRefetch=ie,exports.createApi=C,exports.createBatcher=w,exports.createCourier=te,exports.createMutation=O,exports.createQuery=Y,exports.createStream=Q,exports.hydrateQueryCache=ce,exports.persistQueryCache=se,exports.toSyncStore=le,exports.withBearerAuth=ae,exports.withLogging=$,exports.withRequestId=oe;
|
|
7
|
+
//# sourceMappingURL=courier.cjs.map
|