@blac/core 2.0.20 → 2.0.22

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 (295) 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-DVIBTxGZ.js → StateContainerRegistry-BH7aOMWV.js} +299 -406
  5. package/dist/StateContainerRegistry-BH7aOMWV.js.map +1 -0
  6. package/dist/{StateContainerRegistry-Dj77cok9.js → StateContainerRegistry-BsH_s0pJ.js} +303 -413
  7. package/dist/StateContainerRegistry-BsH_s0pJ.js.map +1 -0
  8. package/dist/{StateContainerRegistry-lIwo-0m3.cjs → StateContainerRegistry-CBLeHObO.cjs} +326 -418
  9. package/dist/StateContainerRegistry-CBLeHObO.cjs.map +1 -0
  10. package/dist/{StateContainerRegistry-D9BIRjwv.cjs → StateContainerRegistry-MkT0W4C2.cjs} +322 -411
  11. package/dist/StateContainerRegistry-MkT0W4C2.cjs.map +1 -0
  12. package/dist/{StateContainerRegistry-CvBOhAT2.js → StateContainerRegistry-vWgsD-RP.js} +277 -392
  13. package/dist/StateContainerRegistry-vWgsD-RP.js.map +1 -0
  14. package/dist/config.d.cts +2 -2
  15. package/dist/config.d.ts +2 -2
  16. package/dist/constants.d.cts +4 -20
  17. package/dist/constants.d.ts +4 -20
  18. package/dist/core/Cubit.d.ts +14 -10
  19. package/dist/core/StateContainer.d.ts +68 -8
  20. package/dist/core/StateContainerRegistry.d.ts +100 -19
  21. package/dist/core/meta.d.ts +34 -47
  22. package/dist/core/symbols.d.ts +18 -1
  23. package/dist/decorators/blac.d.ts +28 -26
  24. package/dist/{ensure-BKdq5xsY.cjs → ensure-BjhbDW8Z.cjs} +223 -40
  25. package/dist/ensure-BjhbDW8Z.cjs.map +1 -0
  26. package/dist/ensure-BnVyd6s4.js +730 -0
  27. package/dist/ensure-BnVyd6s4.js.map +1 -0
  28. package/dist/ensure-CneMkpbI.js +731 -0
  29. package/dist/ensure-CneMkpbI.js.map +1 -0
  30. package/dist/{ensure-CQimeD0I.cjs → ensure-D15vGnk5.cjs} +348 -124
  31. package/dist/ensure-D15vGnk5.cjs.map +1 -0
  32. package/dist/{ensure-B1dEDsZV.cjs → ensure-D3w6YGp9.cjs} +349 -124
  33. package/dist/ensure-D3w6YGp9.cjs.map +1 -0
  34. package/dist/{ensure-B2DYCbbI.cjs → ensure-ORVw-WRE.cjs} +214 -39
  35. package/dist/ensure-ORVw-WRE.cjs.map +1 -0
  36. package/dist/{ensure-B59ri8oo.js → ensure-dsqWhIuO.js} +192 -39
  37. package/dist/ensure-dsqWhIuO.js.map +1 -0
  38. package/dist/{ensure-BSJHkUNX.js → ensure-k-6q7O25.js} +183 -38
  39. package/dist/ensure-k-6q7O25.js.map +1 -0
  40. package/dist/index.cjs +212 -32
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +4 -6
  43. package/dist/index.d.ts +4 -6
  44. package/dist/index.js +192 -12
  45. package/dist/index.js.map +1 -1
  46. package/dist/plugin/BlacPlugin.d.ts +14 -0
  47. package/dist/plugin/PluginManager.d.ts +21 -11
  48. package/dist/plugins.cjs +403 -3
  49. package/dist/plugins.cjs.map +1 -0
  50. package/dist/plugins.d.cts +6 -2
  51. package/dist/plugins.d.ts +6 -2
  52. package/dist/plugins.js +401 -1
  53. package/dist/plugins.js.map +1 -0
  54. package/dist/registry/acquire.d.ts +2 -2
  55. package/dist/registry/config.d.ts +9 -0
  56. package/dist/registry/management.d.ts +3 -0
  57. package/dist/registry/queries.d.ts +15 -0
  58. package/dist/registry/release.d.ts +3 -3
  59. package/dist/testing.cjs +13 -15
  60. package/dist/testing.cjs.map +1 -1
  61. package/dist/testing.d.cts +1 -1
  62. package/dist/testing.d.ts +1 -1
  63. package/dist/testing.js +2 -4
  64. package/dist/testing.js.map +1 -1
  65. package/dist/types/branded.d.ts +3 -3
  66. package/dist/types/utilities.d.ts +45 -18
  67. package/dist/utils/static-props.d.ts +25 -1
  68. package/dist/utils/structural-key.d.ts +0 -7
  69. package/dist/watch/index.d.ts +1 -1
  70. package/dist/watch/watch.d.ts +22 -3
  71. package/package.json +16 -55
  72. package/dist/StateContainerRegistry-4Ljd9EJx.js +0 -1018
  73. package/dist/StateContainerRegistry-4Ljd9EJx.js.map +0 -1
  74. package/dist/StateContainerRegistry-8zNFMAf7.js +0 -1067
  75. package/dist/StateContainerRegistry-8zNFMAf7.js.map +0 -1
  76. package/dist/StateContainerRegistry-B1GYx9Xo.js +0 -975
  77. package/dist/StateContainerRegistry-B1GYx9Xo.js.map +0 -1
  78. package/dist/StateContainerRegistry-B_ViBLvJ.js +0 -975
  79. package/dist/StateContainerRegistry-B_ViBLvJ.js.map +0 -1
  80. package/dist/StateContainerRegistry-Bx2ly-ba.cjs +0 -1162
  81. package/dist/StateContainerRegistry-Bx2ly-ba.cjs.map +0 -1
  82. package/dist/StateContainerRegistry-C0dCNlTF.js +0 -1005
  83. package/dist/StateContainerRegistry-C0dCNlTF.js.map +0 -1
  84. package/dist/StateContainerRegistry-CEWpc1bj.cjs +0 -1113
  85. package/dist/StateContainerRegistry-CEWpc1bj.cjs.map +0 -1
  86. package/dist/StateContainerRegistry-CS18y4au.cjs +0 -1163
  87. package/dist/StateContainerRegistry-CS18y4au.cjs.map +0 -1
  88. package/dist/StateContainerRegistry-CSNm2Ws5.cjs.map +0 -1
  89. package/dist/StateContainerRegistry-CchRjnjX.cjs +0 -1094
  90. package/dist/StateContainerRegistry-CchRjnjX.cjs.map +0 -1
  91. package/dist/StateContainerRegistry-CvBOhAT2.js.map +0 -1
  92. package/dist/StateContainerRegistry-D8nu7Z0T.js +0 -1068
  93. package/dist/StateContainerRegistry-D8nu7Z0T.js.map +0 -1
  94. package/dist/StateContainerRegistry-D9BIRjwv.cjs.map +0 -1
  95. package/dist/StateContainerRegistry-DJQqLrli.cjs +0 -1046
  96. package/dist/StateContainerRegistry-DJQqLrli.cjs.map +0 -1
  97. package/dist/StateContainerRegistry-DVIBTxGZ.js.map +0 -1
  98. package/dist/StateContainerRegistry-Dj77cok9.js.map +0 -1
  99. package/dist/StateContainerRegistry-FWKzPfS-.cjs +0 -1046
  100. package/dist/StateContainerRegistry-FWKzPfS-.cjs.map +0 -1
  101. package/dist/StateContainerRegistry-lIwo-0m3.cjs.map +0 -1
  102. package/dist/acquire-B1LYXSGj.js +0 -35
  103. package/dist/acquire-B1LYXSGj.js.map +0 -1
  104. package/dist/acquire-B1Wuqla6.cjs +0 -46
  105. package/dist/acquire-B1Wuqla6.cjs.map +0 -1
  106. package/dist/acquire-BJ3xUEmh.cjs +0 -46
  107. package/dist/acquire-BJ3xUEmh.cjs.map +0 -1
  108. package/dist/acquire-BP-ZfUH2.js +0 -35
  109. package/dist/acquire-BP-ZfUH2.js.map +0 -1
  110. package/dist/acquire-BRIJVJaA.js +0 -35
  111. package/dist/acquire-BRIJVJaA.js.map +0 -1
  112. package/dist/acquire-BZIs6tpN.cjs +0 -46
  113. package/dist/acquire-BZIs6tpN.cjs.map +0 -1
  114. package/dist/acquire-B_p1fNEh.cjs +0 -46
  115. package/dist/acquire-B_p1fNEh.cjs.map +0 -1
  116. package/dist/acquire-BdsVZBSe.js +0 -35
  117. package/dist/acquire-BdsVZBSe.js.map +0 -1
  118. package/dist/acquire-C3nOb3XR.cjs +0 -46
  119. package/dist/acquire-C3nOb3XR.cjs.map +0 -1
  120. package/dist/acquire-C4WAS13G.cjs +0 -46
  121. package/dist/acquire-C4WAS13G.cjs.map +0 -1
  122. package/dist/acquire-C7beGI55.cjs +0 -46
  123. package/dist/acquire-C7beGI55.cjs.map +0 -1
  124. package/dist/acquire-CbMz7gWs.cjs +0 -46
  125. package/dist/acquire-CbMz7gWs.cjs.map +0 -1
  126. package/dist/acquire-CwB083au.js +0 -35
  127. package/dist/acquire-CwB083au.js.map +0 -1
  128. package/dist/acquire-DHEOEVcj.cjs +0 -46
  129. package/dist/acquire-DHEOEVcj.cjs.map +0 -1
  130. package/dist/acquire-DXEQDZGr.js +0 -35
  131. package/dist/acquire-DXEQDZGr.js.map +0 -1
  132. package/dist/acquire-Dg-uXwjF.js +0 -35
  133. package/dist/acquire-Dg-uXwjF.js.map +0 -1
  134. package/dist/acquire-Don7499S.js +0 -35
  135. package/dist/acquire-Don7499S.js.map +0 -1
  136. package/dist/acquire-G8aB3GAx.js +0 -35
  137. package/dist/acquire-G8aB3GAx.js.map +0 -1
  138. package/dist/branded-BybA0_xw.cjs +0 -18
  139. package/dist/branded-BybA0_xw.cjs.map +0 -1
  140. package/dist/branded-DkbUzncc.js +0 -13
  141. package/dist/branded-DkbUzncc.js.map +0 -1
  142. package/dist/config-8pyOC5gv.js +0 -16
  143. package/dist/config-8pyOC5gv.js.map +0 -1
  144. package/dist/config-B1l8ZGlU.cjs +0 -32
  145. package/dist/config-B1l8ZGlU.cjs.map +0 -1
  146. package/dist/config-C7Ktkjh2.cjs +0 -32
  147. package/dist/config-C7Ktkjh2.cjs.map +0 -1
  148. package/dist/config-COT4Xkbc.js +0 -16
  149. package/dist/config-COT4Xkbc.js.map +0 -1
  150. package/dist/config-CzcoAvBo.js +0 -16
  151. package/dist/config-CzcoAvBo.js.map +0 -1
  152. package/dist/config-D6BoWWPv.js +0 -16
  153. package/dist/config-D6BoWWPv.js.map +0 -1
  154. package/dist/config-DOO82lbZ.js +0 -16
  155. package/dist/config-DOO82lbZ.js.map +0 -1
  156. package/dist/config-DVM_ESQt.js +0 -16
  157. package/dist/config-DVM_ESQt.js.map +0 -1
  158. package/dist/config-DVMnlHLv.cjs +0 -32
  159. package/dist/config-DVMnlHLv.cjs.map +0 -1
  160. package/dist/config-DWUq7zEQ.cjs +0 -32
  161. package/dist/config-DWUq7zEQ.cjs.map +0 -1
  162. package/dist/config-DXI3kzmA.js +0 -16
  163. package/dist/config-DXI3kzmA.js.map +0 -1
  164. package/dist/config-Dk1FxfXj.js +0 -16
  165. package/dist/config-Dk1FxfXj.js.map +0 -1
  166. package/dist/config-b2z51Y_C.cjs +0 -32
  167. package/dist/config-b2z51Y_C.cjs.map +0 -1
  168. package/dist/config-buwpT7l3.js +0 -16
  169. package/dist/config-buwpT7l3.js.map +0 -1
  170. package/dist/config-cwaTn5oy.cjs +0 -32
  171. package/dist/config-cwaTn5oy.cjs.map +0 -1
  172. package/dist/config-d9_U58ef.cjs +0 -32
  173. package/dist/config-d9_U58ef.cjs.map +0 -1
  174. package/dist/config-hWTayX76.cjs +0 -32
  175. package/dist/config-hWTayX76.cjs.map +0 -1
  176. package/dist/config-sCPKPYxZ.cjs +0 -32
  177. package/dist/config-sCPKPYxZ.cjs.map +0 -1
  178. package/dist/debug.cjs +0 -14
  179. package/dist/debug.d.cts +0 -34
  180. package/dist/debug.d.ts +0 -34
  181. package/dist/debug.js +0 -4
  182. package/dist/ensure-B1dEDsZV.cjs.map +0 -1
  183. package/dist/ensure-B2DYCbbI.cjs.map +0 -1
  184. package/dist/ensure-B59ri8oo.js.map +0 -1
  185. package/dist/ensure-B9oOqpOB.cjs +0 -558
  186. package/dist/ensure-B9oOqpOB.cjs.map +0 -1
  187. package/dist/ensure-BKdq5xsY.cjs.map +0 -1
  188. package/dist/ensure-BSJHkUNX.js.map +0 -1
  189. package/dist/ensure-BfYnvAsI.js +0 -709
  190. package/dist/ensure-BfYnvAsI.js.map +0 -1
  191. package/dist/ensure-Bm91o37h.js +0 -536
  192. package/dist/ensure-Bm91o37h.js.map +0 -1
  193. package/dist/ensure-C1a8IFWy.js +0 -535
  194. package/dist/ensure-C1a8IFWy.js.map +0 -1
  195. package/dist/ensure-CLQEx0Nn.cjs +0 -528
  196. package/dist/ensure-CLQEx0Nn.cjs.map +0 -1
  197. package/dist/ensure-CPMiGvov.js +0 -493
  198. package/dist/ensure-CPMiGvov.js.map +0 -1
  199. package/dist/ensure-CQimeD0I.cjs.map +0 -1
  200. package/dist/ensure-DEXSmA-N.js +0 -526
  201. package/dist/ensure-DEXSmA-N.js.map +0 -1
  202. package/dist/ensure-DTTTd2h8.js +0 -536
  203. package/dist/ensure-DTTTd2h8.js.map +0 -1
  204. package/dist/ensure-Dx86f80B.cjs +0 -567
  205. package/dist/ensure-Dx86f80B.cjs.map +0 -1
  206. package/dist/ensure-DzqGej43.js +0 -526
  207. package/dist/ensure-DzqGej43.js.map +0 -1
  208. package/dist/ensure-VuiNPccZ.cjs +0 -559
  209. package/dist/ensure-VuiNPccZ.cjs.map +0 -1
  210. package/dist/ensure-_Oh8XPm4.cjs +0 -732
  211. package/dist/ensure-_Oh8XPm4.cjs.map +0 -1
  212. package/dist/ensure-fsQMjaQL.cjs +0 -567
  213. package/dist/ensure-fsQMjaQL.cjs.map +0 -1
  214. package/dist/ensure-vxAGa2v3.js +0 -536
  215. package/dist/ensure-vxAGa2v3.js.map +0 -1
  216. package/dist/management-14vsHr4t.cjs +0 -85
  217. package/dist/management-14vsHr4t.cjs.map +0 -1
  218. package/dist/management-444tdCBG.js +0 -38
  219. package/dist/management-444tdCBG.js.map +0 -1
  220. package/dist/management-7jLjVHJd.js +0 -38
  221. package/dist/management-7jLjVHJd.js.map +0 -1
  222. package/dist/management-AFuqQEwz.js +0 -38
  223. package/dist/management-AFuqQEwz.js.map +0 -1
  224. package/dist/management-BodVLAfJ.cjs +0 -85
  225. package/dist/management-BodVLAfJ.cjs.map +0 -1
  226. package/dist/management-BqGBdL01.js +0 -38
  227. package/dist/management-BqGBdL01.js.map +0 -1
  228. package/dist/management-BvUAjril.js +0 -38
  229. package/dist/management-BvUAjril.js.map +0 -1
  230. package/dist/management-C-WGcwPD.js +0 -38
  231. package/dist/management-C-WGcwPD.js.map +0 -1
  232. package/dist/management-C2wv4vGS.cjs +0 -85
  233. package/dist/management-C2wv4vGS.cjs.map +0 -1
  234. package/dist/management-D2pjr38g.js +0 -38
  235. package/dist/management-D2pjr38g.js.map +0 -1
  236. package/dist/management-D5WsZoTV.js +0 -38
  237. package/dist/management-D5WsZoTV.js.map +0 -1
  238. package/dist/management-DAV9sN-k.cjs +0 -85
  239. package/dist/management-DAV9sN-k.cjs.map +0 -1
  240. package/dist/management-Df7Y2sF1.cjs +0 -85
  241. package/dist/management-Df7Y2sF1.cjs.map +0 -1
  242. package/dist/management-DwO0nBzL.cjs +0 -85
  243. package/dist/management-DwO0nBzL.cjs.map +0 -1
  244. package/dist/management-GcnNJjnm.cjs +0 -85
  245. package/dist/management-GcnNJjnm.cjs.map +0 -1
  246. package/dist/management-P_Vi_cvw.js +0 -38
  247. package/dist/management-P_Vi_cvw.js.map +0 -1
  248. package/dist/management-QWLpbXlW.cjs +0 -85
  249. package/dist/management-QWLpbXlW.cjs.map +0 -1
  250. package/dist/management-iE4x6IQY.cjs +0 -85
  251. package/dist/management-iE4x6IQY.cjs.map +0 -1
  252. package/dist/types.cjs +0 -3
  253. package/dist/types.d.cts +0 -8
  254. package/dist/types.d.ts +0 -8
  255. package/dist/types.js +0 -2
  256. package/dist/watch-B0pUfh-v.cjs +0 -119
  257. package/dist/watch-B0pUfh-v.cjs.map +0 -1
  258. package/dist/watch-B91opmva.cjs +0 -70
  259. package/dist/watch-B91opmva.cjs.map +0 -1
  260. package/dist/watch-BHfCtbn1.cjs +0 -119
  261. package/dist/watch-BHfCtbn1.cjs.map +0 -1
  262. package/dist/watch-BmC-Tyqg.js +0 -59
  263. package/dist/watch-BmC-Tyqg.js.map +0 -1
  264. package/dist/watch-Bx0y-Kcc.js +0 -108
  265. package/dist/watch-Bx0y-Kcc.js.map +0 -1
  266. package/dist/watch-CKWt788i.cjs +0 -70
  267. package/dist/watch-CKWt788i.cjs.map +0 -1
  268. package/dist/watch-CfrSSrRY.js +0 -59
  269. package/dist/watch-CfrSSrRY.js.map +0 -1
  270. package/dist/watch-Cnh3PtQA.cjs +0 -70
  271. package/dist/watch-Cnh3PtQA.cjs.map +0 -1
  272. package/dist/watch-D01MdwKy.js +0 -108
  273. package/dist/watch-D01MdwKy.js.map +0 -1
  274. package/dist/watch-D8hwbFU1.js +0 -59
  275. package/dist/watch-D8hwbFU1.js.map +0 -1
  276. package/dist/watch-DPVUwPUY.js +0 -59
  277. package/dist/watch-DPVUwPUY.js.map +0 -1
  278. package/dist/watch-DVmvZBMD.cjs +0 -70
  279. package/dist/watch-DVmvZBMD.cjs.map +0 -1
  280. package/dist/watch-DkDGmGAx.cjs +0 -119
  281. package/dist/watch-DkDGmGAx.cjs.map +0 -1
  282. package/dist/watch-DttHqefn.js +0 -108
  283. package/dist/watch-DttHqefn.js.map +0 -1
  284. package/dist/watch-DxdIZjpt.cjs +0 -119
  285. package/dist/watch-DxdIZjpt.cjs.map +0 -1
  286. package/dist/watch-DztcJuUy.cjs +0 -119
  287. package/dist/watch-DztcJuUy.cjs.map +0 -1
  288. package/dist/watch-IrR8G28N.js +0 -108
  289. package/dist/watch-IrR8G28N.js.map +0 -1
  290. package/dist/watch-entry.cjs +0 -4
  291. package/dist/watch-entry.d.cts +0 -24
  292. package/dist/watch-entry.d.ts +0 -24
  293. package/dist/watch-entry.js +0 -2
  294. package/dist/watch-hx0r92Zc.js +0 -108
  295. package/dist/watch-hx0r92Zc.js.map +0 -1
