@vielzeug/sourcerer 1.0.3 → 1.0.5

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.
@@ -1,74 +1,73 @@
1
1
  import { SourcererError as e } from "./errors.js";
2
2
  import { defaultKeyOf as t, extractError as n, retry as r } from "./_utils.js";
3
- import { createAsyncSource as i } from "./asyncSource.js";
3
+ import { createAsyncSearchCoordinator as i } from "./asyncSearch.js";
4
+ import { createAsyncSource as a } from "./asyncSource.js";
4
5
  //#region src/infiniteSource.ts
5
- function a(a) {
6
- let o = a.queryKey ?? t, s = Math.max(1, Math.trunc(a.limit ?? 20)), c = "", l = 1, u = 0, d = [], f = 0, p = null, m = !1, h = null, g = [], _ = {
6
+ function o(o) {
7
+ let s = o.queryKey ?? t, c = Math.max(1, Math.trunc(o.limit ?? 20)), l = "", u = 1, d = 0, f = [], p = 0, m = null, h = !1, g = [], _ = {
7
8
  error: null,
8
9
  hasMore: !1,
9
10
  isLoading: !1,
10
11
  isLoadingMore: !1,
11
12
  isSearchPending: !1,
12
13
  loadedPages: 0,
13
- pageSize: s,
14
+ pageSize: c,
14
15
  totalItems: 0
15
16
  }, v = () => {
16
- g = d, _ = {
17
- error: p,
18
- hasMore: d.length < f,
19
- isLoading: y.pendingCount() > 0 && !m,
20
- isLoadingMore: m,
17
+ g = f, _ = {
18
+ error: m,
19
+ hasMore: f.length < p,
20
+ isLoading: y.pendingCount() > 0 && !h,
21
+ isLoadingMore: h,
21
22
  isSearchPending: y.core.isScheduled,
22
- loadedPages: u,
23
- pageSize: s,
24
- totalItems: f
23
+ loadedPages: d,
24
+ pageSize: c,
25
+ totalItems: p
25
26
  };
26
- }, y = i(a, o, v), { autoFetch: b, debounceMs: x, retryAttempts: S, retryDelay: C } = y;
27
+ }, y = a(o, s, v), { autoFetch: b, debounceMs: x, retryAttempts: S, retryDelay: C } = y;
27
28
  v();
28
29
  let w = () => {
29
30
  y.disposed || y.core.notify();
30
- }, T = () => {
31
- h &&= (h.resolve(), null);
32
- }, E = (t, i) => {
33
- let o = {
34
- ...c && { search: c },
35
- limit: s,
31
+ }, T = i(y.core, w), E = (t, i) => {
32
+ let a = {
33
+ ...l && { search: l },
34
+ limit: c,
36
35
  page: t
37
36
  };
38
- return p = null, y.fetch(o, async (o, s, c) => {
39
- let h = Date.now();
37
+ return m = null, y.fetch(a, async (a, s, c) => {
38
+ let l = Date.now();
40
39
  try {
41
- let e = await r((e) => a.fetch(o, e), {
40
+ let e = await r((e) => o.fetch(a, e), {
42
41
  delay: C,
43
42
  signal: s,
44
43
  times: S + 1
45
44
  });
46
- c() && (d = i ? [...d, ...e.items] : e.items, f = e.total, l = t, u = i ? u + 1 : 1, p = null, a.onFetch?.({
47
- durationMs: Date.now() - h,
48
- query: o,
45
+ c() && (f = i ? [...f, ...e.items] : e.items, p = e.total, u = t, d = i ? d + 1 : 1, m = null, o.onFetch?.({
46
+ durationMs: Date.now() - l,
47
+ query: a,
49
48
  status: "success"
50
49
  }));
51
50
  } catch (t) {
52
51
  if (s.aborted) return;
53
- c() && (i || (d = []), f = i ? f : 0, p = new e(n(t), {
52
+ c() && (i || (f = []), p = i ? p : 0, m = new e(n(t), {
54
53
  cause: t,
55
54
  context: {
56
55
  kind: "infinite",
57
- limit: o.limit,
58
- page: o.page,
59
- ...o.search && { search: o.search }
56
+ limit: a.limit,
57
+ page: a.page,
58
+ ...a.search && { search: a.search }
60
59
  }
61
- }), a.onFetch?.({
62
- durationMs: Date.now() - h,
63
- error: p,
64
- query: o,
60
+ }), o.onFetch?.({
61
+ durationMs: Date.now() - l,
62
+ error: m,
63
+ query: a,
65
64
  status: "error"
66
65
  }));
67
66
  } finally {
68
- m = !1;
67
+ h = !1;
69
68
  }
70
69
  }, () => {
71
- w(), y.pendingCount() === 0 && T();
70
+ w(), T.settleIfIdle(y.pendingCount());
72
71
  });
73
72
  }, D = () => E(1, !1);
74
73
  return b && D(), y.startRefreshInterval(() => void D()), {
@@ -79,13 +78,13 @@ function a(a) {
79
78
  return y.disposalSignal;
80
79
  },
81
80
  dispose: () => {
82
- T(), y.dispose();
81
+ T.cancel(), T.dispose(), y.dispose();
83
82
  },
84
83
  get disposed() {
85
84
  return y.disposed;
86
85
  },
87
86
  loadMore() {
88
- return !_.hasMore || y.pendingCount() > 0 || m ? Promise.resolve() : (m = !0, v(), E(l + 1, !0));
87
+ return !_.hasMore || y.pendingCount() > 0 || h ? Promise.resolve() : (h = !0, v(), E(u + 1, !0));
89
88
  },
90
89
  get meta() {
91
90
  return _;
@@ -94,42 +93,33 @@ function a(a) {
94
93
  let t = !1;
95
94
  if (e.limit !== void 0) {
96
95
  let n = Math.max(1, Math.trunc(e.limit));
97
- n !== s && (s = n, t = !0);
96
+ n !== c && (c = n, t = !0);
98
97
  }
99
- return "search" in e && (e.search !== c || y.core.isScheduled) && (c = e.search ?? "", t = !0), t ? (y.core.cancelTimer(), T(), d = [], f = 0, l = 1, u = 0, D()) : Promise.resolve();
98
+ return "search" in e && (e.search !== l || y.core.isScheduled) && (l = e.search ?? "", t = !0), t ? (T.cancel(), f = [], p = 0, u = 1, d = 0, D()) : Promise.resolve();
100
99
  },
101
100
  get query() {
102
101
  return {
103
- ...c && { search: c },
104
- limit: s,
105
- page: l
102
+ ...l && { search: l },
103
+ limit: c,
104
+ page: u
106
105
  };
107
106
  },
108
107
  ready(e) {
109
108
  return y.ready(e);
110
109
  },
111
110
  reset() {
112
- return y.core.cancelTimer(), T(), l = 1, u = 0, d = [], f = 0, c = "", D();
111
+ return T.cancel(), u = 1, d = 0, f = [], p = 0, l = "", D();
113
112
  },
114
113
  search(e, t) {
115
- if (t?.immediate) return e === c && !y.core.isScheduled ? Promise.resolve() : (y.core.cancelTimer(), T(), c = e, d = [], f = 0, u = 0, l = 1, D());
116
- if (e === c) return Promise.resolve();
117
- T(), c = e, d = [], f = 0, u = 0, l = 1;
118
- let n, r = new Promise((e) => {
119
- n = e;
120
- });
121
- return h = {
122
- promise: r,
123
- resolve: n
124
- }, y.core.schedule(() => void D(), x), w(), r;
114
+ return t?.immediate ? e === l && !y.core.isScheduled ? Promise.resolve() : (T.cancel(), l = e, f = [], p = 0, d = 0, u = 1, D()) : e === l ? Promise.resolve() : (l = e, f = [], p = 0, d = 0, u = 1, T.schedule(() => void D(), x));
125
115
  },
126
116
  subscribe: (e) => y.core.subscribe(e),
127
117
  [Symbol.dispose]: () => {
128
- T(), y.dispose();
118
+ T.cancel(), T.dispose(), y.dispose();
129
119
  }
130
120
  };
131
121
  }
132
122
  //#endregion
133
- export { a as createInfiniteSource };
123
+ export { o as createInfiniteSource };
134
124
 
135
125
  //# sourceMappingURL=infiniteSource.js.map
@@ -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 { defaultKeyOf, extractError, retry } from './_utils';\nimport { createAsyncSource } from './asyncSource';\nimport { SourcererError } from './errors';\n\ntype PendingSearch = { promise: Promise<void>; resolve: () => void };\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 = Math.max(1, Math.trunc(cfg.limit ?? 20));\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 let pendingSearch: PendingSearch | null = null;\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\n const resolvePendingSearch = () => {\n if (pendingSearch) {\n pendingSearch.resolve();\n pendingSearch = null;\n }\n };\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 if (base.pendingCount() === 0) resolvePendingSearch();\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 resolvePendingSearch();\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 = Math.max(1, Math.trunc(changes.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 base.core.cancelTimer();\n resolvePendingSearch();\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 base.core.cancelTimer();\n resolvePendingSearch();\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 base.core.cancelTimer();\n resolvePendingSearch();\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 resolvePendingSearch();\n\n search = q;\n items = [];\n total = 0;\n loadedPages = 0;\n currentPage = 1;\n\n let resolveSearch!: () => void;\n const promise = new Promise<void>((res) => {\n resolveSearch = res;\n });\n\n pendingSearch = { promise, resolve: resolveSearch };\n\n base.core.schedule(() => void doFetch(), debounceMs);\n notifyListeners();\n\n return promise;\n },\n\n subscribe: (listener) => base.core.subscribe(listener),\n\n [Symbol.dispose]: () => {\n resolvePendingSearch();\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 { InfiniteConfig, InfiniteMeta, InfiniteSource, InfiniteSourceQuery, SearchOptions } from './types';\n\nimport { 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 = Math.max(1, Math.trunc(cfg.limit ?? 20));\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 = Math.max(1, Math.trunc(changes.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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"}
package/dist/presets.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=(e,t,n=!1)=>{let r=n?t:t.toLowerCase();return t=>{let i=e(t)??``;return(n?i:i.toLowerCase()).includes(r)}},t=(e,t)=>n=>Object.is(e(n),t),n=(e,t)=>{let n=e=>e instanceof Date?e.getTime():e,r=t.min===void 0?void 0:n(t.min),i=t.max===void 0?void 0:n(t.max);return t=>{let a=n(e(t));return!(r!==void 0&&a<r||i!==void 0&&a>i)}},r=(e,t=`asc`)=>{let n=t===`asc`?1:-1;return(t,r)=>{let i=e(t),a=e(r);return i instanceof Date&&a instanceof Date?(i.getTime()-a.getTime())*n:i<a?-1*n:i>a?1*n:0}};exports.filterContains=e,exports.filterEquals=t,exports.filterRange=n,exports.sortBy=r;
1
+ var e=e=>{if(e==null)return``;if(typeof e==`bigint`||typeof e==`boolean`||typeof e==`number`||typeof e==`string`)return String(e);if(e instanceof Date)return e.toISOString();try{return JSON.stringify(e)??``}catch{return String(e)}},t=(e,t,n=!1)=>{let r=n?t:t.toLowerCase();return t=>{let i=e(t)??``;return(n?i:i.toLowerCase()).includes(r)}},n=(e,t)=>n=>Object.is(e(n),t),r=(e,t)=>{let n=e=>e instanceof Date?e.getTime():e,r=t.min===void 0?void 0:n(t.min),i=t.max===void 0?void 0:n(t.max);return t=>{let a=n(e(t));return!(r!==void 0&&a<r||i!==void 0&&a>i)}},i=(e,t=`asc`)=>{let n=t===`asc`?1:-1;return(t,r)=>{let i=e(t),a=e(r);return i instanceof Date&&a instanceof Date?(i.getTime()-a.getTime())*n:i<a?-1*n:i>a?1*n:0}},a=(t,n={})=>{let r=Array.isArray(t)?t:[t],i=n.caseSensitive===!0;return(t,n)=>{if(n.length===0)return t;let a=i?n:n.toLowerCase();return t.filter(t=>r.some(n=>{let r=e(n(t));return(i?r:r.toLowerCase()).includes(a)}))}};exports.filterContains=t,exports.filterEquals=n,exports.filterRange=r,exports.searchBy=a,exports.sortBy=i;
2
2
  //# sourceMappingURL=presets.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"presets.cjs","names":[],"sources":["../src/presets.ts"],"sourcesContent":["import type { Predicate, Sorter } from './types';\n\ntype Direction = 'asc' | 'desc';\n\ntype PrimitiveComparable = number | string | Date;\n\nexport const filterContains = <T>(\n getValue: (item: T) => string | null | undefined,\n query: string,\n caseSensitive = false,\n): Predicate<T> => {\n const needle = caseSensitive ? query : query.toLowerCase();\n\n return (item) => {\n const value = getValue(item) ?? '';\n const haystack = caseSensitive ? value : value.toLowerCase();\n\n return haystack.includes(needle);\n };\n};\n\nexport const filterEquals = <T, V>(getValue: (item: T) => V, expected: V): Predicate<T> => {\n return (item) => Object.is(getValue(item), expected);\n};\n\nexport const filterRange = <T>(\n getValue: (item: T) => Date | number,\n bounds: Readonly<{ max?: Date | number; min?: Date | number }>,\n): Predicate<T> => {\n const toNum = (v: Date | number): number => (v instanceof Date ? v.getTime() : v);\n\n const min = bounds.min !== undefined ? toNum(bounds.min) : undefined;\n const max = bounds.max !== undefined ? toNum(bounds.max) : undefined;\n\n return (item) => {\n const value = toNum(getValue(item));\n\n if (min !== undefined && value < min) return false;\n\n if (max !== undefined && value > max) return false;\n\n return true;\n };\n};\n\nexport const sortBy = <T, V extends PrimitiveComparable>(\n getValue: (item: T) => V,\n direction: Direction = 'asc',\n): Sorter<T> => {\n const sign = direction === 'asc' ? 1 : -1;\n\n return (a, b) => {\n const left = getValue(a);\n const right = getValue(b);\n\n if (left instanceof Date && right instanceof Date) {\n return (left.getTime() - right.getTime()) * sign;\n }\n\n if (left < right) return -1 * sign;\n\n if (left > right) return 1 * sign;\n\n return 0;\n };\n};\n"],"mappings":"AAMA,IAAa,GACX,EACA,EACA,EAAgB,KACC,CACjB,IAAM,EAAS,EAAgB,EAAQ,EAAM,YAAY,EAEzD,MAAQ,IAAS,CACf,IAAM,EAAQ,EAAS,CAAI,GAAK,GAGhC,OAFiB,EAAgB,EAAQ,EAAM,YAAY,EAAA,CAE3C,SAAS,CAAM,CACjC,CACF,EAEa,GAAsB,EAA0B,IACnD,GAAS,OAAO,GAAG,EAAS,CAAI,EAAG,CAAQ,EAGxC,GACX,EACA,IACiB,CACjB,IAAM,EAAS,GAA8B,aAAa,KAAO,EAAE,QAAQ,EAAI,EAEzE,EAAM,EAAO,MAAQ,IAAA,GAAgC,IAAA,GAApB,EAAM,EAAO,GAAG,EACjD,EAAM,EAAO,MAAQ,IAAA,GAAgC,IAAA,GAApB,EAAM,EAAO,GAAG,EAEvD,MAAQ,IAAS,CACf,IAAM,EAAQ,EAAM,EAAS,CAAI,CAAC,EAMlC,MAFA,EAFI,IAAQ,IAAA,IAAa,EAAQ,GAE7B,IAAQ,IAAA,IAAa,EAAQ,EAGnC,CACF,EAEa,GACX,EACA,EAAuB,QACT,CACd,IAAM,EAAO,IAAc,MAAQ,EAAI,GAEvC,OAAQ,EAAG,IAAM,CACf,IAAM,EAAO,EAAS,CAAC,EACjB,EAAQ,EAAS,CAAC,EAUxB,OARI,aAAgB,MAAQ,aAAiB,MACnC,EAAK,QAAQ,EAAI,EAAM,QAAQ,GAAK,EAG1C,EAAO,EAAc,GAAK,EAE1B,EAAO,EAAc,EAAI,EAEtB,CACT,CACF"}
1
+ {"version":3,"file":"presets.cjs","names":[],"sources":["../src/presets.ts"],"sourcesContent":["import type { Predicate, Sorter } from './types';\n\ntype Direction = 'asc' | 'desc';\n\ntype PrimitiveComparable = number | string | Date;\ntype SearchByGetter<T> = (item: T) => unknown;\ntype SearchByOptions = Readonly<{ caseSensitive?: boolean }>;\n\nconst toSearchableString = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n\n if (\n typeof value === 'bigint' ||\n typeof value === 'boolean' ||\n typeof value === 'number' ||\n typeof value === 'string'\n ) {\n return String(value);\n }\n\n if (value instanceof Date) return value.toISOString();\n\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return String(value);\n }\n};\n\nexport const filterContains = <T>(\n getValue: (item: T) => string | null | undefined,\n query: string,\n caseSensitive = false,\n): Predicate<T> => {\n const needle = caseSensitive ? query : query.toLowerCase();\n\n return (item) => {\n const value = getValue(item) ?? '';\n const haystack = caseSensitive ? value : value.toLowerCase();\n\n return haystack.includes(needle);\n };\n};\n\nexport const filterEquals = <T, V>(getValue: (item: T) => V, expected: V): Predicate<T> => {\n return (item) => Object.is(getValue(item), expected);\n};\n\nexport const filterRange = <T>(\n getValue: (item: T) => Date | number,\n bounds: Readonly<{ max?: Date | number; min?: Date | number }>,\n): Predicate<T> => {\n const toNum = (v: Date | number): number => (v instanceof Date ? v.getTime() : v);\n\n const min = bounds.min !== undefined ? toNum(bounds.min) : undefined;\n const max = bounds.max !== undefined ? toNum(bounds.max) : undefined;\n\n return (item) => {\n const value = toNum(getValue(item));\n\n if (min !== undefined && value < min) return false;\n\n if (max !== undefined && value > max) return false;\n\n return true;\n };\n};\n\nexport const sortBy = <T, V extends PrimitiveComparable>(\n getValue: (item: T) => V,\n direction: Direction = 'asc',\n): Sorter<T> => {\n const sign = direction === 'asc' ? 1 : -1;\n\n return (a, b) => {\n const left = getValue(a);\n const right = getValue(b);\n\n if (left instanceof Date && right instanceof Date) {\n return (left.getTime() - right.getTime()) * sign;\n }\n\n if (left < right) return -1 * sign;\n\n if (left > right) return 1 * sign;\n\n return 0;\n };\n};\n\nexport const searchBy = <T>(\n getValue: SearchByGetter<T> | readonly SearchByGetter<T>[],\n opts: SearchByOptions = {},\n): ((items: readonly T[], query: string) => readonly T[]) => {\n const getters = Array.isArray(getValue) ? getValue : [getValue];\n const caseSensitive = opts.caseSensitive === true;\n\n return (items, query) => {\n if (query.length === 0) return items;\n\n const needle = caseSensitive ? query : query.toLowerCase();\n\n return items.filter((item) => {\n return getters.some((getter) => {\n const raw = toSearchableString(getter(item));\n const haystack = caseSensitive ? raw : raw.toLowerCase();\n\n return haystack.includes(needle);\n });\n });\n };\n};\n"],"mappings":"AAQA,IAAM,EAAsB,GAA2B,CACrD,GAAI,GAAU,KAA6B,MAAO,GAElD,GACE,OAAO,GAAU,UACjB,OAAO,GAAU,WACjB,OAAO,GAAU,UACjB,OAAO,GAAU,SAEjB,OAAO,OAAO,CAAK,EAGrB,GAAI,aAAiB,KAAM,OAAO,EAAM,YAAY,EAEpD,GAAI,CACF,OAAO,KAAK,UAAU,CAAK,GAAK,EAClC,MAAQ,CACN,OAAO,OAAO,CAAK,CACrB,CACF,EAEa,GACX,EACA,EACA,EAAgB,KACC,CACjB,IAAM,EAAS,EAAgB,EAAQ,EAAM,YAAY,EAEzD,MAAQ,IAAS,CACf,IAAM,EAAQ,EAAS,CAAI,GAAK,GAGhC,OAFiB,EAAgB,EAAQ,EAAM,YAAY,EAAA,CAE3C,SAAS,CAAM,CACjC,CACF,EAEa,GAAsB,EAA0B,IACnD,GAAS,OAAO,GAAG,EAAS,CAAI,EAAG,CAAQ,EAGxC,GACX,EACA,IACiB,CACjB,IAAM,EAAS,GAA8B,aAAa,KAAO,EAAE,QAAQ,EAAI,EAEzE,EAAM,EAAO,MAAQ,IAAA,GAAgC,IAAA,GAApB,EAAM,EAAO,GAAG,EACjD,EAAM,EAAO,MAAQ,IAAA,GAAgC,IAAA,GAApB,EAAM,EAAO,GAAG,EAEvD,MAAQ,IAAS,CACf,IAAM,EAAQ,EAAM,EAAS,CAAI,CAAC,EAMlC,MAFA,EAFI,IAAQ,IAAA,IAAa,EAAQ,GAE7B,IAAQ,IAAA,IAAa,EAAQ,EAGnC,CACF,EAEa,GACX,EACA,EAAuB,QACT,CACd,IAAM,EAAO,IAAc,MAAQ,EAAI,GAEvC,OAAQ,EAAG,IAAM,CACf,IAAM,EAAO,EAAS,CAAC,EACjB,EAAQ,EAAS,CAAC,EAUxB,OARI,aAAgB,MAAQ,aAAiB,MACnC,EAAK,QAAQ,EAAI,EAAM,QAAQ,GAAK,EAG1C,EAAO,EAAc,GAAK,EAE1B,EAAO,EAAc,EAAI,EAEtB,CACT,CACF,EAEa,GACX,EACA,EAAwB,CAAC,IACkC,CAC3D,IAAM,EAAU,MAAM,QAAQ,CAAQ,EAAI,EAAW,CAAC,CAAQ,EACxD,EAAgB,EAAK,gBAAkB,GAE7C,OAAQ,EAAO,IAAU,CACvB,GAAI,EAAM,SAAW,EAAG,OAAO,EAE/B,IAAM,EAAS,EAAgB,EAAQ,EAAM,YAAY,EAEzD,OAAO,EAAM,OAAQ,GACZ,EAAQ,KAAM,GAAW,CAC9B,IAAM,EAAM,EAAmB,EAAO,CAAI,CAAC,EAG3C,OAFiB,EAAgB,EAAM,EAAI,YAAY,EAAA,CAEvC,SAAS,CAAM,CACjC,CAAC,CACF,CACH,CACF"}
package/dist/presets.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import type { Predicate, Sorter } from './types';
2
2
  type Direction = 'asc' | 'desc';
3
3
  type PrimitiveComparable = number | string | Date;
4
+ type SearchByGetter<T> = (item: T) => unknown;
5
+ type SearchByOptions = Readonly<{
6
+ caseSensitive?: boolean;
7
+ }>;
4
8
  export declare const filterContains: <T>(getValue: (item: T) => string | null | undefined, query: string, caseSensitive?: boolean) => Predicate<T>;
5
9
  export declare const filterEquals: <T, V>(getValue: (item: T) => V, expected: V) => Predicate<T>;
6
10
  export declare const filterRange: <T>(getValue: (item: T) => Date | number, bounds: Readonly<{
@@ -8,5 +12,6 @@ export declare const filterRange: <T>(getValue: (item: T) => Date | number, boun
8
12
  min?: Date | number;
9
13
  }>) => Predicate<T>;
10
14
  export declare const sortBy: <T, V extends PrimitiveComparable>(getValue: (item: T) => V, direction?: Direction) => Sorter<T>;
15
+ export declare const searchBy: <T>(getValue: SearchByGetter<T> | readonly SearchByGetter<T>[], opts?: SearchByOptions) => ((items: readonly T[], query: string) => readonly T[]);
11
16
  export {};
12
17
  //# sourceMappingURL=presets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjD,KAAK,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC;AAEhC,KAAK,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAElD,eAAO,MAAM,cAAc,GAAI,CAAC,EAC9B,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,EAChD,OAAO,MAAM,EACb,uBAAqB,KACpB,SAAS,CAAC,CAAC,CASb,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,KAAG,SAAS,CAAC,CAAC,CAErF,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,CAAC,EAC3B,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,EACpC,QAAQ,QAAQ,CAAC;IAAE,GAAG,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;CAAE,CAAC,KAC7D,SAAS,CAAC,CAAC,CAeb,CAAC;AAEF,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,CAAC,SAAS,mBAAmB,EACrD,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,EACxB,YAAW,SAAiB,KAC3B,MAAM,CAAC,CAAC,CAiBV,CAAC"}
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjD,KAAK,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC;AAEhC,KAAK,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAClD,KAAK,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;AAC9C,KAAK,eAAe,GAAG,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAuB7D,eAAO,MAAM,cAAc,GAAI,CAAC,EAC9B,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,EAChD,OAAO,MAAM,EACb,uBAAqB,KACpB,SAAS,CAAC,CAAC,CASb,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,KAAG,SAAS,CAAC,CAAC,CAErF,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,CAAC,EAC3B,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,EACpC,QAAQ,QAAQ,CAAC;IAAE,GAAG,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;CAAE,CAAC,KAC7D,SAAS,CAAC,CAAC,CAeb,CAAC;AAEF,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,CAAC,SAAS,mBAAmB,EACrD,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,EACxB,YAAW,SAAiB,KAC3B,MAAM,CAAC,CAAC,CAiBV,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,UAAU,cAAc,CAAC,CAAC,CAAC,GAAG,SAAS,cAAc,CAAC,CAAC,CAAC,EAAE,EAC1D,OAAM,eAAoB,KACzB,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC,EAAE,CAkBvD,CAAC"}
package/dist/presets.js CHANGED
@@ -1,24 +1,43 @@
1
1
  //#region src/presets.ts
2
- var e = (e, t, n = !1) => {
2
+ var e = (e) => {
3
+ if (e == null) return "";
4
+ if (typeof e == "bigint" || typeof e == "boolean" || typeof e == "number" || typeof e == "string") return String(e);
5
+ if (e instanceof Date) return e.toISOString();
6
+ try {
7
+ return JSON.stringify(e) ?? "";
8
+ } catch {
9
+ return String(e);
10
+ }
11
+ }, t = (e, t, n = !1) => {
3
12
  let r = n ? t : t.toLowerCase();
4
13
  return (t) => {
5
14
  let i = e(t) ?? "";
6
15
  return (n ? i : i.toLowerCase()).includes(r);
7
16
  };
8
- }, t = (e, t) => (n) => Object.is(e(n), t), n = (e, t) => {
17
+ }, n = (e, t) => (n) => Object.is(e(n), t), r = (e, t) => {
9
18
  let n = (e) => e instanceof Date ? e.getTime() : e, r = t.min === void 0 ? void 0 : n(t.min), i = t.max === void 0 ? void 0 : n(t.max);
10
19
  return (t) => {
11
20
  let a = n(e(t));
12
21
  return !(r !== void 0 && a < r || i !== void 0 && a > i);
13
22
  };
14
- }, r = (e, t = "asc") => {
23
+ }, i = (e, t = "asc") => {
15
24
  let n = t === "asc" ? 1 : -1;
16
25
  return (t, r) => {
17
26
  let i = e(t), a = e(r);
18
27
  return i instanceof Date && a instanceof Date ? (i.getTime() - a.getTime()) * n : i < a ? -1 * n : i > a ? 1 * n : 0;
19
28
  };
29
+ }, a = (t, n = {}) => {
30
+ let r = Array.isArray(t) ? t : [t], i = n.caseSensitive === !0;
31
+ return (t, n) => {
32
+ if (n.length === 0) return t;
33
+ let a = i ? n : n.toLowerCase();
34
+ return t.filter((t) => r.some((n) => {
35
+ let r = e(n(t));
36
+ return (i ? r : r.toLowerCase()).includes(a);
37
+ }));
38
+ };
20
39
  };
21
40
  //#endregion
22
- export { e as filterContains, t as filterEquals, n as filterRange, r as sortBy };
41
+ export { t as filterContains, n as filterEquals, r as filterRange, a as searchBy, i as sortBy };
23
42
 
24
43
  //# sourceMappingURL=presets.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"presets.js","names":[],"sources":["../src/presets.ts"],"sourcesContent":["import type { Predicate, Sorter } from './types';\n\ntype Direction = 'asc' | 'desc';\n\ntype PrimitiveComparable = number | string | Date;\n\nexport const filterContains = <T>(\n getValue: (item: T) => string | null | undefined,\n query: string,\n caseSensitive = false,\n): Predicate<T> => {\n const needle = caseSensitive ? query : query.toLowerCase();\n\n return (item) => {\n const value = getValue(item) ?? '';\n const haystack = caseSensitive ? value : value.toLowerCase();\n\n return haystack.includes(needle);\n };\n};\n\nexport const filterEquals = <T, V>(getValue: (item: T) => V, expected: V): Predicate<T> => {\n return (item) => Object.is(getValue(item), expected);\n};\n\nexport const filterRange = <T>(\n getValue: (item: T) => Date | number,\n bounds: Readonly<{ max?: Date | number; min?: Date | number }>,\n): Predicate<T> => {\n const toNum = (v: Date | number): number => (v instanceof Date ? v.getTime() : v);\n\n const min = bounds.min !== undefined ? toNum(bounds.min) : undefined;\n const max = bounds.max !== undefined ? toNum(bounds.max) : undefined;\n\n return (item) => {\n const value = toNum(getValue(item));\n\n if (min !== undefined && value < min) return false;\n\n if (max !== undefined && value > max) return false;\n\n return true;\n };\n};\n\nexport const sortBy = <T, V extends PrimitiveComparable>(\n getValue: (item: T) => V,\n direction: Direction = 'asc',\n): Sorter<T> => {\n const sign = direction === 'asc' ? 1 : -1;\n\n return (a, b) => {\n const left = getValue(a);\n const right = getValue(b);\n\n if (left instanceof Date && right instanceof Date) {\n return (left.getTime() - right.getTime()) * sign;\n }\n\n if (left < right) return -1 * sign;\n\n if (left > right) return 1 * sign;\n\n return 0;\n };\n};\n"],"mappings":";AAMA,IAAa,KACX,GACA,GACA,IAAgB,OACC;CACjB,IAAM,IAAS,IAAgB,IAAQ,EAAM,YAAY;CAEzD,QAAQ,MAAS;EACf,IAAM,IAAQ,EAAS,CAAI,KAAK;EAGhC,QAFiB,IAAgB,IAAQ,EAAM,YAAY,EAAA,CAE3C,SAAS,CAAM;CACjC;AACF,GAEa,KAAsB,GAA0B,OACnD,MAAS,OAAO,GAAG,EAAS,CAAI,GAAG,CAAQ,GAGxC,KACX,GACA,MACiB;CACjB,IAAM,KAAS,MAA8B,aAAa,OAAO,EAAE,QAAQ,IAAI,GAEzE,IAAM,EAAO,QAAQ,KAAA,IAAgC,KAAA,IAApB,EAAM,EAAO,GAAG,GACjD,IAAM,EAAO,QAAQ,KAAA,IAAgC,KAAA,IAApB,EAAM,EAAO,GAAG;CAEvD,QAAQ,MAAS;EACf,IAAM,IAAQ,EAAM,EAAS,CAAI,CAAC;EAMlC,OAFA,EAFI,MAAQ,KAAA,KAAa,IAAQ,KAE7B,MAAQ,KAAA,KAAa,IAAQ;CAGnC;AACF,GAEa,KACX,GACA,IAAuB,UACT;CACd,IAAM,IAAO,MAAc,QAAQ,IAAI;CAEvC,QAAQ,GAAG,MAAM;EACf,IAAM,IAAO,EAAS,CAAC,GACjB,IAAQ,EAAS,CAAC;EAUxB,OARI,aAAgB,QAAQ,aAAiB,QACnC,EAAK,QAAQ,IAAI,EAAM,QAAQ,KAAK,IAG1C,IAAO,IAAc,KAAK,IAE1B,IAAO,IAAc,IAAI,IAEtB;CACT;AACF"}
1
+ {"version":3,"file":"presets.js","names":[],"sources":["../src/presets.ts"],"sourcesContent":["import type { Predicate, Sorter } from './types';\n\ntype Direction = 'asc' | 'desc';\n\ntype PrimitiveComparable = number | string | Date;\ntype SearchByGetter<T> = (item: T) => unknown;\ntype SearchByOptions = Readonly<{ caseSensitive?: boolean }>;\n\nconst toSearchableString = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n\n if (\n typeof value === 'bigint' ||\n typeof value === 'boolean' ||\n typeof value === 'number' ||\n typeof value === 'string'\n ) {\n return String(value);\n }\n\n if (value instanceof Date) return value.toISOString();\n\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return String(value);\n }\n};\n\nexport const filterContains = <T>(\n getValue: (item: T) => string | null | undefined,\n query: string,\n caseSensitive = false,\n): Predicate<T> => {\n const needle = caseSensitive ? query : query.toLowerCase();\n\n return (item) => {\n const value = getValue(item) ?? '';\n const haystack = caseSensitive ? value : value.toLowerCase();\n\n return haystack.includes(needle);\n };\n};\n\nexport const filterEquals = <T, V>(getValue: (item: T) => V, expected: V): Predicate<T> => {\n return (item) => Object.is(getValue(item), expected);\n};\n\nexport const filterRange = <T>(\n getValue: (item: T) => Date | number,\n bounds: Readonly<{ max?: Date | number; min?: Date | number }>,\n): Predicate<T> => {\n const toNum = (v: Date | number): number => (v instanceof Date ? v.getTime() : v);\n\n const min = bounds.min !== undefined ? toNum(bounds.min) : undefined;\n const max = bounds.max !== undefined ? toNum(bounds.max) : undefined;\n\n return (item) => {\n const value = toNum(getValue(item));\n\n if (min !== undefined && value < min) return false;\n\n if (max !== undefined && value > max) return false;\n\n return true;\n };\n};\n\nexport const sortBy = <T, V extends PrimitiveComparable>(\n getValue: (item: T) => V,\n direction: Direction = 'asc',\n): Sorter<T> => {\n const sign = direction === 'asc' ? 1 : -1;\n\n return (a, b) => {\n const left = getValue(a);\n const right = getValue(b);\n\n if (left instanceof Date && right instanceof Date) {\n return (left.getTime() - right.getTime()) * sign;\n }\n\n if (left < right) return -1 * sign;\n\n if (left > right) return 1 * sign;\n\n return 0;\n };\n};\n\nexport const searchBy = <T>(\n getValue: SearchByGetter<T> | readonly SearchByGetter<T>[],\n opts: SearchByOptions = {},\n): ((items: readonly T[], query: string) => readonly T[]) => {\n const getters = Array.isArray(getValue) ? getValue : [getValue];\n const caseSensitive = opts.caseSensitive === true;\n\n return (items, query) => {\n if (query.length === 0) return items;\n\n const needle = caseSensitive ? query : query.toLowerCase();\n\n return items.filter((item) => {\n return getters.some((getter) => {\n const raw = toSearchableString(getter(item));\n const haystack = caseSensitive ? raw : raw.toLowerCase();\n\n return haystack.includes(needle);\n });\n });\n };\n};\n"],"mappings":";AAQA,IAAM,KAAsB,MAA2B;CACrD,IAAI,KAAU,MAA6B,OAAO;CAElD,IACE,OAAO,KAAU,YACjB,OAAO,KAAU,aACjB,OAAO,KAAU,YACjB,OAAO,KAAU,UAEjB,OAAO,OAAO,CAAK;CAGrB,IAAI,aAAiB,MAAM,OAAO,EAAM,YAAY;CAEpD,IAAI;EACF,OAAO,KAAK,UAAU,CAAK,KAAK;CAClC,QAAQ;EACN,OAAO,OAAO,CAAK;CACrB;AACF,GAEa,KACX,GACA,GACA,IAAgB,OACC;CACjB,IAAM,IAAS,IAAgB,IAAQ,EAAM,YAAY;CAEzD,QAAQ,MAAS;EACf,IAAM,IAAQ,EAAS,CAAI,KAAK;EAGhC,QAFiB,IAAgB,IAAQ,EAAM,YAAY,EAAA,CAE3C,SAAS,CAAM;CACjC;AACF,GAEa,KAAsB,GAA0B,OACnD,MAAS,OAAO,GAAG,EAAS,CAAI,GAAG,CAAQ,GAGxC,KACX,GACA,MACiB;CACjB,IAAM,KAAS,MAA8B,aAAa,OAAO,EAAE,QAAQ,IAAI,GAEzE,IAAM,EAAO,QAAQ,KAAA,IAAgC,KAAA,IAApB,EAAM,EAAO,GAAG,GACjD,IAAM,EAAO,QAAQ,KAAA,IAAgC,KAAA,IAApB,EAAM,EAAO,GAAG;CAEvD,QAAQ,MAAS;EACf,IAAM,IAAQ,EAAM,EAAS,CAAI,CAAC;EAMlC,OAFA,EAFI,MAAQ,KAAA,KAAa,IAAQ,KAE7B,MAAQ,KAAA,KAAa,IAAQ;CAGnC;AACF,GAEa,KACX,GACA,IAAuB,UACT;CACd,IAAM,IAAO,MAAc,QAAQ,IAAI;CAEvC,QAAQ,GAAG,MAAM;EACf,IAAM,IAAO,EAAS,CAAC,GACjB,IAAQ,EAAS,CAAC;EAUxB,OARI,aAAgB,QAAQ,aAAiB,QACnC,EAAK,QAAQ,IAAI,EAAM,QAAQ,KAAK,IAG1C,IAAO,IAAc,KAAK,IAE1B,IAAO,IAAc,IAAI,IAEtB;CACT;AACF,GAEa,KACX,GACA,IAAwB,CAAC,MACkC;CAC3D,IAAM,IAAU,MAAM,QAAQ,CAAQ,IAAI,IAAW,CAAC,CAAQ,GACxD,IAAgB,EAAK,kBAAkB;CAE7C,QAAQ,GAAO,MAAU;EACvB,IAAI,EAAM,WAAW,GAAG,OAAO;EAE/B,IAAM,IAAS,IAAgB,IAAQ,EAAM,YAAY;EAEzD,OAAO,EAAM,QAAQ,MACZ,EAAQ,MAAM,MAAW;GAC9B,IAAM,IAAM,EAAmB,EAAO,CAAI,CAAC;GAG3C,QAFiB,IAAgB,IAAM,EAAI,YAAY,EAAA,CAEvC,SAAS,CAAM;EACjC,CAAC,CACF;CACH;AACF"}
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./_utils.cjs"),n=require("./asyncSource.cjs"),r=require("./pagination.cjs");let i=require("@vielzeug/arsenal");function a(a){let o=a.queryKey??t.defaultKeyOf,s=a.staleTime??0,c=Math.max(1,Math.trunc(a.limit??20)),l=1,u=``,d=a.filter,f=a.sort,p=[],m=0,h=null,g=0,_=``,v=null,y=null;a.snapshot&&(p=a.snapshot.items,m=a.snapshot.total,a.snapshot.page!==void 0&&(l=a.snapshot.page),a.snapshot.search&&(u=a.snapshot.search),g=Date.now());let b=[],x=r.createMeta({error:null,isLoading:!1,isSearchPending:!1,pageNumber:1,pageSize:c,totalItems:0}),S=()=>{let e=r.pageCount(m,c),t=r.clampPage(l,e);b=p,x=r.createMeta({error:h,isLoading:C.pendingCount()>0,isSearchPending:C.core.isScheduled,pageNumber:t,pageSize:c,totalItems:m})},C=n.createAsyncSource(a,o,S),{autoFetch:w,debounceMs:T,retryAttempts:E,retryDelay:D}=C;S();let O=()=>{C.disposed||C.core.notify()},k=e=>{y?.active&&(y.active=!1,y=null),p=e.items,m=e.total,l=r.clampPage(l,r.pageCount(m,c))},A=()=>{v&&=(v.resolve(),null)},j=()=>({...d!==void 0&&{filter:d},...f!==void 0&&{sort:f},...u&&{search:u},limit:c,page:l}),M=n=>{let r=o(n);return s>0&&r===_&&m>0&&Date.now()-g<s?Promise.resolve():(h=null,C.fetch(n,async(n,o,s)=>{let c=Date.now();try{let e=await(0,i.retry)(e=>a.fetch(n,e),{delay:D,signal:o,times:E+1});s()&&(k(e),h=null,_=r,g=Date.now(),a.onFetch?.({durationMs:Date.now()-c,query:n,status:`success`}))}catch(r){if(o.aborted)return;s()&&(y?.active?(y.active=!1,p=y.prevItems,m=y.prevTotal,y=null):(p=[],m=0),h=new e.SourcererError(t.extractError(r),{cause:r,context:{kind:`remote`,limit:n.limit,page:n.page,...n.search&&{search:n.search}}}),a.onFetch?.({durationMs:Date.now()-c,error:h,query:n,status:`error`}))}},()=>{O(),C.pendingCount()===0&&A()}))},N=()=>M(j());return w&&N(),C.startRefreshInterval(()=>void N()),{get current(){return b},get disposalSignal(){return C.disposalSignal},dispose:()=>{A(),C.dispose()},get disposed(){return C.disposed},goTo(e){let t=m>0?r.clampPage(e,r.pageCount(m,c)):Math.max(1,Math.trunc(e));return t===l?Promise.resolve():(l=t,N())},goToLast(){let e=r.pageCount(m,c);return l===e?Promise.resolve():(l=e,N())},get meta(){return x},next(){let e=r.pageCount(m,c);return l>=e?Promise.resolve():(l++,N())},optimisticUpdate(t,n){if(y?.active)throw new e.SourcererError(`An optimistic update is already active. Rollback the previous update before starting another.`);let r=p,i=m;y={active:!0,prevItems:r,prevTotal:i};try{p=t(p)}catch(e){throw y=null,e}return m=n?.total??m,O(),()=>{y?.active&&(y.active=!1,p=r,m=i,y=null,O())}},patch(e){let t=!1;if(e.limit!==void 0){let n=Math.max(1,Math.trunc(e.limit));n!==c&&(c=n,t=!0)}if(`filter`in e&&(d=e.filter,t=!0),`sort`in e&&(f=e.sort,t=!0),`search`in e&&(e.search!==u||C.core.isScheduled)&&(u=e.search??``,t=!0),e.page!==void 0){let n=m>0?r.clampPage(e.page,r.pageCount(m,c)):Math.max(1,Math.trunc(e.page));n!==l&&(l=n,t=!0)}return t?(C.core.cancelTimer(),A(),(e.limit!==void 0||`filter`in e||`sort`in e||`search`in e)&&e.page===void 0&&(l=1),N()):Promise.resolve()},prev(){return l<=1?Promise.resolve():(l--,N())},get query(){return{...d!==void 0&&{filter:d},...f!==void 0&&{sort:f},...u&&{search:u},limit:c,page:l}},ready(e){return C.ready(e)},refresh(){return N()},reset(){return C.core.cancelTimer(),A(),u=``,l=1,d=a.filter,f=a.sort,N()},search(e,t){if(t?.immediate)return e===u&&!C.core.isScheduled?Promise.resolve():(C.core.cancelTimer(),A(),u=e,l=1,N());if(e===u)return Promise.resolve();A(),u=e,l=1;let n,r=new Promise(e=>{n=e});return v={promise:r,resolve:n},C.core.schedule(()=>void N(),T),O(),r},subscribe:e=>C.core.subscribe(e),[Symbol.dispose]:()=>{A(),C.dispose()}}}exports.createRemoteSource=a;
1
+ const e=require("./errors.cjs"),t=require("./_utils.cjs"),n=require("./asyncSearch.cjs"),r=require("./asyncSource.cjs"),i=require("./pagination.cjs");let a=require("@vielzeug/arsenal");function o(o){let s=o.queryKey??t.defaultKeyOf,c=o.staleTime??0,l=Math.max(1,Math.trunc(o.limit??20)),u=1,d=``,f=o.filter,p=o.sort,m=[],h=0,g=null,_=0,v=``,y=null;o.snapshot&&(m=o.snapshot.items,h=o.snapshot.total,o.snapshot.page!==void 0&&(u=o.snapshot.page),o.snapshot.search&&(d=o.snapshot.search),_=Date.now());let b=[],x=i.createMeta({error:null,isLoading:!1,isSearchPending:!1,pageNumber:1,pageSize:l,totalItems:0}),S=()=>{let e=i.pageCount(h,l),t=i.clampPage(u,e);b=m,x=i.createMeta({error:g,isLoading:C.pendingCount()>0,isSearchPending:C.core.isScheduled,pageNumber:t,pageSize:l,totalItems:h})},C=r.createAsyncSource(o,s,S),{autoFetch:w,debounceMs:T,retryAttempts:E,retryDelay:D}=C;S();let O=()=>{C.disposed||C.core.notify()},k=n.createAsyncSearchCoordinator(C.core,O),A=e=>{y?.active&&(y.active=!1,y=null),m=e.items,h=e.total,u=i.clampPage(u,i.pageCount(h,l))},j=()=>({...f!==void 0&&{filter:f},...p!==void 0&&{sort:p},...d&&{search:d},limit:l,page:u}),M=n=>{let r=s(n);return c>0&&r===v&&h>0&&Date.now()-_<c&&!y?.active?Promise.resolve():(g=null,C.fetch(n,async(n,i,s)=>{let c=Date.now();try{let e=await(0,a.retry)(e=>o.fetch(n,e),{delay:D,signal:i,times:E+1});s()&&(A(e),g=null,v=r,_=Date.now(),o.onFetch?.({durationMs:Date.now()-c,query:n,status:`success`}))}catch(r){if(i.aborted)return;s()&&(y?.active?(y.active=!1,m=y.prevItems,h=y.prevTotal,y=null):(m=[],h=0),g=new e.SourcererError(t.extractError(r),{cause:r,context:{kind:`remote`,limit:n.limit,page:n.page,...n.search&&{search:n.search}}}),o.onFetch?.({durationMs:Date.now()-c,error:g,query:n,status:`error`}))}},()=>{O(),k.settleIfIdle(C.pendingCount())}))},N=()=>M(j());return w&&N(),C.startRefreshInterval(()=>void N()),{get current(){return b},get disposalSignal(){return C.disposalSignal},dispose:()=>{k.cancel(),k.dispose(),C.dispose()},get disposed(){return C.disposed},goTo(e){let t=h>0?i.clampPage(e,i.pageCount(h,l)):Math.max(1,Math.trunc(e));return t===u?Promise.resolve():(u=t,N())},goToLast(){let e=i.pageCount(h,l);return u===e?Promise.resolve():(u=e,N())},get meta(){return x},next(){let e=i.pageCount(h,l);return u>=e?Promise.resolve():(u++,N())},optimisticUpdate(t,n){if(y?.active)throw new e.SourcererError(`An optimistic update is already active. Rollback the previous update before starting another.`);let r=m,i=h;y={active:!0,prevItems:r,prevTotal:i};try{m=t(m)}catch(e){throw y=null,e}return h=n?.total??h,O(),()=>{y?.active&&(y.active=!1,m=r,h=i,y=null,O())}},patch(e){let t=!1;if(e.limit!==void 0){let n=Math.max(1,Math.trunc(e.limit));n!==l&&(l=n,t=!0)}if(`filter`in e&&(f=e.filter,t=!0),`sort`in e&&(p=e.sort,t=!0),`search`in e&&(e.search!==d||C.core.isScheduled)&&(d=e.search??``,t=!0),e.page!==void 0){let n=h>0?i.clampPage(e.page,i.pageCount(h,l)):Math.max(1,Math.trunc(e.page));n!==u&&(u=n,t=!0)}return t?(k.cancel(),(e.limit!==void 0||`filter`in e||`sort`in e||`search`in e)&&e.page===void 0&&(u=1),N()):Promise.resolve()},prev(){return u<=1?Promise.resolve():(u--,N())},get query(){return{...f!==void 0&&{filter:f},...p!==void 0&&{sort:p},...d&&{search:d},limit:l,page:u}},ready(e){return C.ready(e)},refresh(){return N()},reset(){return k.cancel(),d=``,u=1,f=o.filter,p=o.sort,N()},search(e,t){return t?.immediate?e===d&&!C.core.isScheduled?Promise.resolve():(k.cancel(),d=e,u=1,N()):e===d?Promise.resolve():(d=e,u=1,k.schedule(()=>void N(),T))},subscribe:e=>C.core.subscribe(e),[Symbol.dispose]:()=>{k.cancel(),k.dispose(),C.dispose()}}}exports.createRemoteSource=o;
2
2
  //# sourceMappingURL=remoteSource.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"remoteSource.cjs","names":[],"sources":["../src/remoteSource.ts"],"sourcesContent":["import type { RemoteConfig, RemoteSource, RemoteSourceQuery, SearchOptions } from './types';\n\nimport { defaultKeyOf, extractError, retry } from './_utils';\nimport { createAsyncSource } from './asyncSource';\nimport { SourcererError } from './errors';\nimport { clampPage, createMeta, pageCount } from './pagination';\n\ntype PendingSearch = { promise: Promise<void>; resolve: () => void };\n\n/** Creates a remote page-based source that fetches data from a network endpoint. */\nexport function createRemoteSource<T, TFilter = unknown, TSort = unknown>(\n cfg: RemoteConfig<T, TFilter, TSort>,\n): RemoteSource<T, TFilter, TSort> {\n const keyOf = cfg.queryKey ?? defaultKeyOf;\n\n // ── Config defaults ─────────────────────────────────────────────────────────\n const staleTimeMs = cfg.staleTime ?? 0;\n\n // ── Mutable state ───────────────────────────────────────────────────────────\n let limit = Math.max(1, Math.trunc(cfg.limit ?? 20));\n let page = 1;\n let search = '';\n let filter: TFilter | undefined = cfg.filter;\n let sort: TSort | undefined = cfg.sort;\n let items: readonly T[] = [];\n let total = 0;\n let error: SourcererError | null = null;\n let lastFetchTime = 0;\n let lastFetchKey = '';\n let pendingSearch: PendingSearch | null = null;\n\n // Optimistic update state.\n let optimistic: { active: boolean; prevItems: readonly T[]; prevTotal: number } | null = null;\n\n // ── Snapshot pre-population ─────────────────────────────────────────────────\n if (cfg.snapshot) {\n items = cfg.snapshot.items;\n total = cfg.snapshot.total;\n\n if (cfg.snapshot.page !== undefined) page = cfg.snapshot.page;\n\n if (cfg.snapshot.search) search = cfg.snapshot.search;\n\n lastFetchTime = Date.now();\n }\n\n // ── Cached accessors ────────────────────────────────────────────────────────\n let cachedCurrent: readonly T[] = [];\n let cachedMeta = createMeta({\n error: null,\n isLoading: false,\n isSearchPending: false,\n pageNumber: 1,\n pageSize: limit,\n totalItems: 0,\n });\n\n const refreshMeta = () => {\n const pages = pageCount(total, limit);\n const safePage = clampPage(page, pages);\n\n // items is already the current page from the server — no local slicing needed.\n cachedCurrent = items;\n cachedMeta = createMeta({\n error,\n isLoading: base.pendingCount() > 0,\n isSearchPending: base.core.isScheduled,\n pageNumber: safePage,\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<RemoteSourceQuery<TFilter, TSort>>(cfg, keyOf, refreshMeta);\n const { autoFetch, debounceMs, retryAttempts, retryDelay } = base;\n\n // Initialise cache before first fetch.\n refreshMeta();\n\n const notifyListeners = () => {\n if (base.disposed) return;\n\n base.core.notify();\n };\n\n // ── Assign result ───────────────────────────────────────────────────────────\n const assign = (result: Readonly<{ items: readonly T[]; total: number }>) => {\n if (optimistic?.active) {\n optimistic.active = false;\n optimistic = null;\n }\n\n items = result.items;\n total = result.total;\n page = clampPage(page, pageCount(total, limit));\n };\n\n const resolvePendingSearch = () => {\n if (pendingSearch) {\n pendingSearch.resolve();\n pendingSearch = null;\n }\n };\n\n // ── Core fetch ──────────────────────────────────────────────────────────────\n const toRemoteQuery = (): RemoteSourceQuery<TFilter, TSort> => ({\n ...(filter !== undefined && { filter }),\n ...(sort !== undefined && { sort }),\n ...(search && { search }),\n limit,\n page,\n });\n\n const fetchQuery = (q: RemoteSourceQuery<TFilter, TSort>): Promise<void> => {\n const key = keyOf(q);\n\n if (staleTimeMs > 0 && key === lastFetchKey && total > 0 && Date.now() - lastFetchTime < staleTimeMs) {\n return Promise.resolve();\n }\n\n error = null;\n\n return base.fetch(\n q,\n async (q: RemoteSourceQuery<TFilter, TSort>, 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 assign(result);\n error = null;\n lastFetchKey = key;\n lastFetchTime = Date.now();\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 (optimistic?.active) {\n optimistic.active = false;\n items = optimistic.prevItems;\n total = optimistic.prevTotal;\n optimistic = null;\n } else {\n items = [];\n total = 0;\n }\n\n error = new SourcererError(extractError(reason), {\n cause: reason,\n context: { kind: 'remote', 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 }\n },\n () => {\n notifyListeners();\n\n if (base.pendingCount() === 0) resolvePendingSearch();\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 resolvePendingSearch();\n base.dispose();\n },\n\n get disposed() {\n return base.disposed;\n },\n\n goTo(target) {\n const clamped = total > 0 ? clampPage(target, pageCount(total, limit)) : Math.max(1, Math.trunc(target));\n\n if (clamped === page) return Promise.resolve();\n\n page = clamped;\n\n return doUpdate();\n },\n\n goToLast() {\n const last = pageCount(total, limit);\n\n if (page === last) return Promise.resolve();\n\n page = last;\n\n return doUpdate();\n },\n\n get meta() {\n return cachedMeta;\n },\n\n next() {\n const pages = pageCount(total, limit);\n\n if (page >= pages) return Promise.resolve();\n\n page++;\n\n return doUpdate();\n },\n\n optimisticUpdate(mutator, options) {\n if (optimistic?.active) {\n throw new SourcererError(\n 'An optimistic update is already active. Rollback the previous update before starting another.',\n );\n }\n\n const prevItems = items;\n const prevTotal = total;\n\n optimistic = { active: true, prevItems, prevTotal };\n\n try {\n items = mutator(items);\n } catch (e: unknown) {\n optimistic = null;\n throw e;\n }\n\n total = options?.total ?? total;\n notifyListeners();\n\n return () => {\n if (optimistic?.active) {\n optimistic.active = false;\n items = prevItems;\n total = prevTotal;\n optimistic = null;\n notifyListeners();\n }\n };\n },\n\n patch(changes) {\n let changed = false;\n\n if (changes.limit !== undefined) {\n const next = Math.max(1, Math.trunc(changes.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 // `|| 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 (changes.page !== undefined) {\n const next =\n total > 0 ? clampPage(changes.page, pageCount(total, limit)) : Math.max(1, Math.trunc(changes.page));\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 fetch — cancel any debounced search()\n // still pending so it doesn't fire a second, redundant fetch afterwards.\n base.core.cancelTimer();\n resolvePendingSearch();\n\n // Reset to page 1 when non-page query fields changed without 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 doUpdate();\n },\n\n prev() {\n if (page <= 1) return Promise.resolve();\n\n page--;\n\n return doUpdate();\n },\n\n get query() {\n return {\n ...(filter !== undefined && { filter }),\n ...(sort !== undefined && { sort }),\n ...(search && { search }),\n limit,\n page,\n };\n },\n\n ready(timeout) {\n return base.ready(timeout);\n },\n\n refresh() {\n return doUpdate();\n },\n\n reset() {\n base.core.cancelTimer();\n resolvePendingSearch();\n search = '';\n page = 1;\n filter = cfg.filter;\n sort = cfg.sort;\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 base.core.cancelTimer();\n resolvePendingSearch();\n search = q;\n page = 1;\n\n return doUpdate();\n }\n\n if (q === search) return Promise.resolve();\n\n resolvePendingSearch();\n\n search = q;\n page = 1;\n\n let resolveSearch!: () => void;\n const promise = new Promise<void>((res) => {\n resolveSearch = res;\n });\n\n pendingSearch = { promise, resolve: resolveSearch };\n\n base.core.schedule(() => void doUpdate(), debounceMs);\n notifyListeners();\n\n return promise;\n },\n\n subscribe: (listener) => base.core.subscribe(listener),\n\n [Symbol.dispose]: () => {\n resolvePendingSearch();\n base.dispose();\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"remoteSource.cjs","names":[],"sources":["../src/remoteSource.ts"],"sourcesContent":["import type { RemoteConfig, RemoteSource, RemoteSourceQuery, SearchOptions } from './types';\n\nimport { defaultKeyOf, extractError, retry } from './_utils';\nimport { createAsyncSearchCoordinator } from './asyncSearch';\nimport { createAsyncSource } from './asyncSource';\nimport { SourcererError } from './errors';\nimport { clampPage, createMeta, pageCount } from './pagination';\n\n/** Creates a remote page-based source that fetches data from a network endpoint. */\nexport function createRemoteSource<T, TFilter = unknown, TSort = unknown>(\n cfg: RemoteConfig<T, TFilter, TSort>,\n): RemoteSource<T, TFilter, TSort> {\n const keyOf = cfg.queryKey ?? defaultKeyOf;\n\n // ── Config defaults ─────────────────────────────────────────────────────────\n const staleTimeMs = cfg.staleTime ?? 0;\n\n // ── Mutable state ───────────────────────────────────────────────────────────\n let limit = Math.max(1, Math.trunc(cfg.limit ?? 20));\n let page = 1;\n let search = '';\n let filter: TFilter | undefined = cfg.filter;\n let sort: TSort | undefined = cfg.sort;\n let items: readonly T[] = [];\n let total = 0;\n let error: SourcererError | null = null;\n let lastFetchTime = 0;\n let lastFetchKey = '';\n\n // Optimistic update state.\n let optimistic: { active: boolean; prevItems: readonly T[]; prevTotal: number } | null = null;\n\n // ── Snapshot pre-population ─────────────────────────────────────────────────\n if (cfg.snapshot) {\n items = cfg.snapshot.items;\n total = cfg.snapshot.total;\n\n if (cfg.snapshot.page !== undefined) page = cfg.snapshot.page;\n\n if (cfg.snapshot.search) search = cfg.snapshot.search;\n\n lastFetchTime = Date.now();\n }\n\n // ── Cached accessors ────────────────────────────────────────────────────────\n let cachedCurrent: readonly T[] = [];\n let cachedMeta = createMeta({\n error: null,\n isLoading: false,\n isSearchPending: false,\n pageNumber: 1,\n pageSize: limit,\n totalItems: 0,\n });\n\n const refreshMeta = () => {\n const pages = pageCount(total, limit);\n const safePage = clampPage(page, pages);\n\n // items is already the current page from the server — no local slicing needed.\n cachedCurrent = items;\n cachedMeta = createMeta({\n error,\n isLoading: base.pendingCount() > 0,\n isSearchPending: base.core.isScheduled,\n pageNumber: safePage,\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<RemoteSourceQuery<TFilter, TSort>>(cfg, keyOf, refreshMeta);\n const { autoFetch, debounceMs, retryAttempts, retryDelay } = base;\n\n // Initialise cache before first fetch.\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 // ── Assign result ───────────────────────────────────────────────────────────\n const assign = (result: Readonly<{ items: readonly T[]; total: number }>) => {\n if (optimistic?.active) {\n optimistic.active = false;\n optimistic = null;\n }\n\n items = result.items;\n total = result.total;\n page = clampPage(page, pageCount(total, limit));\n };\n\n // ── Core fetch ──────────────────────────────────────────────────────────────\n const toRemoteQuery = (): RemoteSourceQuery<TFilter, TSort> => ({\n ...(filter !== undefined && { filter }),\n ...(sort !== undefined && { sort }),\n ...(search && { search }),\n limit,\n page,\n });\n\n const fetchQuery = (q: RemoteSourceQuery<TFilter, TSort>): Promise<void> => {\n const key = keyOf(q);\n\n if (\n staleTimeMs > 0 &&\n key === lastFetchKey &&\n total > 0 &&\n Date.now() - lastFetchTime < staleTimeMs &&\n !optimistic?.active\n ) {\n return Promise.resolve();\n }\n\n error = null;\n\n return base.fetch(\n q,\n async (q: RemoteSourceQuery<TFilter, TSort>, 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 assign(result);\n error = null;\n lastFetchKey = key;\n lastFetchTime = Date.now();\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 (optimistic?.active) {\n optimistic.active = false;\n items = optimistic.prevItems;\n total = optimistic.prevTotal;\n optimistic = null;\n } else {\n items = [];\n total = 0;\n }\n\n error = new SourcererError(extractError(reason), {\n cause: reason,\n context: { kind: 'remote', 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 }\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 goTo(target) {\n const clamped = total > 0 ? clampPage(target, pageCount(total, limit)) : Math.max(1, Math.trunc(target));\n\n if (clamped === page) return Promise.resolve();\n\n page = clamped;\n\n return doUpdate();\n },\n\n goToLast() {\n const last = pageCount(total, limit);\n\n if (page === last) return Promise.resolve();\n\n page = last;\n\n return doUpdate();\n },\n\n get meta() {\n return cachedMeta;\n },\n\n next() {\n const pages = pageCount(total, limit);\n\n if (page >= pages) return Promise.resolve();\n\n page++;\n\n return doUpdate();\n },\n\n optimisticUpdate(mutator, options) {\n if (optimistic?.active) {\n throw new SourcererError(\n 'An optimistic update is already active. Rollback the previous update before starting another.',\n );\n }\n\n const prevItems = items;\n const prevTotal = total;\n\n optimistic = { active: true, prevItems, prevTotal };\n\n try {\n items = mutator(items);\n } catch (e: unknown) {\n optimistic = null;\n throw e;\n }\n\n total = options?.total ?? total;\n notifyListeners();\n\n return () => {\n if (optimistic?.active) {\n optimistic.active = false;\n items = prevItems;\n total = prevTotal;\n optimistic = null;\n notifyListeners();\n }\n };\n },\n\n patch(changes) {\n let changed = false;\n\n if (changes.limit !== undefined) {\n const next = Math.max(1, Math.trunc(changes.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 // `|| 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 (changes.page !== undefined) {\n const next =\n total > 0 ? clampPage(changes.page, pageCount(total, limit)) : Math.max(1, Math.trunc(changes.page));\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 fetch — cancel any debounced search()\n // still pending so it doesn't fire a second, redundant fetch afterwards.\n searchCoordinator.cancel();\n\n // Reset to page 1 when non-page query fields changed without 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 doUpdate();\n },\n\n prev() {\n if (page <= 1) return Promise.resolve();\n\n page--;\n\n return doUpdate();\n },\n\n get query() {\n return {\n ...(filter !== undefined && { filter }),\n ...(sort !== undefined && { sort }),\n ...(search && { search }),\n limit,\n page,\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 page = 1;\n filter = cfg.filter;\n sort = cfg.sort;\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 page = 1;\n\n return doUpdate();\n }\n\n if (q === search) return Promise.resolve();\n\n search = q;\n page = 1;\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 +1 @@
1
- {"version":3,"file":"remoteSource.d.ts","sourceRoot":"","sources":["../src/remoteSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAoC,MAAM,SAAS,CAAC;AAS5F,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EACtE,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,GACnC,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,CAqYjC"}
1
+ {"version":3,"file":"remoteSource.d.ts","sourceRoot":"","sources":["../src/remoteSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAoC,MAAM,SAAS,CAAC;AAQ5F,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EACtE,GAAG,EAAE,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,GACnC,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,CAuXjC"}