@kuindji/reactive 1.1.0 → 1.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.
@@ -14,6 +14,20 @@ export declare function areTagsEqual(a?: string[], b?: string[]): boolean;
14
14
  * compare by reference, and `tags` use order-insensitive set comparison.
15
15
  */
16
16
  export declare function areListenerOptionsEqual(a?: ListenerOptions | null, b?: ListenerOptions | null): boolean;
17
+ /**
18
+ * Expand a listener's options into a fully-populated set of the soft fields
19
+ * that {@link ListenerOptions} reconciliation updates in place, filling every
20
+ * omitted field with its default.
21
+ *
22
+ * `event.updateListenerOptions` uses partial-merge semantics (only fields
23
+ * present in the passed object change), but the React reconciliation layer is
24
+ * declarative: the options object fully describes the desired listener state,
25
+ * so a field dropped between renders must reset to its default. Passing this
26
+ * normalized object makes partial-merge behave as a full reset for exactly the
27
+ * soft fields — without disturbing `signal` (identity-managed by the abort
28
+ * wiring) or `context` (identity, handled by resubscribe).
29
+ */
30
+ export declare function fillListenerUpdateDefaults(options?: ListenerOptions | null): ListenerOptions;
17
31
  /**
18
32
  * Domain-specific comparator for {@link EventOptions}. Primitives compare after
19
33
  * default semantics; `filter`/`filterContext` compare by reference.
@@ -70,6 +70,32 @@ export function areListenerOptionsEqual(a, b) {
70
70
  }
71
71
  return true;
72
72
  }
73
+ /**
74
+ * Expand a listener's options into a fully-populated set of the soft fields
75
+ * that {@link ListenerOptions} reconciliation updates in place, filling every
76
+ * omitted field with its default.
77
+ *
78
+ * `event.updateListenerOptions` uses partial-merge semantics (only fields
79
+ * present in the passed object change), but the React reconciliation layer is
80
+ * declarative: the options object fully describes the desired listener state,
81
+ * so a field dropped between renders must reset to its default. Passing this
82
+ * normalized object makes partial-merge behave as a full reset for exactly the
83
+ * soft fields — without disturbing `signal` (identity-managed by the abort
84
+ * wiring) or `context` (identity, handled by resubscribe).
85
+ */
86
+ export function fillListenerUpdateDefaults(options) {
87
+ var _a, _b, _c, _d, _e, _f, _g;
88
+ const o = options !== null && options !== void 0 ? options : {};
89
+ return {
90
+ limit: (_a = o.limit) !== null && _a !== void 0 ? _a : 0,
91
+ start: (_b = o.start) !== null && _b !== void 0 ? _b : 1,
92
+ tags: (_c = o.tags) !== null && _c !== void 0 ? _c : [],
93
+ extraData: (_d = o.extraData) !== null && _d !== void 0 ? _d : null,
94
+ alwaysFirst: (_e = o.alwaysFirst) !== null && _e !== void 0 ? _e : false,
95
+ alwaysLast: (_f = o.alwaysLast) !== null && _f !== void 0 ? _f : false,
96
+ async: (_g = o.async) !== null && _g !== void 0 ? _g : null,
97
+ };
98
+ }
73
99
  /**
74
100
  * Domain-specific comparator for {@link EventOptions}. Primitives compare after
75
101
  * default semantics; `filter`/`filterContext` compare by reference.
@@ -0,0 +1,13 @@
1
+ import type { ActionStatus } from "../action.js";
2
+ import type { BaseActionBus } from "../actionBus.js";
3
+ import type { KeyOf } from "../lib/types.js";
4
+ import { type AsyncActionState } from "./useAsyncAction.js";
5
+ export type { ActionStatus, AsyncActionState };
6
+ /**
7
+ * Subscribes to the status of a named action on an ActionBus and returns
8
+ * `{ loading, error, response }` for driving `loading`/`disabled` UI. This is
9
+ * the primary path for apps that route mutations through one shared ActionBus.
10
+ *
11
+ * An unregistered name reports an idle status and is safe to subscribe to.
12
+ */
13
+ export declare function useActionBusStatus<TBus extends BaseActionBus, TName extends KeyOf<TBus["__type"]["actions"]>>(bus: TBus, name: TName): AsyncActionState<TBus["__type"]["actions"][TName]["actionReturnType"]>;
@@ -0,0 +1,23 @@
1
+ import { useCallback, useSyncExternalStore } from "react";
2
+ import { toAsyncActionState } from "./useAsyncAction.js";
3
+ /**
4
+ * Subscribes to the status of a named action on an ActionBus and returns
5
+ * `{ loading, error, response }` for driving `loading`/`disabled` UI. This is
6
+ * the primary path for apps that route mutations through one shared ActionBus.
7
+ *
8
+ * An unregistered name reports an idle status and is safe to subscribe to.
9
+ */
10
+ export function useActionBusStatus(bus, name) {
11
+ const subscribe = useCallback((onChange) => {
12
+ const listener = () => {
13
+ onChange();
14
+ };
15
+ bus.onStatusChange(name, listener);
16
+ return () => {
17
+ bus.removeStatusListener(name, listener);
18
+ };
19
+ }, [bus, name]);
20
+ const getSnapshot = useCallback(() => bus.getStatus(name), [bus, name]);
21
+ const status = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
22
+ return toAsyncActionState(status);
23
+ }
@@ -0,0 +1,26 @@
1
+ import type { ActionResponse, ActionStatus } from "../action.js";
2
+ import type { BaseHandler } from "../lib/types.js";
3
+ export type { ActionResponse, ActionStatus };
4
+ export type AsyncActionState<Response = any> = {
5
+ loading: boolean;
6
+ error: Error | null;
7
+ response: Response | null;
8
+ };
9
+ /**
10
+ * Project an action's {@link ActionStatus} into the {@link AsyncActionState}
11
+ * shape consumed by the status hooks (`pending` -> `loading`). Shared so
12
+ * `useAsyncAction` and `useActionBusStatus` stay in lockstep.
13
+ */
14
+ export declare function toAsyncActionState<Response>(status: ActionStatus<Response>): AsyncActionState<Response>;
15
+ /**
16
+ * Wraps a function in an action and exposes its in-flight status, so a
17
+ * component can drive `loading`/`disabled` without a hand-rolled
18
+ * `useState(false)`. Returns `[invoke, { loading, error, response }]`.
19
+ *
20
+ * For the common app pattern (one shared ActionBus) prefer
21
+ * `useActionBusStatus`. This hook is for a standalone, component-local action.
22
+ */
23
+ export declare function useAsyncAction<Fn extends BaseHandler>(fn: Fn): readonly [
24
+ (...args: Parameters<Fn>) => Promise<ActionResponse<Awaited<ReturnType<Fn>>, Parameters<Fn>>>,
25
+ AsyncActionState<Awaited<ReturnType<Fn>>>
26
+ ];
@@ -0,0 +1,61 @@
1
+ import { useCallback, useLayoutEffect, useMemo, useRef, useSyncExternalStore, } from "react";
2
+ import { createAction } from "../action.js";
3
+ /**
4
+ * Project an action's {@link ActionStatus} into the {@link AsyncActionState}
5
+ * shape consumed by the status hooks (`pending` -> `loading`). Shared so
6
+ * `useAsyncAction` and `useActionBusStatus` stay in lockstep.
7
+ */
8
+ export function toAsyncActionState(status) {
9
+ return {
10
+ loading: status.pending,
11
+ error: status.error,
12
+ response: status.response,
13
+ };
14
+ }
15
+ /**
16
+ * Wraps a function in an action and exposes its in-flight status, so a
17
+ * component can drive `loading`/`disabled` without a hand-rolled
18
+ * `useState(false)`. Returns `[invoke, { loading, error, response }]`.
19
+ *
20
+ * For the common app pattern (one shared ActionBus) prefer
21
+ * `useActionBusStatus`. This hook is for a standalone, component-local action.
22
+ */
23
+ export function useAsyncAction(fn) {
24
+ // Keep the latest fn in a ref. The action wraps a stable indirection that
25
+ // always calls fnRef.current, so it invokes the current fn even from a
26
+ // consumer layout effect that runs after a rerender but before this hook's
27
+ // passive effects — which a useEffect+setAction swap would miss, invoking
28
+ // the previous fn. The ref is updated in a layout effect (commit phase),
29
+ // not during render: a render-phase mutation would leak a fn from a
30
+ // suspended or abandoned concurrent render that never commits into the
31
+ // currently committed UI. Layout effects run only for committed renders,
32
+ // and this one runs before any consumer layout effect declared after the
33
+ // hook call, so consumers still observe the latest fn.
34
+ const fnRef = useRef(fn);
35
+ useLayoutEffect(() => {
36
+ fnRef.current = fn;
37
+ });
38
+ const action = useMemo(() => {
39
+ const action = createAction(((...args) => fnRef.current(...args)));
40
+ // Without an error listener a throwing fn re-throws out of invoke
41
+ // (an unhandled rejection) instead of surfacing through status.
42
+ action.addErrorListener(() => { });
43
+ return action;
44
+ }, []);
45
+ const subscribe = useCallback((onChange) => {
46
+ const listener = () => {
47
+ onChange();
48
+ };
49
+ action.onStatusChange(listener);
50
+ return () => {
51
+ action.removeStatusListener(listener);
52
+ };
53
+ }, [action]);
54
+ const getSnapshot = useCallback(() => action.getStatus(), [action]);
55
+ const status = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
56
+ const invoke = useCallback((...args) => action.invoke(...args), [action]);
57
+ return [
58
+ invoke,
59
+ toAsyncActionState(status),
60
+ ];
61
+ }
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef } from "react";
2
- import { areListenerOptionsEqual } from "./listenerOptionsEqual.js";
2
+ import { areListenerOptionsEqual, fillListenerUpdateDefaults, } from "./listenerOptionsEqual.js";
3
3
  /**
4
4
  * Reconciles a single reactive listener across renders without relying on the
5
5
  * identity of the options object.
@@ -37,7 +37,16 @@ export function useReconciledListener({ keyDeps, options, subscribe, unsubscribe
37
37
  return;
38
38
  }
39
39
  if (!areListenerOptionsEqual(committedRef.current, options)) {
40
- update(context, options);
40
+ // Normalize to a full soft-option set so a field dropped since the
41
+ // last render resets to its default: updateListenerOptions is
42
+ // partial-merge, but the React path is declarative (see
43
+ // fillListenerUpdateDefaults). Carry `signal` through only when
44
+ // present (matching the old whole-options pass), so its abort wiring
45
+ // is rebound/cleared exactly as before; `context` is passed
46
+ // separately and not read from the options object.
47
+ update(context, Object.assign(Object.assign({}, fillListenerUpdateDefaults(options)), ((options === null || options === void 0 ? void 0 : options.signal) !== undefined
48
+ ? { signal: options.signal }
49
+ : {})));
41
50
  }
42
51
  committedRef.current = options;
43
52
  });
@@ -0,0 +1,35 @@
1
+ import type { KeyOf } from "../lib/types.js";
2
+ import type { BaseStore } from "../store.js";
3
+ export type EqualityFn<T> = (a: T, b: T) => boolean;
4
+ /**
5
+ * Subscribes to a derived slice of a store with custom equality. Selector
6
+ * re-execution is gated on the raw input (dep values, or a shallow compare of
7
+ * the full state), so a selector that builds a fresh object each call returns a
8
+ * stable cached reference while its input is unchanged — safe even without an
9
+ * equality fn (an un-gated fresh reference on every getSnapshot call would loop
10
+ * forever). The optional equality fn additionally bails React re-renders when a
11
+ * recompute produces an equal-but-fresh result.
12
+ *
13
+ * Two forms:
14
+ * useStoreSelector(store, (s) => `${s.first} ${s.last}`, shallowEqual?)
15
+ * useStoreSelector(store, ["first", "last"], (first, last) => …, eqFn?)
16
+ *
17
+ * The deps-keyed form recomputes only when the change batch touches its keys.
18
+ *
19
+ * Prefer the deps-keyed form for narrow reads. The selector form (no deps)
20
+ * subscribes to every store change and rebuilds the whole state via getData()
21
+ * on each one, re-running the selector even for unrelated writes (the equality
22
+ * fn still bails React re-renders, but the recompute itself is not filtered).
23
+ * The deps-keyed form both filters the subscription to its keys and avoids
24
+ * materializing the full state, so reach for it when selecting a few slices of
25
+ * a large or frequently-written store.
26
+ *
27
+ * Concurrent-safe: the selection is memoized in a render-phase `useMemo` (an
28
+ * abandoned concurrent render discards it rather than leaking it into the
29
+ * committed tree) and the committed value is recorded in an effect, not during
30
+ * render. This mirrors React's own `useSyncExternalStoreWithSelector`.
31
+ */
32
+ export declare function useStoreSelector<TStore extends BaseStore, R>(store: TStore, selector: (state: TStore["__type"]["propTypes"]) => R, equalityFn?: EqualityFn<R>): R;
33
+ export declare function useStoreSelector<TStore extends BaseStore, const D extends readonly KeyOf<TStore["__type"]["propTypes"]>[], R>(store: TStore, deps: D, selector: (...values: {
34
+ [I in keyof D]: TStore["__type"]["propTypes"][D[I]];
35
+ }) => R, equalityFn?: EqualityFn<R>): R;
@@ -0,0 +1,153 @@
1
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore, } from "react";
2
+ import { ChangeEventName } from "../store.js";
3
+ // Shallow per-entry equality for two state objects (enumerable own keys
4
+ // compared with Object.is). Used to gate selector re-execution in the
5
+ // no-deps form, whose input is rebuilt fresh by getData() on every call.
6
+ function shallowEqualObject(a, b) {
7
+ if (a === b) {
8
+ return true;
9
+ }
10
+ const aKeys = Object.keys(a);
11
+ const bKeys = Object.keys(b);
12
+ if (aKeys.length !== bKeys.length) {
13
+ return false;
14
+ }
15
+ for (const key of aKeys) {
16
+ if (!Object.prototype.hasOwnProperty.call(b, key)
17
+ || !Object.is(a[key], b[key])) {
18
+ return false;
19
+ }
20
+ }
21
+ return true;
22
+ }
23
+ export function useStoreSelector(store, arg2, arg3, arg4) {
24
+ var _a;
25
+ const deps = Array.isArray(arg2) ? arg2 : null;
26
+ const selector = (deps ? arg3 : arg2);
27
+ const equalityFn = (_a = (deps ? arg4 : arg3)) !== null && _a !== void 0 ? _a : Object.is;
28
+ // Committed selection cache. Written ONLY in an effect (commit phase) so an
29
+ // abandoned concurrent render cannot leak its selection into the committed
30
+ // tree (which a render-phase write to this cache would).
31
+ const instRef = useRef(null);
32
+ if (instRef.current === null) {
33
+ instRef.current = { hasValue: false, value: null };
34
+ }
35
+ const inst = instRef.current;
36
+ // Latest deps for the subscribe filter. Updated in a layout effect (commit
37
+ // phase), not during render, and read only inside the change listener (which
38
+ // fires after commit), so the subscription always filters on committed deps.
39
+ const depsRef = useRef(deps);
40
+ useLayoutEffect(() => {
41
+ depsRef.current = deps;
42
+ });
43
+ // The memoized selection getter. Built during render, but the useMemo result
44
+ // is part of the fiber's memoized state: an abandoned concurrent render
45
+ // discards it, so no closure leaks. Rebuilt only when the store, selector,
46
+ // equality, or deps identity changes. The committed `inst.value` is read
47
+ // (never written) here, so a re-render with an equal result bails out to the
48
+ // committed reference.
49
+ const getSelection = useMemo(() => {
50
+ let hasMemo = false;
51
+ let memoizedInput = [];
52
+ let memoized;
53
+ // Read the raw selector input (dep values, or the full state). On a
54
+ // destroyed store, read via getData() (which returns {} without
55
+ // asserting) instead of store.get() (which throws): getSnapshot can
56
+ // run for a still-mounted component after the store is destroyed
57
+ // (e.g. a provider torn down first), and must not throw out of
58
+ // render. Returns the input as an arg array so it can be both
59
+ // shallow-compared and spread into the selector.
60
+ const readInput = () => {
61
+ if (deps) {
62
+ if (store.isDestroyed()) {
63
+ const snapshot = store.getData();
64
+ return deps.map((d) => snapshot[d]);
65
+ }
66
+ return deps.map((d) => store.get(d));
67
+ }
68
+ return [store.getData()];
69
+ };
70
+ // Compare two raw inputs. The deps form holds dep values, compared
71
+ // by identity (the store replaces values on change, so identity
72
+ // tracks change). The selector form holds a single full-state
73
+ // object rebuilt fresh by getData() on every call, so it must be
74
+ // shallow-compared by entries rather than reference.
75
+ const inputsEqual = (a, b) => {
76
+ if (deps) {
77
+ if (a.length !== b.length) {
78
+ return false;
79
+ }
80
+ for (let i = 0; i < a.length; i++) {
81
+ if (!Object.is(a[i], b[i])) {
82
+ return false;
83
+ }
84
+ }
85
+ return true;
86
+ }
87
+ return shallowEqualObject(a[0], b[0]);
88
+ };
89
+ return () => {
90
+ const input = readInput();
91
+ if (!hasMemo) {
92
+ hasMemo = true;
93
+ memoizedInput = input;
94
+ const next = selector(...input);
95
+ if (inst.hasValue && equalityFn(inst.value, next)) {
96
+ memoized = inst.value;
97
+ return inst.value;
98
+ }
99
+ memoized = next;
100
+ return next;
101
+ }
102
+ // Gate selector re-execution on the raw input. getSnapshot must
103
+ // return a cached reference that only changes when the store
104
+ // changes; re-running a fresh-object selector on every call (and
105
+ // relying solely on equalityFn) would return a new reference
106
+ // each call under the default Object.is and loop forever. When
107
+ // the input is unchanged we return the cached selection without
108
+ // re-running the selector — mirroring React's own
109
+ // useSyncExternalStoreWithSelector, which gates on snapshot
110
+ // identity (here the store's reads are not stable references, so
111
+ // we shallow-compare the input instead).
112
+ if (inputsEqual(memoizedInput, input)) {
113
+ return memoized;
114
+ }
115
+ memoizedInput = input;
116
+ const next = selector(...input);
117
+ if (equalityFn(memoized, next)) {
118
+ return memoized;
119
+ }
120
+ memoized = next;
121
+ return next;
122
+ };
123
+ }, [store, selector, equalityFn, deps, inst]);
124
+ const subscribe = useCallback((onStoreChange) => {
125
+ // subscribe can run (during commit) for an already-destroyed store
126
+ // — e.g. a provider torn down before this component mounts, or a
127
+ // `store` change re-running subscribe. control() reaches the
128
+ // destroyed control bus's addListener, which throws "EventBus is
129
+ // destroyed" out of render. Skip subscribing and hand back a no-op
130
+ // cleanup; getSelection already reads destroyed stores safely.
131
+ if (store.isDestroyed()) {
132
+ return () => { };
133
+ }
134
+ const listener = (names) => {
135
+ const currentDeps = depsRef.current;
136
+ if (currentDeps
137
+ && !names.some((n) => currentDeps.indexOf(n) !== -1)) {
138
+ return;
139
+ }
140
+ onStoreChange();
141
+ };
142
+ store.control(ChangeEventName, listener);
143
+ return () => {
144
+ store.removeControl(ChangeEventName, listener);
145
+ };
146
+ }, [store]);
147
+ const value = useSyncExternalStore(subscribe, getSelection, getSelection);
148
+ useEffect(() => {
149
+ inst.hasValue = true;
150
+ inst.value = value;
151
+ }, [value, inst]);
152
+ return value;
153
+ }
@@ -1,6 +1,15 @@
1
1
  import { useCallback, useSyncExternalStore } from "react";