@@ -1,4 +1,3 @@
1
- let _dirtytalk_structural = require("@dirtytalk/structural");
2
1
  //#region src/config.ts
3
2
  /**
4
3
  * Default equality: shallow per-key `Object.is` comparison.
@@ -9,13 +8,16 @@ const shallowEqualState = (prev, next) => {
9
8
  if (Object.is(prev, next)) return true;
10
9
  if (typeof prev !== "object" || prev === null) return false;
11
10
  if (typeof next !== "object" || next === null) return false;
12
- const prevKeys = Object.keys(prev);
13
- if (prevKeys.length !== Object.keys(next).length) return false;
14
- for (let i = 0; i < prevKeys.length; i++) {
15
- const key = prevKeys[i];
16
- if (!Object.is(prev[key], next[key])) return false;
17
- }
18
- return true;
11
+ const p = prev;
12
+ const n = next;
13
+ let prevCount = 0;
14
+ for (const key in p) {
15
+ prevCount++;
16
+ if (!Object.is(p[key], n[key])) return false;
17
+ }
18
+ let nextCount = 0;
19
+ for (const _ in n) nextCount++;
20
+ return prevCount === nextCount;
19
21
  };
20
22
  const defaultConfig = {
21
23
  equality: shallowEqualState,
@@ -25,7 +27,7 @@ const defaultConfig = {
25
27
  };
26
28
  let globalConfig = { ...defaultConfig };
27
29
  /**
28
- * Configure global defaults for @blac/core.
30
+ * Configure global defaults for `@blac/core`.
29
31
  *
30
32
  * @example
31
33
  * ```ts
@@ -56,331 +58,6 @@ function resetBlacConfig() {
56
58
  globalConfig = { ...defaultConfig };
57
59
  }
58
60
  //#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
- this.registry.on("hydrationChanged", (instance, status, previousStatus) => {
210
- this.notifyPlugins("onHydrationChange", instance, status, previousStatus);
211
- })
212
- ];
213
- }
214
- /**
215
- * Subscribe to the container's channel with `ALL_PATHS` interest, so we
216
- * fire on every flush. Capture `prev` from the snapshot taken on the
217
- * previous flush (or at create-time for the first flush) and pass the
218
- * channel's `paths` argument straight through to plugins.
219
- *
220
- * Per-container bookkeeping is stored in a `WeakMap` keyed by the
221
- * container itself, so a disposed/GC'd container drops cleanly.
222
- */
223
- attachStateBridge(container) {
224
- if (this.containerBridges.has(container)) return;
225
- const bridge = {
226
- unsub: () => {},
227
- prevState: container.state
228
- };
229
- this.containerBridges.set(container, bridge);
230
- bridge.unsub = container.channel.subscribe(() => _dirtytalk_structural.ALL_PATHS, (paths) => this.dispatchStateChange(container, paths));
231
- }
232
- detachStateBridge(container) {
233
- const bridge = this.containerBridges.get(container);
234
- if (!bridge) return;
235
- bridge.unsub();
236
- this.containerBridges.delete(container);
237
- }
238
- /**
239
- * Channel-flush callback. Snapshots `next`, hands `(prev, next, paths)`
240
- * to every enabled plugin's `onStateChange`, then updates `prevState`
241
- * for the next flush.
242
- *
243
- * Note: `prev` is captured once and reused across plugins — every
244
- * plugin sees the same `prev`/`next`/`paths` regardless of dispatch
245
- * order, matching the "snapshot prev once per flush" invariant.
246
- */
247
- dispatchStateChange(container, paths) {
248
- const bridge = this.containerBridges.get(container);
249
- if (!bridge) return;
250
- const prev = bridge.prevState;
251
- const next = container.state;
252
- bridge.prevState = next;
253
- let ctx;
254
- for (const { plugin, config } of this.plugins.values()) {
255
- if (!config.enabled) continue;
256
- const hook = plugin.onStateChange;
257
- if (typeof hook !== "function") continue;
258
- try {
259
- ctx ??= this.buildContext(container);
260
- hook.call(plugin, ctx, prev, next, paths);
261
- } catch (error) {
262
- console.error(`[BlaC] Error in plugin "${plugin.name}" onStateChange:`, error);
263
- }
264
- }
265
- }
266
- /**
267
- * Notify all plugins of a lifecycle event.
268
- *
269
- * Builds a fresh `PluginContext` per dispatch (instance becomes
270
- * `ctx.container`), so plugins can reach the focal bloc through
271
- * `ctx.container`. The context is built lazily on the first matching
272
- * hook and reused for every other plugin in this dispatch — zero
273
- * builds if no enabled plugin implements `hookName`.
274
- */
275
- notifyPlugins(hookName, instance, ...extraArgs) {
276
- let ctx;
277
- for (const { plugin, config } of this.plugins.values()) {
278
- if (!config.enabled) continue;
279
- const hook = plugin[hookName];
280
- if (typeof hook !== "function") continue;
281
- try {
282
- ctx ??= this.buildContext(instance);
283
- hook.call(plugin, ctx, ...extraArgs);
284
- } catch (error) {
285
- console.error(`[BlaC] Error in plugin "${plugin.name}" ${hookName}:`, error);
286
- }
287
- }
288
- }
289
- /**
290
- * Build a `PluginContext` for a given focal container. The context is
291
- * cheap to build — its methods close over `this.registry` directly —
292
- * so we create one per dispatch rather than caching per container.
293
- * This sidesteps the lifetime question of "when do we evict a cached
294
- * context?" and keeps `paths`-vs-`container` lifetimes cleanly
295
- * separated.
296
- */
297
- buildContext(container) {
298
- const registry = this.registry;
299
- return {
300
- container,
301
- getInstanceMetadata: (instance) => {
302
- return {
303
- id: instance.$blac.id,
304
- className: instance.constructor.name,
305
- isDisposed: instance.$blac.disposed,
306
- name: instance.$blac.name,
307
- createdAt: instance.$blac.createdAt,
308
- state: instance.state,
309
- hydrationStatus: instance.$blac.hydration.status,
310
- isHydrated: instance.$blac.hydration.isHydrated,
311
- hydrationError: instance.$blac.hydration.error,
312
- changedWhileHydrating: instance.$blac.hydration.changedWhileHydrating,
313
- args: instance.args
314
- };
315
- },
316
- getState: (instance) => {
317
- return instance.state;
318
- },
319
- getHydrationStatus: (instance) => {
320
- return instance.$blac.hydration.status;
321
- },
322
- startHydration: (instance) => {
323
- instance.$blac.hydration.begin();
324
- },
325
- applyHydratedState: (instance, state) => {
326
- return instance.$blac.hydration.apply(state);
327
- },
328
- finishHydration: (instance) => {
329
- instance.$blac.hydration.finish();
330
- },
331
- failHydration: (instance, error) => {
332
- instance.$blac.hydration.fail(error);
333
- },
334
- waitForHydration: (instance) => {
335
- return instance.$blac.hydration.wait();
336
- },
337
- queryInstances: (typeClass) => {
338
- return registry.getAll(typeClass);
339
- },
340
- getAllTypes: () => {
341
- return registry.getTypes();
342
- },
343
- getStats: () => {
344
- return registry.getStats();
345
- },
346
- getRefIds: (instanceId) => {
347
- for (const Type of registry.getTypes()) {
348
- const map = registry.getInstancesMap(Type);
349
- for (const [, entry] of map) if (entry.instance.$blac.id === instanceId) return Array.from(entry.refs.keys());
350
- }
351
- return [];
352
- }
353
- };
354
- }
355
- /**
356
- * Check if plugin should be enabled based on environment
357
- */
358
- shouldEnablePlugin(config) {
359
- if (!config.enabled) return false;
360
- if (config.environment === "all") return true;
361
- return this.getCurrentEnvironment() === config.environment;
362
- }
363
- /**
364
- * Get current environment
365
- */
366
- getCurrentEnvironment() {
367
- if (typeof process !== "undefined") {
368
- if (process.env.NODE_ENV === "test") return "test";
369
- if (process.env.NODE_ENV === "production") return "production";
370
- return "development";
371
- }
372
- return "development";
373
- }
374
- };
375
- /**
376
- * Create a plugin manager instance
377
- * @param registry - The StateContainerRegistry to monitor for lifecycle events
378
- * @returns A new PluginManager instance
379
- */
380
- function createPluginManager(registry) {
381
- return new PluginManager(registry);
382
- }
383
- //#endregion
384
61
  //#region src/constants.ts
