@kuindji/reactive 1.0.24 → 1.1.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.
Files changed (53) hide show
  1. package/README.md +34 -11
  2. package/dist/action.d.ts +12 -10
  3. package/dist/action.js +20 -10
  4. package/dist/actionBus.d.ts +8 -4
  5. package/dist/actionBus.js +34 -2
  6. package/dist/actionMap.d.ts +21 -19
  7. package/dist/actionMap.js +10 -4
  8. package/dist/event.d.ts +3 -2
  9. package/dist/event.js +112 -58
  10. package/dist/eventBus.d.ts +5 -3
  11. package/dist/eventBus.js +88 -31
  12. package/dist/index.d.ts +7 -7
  13. package/dist/index.js +7 -7
  14. package/dist/lib/actionMapInternal.d.ts +8 -0
  15. package/dist/lib/actionMapInternal.js +8 -0
  16. package/dist/lib/isPromiseLike.d.ts +1 -0
  17. package/dist/lib/isPromiseLike.js +5 -0
  18. package/dist/lib/normalizeEventOptions.d.ts +13 -0
  19. package/dist/lib/normalizeEventOptions.js +21 -0
  20. package/dist/react/ErrorBoundary.d.ts +1 -1
  21. package/dist/react/listenerOptionsEqual.d.ts +27 -0
  22. package/dist/react/listenerOptionsEqual.js +121 -0
  23. package/dist/react/useAction.d.ts +3 -3
  24. package/dist/react/useAction.js +10 -7
  25. package/dist/react/useActionBus.d.ts +4 -4
  26. package/dist/react/useActionBus.js +32 -2
  27. package/dist/react/useActionMap.d.ts +4 -4
  28. package/dist/react/useActionMap.js +40 -7
  29. package/dist/react/useEvent.d.ts +2 -2
  30. package/dist/react/useEvent.js +18 -2
  31. package/dist/react/useEventBus.d.ts +2 -2
  32. package/dist/react/useEventBus.js +14 -10
  33. package/dist/react/useListenToAction.d.ts +1 -1
  34. package/dist/react/useListenToAction.js +17 -38
  35. package/dist/react/useListenToActionBus.d.ts +3 -3
  36. package/dist/react/useListenToActionBus.js +15 -9
  37. package/dist/react/useListenToEvent.d.ts +2 -2
  38. package/dist/react/useListenToEvent.js +8 -6
  39. package/dist/react/useListenToEventBus.d.ts +3 -3
  40. package/dist/react/useListenToEventBus.js +9 -7
  41. package/dist/react/useListenToStoreChanges.d.ts +3 -3
  42. package/dist/react/useListenToStoreChanges.js +9 -7
  43. package/dist/react/useReconciledListener.d.ts +33 -0
  44. package/dist/react/useReconciledListener.js +44 -0
  45. package/dist/react/useStore.d.ts +2 -2
  46. package/dist/react/useStore.js +71 -19
  47. package/dist/react/useStoreState.d.ts +2 -2
  48. package/dist/react/useStoreState.js +14 -21
  49. package/dist/react.d.ts +13 -13
  50. package/dist/react.js +13 -13
  51. package/dist/store.d.ts +9 -8
  52. package/dist/store.js +91 -24
  53. package/package.json +13 -3
@@ -1,14 +1,16 @@
1
- import { useCallback, useEffect, useRef } from "react";
1
+ import { useCallback, useRef } from "react";
2
+ import { useReconciledListener } from "./useReconciledListener.js";
2
3
  export function useListenToStoreChanges(store, key, listener, options) {
3
4
  const listenerRef = useRef(listener);
4
5
  listenerRef.current = listener;
5
6
  const genericHandler = useCallback((value, previousValue) => {
6
7
  return listenerRef.current(value, previousValue);
7
8
  }, []);
8
- useEffect(() => {
9
- store.onChange(key, genericHandler, options);
10
- return () => {
11
- store.removeOnChange(key, genericHandler);
12
- };
13
- }, [store, key, genericHandler]);
9
+ useReconciledListener({
10
+ keyDeps: [store, key],
11
+ options,
12
+ subscribe: (opts) => store.onChange(key, genericHandler, opts !== null && opts !== void 0 ? opts : undefined),
13
+ unsubscribe: (ctx) => store.removeOnChange(key, genericHandler, ctx),
14
+ update: (ctx, opts) => store.updateOnChangeOptions(key, genericHandler, ctx, opts !== null && opts !== void 0 ? opts : undefined),
15
+ });
14
16
  }
