@vielzeug/sourceit 3.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 (56) hide show
  1. package/README.md +134 -0
  2. package/dist/codecs.cjs +2 -0
  3. package/dist/codecs.cjs.map +1 -0
  4. package/dist/codecs.d.ts +7 -0
  5. package/dist/codecs.d.ts.map +1 -0
  6. package/dist/codecs.js +45 -0
  7. package/dist/codecs.js.map +1 -0
  8. package/dist/index.cjs +1 -0
  9. package/dist/index.d.ts +10 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +8 -0
  12. package/dist/localSource.cjs +2 -0
  13. package/dist/localSource.cjs.map +1 -0
  14. package/dist/localSource.d.ts +3 -0
  15. package/dist/localSource.d.ts.map +1 -0
  16. package/dist/localSource.js +135 -0
  17. package/dist/localSource.js.map +1 -0
  18. package/dist/pagination.cjs +2 -0
  19. package/dist/pagination.cjs.map +1 -0
  20. package/dist/pagination.d.ts +13 -0
  21. package/dist/pagination.d.ts.map +1 -0
  22. package/dist/pagination.js +28 -0
  23. package/dist/pagination.js.map +1 -0
  24. package/dist/predicates.cjs +2 -0
  25. package/dist/predicates.cjs.map +1 -0
  26. package/dist/predicates.d.ts +14 -0
  27. package/dist/predicates.d.ts.map +1 -0
  28. package/dist/predicates.js +14 -0
  29. package/dist/predicates.js.map +1 -0
  30. package/dist/presets.cjs +2 -0
  31. package/dist/presets.cjs.map +1 -0
  32. package/dist/presets.d.ts +12 -0
  33. package/dist/presets.d.ts.map +1 -0
  34. package/dist/presets.js +21 -0
  35. package/dist/presets.js.map +1 -0
  36. package/dist/remoteSource.cjs +2 -0
  37. package/dist/remoteSource.cjs.map +1 -0
  38. package/dist/remoteSource.d.ts +3 -0
  39. package/dist/remoteSource.d.ts.map +1 -0
  40. package/dist/remoteSource.js +162 -0
  41. package/dist/remoteSource.js.map +1 -0
  42. package/dist/search.cjs +2 -0
  43. package/dist/search.cjs.map +1 -0
  44. package/dist/search.d.ts +3 -0
  45. package/dist/search.d.ts.map +1 -0
  46. package/dist/search.js +40 -0
  47. package/dist/search.js.map +1 -0
  48. package/dist/selector.cjs +2 -0
  49. package/dist/selector.cjs.map +1 -0
  50. package/dist/selector.d.ts +4 -0
  51. package/dist/selector.d.ts.map +1 -0
  52. package/dist/selector.js +22 -0
  53. package/dist/selector.js.map +1 -0
  54. package/dist/types.d.ts +111 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # @vielzeug/sourceit
