@reforgium/statum 2.0.0 → 2.1.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 CHANGED
@@ -3,7 +3,7 @@
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)
5
5
 
6
- **Signals-first state stores and caching utilities for Angular (20+).**
6
+ **Signals-first state stores and caching utilities for Angular (18+).**
7
7
 
8
8
  `@reforgium/statum` provides a set of **API-oriented stores**, **cache strategies**, and a
9
9
  **serialization layer** for building predictable data flows in Angular applications.
@@ -12,14 +12,16 @@ Designed for **application state**, not abstract reducers.
12
12
 
13
13
  ---
14
14
 
15
- ## Features
16
-
17
- - Signals-first API (`signal()` everywhere)
18
- - API-driven stores (resource / paginated / dictionaries)
19
- - Built-in cache strategies (TTL, prefix grouping)
20
- - Deterministic request lifecycle: loading / error / data
21
- - Optional transport-level: dedupe, debounce/throttle, abort
22
- - Explicit serialization / deserialization boundary
15
+ ## Features
16
+
17
+ - Signals-first API (`signal()` everywhere)
18
+ - API-driven stores (resource / paginated / dictionaries)
19
+ - Normalized entity store (`EntityStore`)
20
+ - Built-in cache strategies (TTL, prefix grouping)
21
+ - Deterministic request lifecycle: loading / error / data
22
+ - Optional transport-level: dedupe, debounce/throttle, abort
23
+ - Unified retry and trace hooks in `ResourceStore`
24
+ - Explicit serialization / deserialization boundary
23
25
 
24
26
  ---
25
27
 
@@ -33,9 +35,9 @@ npm install @reforgium/statum
33
35
 
34
36
  ## Package Structure
35
37
 
36
- - **Cache** storage strategies for caching
37
- - **Stores** reusable state containers over HttpClient
38
- - **Serializer** configurable data transformation utility
38
+ - **Cache** - storage strategies for caching
39
+ - **Stores** - reusable state containers over HttpClient
40
+ - **Serializer** - configurable data transformation utility
39
41
 
40
42
  ---
41
43
 
@@ -47,10 +49,10 @@ npm install @reforgium/statum
47
49
 
48
50
  Available strategies:
49
51
 
50
- - `persist` browser localStorage
51
- - `session` browser sessionStorage
52
- - `memory` in-memory storage
53
- - `lru` size-limited in-memory cache
52
+ - `persist` - browser localStorage
53
+ - `session` - browser sessionStorage
54
+ - `memory` - in-memory storage
55
+ - `lru` - size-limited in-memory cache
54
56
 
55
57
  ### StorageInterface
56
58
 
@@ -67,7 +69,7 @@ interface StorageInterface<T> {
67
69
  }
68
70
  ```
69
71
 
70
- Example:
72
+ Example:
71
73
 
72
74
  ```ts
73
75
  import { cacheStrategy } from '@reforgium/statum';
@@ -89,7 +91,7 @@ Transport-level store over HttpClient with cache strategies, deduplication, abor
89
91
 
90
92
  ### When to use
91
93
 
92
- - CRUD and resource endpoints
94
+ - CRUD and "resource" endpoints
93
95
  - Centralized debounce/throttle
94
96
  - Abort in-flight / delayed requests (latest-wins)
95
97
  - Dedupe identical calls to one HTTP request
@@ -97,24 +99,24 @@ Transport-level store over HttpClient with cache strategies, deduplication, abor
97
99
 
98
100
  ### Signals
99
101
 
100
- | Signal | Type | Description |
101
- |---------|-------------------|---------------------|
102
- | value | `Signal<T | null>` | Last successful value |
103
- | status | `'idle' | 'loading' | 'success' | 'error' | 'stale'` | Resource status |
104
- | error | `Signal<unknown | null>` | Last error |
105
- | loading | `Signal<boolean>` | Request in progress |
102
+ | Signal | Type | Description |
103
+ |---------|---------------------------|-------------------------|
104
+ | value | `Signal<T \| null>` | Last successful value |
105
+ | status | `Signal<ResourceStatus>` | Resource status |
106
+ | error | `Signal<unknown \| null>` | Last error |
107
+ | loading | `Signal<boolean>` | Request in progress |
106
108
 
