@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.
- package/README.md +19 -29
- package/dist/{StateContainerRegistry-CSNm2Ws5.cjs → StateContainerRegistry-141uHhtF.cjs} +300 -397
- package/dist/StateContainerRegistry-141uHhtF.cjs.map +1 -0
- package/dist/{StateContainerRegistry-DVIBTxGZ.js → StateContainerRegistry-BH7aOMWV.js} +299 -406
- package/dist/StateContainerRegistry-BH7aOMWV.js.map +1 -0
- package/dist/{StateContainerRegistry-Dj77cok9.js → StateContainerRegistry-BsH_s0pJ.js} +303 -413
- package/dist/StateContainerRegistry-BsH_s0pJ.js.map +1 -0
- package/dist/{StateContainerRegistry-lIwo-0m3.cjs → StateContainerRegistry-CBLeHObO.cjs} +326 -418
- package/dist/StateContainerRegistry-CBLeHObO.cjs.map +1 -0
- package/dist/{StateContainerRegistry-D9BIRjwv.cjs → StateContainerRegistry-MkT0W4C2.cjs} +322 -411
- package/dist/StateContainerRegistry-MkT0W4C2.cjs.map +1 -0
- package/dist/{StateContainerRegistry-CvBOhAT2.js → StateContainerRegistry-vWgsD-RP.js} +277 -392
- package/dist/StateContainerRegistry-vWgsD-RP.js.map +1 -0
- package/dist/config.d.cts +2 -2
- package/dist/config.d.ts +2 -2
- package/dist/constants.d.cts +4 -20
- package/dist/constants.d.ts +4 -20
- package/dist/core/Cubit.d.ts +14 -10
- package/dist/core/StateContainer.d.ts +68 -8
- package/dist/core/StateContainerRegistry.d.ts +100 -19
- package/dist/core/meta.d.ts +34 -47
- package/dist/core/symbols.d.ts +18 -1
- package/dist/decorators/blac.d.ts +28 -26
- package/dist/{ensure-BKdq5xsY.cjs → ensure-BjhbDW8Z.cjs} +223 -40
- package/dist/ensure-BjhbDW8Z.cjs.map +1 -0
- package/dist/ensure-BnVyd6s4.js +730 -0
- package/dist/ensure-BnVyd6s4.js.map +1 -0
- package/dist/ensure-CneMkpbI.js +731 -0
- package/dist/ensure-CneMkpbI.js.map +1 -0
- package/dist/{ensure-CQimeD0I.cjs → ensure-D15vGnk5.cjs} +348 -124
- package/dist/ensure-D15vGnk5.cjs.map +1 -0
- package/dist/{ensure-B1dEDsZV.cjs → ensure-D3w6YGp9.cjs} +349 -124
- package/dist/ensure-D3w6YGp9.cjs.map +1 -0
- package/dist/{ensure-B2DYCbbI.cjs → ensure-ORVw-WRE.cjs} +214 -39
- package/dist/ensure-ORVw-WRE.cjs.map +1 -0
- package/dist/{ensure-B59ri8oo.js → ensure-dsqWhIuO.js} +192 -39
- package/dist/ensure-dsqWhIuO.js.map +1 -0
- package/dist/{ensure-BSJHkUNX.js → ensure-k-6q7O25.js} +183 -38
- package/dist/ensure-k-6q7O25.js.map +1 -0
- package/dist/index.cjs +212 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -6
- package/dist/index.d.ts +4 -6
- package/dist/index.js +192 -12
- package/dist/index.js.map +1 -1
- package/dist/plugin/BlacPlugin.d.ts +14 -0
- package/dist/plugin/PluginManager.d.ts +21 -11
- package/dist/plugins.cjs +403 -3
- package/dist/plugins.cjs.map +1 -0
- package/dist/plugins.d.cts +6 -2
- package/dist/plugins.d.ts +6 -2
- package/dist/plugins.js +401 -1
- package/dist/plugins.js.map +1 -0
- package/dist/registry/acquire.d.ts +2 -2
- package/dist/registry/config.d.ts +9 -0
- package/dist/registry/management.d.ts +3 -0
- package/dist/registry/queries.d.ts +15 -0
- package/dist/registry/release.d.ts +3 -3
- package/dist/testing.cjs +13 -15
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +2 -4
- package/dist/testing.js.map +1 -1
- package/dist/types/branded.d.ts +3 -3
- package/dist/types/utilities.d.ts +45 -18
- package/dist/utils/static-props.d.ts +25 -1
- package/dist/utils/structural-key.d.ts +0 -7
- package/dist/watch/index.d.ts +1 -1
- package/dist/watch/watch.d.ts +22 -3
- package/package.json +16 -55
- package/dist/StateContainerRegistry-4Ljd9EJx.js +0 -1018
- package/dist/StateContainerRegistry-4Ljd9EJx.js.map +0 -1
- package/dist/StateContainerRegistry-8zNFMAf7.js +0 -1067
- package/dist/StateContainerRegistry-8zNFMAf7.js.map +0 -1
- package/dist/StateContainerRegistry-B1GYx9Xo.js +0 -975
- package/dist/StateContainerRegistry-B1GYx9Xo.js.map +0 -1
- package/dist/StateContainerRegistry-B_ViBLvJ.js +0 -975
- package/dist/StateContainerRegistry-B_ViBLvJ.js.map +0 -1
- package/dist/StateContainerRegistry-Bx2ly-ba.cjs +0 -1162
- package/dist/StateContainerRegistry-Bx2ly-ba.cjs.map +0 -1
- package/dist/StateContainerRegistry-C0dCNlTF.js +0 -1005
- package/dist/StateContainerRegistry-C0dCNlTF.js.map +0 -1
- package/dist/StateContainerRegistry-CEWpc1bj.cjs +0 -1113
- package/dist/StateContainerRegistry-CEWpc1bj.cjs.map +0 -1
- package/dist/StateContainerRegistry-CS18y4au.cjs +0 -1163
- package/dist/StateContainerRegistry-CS18y4au.cjs.map +0 -1
- package/dist/StateContainerRegistry-CSNm2Ws5.cjs.map +0 -1
- package/dist/StateContainerRegistry-CchRjnjX.cjs +0 -1094
- package/dist/StateContainerRegistry-CchRjnjX.cjs.map +0 -1
- package/dist/StateContainerRegistry-CvBOhAT2.js.map +0 -1
- package/dist/StateContainerRegistry-D8nu7Z0T.js +0 -1068
- package/dist/StateContainerRegistry-D8nu7Z0T.js.map +0 -1
- package/dist/StateContainerRegistry-D9BIRjwv.cjs.map +0 -1
- package/dist/StateContainerRegistry-DJQqLrli.cjs +0 -1046
- package/dist/StateContainerRegistry-DJQqLrli.cjs.map +0 -1
- package/dist/StateContainerRegistry-DVIBTxGZ.js.map +0 -1
- package/dist/StateContainerRegistry-Dj77cok9.js.map +0 -1
- package/dist/StateContainerRegistry-FWKzPfS-.cjs +0 -1046
- package/dist/StateContainerRegistry-FWKzPfS-.cjs.map +0 -1
- package/dist/StateContainerRegistry-lIwo-0m3.cjs.map +0 -1
- package/dist/acquire-B1LYXSGj.js +0 -35
- package/dist/acquire-B1LYXSGj.js.map +0 -1
- package/dist/acquire-B1Wuqla6.cjs +0 -46
- package/dist/acquire-B1Wuqla6.cjs.map +0 -1
- package/dist/acquire-BJ3xUEmh.cjs +0 -46
- package/dist/acquire-BJ3xUEmh.cjs.map +0 -1
- package/dist/acquire-BP-ZfUH2.js +0 -35
- package/dist/acquire-BP-ZfUH2.js.map +0 -1
- package/dist/acquire-BRIJVJaA.js +0 -35
- package/dist/acquire-BRIJVJaA.js.map +0 -1
- package/dist/acquire-BZIs6tpN.cjs +0 -46
- package/dist/acquire-BZIs6tpN.cjs.map +0 -1
- package/dist/acquire-B_p1fNEh.cjs +0 -46
- package/dist/acquire-B_p1fNEh.cjs.map +0 -1
- package/dist/acquire-BdsVZBSe.js +0 -35
- package/dist/acquire-BdsVZBSe.js.map +0 -1
- package/dist/acquire-C3nOb3XR.cjs +0 -46
- package/dist/acquire-C3nOb3XR.cjs.map +0 -1
- package/dist/acquire-C4WAS13G.cjs +0 -46
- package/dist/acquire-C4WAS13G.cjs.map +0 -1
- package/dist/acquire-C7beGI55.cjs +0 -46
- package/dist/acquire-C7beGI55.cjs.map +0 -1
- package/dist/acquire-CbMz7gWs.cjs +0 -46
- package/dist/acquire-CbMz7gWs.cjs.map +0 -1
- package/dist/acquire-CwB083au.js +0 -35
- package/dist/acquire-CwB083au.js.map +0 -1
- package/dist/acquire-DHEOEVcj.cjs +0 -46
- package/dist/acquire-DHEOEVcj.cjs.map +0 -1
- package/dist/acquire-DXEQDZGr.js +0 -35
- package/dist/acquire-DXEQDZGr.js.map +0 -1
- package/dist/acquire-Dg-uXwjF.js +0 -35
- package/dist/acquire-Dg-uXwjF.js.map +0 -1
- package/dist/acquire-Don7499S.js +0 -35
- package/dist/acquire-Don7499S.js.map +0 -1
- package/dist/acquire-G8aB3GAx.js +0 -35
- package/dist/acquire-G8aB3GAx.js.map +0 -1
- package/dist/branded-BybA0_xw.cjs +0 -18
- package/dist/branded-BybA0_xw.cjs.map +0 -1
- package/dist/branded-DkbUzncc.js +0 -13
- package/dist/branded-DkbUzncc.js.map +0 -1
- package/dist/config-8pyOC5gv.js +0 -16
- package/dist/config-8pyOC5gv.js.map +0 -1
- package/dist/config-B1l8ZGlU.cjs +0 -32
- package/dist/config-B1l8ZGlU.cjs.map +0 -1
- package/dist/config-C7Ktkjh2.cjs +0 -32
- package/dist/config-C7Ktkjh2.cjs.map +0 -1
- package/dist/config-COT4Xkbc.js +0 -16
- package/dist/config-COT4Xkbc.js.map +0 -1
- package/dist/config-CzcoAvBo.js +0 -16
- package/dist/config-CzcoAvBo.js.map +0 -1
- package/dist/config-D6BoWWPv.js +0 -16
- package/dist/config-D6BoWWPv.js.map +0 -1
- package/dist/config-DOO82lbZ.js +0 -16
- package/dist/config-DOO82lbZ.js.map +0 -1
- package/dist/config-DVM_ESQt.js +0 -16
- package/dist/config-DVM_ESQt.js.map +0 -1
- package/dist/config-DVMnlHLv.cjs +0 -32
- package/dist/config-DVMnlHLv.cjs.map +0 -1
- package/dist/config-DWUq7zEQ.cjs +0 -32
- package/dist/config-DWUq7zEQ.cjs.map +0 -1
- package/dist/config-DXI3kzmA.js +0 -16
- package/dist/config-DXI3kzmA.js.map +0 -1
- package/dist/config-Dk1FxfXj.js +0 -16
- package/dist/config-Dk1FxfXj.js.map +0 -1
- package/dist/config-b2z51Y_C.cjs +0 -32
- package/dist/config-b2z51Y_C.cjs.map +0 -1
- package/dist/config-buwpT7l3.js +0 -16
- package/dist/config-buwpT7l3.js.map +0 -1
- package/dist/config-cwaTn5oy.cjs +0 -32
- package/dist/config-cwaTn5oy.cjs.map +0 -1
- package/dist/config-d9_U58ef.cjs +0 -32
- package/dist/config-d9_U58ef.cjs.map +0 -1
- package/dist/config-hWTayX76.cjs +0 -32
- package/dist/config-hWTayX76.cjs.map +0 -1
- package/dist/config-sCPKPYxZ.cjs +0 -32
- package/dist/config-sCPKPYxZ.cjs.map +0 -1
- package/dist/debug.cjs +0 -14
- package/dist/debug.d.cts +0 -34
- package/dist/debug.d.ts +0 -34
- package/dist/debug.js +0 -4
- package/dist/ensure-B1dEDsZV.cjs.map +0 -1
- package/dist/ensure-B2DYCbbI.cjs.map +0 -1
- package/dist/ensure-B59ri8oo.js.map +0 -1
- package/dist/ensure-B9oOqpOB.cjs +0 -558
- package/dist/ensure-B9oOqpOB.cjs.map +0 -1
- package/dist/ensure-BKdq5xsY.cjs.map +0 -1
- package/dist/ensure-BSJHkUNX.js.map +0 -1
- package/dist/ensure-BfYnvAsI.js +0 -709
- package/dist/ensure-BfYnvAsI.js.map +0 -1
- package/dist/ensure-Bm91o37h.js +0 -536
- package/dist/ensure-Bm91o37h.js.map +0 -1
- package/dist/ensure-C1a8IFWy.js +0 -535
- package/dist/ensure-C1a8IFWy.js.map +0 -1
- package/dist/ensure-CLQEx0Nn.cjs +0 -528
- package/dist/ensure-CLQEx0Nn.cjs.map +0 -1
- package/dist/ensure-CPMiGvov.js +0 -493
- package/dist/ensure-CPMiGvov.js.map +0 -1
- package/dist/ensure-CQimeD0I.cjs.map +0 -1
- package/dist/ensure-DEXSmA-N.js +0 -526
- package/dist/ensure-DEXSmA-N.js.map +0 -1
- package/dist/ensure-DTTTd2h8.js +0 -536
- package/dist/ensure-DTTTd2h8.js.map +0 -1
- package/dist/ensure-Dx86f80B.cjs +0 -567
- package/dist/ensure-Dx86f80B.cjs.map +0 -1
- package/dist/ensure-DzqGej43.js +0 -526
- package/dist/ensure-DzqGej43.js.map +0 -1
- package/dist/ensure-VuiNPccZ.cjs +0 -559
- package/dist/ensure-VuiNPccZ.cjs.map +0 -1
- package/dist/ensure-_Oh8XPm4.cjs +0 -732
- package/dist/ensure-_Oh8XPm4.cjs.map +0 -1
- package/dist/ensure-fsQMjaQL.cjs +0 -567
- package/dist/ensure-fsQMjaQL.cjs.map +0 -1
- package/dist/ensure-vxAGa2v3.js +0 -536
- package/dist/ensure-vxAGa2v3.js.map +0 -1
- package/dist/management-14vsHr4t.cjs +0 -85
- package/dist/management-14vsHr4t.cjs.map +0 -1
- package/dist/management-444tdCBG.js +0 -38
- package/dist/management-444tdCBG.js.map +0 -1
- package/dist/management-7jLjVHJd.js +0 -38
- package/dist/management-7jLjVHJd.js.map +0 -1
- package/dist/management-AFuqQEwz.js +0 -38
- package/dist/management-AFuqQEwz.js.map +0 -1
- package/dist/management-BodVLAfJ.cjs +0 -85
- package/dist/management-BodVLAfJ.cjs.map +0 -1
- package/dist/management-BqGBdL01.js +0 -38
- package/dist/management-BqGBdL01.js.map +0 -1
- package/dist/management-BvUAjril.js +0 -38
- package/dist/management-BvUAjril.js.map +0 -1
- package/dist/management-C-WGcwPD.js +0 -38
- package/dist/management-C-WGcwPD.js.map +0 -1
- package/dist/management-C2wv4vGS.cjs +0 -85
- package/dist/management-C2wv4vGS.cjs.map +0 -1
- package/dist/management-D2pjr38g.js +0 -38
- package/dist/management-D2pjr38g.js.map +0 -1
- package/dist/management-D5WsZoTV.js +0 -38
- package/dist/management-D5WsZoTV.js.map +0 -1
- package/dist/management-DAV9sN-k.cjs +0 -85
- package/dist/management-DAV9sN-k.cjs.map +0 -1
- package/dist/management-Df7Y2sF1.cjs +0 -85
- package/dist/management-Df7Y2sF1.cjs.map +0 -1
- package/dist/management-DwO0nBzL.cjs +0 -85
- package/dist/management-DwO0nBzL.cjs.map +0 -1
- package/dist/management-GcnNJjnm.cjs +0 -85
- package/dist/management-GcnNJjnm.cjs.map +0 -1
- package/dist/management-P_Vi_cvw.js +0 -38
- package/dist/management-P_Vi_cvw.js.map +0 -1
- package/dist/management-QWLpbXlW.cjs +0 -85
- package/dist/management-QWLpbXlW.cjs.map +0 -1
- package/dist/management-iE4x6IQY.cjs +0 -85
- package/dist/management-iE4x6IQY.cjs.map +0 -1
- package/dist/types.cjs +0 -3
- package/dist/types.d.cts +0 -8
- package/dist/types.d.ts +0 -8
- package/dist/types.js +0 -2
- package/dist/watch-B0pUfh-v.cjs +0 -119
- package/dist/watch-B0pUfh-v.cjs.map +0 -1
- package/dist/watch-B91opmva.cjs +0 -70
- package/dist/watch-B91opmva.cjs.map +0 -1
- package/dist/watch-BHfCtbn1.cjs +0 -119
- package/dist/watch-BHfCtbn1.cjs.map +0 -1
- package/dist/watch-BmC-Tyqg.js +0 -59
- package/dist/watch-BmC-Tyqg.js.map +0 -1
- package/dist/watch-Bx0y-Kcc.js +0 -108
- package/dist/watch-Bx0y-Kcc.js.map +0 -1
- package/dist/watch-CKWt788i.cjs +0 -70
- package/dist/watch-CKWt788i.cjs.map +0 -1
- package/dist/watch-CfrSSrRY.js +0 -59
- package/dist/watch-CfrSSrRY.js.map +0 -1
- package/dist/watch-Cnh3PtQA.cjs +0 -70
- package/dist/watch-Cnh3PtQA.cjs.map +0 -1
- package/dist/watch-D01MdwKy.js +0 -108
- package/dist/watch-D01MdwKy.js.map +0 -1
- package/dist/watch-D8hwbFU1.js +0 -59
- package/dist/watch-D8hwbFU1.js.map +0 -1
- package/dist/watch-DPVUwPUY.js +0 -59
- package/dist/watch-DPVUwPUY.js.map +0 -1
- package/dist/watch-DVmvZBMD.cjs +0 -70
- package/dist/watch-DVmvZBMD.cjs.map +0 -1
- package/dist/watch-DkDGmGAx.cjs +0 -119
- package/dist/watch-DkDGmGAx.cjs.map +0 -1
- package/dist/watch-DttHqefn.js +0 -108
- package/dist/watch-DttHqefn.js.map +0 -1
- package/dist/watch-DxdIZjpt.cjs +0 -119
- package/dist/watch-DxdIZjpt.cjs.map +0 -1
- package/dist/watch-DztcJuUy.cjs +0 -119
- package/dist/watch-DztcJuUy.cjs.map +0 -1
- package/dist/watch-IrR8G28N.js +0 -108
- package/dist/watch-IrR8G28N.js.map +0 -1
- package/dist/watch-entry.cjs +0 -4
- package/dist/watch-entry.d.cts +0 -24
- package/dist/watch-entry.d.ts +0 -24
- package/dist/watch-entry.js +0 -2
- package/dist/watch-hx0r92Zc.js +0 -108
- package/dist/watch-hx0r92Zc.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/registry/borrow.ts","../src/registry/release.ts","../src/decorators/blac.ts"],"sourcesContent":["import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n} from '../types/utilities';\n\n/**\n * How to identify the instance to borrow: the same `args` shape `useBloc`\n * accepts, resolved through the class's `static key(args)` so call sites use\n * the *same* shape they pass to `useBloc` instead of hand-deriving the key.\n *\n * `borrowSafe(ChannelBloc, { args: { channelId } })` and\n * `useBloc(ChannelBloc, { args: { channelId } })` therefore always agree.\n */\nexport type BorrowTarget<T extends StateContainerConstructor> = {\n args?: ExtractArgs<T>;\n};\n\nfunction resolveBorrowKey<T extends StateContainerConstructor>(\n BlocClass: T,\n target: BorrowTarget<T> | undefined,\n): string {\n return getRegistry().resolveKey(BlocClass, undefined, target?.args);\n}\n\nexport function borrow<T extends StateContainerConstructor>(\n BlocClass: T,\n target?: BorrowTarget<T>,\n): InstanceType<T> {\n return getRegistry().borrow(BlocClass, resolveBorrowKey(BlocClass, target));\n}\n\nexport function borrowSafe<T extends StateContainerConstructor>(\n BlocClass: T,\n target?: BorrowTarget<T>,\n):\n | { error: Error; instance: null }\n | { error: null; instance: InstanceType<T> } {\n return getRegistry().borrowSafe(\n BlocClass,\n resolveBorrowKey(BlocClass, target),\n );\n}\n","import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n} from '../types/utilities';\n\n/**\n * Release a reference to an instance. Instance identity is derived purely from\n * `args` (must match the `args` it was acquired with).\n *\n * @param opts.args - Construction/identity args; derives the instance key\n * @param opts.refId - The specific ref to drop; drops one arbitrary ref if omitted\n * @param opts.forceDispose - Force immediate disposal regardless of refs\n */\nexport function release<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T>; refId?: string; forceDispose?: boolean },\n): void {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n registry.release(BlocClass, key, opts?.forceDispose ?? false, opts?.refId);\n}\n","import { BLAC_STATIC_PROPS } from '../constants';\nimport type { EqualityFn } from '../config';\n\n/**\n * Configuration options for the @blac decorator.\n * Only one option can be specified at a time (union type).\n */\nexport type BlacOptions =\n /** Mark bloc to never be auto-disposed when ref count reaches 0 */\n | { keepAlive: true }\n /** Exclude bloc from DevTools tracking (prevents infinite loops) */\n | { excludeFromDevTools: true }\n /**\n * Override the global equality check for this bloc.\n * Return `true` to skip the emit (states considered equal).\n */\n | { equality: EqualityFn }\n /**\n * Override the default structural-hash identity key.\n * The function receives `args` and returns the instance key string.\n * Distinct return values → distinct instances; identical values → shared instance.\n * Non-identity fields can be excluded so they don't fork new instances.\n */\n | { key: (args: any) => string };\n\n/**\n * Decorator to configure StateContainer classes.\n *\n * @example Decorator syntax (requires experimentalDecorators or TC39 decorators)\n * ```ts\n * @blac({ keepAlive: true })\n * class AuthBloc extends Cubit<AuthState> {}\n *\n * @blac({ excludeFromDevTools: true })\n * class InternalBloc extends Cubit<InternalState> {}\n * ```\n *\n * @example Function syntax (no decorator support needed)\n * ```ts\n * const AuthBloc = blac({ keepAlive: true })(\n * class extends Cubit<AuthState> {}\n * );\n * ```\n */\nexport function blac(options: BlacOptions) {\n return function <T extends new (...args: any[]) => any>(\n target: T,\n _context?: ClassDecoratorContext,\n ): T {\n if ('keepAlive' in options && options.keepAlive) {\n (target as any)[BLAC_STATIC_PROPS.KEEP_ALIVE] = true;\n }\n if ('excludeFromDevTools' in options && options.excludeFromDevTools) {\n (target as any)[BLAC_STATIC_PROPS.EXCLUDE_FROM_DEVTOOLS] = true;\n }\n if ('equality' in options && typeof options.equality === 'function') {\n (target as any)[BLAC_STATIC_PROPS.EQUALITY] = options.equality;\n }\n if ('key' in options && typeof options.key === 'function') {\n (target as any)[BLAC_STATIC_PROPS.KEY] = options.key;\n }\n return target;\n };\n}\n"],"mappings":";;;;;;;;;AAkBA,SAAS,iBACP,WACA,QACQ;CACR,OAAO,YAAY,EAAE,WAAW,WAAW,KAAA,GAAW,QAAQ,IAAI;AACpE;AAEA,SAAgB,OACd,WACA,QACiB;CACjB,OAAO,YAAY,EAAE,OAAO,WAAW,iBAAiB,WAAW,MAAM,CAAC;AAC5E;AAEA,SAAgB,WACd,WACA,QAG6C;CAC7C,OAAO,YAAY,EAAE,WACnB,WACA,iBAAiB,WAAW,MAAM,CACpC;AACF;;;;;;;;;;;AC5BA,SAAgB,QACd,WACA,MACM;CACN,MAAM,WAAW,YAAY;CAC7B,MAAM,MAAM,SAAS,WAAW,WAAW,KAAA,GAAW,MAAM,IAAI;CAChE,SAAS,QAAQ,WAAW,KAAK,MAAM,gBAAgB,OAAO,MAAM,KAAK;AAC3E;;;;;;;;;;;;;;;;;;;;;;ACuBA,SAAgB,KAAK,SAAsB;CACzC,OAAO,SACL,QACA,UACG;EACH,IAAI,eAAe,WAAW,QAAQ,WACpC,OAAgB,kBAAkB,cAAc;EAElD,IAAI,yBAAyB,WAAW,QAAQ,qBAC9C,OAAgB,kBAAkB,yBAAyB;EAE7D,IAAI,cAAc,WAAW,OAAO,QAAQ,aAAa,YACvD,OAAgB,kBAAkB,YAAY,QAAQ;EAExD,IAAI,SAAS,WAAW,OAAO,QAAQ,QAAQ,YAC7C,OAAgB,kBAAkB,OAAO,QAAQ;EAEnD,OAAO;CACT;AACF"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["ALL_PATHS"],"sources":["../src/registry/borrow.ts","../src/registry/release.ts","../src/registry/queries.ts","../src/registry/management.ts","../src/decorators/blac.ts","../src/watch/watch.ts","../src/types/branded.ts"],"sourcesContent":["import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n} from '../types/utilities';\n\n/**\n * How to identify the instance to borrow: the same `args` shape `useBloc`\n * accepts, resolved through the class's `static key(args)` so call sites use\n * the *same* shape they pass to `useBloc` instead of hand-deriving the key.\n *\n * `borrowSafe(ChannelBloc, { args: { channelId } })` and\n * `useBloc(ChannelBloc, { args: { channelId } })` therefore always agree.\n */\nexport type BorrowTarget<T extends StateContainerConstructor> = {\n args?: ExtractArgs<T>;\n};\n\nfunction resolveBorrowKey<T extends StateContainerConstructor>(\n BlocClass: T,\n target: BorrowTarget<T> | undefined,\n): string {\n return getRegistry().resolveKey(BlocClass, undefined, target?.args);\n}\n\nexport function borrow<T extends StateContainerConstructor>(\n BlocClass: T,\n target?: BorrowTarget<T>,\n): InstanceType<T> {\n return getRegistry().borrow(BlocClass, resolveBorrowKey(BlocClass, target));\n}\n\nexport function borrowSafe<T extends StateContainerConstructor>(\n BlocClass: T,\n target?: BorrowTarget<T>,\n):\n | { error: Error; instance: null }\n | { error: null; instance: InstanceType<T> } {\n return getRegistry().borrowSafe(\n BlocClass,\n resolveBorrowKey(BlocClass, target),\n );\n}\n","import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n} from '../types/utilities';\n\n/**\n * Release a reference to an instance. Instance identity is derived purely from\n * `args` (must match the `args` it was acquired with).\n *\n * @param opts - `args` derives the instance key (must match acquire);\n * `refId` picks the ref to drop, otherwise one arbitrary ref goes;\n * `forceDispose` disposes immediately regardless of remaining refs.\n */\nexport function release<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T>; refId?: string; forceDispose?: boolean },\n): void {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n registry.release(BlocClass, key, opts?.forceDispose ?? false, opts?.refId);\n}\n","import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n InstanceReadonlyState,\n} from '../types/utilities';\n\n/**\n * @public\n */\nexport function hasInstance<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T> },\n): boolean {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.hasInstance(BlocClass, key);\n}\n\n/**\n * @public\n */\nexport function getRefCount<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T> },\n): number {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.getRefCount(BlocClass, key);\n}\n\n/**\n * @public\n */\nexport function getRefIds<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T> },\n): string[] {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.getRefIds(BlocClass, key);\n}\n\n/**\n * @public\n */\nexport function getAll<T extends StateContainerConstructor>(\n BlocClass: T,\n): InstanceReadonlyState<T>[] {\n return getRegistry().getAll(BlocClass);\n}\n\n/**\n * @public\n */\nexport function forEach<T extends StateContainerConstructor>(\n BlocClass: T,\n callback: (instance: InstanceReadonlyState<T>) => void,\n): void {\n getRegistry().forEach(BlocClass, callback);\n}\n","import { getRegistry } from './config';\nimport type { StateContainerConstructor } from '../types/utilities';\n\nexport function clear<T extends StateContainerConstructor>(BlocClass: T): void {\n getRegistry().clear(BlocClass);\n}\n\nexport function clearAll(): void {\n getRegistry().clearAll();\n}\n\n/**\n * @public\n */\nexport function register<T extends StateContainerConstructor>(\n BlocClass: T,\n): void {\n getRegistry().register(BlocClass);\n}\n","import { BLAC_STATIC_PROPS } from '../constants';\nimport type { EqualityFn } from '../config';\n\n/**\n * Configuration options for the `@blac` decorator. Any combination may be\n * given at once.\n */\nexport interface BlacOptions {\n /** Mark bloc to never be auto-disposed when ref count reaches 0 */\n keepAlive?: true;\n /** Exclude bloc from DevTools tracking (prevents infinite loops) */\n excludeFromDevTools?: true;\n /**\n * Override the global equality check for this bloc.\n * Return `true` to skip the emit (states considered equal).\n */\n equality?: EqualityFn;\n /**\n * Override the default structural-hash identity key.\n * The function receives `args` and returns the instance key string.\n * Distinct return values → distinct instances; identical values → shared instance.\n * Non-identity fields can be excluded so they don't fork new instances.\n */\n key?: (args: any) => string;\n /**\n * Explicit, minification-safe identity for this bloc class. Falls back to\n * `constructor.name` when unset, which collides across classes once a\n * minifier shortens class names.\n */\n name?: string;\n}\n\n/**\n * Decorator to configure StateContainer classes.\n *\n * @example Decorator syntax (requires experimentalDecorators or TC39 decorators)\n * ```ts\n * @blac({ keepAlive: true })\n * class AuthBloc extends Cubit<AuthState> {}\n *\n * @blac({ excludeFromDevTools: true })\n * class InternalBloc extends Cubit<InternalState> {}\n *\n * @blac({ keepAlive: true, key: (args) => args.userId })\n * class UserBloc extends Cubit<UserState> {}\n * ```\n *\n * @example Function syntax (no decorator support needed)\n * ```ts\n * const AuthBloc = blac({ keepAlive: true })(\n * class extends Cubit<AuthState> {}\n * );\n * ```\n */\nexport function blac(options: BlacOptions) {\n return function <T extends new (...args: any[]) => any>(\n target: T,\n _context?: ClassDecoratorContext,\n ): T {\n if ('keepAlive' in options && options.keepAlive) {\n (target as any)[BLAC_STATIC_PROPS.KEEP_ALIVE] = true;\n }\n if ('excludeFromDevTools' in options && options.excludeFromDevTools) {\n (target as any)[BLAC_STATIC_PROPS.EXCLUDE_FROM_DEVTOOLS] = true;\n }\n if ('equality' in options && typeof options.equality === 'function') {\n (target as any)[BLAC_STATIC_PROPS.EQUALITY] = options.equality;\n }\n if ('key' in options && typeof options.key === 'function') {\n (target as any)[BLAC_STATIC_PROPS.KEY] = options.key;\n }\n if ('name' in options && typeof options.name === 'string') {\n (target as any)[BLAC_STATIC_PROPS.BLAC_NAME] = options.name;\n }\n return target;\n };\n}\n","import { ALL_PATHS } from '@dirtytalk/structural';\nimport { ON_DISPOSE } from '../core/symbols';\nimport { getRegistry } from '../registry';\nimport { resolveInstanceKey } from '../registry/acquire';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n StateContainerInstance,\n} from '../types/utilities';\n\nconst STOP: unique symbol = Symbol('watch.STOP');\ntype StopSymbol = typeof STOP;\n\nconst BLOC_REF_MARKER = Symbol('BlocRef');\n\n/**\n * Reference to a specific bloc instance, identified by class + the resolved\n * key derived from its `args`.\n */\nexport interface BlocRef<T extends StateContainerConstructor> {\n [BLOC_REF_MARKER]: true;\n blocClass: T;\n /** @internal The resolved instance key (derived from `args`). */\n instanceId: string;\n /**\n * @internal The `args` used to derive `instanceId`. Forwarded to\n * `registry.acquire` so a not-yet-created instance is initialized with\n * these args instead of `undefined`.\n */\n args?: unknown;\n}\n\n/**\n * Create a reference to a specific bloc instance, identified by its `args`.\n * The key is derived the same way `useBloc`/`acquire` derive it.\n *\n * @example\n * ```ts\n * watch(instance(UserBloc, { userId: 'user-123' }), (userBloc) => {\n * console.log(userBloc.state.name);\n * });\n * ```\n */\nexport function instance<T extends StateContainerConstructor>(\n BlocClass: T,\n args?: ExtractArgs<T>,\n): BlocRef<T> {\n return {\n [BLOC_REF_MARKER]: true,\n blocClass: BlocClass,\n instanceId: resolveInstanceKey(BlocClass, args),\n args,\n };\n}\n\nfunction isBlocRef(\n input: unknown,\n): input is BlocRef<StateContainerConstructor> {\n return (\n typeof input === 'object' && input !== null && BLOC_REF_MARKER in input\n );\n}\n\ntype BlocInput = StateContainerConstructor | BlocRef<StateContainerConstructor>;\n\n/**\n * Options for `watch`.\n */\nexport interface WatchOptions {\n /**\n * When `false`, `watch` observes passively instead of owning the instance:\n * it does not create a missing instance, and it does not take a real\n * ownership ref on an existing one (so it never keeps an otherwise-\n * unreferenced instance alive).\n *\n * If the instance does not exist yet when `watch` is called, the callback\n * simply never fires for it — `watch` does not poll or wait for a later\n * creation. Call `watch` again once the instance is known to exist (e.g.\n * after some other owner has acquired it).\n *\n * Defaults to `true`.\n */\n create?: boolean;\n}\n\ntype ExtractInstance<T> =\n T extends BlocRef<infer C>\n ? InstanceType<C>\n : T extends StateContainerConstructor\n ? InstanceType<T>\n : never;\n\ntype ExtractInstances<T extends readonly BlocInput[]> = {\n [K in keyof T]: ExtractInstance<T[K]>;\n};\n\n/**\n * Watch function signature for single bloc.\n */\nexport interface WatchSingleFn {\n <T extends StateContainerConstructor>(\n bloc: T | BlocRef<T>,\n callback: (bloc: InstanceType<T>) => void | StopSymbol,\n options?: WatchOptions,\n ): () => void;\n\n STOP: StopSymbol;\n}\n\n/**\n * Watch function signature for multiple blocs.\n */\nexport interface WatchMultipleFn {\n <T extends readonly BlocInput[]>(\n blocs: T,\n callback: (blocs: ExtractInstances<T>) => void | StopSymbol,\n options?: WatchOptions,\n ): () => void;\n\n STOP: StopSymbol;\n}\n\n/**\n * Combined watch function type.\n */\nexport interface WatchFn extends WatchSingleFn {\n <T extends readonly BlocInput[]>(\n blocs: T,\n callback: (blocs: ExtractInstances<T>) => void | StopSymbol,\n options?: WatchOptions,\n ): () => void;\n}\n\n/**\n * Class + resolved key + args needed to (re-)acquire a specific instance.\n * `watch` keeps this around per input so it can re-acquire the same logical\n * instance if the underlying container is disposed elsewhere.\n */\ninterface WatchTarget {\n blocClass: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\nfunction toWatchTarget(input: BlocInput): WatchTarget {\n const registry = getRegistry();\n if (isBlocRef(input)) {\n return {\n blocClass: input.blocClass,\n key: input.instanceId,\n args: input.args,\n };\n }\n return {\n blocClass: input,\n key: registry.resolveKey(input, undefined, undefined),\n args: undefined,\n };\n}\n\nlet watchRefSeq = 0;\n\n/**\n * Acquire a real ref (countRef: true) for the target, carrying its `args`\n * through so a not-yet-created instance is initialized correctly. The\n * caller is responsible for releasing `refId` in cleanup.\n */\nfunction resolveBloc(\n target: WatchTarget,\n refId: string,\n): StateContainerInstance {\n const registry = getRegistry();\n return registry.acquire(target.blocClass, target.key, {\n countRef: true,\n refId,\n args: target.args,\n });\n}\n\n/**\n * Passive lookup for `{ create: false }`: never creates the instance and\n * never takes a ref. Returns `undefined` when no instance currently exists.\n */\nfunction resolveBlocPassive(\n target: WatchTarget,\n): StateContainerInstance | undefined {\n const registry = getRegistry();\n try {\n return registry.acquire(target.blocClass, target.key, {\n canCreate: false,\n countRef: false,\n args: target.args,\n });\n } catch {\n return undefined;\n }\n}\n\nfunction isArray(input: unknown): input is readonly BlocInput[] {\n return Array.isArray(input);\n}\n\n/**\n * Watch one or more blocs for state changes.\n *\n * Thin wrapper around `container.channel.subscribe(ALL_PATHS, ...)`. The\n * callback fires once immediately, then on every state change of any of the\n * passed blocs. Returning `watch.STOP` from the callback tears down all\n * subscriptions.\n *\n * Note: subscriptions are microtask-deferred (per the DirtyChannel default\n * scheduler), so callbacks land asynchronously after `emit()`.\n *\n * By default `watch` creates the instance if it does not exist and holds a\n * real ownership ref until `unwatch`, matching `acquire`. Pass\n * `{ create: false }` to observe passively instead — see `WatchOptions`.\n *\n * @example Single bloc\n * ```ts\n * const unwatch = watch(UserBloc, (userBloc) => {\n * console.log(userBloc.state.name);\n * });\n * ```\n *\n * @example Multiple blocs\n * ```ts\n * const unwatch = watch(\n * [UserBloc, SettingsBloc] as const,\n * ([userBloc, settingsBloc]) => {\n * console.log(userBloc.state.name, settingsBloc.state.theme);\n * }\n * );\n * ```\n *\n * @example With specific instance\n * ```ts\n * const unwatch = watch(\n * instance(UserBloc, { userId: 'user-123' }),\n * (userBloc) => {\n * console.log(userBloc.state.name);\n * }\n * );\n * ```\n *\n * @example Stop watching from callback\n * ```ts\n * const unwatch = watch(UserBloc, (userBloc) => {\n * if (userBloc.state.done) {\n * return watch.STOP;\n * }\n * });\n * ```\n */\nfunction watchImpl<T extends StateContainerConstructor>(\n bloc: T | BlocRef<T>,\n callback: (bloc: InstanceType<T>) => void | StopSymbol,\n options?: WatchOptions,\n): () => void;\n\nfunction watchImpl<T extends readonly BlocInput[]>(\n blocs: T,\n callback: (blocs: ExtractInstances<T>) => void | StopSymbol,\n options?: WatchOptions,\n): () => void;\n\nfunction watchImpl(\n blocsOrBloc: BlocInput | readonly BlocInput[],\n callback: (blocsOrBloc: any) => void | StopSymbol,\n options?: WatchOptions,\n): () => void {\n const isSingle = !isArray(blocsOrBloc);\n const inputs = isSingle ? [blocsOrBloc] : blocsOrBloc;\n const registry = getRegistry();\n const create = options?.create ?? true;\n\n const targets = inputs.map(toWatchTarget);\n const refIds = targets.map(() => `_watch_${watchRefSeq++}`);\n\n let disposed = false;\n const instances: Array<StateContainerInstance | undefined> = targets.map(\n (target, i) =>\n create ? resolveBloc(target, refIds[i]) : resolveBlocPassive(target),\n );\n const channelUnsubs: Array<(() => void) | undefined> = [];\n const disposedUnsubs: Array<(() => void) | undefined> = [];\n\n const cleanup = () => {\n if (disposed) return;\n disposed = true;\n for (const unsub of channelUnsubs) unsub?.();\n for (const unsub of disposedUnsubs) unsub?.();\n channelUnsubs.length = 0;\n disposedUnsubs.length = 0;\n if (create) {\n for (let i = 0; i < targets.length; i++) {\n registry.release(\n targets[i].blocClass,\n targets[i].key,\n false,\n refIds[i],\n );\n }\n }\n };\n\n const runCallback = () => {\n if (disposed) return;\n if (instances.some((i) => i === undefined)) return;\n const arg = isSingle ? instances[0] : instances;\n const result = callback(arg);\n if (result === STOP) cleanup();\n };\n\n const subscribeAt = (index: number) => {\n channelUnsubs[index] = instances[index]?.channel.subscribe(\n () => ALL_PATHS,\n runCallback,\n );\n };\n\n // On external dispose, tear down the stale subscription and re-acquire +\n // resubscribe (microtask-deferred, so it never runs mid-mutation of the\n // registry that triggered the dispose, e.g. `clearAll()`), then notify.\n const resubscribeAt = (index: number) => {\n if (disposed) return;\n instances[index] = create\n ? resolveBloc(targets[index], refIds[index])\n : resolveBlocPassive(targets[index]);\n subscribeAt(index);\n subscribeDisposeAt(index);\n runCallback();\n };\n\n const subscribeDisposeAt = (index: number) => {\n disposedUnsubs[index] = instances[index]?.[ON_DISPOSE](() => {\n channelUnsubs[index]?.();\n channelUnsubs[index] = undefined;\n queueMicrotask(() => resubscribeAt(index));\n });\n };\n\n for (let i = 0; i < instances.length; i++) {\n subscribeAt(i);\n }\n\n for (let i = 0; i < instances.length; i++) {\n subscribeDisposeAt(i);\n }\n\n // Fire once immediately so the consumer sees the current state.\n runCallback();\n\n return cleanup;\n}\n\nexport const watch: WatchFn = Object.assign(watchImpl, { STOP }) as WatchFn;\n","declare const brand: unique symbol;\n\n/**\n * Utility type for creating branded/nominal types.\n * Prevents accidental type confusion between similar primitive types.\n * @typeParam T - The base type\n * @typeParam B - The brand identifier\n */\nexport type Brand<T, B> = T & { [brand]: B };\n\n/**\n * Branded string type for type-safe IDs.\n * @typeParam B - The brand identifier\n */\nexport type BrandedId<B> = Brand<string, B>;\n\n/**\n * Branded string type for state container instance IDs\n */\nexport type InstanceId = Brand<string, 'InstanceId'>;\n\n/**\n * Create a branded InstanceId from a string\n * @param id - The string ID to brand\n * @returns Branded InstanceId\n */\nexport function instanceId(id: string): InstanceId {\n return id as InstanceId;\n}\n"],"mappings":"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"}
|
|
@@ -99,6 +99,20 @@ export interface BlacPlugin {
|
|
|
99
99
|
* Fires when a container's hydration status transitions.
|
|
100
100
|
*/
|
|
101
101
|
onHydrationChange?(ctx: PluginContext, status: HydrationStatus, previousStatus: HydrationStatus): void;
|
|
102
|
+
/**
|
|
103
|
+
* Fires on the same 0↔1 ownership transition that drives the container's
|
|
104
|
+
* own `onActivate`, after that hook has already run. `signal` aborts on the
|
|
105
|
+
* matching `onDeactivate` and on dispose — dispose does NOT fire
|
|
106
|
+
* `onDeactivate` (or this hook), matching `StateContainer`'s single
|
|
107
|
+
* teardown path.
|
|
108
|
+
*/
|
|
109
|
+
onActivate?(ctx: PluginContext, signal: AbortSignal): void;
|
|
110
|
+
/**
|
|
111
|
+
* Fires when an active container goes back to unowned while surviving in
|
|
112
|
+
* the registry (i.e. `keepAlive`), after the container's own `onDeactivate`
|
|
113
|
+
* has already run. Not fired on dispose.
|
|
114
|
+
*/
|
|
115
|
+
onDeactivate?(ctx: PluginContext): void;
|
|
102
116
|
/**
|
|
103
117
|
* @internal Devtools-only — fires when a named ref is acquired.
|
|
104
118
|
* Not part of the state-change event payload.
|
|
@@ -6,7 +6,8 @@ import type { BlacPlugin, PluginConfig } from './BlacPlugin';
|
|
|
6
6
|
* The manager hooks into two surfaces:
|
|
7
7
|
*
|
|
8
8
|
* 1. **Registry lifecycle events** (`created`, `disposed`, `refAcquired`,
|
|
9
|
-
* `refReleased`, `depsChanged`) — synchronous,
|
|
9
|
+
* `refReleased`, `depsChanged`, `activated`, `deactivated`) — synchronous,
|
|
10
|
+
* fired by the registry.
|
|
10
11
|
*
|
|
11
12
|
* 2. **Per-container channel flushes** — microtask-coalesced. For each
|
|
12
13
|
* container, the manager subscribes once at create-time with
|
|
@@ -34,10 +35,20 @@ export declare class PluginManager {
|
|
|
34
35
|
private lifecycleUnsubscribers;
|
|
35
36
|
/**
|
|
36
37
|
* Per-container channel-bridge bookkeeping. Subscribed at `created`,
|
|
37
|
-
* torn down at `disposed`. Holds the rolling `prevState`
|
|
38
|
-
* manager hands plugins on each flush.
|
|
38
|
+
* torn down at `disposed` and at `destroy()`. Holds the rolling `prevState`
|
|
39
|
+
* snapshot the manager hands plugins on each flush.
|
|
40
|
+
*
|
|
41
|
+
* Strong, not a WeakMap, because `destroy()` has to enumerate the live
|
|
42
|
+
* bridges to unsubscribe them. Safe: entries are removed on `disposed`, and
|
|
43
|
+
* the owning registry holds its own strong reference over that same window.
|
|
39
44
|
*/
|
|
40
45
|
private containerBridges;
|
|
46
|
+
/**
|
|
47
|
+
* One `PluginContext` per container. The context closes over `registry` and
|
|
48
|
+
* `container` only — both stable for the container's lifetime — so it is
|
|
49
|
+
* safe to reuse instead of rebuilding its method table on every dispatch.
|
|
50
|
+
*/
|
|
51
|
+
private contextCache;
|
|
41
52
|
/**
|
|
42
53
|
* Create a new PluginManager
|
|
43
54
|
* @param registry - The StateContainerRegistry to monitor for lifecycle events
|
|
@@ -93,8 +104,9 @@ export declare class PluginManager {
|
|
|
93
104
|
* previous flush (or at create-time for the first flush) and pass the
|
|
94
105
|
* channel's `paths` argument straight through to plugins.
|
|
95
106
|
*
|
|
96
|
-
* Per-container bookkeeping is stored in a `
|
|
97
|
-
* container
|
|
107
|
+
* Per-container bookkeeping is stored in `containerBridges`, a strong `Map`
|
|
108
|
+
* keyed by the container. Entries are removed explicitly — on the
|
|
109
|
+
* container's `disposed` event, or for all of them in `destroy()`.
|
|
98
110
|
*/
|
|
99
111
|
private attachStateBridge;
|
|
100
112
|
private detachStateBridge;
|
|
@@ -130,14 +142,12 @@ export declare class PluginManager {
|
|
|
130
142
|
*/
|
|
131
143
|
private notifyPlugins;
|
|
132
144
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* This sidesteps the lifetime question of "when do we evict a cached
|
|
137
|
-
* context?" and keeps `paths`-vs-`container` lifetimes cleanly
|
|
138
|
-
* separated.
|
|
145
|
+
* Get the `PluginContext` for a focal container, building it once and
|
|
146
|
+
* reusing it thereafter. Eviction is handled by the `WeakMap`: the context
|
|
147
|
+
* dies with the container. `undefined` (install-time) is never cached.
|
|
139
148
|
*/
|
|
140
149
|
private buildContext;
|
|
150
|
+
private createContext;
|
|
141
151
|
/**
|
|
142
152
|
* Check if plugin should be enabled based on environment
|
|
143
153
|
*/
|
package/dist/plugins.cjs
CHANGED
|
@@ -1,4 +1,404 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_StateContainerRegistry = require("./StateContainerRegistry-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const require_StateContainerRegistry = require("./StateContainerRegistry-CBLeHObO.cjs");
|
|
3
|
+
let _dirtytalk_structural = require("@dirtytalk/structural");
|
|
4
|
+
//#region src/plugin/PluginManager.ts
|
|
5
|
+
/**
|
|
6
|
+
* Manages plugin lifecycle for the BlaC state management system.
|
|
7
|
+
*
|
|
8
|
+
* The manager hooks into two surfaces:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Registry lifecycle events** (`created`, `disposed`, `refAcquired`,
|
|
11
|
+
* `refReleased`, `depsChanged`, `activated`, `deactivated`) — synchronous,
|
|
12
|
+
* fired by the registry.
|
|
13
|
+
*
|
|
14
|
+
* 2. **Per-container channel flushes** — microtask-coalesced. For each
|
|
15
|
+
* container, the manager subscribes once at create-time with
|
|
16
|
+
* `ALL_PATHS` interest and stashes `prevState`. On every flush it
|
|
17
|
+
* captures the new state + the changed `PathSet` and dispatches
|
|
18
|
+
* `onStateChange(ctx, prev, next, paths)` to every enabled plugin.
|
|
19
|
+
*
|
|
20
|
+
* The `ALL_PATHS` subscription cost: plugins counted as a consumer with
|
|
21
|
+
* `ALL_PATHS` interest will defeat the single-consumer-skip optimization
|
|
22
|
+
* in `StructuralContainer`. This is the intended trade-off — devtools /
|
|
23
|
+
* persist plugins genuinely want every change, and stateful plugins
|
|
24
|
+
* (logging) can decode `paths` via `container.interner.lookup(id)` to log
|
|
25
|
+
* only relevant fields. Plugins that want low overhead should remain
|
|
26
|
+
* uninstalled or environment-gated.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const manager = createPluginManager(registry);
|
|
31
|
+
* manager.install(myPlugin, { environment: 'development' });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
var PluginManager = class {
|
|
35
|
+
/**
|
|
36
|
+
* Create a new PluginManager
|
|
37
|
+
* @param registry - The StateContainerRegistry to monitor for lifecycle events
|
|
38
|
+
*/
|
|
39
|
+
constructor(registry) {
|
|
40
|
+
this.plugins = /* @__PURE__ */ new Map();
|
|
41
|
+
this.lifecycleUnsubscribers = [];
|
|
42
|
+
this.containerBridges = /* @__PURE__ */ new Map();
|
|
43
|
+
this.contextCache = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
this.registry = registry;
|
|
45
|
+
this.setupLifecycleHooks();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Install a plugin with optional configuration
|
|
49
|
+
* @param plugin - The plugin to install
|
|
50
|
+
* @param config - Optional plugin configuration
|
|
51
|
+
* @throws Error if plugin is already installed
|
|
52
|
+
*/
|
|
53
|
+
install(plugin, config = {}) {
|
|
54
|
+
const effectiveConfig = {
|
|
55
|
+
enabled: true,
|
|
56
|
+
environment: "all",
|
|
57
|
+
...config
|
|
58
|
+
};
|
|
59
|
+
if (!this.shouldEnablePlugin(effectiveConfig)) {
|
|
60
|
+
if (require_StateContainerRegistry.IS_DEV) console.log(`[BlaC] Plugin "${plugin.name}" skipped (environment mismatch)`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (this.plugins.has(plugin.name)) throw new Error(`Plugin "${plugin.name}" is already installed`);
|
|
64
|
+
const installContext = this.buildContext(void 0);
|
|
65
|
+
this.plugins.set(plugin.name, {
|
|
66
|
+
plugin,
|
|
67
|
+
config: effectiveConfig,
|
|
68
|
+
installContext
|
|
69
|
+
});
|
|
70
|
+
this.backfillPlugin(this.plugins.get(plugin.name));
|
|
71
|
+
if (plugin.onInstall) try {
|
|
72
|
+
plugin.onInstall(installContext);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(`[BlaC] Error installing plugin "${plugin.name}":`, error);
|
|
75
|
+
this.plugins.delete(plugin.name);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
if (require_StateContainerRegistry.IS_DEV) console.log(`[BlaC] Plugin "${plugin.name}" v${plugin.version} installed`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Uninstall a plugin by name
|
|
82
|
+
* @param pluginName - The name of the plugin to uninstall
|
|
83
|
+
* @throws Error if plugin is not installed
|
|
84
|
+
*/
|
|
85
|
+
uninstall(pluginName) {
|
|
86
|
+
const installed = this.plugins.get(pluginName);
|
|
87
|
+
if (!installed) throw new Error(`Plugin "${pluginName}" is not installed`);
|
|
88
|
+
if (installed.plugin.onUninstall) try {
|
|
89
|
+
installed.plugin.onUninstall();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(`[BlaC] Error uninstalling plugin "${pluginName}":`, error);
|
|
92
|
+
}
|
|
93
|
+
this.plugins.delete(pluginName);
|
|
94
|
+
if (require_StateContainerRegistry.IS_DEV) console.log(`[BlaC] Plugin "${pluginName}" uninstalled`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get an installed plugin by name
|
|
98
|
+
* @param pluginName - The name of the plugin to retrieve
|
|
99
|
+
* @returns The plugin instance or undefined if not found
|
|
100
|
+
*/
|
|
101
|
+
getPlugin(pluginName) {
|
|
102
|
+
return this.plugins.get(pluginName)?.plugin;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get all installed plugins
|
|
106
|
+
* @returns Array of all installed plugins
|
|
107
|
+
*/
|
|
108
|
+
getAllPlugins() {
|
|
109
|
+
return Array.from(this.plugins.values()).map((p) => p.plugin);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if a plugin is installed
|
|
113
|
+
* @param pluginName - The name of the plugin to check
|
|
114
|
+
* @returns true if the plugin is installed
|
|
115
|
+
*/
|
|
116
|
+
hasPlugin(pluginName) {
|
|
117
|
+
return this.plugins.has(pluginName);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Uninstall all plugins
|
|
121
|
+
*/
|
|
122
|
+
clear() {
|
|
123
|
+
for (const name of this.plugins.keys()) this.uninstall(name);
|
|
124
|
+
}
|
|
125
|
+
destroy() {
|
|
126
|
+
this.clear();
|
|
127
|
+
for (const unsub of this.lifecycleUnsubscribers) unsub();
|
|
128
|
+
this.lifecycleUnsubscribers = [];
|
|
129
|
+
for (const bridge of this.containerBridges.values()) bridge.unsub();
|
|
130
|
+
this.containerBridges.clear();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Wire registry lifecycle events into plugin dispatch.
|
|
134
|
+
*
|
|
135
|
+
* `onStateChange` is NOT wired through `registry.on('stateChanged', …)` —
|
|
136
|
+
* that event lacks the `PathSet` payload. Instead, on each `created` we
|
|
137
|
+
* subscribe to the container's channel directly so we get
|
|
138
|
+
* `(paths)` and capture `(prev, next)` via the per-container snapshot.
|
|
139
|
+
*/
|
|
140
|
+
setupLifecycleHooks() {
|
|
141
|
+
this.lifecycleUnsubscribers = [
|
|
142
|
+
this.registry.on("created", (instance) => {
|
|
143
|
+
this.attachStateBridge(instance);
|
|
144
|
+
this.notifyPlugins("onCreated", instance);
|
|
145
|
+
}),
|
|
146
|
+
this.registry.on("disposed", (instance) => {
|
|
147
|
+
this.notifyPlugins("onDestroyed", instance);
|
|
148
|
+
this.detachStateBridge(instance);
|
|
149
|
+
}),
|
|
150
|
+
this.registry.on("refAcquired", (instance, refId) => {
|
|
151
|
+
this.notifyPlugins("onRefAcquired", instance, refId);
|
|
152
|
+
}),
|
|
153
|
+
this.registry.on("refReleased", (instance, refId) => {
|
|
154
|
+
this.notifyPlugins("onRefReleased", instance, refId);
|
|
155
|
+
}),
|
|
156
|
+
this.registry.on("depsChanged", (instance, previousDeps, currentDeps) => {
|
|
157
|
+
this.notifyPlugins("onDepsChanged", instance, previousDeps, currentDeps);
|
|
158
|
+
}),
|
|
159
|
+
this.registry.on("hydrationChanged", (instance, status, previousStatus) => {
|
|
160
|
+
this.notifyPlugins("onHydrationChange", instance, status, previousStatus);
|
|
161
|
+
}),
|
|
162
|
+
this.registry.on("activated", (instance, signal) => {
|
|
163
|
+
this.notifyPlugins("onActivate", instance, signal);
|
|
164
|
+
}),
|
|
165
|
+
this.registry.on("deactivated", (instance) => {
|
|
166
|
+
this.notifyPlugins("onDeactivate", instance);
|
|
167
|
+
})
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Subscribe to the container's channel with `ALL_PATHS` interest, so we
|
|
172
|
+
* fire on every flush. Capture `prev` from the snapshot taken on the
|
|
173
|
+
* previous flush (or at create-time for the first flush) and pass the
|
|
174
|
+
* channel's `paths` argument straight through to plugins.
|
|
175
|
+
*
|
|
176
|
+
* Per-container bookkeeping is stored in `containerBridges`, a strong `Map`
|
|
177
|
+
* keyed by the container. Entries are removed explicitly — on the
|
|
178
|
+
* container's `disposed` event, or for all of them in `destroy()`.
|
|
179
|
+
*/
|
|
180
|
+
attachStateBridge(container) {
|
|
181
|
+
if (this.containerBridges.has(container)) return;
|
|
182
|
+
const bridge = {
|
|
183
|
+
unsub: () => {},
|
|
184
|
+
prevState: container.state
|
|
185
|
+
};
|
|
186
|
+
this.containerBridges.set(container, bridge);
|
|
187
|
+
bridge.unsub = container.channel.subscribe(() => _dirtytalk_structural.ALL_PATHS, (paths) => this.dispatchStateChange(container, paths));
|
|
188
|
+
}
|
|
189
|
+
detachStateBridge(container) {
|
|
190
|
+
const bridge = this.containerBridges.get(container);
|
|
191
|
+
if (!bridge) return;
|
|
192
|
+
bridge.unsub();
|
|
193
|
+
this.containerBridges.delete(container);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Backfill a newly-installed plugin with the app's existing instances.
|
|
197
|
+
*
|
|
198
|
+
* Iterates every registered `Type` and its live instances (`getAll`
|
|
199
|
+
* already skips disposed), attaching the state bridge (idempotent — see
|
|
200
|
+
* `attachStateBridge`) and, if the plugin implements `onCreated`, invoking
|
|
201
|
+
* it directly for each instance. Scoped to this single plugin only — the
|
|
202
|
+
* broadcast `notifyPlugins` would re-notify *every* plugin of *every*
|
|
203
|
+
* instance, which is wrong here.
|
|
204
|
+
*/
|
|
205
|
+
backfillPlugin(installed) {
|
|
206
|
+
const { plugin } = installed;
|
|
207
|
+
for (const Type of this.registry.getTypes()) for (const instance of this.registry.getAll(Type)) {
|
|
208
|
+
const container = instance;
|
|
209
|
+
this.attachStateBridge(container);
|
|
210
|
+
if (!plugin.onCreated) continue;
|
|
211
|
+
try {
|
|
212
|
+
plugin.onCreated(this.buildContext(container));
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error(`[BlaC] Error in plugin "${plugin.name}" onCreated (backfill):`, error);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Channel-flush callback. Snapshots `next`, hands `(prev, next, paths)`
|
|
220
|
+
* to every enabled plugin's `onStateChange`, then updates `prevState`
|
|
221
|
+
* for the next flush.
|
|
222
|
+
*
|
|
223
|
+
* Note: `prev` is captured once and reused across plugins — every
|
|
224
|
+
* plugin sees the same `prev`/`next`/`paths` regardless of dispatch
|
|
225
|
+
* order, matching the "snapshot prev once per flush" invariant.
|
|
226
|
+
*/
|
|
227
|
+
dispatchStateChange(container, paths) {
|
|
228
|
+
const bridge = this.containerBridges.get(container);
|
|
229
|
+
if (!bridge) return;
|
|
230
|
+
const prev = bridge.prevState;
|
|
231
|
+
const next = container.state;
|
|
232
|
+
bridge.prevState = next;
|
|
233
|
+
let ctx;
|
|
234
|
+
for (const { plugin, config } of this.plugins.values()) {
|
|
235
|
+
if (!config.enabled) continue;
|
|
236
|
+
const hook = plugin.onStateChange;
|
|
237
|
+
if (typeof hook !== "function") continue;
|
|
238
|
+
try {
|
|
239
|
+
ctx ??= this.buildContext(container);
|
|
240
|
+
hook.call(plugin, ctx, prev, next, paths);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error(`[BlaC] Error in plugin "${plugin.name}" onStateChange:`, error);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Notify all plugins of a lifecycle event.
|
|
248
|
+
*
|
|
249
|
+
* Builds a fresh `PluginContext` per dispatch (instance becomes
|
|
250
|
+
* `ctx.container`), so plugins can reach the focal bloc through
|
|
251
|
+
* `ctx.container`. The context is built lazily on the first matching
|
|
252
|
+
* hook and reused for every other plugin in this dispatch — zero
|
|
253
|
+
* builds if no enabled plugin implements `hookName`.
|
|
254
|
+
*/
|
|
255
|
+
notifyPlugins(hookName, instance, ...extraArgs) {
|
|
256
|
+
let ctx;
|
|
257
|
+
for (const { plugin, config } of this.plugins.values()) {
|
|
258
|
+
if (!config.enabled) continue;
|
|
259
|
+
const hook = plugin[hookName];
|
|
260
|
+
if (typeof hook !== "function") continue;
|
|
261
|
+
try {
|
|
262
|
+
ctx ??= this.buildContext(instance);
|
|
263
|
+
hook.call(plugin, ctx, ...extraArgs);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error(`[BlaC] Error in plugin "${plugin.name}" ${hookName}:`, error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get the `PluginContext` for a focal container, building it once and
|
|
271
|
+
* reusing it thereafter. Eviction is handled by the `WeakMap`: the context
|
|
272
|
+
* dies with the container. `undefined` (install-time) is never cached.
|
|
273
|
+
*/
|
|
274
|
+
buildContext(container) {
|
|
275
|
+
if (container !== void 0) {
|
|
276
|
+
const cached = this.contextCache.get(container);
|
|
277
|
+
if (cached) return cached;
|
|
278
|
+
}
|
|
279
|
+
const context = this.createContext(container);
|
|
280
|
+
if (container !== void 0) this.contextCache.set(container, context);
|
|
281
|
+
return context;
|
|
282
|
+
}
|
|
283
|
+
createContext(container) {
|
|
284
|
+
const registry = this.registry;
|
|
285
|
+
return {
|
|
286
|
+
container,
|
|
287
|
+
getInstanceMetadata: (instance) => {
|
|
288
|
+
return {
|
|
289
|
+
id: instance.$blac.id,
|
|
290
|
+
className: require_StateContainerRegistry.getBlacName(instance.constructor),
|
|
291
|
+
isDisposed: instance.$blac.disposed,
|
|
292
|
+
name: instance.$blac.name,
|
|
293
|
+
createdAt: instance.$blac.createdAt,
|
|
294
|
+
state: instance.state,
|
|
295
|
+
hydrationStatus: instance.$blac.hydration.status,
|
|
296
|
+
isHydrated: instance.$blac.hydration.isHydrated,
|
|
297
|
+
hydrationError: instance.$blac.hydration.error,
|
|
298
|
+
changedWhileHydrating: instance.$blac.hydration.changedWhileHydrating,
|
|
299
|
+
args: instance.args
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
getState: (instance) => {
|
|
303
|
+
return instance.state;
|
|
304
|
+
},
|
|
305
|
+
getHydrationStatus: (instance) => {
|
|
306
|
+
return instance.$blac.hydration.status;
|
|
307
|
+
},
|
|
308
|
+
startHydration: (instance) => {
|
|
309
|
+
instance.$blac.hydration.begin();
|
|
310
|
+
},
|
|
311
|
+
applyHydratedState: (instance, state) => {
|
|
312
|
+
return instance.$blac.hydration.apply(state);
|
|
313
|
+
},
|
|
314
|
+
finishHydration: (instance) => {
|
|
315
|
+
instance.$blac.hydration.finish();
|
|
316
|
+
},
|
|
317
|
+
failHydration: (instance, error) => {
|
|
318
|
+
instance.$blac.hydration.fail(error);
|
|
319
|
+
},
|
|
320
|
+
waitForHydration: (instance) => {
|
|
321
|
+
return instance.$blac.hydration.wait();
|
|
322
|
+
},
|
|
323
|
+
queryInstances: (typeClass) => {
|
|
324
|
+
return registry.getAll(typeClass);
|
|
325
|
+
},
|
|
326
|
+
getAllTypes: () => {
|
|
327
|
+
return registry.getTypes();
|
|
328
|
+
},
|
|
329
|
+
getStats: () => {
|
|
330
|
+
return registry.getStats();
|
|
331
|
+
},
|
|
332
|
+
getRefIds: (instanceId) => {
|
|
333
|
+
return registry.getRefIdsById(instanceId);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Check if plugin should be enabled based on environment
|
|
339
|
+
*/
|
|
340
|
+
shouldEnablePlugin(config) {
|
|
341
|
+
if (!config.enabled) return false;
|
|
342
|
+
if (config.environment === "all") return true;
|
|
343
|
+
return this.getCurrentEnvironment() === config.environment;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Get current environment
|
|
347
|
+
*/
|
|
348
|
+
getCurrentEnvironment() {
|
|
349
|
+
if (typeof process !== "undefined") {
|
|
350
|
+
if (process.env.NODE_ENV === "test") return "test";
|
|
351
|
+
if (process.env.NODE_ENV === "production") return "production";
|
|
352
|
+
return "development";
|
|
353
|
+
}
|
|
354
|
+
return "development";
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
/**
|
|
358
|
+
* Create a plugin manager instance
|
|
359
|
+
* @param registry - The StateContainerRegistry to monitor for lifecycle events
|
|
360
|
+
* @returns A new PluginManager instance
|
|
361
|
+
*/
|
|
362
|
+
function createPluginManager(registry) {
|
|
363
|
+
return new PluginManager(registry);
|
|
364
|
+
}
|
|
365
|
+
//#endregion
|
|
366
|
+
//#region src/plugins.ts
|
|
367
|
+
/**
|
|
368
|
+
* Plugins Subpath Export
|
|
369
|
+
*
|
|
370
|
+
* Plugin system utilities for extending BlaC functionality.
|
|
371
|
+
* Import from `@blac/core/plugins`
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* ```typescript
|
|
375
|
+
* import { PluginManager, getPluginManager, type BlacPlugin } from '@blac/core/plugins';
|
|
376
|
+
*
|
|
377
|
+
* const myPlugin: BlacPlugin = {
|
|
378
|
+
* name: 'my-plugin',
|
|
379
|
+
* onCreated(context) {
|
|
380
|
+
* console.log('Instance created:', context.container);
|
|
381
|
+
* },
|
|
382
|
+
* };
|
|
383
|
+
*
|
|
384
|
+
* getPluginManager().register(myPlugin);
|
|
385
|
+
* ```
|
|
386
|
+
*
|
|
387
|
+
* @packageDocumentation
|
|
388
|
+
*/
|
|
389
|
+
/**
|
|
390
|
+
* Global plugin manager (initialized lazily)
|
|
391
|
+
*/
|
|
392
|
+
let _globalPluginManager = null;
|
|
393
|
+
/**
|
|
394
|
+
* Get the global plugin manager
|
|
395
|
+
*/
|
|
396
|
+
function getPluginManager() {
|
|
397
|
+
if (!_globalPluginManager) _globalPluginManager = createPluginManager(require_StateContainerRegistry.globalRegistry);
|
|
398
|
+
return _globalPluginManager;
|
|
399
|
+
}
|
|
400
|
+
//#endregion
|
|
401
|
+
exports.PluginManager = PluginManager;
|
|
402
|
+
exports.getPluginManager = getPluginManager;
|
|
403
|
+
|
|
404
|
+
//# sourceMappingURL=plugins.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins.cjs","names":["IS_DEV","ALL_PATHS","getBlacName","globalRegistry"],"sources":["../src/plugin/PluginManager.ts","../src/plugins.ts"],"sourcesContent":["import { ALL_PATHS, type PathSet } from '@dirtytalk/structural';\nimport type { StateContainer } from '../core/StateContainer';\nimport { IS_DEV } from '../constants';\nimport { getBlacName } from '../utils/static-props';\nimport type { StateContainerConstructor } from '../types/utilities';\nimport type { StateContainerRegistry } from '../core/StateContainerRegistry';\nimport type {\n BlacPlugin,\n PluginContext,\n PluginConfig,\n InstanceMetadata,\n} from './BlacPlugin';\n\n/**\n * Internal structure for tracking installed plugins.\n *\n * `installContext` is the context handed to `onInstall` — it has no\n * `container`. Per-container contexts are built on demand by\n * `buildContext()` so each event carries the right focal container\n * without mutating a shared object.\n * @internal\n */\ninterface InstalledPlugin {\n plugin: BlacPlugin;\n config: PluginConfig;\n installContext: PluginContext;\n}\n\n/**\n * Per-container bookkeeping for the channel-bridge plugin dispatcher.\n *\n * - `unsub` tears down the channel subscription on dispose.\n * - `prevState` is the state snapshot the manager will hand plugins as\n * `prev` on the next flush; it is updated to the post-flush state after\n * each dispatch.\n * @internal\n */\ninterface ContainerBridge {\n unsub: () => void;\n prevState: any;\n}\n\n/**\n * Manages plugin lifecycle for the BlaC state management system.\n *\n * The manager hooks into two surfaces:\n *\n * 1. **Registry lifecycle events** (`created`, `disposed`, `refAcquired`,\n * `refReleased`, `depsChanged`, `activated`, `deactivated`) — synchronous,\n * fired by the registry.\n *\n * 2. **Per-container channel flushes** — microtask-coalesced. For each\n * container, the manager subscribes once at create-time with\n * `ALL_PATHS` interest and stashes `prevState`. On every flush it\n * captures the new state + the changed `PathSet` and dispatches\n * `onStateChange(ctx, prev, next, paths)` to every enabled plugin.\n *\n * The `ALL_PATHS` subscription cost: plugins counted as a consumer with\n * `ALL_PATHS` interest will defeat the single-consumer-skip optimization\n * in `StructuralContainer`. This is the intended trade-off — devtools /\n * persist plugins genuinely want every change, and stateful plugins\n * (logging) can decode `paths` via `container.interner.lookup(id)` to log\n * only relevant fields. Plugins that want low overhead should remain\n * uninstalled or environment-gated.\n *\n * @example\n * ```ts\n * const manager = createPluginManager(registry);\n * manager.install(myPlugin, { environment: 'development' });\n * ```\n */\nexport class PluginManager {\n private plugins = new Map<string, InstalledPlugin>();\n private registry: StateContainerRegistry;\n private lifecycleUnsubscribers: (() => void)[] = [];\n\n /**\n * Per-container channel-bridge bookkeeping. Subscribed at `created`,\n * torn down at `disposed` and at `destroy()`. Holds the rolling `prevState`\n * snapshot the manager hands plugins on each flush.\n *\n * Strong, not a WeakMap, because `destroy()` has to enumerate the live\n * bridges to unsubscribe them. Safe: entries are removed on `disposed`, and\n * the owning registry holds its own strong reference over that same window.\n */\n private containerBridges = new Map<\n StateContainer<any, any, any>,\n ContainerBridge\n >();\n\n /**\n * One `PluginContext` per container. The context closes over `registry` and\n * `container` only — both stable for the container's lifetime — so it is\n * safe to reuse instead of rebuilding its method table on every dispatch.\n */\n private contextCache = new WeakMap<\n StateContainer<any, any, any>,\n PluginContext\n >();\n\n /**\n * Create a new PluginManager\n * @param registry - The StateContainerRegistry to monitor for lifecycle events\n */\n constructor(registry: StateContainerRegistry) {\n this.registry = registry;\n this.setupLifecycleHooks();\n }\n\n /**\n * Install a plugin with optional configuration\n * @param plugin - The plugin to install\n * @param config - Optional plugin configuration\n * @throws Error if plugin is already installed\n */\n install(plugin: BlacPlugin, config: PluginConfig = {}): void {\n const effectiveConfig: PluginConfig = {\n enabled: true,\n environment: 'all',\n ...config,\n };\n\n if (!this.shouldEnablePlugin(effectiveConfig)) {\n if (IS_DEV) {\n console.log(\n `[BlaC] Plugin \"${plugin.name}\" skipped (environment mismatch)`,\n );\n }\n return;\n }\n\n if (this.plugins.has(plugin.name)) {\n throw new Error(`Plugin \"${plugin.name}\" is already installed`);\n }\n\n const installContext = this.buildContext(undefined);\n\n this.plugins.set(plugin.name, {\n plugin,\n config: effectiveConfig,\n installContext,\n });\n\n this.backfillPlugin(this.plugins.get(plugin.name)!);\n\n if (plugin.onInstall) {\n try {\n plugin.onInstall(installContext);\n } catch (error) {\n console.error(\n `[BlaC] Error installing plugin \"${plugin.name}\":`,\n error,\n );\n this.plugins.delete(plugin.name);\n throw error;\n }\n }\n\n if (IS_DEV) {\n console.log(\n `[BlaC] Plugin \"${plugin.name}\" v${plugin.version} installed`,\n );\n }\n }\n\n /**\n * Uninstall a plugin by name\n * @param pluginName - The name of the plugin to uninstall\n * @throws Error if plugin is not installed\n */\n uninstall(pluginName: string): void {\n const installed = this.plugins.get(pluginName);\n if (!installed) {\n throw new Error(`Plugin \"${pluginName}\" is not installed`);\n }\n\n if (installed.plugin.onUninstall) {\n try {\n installed.plugin.onUninstall();\n } catch (error) {\n console.error(\n `[BlaC] Error uninstalling plugin \"${pluginName}\":`,\n error,\n );\n }\n }\n\n this.plugins.delete(pluginName);\n if (IS_DEV) {\n console.log(`[BlaC] Plugin \"${pluginName}\" uninstalled`);\n }\n }\n\n /**\n * Get an installed plugin by name\n * @param pluginName - The name of the plugin to retrieve\n * @returns The plugin instance or undefined if not found\n */\n getPlugin(pluginName: string): BlacPlugin | undefined {\n return this.plugins.get(pluginName)?.plugin;\n }\n\n /**\n * Get all installed plugins\n * @returns Array of all installed plugins\n */\n getAllPlugins(): BlacPlugin[] {\n return Array.from(this.plugins.values()).map((p) => p.plugin);\n }\n\n /**\n * Check if a plugin is installed\n * @param pluginName - The name of the plugin to check\n * @returns true if the plugin is installed\n */\n hasPlugin(pluginName: string): boolean {\n return this.plugins.has(pluginName);\n }\n\n /**\n * Uninstall all plugins\n */\n clear(): void {\n for (const name of this.plugins.keys()) {\n this.uninstall(name);\n }\n }\n\n destroy(): void {\n this.clear();\n for (const unsub of this.lifecycleUnsubscribers) {\n unsub();\n }\n this.lifecycleUnsubscribers = [];\n // Without this each container keeps its ALL_PATHS bridge subscriber — and\n // the single-consumer-skip penalty that comes with it — forever.\n for (const bridge of this.containerBridges.values()) {\n bridge.unsub();\n }\n this.containerBridges.clear();\n }\n\n /**\n * Wire registry lifecycle events into plugin dispatch.\n *\n * `onStateChange` is NOT wired through `registry.on('stateChanged', …)` —\n * that event lacks the `PathSet` payload. Instead, on each `created` we\n * subscribe to the container's channel directly so we get\n * `(paths)` and capture `(prev, next)` via the per-container snapshot.\n */\n private setupLifecycleHooks(): void {\n this.lifecycleUnsubscribers = [\n this.registry.on('created', (instance) => {\n this.attachStateBridge(instance);\n this.notifyPlugins('onCreated', instance);\n }),\n this.registry.on('disposed', (instance) => {\n this.notifyPlugins('onDestroyed', instance);\n this.detachStateBridge(instance);\n }),\n this.registry.on('refAcquired', (instance, refId) => {\n this.notifyPlugins('onRefAcquired', instance, refId);\n }),\n this.registry.on('refReleased', (instance, refId) => {\n this.notifyPlugins('onRefReleased', instance, refId);\n }),\n this.registry.on('depsChanged', (instance, previousDeps, currentDeps) => {\n this.notifyPlugins(\n 'onDepsChanged',\n instance,\n previousDeps,\n currentDeps,\n );\n }),\n this.registry.on(\n 'hydrationChanged',\n (instance, status, previousStatus) => {\n this.notifyPlugins(\n 'onHydrationChange',\n instance,\n status,\n previousStatus,\n );\n },\n ),\n this.registry.on('activated', (instance, signal) => {\n this.notifyPlugins('onActivate', instance, signal);\n }),\n this.registry.on('deactivated', (instance) => {\n this.notifyPlugins('onDeactivate', instance);\n }),\n ];\n }\n\n /**\n * Subscribe to the container's channel with `ALL_PATHS` interest, so we\n * fire on every flush. Capture `prev` from the snapshot taken on the\n * previous flush (or at create-time for the first flush) and pass the\n * channel's `paths` argument straight through to plugins.\n *\n * Per-container bookkeeping is stored in `containerBridges`, a strong `Map`\n * keyed by the container. Entries are removed explicitly — on the\n * container's `disposed` event, or for all of them in `destroy()`.\n */\n private attachStateBridge(container: StateContainer<any, any, any>): void {\n // Defensive: if a container is somehow created twice (it shouldn't be),\n // don't double-subscribe — the existing bridge is canonical.\n if (this.containerBridges.has(container)) return;\n\n const bridge: ContainerBridge = {\n unsub: () => {},\n prevState: container.state,\n };\n this.containerBridges.set(container, bridge);\n\n bridge.unsub = container.channel.subscribe(\n () => ALL_PATHS,\n (paths) => this.dispatchStateChange(container, paths),\n );\n }\n\n private detachStateBridge(container: StateContainer<any, any, any>): void {\n const bridge = this.containerBridges.get(container);\n if (!bridge) return;\n bridge.unsub();\n this.containerBridges.delete(container);\n }\n\n /**\n * Backfill a newly-installed plugin with the app's existing instances.\n *\n * Iterates every registered `Type` and its live instances (`getAll`\n * already skips disposed), attaching the state bridge (idempotent — see\n * `attachStateBridge`) and, if the plugin implements `onCreated`, invoking\n * it directly for each instance. Scoped to this single plugin only — the\n * broadcast `notifyPlugins` would re-notify *every* plugin of *every*\n * instance, which is wrong here.\n */\n private backfillPlugin(installed: InstalledPlugin): void {\n const { plugin } = installed;\n for (const Type of this.registry.getTypes()) {\n for (const instance of this.registry.getAll(Type)) {\n // getAll returns the readonly public view; internal bridging needs the\n // real container (same friction the `as any` in queryInstances handles).\n const container = instance as StateContainer<any, any, any>;\n this.attachStateBridge(container);\n\n if (!plugin.onCreated) continue;\n try {\n plugin.onCreated(this.buildContext(container));\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" onCreated (backfill):`,\n error,\n );\n }\n }\n }\n }\n\n /**\n * Channel-flush callback. Snapshots `next`, hands `(prev, next, paths)`\n * to every enabled plugin's `onStateChange`, then updates `prevState`\n * for the next flush.\n *\n * Note: `prev` is captured once and reused across plugins — every\n * plugin sees the same `prev`/`next`/`paths` regardless of dispatch\n * order, matching the \"snapshot prev once per flush\" invariant.\n */\n private dispatchStateChange(\n container: StateContainer<any, any, any>,\n paths: PathSet,\n ): void {\n const bridge = this.containerBridges.get(container);\n if (!bridge) return;\n\n const prev = bridge.prevState;\n const next = container.state;\n bridge.prevState = next;\n\n let ctx: PluginContext | undefined;\n for (const { plugin, config } of this.plugins.values()) {\n if (!config.enabled) continue;\n // eslint-disable-next-line @typescript-eslint/unbound-method -- invoked via .call below\n const hook = plugin.onStateChange;\n if (typeof hook !== 'function') continue;\n\n try {\n ctx ??= this.buildContext(container);\n hook.call(plugin, ctx, prev, next, paths);\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" onStateChange:`,\n error,\n );\n }\n }\n }\n\n /**\n * Notify all plugins of a lifecycle event.\n *\n * Builds a fresh `PluginContext` per dispatch (instance becomes\n * `ctx.container`), so plugins can reach the focal bloc through\n * `ctx.container`. The context is built lazily on the first matching\n * hook and reused for every other plugin in this dispatch — zero\n * builds if no enabled plugin implements `hookName`.\n */\n private notifyPlugins(\n hookName: Exclude<keyof BlacPlugin, 'onStateChange'>,\n instance: StateContainer<any, any, any>,\n ...extraArgs: any[]\n ): void {\n let ctx: PluginContext | undefined;\n for (const { plugin, config } of this.plugins.values()) {\n if (!config.enabled) continue;\n\n const hook = plugin[hookName];\n if (typeof hook !== 'function') continue;\n\n try {\n ctx ??= this.buildContext(instance);\n (hook as any).call(plugin, ctx, ...extraArgs);\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" ${hookName}:`,\n error,\n );\n }\n }\n }\n\n /**\n * Get the `PluginContext` for a focal container, building it once and\n * reusing it thereafter. Eviction is handled by the `WeakMap`: the context\n * dies with the container. `undefined` (install-time) is never cached.\n */\n private buildContext(\n container: StateContainer<any, any, any> | undefined,\n ): PluginContext {\n if (container !== undefined) {\n const cached = this.contextCache.get(container);\n if (cached) return cached;\n }\n const context = this.createContext(container);\n if (container !== undefined) this.contextCache.set(container, context);\n return context;\n }\n\n private createContext(\n container: StateContainer<any, any, any> | undefined,\n ): PluginContext {\n const registry = this.registry;\n return {\n container,\n\n getInstanceMetadata: (\n instance: StateContainer<any, any, any>,\n ): InstanceMetadata => {\n return {\n id: instance.$blac.id,\n className: getBlacName(\n instance.constructor as StateContainerConstructor,\n ),\n isDisposed: instance.$blac.disposed,\n name: instance.$blac.name,\n createdAt: instance.$blac.createdAt,\n state: instance.state,\n hydrationStatus: instance.$blac.hydration.status,\n isHydrated: instance.$blac.hydration.isHydrated,\n hydrationError: instance.$blac.hydration.error,\n changedWhileHydrating: instance.$blac.hydration.changedWhileHydrating,\n args: instance.args,\n };\n },\n\n getState: <S extends object = any>(instance: StateContainer<S>): S => {\n return instance.state;\n },\n\n getHydrationStatus: (instance: StateContainer<any, any, any>) => {\n return instance.$blac.hydration.status;\n },\n\n startHydration: (instance: StateContainer<any, any, any>) => {\n instance.$blac.hydration.begin();\n },\n\n applyHydratedState: <S extends object = any>(\n instance: StateContainer<S>,\n state: S,\n ): boolean => {\n return instance.$blac.hydration.apply(state);\n },\n\n finishHydration: (instance: StateContainer<any, any, any>) => {\n instance.$blac.hydration.finish();\n },\n\n failHydration: (\n instance: StateContainer<any, any, any>,\n error: Error,\n ) => {\n instance.$blac.hydration.fail(error);\n },\n\n waitForHydration: (instance: StateContainer<any, any, any>) => {\n return instance.$blac.hydration.wait();\n },\n\n queryInstances: <T extends StateContainer<any, any, any>>(\n typeClass: new (...args: any[]) => T,\n ): T[] => {\n return registry.getAll(typeClass as any);\n },\n\n getAllTypes: () => {\n return registry.getTypes();\n },\n\n getStats: () => {\n return registry.getStats();\n },\n\n getRefIds: (instanceId: string): string[] => {\n return registry.getRefIdsById(instanceId);\n },\n };\n }\n\n /**\n * Check if plugin should be enabled based on environment\n */\n private shouldEnablePlugin(config: PluginConfig): boolean {\n if (!config.enabled) return false;\n if (config.environment === 'all') return true;\n\n const currentEnv = this.getCurrentEnvironment();\n return currentEnv === config.environment;\n }\n\n /**\n * Get current environment\n */\n private getCurrentEnvironment(): 'development' | 'production' | 'test' {\n if (typeof process !== 'undefined') {\n if (process.env.NODE_ENV === 'test') return 'test';\n if (process.env.NODE_ENV === 'production') return 'production';\n return 'development';\n }\n return 'development';\n }\n}\n\n/**\n * Create a plugin manager instance\n * @param registry - The StateContainerRegistry to monitor for lifecycle events\n * @returns A new PluginManager instance\n */\nexport function createPluginManager(\n registry: StateContainerRegistry,\n): PluginManager {\n return new PluginManager(registry);\n}\n","/**\n * Plugins Subpath Export\n *\n * Plugin system utilities for extending BlaC functionality.\n * Import from `@blac/core/plugins`\n *\n * @example\n * ```typescript\n * import { PluginManager, getPluginManager, type BlacPlugin } from '@blac/core/plugins';\n *\n * const myPlugin: BlacPlugin = {\n * name: 'my-plugin',\n * onCreated(context) {\n * console.log('Instance created:', context.container);\n * },\n * };\n *\n * getPluginManager().register(myPlugin);\n * ```\n *\n * @packageDocumentation\n */\n\nimport { createPluginManager, PluginManager } from './plugin/PluginManager';\nimport { globalRegistry } from './core/StateContainerRegistry';\n\nexport { PluginManager } from './plugin/PluginManager';\nexport type {\n BlacPlugin,\n BlacPluginWithInit,\n PluginContext,\n PluginConfig,\n InstanceMetadata,\n} from './plugin/BlacPlugin';\n\n/**\n * Global plugin manager (initialized lazily)\n */\nlet _globalPluginManager: PluginManager | null = null;\n\n/**\n * Get the global plugin manager\n */\nexport function getPluginManager(): PluginManager {\n if (!_globalPluginManager) {\n _globalPluginManager = createPluginManager(globalRegistry);\n }\n return _globalPluginManager;\n}\n"],"mappings":"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"}
|
package/dist/plugins.d.cts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Plugins Subpath Export
|
|
3
3
|
*
|
|
4
4
|
* Plugin system utilities for extending BlaC functionality.
|
|
5
|
-
* Import from
|
|
5
|
+
* Import from `@blac/core/plugins`
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```typescript
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
*
|
|
21
21
|
* @packageDocumentation
|
|
22
22
|
*/
|
|
23
|
+
import { PluginManager } from './plugin/PluginManager';
|
|
23
24
|
export { PluginManager } from './plugin/PluginManager';
|
|
24
25
|
export type { BlacPlugin, BlacPluginWithInit, PluginContext, PluginConfig, InstanceMetadata, } from './plugin/BlacPlugin';
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Get the global plugin manager
|
|
28
|
+
*/
|
|
29
|
+
export declare function getPluginManager(): PluginManager;
|
package/dist/plugins.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Plugins Subpath Export
|
|
3
3
|
*
|
|
4
4
|
* Plugin system utilities for extending BlaC functionality.
|
|
5
|
-
* Import from
|
|
5
|
+
* Import from `@blac/core/plugins`
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```typescript
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
*
|
|
21
21
|
* @packageDocumentation
|
|
22
22
|
*/
|
|
23
|
+
import { PluginManager } from './plugin/PluginManager';
|
|
23
24
|
export { PluginManager } from './plugin/PluginManager';
|
|
24
25
|
export type { BlacPlugin, BlacPluginWithInit, PluginContext, PluginConfig, InstanceMetadata, } from './plugin/BlacPlugin';
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Get the global plugin manager
|
|
28
|
+
*/
|
|
29
|
+
export declare function getPluginManager(): PluginManager;
|