@blac/react 2.0.0-rc.6 → 2.0.0-rc.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,154 +1,12 @@
1
- import { RefObject } from "react";
2
- import { AnyObject, BlocConstructor, ExtractState, StateContainer } from "@blac/core";
3
-
4
- //#region src/types.d.ts
5
-
6
1
  /**
7
- * Configuration options for the useBloc hook
2
+ * React Integration
8
3
  *
9
- * @template TBloc - The StateContainer type
10
- *
11
- * @example
12
- * ```tsx
13
- * const [state, bloc] = useBloc(CounterBloc, {
14
- * staticProps: { initialCount: 0 },
15
- * dependencies: (state) => [state.count],
16
- * onMount: (bloc) => console.log('Mounted'),
17
- * });
18
- * ```
19
- */
20
- interface UseBlocOptions<TBloc extends StateContainer<AnyObject>> {
21
- /**
22
- * Static props to pass to the Bloc constructor
23
- * Type should match the constructor's first parameter
24
- */
25
- staticProps?: AnyObject;
26
- /**
27
- * Custom instance ID for shared blocs
28
- * - For isolated blocs, each useBloc call gets its own instance
29
- * - For shared blocs, the same instanceId will share the same bloc instance
30
- */
31
- instanceId?: string;
32
- /**
33
- * Manual dependency tracking function
34
- * - When provided, automatic proxy tracking is disabled
35
- * - Component only re-renders when the returned values change (shallow equality)
36
- *
37
- * @param state - Current state of the bloc
38
- * @param bloc - The bloc instance
39
- * @returns Array of values to track
40
- */
41
- dependencies?: (state: ExtractState<TBloc>, bloc: TBloc) => unknown[];
42
- /**
43
- * Control automatic dependency tracking (default: true)
44
- * - true: Automatically track accessed properties via Proxy
45
- * - false: Disable tracking, all state changes trigger re-render
46
- * - Ignored when `dependencies` option is provided
47
- *
48
- * @default true
49
- */
50
- autoTrack?: boolean;
51
- /**
52
- * Callback invoked when the component mounts
53
- *
54
- * @param bloc - The bloc instance
55
- */
56
- onMount?: (bloc: TBloc) => void;
57
- /**
58
- * Callback invoked when the component unmounts
59
- *
60
- * @param bloc - The bloc instance
61
- */
62
- onUnmount?: (bloc: TBloc) => void;
63
- }
64
- /**
65
- * Return type of the useBloc hook
66
- *
67
- * @template TBloc - The StateContainer type
68
- *
69
- * @returns A tuple containing:
70
- * - [0]: The current state (may be a Proxy for tracking)
71
- * - [1]: The bloc instance with methods
72
- * - [2]: Component ref (internal use only)
73
- */
74
- type UseBlocReturn<TBloc extends StateContainer<AnyObject>> = [ExtractState<TBloc>, TBloc, RefObject<ComponentRef>];
75
- /**
76
- * @internal
77
- * Component reference object for internal tracking
78
- * Used to persist instance IDs across React Strict Mode remounts
79
- */
80
- type ComponentRef = {
81
- __blocInstanceId?: string;
82
- __bridge?: any;
83
- };
84
- //#endregion
85
- //#region src/useBloc.d.ts
86
- /**
87
- * Lifecycle: INITIAL MOUNT
88
- * 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn
89
- * 2. useSyncExternalStore calls getSnapshotFn (1st time) - lazy creates tracker, starts tracking, returns proxy
90
- * 3. Component renders - proxy tracks property accesses
91
- * 4. useSyncExternalStore calls getSnapshotFn (2nd time) - captures tracked paths, starts new tracking, returns proxy
92
- * 5. useSyncExternalStore calls subscribeFn - sets up state change listener
93
- *
94
- * Lifecycle: STATE CHANGE
95
- * 1. Bloc state changes
96
- * 2. subscribeFn callback checks hasChanges() - only re-renders if tracked paths changed
97
- * 3. If re-render: getSnapshotFn captures previous paths, starts tracking, returns proxy
98
- *
99
- * Lifecycle: RE-RENDER (parent re-render)
100
- * 1. useMemo returns cached values (same bloc, subscribeFn, getSnapshotFn)
101
- * 2. useSyncExternalStore calls getSnapshotFn - captures paths, starts tracking, returns proxy
102
- */
103
- declare function useBloc<TBloc extends StateContainer<AnyObject>>(BlocClass: BlocConstructor<TBloc>, options?: UseBlocOptions<TBloc>): UseBlocReturn<TBloc>;
104
- //#endregion
105
- //#region src/useBlocActions.d.ts
106
- /**
107
- * Configuration options for the useBlocActions hook
108
- *
109
- * @template TBloc - The StateContainer type
110
- */
111
- interface UseBlocActionsOptions<TBloc extends StateContainer<AnyObject>> {
112
- /**
113
- * Static props to pass to the Bloc constructor
114
- * Type should match the constructor's first parameter
115
- */
116
- staticProps?: AnyObject;
117
- /**
118
- * Custom instance ID for shared blocs
119
- * - For isolated blocs, each useBlocActions call gets its own instance
120
- * - For shared blocs, the same instanceId will share the same bloc instance
121
- */
122
- instanceId?: string;
123
- /**
124
- * Callback invoked when the component mounts
125
- *
126
- * @param bloc - The bloc instance
127
- */
128
- onMount?: (bloc: TBloc) => void;
129
- /**
130
- * Callback invoked when the component unmounts
131
- *
132
- * @param bloc - The bloc instance
133
- */
134
- onUnmount?: (bloc: TBloc) => void;
135
- }
136
- /**
137
- * React hook for accessing bloc instance without state subscription.
138
- * Use this when you only need to call bloc methods/actions without reading state.
139
- *
140
- * Benefits over useBloc:
141
- * - No state subscription overhead
142
- * - No proxy tracking
143
- * - Component never re-renders due to bloc state changes
144
- * - Lighter weight for action-only components
145
- *
146
- * @template TBloc - The StateContainer type
147
- * @param BlocClass - The bloc class constructor
148
- * @param options - Optional configuration
149
- * @returns The bloc instance
4
+ * Clean integration between React and StateContainer architecture.
5
+ * Constructor-based API with automatic type inference.
6
+ * Supports concurrent (useSyncExternalStore) mode for optimal performance.
150
7
  */
