@ngstato/core 0.1.2 → 0.3.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 +55 -3
- package/dist/index.d.mts +178 -2
- package/dist/index.d.ts +178 -2
- package/dist/index.js +1243 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1221 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/action-bus.ts
|
|
4
|
+
var listenersByAction = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
function emitActionEvent(event) {
|
|
6
|
+
const set = listenersByAction.get(event.action);
|
|
7
|
+
if (!set?.size) return;
|
|
8
|
+
for (const listener of set) {
|
|
9
|
+
try {
|
|
10
|
+
listener(event);
|
|
11
|
+
} catch {
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function subscribeToAction(action, listener) {
|
|
16
|
+
let set = listenersByAction.get(action);
|
|
17
|
+
if (!set) {
|
|
18
|
+
set = /* @__PURE__ */ new Set();
|
|
19
|
+
listenersByAction.set(action, set);
|
|
20
|
+
}
|
|
21
|
+
set.add(listener);
|
|
22
|
+
return () => {
|
|
23
|
+
set?.delete(listener);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
3
27
|
// src/store.ts
|
|
4
28
|
var StatoStore = class {
|
|
5
29
|
// Le state interne — jamais accessible directement
|
|
@@ -10,12 +34,46 @@ var StatoStore = class {
|
|
|
10
34
|
_actions = {};
|
|
11
35
|
// Les computed enregistrés
|
|
12
36
|
_computed = {};
|
|
37
|
+
_selectors = {};
|
|
13
38
|
// Les cleanups à appeler à la destruction
|
|
14
39
|
_cleanups = [];
|
|
15
40
|
// Les hooks lifecycle
|
|
16
41
|
_hooks;
|
|
42
|
+
_publicStore = null;
|
|
43
|
+
_publicActions = {};
|
|
44
|
+
_initialized = false;
|
|
45
|
+
_effects = [];
|
|
46
|
+
_createMemoizedSelector(fn) {
|
|
47
|
+
let initialized = false;
|
|
48
|
+
let cachedResult;
|
|
49
|
+
let trackedKeys = [];
|
|
50
|
+
let trackedValues = [];
|
|
51
|
+
return () => {
|
|
52
|
+
if (initialized && trackedKeys.length) {
|
|
53
|
+
const unchanged = trackedKeys.every(
|
|
54
|
+
(key, index) => Object.is(this._state[key], trackedValues[index])
|
|
55
|
+
);
|
|
56
|
+
if (unchanged) return cachedResult;
|
|
57
|
+
}
|
|
58
|
+
const reads = /* @__PURE__ */ new Set();
|
|
59
|
+
const trackingState = new Proxy(this._state, {
|
|
60
|
+
get: (target, prop, receiver) => {
|
|
61
|
+
if (typeof prop === "string" && prop in target) {
|
|
62
|
+
reads.add(prop);
|
|
63
|
+
}
|
|
64
|
+
return Reflect.get(target, prop, receiver);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const result = fn(trackingState);
|
|
68
|
+
trackedKeys = Array.from(reads);
|
|
69
|
+
trackedValues = trackedKeys.map((key) => this._state[key]);
|
|
70
|
+
cachedResult = result;
|
|
71
|
+
initialized = true;
|
|
72
|
+
return result;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
17
75
|
constructor(config) {
|
|
18
|
-
const { actions, computed, hooks, ...initialState } = config;
|
|
76
|
+
const { actions, computed, selectors, effects, hooks, ...initialState } = config;
|
|
19
77
|
this._state = initialState;
|
|
20
78
|
this._hooks = hooks ?? {};
|
|
21
79
|
if (actions) {
|
|
@@ -30,6 +88,27 @@ var StatoStore = class {
|
|
|
30
88
|
}
|
|
31
89
|
}
|
|
32
90
|
}
|
|
91
|
+
if (selectors) {
|
|
92
|
+
for (const [name, fn] of Object.entries(selectors)) {
|
|
93
|
+
if (typeof fn === "function") {
|
|
94
|
+
this._selectors[name] = this._createMemoizedSelector(fn);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (effects) {
|
|
99
|
+
for (const entry of effects) {
|
|
100
|
+
const [deps, run] = entry;
|
|
101
|
+
if (typeof deps === "function" && typeof run === "function") {
|
|
102
|
+
this._effects.push({
|
|
103
|
+
deps,
|
|
104
|
+
run,
|
|
105
|
+
hasRun: false,
|
|
106
|
+
running: false,
|
|
107
|
+
rerunRequested: false
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
33
112
|
}
|
|
34
113
|
// ── Lire le state ──────────────────────────────────
|
|
35
114
|
getState() {
|
|
@@ -38,8 +117,56 @@ var StatoStore = class {
|
|
|
38
117
|
// ── Modifier le state — usage interne uniquement ───
|
|
39
118
|
_setState(partial) {
|
|
40
119
|
this._state = { ...this._state, ...partial };
|
|
120
|
+
this._runEffects();
|
|
41
121
|
this._notify();
|
|
42
122
|
}
|
|
123
|
+
_normalizeDeps(value) {
|
|
124
|
+
return Array.isArray(value) ? value : [value];
|
|
125
|
+
}
|
|
126
|
+
_depsChanged(prev, next) {
|
|
127
|
+
if (!prev) return true;
|
|
128
|
+
if (prev.length !== next.length) return true;
|
|
129
|
+
for (let i = 0; i < next.length; i++) {
|
|
130
|
+
if (!Object.is(prev[i], next[i])) return true;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
_runEffects(force = false) {
|
|
135
|
+
for (const effect of this._effects) {
|
|
136
|
+
const depsValue = effect.deps(this._state);
|
|
137
|
+
const depsArray = this._normalizeDeps(depsValue);
|
|
138
|
+
const shouldRun = force || this._depsChanged(effect.prevDeps, depsArray);
|
|
139
|
+
if (!shouldRun) continue;
|
|
140
|
+
const execute = async () => {
|
|
141
|
+
if (effect.running) {
|
|
142
|
+
effect.rerunRequested = true;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
effect.running = true;
|
|
146
|
+
effect.rerunRequested = false;
|
|
147
|
+
const prevDepsValue = effect.prevDeps;
|
|
148
|
+
effect.prevDeps = depsArray;
|
|
149
|
+
try {
|
|
150
|
+
effect.cleanup?.();
|
|
151
|
+
const maybeCleanup = await effect.run(depsValue, {
|
|
152
|
+
state: { ...this._state },
|
|
153
|
+
store: this._publicStore,
|
|
154
|
+
prevDepsValue
|
|
155
|
+
});
|
|
156
|
+
effect.cleanup = typeof maybeCleanup === "function" ? maybeCleanup : void 0;
|
|
157
|
+
effect.hasRun = true;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
this._hooks.onError?.(error, "effect");
|
|
160
|
+
} finally {
|
|
161
|
+
effect.running = false;
|
|
162
|
+
if (effect.rerunRequested) {
|
|
163
|
+
this._runEffects();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
void execute();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
43
170
|
// ── Notifier tous les abonnés ──────────────────────
|
|
44
171
|
_notify() {
|
|
45
172
|
for (const subscriber of this._subscribers) {
|
|
@@ -57,6 +184,7 @@ var StatoStore = class {
|
|
|
57
184
|
if (!action) {
|
|
58
185
|
throw new Error(`[Stato] Action "${actionName}" introuvable`);
|
|
59
186
|
}
|
|
187
|
+
const publicAction = this._publicActions[actionName];
|
|
60
188
|
this._hooks.onAction?.(actionName, args);
|
|
61
189
|
const start = Date.now();
|
|
62
190
|
const prevState = { ...this._state };
|
|
@@ -69,10 +197,32 @@ var StatoStore = class {
|
|
|
69
197
|
});
|
|
70
198
|
try {
|
|
71
199
|
await action(stateProxy, ...args);
|
|
72
|
-
|
|
200
|
+
const duration = Date.now() - start;
|
|
201
|
+
this._hooks.onActionDone?.(actionName, duration);
|
|
73
202
|
this._hooks.onStateChange?.(prevState, { ...this._state });
|
|
203
|
+
if (publicAction) {
|
|
204
|
+
emitActionEvent({
|
|
205
|
+
action: publicAction,
|
|
206
|
+
name: actionName,
|
|
207
|
+
args,
|
|
208
|
+
store: this._publicStore,
|
|
209
|
+
status: "success",
|
|
210
|
+
duration
|
|
211
|
+
});
|
|
212
|
+
}
|
|
74
213
|
} catch (error) {
|
|
75
214
|
this._hooks.onError?.(error, actionName);
|
|
215
|
+
if (publicAction) {
|
|
216
|
+
emitActionEvent({
|
|
217
|
+
action: publicAction,
|
|
218
|
+
name: actionName,
|
|
219
|
+
args,
|
|
220
|
+
store: this._publicStore,
|
|
221
|
+
status: "error",
|
|
222
|
+
duration: Date.now() - start,
|
|
223
|
+
error
|
|
224
|
+
});
|
|
225
|
+
}
|
|
76
226
|
throw error;
|
|
77
227
|
}
|
|
78
228
|
}
|
|
@@ -82,21 +232,46 @@ var StatoStore = class {
|
|
|
82
232
|
if (!fn) throw new Error(`[Stato] Computed "${name}" introuvable`);
|
|
83
233
|
return fn();
|
|
84
234
|
}
|
|
235
|
+
getSelector(name) {
|
|
236
|
+
const fn = this._selectors[name];
|
|
237
|
+
if (!fn) throw new Error(`[Stato] Selector "${name}" introuvable`);
|
|
238
|
+
return fn();
|
|
239
|
+
}
|
|
85
240
|
// ── Enregistrer un cleanup (pour fromStream) ───────
|
|
86
241
|
registerCleanup(fn) {
|
|
87
242
|
this._cleanups.push(fn);
|
|
88
243
|
}
|
|
244
|
+
registerPublicAction(name, fn) {
|
|
245
|
+
this._publicActions[name] = fn;
|
|
246
|
+
}
|
|
247
|
+
hydrate(partial) {
|
|
248
|
+
this._setState(partial);
|
|
249
|
+
}
|
|
250
|
+
setPublicStore(publicStore) {
|
|
251
|
+
this._publicStore = publicStore;
|
|
252
|
+
this._runEffects(true);
|
|
253
|
+
}
|
|
89
254
|
// ── Lifecycle — appelé par l'adaptateur Angular ────
|
|
90
255
|
init(publicStore) {
|
|
91
|
-
this.
|
|
256
|
+
this._publicStore = publicStore;
|
|
257
|
+
if (!this._initialized) {
|
|
258
|
+
this._initialized = true;
|
|
259
|
+
this._hooks.onInit?.(publicStore);
|
|
260
|
+
}
|
|
261
|
+
this._runEffects(true);
|
|
92
262
|
}
|
|
93
263
|
destroy(publicStore) {
|
|
94
264
|
this._hooks.onDestroy?.(publicStore);
|
|
265
|
+
for (const effect of this._effects) {
|
|
266
|
+
effect.cleanup?.();
|
|
267
|
+
effect.cleanup = void 0;
|
|
268
|
+
}
|
|
95
269
|
for (const cleanup of this._cleanups) {
|
|
96
270
|
cleanup();
|
|
97
271
|
}
|
|
98
272
|
this._cleanups = [];
|
|
99
273
|
this._subscribers.clear();
|
|
274
|
+
this._initialized = false;
|
|
100
275
|
}
|
|
101
276
|
};
|
|
102
277
|
function createStore(config) {
|
|
@@ -119,10 +294,12 @@ function createStore(config) {
|
|
|
119
294
|
configurable: true
|
|
120
295
|
});
|
|
121
296
|
}
|
|
122
|
-
const { actions, computed } = config;
|
|
297
|
+
const { actions, computed, selectors } = config;
|
|
123
298
|
if (actions) {
|
|
124
299
|
for (const name of Object.keys(actions)) {
|
|
125
|
-
|
|
300
|
+
const fn = (...args) => store.dispatch(name, ...args);
|
|
301
|
+
publicStore[name] = fn;
|
|
302
|
+
store.registerPublicAction(name, fn);
|
|
126
303
|
}
|
|
127
304
|
}
|
|
128
305
|
if (computed) {
|
|
@@ -134,8 +311,32 @@ function createStore(config) {
|
|
|
134
311
|
});
|
|
135
312
|
}
|
|
136
313
|
}
|
|
314
|
+
if (selectors) {
|
|
315
|
+
for (const name of Object.keys(selectors)) {
|
|
316
|
+
Object.defineProperty(publicStore, name, {
|
|
317
|
+
get: () => store.getSelector(name),
|
|
318
|
+
enumerable: true,
|
|
319
|
+
configurable: true
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
store.init(publicStore);
|
|
137
324
|
return publicStore;
|
|
138
325
|
}
|
|
326
|
+
function on(sourceAction, handler) {
|
|
327
|
+
return subscribeToAction(sourceAction, (event) => {
|
|
328
|
+
try {
|
|
329
|
+
void handler(event.store, {
|
|
330
|
+
name: event.name,
|
|
331
|
+
args: event.args,
|
|
332
|
+
status: event.status,
|
|
333
|
+
duration: event.duration,
|
|
334
|
+
error: event.error
|
|
335
|
+
});
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
139
340
|
|
|
140
341
|
// src/types.ts
|
|
141
342
|
var StatoHttpError = class extends Error {
|
|
@@ -366,6 +567,1020 @@ function optimistic(immediate, confirm) {
|
|
|
366
567
|
};
|
|
367
568
|
}
|
|
368
569
|
|
|
570
|
+
// src/helpers/exclusive.ts
|
|
571
|
+
function exclusive(fn) {
|
|
572
|
+
let running = false;
|
|
573
|
+
let current = null;
|
|
574
|
+
return (state, ...args) => {
|
|
575
|
+
if (running && current) return current;
|
|
576
|
+
running = true;
|
|
577
|
+
current = (async () => {
|
|
578
|
+
try {
|
|
579
|
+
await fn(state, ...args);
|
|
580
|
+
} finally {
|
|
581
|
+
running = false;
|
|
582
|
+
current = null;
|
|
583
|
+
}
|
|
584
|
+
})();
|
|
585
|
+
return current;
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/helpers/queued.ts
|
|
590
|
+
function queued(fn) {
|
|
591
|
+
const queue = [];
|
|
592
|
+
let processing = false;
|
|
593
|
+
const processNext = () => {
|
|
594
|
+
if (processing) return;
|
|
595
|
+
processing = true;
|
|
596
|
+
const run = async () => {
|
|
597
|
+
while (queue.length) {
|
|
598
|
+
const item = queue.shift();
|
|
599
|
+
if (!item) break;
|
|
600
|
+
try {
|
|
601
|
+
await fn(item.state, ...item.args);
|
|
602
|
+
item.resolve();
|
|
603
|
+
} catch (err) {
|
|
604
|
+
item.reject(err);
|
|
605
|
+
while (queue.length) {
|
|
606
|
+
const rest = queue.shift();
|
|
607
|
+
rest?.reject(err);
|
|
608
|
+
}
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
void run().finally(() => {
|
|
614
|
+
processing = false;
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
return (state, ...args) => {
|
|
618
|
+
return new Promise((resolve, reject) => {
|
|
619
|
+
queue.push({ state, args, resolve, reject });
|
|
620
|
+
processNext();
|
|
621
|
+
});
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// src/helpers/distinct-until-changed.ts
|
|
626
|
+
function distinctUntilChanged(fn, keySelector, comparator = Object.is) {
|
|
627
|
+
let initialized = false;
|
|
628
|
+
let prevKey;
|
|
629
|
+
return async (state, ...args) => {
|
|
630
|
+
const nextKey = keySelector(...args);
|
|
631
|
+
if (initialized && comparator(prevKey, nextKey)) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
initialized = true;
|
|
635
|
+
prevKey = nextKey;
|
|
636
|
+
await fn(state, ...args);
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/helpers/fork-join.ts
|
|
641
|
+
async function forkJoin(tasks, options) {
|
|
642
|
+
const controller = new AbortController();
|
|
643
|
+
const signal = options?.signal;
|
|
644
|
+
if (signal) {
|
|
645
|
+
if (signal.aborted) controller.abort();
|
|
646
|
+
else signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
647
|
+
}
|
|
648
|
+
const entries = Object.entries(tasks);
|
|
649
|
+
const results = await Promise.all(entries.map(async ([key, task]) => {
|
|
650
|
+
const value = await task({ signal: controller.signal });
|
|
651
|
+
return [key, value];
|
|
652
|
+
}));
|
|
653
|
+
return Object.fromEntries(results);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// src/helpers/race.ts
|
|
657
|
+
async function race(tasks, options) {
|
|
658
|
+
const controller = new AbortController();
|
|
659
|
+
const outer = options?.signal;
|
|
660
|
+
if (outer) {
|
|
661
|
+
if (outer.aborted) controller.abort();
|
|
662
|
+
else outer.addEventListener("abort", () => controller.abort(), { once: true });
|
|
663
|
+
}
|
|
664
|
+
const wrapped = tasks.map((task) => (async () => task({ signal: controller.signal }))());
|
|
665
|
+
try {
|
|
666
|
+
return await Promise.race(wrapped);
|
|
667
|
+
} finally {
|
|
668
|
+
controller.abort();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// src/helpers/combine-latest.ts
|
|
673
|
+
function combineLatest() {
|
|
674
|
+
return (...deps) => {
|
|
675
|
+
return (state) => deps.map((fn) => fn(state));
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/helpers/combine-latest-stream.ts
|
|
680
|
+
function combineLatestStream(...sources) {
|
|
681
|
+
return {
|
|
682
|
+
subscribe(observer) {
|
|
683
|
+
const n = sources.length;
|
|
684
|
+
if (!n) {
|
|
685
|
+
observer.complete?.();
|
|
686
|
+
return { unsubscribe() {
|
|
687
|
+
} };
|
|
688
|
+
}
|
|
689
|
+
const hasValue = new Array(n).fill(false);
|
|
690
|
+
const values = new Array(n);
|
|
691
|
+
let completed = 0;
|
|
692
|
+
let closed = false;
|
|
693
|
+
const subs = [];
|
|
694
|
+
const tryEmit = () => {
|
|
695
|
+
if (closed) return;
|
|
696
|
+
if (hasValue.every(Boolean)) {
|
|
697
|
+
observer.next?.(values.slice());
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
const closeAll = () => {
|
|
701
|
+
if (closed) return;
|
|
702
|
+
closed = true;
|
|
703
|
+
for (const s of subs) {
|
|
704
|
+
try {
|
|
705
|
+
s.unsubscribe();
|
|
706
|
+
} catch {
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
sources.forEach((src, index) => {
|
|
711
|
+
const sub = src.subscribe({
|
|
712
|
+
next: (v) => {
|
|
713
|
+
if (closed) return;
|
|
714
|
+
values[index] = v;
|
|
715
|
+
hasValue[index] = true;
|
|
716
|
+
tryEmit();
|
|
717
|
+
},
|
|
718
|
+
error: (err) => {
|
|
719
|
+
if (closed) return;
|
|
720
|
+
observer.error?.(err);
|
|
721
|
+
closeAll();
|
|
722
|
+
},
|
|
723
|
+
complete: () => {
|
|
724
|
+
if (closed) return;
|
|
725
|
+
completed++;
|
|
726
|
+
if (completed >= n) {
|
|
727
|
+
observer.complete?.();
|
|
728
|
+
closeAll();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
subs.push(sub);
|
|
733
|
+
});
|
|
734
|
+
return {
|
|
735
|
+
unsubscribe() {
|
|
736
|
+
closeAll();
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/helpers/entity-adapter.ts
|
|
744
|
+
function defaultSelectId(entity) {
|
|
745
|
+
return entity.id;
|
|
746
|
+
}
|
|
747
|
+
function idKey(id) {
|
|
748
|
+
return String(id);
|
|
749
|
+
}
|
|
750
|
+
function ensureSort(state, sortComparer) {
|
|
751
|
+
if (!sortComparer) return;
|
|
752
|
+
state.ids.sort((a, b) => {
|
|
753
|
+
const ea = state.entities[idKey(a)];
|
|
754
|
+
const eb = state.entities[idKey(b)];
|
|
755
|
+
if (!ea || !eb) return 0;
|
|
756
|
+
return sortComparer(ea, eb);
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
function createEntityAdapter(options = {}) {
|
|
760
|
+
const selectId = options.selectId ?? defaultSelectId;
|
|
761
|
+
const sortComparer = options.sortComparer;
|
|
762
|
+
const getInitialState = (extra) => {
|
|
763
|
+
return {
|
|
764
|
+
ids: [],
|
|
765
|
+
entities: {},
|
|
766
|
+
...extra ?? {}
|
|
767
|
+
};
|
|
768
|
+
};
|
|
769
|
+
const addOne = (entity, state) => {
|
|
770
|
+
const id = selectId(entity);
|
|
771
|
+
const key = idKey(id);
|
|
772
|
+
if (state.entities[key]) return;
|
|
773
|
+
state.ids.push(id);
|
|
774
|
+
state.entities[key] = entity;
|
|
775
|
+
ensureSort(state, sortComparer);
|
|
776
|
+
};
|
|
777
|
+
const addMany = (entities, state) => {
|
|
778
|
+
for (const entity of entities) addOne(entity, state);
|
|
779
|
+
};
|
|
780
|
+
const setAll = (entities, state) => {
|
|
781
|
+
state.ids = [];
|
|
782
|
+
state.entities = {};
|
|
783
|
+
for (const entity of entities) {
|
|
784
|
+
const id = selectId(entity);
|
|
785
|
+
state.ids.push(id);
|
|
786
|
+
state.entities[idKey(id)] = entity;
|
|
787
|
+
}
|
|
788
|
+
ensureSort(state, sortComparer);
|
|
789
|
+
};
|
|
790
|
+
const upsertOne = (entity, state) => {
|
|
791
|
+
const id = selectId(entity);
|
|
792
|
+
const key = idKey(id);
|
|
793
|
+
const exists = !!state.entities[key];
|
|
794
|
+
state.entities[key] = entity;
|
|
795
|
+
if (!exists) state.ids.push(id);
|
|
796
|
+
ensureSort(state, sortComparer);
|
|
797
|
+
};
|
|
798
|
+
const upsertMany = (entities, state) => {
|
|
799
|
+
for (const entity of entities) upsertOne(entity, state);
|
|
800
|
+
};
|
|
801
|
+
const updateOne = (update, state) => {
|
|
802
|
+
const key = idKey(update.id);
|
|
803
|
+
const current = state.entities[key];
|
|
804
|
+
if (!current) return;
|
|
805
|
+
state.entities[key] = { ...current, ...update.changes };
|
|
806
|
+
ensureSort(state, sortComparer);
|
|
807
|
+
};
|
|
808
|
+
const removeOne = (id, state) => {
|
|
809
|
+
const key = idKey(id);
|
|
810
|
+
if (!state.entities[key]) return;
|
|
811
|
+
delete state.entities[key];
|
|
812
|
+
state.ids = state.ids.filter((x) => !Object.is(x, id));
|
|
813
|
+
};
|
|
814
|
+
const removeMany = (ids, state) => {
|
|
815
|
+
const removeSet = new Set(ids.map(idKey));
|
|
816
|
+
for (const key of Object.keys(state.entities)) {
|
|
817
|
+
if (removeSet.has(key)) delete state.entities[key];
|
|
818
|
+
}
|
|
819
|
+
state.ids = state.ids.filter((id) => !removeSet.has(idKey(id)));
|
|
820
|
+
};
|
|
821
|
+
const removeAll = (state) => {
|
|
822
|
+
state.ids = [];
|
|
823
|
+
state.entities = {};
|
|
824
|
+
};
|
|
825
|
+
const getSelectors = (selectState) => {
|
|
826
|
+
const pick = (state) => selectState ? selectState(state) : state;
|
|
827
|
+
return {
|
|
828
|
+
selectIds: (state) => pick(state).ids,
|
|
829
|
+
selectEntities: (state) => pick(state).entities,
|
|
830
|
+
selectAll: (state) => {
|
|
831
|
+
const s = pick(state);
|
|
832
|
+
return s.ids.map((id) => s.entities[idKey(id)]).filter(Boolean);
|
|
833
|
+
},
|
|
834
|
+
selectTotal: (state) => pick(state).ids.length,
|
|
835
|
+
selectById: (state, id) => pick(state).entities[idKey(id)]
|
|
836
|
+
};
|
|
837
|
+
};
|
|
838
|
+
return {
|
|
839
|
+
selectId,
|
|
840
|
+
sortComparer,
|
|
841
|
+
getInitialState,
|
|
842
|
+
addOne,
|
|
843
|
+
addMany,
|
|
844
|
+
setAll,
|
|
845
|
+
upsertOne,
|
|
846
|
+
upsertMany,
|
|
847
|
+
updateOne,
|
|
848
|
+
removeOne,
|
|
849
|
+
removeMany,
|
|
850
|
+
removeAll,
|
|
851
|
+
getSelectors
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/helpers/with-entities.ts
|
|
856
|
+
function cloneEntityState(state) {
|
|
857
|
+
return {
|
|
858
|
+
ids: [...state.ids],
|
|
859
|
+
entities: { ...state.entities }
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function withEntities(config, options) {
|
|
863
|
+
const { key, adapter, initial } = options;
|
|
864
|
+
const initialSlice = adapter.getInitialState();
|
|
865
|
+
if (initial?.length) {
|
|
866
|
+
adapter.setAll(initial, initialSlice);
|
|
867
|
+
}
|
|
868
|
+
const baseSelectors = config.selectors ?? {};
|
|
869
|
+
const baseActions = config.actions ?? {};
|
|
870
|
+
const scopedSelectors = adapter.getSelectors((s) => s[key]);
|
|
871
|
+
const selectorNames = {
|
|
872
|
+
ids: options.selectors?.ids ?? `${key}Ids`,
|
|
873
|
+
entities: options.selectors?.entities ?? `${key}Entities`,
|
|
874
|
+
all: options.selectors?.all ?? `${key}All`,
|
|
875
|
+
total: options.selectors?.total ?? `${key}Total`,
|
|
876
|
+
byId: options.selectors?.byId ?? `${key}ById`
|
|
877
|
+
};
|
|
878
|
+
const actionNames = {
|
|
879
|
+
addOne: options.actions?.addOne ?? `${key}AddOne`,
|
|
880
|
+
addMany: options.actions?.addMany ?? `${key}AddMany`,
|
|
881
|
+
setAll: options.actions?.setAll ?? `${key}SetAll`,
|
|
882
|
+
upsertOne: options.actions?.upsertOne ?? `${key}UpsertOne`,
|
|
883
|
+
upsertMany: options.actions?.upsertMany ?? `${key}UpsertMany`,
|
|
884
|
+
updateOne: options.actions?.updateOne ?? `${key}UpdateOne`,
|
|
885
|
+
removeOne: options.actions?.removeOne ?? `${key}RemoveOne`,
|
|
886
|
+
removeMany: options.actions?.removeMany ?? `${key}RemoveMany`,
|
|
887
|
+
removeAll: options.actions?.removeAll ?? `${key}RemoveAll`
|
|
888
|
+
};
|
|
889
|
+
const nextSelectors = {
|
|
890
|
+
...baseSelectors,
|
|
891
|
+
[selectorNames.ids]: (state) => scopedSelectors.selectIds(state),
|
|
892
|
+
[selectorNames.entities]: (state) => scopedSelectors.selectEntities(state),
|
|
893
|
+
[selectorNames.all]: (state) => scopedSelectors.selectAll(state),
|
|
894
|
+
[selectorNames.total]: (state) => scopedSelectors.selectTotal(state),
|
|
895
|
+
[selectorNames.byId]: (state) => (id) => scopedSelectors.selectById(state, id)
|
|
896
|
+
};
|
|
897
|
+
const nextActions = {
|
|
898
|
+
...baseActions,
|
|
899
|
+
[actionNames.addOne]: (state, entity) => {
|
|
900
|
+
const prev = state[key];
|
|
901
|
+
const next = cloneEntityState(prev);
|
|
902
|
+
adapter.addOne(entity, next);
|
|
903
|
+
state[key] = next;
|
|
904
|
+
},
|
|
905
|
+
[actionNames.addMany]: (state, entities) => {
|
|
906
|
+
const prev = state[key];
|
|
907
|
+
const next = cloneEntityState(prev);
|
|
908
|
+
adapter.addMany(entities, next);
|
|
909
|
+
state[key] = next;
|
|
910
|
+
},
|
|
911
|
+
[actionNames.setAll]: (state, entities) => {
|
|
912
|
+
const next = adapter.getInitialState();
|
|
913
|
+
adapter.setAll(entities, next);
|
|
914
|
+
state[key] = next;
|
|
915
|
+
},
|
|
916
|
+
[actionNames.upsertOne]: (state, entity) => {
|
|
917
|
+
const prev = state[key];
|
|
918
|
+
const next = cloneEntityState(prev);
|
|
919
|
+
adapter.upsertOne(entity, next);
|
|
920
|
+
state[key] = next;
|
|
921
|
+
},
|
|
922
|
+
[actionNames.upsertMany]: (state, entities) => {
|
|
923
|
+
const prev = state[key];
|
|
924
|
+
const next = cloneEntityState(prev);
|
|
925
|
+
adapter.upsertMany(entities, next);
|
|
926
|
+
state[key] = next;
|
|
927
|
+
},
|
|
928
|
+
[actionNames.updateOne]: (state, update) => {
|
|
929
|
+
const prev = state[key];
|
|
930
|
+
const next = cloneEntityState(prev);
|
|
931
|
+
adapter.updateOne(update, next);
|
|
932
|
+
state[key] = next;
|
|
933
|
+
},
|
|
934
|
+
[actionNames.removeOne]: (state, id) => {
|
|
935
|
+
const prev = state[key];
|
|
936
|
+
const next = cloneEntityState(prev);
|
|
937
|
+
adapter.removeOne(id, next);
|
|
938
|
+
state[key] = next;
|
|
939
|
+
},
|
|
940
|
+
[actionNames.removeMany]: (state, ids) => {
|
|
941
|
+
const prev = state[key];
|
|
942
|
+
const next = cloneEntityState(prev);
|
|
943
|
+
adapter.removeMany(ids, next);
|
|
944
|
+
state[key] = next;
|
|
945
|
+
},
|
|
946
|
+
[actionNames.removeAll]: (state) => {
|
|
947
|
+
state[key] = adapter.getInitialState();
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
return {
|
|
951
|
+
...config,
|
|
952
|
+
[key]: config[key] ?? initialSlice,
|
|
953
|
+
actions: nextActions,
|
|
954
|
+
selectors: nextSelectors
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// src/helpers/stream-operators.ts
|
|
959
|
+
function pipeStream(source, ...ops) {
|
|
960
|
+
return ops.reduce((acc, op) => op(acc), source);
|
|
961
|
+
}
|
|
962
|
+
function isObservable(value) {
|
|
963
|
+
return !!value && typeof value === "object" && typeof value.subscribe === "function";
|
|
964
|
+
}
|
|
965
|
+
function toObservable(value) {
|
|
966
|
+
if (isObservable(value)) return value;
|
|
967
|
+
return {
|
|
968
|
+
subscribe(observer) {
|
|
969
|
+
let closed = false;
|
|
970
|
+
Promise.resolve(value).then((resolved) => {
|
|
971
|
+
if (closed) return;
|
|
972
|
+
observer.next?.(resolved);
|
|
973
|
+
observer.complete?.();
|
|
974
|
+
}).catch((error) => {
|
|
975
|
+
if (closed) return;
|
|
976
|
+
observer.error?.(error);
|
|
977
|
+
});
|
|
978
|
+
return { unsubscribe: () => {
|
|
979
|
+
closed = true;
|
|
980
|
+
} };
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function mapStream(mapFn) {
|
|
985
|
+
return (source) => ({
|
|
986
|
+
subscribe(observer) {
|
|
987
|
+
return source.subscribe({
|
|
988
|
+
next: (value) => observer.next?.(mapFn(value)),
|
|
989
|
+
error: (error) => observer.error?.(error),
|
|
990
|
+
complete: () => observer.complete?.()
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
function filterStream(predicate) {
|
|
996
|
+
return (source) => ({
|
|
997
|
+
subscribe(observer) {
|
|
998
|
+
return source.subscribe({
|
|
999
|
+
next: (value) => {
|
|
1000
|
+
if (predicate(value)) observer.next?.(value);
|
|
1001
|
+
},
|
|
1002
|
+
error: (error) => observer.error?.(error),
|
|
1003
|
+
complete: () => observer.complete?.()
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
function closeSubs(subs) {
|
|
1009
|
+
for (const sub of subs) {
|
|
1010
|
+
try {
|
|
1011
|
+
sub.unsubscribe();
|
|
1012
|
+
} catch {
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
function switchMapStream(mapper) {
|
|
1017
|
+
return (source) => ({
|
|
1018
|
+
subscribe(observer) {
|
|
1019
|
+
let closed = false;
|
|
1020
|
+
let sourceDone = false;
|
|
1021
|
+
let innerSub = null;
|
|
1022
|
+
let innerActive = false;
|
|
1023
|
+
let controller = null;
|
|
1024
|
+
const maybeComplete = () => {
|
|
1025
|
+
if (!closed && sourceDone && !innerActive) {
|
|
1026
|
+
closed = true;
|
|
1027
|
+
observer.complete?.();
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
const sourceSub = source.subscribe({
|
|
1031
|
+
next: (value) => {
|
|
1032
|
+
if (closed) return;
|
|
1033
|
+
controller?.abort();
|
|
1034
|
+
innerSub?.unsubscribe();
|
|
1035
|
+
controller = new AbortController();
|
|
1036
|
+
innerActive = true;
|
|
1037
|
+
innerSub = toObservable(mapper(value, { signal: controller.signal })).subscribe({
|
|
1038
|
+
next: (v) => {
|
|
1039
|
+
if (!closed) observer.next?.(v);
|
|
1040
|
+
},
|
|
1041
|
+
error: (error) => {
|
|
1042
|
+
if (closed) return;
|
|
1043
|
+
closed = true;
|
|
1044
|
+
observer.error?.(error);
|
|
1045
|
+
sourceSub.unsubscribe();
|
|
1046
|
+
innerSub?.unsubscribe();
|
|
1047
|
+
},
|
|
1048
|
+
complete: () => {
|
|
1049
|
+
innerActive = false;
|
|
1050
|
+
maybeComplete();
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
},
|
|
1054
|
+
error: (error) => {
|
|
1055
|
+
if (closed) return;
|
|
1056
|
+
closed = true;
|
|
1057
|
+
observer.error?.(error);
|
|
1058
|
+
controller?.abort();
|
|
1059
|
+
innerSub?.unsubscribe();
|
|
1060
|
+
},
|
|
1061
|
+
complete: () => {
|
|
1062
|
+
sourceDone = true;
|
|
1063
|
+
maybeComplete();
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
return {
|
|
1067
|
+
unsubscribe() {
|
|
1068
|
+
if (closed) return;
|
|
1069
|
+
closed = true;
|
|
1070
|
+
controller?.abort();
|
|
1071
|
+
sourceSub.unsubscribe();
|
|
1072
|
+
innerSub?.unsubscribe();
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
function concatMapStream(mapper) {
|
|
1079
|
+
return (source) => ({
|
|
1080
|
+
subscribe(observer) {
|
|
1081
|
+
let closed = false;
|
|
1082
|
+
let sourceDone = false;
|
|
1083
|
+
const queue = [];
|
|
1084
|
+
let running = false;
|
|
1085
|
+
let currentSub = null;
|
|
1086
|
+
let currentController = null;
|
|
1087
|
+
const maybeComplete = () => {
|
|
1088
|
+
if (!closed && sourceDone && !running && queue.length === 0) {
|
|
1089
|
+
closed = true;
|
|
1090
|
+
observer.complete?.();
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
const runNext = () => {
|
|
1094
|
+
if (closed || running || queue.length === 0) {
|
|
1095
|
+
maybeComplete();
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
running = true;
|
|
1099
|
+
const value = queue.shift();
|
|
1100
|
+
currentController = new AbortController();
|
|
1101
|
+
currentSub = toObservable(mapper(value, { signal: currentController.signal })).subscribe({
|
|
1102
|
+
next: (v) => {
|
|
1103
|
+
if (!closed) observer.next?.(v);
|
|
1104
|
+
},
|
|
1105
|
+
error: (error) => {
|
|
1106
|
+
if (closed) return;
|
|
1107
|
+
closed = true;
|
|
1108
|
+
observer.error?.(error);
|
|
1109
|
+
sourceSub.unsubscribe();
|
|
1110
|
+
currentController?.abort();
|
|
1111
|
+
currentSub?.unsubscribe();
|
|
1112
|
+
queue.length = 0;
|
|
1113
|
+
},
|
|
1114
|
+
complete: () => {
|
|
1115
|
+
running = false;
|
|
1116
|
+
runNext();
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
};
|
|
1120
|
+
const sourceSub = source.subscribe({
|
|
1121
|
+
next: (value) => {
|
|
1122
|
+
if (closed) return;
|
|
1123
|
+
queue.push(value);
|
|
1124
|
+
runNext();
|
|
1125
|
+
},
|
|
1126
|
+
error: (error) => {
|
|
1127
|
+
if (closed) return;
|
|
1128
|
+
closed = true;
|
|
1129
|
+
observer.error?.(error);
|
|
1130
|
+
currentController?.abort();
|
|
1131
|
+
currentSub?.unsubscribe();
|
|
1132
|
+
queue.length = 0;
|
|
1133
|
+
},
|
|
1134
|
+
complete: () => {
|
|
1135
|
+
sourceDone = true;
|
|
1136
|
+
maybeComplete();
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
return {
|
|
1140
|
+
unsubscribe() {
|
|
1141
|
+
if (closed) return;
|
|
1142
|
+
closed = true;
|
|
1143
|
+
sourceSub.unsubscribe();
|
|
1144
|
+
currentController?.abort();
|
|
1145
|
+
currentSub?.unsubscribe();
|
|
1146
|
+
queue.length = 0;
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
function exhaustMapStream(mapper) {
|
|
1153
|
+
return (source) => ({
|
|
1154
|
+
subscribe(observer) {
|
|
1155
|
+
let closed = false;
|
|
1156
|
+
let sourceDone = false;
|
|
1157
|
+
let running = false;
|
|
1158
|
+
let currentSub = null;
|
|
1159
|
+
let controller = null;
|
|
1160
|
+
const maybeComplete = () => {
|
|
1161
|
+
if (!closed && sourceDone && !running) {
|
|
1162
|
+
closed = true;
|
|
1163
|
+
observer.complete?.();
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
const sourceSub = source.subscribe({
|
|
1167
|
+
next: (value) => {
|
|
1168
|
+
if (closed || running) return;
|
|
1169
|
+
running = true;
|
|
1170
|
+
controller = new AbortController();
|
|
1171
|
+
currentSub = toObservable(mapper(value, { signal: controller.signal })).subscribe({
|
|
1172
|
+
next: (v) => {
|
|
1173
|
+
if (!closed) observer.next?.(v);
|
|
1174
|
+
},
|
|
1175
|
+
error: (error) => {
|
|
1176
|
+
if (closed) return;
|
|
1177
|
+
closed = true;
|
|
1178
|
+
observer.error?.(error);
|
|
1179
|
+
sourceSub.unsubscribe();
|
|
1180
|
+
controller?.abort();
|
|
1181
|
+
currentSub?.unsubscribe();
|
|
1182
|
+
},
|
|
1183
|
+
complete: () => {
|
|
1184
|
+
running = false;
|
|
1185
|
+
maybeComplete();
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
},
|
|
1189
|
+
error: (error) => {
|
|
1190
|
+
if (closed) return;
|
|
1191
|
+
closed = true;
|
|
1192
|
+
observer.error?.(error);
|
|
1193
|
+
controller?.abort();
|
|
1194
|
+
currentSub?.unsubscribe();
|
|
1195
|
+
},
|
|
1196
|
+
complete: () => {
|
|
1197
|
+
sourceDone = true;
|
|
1198
|
+
maybeComplete();
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
return {
|
|
1202
|
+
unsubscribe() {
|
|
1203
|
+
if (closed) return;
|
|
1204
|
+
closed = true;
|
|
1205
|
+
sourceSub.unsubscribe();
|
|
1206
|
+
controller?.abort();
|
|
1207
|
+
currentSub?.unsubscribe();
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
function mergeMapStream(mapper, options) {
|
|
1214
|
+
const concurrency = Math.max(1, options?.concurrency ?? Number.POSITIVE_INFINITY);
|
|
1215
|
+
return (source) => ({
|
|
1216
|
+
subscribe(observer) {
|
|
1217
|
+
let closed = false;
|
|
1218
|
+
let sourceDone = false;
|
|
1219
|
+
const queue = [];
|
|
1220
|
+
const active = /* @__PURE__ */ new Set();
|
|
1221
|
+
const controllers = /* @__PURE__ */ new Set();
|
|
1222
|
+
const maybeComplete = () => {
|
|
1223
|
+
if (!closed && sourceDone && active.size === 0 && queue.length === 0) {
|
|
1224
|
+
closed = true;
|
|
1225
|
+
observer.complete?.();
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
const spawn = (value) => {
|
|
1229
|
+
const controller = new AbortController();
|
|
1230
|
+
controllers.add(controller);
|
|
1231
|
+
const sub = toObservable(mapper(value, { signal: controller.signal })).subscribe({
|
|
1232
|
+
next: (v) => {
|
|
1233
|
+
if (!closed) observer.next?.(v);
|
|
1234
|
+
},
|
|
1235
|
+
error: (error) => {
|
|
1236
|
+
if (closed) return;
|
|
1237
|
+
closed = true;
|
|
1238
|
+
observer.error?.(error);
|
|
1239
|
+
sourceSub.unsubscribe();
|
|
1240
|
+
closeSubs(Array.from(active));
|
|
1241
|
+
active.clear();
|
|
1242
|
+
for (const c of controllers) c.abort();
|
|
1243
|
+
controllers.clear();
|
|
1244
|
+
queue.length = 0;
|
|
1245
|
+
},
|
|
1246
|
+
complete: () => {
|
|
1247
|
+
active.delete(sub);
|
|
1248
|
+
controllers.delete(controller);
|
|
1249
|
+
drain();
|
|
1250
|
+
maybeComplete();
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
active.add(sub);
|
|
1254
|
+
};
|
|
1255
|
+
const drain = () => {
|
|
1256
|
+
while (!closed && active.size < concurrency && queue.length > 0) {
|
|
1257
|
+
spawn(queue.shift());
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
const sourceSub = source.subscribe({
|
|
1261
|
+
next: (value) => {
|
|
1262
|
+
if (closed) return;
|
|
1263
|
+
queue.push(value);
|
|
1264
|
+
drain();
|
|
1265
|
+
},
|
|
1266
|
+
error: (error) => {
|
|
1267
|
+
if (closed) return;
|
|
1268
|
+
closed = true;
|
|
1269
|
+
observer.error?.(error);
|
|
1270
|
+
closeSubs(Array.from(active));
|
|
1271
|
+
active.clear();
|
|
1272
|
+
for (const c of controllers) c.abort();
|
|
1273
|
+
controllers.clear();
|
|
1274
|
+
queue.length = 0;
|
|
1275
|
+
},
|
|
1276
|
+
complete: () => {
|
|
1277
|
+
sourceDone = true;
|
|
1278
|
+
maybeComplete();
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
return {
|
|
1282
|
+
unsubscribe() {
|
|
1283
|
+
if (closed) return;
|
|
1284
|
+
closed = true;
|
|
1285
|
+
sourceSub.unsubscribe();
|
|
1286
|
+
closeSubs(Array.from(active));
|
|
1287
|
+
active.clear();
|
|
1288
|
+
for (const c of controllers) c.abort();
|
|
1289
|
+
controllers.clear();
|
|
1290
|
+
queue.length = 0;
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
function distinctUntilChangedStream(keySelector, comparator = Object.is) {
|
|
1297
|
+
return (source) => ({
|
|
1298
|
+
subscribe(observer) {
|
|
1299
|
+
let initialized = false;
|
|
1300
|
+
let prevKey;
|
|
1301
|
+
return source.subscribe({
|
|
1302
|
+
next: (value) => {
|
|
1303
|
+
const nextKey = keySelector ? keySelector(value) : value;
|
|
1304
|
+
if (initialized && comparator(prevKey, nextKey)) return;
|
|
1305
|
+
initialized = true;
|
|
1306
|
+
prevKey = nextKey;
|
|
1307
|
+
observer.next?.(value);
|
|
1308
|
+
},
|
|
1309
|
+
error: (error) => observer.error?.(error),
|
|
1310
|
+
complete: () => observer.complete?.()
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
function debounceStream(ms) {
|
|
1316
|
+
return (source) => ({
|
|
1317
|
+
subscribe(observer) {
|
|
1318
|
+
let timer = null;
|
|
1319
|
+
let sourceDone = false;
|
|
1320
|
+
let lastValue;
|
|
1321
|
+
let hasValue = false;
|
|
1322
|
+
let closed = false;
|
|
1323
|
+
const flush = () => {
|
|
1324
|
+
if (!hasValue || closed) return;
|
|
1325
|
+
observer.next?.(lastValue);
|
|
1326
|
+
hasValue = false;
|
|
1327
|
+
lastValue = void 0;
|
|
1328
|
+
};
|
|
1329
|
+
const maybeComplete = () => {
|
|
1330
|
+
if (sourceDone && !timer && !closed) {
|
|
1331
|
+
closed = true;
|
|
1332
|
+
observer.complete?.();
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
const sub = source.subscribe({
|
|
1336
|
+
next: (value) => {
|
|
1337
|
+
if (closed) return;
|
|
1338
|
+
lastValue = value;
|
|
1339
|
+
hasValue = true;
|
|
1340
|
+
if (timer) clearTimeout(timer);
|
|
1341
|
+
timer = setTimeout(() => {
|
|
1342
|
+
timer = null;
|
|
1343
|
+
flush();
|
|
1344
|
+
maybeComplete();
|
|
1345
|
+
}, ms);
|
|
1346
|
+
},
|
|
1347
|
+
error: (error) => {
|
|
1348
|
+
if (closed) return;
|
|
1349
|
+
closed = true;
|
|
1350
|
+
if (timer) clearTimeout(timer);
|
|
1351
|
+
observer.error?.(error);
|
|
1352
|
+
},
|
|
1353
|
+
complete: () => {
|
|
1354
|
+
sourceDone = true;
|
|
1355
|
+
if (!timer) {
|
|
1356
|
+
maybeComplete();
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
return {
|
|
1361
|
+
unsubscribe() {
|
|
1362
|
+
if (closed) return;
|
|
1363
|
+
closed = true;
|
|
1364
|
+
if (timer) clearTimeout(timer);
|
|
1365
|
+
sub.unsubscribe();
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
function throttleStream(ms) {
|
|
1372
|
+
return (source) => ({
|
|
1373
|
+
subscribe(observer) {
|
|
1374
|
+
let throttled2 = false;
|
|
1375
|
+
let timer = null;
|
|
1376
|
+
let closed = false;
|
|
1377
|
+
const sub = source.subscribe({
|
|
1378
|
+
next: (value) => {
|
|
1379
|
+
if (closed || throttled2) return;
|
|
1380
|
+
observer.next?.(value);
|
|
1381
|
+
throttled2 = true;
|
|
1382
|
+
timer = setTimeout(() => {
|
|
1383
|
+
throttled2 = false;
|
|
1384
|
+
timer = null;
|
|
1385
|
+
}, ms);
|
|
1386
|
+
},
|
|
1387
|
+
error: (error) => {
|
|
1388
|
+
if (closed) return;
|
|
1389
|
+
closed = true;
|
|
1390
|
+
if (timer) clearTimeout(timer);
|
|
1391
|
+
observer.error?.(error);
|
|
1392
|
+
},
|
|
1393
|
+
complete: () => {
|
|
1394
|
+
if (closed) return;
|
|
1395
|
+
closed = true;
|
|
1396
|
+
if (timer) clearTimeout(timer);
|
|
1397
|
+
observer.complete?.();
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
return {
|
|
1401
|
+
unsubscribe() {
|
|
1402
|
+
if (closed) return;
|
|
1403
|
+
closed = true;
|
|
1404
|
+
if (timer) clearTimeout(timer);
|
|
1405
|
+
sub.unsubscribe();
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
function catchErrorStream(handler) {
|
|
1412
|
+
return (source) => ({
|
|
1413
|
+
subscribe(observer) {
|
|
1414
|
+
let closed = false;
|
|
1415
|
+
let fallbackSub = null;
|
|
1416
|
+
const sourceSub = source.subscribe({
|
|
1417
|
+
next: (value) => {
|
|
1418
|
+
if (!closed) observer.next?.(value);
|
|
1419
|
+
},
|
|
1420
|
+
error: (error) => {
|
|
1421
|
+
if (closed) return;
|
|
1422
|
+
fallbackSub = toObservable(handler(error)).subscribe({
|
|
1423
|
+
next: (value) => {
|
|
1424
|
+
if (!closed) observer.next?.(value);
|
|
1425
|
+
},
|
|
1426
|
+
error: (innerErr) => {
|
|
1427
|
+
if (closed) return;
|
|
1428
|
+
closed = true;
|
|
1429
|
+
observer.error?.(innerErr);
|
|
1430
|
+
},
|
|
1431
|
+
complete: () => {
|
|
1432
|
+
if (closed) return;
|
|
1433
|
+
closed = true;
|
|
1434
|
+
observer.complete?.();
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
},
|
|
1438
|
+
complete: () => {
|
|
1439
|
+
if (closed) return;
|
|
1440
|
+
closed = true;
|
|
1441
|
+
observer.complete?.();
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
return {
|
|
1445
|
+
unsubscribe() {
|
|
1446
|
+
if (closed) return;
|
|
1447
|
+
closed = true;
|
|
1448
|
+
sourceSub.unsubscribe();
|
|
1449
|
+
fallbackSub?.unsubscribe();
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
function retryStream(options = {}) {
|
|
1456
|
+
const attempts = Math.max(1, options.attempts ?? 3);
|
|
1457
|
+
const delay = Math.max(0, options.delay ?? 0);
|
|
1458
|
+
const backoff = options.backoff ?? "fixed";
|
|
1459
|
+
return (source) => ({
|
|
1460
|
+
subscribe(observer) {
|
|
1461
|
+
let closed = false;
|
|
1462
|
+
let attempt = 0;
|
|
1463
|
+
let activeSub = null;
|
|
1464
|
+
let retryTimer = null;
|
|
1465
|
+
const subscribeOnce = () => {
|
|
1466
|
+
if (closed) return;
|
|
1467
|
+
attempt++;
|
|
1468
|
+
activeSub = source.subscribe({
|
|
1469
|
+
next: (value) => {
|
|
1470
|
+
if (!closed) observer.next?.(value);
|
|
1471
|
+
},
|
|
1472
|
+
complete: () => {
|
|
1473
|
+
if (closed) return;
|
|
1474
|
+
closed = true;
|
|
1475
|
+
observer.complete?.();
|
|
1476
|
+
},
|
|
1477
|
+
error: (error) => {
|
|
1478
|
+
if (closed) return;
|
|
1479
|
+
if (attempt >= attempts) {
|
|
1480
|
+
closed = true;
|
|
1481
|
+
observer.error?.(error);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
const wait = backoff === "exponential" ? delay * Math.pow(2, attempt - 1) : delay;
|
|
1485
|
+
retryTimer = setTimeout(() => {
|
|
1486
|
+
retryTimer = null;
|
|
1487
|
+
subscribeOnce();
|
|
1488
|
+
}, wait);
|
|
1489
|
+
}
|
|
1490
|
+
});
|
|
1491
|
+
};
|
|
1492
|
+
subscribeOnce();
|
|
1493
|
+
return {
|
|
1494
|
+
unsubscribe() {
|
|
1495
|
+
if (closed) return;
|
|
1496
|
+
closed = true;
|
|
1497
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
1498
|
+
activeSub?.unsubscribe();
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// src/helpers/with-persist.ts
|
|
1506
|
+
function resolveStorage(custom) {
|
|
1507
|
+
if (custom) return custom;
|
|
1508
|
+
if (typeof window === "undefined") return null;
|
|
1509
|
+
try {
|
|
1510
|
+
return window.localStorage;
|
|
1511
|
+
} catch {
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function pickState(state, keys) {
|
|
1516
|
+
if (!keys?.length) return state;
|
|
1517
|
+
const picked = {};
|
|
1518
|
+
for (const key of keys) {
|
|
1519
|
+
picked[key] = state[key];
|
|
1520
|
+
}
|
|
1521
|
+
return picked;
|
|
1522
|
+
}
|
|
1523
|
+
function withPersist(config, options) {
|
|
1524
|
+
const {
|
|
1525
|
+
key,
|
|
1526
|
+
version = 1,
|
|
1527
|
+
storage: customStorage,
|
|
1528
|
+
pick,
|
|
1529
|
+
migrate,
|
|
1530
|
+
onError
|
|
1531
|
+
} = options;
|
|
1532
|
+
const storage = resolveStorage(customStorage);
|
|
1533
|
+
const userHooks = config.hooks ?? {};
|
|
1534
|
+
const mergedHooks = {
|
|
1535
|
+
...userHooks,
|
|
1536
|
+
onInit(store) {
|
|
1537
|
+
try {
|
|
1538
|
+
if (!storage) return userHooks.onInit?.(store);
|
|
1539
|
+
const raw = storage.getItem(key);
|
|
1540
|
+
if (!raw) return userHooks.onInit?.(store);
|
|
1541
|
+
const parsed = JSON.parse(raw);
|
|
1542
|
+
const data = parsed.v === version ? parsed.data : migrate ? migrate(parsed.data, parsed.v) : parsed.data;
|
|
1543
|
+
if (data && typeof data === "object") {
|
|
1544
|
+
store.__store__?.hydrate?.(data);
|
|
1545
|
+
}
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
onError?.(error);
|
|
1548
|
+
}
|
|
1549
|
+
return userHooks.onInit?.(store);
|
|
1550
|
+
},
|
|
1551
|
+
onStateChange(prev, next) {
|
|
1552
|
+
try {
|
|
1553
|
+
if (storage) {
|
|
1554
|
+
const payload = {
|
|
1555
|
+
v: version,
|
|
1556
|
+
data: pickState(next, pick)
|
|
1557
|
+
};
|
|
1558
|
+
storage.setItem(key, JSON.stringify(payload));
|
|
1559
|
+
}
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
onError?.(error);
|
|
1562
|
+
}
|
|
1563
|
+
userHooks.onStateChange?.(prev, next);
|
|
1564
|
+
},
|
|
1565
|
+
onDestroy(store) {
|
|
1566
|
+
return userHooks.onDestroy?.(store);
|
|
1567
|
+
},
|
|
1568
|
+
onAction(name, args) {
|
|
1569
|
+
return userHooks.onAction?.(name, args);
|
|
1570
|
+
},
|
|
1571
|
+
onActionDone(name, duration) {
|
|
1572
|
+
return userHooks.onActionDone?.(name, duration);
|
|
1573
|
+
},
|
|
1574
|
+
onError(error, actionName) {
|
|
1575
|
+
return userHooks.onError?.(error, actionName);
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
return {
|
|
1579
|
+
...config,
|
|
1580
|
+
hooks: mergedHooks
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
|
|
369
1584
|
// src/devtools.ts
|
|
370
1585
|
function createDevTools(maxLogs = 50) {
|
|
371
1586
|
let counter = 0;
|
|
@@ -455,17 +1670,40 @@ function connectDevTools(store, storeName) {
|
|
|
455
1670
|
exports.StatoHttp = StatoHttp;
|
|
456
1671
|
exports.StatoHttpError = StatoHttpError;
|
|
457
1672
|
exports.abortable = abortable;
|
|
1673
|
+
exports.catchErrorStream = catchErrorStream;
|
|
1674
|
+
exports.combineLatest = combineLatest;
|
|
1675
|
+
exports.combineLatestStream = combineLatestStream;
|
|
1676
|
+
exports.concatMapStream = concatMapStream;
|
|
458
1677
|
exports.configureHttp = configureHttp;
|
|
459
1678
|
exports.connectDevTools = connectDevTools;
|
|
460
1679
|
exports.createDevTools = createDevTools;
|
|
1680
|
+
exports.createEntityAdapter = createEntityAdapter;
|
|
461
1681
|
exports.createHttp = createHttp;
|
|
462
1682
|
exports.createStore = createStore;
|
|
1683
|
+
exports.debounceStream = debounceStream;
|
|
463
1684
|
exports.debounced = debounced;
|
|
464
1685
|
exports.devTools = devTools;
|
|
1686
|
+
exports.distinctUntilChanged = distinctUntilChanged;
|
|
1687
|
+
exports.distinctUntilChangedStream = distinctUntilChangedStream;
|
|
1688
|
+
exports.exclusive = exclusive;
|
|
1689
|
+
exports.exhaustMapStream = exhaustMapStream;
|
|
1690
|
+
exports.filterStream = filterStream;
|
|
1691
|
+
exports.forkJoin = forkJoin;
|
|
465
1692
|
exports.fromStream = fromStream;
|
|
466
1693
|
exports.http = http;
|
|
1694
|
+
exports.mapStream = mapStream;
|
|
1695
|
+
exports.mergeMapStream = mergeMapStream;
|
|
1696
|
+
exports.on = on;
|
|
467
1697
|
exports.optimistic = optimistic;
|
|
1698
|
+
exports.pipeStream = pipeStream;
|
|
1699
|
+
exports.queued = queued;
|
|
1700
|
+
exports.race = race;
|
|
1701
|
+
exports.retryStream = retryStream;
|
|
468
1702
|
exports.retryable = retryable;
|
|
1703
|
+
exports.switchMapStream = switchMapStream;
|
|
1704
|
+
exports.throttleStream = throttleStream;
|
|
469
1705
|
exports.throttled = throttled;
|
|
1706
|
+
exports.withEntities = withEntities;
|
|
1707
|
+
exports.withPersist = withPersist;
|
|
470
1708
|
//# sourceMappingURL=index.js.map
|
|
471
1709
|
//# sourceMappingURL=index.js.map
|