@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
@@ -0,0 +1,27 @@
1
+ import type { EventOptions, ListenerOptions } from "../event.js";
2
+ import type { BaseEventMap, EventBusOptions } from "../eventBus.js";
3
+ import type { BaseHandler } from "../lib/types.js";
4
+ /**
5
+ * Order-insensitive set comparison for listener tags.
6
+ * `undefined`, `[]` and missing all compare equal. Order and duplicates do
7
+ * not matter because the core only ever uses tags via membership and
8
+ * intersection checks.
9
+ */
10
+ export declare function areTagsEqual(a?: string[], b?: string[]): boolean;
11
+ /**
12
+ * Domain-specific comparator for {@link ListenerOptions}. Avoids generic deep
13
+ * equality: primitives compare after default semantics, `context`/`extraData`
14
+ * compare by reference, and `tags` use order-insensitive set comparison.
15
+ */
16
+ export declare function areListenerOptionsEqual(a?: ListenerOptions | null, b?: ListenerOptions | null): boolean;
17
+ /**
18
+ * Domain-specific comparator for {@link EventOptions}. Primitives compare after
19
+ * default semantics; `filter`/`filterContext` compare by reference.
20
+ */
21
+ export declare function areEventOptionsEqual(a?: EventOptions<BaseHandler> | null, b?: EventOptions<BaseHandler> | null): boolean;
22
+ /**
23
+ * Compares the per-event `eventOptions` maps of two {@link EventBusOptions}.
24
+ * Equal when every event name present in either map has semantically equal
25
+ * {@link EventOptions} (missing entries compare as default options).
26
+ */
27
+ export declare function areEventBusOptionsEqual(a?: EventBusOptions<BaseEventMap> | null, b?: EventBusOptions<BaseEventMap> | null): boolean;
@@ -0,0 +1,121 @@
1
+ function normalizeAsync(value) {
2
+ if (value === undefined || value === null) {
3
+ return null;
4
+ }
5
+ if (value === true) {
6
+ return 1;
7
+ }
8
+ return value;
9
+ }
10
+ /**
11
+ * Order-insensitive set comparison for listener tags.
12
+ * `undefined`, `[]` and missing all compare equal. Order and duplicates do
13
+ * not matter because the core only ever uses tags via membership and
14
+ * intersection checks.
15
+ */
16
+ export function areTagsEqual(a, b) {
17
+ const aa = a !== null && a !== void 0 ? a : [];
18
+ const bb = b !== null && b !== void 0 ? b : [];
19
+ if (aa.length === 0 && bb.length === 0) {
20
+ return true;
21
+ }
22
+ const sa = new Set(aa);
23
+ const sb = new Set(bb);
24
+ if (sa.size !== sb.size) {
25
+ return false;
26
+ }
27
+ for (const tag of sa) {
28
+ if (!sb.has(tag)) {
29
+ return false;
30
+ }
31
+ }
32
+ return true;
33
+ }
34
+ /**
35
+ * Domain-specific comparator for {@link ListenerOptions}. Avoids generic deep
36
+ * equality: primitives compare after default semantics, `context`/`extraData`
37
+ * compare by reference, and `tags` use order-insensitive set comparison.
38
+ */
39
+ export function areListenerOptionsEqual(a, b) {
40
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
41
+ const aa = a !== null && a !== void 0 ? a : {};
42
+ const bb = b !== null && b !== void 0 ? b : {};
43
+ if (((_a = aa.limit) !== null && _a !== void 0 ? _a : 0) !== ((_b = bb.limit) !== null && _b !== void 0 ? _b : 0)) {
44
+ return false;
45
+ }
46
+ if (((_c = aa.start) !== null && _c !== void 0 ? _c : 1) !== ((_d = bb.start) !== null && _d !== void 0 ? _d : 1)) {
47
+ return false;
48
+ }
49
+ if (((_e = aa.first) !== null && _e !== void 0 ? _e : false) !== ((_f = bb.first) !== null && _f !== void 0 ? _f : false)) {
50
+ return false;
51
+ }
52
+ if (((_g = aa.alwaysFirst) !== null && _g !== void 0 ? _g : false) !== ((_h = bb.alwaysFirst) !== null && _h !== void 0 ? _h : false)) {
53
+ return false;
54
+ }
55
+ if (((_j = aa.alwaysLast) !== null && _j !== void 0 ? _j : false) !== ((_k = bb.alwaysLast) !== null && _k !== void 0 ? _k : false)) {
56
+ return false;
57
+ }
58
+ if (normalizeAsync(aa.async) !== normalizeAsync(bb.async)) {
59
+ return false;
60
+ }
61
+ if (((_l = aa.context) !== null && _l !== void 0 ? _l : null) !== ((_m = bb.context) !== null && _m !== void 0 ? _m : null)) {
62
+ return false;
63
+ }
64
+ // reference equality only; do not deep compare arbitrary values
65
+ if (aa.extraData !== bb.extraData) {
66
+ return false;
67
+ }
68
+ if (!areTagsEqual(aa.tags, bb.tags)) {
69
+ return false;
70
+ }
71
+ return true;
72
+ }
73
+ /**
74
+ * Domain-specific comparator for {@link EventOptions}. Primitives compare after
75
+ * default semantics; `filter`/`filterContext` compare by reference.
76
+ */
77
+ export function areEventOptionsEqual(a, b) {
78
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
79
+ const aa = a !== null && a !== void 0 ? a : {};
80
+ const bb = b !== null && b !== void 0 ? b : {};
81
+ if (normalizeAsync(aa.async) !== normalizeAsync(bb.async)) {
82
+ return false;
83
+ }
84
+ if (((_a = aa.limit) !== null && _a !== void 0 ? _a : null) !== ((_b = bb.limit) !== null && _b !== void 0 ? _b : null)) {
85
+ return false;
86
+ }
87
+ if (((_c = aa.autoTrigger) !== null && _c !== void 0 ? _c : null) !== ((_d = bb.autoTrigger) !== null && _d !== void 0 ? _d : null)) {
88
+ return false;
89
+ }
90
+ if (((_e = aa.maxListeners) !== null && _e !== void 0 ? _e : 0) !== ((_f = bb.maxListeners) !== null && _f !== void 0 ? _f : 0)) {
91
+ return false;
92
+ }
93
+ // reference equality
94
+ if (((_g = aa.filter) !== null && _g !== void 0 ? _g : null) !== ((_h = bb.filter) !== null && _h !== void 0 ? _h : null)) {
95
+ return false;
96
+ }
97
+ if (((_j = aa.filterContext) !== null && _j !== void 0 ? _j : null) !== ((_k = bb.filterContext) !== null && _k !== void 0 ? _k : null)) {
98
+ return false;
99
+ }
100
+ return true;
101
+ }
102
+ /**
103
+ * Compares the per-event `eventOptions` maps of two {@link EventBusOptions}.
104
+ * Equal when every event name present in either map has semantically equal
105
+ * {@link EventOptions} (missing entries compare as default options).
106
+ */
107
+ export function areEventBusOptionsEqual(a, b) {
108
+ var _a, _b;
109
+ const aMap = (_a = a === null || a === void 0 ? void 0 : a.eventOptions) !== null && _a !== void 0 ? _a : {};
110
+ const bMap = (_b = b === null || b === void 0 ? void 0 : b.eventOptions) !== null && _b !== void 0 ? _b : {};
111
+ const names = new Set([
112
+ ...Object.keys(aMap),
113
+ ...Object.keys(bMap),
114
+ ]);
115
+ for (const name of names) {
116
+ if (!areEventOptionsEqual(aMap[name], bMap[name])) {
117
+ return false;
118
+ }
119
+ }
120
+ return true;
121
+ }
@@ -1,5 +1,5 @@
1
- import { createAction } from "../action";
2
- import type { ActionResponse, BeforeActionSignature, ListenerSignature } from "../action";
3
- import type { BaseHandler, ErrorListenerSignature, ErrorResponse } from "../lib/types";
1
+ import { createAction } from "../action.js";
2
+ import type { ActionResponse, BeforeActionSignature, ListenerSignature } from "../action.js";
3
+ import type { BaseHandler, ErrorListenerSignature, ErrorResponse } from "../lib/types.js";
4
4
  export type { ActionResponse, BaseHandler, ErrorListenerSignature, ErrorResponse, ListenerSignature, };
