Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

@blac/react

Package Overview
Dependencies
Maintainers
1
Versions
75
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@blac/react - npm Package Compare versions

Comparing version
2.0.0-rc.6
to
2.0.0-rc.8
+1
dist/index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC"}
import type { ExtractState } from '@blac/core';
import type { RefObject } from 'react';
export interface UseBlocOptions<TBloc> {
staticProps?: any;
instanceId?: string | number;
dependencies?: (state: ExtractState<TBloc>, bloc: TBloc) => unknown[];
autoTrack?: boolean;
disableGetterCache?: boolean;
onMount?: (bloc: TBloc) => void;
onUnmount?: (bloc: TBloc) => void;
}
export interface UseBlocOptionsWithDependencies<TBloc> extends UseBlocOptions<TBloc> {
dependencies: (state: ExtractState<TBloc>, bloc: TBloc) => unknown[];
autoTrack?: never;
}
export type UseBlocReturn<TBloc> = [
ExtractState<TBloc>,
TBloc,
RefObject<ComponentRef>
];
export type ComponentRef = {
__blocInstanceId?: string;
__bridge?: any;
};
//# sourceMappingURL=types.d.ts.map
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,cAAc,CAAC,KAAK;IACnC,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,KAAK,OAAO,EAAE,CAAC;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,8BAA8B,CAAC,KAAK,CACnD,SAAQ,cAAc,CAAC,KAAK,CAAC;IAC7B,YAAY,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,KAAK,OAAO,EAAE,CAAC;IACrE,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,CAAC,KAAK,IAAI;IACjC,YAAY,CAAC,KAAK,CAAC;IACnB,KAAK;IACL,SAAS,CAAC,YAAY,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB,CAAC"}
import { type BlocConstructor, StateContainer } from '@blac/core';
import type { UseBlocOptions, UseBlocReturn } from './types';
/**
* Lifecycle: INITIAL MOUNT
* 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn
* 2. useSyncExternalStore calls getSnapshotFn (1st time) - lazy creates tracker, starts tracking, returns proxy
* 3. Component renders - proxy tracks property accesses
* 4. useSyncExternalStore calls getSnapshotFn (2nd time) - captures tracked paths, starts new tracking, returns proxy
* 5. useSyncExternalStore calls subscribeFn - sets up state change listener
*
* Lifecycle: STATE CHANGE
* 1. Bloc state changes
* 2. subscribeFn callback checks hasChanges() - only re-renders if tracked paths changed
* 3. If re-render: getSnapshotFn captures previous paths, starts tracking, returns proxy
*
* Lifecycle: RE-RENDER (parent re-render)
* 1. useMemo returns cached values (same bloc, subscribeFn, getSnapshotFn)
* 2. useSyncExternalStore calls getSnapshotFn - captures paths, starts tracking, returns proxy
*/
export declare function useBloc<T extends new (...args: any[]) => StateContainer<any>>(BlocClass: T & BlocConstructor<InstanceType<T>>, options?: UseBlocOptions<InstanceType<T>>): UseBlocReturn<InstanceType<T>>;
//# sourceMappingURL=useBloc.d.ts.map
{"version":3,"file":"useBloc.d.ts","sourceRoot":"","sources":["../src/useBloc.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,eAAe,EACpB,cAAc,EAef,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAgB,MAAM,SAAS,CAAC;AAuB3E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,cAAc,CAAC,GAAG,CAAC,EAC3E,SAAS,EAAE,CAAC,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAC/C,OAAO,CAAC,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GACxC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAmHhC"}
import { type BlocConstructor, StateContainer } from '@blac/core';
export interface UseBlocActionsOptions<TBloc> {
staticProps?: any;
instanceId?: string | number;
onMount?: (bloc: TBloc) => void;
onUnmount?: (bloc: TBloc) => void;
}
export declare function useBlocActions<T extends new (...args: any[]) => StateContainer<any>>(BlocClass: T & BlocConstructor<InstanceType<T>>, options?: UseBlocActionsOptions<InstanceType<T>>): InstanceType<T>;
//# sourceMappingURL=useBlocActions.d.ts.map
{"version":3,"file":"useBlocActions.d.ts","sourceRoot":"","sources":["../src/useBlocActions.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,eAAe,EACpB,cAAc,EAEf,MAAM,YAAY,CAAC;AAUpB,MAAM,WAAW,qBAAqB,CAAC,KAAK;IAC1C,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;CACnC;AAED,wBAAgB,cAAc,CAC5B,CAAC,SAAS,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,cAAc,CAAC,GAAG,CAAC,EAErD,SAAS,EAAE,CAAC,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAC/C,OAAO,CAAC,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAC/C,YAAY,CAAC,CAAC,CAAC,CAiDjB"}
/**
* Instance key generation utilities for React integration
*/
import type { ComponentRef } from '../types';
/**
* Generate an instance key for a bloc
*
* Logic:
* - If user provides instanceId, use it (convert number to string)
* - If isolated, generate or reuse a unique key for this component
* - Otherwise, return undefined (use default key)
*
* @param componentRef - React component reference (persists across remounts)
* @param isIsolated - Whether the bloc is isolated
* @param providedId - User-provided instance ID (from options)
* @returns Instance key string or undefined for default
*/
export declare function generateInstanceKey(componentRef: ComponentRef, isIsolated: boolean, providedId?: string | number): string | undefined;
//# sourceMappingURL=instance-keys.d.ts.map
{"version":3,"file":"instance-keys.d.ts","sourceRoot":"","sources":["../../src/utils/instance-keys.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,OAAO,EACnB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAC3B,MAAM,GAAG,SAAS,CAgBpB"}
+48
-312

@@ -1,195 +0,31 @@

//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let react = require("react");
react = __toESM(react);
let __blac_core = require("@blac/core");
__blac_core = __toESM(__blac_core);
//#region src/useBloc.ts
//#region src/utils/instance-keys.ts
/**
* Cache for property descriptors to avoid repeated prototype chain walks
* Maps from object constructor to a map of property name to descriptor
* Instance key generation utilities for React integration
*/
const descriptorCache = /* @__PURE__ */ new WeakMap();
/**
* Cache for proxied blocs to ensure same proxy is returned for same bloc instance
* This is important for identity checks (e.g., bloc1 === bloc2) across components
* using the same shared bloc instance.
*/
const blocProxyCache = /* @__PURE__ */ new WeakMap();
/**
* Map to store the currently active tracker during render.
* This allows the cached proxy to know which component's tracker to use.
* Set before render, cleared after render.
*/
const activeTrackerMap = /* @__PURE__ */ new WeakMap();
/**
* Get property descriptor for a given property, with caching
* Generate an instance key for a bloc
*
* @remarks
* Walks up the prototype chain once per class to find the descriptor,
* then caches the result for performance. This is critical because
* we need to distinguish getters from methods and properties.
* Logic:
* - If user provides instanceId, use it (convert number to string)
* - If isolated, generate or reuse a unique key for this component
* - Otherwise, return undefined (use default key)
*
* @param obj - The object to get the descriptor from
* @param prop - The property name or symbol
* @returns The property descriptor if found, undefined otherwise
* @param componentRef - React component reference (persists across remounts)
* @param isIsolated - Whether the bloc is isolated
* @param providedId - User-provided instance ID (from options)
* @returns Instance key string or undefined for default
*/
function getDescriptor(obj, prop) {
const constructor = obj.constructor;
let constructorCache = descriptorCache.get(constructor);
if (constructorCache?.has(prop)) return constructorCache.get(prop);
let current = obj;
let descriptor;
while (current && current !== Object.prototype) {
descriptor = Object.getOwnPropertyDescriptor(current, prop);
if (descriptor) break;
current = Object.getPrototypeOf(current);
}
if (!constructorCache) {
constructorCache = /* @__PURE__ */ new Map();
descriptorCache.set(constructor, constructorCache);
}
constructorCache.set(prop, descriptor);
return descriptor;
}
/**
* Check if a property is a getter (has a getter descriptor)
*
* @param obj - The object to check
* @param prop - The property name or symbol
* @returns True if the property is a getter, false otherwise
*/
function isGetter(obj, prop) {
return getDescriptor(obj, prop)?.get !== void 0;
}
/**
* Create a new getter tracking state
*
* @returns A new GetterTrackingState initialized with empty collections
*/
function createGetterTracker() {
return {
trackedValues: /* @__PURE__ */ new Map(),
currentlyAccessing: /* @__PURE__ */ new Set(),
trackedGetters: /* @__PURE__ */ new Set(),
isTracking: false,
renderCache: /* @__PURE__ */ new Map(),
cacheValid: false
};
}
/**
* Create a proxy that intercepts getter access on a bloc instance
*
* @remarks
* This proxy wraps the bloc instance to track which getters are accessed
* during component render. When tracking is enabled (during render phase),
* it records accessed getters and stores their computed values for later
* comparison.
*
* IMPORTANT: This function caches proxies per bloc instance. Multiple components
* sharing the same bloc will get the same proxy instance. Each component sets
* its tracker in activeTrackerMap before render, and the proxy looks it up.
*
* @param bloc - The bloc instance to wrap
* @returns A proxied bloc that tracks getter access
*/
function createBlocProxy(bloc) {
const cached = blocProxyCache.get(bloc);
if (cached) return cached;
const proxy = new Proxy(bloc, { get(target, prop, receiver) {
const tracker = activeTrackerMap.get(target);
if (tracker?.isTracking && isGetter(target, prop)) {
tracker.currentlyAccessing.add(prop);
if (tracker.cacheValid && tracker.renderCache.has(prop)) {
const cachedValue = tracker.renderCache.get(prop);
tracker.trackedValues.set(prop, cachedValue);
return cachedValue;
}
const value = getDescriptor(target, prop).get.call(target);
tracker.trackedValues.set(prop, value);
return value;
}
return Reflect.get(target, prop, receiver);
} });
blocProxyCache.set(bloc, proxy);
return proxy;
}
/**
* Check if any tracked getters have changed values
*
* @remarks
* Re-computes all getters that were accessed during the last render and
* compares their new values with stored values using Object.is() (reference
* equality).
*
* OPTIMIZATION: Render cache population
* - Computes ALL tracked getters (no early exit) to populate the render cache
* - This ensures each getter is computed only once per render cycle
* - If we're going to re-render, getters accessed during render will use cached values
* - Cache is populated even if no changes detected (useful for parent-triggered re-renders)
* - Trade-off: Give up early exit to ensure full cache population
*
* Error handling: If a getter throws during re-computation, we log a warning,
* stop tracking that specific getter, and treat it as "changed" to trigger
* a re-render. This prevents the tracking system from breaking while still
* allowing React's error boundary to handle the error on the next render.
*
* @param bloc - The bloc instance
* @param tracker - The getter tracking state
* @returns True if any tracked getter value changed, false otherwise
*/
function hasGetterChanges(bloc, tracker) {
if (!tracker || tracker.trackedGetters.size === 0) return false;
tracker.renderCache.clear();
let hasAnyChange = false;
for (const prop of tracker.trackedGetters) try {
const descriptor = getDescriptor(bloc, prop);
if (!descriptor?.get) continue;
const newValue = descriptor.get.call(bloc);
const oldValue = tracker.trackedValues.get(prop);
tracker.renderCache.set(prop, newValue);
tracker.trackedValues.set(prop, newValue);
if (!Object.is(newValue, oldValue)) hasAnyChange = true;
} catch (error) {
console.warn(`[useBloc] Getter "${String(prop)}" threw error during change detection. Stopping tracking for this getter.`, error);
tracker.trackedGetters.delete(prop);
tracker.trackedValues.delete(prop);
tracker.cacheValid = false;
return true;
}
tracker.cacheValid = true;
return hasAnyChange;
}
/**
* Generates instance ID for isolated blocs
*/
function generateInstanceId$1(componentRef, isIsolated, providedId) {
if (providedId) return providedId;
function generateInstanceKey(componentRef, isIsolated, providedId) {
if (providedId !== void 0) return typeof providedId === "number" ? String(providedId) : providedId;
if (isIsolated) {
if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = `isolated-${Math.random().toString(36).slice(2, 11)}`;
if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = (0, __blac_core.generateIsolatedKey)();
return componentRef.__blocInstanceId;
}
}
//#endregion
//#region src/useBloc.ts
function determineTrackingMode(options) {

@@ -202,71 +38,2 @@ return {

/**
* Factory: Creates subscribe function for automatic proxy tracking mode
*/
function createAutoTrackSubscribe(instance, hookState) {
return (callback) => {
return instance.subscribe(() => {
const tracker = hookState.tracker || (hookState.tracker = (0, __blac_core.createTrackerState)());
let stateChanged = (0, __blac_core.hasChanges)(tracker, instance.state);
if (tracker.pathCache.size === 0 && hookState.getterTracker && hookState.getterTracker.trackedGetters.size > 0) stateChanged = false;
if (stateChanged) {
callback();
return;
}
if (hasGetterChanges(instance, hookState.getterTracker)) callback();
});
};
}
/**
* Factory: Creates subscribe function for manual dependencies mode
*/
function createManualDepsSubscribe(instance, hookState, options) {
return (callback) => {
return instance.subscribe(() => {
const newDeps = options.dependencies(instance.state, instance);
if (!hookState.manualDepsCache || !(0, __blac_core.shallowEqual)(hookState.manualDepsCache, newDeps)) {
hookState.manualDepsCache = newDeps;
callback();
}
});
};
}
/**
* Factory: Creates subscribe function for no-tracking mode
*/
function createNoTrackSubscribe(instance) {
return (callback) => instance.subscribe(callback);
}
/**
* Factory: Creates getSnapshot function for automatic proxy tracking mode
*/
function createAutoTrackSnapshot(instance, hookState) {
return () => {
const tracker = hookState.tracker || (hookState.tracker = (0, __blac_core.createTrackerState)());
if ((0, __blac_core.hasTrackedData)(tracker)) (0, __blac_core.captureTrackedPaths)(tracker, instance.state);
if (hookState.getterTracker) {
if (hookState.getterTracker.currentlyAccessing.size > 0) hookState.getterTracker.trackedGetters = new Set(hookState.getterTracker.currentlyAccessing);
hookState.getterTracker.currentlyAccessing.clear();
hookState.getterTracker.isTracking = true;
activeTrackerMap.set(instance, hookState.getterTracker);
}
(0, __blac_core.startTracking)(tracker);
return (0, __blac_core.createProxy)(tracker, instance.state);
};
}
/**
* Factory: Creates getSnapshot function for manual dependencies mode
*/
function createManualDepsSnapshot(instance, hookState, options) {
return () => {
hookState.manualDepsCache = options.dependencies(instance.state, instance);
return instance.state;
};
}
/**
* Factory: Creates getSnapshot function for no-tracking mode
*/
function createNoTrackSnapshot(instance) {
return () => instance.state;
}
/**
* Lifecycle: INITIAL MOUNT

@@ -290,46 +57,39 @@ * 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn

const componentRef = (0, react.useRef)({});
const [bloc, subscribe, getSnapshot, instanceKey, hookState, rawInstance] = (0, react.useMemo)(() => {
const isIsolated = BlocClass.isolated === true;
const Constructor = BlocClass;
const instanceId = generateInstanceId$1(componentRef.current, isIsolated, options?.instanceId);
const instance = Constructor.getOrCreate(instanceId, options?.staticProps);
const Constructor = BlocClass;
const isIsolated = (0, __blac_core.isIsolatedClass)(BlocClass);
const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = (0, react.useMemo)(() => {
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
const instance = BlocClass.resolve(instanceKey$1, options?.staticProps);
const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
const hookState$1 = {
tracker: null,
manualDepsCache: null,
getterTracker: null,
proxiedBloc: null
};
let subscribeFn;
let getSnapshotFn;
if (useManualDeps) {
subscribeFn = createManualDepsSubscribe(instance, hookState$1, options);
getSnapshotFn = createManualDepsSnapshot(instance, hookState$1, options);
hookState$1.proxiedBloc = instance;
let adapterState$1;
if (useManualDeps && options?.dependencies) {
adapterState$1 = (0, __blac_core.initManualDepsState)(instance);
subscribeFn = (0, __blac_core.createManualDepsSubscribe)(instance, adapterState$1, { dependencies: options.dependencies });
getSnapshotFn = (0, __blac_core.createManualDepsSnapshot)(instance, adapterState$1, { dependencies: options.dependencies });
} else if (!autoTrackEnabled) {
subscribeFn = createNoTrackSubscribe(instance);
getSnapshotFn = createNoTrackSnapshot(instance);
hookState$1.proxiedBloc = instance;
adapterState$1 = (0, __blac_core.initNoTrackState)(instance);
subscribeFn = (0, __blac_core.createNoTrackSubscribe)(instance);
getSnapshotFn = (0, __blac_core.createNoTrackSnapshot)(instance);
} else {
subscribeFn = createAutoTrackSubscribe(instance, hookState$1);
getSnapshotFn = createAutoTrackSnapshot(instance, hookState$1);
hookState$1.getterTracker = createGetterTracker();
hookState$1.proxiedBloc = createBlocProxy(instance);
adapterState$1 = (0, __blac_core.initAutoTrackState)(instance);
subscribeFn = (0, __blac_core.createAutoTrackSubscribe)(instance, adapterState$1);
getSnapshotFn = (0, __blac_core.createAutoTrackSnapshot)(instance, adapterState$1);
}
return [
hookState$1.proxiedBloc,
adapterState$1.proxiedBloc,
subscribeFn,
getSnapshotFn,
instanceId,
hookState$1,
instanceKey$1,
adapterState$1,
instance
];
}, [BlocClass]);
}, [BlocClass, options?.instanceId]);
const state = (0, react.useSyncExternalStore)(subscribe, getSnapshot);
const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
const externalDepsManager = (0, react.useRef)(new __blac_core.ExternalDependencyManager());
(0, react.useEffect)(() => {
if (hookState.getterTracker) {
hookState.getterTracker.isTracking = false;
hookState.getterTracker.cacheValid = false;
activeTrackerMap.delete(rawInstance);
}
(0, __blac_core.disableGetterTracking)(adapterState, rawInstance);
externalDepsManager.current.updateSubscriptions(adapterState.getterTracker, rawInstance, forceUpdate);
});

@@ -339,5 +99,6 @@ (0, react.useEffect)(() => {

return () => {
externalDepsManager.current.cleanup();
if (options?.onUnmount) options.onUnmount(bloc);
BlocClass.release(instanceKey);
if (BlocClass.isolated === true && !rawInstance.isDisposed) rawInstance.dispose();
Constructor.release(instanceKey);
if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
};

@@ -354,34 +115,9 @@ }, []);

//#region src/useBlocActions.ts
/**
* Generates instance ID for isolated blocs
*/
function generateInstanceId(componentRef, isIsolated, providedId) {
if (providedId) return providedId;
if (isIsolated) {
if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = `isolated-${Math.random().toString(36).slice(2, 11)}`;
return componentRef.__blocInstanceId;
}
}
/**
* React hook for accessing bloc instance without state subscription.
* Use this when you only need to call bloc methods/actions without reading state.
*
* Benefits over useBloc:
* - No state subscription overhead
* - No proxy tracking
* - Component never re-renders due to bloc state changes
* - Lighter weight for action-only components
*
* @template TBloc - The StateContainer type
* @param BlocClass - The bloc class constructor
* @param options - Optional configuration
* @returns The bloc instance
*/
function useBlocActions(BlocClass, options) {
const componentRef = (0, react.useRef)({});
const [bloc, instanceKey] = (0, react.useMemo)(() => {
const isIsolated = BlocClass.isolated === true;
const isIsolated = (0, __blac_core.isIsolatedClass)(BlocClass);
const Constructor = BlocClass;
const instanceId = generateInstanceId(componentRef.current, isIsolated, options?.instanceId);
return [Constructor.getOrCreate(instanceId, options?.staticProps), instanceId];
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
return [options?.staticProps ? Constructor.resolve(instanceKey$1, options.staticProps) : Constructor.resolve(instanceKey$1), instanceKey$1];
}, [BlocClass]);

@@ -393,3 +129,3 @@ (0, react.useEffect)(() => {

BlocClass.release(instanceKey);
if (BlocClass.isolated === true && !bloc.isDisposed) bloc.dispose();
if ((0, __blac_core.isIsolatedClass)(BlocClass) && !bloc.isDisposed) bloc.dispose();
};

@@ -396,0 +132,0 @@ }, []);

@@ -1,1 +0,1 @@

{"version":3,"file":"index.cjs","names":["descriptor: PropertyDescriptor | undefined","generateInstanceId","hookState: HookState<TBloc>","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<TBloc>","hookState"],"sources":["../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * useBloc - hook for BlaC state management in React with automatic proxy tracking\n *\n * @example\n * ```tsx\n * // Basic usage - automatic tracking of accessed properties\n * function Counter() {\n * const [state, bloc] = useBloc(CounterBloc);\n * return (\n * <div>\n * <p>Count: {state.count}</p> // Only re-renders when count changes\n * <button onClick={bloc.increment}>+</button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useMemo, useSyncExternalStore, useEffect, useRef } from 'react';\nimport {\n type AnyObject,\n type BlocConstructor,\n StateContainer,\n type ExtractState,\n // Import tracking utilities from core\n type TrackerState,\n createTrackerState,\n startTracking,\n createProxy,\n captureTrackedPaths,\n hasChanges,\n hasTrackedData,\n shallowEqual,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\n\n/**\n * StateContainer constructor with required static methods\n */\ntype StateContainerConstructor<TBloc extends StateContainer<any>> =\n BlocConstructor<TBloc> & {\n getOrCreate(instanceKey?: string, ...args: any[]): TBloc;\n release(instanceKey?: string): void;\n };\n\n/**\n * State for tracking getter access and values during render\n *\n * @remarks\n * This tracks which getters are accessed during component render and stores\n * their computed values for comparison on subsequent state changes.\n * Only getters (properties with a getter descriptor) are tracked automatically.\n *\n * Similar to state tracking, we use two sets:\n * - `currentlyAccessing`: Temporary set for getters accessed during current render\n * - `trackedGetters`: Committed set of getters from last completed render\n *\n * PERFORMANCE: Render cache optimization\n * - When checking if we should re-render (hasGetterChanges), we compute all getters\n * - Store these computed values in `renderCache` for the upcoming render\n * - During render, if we access a cached getter, use the cached value instead of recomputing\n * - This ensures each getter is computed at most once per render cycle\n * - Cache is invalidated after render completes or when state changes again\n */\ninterface GetterTrackingState {\n /** Map of getter names to their last computed values (for comparison) */\n trackedValues: Map<string | symbol, unknown>;\n /** Temporary set of getters being accessed during current render */\n currentlyAccessing: Set<string | symbol>;\n /** Committed set of getters from last completed render (used for change detection) */\n trackedGetters: Set<string | symbol>;\n /** Flag to enable/disable tracking (only enabled during render phase) */\n isTracking: boolean;\n /** Cache of getter values computed during hasGetterChanges (valid for current render cycle) */\n renderCache: Map<string | symbol, unknown>;\n /** Flag indicating render cache is valid and can be used */\n cacheValid: boolean;\n}\n\n/**\n * Cache for property descriptors to avoid repeated prototype chain walks\n * Maps from object constructor to a map of property name to descriptor\n */\nconst descriptorCache = new WeakMap<\n Function,\n Map<string | symbol, PropertyDescriptor | undefined>\n>();\n\n/**\n * Cache for proxied blocs to ensure same proxy is returned for same bloc instance\n * This is important for identity checks (e.g., bloc1 === bloc2) across components\n * using the same shared bloc instance.\n */\nconst blocProxyCache = new WeakMap<StateContainer<any>, any>();\n\n/**\n * Map to store the currently active tracker during render.\n * This allows the cached proxy to know which component's tracker to use.\n * Set before render, cleared after render.\n */\nconst activeTrackerMap = new WeakMap<\n StateContainer<any>,\n GetterTrackingState\n>();\n\n/**\n * Get property descriptor for a given property, with caching\n *\n * @remarks\n * Walks up the prototype chain once per class to find the descriptor,\n * then caches the result for performance. This is critical because\n * we need to distinguish getters from methods and properties.\n *\n * @param obj - The object to get the descriptor from\n * @param prop - The property name or symbol\n * @returns The property descriptor if found, undefined otherwise\n */\nfunction getDescriptor(\n obj: any,\n prop: string | symbol,\n): PropertyDescriptor | undefined {\n const constructor = obj.constructor;\n\n // Try to get from cache\n let constructorCache = descriptorCache.get(constructor);\n if (constructorCache?.has(prop)) {\n return constructorCache.get(prop);\n }\n\n // Walk prototype chain to find descriptor\n let current = obj;\n let descriptor: PropertyDescriptor | undefined;\n\n while (current && current !== Object.prototype) {\n descriptor = Object.getOwnPropertyDescriptor(current, prop);\n if (descriptor) {\n break;\n }\n current = Object.getPrototypeOf(current);\n }\n\n // Cache the result\n if (!constructorCache) {\n constructorCache = new Map();\n descriptorCache.set(constructor, constructorCache);\n }\n constructorCache.set(prop, descriptor);\n\n return descriptor;\n}\n\n/**\n * Check if a property is a getter (has a getter descriptor)\n *\n * @param obj - The object to check\n * @param prop - The property name or symbol\n * @returns True if the property is a getter, false otherwise\n */\nfunction isGetter(obj: any, prop: string | symbol): boolean {\n const descriptor = getDescriptor(obj, prop);\n return descriptor?.get !== undefined;\n}\n\n/**\n * Create a new getter tracking state\n *\n * @returns A new GetterTrackingState initialized with empty collections\n */\nfunction createGetterTracker(): GetterTrackingState {\n return {\n trackedValues: new Map(),\n currentlyAccessing: new Set(),\n trackedGetters: new Set(),\n isTracking: false,\n renderCache: new Map(),\n cacheValid: false,\n };\n}\n\n/**\n * Create a proxy that intercepts getter access on a bloc instance\n *\n * @remarks\n * This proxy wraps the bloc instance to track which getters are accessed\n * during component render. When tracking is enabled (during render phase),\n * it records accessed getters and stores their computed values for later\n * comparison.\n *\n * IMPORTANT: This function caches proxies per bloc instance. Multiple components\n * sharing the same bloc will get the same proxy instance. Each component sets\n * its tracker in activeTrackerMap before render, and the proxy looks it up.\n *\n * @param bloc - The bloc instance to wrap\n * @returns A proxied bloc that tracks getter access\n */\nfunction createBlocProxy<TBloc extends StateContainer<AnyObject>>(\n bloc: TBloc,\n): TBloc {\n // Check cache first - return existing proxy if available\n const cached = blocProxyCache.get(bloc);\n if (cached) {\n return cached;\n }\n\n const proxy = new Proxy(bloc, {\n get(target, prop, receiver) {\n // Get the active tracker for this bloc (set by getSnapshot)\n const tracker = activeTrackerMap.get(target);\n\n // Only track during render phase (when tracker is active and tracking enabled)\n if (tracker?.isTracking && isGetter(target, prop)) {\n // Record that this getter was accessed during current render\n tracker.currentlyAccessing.add(prop);\n\n // Use cached value if available from previous change detection\n if (tracker.cacheValid && tracker.renderCache.has(prop)) {\n const cachedValue = tracker.renderCache.get(prop);\n // Also store in trackedValues for consistency\n tracker.trackedValues.set(prop, cachedValue);\n return cachedValue;\n }\n\n // Compute getter if no cache available (first access or cache invalidated)\n const descriptor = getDescriptor(target, prop);\n const value = descriptor!.get!.call(target);\n tracker.trackedValues.set(prop, value);\n return value;\n }\n\n // Default behavior for non-getters or when tracking disabled\n return Reflect.get(target, prop, receiver);\n },\n });\n\n blocProxyCache.set(bloc, proxy);\n return proxy;\n}\n\n/**\n * Check if any tracked getters have changed values\n *\n * @remarks\n * Re-computes all getters that were accessed during the last render and\n * compares their new values with stored values using Object.is() (reference\n * equality).\n *\n * OPTIMIZATION: Render cache population\n * - Computes ALL tracked getters (no early exit) to populate the render cache\n * - This ensures each getter is computed only once per render cycle\n * - If we're going to re-render, getters accessed during render will use cached values\n * - Cache is populated even if no changes detected (useful for parent-triggered re-renders)\n * - Trade-off: Give up early exit to ensure full cache population\n *\n * Error handling: If a getter throws during re-computation, we log a warning,\n * stop tracking that specific getter, and treat it as \"changed\" to trigger\n * a re-render. This prevents the tracking system from breaking while still\n * allowing React's error boundary to handle the error on the next render.\n *\n * @param bloc - The bloc instance\n * @param tracker - The getter tracking state\n * @returns True if any tracked getter value changed, false otherwise\n */\nfunction hasGetterChanges<TBloc extends StateContainer<AnyObject>>(\n bloc: TBloc,\n tracker: GetterTrackingState | null,\n): boolean {\n // Early return if no tracker or no getters tracked\n if (!tracker || tracker.trackedGetters.size === 0) {\n return false;\n }\n\n // Clear previous render cache\n tracker.renderCache.clear();\n\n let hasAnyChange = false;\n\n // Compute all getters to populate render cache (no early exit)\n for (const prop of tracker.trackedGetters) {\n try {\n const descriptor = getDescriptor(bloc, prop);\n if (!descriptor?.get) {\n // Getter no longer exists (shouldn't happen, but be defensive)\n continue;\n }\n\n const newValue = descriptor.get.call(bloc);\n const oldValue = tracker.trackedValues.get(prop);\n\n // Store in render cache for upcoming render (even if unchanged)\n tracker.renderCache.set(prop, newValue);\n\n // Update tracked values for next comparison\n tracker.trackedValues.set(prop, newValue);\n\n // Use Object.is for reference equality comparison\n if (!Object.is(newValue, oldValue)) {\n hasAnyChange = true;\n // Don't return early - continue computing and caching remaining getters\n }\n } catch (error) {\n // Getter threw an error during comparison\n console.warn(\n `[useBloc] Getter \"${String(prop)}\" threw error during change detection. Stopping tracking for this getter.`,\n error,\n );\n\n // Stop tracking this getter\n tracker.trackedGetters.delete(prop);\n tracker.trackedValues.delete(prop);\n\n // Treat as \"changed\" to trigger re-render\n // Still return early on error to avoid cascading failures\n tracker.cacheValid = false; // Invalidate cache due to error\n return true;\n }\n }\n\n // Mark cache as valid for the upcoming render\n tracker.cacheValid = true;\n\n return hasAnyChange;\n}\n\n/**\n * Generates instance ID for isolated blocs\n */\nfunction generateInstanceId(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string,\n): string | undefined {\n if (providedId) return providedId;\n\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = `isolated-${Math.random().toString(36).slice(2, 11)}`;\n }\n return componentRef.__blocInstanceId;\n }\n\n return undefined;\n}\n\n/**\n * Determines tracking mode from options\n */\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainer<AnyObject>>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled: options?.autoTrack !== false,\n };\n}\n\n/**\n * Internal state for subscription and snapshot functions\n */\ninterface HookState<TBloc extends StateContainer<AnyObject>> {\n /** State property tracker (existing) */\n tracker: TrackerState<ExtractState<TBloc>> | null;\n /** Manual dependencies cache (existing) */\n manualDepsCache: unknown[] | null;\n /** Getter tracking state (new) */\n getterTracker: GetterTrackingState | null;\n /** Cached proxied bloc instance (new) */\n proxiedBloc: TBloc | null;\n}\n\n/**\n * Factory: Creates subscribe function for automatic proxy tracking mode\n */\nfunction createAutoTrackSubscribe<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n hookState: HookState<TBloc>,\n): (callback: () => void) => () => void {\n return (callback: () => void) => {\n return instance.subscribe(() => {\n const tracker =\n hookState.tracker ||\n (hookState.tracker = createTrackerState<ExtractState<TBloc>>());\n\n let stateChanged = hasChanges(tracker, instance.state);\n\n // Special case: if NO state properties were tracked (pathCache.size === 0)\n // but getters WERE tracked, then don't treat \"no state tracking\" as \"track everything\".\n // Only rely on getter changes in this case.\n if (\n tracker.pathCache.size === 0 &&\n hookState.getterTracker &&\n hookState.getterTracker.trackedGetters.size > 0\n ) {\n stateChanged = false; // Override - only getters are relevant\n }\n\n // EARLY EXIT: If state already changed, skip getter checks entirely\n if (stateChanged) {\n callback();\n return;\n }\n\n // Only check getters if state didn't change\n const getterChanged = hasGetterChanges(instance, hookState.getterTracker);\n\n if (getterChanged) {\n callback();\n }\n });\n };\n}\n\n/**\n * Factory: Creates subscribe function for manual dependencies mode\n */\nfunction createManualDepsSubscribe<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n hookState: HookState<TBloc>,\n options: UseBlocOptions<TBloc>,\n): (callback: () => void) => () => void {\n return (callback: () => void) => {\n return instance.subscribe(() => {\n const newDeps = options.dependencies!(instance.state, instance);\n if (\n !hookState.manualDepsCache ||\n !shallowEqual(hookState.manualDepsCache, newDeps)\n ) {\n hookState.manualDepsCache = newDeps;\n callback();\n }\n });\n };\n}\n\n/**\n * Factory: Creates subscribe function for no-tracking mode\n */\nfunction createNoTrackSubscribe<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n): (callback: () => void) => () => void {\n return (callback: () => void) => instance.subscribe(callback);\n}\n\n/**\n * Factory: Creates getSnapshot function for automatic proxy tracking mode\n */\nfunction createAutoTrackSnapshot<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n hookState: HookState<TBloc>,\n): () => ExtractState<TBloc> {\n return () => {\n const tracker =\n hookState.tracker ||\n (hookState.tracker = createTrackerState<ExtractState<TBloc>>());\n\n if (hasTrackedData(tracker)) {\n captureTrackedPaths(tracker, instance.state);\n }\n\n // Enable getter tracking during render and set as active tracker\n if (hookState.getterTracker) {\n // Capture getters from previous render (commit currentlyAccessing to trackedGetters)\n if (hookState.getterTracker.currentlyAccessing.size > 0) {\n hookState.getterTracker.trackedGetters = new Set(\n hookState.getterTracker.currentlyAccessing,\n );\n }\n\n // Clear and enable tracking for this render\n hookState.getterTracker.currentlyAccessing.clear();\n hookState.getterTracker.isTracking = true;\n\n // Set this component's tracker as the active one for this bloc\n activeTrackerMap.set(instance, hookState.getterTracker);\n }\n\n startTracking(tracker);\n return createProxy(tracker, instance.state);\n };\n}\n\n/**\n * Factory: Creates getSnapshot function for manual dependencies mode\n */\nfunction createManualDepsSnapshot<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n hookState: HookState<TBloc>,\n options: UseBlocOptions<TBloc>,\n): () => ExtractState<TBloc> {\n return () => {\n hookState.manualDepsCache = options.dependencies!(instance.state, instance);\n return instance.state;\n };\n}\n\n/**\n * Factory: Creates getSnapshot function for no-tracking mode\n */\nfunction createNoTrackSnapshot<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n): () => ExtractState<TBloc> {\n return () => instance.state;\n}\n\n/**\n * Lifecycle: INITIAL MOUNT\n * 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn\n * 2. useSyncExternalStore calls getSnapshotFn (1st time) - lazy creates tracker, starts tracking, returns proxy\n * 3. Component renders - proxy tracks property accesses\n * 4. useSyncExternalStore calls getSnapshotFn (2nd time) - captures tracked paths, starts new tracking, returns proxy\n * 5. useSyncExternalStore calls subscribeFn - sets up state change listener\n *\n * Lifecycle: STATE CHANGE\n * 1. Bloc state changes\n * 2. subscribeFn callback checks hasChanges() - only re-renders if tracked paths changed\n * 3. If re-render: getSnapshotFn captures previous paths, starts tracking, returns proxy\n *\n * Lifecycle: RE-RENDER (parent re-render)\n * 1. useMemo returns cached values (same bloc, subscribeFn, getSnapshotFn)\n * 2. useSyncExternalStore calls getSnapshotFn - captures paths, starts tracking, returns proxy\n */\nexport function useBloc<TBloc extends StateContainer<AnyObject>>(\n BlocClass: BlocConstructor<TBloc>,\n options?: UseBlocOptions<TBloc>,\n): UseBlocReturn<TBloc> {\n // Component reference that persists across React Strict Mode remounts\n const componentRef = useRef<ComponentRef>({});\n\n const [bloc, subscribe, getSnapshot, instanceKey, hookState, rawInstance] =\n useMemo(() => {\n const isIsolated =\n (BlocClass as { isolated?: boolean }).isolated === true;\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n\n // Generate instance key\n const instanceId = generateInstanceId(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n // Get or create bloc instance\n const instance = Constructor.getOrCreate(\n instanceId,\n options?.staticProps,\n );\n\n // Determine tracking mode\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n // Mutable state shared between subscribe and getSnapshot\n const hookState: HookState<TBloc> = {\n tracker: null,\n manualDepsCache: null,\n getterTracker: null,\n proxiedBloc: null,\n };\n\n // Create subscribe and getSnapshot functions based on tracking mode\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<TBloc>;\n\n if (useManualDeps) {\n // Manual dependencies mode - no automatic tracking\n subscribeFn = createManualDepsSubscribe(instance, hookState, options!);\n getSnapshotFn = createManualDepsSnapshot(instance, hookState, options!);\n hookState.proxiedBloc = instance; // Use raw instance\n } else if (!autoTrackEnabled) {\n // No tracking mode\n subscribeFn = createNoTrackSubscribe(instance);\n getSnapshotFn = createNoTrackSnapshot(instance);\n hookState.proxiedBloc = instance; // Use raw instance\n } else {\n // Auto-tracking mode - enable both state and getter tracking\n subscribeFn = createAutoTrackSubscribe(instance, hookState);\n getSnapshotFn = createAutoTrackSnapshot(instance, hookState);\n\n // Initialize getter tracker and create proxied bloc\n hookState.getterTracker = createGetterTracker();\n hookState.proxiedBloc = createBlocProxy(instance);\n }\n\n return [\n hookState.proxiedBloc!,\n subscribeFn,\n getSnapshotFn,\n instanceId,\n hookState,\n instance,\n ] as const;\n }, [BlocClass]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n // Disable getter tracking after each render and clear active tracker\n // Also invalidate render cache since this render cycle is complete\n useEffect(() => {\n if (hookState.getterTracker) {\n hookState.getterTracker.isTracking = false;\n hookState.getterTracker.cacheValid = false; // Invalidate cache after render\n activeTrackerMap.delete(rawInstance);\n }\n });\n\n // Mount/unmount lifecycle\n useEffect(() => {\n // Call onMount callback if provided\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n // Call onUnmount callback if provided\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n // Release bloc reference\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n Constructor.release(instanceKey);\n\n // For isolated instances, dispose manually since registry doesn't track them\n const isIsolated =\n (BlocClass as { isolated?: boolean }).isolated === true;\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<TBloc>;\n}\n","/**\n * useBlocActions - hook for accessing bloc instance without state subscription\n *\n * @example\n * ```tsx\n * // Use when you only need to call actions, not read state\n * function ActionsOnly() {\n * const bloc = useBlocActions(CounterBloc);\n * return (\n * <div>\n * <button onClick={bloc.increment}>+</button>\n * <button onClick={bloc.decrement}>-</button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useMemo, useEffect, useRef } from 'react';\nimport type { AnyObject, BlocConstructor, StateContainer } from '@blac/core';\nimport type { ComponentRef } from './types';\n\n/**\n * StateContainer constructor with required static methods\n */\ntype StateContainerConstructor<TBloc extends StateContainer<any>> =\n BlocConstructor<TBloc> & {\n getOrCreate(instanceKey?: string, ...args: any[]): TBloc;\n release(instanceKey?: string): void;\n };\n\n/**\n * Configuration options for the useBlocActions hook\n *\n * @template TBloc - The StateContainer type\n */\nexport interface UseBlocActionsOptions<\n TBloc extends StateContainer<AnyObject>,\n> {\n /**\n * Static props to pass to the Bloc constructor\n * Type should match the constructor's first parameter\n */\n staticProps?: AnyObject;\n\n /**\n * Custom instance ID for shared blocs\n * - For isolated blocs, each useBlocActions call gets its own instance\n * - For shared blocs, the same instanceId will share the same bloc instance\n */\n instanceId?: string;\n\n /**\n * Callback invoked when the component mounts\n *\n * @param bloc - The bloc instance\n */\n onMount?: (bloc: TBloc) => void;\n\n /**\n * Callback invoked when the component unmounts\n *\n * @param bloc - The bloc instance\n */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * Generates instance ID for isolated blocs\n */\nfunction generateInstanceId(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string,\n): string | undefined {\n if (providedId) return providedId;\n\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = `isolated-${Math.random().toString(36).slice(2, 11)}`;\n }\n return componentRef.__blocInstanceId;\n }\n\n return undefined;\n}\n\n/**\n * React hook for accessing bloc instance without state subscription.\n * Use this when you only need to call bloc methods/actions without reading state.\n *\n * Benefits over useBloc:\n * - No state subscription overhead\n * - No proxy tracking\n * - Component never re-renders due to bloc state changes\n * - Lighter weight for action-only components\n *\n * @template TBloc - The StateContainer type\n * @param BlocClass - The bloc class constructor\n * @param options - Optional configuration\n * @returns The bloc instance\n */\nexport function useBlocActions<TBloc extends StateContainer<AnyObject>>(\n BlocClass: BlocConstructor<TBloc>,\n options?: UseBlocActionsOptions<TBloc>,\n): TBloc {\n // Component reference that persists across React Strict Mode remounts\n const componentRef = useRef<ComponentRef>({});\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = (BlocClass as { isolated?: boolean }).isolated === true;\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n\n // Generate instance key\n const instanceId = generateInstanceId(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n // Get or create bloc instance\n const instance = Constructor.getOrCreate(instanceId, options?.staticProps);\n\n return [instance, instanceId] as const;\n }, [BlocClass]);\n\n // Mount/unmount lifecycle\n useEffect(() => {\n // Call onMount callback if provided\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n // Call onUnmount callback if provided\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n // Release bloc reference\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n Constructor.release(instanceKey);\n\n // For isolated instances, dispose manually since registry doesn't track them\n const isIsolated =\n (BlocClass as { isolated?: boolean }).isolated === true;\n if (isIsolated && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc;\n}\n"],"mappings":"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"}
{"version":3,"file":"index.cjs","names":["instanceKey","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<TBloc>","adapterState: AdapterState<TBloc>","adapterState","ExternalDependencyManager","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type BlocConstructor,\n StateContainer,\n type ExtractState,\n type AdapterState,\n ExternalDependencyManager,\n createAutoTrackSubscribe,\n createManualDepsSubscribe,\n createNoTrackSubscribe,\n createAutoTrackSnapshot,\n createManualDepsSnapshot,\n createNoTrackSnapshot,\n initAutoTrackState,\n initManualDepsState,\n initNoTrackState,\n disableGetterTracking,\n isIsolatedClass,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\ntype StateContainerConstructor<TBloc extends StateContainer<any>> =\n BlocConstructor<TBloc> & {\n resolve(instanceKey?: string, ...args: any[]): TBloc;\n release(instanceKey?: string): void;\n };\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainer<any>>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled: options?.autoTrack !== false,\n };\n}\n\n/**\n * Lifecycle: INITIAL MOUNT\n * 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn\n * 2. useSyncExternalStore calls getSnapshotFn (1st time) - lazy creates tracker, starts tracking, returns proxy\n * 3. Component renders - proxy tracks property accesses\n * 4. useSyncExternalStore calls getSnapshotFn (2nd time) - captures tracked paths, starts new tracking, returns proxy\n * 5. useSyncExternalStore calls subscribeFn - sets up state change listener\n *\n * Lifecycle: STATE CHANGE\n * 1. Bloc state changes\n * 2. subscribeFn callback checks hasChanges() - only re-renders if tracked paths changed\n * 3. If re-render: getSnapshotFn captures previous paths, starts tracking, returns proxy\n *\n * Lifecycle: RE-RENDER (parent re-render)\n * 1. useMemo returns cached values (same bloc, subscribeFn, getSnapshotFn)\n * 2. useSyncExternalStore calls getSnapshotFn - captures paths, starts tracking, returns proxy\n */\nexport function useBloc<T extends new (...args: any[]) => StateContainer<any>>(\n BlocClass: T & BlocConstructor<InstanceType<T>>,\n options?: UseBlocOptions<InstanceType<T>>,\n): UseBlocReturn<InstanceType<T>> {\n // Component reference that persists across React Strict Mode remounts\n type TBloc = InstanceType<T>;\n const componentRef = useRef<ComponentRef>({});\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n const isIsolated = isIsolatedClass(BlocClass);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<TBloc>,\n string | undefined,\n AdapterState<TBloc>,\n TBloc,\n ]\n >(() => {\n // Generate instance key\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n // Get or create bloc instance with ownership (increments ref count)\n const instance = BlocClass.resolve(instanceKey, options?.staticProps);\n\n // Determine tracking mode\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n // Create subscribe and getSnapshot functions based on tracking mode\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<TBloc>;\n let adapterState: AdapterState<TBloc>;\n\n if (useManualDeps && options?.dependencies) {\n // Manual dependencies mode - no automatic tracking\n adapterState = initManualDepsState(instance);\n subscribeFn = createManualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = createManualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n // No tracking mode\n adapterState = initNoTrackState(instance);\n subscribeFn = createNoTrackSubscribe(instance);\n getSnapshotFn = createNoTrackSnapshot(instance);\n } else {\n // Auto-tracking mode - enable both state and getter tracking\n adapterState = initAutoTrackState(instance);\n subscribeFn = createAutoTrackSubscribe(instance, adapterState);\n getSnapshotFn = createAutoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc!,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n // Force re-render mechanism for external bloc changes\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n // External dependency manager (persists across renders)\n const externalDepsManager = useRef(new ExternalDependencyManager());\n\n // Disable getter tracking and manage external bloc subscriptions after each render\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterTracker,\n rawInstance,\n forceUpdate,\n );\n }); // Run on every render to pick up new dependencies and disable tracking\n\n // Mount/unmount lifecycle\n useEffect(() => {\n // Call onMount callback if provided\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n // Cleanup in proper order: subscriptions -> callbacks -> disposal\n\n // 1. Clean up external subscriptions FIRST (before bloc is disposed)\n externalDepsManager.current.cleanup();\n\n // 2. Call onUnmount callback if provided\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n // 3. Release bloc reference\n Constructor.release(instanceKey);\n\n // 4. For isolated instances, dispose manually since registry doesn't track them\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<TBloc>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport {\n type BlocConstructor,\n StateContainer,\n isIsolatedClass,\n} from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\ntype StateContainerConstructor<TBloc extends StateContainer<any>> =\n BlocConstructor<TBloc> & {\n resolve(instanceKey?: string, ...args: any[]): TBloc;\n release(instanceKey?: string): void;\n };\n\nexport interface UseBlocActionsOptions<TBloc> {\n staticProps?: any;\n instanceId?: string | number;\n onMount?: (bloc: TBloc) => void;\n onUnmount?: (bloc: TBloc) => void;\n}\n\nexport function useBlocActions<\n T extends new (...args: any[]) => StateContainer<any>,\n>(\n BlocClass: T & BlocConstructor<InstanceType<T>>,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n // Component reference that persists across React Strict Mode remounts\n type TBloc = InstanceType<T>;\n const componentRef = useRef<ComponentRef>({});\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n\n // Generate instance key\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n // Get or create bloc instance with ownership (increments ref count)\n const instance = options?.staticProps\n ? Constructor.resolve(instanceKey, options.staticProps)\n : Constructor.resolve(instanceKey);\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n // Mount/unmount lifecycle\n useEffect(() => {\n // Call onMount callback if provided\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n // Call onUnmount callback if provided\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n // Release bloc reference\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n Constructor.release(instanceKey);\n\n // For isolated instances, dispose manually since registry doesn't track them\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBACd,cACA,YACA,YACoB;AAEpB,KAAI,eAAe,OACjB,QAAO,OAAO,eAAe,WAAW,OAAO,WAAW,GAAG;AAI/D,KAAI,YAAY;AACd,MAAI,CAAC,aAAa,iBAChB,cAAa,yDAAwC;AAEvD,SAAO,aAAa;;;;;;ACIxB,SAAS,sBACP,SACc;AACd,QAAO;EACL,eAAe,SAAS,iBAAiB;EACzC,kBAAkB,SAAS,cAAc;EAC1C;;;;;;;;;;;;;;;;;;;AAoBH,SAAgB,QACd,WACA,SACgC;CAGhC,MAAM,iCAAoC,EAAE,CAAC;CAC7C,MAAM,cAAc;CACpB,MAAM,8CAA6B,UAAU;CAE7C,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,wCAUtD;EAEN,MAAMA,gBAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;EAGD,MAAM,WAAW,UAAU,QAAQA,eAAa,SAAS,YAAY;EAGrE,MAAM,EAAE,eAAe,qBACrB,sBAAsB,QAAQ;EAGhC,IAAIC;EACJ,IAAIC;EACJ,IAAIC;AAEJ,MAAI,iBAAiB,SAAS,cAAc;AAE1C,yDAAmC,SAAS;AAC5C,4DAAwC,UAAUC,gBAAc,EAC9D,cAAc,QAAQ,cACvB,CAAC;AACF,6DAAyC,UAAUA,gBAAc,EAC/D,cAAc,QAAQ,cACvB,CAAC;aACO,CAAC,kBAAkB;AAE5B,sDAAgC,SAAS;AACzC,yDAAqC,SAAS;AAC9C,0DAAsC,SAAS;SAC1C;AAEL,wDAAkC,SAAS;AAC3C,2DAAuC,UAAUA,eAAa;AAC9D,4DAAwC,UAAUA,eAAa;;AAGjE,SAAO;GACLA,eAAa;GACb;GACA;GACAJ;GACAI;GACA;GACD;IACA,CAAC,WAAW,SAAS,WAAW,CAAC;CAEtC,MAAM,wCAA6B,WAAW,YAAY;CAG1D,MAAM,GAAG,sCAA2B,MAAc,IAAI,GAAG,EAAE;CAG3D,MAAM,wCAA6B,IAAIC,uCAA2B,CAAC;AAGnE,4BAAgB;AACd,yCAAsB,cAAc,YAAY;AAChD,sBAAoB,QAAQ,oBAC1B,aAAa,eACb,aACA,YACD;GACD;AAGF,4BAAgB;AAEd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAK;AAGvB,eAAa;AAIX,uBAAoB,QAAQ,SAAS;AAGrC,OAAI,SAAS,UACX,SAAQ,UAAU,KAAK;AAIzB,eAAY,QAAQ,YAAY;AAGhC,OAAI,cAAc,CAAC,YAAY,WAC7B,aAAY,SAAS;;IAGxB,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa;;;;;AChKpC,SAAgB,eAGd,WACA,SACiB;CAGjB,MAAM,iCAAoC,EAAE,CAAC;CAE7C,MAAM,CAAC,MAAM,wCAA6B;EACxC,MAAM,8CAA6B,UAAU;EAC7C,MAAM,cAAc;EAGpB,MAAMC,gBAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;AAOD,SAAO,CAJU,SAAS,cACtB,YAAY,QAAQA,eAAa,QAAQ,YAAY,GACrD,YAAY,QAAQA,cAAY,EAElBA,cAAY;IAC7B,CAAC,UAAU,CAAC;AAGf,4BAAgB;AAEd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAK;AAGvB,eAAa;AAEX,OAAI,SAAS,UACX,SAAQ,UAAU,KAAK;AAKzB,GADoB,UACR,QAAQ,YAAY;AAGhC,wCAAoB,UAAU,IAAI,CAAC,KAAK,WACtC,MAAK,SAAS;;IAGjB,EAAE,CAAC;AAEN,QAAO"}

@@ -1,154 +0,12 @@

import { AnyObject, BlocConstructor, ExtractState, StateContainer } from "@blac/core";
import { RefObject } from "react";
//#region src/types.d.ts
/**
* Configuration options for the useBloc hook
* React Integration
*
* @template TBloc - The StateContainer type
*
* @example
* ```tsx
* const [state, bloc] = useBloc(CounterBloc, {
* staticProps: { initialCount: 0 },
* dependencies: (state) => [state.count],
* onMount: (bloc) => console.log('Mounted'),
* });
* ```
* Clean integration between React and StateContainer architecture.
* Constructor-based API with automatic type inference.
* Supports concurrent (useSyncExternalStore) mode for optimal performance.
*/
interface UseBlocOptions<TBloc extends StateContainer<AnyObject>> {
/**
* Static props to pass to the Bloc constructor
* Type should match the constructor's first parameter
*/
staticProps?: AnyObject;
/**
* Custom instance ID for shared blocs
* - For isolated blocs, each useBloc call gets its own instance
* - For shared blocs, the same instanceId will share the same bloc instance
*/
instanceId?: string;
/**
* Manual dependency tracking function
* - When provided, automatic proxy tracking is disabled
* - Component only re-renders when the returned values change (shallow equality)
*
* @param state - Current state of the bloc
* @param bloc - The bloc instance
* @returns Array of values to track
*/
dependencies?: (state: ExtractState<TBloc>, bloc: TBloc) => unknown[];
/**
* Control automatic dependency tracking (default: true)
* - true: Automatically track accessed properties via Proxy
* - false: Disable tracking, all state changes trigger re-render
* - Ignored when `dependencies` option is provided
*
* @default true
*/
autoTrack?: boolean;
/**
* Callback invoked when the component mounts
*
* @param bloc - The bloc instance
*/
onMount?: (bloc: TBloc) => void;
/**
* Callback invoked when the component unmounts
*
* @param bloc - The bloc instance
*/
onUnmount?: (bloc: TBloc) => void;
}
/**
* Return type of the useBloc hook
*
* @template TBloc - The StateContainer type
*
* @returns A tuple containing:
* - [0]: The current state (may be a Proxy for tracking)
* - [1]: The bloc instance with methods
* - [2]: Component ref (internal use only)
*/
type UseBlocReturn<TBloc extends StateContainer<AnyObject>> = [ExtractState<TBloc>, TBloc, RefObject<ComponentRef>];
/**
* @internal
* Component reference object for internal tracking
* Used to persist instance IDs across React Strict Mode remounts
*/
type ComponentRef = {
__blocInstanceId?: string;
__bridge?: any;
};
//#endregion
//#region src/useBloc.d.ts
/**
* Lifecycle: INITIAL MOUNT
* 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn
* 2. useSyncExternalStore calls getSnapshotFn (1st time) - lazy creates tracker, starts tracking, returns proxy
* 3. Component renders - proxy tracks property accesses
* 4. useSyncExternalStore calls getSnapshotFn (2nd time) - captures tracked paths, starts new tracking, returns proxy
* 5. useSyncExternalStore calls subscribeFn - sets up state change listener
*
* Lifecycle: STATE CHANGE
* 1. Bloc state changes
* 2. subscribeFn callback checks hasChanges() - only re-renders if tracked paths changed
* 3. If re-render: getSnapshotFn captures previous paths, starts tracking, returns proxy
*
* Lifecycle: RE-RENDER (parent re-render)
* 1. useMemo returns cached values (same bloc, subscribeFn, getSnapshotFn)
* 2. useSyncExternalStore calls getSnapshotFn - captures paths, starts tracking, returns proxy
*/
declare function useBloc<TBloc extends StateContainer<AnyObject>>(BlocClass: BlocConstructor<TBloc>, options?: UseBlocOptions<TBloc>): UseBlocReturn<TBloc>;
//#endregion
//#region src/useBlocActions.d.ts
/**
* Configuration options for the useBlocActions hook
*
* @template TBloc - The StateContainer type
*/
interface UseBlocActionsOptions<TBloc extends StateContainer<AnyObject>> {
/**
* Static props to pass to the Bloc constructor
* Type should match the constructor's first parameter
*/
staticProps?: AnyObject;
/**
* Custom instance ID for shared blocs
* - For isolated blocs, each useBlocActions call gets its own instance
* - For shared blocs, the same instanceId will share the same bloc instance
*/
instanceId?: string;
/**
* Callback invoked when the component mounts
*
* @param bloc - The bloc instance
*/
onMount?: (bloc: TBloc) => void;
/**
* Callback invoked when the component unmounts
*
* @param bloc - The bloc instance
*/
onUnmount?: (bloc: TBloc) => void;
}
/**
* React hook for accessing bloc instance without state subscription.
* Use this when you only need to call bloc methods/actions without reading state.
*
* Benefits over useBloc:
* - No state subscription overhead
* - No proxy tracking
* - Component never re-renders due to bloc state changes
* - Lighter weight for action-only components
*
* @template TBloc - The StateContainer type
* @param BlocClass - The bloc class constructor
* @param options - Optional configuration
* @returns The bloc instance
*/
declare function useBlocActions<TBloc extends StateContainer<AnyObject>>(BlocClass: BlocConstructor<TBloc>, options?: UseBlocActionsOptions<TBloc>): TBloc;
//#endregion
export { type UseBlocActionsOptions, type UseBlocOptions, type UseBlocReturn, useBloc, useBlocActions };
//# sourceMappingURL=index.d.cts.map
export { useBloc } from './useBloc';
export { useBlocActions } from './useBlocActions';
export type { UseBlocOptions, UseBlocReturn } from './types';
export type { UseBlocActionsOptions } from './useBlocActions';
//# sourceMappingURL=index.d.ts.map

@@ -1,154 +0,12 @@

import { RefObject } from "react";
import { AnyObject, BlocConstructor, ExtractState, StateContainer } from "@blac/core";
//#region src/types.d.ts
/**
* Configuration options for the useBloc hook
* React Integration
*
* @template TBloc - The StateContainer type
*
* @example
* ```tsx
* const [state, bloc] = useBloc(CounterBloc, {
* staticProps: { initialCount: 0 },
* dependencies: (state) => [state.count],
* onMount: (bloc) => console.log('Mounted'),
* });
* ```
* Clean integration between React and StateContainer architecture.
* Constructor-based API with automatic type inference.
* Supports concurrent (useSyncExternalStore) mode for optimal performance.
*/
interface UseBlocOptions<TBloc extends StateContainer<AnyObject>> {
/**
* Static props to pass to the Bloc constructor
* Type should match the constructor's first parameter
*/
staticProps?: AnyObject;
/**
* Custom instance ID for shared blocs
* - For isolated blocs, each useBloc call gets its own instance
* - For shared blocs, the same instanceId will share the same bloc instance
*/
instanceId?: string;
/**
* Manual dependency tracking function
* - When provided, automatic proxy tracking is disabled
* - Component only re-renders when the returned values change (shallow equality)
*
* @param state - Current state of the bloc
* @param bloc - The bloc instance
* @returns Array of values to track
*/
dependencies?: (state: ExtractState<TBloc>, bloc: TBloc) => unknown[];
/**
* Control automatic dependency tracking (default: true)
* - true: Automatically track accessed properties via Proxy
* - false: Disable tracking, all state changes trigger re-render
* - Ignored when `dependencies` option is provided
*
* @default true
*/
autoTrack?: boolean;
/**
* Callback invoked when the component mounts
*
* @param bloc - The bloc instance
*/
onMount?: (bloc: TBloc) => void;
/**
* Callback invoked when the component unmounts
*
* @param bloc - The bloc instance
*/
onUnmount?: (bloc: TBloc) => void;
}
/**
* Return type of the useBloc hook
*
* @template TBloc - The StateContainer type
*
* @returns A tuple containing:
* - [0]: The current state (may be a Proxy for tracking)
* - [1]: The bloc instance with methods
* - [2]: Component ref (internal use only)
*/
type UseBlocReturn<TBloc extends StateContainer<AnyObject>> = [ExtractState<TBloc>, TBloc, RefObject<ComponentRef>];
/**
* @internal
* Component reference object for internal tracking
* Used to persist instance IDs across React Strict Mode remounts
*/
type ComponentRef = {
__blocInstanceId?: string;
__bridge?: any;
};
//#endregion
//#region src/useBloc.d.ts
/**
* Lifecycle: INITIAL MOUNT
* 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn
* 2. useSyncExternalStore calls getSnapshotFn (1st time) - lazy creates tracker, starts tracking, returns proxy
* 3. Component renders - proxy tracks property accesses
* 4. useSyncExternalStore calls getSnapshotFn (2nd time) - captures tracked paths, starts new tracking, returns proxy
* 5. useSyncExternalStore calls subscribeFn - sets up state change listener
*
* Lifecycle: STATE CHANGE
* 1. Bloc state changes
* 2. subscribeFn callback checks hasChanges() - only re-renders if tracked paths changed
* 3. If re-render: getSnapshotFn captures previous paths, starts tracking, returns proxy
*
* Lifecycle: RE-RENDER (parent re-render)
* 1. useMemo returns cached values (same bloc, subscribeFn, getSnapshotFn)
* 2. useSyncExternalStore calls getSnapshotFn - captures paths, starts tracking, returns proxy
*/
declare function useBloc<TBloc extends StateContainer<AnyObject>>(BlocClass: BlocConstructor<TBloc>, options?: UseBlocOptions<TBloc>): UseBlocReturn<TBloc>;
//#endregion
//#region src/useBlocActions.d.ts
/**
* Configuration options for the useBlocActions hook
*
* @template TBloc - The StateContainer type
*/
interface UseBlocActionsOptions<TBloc extends StateContainer<AnyObject>> {
/**
* Static props to pass to the Bloc constructor
* Type should match the constructor's first parameter
*/
staticProps?: AnyObject;
/**
* Custom instance ID for shared blocs
* - For isolated blocs, each useBlocActions call gets its own instance
* - For shared blocs, the same instanceId will share the same bloc instance
*/
instanceId?: string;
/**
* Callback invoked when the component mounts
*
* @param bloc - The bloc instance
*/
onMount?: (bloc: TBloc) => void;
/**
* Callback invoked when the component unmounts
*
* @param bloc - The bloc instance
*/
onUnmount?: (bloc: TBloc) => void;
}
/**
* React hook for accessing bloc instance without state subscription.
* Use this when you only need to call bloc methods/actions without reading state.
*
* Benefits over useBloc:
* - No state subscription overhead
* - No proxy tracking
* - Component never re-renders due to bloc state changes
* - Lighter weight for action-only components
*
* @template TBloc - The StateContainer type
* @param BlocClass - The bloc class constructor
* @param options - Optional configuration
* @returns The bloc instance
*/
declare function useBlocActions<TBloc extends StateContainer<AnyObject>>(BlocClass: BlocConstructor<TBloc>, options?: UseBlocActionsOptions<TBloc>): TBloc;
//#endregion
export { type UseBlocActionsOptions, type UseBlocOptions, type UseBlocReturn, useBloc, useBlocActions };
export { useBloc } from './useBloc';
export { useBlocActions } from './useBlocActions';
export type { UseBlocOptions, UseBlocReturn } from './types';
export type { UseBlocActionsOptions } from './useBlocActions';
//# sourceMappingURL=index.d.ts.map

@@ -1,170 +0,31 @@

import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
import { captureTrackedPaths, createProxy, createTrackerState, hasChanges, hasTrackedData, shallowEqual, startTracking } from "@blac/core";
import { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
import { ExternalDependencyManager, createAutoTrackSnapshot, createAutoTrackSubscribe, createManualDepsSnapshot, createManualDepsSubscribe, createNoTrackSnapshot, createNoTrackSubscribe, disableGetterTracking, generateIsolatedKey, initAutoTrackState, initManualDepsState, initNoTrackState, isIsolatedClass } from "@blac/core";
//#region src/useBloc.ts
//#region src/utils/instance-keys.ts
/**
* Cache for property descriptors to avoid repeated prototype chain walks
* Maps from object constructor to a map of property name to descriptor
* Instance key generation utilities for React integration
*/
const descriptorCache = /* @__PURE__ */ new WeakMap();
/**
* Cache for proxied blocs to ensure same proxy is returned for same bloc instance
* This is important for identity checks (e.g., bloc1 === bloc2) across components
* using the same shared bloc instance.
*/
const blocProxyCache = /* @__PURE__ */ new WeakMap();
/**
* Map to store the currently active tracker during render.
* This allows the cached proxy to know which component's tracker to use.
* Set before render, cleared after render.
*/
const activeTrackerMap = /* @__PURE__ */ new WeakMap();
/**
* Get property descriptor for a given property, with caching
* Generate an instance key for a bloc
*
* @remarks
* Walks up the prototype chain once per class to find the descriptor,
* then caches the result for performance. This is critical because
* we need to distinguish getters from methods and properties.
* Logic:
* - If user provides instanceId, use it (convert number to string)
* - If isolated, generate or reuse a unique key for this component
* - Otherwise, return undefined (use default key)
*
* @param obj - The object to get the descriptor from
* @param prop - The property name or symbol
* @returns The property descriptor if found, undefined otherwise
* @param componentRef - React component reference (persists across remounts)
* @param isIsolated - Whether the bloc is isolated
* @param providedId - User-provided instance ID (from options)
* @returns Instance key string or undefined for default
*/
function getDescriptor(obj, prop) {
const constructor = obj.constructor;
let constructorCache = descriptorCache.get(constructor);
if (constructorCache?.has(prop)) return constructorCache.get(prop);
let current = obj;
let descriptor;
while (current && current !== Object.prototype) {
descriptor = Object.getOwnPropertyDescriptor(current, prop);
if (descriptor) break;
current = Object.getPrototypeOf(current);
}
if (!constructorCache) {
constructorCache = /* @__PURE__ */ new Map();
descriptorCache.set(constructor, constructorCache);
}
constructorCache.set(prop, descriptor);
return descriptor;
}
/**
* Check if a property is a getter (has a getter descriptor)
*
* @param obj - The object to check
* @param prop - The property name or symbol
* @returns True if the property is a getter, false otherwise
*/
function isGetter(obj, prop) {
return getDescriptor(obj, prop)?.get !== void 0;
}
/**
* Create a new getter tracking state
*
* @returns A new GetterTrackingState initialized with empty collections
*/
function createGetterTracker() {
return {
trackedValues: /* @__PURE__ */ new Map(),
currentlyAccessing: /* @__PURE__ */ new Set(),
trackedGetters: /* @__PURE__ */ new Set(),
isTracking: false,
renderCache: /* @__PURE__ */ new Map(),
cacheValid: false
};
}
/**
* Create a proxy that intercepts getter access on a bloc instance
*
* @remarks
* This proxy wraps the bloc instance to track which getters are accessed
* during component render. When tracking is enabled (during render phase),
* it records accessed getters and stores their computed values for later
* comparison.
*
* IMPORTANT: This function caches proxies per bloc instance. Multiple components
* sharing the same bloc will get the same proxy instance. Each component sets
* its tracker in activeTrackerMap before render, and the proxy looks it up.
*
* @param bloc - The bloc instance to wrap
* @returns A proxied bloc that tracks getter access
*/
function createBlocProxy(bloc) {
const cached = blocProxyCache.get(bloc);
if (cached) return cached;
const proxy = new Proxy(bloc, { get(target, prop, receiver) {
const tracker = activeTrackerMap.get(target);
if (tracker?.isTracking && isGetter(target, prop)) {
tracker.currentlyAccessing.add(prop);
if (tracker.cacheValid && tracker.renderCache.has(prop)) {
const cachedValue = tracker.renderCache.get(prop);
tracker.trackedValues.set(prop, cachedValue);
return cachedValue;
}
const value = getDescriptor(target, prop).get.call(target);
tracker.trackedValues.set(prop, value);
return value;
}
return Reflect.get(target, prop, receiver);
} });
blocProxyCache.set(bloc, proxy);
return proxy;
}
/**
* Check if any tracked getters have changed values
*
* @remarks
* Re-computes all getters that were accessed during the last render and
* compares their new values with stored values using Object.is() (reference
* equality).
*
* OPTIMIZATION: Render cache population
* - Computes ALL tracked getters (no early exit) to populate the render cache
* - This ensures each getter is computed only once per render cycle
* - If we're going to re-render, getters accessed during render will use cached values
* - Cache is populated even if no changes detected (useful for parent-triggered re-renders)
* - Trade-off: Give up early exit to ensure full cache population
*
* Error handling: If a getter throws during re-computation, we log a warning,
* stop tracking that specific getter, and treat it as "changed" to trigger
* a re-render. This prevents the tracking system from breaking while still
* allowing React's error boundary to handle the error on the next render.
*
* @param bloc - The bloc instance
* @param tracker - The getter tracking state
* @returns True if any tracked getter value changed, false otherwise
*/
function hasGetterChanges(bloc, tracker) {
if (!tracker || tracker.trackedGetters.size === 0) return false;
tracker.renderCache.clear();
let hasAnyChange = false;
for (const prop of tracker.trackedGetters) try {
const descriptor = getDescriptor(bloc, prop);
if (!descriptor?.get) continue;
const newValue = descriptor.get.call(bloc);
const oldValue = tracker.trackedValues.get(prop);
tracker.renderCache.set(prop, newValue);
tracker.trackedValues.set(prop, newValue);
if (!Object.is(newValue, oldValue)) hasAnyChange = true;
} catch (error) {
console.warn(`[useBloc] Getter "${String(prop)}" threw error during change detection. Stopping tracking for this getter.`, error);
tracker.trackedGetters.delete(prop);
tracker.trackedValues.delete(prop);
tracker.cacheValid = false;
return true;
}
tracker.cacheValid = true;
return hasAnyChange;
}
/**
* Generates instance ID for isolated blocs
*/
function generateInstanceId$1(componentRef, isIsolated, providedId) {
if (providedId) return providedId;
function generateInstanceKey(componentRef, isIsolated, providedId) {
if (providedId !== void 0) return typeof providedId === "number" ? String(providedId) : providedId;
if (isIsolated) {
if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = `isolated-${Math.random().toString(36).slice(2, 11)}`;
if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = generateIsolatedKey();
return componentRef.__blocInstanceId;
}
}
//#endregion
//#region src/useBloc.ts
function determineTrackingMode(options) {

@@ -177,71 +38,2 @@ return {

/**
* Factory: Creates subscribe function for automatic proxy tracking mode
*/
function createAutoTrackSubscribe(instance, hookState) {
return (callback) => {
return instance.subscribe(() => {
const tracker = hookState.tracker || (hookState.tracker = createTrackerState());
let stateChanged = hasChanges(tracker, instance.state);
if (tracker.pathCache.size === 0 && hookState.getterTracker && hookState.getterTracker.trackedGetters.size > 0) stateChanged = false;
if (stateChanged) {
callback();
return;
}
if (hasGetterChanges(instance, hookState.getterTracker)) callback();
});
};
}
/**
* Factory: Creates subscribe function for manual dependencies mode
*/
function createManualDepsSubscribe(instance, hookState, options) {
return (callback) => {
return instance.subscribe(() => {
const newDeps = options.dependencies(instance.state, instance);
if (!hookState.manualDepsCache || !shallowEqual(hookState.manualDepsCache, newDeps)) {
hookState.manualDepsCache = newDeps;
callback();
}
});
};
}
/**
* Factory: Creates subscribe function for no-tracking mode
*/
function createNoTrackSubscribe(instance) {
return (callback) => instance.subscribe(callback);
}
/**
* Factory: Creates getSnapshot function for automatic proxy tracking mode
*/
function createAutoTrackSnapshot(instance, hookState) {
return () => {
const tracker = hookState.tracker || (hookState.tracker = createTrackerState());
if (hasTrackedData(tracker)) captureTrackedPaths(tracker, instance.state);
if (hookState.getterTracker) {
if (hookState.getterTracker.currentlyAccessing.size > 0) hookState.getterTracker.trackedGetters = new Set(hookState.getterTracker.currentlyAccessing);
hookState.getterTracker.currentlyAccessing.clear();
hookState.getterTracker.isTracking = true;
activeTrackerMap.set(instance, hookState.getterTracker);
}
startTracking(tracker);
return createProxy(tracker, instance.state);
};
}
/**
* Factory: Creates getSnapshot function for manual dependencies mode
*/
function createManualDepsSnapshot(instance, hookState, options) {
return () => {
hookState.manualDepsCache = options.dependencies(instance.state, instance);
return instance.state;
};
}
/**
* Factory: Creates getSnapshot function for no-tracking mode
*/
function createNoTrackSnapshot(instance) {
return () => instance.state;
}
/**
* Lifecycle: INITIAL MOUNT

@@ -265,46 +57,39 @@ * 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn

const componentRef = useRef({});
const [bloc, subscribe, getSnapshot, instanceKey, hookState, rawInstance] = useMemo(() => {
const isIsolated = BlocClass.isolated === true;
const Constructor = BlocClass;
const instanceId = generateInstanceId$1(componentRef.current, isIsolated, options?.instanceId);
const instance = Constructor.getOrCreate(instanceId, options?.staticProps);
const Constructor = BlocClass;
const isIsolated = isIsolatedClass(BlocClass);
const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => {
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
const instance = BlocClass.resolve(instanceKey$1, options?.staticProps);
const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
const hookState$1 = {
tracker: null,
manualDepsCache: null,
getterTracker: null,
proxiedBloc: null
};
let subscribeFn;
let getSnapshotFn;
if (useManualDeps) {
subscribeFn = createManualDepsSubscribe(instance, hookState$1, options);
getSnapshotFn = createManualDepsSnapshot(instance, hookState$1, options);
hookState$1.proxiedBloc = instance;
let adapterState$1;
if (useManualDeps && options?.dependencies) {
adapterState$1 = initManualDepsState(instance);
subscribeFn = createManualDepsSubscribe(instance, adapterState$1, { dependencies: options.dependencies });
getSnapshotFn = createManualDepsSnapshot(instance, adapterState$1, { dependencies: options.dependencies });
} else if (!autoTrackEnabled) {
adapterState$1 = initNoTrackState(instance);
subscribeFn = createNoTrackSubscribe(instance);
getSnapshotFn = createNoTrackSnapshot(instance);
hookState$1.proxiedBloc = instance;
} else {
subscribeFn = createAutoTrackSubscribe(instance, hookState$1);
getSnapshotFn = createAutoTrackSnapshot(instance, hookState$1);
hookState$1.getterTracker = createGetterTracker();
hookState$1.proxiedBloc = createBlocProxy(instance);
adapterState$1 = initAutoTrackState(instance);
subscribeFn = createAutoTrackSubscribe(instance, adapterState$1);
getSnapshotFn = createAutoTrackSnapshot(instance, adapterState$1);
}
return [
hookState$1.proxiedBloc,
adapterState$1.proxiedBloc,
subscribeFn,
getSnapshotFn,
instanceId,
hookState$1,
instanceKey$1,
adapterState$1,
instance
];
}, [BlocClass]);
}, [BlocClass, options?.instanceId]);
const state = useSyncExternalStore(subscribe, getSnapshot);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const externalDepsManager = useRef(new ExternalDependencyManager());
useEffect(() => {
if (hookState.getterTracker) {
hookState.getterTracker.isTracking = false;
hookState.getterTracker.cacheValid = false;
activeTrackerMap.delete(rawInstance);
}
disableGetterTracking(adapterState, rawInstance);
externalDepsManager.current.updateSubscriptions(adapterState.getterTracker, rawInstance, forceUpdate);
});

@@ -314,5 +99,6 @@ useEffect(() => {

return () => {
externalDepsManager.current.cleanup();
if (options?.onUnmount) options.onUnmount(bloc);
BlocClass.release(instanceKey);
if (BlocClass.isolated === true && !rawInstance.isDisposed) rawInstance.dispose();
Constructor.release(instanceKey);
if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
};

@@ -329,34 +115,9 @@ }, []);

//#region src/useBlocActions.ts
/**
* Generates instance ID for isolated blocs
*/
function generateInstanceId(componentRef, isIsolated, providedId) {
if (providedId) return providedId;
if (isIsolated) {
if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = `isolated-${Math.random().toString(36).slice(2, 11)}`;
return componentRef.__blocInstanceId;
}
}
/**
* React hook for accessing bloc instance without state subscription.
* Use this when you only need to call bloc methods/actions without reading state.
*
* Benefits over useBloc:
* - No state subscription overhead
* - No proxy tracking
* - Component never re-renders due to bloc state changes
* - Lighter weight for action-only components
*
* @template TBloc - The StateContainer type
* @param BlocClass - The bloc class constructor
* @param options - Optional configuration
* @returns The bloc instance
*/
function useBlocActions(BlocClass, options) {
const componentRef = useRef({});
const [bloc, instanceKey] = useMemo(() => {
const isIsolated = BlocClass.isolated === true;
const isIsolated = isIsolatedClass(BlocClass);
const Constructor = BlocClass;
const instanceId = generateInstanceId(componentRef.current, isIsolated, options?.instanceId);
return [Constructor.getOrCreate(instanceId, options?.staticProps), instanceId];
const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
return [options?.staticProps ? Constructor.resolve(instanceKey$1, options.staticProps) : Constructor.resolve(instanceKey$1), instanceKey$1];
}, [BlocClass]);

@@ -368,3 +129,3 @@ useEffect(() => {

BlocClass.release(instanceKey);
if (BlocClass.isolated === true && !bloc.isDisposed) bloc.dispose();
if (isIsolatedClass(BlocClass) && !bloc.isDisposed) bloc.dispose();
};

@@ -371,0 +132,0 @@ }, []);

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","names":["descriptor: PropertyDescriptor | undefined","generateInstanceId","hookState: HookState<TBloc>","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<TBloc>","hookState"],"sources":["../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * useBloc - hook for BlaC state management in React with automatic proxy tracking\n *\n * @example\n * ```tsx\n * // Basic usage - automatic tracking of accessed properties\n * function Counter() {\n * const [state, bloc] = useBloc(CounterBloc);\n * return (\n * <div>\n * <p>Count: {state.count}</p> // Only re-renders when count changes\n * <button onClick={bloc.increment}>+</button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useMemo, useSyncExternalStore, useEffect, useRef } from 'react';\nimport {\n type AnyObject,\n type BlocConstructor,\n StateContainer,\n type ExtractState,\n // Import tracking utilities from core\n type TrackerState,\n createTrackerState,\n startTracking,\n createProxy,\n captureTrackedPaths,\n hasChanges,\n hasTrackedData,\n shallowEqual,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\n\n/**\n * StateContainer constructor with required static methods\n */\ntype StateContainerConstructor<TBloc extends StateContainer<any>> =\n BlocConstructor<TBloc> & {\n getOrCreate(instanceKey?: string, ...args: any[]): TBloc;\n release(instanceKey?: string): void;\n };\n\n/**\n * State for tracking getter access and values during render\n *\n * @remarks\n * This tracks which getters are accessed during component render and stores\n * their computed values for comparison on subsequent state changes.\n * Only getters (properties with a getter descriptor) are tracked automatically.\n *\n * Similar to state tracking, we use two sets:\n * - `currentlyAccessing`: Temporary set for getters accessed during current render\n * - `trackedGetters`: Committed set of getters from last completed render\n *\n * PERFORMANCE: Render cache optimization\n * - When checking if we should re-render (hasGetterChanges), we compute all getters\n * - Store these computed values in `renderCache` for the upcoming render\n * - During render, if we access a cached getter, use the cached value instead of recomputing\n * - This ensures each getter is computed at most once per render cycle\n * - Cache is invalidated after render completes or when state changes again\n */\ninterface GetterTrackingState {\n /** Map of getter names to their last computed values (for comparison) */\n trackedValues: Map<string | symbol, unknown>;\n /** Temporary set of getters being accessed during current render */\n currentlyAccessing: Set<string | symbol>;\n /** Committed set of getters from last completed render (used for change detection) */\n trackedGetters: Set<string | symbol>;\n /** Flag to enable/disable tracking (only enabled during render phase) */\n isTracking: boolean;\n /** Cache of getter values computed during hasGetterChanges (valid for current render cycle) */\n renderCache: Map<string | symbol, unknown>;\n /** Flag indicating render cache is valid and can be used */\n cacheValid: boolean;\n}\n\n/**\n * Cache for property descriptors to avoid repeated prototype chain walks\n * Maps from object constructor to a map of property name to descriptor\n */\nconst descriptorCache = new WeakMap<\n Function,\n Map<string | symbol, PropertyDescriptor | undefined>\n>();\n\n/**\n * Cache for proxied blocs to ensure same proxy is returned for same bloc instance\n * This is important for identity checks (e.g., bloc1 === bloc2) across components\n * using the same shared bloc instance.\n */\nconst blocProxyCache = new WeakMap<StateContainer<any>, any>();\n\n/**\n * Map to store the currently active tracker during render.\n * This allows the cached proxy to know which component's tracker to use.\n * Set before render, cleared after render.\n */\nconst activeTrackerMap = new WeakMap<\n StateContainer<any>,\n GetterTrackingState\n>();\n\n/**\n * Get property descriptor for a given property, with caching\n *\n * @remarks\n * Walks up the prototype chain once per class to find the descriptor,\n * then caches the result for performance. This is critical because\n * we need to distinguish getters from methods and properties.\n *\n * @param obj - The object to get the descriptor from\n * @param prop - The property name or symbol\n * @returns The property descriptor if found, undefined otherwise\n */\nfunction getDescriptor(\n obj: any,\n prop: string | symbol,\n): PropertyDescriptor | undefined {\n const constructor = obj.constructor;\n\n // Try to get from cache\n let constructorCache = descriptorCache.get(constructor);\n if (constructorCache?.has(prop)) {\n return constructorCache.get(prop);\n }\n\n // Walk prototype chain to find descriptor\n let current = obj;\n let descriptor: PropertyDescriptor | undefined;\n\n while (current && current !== Object.prototype) {\n descriptor = Object.getOwnPropertyDescriptor(current, prop);\n if (descriptor) {\n break;\n }\n current = Object.getPrototypeOf(current);\n }\n\n // Cache the result\n if (!constructorCache) {\n constructorCache = new Map();\n descriptorCache.set(constructor, constructorCache);\n }\n constructorCache.set(prop, descriptor);\n\n return descriptor;\n}\n\n/**\n * Check if a property is a getter (has a getter descriptor)\n *\n * @param obj - The object to check\n * @param prop - The property name or symbol\n * @returns True if the property is a getter, false otherwise\n */\nfunction isGetter(obj: any, prop: string | symbol): boolean {\n const descriptor = getDescriptor(obj, prop);\n return descriptor?.get !== undefined;\n}\n\n/**\n * Create a new getter tracking state\n *\n * @returns A new GetterTrackingState initialized with empty collections\n */\nfunction createGetterTracker(): GetterTrackingState {\n return {\n trackedValues: new Map(),\n currentlyAccessing: new Set(),\n trackedGetters: new Set(),\n isTracking: false,\n renderCache: new Map(),\n cacheValid: false,\n };\n}\n\n/**\n * Create a proxy that intercepts getter access on a bloc instance\n *\n * @remarks\n * This proxy wraps the bloc instance to track which getters are accessed\n * during component render. When tracking is enabled (during render phase),\n * it records accessed getters and stores their computed values for later\n * comparison.\n *\n * IMPORTANT: This function caches proxies per bloc instance. Multiple components\n * sharing the same bloc will get the same proxy instance. Each component sets\n * its tracker in activeTrackerMap before render, and the proxy looks it up.\n *\n * @param bloc - The bloc instance to wrap\n * @returns A proxied bloc that tracks getter access\n */\nfunction createBlocProxy<TBloc extends StateContainer<AnyObject>>(\n bloc: TBloc,\n): TBloc {\n // Check cache first - return existing proxy if available\n const cached = blocProxyCache.get(bloc);\n if (cached) {\n return cached;\n }\n\n const proxy = new Proxy(bloc, {\n get(target, prop, receiver) {\n // Get the active tracker for this bloc (set by getSnapshot)\n const tracker = activeTrackerMap.get(target);\n\n // Only track during render phase (when tracker is active and tracking enabled)\n if (tracker?.isTracking && isGetter(target, prop)) {\n // Record that this getter was accessed during current render\n tracker.currentlyAccessing.add(prop);\n\n // Use cached value if available from previous change detection\n if (tracker.cacheValid && tracker.renderCache.has(prop)) {\n const cachedValue = tracker.renderCache.get(prop);\n // Also store in trackedValues for consistency\n tracker.trackedValues.set(prop, cachedValue);\n return cachedValue;\n }\n\n // Compute getter if no cache available (first access or cache invalidated)\n const descriptor = getDescriptor(target, prop);\n const value = descriptor!.get!.call(target);\n tracker.trackedValues.set(prop, value);\n return value;\n }\n\n // Default behavior for non-getters or when tracking disabled\n return Reflect.get(target, prop, receiver);\n },\n });\n\n blocProxyCache.set(bloc, proxy);\n return proxy;\n}\n\n/**\n * Check if any tracked getters have changed values\n *\n * @remarks\n * Re-computes all getters that were accessed during the last render and\n * compares their new values with stored values using Object.is() (reference\n * equality).\n *\n * OPTIMIZATION: Render cache population\n * - Computes ALL tracked getters (no early exit) to populate the render cache\n * - This ensures each getter is computed only once per render cycle\n * - If we're going to re-render, getters accessed during render will use cached values\n * - Cache is populated even if no changes detected (useful for parent-triggered re-renders)\n * - Trade-off: Give up early exit to ensure full cache population\n *\n * Error handling: If a getter throws during re-computation, we log a warning,\n * stop tracking that specific getter, and treat it as \"changed\" to trigger\n * a re-render. This prevents the tracking system from breaking while still\n * allowing React's error boundary to handle the error on the next render.\n *\n * @param bloc - The bloc instance\n * @param tracker - The getter tracking state\n * @returns True if any tracked getter value changed, false otherwise\n */\nfunction hasGetterChanges<TBloc extends StateContainer<AnyObject>>(\n bloc: TBloc,\n tracker: GetterTrackingState | null,\n): boolean {\n // Early return if no tracker or no getters tracked\n if (!tracker || tracker.trackedGetters.size === 0) {\n return false;\n }\n\n // Clear previous render cache\n tracker.renderCache.clear();\n\n let hasAnyChange = false;\n\n // Compute all getters to populate render cache (no early exit)\n for (const prop of tracker.trackedGetters) {\n try {\n const descriptor = getDescriptor(bloc, prop);\n if (!descriptor?.get) {\n // Getter no longer exists (shouldn't happen, but be defensive)\n continue;\n }\n\n const newValue = descriptor.get.call(bloc);\n const oldValue = tracker.trackedValues.get(prop);\n\n // Store in render cache for upcoming render (even if unchanged)\n tracker.renderCache.set(prop, newValue);\n\n // Update tracked values for next comparison\n tracker.trackedValues.set(prop, newValue);\n\n // Use Object.is for reference equality comparison\n if (!Object.is(newValue, oldValue)) {\n hasAnyChange = true;\n // Don't return early - continue computing and caching remaining getters\n }\n } catch (error) {\n // Getter threw an error during comparison\n console.warn(\n `[useBloc] Getter \"${String(prop)}\" threw error during change detection. Stopping tracking for this getter.`,\n error,\n );\n\n // Stop tracking this getter\n tracker.trackedGetters.delete(prop);\n tracker.trackedValues.delete(prop);\n\n // Treat as \"changed\" to trigger re-render\n // Still return early on error to avoid cascading failures\n tracker.cacheValid = false; // Invalidate cache due to error\n return true;\n }\n }\n\n // Mark cache as valid for the upcoming render\n tracker.cacheValid = true;\n\n return hasAnyChange;\n}\n\n/**\n * Generates instance ID for isolated blocs\n */\nfunction generateInstanceId(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string,\n): string | undefined {\n if (providedId) return providedId;\n\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = `isolated-${Math.random().toString(36).slice(2, 11)}`;\n }\n return componentRef.__blocInstanceId;\n }\n\n return undefined;\n}\n\n/**\n * Determines tracking mode from options\n */\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainer<AnyObject>>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled: options?.autoTrack !== false,\n };\n}\n\n/**\n * Internal state for subscription and snapshot functions\n */\ninterface HookState<TBloc extends StateContainer<AnyObject>> {\n /** State property tracker (existing) */\n tracker: TrackerState<ExtractState<TBloc>> | null;\n /** Manual dependencies cache (existing) */\n manualDepsCache: unknown[] | null;\n /** Getter tracking state (new) */\n getterTracker: GetterTrackingState | null;\n /** Cached proxied bloc instance (new) */\n proxiedBloc: TBloc | null;\n}\n\n/**\n * Factory: Creates subscribe function for automatic proxy tracking mode\n */\nfunction createAutoTrackSubscribe<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n hookState: HookState<TBloc>,\n): (callback: () => void) => () => void {\n return (callback: () => void) => {\n return instance.subscribe(() => {\n const tracker =\n hookState.tracker ||\n (hookState.tracker = createTrackerState<ExtractState<TBloc>>());\n\n let stateChanged = hasChanges(tracker, instance.state);\n\n // Special case: if NO state properties were tracked (pathCache.size === 0)\n // but getters WERE tracked, then don't treat \"no state tracking\" as \"track everything\".\n // Only rely on getter changes in this case.\n if (\n tracker.pathCache.size === 0 &&\n hookState.getterTracker &&\n hookState.getterTracker.trackedGetters.size > 0\n ) {\n stateChanged = false; // Override - only getters are relevant\n }\n\n // EARLY EXIT: If state already changed, skip getter checks entirely\n if (stateChanged) {\n callback();\n return;\n }\n\n // Only check getters if state didn't change\n const getterChanged = hasGetterChanges(instance, hookState.getterTracker);\n\n if (getterChanged) {\n callback();\n }\n });\n };\n}\n\n/**\n * Factory: Creates subscribe function for manual dependencies mode\n */\nfunction createManualDepsSubscribe<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n hookState: HookState<TBloc>,\n options: UseBlocOptions<TBloc>,\n): (callback: () => void) => () => void {\n return (callback: () => void) => {\n return instance.subscribe(() => {\n const newDeps = options.dependencies!(instance.state, instance);\n if (\n !hookState.manualDepsCache ||\n !shallowEqual(hookState.manualDepsCache, newDeps)\n ) {\n hookState.manualDepsCache = newDeps;\n callback();\n }\n });\n };\n}\n\n/**\n * Factory: Creates subscribe function for no-tracking mode\n */\nfunction createNoTrackSubscribe<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n): (callback: () => void) => () => void {\n return (callback: () => void) => instance.subscribe(callback);\n}\n\n/**\n * Factory: Creates getSnapshot function for automatic proxy tracking mode\n */\nfunction createAutoTrackSnapshot<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n hookState: HookState<TBloc>,\n): () => ExtractState<TBloc> {\n return () => {\n const tracker =\n hookState.tracker ||\n (hookState.tracker = createTrackerState<ExtractState<TBloc>>());\n\n if (hasTrackedData(tracker)) {\n captureTrackedPaths(tracker, instance.state);\n }\n\n // Enable getter tracking during render and set as active tracker\n if (hookState.getterTracker) {\n // Capture getters from previous render (commit currentlyAccessing to trackedGetters)\n if (hookState.getterTracker.currentlyAccessing.size > 0) {\n hookState.getterTracker.trackedGetters = new Set(\n hookState.getterTracker.currentlyAccessing,\n );\n }\n\n // Clear and enable tracking for this render\n hookState.getterTracker.currentlyAccessing.clear();\n hookState.getterTracker.isTracking = true;\n\n // Set this component's tracker as the active one for this bloc\n activeTrackerMap.set(instance, hookState.getterTracker);\n }\n\n startTracking(tracker);\n return createProxy(tracker, instance.state);\n };\n}\n\n/**\n * Factory: Creates getSnapshot function for manual dependencies mode\n */\nfunction createManualDepsSnapshot<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n hookState: HookState<TBloc>,\n options: UseBlocOptions<TBloc>,\n): () => ExtractState<TBloc> {\n return () => {\n hookState.manualDepsCache = options.dependencies!(instance.state, instance);\n return instance.state;\n };\n}\n\n/**\n * Factory: Creates getSnapshot function for no-tracking mode\n */\nfunction createNoTrackSnapshot<TBloc extends StateContainer<AnyObject>>(\n instance: TBloc,\n): () => ExtractState<TBloc> {\n return () => instance.state;\n}\n\n/**\n * Lifecycle: INITIAL MOUNT\n * 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn\n * 2. useSyncExternalStore calls getSnapshotFn (1st time) - lazy creates tracker, starts tracking, returns proxy\n * 3. Component renders - proxy tracks property accesses\n * 4. useSyncExternalStore calls getSnapshotFn (2nd time) - captures tracked paths, starts new tracking, returns proxy\n * 5. useSyncExternalStore calls subscribeFn - sets up state change listener\n *\n * Lifecycle: STATE CHANGE\n * 1. Bloc state changes\n * 2. subscribeFn callback checks hasChanges() - only re-renders if tracked paths changed\n * 3. If re-render: getSnapshotFn captures previous paths, starts tracking, returns proxy\n *\n * Lifecycle: RE-RENDER (parent re-render)\n * 1. useMemo returns cached values (same bloc, subscribeFn, getSnapshotFn)\n * 2. useSyncExternalStore calls getSnapshotFn - captures paths, starts tracking, returns proxy\n */\nexport function useBloc<TBloc extends StateContainer<AnyObject>>(\n BlocClass: BlocConstructor<TBloc>,\n options?: UseBlocOptions<TBloc>,\n): UseBlocReturn<TBloc> {\n // Component reference that persists across React Strict Mode remounts\n const componentRef = useRef<ComponentRef>({});\n\n const [bloc, subscribe, getSnapshot, instanceKey, hookState, rawInstance] =\n useMemo(() => {\n const isIsolated =\n (BlocClass as { isolated?: boolean }).isolated === true;\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n\n // Generate instance key\n const instanceId = generateInstanceId(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n // Get or create bloc instance\n const instance = Constructor.getOrCreate(\n instanceId,\n options?.staticProps,\n );\n\n // Determine tracking mode\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n // Mutable state shared between subscribe and getSnapshot\n const hookState: HookState<TBloc> = {\n tracker: null,\n manualDepsCache: null,\n getterTracker: null,\n proxiedBloc: null,\n };\n\n // Create subscribe and getSnapshot functions based on tracking mode\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<TBloc>;\n\n if (useManualDeps) {\n // Manual dependencies mode - no automatic tracking\n subscribeFn = createManualDepsSubscribe(instance, hookState, options!);\n getSnapshotFn = createManualDepsSnapshot(instance, hookState, options!);\n hookState.proxiedBloc = instance; // Use raw instance\n } else if (!autoTrackEnabled) {\n // No tracking mode\n subscribeFn = createNoTrackSubscribe(instance);\n getSnapshotFn = createNoTrackSnapshot(instance);\n hookState.proxiedBloc = instance; // Use raw instance\n } else {\n // Auto-tracking mode - enable both state and getter tracking\n subscribeFn = createAutoTrackSubscribe(instance, hookState);\n getSnapshotFn = createAutoTrackSnapshot(instance, hookState);\n\n // Initialize getter tracker and create proxied bloc\n hookState.getterTracker = createGetterTracker();\n hookState.proxiedBloc = createBlocProxy(instance);\n }\n\n return [\n hookState.proxiedBloc!,\n subscribeFn,\n getSnapshotFn,\n instanceId,\n hookState,\n instance,\n ] as const;\n }, [BlocClass]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n // Disable getter tracking after each render and clear active tracker\n // Also invalidate render cache since this render cycle is complete\n useEffect(() => {\n if (hookState.getterTracker) {\n hookState.getterTracker.isTracking = false;\n hookState.getterTracker.cacheValid = false; // Invalidate cache after render\n activeTrackerMap.delete(rawInstance);\n }\n });\n\n // Mount/unmount lifecycle\n useEffect(() => {\n // Call onMount callback if provided\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n // Call onUnmount callback if provided\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n // Release bloc reference\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n Constructor.release(instanceKey);\n\n // For isolated instances, dispose manually since registry doesn't track them\n const isIsolated =\n (BlocClass as { isolated?: boolean }).isolated === true;\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<TBloc>;\n}\n","/**\n * useBlocActions - hook for accessing bloc instance without state subscription\n *\n * @example\n * ```tsx\n * // Use when you only need to call actions, not read state\n * function ActionsOnly() {\n * const bloc = useBlocActions(CounterBloc);\n * return (\n * <div>\n * <button onClick={bloc.increment}>+</button>\n * <button onClick={bloc.decrement}>-</button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useMemo, useEffect, useRef } from 'react';\nimport type { AnyObject, BlocConstructor, StateContainer } from '@blac/core';\nimport type { ComponentRef } from './types';\n\n/**\n * StateContainer constructor with required static methods\n */\ntype StateContainerConstructor<TBloc extends StateContainer<any>> =\n BlocConstructor<TBloc> & {\n getOrCreate(instanceKey?: string, ...args: any[]): TBloc;\n release(instanceKey?: string): void;\n };\n\n/**\n * Configuration options for the useBlocActions hook\n *\n * @template TBloc - The StateContainer type\n */\nexport interface UseBlocActionsOptions<\n TBloc extends StateContainer<AnyObject>,\n> {\n /**\n * Static props to pass to the Bloc constructor\n * Type should match the constructor's first parameter\n */\n staticProps?: AnyObject;\n\n /**\n * Custom instance ID for shared blocs\n * - For isolated blocs, each useBlocActions call gets its own instance\n * - For shared blocs, the same instanceId will share the same bloc instance\n */\n instanceId?: string;\n\n /**\n * Callback invoked when the component mounts\n *\n * @param bloc - The bloc instance\n */\n onMount?: (bloc: TBloc) => void;\n\n /**\n * Callback invoked when the component unmounts\n *\n * @param bloc - The bloc instance\n */\n onUnmount?: (bloc: TBloc) => void;\n}\n\n/**\n * Generates instance ID for isolated blocs\n */\nfunction generateInstanceId(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string,\n): string | undefined {\n if (providedId) return providedId;\n\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = `isolated-${Math.random().toString(36).slice(2, 11)}`;\n }\n return componentRef.__blocInstanceId;\n }\n\n return undefined;\n}\n\n/**\n * React hook for accessing bloc instance without state subscription.\n * Use this when you only need to call bloc methods/actions without reading state.\n *\n * Benefits over useBloc:\n * - No state subscription overhead\n * - No proxy tracking\n * - Component never re-renders due to bloc state changes\n * - Lighter weight for action-only components\n *\n * @template TBloc - The StateContainer type\n * @param BlocClass - The bloc class constructor\n * @param options - Optional configuration\n * @returns The bloc instance\n */\nexport function useBlocActions<TBloc extends StateContainer<AnyObject>>(\n BlocClass: BlocConstructor<TBloc>,\n options?: UseBlocActionsOptions<TBloc>,\n): TBloc {\n // Component reference that persists across React Strict Mode remounts\n const componentRef = useRef<ComponentRef>({});\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = (BlocClass as { isolated?: boolean }).isolated === true;\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n\n // Generate instance key\n const instanceId = generateInstanceId(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n // Get or create bloc instance\n const instance = Constructor.getOrCreate(instanceId, options?.staticProps);\n\n return [instance, instanceId] as const;\n }, [BlocClass]);\n\n // Mount/unmount lifecycle\n useEffect(() => {\n // Call onMount callback if provided\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n // Call onUnmount callback if provided\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n // Release bloc reference\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n Constructor.release(instanceKey);\n\n // For isolated instances, dispose manually since registry doesn't track them\n const isIsolated =\n (BlocClass as { isolated?: boolean }).isolated === true;\n if (isIsolated && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc;\n}\n"],"mappings":"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"}
{"version":3,"file":"index.js","names":["instanceKey","subscribeFn: (callback: () => void) => () => void","getSnapshotFn: () => ExtractState<TBloc>","adapterState: AdapterState<TBloc>","adapterState","instanceKey"],"sources":["../src/utils/instance-keys.ts","../src/useBloc.ts","../src/useBlocActions.ts"],"sourcesContent":["/**\n * Instance key generation utilities for React integration\n */\n\nimport { generateIsolatedKey } from '@blac/core';\nimport type { ComponentRef } from '../types';\n\n/**\n * Generate an instance key for a bloc\n *\n * Logic:\n * - If user provides instanceId, use it (convert number to string)\n * - If isolated, generate or reuse a unique key for this component\n * - Otherwise, return undefined (use default key)\n *\n * @param componentRef - React component reference (persists across remounts)\n * @param isIsolated - Whether the bloc is isolated\n * @param providedId - User-provided instance ID (from options)\n * @returns Instance key string or undefined for default\n */\nexport function generateInstanceKey(\n componentRef: ComponentRef,\n isIsolated: boolean,\n providedId?: string | number,\n): string | undefined {\n // User explicitly provided an ID - use it\n if (providedId !== undefined) {\n return typeof providedId === 'number' ? String(providedId) : providedId;\n }\n\n // Isolated bloc - generate unique key per component\n if (isIsolated) {\n if (!componentRef.__blocInstanceId) {\n componentRef.__blocInstanceId = generateIsolatedKey();\n }\n return componentRef.__blocInstanceId;\n }\n\n // Shared bloc - use default key (undefined)\n return undefined;\n}\n","import {\n useMemo,\n useSyncExternalStore,\n useEffect,\n useRef,\n useReducer,\n} from 'react';\nimport {\n type BlocConstructor,\n StateContainer,\n type ExtractState,\n type AdapterState,\n ExternalDependencyManager,\n createAutoTrackSubscribe,\n createManualDepsSubscribe,\n createNoTrackSubscribe,\n createAutoTrackSnapshot,\n createManualDepsSnapshot,\n createNoTrackSnapshot,\n initAutoTrackState,\n initManualDepsState,\n initNoTrackState,\n disableGetterTracking,\n isIsolatedClass,\n} from '@blac/core';\nimport type { UseBlocOptions, UseBlocReturn, ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\ntype StateContainerConstructor<TBloc extends StateContainer<any>> =\n BlocConstructor<TBloc> & {\n resolve(instanceKey?: string, ...args: any[]): TBloc;\n release(instanceKey?: string): void;\n };\n\ninterface TrackingMode {\n useManualDeps: boolean;\n autoTrackEnabled: boolean;\n}\n\nfunction determineTrackingMode<TBloc extends StateContainer<any>>(\n options?: UseBlocOptions<TBloc>,\n): TrackingMode {\n return {\n useManualDeps: options?.dependencies !== undefined,\n autoTrackEnabled: options?.autoTrack !== false,\n };\n}\n\n/**\n * Lifecycle: INITIAL MOUNT\n * 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn\n * 2. useSyncExternalStore calls getSnapshotFn (1st time) - lazy creates tracker, starts tracking, returns proxy\n * 3. Component renders - proxy tracks property accesses\n * 4. useSyncExternalStore calls getSnapshotFn (2nd time) - captures tracked paths, starts new tracking, returns proxy\n * 5. useSyncExternalStore calls subscribeFn - sets up state change listener\n *\n * Lifecycle: STATE CHANGE\n * 1. Bloc state changes\n * 2. subscribeFn callback checks hasChanges() - only re-renders if tracked paths changed\n * 3. If re-render: getSnapshotFn captures previous paths, starts tracking, returns proxy\n *\n * Lifecycle: RE-RENDER (parent re-render)\n * 1. useMemo returns cached values (same bloc, subscribeFn, getSnapshotFn)\n * 2. useSyncExternalStore calls getSnapshotFn - captures paths, starts tracking, returns proxy\n */\nexport function useBloc<T extends new (...args: any[]) => StateContainer<any>>(\n BlocClass: T & BlocConstructor<InstanceType<T>>,\n options?: UseBlocOptions<InstanceType<T>>,\n): UseBlocReturn<InstanceType<T>> {\n // Component reference that persists across React Strict Mode remounts\n type TBloc = InstanceType<T>;\n const componentRef = useRef<ComponentRef>({});\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n const isIsolated = isIsolatedClass(BlocClass);\n\n const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] =\n useMemo<\n readonly [\n TBloc,\n (callback: () => void) => () => void,\n () => ExtractState<TBloc>,\n string | undefined,\n AdapterState<TBloc>,\n TBloc,\n ]\n >(() => {\n // Generate instance key\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n // Get or create bloc instance with ownership (increments ref count)\n const instance = BlocClass.resolve(instanceKey, options?.staticProps);\n\n // Determine tracking mode\n const { useManualDeps, autoTrackEnabled } =\n determineTrackingMode(options);\n\n // Create subscribe and getSnapshot functions based on tracking mode\n let subscribeFn: (callback: () => void) => () => void;\n let getSnapshotFn: () => ExtractState<TBloc>;\n let adapterState: AdapterState<TBloc>;\n\n if (useManualDeps && options?.dependencies) {\n // Manual dependencies mode - no automatic tracking\n adapterState = initManualDepsState(instance);\n subscribeFn = createManualDepsSubscribe(instance, adapterState, {\n dependencies: options.dependencies,\n });\n getSnapshotFn = createManualDepsSnapshot(instance, adapterState, {\n dependencies: options.dependencies,\n });\n } else if (!autoTrackEnabled) {\n // No tracking mode\n adapterState = initNoTrackState(instance);\n subscribeFn = createNoTrackSubscribe(instance);\n getSnapshotFn = createNoTrackSnapshot(instance);\n } else {\n // Auto-tracking mode - enable both state and getter tracking\n adapterState = initAutoTrackState(instance);\n subscribeFn = createAutoTrackSubscribe(instance, adapterState);\n getSnapshotFn = createAutoTrackSnapshot(instance, adapterState);\n }\n\n return [\n adapterState.proxiedBloc!,\n subscribeFn,\n getSnapshotFn,\n instanceKey,\n adapterState,\n instance,\n ];\n }, [BlocClass, options?.instanceId]);\n\n const state = useSyncExternalStore(subscribe, getSnapshot);\n\n // Force re-render mechanism for external bloc changes\n const [, forceUpdate] = useReducer((x: number) => x + 1, 0);\n\n // External dependency manager (persists across renders)\n const externalDepsManager = useRef(new ExternalDependencyManager());\n\n // Disable getter tracking and manage external bloc subscriptions after each render\n useEffect(() => {\n disableGetterTracking(adapterState, rawInstance);\n externalDepsManager.current.updateSubscriptions(\n adapterState.getterTracker,\n rawInstance,\n forceUpdate,\n );\n }); // Run on every render to pick up new dependencies and disable tracking\n\n // Mount/unmount lifecycle\n useEffect(() => {\n // Call onMount callback if provided\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n // Cleanup in proper order: subscriptions -> callbacks -> disposal\n\n // 1. Clean up external subscriptions FIRST (before bloc is disposed)\n externalDepsManager.current.cleanup();\n\n // 2. Call onUnmount callback if provided\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n // 3. Release bloc reference\n Constructor.release(instanceKey);\n\n // 4. For isolated instances, dispose manually since registry doesn't track them\n if (isIsolated && !rawInstance.isDisposed) {\n rawInstance.dispose();\n }\n };\n }, []);\n\n return [state, bloc, componentRef] as UseBlocReturn<TBloc>;\n}\n","import { useMemo, useEffect, useRef } from 'react';\nimport {\n type BlocConstructor,\n StateContainer,\n isIsolatedClass,\n} from '@blac/core';\nimport type { ComponentRef } from './types';\nimport { generateInstanceKey } from './utils/instance-keys';\n\ntype StateContainerConstructor<TBloc extends StateContainer<any>> =\n BlocConstructor<TBloc> & {\n resolve(instanceKey?: string, ...args: any[]): TBloc;\n release(instanceKey?: string): void;\n };\n\nexport interface UseBlocActionsOptions<TBloc> {\n staticProps?: any;\n instanceId?: string | number;\n onMount?: (bloc: TBloc) => void;\n onUnmount?: (bloc: TBloc) => void;\n}\n\nexport function useBlocActions<\n T extends new (...args: any[]) => StateContainer<any>,\n>(\n BlocClass: T & BlocConstructor<InstanceType<T>>,\n options?: UseBlocActionsOptions<InstanceType<T>>,\n): InstanceType<T> {\n // Component reference that persists across React Strict Mode remounts\n type TBloc = InstanceType<T>;\n const componentRef = useRef<ComponentRef>({});\n\n const [bloc, instanceKey] = useMemo(() => {\n const isIsolated = isIsolatedClass(BlocClass);\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n\n // Generate instance key\n const instanceKey = generateInstanceKey(\n componentRef.current,\n isIsolated,\n options?.instanceId,\n );\n\n // Get or create bloc instance with ownership (increments ref count)\n const instance = options?.staticProps\n ? Constructor.resolve(instanceKey, options.staticProps)\n : Constructor.resolve(instanceKey);\n\n return [instance, instanceKey] as const;\n }, [BlocClass]);\n\n // Mount/unmount lifecycle\n useEffect(() => {\n // Call onMount callback if provided\n if (options?.onMount) {\n options.onMount(bloc);\n }\n\n return () => {\n // Call onUnmount callback if provided\n if (options?.onUnmount) {\n options.onUnmount(bloc);\n }\n\n // Release bloc reference\n const Constructor = BlocClass as StateContainerConstructor<TBloc>;\n Constructor.release(instanceKey);\n\n // For isolated instances, dispose manually since registry doesn't track them\n if (isIsolatedClass(BlocClass) && !bloc.isDisposed) {\n bloc.dispose();\n }\n };\n }, []);\n\n return bloc;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,oBACd,cACA,YACA,YACoB;AAEpB,KAAI,eAAe,OACjB,QAAO,OAAO,eAAe,WAAW,OAAO,WAAW,GAAG;AAI/D,KAAI,YAAY;AACd,MAAI,CAAC,aAAa,iBAChB,cAAa,mBAAmB,qBAAqB;AAEvD,SAAO,aAAa;;;;;;ACIxB,SAAS,sBACP,SACc;AACd,QAAO;EACL,eAAe,SAAS,iBAAiB;EACzC,kBAAkB,SAAS,cAAc;EAC1C;;;;;;;;;;;;;;;;;;;AAoBH,SAAgB,QACd,WACA,SACgC;CAGhC,MAAM,eAAe,OAAqB,EAAE,CAAC;CAC7C,MAAM,cAAc;CACpB,MAAM,aAAa,gBAAgB,UAAU;CAE7C,MAAM,CAAC,MAAM,WAAW,aAAa,aAAa,cAAc,eAC9D,cASQ;EAEN,MAAMA,gBAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;EAGD,MAAM,WAAW,UAAU,QAAQA,eAAa,SAAS,YAAY;EAGrE,MAAM,EAAE,eAAe,qBACrB,sBAAsB,QAAQ;EAGhC,IAAIC;EACJ,IAAIC;EACJ,IAAIC;AAEJ,MAAI,iBAAiB,SAAS,cAAc;AAE1C,oBAAe,oBAAoB,SAAS;AAC5C,iBAAc,0BAA0B,UAAUC,gBAAc,EAC9D,cAAc,QAAQ,cACvB,CAAC;AACF,mBAAgB,yBAAyB,UAAUA,gBAAc,EAC/D,cAAc,QAAQ,cACvB,CAAC;aACO,CAAC,kBAAkB;AAE5B,oBAAe,iBAAiB,SAAS;AACzC,iBAAc,uBAAuB,SAAS;AAC9C,mBAAgB,sBAAsB,SAAS;SAC1C;AAEL,oBAAe,mBAAmB,SAAS;AAC3C,iBAAc,yBAAyB,UAAUA,eAAa;AAC9D,mBAAgB,wBAAwB,UAAUA,eAAa;;AAGjE,SAAO;GACLA,eAAa;GACb;GACA;GACAJ;GACAI;GACA;GACD;IACA,CAAC,WAAW,SAAS,WAAW,CAAC;CAEtC,MAAM,QAAQ,qBAAqB,WAAW,YAAY;CAG1D,MAAM,GAAG,eAAe,YAAY,MAAc,IAAI,GAAG,EAAE;CAG3D,MAAM,sBAAsB,OAAO,IAAI,2BAA2B,CAAC;AAGnE,iBAAgB;AACd,wBAAsB,cAAc,YAAY;AAChD,sBAAoB,QAAQ,oBAC1B,aAAa,eACb,aACA,YACD;GACD;AAGF,iBAAgB;AAEd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAK;AAGvB,eAAa;AAIX,uBAAoB,QAAQ,SAAS;AAGrC,OAAI,SAAS,UACX,SAAQ,UAAU,KAAK;AAIzB,eAAY,QAAQ,YAAY;AAGhC,OAAI,cAAc,CAAC,YAAY,WAC7B,aAAY,SAAS;;IAGxB,EAAE,CAAC;AAEN,QAAO;EAAC;EAAO;EAAM;EAAa;;;;;AChKpC,SAAgB,eAGd,WACA,SACiB;CAGjB,MAAM,eAAe,OAAqB,EAAE,CAAC;CAE7C,MAAM,CAAC,MAAM,eAAe,cAAc;EACxC,MAAM,aAAa,gBAAgB,UAAU;EAC7C,MAAM,cAAc;EAGpB,MAAMC,gBAAc,oBAClB,aAAa,SACb,YACA,SAAS,WACV;AAOD,SAAO,CAJU,SAAS,cACtB,YAAY,QAAQA,eAAa,QAAQ,YAAY,GACrD,YAAY,QAAQA,cAAY,EAElBA,cAAY;IAC7B,CAAC,UAAU,CAAC;AAGf,iBAAgB;AAEd,MAAI,SAAS,QACX,SAAQ,QAAQ,KAAK;AAGvB,eAAa;AAEX,OAAI,SAAS,UACX,SAAQ,UAAU,KAAK;AAKzB,GADoB,UACR,QAAQ,YAAY;AAGhC,OAAI,gBAAgB,UAAU,IAAI,CAAC,KAAK,WACtC,MAAK,SAAS;;IAGjB,EAAE,CAAC;AAEN,QAAO"}
{
"name": "@blac/react",
"version": "2.0.0-rc.6",
"version": "2.0.0-rc.8",
"license": "MIT",
"author": "Brendan Mullins <jsnanigans@gmail.com>",
"main": "./dist/index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/jsnanigans/blac.git",
"directory": "packages/blac-react"
},
"homepage": "https://github.com/jsnanigans/blac#readme",
"bugs": {
"url": "https://github.com/jsnanigans/blac/issues"
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",

@@ -22,3 +31,7 @@ "types": "./dist/index.d.ts",

"files": [
"dist",
"dist/**/*.js",
"dist/**/*.cjs",
"dist/**/*.d.ts",
"dist/**/*.d.cts",
"dist/**/*.map",
"README.md",

@@ -40,3 +53,3 @@ "LICENSE"

"react": "^18.0.0 || ^19.0.0",
"@blac/core": "2.0.0-rc.6"
"@blac/core": "2.0.0-rc.8"
},

@@ -70,7 +83,7 @@ "peerDependenciesMeta": {

"vitest": "^3.2.4",
"@blac/core": "2.0.0-rc.6"
"@blac/core": "2.0.0-rc.8"
},
"scripts": {
"dev": "tsdown --watch",
"build": "tsdown",
"build": "tsdown && tsc -p tsconfig.build.json && cp dist/index.d.ts dist/index.d.cts",
"clean": "rm -rf dist",

@@ -77,0 +90,0 @@ "format": "prettier --write \".\"",

Sorry, the diff of this file is too big to display