@reforgium/statum 3.1.5 → 3.2.0

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,26 +1,59 @@
1
- ## [3.1.5]: 2026-04-14
2
-
3
- ### Fix:
4
- - `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
5
- - `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
6
-
7
- ### Test:
8
- - added regression coverage for cache invalidation on route-param session changes without store destruction
9
-
10
- ---
11
-
12
- ## [3.1.4]: 2026-04-10
13
-
14
- ### Fix:
15
- - `PagedQueryStore`: `totalElements` now preserves `undefined` when `parseResponse` omits the field, so unknown-total datasets stay open-ended instead of being forced back to `0` after reset/refetch.
16
- - `PagedQueryStore`: `refetchWith(...)` now treats real filters/query/sort changes as dataset replacement, clearing page cache/items and bumping `version` so `@reforgium/data-grid` infinity buffers do not keep stale rows after an empty successful response.
17
-
18
- ### Test:
19
- - added regression coverage for omitted `totalElements`, reset/refetch clearing, and empty-result unknown-total flows
20
-
21
- ---
22
-
23
- ## [3.1.3]: 2026-04-06
1
+ ## [3.2.0]: 2026-04-24
2
+
3
+ ### Feat:
4
+ - `DictStore`: added `autoLoad: 'onDemand'` and `ensureLoaded()` for controlled lazy startup; dictionaries can now stay passive on construction and fetch only when a consumer explicitly needs them
5
+ - `DictStore`: added `autoLoad: 'onAccess'` as an explicitly dirty opt-in mode for UI consumers that want the first read of `items()` / `options()` to lazily schedule loading
6
+ - `DictStoreProviderConfig`: added `defaultAutoLoad` so the default startup policy can be set once at provider level instead of repeating `autoLoad` on every dictionary instance
7
+
8
+ ### Fix:
9
+ - `DictStore`: `ttlMs` now enables stale-cache revalidation by default when `revalidate` is omitted; stale dictionaries no longer look like they have TTL configured while silently skipping refresh
10
+ - `DictStore`: stale-cache refresh now depends only on `ttlMs`; deprecated `revalidate` no longer changes runtime behavior, so TTL-based freshness is no longer split across two partially overlapping flags
11
+
12
+ ### Test:
13
+ - added regression coverage for deprecated `revalidate: false` compatibility, on-demand loading, and on-access lazy activation
14
+
15
+ ### Docs:
16
+ - `README`: clarified that `ttlMs` implies background revalidation unless `revalidate` is set to `false` explicitly
17
+ - `README`: documented `autoLoad: 'onDemand'`, `autoLoad: 'onAccess'`, `ensureLoaded()`, provider-level `defaultAutoLoad`, and the deprecation of `revalidate`
18
+
19
+ ---
20
+
21
+ ## [3.1.6]: 2026-04-17
22
+
23
+ ### Fix:
24
+ - `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
25
+
26
+ ### Test:
27
+ - updated flat-array regression coverage to assert unknown-total semantics instead of the old inferred-length behavior
28
+
29
+ ### Docs:
30
+ - `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(...)`
31
+
32
+ ---
33
+
34
+ ## [3.1.5]: 2026-04-14
35
+
36
+ ### Fix:
37
+ - `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
38
+ - `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
39
+
40
+ ### Test:
41
+ - added regression coverage for cache invalidation on route-param session changes without store destruction
42
+
43
+ ---
44
+
45
+ ## [3.1.4]: 2026-04-10
46
+
47
+ ### Fix:
48
+ - `PagedQueryStore`: `totalElements` now preserves `undefined` when `parseResponse` omits the field, so unknown-total datasets stay open-ended instead of being forced back to `0` after reset/refetch.
49
+ - `PagedQueryStore`: `refetchWith(...)` now treats real filters/query/sort changes as dataset replacement, clearing page cache/items and bumping `version` so `@reforgium/data-grid` infinity buffers do not keep stale rows after an empty successful response.
50
+
51
+ ### Test:
52
+ - added regression coverage for omitted `totalElements`, reset/refetch clearing, and empty-result unknown-total flows
53
+
54
+ ---
55
+
56
+ ## [3.1.3]: 2026-04-06
24
57
 
25
58
  ### Fix:
26
59
  - `PagedQueryStore`: all public state getters (`filters`, `query`, `page`, `pageSize`, `totalElements`, `sort`) and direct `#routeParams` reads now wrap the underlying signal in `untracked()`; previously calling `fetch()`, `refetchWith()`, `updatePage()`, `setRouteParams()`, etc. from inside an Angular `effect()` or `computed()` would inadvertently subscribe the reactive context to internal state signals, causing cascading re-runs; reactive subscriptions remain available exclusively through the `*State` readonly signals (`filtersState`, `sortState`, `pageState`, …)
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.
@@ -612,17 +616,60 @@ import { DictStore } from '@reforgium/statum';
612
616
 