5
5
  export declare function useAction<ActionSignature extends BaseHandler, Listener extends ListenerSignature<ActionSignature>, ErrorListener extends ErrorListenerSignature<Parameters<ActionSignature>>, BeforeActionListener extends BeforeActionSignature<ActionSignature>>(actionSignature: ActionSignature, listener?: Listener | null, errorListener?: ErrorListener | null, beforeActionListener?: BeforeActionListener | null): ReturnType<typeof createAction<ActionSignature>>;
@@ -1,9 +1,9 @@
1
1
  import { useContext, useEffect, useMemo, useRef } from "react";
2
- import { createAction } from "../action";
3
- import { ErrorBoundaryContext } from "./ErrorBoundary";
2
+ import { createAction } from "../action.js";
3
+ import { ErrorBoundaryContext } from "./ErrorBoundary.js";
4
4
  export function useAction(actionSignature, listener, errorListener, beforeActionListener) {
5
5
  const boundaryErrorListener = useContext(ErrorBoundaryContext);
6
- const updateRef = useRef(0);
6
+ const actionSignatureRef = useRef(actionSignature);
7
7
  const listenerRef = useRef(listener);
8
8
  const errorListenerRef = useRef(errorListener);
9
9
  const boundaryErrorListenerRef = useRef(boundaryErrorListener);
@@ -24,11 +24,15 @@ export function useAction(actionSignature, listener, errorListener, beforeAction
24
24
  }
25
25
  return action;
26
26
  }, []);
