@blac/core 2.0.20 → 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 (283) 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 +55 -4
  12. package/dist/core/StateContainerRegistry.d.ts +94 -18
  13. package/dist/core/meta.d.ts +5 -11
  14. package/dist/core/symbols.d.ts +18 -1
  15. package/dist/decorators/blac.d.ts +28 -26
  16. package/dist/{ensure-B2DYCbbI.cjs → ensure-ORVw-WRE.cjs} +214 -39
  17. package/dist/ensure-ORVw-WRE.cjs.map +1 -0
  18. package/dist/{ensure-BSJHkUNX.js → ensure-k-6q7O25.js} +183 -38
  19. package/dist/ensure-k-6q7O25.js.map +1 -0
  20. package/dist/index.cjs +212 -32
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +4 -6
  23. package/dist/index.d.ts +4 -6
  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 +12 -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/static-props.d.ts +25 -1
  48. package/dist/utils/structural-key.d.ts +0 -7
  49. package/dist/watch/index.d.ts +1 -1
  50. package/dist/watch/watch.d.ts +22 -3
  51. package/package.json +17 -55
  52. package/dist/StateContainerRegistry-4Ljd9EJx.js +0 -1018
  53. package/dist/StateContainerRegistry-4Ljd9EJx.js.map +0 -1
  54. package/dist/StateContainerRegistry-8zNFMAf7.js +0 -1067
  55. package/dist/StateContainerRegistry-8zNFMAf7.js.map +0 -1
  56. package/dist/StateContainerRegistry-B1GYx9Xo.js +0 -975
  57. package/dist/StateContainerRegistry-B1GYx9Xo.js.map +0 -1
  58. package/dist/StateContainerRegistry-B_ViBLvJ.js +0 -975
  59. package/dist/StateContainerRegistry-B_ViBLvJ.js.map +0 -1
  60. package/dist/StateContainerRegistry-Bx2ly-ba.cjs +0 -1162
  61. package/dist/StateContainerRegistry-Bx2ly-ba.cjs.map +0 -1
  62. package/dist/StateContainerRegistry-C0dCNlTF.js +0 -1005
  63. package/dist/StateContainerRegistry-C0dCNlTF.js.map +0 -1
  64. package/dist/StateContainerRegistry-CEWpc1bj.cjs +0 -1113
  65. package/dist/StateContainerRegistry-CEWpc1bj.cjs.map +0 -1
  66. package/dist/StateContainerRegistry-CS18y4au.cjs +0 -1163
  67. package/dist/StateContainerRegistry-CS18y4au.cjs.map +0 -1
  68. package/dist/StateContainerRegistry-CSNm2Ws5.cjs.map +0 -1
  69. package/dist/StateContainerRegistry-CchRjnjX.cjs +0 -1094
  70. package/dist/StateContainerRegistry-CchRjnjX.cjs.map +0 -1
  71. package/dist/StateContainerRegistry-CvBOhAT2.js.map +0 -1
  72. package/dist/StateContainerRegistry-D8nu7Z0T.js +0 -1068
  73. package/dist/StateContainerRegistry-D8nu7Z0T.js.map +0 -1
  74. package/dist/StateContainerRegistry-D9BIRjwv.cjs +0 -1088
  75. package/dist/StateContainerRegistry-D9BIRjwv.cjs.map +0 -1
  76. package/dist/StateContainerRegistry-DJQqLrli.cjs +0 -1046
  77. package/dist/StateContainerRegistry-DJQqLrli.cjs.map +0 -1
  78. package/dist/StateContainerRegistry-DVIBTxGZ.js +0 -999
  79. package/dist/StateContainerRegistry-DVIBTxGZ.js.map +0 -1
  80. package/dist/StateContainerRegistry-Dj77cok9.js +0 -1005
  81. package/dist/StateContainerRegistry-Dj77cok9.js.map +0 -1
  82. package/dist/StateContainerRegistry-FWKzPfS-.cjs +0 -1046
  83. package/dist/StateContainerRegistry-FWKzPfS-.cjs.map +0 -1
  84. package/dist/StateContainerRegistry-lIwo-0m3.cjs +0 -1094
  85. package/dist/StateContainerRegistry-lIwo-0m3.cjs.map +0 -1
  86. package/dist/acquire-B1LYXSGj.js +0 -35
  87. package/dist/acquire-B1LYXSGj.js.map +0 -1
  88. package/dist/acquire-B1Wuqla6.cjs +0 -46
  89. package/dist/acquire-B1Wuqla6.cjs.map +0 -1
  90. package/dist/acquire-BJ3xUEmh.cjs +0 -46
  91. package/dist/acquire-BJ3xUEmh.cjs.map +0 -1
  92. package/dist/acquire-BP-ZfUH2.js +0 -35
  93. package/dist/acquire-BP-ZfUH2.js.map +0 -1
  94. package/dist/acquire-BRIJVJaA.js +0 -35
  95. package/dist/acquire-BRIJVJaA.js.map +0 -1
  96. package/dist/acquire-BZIs6tpN.cjs +0 -46
  97. package/dist/acquire-BZIs6tpN.cjs.map +0 -1
  98. package/dist/acquire-B_p1fNEh.cjs +0 -46
  99. package/dist/acquire-B_p1fNEh.cjs.map +0 -1
  100. package/dist/acquire-BdsVZBSe.js +0 -35
  101. package/dist/acquire-BdsVZBSe.js.map +0 -1
  102. package/dist/acquire-C3nOb3XR.cjs +0 -46
  103. package/dist/acquire-C3nOb3XR.cjs.map +0 -1
  104. package/dist/acquire-C4WAS13G.cjs +0 -46
  105. package/dist/acquire-C4WAS13G.cjs.map +0 -1
  106. package/dist/acquire-C7beGI55.cjs +0 -46
  107. package/dist/acquire-C7beGI55.cjs.map +0 -1
  108. package/dist/acquire-CbMz7gWs.cjs +0 -46
  109. package/dist/acquire-CbMz7gWs.cjs.map +0 -1
  110. package/dist/acquire-CwB083au.js +0 -35
  111. package/dist/acquire-CwB083au.js.map +0 -1
  112. package/dist/acquire-DHEOEVcj.cjs +0 -46
  113. package/dist/acquire-DHEOEVcj.cjs.map +0 -1
  114. package/dist/acquire-DXEQDZGr.js +0 -35
  115. package/dist/acquire-DXEQDZGr.js.map +0 -1
  116. package/dist/acquire-Dg-uXwjF.js +0 -35
  117. package/dist/acquire-Dg-uXwjF.js.map +0 -1
  118. package/dist/acquire-Don7499S.js +0 -35
  119. package/dist/acquire-Don7499S.js.map +0 -1
  120. package/dist/acquire-G8aB3GAx.js +0 -35
  121. package/dist/acquire-G8aB3GAx.js.map +0 -1
  122. package/dist/branded-BybA0_xw.cjs +0 -18
  123. package/dist/branded-BybA0_xw.cjs.map +0 -1
  124. package/dist/branded-DkbUzncc.js +0 -13
  125. package/dist/branded-DkbUzncc.js.map +0 -1
  126. package/dist/config-8pyOC5gv.js +0 -16
  127. package/dist/config-8pyOC5gv.js.map +0 -1
  128. package/dist/config-B1l8ZGlU.cjs +0 -32
  129. package/dist/config-B1l8ZGlU.cjs.map +0 -1
  130. package/dist/config-C7Ktkjh2.cjs +0 -32
  131. package/dist/config-C7Ktkjh2.cjs.map +0 -1
  132. package/dist/config-COT4Xkbc.js +0 -16
  133. package/dist/config-COT4Xkbc.js.map +0 -1
  134. package/dist/config-CzcoAvBo.js +0 -16
  135. package/dist/config-CzcoAvBo.js.map +0 -1
  136. package/dist/config-D6BoWWPv.js +0 -16
  137. package/dist/config-D6BoWWPv.js.map +0 -1
  138. package/dist/config-DOO82lbZ.js +0 -16
  139. package/dist/config-DOO82lbZ.js.map +0 -1
  140. package/dist/config-DVM_ESQt.js +0 -16
  141. package/dist/config-DVM_ESQt.js.map +0 -1
  142. package/dist/config-DVMnlHLv.cjs +0 -32
  143. package/dist/config-DVMnlHLv.cjs.map +0 -1
  144. package/dist/config-DWUq7zEQ.cjs +0 -32
  145. package/dist/config-DWUq7zEQ.cjs.map +0 -1
  146. package/dist/config-DXI3kzmA.js +0 -16
  147. package/dist/config-DXI3kzmA.js.map +0 -1
  148. package/dist/config-Dk1FxfXj.js +0 -16
  149. package/dist/config-Dk1FxfXj.js.map +0 -1
  150. package/dist/config-b2z51Y_C.cjs +0 -32
  151. package/dist/config-b2z51Y_C.cjs.map +0 -1
  152. package/dist/config-buwpT7l3.js +0 -16
  153. package/dist/config-buwpT7l3.js.map +0 -1
  154. package/dist/config-cwaTn5oy.cjs +0 -32
  155. package/dist/config-cwaTn5oy.cjs.map +0 -1
  156. package/dist/config-d9_U58ef.cjs +0 -32
  157. package/dist/config-d9_U58ef.cjs.map +0 -1
  158. package/dist/config-hWTayX76.cjs +0 -32
  159. package/dist/config-hWTayX76.cjs.map +0 -1
  160. package/dist/config-sCPKPYxZ.cjs +0 -32
  161. package/dist/config-sCPKPYxZ.cjs.map +0 -1
  162. package/dist/debug.cjs +0 -14
  163. package/dist/debug.d.cts +0 -34
  164. package/dist/debug.d.ts +0 -34
  165. package/dist/debug.js +0 -4
  166. package/dist/ensure-B1dEDsZV.cjs +0 -559
  167. package/dist/ensure-B1dEDsZV.cjs.map +0 -1
  168. package/dist/ensure-B2DYCbbI.cjs.map +0 -1
  169. package/dist/ensure-B59ri8oo.js +0 -555
  170. package/dist/ensure-B59ri8oo.js.map +0 -1
  171. package/dist/ensure-B9oOqpOB.cjs +0 -558
  172. package/dist/ensure-B9oOqpOB.cjs.map +0 -1
  173. package/dist/ensure-BKdq5xsY.cjs +0 -578
  174. package/dist/ensure-BKdq5xsY.cjs.map +0 -1
  175. package/dist/ensure-BSJHkUNX.js.map +0 -1
  176. package/dist/ensure-BfYnvAsI.js +0 -709
  177. package/dist/ensure-BfYnvAsI.js.map +0 -1
  178. package/dist/ensure-Bm91o37h.js +0 -536
  179. package/dist/ensure-Bm91o37h.js.map +0 -1
  180. package/dist/ensure-C1a8IFWy.js +0 -535
  181. package/dist/ensure-C1a8IFWy.js.map +0 -1
  182. package/dist/ensure-CLQEx0Nn.cjs +0 -528
  183. package/dist/ensure-CLQEx0Nn.cjs.map +0 -1
  184. package/dist/ensure-CPMiGvov.js +0 -493
  185. package/dist/ensure-CPMiGvov.js.map +0 -1
  186. package/dist/ensure-CQimeD0I.cjs +0 -559
  187. package/dist/ensure-CQimeD0I.cjs.map +0 -1
  188. package/dist/ensure-DEXSmA-N.js +0 -526
  189. package/dist/ensure-DEXSmA-N.js.map +0 -1
  190. package/dist/ensure-DTTTd2h8.js +0 -536
  191. package/dist/ensure-DTTTd2h8.js.map +0 -1
  192. package/dist/ensure-Dx86f80B.cjs +0 -567
  193. package/dist/ensure-Dx86f80B.cjs.map +0 -1
  194. package/dist/ensure-DzqGej43.js +0 -526
  195. package/dist/ensure-DzqGej43.js.map +0 -1
  196. package/dist/ensure-VuiNPccZ.cjs +0 -559
  197. package/dist/ensure-VuiNPccZ.cjs.map +0 -1
  198. package/dist/ensure-_Oh8XPm4.cjs +0 -732
  199. package/dist/ensure-_Oh8XPm4.cjs.map +0 -1
  200. package/dist/ensure-fsQMjaQL.cjs +0 -567
  201. package/dist/ensure-fsQMjaQL.cjs.map +0 -1
  202. package/dist/ensure-vxAGa2v3.js +0 -536
  203. package/dist/ensure-vxAGa2v3.js.map +0 -1
  204. package/dist/management-14vsHr4t.cjs +0 -85
  205. package/dist/management-14vsHr4t.cjs.map +0 -1
  206. package/dist/management-444tdCBG.js +0 -38
  207. package/dist/management-444tdCBG.js.map +0 -1
  208. package/dist/management-7jLjVHJd.js +0 -38
  209. package/dist/management-7jLjVHJd.js.map +0 -1
  210. package/dist/management-AFuqQEwz.js +0 -38
  211. package/dist/management-AFuqQEwz.js.map +0 -1
  212. package/dist/management-BodVLAfJ.cjs +0 -85
  213. package/dist/management-BodVLAfJ.cjs.map +0 -1
  214. package/dist/management-BqGBdL01.js +0 -38
  215. package/dist/management-BqGBdL01.js.map +0 -1
  216. package/dist/management-BvUAjril.js +0 -38
  217. package/dist/management-BvUAjril.js.map +0 -1
  218. package/dist/management-C-WGcwPD.js +0 -38
  219. package/dist/management-C-WGcwPD.js.map +0 -1
  220. package/dist/management-C2wv4vGS.cjs +0 -85
  221. package/dist/management-C2wv4vGS.cjs.map +0 -1
  222. package/dist/management-D2pjr38g.js +0 -38
  223. package/dist/management-D2pjr38g.js.map +0 -1
  224. package/dist/management-D5WsZoTV.js +0 -38
  225. package/dist/management-D5WsZoTV.js.map +0 -1
  226. package/dist/management-DAV9sN-k.cjs +0 -85
  227. package/dist/management-DAV9sN-k.cjs.map +0 -1
  228. package/dist/management-Df7Y2sF1.cjs +0 -85
  229. package/dist/management-Df7Y2sF1.cjs.map +0 -1
  230. package/dist/management-DwO0nBzL.cjs +0 -85
  231. package/dist/management-DwO0nBzL.cjs.map +0 -1
  232. package/dist/management-GcnNJjnm.cjs +0 -85
  233. package/dist/management-GcnNJjnm.cjs.map +0 -1
  234. package/dist/management-P_Vi_cvw.js +0 -38
  235. package/dist/management-P_Vi_cvw.js.map +0 -1
  236. package/dist/management-QWLpbXlW.cjs +0 -85
  237. package/dist/management-QWLpbXlW.cjs.map +0 -1
  238. package/dist/management-iE4x6IQY.cjs +0 -85
  239. package/dist/management-iE4x6IQY.cjs.map +0 -1
  240. package/dist/types.cjs +0 -3
  241. package/dist/types.d.cts +0 -8
  242. package/dist/types.d.ts +0 -8
  243. package/dist/types.js +0 -2
  244. package/dist/watch-B0pUfh-v.cjs +0 -119
  245. package/dist/watch-B0pUfh-v.cjs.map +0 -1
  246. package/dist/watch-B91opmva.cjs +0 -70
  247. package/dist/watch-B91opmva.cjs.map +0 -1
  248. package/dist/watch-BHfCtbn1.cjs +0 -119
  249. package/dist/watch-BHfCtbn1.cjs.map +0 -1
  250. package/dist/watch-BmC-Tyqg.js +0 -59
  251. package/dist/watch-BmC-Tyqg.js.map +0 -1
  252. package/dist/watch-Bx0y-Kcc.js +0 -108
  253. package/dist/watch-Bx0y-Kcc.js.map +0 -1
  254. package/dist/watch-CKWt788i.cjs +0 -70
  255. package/dist/watch-CKWt788i.cjs.map +0 -1
  256. package/dist/watch-CfrSSrRY.js +0 -59
  257. package/dist/watch-CfrSSrRY.js.map +0 -1
  258. package/dist/watch-Cnh3PtQA.cjs +0 -70
  259. package/dist/watch-Cnh3PtQA.cjs.map +0 -1
  260. package/dist/watch-D01MdwKy.js +0 -108
  261. package/dist/watch-D01MdwKy.js.map +0 -1
  262. package/dist/watch-D8hwbFU1.js +0 -59
  263. package/dist/watch-D8hwbFU1.js.map +0 -1
  264. package/dist/watch-DPVUwPUY.js +0 -59
  265. package/dist/watch-DPVUwPUY.js.map +0 -1
  266. package/dist/watch-DVmvZBMD.cjs +0 -70
  267. package/dist/watch-DVmvZBMD.cjs.map +0 -1
  268. package/dist/watch-DkDGmGAx.cjs +0 -119
  269. package/dist/watch-DkDGmGAx.cjs.map +0 -1
  270. package/dist/watch-DttHqefn.js +0 -108
  271. package/dist/watch-DttHqefn.js.map +0 -1
  272. package/dist/watch-DxdIZjpt.cjs +0 -119
  273. package/dist/watch-DxdIZjpt.cjs.map +0 -1
  274. package/dist/watch-DztcJuUy.cjs +0 -119
  275. package/dist/watch-DztcJuUy.cjs.map +0 -1
  276. package/dist/watch-IrR8G28N.js +0 -108
  277. package/dist/watch-IrR8G28N.js.map +0 -1
  278. package/dist/watch-entry.cjs +0 -4
  279. package/dist/watch-entry.d.cts +0 -24
  280. package/dist/watch-entry.d.ts +0 -24
  281. package/dist/watch-entry.js +0 -2
  282. package/dist/watch-hx0r92Zc.js +0 -108
  283. package/dist/watch-hx0r92Zc.js.map +0 -1
