@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.
Files changed (144) hide show
  1. package/README.md +19 -74
  2. package/dist/_dev.cjs +1 -1
  3. package/dist/_dev.cjs.map +1 -1
  4. package/dist/_dev.js +2 -8
  5. package/dist/_dev.js.map +1 -1
  6. package/dist/asyncSource.cjs +1 -1
  7. package/dist/asyncSource.cjs.map +1 -1
  8. package/dist/asyncSource.d.ts +14 -58
  9. package/dist/asyncSource.d.ts.map +1 -1
  10. package/dist/asyncSource.js +28 -27
  11. package/dist/asyncSource.js.map +1 -1
  12. package/dist/cursorSource.cjs +1 -1
  13. package/dist/cursorSource.cjs.map +1 -1
  14. package/dist/cursorSource.d.ts +3 -3
  15. package/dist/cursorSource.d.ts.map +1 -1
  16. package/dist/cursorSource.js +76 -109
  17. package/dist/cursorSource.js.map +1 -1
  18. package/dist/devtools.cjs +1 -1
  19. package/dist/devtools.cjs.map +1 -1
  20. package/dist/devtools.d.ts +4 -50
  21. package/dist/devtools.d.ts.map +1 -1
  22. package/dist/devtools.js +3 -15
  23. package/dist/devtools.js.map +1 -1
  24. package/dist/index.cjs +1 -1
  25. package/dist/index.d.ts +2 -11
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +5 -13
  28. package/dist/infiniteSource.cjs +1 -1
  29. package/dist/infiniteSource.cjs.map +1 -1
  30. package/dist/infiniteSource.d.ts +3 -3
  31. package/dist/infiniteSource.d.ts.map +1 -1
  32. package/dist/infiniteSource.js +71 -106
  33. package/dist/infiniteSource.js.map +1 -1
  34. package/dist/localSource.cjs +1 -1
  35. package/dist/localSource.cjs.map +1 -1
  36. package/dist/localSource.d.ts +2 -22
  37. package/dist/localSource.d.ts.map +1 -1
  38. package/dist/localSource.js +58 -104
  39. package/dist/localSource.js.map +1 -1
  40. package/dist/pageSource.cjs +2 -0
  41. package/dist/pageSource.cjs.map +1 -0
  42. package/dist/pageSource.d.ts +4 -0
  43. package/dist/pageSource.d.ts.map +1 -0
  44. package/dist/pageSource.js +96 -0
  45. package/dist/pageSource.js.map +1 -0
  46. package/dist/pagination.cjs +1 -1
  47. package/dist/pagination.cjs.map +1 -1
  48. package/dist/pagination.d.ts +5 -30
  49. package/dist/pagination.d.ts.map +1 -1
  50. package/dist/pagination.js +21 -27
  51. package/dist/pagination.js.map +1 -1
  52. package/dist/requestController.cjs +2 -0
  53. package/dist/requestController.cjs.map +1 -0
  54. package/dist/requestController.d.ts +13 -0
  55. package/dist/requestController.d.ts.map +1 -0
  56. package/dist/requestController.js +29 -0
  57. package/dist/requestController.js.map +1 -0
  58. package/dist/sourceStore.cjs +2 -0
  59. package/dist/sourceStore.cjs.map +1 -0
  60. package/dist/sourceStore.d.ts +10 -0
  61. package/dist/sourceStore.d.ts.map +1 -0
  62. package/dist/sourceStore.js +38 -0
  63. package/dist/sourceStore.js.map +1 -0
  64. package/dist/sourcerer.cjs +1 -1
  65. package/dist/sourcerer.cjs.map +1 -1
  66. package/dist/sourcerer.iife.js +1 -1
  67. package/dist/sourcerer.iife.js.map +1 -1
  68. package/dist/sourcerer.js +1 -1
  69. package/dist/sourcerer.js.map +1 -1
  70. package/dist/types.d.ts +128 -355
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +4 -7
  73. package/dist/_utils.cjs +0 -2
  74. package/dist/_utils.cjs.map +0 -1
  75. package/dist/_utils.d.ts +0 -28
  76. package/dist/_utils.d.ts.map +0 -1
  77. package/dist/_utils.js +0 -20
  78. package/dist/_utils.js.map +0 -1
  79. package/dist/asyncSearch.cjs +0 -2
  80. package/dist/asyncSearch.cjs.map +0 -1
  81. package/dist/asyncSearch.d.ts +0 -13
  82. package/dist/asyncSearch.d.ts.map +0 -1
  83. package/dist/asyncSearch.js +0 -28
  84. package/dist/asyncSearch.js.map +0 -1
  85. package/dist/codecs.cjs +0 -2
  86. package/dist/codecs.cjs.map +0 -1
  87. package/dist/codecs.d.ts +0 -30
  88. package/dist/codecs.d.ts.map +0 -1
  89. package/dist/codecs.js +0 -38
  90. package/dist/codecs.js.map +0 -1
  91. package/dist/core.cjs +0 -2
  92. package/dist/core.cjs.map +0 -1
  93. package/dist/core.d.ts +0 -71
  94. package/dist/core.d.ts.map +0 -1
  95. package/dist/core.js +0 -59
  96. package/dist/core.js.map +0 -1
  97. package/dist/derive.cjs +0 -2
  98. package/dist/derive.cjs.map +0 -1
  99. package/dist/derive.d.ts +0 -29
  100. package/dist/derive.d.ts.map +0 -1
  101. package/dist/derive.js +0 -38
  102. package/dist/derive.js.map +0 -1
  103. package/dist/errors.cjs +0 -2
  104. package/dist/errors.cjs.map +0 -1
  105. package/dist/errors.d.ts +0 -62
  106. package/dist/errors.d.ts.map +0 -1
  107. package/dist/errors.js +0 -29
  108. package/dist/errors.js.map +0 -1
  109. package/dist/fetchManager.cjs +0 -2
  110. package/dist/fetchManager.cjs.map +0 -1
  111. package/dist/fetchManager.d.ts +0 -40
  112. package/dist/fetchManager.d.ts.map +0 -1
  113. package/dist/fetchManager.js +0 -41
  114. package/dist/fetchManager.js.map +0 -1
  115. package/dist/merge.cjs +0 -2
  116. package/dist/merge.cjs.map +0 -1
  117. package/dist/merge.d.ts +0 -35
  118. package/dist/merge.d.ts.map +0 -1
  119. package/dist/merge.js +0 -41
  120. package/dist/merge.js.map +0 -1
  121. package/dist/prefetch.cjs +0 -2
  122. package/dist/prefetch.cjs.map +0 -1
  123. package/dist/prefetch.d.ts +0 -32
  124. package/dist/prefetch.d.ts.map +0 -1
  125. package/dist/prefetch.js +0 -38
  126. package/dist/prefetch.js.map +0 -1
  127. package/dist/presets.cjs +0 -2
  128. package/dist/presets.cjs.map +0 -1
  129. package/dist/presets.d.ts +0 -17
  130. package/dist/presets.d.ts.map +0 -1
  131. package/dist/presets.js +0 -43
  132. package/dist/presets.js.map +0 -1
  133. package/dist/remoteSource.cjs +0 -2
  134. package/dist/remoteSource.cjs.map +0 -1
  135. package/dist/remoteSource.d.ts +0 -4
  136. package/dist/remoteSource.d.ts.map +0 -1
  137. package/dist/remoteSource.js +0 -165
  138. package/dist/remoteSource.js.map +0 -1
  139. package/dist/state.cjs +0 -2
  140. package/dist/state.cjs.map +0 -1
  141. package/dist/state.d.ts +0 -23
  142. package/dist/state.d.ts.map +0 -1
  143. package/dist/state.js +0 -14
  144. package/dist/state.js.map +0 -1
