@blac/core 2.0.19 → 2.0.21

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 (236) hide show
  1. package/README.md +19 -29
  2. package/dist/{StateContainerRegistry-CSNm2Ws5.cjs → StateContainerRegistry-141uHhtF.cjs} +300 -397
  3. package/dist/StateContainerRegistry-141uHhtF.cjs.map +1 -0
  4. package/dist/{StateContainerRegistry-CvBOhAT2.js → StateContainerRegistry-vWgsD-RP.js} +277 -392
  5. package/dist/StateContainerRegistry-vWgsD-RP.js.map +1 -0
  6. package/dist/config.d.cts +2 -2
  7. package/dist/config.d.ts +2 -2
  8. package/dist/constants.d.cts +11 -20
  9. package/dist/constants.d.ts +11 -20
  10. package/dist/core/Cubit.d.ts +14 -10
  11. package/dist/core/StateContainer.d.ts +61 -4
  12. package/dist/core/StateContainerRegistry.d.ts +119 -16
  13. package/dist/core/meta.d.ts +5 -11
  14. package/dist/core/symbols.d.ts +24 -1
  15. package/dist/decorators/blac.d.ts +28 -26
  16. package/dist/{ensure-CQimeD0I.cjs → ensure-ORVw-WRE.cjs} +234 -40
  17. package/dist/ensure-ORVw-WRE.cjs.map +1 -0
  18. package/dist/{ensure-DTTTd2h8.js → ensure-k-6q7O25.js} +203 -39
  19. package/dist/ensure-k-6q7O25.js.map +1 -0
  20. package/dist/index.cjs +213 -32
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +6 -8
  23. package/dist/index.d.ts +6 -8
  24. package/dist/index.js +192 -12
  25. package/dist/index.js.map +1 -1
  26. package/dist/plugin/BlacPlugin.d.ts +14 -0
  27. package/dist/plugin/PluginManager.d.ts +23 -7
  28. package/dist/plugins.cjs +400 -3
  29. package/dist/plugins.cjs.map +1 -0
  30. package/dist/plugins.d.cts +6 -2
  31. package/dist/plugins.d.ts +6 -2
  32. package/dist/plugins.js +398 -1
  33. package/dist/plugins.js.map +1 -0
  34. package/dist/registry/acquire.d.ts +2 -2
  35. package/dist/registry/config.d.ts +9 -0
  36. package/dist/registry/management.d.ts +3 -0
  37. package/dist/registry/queries.d.ts +15 -0
  38. package/dist/registry/release.d.ts +3 -3
  39. package/dist/testing.cjs +13 -15
  40. package/dist/testing.cjs.map +1 -1
  41. package/dist/testing.d.cts +1 -1
  42. package/dist/testing.d.ts +1 -1
  43. package/dist/testing.js +2 -4
  44. package/dist/testing.js.map +1 -1
  45. package/dist/types/branded.d.ts +3 -3
  46. package/dist/types/utilities.d.ts +45 -18
  47. package/dist/utils/idGenerator.d.ts +0 -36
  48. package/dist/utils/static-props.d.ts +25 -1
  49. package/dist/utils/structural-key.d.ts +0 -7
  50. package/dist/watch/index.d.ts +1 -1
  51. package/dist/watch/watch.d.ts +22 -3
  52. package/package.json +17 -55
  53. package/dist/StateContainerRegistry-4Ljd9EJx.js +0 -1018
  54. package/dist/StateContainerRegistry-4Ljd9EJx.js.map +0 -1
  55. package/dist/StateContainerRegistry-B1GYx9Xo.js +0 -975
  56. package/dist/StateContainerRegistry-B1GYx9Xo.js.map +0 -1
  57. package/dist/StateContainerRegistry-B_ViBLvJ.js +0 -975
  58. package/dist/StateContainerRegistry-B_ViBLvJ.js.map +0 -1
  59. package/dist/StateContainerRegistry-C0dCNlTF.js +0 -1005
  60. package/dist/StateContainerRegistry-C0dCNlTF.js.map +0 -1
  61. package/dist/StateContainerRegistry-CEWpc1bj.cjs +0 -1113
  62. package/dist/StateContainerRegistry-CEWpc1bj.cjs.map +0 -1
  63. package/dist/StateContainerRegistry-CSNm2Ws5.cjs.map +0 -1
  64. package/dist/StateContainerRegistry-CchRjnjX.cjs +0 -1094
  65. package/dist/StateContainerRegistry-CchRjnjX.cjs.map +0 -1
  66. package/dist/StateContainerRegistry-CvBOhAT2.js.map +0 -1
  67. package/dist/StateContainerRegistry-D9BIRjwv.cjs +0 -1088
  68. package/dist/StateContainerRegistry-D9BIRjwv.cjs.map +0 -1
  69. package/dist/StateContainerRegistry-DJQqLrli.cjs +0 -1046
  70. package/dist/StateContainerRegistry-DJQqLrli.cjs.map +0 -1
  71. package/dist/StateContainerRegistry-DVIBTxGZ.js +0 -999
  72. package/dist/StateContainerRegistry-DVIBTxGZ.js.map +0 -1
  73. package/dist/StateContainerRegistry-Dj77cok9.js +0 -1005
  74. package/dist/StateContainerRegistry-Dj77cok9.js.map +0 -1
  75. package/dist/StateContainerRegistry-FWKzPfS-.cjs +0 -1046
  76. package/dist/StateContainerRegistry-FWKzPfS-.cjs.map +0 -1
  77. package/dist/StateContainerRegistry-lIwo-0m3.cjs +0 -1094
  78. package/dist/StateContainerRegistry-lIwo-0m3.cjs.map +0 -1
  79. package/dist/acquire-B1Wuqla6.cjs +0 -46
  80. package/dist/acquire-B1Wuqla6.cjs.map +0 -1
  81. package/dist/acquire-BP-ZfUH2.js +0 -35
  82. package/dist/acquire-BP-ZfUH2.js.map +0 -1
  83. package/dist/acquire-BRIJVJaA.js +0 -35
  84. package/dist/acquire-BRIJVJaA.js.map +0 -1
  85. package/dist/acquire-BZIs6tpN.cjs +0 -46
  86. package/dist/acquire-BZIs6tpN.cjs.map +0 -1
  87. package/dist/acquire-C3nOb3XR.cjs +0 -46
  88. package/dist/acquire-C3nOb3XR.cjs.map +0 -1
  89. package/dist/acquire-C4WAS13G.cjs +0 -46
  90. package/dist/acquire-C4WAS13G.cjs.map +0 -1
  91. package/dist/acquire-C7beGI55.cjs +0 -46
  92. package/dist/acquire-C7beGI55.cjs.map +0 -1
  93. package/dist/acquire-CbMz7gWs.cjs +0 -46
  94. package/dist/acquire-CbMz7gWs.cjs.map +0 -1
  95. package/dist/acquire-CwB083au.js +0 -35
  96. package/dist/acquire-CwB083au.js.map +0 -1
  97. package/dist/acquire-DHEOEVcj.cjs +0 -46
  98. package/dist/acquire-DHEOEVcj.cjs.map +0 -1
  99. package/dist/acquire-DXEQDZGr.js +0 -35
  100. package/dist/acquire-DXEQDZGr.js.map +0 -1
  101. package/dist/acquire-Dg-uXwjF.js +0 -35
  102. package/dist/acquire-Dg-uXwjF.js.map +0 -1
  103. package/dist/acquire-Don7499S.js +0 -35
  104. package/dist/acquire-Don7499S.js.map +0 -1
  105. package/dist/acquire-G8aB3GAx.js +0 -35
  106. package/dist/acquire-G8aB3GAx.js.map +0 -1
  107. package/dist/branded-BybA0_xw.cjs +0 -18
  108. package/dist/branded-BybA0_xw.cjs.map +0 -1
  109. package/dist/branded-DkbUzncc.js +0 -13
  110. package/dist/branded-DkbUzncc.js.map +0 -1
  111. package/dist/config-8pyOC5gv.js +0 -16
  112. package/dist/config-8pyOC5gv.js.map +0 -1
  113. package/dist/config-B1l8ZGlU.cjs +0 -32
  114. package/dist/config-B1l8ZGlU.cjs.map +0 -1
  115. package/dist/config-C7Ktkjh2.cjs +0 -32
  116. package/dist/config-C7Ktkjh2.cjs.map +0 -1
  117. package/dist/config-COT4Xkbc.js +0 -16
  118. package/dist/config-COT4Xkbc.js.map +0 -1
  119. package/dist/config-CzcoAvBo.js +0 -16
  120. package/dist/config-CzcoAvBo.js.map +0 -1
  121. package/dist/config-D6BoWWPv.js +0 -16
  122. package/dist/config-D6BoWWPv.js.map +0 -1
  123. package/dist/config-DVM_ESQt.js +0 -16
  124. package/dist/config-DVM_ESQt.js.map +0 -1
  125. package/dist/config-DVMnlHLv.cjs +0 -32
  126. package/dist/config-DVMnlHLv.cjs.map +0 -1
  127. package/dist/config-Dk1FxfXj.js +0 -16
  128. package/dist/config-Dk1FxfXj.js.map +0 -1
  129. package/dist/config-b2z51Y_C.cjs +0 -32
  130. package/dist/config-b2z51Y_C.cjs.map +0 -1
  131. package/dist/config-buwpT7l3.js +0 -16
  132. package/dist/config-buwpT7l3.js.map +0 -1
  133. package/dist/config-d9_U58ef.cjs +0 -32
  134. package/dist/config-d9_U58ef.cjs.map +0 -1
  135. package/dist/config-hWTayX76.cjs +0 -32
  136. package/dist/config-hWTayX76.cjs.map +0 -1
  137. package/dist/config-sCPKPYxZ.cjs +0 -32
  138. package/dist/config-sCPKPYxZ.cjs.map +0 -1
  139. package/dist/debug.cjs +0 -14
  140. package/dist/debug.d.cts +0 -34
  141. package/dist/debug.d.ts +0 -34
  142. package/dist/debug.js +0 -4
  143. package/dist/ensure-B1dEDsZV.cjs +0 -559
  144. package/dist/ensure-B1dEDsZV.cjs.map +0 -1
  145. package/dist/ensure-B9oOqpOB.cjs +0 -558
  146. package/dist/ensure-B9oOqpOB.cjs.map +0 -1
  147. package/dist/ensure-BfYnvAsI.js +0 -709
  148. package/dist/ensure-BfYnvAsI.js.map +0 -1
  149. package/dist/ensure-Bm91o37h.js +0 -536
  150. package/dist/ensure-Bm91o37h.js.map +0 -1
  151. package/dist/ensure-C1a8IFWy.js +0 -535
  152. package/dist/ensure-C1a8IFWy.js.map +0 -1
  153. package/dist/ensure-CLQEx0Nn.cjs +0 -528
  154. package/dist/ensure-CLQEx0Nn.cjs.map +0 -1
  155. package/dist/ensure-CPMiGvov.js +0 -493
  156. package/dist/ensure-CPMiGvov.js.map +0 -1
  157. package/dist/ensure-CQimeD0I.cjs.map +0 -1
  158. package/dist/ensure-DEXSmA-N.js +0 -526
  159. package/dist/ensure-DEXSmA-N.js.map +0 -1
  160. package/dist/ensure-DTTTd2h8.js.map +0 -1
  161. package/dist/ensure-Dx86f80B.cjs +0 -567
  162. package/dist/ensure-Dx86f80B.cjs.map +0 -1
  163. package/dist/ensure-DzqGej43.js +0 -526
  164. package/dist/ensure-DzqGej43.js.map +0 -1
  165. package/dist/ensure-VuiNPccZ.cjs +0 -559
  166. package/dist/ensure-VuiNPccZ.cjs.map +0 -1
  167. package/dist/ensure-_Oh8XPm4.cjs +0 -732
  168. package/dist/ensure-_Oh8XPm4.cjs.map +0 -1
  169. package/dist/ensure-fsQMjaQL.cjs +0 -567
  170. package/dist/ensure-fsQMjaQL.cjs.map +0 -1
  171. package/dist/ensure-vxAGa2v3.js +0 -536
  172. package/dist/ensure-vxAGa2v3.js.map +0 -1
  173. package/dist/management-14vsHr4t.cjs +0 -85
  174. package/dist/management-14vsHr4t.cjs.map +0 -1
  175. package/dist/management-444tdCBG.js +0 -38
  176. package/dist/management-444tdCBG.js.map +0 -1
  177. package/dist/management-7jLjVHJd.js +0 -38
  178. package/dist/management-7jLjVHJd.js.map +0 -1
  179. package/dist/management-AFuqQEwz.js +0 -38
  180. package/dist/management-AFuqQEwz.js.map +0 -1
  181. package/dist/management-BodVLAfJ.cjs +0 -85
  182. package/dist/management-BodVLAfJ.cjs.map +0 -1
  183. package/dist/management-BqGBdL01.js +0 -38
  184. package/dist/management-BqGBdL01.js.map +0 -1
  185. package/dist/management-BvUAjril.js +0 -38
  186. package/dist/management-BvUAjril.js.map +0 -1
  187. package/dist/management-C2wv4vGS.cjs +0 -85
  188. package/dist/management-C2wv4vGS.cjs.map +0 -1
  189. package/dist/management-D2pjr38g.js +0 -38
  190. package/dist/management-D2pjr38g.js.map +0 -1
  191. package/dist/management-D5WsZoTV.js +0 -38
  192. package/dist/management-D5WsZoTV.js.map +0 -1
  193. package/dist/management-DAV9sN-k.cjs +0 -85
  194. package/dist/management-DAV9sN-k.cjs.map +0 -1
  195. package/dist/management-Df7Y2sF1.cjs +0 -85
  196. package/dist/management-Df7Y2sF1.cjs.map +0 -1
  197. package/dist/management-DwO0nBzL.cjs +0 -85
  198. package/dist/management-DwO0nBzL.cjs.map +0 -1
  199. package/dist/management-GcnNJjnm.cjs +0 -85
  200. package/dist/management-GcnNJjnm.cjs.map +0 -1
  201. package/dist/types.cjs +0 -3
  202. package/dist/types.d.cts +0 -8
  203. package/dist/types.d.ts +0 -8
  204. package/dist/types.js +0 -2
  205. package/dist/watch-B91opmva.cjs +0 -70
  206. package/dist/watch-B91opmva.cjs.map +0 -1
  207. package/dist/watch-BHfCtbn1.cjs +0 -119
  208. package/dist/watch-BHfCtbn1.cjs.map +0 -1
  209. package/dist/watch-BmC-Tyqg.js +0 -59
  210. package/dist/watch-BmC-Tyqg.js.map +0 -1
  211. package/dist/watch-CKWt788i.cjs +0 -70
  212. package/dist/watch-CKWt788i.cjs.map +0 -1
  213. package/dist/watch-CfrSSrRY.js +0 -59
  214. package/dist/watch-CfrSSrRY.js.map +0 -1
  215. package/dist/watch-Cnh3PtQA.cjs +0 -70
  216. package/dist/watch-Cnh3PtQA.cjs.map +0 -1
  217. package/dist/watch-D01MdwKy.js +0 -108
  218. package/dist/watch-D01MdwKy.js.map +0 -1
  219. package/dist/watch-D8hwbFU1.js +0 -59
  220. package/dist/watch-D8hwbFU1.js.map +0 -1
  221. package/dist/watch-DPVUwPUY.js +0 -59
  222. package/dist/watch-DPVUwPUY.js.map +0 -1
  223. package/dist/watch-DVmvZBMD.cjs +0 -70
  224. package/dist/watch-DVmvZBMD.cjs.map +0 -1
  225. package/dist/watch-DkDGmGAx.cjs +0 -119
  226. package/dist/watch-DkDGmGAx.cjs.map +0 -1
  227. package/dist/watch-DztcJuUy.cjs +0 -119
  228. package/dist/watch-DztcJuUy.cjs.map +0 -1
  229. package/dist/watch-IrR8G28N.js +0 -108
  230. package/dist/watch-IrR8G28N.js.map +0 -1
  231. package/dist/watch-entry.cjs +0 -4
  232. package/dist/watch-entry.d.cts +0 -24
  233. package/dist/watch-entry.d.ts +0 -24
  234. package/dist/watch-entry.js +0 -2
  235. package/dist/watch-hx0r92Zc.js +0 -108
  236. package/dist/watch-hx0r92Zc.js.map +0 -1
