@vielzeug/sourcerer 1.1.0 → 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 +1 -1
- package/dist/_dev.cjs.map +1 -1
- package/dist/_dev.js +2 -8
- package/dist/_dev.js.map +1 -1
- 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 +76 -109
- package/dist/cursorSource.js.map +1 -1
- package/dist/devtools.cjs +1 -1
- package/dist/devtools.cjs.map +1 -1
- package/dist/devtools.d.ts +4 -50
- package/dist/devtools.d.ts.map +1 -1
- package/dist/devtools.js +3 -15
- package/dist/devtools.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -13
- 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 +71 -106
- 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 -104
- 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 -355
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -7
- package/dist/_utils.cjs +0 -2
- package/dist/_utils.cjs.map +0 -1
- package/dist/_utils.d.ts +0 -28
- package/dist/_utils.d.ts.map +0 -1
- package/dist/_utils.js +0 -20
- package/dist/_utils.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 -38
- 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 -71
- 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 -29
- package/dist/derive.d.ts.map +0 -1
- package/dist/derive.js +0 -38
- 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 -40
- 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 -35
- package/dist/merge.d.ts.map +0 -1
- package/dist/merge.js +0 -41
- 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/README.md
CHANGED
|
@@ -1,98 +1,43 @@
|
|
|
1
1
|
# @vielzeug/sourcerer
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/@vielzeug/sourcerer) [](https://opensource.org/licenses/MIT)
|
|
6
|
-
|
|
7
|
-
`@vielzeug/sourcerer` is part of Vielzeug and ships as a TypeScript package with ESM+CJS output.
|
|
8
|
-
|
|
9
|
-
## Installation
|
|
3
|
+
Atomic reactive sources for local, numbered-page, cursor, and infinite collections.
|
|
10
4
|
|
|
11
5
|
```sh
|
|
12
6
|
pnpm add @vielzeug/sourcerer
|
|
13
|
-
npm install @vielzeug/sourcerer
|
|
14
|
-
yarn add @vielzeug/sourcerer
|
|
15
7
|
```
|
|
16
8
|
|
|
17
|
-
## Quick Start
|
|
18
|
-
|
|
19
9
|
```ts
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
const source = createLocalSource(
|
|
23
|
-
[
|
|
24
|
-
{ id: 1, name: 'Ada' },
|
|
25
|
-
{ id: 2, name: 'Grace' },
|
|
26
|
-
{ id: 3, name: 'Linus' },
|
|
27
|
-
],
|
|
28
|
-
{ limit: 2 },
|
|
29
|
-
);
|
|
10
|
+
import { createPageSource } from '@vielzeug/sourcerer';
|
|
30
11
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
import { createRemoteSource } from '@vielzeug/sourcerer';
|
|
38
|
-
|
|
39
|
-
const source = createRemoteSource({
|
|
40
|
-
fetch: async ({ limit, page, search }, signal) => {
|
|
41
|
-
const res = await fetch(`/api/users?page=${page}&limit=${limit}&q=${search ?? ''}`, { signal });
|
|
42
|
-
return res.json(); // { items: User[], total: number }
|
|
43
|
-
},
|
|
44
|
-
limit: 20,
|
|
12
|
+
const users = createPageSource({
|
|
13
|
+
autoStart: false,
|
|
14
|
+
initialQuery: { pageSize: 20 },
|
|
15
|
+
load: async () => ({ data: [{ id: 1, name: 'Ada' }], total: 1 }),
|
|
45
16
|
});
|
|
46
17
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
console.log(
|
|
18
|
+
await users.reload();
|
|
19
|
+
console.log(users.snapshot.data);
|
|
20
|
+
console.log(users.snapshot.pagination.total);
|
|
21
|
+
users.dispose();
|
|
50
22
|
```
|
|
51
23
|
|
|
52
|
-
|
|
24
|
+
Every source exposes an atomic `snapshot` and `subscribe(listener)`. `setQuery()` changes query fields; `page.*` changes page index. While newer work is active, `pendingQuery` records it without mixing it into loaded data. Page sources cancel superseded requests and preserve successful data on background refresh failures.
|
|
53
25
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
26
|
+
| Factory | Purpose |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `createLocalSource()` | Synchronous in-memory collection |
|
|
29
|
+
| `createPageSource()` | Numbered async pages |
|
|
30
|
+
| `createCursorSource()` | Cursor-based async pages |
|
|
31
|
+
| `createInfiniteSource()` | Appended async pages |
|
|
58
32
|
|
|
59
|
-
|
|
60
|
-
searchFn: searchBy([(u) => u.name, (u) => u.email]),
|
|
61
|
-
});
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Sources Overview
|
|
65
|
-
|
|
66
|
-
| Factory | Data model | Navigation |
|
|
67
|
-
| ------------------------ | ------------------ | ------------------------- |
|
|
68
|
-
| `createLocalSource()` | In-memory array | Page number |
|
|
69
|
-
| `createRemoteSource()` | Async server fetch | Page number |
|
|
70
|
-
| `createCursorSource()` | Async server fetch | Cursor tokens (next/prev) |
|
|
71
|
-
| `createInfiniteSource()` | Async server fetch | Append (load more) |
|
|
72
|
-
|
|
73
|
-
## Error Handling
|
|
74
|
-
|
|
75
|
-
All async sources expose `meta.error` as a typed `SourceError | null` — not a plain string.
|
|
76
|
-
`SourceError` extends `Error` and carries structured context:
|
|
77
|
-
|
|
78
|
-
```ts
|
|
79
|
-
import { sourceState } from '@vielzeug/sourcerer';
|
|
80
|
-
|
|
81
|
-
const state = sourceState(source);
|
|
82
|
-
if (state.status === 'error') {
|
|
83
|
-
console.error(state.error.message); // human-readable message
|
|
84
|
-
console.error(state.error.cause); // original thrown value
|
|
85
|
-
console.error(state.error.context); // structured context bag (query fields, kind, etc.)
|
|
86
|
-
}
|
|
87
|
-
```
|
|
33
|
+
Sourcerer manages collection state and request succession. Configure retries, cache, polling, URL serialization, and optimistic mutation with your transport or application layer.
|
|
88
34
|
|
|
89
35
|
## Documentation
|
|
90
36
|
|
|
91
37
|
- [Overview](https://vielzeug.dev/sourcerer/)
|
|
92
38
|
- [Usage Guide](https://vielzeug.dev/sourcerer/usage)
|
|
93
39
|
- [API Reference](https://vielzeug.dev/sourcerer/api)
|
|
94
|
-
- [Examples](https://vielzeug.dev/sourcerer/examples)
|
|
95
40
|
|
|
96
41
|
## License
|
|
97
42
|
|
|
98
|
-
MIT ©
|
|
43
|
+
MIT © Helmuth Saatkamp — part of Vielzeug.
|
package/dist/_dev.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
function e(e){}exports.warn=e;
|
|
2
2
|
//# sourceMappingURL=_dev.cjs.map
|
package/dist/_dev.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __SOURCERER_PROD__?: boolean }).__SOURCERER_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/sourcerer] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __SOURCERER_PROD__?: boolean }).__SOURCERER_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/sourcerer] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":"AAGA,SAAgB,EAAK,EAAmB,CAExC"}
|
package/dist/_dev.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
//#region src/_dev.ts
|
|
2
|
-
|
|
3
|
-
function t(t) {
|
|
4
|
-
e && console.warn(`[@vielzeug/sourcerer] ${t}`);
|
|
5
|
-
}
|
|
6
|
-
function n(t) {
|
|
7
|
-
e && t();
|
|
8
|
-
}
|
|
2
|
+
function e(e) {}
|
|
9
3
|
//#endregion
|
|
10
|
-
export {
|
|
4
|
+
export { e as warn };
|
|
11
5
|
|
|
12
6
|
//# sourceMappingURL=_dev.js.map
|
package/dist/_dev.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __SOURCERER_PROD__?: boolean }).__SOURCERER_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/sourcerer] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __SOURCERER_PROD__?: boolean }).__SOURCERER_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/sourcerer] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":";AAGA,SAAgB,EAAK,GAAmB,CAExC"}
|
package/dist/asyncSource.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./
|
|
1
|
+
const e=require("./requestController.cjs"),t=require("./sourceStore.cjs");function n(n){let r=t.createSourceStore(n),i=e.createRequestController();return{get disposalSignal(){return r.disposalSignal},dispose(){i.dispose(),r.dispose()},get disposed(){return r.disposed},async fetch(t){if(r.disposed)return;let n=i.begin(),a=r.value;r.set(t.pending(a));let o;try{o=t.success(await t.load(n.signal))}catch(i){if(!n.isCurrent()||r.disposed)return;let o=e.toError(i);throw r.set(t.failure(a,o)),o}finally{n.finish()}!n.isCurrent()||r.disposed||r.set(o)},get snapshot(){return r.value},subscribe:r.subscribe}}exports.createAsyncSource=n;
|
|
2
2
|
//# sourceMappingURL=asyncSource.cjs.map
|
package/dist/asyncSource.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asyncSource.cjs","names":[],"sources":["../src/asyncSource.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"asyncSource.cjs","names":[],"sources":["../src/asyncSource.ts"],"sourcesContent":["import type { AnyPagination, SourceSnapshot } from './types';\n\nimport { createRequestController, toError } from './requestController';\nimport { createSourceStore } from './sourceStore';\n\ntype AsyncSource<T, TQuery, TPagination extends AnyPagination> = Readonly<{\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n fetch<TResult>(options: {\n failure(previous: SourceSnapshot<T, TQuery, TPagination>, error: Error): SourceSnapshot<T, TQuery, TPagination>;\n load(signal: AbortSignal): Promise<TResult>;\n pending(previous: SourceSnapshot<T, TQuery, TPagination>): SourceSnapshot<T, TQuery, TPagination>;\n success(result: TResult): SourceSnapshot<T, TQuery, TPagination>;\n }): Promise<void>;\n readonly snapshot: SourceSnapshot<T, TQuery, TPagination>;\n subscribe(listener: (snapshot: SourceSnapshot<T, TQuery, TPagination>) => void): () => void;\n}>;\n\n/** Request failures and observer failures stay separate so observers cannot corrupt source state. */\nexport function createAsyncSource<T, TQuery, TPagination extends AnyPagination>(\n initial: SourceSnapshot<T, TQuery, TPagination>,\n): AsyncSource<T, TQuery, TPagination> {\n const store = createSourceStore(initial);\n const requests = createRequestController();\n\n return {\n get disposalSignal() {\n return store.disposalSignal;\n },\n\n dispose() {\n requests.dispose();\n store.dispose();\n },\n\n get disposed() {\n return store.disposed;\n },\n\n async fetch(options) {\n if (store.disposed) return;\n\n const request = requests.begin();\n const previous = store.value;\n\n store.set(options.pending(previous));\n\n let next: SourceSnapshot<T, TQuery, TPagination>;\n\n try {\n next = options.success(await options.load(request.signal));\n } catch (reason: unknown) {\n if (!request.isCurrent() || store.disposed) return;\n\n const error = toError(reason);\n\n store.set(options.failure(previous, error));\n throw error;\n } finally {\n request.finish();\n }\n\n if (!request.isCurrent() || store.disposed) return;\n\n store.set(next);\n },\n\n get snapshot() {\n return store.value;\n },\n\n subscribe: store.subscribe,\n };\n}\n"],"mappings":"0EAoBA,SAAgB,EACd,EACqC,CACrC,IAAM,EAAQ,EAAA,kBAAkB,CAAO,EACjC,EAAW,EAAA,wBAAwB,EAEzC,MAAO,CACL,IAAI,gBAAiB,CACnB,OAAO,EAAM,cACf,EAEA,SAAU,CACR,EAAS,QAAQ,EACjB,EAAM,QAAQ,CAChB,EAEA,IAAI,UAAW,CACb,OAAO,EAAM,QACf,EAEA,MAAM,MAAM,EAAS,CACnB,GAAI,EAAM,SAAU,OAEpB,IAAM,EAAU,EAAS,MAAM,EACzB,EAAW,EAAM,MAEvB,EAAM,IAAI,EAAQ,QAAQ,CAAQ,CAAC,EAEnC,IAAI,EAEJ,GAAI,CACF,EAAO,EAAQ,QAAQ,MAAM,EAAQ,KAAK,EAAQ,MAAM,CAAC,CAC3D,OAAS,EAAiB,CACxB,GAAI,CAAC,EAAQ,UAAU,GAAK,EAAM,SAAU,OAE5C,IAAM,EAAQ,EAAA,QAAQ,CAAM,EAG5B,MADA,EAAM,IAAI,EAAQ,QAAQ,EAAU,CAAK,CAAC,EACpC,CACR,QAAU,CACR,EAAQ,OAAO,CACjB,CAEI,CAAC,EAAQ,UAAU,GAAK,EAAM,UAElC,EAAM,IAAI,CAAI,CAChB,EAEA,IAAI,UAAW,CACb,OAAO,EAAM,KACf,EAEA,UAAW,EAAM,SACnB,CACF"}
|
package/dist/asyncSource.d.ts
CHANGED
|
@@ -1,62 +1,18 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
* Configuration subset common to all async source types.
|
|
4
|
-
* Extracted to eliminate duplication across remote, cursor, and infinite sources.
|
|
5
|
-
*/
|
|
6
|
-
export type AsyncSourceConfig<TQuery> = Readonly<{
|
|
7
|
-
autoFetch?: boolean;
|
|
8
|
-
debounceMs?: number;
|
|
9
|
-
queryKey?: (q: TQuery) => string;
|
|
10
|
-
refreshInterval?: number;
|
|
11
|
-
retry?: {
|
|
12
|
-
attempts?: number;
|
|
13
|
-
delay?: (attempt: number) => number;
|
|
14
|
-
};
|
|
15
|
-
}>;
|
|
16
|
-
export type FetchExecutor<TQuery> = (q: TQuery, signal: AbortSignal, isLatest: () => boolean) => Promise<void>;
|
|
17
|
-
export type AsyncSourceInfra<TQuery> = {
|
|
18
|
-
[Symbol.dispose](): void;
|
|
19
|
-
/** `true` when `autoFetch !== false`. */
|
|
20
|
-
readonly autoFetch: boolean;
|
|
21
|
-
/** The underlying SourceCore — use directly for subscribe, schedule, cancelTimer, flush, isScheduled. */
|
|
22
|
-
readonly core: SourceCore;
|
|
23
|
-
/** Debounce delay in ms. */
|
|
24
|
-
readonly debounceMs: number;
|
|
25
|
-
/** `AbortSignal` aborted when `dispose()` is called. */
|
|
1
|
+
import type { AnyPagination, SourceSnapshot } from './types';
|
|
2
|
+
type AsyncSource<T, TQuery, TPagination extends AnyPagination> = Readonly<{
|
|
26
3
|
readonly disposalSignal: AbortSignal;
|
|
27
|
-
/** Dispose lifecycle: cancel refresh interval, dispose FM, dispose core. Idempotent. */
|
|
28
4
|
dispose(): void;
|
|
29
|
-
/** `true` after `dispose()`. */
|
|
30
5
|
readonly disposed: boolean;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
readonly retryDelay: (attempt: number) => number;
|
|
44
|
-
/**
|
|
45
|
-
* Wire the refresh interval. Must be called once after construction, passing
|
|
46
|
-
* the source-level `doUpdate` callback. Does nothing when `refreshInterval`
|
|
47
|
-
* is unset or ≤ 0.
|
|
48
|
-
*/
|
|
49
|
-
startRefreshInterval(doUpdate: () => void): void;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Creates the shared infrastructure used by `createRemoteSource`, `createCursorSource`,
|
|
53
|
-
* and `createInfiniteSource`. Eliminates duplication of lifecycle, config-defaults,
|
|
54
|
-
* fetch-manager setup, dispose, ready, flush, and refresh-interval logic.
|
|
55
|
-
*
|
|
56
|
-
* Each source factory composes this and adds its own query state, meta shape, and
|
|
57
|
-
* source-specific methods. Pass `onBeforeNotify` to run `refreshMeta()` before
|
|
58
|
-
* subscribers observe each notification — this eliminates the need for a parallel
|
|
59
|
-
* listeners Set in each source factory.
|
|
60
|
-
*/
|
|
61
|
-
export declare function createAsyncSource<TQuery>(cfg: AsyncSourceConfig<TQuery>, keyOf?: (q: TQuery) => string, onBeforeNotify?: () => void): AsyncSourceInfra<TQuery>;
|
|
6
|
+
fetch<TResult>(options: {
|
|
7
|
+
failure(previous: SourceSnapshot<T, TQuery, TPagination>, error: Error): SourceSnapshot<T, TQuery, TPagination>;
|
|
8
|
+
load(signal: AbortSignal): Promise<TResult>;
|
|
9
|
+
pending(previous: SourceSnapshot<T, TQuery, TPagination>): SourceSnapshot<T, TQuery, TPagination>;
|
|
10
|
+
success(result: TResult): SourceSnapshot<T, TQuery, TPagination>;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
readonly snapshot: SourceSnapshot<T, TQuery, TPagination>;
|
|
13
|
+
subscribe(listener: (snapshot: SourceSnapshot<T, TQuery, TPagination>) => void): () => void;
|
|
14
|
+
}>;
|
|
15
|
+
/** Request failures and observer failures stay separate so observers cannot corrupt source state. */
|
|
16
|
+
export declare function createAsyncSource<T, TQuery, TPagination extends AnyPagination>(initial: SourceSnapshot<T, TQuery, TPagination>): AsyncSource<T, TQuery, TPagination>;
|
|
17
|
+
export {};
|
|
62
18
|
//# sourceMappingURL=asyncSource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asyncSource.d.ts","sourceRoot":"","sources":["../src/asyncSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"asyncSource.d.ts","sourceRoot":"","sources":["../src/asyncSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAK7D,KAAK,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,SAAS,aAAa,IAAI,QAAQ,CAAC;IACxE,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;IACrC,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE;QACtB,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;QAChH,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5C,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,GAAG,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;QAClG,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;KAClE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC1D,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC7F,CAAC,CAAC;AAEH,qGAAqG;AACrG,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,SAAS,aAAa,EAC5E,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,GAC9C,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAoDrC"}
|
package/dist/asyncSource.js
CHANGED
|
@@ -1,40 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { createFetchManager as r } from "./fetchManager.js";
|
|
1
|
+
import { createRequestController as e, toError as t } from "./requestController.js";
|
|
2
|
+
import { createSourceStore as n } from "./sourceStore.js";
|
|
4
3
|
//#region src/asyncSource.ts
|
|
5
|
-
function
|
|
6
|
-
let
|
|
7
|
-
s.isDisposed || (p !== void 0 && (clearInterval(p), p = void 0), c.dispose(), s.dispose());
|
|
8
|
-
};
|
|
4
|
+
function r(r) {
|
|
5
|
+
let i = n(r), a = e();
|
|
9
6
|
return {
|
|
10
|
-
autoFetch: f,
|
|
11
|
-
core: s,
|
|
12
|
-
debounceMs: l,
|
|
13
7
|
get disposalSignal() {
|
|
14
|
-
return
|
|
8
|
+
return i.disposalSignal;
|
|
15
9
|
},
|
|
16
|
-
dispose
|
|
17
|
-
|
|
18
|
-
return s.isDisposed;
|
|
10
|
+
dispose() {
|
|
11
|
+
a.dispose(), i.dispose();
|
|
19
12
|
},
|
|
20
|
-
|
|
21
|
-
return
|
|
13
|
+
get disposed() {
|
|
14
|
+
return i.disposed;
|
|
22
15
|
},
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
async fetch(e) {
|
|
17
|
+
if (i.disposed) return;
|
|
18
|
+
let n = a.begin(), r = i.value;
|
|
19
|
+
i.set(e.pending(r));
|
|
20
|
+
let o;
|
|
21
|
+
try {
|
|
22
|
+
o = e.success(await e.load(n.signal));
|
|
23
|
+
} catch (a) {
|
|
24
|
+
if (!n.isCurrent() || i.disposed) return;
|
|
25
|
+
let o = t(a);
|
|
26
|
+
throw i.set(e.failure(r, o)), o;
|
|
27
|
+
} finally {
|
|
28
|
+
n.finish();
|
|
29
|
+
}
|
|
30
|
+
!n.isCurrent() || i.disposed || i.set(o);
|
|
26
31
|
},
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
startRefreshInterval(e) {
|
|
30
|
-
i.refreshInterval !== void 0 && i.refreshInterval > 0 && (p = setInterval(() => {
|
|
31
|
-
s.isDisposed || e();
|
|
32
|
-
}, i.refreshInterval));
|
|
32
|
+
get snapshot() {
|
|
33
|
+
return i.value;
|
|
33
34
|
},
|
|
34
|
-
|
|
35
|
+
subscribe: i.subscribe
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
//#endregion
|
|
38
|
-
export {
|
|
39
|
+
export { r as createAsyncSource };
|
|
39
40
|
|
|
40
41
|
//# sourceMappingURL=asyncSource.js.map
|
package/dist/asyncSource.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asyncSource.js","names":[],"sources":["../src/asyncSource.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"asyncSource.js","names":[],"sources":["../src/asyncSource.ts"],"sourcesContent":["import type { AnyPagination, SourceSnapshot } from './types';\n\nimport { createRequestController, toError } from './requestController';\nimport { createSourceStore } from './sourceStore';\n\ntype AsyncSource<T, TQuery, TPagination extends AnyPagination> = Readonly<{\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n fetch<TResult>(options: {\n failure(previous: SourceSnapshot<T, TQuery, TPagination>, error: Error): SourceSnapshot<T, TQuery, TPagination>;\n load(signal: AbortSignal): Promise<TResult>;\n pending(previous: SourceSnapshot<T, TQuery, TPagination>): SourceSnapshot<T, TQuery, TPagination>;\n success(result: TResult): SourceSnapshot<T, TQuery, TPagination>;\n }): Promise<void>;\n readonly snapshot: SourceSnapshot<T, TQuery, TPagination>;\n subscribe(listener: (snapshot: SourceSnapshot<T, TQuery, TPagination>) => void): () => void;\n}>;\n\n/** Request failures and observer failures stay separate so observers cannot corrupt source state. */\nexport function createAsyncSource<T, TQuery, TPagination extends AnyPagination>(\n initial: SourceSnapshot<T, TQuery, TPagination>,\n): AsyncSource<T, TQuery, TPagination> {\n const store = createSourceStore(initial);\n const requests = createRequestController();\n\n return {\n get disposalSignal() {\n return store.disposalSignal;\n },\n\n dispose() {\n requests.dispose();\n store.dispose();\n },\n\n get disposed() {\n return store.disposed;\n },\n\n async fetch(options) {\n if (store.disposed) return;\n\n const request = requests.begin();\n const previous = store.value;\n\n store.set(options.pending(previous));\n\n let next: SourceSnapshot<T, TQuery, TPagination>;\n\n try {\n next = options.success(await options.load(request.signal));\n } catch (reason: unknown) {\n if (!request.isCurrent() || store.disposed) return;\n\n const error = toError(reason);\n\n store.set(options.failure(previous, error));\n throw error;\n } finally {\n request.finish();\n }\n\n if (!request.isCurrent() || store.disposed) return;\n\n store.set(next);\n },\n\n get snapshot() {\n return store.value;\n },\n\n subscribe: store.subscribe,\n };\n}\n"],"mappings":";;;AAoBA,SAAgB,EACd,GACqC;CACrC,IAAM,IAAQ,EAAkB,CAAO,GACjC,IAAW,EAAwB;CAEzC,OAAO;EACL,IAAI,iBAAiB;GACnB,OAAO,EAAM;EACf;EAEA,UAAU;GAER,AADA,EAAS,QAAQ,GACjB,EAAM,QAAQ;EAChB;EAEA,IAAI,WAAW;GACb,OAAO,EAAM;EACf;EAEA,MAAM,MAAM,GAAS;GACnB,IAAI,EAAM,UAAU;GAEpB,IAAM,IAAU,EAAS,MAAM,GACzB,IAAW,EAAM;GAEvB,EAAM,IAAI,EAAQ,QAAQ,CAAQ,CAAC;GAEnC,IAAI;GAEJ,IAAI;IACF,IAAO,EAAQ,QAAQ,MAAM,EAAQ,KAAK,EAAQ,MAAM,CAAC;GAC3D,SAAS,GAAiB;IACxB,IAAI,CAAC,EAAQ,UAAU,KAAK,EAAM,UAAU;IAE5C,IAAM,IAAQ,EAAQ,CAAM;IAG5B,MADA,EAAM,IAAI,EAAQ,QAAQ,GAAU,CAAK,CAAC,GACpC;GACR,UAAU;IACR,EAAQ,OAAO;GACjB;GAEI,CAAC,EAAQ,UAAU,KAAK,EAAM,YAElC,EAAM,IAAI,CAAI;EAChB;EAEA,IAAI,WAAW;GACb,OAAO,EAAM;EACf;EAEA,WAAW,EAAM;CACnB;AACF"}
|
package/dist/cursorSource.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./
|
|
1
|
+
const e=require("./_dev.cjs"),t=require("./asyncSource.cjs"),n=require("./pagination.cjs");var r=e=>({hasNext:e?.nextCursor!==void 0,hasPrevious:e?.previousCursor!==void 0,kind:`cursor`,...e?.nextCursor!==void 0&&{nextCursor:e.nextCursor},...e?.previousCursor!==void 0&&{previousCursor:e.previousCursor},...e?.total!==void 0&&{total:n.totalItems(e.total)}}),i=(e,t={})=>{if(t.after!==void 0&&t.before!==void 0)throw RangeError(`Cursor query cannot include both after and before`);let r=t.pageSize!==void 0||t.search!==void 0,i=r||`before`in t?void 0:`after`in t?t.after:e.after,a=r||`after`in t?void 0:`before`in t?t.before:e.before;return{...i!==void 0&&{after:i},...a!==void 0&&{before:a},pageSize:n.positiveInteger(t.pageSize??e.pageSize,`pageSize`),search:t.search??e.search}};function a(a){let o=i({pageSize:20,search:``},a.initialQuery),s=t.createAsyncSource({data:[],error:null,isFetching:!1,pagination:r(),query:o}),c=e=>s.fetch({failure:(e,t)=>({...e,error:t,isFetching:!1,pendingQuery:void 0}),load:t=>a.load({query:e,signal:t}),pending:t=>({...t,error:null,isFetching:!0,pendingQuery:e}),success:t=>({data:t.data,error:null,isFetching:!1,pagination:r(t),query:e})}),l=()=>c(o),u=async e=>{let t=i(s.snapshot.pendingQuery??s.snapshot.query,e);n.sameQuery(t,o)||(o=t,await c(t))},d={get disposalSignal(){return s.disposalSignal},dispose:s.dispose,get disposed(){return s.disposed},page:{next(){let e=s.snapshot.pagination.nextCursor;return s.snapshot.isFetching||e===void 0?Promise.resolve():u({after:e})},previous(){let e=s.snapshot.pagination.previousCursor;return s.snapshot.isFetching||e===void 0?Promise.resolve():u({before:e})}},reload:l,setQuery:u,get snapshot(){return s.snapshot},subscribe:s.subscribe,[Symbol.dispose](){d.dispose()}};return a.autoStart!==!1&&l().catch(()=>e.warn(`Initial load failed. Inspect source.snapshot.error.`)),d}exports.createCursorSource=a;
|
|
2
2
|
//# sourceMappingURL=cursorSource.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursorSource.cjs","names":[],"sources":["../src/cursorSource.ts"],"sourcesContent":["import type { CursorConfig, CursorMeta, CursorSource, CursorSourceQuery, SearchOptions } from './types';\n\nimport { clampPositiveInt, defaultKeyOf, extractError, retry } from './_utils';\nimport { createAsyncSearchCoordinator } from './asyncSearch';\nimport { createAsyncSource } from './asyncSource';\nimport { SourcererError } from './errors';\n\n/** Creates a cursor-based (keyset-pagination) source that fetches data from a network endpoint. */\nexport function createCursorSource<T, TCursor = string>(cfg: CursorConfig<T, TCursor>): CursorSource<T, TCursor> {\n const keyOf = cfg.queryKey ?? defaultKeyOf;\n\n // ── Mutable state ───────────────────────────────────────────────────────────\n let limit = clampPositiveInt(cfg.limit ?? 20, 'createCursorSource', 'limit');\n let search = '';\n let afterCursor: TCursor | undefined;\n let beforeCursor: TCursor | undefined;\n let nextCursor: TCursor | undefined;\n let prevCursor: TCursor | undefined;\n let items: readonly T[] = [];\n let total = 0;\n let error: SourcererError | null = null;\n\n // ── Cached accessors ────────────────────────────────────────────────────────\n let cachedCurrent: readonly T[] = [];\n let cachedMeta: CursorMeta = {\n error: null,\n hasNextPage: false,\n hasPrevPage: false,\n isLoading: false,\n isSearchPending: false,\n pageSize: limit,\n totalItems: 0,\n };\n\n const refreshMeta = () => {\n cachedCurrent = items;\n cachedMeta = {\n error,\n hasNextPage: nextCursor !== undefined,\n hasPrevPage: prevCursor !== undefined,\n isLoading: base.pendingCount() > 0,\n isSearchPending: base.core.isScheduled,\n pageSize: limit,\n totalItems: total,\n };\n };\n\n // onBeforeNotify ensures refreshMeta() runs before any subscriber observes the new state,\n // eliminating the need for a parallel listeners Set.\n const base = createAsyncSource<CursorSourceQuery<TCursor>>(cfg, keyOf, refreshMeta);\n const { autoFetch, debounceMs, retryAttempts, retryDelay } = base;\n\n refreshMeta();\n\n const notifyListeners = () => {\n if (base.disposed) return;\n\n base.core.notify();\n };\n const searchCoordinator = createAsyncSearchCoordinator(base.core, notifyListeners);\n\n // ── Core fetch ──────────────────────────────────────────────────────────────\n const toRemoteQuery = (): CursorSourceQuery<TCursor> => ({\n ...(afterCursor !== undefined && { after: afterCursor }),\n ...(beforeCursor !== undefined && { before: beforeCursor }),\n ...(search && { search }),\n limit,\n });\n\n const fetchQuery = (q: CursorSourceQuery<TCursor>): Promise<void> => {\n error = null;\n\n return base.fetch(\n q,\n async (q, signal, isLatest) => {\n const startTime = Date.now();\n\n try {\n const result = await retry((sig) => cfg.fetch(q, sig!), {\n delay: retryDelay,\n signal,\n times: retryAttempts + 1,\n });\n\n if (isLatest()) {\n items = result.items;\n total = result.total ?? result.items.length;\n nextCursor = result.nextCursor;\n prevCursor = result.prevCursor;\n error = null;\n cfg.onFetch?.({ durationMs: Date.now() - startTime, query: q, status: 'success' });\n }\n } catch (reason: unknown) {\n if (signal.aborted) return;\n\n if (isLatest()) {\n items = [];\n total = 0;\n nextCursor = undefined;\n prevCursor = undefined;\n error = new SourcererError(extractError(reason), {\n cause: reason,\n context: { kind: 'cursor', limit: q.limit, ...(q.search && { search: q.search }) },\n });\n cfg.onFetch?.({ durationMs: Date.now() - startTime, error, query: q, status: 'error' });\n }\n }\n },\n () => {\n notifyListeners();\n\n searchCoordinator.settleIfIdle(base.pendingCount());\n },\n );\n };\n\n const doUpdate = () => fetchQuery(toRemoteQuery());\n\n // ── Auto-fetch + refresh interval ───────────────────────────────────────────\n if (autoFetch) void doUpdate();\n\n base.startRefreshInterval(() => void doUpdate());\n\n // ── Public API ──────────────────────────────────────────────────────────────\n return {\n get current() {\n return cachedCurrent;\n },\n\n get disposalSignal() {\n return base.disposalSignal;\n },\n\n dispose: () => {\n searchCoordinator.cancel();\n searchCoordinator.dispose();\n base.dispose();\n },\n\n get disposed() {\n return base.disposed;\n },\n\n get meta() {\n return cachedMeta;\n },\n\n next() {\n if (!nextCursor) return Promise.resolve();\n\n afterCursor = nextCursor;\n beforeCursor = undefined;\n\n return doUpdate();\n },\n\n patch(changes) {\n let changed = false;\n\n if (changes.limit !== undefined) {\n const next = clampPositiveInt(changes.limit, 'source.patch', 'limit');\n\n if (next !== limit) {\n limit = next;\n afterCursor = undefined;\n beforeCursor = undefined;\n changed = true;\n }\n }\n\n // `|| base.core.isScheduled` also catches patching in the same search text as an already-pending\n // debounced search() call — that still needs flushing, not silently dropping as a no-op.\n if ('search' in changes && (changes.search !== search || base.core.isScheduled)) {\n search = changes.search ?? '';\n afterCursor = undefined;\n beforeCursor = undefined;\n changed = true;\n }\n\n if (!changed) return Promise.resolve();\n\n // patch() is documented as a single atomic fetch — cancel any debounced search()\n // still pending so it doesn't fire a second, redundant fetch afterwards.\n searchCoordinator.cancel();\n\n return doUpdate();\n },\n\n prev() {\n if (!prevCursor) return Promise.resolve();\n\n beforeCursor = prevCursor;\n afterCursor = undefined;\n\n return doUpdate();\n },\n\n get query() {\n return {\n ...(afterCursor !== undefined && { after: afterCursor }),\n ...(beforeCursor !== undefined && { before: beforeCursor }),\n ...(search && { search }),\n limit,\n };\n },\n\n ready(timeout) {\n return base.ready(timeout);\n },\n\n refresh() {\n return doUpdate();\n },\n\n reset() {\n searchCoordinator.cancel();\n search = '';\n afterCursor = undefined;\n beforeCursor = undefined;\n\n return doUpdate();\n },\n\n search(q, opts?: SearchOptions): Promise<void> {\n if (opts?.immediate) {\n // A pending debounced search for this same text still needs flushing —\n // `q === search` alone doesn't mean the fetch already happened.\n if (q === search && !base.core.isScheduled) return Promise.resolve();\n\n searchCoordinator.cancel();\n search = q;\n afterCursor = undefined;\n beforeCursor = undefined;\n\n return doUpdate();\n }\n\n if (q === search) return Promise.resolve();\n\n search = q;\n afterCursor = undefined;\n beforeCursor = undefined;\n\n return searchCoordinator.schedule(() => void doUpdate(), debounceMs);\n },\n\n subscribe: (listener) => base.core.subscribe(listener),\n\n [Symbol.dispose]: () => {\n searchCoordinator.cancel();\n searchCoordinator.dispose();\n base.dispose();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cursorSource.cjs","names":[],"sources":["../src/cursorSource.ts"],"sourcesContent":["import type { CursorPagination, CursorQuery, CursorQueryPatch, CursorSource, CursorSourceConfig } from './types';\n\nimport { warn } from './_dev';\nimport { createAsyncSource } from './asyncSource';\nimport { positiveInteger, sameQuery, totalItems } from './pagination';\n\nconst createPagination = <TCursor>(result?: {\n nextCursor?: TCursor;\n previousCursor?: TCursor;\n total?: number;\n}): CursorPagination<TCursor> => ({\n hasNext: result?.nextCursor !== undefined,\n hasPrevious: result?.previousCursor !== undefined,\n kind: 'cursor',\n ...(result?.nextCursor !== undefined && { nextCursor: result.nextCursor }),\n ...(result?.previousCursor !== undefined && { previousCursor: result.previousCursor }),\n ...(result?.total !== undefined && { total: totalItems(result.total) }),\n});\n\nconst normalizeQuery = <TCursor>(\n current: CursorQuery<TCursor>,\n patch: CursorQueryPatch<TCursor> = {},\n): CursorQuery<TCursor> => {\n if (patch.after !== undefined && patch.before !== undefined) {\n throw new RangeError('Cursor query cannot include both after and before');\n }\n\n const resetsCursor = patch.pageSize !== undefined || patch.search !== undefined;\n const after = resetsCursor || 'before' in patch ? undefined : 'after' in patch ? patch.after : current.after;\n const before = resetsCursor || 'after' in patch ? undefined : 'before' in patch ? patch.before : current.before;\n\n return {\n ...(after !== undefined && { after }),\n ...(before !== undefined && { before }),\n pageSize: positiveInteger(patch.pageSize ?? current.pageSize, 'pageSize'),\n search: patch.search ?? current.search,\n };\n};\n\n/** Cursor sources retain loaded cursors while pendingQuery records newer navigation. */\nexport function createCursorSource<T, TCursor = string>(\n config: CursorSourceConfig<T, TCursor>,\n): CursorSource<T, TCursor> {\n const initialQuery: CursorQuery<TCursor> = { pageSize: 20, search: '' };\n let requestedQuery = normalizeQuery(initialQuery, config.initialQuery);\n const asyncSource = createAsyncSource<T, CursorQuery<TCursor>, CursorPagination<TCursor>>({\n data: [],\n error: null,\n isFetching: false,\n pagination: createPagination(),\n query: requestedQuery,\n });\n\n const fetch = (query: CursorQuery<TCursor>): Promise<void> =>\n asyncSource.fetch({\n failure: (previous, error) => ({ ...previous, error, isFetching: false, pendingQuery: undefined }),\n load: (signal) => config.load({ query, signal }),\n pending: (previous) => ({ ...previous, error: null, isFetching: true, pendingQuery: query }),\n success: (result) => ({\n data: result.data,\n error: null,\n isFetching: false,\n pagination: createPagination(result),\n query,\n }),\n });\n\n const reload = (): Promise<void> => fetch(requestedQuery);\n\n const setQuery = async (patch: CursorQueryPatch<TCursor>): Promise<void> => {\n const current = asyncSource.snapshot.pendingQuery ?? asyncSource.snapshot.query;\n const next = normalizeQuery(current, patch);\n\n if (sameQuery(next, requestedQuery)) return;\n\n requestedQuery = next;\n await fetch(next);\n };\n\n const source: CursorSource<T, TCursor> = {\n get disposalSignal() {\n return asyncSource.disposalSignal;\n },\n\n dispose: asyncSource.dispose,\n\n get disposed() {\n return asyncSource.disposed;\n },\n\n page: {\n next() {\n const cursor = asyncSource.snapshot.pagination.nextCursor;\n\n return asyncSource.snapshot.isFetching || cursor === undefined\n ? Promise.resolve()\n : setQuery({ after: cursor });\n },\n\n previous() {\n const cursor = asyncSource.snapshot.pagination.previousCursor;\n\n return asyncSource.snapshot.isFetching || cursor === undefined\n ? Promise.resolve()\n : setQuery({ before: cursor });\n },\n },\n\n reload,\n setQuery,\n\n get snapshot() {\n return asyncSource.snapshot;\n },\n\n subscribe: asyncSource.subscribe,\n\n [Symbol.dispose]() {\n source.dispose();\n },\n };\n\n if (config.autoStart !== false)\n void reload().catch(() => warn('Initial load failed. Inspect source.snapshot.error.'));\n\n return source;\n}\n"],"mappings":"2FAMA,IAAM,EAA6B,IAID,CAChC,QAAS,GAAQ,aAAe,IAAA,GAChC,YAAa,GAAQ,iBAAmB,IAAA,GACxC,KAAM,SACN,GAAI,GAAQ,aAAe,IAAA,IAAa,CAAE,WAAY,EAAO,UAAW,EACxE,GAAI,GAAQ,iBAAmB,IAAA,IAAa,CAAE,eAAgB,EAAO,cAAe,EACpF,GAAI,GAAQ,QAAU,IAAA,IAAa,CAAE,MAAO,EAAA,WAAW,EAAO,KAAK,CAAE,CACvE,GAEM,GACJ,EACA,EAAmC,CAAC,IACX,CACzB,GAAI,EAAM,QAAU,IAAA,IAAa,EAAM,SAAW,IAAA,GAChD,MAAU,WAAW,mDAAmD,EAG1E,IAAM,EAAe,EAAM,WAAa,IAAA,IAAa,EAAM,SAAW,IAAA,GAChE,EAAQ,GAAgB,WAAY,EAAQ,IAAA,GAAY,UAAW,EAAQ,EAAM,MAAQ,EAAQ,MACjG,EAAS,GAAgB,UAAW,EAAQ,IAAA,GAAY,WAAY,EAAQ,EAAM,OAAS,EAAQ,OAEzG,MAAO,CACL,GAAI,IAAU,IAAA,IAAa,CAAE,OAAM,EACnC,GAAI,IAAW,IAAA,IAAa,CAAE,QAAO,EACrC,SAAU,EAAA,gBAAgB,EAAM,UAAY,EAAQ,SAAU,UAAU,EACxE,OAAQ,EAAM,QAAU,EAAQ,MAClC,CACF,EAGA,SAAgB,EACd,EAC0B,CAE1B,IAAI,EAAiB,EAAe,CADS,SAAU,GAAI,OAAQ,EAC/B,EAAc,EAAO,YAAY,EAC/D,EAAc,EAAA,kBAAsE,CACxF,KAAM,CAAC,EACP,MAAO,KACP,WAAY,GACZ,WAAY,EAAiB,EAC7B,MAAO,CACT,CAAC,EAEK,EAAS,GACb,EAAY,MAAM,CAChB,SAAU,EAAU,KAAW,CAAE,GAAG,EAAU,QAAO,WAAY,GAAO,aAAc,IAAA,EAAU,GAChG,KAAO,GAAW,EAAO,KAAK,CAAE,QAAO,QAAO,CAAC,EAC/C,QAAU,IAAc,CAAE,GAAG,EAAU,MAAO,KAAM,WAAY,GAAM,aAAc,CAAM,GAC1F,QAAU,IAAY,CACpB,KAAM,EAAO,KACb,MAAO,KACP,WAAY,GACZ,WAAY,EAAiB,CAAM,EACnC,OACF,EACF,CAAC,EAEG,MAA8B,EAAM,CAAc,EAElD,EAAW,KAAO,IAAoD,CAE1E,IAAM,EAAO,EADG,EAAY,SAAS,cAAgB,EAAY,SAAS,MACrC,CAAK,EAEtC,EAAA,UAAU,EAAM,CAAc,IAElC,EAAiB,EACjB,MAAM,EAAM,CAAI,EAClB,EAEM,EAAmC,CACvC,IAAI,gBAAiB,CACnB,OAAO,EAAY,cACrB,EAEA,QAAS,EAAY,QAErB,IAAI,UAAW,CACb,OAAO,EAAY,QACrB,EAEA,KAAM,CACJ,MAAO,CACL,IAAM,EAAS,EAAY,SAAS,WAAW,WAE/C,OAAO,EAAY,SAAS,YAAc,IAAW,IAAA,GACjD,QAAQ,QAAQ,EAChB,EAAS,CAAE,MAAO,CAAO,CAAC,CAChC,EAEA,UAAW,CACT,IAAM,EAAS,EAAY,SAAS,WAAW,eAE/C,OAAO,EAAY,SAAS,YAAc,IAAW,IAAA,GACjD,QAAQ,QAAQ,EAChB,EAAS,CAAE,OAAQ,CAAO,CAAC,CACjC,CACF,EAEA,SACA,WAEA,IAAI,UAAW,CACb,OAAO,EAAY,QACrB,EAEA,UAAW,EAAY,UAEvB,CAAC,OAAO,UAAW,CACjB,EAAO,QAAQ,CACjB,CACF,EAKA,OAHI,EAAO,YAAc,IACvB,EAAY,CAAC,CAAC,UAAY,EAAA,KAAK,qDAAqD,CAAC,EAEhF,CACT"}
|
package/dist/cursorSource.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/**
|
|
3
|
-
export declare function createCursorSource<T, TCursor = string>(
|
|
1
|
+
import type { CursorSource, CursorSourceConfig } from './types';
|
|
2
|
+
/** Cursor sources retain loaded cursors while pendingQuery records newer navigation. */
|
|
3
|
+
export declare function createCursorSource<T, TCursor = string>(config: CursorSourceConfig<T, TCursor>): CursorSource<T, TCursor>;
|
|
4
4
|
//# sourceMappingURL=cursorSource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursorSource.d.ts","sourceRoot":"","sources":["../src/cursorSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"cursorSource.d.ts","sourceRoot":"","sources":["../src/cursorSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmD,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAuCjH,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,EACpD,MAAM,EAAE,kBAAkB,CAAC,CAAC,EAAE,OAAO,CAAC,GACrC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAoF1B"}
|