@@ -1,4 +1,3 @@
1
- import { ALL_PATHS } from "@dirtytalk/structural";
2
1
  //#region src/config.ts
3
2
  /**
4
3
  * Default equality: shallow per-key `Object.is` comparison.
@@ -19,13 +18,13 @@ const shallowEqualState = (prev, next) => {
19
18
  };
20
19
  const defaultConfig = {
21
20
  equality: shallowEqualState,
22
- maxInstancesPerType: 1e3,
23
- maxRefsPerInstance: 1e3,
24
- maxEmitsPerSecond: 100
21
+ maxInstancesPerType: 1e5,
22
+ maxRefsPerInstance: 1e5,
23
+ maxEmitsPerSecond: 1e3
25
24
  };
26
25
  let globalConfig = { ...defaultConfig };
27
26
  /**
28
- * Configure global defaults for @blac/core.
27
+ * Configure global defaults for `@blac/core`.
29
28
  *
30
29
  * @example
31
30
  * ```ts
@@ -56,322 +55,6 @@ function resetBlacConfig() {
56
55
  globalConfig = { ...defaultConfig };
57
56
  }
58
57
  //#endregion
59
- //#region src/plugin/PluginManager.ts
60
- /**
61
- * Manages plugin lifecycle for the BlaC state management system.
62
- *
63
- * The manager hooks into two surfaces:
64
- *
65
- * 1. **Registry lifecycle events** (`created`, `disposed`, `refAcquired`,
66
- * `refReleased`, `depsChanged`) — synchronous, fired by the registry.
67
- *
68
- * 2. **Per-container channel flushes** — microtask-coalesced. For each
69
- * container, the manager subscribes once at create-time with
70
- * `ALL_PATHS` interest and stashes `prevState`. On every flush it
71
- * captures the new state + the changed `PathSet` and dispatches
72
- * `onStateChange(ctx, prev, next, paths)` to every enabled plugin.
73
- *
74
- * The `ALL_PATHS` subscription cost: plugins counted as a consumer with
75
- * `ALL_PATHS` interest will defeat the single-consumer-skip optimization
76
- * in `StructuralContainer`. This is the intended trade-off — devtools /
77
- * persist plugins genuinely want every change, and stateful plugins
78
- * (logging) can decode `paths` via `container.interner.lookup(id)` to log
79
- * only relevant fields. Plugins that want low overhead should remain
80
- * uninstalled or environment-gated.
81
- *
82
- * @example
83
- * ```ts
84
- * const manager = createPluginManager(registry);
85
- * manager.install(myPlugin, { environment: 'development' });
86
- * ```
87
- */
88
- var PluginManager = class {
89
- /**
90
- * Create a new PluginManager
91
- * @param registry - The StateContainerRegistry to monitor for lifecycle events
92
- */
93
- constructor(registry) {
94
- this.plugins = /* @__PURE__ */ new Map();
95
- this.lifecycleUnsubscribers = [];
96
- this.containerBridges = /* @__PURE__ */ new WeakMap();
97
- this.registry = registry;
98
- this.setupLifecycleHooks();
99
- }
100
- /**
101
- * Install a plugin with optional configuration
102
- * @param plugin - The plugin to install
103
- * @param config - Optional plugin configuration
104
- * @throws Error if plugin is already installed
105
- */
106
- install(plugin, config = {}) {
107
- const effectiveConfig = {
108
- enabled: true,
109
- environment: "all",
110
- ...config
111
- };
112
- if (!this.shouldEnablePlugin(effectiveConfig)) {
113
- console.log(`[BlaC] Plugin "${plugin.name}" skipped (environment mismatch)`);
114
- return;
115
- }
116
- if (this.plugins.has(plugin.name)) throw new Error(`Plugin "${plugin.name}" is already installed`);
117
- const installContext = this.buildContext(void 0);
118
- this.plugins.set(plugin.name, {
119
- plugin,
120
- config: effectiveConfig,
121
- installContext
122
- });
123
- if (plugin.onInstall) try {
124
- plugin.onInstall(installContext);
125
- } catch (error) {
126
- console.error(`[BlaC] Error installing plugin "${plugin.name}":`, error);
127
- this.plugins.delete(plugin.name);
128
- throw error;
129
- }
130
- console.log(`[BlaC] Plugin "${plugin.name}" v${plugin.version} installed`);
131
- }
132
- /**
133
- * Uninstall a plugin by name
134
- * @param pluginName - The name of the plugin to uninstall
135
- * @throws Error if plugin is not installed
136
- */
137
- uninstall(pluginName) {
138
- const installed = this.plugins.get(pluginName);
139
- if (!installed) throw new Error(`Plugin "${pluginName}" is not installed`);
140
- if (installed.plugin.onUninstall) try {
141
- installed.plugin.onUninstall();
142
- } catch (error) {
143
- console.error(`[BlaC] Error uninstalling plugin "${pluginName}":`, error);
144
- }
145
- this.plugins.delete(pluginName);
146
- console.log(`[BlaC] Plugin "${pluginName}" uninstalled`);
147
- }
148
- /**
149
- * Get an installed plugin by name
150
- * @param pluginName - The name of the plugin to retrieve
151
- * @returns The plugin instance or undefined if not found
152
- */
153
- getPlugin(pluginName) {
154
- return this.plugins.get(pluginName)?.plugin;
155
- }
156
- /**
157
- * Get all installed plugins
158
- * @returns Array of all installed plugins
159
- */
160
- getAllPlugins() {
161
- return Array.from(this.plugins.values()).map((p) => p.plugin);
162
- }
163
- /**
164
- * Check if a plugin is installed
165
- * @param pluginName - The name of the plugin to check
166
- * @returns true if the plugin is installed
167
- */
168
- hasPlugin(pluginName) {
169
- return this.plugins.has(pluginName);
170
- }
171
- /**
172
- * Uninstall all plugins
173
- */
174
- clear() {
175
- for (const name of this.plugins.keys()) this.uninstall(name);
176
- }
177
- destroy() {
178
- this.clear();
179
- for (const unsub of this.lifecycleUnsubscribers) unsub();
180
- this.lifecycleUnsubscribers = [];
181
- }
182
- /**
183
- * Wire registry lifecycle events into plugin dispatch.
184
- *
185
- * `onStateChange` is NOT wired through `registry.on('stateChanged', …)` —
186
- * that event lacks the `PathSet` payload. Instead, on each `created` we
187
- * subscribe to the container's channel directly so we get
188
- * `(paths)` and capture `(prev, next)` via the per-container snapshot.
189
- */
190
- setupLifecycleHooks() {
191
- this.lifecycleUnsubscribers = [
192
- this.registry.on("created", (instance) => {
193
- this.attachStateBridge(instance);
194
- this.notifyPlugins("onCreated", instance);
195
- }),
196
- this.registry.on("disposed", (instance) => {
197
- this.notifyPlugins("onDestroyed", instance);
198
- this.detachStateBridge(instance);
199
- }),
200
- this.registry.on("refAcquired", (instance, refId) => {
201
- this.notifyPlugins("onRefAcquired", instance, refId);
202
- }),
203
- this.registry.on("refReleased", (instance, refId) => {
204
- this.notifyPlugins("onRefReleased", instance, refId);
205
- }),
206
- this.registry.on("depsChanged", (instance, previousDeps, currentDeps) => {
207
- this.notifyPlugins("onDepsChanged", instance, previousDeps, currentDeps);
208
- })
209
- ];
210
- }
211
- /**
212
- * Subscribe to the container's channel with `ALL_PATHS` interest, so we
213
- * fire on every flush. Capture `prev` from the snapshot taken on the
214
- * previous flush (or at create-time for the first flush) and pass the
215
- * channel's `paths` argument straight through to plugins.
216
- *
217
- * Per-container bookkeeping is stored in a `WeakMap` keyed by the
218
- * container itself, so a disposed/GC'd container drops cleanly.
219
- */
220
- attachStateBridge(container) {
221
- if (this.containerBridges.has(container)) return;
222
- const bridge = {
223
- unsub: () => {},
224
- prevState: container.state
225
- };
226
- this.containerBridges.set(container, bridge);
227
- bridge.unsub = container.channel.subscribe(() => ALL_PATHS, (paths) => this.dispatchStateChange(container, paths));
228
- }
229
- detachStateBridge(container) {
230
- const bridge = this.containerBridges.get(container);
231
- if (!bridge) return;
232
- bridge.unsub();
233
- this.containerBridges.delete(container);
234
- }
235
- /**
236
- * Channel-flush callback. Snapshots `next`, hands `(prev, next, paths)`
237
- * to every enabled plugin's `onStateChange`, then updates `prevState`
238
- * for the next flush.
239
- *
240
- * Note: `prev` is captured once and reused across plugins — every
241
- * plugin sees the same `prev`/`next`/`paths` regardless of dispatch
242
- * order, matching the "snapshot prev once per flush" invariant.
243
- */
244
- dispatchStateChange(container, paths) {
245
- const bridge = this.containerBridges.get(container);
246
- if (!bridge) return;
247
- const prev = bridge.prevState;
248
- const next = container.state;
249
- bridge.prevState = next;
250
- for (const { plugin, config } of this.plugins.values()) {
251
- if (!config.enabled) continue;
252
- const hook = plugin.onStateChange;
253
- if (typeof hook !== "function") continue;
254
- try {
255
- hook.call(plugin, this.buildContext(container), prev, next, paths);
256
- } catch (error) {
257
- console.error(`[BlaC] Error in plugin "${plugin.name}" onStateChange:`, error);
258
- }
259
- }
260
- }
261
- /**
262
- * Notify all plugins of a lifecycle event.
263
- *
264
- * Builds a fresh `PluginContext` per dispatch (instance becomes
265
- * `ctx.container`), so plugins can reach the focal bloc through
266
- * `ctx.container`.
267
- */
268
- notifyPlugins(hookName, instance, ...extraArgs) {
269
- for (const { plugin, config } of this.plugins.values()) {
270
- if (!config.enabled) continue;
271
- const hook = plugin[hookName];
272
- if (typeof hook !== "function") continue;
273
- try {
274
- hook.call(plugin, this.buildContext(instance), ...extraArgs);
275
- } catch (error) {
276
- console.error(`[BlaC] Error in plugin "${plugin.name}" ${hookName}:`, error);
277
- }
278
- }
279
- }
280
- /**
281
- * Build a `PluginContext` for a given focal container. The context is
282
- * cheap to build — its methods close over `this.registry` directly —
283
- * so we create one per dispatch rather than caching per container.
284
- * This sidesteps the lifetime question of "when do we evict a cached
285
- * context?" and keeps `paths`-vs-`container` lifetimes cleanly
286
- * separated.
287
- */
288
- buildContext(container) {
289
- const registry = this.registry;
290
- return {
291
- container,
292
- getInstanceMetadata: (instance) => {
293
- return {
294
- id: instance.$blac.id,
295
- className: instance.constructor.name,
296
- isDisposed: instance.$blac.disposed,
297
- name: instance.$blac.name,
298
- createdAt: instance.$blac.createdAt,
299
- state: instance.state,
300
- hydrationStatus: instance.$blac.hydration.status,
301
- isHydrated: instance.$blac.hydration.isHydrated,
302
- hydrationError: instance.$blac.hydration.error,
303
- changedWhileHydrating: instance.$blac.hydration.changedWhileHydrating,
304
- args: instance.args
305
- };
306
- },
307
- getState: (instance) => {
308
- return instance.state;
309
- },
310
- getHydrationStatus: (instance) => {
311
- return instance.$blac.hydration.status;
312
- },
313
- startHydration: (instance) => {
314
- instance.$blac.hydration.begin();
315
- },
316
- applyHydratedState: (instance, state) => {
317
- return instance.$blac.hydration.apply(state);
318
- },
319
- finishHydration: (instance) => {
320
- instance.$blac.hydration.finish();
321
- },
322
- failHydration: (instance, error) => {
323
- instance.$blac.hydration.fail(error);
324
- },
325
- waitForHydration: (instance) => {
326
- return instance.$blac.hydration.wait();
327
- },
328
- queryInstances: (typeClass) => {
329
- return registry.getAll(typeClass);
330
- },
331
- getAllTypes: () => {
332
- return registry.getTypes();
333
- },
334
- getStats: () => {
335
- return registry.getStats();
336
- },
337
- getRefIds: (instanceId) => {
338
- for (const Type of registry.getTypes()) {
339
- const map = registry.getInstancesMap(Type);
340
- for (const [, entry] of map) if (entry.instance.$blac.id === instanceId) return Array.from(entry.refs.keys());
341
- }
342
- return [];
343
- }
344
- };
345
- }
346
- /**
347
- * Check if plugin should be enabled based on environment
348
- */
349
- shouldEnablePlugin(config) {
350
- if (!config.enabled) return false;
351
- if (config.environment === "all") return true;
352
- return this.getCurrentEnvironment() === config.environment;
353
- }
354
- /**
355
- * Get current environment
356
- */
357
- getCurrentEnvironment() {
358
- if (typeof process !== "undefined") {
359
- if (process.env.NODE_ENV === "test") return "test";
360
- if (process.env.NODE_ENV === "production") return "production";
361
- return "development";
362
- }
363
- return "development";
364
- }
365
- };
366
- /**
367
- * Create a plugin manager instance
368
- * @param registry - The StateContainerRegistry to monitor for lifecycle events
369
- * @returns A new PluginManager instance
370
- */
371
- function createPluginManager(registry) {
372
- return new PluginManager(registry);
373
- }
374
- //#endregion
375
58
  //#region src/constants.ts