2
2
  export function useStoreState(store, key) {
3
3
  const subscribe = useCallback((onStoreChange) => {
4
+ // subscribe can run (during commit) for a store that was already
5
+ // destroyed — e.g. a provider torn down before this component
6
+ // mounts, or a `key`/`store` change re-running subscribe. onChange
7
+ // reaches the destroyed changes event's addListener, which throws
8
+ // "Event is destroyed" out of render. Skip subscribing and hand back
9
+ // a no-op cleanup; getSnapshot already reads destroyed stores safely.
10
+ if (store.isDestroyed()) {
11
+ return () => { };
12
+ }
4
13
  const listener = () => {
5
14
  onStoreChange();
6
15
  };
@@ -9,10 +18,22 @@ export function useStoreState(store, key) {
9
18
  store.removeOnChange(key, listener);
10
19
  };
11
20
  }, [store, key]);
12
- const getSnapshot = useCallback(() => store.get(key), [store, key]);
21
+ const getSnapshot = useCallback(
22
+ // getSnapshot can run for a still-mounted component after the store is
23
+ // destroyed (e.g. a provider torn down first), and must not throw out of
24
+ // render. On a destroyed store read via getData() (returns {} without
25
+ // asserting) instead of get() (which throws). Mirrors useStoreSelector.
26
+ () => (store.isDestroyed()
27
+ ? store.getData()[key]
28
+ : store.get(key)), [store, key]);
13
29
  const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
14
30
  const setter = useCallback((value) => {
15
31
  if (typeof value === "function") {
32
+ // The cast is required by tsc (the typeof-narrowed `value` is
33
+ // `Setter | (ValueType & Function)`, not all callable), even
34
+ // though no-unnecessary-type-assertion disagrees on this TS
35
+ // version.
36
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
16
37
  store.set(key, value(store.get(key)));
17
38
  }
18
39
  else {
package/dist/react.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from "./react/ErrorBoundary.js";
2
2
  export * from "./react/useAction.js";
3
3
  export * from "./react/useActionBus.js";
4
+ export * from "./react/useActionBusStatus.js";
5
+ export * from "./react/useAsyncAction.js";
4
6
  export * from "./react/useActionMap.js";
5
7
  export * from "./react/useEvent.js";
6
8
  export * from "./react/useEventBus.js";
@@ -10,4 +12,5 @@ export * from "./react/useListenToEvent.js";
10
12
  export * from "./react/useListenToEventBus.js";
11
13
  export * from "./react/useListenToStoreChanges.js";
12
14
  export * from "./react/useStore.js";
15
+ export * from "./react/useStoreSelector.js";
13
16
  export * from "./react/useStoreState.js";
package/dist/react.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from "./react/ErrorBoundary.js";
2
2
  export * from "./react/useAction.js";
3
3
  export * from "./react/useActionBus.js";
4
+ export * from "./react/useActionBusStatus.js";
5
+ export * from "./react/useAsyncAction.js";
4
6
  export * from "./react/useActionMap.js";
5
7
  export * from "./react/useEvent.js";
6
8
  export * from "./react/useEventBus.js";
@@ -10,4 +12,5 @@ export * from "./react/useListenToEvent.js";
10
12
  export * from "./react/useListenToEventBus.js";
11
13
  export * from "./react/useListenToStoreChanges.js";
12
14
  export * from "./react/useStore.js";
15
+ export * from "./react/useStoreSelector.js";
13
16
  export * from "./react/useStoreState.js";
package/dist/store.d.ts CHANGED
@@ -42,8 +42,11 @@ export declare function createStore<PropMap extends BasePropMap = BasePropMap>(i
42
42
  <K extends KeyOf<PropMap>>(key: K, value: PropMap[K] | undefined): void;
43
43
  (key: Partial<PropMap>): void;
44
44
  };
45
+ readonly computed: <K extends KeyOf<PropMap>, const D extends readonly KeyOf<PropMap>[]>(key: K, deps: D, fn: (...values: { [I in keyof D]: PropMap[D[I]] | undefined; }) => PropMap[K]) => void;
45
46
  readonly isEmpty: () => boolean;
46
47
  readonly reset: () => void;
48
+ readonly destroy: () => void;
49
+ readonly isDestroyed: () => boolean;
47
50
  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
51
  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
52
  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;