@@ -0,0 +1,33 @@
1
+ import type { ListenerOptions } from "../event.js";
2
+ type ListenerOps = {
3
+ /**
4
+ * Identity dependencies. When any element changes (reference equality) the
5
+ * listener is fully resubscribed: the old registration is removed using the
6
+ * previous closure (previous target + previous context) and a fresh one is
7
+ * added. `context` is appended automatically because it is part of listener
8
+ * identity. The array length must stay constant across renders.
9
+ */
10
+ keyDeps: ReadonlyArray<unknown>;
11
+ options?: ListenerOptions | null;
12
+ /** Add the listener to the current target with the given options. */
13
+ subscribe: (options?: ListenerOptions | null) => void;
14
+ /** Remove the listener from the current target using the given context. */
15
+ unsubscribe: (context: object | null) => void;
16
+ /** Update soft options on the live listener in place (counters preserved). */
17
+ update: (context: object | null, options?: ListenerOptions | null) => void;
18
+ };
19
+ /**
20
+ * Reconciles a single reactive listener across renders without relying on the
21
+ * identity of the options object.
22
+ *
23
+ * Two effects cooperate:
24
+ * - an identity effect keyed by `[...keyDeps, context]` performs the classic
25
+ * add-on-mount / remove-on-cleanup cycle, so target/context changes (and
26
+ * React StrictMode remounts) resubscribe correctly using the OLD context on
27
+ * cleanup;
28
+ * - a reconciliation effect runs every render and, when only soft options
29
+ * changed, updates the live listener in place instead of resubscribing, so
30
+ * per-listener counters are preserved.
31
+ */
32
+ export declare function useReconciledListener({ keyDeps, options, subscribe, unsubscribe, update, }: ListenerOps): void;
33
+ export {};
@@ -0,0 +1,44 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { areListenerOptionsEqual } from "./listenerOptionsEqual.js";
3
+ /**
4
+ * Reconciles a single reactive listener across renders without relying on the
5
+ * identity of the options object.
6
+ *
7
+ * Two effects cooperate:
8
+ * - an identity effect keyed by `[...keyDeps, context]` performs the classic
9
+ * add-on-mount / remove-on-cleanup cycle, so target/context changes (and
10
+ * React StrictMode remounts) resubscribe correctly using the OLD context on
11
+ * cleanup;
12
+ * - a reconciliation effect runs every render and, when only soft options
13
+ * changed, updates the live listener in place instead of resubscribing, so
14
+ * per-listener counters are preserved.
15
+ */
16
+ export function useReconciledListener({ keyDeps, options, subscribe, unsubscribe, update, }) {
17
+ var _a;
18
+ const context = (_a = options === null || options === void 0 ? void 0 : options.context) !== null && _a !== void 0 ? _a : null;
19
+ const committedRef = useRef(undefined);
20
+ const registeredRef = useRef(false);
21
+ // Identity effect: (re)subscribe on target/context change.
22
+ useEffect(() => {
23
+ subscribe(options);
24
+ committedRef.current = options;
25
+ registeredRef.current = true;
26
+ return () => {
27
+ unsubscribe(context);
28
+ registeredRef.current = false;
29
+ };
30
+ }, [...keyDeps, context]);
31
+ // Reconciliation effect: in-place soft-option updates every render.
32
+ useEffect(() => {
33
+ if (!registeredRef.current) {
34
+ return;
35
+ }
36
+ if (committedRef.current === options) {
37
+ return;
38
+ }
39
+ if (!areListenerOptionsEqual(committedRef.current, options)) {
40
+ update(context, options);
41
+ }
42
+ committedRef.current = options;
43
+ });
44
+ }
@@ -1,5 +1,5 @@
1
- import { createStore } from "../store";
2
- import type { BasePropMap, BeforeChangeEventName, ChangeEventName, ErrorEventName, ResetEventName, StoreDefinitionHelper } from "../store";
1
+ import { createStore } from "../store.js";
2
+ import type { BasePropMap, BeforeChangeEventName, ChangeEventName, ErrorEventName, ResetEventName, StoreDefinitionHelper } from "../store.js";
3
3
  export type { BasePropMap, BeforeChangeEventName, ChangeEventName, ErrorEventName, ResetEventName, StoreDefinitionHelper, };