385
62
  /**
386
63
  * Default configuration constants for BlaC
@@ -388,19 +65,6 @@ function createPluginManager(registry) {
388
65
  * Centralized location for all magic numbers and default values.
389
66
  */
390
67
  /**
391
- * Default configuration constants for BlaC
392
- */
393
- const BLAC_DEFAULTS = {
394
- /**
395
- * Default instance key for shared instances
396
- */
397
- DEFAULT_INSTANCE_KEY: "default",
398
- /**
399
- * Maximum getter nesting depth (prevents infinite recursion)
400
- */
401
- MAX_GETTER_DEPTH: 10
402
- };
403
- /**
404
68
  * Static property names for StateContainer classes
405
69
  * Used for feature flags and configuration on bloc classes
406
70
  */
@@ -425,19 +89,42 @@ const BLAC_STATIC_PROPS = {
425
89
  * instead of using a structural hash of `args`. Allows blocs to ignore
426
90
  * non-identity fields (e.g., `readonly` flags) while still keying by id.
427
91
  */
428
- KEY: "key"
92
+ KEY: "key",
93
+ /**
94
+ * Explicit, minification-safe identity for a bloc class: `static blacName = '...'`.
95
+ * When unset, identity falls back to `constructor.name`.
96
+ */
97
+ BLAC_NAME: "blacName"
429
98
  };
