@wirestate/react 0.6.3 → 0.7.0-experimental.2

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 (158) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +109 -25
  3. package/cjs/development/index.js +462 -222
  4. package/cjs/development/index.js.map +1 -1
  5. package/cjs/development/lib.js +167 -45
  6. package/cjs/development/lib.js.map +1 -1
  7. package/cjs/development/test-utils.js +22 -11
  8. package/cjs/development/test-utils.js.map +1 -1
  9. package/cjs/production/index.js +1 -1
  10. package/cjs/production/index.js.map +1 -1
  11. package/cjs/production/lib.js +1 -1
  12. package/cjs/production/lib.js.map +1 -1
  13. package/cjs/production/test-utils.js +1 -1
  14. package/cjs/production/test-utils.js.map +1 -1
  15. package/esm/development/commands/use-command-caller.js +16 -3
  16. package/esm/development/commands/use-command-caller.js.map +1 -1
  17. package/esm/development/commands/use-command-handler.js +20 -4
  18. package/esm/development/commands/use-command-handler.js.map +1 -1
  19. package/esm/development/commands/use-optional-command-caller.js +20 -4
  20. package/esm/development/commands/use-optional-command-caller.js.map +1 -1
  21. package/esm/development/context/container-context.js +16 -0
  22. package/esm/development/context/container-context.js.map +1 -0
  23. package/esm/development/context/use-container.js +33 -0
  24. package/esm/development/context/use-container.js.map +1 -0
  25. package/esm/development/context/use-root-container.js +35 -0
  26. package/esm/development/context/use-root-container.js.map +1 -0
  27. package/esm/development/context/use-scope.js +31 -0
  28. package/esm/development/context/use-scope.js.map +1 -0
  29. package/esm/development/error/error-code.js +1 -3
  30. package/esm/development/error/error-code.js.map +1 -1
  31. package/esm/development/events/use-event-emitter.js +20 -4
  32. package/esm/development/events/use-event-emitter.js.map +1 -1
  33. package/esm/development/events/use-event.js +18 -4
  34. package/esm/development/events/use-event.js.map +1 -1
  35. package/esm/development/events/use-events-handler.js +18 -3
  36. package/esm/development/events/use-events-handler.js.map +1 -1
  37. package/esm/development/events/use-events.js +18 -4
  38. package/esm/development/events/use-events.js.map +1 -1
  39. package/esm/development/index.js +12 -10
  40. package/esm/development/index.js.map +1 -1
  41. package/esm/development/injection/use-injection.js +36 -0
  42. package/esm/development/injection/use-injection.js.map +1 -0
  43. package/esm/development/injection/use-optional-injection.js +40 -0
  44. package/esm/development/injection/use-optional-injection.js.map +1 -0
  45. package/esm/development/provision/container-activator.js +33 -0
  46. package/esm/development/provision/container-activator.js.map +1 -0
  47. package/esm/development/provision/container-provider.js +74 -0
  48. package/esm/development/provision/container-provider.js.map +1 -0
  49. package/esm/development/provision/sub-container-provider.js +69 -0
  50. package/esm/development/provision/sub-container-provider.js.map +1 -0
  51. package/esm/development/provision/use-container-provision-state.js +76 -0
  52. package/esm/development/provision/use-container-provision-state.js.map +1 -0
  53. package/esm/development/queries/use-optional-query-caller.js +16 -4
  54. package/esm/development/queries/use-optional-query-caller.js.map +1 -1
  55. package/esm/development/queries/use-optional-sync-query-caller.js +14 -3
  56. package/esm/development/queries/use-optional-sync-query-caller.js.map +1 -1
  57. package/esm/development/queries/use-query-caller.js +15 -3
  58. package/esm/development/queries/use-query-caller.js.map +1 -1
  59. package/esm/development/queries/use-query-handler.js +21 -5
  60. package/esm/development/queries/use-query-handler.js.map +1 -1
  61. package/esm/development/queries/use-sync-query-caller.js +15 -3
  62. package/esm/development/queries/use-sync-query-caller.js.map +1 -1
  63. package/esm/development/test-utils/with-container-provider.js +35 -0
  64. package/esm/development/test-utils/with-container-provider.js.map +1 -0
  65. package/esm/development/test-utils.js +1 -1
  66. package/esm/development/utils/shallow-equal-arrays.js +28 -0
  67. package/esm/development/utils/shallow-equal-arrays.js.map +1 -0
  68. package/esm/production/commands/use-command-caller.js +1 -1
  69. package/esm/production/commands/use-command-caller.js.map +1 -1
  70. package/esm/production/commands/use-command-handler.js +1 -1
  71. package/esm/production/commands/use-command-handler.js.map +1 -1
  72. package/esm/production/commands/use-optional-command-caller.js +1 -1
  73. package/esm/production/commands/use-optional-command-caller.js.map +1 -1
  74. package/esm/production/context/container-context.js +1 -0
  75. package/esm/production/context/container-context.js.map +1 -0
  76. package/esm/production/context/use-container.js +1 -0
  77. package/esm/production/context/use-container.js.map +1 -0
  78. package/esm/production/context/use-root-container.js +1 -0
  79. package/esm/production/context/use-root-container.js.map +1 -0
  80. package/esm/production/context/use-scope.js +1 -0
  81. package/esm/production/context/use-scope.js.map +1 -0
  82. package/esm/production/error/error-code.js +1 -1
  83. package/esm/production/error/error-code.js.map +1 -1
  84. package/esm/production/events/use-event-emitter.js +1 -1
  85. package/esm/production/events/use-event-emitter.js.map +1 -1
  86. package/esm/production/events/use-event.js +1 -1
  87. package/esm/production/events/use-event.js.map +1 -1
  88. package/esm/production/events/use-events-handler.js +1 -1
  89. package/esm/production/events/use-events-handler.js.map +1 -1
  90. package/esm/production/events/use-events.js +1 -1
  91. package/esm/production/events/use-events.js.map +1 -1
  92. package/esm/production/index.js +1 -1
  93. package/esm/production/injection/use-injection.js +1 -0
  94. package/esm/production/injection/use-injection.js.map +1 -0
  95. package/esm/production/injection/use-optional-injection.js +1 -0
  96. package/esm/production/injection/use-optional-injection.js.map +1 -0
  97. package/esm/production/provision/container-activator.js +1 -0
  98. package/esm/production/provision/container-activator.js.map +1 -0
  99. package/esm/production/provision/container-provider.js +1 -0
  100. package/esm/production/provision/container-provider.js.map +1 -0
  101. package/esm/production/provision/sub-container-provider.js +1 -0
  102. package/esm/production/provision/sub-container-provider.js.map +1 -0
  103. package/esm/production/provision/use-container-provision-state.js +1 -0
  104. package/esm/production/provision/use-container-provision-state.js.map +1 -0
  105. package/esm/production/queries/use-optional-query-caller.js +1 -1
  106. package/esm/production/queries/use-optional-query-caller.js.map +1 -1
  107. package/esm/production/queries/use-optional-sync-query-caller.js +1 -1
  108. package/esm/production/queries/use-optional-sync-query-caller.js.map +1 -1
  109. package/esm/production/queries/use-query-caller.js +1 -1
  110. package/esm/production/queries/use-query-caller.js.map +1 -1
  111. package/esm/production/queries/use-query-handler.js +1 -1
  112. package/esm/production/queries/use-query-handler.js.map +1 -1
  113. package/esm/production/queries/use-sync-query-caller.js +1 -1
  114. package/esm/production/queries/use-sync-query-caller.js.map +1 -1
  115. package/esm/production/test-utils/with-container-provider.js +1 -0
  116. package/esm/production/test-utils/with-container-provider.js.map +1 -0
  117. package/esm/production/test-utils.js +1 -1
  118. package/esm/production/utils/shallow-equal-arrays.js +1 -0
  119. package/esm/production/utils/shallow-equal-arrays.js.map +1 -0
  120. package/index.d.ts +554 -137
  121. package/package.json +2 -2
  122. package/test-utils.d.ts +19 -8
  123. package/esm/development/provision/create-injectables-provider.js +0 -101
  124. package/esm/development/provision/create-injectables-provider.js.map +0 -1
  125. package/esm/development/provision/ioc-context.js +0 -11
  126. package/esm/development/provision/ioc-context.js.map +0 -1
  127. package/esm/development/provision/ioc-provider.js +0 -50
  128. package/esm/development/provision/ioc-provider.js.map +0 -1
  129. package/esm/development/provision/use-container-revision.js +0 -13
  130. package/esm/development/provision/use-container-revision.js.map +0 -1
  131. package/esm/development/provision/use-container.js +0 -13
  132. package/esm/development/provision/use-container.js.map +0 -1
  133. package/esm/development/provision/use-injection.js +0 -22
  134. package/esm/development/provision/use-injection.js.map +0 -1
  135. package/esm/development/provision/use-ioc-context.js +0 -21
  136. package/esm/development/provision/use-ioc-context.js.map +0 -1
  137. package/esm/development/provision/use-optional-injection.js +0 -29
  138. package/esm/development/provision/use-optional-injection.js.map +0 -1
  139. package/esm/development/test-utils/with-ioc-provider.js +0 -24
  140. package/esm/development/test-utils/with-ioc-provider.js.map +0 -1
  141. package/esm/production/provision/create-injectables-provider.js +0 -1
  142. package/esm/production/provision/create-injectables-provider.js.map +0 -1
  143. package/esm/production/provision/ioc-context.js +0 -1
  144. package/esm/production/provision/ioc-context.js.map +0 -1
  145. package/esm/production/provision/ioc-provider.js +0 -1
  146. package/esm/production/provision/ioc-provider.js.map +0 -1
  147. package/esm/production/provision/use-container-revision.js +0 -1
  148. package/esm/production/provision/use-container-revision.js.map +0 -1
  149. package/esm/production/provision/use-container.js +0 -1
  150. package/esm/production/provision/use-container.js.map +0 -1
  151. package/esm/production/provision/use-injection.js +0 -1
  152. package/esm/production/provision/use-injection.js.map +0 -1
  153. package/esm/production/provision/use-ioc-context.js +0 -1
  154. package/esm/production/provision/use-ioc-context.js.map +0 -1
  155. package/esm/production/provision/use-optional-injection.js +0 -1
  156. package/esm/production/provision/use-optional-injection.js.map +0 -1
  157. package/esm/production/test-utils/with-ioc-provider.js +0 -1
  158. package/esm/production/test-utils/with-ioc-provider.js.map +0 -1
