@reforgium/statum 3.0.0-rc.1 → 3.0.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/README.md +342 -83
- package/fesm2022/reforgium-statum.mjs +416 -127
- package/package.json +3 -3
- package/types/reforgium-statum.d.ts +176 -59
package/README.md
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@reforgium/statum)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
**Signals-first
|
|
6
|
+
**Signals-first query and data stores for Angular (18+).**
|
|
7
7
|
|
|
8
|
-
`@reforgium/statum` provides
|
|
9
|
-
**serialization layer** for
|
|
8
|
+
`@reforgium/statum` provides **API-oriented stores**, **cache strategies**, and a
|
|
9
|
+
**serialization layer** for Angular applications that talk to HTTP backends.
|
|
10
10
|
|
|
11
|
-
Designed for **
|
|
11
|
+
Designed for **request orchestration, pagination, dictionaries, and entity state**.
|
|
12
|
+
It is not a reduced framework and does not try to replace NgRx-style global event modeling.
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
@@ -23,31 +24,69 @@ Designed for **application state**, not abstract reducers.
|
|
|
23
24
|
- Unified retry and trace hooks in `ResourceStore`
|
|
24
25
|
- Explicit serialization / deserialization boundary
|
|
25
26
|
|
|
27
|
+
## Best Fit
|
|
28
|
+
|
|
29
|
+
Use `statum` when you need:
|
|
30
|
+
|
|
31
|
+
- HTTP-first application state for CRUD-heavy Angular apps
|
|
32
|
+
- Predictable request behavior (dedupe, abort, retry, latest-wins)
|
|
33
|
+
- Reusable pagination and dictionary flows for tables and forms
|
|
34
|
+
- Lightweight entity normalization without a full reducer architecture
|
|
35
|
+
|
|
36
|
+
`statum` is usually a good fit between:
|
|
37
|
+
|
|
38
|
+
- plain `HttpClient` services that have started to accumulate state logic
|
|
39
|
+
- full application state frameworks where global reducers/effects are too expensive for the problem
|
|
40
|
+
|
|
41
|
+
`statum` is usually not the right fit when:
|
|
42
|
+
|
|
43
|
+
- you need cross-page event sourcing, time-travel tooling, or centralized action logs
|
|
44
|
+
- your app is mostly local UI state with little backend orchestration
|
|
45
|
+
- you want a framework-agnostic data layer
|
|
46
|
+
|
|
26
47
|
---
|
|
27
48
|
|
|
28
|
-
## Installation
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install @reforgium/statum
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Configuration
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
import { provideStatum } from '@reforgium/statum';
|
|
38
|
-
|
|
39
|
-
providers: [
|
|
40
|
-
provideStatum({
|
|
41
|
-
pagedQuery: {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @reforgium/statum
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { provideStatum } from '@reforgium/statum';
|
|
59
|
+
|
|
60
|
+
providers: [
|
|
61
|
+
provideStatum({
|
|
62
|
+
pagedQuery: {
|
|
63
|
+
defaultBaseUrl: '/api',
|
|
64
|
+
defaultMethod: 'GET',
|
|
65
|
+
defaultHasCache: true,
|
|
66
|
+
defaultCacheSize: 10,
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
];
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## API Stability
|
|
73
|
+
|
|
74
|
+
`statum` v3 narrows its public API around explicit store methods and object-style contracts.
|
|
75
|
+
|
|
76
|
+
Stability policy for the documented public surface:
|
|
77
|
+
|
|
78
|
+
- Existing documented methods are treated as stable within `3.x`
|
|
79
|
+
- Signature expansions should stay additive and backward-compatible
|
|
80
|
+
- Behavioral changes should be reflected in `CHANGELOG.md` and, when needed, `MIGRATION.md`
|
|
81
|
+
- Removed or renamed APIs should go through a documented migration path first
|
|
82
|
+
|
|
83
|
+
The safest long-term entry points are:
|
|
84
|
+
|
|
85
|
+
- package exports from `@reforgium/statum`
|
|
86
|
+
- documented store methods in this README
|
|
87
|
+
- provider helpers such as `provideStatum(...)`
|
|
88
|
+
|
|
89
|
+
---
|
|
51
90
|
|
|
52
91
|
## Package Structure
|
|
53
92
|
|
|
@@ -55,6 +94,59 @@ providers: [
|
|
|
55
94
|
- **Stores** - reusable state containers over HttpClient
|
|
56
95
|
- **Serializer** - configurable data transformation utility
|
|
57
96
|
|
|
97
|
+
## Behavioral Guarantees
|
|
98
|
+
|
|
99
|
+
The core stores are designed around explicit, testable runtime guarantees.
|
|
100
|
+
|
|
101
|
+
`ResourceStore` guarantees:
|
|
102
|
+
|
|
103
|
+
- Identical in-flight requests can be deduplicated into one transport call
|
|
104
|
+
- `abort(...)` / `abortAll(...)` cancel active or scheduled requests and clear inflight dedupe references
|
|
105
|
+
- Retry policy can be set globally and overridden per request
|
|
106
|
+
- Trace hooks receive cache/request lifecycle events for observability
|
|
107
|
+
|
|
108
|
+
`PagedQueryStore` guarantees:
|
|
109
|
+
|
|
110
|
+
- `fetch(...)` always starts from page `0` and clears page cache first
|
|
111
|
+
- `updatePage(...)` can read from cache unless `ignoreCache` is enabled
|
|
112
|
+
- `updatePageSize(...)` resets cache before loading the first page with the new size
|
|
113
|
+
- `updateByOffset(...)` maps table offsets into normalized `page + size` requests
|
|
114
|
+
- `latest-wins` keeps `loading` stable during abort/restart request bursts and ignores stale async parse results
|
|
115
|
+
- `parallel` allows overlapping page requests when the source intentionally supports concurrent fetches
|
|
116
|
+
|
|
117
|
+
`DictStore` guarantees:
|
|
118
|
+
|
|
119
|
+
- `fixed: true` performs local search over the loaded cache after initial fill
|
|
120
|
+
- `fixed: false` delegates search to the server with debounce support
|
|
121
|
+
- Visible `options` stay normalized to `{ label, value }`
|
|
122
|
+
|
|
123
|
+
Performance notes:
|
|
124
|
+
|
|
125
|
+
- `ResourceStore`, `PagedQueryStore`, and `DictStore` include stress/perf coverage in the test suite
|
|
126
|
+
- The goal of these tests is behavioral regression detection and upper-bound sanity checks, not synthetic benchmark marketing
|
|
127
|
+
- For production evaluation, prefer measuring with your own API latency, payload size, and change detection profile
|
|
128
|
+
- Run `npm run bench:statum` for a current local timing snapshot
|
|
129
|
+
- Run `npm run bench:statum:browser` and open `/test-routing/statum-bench` for browser-side render measurements
|
|
130
|
+
- See `PERF.md` for the latest checked-in baseline captured in this repository
|
|
131
|
+
|
|
132
|
+
Reproducible examples are covered in tests:
|
|
133
|
+
|
|
134
|
+
- `dedupe identical requests` - many callers hitting the same `ResourceStore.get(...)` with `dedupe: true` still produce one transport request
|
|
135
|
+
- `cache hit/miss semantics` - `cache-only` fails on a cold key, then `cache-first` serves the warmed key without network
|
|
136
|
+
- `latest-wins abort behavior` - `PagedQueryStore.updatePage(...)` bursts cancel older in-flight requests and only the newest page is applied
|
|
137
|
+
- `pagination cache eviction` - when the page cache is full, revisiting an evicted page performs a fresh request
|
|
138
|
+
- `warm cache replay` - replaying the hot cache window stays network-free across the full cached range
|
|
139
|
+
|
|
140
|
+
These examples are intentionally implemented as specs, not ad-hoc benchmark scripts, so they can be rerun in CI and used as regression checks.
|
|
141
|
+
|
|
142
|
+
The checked-in benchmark baseline in `PERF.md` also shows a practical scaling claim for `ResourceStore`:
|
|
143
|
+
|
|
144
|
+
- identical request fan-in collapses from `N` transport calls to `1` when `dedupe: true`
|
|
145
|
+
- in the current local snapshot, a `10,000` caller burst is roughly an order of magnitude faster with dedupe enabled
|
|
146
|
+
- the same `10,000` caller burst reduces memory pressure from tens of MB to low single-digit MB in the deduped path
|
|
147
|
+
|
|
148
|
+
Those numbers are machine-specific and should be treated as a local envelope, not a universal SLA.
|
|
149
|
+
|
|
58
150
|
---
|
|
59
151
|
|
|
60
152
|
## Cache
|
|
@@ -152,7 +244,7 @@ const user = await userStore.get(
|
|
|
152
244
|
);
|
|
153
245
|
```
|
|
154
246
|
|
|
155
|
-
Retry + trace example:
|
|
247
|
+
Retry + trace example:
|
|
156
248
|
|
|
157
249
|
```ts
|
|
158
250
|
import { ResourceStore } from '@reforgium/statum';
|
|
@@ -166,22 +258,22 @@ const store = new ResourceStore<{ id: number; name: string }>(
|
|
|
166
258
|
}
|
|
167
259
|
);
|
|
168
260
|
|
|
169
|
-
await store.get(
|
|
170
|
-
{ params: { id: '42' } },
|
|
171
|
-
{ retry: { attempts: 1 } } // per-call override
|
|
172
|
-
);
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
Profiles example:
|
|
176
|
-
|
|
177
|
-
```ts
|
|
178
|
-
import { createResourceProfile, ResourceStore } from '@reforgium/statum';
|
|
179
|
-
|
|
180
|
-
const usersStore = new ResourceStore<{ id: number; name: string }>(
|
|
181
|
-
{ GET: '/users' },
|
|
182
|
-
createResourceProfile('table', { baseUrl: '/api' })
|
|
183
|
-
);
|
|
184
|
-
```
|
|
261
|
+
await store.get(
|
|
262
|
+
{ params: { id: '42' } },
|
|
263
|
+
{ retry: { attempts: 1 } } // per-call override
|
|
264
|
+
);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Profiles example:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { createResourceProfile, ResourceStore } from '@reforgium/statum';
|
|
271
|
+
|
|
272
|
+
const usersStore = new ResourceStore<{ id: number; name: string }>(
|
|
273
|
+
{ GET: '/users' },
|
|
274
|
+
createResourceProfile('table', { baseUrl: '/api' })
|
|
275
|
+
);
|
|
276
|
+
```
|
|
185
277
|
|
|
186
278
|
---
|
|
187
279
|
|
|
@@ -206,9 +298,9 @@ entities.removeOne(1);
|
|
|
206
298
|
|
|
207
299
|
---
|
|
208
300
|
|
|
209
|
-
## PagedQueryStore
|
|
210
|
-
|
|
211
|
-
Lightweight store for server-side pagination with filtering, dynamic query params, and page cache.
|
|
301
|
+
## PagedQueryStore
|
|
302
|
+
|
|
303
|
+
Lightweight store for server-side pagination with filtering, dynamic query params, and page cache.
|
|
212
304
|
|
|
213
305
|
### When to use
|
|
214
306
|
|
|
@@ -224,35 +316,65 @@ Lightweight store for server-side pagination with filtering, dynamic query param
|
|
|
224
316
|
| items | `WritableSignal<T[]>` | Current page items |
|
|
225
317
|
| cached | `WritableSignal<T[]>` | Flattened cache of cached pages |
|
|
226
318
|
| loading | `WritableSignal<boolean>` | Loading indicator |
|
|
319
|
+
| error | `WritableSignal<unknown \| null>` | Last request error |
|
|
320
|
+
| version | `WritableSignal<number>` | Increments when the dataset is reset |
|
|
227
321
|
| page | `number` | Current page (0-based) |
|
|
228
322
|
| pageSize | `number` | Page size |
|
|
229
323
|
| totalElements | `number` | Total items on server |
|
|
230
|
-
| filters | `Partial<F>` | Active filters |
|
|
231
|
-
| query | `Record<string, unknown>` | Active query params |
|
|
324
|
+
| filters | `Partial<F>` | Active filters |
|
|
325
|
+
| query | `Record<string, unknown>` | Active query params |
|
|
326
|
+
| sort | `ReadonlyArray<{ sort: string; order: 'asc' \| 'desc' }>` | Active sort state |
|
|
327
|
+
|
|
328
|
+
Reactive metadata signals are also available:
|
|
329
|
+
|
|
330
|
+
- `pageState`
|
|
331
|
+
- `pageSizeState`
|
|
332
|
+
- `totalElementsState`
|
|
333
|
+
- `filtersState`
|
|
334
|
+
- `queryState`
|
|
335
|
+
- `sortState`
|
|
336
|
+
- `routeParamsState`
|
|
232
337
|
|
|
233
338
|
### Methods
|
|
234
339
|
|
|
235
|
-
| Method | Description
|
|
236
|
-
|
|
237
|
-
| fetch | Clean first-page request: `fetch({ filters, query, routeParams })`
|
|
238
|
-
| refetchWith
|
|
239
|
-
| updatePage | Change page: `updatePage(page, { ignoreCache })`
|
|
240
|
-
| updatePageSize | Change page size and reset cache: `updatePageSize(size)`
|
|
241
|
-
|
|
|
242
|
-
|
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
340
|
+
| Method | Description |
|
|
341
|
+
|----------------|-----------------------------------------------------------------------------------------|
|
|
342
|
+
| fetch | Clean first-page request: `fetch({ filters, query, routeParams })` |
|
|
343
|
+
| refetchWith | Repeat request, optional merge overrides: `refetchWith({ filters, query })` |
|
|
344
|
+
| updatePage | Change page: `updatePage(page, { ignoreCache })` or `updatePage({ page, ignoreCache })` |
|
|
345
|
+
| updatePageSize | Change page size and reset cache: `updatePageSize(size)` |
|
|
346
|
+
| setSort | Update sort state without triggering a request |
|
|
347
|
+
| updateSort | Apply single-sort state and load from the first page |
|
|
348
|
+
| updateSorts | Apply multi-sort state and load from the first page |
|
|
349
|
+
| updateByOffset | Table-event mapper: `updateByOffset({ page/first/rows }, { query })` |
|
|
350
|
+
| setRouteParams | Update route params: `setRouteParams(params, { reset, abort })` |
|
|
351
|
+
| updateConfig | Patch config: `updateConfig(config)` |
|
|
352
|
+
| copy | Copy config/meta: `copy(store)` |
|
|
353
|
+
| destroy | Manual destroying of caches and abort requests |
|
|
354
|
+
|
|
355
|
+
`version` is useful for consumers that keep their own local page buffer. For example, `@reforgium/data-grid` can use `[source]="store"` and clear its internal infinity buffer when `fetch()`, `updatePageSize()`, or `setRouteParams(..., { reset: true })` replace the dataset.
|
|
356
|
+
|
|
357
|
+
Sorting is built into `PagedQueryStore` and serialized in the common backend-friendly form:
|
|
358
|
+
|
|
359
|
+
```txt
|
|
360
|
+
sort=name,asc
|
|
361
|
+
sort=name,asc&sort=createdAt,desc
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Concurrency can be configured per store:
|
|
365
|
+
|
|
366
|
+
- `latest-wins` - abort older in-flight requests before the next page request starts
|
|
367
|
+
- `parallel` - allow overlapping requests and keep `loading()` true until all active requests finish
|
|
368
|
+
|
|
369
|
+
### Cache behavior
|
|
370
|
+
|
|
371
|
+
| Method | Cache read | Cache reset | Notes |
|
|
372
|
+
|----------------|------------|-------------|--------------------------------------------------|
|
|
373
|
+
| fetch | no | yes | Always starts clean from page `0` |
|
|
374
|
+
| refetchWith | no | no | Uses active page/filters/query, merges overrides |
|
|
375
|
+
| updatePage | yes | no | Can bypass with `ignoreCache: true` |
|
|
376
|
+
| updatePageSize | no | yes | Prevents mixed caches for different page sizes |
|
|
377
|
+
| updateByOffset | yes | no | Internally maps to `page + size` |
|
|
256
378
|
|
|
257
379
|
Example:
|
|
258
380
|
|
|
@@ -261,15 +383,51 @@ import { PagedQueryStore } from '@reforgium/statum';
|
|
|
261
383
|
|
|
262
384
|
type User = { id: number; name: string };
|
|
263
385
|
|
|
264
|
-
const store = new PagedQueryStore<User, { search?: string }>('api/users', {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
386
|
+
const store = new PagedQueryStore<User, { search?: string }>('api/users', {
|
|
387
|
+
baseUrl: '/api',
|
|
388
|
+
method: 'GET',
|
|
389
|
+
debounceTime: 200,
|
|
390
|
+
concurrency: 'latest-wins',
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
store.fetch({ filters: { search: 'neo' }, query: { tenant: 'kg' } });
|
|
394
|
+
store.updateByOffset({ first: 20, rows: 20 });
|
|
395
|
+
store.updateSort({ sort: 'name', order: 'asc' });
|
|
271
396
|
```
|
|
272
397
|
|
|
398
|
+
`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.
|
|
399
|
+
|
|
400
|
+
Direct state mutation setters for `page`, `pageSize`, `filters`, `query`, and `totalElements` still exist for backward compatibility, but they are deprecated in favor of explicit store methods. `sort` and `routeParams` should be changed only through `setSort(...)` and `setRouteParams(...)`.
|
|
401
|
+
|
|
402
|
+
### PagedQueryStore + DataGrid source mode
|
|
403
|
+
|
|
404
|
+
`PagedQueryStore` can be passed directly into `@reforgium/data-grid` `source` mode:
|
|
405
|
+
|
|
406
|
+
```html
|
|
407
|
+
<re-data-grid
|
|
408
|
+
mode="infinity"
|
|
409
|
+
[columns]="columns"
|
|
410
|
+
[source]="usersStore"
|
|
411
|
+
/>
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
This works because the store already exposes the expected source contract:
|
|
415
|
+
|
|
416
|
+
- `items`
|
|
417
|
+
- `loading`
|
|
418
|
+
- `error`
|
|
419
|
+
- `page`
|
|
420
|
+
- `pageSize`
|
|
421
|
+
- `totalElements`
|
|
422
|
+
- `sort`
|
|
423
|
+
- `version`
|
|
424
|
+
- `updatePage(...)`
|
|
425
|
+
- `updatePageSize(...)`
|
|
426
|
+
- `updateSort(...)`
|
|
427
|
+
- `updateSorts(...)`
|
|
428
|
+
|
|
429
|
+
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.
|
|
430
|
+
|
|
273
431
|
---
|
|
274
432
|
|
|
275
433
|
## DictStore
|
|
@@ -364,6 +522,107 @@ This keeps page-loading logic in `PagedQueryStore` and normalized lookup/update
|
|
|
364
522
|
|
|
365
523
|
---
|
|
366
524
|
|
|
525
|
+
## Recipes
|
|
526
|
+
|
|
527
|
+
### Debounced Remote Table
|
|
528
|
+
|
|
529
|
+
Use `PagedQueryStore` as a reusable server-table state layer for filters, lazy paging, and cache-aware navigation.
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
import { PagedQueryStore } from '@reforgium/statum';
|
|
533
|
+
|
|
534
|
+
type User = { id: number; name: string; role: string };
|
|
535
|
+
type UserFilters = { search?: string; role?: string };
|
|
536
|
+
|
|
537
|
+
const users = new PagedQueryStore<User, UserFilters>('/api/users', {
|
|
538
|
+
baseUrl: '/gateway',
|
|
539
|
+
method: 'GET',
|
|
540
|
+
debounceTime: 250,
|
|
541
|
+
hasCache: true,
|
|
542
|
+
cacheSize: 5,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
await users.fetch({
|
|
546
|
+
filters: { search: 'neo', role: 'admin' },
|
|
547
|
+
query: { tenant: 'main' },
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
await users.updateByOffset({ first: 20, rows: 20 });
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Use this when the component should stay thin and pagination/filter state should live outside the template layer.
|
|
554
|
+
|
|
555
|
+
### Latest-Wins Details Loader
|
|
556
|
+
|
|
557
|
+
Use `ResourceStore` when route changes or rapid user clicks can trigger overlapping requests.
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
import { ResourceStore } from '@reforgium/statum';
|
|
561
|
+
|
|
562
|
+
type UserDetails = { id: number; name: string; email: string };
|
|
563
|
+
|
|
564
|
+
const details = new ResourceStore<UserDetails>(
|
|
565
|
+
{ GET: '/api/users/:id' },
|
|
566
|
+
{ ttlMs: 30_000 }
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
async function openUser(id: number) {
|
|
570
|
+
details.abortAll();
|
|
571
|
+
|
|
572
|
+
return details.get(
|
|
573
|
+
{ params: { id } },
|
|
574
|
+
{ dedupe: true }
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Use this when the newest selection should win and stale responses should not keep competing for UI state.
|
|
580
|
+
|
|
581
|
+
### Autocomplete With Cached Dictionary
|
|
582
|
+
|
|
583
|
+
Use `DictStore` to separate option loading, debounce, and local filtering from the component.
|
|
584
|
+
|
|
585
|
+
```ts
|
|
586
|
+
import { DictStore } from '@reforgium/statum';
|
|
587
|
+
|
|
588
|
+
type Country = { code: string; name: string };
|
|
589
|
+
|
|
590
|
+
const countries = new DictStore<Country>('/api/dictionaries/countries', 'countries', {
|
|
591
|
+
fixed: true,
|
|
592
|
+
labelKey: 'name',
|
|
593
|
+
valueKey: 'code',
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
await countries.search('');
|
|
597
|
+
countries.search('kir');
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
Use `fixed: true` when the dataset is small enough to keep locally and you want immediate repeated searches without extra network calls.
|
|
601
|
+
|
|
602
|
+
### Normalize Paged Data For Detail Mutations
|
|
603
|
+
|
|
604
|
+
Use `PagedQueryStore` for loading and `EntityStore` for stable updates after edits.
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
import { effect } from '@angular/core';
|
|
608
|
+
import { EntityStore, PagedQueryStore } from '@reforgium/statum';
|
|
609
|
+
|
|
610
|
+
type User = { id: number; name: string };
|
|
611
|
+
|
|
612
|
+
const pages = new PagedQueryStore<User>('/api/users');
|
|
613
|
+
const entities = new EntityStore<User, 'id'>({ idKey: 'id' });
|
|
614
|
+
|
|
615
|
+
effect(() => {
|
|
616
|
+
entities.upsertMany(pages.items());
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
entities.patchOne(1, { name: 'Updated' });
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Use this when list fetching and local item mutations need different lifecycles.
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
367
626
|
## Serializer
|
|
368
627
|
|
|
369
628
|
### Serializer
|
|
@@ -409,12 +668,12 @@ const body = serializer.serialize({ name: ' Vasya ', active: null });
|
|
|
409
668
|
|
|
410
669
|
---
|
|
411
670
|
|
|
412
|
-
## Source Structure
|
|
413
|
-
|
|
414
|
-
- Cache: `src/cache`
|
|
415
|
-
- Stores: `src/stores`
|
|
416
|
-
- Serializer: `src/serializer`
|
|
417
|
-
- Migration: `MIGRATION.md`
|
|
671
|
+
## Source Structure
|
|
672
|
+
|
|
673
|
+
- Cache: `src/cache`
|
|
674
|
+
- Stores: `src/stores`
|
|
675
|
+
- Serializer: `src/serializer`
|
|
676
|
+
- Migration: `MIGRATION.md`
|
|
418
677
|
|
|
419
678
|
---
|
|
420
679
|
|