@kuindji/reactive 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,8 +9,8 @@ A JavaScript/TypeScript utility library for building reactive applications with
9
9
  ## Features
10
10
 
11
11
  - **Event System**: Event emitter with subscriber/dispatcher and collector modes
12
- - **Action System**: Async action handling with error management and response tracking
13
- - **Store System**: Reactive state management with change tracking and validation
12
+ - **Action System**: Async action handling with error management, response tracking and loading/error/response status
13
+ - **Store System**: Reactive state management with change tracking, validation and computed/derived values
14
14
  - **EventBus**: Centralized event management for complex applications
15
15
  - **ActionBus & ActionMap**: Organized action management with error handling
16
16
  - **React Integration**: Full React hooks support with error boundaries
@@ -99,9 +99,21 @@ event.addListener(handler, {
99
99
  tags: string[], // Listener tags for filtering; default undefined
100
100
  async: booleantrue, // Call this listener asynchronously; default false
101
101
  extraData: object, // Custom data will be passed to filter()
102
+ signal: AbortSignal, // Auto-remove the listener when this signal aborts
102
103
  });
103
104
  ```
104
105
 
106
+ When a `signal` is provided, the listener is removed automatically once the
107
+ signal aborts (and is not added at all if the signal is already aborted). The
108
+ abort subscription is cleaned up if the listener is removed first, so there is no
109
+ dangling reference into a still-live signal:
110
+
111
+ ```typescript
112
+ const controller = new AbortController();
113
+ event.addListener(handler, { signal: controller.signal });
114
+ controller.abort(); // handler is now removed
115
+ ```
116
+
105
117
  ### Collector
106
118
 
107
119
  Collector allows you to gather data from listeners.
@@ -159,9 +171,10 @@ const value = event.pipe(1); // value = 4
159
171
 
160
172
  - `addListener(listener, options?)` - Add event listener
161
173
  - **Aliases**: `on()`, `listen()`, `subscribe()`
174
+ - `once(listener, options?)` - Add a listener that is removed after a single call (sugar for `addListener(listener, { ...options, limit: 1 })`)
162
175
  - `removeListener(listener, context?, tag?)` - Remove specific listener
163
176
  - **Aliases**: `un()`, `off()`, `remove()`, `unsubscribe()`
164
- - `updateListenerOptions(listener, context?, nextOptions?)` - Update a registered listener's soft options (`limit`, `start`, `async`, `tags`, `extraData`, `alwaysFirst`/`alwaysLast`) **in place**, preserving its `called`/`count` counters. Matches the listener by `listener` + `context`. Returns `true` if a listener was found. `context` is an identity field and is not updated here (resubscribe to change it); `first` is insertion-time only and ignored. Lowering `limit` to at/below the current `called` removes the listener immediately.
177
+ - `updateListenerOptions(listener, context?, nextOptions?)` - Update a registered listener's soft options (`limit`, `start`, `async`, `tags`, `extraData`, `alwaysFirst`/`alwaysLast`, `signal`) **in place**, preserving its `called`/`count` counters. This is a **partial update**: only fields explicitly present in `nextOptions` change; any omitted field keeps its current value. Pass a field explicitly to clear it (e.g. `limit: 0` for unlimited, `signal: null` to drop abort wiring). Matches the listener by `listener` + `context`. Returns `true` if a listener was found. `context` is an identity field and is not updated here (resubscribe to change it); `first` is insertion-time only and ignored. Lowering `limit` to at/below the current `called` removes the listener immediately.
165
178
  - `hasListener(listener?, context?, tag?)` - Check if listener exists
166
179
  - **Aliases**: `has()`
167
180
  - `removeAllListeners(tag?)` - Remove all listeners (optionally by tag)
@@ -197,8 +210,17 @@ const value = event.pipe(1); // value = 4
197
210
  - `suspend(withQueue?: boolean)` - Suspend event triggering; When `withQueue=true`, all trigger calls will be queued and replayed after resume()
198
211
  - `resume()` - Resume event triggering
199
212
  - `reset()` - Reset event state
213
+ - `destroy()` - Tear down the event: remove all listeners (unwinding any `AbortSignal` subscriptions) and mark it dead. After `destroy()`, `trigger()` and `addListener()` throw rather than silently no-op.
214
+ - `isDestroyed()` - Returns `true` once `destroy()` has been called
200
215
  - `withTags(tags: string[], callback: () => CallbackResponse) => CallbackResponse` - Execute callback with specific tags
201
216
 
217
+ #### Introspection
218
+
219
+ - `listenerCount(tag?)` - Number of registered listeners, optionally filtered by tag
220
+ - `triggeredCount()` - How many times the event has been triggered
221
+ - `lastTriggerArgs()` - The most recent trigger arguments (a copy), or `null` if never triggered
222
+ - `getListeners()` - Read-only projection of registered listeners (`handler`, `context`, `tags`, `limit`, `start`, `called`, `count`, `async`, ordering flags, `extraData`). Mutating the returned objects does not affect the event.
223
+
202
224
  ## EventBus
203
225
 
204
226
  ### Description
@@ -522,7 +544,9 @@ customSource.trigger("appStart");
522
544
  - `removeEventSource(source)` - Remove event source
523
545
  - `suspendAll(withQueue?)` - Suspend all events
524
546
  - `resumeAll()` - Resume all events
525
- - `reset()` - Reset all events
547
+ - `reset()` - Reset all events: unrelay all relays and remove all event sources (detaching their external listeners), then clear every owned event and interception/tag state. The bus stays usable afterwards.
548
+ - `destroy()` - Tear down the bus: unrelay all relays, remove all event sources (detaching their external listeners), destroy every owned event, and mark the bus dead. After `destroy()`, `trigger()`/`on()` throw.
549
+ - `isDestroyed()` - Returns `true` once `destroy()` has been called
526
550
  - `withTags(tags, callback)` - Execute callback with specific tags
527
551
 
528
552
  ## Action
@@ -571,6 +595,8 @@ const result = await fetchUserAction.invoke("user123");
571
595
  - **Aliases**: `un()`, `off()`, `remove()`, `unsubscribe()`
572
596
  - `updateListenerOptions(handler, context?, nextOptions?)` - Update a response listener's soft options in place (see Event's `updateListenerOptions`)
573
597
  - `removeAllListeners(tag?)` - Remove all listeners
598
+ - `destroy()` - Tear down the action: destroy its response, before-action, error and status events and mark it dead. After `destroy()`, `invoke()`/`addListener()` throw.
599
+ - `isDestroyed()` - Returns `true` once `destroy()` has been called
574
600
 
575
601
  #### Error Handling
576
602
 
@@ -578,6 +604,30 @@ const result = await fetchUserAction.invoke("user123");
578
604
  - `removeErrorListener(handler, context?)` - Remove error listener
579
605
  - `removeAllErrorListeners(tag?)` - Remove all error listeners
580
606
 
607
+ #### Status (loading / error / response)
608
+
609
+ An action tracks the status of its `invoke` lifecycle so UI can drive
610
+ `loading`/`disabled` without a hand-rolled `useState(false)`. `pending` is true
611
+ while one or more invocations are in flight; `response`/`error` hold the last
612
+ settled outcome (a before-action veto settles to neither). This is **not** a
613
+ cache — `response` is just the last value.
614
+
615
+ - `getStatus()` - Returns `{ pending: boolean, error: Error | null, response: T | null }`. The reference is stable while unchanged (safe for `useSyncExternalStore`).
616
+ - `onStatusChange(handler)` - Subscribe to status changes
617
+ - `removeStatusListener(handler)` - Remove a status listener
618
+
619
+ ```typescript
620
+ const saveAction = createAction(async (data: FormData) => save(data));
621
+
622
+ saveAction.onStatusChange(({ pending, error }) => {
623
+ button.disabled = pending;
624
+ });
625
+
626
+ await saveAction.invoke(form); // pending -> true, then false on settle
627
+ ```
628
+
629
+ In React, prefer the `useAsyncAction` / `useActionBusStatus` hooks (see React Hooks).
630
+
581
631
  #### Utility Methods
582
632
 
583
633
  - `promise(options?)` - Get promise for next invocation
@@ -703,6 +753,17 @@ const user = await actionBus.invoke("fetchUser", "user123");
703
753
  - `removeListener(name, handler, context?, tag?)` - Remove listener
704
754
  - **Aliases**: `un()`, `off()`, `remove()`, `unsubscribe()`
705
755
  - `updateListenerOptions(name, handler, context?, nextOptions?)` - Update a response listener's soft options in place (see Event's `updateListenerOptions`)
756
+ - `destroy()` - Tear down the bus: destroy every owned action and the error event, then drop them all. After `destroy()`, `invoke()`/`on()` throw.
757
+ - `isDestroyed()` - Returns `true` once `destroy()` has been called
758
+
759
+ #### Status (loading / error / response)
760
+
761
+ Delegates to the underlying action's status (see Action → Status). This is the
762
+ primary path for apps that route mutations through one shared ActionBus.
763
+
764
+ - `getStatus(name)` - Status for a named action; an unregistered name reports an idle status
765
+ - `onStatusChange(name, handler)` - Subscribe to a named action's status. Subscribing before the action is registered is retained and attached automatically once it is added (and re-attached if the action is later removed and re-added)
766
+ - `removeStatusListener(name, handler)` - Remove a status listener (also clears a subscription retained before registration)
706
767
 
707
768
  #### Error Handling
708
769
 
@@ -761,9 +822,12 @@ const userData = userStore.get([ "name", "email" ]); // { name: string, email: s
761
822
  - `asyncSet(data)` - Async set multiple properties
762
823
  - `get(key)` - Get single property
763
824
  - `get(keys)` - Get multiple properties
825
+ - `computed(key, deps, fn)` - Register a derived value (see Computed values)
764
826
  - `isEmpty()` - Check if store is empty
765
827
  - `getData()` - Get all store data
766
- - `reset()` - Clear store data
828
+ - `reset()` - Clear store data. Computed keys are re-seeded from the cleared dependencies (so they stay consistent with `fn(deps)` rather than going stale) and remain live.
829
+ - `destroy()` - Tear down the store: destroy the underlying change/pipe/control buses and drop all data. After `destroy()`, `set()`/`get()` throw.
830
+ - `isDestroyed()` - Returns `true` once `destroy()` has been called
767
831
 
768
832
  #### Event Methods
769
833
 
@@ -784,6 +848,40 @@ const userData = userStore.get([ "name", "email" ]); // { name: string, email: s
784
848
 
785
849
  - `batch(fn)` - Batch multiple changes
786
850
 
851
+ #### Computed values
852
+
853
+ Declare a derived key in the store type, then attach its derivation with
854
+ `computed(key, deps, fn)`. It recomputes automatically when any dependency
855
+ changes and notifies like any other key — `get`, `getData`, `onChange`,
856
+ `useStoreState` and `useStoreSelector` all see it transparently. Computed keys
857
+ are read-only: calling `set` on one throws. Computed-of-computed chains are
858
+ supported, and a cyclic computed throws rather than looping.
859
+
860
+ ```typescript
861
+ type UserStore = {
862
+ first: string;
863
+ last: string;
864
+ fullName: string; // declared in the type, registered as computed
865
+ };
866
+
867
+ const store = createStore<UserStore>({ first: "Jane", last: "Doe" });
868
+
869
+ store.computed("fullName", [ "first", "last" ], (first, last) => `${first} ${last}`);
870
+
871
+ store.get("fullName"); // "Jane Doe"
872
+ store.onChange("fullName", (v) => console.log(v));
873
+ store.set("first", "John"); // fullName recomputes -> "John Doe"
874
+ store.set("fullName", "x"); // throws: computed is read-only
875
+ ```
876
+
877
+ > **Note:** recompute is registration-order, not topologically sorted, so a
878
+ > chained or diamond-shaped computed may recompute internally more than once per
879
+ > change. This is invisible to consumers: a single `set(...)`/`set({...})`/`batch`
880
+ > coalesces the `onChange` stream, so each computed fires `onChange` once with
881
+ > its settled value and the correct previous value. The final value is always
882
+ > correct. Registering base computeds before dependents reduces redundant
883
+ > internal recomputes.
884
+
787
885
  ## React Hooks
788
886
 
789
887
  ### Description
@@ -965,6 +1063,31 @@ useListenToStoreChanges(
965
1063
  )
966
1064
  ```
