@reforgium/statum 3.1.1 → 3.1.2

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,10 @@
1
+ ## [3.1.2]: 2026-04-06
2
+
3
+ ### Fix:
4
+ - `DictStore`: `presetFilters` were passed to the internal `PagedQueryStore` as its initial filter state, which was immediately overwritten on the first `fetch()` call; `presetFilters` are now stored directly on `DictStore` and explicitly merged into every `fetch` call as `{ name, ...presetFilters, ...runtimeFilters }`, so direction/status constraints are always included regardless of runtime filter state
5
+
6
+ ---
7
+
1
8
  ## [3.1.1]: 2026-04-04
2
9
 
3
10
  ### Refactor:
@@ -277,10 +277,10 @@ class KeyedScheduler {
277
277
  class ResourceStore {
278
278
  http = inject(HttpClient);
279
279
  serializer;
280
- #value = signal(null, ...(ngDevMode ? [{ debugName: "#value" }] : /* istanbul ignore next */ []));
281
- #status = signal('idle', ...(ngDevMode ? [{ debugName: "#status" }] : /* istanbul ignore next */ []));
282
- #error = signal(null, ...(ngDevMode ? [{ debugName: "#error" }] : /* istanbul ignore next */ []));
283
- #activeRequests = signal(0, ...(ngDevMode ? [{ debugName: "#activeRequests" }] : /* istanbul ignore next */ []));
280
+ #value = signal(null, ...(ngDevMode ? [{ debugName: "#value" }] : []));
281
+ #status = signal('idle', ...(ngDevMode ? [{ debugName: "#status" }] : []));
282
+ #error = signal(null, ...(ngDevMode ? [{ debugName: "#error" }] : []));
283
+ #activeRequests = signal(0, ...(ngDevMode ? [{ debugName: "#activeRequests" }] : []));
284
284
  /**
285
285
  * Current resource value.
286
286
  * Returns `null` if no data yet or the request failed.
@@ -298,7 +298,7 @@ class ResourceStore {
298
298
  * Convenience loading flag: `true` when `loading` or `stale`.
299
299
  * Useful for spinners and disabling buttons.
300
300
  */
301
- loading = computed(() => this.#activeRequests() > 0 || this.#status() === 'stale', ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
301
+ loading = computed(() => this.#activeRequests() > 0 || this.#status() === 'stale', ...(ngDevMode ? [{ debugName: "loading" }] : []));
302
302
  routes;
303
303
  opts;
304
304
  maxEntries;
@@ -819,7 +819,7 @@ const createResourceProfile = (profile, overrides = {}) => ({
819
819
  * - reactive signals: `items`, `loading`, `cached`;
820
820
  * - methods to control page/size/query;
821
821
  * - optional LRU cache by pages;
822
- * - configurable transport (GET/POST/PATCH/…).
822
+ * - configurable transport (GET/POST/PATCH/…).
823
823
  *
824
824
  * Example:
825
825
  * ```ts
@@ -839,22 +839,22 @@ class PagedQueryStore {
839
839
  #transport;
840
840
  #cache;
841
841
  /** Current page data (reactive). */
842
- items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
842
+ items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : []));
843
843
  /** Merged cache of pages (flat list) handy for search/export. */
844
- cached = signal([], ...(ngDevMode ? [{ debugName: "cached" }] : /* istanbul ignore next */ []));
844
+ cached = signal([], ...(ngDevMode ? [{ debugName: "cached" }] : []));
845
845
  /** Loading flag of the current operation. */
846
- loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
846
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
847
847
  /** Last request error (if any). */
848
- error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
848
+ error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
849
849
  /** Increments when the current dataset is reset/replaced. Useful for external consumers with local buffers. */
850
- version = signal(0, ...(ngDevMode ? [{ debugName: "version" }] : /* istanbul ignore next */ []));
851
- #page = signal(0, ...(ngDevMode ? [{ debugName: "#page" }] : /* istanbul ignore next */ []));
852
- #pageSize = signal(20, ...(ngDevMode ? [{ debugName: "#pageSize" }] : /* istanbul ignore next */ []));
853
- #totalElements = signal(0, ...(ngDevMode ? [{ debugName: "#totalElements" }] : /* istanbul ignore next */ []));
854
- #filters = signal({}, ...(ngDevMode ? [{ debugName: "#filters" }] : /* istanbul ignore next */ []));
855
- #query = signal({}, ...(ngDevMode ? [{ debugName: "#query" }] : /* istanbul ignore next */ []));
856
- #sort = signal([], ...(ngDevMode ? [{ debugName: "#sort" }] : /* istanbul ignore next */ []));
857
- #routeParams = signal({}, ...(ngDevMode ? [{ debugName: "#routeParams" }] : /* istanbul ignore next */ []));
850
+ version = signal(0, ...(ngDevMode ? [{ debugName: "version" }] : []));
851
+ #page = signal(0, ...(ngDevMode ? [{ debugName: "#page" }] : []));
852
+ #pageSize = signal(20, ...(ngDevMode ? [{ debugName: "#pageSize" }] : []));
853
+ #totalElements = signal(0, ...(ngDevMode ? [{ debugName: "#totalElements" }] : []));
854
+ #filters = signal({}, ...(ngDevMode ? [{ debugName: "#filters" }] : []));
855
+ #query = signal({}, ...(ngDevMode ? [{ debugName: "#query" }] : []));
856
+ #sort = signal([], ...(ngDevMode ? [{ debugName: "#sort" }] : []));
857
+ #routeParams = signal({}, ...(ngDevMode ? [{ debugName: "#routeParams" }] : []));
858
858
  pageState = this.#page.asReadonly();
859
859
  pageSizeState = this.#pageSize.asReadonly();
860
860
  totalElementsState = this.#totalElements.asReadonly();
@@ -1285,21 +1285,22 @@ class DictStore {
1285
1285
  metaStorage;
1286
1286
  ttlMs;
1287
1287
  revalidate;
1288
- cacheUpdatedAt = signal(null, ...(ngDevMode ? [{ debugName: "cacheUpdatedAt" }] : /* istanbul ignore next */ []));
1288
+ cacheUpdatedAt = signal(null, ...(ngDevMode ? [{ debugName: "cacheUpdatedAt" }] : []));
1289
+ presetFilters = {};
1289
1290
  /**
1290
1291
  * Search text.
1291
1292
  * With `fixed: true` filters the local cache; with `fixed: false` triggers server search.
1292
1293
  */
1293
- searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
1294
+ searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
1294
1295
  debouncedSearchText;
1295
1296
  /**
1296
1297
  * Additional filters for server request (or presets).
1297
1298
  */
1298
- filters = signal({}, ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
1299
- cachedItems = signal([], ...(ngDevMode ? [{ debugName: "cachedItems" }] : /* istanbul ignore next */ []));
1299
+ filters = signal({}, ...(ngDevMode ? [{ debugName: "filters" }] : []));
1300
+ cachedItems = signal([], ...(ngDevMode ? [{ debugName: "cachedItems" }] : []));
1300
1301
  /**
1301
1302
  * Current list of dictionary items.
1302
- * Source — local cache (fixed=true) or data from `PagedQueryStore`.
1303
+ * Source local cache (fixed=true) or data from `PagedQueryStore`.
1303
1304
  */
1304
1305
  items = computed(() => {
1305
1306
  const cached = this.cachedItems();
@@ -1307,7 +1308,7 @@ class DictStore {
1307
1308
  return this.debouncedSearchText() || !cached.length ? this.#helper.items() : cached;
1308
1309
  }
1309
1310
  return cached.length ? this.filterLocal() : this.#helper.items();
1310
- }, ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
1311
+ }, ...(ngDevMode ? [{ debugName: "items" }] : []));
1311
1312
  /**
1312
1313
  * Ready-to-use dropdown options: `{ label, value }`.
1313
1314
  * Respects `maxOptionsSize` for truncating the list.
@@ -1315,9 +1316,9 @@ class DictStore {
1315
1316
  options = computed(() => {
1316
1317
  const options = this.items().map((it) => ({ label: String(it[this.labelKey] ?? ''), value: it[this.valueKey] }));
1317
1318
  return this.maxOptionsSize ? options.slice(0, this.maxOptionsSize) : options;
1318
- }, ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
1319
+ }, ...(ngDevMode ? [{ debugName: "options" }] : []));
1319
1320
  _lastPromise = null;
1320
- _armed = signal(false, ...(ngDevMode ? [{ debugName: "_armed" }] : /* istanbul ignore next */ []));
1321
+ _armed = signal(false, ...(ngDevMode ? [{ debugName: "_armed" }] : []));
1321
1322
  // todo add i18n support
1322
1323
  /**
1323
1324
  * @param apiUrl dictionary endpoint (e.g., `'/api/dicts/countries'`)
@@ -1329,10 +1330,10 @@ class DictStore {
1329
1330
  this.storageKey = storageKey;
1330
1331
  const searchDebounce = debounceTime ?? 300;
1331
1332
  this.debouncedSearchText = debounceSignal(this.searchText, searchDebounce);
1333
+ this.presetFilters = presetFilters ?? {};
1332
1334
  this.#helper = new PagedQueryStore(this.apiUrl, {
1333
1335
  method: method,
1334
1336
  hasCache: false,
1335
- presetFilters: { name: '', ...presetFilters },
1336
1337
  parseResponse: parseResponse,
1337
1338
  parseRequest: parseRequest,
1338
1339
  debounceTime: debounceTime,
@@ -1366,12 +1367,14 @@ class DictStore {
1366
1367
  if (!this.fixed) {
1367
1368
  const query = this.debouncedSearchText().trim();
1368
1369
  untracked(() => {
1369
- this._lastPromise = this.#helper.fetch({ filters: { name: query, ...rest } });
1370
+ this._lastPromise = this.#helper.fetch({ filters: { name: query, ...this.presetFilters, ...rest } });
1370
1371
  });
1371
1372
  }
1372
1373
  else if (this.shouldFetchFixedCache()) {
1373
1374
  untracked(() => {
1374
- this._lastPromise = this.#helper.fetch({ filters: { name: '', ...rest } }).then((items) => {
1375
+ this._lastPromise = this.#helper
1376
+ .fetch({ filters: { name: '', ...this.presetFilters, ...rest } })
1377
+ .then((items) => {
1375
1378
  items?.length && this.mergeIntoCache(items);
1376
1379
  return items;
1377
1380
  });
@@ -1392,7 +1395,7 @@ class DictStore {
1392
1395
  }
1393
1396
  /**
1394
1397
  * Set a search query and filters.
1395
- * With `fixed: false` initiates server search; with `fixed: true` — local filtering.
1398
+ * With `fixed: false` initiates server search; with `fixed: true` local filtering.
1396
1399
  */
1397
1400
  search = (name = '', filters = {}) => {
1398
1401
  this._armed.set(true);
@@ -1571,8 +1574,8 @@ class DictLocalStore {
1571
1574
  * Represents the full, unfiltered list of dictionary entries.
1572
1575
  * Used as the base data set for search and option generation.
1573
1576
  */
1574
- items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
1575
- #draftItems = signal([], ...(ngDevMode ? [{ debugName: "#draftItems" }] : /* istanbul ignore next */ []));
1577
+ items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : []));
1578
+ #draftItems = signal([], ...(ngDevMode ? [{ debugName: "#draftItems" }] : []));
1576
1579
  /**
1577
1580
  * Computed list of options in `{ label, value }` format.
1578
1581
  *
@@ -1587,7 +1590,7 @@ class DictLocalStore {
1587
1590
  value: it[this.valueKey],
1588
1591
  }));
1589
1592
  return this.maxOptionsSize ? options.slice(0, this.maxOptionsSize) : options;
1590
- }, ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
1593
+ }, ...(ngDevMode ? [{ debugName: "options" }] : []));
1591
1594
  labelKey;
1592
1595
  valueKey;
1593
1596
  maxOptionsSize;
@@ -1672,14 +1675,14 @@ class DictLocalStore {
1672
1675
  class EntityStore {
1673
1676
  idKey;
1674
1677
  sortIds;
1675
- byId = signal({}, ...(ngDevMode ? [{ debugName: "byId" }] : /* istanbul ignore next */ []));
1676
- ids = signal([], ...(ngDevMode ? [{ debugName: "ids" }] : /* istanbul ignore next */ []));
1678
+ byId = signal({}, ...(ngDevMode ? [{ debugName: "byId" }] : []));
1679
+ ids = signal([], ...(ngDevMode ? [{ debugName: "ids" }] : []));
1677
1680
  items = computed(() => {
1678
1681
  const byId = this.byId();
1679
1682
  return this.ids()
1680
1683
  .map((id) => byId[String(id)])
1681
1684
  .filter((item) => item !== undefined);
1682
- }, ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
1685
+ }, ...(ngDevMode ? [{ debugName: "items" }] : []));
1683
1686
  constructor(config) {
1684
1687
  this.idKey = config.idKey;
1685
1688
  this.sortIds = config.sortIds;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.1.1",
2
+ "version": "3.1.2",
3
3
  "name": "@reforgium/statum",
4
4
  "description": "Signals-first API state and query stores for Angular",
5
5
  "author": "rtommievich",
@@ -477,7 +477,7 @@ type PagedQueryStoreProviderConfig = {
477
477
  * - reactive signals: `items`, `loading`, `cached`;
478
478
  * - methods to control page/size/query;
479
479
  * - optional LRU cache by pages;
480
- * - configurable transport (GET/POST/PATCH/…).
480
+ * - configurable transport (GET/POST/PATCH/…).
481
481
  *
482
482
  * Example:
483
483
  * ```ts
@@ -761,6 +761,7 @@ declare class DictStore<Type extends AnyDict> {
761
761
  private readonly ttlMs?;
762
762
  private readonly revalidate;
763
763
  private readonly cacheUpdatedAt;
764
+ private readonly presetFilters;
764
765
  /**
765
766
  * Search text.
766
767
  * With `fixed: true` filters the local cache; with `fixed: false` triggers server search.
@@ -774,7 +775,7 @@ declare class DictStore<Type extends AnyDict> {
774
775
  private cachedItems;
775
776
  /**
776
777
  * Current list of dictionary items.
777
- * Source — local cache (fixed=true) or data from `PagedQueryStore`.
778
+ * Source local cache (fixed=true) or data from `PagedQueryStore`.
778
779
  */
779
780
  items: Signal<readonly Type[]>;
780
781
  /**
@@ -798,7 +799,7 @@ declare class DictStore<Type extends AnyDict> {
798
799
  clearCache(): void;
799
800
  /**
800
801
  * Set a search query and filters.
801
- * With `fixed: false` initiates server search; with `fixed: true` — local filtering.
802
+ * With `fixed: false` initiates server search; with `fixed: true` local filtering.
802
803
  */
803
804
  search: (name?: string, filters?: AnyDict) => void;
804
805
  /**