@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 CHANGED
@@ -1,4 +1,4 @@
1
- # @reforgium/statum
1
+ # @reforgium/statum
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/%40reforgium%2Fstatum.svg)](https://www.npmjs.com/package/@reforgium/statum)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 with sorting, filtering, and page cache.
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 + sort, go to page 0 |
215
- | updateQuery | Map table events to `page`/`sort` |
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 mergedQuery = { ...(this.opts.presetQueries || {}), ...(args.query || {}) };
1142
- return this.serializer.serialize(mergedQuery);
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/sorting;
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/sort). */
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/sortField/sortOrder`.
1330
+ * Supports `page/first/rows`.
1315
1331
  */
1316
- updateQuery = ({ page: pageNum, first = 0, rows = 0, sortOrder, sortField }) => {
1332
+ updateQuery = ({ page: pageNum, first = 0, rows = 0 }) => {
1317
1333
  const page = (pageNum ?? (first && rows && Math.floor(first / rows))) || 0;
1318
- const sort = sortField ? `${sortField},${sortOrder === 1 ? 'asc' : 'desc'}` : '';
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 and resets sorting) and fetch.
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, sort: undefined });
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, sort = this.sort, filters = this.filters, }) => {
1411
- const query = this.parseQuery({ page, size, sort, filters });
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, sort: this.sort },
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, sort, filters }) {
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, sort }) || 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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.1.2",
2
+ "version": "2.2.1",
3
3
  "name": "@reforgium/statum",
4
4
  "description": "reforgium State modules",
5
5
  "author": "rtommievich",
@@ -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/sort params. `page` is required. */
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/sort` → API-specific keys.
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/sort`. */
614
+ /** Default base `page/pageSize`. */
615
615
  defaultQuery?: PaginatedDataStoreConfig<object>['presetQuery'];
616
- /** Global `parseRequest` — maps pagination/sort into a query object. */
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/sorting;
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/sort). */
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/sortField/sortOrder`.
691
+ * Supports `page/first/rows`.
696
692
  */
697
- updateQuery: ({ page: pageNum, first, rows, sortOrder, sortField }: PaginationType) => Promise<ItemsType[] | undefined>;
693
+ updateQuery: ({ page: pageNum, first, rows }: PaginationType) => Promise<ItemsType[] | undefined>;
698
694
  /**
699
- * Set filters from scratch (goes to the first page and resets sorting) and fetch.
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/sort request into query params.
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;