967
1065
 
1066
+ Select a derived slice with equality (bails out of re-renders while the result
1067
+ is unchanged). Two forms — a selector over the whole state, or a deps-keyed form
1068
+ that recomputes only when the listed keys change:
1069
+
1070
+ ```typescript
1071
+ // selector form (default equality is Object.is)
1072
+ const label = useStoreSelector(store, (s) => `${s.first} ${s.last}`, shallowEqual?);
1073
+
1074
+ // deps-keyed form
1075
+ const anyLoading = useStoreSelector(store, [ "a", "b", "c" ], (a, b, c) => a || b || c);
1076
+ ```
1077
+
1078
+ Drive `loading`/`disabled` from an action's status. `useActionBusStatus` is the
1079
+ primary path for apps built around one shared ActionBus; `useAsyncAction` wraps a
1080
+ standalone function:
1081
+
1082
+ ```typescript
1083
+ // shared ActionBus
1084
+ const { loading, error, response } = useActionBusStatus(appActions, "user/login");
1085
+
1086
+ // standalone action
1087
+ const [ submit, { loading, error } ] = useAsyncAction(saveProfileFn);
1088
+ // <Button loading={loading} disabled={loading} onClick={() => submit(form)} />
1089
+ ```
1090
+
968
1091
  ### Reconciliation across renders
