@microphi/store 2.11.1 → 2.11.4
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 +120 -172
- package/lib/cjs/lib/operators/cache.d.ts +2 -18
- package/lib/cjs/lib/operators/cache.d.ts.map +1 -1
- package/lib/cjs/lib/operators/cache.js +6 -37
- package/lib/cjs/lib/reduce/reduce.js +1 -1
- package/lib/cjs/lib/store/store.d.ts +3 -6
- package/lib/cjs/lib/store/store.d.ts.map +1 -1
- package/lib/cjs/lib/store/store.js +1 -1
- package/lib/cjs/lib/store/types.d.ts.map +1 -1
- package/lib/cjs/lib/utilities/scan-instance.js +1 -1
- package/lib/esm/lib/operators/cache.d.ts +2 -18
- package/lib/esm/lib/operators/cache.d.ts.map +1 -1
- package/lib/esm/lib/operators/cache.js +3 -35
- package/lib/esm/lib/reduce/reduce.js +1 -1
- package/lib/esm/lib/store/store.d.ts +3 -6
- package/lib/esm/lib/store/store.d.ts.map +1 -1
- package/lib/esm/lib/store/store.js +1 -1
- package/lib/esm/lib/store/types.d.ts.map +1 -1
- package/lib/esm/lib/utilities/scan-instance.js +1 -1
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/package.json +19 -9
package/README.md
CHANGED
|
@@ -1,252 +1,200 @@
|
|
|
1
|
-
# @microphi/store
|
|
1
|
+
# @microphi/store [](https://www.npmjs.com/package/@microphi/store)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Pure RxJS state manager driven by `@Effect` / `@Reduce` decorators
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm i @microphi/store @microphi/di rxjs reflect-metadata
|
|
7
9
|
```
|
|
8
|
-
|
|
10
|
+
|
|
11
|
+
Enable legacy decorators in `tsconfig.json`:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"compilerOptions": {
|
|
16
|
+
"experimentalDecorators": true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
9
19
|
```
|
|
10
20
|
|
|
21
|
+
## How to use
|
|
22
|
+
|
|
11
23
|
### Define the state
|
|
24
|
+
|
|
12
25
|
```typescript
|
|
13
|
-
interface
|
|
26
|
+
interface UsersState {
|
|
14
27
|
users: string[];
|
|
15
|
-
selected?:
|
|
28
|
+
selected?: string;
|
|
16
29
|
}
|
|
17
30
|
```
|
|
18
31
|
|
|
19
32
|
### Define the actions
|
|
33
|
+
|
|
34
|
+
Every action returns an `Observable`; its arguments are what `dispatch` takes.
|
|
35
|
+
|
|
20
36
|
```typescript
|
|
21
|
-
interface
|
|
22
|
-
// An action MUST always return an Observable
|
|
37
|
+
interface UsersActions {
|
|
23
38
|
findAll: () => Observable<string[]>;
|
|
24
|
-
|
|
39
|
+
findOne: (id: string) => Observable<string>;
|
|
25
40
|
}
|
|
26
41
|
```
|
|
27
|
-
|
|
42
|
+
|
|
43
|
+
### Implement effects and reducers
|
|
44
|
+
|
|
45
|
+
For each action the store needs an `@Effect()` method named like the action and an `@Reduce()` method named `on` + `PascalCase(action)`. A missing one throws when the store is constructed.
|
|
28
46
|
|
|
29
47
|
```typescript
|
|
30
|
-
import {
|
|
48
|
+
import { Observable, of } from 'rxjs';
|
|
49
|
+
import { Effect, makeStore, Reduce, Store } from '@microphi/store';
|
|
31
50
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// -> optionally implement `makeStore` to add extra type checking
|
|
51
|
+
class UsersStore extends Store<UsersState, UsersActions>
|
|
52
|
+
// optional: type-checks that every effect and reducer is implemented
|
|
53
|
+
implements makeStore<UsersState, UsersActions> {
|
|
36
54
|
|
|
37
55
|
constructor() {
|
|
38
|
-
//
|
|
39
|
-
super({
|
|
40
|
-
users: ['alice', 'bob'],
|
|
41
|
-
});
|
|
56
|
+
// initial state, must be serializable: each instance gets its own deep copy
|
|
57
|
+
super({ users: ['alice', 'bob'] });
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
@Effect()
|
|
45
|
-
// -> Implement `findAll` method
|
|
46
61
|
findAll(): Observable<string[]> {
|
|
47
|
-
|
|
48
|
-
return of(['alice', 'bob', 'carl', 'denise']);
|
|
62
|
+
return of(['carl', 'denise']);
|
|
49
63
|
}
|
|
50
64
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
// receives the current state and the value emitted by the effect
|
|
66
|
+
@Reduce()
|
|
67
|
+
onFindAll(state: UsersState, users: string[]): UsersState {
|
|
68
|
+
return { ...state, users: [...state.users, ...users] };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@Effect()
|
|
72
|
+
findOne(id: string): Observable<string> {
|
|
73
|
+
return fromFetch(`https://my.endpoint/users/${id}`).pipe(switchMap((r) => r.json()));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Reduce()
|
|
77
|
+
onFindOne(state: UsersState, selected: string): UsersState {
|
|
78
|
+
return { ...state, selected };
|
|
62
79
|
}
|
|
63
80
|
}
|
|
64
81
|
```
|
|
65
82
|
|
|
66
|
-
### Dispatch and select
|
|
83
|
+
### Dispatch and select
|
|
84
|
+
|
|
67
85
|
```typescript
|
|
68
|
-
|
|
69
|
-
const store = new MyStore();
|
|
86
|
+
const store = new UsersStore();
|
|
70
87
|
|
|
71
|
-
//
|
|
72
|
-
store.state$.subscribe((state) => console.log(
|
|
88
|
+
// every state change
|
|
89
|
+
store.state$.subscribe((state) => console.log(state));
|
|
73
90
|
|
|
74
|
-
// or
|
|
75
|
-
store.select(({users}) => users).subscribe((users) => console.log(
|
|
91
|
+
// or a projection, emitted only when it changes
|
|
92
|
+
store.select(({ users }) => users).subscribe((users) => console.log(users));
|
|
76
93
|
|
|
77
|
-
// finally dispatch an action
|
|
78
94
|
store.dispatch('findAll');
|
|
79
|
-
|
|
95
|
+
store.dispatch('findOne', 'alice'); // arguments are type-checked against UsersActions
|
|
80
96
|
```
|
|
81
97
|
|
|
82
|
-
|
|
83
|
-
We assume every action to be an asyncronious task so every action as a loading state associated to it. Loading state can we subscribed such as:
|
|
84
|
-
```typescript
|
|
85
|
-
store.getLoadingFor('findAll').subscribe((loading) => {
|
|
86
|
-
|
|
87
|
-
console.log({loading});
|
|
98
|
+
### Loading and errors
|
|
88
99
|
|
|
89
|
-
|
|
100
|
+
Every action is treated as async, so each has a loading state:
|
|
90
101
|
|
|
102
|
+
```typescript
|
|
103
|
+
store.getLoadingFor('findOne').subscribe((loading) => console.log({ loading }));
|
|
91
104
|
```
|
|
92
105
|
|
|
93
|
-
|
|
94
|
-
We assume that an action always execute an async effect. Internally such effect is wrapped in a RxJS pipe, as such there are several way this can be done.
|
|
95
|
-
So for example when we dispatch an action such as `findOne` let's say its effect is a call to and endpoint to retrieve an entity by its id.
|
|
96
|
-
Dependending on the scenario we may want to `switchMap`, `mergeMap` or `concatMap`.
|
|
106
|
+
`loading$` carries the full event: `{ code, payload, status, response?, error? }`.
|
|
97
107
|
|
|
98
|
-
|
|
108
|
+
If an effect errors, the error is logged and emitted on `loading$` (with `status: false` and `error`), the state is left untouched and the action can be dispatched again.
|
|
99
109
|
|
|
100
|
-
|
|
110
|
+
### Effects that emit more than once
|
|
101
111
|
|
|
102
|
-
|
|
103
|
-
implements makeStore<ItemsState, ItemsActions> {
|
|
112
|
+
The reducer runs for every value the effect emits, so a long-lived effect (a connection, a socket) keeps updating the state after the dispatch that started it.
|
|
104
113
|
|
|
105
|
-
|
|
106
|
-
super({
|
|
107
|
-
users: ['alice', 'bob'],
|
|
108
|
-
selected: undefined,
|
|
114
|
+
## Strategies
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
}
|
|
116
|
+
When an action is dispatched again while its previous effect is still running, the strategy passed to `@Effect` decides what happens:
|
|
112
117
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
| Strategy | Behaviour |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `switchMap` (default) | the pending effect is cancelled, only the latest one reduces |
|
|
121
|
+
| `mergeMap` | effects run in parallel, each reduces as it completes |
|
|
122
|
+
| `concatMap` | effects run one after the other, in dispatch order |
|
|
117
123
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
selected,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
124
|
+
```typescript
|
|
125
|
+
@Effect('mergeMap')
|
|
126
|
+
findOne(id: string): Observable<string> {
|
|
127
|
+
return fromFetch(`https://my.endpoint/users/${id}`).pipe(switchMap((r) => r.json()));
|
|
125
128
|
}
|
|
126
129
|
```
|
|
127
130
|
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
## Operators
|
|
132
|
+
|
|
133
|
+
### `@Cache`
|
|
130
134
|
|
|
131
|
-
|
|
132
|
-
It caches the value returned from the effect (i.e.: its observable) for 300_000 (default) milliseconds (i.e.: 5 mins) or the amount of milliseconds provided in the `ttl` field.
|
|
133
|
-
The effect will be cached according to its arguments. I.e.: like a memoized function `@Cache` will get check arguments and cache their result separately.
|
|
135
|
+
Caches the Observable an effect returns, per store instance and per arguments, so a cached dispatch does not call the effect method again. The Observable itself is resubscribed: a cold one (like `fromFetch`) re-runs its request unless it is shared, e.g. with `shareReplay(1)`.
|
|
134
136
|
|
|
135
|
-
Optionally a `trackBy` function can be provided in order to customized how arguments are considered for caching.
|
|
136
|
-
If not provided `JSON.stringify` is used to serialize the arguments.
|
|
137
|
-
If provided `trackBy` will have the exact same arguments of the effect being annotated.
|
|
138
137
|
```typescript
|
|
138
|
+
@Effect('mergeMap')
|
|
139
|
+
@Cache({ ttl: 1_000, trackBy: (id: string) => id.toLowerCase() })
|
|
140
|
+
findOne(id: string): Observable<string> {
|
|
141
|
+
return fromFetch(`https://my.endpoint/users/${id}`).pipe(
|
|
142
|
+
switchMap((r) => r.json()),
|
|
143
|
+
shareReplay(1),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
139
147
|
|
|
140
|
-
|
|
141
|
-
|
|
148
|
+
- `ttl`: time to live in ms, default 5 minutes
|
|
149
|
+
- `trackBy`: builds the cache key from the effect's arguments, default `JSON.stringify(args)`
|
|
142
150
|
|
|
143
|
-
|
|
144
|
-
super({
|
|
145
|
-
users: ['alice', 'bob'],
|
|
146
|
-
selected: undefined,
|
|
151
|
+
`@Cache` lives in `@microphi/di` and works on any class; it is re-exported here.
|
|
147
152
|
|
|
148
|
-
|
|
149
|
-
}
|
|
153
|
+
### `@DebounceTime`
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
@Cache({ttl: 1_000, trackBy: (id: string) => id.toLowerCase()})
|
|
153
|
-
findOne(id: string): Observable<string> {
|
|
154
|
-
return fromFetch(`http://my.super.endpoint/${id}`)
|
|
155
|
-
}
|
|
155
|
+
Debounces dispatches of the action, as `debounceTime` in an RxJS pipe:
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
};
|
|
163
|
-
}
|
|
157
|
+
```typescript
|
|
158
|
+
@Effect('switchMap')
|
|
159
|
+
@DebounceTime(300)
|
|
160
|
+
search(query: string): Observable<string[]> {
|
|
161
|
+
return fromFetch(`https://my.endpoint/search?q=${query}`).pipe(switchMap((r) => r.json()));
|
|
164
162
|
}
|
|
165
163
|
```
|
|
166
164
|
|
|
165
|
+
### `@DelayTime`
|
|
167
166
|
|
|
168
|
-
|
|
169
|
-
It will debouce calling the effect as in a RxJS pipe
|
|
167
|
+
Delays each effect's result, as `delay` in an RxJS pipe:
|
|
170
168
|
|
|
171
169
|
```typescript
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
selected: undefined,
|
|
180
|
-
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
@Effect('mergeMap')
|
|
185
|
-
@DebounceTime(300)
|
|
186
|
-
findOne(id: string): Observable<string> {
|
|
187
|
-
return fromFetch(`http://my.super.endpoint/${id}`)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
@Reduce()
|
|
191
|
-
public onFindOne(state: ItemsState, select: string) {
|
|
192
|
-
return {
|
|
193
|
-
...state,
|
|
194
|
-
selected,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
170
|
+
/**
|
|
171
|
+
* with concatMap this rate-limits the effect to one call per 100 ms
|
|
172
|
+
*/
|
|
173
|
+
@Effect('concatMap')
|
|
174
|
+
@DelayTime(100)
|
|
175
|
+
track(id: string) {
|
|
176
|
+
return fromFetch(`https://my.endpoint/track/${id}`);
|
|
197
177
|
}
|
|
198
178
|
```
|
|
199
179
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
class TestClass extends Store<State, Actions> {
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* this effect will be rate limited at one call per 100 ms
|
|
207
|
-
*/
|
|
208
|
-
@Effect('concatMap')
|
|
209
|
-
@DelayTime(100)
|
|
210
|
-
effectWithDelay(id: string) {
|
|
211
|
-
return fromFetch(`http://my.super.endpoint/${id}`)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
@Reduce()
|
|
215
|
-
onEffectWithDelay(state: State, payload: string): State {
|
|
216
|
-
return {...state, items: [...state.items, payload]};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
⚠️ Note the use of `concatMap` strategy. If another strategy is used the effect of `@DelayTime` may be not what really expected.
|
|
221
|
-
Please see [delay.spec.ts](./src/lib/operators/delay.spec.ts) for all possible combination of `strategy` and `@DelayTime`
|
|
180
|
+
⚠️ With strategies other than `concatMap` the result may not be what you expect: see [delay.spec.ts](./src/lib/operators/delay.spec.ts) for every combination.
|
|
181
|
+
|
|
182
|
+
`@DebounceTime` and `@DelayTime` are read by the store's dispatch pipeline, so they only have an effect on a `Store` subclass.
|
|
222
183
|
|
|
223
184
|
## .skills
|
|
185
|
+
|
|
224
186
|
Automation agents can invoke the following skills when working inside `packages/store`.
|
|
225
187
|
|
|
226
188
|
### build-store-lib
|
|
227
189
|
- Goal: emit both ESM and CJS bundles from `src`.
|
|
228
|
-
-
|
|
229
|
-
|
|
230
|
-
2. `npm run build -- --scope @microphi/store` to leverage Lerna caching, or `cd packages/store && npm run build` for isolated builds.
|
|
231
|
-
- Checks: confirm `packages/store/lib/esm/index.js` and `packages/store/lib/cjs/index.js` are regenerated.
|
|
190
|
+
- From the repo root: `npm install` (first run or when deps change), then `npm run build -w packages/di -w packages/store` (store needs di's `lib/` output).
|
|
191
|
+
- Checks: `packages/store/lib/esm/index.js` and `packages/store/lib/cjs/index.js` are regenerated.
|
|
232
192
|
|
|
233
193
|
### test-store-lib
|
|
234
|
-
- Goal:
|
|
235
|
-
-
|
|
236
|
-
|
|
237
|
-
2. `npm run test:ci` (wraps `jest --coverage --ci`).
|
|
238
|
-
- When debugging a single spec use `npx jest src/lib/store/store.spec.ts --runTestsByPath` within this folder.
|
|
194
|
+
- Goal: run the package's vitest suite with coverage, mirroring CI.
|
|
195
|
+
- `cd packages/store && npm run test:ci` (wraps `vitest run --coverage`).
|
|
196
|
+
- Single spec: `npx vitest run src/lib/store/store.spec.ts` within this folder.
|
|
239
197
|
|
|
240
198
|
### lint-store-lib
|
|
241
|
-
- Goal: enforce the repo
|
|
242
|
-
-
|
|
243
|
-
1. `cd packages/store`.
|
|
244
|
-
2. `npm run lint:ci` (runs `eslint ./src`).
|
|
245
|
-
- Fix order: apply autofixes via `npx eslint ./src --fix`, rerun lint, then tests.
|
|
246
|
-
|
|
247
|
-
### release-store-docs
|
|
248
|
-
- Goal: regenerate docs after updating public APIs.
|
|
249
|
-
- Commands:
|
|
250
|
-
1. From repo root `npm run docs -- --scope @microphi/store` if scoped docs exist, otherwise invoke `npm run docs` to rebuild all packages.
|
|
251
|
-
2. Run `npm run postdocs` to refresh aggregated docs.
|
|
252
|
-
- Verify README snippets still compile by running the TypeScript samples inside `examples/` or via `ts-node` scratch files as needed.
|
|
199
|
+
- Goal: enforce the repo's TypeScript + ESLint rules before commits.
|
|
200
|
+
- `cd packages/store && npm run lint` (runs `eslint ./src`); autofix with `npx eslint ./src --fix`, then rerun lint and tests.
|
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
import { Store } from '../store/store';
|
|
2
1
|
import { Observable } from 'rxjs';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export declare const CacheSymbol: unique symbol;
|
|
6
|
-
export interface CacheOptions {
|
|
7
|
-
/**
|
|
8
|
-
* time to live in milliseconds
|
|
9
|
-
*/
|
|
10
|
-
ttl?: number;
|
|
11
|
-
/**
|
|
12
|
-
* This function has the same arguments as the decorated method
|
|
13
|
-
*/
|
|
14
|
-
trackBy?: (...args: any[]) => string | number;
|
|
15
|
-
}
|
|
2
|
+
export { Cache, CacheSymbol } from '@microphi/di';
|
|
3
|
+
export type { CacheEntry, CacheMap, CacheOptions } from '@microphi/di';
|
|
16
4
|
export type EffectMethod = (...args: any[]) => Observable<any>;
|
|
17
|
-
/**
|
|
18
|
-
* Caches an effect's value
|
|
19
|
-
*/
|
|
20
|
-
export declare function Cache(options?: CacheOptions): <S extends Store<any, any>>(_target: S, property: Key, descriptor: TypedPropertyDescriptor<Fn>) => void;
|
|
21
5
|
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/lib/operators/cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/lib/operators/cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAIlC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvE,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,CAAC,GAAG,CAAC,CAAC"}
|
|
@@ -1,39 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function Cache(options) {
|
|
10
|
-
return (_target, property, descriptor) => {
|
|
11
|
-
const originalFn = descriptor.value;
|
|
12
|
-
if (originalFn) {
|
|
13
|
-
descriptor.value = function (...args) {
|
|
14
|
-
const root = this;
|
|
15
|
-
const cache = root[exports.CacheSymbol];
|
|
16
|
-
// this can grow huge
|
|
17
|
-
// shall we put some sort of cap?
|
|
18
|
-
const key = options?.trackBy ? options.trackBy(...args) : JSON.stringify(args);
|
|
19
|
-
const id = `${property}:${key}`;
|
|
20
|
-
const cached = cache.get(id);
|
|
21
|
-
if (cached) {
|
|
22
|
-
// check ttl given timestamp
|
|
23
|
-
const timestamp = cached.timestamp;
|
|
24
|
-
const now = Date.now();
|
|
25
|
-
const ttl = now - timestamp;
|
|
26
|
-
const maxTtl = options?.ttl || 300000; // 5 minutes
|
|
27
|
-
if (ttl <= maxTtl) {
|
|
28
|
-
const value = cached.value;
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
const retValue = originalFn.apply(this, args);
|
|
33
|
-
cache.set(id, { value: retValue, timestamp: Date.now() });
|
|
34
|
-
return retValue;
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
exports.Cache = Cache;
|
|
3
|
+
exports.CacheSymbol = exports.Cache = void 0;
|
|
4
|
+
// `@Cache` lives in `@microphi/di` so that it can be used on any class.
|
|
5
|
+
// It is re-exported here to keep the store's public API unchanged.
|
|
6
|
+
var di_1 = require("@microphi/di");
|
|
7
|
+
Object.defineProperty(exports, "Cache", { enumerable: true, get: function () { return di_1.Cache; } });
|
|
8
|
+
Object.defineProperty(exports, "CacheSymbol", { enumerable: true, get: function () { return di_1.CacheSymbol; } });
|
|
@@ -5,7 +5,7 @@ require("reflect-metadata");
|
|
|
5
5
|
const scan_instance_1 = require("../utilities/scan-instance");
|
|
6
6
|
exports.ReduceSymbol = Symbol('@Reduce');
|
|
7
7
|
function Reduce() {
|
|
8
|
-
return (target, propertyKey
|
|
8
|
+
return (target, propertyKey) => {
|
|
9
9
|
// by convention a reducer have name such as onFindAll
|
|
10
10
|
// where findAll is the associated action
|
|
11
11
|
// with the following we extract the action's name
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { BehaviorSubject, Observable, SchedulerLike } from 'rxjs';
|
|
2
2
|
import { Effect } from '../effect/effect';
|
|
3
|
-
import { CacheSymbol } from '../operators/cache';
|
|
3
|
+
import { CacheMap, CacheSymbol } from '../operators/cache';
|
|
4
4
|
import { Reducer } from '../reduce/reduce';
|
|
5
5
|
import { getPayloadFromActionType, LoadingState } from './types';
|
|
6
6
|
export declare abstract class Store<State, A> {
|
|
7
7
|
private initialState;
|
|
8
|
-
protected [CacheSymbol]:
|
|
9
|
-
timestamp: number;
|
|
10
|
-
value: Observable<any>;
|
|
11
|
-
}>;
|
|
8
|
+
protected [CacheSymbol]: CacheMap;
|
|
12
9
|
private readonly actions;
|
|
13
10
|
readonly effects: Effect[];
|
|
14
11
|
readonly reducers: Reducer[];
|
|
@@ -16,7 +13,7 @@ export declare abstract class Store<State, A> {
|
|
|
16
13
|
get loading$(): Observable<LoadingState<A>>;
|
|
17
14
|
protected readonly _store$: BehaviorSubject<State>;
|
|
18
15
|
get state$(): Observable<State>;
|
|
19
|
-
constructor(initialState: State,
|
|
16
|
+
constructor(initialState: State, _scheduler?: SchedulerLike);
|
|
20
17
|
select<R>(projection: (s: State) => R): Observable<R>;
|
|
21
18
|
getLoadingFor<C extends keyof A>(action: C): Observable<boolean>;
|
|
22
19
|
dispatch<C extends keyof A>(action: C, ...payload: getPayloadFromActionType<A, C>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/store.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,eAAe,EAAS,UAAU,EAAM,aAAa,EAAW,MAAM,MAAM,CAAC;AAErG,OAAO,EAAE,MAAM,EAA8B,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/store.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,eAAe,EAAS,UAAU,EAAM,aAAa,EAAW,MAAM,MAAM,CAAC;AAErG,OAAO,EAAE,MAAM,EAA8B,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EAAe,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAM,wBAAwB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGrE,8BAAsB,KAAK,CAAC,KAAK,EAAE,CAAC;IA0BtB,OAAO,CAAC,YAAY;IAxBhC,SAAS,CAAC,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAa;IAE9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoE;IAE5F,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAE3B,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC;IAG7B,OAAO,CAAC,SAAS,CAAkC;IAEnD,IAAW,QAAQ,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAEjD;IAED,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,CAGhD;IAEF,IAAW,MAAM,IAAI,UAAU,CAAC,KAAK,CAAC,CAErC;gBAEmB,YAAY,EAAE,KAAK,EAAE,UAAU,GAAE,aAA6B;IAyFlF,MAAM,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC;IAOrC,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC;IAOhE,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,EACxB,MAAM,EAAE,CAAC,EACT,GAAG,OAAO,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC;IAa5C,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG;IAIxC,OAAO,CAAC,MAAM,CAAC,WAAW;IAU1B,OAAO,CAAC,MAAM,CAAC,cAAc;IAS7B,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,YAAY;CAarB"}
|
|
@@ -17,7 +17,7 @@ class Store {
|
|
|
17
17
|
get state$() {
|
|
18
18
|
return this._store$.asObservable();
|
|
19
19
|
}
|
|
20
|
-
constructor(initialState,
|
|
20
|
+
constructor(initialState, _scheduler = rxjs_1.asapScheduler) {
|
|
21
21
|
this.initialState = initialState;
|
|
22
22
|
this[_a] = new Map();
|
|
23
23
|
this.actions = new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,MAAM,CAAC;AAElC,MAAM,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AAEzC,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C,MAAM,MAAM,wBAAwB,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IACvD,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,GACpC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,GAC9C,CAAC,GAAG,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAE7B,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,SAAS,IAAI,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AAEnE,MAAM,MAAM,WAAW,CAAC,OAAO,IAAI;KAEhC,CAAC,IAAI,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,WAAW,CAAC,SAAS,CAAC,GAC3D,KAAK,GACL,OAAO,CAAC,CAAC,CAAC;CACf,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,MAAM,CAAC;AAElC,MAAM,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AAEzC,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C,MAAM,MAAM,wBAAwB,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IACvD,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,GACpC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,GAC9C,CAAC,GAAG,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAE7B,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,SAAS,IAAI,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AAEnE,MAAM,MAAM,WAAW,CAAC,OAAO,IAAI;KAEhC,CAAC,IAAI,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,WAAW,CAAC,SAAS,CAAC,GAC3D,KAAK,GACL,OAAO,CAAC,CAAC,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,KAAK,EAAE,OAAO,IAClC;KACG,CAAC,IAAI,MAAM,WAAW,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;CAC3D,GAID;KAEG,CAAC,IAAI,MAAM,OAAO,IAAI,KAAK,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,UAAU,CAAC,MAAM,CAAC,CAAC,GAC3G,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,KAAK,GACnC,OAAO,CAAC,CAAC,CAAC,SAAS,WAAW,CAAC,MAAM,CAAC,CAAC,GACrC,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,KAAK,GACrC,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,KAAK;CAE/B,CAAC;AAEJ,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC,CAAC;IACd,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;CACjB"}
|
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
import { Store } from '../store/store';
|
|
2
1
|
import { Observable } from 'rxjs';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export declare const CacheSymbol: unique symbol;
|
|
6
|
-
export interface CacheOptions {
|
|
7
|
-
/**
|
|
8
|
-
* time to live in milliseconds
|
|
9
|
-
*/
|
|
10
|
-
ttl?: number;
|
|
11
|
-
/**
|
|
12
|
-
* This function has the same arguments as the decorated method
|
|
13
|
-
*/
|
|
14
|
-
trackBy?: (...args: any[]) => string | number;
|
|
15
|
-
}
|
|
2
|
+
export { Cache, CacheSymbol } from '@microphi/di';
|
|
3
|
+
export type { CacheEntry, CacheMap, CacheOptions } from '@microphi/di';
|
|
16
4
|
export type EffectMethod = (...args: any[]) => Observable<any>;
|
|
17
|
-
/**
|
|
18
|
-
* Caches an effect's value
|
|
19
|
-
*/
|
|
20
|
-
export declare function Cache(options?: CacheOptions): <S extends Store<any, any>>(_target: S, property: Key, descriptor: TypedPropertyDescriptor<Fn>) => void;
|
|
21
5
|
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/lib/operators/cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/lib/operators/cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAIlC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvE,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,CAAC,GAAG,CAAC,CAAC"}
|
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Caches an effect's value
|
|
5
|
-
*/
|
|
6
|
-
export function Cache(options) {
|
|
7
|
-
return (_target, property, descriptor) => {
|
|
8
|
-
const originalFn = descriptor.value;
|
|
9
|
-
if (originalFn) {
|
|
10
|
-
descriptor.value = function (...args) {
|
|
11
|
-
const root = this;
|
|
12
|
-
const cache = root[CacheSymbol];
|
|
13
|
-
// this can grow huge
|
|
14
|
-
// shall we put some sort of cap?
|
|
15
|
-
const key = options?.trackBy ? options.trackBy(...args) : JSON.stringify(args);
|
|
16
|
-
const id = `${property}:${key}`;
|
|
17
|
-
const cached = cache.get(id);
|
|
18
|
-
if (cached) {
|
|
19
|
-
// check ttl given timestamp
|
|
20
|
-
const timestamp = cached.timestamp;
|
|
21
|
-
const now = Date.now();
|
|
22
|
-
const ttl = now - timestamp;
|
|
23
|
-
const maxTtl = options?.ttl || 300000; // 5 minutes
|
|
24
|
-
if (ttl <= maxTtl) {
|
|
25
|
-
const value = cached.value;
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
const retValue = originalFn.apply(this, args);
|
|
30
|
-
cache.set(id, { value: retValue, timestamp: Date.now() });
|
|
31
|
-
return retValue;
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
1
|
+
// `@Cache` lives in `@microphi/di` so that it can be used on any class.
|
|
2
|
+
// It is re-exported here to keep the store's public API unchanged.
|
|
3
|
+
export { Cache, CacheSymbol } from '@microphi/di';
|
|
@@ -2,7 +2,7 @@ import 'reflect-metadata';
|
|
|
2
2
|
import { scanInstance } from '../utilities/scan-instance';
|
|
3
3
|
export const ReduceSymbol = Symbol('@Reduce');
|
|
4
4
|
export function Reduce() {
|
|
5
|
-
return (target, propertyKey
|
|
5
|
+
return (target, propertyKey) => {
|
|
6
6
|
// by convention a reducer have name such as onFindAll
|
|
7
7
|
// where findAll is the associated action
|
|
8
8
|
// with the following we extract the action's name
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { BehaviorSubject, Observable, SchedulerLike } from 'rxjs';
|
|
2
2
|
import { Effect } from '../effect/effect';
|
|
3
|
-
import { CacheSymbol } from '../operators/cache';
|
|
3
|
+
import { CacheMap, CacheSymbol } from '../operators/cache';
|
|
4
4
|
import { Reducer } from '../reduce/reduce';
|
|
5
5
|
import { getPayloadFromActionType, LoadingState } from './types';
|
|
6
6
|
export declare abstract class Store<State, A> {
|
|
7
7
|
private initialState;
|
|
8
|
-
protected [CacheSymbol]:
|
|
9
|
-
timestamp: number;
|
|
10
|
-
value: Observable<any>;
|
|
11
|
-
}>;
|
|
8
|
+
protected [CacheSymbol]: CacheMap;
|
|
12
9
|
private readonly actions;
|
|
13
10
|
readonly effects: Effect[];
|
|
14
11
|
readonly reducers: Reducer[];
|
|
@@ -16,7 +13,7 @@ export declare abstract class Store<State, A> {
|
|
|
16
13
|
get loading$(): Observable<LoadingState<A>>;
|
|
17
14
|
protected readonly _store$: BehaviorSubject<State>;
|
|
18
15
|
get state$(): Observable<State>;
|
|
19
|
-
constructor(initialState: State,
|
|
16
|
+
constructor(initialState: State, _scheduler?: SchedulerLike);
|
|
20
17
|
select<R>(projection: (s: State) => R): Observable<R>;
|
|
21
18
|
getLoadingFor<C extends keyof A>(action: C): Observable<boolean>;
|
|
22
19
|
dispatch<C extends keyof A>(action: C, ...payload: getPayloadFromActionType<A, C>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/store.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,eAAe,EAAS,UAAU,EAAM,aAAa,EAAW,MAAM,MAAM,CAAC;AAErG,OAAO,EAAE,MAAM,EAA8B,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/lib/store/store.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,eAAe,EAAS,UAAU,EAAM,aAAa,EAAW,MAAM,MAAM,CAAC;AAErG,OAAO,EAAE,MAAM,EAA8B,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EAAe,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAM,wBAAwB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGrE,8BAAsB,KAAK,CAAC,KAAK,EAAE,CAAC;IA0BtB,OAAO,CAAC,YAAY;IAxBhC,SAAS,CAAC,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAa;IAE9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoE;IAE5F,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAE3B,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC;IAG7B,OAAO,CAAC,SAAS,CAAkC;IAEnD,IAAW,QAAQ,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAEjD;IAED,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,CAGhD;IAEF,IAAW,MAAM,IAAI,UAAU,CAAC,KAAK,CAAC,CAErC;gBAEmB,YAAY,EAAE,KAAK,EAAE,UAAU,GAAE,aAA6B;IAyFlF,MAAM,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC;IAOrC,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC;IAOhE,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,EACxB,MAAM,EAAE,CAAC,EACT,GAAG,OAAO,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC;IAa5C,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG;IAIxC,OAAO,CAAC,MAAM,CAAC,WAAW;IAU1B,OAAO,CAAC,MAAM,CAAC,cAAc;IAS7B,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,YAAY;CAarB"}
|