@ngstato/core 0.2.0 → 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/dist/index.mjs CHANGED
@@ -1,3 +1,27 @@
1
+ // src/action-bus.ts
2
+ var listenersByAction = /* @__PURE__ */ new WeakMap();
3
+ function emitActionEvent(event) {
4
+ const set = listenersByAction.get(event.action);
5
+ if (!set?.size) return;
6
+ for (const listener of set) {
7
+ try {
8
+ listener(event);
9
+ } catch {
10
+ }
11
+ }
12
+ }
13
+ function subscribeToAction(action, listener) {
14
+ let set = listenersByAction.get(action);
15
+ if (!set) {
16
+ set = /* @__PURE__ */ new Set();
17
+ listenersByAction.set(action, set);
18
+ }
19
+ set.add(listener);
20
+ return () => {
21
+ set?.delete(listener);
22
+ };
23
+ }
24
+
1
25
  // src/store.ts
2
26
  var StatoStore = class {
3
27
  // Le state interne — jamais accessible directement
@@ -14,6 +38,8 @@ var StatoStore = class {
14
38
  // Les hooks lifecycle
15
39
  _hooks;
16
40
  _publicStore = null;
41
+ _publicActions = {};
42
+ _initialized = false;
17
43
  _effects = [];
18
44
  _createMemoizedSelector(fn) {
19
45
  let initialized = false;
@@ -156,6 +182,7 @@ var StatoStore = class {
156
182
  if (!action) {
157
183
  throw new Error(`[Stato] Action "${actionName}" introuvable`);
158
184
  }
185
+ const publicAction = this._publicActions[actionName];
159
186
  this._hooks.onAction?.(actionName, args);
160
187
  const start = Date.now();
161
188
  const prevState = { ...this._state };
@@ -168,10 +195,32 @@ var StatoStore = class {
168
195
  });
169
196
  try {
170
197
  await action(stateProxy, ...args);
171
- this._hooks.onActionDone?.(actionName, Date.now() - start);
198
+ const duration = Date.now() - start;
199
+ this._hooks.onActionDone?.(actionName, duration);
172
200
  this._hooks.onStateChange?.(prevState, { ...this._state });
201
+ if (publicAction) {
202
+ emitActionEvent({
203
+ action: publicAction,
204
+ name: actionName,
205
+ args,
206
+ store: this._publicStore,
207
+ status: "success",
208
+ duration
209
+ });
210
+ }
173
211
  } catch (error) {
174
212
  this._hooks.onError?.(error, actionName);
213
+ if (publicAction) {
214
+ emitActionEvent({
215
+ action: publicAction,
216
+ name: actionName,
217
+ args,
218
+ store: this._publicStore,
219
+ status: "error",
220
+ duration: Date.now() - start,
221
+ error
222
+ });
223
+ }
175
224
  throw error;
176
225
  }
177
226
  }
@@ -190,6 +239,9 @@ var StatoStore = class {
190
239
  registerCleanup(fn) {
191
240
  this._cleanups.push(fn);
192
241
  }
242
+ registerPublicAction(name, fn) {
243
+ this._publicActions[name] = fn;
244
+ }
193
245
  hydrate(partial) {
194
246
  this._setState(partial);
195
247
  }
@@ -200,7 +252,10 @@ var StatoStore = class {
200
252
  // ── Lifecycle — appelé par l'adaptateur Angular ────
201
253
  init(publicStore) {
202
254
  this._publicStore = publicStore;
203
- this._hooks.onInit?.(publicStore);
255
+ if (!this._initialized) {
256
+ this._initialized = true;
257
+ this._hooks.onInit?.(publicStore);
258
+ }
204
259
  this._runEffects(true);
205
260
  }
206
261
  destroy(publicStore) {
@@ -214,6 +269,7 @@ var StatoStore = class {
214
269
  }
215
270
  this._cleanups = [];
216
271
  this._subscribers.clear();
272
+ this._initialized = false;
217
273
  }
218
274
  };
219
275
  function createStore(config) {
@@ -239,7 +295,9 @@ function createStore(config) {
239
295
  const { actions, computed, selectors } = config;
240
296
  if (actions) {
241
297
  for (const name of Object.keys(actions)) {
242
- publicStore[name] = (...args) => store.dispatch(name, ...args);
298
+ const fn = (...args) => store.dispatch(name, ...args);
299
+ publicStore[name] = fn;
300
+ store.registerPublicAction(name, fn);
243
301
  }
244
302
  }
245
303
  if (computed) {
@@ -260,9 +318,23 @@ function createStore(config) {
260
318
  });
261
319
  }
262
320
  }
263
- store.setPublicStore(publicStore);
321
+ store.init(publicStore);
264
322
  return publicStore;
265
323
  }
324
+ function on(sourceAction, handler) {
325
+ return subscribeToAction(sourceAction, (event) => {
326
+ try {
327
+ void handler(event.store, {
328
+ name: event.name,
329
+ args: event.args,
330
+ status: event.status,
331
+ duration: event.duration,
332
+ error: event.error
333
+ });
334
+ } catch {
335
+ }
336
+ });
337
+ }
266
338
 
267
339
  // src/types.ts
268
340
  var StatoHttpError = class extends Error {
@@ -493,6 +565,941 @@ function optimistic(immediate, confirm) {
493
565
  };
494
566
  }
495
567
 
568
+ // src/helpers/exclusive.ts
569
+ function exclusive(fn) {
570
+ let running = false;
571
+ let current = null;
572
+ return (state, ...args) => {
573
+ if (running && current) return current;
574
+ running = true;
575
+ current = (async () => {
576
+ try {
577
+ await fn(state, ...args);
578
+ } finally {
579
+ running = false;
580
+ current = null;
581
+ }
582
+ })();
583
+ return current;
584
+ };
585
+ }
586
+
587
+ // src/helpers/queued.ts
588
+ function queued(fn) {
589
+ const queue = [];
590
+ let processing = false;
591
+ const processNext = () => {
592
+ if (processing) return;
593
+ processing = true;
594
+ const run = async () => {
595
+ while (queue.length) {
596
+ const item = queue.shift();
597
+ if (!item) break;
598
+ try {
599
+ await fn(item.state, ...item.args);
600
+ item.resolve();
601
+ } catch (err) {
602
+ item.reject(err);
603
+ while (queue.length) {
604
+ const rest = queue.shift();
605
+ rest?.reject(err);
606
+ }
607
+ return;
608
+ }
609
+ }
610
+ };
611
+ void run().finally(() => {
612
+ processing = false;
613
+ });
614
+ };
615
+ return (state, ...args) => {
616
+ return new Promise((resolve, reject) => {
617
+ queue.push({ state, args, resolve, reject });
618
+ processNext();
619
+ });
620
+ };
621
+ }
622
+
623
+ // src/helpers/distinct-until-changed.ts
624
+ function distinctUntilChanged(fn, keySelector, comparator = Object.is) {
625
+ let initialized = false;
626
+ let prevKey;
627
+ return async (state, ...args) => {
628
+ const nextKey = keySelector(...args);
629
+ if (initialized && comparator(prevKey, nextKey)) {
630
+ return;
631
+ }
632
+ initialized = true;
633
+ prevKey = nextKey;
634
+ await fn(state, ...args);
635
+ };
636
+ }
637
+
638
+ // src/helpers/fork-join.ts
639
+ async function forkJoin(tasks, options) {
640
+ const controller = new AbortController();
641
+ const signal = options?.signal;
642
+ if (signal) {
643
+ if (signal.aborted) controller.abort();
644
+ else signal.addEventListener("abort", () => controller.abort(), { once: true });
645
+ }
646
+ const entries = Object.entries(tasks);
647
+ const results = await Promise.all(entries.map(async ([key, task]) => {
648
+ const value = await task({ signal: controller.signal });
649
+ return [key, value];
650
+ }));
651
+ return Object.fromEntries(results);
652
+ }
653
+
654
+ // src/helpers/race.ts
655
+ async function race(tasks, options) {
656
+ const controller = new AbortController();
657
+ const outer = options?.signal;
658
+ if (outer) {
659
+ if (outer.aborted) controller.abort();
660
+ else outer.addEventListener("abort", () => controller.abort(), { once: true });
661
+ }
662
+ const wrapped = tasks.map((task) => (async () => task({ signal: controller.signal }))());
663
+ try {
664
+ return await Promise.race(wrapped);
665
+ } finally {
666
+ controller.abort();
667
+ }
668
+ }
669
+
670
+ // src/helpers/combine-latest.ts
671
+ function combineLatest() {
672
+ return (...deps) => {
673
+ return (state) => deps.map((fn) => fn(state));
674
+ };
675
+ }
676
+
677
+ // src/helpers/combine-latest-stream.ts
678
+ function combineLatestStream(...sources) {
679
+ return {
680
+ subscribe(observer) {
681
+ const n = sources.length;
682
+ if (!n) {
683
+ observer.complete?.();
684
+ return { unsubscribe() {
685
+ } };
686
+ }
687
+ const hasValue = new Array(n).fill(false);
688
+ const values = new Array(n);
689
+ let completed = 0;
690
+ let closed = false;
691
+ const subs = [];
692
+ const tryEmit = () => {
693
+ if (closed) return;
694
+ if (hasValue.every(Boolean)) {
695
+ observer.next?.(values.slice());
696
+ }
697
+ };
698
+ const closeAll = () => {
699
+ if (closed) return;
700
+ closed = true;
701
+ for (const s of subs) {
702
+ try {
703
+ s.unsubscribe();
704
+ } catch {
705
+ }
706
+ }
707
+ };
708
+ sources.forEach((src, index) => {
709
+ const sub = src.subscribe({
710
+ next: (v) => {
711
+ if (closed) return;
712
+ values[index] = v;
713
+ hasValue[index] = true;
714
+ tryEmit();
715
+ },
716
+ error: (err) => {
717
+ if (closed) return;
718
+ observer.error?.(err);
719
+ closeAll();
720
+ },
721
+ complete: () => {
722
+ if (closed) return;
723
+ completed++;
724
+ if (completed >= n) {
725
+ observer.complete?.();
726
+ closeAll();
727
+ }
728
+ }
729
+ });
730
+ subs.push(sub);
731
+ });
732
+ return {
733
+ unsubscribe() {
734
+ closeAll();
735
+ }
736
+ };
737
+ }
738
+ };
739
+ }
740
+
741
+ // src/helpers/entity-adapter.ts
742
+ function defaultSelectId(entity) {
743
+ return entity.id;
744
+ }
745
+ function idKey(id) {
746
+ return String(id);
747
+ }
748
+ function ensureSort(state, sortComparer) {
749
+ if (!sortComparer) return;
750
+ state.ids.sort((a, b) => {
751
+ const ea = state.entities[idKey(a)];
752
+ const eb = state.entities[idKey(b)];
753
+ if (!ea || !eb) return 0;
754
+ return sortComparer(ea, eb);
755
+ });
756
+ }
757
+ function createEntityAdapter(options = {}) {
758
+ const selectId = options.selectId ?? defaultSelectId;
759
+ const sortComparer = options.sortComparer;
760
+ const getInitialState = (extra) => {
761
+ return {
762
+ ids: [],
763
+ entities: {},
764
+ ...extra ?? {}
765
+ };
766
+ };
767
+ const addOne = (entity, state) => {
768
+ const id = selectId(entity);
769
+ const key = idKey(id);
770
+ if (state.entities[key]) return;
771
+ state.ids.push(id);
772
+ state.entities[key] = entity;
773
+ ensureSort(state, sortComparer);
774
+ };
775
+ const addMany = (entities, state) => {
776
+ for (const entity of entities) addOne(entity, state);
777
+ };
778
+ const setAll = (entities, state) => {
779
+ state.ids = [];
780
+ state.entities = {};
781
+ for (const entity of entities) {
782
+ const id = selectId(entity);
783
+ state.ids.push(id);
784
+ state.entities[idKey(id)] = entity;
785
+ }
786
+ ensureSort(state, sortComparer);
787
+ };
788
+ const upsertOne = (entity, state) => {
789
+ const id = selectId(entity);
790
+ const key = idKey(id);
791
+ const exists = !!state.entities[key];
792
+ state.entities[key] = entity;
793
+ if (!exists) state.ids.push(id);
794
+ ensureSort(state, sortComparer);
795
+ };
796
+ const upsertMany = (entities, state) => {
797
+ for (const entity of entities) upsertOne(entity, state);
798
+ };
799
+ const updateOne = (update, state) => {
800
+ const key = idKey(update.id);
801
+ const current = state.entities[key];
802
+ if (!current) return;
803
+ state.entities[key] = { ...current, ...update.changes };
804
+ ensureSort(state, sortComparer);
805
+ };
806
+ const removeOne = (id, state) => {
807
+ const key = idKey(id);
808
+ if (!state.entities[key]) return;
809
+ delete state.entities[key];
810
+ state.ids = state.ids.filter((x) => !Object.is(x, id));
811
+ };
812
+ const removeMany = (ids, state) => {
813
+ const removeSet = new Set(ids.map(idKey));
814
+ for (const key of Object.keys(state.entities)) {
815
+ if (removeSet.has(key)) delete state.entities[key];
816
+ }
817
+ state.ids = state.ids.filter((id) => !removeSet.has(idKey(id)));
818
+ };
819
+ const removeAll = (state) => {
820
+ state.ids = [];
821
+ state.entities = {};
822
+ };
823
+ const getSelectors = (selectState) => {
824
+ const pick = (state) => selectState ? selectState(state) : state;
825
+ return {
826
+ selectIds: (state) => pick(state).ids,
827
+ selectEntities: (state) => pick(state).entities,
828
+ selectAll: (state) => {
829
+ const s = pick(state);
830
+ return s.ids.map((id) => s.entities[idKey(id)]).filter(Boolean);
831
+ },
832
+ selectTotal: (state) => pick(state).ids.length,
833
+ selectById: (state, id) => pick(state).entities[idKey(id)]
834
+ };
835
+ };
836
+ return {
837
+ selectId,
838
+ sortComparer,
839
+ getInitialState,
840
+ addOne,
841
+ addMany,
842
+ setAll,
843
+ upsertOne,
844
+ upsertMany,
845
+ updateOne,
846
+ removeOne,
847
+ removeMany,
848
+ removeAll,
849
+ getSelectors
850
+ };
851
+ }
852
+
853
+ // src/helpers/with-entities.ts
854
+ function cloneEntityState(state) {
855
+ return {
856
+ ids: [...state.ids],
857
+ entities: { ...state.entities }
858
+ };
859
+ }
860
+ function withEntities(config, options) {
861
+ const { key, adapter, initial } = options;
862
+ const initialSlice = adapter.getInitialState();
863
+ if (initial?.length) {
864
+ adapter.setAll(initial, initialSlice);
865
+ }
866
+ const baseSelectors = config.selectors ?? {};
867
+ const baseActions = config.actions ?? {};
868
+ const scopedSelectors = adapter.getSelectors((s) => s[key]);
869
+ const selectorNames = {
870
+ ids: options.selectors?.ids ?? `${key}Ids`,
871
+ entities: options.selectors?.entities ?? `${key}Entities`,
872
+ all: options.selectors?.all ?? `${key}All`,
873
+ total: options.selectors?.total ?? `${key}Total`,
874
+ byId: options.selectors?.byId ?? `${key}ById`
875
+ };
876
+ const actionNames = {
877
+ addOne: options.actions?.addOne ?? `${key}AddOne`,
878
+ addMany: options.actions?.addMany ?? `${key}AddMany`,
879
+ setAll: options.actions?.setAll ?? `${key}SetAll`,
880
+ upsertOne: options.actions?.upsertOne ?? `${key}UpsertOne`,
881
+ upsertMany: options.actions?.upsertMany ?? `${key}UpsertMany`,
882
+ updateOne: options.actions?.updateOne ?? `${key}UpdateOne`,
883
+ removeOne: options.actions?.removeOne ?? `${key}RemoveOne`,
884
+ removeMany: options.actions?.removeMany ?? `${key}RemoveMany`,
885
+ removeAll: options.actions?.removeAll ?? `${key}RemoveAll`
886
+ };
887
+ const nextSelectors = {
888
+ ...baseSelectors,
889
+ [selectorNames.ids]: (state) => scopedSelectors.selectIds(state),
890
+ [selectorNames.entities]: (state) => scopedSelectors.selectEntities(state),
891
+ [selectorNames.all]: (state) => scopedSelectors.selectAll(state),
892
+ [selectorNames.total]: (state) => scopedSelectors.selectTotal(state),
893
+ [selectorNames.byId]: (state) => (id) => scopedSelectors.selectById(state, id)
894
+ };
895
+ const nextActions = {
896
+ ...baseActions,
897
+ [actionNames.addOne]: (state, entity) => {
898
+ const prev = state[key];
899
+ const next = cloneEntityState(prev);
900
+ adapter.addOne(entity, next);
901
+ state[key] = next;
902
+ },
903
+ [actionNames.addMany]: (state, entities) => {
904
+ const prev = state[key];
905
+ const next = cloneEntityState(prev);
906
+ adapter.addMany(entities, next);
907
+ state[key] = next;
908
+ },
909
+ [actionNames.setAll]: (state, entities) => {
910
+ const next = adapter.getInitialState();
911
+ adapter.setAll(entities, next);
912
+ state[key] = next;
913
+ },
914
+ [actionNames.upsertOne]: (state, entity) => {
915
+ const prev = state[key];
916
+ const next = cloneEntityState(prev);
917
+ adapter.upsertOne(entity, next);
918
+ state[key] = next;
919
+ },
920
+ [actionNames.upsertMany]: (state, entities) => {
921
+ const prev = state[key];
922
+ const next = cloneEntityState(prev);
923
+ adapter.upsertMany(entities, next);
924
+ state[key] = next;
925
+ },
926
+ [actionNames.updateOne]: (state, update) => {
927
+ const prev = state[key];
928
+ const next = cloneEntityState(prev);
929
+ adapter.updateOne(update, next);
930
+ state[key] = next;
931
+ },
932
+ [actionNames.removeOne]: (state, id) => {
933
+ const prev = state[key];
934
+ const next = cloneEntityState(prev);
935
+ adapter.removeOne(id, next);
936
+ state[key] = next;
937
+ },
938
+ [actionNames.removeMany]: (state, ids) => {
939
+ const prev = state[key];
940
+ const next = cloneEntityState(prev);
941
+ adapter.removeMany(ids, next);
942
+ state[key] = next;
943
+ },
944
+ [actionNames.removeAll]: (state) => {
945
+ state[key] = adapter.getInitialState();
946
+ }
947
+ };
948
+ return {
949
+ ...config,
950
+ [key]: config[key] ?? initialSlice,
951
+ actions: nextActions,
952
+ selectors: nextSelectors
953
+ };
954
+ }
955
+
956
+ // src/helpers/stream-operators.ts
957
+ function pipeStream(source, ...ops) {
958
+ return ops.reduce((acc, op) => op(acc), source);
959
+ }
960
+ function isObservable(value) {
961
+ return !!value && typeof value === "object" && typeof value.subscribe === "function";
962
+ }
963
+ function toObservable(value) {
964
+ if (isObservable(value)) return value;
965
+ return {
966
+ subscribe(observer) {
967
+ let closed = false;
968
+ Promise.resolve(value).then((resolved) => {
969
+ if (closed) return;
970
+ observer.next?.(resolved);
971
+ observer.complete?.();
972
+ }).catch((error) => {
973
+ if (closed) return;
974
+ observer.error?.(error);
975
+ });
976
+ return { unsubscribe: () => {
977
+ closed = true;
978
+ } };
979
+ }
980
+ };
981
+ }
982
+ function mapStream(mapFn) {
983
+ return (source) => ({
984
+ subscribe(observer) {
985
+ return source.subscribe({
986
+ next: (value) => observer.next?.(mapFn(value)),
987
+ error: (error) => observer.error?.(error),
988
+ complete: () => observer.complete?.()
989
+ });
990
+ }
991
+ });
992
+ }
993
+ function filterStream(predicate) {
994
+ return (source) => ({
995
+ subscribe(observer) {
996
+ return source.subscribe({
997
+ next: (value) => {
998
+ if (predicate(value)) observer.next?.(value);
999
+ },
1000
+ error: (error) => observer.error?.(error),
1001
+ complete: () => observer.complete?.()
1002
+ });
1003
+ }
1004
+ });
1005
+ }
1006
+ function closeSubs(subs) {
1007
+ for (const sub of subs) {
1008
+ try {
1009
+ sub.unsubscribe();
1010
+ } catch {
1011
+ }
1012
+ }
1013
+ }
1014
+ function switchMapStream(mapper) {
1015
+ return (source) => ({
1016
+ subscribe(observer) {
1017
+ let closed = false;
1018
+ let sourceDone = false;
1019
+ let innerSub = null;
1020
+ let innerActive = false;
1021
+ let controller = null;
1022
+ const maybeComplete = () => {
1023
+ if (!closed && sourceDone && !innerActive) {
1024
+ closed = true;
1025
+ observer.complete?.();
1026
+ }
1027
+ };
1028
+ const sourceSub = source.subscribe({
1029
+ next: (value) => {
1030
+ if (closed) return;
1031
+ controller?.abort();
1032
+ innerSub?.unsubscribe();
1033
+ controller = new AbortController();
1034
+ innerActive = true;
1035
+ innerSub = toObservable(mapper(value, { signal: controller.signal })).subscribe({
1036
+ next: (v) => {
1037
+ if (!closed) observer.next?.(v);
1038
+ },
1039
+ error: (error) => {
1040
+ if (closed) return;
1041
+ closed = true;
1042
+ observer.error?.(error);
1043
+ sourceSub.unsubscribe();
1044
+ innerSub?.unsubscribe();
1045
+ },
1046
+ complete: () => {
1047
+ innerActive = false;
1048
+ maybeComplete();
1049
+ }
1050
+ });
1051
+ },
1052
+ error: (error) => {
1053
+ if (closed) return;
1054
+ closed = true;
1055
+ observer.error?.(error);
1056
+ controller?.abort();
1057
+ innerSub?.unsubscribe();
1058
+ },
1059
+ complete: () => {
1060
+ sourceDone = true;
1061
+ maybeComplete();
1062
+ }
1063
+ });
1064
+ return {
1065
+ unsubscribe() {
1066
+ if (closed) return;
1067
+ closed = true;
1068
+ controller?.abort();
1069
+ sourceSub.unsubscribe();
1070
+ innerSub?.unsubscribe();
1071
+ }
1072
+ };
1073
+ }
1074
+ });
1075
+ }
1076
+ function concatMapStream(mapper) {
1077
+ return (source) => ({
1078
+ subscribe(observer) {
1079
+ let closed = false;
1080
+ let sourceDone = false;
1081
+ const queue = [];
1082
+ let running = false;
1083
+ let currentSub = null;
1084
+ let currentController = null;
1085
+ const maybeComplete = () => {
1086
+ if (!closed && sourceDone && !running && queue.length === 0) {
1087
+ closed = true;
1088
+ observer.complete?.();
1089
+ }
1090
+ };
1091
+ const runNext = () => {
1092
+ if (closed || running || queue.length === 0) {
1093
+ maybeComplete();
1094
+ return;
1095
+ }
1096
+ running = true;
1097
+ const value = queue.shift();
1098
+ currentController = new AbortController();
1099
+ currentSub = toObservable(mapper(value, { signal: currentController.signal })).subscribe({
1100
+ next: (v) => {
1101
+ if (!closed) observer.next?.(v);
1102
+ },
1103
+ error: (error) => {
1104
+ if (closed) return;
1105
+ closed = true;
1106
+ observer.error?.(error);
1107
+ sourceSub.unsubscribe();
1108
+ currentController?.abort();
1109
+ currentSub?.unsubscribe();
1110
+ queue.length = 0;
1111
+ },
1112
+ complete: () => {
1113
+ running = false;
1114
+ runNext();
1115
+ }
1116
+ });
1117
+ };
1118
+ const sourceSub = source.subscribe({
1119
+ next: (value) => {
1120
+ if (closed) return;
1121
+ queue.push(value);
1122
+ runNext();
1123
+ },
1124
+ error: (error) => {
1125
+ if (closed) return;
1126
+ closed = true;
1127
+ observer.error?.(error);
1128
+ currentController?.abort();
1129
+ currentSub?.unsubscribe();
1130
+ queue.length = 0;
1131
+ },
1132
+ complete: () => {
1133
+ sourceDone = true;
1134
+ maybeComplete();
1135
+ }
1136
+ });
1137
+ return {
1138
+ unsubscribe() {
1139
+ if (closed) return;
1140
+ closed = true;
1141
+ sourceSub.unsubscribe();
1142
+ currentController?.abort();
1143
+ currentSub?.unsubscribe();
1144
+ queue.length = 0;
1145
+ }
1146
+ };
1147
+ }
1148
+ });
1149
+ }
1150
+ function exhaustMapStream(mapper) {
1151
+ return (source) => ({
1152
+ subscribe(observer) {
1153
+ let closed = false;
1154
+ let sourceDone = false;
1155
+ let running = false;
1156
+ let currentSub = null;
1157
+ let controller = null;
1158
+ const maybeComplete = () => {
1159
+ if (!closed && sourceDone && !running) {
1160
+ closed = true;
1161
+ observer.complete?.();
1162
+ }
1163
+ };
1164
+ const sourceSub = source.subscribe({
1165
+ next: (value) => {
1166
+ if (closed || running) return;
1167
+ running = true;
1168
+ controller = new AbortController();
1169
+ currentSub = toObservable(mapper(value, { signal: controller.signal })).subscribe({
1170
+ next: (v) => {
1171
+ if (!closed) observer.next?.(v);
1172
+ },
1173
+ error: (error) => {
1174
+ if (closed) return;
1175
+ closed = true;
1176
+ observer.error?.(error);
1177
+ sourceSub.unsubscribe();
1178
+ controller?.abort();
1179
+ currentSub?.unsubscribe();
1180
+ },
1181
+ complete: () => {
1182
+ running = false;
1183
+ maybeComplete();
1184
+ }
1185
+ });
1186
+ },
1187
+ error: (error) => {
1188
+ if (closed) return;
1189
+ closed = true;
1190
+ observer.error?.(error);
1191
+ controller?.abort();
1192
+ currentSub?.unsubscribe();
1193
+ },
1194
+ complete: () => {
1195
+ sourceDone = true;
1196
+ maybeComplete();
1197
+ }
1198
+ });
1199
+ return {
1200
+ unsubscribe() {
1201
+ if (closed) return;
1202
+ closed = true;
1203
+ sourceSub.unsubscribe();
1204
+ controller?.abort();
1205
+ currentSub?.unsubscribe();
1206
+ }
1207
+ };
1208
+ }
1209
+ });
1210
+ }
1211
+ function mergeMapStream(mapper, options) {
1212
+ const concurrency = Math.max(1, options?.concurrency ?? Number.POSITIVE_INFINITY);
1213
+ return (source) => ({
1214
+ subscribe(observer) {
1215
+ let closed = false;
1216
+ let sourceDone = false;
1217
+ const queue = [];
1218
+ const active = /* @__PURE__ */ new Set();
1219
+ const controllers = /* @__PURE__ */ new Set();
1220
+ const maybeComplete = () => {
1221
+ if (!closed && sourceDone && active.size === 0 && queue.length === 0) {
1222
+ closed = true;
1223
+ observer.complete?.();
1224
+ }
1225
+ };
1226
+ const spawn = (value) => {
1227
+ const controller = new AbortController();
1228
+ controllers.add(controller);
1229
+ const sub = toObservable(mapper(value, { signal: controller.signal })).subscribe({
1230
+ next: (v) => {
1231
+ if (!closed) observer.next?.(v);
1232
+ },
1233
+ error: (error) => {
1234
+ if (closed) return;
1235
+ closed = true;
1236
+ observer.error?.(error);
1237
+ sourceSub.unsubscribe();
1238
+ closeSubs(Array.from(active));
1239
+ active.clear();
1240
+ for (const c of controllers) c.abort();
1241
+ controllers.clear();
1242
+ queue.length = 0;
1243
+ },
1244
+ complete: () => {
1245
+ active.delete(sub);
1246
+ controllers.delete(controller);
1247
+ drain();
1248
+ maybeComplete();
1249
+ }
1250
+ });
1251
+ active.add(sub);
1252
+ };
1253
+ const drain = () => {
1254
+ while (!closed && active.size < concurrency && queue.length > 0) {
1255
+ spawn(queue.shift());
1256
+ }
1257
+ };
1258
+ const sourceSub = source.subscribe({
1259
+ next: (value) => {
1260
+ if (closed) return;
1261
+ queue.push(value);
1262
+ drain();
1263
+ },
1264
+ error: (error) => {
1265
+ if (closed) return;
1266
+ closed = true;
1267
+ observer.error?.(error);
1268
+ closeSubs(Array.from(active));
1269
+ active.clear();
1270
+ for (const c of controllers) c.abort();
1271
+ controllers.clear();
1272
+ queue.length = 0;
1273
+ },
1274
+ complete: () => {
1275
+ sourceDone = true;
1276
+ maybeComplete();
1277
+ }
1278
+ });
1279
+ return {
1280
+ unsubscribe() {
1281
+ if (closed) return;
1282
+ closed = true;
1283
+ sourceSub.unsubscribe();
1284
+ closeSubs(Array.from(active));
1285
+ active.clear();
1286
+ for (const c of controllers) c.abort();
1287
+ controllers.clear();
1288
+ queue.length = 0;
1289
+ }
1290
+ };
1291
+ }
1292
+ });
1293
+ }
1294
+ function distinctUntilChangedStream(keySelector, comparator = Object.is) {
1295
+ return (source) => ({
1296
+ subscribe(observer) {
1297
+ let initialized = false;
1298
+ let prevKey;
1299
+ return source.subscribe({
1300
+ next: (value) => {
1301
+ const nextKey = keySelector ? keySelector(value) : value;
1302
+ if (initialized && comparator(prevKey, nextKey)) return;
1303
+ initialized = true;
1304
+ prevKey = nextKey;
1305
+ observer.next?.(value);
1306
+ },
1307
+ error: (error) => observer.error?.(error),
1308
+ complete: () => observer.complete?.()
1309
+ });
1310
+ }
1311
+ });
1312
+ }
1313
+ function debounceStream(ms) {
1314
+ return (source) => ({
1315
+ subscribe(observer) {
1316
+ let timer = null;
1317
+ let sourceDone = false;
1318
+ let lastValue;
1319
+ let hasValue = false;
1320
+ let closed = false;
1321
+ const flush = () => {
1322
+ if (!hasValue || closed) return;
1323
+ observer.next?.(lastValue);
1324
+ hasValue = false;
1325
+ lastValue = void 0;
1326
+ };
1327
+ const maybeComplete = () => {
1328
+ if (sourceDone && !timer && !closed) {
1329
+ closed = true;
1330
+ observer.complete?.();
1331
+ }
1332
+ };
1333
+ const sub = source.subscribe({
1334
+ next: (value) => {
1335
+ if (closed) return;
1336
+ lastValue = value;
1337
+ hasValue = true;
1338
+ if (timer) clearTimeout(timer);
1339
+ timer = setTimeout(() => {
1340
+ timer = null;
1341
+ flush();
1342
+ maybeComplete();
1343
+ }, ms);
1344
+ },
1345
+ error: (error) => {
1346
+ if (closed) return;
1347
+ closed = true;
1348
+ if (timer) clearTimeout(timer);
1349
+ observer.error?.(error);
1350
+ },
1351
+ complete: () => {
1352
+ sourceDone = true;
1353
+ if (!timer) {
1354
+ maybeComplete();
1355
+ }
1356
+ }
1357
+ });
1358
+ return {
1359
+ unsubscribe() {
1360
+ if (closed) return;
1361
+ closed = true;
1362
+ if (timer) clearTimeout(timer);
1363
+ sub.unsubscribe();
1364
+ }
1365
+ };
1366
+ }
1367
+ });
1368
+ }
1369
+ function throttleStream(ms) {
1370
+ return (source) => ({
1371
+ subscribe(observer) {
1372
+ let throttled2 = false;
1373
+ let timer = null;
1374
+ let closed = false;
1375
+ const sub = source.subscribe({
1376
+ next: (value) => {
1377
+ if (closed || throttled2) return;
1378
+ observer.next?.(value);
1379
+ throttled2 = true;
1380
+ timer = setTimeout(() => {
1381
+ throttled2 = false;
1382
+ timer = null;
1383
+ }, ms);
1384
+ },
1385
+ error: (error) => {
1386
+ if (closed) return;
1387
+ closed = true;
1388
+ if (timer) clearTimeout(timer);
1389
+ observer.error?.(error);
1390
+ },
1391
+ complete: () => {
1392
+ if (closed) return;
1393
+ closed = true;
1394
+ if (timer) clearTimeout(timer);
1395
+ observer.complete?.();
1396
+ }
1397
+ });
1398
+ return {
1399
+ unsubscribe() {
1400
+ if (closed) return;
1401
+ closed = true;
1402
+ if (timer) clearTimeout(timer);
1403
+ sub.unsubscribe();
1404
+ }
1405
+ };
1406
+ }
1407
+ });
1408
+ }
1409
+ function catchErrorStream(handler) {
1410
+ return (source) => ({
1411
+ subscribe(observer) {
1412
+ let closed = false;
1413
+ let fallbackSub = null;
1414
+ const sourceSub = source.subscribe({
1415
+ next: (value) => {
1416
+ if (!closed) observer.next?.(value);
1417
+ },
1418
+ error: (error) => {
1419
+ if (closed) return;
1420
+ fallbackSub = toObservable(handler(error)).subscribe({
1421
+ next: (value) => {
1422
+ if (!closed) observer.next?.(value);
1423
+ },
1424
+ error: (innerErr) => {
1425
+ if (closed) return;
1426
+ closed = true;
1427
+ observer.error?.(innerErr);
1428
+ },
1429
+ complete: () => {
1430
+ if (closed) return;
1431
+ closed = true;
1432
+ observer.complete?.();
1433
+ }
1434
+ });
1435
+ },
1436
+ complete: () => {
1437
+ if (closed) return;
1438
+ closed = true;
1439
+ observer.complete?.();
1440
+ }
1441
+ });
1442
+ return {
1443
+ unsubscribe() {
1444
+ if (closed) return;
1445
+ closed = true;
1446
+ sourceSub.unsubscribe();
1447
+ fallbackSub?.unsubscribe();
1448
+ }
1449
+ };
1450
+ }
1451
+ });
1452
+ }
1453
+ function retryStream(options = {}) {
1454
+ const attempts = Math.max(1, options.attempts ?? 3);
1455
+ const delay = Math.max(0, options.delay ?? 0);
1456
+ const backoff = options.backoff ?? "fixed";
1457
+ return (source) => ({
1458
+ subscribe(observer) {
1459
+ let closed = false;
1460
+ let attempt = 0;
1461
+ let activeSub = null;
1462
+ let retryTimer = null;
1463
+ const subscribeOnce = () => {
1464
+ if (closed) return;
1465
+ attempt++;
1466
+ activeSub = source.subscribe({
1467
+ next: (value) => {
1468
+ if (!closed) observer.next?.(value);
1469
+ },
1470
+ complete: () => {
1471
+ if (closed) return;
1472
+ closed = true;
1473
+ observer.complete?.();
1474
+ },
1475
+ error: (error) => {
1476
+ if (closed) return;
1477
+ if (attempt >= attempts) {
1478
+ closed = true;
1479
+ observer.error?.(error);
1480
+ return;
1481
+ }
1482
+ const wait = backoff === "exponential" ? delay * Math.pow(2, attempt - 1) : delay;
1483
+ retryTimer = setTimeout(() => {
1484
+ retryTimer = null;
1485
+ subscribeOnce();
1486
+ }, wait);
1487
+ }
1488
+ });
1489
+ };
1490
+ subscribeOnce();
1491
+ return {
1492
+ unsubscribe() {
1493
+ if (closed) return;
1494
+ closed = true;
1495
+ if (retryTimer) clearTimeout(retryTimer);
1496
+ activeSub?.unsubscribe();
1497
+ }
1498
+ };
1499
+ }
1500
+ });
1501
+ }
1502
+
496
1503
  // src/helpers/with-persist.ts
497
1504
  function resolveStorage(custom) {
498
1505
  if (custom) return custom;
@@ -658,6 +1665,6 @@ function connectDevTools(store, storeName) {
658
1665
  };
659
1666
  }
660
1667
 
661
- export { StatoHttp, StatoHttpError, abortable, configureHttp, connectDevTools, createDevTools, createHttp, createStore, debounced, devTools, fromStream, http, optimistic, retryable, throttled, withPersist };
1668
+ export { StatoHttp, StatoHttpError, abortable, catchErrorStream, combineLatest, combineLatestStream, concatMapStream, configureHttp, connectDevTools, createDevTools, createEntityAdapter, createHttp, createStore, debounceStream, debounced, devTools, distinctUntilChanged, distinctUntilChangedStream, exclusive, exhaustMapStream, filterStream, forkJoin, fromStream, http, mapStream, mergeMapStream, on, optimistic, pipeStream, queued, race, retryStream, retryable, switchMapStream, throttleStream, throttled, withEntities, withPersist };
662
1669
  //# sourceMappingURL=index.mjs.map
663
1670
  //# sourceMappingURL=index.mjs.map