376
59
  /**
377
60
  * Default configuration constants for BlaC
@@ -379,19 +62,6 @@ function createPluginManager(registry) {
379
62
  * Centralized location for all magic numbers and default values.
380
63
  */
381
64
  /**
382
- * Default configuration constants for BlaC
383
- */
384
- const BLAC_DEFAULTS = {
385
- /**
386
- * Default instance key for shared instances
387
- */
388
- DEFAULT_INSTANCE_KEY: "default",
389
- /**
390
- * Maximum getter nesting depth (prevents infinite recursion)
391
- */
392
- MAX_GETTER_DEPTH: 10
393
- };
394
- /**
395
65
  * Static property names for StateContainer classes
396
66
  * Used for feature flags and configuration on bloc classes
397
67
  */
@@ -416,19 +86,33 @@ const BLAC_STATIC_PROPS = {
416
86
  * instead of using a structural hash of `args`. Allows blocs to ignore
417
87
  * non-identity fields (e.g., `readonly` flags) while still keying by id.
418
88
  */
419
- KEY: "key"
89
+ KEY: "key",
90
+ /**
91
+ * Explicit, minification-safe identity for a bloc class: `static blacName = '...'`.
92
+ * When unset, identity falls back to `constructor.name`.
93
+ */
94
+ BLAC_NAME: "blacName"
420
95
  };
