flurryx 0.6.1 → 0.7.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 +93 -12
- 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)
|
|
@@ -143,18 +145,20 @@ npm install @flurryx/core @flurryx/store @flurryx/rx
|
|
|
143
145
|
|
|
144
146
|
### Step 1 — Define your store
|
|
145
147
|
|
|
146
|
-
|
|
148
|
+
Define a TypeScript interface mapping slot names to their data types, then pass it to the `Store` builder:
|
|
147
149
|
|
|
148
150
|
```typescript
|
|
149
151
|
import { Store } from "flurryx";
|
|
150
152
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
interface ProductStoreConfig {
|
|
154
|
+
LIST: Product[];
|
|
155
|
+
DETAIL: Product;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const ProductStore = Store.for<ProductStoreConfig>().build();
|
|
155
159
|
```
|
|
156
160
|
|
|
157
|
-
That's it.
|
|
161
|
+
That's it. The interface is type-only — zero runtime cost. The builder returns an `InjectionToken` with `providedIn: 'root'`. Every call to `store.get('LIST')` returns `WritableSignal<ResourceState<Product[]>>`, and invalid keys or mismatched types are caught at compile time.
|
|
158
162
|
|
|
159
163
|
### Step 2 — Create a facade
|
|
160
164
|
|
|
@@ -252,13 +256,27 @@ A slot starts as `{ data: undefined, isLoading: false, status: undefined, errors
|
|
|
252
256
|
|
|
253
257
|
### Store API
|
|
254
258
|
|
|
255
|
-
The `Store` builder creates a store backed by `WritableSignal<ResourceState>` per slot.
|
|
259
|
+
The `Store` builder creates a store backed by `WritableSignal<ResourceState>` per slot. Three creation styles are available:
|
|
256
260
|
|
|
257
261
|
```typescript
|
|
262
|
+
// 1. Interface-based (recommended) — type-safe with zero boilerplate
|
|
263
|
+
interface MyStoreConfig {
|
|
264
|
+
USERS: User[];
|
|
265
|
+
SELECTED: User;
|
|
266
|
+
}
|
|
267
|
+
export const MyStore = Store.for<MyStoreConfig>().build();
|
|
268
|
+
|
|
269
|
+
// 2. Fluent chaining — inline slot definitions
|
|
258
270
|
export const MyStore = Store
|
|
259
271
|
.resource('USERS').as<User[]>()
|
|
260
272
|
.resource('SELECTED').as<User>()
|
|
261
273
|
.build();
|
|
274
|
+
|
|
275
|
+
// 3. Enum-constrained — validates keys against a runtime enum
|
|
276
|
+
export const MyStore = Store.for(MyStoreEnum)
|
|
277
|
+
.resource('USERS').as<User[]>()
|
|
278
|
+
.resource('SELECTED').as<User>()
|
|
279
|
+
.build();
|
|
262
280
|
```
|
|
263
281
|
|
|
264
282
|
Once injected, the store exposes these methods:
|
|
@@ -283,6 +301,69 @@ Once injected, the store exposes these methods:
|
|
|
283
301
|
|
|
284
302
|
> Update hooks are stored in a `WeakMap` keyed by store instance, so garbage collection works naturally across multiple store lifetimes.
|
|
285
303
|
|
|
304
|
+
### Store Creation Styles
|
|
305
|
+
|
|
306
|
+
#### Interface-based: `Store.for<Config>().build()`
|
|
307
|
+
|
|
308
|
+
The recommended approach. Define a TypeScript interface where keys are slot names and values are the data types:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import { Store } from "flurryx";
|
|
312
|
+
|
|
313
|
+
interface ChatStoreConfig {
|
|
314
|
+
SESSIONS: ChatSession[];
|
|
315
|
+
CURRENT_SESSION: ChatSession;
|
|
316
|
+
MESSAGES: ChatMessage[];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export const ChatStore = Store.for<ChatStoreConfig>().build();
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
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.
|
|
323
|
+
|
|
324
|
+
Type safety is fully enforced:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
const store = inject(ChatStore);
|
|
328
|
+
|
|
329
|
+
store.get('SESSIONS'); // WritableSignal<ResourceState<ChatSession[]>>
|
|
330
|
+
store.update('SESSIONS', { data: [session] }); // ✅ type-checked
|
|
331
|
+
store.update('SESSIONS', { data: 42 }); // ❌ TS error — number is not ChatSession[]
|
|
332
|
+
store.get('INVALID'); // ❌ TS error — key does not exist
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### Fluent chaining: `Store.resource().as<T>().build()`
|
|
336
|
+
|
|
337
|
+
Define slots inline without a separate interface:
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
export const ChatStore = Store
|
|
341
|
+
.resource('SESSIONS').as<ChatSession[]>()
|
|
342
|
+
.resource('CURRENT_SESSION').as<ChatSession>()
|
|
343
|
+
.resource('MESSAGES').as<ChatMessage[]>()
|
|
344
|
+
.build();
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### Enum-constrained: `Store.for(enum).resource().as<T>().build()`
|
|
348
|
+
|
|
349
|
+
When you have a runtime enum (e.g. shared with backend code), pass it to `.for()` to ensure every key is accounted for:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
const ChatStoreEnum = {
|
|
353
|
+
SESSIONS: 'SESSIONS',
|
|
354
|
+
CURRENT_SESSION: 'CURRENT_SESSION',
|
|
355
|
+
MESSAGES: 'MESSAGES',
|
|
356
|
+
} as const;
|
|
357
|
+
|
|
358
|
+
export const ChatStore = Store.for(ChatStoreEnum)
|
|
359
|
+
.resource('SESSIONS').as<ChatSession[]>()
|
|
360
|
+
.resource('CURRENT_SESSION').as<ChatSession>()
|
|
361
|
+
.resource('MESSAGES').as<ChatMessage[]>()
|
|
362
|
+
.build();
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The builder only allows keys from the enum, and `.build()` is only available once all keys have been defined.
|
|
366
|
+
|
|
286
367
|
### syncToStore
|
|
287
368
|
|
|
288
369
|
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.
|
|
3
|
+
"version": "0.7.0",
|
|
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.
|
|
42
|
-
"@flurryx/store": "0.
|
|
43
|
-
"@flurryx/rx": "0.
|
|
41
|
+
"@flurryx/core": "0.7.0",
|
|
42
|
+
"@flurryx/store": "0.7.0",
|
|
43
|
+
"@flurryx/rx": "0.7.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@angular/core": ">=17.0.0",
|