430
99
  /**
431
100
  * Standard error message prefix
432
101
  */
433
102
  const BLAC_ERROR_PREFIX = "[BlaC]";
103
+ /**
104
+ * True only when `process.env.NODE_ENV` is defined and not `'production'`.
105
+ *
106
+ * Dev-only checks (emit-rate breaker, deps collision scan, args-mismatch key)
107
+ * cost real work per emit/acquire, so an environment that says nothing about
108
+ * its mode — plain ESM in the browser, Deno, a bundle without a `process`
109
+ * shim — is treated as production. Bundlers that define `process.env.NODE_ENV`
110
+ * still constant-fold the read.
111
+ */
112
+ const nodeEnv = (() => {
113
+ try {
114
+ return process.env.NODE_ENV;
115
+ } catch {
116
+ return;
117
+ }
118
+ })();
119
+ const IS_DEV = nodeEnv !== void 0 && nodeEnv !== "production";
434
120
  //#endregion
435
121
  //#region src/utils/static-props.ts
436
122
  /**
437
123
  * Utility functions for accessing static properties on StateContainer classes
438
124
  */
439
125
  /**
440
- * Get a static property from a class constructor
126
+ * Get a static property from a class constructor, walking the prototype
127
+ * chain so subclasses inherit the value unless they define their own.
441
128
  * Type-safe helper that avoids (Type as any) casts
442
129
  *
443
130
  * @param Type - The class constructor
@@ -449,8 +136,20 @@ function getStaticProp(Type, propName, defaultValue) {
449
136
  return Type[propName] ?? defaultValue;
450
137
  }
451
138
  /**
139
+ * Get a static property declared directly on `Type`, ignoring the prototype
140
+ * chain, so subclasses never inherit a base class's value.
141
+ *
142
+ * @param Type - The class constructor
143
+ * @param propName - The property name to access
144
+ * @returns The own property value or `undefined`
145
+ */
146
+ function getOwnStaticProp(Type, propName) {
147
+ return Object.hasOwn(Type, propName) ? Type[propName] : void 0;
148
+ }
149
+ /**
452
150
  * Check if a class is marked as keepAlive.
453
151
  * KeepAlive classes are never auto-disposed when ref count reaches 0.
152
+ * Inherited: a subclass keeps a base class's `keepAlive` unless overridden.
454
153
  * @param Type - The class constructor to check
455
154
  * @returns true if the class has `static keepAlive = true`
456
155
  */