421
96
  /**
422
97
  * Standard error message prefix
423
98
  */
424
99
  const BLAC_ERROR_PREFIX = "[BlaC]";
100
+ /**
101
+ * True outside production builds.
102
+ *
103
+ * Reads `process.env.NODE_ENV` defensively: plain ESM in the browser, Deno and
104
+ * some test runners have no `process` binding, where a bare read throws.
105
+ * Bundlers that define `process.env.NODE_ENV` still constant-fold this.
106
+ */
107
+ const IS_DEV = typeof process === "undefined" || process.env?.NODE_ENV !== "production";
425
108
  //#endregion
426
109
  //#region src/utils/static-props.ts
427
110
  /**
428
111
  * Utility functions for accessing static properties on StateContainer classes
429
112
  */
430
113
  /**
431
- * Get a static property from a class constructor
114
+ * Get a static property from a class constructor, walking the prototype
115
+ * chain so subclasses inherit the value unless they define their own.
432
116
  * Type-safe helper that avoids (Type as any) casts
433
117
  *
434
118
  * @param Type - The class constructor
@@ -440,8 +124,20 @@ function getStaticProp(Type, propName, defaultValue) {
440
124
  return Type[propName] ?? defaultValue;
441
125
  }
442
126
  /**
127
+ * Get a static property declared directly on `Type`, ignoring the prototype
128
+ * chain, so subclasses never inherit a base class's value.
129
+ *
130
+ * @param Type - The class constructor
131
+ * @param propName - The property name to access
132
+ * @returns The own property value or `undefined`
133
+ */
134
+ function getOwnStaticProp(Type, propName) {
135
+ return Object.hasOwn(Type, propName) ? Type[propName] : void 0;
136
+ }
137
+ /**
443
138
  * Check if a class is marked as keepAlive.
444
139
  * KeepAlive classes are never auto-disposed when ref count reaches 0.
140
+ * Inherited: a subclass keeps a base class's `keepAlive` unless overridden.
445
141
  * @param Type - The class constructor to check
446
142
  * @returns true if the class has `static keepAlive = true`
447
143
  */