969
1092
 
970
1093
  Hook inputs are reconciled on every render using semantic comparison, so you
package/dist/action.d.ts CHANGED
@@ -9,6 +9,18 @@ export type ActionResponse<Response = any, Args extends unknown[] = unknown[]> =
9
9
  args: Args;
10
10
  };
11
11
  export type ListenerSignature<ActionSignature extends BaseHandler> = (arg: ActionResponse<Awaited<ReturnType<ActionSignature>>, Parameters<ActionSignature>>) => void;
12
+ /**
13
+ * Status of an action's `invoke` lifecycle, suitable for driving
14
+ * `loading`/`disabled` UI. `pending` is true while one or more invocations are
15
+ * in flight; `response`/`error` hold the last settled outcome (a before-veto
16
+ * settles to neither). This is not a cache — `response` is just the last value.
17
+ */
18
+ export type ActionStatus<Response = any> = {
19
+ pending: boolean;
20
+ error: Error | null;
21
+ response: Response | null;
22
+ };
23
+ export type StatusListenerSignature<ActionSignature extends BaseHandler> = (status: ActionStatus<Awaited<ReturnType<ActionSignature>>>) => void;
12
24
  export type BeforeActionSignature<ActionSignature extends BaseHandler> = (...args: Parameters<ActionSignature>) => false | void | Promise<false | void>;