107
109
  ### Methods
108
110
 
109
- | Method | Description |
110
- |------|-------------|
111
- | get | GET request |
112
- | post | POST request |
113
- | put | PUT request |
114
- | patch | PATCH request |
115
- | delete | DELETE request |
116
- | abort | Abort a specific request |
117
- | abortAll | Abort all requests |
111
+ | Method | Description |
112
+ |----------|--------------------------|
113
+ | get | GET request |
114
+ | post | POST request |
115
+ | put | PUT request |
116
+ | patch | PATCH request |
117
+ | delete | DELETE request |
118
+ | abort | Abort a specific request |
119
+ | abortAll | Abort all requests |
118
120
 
119
121
  Example:
120
122
 
@@ -128,17 +130,58 @@ const userStore = new ResourceStore<User>(
128
130
  { baseUrl: '/api', ttlMs: 60_000 }
129
131
  );
130
132
 
131
- const user = await userStore.get(
132
- { params: { id: '42' }, query: {} },
133
- { dedupe: true }
134
- );
135
- ```
136
-
137
- ---
138
-
139
- ## PaginatedDataStore
140
-
141
- Lightweight store for server-side pagination with sorting, filtering and page cache.
133
+ const user = await userStore.get(
134
+ { params: { id: '42' }, query: {} },
135
+ { dedupe: true }
136
+ );
137
+ ```
138
+
139
+ Retry + trace example:
140
+
141
+ ```ts
142
+ import { ResourceStore } from '@reforgium/statum';
143
+
144
+ const store = new ResourceStore<{ id: number; name: string }>(
145
+ { GET: '/users/:id' },
146
+ {
147
+ baseUrl: '/api',
148
+ retry: { attempts: 2, delayMs: 150, backoff: 'exponential' },
149
+ onTrace: (event) => console.debug('[resource-trace]', event),
150
+ }
151
+ );
152
+
153
+ await store.get(
154
+ { params: { id: '42' } },
155
+ { retry: { attempts: 1 } } // per-call override
156
+ );
157
+ ```
158
+
159
+ ---
160
+
161
+ ## EntityStore
162
+
163
+ Normalized entities store (`byId` + `ids`) for fast updates and stable list composition.
164
+
165
+ Example:
166
+
167
+ ```ts
168
+ import { EntityStore } from '@reforgium/statum';
169
+
170
+ type User = { id: number; name: string };
171
+
172
+ const entities = new EntityStore<User, 'id'>({ idKey: 'id' });
173
+
174
+ entities.setAll([{ id: 1, name: 'Neo' }]);
175
+ entities.upsertOne({ id: 2, name: 'Trinity' });
176
+ entities.patchOne(2, { name: 'Trin' });
177
+ entities.removeOne(1);
178
+ ```
179
+
180
+ ---
181
+
182
+ ## PaginatedDataStore
183
+
184
+ Lightweight store for server-side pagination with sorting, filtering, and page cache.
142
185
 
143
186
  ### When to use
144
187
 
@@ -149,16 +192,16 @@ Lightweight store for server-side pagination with sorting, filtering and page ca
149
192
 
150
193
  ### State
151
194
 