package/README.md CHANGED
@@ -1,98 +1,43 @@
1
1
  # @vielzeug/sourcerer
2
2
 
3
- > Typed reactive data sources for pagination, filtering, sorting, search, and infinite scroll.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@vielzeug/sourcerer)](https://www.npmjs.com/package/@vielzeug/sourcerer) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 { createLocalSource } from '@vielzeug/sourcerer';
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
- await source.search('a', { immediate: true });
32
- console.log(source.current); // [{ id: 1, name: 'Ada' }]
33
- console.log(source.meta.pageNumber); // 1
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
- // autoFetch is true by default — initial data loads immediately
48
- await source.ready();
49
- console.log(source.current, source.meta.totalItems);
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
- ## Local Search Presets
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
- For large in-memory lists, prefer `searchBy(...)` over the default JSON-stringify search:
55
-
56
- ```ts
57
- import { createLocalSource, searchBy } from '@vielzeug/sourcerer';
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
- const source = createLocalSource(users, {
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 © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
43
+ MIT © Helmuth Saatkamp — part of Vielzeug.
package/dist/_dev.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=!globalThis.__SOURCERER_PROD__;function t(t){e&&console.warn(`[@vielzeug/sourcerer] ${t}`)}function n(t){e&&t()}exports.devOnly=n,exports.warn=t;
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":"AAAA,IAAM,EAAQ,CAAE,WAAgD,mBAGhE,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,yBAAyB,GAAK,CACxD,CAGA,SAAgB,EAAQ,EAAsB,CACxC,GAAO,EAAG,CAChB"}
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
- var e = !globalThis.__SOURCERER_PROD__;
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 { n as devOnly, t as warn };
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":";AAAA,IAAM,IAAQ,CAAE,WAAgD;AAGhE,SAAgB,EAAK,GAAmB;CACtC,AAAI,KAAO,QAAQ,KAAK,yBAAyB,GAAK;AACxD;AAGA,SAAgB,EAAQ,GAAsB;CAC5C,AAAI,KAAO,EAAG;AAChB"}
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"}
@@ -1,2 +1,2 @@
1
- const e=require("./_utils.cjs"),t=require("./core.cjs"),n=require("./fetchManager.cjs");function r(r,i=e.defaultKeyOf,a){let o=t.createSourceCore({onBeforeNotify:a}),s=n.createFetchManager(i),c=r.debounceMs??300,l=r.retry?.attempts??0,u=r.retry?.delay??e.defaultRetryDelay,d=r.autoFetch!==!1,f,p=()=>{o.isDisposed||(f!==void 0&&(clearInterval(f),f=void 0),s.dispose(),o.dispose())};return{autoFetch:d,core:o,debounceMs:c,get disposalSignal(){return o.disposalSignal},dispose:p,get disposed(){return o.isDisposed},fetch(e,t,n){return s.run(e,t,n)},pendingCount:()=>s.pendingCount,ready(e){return o.ready(()=>s.pendingCount===0&&!o.isScheduled,e)},retryAttempts:l,retryDelay:u,startRefreshInterval(e){r.refreshInterval!==void 0&&r.refreshInterval>0&&(f=setInterval(()=>{o.isDisposed||e()},r.refreshInterval))},[Symbol.dispose]:p}}exports.createAsyncSource=r;
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
@@ -1 +1 @@
1
- {"version":3,"file":"asyncSource.cjs","names":[],"sources":["../src/asyncSource.ts"],"sourcesContent":["import type { SourceCore } from './core';\n\nimport { defaultKeyOf, defaultRetryDelay } from './_utils';\nimport { createSourceCore } from './core';\nimport { createFetchManager } from './fetchManager';\n\n/**\n * Configuration subset common to all async source types.\n * Extracted to eliminate duplication across remote, cursor, and infinite sources.\n */\nexport type AsyncSourceConfig<TQuery> = Readonly<{\n autoFetch?: boolean;\n debounceMs?: number;\n queryKey?: (q: TQuery) => string;\n refreshInterval?: number;\n retry?: { attempts?: number; delay?: (attempt: number) => number };\n}>;\n\nexport type FetchExecutor<TQuery> = (q: TQuery, signal: AbortSignal, isLatest: () => boolean) => Promise<void>;\n\nexport type AsyncSourceInfra<TQuery> = {\n [Symbol.dispose](): void;\n /** `true` when `autoFetch !== false`. */\n readonly autoFetch: boolean;\n /** The underlying SourceCore — use directly for subscribe, schedule, cancelTimer, flush, isScheduled. */\n readonly core: SourceCore;\n /** Debounce delay in ms. */\n readonly debounceMs: number;\n /** `AbortSignal` aborted when `dispose()` is called. */\n readonly disposalSignal: AbortSignal;\n /** Dispose lifecycle: cancel refresh interval, dispose FM, dispose core. Idempotent. */\n dispose(): void;\n /** `true` after `dispose()`. */\n readonly disposed: boolean;\n /**\n * Run a deduplicated fetch for query `q`.\n * Calls `onCommit()` whenever pending count changes (for subscriber notification).\n */\n fetch(q: TQuery, execute: FetchExecutor<TQuery>, onCommit: () => void): Promise<void>;\n /** Number of currently in-flight requests. */\n pendingCount(): number;\n /** Resolves when idle (no in-flight requests, no pending debounce). */\n ready(timeout?: number): Promise<void>;\n /** Number of retry attempts after the first failure. */\n readonly retryAttempts: number;\n /** Returns the delay in ms before the nth retry. */\n readonly retryDelay: (attempt: number) => number;\n /**\n * Wire the refresh interval. Must be called once after construction, passing\n * the source-level `doUpdate` callback. Does nothing when `refreshInterval`\n * is unset or ≤ 0.\n */\n startRefreshInterval(doUpdate: () => void): void;\n};\n\n/**\n * Creates the shared infrastructure used by `createRemoteSource`, `createCursorSource`,\n * and `createInfiniteSource`. Eliminates duplication of lifecycle, config-defaults,\n * fetch-manager setup, dispose, ready, flush, and refresh-interval logic.\n *\n * Each source factory composes this and adds its own query state, meta shape, and\n * source-specific methods. Pass `onBeforeNotify` to run `refreshMeta()` before\n * subscribers observe each notification — this eliminates the need for a parallel\n * listeners Set in each source factory.\n */\nexport function createAsyncSource<TQuery>(\n cfg: AsyncSourceConfig<TQuery>,\n keyOf: (q: TQuery) => string = defaultKeyOf as (q: TQuery) => string,\n onBeforeNotify?: () => void,\n): AsyncSourceInfra<TQuery> {\n const core = createSourceCore({ onBeforeNotify });\n const fm = createFetchManager<TQuery>(keyOf);\n const debounceMs = cfg.debounceMs ?? 300;\n const retryAttempts = cfg.retry?.attempts ?? 0;\n const retryDelay = cfg.retry?.delay ?? defaultRetryDelay;\n const autoFetch = cfg.autoFetch !== false;\n\n let refreshTimer: ReturnType<typeof setInterval> | undefined;\n\n const dispose = (): void => {\n if (core.isDisposed) return;\n\n if (refreshTimer !== undefined) {\n clearInterval(refreshTimer);\n refreshTimer = undefined;\n }\n\n fm.dispose();\n core.dispose();\n };\n\n return {\n autoFetch,\n core,\n debounceMs,\n get disposalSignal() {\n return core.disposalSignal;\n },\n\n dispose,\n\n get disposed() {\n return core.isDisposed;\n },\n\n fetch(q, execute, onCommit) {\n return fm.run(q, execute, onCommit);\n },\n\n pendingCount: () => fm.pendingCount,\n\n ready(timeout) {\n return core.ready(() => fm.pendingCount === 0 && !core.isScheduled, timeout);\n },\n retryAttempts,\n\n retryDelay,\n\n startRefreshInterval(doUpdate) {\n if (cfg.refreshInterval !== undefined && cfg.refreshInterval > 0) {\n refreshTimer = setInterval(() => {\n if (!core.isDisposed) doUpdate();\n }, cfg.refreshInterval);\n }\n },\n\n [Symbol.dispose]: dispose,\n };\n}\n"],"mappings":"wFAiEA,SAAgB,EACd,EACA,EAA+B,EAAA,aAC/B,EAC0B,CAC1B,IAAM,EAAO,EAAA,iBAAiB,CAAE,gBAAe,CAAC,EAC1C,EAAK,EAAA,mBAA2B,CAAK,EACrC,EAAa,EAAI,YAAc,IAC/B,EAAgB,EAAI,OAAO,UAAY,EACvC,EAAa,EAAI,OAAO,OAAS,EAAA,kBACjC,EAAY,EAAI,YAAc,GAEhC,EAEE,MAAsB,CACtB,EAAK,aAEL,IAAiB,IAAA,KACnB,cAAc,CAAY,EAC1B,EAAe,IAAA,IAGjB,EAAG,QAAQ,EACX,EAAK,QAAQ,EACf,EAEA,MAAO,CACL,YACA,OACA,aACA,IAAI,gBAAiB,CACnB,OAAO,EAAK,cACd,EAEA,UAEA,IAAI,UAAW,CACb,OAAO,EAAK,UACd,EAEA,MAAM,EAAG,EAAS,EAAU,CAC1B,OAAO,EAAG,IAAI,EAAG,EAAS,CAAQ,CACpC,EAEA,iBAAoB,EAAG,aAEvB,MAAM,EAAS,CACb,OAAO,EAAK,UAAY,EAAG,eAAiB,GAAK,CAAC,EAAK,YAAa,CAAO,CAC7E,EACA,gBAEA,aAEA,qBAAqB,EAAU,CACzB,EAAI,kBAAoB,IAAA,IAAa,EAAI,gBAAkB,IAC7D,EAAe,gBAAkB,CAC1B,EAAK,YAAY,EAAS,CACjC,EAAG,EAAI,eAAe,EAE1B,GAEC,OAAO,SAAU,CACpB,CACF"}
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"}
@@ -1,62 +1,18 @@
1
- import type { SourceCore } from './core';
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
- * Run a deduplicated fetch for query `q`.
33
- * Calls `onCommit()` whenever pending count changes (for subscriber notification).
34
- */
35
- fetch(q: TQuery, execute: FetchExecutor<TQuery>, onCommit: () => void): Promise<void>;
36
- /** Number of currently in-flight requests. */
37
- pendingCount(): number;
38
- /** Resolves when idle (no in-flight requests, no pending debounce). */
39
- ready(timeout?: number): Promise<void>;
40
- /** Number of retry attempts after the first failure. */
41
- readonly retryAttempts: number;
42
- /** Returns the delay in ms before the nth retry. */
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,UAAU,EAAE,MAAM,QAAQ,CAAC;AAMzC;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,MAAM,IAAI,QAAQ,CAAC;IAC/C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;KAAE,CAAC;CACpE,CAAC,CAAC;AAEH,MAAM,MAAM,aAAa,CAAC,MAAM,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE/G,MAAM,MAAM,gBAAgB,CAAC,MAAM,IAAI;IACrC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,yGAAyG;IACzG,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,4BAA4B;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;IACrC,wFAAwF;IACxF,OAAO,IAAI,IAAI,CAAC;IAChB,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtF,8CAA8C;IAC9C,YAAY,IAAI,MAAM,CAAC;IACvB,uEAAuE;IACvE,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,wDAAwD;IACxD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,oDAAoD;IACpD,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAClD,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EACtC,GAAG,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAC9B,KAAK,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAA8C,EACpE,cAAc,CAAC,EAAE,MAAM,IAAI,GAC1B,gBAAgB,CAAC,MAAM,CAAC,CA2D1B"}
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"}
@@ -1,40 +1,41 @@
1
- import { defaultKeyOf as e, defaultRetryDelay as t } from "./_utils.js";
2
- import { createSourceCore as n } from "./core.js";
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 i(i, a = e, o) {
6
- let s = n({ onBeforeNotify: o }), c = r(a), l = i.debounceMs ?? 300, u = i.retry?.attempts ?? 0, d = i.retry?.delay ?? t, f = i.autoFetch !== !1, p, m = () => {
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 s.disposalSignal;
8
+ return i.disposalSignal;
15
9
  },
16
- dispose: m,
17
- get disposed() {
18
- return s.isDisposed;
10
+ dispose() {
11
+ a.dispose(), i.dispose();
19
12
  },
20
- fetch(e, t, n) {
21
- return c.run(e, t, n);
13
+ get disposed() {
14
+ return i.disposed;
22
15
  },
23
- pendingCount: () => c.pendingCount,
24
- ready(e) {
25
- return s.ready(() => c.pendingCount === 0 && !s.isScheduled, e);
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
- retryAttempts: u,
28
- retryDelay: d,
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
- [Symbol.dispose]: m
35
+ subscribe: i.subscribe
35
36
  };
36
37
  }
37
38
  //#endregion
38
- export { i as createAsyncSource };
39
+ export { r as createAsyncSource };
39
40
 
40
41
  //# sourceMappingURL=asyncSource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"asyncSource.js","names":[],"sources":["../src/asyncSource.ts"],"sourcesContent":["import type { SourceCore } from './core';\n\nimport { defaultKeyOf, defaultRetryDelay } from './_utils';\nimport { createSourceCore } from './core';\nimport { createFetchManager } from './fetchManager';\n\n/**\n * Configuration subset common to all async source types.\n * Extracted to eliminate duplication across remote, cursor, and infinite sources.\n */\nexport type AsyncSourceConfig<TQuery> = Readonly<{\n autoFetch?: boolean;\n debounceMs?: number;\n queryKey?: (q: TQuery) => string;\n refreshInterval?: number;\n retry?: { attempts?: number; delay?: (attempt: number) => number };\n}>;\n\nexport type FetchExecutor<TQuery> = (q: TQuery, signal: AbortSignal, isLatest: () => boolean) => Promise<void>;\n\nexport type AsyncSourceInfra<TQuery> = {\n [Symbol.dispose](): void;\n /** `true` when `autoFetch !== false`. */\n readonly autoFetch: boolean;\n /** The underlying SourceCore — use directly for subscribe, schedule, cancelTimer, flush, isScheduled. */\n readonly core: SourceCore;\n /** Debounce delay in ms. */\n readonly debounceMs: number;\n /** `AbortSignal` aborted when `dispose()` is called. */\n readonly disposalSignal: AbortSignal;\n /** Dispose lifecycle: cancel refresh interval, dispose FM, dispose core. Idempotent. */\n dispose(): void;\n /** `true` after `dispose()`. */\n readonly disposed: boolean;\n /**\n * Run a deduplicated fetch for query `q`.\n * Calls `onCommit()` whenever pending count changes (for subscriber notification).\n */\n fetch(q: TQuery, execute: FetchExecutor<TQuery>, onCommit: () => void): Promise<void>;\n /** Number of currently in-flight requests. */\n pendingCount(): number;\n /** Resolves when idle (no in-flight requests, no pending debounce). */\n ready(timeout?: number): Promise<void>;\n /** Number of retry attempts after the first failure. */\n readonly retryAttempts: number;\n /** Returns the delay in ms before the nth retry. */\n readonly retryDelay: (attempt: number) => number;\n /**\n * Wire the refresh interval. Must be called once after construction, passing\n * the source-level `doUpdate` callback. Does nothing when `refreshInterval`\n * is unset or ≤ 0.\n */\n startRefreshInterval(doUpdate: () => void): void;\n};\n\n/**\n * Creates the shared infrastructure used by `createRemoteSource`, `createCursorSource`,\n * and `createInfiniteSource`. Eliminates duplication of lifecycle, config-defaults,\n * fetch-manager setup, dispose, ready, flush, and refresh-interval logic.\n *\n * Each source factory composes this and adds its own query state, meta shape, and\n * source-specific methods. Pass `onBeforeNotify` to run `refreshMeta()` before\n * subscribers observe each notification — this eliminates the need for a parallel\n * listeners Set in each source factory.\n */\nexport function createAsyncSource<TQuery>(\n cfg: AsyncSourceConfig<TQuery>,\n keyOf: (q: TQuery) => string = defaultKeyOf as (q: TQuery) => string,\n onBeforeNotify?: () => void,\n): AsyncSourceInfra<TQuery> {\n const core = createSourceCore({ onBeforeNotify });\n const fm = createFetchManager<TQuery>(keyOf);\n const debounceMs = cfg.debounceMs ?? 300;\n const retryAttempts = cfg.retry?.attempts ?? 0;\n const retryDelay = cfg.retry?.delay ?? defaultRetryDelay;\n const autoFetch = cfg.autoFetch !== false;\n\n let refreshTimer: ReturnType<typeof setInterval> | undefined;\n\n const dispose = (): void => {\n if (core.isDisposed) return;\n\n if (refreshTimer !== undefined) {\n clearInterval(refreshTimer);\n refreshTimer = undefined;\n }\n\n fm.dispose();\n core.dispose();\n };\n\n return {\n autoFetch,\n core,\n debounceMs,\n get disposalSignal() {\n return core.disposalSignal;\n },\n\n dispose,\n\n get disposed() {\n return core.isDisposed;\n },\n\n fetch(q, execute, onCommit) {\n return fm.run(q, execute, onCommit);\n },\n\n pendingCount: () => fm.pendingCount,\n\n ready(timeout) {\n return core.ready(() => fm.pendingCount === 0 && !core.isScheduled, timeout);\n },\n retryAttempts,\n\n retryDelay,\n\n startRefreshInterval(doUpdate) {\n if (cfg.refreshInterval !== undefined && cfg.refreshInterval > 0) {\n refreshTimer = setInterval(() => {\n if (!core.isDisposed) doUpdate();\n }, cfg.refreshInterval);\n }\n },\n\n [Symbol.dispose]: dispose,\n };\n}\n"],"mappings":";;;;AAiEA,SAAgB,EACd,GACA,IAA+B,GAC/B,GAC0B;CAC1B,IAAM,IAAO,EAAiB,EAAE,kBAAe,CAAC,GAC1C,IAAK,EAA2B,CAAK,GACrC,IAAa,EAAI,cAAc,KAC/B,IAAgB,EAAI,OAAO,YAAY,GACvC,IAAa,EAAI,OAAO,SAAS,GACjC,IAAY,EAAI,cAAc,IAEhC,GAEE,UAAsB;EACtB,EAAK,eAEL,MAAiB,KAAA,MACnB,cAAc,CAAY,GAC1B,IAAe,KAAA,IAGjB,EAAG,QAAQ,GACX,EAAK,QAAQ;CACf;CAEA,OAAO;EACL;EACA;EACA;EACA,IAAI,iBAAiB;GACnB,OAAO,EAAK;EACd;EAEA;EAEA,IAAI,WAAW;GACb,OAAO,EAAK;EACd;EAEA,MAAM,GAAG,GAAS,GAAU;GAC1B,OAAO,EAAG,IAAI,GAAG,GAAS,CAAQ;EACpC;EAEA,oBAAoB,EAAG;EAEvB,MAAM,GAAS;GACb,OAAO,EAAK,YAAY,EAAG,iBAAiB,KAAK,CAAC,EAAK,aAAa,CAAO;EAC7E;EACA;EAEA;EAEA,qBAAqB,GAAU;GAC7B,AAAI,EAAI,oBAAoB,KAAA,KAAa,EAAI,kBAAkB,MAC7D,IAAe,kBAAkB;IAC/B,AAAK,EAAK,cAAY,EAAS;GACjC,GAAG,EAAI,eAAe;EAE1B;GAEC,OAAO,UAAU;CACpB;AACF"}
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"}
@@ -1,2 +1,2 @@
1
- const e=require("./_utils.cjs"),t=require("./errors.cjs"),n=require("./asyncSearch.cjs"),r=require("./asyncSource.cjs");let i=require("@vielzeug/arsenal");function a(a){let o=a.queryKey??e.defaultKeyOf,s=e.clampPositiveInt(a.limit??20,`createCursorSource`,`limit`),c=``,l,u,d,f,p=[],m=0,h=null,g=[],_={error:null,hasNextPage:!1,hasPrevPage:!1,isLoading:!1,isSearchPending:!1,pageSize:s,totalItems:0},v=()=>{g=p,_={error:h,hasNextPage:d!==void 0,hasPrevPage:f!==void 0,isLoading:y.pendingCount()>0,isSearchPending:y.core.isScheduled,pageSize:s,totalItems:m}},y=r.createAsyncSource(a,o,v),{autoFetch:b,debounceMs:x,retryAttempts:S,retryDelay:C}=y;v();let w=()=>{y.disposed||y.core.notify()},T=n.createAsyncSearchCoordinator(y.core,w),E=()=>({...l!==void 0&&{after:l},...u!==void 0&&{before:u},...c&&{search:c},limit:s}),D=n=>(h=null,y.fetch(n,async(n,r,o)=>{let s=Date.now();try{let e=await(0,i.retry)(e=>a.fetch(n,e),{delay:C,signal:r,times:S+1});o()&&(p=e.items,m=e.total??e.items.length,d=e.nextCursor,f=e.prevCursor,h=null,a.onFetch?.({durationMs:Date.now()-s,query:n,status:`success`}))}catch(i){if(r.aborted)return;o()&&(p=[],m=0,d=void 0,f=void 0,h=new t.SourcererError(e.extractError(i),{cause:i,context:{kind:`cursor`,limit:n.limit,...n.search&&{search:n.search}}}),a.onFetch?.({durationMs:Date.now()-s,error:h,query:n,status:`error`}))}},()=>{w(),T.settleIfIdle(y.pendingCount())})),O=()=>D(E());return b&&O(),y.startRefreshInterval(()=>void O()),{get current(){return g},get disposalSignal(){return y.disposalSignal},dispose:()=>{T.cancel(),T.dispose(),y.dispose()},get disposed(){return y.disposed},get meta(){return _},next(){return d?(l=d,u=void 0,O()):Promise.resolve()},patch(t){let n=!1;if(t.limit!==void 0){let r=e.clampPositiveInt(t.limit,`source.patch`,`limit`);r!==s&&(s=r,l=void 0,u=void 0,n=!0)}return`search`in t&&(t.search!==c||y.core.isScheduled)&&(c=t.search??``,l=void 0,u=void 0,n=!0),n?(T.cancel(),O()):Promise.resolve()},prev(){return f?(u=f,l=void 0,O()):Promise.resolve()},get query(){return{...l!==void 0&&{after:l},...u!==void 0&&{before:u},...c&&{search:c},limit:s}},ready(e){return y.ready(e)},refresh(){return O()},reset(){return T.cancel(),c=``,l=void 0,u=void 0,O()},search(e,t){return t?.immediate?e===c&&!y.core.isScheduled?Promise.resolve():(T.cancel(),c=e,l=void 0,u=void 0,O()):e===c?Promise.resolve():(c=e,l=void 0,u=void 0,T.schedule(()=>void O(),x))},subscribe:e=>y.core.subscribe(e),[Symbol.dispose]:()=>{T.cancel(),T.dispose(),y.dispose()}}}exports.createCursorSource=a;
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"}
@@ -1,4 +1,4 @@
1
- import type { CursorConfig, CursorSource } from './types';
2
- /** Creates a cursor-based (keyset-pagination) source that fetches data from a network endpoint. */
3
- export declare function createCursorSource<T, TCursor = string>(cfg: CursorConfig<T, TCursor>): CursorSource<T, TCursor>;
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,EAAE,YAAY,EAAc,YAAY,EAAoC,MAAM,SAAS,CAAC;AAOxG,mGAAmG;AACnG,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAsP/G"}
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"}