4
4
  export declare function useStore<PropMap extends BasePropMap, Store extends StoreDefinitionHelper<PropMap> = StoreDefinitionHelper<PropMap>, Config extends {
5
5
  onChange?: Partial<Store["changeEvents"]>;
@@ -1,26 +1,78 @@
1
- import { useMemo } from "react";
2
- import { createStore } from "../store";
1
+ import { useEffect, useMemo, useRef } from "react";
2
+ import { createStore } from "../store.js";
3
3
  export function useStore(initialData = {}, config) {
4
- const store = useMemo(() => {
5
- const store = createStore(initialData);
6
- if (config === null || config === void 0 ? void 0 : config.onChange) {
7
- for (const key in config.onChange) {
8
- store.onChange(key, config.onChange[key]);
9
- }
4
+ // initialData is seed-only (captured once); later changes are ignored.
5
+ const store = useMemo(() => createStore(initialData), []);
6
+ // Track only the handlers we added (per category + key) and compare by
7
+ // reference, so consumer listeners added outside the hook are never
8
+ // touched and inline-equal config maps do not duplicate or churn
9
+ // subscriptions.
10
+ const appliedRef = useRef({
11
+ onChange: {},
12
+ pipes: {},
13
+ control: {},
14
+ });
15
+ const add = (category, key, fn) => {
16
+ if (category === "onChange") {
17
+ store.onChange(key, fn);
10
18
  }
11
- if (config === null || config === void 0 ? void 0 : config.pipes) {
12
- for (const key in config.pipes) {
13
- // @ts-expect-error - TS widens for-in key to string; types are correct
14
- store.pipe(key, config.pipes[key]);
15
- }
19
+ else if (category === "pipes") {
20
+ store.pipe(key, fn);
16
21
  }
17
- if (config === null || config === void 0 ? void 0 : config.control) {
18
- for (const key in config.control) {
19
- // @ts-expect-error - TS widens for-in key to string; types are correct
20
- store.control(key, config.control[key]);
21
- }
22
+ else {
23
+ store.control(key, fn);
24
+ }
25
+ };
26
+ const remove = (category, key, fn) => {
27
+ if (category === "onChange") {
28
+ store.removeOnChange(key, fn);
22
29
  }
23
- return store;
30
+ else if (category === "pipes") {
31
+ store.removePipe(key, fn);
32
+ }
33
+ else {
34
+ store.removeControl(key, fn);
35
+ }
36
+ };
37
+ // Reconcile config handlers every render (no cleanup here, so equal config
38
+ // never causes remove/add churn).
39
+ useEffect(() => {
40
+ const categories = {
41
+ onChange: config === null || config === void 0 ? void 0 : config.onChange,
42
+ pipes: config === null || config === void 0 ? void 0 : config.pipes,
43
+ control: config === null || config === void 0 ? void 0 : config.control,
44
+ };
45
+ Object.keys(categories).forEach((category) => {
46
+ var _a;
47
+ const next = (_a = categories[category]) !== null && _a !== void 0 ? _a : {};
48
+ const prev = appliedRef.current[category];
49
+ // Remove stale/changed handlers before adding (matters for pipes).
50
+ for (const key in prev) {
51
+ if (next[key] !== prev[key]) {
52
+ remove(category, key, prev[key]);
53
+ }
54
+ }
55
+ for (const key in next) {
56
+ if (next[key] !== prev[key]) {
57
+ add(category, key, next[key]);
58
+ }
59
+ }
60
+ appliedRef.current[category] = Object.assign({}, next);
61
+ });
62
+ });
63
+ // Unmount cleanup: detach everything we applied (also makes StrictMode
64
+ // remount re-subscribe cleanly via the reconcile effect above).
65
+ useEffect(() => {
66
+ return () => {
67
+ const applied = appliedRef.current;
68
+ Object.keys(applied).forEach((category) => {
69
+ const map = applied[category];
70
+ for (const key in map) {
71
+ remove(category, key, map[key]);
72
+ }
73
+ applied[category] = {};
74
+ });
75
+ };
24
76
  }, []);
25
77
  return store;
26
78
  }
@@ -1,3 +1,3 @@
1
- import { KeyOf } from "../lib/types";
2
- import { BaseStore } from "../store";
1
+ import { KeyOf } from "../lib/types.js";
2
+ import { BaseStore } from "../store.js";
3
3
  export declare function useStoreState<TStore extends BaseStore, TKey extends KeyOf<TStore["__type"]["propTypes"]>>(store: TStore, key: TKey): readonly [TStore["__type"]["propTypes"][TKey], (value: TStore["__type"]["propTypes"][TKey] | ((previousValue?: TStore["__type"]["propTypes"][TKey]) => TStore["__type"]["propTypes"][TKey])) => void];
@@ -1,30 +1,23 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
1
+ import { useCallback, useSyncExternalStore } from "react";
2
2
  export function useStoreState(store, key) {
3
- const [value, setValue] = useState(store.get(key));
4
- const storeRef = useRef(store);
5
- const keyRef = useRef(key);
6
- const onChange = useCallback((value) => {
7
- setValue(value);
8
- }, []);
3
+ const subscribe = useCallback((onStoreChange) => {
4
+ const listener = () => {
5
+ onStoreChange();
6
+ };
7
+ store.onChange(key, listener);
8
+ return () => {
9
+ store.removeOnChange(key, listener);
10
+ };
11
+ }, [store, key]);
12
+ const getSnapshot = useCallback(() => store.get(key), [store, key]);
13
+ const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
9
14
  const setter = useCallback((value) => {
10
15
  if (typeof value === "function") {
11
- storeRef.current.set(keyRef.current, value(storeRef.current.get(keyRef.current)));
16
+ store.set(key, value(store.get(key)));
12
17
  }
13
18
  else {
14
- storeRef.current.set(keyRef.current, value);
19
+ store.set(key, value);
15
20
  }
16
- }, []);
17
- useEffect(() => {
18
- return () => {
19
- storeRef.current.removeOnChange(keyRef.current, onChange);
20
- };
21
- }, []);
22
- useEffect(() => {
23
- storeRef.current.removeOnChange(keyRef.current, onChange);
24
- storeRef.current = store;
25
- keyRef.current = key;
26
- storeRef.current.onChange(keyRef.current, onChange);
27
- setValue(store.get(key));
28
21
  }, [store, key]);
29
22
  return [value, setter];
30
23
  }
package/dist/react.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- export * from "./react/ErrorBoundary";
2
- export * from "./react/useAction";
3
- export * from "./react/useActionBus";
4
- export * from "./react/useActionMap";
5
- export * from "./react/useEvent";
6
- export * from "./react/useEventBus";
7
- export * from "./react/useListenToAction";
8
- export * from "./react/useListenToActionBus";
9
- export * from "./react/useListenToEvent";
10
- export * from "./react/useListenToEventBus";
11
- export * from "./react/useListenToStoreChanges";
12
- export * from "./react/useStore";
13
- export * from "./react/useStoreState";
1
+ export * from "./react/ErrorBoundary.js";
2
+ export * from "./react/useAction.js";
3
+ export * from "./react/useActionBus.js";
4
+ export * from "./react/useActionMap.js";
5
+ export * from "./react/useEvent.js";
6
+ export * from "./react/useEventBus.js";
7
+ export * from "./react/useListenToAction.js";
8
+ export * from "./react/useListenToActionBus.js";
9
+ export * from "./react/useListenToEvent.js";
10
+ export * from "./react/useListenToEventBus.js";
11
+ export * from "./react/useListenToStoreChanges.js";
12
+ export * from "./react/useStore.js";
13
+ export * from "./react/useStoreState.js";
package/dist/react.js CHANGED
@@ -1,13 +1,13 @@
1
- export * from "./react/ErrorBoundary";
2
- export * from "./react/useAction";
3
- export * from "./react/useActionBus";
4
- export * from "./react/useActionMap";
5
- export * from "./react/useEvent";
6
- export * from "./react/useEventBus";
7
- export * from "./react/useListenToAction";
8
- export * from "./react/useListenToActionBus";
9
- export * from "./react/useListenToEvent";
10
- export * from "./react/useListenToEventBus";
11
- export * from "./react/useListenToStoreChanges";
12
- export * from "./react/useStore";
13
- export * from "./react/useStoreState";
1
+ export * from "./react/ErrorBoundary.js";
2
+ export * from "./react/useAction.js";
3
+ export * from "./react/useActionBus.js";
4
+ export * from "./react/useActionMap.js";
5
+ export * from "./react/useEvent.js";
6
+ export * from "./react/useEventBus.js";
7
+ export * from "./react/useListenToAction.js";
8
+ export * from "./react/useListenToActionBus.js";
9
+ export * from "./react/useListenToEvent.js";
10
+ export * from "./react/useListenToEventBus.js";
11
+ export * from "./react/useListenToStoreChanges.js";
12
+ export * from "./react/useStore.js";
13
+ export * from "./react/useStoreState.js";
package/dist/store.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { EventBusDefinitionHelper } from "./eventBus";
2
- import type { ApiType, ErrorListenerSignature, KeyOf, MapKey } from "./lib/types";
1
+ import { EventBusDefinitionHelper } from "./eventBus.js";
2
+ import type { ApiType, ErrorListenerSignature, KeyOf, MapKey } from "./lib/types.js";
3
3
  export interface BasePropMap {
4
4
  [key: MapKey]: any;
5
5
  }
@@ -44,12 +44,13 @@ export declare function createStore<PropMap extends BasePropMap = BasePropMap>(i
44
44
  };
45
45
  readonly isEmpty: () => boolean;
46
46
  readonly reset: () => void;
47
- readonly onChange: <K extends KeyOf<import("./eventBus").GetEventsMap<StoreChangeEvents<PropMap>>>, H extends import("./eventBus").GetEventsMap<StoreChangeEvents<PropMap>>[K]["signature"]>(name: K, handler: H, options?: import("./event").ListenerOptions) => void;
48
- readonly removeOnChange: <K extends KeyOf<import("./eventBus").GetEventsMap<StoreChangeEvents<PropMap>>>, H extends import("./eventBus").GetEventsMap<StoreChangeEvents<PropMap>>[K]["signature"]>(name: K, handler: H, context?: object | null, tag?: string | null) => void;
49
- readonly control: <K extends KeyOf<import("./eventBus").GetEventsMap<StoreControlEvents<PropMap>>>, H extends import("./eventBus").GetEventsMap<StoreControlEvents<PropMap>>[K]["signature"]>(name: K, handler: H, options?: import("./event").ListenerOptions) => void;
50
- readonly removeControl: <K extends KeyOf<import("./eventBus").GetEventsMap<StoreControlEvents<PropMap>>>, H extends import("./eventBus").GetEventsMap<StoreControlEvents<PropMap>>[K]["signature"]>(name: K, handler: H, context?: object | null, tag?: string | null) => void;
51
- readonly pipe: <K extends KeyOf<import("./eventBus").GetEventsMap<StorePipeEvents<PropMap>>>, H extends import("./eventBus").GetEventsMap<StorePipeEvents<PropMap>>[K]["signature"]>(name: K, handler: H, options?: import("./event").ListenerOptions) => void;
52
- readonly removePipe: <K extends KeyOf<import("./eventBus").GetEventsMap<StorePipeEvents<PropMap>>>, H extends import("./eventBus").GetEventsMap<StorePipeEvents<PropMap>>[K]["signature"]>(name: K, handler: H, context?: object | null, tag?: string | null) => void;
47
+ readonly onChange: <K extends KeyOf<import("./eventBus.js").GetEventsMap<StoreChangeEvents<PropMap>>>, H extends import("./eventBus.js").GetEventsMap<StoreChangeEvents<PropMap>>[K]["signature"]>(name: K, handler: H, options?: import("./event.js").ListenerOptions) => void;
48
+ readonly removeOnChange: <K extends KeyOf<import("./eventBus.js").GetEventsMap<StoreChangeEvents<PropMap>>>, H extends import("./eventBus.js").GetEventsMap<StoreChangeEvents<PropMap>>[K]["signature"]>(name: K, handler: H, context?: object | null, tag?: string | null) => void;
49
+ readonly updateOnChangeOptions: <K extends KeyOf<import("./eventBus.js").GetEventsMap<StoreChangeEvents<PropMap>>>, H extends import("./eventBus.js").GetEventsMap<StoreChangeEvents<PropMap>>[K]["signature"]>(name: K, handler: H, context?: object | null, nextOptions?: import("./event.js").ListenerOptions) => boolean;
50
+ readonly control: <K extends KeyOf<import("./eventBus.js").GetEventsMap<StoreControlEvents<PropMap>>>, H extends import("./eventBus.js").GetEventsMap<StoreControlEvents<PropMap>>[K]["signature"]>(name: K, handler: H, options?: import("./event.js").ListenerOptions) => void;
51
+ readonly removeControl: <K extends KeyOf<import("./eventBus.js").GetEventsMap<StoreControlEvents<PropMap>>>, H extends import("./eventBus.js").GetEventsMap<StoreControlEvents<PropMap>>[K]["signature"]>(name: K, handler: H, context?: object | null, tag?: string | null) => void;
52
+ readonly pipe: <K extends KeyOf<import("./eventBus.js").GetEventsMap<StorePipeEvents<PropMap>>>, H extends import("./eventBus.js").GetEventsMap<StorePipeEvents<PropMap>>[K]["signature"]>(name: K, handler: H, options?: import("./event.js").ListenerOptions) => void;
53
+ readonly removePipe: <K extends KeyOf<import("./eventBus.js").GetEventsMap<StorePipeEvents<PropMap>>>, H extends import("./eventBus.js").GetEventsMap<StorePipeEvents<PropMap>>[K]["signature"]>(name: K, handler: H, context?: object | null, tag?: string | null) => void;
53
54
  }>;
54
55
  export type BaseStoreDefinition = StoreDefinitionHelper<BasePropMap>;
55
56
  export type BaseStore = ReturnType<typeof createStore<any>>;
package/dist/store.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createEventBus } from "./eventBus";
1
+ import { createEventBus } from "./eventBus.js";
2
2
  export const BeforeChangeEventName = "before";
3
3
  export const ChangeEventName = "change";
4
4
  export const ResetEventName = "reset";
@@ -21,8 +21,8 @@ export function createStore(initialData = {}) {
21
21
  var _a, _b, _c, _d, _e;
22
22
  const prev = data.get(name);
23
23
  if (prev !== value) {
24
- if (control.firstNonEmpty(BeforeChangeEventName, name, value)
25
- === false) {
24
+ const beforeChangeResults = control.all(BeforeChangeEventName, name, value);
25
+ if (beforeChangeResults.some((result) => result === false)) {
26
26
  return;
27
27
  }
28
28
  const pipeArgs = [value];
@@ -73,12 +73,16 @@ export function createStore(initialData = {}) {
73
73
  if ((_c = control.get(EffectEventName)) === null || _c === void 0 ? void 0 : _c.hasListener()) {
74
74
  try {
75
75
  const isIntercepting = control.isIntercepting();
76
- if (!isIntercepting) {
77
- control.intercept(effectInterceptor);
76
+ try {
77
+ if (!isIntercepting) {
78
+ control.intercept(effectInterceptor);
79
+ }
80
+ control.trigger(EffectEventName, name, value);
78
81
  }
79
- control.trigger(EffectEventName, name, value);
80
- if (!isIntercepting) {
81
- control.stopIntercepting();
82
+ finally {
83
+ if (!isIntercepting) {
84
+ control.stopIntercepting();
85
+ }
82
86
  }
83
87
  }
84
88
  catch (error) {
@@ -143,36 +147,48 @@ export function createStore(initialData = {}) {
143
147
  const changedKeys = [];
144
148
  const isIntercepting = control.isIntercepting();
145
149
  const hasEffectListener = (_a = control.get(EffectEventName)) === null || _a === void 0 ? void 0 : _a.hasListener();
146
- if (hasEffectListener && !isIntercepting) {
150
+ const shouldInterceptEffects = hasEffectListener && !isIntercepting;
151
+ let controlError = null;
152
+ if (shouldInterceptEffects) {
147
153
  control.intercept(effectInterceptor);
148
154
  }
149
- Object.entries(name).forEach(([k, v]) => {
150
- if (_set(k, v, false)) {
151
- changedKeys.push(k);
152
- }
153
- });
154
155
  try {
155
- control.trigger(ChangeEventName, [
156
+ Object.entries(name).forEach(([k, v]) => {
157
+ if (_set(k, v, false)) {
158
+ changedKeys.push(k);
159
+ }
160
+ });
161
+ const allChangedKeys = [
156
162
  ...changedKeys,
157
163
  ...effectKeys,
158
- ]);
159
- if (hasEffectListener && !isIntercepting) {
164
+ ];
165
+ if (allChangedKeys.length > 0) {
166
+ try {
167
+ control.trigger(ChangeEventName, allChangedKeys);
168
+ }
169
+ catch (error) {
170
+ controlError = error instanceof Error
171
+ ? error
172
+ : new Error(String(error));
173
+ }
174
+ }
175
+ }
176
+ finally {
177
+ if (shouldInterceptEffects) {
160
178
  effectKeys = [];
161
179
  control.stopIntercepting();
162
180
  }
163
181
  }
164
- catch (error) {
182
+ if (controlError) {
165
183
  control.trigger(ErrorEventName, {
166
- error: error instanceof Error
167
- ? error
168
- : new Error(String(error)),
184
+ error: controlError,
169
185
  args: [name],
170
186
  type: "store-control",
171
187
  });
172
188
  if ((_b = control.get(ErrorEventName)) === null || _b === void 0 ? void 0 : _b.hasListener()) {
173
189
  return;
174
190
  }
175
- throw error;
191
+ throw controlError;
176
192
  }
177
193
  }
178
194
  else {
@@ -207,6 +223,7 @@ export function createStore(initialData = {}) {
207
223
  };
208
224
  let batching = false;
209
225
  const batch = (fn) => {
226
+ var _a, _b;
210
227
  if (batching) {
211
228
  throw new Error("Nested batch() calls are not supported");
212
229
  }
@@ -226,9 +243,15 @@ export function createStore(initialData = {}) {
226
243
  };
227
244
  changes.intercept(changeInterceptor);
228
245
  control.intercept(controlInterceptor);
246
+ let callbackError;
247
+ let hasCallbackError = false;
229
248
  try {
230
249
  fn();
231
250
  }
251
+ catch (error) {
252
+ callbackError = error;
253
+ hasCallbackError = true;
254
+ }
232
255
  finally {
233
256
  control.stopIntercepting();
234
257
  changes.stopIntercepting();
@@ -239,10 +262,53 @@ export function createStore(initialData = {}) {
239
262
  value,
240
263
  prev,
241
264
  ];
242
- changes.trigger(propName, ...changeArgs);
265
+ try {
266
+ changes.trigger(propName, ...changeArgs);
267
+ }
268
+ catch (error) {
269
+ control.trigger(ErrorEventName, {
270
+ error: error instanceof Error
271
+ ? error
272
+ : new Error(String(error)),
273
+ args: changeArgs,
274
+ type: "store-change",
275
+ name: propName,
276
+ });
277
+ if ((_a = control.get(ErrorEventName)) === null || _a === void 0 ? void 0 : _a.hasListener()) {
278
+ continue;
279
+ }
280
+ if (hasCallbackError) {
281
+ continue;
282
+ }
283
+ throw error;
284
+ }
243
285
  }
244
286
  if (allChangedKeys.length > 0) {
245
- control.trigger(ChangeEventName, allChangedKeys);
287
+ try {
288
+ control.trigger(ChangeEventName, allChangedKeys);
289
+ }
290
+ catch (error) {
291
+ control.trigger(ErrorEventName, {
292
+ error: error instanceof Error
293
+ ? error
294
+ : new Error(String(error)),
295
+ args: [allChangedKeys],
296
+ type: "store-control",
297
+ });
298
+ if ((_b = control.get(ErrorEventName)) === null || _b === void 0 ? void 0 : _b.hasListener()) {
299
+ if (hasCallbackError) {
300
+ throw callbackError;
301
+ }
302
+ return;
303
+ }
304
+ if (hasCallbackError) {
305
+ throw callbackError;
306
+ }
307
+ throw error;
308
+ }
309
+ }
310
+ if (hasCallbackError) {
311
+ throw callbackError;
246
312
  }
247
313
  };
248
314
  const reset = () => {
@@ -259,6 +325,7 @@ export function createStore(initialData = {}) {
259
325
  reset,
260
326
  onChange: changes.addListener,
261
327
  removeOnChange: changes.removeListener,
328
+ updateOnChangeOptions: changes.updateListenerOptions,
262
329
  control: control.addListener,
263
330
  removeControl: control.removeListener,
264
331
  pipe: pipe.addListener,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuindji/reactive",
3
- "version": "1.0.24",
3
+ "version": "1.1.0",
4
4
  "author": "Ivan Kuindzhi",
5
5
  "type": "module",
6
6
  "repository": {
@@ -22,6 +22,14 @@
22
22
  "typescript": "^5.9.3",
23
23
  "typescript-eslint": "^8.48.0"
24
24
  },
25
+ "peerDependencies": {
26
+ "react": ">=18.0.0"
27
+ },
28
+ "peerDependenciesMeta": {
29
+ "react": {
30
+ "optional": true
31
+ }
32
+ },
25
33
  "exports": {
26
34
  ".": {
27
35
  "types": "./dist/index.d.ts",
@@ -62,11 +70,13 @@
62
70
  "license": "ISC",
63
71
  "scripts": {
64
72
  "build": "tsc -p ./tsconfig-build.json",
73
+ "prepublishOnly": "bun run build",
65
74
  "lint": "bun eslint .",
75
+ "type-check": "tsc --noEmit",
66
76
  "test": "bun test tests/**/*.spec.ts*",
67
77
  "test:types": "tsc -p ./tests/types/tsconfig.json",
68
- "test:all": "bun run test:types && bun run test"
78
+ "test:all": "bun run type-check && bun run test:types && bun run test"
69
79
  },
70
80
  "sideEffects": false,
71
81
  "types": "dist/index.d.ts"
72
- }
82
+ }