@blac/core 2.0.21 → 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/dist/StateContainerRegistry-141uHhtF.cjs.map +1 -1
- package/dist/StateContainerRegistry-BH7aOMWV.js +892 -0
- package/dist/StateContainerRegistry-BH7aOMWV.js.map +1 -0
- package/dist/StateContainerRegistry-BsH_s0pJ.js +895 -0
- package/dist/StateContainerRegistry-BsH_s0pJ.js.map +1 -0
- package/dist/StateContainerRegistry-CBLeHObO.cjs +1002 -0
- package/dist/StateContainerRegistry-CBLeHObO.cjs.map +1 -0
- package/dist/StateContainerRegistry-MkT0W4C2.cjs +999 -0
- package/dist/StateContainerRegistry-MkT0W4C2.cjs.map +1 -0
- package/dist/StateContainerRegistry-vWgsD-RP.js.map +1 -1
- package/dist/constants.d.cts +0 -7
- package/dist/constants.d.ts +0 -7
- package/dist/core/StateContainer.d.ts +16 -7
- package/dist/core/StateContainerRegistry.d.ts +9 -4
- package/dist/core/meta.d.ts +34 -41
- package/dist/ensure-BjhbDW8Z.cjs +761 -0
- 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-D15vGnk5.cjs +783 -0
- package/dist/ensure-D15vGnk5.cjs.map +1 -0
- package/dist/ensure-D3w6YGp9.cjs +784 -0
- package/dist/ensure-D3w6YGp9.cjs.map +1 -0
- package/dist/ensure-dsqWhIuO.js +708 -0
- package/dist/ensure-dsqWhIuO.js.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.js +2 -2
- package/dist/plugin/PluginManager.d.ts +9 -4
- package/dist/plugins.cjs +7 -4
- package/dist/plugins.cjs.map +1 -1
- package/dist/plugins.js +7 -4
- package/dist/plugins.js.map +1 -1
- package/dist/testing.cjs +2 -2
- package/dist/testing.js +2 -2
- package/package.json +2 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensure-dsqWhIuO.js","names":[],"sources":["../src/utils/idGenerator.ts","../src/registry/config.ts","../src/core/meta.ts","../src/core/StateContainer.ts","../src/core/Cubit.ts","../src/registry/acquire.ts","../src/registry/ensure.ts"],"sourcesContent":["/**\n * Centralized ID Generation\n *\n * Provides consistent, collision-resistant ID generation for all BlaC subsystems.\n * Uses timestamp + counter + random suffix for uniqueness.\n */\n\n/**\n * Generate simple ID with timestamp and random (no counter tracking)\n *\n * Format: `${prefix}:${timestamp}_${random}`\n *\n * @param prefix - Prefix for the ID\n * @returns Branded ID string\n *\n * @example\n * ```ts\n * const id = generateSimpleId('CounterBloc');\n * // Returns: \"CounterBloc:1698765432100_a3k9d7f2q\"\n * ```\n */\nexport function generateSimpleId(prefix: string, affix?: string): string {\n if (affix) {\n return `${prefix}:${affix}`;\n }\n const timestamp = Date.now();\n const random = Math.random().toString(36).substring(2, 11);\n return `${prefix}:${timestamp}_${random}`;\n}\n","import {\n globalRegistry,\n StateContainerRegistry,\n} from '../core/StateContainerRegistry';\n\nlet _registry = globalRegistry;\n\n/**\n * @public\n */\nexport function getRegistry(): StateContainerRegistry {\n return _registry;\n}\n\n/**\n * @public\n */\nexport function setRegistry(registry: StateContainerRegistry): void {\n _registry = registry;\n}\n\n/**\n * @public\n */\nexport function getStats(): {\n registeredTypes: number;\n totalInstances: number;\n typeBreakdown: Record<string, number>;\n} {\n return _registry.getStats();\n}\n","/**\n * `$blac` meta namespace for `StateContainer`.\n *\n * Getters/methods here close over the *real* container instance captured at\n * `createMeta` time rather than reading a `this`-receiver. `buildTrackedProxy`\n * no longer uses a `this`-Proxy (getters run with `this` = the real instance),\n * so ES `#private` is safe here now; the closure style is kept because it also\n * keeps this own, frozen property independent of the proxy entirely.\n */\n\nimport { generateSimpleId } from '../utils/idGenerator';\nimport { getBlacName } from '../utils/static-props';\nimport type { HydrationStatus } from './StateContainer';\nimport type { StateContainerConstructor } from '../types/utilities';\n\n/**\n * Hydration sub-surface of {@link BlacMeta}. Exposes the container's\n * hydration state machine — the `begin`/`apply`/`finish`/`fail`/`wait`\n * methods and the `status`/`error`/`isHydrated`/`changedWhileHydrating`\n * getters — namespaced under `$blac`.\n */\nexport interface BlacHydration<S extends object> {\n readonly status: HydrationStatus;\n readonly error: Error | undefined;\n readonly isHydrated: boolean;\n readonly changedWhileHydrating: boolean;\n begin(): void;\n apply(next: S): boolean;\n finish(): void;\n fail(error: Error): void;\n wait(): Promise<void>;\n}\n\n/**\n * The reserved `$blac` instance member on every `StateContainer`. Gathers the\n * container's identity/lifecycle/hydration surface (`name`, `id`, `debug`,\n * `createdAt`, `disposed`, `dependencies`, and the hydration methods) under one\n * namespace, keeping the generic names free for userland blocs.\n *\n * The object is allocated once per container, frozen, branded, and exposes\n * live getters that read the container's current `_`-private fields — values\n * are never snapshotted.\n */\nexport interface BlacMeta<S extends object = any> {\n readonly name: string;\n readonly id: string;\n readonly debug: boolean;\n readonly createdAt: number;\n readonly disposed: boolean;\n readonly dependencies: ReadonlyMap<StateContainerConstructor, string>;\n readonly hydration: BlacHydration<S>;\n}\n\n/**\n * Non-enumerable brand carried on every `$blac` object produced by\n * {@link createMeta}. Used by the clobber guard in `StateContainer` to detect\n * a subclass class-field shadowing the base's own `$blac` property, and by the\n * meta tests. Intentionally NOT re-exported from the package barrel.\n */\nexport const META_BRAND = Symbol('blac.meta');\n\nconst EMPTY_DEPS: ReadonlyMap<StateContainerConstructor, string> = new Map();\n\n/**\n * Narrow view of the parts of `StateContainer` the meta getters read. The\n * container's fields are TS-private (compile-time only), so they are not\n * reachable cross-module by name; `createMeta` performs a single internal cast\n * to this shape. This is purely a typing bridge — zero runtime indirection.\n *\n * The `$blac.hydration` surface delegates to these `_`-private methods, so the\n * container has a single source of truth for the hydration state machine.\n */\ninterface MetaInternals<S extends object> {\n readonly constructor: StateContainerConstructor;\n _name: string;\n _instanceId?: string;\n _debug: boolean;\n _createdAt: number;\n _disposed: boolean;\n _hydrationStatus: HydrationStatus;\n _hydrationError: Error | undefined;\n _changedWhileHydrating: boolean;\n _dependencies: Map<StateContainerConstructor, string> | null;\n _beginHydration(): void;\n _applyHydratedState(next: S): boolean;\n _finishHydration(): void;\n _failHydration(error: Error): void;\n _waitForHydration(): Promise<void>;\n}\n\n/**\n * Build the frozen, branded `$blac` meta object for a container.\n *\n * Called from a `StateContainer` field initializer (`$blac = createMeta(this)`).\n * Only the back-reference is captured at this point; every getter/method reads\n * the container's `_`-private fields lazily on access, so field-initializer\n * ordering relative to those private fields is irrelevant.\n *\n * The getters close over `container` (the real instance), never over a\n * `this`-receiver — this is what keeps `$blac` reads safe through\n * `buildTrackedProxy`.\n */\nexport function createMeta<S extends object>(container: object): BlacMeta<S> {\n const c = container as unknown as MetaInternals<S>;\n\n const hydration: BlacHydration<S> = Object.freeze({\n get status() {\n return c._hydrationStatus;\n },\n get error() {\n return c._hydrationError;\n },\n get isHydrated() {\n return c._hydrationStatus === 'hydrated';\n },\n get changedWhileHydrating() {\n return c._changedWhileHydrating;\n },\n begin() {\n c._beginHydration();\n },\n apply(next: S) {\n return c._applyHydratedState(next);\n },\n finish() {\n c._finishHydration();\n },\n fail(error: Error) {\n c._failHydration(error);\n },\n wait() {\n return c._waitForHydration();\n },\n });\n\n const meta: BlacMeta<S> = {\n get name() {\n return c._name;\n },\n get id() {\n // Generated on first read — the container leaves it undefined so an\n // instance nobody registers or inspects never pays for one.\n return (c._instanceId ??= generateSimpleId(\n getBlacName(c.constructor),\n 'main',\n ));\n },\n get debug() {\n return c._debug;\n },\n get createdAt() {\n return c._createdAt;\n },\n get disposed() {\n return c._disposed;\n },\n get dependencies() {\n return c._dependencies ?? EMPTY_DEPS;\n },\n hydration,\n };\n\n Object.defineProperty(meta, META_BRAND, {\n value: true,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n return Object.freeze(meta);\n}\n","import {\n ALL_PATHS,\n StructuralContainer,\n type DeepPartial,\n type StructuralContainerOptions,\n} from '@dirtytalk/structural';\nimport { generateSimpleId } from '../utils/idGenerator';\nimport { IS_DEV } from '../constants';\nimport { getRegistry } from '../registry/config';\nimport type {\n ExtractArgs,\n ExtractState,\n StateContainerConstructor,\n} from '../types/utilities';\nimport {\n APPLY_DEPS,\n INIT_CONFIG,\n ON_DISPOSE,\n REMOVE_DEPS_OWNER,\n SET_ACTIVE,\n WITH_TRACKED_STATE,\n} from './symbols';\nimport { type EqualityFn, getBlacConfig } from '../config';\nimport { getClassEquality, getBlacName } from '../utils/static-props';\nimport { type BlacMeta, createMeta, META_BRAND } from './meta';\n\n/**\n * Brand symbol carried (non-enumerable) on every handle returned by\n * `depend()`. Framework adapters (`@blac/react`) use this to detect handles\n * inside a tracked-proxy and swap in a session-bound wrapper.\n *\n * @internal — exported for `@blac/react`; not part of the public API.\n */\nexport const DEP_BRAND = Symbol('blac.depHandle');\n\n/**\n * Per-access options for a {@link DepHandle}. `args` resolves the specific dep\n * instance for this call; omit it to fall back to the default args passed to\n * `depend()`. Args resolve at call time so they can derive from current state.\n */\nexport interface DepAccessOptions<T extends StateContainerConstructor> {\n args?: ExtractArgs<T>;\n}\n\n/**\n * Branded handle returned by `StateContainer.depend()`. Two intention-revealing\n * accessors, no callable surface:\n *\n * - `handle.track(options?)` — base impl: returns `[instance.state, instance]`\n * live with no subscription. The React layer replaces `.track()` per-consumer\n * so that cross-bloc state changes trigger re-renders of the reading component.\n * - `handle.untracked(options?)` — returns the live dep instance with no\n * tracking. Use for imperative calls (`.untracked().doThing()`) and one-off\n * state reads that should not subscribe the reader.\n * - `handle[DEP_BRAND]` — carries `{ Type, defaultArgs }` for framework use.\n */\nexport interface DepHandle<T extends StateContainerConstructor> {\n track(options?: DepAccessOptions<T>): [ExtractState<T>, InstanceType<T>];\n untracked(options?: DepAccessOptions<T>): InstanceType<T>;\n readonly [DEP_BRAND]: { Type: T; defaultArgs?: ExtractArgs<T> };\n}\n\nexport interface StateContainerConfig {\n name?: string;\n debug?: boolean;\n instanceId?: string;\n /** Args passed at acquire time; forwarded to init(). */\n args?: unknown;\n}\n\nexport type HydrationStatus = 'idle' | 'hydrating' | 'hydrated' | 'error';\n\nexport type SystemEvent = 'stateChanged' | 'dispose' | 'hydrationChanged';\n\nexport interface SystemEventPayloads<S> {\n stateChanged: { state: S; previousState: S };\n dispose: void;\n hydrationChanged: {\n status: HydrationStatus;\n previousStatus: HydrationStatus;\n error?: Error;\n changedWhileHydrating: boolean;\n };\n}\n\ntype SystemEventHandler<S, E extends SystemEvent> = (\n payload: SystemEventPayloads<S>[E],\n) => void;\n\n/**\n * Shallow per-key `Object.is` comparison of two plain records. Keys are\n * considered: a key present in one but not the other (regardless of value)\n * makes the records unequal. Used to detect real deps changes.\n */\nfunction shallowEqualRecord(\n a: Record<string, unknown>,\n b: Record<string, unknown>,\n): boolean {\n if (a === b) return true;\n const aKeys = Object.keys(a);\n if (aKeys.length !== Object.keys(b).length) return false;\n for (const key of aKeys) {\n if (!(key in b)) return false;\n if (!Object.is(a[key], b[key])) return false;\n }\n return true;\n}\n\n/**\n * BlaC's lifecycle/identity/dependency layer on top of `StructuralContainer`.\n *\n * StructuralContainer provides:\n * - `state` getter\n * - `emit` / `patch` / `update` (path-tracked, microtask-flushed)\n * - `channel` (the underlying `DirtyChannel`)\n * - `registerConsumerPaths` / `unregisterConsumer` (for fine-grained consumers)\n * - per-class `PathInterner`\n *\n * StateContainer layers on:\n * - identity / lifecycle / hydration (exposed via `$blac: BlacMeta`)\n * - cross-bloc deps (`depend()`, `$blac.dependencies`)\n * - per-consumer deps slices (`APPLY_DEPS` / `REMOVE_DEPS_OWNER` / `onDepsChanged`)\n * - registry integration (config-driven equality, emit-rate circuit breaker)\n *\n * Subscribers can attach via:\n * - `onSystemEvent('stateChanged' | 'dispose' | 'hydrationChanged', cb)` —\n * coarse lifecycle events.\n * - `this.channel.subscribe(interest, cb)` — path-scoped.\n */\nexport abstract class StateContainer<\n S extends object = any,\n Args = void,\n Deps extends object = Record<string, never>,\n> extends StructuralContainer<S> {\n static __excludeFromDevTools = false;\n\n /** @internal phantom — the args type this bloc is constructed with (see init()) */\n declare readonly __args: Args;\n /** @internal phantom — the injected deps type */\n declare readonly __deps: Deps;\n\n // ---------------------------------------------------------------------------\n // Per-consumer deps slices (APPLY_DEPS / REMOVE_DEPS_OWNER)\n //\n // Framework adapters wire these per consumer:\n // `@blac/react/src/useBloc.ts` reads APPLY_DEPS / REMOVE_DEPS_OWNER.\n // ---------------------------------------------------------------------------\n\n private _depsByOwner: Map<string, Partial<Deps>> | null = null;\n private _deps: Partial<Deps> = {};\n\n get deps(): Readonly<Deps> {\n return this._deps as Readonly<Deps>;\n }\n\n /**\n * @internal Apply one owner's (consumer's) deps slice. Shallow-merges the\n * slice, reconciles keys the owner dropped since its last apply (other\n * owners' keys untouched), dev-warns on cross-owner key collisions, then\n * recomputes the merged view and fires onDepsChanged if it changed.\n *\n * Idempotent: re-applying an identical slice for the same owner is a no-op.\n */\n [APPLY_DEPS](ownerId: string, slice: Partial<Deps>): void {\n if (this._disposed) return;\n\n const owners = (this._depsByOwner ??= new Map());\n const prevSlice = owners.get(ownerId);\n\n if (prevSlice && shallowEqualRecord(prevSlice, slice)) {\n return;\n }\n\n if (IS_DEV) {\n for (const key of Object.keys(slice) as (keyof Deps)[]) {\n for (const [otherOwner, otherSlice] of owners) {\n if (otherOwner === ownerId) continue;\n if (\n key in otherSlice &&\n (otherSlice as Record<keyof Deps, unknown>)[key] !==\n (slice as Record<keyof Deps, unknown>)[key]\n ) {\n console.warn(\n `[${this._name}] multiple owners writing dep \\`${String(\n key,\n )}\\`; last write wins`,\n );\n }\n }\n }\n }\n\n owners.set(ownerId, { ...slice });\n this.reconcileDeps();\n }\n\n /**\n * @internal Withdraw an owner's entire deps slice (consumer unmounted).\n */\n [REMOVE_DEPS_OWNER](ownerId: string): void {\n if (this._disposed) return;\n if (!this._depsByOwner?.delete(ownerId)) return;\n this.reconcileDeps();\n }\n\n /**\n * @internal Subscribe to this instance's own disposal. Delegates to the\n * per-instance `'dispose'` system event — used by `watch()` in place of\n * filtering the global registry `'disposed'` event.\n */\n [ON_DISPOSE](handler: () => void): () => void {\n return this.onSystemEvent('dispose', handler);\n }\n\n /**\n * @internal Drive the 0↔1 ownership transition. Idempotent: repeated calls\n * with the same value are no-ops, so the registry can call it from every\n * acquire/release without tracking edges itself.\n *\n * Returns which real transition (if any) just happened, so the registry can\n * emit the matching `activated`/`deactivated` plugin event without exposing\n * `_activation` itself — `'none'` for an idempotent no-op call.\n */\n [SET_ACTIVE](\n active: boolean,\n ): { kind: 'activated'; signal: AbortSignal } | 'deactivated' | 'none' {\n if (this._disposed) return 'none';\n if (active === (this._activation !== null)) return 'none';\n if (active) {\n this._activation = new AbortController();\n this.onActivate(this._activation.signal);\n return { kind: 'activated', signal: this._activation.signal };\n }\n this._abortActivation();\n this.onDeactivate();\n return 'deactivated';\n }\n\n private _abortActivation(): void {\n this._activation?.abort();\n this._activation = null;\n }\n\n private reconcileDeps(): void {\n const prev = this._deps;\n const next: Partial<Deps> = {};\n if (this._depsByOwner) {\n for (const slice of this._depsByOwner.values()) {\n Object.assign(next, slice);\n }\n }\n for (const key of Object.keys(prev) as (keyof Deps)[]) {\n if (!(key in next)) {\n (next as Record<keyof Deps, unknown>)[key] = undefined;\n }\n }\n\n if (shallowEqualRecord(prev, next)) return;\n\n this._deps = next;\n this.onDepsChanged(next as Readonly<Deps>, prev as Readonly<Deps>);\n this._registry.emit(\n 'depsChanged',\n this,\n prev as Readonly<Record<string, unknown>>,\n next as Readonly<Record<string, unknown>>,\n );\n }\n\n protected onDepsChanged(_next: Readonly<Deps>, _prev: Readonly<Deps>): void {}\n\n // ---------------------------------------------------------------------------\n // Identity / lifecycle\n // ---------------------------------------------------------------------------\n\n private _disposed = false;\n private _hydrationStatus: HydrationStatus = 'idle';\n private _hydrationError?: Error;\n private _changedWhileHydrating = false;\n private _hydrationPromise: Promise<void> | null = null;\n private _resolveHydrationPromise?: () => void;\n private _rejectHydrationPromise?: (error: Error) => void;\n private _hydrationPromiseSettled = false;\n private _config: StateContainerConfig = {};\n private _initCalled = false;\n private _activation: AbortController | null = null;\n\n // Dev-only emit-rate circuit breaker state (see configureBlac.maxEmitsPerSecond).\n private _emitWindowStart = 0;\n private _emitCount = 0;\n private _emitRateWarned = false;\n\n // System-event handlers (stateChanged | dispose | hydrationChanged).\n private readonly _systemEventHandlers = new Map<\n SystemEvent,\n Set<SystemEventHandler<S, any>>\n >();\n\n // Cross-bloc dependencies recorded by depend(). Map<DepCtor, instanceKey>.\n private _dependencies: Map<StateContainerConstructor, string> | null = null;\n\n // Pending state-change capture; set by emit(), drained by the channel-bridge\n // callback on flush. Coalesced: multiple emits in one tick collapse to one\n // (prev = the first prev, next = the latest next).\n private _pendingChange: { prev: S; next: S } | null = null;\n\n // Unsubscribe from the internal bridge that turns channel flushes into\n // 'stateChanged' system events.\n private _bridgeUnsub: (() => void) | null = null;\n\n private _registry = getRegistry();\n private _equalityFn: EqualityFn = getBlacConfig().equality;\n\n // Identity fields. The `$blac` meta getters close over these.\n private _name: string = getBlacName(\n this.constructor as StateContainerConstructor,\n );\n private _debug: boolean = false;\n // Left undefined until first read: generating an id costs a `Date.now()` plus\n // a random base-36 string, and a container that is never registered or\n // inspected never needs one. `$blac.id` fills it in on demand (see\n // `createMeta`), and `[INIT_CONFIG]` overwrites it when the registry supplies\n // a configured instanceId. Deliberately a plain field, not a getter —\n // devtools enumerates prototype getters to find user-defined ones, and a\n // `_instanceId` accessor would show up there as bloc state.\n private _instanceId?: string;\n private _createdAt: number = Date.now();\n\n /**\n * Reserved meta namespace: identity (`name`/`id`/`debug`/`createdAt`),\n * lifecycle (`disposed`/`dependencies`), and the `hydration` sub-surface.\n * Own, frozen, branded data property — `buildTrackedProxy` only intercepts\n * prototype getters, so this own property and its closure-based getters are\n * proxy-safe. See `createMeta`.\n */\n readonly $blac: BlacMeta<S> = createMeta<S>(this);\n\n /**\n * Both set only for the duration of a `[WITH_TRACKED_STATE]` call. TS-private\n * so they stay out of the public surface.\n */\n private _stateOverride: S | undefined;\n private _depHandleHook: ((handle: object) => unknown) | undefined;\n\n override get state(): S {\n return this._stateOverride ?? super.state;\n }\n\n /**\n * @internal Run `fn` with `state` reporting `tracked`, and with dep handles\n * read off `this` routed through `onDepHandle`. Restored in `finally`, and\n * nestable — getters reading other getters stay in tracked context.\n *\n * The dep hook lives here rather than in the adapter's proxy because getters\n * now run with `this` = the real instance (so ES `#private` works), which\n * means a `this.someDep` read never passes through the proxy's trap.\n */\n [WITH_TRACKED_STATE]<R>(\n tracked: S,\n fn: () => R,\n onDepHandle?: (handle: object) => unknown,\n ): R {\n const prevState = this._stateOverride;\n const prevHook = this._depHandleHook;\n this._stateOverride = tracked;\n this._depHandleHook = onDepHandle;\n try {\n return fn();\n } finally {\n this._stateOverride = prevState;\n this._depHandleHook = prevHook;\n }\n }\n\n get args(): Args | undefined {\n return this._config.args as Args | undefined;\n }\n\n /**\n * Declare a cross-bloc dependency. Returns a branded handle with two\n * accessors — the dep instance is resolved against the registry on each call,\n * which keeps the surface immune to dep-instance churn:\n *\n * - `handle.track(options?)` — reactive read. Returns `[state, instance]`.\n * Inside a getter reached through the React proxy this subscribes the\n * reading component to the dep's changes (base impl: live, no subscription).\n * - `handle.untracked(options?)` — returns the live instance with no tracking,\n * for imperative method calls and one-off reads.\n *\n * `defaultArgs` resolves the dep instance when an accessor is called without\n * its own `args`; per-call `options.args` overrides it and can derive from\n * current state. This does NOT auto-resubscribe outside the React proxy;\n * non-React consumers needing updates should subscribe explicitly.\n */\n protected depend<T extends StateContainerConstructor>(\n Type: T,\n defaultArgs?: ExtractArgs<T>,\n ): DepHandle<T> {\n if (!this._dependencies) {\n this._dependencies = new Map();\n }\n this._dependencies.set(\n Type,\n this._registry.resolveKey(Type, undefined, defaultArgs),\n );\n\n const resolve = (args?: ExtractArgs<T>): InstanceType<T> => {\n const effectiveArgs = args ?? defaultArgs;\n const key = this._registry.resolveKey(Type, undefined, effectiveArgs);\n // Same no-ref semantics as `ensure()`, but records `this` as a\n // dependent edge on the resolved entry so the registry can sweep it\n // on `this`'s disposal (see StateContainerRegistry._releaseDependent).\n return this._registry.acquire(Type, key, {\n canCreate: true,\n countRef: false,\n args: effectiveArgs,\n dependent: this,\n }) as InstanceType<T>;\n };\n\n const handle = {\n track: (\n options?: DepAccessOptions<T>,\n ): [ExtractState<T>, InstanceType<T>] => {\n // While a consumer is rendering, `.track()` must go through its\n // session-bound wrapper so the read subscribes that consumer. The\n // wrapper is only reachable via the hook, since `this.<dep>` is read\n // straight off the real instance.\n const wrapped = this._depHandleHook?.(handle) as\n | DepHandle<T>\n | undefined;\n if (wrapped !== undefined && wrapped !== handle) {\n return wrapped.track(options);\n }\n const instance = resolve(options?.args);\n return [instance.state as ExtractState<T>, instance];\n },\n untracked: (options?: DepAccessOptions<T>): InstanceType<T> =>\n resolve(options?.args),\n } as DepHandle<T>;\n\n Object.defineProperty(handle, DEP_BRAND, {\n value: { Type, defaultArgs },\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n return handle;\n }\n\n constructor(initialState: S, options?: StructuralContainerOptions) {\n super(initialState, options);\n }\n\n /**\n * Called once after construction with the args passed at acquire time, before the first\n * state snapshot is read by any consumer. Override to seed args-derived state (via\n * this.emit(...)) or kick off loads.\n */\n protected init(_args: Args): void {}\n\n /**\n * Called when this instance goes from unowned to owned (first ref or first\n * `depend()` dependent). Override to start work that should only run while\n * something is actually using the instance — fetches, timers, listeners.\n *\n * `signal` aborts on the matching {@link onDeactivate} and on dispose, so\n * async work started here can be cancelled without extra bookkeeping.\n *\n * Runs before the first `useBlocDeps` slice is applied, so `this.deps` is\n * empty on first activation — use `onDepsChanged` for deps-driven work.\n */\n protected onActivate(_signal: AbortSignal): void {}\n\n /**\n * Called when this instance goes from owned back to unowned while surviving\n * in the registry (i.e. `keepAlive`). A disposed instance does NOT get this\n * callback — `dispose()` is the teardown path for that case.\n */\n protected onDeactivate(): void {}\n\n /**\n * @internal Framework-only configuration entry point (registry + testing\n * helpers). Writes the `_`-private identity fields directly, resolves\n * per-class equality, emits the registry `created` event, and runs `init()`\n * once. See {@link INIT_CONFIG}.\n */\n [INIT_CONFIG](config: StateContainerConfig): void {\n this._config = { ...config };\n this._name =\n this._config.name ||\n getBlacName(this.constructor as StateContainerConstructor);\n this._debug = this._config.debug ?? false;\n this._instanceId = generateSimpleId(\n getBlacName(this.constructor as StateContainerConstructor),\n this._config.instanceId,\n );\n const perClass = getClassEquality(\n this.constructor as StateContainerConstructor,\n );\n this._equalityFn = perClass ?? getBlacConfig().equality;\n // `created` fires AFTER init() so plugins observe a fully initialised\n // instance. Emitting it first let a plugin start hydration, which init()'s\n // seeding emits then cancelled — silently discarding persisted state.\n if (!this._initCalled) {\n this._initCalled = true;\n this.init(this._config.args as Args);\n }\n this._registry.emit('created', this);\n\n // Clobber guard: a subclass class-field `$blac = ...` initializes after\n // `super()` and would overwrite the base's own meta property. Dev-only.\n if (IS_DEV) {\n if (!(this.$blac as unknown as Record<symbol, unknown>)?.[META_BRAND]) {\n console.warn(\n `[blac] ${this.constructor.name} appears to declare its own \\`$blac\\` ` +\n `member, which shadows the reserved meta namespace. \\`$blac\\` is ` +\n `reserved by StateContainer — remove the subclass field.`,\n );\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle: dispose\n // ---------------------------------------------------------------------------\n\n dispose(): void {\n if (this._disposed) return;\n\n if (this._debug) {\n console.log(`[${this._name}] Disposing...`);\n }\n\n // Reconcile to an empty deps view so a final onDepsChanged(next, prev)\n // fires with all keys absent (undefined). After this, APPLY_DEPS /\n // REMOVE_DEPS_OWNER are guarded by _disposed.\n if (this._depsByOwner) {\n this._depsByOwner.clear();\n this.reconcileDeps();\n }\n\n this._disposed = true;\n\n if (this._hydrationStatus === 'hydrating') {\n this._failHydration(\n new Error(`Hydration cancelled because ${this._name} was disposed`),\n );\n }\n\n this.emitSystemEvent('dispose', undefined as void);\n\n // Tear down the channel bridge so we don't leak a subscription.\n this._bridgeUnsub?.();\n this._bridgeUnsub = null;\n\n // Dispose is the teardown path for an active instance: the signal aborts\n // but `onDeactivate` does NOT fire, so a container has exactly one teardown\n // callback per lifecycle rather than two.\n this._abortActivation();\n\n this._systemEventHandlers.clear();\n this._pendingChange = null;\n\n this._registry.emit('disposed', this);\n\n // Tear down the channel itself (cancels any scheduled flush).\n super.dispose();\n\n if (this._debug) {\n console.log(`[${this._name}] Disposed successfully`);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Mutation: emit / patch / update.\n //\n // We override `emit` to layer in:\n // - disposed guard\n // - equality-fn short-circuit (consults getBlacConfig().equality or the\n // per-class override via @blac decorator)\n // - emit-rate circuit breaker (dev-only)\n // - `_changedWhileHydrating` flag tracking\n // - registry-level stateChanged notification (microtask-deferred)\n // - pending-change capture so the channel-bridge callback can fire the\n // 'stateChanged' system event with prev/next.\n //\n // The actual change-detection (path diff, channel mark, single-consumer\n // skip) is delegated to `super.emit`.\n // ---------------------------------------------------------------------------\n\n protected override emit(next: S): void {\n this.applyState(next, 'default');\n }\n\n /**\n * Override of `StructuralContainer.patch` that routes through the\n * StateContainer concerns: disposed guard, dev-only emit-rate check,\n * `_changedWhileHydrating` flag, pending-change capture (so `stateChanged`\n * system events see the merged prev/next), and the registry-level\n * `stateChanged` notification. We still call\n * `super.patch` so path-marking semantics (the whole point of patch) are\n * preserved.\n */\n protected override patch(partial: DeepPartial<S>): void {\n if (this._disposed) {\n this._warnDisposedMutation('patch');\n return;\n }\n const prev = this.state;\n\n super.patch(partial);\n\n // `super.patch` returns `prev` by reference when the merge changed\n // nothing, so identity is the no-op test — no pre-scan needed.\n const next = this.state;\n if (Object.is(prev, next)) return;\n\n if (IS_DEV) {\n this._checkEmitRate();\n }\n\n if (this._hydrationStatus === 'hydrating') {\n this._changedWhileHydrating = true;\n }\n\n if (this._bridgeUnsub !== null) {\n if (this._pendingChange) {\n this._pendingChange.next = next;\n } else {\n this._pendingChange = { prev, next };\n }\n }\n\n if (this._registry.hasStateChangedListeners) {\n this._registry.notifyStateChanged(this, prev, next);\n }\n }\n\n private applyState(next: S, source: 'default' | 'hydration'): void {\n if (this._disposed) {\n this._warnDisposedMutation('emit');\n return;\n }\n\n const prev = this.state;\n if (prev === next) return;\n if (this._equalityFn(prev, next)) return;\n\n if (IS_DEV) {\n this._checkEmitRate();\n }\n\n if (this._hydrationStatus === 'hydrating' && source !== 'hydration') {\n this._changedWhileHydrating = true;\n }\n\n // Coalesce: keep the first prev seen this tick, take the latest next.\n // Only tracked when the bridge is attached; nothing reads it otherwise.\n if (this._bridgeUnsub !== null) {\n if (this._pendingChange) {\n this._pendingChange.next = next;\n } else {\n this._pendingChange = { prev, next };\n }\n }\n\n super.emit(next);\n\n if (this._registry.hasStateChangedListeners) {\n this._registry.notifyStateChanged(this, prev, next);\n }\n }\n\n /**\n * Called by the channel-bridge subscriber on each flush. Drains the\n * pending change (if any) into the 'stateChanged' system event.\n * No pending change == flush from a no-op patch == skip.\n */\n private _drainPending(): void {\n const pending = this._pendingChange;\n if (!pending) return;\n this._pendingChange = null;\n\n // Iterate against a fixed-size snapshot so a handler that subscribes a\n // new handler mid-drain does not get the late one called in this flush.\n const handlers = this._systemEventHandlers.get('stateChanged');\n if (handlers && handlers.size > 0) {\n const payload = { state: pending.next, previousState: pending.prev };\n let count = 0;\n const size = handlers.size;\n for (const handler of handlers) {\n if (++count > size) break;\n try {\n handler(payload);\n } catch (error) {\n console.error(\n `[${this._name}] Error in system event handler:`,\n error,\n );\n }\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Hydration\n // ---------------------------------------------------------------------------\n\n // The hydration state machine lives in these `_`-private impls. The\n // `$blac.hydration` surface delegates here, so there is a single source\n // of truth for the hydration state machine.\n\n private _beginHydration(): void {\n if (this._disposed) {\n throw new Error(\n `Cannot begin hydration for disposed container ${this._name}`,\n );\n }\n\n this._changedWhileHydrating = false;\n this._hydrationError = undefined;\n this._hydrationPromise = null;\n this._resolveHydrationPromise = undefined;\n this._rejectHydrationPromise = undefined;\n this._hydrationPromiseSettled = false;\n void this.ensureHydrationPromise();\n this.setHydrationStatus('hydrating');\n }\n\n private _warnDisposedMutation(op: 'emit' | 'patch'): void {\n if (IS_DEV) {\n console.warn(\n `[blac] ${this._name}: ${op}() after dispose ignored. This usually ` +\n `means an async method resolved after the last consumer unmounted. ` +\n `Guard with \\`if (this.$blac.disposed) return\\` after each \\`await\\`, ` +\n `or abort the work from \\`onSystemEvent('dispose', ...)\\`.`,\n );\n }\n }\n\n private _applyHydratedState(next: S): boolean {\n if (this._disposed) {\n return false;\n }\n\n if (this._hydrationStatus !== 'hydrating' || this._changedWhileHydrating) {\n if (IS_DEV && this._changedWhileHydrating) {\n console.warn(\n `[blac] ${this._name}: persisted state discarded because the ` +\n `container emitted while hydrating.`,\n );\n }\n return false;\n }\n\n this.applyState(next, 'hydration');\n return true;\n }\n\n private _finishHydration(): void {\n if (this._hydrationStatus !== 'hydrating') {\n if (this._hydrationStatus === 'hydrated') {\n return;\n }\n if (this._hydrationStatus === 'error') {\n void this.ensureHydrationPromise();\n }\n }\n\n this.setHydrationStatus('hydrated');\n this.resolveHydration();\n }\n\n private _failHydration(error: Error): void {\n const err =\n error instanceof Error\n ? error\n : new Error(`Hydration failed: ${String(error)}`);\n\n this._hydrationError = err;\n this.setHydrationStatus('error', err);\n this.rejectHydration(err);\n }\n\n private _waitForHydration(): Promise<void> {\n if (\n this._hydrationStatus === 'idle' ||\n this._hydrationStatus === 'hydrated'\n ) {\n return Promise.resolve();\n }\n\n if (this._hydrationStatus === 'error') {\n return Promise.reject(\n this._hydrationError ??\n new Error(`Hydration failed for container ${this._name}`),\n );\n }\n\n return this.ensureHydrationPromise();\n }\n\n private setHydrationStatus(status: HydrationStatus, error?: Error): void {\n const previousStatus = this._hydrationStatus;\n this._hydrationStatus = status;\n this._hydrationError = error;\n\n this.emitSystemEvent('hydrationChanged', {\n status,\n previousStatus,\n error,\n changedWhileHydrating: this._changedWhileHydrating,\n });\n this._registry.emit('hydrationChanged', this, status, previousStatus);\n }\n\n private ensureHydrationPromise(): Promise<void> {\n if (!this._hydrationPromise || this._hydrationPromiseSettled) {\n this._hydrationPromiseSettled = false;\n this._hydrationPromise = new Promise<void>((resolve, reject) => {\n this._resolveHydrationPromise = () => {\n if (this._hydrationPromiseSettled) return;\n this._hydrationPromiseSettled = true;\n resolve();\n };\n this._rejectHydrationPromise = (error: Error) => {\n if (this._hydrationPromiseSettled) return;\n this._hydrationPromiseSettled = true;\n reject(error);\n };\n });\n this._hydrationPromise.catch(() => {});\n }\n\n return this._hydrationPromise;\n }\n\n private resolveHydration(): void {\n this._resolveHydrationPromise?.();\n }\n\n private rejectHydration(error: Error): void {\n void this.ensureHydrationPromise();\n this._rejectHydrationPromise?.(error);\n }\n\n // ---------------------------------------------------------------------------\n // System events\n // ---------------------------------------------------------------------------\n\n protected onSystemEvent = <E extends SystemEvent>(\n event: E,\n handler: SystemEventHandler<S, E>,\n ): (() => void) => {\n let handlers = this._systemEventHandlers.get(event);\n if (!handlers) {\n handlers = new Set();\n this._systemEventHandlers.set(event, handlers);\n }\n handlers.add(handler as SystemEventHandler<S, any>);\n\n // The channel bridge only exists to feed 'stateChanged'. Subscribe on the\n // first such handler so instances nobody observes stay off the channel.\n if (event === 'stateChanged' && this._bridgeUnsub === null) {\n this._bridgeUnsub = this.channel.subscribe(\n () => ALL_PATHS,\n () => this._drainPending(),\n );\n }\n\n return () => {\n handlers?.delete(handler as SystemEventHandler<S, any>);\n if (event === 'stateChanged' && handlers?.size === 0) {\n this._bridgeUnsub?.();\n this._bridgeUnsub = null;\n this._pendingChange = null;\n }\n };\n };\n\n private emitSystemEvent<E extends SystemEvent>(\n event: E,\n payload: SystemEventPayloads<S>[E],\n ): void {\n const handlers = this._systemEventHandlers.get(event);\n if (!handlers) return;\n\n for (const handler of handlers) {\n try {\n handler(payload);\n } catch (error) {\n console.error(`[${this._name}] Error in system event handler:`, error);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Emit-rate circuit breaker (dev-only)\n // ---------------------------------------------------------------------------\n\n /**\n * Dev-only soft circuit breaker. Counts real state changes in a rolling 1s\n * window and warns once if the rate exceeds `maxEmitsPerSecond` — the\n * signature of a runaway loop (RAF/animation or emit-on-every-commit) pushing\n * high-frequency data through state, which freezes subscribers/plugins.\n */\n private _checkEmitRate(): void {\n const limit = getBlacConfig().maxEmitsPerSecond;\n if (!(limit > 0) || !Number.isFinite(limit) || this._emitRateWarned) return;\n\n const now = Date.now();\n if (now - this._emitWindowStart >= 1000) {\n this._emitWindowStart = now;\n this._emitCount = 1;\n return;\n }\n\n this._emitCount++;\n if (this._emitCount > limit) {\n this._emitRateWarned = true;\n console.warn(\n `[${this._name}] emitted more than ${limit} state changes in under a second. ` +\n `This is usually a runaway loop pushing high-frequency data through state ` +\n `(e.g. \\`emit\\`/\\`patch\\` inside a requestAnimationFrame loop, or an effect ` +\n `that emits on every render). It can freeze the app by saturating ` +\n `subscribers and plugins (logging/devtools). Keep high-frequency work ` +\n `imperative and emit only coarse/throttled state, or raise ` +\n `\\`configureBlac({ maxEmitsPerSecond })\\`. (This warning fires once.)`,\n );\n }\n }\n}\n","import { StateContainer } from './StateContainer';\nimport type { DeepPartial } from '@dirtytalk/structural';\n\n/**\n * `Cubit<S>` is a `StateContainer<S>` that exposes mutation publicly.\n *\n * That is the whole difference, and it is a real one: on `StateContainer`\n * `emit` / `patch` / `update` are `protected`, so a container mutates itself\n * from its own methods and callers go through the API it chooses to publish.\n * `Cubit` re-declares the three as `public` for the cases where a caller\n * legitimately drives state from outside — test helpers, devtools\n * time-travel, benchmarks.\n *\n * Reach for `StateContainer` when business logic should live in the class,\n * and `Cubit` when the caller owns the transitions.\n */\nexport abstract class Cubit<\n S extends object = any,\n Args = void,\n Deps extends object = Record<string, never>,\n> extends StateContainer<S, Args, Deps> {\n override emit(next: S): void {\n super.emit(next);\n }\n\n override patch(partial: DeepPartial<S>): void {\n super.patch(partial);\n }\n\n override update(fn: (state: S) => S): void {\n super.update(fn);\n }\n}\n","import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n} from '../types/utilities';\n\n/**\n * Acquire an instance with ref tracking (ownership semantics). Instance\n * identity is derived purely from `args` (via the class's `static key(args)`,\n * a structural hash of `args`, or the default sentinel when there are none).\n *\n * @param BlocClass - The StateContainer class constructor\n * @param opts - `args` derives the instance key; `refId` names the reference\n * for debugging and is auto-generated when omitted.\n */\nexport function acquire<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T>; refId?: string },\n): InstanceType<T> {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.acquire(BlocClass, key, {\n canCreate: true,\n countRef: true,\n refId: opts?.refId,\n args: opts?.args,\n });\n}\n\n/**\n * Resolve the storage key an instance would be acquired under, given its\n * construction `args`. This is the canonical key-computation path. Callers that\n * both acquire and release against the `@internal` registry tier (e.g.\n * `@blac/react` `useBloc`) must use this once and pass the same resolved key to\n * both, so the ref is dropped under the key it was taken.\n */\nexport function resolveInstanceKey<T extends StateContainerConstructor>(\n BlocClass: T,\n args?: ExtractArgs<T>,\n): string {\n return getRegistry().resolveKey(BlocClass, undefined, args);\n}\n","import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n} from '../types/utilities';\n\n/**\n * Ensure an instance exists without taking ownership (no ref added). Instance\n * identity is derived purely from `args`, matching `acquire`.\n */\nexport function ensure<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T> },\n): InstanceType<T> {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.ensure(BlocClass, key, opts?.args);\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_StateContainerRegistry = require("./StateContainerRegistry-
|
|
3
|
-
const require_ensure = require("./ensure-
|
|
2
|
+
const require_StateContainerRegistry = require("./StateContainerRegistry-CBLeHObO.cjs");
|
|
3
|
+
const require_ensure = require("./ensure-D3w6YGp9.cjs");
|
|
4
4
|
let _dirtytalk_structural = require("@dirtytalk/structural");
|
|
5
5
|
//#region src/registry/borrow.ts
|
|
6
6
|
function resolveBorrowKey(BlocClass, target) {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as resetBlacConfig, a as ON_DISPOSE, c as WITH_TRACKED_STATE, d as isExcludedFromDevTools, f as isKeepAliveClass, g as getBlacConfig, h as configureBlac, i as INIT_CONFIG, l as getBlacName, n as globalRegistry, o as REMOVE_DEPS_OWNER, p as BLAC_STATIC_PROPS, r as APPLY_DEPS, t as StateContainerRegistry, v as shallowEqualState } from "./StateContainerRegistry-
|
|
2
|
-
import { a as DEP_BRAND, c as getStats, i as Cubit, l as setRegistry, n as acquire, o as StateContainer, r as resolveInstanceKey, s as getRegistry, t as ensure } from "./ensure-
|
|
1
|
+
import { _ as resetBlacConfig, a as ON_DISPOSE, c as WITH_TRACKED_STATE, d as isExcludedFromDevTools, f as isKeepAliveClass, g as getBlacConfig, h as configureBlac, i as INIT_CONFIG, l as getBlacName, n as globalRegistry, o as REMOVE_DEPS_OWNER, p as BLAC_STATIC_PROPS, r as APPLY_DEPS, t as StateContainerRegistry, v as shallowEqualState } from "./StateContainerRegistry-BsH_s0pJ.js";
|
|
2
|
+
import { a as DEP_BRAND, c as getStats, i as Cubit, l as setRegistry, n as acquire, o as StateContainer, r as resolveInstanceKey, s as getRegistry, t as ensure } from "./ensure-CneMkpbI.js";
|
|
3
3
|
import { ALL_PATHS, ALL_PATHS as ALL_PATHS$1 } from "@dirtytalk/structural";
|
|
4
4
|
//#region src/registry/borrow.ts
|
|
5
5
|
function resolveBorrowKey(BlocClass, target) {
|
|
@@ -35,8 +35,12 @@ export declare class PluginManager {
|
|
|
35
35
|
private lifecycleUnsubscribers;
|
|
36
36
|
/**
|
|
37
37
|
* Per-container channel-bridge bookkeeping. Subscribed at `created`,
|
|
38
|
-
* torn down at `disposed`. Holds the rolling `prevState`
|
|
39
|
-
* 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.
|
|
40
44
|
*/
|
|
41
45
|
private containerBridges;
|
|
42
46
|
/**
|
|
@@ -100,8 +104,9 @@ export declare class PluginManager {
|
|
|
100
104
|
* previous flush (or at create-time for the first flush) and pass the
|
|
101
105
|
* channel's `paths` argument straight through to plugins.
|
|
102
106
|
*
|
|
103
|
-
* Per-container bookkeeping is stored in a `
|
|
104
|
-
* 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()`.
|
|
105
110
|
*/
|
|
106
111
|
private attachStateBridge;
|
|
107
112
|
private detachStateBridge;
|
package/dist/plugins.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_StateContainerRegistry = require("./StateContainerRegistry-
|
|
2
|
+
const require_StateContainerRegistry = require("./StateContainerRegistry-CBLeHObO.cjs");
|
|
3
3
|
let _dirtytalk_structural = require("@dirtytalk/structural");
|
|
4
4
|
//#region src/plugin/PluginManager.ts
|
|
5
5
|
/**
|
|
@@ -39,7 +39,7 @@ var PluginManager = class {
|
|
|
39
39
|
constructor(registry) {
|
|
40
40
|
this.plugins = /* @__PURE__ */ new Map();
|
|
41
41
|
this.lifecycleUnsubscribers = [];
|
|
42
|
-
this.containerBridges = /* @__PURE__ */ new
|
|
42
|
+
this.containerBridges = /* @__PURE__ */ new Map();
|
|
43
43
|
this.contextCache = /* @__PURE__ */ new WeakMap();
|
|
44
44
|
this.registry = registry;
|
|
45
45
|
this.setupLifecycleHooks();
|
|
@@ -126,6 +126,8 @@ var PluginManager = class {
|
|
|
126
126
|
this.clear();
|
|
127
127
|
for (const unsub of this.lifecycleUnsubscribers) unsub();
|
|
128
128
|
this.lifecycleUnsubscribers = [];
|
|
129
|
+
for (const bridge of this.containerBridges.values()) bridge.unsub();
|
|
130
|
+
this.containerBridges.clear();
|
|
129
131
|
}
|
|
130
132
|
/**
|
|
131
133
|
* Wire registry lifecycle events into plugin dispatch.
|
|
@@ -171,8 +173,9 @@ var PluginManager = class {
|
|
|
171
173
|
* previous flush (or at create-time for the first flush) and pass the
|
|
172
174
|
* channel's `paths` argument straight through to plugins.
|
|
173
175
|
*
|
|
174
|
-
* Per-container bookkeeping is stored in a `
|
|
175
|
-
* container
|
|
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()`.
|
|
176
179
|
*/
|
|
177
180
|
attachStateBridge(container) {
|
|
178
181
|
if (this.containerBridges.has(container)) return;
|
package/dist/plugins.cjs.map
CHANGED
|
@@ -1 +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`. Holds the rolling `prevState` snapshot the\n * manager hands plugins on each flush.\n */\n private containerBridges = new WeakMap<\n StateContainer<any, any, any>,\n ContainerBridge\n >();\n\n /**\n * One `PluginContext` per container. The context closes over `registry` and\n * `container` only — both stable for the container's lifetime — so it is\n * safe to reuse instead of rebuilding its method table on every dispatch.\n */\n private contextCache = new WeakMap<\n StateContainer<any, any, any>,\n PluginContext\n >();\n\n /**\n * Create a new PluginManager\n * @param registry - The StateContainerRegistry to monitor for lifecycle events\n */\n constructor(registry: StateContainerRegistry) {\n this.registry = registry;\n this.setupLifecycleHooks();\n }\n\n /**\n * Install a plugin with optional configuration\n * @param plugin - The plugin to install\n * @param config - Optional plugin configuration\n * @throws Error if plugin is already installed\n */\n install(plugin: BlacPlugin, config: PluginConfig = {}): void {\n const effectiveConfig: PluginConfig = {\n enabled: true,\n environment: 'all',\n ...config,\n };\n\n if (!this.shouldEnablePlugin(effectiveConfig)) {\n if (IS_DEV) {\n console.log(\n `[BlaC] Plugin \"${plugin.name}\" skipped (environment mismatch)`,\n );\n }\n return;\n }\n\n if (this.plugins.has(plugin.name)) {\n throw new Error(`Plugin \"${plugin.name}\" is already installed`);\n }\n\n const installContext = this.buildContext(undefined);\n\n this.plugins.set(plugin.name, {\n plugin,\n config: effectiveConfig,\n installContext,\n });\n\n this.backfillPlugin(this.plugins.get(plugin.name)!);\n\n if (plugin.onInstall) {\n try {\n plugin.onInstall(installContext);\n } catch (error) {\n console.error(\n `[BlaC] Error installing plugin \"${plugin.name}\":`,\n error,\n );\n this.plugins.delete(plugin.name);\n throw error;\n }\n }\n\n if (IS_DEV) {\n console.log(\n `[BlaC] Plugin \"${plugin.name}\" v${plugin.version} installed`,\n );\n }\n }\n\n /**\n * Uninstall a plugin by name\n * @param pluginName - The name of the plugin to uninstall\n * @throws Error if plugin is not installed\n */\n uninstall(pluginName: string): void {\n const installed = this.plugins.get(pluginName);\n if (!installed) {\n throw new Error(`Plugin \"${pluginName}\" is not installed`);\n }\n\n if (installed.plugin.onUninstall) {\n try {\n installed.plugin.onUninstall();\n } catch (error) {\n console.error(\n `[BlaC] Error uninstalling plugin \"${pluginName}\":`,\n error,\n );\n }\n }\n\n this.plugins.delete(pluginName);\n if (IS_DEV) {\n console.log(`[BlaC] Plugin \"${pluginName}\" uninstalled`);\n }\n }\n\n /**\n * Get an installed plugin by name\n * @param pluginName - The name of the plugin to retrieve\n * @returns The plugin instance or undefined if not found\n */\n getPlugin(pluginName: string): BlacPlugin | undefined {\n return this.plugins.get(pluginName)?.plugin;\n }\n\n /**\n * Get all installed plugins\n * @returns Array of all installed plugins\n */\n getAllPlugins(): BlacPlugin[] {\n return Array.from(this.plugins.values()).map((p) => p.plugin);\n }\n\n /**\n * Check if a plugin is installed\n * @param pluginName - The name of the plugin to check\n * @returns true if the plugin is installed\n */\n hasPlugin(pluginName: string): boolean {\n return this.plugins.has(pluginName);\n }\n\n /**\n * Uninstall all plugins\n */\n clear(): void {\n for (const name of this.plugins.keys()) {\n this.uninstall(name);\n }\n }\n\n destroy(): void {\n this.clear();\n for (const unsub of this.lifecycleUnsubscribers) {\n unsub();\n }\n this.lifecycleUnsubscribers = [];\n }\n\n /**\n * Wire registry lifecycle events into plugin dispatch.\n *\n * `onStateChange` is NOT wired through `registry.on('stateChanged', …)` —\n * that event lacks the `PathSet` payload. Instead, on each `created` we\n * subscribe to the container's channel directly so we get\n * `(paths)` and capture `(prev, next)` via the per-container snapshot.\n */\n private setupLifecycleHooks(): void {\n this.lifecycleUnsubscribers = [\n this.registry.on('created', (instance) => {\n this.attachStateBridge(instance);\n this.notifyPlugins('onCreated', instance);\n }),\n this.registry.on('disposed', (instance) => {\n this.notifyPlugins('onDestroyed', instance);\n this.detachStateBridge(instance);\n }),\n this.registry.on('refAcquired', (instance, refId) => {\n this.notifyPlugins('onRefAcquired', instance, refId);\n }),\n this.registry.on('refReleased', (instance, refId) => {\n this.notifyPlugins('onRefReleased', instance, refId);\n }),\n this.registry.on('depsChanged', (instance, previousDeps, currentDeps) => {\n this.notifyPlugins(\n 'onDepsChanged',\n instance,\n previousDeps,\n currentDeps,\n );\n }),\n this.registry.on(\n 'hydrationChanged',\n (instance, status, previousStatus) => {\n this.notifyPlugins(\n 'onHydrationChange',\n instance,\n status,\n previousStatus,\n );\n },\n ),\n this.registry.on('activated', (instance, signal) => {\n this.notifyPlugins('onActivate', instance, signal);\n }),\n this.registry.on('deactivated', (instance) => {\n this.notifyPlugins('onDeactivate', instance);\n }),\n ];\n }\n\n /**\n * Subscribe to the container's channel with `ALL_PATHS` interest, so we\n * fire on every flush. Capture `prev` from the snapshot taken on the\n * previous flush (or at create-time for the first flush) and pass the\n * channel's `paths` argument straight through to plugins.\n *\n * Per-container bookkeeping is stored in a `WeakMap` keyed by the\n * container itself, so a disposed/GC'd container drops cleanly.\n */\n private attachStateBridge(container: StateContainer<any, any, any>): void {\n // Defensive: if a container is somehow created twice (it shouldn't be),\n // don't double-subscribe — the existing bridge is canonical.\n if (this.containerBridges.has(container)) return;\n\n const bridge: ContainerBridge = {\n unsub: () => {},\n prevState: container.state,\n };\n this.containerBridges.set(container, bridge);\n\n bridge.unsub = container.channel.subscribe(\n () => ALL_PATHS,\n (paths) => this.dispatchStateChange(container, paths),\n );\n }\n\n private detachStateBridge(container: StateContainer<any, any, any>): void {\n const bridge = this.containerBridges.get(container);\n if (!bridge) return;\n bridge.unsub();\n this.containerBridges.delete(container);\n }\n\n /**\n * Backfill a newly-installed plugin with the app's existing instances.\n *\n * Iterates every registered `Type` and its live instances (`getAll`\n * already skips disposed), attaching the state bridge (idempotent — see\n * `attachStateBridge`) and, if the plugin implements `onCreated`, invoking\n * it directly for each instance. Scoped to this single plugin only — the\n * broadcast `notifyPlugins` would re-notify *every* plugin of *every*\n * instance, which is wrong here.\n */\n private backfillPlugin(installed: InstalledPlugin): void {\n const { plugin } = installed;\n for (const Type of this.registry.getTypes()) {\n for (const instance of this.registry.getAll(Type)) {\n // getAll returns the readonly public view; internal bridging needs the\n // real container (same friction the `as any` in queryInstances handles).\n const container = instance as StateContainer<any, any, any>;\n this.attachStateBridge(container);\n\n if (!plugin.onCreated) continue;\n try {\n plugin.onCreated(this.buildContext(container));\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" onCreated (backfill):`,\n error,\n );\n }\n }\n }\n }\n\n /**\n * Channel-flush callback. Snapshots `next`, hands `(prev, next, paths)`\n * to every enabled plugin's `onStateChange`, then updates `prevState`\n * for the next flush.\n *\n * Note: `prev` is captured once and reused across plugins — every\n * plugin sees the same `prev`/`next`/`paths` regardless of dispatch\n * order, matching the \"snapshot prev once per flush\" invariant.\n */\n private dispatchStateChange(\n container: StateContainer<any, any, any>,\n paths: PathSet,\n ): void {\n const bridge = this.containerBridges.get(container);\n if (!bridge) return;\n\n const prev = bridge.prevState;\n const next = container.state;\n bridge.prevState = next;\n\n let ctx: PluginContext | undefined;\n for (const { plugin, config } of this.plugins.values()) {\n if (!config.enabled) continue;\n // eslint-disable-next-line @typescript-eslint/unbound-method -- invoked via .call below\n const hook = plugin.onStateChange;\n if (typeof hook !== 'function') continue;\n\n try {\n ctx ??= this.buildContext(container);\n hook.call(plugin, ctx, prev, next, paths);\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" onStateChange:`,\n error,\n );\n }\n }\n }\n\n /**\n * Notify all plugins of a lifecycle event.\n *\n * Builds a fresh `PluginContext` per dispatch (instance becomes\n * `ctx.container`), so plugins can reach the focal bloc through\n * `ctx.container`. The context is built lazily on the first matching\n * hook and reused for every other plugin in this dispatch — zero\n * builds if no enabled plugin implements `hookName`.\n */\n private notifyPlugins(\n hookName: Exclude<keyof BlacPlugin, 'onStateChange'>,\n instance: StateContainer<any, any, any>,\n ...extraArgs: any[]\n ): void {\n let ctx: PluginContext | undefined;\n for (const { plugin, config } of this.plugins.values()) {\n if (!config.enabled) continue;\n\n const hook = plugin[hookName];\n if (typeof hook !== 'function') continue;\n\n try {\n ctx ??= this.buildContext(instance);\n (hook as any).call(plugin, ctx, ...extraArgs);\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" ${hookName}:`,\n error,\n );\n }\n }\n }\n\n /**\n * Get the `PluginContext` for a focal container, building it once and\n * reusing it thereafter. Eviction is handled by the `WeakMap`: the context\n * dies with the container. `undefined` (install-time) is never cached.\n */\n private buildContext(\n container: StateContainer<any, any, any> | undefined,\n ): PluginContext {\n if (container !== undefined) {\n const cached = this.contextCache.get(container);\n if (cached) return cached;\n }\n const context = this.createContext(container);\n if (container !== undefined) this.contextCache.set(container, context);\n return context;\n }\n\n private createContext(\n container: StateContainer<any, any, any> | undefined,\n ): PluginContext {\n const registry = this.registry;\n return {\n container,\n\n getInstanceMetadata: (\n instance: StateContainer<any, any, any>,\n ): InstanceMetadata => {\n return {\n id: instance.$blac.id,\n className: getBlacName(\n instance.constructor as StateContainerConstructor,\n ),\n isDisposed: instance.$blac.disposed,\n name: instance.$blac.name,\n createdAt: instance.$blac.createdAt,\n state: instance.state,\n hydrationStatus: instance.$blac.hydration.status,\n isHydrated: instance.$blac.hydration.isHydrated,\n hydrationError: instance.$blac.hydration.error,\n changedWhileHydrating: instance.$blac.hydration.changedWhileHydrating,\n args: instance.args,\n };\n },\n\n getState: <S extends object = any>(instance: StateContainer<S>): S => {\n return instance.state;\n },\n\n getHydrationStatus: (instance: StateContainer<any, any, any>) => {\n return instance.$blac.hydration.status;\n },\n\n startHydration: (instance: StateContainer<any, any, any>) => {\n instance.$blac.hydration.begin();\n },\n\n applyHydratedState: <S extends object = any>(\n instance: StateContainer<S>,\n state: S,\n ): boolean => {\n return instance.$blac.hydration.apply(state);\n },\n\n finishHydration: (instance: StateContainer<any, any, any>) => {\n instance.$blac.hydration.finish();\n },\n\n failHydration: (\n instance: StateContainer<any, any, any>,\n error: Error,\n ) => {\n instance.$blac.hydration.fail(error);\n },\n\n waitForHydration: (instance: StateContainer<any, any, any>) => {\n return instance.$blac.hydration.wait();\n },\n\n queryInstances: <T extends StateContainer<any, any, any>>(\n typeClass: new (...args: any[]) => T,\n ): T[] => {\n return registry.getAll(typeClass as any);\n },\n\n getAllTypes: () => {\n return registry.getTypes();\n },\n\n getStats: () => {\n return registry.getStats();\n },\n\n getRefIds: (instanceId: string): string[] => {\n return registry.getRefIdsById(instanceId);\n },\n };\n }\n\n /**\n * Check if plugin should be enabled based on environment\n */\n private shouldEnablePlugin(config: PluginConfig): boolean {\n if (!config.enabled) return false;\n if (config.environment === 'all') return true;\n\n const currentEnv = this.getCurrentEnvironment();\n return currentEnv === config.environment;\n }\n\n /**\n * Get current environment\n */\n private getCurrentEnvironment(): 'development' | 'production' | 'test' {\n if (typeof process !== 'undefined') {\n if (process.env.NODE_ENV === 'test') return 'test';\n if (process.env.NODE_ENV === 'production') return 'production';\n return 'development';\n }\n return 'development';\n }\n}\n\n/**\n * Create a plugin manager instance\n * @param registry - The StateContainerRegistry to monitor for lifecycle events\n * @returns A new PluginManager instance\n */\nexport function createPluginManager(\n registry: StateContainerRegistry,\n): PluginManager {\n return new PluginManager(registry);\n}\n","/**\n * Plugins Subpath Export\n *\n * Plugin system utilities for extending BlaC functionality.\n * Import from `@blac/core/plugins`\n *\n * @example\n * ```typescript\n * import { PluginManager, getPluginManager, type BlacPlugin } from '@blac/core/plugins';\n *\n * const myPlugin: BlacPlugin = {\n * name: 'my-plugin',\n * onCreated(context) {\n * console.log('Instance created:', context.container);\n * },\n * };\n *\n * getPluginManager().register(myPlugin);\n * ```\n *\n * @packageDocumentation\n */\n\nimport { createPluginManager, PluginManager } from './plugin/PluginManager';\nimport { globalRegistry } from './core/StateContainerRegistry';\n\nexport { PluginManager } from './plugin/PluginManager';\nexport type {\n BlacPlugin,\n BlacPluginWithInit,\n PluginContext,\n PluginConfig,\n InstanceMetadata,\n} from './plugin/BlacPlugin';\n\n/**\n * Global plugin manager (initialized lazily)\n */\nlet _globalPluginManager: PluginManager | null = null;\n\n/**\n * Get the global plugin manager\n */\nexport function getPluginManager(): PluginManager {\n if (!_globalPluginManager) {\n _globalPluginManager = createPluginManager(globalRegistry);\n }\n return _globalPluginManager;\n}\n"],"mappings":"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"}
|
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,IAAa,gBAAb,MAA2B;;;;;CAiCzB,YAAY,UAAkC;iCAhC5B,IAAI,IAA6B;gCAEF,CAAC;0CAWvB,IAAI,IAG7B;sCAOqB,IAAI,QAGzB;EAOA,KAAK,WAAW;EAChB,KAAK,oBAAoB;CAC3B;;;;;;;CAQA,QAAQ,QAAoB,SAAuB,CAAC,GAAS;EAC3D,MAAM,kBAAgC;GACpC,SAAS;GACT,aAAa;GACb,GAAG;EACL;EAEA,IAAI,CAAC,KAAK,mBAAmB,eAAe,GAAG;GAC7C,IAAIA,+BAAAA,QACF,QAAQ,IACN,kBAAkB,OAAO,KAAK,iCAChC;GAEF;EACF;EAEA,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,GAC9B,MAAM,IAAI,MAAM,WAAW,OAAO,KAAK,uBAAuB;EAGhE,MAAM,iBAAiB,KAAK,aAAa,KAAA,CAAS;EAElD,KAAK,QAAQ,IAAI,OAAO,MAAM;GAC5B;GACA,QAAQ;GACR;EACF,CAAC;EAED,KAAK,eAAe,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAE;EAElD,IAAI,OAAO,WACT,IAAI;GACF,OAAO,UAAU,cAAc;EACjC,SAAS,OAAO;GACd,QAAQ,MACN,mCAAmC,OAAO,KAAK,KAC/C,KACF;GACA,KAAK,QAAQ,OAAO,OAAO,IAAI;GAC/B,MAAM;EACR;EAGF,IAAIA,+BAAAA,QACF,QAAQ,IACN,kBAAkB,OAAO,KAAK,KAAK,OAAO,QAAQ,WACpD;CAEJ;;;;;;CAOA,UAAU,YAA0B;EAClC,MAAM,YAAY,KAAK,QAAQ,IAAI,UAAU;EAC7C,IAAI,CAAC,WACH,MAAM,IAAI,MAAM,WAAW,WAAW,mBAAmB;EAG3D,IAAI,UAAU,OAAO,aACnB,IAAI;GACF,UAAU,OAAO,YAAY;EAC/B,SAAS,OAAO;GACd,QAAQ,MACN,qCAAqC,WAAW,KAChD,KACF;EACF;EAGF,KAAK,QAAQ,OAAO,UAAU;EAC9B,IAAIA,+BAAAA,QACF,QAAQ,IAAI,kBAAkB,WAAW,cAAc;CAE3D;;;;;;CAOA,UAAU,YAA4C;EACpD,OAAO,KAAK,QAAQ,IAAI,UAAU,GAAG;CACvC;;;;;CAMA,gBAA8B;EAC5B,OAAO,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC,EAAE,KAAK,MAAM,EAAE,MAAM;CAC9D;;;;;;CAOA,UAAU,YAA6B;EACrC,OAAO,KAAK,QAAQ,IAAI,UAAU;CACpC;;;;CAKA,QAAc;EACZ,KAAK,MAAM,QAAQ,KAAK,QAAQ,KAAK,GACnC,KAAK,UAAU,IAAI;CAEvB;CAEA,UAAgB;EACd,KAAK,MAAM;EACX,KAAK,MAAM,SAAS,KAAK,wBACvB,MAAM;EAER,KAAK,yBAAyB,CAAC;EAG/B,KAAK,MAAM,UAAU,KAAK,iBAAiB,OAAO,GAChD,OAAO,MAAM;EAEf,KAAK,iBAAiB,MAAM;CAC9B;;;;;;;;;CAUA,sBAAoC;EAClC,KAAK,yBAAyB;GAC5B,KAAK,SAAS,GAAG,YAAY,aAAa;IACxC,KAAK,kBAAkB,QAAQ;IAC/B,KAAK,cAAc,aAAa,QAAQ;GAC1C,CAAC;GACD,KAAK,SAAS,GAAG,aAAa,aAAa;IACzC,KAAK,cAAc,eAAe,QAAQ;IAC1C,KAAK,kBAAkB,QAAQ;GACjC,CAAC;GACD,KAAK,SAAS,GAAG,gBAAgB,UAAU,UAAU;IACnD,KAAK,cAAc,iBAAiB,UAAU,KAAK;GACrD,CAAC;GACD,KAAK,SAAS,GAAG,gBAAgB,UAAU,UAAU;IACnD,KAAK,cAAc,iBAAiB,UAAU,KAAK;GACrD,CAAC;GACD,KAAK,SAAS,GAAG,gBAAgB,UAAU,cAAc,gBAAgB;IACvE,KAAK,cACH,iBACA,UACA,cACA,WACF;GACF,CAAC;GACD,KAAK,SAAS,GACZ,qBACC,UAAU,QAAQ,mBAAmB;IACpC,KAAK,cACH,qBACA,UACA,QACA,cACF;GACF,CACF;GACA,KAAK,SAAS,GAAG,cAAc,UAAU,WAAW;IAClD,KAAK,cAAc,cAAc,UAAU,MAAM;GACnD,CAAC;GACD,KAAK,SAAS,GAAG,gBAAgB,aAAa;IAC5C,KAAK,cAAc,gBAAgB,QAAQ;GAC7C,CAAC;EACH;CACF;;;;;;;;;;;CAYA,kBAA0B,WAAgD;EAGxE,IAAI,KAAK,iBAAiB,IAAI,SAAS,GAAG;EAE1C,MAAM,SAA0B;GAC9B,aAAa,CAAC;GACd,WAAW,UAAU;EACvB;EACA,KAAK,iBAAiB,IAAI,WAAW,MAAM;EAE3C,OAAO,QAAQ,UAAU,QAAQ,gBACzBC,sBAAAA,YACL,UAAU,KAAK,oBAAoB,WAAW,KAAK,CACtD;CACF;CAEA,kBAA0B,WAAgD;EACxE,MAAM,SAAS,KAAK,iBAAiB,IAAI,SAAS;EAClD,IAAI,CAAC,QAAQ;EACb,OAAO,MAAM;EACb,KAAK,iBAAiB,OAAO,SAAS;CACxC;;;;;;;;;;;CAYA,eAAuB,WAAkC;EACvD,MAAM,EAAE,WAAW;EACnB,KAAK,MAAM,QAAQ,KAAK,SAAS,SAAS,GACxC,KAAK,MAAM,YAAY,KAAK,SAAS,OAAO,IAAI,GAAG;GAGjD,MAAM,YAAY;GAClB,KAAK,kBAAkB,SAAS;GAEhC,IAAI,CAAC,OAAO,WAAW;GACvB,IAAI;IACF,OAAO,UAAU,KAAK,aAAa,SAAS,CAAC;GAC/C,SAAS,OAAO;IACd,QAAQ,MACN,2BAA2B,OAAO,KAAK,0BACvC,KACF;GACF;EACF;CAEJ;;;;;;;;;;CAWA,oBACE,WACA,OACM;EACN,MAAM,SAAS,KAAK,iBAAiB,IAAI,SAAS;EAClD,IAAI,CAAC,QAAQ;EAEb,MAAM,OAAO,OAAO;EACpB,MAAM,OAAO,UAAU;EACvB,OAAO,YAAY;EAEnB,IAAI;EACJ,KAAK,MAAM,EAAE,QAAQ,YAAY,KAAK,QAAQ,OAAO,GAAG;GACtD,IAAI,CAAC,OAAO,SAAS;GAErB,MAAM,OAAO,OAAO;GACpB,IAAI,OAAO,SAAS,YAAY;GAEhC,IAAI;IACF,QAAQ,KAAK,aAAa,SAAS;IACnC,KAAK,KAAK,QAAQ,KAAK,MAAM,MAAM,KAAK;GAC1C,SAAS,OAAO;IACd,QAAQ,MACN,2BAA2B,OAAO,KAAK,mBACvC,KACF;GACF;EACF;CACF;;;;;;;;;;CAWA,cACE,UACA,UACA,GAAG,WACG;EACN,IAAI;EACJ,KAAK,MAAM,EAAE,QAAQ,YAAY,KAAK,QAAQ,OAAO,GAAG;GACtD,IAAI,CAAC,OAAO,SAAS;GAErB,MAAM,OAAO,OAAO;GACpB,IAAI,OAAO,SAAS,YAAY;GAEhC,IAAI;IACF,QAAQ,KAAK,aAAa,QAAQ;IAClC,KAAc,KAAK,QAAQ,KAAK,GAAG,SAAS;GAC9C,SAAS,OAAO;IACd,QAAQ,MACN,2BAA2B,OAAO,KAAK,IAAI,SAAS,IACpD,KACF;GACF;EACF;CACF;;;;;;CAOA,aACE,WACe;EACf,IAAI,cAAc,KAAA,GAAW;GAC3B,MAAM,SAAS,KAAK,aAAa,IAAI,SAAS;GAC9C,IAAI,QAAQ,OAAO;EACrB;EACA,MAAM,UAAU,KAAK,cAAc,SAAS;EAC5C,IAAI,cAAc,KAAA,GAAW,KAAK,aAAa,IAAI,WAAW,OAAO;EACrE,OAAO;CACT;CAEA,cACE,WACe;EACf,MAAM,WAAW,KAAK;EACtB,OAAO;GACL;GAEA,sBACE,aACqB;IACrB,OAAO;KACL,IAAI,SAAS,MAAM;KACnB,WAAWC,+BAAAA,YACT,SAAS,WACX;KACA,YAAY,SAAS,MAAM;KAC3B,MAAM,SAAS,MAAM;KACrB,WAAW,SAAS,MAAM;KAC1B,OAAO,SAAS;KAChB,iBAAiB,SAAS,MAAM,UAAU;KAC1C,YAAY,SAAS,MAAM,UAAU;KACrC,gBAAgB,SAAS,MAAM,UAAU;KACzC,uBAAuB,SAAS,MAAM,UAAU;KAChD,MAAM,SAAS;IACjB;GACF;GAEA,WAAmC,aAAmC;IACpE,OAAO,SAAS;GAClB;GAEA,qBAAqB,aAA4C;IAC/D,OAAO,SAAS,MAAM,UAAU;GAClC;GAEA,iBAAiB,aAA4C;IAC3D,SAAS,MAAM,UAAU,MAAM;GACjC;GAEA,qBACE,UACA,UACY;IACZ,OAAO,SAAS,MAAM,UAAU,MAAM,KAAK;GAC7C;GAEA,kBAAkB,aAA4C;IAC5D,SAAS,MAAM,UAAU,OAAO;GAClC;GAEA,gBACE,UACA,UACG;IACH,SAAS,MAAM,UAAU,KAAK,KAAK;GACrC;GAEA,mBAAmB,aAA4C;IAC7D,OAAO,SAAS,MAAM,UAAU,KAAK;GACvC;GAEA,iBACE,cACQ;IACR,OAAO,SAAS,OAAO,SAAgB;GACzC;GAEA,mBAAmB;IACjB,OAAO,SAAS,SAAS;GAC3B;GAEA,gBAAgB;IACd,OAAO,SAAS,SAAS;GAC3B;GAEA,YAAY,eAAiC;IAC3C,OAAO,SAAS,cAAc,UAAU;GAC1C;EACF;CACF;;;;CAKA,mBAA2B,QAA+B;EACxD,IAAI,CAAC,OAAO,SAAS,OAAO;EAC5B,IAAI,OAAO,gBAAgB,OAAO,OAAO;EAGzC,OADmB,KAAK,sBACR,MAAM,OAAO;CAC/B;;;;CAKA,wBAAuE;EACrE,IAAI,OAAO,YAAY,aAAa;GAClC,IAAI,QAAQ,IAAI,aAAa,QAAQ,OAAO;GAC5C,IAAI,QAAQ,IAAI,aAAa,cAAc,OAAO;GAClD,OAAO;EACT;EACA,OAAO;CACT;AACF;;;;;;AAOA,SAAgB,oBACd,UACe;CACf,OAAO,IAAI,cAAc,QAAQ;AACnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7gBA,IAAI,uBAA6C;;;;AAKjD,SAAgB,mBAAkC;CAChD,IAAI,CAAC,sBACH,uBAAuB,oBAAoBC,+BAAAA,cAAc;CAE3D,OAAO;AACT"}
|
package/dist/plugins.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as getBlacName, m as IS_DEV, n as globalRegistry } from "./StateContainerRegistry-
|
|
1
|
+
import { l as getBlacName, m as IS_DEV, n as globalRegistry } from "./StateContainerRegistry-BsH_s0pJ.js";
|
|
2
2
|
import { ALL_PATHS } from "@dirtytalk/structural";
|
|
3
3
|
//#region src/plugin/PluginManager.ts
|
|
4
4
|
/**
|
|
@@ -38,7 +38,7 @@ var PluginManager = class {
|
|
|
38
38
|
constructor(registry) {
|
|
39
39
|
this.plugins = /* @__PURE__ */ new Map();
|
|
40
40
|
this.lifecycleUnsubscribers = [];
|
|
41
|
-
this.containerBridges = /* @__PURE__ */ new
|
|
41
|
+
this.containerBridges = /* @__PURE__ */ new Map();
|
|
42
42
|
this.contextCache = /* @__PURE__ */ new WeakMap();
|
|
43
43
|
this.registry = registry;
|
|
44
44
|
this.setupLifecycleHooks();
|
|
@@ -125,6 +125,8 @@ var PluginManager = class {
|
|
|
125
125
|
this.clear();
|
|
126
126
|
for (const unsub of this.lifecycleUnsubscribers) unsub();
|
|
127
127
|
this.lifecycleUnsubscribers = [];
|
|
128
|
+
for (const bridge of this.containerBridges.values()) bridge.unsub();
|
|
129
|
+
this.containerBridges.clear();
|
|
128
130
|
}
|
|
129
131
|
/**
|
|
130
132
|
* Wire registry lifecycle events into plugin dispatch.
|
|
@@ -170,8 +172,9 @@ var PluginManager = class {
|
|
|
170
172
|
* previous flush (or at create-time for the first flush) and pass the
|
|
171
173
|
* channel's `paths` argument straight through to plugins.
|
|
172
174
|
*
|
|
173
|
-
* Per-container bookkeeping is stored in a `
|
|
174
|
-
* container
|
|
175
|
+
* Per-container bookkeeping is stored in `containerBridges`, a strong `Map`
|
|
176
|
+
* keyed by the container. Entries are removed explicitly — on the
|
|
177
|
+
* container's `disposed` event, or for all of them in `destroy()`.
|
|
175
178
|
*/
|
|
176
179
|
attachStateBridge(container) {
|
|
177
180
|
if (this.containerBridges.has(container)) return;
|
package/dist/plugins.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugins.js","names":[],"sources":["../src/plugin/PluginManager.ts","../src/plugins.ts"],"sourcesContent":["import { ALL_PATHS, type PathSet } from '@dirtytalk/structural';\nimport type { StateContainer } from '../core/StateContainer';\nimport { IS_DEV } from '../constants';\nimport { getBlacName } from '../utils/static-props';\nimport type { StateContainerConstructor } from '../types/utilities';\nimport type { StateContainerRegistry } from '../core/StateContainerRegistry';\nimport type {\n BlacPlugin,\n PluginContext,\n PluginConfig,\n InstanceMetadata,\n} from './BlacPlugin';\n\n/**\n * Internal structure for tracking installed plugins.\n *\n * `installContext` is the context handed to `onInstall` — it has no\n * `container`. Per-container contexts are built on demand by\n * `buildContext()` so each event carries the right focal container\n * without mutating a shared object.\n * @internal\n */\ninterface InstalledPlugin {\n plugin: BlacPlugin;\n config: PluginConfig;\n installContext: PluginContext;\n}\n\n/**\n * Per-container bookkeeping for the channel-bridge plugin dispatcher.\n *\n * - `unsub` tears down the channel subscription on dispose.\n * - `prevState` is the state snapshot the manager will hand plugins as\n * `prev` on the next flush; it is updated to the post-flush state after\n * each dispatch.\n * @internal\n */\ninterface ContainerBridge {\n unsub: () => void;\n prevState: any;\n}\n\n/**\n * Manages plugin lifecycle for the BlaC state management system.\n *\n * The manager hooks into two surfaces:\n *\n * 1. **Registry lifecycle events** (`created`, `disposed`, `refAcquired`,\n * `refReleased`, `depsChanged`, `activated`, `deactivated`) — synchronous,\n * fired by the registry.\n *\n * 2. **Per-container channel flushes** — microtask-coalesced. For each\n * container, the manager subscribes once at create-time with\n * `ALL_PATHS` interest and stashes `prevState`. On every flush it\n * captures the new state + the changed `PathSet` and dispatches\n * `onStateChange(ctx, prev, next, paths)` to every enabled plugin.\n *\n * The `ALL_PATHS` subscription cost: plugins counted as a consumer with\n * `ALL_PATHS` interest will defeat the single-consumer-skip optimization\n * in `StructuralContainer`. This is the intended trade-off — devtools /\n * persist plugins genuinely want every change, and stateful plugins\n * (logging) can decode `paths` via `container.interner.lookup(id)` to log\n * only relevant fields. Plugins that want low overhead should remain\n * uninstalled or environment-gated.\n *\n * @example\n * ```ts\n * const manager = createPluginManager(registry);\n * manager.install(myPlugin, { environment: 'development' });\n * ```\n */\nexport class PluginManager {\n private plugins = new Map<string, InstalledPlugin>();\n private registry: StateContainerRegistry;\n private lifecycleUnsubscribers: (() => void)[] = [];\n\n /**\n * Per-container channel-bridge bookkeeping. Subscribed at `created`,\n * torn down at `disposed`. Holds the rolling `prevState` snapshot the\n * manager hands plugins on each flush.\n */\n private containerBridges = new WeakMap<\n StateContainer<any, any, any>,\n ContainerBridge\n >();\n\n /**\n * One `PluginContext` per container. The context closes over `registry` and\n * `container` only — both stable for the container's lifetime — so it is\n * safe to reuse instead of rebuilding its method table on every dispatch.\n */\n private contextCache = new WeakMap<\n StateContainer<any, any, any>,\n PluginContext\n >();\n\n /**\n * Create a new PluginManager\n * @param registry - The StateContainerRegistry to monitor for lifecycle events\n */\n constructor(registry: StateContainerRegistry) {\n this.registry = registry;\n this.setupLifecycleHooks();\n }\n\n /**\n * Install a plugin with optional configuration\n * @param plugin - The plugin to install\n * @param config - Optional plugin configuration\n * @throws Error if plugin is already installed\n */\n install(plugin: BlacPlugin, config: PluginConfig = {}): void {\n const effectiveConfig: PluginConfig = {\n enabled: true,\n environment: 'all',\n ...config,\n };\n\n if (!this.shouldEnablePlugin(effectiveConfig)) {\n if (IS_DEV) {\n console.log(\n `[BlaC] Plugin \"${plugin.name}\" skipped (environment mismatch)`,\n );\n }\n return;\n }\n\n if (this.plugins.has(plugin.name)) {\n throw new Error(`Plugin \"${plugin.name}\" is already installed`);\n }\n\n const installContext = this.buildContext(undefined);\n\n this.plugins.set(plugin.name, {\n plugin,\n config: effectiveConfig,\n installContext,\n });\n\n this.backfillPlugin(this.plugins.get(plugin.name)!);\n\n if (plugin.onInstall) {\n try {\n plugin.onInstall(installContext);\n } catch (error) {\n console.error(\n `[BlaC] Error installing plugin \"${plugin.name}\":`,\n error,\n );\n this.plugins.delete(plugin.name);\n throw error;\n }\n }\n\n if (IS_DEV) {\n console.log(\n `[BlaC] Plugin \"${plugin.name}\" v${plugin.version} installed`,\n );\n }\n }\n\n /**\n * Uninstall a plugin by name\n * @param pluginName - The name of the plugin to uninstall\n * @throws Error if plugin is not installed\n */\n uninstall(pluginName: string): void {\n const installed = this.plugins.get(pluginName);\n if (!installed) {\n throw new Error(`Plugin \"${pluginName}\" is not installed`);\n }\n\n if (installed.plugin.onUninstall) {\n try {\n installed.plugin.onUninstall();\n } catch (error) {\n console.error(\n `[BlaC] Error uninstalling plugin \"${pluginName}\":`,\n error,\n );\n }\n }\n\n this.plugins.delete(pluginName);\n if (IS_DEV) {\n console.log(`[BlaC] Plugin \"${pluginName}\" uninstalled`);\n }\n }\n\n /**\n * Get an installed plugin by name\n * @param pluginName - The name of the plugin to retrieve\n * @returns The plugin instance or undefined if not found\n */\n getPlugin(pluginName: string): BlacPlugin | undefined {\n return this.plugins.get(pluginName)?.plugin;\n }\n\n /**\n * Get all installed plugins\n * @returns Array of all installed plugins\n */\n getAllPlugins(): BlacPlugin[] {\n return Array.from(this.plugins.values()).map((p) => p.plugin);\n }\n\n /**\n * Check if a plugin is installed\n * @param pluginName - The name of the plugin to check\n * @returns true if the plugin is installed\n */\n hasPlugin(pluginName: string): boolean {\n return this.plugins.has(pluginName);\n }\n\n /**\n * Uninstall all plugins\n */\n clear(): void {\n for (const name of this.plugins.keys()) {\n this.uninstall(name);\n }\n }\n\n destroy(): void {\n this.clear();\n for (const unsub of this.lifecycleUnsubscribers) {\n unsub();\n }\n this.lifecycleUnsubscribers = [];\n }\n\n /**\n * Wire registry lifecycle events into plugin dispatch.\n *\n * `onStateChange` is NOT wired through `registry.on('stateChanged', …)` —\n * that event lacks the `PathSet` payload. Instead, on each `created` we\n * subscribe to the container's channel directly so we get\n * `(paths)` and capture `(prev, next)` via the per-container snapshot.\n */\n private setupLifecycleHooks(): void {\n this.lifecycleUnsubscribers = [\n this.registry.on('created', (instance) => {\n this.attachStateBridge(instance);\n this.notifyPlugins('onCreated', instance);\n }),\n this.registry.on('disposed', (instance) => {\n this.notifyPlugins('onDestroyed', instance);\n this.detachStateBridge(instance);\n }),\n this.registry.on('refAcquired', (instance, refId) => {\n this.notifyPlugins('onRefAcquired', instance, refId);\n }),\n this.registry.on('refReleased', (instance, refId) => {\n this.notifyPlugins('onRefReleased', instance, refId);\n }),\n this.registry.on('depsChanged', (instance, previousDeps, currentDeps) => {\n this.notifyPlugins(\n 'onDepsChanged',\n instance,\n previousDeps,\n currentDeps,\n );\n }),\n this.registry.on(\n 'hydrationChanged',\n (instance, status, previousStatus) => {\n this.notifyPlugins(\n 'onHydrationChange',\n instance,\n status,\n previousStatus,\n );\n },\n ),\n this.registry.on('activated', (instance, signal) => {\n this.notifyPlugins('onActivate', instance, signal);\n }),\n this.registry.on('deactivated', (instance) => {\n this.notifyPlugins('onDeactivate', instance);\n }),\n ];\n }\n\n /**\n * Subscribe to the container's channel with `ALL_PATHS` interest, so we\n * fire on every flush. Capture `prev` from the snapshot taken on the\n * previous flush (or at create-time for the first flush) and pass the\n * channel's `paths` argument straight through to plugins.\n *\n * Per-container bookkeeping is stored in a `WeakMap` keyed by the\n * container itself, so a disposed/GC'd container drops cleanly.\n */\n private attachStateBridge(container: StateContainer<any, any, any>): void {\n // Defensive: if a container is somehow created twice (it shouldn't be),\n // don't double-subscribe — the existing bridge is canonical.\n if (this.containerBridges.has(container)) return;\n\n const bridge: ContainerBridge = {\n unsub: () => {},\n prevState: container.state,\n };\n this.containerBridges.set(container, bridge);\n\n bridge.unsub = container.channel.subscribe(\n () => ALL_PATHS,\n (paths) => this.dispatchStateChange(container, paths),\n );\n }\n\n private detachStateBridge(container: StateContainer<any, any, any>): void {\n const bridge = this.containerBridges.get(container);\n if (!bridge) return;\n bridge.unsub();\n this.containerBridges.delete(container);\n }\n\n /**\n * Backfill a newly-installed plugin with the app's existing instances.\n *\n * Iterates every registered `Type` and its live instances (`getAll`\n * already skips disposed), attaching the state bridge (idempotent — see\n * `attachStateBridge`) and, if the plugin implements `onCreated`, invoking\n * it directly for each instance. Scoped to this single plugin only — the\n * broadcast `notifyPlugins` would re-notify *every* plugin of *every*\n * instance, which is wrong here.\n */\n private backfillPlugin(installed: InstalledPlugin): void {\n const { plugin } = installed;\n for (const Type of this.registry.getTypes()) {\n for (const instance of this.registry.getAll(Type)) {\n // getAll returns the readonly public view; internal bridging needs the\n // real container (same friction the `as any` in queryInstances handles).\n const container = instance as StateContainer<any, any, any>;\n this.attachStateBridge(container);\n\n if (!plugin.onCreated) continue;\n try {\n plugin.onCreated(this.buildContext(container));\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" onCreated (backfill):`,\n error,\n );\n }\n }\n }\n }\n\n /**\n * Channel-flush callback. Snapshots `next`, hands `(prev, next, paths)`\n * to every enabled plugin's `onStateChange`, then updates `prevState`\n * for the next flush.\n *\n * Note: `prev` is captured once and reused across plugins — every\n * plugin sees the same `prev`/`next`/`paths` regardless of dispatch\n * order, matching the \"snapshot prev once per flush\" invariant.\n */\n private dispatchStateChange(\n container: StateContainer<any, any, any>,\n paths: PathSet,\n ): void {\n const bridge = this.containerBridges.get(container);\n if (!bridge) return;\n\n const prev = bridge.prevState;\n const next = container.state;\n bridge.prevState = next;\n\n let ctx: PluginContext | undefined;\n for (const { plugin, config } of this.plugins.values()) {\n if (!config.enabled) continue;\n // eslint-disable-next-line @typescript-eslint/unbound-method -- invoked via .call below\n const hook = plugin.onStateChange;\n if (typeof hook !== 'function') continue;\n\n try {\n ctx ??= this.buildContext(container);\n hook.call(plugin, ctx, prev, next, paths);\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" onStateChange:`,\n error,\n );\n }\n }\n }\n\n /**\n * Notify all plugins of a lifecycle event.\n *\n * Builds a fresh `PluginContext` per dispatch (instance becomes\n * `ctx.container`), so plugins can reach the focal bloc through\n * `ctx.container`. The context is built lazily on the first matching\n * hook and reused for every other plugin in this dispatch — zero\n * builds if no enabled plugin implements `hookName`.\n */\n private notifyPlugins(\n hookName: Exclude<keyof BlacPlugin, 'onStateChange'>,\n instance: StateContainer<any, any, any>,\n ...extraArgs: any[]\n ): void {\n let ctx: PluginContext | undefined;\n for (const { plugin, config } of this.plugins.values()) {\n if (!config.enabled) continue;\n\n const hook = plugin[hookName];\n if (typeof hook !== 'function') continue;\n\n try {\n ctx ??= this.buildContext(instance);\n (hook as any).call(plugin, ctx, ...extraArgs);\n } catch (error) {\n console.error(\n `[BlaC] Error in plugin \"${plugin.name}\" ${hookName}:`,\n error,\n );\n }\n }\n }\n\n /**\n * Get the `PluginContext` for a focal container, building it once and\n * reusing it thereafter. Eviction is handled by the `WeakMap`: the context\n * dies with the container. `undefined` (install-time) is never cached.\n */\n private buildContext(\n container: StateContainer<any, any, any> | undefined,\n ): PluginContext {\n if (container !== undefined) {\n const cached = this.contextCache.get(container);\n if (cached) return cached;\n }\n const context = this.createContext(container);\n if (container !== undefined) this.contextCache.set(container, context);\n return context;\n }\n\n private createContext(\n container: StateContainer<any, any, any> | undefined,\n ): PluginContext {\n const registry = this.registry;\n return {\n container,\n\n getInstanceMetadata: (\n instance: StateContainer<any, any, any>,\n ): InstanceMetadata => {\n return {\n id: instance.$blac.id,\n className: getBlacName(\n instance.constructor as StateContainerConstructor,\n ),\n isDisposed: instance.$blac.disposed,\n name: instance.$blac.name,\n createdAt: instance.$blac.createdAt,\n state: instance.state,\n hydrationStatus: instance.$blac.hydration.status,\n isHydrated: instance.$blac.hydration.isHydrated,\n hydrationError: instance.$blac.hydration.error,\n changedWhileHydrating: instance.$blac.hydration.changedWhileHydrating,\n args: instance.args,\n };\n },\n\n getState: <S extends object = any>(instance: StateContainer<S>): S => {\n return instance.state;\n },\n\n getHydrationStatus: (instance: StateContainer<any, any, any>) => {\n return instance.$blac.hydration.status;\n },\n\n startHydration: (instance: StateContainer<any, any, any>) => {\n instance.$blac.hydration.begin();\n },\n\n applyHydratedState: <S extends object = any>(\n instance: StateContainer<S>,\n state: S,\n ): boolean => {\n return instance.$blac.hydration.apply(state);\n },\n\n finishHydration: (instance: StateContainer<any, any, any>) => {\n instance.$blac.hydration.finish();\n },\n\n failHydration: (\n instance: StateContainer<any, any, any>,\n error: Error,\n ) => {\n instance.$blac.hydration.fail(error);\n },\n\n waitForHydration: (instance: StateContainer<any, any, any>) => {\n return instance.$blac.hydration.wait();\n },\n\n queryInstances: <T extends StateContainer<any, any, any>>(\n typeClass: new (...args: any[]) => T,\n ): T[] => {\n return registry.getAll(typeClass as any);\n },\n\n getAllTypes: () => {\n return registry.getTypes();\n },\n\n getStats: () => {\n return registry.getStats();\n },\n\n getRefIds: (instanceId: string): string[] => {\n return registry.getRefIdsById(instanceId);\n },\n };\n }\n\n /**\n * Check if plugin should be enabled based on environment\n */\n private shouldEnablePlugin(config: PluginConfig): boolean {\n if (!config.enabled) return false;\n if (config.environment === 'all') return true;\n\n const currentEnv = this.getCurrentEnvironment();\n return currentEnv === config.environment;\n }\n\n /**\n * Get current environment\n */\n private getCurrentEnvironment(): 'development' | 'production' | 'test' {\n if (typeof process !== 'undefined') {\n if (process.env.NODE_ENV === 'test') return 'test';\n if (process.env.NODE_ENV === 'production') return 'production';\n return 'development';\n }\n return 'development';\n }\n}\n\n/**\n * Create a plugin manager instance\n * @param registry - The StateContainerRegistry to monitor for lifecycle events\n * @returns A new PluginManager instance\n */\nexport function createPluginManager(\n registry: StateContainerRegistry,\n): PluginManager {\n return new PluginManager(registry);\n}\n","/**\n * Plugins Subpath Export\n *\n * Plugin system utilities for extending BlaC functionality.\n * Import from `@blac/core/plugins`\n *\n * @example\n * ```typescript\n * import { PluginManager, getPluginManager, type BlacPlugin } from '@blac/core/plugins';\n *\n * const myPlugin: BlacPlugin = {\n * name: 'my-plugin',\n * onCreated(context) {\n * console.log('Instance created:', context.container);\n * },\n * };\n *\n * getPluginManager().register(myPlugin);\n * ```\n *\n * @packageDocumentation\n */\n\nimport { createPluginManager, PluginManager } from './plugin/PluginManager';\nimport { globalRegistry } from './core/StateContainerRegistry';\n\nexport { PluginManager } from './plugin/PluginManager';\nexport type {\n BlacPlugin,\n BlacPluginWithInit,\n PluginContext,\n PluginConfig,\n InstanceMetadata,\n} from './plugin/BlacPlugin';\n\n/**\n * Global plugin manager (initialized lazily)\n */\nlet _globalPluginManager: PluginManager | null = null;\n\n/**\n * Get the global plugin manager\n */\nexport function getPluginManager(): PluginManager {\n if (!_globalPluginManager) {\n _globalPluginManager = createPluginManager(globalRegistry);\n }\n return _globalPluginManager;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugins.js","names":[],"sources":["../src/plugin/PluginManager.ts","../src/plugins.ts"],"sourcesContent":["import { ALL_PATHS, type PathSet } from '@dirtytalk/structural';\nimport type { StateContainer } from '../core/StateContainer';\nimport { IS_DEV } from '../constants';\nimport { getBlacName } from '../utils/static-props';\nimport type { StateContainerConstructor } from '../types/utilities';\nimport type { StateContainerRegistry } from '../core/StateContainerRegistry';\nimport type {\n BlacPlugin,\n PluginContext,\n PluginConfig,\n InstanceMetadata,\n} from './BlacPlugin';\n\n/**\n * Internal structure for tracking installed plugins.\n *\n * `installContext` is the context handed to `onInstall` — it has no\n * `container`. Per-container contexts are built on demand by\n * `buildContext()` so each event carries the right focal container\n * without mutating a shared object.\n * @internal\n */\ninterface InstalledPlugin {\n plugin: BlacPlugin;\n config: PluginConfig;\n installContext: PluginContext;\n}\n\n/**\n * Per-container bookkeeping for the channel-bridge plugin dispatcher.\n *\n * - `unsub` tears down the channel subscription on dispose.\n * - `prevState` is the state snapshot the manager will hand plugins as\n * `prev` on the next flush; it is updated to the post-flush state after\n * each dispatch.\n * @internal\n */\ninterface ContainerBridge {\n unsub: () => void;\n prevState: any;\n}\n\n/**\n * Manages plugin lifecycle for the BlaC state management system.\n *\n * The manager hooks into two surfaces:\n *\n * 1. **Registry lifecycle events** (`created`, `disposed`, `refAcquired`,\n * `refReleased`, `depsChanged`, `activated`, `deactivated`) — synchronous,\n * fired by the registry.\n *\n * 2. **Per-container channel flushes** — microtask-coalesced. For each\n * container, the manager subscribes once at create-time with\n * `ALL_PATHS` interest and stashes `prevState`. On every flush it\n * captures the new state + the changed `PathSet` and dispatches\n * `onStateChange(ctx, prev, next, paths)` to every enabled plugin.\n *\n * The `ALL_PATHS` subscription cost: plugins counted as a consumer with\n * `ALL_PATHS` interest will defeat the single-consumer-skip optimization\n * in `StructuralContainer`. This is the intended trade-off — devtools /\n * persist plugins genuinely want every change, and stateful plugins\n * (logging) can decode `paths` via `container.interner.lookup(id)` to log\n * only relevant fields. Plugins that want low overhead should remain\n * uninstalled or environment-gated.\n *\n * @example\n * ```ts\n * const manager = createPluginManager(registry);\n * manager.install(myPlugin, { environment: 'development' });\n * ```\n */\nexport class PluginManager {\n private plugins = new Map<string, InstalledPlugin>();\n private registry: StateContainerRegistry;\n private lifecycleUnsubscribers: (() => void)[] = [];\n\n /**\n * Per-container channel-bridge bookkeeping. Subscribed at `created`,\n * torn down at `disposed` 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/testing.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_StateContainerRegistry = require("./StateContainerRegistry-
|
|
3
|
-
const require_ensure = require("./ensure-
|
|
2
|
+
const require_StateContainerRegistry = require("./StateContainerRegistry-CBLeHObO.cjs");
|
|
3
|
+
const require_ensure = require("./ensure-D3w6YGp9.cjs");
|
|
4
4
|
//#region src/testing.ts
|
|
5
5
|
/** Synthetic owner id used by test helpers for pre-wired deps. */
|
|
6
6
|
const TESTING_DEPS_OWNER = "testing-deps";
|
package/dist/testing.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as INIT_CONFIG, r as APPLY_DEPS, t as StateContainerRegistry } from "./StateContainerRegistry-
|
|
2
|
-
import { i as Cubit, l as setRegistry, r as resolveInstanceKey, s as getRegistry, t as ensure } from "./ensure-
|
|
1
|
+
import { i as INIT_CONFIG, r as APPLY_DEPS, t as StateContainerRegistry } from "./StateContainerRegistry-BsH_s0pJ.js";
|
|
2
|
+
import { i as Cubit, l as setRegistry, r as resolveInstanceKey, s as getRegistry, t as ensure } from "./ensure-CneMkpbI.js";
|
|
3
3
|
//#region src/testing.ts
|
|
4
4
|
/** Synthetic owner id used by test helpers for pre-wired deps. */
|
|
5
5
|
const TESTING_DEPS_OWNER = "testing-deps";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blac/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.22",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Brendan Mullins <jsnanigans@gmail.com>",
|
|
6
6
|
"description": "Type-safe, class-based state management with proxy tracking and plugin system",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
}
|
|
84
84
|
],
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@dirtytalk/structural": "^0.1.
|
|
86
|
+
"@dirtytalk/structural": "^0.1.2"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
@@ -92,7 +92,6 @@
|
|
|
92
92
|
"@types/node": "^26.4.1",
|
|
93
93
|
"arktype": "^2.2.3",
|
|
94
94
|
"fast-check": "^4.9.0",
|
|
95
|
-
"jsdom": "^30.0.1",
|
|
96
95
|
"publint": "^0.3.24",
|
|
97
96
|
"size-limit": "^13.0.3",
|
|
98
97
|
"typescript": "^6.0.3",
|