151
- declare function useBlocActions<TBloc extends StateContainer<AnyObject>>(BlocClass: BlocConstructor<TBloc>, options?: UseBlocActionsOptions<TBloc>): TBloc;
152
- //#endregion
153
- export { type UseBlocActionsOptions, type UseBlocOptions, type UseBlocReturn, useBloc, useBlocActions };
8
+ export { useBloc } from './useBloc';
9
+ export { useBlocActions } from './useBlocActions';
10
+ export type { UseBlocOptions, UseBlocReturn } from './types';
11
+ export type { UseBlocActionsOptions } from './useBlocActions';
154
12
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,172 +1,33 @@
1
- import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
2
- import { captureTrackedPaths, createProxy, createTrackerState, hasChanges, hasTrackedData, shallowEqual, startTracking } from "@blac/core";
1
+ import { useEffect, useMemo, useReducer, useRef, useSyncExternalStore } from "react";
2
+ import { ExternalDependencyManager, createAutoTrackSnapshot, createAutoTrackSubscribe, createManualDepsSnapshot, createManualDepsSubscribe, createNoTrackSnapshot, createNoTrackSubscribe, disableGetterTracking, generateIsolatedKey, initAutoTrackState, initManualDepsState, initNoTrackState, isIsolatedClass } from "@blac/core";
3
3
 