613
617
  type Country = { code: string; name: string };
614
618
 
615
- const countries = new DictStore<Country>('/api/dictionaries/countries', 'countries', {
616
- fixed: true,
617
- labelKey: 'name',
618
- valueKey: 'code',
619
- });
619
+ const countries = new DictStore<Country>('/api/dictionaries/countries', 'countries', {
620
+ fixed: true,
621
+ labelKey: 'name',
622
+ valueKey: 'code',
623
+ });
620
624
 
621
625
  await countries.search('');
622
626
  countries.search('kir');
623
627
  ```
624
628
 
625
- Use `fixed: true` when the dataset is small enough to keep locally and you want immediate repeated searches without extra network calls.
629
+ Use `fixed: true` when the dataset is small enough to keep locally and you want immediate repeated searches without extra network calls.
630
+
631
+ `DictStore` cache freshness notes:
632
+
633
+ - `ttlMs` marks persisted dictionary cache as stale after the configured window
634
+ - stale cache is refreshed automatically when `ttlMs` is set
635
+ - `revalidate` is deprecated and no longer changes runtime behavior
636
+ - storage `updatedAt` changes when cache is actually persisted again, not just because wall-clock time passed
637
+
638
+ For controlled lazy startup, use `autoLoad: 'onDemand'` and trigger the first fetch explicitly:
639
+
640
+ ```ts
641
+ const countries = new DictStore<Country>('/api/dictionaries/countries', 'countries', {
642
+ fixed: true,
643
+ autoLoad: 'onDemand',
644
+ });
645
+
646
+ countries.ensureLoaded(); // first request only when the UI actually needs the dictionary
647
+ ```
648
+
649
+ There is also a dirtier opt-in mode for select-like consumers that want loading to start on the first read:
650
+
651
+ ```ts
652
+ const countries = new DictStore<Country>('/api/dictionaries/countries', 'countries', {
653
+ fixed: true,
654
+ autoLoad: 'onAccess',
655
+ });
656
+
657
+ countries.options(); // first read schedules loading lazily
658
+ ```
659
+
660
+ Use `onAccess` only when you explicitly accept read-triggered loading semantics. It is guarded against same-turn request storms, but it is still intentionally less pure than `onDemand`.
661
+
662
+ The same policy can be moved to the global provider:
663
+
664
+ ```ts
665
+ provideStatum({
666
+ dict: {
667
+ defaultAutoLoad: 'onDemand',
668
+ },
669
+ });
670
+ ```
671
+
672
+ `ensureLoaded()` is not required for every `DictStore`. It is only needed when the effective auto-load mode is `'onDemand'` and you want the first fetch to happen explicitly. In `true`, `false`, or `'whenEmpty'` modes it remains an optional manual trigger.
626
673
 
627
674
  ### Normalize Paged Data For Detail Mutations
628
675
 
@@ -1177,7 +1177,7 @@ class PagedQueryStore {
1177
1177
  this.cached.set([]);
1178
1178
  }
1179
1179
  parseFlatArray(data) {
1180
- return { content: data, totalElements: data.length };
1180
+ return { content: data };
1181
1181
  }
1182
1182
  resetDataset() {
1183
1183
  this.page = 0;
@@ -1296,9 +1296,10 @@ class DictStore {
1296
1296
  storage;
1297
1297
  metaStorage;
1298
1298
  ttlMs;
1299
- revalidate;
1299
+ accessAutoLoadMode;
1300
1300
  cacheUpdatedAt = signal(null, ...(ngDevMode ? [{ debugName: "cacheUpdatedAt" }] : /* istanbul ignore next */ []));
1301
1301
  presetFilters = {};
1302
+ accessLoadQueued = false;
1302
1303
  /**
1303
1304
  * Search text.
1304
1305
  * With `fixed: true` filters the local cache; with `fixed: false` triggers server search.
@@ -1315,6 +1316,7 @@ class DictStore {
1315
1316
  * Source — local cache (fixed=true) or data from `PagedQueryStore`.
1316
1317
  */
1317
1318
  items = computed(() => {
1319
+ this.maybeScheduleAccessLoad();
1318
1320
  const cached = this.cachedItems();
1319
1321
  if (!this.fixed) {
1320
1322
  return this.debouncedSearchText() || !cached.length ? this.#helper.items() : cached;
@@ -1337,7 +1339,7 @@ class DictStore {
1337
1339
  * @param storageKey key for saving cache in the selected strategy
1338
1340
  * @param cfg behavior (fixed/search, parsers, label/value keys, cache strategy, etc.)
1339
1341
  */
1340
- constructor(apiUrl, storageKey, { autoLoad = true, method = this.defaultConfig.defaultRestMethod, presetFilters = this.defaultConfig.defaultPresetFilters, parseResponse, parseRequest, debounceTime = this.defaultConfig.defaultDebounceTime, ttlMs = this.defaultConfig.defaultTtlMs, revalidate = this.defaultConfig.defaultRevalidate ?? false, fixed = true, maxOptionsSize = this.defaultConfig.defaultMaxOptionsSize, labelKey = this.defaultConfig.defaultLabelKey || 'name', valueKey = this.defaultConfig.defaultValueKey || 'code', keyPrefix = this.defaultConfig.defaultPrefix || 're', cacheStrategy = this.defaultConfig.defaultCacheStrategy || 'persist', }) {
1342
+ constructor(apiUrl, storageKey, { autoLoad = this.defaultConfig.defaultAutoLoad ?? true, method = this.defaultConfig.defaultRestMethod, presetFilters = this.defaultConfig.defaultPresetFilters, parseResponse, parseRequest, debounceTime = this.defaultConfig.defaultDebounceTime, ttlMs = this.defaultConfig.defaultTtlMs, fixed = true, maxOptionsSize = this.defaultConfig.defaultMaxOptionsSize, labelKey = this.defaultConfig.defaultLabelKey || 'name', valueKey = this.defaultConfig.defaultValueKey || 'code', keyPrefix = this.defaultConfig.defaultPrefix || 're', cacheStrategy = this.defaultConfig.defaultCacheStrategy || 'persist', }) {
1341
1343
  this.apiUrl = apiUrl;
1342
1344
  this.storageKey = storageKey;
1343
1345
  const searchDebounce = debounceTime ?? 300;
@@ -1352,7 +1354,7 @@ class DictStore {
1352
1354
  });
1353
1355
  this.fixed = fixed;
1354
1356
  this.ttlMs = ttlMs;
1355
- this.revalidate = revalidate;
1357
+ this.accessAutoLoadMode = autoLoad === 'onAccess';
1356
1358
  this.labelKey = labelKey;
1357
1359
  this.valueKey = valueKey;
1358
1360
  this.maxOptionsSize = maxOptionsSize;
@@ -1414,6 +1416,16 @@ class DictStore {
1414
1416
  this.searchText.set(name);
1415
1417
  this.filters.set(filters);
1416
1418
  };
1419
+ /**
1420
+ * Explicitly arms the store and loads data only when it is actually needed.
1421
+ *
1422
+ * Useful for dropdown/popup flows where eager constructor-time requests would
1423
+ * create too many parallel dictionary loads.
1424
+ */
1425
+ ensureLoaded = (filters = this.filters()) => {
1426
+ this._armed.set(true);
1427
+ this.filters.set(filters);
1428
+ };
1417
1429
  /**
1418
1430
  * Find display label by value (typically for reverse binding).
1419
1431
  * @returns label string or `undefined` if not found
@@ -1538,17 +1550,45 @@ class DictStore {
1538
1550
  setAutoload(autoLoad) {
1539
1551
  if (autoLoad === 'whenEmpty') {
1540
1552
  const isEmpty = !this.cachedItems().length;
1541
- this._armed.set(isEmpty || this.shouldRevalidateCache());
1553
+ this._armed.set(isEmpty || this.isCacheStale());
1554
+ return;
1555
+ }
1556
+ if (autoLoad === 'onDemand' || autoLoad === 'onAccess') {
1557
+ this._armed.set(false);
1558
+ return;
1542
1559
  }
1543
1560
  else {
1544
1561
  this._armed.set(autoLoad);
1545
1562
  }
1546
1563
  }
1547
1564
  shouldFetchFixedCache() {
1548
- return !this.cachedItems().length || this.shouldRevalidateCache();
1565
+ return !this.cachedItems().length || this.isCacheStale();
1549
1566
  }
1550
- shouldRevalidateCache() {
1551
- return this.revalidate && this.isCacheStale();
1567
+ maybeScheduleAccessLoad() {
1568
+ if (!this.accessAutoLoadMode || this._armed() || this.accessLoadQueued) {
1569
+ return;
1570
+ }
1571
+ if (!this.shouldFetchOnAccessRead()) {
1572
+ return;
1573
+ }
1574
+ this.accessLoadQueued = true;
1575
+ // Note: this is an intentionally dirty opt-in mode.
1576
+ // We never mutate `_armed` synchronously inside `computed()`, because reads should stay pure by default.
1577
+ // Instead, `autoLoad: 'onAccess'` schedules a single deferred activation after the first read of
1578
+ // `items()` / `options()`. This keeps the hack isolated and prevents request storms from repeated reads
1579
+ // during the same render/effect turn.
1580
+ queueMicrotask(() => {
1581
+ this.accessLoadQueued = false;
1582
+ if (!this._armed() && this.shouldFetchOnAccessRead()) {
1583
+ this.ensureLoaded();
1584
+ }
1585
+ });
1586
+ }
1587
+ shouldFetchOnAccessRead() {
1588
+ if (this.fixed) {
1589
+ return this.shouldFetchFixedCache();
1590
+ }
1591
+ return this.#helper.items().length === 0 && !this.cachedItems().length;
1552
1592
  }
1553
1593
  isCacheStale() {
1554
1594
  if (!this.cachedItems().length) {
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.1.5",
2
+ "version": "3.2.0",
3
3
  "name": "@reforgium/statum",
4
4
  "description": "Signals-first API state and query stores for Angular",
5
5
  "author": "rtommievich",
@@ -644,8 +644,16 @@ type DictStoreConfig<ItemsType extends object> = {
644
644
  keyPrefix?: string;
645
645
  /** Initial filters (added to the first request/filtering). */
646
646
  presetFilters?: Record<string, string>;
647
- /** Autoload data on initialization (`true` by default). */
648
- autoLoad?: boolean | 'whenEmpty';
647
+ /**
648
+ * Autoload data on initialization (`true` by default).
649
+ *
650
+ * - `true`: arm immediately on construction
651
+ * - `false`: stay passive until `search(...)` or `ensureLoaded()`
652
+ * - `'whenEmpty'`: arm on init only when cache is empty or stale
653
+ * - `'onDemand'`: never arm on init; load only after an explicit `ensureLoaded()` / `search(...)`
654
+ * - `'onAccess'`: dirty opt-in mode; first read of `items()` / `options()` lazily schedules loading
655
+ */
656
+ autoLoad?: boolean | 'whenEmpty' | 'onDemand' | 'onAccess';
649
657
  /**
650
658
  * Custom mapper of pagination/sort request into query params.
651
659
  * Useful if the API expects non-standard field names.
@@ -662,7 +670,10 @@ type DictStoreConfig<ItemsType extends object> = {
662
670
  debounceTime?: number;
663
671
  /** Cache freshness window (ms). Undefined means restored cache does not expire. */
664
672
  ttlMs?: number;
665
- /** When cache is stale, keep visible items and refresh them in the background. */
673
+ /**
674
+ * @deprecated `DictStore` now revalidates stale cache whenever `ttlMs` is set.
675
+ * Pass `ttlMs: undefined` to disable time-based refresh semantics entirely.
676
+ */
666
677
  revalidate?: boolean;
667
678
  /** Maximum number of options exposed (truncates `options`). */
668
679
  maxOptionsSize?: number;
@@ -708,13 +719,18 @@ type DictStoreProviderConfig = {
708
719
  defaultPrefix?: string;
709
720
  /** Initial filters (added to the first request/filtering). */
710
721
  defaultPresetFilters?: DictStoreConfig<AnyType>['presetFilters'];
722
+ /** Default autoload policy for dictionary stores. */
723
+ defaultAutoLoad?: DictStoreConfig<AnyType>['autoLoad'];
711
724
  /** Transport method for fetching the dictionary. Defaults to 'POST'. */
712
725
  defaultRestMethod?: DictStoreConfig<AnyType>['method'];
713
726
  /** Debounce delay before request (ms) — for frequent input/search. */
714
727
  defaultDebounceTime?: DictStoreConfig<AnyType>['debounceTime'];
715
728
  /** Cache freshness window (ms). Undefined means restored cache does not expire. */
716
729
  defaultTtlMs?: DictStoreConfig<AnyType>['ttlMs'];
717
- /** When cache is stale, keep visible items and refresh them in the background. */
730
+ /**
731
+ * @deprecated `DictStore` now revalidates stale cache whenever `defaultTtlMs` is set.
732
+ * Keep this field only for backward compatibility.
733
+ */
718
734
  defaultRevalidate?: DictStoreConfig<AnyType>['revalidate'];
719
735
  /** Maximum number of options exposed (truncates `options`). */
720
736
  defaultMaxOptionsSize?: DictStoreConfig<AnyType>['maxOptionsSize'];
@@ -760,9 +776,10 @@ declare class DictStore<Type extends AnyDict> {
760
776
  private readonly storage?;
761
777
  private readonly metaStorage?;
762
778
  private readonly ttlMs?;
763
- private readonly revalidate;
779
+ private readonly accessAutoLoadMode;
764
780
  private readonly cacheUpdatedAt;
765
781
  private readonly presetFilters;
782
+ private accessLoadQueued;
766
783
  /**
767
784
  * Search text.
768
785
  * With `fixed: true` filters the local cache; with `fixed: false` triggers server search.
@@ -794,7 +811,7 @@ declare class DictStore<Type extends AnyDict> {
794
811
  * @param storageKey key for saving cache in the selected strategy
795
812
  * @param cfg behavior (fixed/search, parsers, label/value keys, cache strategy, etc.)
796
813
  */
797
- constructor(apiUrl: string, storageKey: string, { autoLoad, method, presetFilters, parseResponse, parseRequest, debounceTime, ttlMs, revalidate, fixed, maxOptionsSize, labelKey, valueKey, keyPrefix, cacheStrategy, }: DictStoreConfig<Type>);
814
+ constructor(apiUrl: string, storageKey: string, { autoLoad, method, presetFilters, parseResponse, parseRequest, debounceTime, ttlMs, fixed, maxOptionsSize, labelKey, valueKey, keyPrefix, cacheStrategy, }: DictStoreConfig<Type>);
798
815
  /** Restore cache from the selected storage (`persist`/`session`/`lru`/`memory`). */
799
816
  restoreCache(): void;
800
817
  clearCache(): void;
@@ -803,6 +820,13 @@ declare class DictStore<Type extends AnyDict> {
803
820
  * With `fixed: false` initiates server search; with `fixed: true` — local filtering.
804
821
  */
805
822
  search: (name?: string, filters?: AnyDict) => void;
823
+ /**
824
+ * Explicitly arms the store and loads data only when it is actually needed.
825
+ *
826
+ * Useful for dropdown/popup flows where eager constructor-time requests would
827
+ * create too many parallel dictionary loads.
828
+ */
829
+ ensureLoaded: (filters?: AnyDict) => void;
806
830
  /**
807
831
  * Find display label by value (typically for reverse binding).
808
832
  * @returns label string or `undefined` if not found
@@ -829,7 +853,8 @@ declare class DictStore<Type extends AnyDict> {
829
853
  private keyOf;
830
854
  private setAutoload;
831
855
  private shouldFetchFixedCache;
832
- private shouldRevalidateCache;
856
+ private maybeScheduleAccessLoad;
857
+ private shouldFetchOnAccessRead;
833
858
  private isCacheStale;
834
859
  private metaKey;
835
860
  }