@reforgium/statum 2.1.2 → 2.2.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.
- package/README.md +15 -16
- package/fesm2022/reforgium-statum.mjs +33 -21
- package/package.json +1 -1
- package/types/reforgium-statum.d.ts +11 -15
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @reforgium/statum
|
|
1
|
+
# @reforgium/statum
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@reforgium/statum)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -181,7 +181,7 @@ entities.removeOne(1);
|
|
|
181
181
|
|
|
182
182
|
## PaginatedDataStore
|
|
183
183
|
|
|
184
|
-
Lightweight store for server-side pagination
|
|
184
|
+
Lightweight store for server-side pagination, filtering, and page cache.
|
|
185
185
|
|
|
186
186
|
### When to use
|
|
187
187
|
|
|
@@ -201,23 +201,22 @@ Lightweight store for server-side pagination with sorting, filtering, and page c
|
|
|
201
201
|
| pageSize | `number` | Page size |
|
|
202
202
|
| totalElements | `number` | Total items on server |
|
|
203
203
|
| filters | `Partial<F>` | Active filters |
|
|
204
|
-
| sort | `string \| undefined` | Sort as `"field,asc"` / `"field,desc"` |
|
|
205
204
|
|
|
206
205
|
### Methods
|
|
207
206
|
|
|
208
|
-
| Method | Description
|
|
209
|
-
|
|
210
|
-
| refresh | Repeat request with current params
|
|
211
|
-
| updatePage | Change page (can use cache)
|
|
212
|
-
| updatePageSize | Change page size (can use cache)
|
|
213
|
-
| updateFilters | Merge filters, reset cache, go to page 0
|
|
214
|
-
| setFilters | Replace filters, reset cache
|
|
215
|
-
| updateQuery | Map table events to `page
|
|
216
|
-
| updateRoute | Change endpoint, reset meta/cache
|
|
217
|
-
| setRouteParams | Fill route url with params
|
|
218
|
-
| updateConfig | Patch config and apply presets
|
|
219
|
-
| copy | Copy config/meta from another store
|
|
220
|
-
| destroy | Manual destroying of caches and abort requests
|
|
207
|
+
| Method | Description |
|
|
208
|
+
|----------------|------------------------------------------------|
|
|
209
|
+
| refresh | Repeat request with current params |
|
|
210
|
+
| updatePage | Change page (can use cache) |
|
|
211
|
+
| updatePageSize | Change page size (can use cache) |
|
|
212
|
+
| updateFilters | Merge filters, reset cache, go to page 0 |
|
|
213
|
+
| setFilters | Replace filters, reset cache, go to page 0 |
|
|
214
|
+
| updateQuery | Map table events to `page` |
|
|
215
|
+
| updateRoute | Change endpoint, reset meta/cache |
|
|
216
|
+
| setRouteParams | Fill route url with params |
|
|
217
|
+
| updateConfig | Patch config and apply presets |
|
|
218
|
+
| copy | Copy config/meta from another store |
|
|
219
|
+
| destroy | Manual destroying of caches and abort requests |
|
|
221
220
|
|
|
222
221
|
Example:
|
|
223
222
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatDate, isNullable, isDatePeriod, parseToDate, makeQuery, isNumber, isObject, parseToDatePeriod, parseQueryArray, fillUrlWithParams, deepEqual, concatArray, debounceSignal } from '@reforgium/internal';
|
|
2
|
-
import { HttpClient } from '@angular/common/http';
|
|
2
|
+
import { HttpClient, HttpParams } from '@angular/common/http';
|
|
3
3
|
import { InjectionToken, inject, signal, computed, DestroyRef, effect, untracked } from '@angular/core';
|
|
4
4
|
import { Subject, filter, timer, merge, map } from 'rxjs';
|
|
5
5
|
import { debounce, tap, throttle, finalize } from 'rxjs/operators';
|
|
@@ -1138,8 +1138,18 @@ class ResourceStore {
|
|
|
1138
1138
|
return joinUrl(this.opts.baseUrl, path);
|
|
1139
1139
|
}
|
|
1140
1140
|
prepareQuery(args) {
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1141
|
+
const presetQuery = this.opts.presetQueries;
|
|
1142
|
+
const requestQuery = args.query;
|
|
1143
|
+
if (!presetQuery && !requestQuery) {
|
|
1144
|
+
return new HttpParams();
|
|
1145
|
+
}
|
|
1146
|
+
const mergedQuery = (!presetQuery ? (requestQuery ?? {}) : !requestQuery ? presetQuery : { ...presetQuery, ...requestQuery });
|
|
1147
|
+
if (this.isEmptyObject(mergedQuery)) {
|
|
1148
|
+
return new HttpParams();
|
|
1149
|
+
}
|
|
1150
|
+
return new HttpParams({
|
|
1151
|
+
fromString: this.serializer.toQuery(mergedQuery),
|
|
1152
|
+
});
|
|
1143
1153
|
}
|
|
1144
1154
|
trace(event) {
|
|
1145
1155
|
try {
|
|
@@ -1217,6 +1227,14 @@ class ResourceStore {
|
|
|
1217
1227
|
this.#status.set(entry.status);
|
|
1218
1228
|
this.#error.set(entry.error);
|
|
1219
1229
|
}
|
|
1230
|
+
isEmptyObject(value) {
|
|
1231
|
+
for (const _key in value) {
|
|
1232
|
+
if (Object.prototype.hasOwnProperty.call(value, _key)) {
|
|
1233
|
+
return false;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return true;
|
|
1237
|
+
}
|
|
1220
1238
|
}
|
|
1221
1239
|
|
|
1222
1240
|
// noinspection ES6PreferShortImport
|
|
@@ -1225,7 +1243,7 @@ class ResourceStore {
|
|
|
1225
1243
|
*
|
|
1226
1244
|
* Provides:
|
|
1227
1245
|
* - reactive signals: `items`, `loading`, `cached`;
|
|
1228
|
-
* - methods to control page/size/filters
|
|
1246
|
+
* - methods to control page/size/filters;
|
|
1229
1247
|
* - optional LRU cache by pages;
|
|
1230
1248
|
* - configurable transport (GET/POST/PATCH/…).
|
|
1231
1249
|
*
|
|
@@ -1250,8 +1268,6 @@ class PaginatedDataStore {
|
|
|
1250
1268
|
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
1251
1269
|
/** Current filters (applied to requests). */
|
|
1252
1270
|
filters = {};
|
|
1253
|
-
/** Current sorting (`field,asc|desc`). */
|
|
1254
|
-
sort;
|
|
1255
1271
|
/** Current page index (0-based). */
|
|
1256
1272
|
page = 0;
|
|
1257
1273
|
/** Default page size. */
|
|
@@ -1272,7 +1288,7 @@ class PaginatedDataStore {
|
|
|
1272
1288
|
this.initTransport();
|
|
1273
1289
|
inject(DestroyRef).onDestroy(() => this.destroy());
|
|
1274
1290
|
}
|
|
1275
|
-
/** Force reload current data (with the same page/filters
|
|
1291
|
+
/** Force reload current data (with the same page/filters). */
|
|
1276
1292
|
refresh() {
|
|
1277
1293
|
return this.#fetchItems({});
|
|
1278
1294
|
}
|
|
@@ -1311,21 +1327,20 @@ class PaginatedDataStore {
|
|
|
1311
1327
|
};
|
|
1312
1328
|
/**
|
|
1313
1329
|
* Update the state from table events (PrimeNG, etc.) and fetch.
|
|
1314
|
-
* Supports `page/first/rows
|
|
1330
|
+
* Supports `page/first/rows`.
|
|
1315
1331
|
*/
|
|
1316
|
-
updateQuery = ({ page: pageNum, first = 0, rows = 0
|
|
1332
|
+
updateQuery = ({ page: pageNum, first = 0, rows = 0 }) => {
|
|
1317
1333
|
const page = (pageNum ?? (first && rows && Math.floor(first / rows))) || 0;
|
|
1318
|
-
|
|
1319
|
-
return this.#fetchItems({ page, sort });
|
|
1334
|
+
return this.#fetchItems({ page });
|
|
1320
1335
|
};
|
|
1321
1336
|
/**
|
|
1322
|
-
* Set filters from scratch (goes to the first page
|
|
1337
|
+
* Set filters from scratch (goes to the first page) and fetch.
|
|
1323
1338
|
* Useful for quick presets.
|
|
1324
1339
|
*/
|
|
1325
1340
|
setFilters = (filters = {}) => {
|
|
1326
1341
|
this.#cache.clear();
|
|
1327
1342
|
this.cached.set([]);
|
|
1328
|
-
return this.#fetchItems({ page: 0, filters
|
|
1343
|
+
return this.#fetchItems({ page: 0, filters });
|
|
1329
1344
|
};
|
|
1330
1345
|
/**
|
|
1331
1346
|
* Change the resource route (resets page, cache, and presets) without fetching.
|
|
@@ -1407,14 +1422,13 @@ class PaginatedDataStore {
|
|
|
1407
1422
|
}
|
|
1408
1423
|
}
|
|
1409
1424
|
}
|
|
1410
|
-
#fetchItems = async ({ page = this.page, size = this.pageSize,
|
|
1411
|
-
const query = this.parseQuery({ page, size,
|
|
1425
|
+
#fetchItems = async ({ page = this.page, size = this.pageSize, filters = this.filters }) => {
|
|
1426
|
+
const query = this.parseQuery({ page, size, filters });
|
|
1412
1427
|
this.loading.set(true);
|
|
1413
1428
|
this.#transport.abortAll();
|
|
1414
1429
|
try {
|
|
1415
1430
|
const response = await this.runTransport(filters, query);
|
|
1416
1431
|
this.page = page;
|
|
1417
|
-
this.sort = sort;
|
|
1418
1432
|
this.filters = filters;
|
|
1419
1433
|
const parsed = await this.parseResponseData(response);
|
|
1420
1434
|
this.pageSize = parsed.pageable?.pageSize || size;
|
|
@@ -1437,7 +1451,7 @@ class PaginatedDataStore {
|
|
|
1437
1451
|
this.#transport = new ResourceStore({ [this.config.method || 'GET']: this.route }, {
|
|
1438
1452
|
delay: this.config.debounceTime,
|
|
1439
1453
|
delayMode: 'debounce',
|
|
1440
|
-
presetQueries: { page: this.page, size: this.pageSize
|
|
1454
|
+
presetQueries: { page: this.page, size: this.pageSize },
|
|
1441
1455
|
presetPayload: this.filters,
|
|
1442
1456
|
});
|
|
1443
1457
|
}
|
|
@@ -1450,10 +1464,10 @@ class PaginatedDataStore {
|
|
|
1450
1464
|
// @ts-ignore
|
|
1451
1465
|
return this.#transport[method]({ query, payload, params }, { promote: false, dedupe: true });
|
|
1452
1466
|
}
|
|
1453
|
-
parseQuery({ page = 0, size,
|
|
1467
|
+
parseQuery({ page = 0, size, filters }) {
|
|
1454
1468
|
const method = this.config.method || 'GET';
|
|
1455
1469
|
const requestPayload = { page, size, ...(method === 'GET' ? filters : {}) };
|
|
1456
|
-
const rawQueries = this.config.parseRequest?.({ ...requestPayload
|
|
1470
|
+
const rawQueries = this.config.parseRequest?.({ ...requestPayload }) || requestPayload;
|
|
1457
1471
|
const queries = {};
|
|
1458
1472
|
Object.entries(rawQueries).forEach(([key, value]) => {
|
|
1459
1473
|
if (Array.isArray(value)) {
|
|
@@ -1463,7 +1477,6 @@ class PaginatedDataStore {
|
|
|
1463
1477
|
queries[key] = value;
|
|
1464
1478
|
}
|
|
1465
1479
|
});
|
|
1466
|
-
sort && (queries['sort'] = sort);
|
|
1467
1480
|
return queries;
|
|
1468
1481
|
}
|
|
1469
1482
|
parseResponseData = async (data) => {
|
|
@@ -1509,7 +1522,6 @@ class PaginatedDataStore {
|
|
|
1509
1522
|
this.filters = this.config.presetFilters || {};
|
|
1510
1523
|
this.pageSize = this.config.presetQuery?.pageSize || 20;
|
|
1511
1524
|
this.page = this.config.presetQuery?.page || 0;
|
|
1512
|
-
this.sort = this.config.presetQuery?.sort;
|
|
1513
1525
|
}
|
|
1514
1526
|
}
|
|
1515
1527
|
|
package/package.json
CHANGED
|
@@ -532,6 +532,7 @@ declare class ResourceStore<Data> {
|
|
|
532
532
|
private runWithRetry;
|
|
533
533
|
private exec$;
|
|
534
534
|
private promoteCurrent;
|
|
535
|
+
private isEmptyObject;
|
|
535
536
|
}
|
|
536
537
|
|
|
537
538
|
/**
|
|
@@ -585,17 +586,16 @@ type PaginatedDataStoreConfig<ItemsType extends object, FilterType = unknown> =
|
|
|
585
586
|
cacheSize?: number;
|
|
586
587
|
/** Debounce delay before request (ms). Useful for frequent filter changes. */
|
|
587
588
|
debounceTime?: number;
|
|
588
|
-
/** Initial pagination
|
|
589
|
+
/** Initial pagination params. `page` is required. */
|
|
589
590
|
presetQuery?: {
|
|
590
591
|
page: number;
|
|
591
592
|
pageSize?: number;
|
|
592
|
-
sort?: string;
|
|
593
593
|
};
|
|
594
594
|
/** Initial filters. Will be sent with the first request. */
|
|
595
595
|
presetFilters?: Partial<FilterType>;
|
|
596
596
|
/**
|
|
597
597
|
* Custom transformation of request data into a query object.
|
|
598
|
-
* Useful for mapping `page/pageSize
|
|
598
|
+
* Useful for mapping `page/pageSize` → API-specific keys.
|
|
599
599
|
*/
|
|
600
600
|
parseRequest?: (data: PageableRequest) => Query;
|
|
601
601
|
/**
|
|
@@ -611,9 +611,9 @@ type PaginatedDataStoreConfig<ItemsType extends object, FilterType = unknown> =
|
|
|
611
611
|
type PaginatedDataStoreProviderConfig = {
|
|
612
612
|
/** Default method for all stores (if not overridden locally). */
|
|
613
613
|
defaultMethod?: RestMethods;
|
|
614
|
-
/** Default base `page/pageSize
|
|
614
|
+
/** Default base `page/pageSize`. */
|
|
615
615
|
defaultQuery?: PaginatedDataStoreConfig<object>['presetQuery'];
|
|
616
|
-
/** Global `parseRequest` — maps pagination
|
|
616
|
+
/** Global `parseRequest` — maps pagination into a query object. */
|
|
617
617
|
defaultParseRequest?: PaginatedDataStoreConfig<object>['parseRequest'];
|
|
618
618
|
/** Whether to enable page cache by default. */
|
|
619
619
|
defaultHasCache?: boolean;
|
|
@@ -625,15 +625,13 @@ type PaginationType = {
|
|
|
625
625
|
page?: number;
|
|
626
626
|
first?: number | null;
|
|
627
627
|
rows?: number | null;
|
|
628
|
-
sortField?: string | string[] | null;
|
|
629
|
-
sortOrder?: number | null;
|
|
630
628
|
};
|
|
631
629
|
/**
|
|
632
630
|
* Store for paginated data (tables/lists) with per-page cache and unified requests.
|
|
633
631
|
*
|
|
634
632
|
* Provides:
|
|
635
633
|
* - reactive signals: `items`, `loading`, `cached`;
|
|
636
|
-
* - methods to control page/size/filters
|
|
634
|
+
* - methods to control page/size/filters;
|
|
637
635
|
* - optional LRU cache by pages;
|
|
638
636
|
* - configurable transport (GET/POST/PATCH/…).
|
|
639
637
|
*
|
|
@@ -657,8 +655,6 @@ declare class PaginatedDataStore<ItemsType extends object, FilterType = unknown>
|
|
|
657
655
|
loading: WritableSignal<boolean>;
|
|
658
656
|
/** Current filters (applied to requests). */
|
|
659
657
|
filters: Partial<FilterType>;
|
|
660
|
-
/** Current sorting (`field,asc|desc`). */
|
|
661
|
-
sort?: string | ReadonlyArray<string>;
|
|
662
658
|
/** Current page index (0-based). */
|
|
663
659
|
page: number;
|
|
664
660
|
/** Default page size. */
|
|
@@ -671,7 +667,7 @@ declare class PaginatedDataStore<ItemsType extends object, FilterType = unknown>
|
|
|
671
667
|
* @param config Store behavior: method, cache, request/response parsers, debounce, presets, etc.
|
|
672
668
|
*/
|
|
673
669
|
constructor(route: string, config?: PaginatedDataStoreConfig<ItemsType, FilterType>);
|
|
674
|
-
/** Force reload current data (with the same page/filters
|
|
670
|
+
/** Force reload current data (with the same page/filters). */
|
|
675
671
|
refresh(): Promise<ItemsType[] | undefined>;
|
|
676
672
|
/**
|
|
677
673
|
* Switch page with a request.
|
|
@@ -692,11 +688,11 @@ declare class PaginatedDataStore<ItemsType extends object, FilterType = unknown>
|
|
|
692
688
|
updateFilters: (filters: Partial<FilterType>) => Promise<ItemsType[] | undefined>;
|
|
693
689
|
/**
|
|
694
690
|
* Update the state from table events (PrimeNG, etc.) and fetch.
|
|
695
|
-
* Supports `page/first/rows
|
|
691
|
+
* Supports `page/first/rows`.
|
|
696
692
|
*/
|
|
697
|
-
updateQuery: ({ page: pageNum, first, rows
|
|
693
|
+
updateQuery: ({ page: pageNum, first, rows }: PaginationType) => Promise<ItemsType[] | undefined>;
|
|
698
694
|
/**
|
|
699
|
-
* Set filters from scratch (goes to the first page
|
|
695
|
+
* Set filters from scratch (goes to the first page) and fetch.
|
|
700
696
|
* Useful for quick presets.
|
|
701
697
|
*/
|
|
702
698
|
setFilters: (filters?: Partial<FilterType>) => Promise<ItemsType[] | undefined>;
|
|
@@ -779,7 +775,7 @@ type DictStoreConfig<ItemsType extends object> = {
|
|
|
779
775
|
/** Autoload data on initialization (`true` by default). */
|
|
780
776
|
autoLoad?: boolean | 'whenEmpty';
|
|
781
777
|
/**
|
|
782
|
-
* Custom mapper of pagination
|
|
778
|
+
* Custom mapper of pagination request into query params.
|
|
783
779
|
* Useful if the API expects non-standard field names.
|
|
784
780
|
*/
|
|
785
781
|
parseRequest?: (data: PageableRequest) => AnyDict;
|