152
- | Field / Signal | Type | Description |
153
- |----------------|---------------------------|---------------------------------|
154
- | items | `WritableSignal<T[]>` | Current page items |
155
- | cached | `WritableSignal<T[]>` | Flattened cache of cached pages |
156
- | loading | `WritableSignal<boolean>` | Loading indicator |
157
- | page | `number` | Current page (0-based) |
158
- | pageSize | `number` | Page size |
159
- | totalElements | `number` | Total items on server |
160
- | filters | `Partial<F>` | Active filters |
161
- | sort | `string \| undefined` | Sort as `"field,asc |
195
+ | Field / Signal | Type | Description |
196
+ |----------------|---------------------------|----------------------------------------|
197
+ | items | `WritableSignal<T[]>` | Current page items |
198
+ | cached | `WritableSignal<T[]>` | Flattened cache of cached pages |
199
+ | loading | `WritableSignal<boolean>` | Loading indicator |
200
+ | page | `number` | Current page (0-based) |
201
+ | pageSize | `number` | Page size |
202
+ | totalElements | `number` | Total items on server |
203
+ | filters | `Partial<F>` | Active filters |
204
+ | sort | `string \| undefined` | Sort as `"field,asc"` / `"field,desc"` |
162
205
 
163
206
  ### Methods
164
207
 
@@ -208,8 +251,8 @@ Helper for dictionaries/options (select/autocomplete) on top of PaginatedDataSto
208
251
  ### Key behavior
209
252
 
210
253
  - Two modes:
211
- - `fixed: true` first load fills local cache; search happens locally
212
- - `fixed: false` search goes to server (passes filter `name`)
254
+ - `fixed: true` - first load fills local cache; search happens locally
255
+ - `fixed: false` - search goes to server (passes filter `name`)
213
256
  - Local cache merges without duplicates and is size-limited
214
257
 
215
258
  ### Public API
@@ -233,17 +276,17 @@ Methods:
233
276
 
234
277
  Config:
235
278
 
236
- | Option | Type | Default | Description |
237
- |---------------|-----------------------------------------------|------------|---------------------------------------------|
238
- | labelKey | `string` | `'label'` | Property name to use as option label |
239
- | valueKey | `string` | `'value'` | Property name to use as option value |
240
- | fixed | `boolean` | `false` | Use local search instead of server requests |
241
- | method | `'GET' \| 'POST'` | `'GET'` | HTTP method for requests |
242
- | cacheKey | `string \| null` | `null` | Storage key for persisting cache |
243
- | debounceTime | `number` | `200` | Debounce time for search requests |
244
- | pageSize | `number` | `50` | Page size for server requests |
245
- | cacheLimit | `number` | `1000` | Maximum items in local cache |
246
- | cacheStrategy | `'persist' \| 'session' \| 'memory' \| 'lru'` | `'memory'` | Cache storage strategy |
279
+ | Option | Type | Default | Description |
280
+ |----------------|--------------------------------------|-------------|---------------------------------------------|
281
+ | labelKey | `string` | `'name'` | Property name to use as option label |
282
+ | valueKey | `string` | `'code'` | Property name to use as option value |
283
+ | fixed | `boolean` | `true` | Use local search instead of server requests |
284
+ | method | `'GET' \| 'POST'` | `'POST'` | HTTP method for requests |
285
+ | debounceTime | `number` | `0` | Debounce time for search requests |
286
+ | maxOptionsSize | `number` | `undefined` | Maximum items exposed in `options` |
287
+ | cacheStrategy | `'persist' \| 'session' \| 'memory'` | `'persist'` | Cache storage strategy |
288
+ | keyPrefix | `string` | `'re'` | Prefix for storage keys |
289
+ | presetFilters | `Record<string, string>` | `{}` | Filters merged into each request |
247
290
 
248
291
  Example:
249
292
 
@@ -259,16 +302,38 @@ const dict = new DictStore<Country>('api/dictionaries/countries', 'countries', {
259
302
  });
260
303
 
261
304
  dict.search(''); // initial fill
262
- dict.search('kir'); // local search over cache
263
- ```
264
-
265
- ---
266
-
267
- ## Serializer
305
+ dict.search('kir'); // local search over cache
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Composition Patterns
311
+
312
+ ### PaginatedDataStore + EntityStore
313
+
314
+ ```ts
315
+ import { effect } from '@angular/core';
316
+ import { EntityStore, PaginatedDataStore } from '@reforgium/statum';
317
+
318
+ type User = { id: number; name: string };
319
+
320
+ const pages = new PaginatedDataStore<User, { search?: string }>('/api/users');
321
+ const entities = new EntityStore<User, 'id'>({ idKey: 'id' });
322
+
323
+ effect(() => {
324
+ entities.upsertMany(pages.items());
325
+ });
326
+ ```
327
+
328
+ This keeps page-loading logic in `PaginatedDataStore` and normalized lookup/update logic in `EntityStore`.
329
+
330
+ ---
331
+
332
+ ## Serializer
268
333
 
269
334
  ### Serializer
270
335
 
271
- Utility for serialization/deserialization between layers (UI API, objects query string).
336
+ Utility for serialization/deserialization between layers (UI -> API, objects -> query string).
272
337
 
273
338
  Core API:
274
339