package/dist/plugins.js CHANGED
@@ -1,2 +1,399 @@
1
- import { d as PluginManager, n as getPluginManager } from "./StateContainerRegistry-Dj77cok9.js";
1
+ import { l as getBlacName, m as IS_DEV, n as globalRegistry } from "./StateContainerRegistry-vWgsD-RP.js";
2
+ import { ALL_PATHS } from "@dirtytalk/structural";
3
+ //#region src/plugin/PluginManager.ts
4
+ /**
5
+ * Manages plugin lifecycle for the BlaC state management system.
6
+ *
7
+ * The manager hooks into two surfaces:
8
+ *
9
+ * 1. **Registry lifecycle events** (`created`, `disposed`, `refAcquired`,
10
+ * `refReleased`, `depsChanged`, `activated`, `deactivated`) — synchronous,
11
+ * fired by the registry.
12
+ *
13
+ * 2. **Per-container channel flushes** — microtask-coalesced. For each
14
+ * container, the manager subscribes once at create-time with
15
+ * `ALL_PATHS` interest and stashes `prevState`. On every flush it
16
+ * captures the new state + the changed `PathSet` and dispatches
17
+ * `onStateChange(ctx, prev, next, paths)` to every enabled plugin.
18
+ *
19
+ * The `ALL_PATHS` subscription cost: plugins counted as a consumer with
20
+ * `ALL_PATHS` interest will defeat the single-consumer-skip optimization
21
+ * in `StructuralContainer`. This is the intended trade-off — devtools /
22
+ * persist plugins genuinely want every change, and stateful plugins
23
+ * (logging) can decode `paths` via `container.interner.lookup(id)` to log
24
+ * only relevant fields. Plugins that want low overhead should remain
25
+ * uninstalled or environment-gated.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const manager = createPluginManager(registry);
30
+ * manager.install(myPlugin, { environment: 'development' });
31
+ * ```
32
+ */
33
+ var PluginManager = class {
34
+ /**
35
+ * Create a new PluginManager
36
+ * @param registry - The StateContainerRegistry to monitor for lifecycle events
37
+ */
38
+ constructor(registry) {
39
+ this.plugins = /* @__PURE__ */ new Map();
40
+ this.lifecycleUnsubscribers = [];
41
+ this.containerBridges = /* @__PURE__ */ new WeakMap();
42
+ this.contextCache = /* @__PURE__ */ new WeakMap();
43
+ this.registry = registry;
44
+ this.setupLifecycleHooks();
45
+ }
46
+ /**
47
+ * Install a plugin with optional configuration
48
+ * @param plugin - The plugin to install
49
+ * @param config - Optional plugin configuration
50
+ * @throws Error if plugin is already installed
51
+ */
52
+ install(plugin, config = {}) {
53
+ const effectiveConfig = {
54
+ enabled: true,
55
+ environment: "all",
56
+ ...config
57
+ };
58
+ if (!this.shouldEnablePlugin(effectiveConfig)) {
59
+ if (IS_DEV) console.log(`[BlaC] Plugin "${plugin.name}" skipped (environment mismatch)`);
60
+ return;
61
+ }
62
+ if (this.plugins.has(plugin.name)) throw new Error(`Plugin "${plugin.name}" is already installed`);
63
+ const installContext = this.buildContext(void 0);
64
+ this.plugins.set(plugin.name, {
65
+ plugin,
66
+ config: effectiveConfig,
67
+ installContext
68
+ });
69
+ this.backfillPlugin(this.plugins.get(plugin.name));
70
+ if (plugin.onInstall) try {
71
+ plugin.onInstall(installContext);
72
+ } catch (error) {
73
+ console.error(`[BlaC] Error installing plugin "${plugin.name}":`, error);
74
+ this.plugins.delete(plugin.name);
75
+ throw error;
76
+ }
77
+ if (IS_DEV) console.log(`[BlaC] Plugin "${plugin.name}" v${plugin.version} installed`);
78
+ }
79
+ /**
80
+ * Uninstall a plugin by name
81
+ * @param pluginName - The name of the plugin to uninstall
82
+ * @throws Error if plugin is not installed
83
+ */
84
+ uninstall(pluginName) {
85
+ const installed = this.plugins.get(pluginName);
86
+ if (!installed) throw new Error(`Plugin "${pluginName}" is not installed`);
87
+ if (installed.plugin.onUninstall) try {
88
+ installed.plugin.onUninstall();
89
+ } catch (error) {
90
+ console.error(`[BlaC] Error uninstalling plugin "${pluginName}":`, error);
91
+ }
92
+ this.plugins.delete(pluginName);
93
+ if (IS_DEV) console.log(`[BlaC] Plugin "${pluginName}" uninstalled`);
94
+ }
95
+ /**
96
+ * Get an installed plugin by name
97
+ * @param pluginName - The name of the plugin to retrieve
98
+ * @returns The plugin instance or undefined if not found
99
+ */
100
+ getPlugin(pluginName) {
101
+ return this.plugins.get(pluginName)?.plugin;
102
+ }
103
+ /**
104
+ * Get all installed plugins
105
+ * @returns Array of all installed plugins
106
+ */
107
+ getAllPlugins() {
108
+ return Array.from(this.plugins.values()).map((p) => p.plugin);
109
+ }
110
+ /**
111
+ * Check if a plugin is installed
112
+ * @param pluginName - The name of the plugin to check
113
+ * @returns true if the plugin is installed
114
+ */
115
+ hasPlugin(pluginName) {
116
+ return this.plugins.has(pluginName);
117
+ }
118
+ /**
119
+ * Uninstall all plugins
120
+ */
121
+ clear() {
122
+ for (const name of this.plugins.keys()) this.uninstall(name);
123
+ }
124
+ destroy() {
125
+ this.clear();
126
+ for (const unsub of this.lifecycleUnsubscribers) unsub();
127
+ this.lifecycleUnsubscribers = [];
128
+ }
129
+ /**
130
+ * Wire registry lifecycle events into plugin dispatch.
131
+ *
132
+ * `onStateChange` is NOT wired through `registry.on('stateChanged', …)` —
133
+ * that event lacks the `PathSet` payload. Instead, on each `created` we
134
+ * subscribe to the container's channel directly so we get
135
+ * `(paths)` and capture `(prev, next)` via the per-container snapshot.
136
+ */
137
+ setupLifecycleHooks() {
138
+ this.lifecycleUnsubscribers = [
139
+ this.registry.on("created", (instance) => {
140
+ this.attachStateBridge(instance);
141
+ this.notifyPlugins("onCreated", instance);
142
+ }),
143
+ this.registry.on("disposed", (instance) => {
144
+ this.notifyPlugins("onDestroyed", instance);
145
+ this.detachStateBridge(instance);
146
+ }),
147
+ this.registry.on("refAcquired", (instance, refId) => {
148
+ this.notifyPlugins("onRefAcquired", instance, refId);
149
+ }),
150
+ this.registry.on("refReleased", (instance, refId) => {
151
+ this.notifyPlugins("onRefReleased", instance, refId);
152
+ }),
153
+ this.registry.on("depsChanged", (instance, previousDeps, currentDeps) => {
154
+ this.notifyPlugins("onDepsChanged", instance, previousDeps, currentDeps);
155
+ }),
156
+ this.registry.on("hydrationChanged", (instance, status, previousStatus) => {
157
+ this.notifyPlugins("onHydrationChange", instance, status, previousStatus);
158
+ }),
159
+ this.registry.on("activated", (instance, signal) => {
160
+ this.notifyPlugins("onActivate", instance, signal);
161
+ }),
162
+ this.registry.on("deactivated", (instance) => {
163
+ this.notifyPlugins("onDeactivate", instance);
164
+ })
165
+ ];
166
+ }
167
+ /**
168
+ * Subscribe to the container's channel with `ALL_PATHS` interest, so we
169
+ * fire on every flush. Capture `prev` from the snapshot taken on the
170
+ * previous flush (or at create-time for the first flush) and pass the
171
+ * channel's `paths` argument straight through to plugins.
172
+ *
173
+ * Per-container bookkeeping is stored in a `WeakMap` keyed by the
174
+ * container itself, so a disposed/GC'd container drops cleanly.
175
+ */
176
+ attachStateBridge(container) {
177
+ if (this.containerBridges.has(container)) return;
178
+ const bridge = {
179
+ unsub: () => {},
180
+ prevState: container.state
181
+ };
182
+ this.containerBridges.set(container, bridge);
183
+ bridge.unsub = container.channel.subscribe(() => ALL_PATHS, (paths) => this.dispatchStateChange(container, paths));
184
+ }
185
+ detachStateBridge(container) {
186
+ const bridge = this.containerBridges.get(container);
187
+ if (!bridge) return;
188
+ bridge.unsub();
189
+ this.containerBridges.delete(container);
190
+ }
191
+ /**
192
+ * Backfill a newly-installed plugin with the app's existing instances.
193
+ *
194
+ * Iterates every registered `Type` and its live instances (`getAll`
195
+ * already skips disposed), attaching the state bridge (idempotent — see
196
+ * `attachStateBridge`) and, if the plugin implements `onCreated`, invoking
197
+ * it directly for each instance. Scoped to this single plugin only — the
198
+ * broadcast `notifyPlugins` would re-notify *every* plugin of *every*
199
+ * instance, which is wrong here.
200
+ */
201
+ backfillPlugin(installed) {
202
+ const { plugin } = installed;
203
+ for (const Type of this.registry.getTypes()) for (const instance of this.registry.getAll(Type)) {
204
+ const container = instance;
205
+ this.attachStateBridge(container);
206
+ if (!plugin.onCreated) continue;
207
+ try {
208
+ plugin.onCreated(this.buildContext(container));
209
+ } catch (error) {
210
+ console.error(`[BlaC] Error in plugin "${plugin.name}" onCreated (backfill):`, error);
211
+ }
212
+ }
213
+ }
214
+ /**
215
+ * Channel-flush callback. Snapshots `next`, hands `(prev, next, paths)`
216
+ * to every enabled plugin's `onStateChange`, then updates `prevState`
217
+ * for the next flush.
218
+ *
219
+ * Note: `prev` is captured once and reused across plugins — every
220
+ * plugin sees the same `prev`/`next`/`paths` regardless of dispatch
221
+ * order, matching the "snapshot prev once per flush" invariant.
222
+ */
223
+ dispatchStateChange(container, paths) {
224
+ const bridge = this.containerBridges.get(container);
225
+ if (!bridge) return;
226
+ const prev = bridge.prevState;
227
+ const next = container.state;
228
+ bridge.prevState = next;
229
+ let ctx;
230
+ for (const { plugin, config } of this.plugins.values()) {
231
+ if (!config.enabled) continue;
232
+ const hook = plugin.onStateChange;
233
+ if (typeof hook !== "function") continue;
234
+ try {
235
+ ctx ??= this.buildContext(container);
236
+ hook.call(plugin, ctx, prev, next, paths);
237
+ } catch (error) {
238
+ console.error(`[BlaC] Error in plugin "${plugin.name}" onStateChange:`, error);
239
+ }
240
+ }
241
+ }
242
+ /**
243
+ * Notify all plugins of a lifecycle event.
244
+ *
245
+ * Builds a fresh `PluginContext` per dispatch (instance becomes
246
+ * `ctx.container`), so plugins can reach the focal bloc through
247
+ * `ctx.container`. The context is built lazily on the first matching
248
+ * hook and reused for every other plugin in this dispatch — zero
249
+ * builds if no enabled plugin implements `hookName`.
250
+ */
251
+ notifyPlugins(hookName, instance, ...extraArgs) {
252
+ let ctx;
253
+ for (const { plugin, config } of this.plugins.values()) {
254
+ if (!config.enabled) continue;
255
+ const hook = plugin[hookName];
256
+ if (typeof hook !== "function") continue;
257
+ try {
258
+ ctx ??= this.buildContext(instance);
259
+ hook.call(plugin, ctx, ...extraArgs);
260
+ } catch (error) {
261
+ console.error(`[BlaC] Error in plugin "${plugin.name}" ${hookName}:`, error);
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Get the `PluginContext` for a focal container, building it once and
267
+ * reusing it thereafter. Eviction is handled by the `WeakMap`: the context
268
+ * dies with the container. `undefined` (install-time) is never cached.
269
+ */
270
+ buildContext(container) {
271
+ if (container !== void 0) {
272
+ const cached = this.contextCache.get(container);
273
+ if (cached) return cached;
274
+ }
275
+ const context = this.createContext(container);
276
+ if (container !== void 0) this.contextCache.set(container, context);
277
+ return context;
278
+ }
279
+ createContext(container) {
280
+ const registry = this.registry;
281
+ return {
282
+ container,
283
+ getInstanceMetadata: (instance) => {
284
+ return {
285
+ id: instance.$blac.id,
286
+ className: getBlacName(instance.constructor),
287
+ isDisposed: instance.$blac.disposed,
288
+ name: instance.$blac.name,
289
+ createdAt: instance.$blac.createdAt,
290
+ state: instance.state,
291
+ hydrationStatus: instance.$blac.hydration.status,
292
+ isHydrated: instance.$blac.hydration.isHydrated,
293
+ hydrationError: instance.$blac.hydration.error,
294
+ changedWhileHydrating: instance.$blac.hydration.changedWhileHydrating,
295
+ args: instance.args
296
+ };
297
+ },
298
+ getState: (instance) => {
299
+ return instance.state;
300
+ },
301
+ getHydrationStatus: (instance) => {
302
+ return instance.$blac.hydration.status;
303
+ },
304
+ startHydration: (instance) => {
305
+ instance.$blac.hydration.begin();
306
+ },
307
+ applyHydratedState: (instance, state) => {
308
+ return instance.$blac.hydration.apply(state);
309
+ },
310
+ finishHydration: (instance) => {
311
+ instance.$blac.hydration.finish();
312
+ },
313
+ failHydration: (instance, error) => {
314
+ instance.$blac.hydration.fail(error);
315
+ },
316
+ waitForHydration: (instance) => {
317
+ return instance.$blac.hydration.wait();
318
+ },
319
+ queryInstances: (typeClass) => {
320
+ return registry.getAll(typeClass);
321
+ },
322
+ getAllTypes: () => {
323
+ return registry.getTypes();
324
+ },
325
+ getStats: () => {
326
+ return registry.getStats();
327
+ },
328
+ getRefIds: (instanceId) => {
329
+ return registry.getRefIdsById(instanceId);
330
+ }
331
+ };
332
+ }
333
+ /**
334
+ * Check if plugin should be enabled based on environment
335
+ */
336
+ shouldEnablePlugin(config) {
337
+ if (!config.enabled) return false;
338
+ if (config.environment === "all") return true;
339
+ return this.getCurrentEnvironment() === config.environment;
340
+ }
341
+ /**
342
+ * Get current environment
343
+ */
344
+ getCurrentEnvironment() {
345
+ if (typeof process !== "undefined") {
346
+ if (process.env.NODE_ENV === "test") return "test";
347
+ if (process.env.NODE_ENV === "production") return "production";
348
+ return "development";
349
+ }
350
+ return "development";
351
+ }
352
+ };
353
+ /**
354
+ * Create a plugin manager instance
355
+ * @param registry - The StateContainerRegistry to monitor for lifecycle events
356
+ * @returns A new PluginManager instance
357
+ */
358
+ function createPluginManager(registry) {
359
+ return new PluginManager(registry);
360
+ }
361
+ //#endregion
362
+ //#region src/plugins.ts
363
+ /**
364
+ * Plugins Subpath Export
365
+ *
366
+ * Plugin system utilities for extending BlaC functionality.
367
+ * Import from `@blac/core/plugins`
368
+ *
369
+ * @example
370
+ * ```typescript
371
+ * import { PluginManager, getPluginManager, type BlacPlugin } from '@blac/core/plugins';
372
+ *
373
+ * const myPlugin: BlacPlugin = {
374
+ * name: 'my-plugin',
375
+ * onCreated(context) {
376
+ * console.log('Instance created:', context.container);
377
+ * },
378
+ * };
379
+ *
380
+ * getPluginManager().register(myPlugin);
381
+ * ```
382
+ *
383
+ * @packageDocumentation
384
+ */
385
+ /**
386
+ * Global plugin manager (initialized lazily)
387
+ */
388
+ let _globalPluginManager = null;
389
+ /**
390
+ * Get the global plugin manager
391
+ */
392
+ function getPluginManager() {
393
+ if (!_globalPluginManager) _globalPluginManager = createPluginManager(globalRegistry);
394
+ return _globalPluginManager;
395
+ }
396
+ //#endregion
2
397
  export { PluginManager, getPluginManager };
398
+
399
+ //# sourceMappingURL=plugins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.js","names":[],"sources":["../src/plugin/PluginManager.ts","../src/plugins.ts"],"sourcesContent":["import { ALL_PATHS, type PathSet } from '@dirtytalk/structural';\nimport type { StateContainer } from '../core/StateContainer';\nimport { IS_DEV } from '../constants';\nimport { getBlacName } from '../utils/static-props';\nimport type { StateContainerConstructor } from '../types/utilities';\nimport type { StateContainerRegistry } from '../core/StateContainerRegistry';\nimport type {\n BlacPlugin,\n PluginContext,\n PluginConfig,\n InstanceMetadata,\n} from './BlacPlugin';\n\n/**\n * Internal structure for tracking installed plugins.\n *\n * `installContext` is the context handed to `onInstall` — it has no\n * `container`. Per-container contexts are built on demand by\n * `buildContext()` so each event carries the right focal container\n * without mutating a shared object.\n * @internal\n */\ninterface InstalledPlugin {\n plugin: BlacPlugin;\n config: PluginConfig;\n installContext: PluginContext;\n}\n\n/**\n * Per-container bookkeeping for the channel-bridge plugin dispatcher.\n *\n * - `unsub` tears down the channel subscription on dispose.\n * - `prevState` is the state snapshot the manager will hand plugins as\n * `prev` on the next flush; it is updated to the post-flush state after\n * each dispatch.\n * @internal\n */\ninterface ContainerBridge {\n unsub: () => void;\n prevState: any;\n}\n\n/**\n * Manages plugin lifecycle for the BlaC state management system.\n *\n * The manager hooks into two surfaces:\n *\n * 1. **Registry lifecycle events** (`created`, `disposed`, `refAcquired`,\n * `refReleased`, `depsChanged`, `activated`, `deactivated`) — synchronous,\n * fired by the registry.\n *\n * 2. **Per-container channel flushes** — microtask-coalesced. For each\n * container, the manager subscribes once at create-time with\n * `ALL_PATHS` interest and stashes `prevState`. On every flush it\n * captures the new state + the changed `PathSet` and dispatches\n * `onStateChange(ctx, prev, next, paths)` to every enabled plugin.\n *\n * The `ALL_PATHS` subscription cost: plugins counted as a consumer with\n * `ALL_PATHS` interest will defeat the single-consumer-skip optimization\n * in `StructuralContainer`. This is the intended trade-off — devtools /\n * persist plugins genuinely want every change, and stateful plugins\n * (logging) can decode `paths` via `container.interner.lookup(id)` to log\n * only relevant fields. Plugins that want low overhead should remain\n * uninstalled or environment-gated.\n *\n * @example\n * ```ts\n * const manager = createPluginManager(registry);\n * manager.install(myPlugin, { environment: 'development' });\n * ```\n */\nexport class PluginManager {\n private plugins = new Map<string, InstalledPlugin>();\n private registry: StateContainerRegistry;\n private lifecycleUnsubscribers: (() => void)[] = [];\n\n /**\n * Per-container channel-bridge bookkeeping. Subscribed at `created`,\n * torn down at `disposed`. Holds the rolling `prevState` snapshot the\n * manager hands plugins on each flush.\n */\n private containerBridges = new WeakMap<\n StateContainer<any, any, any>,\n ContainerBridge\n >();\n\n /**\n * One `PluginContext` per container. The context closes over `registry` and\n * `container` only — both stable for the container's lifetime — so it is\n * safe to reuse instead of rebuilding its method table on every dispatch.\n */\n private contextCache = new WeakMap<\n StateContainer<any, any, any>,\n PluginContext\n >();\n\n /**\n * Create a new PluginManager\n * @param registry - The StateContainerRegistry to monitor for lifecycle events\n */\n constructor(registry: StateContainerRegistry) {\n this.registry = registry;\n this.setupLifecycleHooks();\n }\n\n /**\n * Install a plugin with optional configuration\n * @param plugin - The plugin to install\n * @param config - Optional plugin configuration\n * @throws Error if plugin is already installed\n */\n install(plugin: BlacPlugin, config: PluginConfig = {}): void {\n const effectiveConfig: PluginConfig = {\n enabled: true,\n environment: 'all',\n ...config,\n };\n\n if (!this.shouldEnablePlugin(effectiveConfig)) {\n if (IS_DEV) {\n console.log(\n `[BlaC] Plugin \"${plugin.name}\" skipped (environment mismatch)`,\n );\n }\n return;\n }\n\n if (this.plugins.has(plugin.name)) {\n throw new Error(`Plugin \"${plugin.name}\" is already installed`);\n }\n\n const installContext = this.buildContext(undefined);\n\n this.plugins.set(plugin.name, {\n plugin,\n config: effectiveConfig,\n installContext,\n });\n\n this.backfillPlugin(this.plugins.get(plugin.name)!);\n\n if (plugin.onInstall) {\n try {\n plugin.onInstall(installContext);\n } catch (error) {\n console.error(\n `[BlaC] Error installing plugin \"${plugin.name}\":`,\n error,\n );\n this.plugins.delete(plugin.name);\n throw error;\n }\n }\n\n if (IS_DEV) {\n console.log(\n `[BlaC] Plugin \"${plugin.name}\" v${plugin.version} installed`,\n );\n }\n }\n\n /**\n * Uninstall a plugin by name\n * @param pluginName - The name of the plugin to uninstall\n * @throws Error if plugin is not installed\n */\n uninstall(pluginName: string): void {\n const installed = this.plugins.get(pluginName);\n if (!installed) {\n throw new Error(`Plugin \"${pluginName}\" is not installed`);\n }\n\n if (installed.plugin.onUninstall) {\n try {\n installed.plugin.onUninstall();\n } catch (error) {\n console.error(\n `[BlaC] Error uninstalling plugin \"${pluginName}\":`,\n error,\n );\n }\n }\n\n this.plugins.delete(pluginName);\n if (IS_DEV) {\n console.log(`[BlaC] Plugin \"${pluginName}\" uninstalled`);\n }\n }\n\n /**\n * Get an installed plugin by name\n * @param pluginName - The name of the plugin to retrieve\n * @returns The plugin instance or undefined if not found\n */\n getPlugin(pluginName: string): BlacPlugin | undefined {\n return this.plugins.get(pluginName)?.plugin;\n }\n\n /**\n * Get all installed plugins\n * @returns Array of all installed plugins\n */\n getAllPlugins(): BlacPlugin[] {\n return Array.from(this.plugins.values()).map((p) => p.plugin);\n }\n\n /**\n * Check if a plugin is installed\n * @param pluginName - The name of the plugin to check\n * @returns true if the plugin is installed\n */\n hasPlugin(pluginName: string): boolean {\n return this.plugins.has(pluginName);\n }\n\n /**\n * Uninstall all plugins\n */\n clear(): void {\n for (const name of this.plugins.keys()) {\n this.uninstall(name);\n }\n }\n\n destroy(): void {\n this.clear();\n for (const unsub of this.lifecycleUnsubscribers) {\n unsub();\n }\n this.lifecycleUnsubscribers = [];\n }\n\n /**\n * Wire registry lifecycle events into plugin dispatch.\n *\n * `onStateChange` is NOT wired through `registry.on('stateChanged', …)` —\n * that event lacks the `PathSet` payload. Instead, on each `created` we\n * subscribe to the container's channel directly so we get\n * `(paths)` and capture `(prev, next)` via the per-container snapshot.\n */\n private setupLifecycleHooks(): void {\n this.lifecycleUnsubscribers = [\n this.registry.on('created', (instance) => {\n this.attachStateBridge(instance);\n this.notifyPlugins('onCreated', instance);\n }),\n this.registry.on('disposed', (instance) => {\n this.notifyPlugins('onDestroyed', instance);\n this.detachStateBridge(instance);\n }),\n this.registry.on('refAcquired', (instance, refId) => {\n this.notifyPlugins('onRefAcquired', instance, refId);\n }),\n this.registry.on('refReleased', (instance, refId) => {\n this.notifyPlugins('onRefReleased', instance, refId);\n }),\n this.registry.on('depsChanged', (instance, previousDeps, currentDeps) => {\n this.notifyPlugins(\n 'onDepsChanged',\n instance,\n previousDeps,\n currentDeps,\n );\n }),\n this.registry.on(\n 'hydrationChanged',\n (instance, status, previousStatus) => {\n this.notifyPlugins(\n 'onHydrationChange',\n instance,\n status,\n previousStatus,\n );\n },\n ),\n this.registry.on('activated', (instance, signal) => {\n this.notifyPlugins('onActivate', instance, signal);\n }),\n this.registry.on('deactivated', (instance) => {\n this.notifyPlugins('onDeactivate', instance);\n }),\n ];\n }\n\n /**\n * Subscribe to the container's channel with `ALL_PATHS` interest, so we\n * fire on every flush. Capture `prev` from the snapshot taken on the\n * previous flush (or at create-time for the first flush) and pass the\n * channel's `paths` argument straight through to plugins.\n *\n * Per-container bookkeeping is stored in a `WeakMap` keyed by the\n * container itself, so a disposed/GC'd container drops cleanly.\n */\n private attachStateBridge(container: StateContainer<any, any, any>): void {\n // Defensive: if a container is somehow created twice (it shouldn't be),\n // don't double-subscribe — the existing bridge is canonical.\n if (this.containerBridges.has(container)) return;\n\n const bridge: ContainerBridge = {\n unsub: () => {},\n prevState: container.state,\n };\n this.containerBridges.set(container, bridge);\n\n bridge.unsub = container.channel.subscribe(\n () => ALL_PATHS,\n (paths) => this.dispatchStateChange(container, paths),\n );\n }\n\n private detachStateBridge(container: StateContainer<any, any, any>): void {\n const bridge = this.containerBridges.get(container);\n if (!bridge) return;\n bridge.unsub();\n this.containerBridges.delete(container);\n }\n\n /**\n * Backfill a newly-installed plugin with the app's existing instances.\n *\n * Iterates every registered `Type` and its live instances (`getAll`\n * already skips disposed), attaching the state bridge (idempotent — see\n * `attachStateBridge`) and, if the plugin implements `onCreated`, invoking\n * it directly for each instance. Scoped to this single plugin only — the\n * broadcast `notifyPlugins` would re-notify *every* plugin of *every*\n * instance, which is wrong here.\n */\n private backfillPlugin(installed: InstalledPlugin): void {\n const { plugin } = installed;\n for (const Type of this.registry.getTypes()) {\n for (const instance of this.registry.getAll(Type)) {\n // getAll returns the readonly public view; internal bridging needs the\n // real container (same friction the `as any` in queryInstances handles).\n const container = instance as StateContainer<any, any, any>;\n this.attachStateBridge(container);\n\n if (!plugin.onCreated) continue;\n try {\n plugin.onCreated(this.buildContext(container));\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" onCreated (backfill):`,\n error,\n );\n }\n }\n }\n }\n\n /**\n * Channel-flush callback. Snapshots `next`, hands `(prev, next, paths)`\n * to every enabled plugin's `onStateChange`, then updates `prevState`\n * for the next flush.\n *\n * Note: `prev` is captured once and reused across plugins — every\n * plugin sees the same `prev`/`next`/`paths` regardless of dispatch\n * order, matching the \"snapshot prev once per flush\" invariant.\n */\n private dispatchStateChange(\n container: StateContainer<any, any, any>,\n paths: PathSet,\n ): void {\n const bridge = this.containerBridges.get(container);\n if (!bridge) return;\n\n const prev = bridge.prevState;\n const next = container.state;\n bridge.prevState = next;\n\n let ctx: PluginContext | undefined;\n for (const { plugin, config } of this.plugins.values()) {\n if (!config.enabled) continue;\n // eslint-disable-next-line @typescript-eslint/unbound-method -- invoked via .call below\n const hook = plugin.onStateChange;\n if (typeof hook !== 'function') continue;\n\n try {\n ctx ??= this.buildContext(container);\n hook.call(plugin, ctx, prev, next, paths);\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" onStateChange:`,\n error,\n );\n }\n }\n }\n\n /**\n * Notify all plugins of a lifecycle event.\n *\n * Builds a fresh `PluginContext` per dispatch (instance becomes\n * `ctx.container`), so plugins can reach the focal bloc through\n * `ctx.container`. The context is built lazily on the first matching\n * hook and reused for every other plugin in this dispatch — zero\n * builds if no enabled plugin implements `hookName`.\n */\n private notifyPlugins(\n hookName: Exclude<keyof BlacPlugin, 'onStateChange'>,\n instance: StateContainer<any, any, any>,\n ...extraArgs: any[]\n ): void {\n let ctx: PluginContext | undefined;\n for (const { plugin, config } of this.plugins.values()) {\n if (!config.enabled) continue;\n\n const hook = plugin[hookName];\n if (typeof hook !== 'function') continue;\n\n try {\n ctx ??= this.buildContext(instance);\n (hook as any).call(plugin, ctx, ...extraArgs);\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" ${hookName}:`,\n error,\n );\n }\n }\n }\n\n /**\n * Get the `PluginContext` for a focal container, building it once and\n * reusing it thereafter. Eviction is handled by the `WeakMap`: the context\n * dies with the container. `undefined` (install-time) is never cached.\n */\n private buildContext(\n container: StateContainer<any, any, any> | undefined,\n ): PluginContext {\n if (container !== undefined) {\n const cached = this.contextCache.get(container);\n if (cached) return cached;\n }\n const context = this.createContext(container);\n if (container !== undefined) this.contextCache.set(container, context);\n return context;\n }\n\n private createContext(\n container: StateContainer<any, any, any> | undefined,\n ): PluginContext {\n const registry = this.registry;\n return {\n container,\n\n getInstanceMetadata: (\n instance: StateContainer<any, any, any>,\n ): InstanceMetadata => {\n return {\n id: instance.$blac.id,\n className: getBlacName(\n instance.constructor as StateContainerConstructor,\n ),\n isDisposed: instance.$blac.disposed,\n name: instance.$blac.name,\n createdAt: instance.$blac.createdAt,\n state: instance.state,\n hydrationStatus: instance.$blac.hydration.status,\n isHydrated: instance.$blac.hydration.isHydrated,\n hydrationError: instance.$blac.hydration.error,\n changedWhileHydrating: instance.$blac.hydration.changedWhileHydrating,\n args: instance.args,\n };\n },\n\n getState: <S extends object = any>(instance: StateContainer<S>): S => {\n return instance.state;\n },\n\n getHydrationStatus: (instance: StateContainer<any, any, any>) => {\n return instance.$blac.hydration.status;\n },\n\n startHydration: (instance: StateContainer<any, any, any>) => {\n instance.$blac.hydration.begin();\n },\n\n applyHydratedState: <S extends object = any>(\n instance: StateContainer<S>,\n state: S,\n ): boolean => {\n return instance.$blac.hydration.apply(state);\n },\n\n finishHydration: (instance: StateContainer<any, any, any>) => {\n instance.$blac.hydration.finish();\n },\n\n failHydration: (\n instance: StateContainer<any, any, any>,\n error: Error,\n ) => {\n instance.$blac.hydration.fail(error);\n },\n\n waitForHydration: (instance: StateContainer<any, any, any>) => {\n return instance.$blac.hydration.wait();\n },\n\n queryInstances: <T extends StateContainer<any, any, any>>(\n typeClass: new (...args: any[]) => T,\n ): T[] => {\n return registry.getAll(typeClass as any);\n },\n\n getAllTypes: () => {\n return registry.getTypes();\n },\n\n getStats: () => {\n return registry.getStats();\n },\n\n getRefIds: (instanceId: string): string[] => {\n return registry.getRefIdsById(instanceId);\n },\n };\n }\n\n /**\n * Check if plugin should be enabled based on environment\n */\n private shouldEnablePlugin(config: PluginConfig): boolean {\n if (!config.enabled) return false;\n if (config.environment === 'all') return true;\n\n const currentEnv = this.getCurrentEnvironment();\n return currentEnv === config.environment;\n }\n\n /**\n * Get current environment\n */\n private getCurrentEnvironment(): 'development' | 'production' | 'test' {\n if (typeof process !== 'undefined') {\n if (process.env.NODE_ENV === 'test') return 'test';\n if (process.env.NODE_ENV === 'production') return 'production';\n return 'development';\n }\n return 'development';\n }\n}\n\n/**\n * Create a plugin manager instance\n * @param registry - The StateContainerRegistry to monitor for lifecycle events\n * @returns A new PluginManager instance\n */\nexport function createPluginManager(\n registry: StateContainerRegistry,\n): PluginManager {\n return new PluginManager(registry);\n}\n","/**\n * Plugins Subpath Export\n *\n * Plugin system utilities for extending BlaC functionality.\n * Import from `@blac/core/plugins`\n *\n * @example\n * ```typescript\n * import { PluginManager, getPluginManager, type BlacPlugin } from '@blac/core/plugins';\n *\n * const myPlugin: BlacPlugin = {\n * name: 'my-plugin',\n * onCreated(context) {\n * console.log('Instance created:', context.container);\n * },\n * };\n *\n * getPluginManager().register(myPlugin);\n * ```\n *\n * @packageDocumentation\n */\n\nimport { createPluginManager, PluginManager } from './plugin/PluginManager';\nimport { globalRegistry } from './core/StateContainerRegistry';\n\nexport { PluginManager } from './plugin/PluginManager';\nexport type {\n BlacPlugin,\n BlacPluginWithInit,\n PluginContext,\n PluginConfig,\n InstanceMetadata,\n} from './plugin/BlacPlugin';\n\n/**\n * Global plugin manager (initialized lazily)\n */\nlet _globalPluginManager: PluginManager | null = null;\n\n/**\n * Get the global plugin manager\n */\nexport function getPluginManager(): PluginManager {\n if (!_globalPluginManager) {\n _globalPluginManager = createPluginManager(globalRegistry);\n }\n return _globalPluginManager;\n}\n"],"mappings":"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"}
@@ -5,8 +5,8 @@ import type { ExtractArgs, StateContainerConstructor } from '../types/utilities'
5
5
  * a structural hash of `args`, or the default sentinel when there are none).
6
6
  *
7
7
  * @param BlocClass - The StateContainer class constructor
8
- * @param opts.args - Construction/identity args; derives the instance key
9
- * @param opts.refId - Named reference ID for debugging; auto-generated if omitted
8
+ * @param opts - `args` derives the instance key; `refId` names the reference
9
+ * for debugging and is auto-generated when omitted.
10
10
  */
11
11
  export declare function acquire<T extends StateContainerConstructor>(BlocClass: T, opts?: {
12
12
  args?: ExtractArgs<T>;
@@ -1,6 +1,15 @@
1
1
  import { StateContainerRegistry } from '../core/StateContainerRegistry';
2
+ /**
3
+ * @public
4
+ */
2
5
  export declare function getRegistry(): StateContainerRegistry;
6
+ /**
7
+ * @public
8
+ */
3
9
  export declare function setRegistry(registry: StateContainerRegistry): void;
10
+ /**
11
+ * @public
12
+ */
4
13
  export declare function getStats(): {
5
14
  registeredTypes: number;
6
15
  totalInstances: number;
@@ -1,4 +1,7 @@
1
1
  import type { StateContainerConstructor } from '../types/utilities';
2
2
  export declare function clear<T extends StateContainerConstructor>(BlocClass: T): void;
3
3
  export declare function clearAll(): void;
4
+ /**
5
+ * @public
6
+ */
4
7
  export declare function register<T extends StateContainerConstructor>(BlocClass: T): void;
@@ -1,12 +1,27 @@
1
1
  import type { ExtractArgs, StateContainerConstructor, InstanceReadonlyState } from '../types/utilities';
2
+ /**
3
+ * @public
4
+ */
2
5
  export declare function hasInstance<T extends StateContainerConstructor>(BlocClass: T, opts?: {
3
6
  args?: ExtractArgs<T>;
4
7
  }): boolean;
8
+ /**
9
+ * @public
10
+ */
5
11
  export declare function getRefCount<T extends StateContainerConstructor>(BlocClass: T, opts?: {
6
12
  args?: ExtractArgs<T>;
7
13
  }): number;
14
+ /**
15
+ * @public
16
+ */
8
17
  export declare function getRefIds<T extends StateContainerConstructor>(BlocClass: T, opts?: {
9
18
  args?: ExtractArgs<T>;
10
19
  }): string[];
20
+ /**
21
+ * @public
22
+ */
11
23
  export declare function getAll<T extends StateContainerConstructor>(BlocClass: T): InstanceReadonlyState<T>[];
24
+ /**
25
+ * @public
26
+ */
12
27
  export declare function forEach<T extends StateContainerConstructor>(BlocClass: T, callback: (instance: InstanceReadonlyState<T>) => void): void;
@@ -3,9 +3,9 @@ import type { ExtractArgs, StateContainerConstructor } from '../types/utilities'
3
3
  * Release a reference to an instance. Instance identity is derived purely from
4
4
  * `args` (must match the `args` it was acquired with).
5
5
  *
6
- * @param opts.args - Construction/identity args; derives the instance key
7
- * @param opts.refId - The specific ref to drop; drops one arbitrary ref if omitted
8
- * @param opts.forceDispose - Force immediate disposal regardless of refs
6
+ * @param opts - `args` derives the instance key (must match acquire);
7
+ * `refId` picks the ref to drop, otherwise one arbitrary ref goes;
8
+ * `forceDispose` disposes immediately regardless of remaining refs.
9
9
  */
10
10
  export declare function release<T extends StateContainerConstructor>(BlocClass: T, opts?: {
11
11
  args?: ExtractArgs<T>;
package/dist/testing.cjs CHANGED
@@ -1,8 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_StateContainerRegistry = require("./StateContainerRegistry-lIwo-0m3.cjs");
3
- const require_ensure = require("./ensure-VuiNPccZ.cjs");
4
- const require_config = require("./config-B1l8ZGlU.cjs");
5
- const require_acquire = require("./acquire-C4WAS13G.cjs");
2
+ const require_StateContainerRegistry = require("./StateContainerRegistry-141uHhtF.cjs");
3
+ const require_ensure = require("./ensure-ORVw-WRE.cjs");
6
4
  //#region src/testing.ts
7
5
  /** Synthetic owner id used by test helpers for pre-wired deps. */
8
6
  const TESTING_DEPS_OWNER = "testing-deps";
@@ -10,38 +8,38 @@ function createTestRegistry() {
10
8
  return new require_StateContainerRegistry.StateContainerRegistry();
11
9
  }
12
10
  function withTestRegistry(fn) {
13
- const previous = require_config.getRegistry();
11
+ const previous = require_ensure.getRegistry();
14
12
  const testRegistry = createTestRegistry();
15
- require_config.setRegistry(testRegistry);
13
+ require_ensure.setRegistry(testRegistry);
16
14
  try {
17
15
  const result = fn(testRegistry);
18
16
  if (result instanceof Promise) return result.then((value) => {
19
- require_config.setRegistry(previous);
17
+ require_ensure.setRegistry(previous);
20
18
  return value;
21
19
  }, (error) => {
22
- require_config.setRegistry(previous);
20
+ require_ensure.setRegistry(previous);
23
21
  throw error;
24
22
  });
25
- require_config.setRegistry(previous);
23
+ require_ensure.setRegistry(previous);
26
24
  return result;
27
25
  } catch (error) {
28
- require_config.setRegistry(previous);
26
+ require_ensure.setRegistry(previous);
29
27
  throw error;
30
28
  }
31
29
  }
32
30
  function blacTestSetup() {
33
31
  let savedRegistry;
34
32
  beforeEach(() => {
35
- savedRegistry = require_config.getRegistry();
36
- require_config.setRegistry(new require_StateContainerRegistry.StateContainerRegistry());
33
+ savedRegistry = require_ensure.getRegistry();
34
+ require_ensure.setRegistry(new require_StateContainerRegistry.StateContainerRegistry());
37
35
  });
38
36
  afterEach(() => {
39
- require_config.setRegistry(savedRegistry);
37
+ require_ensure.setRegistry(savedRegistry);
40
38
  });
41
39
  }
42
40
  function registerOverride(BlocClass, instance, args) {
43
- const registry = require_config.getRegistry();
44
- const key = require_acquire.resolveInstanceKey(BlocClass, args);
41
+ const registry = require_ensure.getRegistry();
42
+ const key = require_ensure.resolveInstanceKey(BlocClass, args);
45
43
  registry.insertInstance(BlocClass, key, instance, new Map([["testing-override", 1]]));
46
44
  }
47
45
  function overrideEnsure(BlocClass, instance, fn, args) {
@@ -1 +1 @@
1
- {"version":3,"file":"testing.cjs","names":["StateContainerRegistry","getRegistry","resolveInstanceKey","INIT_CONFIG","Cubit","APPLY_DEPS","ensure"],"sources":["../src/testing.ts"],"sourcesContent":["import { Cubit } from './core/Cubit';\nimport { StateContainerRegistry } from './core/StateContainerRegistry';\nimport { APPLY_DEPS, INIT_CONFIG } from './core/symbols';\nimport { ensure, getRegistry, setRegistry } from './registry';\nimport { resolveInstanceKey } from './registry/acquire';\nimport type {\n ExtractArgs,\n ExtractDeps,\n ExtractState,\n StateContainerConstructor,\n} from './types/utilities';\n\n/** Synthetic owner id used by test helpers for pre-wired deps. */\nconst TESTING_DEPS_OWNER = 'testing-deps';\n\ndeclare const beforeEach: (fn: () => void) => void;\ndeclare const afterEach: (fn: () => void) => void;\n\n// --- createTestRegistry + withTestRegistry ---\n\nexport function createTestRegistry(): StateContainerRegistry {\n return new StateContainerRegistry();\n}\n\nexport function withTestRegistry<T>(\n fn: (registry: StateContainerRegistry) => T,\n): T {\n const previous = getRegistry();\n const testRegistry = createTestRegistry();\n setRegistry(testRegistry);\n try {\n const result = fn(testRegistry);\n if (result instanceof Promise) {\n return result.then(\n (value) => {\n setRegistry(previous);\n return value;\n },\n (error) => {\n setRegistry(previous);\n throw error;\n },\n ) as T;\n }\n setRegistry(previous);\n return result;\n } catch (error) {\n setRegistry(previous);\n throw error;\n }\n}\n\n// --- blacTestSetup ---\n\nexport function blacTestSetup(): void {\n let savedRegistry: StateContainerRegistry;\n beforeEach(() => {\n savedRegistry = getRegistry();\n setRegistry(new StateContainerRegistry());\n });\n afterEach(() => {\n setRegistry(savedRegistry);\n });\n}\n\n// --- registerOverride + overrideEnsure ---\n\nexport function registerOverride<T extends StateContainerConstructor>(\n BlocClass: T,\n instance: InstanceType<T>,\n args?: ExtractArgs<T>,\n): void {\n const registry = getRegistry();\n const key = resolveInstanceKey(BlocClass, args);\n registry.insertInstance(\n BlocClass,\n key,\n instance,\n new Map([['testing-override', 1]]),\n );\n}\n\nexport function overrideEnsure<T extends StateContainerConstructor, R>(\n BlocClass: T,\n instance: InstanceType<T>,\n fn: () => R,\n args?: ExtractArgs<T>,\n): R {\n return withTestRegistry(() => {\n registerOverride(BlocClass, instance, args);\n return fn();\n });\n}\n\n// --- createCubitStub ---\n\ntype MethodKeys<T> = {\n [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;\n}[keyof T];\n\nexport interface CubitStubOptions<T extends StateContainerConstructor> {\n state?: ExtractState<T> extends Record<string, any>\n ? Partial<ExtractState<T>>\n : ExtractState<T>;\n methods?: Partial<\n Record<MethodKeys<InstanceType<T>>, (...args: any[]) => any>\n >;\n /**\n * Args to pass to init(). If the bloc's Args type is not void, supplying\n * args here causes initConfig({ args }) to be called so init() runs.\n */\n args?: ExtractArgs<T> extends void ? never : ExtractArgs<T>;\n /**\n * Deps slice to pre-wire via the core [APPLY_DEPS] path (synthetic owner\n * \"testing-deps\"), so onDepsChanged fires during tests.\n */\n deps?: Partial<ExtractDeps<T>>;\n}\n\nexport function createCubitStub<T extends StateContainerConstructor>(\n BlocClass: T,\n options?: CubitStubOptions<T>,\n): InstanceType<T> {\n const instance = new BlocClass() as InstanceType<T>;\n\n // Run init() if args are supplied — goes through the same initConfig path\n // that the registry uses, so lifecycle hooks fire correctly.\n if (options?.args != null) {\n instance[INIT_CONFIG]({ args: options.args });\n }\n\n if (options?.state != null) {\n if (instance instanceof Cubit) {\n const currentState = instance.state;\n if (\n typeof currentState === 'object' &&\n currentState !== null &&\n typeof options.state === 'object' &&\n options.state !== null\n ) {\n instance.patch(options.state as any);\n } else {\n instance.emit(options.state as any);\n }\n }\n }\n if (options?.methods) {\n for (const [key, impl] of Object.entries(options.methods)) {\n if (typeof impl === 'function') {\n (instance as any)[key] = impl;\n }\n }\n }\n\n // Pre-wire deps via the core merge path so onDepsChanged fires in tests.\n if (options?.deps != null) {\n (instance as any)[APPLY_DEPS](TESTING_DEPS_OWNER, options.deps);\n }\n\n return instance;\n}\n\n// --- withBlocState ---\n\nexport function withBlocState<T extends StateContainerConstructor>(\n BlocClass: T,\n state: ExtractState<T> extends Record<string, any>\n ? Partial<ExtractState<T>>\n : ExtractState<T>,\n args?: ExtractArgs<T>,\n): InstanceType<T> {\n const instance = ensure(BlocClass, { args });\n if (instance instanceof Cubit) {\n const currentState = instance.state;\n if (\n typeof currentState === 'object' &&\n currentState !== null &&\n typeof state === 'object' &&\n state !== null\n ) {\n instance.patch(state as any);\n } else {\n instance.emit(state as any);\n }\n }\n return instance;\n}\n\n// --- withBlocMethod ---\n\nexport function withBlocMethod<T extends StateContainerConstructor>(\n BlocClass: T,\n methodName: keyof InstanceType<T>,\n impl: (...args: any[]) => any,\n args?: ExtractArgs<T>,\n): InstanceType<T> {\n const instance = ensure(BlocClass, { args });\n (instance as any)[methodName] = impl;\n return instance;\n}\n\n// --- flush ---\n\n/**\n * Drain pending microtasks so any channel-flushed effects (channel\n * subscribers, `onSystemEvent('stateChanged')` handlers, plugin hooks) run\n * before the next assertion.\n *\n * The default `MicrotaskScheduler` coalesces emits within a tick; tests\n * that emit and then assert on subscriber side-effects need `await flush()`\n * between the two.\n */\nexport async function flush(): Promise<void> {\n // queueMicrotask wins the race against the channel's own queued flush —\n // we resolve after the channel has drained its current pending flush.\n await Promise.resolve();\n await Promise.resolve();\n}\n"],"mappings":";;;;;;;AAaA,MAAM,qBAAqB;AAO3B,SAAgB,qBAA6C;CAC3D,OAAO,IAAIA,+BAAAA,uBAAuB;AACpC;AAEA,SAAgB,iBACd,IACG;CACH,MAAM,WAAWC,eAAAA,YAAY;CAC7B,MAAM,eAAe,mBAAmB;CACxC,eAAA,YAAY,YAAY;CACxB,IAAI;EACF,MAAM,SAAS,GAAG,YAAY;EAC9B,IAAI,kBAAkB,SACpB,OAAO,OAAO,MACX,UAAU;GACT,eAAA,YAAY,QAAQ;GACpB,OAAO;EACT,IACC,UAAU;GACT,eAAA,YAAY,QAAQ;GACpB,MAAM;EACR,CACF;EAEF,eAAA,YAAY,QAAQ;EACpB,OAAO;CACT,SAAS,OAAO;EACd,eAAA,YAAY,QAAQ;EACpB,MAAM;CACR;AACF;AAIA,SAAgB,gBAAsB;CACpC,IAAI;CACJ,iBAAiB;EACf,gBAAgBA,eAAAA,YAAY;EAC5B,eAAA,YAAY,IAAID,+BAAAA,uBAAuB,CAAC;CAC1C,CAAC;CACD,gBAAgB;EACd,eAAA,YAAY,aAAa;CAC3B,CAAC;AACH;AAIA,SAAgB,iBACd,WACA,UACA,MACM;CACN,MAAM,WAAWC,eAAAA,YAAY;CAC7B,MAAM,MAAMC,gBAAAA,mBAAmB,WAAW,IAAI;CAC9C,SAAS,eACP,WACA,KACA,UACA,IAAI,IAAI,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CACnC;AACF;AAEA,SAAgB,eACd,WACA,UACA,IACA,MACG;CACH,OAAO,uBAAuB;EAC5B,iBAAiB,WAAW,UAAU,IAAI;EAC1C,OAAO,GAAG;CACZ,CAAC;AACH;AA2BA,SAAgB,gBACd,WACA,SACiB;CACjB,MAAM,WAAW,IAAI,UAAU;CAI/B,IAAI,SAAS,QAAQ,MACnB,SAASC,+BAAAA,aAAa,EAAE,MAAM,QAAQ,KAAK,CAAC;CAG9C,IAAI,SAAS,SAAS;MAChB,oBAAoBC,eAAAA,OAAO;GAC7B,MAAM,eAAe,SAAS;GAC9B,IACE,OAAO,iBAAiB,YACxB,iBAAiB,QACjB,OAAO,QAAQ,UAAU,YACzB,QAAQ,UAAU,MAElB,SAAS,MAAM,QAAQ,KAAY;QAEnC,SAAS,KAAK,QAAQ,KAAY;EAEtC;;CAEF,IAAI,SAAS;OACN,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,QAAQ,OAAO,GACtD,IAAI,OAAO,SAAS,YAClB,SAAkB,OAAO;CAAA;CAM/B,IAAI,SAAS,QAAQ,MACnB,SAAkBC,+BAAAA,YAAY,oBAAoB,QAAQ,IAAI;CAGhE,OAAO;AACT;AAIA,SAAgB,cACd,WACA,OAGA,MACiB;CACjB,MAAM,WAAWC,eAAAA,OAAO,WAAW,EAAE,KAAK,CAAC;CAC3C,IAAI,oBAAoBF,eAAAA,OAAO;EAC7B,MAAM,eAAe,SAAS;EAC9B,IACE,OAAO,iBAAiB,YACxB,iBAAiB,QACjB,OAAO,UAAU,YACjB,UAAU,MAEV,SAAS,MAAM,KAAY;OAE3B,SAAS,KAAK,KAAY;CAE9B;CACA,OAAO;AACT;AAIA,SAAgB,eACd,WACA,YACA,MACA,MACiB;CACjB,MAAM,WAAWE,eAAAA,OAAO,WAAW,EAAE,KAAK,CAAC;CAC3C,SAAkB,cAAc;CAChC,OAAO;AACT;;;;;;;;;;AAaA,eAAsB,QAAuB;CAG3C,MAAM,QAAQ,QAAQ;CACtB,MAAM,QAAQ,QAAQ;AACxB"}
1
+ {"version":3,"file":"testing.cjs","names":["StateContainerRegistry","getRegistry","resolveInstanceKey","INIT_CONFIG","Cubit","APPLY_DEPS","ensure"],"sources":["../src/testing.ts"],"sourcesContent":["import { Cubit } from './core/Cubit';\nimport { StateContainerRegistry } from './core/StateContainerRegistry';\nimport { APPLY_DEPS, INIT_CONFIG } from './core/symbols';\nimport { ensure, getRegistry, setRegistry } from './registry';\nimport { resolveInstanceKey } from './registry/acquire';\nimport type {\n ExtractArgs,\n ExtractDeps,\n ExtractState,\n StateContainerConstructor,\n} from './types/utilities';\n\n/** Synthetic owner id used by test helpers for pre-wired deps. */\nconst TESTING_DEPS_OWNER = 'testing-deps';\n\ndeclare const beforeEach: (fn: () => void) => void;\ndeclare const afterEach: (fn: () => void) => void;\n\n// --- createTestRegistry + withTestRegistry ---\n\nexport function createTestRegistry(): StateContainerRegistry {\n return new StateContainerRegistry();\n}\n\nexport function withTestRegistry<T>(\n fn: (registry: StateContainerRegistry) => T,\n): T {\n const previous = getRegistry();\n const testRegistry = createTestRegistry();\n setRegistry(testRegistry);\n try {\n const result = fn(testRegistry);\n if (result instanceof Promise) {\n return result.then(\n (value) => {\n setRegistry(previous);\n return value;\n },\n (error) => {\n setRegistry(previous);\n throw error;\n },\n ) as T;\n }\n setRegistry(previous);\n return result;\n } catch (error) {\n setRegistry(previous);\n throw error;\n }\n}\n\n// --- blacTestSetup ---\n\nexport function blacTestSetup(): void {\n let savedRegistry: StateContainerRegistry;\n beforeEach(() => {\n savedRegistry = getRegistry();\n setRegistry(new StateContainerRegistry());\n });\n afterEach(() => {\n setRegistry(savedRegistry);\n });\n}\n\n// --- registerOverride + overrideEnsure ---\n\nexport function registerOverride<T extends StateContainerConstructor>(\n BlocClass: T,\n instance: InstanceType<T>,\n args?: ExtractArgs<T>,\n): void {\n const registry = getRegistry();\n const key = resolveInstanceKey(BlocClass, args);\n registry.insertInstance(\n BlocClass,\n key,\n instance,\n new Map([['testing-override', 1]]),\n );\n}\n\nexport function overrideEnsure<T extends StateContainerConstructor, R>(\n BlocClass: T,\n instance: InstanceType<T>,\n fn: () => R,\n args?: ExtractArgs<T>,\n): R {\n return withTestRegistry(() => {\n registerOverride(BlocClass, instance, args);\n return fn();\n });\n}\n\n// --- createCubitStub ---\n\ntype MethodKeys<T> = {\n [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;\n}[keyof T];\n\nexport interface CubitStubOptions<T extends StateContainerConstructor> {\n state?: ExtractState<T> extends Record<string, any>\n ? Partial<ExtractState<T>>\n : ExtractState<T>;\n methods?: Partial<\n Record<MethodKeys<InstanceType<T>>, (...args: any[]) => any>\n >;\n /**\n * Args to pass to init(). If the bloc's Args type is not void, supplying\n * args here causes `initConfig({ args })` to be called so `init()` runs.\n */\n args?: ExtractArgs<T> extends void ? never : ExtractArgs<T>;\n /**\n * Deps slice to pre-wire via the core [APPLY_DEPS] path (synthetic owner\n * \"testing-deps\"), so onDepsChanged fires during tests.\n */\n deps?: Partial<ExtractDeps<T>>;\n}\n\nexport function createCubitStub<T extends StateContainerConstructor>(\n BlocClass: T,\n options?: CubitStubOptions<T>,\n): InstanceType<T> {\n const instance = new BlocClass() as InstanceType<T>;\n\n // Run init() if args are supplied — goes through the same initConfig path\n // that the registry uses, so lifecycle hooks fire correctly.\n if (options?.args != null) {\n instance[INIT_CONFIG]({ args: options.args });\n }\n\n if (options?.state != null) {\n if (instance instanceof Cubit) {\n const currentState = instance.state;\n if (\n typeof currentState === 'object' &&\n currentState !== null &&\n typeof options.state === 'object' &&\n options.state !== null\n ) {\n instance.patch(options.state as any);\n } else {\n instance.emit(options.state as any);\n }\n }\n }\n if (options?.methods) {\n for (const [key, impl] of Object.entries(options.methods)) {\n if (typeof impl === 'function') {\n (instance as any)[key] = impl;\n }\n }\n }\n\n // Pre-wire deps via the core merge path so onDepsChanged fires in tests.\n if (options?.deps != null) {\n (instance as any)[APPLY_DEPS](TESTING_DEPS_OWNER, options.deps);\n }\n\n return instance;\n}\n\n// --- withBlocState ---\n\nexport function withBlocState<T extends StateContainerConstructor>(\n BlocClass: T,\n state: ExtractState<T> extends Record<string, any>\n ? Partial<ExtractState<T>>\n : ExtractState<T>,\n args?: ExtractArgs<T>,\n): InstanceType<T> {\n const instance = ensure(BlocClass, { args });\n if (instance instanceof Cubit) {\n const currentState = instance.state;\n if (\n typeof currentState === 'object' &&\n currentState !== null &&\n typeof state === 'object' &&\n state !== null\n ) {\n instance.patch(state as any);\n } else {\n instance.emit(state as any);\n }\n }\n return instance;\n}\n\n// --- withBlocMethod ---\n\nexport function withBlocMethod<T extends StateContainerConstructor>(\n BlocClass: T,\n methodName: keyof InstanceType<T>,\n impl: (...args: any[]) => any,\n args?: ExtractArgs<T>,\n): InstanceType<T> {\n const instance = ensure(BlocClass, { args });\n (instance as any)[methodName] = impl;\n return instance;\n}\n\n// --- flush ---\n\n/**\n * Drain pending microtasks so any channel-flushed effects (channel\n * subscribers, `onSystemEvent('stateChanged')` handlers, plugin hooks) run\n * before the next assertion.\n *\n * The default `MicrotaskScheduler` coalesces emits within a tick; tests\n * that emit and then assert on subscriber side-effects need `await flush()`\n * between the two.\n */\nexport async function flush(): Promise<void> {\n // queueMicrotask wins the race against the channel's own queued flush —\n // we resolve after the channel has drained its current pending flush.\n await Promise.resolve();\n await Promise.resolve();\n}\n"],"mappings":";;;;;AAaA,MAAM,qBAAqB;AAO3B,SAAgB,qBAA6C;CAC3D,OAAO,IAAIA,+BAAAA,uBAAuB;AACpC;AAEA,SAAgB,iBACd,IACG;CACH,MAAM,WAAWC,eAAAA,YAAY;CAC7B,MAAM,eAAe,mBAAmB;CACxC,eAAA,YAAY,YAAY;CACxB,IAAI;EACF,MAAM,SAAS,GAAG,YAAY;EAC9B,IAAI,kBAAkB,SACpB,OAAO,OAAO,MACX,UAAU;GACT,eAAA,YAAY,QAAQ;GACpB,OAAO;EACT,IACC,UAAU;GACT,eAAA,YAAY,QAAQ;GACpB,MAAM;EACR,CACF;EAEF,eAAA,YAAY,QAAQ;EACpB,OAAO;CACT,SAAS,OAAO;EACd,eAAA,YAAY,QAAQ;EACpB,MAAM;CACR;AACF;AAIA,SAAgB,gBAAsB;CACpC,IAAI;CACJ,iBAAiB;EACf,gBAAgBA,eAAAA,YAAY;EAC5B,eAAA,YAAY,IAAID,+BAAAA,uBAAuB,CAAC;CAC1C,CAAC;CACD,gBAAgB;EACd,eAAA,YAAY,aAAa;CAC3B,CAAC;AACH;AAIA,SAAgB,iBACd,WACA,UACA,MACM;CACN,MAAM,WAAWC,eAAAA,YAAY;CAC7B,MAAM,MAAMC,eAAAA,mBAAmB,WAAW,IAAI;CAC9C,SAAS,eACP,WACA,KACA,UACA,IAAI,IAAI,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CACnC;AACF;AAEA,SAAgB,eACd,WACA,UACA,IACA,MACG;CACH,OAAO,uBAAuB;EAC5B,iBAAiB,WAAW,UAAU,IAAI;EAC1C,OAAO,GAAG;CACZ,CAAC;AACH;AA2BA,SAAgB,gBACd,WACA,SACiB;CACjB,MAAM,WAAW,IAAI,UAAU;CAI/B,IAAI,SAAS,QAAQ,MACnB,SAASC,+BAAAA,aAAa,EAAE,MAAM,QAAQ,KAAK,CAAC;CAG9C,IAAI,SAAS,SAAS;MAChB,oBAAoBC,eAAAA,OAAO;GAC7B,MAAM,eAAe,SAAS;GAC9B,IACE,OAAO,iBAAiB,YACxB,iBAAiB,QACjB,OAAO,QAAQ,UAAU,YACzB,QAAQ,UAAU,MAElB,SAAS,MAAM,QAAQ,KAAY;QAEnC,SAAS,KAAK,QAAQ,KAAY;EAEtC;;CAEF,IAAI,SAAS;OACN,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,QAAQ,OAAO,GACtD,IAAI,OAAO,SAAS,YAClB,SAAkB,OAAO;CAAA;CAM/B,IAAI,SAAS,QAAQ,MACnB,SAAkBC,+BAAAA,YAAY,oBAAoB,QAAQ,IAAI;CAGhE,OAAO;AACT;AAIA,SAAgB,cACd,WACA,OAGA,MACiB;CACjB,MAAM,WAAWC,eAAAA,OAAO,WAAW,EAAE,KAAK,CAAC;CAC3C,IAAI,oBAAoBF,eAAAA,OAAO;EAC7B,MAAM,eAAe,SAAS;EAC9B,IACE,OAAO,iBAAiB,YACxB,iBAAiB,QACjB,OAAO,UAAU,YACjB,UAAU,MAEV,SAAS,MAAM,KAAY;OAE3B,SAAS,KAAK,KAAY;CAE9B;CACA,OAAO;AACT;AAIA,SAAgB,eACd,WACA,YACA,MACA,MACiB;CACjB,MAAM,WAAWE,eAAAA,OAAO,WAAW,EAAE,KAAK,CAAC;CAC3C,SAAkB,cAAc;CAChC,OAAO;AACT;;;;;;;;;;AAaA,eAAsB,QAAuB;CAG3C,MAAM,QAAQ,QAAQ;CACtB,MAAM,QAAQ,QAAQ;AACxB"}
@@ -13,7 +13,7 @@ export interface CubitStubOptions<T extends StateContainerConstructor> {
13
13
  methods?: Partial<Record<MethodKeys<InstanceType<T>>, (...args: any[]) => any>>;
14
14
  /**
15
15
  * Args to pass to init(). If the bloc's Args type is not void, supplying
16
- * args here causes initConfig({ args }) to be called so init() runs.
16
+ * args here causes `initConfig({ args })` to be called so `init()` runs.
17
17
  */
18
18
  args?: ExtractArgs<T> extends void ? never : ExtractArgs<T>;
19
19
  /**
package/dist/testing.d.ts CHANGED
@@ -13,7 +13,7 @@ export interface CubitStubOptions<T extends StateContainerConstructor> {
13
13
  methods?: Partial<Record<MethodKeys<InstanceType<T>>, (...args: any[]) => any>>;
14
14
  /**
15
15
  * Args to pass to init(). If the bloc's Args type is not void, supplying
16
- * args here causes initConfig({ args }) to be called so init() runs.
16
+ * args here causes `initConfig({ args })` to be called so `init()` runs.
17
17
  */
18
18
  args?: ExtractArgs<T> extends void ? never : ExtractArgs<T>;
19
19
  /**