@reforgium/statum 3.1.4 → 3.1.6

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/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## [3.1.6]: 2026-04-17
2
+
3
+ ### Fix:
4
+ - `PagedQueryStore`: plain array responses (`T[]`) no longer synthesize `totalElements = data.length`; `parseFlatArray()` now keeps `totalElements` as `undefined` unless the backend or a custom `parseResponse(...)` provides an exact total explicitly
5
+
6
+ ### Test:
7
+ - updated flat-array regression coverage to assert unknown-total semantics instead of the old inferred-length behavior
8
+
9
+ ### Docs:
10
+ - `README`: clarified that bare array responses are treated as unknown-total datasets and that exact totals must come from the backend or a custom `parseResponse(...)`
11
+
12
+ ---
13
+
14
+ ## [3.1.5]: 2026-04-14
15
+
16
+ ### Fix:
17
+ - `PagedQueryStore`: page cache is now invalidated when `routeParams` change even if `reset: false` is used, so long-lived singleton-like stores do not replay cached pages from the previous logical dataset/session after a screen remount or route-context change
18
+ - `PagedQueryStore.updateConfig(...)` / `copy(...)`: cached page window is now cleared before transport reconfiguration so reused source instances do not leak stale buffered pages into `@reforgium/data-grid` source mode
19
+
20
+ ### Test:
21
+ - added regression coverage for cache invalidation on route-param session changes without store destruction
22
+
23
+ ---
24
+
1
25
  ## [3.1.4]: 2026-04-10
2
26
 
3
27
  ### Fix:
package/README.md CHANGED
@@ -336,19 +336,19 @@ Lightweight store for server-side pagination with filtering, dynamic query param
336
336
 
337
337
  ### State
338
338
 
339
- | Field / Signal | Type | Description |
340
- |----------------|---------------------------|----------------------------------------|
341
- | items | `WritableSignal<T[]>` | Current page items |
342
- | cached | `WritableSignal<T[]>` | Flattened cache of cached pages |
343
- | loading | `WritableSignal<boolean>` | Loading indicator |
344
- | error | `WritableSignal<unknown \| null>` | Last request error |
345
- | version | `WritableSignal<number>` | Increments when the dataset is reset |
346
- | page | `number` | Current page (0-based) |
347
- | pageSize | `number` | Page size |
348
- | totalElements | `number \| undefined` | Total items on server, if known |
349
- | filters | `Partial<F>` | Active filters |
350
- | query | `Record<string, unknown>` | Active query params |
351
- | sort | `ReadonlyArray<{ sort: string; order: 'asc' \| 'desc' }>` | Active sort state |
339
+ | Field / Signal | Type | Description |
340
+ |----------------|-----------------------------------------------------------|---------------------------------------|
341
+ | items | `WritableSignal<T[]>` | Current page items |
342
+ | cached | `WritableSignal<T[]>` | Flattened cache of cached pages |
343
+ | loading | `WritableSignal<boolean>` | Loading indicator |
344
+ | error | `WritableSignal<unknown \| null>` | Last request error |
345
+ | version | `WritableSignal<number>` | Increments when the dataset is reset |
346
+ | page | `number` | Current page (0-based) |
347
+ | pageSize | `number` | Page size |
348
+ | totalElements | `number \| undefined` | Exact total items on server, if known |
349
+ | filters | `Partial<F>` | Active filters |
350
+ | query | `Record<string, unknown>` | Active query params |
351
+ | sort | `ReadonlyArray<{ sort: string; order: 'asc' \| 'desc' }>` | Active sort state |
352
352
 
353
353
  Reactive metadata signals are also available:
354
354
 
@@ -368,9 +368,9 @@ Reactive metadata signals are also available:
368
368
  | refetchWith | Repeat request, optional merge overrides: `refetchWith({ filters, query })` |
369
369
  | updatePage | Change page: `updatePage(page, { ignoreCache })` or `updatePage({ page, ignoreCache })` |
370
370
  | updatePageSize | Change page size and reset cache: `updatePageSize(size)` |
371
- | setSort | Update sort state without triggering a request |
372
- | updateSort | Apply single-sort state and load from the first page |
373
- | updateSorts | Apply multi-sort state and load from the first page |
371
+ | setSort | Update sort state without triggering a request |
372
+ | updateSort | Apply single-sort state and load from the first page |
373
+ | updateSorts | Apply multi-sort state and load from the first page |
374
374
  | updateByOffset | Table-event mapper: `updateByOffset({ page/first/rows }, { query })` |
375
375
  | setRouteParams | Update route params: `setRouteParams(params, { reset, abort })` |
376
376
  | updateConfig | Patch config: `updateConfig(config)` |
@@ -393,13 +393,13 @@ Concurrency can be configured per store:
393
393
 
394
394
  ### Cache behavior
395
395
 