@@ -460,6 +159,7 @@ function isKeepAliveClass(Type) {
460
159
  /**
461
160
  * Check if a class should be excluded from DevTools.
462
161
  * Used to prevent infinite loops when DevTools tracks itself.
162
+ * Inherited: a subclass keeps a base class's exclusion unless overridden.
463
163
  * @param Type - The class constructor to check
464
164
  * @returns true if the class has `static __excludeFromDevTools = true`
465
165
  */
@@ -468,6 +168,7 @@ function isExcludedFromDevTools(Type) {
468
168
  }
469
169
  /**
470
170
  * Get the per-class equality function set via `@blac({ equality })`, if any.
171
+ * Inherited: a subclass keeps a base class's equality fn unless overridden.
471
172
  * @param Type - The class constructor to check
472
173
  * @returns the equality function or `undefined`
473
174
  */
@@ -476,13 +177,26 @@ function getClassEquality(Type) {
476
177
  return typeof fn === "function" ? fn : void 0;
477
178
  }
478
179
  /**
180
+ * Get a class's minification-safe identity name.
181
+ * Own only: a subclass never inherits a base class's explicit `blacName`,
182
+ * since that would collapse two distinct blocs onto one identity.
183
+ * Falls back to `Type.name` (the runtime `constructor.name`) when unset.
184
+ * @param Type - The class constructor to check
185
+ * @returns the explicit `static blacName` or `Type.name`
186
+ */
187
+ function getBlacName(Type) {
188
+ return getOwnStaticProp(Type, BLAC_STATIC_PROPS.BLAC_NAME) ?? Type.name;
189
+ }
190
+ /**
479
191
  * Get the per-class key function set via `@blac({ key })` or `static key = …`, if any.
480
192
  * Used by the registry to derive a stable instance key from `args`.
193
+ * Own only: a subclass never inherits a base class's `key`, since a
194
+ * different `Args` shape would derive the wrong instance key.
481
195
  * @param Type - The class constructor to check
482
196
  * @returns the key function or `undefined`
483
197
  */
484
198
  function getClassKey(Type) {
485
- const fn = getStaticProp(Type, BLAC_STATIC_PROPS.KEY, void 0);
199
+ const fn = getOwnStaticProp(Type, BLAC_STATIC_PROPS.KEY);
486
200
  return typeof fn === "function" ? fn : void 0;
487
201
  }
488
202
  //#endregion
@@ -499,11 +213,24 @@ const DEFAULT_STRUCTURAL_KEY = "default";
499
213
  * - Object keys are sorted for order-independence.
500
214
  * - Arrays keep their insertion order.
501
215
  * - Functions throw — they must be passed via `deps`, not `args`.
216
+ * - Object args are memoized by identity; callers on hot paths (`resolveKey`,
217
+ * `depend`) re-resolve the same args object on every call.
502
218
  */
219
+ const keyCache = /* @__PURE__ */ new WeakMap();
503
220
  function structuralKey(args) {
504
221
  if (args === void 0 || args === null) return DEFAULT_STRUCTURAL_KEY;
222
+ if (typeof args === "object") {
223
+ const cached = keyCache.get(args);
224
+ if (cached !== void 0) return cached;
225
+ const key = serialize(args);
226
+ keyCache.set(args, key);
227
+ return key;
228
+ }
229
+ return serialize(args);
230
+ }
231
+ function serialize(args) {
505
232
  return JSON.stringify(args, function(_k, v) {
506
- if (typeof v === "function") throw new Error("[blac] args must be serializable; put refs/callbacks in `deps`");
233
+ if (typeof v === "function") throw new Error(`[blac] args must be serializable; put refs/callbacks in \`deps\` (found a function at key "${_k || "(root)"}")`);
507
234
  if (v && typeof v === "object" && !Array.isArray(v)) return Object.keys(v).sort().reduce((o, k) => {
508
235
  o[k] = v[k];
509
236
  return o;
@@ -515,7 +242,7 @@ function structuralKey(args) {
515
242
  //#region src/core/symbols.ts
516
243
  /**
517
244
  * @internal Apply one owner's (consumer's) deps slice to an instance.
518
- * Called by framework adapters (e.g. @blac/react useBloc) — not public API.
245
+ * Called by framework adapters (e.g. `@blac/react` useBloc) — not public API.
519
246
  */
520
247
  const APPLY_DEPS = Symbol("blac.applyDeps");
521
248
  /**
@@ -524,6 +251,12 @@ const APPLY_DEPS = Symbol("blac.applyDeps");
524
251
  */
525
252
  const REMOVE_DEPS_OWNER = Symbol("blac.removeDepsOwner");
526
253
  /**
254
+ * @internal Subscribe to this specific instance's own disposal, bypassing
255
+ * the global registry `'disposed'` event. Called by `watch()` — not public
256
+ * API.
257
+ */
258
+ const ON_DISPOSE = Symbol("blac.onDispose");
259
+ /**
527
260
  * @internal Symbol-keyed configuration entry point on `StateContainer`.
528
261
  * Writes the instance's identity fields (`_name`/`_debug`/`_instanceId`),
529
262
  * resolves per-class equality, emits the registry `created` event, and runs
@@ -533,8 +266,27 @@ const REMOVE_DEPS_OWNER = Symbol("blac.removeDepsOwner");
533
266
  * helpers are the sole callers, mirroring the `APPLY_DEPS` precedent.
534
267
  */
535
268
  const INIT_CONFIG = Symbol("blac.initConfig");
269
+ /**
270
+ * @internal Run `fn` with `state` temporarily reporting a render's tracking
271
+ * proxy, so getters record the paths they read. Called by `@blac/react`'s
272
+ * `buildTrackedProxy` — not public API.
273
+ *
274
+ * Replaces the old `this`-Proxy receiver: getters now run with `this` bound to
275
+ * the real instance, so ES `#private` works in user blocs.
276
+ */
277
+ const WITH_TRACKED_STATE = Symbol("blac.withTrackedState");
278
+ /**
279
+ * @internal Drive the activation lifecycle from the registry's 0↔1 ownership
280
+ * transition. Owns the per-instance AbortController so `onActivate`'s signal
281
+ * aborts on the matching deactivation and on dispose.
282
+ *
283
+ * Framework-only — `StateContainerRegistry` is the sole caller.
284
+ */
285
+ const SET_ACTIVE = Symbol("blac.setActive");
536
286
  //#endregion
537
287
  //#region src/core/StateContainerRegistry.ts
288
+ /** Shared, read-only Map returned by `getInstancesMap` for unregistered types. */
289
+ const EMPTY_INSTANCES_MAP = /* @__PURE__ */ new Map();
538
290
  /**
539
291
  * Central registry for managing StateContainer instances.
540
292
  * Handles instance lifecycle, named ref tracking, and lifecycle event emission.
@@ -548,19 +300,117 @@ const INIT_CONFIG = Symbol("blac.initConfig");
548
300
  * console.log('State changed:', prev, '->', next);
549
301
  * });
550
302
  * ```
303
+ *
304
+ * `stateChanged` fires once per emit (microtask-deferred but uncoalesced), so
305
+ * listeners see every intermediate transition. Plugins' `onStateChange` fires
306
+ * once per channel flush with coalesced `prev`/`next` and a `PathSet`. Pick
307
+ * the transition log for devtools/time-travel, the plugin hook for
308
+ * change-driven work.
551
309
  */
552
310
  var StateContainerRegistry = class {
311
+ /**
312
+ * Derived from the listener Set, never a parallel counter: `on()` with the
313
+ * same function twice adds once, so a counter cannot stay in step.
314
+ */
315
+ get hasStateChangedListeners() {
316
+ return (this.listeners.get("stateChanged")?.size ?? 0) > 0;
317
+ }
553
318
  constructor() {
554
319
  this.instancesByConstructor = /* @__PURE__ */ new WeakMap();
555
320
  this.types = /* @__PURE__ */ new Set();
556
- this.registeredTypeNames = /* @__PURE__ */ new Set();
321
+ this._dependentEdges = /* @__PURE__ */ new WeakMap();
322
+ this._entryById = /* @__PURE__ */ new Map();
557
323
  this.listeners = /* @__PURE__ */ new Map();
558
- this._stateChangedListenerCount = 0;
559
324
  this._pendingStateChanges = null;
560
325
  this._autoRefIdCounter = 0;
326
+ this.on("disposed", (container) => this._handleDisposed(container));
561
327
  }
562
- get hasStateChangedListeners() {
563
- return this._stateChangedListenerCount > 0;
328
+ /**
329
+ * Registry-internal reaction to any instance's `disposed` lifecycle event.
330
+ * If the disposing container is itself tracked in this registry (i.e. it
331
+ * was `acquire()`/`ensure()`'d, not a bare `new`), prune its Map entry and
332
+ * release its dependent edges on whatever it `depend()`'d on.
333
+ */
334
+ _handleDisposed(container) {
335
+ const Type = container.constructor;
336
+ if (!this._pruneEntry(Type, container)) return;
337
+ const edges = this._dependentEdges.get(container);
338
+ if (!edges) return;
339
+ this._dependentEdges.delete(container);
340
+ for (const [DepType, keys] of edges) for (const depKey of keys) this._releaseDependent(DepType, depKey, container);
341
+ }
342
+ /**
343
+ * An entry may be disposed only when nothing owns it: no public refs, no
344
+ * `depend()` dependents, and not keepAlive. Every dispose decision goes
345
+ * through here so `release()` and `_releaseDependent` cannot disagree.
346
+ */
347
+ _isUnowned(Type, entry) {
348
+ return this._hasNoOwners(entry) && !isKeepAliveClass(Type);
349
+ }
350
+ /**
351
+ * Pure ownership: refs plus `depend()` dependents, with no keepAlive term.
352
+ * `_isUnowned` is "may be disposed"; this is "nothing is using it", which is
353
+ * what the activation lifecycle keys off — a keepAlive instance still
354
+ * deactivates when its last owner goes.
355
+ */
356
+ _hasNoOwners(entry) {
357
+ return entry.refs.size === 0 && (entry.dependents === void 0 || entry.dependents.size === 0);
358
+ }
359
+ /**
360
+ * Single funnel for the 0↔1 ownership transition. `SET_ACTIVE` is idempotent,
361
+ * so every acquire/release path can call this unconditionally rather than
362
+ * each computing the edge itself. Emits `activated`/`deactivated` for
363
+ * plugins to observe the same transition `onActivate`/`onDeactivate` expose
364
+ * to bloc authors — after `SET_ACTIVE` has already run the instance's own
365
+ * hook, so plugins always see a container that has finished reacting.
366
+ */
367
+ _syncActivation(entry) {
368
+ const transition = entry.instance[SET_ACTIVE](!this._hasNoOwners(entry));
369
+ if (transition === "none") return;
370
+ if (transition === "deactivated") this.emit("deactivated", entry.instance);
371
+ else this.emit("activated", entry.instance, transition.signal);
372
+ }
373
+ /**
374
+ * Record that `dependent` resolved `Type` at `key`, so the edge can be
375
+ * released on the owner's disposal. Keyed per resolved key, not per type:
376
+ * one `depend()` handle resolves a distinct key for every `args` it is
377
+ * called with, and all of them must be swept.
378
+ */
379
+ _recordDependentEdge(dependent, Type, key) {
380
+ let byType = this._dependentEdges.get(dependent);
381
+ if (!byType) this._dependentEdges.set(dependent, byType = /* @__PURE__ */ new Map());
382
+ let keys = byType.get(Type);
383
+ if (!keys) byType.set(Type, keys = /* @__PURE__ */ new Set());
384
+ keys.add(key);
385
+ }
386
+ /**
387
+ * Remove the Map entry for `container`, if this registry is tracking it.
388
+ * @returns true if an entry was found and removed
389
+ */
390
+ _pruneEntry(Type, container) {
391
+ const id = container._instanceId;
392
+ if (id === void 0) return false;
393
+ const entry = this._entryById.get(id);
394
+ if (entry === void 0 || entry.instance !== container) return false;
395
+ this.instancesByConstructor.get(Type)?.delete(entry.key);
396
+ this._entryById.delete(id);
397
+ return true;
398
+ }
399
+ /**
400
+ * Drop `dependent`'s edge on the `Type`/`key` entry (a `depend()`-resolved
401
+ * instance). When that leaves the entry with no dependents AND no public
402
+ * refs AND it isn't keepAlive, dispose it — this recurses into the same
403
+ * `disposed` listener, so dep-of-dep chains unwind naturally.
404
+ */
405
+ _releaseDependent(Type, key, dependent) {
406
+ const entry = this.instancesByConstructor.get(Type)?.get(key);
407
+ if (!entry) return;
408
+ entry.dependents?.delete(dependent);
409
+ if (this._isUnowned(Type, entry)) {
410
+ if (!entry.instance.$blac.disposed) entry.instance.dispose();
411
+ return;
412
+ }
413
+ this._syncActivation(entry);
564
414
  }
565
415
  /**
566
416
  * Register a type for lifecycle event tracking
@@ -575,9 +425,7 @@ var StateContainerRegistry = class {
575
425
  * @throws Error if type is already registered
576
426
  */
577
427
  register(constructor) {
578
- const className = constructor.name;
579
- if (this.registeredTypeNames.has(className)) throw new Error(`${BLAC_ERROR_PREFIX} Type "${className}" is already registered`);
580
- this.registeredTypeNames.add(className);
428
+ if (this.types.has(constructor)) throw new Error(`${BLAC_ERROR_PREFIX} Type "${getBlacName(constructor)}" is already registered`);
581
429
  this.registerType(constructor);
582
430
  }
583
431
  ensureInstancesMap(Type) {
@@ -592,7 +440,7 @@ var StateContainerRegistry = class {
592
440
  * Get the instances Map for a specific class (public API for stats/debugging)
593
441
  */
594
442
  getInstancesMap(Type) {
595
- return this.instancesByConstructor.get(Type) || /* @__PURE__ */ new Map();
443
+ return this.instancesByConstructor.get(Type) || EMPTY_INSTANCES_MAP;
596
444
  }
597
445
  /**
598
446
  * Directly insert an instance entry into the registry without going through
@@ -609,10 +457,13 @@ var StateContainerRegistry = class {
609
457
  const instances = this.ensureInstancesMap(Type);
610
458
  const existingEntry = instances.get(instanceKey);
611
459
  if (existingEntry && existingEntry.instance !== instance && !existingEntry.instance.$blac.disposed) existingEntry.instance.dispose();
612
- instances.set(instanceKey, {
460
+ const entry = {
613
461
  instance,
462
+ key: instanceKey,
614
463
  refs
615
- });
464
+ };
465
+ instances.set(instanceKey, entry);
466
+ this._entryById.set(instance.$blac.id, entry);
616
467
  }
617
468
  /**
618
469
  * Resolve the storage key for an instance. This is the single source of truth
@@ -620,7 +471,7 @@ var StateContainerRegistry = class {
620
471
  * an args-derived key is never dropped (instance leaks).
621
472
  *
622
473
  * Resolution order (explicit beats derived):
623
- * explicit key > `static key(args)` > structural hash of args > default sentinel.
474
+ * explicit key \> `static key(args)` \> structural hash of args \> default sentinel.
624
475
  *
625
476
  * @param Type - The StateContainer class constructor
626
477
  * @param instanceKey - Explicit key, or undefined to derive from args
@@ -661,20 +512,19 @@ var StateContainerRegistry = class {
661
512
  * wrapper; `useBloc` and `watch` address a pre-resolved key here.
662
513
  * @param Type - The StateContainer class constructor
663
514
  * @param instanceKey - Pre-resolved instance key (defaults to 'default')
664
- * @param options - Acquisition options
665
- * @param options.canCreate - Whether to create new instance if not found (default: true)
666
- * @param options.countRef - Whether to add a reference (default: true)
667
- * @param options.refId - Named reference ID for debugging; auto-generated if omitted
515
+ * @param options - Acquisition options: `canCreate` (create when absent,
516
+ * default true), `countRef` (add a reference, default true), `refId`
517
+ * (named reference for debugging, auto-generated when omitted),
518
+ * `dependent` — the `depend()`-owner resolving this instance, recorded so
519
+ * its dependent edge is released on the owner's disposal — and
520
+ * `sweepIfUnowned`, for callers that create speculatively and claim
521
+ * ownership later (see {@link _scheduleSweep}).
668
522
  * @returns The state container instance
669
523
  */
670
524
  acquire(Type, instanceKey, options = {}) {
671
525
  const { canCreate = true, countRef = true } = options;
672
526
  const args = options.args;
673
527
  const resolvedKey = this.resolveKey(Type, instanceKey, args);
674
- const config = {
675
- instanceId: resolvedKey,
676
- args
677
- };
678
528
  const instances = this.ensureInstancesMap(Type);
679
529
  let entry = instances.get(resolvedKey);
680
530
  if (entry?.instance.$blac.disposed) {
@@ -682,9 +532,9 @@ var StateContainerRegistry = class {
682
532
  entry = void 0;
683
533
  }
684
534
  if (entry) {
685
- if (process.env.NODE_ENV !== "production" && args !== void 0 && entry.args !== void 0) {
535
+ if (IS_DEV && args !== void 0 && entry.args !== void 0) {
686
536
  const incomingKey = structuralKey(args);
687
- const storedKey = structuralKey(entry.args);
537
+ const storedKey = entry.argsKey ??= structuralKey(entry.args);
688
538
  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.`);
689
539
  }
690
540
  if (countRef) {
@@ -693,10 +543,20 @@ var StateContainerRegistry = class {
693
543
  this.assertRefLimit(Type, resolvedKey, entry.refs.size);
694
544
  this.emit("refAcquired", entry.instance, refId);
695
545
  }
546
+ if (options.dependent) {
547
+ (entry.dependents ??= /* @__PURE__ */ new Set()).add(options.dependent);
548
+ this._recordDependentEdge(options.dependent, Type, resolvedKey);
549
+ }
550
+ this._syncActivation(entry);
696
551
  return entry.instance;
697
552
  }
698
553
  if (!canCreate) throw new Error(`${BLAC_ERROR_PREFIX} ${Type.name} instance "${resolvedKey}" not found and creation is disabled.`);
699
554
  this.assertInstanceLimit(Type, instances.size);
555
+ const config = {
556
+ instanceId: resolvedKey,
557
+ args,
558
+ registry: this
559
+ };
700
560
  const instance = new Type();
701
561
  instance[INIT_CONFIG](config);
702
562
  const initialRefs = /* @__PURE__ */ new Map();
@@ -705,16 +565,45 @@ var StateContainerRegistry = class {
705
565
  initialRefId = options.refId ?? `_auto_${this._autoRefIdCounter++}`;
706
566
  initialRefs.set(initialRefId, 1);
707
567
  }
708
- instances.set(resolvedKey, {
568
+ const newEntry = {
709
569
  instance,
570
+ key: resolvedKey,
710
571
  refs: initialRefs,
711
572
  args
712
- });
573
+ };
574
+ if (options.dependent) {
575
+ (newEntry.dependents ??= /* @__PURE__ */ new Set()).add(options.dependent);
576
+ this._recordDependentEdge(options.dependent, Type, resolvedKey);
577
+ }
578
+ instances.set(resolvedKey, newEntry);
579
+ this._entryById.set(instance.$blac.id, newEntry);
713
580
  this.registerType(Type);
714
581
  if (initialRefId) this.emit("refAcquired", instance, initialRefId);
582
+ this._syncActivation(newEntry);
583
+ if (options.sweepIfUnowned && this._hasNoOwners(newEntry)) this._scheduleSweep(Type, newEntry);
715
584
  return instance;
716
585
  }
717
586
  /**
587
+ * Speculative creates (`useBloc`'s render-time create) leak forever when the
588
+ * render is discarded or never commits — SSR has no commit at all, so nothing
589
+ * ever claims ownership. Sweep at the end of the microtask unless the entry
590
+ * gained an owner or is keepAlive.
591
+ *
592
+ * Opt-in, not automatic: a bare `ensure()` hands the instance to a caller
593
+ * that legitimately holds it without a ref, and sweeping those would make
594
+ * `ensure` unusable across an `await`.
595
+ */
596
+ _scheduleSweep(Type, entry) {
597
+ queueMicrotask(() => {
598
+ const instances = this.instancesByConstructor.get(Type);
599
+ if (instances?.get(entry.key) !== entry) return;
600
+ if (entry.instance.$blac.disposed) return;
601
+ if (!this._isUnowned(Type, entry)) return;
602
+ entry.instance.dispose();
603
+ instances.delete(entry.key);
604
+ });
605
+ }
606
+ /**
718
607
  * Borrow an existing instance without adding a ref (borrowing semantics).
719
608
  * Tracks cross-bloc dependency for reactive updates.
720
609
  *
@@ -724,7 +613,7 @@ var StateContainerRegistry = class {
724
613
  * @returns The state container instance
725
614
  * @throws Error if instance doesn't exist
726
615
  */
727
- borrow(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
616
+ borrow(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
728
617
  return this.acquire(Type, instanceKey, {
729
618
  canCreate: false,
730
619
  countRef: false
@@ -739,7 +628,7 @@ var StateContainerRegistry = class {
739
628
  * @param instanceKey - Pre-resolved instance key (defaults to 'default')
740
629
  * @returns Discriminated union with either the instance or an error
741
630
  */
742
- borrowSafe(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
631
+ borrowSafe(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
743
632
  try {
744
633
  return {
745
634
  error: null,
@@ -785,7 +674,7 @@ var StateContainerRegistry = class {
785
674
  * @param forceDispose - Force immediate disposal regardless of refs
786
675
  * @param refId - The specific ref to remove; removes one arbitrary ref if omitted
787
676
  */
788
- release(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY, forceDispose = false, refId) {
677
+ release(Type, instanceKey = DEFAULT_STRUCTURAL_KEY, forceDispose = false, refId) {
789
678
  const instances = this.ensureInstancesMap(Type);
790
679
  const entry = instances.get(instanceKey);
791
680
  if (!entry) return;
@@ -810,20 +699,12 @@ var StateContainerRegistry = class {
810
699
  }
811
700
  }
812
701
  if (releasedRefId) this.emit("refReleased", entry.instance, releasedRefId);
813
- const keepAlive = isKeepAliveClass(Type);
814
- if (entry.refs.size === 0 && !keepAlive) {
815
- const deps = entry.instance.$blac.dependencies;
702
+ if (this._isUnowned(Type, entry)) {
816
703
  if (!entry.instance.$blac.disposed) entry.instance.dispose();
817
704
  instances.delete(instanceKey);
818
- for (const [DepType, depKey] of deps) {
819
- const depInstances = this.ensureInstancesMap(DepType);
820
- const depEntry = depInstances.get(depKey);
821
- if (depEntry && depEntry.refs.size === 0 && !isKeepAliveClass(DepType) && !depEntry.instance.$blac.disposed) {
822
- depEntry.instance.dispose();
823
- depInstances.delete(depKey);
824
- }
825
- }
705
+ return;
826
706
  }
707
+ this._syncActivation(entry);
827
708
  }
828
709
  /**
829
710
  * Get all instances of a specific type.
@@ -833,7 +714,7 @@ var StateContainerRegistry = class {
833
714
  getAll(Type) {
834
715
  const instances = this.ensureInstancesMap(Type);
835
716
  const result = [];
836
- for (const entry of instances.values()) result.push(entry.instance);
717
+ for (const entry of instances.values()) if (!entry.instance.$blac.disposed) result.push(entry.instance);
837
718
  return result;
838
719
  }
839
720
  /**
@@ -870,7 +751,7 @@ var StateContainerRegistry = class {
870
751
  * @param instanceKey - Pre-resolved instance key (defaults to 'default')
871
752
  * @returns Current ref count (0 if instance doesn't exist)
872
753
  */
873
- getRefCount(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
754
+ getRefCount(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
874
755
  return this.ensureInstancesMap(Type).get(instanceKey)?.refs.size ?? 0;
875
756
  }
876
757
  /**
@@ -881,13 +762,23 @@ var StateContainerRegistry = class {
881
762
  * @param instanceKey - Pre-resolved instance key (defaults to 'default')
882
763
  * @returns Array of ref ID strings (empty if instance doesn't exist)
883
764
  */
884
- getRefIds(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
765
+ getRefIds(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
885
766
  const instances = this.instancesByConstructor.get(Type);
886
767
  if (!instances) return [];
887
768
  const entry = instances.get(instanceKey);
888
769
  return entry ? Array.from(entry.refs.keys()) : [];
889
770
  }
890
771
  /**
772
+ * Get all active reference IDs for an instance by its `$blac.id`, without
773
+ * needing the owning Type. Used by `PluginContext.getRefIds`.
774
+ * @param instanceId - The instance's `$blac.id`
775
+ * @returns Array of ref ID strings (empty if instance doesn't exist)
776
+ */
777
+ getRefIdsById(instanceId) {
778
+ const entry = this._entryById.get(instanceId);
779
+ return entry ? Array.from(entry.refs.keys()) : [];
780
+ }
781
+ /**
891
782
  * Check if an instance exists.
892
783
  *
893
784
  * @internal Internal key tier; public callers use the args-based `hasInstance`.
@@ -895,7 +786,7 @@ var StateContainerRegistry = class {
895
786
  * @param instanceKey - Pre-resolved instance key (defaults to 'default')
896
787
  * @returns true if instance exists
897
788
  */
898
- hasInstance(Type, instanceKey = BLAC_DEFAULTS.DEFAULT_INSTANCE_KEY) {
789
+ hasInstance(Type, instanceKey = DEFAULT_STRUCTURAL_KEY) {
899
790
  return this.ensureInstancesMap(Type).has(instanceKey);
900
791
  }
901
792
  /**
@@ -907,7 +798,6 @@ var StateContainerRegistry = class {
907
798
  clearAll() {
908
799
  for (const Type of this.types) this.clear(Type);
909
800
  this.types.clear();
910
- this.registeredTypeNames.clear();
911
801
  }
912
802
  /**
913
803
  * Get registry statistics for debugging.
@@ -942,13 +832,14 @@ var StateContainerRegistry = class {
942
832
  * @returns Unsubscribe function
943
833
  */
944
834
  on(event, listener) {
945
- if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
946
- const instance = this.listeners.get(event);
947
- if (!instance) throw new Error(`${BLAC_ERROR_PREFIX} Failed to register listener for event '${event}'`);
835
+ let instance = this.listeners.get(event);
836
+ if (!instance) {
837
+ instance = /* @__PURE__ */ new Set();
838
+ this.listeners.set(event, instance);
839
+ }
948
840
  instance.add(listener);
949
- if (event === "stateChanged") this._stateChangedListenerCount++;
950
841
  return () => {
951
- if (this.listeners.get(event)?.delete(listener) && event === "stateChanged") this._stateChangedListenerCount--;
842
+ this.listeners.get(event)?.delete(listener);
952
843
  };
953
844
  }
954
845
  emit(event, ...args) {
@@ -966,7 +857,7 @@ var StateContainerRegistry = class {
966
857
  * @internal - Called by StateContainer.applyState
967
858
  */
968
859
  notifyStateChanged(container, previousState, newState) {
969
- if (this._stateChangedListenerCount === 0) return;
860
+ if (!this.hasStateChangedListeners) return;
970
861
  if (!this._pendingStateChanges) {
971
862
  this._pendingStateChanges = [];
972
863
  queueMicrotask(() => this.flushStateChanged());
@@ -977,6 +868,15 @@ var StateContainerRegistry = class {
977
868
  newState
978
869
  ]);
979
870
  }
871
+ /**
872
+ * Deliver one event per emit, in order — deliberately NOT coalesced.
873
+ *
874
+ * This is a transition log: devtools, time-travel debugging and perf
875
+ * monitoring need every intermediate `prev`/`next`, which coalescing would
876
+ * destroy. The plugin `onStateChange` bridge is the coalesced, `PathSet`-
877
+ * carrying lane (see `PluginManager.setupLifecycleHooks`); the two differ on
878
+ * purpose, so a listener picks the lane that matches what it needs.
879
+ */
980
880
  flushStateChanged() {
981
881
  const pending = this._pendingStateChanges;
982
882
  this._pendingStateChanges = null;
@@ -986,19 +886,9 @@ var StateContainerRegistry = class {
986
886
  };
987
887
  /**
988
888
  * Global default registry instance
889
+ * @public
989
890
  */
990
891
  const globalRegistry = new StateContainerRegistry();
991
- /**
992
- * Global plugin manager (initialized lazily)
993
- */
994
- let _globalPluginManager = null;
995
- /**
996
- * Get the global plugin manager
997
- */
998
- function getPluginManager() {
999
- if (!_globalPluginManager) _globalPluginManager = createPluginManager(globalRegistry);
1000
- return _globalPluginManager;
1001
- }
1002
892
  //#endregion
1003
893
  Object.defineProperty(exports, "APPLY_DEPS", {
1004
894
  enumerable: true,
@@ -1018,10 +908,16 @@ Object.defineProperty(exports, "INIT_CONFIG", {
1018
908
  return INIT_CONFIG;
1019
909
  }
1020
910
  });
1021
- Object.defineProperty(exports, "PluginManager", {
911
+ Object.defineProperty(exports, "IS_DEV", {
912
+ enumerable: true,
913
+ get: function() {
914
+ return IS_DEV;
915
+ }
916
+ });
917
+ Object.defineProperty(exports, "ON_DISPOSE", {
1022
918
  enumerable: true,
1023
919
  get: function() {
1024
- return PluginManager;
920
+ return ON_DISPOSE;
1025
921
  }
1026
922
  });
1027
923
  Object.defineProperty(exports, "REMOVE_DEPS_OWNER", {
@@ -1030,12 +926,24 @@ Object.defineProperty(exports, "REMOVE_DEPS_OWNER", {
1030
926
  return REMOVE_DEPS_OWNER;
1031
927
  }
1032
928
  });
929
+ Object.defineProperty(exports, "SET_ACTIVE", {
930
+ enumerable: true,
931
+ get: function() {
932
+ return SET_ACTIVE;
933
+ }
934
+ });
1033
935
  Object.defineProperty(exports, "StateContainerRegistry", {
1034
936
  enumerable: true,
1035
937
  get: function() {
1036
938
  return StateContainerRegistry;
1037
939
  }
1038
940
  });
941
+ Object.defineProperty(exports, "WITH_TRACKED_STATE", {
942
+ enumerable: true,
943
+ get: function() {
944
+ return WITH_TRACKED_STATE;
945
+ }
946
+ });
1039
947
  Object.defineProperty(exports, "configureBlac", {
1040
948
  enumerable: true,
1041
949
  get: function() {
@@ -1048,16 +956,16 @@ Object.defineProperty(exports, "getBlacConfig", {
1048
956
  return getBlacConfig;
1049
957
  }
1050
958
  });
1051
- Object.defineProperty(exports, "getClassEquality", {
959
+ Object.defineProperty(exports, "getBlacName", {
1052
960
  enumerable: true,
1053
961
  get: function() {
1054
- return getClassEquality;
962
+ return getBlacName;
1055
963
  }
1056
964
  });
1057
- Object.defineProperty(exports, "getPluginManager", {
965
+ Object.defineProperty(exports, "getClassEquality", {
1058
966
  enumerable: true,
1059
967
  get: function() {
1060
- return getPluginManager;
968
+ return getClassEquality;
1061
969
  }
1062
970
  });
1063
971
  Object.defineProperty(exports, "globalRegistry", {
@@ -1091,4 +999,4 @@ Object.defineProperty(exports, "shallowEqualState", {
1091
999
  }
1092
1000
  });
1093
1001
 
1094
- //# sourceMappingURL=StateContainerRegistry-lIwo-0m3.cjs.map
1002
+ //# sourceMappingURL=StateContainerRegistry-CBLeHObO.cjs.map