@vielzeug/sourcerer 1.1.0 → 2.0.1

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 +11 -9
  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
@@ -1,123 +1,88 @@
1
- import { clampPositiveInt as e, defaultKeyOf as t, extractError as n, retry as r } from "./_utils.js";
2
- import { SourcererError as i } from "./errors.js";
3
- import { createAsyncSearchCoordinator as a } from "./asyncSearch.js";
4
- import { createAsyncSource as o } from "./asyncSource.js";
1
+ import { warn as e } from "./_dev.js";
2
+ import { createAsyncSource as t } from "./asyncSource.js";
3
+ import { positiveInteger as n, sameQuery as r, totalItems as i } from "./pagination.js";
5
4
  //#region src/infiniteSource.ts
6
- function s(s) {
7
- let c = s.queryKey ?? t, l = e(s.limit ?? 20, "createInfiniteSource", "limit"), u = "", d = 1, f = 0, p = [], m = 0, h = null, g = !1, _ = [], v = {
5
+ var a = (e, t, n) => ({
6
+ hasMore: e < t,
7
+ isLoadingMore: n,
8
+ kind: "infinite",
9
+ loaded: e,
10
+ total: t
11
+ }), o = (e, t = {}) => ({
12
+ pageSize: n(t.pageSize ?? e.pageSize, "pageSize"),
13
+ search: t.search ?? e.search
14
+ });
15
+ function s(n) {
16
+ let s = o({
17
+ pageSize: 20,
18
+ search: ""
19
+ }, n.initialQuery), c = 1, l = t({
20
+ data: [],
8
21
  error: null,
9
- hasMore: !1,
10
- isLoading: !1,
11
- isLoadingMore: !1,
12
- isSearchPending: !1,
13
- loadedPages: 0,
14
- pageSize: l,
15
- totalItems: 0
16
- }, y = () => {
17
- _ = p, v = {
18
- error: h,
19
- hasMore: p.length < m,
20
- isLoading: b.pendingCount() > 0 && !g,
21
- isLoadingMore: g,
22
- isSearchPending: b.core.isScheduled,
23
- loadedPages: f,
24
- pageSize: l,
25
- totalItems: m
26
- };
27
- }, b = o(s, c, y), { autoFetch: x, debounceMs: S, retryAttempts: C, retryDelay: w } = b;
28
- y();
29
- let T = () => {
30
- b.disposed || b.core.notify();
31
- }, E = a(b.core, T), D = (e, t) => {
32
- let a = {
33
- ...u && { search: u },
34
- limit: l,
35
- page: e
36
- };
37
- return h = null, b.fetch(a, async (a, o, c) => {
38
- let l = Date.now();
39
- try {
40
- let n = await r((e) => s.fetch(a, e), {
41
- delay: w,
42
- signal: o,
43
- times: C + 1
44
- });
45
- c() && (p = t ? [...p, ...n.items] : n.items, m = n.total, d = e, f = t ? f + 1 : 1, h = null, s.onFetch?.({
46
- durationMs: Date.now() - l,
47
- query: a,
48
- status: "success"
49
- }));
50
- } catch (e) {
51
- if (o.aborted) return;
52
- c() && (t || (p = []), m = t ? m : 0, h = new i(n(e), {
53
- cause: e,
54
- context: {
55
- kind: "infinite",
56
- limit: a.limit,
57
- page: a.page,
58
- ...a.search && { search: a.search }
59
- }
60
- }), s.onFetch?.({
61
- durationMs: Date.now() - l,
62
- error: h,
63
- query: a,
64
- status: "error"
65
- }));
66
- } finally {
67
- g = !1;
22
+ isFetching: !1,
23
+ pagination: a(0, 0, !1),
24
+ query: s
25
+ }), u = (e, t, r) => {
26
+ let o = l.snapshot.data;
27
+ return l.fetch({
28
+ failure: (e, t) => ({
29
+ ...e,
30
+ error: t,
31
+ isFetching: !1,
32
+ pendingQuery: void 0
33
+ }),
34
+ load: (r) => n.load({
35
+ query: {
36
+ ...e,
37
+ page: t,
38
+ pageSize: e.pageSize
39
+ },
40
+ signal: r
41
+ }),
42
+ pending: (t) => ({
43
+ ...t,
44
+ error: null,
45
+ isFetching: !0,
46
+ pagination: a(t.data.length, t.pagination.total, r),
47
+ pendingQuery: e
48
+ }),
49
+ success: (n) => {
50
+ let s = i(n.total), l = r ? [...o, ...n.data] : n.data;
51
+ return c = t + 1, {
52
+ data: l,
53
+ error: null,
54
+ isFetching: !1,
55
+ pagination: a(l.length, s, !1),
56
+ query: e
57
+ };
68
58
  }
69
- }, () => {
70
- T(), E.settleIfIdle(b.pendingCount());
71
59
  });
72
- }, O = () => D(1, !1);
73
- return x && O(), b.startRefreshInterval(() => void O()), {
74
- get current() {
75
- return _;
76
- },
60
+ }, d = () => (c = 1, u(s, 1, !1)), f = {
77
61
  get disposalSignal() {
78
- return b.disposalSignal;
79
- },
80
- dispose: () => {
81
- E.cancel(), E.dispose(), b.dispose();
62
+ return l.disposalSignal;
82
63
  },
64
+ dispose: l.dispose,
83
65
  get disposed() {
84
- return b.disposed;
66
+ return l.disposed;
85
67
  },
86
68
  loadMore() {
87
- return !v.hasMore || b.pendingCount() > 0 || g ? Promise.resolve() : (g = !0, y(), D(d + 1, !0));
88
- },
89
- get meta() {
90
- return v;
91
- },
92
- patch(t) {
93
- let n = !1;
94
- if (t.limit !== void 0) {
95
- let r = e(t.limit, "source.patch", "limit");
96
- r !== l && (l = r, n = !0);
97
- }
98
- return "search" in t && (t.search !== u || b.core.isScheduled) && (u = t.search ?? "", n = !0), n ? (E.cancel(), p = [], m = 0, d = 1, f = 0, O()) : Promise.resolve();
99
- },
100
- get query() {
101
- return {
102
- ...u && { search: u },
103
- limit: l,
104
- page: d
105
- };
106
- },
107
- ready(e) {
108
- return b.ready(e);
69
+ let { isFetching: e, pagination: t } = l.snapshot;
70
+ return e || l.snapshot.data.length > 0 && !t.hasMore ? Promise.resolve() : u(s, c, !0);
109
71
  },
110
- reset() {
111
- return E.cancel(), d = 1, f = 0, p = [], m = 0, u = "", O();
72
+ reload: d,
73
+ setQuery: async (e) => {
74
+ let t = o(l.snapshot.pendingQuery ?? l.snapshot.query, e);
75
+ r(t, s) || (s = t, c = 1, await u(t, 1, !1));
112
76
  },
113
- search(e, t) {
114
- return t?.immediate ? e === u && !b.core.isScheduled ? Promise.resolve() : (E.cancel(), u = e, p = [], m = 0, f = 0, d = 1, O()) : e === u ? Promise.resolve() : (u = e, p = [], m = 0, f = 0, d = 1, E.schedule(() => void O(), S));
77
+ get snapshot() {
78
+ return l.snapshot;
115
79
  },
116
- subscribe: (e) => b.core.subscribe(e),
117
- [Symbol.dispose]: () => {
118
- E.cancel(), E.dispose(), b.dispose();
80
+ subscribe: l.subscribe,
81
+ [Symbol.dispose]() {
82
+ f.dispose();
119
83
  }
120
84
  };
85
+ return n.autoStart !== !1 && d().catch(() => /* @__PURE__ */ e("Initial load failed. Inspect source.snapshot.error.")), f;
121
86
  }
122
87
  //#endregion
123
88
  export { s as createInfiniteSource };
@@ -1 +1 @@
1
- {"version":3,"file":"infiniteSource.js","names":[],"sources":["../src/infiniteSource.ts"],"sourcesContent":["import type { InfiniteConfig, InfiniteMeta, InfiniteSource, InfiniteSourceQuery, 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 an infinite-scroll source that appends pages on `loadMore()`. */\nexport function createInfiniteSource<T>(cfg: InfiniteConfig<T>): InfiniteSource<T> {\n const keyOf = cfg.queryKey ?? defaultKeyOf;\n\n // ── Mutable state ───────────────────────────────────────────────────────────\n let limit = clampPositiveInt(cfg.limit ?? 20, 'createInfiniteSource', 'limit');\n let search = '';\n let currentPage = 1;\n let loadedPages = 0;\n let items: readonly T[] = [];\n let total = 0;\n let error: SourcererError | null = null;\n let isLoadingMore = false;\n\n // ── Cached accessors ─────────────────────────────────────────────────────────\n let cachedCurrent: readonly T[] = [];\n let cachedMeta: InfiniteMeta = {\n error: null,\n hasMore: false,\n isLoading: false,\n isLoadingMore: false,\n isSearchPending: false,\n loadedPages: 0,\n pageSize: limit,\n totalItems: 0,\n };\n\n const refreshMeta = () => {\n cachedCurrent = items;\n cachedMeta = {\n error,\n hasMore: items.length < total,\n isLoading: base.pendingCount() > 0 && !isLoadingMore,\n isLoadingMore,\n isSearchPending: base.core.isScheduled,\n loadedPages,\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<InfiniteSourceQuery>(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 fetchPage = (targetPage: number, append: boolean): Promise<void> => {\n const q: InfiniteSourceQuery = {\n ...(search && { search }),\n limit,\n page: targetPage,\n };\n\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 if (append) {\n items = [...items, ...result.items];\n } else {\n items = result.items;\n }\n\n total = result.total;\n currentPage = targetPage;\n loadedPages = append ? loadedPages + 1 : 1;\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 if (!append) items = [];\n\n total = append ? total : 0;\n error = new SourcererError(extractError(reason), {\n cause: reason,\n context: { kind: 'infinite', limit: q.limit, page: q.page, ...(q.search && { search: q.search }) },\n });\n cfg.onFetch?.({ durationMs: Date.now() - startTime, error, query: q, status: 'error' });\n }\n } finally {\n isLoadingMore = false;\n }\n },\n () => {\n notifyListeners();\n\n searchCoordinator.settleIfIdle(base.pendingCount());\n },\n );\n };\n\n const doFetch = () => fetchPage(1, false);\n\n // ── Auto-fetch + refresh interval ───────────────────────────────────────────\n if (autoFetch) void doFetch();\n\n base.startRefreshInterval(() => void doFetch());\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 loadMore() {\n if (!cachedMeta.hasMore || base.pendingCount() > 0 || isLoadingMore) return Promise.resolve();\n\n isLoadingMore = true;\n refreshMeta();\n\n return fetchPage(currentPage + 1, true);\n },\n\n get meta() {\n return cachedMeta;\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 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 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 items = [];\n total = 0;\n currentPage = 1;\n loadedPages = 0;\n\n return doFetch();\n },\n\n get query() {\n return {\n ...(search && { search }),\n limit,\n page: currentPage,\n };\n },\n\n ready(timeout) {\n return base.ready(timeout);\n },\n\n reset() {\n searchCoordinator.cancel();\n currentPage = 1;\n loadedPages = 0;\n items = [];\n total = 0;\n search = '';\n\n return doFetch();\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 items = [];\n total = 0;\n loadedPages = 0;\n currentPage = 1;\n\n return doFetch();\n }\n\n if (q === search) return Promise.resolve();\n\n search = q;\n items = [];\n total = 0;\n loadedPages = 0;\n currentPage = 1;\n\n return searchCoordinator.schedule(() => void doFetch(), 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":"infiniteSource.js","names":[],"sources":["../src/infiniteSource.ts"],"sourcesContent":["import type {\n InfinitePagination,\n InfiniteQuery,\n InfiniteQueryPatch,\n InfiniteSource,\n InfiniteSourceConfig,\n} from './types';\n\nimport { warn } from './_dev';\nimport { createAsyncSource } from './asyncSource';\nimport { positiveInteger, sameQuery, totalItems } from './pagination';\n\nconst createPagination = (loaded: number, total: number, isLoadingMore: boolean): InfinitePagination => ({\n hasMore: loaded < total,\n isLoadingMore,\n kind: 'infinite',\n loaded,\n total,\n});\n\nconst normalizeQuery = (current: InfiniteQuery, patch: InfiniteQueryPatch = {}): InfiniteQuery => ({\n pageSize: positiveInteger(patch.pageSize ?? current.pageSize, 'pageSize'),\n search: patch.search ?? current.search,\n});\n\n/** Infinite sources preserve loaded collection state while a pending query replaces it. */\nexport function createInfiniteSource<T>(config: InfiniteSourceConfig<T>): InfiniteSource<T> {\n const initialQuery: InfiniteQuery = { pageSize: 20, search: '' };\n let requestedQuery = normalizeQuery(initialQuery, config.initialQuery);\n let nextPage = 1;\n const asyncSource = createAsyncSource<T, InfiniteQuery, InfinitePagination>({\n data: [],\n error: null,\n isFetching: false,\n pagination: createPagination(0, 0, false),\n query: requestedQuery,\n });\n\n const fetch = (query: InfiniteQuery, page: number, append: boolean): Promise<void> => {\n const loadedData = asyncSource.snapshot.data;\n\n return asyncSource.fetch({\n failure: (previous, error) => ({ ...previous, error, isFetching: false, pendingQuery: undefined }),\n load: (signal) => config.load({ query: { ...query, page, pageSize: query.pageSize }, signal }),\n pending: (previous) => ({\n ...previous,\n error: null,\n isFetching: true,\n pagination: createPagination(previous.data.length, previous.pagination.total, append),\n pendingQuery: query,\n }),\n success: (result) => {\n const total = totalItems(result.total);\n const data = append ? [...loadedData, ...result.data] : result.data;\n\n nextPage = page + 1;\n\n return {\n data,\n error: null,\n isFetching: false,\n pagination: createPagination(data.length, total, false),\n query,\n };\n },\n });\n };\n\n const reload = (): Promise<void> => {\n nextPage = 1;\n\n return fetch(requestedQuery, 1, false);\n };\n\n const setQuery = async (patch: InfiniteQueryPatch): 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 nextPage = 1;\n await fetch(next, 1, false);\n };\n\n const source: InfiniteSource<T> = {\n get disposalSignal() {\n return asyncSource.disposalSignal;\n },\n\n dispose: asyncSource.dispose,\n\n get disposed() {\n return asyncSource.disposed;\n },\n\n loadMore() {\n const { isFetching, pagination } = asyncSource.snapshot;\n\n return isFetching || (asyncSource.snapshot.data.length > 0 && !pagination.hasMore)\n ? Promise.resolve()\n : fetch(requestedQuery, nextPage, true);\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":";;;;AAYA,IAAM,KAAoB,GAAgB,GAAe,OAAgD;CACvG,SAAS,IAAS;CAClB;CACA,MAAM;CACN;CACA;AACF,IAEM,KAAkB,GAAwB,IAA4B,CAAC,OAAsB;CACjG,UAAU,EAAgB,EAAM,YAAY,EAAQ,UAAU,UAAU;CACxE,QAAQ,EAAM,UAAU,EAAQ;AAClC;AAGA,SAAgB,EAAwB,GAAoD;CAE1F,IAAI,IAAiB,EAAe;EADE,UAAU;EAAI,QAAQ;CACxB,GAAc,EAAO,YAAY,GACjE,IAAW,GACT,IAAc,EAAwD;EAC1E,MAAM,CAAC;EACP,OAAO;EACP,YAAY;EACZ,YAAY,EAAiB,GAAG,GAAG,EAAK;EACxC,OAAO;CACT,CAAC,GAEK,KAAS,GAAsB,GAAc,MAAmC;EACpF,IAAM,IAAa,EAAY,SAAS;EAExC,OAAO,EAAY,MAAM;GACvB,UAAU,GAAU,OAAW;IAAE,GAAG;IAAU;IAAO,YAAY;IAAO,cAAc,KAAA;GAAU;GAChG,OAAO,MAAW,EAAO,KAAK;IAAE,OAAO;KAAE,GAAG;KAAO;KAAM,UAAU,EAAM;IAAS;IAAG;GAAO,CAAC;GAC7F,UAAU,OAAc;IACtB,GAAG;IACH,OAAO;IACP,YAAY;IACZ,YAAY,EAAiB,EAAS,KAAK,QAAQ,EAAS,WAAW,OAAO,CAAM;IACpF,cAAc;GAChB;GACA,UAAU,MAAW;IACnB,IAAM,IAAQ,EAAW,EAAO,KAAK,GAC/B,IAAO,IAAS,CAAC,GAAG,GAAY,GAAG,EAAO,IAAI,IAAI,EAAO;IAI/D,OAFA,IAAW,IAAO,GAEX;KACL;KACA,OAAO;KACP,YAAY;KACZ,YAAY,EAAiB,EAAK,QAAQ,GAAO,EAAK;KACtD;IACF;GACF;EACF,CAAC;CACH,GAEM,WACJ,IAAW,GAEJ,EAAM,GAAgB,GAAG,EAAK,IAcjC,IAA4B;EAChC,IAAI,iBAAiB;GACnB,OAAO,EAAY;EACrB;EAEA,SAAS,EAAY;EAErB,IAAI,WAAW;GACb,OAAO,EAAY;EACrB;EAEA,WAAW;GACT,IAAM,EAAE,eAAY,kBAAe,EAAY;GAE/C,OAAO,KAAe,EAAY,SAAS,KAAK,SAAS,KAAK,CAAC,EAAW,UACtE,QAAQ,QAAQ,IAChB,EAAM,GAAgB,GAAU,EAAI;EAC1C;EAEA;EACA,iBA/BsB,MAA6C;GAEnE,IAAM,IAAO,EADG,EAAY,SAAS,gBAAgB,EAAY,SAAS,OACrC,CAAK;GAEtC,EAAU,GAAM,CAAc,MAElC,IAAiB,GACjB,IAAW,GACX,MAAM,EAAM,GAAM,GAAG,EAAK;EAC5B;EAwBE,IAAI,WAAW;GACb,OAAO,EAAY;EACrB;EAEA,WAAW,EAAY;EAEvB,CAAC,OAAO,WAAW;GACjB,EAAO,QAAQ;EACjB;CACF;CAKA,OAHI,EAAO,cAAc,MACvB,EAAY,CAAC,CAAC,YAAY,kBAAK,qDAAqD,CAAC,GAEhF;AACT"}
@@ -1,2 +1,2 @@
1
- const e=require("./_utils.cjs"),t=require("./errors.cjs"),n=require("./asyncSearch.cjs"),r=require("./core.cjs"),i=require("./pagination.cjs");var a=20,o=300;function s(e,t){let n=t.toLowerCase();return e.filter(e=>JSON.stringify(e).toLowerCase().includes(n))}function c(c,l={}){let u=r.createSourceCore(),d=l.filterAsync!==void 0||l.sortAsync!==void 0,f=l.debounceMs??o,p=l.initialData??c,m=``,h=l.filter,g=l.sort,_=e.clampPositiveInt(l.limit??a,`createLocalSource`,`limit`),v=1,y=[],b=!1,x=!1,S=null,C=null,w=()=>u.notify(),T=n.createAsyncSearchCoordinator(u,w),E=()=>{let e=p;return m&&(e=(l.searchFn??s)(e,m)),h&&(e=e.filter(h)),g&&(e=[...e].sort(g)),e},D=()=>{y=E();let e=i.pageCount(y.length,_);v=i.clampPage(v,e)},O=async()=>{C?.abort();let n=new AbortController;C=n,b=!0,S=null,w();try{let e=p;if(m&&(e=(l.searchFn??s)(e,m)),h&&(e=e.filter(h)),l.filterAsync&&!n.signal.aborted&&(e=await l.filterAsync(e,n.signal)),g&&(e=[...e].sort(g)),l.sortAsync&&!n.signal.aborted&&(e=await l.sortAsync(e,n.signal)),n.signal.aborted)return;y=e,b=!1,x=!1;let t=i.pageCount(y.length,_);v=i.clampPage(v,t)}catch(r){n.signal.aborted||(b=!1,x=!1,S=new t.SourcererError(e.extractError(r),{cause:r}))}finally{C===n&&(C=null)}w(),T.settleIfIdle(0)},k=()=>d?O():(x=!1,D(),w(),T.settleIfIdle(0),Promise.resolve());d?(y=E(),O()):D();let A={get current(){let e=(v-1)*_;return y.slice(e,e+_)},get disposalSignal(){return u.disposalSignal},dispose(){u.isDisposed||(C?.abort(),C=null,T.cancel(),T.dispose(),u.dispose())},get disposed(){return u.isDisposed},goTo(e){let t=i.clampPage(e,i.pageCount(y.length,_));return t===v?Promise.resolve():(v=t,w(),Promise.resolve())},goToLast(){return A.goTo(i.pageCount(y.length,_))},get meta(){return i.createMeta({error:S,isLoading:b,isSearchPending:x,pageNumber:v,pageSize:_,totalItems:y.length})},next(){return A.goTo(v+1)},patch(t){let n=!1;if(t.limit!==void 0){let r=e.clampPositiveInt(t.limit,`source.patch`,`limit`);r!==_&&(_=r,n=!0)}if(`filter`in t&&(h=t.filter,n=!0),`sort`in t&&(g=t.sort,n=!0),`search`in t&&(t.search!==m||u.isScheduled)&&(m=t.search??``,n=!0),t.page!==void 0){let e=i.clampPage(t.page,i.pageCount(y.length,_));e!==v&&(v=e,n=!0)}return n?(T.cancel(),(t.limit!==void 0||`filter`in t||`sort`in t||`search`in t)&&t.page===void 0&&(v=1),k()):Promise.resolve()},prev(){return A.goTo(v-1)},get query(){return{limit:_,page:v,...m&&{search:m}}},ready(e){return u.ready(()=>!b&&!u.isScheduled,e)},reset(){return T.cancel(),x=!1,m=``,h=l.filter,g=l.sort,v=1,k()},search(e,t){return t?.immediate?e===m&&!u.isScheduled?Promise.resolve():(T.cancel(),x=!1,m=e,v=1,k()):e===m?Promise.resolve():(m=e,v=1,x=!0,T.schedule(()=>{x=!1,k()},f))},setData(e){return p=e,v=1,k()},subscribe(e){return u.subscribe(e)},[Symbol.dispose](){A.dispose()}};return A}exports.createLocalSource=c;
1
+ const e=require("./sourceStore.cjs"),t=require("./pagination.cjs");var n=(e,n={},r)=>{let i=n.page??e.page,a=n.pageSize??e.pageSize,o=n.search??e.search,s=n.pageSize!==void 0||n.search!==void 0,c=n.page??(s?1:i);return{page:r===void 0?t.positiveInteger(c,`page`):Math.min(t.positiveInteger(c,`page`),r),pageSize:t.positiveInteger(a,`pageSize`),search:o}};function r(r,i={}){let a=r,o=n({page:1,pageSize:20,search:``},i.initialQuery),s=()=>{let e=o.search.length>0&&i.match?a.filter(e=>i.match(e,o.search)):a,n=t.totalItems(e.length),r=t.createPagePagination(o.page,o.pageSize,n),s=(r.index-1)*r.size;return o={...o,page:r.index},{data:e.slice(s,s+r.size),error:null,isFetching:!1,pagination:r,query:o}},c=e.createSourceStore(s()),l=()=>{let e=s();return t.sameQuery(e.query,c.value.query)&&e.data===c.value.data&&e.pagination===c.value.pagination?!1:(c.set(e),!0)},u=e=>{let r=n(o,e,c.value.pagination.count);return!t.sameQuery(r,o)&&(o=r,l())},d={get disposalSignal(){return c.disposalSignal},dispose:c.dispose,get disposed(){return c.disposed},page:{go(e){return u({page:e})},last(){return u({page:c.value.pagination.count})},next(){return c.value.pagination.hasNext&&u({page:c.value.pagination.index+1})},previous(){return c.value.pagination.hasPrevious&&u({page:c.value.pagination.index-1})}},setData(e){return e!==a&&(a=e,l())},setQuery:u,get snapshot(){return c.value},subscribe:c.subscribe,[Symbol.dispose](){d.dispose()}};return d}exports.createLocalSource=r;
2
2
  //# sourceMappingURL=localSource.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"localSource.cjs","names":[],"sources":["../src/localSource.ts"],"sourcesContent":["import type { LocalSource, LocalSourceConfig, LocalSourceQuery, SearchOptions, SourceQuery } from './types';\n\nimport { clampPositiveInt, extractError } from './_utils';\nimport { createAsyncSearchCoordinator } from './asyncSearch';\nimport { createSourceCore } from './core';\nimport { SourcererError } from './errors';\nimport { clampPage, createMeta, pageCount } from './pagination';\n\nconst DEFAULT_LIMIT = 20;\nconst DEFAULT_DEBOUNCE_MS = 300;\n\nfunction defaultSearch<T>(items: readonly T[], query: string): readonly T[] {\n const q = query.toLowerCase();\n\n return items.filter((item) => JSON.stringify(item).toLowerCase().includes(q));\n}\n\n/**\n * Creates an in-memory paginated source with optional async filter/sort pipelines.\n *\n * When neither `filterAsync` nor `sortAsync` are configured, all operations\n * complete synchronously — there is no loading state and `ready()` resolves immediately.\n * Provide async ops to offload heavy computation (e.g. to a Web Worker).\n *\n * @example\n * ```ts\n * // Synchronous (common case)\n * const source = createLocalSource([1, 2, 3, 4, 5], { limit: 2 });\n * await source.goTo(2);\n * console.log(source.current); // [3, 4]\n *\n * // Async pipeline (Web Worker offloading)\n * const source = createLocalSource(users, {\n * filterAsync: (items, signal) => myWorker.filter(items, signal),\n * sortAsync: (items, signal) => myWorker.sort(items, signal),\n * });\n * ```\n */\nexport function createLocalSource<T>(data: readonly T[], cfg: LocalSourceConfig<T> = {}): LocalSource<T> {\n const core = createSourceCore();\n const isAsync = cfg.filterAsync !== undefined || cfg.sortAsync !== undefined;\n const debounceMs = cfg.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\n let allData: readonly T[] = cfg.initialData ?? data;\n let search = '';\n let filter = cfg.filter;\n let sort = cfg.sort;\n let limit = clampPositiveInt(cfg.limit ?? DEFAULT_LIMIT, 'createLocalSource', 'limit');\n let page = 1;\n let processed: readonly T[] = [];\n let isLoading = false;\n let isSearchPending = false;\n let error: SourcererError | null = null;\n let asyncController: AbortController | null = null;\n\n const commit = () => core.notify();\n // Shared debounced-search promise coordinator — the same one remote/cursor/infinite use,\n // instead of a fourth hand-rolled copy of \"pendingSearch/resolvePendingSearch\" bookkeeping.\n const searchCoordinator = createAsyncSearchCoordinator(core, commit);\n\n // ── Sync pipeline (used when no async ops) ──────────────────────────────────\n const runSyncPipeline = (): readonly T[] => {\n let result: readonly T[] = allData;\n\n if (search) result = (cfg.searchFn ?? defaultSearch)(result, search);\n\n if (filter) result = result.filter(filter);\n\n if (sort) result = [...result].sort(sort);\n\n return result;\n };\n\n const recompute = () => {\n processed = runSyncPipeline();\n\n const pages = pageCount(processed.length, limit);\n\n page = clampPage(page, pages);\n };\n\n // ── Async pipeline ──────────────────────────────────────────────────────────\n const runAsyncPipeline = async (): Promise<void> => {\n asyncController?.abort();\n\n const controller = new AbortController();\n\n asyncController = controller;\n isLoading = true;\n error = null;\n commit();\n\n try {\n let result: readonly T[] = allData;\n\n if (search) result = (cfg.searchFn ?? defaultSearch)(result, search);\n\n if (filter) result = result.filter(filter);\n\n if (cfg.filterAsync && !controller.signal.aborted) {\n result = await cfg.filterAsync(result, controller.signal);\n }\n\n if (sort) result = [...result].sort(sort);\n\n if (cfg.sortAsync && !controller.signal.aborted) {\n result = await cfg.sortAsync(result, controller.signal);\n }\n\n if (controller.signal.aborted) return;\n\n processed = result;\n isLoading = false;\n isSearchPending = false;\n\n const pages = pageCount(processed.length, limit);\n\n page = clampPage(page, pages);\n } catch (reason: unknown) {\n if (!controller.signal.aborted) {\n isLoading = false;\n isSearchPending = false;\n error = new SourcererError(extractError(reason), { cause: reason });\n }\n } finally {\n if (asyncController === controller) asyncController = null;\n }\n\n commit();\n // Local source never has more than one pipeline run in flight — settling always means idle.\n searchCoordinator.settleIfIdle(0);\n };\n\n // Internal flush — not part of public API\n const flushInternal = (): Promise<void> => {\n if (isAsync) return runAsyncPipeline();\n\n isSearchPending = false;\n recompute();\n commit();\n searchCoordinator.settleIfIdle(0);\n\n return Promise.resolve();\n };\n\n // ── Initial state ───────────────────────────────────────────────────────────\n if (isAsync) {\n // Async: seed with a sync pass so `current` isn't empty while the async pass runs.\n processed = runSyncPipeline();\n void runAsyncPipeline();\n } else {\n recompute();\n }\n\n const source: LocalSource<T> = {\n get current() {\n const start = (page - 1) * limit;\n\n return processed.slice(start, start + limit);\n },\n\n get disposalSignal() {\n return core.disposalSignal;\n },\n\n dispose() {\n if (core.isDisposed) return;\n\n asyncController?.abort();\n asyncController = null;\n searchCoordinator.cancel();\n searchCoordinator.dispose();\n core.dispose();\n },\n\n get disposed() {\n return core.isDisposed;\n },\n\n goTo(n) {\n const pages = pageCount(processed.length, limit);\n const clamped = clampPage(n, pages);\n\n if (clamped === page) return Promise.resolve();\n\n page = clamped;\n commit();\n\n return Promise.resolve();\n },\n\n goToLast() {\n return source.goTo(pageCount(processed.length, limit));\n },\n\n get meta() {\n return createMeta({\n error,\n isLoading,\n isSearchPending,\n pageNumber: page,\n pageSize: limit,\n totalItems: processed.length,\n });\n },\n\n next() {\n return source.goTo(page + 1);\n },\n\n patch(changes: LocalSourceQuery<T>) {\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 changed = true;\n }\n }\n\n if ('filter' in changes) {\n filter = changes.filter;\n changed = true;\n }\n\n if ('sort' in changes) {\n sort = changes.sort;\n changed = true;\n }\n\n // `|| 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 || core.isScheduled)) {\n search = changes.search ?? '';\n changed = true;\n }\n\n if (changes.page !== undefined) {\n const next = clampPage(changes.page, pageCount(processed.length, limit));\n\n if (next !== page) {\n page = next;\n changed = true;\n }\n }\n\n if (!changed) return Promise.resolve();\n\n // patch() is documented as a single atomic recompute — cancel any debounced search()\n // still pending so it doesn't fire a second, redundant recompute afterwards.\n searchCoordinator.cancel();\n\n // Reset page only when non-page query fields changed without an explicit page\n if (\n (changes.limit !== undefined || 'filter' in changes || 'sort' in changes || 'search' in changes) &&\n changes.page === undefined\n ) {\n page = 1;\n }\n\n return flushInternal();\n },\n\n prev() {\n return source.goTo(page - 1);\n },\n\n get query(): SourceQuery {\n return {\n limit,\n page,\n ...(search && { search }),\n };\n },\n\n ready(timeout?: number) {\n return core.ready(() => !isLoading && !core.isScheduled, timeout);\n },\n\n reset() {\n searchCoordinator.cancel();\n isSearchPending = false;\n search = '';\n filter = cfg.filter;\n sort = cfg.sort;\n page = 1;\n\n return flushInternal();\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 recompute already happened.\n if (q === search && !core.isScheduled) return Promise.resolve();\n\n searchCoordinator.cancel();\n isSearchPending = false;\n search = q;\n page = 1;\n\n return flushInternal();\n }\n\n if (q === search) return Promise.resolve();\n\n search = q;\n page = 1;\n isSearchPending = true;\n\n return searchCoordinator.schedule(() => {\n isSearchPending = false;\n void flushInternal();\n }, debounceMs);\n },\n\n setData(d) {\n allData = d;\n page = 1;\n\n return flushInternal();\n },\n\n subscribe(listener) {\n return core.subscribe(listener);\n },\n\n [Symbol.dispose]() {\n source.dispose();\n },\n };\n\n return source;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"localSource.cjs","names":[],"sources":["../src/localSource.ts"],"sourcesContent":["import type {\n LocalQuery,\n LocalQueryPatch,\n LocalSource,\n LocalSourceConfig,\n PagePagination,\n SourceSnapshot,\n} from './types';\n\nimport { createPagePagination, positiveInteger, sameQuery, totalItems } from './pagination';\nimport { createSourceStore } from './sourceStore';\n\nconst normalizeQuery = (current: LocalQuery, patch: LocalQueryPatch = {}, maxPage?: number): LocalQuery => {\n const page = patch.page ?? current.page;\n const pageSize = patch.pageSize ?? current.pageSize;\n const search = patch.search ?? current.search;\n const resetsPage = patch.pageSize !== undefined || patch.search !== undefined;\n\n const requestedPage = patch.page ?? (resetsPage ? 1 : page);\n\n return {\n page:\n maxPage === undefined\n ? positiveInteger(requestedPage, 'page')\n : Math.min(positiveInteger(requestedPage, 'page'), maxPage),\n pageSize: positiveInteger(pageSize, 'pageSize'),\n search,\n };\n};\n\n/** Local sources own only search and pagination; callers prepare filtering and ranking. */\nexport function createLocalSource<T>(data: readonly T[], config: LocalSourceConfig<T> = {}): LocalSource<T> {\n let allData = data;\n let query = normalizeQuery({ page: 1, pageSize: 20, search: '' }, config.initialQuery);\n\n const buildSnapshot = (): SourceSnapshot<T, LocalQuery, PagePagination> => {\n const matched =\n query.search.length > 0 && config.match ? allData.filter((item) => config.match!(item, query.search)) : allData;\n const total = totalItems(matched.length);\n const pagination = createPagePagination(query.page, query.pageSize, total);\n const start = (pagination.index - 1) * pagination.size;\n\n query = { ...query, page: pagination.index };\n\n return {\n data: matched.slice(start, start + pagination.size),\n error: null,\n isFetching: false,\n pagination,\n query,\n };\n };\n\n const store = createSourceStore(buildSnapshot());\n\n const commit = (): boolean => {\n const next = buildSnapshot();\n\n if (\n sameQuery(next.query, store.value.query) &&\n next.data === store.value.data &&\n next.pagination === store.value.pagination\n ) {\n return false;\n }\n\n store.set(next);\n\n return true;\n };\n\n const setQuery = (patch: LocalQueryPatch): boolean => {\n const next = normalizeQuery(query, patch, store.value.pagination.count);\n\n if (sameQuery(next, query)) return false;\n\n query = next;\n\n return commit();\n };\n\n const source: LocalSource<T> = {\n get disposalSignal() {\n return store.disposalSignal;\n },\n\n dispose: store.dispose,\n\n get disposed() {\n return store.disposed;\n },\n\n page: {\n go(index) {\n return setQuery({ page: index });\n },\n\n last() {\n return setQuery({ page: store.value.pagination.count });\n },\n\n next() {\n return store.value.pagination.hasNext && setQuery({ page: store.value.pagination.index + 1 });\n },\n\n previous() {\n return store.value.pagination.hasPrevious && setQuery({ page: store.value.pagination.index - 1 });\n },\n },\n\n setData(nextData) {\n if (nextData === allData) return false;\n\n allData = nextData;\n\n return commit();\n },\n\n setQuery,\n\n get snapshot() {\n return store.value;\n },\n\n subscribe: store.subscribe,\n\n [Symbol.dispose]() {\n source.dispose();\n },\n };\n\n return source;\n}\n"],"mappings":"mEAYA,IAAM,GAAkB,EAAqB,EAAyB,CAAC,EAAG,IAAiC,CACzG,IAAM,EAAO,EAAM,MAAQ,EAAQ,KAC7B,EAAW,EAAM,UAAY,EAAQ,SACrC,EAAS,EAAM,QAAU,EAAQ,OACjC,EAAa,EAAM,WAAa,IAAA,IAAa,EAAM,SAAW,IAAA,GAE9D,EAAgB,EAAM,OAAS,EAAa,EAAI,GAEtD,MAAO,CACL,KACE,IAAY,IAAA,GACR,EAAA,gBAAgB,EAAe,MAAM,EACrC,KAAK,IAAI,EAAA,gBAAgB,EAAe,MAAM,EAAG,CAAO,EAC9D,SAAU,EAAA,gBAAgB,EAAU,UAAU,EAC9C,QACF,CACF,EAGA,SAAgB,EAAqB,EAAoB,EAA+B,CAAC,EAAmB,CAC1G,IAAI,EAAU,EACV,EAAQ,EAAe,CAAE,KAAM,EAAG,SAAU,GAAI,OAAQ,EAAG,EAAG,EAAO,YAAY,EAE/E,MAAqE,CACzE,IAAM,EACJ,EAAM,OAAO,OAAS,GAAK,EAAO,MAAQ,EAAQ,OAAQ,GAAS,EAAO,MAAO,EAAM,EAAM,MAAM,CAAC,EAAI,EACpG,EAAQ,EAAA,WAAW,EAAQ,MAAM,EACjC,EAAa,EAAA,qBAAqB,EAAM,KAAM,EAAM,SAAU,CAAK,EACnE,GAAS,EAAW,MAAQ,GAAK,EAAW,KAIlD,MAFA,GAAQ,CAAE,GAAG,EAAO,KAAM,EAAW,KAAM,EAEpC,CACL,KAAM,EAAQ,MAAM,EAAO,EAAQ,EAAW,IAAI,EAClD,MAAO,KACP,WAAY,GACZ,aACA,OACF,CACF,EAEM,EAAQ,EAAA,kBAAkB,EAAc,CAAC,EAEzC,MAAwB,CAC5B,IAAM,EAAO,EAAc,EAY3B,OATE,EAAA,UAAU,EAAK,MAAO,EAAM,MAAM,KAAK,GACvC,EAAK,OAAS,EAAM,MAAM,MAC1B,EAAK,aAAe,EAAM,MAAM,WAEzB,IAGT,EAAM,IAAI,CAAI,EAEP,GACT,EAEM,EAAY,GAAoC,CACpD,IAAM,EAAO,EAAe,EAAO,EAAO,EAAM,MAAM,WAAW,KAAK,EAMtE,MAJA,CAAI,EAAA,UAAU,EAAM,CAAK,IAEzB,EAAQ,EAED,EAAO,EAChB,EAEM,EAAyB,CAC7B,IAAI,gBAAiB,CACnB,OAAO,EAAM,cACf,EAEA,QAAS,EAAM,QAEf,IAAI,UAAW,CACb,OAAO,EAAM,QACf,EAEA,KAAM,CACJ,GAAG,EAAO,CACR,OAAO,EAAS,CAAE,KAAM,CAAM,CAAC,CACjC,EAEA,MAAO,CACL,OAAO,EAAS,CAAE,KAAM,EAAM,MAAM,WAAW,KAAM,CAAC,CACxD,EAEA,MAAO,CACL,OAAO,EAAM,MAAM,WAAW,SAAW,EAAS,CAAE,KAAM,EAAM,MAAM,WAAW,MAAQ,CAAE,CAAC,CAC9F,EAEA,UAAW,CACT,OAAO,EAAM,MAAM,WAAW,aAAe,EAAS,CAAE,KAAM,EAAM,MAAM,WAAW,MAAQ,CAAE,CAAC,CAClG,CACF,EAEA,QAAQ,EAAU,CAKhB,OAJI,IAAa,IAEjB,EAAU,EAEH,EAAO,EAChB,EAEA,WAEA,IAAI,UAAW,CACb,OAAO,EAAM,KACf,EAEA,UAAW,EAAM,UAEjB,CAAC,OAAO,UAAW,CACjB,EAAO,QAAQ,CACjB,CACF,EAEA,OAAO,CACT"}
@@ -1,24 +1,4 @@
1
1
  import type { LocalSource, LocalSourceConfig } from './types';
2
- /**
3
- * Creates an in-memory paginated source with optional async filter/sort pipelines.
4
- *
5
- * When neither `filterAsync` nor `sortAsync` are configured, all operations
6
- * complete synchronously — there is no loading state and `ready()` resolves immediately.
7
- * Provide async ops to offload heavy computation (e.g. to a Web Worker).
8
- *
9
- * @example
10
- * ```ts
11
- * // Synchronous (common case)
12
- * const source = createLocalSource([1, 2, 3, 4, 5], { limit: 2 });
13
- * await source.goTo(2);
14
- * console.log(source.current); // [3, 4]
15
- *
16
- * // Async pipeline (Web Worker offloading)
17
- * const source = createLocalSource(users, {
18
- * filterAsync: (items, signal) => myWorker.filter(items, signal),
19
- * sortAsync: (items, signal) => myWorker.sort(items, signal),
20
- * });
21
- * ```
22
- */
23
- export declare function createLocalSource<T>(data: readonly T[], cfg?: LocalSourceConfig<T>): LocalSource<T>;
2
+ /** Local sources own only search and pagination; callers prepare filtering and ranking. */
3
+ export declare function createLocalSource<T>(data: readonly T[], config?: LocalSourceConfig<T>): LocalSource<T>;
24
4
  //# sourceMappingURL=localSource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"localSource.d.ts","sourceRoot":"","sources":["../src/localSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAgD,MAAM,SAAS,CAAC;AAiB5G;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,GAAE,iBAAiB,CAAC,CAAC,CAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAySvG"}
1
+ {"version":3,"file":"localSource.d.ts","sourceRoot":"","sources":["../src/localSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,WAAW,EACX,iBAAiB,EAGlB,MAAM,SAAS,CAAC;AAuBjB,2FAA2F;AAC3F,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,GAAE,iBAAiB,CAAC,CAAC,CAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAqG1G"}
@@ -1,120 +1,74 @@
1
- import { clampPositiveInt as e, extractError as t } from "./_utils.js";
2
- import { SourcererError as n } from "./errors.js";
3
- import { createAsyncSearchCoordinator as r } from "./asyncSearch.js";
4
- import { createSourceCore as i } from "./core.js";
5
- import { clampPage as a, createMeta as o, pageCount as s } from "./pagination.js";
1
+ import { createSourceStore as e } from "./sourceStore.js";
2
+ import { createPagePagination as t, positiveInteger as n, sameQuery as r, totalItems as i } from "./pagination.js";
6
3
  //#region src/localSource.ts
7
- var c = 20, l = 300;
8
- function u(e, t) {
9
- let n = t.toLowerCase();
10
- return e.filter((e) => JSON.stringify(e).toLowerCase().includes(n));
11
- }
12
- function d(d, f = {}) {
13
- let p = i(), m = f.filterAsync !== void 0 || f.sortAsync !== void 0, h = f.debounceMs ?? l, g = f.initialData ?? d, _ = "", v = f.filter, y = f.sort, b = e(f.limit ?? c, "createLocalSource", "limit"), x = 1, S = [], C = !1, w = !1, T = null, E = null, D = () => p.notify(), O = r(p, D), k = () => {
14
- let e = g;
15
- return _ && (e = (f.searchFn ?? u)(e, _)), v && (e = e.filter(v)), y && (e = [...e].sort(y)), e;
16
- }, A = () => {
17
- S = k();
18
- let e = s(S.length, b);
19
- x = a(x, e);
20
- }, j = async () => {
21
- E?.abort();
22
- let e = new AbortController();
23
- E = e, C = !0, T = null, D();
24
- try {
25
- let t = g;
26
- if (_ && (t = (f.searchFn ?? u)(t, _)), v && (t = t.filter(v)), f.filterAsync && !e.signal.aborted && (t = await f.filterAsync(t, e.signal)), y && (t = [...t].sort(y)), f.sortAsync && !e.signal.aborted && (t = await f.sortAsync(t, e.signal)), e.signal.aborted) return;
27
- S = t, C = !1, w = !1;
28
- let n = s(S.length, b);
29
- x = a(x, n);
30
- } catch (r) {
31
- e.signal.aborted || (C = !1, w = !1, T = new n(t(r), { cause: r }));
32
- } finally {
33
- E === e && (E = null);
34
- }
35
- D(), O.settleIfIdle(0);
36
- }, M = () => m ? j() : (w = !1, A(), D(), O.settleIfIdle(0), Promise.resolve());
37
- m ? (S = k(), j()) : A();
38
- let N = {
39
- get current() {
40
- let e = (x - 1) * b;
41
- return S.slice(e, e + b);
42
- },
4
+ var a = (e, t = {}, r) => {
5
+ let i = t.page ?? e.page, a = t.pageSize ?? e.pageSize, o = t.search ?? e.search, s = t.pageSize !== void 0 || t.search !== void 0, c = t.page ?? (s ? 1 : i);
6
+ return {
7
+ page: r === void 0 ? n(c, "page") : Math.min(n(c, "page"), r),
8
+ pageSize: n(a, "pageSize"),
9
+ search: o
10
+ };
11
+ };
12
+ function o(n, o = {}) {
13
+ let s = n, c = a({
14
+ page: 1,
15
+ pageSize: 20,
16
+ search: ""
17
+ }, o.initialQuery), l = () => {
18
+ let e = c.search.length > 0 && o.match ? s.filter((e) => o.match(e, c.search)) : s, n = i(e.length), r = t(c.page, c.pageSize, n), a = (r.index - 1) * r.size;
19
+ return c = {
20
+ ...c,
21
+ page: r.index
22
+ }, {
23
+ data: e.slice(a, a + r.size),
24
+ error: null,
25
+ isFetching: !1,
26
+ pagination: r,
27
+ query: c
28
+ };
29
+ }, u = e(l()), d = () => {
30
+ let e = l();
31
+ return r(e.query, u.value.query) && e.data === u.value.data && e.pagination === u.value.pagination ? !1 : (u.set(e), !0);
32
+ }, f = (e) => {
33
+ let t = a(c, e, u.value.pagination.count);
34
+ return !r(t, c) && (c = t, d());
35
+ }, p = {
43
36
  get disposalSignal() {
44
- return p.disposalSignal;
45
- },
46
- dispose() {
47
- p.isDisposed || (E?.abort(), E = null, O.cancel(), O.dispose(), p.dispose());
37
+ return u.disposalSignal;
48
38
  },
39
+ dispose: u.dispose,
49
40
  get disposed() {
50
- return p.isDisposed;
51
- },
52
- goTo(e) {
53
- let t = a(e, s(S.length, b));
54
- return t === x ? Promise.resolve() : (x = t, D(), Promise.resolve());
55
- },
56
- goToLast() {
57
- return N.goTo(s(S.length, b));
58
- },
59
- get meta() {
60
- return o({
61
- error: T,
62
- isLoading: C,
63
- isSearchPending: w,
64
- pageNumber: x,
65
- pageSize: b,
66
- totalItems: S.length
67
- });
68
- },
69
- next() {
70
- return N.goTo(x + 1);
71
- },
72
- patch(t) {
73
- let n = !1;
74
- if (t.limit !== void 0) {
75
- let r = e(t.limit, "source.patch", "limit");
76
- r !== b && (b = r, n = !0);
41
+ return u.disposed;
42
+ },
43
+ page: {
44
+ go(e) {
45
+ return f({ page: e });
46
+ },
47
+ last() {
48
+ return f({ page: u.value.pagination.count });
49
+ },
50
+ next() {
51
+ return u.value.pagination.hasNext && f({ page: u.value.pagination.index + 1 });
52
+ },
53
+ previous() {
54
+ return u.value.pagination.hasPrevious && f({ page: u.value.pagination.index - 1 });
77
55
  }
78
- if ("filter" in t && (v = t.filter, n = !0), "sort" in t && (y = t.sort, n = !0), "search" in t && (t.search !== _ || p.isScheduled) && (_ = t.search ?? "", n = !0), t.page !== void 0) {
79
- let e = a(t.page, s(S.length, b));
80
- e !== x && (x = e, n = !0);
81
- }
82
- return n ? (O.cancel(), (t.limit !== void 0 || "filter" in t || "sort" in t || "search" in t) && t.page === void 0 && (x = 1), M()) : Promise.resolve();
83
- },
84
- prev() {
85
- return N.goTo(x - 1);
86
- },
87
- get query() {
88
- return {
89
- limit: b,
90
- page: x,
91
- ..._ && { search: _ }
92
- };
93
- },
94
- ready(e) {
95
- return p.ready(() => !C && !p.isScheduled, e);
96
- },
97
- reset() {
98
- return O.cancel(), w = !1, _ = "", v = f.filter, y = f.sort, x = 1, M();
99
- },
100
- search(e, t) {
101
- return t?.immediate ? e === _ && !p.isScheduled ? Promise.resolve() : (O.cancel(), w = !1, _ = e, x = 1, M()) : e === _ ? Promise.resolve() : (_ = e, x = 1, w = !0, O.schedule(() => {
102
- w = !1, M();
103
- }, h));
104
56
  },
105
57
  setData(e) {
106
- return g = e, x = 1, M();
58
+ return e !== s && (s = e, d());
107
59
  },
108
- subscribe(e) {
109
- return p.subscribe(e);
60
+ setQuery: f,
61
+ get snapshot() {
62
+ return u.value;
110
63
  },
64
+ subscribe: u.subscribe,
111
65
  [Symbol.dispose]() {
112
- N.dispose();
66
+ p.dispose();
113
67
  }
114
68
  };
115
- return N;
69
+ return p;
116
70
  }
117
71
  //#endregion
118
- export { d as createLocalSource };
72
+ export { o as createLocalSource };
119
73
 
120
74
  //# sourceMappingURL=localSource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"localSource.js","names":[],"sources":["../src/localSource.ts"],"sourcesContent":["import type { LocalSource, LocalSourceConfig, LocalSourceQuery, SearchOptions, SourceQuery } from './types';\n\nimport { clampPositiveInt, extractError } from './_utils';\nimport { createAsyncSearchCoordinator } from './asyncSearch';\nimport { createSourceCore } from './core';\nimport { SourcererError } from './errors';\nimport { clampPage, createMeta, pageCount } from './pagination';\n\nconst DEFAULT_LIMIT = 20;\nconst DEFAULT_DEBOUNCE_MS = 300;\n\nfunction defaultSearch<T>(items: readonly T[], query: string): readonly T[] {\n const q = query.toLowerCase();\n\n return items.filter((item) => JSON.stringify(item).toLowerCase().includes(q));\n}\n\n/**\n * Creates an in-memory paginated source with optional async filter/sort pipelines.\n *\n * When neither `filterAsync` nor `sortAsync` are configured, all operations\n * complete synchronously — there is no loading state and `ready()` resolves immediately.\n * Provide async ops to offload heavy computation (e.g. to a Web Worker).\n *\n * @example\n * ```ts\n * // Synchronous (common case)\n * const source = createLocalSource([1, 2, 3, 4, 5], { limit: 2 });\n * await source.goTo(2);\n * console.log(source.current); // [3, 4]\n *\n * // Async pipeline (Web Worker offloading)\n * const source = createLocalSource(users, {\n * filterAsync: (items, signal) => myWorker.filter(items, signal),\n * sortAsync: (items, signal) => myWorker.sort(items, signal),\n * });\n * ```\n */\nexport function createLocalSource<T>(data: readonly T[], cfg: LocalSourceConfig<T> = {}): LocalSource<T> {\n const core = createSourceCore();\n const isAsync = cfg.filterAsync !== undefined || cfg.sortAsync !== undefined;\n const debounceMs = cfg.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\n let allData: readonly T[] = cfg.initialData ?? data;\n let search = '';\n let filter = cfg.filter;\n let sort = cfg.sort;\n let limit = clampPositiveInt(cfg.limit ?? DEFAULT_LIMIT, 'createLocalSource', 'limit');\n let page = 1;\n let processed: readonly T[] = [];\n let isLoading = false;\n let isSearchPending = false;\n let error: SourcererError | null = null;\n let asyncController: AbortController | null = null;\n\n const commit = () => core.notify();\n // Shared debounced-search promise coordinator — the same one remote/cursor/infinite use,\n // instead of a fourth hand-rolled copy of \"pendingSearch/resolvePendingSearch\" bookkeeping.\n const searchCoordinator = createAsyncSearchCoordinator(core, commit);\n\n // ── Sync pipeline (used when no async ops) ──────────────────────────────────\n const runSyncPipeline = (): readonly T[] => {\n let result: readonly T[] = allData;\n\n if (search) result = (cfg.searchFn ?? defaultSearch)(result, search);\n\n if (filter) result = result.filter(filter);\n\n if (sort) result = [...result].sort(sort);\n\n return result;\n };\n\n const recompute = () => {\n processed = runSyncPipeline();\n\n const pages = pageCount(processed.length, limit);\n\n page = clampPage(page, pages);\n };\n\n // ── Async pipeline ──────────────────────────────────────────────────────────\n const runAsyncPipeline = async (): Promise<void> => {\n asyncController?.abort();\n\n const controller = new AbortController();\n\n asyncController = controller;\n isLoading = true;\n error = null;\n commit();\n\n try {\n let result: readonly T[] = allData;\n\n if (search) result = (cfg.searchFn ?? defaultSearch)(result, search);\n\n if (filter) result = result.filter(filter);\n\n if (cfg.filterAsync && !controller.signal.aborted) {\n result = await cfg.filterAsync(result, controller.signal);\n }\n\n if (sort) result = [...result].sort(sort);\n\n if (cfg.sortAsync && !controller.signal.aborted) {\n result = await cfg.sortAsync(result, controller.signal);\n }\n\n if (controller.signal.aborted) return;\n\n processed = result;\n isLoading = false;\n isSearchPending = false;\n\n const pages = pageCount(processed.length, limit);\n\n page = clampPage(page, pages);\n } catch (reason: unknown) {\n if (!controller.signal.aborted) {\n isLoading = false;\n isSearchPending = false;\n error = new SourcererError(extractError(reason), { cause: reason });\n }\n } finally {\n if (asyncController === controller) asyncController = null;\n }\n\n commit();\n // Local source never has more than one pipeline run in flight — settling always means idle.\n searchCoordinator.settleIfIdle(0);\n };\n\n // Internal flush — not part of public API\n const flushInternal = (): Promise<void> => {\n if (isAsync) return runAsyncPipeline();\n\n isSearchPending = false;\n recompute();\n commit();\n searchCoordinator.settleIfIdle(0);\n\n return Promise.resolve();\n };\n\n // ── Initial state ───────────────────────────────────────────────────────────\n if (isAsync) {\n // Async: seed with a sync pass so `current` isn't empty while the async pass runs.\n processed = runSyncPipeline();\n void runAsyncPipeline();\n } else {\n recompute();\n }\n\n const source: LocalSource<T> = {\n get current() {\n const start = (page - 1) * limit;\n\n return processed.slice(start, start + limit);\n },\n\n get disposalSignal() {\n return core.disposalSignal;\n },\n\n dispose() {\n if (core.isDisposed) return;\n\n asyncController?.abort();\n asyncController = null;\n searchCoordinator.cancel();\n searchCoordinator.dispose();\n core.dispose();\n },\n\n get disposed() {\n return core.isDisposed;\n },\n\n goTo(n) {\n const pages = pageCount(processed.length, limit);\n const clamped = clampPage(n, pages);\n\n if (clamped === page) return Promise.resolve();\n\n page = clamped;\n commit();\n\n return Promise.resolve();\n },\n\n goToLast() {\n return source.goTo(pageCount(processed.length, limit));\n },\n\n get meta() {\n return createMeta({\n error,\n isLoading,\n isSearchPending,\n pageNumber: page,\n pageSize: limit,\n totalItems: processed.length,\n });\n },\n\n next() {\n return source.goTo(page + 1);\n },\n\n patch(changes: LocalSourceQuery<T>) {\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 changed = true;\n }\n }\n\n if ('filter' in changes) {\n filter = changes.filter;\n changed = true;\n }\n\n if ('sort' in changes) {\n sort = changes.sort;\n changed = true;\n }\n\n // `|| 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 || core.isScheduled)) {\n search = changes.search ?? '';\n changed = true;\n }\n\n if (changes.page !== undefined) {\n const next = clampPage(changes.page, pageCount(processed.length, limit));\n\n if (next !== page) {\n page = next;\n changed = true;\n }\n }\n\n if (!changed) return Promise.resolve();\n\n // patch() is documented as a single atomic recompute — cancel any debounced search()\n // still pending so it doesn't fire a second, redundant recompute afterwards.\n searchCoordinator.cancel();\n\n // Reset page only when non-page query fields changed without an explicit page\n if (\n (changes.limit !== undefined || 'filter' in changes || 'sort' in changes || 'search' in changes) &&\n changes.page === undefined\n ) {\n page = 1;\n }\n\n return flushInternal();\n },\n\n prev() {\n return source.goTo(page - 1);\n },\n\n get query(): SourceQuery {\n return {\n limit,\n page,\n ...(search && { search }),\n };\n },\n\n ready(timeout?: number) {\n return core.ready(() => !isLoading && !core.isScheduled, timeout);\n },\n\n reset() {\n searchCoordinator.cancel();\n isSearchPending = false;\n search = '';\n filter = cfg.filter;\n sort = cfg.sort;\n page = 1;\n\n return flushInternal();\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 recompute already happened.\n if (q === search && !core.isScheduled) return Promise.resolve();\n\n searchCoordinator.cancel();\n isSearchPending = false;\n search = q;\n page = 1;\n\n return flushInternal();\n }\n\n if (q === search) return Promise.resolve();\n\n search = q;\n page = 1;\n isSearchPending = true;\n\n return searchCoordinator.schedule(() => {\n isSearchPending = false;\n void flushInternal();\n }, debounceMs);\n },\n\n setData(d) {\n allData = d;\n page = 1;\n\n return flushInternal();\n },\n\n subscribe(listener) {\n return core.subscribe(listener);\n },\n\n [Symbol.dispose]() {\n source.dispose();\n },\n };\n\n return source;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"localSource.js","names":[],"sources":["../src/localSource.ts"],"sourcesContent":["import type {\n LocalQuery,\n LocalQueryPatch,\n LocalSource,\n LocalSourceConfig,\n PagePagination,\n SourceSnapshot,\n} from './types';\n\nimport { createPagePagination, positiveInteger, sameQuery, totalItems } from './pagination';\nimport { createSourceStore } from './sourceStore';\n\nconst normalizeQuery = (current: LocalQuery, patch: LocalQueryPatch = {}, maxPage?: number): LocalQuery => {\n const page = patch.page ?? current.page;\n const pageSize = patch.pageSize ?? current.pageSize;\n const search = patch.search ?? current.search;\n const resetsPage = patch.pageSize !== undefined || patch.search !== undefined;\n\n const requestedPage = patch.page ?? (resetsPage ? 1 : page);\n\n return {\n page:\n maxPage === undefined\n ? positiveInteger(requestedPage, 'page')\n : Math.min(positiveInteger(requestedPage, 'page'), maxPage),\n pageSize: positiveInteger(pageSize, 'pageSize'),\n search,\n };\n};\n\n/** Local sources own only search and pagination; callers prepare filtering and ranking. */\nexport function createLocalSource<T>(data: readonly T[], config: LocalSourceConfig<T> = {}): LocalSource<T> {\n let allData = data;\n let query = normalizeQuery({ page: 1, pageSize: 20, search: '' }, config.initialQuery);\n\n const buildSnapshot = (): SourceSnapshot<T, LocalQuery, PagePagination> => {\n const matched =\n query.search.length > 0 && config.match ? allData.filter((item) => config.match!(item, query.search)) : allData;\n const total = totalItems(matched.length);\n const pagination = createPagePagination(query.page, query.pageSize, total);\n const start = (pagination.index - 1) * pagination.size;\n\n query = { ...query, page: pagination.index };\n\n return {\n data: matched.slice(start, start + pagination.size),\n error: null,\n isFetching: false,\n pagination,\n query,\n };\n };\n\n const store = createSourceStore(buildSnapshot());\n\n const commit = (): boolean => {\n const next = buildSnapshot();\n\n if (\n sameQuery(next.query, store.value.query) &&\n next.data === store.value.data &&\n next.pagination === store.value.pagination\n ) {\n return false;\n }\n\n store.set(next);\n\n return true;\n };\n\n const setQuery = (patch: LocalQueryPatch): boolean => {\n const next = normalizeQuery(query, patch, store.value.pagination.count);\n\n if (sameQuery(next, query)) return false;\n\n query = next;\n\n return commit();\n };\n\n const source: LocalSource<T> = {\n get disposalSignal() {\n return store.disposalSignal;\n },\n\n dispose: store.dispose,\n\n get disposed() {\n return store.disposed;\n },\n\n page: {\n go(index) {\n return setQuery({ page: index });\n },\n\n last() {\n return setQuery({ page: store.value.pagination.count });\n },\n\n next() {\n return store.value.pagination.hasNext && setQuery({ page: store.value.pagination.index + 1 });\n },\n\n previous() {\n return store.value.pagination.hasPrevious && setQuery({ page: store.value.pagination.index - 1 });\n },\n },\n\n setData(nextData) {\n if (nextData === allData) return false;\n\n allData = nextData;\n\n return commit();\n },\n\n setQuery,\n\n get snapshot() {\n return store.value;\n },\n\n subscribe: store.subscribe,\n\n [Symbol.dispose]() {\n source.dispose();\n },\n };\n\n return source;\n}\n"],"mappings":";;;AAYA,IAAM,KAAkB,GAAqB,IAAyB,CAAC,GAAG,MAAiC;CACzG,IAAM,IAAO,EAAM,QAAQ,EAAQ,MAC7B,IAAW,EAAM,YAAY,EAAQ,UACrC,IAAS,EAAM,UAAU,EAAQ,QACjC,IAAa,EAAM,aAAa,KAAA,KAAa,EAAM,WAAW,KAAA,GAE9D,IAAgB,EAAM,SAAS,IAAa,IAAI;CAEtD,OAAO;EACL,MACE,MAAY,KAAA,IACR,EAAgB,GAAe,MAAM,IACrC,KAAK,IAAI,EAAgB,GAAe,MAAM,GAAG,CAAO;EAC9D,UAAU,EAAgB,GAAU,UAAU;EAC9C;CACF;AACF;AAGA,SAAgB,EAAqB,GAAoB,IAA+B,CAAC,GAAmB;CAC1G,IAAI,IAAU,GACV,IAAQ,EAAe;EAAE,MAAM;EAAG,UAAU;EAAI,QAAQ;CAAG,GAAG,EAAO,YAAY,GAE/E,UAAqE;EACzE,IAAM,IACJ,EAAM,OAAO,SAAS,KAAK,EAAO,QAAQ,EAAQ,QAAQ,MAAS,EAAO,MAAO,GAAM,EAAM,MAAM,CAAC,IAAI,GACpG,IAAQ,EAAW,EAAQ,MAAM,GACjC,IAAa,EAAqB,EAAM,MAAM,EAAM,UAAU,CAAK,GACnE,KAAS,EAAW,QAAQ,KAAK,EAAW;EAIlD,OAFA,IAAQ;GAAE,GAAG;GAAO,MAAM,EAAW;EAAM,GAEpC;GACL,MAAM,EAAQ,MAAM,GAAO,IAAQ,EAAW,IAAI;GAClD,OAAO;GACP,YAAY;GACZ;GACA;EACF;CACF,GAEM,IAAQ,EAAkB,EAAc,CAAC,GAEzC,UAAwB;EAC5B,IAAM,IAAO,EAAc;EAY3B,OATE,EAAU,EAAK,OAAO,EAAM,MAAM,KAAK,KACvC,EAAK,SAAS,EAAM,MAAM,QAC1B,EAAK,eAAe,EAAM,MAAM,aAEzB,MAGT,EAAM,IAAI,CAAI,GAEP;CACT,GAEM,KAAY,MAAoC;EACpD,IAAM,IAAO,EAAe,GAAO,GAAO,EAAM,MAAM,WAAW,KAAK;EAMtE,OAJA,CAAI,EAAU,GAAM,CAAK,MAEzB,IAAQ,GAED,EAAO;CAChB,GAEM,IAAyB;EAC7B,IAAI,iBAAiB;GACnB,OAAO,EAAM;EACf;EAEA,SAAS,EAAM;EAEf,IAAI,WAAW;GACb,OAAO,EAAM;EACf;EAEA,MAAM;GACJ,GAAG,GAAO;IACR,OAAO,EAAS,EAAE,MAAM,EAAM,CAAC;GACjC;GAEA,OAAO;IACL,OAAO,EAAS,EAAE,MAAM,EAAM,MAAM,WAAW,MAAM,CAAC;GACxD;GAEA,OAAO;IACL,OAAO,EAAM,MAAM,WAAW,WAAW,EAAS,EAAE,MAAM,EAAM,MAAM,WAAW,QAAQ,EAAE,CAAC;GAC9F;GAEA,WAAW;IACT,OAAO,EAAM,MAAM,WAAW,eAAe,EAAS,EAAE,MAAM,EAAM,MAAM,WAAW,QAAQ,EAAE,CAAC;GAClG;EACF;EAEA,QAAQ,GAAU;GAKhB,OAJI,MAAa,MAEjB,IAAU,GAEH,EAAO;EAChB;EAEA;EAEA,IAAI,WAAW;GACb,OAAO,EAAM;EACf;EAEA,WAAW,EAAM;EAEjB,CAAC,OAAO,WAAW;GACjB,EAAO,QAAQ;EACjB;CACF;CAEA,OAAO;AACT"}
@@ -0,0 +1,2 @@
1
+ const e=require("./_dev.cjs"),t=require("./asyncSource.cjs"),n=require("./pagination.cjs");var r=(e,t={},r)=>{let i=t.page??e.page,a=t.pageSize??e.pageSize,o=t.search??e.search,s=`filter`in t||t.pageSize!==void 0||t.search!==void 0||`sort`in t,c=t.page??(s?1:i);return{...`filter`in t?t.filter!==void 0&&{filter:t.filter}:e.filter!==void 0&&{filter:e.filter},...`sort`in t?t.sort!==void 0&&{sort:t.sort}:e.sort!==void 0&&{sort:e.sort},page:r===void 0?n.positiveInteger(c,`page`):Math.min(n.positiveInteger(c,`page`),r),pageSize:n.positiveInteger(a,`pageSize`),search:o}};function i(i){let a=r({page:1,pageSize:20,search:``},i.initialQuery),o=t.createAsyncSource({data:[],error:null,isFetching:!1,pagination:n.createPagePagination(a.page,a.pageSize,0),query:a}),s=e=>o.fetch({failure:(e,t)=>({...e,error:t,isFetching:!1,pendingQuery:void 0}),load:t=>i.load({query:e,signal:t}),pending:t=>({...t,error:null,isFetching:!0,pendingQuery:e}),success:t=>{let r=n.totalItems(t.total),i=n.createPagePagination(e.page,e.pageSize,r),o={...e,page:i.index};return a=o,{data:t.data,error:null,isFetching:!1,pagination:i,query:o}}}),c=()=>s(a),l=async e=>{let t=r(o.snapshot.pendingQuery??o.snapshot.query,e,o.snapshot.pagination.total>0?o.snapshot.pagination.count:void 0);n.sameQuery(t,a)||(a=t,await s(t))},u={get disposalSignal(){return o.disposalSignal},dispose:o.dispose,get disposed(){return o.disposed},page:{go(e){return o.snapshot.isFetching?Promise.resolve():l({page:e})},last(){return o.snapshot.isFetching?Promise.resolve():l({page:o.snapshot.pagination.count})},next(){return o.snapshot.isFetching||!o.snapshot.pagination.hasNext?Promise.resolve():l({page:o.snapshot.pagination.index+1})},previous(){return o.snapshot.isFetching||!o.snapshot.pagination.hasPrevious?Promise.resolve():l({page:o.snapshot.pagination.index-1})}},reload:c,setQuery:l,get snapshot(){return o.snapshot},subscribe:o.subscribe,[Symbol.dispose](){u.dispose()}};return i.autoStart!==!1&&c().catch(()=>e.warn(`Initial load failed. Inspect source.snapshot.error.`)),u}exports.createPageSource=i;
2
+ //# sourceMappingURL=pageSource.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pageSource.cjs","names":[],"sources":["../src/pageSource.ts"],"sourcesContent":["import type { PagePagination, PageQuery, PageQueryPatch, PageSource, PageSourceConfig } from './types';\n\nimport { warn } from './_dev';\nimport { createAsyncSource } from './asyncSource';\nimport { createPagePagination, positiveInteger, sameQuery, totalItems } from './pagination';\n\nconst normalizeQuery = <TFilter, TSort>(\n current: PageQuery<TFilter, TSort>,\n patch: PageQueryPatch<TFilter, TSort> = {},\n maxPage?: number,\n): PageQuery<TFilter, TSort> => {\n const page = patch.page ?? current.page;\n const pageSize = patch.pageSize ?? current.pageSize;\n const search = patch.search ?? current.search;\n const resetsPage = 'filter' in patch || patch.pageSize !== undefined || patch.search !== undefined || 'sort' in patch;\n\n const requestedPage = patch.page ?? (resetsPage ? 1 : page);\n\n return {\n ...('filter' in patch\n ? patch.filter !== undefined && { filter: patch.filter }\n : current.filter !== undefined && { filter: current.filter }),\n ...('sort' in patch\n ? patch.sort !== undefined && { sort: patch.sort }\n : current.sort !== undefined && { sort: current.sort }),\n page:\n maxPage === undefined\n ? positiveInteger(requestedPage, 'page')\n : Math.min(positiveInteger(requestedPage, 'page'), maxPage),\n pageSize: positiveInteger(pageSize, 'pageSize'),\n search,\n };\n};\n\n/** Page sources retain loaded state while pendingQuery records newer work. */\nexport function createPageSource<T, TFilter = unknown, TSort = unknown>(\n config: PageSourceConfig<T, TFilter, TSort>,\n): PageSource<T, TFilter, TSort> {\n const initialQuery: PageQuery<TFilter, TSort> = {\n page: 1,\n pageSize: 20,\n search: '',\n };\n let requestedQuery = normalizeQuery(initialQuery, config.initialQuery);\n const asyncSource = createAsyncSource<T, PageQuery<TFilter, TSort>, PagePagination>({\n data: [],\n error: null,\n isFetching: false,\n pagination: createPagePagination(requestedQuery.page, requestedQuery.pageSize, 0),\n query: requestedQuery,\n });\n\n const fetch = (query: PageQuery<TFilter, TSort>): 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 const total = totalItems(result.total);\n const pagination = createPagePagination(query.page, query.pageSize, total);\n const loadedQuery = { ...query, page: pagination.index };\n\n requestedQuery = loadedQuery;\n\n return {\n data: result.data,\n error: null,\n isFetching: false,\n pagination,\n query: loadedQuery,\n };\n },\n });\n\n const reload = (): Promise<void> => fetch(requestedQuery);\n\n const setQuery = async (patch: PageQueryPatch<TFilter, TSort>): Promise<void> => {\n const current = asyncSource.snapshot.pendingQuery ?? asyncSource.snapshot.query;\n const next = normalizeQuery(\n current,\n patch,\n asyncSource.snapshot.pagination.total > 0 ? asyncSource.snapshot.pagination.count : undefined,\n );\n\n if (sameQuery(next, requestedQuery)) return;\n\n requestedQuery = next;\n await fetch(next);\n };\n\n const source: PageSource<T, TFilter, TSort> = {\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 go(index) {\n return asyncSource.snapshot.isFetching ? Promise.resolve() : setQuery({ page: index });\n },\n\n last() {\n return asyncSource.snapshot.isFetching\n ? Promise.resolve()\n : setQuery({ page: asyncSource.snapshot.pagination.count });\n },\n\n next() {\n return asyncSource.snapshot.isFetching || !asyncSource.snapshot.pagination.hasNext\n ? Promise.resolve()\n : setQuery({ page: asyncSource.snapshot.pagination.index + 1 });\n },\n\n previous() {\n return asyncSource.snapshot.isFetching || !asyncSource.snapshot.pagination.hasPrevious\n ? Promise.resolve()\n : setQuery({ page: asyncSource.snapshot.pagination.index - 1 });\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":"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"}
@@ -0,0 +1,4 @@
1
+ import type { PageSource, PageSourceConfig } from './types';
2
+ /** Page sources retain loaded state while pendingQuery records newer work. */
3
+ export declare function createPageSource<T, TFilter = unknown, TSort = unknown>(config: PageSourceConfig<T, TFilter, TSort>): PageSource<T, TFilter, TSort>;
4
+ //# sourceMappingURL=pageSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pageSource.d.ts","sourceRoot":"","sources":["../src/pageSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA6C,UAAU,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAkCvG,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EACpE,MAAM,EAAE,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,GAC1C,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,CA0G/B"}