27
+ // Replace the action function in place when its reference changes,
28
+ // preserving all listeners and the action identity. A changed function
29
+ // must keep a compatible signature (TypeScript fixes the generic from the
30
+ // initial render).
27
31
  useEffect(() => {
28
- if (updateRef.current > 0) {
29
- throw new Error("Action cannot be updated");
32
+ if (actionSignatureRef.current !== actionSignature) {
33
+ action.setAction(actionSignature);
34
+ actionSignatureRef.current = actionSignature;
30
35
  }
31
- updateRef.current++;
32
36
  }, [actionSignature]);
33
37
  useEffect(() => {
34
38
  if (listenerRef.current !== listener) {
@@ -91,7 +95,6 @@ export function useAction(actionSignature, listener, errorListener, beforeAction
91
95
  action.removeAllListeners();
92
96
  action.removeAllBeforeActionListeners();
93
97
  action.removeAllErrorListeners();
94
- updateRef.current = 0;
95
98
  };
96
99
  }, []);
97
100
  return action;
@@ -1,6 +1,6 @@
1
- import type { ActionResponse, ListenerSignature } from "../action";
2
- import type { BaseActionsMap } from "../actionBus";
3
- import { createActionBus } from "../actionBus";
4
- import type { ErrorListenerSignature, ErrorResponse } from "../lib/types";
1
+ import type { ActionResponse, ListenerSignature } from "../action.js";
2
+ import type { BaseActionsMap } from "../actionBus.js";
3
+ import { createActionBus } from "../actionBus.js";
4
+ import type { ErrorListenerSignature, ErrorResponse } from "../lib/types.js";
5
5
  export type { ActionResponse, BaseActionsMap, ErrorListenerSignature, ErrorResponse, ListenerSignature, };
6
6
  export declare function useActionBus<ActionsMap extends BaseActionsMap = BaseActionsMap>(initialActions?: ActionsMap, errorListener?: ErrorListenerSignature<any[]>): ReturnType<typeof createActionBus<ActionsMap>>;
@@ -1,6 +1,6 @@
1
1
  import { useContext, useEffect, useMemo, useRef } from "react";
2
- import { createActionBus } from "../actionBus";
3
- import { ErrorBoundaryContext } from "./ErrorBoundary";
2
+ import { createActionBus } from "../actionBus.js";
3
+ import { ErrorBoundaryContext } from "./ErrorBoundary.js";
4
4
  export function useActionBus(initialActions, errorListener) {
5
5
  const boundaryErrorListener = useContext(ErrorBoundaryContext);
6
6
  const errorListenerRef = useRef(errorListener);
@@ -15,6 +15,36 @@ export function useActionBus(initialActions, errorListener) {
15
15
  }
16
16
  return actionBus;
17
17
  }, []);
18
+ // Reconcile the actions map every render. Functions are compared by
19
+ // reference and never invoked.
20
+ const appliedActionsRef = useRef(Object.assign({}, initialActions));
21
+ const nextActions = (initialActions !== null && initialActions !== void 0 ? initialActions : {});
22
+ // Add newly-introduced actions during render (not in an effect): React runs
23
+ // child passive effects BEFORE parent passive effects, so a child rendered
24
+ // in the same pass that subscribes to a new action would otherwise throw
25
+ // "Action <name> not found". Parent render precedes child render, and
26
+ // add() is idempotent (a no-op if the action already exists).
27
+ for (const key in nextActions) {
28
+ actionBus.add(key, nextActions[key]);
29
+ }
30
+ // Replacements and removals can be deferred to a passive effect: a replaced
31
+ // action keeps its identity/listeners (so subscriptions are unaffected by
32
+ // timing), and removing late is harmless.
33
+ useEffect(() => {
34
+ const next = (initialActions !== null && initialActions !== void 0 ? initialActions : {});
35
+ const prev = appliedActionsRef.current;
36
+ for (const key in prev) {
37
+ if (!(key in next)) {
38
+ actionBus.removeAction(key);
39
+ }
40
+ }
41
+ for (const key in next) {
42
+ if (key in prev && next[key] !== prev[key]) {
43
+ actionBus.replace(key, next[key]);
44
+ }
45
+ }
46
+ appliedActionsRef.current = Object.assign({}, next);
47
+ });
18
48
  useEffect(() => {
19
49
  if (errorListenerRef.current !== errorListener) {
20
50
  if (errorListenerRef.current) {
@@ -1,6 +1,6 @@
1
- import type { ActionResponse, ListenerSignature } from "../action";
2
- import type { BaseActionsMap } from "../actionBus";
3
- import { createActionMap } from "../actionMap";
4
- import type { ErrorListenerSignature, ErrorResponse } from "../lib/types";
1
+ import type { ActionResponse, ListenerSignature } from "../action.js";
2
+ import type { BaseActionsMap } from "../actionBus.js";
3
+ import { createActionMap } from "../actionMap.js";
4
+ import type { ErrorListenerSignature, ErrorResponse } from "../lib/types.js";
5
5
  export type { ActionResponse, BaseActionsMap, ErrorListenerSignature, ErrorResponse, ListenerSignature, };
6
6
  export declare function useActionMap<M extends BaseActionsMap>(actions: M, errorListener?: ErrorListenerSignature<any[]>): ReturnType<typeof createActionMap<M>>;
@@ -1,9 +1,12 @@
1
1
  import { useContext, useEffect, useMemo, useRef } from "react";
2
- import { createActionMap } from "../actionMap";
3
- import { ErrorBoundaryContext } from "./ErrorBoundary";
2
+ import { createActionMap } from "../actionMap.js";
3
+ import { ActionMapSetErrorListeners } from "../lib/actionMapInternal.js";
4
+ import { ErrorBoundaryContext } from "./ErrorBoundary.js";
4
5
  export function useActionMap(actions, errorListener) {
5
6
  const boundaryErrorListener = useContext(ErrorBoundaryContext);
6
- const changeRef = useRef(0);
7
+ const committedActionsRef = useRef(actions);
8
+ const committedErrorListenerRef = useRef(errorListener !== null && errorListener !== void 0 ? errorListener : null);
9
+ const committedBoundaryErrorListenerRef = useRef(boundaryErrorListener !== null && boundaryErrorListener !== void 0 ? boundaryErrorListener : null);
7
10
  const actionMap = useMemo(() => {
8
11
  const errorListeners = [
9
12
  ...(errorListener ? [errorListener] : []),
@@ -12,11 +15,41 @@ export function useActionMap(actions, errorListener) {
12
15
  const actionMap = createActionMap(actions, errorListeners);
13
16
  return actionMap;
14
17
  }, []);
18
+ // The action map TYPE fixes the available keys, so the key set is static:
19
+ // reconcile values only (in-place setAction) and the forwarded error
20
+ // listeners. A runtime key-set change is a type-contract violation and
21
+ // keeps a defensive throw.
15
22
  useEffect(() => {
16
- if (changeRef.current > 0) {
17
- throw new Error("useActionMap() does not support changing actions or errorListener");
23
+ const next = actions;
24
+ const prev = committedActionsRef.current;
25
+ const prevKeys = Object.keys(prev);
26
+ const nextKeys = Object.keys(next);
27
+ if (prevKeys.length !== nextKeys.length
28
+ || nextKeys.some((key) => !(key in prev))) {
29
+ throw new Error("useActionMap() does not support changing the set of action keys");
18
30
  }
19
- changeRef.current++;
20
- }, [actions, errorListener !== null && errorListener !== void 0 ? errorListener : null, boundaryErrorListener !== null && boundaryErrorListener !== void 0 ? boundaryErrorListener : null]);
31
+ for (const key of nextKeys) {
32
+ if (next[key] !== prev[key]) {
33
+ actionMap[key].setAction(next[key]);
34
+ }
35
+ }
36
+ committedActionsRef.current = next;
37
+ const nextErrorListener = errorListener !== null && errorListener !== void 0 ? errorListener : null;
38
+ const nextBoundaryErrorListener = boundaryErrorListener !== null && boundaryErrorListener !== void 0 ? boundaryErrorListener : null;
39
+ if (committedErrorListenerRef.current !== nextErrorListener
40
+ || committedBoundaryErrorListenerRef.current
41
+ !== nextBoundaryErrorListener) {
42
+ const errorListeners = [
43
+ ...(nextErrorListener ? [nextErrorListener] : []),
44
+ ...(nextBoundaryErrorListener
45
+ ? [nextBoundaryErrorListener]
46
+ : []),
47
+ ];
48
+ actionMap[ActionMapSetErrorListeners](errorListeners);
49
+ committedErrorListenerRef.current = nextErrorListener;
50
+ committedBoundaryErrorListenerRef.current =
51
+ nextBoundaryErrorListener;
52
+ }
53
+ });
21
54
  return actionMap;
22
55
  }
@@ -1,4 +1,4 @@
1
- import { createEvent, type EventOptions } from "../event";
2
- import type { BaseHandler, ErrorListenerSignature, ErrorResponse } from "../lib/types";
1
+ import { createEvent, type EventOptions } from "../event.js";
2
+ import type { BaseHandler, ErrorListenerSignature, ErrorResponse } from "../lib/types.js";
3
3
  export type { BaseHandler, ErrorListenerSignature, ErrorResponse, EventOptions, };
4
4
  export declare function useEvent<Listener extends BaseHandler = BaseHandler, ErrorListener extends ErrorListenerSignature<Parameters<Listener>> = ErrorListenerSignature<Parameters<Listener>>>(eventOptions?: EventOptions<Listener>, listener?: Listener | null, errorListener?: ErrorListener | null): ReturnType<typeof createEvent<Listener>>;
@@ -1,6 +1,8 @@
1
1
  import { useContext, useEffect, useMemo, useRef } from "react";
2
- import { createEvent } from "../event";
3
- import { ErrorBoundaryContext } from "./ErrorBoundary";
2
+ import { createEvent } from "../event.js";
3
+ import { normalizeEventOptions } from "../lib/normalizeEventOptions.js";
4
+ import { ErrorBoundaryContext } from "./ErrorBoundary.js";
5
+ import { areEventOptionsEqual } from "./listenerOptionsEqual.js";
4
6
  export function useEvent(eventOptions = {}, listener, errorListener) {
5
7
  const boundaryErrorListener = useContext(ErrorBoundaryContext);
6
8
  const listenerRef = useRef(listener);
@@ -19,6 +21,20 @@ export function useEvent(eventOptions = {}, listener, errorListener) {
19
21
  }
20
22
  return event;
21
23
  }, []);
24
+ // Reconcile event options across renders without relying on object
25
+ // identity. Applied in place via setOptions; triggered count is preserved.
26
+ const committedEventOptionsRef = useRef(eventOptions);
27
+ useEffect(() => {
28
+ if (committedEventOptionsRef.current === eventOptions) {
29
+ return;
30
+ }
31
+ if (!areEventOptionsEqual(committedEventOptionsRef.current, eventOptions)) {
32
+ // Normalize so fields removed since the last render reset to their
33
+ // defaults (event.setOptions merges, it does not reset).
34
+ event.setOptions(normalizeEventOptions(eventOptions));
35
+ }
36
+ committedEventOptionsRef.current = eventOptions;
37
+ });
22
38
  useEffect(() => {
23
39
  if (listenerRef.current !== listener) {
24
40
  if (listenerRef.current) {
@@ -1,4 +1,4 @@
1
- import { BaseEventMap, createEventBus, DefaultEventMap, EventBusOptions } from "../eventBus";
2
- import type { BaseHandler, ErrorListenerSignature, ErrorResponse } from "../lib/types";
1
+ import { BaseEventMap, createEventBus, DefaultEventMap, EventBusOptions } from "../eventBus.js";
2
+ import type { BaseHandler, ErrorListenerSignature, ErrorResponse } from "../lib/types.js";
3
3
  export type { BaseEventMap, BaseHandler, ErrorListenerSignature, ErrorResponse, EventBusOptions, };
4
4
  export declare function useEventBus<EventsMap extends BaseEventMap = DefaultEventMap>(eventBusOptions?: EventBusOptions<EventsMap>, allEventsListener?: BaseHandler, errorListener?: ErrorListenerSignature<any[]>): ReturnType<typeof createEventBus<EventsMap>>;
@@ -1,9 +1,10 @@
1
1
  import { useContext, useEffect, useMemo, useRef } from "react";
2
- import { createEventBus, } from "../eventBus";
3
- import { ErrorBoundaryContext } from "./ErrorBoundary";
2
+ import { createEventBus, } from "../eventBus.js";
3
+ import { ErrorBoundaryContext } from "./ErrorBoundary.js";
4
+ import { areEventBusOptionsEqual } from "./listenerOptionsEqual.js";
4
5
  export function useEventBus(eventBusOptions, allEventsListener, errorListener) {
5
6
  const boundaryErrorListener = useContext(ErrorBoundaryContext);
6
- const updateRef = useRef(0);
7
+ const committedOptionsRef = useRef(eventBusOptions);
7
8
  const errorListenerRef = useRef(errorListener || null);
8
9
  const allEventsListenerRef = useRef(allEventsListener || null);
9
10
  const boundaryErrorListenerRef = useRef(boundaryErrorListener || null);
@@ -20,14 +21,18 @@ export function useEventBus(eventBusOptions, allEventsListener, errorListener) {
20
21
  }
21
22
  return eventBus;
22
23
  }, []);
24
+ // Reconcile event bus options across renders instead of throwing. Present
25
+ // entries are applied via event.setOptions; a removed event-name entry
26
+ // leaves the existing event unchanged.
23
27
  useEffect(() => {
24
- if (eventBusOptions) {
25
- if (updateRef.current > 0) {
26
- throw new Error("EventBus options can't be updated");
27
- }
28
- updateRef.current++;
28
+ if (committedOptionsRef.current === eventBusOptions) {
29
+ return;
30
+ }
31
+ if (!areEventBusOptionsEqual(committedOptionsRef.current, eventBusOptions)) {
32
+ eventBus.setOptions(eventBusOptions);
29
33
  }
30
- }, [eventBusOptions]);
34
+ committedOptionsRef.current = eventBusOptions;
35
+ });
31
36
  useEffect(() => {
32
37
  if (allEventsListenerRef.current !== allEventsListener) {
33
38
  if (allEventsListenerRef.current) {
@@ -76,7 +81,6 @@ export function useEventBus(eventBusOptions, allEventsListener, errorListener) {
76
81
  eventBus.removeErrorListener(boundaryErrorListenerRef.current);
77
82
  boundaryErrorListenerRef.current = null;
78
83
  }
79
- updateRef.current = 0;
80
84
  };
81
85
  }, []);
82
86
  return eventBus;
@@ -1,3 +1,3 @@
1
- import type { BaseAction } from "../action";
1
+ import type { BaseAction } from "../action.js";
2
2
  export type { BaseAction };
3
3
  export declare function useListenToAction<TAction extends BaseAction, TListenerSignature extends TAction["__type"]["listenerSignature"] = TAction["__type"]["listenerSignature"], TErrorListenerSignature extends TAction["__type"]["errorListenerSignature"] = TAction["__type"]["errorListenerSignature"], TBeforeActionListenerSignature extends TAction["__type"]["beforeActionSignature"] = TAction["__type"]["beforeActionSignature"]>(action: TAction, listener: TListenerSignature | null, errorListener?: TErrorListenerSignature | null, beforeActionListener?: TBeforeActionListenerSignature | null): void;
@@ -1,52 +1,31 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
2
  export function useListenToAction(action, listener, errorListener, beforeActionListener) {
3
3
  const listenerRef = useRef(listener);
4
- const actionRef = useRef(action);
5
- const errorListenerRef = useRef(null);
6
- const beforeActionListenerRef = useRef(null);
7
4
  listenerRef.current = listener;
8
5
  const genericHandler = useCallback((arg) => {
9
6
  var _a;
10
7
  (_a = listenerRef.current) === null || _a === void 0 ? void 0 : _a.call(listenerRef, arg);
11
8
  }, []);
12
9
  useEffect(() => {
13
- actionRef.current.removeListener(genericHandler);
14
- actionRef.current = action;
15
- actionRef.current.addListener(genericHandler);
16
- }, [action]);
10
+ action.addListener(genericHandler);
11
+ return () => {
12
+ action.removeListener(genericHandler);
13
+ };
14
+ }, [action, genericHandler]);
17
15
  useEffect(() => {
18
- if (errorListenerRef.current !== errorListener) {
19
- if (errorListenerRef.current) {
20
- actionRef.current.removeErrorListener(errorListenerRef.current);
21
- }
22
- errorListenerRef.current = errorListener || null;
23
- if (errorListener) {
24
- actionRef.current.addErrorListener(errorListener);
25
- }
16
+ if (errorListener) {
17
+ action.addErrorListener(errorListener);
18
+ return () => {
19
+ action.removeErrorListener(errorListener);
20
+ };
26
21
  }
27
- }, [errorListener]);
22
+ }, [action, errorListener]);
28
23
  useEffect(() => {
29
- if (beforeActionListenerRef.current !== beforeActionListener) {
30
- if (beforeActionListenerRef.current) {
31
- actionRef.current.removeBeforeActionListener(beforeActionListenerRef.current);
32
- }
33
- beforeActionListenerRef.current = beforeActionListener || null;
34
- if (beforeActionListener) {
35
- actionRef.current.addBeforeActionListener(beforeActionListener);
36
- }
24
+ if (beforeActionListener) {
25
+ action.addBeforeActionListener(beforeActionListener);
26
+ return () => {
27
+ action.removeBeforeActionListener(beforeActionListener);
28
+ };
37
29
  }
38
- }, [beforeActionListener]);
39
- useEffect(() => {
40
- return () => {
41
- actionRef.current.removeListener(genericHandler);
42
- if (errorListenerRef.current) {
43
- actionRef.current.removeErrorListener(errorListenerRef.current);
44
- errorListenerRef.current = null;
45
- }
46
- if (beforeActionListenerRef.current) {
47
- actionRef.current.removeBeforeActionListener(beforeActionListenerRef.current);
48
- beforeActionListenerRef.current = null;
49
- }
50
- };
51
- }, []);
30
+ }, [action, beforeActionListener]);
52
31
  }
@@ -1,6 +1,6 @@
1
- import type { BaseActionBus } from "../actionBus";
2
- import type { ListenerOptions } from "../event";
3
- import type { ErrorListenerSignature, KeyOf } from "../lib/types";
1
+ import type { BaseActionBus } from "../actionBus.js";
2
+ import type { ListenerOptions } from "../event.js";
3
+ import type { ErrorListenerSignature, KeyOf } from "../lib/types.js";
4
4
  export type { BaseActionBus, ErrorListenerSignature, ListenerOptions };
5
5
  export declare function useListenToActionBus<TActionBus extends BaseActionBus, TKey extends KeyOf<TActionBus["__type"]["actions"]>, TListener extends TActionBus["__type"]["actions"][TKey]["listenerSignature"], TBeforeActionListener extends TActionBus["__type"]["actions"][TKey]["beforeActionSignature"]>(actionBus: TActionBus, actionName: TKey, listener?: TListener | null | {
6
6
  listener?: TListener;
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
+ import { useReconciledListener } from "./useReconciledListener.js";
2
3
  export function useListenToActionBus(actionBus, actionName, listener, options, errorListener, beforeActionListener) {
3
4
  if (listener && typeof listener !== "function") {
4
5
  options = listener.options;
@@ -16,17 +17,22 @@ export function useListenToActionBus(actionBus, actionName, listener, options, e
16
17
  }, []);
17
18
  const genericBeforeActionHandler = useCallback((...args) => {
18
19
  var _a;
19
- return ((_a = beforeActionListenerRef.current) === null || _a === void 0 ? void 0 : _a.call(beforeActionListenerRef, ...args)) || undefined;
20
+ return (_a = beforeActionListenerRef.current) === null || _a === void 0 ? void 0 : _a.call(beforeActionListenerRef, ...args);
20
21
  }, []);
21
- // Main listener + beforeAction listener - tied to actionName
22
- useEffect(() => {
23
- actionBus.addListener(actionName, genericHandler, options || undefined);
24
- actionBus.get(actionName).addBeforeActionListener(genericBeforeActionHandler);
25
- return () => {
26
- actionBus.removeListener(actionName, genericHandler);
22
+ // Main listener + beforeAction listener - reconciled across changes
23
+ useReconciledListener({
24
+ keyDeps: [actionBus, actionName],
25
+ options: options !== null && options !== void 0 ? options : undefined,
26
+ subscribe: (opts) => {
27
+ actionBus.addListener(actionName, genericHandler, opts !== null && opts !== void 0 ? opts : undefined);
28
+ actionBus.get(actionName).addBeforeActionListener(genericBeforeActionHandler);
29
+ },
30
+ unsubscribe: (ctx) => {
31
+ actionBus.removeListener(actionName, genericHandler, ctx);
27
32
  actionBus.get(actionName).removeBeforeActionListener(genericBeforeActionHandler);
28
- };
29
- }, [actionBus, actionName, genericHandler, genericBeforeActionHandler]);
33
+ },
34
+ update: (ctx, opts) => actionBus.updateListenerOptions(actionName, genericHandler, ctx, opts !== null && opts !== void 0 ? opts : undefined),
35
+ });
30
36
  // Error listener - bus level
31
37
  useEffect(() => {
32
38
  if (errorListener) {
@@ -1,4 +1,4 @@
1
- import type { BaseEvent, ListenerOptions } from "../event";
2
- import type { ErrorListenerSignature } from "../lib/types";
1
+ import type { BaseEvent, ListenerOptions } from "../event.js";
2
+ import type { ErrorListenerSignature } from "../lib/types.js";
3
3
  export type { BaseEvent, ErrorListenerSignature, ListenerOptions };
4
4
  export declare function useListenToEvent<TEvent extends BaseEvent, TListenerSignature extends TEvent["__type"]["signature"], TErrorListenerSignature extends TEvent["__type"]["errorListenerSignature"]>(event: TEvent, listener: TListenerSignature, options?: ListenerOptions, errorListener?: TErrorListenerSignature): void;
@@ -1,16 +1,18 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
+ import { useReconciledListener } from "./useReconciledListener.js";
2
3
  export function useListenToEvent(event, listener, options, errorListener) {
3
4
  const listenerRef = useRef(listener);
4
5
  listenerRef.current = listener;
5
6
  const genericHandler = useCallback((...args) => {
6
7
  return listenerRef.current(...args);
7
8
  }, []);
8
- useEffect(() => {
9
- event.addListener(genericHandler, options);
10
- return () => {
11
- event.removeListener(genericHandler);
12
- };
13
- }, [event, genericHandler]);
9
+ useReconciledListener({
10
+ keyDeps: [event],
11
+ options,
12
+ subscribe: (opts) => event.addListener(genericHandler, opts !== null && opts !== void 0 ? opts : undefined),
13
+ unsubscribe: (ctx) => event.removeListener(genericHandler, ctx),
14
+ update: (ctx, opts) => event.updateListenerOptions(genericHandler, ctx, opts !== null && opts !== void 0 ? opts : undefined),
15
+ });
14
16
  useEffect(() => {
15
17
  if (errorListener) {
16
18
  event.addErrorListener(errorListener);
@@ -1,5 +1,5 @@
1
- import type { ListenerOptions } from "../event";
2
- import type { BaseEventBus } from "../eventBus";
3
- import type { ErrorListenerSignature, KeyOf } from "../lib/types";
1
+ import type { ListenerOptions } from "../event.js";
2
+ import type { BaseEventBus } from "../eventBus.js";
3
+ import type { ErrorListenerSignature, KeyOf } from "../lib/types.js";
4
4
  export type { BaseEventBus, ErrorListenerSignature, ListenerOptions };
5
5
  export declare function useListenToEventBus<TEventBus extends BaseEventBus, TKey extends KeyOf<TEventBus["__type"]["eventSignatures"]>, TListener extends TEventBus["__type"]["eventSignatures"][TKey]>(eventBus: TEventBus, eventName: TKey, listener: TListener, options?: ListenerOptions, errorListener?: ErrorListenerSignature<any[]>): void;
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
+ import { useReconciledListener } from "./useReconciledListener.js";
2
3
  export function useListenToEventBus(eventBus, eventName, listener, options, errorListener) {
3
4
  const listenerRef = useRef(listener);
4
5
  listenerRef.current = listener;
@@ -6,13 +7,14 @@ export function useListenToEventBus(eventBus, eventName, listener, options, erro
6
7
  var _a;
7
8
  return (_a = listenerRef.current) === null || _a === void 0 ? void 0 : _a.call(listenerRef, ...args);
8
9
  }, []);
9
- // Main listener - cleanup pattern handles eventBus/eventName changes
10
- useEffect(() => {
11
- eventBus.addListener(eventName, genericHandler, options);
12
- return () => {
13
- eventBus.removeListener(eventName, genericHandler);
14
- };
15
- }, [eventBus, eventName, genericHandler]);
10
+ // Main listener - reconciled across eventBus/eventName/option changes
11
+ useReconciledListener({
12
+ keyDeps: [eventBus, eventName],
13
+ options,
14
+ subscribe: (opts) => eventBus.addListener(eventName, genericHandler, opts !== null && opts !== void 0 ? opts : undefined),
15
+ unsubscribe: (ctx) => eventBus.removeListener(eventName, genericHandler, ctx),
16
+ update: (ctx, opts) => eventBus.updateListenerOptions(eventName, genericHandler, ctx, opts !== null && opts !== void 0 ? opts : undefined),
17
+ });
16
18
  // Error listener - cleanup pattern
17
19
  useEffect(() => {
18
20
  if (errorListener) {
@@ -1,5 +1,5 @@
1
- import type { ListenerOptions } from "../event";
2
- import { KeyOf } from "../lib/types";
3
- import type { BaseStore } from "../store";
1
+ import type { ListenerOptions } from "../event.js";
2
+ import { KeyOf } from "../lib/types.js";
3
+ import type { BaseStore } from "../store.js";
4
4
  export type { BaseStore, ListenerOptions };
5
5
  export declare function useListenToStoreChanges<TStore extends BaseStore, TKey extends KeyOf<TStore["__type"]["propTypes"]>, TListener extends TStore["__type"]["changeEvents"][TKey]>(store: TStore, key: TKey, listener: TListener, options?: ListenerOptions): void;