@@ -451,6 +147,7 @@ function isKeepAliveClass(Type) {
451
147
  /**
452
148
  * Check if a class should be excluded from DevTools.
453
149
  * Used to prevent infinite loops when DevTools tracks itself.
150
+ * Inherited: a subclass keeps a base class's exclusion unless overridden.
454
151
  * @param Type - The class constructor to check
455
152
  * @returns true if the class has `static __excludeFromDevTools = true`
456
153
  */
@@ -459,6 +156,7 @@ function isExcludedFromDevTools(Type) {
459
156
  }
460
157
  /**
461
158
  * Get the per-class equality function set via `@blac({ equality })`, if any.
159
+ * Inherited: a subclass keeps a base class's equality fn unless overridden.
462
160
  * @param Type - The class constructor to check
463
161
  * @returns the equality function or `undefined`
464
162
  */
@@ -467,13 +165,26 @@ function getClassEquality(Type) {
467
165
  return typeof fn === "function" ? fn : void 0;
468
166
  }
469
167
  /**
168
+ * Get a class's minification-safe identity name.
169
+ * Own only: a subclass never inherits a base class's explicit `blacName`,
170
+ * since that would collapse two distinct blocs onto one identity.
171
+ * Falls back to `Type.name` (the runtime `constructor.name`) when unset.
172
+ * @param Type - The class constructor to check
173
+ * @returns the explicit `static blacName` or `Type.name`
174
+ */
175
+ function getBlacName(Type) {
176
+ return getOwnStaticProp(Type, BLAC_STATIC_PROPS.BLAC_NAME) ?? Type.name;
177
+ }
178
+ /**
470
179
  * Get the per-class key function set via `@blac({ key })` or `static key = …`, if any.
471
180
  * Used by the registry to derive a stable instance key from `args`.
181
+ * Own only: a subclass never inherits a base class's `key`, since a
182
+ * different `Args` shape would derive the wrong instance key.
472
183
  * @param Type - The class constructor to check
473
184
  * @returns the key function or `undefined`
474
185
  */
475
186
  function getClassKey(Type) {
476
- const fn = getStaticProp(Type, BLAC_STATIC_PROPS.KEY, void 0);
187
+ const fn = getOwnStaticProp(Type, BLAC_STATIC_PROPS.KEY);
477
188
  return typeof fn === "function" ? fn : void 0;
478
189
  }
479
190
  //#endregion
@@ -490,11 +201,24 @@ const DEFAULT_STRUCTURAL_KEY = "default";
490
201
  * - Object keys are sorted for order-independence.
491
202
  * - Arrays keep their insertion order.
492
203
  * - Functions throw — they must be passed via `deps`, not `args`.
204
+ * - Object args are memoized by identity; callers on hot paths (`resolveKey`,
205
+ * `depend`) re-resolve the same args object on every call.
493
206
  */
207
+ const keyCache = /* @__PURE__ */ new WeakMap();
494
208
  function structuralKey(args) {
495
209
  if (args === void 0 || args === null) return DEFAULT_STRUCTURAL_KEY;
210
+ if (typeof args === "object") {
211
+ const cached = keyCache.get(args);
212
+ if (cached !== void 0) return cached;
213
+ const key = serialize(args);
214
+ keyCache.set(args, key);
215
+ return key;
216
+ }
217
+ return serialize(args);
218
+ }
219
+ function serialize(args) {
496
220
  return JSON.stringify(args, function(_k, v) {
497
- if (typeof v === "function") throw new Error("[blac] args must be serializable; put refs/callbacks in `deps`");
221
+ if (typeof v === "function") throw new Error(`[blac] args must be serializable; put refs/callbacks in \`deps\` (found a function at key "${_k || "(root)"}")`);
498
222
  if (v && typeof v === "object" && !Array.isArray(v)) return Object.keys(v).sort().reduce((o, k) => {
499
223
  o[k] = v[k];
500
224
  return o;
@@ -506,7 +230,7 @@ function structuralKey(args) {
506
230
  //#region src/core/symbols.ts
507
231
  /**
508
232
  * @internal Apply one owner's (consumer's) deps slice to an instance.
509
- * Called by framework adapters (e.g. @blac/react useBloc) — not public API.
233
+ * Called by framework adapters (e.g. `@blac/react` useBloc) — not public API.
510
234
  */
511
235
  const APPLY_DEPS = Symbol("blac.applyDeps");
512
236
  /**
@@ -515,6 +239,12 @@ const APPLY_DEPS = Symbol("blac.applyDeps");
515
239
  */
516
240
  const REMOVE_DEPS_OWNER = Symbol("blac.removeDepsOwner");
517
241
  /**
242
+ * @internal Subscribe to this specific instance's own disposal, bypassing
243
+ * the global registry `'disposed'` event. Called by `watch()` — not public
244
+ * API.
245
+ */
246
+ const ON_DISPOSE = Symbol("blac.onDispose");
247
+ /**
518
248
  * @internal Symbol-keyed configuration entry point on `StateContainer`.
519
249
  * Writes the instance's identity fields (`_name`/`_debug`/`_instanceId`),
520
250
  * resolves per-class equality, emits the registry `created` event, and runs
@@ -524,8 +254,27 @@ const REMOVE_DEPS_OWNER = Symbol("blac.removeDepsOwner");
524
254
  * helpers are the sole callers, mirroring the `APPLY_DEPS` precedent.
525
255
  */
526
256
  const INIT_CONFIG = Symbol("blac.initConfig");
257
+ /**
258
+ * @internal Run `fn` with `state` temporarily reporting a render's tracking
259
+ * proxy, so getters record the paths they read. Called by `@blac/react`'s
260
+ * `buildTrackedProxy` — not public API.
261
+ *
262
+ * Replaces the old `this`-Proxy receiver: getters now run with `this` bound to
263
+ * the real instance, so ES `#private` works in user blocs.
264
+ */
265
+ const WITH_TRACKED_STATE = Symbol("blac.withTrackedState");
266
+ /**
267
+ * @internal Drive the activation lifecycle from the registry's 0↔1 ownership
268
+ * transition. Owns the per-instance AbortController so `onActivate`'s signal
269
+ * aborts on the matching deactivation and on dispose.
270
+ *
271
+ * Framework-only — `StateContainerRegistry` is the sole caller.
272
+ */
273
+ const SET_ACTIVE = Symbol("blac.setActive");
527
274
  //#endregion
528
275
  //#region src/core/StateContainerRegistry.ts
276
+ /** Shared, read-only Map returned by `getInstancesMap` for unregistered types. */
277
+ const EMPTY_INSTANCES_MAP = /* @__PURE__ */ new Map();
529
278
  /**
530
279
  * Central registry for managing StateContainer instances.
531
280
  * Handles instance lifecycle, named ref tracking, and lifecycle event emission.
@@ -539,19 +288,113 @@ const INIT_CONFIG = Symbol("blac.initConfig");
539
288
  * console.log('State changed:', prev, '->', next);
540
289
  * });
541
290
  * ```
291
+ *
292
+ * `stateChanged` fires once per emit (microtask-deferred but uncoalesced), so
293
+ * listeners see every intermediate transition. Plugins' `onStateChange` fires
294
+ * once per channel flush with coalesced `prev`/`next` and a `PathSet`. Pick
295
+ * the transition log for devtools/time-travel, the plugin hook for
296
+ * change-driven work.
542
297
  */
543
298
  var StateContainerRegistry = class {
299
+ get hasStateChangedListeners() {
300
+ return this._stateChangedListenerCount > 0;
301
+ }
544
302
  constructor() {
545
303
  this.instancesByConstructor = /* @__PURE__ */ new WeakMap();
546
304
  this.types = /* @__PURE__ */ new Set();
547
- this.registeredTypeNames = /* @__PURE__ */ new Set();
305
+ this._dependentEdges = /* @__PURE__ */ new WeakMap();
306
+ this._entryByInstance = /* @__PURE__ */ new WeakMap();
307
+ this._entryById = /* @__PURE__ */ new Map();
548
308
  this.listeners = /* @__PURE__ */ new Map();
549
309
  this._stateChangedListenerCount = 0;
550
310
  this._pendingStateChanges = null;
551
311
  this._autoRefIdCounter = 0;
312
+ this.on("disposed", (container) => this._handleDisposed(container));
552
313
  }
553
- get hasStateChangedListeners() {
554
- return this._stateChangedListenerCount > 0;
314
+ /**
315
+ * Registry-internal reaction to any instance's `disposed` lifecycle event.
316
+ * If the disposing container is itself tracked in this registry (i.e. it
317
+ * was `acquire()`/`ensure()`'d, not a bare `new`), prune its Map entry and
318
+ * release its dependent edges on whatever it `depend()`'d on.
319
+ */
320
+ _handleDisposed(container) {
321
+ const Type = container.constructor;
322
+ if (!this._pruneEntry(Type, container)) return;
323
+ const edges = this._dependentEdges.get(container);
324
+ if (!edges) return;
325
+ this._dependentEdges.delete(container);
326
+ for (const [DepType, keys] of edges) for (const depKey of keys) this._releaseDependent(DepType, depKey, container);
327
+ }
328
+ /**
329
+ * An entry may be disposed only when nothing owns it: no public refs, no
330
+ * `depend()` dependents, and not keepAlive. Every dispose decision goes
331
+ * through here so `release()` and `_releaseDependent` cannot disagree.
332
+ */
333
+ _isUnowned(Type, entry) {
334
+ return this._hasNoOwners(entry) && !isKeepAliveClass(Type);
335
+ }
336
+ /**
337
+ * Pure ownership: refs plus `depend()` dependents, with no keepAlive term.
338
+ * `_isUnowned` is "may be disposed"; this is "nothing is using it", which is
339
+ * what the activation lifecycle keys off — a keepAlive instance still
340
+ * deactivates when its last owner goes.
341
+ */
342
+ _hasNoOwners(entry) {
343
+ return entry.refs.size === 0 && (entry.dependents === void 0 || entry.dependents.size === 0);
344
+ }
345
+ /**
346
+ * Single funnel for the 0↔1 ownership transition. `SET_ACTIVE` is idempotent,
347
+ * so every acquire/release path can call this unconditionally rather than
348
+ * each computing the edge itself. Emits `activated`/`deactivated` for
349
+ * plugins to observe the same transition `onActivate`/`onDeactivate` expose
350
+ * to bloc authors — after `SET_ACTIVE` has already run the instance's own
351
+ * hook, so plugins always see a container that has finished reacting.
352
+ */
353
+ _syncActivation(entry) {
354
+ const transition = entry.instance[SET_ACTIVE](!this._hasNoOwners(entry));
355
+ if (transition === "none") return;
356
+ if (transition === "deactivated") this.emit("deactivated", entry.instance);
357
+ else this.emit("activated", entry.instance, transition.signal);
358
+ }
359
+ /**
360
+ * Record that `dependent` resolved `Type` at `key`, so the edge can be
361
+ * released on the owner's disposal. Keyed per resolved key, not per type:
362
+ * one `depend()` handle resolves a distinct key for every `args` it is
363
+ * called with, and all of them must be swept.
364
+ */
365
+ _recordDependentEdge(dependent, Type, key) {
366
+ let byType = this._dependentEdges.get(dependent);
367
+ if (!byType) this._dependentEdges.set(dependent, byType = /* @__PURE__ */ new Map());
368
+ let keys = byType.get(Type);
369
+ if (!keys) byType.set(Type, keys = /* @__PURE__ */ new Set());
370
+ keys.add(key);
371
+ }
372
+ /**
373
+ * Remove the Map entry for `container`, if this registry is tracking it.
374
+ * @returns true if an entry was found and removed
375
+ */
376
+ _pruneEntry(Type, container) {
377
+ const entry = this._entryByInstance.get(container);
378
+ if (!entry) return false;
379
+ this.instancesByConstructor.get(Type)?.delete(entry.key);
380
+ this._entryById.delete(container.$blac.id);
381
+ return true;
382
+ }
383
+ /**
384
+ * Drop `dependent`'s edge on the `Type`/`key` entry (a `depend()`-resolved
385
+ * instance). When that leaves the entry with no dependents AND no public
386
+ * refs AND it isn't keepAlive, dispose it — this recurses into the same
387
+ * `disposed` listener, so dep-of-dep chains unwind naturally.
388
+ */
389
+ _releaseDependent(Type, key, dependent) {
390
+ const entry = this.instancesByConstructor.get(Type)?.get(key);
391
+ if (!entry) return;
392
+ entry.dependents?.delete(dependent);
393
+ if (this._isUnowned(Type, entry)) {
394
+ if (!entry.instance.$blac.disposed) entry.instance.dispose();
395
+ return;
396
+ }
397
+ this._syncActivation(entry);
555
398
  }
556
399
  /**
557
400
  * Register a type for lifecycle event tracking
@@ -566,9 +409,7 @@ var StateContainerRegistry = class {
566
409
  * @throws Error if type is already registered
567
410
  */
568
411
  register(constructor) {
569
- const className = constructor.name;
570
- if (this.registeredTypeNames.has(className)) throw new Error(`${BLAC_ERROR_PREFIX} Type "${className}" is already registered`);
571
- this.registeredTypeNames.add(className);
412
+ if (this.types.has(constructor)) throw new Error(`${BLAC_ERROR_PREFIX} Type "${getBlacName(constructor)}" is already registered`);
572
413
  this.registerType(constructor);
573
414
  }
574
415
  ensureInstancesMap(Type) {
@@ -583,7 +424,7 @@ var StateContainerRegistry = class {
583
424
  * Get the instances Map for a specific class (public API for stats/debugging)
584
425
  */
585
426
  getInstancesMap(Type) {
586
- return this.instancesByConstructor.get(Type) || /* @__PURE__ */ new Map();
427
+ return this.instancesByConstructor.get(Type) || EMPTY_INSTANCES_MAP;
587
428
  }
588
429
  /**
589
430
  * Directly insert an instance entry into the registry without going through
@@ -600,10 +441,14 @@ var StateContainerRegistry = class {
600
441
  const instances = this.ensureInstancesMap(Type);
601
442
  const existingEntry = instances.get(instanceKey);
602
443
  if (existingEntry && existingEntry.instance !== instance && !existingEntry.instance.$blac.disposed) existingEntry.instance.dispose();
603
- instances.set(instanceKey, {
444
+ const entry = {
604
445
  instance,
446
+ key: instanceKey,
605
447
  refs
606
- });
448
+ };
449
+ instances.set(instanceKey, entry);
450
+ this._entryByInstance.set(instance, entry);
451
+ this._entryById.set(instance.$blac.id, entry);
607
452
  }
608
453
  /**
609
454
  * Resolve the storage key for an instance. This is the single source of truth
@@ -611,7 +456,7 @@ var StateContainerRegistry = class {
611
456
  * an args-derived key is never dropped (instance leaks).
612
457
  *
613
458
  * Resolution order (explicit beats derived):
614
- * explicit key > `static key(args)` > structural hash of args > default sentinel.
459
+ * explicit key \> `static key(args)` \> structural hash of args \> default sentinel.
615
460
  *
616
461
  * @param Type - The StateContainer class constructor
617
462
  * @param instanceKey - Explicit key, or undefined to derive from args
@@ -652,10 +497,13 @@ var StateContainerRegistry = class {
652
497
  * wrapper; `useBloc` and `watch` address a pre-resolved key here.
653
498
  * @param Type - The StateContainer class constructor
654
499
  * @param instanceKey - Pre-resolved instance key (defaults to 'default')
655
- * @param options - Acquisition options
656
- * @param options.canCreate - Whether to create new instance if not found (default: true)
657
- * @param options.countRef - Whether to add a reference (default: true)
658
- * @param options.refId - Named reference ID for debugging; auto-generated if omitted
500
+ * @param options - Acquisition options: `canCreate` (create when absent,
501
+ * default true), `countRef` (add a reference, default true), `refId`
502
+ * (named reference for debugging, auto-generated when omitted),
503
+ * `dependent` — the `depend()`-owner resolving this instance, recorded so
504
+ * its dependent edge is released on the owner's disposal — and
505
+ * `sweepIfUnowned`, for callers that create speculatively and claim
506
+ * ownership later (see {@link _scheduleSweep}).
659
507
  * @returns The state container instance
660
508
  */
661
509
  acquire(Type, instanceKey, options = {}) {
@@ -673,9 +521,9 @@ var StateContainerRegistry = class {
673
521
  entry = void 0;
674
522
  }
675
523
  if (entry) {
676
- if (process.env.NODE_ENV !== "production" && args !== void 0 && entry.args !== void 0) {
524
+ if (IS_DEV && args !== void 0 && entry.args !== void 0) {
677
525
  const incomingKey = structuralKey(args);
678
- const storedKey = structuralKey(entry.args);
526
+ const storedKey = entry.argsKey ??= structuralKey(entry.args);
679
527
  if (incomingKey !== storedKey) console.warn(`${BLAC_ERROR_PREFIX} ${Type.name} instance key "${resolvedKey}" was acquired with different args. Existing args: ${storedKey}, new args: ${incomingKey}. The existing instance will be reused. If distinct args should produce distinct instances, either remove the explicit instanceKey or provide a \`static key\` function that reflects the difference.`);
680
528
  }
681
529
  if (countRef) {
@@ -684,6 +532,11 @@ var StateContainerRegistry = class {
684
532
  this.assertRefLimit(Type, resolvedKey, entry.refs.size);
685
533
  this.emit("refAcquired", entry.instance, refId);
686
534
  }
535
+ if (options.dependent) {
536
+ (entry.dependents ??= /* @__PURE__ */ new Set()).add(options.dependent);
537
+ this._recordDependentEdge(options.dependent, Type, resolvedKey);
538
+ }
539
+ this._syncActivation(entry);
687
540
  return entry.instance;
688
541
  }
689
542
  if (!canCreate) throw new Error(`${BLAC_ERROR_PREFIX} ${Type.name} instance "${resolvedKey}" not found and creation is disabled.`);
@@ -696,16 +549,46 @@ var StateContainerRegistry = class {
696
549
  initialRefId = options.refId ?? `_auto_${this._autoRefIdCounter++}`;
697
550
  initialRefs.set(initialRefId, 1);
698
551
  }
699
- instances.set(resolvedKey, {
552
+ const newEntry = {
700
553
  instance,
554
+ key: resolvedKey,
701
555
  refs: initialRefs,
702
556
  args
703
- });
557
+ };
558
+ if (options.dependent) {
559
+ (newEntry.dependents ??= /* @__PURE__ */ new Set()).add(options.dependent);
560
+ this._recordDependentEdge(options.dependent, Type, resolvedKey);
561
+ }
562
+ instances.set(resolvedKey, newEntry);
563
+ this._entryByInstance.set(instance, newEntry);
564
+ this._entryById.set(instance.$blac.id, newEntry);
704
565
  this.registerType(Type);
705
566
  if (initialRefId) this.emit("refAcquired", instance, initialRefId);
567
+ this._syncActivation(newEntry);
568
+ if (options.sweepIfUnowned && this._hasNoOwners(newEntry)) this._scheduleSweep(Type, newEntry);
706
569
  return instance;
707
570
  }
708
571
  /**
572
+ * Speculative creates (`useBloc`'s render-time create) leak forever when the
573
+ * render is discarded or never commits — SSR has no commit at all, so nothing
574
+ * ever claims ownership. Sweep at the end of the microtask unless the entry
575
+ * gained an owner or is keepAlive.
576
+ *
577
+ * Opt-in, not automatic: a bare `ensure()` hands the instance to a caller
578
+ * that legitimately holds it without a ref, and sweeping those would make
579
+ * `ensure` unusable across an `await`.
580
+ */
581
+ _scheduleSweep(Type, entry) {
582
+ queueMicrotask(() => {
583
+ const instances = this.instancesByConstructor.get(Type);
584
+ if (instances?.get(entry.key) !== entry) return;
585
+ if (entry.instance.$blac.disposed) return;
586
+ if (!this._isUnowned(Type, entry)) return;
587
+ entry.instance.dispose();
588
+ instances.delete(entry.key);
589
+ });
590
+ }
591
+ /**
709
592
  * Borrow an existing instance without adding a ref (borrowing semantics).
710
593
  * Tracks cross-bloc dependency for reactive updates.
711
594
  *
@@ -715,7 +598,7 @@ var StateContainerRegistry = class {
715
598
  * @returns The state container instance
716
599
  * @throws Error if instance doesn't exist
717
600
  */
718
- borrow(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
601
+ borrow(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
719
602
  return this.acquire(Type, instanceKey, {
720
603
  canCreate: false,
721
604
  countRef: false
@@ -730,7 +613,7 @@ var StateContainerRegistry = class {
730
613
  * @param instanceKey - Pre-resolved instance key (defaults to 'default')
731
614
  * @returns Discriminated union with either the instance or an error
732
615
  */
733
- borrowSafe(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
616
+ borrowSafe(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
734
617
  try {
735
618
  return {
736
619
  error: null,
@@ -776,7 +659,7 @@ var StateContainerRegistry = class {
776
659
  * @param forceDispose - Force immediate disposal regardless of refs
777
660
  * @param refId - The specific ref to remove; removes one arbitrary ref if omitted
778
661
  */
779
- release(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, forceDispose = false, refId) {
662
+ release(Type, instanceKey = DEFAULT_STRUCTURAL_KEY, forceDispose = false, refId) {
780
663
  const instances = this.ensureInstancesMap(Type);
781
664
  const entry = instances.get(instanceKey);
782
665
  if (!entry) return;
@@ -801,20 +684,12 @@ var StateContainerRegistry = class {
801
684
  }
802
685
  }
803
686
  if (releasedRefId) this.emit("refReleased", entry.instance, releasedRefId);
804
- const keepAlive = isKeepAliveClass(Type);
805
- if (entry.refs.size === 0 && !keepAlive) {
806
- const deps = entry.instance.$blac.dependencies;
687
+ if (this._isUnowned(Type, entry)) {
807
688
  if (!entry.instance.$blac.disposed) entry.instance.dispose();
808
689
  instances.delete(instanceKey);
809
- for (const [DepType, depKey] of deps) {
810
- const depInstances = this.ensureInstancesMap(DepType);
811
- const depEntry = depInstances.get(depKey);
812
- if (depEntry && depEntry.refs.size === 0 && !isKeepAliveClass(DepType) && !depEntry.instance.$blac.disposed) {
813
- depEntry.instance.dispose();
814
- depInstances.delete(depKey);
815
- }
816
- }
690
+ return;
817
691
  }
692
+ this._syncActivation(entry);
818
693
  }
819
694
  /**
820
695
  * Get all instances of a specific type.
@@ -824,7 +699,7 @@ var StateContainerRegistry = class {
824
699
  getAll(Type) {
825
700
  const instances = this.ensureInstancesMap(Type);
826
701
  const result = [];
827
- for (const entry of instances.values()) result.push(entry.instance);
702
+ for (const entry of instances.values()) if (!entry.instance.$blac.disposed) result.push(entry.instance);
828
703
  return result;
829
704
  }
830
705
  /**
@@ -861,7 +736,7 @@ var StateContainerRegistry = class {
861
736
  * @param instanceKey - Pre-resolved instance key (defaults to 'default')
862
737
  * @returns Current ref count (0 if instance doesn't exist)
863
738
  */
864
- getRefCount(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
739
+ getRefCount(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
865
740
  return this.ensureInstancesMap(Type).get(instanceKey)?.refs.size ?? 0;
866
741
  }
867
742
  /**
@@ -872,13 +747,23 @@ var StateContainerRegistry = class {
872
747
  * @param instanceKey - Pre-resolved instance key (defaults to 'default')
873
748
  * @returns Array of ref ID strings (empty if instance doesn't exist)
874
749
  */
875
- getRefIds(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
750
+ getRefIds(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
876
751
  const instances = this.instancesByConstructor.get(Type);
877
752
  if (!instances) return [];
878
753
  const entry = instances.get(instanceKey);
879
754
  return entry ? Array.from(entry.refs.keys()) : [];
880
755
  }
881
756
  /**
757
+ * Get all active reference IDs for an instance by its `$blac.id`, without
758
+ * needing the owning Type. Used by `PluginContext.getRefIds`.
759
+ * @param instanceId - The instance's `$blac.id`
760
+ * @returns Array of ref ID strings (empty if instance doesn't exist)
761
+ */
762
+ getRefIdsById(instanceId) {
763
+ const entry = this._entryById.get(instanceId);
764
+ return entry ? Array.from(entry.refs.keys()) : [];
765
+ }
766
+ /**
882
767
  * Check if an instance exists.
883
768
  *
884
769
  * @internal Internal key tier; public callers use the args-based `hasInstance`.
@@ -886,7 +771,7 @@ var StateContainerRegistry = class {
886
771
  * @param instanceKey - Pre-resolved instance key (defaults to 'default')
887
772
  * @returns true if instance exists
888
773
  */
889
- hasInstance(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
774
+ hasInstance(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
890
775
  return this.ensureInstancesMap(Type).has(instanceKey);
891
776
  }
892
777
  /**
@@ -898,7 +783,6 @@ var StateContainerRegistry = class {
898
783
  clearAll() {
899
784
  for (const Type of this.types) this.clear(Type);
900
785
  this.types.clear();
901
- this.registeredTypeNames.clear();
902
786
  }
903
787
  /**
904
788
  * Get registry statistics for debugging.
@@ -933,9 +817,11 @@ var StateContainerRegistry = class {
933
817
  * @returns Unsubscribe function
934
818
  */
935
819
  on(event, listener) {
936
- if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
937
- const instance = this.listeners.get(event);
938
- if (!instance) throw new Error(`${BLAC_ERROR_PREFIX} Failed to register listener for event '${event}'`);
820
+ let instance = this.listeners.get(event);
821
+ if (!instance) {
822
+ instance = /* @__PURE__ */ new Set();
823
+ this.listeners.set(event, instance);
824
+ }
939
825
  instance.add(listener);
940
826
  if (event === "stateChanged") this._stateChangedListenerCount++;
941
827
  return () => {
@@ -968,6 +854,15 @@ var StateContainerRegistry = class {
968
854
  newState
969
855
  ]);
970
856
  }
857
+ /**
858
+ * Deliver one event per emit, in order — deliberately NOT coalesced.
859
+ *
860
+ * This is a transition log: devtools, time-travel debugging and perf
861
+ * monitoring need every intermediate `prev`/`next`, which coalescing would
862
+ * destroy. The plugin `onStateChange` bridge is the coalesced, `PathSet`-
863
+ * carrying lane (see `PluginManager.setupLifecycleHooks`); the two differ on
864
+ * purpose, so a listener picks the lane that matches what it needs.
865
+ */
971
866
  flushStateChanged() {
972
867
  const pending = this._pendingStateChanges;
973
868
  this._pendingStateChanges = null;
@@ -977,20 +872,10 @@ var StateContainerRegistry = class {
977
872
  };
978
873
  /**
979
874
  * Global default registry instance
875
+ * @public
980
876
  */
981
877
  const globalRegistry = new StateContainerRegistry();
982
- /**
983
- * Global plugin manager (initialized lazily)
984
- */
985
- let _globalPluginManager = null;
986
- /**
987
- * Get the global plugin manager
988
- */
989
- function getPluginManager() {
990
- if (!_globalPluginManager) _globalPluginManager = createPluginManager(globalRegistry);
991
- return _globalPluginManager;
992
- }
993
878
  //#endregion
994
- export { INIT_CONFIG as a, isExcludedFromDevTools as c, PluginManager as d, configureBlac as f, shallowEqualState as h, APPLY_DEPS as i, isKeepAliveClass as l, resetBlacConfig as m, getPluginManager as n, REMOVE_DEPS_OWNER as o, getBlacConfig as p, globalRegistry as r, getClassEquality as s, StateContainerRegistry as t, BLAC_STATIC_PROPS as u };
879
+ export { resetBlacConfig as _, ON_DISPOSE as a, WITH_TRACKED_STATE as c, isExcludedFromDevTools as d, isKeepAliveClass as f, getBlacConfig as g, configureBlac as h, INIT_CONFIG as i, getBlacName as l, IS_DEV as m, globalRegistry as n, REMOVE_DEPS_OWNER as o, BLAC_STATIC_PROPS as p, APPLY_DEPS as r, SET_ACTIVE as s, StateContainerRegistry as t, getClassEquality as u, shallowEqualState as v };
995
880
 
996
- //# sourceMappingURL=StateContainerRegistry-CvBOhAT2.js.map
881
+ //# sourceMappingURL=StateContainerRegistry-vWgsD-RP.js.map