2
+
3
+ ![Sourceit Logo](../../docs/public/logo-sourceit.svg)
4
+
5
+ Reactive, typed local and remote query sources for list-like UIs.
6
+
7
+ - Local in-memory sources via `createLocalSource`
8
+ - Remote async sources via `createRemoteSource`
9
+ - Shared pagination/filter/sort/search behavior
10
+ - URL query param encode/decode helpers
11
+ - Selector subscriptions via `subscribeSelector`
12
+
13
+ ## Installation
14
+
15
+ ```sh
16
+ pnpm add @vielzeug/sourceit
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```ts
22
+ import { createLocalSource } from '@vielzeug/sourceit';
23
+
24
+ const source = createLocalSource(
25
+ [
26
+ { id: 1, name: 'Ada' },
27
+ { id: 2, name: 'Grace' },
28
+ { id: 3, name: 'Linus' },
29
+ ],
30
+ { limit: 2 },
31
+ );
32
+
33
+ source.searchNow('a');
34
+
35
+ console.log(source.current);
36
+ console.log(source.meta.pageNumber);
37
+ ```
38
+
39
+ ## Core APIs
40
+
41
+ ### `createLocalSource`
42
+
43
+ ```ts
44
+ createLocalSource<T>(
45
+ initialData: readonly T[],
46
+ cfg?: {
47
+ debounceMs?: number;
48
+ filter?: (value: T, index: number, array: readonly T[]) => boolean;
49
+ limit?: number;
50
+ searchFn?: (items: readonly T[], query: string) => readonly T[];
51
+ sort?: (a: T, b: T) => number;
52
+ },
53
+ ): LocalSource<T>
54
+ ```
55
+
56
+ ### `createRemoteSource`
57
+
58
+ ```ts
59
+ createRemoteSource<T, TFilter = unknown, TSort = unknown>(
60
+ cfg: {
61
+ debounceMs?: number;
62
+ fetch: (q: {
63
+ filter?: TFilter;
64
+ limit: number;
65
+ page: number;
66
+ search?: string;
67
+ sort?: TSort;
68
+ }) => Promise<{ items: readonly T[]; total: number }>;
69
+ filter?: TFilter;
70
+ limit?: number;
71
+ sort?: TSort;
72
+ },
73
+ ): RemoteSource<T, TFilter, TSort>
74
+ ```
75
+
76
+ ## Shared Source Surface
77
+
78
+ Both local and remote sources expose:
79
+
80
+ - `current`
81
+ - `meta`
82
+ - `subscribe(listener)`
83
+ - `toQuery()`
84
+ - `fromQueryParams(params)`
85
+ - `restore(state)`
86
+ - `batch(mutator)`
87
+
88
+ Pagination/search/filter/sort methods are also aligned (`goTo`, `goToLast`, `next`, `prev`, `setFilter`, `setSort`, `setLimit`, `search`, `searchNow`, `reset`, `commit`).
89
+
90
+ Remote sources additionally provide:
91
+
92
+ - `refresh()`
93
+ - `ready(): Promise<void>`
94
+
95
+ ## URL Query Param Helpers
96
+
97
+ ```ts
98
+ import {
99
+ decodeLocalQueryParams,
100
+ decodeRemoteQueryParams,
101
+ decodeRemoteQueryParamsStrict,
102
+ encodeLocalQueryParams,
103
+ encodeRemoteQueryParams,
104
+ } from '@vielzeug/sourceit';
105
+ ```
106
+
107
+ ## Selector Subscriptions
108
+
109
+ ```ts
110
+ import { subscribeSelector } from '@vielzeug/sourceit';
111
+
112
+ const stop = subscribeSelector(
113
+ source,
114
+ (s) => s.meta.pageNumber,
115
+ (next, prev) => {
116
+ console.log('page changed', prev, '->', next);
117
+ },
118
+ );
119
+
120
+ stop();
121
+ ```
122
+
123
+ ## Development
124
+
125
+ ```sh
126
+ pnpm --filter @vielzeug/sourceit test
127
+ pnpm --filter @vielzeug/sourceit build
128
+ ```
129
+
130
+ ## Docs
131
+
132
+ - Package docs: [vielzeug.dev/sourceit](https://vielzeug.dev/sourceit/)
133
+ - Usage guide: [vielzeug.dev/sourceit/usage](https://vielzeug.dev/sourceit/usage)
134
+ - API reference: [vielzeug.dev/sourceit/api](https://vielzeug.dev/sourceit/api)
@@ -0,0 +1,2 @@
1
+ var e=(e,t)=>{let n=Number.parseInt(e??``,10);return!Number.isFinite(n)||n<1?t:n},t=(t,n=10)=>{let r=typeof t.page==`string`?t.page:void 0,i=typeof t.limit==`string`?t.limit:void 0,a=typeof t.search==`string`?t.search:void 0;return{limit:e(i,n),page:e(r,1),search:a??``}},n=e=>({...e.search&&{search:e.search},limit:String(e.limit),page:String(e.page)}),r=(t,n,r)=>{let i=typeof t.page==`string`?t.page:void 0,a=typeof t.limit==`string`?t.limit:void 0,o=typeof t.search==`string`?t.search:void 0,s=typeof t.filter==`string`?t.filter:void 0,c=typeof t.sort==`string`?t.sort:void 0,l,u,d=!1,f=!1;if(s!==void 0)try{l=JSON.parse(s),d=!0}catch(e){if(r)throw Error(`Invalid query param "filter": ${String(e)}`,{cause:e})}if(c!==void 0)try{u=JSON.parse(c),f=!0}catch(e){if(r)throw Error(`Invalid query param "sort": ${String(e)}`,{cause:e})}return{...d&&{filter:l},...f&&{sort:u},limit:e(a,n),page:e(i,1),search:o??``}},i=(e,t=10)=>r(e,t,!1),a=(e,t=10)=>r(e,t,!0),o=e=>({...e.search&&{search:e.search},limit:String(e.limit),page:String(e.page),...e.filter!==void 0&&{filter:JSON.stringify(e.filter)},...e.sort!==void 0&&{sort:JSON.stringify(e.sort)}});exports.decodeLocalQueryParams=t,exports.decodeRemoteQueryParams=i,exports.decodeRemoteQueryParamsStrict=a,exports.encodeLocalQueryParams=n,exports.encodeRemoteQueryParams=o;
2
+ //# sourceMappingURL=codecs.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codecs.cjs","names":[],"sources":["../src/codecs.ts"],"sourcesContent":["import type { QueryParams, QueryParamsInput, RemoteSourceQuery, SourceQuery } from './types';\n\nconst parsePositiveInt = (value: string | undefined, fallback: number) => {\n const parsed = Number.parseInt(value ?? '', 10);\n\n if (!Number.isFinite(parsed) || parsed < 1) {\n return fallback;\n }\n\n return parsed;\n};\n\nexport const decodeLocalQueryParams = (params: QueryParamsInput, defaultLimit = 10): Partial<SourceQuery> => {\n const rawPage = typeof params.page === 'string' ? params.page : undefined;\n const rawLimit = typeof params.limit === 'string' ? params.limit : undefined;\n const rawSearch = typeof params.search === 'string' ? params.search : undefined;\n\n return {\n limit: parsePositiveInt(rawLimit, defaultLimit),\n page: parsePositiveInt(rawPage, 1),\n search: rawSearch ?? '',\n };\n};\n\nexport const encodeLocalQueryParams = (query: SourceQuery): QueryParams => ({\n ...(query.search && { search: query.search }),\n limit: String(query.limit),\n page: String(query.page),\n});\n\nconst parseRemoteQuery = <TFilter, TSort>(\n params: QueryParamsInput,\n defaultLimit: number,\n strict: boolean,\n): Partial<RemoteSourceQuery<TFilter, TSort>> => {\n const rawPage = typeof params.page === 'string' ? params.page : undefined;\n const rawLimit = typeof params.limit === 'string' ? params.limit : undefined;\n const rawSearch = typeof params.search === 'string' ? params.search : undefined;\n const rawFilter = typeof params.filter === 'string' ? params.filter : undefined;\n const rawSort = typeof params.sort === 'string' ? params.sort : undefined;\n\n let filter: TFilter | undefined;\n let sort: TSort | undefined;\n let hasFilter = false;\n let hasSort = false;\n\n if (rawFilter !== undefined) {\n try {\n filter = JSON.parse(rawFilter) as TFilter;\n hasFilter = true;\n } catch (error) {\n if (strict) {\n throw new Error(`Invalid query param \"filter\": ${String(error)}`, { cause: error });\n }\n }\n }\n\n if (rawSort !== undefined) {\n try {\n sort = JSON.parse(rawSort) as TSort;\n hasSort = true;\n } catch (error) {\n if (strict) {\n throw new Error(`Invalid query param \"sort\": ${String(error)}`, { cause: error });\n }\n }\n }\n\n return {\n ...(hasFilter && { filter }),\n ...(hasSort && { sort }),\n limit: parsePositiveInt(rawLimit, defaultLimit),\n page: parsePositiveInt(rawPage, 1),\n search: rawSearch ?? '',\n };\n};\n\nexport const decodeRemoteQueryParams = <TFilter = unknown, TSort = unknown>(\n params: QueryParamsInput,\n defaultLimit = 10,\n): Partial<RemoteSourceQuery<TFilter, TSort>> => {\n return parseRemoteQuery<TFilter, TSort>(params, defaultLimit, false);\n};\n\nexport const decodeRemoteQueryParamsStrict = <TFilter = unknown, TSort = unknown>(\n params: QueryParamsInput,\n defaultLimit = 10,\n): Partial<RemoteSourceQuery<TFilter, TSort>> => {\n return parseRemoteQuery<TFilter, TSort>(params, defaultLimit, true);\n};\n\nexport const encodeRemoteQueryParams = <TFilter = unknown, TSort = unknown>(\n query: RemoteSourceQuery<TFilter, TSort>,\n): QueryParams => ({\n ...(query.search && { search: query.search }),\n limit: String(query.limit),\n page: String(query.page),\n ...(query.filter !== undefined && { filter: JSON.stringify(query.filter) }),\n ...(query.sort !== undefined && { sort: JSON.stringify(query.sort) }),\n});\n"],"mappings":"AAEA,IAAM,GAAoB,EAA2B,IAAqB,CACxE,IAAM,EAAS,OAAO,SAAS,GAAS,GAAI,EAAE,EAM9C,MAJI,CAAC,OAAO,SAAS,CAAM,GAAK,EAAS,EAChC,EAGF,CACT,EAEa,GAA0B,EAA0B,EAAe,KAA6B,CAC3G,IAAM,EAAU,OAAO,EAAO,MAAS,SAAW,EAAO,KAAO,IAAA,GAC1D,EAAW,OAAO,EAAO,OAAU,SAAW,EAAO,MAAQ,IAAA,GAC7D,EAAY,OAAO,EAAO,QAAW,SAAW,EAAO,OAAS,IAAA,GAEtE,MAAO,CACL,MAAO,EAAiB,EAAU,CAAY,EAC9C,KAAM,EAAiB,EAAS,CAAC,EACjC,OAAQ,GAAa,EACvB,CACF,EAEa,EAA0B,IAAqC,CAC1E,GAAI,EAAM,QAAU,CAAE,OAAQ,EAAM,MAAO,EAC3C,MAAO,OAAO,EAAM,KAAK,EACzB,KAAM,OAAO,EAAM,IAAI,CACzB,GAEM,GACJ,EACA,EACA,IAC+C,CAC/C,IAAM,EAAU,OAAO,EAAO,MAAS,SAAW,EAAO,KAAO,IAAA,GAC1D,EAAW,OAAO,EAAO,OAAU,SAAW,EAAO,MAAQ,IAAA,GAC7D,EAAY,OAAO,EAAO,QAAW,SAAW,EAAO,OAAS,IAAA,GAChE,EAAY,OAAO,EAAO,QAAW,SAAW,EAAO,OAAS,IAAA,GAChE,EAAU,OAAO,EAAO,MAAS,SAAW,EAAO,KAAO,IAAA,GAE5D,EACA,EACA,EAAY,GACZ,EAAU,GAEd,GAAI,IAAc,IAAA,GAChB,GAAI,CACF,EAAS,KAAK,MAAM,CAAS,EAC7B,EAAY,EACd,OAAS,EAAO,CACd,GAAI,EACF,MAAU,MAAM,iCAAiC,OAAO,CAAK,IAAK,CAAE,MAAO,CAAM,CAAC,CAEtF,CAGF,GAAI,IAAY,IAAA,GACd,GAAI,CACF,EAAO,KAAK,MAAM,CAAO,EACzB,EAAU,EACZ,OAAS,EAAO,CACd,GAAI,EACF,MAAU,MAAM,+BAA+B,OAAO,CAAK,IAAK,CAAE,MAAO,CAAM,CAAC,CAEpF,CAGF,MAAO,CACL,GAAI,GAAa,CAAE,QAAO,EAC1B,GAAI,GAAW,CAAE,MAAK,EACtB,MAAO,EAAiB,EAAU,CAAY,EAC9C,KAAM,EAAiB,EAAS,CAAC,EACjC,OAAQ,GAAa,EACvB,CACF,EAEa,GACX,EACA,EAAe,KAER,EAAiC,EAAQ,EAAc,EAAK,EAGxD,GACX,EACA,EAAe,KAER,EAAiC,EAAQ,EAAc,EAAI,EAGvD,EACX,IACiB,CACjB,GAAI,EAAM,QAAU,CAAE,OAAQ,EAAM,MAAO,EAC3C,MAAO,OAAO,EAAM,KAAK,EACzB,KAAM,OAAO,EAAM,IAAI,EACvB,GAAI,EAAM,SAAW,IAAA,IAAa,CAAE,OAAQ,KAAK,UAAU,EAAM,MAAM,CAAE,EACzE,GAAI,EAAM,OAAS,IAAA,IAAa,CAAE,KAAM,KAAK,UAAU,EAAM,IAAI,CAAE,CACrE"}
@@ -0,0 +1,7 @@
1
+ import type { QueryParams, QueryParamsInput, RemoteSourceQuery, SourceQuery } from './types';
2
+ export declare const decodeLocalQueryParams: (params: QueryParamsInput, defaultLimit?: number) => Partial<SourceQuery>;
3
+ export declare const encodeLocalQueryParams: (query: SourceQuery) => QueryParams;
4
+ export declare const decodeRemoteQueryParams: <TFilter = unknown, TSort = unknown>(params: QueryParamsInput, defaultLimit?: number) => Partial<RemoteSourceQuery<TFilter, TSort>>;
5
+ export declare const decodeRemoteQueryParamsStrict: <TFilter = unknown, TSort = unknown>(params: QueryParamsInput, defaultLimit?: number) => Partial<RemoteSourceQuery<TFilter, TSort>>;
6
+ export declare const encodeRemoteQueryParams: <TFilter = unknown, TSort = unknown>(query: RemoteSourceQuery<TFilter, TSort>) => QueryParams;
7
+ //# sourceMappingURL=codecs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codecs.d.ts","sourceRoot":"","sources":["../src/codecs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAY7F,eAAO,MAAM,sBAAsB,GAAI,QAAQ,gBAAgB,EAAE,qBAAiB,KAAG,OAAO,CAAC,WAAW,CAUvG,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,OAAO,WAAW,KAAG,WAI1D,CAAC;AAiDH,eAAO,MAAM,uBAAuB,GAAI,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EACxE,QAAQ,gBAAgB,EACxB,qBAAiB,KAChB,OAAO,CAAC,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,CAE3C,CAAC;AAEF,eAAO,MAAM,6BAA6B,GAAI,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EAC9E,QAAQ,gBAAgB,EACxB,qBAAiB,KAChB,OAAO,CAAC,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,CAE3C,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EACxE,OAAO,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,KACvC,WAMD,CAAC"}
package/dist/codecs.js ADDED
@@ -0,0 +1,45 @@
1
+ //#region src/codecs.ts
2
+ var e = (e, t) => {
3
+ let n = Number.parseInt(e ?? "", 10);
4
+ return !Number.isFinite(n) || n < 1 ? t : n;
5
+ }, t = (t, n = 10) => {
6
+ let r = typeof t.page == "string" ? t.page : void 0, i = typeof t.limit == "string" ? t.limit : void 0, a = typeof t.search == "string" ? t.search : void 0;
7
+ return {
8
+ limit: e(i, n),
9
+ page: e(r, 1),
10
+ search: a ?? ""
11
+ };
12
+ }, n = (e) => ({
13
+ ...e.search && { search: e.search },
14
+ limit: String(e.limit),
15
+ page: String(e.page)
16
+ }), r = (t, n, r) => {
17
+ let i = typeof t.page == "string" ? t.page : void 0, a = typeof t.limit == "string" ? t.limit : void 0, o = typeof t.search == "string" ? t.search : void 0, s = typeof t.filter == "string" ? t.filter : void 0, c = typeof t.sort == "string" ? t.sort : void 0, l, u, d = !1, f = !1;
18
+ if (s !== void 0) try {
19
+ l = JSON.parse(s), d = !0;
20
+ } catch (e) {
21
+ if (r) throw Error(`Invalid query param "filter": ${String(e)}`, { cause: e });
22
+ }
23
+ if (c !== void 0) try {
24
+ u = JSON.parse(c), f = !0;
25
+ } catch (e) {
26
+ if (r) throw Error(`Invalid query param "sort": ${String(e)}`, { cause: e });
27
+ }
28
+ return {
29
+ ...d && { filter: l },
30
+ ...f && { sort: u },
31
+ limit: e(a, n),
32
+ page: e(i, 1),
33
+ search: o ?? ""
34
+ };
35
+ }, i = (e, t = 10) => r(e, t, !1), a = (e, t = 10) => r(e, t, !0), o = (e) => ({
36
+ ...e.search && { search: e.search },
37
+ limit: String(e.limit),
38
+ page: String(e.page),
39
+ ...e.filter !== void 0 && { filter: JSON.stringify(e.filter) },
40
+ ...e.sort !== void 0 && { sort: JSON.stringify(e.sort) }
41
+ });
42
+ //#endregion
43
+ export { t as decodeLocalQueryParams, i as decodeRemoteQueryParams, a as decodeRemoteQueryParamsStrict, n as encodeLocalQueryParams, o as encodeRemoteQueryParams };
44
+
45
+ //# sourceMappingURL=codecs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codecs.js","names":[],"sources":["../src/codecs.ts"],"sourcesContent":["import type { QueryParams, QueryParamsInput, RemoteSourceQuery, SourceQuery } from './types';\n\nconst parsePositiveInt = (value: string | undefined, fallback: number) => {\n const parsed = Number.parseInt(value ?? '', 10);\n\n if (!Number.isFinite(parsed) || parsed < 1) {\n return fallback;\n }\n\n return parsed;\n};\n\nexport const decodeLocalQueryParams = (params: QueryParamsInput, defaultLimit = 10): Partial<SourceQuery> => {\n const rawPage = typeof params.page === 'string' ? params.page : undefined;\n const rawLimit = typeof params.limit === 'string' ? params.limit : undefined;\n const rawSearch = typeof params.search === 'string' ? params.search : undefined;\n\n return {\n limit: parsePositiveInt(rawLimit, defaultLimit),\n page: parsePositiveInt(rawPage, 1),\n search: rawSearch ?? '',\n };\n};\n\nexport const encodeLocalQueryParams = (query: SourceQuery): QueryParams => ({\n ...(query.search && { search: query.search }),\n limit: String(query.limit),\n page: String(query.page),\n});\n\nconst parseRemoteQuery = <TFilter, TSort>(\n params: QueryParamsInput,\n defaultLimit: number,\n strict: boolean,\n): Partial<RemoteSourceQuery<TFilter, TSort>> => {\n const rawPage = typeof params.page === 'string' ? params.page : undefined;\n const rawLimit = typeof params.limit === 'string' ? params.limit : undefined;\n const rawSearch = typeof params.search === 'string' ? params.search : undefined;\n const rawFilter = typeof params.filter === 'string' ? params.filter : undefined;\n const rawSort = typeof params.sort === 'string' ? params.sort : undefined;\n\n let filter: TFilter | undefined;\n let sort: TSort | undefined;\n let hasFilter = false;\n let hasSort = false;\n\n if (rawFilter !== undefined) {\n try {\n filter = JSON.parse(rawFilter) as TFilter;\n hasFilter = true;\n } catch (error) {\n if (strict) {\n throw new Error(`Invalid query param \"filter\": ${String(error)}`, { cause: error });\n }\n }\n }\n\n if (rawSort !== undefined) {\n try {\n sort = JSON.parse(rawSort) as TSort;\n hasSort = true;\n } catch (error) {\n if (strict) {\n throw new Error(`Invalid query param \"sort\": ${String(error)}`, { cause: error });\n }\n }\n }\n\n return {\n ...(hasFilter && { filter }),\n ...(hasSort && { sort }),\n limit: parsePositiveInt(rawLimit, defaultLimit),\n page: parsePositiveInt(rawPage, 1),\n search: rawSearch ?? '',\n };\n};\n\nexport const decodeRemoteQueryParams = <TFilter = unknown, TSort = unknown>(\n params: QueryParamsInput,\n defaultLimit = 10,\n): Partial<RemoteSourceQuery<TFilter, TSort>> => {\n return parseRemoteQuery<TFilter, TSort>(params, defaultLimit, false);\n};\n\nexport const decodeRemoteQueryParamsStrict = <TFilter = unknown, TSort = unknown>(\n params: QueryParamsInput,\n defaultLimit = 10,\n): Partial<RemoteSourceQuery<TFilter, TSort>> => {\n return parseRemoteQuery<TFilter, TSort>(params, defaultLimit, true);\n};\n\nexport const encodeRemoteQueryParams = <TFilter = unknown, TSort = unknown>(\n query: RemoteSourceQuery<TFilter, TSort>,\n): QueryParams => ({\n ...(query.search && { search: query.search }),\n limit: String(query.limit),\n page: String(query.page),\n ...(query.filter !== undefined && { filter: JSON.stringify(query.filter) }),\n ...(query.sort !== undefined && { sort: JSON.stringify(query.sort) }),\n});\n"],"mappings":";AAEA,IAAM,KAAoB,GAA2B,MAAqB;CACxE,IAAM,IAAS,OAAO,SAAS,KAAS,IAAI,EAAE;CAM9C,OAJI,CAAC,OAAO,SAAS,CAAM,KAAK,IAAS,IAChC,IAGF;AACT,GAEa,KAA0B,GAA0B,IAAe,OAA6B;CAC3G,IAAM,IAAU,OAAO,EAAO,QAAS,WAAW,EAAO,OAAO,KAAA,GAC1D,IAAW,OAAO,EAAO,SAAU,WAAW,EAAO,QAAQ,KAAA,GAC7D,IAAY,OAAO,EAAO,UAAW,WAAW,EAAO,SAAS,KAAA;CAEtE,OAAO;EACL,OAAO,EAAiB,GAAU,CAAY;EAC9C,MAAM,EAAiB,GAAS,CAAC;EACjC,QAAQ,KAAa;CACvB;AACF,GAEa,KAA0B,OAAqC;CAC1E,GAAI,EAAM,UAAU,EAAE,QAAQ,EAAM,OAAO;CAC3C,OAAO,OAAO,EAAM,KAAK;CACzB,MAAM,OAAO,EAAM,IAAI;AACzB,IAEM,KACJ,GACA,GACA,MAC+C;CAC/C,IAAM,IAAU,OAAO,EAAO,QAAS,WAAW,EAAO,OAAO,KAAA,GAC1D,IAAW,OAAO,EAAO,SAAU,WAAW,EAAO,QAAQ,KAAA,GAC7D,IAAY,OAAO,EAAO,UAAW,WAAW,EAAO,SAAS,KAAA,GAChE,IAAY,OAAO,EAAO,UAAW,WAAW,EAAO,SAAS,KAAA,GAChE,IAAU,OAAO,EAAO,QAAS,WAAW,EAAO,OAAO,KAAA,GAE5D,GACA,GACA,IAAY,IACZ,IAAU;CAEd,IAAI,MAAc,KAAA,GAChB,IAAI;EAEF,AADA,IAAS,KAAK,MAAM,CAAS,GAC7B,IAAY;CACd,SAAS,GAAO;EACd,IAAI,GACF,MAAU,MAAM,iCAAiC,OAAO,CAAK,KAAK,EAAE,OAAO,EAAM,CAAC;CAEtF;CAGF,IAAI,MAAY,KAAA,GACd,IAAI;EAEF,AADA,IAAO,KAAK,MAAM,CAAO,GACzB,IAAU;CACZ,SAAS,GAAO;EACd,IAAI,GACF,MAAU,MAAM,+BAA+B,OAAO,CAAK,KAAK,EAAE,OAAO,EAAM,CAAC;CAEpF;CAGF,OAAO;EACL,GAAI,KAAa,EAAE,UAAO;EAC1B,GAAI,KAAW,EAAE,QAAK;EACtB,OAAO,EAAiB,GAAU,CAAY;EAC9C,MAAM,EAAiB,GAAS,CAAC;EACjC,QAAQ,KAAa;CACvB;AACF,GAEa,KACX,GACA,IAAe,OAER,EAAiC,GAAQ,GAAc,EAAK,GAGxD,KACX,GACA,IAAe,OAER,EAAiC,GAAQ,GAAc,EAAI,GAGvD,KACX,OACiB;CACjB,GAAI,EAAM,UAAU,EAAE,QAAQ,EAAM,OAAO;CAC3C,OAAO,OAAO,EAAM,KAAK;CACzB,MAAM,OAAO,EAAM,IAAI;CACvB,GAAI,EAAM,WAAW,KAAA,KAAa,EAAE,QAAQ,KAAK,UAAU,EAAM,MAAM,EAAE;CACzE,GAAI,EAAM,SAAS,KAAA,KAAa,EAAE,MAAM,KAAK,UAAU,EAAM,IAAI,EAAE;AACrE"}
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./codecs.cjs`),t=require(`./search.cjs`),n=require(`./localSource.cjs`),r=require(`./predicates.cjs`),i=require(`./presets.cjs`),a=require(`./remoteSource.cjs`),o=require(`./selector.cjs`);exports.and=r.and,exports.containsSearch=t.containsSearch,exports.createLocalSource=n.createLocalSource,exports.createRemoteSource=a.createRemoteSource,exports.decodeLocalQueryParams=e.decodeLocalQueryParams,exports.decodeRemoteQueryParams=e.decodeRemoteQueryParams,exports.decodeRemoteQueryParamsStrict=e.decodeRemoteQueryParamsStrict,exports.encodeLocalQueryParams=e.encodeLocalQueryParams,exports.encodeRemoteQueryParams=e.encodeRemoteQueryParams,exports.filterContains=i.filterContains,exports.filterEquals=i.filterEquals,exports.filterRange=i.filterRange,exports.fuzzySearch=t.fuzzySearch,exports.not=r.not,exports.or=r.or,exports.shallowEqual=o.shallowEqual,exports.sortBy=i.sortBy,exports.subscribeSelector=o.subscribeSelector;
@@ -0,0 +1,10 @@
1
+ export * from './codecs';
2
+ export * from './localSource';
3
+ export * from './predicates';
4
+ export * from './presets';
5
+ export * from './remoteSource';
6
+ export * from './search';
7
+ export * from './selector';
8
+ export * from './types';
9
+ export type { BaseSource, LocalSource, RemoteSource, SourceMeta } from './types';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AAGxB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import { decodeLocalQueryParams as e, decodeRemoteQueryParams as t, decodeRemoteQueryParamsStrict as n, encodeLocalQueryParams as r, encodeRemoteQueryParams as i } from "./codecs.js";
2
+ import { containsSearch as a, fuzzySearch as o } from "./search.js";
3
+ import { createLocalSource as s } from "./localSource.js";
4
+ import { and as c, not as l, or as u } from "./predicates.js";
5
+ import { filterContains as d, filterEquals as f, filterRange as p, sortBy as m } from "./presets.js";
6
+ import { createRemoteSource as h } from "./remoteSource.js";
7
+ import { shallowEqual as g, subscribeSelector as _ } from "./selector.js";
8
+ export { c as and, a as containsSearch, s as createLocalSource, h as createRemoteSource, e as decodeLocalQueryParams, t as decodeRemoteQueryParams, n as decodeRemoteQueryParamsStrict, r as encodeLocalQueryParams, i as encodeRemoteQueryParams, d as filterContains, f as filterEquals, p as filterRange, o as fuzzySearch, l as not, u as or, g as shallowEqual, m as sortBy, _ as subscribeSelector };
@@ -0,0 +1,2 @@
1
+ const e=require(`./codecs.cjs`),t=require(`./pagination.cjs`),n=require(`./search.cjs`);var r={debounceMs:300,limit:10};function i(i,a={}){let o=new Set,s=a.searchFn??n.containsSearch,c=[...i],l=Math.max(1,a.limit??r.limit),u=a.filter,d=a.sort,f=``,p=0,m=[],h,g,_,v=()=>{if(!m.length)g=[];else{let e=p*l;g=m.slice(e,e+l)}_=t.createMeta({errorMessage:null,isLoading:!1,isSearchPending:h!==void 0,pageNumber:p+1,pageSize:l,totalItems:m.length})},y=()=>{v();for(let e of o)e()},b=()=>{let e=f?s(c,f):c;u&&(e=e.filter(u)),d&&(e=[...e].sort(d));let n=t.pageCount(e.length,l);p=t.clampOffset(p,n),m=e},x=()=>{b(),y()},S=()=>({limit:l,page:p+1,search:f}),C=e=>{h&&clearTimeout(h),f=e,p=0,h=setTimeout(()=>{h=void 0,x()},a.debounceMs??r.debounceMs),y()};return b(),v(),{batch:e=>{let t=l,n=u,r=d,i=f,a=c,o=p;e({goTo:e=>{o=Math.max(0,Math.trunc(e)-1)},search:e=>{i=e,o=0},setData:e=>{a=[...e],o=0},setFilter:e=>{n=e,o=0},setLimit:e=>{t=Math.max(1,Math.trunc(e)),o=0},setSort:e=>{r=e,o=0}}),l=t,u=n,d=r,f=i,c=a,p=o,x()},commit(){h&&(clearTimeout(h),h=void 0,x())},get current(){return g},fromQueryParams(t){this.restore(e.decodeLocalQueryParams(t,a.limit??r.limit))},goTo(e){p=t.clampOffset(e-1,t.pageCount(m.length,l)),y()},goToLast(){this.goTo(t.pageCount(m.length,l))},get meta(){return _},next(){this.goTo(this.meta.pageNumber+1)},prev(){this.goTo(this.meta.pageNumber-1)},reset(){l=Math.max(1,a.limit??r.limit),u=a.filter,d=a.sort,f=``,p=0,x()},restore(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(e.search!==void 0&&e.search!==f&&(f=e.search,t=!0),e.page!==void 0){let n=Math.max(0,Math.trunc(e.page)-1);n!==p&&(p=n,t=!0)}t&&x()},search(e){C(e)},searchNow:e=>{h&&=(clearTimeout(h),void 0),f=e,p=0,x()},setData(e){c=[...e],p=0,x()},setFilter(e){u=e,p=0,x()},setLimit(e){l=Math.max(1,Math.trunc(e)),p=0,x()},setSort(e){d=e,p=0,x()},subscribe(e){return o.add(e),()=>o.delete(e)},toQuery(){return S()}}}exports.createLocalSource=i;
2
+ //# sourceMappingURL=localSource.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localSource.cjs","names":[],"sources":["../src/localSource.ts"],"sourcesContent":["import type { LocalBatchContext, LocalConfig, LocalSource, Predicate, Sorter, SourceMeta, SourceQuery } from './types';\n\nimport { decodeLocalQueryParams } from './codecs';\nimport { clampOffset, createMeta, pageCount } from './pagination';\nimport { containsSearch as defaultSearch } from './search';\n\nconst DEFAULTS = { debounceMs: 300, limit: 10 } as const;\n\nexport function createLocalSource<T>(initialData: readonly T[], cfg: LocalConfig<T> = {}): LocalSource<T> {\n const listeners = new Set<() => void>();\n const searchFn = cfg.searchFn ?? defaultSearch;\n\n let rawData: readonly T[] = [...initialData];\n let limit = Math.max(1, cfg.limit ?? DEFAULTS.limit);\n let filterFn: Predicate<T> | undefined = cfg.filter;\n let sortFn: Sorter<T> | undefined = cfg.sort;\n let query = '';\n let offset = 0;\n let view: readonly T[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n let cachedCurrent!: readonly T[];\n let cachedMeta!: SourceMeta;\n\n const refreshCache = () => {\n if (!view.length) {\n cachedCurrent = [];\n } else {\n const start = offset * limit;\n\n cachedCurrent = view.slice(start, start + limit);\n }\n\n cachedMeta = createMeta({\n errorMessage: null,\n isLoading: false,\n isSearchPending: timer !== undefined,\n pageNumber: offset + 1,\n pageSize: limit,\n totalItems: view.length,\n });\n };\n\n const notify = () => {\n refreshCache();\n\n for (const listener of listeners) {\n listener();\n }\n };\n\n const recompute = () => {\n let next = query ? searchFn(rawData, query) : rawData;\n\n if (filterFn) {\n next = next.filter(filterFn);\n }\n\n if (sortFn) {\n next = [...next].sort(sortFn);\n }\n\n const pages = pageCount(next.length, limit);\n\n offset = clampOffset(offset, pages);\n view = next;\n };\n\n const emit = () => {\n recompute();\n notify();\n };\n\n const toQuery = (): SourceQuery => ({\n limit,\n page: offset + 1,\n search: query,\n });\n\n const scheduleSearch = (searchTerm: string) => {\n if (timer) {\n clearTimeout(timer);\n }\n\n query = searchTerm;\n offset = 0;\n\n timer = setTimeout(() => {\n timer = undefined;\n emit();\n }, cfg.debounceMs ?? DEFAULTS.debounceMs);\n\n notify();\n };\n\n const searchNow = (searchTerm: string) => {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n\n query = searchTerm;\n offset = 0;\n emit();\n };\n\n const batch = (mutator: (ctx: LocalBatchContext<T>) => void) => {\n let nextLimit = limit;\n let nextFilter = filterFn;\n let nextSort = sortFn;\n let nextQuery = query;\n let nextData = rawData;\n let nextOffset = offset;\n\n mutator({\n goTo: (page) => {\n nextOffset = Math.max(0, Math.trunc(page) - 1);\n },\n search: (searchTerm) => {\n nextQuery = searchTerm;\n nextOffset = 0;\n },\n setData: (data) => {\n nextData = [...data];\n nextOffset = 0;\n },\n setFilter: (filter) => {\n nextFilter = filter;\n nextOffset = 0;\n },\n setLimit: (patchLimit) => {\n nextLimit = Math.max(1, Math.trunc(patchLimit));\n nextOffset = 0;\n },\n setSort: (sort) => {\n nextSort = sort;\n nextOffset = 0;\n },\n });\n\n limit = nextLimit;\n filterFn = nextFilter;\n sortFn = nextSort;\n query = nextQuery;\n rawData = nextData;\n offset = nextOffset;\n emit();\n };\n\n recompute();\n refreshCache();\n\n return {\n batch,\n commit() {\n if (!timer) {\n return;\n }\n\n clearTimeout(timer);\n timer = undefined;\n emit();\n },\n get current() {\n return cachedCurrent;\n },\n fromQueryParams(params) {\n this.restore(decodeLocalQueryParams(params, cfg.limit ?? DEFAULTS.limit));\n },\n goTo(page) {\n offset = clampOffset(page - 1, pageCount(view.length, limit));\n notify();\n },\n goToLast() {\n this.goTo(pageCount(view.length, limit));\n },\n get meta() {\n return cachedMeta;\n },\n next() {\n this.goTo(this.meta.pageNumber + 1);\n },\n prev() {\n this.goTo(this.meta.pageNumber - 1);\n },\n reset() {\n limit = Math.max(1, cfg.limit ?? DEFAULTS.limit);\n filterFn = cfg.filter;\n sortFn = cfg.sort;\n query = '';\n offset = 0;\n emit();\n },\n restore(state) {\n let changed = false;\n\n if (state.limit !== undefined) {\n const nextLimit = Math.max(1, Math.trunc(state.limit));\n\n if (nextLimit !== limit) {\n limit = nextLimit;\n changed = true;\n }\n }\n\n if (state.search !== undefined && state.search !== query) {\n query = state.search;\n changed = true;\n }\n\n if (state.page !== undefined) {\n const nextOffset = Math.max(0, Math.trunc(state.page) - 1);\n\n if (nextOffset !== offset) {\n offset = nextOffset;\n changed = true;\n }\n }\n\n if (changed) {\n emit();\n }\n },\n search(searchTerm) {\n scheduleSearch(searchTerm);\n },\n searchNow,\n setData(data) {\n rawData = [...data];\n offset = 0;\n emit();\n },\n setFilter(filter) {\n filterFn = filter;\n offset = 0;\n emit();\n },\n setLimit(nextLimit) {\n limit = Math.max(1, Math.trunc(nextLimit));\n offset = 0;\n emit();\n },\n setSort(sort) {\n sortFn = sort;\n offset = 0;\n emit();\n },\n subscribe(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n toQuery() {\n return toQuery();\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { LocalConfig, LocalSource } from './types';
2
+ export declare function createLocalSource<T>(initialData: readonly T[], cfg?: LocalConfig<T>): LocalSource<T>;
3
+ //# sourceMappingURL=localSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localSource.d.ts","sourceRoot":"","sources":["../src/localSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,WAAW,EAAE,WAAW,EAA8C,MAAM,SAAS,CAAC;AAQvH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,GAAE,WAAW,CAAC,CAAC,CAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAuPxG"}
@@ -0,0 +1,135 @@
1
+ import { decodeLocalQueryParams as e } from "./codecs.js";
2
+ import { clampOffset as t, createMeta as n, pageCount as r } from "./pagination.js";
3
+ import { containsSearch as i } from "./search.js";
4
+ //#region src/localSource.ts
5
+ var a = {
6
+ debounceMs: 300,
7
+ limit: 10
8
+ };
9
+ function o(o, s = {}) {
10
+ let c = /* @__PURE__ */ new Set(), l = s.searchFn ?? i, u = [...o], d = Math.max(1, s.limit ?? a.limit), f = s.filter, p = s.sort, m = "", h = 0, g = [], _, v, y, b = () => {
11
+ if (!g.length) v = [];
12
+ else {
13
+ let e = h * d;
14
+ v = g.slice(e, e + d);
15
+ }
16
+ y = n({
17
+ errorMessage: null,
18
+ isLoading: !1,
19
+ isSearchPending: _ !== void 0,
20
+ pageNumber: h + 1,
21
+ pageSize: d,
22
+ totalItems: g.length
23
+ });
24
+ }, x = () => {
25
+ b();
26
+ for (let e of c) e();
27
+ }, S = () => {
28
+ let e = m ? l(u, m) : u;
29
+ f && (e = e.filter(f)), p && (e = [...e].sort(p));
30
+ let n = r(e.length, d);
31
+ h = t(h, n), g = e;
32
+ }, C = () => {
33
+ S(), x();
34
+ }, w = () => ({
35
+ limit: d,
36
+ page: h + 1,
37
+ search: m
38
+ }), T = (e) => {
39
+ _ && clearTimeout(_), m = e, h = 0, _ = setTimeout(() => {
40
+ _ = void 0, C();
41
+ }, s.debounceMs ?? a.debounceMs), x();
42
+ };
43
+ return S(), b(), {
44
+ batch: (e) => {
45
+ let t = d, n = f, r = p, i = m, a = u, o = h;
46
+ e({
47
+ goTo: (e) => {
48
+ o = Math.max(0, Math.trunc(e) - 1);
49
+ },
50
+ search: (e) => {
51
+ i = e, o = 0;
52
+ },
53
+ setData: (e) => {
54
+ a = [...e], o = 0;
55
+ },
56
+ setFilter: (e) => {
57
+ n = e, o = 0;
58
+ },
59
+ setLimit: (e) => {
60
+ t = Math.max(1, Math.trunc(e)), o = 0;
61
+ },
62
+ setSort: (e) => {
63
+ r = e, o = 0;
64
+ }
65
+ }), d = t, f = n, p = r, m = i, u = a, h = o, C();
66
+ },
67
+ commit() {
68
+ _ && (clearTimeout(_), _ = void 0, C());
69
+ },
70
+ get current() {
71
+ return v;
72
+ },
73
+ fromQueryParams(t) {
74
+ this.restore(e(t, s.limit ?? a.limit));
75
+ },
76
+ goTo(e) {
77
+ h = t(e - 1, r(g.length, d)), x();
78
+ },
79
+ goToLast() {
80
+ this.goTo(r(g.length, d));
81
+ },
82
+ get meta() {
83
+ return y;
84
+ },
85
+ next() {
86
+ this.goTo(this.meta.pageNumber + 1);
87
+ },
88
+ prev() {
89
+ this.goTo(this.meta.pageNumber - 1);
90
+ },
91
+ reset() {
92
+ d = Math.max(1, s.limit ?? a.limit), f = s.filter, p = s.sort, m = "", h = 0, C();
93
+ },
94
+ restore(e) {
95
+ let t = !1;
96
+ if (e.limit !== void 0) {
97
+ let n = Math.max(1, Math.trunc(e.limit));
98
+ n !== d && (d = n, t = !0);
99
+ }
100
+ if (e.search !== void 0 && e.search !== m && (m = e.search, t = !0), e.page !== void 0) {
101
+ let n = Math.max(0, Math.trunc(e.page) - 1);
102
+ n !== h && (h = n, t = !0);
103
+ }
104
+ t && C();
105
+ },
106
+ search(e) {
107
+ T(e);
108
+ },
109
+ searchNow: (e) => {
110
+ _ &&= (clearTimeout(_), void 0), m = e, h = 0, C();
111
+ },
112
+ setData(e) {
113
+ u = [...e], h = 0, C();
114
+ },
115
+ setFilter(e) {
116
+ f = e, h = 0, C();
117
+ },
118
+ setLimit(e) {
119
+ d = Math.max(1, Math.trunc(e)), h = 0, C();
120
+ },
121
+ setSort(e) {
122
+ p = e, h = 0, C();
123
+ },
124
+ subscribe(e) {
125
+ return c.add(e), () => c.delete(e);
126
+ },
127
+ toQuery() {
128
+ return w();
129
+ }
130
+ };
131
+ }
132
+ //#endregion
133
+ export { o as createLocalSource };
134
+
135
+ //# sourceMappingURL=localSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localSource.js","names":[],"sources":["../src/localSource.ts"],"sourcesContent":["import type { LocalBatchContext, LocalConfig, LocalSource, Predicate, Sorter, SourceMeta, SourceQuery } from './types';\n\nimport { decodeLocalQueryParams } from './codecs';\nimport { clampOffset, createMeta, pageCount } from './pagination';\nimport { containsSearch as defaultSearch } from './search';\n\nconst DEFAULTS = { debounceMs: 300, limit: 10 } as const;\n\nexport function createLocalSource<T>(initialData: readonly T[], cfg: LocalConfig<T> = {}): LocalSource<T> {\n const listeners = new Set<() => void>();\n const searchFn = cfg.searchFn ?? defaultSearch;\n\n let rawData: readonly T[] = [...initialData];\n let limit = Math.max(1, cfg.limit ?? DEFAULTS.limit);\n let filterFn: Predicate<T> | undefined = cfg.filter;\n let sortFn: Sorter<T> | undefined = cfg.sort;\n let query = '';\n let offset = 0;\n let view: readonly T[] = [];\n let timer: ReturnType<typeof setTimeout> | undefined;\n let cachedCurrent!: readonly T[];\n let cachedMeta!: SourceMeta;\n\n const refreshCache = () => {\n if (!view.length) {\n cachedCurrent = [];\n } else {\n const start = offset * limit;\n\n cachedCurrent = view.slice(start, start + limit);\n }\n\n cachedMeta = createMeta({\n errorMessage: null,\n isLoading: false,\n isSearchPending: timer !== undefined,\n pageNumber: offset + 1,\n pageSize: limit,\n totalItems: view.length,\n });\n };\n\n const notify = () => {\n refreshCache();\n\n for (const listener of listeners) {\n listener();\n }\n };\n\n const recompute = () => {\n let next = query ? searchFn(rawData, query) : rawData;\n\n if (filterFn) {\n next = next.filter(filterFn);\n }\n\n if (sortFn) {\n next = [...next].sort(sortFn);\n }\n\n const pages = pageCount(next.length, limit);\n\n offset = clampOffset(offset, pages);\n view = next;\n };\n\n const emit = () => {\n recompute();\n notify();\n };\n\n const toQuery = (): SourceQuery => ({\n limit,\n page: offset + 1,\n search: query,\n });\n\n const scheduleSearch = (searchTerm: string) => {\n if (timer) {\n clearTimeout(timer);\n }\n\n query = searchTerm;\n offset = 0;\n\n timer = setTimeout(() => {\n timer = undefined;\n emit();\n }, cfg.debounceMs ?? DEFAULTS.debounceMs);\n\n notify();\n };\n\n const searchNow = (searchTerm: string) => {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n\n query = searchTerm;\n offset = 0;\n emit();\n };\n\n const batch = (mutator: (ctx: LocalBatchContext<T>) => void) => {\n let nextLimit = limit;\n let nextFilter = filterFn;\n let nextSort = sortFn;\n let nextQuery = query;\n let nextData = rawData;\n let nextOffset = offset;\n\n mutator({\n goTo: (page) => {\n nextOffset = Math.max(0, Math.trunc(page) - 1);\n },\n search: (searchTerm) => {\n nextQuery = searchTerm;\n nextOffset = 0;\n },\n setData: (data) => {\n nextData = [...data];\n nextOffset = 0;\n },\n setFilter: (filter) => {\n nextFilter = filter;\n nextOffset = 0;\n },\n setLimit: (patchLimit) => {\n nextLimit = Math.max(1, Math.trunc(patchLimit));\n nextOffset = 0;\n },\n setSort: (sort) => {\n nextSort = sort;\n nextOffset = 0;\n },\n });\n\n limit = nextLimit;\n filterFn = nextFilter;\n sortFn = nextSort;\n query = nextQuery;\n rawData = nextData;\n offset = nextOffset;\n emit();\n };\n\n recompute();\n refreshCache();\n\n return {\n batch,\n commit() {\n if (!timer) {\n return;\n }\n\n clearTimeout(timer);\n timer = undefined;\n emit();\n },\n get current() {\n return cachedCurrent;\n },\n fromQueryParams(params) {\n this.restore(decodeLocalQueryParams(params, cfg.limit ?? DEFAULTS.limit));\n },\n goTo(page) {\n offset = clampOffset(page - 1, pageCount(view.length, limit));\n notify();\n },\n goToLast() {\n this.goTo(pageCount(view.length, limit));\n },\n get meta() {\n return cachedMeta;\n },\n next() {\n this.goTo(this.meta.pageNumber + 1);\n },\n prev() {\n this.goTo(this.meta.pageNumber - 1);\n },\n reset() {\n limit = Math.max(1, cfg.limit ?? DEFAULTS.limit);\n filterFn = cfg.filter;\n sortFn = cfg.sort;\n query = '';\n offset = 0;\n emit();\n },\n restore(state) {\n let changed = false;\n\n if (state.limit !== undefined) {\n const nextLimit = Math.max(1, Math.trunc(state.limit));\n\n if (nextLimit !== limit) {\n limit = nextLimit;\n changed = true;\n }\n }\n\n if (state.search !== undefined && state.search !== query) {\n query = state.search;\n changed = true;\n }\n\n if (state.page !== undefined) {\n const nextOffset = Math.max(0, Math.trunc(state.page) - 1);\n\n if (nextOffset !== offset) {\n offset = nextOffset;\n changed = true;\n }\n }\n\n if (changed) {\n emit();\n }\n },\n search(searchTerm) {\n scheduleSearch(searchTerm);\n },\n searchNow,\n setData(data) {\n rawData = [...data];\n offset = 0;\n emit();\n },\n setFilter(filter) {\n filterFn = filter;\n offset = 0;\n emit();\n },\n setLimit(nextLimit) {\n limit = Math.max(1, Math.trunc(nextLimit));\n offset = 0;\n emit();\n },\n setSort(sort) {\n sortFn = sort;\n offset = 0;\n emit();\n },\n subscribe(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n toQuery() {\n return toQuery();\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e=(e,t)=>{let n=Math.trunc(e);return Number.isFinite(n)?Math.max(t,n):t},t=(t,n)=>{let r=e(t,0),i=e(n,1);return Math.max(1,Math.ceil(r/i))},n=(t,n)=>Math.max(1,Math.min(e(t,1),Math.max(1,n))),r=(t,n)=>Math.max(0,Math.min(e(t,0),Math.max(1,n)-1)),i=r=>{let i=e(r.pageSize,1),a=e(r.totalItems,0),o=t(a,i),s=n(r.pageNumber,o),c=a===0,l=c?0:(s-1)*i+1,u=c?0:Math.min(s*i,a);return{errorMessage:r.errorMessage,hasNoItems:c,isFirstPage:s<=1,isLastPage:s>=o,isLoading:r.isLoading,isSearchPending:r.isSearchPending,itemEnd:u,itemStart:l,pageCount:o,pageNumber:s,pageSize:i,totalItems:a}};exports.clampOffset=r,exports.createMeta=i,exports.pageCount=t;
2
+ //# sourceMappingURL=pagination.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination.cjs","names":[],"sources":["../src/pagination.ts"],"sourcesContent":["import type { SourceMeta } from './types';\n\nconst clampInt = (value: number, minimum: number) => {\n const parsed = Math.trunc(value);\n\n if (!Number.isFinite(parsed)) {\n return minimum;\n }\n\n return Math.max(minimum, parsed);\n};\n\nexport const pageCount = (total: number, limit: number) => {\n const safeTotal = clampInt(total, 0);\n const safeLimit = clampInt(limit, 1);\n\n return Math.max(1, Math.ceil(safeTotal / safeLimit));\n};\n\nexport const clampPage = (page: number, pages: number) => {\n return Math.max(1, Math.min(clampInt(page, 1), Math.max(1, pages)));\n};\n\nexport const clampOffset = (offset: number, pages: number) => {\n return Math.max(0, Math.min(clampInt(offset, 0), Math.max(1, pages) - 1));\n};\n\nexport const createMeta = (state: {\n errorMessage: string | null;\n isLoading: boolean;\n isSearchPending: boolean;\n pageNumber: number;\n pageSize: number;\n totalItems: number;\n}): SourceMeta => {\n const safeLimit = clampInt(state.pageSize, 1);\n const safeTotal = clampInt(state.totalItems, 0);\n const pages = pageCount(safeTotal, safeLimit);\n const page = clampPage(state.pageNumber, pages);\n const isEmpty = safeTotal === 0;\n const start = isEmpty ? 0 : (page - 1) * safeLimit + 1;\n const end = isEmpty ? 0 : Math.min(page * safeLimit, safeTotal);\n\n return {\n errorMessage: state.errorMessage,\n hasNoItems: isEmpty,\n isFirstPage: page <= 1,\n isLastPage: page >= pages,\n isLoading: state.isLoading,\n isSearchPending: state.isSearchPending,\n itemEnd: end,\n itemStart: start,\n pageCount: pages,\n pageNumber: page,\n pageSize: safeLimit,\n totalItems: safeTotal,\n };\n};\n"],"mappings":"AAEA,IAAM,GAAY,EAAe,IAAoB,CACnD,IAAM,EAAS,KAAK,MAAM,CAAK,EAM/B,OAJK,OAAO,SAAS,CAAM,EAIpB,KAAK,IAAI,EAAS,CAAM,EAHtB,CAIX,EAEa,GAAa,EAAe,IAAkB,CACzD,IAAM,EAAY,EAAS,EAAO,CAAC,EAC7B,EAAY,EAAS,EAAO,CAAC,EAEnC,OAAO,KAAK,IAAI,EAAG,KAAK,KAAK,EAAY,CAAS,CAAC,CACrD,EAEa,GAAa,EAAc,IAC/B,KAAK,IAAI,EAAG,KAAK,IAAI,EAAS,EAAM,CAAC,EAAG,KAAK,IAAI,EAAG,CAAK,CAAC,CAAC,EAGvD,GAAe,EAAgB,IACnC,KAAK,IAAI,EAAG,KAAK,IAAI,EAAS,EAAQ,CAAC,EAAG,KAAK,IAAI,EAAG,CAAK,EAAI,CAAC,CAAC,EAG7D,EAAc,GAOT,CAChB,IAAM,EAAY,EAAS,EAAM,SAAU,CAAC,EACtC,EAAY,EAAS,EAAM,WAAY,CAAC,EACxC,EAAQ,EAAU,EAAW,CAAS,EACtC,EAAO,EAAU,EAAM,WAAY,CAAK,EACxC,EAAU,IAAc,EACxB,EAAQ,EAAU,GAAK,EAAO,GAAK,EAAY,EAC/C,EAAM,EAAU,EAAI,KAAK,IAAI,EAAO,EAAW,CAAS,EAE9D,MAAO,CACL,aAAc,EAAM,aACpB,WAAY,EACZ,YAAa,GAAQ,EACrB,WAAY,GAAQ,EACpB,UAAW,EAAM,UACjB,gBAAiB,EAAM,gBACvB,QAAS,EACT,UAAW,EACX,UAAW,EACX,WAAY,EACZ,SAAU,EACV,WAAY,CACd,CACF"}
@@ -0,0 +1,13 @@
1
+ import type { SourceMeta } from './types';
2
+ export declare const pageCount: (total: number, limit: number) => number;
3
+ export declare const clampPage: (page: number, pages: number) => number;
4
+ export declare const clampOffset: (offset: number, pages: number) => number;
5
+ export declare const createMeta: (state: {
6
+ errorMessage: string | null;
7
+ isLoading: boolean;
8
+ isSearchPending: boolean;
9
+ pageNumber: number;
10
+ pageSize: number;
11
+ totalItems: number;
12
+ }) => SourceMeta;
13
+ //# sourceMappingURL=pagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAY1C,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,EAAE,OAAO,MAAM,WAKrD,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,WAEpD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,OAAO,MAAM,WAExD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,OAAO;IAChC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,KAAG,UAuBH,CAAC"}
@@ -0,0 +1,28 @@
1
+ //#region src/pagination.ts
2
+ var e = (e, t) => {
3
+ let n = Math.trunc(e);
4
+ return Number.isFinite(n) ? Math.max(t, n) : t;
5
+ }, t = (t, n) => {
6
+ let r = e(t, 0), i = e(n, 1);
7
+ return Math.max(1, Math.ceil(r / i));
8
+ }, n = (t, n) => Math.max(1, Math.min(e(t, 1), Math.max(1, n))), r = (t, n) => Math.max(0, Math.min(e(t, 0), Math.max(1, n) - 1)), i = (r) => {
9
+ let i = e(r.pageSize, 1), a = e(r.totalItems, 0), o = t(a, i), s = n(r.pageNumber, o), c = a === 0, l = c ? 0 : (s - 1) * i + 1, u = c ? 0 : Math.min(s * i, a);
10
+ return {
11
+ errorMessage: r.errorMessage,
12
+ hasNoItems: c,
13
+ isFirstPage: s <= 1,
14
+ isLastPage: s >= o,
15
+ isLoading: r.isLoading,
16
+ isSearchPending: r.isSearchPending,
17
+ itemEnd: u,
18
+ itemStart: l,
19
+ pageCount: o,
20
+ pageNumber: s,
21
+ pageSize: i,
22
+ totalItems: a
23
+ };
24
+ };
25
+ //#endregion
26
+ export { r as clampOffset, i as createMeta, t as pageCount };
27
+
28
+ //# sourceMappingURL=pagination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination.js","names":[],"sources":["../src/pagination.ts"],"sourcesContent":["import type { SourceMeta } from './types';\n\nconst clampInt = (value: number, minimum: number) => {\n const parsed = Math.trunc(value);\n\n if (!Number.isFinite(parsed)) {\n return minimum;\n }\n\n return Math.max(minimum, parsed);\n};\n\nexport const pageCount = (total: number, limit: number) => {\n const safeTotal = clampInt(total, 0);\n const safeLimit = clampInt(limit, 1);\n\n return Math.max(1, Math.ceil(safeTotal / safeLimit));\n};\n\nexport const clampPage = (page: number, pages: number) => {\n return Math.max(1, Math.min(clampInt(page, 1), Math.max(1, pages)));\n};\n\nexport const clampOffset = (offset: number, pages: number) => {\n return Math.max(0, Math.min(clampInt(offset, 0), Math.max(1, pages) - 1));\n};\n\nexport const createMeta = (state: {\n errorMessage: string | null;\n isLoading: boolean;\n isSearchPending: boolean;\n pageNumber: number;\n pageSize: number;\n totalItems: number;\n}): SourceMeta => {\n const safeLimit = clampInt(state.pageSize, 1);\n const safeTotal = clampInt(state.totalItems, 0);\n const pages = pageCount(safeTotal, safeLimit);\n const page = clampPage(state.pageNumber, pages);\n const isEmpty = safeTotal === 0;\n const start = isEmpty ? 0 : (page - 1) * safeLimit + 1;\n const end = isEmpty ? 0 : Math.min(page * safeLimit, safeTotal);\n\n return {\n errorMessage: state.errorMessage,\n hasNoItems: isEmpty,\n isFirstPage: page <= 1,\n isLastPage: page >= pages,\n isLoading: state.isLoading,\n isSearchPending: state.isSearchPending,\n itemEnd: end,\n itemStart: start,\n pageCount: pages,\n pageNumber: page,\n pageSize: safeLimit,\n totalItems: safeTotal,\n };\n};\n"],"mappings":";AAEA,IAAM,KAAY,GAAe,MAAoB;CACnD,IAAM,IAAS,KAAK,MAAM,CAAK;CAM/B,OAJK,OAAO,SAAS,CAAM,IAIpB,KAAK,IAAI,GAAS,CAAM,IAHtB;AAIX,GAEa,KAAa,GAAe,MAAkB;CACzD,IAAM,IAAY,EAAS,GAAO,CAAC,GAC7B,IAAY,EAAS,GAAO,CAAC;CAEnC,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,IAAY,CAAS,CAAC;AACrD,GAEa,KAAa,GAAc,MAC/B,KAAK,IAAI,GAAG,KAAK,IAAI,EAAS,GAAM,CAAC,GAAG,KAAK,IAAI,GAAG,CAAK,CAAC,CAAC,GAGvD,KAAe,GAAgB,MACnC,KAAK,IAAI,GAAG,KAAK,IAAI,EAAS,GAAQ,CAAC,GAAG,KAAK,IAAI,GAAG,CAAK,IAAI,CAAC,CAAC,GAG7D,KAAc,MAOT;CAChB,IAAM,IAAY,EAAS,EAAM,UAAU,CAAC,GACtC,IAAY,EAAS,EAAM,YAAY,CAAC,GACxC,IAAQ,EAAU,GAAW,CAAS,GACtC,IAAO,EAAU,EAAM,YAAY,CAAK,GACxC,IAAU,MAAc,GACxB,IAAQ,IAAU,KAAK,IAAO,KAAK,IAAY,GAC/C,IAAM,IAAU,IAAI,KAAK,IAAI,IAAO,GAAW,CAAS;CAE9D,OAAO;EACL,cAAc,EAAM;EACpB,YAAY;EACZ,aAAa,KAAQ;EACrB,YAAY,KAAQ;EACpB,WAAW,EAAM;EACjB,iBAAiB,EAAM;EACvB,SAAS;EACT,WAAW;EACX,WAAW;EACX,YAAY;EACZ,UAAU;EACV,YAAY;CACd;AACF"}
@@ -0,0 +1,2 @@
1
+ function e(e){return(t,n,r)=>!e(t,n,r)}function t(...e){return(t,n,r)=>e.every(e=>e(t,n,r))}function n(...e){return(t,n,r)=>e.some(e=>e(t,n,r))}exports.and=t,exports.not=e,exports.or=n;
2
+ //# sourceMappingURL=predicates.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicates.cjs","names":[],"sources":["../src/predicates.ts"],"sourcesContent":["import type { Predicate } from './types';\n\n/**\n * Returns a predicate that negates the given predicate.\n */\nexport function not<T>(predicate: Predicate<T>): Predicate<T> {\n return (item, index, array) => !predicate(item, index, array);\n}\n\n/**\n * Returns a predicate that succeeds when all predicates succeed.\n */\nexport function and<T>(...predicates: Predicate<T>[]): Predicate<T> {\n return (item, index, array) => predicates.every((p) => p(item, index, array));\n}\n\n/**\n * Returns a predicate that succeeds when any predicate succeeds.\n */\nexport function or<T>(...predicates: Predicate<T>[]): Predicate<T> {\n return (item, index, array) => predicates.some((p) => p(item, index, array));\n}\n"],"mappings":"AAKA,SAAgB,EAAO,EAAuC,CAC5D,OAAQ,EAAM,EAAO,IAAU,CAAC,EAAU,EAAM,EAAO,CAAK,CAC9D,CAKA,SAAgB,EAAO,GAAG,EAA0C,CAClE,OAAQ,EAAM,EAAO,IAAU,EAAW,MAAO,GAAM,EAAE,EAAM,EAAO,CAAK,CAAC,CAC9E,CAKA,SAAgB,EAAM,GAAG,EAA0C,CACjE,OAAQ,EAAM,EAAO,IAAU,EAAW,KAAM,GAAM,EAAE,EAAM,EAAO,CAAK,CAAC,CAC7E"}
@@ -0,0 +1,14 @@
1
+ import type { Predicate } from './types';
2
+ /**
3
+ * Returns a predicate that negates the given predicate.
4
+ */
5
+ export declare function not<T>(predicate: Predicate<T>): Predicate<T>;
6
+ /**
7
+ * Returns a predicate that succeeds when all predicates succeed.
8
+ */
9
+ export declare function and<T>(...predicates: Predicate<T>[]): Predicate<T>;
10
+ /**
11
+ * Returns a predicate that succeeds when any predicate succeeds.
12
+ */
13
+ export declare function or<T>(...predicates: Predicate<T>[]): Predicate<T>;
14
+ //# sourceMappingURL=predicates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicates.d.ts","sourceRoot":"","sources":["../src/predicates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;GAEG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAE5D;AAED;;GAEG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAElE;AAED;;GAEG;AACH,wBAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAEjE"}
@@ -0,0 +1,14 @@
1
+ //#region src/predicates.ts
2
+ function e(e) {
3
+ return (t, n, r) => !e(t, n, r);
4
+ }
5
+ function t(...e) {
6
+ return (t, n, r) => e.every((e) => e(t, n, r));
7
+ }
8
+ function n(...e) {
9
+ return (t, n, r) => e.some((e) => e(t, n, r));
10
+ }
11
+ //#endregion
12
+ export { t as and, e as not, n as or };
13
+
14
+ //# sourceMappingURL=predicates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicates.js","names":[],"sources":["../src/predicates.ts"],"sourcesContent":["import type { Predicate } from './types';\n\n/**\n * Returns a predicate that negates the given predicate.\n */\nexport function not<T>(predicate: Predicate<T>): Predicate<T> {\n return (item, index, array) => !predicate(item, index, array);\n}\n\n/**\n * Returns a predicate that succeeds when all predicates succeed.\n */\nexport function and<T>(...predicates: Predicate<T>[]): Predicate<T> {\n return (item, index, array) => predicates.every((p) => p(item, index, array));\n}\n\n/**\n * Returns a predicate that succeeds when any predicate succeeds.\n */\nexport function or<T>(...predicates: Predicate<T>[]): Predicate<T> {\n return (item, index, array) => predicates.some((p) => p(item, index, array));\n}\n"],"mappings":";AAKA,SAAgB,EAAO,GAAuC;CAC5D,QAAQ,GAAM,GAAO,MAAU,CAAC,EAAU,GAAM,GAAO,CAAK;AAC9D;AAKA,SAAgB,EAAO,GAAG,GAA0C;CAClE,QAAQ,GAAM,GAAO,MAAU,EAAW,OAAO,MAAM,EAAE,GAAM,GAAO,CAAK,CAAC;AAC9E;AAKA,SAAgB,EAAM,GAAG,GAA0C;CACjE,QAAQ,GAAM,GAAO,MAAU,EAAW,MAAM,MAAM,EAAE,GAAM,GAAO,CAAK,CAAC;AAC7E"}
@@ -0,0 +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)=>n=>{let r=e(n);return!(t.min!==void 0&&r<t.min||t.max!==void 0&&r>t.max)},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;
2
+ //# sourceMappingURL=presets.cjs.map
@@ -0,0 +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) => number,\n bounds: Readonly<{ max?: number; min?: number }>,\n): Predicate<T> => {\n return (item) => {\n const value = getValue(item);\n\n if (bounds.min !== undefined && value < bounds.min) return false;\n\n if (bounds.max !== undefined && value > bounds.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,GAE3C,SAAS,CAAM,CACjC,CACF,EAEa,GAAsB,EAA0B,IACnD,GAAS,OAAO,GAAG,EAAS,CAAI,EAAG,CAAQ,EAGxC,GACX,EACA,IAEQ,GAAS,CACf,IAAM,EAAQ,EAAS,CAAI,EAM3B,MAFA,EAFI,EAAO,MAAQ,IAAA,IAAa,EAAQ,EAAO,KAE3C,EAAO,MAAQ,IAAA,IAAa,EAAQ,EAAO,IAGjD,EAGW,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"}
@@ -0,0 +1,12 @@
1
+ import type { Predicate, Sorter } from './types';
2
+ type Direction = 'asc' | 'desc';
3
+ type PrimitiveComparable = number | string | Date;
4
+ export declare const filterContains: <T>(getValue: (item: T) => string | null | undefined, query: string, caseSensitive?: boolean) => Predicate<T>;
5
+ export declare const filterEquals: <T, V>(getValue: (item: T) => V, expected: V) => Predicate<T>;
6
+ export declare const filterRange: <T>(getValue: (item: T) => number, bounds: Readonly<{
7
+ max?: number;
8
+ min?: number;
9
+ }>) => Predicate<T>;
10
+ export declare const sortBy: <T, V extends PrimitiveComparable>(getValue: (item: T) => V, direction?: Direction) => Sorter<T>;
11
+ export {};
12
+ //# sourceMappingURL=presets.d.ts.map
@@ -0,0 +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,MAAM,EAC7B,QAAQ,QAAQ,CAAC;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAC/C,SAAS,CAAC,CAAC,CAUb,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"}
@@ -0,0 +1,21 @@
1
+ //#region src/presets.ts
2
+ var e = (e, t, n = !1) => {
3
+ let r = n ? t : t.toLowerCase();
4
+ return (t) => {
5
+ let i = e(t) ?? "";
6
+ return (n ? i : i.toLowerCase()).includes(r);
7
+ };
8
+ }, t = (e, t) => (n) => Object.is(e(n), t), n = (e, t) => (n) => {
9
+ let r = e(n);
10
+ return !(t.min !== void 0 && r < t.min || t.max !== void 0 && r > t.max);
11
+ }, r = (e, t = "asc") => {
12
+ let n = t === "asc" ? 1 : -1;
13
+ return (t, r) => {
14
+ let i = e(t), a = e(r);
15
+ return i instanceof Date && a instanceof Date ? (i.getTime() - a.getTime()) * n : i < a ? -1 * n : i > a ? 1 * n : 0;
16
+ };
17
+ };
18
+ //#endregion
19
+ export { e as filterContains, t as filterEquals, n as filterRange, r as sortBy };
20
+
21
+ //# sourceMappingURL=presets.js.map
@@ -0,0 +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) => number,\n bounds: Readonly<{ max?: number; min?: number }>,\n): Predicate<T> => {\n return (item) => {\n const value = getValue(item);\n\n if (bounds.min !== undefined && value < bounds.min) return false;\n\n if (bounds.max !== undefined && value > bounds.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,GAE3C,SAAS,CAAM;CACjC;AACF,GAEa,KAAsB,GAA0B,OACnD,MAAS,OAAO,GAAG,EAAS,CAAI,GAAG,CAAQ,GAGxC,KACX,GACA,OAEQ,MAAS;CACf,IAAM,IAAQ,EAAS,CAAI;CAM3B,OAFA,EAFI,EAAO,QAAQ,KAAA,KAAa,IAAQ,EAAO,OAE3C,EAAO,QAAQ,KAAA,KAAa,IAAQ,EAAO;AAGjD,GAGW,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"}
@@ -0,0 +1,2 @@
1
+ const e=require(`./codecs.cjs`),t=require(`./pagination.cjs`);var n=e=>JSON.stringify(e,(e,t)=>{if(typeof t==`object`&&t&&!Array.isArray(t)){let e={};for(let n of Object.keys(t).sort())e[n]=t[n];return e}return t});function r(r){let i=new Set,a=Math.max(1,r.limit??10),o=r.debounceMs??300,s=1,c=a,l=``,u=r.filter,d=r.sort,f=[],p=0,m=null,h,g=0,_=``,v=new Map,y=()=>({filter:u,limit:c,page:s,search:l||void 0,sort:d}),b=()=>({filter:u,limit:c,page:s,search:l,sort:d}),x=e=>{f=e.items,p=e.total??0;let n=t.pageCount(p,c);s=Math.min(Math.max(1,s),n)},S=()=>{h=t.createMeta({errorMessage:m,isLoading:g>0,isSearchPending:E!==void 0,pageNumber:s,pageSize:c,totalItems:p})},C=()=>{S();for(let e of i)e()},w=async e=>{let t=n(e);if(_=t,v.has(t)){g++,C();try{await v.get(t)}finally{g--,C()}return}g++,m=null,C();let i=r.fetch(e).then(e=>{t===_&&(x(e),m=null)}).catch(e=>{t===_&&(m=e?.message??`Request failed`,f=[],p=0)}).finally(()=>{v.delete(t),g--,C()});v.set(t,i),await i},T=()=>w(y()),E,D=()=>{E&&clearTimeout(E),E=setTimeout(()=>{E=void 0,T()},o),C()},O={batch:e=>{let t=s,n=c,r=l,i=u,a=d;return e({goTo:e=>{t=Math.max(1,Math.trunc(e))},search:e=>{r=e,t=1},setFilter:e=>{i=e,t=1},setLimit:e=>{n=Math.max(1,Math.trunc(e)),t=1},setSort:e=>{a=e,t=1}}),s=t,c=n,l=r,u=i,d=a,T()},commit(){return E?(clearTimeout(E),E=void 0,T()):Promise.resolve()},get current(){return f},fromQueryParams(t){return O.restore(e.decodeRemoteQueryParams(t,a))},goTo(e){return s=Math.max(1,Math.trunc(e)),T()},goToLast(){return O.goTo(t.pageCount(p,c))},get meta(){return h},next(){let e=t.pageCount(p,c);return s>=e?Promise.resolve():(s+=1,T())},prev(){return s<=1?Promise.resolve():(--s,T())},ready(){return g===0&&!E?Promise.resolve():new Promise(e=>{let t=()=>{g===0&&!E&&(i.delete(t),e())};i.add(t)})},refresh(){return T()},reset(){return s=1,c=a,l=``,u=r.filter,d=r.sort,T()},restore(e){let t=!1;if(`limit`in e&&e.limit!==void 0){let n=Math.max(1,Math.trunc(e.limit));n!==c&&(c=n,t=!0)}if(`search`in e&&e.search!==void 0&&e.search!==l&&(l=e.search,t=!0),`filter`in e&&e.filter!==u&&(u=e.filter,t=!0),`sort`in e&&e.sort!==d&&(d=e.sort,t=!0),`page`in e&&e.page!==void 0){let n=Math.max(1,Math.trunc(e.page));n!==s&&(s=n,t=!0)}return t?T():Promise.resolve()},search(e){l=e,s=1,D()},searchNow:e=>(l=e,s=1,E&&=(clearTimeout(E),void 0),T()),setFilter(e){return u=e,s=1,T()},setLimit(e){return c=Math.max(1,Math.trunc(e)),s=1,T()},setSort(e){return d=e,s=1,T()},subscribe(e){return i.add(e),()=>i.delete(e)},toQuery(){return b()}};return S(),O}exports.createRemoteSource=r;
2
+ //# sourceMappingURL=remoteSource.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remoteSource.cjs","names":[],"sources":["../src/remoteSource.ts"],"sourcesContent":["import type { RemoteBatchContext, RemoteConfig, RemoteSource, RemoteSourceQuery, SourceMeta } from './types';\n\nimport { decodeRemoteQueryParams } from './codecs';\nimport { createMeta, pageCount } from './pagination';\n\ntype RemoteQuery<TFilter, TSort> = Readonly<{\n filter?: TFilter;\n limit: number;\n page: number;\n search?: string;\n sort?: TSort;\n}>;\n\ntype RemoteResult<T> = Readonly<{ items: readonly T[]; total: number }>;\n\n// Recursively sort object keys for stable, order-independent cache keys.\nconst keyOf = (q: unknown): string =>\n JSON.stringify(q, (_, value: unknown) => {\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n const sorted: Record<string, unknown> = {};\n\n for (const k of Object.keys(value as object).sort()) {\n sorted[k] = (value as Record<string, unknown>)[k];\n }\n\n return sorted;\n }\n\n return value;\n });\n\nexport function createRemoteSource<T, TFilter = unknown, TSort = unknown>(\n cfg: RemoteConfig<T, TFilter, TSort>,\n): RemoteSource<T, TFilter, TSort> {\n const listeners = new Set<() => void>();\n\n const limitDefault = Math.max(1, cfg.limit ?? 10);\n const debounceMs = cfg.debounceMs ?? 300;\n\n let page = 1;\n let limit = limitDefault;\n let search = '';\n let filter = cfg.filter as TFilter | undefined;\n let sort = cfg.sort as TSort | undefined;\n\n let items: readonly T[] = [];\n let total = 0;\n let error: string | null = null;\n let cachedMeta!: SourceMeta;\n\n // pendingCount tracks the number of in-flight requests across all concurrent calls,\n // including joiners that attach to an existing in-flight request for the same key.\n // isLoading is derived from pendingCount > 0 so it stays true until every caller is settled.\n let pendingCount = 0;\n\n // latestKey is the key of the most recently dispatched query.\n // Only the response whose key matches latestKey at the time of resolution is applied to state,\n // preventing stale out-of-order responses from overwriting newer results.\n let latestKey = '';\n\n const inflight = new Map<string, Promise<void>>();\n\n const queryOf = (): RemoteQuery<TFilter, TSort> => ({\n filter,\n limit,\n page,\n search: search || undefined,\n sort,\n });\n\n const toQuery = (): RemoteSourceQuery<TFilter, TSort> => ({\n filter,\n limit,\n page,\n search,\n sort,\n });\n\n const assign = (res: RemoteResult<T>) => {\n items = res.items;\n total = res.total ?? 0;\n\n const pages = pageCount(total, limit);\n\n page = Math.min(Math.max(1, page), pages);\n };\n\n const refreshMeta = () => {\n cachedMeta = createMeta({\n errorMessage: error,\n isLoading: pendingCount > 0,\n isSearchPending: timer !== undefined,\n pageNumber: page,\n pageSize: limit,\n totalItems: total,\n });\n };\n\n const notify = () => {\n refreshMeta();\n\n for (const listener of listeners) {\n listener();\n }\n };\n\n const fetchQuery = async (q: RemoteQuery<TFilter, TSort>): Promise<void> => {\n const key = keyOf(q);\n\n latestKey = key;\n\n // Join an identical in-flight request rather than issuing a duplicate.\n if (inflight.has(key)) {\n pendingCount++;\n notify();\n\n try {\n await inflight.get(key);\n } finally {\n pendingCount--;\n notify();\n }\n\n return;\n }\n\n pendingCount++;\n error = null;\n notify();\n\n const promise = cfg\n .fetch(q)\n .then((result) => {\n // Only apply if this is still the latest query; discard stale responses.\n if (key === latestKey) {\n assign(result);\n error = null;\n }\n })\n .catch((reason) => {\n if (key === latestKey) {\n error = reason?.message ?? 'Request failed';\n items = [];\n total = 0;\n }\n })\n .finally(() => {\n inflight.delete(key);\n pendingCount--;\n notify();\n });\n\n inflight.set(key, promise);\n await promise;\n };\n\n const doUpdate = () => fetchQuery(queryOf());\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const debounced = () => {\n if (timer) clearTimeout(timer);\n\n timer = setTimeout(() => {\n timer = undefined;\n void doUpdate();\n }, debounceMs);\n\n notify();\n };\n\n const searchNow = (q: string): Promise<void> => {\n search = q;\n page = 1;\n\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n\n return doUpdate();\n };\n\n const batch = (mutator: (ctx: RemoteBatchContext<TFilter, TSort>) => void): Promise<void> => {\n let nextPage = page;\n let nextLimit = limit;\n let nextSearch = search;\n let nextFilter = filter as TFilter | undefined;\n let nextSort = sort as TSort | undefined;\n\n mutator({\n goTo: (p) => {\n nextPage = Math.max(1, Math.trunc(p));\n },\n search: (q) => {\n nextSearch = q;\n nextPage = 1;\n },\n setFilter: (f) => {\n nextFilter = f;\n nextPage = 1;\n },\n setLimit: (n) => {\n nextLimit = Math.max(1, Math.trunc(n));\n nextPage = 1;\n },\n setSort: (s) => {\n nextSort = s;\n nextPage = 1;\n },\n });\n\n page = nextPage;\n limit = nextLimit;\n search = nextSearch;\n filter = nextFilter;\n sort = nextSort;\n\n return doUpdate();\n };\n\n const api: RemoteSource<T, TFilter, TSort> = {\n batch,\n commit() {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n\n return doUpdate();\n }\n\n return Promise.resolve();\n },\n get current() {\n return items;\n },\n fromQueryParams(params) {\n return api.restore(decodeRemoteQueryParams(params, limitDefault));\n },\n goTo(p) {\n page = Math.max(1, Math.trunc(p));\n\n return doUpdate();\n },\n goToLast() {\n return api.goTo(pageCount(total, limit));\n },\n get meta() {\n return cachedMeta;\n },\n next() {\n const pages = pageCount(total, limit);\n\n if (page >= pages) return Promise.resolve();\n\n page += 1;\n\n return doUpdate();\n },\n prev() {\n if (page <= 1) return Promise.resolve();\n\n page -= 1;\n\n return doUpdate();\n },\n ready() {\n if (pendingCount === 0 && !timer) {\n return Promise.resolve();\n }\n\n return new Promise<void>((resolve) => {\n const checkReady = () => {\n if (pendingCount === 0 && !timer) {\n listeners.delete(checkReady);\n resolve();\n }\n };\n\n listeners.add(checkReady);\n });\n },\n refresh() {\n return doUpdate();\n },\n reset() {\n page = 1;\n limit = limitDefault;\n search = '';\n filter = cfg.filter as TFilter | undefined;\n sort = cfg.sort as TSort | undefined;\n\n return doUpdate();\n },\n restore(state) {\n let changed = false;\n\n if ('limit' in state && state.limit !== undefined) {\n const nextLimit = Math.max(1, Math.trunc(state.limit));\n\n if (nextLimit !== limit) {\n limit = nextLimit;\n changed = true;\n }\n }\n\n if ('search' in state && state.search !== undefined) {\n if (state.search !== search) {\n search = state.search;\n changed = true;\n }\n }\n\n if ('filter' in state) {\n if (state.filter !== filter) {\n filter = state.filter;\n changed = true;\n }\n }\n\n if ('sort' in state) {\n if (state.sort !== sort) {\n sort = state.sort;\n changed = true;\n }\n }\n\n if ('page' in state && state.page !== undefined) {\n const nextPage = Math.max(1, Math.trunc(state.page));\n\n if (nextPage !== page) {\n page = nextPage;\n changed = true;\n }\n }\n\n if (changed) return doUpdate();\n\n return Promise.resolve();\n },\n search(query) {\n search = query;\n page = 1;\n debounced();\n },\n searchNow,\n setFilter(f) {\n filter = f;\n page = 1;\n\n return doUpdate();\n },\n setLimit(n) {\n limit = Math.max(1, Math.trunc(n));\n page = 1;\n\n return doUpdate();\n },\n setSort(s) {\n sort = s;\n page = 1;\n\n return doUpdate();\n },\n subscribe(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n toQuery() {\n return toQuery();\n },\n };\n\n refreshMeta();\n\n return api;\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { RemoteConfig, RemoteSource } from './types';
2
+ export declare function createRemoteSource<T, TFilter = unknown, TSort = unknown>(cfg: RemoteConfig<T, TFilter, TSort>): RemoteSource<T, TFilter, TSort>;
3
+ //# sourceMappingURL=remoteSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remoteSource.d.ts","sourceRoot":"","sources":["../src/remoteSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,YAAY,EAAE,YAAY,EAAiC,MAAM,SAAS,CAAC;AA+B7G,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,CAwVjC"}
@@ -0,0 +1,162 @@
1
+ import { decodeRemoteQueryParams as e } from "./codecs.js";
2
+ import { createMeta as t, pageCount as n } from "./pagination.js";
3
+ //#region src/remoteSource.ts
4
+ var r = (e) => JSON.stringify(e, (e, t) => {
5
+ if (typeof t == "object" && t && !Array.isArray(t)) {
6
+ let e = {};
7
+ for (let n of Object.keys(t).sort()) e[n] = t[n];
8
+ return e;
9
+ }
10
+ return t;
11
+ });
12
+ function i(i) {
13
+ let a = /* @__PURE__ */ new Set(), o = Math.max(1, i.limit ?? 10), s = i.debounceMs ?? 300, c = 1, l = o, u = "", d = i.filter, f = i.sort, p = [], m = 0, h = null, g, _ = 0, v = "", y = /* @__PURE__ */ new Map(), b = () => ({
14
+ filter: d,
15
+ limit: l,
16
+ page: c,
17
+ search: u || void 0,
18
+ sort: f
19
+ }), x = () => ({
20
+ filter: d,
21
+ limit: l,
22
+ page: c,
23
+ search: u,
24
+ sort: f
25
+ }), S = (e) => {
26
+ p = e.items, m = e.total ?? 0;
27
+ let t = n(m, l);
28
+ c = Math.min(Math.max(1, c), t);
29
+ }, C = () => {
30
+ g = t({
31
+ errorMessage: h,
32
+ isLoading: _ > 0,
33
+ isSearchPending: D !== void 0,
34
+ pageNumber: c,
35
+ pageSize: l,
36
+ totalItems: m
37
+ });
38
+ }, w = () => {
39
+ C();
40
+ for (let e of a) e();
41
+ }, T = async (e) => {
42
+ let t = r(e);
43
+ if (v = t, y.has(t)) {
44
+ _++, w();
45
+ try {
46
+ await y.get(t);
47
+ } finally {
48
+ _--, w();
49
+ }
50
+ return;
51
+ }
52
+ _++, h = null, w();
53
+ let n = i.fetch(e).then((e) => {
54
+ t === v && (S(e), h = null);
55
+ }).catch((e) => {
56
+ t === v && (h = e?.message ?? "Request failed", p = [], m = 0);
57
+ }).finally(() => {
58
+ y.delete(t), _--, w();
59
+ });
60
+ y.set(t, n), await n;
61
+ }, E = () => T(b()), D, O = () => {
62
+ D && clearTimeout(D), D = setTimeout(() => {
63
+ D = void 0, E();
64
+ }, s), w();
65
+ }, k = {
66
+ batch: (e) => {
67
+ let t = c, n = l, r = u, i = d, a = f;
68
+ return e({
69
+ goTo: (e) => {
70
+ t = Math.max(1, Math.trunc(e));
71
+ },
72
+ search: (e) => {
73
+ r = e, t = 1;
74
+ },
75
+ setFilter: (e) => {
76
+ i = e, t = 1;
77
+ },
78
+ setLimit: (e) => {
79
+ n = Math.max(1, Math.trunc(e)), t = 1;
80
+ },
81
+ setSort: (e) => {
82
+ a = e, t = 1;
83
+ }
84
+ }), c = t, l = n, u = r, d = i, f = a, E();
85
+ },
86
+ commit() {
87
+ return D ? (clearTimeout(D), D = void 0, E()) : Promise.resolve();
88
+ },
89
+ get current() {
90
+ return p;
91
+ },
92
+ fromQueryParams(t) {
93
+ return k.restore(e(t, o));
94
+ },
95
+ goTo(e) {
96
+ return c = Math.max(1, Math.trunc(e)), E();
97
+ },
98
+ goToLast() {
99
+ return k.goTo(n(m, l));
100
+ },
101
+ get meta() {
102
+ return g;
103
+ },
104
+ next() {
105
+ let e = n(m, l);
106
+ return c >= e ? Promise.resolve() : (c += 1, E());
107
+ },
108
+ prev() {
109
+ return c <= 1 ? Promise.resolve() : (--c, E());
110
+ },
111
+ ready() {
112
+ return _ === 0 && !D ? Promise.resolve() : new Promise((e) => {
113
+ let t = () => {
114
+ _ === 0 && !D && (a.delete(t), e());
115
+ };
116
+ a.add(t);
117
+ });
118
+ },
119
+ refresh() {
120
+ return E();
121
+ },
122
+ reset() {
123
+ return c = 1, l = o, u = "", d = i.filter, f = i.sort, E();
124
+ },
125
+ restore(e) {
126
+ let t = !1;
127
+ if ("limit" in e && e.limit !== void 0) {
128
+ let n = Math.max(1, Math.trunc(e.limit));
129
+ n !== l && (l = n, t = !0);
130
+ }
131
+ if ("search" in e && e.search !== void 0 && e.search !== u && (u = e.search, t = !0), "filter" in e && e.filter !== d && (d = e.filter, t = !0), "sort" in e && e.sort !== f && (f = e.sort, t = !0), "page" in e && e.page !== void 0) {
132
+ let n = Math.max(1, Math.trunc(e.page));
133
+ n !== c && (c = n, t = !0);
134
+ }
135
+ return t ? E() : Promise.resolve();
136
+ },
137
+ search(e) {
138
+ u = e, c = 1, O();
139
+ },
140
+ searchNow: (e) => (u = e, c = 1, D &&= (clearTimeout(D), void 0), E()),
141
+ setFilter(e) {
142
+ return d = e, c = 1, E();
143
+ },
144
+ setLimit(e) {
145
+ return l = Math.max(1, Math.trunc(e)), c = 1, E();
146
+ },
147
+ setSort(e) {
148
+ return f = e, c = 1, E();
149
+ },
150
+ subscribe(e) {
151
+ return a.add(e), () => a.delete(e);
152
+ },
153
+ toQuery() {
154
+ return x();
155
+ }
156
+ };
157
+ return C(), k;
158
+ }
159
+ //#endregion
160
+ export { i as createRemoteSource };
161
+
162
+ //# sourceMappingURL=remoteSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remoteSource.js","names":[],"sources":["../src/remoteSource.ts"],"sourcesContent":["import type { RemoteBatchContext, RemoteConfig, RemoteSource, RemoteSourceQuery, SourceMeta } from './types';\n\nimport { decodeRemoteQueryParams } from './codecs';\nimport { createMeta, pageCount } from './pagination';\n\ntype RemoteQuery<TFilter, TSort> = Readonly<{\n filter?: TFilter;\n limit: number;\n page: number;\n search?: string;\n sort?: TSort;\n}>;\n\ntype RemoteResult<T> = Readonly<{ items: readonly T[]; total: number }>;\n\n// Recursively sort object keys for stable, order-independent cache keys.\nconst keyOf = (q: unknown): string =>\n JSON.stringify(q, (_, value: unknown) => {\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n const sorted: Record<string, unknown> = {};\n\n for (const k of Object.keys(value as object).sort()) {\n sorted[k] = (value as Record<string, unknown>)[k];\n }\n\n return sorted;\n }\n\n return value;\n });\n\nexport function createRemoteSource<T, TFilter = unknown, TSort = unknown>(\n cfg: RemoteConfig<T, TFilter, TSort>,\n): RemoteSource<T, TFilter, TSort> {\n const listeners = new Set<() => void>();\n\n const limitDefault = Math.max(1, cfg.limit ?? 10);\n const debounceMs = cfg.debounceMs ?? 300;\n\n let page = 1;\n let limit = limitDefault;\n let search = '';\n let filter = cfg.filter as TFilter | undefined;\n let sort = cfg.sort as TSort | undefined;\n\n let items: readonly T[] = [];\n let total = 0;\n let error: string | null = null;\n let cachedMeta!: SourceMeta;\n\n // pendingCount tracks the number of in-flight requests across all concurrent calls,\n // including joiners that attach to an existing in-flight request for the same key.\n // isLoading is derived from pendingCount > 0 so it stays true until every caller is settled.\n let pendingCount = 0;\n\n // latestKey is the key of the most recently dispatched query.\n // Only the response whose key matches latestKey at the time of resolution is applied to state,\n // preventing stale out-of-order responses from overwriting newer results.\n let latestKey = '';\n\n const inflight = new Map<string, Promise<void>>();\n\n const queryOf = (): RemoteQuery<TFilter, TSort> => ({\n filter,\n limit,\n page,\n search: search || undefined,\n sort,\n });\n\n const toQuery = (): RemoteSourceQuery<TFilter, TSort> => ({\n filter,\n limit,\n page,\n search,\n sort,\n });\n\n const assign = (res: RemoteResult<T>) => {\n items = res.items;\n total = res.total ?? 0;\n\n const pages = pageCount(total, limit);\n\n page = Math.min(Math.max(1, page), pages);\n };\n\n const refreshMeta = () => {\n cachedMeta = createMeta({\n errorMessage: error,\n isLoading: pendingCount > 0,\n isSearchPending: timer !== undefined,\n pageNumber: page,\n pageSize: limit,\n totalItems: total,\n });\n };\n\n const notify = () => {\n refreshMeta();\n\n for (const listener of listeners) {\n listener();\n }\n };\n\n const fetchQuery = async (q: RemoteQuery<TFilter, TSort>): Promise<void> => {\n const key = keyOf(q);\n\n latestKey = key;\n\n // Join an identical in-flight request rather than issuing a duplicate.\n if (inflight.has(key)) {\n pendingCount++;\n notify();\n\n try {\n await inflight.get(key);\n } finally {\n pendingCount--;\n notify();\n }\n\n return;\n }\n\n pendingCount++;\n error = null;\n notify();\n\n const promise = cfg\n .fetch(q)\n .then((result) => {\n // Only apply if this is still the latest query; discard stale responses.\n if (key === latestKey) {\n assign(result);\n error = null;\n }\n })\n .catch((reason) => {\n if (key === latestKey) {\n error = reason?.message ?? 'Request failed';\n items = [];\n total = 0;\n }\n })\n .finally(() => {\n inflight.delete(key);\n pendingCount--;\n notify();\n });\n\n inflight.set(key, promise);\n await promise;\n };\n\n const doUpdate = () => fetchQuery(queryOf());\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const debounced = () => {\n if (timer) clearTimeout(timer);\n\n timer = setTimeout(() => {\n timer = undefined;\n void doUpdate();\n }, debounceMs);\n\n notify();\n };\n\n const searchNow = (q: string): Promise<void> => {\n search = q;\n page = 1;\n\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n\n return doUpdate();\n };\n\n const batch = (mutator: (ctx: RemoteBatchContext<TFilter, TSort>) => void): Promise<void> => {\n let nextPage = page;\n let nextLimit = limit;\n let nextSearch = search;\n let nextFilter = filter as TFilter | undefined;\n let nextSort = sort as TSort | undefined;\n\n mutator({\n goTo: (p) => {\n nextPage = Math.max(1, Math.trunc(p));\n },\n search: (q) => {\n nextSearch = q;\n nextPage = 1;\n },\n setFilter: (f) => {\n nextFilter = f;\n nextPage = 1;\n },\n setLimit: (n) => {\n nextLimit = Math.max(1, Math.trunc(n));\n nextPage = 1;\n },\n setSort: (s) => {\n nextSort = s;\n nextPage = 1;\n },\n });\n\n page = nextPage;\n limit = nextLimit;\n search = nextSearch;\n filter = nextFilter;\n sort = nextSort;\n\n return doUpdate();\n };\n\n const api: RemoteSource<T, TFilter, TSort> = {\n batch,\n commit() {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n\n return doUpdate();\n }\n\n return Promise.resolve();\n },\n get current() {\n return items;\n },\n fromQueryParams(params) {\n return api.restore(decodeRemoteQueryParams(params, limitDefault));\n },\n goTo(p) {\n page = Math.max(1, Math.trunc(p));\n\n return doUpdate();\n },\n goToLast() {\n return api.goTo(pageCount(total, limit));\n },\n get meta() {\n return cachedMeta;\n },\n next() {\n const pages = pageCount(total, limit);\n\n if (page >= pages) return Promise.resolve();\n\n page += 1;\n\n return doUpdate();\n },\n prev() {\n if (page <= 1) return Promise.resolve();\n\n page -= 1;\n\n return doUpdate();\n },\n ready() {\n if (pendingCount === 0 && !timer) {\n return Promise.resolve();\n }\n\n return new Promise<void>((resolve) => {\n const checkReady = () => {\n if (pendingCount === 0 && !timer) {\n listeners.delete(checkReady);\n resolve();\n }\n };\n\n listeners.add(checkReady);\n });\n },\n refresh() {\n return doUpdate();\n },\n reset() {\n page = 1;\n limit = limitDefault;\n search = '';\n filter = cfg.filter as TFilter | undefined;\n sort = cfg.sort as TSort | undefined;\n\n return doUpdate();\n },\n restore(state) {\n let changed = false;\n\n if ('limit' in state && state.limit !== undefined) {\n const nextLimit = Math.max(1, Math.trunc(state.limit));\n\n if (nextLimit !== limit) {\n limit = nextLimit;\n changed = true;\n }\n }\n\n if ('search' in state && state.search !== undefined) {\n if (state.search !== search) {\n search = state.search;\n changed = true;\n }\n }\n\n if ('filter' in state) {\n if (state.filter !== filter) {\n filter = state.filter;\n changed = true;\n }\n }\n\n if ('sort' in state) {\n if (state.sort !== sort) {\n sort = state.sort;\n changed = true;\n }\n }\n\n if ('page' in state && state.page !== undefined) {\n const nextPage = Math.max(1, Math.trunc(state.page));\n\n if (nextPage !== page) {\n page = nextPage;\n changed = true;\n }\n }\n\n if (changed) return doUpdate();\n\n return Promise.resolve();\n },\n search(query) {\n search = query;\n page = 1;\n debounced();\n },\n searchNow,\n setFilter(f) {\n filter = f;\n page = 1;\n\n return doUpdate();\n },\n setLimit(n) {\n limit = Math.max(1, Math.trunc(n));\n page = 1;\n\n return doUpdate();\n },\n setSort(s) {\n sort = s;\n page = 1;\n\n return doUpdate();\n },\n subscribe(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n toQuery() {\n return toQuery();\n },\n };\n\n refreshMeta();\n\n return api;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ function e(e,t){let n=e.toLowerCase(),r=t.toLowerCase();if(n===r)return 1;if(n.length===0)return+(r.length===0);if(r.length===0)return 0;let[i,a]=n.length<r.length?[n,r]:[r,n],o=i.length,s=a.length,c=Array.from({length:o+1},(e,t)=>t),l=Array(o+1);for(let e=1;e<=s;e++){l[0]=e;for(let t=1;t<=o;t++){let n=a[e-1]===i[t-1]?0:1;l[t]=Math.min(l[t-1]+1,c[t]+1,c[t-1]+n)}[c,l]=[l,c]}return 1-c[o]/Math.max(n.length,r.length)}function t(n,r,i,a){return n==null?!1:typeof n==`string`||typeof n==`number`?e(String(n),r)>=i:Array.isArray(n)?n.some(e=>t(e,r,i,a)):typeof n==`object`?a.has(n)?!1:(a.add(n),Object.values(n).some(e=>t(e,r,i,a))):!1}function n(e,t,r){return e==null?!1:typeof e==`string`||typeof e==`number`?String(e).toLowerCase().includes(t):Array.isArray(e)?e.some(e=>n(e,t,r)):typeof e==`object`?r.has(e)?!1:(r.add(e),Object.values(e).some(e=>n(e,t,r))):!1}function r(e,n,r=.25){if(typeof n!=`string`)throw TypeError(`Expected query to be a string`);if(typeof r!=`number`||r<0||r>1)throw TypeError(`Tone must be a number between 0 and 1`);if(!n)return[...e];let i=n.toLowerCase();return e.filter(e=>t(e,i,r,new WeakSet))}function i(e,t){if(typeof t!=`string`)throw TypeError(`Expected query to be a string`);if(!t)return[...e];let r=t.toLowerCase();return e.filter(e=>n(e,r,new WeakSet))}exports.containsSearch=i,exports.fuzzySearch=r;
2
+ //# sourceMappingURL=search.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.cjs","names":[],"sources":["../src/search.ts"],"sourcesContent":["function similarity(left: string, right: string): number {\n const a = left.toLowerCase();\n const b = right.toLowerCase();\n\n if (a === b) return 1;\n\n if (a.length === 0) return b.length === 0 ? 1 : 0;\n\n if (b.length === 0) return 0;\n\n const [shorter, longer] = a.length < b.length ? [a, b] : [b, a];\n const shorterLength = shorter.length;\n const longerLength = longer.length;\n\n let prevRow = Array.from({ length: shorterLength + 1 }, (_, i) => i);\n let currRow = new Array(shorterLength + 1);\n\n for (let i = 1; i <= longerLength; i++) {\n currRow[0] = i;\n\n for (let j = 1; j <= shorterLength; j++) {\n const cost = longer[i - 1] === shorter[j - 1] ? 0 : 1;\n\n currRow[j] = Math.min(currRow[j - 1] + 1, prevRow[j] + 1, prevRow[j - 1] + cost);\n }\n\n [prevRow, currRow] = [currRow, prevRow];\n }\n\n const distance = prevRow[shorterLength];\n\n return 1 - distance / Math.max(a.length, b.length);\n}\n\nfunction seekFuzzy(item: unknown, query: string, tone: number, visited: WeakSet<object>): boolean {\n if (item === null || item === undefined) return false;\n\n if (typeof item === 'string' || typeof item === 'number') {\n return similarity(String(item), query) >= tone;\n }\n\n if (Array.isArray(item)) {\n return item.some((value) => seekFuzzy(value, query, tone, visited));\n }\n\n if (typeof item === 'object') {\n if (visited.has(item)) return false;\n\n visited.add(item);\n\n return Object.values(item).some((value) => seekFuzzy(value, query, tone, visited));\n }\n\n return false;\n}\n\nfunction seekIncludes(item: unknown, query: string, visited: WeakSet<object>): boolean {\n if (item === null || item === undefined) return false;\n\n if (typeof item === 'string' || typeof item === 'number') {\n return String(item).toLowerCase().includes(query);\n }\n\n if (Array.isArray(item)) {\n return item.some((value) => seekIncludes(value, query, visited));\n }\n\n if (typeof item === 'object') {\n if (visited.has(item)) return false;\n\n visited.add(item);\n\n return Object.values(item).some((value) => seekIncludes(value, query, visited));\n }\n\n return false;\n}\n\nexport function fuzzySearch<T>(array: readonly T[], query: string, tone = 0.25): T[] {\n if (typeof query !== 'string') throw new TypeError('Expected query to be a string');\n\n if (typeof tone !== 'number' || tone < 0 || tone > 1) {\n throw new TypeError('Tone must be a number between 0 and 1');\n }\n\n if (!query) return [...array];\n\n const searchTerm = query.toLowerCase();\n\n return array.filter((obj) => seekFuzzy(obj, searchTerm, tone, new WeakSet<object>()));\n}\n\nexport function containsSearch<T>(array: readonly T[], query: string): T[] {\n if (typeof query !== 'string') throw new TypeError('Expected query to be a string');\n\n if (!query) return [...array];\n\n const searchTerm = query.toLowerCase();\n\n return array.filter((obj) => seekIncludes(obj, searchTerm, new WeakSet<object>()));\n}\n"],"mappings":"AAAA,SAAS,EAAW,EAAc,EAAuB,CACvD,IAAM,EAAI,EAAK,YAAY,EACrB,EAAI,EAAM,YAAY,EAE5B,GAAI,IAAM,EAAG,MAAO,GAEpB,GAAI,EAAE,SAAW,EAAG,MAAO,IAAE,SAAW,GAExC,GAAI,EAAE,SAAW,EAAG,MAAO,GAE3B,GAAM,CAAC,EAAS,GAAU,EAAE,OAAS,EAAE,OAAS,CAAC,EAAG,CAAC,EAAI,CAAC,EAAG,CAAC,EACxD,EAAgB,EAAQ,OACxB,EAAe,EAAO,OAExB,EAAU,MAAM,KAAK,CAAE,OAAQ,EAAgB,CAAE,GAAI,EAAG,IAAM,CAAC,EAC/D,EAAc,MAAM,EAAgB,CAAC,EAEzC,IAAK,IAAI,EAAI,EAAG,GAAK,EAAc,IAAK,CACtC,EAAQ,GAAK,EAEb,IAAK,IAAI,EAAI,EAAG,GAAK,EAAe,IAAK,CACvC,IAAM,EAAO,EAAO,EAAI,KAAO,EAAQ,EAAI,GAAK,EAAI,EAEpD,EAAQ,GAAK,KAAK,IAAI,EAAQ,EAAI,GAAK,EAAG,EAAQ,GAAK,EAAG,EAAQ,EAAI,GAAK,CAAI,CACjF,CAEA,CAAC,EAAS,GAAW,CAAC,EAAS,CAAO,CACxC,CAIA,MAAO,GAFU,EAAQ,GAEH,KAAK,IAAI,EAAE,OAAQ,EAAE,MAAM,CACnD,CAEA,SAAS,EAAU,EAAe,EAAe,EAAc,EAAmC,CAmBhG,OAlBI,GAAS,KAAmC,GAE5C,OAAO,GAAS,UAAY,OAAO,GAAS,SACvC,EAAW,OAAO,CAAI,EAAG,CAAK,GAAK,EAGxC,MAAM,QAAQ,CAAI,EACb,EAAK,KAAM,GAAU,EAAU,EAAO,EAAO,EAAM,CAAO,CAAC,EAGhE,OAAO,GAAS,SACd,EAAQ,IAAI,CAAI,EAAU,IAE9B,EAAQ,IAAI,CAAI,EAET,OAAO,OAAO,CAAI,EAAE,KAAM,GAAU,EAAU,EAAO,EAAO,EAAM,CAAO,CAAC,GAG5E,EACT,CAEA,SAAS,EAAa,EAAe,EAAe,EAAmC,CAmBrF,OAlBI,GAAS,KAAmC,GAE5C,OAAO,GAAS,UAAY,OAAO,GAAS,SACvC,OAAO,CAAI,EAAE,YAAY,EAAE,SAAS,CAAK,EAG9C,MAAM,QAAQ,CAAI,EACb,EAAK,KAAM,GAAU,EAAa,EAAO,EAAO,CAAO,CAAC,EAG7D,OAAO,GAAS,SACd,EAAQ,IAAI,CAAI,EAAU,IAE9B,EAAQ,IAAI,CAAI,EAET,OAAO,OAAO,CAAI,EAAE,KAAM,GAAU,EAAa,EAAO,EAAO,CAAO,CAAC,GAGzE,EACT,CAEA,SAAgB,EAAe,EAAqB,EAAe,EAAO,IAAW,CACnF,GAAI,OAAO,GAAU,SAAU,MAAU,UAAU,+BAA+B,EAElF,GAAI,OAAO,GAAS,UAAY,EAAO,GAAK,EAAO,EACjD,MAAU,UAAU,uCAAuC,EAG7D,GAAI,CAAC,EAAO,MAAO,CAAC,GAAG,CAAK,EAE5B,IAAM,EAAa,EAAM,YAAY,EAErC,OAAO,EAAM,OAAQ,GAAQ,EAAU,EAAK,EAAY,EAAM,IAAI,OAAiB,CAAC,CACtF,CAEA,SAAgB,EAAkB,EAAqB,EAAoB,CACzE,GAAI,OAAO,GAAU,SAAU,MAAU,UAAU,+BAA+B,EAElF,GAAI,CAAC,EAAO,MAAO,CAAC,GAAG,CAAK,EAE5B,IAAM,EAAa,EAAM,YAAY,EAErC,OAAO,EAAM,OAAQ,GAAQ,EAAa,EAAK,EAAY,IAAI,OAAiB,CAAC,CACnF"}
@@ -0,0 +1,3 @@
1
+ export declare function fuzzySearch<T>(array: readonly T[], query: string, tone?: number): T[];
2
+ export declare function containsSearch<T>(array: readonly T[], query: string): T[];
3
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AA8EA,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,SAAO,GAAG,CAAC,EAAE,CAYnF;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,CAQzE"}
package/dist/search.js ADDED
@@ -0,0 +1,40 @@
1
+ //#region src/search.ts
2
+ function e(e, t) {
3
+ let n = e.toLowerCase(), r = t.toLowerCase();
4
+ if (n === r) return 1;
5
+ if (n.length === 0) return +(r.length === 0);
6
+ if (r.length === 0) return 0;
7
+ let [i, a] = n.length < r.length ? [n, r] : [r, n], o = i.length, s = a.length, c = Array.from({ length: o + 1 }, (e, t) => t), l = Array(o + 1);
8
+ for (let e = 1; e <= s; e++) {
9
+ l[0] = e;
10
+ for (let t = 1; t <= o; t++) {
11
+ let n = a[e - 1] === i[t - 1] ? 0 : 1;
12
+ l[t] = Math.min(l[t - 1] + 1, c[t] + 1, c[t - 1] + n);
13
+ }
14
+ [c, l] = [l, c];
15
+ }
16
+ return 1 - c[o] / Math.max(n.length, r.length);
17
+ }
18
+ function t(n, r, i, a) {
19
+ return n == null ? !1 : typeof n == "string" || typeof n == "number" ? e(String(n), r) >= i : Array.isArray(n) ? n.some((e) => t(e, r, i, a)) : typeof n == "object" ? a.has(n) ? !1 : (a.add(n), Object.values(n).some((e) => t(e, r, i, a))) : !1;
20
+ }
21
+ function n(e, t, r) {
22
+ return e == null ? !1 : typeof e == "string" || typeof e == "number" ? String(e).toLowerCase().includes(t) : Array.isArray(e) ? e.some((e) => n(e, t, r)) : typeof e == "object" ? r.has(e) ? !1 : (r.add(e), Object.values(e).some((e) => n(e, t, r))) : !1;
23
+ }
24
+ function r(e, n, r = .25) {
25
+ if (typeof n != "string") throw TypeError("Expected query to be a string");
26
+ if (typeof r != "number" || r < 0 || r > 1) throw TypeError("Tone must be a number between 0 and 1");
27
+ if (!n) return [...e];
28
+ let i = n.toLowerCase();
29
+ return e.filter((e) => t(e, i, r, /* @__PURE__ */ new WeakSet()));
30
+ }
31
+ function i(e, t) {
32
+ if (typeof t != "string") throw TypeError("Expected query to be a string");
33
+ if (!t) return [...e];
34
+ let r = t.toLowerCase();
35
+ return e.filter((e) => n(e, r, /* @__PURE__ */ new WeakSet()));
36
+ }
37
+ //#endregion
38
+ export { i as containsSearch, r as fuzzySearch };
39
+
40
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","names":[],"sources":["../src/search.ts"],"sourcesContent":["function similarity(left: string, right: string): number {\n const a = left.toLowerCase();\n const b = right.toLowerCase();\n\n if (a === b) return 1;\n\n if (a.length === 0) return b.length === 0 ? 1 : 0;\n\n if (b.length === 0) return 0;\n\n const [shorter, longer] = a.length < b.length ? [a, b] : [b, a];\n const shorterLength = shorter.length;\n const longerLength = longer.length;\n\n let prevRow = Array.from({ length: shorterLength + 1 }, (_, i) => i);\n let currRow = new Array(shorterLength + 1);\n\n for (let i = 1; i <= longerLength; i++) {\n currRow[0] = i;\n\n for (let j = 1; j <= shorterLength; j++) {\n const cost = longer[i - 1] === shorter[j - 1] ? 0 : 1;\n\n currRow[j] = Math.min(currRow[j - 1] + 1, prevRow[j] + 1, prevRow[j - 1] + cost);\n }\n\n [prevRow, currRow] = [currRow, prevRow];\n }\n\n const distance = prevRow[shorterLength];\n\n return 1 - distance / Math.max(a.length, b.length);\n}\n\nfunction seekFuzzy(item: unknown, query: string, tone: number, visited: WeakSet<object>): boolean {\n if (item === null || item === undefined) return false;\n\n if (typeof item === 'string' || typeof item === 'number') {\n return similarity(String(item), query) >= tone;\n }\n\n if (Array.isArray(item)) {\n return item.some((value) => seekFuzzy(value, query, tone, visited));\n }\n\n if (typeof item === 'object') {\n if (visited.has(item)) return false;\n\n visited.add(item);\n\n return Object.values(item).some((value) => seekFuzzy(value, query, tone, visited));\n }\n\n return false;\n}\n\nfunction seekIncludes(item: unknown, query: string, visited: WeakSet<object>): boolean {\n if (item === null || item === undefined) return false;\n\n if (typeof item === 'string' || typeof item === 'number') {\n return String(item).toLowerCase().includes(query);\n }\n\n if (Array.isArray(item)) {\n return item.some((value) => seekIncludes(value, query, visited));\n }\n\n if (typeof item === 'object') {\n if (visited.has(item)) return false;\n\n visited.add(item);\n\n return Object.values(item).some((value) => seekIncludes(value, query, visited));\n }\n\n return false;\n}\n\nexport function fuzzySearch<T>(array: readonly T[], query: string, tone = 0.25): T[] {\n if (typeof query !== 'string') throw new TypeError('Expected query to be a string');\n\n if (typeof tone !== 'number' || tone < 0 || tone > 1) {\n throw new TypeError('Tone must be a number between 0 and 1');\n }\n\n if (!query) return [...array];\n\n const searchTerm = query.toLowerCase();\n\n return array.filter((obj) => seekFuzzy(obj, searchTerm, tone, new WeakSet<object>()));\n}\n\nexport function containsSearch<T>(array: readonly T[], query: string): T[] {\n if (typeof query !== 'string') throw new TypeError('Expected query to be a string');\n\n if (!query) return [...array];\n\n const searchTerm = query.toLowerCase();\n\n return array.filter((obj) => seekIncludes(obj, searchTerm, new WeakSet<object>()));\n}\n"],"mappings":";AAAA,SAAS,EAAW,GAAc,GAAuB;CACvD,IAAM,IAAI,EAAK,YAAY,GACrB,IAAI,EAAM,YAAY;CAE5B,IAAI,MAAM,GAAG,OAAO;CAEpB,IAAI,EAAE,WAAW,GAAG,OAAO,IAAE,WAAW;CAExC,IAAI,EAAE,WAAW,GAAG,OAAO;CAE3B,IAAM,CAAC,GAAS,KAAU,EAAE,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GACxD,IAAgB,EAAQ,QACxB,IAAe,EAAO,QAExB,IAAU,MAAM,KAAK,EAAE,QAAQ,IAAgB,EAAE,IAAI,GAAG,MAAM,CAAC,GAC/D,IAAc,MAAM,IAAgB,CAAC;CAEzC,KAAK,IAAI,IAAI,GAAG,KAAK,GAAc,KAAK;EACtC,EAAQ,KAAK;EAEb,KAAK,IAAI,IAAI,GAAG,KAAK,GAAe,KAAK;GACvC,IAAM,IAAO,EAAO,IAAI,OAAO,EAAQ,IAAI,KAAK,IAAI;GAEpD,EAAQ,KAAK,KAAK,IAAI,EAAQ,IAAI,KAAK,GAAG,EAAQ,KAAK,GAAG,EAAQ,IAAI,KAAK,CAAI;EACjF;EAEA,CAAC,GAAS,KAAW,CAAC,GAAS,CAAO;CACxC;CAIA,OAAO,IAFU,EAAQ,KAEH,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AACnD;AAEA,SAAS,EAAU,GAAe,GAAe,GAAc,GAAmC;CAmBhG,OAlBI,KAAS,OAAmC,KAE5C,OAAO,KAAS,YAAY,OAAO,KAAS,WACvC,EAAW,OAAO,CAAI,GAAG,CAAK,KAAK,IAGxC,MAAM,QAAQ,CAAI,IACb,EAAK,MAAM,MAAU,EAAU,GAAO,GAAO,GAAM,CAAO,CAAC,IAGhE,OAAO,KAAS,WACd,EAAQ,IAAI,CAAI,IAAU,MAE9B,EAAQ,IAAI,CAAI,GAET,OAAO,OAAO,CAAI,EAAE,MAAM,MAAU,EAAU,GAAO,GAAO,GAAM,CAAO,CAAC,KAG5E;AACT;AAEA,SAAS,EAAa,GAAe,GAAe,GAAmC;CAmBrF,OAlBI,KAAS,OAAmC,KAE5C,OAAO,KAAS,YAAY,OAAO,KAAS,WACvC,OAAO,CAAI,EAAE,YAAY,EAAE,SAAS,CAAK,IAG9C,MAAM,QAAQ,CAAI,IACb,EAAK,MAAM,MAAU,EAAa,GAAO,GAAO,CAAO,CAAC,IAG7D,OAAO,KAAS,WACd,EAAQ,IAAI,CAAI,IAAU,MAE9B,EAAQ,IAAI,CAAI,GAET,OAAO,OAAO,CAAI,EAAE,MAAM,MAAU,EAAa,GAAO,GAAO,CAAO,CAAC,KAGzE;AACT;AAEA,SAAgB,EAAe,GAAqB,GAAe,IAAO,KAAW;CACnF,IAAI,OAAO,KAAU,UAAU,MAAU,UAAU,+BAA+B;CAElF,IAAI,OAAO,KAAS,YAAY,IAAO,KAAK,IAAO,GACjD,MAAU,UAAU,uCAAuC;CAG7D,IAAI,CAAC,GAAO,OAAO,CAAC,GAAG,CAAK;CAE5B,IAAM,IAAa,EAAM,YAAY;CAErC,OAAO,EAAM,QAAQ,MAAQ,EAAU,GAAK,GAAY,mBAAM,IAAI,QAAgB,CAAC,CAAC;AACtF;AAEA,SAAgB,EAAkB,GAAqB,GAAoB;CACzE,IAAI,OAAO,KAAU,UAAU,MAAU,UAAU,+BAA+B;CAElF,IAAI,CAAC,GAAO,OAAO,CAAC,GAAG,CAAK;CAE5B,IAAM,IAAa,EAAM,YAAY;CAErC,OAAO,EAAM,QAAQ,MAAQ,EAAa,GAAK,mBAAY,IAAI,QAAgB,CAAC,CAAC;AACnF"}
@@ -0,0 +1,2 @@
1
+ function e(e,t){if(Object.is(e,t))return!0;if(!e||!t||typeof e!=`object`||typeof t!=`object`)return!1;let n=e,r=t,i=Object.keys(n),a=Object.keys(r);if(i.length!==a.length)return!1;for(let e of i)if(!Object.is(n[e],r[e]))return!1;return!0}var t=(e,t,n,r=Object.is)=>{let i=t(e);return e.subscribe(()=>{let a=t(e);if(r(a,i))return;let o=i;i=a,n(a,o)})};exports.shallowEqual=e,exports.subscribeSelector=t;
2
+ //# sourceMappingURL=selector.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.cjs","names":[],"sources":["../src/selector.ts"],"sourcesContent":["import type { BaseSource } from './types';\n\nexport function shallowEqual<T>(left: T, right: T): boolean {\n if (Object.is(left, right)) {\n return true;\n }\n\n if (!left || !right || typeof left !== 'object' || typeof right !== 'object') {\n return false;\n }\n\n const leftObject = left as Record<string, unknown>;\n const rightObject = right as Record<string, unknown>;\n const leftKeys = Object.keys(leftObject);\n const rightKeys = Object.keys(rightObject);\n\n if (leftKeys.length !== rightKeys.length) {\n return false;\n }\n\n for (const key of leftKeys) {\n if (!Object.is(leftObject[key], rightObject[key])) {\n return false;\n }\n }\n\n return true;\n}\n\nexport const subscribeSelector = <T, U>(\n source: BaseSource<T>,\n selector: (source: BaseSource<T>) => U,\n listener: (next: U, prev: U) => void,\n isEqual: (a: U, b: U) => boolean = Object.is,\n): (() => void) => {\n let previous = selector(source);\n\n return source.subscribe(() => {\n const next = selector(source);\n\n if (isEqual(next, previous)) {\n return;\n }\n\n const prev = previous;\n\n previous = next;\n listener(next, prev);\n });\n};\n"],"mappings":"AAEA,SAAgB,EAAgB,EAAS,EAAmB,CAC1D,GAAI,OAAO,GAAG,EAAM,CAAK,EACvB,MAAO,GAGT,GAAI,CAAC,GAAQ,CAAC,GAAS,OAAO,GAAS,UAAY,OAAO,GAAU,SAClE,MAAO,GAGT,IAAM,EAAa,EACb,EAAc,EACd,EAAW,OAAO,KAAK,CAAU,EACjC,EAAY,OAAO,KAAK,CAAW,EAEzC,GAAI,EAAS,SAAW,EAAU,OAChC,MAAO,GAGT,IAAK,IAAM,KAAO,EAChB,GAAI,CAAC,OAAO,GAAG,EAAW,GAAM,EAAY,EAAI,EAC9C,MAAO,GAIX,MAAO,EACT,CAEA,IAAa,GACX,EACA,EACA,EACA,EAAmC,OAAO,KACzB,CACjB,IAAI,EAAW,EAAS,CAAM,EAE9B,OAAO,EAAO,cAAgB,CAC5B,IAAM,EAAO,EAAS,CAAM,EAE5B,GAAI,EAAQ,EAAM,CAAQ,EACxB,OAGF,IAAM,EAAO,EAEb,EAAW,EACX,EAAS,EAAM,CAAI,CACrB,CAAC,CACH"}
@@ -0,0 +1,4 @@
1
+ import type { BaseSource } from './types';
2
+ export declare function shallowEqual<T>(left: T, right: T): boolean;
3
+ export declare const subscribeSelector: <T, U>(source: BaseSource<T>, selector: (source: BaseSource<T>) => U, listener: (next: U, prev: U) => void, isEqual?: (a: U, b: U) => boolean) => (() => void);
4
+ //# sourceMappingURL=selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../src/selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAyB1D;AAED,eAAO,MAAM,iBAAiB,GAAI,CAAC,EAAE,CAAC,EACpC,QAAQ,UAAU,CAAC,CAAC,CAAC,EACrB,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,EACtC,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,IAAI,EACpC,UAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAmB,KAC3C,CAAC,MAAM,IAAI,CAeb,CAAC"}
@@ -0,0 +1,22 @@
1
+ //#region src/selector.ts
2
+ function e(e, t) {
3
+ if (Object.is(e, t)) return !0;
4
+ if (!e || !t || typeof e != "object" || typeof t != "object") return !1;
5
+ let n = e, r = t, i = Object.keys(n), a = Object.keys(r);
6
+ if (i.length !== a.length) return !1;
7
+ for (let e of i) if (!Object.is(n[e], r[e])) return !1;
8
+ return !0;
9
+ }
10
+ var t = (e, t, n, r = Object.is) => {
11
+ let i = t(e);
12
+ return e.subscribe(() => {
13
+ let a = t(e);
14
+ if (r(a, i)) return;
15
+ let o = i;
16
+ i = a, n(a, o);
17
+ });
18
+ };
19
+ //#endregion
20
+ export { e as shallowEqual, t as subscribeSelector };
21
+
22
+ //# sourceMappingURL=selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.js","names":[],"sources":["../src/selector.ts"],"sourcesContent":["import type { BaseSource } from './types';\n\nexport function shallowEqual<T>(left: T, right: T): boolean {\n if (Object.is(left, right)) {\n return true;\n }\n\n if (!left || !right || typeof left !== 'object' || typeof right !== 'object') {\n return false;\n }\n\n const leftObject = left as Record<string, unknown>;\n const rightObject = right as Record<string, unknown>;\n const leftKeys = Object.keys(leftObject);\n const rightKeys = Object.keys(rightObject);\n\n if (leftKeys.length !== rightKeys.length) {\n return false;\n }\n\n for (const key of leftKeys) {\n if (!Object.is(leftObject[key], rightObject[key])) {\n return false;\n }\n }\n\n return true;\n}\n\nexport const subscribeSelector = <T, U>(\n source: BaseSource<T>,\n selector: (source: BaseSource<T>) => U,\n listener: (next: U, prev: U) => void,\n isEqual: (a: U, b: U) => boolean = Object.is,\n): (() => void) => {\n let previous = selector(source);\n\n return source.subscribe(() => {\n const next = selector(source);\n\n if (isEqual(next, previous)) {\n return;\n }\n\n const prev = previous;\n\n previous = next;\n listener(next, prev);\n });\n};\n"],"mappings":";AAEA,SAAgB,EAAgB,GAAS,GAAmB;CAC1D,IAAI,OAAO,GAAG,GAAM,CAAK,GACvB,OAAO;CAGT,IAAI,CAAC,KAAQ,CAAC,KAAS,OAAO,KAAS,YAAY,OAAO,KAAU,UAClE,OAAO;CAGT,IAAM,IAAa,GACb,IAAc,GACd,IAAW,OAAO,KAAK,CAAU,GACjC,IAAY,OAAO,KAAK,CAAW;CAEzC,IAAI,EAAS,WAAW,EAAU,QAChC,OAAO;CAGT,KAAK,IAAM,KAAO,GAChB,IAAI,CAAC,OAAO,GAAG,EAAW,IAAM,EAAY,EAAI,GAC9C,OAAO;CAIX,OAAO;AACT;AAEA,IAAa,KACX,GACA,GACA,GACA,IAAmC,OAAO,OACzB;CACjB,IAAI,IAAW,EAAS,CAAM;CAE9B,OAAO,EAAO,gBAAgB;EAC5B,IAAM,IAAO,EAAS,CAAM;EAE5B,IAAI,EAAQ,GAAM,CAAQ,GACxB;EAGF,IAAM,IAAO;EAGb,AADA,IAAW,GACX,EAAS,GAAM,CAAI;CACrB,CAAC;AACH"}
@@ -0,0 +1,111 @@
1
+ export type Predicate<T> = (value: T, index: number, array: readonly T[]) => boolean;
2
+ export type Sorter<T> = (a: T, b: T) => number;
3
+ export type QueryParamsInput = Record<string, string | string[] | undefined>;
4
+ export type QueryParams = Record<string, string>;
5
+ export type SourceQuery = Readonly<{
6
+ limit: number;
7
+ page: number;
8
+ search: string;
9
+ }>;
10
+ export type RemoteSourceQuery<TFilter = unknown, TSort = unknown> = SourceQuery & Readonly<{
11
+ filter?: TFilter;
12
+ sort?: TSort;
13
+ }>;
14
+ /**
15
+ * Common metadata shared by both local and remote sources.
16
+ */
17
+ export type SourceMeta = Readonly<{
18
+ errorMessage: string | null;
19
+ hasNoItems: boolean;
20
+ isFirstPage: boolean;
21
+ isLastPage: boolean;
22
+ isLoading: boolean;
23
+ isSearchPending: boolean;
24
+ itemEnd: number;
25
+ itemStart: number;
26
+ pageCount: number;
27
+ pageNumber: number;
28
+ pageSize: number;
29
+ totalItems: number;
30
+ }>;
31
+ export type BaseSource<T> = {
32
+ readonly current: readonly T[];
33
+ readonly meta: SourceMeta;
34
+ subscribe(listener: () => void): () => void;
35
+ };
36
+ export type LocalBatchContext<T> = {
37
+ goTo(page: number): void;
38
+ search(query: string): void;
39
+ setData(data: readonly T[]): void;
40
+ setFilter(filter?: Predicate<T>): void;
41
+ setLimit(limit: number): void;
42
+ setSort(sort?: Sorter<T>): void;
43
+ };
44
+ export type RemoteBatchContext<TFilter = unknown, TSort = unknown> = {
45
+ goTo(page: number): void;
46
+ search(query: string): void;
47
+ setFilter(filter?: TFilter): void;
48
+ setLimit(limit: number): void;
49
+ setSort(sort?: TSort): void;
50
+ };
51
+ export type LocalSource<T> = BaseSource<T> & {
52
+ batch(mutator: (ctx: LocalBatchContext<T>) => void): void;
53
+ commit(): void;
54
+ fromQueryParams(params: QueryParamsInput): void;
55
+ goTo(page: number): void;
56
+ goToLast(): void;
57
+ next(): void;
58
+ prev(): void;
59
+ reset(): void;
60
+ restore(state: Partial<SourceQuery>): void;
61
+ search(query: string): void;
62
+ searchNow(query: string): void;
63
+ setData(data: readonly T[]): void;
64
+ setFilter(filter?: Predicate<T>): void;
65
+ setLimit(limit: number): void;
66
+ setSort(sort?: Sorter<T>): void;
67
+ toQuery(): SourceQuery;
68
+ };
69
+ export type RemoteSource<T, TFilter = unknown, TSort = unknown> = BaseSource<T> & {
70
+ batch(mutator: (ctx: RemoteBatchContext<TFilter, TSort>) => void): Promise<void>;
71
+ commit(): Promise<void>;
72
+ fromQueryParams(params: QueryParamsInput): Promise<void>;
73
+ goTo(page: number): Promise<void>;
74
+ goToLast(): Promise<void>;
75
+ next(): Promise<void>;
76
+ prev(): Promise<void>;
77
+ ready(): Promise<void>;
78
+ refresh(): Promise<void>;
79
+ reset(): Promise<void>;
80
+ restore(state: Partial<RemoteSourceQuery<TFilter, TSort>>): Promise<void>;
81
+ search(query: string): void;
82
+ searchNow(query: string): Promise<void>;
83
+ setFilter(filter?: TFilter): Promise<void>;
84
+ setLimit(limit: number): Promise<void>;
85
+ setSort(sort?: TSort): Promise<void>;
86
+ toQuery(): RemoteSourceQuery<TFilter, TSort>;
87
+ };
88
+ export type LocalConfig<T> = Readonly<{
89
+ debounceMs?: number;
90
+ filter?: Predicate<T>;
91
+ limit?: number;
92
+ searchFn?: (items: readonly T[], query: string) => readonly T[];
93
+ sort?: Sorter<T>;
94
+ }>;
95
+ export type RemoteConfig<T, TFilter, TSort> = Readonly<{
96
+ debounceMs?: number;
97
+ fetch: (q: Readonly<{
98
+ filter?: TFilter;
99
+ limit: number;
100
+ page: number;
101
+ search?: string;
102
+ sort?: TSort;
103
+ }>) => Promise<Readonly<{
104
+ items: readonly T[];
105
+ total: number;
106
+ }>>;
107
+ filter?: TFilter;
108
+ limit?: number;
109
+ sort?: TSort;
110
+ }>;
111
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@vielzeug/sourceit",
3
+ "version": "3.0.1",
4
+ "type": "module",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "source": "./src/index.ts",
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "vite build && pnpm run build:types",
21
+ "build:types": "tsc -p tsconfig.declarations.json",
22
+ "fix": "eslint --fix src",
23
+ "lint": "eslint src",
24
+ "prepublishOnly": "pnpm run build",
25
+ "preview": "vite preview",
26
+ "test": "vitest"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org/"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^25.8.0",
34
+ "typescript": "~6.0.3",
35
+ "vite": "^8.0.13",
36
+ "vitest": "^4.1.6"
37
+ }
38
+ }