flurryx 0.6.1 → 0.6.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/README.md +121 -7
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -21,11 +21,12 @@
|
|
|
21
21
|
flurryx bridges the gap between RxJS async operations and Angular signals. Define a store, pipe your HTTP calls through an operator, read signals in your templates. No actions, no reducers, no effects boilerplate.
|
|
22
22
|
|
|
23
23
|
```typescript
|
|
24
|
-
// Store — declare your slots
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
// Store — declare your slots with a single interface
|
|
25
|
+
interface ProductStoreConfig {
|
|
26
|
+
LIST: Product[];
|
|
27
|
+
DETAIL: Product;
|
|
28
|
+
}
|
|
29
|
+
export const ProductStore = Store.for<ProductStoreConfig>().build();
|
|
29
30
|
|
|
30
31
|
// Facade
|
|
31
32
|
@SkipIfCached('LIST', (i) => i.store)
|
|
@@ -56,6 +57,7 @@ No `async` pipe. No `subscribe` in templates. No manual unsubscription.
|
|
|
56
57
|
- [How to Use](#how-to-use)
|
|
57
58
|
- [ResourceState](#resourcestate)
|
|
58
59
|
- [Store API](#store-api)
|
|
60
|
+
- [Store Creation Styles](#store-creation-styles)
|
|
59
61
|
- [syncToStore](#synctostore)
|
|
60
62
|
- [syncToKeyedStore](#synctokeyedstore)
|
|
61
63
|
- [@SkipIfCached](#skipifcached)
|
|
@@ -145,16 +147,51 @@ npm install @flurryx/core @flurryx/store @flurryx/rx
|
|
|
145
147
|
|
|
146
148
|
Use the `Store` fluent builder to declare named slots. Each slot holds a `ResourceState<T>`.
|
|
147
149
|
|
|
150
|
+
**Option A — Interface-based (recommended)**
|
|
151
|
+
|
|
152
|
+
Define a TypeScript interface mapping keys to data types, pass it as a generic:
|
|
153
|
+
|
|
148
154
|
```typescript
|
|
149
155
|
import { Store } from "flurryx";
|
|
150
156
|
|
|
157
|
+
interface ProductStoreConfig {
|
|
158
|
+
LIST: Product[];
|
|
159
|
+
DETAIL: Product;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const ProductStore = Store.for<ProductStoreConfig>().build();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
No enum, no chaining, no class. The interface is type-only — zero runtime cost. Every call to `store.get('LIST')` returns `WritableSignal<ResourceState<Product[]>>`, and invalid keys or mismatched types are caught at compile time.
|
|
166
|
+
|
|
167
|
+
**Option B — Fluent chaining**
|
|
168
|
+
|
|
169
|
+
Declare each slot inline:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
151
172
|
export const ProductStore = Store
|
|
152
173
|
.resource('LIST').as<Product[]>()
|
|
153
174
|
.resource('DETAIL').as<Product>()
|
|
154
175
|
.build();
|
|
155
176
|
```
|
|
156
177
|
|
|
157
|
-
|
|
178
|
+
**Option C — Enum-constrained**
|
|
179
|
+
|
|
180
|
+
Bind the builder to an enum for compile-time key validation:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const ProductStoreEnum = {
|
|
184
|
+
LIST: 'LIST',
|
|
185
|
+
DETAIL: 'DETAIL',
|
|
186
|
+
} as const;
|
|
187
|
+
|
|
188
|
+
export const ProductStore = Store.for(ProductStoreEnum)
|
|
189
|
+
.resource('LIST').as<Product[]>()
|
|
190
|
+
.resource('DETAIL').as<Product>()
|
|
191
|
+
.build();
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
All three options return an `InjectionToken` with `providedIn: 'root'`.
|
|
158
195
|
|
|
159
196
|
### Step 2 — Create a facade
|
|
160
197
|
|
|
@@ -252,13 +289,27 @@ A slot starts as `{ data: undefined, isLoading: false, status: undefined, errors
|
|
|
252
289
|
|
|
253
290
|
### Store API
|
|
254
291
|
|
|
255
|
-
The `Store` builder creates a store backed by `WritableSignal<ResourceState>` per slot.
|
|
292
|
+
The `Store` builder creates a store backed by `WritableSignal<ResourceState>` per slot. Three creation styles are available:
|
|
256
293
|
|
|
257
294
|
```typescript
|
|
295
|
+
// 1. Interface-based (recommended) — type-safe with zero boilerplate
|
|
296
|
+
interface MyStoreConfig {
|
|
297
|
+
USERS: User[];
|
|
298
|
+
SELECTED: User;
|
|
299
|
+
}
|
|
300
|
+
export const MyStore = Store.for<MyStoreConfig>().build();
|
|
301
|
+
|
|
302
|
+
// 2. Fluent chaining — inline slot definitions
|
|
258
303
|
export const MyStore = Store
|
|
259
304
|
.resource('USERS').as<User[]>()
|
|
260
305
|
.resource('SELECTED').as<User>()
|
|
261
306
|
.build();
|
|
307
|
+
|
|
308
|
+
// 3. Enum-constrained — validates keys against a runtime enum
|
|
309
|
+
export const MyStore = Store.for(MyStoreEnum)
|
|
310
|
+
.resource('USERS').as<User[]>()
|
|
311
|
+
.resource('SELECTED').as<User>()
|
|
312
|
+
.build();
|
|
262
313
|
```
|
|
263
314
|
|
|
264
315
|
Once injected, the store exposes these methods:
|
|
@@ -283,6 +334,69 @@ Once injected, the store exposes these methods:
|
|
|
283
334
|
|
|
284
335
|
> Update hooks are stored in a `WeakMap` keyed by store instance, so garbage collection works naturally across multiple store lifetimes.
|
|
285
336
|
|
|
337
|
+
### Store Creation Styles
|
|
338
|
+
|
|
339
|
+
#### Interface-based: `Store.for<Config>().build()`
|
|
340
|
+
|
|
341
|
+
The recommended approach. Define a TypeScript interface where keys are slot names and values are the data types:
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { Store } from "flurryx";
|
|
345
|
+
|
|
346
|
+
interface ChatStoreConfig {
|
|
347
|
+
SESSIONS: ChatSession[];
|
|
348
|
+
CURRENT_SESSION: ChatSession;
|
|
349
|
+
MESSAGES: ChatMessage[];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export const ChatStore = Store.for<ChatStoreConfig>().build();
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The generic argument is type-only — there is no runtime enum or config object. Under the hood, the store lazily creates signals on first access, so un-accessed keys have zero overhead.
|
|
356
|
+
|
|
357
|
+
Type safety is fully enforced:
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
const store = inject(ChatStore);
|
|
361
|
+
|
|
362
|
+
store.get('SESSIONS'); // WritableSignal<ResourceState<ChatSession[]>>
|
|
363
|
+
store.update('SESSIONS', { data: [session] }); // ✅ type-checked
|
|
364
|
+
store.update('SESSIONS', { data: 42 }); // ❌ TS error — number is not ChatSession[]
|
|
365
|
+
store.get('INVALID'); // ❌ TS error — key does not exist
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### Fluent chaining: `Store.resource().as<T>().build()`
|
|
369
|
+
|
|
370
|
+
Define slots inline without a separate interface:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
export const ChatStore = Store
|
|
374
|
+
.resource('SESSIONS').as<ChatSession[]>()
|
|
375
|
+
.resource('CURRENT_SESSION').as<ChatSession>()
|
|
376
|
+
.resource('MESSAGES').as<ChatMessage[]>()
|
|
377
|
+
.build();
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### Enum-constrained: `Store.for(enum).resource().as<T>().build()`
|
|
381
|
+
|
|
382
|
+
When you have a runtime enum (e.g. shared with backend code), pass it to `.for()` to ensure every key is accounted for:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
const ChatStoreEnum = {
|
|
386
|
+
SESSIONS: 'SESSIONS',
|
|
387
|
+
CURRENT_SESSION: 'CURRENT_SESSION',
|
|
388
|
+
MESSAGES: 'MESSAGES',
|
|
389
|
+
} as const;
|
|
390
|
+
|
|
391
|
+
export const ChatStore = Store.for(ChatStoreEnum)
|
|
392
|
+
.resource('SESSIONS').as<ChatSession[]>()
|
|
393
|
+
.resource('CURRENT_SESSION').as<ChatSession>()
|
|
394
|
+
.resource('MESSAGES').as<ChatMessage[]>()
|
|
395
|
+
.build();
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
The builder only allows keys from the enum, and `.build()` is only available once all keys have been defined.
|
|
399
|
+
|
|
286
400
|
### syncToStore
|
|
287
401
|
|
|
288
402
|
RxJS pipeable operator that bridges an `Observable` to a store slot.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flurryx",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Signal-first reactive state management for Angular",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
},
|
|
39
39
|
"sideEffects": false,
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flurryx/core": "0.6.
|
|
42
|
-
"@flurryx/store": "0.6.
|
|
43
|
-
"@flurryx/rx": "0.6.
|
|
41
|
+
"@flurryx/core": "0.6.2",
|
|
42
|
+
"@flurryx/store": "0.6.2",
|
|
43
|
+
"@flurryx/rx": "0.6.2"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@angular/core": ">=17.0.0",
|