@vielzeug/sourcerer 1.0.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -74
- 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 +6 -0
- package/dist/_dev.js.map +1 -0
- package/dist/asyncSource.cjs +1 -1
- package/dist/asyncSource.cjs.map +1 -1
- package/dist/asyncSource.d.ts +14 -58
- package/dist/asyncSource.d.ts.map +1 -1
- package/dist/asyncSource.js +28 -27
- package/dist/asyncSource.js.map +1 -1
- package/dist/cursorSource.cjs +1 -1
- package/dist/cursorSource.cjs.map +1 -1
- package/dist/cursorSource.d.ts +3 -3
- package/dist/cursorSource.d.ts.map +1 -1
- package/dist/cursorSource.js +77 -110
- package/dist/cursorSource.js.map +1 -1
- package/dist/devtools.cjs +2 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.ts +6 -0
- package/dist/devtools.d.ts.map +1 -0
- package/dist/devtools.js +8 -0
- package/dist/devtools.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -14
- package/dist/infiniteSource.cjs +1 -1
- package/dist/infiniteSource.cjs.map +1 -1
- package/dist/infiniteSource.d.ts +3 -3
- package/dist/infiniteSource.d.ts.map +1 -1
- package/dist/infiniteSource.js +72 -107
- package/dist/infiniteSource.js.map +1 -1
- package/dist/localSource.cjs +1 -1
- package/dist/localSource.cjs.map +1 -1
- package/dist/localSource.d.ts +2 -22
- package/dist/localSource.d.ts.map +1 -1
- package/dist/localSource.js +58 -114
- package/dist/localSource.js.map +1 -1
- package/dist/pageSource.cjs +2 -0
- package/dist/pageSource.cjs.map +1 -0
- package/dist/pageSource.d.ts +4 -0
- package/dist/pageSource.d.ts.map +1 -0
- package/dist/pageSource.js +96 -0
- package/dist/pageSource.js.map +1 -0
- package/dist/pagination.cjs +1 -1
- package/dist/pagination.cjs.map +1 -1
- package/dist/pagination.d.ts +5 -30
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +21 -27
- package/dist/pagination.js.map +1 -1
- package/dist/requestController.cjs +2 -0
- package/dist/requestController.cjs.map +1 -0
- package/dist/requestController.d.ts +13 -0
- package/dist/requestController.d.ts.map +1 -0
- package/dist/requestController.js +29 -0
- package/dist/requestController.js.map +1 -0
- package/dist/sourceStore.cjs +2 -0
- package/dist/sourceStore.cjs.map +1 -0
- package/dist/sourceStore.d.ts +10 -0
- package/dist/sourceStore.d.ts.map +1 -0
- package/dist/sourceStore.js +38 -0
- package/dist/sourceStore.js.map +1 -0
- package/dist/sourcerer.cjs +1 -1
- package/dist/sourcerer.cjs.map +1 -1
- package/dist/sourcerer.iife.js +1 -1
- package/dist/sourcerer.iife.js.map +1 -1
- package/dist/sourcerer.js +1 -1
- package/dist/sourcerer.js.map +1 -1
- package/dist/types.d.ts +128 -345
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -7
- package/dist/_utils.cjs +0 -2
- package/dist/_utils.cjs.map +0 -1
- package/dist/_utils.d.ts +0 -8
- package/dist/_utils.d.ts.map +0 -1
- package/dist/_utils.js +0 -7
- package/dist/_utils.js.map +0 -1
- package/dist/applyQuery.cjs +0 -2
- package/dist/applyQuery.cjs.map +0 -1
- package/dist/applyQuery.d.ts +0 -20
- package/dist/applyQuery.d.ts.map +0 -1
- package/dist/applyQuery.js +0 -8
- package/dist/applyQuery.js.map +0 -1
- package/dist/asyncSearch.cjs +0 -2
- package/dist/asyncSearch.cjs.map +0 -1
- package/dist/asyncSearch.d.ts +0 -13
- package/dist/asyncSearch.d.ts.map +0 -1
- package/dist/asyncSearch.js +0 -28
- package/dist/asyncSearch.js.map +0 -1
- package/dist/codecs.cjs +0 -2
- package/dist/codecs.cjs.map +0 -1
- package/dist/codecs.d.ts +0 -30
- package/dist/codecs.d.ts.map +0 -1
- package/dist/codecs.js +0 -36
- package/dist/codecs.js.map +0 -1
- package/dist/core.cjs +0 -2
- package/dist/core.cjs.map +0 -1
- package/dist/core.d.ts +0 -47
- package/dist/core.d.ts.map +0 -1
- package/dist/core.js +0 -59
- package/dist/core.js.map +0 -1
- package/dist/derive.cjs +0 -2
- package/dist/derive.cjs.map +0 -1
- package/dist/derive.d.ts +0 -28
- package/dist/derive.d.ts.map +0 -1
- package/dist/derive.js +0 -40
- package/dist/derive.js.map +0 -1
- package/dist/errors.cjs +0 -2
- package/dist/errors.cjs.map +0 -1
- package/dist/errors.d.ts +0 -62
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -29
- package/dist/errors.js.map +0 -1
- package/dist/fetchManager.cjs +0 -2
- package/dist/fetchManager.cjs.map +0 -1
- package/dist/fetchManager.d.ts +0 -27
- package/dist/fetchManager.d.ts.map +0 -1
- package/dist/fetchManager.js +0 -41
- package/dist/fetchManager.js.map +0 -1
- package/dist/merge.cjs +0 -2
- package/dist/merge.cjs.map +0 -1
- package/dist/merge.d.ts +0 -30
- package/dist/merge.d.ts.map +0 -1
- package/dist/merge.js +0 -34
- package/dist/merge.js.map +0 -1
- package/dist/prefetch.cjs +0 -2
- package/dist/prefetch.cjs.map +0 -1
- package/dist/prefetch.d.ts +0 -32
- package/dist/prefetch.d.ts.map +0 -1
- package/dist/prefetch.js +0 -38
- package/dist/prefetch.js.map +0 -1
- package/dist/presets.cjs +0 -2
- package/dist/presets.cjs.map +0 -1
- package/dist/presets.d.ts +0 -17
- package/dist/presets.d.ts.map +0 -1
- package/dist/presets.js +0 -43
- package/dist/presets.js.map +0 -1
- package/dist/remoteSource.cjs +0 -2
- package/dist/remoteSource.cjs.map +0 -1
- package/dist/remoteSource.d.ts +0 -4
- package/dist/remoteSource.d.ts.map +0 -1
- package/dist/remoteSource.js +0 -165
- package/dist/remoteSource.js.map +0 -1
- package/dist/state.cjs +0 -2
- package/dist/state.cjs.map +0 -1
- package/dist/state.d.ts +0 -23
- package/dist/state.d.ts.map +0 -1
- package/dist/state.js +0 -14
- package/dist/state.js.map +0 -1
package/dist/applyQuery.cjs
DELETED
package/dist/applyQuery.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"applyQuery.cjs","names":[],"sources":["../src/applyQuery.ts"],"sourcesContent":["import type { SourceQuery } from './types';\n\n/**\n * Generic query applicator — delegates to `source.patch(changes)`.\n * Use this with `decodeQuery()` output to restore URL state onto any source:\n *\n * @example\n * ```ts\n * const q = decodeQuery(new URL(location.href).searchParams, { defaultLimit: 20 });\n * await applyQuery(source, q);\n * ```\n *\n * ⚠️ `CursorSource` and `InfiniteSource` have no page-number concept (keyset/append navigation) —\n * their `patch()` only reads `limit`/`search` and silently ignores a `page` field. This compiles\n * (both accept `Partial<SourceQuery>`-shaped input) but a `page` value from `decodeQuery()` is a\n * no-op on those two source types.\n */\nexport function applyQuery<T extends { patch(changes: Partial<SourceQuery>): Promise<void> }>(\n source: T,\n changes: Partial<SourceQuery>,\n): Promise<void> {\n return source.patch(changes);\n}\n"],"mappings":"AAiBA,SAAgB,EACd,EACA,EACe,CACf,OAAO,EAAO,MAAM,CAAO,CAC7B"}
|
package/dist/applyQuery.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { SourceQuery } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Generic query applicator — delegates to `source.patch(changes)`.
|
|
4
|
-
* Use this with `decodeQuery()` output to restore URL state onto any source:
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```ts
|
|
8
|
-
* const q = decodeQuery(new URL(location.href).searchParams, { defaultLimit: 20 });
|
|
9
|
-
* await applyQuery(source, q);
|
|
10
|
-
* ```
|
|
11
|
-
*
|
|
12
|
-
* ⚠️ `CursorSource` and `InfiniteSource` have no page-number concept (keyset/append navigation) —
|
|
13
|
-
* their `patch()` only reads `limit`/`search` and silently ignores a `page` field. This compiles
|
|
14
|
-
* (both accept `Partial<SourceQuery>`-shaped input) but a `page` value from `decodeQuery()` is a
|
|
15
|
-
* no-op on those two source types.
|
|
16
|
-
*/
|
|
17
|
-
export declare function applyQuery<T extends {
|
|
18
|
-
patch(changes: Partial<SourceQuery>): Promise<void>;
|
|
19
|
-
}>(source: T, changes: Partial<SourceQuery>): Promise<void>;
|
|
20
|
-
//# sourceMappingURL=applyQuery.d.ts.map
|
package/dist/applyQuery.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"applyQuery.d.ts","sourceRoot":"","sources":["../src/applyQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS;IAAE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,EAC1F,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
package/dist/applyQuery.js
DELETED
package/dist/applyQuery.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"applyQuery.js","names":[],"sources":["../src/applyQuery.ts"],"sourcesContent":["import type { SourceQuery } from './types';\n\n/**\n * Generic query applicator — delegates to `source.patch(changes)`.\n * Use this with `decodeQuery()` output to restore URL state onto any source:\n *\n * @example\n * ```ts\n * const q = decodeQuery(new URL(location.href).searchParams, { defaultLimit: 20 });\n * await applyQuery(source, q);\n * ```\n *\n * ⚠️ `CursorSource` and `InfiniteSource` have no page-number concept (keyset/append navigation) —\n * their `patch()` only reads `limit`/`search` and silently ignores a `page` field. This compiles\n * (both accept `Partial<SourceQuery>`-shaped input) but a `page` value from `decodeQuery()` is a\n * no-op on those two source types.\n */\nexport function applyQuery<T extends { patch(changes: Partial<SourceQuery>): Promise<void> }>(\n source: T,\n changes: Partial<SourceQuery>,\n): Promise<void> {\n return source.patch(changes);\n}\n"],"mappings":";AAiBA,SAAgB,EACd,GACA,GACe;CACf,OAAO,EAAO,MAAM,CAAO;AAC7B"}
|
package/dist/asyncSearch.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
function e(e,t){let n=null,r=()=>{n&&=(n(),null)};return{cancel(){e.cancelTimer(),r()},dispose(){r()},schedule(i,a){r();let o=new Promise(e=>{n=e});return e.schedule(i,a),t(),o},settleIfIdle(e){e===0&&r()}}}exports.createAsyncSearchCoordinator=e;
|
|
2
|
-
//# sourceMappingURL=asyncSearch.cjs.map
|
package/dist/asyncSearch.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asyncSearch.cjs","names":[],"sources":["../src/asyncSearch.ts"],"sourcesContent":["import type { SourceCore } from './core';\n\nexport type AsyncSearchCoordinator = Readonly<{\n cancel(): void;\n dispose(): void;\n schedule(run: () => void, delayMs: number): Promise<void>;\n settleIfIdle(pendingCount: number): void;\n}>;\n\n/**\n * Shared debounced-search promise orchestration for async sources.\n * Keeps search() promise lifecycles consistent across remote/cursor/infinite sources.\n */\nexport function createAsyncSearchCoordinator(core: SourceCore, notify: () => void): AsyncSearchCoordinator {\n let pendingResolve: (() => void) | null = null;\n\n const resolvePending = () => {\n if (pendingResolve) {\n pendingResolve();\n pendingResolve = null;\n }\n };\n\n return {\n cancel() {\n core.cancelTimer();\n resolvePending();\n },\n\n dispose() {\n resolvePending();\n },\n\n schedule(run, delayMs) {\n resolvePending();\n\n const promise = new Promise<void>((resolve) => {\n pendingResolve = resolve;\n });\n\n core.schedule(run, delayMs);\n notify();\n\n return promise;\n },\n\n settleIfIdle(pendingCount) {\n if (pendingCount === 0) resolvePending();\n },\n };\n}\n"],"mappings":"AAaA,SAAgB,EAA6B,EAAkB,EAA4C,CACzG,IAAI,EAAsC,KAEpC,MAAuB,CAC3B,AAEE,KADA,EAAe,EACE,KAErB,EAEA,MAAO,CACL,QAAS,CACP,EAAK,YAAY,EACjB,EAAe,CACjB,EAEA,SAAU,CACR,EAAe,CACjB,EAEA,SAAS,EAAK,EAAS,CACrB,EAAe,EAEf,IAAM,EAAU,IAAI,QAAe,GAAY,CAC7C,EAAiB,CACnB,CAAC,EAKD,OAHA,EAAK,SAAS,EAAK,CAAO,EAC1B,EAAO,EAEA,CACT,EAEA,aAAa,EAAc,CACrB,IAAiB,GAAG,EAAe,CACzC,CACF,CACF"}
|
package/dist/asyncSearch.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { SourceCore } from './core';
|
|
2
|
-
export type AsyncSearchCoordinator = Readonly<{
|
|
3
|
-
cancel(): void;
|
|
4
|
-
dispose(): void;
|
|
5
|
-
schedule(run: () => void, delayMs: number): Promise<void>;
|
|
6
|
-
settleIfIdle(pendingCount: number): void;
|
|
7
|
-
}>;
|
|
8
|
-
/**
|
|
9
|
-
* Shared debounced-search promise orchestration for async sources.
|
|
10
|
-
* Keeps search() promise lifecycles consistent across remote/cursor/infinite sources.
|
|
11
|
-
*/
|
|
12
|
-
export declare function createAsyncSearchCoordinator(core: SourceCore, notify: () => void): AsyncSearchCoordinator;
|
|
13
|
-
//# sourceMappingURL=asyncSearch.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asyncSearch.d.ts","sourceRoot":"","sources":["../src/asyncSearch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEzC,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C,CAAC,CAAC;AAEH;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,IAAI,GAAG,sBAAsB,CAqCzG"}
|
package/dist/asyncSearch.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
//#region src/asyncSearch.ts
|
|
2
|
-
function e(e, t) {
|
|
3
|
-
let n = null, r = () => {
|
|
4
|
-
n &&= (n(), null);
|
|
5
|
-
};
|
|
6
|
-
return {
|
|
7
|
-
cancel() {
|
|
8
|
-
e.cancelTimer(), r();
|
|
9
|
-
},
|
|
10
|
-
dispose() {
|
|
11
|
-
r();
|
|
12
|
-
},
|
|
13
|
-
schedule(i, a) {
|
|
14
|
-
r();
|
|
15
|
-
let o = new Promise((e) => {
|
|
16
|
-
n = e;
|
|
17
|
-
});
|
|
18
|
-
return e.schedule(i, a), t(), o;
|
|
19
|
-
},
|
|
20
|
-
settleIfIdle(e) {
|
|
21
|
-
e === 0 && r();
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
26
|
-
export { e as createAsyncSearchCoordinator };
|
|
27
|
-
|
|
28
|
-
//# sourceMappingURL=asyncSearch.js.map
|
package/dist/asyncSearch.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asyncSearch.js","names":[],"sources":["../src/asyncSearch.ts"],"sourcesContent":["import type { SourceCore } from './core';\n\nexport type AsyncSearchCoordinator = Readonly<{\n cancel(): void;\n dispose(): void;\n schedule(run: () => void, delayMs: number): Promise<void>;\n settleIfIdle(pendingCount: number): void;\n}>;\n\n/**\n * Shared debounced-search promise orchestration for async sources.\n * Keeps search() promise lifecycles consistent across remote/cursor/infinite sources.\n */\nexport function createAsyncSearchCoordinator(core: SourceCore, notify: () => void): AsyncSearchCoordinator {\n let pendingResolve: (() => void) | null = null;\n\n const resolvePending = () => {\n if (pendingResolve) {\n pendingResolve();\n pendingResolve = null;\n }\n };\n\n return {\n cancel() {\n core.cancelTimer();\n resolvePending();\n },\n\n dispose() {\n resolvePending();\n },\n\n schedule(run, delayMs) {\n resolvePending();\n\n const promise = new Promise<void>((resolve) => {\n pendingResolve = resolve;\n });\n\n core.schedule(run, delayMs);\n notify();\n\n return promise;\n },\n\n settleIfIdle(pendingCount) {\n if (pendingCount === 0) resolvePending();\n },\n };\n}\n"],"mappings":";AAaA,SAAgB,EAA6B,GAAkB,GAA4C;CACzG,IAAI,IAAsC,MAEpC,UAAuB;EAC3B,AAEE,OADA,EAAe,GACE;CAErB;CAEA,OAAO;EACL,SAAS;GAEP,AADA,EAAK,YAAY,GACjB,EAAe;EACjB;EAEA,UAAU;GACR,EAAe;EACjB;EAEA,SAAS,GAAK,GAAS;GACrB,EAAe;GAEf,IAAM,IAAU,IAAI,SAAe,MAAY;IAC7C,IAAiB;GACnB,CAAC;GAKD,OAHA,EAAK,SAAS,GAAK,CAAO,GAC1B,EAAO,GAEA;EACT;EAEA,aAAa,GAAc;GACzB,AAAI,MAAiB,KAAG,EAAe;EACzC;CACF;AACF"}
|
package/dist/codecs.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./errors.cjs");var t=e=>{let t={...e.search&&{search:e.search},limit:String(e.limit),page:String(e.page)},n=e;return n.filter!==void 0&&(t.filter=JSON.stringify(n.filter)),n.sort!==void 0&&(t.sort=JSON.stringify(n.sort)),t},n=(t,n={})=>{let r=t instanceof URLSearchParams?Object.fromEntries(t.entries()):t,{defaultLimit:i=20,strict:a=!1}=n,o=(e,t)=>{if(e===void 0)return t;let n=Array.isArray(e)?e[0]:e,r=Number(n);return Number.isInteger(r)&&r>0?r:t},s=(t,n)=>{if(n===void 0)return;let r=Array.isArray(n)?n[0]:n;try{return JSON.parse(r)}catch{if(a)throw new e.SourcererError(`Invalid query param "${t}": ${r}`);return}},c=r.limit,l=r.page,u=r.search,d=r.filter,f=r.sort,p=d===void 0?void 0:s(`filter`,d),m=f===void 0?void 0:s(`sort`,f);return{...p!==void 0&&{filter:p},...m!==void 0&&{sort:m},...u!==void 0&&{search:Array.isArray(u)?u[0]:u},limit:o(c,i),page:o(l,1)}};exports.decodeQuery=n,exports.encodeQuery=t;
|
|
2
|
-
//# sourceMappingURL=codecs.cjs.map
|
package/dist/codecs.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"codecs.cjs","names":[],"sources":["../src/codecs.ts"],"sourcesContent":["import type { QueryParams, QueryParamsInput, RemoteSourceQuery, SourceQuery } from './types';\n\nimport { SourcererError } from './errors';\n\n/**\n * Serialises a `SourceQuery` or `RemoteSourceQuery` into plain URL-safe string params.\n *\n * ⚠️ `filter` and `sort` are JSON-stringified without a try/catch. A circular object reference\n * throws a native `TypeError` (\"Converting circular structure to JSON\") straight out of this\n * function — ensure filter/sort values are plain serialisable objects, or wrap the call yourself.\n *\n * ⚠️ Round-trip fidelity: `page` and `limit` must be positive integers. `encodeQuery` will\n * serialise any number (including 0), but `decodeQuery` clamps non-positive values to 1/defaultLimit.\n *\n * `encodeQuery` + `decodeQuery` form a round-trip pair: filter/sort values are JSON-stringified\n * on encode and JSON-parsed on decode.\n */\nexport const encodeQuery = <TFilter = unknown, TSort = unknown>(\n query: RemoteSourceQuery<TFilter, TSort> | SourceQuery,\n): QueryParams => {\n const base: QueryParams = {\n ...(query.search && { search: query.search }),\n limit: String(query.limit),\n page: String(query.page),\n };\n\n const rq = query as RemoteSourceQuery<TFilter, TSort>;\n\n if (rq.filter !== undefined) {\n base['filter'] = JSON.stringify(rq.filter);\n }\n\n if (rq.sort !== undefined) {\n base['sort'] = JSON.stringify(rq.sort);\n }\n\n return base;\n};\n\nexport type DecodeQueryOptions = Readonly<{\n defaultLimit?: number;\n strict?: boolean;\n}>;\n\n/**\n * Parses URL query params into a `Partial<RemoteSourceQuery<unknown, unknown>>`.\n * Accepts either a plain `Record<string, string | string[] | undefined>` or a `URLSearchParams` instance.\n *\n * - `filter` and `sort` are JSON-parsed and typed as `unknown` — validate and narrow them\n * with a runtime schema (e.g. Zod) before passing to `applyQuery`.\n * - `search` is omitted from the result when the param is absent (rather than defaulting to `''`).\n * - `limit` and `page` are parsed as positive integers; invalid values fall back to defaults.\n */\nexport const decodeQuery = (\n params: QueryParamsInput | URLSearchParams,\n options: DecodeQueryOptions = {},\n): Partial<RemoteSourceQuery<unknown, unknown>> => {\n const raw: QueryParamsInput =\n params instanceof URLSearchParams ? (Object.fromEntries(params.entries()) as QueryParamsInput) : params;\n\n const { defaultLimit = 20, strict = false } = options;\n\n const parsePositiveInt = (value: string | string[] | undefined, fallback: number): number => {\n if (value === undefined) return fallback;\n\n const str = Array.isArray(value) ? value[0] : value;\n const n = Number(str);\n\n return Number.isInteger(n) && n > 0 ? n : fallback;\n };\n\n const parseJson = (key: string, value: string | string[] | undefined): unknown => {\n if (value === undefined) return undefined;\n\n const str = Array.isArray(value) ? value[0] : value;\n\n try {\n return JSON.parse(str) as unknown;\n } catch {\n if (strict) throw new SourcererError(`Invalid query param \"${key}\": ${str}`);\n\n return undefined;\n }\n };\n\n const rawLimit = raw['limit'];\n const rawPage = raw['page'];\n const rawSearch = raw['search'];\n const rawFilter = raw['filter'];\n const rawSort = raw['sort'];\n\n const filter = rawFilter !== undefined ? parseJson('filter', rawFilter) : undefined;\n const sort = rawSort !== undefined ? parseJson('sort', rawSort) : undefined;\n\n const result: Partial<RemoteSourceQuery<unknown, unknown>> = {\n ...(filter !== undefined && { filter }),\n ...(sort !== undefined && { sort }),\n ...(rawSearch !== undefined && { search: Array.isArray(rawSearch) ? rawSearch[0] : rawSearch }),\n limit: parsePositiveInt(rawLimit, defaultLimit),\n page: parsePositiveInt(rawPage, 1),\n };\n\n return result;\n};\n"],"mappings":"gCAiBA,IAAa,EACX,GACgB,CAChB,IAAM,EAAoB,CACxB,GAAI,EAAM,QAAU,CAAE,OAAQ,EAAM,MAAO,EAC3C,MAAO,OAAO,EAAM,KAAK,EACzB,KAAM,OAAO,EAAM,IAAI,CACzB,EAEM,EAAK,EAUX,OARI,EAAG,SAAW,IAAA,KAChB,EAAK,OAAY,KAAK,UAAU,EAAG,MAAM,GAGvC,EAAG,OAAS,IAAA,KACd,EAAK,KAAU,KAAK,UAAU,EAAG,IAAI,GAGhC,CACT,EAgBa,GACX,EACA,EAA8B,CAAC,IACkB,CACjD,IAAM,EACJ,aAAkB,gBAAmB,OAAO,YAAY,EAAO,QAAQ,CAAC,EAAyB,EAE7F,CAAE,eAAe,GAAI,SAAS,IAAU,EAExC,GAAoB,EAAsC,IAA6B,CAC3F,GAAI,IAAU,IAAA,GAAW,OAAO,EAEhC,IAAM,EAAM,MAAM,QAAQ,CAAK,EAAI,EAAM,GAAK,EACxC,EAAI,OAAO,CAAG,EAEpB,OAAO,OAAO,UAAU,CAAC,GAAK,EAAI,EAAI,EAAI,CAC5C,EAEM,GAAa,EAAa,IAAkD,CAChF,GAAI,IAAU,IAAA,GAAW,OAEzB,IAAM,EAAM,MAAM,QAAQ,CAAK,EAAI,EAAM,GAAK,EAE9C,GAAI,CACF,OAAO,KAAK,MAAM,CAAG,CACvB,MAAQ,CACN,GAAI,EAAQ,MAAM,IAAI,EAAA,eAAe,wBAAwB,EAAI,KAAK,GAAK,EAE3E,MACF,CACF,EAEM,EAAW,EAAI,MACf,EAAU,EAAI,KACd,EAAY,EAAI,OAChB,EAAY,EAAI,OAChB,EAAU,EAAI,KAEd,EAAS,IAAc,IAAA,GAA6C,IAAA,GAAjC,EAAU,SAAU,CAAS,EAChE,EAAO,IAAY,IAAA,GAAyC,IAAA,GAA7B,EAAU,OAAQ,CAAO,EAU9D,MAAO,CAPL,GAAI,IAAW,IAAA,IAAa,CAAE,QAAO,EACrC,GAAI,IAAS,IAAA,IAAa,CAAE,MAAK,EACjC,GAAI,IAAc,IAAA,IAAa,CAAE,OAAQ,MAAM,QAAQ,CAAS,EAAI,EAAU,GAAK,CAAU,EAC7F,MAAO,EAAiB,EAAU,CAAY,EAC9C,KAAM,EAAiB,EAAS,CAAC,CAG5B,CACT"}
|
package/dist/codecs.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { QueryParams, QueryParamsInput, RemoteSourceQuery, SourceQuery } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Serialises a `SourceQuery` or `RemoteSourceQuery` into plain URL-safe string params.
|
|
4
|
-
*
|
|
5
|
-
* ⚠️ `filter` and `sort` are JSON-stringified without a try/catch. A circular object reference
|
|
6
|
-
* throws a native `TypeError` ("Converting circular structure to JSON") straight out of this
|
|
7
|
-
* function — ensure filter/sort values are plain serialisable objects, or wrap the call yourself.
|
|
8
|
-
*
|
|
9
|
-
* ⚠️ Round-trip fidelity: `page` and `limit` must be positive integers. `encodeQuery` will
|
|
10
|
-
* serialise any number (including 0), but `decodeQuery` clamps non-positive values to 1/defaultLimit.
|
|
11
|
-
*
|
|
12
|
-
* `encodeQuery` + `decodeQuery` form a round-trip pair: filter/sort values are JSON-stringified
|
|
13
|
-
* on encode and JSON-parsed on decode.
|
|
14
|
-
*/
|
|
15
|
-
export declare const encodeQuery: <TFilter = unknown, TSort = unknown>(query: RemoteSourceQuery<TFilter, TSort> | SourceQuery) => QueryParams;
|
|
16
|
-
export type DecodeQueryOptions = Readonly<{
|
|
17
|
-
defaultLimit?: number;
|
|
18
|
-
strict?: boolean;
|
|
19
|
-
}>;
|
|
20
|
-
/**
|
|
21
|
-
* Parses URL query params into a `Partial<RemoteSourceQuery<unknown, unknown>>`.
|
|
22
|
-
* Accepts either a plain `Record<string, string | string[] | undefined>` or a `URLSearchParams` instance.
|
|
23
|
-
*
|
|
24
|
-
* - `filter` and `sort` are JSON-parsed and typed as `unknown` — validate and narrow them
|
|
25
|
-
* with a runtime schema (e.g. Zod) before passing to `applyQuery`.
|
|
26
|
-
* - `search` is omitted from the result when the param is absent (rather than defaulting to `''`).
|
|
27
|
-
* - `limit` and `page` are parsed as positive integers; invalid values fall back to defaults.
|
|
28
|
-
*/
|
|
29
|
-
export declare const decodeQuery: (params: QueryParamsInput | URLSearchParams, options?: DecodeQueryOptions) => Partial<RemoteSourceQuery<unknown, unknown>>;
|
|
30
|
-
//# sourceMappingURL=codecs.d.ts.map
|
package/dist/codecs.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"codecs.d.ts","sourceRoot":"","sources":["../src/codecs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI7F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EAC5D,OAAO,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,WAAW,KACrD,WAkBF,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,GACtB,QAAQ,gBAAgB,GAAG,eAAe,EAC1C,UAAS,kBAAuB,KAC/B,OAAO,CAAC,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CA+C7C,CAAC"}
|
package/dist/codecs.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { SourcererError as e } from "./errors.js";
|
|
2
|
-
//#region src/codecs.ts
|
|
3
|
-
var t = (e) => {
|
|
4
|
-
let t = {
|
|
5
|
-
...e.search && { search: e.search },
|
|
6
|
-
limit: String(e.limit),
|
|
7
|
-
page: String(e.page)
|
|
8
|
-
}, n = e;
|
|
9
|
-
return n.filter !== void 0 && (t.filter = JSON.stringify(n.filter)), n.sort !== void 0 && (t.sort = JSON.stringify(n.sort)), t;
|
|
10
|
-
}, n = (t, n = {}) => {
|
|
11
|
-
let r = t instanceof URLSearchParams ? Object.fromEntries(t.entries()) : t, { defaultLimit: i = 20, strict: a = !1 } = n, o = (e, t) => {
|
|
12
|
-
if (e === void 0) return t;
|
|
13
|
-
let n = Array.isArray(e) ? e[0] : e, r = Number(n);
|
|
14
|
-
return Number.isInteger(r) && r > 0 ? r : t;
|
|
15
|
-
}, s = (t, n) => {
|
|
16
|
-
if (n === void 0) return;
|
|
17
|
-
let r = Array.isArray(n) ? n[0] : n;
|
|
18
|
-
try {
|
|
19
|
-
return JSON.parse(r);
|
|
20
|
-
} catch {
|
|
21
|
-
if (a) throw new e(`Invalid query param "${t}": ${r}`);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
}, c = r.limit, l = r.page, u = r.search, d = r.filter, f = r.sort, p = d === void 0 ? void 0 : s("filter", d), m = f === void 0 ? void 0 : s("sort", f);
|
|
25
|
-
return {
|
|
26
|
-
...p !== void 0 && { filter: p },
|
|
27
|
-
...m !== void 0 && { sort: m },
|
|
28
|
-
...u !== void 0 && { search: Array.isArray(u) ? u[0] : u },
|
|
29
|
-
limit: o(c, i),
|
|
30
|
-
page: o(l, 1)
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
//#endregion
|
|
34
|
-
export { n as decodeQuery, t as encodeQuery };
|
|
35
|
-
|
|
36
|
-
//# sourceMappingURL=codecs.js.map
|
package/dist/codecs.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"codecs.js","names":[],"sources":["../src/codecs.ts"],"sourcesContent":["import type { QueryParams, QueryParamsInput, RemoteSourceQuery, SourceQuery } from './types';\n\nimport { SourcererError } from './errors';\n\n/**\n * Serialises a `SourceQuery` or `RemoteSourceQuery` into plain URL-safe string params.\n *\n * ⚠️ `filter` and `sort` are JSON-stringified without a try/catch. A circular object reference\n * throws a native `TypeError` (\"Converting circular structure to JSON\") straight out of this\n * function — ensure filter/sort values are plain serialisable objects, or wrap the call yourself.\n *\n * ⚠️ Round-trip fidelity: `page` and `limit` must be positive integers. `encodeQuery` will\n * serialise any number (including 0), but `decodeQuery` clamps non-positive values to 1/defaultLimit.\n *\n * `encodeQuery` + `decodeQuery` form a round-trip pair: filter/sort values are JSON-stringified\n * on encode and JSON-parsed on decode.\n */\nexport const encodeQuery = <TFilter = unknown, TSort = unknown>(\n query: RemoteSourceQuery<TFilter, TSort> | SourceQuery,\n): QueryParams => {\n const base: QueryParams = {\n ...(query.search && { search: query.search }),\n limit: String(query.limit),\n page: String(query.page),\n };\n\n const rq = query as RemoteSourceQuery<TFilter, TSort>;\n\n if (rq.filter !== undefined) {\n base['filter'] = JSON.stringify(rq.filter);\n }\n\n if (rq.sort !== undefined) {\n base['sort'] = JSON.stringify(rq.sort);\n }\n\n return base;\n};\n\nexport type DecodeQueryOptions = Readonly<{\n defaultLimit?: number;\n strict?: boolean;\n}>;\n\n/**\n * Parses URL query params into a `Partial<RemoteSourceQuery<unknown, unknown>>`.\n * Accepts either a plain `Record<string, string | string[] | undefined>` or a `URLSearchParams` instance.\n *\n * - `filter` and `sort` are JSON-parsed and typed as `unknown` — validate and narrow them\n * with a runtime schema (e.g. Zod) before passing to `applyQuery`.\n * - `search` is omitted from the result when the param is absent (rather than defaulting to `''`).\n * - `limit` and `page` are parsed as positive integers; invalid values fall back to defaults.\n */\nexport const decodeQuery = (\n params: QueryParamsInput | URLSearchParams,\n options: DecodeQueryOptions = {},\n): Partial<RemoteSourceQuery<unknown, unknown>> => {\n const raw: QueryParamsInput =\n params instanceof URLSearchParams ? (Object.fromEntries(params.entries()) as QueryParamsInput) : params;\n\n const { defaultLimit = 20, strict = false } = options;\n\n const parsePositiveInt = (value: string | string[] | undefined, fallback: number): number => {\n if (value === undefined) return fallback;\n\n const str = Array.isArray(value) ? value[0] : value;\n const n = Number(str);\n\n return Number.isInteger(n) && n > 0 ? n : fallback;\n };\n\n const parseJson = (key: string, value: string | string[] | undefined): unknown => {\n if (value === undefined) return undefined;\n\n const str = Array.isArray(value) ? value[0] : value;\n\n try {\n return JSON.parse(str) as unknown;\n } catch {\n if (strict) throw new SourcererError(`Invalid query param \"${key}\": ${str}`);\n\n return undefined;\n }\n };\n\n const rawLimit = raw['limit'];\n const rawPage = raw['page'];\n const rawSearch = raw['search'];\n const rawFilter = raw['filter'];\n const rawSort = raw['sort'];\n\n const filter = rawFilter !== undefined ? parseJson('filter', rawFilter) : undefined;\n const sort = rawSort !== undefined ? parseJson('sort', rawSort) : undefined;\n\n const result: Partial<RemoteSourceQuery<unknown, unknown>> = {\n ...(filter !== undefined && { filter }),\n ...(sort !== undefined && { sort }),\n ...(rawSearch !== undefined && { search: Array.isArray(rawSearch) ? rawSearch[0] : rawSearch }),\n limit: parsePositiveInt(rawLimit, defaultLimit),\n page: parsePositiveInt(rawPage, 1),\n };\n\n return result;\n};\n"],"mappings":";;AAiBA,IAAa,KACX,MACgB;CAChB,IAAM,IAAoB;EACxB,GAAI,EAAM,UAAU,EAAE,QAAQ,EAAM,OAAO;EAC3C,OAAO,OAAO,EAAM,KAAK;EACzB,MAAM,OAAO,EAAM,IAAI;CACzB,GAEM,IAAK;CAUX,OARI,EAAG,WAAW,KAAA,MAChB,EAAK,SAAY,KAAK,UAAU,EAAG,MAAM,IAGvC,EAAG,SAAS,KAAA,MACd,EAAK,OAAU,KAAK,UAAU,EAAG,IAAI,IAGhC;AACT,GAgBa,KACX,GACA,IAA8B,CAAC,MACkB;CACjD,IAAM,IACJ,aAAkB,kBAAmB,OAAO,YAAY,EAAO,QAAQ,CAAC,IAAyB,GAE7F,EAAE,kBAAe,IAAI,YAAS,OAAU,GAExC,KAAoB,GAAsC,MAA6B;EAC3F,IAAI,MAAU,KAAA,GAAW,OAAO;EAEhC,IAAM,IAAM,MAAM,QAAQ,CAAK,IAAI,EAAM,KAAK,GACxC,IAAI,OAAO,CAAG;EAEpB,OAAO,OAAO,UAAU,CAAC,KAAK,IAAI,IAAI,IAAI;CAC5C,GAEM,KAAa,GAAa,MAAkD;EAChF,IAAI,MAAU,KAAA,GAAW;EAEzB,IAAM,IAAM,MAAM,QAAQ,CAAK,IAAI,EAAM,KAAK;EAE9C,IAAI;GACF,OAAO,KAAK,MAAM,CAAG;EACvB,QAAQ;GACN,IAAI,GAAQ,MAAM,IAAI,EAAe,wBAAwB,EAAI,KAAK,GAAK;GAE3E;EACF;CACF,GAEM,IAAW,EAAI,OACf,IAAU,EAAI,MACd,IAAY,EAAI,QAChB,IAAY,EAAI,QAChB,IAAU,EAAI,MAEd,IAAS,MAAc,KAAA,IAA6C,KAAA,IAAjC,EAAU,UAAU,CAAS,GAChE,IAAO,MAAY,KAAA,IAAyC,KAAA,IAA7B,EAAU,QAAQ,CAAO;CAU9D,OAAO;EAPL,GAAI,MAAW,KAAA,KAAa,EAAE,UAAO;EACrC,GAAI,MAAS,KAAA,KAAa,EAAE,QAAK;EACjC,GAAI,MAAc,KAAA,KAAa,EAAE,QAAQ,MAAM,QAAQ,CAAS,IAAI,EAAU,KAAK,EAAU;EAC7F,OAAO,EAAiB,GAAU,CAAY;EAC9C,MAAM,EAAiB,GAAS,CAAC;CAG5B;AACT"}
|
package/dist/core.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./errors.cjs");function t(t){let n=new Set,r=new Set,i=new AbortController,a,o=!1;return{cancelTimer(){a&&=(clearTimeout(a),void 0)},get disposalSignal(){return i.signal},dispose(){a&&=(clearTimeout(a),void 0);for(let t of r)t.timeoutId!==void 0&&clearTimeout(t.timeoutId),t.reject(new e.SourceDisposedError);r.clear(),n.clear(),o=!0,i.abort()},flush(e){return a?(clearTimeout(a),a=void 0,e()):Promise.resolve()},get isDisposed(){return o},get isScheduled(){return a!==void 0},notify(e){if(!o){(e??t?.onBeforeNotify)?.();for(let e of n)e();for(let e of r)e.check()}},ready(t,n){return o?Promise.reject(new e.SourceDisposedError):t()?Promise.resolve():new Promise((i,a)=>{let o={check:()=>{t()&&(o.timeoutId!==void 0&&clearTimeout(o.timeoutId),r.delete(o),i())},reject:a};n!==void 0&&(o.timeoutId=setTimeout(()=>{r.delete(o),a(new e.SourceTimeoutError(n))},n)),r.add(o)})},schedule(e,t){o||(a&&clearTimeout(a),a=setTimeout(()=>{a=void 0,e()},t))},subscribe(e){return o?()=>{}:(n.add(e),()=>n.delete(e))}}}exports.createSourceCore=t;
|
|
2
|
-
//# sourceMappingURL=core.cjs.map
|
package/dist/core.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"core.cjs","names":[],"sources":["../src/core.ts"],"sourcesContent":["import { SourceDisposedError, SourceTimeoutError } from './errors';\n\n/**\n * Shared source infrastructure: listener management, debounce scheduling, and ready() polling.\n * All source factories compose this core rather than duplicating the same patterns.\n */\nexport type SourceCore = {\n /** Cancels any pending timer without invoking the callback. */\n cancelTimer(): void;\n\n /** `AbortSignal` that is aborted when `dispose()` is called. */\n readonly disposalSignal: AbortSignal;\n\n /**\n * Clears all listeners, cancels any pending timer, and marks the core as disposed.\n * Subsequent method calls on the core become no-ops.\n */\n dispose(): void;\n\n /**\n * If a timer is scheduled, cancels it and immediately invokes `fn`.\n * Returns a resolved Promise when no timer is pending.\n */\n flush(fn: () => Promise<void>): Promise<void>;\n\n /** Whether `dispose()` has been called. All methods become no-ops after disposal. */\n readonly isDisposed: boolean;\n\n /** Whether a debounce timer is currently scheduled (drives `meta.isSearchPending`). */\n readonly isScheduled: boolean;\n\n /**\n * Fire all registered listeners and wake any `ready()` waiters.\n * When provided, `onBefore` runs once before listeners are called — use it to\n * refresh cached meta/current before subscribers observe the new state.\n */\n notify(onBefore?: () => void): void;\n\n /**\n * Returns a Promise that resolves when `isIdle()` returns true.\n * Resolves synchronously if already idle.\n * Rejects with a `TimeoutError` after `timeoutMs` ms if still not idle.\n */\n ready(isIdle: () => boolean, timeoutMs?: number): Promise<void>;\n\n /**\n * Schedules a debounced callback, replacing any pending timer.\n * The callback is invoked after `delayMs` ms of inactivity.\n */\n schedule(fn: () => void, delayMs: number): void;\n\n /** Subscribe to change notifications. Returns an unsubscribe function. */\n subscribe(listener: () => void): () => void;\n};\n\nexport function createSourceCore(opts?: { onBeforeNotify?: () => void }): SourceCore {\n const listeners = new Set<() => void>();\n const readyWaiters = new Set<{\n check: () => void;\n reject: (err: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\n }>();\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let disposed = false;\n\n return {\n cancelTimer() {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n },\n\n get disposalSignal() {\n return controller.signal;\n },\n\n dispose() {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n\n for (const waiter of readyWaiters) {\n if (waiter.timeoutId !== undefined) clearTimeout(waiter.timeoutId);\n\n waiter.reject(new SourceDisposedError());\n }\n\n readyWaiters.clear();\n listeners.clear();\n disposed = true;\n controller.abort();\n },\n\n flush(fn) {\n if (!timer) return Promise.resolve();\n\n clearTimeout(timer);\n timer = undefined;\n\n return fn();\n },\n\n get isDisposed() {\n return disposed;\n },\n\n get isScheduled() {\n return timer !== undefined;\n },\n\n notify(onBefore?: () => void) {\n if (disposed) return;\n\n (onBefore ?? opts?.onBeforeNotify)?.();\n\n for (const listener of listeners) {\n listener();\n }\n\n for (const waiter of readyWaiters) {\n waiter.check();\n }\n },\n\n ready(isIdle, timeoutMs) {\n if (disposed) return Promise.reject(new SourceDisposedError());\n\n if (isIdle()) return Promise.resolve();\n\n return new Promise<void>((resolve, reject) => {\n const waiter: { check: () => void; reject: (err: unknown) => void; timeoutId?: ReturnType<typeof setTimeout> } =\n {\n check: () => {\n if (isIdle()) {\n if (waiter.timeoutId !== undefined) clearTimeout(waiter.timeoutId);\n\n readyWaiters.delete(waiter);\n resolve();\n }\n },\n reject,\n };\n\n if (timeoutMs !== undefined) {\n waiter.timeoutId = setTimeout(() => {\n readyWaiters.delete(waiter);\n reject(new SourceTimeoutError(timeoutMs));\n }, timeoutMs);\n }\n\n readyWaiters.add(waiter);\n });\n },\n\n schedule(fn, delayMs) {\n if (disposed) return;\n\n if (timer) clearTimeout(timer);\n\n timer = setTimeout(() => {\n timer = undefined;\n fn();\n }, delayMs);\n },\n\n subscribe(listener) {\n if (disposed) return () => {};\n\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n };\n}\n"],"mappings":"gCAuDA,SAAgB,EAAiB,EAAoD,CACnF,IAAM,EAAY,IAAI,IAChB,EAAe,IAAI,IAKnB,EAAa,IAAI,gBACnB,EACA,EAAW,GAEf,MAAO,CACL,aAAc,CACZ,AAEE,KADA,aAAa,CAAK,EACV,IAAA,GAEZ,EAEA,IAAI,gBAAiB,CACnB,OAAO,EAAW,MACpB,EAEA,SAAU,CACR,AAEE,KADA,aAAa,CAAK,EACV,IAAA,IAGV,IAAK,IAAM,KAAU,EACf,EAAO,YAAc,IAAA,IAAW,aAAa,EAAO,SAAS,EAEjE,EAAO,OAAO,IAAI,EAAA,mBAAqB,EAGzC,EAAa,MAAM,EACnB,EAAU,MAAM,EAChB,EAAW,GACX,EAAW,MAAM,CACnB,EAEA,MAAM,EAAI,CAMR,OALK,GAEL,aAAa,CAAK,EAClB,EAAQ,IAAA,GAED,EAAG,GALS,QAAQ,QAAQ,CAMrC,EAEA,IAAI,YAAa,CACf,OAAO,CACT,EAEA,IAAI,aAAc,CAChB,OAAO,IAAU,IAAA,EACnB,EAEA,OAAO,EAAuB,CACxB,MAEJ,EAAC,GAAY,GAAM,eAAA,GAAkB,EAErC,IAAK,IAAM,KAAY,EACrB,EAAS,EAGX,IAAK,IAAM,KAAU,EACnB,EAAO,MAAM,CAPsB,CASvC,EAEA,MAAM,EAAQ,EAAW,CAKvB,OAJI,EAAiB,QAAQ,OAAO,IAAI,EAAA,mBAAqB,EAEzD,EAAO,EAAU,QAAQ,QAAQ,EAE9B,IAAI,SAAe,EAAS,IAAW,CAC5C,IAAM,EACJ,CACE,UAAa,CACP,EAAO,IACL,EAAO,YAAc,IAAA,IAAW,aAAa,EAAO,SAAS,EAEjE,EAAa,OAAO,CAAM,EAC1B,EAAQ,EAEZ,EACA,QACF,EAEE,IAAc,IAAA,KAChB,EAAO,UAAY,eAAiB,CAClC,EAAa,OAAO,CAAM,EAC1B,EAAO,IAAI,EAAA,mBAAmB,CAAS,CAAC,CAC1C,EAAG,CAAS,GAGd,EAAa,IAAI,CAAM,CACzB,CAAC,CACH,EAEA,SAAS,EAAI,EAAS,CAChB,IAEA,GAAO,aAAa,CAAK,EAE7B,EAAQ,eAAiB,CACvB,EAAQ,IAAA,GACR,EAAG,CACL,EAAG,CAAO,EACZ,EAEA,UAAU,EAAU,CAKlB,OAJI,MAAuB,CAAC,GAE5B,EAAU,IAAI,CAAQ,MAET,EAAU,OAAO,CAAQ,EACxC,CACF,CACF"}
|
package/dist/core.d.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared source infrastructure: listener management, debounce scheduling, and ready() polling.
|
|
3
|
-
* All source factories compose this core rather than duplicating the same patterns.
|
|
4
|
-
*/
|
|
5
|
-
export type SourceCore = {
|
|
6
|
-
/** Cancels any pending timer without invoking the callback. */
|
|
7
|
-
cancelTimer(): void;
|
|
8
|
-
/** `AbortSignal` that is aborted when `dispose()` is called. */
|
|
9
|
-
readonly disposalSignal: AbortSignal;
|
|
10
|
-
/**
|
|
11
|
-
* Clears all listeners, cancels any pending timer, and marks the core as disposed.
|
|
12
|
-
* Subsequent method calls on the core become no-ops.
|
|
13
|
-
*/
|
|
14
|
-
dispose(): void;
|
|
15
|
-
/**
|
|
16
|
-
* If a timer is scheduled, cancels it and immediately invokes `fn`.
|
|
17
|
-
* Returns a resolved Promise when no timer is pending.
|
|
18
|
-
*/
|
|
19
|
-
flush(fn: () => Promise<void>): Promise<void>;
|
|
20
|
-
/** Whether `dispose()` has been called. All methods become no-ops after disposal. */
|
|
21
|
-
readonly isDisposed: boolean;
|
|
22
|
-
/** Whether a debounce timer is currently scheduled (drives `meta.isSearchPending`). */
|
|
23
|
-
readonly isScheduled: boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Fire all registered listeners and wake any `ready()` waiters.
|
|
26
|
-
* When provided, `onBefore` runs once before listeners are called — use it to
|
|
27
|
-
* refresh cached meta/current before subscribers observe the new state.
|
|
28
|
-
*/
|
|
29
|
-
notify(onBefore?: () => void): void;
|
|
30
|
-
/**
|
|
31
|
-
* Returns a Promise that resolves when `isIdle()` returns true.
|
|
32
|
-
* Resolves synchronously if already idle.
|
|
33
|
-
* Rejects with a `TimeoutError` after `timeoutMs` ms if still not idle.
|
|
34
|
-
*/
|
|
35
|
-
ready(isIdle: () => boolean, timeoutMs?: number): Promise<void>;
|
|
36
|
-
/**
|
|
37
|
-
* Schedules a debounced callback, replacing any pending timer.
|
|
38
|
-
* The callback is invoked after `delayMs` ms of inactivity.
|
|
39
|
-
*/
|
|
40
|
-
schedule(fn: () => void, delayMs: number): void;
|
|
41
|
-
/** Subscribe to change notifications. Returns an unsubscribe function. */
|
|
42
|
-
subscribe(listener: () => void): () => void;
|
|
43
|
-
};
|
|
44
|
-
export declare function createSourceCore(opts?: {
|
|
45
|
-
onBeforeNotify?: () => void;
|
|
46
|
-
}): SourceCore;
|
|
47
|
-
//# sourceMappingURL=core.d.ts.map
|
package/dist/core.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,+DAA+D;IAC/D,WAAW,IAAI,IAAI,CAAC;IAEpB,gEAAgE;IAChE,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;IAErC;;;OAGG;IACH,OAAO,IAAI,IAAI,CAAC;IAEhB;;;OAGG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAE7B,uFAAuF;IACvF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAEpC;;;;OAIG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhE;;;OAGG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhD,0EAA0E;IAC1E,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC7C,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG,UAAU,CAyHnF"}
|
package/dist/core.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { SourceDisposedError as e, SourceTimeoutError as t } from "./errors.js";
|
|
2
|
-
//#region src/core.ts
|
|
3
|
-
function n(n) {
|
|
4
|
-
let r = /* @__PURE__ */ new Set(), i = /* @__PURE__ */ new Set(), a = new AbortController(), o, s = !1;
|
|
5
|
-
return {
|
|
6
|
-
cancelTimer() {
|
|
7
|
-
o &&= (clearTimeout(o), void 0);
|
|
8
|
-
},
|
|
9
|
-
get disposalSignal() {
|
|
10
|
-
return a.signal;
|
|
11
|
-
},
|
|
12
|
-
dispose() {
|
|
13
|
-
o &&= (clearTimeout(o), void 0);
|
|
14
|
-
for (let t of i) t.timeoutId !== void 0 && clearTimeout(t.timeoutId), t.reject(new e());
|
|
15
|
-
i.clear(), r.clear(), s = !0, a.abort();
|
|
16
|
-
},
|
|
17
|
-
flush(e) {
|
|
18
|
-
return o ? (clearTimeout(o), o = void 0, e()) : Promise.resolve();
|
|
19
|
-
},
|
|
20
|
-
get isDisposed() {
|
|
21
|
-
return s;
|
|
22
|
-
},
|
|
23
|
-
get isScheduled() {
|
|
24
|
-
return o !== void 0;
|
|
25
|
-
},
|
|
26
|
-
notify(e) {
|
|
27
|
-
if (!s) {
|
|
28
|
-
(e ?? n?.onBeforeNotify)?.();
|
|
29
|
-
for (let e of r) e();
|
|
30
|
-
for (let e of i) e.check();
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
ready(n, r) {
|
|
34
|
-
return s ? Promise.reject(new e()) : n() ? Promise.resolve() : new Promise((e, a) => {
|
|
35
|
-
let o = {
|
|
36
|
-
check: () => {
|
|
37
|
-
n() && (o.timeoutId !== void 0 && clearTimeout(o.timeoutId), i.delete(o), e());
|
|
38
|
-
},
|
|
39
|
-
reject: a
|
|
40
|
-
};
|
|
41
|
-
r !== void 0 && (o.timeoutId = setTimeout(() => {
|
|
42
|
-
i.delete(o), a(new t(r));
|
|
43
|
-
}, r)), i.add(o);
|
|
44
|
-
});
|
|
45
|
-
},
|
|
46
|
-
schedule(e, t) {
|
|
47
|
-
s || (o && clearTimeout(o), o = setTimeout(() => {
|
|
48
|
-
o = void 0, e();
|
|
49
|
-
}, t));
|
|
50
|
-
},
|
|
51
|
-
subscribe(e) {
|
|
52
|
-
return s ? () => {} : (r.add(e), () => r.delete(e));
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
//#endregion
|
|
57
|
-
export { n as createSourceCore };
|
|
58
|
-
|
|
59
|
-
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"core.js","names":[],"sources":["../src/core.ts"],"sourcesContent":["import { SourceDisposedError, SourceTimeoutError } from './errors';\n\n/**\n * Shared source infrastructure: listener management, debounce scheduling, and ready() polling.\n * All source factories compose this core rather than duplicating the same patterns.\n */\nexport type SourceCore = {\n /** Cancels any pending timer without invoking the callback. */\n cancelTimer(): void;\n\n /** `AbortSignal` that is aborted when `dispose()` is called. */\n readonly disposalSignal: AbortSignal;\n\n /**\n * Clears all listeners, cancels any pending timer, and marks the core as disposed.\n * Subsequent method calls on the core become no-ops.\n */\n dispose(): void;\n\n /**\n * If a timer is scheduled, cancels it and immediately invokes `fn`.\n * Returns a resolved Promise when no timer is pending.\n */\n flush(fn: () => Promise<void>): Promise<void>;\n\n /** Whether `dispose()` has been called. All methods become no-ops after disposal. */\n readonly isDisposed: boolean;\n\n /** Whether a debounce timer is currently scheduled (drives `meta.isSearchPending`). */\n readonly isScheduled: boolean;\n\n /**\n * Fire all registered listeners and wake any `ready()` waiters.\n * When provided, `onBefore` runs once before listeners are called — use it to\n * refresh cached meta/current before subscribers observe the new state.\n */\n notify(onBefore?: () => void): void;\n\n /**\n * Returns a Promise that resolves when `isIdle()` returns true.\n * Resolves synchronously if already idle.\n * Rejects with a `TimeoutError` after `timeoutMs` ms if still not idle.\n */\n ready(isIdle: () => boolean, timeoutMs?: number): Promise<void>;\n\n /**\n * Schedules a debounced callback, replacing any pending timer.\n * The callback is invoked after `delayMs` ms of inactivity.\n */\n schedule(fn: () => void, delayMs: number): void;\n\n /** Subscribe to change notifications. Returns an unsubscribe function. */\n subscribe(listener: () => void): () => void;\n};\n\nexport function createSourceCore(opts?: { onBeforeNotify?: () => void }): SourceCore {\n const listeners = new Set<() => void>();\n const readyWaiters = new Set<{\n check: () => void;\n reject: (err: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\n }>();\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let disposed = false;\n\n return {\n cancelTimer() {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n },\n\n get disposalSignal() {\n return controller.signal;\n },\n\n dispose() {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n\n for (const waiter of readyWaiters) {\n if (waiter.timeoutId !== undefined) clearTimeout(waiter.timeoutId);\n\n waiter.reject(new SourceDisposedError());\n }\n\n readyWaiters.clear();\n listeners.clear();\n disposed = true;\n controller.abort();\n },\n\n flush(fn) {\n if (!timer) return Promise.resolve();\n\n clearTimeout(timer);\n timer = undefined;\n\n return fn();\n },\n\n get isDisposed() {\n return disposed;\n },\n\n get isScheduled() {\n return timer !== undefined;\n },\n\n notify(onBefore?: () => void) {\n if (disposed) return;\n\n (onBefore ?? opts?.onBeforeNotify)?.();\n\n for (const listener of listeners) {\n listener();\n }\n\n for (const waiter of readyWaiters) {\n waiter.check();\n }\n },\n\n ready(isIdle, timeoutMs) {\n if (disposed) return Promise.reject(new SourceDisposedError());\n\n if (isIdle()) return Promise.resolve();\n\n return new Promise<void>((resolve, reject) => {\n const waiter: { check: () => void; reject: (err: unknown) => void; timeoutId?: ReturnType<typeof setTimeout> } =\n {\n check: () => {\n if (isIdle()) {\n if (waiter.timeoutId !== undefined) clearTimeout(waiter.timeoutId);\n\n readyWaiters.delete(waiter);\n resolve();\n }\n },\n reject,\n };\n\n if (timeoutMs !== undefined) {\n waiter.timeoutId = setTimeout(() => {\n readyWaiters.delete(waiter);\n reject(new SourceTimeoutError(timeoutMs));\n }, timeoutMs);\n }\n\n readyWaiters.add(waiter);\n });\n },\n\n schedule(fn, delayMs) {\n if (disposed) return;\n\n if (timer) clearTimeout(timer);\n\n timer = setTimeout(() => {\n timer = undefined;\n fn();\n }, delayMs);\n },\n\n subscribe(listener) {\n if (disposed) return () => {};\n\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n };\n}\n"],"mappings":";;AAuDA,SAAgB,EAAiB,GAAoD;CACnF,IAAM,oBAAY,IAAI,IAAgB,GAChC,oBAAe,IAAI,IAItB,GACG,IAAa,IAAI,gBAAgB,GACnC,GACA,IAAW;CAEf,OAAO;EACL,cAAc;GACZ,AAEE,OADA,aAAa,CAAK,GACV,KAAA;EAEZ;EAEA,IAAI,iBAAiB;GACnB,OAAO,EAAW;EACpB;EAEA,UAAU;GACR,AAEE,OADA,aAAa,CAAK,GACV,KAAA;GAGV,KAAK,IAAM,KAAU,GAGnB,AAFI,EAAO,cAAc,KAAA,KAAW,aAAa,EAAO,SAAS,GAEjE,EAAO,OAAO,IAAI,EAAoB,CAAC;GAMzC,AAHA,EAAa,MAAM,GACnB,EAAU,MAAM,GAChB,IAAW,IACX,EAAW,MAAM;EACnB;EAEA,MAAM,GAAI;GAMR,OALK,KAEL,aAAa,CAAK,GAClB,IAAQ,KAAA,GAED,EAAG,KALS,QAAQ,QAAQ;EAMrC;EAEA,IAAI,aAAa;GACf,OAAO;EACT;EAEA,IAAI,cAAc;GAChB,OAAO,MAAU,KAAA;EACnB;EAEA,OAAO,GAAuB;GACxB,QAEJ;KAAC,KAAY,GAAM,eAAA,GAAkB;IAErC,KAAK,IAAM,KAAY,GACrB,EAAS;IAGX,KAAK,IAAM,KAAU,GACnB,EAAO,MAAM;GAPsB;EASvC;EAEA,MAAM,GAAQ,GAAW;GAKvB,OAJI,IAAiB,QAAQ,OAAO,IAAI,EAAoB,CAAC,IAEzD,EAAO,IAAU,QAAQ,QAAQ,IAE9B,IAAI,SAAe,GAAS,MAAW;IAC5C,IAAM,IACJ;KACE,aAAa;MACX,AAAI,EAAO,MACL,EAAO,cAAc,KAAA,KAAW,aAAa,EAAO,SAAS,GAEjE,EAAa,OAAO,CAAM,GAC1B,EAAQ;KAEZ;KACA;IACF;IASF,AAPI,MAAc,KAAA,MAChB,EAAO,YAAY,iBAAiB;KAElC,AADA,EAAa,OAAO,CAAM,GAC1B,EAAO,IAAI,EAAmB,CAAS,CAAC;IAC1C,GAAG,CAAS,IAGd,EAAa,IAAI,CAAM;GACzB,CAAC;EACH;EAEA,SAAS,GAAI,GAAS;GAChB,MAEA,KAAO,aAAa,CAAK,GAE7B,IAAQ,iBAAiB;IAEvB,AADA,IAAQ,KAAA,GACR,EAAG;GACL,GAAG,CAAO;EACZ;EAEA,UAAU,GAAU;GAKlB,OAJI,UAAuB,CAAC,KAE5B,EAAU,IAAI,CAAQ,SAET,EAAU,OAAO,CAAQ;EACxC;CACF;AACF"}
|
package/dist/derive.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./core.cjs");function t(t,n){let r=e.createSourceCore(),i=[],a=()=>{i=n(t.current)};a();let o=t.subscribe(()=>{a(),r.notify()});`disposalSignal`in t&&t.disposalSignal instanceof AbortSignal&&t.disposalSignal.addEventListener(`abort`,()=>r.dispose(),{once:!0});let s={get current(){return i},get disposalSignal(){return r.disposalSignal},dispose(){r.isDisposed||(o(),r.dispose())},get disposed(){return r.isDisposed},get meta(){return t.meta},subscribe(e){return r.subscribe(e)},[Symbol.dispose](){s.dispose()}};return s}exports.deriveSource=t;
|
|
2
|
-
//# sourceMappingURL=derive.cjs.map
|
package/dist/derive.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"derive.cjs","names":[],"sources":["../src/derive.ts"],"sourcesContent":["import type { DerivedSource } from './types';\n\nimport { createSourceCore } from './core';\n\n/**\n * Creates a reactive read-only projection derived from a parent source.\n *\n * The derived source mirrors the parent's `meta` and applies `transform` to `current`\n * whenever the parent changes. This is useful for client-side projections (e.g. additional\n * filtering, mapping, or sorting) over a remote source without re-fetching.\n *\n * Call `dispose()` to stop listening to the parent.\n *\n * @example\n * ```ts\n * const source = createRemoteSource<User>({ fetch: fetchUsers, limit: 20 });\n *\n * // Show only active users without re-fetching.\n * const activeUsers = deriveSource(source, (items) => items.filter((u) => u.active));\n *\n * activeUsers.subscribe(() => {\n * console.log(activeUsers.current, activeUsers.meta.totalItems);\n * });\n * ```\n */\nexport function deriveSource<T, U, TMeta>(\n parent: {\n readonly current: readonly T[];\n readonly meta: TMeta;\n subscribe(listener: () => void): () => void;\n },\n transform: (items: readonly T[]) => readonly U[],\n): DerivedSource<U, TMeta> {\n const core = createSourceCore();\n let cachedCurrent: readonly U[] = [];\n\n const applyTransform = () => {\n cachedCurrent = transform(parent.current);\n };\n\n applyTransform();\n\n const unsubscribeParent = parent.subscribe(() => {\n applyTransform();\n core.notify();\n });\n\n if ('disposalSignal' in parent && parent.disposalSignal instanceof AbortSignal) {\n parent.disposalSignal.addEventListener('abort', () => core.dispose(), { once: true });\n }\n\n const source: DerivedSource<U, TMeta> = {\n get current() {\n return cachedCurrent;\n },\n\n get disposalSignal() {\n return core.disposalSignal;\n },\n\n dispose() {\n if (core.isDisposed) return;\n\n unsubscribeParent();\n core.dispose();\n },\n\n get disposed() {\n return core.isDisposed;\n },\n\n get meta() {\n return parent.meta;\n },\n\n subscribe(listener) {\n return core.subscribe(listener);\n },\n\n [Symbol.dispose]() {\n source.dispose();\n },\n };\n\n return source;\n}\n"],"mappings":"8BAyBA,SAAgB,EACd,EAKA,EACyB,CACzB,IAAM,EAAO,EAAA,iBAAiB,EAC1B,EAA8B,CAAC,EAE7B,MAAuB,CAC3B,EAAgB,EAAU,EAAO,OAAO,CAC1C,EAEA,EAAe,EAEf,IAAM,EAAoB,EAAO,cAAgB,CAC/C,EAAe,EACf,EAAK,OAAO,CACd,CAAC,EAEG,mBAAoB,GAAU,EAAO,0BAA0B,aACjE,EAAO,eAAe,iBAAiB,YAAe,EAAK,QAAQ,EAAG,CAAE,KAAM,EAAK,CAAC,EAGtF,IAAM,EAAkC,CACtC,IAAI,SAAU,CACZ,OAAO,CACT,EAEA,IAAI,gBAAiB,CACnB,OAAO,EAAK,cACd,EAEA,SAAU,CACJ,EAAK,aAET,EAAkB,EAClB,EAAK,QAAQ,EACf,EAEA,IAAI,UAAW,CACb,OAAO,EAAK,UACd,EAEA,IAAI,MAAO,CACT,OAAO,EAAO,IAChB,EAEA,UAAU,EAAU,CAClB,OAAO,EAAK,UAAU,CAAQ,CAChC,EAEA,CAAC,OAAO,UAAW,CACjB,EAAO,QAAQ,CACjB,CACF,EAEA,OAAO,CACT"}
|
package/dist/derive.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { DerivedSource } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Creates a reactive read-only projection derived from a parent source.
|
|
4
|
-
*
|
|
5
|
-
* The derived source mirrors the parent's `meta` and applies `transform` to `current`
|
|
6
|
-
* whenever the parent changes. This is useful for client-side projections (e.g. additional
|
|
7
|
-
* filtering, mapping, or sorting) over a remote source without re-fetching.
|
|
8
|
-
*
|
|
9
|
-
* Call `dispose()` to stop listening to the parent.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* const source = createRemoteSource<User>({ fetch: fetchUsers, limit: 20 });
|
|
14
|
-
*
|
|
15
|
-
* // Show only active users without re-fetching.
|
|
16
|
-
* const activeUsers = deriveSource(source, (items) => items.filter((u) => u.active));
|
|
17
|
-
*
|
|
18
|
-
* activeUsers.subscribe(() => {
|
|
19
|
-
* console.log(activeUsers.current, activeUsers.meta.totalItems);
|
|
20
|
-
* });
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export declare function deriveSource<T, U, TMeta>(parent: {
|
|
24
|
-
readonly current: readonly T[];
|
|
25
|
-
readonly meta: TMeta;
|
|
26
|
-
subscribe(listener: () => void): () => void;
|
|
27
|
-
}, transform: (items: readonly T[]) => readonly U[]): DerivedSource<U, TMeta>;
|
|
28
|
-
//# sourceMappingURL=derive.d.ts.map
|
package/dist/derive.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../src/derive.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAI7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EACtC,MAAM,EAAE;IACN,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC7C,EACD,SAAS,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,GAC/C,aAAa,CAAC,CAAC,EAAE,KAAK,CAAC,CAqDzB"}
|
package/dist/derive.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { createSourceCore as e } from "./core.js";
|
|
2
|
-
//#region src/derive.ts
|
|
3
|
-
function t(t, n) {
|
|
4
|
-
let r = e(), i = [], a = () => {
|
|
5
|
-
i = n(t.current);
|
|
6
|
-
};
|
|
7
|
-
a();
|
|
8
|
-
let o = t.subscribe(() => {
|
|
9
|
-
a(), r.notify();
|
|
10
|
-
});
|
|
11
|
-
"disposalSignal" in t && t.disposalSignal instanceof AbortSignal && t.disposalSignal.addEventListener("abort", () => r.dispose(), { once: !0 });
|
|
12
|
-
let s = {
|
|
13
|
-
get current() {
|
|
14
|
-
return i;
|
|
15
|
-
},
|
|
16
|
-
get disposalSignal() {
|
|
17
|
-
return r.disposalSignal;
|
|
18
|
-
},
|
|
19
|
-
dispose() {
|
|
20
|
-
r.isDisposed || (o(), r.dispose());
|
|
21
|
-
},
|
|
22
|
-
get disposed() {
|
|
23
|
-
return r.isDisposed;
|
|
24
|
-
},
|
|
25
|
-
get meta() {
|
|
26
|
-
return t.meta;
|
|
27
|
-
},
|
|
28
|
-
subscribe(e) {
|
|
29
|
-
return r.subscribe(e);
|
|
30
|
-
},
|
|
31
|
-
[Symbol.dispose]() {
|
|
32
|
-
s.dispose();
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
return s;
|
|
36
|
-
}
|
|
37
|
-
//#endregion
|
|
38
|
-
export { t as deriveSource };
|
|
39
|
-
|
|
40
|
-
//# sourceMappingURL=derive.js.map
|
package/dist/derive.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"derive.js","names":[],"sources":["../src/derive.ts"],"sourcesContent":["import type { DerivedSource } from './types';\n\nimport { createSourceCore } from './core';\n\n/**\n * Creates a reactive read-only projection derived from a parent source.\n *\n * The derived source mirrors the parent's `meta` and applies `transform` to `current`\n * whenever the parent changes. This is useful for client-side projections (e.g. additional\n * filtering, mapping, or sorting) over a remote source without re-fetching.\n *\n * Call `dispose()` to stop listening to the parent.\n *\n * @example\n * ```ts\n * const source = createRemoteSource<User>({ fetch: fetchUsers, limit: 20 });\n *\n * // Show only active users without re-fetching.\n * const activeUsers = deriveSource(source, (items) => items.filter((u) => u.active));\n *\n * activeUsers.subscribe(() => {\n * console.log(activeUsers.current, activeUsers.meta.totalItems);\n * });\n * ```\n */\nexport function deriveSource<T, U, TMeta>(\n parent: {\n readonly current: readonly T[];\n readonly meta: TMeta;\n subscribe(listener: () => void): () => void;\n },\n transform: (items: readonly T[]) => readonly U[],\n): DerivedSource<U, TMeta> {\n const core = createSourceCore();\n let cachedCurrent: readonly U[] = [];\n\n const applyTransform = () => {\n cachedCurrent = transform(parent.current);\n };\n\n applyTransform();\n\n const unsubscribeParent = parent.subscribe(() => {\n applyTransform();\n core.notify();\n });\n\n if ('disposalSignal' in parent && parent.disposalSignal instanceof AbortSignal) {\n parent.disposalSignal.addEventListener('abort', () => core.dispose(), { once: true });\n }\n\n const source: DerivedSource<U, TMeta> = {\n get current() {\n return cachedCurrent;\n },\n\n get disposalSignal() {\n return core.disposalSignal;\n },\n\n dispose() {\n if (core.isDisposed) return;\n\n unsubscribeParent();\n core.dispose();\n },\n\n get disposed() {\n return core.isDisposed;\n },\n\n get meta() {\n return parent.meta;\n },\n\n subscribe(listener) {\n return core.subscribe(listener);\n },\n\n [Symbol.dispose]() {\n source.dispose();\n },\n };\n\n return source;\n}\n"],"mappings":";;AAyBA,SAAgB,EACd,GAKA,GACyB;CACzB,IAAM,IAAO,EAAiB,GAC1B,IAA8B,CAAC,GAE7B,UAAuB;EAC3B,IAAgB,EAAU,EAAO,OAAO;CAC1C;CAEA,EAAe;CAEf,IAAM,IAAoB,EAAO,gBAAgB;EAE/C,AADA,EAAe,GACf,EAAK,OAAO;CACd,CAAC;CAED,AAAI,oBAAoB,KAAU,EAAO,0BAA0B,eACjE,EAAO,eAAe,iBAAiB,eAAe,EAAK,QAAQ,GAAG,EAAE,MAAM,GAAK,CAAC;CAGtF,IAAM,IAAkC;EACtC,IAAI,UAAU;GACZ,OAAO;EACT;EAEA,IAAI,iBAAiB;GACnB,OAAO,EAAK;EACd;EAEA,UAAU;GACJ,EAAK,eAET,EAAkB,GAClB,EAAK,QAAQ;EACf;EAEA,IAAI,WAAW;GACb,OAAO,EAAK;EACd;EAEA,IAAI,OAAO;GACT,OAAO,EAAO;EAChB;EAEA,UAAU,GAAU;GAClB,OAAO,EAAK,UAAU,CAAQ;EAChC;EAEA,CAAC,OAAO,WAAW;GACjB,EAAO,QAAQ;EACjB;CACF;CAEA,OAAO;AACT"}
|
package/dist/errors.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
var e=class e extends Error{#e;constructor(e,t={}){super(e,t.cause===void 0?void 0:{cause:t.cause}),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype),this.#e=t}get attempt(){return this.#e.attempt??0}get context(){return this.#e.context}static is(t){return t instanceof e}},t=class extends e{timeoutMs;constructor(e){super(`Source.ready() timed out after ${e}ms`),this.timeoutMs=e}},n=class extends e{constructor(){super(`Source disposed while waiting for ready()`)}};exports.SourceDisposedError=n,exports.SourceTimeoutError=t,exports.SourcererError=e;
|
|
2
|
-
//# sourceMappingURL=errors.cjs.map
|
package/dist/errors.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.cjs","names":["#opts"],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Structured context carried by `SourcererError`.\n * Discriminated on `kind` — narrow with `error.context?.kind` to access typed fields.\n *\n * @example\n * ```ts\n * if (source.meta.error?.context?.kind === 'remote') {\n * console.log('Failed page', source.meta.error.context.page);\n * }\n * ```\n */\nexport type SourcererErrorContext =\n | Readonly<{ kind: 'cursor'; limit: number; search?: string }>\n | Readonly<{ kind: 'infinite'; limit: number; page: number; search?: string }>\n | Readonly<{ kind: 'remote'; limit: number; page: number; search?: string }>;\n\n/**\n * Base class for all sourcerer errors. Catch with `instanceof SourcererError` to handle any\n * sourcerer-originated error regardless of specific subtype.\n *\n * Carries the original cause, structured context, and the attempt number.\n *\n * @example\n * ```ts\n * if (source.meta.error) {\n * console.error(source.meta.error.message, source.meta.error.context);\n * }\n * ```\n */\nexport class SourcererError extends Error {\n readonly #opts: {\n readonly attempt?: number;\n readonly cause?: unknown;\n readonly context?: SourcererErrorContext;\n };\n\n constructor(\n message: string,\n opts: {\n readonly attempt?: number;\n readonly cause?: unknown;\n readonly context?: SourcererErrorContext;\n } = {},\n ) {\n super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n this.#opts = opts;\n }\n\n get attempt(): number {\n return this.#opts.attempt ?? 0;\n }\n\n /** Context bag for the error — safe to log. */\n get context(): SourcererErrorContext | undefined {\n return this.#opts.context;\n }\n\n static is(err: unknown): err is SourcererError {\n return err instanceof SourcererError;\n }\n}\n\n/** Thrown by `ready()` when the source timed out waiting for the first successful fetch. */\nexport class SourceTimeoutError extends SourcererError {\n /** The configured timeout in milliseconds. */\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number) {\n super(`Source.ready() timed out after ${timeoutMs}ms`);\n this.timeoutMs = timeoutMs;\n }\n}\n\n/** Thrown by `ready()` when the source is disposed while waiting. */\nexport class SourceDisposedError extends SourcererError {\n constructor() {\n super('Source disposed while waiting for ready()');\n }\n}\n"],"mappings":"AA6BA,IAAa,EAAb,MAAa,UAAuB,KAAM,CACxC,GAMA,YACE,EACA,EAII,CAAC,EACL,CACA,MAAM,EAAS,EAAK,QAAU,IAAA,GAAoC,IAAA,GAAxB,CAAE,MAAO,EAAK,KAAM,CAAa,EAC3E,KAAK,KAAO,IAAI,OAAO,KACvB,OAAO,eAAe,KAAM,IAAI,OAAO,SAAS,EAChD,KAAKA,GAAQ,CACf,CAEA,IAAI,SAAkB,CACpB,OAAO,KAAKA,GAAM,SAAW,CAC/B,CAGA,IAAI,SAA6C,CAC/C,OAAO,KAAKA,GAAM,OACpB,CAEA,OAAO,GAAG,EAAqC,CAC7C,OAAO,aAAe,CACxB,CACF,EAGa,EAAb,cAAwC,CAAe,CAErD,UAEA,YAAY,EAAmB,CAC7B,MAAM,kCAAkC,EAAU,GAAG,EACrD,KAAK,UAAY,CACnB,CACF,EAGa,EAAb,cAAyC,CAAe,CACtD,aAAc,CACZ,MAAM,2CAA2C,CACnD,CACF"}
|
package/dist/errors.d.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structured context carried by `SourcererError`.
|
|
3
|
-
* Discriminated on `kind` — narrow with `error.context?.kind` to access typed fields.
|
|
4
|
-
*
|
|
5
|
-
* @example
|
|
6
|
-
* ```ts
|
|
7
|
-
* if (source.meta.error?.context?.kind === 'remote') {
|
|
8
|
-
* console.log('Failed page', source.meta.error.context.page);
|
|
9
|
-
* }
|
|
10
|
-
* ```
|
|
11
|
-
*/
|
|
12
|
-
export type SourcererErrorContext = Readonly<{
|
|
13
|
-
kind: 'cursor';
|
|
14
|
-
limit: number;
|
|
15
|
-
search?: string;
|
|
16
|
-
}> | Readonly<{
|
|
17
|
-
kind: 'infinite';
|
|
18
|
-
limit: number;
|
|
19
|
-
page: number;
|
|
20
|
-
search?: string;
|
|
21
|
-
}> | Readonly<{
|
|
22
|
-
kind: 'remote';
|
|
23
|
-
limit: number;
|
|
24
|
-
page: number;
|
|
25
|
-
search?: string;
|
|
26
|
-
}>;
|
|
27
|
-
/**
|
|
28
|
-
* Base class for all sourcerer errors. Catch with `instanceof SourcererError` to handle any
|
|
29
|
-
* sourcerer-originated error regardless of specific subtype.
|
|
30
|
-
*
|
|
31
|
-
* Carries the original cause, structured context, and the attempt number.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```ts
|
|
35
|
-
* if (source.meta.error) {
|
|
36
|
-
* console.error(source.meta.error.message, source.meta.error.context);
|
|
37
|
-
* }
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
export declare class SourcererError extends Error {
|
|
41
|
-
#private;
|
|
42
|
-
constructor(message: string, opts?: {
|
|
43
|
-
readonly attempt?: number;
|
|
44
|
-
readonly cause?: unknown;
|
|
45
|
-
readonly context?: SourcererErrorContext;
|
|
46
|
-
});
|
|
47
|
-
get attempt(): number;
|
|
48
|
-
/** Context bag for the error — safe to log. */
|
|
49
|
-
get context(): SourcererErrorContext | undefined;
|
|
50
|
-
static is(err: unknown): err is SourcererError;
|
|
51
|
-
}
|
|
52
|
-
/** Thrown by `ready()` when the source timed out waiting for the first successful fetch. */
|
|
53
|
-
export declare class SourceTimeoutError extends SourcererError {
|
|
54
|
-
/** The configured timeout in milliseconds. */
|
|
55
|
-
readonly timeoutMs: number;
|
|
56
|
-
constructor(timeoutMs: number);
|
|
57
|
-
}
|
|
58
|
-
/** Thrown by `ready()` when the source is disposed while waiting. */
|
|
59
|
-
export declare class SourceDisposedError extends SourcererError {
|
|
60
|
-
constructor();
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,qBAAqB,GAC7B,QAAQ,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5D,QAAQ,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5E,QAAQ,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE/E;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAe,SAAQ,KAAK;;gBAQrC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;QACJ,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC;KACrC;IAQR,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,+CAA+C;IAC/C,IAAI,OAAO,IAAI,qBAAqB,GAAG,SAAS,CAE/C;IAED,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,cAAc;CAG/C;AAED,4FAA4F;AAC5F,qBAAa,kBAAmB,SAAQ,cAAc;IACpD,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,SAAS,EAAE,MAAM;CAI9B;AAED,qEAAqE;AACrE,qBAAa,mBAAoB,SAAQ,cAAc;;CAItD"}
|
package/dist/errors.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
//#region src/errors.ts
|
|
2
|
-
var e = class e extends Error {
|
|
3
|
-
#e;
|
|
4
|
-
constructor(e, t = {}) {
|
|
5
|
-
super(e, t.cause === void 0 ? void 0 : { cause: t.cause }), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype), this.#e = t;
|
|
6
|
-
}
|
|
7
|
-
get attempt() {
|
|
8
|
-
return this.#e.attempt ?? 0;
|
|
9
|
-
}
|
|
10
|
-
get context() {
|
|
11
|
-
return this.#e.context;
|
|
12
|
-
}
|
|
13
|
-
static is(t) {
|
|
14
|
-
return t instanceof e;
|
|
15
|
-
}
|
|
16
|
-
}, t = class extends e {
|
|
17
|
-
timeoutMs;
|
|
18
|
-
constructor(e) {
|
|
19
|
-
super(`Source.ready() timed out after ${e}ms`), this.timeoutMs = e;
|
|
20
|
-
}
|
|
21
|
-
}, n = class extends e {
|
|
22
|
-
constructor() {
|
|
23
|
-
super("Source disposed while waiting for ready()");
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
//#endregion
|
|
27
|
-
export { n as SourceDisposedError, t as SourceTimeoutError, e as SourcererError };
|
|
28
|
-
|
|
29
|
-
//# sourceMappingURL=errors.js.map
|
package/dist/errors.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":["#opts"],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Structured context carried by `SourcererError`.\n * Discriminated on `kind` — narrow with `error.context?.kind` to access typed fields.\n *\n * @example\n * ```ts\n * if (source.meta.error?.context?.kind === 'remote') {\n * console.log('Failed page', source.meta.error.context.page);\n * }\n * ```\n */\nexport type SourcererErrorContext =\n | Readonly<{ kind: 'cursor'; limit: number; search?: string }>\n | Readonly<{ kind: 'infinite'; limit: number; page: number; search?: string }>\n | Readonly<{ kind: 'remote'; limit: number; page: number; search?: string }>;\n\n/**\n * Base class for all sourcerer errors. Catch with `instanceof SourcererError` to handle any\n * sourcerer-originated error regardless of specific subtype.\n *\n * Carries the original cause, structured context, and the attempt number.\n *\n * @example\n * ```ts\n * if (source.meta.error) {\n * console.error(source.meta.error.message, source.meta.error.context);\n * }\n * ```\n */\nexport class SourcererError extends Error {\n readonly #opts: {\n readonly attempt?: number;\n readonly cause?: unknown;\n readonly context?: SourcererErrorContext;\n };\n\n constructor(\n message: string,\n opts: {\n readonly attempt?: number;\n readonly cause?: unknown;\n readonly context?: SourcererErrorContext;\n } = {},\n ) {\n super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n this.#opts = opts;\n }\n\n get attempt(): number {\n return this.#opts.attempt ?? 0;\n }\n\n /** Context bag for the error — safe to log. */\n get context(): SourcererErrorContext | undefined {\n return this.#opts.context;\n }\n\n static is(err: unknown): err is SourcererError {\n return err instanceof SourcererError;\n }\n}\n\n/** Thrown by `ready()` when the source timed out waiting for the first successful fetch. */\nexport class SourceTimeoutError extends SourcererError {\n /** The configured timeout in milliseconds. */\n readonly timeoutMs: number;\n\n constructor(timeoutMs: number) {\n super(`Source.ready() timed out after ${timeoutMs}ms`);\n this.timeoutMs = timeoutMs;\n }\n}\n\n/** Thrown by `ready()` when the source is disposed while waiting. */\nexport class SourceDisposedError extends SourcererError {\n constructor() {\n super('Source disposed while waiting for ready()');\n }\n}\n"],"mappings":";AA6BA,IAAa,IAAb,MAAa,UAAuB,MAAM;CACxC;CAMA,YACE,GACA,IAII,CAAC,GACL;EAIA,AAHA,MAAM,GAAS,EAAK,UAAU,KAAA,IAAoC,KAAA,IAAxB,EAAE,OAAO,EAAK,MAAM,CAAa,GAC3E,KAAK,OAAO,IAAI,OAAO,MACvB,OAAO,eAAe,MAAM,IAAI,OAAO,SAAS,GAChD,KAAKA,KAAQ;CACf;CAEA,IAAI,UAAkB;EACpB,OAAO,KAAKA,GAAM,WAAW;CAC/B;CAGA,IAAI,UAA6C;EAC/C,OAAO,KAAKA,GAAM;CACpB;CAEA,OAAO,GAAG,GAAqC;EAC7C,OAAO,aAAe;CACxB;AACF,GAGa,IAAb,cAAwC,EAAe;CAErD;CAEA,YAAY,GAAmB;EAE7B,AADA,MAAM,kCAAkC,EAAU,GAAG,GACrD,KAAK,YAAY;CACnB;AACF,GAGa,IAAb,cAAyC,EAAe;CACtD,cAAc;EACZ,MAAM,2CAA2C;CACnD;AACF"}
|
package/dist/fetchManager.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
function e(e){let t=new Map,n=``,r=0;return{dispose(){for(let e of t.values())e.controller.abort();t.clear(),r=0},get pendingCount(){return r},async run(i,a,o){let s=e(i);n=s;for(let[e,n]of t)e!==s&&(n.controller.abort(),t.delete(e));let c=t.get(s);if(c){r++,o();try{await c.promise}finally{r--,o()}return}let l=new AbortController;r++,o();let u=a(i,l.signal,()=>s===n).finally(()=>{t.delete(s),r--,o()});t.set(s,{controller:l,promise:u}),await u}}}exports.createFetchManager=e;
|
|
2
|
-
//# sourceMappingURL=fetchManager.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fetchManager.cjs","names":[],"sources":["../src/fetchManager.ts"],"sourcesContent":["/**\n * Internal fetch deduplication and in-flight request manager.\n *\n * - Deduplicates identical concurrent queries: joiners await the existing in-flight promise.\n * - Aborts superseded in-flight requests when a new query key arrives.\n * - Tracks pending count so callers can reflect loading state.\n *\n * Not exported from the public API — consumed via `asyncSource.ts` by remoteSource,\n * cursorSource, and infiniteSource.\n */\n\ntype InFlightEntry = { controller: AbortController; promise: Promise<void> };\n\nexport type FetchManager<TQuery> = {\n /** Abort all in-flight requests and clear the registry. */\n dispose(): void;\n /** Number of currently in-flight requests (including joiners). */\n readonly pendingCount: number;\n /**\n * Run a fetch for query `q`.\n *\n * - If an identical key is already in-flight, joins as a waiter.\n * - If a different key is in-flight, aborts it and starts a new request.\n * - Calls `onPendingChange()` whenever `pendingCount` changes.\n * - Passes an `isLatest()` predicate to `execute` so it can skip stale responses.\n */\n run(\n q: TQuery,\n execute: (q: TQuery, signal: AbortSignal, isLatest: () => boolean) => Promise<void>,\n onPendingChange: () => void,\n ): Promise<void>;\n};\n\nexport function createFetchManager<TQuery>(keyOf: (q: TQuery) => string): FetchManager<TQuery> {\n const inflight = new Map<string, InFlightEntry>();\n let latestKey = '';\n let _pendingCount = 0;\n\n return {\n dispose() {\n for (const entry of inflight.values()) {\n entry.controller.abort();\n }\n\n inflight.clear();\n _pendingCount = 0;\n },\n\n get pendingCount() {\n return _pendingCount;\n },\n\n async run(q, execute, onPendingChange) {\n const key = keyOf(q);\n\n latestKey = key;\n\n // Abort and remove all superseded in-flight requests.\n for (const [k, entry] of inflight) {\n if (k !== key) {\n entry.controller.abort();\n inflight.delete(k);\n }\n }\n\n // Join an identical in-flight request rather than issuing a duplicate.\n const existing = inflight.get(key);\n\n if (existing) {\n _pendingCount++;\n onPendingChange();\n\n try {\n await existing.promise;\n } finally {\n _pendingCount--;\n onPendingChange();\n }\n\n return;\n }\n\n // New request.\n const controller = new AbortController();\n\n _pendingCount++;\n onPendingChange();\n\n const promise = execute(q, controller.signal, () => key === latestKey).finally(() => {\n inflight.delete(key);\n _pendingCount--;\n onPendingChange();\n });\n\n inflight.set(key, { controller, promise });\n await promise;\n },\n };\n}\n"],"mappings":"AAiCA,SAAgB,EAA2B,EAAoD,CAC7F,IAAM,EAAW,IAAI,IACjB,EAAY,GACZ,EAAgB,EAEpB,MAAO,CACL,SAAU,CACR,IAAK,IAAM,KAAS,EAAS,OAAO,EAClC,EAAM,WAAW,MAAM,EAGzB,EAAS,MAAM,EACf,EAAgB,CAClB,EAEA,IAAI,cAAe,CACjB,OAAO,CACT,EAEA,MAAM,IAAI,EAAG,EAAS,EAAiB,CACrC,IAAM,EAAM,EAAM,CAAC,EAEnB,EAAY,EAGZ,IAAK,GAAM,CAAC,EAAG,KAAU,EACnB,IAAM,IACR,EAAM,WAAW,MAAM,EACvB,EAAS,OAAO,CAAC,GAKrB,IAAM,EAAW,EAAS,IAAI,CAAG,EAEjC,GAAI,EAAU,CACZ,IACA,EAAgB,EAEhB,GAAI,CACF,MAAM,EAAS,OACjB,QAAU,CACR,IACA,EAAgB,CAClB,CAEA,MACF,CAGA,IAAM,EAAa,IAAI,gBAEvB,IACA,EAAgB,EAEhB,IAAM,EAAU,EAAQ,EAAG,EAAW,WAAc,IAAQ,CAAS,CAAC,CAAC,YAAc,CACnF,EAAS,OAAO,CAAG,EACnB,IACA,EAAgB,CAClB,CAAC,EAED,EAAS,IAAI,EAAK,CAAE,aAAY,SAAQ,CAAC,EACzC,MAAM,CACR,CACF,CACF"}
|