4
- //#region src/useBloc.ts
5
- /**
6
- * Cache for property descriptors to avoid repeated prototype chain walks
7
- * Maps from object constructor to a map of property name to descriptor
8
- */
9
- const descriptorCache = /* @__PURE__ */ new WeakMap();
10
- /**
11
- * Cache for proxied blocs to ensure same proxy is returned for same bloc instance
12
- * This is important for identity checks (e.g., bloc1 === bloc2) across components
13
- * using the same shared bloc instance.
14
- */
15
- const blocProxyCache = /* @__PURE__ */ new WeakMap();
16
- /**
17
- * Map to store the currently active tracker during render.
18
- * This allows the cached proxy to know which component's tracker to use.
19
- * Set before render, cleared after render.
20
- */
21
- const activeTrackerMap = /* @__PURE__ */ new WeakMap();
22
- /**
23
- * Get property descriptor for a given property, with caching
24
- *
25
- * @remarks
26
- * Walks up the prototype chain once per class to find the descriptor,
27
- * then caches the result for performance. This is critical because
28
- * we need to distinguish getters from methods and properties.
29
- *
30
- * @param obj - The object to get the descriptor from
31
- * @param prop - The property name or symbol
32
- * @returns The property descriptor if found, undefined otherwise
33
- */
34
- function getDescriptor(obj, prop) {
35
- const constructor = obj.constructor;
36
- let constructorCache = descriptorCache.get(constructor);
37
- if (constructorCache?.has(prop)) return constructorCache.get(prop);
38
- let current = obj;
39
- let descriptor;
40
- while (current && current !== Object.prototype) {
41
- descriptor = Object.getOwnPropertyDescriptor(current, prop);
42
- if (descriptor) break;
43
- current = Object.getPrototypeOf(current);
44
- }
45
- if (!constructorCache) {
46
- constructorCache = /* @__PURE__ */ new Map();
47
- descriptorCache.set(constructor, constructorCache);
48
- }
49
- constructorCache.set(prop, descriptor);
50
- return descriptor;
51
- }
52
- /**
53
- * Check if a property is a getter (has a getter descriptor)
54
- *
55
- * @param obj - The object to check
56
- * @param prop - The property name or symbol
57
- * @returns True if the property is a getter, false otherwise
58
- */
59
- function isGetter(obj, prop) {
60
- return getDescriptor(obj, prop)?.get !== void 0;
61
- }
62
- /**
63
- * Create a new getter tracking state
64
- *
65
- * @returns A new GetterTrackingState initialized with empty collections
66
- */
67
- function createGetterTracker() {
68
- return {
69
- trackedValues: /* @__PURE__ */ new Map(),
70
- currentlyAccessing: /* @__PURE__ */ new Set(),
71
- trackedGetters: /* @__PURE__ */ new Set(),
72
- isTracking: false,
73
- renderCache: /* @__PURE__ */ new Map(),
74
- cacheValid: false
75
- };
76
- }
4
+ //#region src/utils/instance-keys.ts
77
5
  /**
78
- * Create a proxy that intercepts getter access on a bloc instance
79
- *
80
- * @remarks
81
- * This proxy wraps the bloc instance to track which getters are accessed
82
- * during component render. When tracking is enabled (during render phase),
83
- * it records accessed getters and stores their computed values for later
84
- * comparison.
85
- *
86
- * IMPORTANT: This function caches proxies per bloc instance. Multiple components
87
- * sharing the same bloc will get the same proxy instance. Each component sets
88
- * its tracker in activeTrackerMap before render, and the proxy looks it up.
89
- *
90
- * @param bloc - The bloc instance to wrap
91
- * @returns A proxied bloc that tracks getter access
6
+ * Instance key generation utilities for React integration
92
7
  */
93
- function createBlocProxy(bloc) {
94
- const cached = blocProxyCache.get(bloc);
95
- if (cached) return cached;
96
- const proxy = new Proxy(bloc, { get(target, prop, receiver) {
97
- const tracker = activeTrackerMap.get(target);
98
- if (tracker?.isTracking && isGetter(target, prop)) {
99
- tracker.currentlyAccessing.add(prop);
100
- if (tracker.cacheValid && tracker.renderCache.has(prop)) {
101
- const cachedValue = tracker.renderCache.get(prop);
102
- tracker.trackedValues.set(prop, cachedValue);
103
- return cachedValue;
104
- }
105
- const value = getDescriptor(target, prop).get.call(target);
106
- tracker.trackedValues.set(prop, value);
107
- return value;
108
- }
109
- return Reflect.get(target, prop, receiver);
110
- } });
111
- blocProxyCache.set(bloc, proxy);
112
- return proxy;
113
- }
114
8
  /**
115
- * Check if any tracked getters have changed values
9
+ * Generate an instance key for a bloc
116
10
  *
117
- * @remarks
118
- * Re-computes all getters that were accessed during the last render and
119
- * compares their new values with stored values using Object.is() (reference
120
- * equality).
11
+ * Logic:
12
+ * - If user provides instanceId, use it (convert number to string)
13
+ * - If isolated, generate or reuse a unique key for this component
14
+ * - Otherwise, return undefined (use default key)
121
15
  *
122
- * OPTIMIZATION: Render cache population
123
- * - Computes ALL tracked getters (no early exit) to populate the render cache
124
- * - This ensures each getter is computed only once per render cycle
125
- * - If we're going to re-render, getters accessed during render will use cached values
126
- * - Cache is populated even if no changes detected (useful for parent-triggered re-renders)
127
- * - Trade-off: Give up early exit to ensure full cache population
128
- *
129
- * Error handling: If a getter throws during re-computation, we log a warning,
130
- * stop tracking that specific getter, and treat it as "changed" to trigger
131
- * a re-render. This prevents the tracking system from breaking while still
132
- * allowing React's error boundary to handle the error on the next render.
133
- *
134
- * @param bloc - The bloc instance
135
- * @param tracker - The getter tracking state
136
- * @returns True if any tracked getter value changed, false otherwise
16
+ * @param componentRef - React component reference (persists across remounts)
17
+ * @param isIsolated - Whether the bloc is isolated
18
+ * @param providedId - User-provided instance ID (from options)
19
+ * @returns Instance key string or undefined for default
137
20
  */
