@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 +139 -74
- package/fesm2022/reforgium-statum.mjs +561 -95
- package/fesm2022/reforgium-statum.mjs.map +1 -1
- package/package.json +1 -1
- package/types/reforgium-statum.d.ts +90 -9
- package/types/reforgium-statum.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@reforgium/statum)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
**Signals-first state stores and caching utilities for Angular (
|
|
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
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
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**
|
|
37
|
-
- **Stores**
|
|
38
|
-
- **Serializer**
|
|
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`
|
|
51
|
-
- `session`
|
|
52
|
-
- `memory`
|
|
53
|
-
- `lru`
|
|
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
|
|
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
|
|
101
|
-
|
|
102
|
-
| value | `Signal<T
|
|
103
|
-
| status | `
|
|
104
|
-
| error | `Signal<unknown
|
|
105
|
-
| loading | `Signal<boolean>`
|
|
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
|
|
110
|
-
|
|
111
|
-
| get
|
|
112
|
-
| post
|
|
113
|
-
| put
|
|
114
|
-
| patch
|
|
115
|
-
| delete
|
|
116
|
-
| abort
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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`
|
|
212
|
-
- `fixed: false`
|
|
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
|
|
237
|
-
|
|
238
|
-
| labelKey
|
|
239
|
-
| valueKey
|
|
240
|
-
| fixed
|
|
241
|
-
| method
|
|
242
|
-
|
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
|
|
|
246
|
-
|
|
|
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
|
-
##
|
|
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
|
|
336
|
+
Utility for serialization/deserialization between layers (UI -> API, objects -> query string).
|
|
272
337
|
|
|
273
338
|
Core API:
|
|
274
339
|
|