13
25
  export type ActionDefinitionHelper<A extends BaseHandler> = {
14
26
  actionSignature: A;
@@ -21,10 +33,17 @@ export type ActionDefinitionHelper<A extends BaseHandler> = {
21
33
  beforeActionSignature: BeforeActionSignature<A>;
22
34
  errorListenerArgument: ErrorResponse<Parameters<A>>;
23
35
  errorListenerSignature: ErrorListenerSignature<Parameters<A>>;
36
+ statusType: ActionStatus<Awaited<ReturnType<A>>>;
37
+ statusListenerSignature: StatusListenerSignature<A>;
24
38
  };
25
39
  export declare function createAction<A extends BaseHandler>(action: A): ApiType<ActionDefinitionHelper<A>, {
26
40
  readonly invoke: (...args: Parameters<A>) => Promise<ActionResponse<Awaited<ReturnType<A>>, Parameters<A>>>;
27
41
  readonly setAction: (nextAction: A) => void;
42
+ readonly destroy: () => void;
43
+ readonly isDestroyed: () => boolean;
44
+ readonly getStatus: () => ActionStatus<Awaited<ReturnType<A>>>;
45
+ readonly onStatusChange: (handler: StatusListenerSignature<A>, listenerOptions?: import("./event.js").ListenerOptions) => void;
46
+ readonly removeStatusListener: (handler: StatusListenerSignature<A>, context?: object | null, tag?: string | null) => boolean;
28
47
  readonly addListener: (handler: ListenerSignature<A>, listenerOptions?: import("./event.js").ListenerOptions) => void;
29
48
  /** @alias addListener */
30
49
  readonly on: (handler: ListenerSignature<A>, listenerOptions?: import("./event.js").ListenerOptions) => void;
package/dist/action.js CHANGED
@@ -14,10 +14,86 @@ export function createAction(action) {
14
14
  // place via setAction without disturbing any listeners (response, before
15
15
  // and error listeners live in separate events independent of the function).
16
16
  let actionFn = action;
17
- const { trigger, addListener, removeAllListeners, removeListener, updateListenerOptions, promise, } = createEvent();
18
- const { all: triggerBeforeAction, addListener: addBeforeActionListener, removeAllListeners: removeAllBeforeActionListeners, removeListener: removeBeforeActionListener, promise: beforeActionPromise, } = createEvent();
19
- const { trigger: triggerError, addListener: addErrorListener, removeAllListeners: removeAllErrorListeners, removeListener: removeErrorListener, promise: errorPromise, hasListener: hasErrorListeners, } = createEvent();
17
+ const { trigger, addListener, removeAllListeners, removeListener, updateListenerOptions, promise, destroy: destroyResponseEvent, } = createEvent();
18
+ const { all: triggerBeforeAction, addListener: addBeforeActionListener, removeAllListeners: removeAllBeforeActionListeners, removeListener: removeBeforeActionListener, promise: beforeActionPromise, destroy: destroyBeforeEvent, } = createEvent();
19
+ const { trigger: triggerError, addListener: addErrorListener, removeAllListeners: removeAllErrorListeners, removeListener: removeErrorListener, promise: errorPromise, hasListener: hasErrorListeners, destroy: destroyErrorEvent, } = createEvent();
20
+ // Status is a side channel over the invoke lifecycle: a dedicated event so
21
+ // a React hook can subscribe through useSyncExternalStore. The status
22
+ // object reference is kept stable and only rebuilt when a field actually
23
+ // changes, which is required for useSyncExternalStore to bail out of
24
+ // redundant renders.
25
+ const { trigger: triggerStatus, addListener: addStatusListener, removeListener: removeStatusListener, destroy: destroyStatusEvent, } = createEvent();
26
+ let destroyed = false;
27
+ let inFlight = 0;
28
+ let lastResponse = null;
29
+ let lastError = null;
30
+ // Frozen so getStatus() can hand out the live reference (required for
31
+ // useSyncExternalStore to bail out of redundant renders) without a consumer
32
+ // being able to mutate it — a tampered `pending` would make updateStatus()
33
+ // believe nothing changed and suppress the next notification.
34
+ let currentStatus = Object.freeze({
35
+ pending: false,
36
+ error: null,
37
+ response: null,
38
+ });
39
+ const updateStatus = () => {
40
+ const pending = inFlight > 0;
41
+ if (currentStatus.pending === pending
42
+ && currentStatus.error === lastError
43
+ && currentStatus.response === lastResponse) {
44
+ return;
45
+ }
46
+ currentStatus = Object.freeze({
47
+ pending,
48
+ error: lastError,
49
+ response: lastResponse,
50
+ });
51
+ // The status event may have been torn down while an invocation was in
52
+ // flight. Still reconcile `currentStatus` above (so getStatus() does not
53
+ // strand `pending: true` after a mid-flight destroy), but skip emitting
54
+ // onto the dead event, which would throw and mask the real outcome.
55
+ if (destroyed) {
56
+ return;
57
+ }
58
+ // Status is a side channel. A throwing status listener must not corrupt
59
+ // the invoke lifecycle: if it propagated here it would, depending on the
60
+ // call site, abort execution or skip the inFlight decrement and strand
61
+ // `pending: true`. Isolate it and surface it via the error event.
62
+ try {
63
+ triggerStatus(currentStatus);
64
+ }
65
+ catch (error) {
66
+ // Surface the failure via the error event, but a throwing error
67
+ // listener must not re-escape either: this runs before invoke()
68
+ // enters its try/finally, so any escape would strand pending:true
69
+ // and skip execution entirely.
70
+ try {
71
+ triggerError({
72
+ error: error instanceof Error
73
+ ? error
74
+ : new Error(String(error)),
75
+ args: [],
76
+ type: "action-status",
77
+ });
78
+ }
79
+ catch (_a) {
80
+ // Nothing left to route to; swallow to protect the lifecycle.
81
+ }
82
+ }
83
+ };
84
+ const getStatus = () => currentStatus;
20
85
  const invoke = (...args) => __awaiter(this, void 0, void 0, function* () {
86
+ if (destroyed) {
87
+ throw new Error("Action is destroyed");
88
+ }
89
+ // Snapshot whether error listeners existed at invocation start. The
90
+ // catch below ORs this with a live re-check: the snapshot guards against
91
+ // destroy() tearing listeners down mid-flight (which must not flip a
92
+ // handled failure into a rejection), while the live check still routes
93
+ // the error to a listener registered after invoke() began.
94
+ const handlesErrors = hasErrorListeners();
95
+ inFlight++;
96
+ updateStatus();
21
97
  try {
22
98
  const beforeResponse = triggerBeforeAction(...args);
23
99
  const beforeResults = isPromiseLike(beforeResponse)
@@ -25,12 +101,22 @@ export function createAction(action) {
25
101
  : beforeResponse;
26
102
  for (const before of beforeResults) {
27
103
  if (before === false) {
104
+ // A before-veto is a no-op for the caller, not a settlement:
105
+ // leave lastResponse/lastError untouched so a vetoed
106
+ // invocation cannot wipe the status of a concurrent (or
107
+ // prior) real invocation. A fresh action still reads idle
108
+ // because both start null.
28
109
  const response = {
29
110
  response: null,
30
111
  error: "Action cancelled",
31
112
  args: args,
32
113
  };
33
- trigger(response);
114
+ // Skip emitting if destroyed mid-flight: the caller still
115
+ // gets its settled response, but the torn-down event is not
116
+ // triggered (which would throw "Event is destroyed").
117
+ if (!destroyed) {
118
+ trigger(response);
119
+ }
34
120
  return response;
35
121
  }
36
122
  }
@@ -38,16 +124,33 @@ export function createAction(action) {
38
124
  if (isPromiseLike(result)) {
39
125
  result = yield Promise.resolve(result);
40
126
  }
127
+ lastResponse = result;
128
+ lastError = null;
41
129
  const response = {
42
130
  response: result,
43
131
  error: null,
44
132
  args: args,
45
133
  };
46
- trigger(response);
134
+ // A successful invocation must still resolve with its result even if
135
+ // the action was destroyed while awaiting; only skip the emit.
136
+ if (!destroyed) {
137
+ trigger(response);
138
+ }
47
139
  return response;
48
140
  }
49
141
  catch (error) {
50
- if (!hasErrorListeners()) {
142
+ // Record the failure before the re-throw branch so status is
143
+ // correct even when invoke re-throws (no error listener).
144
+ lastError = error instanceof Error
145
+ ? error
146
+ : new Error(error);
147
+ lastResponse = null;
148
+ // Handle the error if listeners existed at invoke start OR were
149
+ // registered while the invocation was in flight. The start-of-invoke
150
+ // snapshot is retained (rather than relying solely on the live check)
151
+ // so that destroy() tearing the listeners down mid-flight cannot flip
152
+ // a previously-handled failure into a rejection.
153
+ if (!handlesErrors && !hasErrorListeners()) {
51
154
  throw error;
52
155
  }
53
156
  const response = {
@@ -55,23 +158,43 @@ export function createAction(action) {
55
158
  error: error instanceof Error ? error.message : error,
56
159
  args: args,
57
160
  };
58
- trigger(response);
59
- triggerError({
60
- error: error instanceof Error
61
- ? error
62
- : new Error(error),
63
- args: args,
64
- type: "action",
65
- });
161
+ if (!destroyed) {
162
+ trigger(response);
163
+ triggerError({
164
+ error: lastError,
165
+ args: args,
166
+ type: "action",
167
+ });
168
+ }
66
169
  return response;
67
170
  }
171
+ finally {
172
+ inFlight--;
173
+ updateStatus();
174
+ }
68
175
  });
69
176
  const setAction = (nextAction) => {
70
177
  actionFn = nextAction;
71
178
  };
179
+ // One-call teardown: destroy the underlying response/before/error/status
180
+ // events and mark the action dead. Post-destroy invoke/addListener throw
181
+ // rather than silently no-op.
182
+ const destroy = () => {
183
+ destroyResponseEvent();
184
+ destroyBeforeEvent();
185
+ destroyErrorEvent();
186
+ destroyStatusEvent();
187
+ destroyed = true;
188
+ };
189
+ const isDestroyed = () => destroyed;
72
190
  const api = {
73
191
  invoke,
74
192
  setAction,
193
+ destroy,
194
+ isDestroyed,
195
+ getStatus,
196
+ onStatusChange: addStatusListener,
197
+ removeStatusListener,
75
198
  addListener,
76
199
  /** @alias addListener */
77
200
  on: addListener,
@@ -21,6 +21,11 @@ export declare function createActionBus<ActionsMap extends BaseActionsMap>(initi
21
21
  readonly has: (name: MapKey) => boolean;
22
22
  readonly get: <K extends KeyOf<GetActionDefinitionsMap<ActionsMap>>>(name: K) => GetActionTypesMap<ActionsMap>[K];
23
23
  readonly invoke: <K extends KeyOf<GetActionDefinitionsMap<ActionsMap>>>(name: K, ...args: GetActionDefinitionsMap<ActionsMap>[K]["actionArguments"]) => Promise<import("./action.js").ActionResponse<Awaited<ReturnType<ActionsMap[K]>>, Parameters<ActionsMap[K]>>>;
24
+ readonly destroy: () => void;
25
+ readonly isDestroyed: () => boolean;
26
+ readonly getStatus: <K extends KeyOf<GetActionDefinitionsMap<ActionsMap>>>(name: K) => GetActionDefinitionsMap<ActionsMap>[K]["statusType"];
27
+ readonly onStatusChange: <K extends KeyOf<GetActionDefinitionsMap<ActionsMap>>>(name: K, handler: GetActionDefinitionsMap<ActionsMap>[K]["statusListenerSignature"]) => void;
28
+ readonly removeStatusListener: <K extends KeyOf<GetActionDefinitionsMap<ActionsMap>>>(name: K, handler: GetActionDefinitionsMap<ActionsMap>[K]["statusListenerSignature"]) => boolean | undefined;
24
29
  readonly addListener: <K extends KeyOf<GetActionDefinitionsMap<ActionsMap>>>(name: K, handler: GetActionDefinitionsMap<ActionsMap>[K]["listenerSignature"], options?: ListenerOptions) => void;
25
30
  /** @alias addListener */
26
31
  readonly on: <K extends KeyOf<GetActionDefinitionsMap<ActionsMap>>>(name: K, handler: GetActionDefinitionsMap<ActionsMap>[K]["listenerSignature"], options?: ListenerOptions) => void;