138
- function hasGetterChanges(bloc, tracker) {
139
- if (!tracker || tracker.trackedGetters.size === 0) return false;
140
- tracker.renderCache.clear();
141
- let hasAnyChange = false;
142
- for (const prop of tracker.trackedGetters) try {
143
- const descriptor = getDescriptor(bloc, prop);
144
- if (!descriptor?.get) continue;
145
- const newValue = descriptor.get.call(bloc);
146
- const oldValue = tracker.trackedValues.get(prop);
147
- tracker.renderCache.set(prop, newValue);
148
- tracker.trackedValues.set(prop, newValue);
149
- if (!Object.is(newValue, oldValue)) hasAnyChange = true;
150
- } catch (error) {
151
- console.warn(`[useBloc] Getter "${String(prop)}" threw error during change detection. Stopping tracking for this getter.`, error);
152
- tracker.trackedGetters.delete(prop);
153
- tracker.trackedValues.delete(prop);
154
- tracker.cacheValid = false;
155
- return true;
156
- }
157
- tracker.cacheValid = true;
158
- return hasAnyChange;
159
- }
160
- /**
161
- * Generates instance ID for isolated blocs
162
- */
163
- function generateInstanceId$1(componentRef, isIsolated, providedId) {
164
- if (providedId) return providedId;
21
+ function generateInstanceKey(componentRef, isIsolated, providedId) {
22
+ if (providedId !== void 0) return typeof providedId === "number" ? String(providedId) : providedId;
165
23
  if (isIsolated) {
166
- if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = `isolated-${Math.random().toString(36).slice(2, 11)}`;
24
+ if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = generateIsolatedKey();
167
25
  return componentRef.__blocInstanceId;
168
26
  }
169
27
  }
28
+
29
+ //#endregion
30
+ //#region src/useBloc.ts
170
31
  function determineTrackingMode(options) {
171
32
  return {
172
33
  useManualDeps: options?.dependencies !== void 0,
@@ -174,75 +35,6 @@ function determineTrackingMode(options) {
174
35
  };
175
36
  }
176
37
  /**
177
- * Factory: Creates subscribe function for automatic proxy tracking mode
178
- */
179
- function createAutoTrackSubscribe(instance, hookState) {
180
- return (callback) => {
181
- return instance.subscribe(() => {
182
- const tracker = hookState.tracker || (hookState.tracker = createTrackerState());
183
- let stateChanged = hasChanges(tracker, instance.state);
184
- if (tracker.pathCache.size === 0 && hookState.getterTracker && hookState.getterTracker.trackedGetters.size > 0) stateChanged = false;
185
- if (stateChanged) {
186
- callback();
187
- return;
188
- }
189
- if (hasGetterChanges(instance, hookState.getterTracker)) callback();
190
- });
191
- };
192
- }
193
- /**
194
- * Factory: Creates subscribe function for manual dependencies mode
195
- */
196
- function createManualDepsSubscribe(instance, hookState, options) {
197
- return (callback) => {
198
- return instance.subscribe(() => {
199
- const newDeps = options.dependencies(instance.state, instance);
200
- if (!hookState.manualDepsCache || !shallowEqual(hookState.manualDepsCache, newDeps)) {
201
- hookState.manualDepsCache = newDeps;
202
- callback();
203
- }
204
- });
205
- };
206
- }
207
- /**
208
- * Factory: Creates subscribe function for no-tracking mode
209
- */
210
- function createNoTrackSubscribe(instance) {
211
- return (callback) => instance.subscribe(callback);
212
- }
213
- /**
214
- * Factory: Creates getSnapshot function for automatic proxy tracking mode
215
- */
216
- function createAutoTrackSnapshot(instance, hookState) {
217
- return () => {
218
- const tracker = hookState.tracker || (hookState.tracker = createTrackerState());
219
- if (hasTrackedData(tracker)) captureTrackedPaths(tracker, instance.state);
220
- if (hookState.getterTracker) {
221
- if (hookState.getterTracker.currentlyAccessing.size > 0) hookState.getterTracker.trackedGetters = new Set(hookState.getterTracker.currentlyAccessing);
222
- hookState.getterTracker.currentlyAccessing.clear();
223
- hookState.getterTracker.isTracking = true;
224
- activeTrackerMap.set(instance, hookState.getterTracker);
225
- }
226
- startTracking(tracker);
227
- return createProxy(tracker, instance.state);
228
- };
229
- }
230
- /**
231
- * Factory: Creates getSnapshot function for manual dependencies mode
232
- */
233
- function createManualDepsSnapshot(instance, hookState, options) {
234
- return () => {
235
- hookState.manualDepsCache = options.dependencies(instance.state, instance);
236
- return instance.state;
237
- };
238
- }
239
- /**
240
- * Factory: Creates getSnapshot function for no-tracking mode
241
- */
242
- function createNoTrackSnapshot(instance) {
243
- return () => instance.state;
244
- }
245
- /**
246
38
  * Lifecycle: INITIAL MOUNT
247
39
  * 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn
248
40
  * 2. useSyncExternalStore calls getSnapshotFn (1st time) - lazy creates tracker, starts tracking, returns proxy
@@ -261,57 +53,51 @@ function createNoTrackSnapshot(instance) {
261
53
  */
262
54
  function useBloc(BlocClass, options) {
263
55
  const componentRef = useRef({});
264
- const [bloc, subscribe, getSnapshot, instanceKey, hookState, rawInstance] = useMemo(() => {
265
- const isIsolated = BlocClass.isolated === true;
266
- const Constructor = BlocClass;
267
- const instanceId = generateInstanceId$1(componentRef.current, isIsolated, options?.instanceId);
268
- const instance = Constructor.getOrCreate(instanceId, options?.staticProps);
56
+ const Constructor = BlocClass;
57
+ const isIsolated = isIsolatedClass(BlocClass);
58
+ const [bloc, subscribe, getSnapshot, instanceKey, adapterState, rawInstance] = useMemo(() => {
59
+ const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
60
+ const instance = BlocClass.resolve(instanceKey$1, options?.staticProps);
269
61
  const { useManualDeps, autoTrackEnabled } = determineTrackingMode(options);
270
- const hookState$1 = {
271
- tracker: null,
272
- manualDepsCache: null,
273
- getterTracker: null,
274
- proxiedBloc: null
275
- };
276
62
  let subscribeFn;
277
63
  let getSnapshotFn;
278
- if (useManualDeps) {
279
- subscribeFn = createManualDepsSubscribe(instance, hookState$1, options);
280
- getSnapshotFn = createManualDepsSnapshot(instance, hookState$1, options);
281
- hookState$1.proxiedBloc = instance;
64
+ let adapterState$1;
65
+ if (useManualDeps && options?.dependencies) {
66
+ adapterState$1 = initManualDepsState(instance);
67
+ subscribeFn = createManualDepsSubscribe(instance, adapterState$1, { dependencies: options.dependencies });
68
+ getSnapshotFn = createManualDepsSnapshot(instance, adapterState$1, { dependencies: options.dependencies });
282
69
  } else if (!autoTrackEnabled) {
70
+ adapterState$1 = initNoTrackState(instance);
283
71
  subscribeFn = createNoTrackSubscribe(instance);
284
72
  getSnapshotFn = createNoTrackSnapshot(instance);
285
- hookState$1.proxiedBloc = instance;
286
73
  } else {
287
- subscribeFn = createAutoTrackSubscribe(instance, hookState$1);
288
- getSnapshotFn = createAutoTrackSnapshot(instance, hookState$1);
289
- hookState$1.getterTracker = createGetterTracker();
290
- hookState$1.proxiedBloc = createBlocProxy(instance);
74
+ adapterState$1 = initAutoTrackState(instance);
75
+ subscribeFn = createAutoTrackSubscribe(instance, adapterState$1);
76
+ getSnapshotFn = createAutoTrackSnapshot(instance, adapterState$1);
291
77
  }
292
78
  return [
293
- hookState$1.proxiedBloc,
79
+ adapterState$1.proxiedBloc,
294
80
  subscribeFn,
295
81
  getSnapshotFn,
296
- instanceId,
297
- hookState$1,
82
+ instanceKey$1,
83
+ adapterState$1,
298
84
  instance
299
85
  ];
300
- }, [BlocClass]);
86
+ }, [BlocClass, options?.instanceId]);
301
87
  const state = useSyncExternalStore(subscribe, getSnapshot);
88
+ const [, forceUpdate] = useReducer((x) => x + 1, 0);
89
+ const externalDepsManager = useRef(new ExternalDependencyManager());
302
90
  useEffect(() => {
303
- if (hookState.getterTracker) {
304
- hookState.getterTracker.isTracking = false;
305
- hookState.getterTracker.cacheValid = false;
306
- activeTrackerMap.delete(rawInstance);
307
- }
91
+ disableGetterTracking(adapterState, rawInstance);
92
+ externalDepsManager.current.updateSubscriptions(adapterState.getterTracker, rawInstance, forceUpdate);
308
93
  });
309
94
  useEffect(() => {
310
95
  if (options?.onMount) options.onMount(bloc);
311
96
  return () => {
97
+ externalDepsManager.current.cleanup();
312
98
  if (options?.onUnmount) options.onUnmount(bloc);
313
- BlocClass.release(instanceKey);
314
- if (BlocClass.isolated === true && !rawInstance.isDisposed) rawInstance.dispose();
99
+ Constructor.release(instanceKey);
100
+ if (isIsolated && !rawInstance.isDisposed) rawInstance.dispose();
315
101
  };
316
102
  }, []);
317
103
  return [
@@ -323,45 +109,20 @@ function useBloc(BlocClass, options) {
323
109
 
324
110
  //#endregion
325
111
  //#region src/useBlocActions.ts
326
- /**
327
- * Generates instance ID for isolated blocs
328
- */
329
- function generateInstanceId(componentRef, isIsolated, providedId) {
330
- if (providedId) return providedId;
331
- if (isIsolated) {
332
- if (!componentRef.__blocInstanceId) componentRef.__blocInstanceId = `isolated-${Math.random().toString(36).slice(2, 11)}`;
333
- return componentRef.__blocInstanceId;
334
- }
335
- }
336
- /**
337
- * React hook for accessing bloc instance without state subscription.
338
- * Use this when you only need to call bloc methods/actions without reading state.
339
- *
340
- * Benefits over useBloc:
341
- * - No state subscription overhead
342
- * - No proxy tracking
343
- * - Component never re-renders due to bloc state changes
344
- * - Lighter weight for action-only components
345
- *
346
- * @template TBloc - The StateContainer type
347
- * @param BlocClass - The bloc class constructor
348
- * @param options - Optional configuration
349
- * @returns The bloc instance
350
- */
351
112
  function useBlocActions(BlocClass, options) {
352
113
  const componentRef = useRef({});
353
114
  const [bloc, instanceKey] = useMemo(() => {
354
- const isIsolated = BlocClass.isolated === true;
115
+ const isIsolated = isIsolatedClass(BlocClass);
355
116
  const Constructor = BlocClass;
356
- const instanceId = generateInstanceId(componentRef.current, isIsolated, options?.instanceId);
357
- return [Constructor.getOrCreate(instanceId, options?.staticProps), instanceId];
117
+ const instanceKey$1 = generateInstanceKey(componentRef.current, isIsolated, options?.instanceId);
118
+ return [options?.staticProps ? Constructor.resolve(instanceKey$1, options.staticProps) : Constructor.resolve(instanceKey$1), instanceKey$1];
358
119
  }, [BlocClass]);
359
120
  useEffect(() => {
360
121
  if (options?.onMount) options.onMount(bloc);
361
122
  return () => {
362
123
  if (options?.onUnmount) options.onUnmount(bloc);
363
124
  BlocClass.release(instanceKey);
364
- if (BlocClass.isolated === true && !bloc.isDisposed) bloc.dispose();
125
+ if (isIsolatedClass(BlocClass) && !bloc.isDisposed) bloc.dispose();
365
126
  };
366
127
  }, []);
367
128
  return bloc;
@@ -0,0 +1,25 @@
1
+ import type { ExtractState } from '@blac/core';
2
+ import type { RefObject } from 'react';
3
+ export interface UseBlocOptions<TBloc> {
4
+ staticProps?: any;
5
+ instanceId?: string | number;
6
+ dependencies?: (state: ExtractState<TBloc>, bloc: TBloc) => unknown[];
7
+ autoTrack?: boolean;
8
+ disableGetterCache?: boolean;
9
+ onMount?: (bloc: TBloc) => void;
10
+ onUnmount?: (bloc: TBloc) => void;
11
+ }
12
+ export interface UseBlocOptionsWithDependencies<TBloc> extends UseBlocOptions<TBloc> {
13
+ dependencies: (state: ExtractState<TBloc>, bloc: TBloc) => unknown[];
14
+ autoTrack?: never;
15
+ }
16
+ export type UseBlocReturn<TBloc> = [
17
+ ExtractState<TBloc>,
18
+ TBloc,
19
+ RefObject<ComponentRef>
20
+ ];
21
+ export type ComponentRef = {
22
+ __blocInstanceId?: string;
23
+ __bridge?: any;
24
+ };
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { type BlocConstructor, StateContainer } from '@blac/core';
2
+ import type { UseBlocOptions, UseBlocReturn } from './types';
3
+ /**
4
+ * Lifecycle: INITIAL MOUNT
5
+ * 1. useMemo runs once - creates bloc, subscribeFn, getSnapshotFn
6
+ * 2. useSyncExternalStore calls getSnapshotFn (1st time) - lazy creates tracker, starts tracking, returns proxy
7
+ * 3. Component renders - proxy tracks property accesses
8
+ * 4. useSyncExternalStore calls getSnapshotFn (2nd time) - captures tracked paths, starts new tracking, returns proxy
9
+ * 5. useSyncExternalStore calls subscribeFn - sets up state change listener
10
+ *
11
+ * Lifecycle: STATE CHANGE
12
+ * 1. Bloc state changes
13
+ * 2. subscribeFn callback checks hasChanges() - only re-renders if tracked paths changed
14
+ * 3. If re-render: getSnapshotFn captures previous paths, starts tracking, returns proxy
15
+ *
16
+ * Lifecycle: RE-RENDER (parent re-render)
17
+ * 1. useMemo returns cached values (same bloc, subscribeFn, getSnapshotFn)
18
+ * 2. useSyncExternalStore calls getSnapshotFn - captures paths, starts tracking, returns proxy
19
+ */
20
+ export declare function useBloc<T extends new (...args: any[]) => StateContainer<any>>(BlocClass: T & BlocConstructor<InstanceType<T>>, options?: UseBlocOptions<InstanceType<T>>): UseBlocReturn<InstanceType<T>>;
21
+ //# sourceMappingURL=useBloc.d.ts.map
@@ -0,0 +1,9 @@
1
+ import { type BlocConstructor, StateContainer } from '@blac/core';
2
+ export interface UseBlocActionsOptions<TBloc> {
3
+ staticProps?: any;
4
+ instanceId?: string | number;
5
+ onMount?: (bloc: TBloc) => void;
6
+ onUnmount?: (bloc: TBloc) => void;
7
+ }
8
+ export declare function useBlocActions<T extends new (...args: any[]) => StateContainer<any>>(BlocClass: T & BlocConstructor<InstanceType<T>>, options?: UseBlocActionsOptions<InstanceType<T>>): InstanceType<T>;
9
+ //# sourceMappingURL=useBlocActions.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Instance key generation utilities for React integration
3
+ */
4
+ import type { ComponentRef } from '../types';
5
+ /**
6
+ * Generate an instance key for a bloc
7
+ *
8
+ * Logic:
9
+ * - If user provides instanceId, use it (convert number to string)
10
+ * - If isolated, generate or reuse a unique key for this component
11
+ * - Otherwise, return undefined (use default key)
12
+ *
13
+ * @param componentRef - React component reference (persists across remounts)
14
+ * @param isIsolated - Whether the bloc is isolated
15
+ * @param providedId - User-provided instance ID (from options)
16
+ * @returns Instance key string or undefined for default
17
+ */
18
+ export declare function generateInstanceKey(componentRef: ComponentRef, isIsolated: boolean, providedId?: string | number): string | undefined;
19
+ //# sourceMappingURL=instance-keys.d.ts.map