@@ -2,35 +2,53 @@
2
2
 
3
3
  var core = require('@wirestate/core');
4
4
  var react = require('react');
5
- var iocProvider = require('./lib.js');
5
+ var containerProvider = require('./lib.js');
6
+
7
+ var ERROR_CODE_INVALID_CONTEXT = 1052;
6
8
 
7
9
  /**
8
- * Returns the full IoC context.
10
+ * Returns the active container from the context.
11
+ *
12
+ * @remarks
13
+ * Use this hook when you need direct access to the {@link Container} for manual
14
+ * resolution or checking bindings. For typical service usage, prefer
15
+ * {@link useInjection}.
16
+ *
17
+ * @group Context
18
+ *
19
+ * @returns The active container.
9
20
  *
10
- * @returns active IoC context
11
- * @internal
21
+ * @example
22
+ * ```tsx
23
+ * const container: Container = useContainer();
24
+ * const isBound: boolean = container.isBound(MyToken);
25
+ * ```
12
26
  */
13
- function useIocContext() {
14
- var value = react.useContext(iocProvider.IocReactContext);
27
+ function useContainer() {
28
+ var value = react.useContext(containerProvider.ContainerReactContext);
15
29
  if (!value) {
16
- throw new core.WirestateError(iocProvider.ERROR_CODE_INVALID_CONTEXT, "Trying to access IOC context from React subtree not wrapped in <IocProvider>.");
30
+ throw new core.WirestateError(ERROR_CODE_INVALID_CONTEXT, "Trying to access container context from React subtree not wrapped in <ContainerProvider>.");
17
31
  }
18
32
  return value;
19
33
  }
20
34
 
21
35
  /**
22
- * Returns the active IoC container.
36
+ * Returns a stable function to dispatch commands on the active container.
23
37
  *
24
- * @returns active Inversify container
25
- */
26
- function useContainer() {
27
- return useIocContext().container;
28
- }
29
-
30
- /**
31
- * Returns a function to dispatch commands on the active container.
38
+ * @remarks
39
+ * The returned dispatcher is memoized using `useCallback` and stays stable
40
+ * for the lifetime of the container. It uses {@link CommandBus.command} internally.
41
+ *
42
+ * @group Commands
43
+ *
44
+ * @returns A command dispatcher function that takes a type and optional data.
32
45
  *
33
- * @returns command dispatcher
46
+ * @example
47
+ * ```tsx
48
+ * const call: CommandCaller = useCommandCaller();
49
+ *
50
+ * const onClick = () => call("SAVE_USER_COMMAND", { id: 1 });
51
+ * ```
34
52
  */
35
53
  function useCommandCaller() {
36
54
  var container = useContainer();
@@ -40,10 +58,26 @@ function useCommandCaller() {
40
58
  }
41
59
 
42
60
  /**
43
- * Returns a function to dispatch optional commands on the active container.
44
- * Returns null instead of throwing when no handler is registered.
61
+ * Returns a stable function to dispatch optional commands on the active container.
62
+ *
63
+ * @remarks
64
+ * Similar to {@link useCommandCaller}, but returns `null` instead of throwing
65
+ * {WirestateError} if no handler is registered for the command type.
66
+ * Uses {@link CommandBus.commandOptional} internally.
67
+ *
68
+ * @group Commands
69
+ *
70
+ * @returns An optional command dispatcher function.
45
71
  *
46
- * @returns optional command dispatcher
72
+ * @example
73
+ * ```tsx
74
+ * const callOptional: OptionalCommandCaller = useOptionalCommandCaller();
75
+ * const descriptor: CommandDescriptor<string> | null = callOptional("OPTIONAL_COMMAND", data);
76
+ *
77
+ * if (descriptor) {
78
+ * const result: string = await descriptor.task;
79
+ * }
80
+ * ```
47
81
  */
48
82
  function useOptionalCommandCaller() {
49
83
  var container = useContainer();
@@ -54,11 +88,27 @@ function useOptionalCommandCaller() {
54
88
 
55
89
  /**
56
90
  * Registers a command handler for the component's lifetime.
57
- * The handler is stored in a ref to avoid manual memoization.
91
+ *
92
+ * @remarks
93
+ * The handler is stored in a `useRef` and synced on every render to avoid stale
94
+ * closures without requiring manual memoization of the handler function.
58
95
  * Only one handler is active per type; newer registrations shadow older ones.
96
+ * The handler is automatically unregistered when the component unmounts.
97
+ *
98
+ * @group Commands
99
+ *
100
+ * @template R - Result type of the command.
101
+ * @template D - Data/payload type of the command.
59
102
  *
60
- * @param type - command type
61
- * @param handler - command handler function
103
+ * @param type - Command type (string or symbol).
104
+ * @param handler - Command handler function.
105
+ *
106
+ * @example
107
+ * ```tsx
108
+ * useCommandHandler("SAVE_COMMAND", (data) => {
109
+ * return api.save(data);
110
+ * });
111
+ * ```
62
112
  */
63
113
  function useCommandHandler(type, handler) {
64
114
  var container = useContainer();
@@ -75,107 +125,257 @@ function useCommandHandler(type, handler) {
75
125
  }
76
126
 
77
127
  /**
78
- * Returns a function to dispatch queries on the active container.
128
+ * Creates and memoizes a root container for a component.
129
+ *
130
+ * @remarks
131
+ * The `factory` function re-runs only when one of `deps` changes.
132
+ * Between such changes, the same container instance is returned.
133
+ *
134
+ * @group Context
135
+ *
136
+ * @param factory - Lazily creates the root container.
137
+ * @param deps - Dependency list controlling when container is recreated.
138
+ * @returns The memoized root container instance.
79
139
  *
80
- * @returns query dispatcher
140
+ * @example
141
+ * ```tsx
142
+ * const container: Container = useRootContainer(
143
+ * () =>
144
+ * createContainer({
145
+ * entries: [CounterService, LoggerService],
146
+ * }),
147
+ * []
148
+ * );
149
+ * ```
81
150
  */
82
- function useQueryCaller() {
83
- var container = useContainer();
84
- return react.useCallback(function (type, data) {
85
- return container.get(core.QueryBus).query(type, data);
86
- }, [container]);
151
+ function useRootContainer(factory, deps) {
152
+ return react.useMemo(function () {
153
+ var container = factory();
154
+ return container;
155
+ }, deps);
87
156
  }
88
157
 
89
158
  /**
90
- * Returns a function to dispatch optional queries on the active container.
91
- * Returns null instead of throwing when no handler is registered.
159
+ * Returns a {@link WireScope} instance bound to the active container.
160
+ *
161
+ * @remarks
162
+ * The scope is recreated if the container changes. It provides a convenient
163
+ * way to access container features like events, commands, and queries.
92
164
  *
93
- * @returns optional query dispatcher
165
+ * @group Context
166
+ *
167
+ * @returns A {@link WireScope} instance.
168
+ *
169
+ * @example
170
+ * ```tsx
171
+ * const scope: WireScope = useScope();
172
+ *
173
+ * scope.emitEvent("UI_READY");
174
+ * ```
94
175
  */
95
- function useOptionalQueryCaller() {
176
+ function useScope() {
96
177
  var container = useContainer();
97
- return react.useCallback(function (type, data) {
98
- return container.get(core.QueryBus).queryOptional(type, data);
178
+ return react.useMemo(function () {
179
+ return container.get(core.WireScope);
99
180
  }, [container]);
100
181
  }
101
182
 
102
183
  /**
103
- * Registers a query handler for the component's lifetime.
104
- * The handler is stored in a ref to avoid manual memoization.
105
- * Only one handler is active per type; newer registrations shadow older ones.
184
+ * Subscribes a component to a specific event type on the {@link EventBus}.
185
+ *
186
+ * @remarks
187
+ * The subscription is active for the component's lifetime and is automatically
188
+ * cleaned up on unmount. The handler is synced via `useRef` to avoid stale
189
+ * closures without requiring manual memoization of the handler function.
190
+ *
191
+ * @group Events
192
+ *
193
+ * @param type - Event type to listen for.
194
+ * @param handler - Function invoked when the specified event is emitted.
106
195
  *
107
- * @param type - query type
108
- * @param handler - query handler function
196
+ * @example
197
+ * ```tsx
198
+ * useEvent("USER_LOGGED_IN", (event) => {
199
+ * console.log("User logged in:,", event);
200
+ * });
201
+ * ```
109
202
  */
110
- function useQueryHandler(type, handler) {
111
- var container = useContainer();
203
+ function useEvent(type, handler) {
204
+ var typeRef = react.useRef(type);
112
205
  var handlerRef = react.useRef(handler);
206
+ var container = useContainer();
113
207
  react.useEffect(function () {
208
+ typeRef.current = type;
114
209
  handlerRef.current = handler;
115
210
  });
116
211
  react.useEffect(function () {
117
- return container.get(core.QueryBus).register(type, function (data) {
118
- return handlerRef.current(data);
212
+ return container.get(core.EventBus).subscribe(function (event) {
213
+ var _a;
214
+ if (event.type === typeRef.current) {
215
+ (_a = handlerRef.current) === null || _a === void 0 ? void 0 : _a.call(handlerRef, event);
216
+ }
119
217
  });
120
218
  }, [container, type]);
121
219
  }
122
220
 
123
221
  /**
124
- * Returns a stable function to dispatch synchronous queries.
125
- * Returns the value directly from the handler.
222
+ * Subscribes a component to multiple event types on the {@link EventBus}.
223
+ *
224
+ * @remarks
225
+ * Similar to {@link useEvent}, but allows listening for a collection of event
226
+ * types using a single handler.
227
+ * The handler and type list are synced via `useRef` to avoid stale closures.
126
228
  *
127
- * @returns sync query dispatcher
229
+ * @group Events
230
+ *
231
+ * @param types - Array of event types (strings or symbols) to filter by.
232
+ * @param handler - Function invoked when any of the specified events are emitted.
233
+ *
234
+ * @example
235
+ * ```tsx
236
+ * useEvents(["USER_UPDATED", "USER_DELETED"], (event) => {
237
+ * refreshList();
238
+ * });
239
+ * ```
128
240
  */
129
- function useSyncQueryCaller() {
241
+ function useEvents(types, handler) {
242
+ var typesRef = react.useRef(types);
243
+ var handlerRef = react.useRef(handler);
130
244
  var container = useContainer();
131
- return react.useCallback(function (type, data) {
132
- // Access the container-scoped QueryBus and execute the query.
133
- return container.get(core.QueryBus).query(type, data);
245
+ react.useEffect(function () {
246
+ typesRef.current = types;
247
+ handlerRef.current = handler;
248
+ });
249
+ react.useEffect(function () {
250
+ return container.get(core.EventBus).subscribe(function (event) {
251
+ var _a;
252
+ if (typesRef.current.includes(event.type)) {
253
+ (_a = handlerRef.current) === null || _a === void 0 ? void 0 : _a.call(handlerRef, event);
254
+ }
255
+ });
134
256
  }, [container]);
135
257
  }
136
258
 
137
259
  /**
138
- * Returns a stable function to dispatch synchronous optional queries.
139
- * Returns null instead of throwing when no handler is registered.
260
+ * Subscribes a component to all events on the {@link EventBus} without type filtering.
261
+ *
262
+ * @remarks
263
+ * Useful for logging, debugging, or cross-cutting concerns that need to see
264
+ * every event passing through the bus.
265
+ * The handler is synced via `useRef` to avoid stale closures.
266
+ * The subscription is automatically cleaned up on unmount.
140
267
  *
141
- * @returns optional sync query dispatcher
268
+ * @group Events
269
+ *
270
+ * @param handler - Event handler invoked for every emitted event.
271
+ *
272
+ * @example
273
+ * ```tsx
274
+ * useEventsHandler((event) => {
275
+ * console.log('Event receieved:', event.type, event.payload);
276
+ * });
277
+ * ```
142
278
  */
143
- function useOptionalSyncQueryCaller() {
279
+ function useEventsHandler(handler) {
280
+ var handlerRef = react.useRef(handler);
144
281
  var container = useContainer();
145
- return react.useCallback(function (type, data) {
146
- return container.get(core.QueryBus).queryOptional(type, data);
282
+ react.useEffect(function () {
283
+ handlerRef.current = handler;
284
+ });
285
+ react.useEffect(function () {
286
+ return container.get(core.EventBus).subscribe(function (event) {
287
+ var _a;
288
+ (_a = handlerRef.current) === null || _a === void 0 ? void 0 : _a.call(handlerRef, event);
289
+ });
290
+ }, [container]);
291
+ }
292
+
293
+ /**
294
+ * Returns a stable function to emit events via the {@link EventBus}.
295
+ *
296
+ * @remarks
297
+ * The returned emitter is memoized using `useCallback` and stays stable
298
+ * for the lifetime of the container.
299
+ *
300
+ * @group Events
301
+ *
302
+ * @template P - Default payload type for emitted events.
303
+ * @template T - Default event identifier type.
304
+ *
305
+ * @returns An event emitter function.
306
+ *
307
+ * @example
308
+ * ```tsx
309
+ * const emit: EventEmitter = useEventEmitter();
310
+ *
311
+ * const onClick = () => emit("BUTTON_CLICKED", { id: "submit" });
312
+ * ```
313
+ */
314
+ function useEventEmitter() {
315
+ var container = useContainer();
316
+ return react.useCallback(function (type, payload, from) {
317
+ container.get(core.EventBus).emit({
318
+ type: type,
319
+ payload: payload,
320
+ from: from
321
+ });
147
322
  }, [container]);
148
323
  }
149
324
 
150
325
  /**
151
- * Resolves a value from the container - constant or service.
152
- * Automatically re-resolves if the container is reset or services are rebound.
326
+ * Resolves a service or constant from the active container.
153
327
  *
154
- * @param injectionId - injection identifier
155
- * @returns resolved value
328
+ * @remarks
329
+ * This hook automatically re-resolves the dependency if the container's
330
+ * revision changes (e.g., due to re-binding in a provider).
331
+ *
332
+ * @group Injection
333
+ *
334
+ * @template T - The type of the value being resolved.
335
+ *
336
+ * @param injectionId - The service identifier (string, symbol, or constructor).
337
+ *
338
+ * @returns The resolved instance or value.
339
+ *
340
+ * @throws {WirestateError} If the container is not found in context.
341
+ * @throws {Error} If Inversify fails to resolve the identifier.
342
+ *
343
+ * @example
344
+ * ```tsx
345
+ * const api: ApiService = useInjection(ApiService);
346
+ * ```
156
347
  */
157
348
  function useInjection(injectionId) {
158
- var _a = useIocContext(),
159
- container = _a.container,
160
- revision = _a.revision;
349
+ var container = useContainer();
161
350
  // Revision bump causes a container reset; force re-resolution to drop stale instances.
162
351
  return react.useMemo(function () {
163
352
  return container.get(injectionId);
164
- }, [container, revision, injectionId]);
353
+ }, [container, injectionId]);
165
354
  }
166
355
 
167
356
  /**
168
- * Resolves a value from the container if bound, returning null otherwise.
169
- * Unlike {@link useInjection}, this hook does not throw when the token is not bound.
357
+ * Safely resolves a value from the container, returning a fallback or null if not bound.
170
358
  *
171
- * @param injectionId - injection identifier
172
- * @param onFallback - optional callback to handle cases when dependency was not resolved
173
- * @returns resolved value, result of optional fallback handler or null
359
+ * @remarks
360
+ * Unlike {@link useInjection}, this hook does not throw if the dependency
361
+ * is missing from the container.
362
+ *
363
+ * @group Injection
364
+ *
365
+ * @template T - The type of the value being resolved.
366
+ *
367
+ * @param injectionId - The service identifier (string, symbol, or constructor).
368
+ * @param onFallback - Optional function called to provide a value if the token is not bound.
369
+ *
370
+ * @returns The resolved value, the result of the fallback function, or `null`.
371
+ *
372
+ * @example
373
+ * ```tsx
374
+ * const logger = useOptionalInjection(FileLogger, (container) => container.get(ConsoleLoggerService);
375
+ * ```
174
376
  */
175
377
  function useOptionalInjection(injectionId, onFallback) {
176
- var _a = useIocContext(),
177
- container = _a.container,
178
- revision = _a.revision;
378
+ var container = useContainer();
179
379
  // Revision bump forces a container reset; force re-resolution to drop stale instances.
180
380
  return react.useMemo(function () {
181
381
  if (container.isBound(injectionId)) {
@@ -185,201 +385,239 @@ function useOptionalInjection(injectionId, onFallback) {
185
385
  } else {
186
386
  return null;
187
387
  }
188
- }, [container, revision, injectionId]);
388
+ }, [container, injectionId]);
189
389
  }
190
390
 
191
391
  /**
192
- * Creates a component that manages injectable lifetimes for its subtree.
392
+ * Provides a child container derived from the nearest parent container.
393
+ *
394
+ * @remarks
395
+ * The provider owns the child container. It disposes the previous child before
396
+ * exposing a replacement, recreates on parent or `entries` changes, and revives
397
+ * a cleaned child after React development remount cleanup.
398
+ *
399
+ * @group Provision
193
400
  *
194
- * @param entries - service classes or injectable descriptors to bind
195
- * @param options - provider configuration
196
- * @returns injectables provider component
401
+ * @param props - Provider props.
402
+ * @returns A React context provider for the child container.
197
403
  */
198
- function createInjectablesProvider(entries, options) {
199
- if (options === void 0) {
200
- options = {};
201
- }
202
- var activate = options.activate;
203
- if (activate && activate.length > 0) {
204
- var entryTokens = entries.map(core.getEntryToken);
205
- for (var _i = 0, activate_1 = activate; _i < activate_1.length; _i++) {
206
- var eager = activate_1[_i];
207
- if (!entryTokens.includes(eager)) {
208
- throw new core.WirestateError(iocProvider.ERROR_CODE_VALIDATION_ERROR, "createInjectablesProvider: '".concat(String(eager), "' is listed in 'activate' but was not provided in 'entries'."));
209
- }
210
- }
211
- }
212
- function InjectablesProviderComponent(props) {
213
- var iocContext = react.useContext(iocProvider.IocReactContext);
214
- if (!iocContext) {
215
- throw new core.WirestateError(iocProvider.ERROR_CODE_INVALID_CONTEXT, "<InjectablesProvider> must be rendered inside an <IocProvider> React subtree.");
404
+ function SubContainerProvider(props) {
405
+ var _a;
406
+ var parent = useContainer();
407
+ var source = {
408
+ entries: props.entries,
409
+ parent: parent,
410
+ seeds: props.seeds
411
+ };
412
+ var state = containerProvider.useContainerProvisionState(source, {
413
+ create: createSubContainerState,
414
+ label: "SubContainerProvider",
415
+ reuse: canReuseSubContainerState
416
+ });
417
+ return react.createElement(containerProvider.ContainerReactContext.Provider, {
418
+ value: state.container
419
+ }, (_a = props.children) !== null && _a !== void 0 ? _a : null);
420
+ }
421
+ /**
422
+ * Selects the child-container state that should be exposed for this render.
423
+ *
424
+ * @param current - Previously exposed state, if any.
425
+ * @param source - Current provider source.
426
+ * @param disposed - Child containers already disposed by this provider.
427
+ * @returns Existing or replacement child-container state.
428
+ */
429
+ function canReuseSubContainerState(current, source, disposed) {
430
+ return !disposed.has(current.container) && current.source.parent === source.parent && containerProvider.shallowEqualArrays(source.entries, current.source.entries);
431
+ }
432
+ /**
433
+ * Creates a child container, applies seeds, and binds entries.
434
+ *
435
+ * @param source - Parent container plus child bindings.
436
+ * @returns Child-container state ready for context.
437
+ */
438
+ function createSubContainerState(source) {
439
+ var container = core.createContainer({
440
+ entries: source.entries,
441
+ parent: source.parent,
442
+ seeds: source.seeds
443
+ });
444
+ return {
445
+ source: source,
446
+ container: container,
447
+ owned: true
448
+ };
449
+ }
450
+
451
+ /**
452
+ * Resolves specified services from the current IoC container before rendering children.
453
+ *
454
+ * @remarks
455
+ * Activation runs once per container instance.
456
+ * On rerender with the same container, services are not resolved again.
457
+ *
458
+ * @group Provision
459
+ *
460
+ * @param props - Component properties.
461
+ * @param props.activate - Services to resolve eagerly from container.
462
+ * @param props.children - React children element.
463
+ * @returns React children after activation side effect is applied.
464
+ */
465
+ function ContainerActivator(props) {
466
+ var _a;
467
+ var container = useContainer();
468
+ var activatedContainerRef = react.useRef(null);
469
+ if (activatedContainerRef.current !== container) {
470
+ activatedContainerRef.current = container;
471
+ for (var _i = 0, _b = props.activate; _i < _b.length; _i++) {
472
+ var entry = _b[_i];
473
+ container.get(entry);
216
474
  }
217
- // Snapshot props on mount to ensure binding stability.
218
- // useState lazy initializer ensures it only runs once.
219
- var initialPropsSnapshot = react.useState(function () {
220
- return props;
221
- })[0];
222
- react.useMemo(function () {
223
- // Seed must be applied BEFORE binding so @Inject(INITIAL_STATE_TOKEN) works during activation.
224
- if (initialPropsSnapshot.seeds) {
225
- core.applySeeds(iocContext.container, initialPropsSnapshot.seeds);
226
- }
227
- for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) {
228
- var entry = entries_1[_i];
229
- if (!iocContext.container.isBound(core.getEntryToken(entry))) {
230
- core.bindEntry(iocContext.container, entry);
231
- }
232
- }
233
- if (activate) {
234
- for (var _a = 0, activate_2 = activate; _a < activate_2.length; _a++) {
235
- var eager = activate_2[_a];
236
- iocContext.container.get(eager);
237
- }
238
- }
239
- }, entries);
240
- react.useEffect(function () {
241
- // Re-apply state and re-bind if container was reset (e.g. StrictMode remount or HMR).
242
- var didRebind = false;
243
- if (initialPropsSnapshot.seeds) {
244
- core.applySeeds(iocContext.container, initialPropsSnapshot.seeds);
245
- }
246
- for (var _i = 0, entries_2 = entries; _i < entries_2.length; _i++) {
247
- var entry = entries_2[_i];
248
- if (!iocContext.container.isBound(core.getEntryToken(entry))) {
249
- didRebind = true;
250
- core.bindEntry(iocContext.container, entry);
251
- }
252
- }
253
- if (activate) {
254
- for (var _a = 0, activate_3 = activate; _a < activate_3.length; _a++) {
255
- var eager = activate_3[_a];
256
- iocContext.container.get(eager);
257
- }
258
- }
259
- // Increment revision to invalidate stale injection caches.
260
- if (didRebind) {
261
- iocContext.setRevision(function (r) {
262
- return r + 1;
263
- });
264
- }
265
- return function () {
266
- for (var _i = 0, entries_3 = entries; _i < entries_3.length; _i++) {
267
- var entry = entries_3[_i];
268
- var token = core.getEntryToken(entry);
269
- if (iocContext.container.isBound(token)) {
270
- iocContext.container.unbind(token);
271
- }
272
- }
273
- // Remove only this provider's targeted initial state entries.
274
- if (initialPropsSnapshot.seeds) {
275
- core.unapplySeeds(iocContext.container, initialPropsSnapshot.seeds);
276
- }
277
- };
278
- }, entries);
279
- return props.children;
280
475
  }
281
- InjectablesProviderComponent.displayName = "InjectablesProvider";
282
- return InjectablesProviderComponent;
476
+ return (_a = props.children) !== null && _a !== void 0 ? _a : null;
283
477
  }
284
478
 
285
479
  /**
286
- * Returns the current container revision.
480
+ * Returns a stable function to dispatch queries on the active container.
481
+ *
482
+ * @remarks
483
+ * The returned dispatcher is memoized using `useCallback` and stays stable
484
+ * for the lifetime of the container. It uses {@link QueryBus.query} internally.
287
485
  *
288
- * @returns revision number
486
+ * @group Queries
487
+ *
488
+ * @returns A query dispatcher function.
489
+ *
490
+ * @example
491
+ * ```tsx
492
+ * const query: QueryCaller = useQueryCaller();
493
+ * const result: UserProfile = await query(GET_USER_PROFILE, { id: 123 });
494
+ * ```
289
495
  */
290
- function useContainerRevision() {
291
- return useIocContext().revision;
496
+ function useQueryCaller() {
497
+ var container = useContainer();
498
+ return react.useCallback(function (type, data) {
499
+ return container.get(core.QueryBus).query(type, data);
500
+ }, [container]);
292
501
  }
293
502
 
294
503
  /**
295
- * Subscribes a component to events.
504
+ * Returns a stable function to dispatch optional queries on the active container.
296
505
  *
297
- * @param type - event type to listen to
298
- * @param handler - event handler to invoke when event is emitted
506
+ * @remarks
507
+ * The returned dispatcher is memoized using `useCallback` and stays stable
508
+ * for the lifetime of the container. It returns `null` instead of throwing
509
+ * if no handler is registered.
510
+ *
511
+ * @group Queries
512
+ *
513
+ * @returns An optional query dispatcher function.
514
+ *
515
+ * @example
516
+ * ```tsx
517
+ * const queryOptional: OptionalQueryCaller = useOptionalQueryCaller();
518
+ * const settings: UserSettings | null = await queryOptional(GET_USER_SETTINGS, { id: 1 });
519
+ * ```
299
520
  */
300
- function useEvent(type, handler) {
301
- var typeRef = react.useRef(type);
302
- var handlerRef = react.useRef(handler);
521
+ function useOptionalQueryCaller() {
303
522
  var container = useContainer();
304
- react.useEffect(function () {
305
- typeRef.current = type;
306
- handlerRef.current = handler;
307
- });
308
- react.useEffect(function () {
309
- return container.get(core.EventBus).subscribe(function (event) {
310
- var _a;
311
- if (event.type === typeRef.current) {
312
- (_a = handlerRef.current) === null || _a === void 0 ? void 0 : _a.call(handlerRef, event);
313
- }
314
- });
315
- }, [container, type]);
523
+ return react.useCallback(function (type, data) {
524
+ return container.get(core.QueryBus).queryOptional(type, data);
525
+ }, [container]);
316
526
  }
317
527
 
318
528
  /**
319
- * Subscribes a component to multiple event types.
529
+ * Registers a query handler for the component's lifetime.
320
530
  *
321
- * @param types - event types to filter by
322
- * @param handler - events handler
531
+ * @remarks
532
+ * The handler is stored in a `useRef` and synced on every render to avoid stale
533
+ * closures. Only one handler is active per type; newer registrations shadow older ones.
534
+ * The handler is automatically unregistered when the component unmounts.
535
+ *
536
+ * @group Queries
537
+ *
538
+ * @template R - Result type of the query.
539
+ * @template D - Data/payload type of the query.
540
+ * @template T - Query identifier type.
541
+ *
542
+ * @param type - Query identifier (string or symbol).
543
+ * @param handler - Function that responds to the query.
544
+ *
545
+ * @example
546
+ * ```tsx
547
+ * useQueryHandler("GET_DATA", (data) => {
548
+ * return { id: data.id, value: "Resolved" };
549
+ * });
550
+ * ```
323
551
  */
324
- function useEvents(types, handler) {
325
- var typesRef = react.useRef(types);
326
- var handlerRef = react.useRef(handler);
552
+ function useQueryHandler(type, handler) {
327
553
  var container = useContainer();
554
+ var handlerRef = react.useRef(handler);
328
555
  react.useEffect(function () {
329
- typesRef.current = types;
330
556
  handlerRef.current = handler;
331
557
  });
332
558
  react.useEffect(function () {
333
- return container.get(core.EventBus).subscribe(function (event) {
334
- var _a;
335
- if (typesRef.current.includes(event.type)) {
336
- (_a = handlerRef.current) === null || _a === void 0 ? void 0 : _a.call(handlerRef, event);
337
- }
559
+ return container.get(core.QueryBus).register(type, function (data) {
560
+ return handlerRef.current(data);
338
561
  });
339
- }, [container]);
562
+ }, [container, type]);
340
563
  }
341
564
 
342
565
  /**
343
- * Subscribes a component to all events without type filtering.
566
+ * Returns a stable function to dispatch synchronous queries.
567
+ *
568
+ * @remarks
569
+ * The returned dispatcher returns the value directly from the handler
570
+ * instead of a Promise (unless the handler itself returns a Promise).
571
+ * Memoized using `useCallback`.
572
+ *
573
+ * @group Queries
344
574
  *
345
- * @param handler - event handler invoked for every emitted event
575
+ * @returns A synchronous query dispatcher function.
576
+ *
577
+ * @example
578
+ * ```tsx
579
+ * const querySync: SyncQueryCaller = useSyncQueryCaller();
580
+ * const config: ApplicationConfig = querySync("GET_APP_CONFIG");
581
+ * ```
346
582
  */
347
- function useEventsHandler(handler) {
348
- var handlerRef = react.useRef(handler);
583
+ function useSyncQueryCaller() {
349
584
  var container = useContainer();
350
- react.useEffect(function () {
351
- handlerRef.current = handler;
352
- });
353
- react.useEffect(function () {
354
- return container.get(core.EventBus).subscribe(function (event) {
355
- var _a;
356
- (_a = handlerRef.current) === null || _a === void 0 ? void 0 : _a.call(handlerRef, event);
357
- });
585
+ return react.useCallback(function (type, data) {
586
+ // Access the container-scoped QueryBus and execute the query.
587
+ return container.get(core.QueryBus).query(type, data);
358
588
  }, [container]);
359
589
  }
360
590
 
361
591
  /**
362
- * Returns a stable function to emit events.
592
+ * Returns a stable function to dispatch synchronous optional queries.
593
+ *
594
+ * @remarks
595
+ * Similar to {@link useOptionalQueryCaller}, but returns the value directly
596
+ * (synchronously) from the handler. Returns `null` if no handler is registered.
597
+ *
598
+ * @group Queries
599
+ *
600
+ * @returns An optional synchronous query dispatcher function.
363
601
  *
364
- * @returns event emitter
602
+ * @example
603
+ * ```tsx
604
+ * const querySyncOptional: OptionalSyncQueryCaller = useOptionalSyncQueryCaller();
605
+ * const value: ThemePreference | null = querySyncOptional(GET_THEME_PREFERENCE);
606
+ * ```
365
607
  */
366
- function useEventEmitter() {
367
- var container = useIocContext().container;
368
- return react.useCallback(function (type, payload, from) {
369
- container.get(core.EventBus).emit({
370
- type: type,
371
- payload: payload,
372
- from: from
373
- });
608
+ function useOptionalSyncQueryCaller() {
609
+ var container = useContainer();
610
+ return react.useCallback(function (type, data) {
611
+ return container.get(core.QueryBus).queryOptional(type, data);
374
612
  }, [container]);
375
613
  }
376
614
 
377
- exports.IocProvider = iocProvider.IocProvider;
378
- exports.createInjectablesProvider = createInjectablesProvider;
615
+ exports.ContainerProvider = containerProvider.ContainerProvider;
616
+ exports.ContainerActivator = ContainerActivator;
617
+ exports.SubContainerProvider = SubContainerProvider;
379
618
  exports.useCommandCaller = useCommandCaller;
380
619
  exports.useCommandHandler = useCommandHandler;
381
620
  exports.useContainer = useContainer;
382
- exports.useContainerRevision = useContainerRevision;
383
621
  exports.useEvent = useEvent;
384
622
  exports.useEventEmitter = useEventEmitter;
385
623
  exports.useEvents = useEvents;
@@ -391,5 +629,7 @@ exports.useOptionalQueryCaller = useOptionalQueryCaller;
391
629
  exports.useOptionalSyncQueryCaller = useOptionalSyncQueryCaller;
392
630
  exports.useQueryCaller = useQueryCaller;
393
631
  exports.useQueryHandler = useQueryHandler;
632
+ exports.useRootContainer = useRootContainer;
633
+ exports.useScope = useScope;
394
634
  exports.useSyncQueryCaller = useSyncQueryCaller;
395
635
  //# sourceMappingURL=index.js.map