396
- | Method | Cache read | Cache reset | Notes |
397
- |----------------|------------|-------------|--------------------------------------------------|
398
- | fetch | no | yes | Always starts clean from page `0` |
399
- | refetchWith | no | conditional | Reloads current state; when filters/query/sort actually change, resets to page `0` and replaces the dataset |
400
- | updatePage | yes | no | Can bypass with `ignoreCache: true` |
401
- | updatePageSize | no | yes | Prevents mixed caches for different page sizes |
402
- | updateByOffset | yes | no | Internally maps to `page + size` |
396
+ | Method | Cache read | Cache reset | Notes |
397
+ |----------------|------------|-------------|-------------------------------------------------------------------------------------------------------------|
398
+ | fetch | no | yes | Always starts clean from page `0` |
399
+ | refetchWith | no | conditional | Reloads current state; when filters/query/sort actually change, resets to page `0` and replaces the dataset |
400
+ | updatePage | yes | no | Can bypass with `ignoreCache: true` |
401
+ | updatePageSize | no | yes | Prevents mixed caches for different page sizes |
402
+ | updateByOffset | yes | no | Internally maps to `page + size` |
403
403
 
404
404
  Example:
405
405
 
@@ -422,7 +422,9 @@ store.updateSort({ sort: 'name', order: 'asc' });
422
422
 
423
423
  `cached()` remains a bounded hot-cache view. It is useful for cache-aware revisit/export/search helpers, but it is not the right datasource for infinity scrolling once cache eviction matters. For `data-grid` infinity mode, prefer passing the whole store as a `GridPagedDataSource` (`[source]="store"`) and let the grid keep its own page buffer.
424
424
 
425
- `sort` and `routeParams` should be changed only through `setSort(...)` and `setRouteParams(...)`. Direct state mutation setters for `page`, `pageSize`, `filters`, `query`, and `totalElements` are available for low-level integration scenarios (such as external data-grid source contracts) but prefer the explicit store methods for typical use. `totalElements` may be `undefined` when the backend does not report a total.
425
+ `sort` and `routeParams` should be changed only through `setSort(...)` and `setRouteParams(...)`. Direct state mutation setters for `page`, `pageSize`, `filters`, `query`, and `totalElements` are available for low-level integration scenarios (such as external data-grid source contracts) but prefer the explicit store methods for typical use. `totalElements` may be `undefined` when the backend does not report a total.
426
+
427
+ If the transport returns a bare array (`T[]`) instead of a pageable object, `PagedQueryStore` treats it as an unknown-total dataset and keeps `totalElements === undefined`. A plain array does not prove the final dataset size. If your backend knows the exact total, return it explicitly or map the response with `parseResponse(...)`.
426
428
 
427
429
  ### PagedQueryStore + DataGrid source mode
428
430
 
@@ -449,6 +451,8 @@ This works because the store already exposes the expected source contract:
449
451
  - `updatePage(...)`
450
452
  - `updatePageSize(...)`
451
453
  - `updateSort(...)`
454
+
455
+ When `PagedQueryStore` is fed by a flat-array response without an exact backend total, `totalElements` stays `undefined`. This is intentional: consumers such as `@reforgium/data-grid` can then stay in open-ended paging mode instead of trusting a fake total derived from `data.length`.
452
456
  - `updateSorts(...)`
453
457
 
454
458
  Keep `data-grid` source prefetch in `sequential` mode when the store uses `latest-wins`. Switch to `parallel` only if the store is configured with `concurrency: 'parallel'` and the backend flow supports overlapping page requests.
@@ -1007,6 +1007,7 @@ class PagedQueryStore {
1007
1007
  if (!isChanged) {
1008
1008
  return;
1009
1009
  }
1010
+ this.invalidatePageCache();
1010
1011
  this.#routeParams.set(params);
1011
1012
  if (opts.reset) {
1012
1013
  this.resetDataset();
@@ -1028,6 +1029,7 @@ class PagedQueryStore {
1028
1029
  updateConfig = (config) => {
1029
1030
  this.config = { ...this.config, ...config };
1030
1031
  this.#cache.limit = this.resolveCacheLimit();
1032
+ this.invalidatePageCache();
1031
1033
  this.applyPresetMeta();
1032
1034
  this.reinitTransport();
1033
1035
  };
@@ -1036,6 +1038,7 @@ class PagedQueryStore {
1036
1038
  */
1037
1039
  copy(store) {
1038
1040
  this.applyConfig(store.config);
1041
+ this.invalidatePageCache();
1039
1042
  this.applyPresetMeta();
1040
1043
  this.reinitTransport();
1041
1044
  }
@@ -1169,8 +1172,12 @@ class PagedQueryStore {
1169
1172
  }
1170
1173
  this.cached.set(flat);
1171
1174
  };
1175
+ invalidatePageCache() {
1176
+ this.#cache.clear();
1177
+ this.cached.set([]);
1178
+ }
1172
1179
  parseFlatArray(data) {
1173
- return { content: data, totalElements: data.length };
1180
+ return { content: data };
1174
1181
  }
1175
1182
  resetDataset() {
1176
1183
  this.page = 0;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.1.4",
2
+ "version": "3.1.6",
3
3
  "name": "@reforgium/statum",
4
4
  "description": "Signals-first API state and query stores for Angular",
5
5
  "author": "rtommievich",
@@ -600,6 +600,7 @@ declare class PagedQueryStore<ItemsType extends AnyDict, FilterType extends AnyD
600
600
  private parseQuery;
601
601
  private parseResponseData;
602
602
  private updateCache;
603
+ private invalidatePageCache;
603
604
  private parseFlatArray;
604
605
  private resetDataset;
605
606
  private bumpVersion;