@blac/react 2.0.21 → 2.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +97 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +97 -90
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -91,6 +91,22 @@ function RegistryProvider({ registry, children }) {
|
|
|
91
91
|
function supportsTrackedState(value) {
|
|
92
92
|
return typeof value[_blac_core.WITH_TRACKED_STATE] === "function";
|
|
93
93
|
}
|
|
94
|
+
const gettersByProto = /* @__PURE__ */ new WeakMap();
|
|
95
|
+
function collectGetters(proto) {
|
|
96
|
+
const cached = gettersByProto.get(proto);
|
|
97
|
+
if (cached !== void 0) return cached;
|
|
98
|
+
const getters = /* @__PURE__ */ new Map();
|
|
99
|
+
let p = proto;
|
|
100
|
+
while (p !== null && p !== Object.prototype) {
|
|
101
|
+
for (const key of Reflect.ownKeys(p)) {
|
|
102
|
+
const desc = Object.getOwnPropertyDescriptor(p, key);
|
|
103
|
+
if (desc?.get && !getters.has(key)) getters.set(key, desc.get);
|
|
104
|
+
}
|
|
105
|
+
p = Object.getPrototypeOf(p);
|
|
106
|
+
}
|
|
107
|
+
gettersByProto.set(proto, getters);
|
|
108
|
+
return getters;
|
|
109
|
+
}
|
|
94
110
|
/**
|
|
95
111
|
* Build a per-consumer proxy for a bloc instance.
|
|
96
112
|
*
|
|
@@ -109,27 +125,18 @@ function supportsTrackedState(value) {
|
|
|
109
125
|
* caching wrappers per handle to avoid re-allocation.
|
|
110
126
|
*/
|
|
111
127
|
function buildTrackedProxy(instance, trackedStateRef, onDepHandle) {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
while (proto && proto !== Object.prototype) {
|
|
115
|
-
const keys = [...Object.getOwnPropertyNames(proto), ...Object.getOwnPropertySymbols(proto)];
|
|
116
|
-
for (const key of keys) {
|
|
117
|
-
const desc = Object.getOwnPropertyDescriptor(proto, key);
|
|
118
|
-
if (desc?.get && !getterDescs.has(key)) getterDescs.set(key, desc);
|
|
119
|
-
}
|
|
120
|
-
proto = Object.getPrototypeOf(proto);
|
|
121
|
-
}
|
|
128
|
+
const getters = collectGetters(Object.getPrototypeOf(instance));
|
|
129
|
+
const tracksState = supportsTrackedState(instance);
|
|
122
130
|
const boundMethods = /* @__PURE__ */ new Map();
|
|
123
131
|
const wrapDepHandle = (value) => {
|
|
124
132
|
if (onDepHandle !== void 0 && (typeof value === "function" || typeof value === "object") && value !== null && value[_blac_core.DEP_BRAND] !== void 0) return onDepHandle(value);
|
|
125
133
|
return value;
|
|
126
134
|
};
|
|
127
135
|
return { proxy: new Proxy(instance, { get(target, key) {
|
|
128
|
-
const getter =
|
|
129
|
-
if (getter) {
|
|
136
|
+
const getter = getters.get(key);
|
|
137
|
+
if (getter !== void 0) {
|
|
130
138
|
const tracked = trackedStateRef.current;
|
|
131
|
-
|
|
132
|
-
return wrapDepHandle(tracked == null || !supportsTrackedState(target) ? read() : target[_blac_core.WITH_TRACKED_STATE](tracked, read, onDepHandle));
|
|
139
|
+
return wrapDepHandle(tracked == null || !tracksState ? getter.call(target) : target[_blac_core.WITH_TRACKED_STATE](tracked, getter, onDepHandle));
|
|
133
140
|
}
|
|
134
141
|
const value = Reflect.get(target, key, target);
|
|
135
142
|
if (typeof value === "function") {
|
|
@@ -147,8 +154,6 @@ function buildTrackedProxy(instance, trackedStateRef, onDepHandle) {
|
|
|
147
154
|
//#region src/useBloc.ts
|
|
148
155
|
let nextConsumerId = 0;
|
|
149
156
|
const ARGS_UNSET = Symbol("blac.argsKeyUnset");
|
|
150
|
-
const primaryRefId = (consumerId) => `useBloc@${consumerId}`;
|
|
151
|
-
const depRefId = (consumerId) => `useBloc@${consumerId}:dep`;
|
|
152
157
|
/**
|
|
153
158
|
* React hook that connects a component to a state container with automatic
|
|
154
159
|
* re-render on state changes.
|
|
@@ -202,51 +207,29 @@ const depRefId = (consumerId) => `useBloc@${consumerId}:dep`;
|
|
|
202
207
|
*/
|
|
203
208
|
function useBloc(BlocClass, options) {
|
|
204
209
|
const registry = (0, react.useContext)(RegistryContext) ?? (0, _blac_core.getRegistry)();
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
const consumerId =
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
onMountRef.current = options?.onMount;
|
|
212
|
-
const onUnmountRef = (0, react.useRef)(options?.onUnmount);
|
|
213
|
-
onUnmountRef.current = options?.onUnmount;
|
|
210
|
+
const consumerRef = (0, react.useRef)(null);
|
|
211
|
+
const consumer = consumerRef.current ??= createConsumer();
|
|
212
|
+
const consumerId = consumer.id;
|
|
213
|
+
consumer.select = options?.select;
|
|
214
|
+
consumer.onMount = options?.onMount;
|
|
215
|
+
consumer.onUnmount = options?.onUnmount;
|
|
214
216
|
const ownArgs = options?.args;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (!Object.is(ownArgsKeyRef.current.ref, ownArgs)) ownArgsKeyRef.current = {
|
|
222
|
-
ref: ownArgs,
|
|
223
|
-
key: ownArgs === void 0 ? void 0 : JSON.stringify(ownArgs)
|
|
224
|
-
};
|
|
225
|
-
const ownArgsKey = ownArgsKeyRef.current.key;
|
|
217
|
+
consumer.ownArgs = ownArgs;
|
|
218
|
+
if (!Object.is(consumer.ownArgsKeyFor, ownArgs)) {
|
|
219
|
+
consumer.ownArgsKeyFor = ownArgs;
|
|
220
|
+
consumer.ownArgsKey = ownArgs === void 0 ? void 0 : JSON.stringify(ownArgs);
|
|
221
|
+
}
|
|
222
|
+
const ownArgsKey = consumer.ownArgsKey;
|
|
226
223
|
const providerArgs = useProvidedArgs(BlocClass);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
ref: providerArgs,
|
|
235
|
-
key: providerArgs === void 0 ? void 0 : JSON.stringify(providerArgs)
|
|
236
|
-
};
|
|
237
|
-
const providerArgsKey = providerArgsKeyRef.current.key;
|
|
238
|
-
const resolveEffectiveArgs = () => ownArgsRef.current !== void 0 ? ownArgsRef.current : providerArgsRef.current;
|
|
239
|
-
const trackedStateRef = (0, react.useRef)(null);
|
|
240
|
-
const proxyCacheRef = (0, react.useRef)(new _dirtytalk_structural.ProxyCache());
|
|
241
|
-
const depWrapperCacheRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
242
|
-
const rebindNonceRef = (0, react.useRef)(0);
|
|
243
|
-
const rebindNonce = rebindNonceRef.current;
|
|
244
|
-
const consumerRef = (0, react.useRef)(null);
|
|
245
|
-
consumerRef.current ??= createConsumer();
|
|
246
|
-
const consumer = consumerRef.current;
|
|
247
|
-
const ownedBlocRef = (0, react.useRef)(null);
|
|
224
|
+
consumer.providerArgs = providerArgs;
|
|
225
|
+
if (!Object.is(consumer.providerArgsKeyFor, providerArgs)) {
|
|
226
|
+
consumer.providerArgsKeyFor = providerArgs;
|
|
227
|
+
consumer.providerArgsKey = providerArgs === void 0 ? void 0 : JSON.stringify(providerArgs);
|
|
228
|
+
}
|
|
229
|
+
const providerArgsKey = consumer.providerArgsKey;
|
|
230
|
+
const rebindNonce = consumer.rebindNonce;
|
|
248
231
|
const { bloc, instanceKey, trackedBloc } = (0, react.useMemo)(() => {
|
|
249
|
-
const effectiveArgs = resolveEffectiveArgs();
|
|
232
|
+
const effectiveArgs = resolveEffectiveArgs(consumer);
|
|
250
233
|
const resolvedKey = (0, _blac_core.resolveInstanceKey)(BlocClass, effectiveArgs);
|
|
251
234
|
const instance = registry.acquire(BlocClass, resolvedKey, {
|
|
252
235
|
canCreate: true,
|
|
@@ -255,14 +238,14 @@ function useBloc(BlocClass, options) {
|
|
|
255
238
|
sweepIfUnowned: true
|
|
256
239
|
});
|
|
257
240
|
const onDepHandle = (handle) => {
|
|
258
|
-
const cache =
|
|
241
|
+
const cache = consumer.depWrappers ??= /* @__PURE__ */ new Map();
|
|
259
242
|
const cached = cache.get(handle);
|
|
260
243
|
if (cached !== void 0) return cached;
|
|
261
|
-
const wrapper = makeDepWrapper(handle,
|
|
244
|
+
const wrapper = makeDepWrapper(handle, registry, consumer, onDepHandle);
|
|
262
245
|
cache.set(handle, wrapper);
|
|
263
246
|
return wrapper;
|
|
264
247
|
};
|
|
265
|
-
const { proxy } = buildTrackedProxy(instance,
|
|
248
|
+
const { proxy } = buildTrackedProxy(instance, consumer.tracked, onDepHandle);
|
|
266
249
|
return {
|
|
267
250
|
bloc: instance,
|
|
268
251
|
instanceKey: resolvedKey,
|
|
@@ -281,7 +264,7 @@ function useBloc(BlocClass, options) {
|
|
|
281
264
|
const container = consumer.container;
|
|
282
265
|
const unsubscribe = container.channel.subscribe(() => consumer.interest, () => {
|
|
283
266
|
if (consumer.isSelectMode) {
|
|
284
|
-
const select =
|
|
267
|
+
const select = consumer.select;
|
|
285
268
|
if (select) {
|
|
286
269
|
const next = select(container.state, container);
|
|
287
270
|
const prev = consumer.selection;
|
|
@@ -307,18 +290,18 @@ function useBloc(BlocClass, options) {
|
|
|
307
290
|
const live = registry.acquire(BlocClass, instanceKey, {
|
|
308
291
|
canCreate: true,
|
|
309
292
|
countRef: true,
|
|
310
|
-
refId: primaryRefId
|
|
311
|
-
args: resolveEffectiveArgs()
|
|
293
|
+
refId: consumer.primaryRefId,
|
|
294
|
+
args: resolveEffectiveArgs(consumer)
|
|
312
295
|
});
|
|
313
|
-
|
|
314
|
-
|
|
296
|
+
consumer.ownedBloc = live;
|
|
297
|
+
consumer.onMount?.(live);
|
|
315
298
|
if (live !== bloc) {
|
|
316
|
-
|
|
299
|
+
consumer.rebindNonce++;
|
|
317
300
|
consumer.bump();
|
|
318
301
|
}
|
|
319
302
|
return () => {
|
|
320
|
-
|
|
321
|
-
registry.release(BlocClass, instanceKey, false, primaryRefId
|
|
303
|
+
consumer.onUnmount?.(consumer.ownedBloc ?? bloc);
|
|
304
|
+
registry.release(BlocClass, instanceKey, false, consumer.primaryRefId);
|
|
322
305
|
};
|
|
323
306
|
}, [
|
|
324
307
|
BlocClass,
|
|
@@ -328,7 +311,7 @@ function useBloc(BlocClass, options) {
|
|
|
328
311
|
]);
|
|
329
312
|
const container = consumer.container;
|
|
330
313
|
const rawState = container.state;
|
|
331
|
-
const select =
|
|
314
|
+
const select = consumer.select;
|
|
332
315
|
consumer.isSelectMode = select !== void 0;
|
|
333
316
|
let state;
|
|
334
317
|
if (select !== void 0) {
|
|
@@ -336,20 +319,25 @@ function useBloc(BlocClass, options) {
|
|
|
336
319
|
if (consumer.selection === null) consumer.selection = select(rawState, bloc);
|
|
337
320
|
consumer.interest = _dirtytalk_structural.ALL_PATHS;
|
|
338
321
|
} else {
|
|
339
|
-
const tracked = (0, _dirtytalk_structural.trackRender)(rawState, container.interner,
|
|
322
|
+
const tracked = (0, _dirtytalk_structural.trackRender)(rawState, container.interner, consumer.proxyCache ??= new _dirtytalk_structural.ProxyCache());
|
|
340
323
|
state = tracked.value;
|
|
341
|
-
|
|
324
|
+
consumer.tracked.current = tracked.value;
|
|
342
325
|
consumer.paths = tracked.paths;
|
|
326
|
+
consumer.disarm = tracked.disarm;
|
|
343
327
|
const session = consumer.session;
|
|
344
328
|
session.clear();
|
|
345
329
|
session.set(container, {
|
|
346
330
|
kind: "primary",
|
|
347
331
|
paths: tracked.paths
|
|
348
332
|
});
|
|
349
|
-
queueMicrotask(tracked.disarm);
|
|
350
333
|
}
|
|
351
334
|
(0, react.useLayoutEffect)(() => {
|
|
352
|
-
|
|
335
|
+
consumer.tracked.current = null;
|
|
336
|
+
const disarm = consumer.disarm;
|
|
337
|
+
if (disarm !== null) {
|
|
338
|
+
consumer.disarm = null;
|
|
339
|
+
disarm();
|
|
340
|
+
}
|
|
353
341
|
if (consumer.isSelectMode) {
|
|
354
342
|
consumer.lastReconcile = null;
|
|
355
343
|
return;
|
|
@@ -436,7 +424,11 @@ function useBloc(BlocClass, options) {
|
|
|
436
424
|
}
|
|
437
425
|
const noop = () => {};
|
|
438
426
|
function createConsumer() {
|
|
427
|
+
const id = `useBloc-${nextConsumerId++}`;
|
|
439
428
|
const consumer = {
|
|
429
|
+
id,
|
|
430
|
+
primaryRefId: `useBloc@${id}`,
|
|
431
|
+
depRefId: `useBloc@${id}:dep`,
|
|
440
432
|
container: null,
|
|
441
433
|
version: 0,
|
|
442
434
|
notify: noop,
|
|
@@ -452,16 +444,32 @@ function createConsumer() {
|
|
|
452
444
|
selection: null,
|
|
453
445
|
session: /* @__PURE__ */ new Map(),
|
|
454
446
|
depSubs: /* @__PURE__ */ new Map(),
|
|
455
|
-
lastReconcile: null
|
|
447
|
+
lastReconcile: null,
|
|
448
|
+
select: void 0,
|
|
449
|
+
onMount: void 0,
|
|
450
|
+
onUnmount: void 0,
|
|
451
|
+
ownArgs: void 0,
|
|
452
|
+
ownArgsKeyFor: ARGS_UNSET,
|
|
453
|
+
ownArgsKey: void 0,
|
|
454
|
+
providerArgs: void 0,
|
|
455
|
+
providerArgsKeyFor: ARGS_UNSET,
|
|
456
|
+
providerArgsKey: void 0,
|
|
457
|
+
tracked: { current: null },
|
|
458
|
+
disarm: null,
|
|
459
|
+
proxyCache: null,
|
|
460
|
+
depWrappers: null,
|
|
461
|
+
rebindNonce: 0,
|
|
462
|
+
ownedBloc: null
|
|
456
463
|
};
|
|
457
464
|
return consumer;
|
|
458
465
|
}
|
|
466
|
+
const resolveEffectiveArgs = (consumer) => consumer.ownArgs !== void 0 ? consumer.ownArgs : consumer.providerArgs;
|
|
459
467
|
/**
|
|
460
468
|
* Build the per-consumer wrapper that replaces a branded dep handle inside a
|
|
461
469
|
* tracked getter's `this`. The wrapper exposes the same accessors as the core
|
|
462
470
|
* handle and overrides `.track()`:
|
|
463
471
|
*
|
|
464
|
-
* - **Inside a render** (`
|
|
472
|
+
* - **Inside a render** (`consumer.tracked.current != null`): resolve (ENSURE,
|
|
465
473
|
* no ref) the dep, `trackRender` its state, merge the recorded paths into the
|
|
466
474
|
* session entry, build/reuse a tracked proxy for the dep so its OWN getters
|
|
467
475
|
* track too, and return `[trackedValue, depProxy]`. The ownership ref is taken
|
|
@@ -478,9 +486,9 @@ function createConsumer() {
|
|
|
478
486
|
* the dep already has a non-primary session entry this render, reuse its proxy
|
|
479
487
|
* + union its paths instead of re-acquiring.
|
|
480
488
|
*/
|
|
481
|
-
function makeDepWrapper(handle,
|
|
489
|
+
function makeDepWrapper(handle, registry, consumer, onDepHandle) {
|
|
482
490
|
const brand = handle[_blac_core.DEP_BRAND];
|
|
483
|
-
const refId = depRefId
|
|
491
|
+
const refId = consumer.depRefId;
|
|
484
492
|
const perDep = /* @__PURE__ */ new Map();
|
|
485
493
|
const proxyCache = new _dirtytalk_structural.ProxyCache();
|
|
486
494
|
const resolve = (options) => {
|
|
@@ -496,7 +504,7 @@ function makeDepWrapper(handle, consumerId, registry, trackedStateRef, consumer,
|
|
|
496
504
|
untracked: (options) => resolve(options).dep,
|
|
497
505
|
track: (options) => {
|
|
498
506
|
const { dep, key, args } = resolve(options);
|
|
499
|
-
if (
|
|
507
|
+
if (consumer.tracked.current == null) return [dep.state, dep];
|
|
500
508
|
const session = consumer.session;
|
|
501
509
|
const existing = session.get(dep);
|
|
502
510
|
const tracked = (0, _dirtytalk_structural.trackRender)(dep.state, dep.interner, proxyCache);
|
|
@@ -553,7 +561,7 @@ const shallowArrayEqual = (a, b) => {
|
|
|
553
561
|
* would miss a `patch`-triggered atomic-replacement of `items`.
|
|
554
562
|
*
|
|
555
563
|
* Ancestors are added under the interner's *ancestor-watch* lane
|
|
556
|
-
* (`
|
|
564
|
+
* (`ancestorWatchIds`), NOT as normal ids. The source emits a matching
|
|
557
565
|
* ancestor-watch mark only for paths it replaces atomically — never for a
|
|
558
566
|
* plain-object structural pulse-up. So `{'items.length'}` wakes when the array
|
|
559
567
|
* `items` is replaced, but `{'user.email'}` does NOT wake when a sibling
|
|
@@ -563,22 +571,21 @@ const shallowArrayEqual = (a, b) => {
|
|
|
563
571
|
* Example: leaf `'a.b.c'` adds ancestor-watch ids for `'a.b'` and `'a'` (but
|
|
564
572
|
* NOT the `''` root — a root change is covered by `ALL_PATHS` from the source,
|
|
565
573
|
* and `''` would wake this consumer on every field change).
|
|
574
|
+
*
|
|
575
|
+
* Returns `paths` itself when no leaf has an ancestor (top-level fields only);
|
|
576
|
+
* the set is frozen by then (see `Consumer.disarm`), so sharing it is safe.
|
|
566
577
|
*/
|
|
567
578
|
function expandWithAncestors(paths, interner) {
|
|
568
579
|
if (paths === _dirtytalk_structural.ALL_PATHS) return _dirtytalk_structural.ALL_PATHS;
|
|
569
580
|
const leafPaths = paths;
|
|
570
|
-
|
|
571
|
-
const expanded = new Set(leafPaths);
|
|
581
|
+
let expanded;
|
|
572
582
|
for (const id of leafPaths) {
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
expanded.add(interner.internAncestor(ancestor));
|
|
578
|
-
idx = ancestor.lastIndexOf(".");
|
|
579
|
-
}
|
|
583
|
+
const watch = interner.ancestorWatchIds(id);
|
|
584
|
+
if (watch.length === 0) continue;
|
|
585
|
+
expanded ??= new Set(leafPaths);
|
|
586
|
+
for (let i = 0; i < watch.length; i++) expanded.add(watch[i]);
|
|
580
587
|
}
|
|
581
|
-
return expanded;
|
|
588
|
+
return expanded ?? paths;
|
|
582
589
|
}
|
|
583
590
|
//#endregion
|
|
584
591
|
//#region src/useBlocDeps.ts
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["WITH_TRACKED_STATE","DEP_BRAND","ProxyCache","ALL_PATHS","DEP_BRAND","APPLY_DEPS","REMOVE_DEPS_OWNER"],"sources":["../src/BlocProvider.tsx","../src/RegistryProvider.tsx","../src/buildTrackedProxy.ts","../src/useBloc.ts","../src/useBlocDeps.ts"],"sourcesContent":["import {\n createContext,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { resolveInstanceKey } from '@blac/core';\nimport type { ExtractArgs, StateContainerConstructor } from '@blac/core';\n\n/**\n * Internal context value: a Map from bloc constructor to the args object\n * provided by the nearest BlocProvider for that bloc class.\n *\n * Stored as a WeakMap-keyed record so nested providers for different blocs\n * compose without clobbering each other.\n */\ntype ProvidedArgsMap = Map<StateContainerConstructor, unknown>;\n\nconst ProvidedArgsContext = createContext<ProvidedArgsMap>(new Map());\n\n/**\n * Props for {@link BlocProvider}.\n * @public\n */\nexport interface BlocProviderProps<T extends StateContainerConstructor> {\n /**\n * The bloc class whose args are being provided to descendants.\n */\n bloc: T;\n /**\n * Args that descendant `useBloc(bloc)` calls will resolve to when no\n * own `args` are given.\n */\n args: ExtractArgs<T>;\n children: ReactNode;\n}\n\n/**\n * Provides args to descendant `useBloc` calls for a specific bloc class via\n * React context.\n *\n * Descendants calling `useBloc(Bloc)` without their own `args` resolve to the\n * args supplied here. Own `args` on the `useBloc` call always win.\n *\n * Multiple `BlocProvider` wrappers for different bloc classes compose: each\n * provider merges its entry into the inherited map, so nested providers for\n * different blocs do not interfere.\n *\n * @example\n * ```tsx\n * <BlocProvider bloc={UserBloc} args={{ userId: 'alice' }}>\n * <UserProfile />\n * </BlocProvider>\n * ```\n *\n * @example Per-mount private instance\n * ```tsx\n * const id = useId();\n * <BlocProvider bloc={CartBloc} args={{ _id: id }}>\n * <CartWidget />\n * </BlocProvider>\n * ```\n * @public\n */\nexport function BlocProvider<T extends StateContainerConstructor>({\n bloc,\n args,\n children,\n}: BlocProviderProps<T>): ReactElement {\n const parentMap = useContext(ProvidedArgsContext);\n\n // Keyed on the resolved instance key, not `args` identity, so an inline\n // `args={{ ... }}` literal does not rebuild the map on every render and\n // re-render every consumer below.\n const argsKey = resolveInstanceKey(bloc, args);\n\n // Merge our entry into a new Map so sibling/parent providers for other blocs\n // are preserved.\n const mergedMap = useMemo(() => {\n const next = new Map(parentMap);\n next.set(bloc, args);\n return next;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parentMap, bloc, argsKey]);\n\n return (\n <ProvidedArgsContext.Provider value={mergedMap}>\n {children}\n </ProvidedArgsContext.Provider>\n );\n}\n\n/**\n * Returns the args provided by the nearest {@link BlocProvider} for the given\n * bloc class, or `undefined` when called outside a matching provider.\n *\n * Used by `useBloc` to inherit provider args when no own `args` are given.\n * @public\n */\nexport function useProvidedArgs<T extends StateContainerConstructor>(\n BlocClass: T,\n): ExtractArgs<T> | undefined {\n const map = useContext(ProvidedArgsContext);\n return map.get(BlocClass) as ExtractArgs<T> | undefined;\n}\n","import { createContext, type ReactElement, type ReactNode } from 'react';\nimport type { StateContainerRegistry } from '@blac/core';\n\n/**\n * Internal context value: a registry to use in place of the module-global\n * default (`getRegistry()`), or `null` when no provider is present.\n */\nexport const RegistryContext = createContext<StateContainerRegistry | null>(\n null,\n);\n\n/**\n * Props for {@link RegistryProvider}.\n * @public\n */\nexport interface RegistryProviderProps {\n registry: StateContainerRegistry;\n children: ReactNode;\n}\n\n/**\n * Scopes descendant `useBloc` calls to a specific registry instead of the\n * module-global default.\n *\n * Useful for test isolation, SSR isolation, and micro-frontends that must\n * not share bloc instances with the host page.\n *\n * @example\n * ```tsx\n * <RegistryProvider registry={new StateContainerRegistry()}>\n * <App />\n * </RegistryProvider>\n * ```\n * @public\n */\nexport function RegistryProvider({\n registry,\n children,\n}: RegistryProviderProps): ReactElement {\n return (\n <RegistryContext.Provider value={registry}>\n {children}\n </RegistryContext.Provider>\n );\n}\n","import { DEP_BRAND, WITH_TRACKED_STATE } from '@blac/core';\n\ninterface TrackedStateTarget {\n [WITH_TRACKED_STATE]<R>(\n tracked: unknown,\n fn: () => R,\n onDepHandle?: (handle: object) => unknown,\n ): R;\n}\n\nfunction supportsTrackedState(value: object): value is TrackedStateTarget {\n return (\n typeof (value as TrackedStateTarget)[WITH_TRACKED_STATE] === 'function'\n );\n}\n\n/**\n * Build a per-consumer proxy for a bloc instance.\n *\n * Getters run with `this` bound to the **real instance**, not a proxy, so ES\n * `#private` fields and methods work in user blocs. Path recording is done by\n * `[WITH_TRACKED_STATE]`, which makes the instance's own `state` getter report\n * the render's tracking proxy for the duration of the call; nested getter\n * chains stay tracked because the override is still set.\n *\n * One allocation per bloc acquisition (inside `useMemo`), so the proxy is\n * stable across renders.\n *\n * @param onDepHandle - Optional callback invoked when a read returns a branded\n * dep handle. Receives the original handle and returns the value to expose in\n * its place (the session-bound wrapper). The callback is responsible for\n * caching wrappers per handle to avoid re-allocation.\n */\nexport function buildTrackedProxy<T extends object>(\n instance: T,\n trackedStateRef: { current: unknown },\n onDepHandle?: (handle: object) => unknown,\n): { proxy: T } {\n // Getter descriptors from the prototype chain (excluding Object.prototype),\n // collected once per acquisition so the get trap stays O(1) per access. Both\n // string- and symbol-keyed getters are collected. Arrow-function class\n // properties are own values, not getters, and pass through unmodified.\n const getterDescs = new Map<string | symbol, PropertyDescriptor>();\n let proto = Object.getPrototypeOf(instance);\n while (proto && proto !== Object.prototype) {\n const keys: (string | symbol)[] = [\n ...Object.getOwnPropertyNames(proto),\n ...Object.getOwnPropertySymbols(proto),\n ];\n for (const key of keys) {\n const desc = Object.getOwnPropertyDescriptor(proto, key);\n if (desc?.get && !getterDescs.has(key)) getterDescs.set(key, desc);\n }\n proto = Object.getPrototypeOf(proto);\n }\n\n // Bound methods are cached so `bloc.method` keeps a stable identity across\n // reads — an unstable one would defeat memoisation in consumers.\n const boundMethods = new Map<string | symbol, unknown>();\n\n const wrapDepHandle = (value: unknown): unknown => {\n if (\n onDepHandle !== undefined &&\n (typeof value === 'function' || typeof value === 'object') &&\n value !== null &&\n (value as Record<symbol, unknown>)[DEP_BRAND] !== undefined\n ) {\n return onDepHandle(value as object);\n }\n return value;\n };\n\n const proxy = new Proxy(instance as object, {\n get(target, key) {\n const desc = getterDescs.get(key);\n // Not an unbound method — a descriptor getter, always `.call`ed below\n // with an explicit receiver.\n // oxlint-disable-next-line typescript/unbound-method\n const getter = desc?.get;\n if (getter) {\n const tracked = trackedStateRef.current;\n const read = () => getter.call(target);\n return wrapDepHandle(\n tracked == null || !supportsTrackedState(target)\n ? read()\n : target[WITH_TRACKED_STATE](tracked, read, onDepHandle),\n );\n }\n\n // Receiver is the real instance, so own fields keep their `#private`\n // brand. Methods are bound to it too, otherwise `bloc.method()` would\n // call with `this` = proxy and fail the same brand check.\n const value = Reflect.get(target, key, target);\n if (typeof value === 'function') {\n let bound = boundMethods.get(key);\n if (bound === undefined) {\n bound = (value as (...a: unknown[]) => unknown).bind(target);\n boundMethods.set(key, bound);\n }\n return wrapDepHandle(bound);\n }\n return wrapDepHandle(value);\n },\n }) as T;\n\n return { proxy };\n}\n","import {\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport {\n DEP_BRAND,\n getRegistry,\n resolveInstanceKey,\n type ExtractArgs,\n type ExtractState,\n type InstanceState,\n type StateContainer,\n type StateContainerConstructor,\n type StateContainerRegistry,\n} from '@blac/core';\nimport {\n ALL_PATHS,\n emptyPathSet,\n pathSetEquals,\n trackRender,\n PathInterner,\n ProxyCache,\n type PathSet,\n} from '@dirtytalk/structural';\nimport { useProvidedArgs } from './BlocProvider';\nimport { RegistryContext } from './RegistryProvider';\nimport { buildTrackedProxy } from './buildTrackedProxy';\nimport type { UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\n\n// Sentinel that can never `Object.is`-equal a real args value (including\n// `undefined`). Used to lazily seed args-key refs so the structural key is\n// computed only when the guard actually runs, never on every render.\nconst ARGS_UNSET: unique symbol = Symbol('blac.argsKeyUnset');\n\n// Registry refId formats for a consumer's primary bloc and its tracked deps.\n// Centralised so the `acquire` and `release` sites can never drift apart — a\n// mismatch would leak the ref and keep the bloc alive past unmount.\nconst primaryRefId = (consumerId: string): string => `useBloc@${consumerId}`;\nconst depRefId = (consumerId: string): string => `useBloc@${consumerId}:dep`;\n\n/**\n * React hook that connects a component to a state container with automatic\n * re-render on state changes.\n *\n * Two tracking modes:\n * - **Auto-tracking** (default): the returned state value is a proxy that\n * records read paths during render. The component re-renders when any\n * recorded path changes. Backed by `@dirtytalk/structural`'s\n * `trackRender` + the container's path-scoped `DirtyChannel`.\n * - **Manual select**: pass `options.select` to opt out of auto-tracking.\n * The hook re-renders only when the returned array's elements change\n * (per-index `Object.is`).\n *\n * Lifecycle:\n * - The bloc is acquired from the registry on mount and released on\n * unmount. The instance key is derived from `options.args` (own args),\n * then the surrounding {@link BlocProvider} context args for this bloc,\n * then the default key (no args).\n * - `options.onMount` fires after the bloc is acquired; `options.onUnmount`\n * fires *before* the registry releases its ref, so the bloc is still\n * alive when the callback runs.\n *\n * Per-mount private instance:\n * ```ts\n * const id = useId();\n * const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });\n * ```\n *\n * @typeParam T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options\n * @returns Tuple of `[state, bloc, ref]`\n *\n * @example Basic usage\n * ```ts\n * const [state, bloc] = useBloc(MyBloc);\n * ```\n *\n * @example Manual select\n * ```ts\n * const [state, bloc] = useBloc(MyBloc, {\n * select: (state) => [state.count],\n * });\n * ```\n *\n * @example Args-based shared instance\n * ```ts\n * const [state, bloc] = useBloc(UserBloc, { args: { userId: 'alice' } });\n * ```\n * @public\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n // Registry scoping: the nearest RegistryProvider wins over the module-global\n // default. Resolved once here (top level, per React's rules of hooks) and\n // closed over by every effect/memo below instead of each calling\n // `getRegistry()` directly.\n const registry = useContext(RegistryContext) ?? getRegistry();\n\n // Stable per-consumer id (for the structural container's consumer registry).\n // Plain counter rather than `useId()` so we don't compete with internal hooks\n // for SSR id slots.\n const consumerIdRef = useRef<string | null>(null);\n if (consumerIdRef.current === null) {\n consumerIdRef.current = `useBloc-${nextConsumerId++}`;\n }\n const consumerId = consumerIdRef.current;\n\n // Refs that always carry the latest option callbacks, so the commit effect\n // can read them without re-keying.\n const selectRef = useRef(options?.select);\n selectRef.current = options?.select;\n const onMountRef = useRef(options?.onMount);\n onMountRef.current = options?.onMount;\n const onUnmountRef = useRef(options?.onUnmount);\n onUnmountRef.current = options?.onUnmount;\n\n // ---------------------------------------------------------------------------\n // Identity resolution\n //\n // Priority: own args > provider args (for this bloc class) > none.\n //\n // Args are user-supplied; callers commonly pass a fresh object literal each\n // render. Memoising on `args` directly would bust every render. We compute a\n // structural key (JSON.stringify) for the useMemo dep instead — undefined\n // args (void-args blocs) collapse to an undefined key.\n // ---------------------------------------------------------------------------\n const ownArgs = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n const ownArgsRef = useRef(ownArgs);\n ownArgsRef.current = ownArgs;\n // Fast-path: only recompute the structural key when the args REFERENCE\n // changes. Callers commonly pass a stable args object (e.g. memoised or\n // module-level), so this skips a JSON.stringify call every render.\n const ownArgsKeyRef = useRef<{ ref: unknown; key: string | undefined }>({\n ref: ARGS_UNSET,\n key: undefined,\n });\n if (!Object.is(ownArgsKeyRef.current.ref, ownArgs)) {\n ownArgsKeyRef.current = {\n ref: ownArgs,\n key: ownArgs === undefined ? undefined : JSON.stringify(ownArgs),\n };\n }\n const ownArgsKey = ownArgsKeyRef.current.key;\n\n // Read provided args from the nearest BlocProvider for this bloc class.\n const providerArgs = useProvidedArgs(BlocClass);\n const providerArgsRef = useRef(providerArgs);\n providerArgsRef.current = providerArgs;\n // Same reference fast-path as ownArgsKey above.\n const providerArgsKeyRef = useRef<{ ref: unknown; key: string | undefined }>({\n ref: ARGS_UNSET,\n key: undefined,\n });\n if (!Object.is(providerArgsKeyRef.current.ref, providerArgs)) {\n providerArgsKeyRef.current = {\n ref: providerArgs,\n key:\n providerArgs === undefined ? undefined : JSON.stringify(providerArgs),\n };\n }\n const providerArgsKey = providerArgsKeyRef.current.key;\n\n // Own args win over provider args; provider args win over no args. Read from\n // the refs (refreshed every render) so the render-time acquire and the layout\n // effect below resolve args identically — the effect re-creates the instance\n // when the rendered entry was disposed (StrictMode remount), and dropping\n // args there would run `init(undefined)`.\n const resolveEffectiveArgs = (): ExtractArgs<T> | undefined =>\n ownArgsRef.current !== undefined\n ? ownArgsRef.current\n : (providerArgsRef.current as ExtractArgs<T> | undefined);\n\n // Current render's tracking proxy. Declared before the memo so the stable\n // ref object can be passed to buildTrackedProxy at acquisition time. The\n // proxy trap only reads `.current` at invocation time (not during creation),\n // so it is safe to pass on first mount even though the value is null.\n // Populated during each auto-track render snapshot (below); cleared to null\n // by useLayoutEffect after commit.\n const trackedStateRef = useRef<unknown>(null);\n\n // ---------------------------------------------------------------------------\n // Per-consumer cross-bloc session.\n //\n // Each render rebuilds a map of every container this consumer is currently\n // interested in. The PRIMARY bloc is the first uniform entry; every dep\n // reached through `this.<handle>.track()` inside a tracked getter adds an\n // entry. The layout-effect reconcile (below) diffs this map vs the previous\n // render to subscribe new deps and release dropped ones. The session lives on\n // this hook's consumer object only — there is no global ambient state, so\n // sibling renders never cross-contaminate.\n // ---------------------------------------------------------------------------\n const proxyCacheRef = useRef(new ProxyCache());\n // Per-handle wrapper cache, allocated once per bloc acquisition (in the memo)\n // so wrappers are stable across renders. handle -> session-bound wrapper.\n const depWrapperCacheRef = useRef<Map<object, unknown>>(new Map());\n\n // Rebind nonce: bumped by the ownership layout-effect when the instance the\n // render captured was disposed + recreated out from under us. This happens on\n // a same-commit ownership handoff of a shared (non-keepAlive) key — the sole\n // prior owner's effect cleanup releases refs→0 and SYNCHRONOUSLY disposes the\n // instance before this consumer's layout setup re-acquires (creating a fresh\n // one) — and equivalently under StrictMode's setup→cleanup→setup double-invoke\n // for a lone owner. Threaded into the memo deps so bumping it rebuilds `bloc`\n // and its tracked proxy against the LIVE registry entry: the proxy binds its\n // target at construction and cannot be retargeted in place.\n //\n // Held in a ref rather than useReducer state because the re-render it needs\n // is already delivered by the consumer's version bump through uSES.\n const rebindNonceRef = useRef(0);\n const rebindNonce = rebindNonceRef.current;\n\n // The uSES store for this hook instance. Created once and mutated in place:\n // it must outlive a rebind (which rebuilds `bloc` + proxy) so the live\n // subscription and version counter are not reset under React.\n const consumerRef = useRef<Consumer | null>(null);\n consumerRef.current ??= createConsumer();\n const consumer = consumerRef.current;\n\n // The live instance actually owned (ref held) by the ownership layout-effect,\n // read by its cleanup so onUnmount always fires with the owned instance.\n const ownedBlocRef = useRef<TBloc | null>(null);\n\n const { bloc, instanceKey, trackedBloc } = useMemo<{\n bloc: TBloc;\n instanceKey: string;\n trackedBloc: TBloc;\n }>(() => {\n const effectiveArgs = resolveEffectiveArgs();\n\n const resolvedKey = resolveInstanceKey(BlocClass, effectiveArgs);\n // Render only ENSUREs the instance exists (no ref). Ownership is claimed in\n // the layout effect below, so an abandoned/uncommitted render can never\n // leak a ref and a memo re-run can never double-count one (R3/R4).\n const instance = registry.acquire(BlocClass, resolvedKey, {\n canCreate: true,\n countRef: false,\n args: effectiveArgs,\n // A render that never commits (SSR, a discarded render) leaves this\n // instance ref-less forever; let the registry sweep it if the layout\n // effect below never claims ownership.\n sweepIfUnowned: true,\n }) as TBloc;\n\n // Build a session-bound wrapper for a dep handle the first time a getter\n // reads it off `this`; cache per handle so the wrapper identity is stable.\n // `onDepHandle` is threaded into each dep's tracked proxy too, so a nested\n // `this.<otherHandle>.track()` inside a dep's getter records into the SAME\n // consumer session — that is what makes deep chains (A→B→C) reactive.\n const onDepHandle = (handle: object): unknown => {\n const cache = depWrapperCacheRef.current;\n const cached = cache.get(handle);\n if (cached !== undefined) return cached;\n const wrapper = makeDepWrapper(\n handle as DepHandleLike,\n consumerId,\n registry,\n trackedStateRef,\n consumer,\n onDepHandle,\n );\n cache.set(handle, wrapper);\n return wrapper;\n };\n\n const { proxy } = buildTrackedProxy(\n instance as object,\n trackedStateRef,\n onDepHandle,\n );\n\n return {\n bloc: instance,\n instanceKey: resolvedKey,\n trackedBloc: proxy as TBloc,\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, ownArgsKey, providerArgsKey, rebindNonce, registry]);\n\n // The memo is the single writer of the container the consumer reads and\n // subscribes to; a re-key or rebind retargets it here, before any effect runs.\n consumer.container = bloc as unknown as StateContainer;\n\n // ---------------------------------------------------------------------------\n // Channel subscription via useSyncExternalStore.\n //\n // `subscribe` is memoised on [BlocClass, instanceKey] — NOT on `bloc` — so a\n // genuine re-key (args change, OR a BlocClass swap) re-subscribes while a\n // pure instance replacement under the same class+key does not churn the\n // subscription.\n //\n // `BlocClass` MUST stay in the dep array even though `instanceKey` alone\n // often determines identity: `resolveInstanceKey`/`resolveKey` collapse to\n // the same `DEFAULT_STRUCTURAL_KEY` sentinel across DIFFERENT classes when\n // neither has args nor a `static key` (e.g.\n // `useBloc(cond ? AdminBloc : UserBloc)`). Memoising on `bloc` alone would\n // reintroduce that leak: swapping classes at that shared key would never\n // re-subscribe.\n //\n // We talk directly to `bloc.channel` — the StructuralContainer's path-scoped\n // DirtyChannel. Subscribing with a dynamic interest function lets us narrow\n // wakeups per consumer, so a component only re-renders when a path it\n // actually read changes (rather than on every state change).\n // ---------------------------------------------------------------------------\n const subscribe = useMemo(\n () => (onStoreChange: () => void) => {\n consumer.notify = onStoreChange;\n const container = consumer.container;\n const unsubscribe = container.channel.subscribe(\n () => consumer.interest,\n () => {\n if (consumer.isSelectMode) {\n const select = selectRef.current;\n if (select) {\n const next = select(\n container.state as ExtractState<T>,\n container as unknown as InstanceState<T>,\n );\n const prev = consumer.selection;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n consumer.selection = next;\n }\n }\n consumer.bump();\n },\n );\n container.registerConsumerPaths(consumerId, consumer.paths);\n // No render→subscribe mount-gap compensation is needed: the channel\n // accumulates marks and flushes on its scheduler, so an emit raised\n // during render is still pending when this subscriber registers and is\n // delivered by that flush. The pre-uSES hook needed `renderStateRef` +\n // a manual force dispatch here only because its subscribe ran in a\n // passive effect keyed on the instance.\n return () => {\n consumer.notify = noop;\n unsubscribe();\n container.unregisterConsumer(consumerId);\n };\n },\n // `rebindNonce` is here for the same reason it is in the memo above: a\n // rebind swaps the underlying instance (and therefore its channel) without\n // changing the class or key, so the subscription must be re-established\n // against the live container.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n [BlocClass, instanceKey, consumerId, rebindNonce],\n );\n\n useSyncExternalStore(\n subscribe,\n consumer.getSnapshot,\n consumer.getServerSnapshot,\n );\n\n // ---------------------------------------------------------------------------\n // Ownership + mount / unmount lifecycle.\n //\n // The ownership ref is claimed HERE (a layout effect), not in the render/memo,\n // so acquire and release are perfectly paired: a memo re-run can no longer\n // double-count (R3) and an uncommitted render can no longer leak (R4). It\n // MUST stay a *layout* effect: the render-time `acquire` above schedules a\n // microtask sweep that disposes entries still unowned when it runs. Layout\n // effects flush before that microtask drains; passive effects do not, so\n // moving this acquire to a passive effect would let the sweep dispose live\n // mounts.\n //\n // Keyed on [BlocClass, instanceKey, consumerId] for the same reason the\n // subscription is — see the `BlocClass`-in-deps hazard documented above.\n //\n // `acquire` returns the authoritative LIVE instance for the key. onMount /\n // onUnmount fire with that owned live instance and stay co-located with\n // acquire/release so onUnmount(bloc) runs BEFORE release(...) within one\n // cleanup, keeping the instance alive while the callback runs.\n // ---------------------------------------------------------------------------\n useLayoutEffect(() => {\n const live = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: true,\n refId: primaryRefId(consumerId),\n args: resolveEffectiveArgs(),\n }) as TBloc;\n ownedBlocRef.current = live;\n onMountRef.current?.(live as InstanceType<T>);\n // Rebind if the render captured a stale (disposed/replaced) instance so the\n // component renders + subscribes against the live registry entry, not a\n // disposed one. Only bumps on an actual mismatch, so it can fire at most\n // once per handoff and never loops (the re-ensured `bloc` equals `live`,\n // and this effect is not keyed on `bloc` so it won't re-run and re-release).\n if (live !== bloc) {\n rebindNonceRef.current++;\n consumer.bump();\n }\n return () => {\n onUnmountRef.current?.((ownedBlocRef.current ?? bloc) as InstanceType<T>);\n registry.release(BlocClass, instanceKey, false, primaryRefId(consumerId));\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, instanceKey, consumerId, registry]);\n\n // ---------------------------------------------------------------------------\n // Snapshot\n //\n // - Auto-track: wrap state in trackRender, record paths into consumer.paths,\n // and re-register with the container so the skeleton picks up new interest.\n // - Select-mode: return state directly; the subscription callback compares\n // selections to decide whether to re-render.\n // ---------------------------------------------------------------------------\n const container = consumer.container;\n const rawState = container.state as ExtractState<T>;\n const select = selectRef.current;\n consumer.isSelectMode = select !== undefined;\n let state: ExtractState<T>;\n if (select !== undefined) {\n state = rawState;\n // Seed the last selection on the first render so we don't fire an\n // immediate \"different from null\" wakeup on the first emit.\n if (consumer.selection === null) {\n consumer.selection = select(rawState, bloc as InstanceState<T>);\n }\n // Select-mode wakes on every change and filters in the callback.\n consumer.interest = ALL_PATHS;\n } else {\n const tracked = trackRender(\n rawState,\n container.interner,\n proxyCacheRef.current,\n );\n state = tracked.value as ExtractState<T>;\n trackedStateRef.current = tracked.value;\n consumer.paths = tracked.paths;\n // Rebuild the per-consumer session for this render. The primary bloc is the\n // first uniform entry; its `paths` are the SAME PathSet object the proxy\n // mutates during JSX (so it stays live as getters record leaves). Dep\n // entries are appended during JSX as `this.<handle>.track()` runs. Cleared\n // here (not in the layout effect) so a render that no longer tracks a dep\n // produces a session without it, and the reconcile drops it.\n const session = consumer.session;\n session.clear();\n session.set(container, { kind: 'primary', paths: tracked.paths });\n // Freeze this render's tracking proxy after the synchronous render+commit\n // pass. The microtask fires only once the current task unwinds, so all\n // render-time JSX reads still record; reads afterwards — effects, event\n // handlers, async callbacks, devtools inspecting `state` — hit the\n // disarmed proxy and record nothing, so this render's path set can't be\n // polluted by work that outlives the render that owns it.\n queueMicrotask(tracked.disarm);\n // NOTE: registerConsumerPaths is intentionally NOT called here. The\n // proxy hasn't been accessed yet, so `tracked.paths` is an empty Set\n // that the proxy will mutate during JSX evaluation. Registering at\n // this point would store an empty interest with the container and\n // freeze the skeleton at that snapshot — subsequent emits would\n // diff against an empty skeleton and silently drop wakeups. The\n // useLayoutEffect below registers the populated set after render.\n }\n\n // After the render commits, consumer.paths is the consumer's actual leaf\n // interest (populated by the proxy during JSX evaluation). Re-register with\n // the container so the skeleton reflects the latest paths, and expand the\n // interest to include ancestor paths for the channel subscription.\n //\n // useLayoutEffect runs before the browser paints (and before any emit\n // triggered by another effect), so the skeleton is fresh by the time the\n // next emit fires.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n // Clear the render-time tracking proxy now that JSX has been evaluated and\n // committed. Getters invoked after this point (event handlers, effects,\n // method→getter chains) fall through to live state instead of reading this\n // render's frozen snapshot. The render body re-seeds it next render.\n trackedStateRef.current = null;\n if (consumer.isSelectMode) {\n // Switching into (or staying in) select-mode: invalidate any prior full\n // reconcile so a later switch back to auto-track mode never mistakes a\n // stale signature for \"unchanged\" and skips a needed reconcile.\n consumer.lastReconcile = null;\n return;\n }\n const primary = consumer.container;\n const paths = consumer.paths;\n const session = consumer.session;\n\n // ---------------------------------------------------------------------\n // Short-circuit: if the primary path set AND the full dep session are\n // set-equal (paths + key/refId/args) to the last FULL reconcile, none of\n // registerConsumerPaths/subscribe/unsubscribe/expandWithAncestors below\n // can have anything new to do — skip the whole block. Any mismatch, or\n // `consumer.lastReconcile === null` (first commit, or the immediately\n // preceding commit was select-mode / uncertain), falls through to the\n // full reconcile. Never skip on uncertainty — a missed re-subscribe would\n // leave a stale/dropped subscription.\n // ---------------------------------------------------------------------\n const last = consumer.lastReconcile;\n if (\n last !== null &&\n last.primaryContainer === primary &&\n pathSetEquals(last.primaryPaths, paths)\n ) {\n let unchanged = last.deps.size === session.size - 1;\n if (unchanged) {\n for (const [depContainer, entry] of session) {\n if (entry.kind === 'primary') continue;\n const prevEntry = last.deps.get(depContainer);\n if (\n prevEntry === undefined ||\n prevEntry.key !== entry.key ||\n prevEntry.refId !== entry.refId ||\n !Object.is(prevEntry.args, entry.args) ||\n !pathSetEquals(prevEntry.paths, entry.paths)\n ) {\n unchanged = false;\n break;\n }\n }\n }\n if (unchanged) return;\n }\n\n primary.registerConsumerPaths(consumerId, paths);\n // Register the *normal* leaf paths above for the source-side skeleton, then\n // build the channel interest as leaves + ancestor-watch ids so that an\n // atomic `patch` replacement of a parent (e.g. the array 'items') wakes a\n // consumer that tracked a child (e.g. 'items.length').\n consumer.interest = expandWithAncestors(paths, primary.interner);\n\n // -----------------------------------------------------------------------\n // Reconcile DEP containers (cross-bloc `.track()` interest).\n //\n // The primary bloc keeps its own dedicated subscription above; this block\n // manages only the *dep* containers recorded in the session this render.\n // We diff the new dep set vs the previously-subscribed set:\n // - new dep -> acquire was already done in `.track()`; subscribe its\n // channel + registerConsumerPaths + seed interest.\n // - surviving -> refresh its interest ref (subscribe closure reads it).\n // - dropped -> unsubscribe, unregisterConsumer, and release its ref.\n // -----------------------------------------------------------------------\n const subs = consumer.depSubs;\n\n // Pass 1: drop containers no longer in the session.\n for (const [depContainer, sub] of subs) {\n if (!session.has(depContainer)) {\n sub.unsubscribe();\n depContainer.unregisterConsumer(consumerId);\n registry.release(sub.Type, sub.key, false, sub.refId);\n subs.delete(depContainer);\n }\n }\n\n // Pass 2: add/refresh containers in the session (skip the primary).\n for (const [depContainer, entry] of session) {\n if (entry.kind === 'primary') continue;\n const interest = expandWithAncestors(entry.paths, depContainer.interner);\n depContainer.registerConsumerPaths(consumerId, entry.paths);\n const existing = subs.get(depContainer);\n if (existing) {\n existing.interestRef.current = interest;\n continue;\n }\n // First commit that sees this dep: take the ownership ref HERE (not in\n // render/`.track()`), so an uncommitted render can never leak it (R4).\n registry.acquire(entry.Type, entry.key, {\n canCreate: true,\n countRef: true,\n refId: entry.refId,\n args: entry.args,\n });\n const interestRef: { current: PathSet } = { current: interest };\n const unsubscribe = depContainer.channel.subscribe(\n () => interestRef.current,\n () => consumer.bump(),\n );\n subs.set(depContainer, {\n unsubscribe,\n interestRef,\n Type: entry.Type,\n key: entry.key,\n refId: entry.refId,\n args: entry.args,\n });\n }\n\n // Capture this full reconcile's shape for the NEXT commit's short-circuit\n // check above. `paths`/`entry.paths` are fresh Sets seeded this render\n // (trackRender/unionPaths always allocate new Sets, never mutate one from\n // a prior render) — safe to keep direct references without cloning.\n const depsSignature = new Map<StateContainer, ReconcileDepSignature>();\n for (const [depContainer, entry] of session) {\n if (entry.kind === 'primary') continue;\n depsSignature.set(depContainer, {\n paths: entry.paths,\n key: entry.key,\n refId: entry.refId,\n args: entry.args,\n });\n }\n consumer.lastReconcile = {\n primaryContainer: primary,\n primaryPaths: paths,\n deps: depsSignature,\n };\n });\n\n // Unmount: tear down every dep subscription + ref exactly once. Kept in its\n // own effect (consumerId is stable for the component's lifetime, so this only\n // runs on final unmount, not on every reconcile). consumer.depSubs is mutated\n // in place by the reconcile, so the captured Map reference still holds the\n // live set at unmount.\n useEffect(() => {\n const subs = consumer.depSubs;\n return () => {\n for (const [depContainer, sub] of subs) {\n sub.unsubscribe();\n depContainer.unregisterConsumer(consumerId);\n registry.release(sub.Type, sub.key, false, sub.refId);\n }\n subs.clear();\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [consumerId, registry]);\n\n return [state, trackedBloc] as UseBlocReturn<T, ExtractState<T>>;\n}\n\n// ---------------------------------------------------------------------------\n// Per-consumer store object.\n// ---------------------------------------------------------------------------\n\nconst noop = (): void => {};\n\n/**\n * Everything one `useBloc` call keeps between renders, re-created whenever the\n * hook re-keys on `[BlocClass, instanceKey]`.\n *\n * `version` is the `useSyncExternalStore` snapshot: a plain number, so\n * `getSnapshot` is stable and never allocates. `bump` increments it BEFORE\n * calling `notify` — uSES requires `getSnapshot()` to already reflect the\n * change by the time it is notified, and the channel delivers deferred, so\n * doing it the other way round silently drops renders.\n */\ninterface Consumer {\n /** The live container this consumer currently reads and subscribes to.\n * Written by the render memo, which is its single writer. */\n container: StateContainer;\n version: number;\n /** uSES's wake callback; `noop` while unsubscribed. */\n notify: () => void;\n bump: () => void;\n getSnapshot: () => number;\n getServerSnapshot: () => number;\n /** Channel interest: expanded leaf paths, or ALL_PATHS in select-mode. */\n interest: PathSet;\n /** Raw tracked leaf paths from the latest render (skeleton registration). */\n paths: PathSet;\n isSelectMode: boolean;\n /** Last select-mode result, compared before waking. */\n selection: unknown[] | null;\n session: Map<StateContainer, SessionEntry>;\n depSubs: Map<StateContainer, DepSub>;\n lastReconcile: ReconcileSignature | null;\n}\n\nfunction createConsumer(): Consumer {\n const consumer: Consumer = {\n // Assigned by the render memo before any read; never observed unset.\n container: null as unknown as StateContainer,\n version: 0,\n notify: noop,\n bump: () => {\n consumer.version++;\n consumer.notify();\n },\n getSnapshot: () => consumer.version,\n getServerSnapshot: () => consumer.version,\n interest: emptyPathSet(),\n paths: emptyPathSet(),\n isSelectMode: false,\n selection: null,\n session: new Map(),\n depSubs: new Map(),\n lastReconcile: null,\n };\n return consumer;\n}\n\n// ---------------------------------------------------------------------------\n// Cross-bloc session types + dep-handle wrapper.\n// ---------------------------------------------------------------------------\n\n/**\n * One entry in a consumer's per-render session map. Discriminated on `kind`:\n * the primary bloc is managed by its own dedicated subscription, while dep\n * entries carry the registry coordinates the reconcile needs to release their\n * ref.\n */\ntype SessionEntry =\n | {\n kind: 'primary';\n /** Tracked leaf paths recorded against the primary this render. */\n paths: PathSet;\n }\n | {\n kind: 'dep';\n /** Tracked leaf paths recorded against this dep this render. */\n paths: PathSet;\n /** Constructor for registry release. */\n Type: StateContainerConstructor;\n /** Resolved instance key for registry release. */\n key: string;\n /** refId held for this dep (released on drop/unmount). */\n refId: string;\n /** Construction args, used by the reconcile pass to acquire the ref. */\n args: unknown;\n };\n\n/** A live dep-channel subscription tracked between renders for reconciliation. */\ninterface DepSub {\n unsubscribe: () => void;\n interestRef: { current: PathSet };\n Type: StateContainerConstructor;\n key: string;\n refId: string;\n args: unknown;\n}\n\n/**\n * Snapshot of one dep's shape from the last FULL reconcile, compared against\n * the current session entry to decide whether the dep-reconcile layout effect\n * can short-circuit (see `Consumer.lastReconcile`).\n */\ninterface ReconcileDepSignature {\n paths: PathSet;\n key: string;\n refId: string;\n args: unknown;\n}\n\n/** Snapshot of the last FULL dep-reconcile layout effect run. */\ninterface ReconcileSignature {\n /** The primary container this signature was captured against (identity\n * check — a rebind/re-key swaps this even if the tracked paths happen to\n * be textually identical, and must never be mistaken for \"unchanged\"). */\n primaryContainer: StateContainer;\n primaryPaths: PathSet;\n deps: Map<StateContainer, ReconcileDepSignature>;\n}\n\n/** Per-access options shared by both dep accessors. */\ninterface DepAccessOptionsLike {\n args?: unknown;\n}\n\n/** Structural shape of a branded `depend()` handle as seen from React. */\ninterface DepHandleLike {\n track(options?: DepAccessOptionsLike): [unknown, StateContainer];\n untracked(options?: DepAccessOptionsLike): StateContainer;\n readonly [DEP_BRAND]: {\n Type: StateContainerConstructor;\n defaultArgs?: unknown;\n };\n}\n\n/**\n * Build the per-consumer wrapper that replaces a branded dep handle inside a\n * tracked getter's `this`. The wrapper exposes the same accessors as the core\n * handle and overrides `.track()`:\n *\n * - **Inside a render** (`trackedStateRef.current != null`): resolve (ENSURE,\n * no ref) the dep, `trackRender` its state, merge the recorded paths into the\n * session entry, build/reuse a tracked proxy for the dep so its OWN getters\n * track too, and return `[trackedValue, depProxy]`. The ownership ref is taken\n * by the layout-effect reconcile pass, not here.\n * - **Outside a render**: degrade to live `[dep.state, dep]` — matches the core\n * base impl, safe in event handlers/effects/methods.\n *\n * `.untracked()` always returns the live instance with no subscription.\n *\n * Args resolve at call time (`options.args ?? defaultArgs`), so a single handle\n * can resolve different dep instances across calls; tracked-proxy state is\n * therefore cached per resolved instance, not per handle. Guards against a\n * container re-entering tracking within the same render (mutual A↔B deps): if\n * the dep already has a non-primary session entry this render, reuse its proxy\n * + union its paths instead of re-acquiring.\n */\nfunction makeDepWrapper(\n handle: DepHandleLike,\n consumerId: string,\n registry: StateContainerRegistry,\n trackedStateRef: { current: unknown },\n consumer: Consumer,\n onDepHandle: (handle: object) => unknown,\n): DepHandleLike {\n const brand = handle[DEP_BRAND];\n const refId = depRefId(consumerId);\n // Per-resolved-instance tracked-state ref + proxy. Call-time args mean one\n // handle can resolve several instances, so cache is keyed by the instance.\n const perDep = new Map<\n StateContainer,\n { ref: { current: unknown }; proxy: StateContainer }\n >();\n // One ProxyCache shared across every instance this handle resolves to\n // (call-time args can resolve different dep instances across calls) — safe\n // because ProxyCache's internal map is keyed by target object identity, so\n // unrelated instances' objects never collide in it.\n const proxyCache = new ProxyCache();\n\n const resolve = (options?: DepAccessOptionsLike) => {\n const args = options?.args ?? brand.defaultArgs;\n const key = registry.resolveKey(brand.Type, undefined, args);\n const dep = registry.ensure(\n brand.Type,\n key,\n args,\n ) as unknown as StateContainer;\n return { dep, key, args };\n };\n\n const wrapper = {\n untracked: (options?: DepAccessOptionsLike) => resolve(options).dep,\n track: (options?: DepAccessOptionsLike) => {\n const { dep, key, args } = resolve(options);\n\n // Outside a render: live values, no subscription (core base behavior).\n if (trackedStateRef.current == null) {\n return [dep.state, dep];\n }\n\n const session = consumer.session;\n const existing = session.get(dep);\n\n // Render only ENSUREs the dep instance (via `resolve()` above); it does\n // NOT take a ref. Ownership is claimed by the layout-effect reconcile\n // pass-2 the first commit it sees this dep, and released on drop/unmount.\n // This keeps acquire/release paired so an uncommitted render can't leak a\n // dep ref (R4).\n\n const tracked = trackRender(dep.state, dep.interner, proxyCache);\n let cache = perDep.get(dep);\n if (cache === undefined) {\n const ref = { current: tracked.value as unknown };\n cache = { ref, proxy: buildTrackedProxy(dep, ref, onDepHandle).proxy };\n perDep.set(dep, cache);\n } else {\n cache.ref.current = tracked.value;\n }\n\n if (existing !== undefined) {\n // Re-entry this render (`.track()` twice, or a mutual cycle): union the\n // new paths into the existing entry rather than re-acquiring.\n existing.paths = unionPaths(existing.paths, tracked.paths);\n } else {\n session.set(dep, {\n kind: 'dep',\n paths: tracked.paths,\n Type: brand.Type,\n key,\n refId,\n args,\n });\n }\n\n return [tracked.value, cache.proxy];\n },\n } as DepHandleLike;\n\n Object.defineProperty(wrapper, DEP_BRAND, {\n value: brand,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n return wrapper;\n}\n\n/** Union two PathSets (ALL_PATHS dominates). */\nfunction unionPaths(a: PathSet, b: PathSet): PathSet {\n if (a === ALL_PATHS || b === ALL_PATHS) return ALL_PATHS;\n const out = new Set<number>(a as Set<number>);\n for (const id of b as Set<number>) out.add(id);\n return out;\n}\n\nconst shallowArrayEqual = (a: unknown[], b: unknown[]): boolean => {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return false;\n }\n return true;\n};\n\n/**\n * Expand a PathSet to include an *ancestor-watch* id for every ancestor of\n * every tracked leaf.\n *\n * The auto-tracker records leaf paths (e.g. `'items.length'`), but\n * `StructuralContainer.patch` can only mark the parent (`'items'`) when it\n * replaces a value atomically (arrays, `null`, primitives — it can't see\n * inside). Without expansion, a subscriber with interest `{'items.length'}`\n * would miss a `patch`-triggered atomic-replacement of `items`.\n *\n * Ancestors are added under the interner's *ancestor-watch* lane\n * (`internAncestor`), NOT as normal ids. The source emits a matching\n * ancestor-watch mark only for paths it replaces atomically — never for a\n * plain-object structural pulse-up. So `{'items.length'}` wakes when the array\n * `items` is replaced, but `{'user.email'}` does NOT wake when a sibling\n * `user.name` changes and pulses `user` up: pulse-up `user` is a normal id and\n * the ancestor-watch `user` only intersects another ancestor-watch `user`.\n *\n * Example: leaf `'a.b.c'` adds ancestor-watch ids for `'a.b'` and `'a'` (but\n * NOT the `''` root — a root change is covered by `ALL_PATHS` from the source,\n * and `''` would wake this consumer on every field change).\n */\nfunction expandWithAncestors(paths: PathSet, interner: PathInterner): PathSet {\n if (paths === ALL_PATHS) return ALL_PATHS;\n const leafPaths = paths as Set<number>;\n if (leafPaths.size === 0) return paths;\n\n const expanded = new Set<number>(leafPaths);\n for (const id of leafPaths) {\n const str = interner.lookup(id);\n // Add all non-root ancestor segments as *ancestor-watch* ids: 'a.b.c' →\n // watch 'a.b' and 'a'. These live in the interner's ancestor lane so they\n // only intersect the source's atomic-replacement marks (`internAncestor`),\n // never a structural pulse-up mark of the same path. That is what lets a\n // descendant-reader (e.g. `items.length`) wake on an array/null replacement\n // without a sibling-leaf reader (`user.email`) waking when a sibling\n // (`user.name`) changes and pulses up through `user`.\n let idx = str.lastIndexOf('.');\n while (idx > 0) {\n const ancestor = str.slice(0, idx);\n expanded.add(interner.internAncestor(ancestor));\n idx = ancestor.lastIndexOf('.');\n }\n }\n return expanded;\n}\n","import { useEffect, useId, useRef } from 'react';\nimport { APPLY_DEPS, REMOVE_DEPS_OWNER, type StateContainer } from '@blac/core';\n\n/**\n * Feed live values from the component into a bloc's `deps` lane.\n *\n * Each calling component is one owner: its slice is shallow-merged into the\n * bloc's merged `deps` view, and withdrawn when the component unmounts. The\n * bloc observes changes through `onDepsChanged`.\n *\n * ```tsx\n * const [state, bloc] = useBloc(CanvasCubit);\n * useBlocDeps(bloc, { canvas, onTick });\n * ```\n *\n * Use this for values a bloc must *read* but should not own — DOM handles,\n * callbacks, values from other hooks. Pass identity-stable values (`useMemo` /\n * `useCallback`) as you would to any hook; a new function identity each render\n * counts as a change.\n */\nexport function useBlocDeps<D extends object>(\n bloc: StateContainer<any, any, D>,\n slice: Partial<D>,\n): void {\n const ownerId = useId();\n const latest = useRef(slice);\n latest.current = slice;\n\n // `APPLY_DEPS` shallow-compares and no-ops on an unchanged slice, so applying\n // on every commit is cheaper than asking callers to maintain a dep array.\n useEffect(() => {\n bloc[APPLY_DEPS](ownerId, latest.current);\n });\n\n useEffect(() => {\n return () => {\n bloc[REMOVE_DEPS_OWNER](ownerId);\n };\n }, [bloc, ownerId]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["WITH_TRACKED_STATE","DEP_BRAND","ALL_PATHS","ProxyCache","DEP_BRAND","APPLY_DEPS","REMOVE_DEPS_OWNER"],"sources":["../src/BlocProvider.tsx","../src/RegistryProvider.tsx","../src/buildTrackedProxy.ts","../src/useBloc.ts","../src/useBlocDeps.ts"],"sourcesContent":["import {\n createContext,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { resolveInstanceKey } from '@blac/core';\nimport type { ExtractArgs, StateContainerConstructor } from '@blac/core';\n\n/**\n * Internal context value: a Map from bloc constructor to the args object\n * provided by the nearest BlocProvider for that bloc class.\n *\n * Stored as a WeakMap-keyed record so nested providers for different blocs\n * compose without clobbering each other.\n */\ntype ProvidedArgsMap = Map<StateContainerConstructor, unknown>;\n\nconst ProvidedArgsContext = createContext<ProvidedArgsMap>(new Map());\n\n/**\n * Props for {@link BlocProvider}.\n * @public\n */\nexport interface BlocProviderProps<T extends StateContainerConstructor> {\n /**\n * The bloc class whose args are being provided to descendants.\n */\n bloc: T;\n /**\n * Args that descendant `useBloc(bloc)` calls will resolve to when no\n * own `args` are given.\n */\n args: ExtractArgs<T>;\n children: ReactNode;\n}\n\n/**\n * Provides args to descendant `useBloc` calls for a specific bloc class via\n * React context.\n *\n * Descendants calling `useBloc(Bloc)` without their own `args` resolve to the\n * args supplied here. Own `args` on the `useBloc` call always win.\n *\n * Multiple `BlocProvider` wrappers for different bloc classes compose: each\n * provider merges its entry into the inherited map, so nested providers for\n * different blocs do not interfere.\n *\n * @example\n * ```tsx\n * <BlocProvider bloc={UserBloc} args={{ userId: 'alice' }}>\n * <UserProfile />\n * </BlocProvider>\n * ```\n *\n * @example Per-mount private instance\n * ```tsx\n * const id = useId();\n * <BlocProvider bloc={CartBloc} args={{ _id: id }}>\n * <CartWidget />\n * </BlocProvider>\n * ```\n * @public\n */\nexport function BlocProvider<T extends StateContainerConstructor>({\n bloc,\n args,\n children,\n}: BlocProviderProps<T>): ReactElement {\n const parentMap = useContext(ProvidedArgsContext);\n\n // Keyed on the resolved instance key, not `args` identity, so an inline\n // `args={{ ... }}` literal does not rebuild the map on every render and\n // re-render every consumer below.\n const argsKey = resolveInstanceKey(bloc, args);\n\n // Merge our entry into a new Map so sibling/parent providers for other blocs\n // are preserved.\n const mergedMap = useMemo(() => {\n const next = new Map(parentMap);\n next.set(bloc, args);\n return next;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parentMap, bloc, argsKey]);\n\n return (\n <ProvidedArgsContext.Provider value={mergedMap}>\n {children}\n </ProvidedArgsContext.Provider>\n );\n}\n\n/**\n * Returns the args provided by the nearest {@link BlocProvider} for the given\n * bloc class, or `undefined` when called outside a matching provider.\n *\n * Used by `useBloc` to inherit provider args when no own `args` are given.\n * @public\n */\nexport function useProvidedArgs<T extends StateContainerConstructor>(\n BlocClass: T,\n): ExtractArgs<T> | undefined {\n const map = useContext(ProvidedArgsContext);\n return map.get(BlocClass) as ExtractArgs<T> | undefined;\n}\n","import { createContext, type ReactElement, type ReactNode } from 'react';\nimport type { StateContainerRegistry } from '@blac/core';\n\n/**\n * Internal context value: a registry to use in place of the module-global\n * default (`getRegistry()`), or `null` when no provider is present.\n */\nexport const RegistryContext = createContext<StateContainerRegistry | null>(\n null,\n);\n\n/**\n * Props for {@link RegistryProvider}.\n * @public\n */\nexport interface RegistryProviderProps {\n registry: StateContainerRegistry;\n children: ReactNode;\n}\n\n/**\n * Scopes descendant `useBloc` calls to a specific registry instead of the\n * module-global default.\n *\n * Useful for test isolation, SSR isolation, and micro-frontends that must\n * not share bloc instances with the host page.\n *\n * @example\n * ```tsx\n * <RegistryProvider registry={new StateContainerRegistry()}>\n * <App />\n * </RegistryProvider>\n * ```\n * @public\n */\nexport function RegistryProvider({\n registry,\n children,\n}: RegistryProviderProps): ReactElement {\n return (\n <RegistryContext.Provider value={registry}>\n {children}\n </RegistryContext.Provider>\n );\n}\n","import { DEP_BRAND, WITH_TRACKED_STATE } from '@blac/core';\n\ninterface TrackedStateTarget {\n [WITH_TRACKED_STATE]<R>(\n tracked: unknown,\n fn: () => R,\n onDepHandle?: (handle: object) => unknown,\n ): R;\n}\n\nfunction supportsTrackedState(value: object): value is TrackedStateTarget {\n return (\n typeof (value as TrackedStateTarget)[WITH_TRACKED_STATE] === 'function'\n );\n}\n\ntype Getter = (this: object) => unknown;\n\n// Prototype getters per class prototype. The chain is static, so walking it\n// once per class (not once per mounted component) keeps the get trap O(1).\nconst gettersByProto = new WeakMap<object, Map<string | symbol, Getter>>();\n\nfunction collectGetters(proto: object): Map<string | symbol, Getter> {\n const cached = gettersByProto.get(proto);\n if (cached !== undefined) return cached;\n const getters = new Map<string | symbol, Getter>();\n let p: object | null = proto;\n while (p !== null && p !== Object.prototype) {\n for (const key of Reflect.ownKeys(p)) {\n const desc = Object.getOwnPropertyDescriptor(p, key);\n // A descriptor getter, always `.call`ed with an explicit receiver.\n // oxlint-disable-next-line typescript/unbound-method\n if (desc?.get && !getters.has(key)) getters.set(key, desc.get);\n }\n p = Object.getPrototypeOf(p);\n }\n gettersByProto.set(proto, getters);\n return getters;\n}\n\n/**\n * Build a per-consumer proxy for a bloc instance.\n *\n * Getters run with `this` bound to the **real instance**, not a proxy, so ES\n * `#private` fields and methods work in user blocs. Path recording is done by\n * `[WITH_TRACKED_STATE]`, which makes the instance's own `state` getter report\n * the render's tracking proxy for the duration of the call; nested getter\n * chains stay tracked because the override is still set.\n *\n * One allocation per bloc acquisition (inside `useMemo`), so the proxy is\n * stable across renders.\n *\n * @param onDepHandle - Optional callback invoked when a read returns a branded\n * dep handle. Receives the original handle and returns the value to expose in\n * its place (the session-bound wrapper). The callback is responsible for\n * caching wrappers per handle to avoid re-allocation.\n */\nexport function buildTrackedProxy<T extends object>(\n instance: T,\n trackedStateRef: { current: unknown },\n onDepHandle?: (handle: object) => unknown,\n): { proxy: T } {\n // Both string- and symbol-keyed prototype getters (excluding\n // Object.prototype). Arrow-function class properties are own values, not\n // getters, and pass through unmodified.\n const getters = collectGetters(Object.getPrototypeOf(instance) as object);\n const tracksState = supportsTrackedState(instance as object);\n\n // Bound methods are cached so `bloc.method` keeps a stable identity across\n // reads — an unstable one would defeat memoisation in consumers.\n const boundMethods = new Map<string | symbol, unknown>();\n\n const wrapDepHandle = (value: unknown): unknown => {\n if (\n onDepHandle !== undefined &&\n (typeof value === 'function' || typeof value === 'object') &&\n value !== null &&\n (value as Record<symbol, unknown>)[DEP_BRAND] !== undefined\n ) {\n return onDepHandle(value as object);\n }\n return value;\n };\n\n const proxy = new Proxy(instance as object, {\n get(target, key) {\n const getter = getters.get(key);\n if (getter !== undefined) {\n const tracked = trackedStateRef.current;\n // `[WITH_TRACKED_STATE]` invokes the getter with `this` = the real\n // instance, so no per-read thunk is needed.\n return wrapDepHandle(\n tracked == null || !tracksState\n ? getter.call(target)\n : (target as TrackedStateTarget)[WITH_TRACKED_STATE](\n tracked,\n getter,\n onDepHandle,\n ),\n );\n }\n\n // Receiver is the real instance, so own fields keep their `#private`\n // brand. Methods are bound to it too, otherwise `bloc.method()` would\n // call with `this` = proxy and fail the same brand check.\n const value = Reflect.get(target, key, target);\n if (typeof value === 'function') {\n let bound = boundMethods.get(key);\n if (bound === undefined) {\n bound = (value as (...a: unknown[]) => unknown).bind(target);\n boundMethods.set(key, bound);\n }\n return wrapDepHandle(bound);\n }\n return wrapDepHandle(value);\n },\n }) as T;\n\n return { proxy };\n}\n","import {\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport {\n DEP_BRAND,\n getRegistry,\n resolveInstanceKey,\n type ExtractArgs,\n type ExtractState,\n type InstanceState,\n type StateContainer,\n type StateContainerConstructor,\n type StateContainerRegistry,\n} from '@blac/core';\nimport {\n ALL_PATHS,\n emptyPathSet,\n pathSetEquals,\n trackRender,\n PathInterner,\n ProxyCache,\n type PathSet,\n} from '@dirtytalk/structural';\nimport { useProvidedArgs } from './BlocProvider';\nimport { RegistryContext } from './RegistryProvider';\nimport { buildTrackedProxy } from './buildTrackedProxy';\nimport type { UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\n\n// Sentinel that can never `Object.is`-equal a real args value (including\n// `undefined`). Used to lazily seed args-key caches so the structural key is\n// computed only when the guard actually runs, never on every render.\nconst ARGS_UNSET: unique symbol = Symbol('blac.argsKeyUnset');\n\n/**\n * React hook that connects a component to a state container with automatic\n * re-render on state changes.\n *\n * Two tracking modes:\n * - **Auto-tracking** (default): the returned state value is a proxy that\n * records read paths during render. The component re-renders when any\n * recorded path changes. Backed by `@dirtytalk/structural`'s\n * `trackRender` + the container's path-scoped `DirtyChannel`.\n * - **Manual select**: pass `options.select` to opt out of auto-tracking.\n * The hook re-renders only when the returned array's elements change\n * (per-index `Object.is`).\n *\n * Lifecycle:\n * - The bloc is acquired from the registry on mount and released on\n * unmount. The instance key is derived from `options.args` (own args),\n * then the surrounding {@link BlocProvider} context args for this bloc,\n * then the default key (no args).\n * - `options.onMount` fires after the bloc is acquired; `options.onUnmount`\n * fires *before* the registry releases its ref, so the bloc is still\n * alive when the callback runs.\n *\n * Per-mount private instance:\n * ```ts\n * const id = useId();\n * const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });\n * ```\n *\n * @typeParam T - The state container constructor type (inferred from BlocClass)\n * @param BlocClass - The state container class to connect to\n * @param options - Configuration options\n * @returns Tuple of `[state, bloc, ref]`\n *\n * @example Basic usage\n * ```ts\n * const [state, bloc] = useBloc(MyBloc);\n * ```\n *\n * @example Manual select\n * ```ts\n * const [state, bloc] = useBloc(MyBloc, {\n * select: (state) => [state.count],\n * });\n * ```\n *\n * @example Args-based shared instance\n * ```ts\n * const [state, bloc] = useBloc(UserBloc, { args: { userId: 'alice' } });\n * ```\n * @public\n */\nexport function useBloc<\n T extends StateContainerConstructor = StateContainerConstructor,\n>(\n BlocClass: T,\n options?: UseBlocOptions<T>,\n): UseBlocReturn<T, ExtractState<T>> {\n type TBloc = InstanceState<T>;\n\n // Registry scoping: the nearest RegistryProvider wins over the module-global\n // default. Resolved once here (top level, per React's rules of hooks) and\n // closed over by every effect/memo below instead of each calling\n // `getRegistry()` directly.\n const registry = useContext(RegistryContext) ?? getRegistry();\n\n // Everything this hook instance keeps between renders lives on one object\n // (see `Consumer`) rather than in a ref per field: fewer hook slots per\n // mount, and the effects below read the latest values off it directly.\n const consumerRef = useRef<Consumer | null>(null);\n const consumer = (consumerRef.current ??= createConsumer());\n const consumerId = consumer.id;\n\n consumer.select = options?.select;\n consumer.onMount = options?.onMount;\n consumer.onUnmount = options?.onUnmount;\n\n // ---------------------------------------------------------------------------\n // Identity resolution\n //\n // Priority: own args > provider args (for this bloc class) > none.\n //\n // Args are user-supplied; callers commonly pass a fresh object literal each\n // render. Memoising on `args` directly would bust every render. We compute a\n // structural key (JSON.stringify) for the useMemo dep instead — undefined\n // args (void-args blocs) collapse to an undefined key. The key is only\n // recomputed when the args REFERENCE changes, so a stable args object costs\n // nothing per render.\n // ---------------------------------------------------------------------------\n const ownArgs = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n consumer.ownArgs = ownArgs;\n if (!Object.is(consumer.ownArgsKeyFor, ownArgs)) {\n consumer.ownArgsKeyFor = ownArgs;\n consumer.ownArgsKey =\n ownArgs === undefined ? undefined : JSON.stringify(ownArgs);\n }\n const ownArgsKey = consumer.ownArgsKey;\n\n // Read provided args from the nearest BlocProvider for this bloc class.\n const providerArgs = useProvidedArgs(BlocClass);\n consumer.providerArgs = providerArgs;\n if (!Object.is(consumer.providerArgsKeyFor, providerArgs)) {\n consumer.providerArgsKeyFor = providerArgs;\n consumer.providerArgsKey =\n providerArgs === undefined ? undefined : JSON.stringify(providerArgs);\n }\n const providerArgsKey = consumer.providerArgsKey;\n\n // Rebind nonce: bumped by the ownership layout-effect when the instance the\n // render captured was disposed + recreated out from under us. This happens on\n // a same-commit ownership handoff of a shared (non-keepAlive) key — the sole\n // prior owner's effect cleanup releases refs→0 and SYNCHRONOUSLY disposes the\n // instance before this consumer's layout setup re-acquires (creating a fresh\n // one) — and equivalently under StrictMode's setup→cleanup→setup double-invoke\n // for a lone owner. Threaded into the memo deps so bumping it rebuilds `bloc`\n // and its tracked proxy against the LIVE registry entry: the proxy binds its\n // target at construction and cannot be retargeted in place.\n //\n // Held on the consumer rather than in useReducer state because the re-render\n // it needs is already delivered by the consumer's version bump through uSES.\n const rebindNonce = consumer.rebindNonce;\n\n const { bloc, instanceKey, trackedBloc } = useMemo<{\n bloc: TBloc;\n instanceKey: string;\n trackedBloc: TBloc;\n }>(() => {\n const effectiveArgs = resolveEffectiveArgs(consumer) as\n | ExtractArgs<T>\n | undefined;\n\n const resolvedKey = resolveInstanceKey(BlocClass, effectiveArgs);\n // Render only ENSUREs the instance exists (no ref). Ownership is claimed in\n // the layout effect below, so an abandoned/uncommitted render can never\n // leak a ref and a memo re-run can never double-count one (R3/R4).\n const instance = registry.acquire(BlocClass, resolvedKey, {\n canCreate: true,\n countRef: false,\n args: effectiveArgs,\n // A render that never commits (SSR, a discarded render) leaves this\n // instance ref-less forever; let the registry sweep it if the layout\n // effect below never claims ownership.\n sweepIfUnowned: true,\n }) as TBloc;\n\n // Build a session-bound wrapper for a dep handle the first time a getter\n // reads it off `this`; cache per handle so the wrapper identity is stable.\n // `onDepHandle` is threaded into each dep's tracked proxy too, so a nested\n // `this.<otherHandle>.track()` inside a dep's getter records into the SAME\n // consumer session — that is what makes deep chains (A→B→C) reactive.\n const onDepHandle = (handle: object): unknown => {\n const cache = (consumer.depWrappers ??= new Map());\n const cached = cache.get(handle);\n if (cached !== undefined) return cached;\n const wrapper = makeDepWrapper(\n handle as DepHandleLike,\n registry,\n consumer,\n onDepHandle,\n );\n cache.set(handle, wrapper);\n return wrapper;\n };\n\n const { proxy } = buildTrackedProxy(\n instance as object,\n consumer.tracked,\n onDepHandle,\n );\n\n return {\n bloc: instance,\n instanceKey: resolvedKey,\n trackedBloc: proxy as TBloc,\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, ownArgsKey, providerArgsKey, rebindNonce, registry]);\n\n // The memo is the single writer of the container the consumer reads and\n // subscribes to; a re-key or rebind retargets it here, before any effect runs.\n consumer.container = bloc as unknown as StateContainer;\n\n // ---------------------------------------------------------------------------\n // Channel subscription via useSyncExternalStore.\n //\n // `subscribe` is memoised on [BlocClass, instanceKey] — NOT on `bloc` — so a\n // genuine re-key (args change, OR a BlocClass swap) re-subscribes while a\n // pure instance replacement under the same class+key does not churn the\n // subscription.\n //\n // `BlocClass` MUST stay in the dep array even though `instanceKey` alone\n // often determines identity: `resolveInstanceKey`/`resolveKey` collapse to\n // the same `DEFAULT_STRUCTURAL_KEY` sentinel across DIFFERENT classes when\n // neither has args nor a `static key` (e.g.\n // `useBloc(cond ? AdminBloc : UserBloc)`). Memoising on `bloc` alone would\n // reintroduce that leak: swapping classes at that shared key would never\n // re-subscribe.\n //\n // We talk directly to `bloc.channel` — the StructuralContainer's path-scoped\n // DirtyChannel. Subscribing with a dynamic interest function lets us narrow\n // wakeups per consumer, so a component only re-renders when a path it\n // actually read changes (rather than on every state change).\n // ---------------------------------------------------------------------------\n const subscribe = useMemo(\n () => (onStoreChange: () => void) => {\n consumer.notify = onStoreChange;\n const container = consumer.container;\n const unsubscribe = container.channel.subscribe(\n () => consumer.interest,\n () => {\n if (consumer.isSelectMode) {\n const select = consumer.select;\n if (select) {\n const next = select(\n container.state as ExtractState<T>,\n container as unknown as InstanceState<T>,\n );\n const prev = consumer.selection;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n consumer.selection = next;\n }\n }\n consumer.bump();\n },\n );\n container.registerConsumerPaths(consumerId, consumer.paths);\n // No render→subscribe mount-gap compensation is needed: the channel\n // accumulates marks and flushes on its scheduler, so an emit raised\n // during render is still pending when this subscriber registers and is\n // delivered by that flush. The pre-uSES hook needed `renderStateRef` +\n // a manual force dispatch here only because its subscribe ran in a\n // passive effect keyed on the instance.\n return () => {\n consumer.notify = noop;\n unsubscribe();\n container.unregisterConsumer(consumerId);\n };\n },\n // `rebindNonce` is here for the same reason it is in the memo above: a\n // rebind swaps the underlying instance (and therefore its channel) without\n // changing the class or key, so the subscription must be re-established\n // against the live container.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n [BlocClass, instanceKey, consumerId, rebindNonce],\n );\n\n useSyncExternalStore(\n subscribe,\n consumer.getSnapshot,\n consumer.getServerSnapshot,\n );\n\n // ---------------------------------------------------------------------------\n // Ownership + mount / unmount lifecycle.\n //\n // The ownership ref is claimed HERE (a layout effect), not in the render/memo,\n // so acquire and release are perfectly paired: a memo re-run can no longer\n // double-count (R3) and an uncommitted render can no longer leak (R4). It\n // MUST stay a *layout* effect: the render-time `acquire` above schedules a\n // microtask sweep that disposes entries still unowned when it runs. Layout\n // effects flush before that microtask drains; passive effects do not, so\n // moving this acquire to a passive effect would let the sweep dispose live\n // mounts.\n //\n // Keyed on [BlocClass, instanceKey, consumerId] for the same reason the\n // subscription is — see the `BlocClass`-in-deps hazard documented above.\n //\n // `acquire` returns the authoritative LIVE instance for the key. onMount /\n // onUnmount fire with that owned live instance and stay co-located with\n // acquire/release so onUnmount(bloc) runs BEFORE release(...) within one\n // cleanup, keeping the instance alive while the callback runs.\n // ---------------------------------------------------------------------------\n useLayoutEffect(() => {\n const live = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: true,\n refId: consumer.primaryRefId,\n args: resolveEffectiveArgs(consumer),\n }) as TBloc;\n consumer.ownedBloc = live;\n consumer.onMount?.(live as InstanceType<T>);\n // Rebind if the render captured a stale (disposed/replaced) instance so the\n // component renders + subscribes against the live registry entry, not a\n // disposed one. Only bumps on an actual mismatch, so it can fire at most\n // once per handoff and never loops (the re-ensured `bloc` equals `live`,\n // and this effect is not keyed on `bloc` so it won't re-run and re-release).\n if (live !== bloc) {\n consumer.rebindNonce++;\n consumer.bump();\n }\n return () => {\n consumer.onUnmount?.((consumer.ownedBloc ?? bloc) as InstanceType<T>);\n registry.release(BlocClass, instanceKey, false, consumer.primaryRefId);\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [BlocClass, instanceKey, consumerId, registry]);\n\n // ---------------------------------------------------------------------------\n // Snapshot\n //\n // - Auto-track: wrap state in trackRender, record paths into consumer.paths,\n // and re-register with the container so the skeleton picks up new interest.\n // - Select-mode: return state directly; the subscription callback compares\n // selections to decide whether to re-render.\n // ---------------------------------------------------------------------------\n const container = consumer.container;\n const rawState = container.state as ExtractState<T>;\n const select = consumer.select;\n consumer.isSelectMode = select !== undefined;\n let state: ExtractState<T>;\n if (select !== undefined) {\n state = rawState;\n // Seed the last selection on the first render so we don't fire an\n // immediate \"different from null\" wakeup on the first emit.\n if (consumer.selection === null) {\n consumer.selection = select(rawState, bloc as InstanceState<T>);\n }\n // Select-mode wakes on every change and filters in the callback.\n consumer.interest = ALL_PATHS;\n } else {\n const tracked = trackRender(\n rawState,\n container.interner,\n (consumer.proxyCache ??= new ProxyCache()),\n );\n state = tracked.value as ExtractState<T>;\n consumer.tracked.current = tracked.value;\n consumer.paths = tracked.paths;\n // Frozen by the commit layout effect below, once the synchronous\n // render+commit pass is over: all render-time JSX reads still record;\n // reads afterwards — effects, event handlers, async callbacks, devtools\n // inspecting `state` — hit the disarmed proxy and record nothing, so this\n // render's path set can't be polluted by work that outlives it.\n consumer.disarm = tracked.disarm;\n // Rebuild the per-consumer session for this render. The primary bloc is the\n // first uniform entry; its `paths` are the SAME PathSet object the proxy\n // mutates during JSX (so it stays live as getters record leaves). Dep\n // entries are appended during JSX as `this.<handle>.track()` runs. Cleared\n // here (not in the layout effect) so a render that no longer tracks a dep\n // produces a session without it, and the reconcile drops it.\n const session = consumer.session;\n session.clear();\n session.set(container, { kind: 'primary', paths: tracked.paths });\n // NOTE: registerConsumerPaths is intentionally NOT called here. The\n // proxy hasn't been accessed yet, so `tracked.paths` is an empty Set\n // that the proxy will mutate during JSX evaluation. Registering at\n // this point would store an empty interest with the container and\n // freeze the skeleton at that snapshot — subsequent emits would\n // diff against an empty skeleton and silently drop wakeups. The\n // useLayoutEffect below registers the populated set after render.\n }\n\n // After the render commits, consumer.paths is the consumer's actual leaf\n // interest (populated by the proxy during JSX evaluation). Re-register with\n // the container so the skeleton reflects the latest paths, and expand the\n // interest to include ancestor paths for the channel subscription.\n //\n // useLayoutEffect runs before the browser paints (and before any emit\n // triggered by another effect), so the skeleton is fresh by the time the\n // next emit fires.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n // Clear the render-time tracking proxy now that JSX has been evaluated and\n // committed. Getters invoked after this point (event handlers, effects,\n // method→getter chains) fall through to live state instead of reading this\n // render's frozen snapshot. The render body re-seeds it next render.\n consumer.tracked.current = null;\n const disarm = consumer.disarm;\n if (disarm !== null) {\n consumer.disarm = null;\n disarm();\n }\n if (consumer.isSelectMode) {\n // Switching into (or staying in) select-mode: invalidate any prior full\n // reconcile so a later switch back to auto-track mode never mistakes a\n // stale signature for \"unchanged\" and skips a needed reconcile.\n consumer.lastReconcile = null;\n return;\n }\n const primary = consumer.container;\n const paths = consumer.paths;\n const session = consumer.session;\n\n // ---------------------------------------------------------------------\n // Short-circuit: if the primary path set AND the full dep session are\n // set-equal (paths + key/refId/args) to the last FULL reconcile, none of\n // registerConsumerPaths/subscribe/unsubscribe/expandWithAncestors below\n // can have anything new to do — skip the whole block. Any mismatch, or\n // `consumer.lastReconcile === null` (first commit, or the immediately\n // preceding commit was select-mode / uncertain), falls through to the\n // full reconcile. Never skip on uncertainty — a missed re-subscribe would\n // leave a stale/dropped subscription.\n // ---------------------------------------------------------------------\n const last = consumer.lastReconcile;\n if (\n last !== null &&\n last.primaryContainer === primary &&\n pathSetEquals(last.primaryPaths, paths)\n ) {\n let unchanged = last.deps.size === session.size - 1;\n if (unchanged) {\n for (const [depContainer, entry] of session) {\n if (entry.kind === 'primary') continue;\n const prevEntry = last.deps.get(depContainer);\n if (\n prevEntry === undefined ||\n prevEntry.key !== entry.key ||\n prevEntry.refId !== entry.refId ||\n !Object.is(prevEntry.args, entry.args) ||\n !pathSetEquals(prevEntry.paths, entry.paths)\n ) {\n unchanged = false;\n break;\n }\n }\n }\n if (unchanged) return;\n }\n\n primary.registerConsumerPaths(consumerId, paths);\n // Register the *normal* leaf paths above for the source-side skeleton, then\n // build the channel interest as leaves + ancestor-watch ids so that an\n // atomic `patch` replacement of a parent (e.g. the array 'items') wakes a\n // consumer that tracked a child (e.g. 'items.length').\n consumer.interest = expandWithAncestors(paths, primary.interner);\n\n // -----------------------------------------------------------------------\n // Reconcile DEP containers (cross-bloc `.track()` interest).\n //\n // The primary bloc keeps its own dedicated subscription above; this block\n // manages only the *dep* containers recorded in the session this render.\n // We diff the new dep set vs the previously-subscribed set:\n // - new dep -> acquire was already done in `.track()`; subscribe its\n // channel + registerConsumerPaths + seed interest.\n // - surviving -> refresh its interest ref (subscribe closure reads it).\n // - dropped -> unsubscribe, unregisterConsumer, and release its ref.\n // -----------------------------------------------------------------------\n const subs = consumer.depSubs;\n\n // Pass 1: drop containers no longer in the session.\n for (const [depContainer, sub] of subs) {\n if (!session.has(depContainer)) {\n sub.unsubscribe();\n depContainer.unregisterConsumer(consumerId);\n registry.release(sub.Type, sub.key, false, sub.refId);\n subs.delete(depContainer);\n }\n }\n\n // Pass 2: add/refresh containers in the session (skip the primary).\n for (const [depContainer, entry] of session) {\n if (entry.kind === 'primary') continue;\n const interest = expandWithAncestors(entry.paths, depContainer.interner);\n depContainer.registerConsumerPaths(consumerId, entry.paths);\n const existing = subs.get(depContainer);\n if (existing) {\n existing.interestRef.current = interest;\n continue;\n }\n // First commit that sees this dep: take the ownership ref HERE (not in\n // render/`.track()`), so an uncommitted render can never leak it (R4).\n registry.acquire(entry.Type, entry.key, {\n canCreate: true,\n countRef: true,\n refId: entry.refId,\n args: entry.args,\n });\n const interestRef: { current: PathSet } = { current: interest };\n const unsubscribe = depContainer.channel.subscribe(\n () => interestRef.current,\n () => consumer.bump(),\n );\n subs.set(depContainer, {\n unsubscribe,\n interestRef,\n Type: entry.Type,\n key: entry.key,\n refId: entry.refId,\n args: entry.args,\n });\n }\n\n // Capture this full reconcile's shape for the NEXT commit's short-circuit\n // check above. `paths`/`entry.paths` are fresh Sets seeded this render\n // (trackRender/unionPaths always allocate new Sets, never mutate one from\n // a prior render) — safe to keep direct references without cloning.\n const depsSignature = new Map<StateContainer, ReconcileDepSignature>();\n for (const [depContainer, entry] of session) {\n if (entry.kind === 'primary') continue;\n depsSignature.set(depContainer, {\n paths: entry.paths,\n key: entry.key,\n refId: entry.refId,\n args: entry.args,\n });\n }\n consumer.lastReconcile = {\n primaryContainer: primary,\n primaryPaths: paths,\n deps: depsSignature,\n };\n });\n\n // Unmount: tear down every dep subscription + ref exactly once. Kept in its\n // own effect (consumerId is stable for the component's lifetime, so this only\n // runs on final unmount, not on every reconcile). consumer.depSubs is mutated\n // in place by the reconcile, so the captured Map reference still holds the\n // live set at unmount.\n useEffect(() => {\n const subs = consumer.depSubs;\n return () => {\n for (const [depContainer, sub] of subs) {\n sub.unsubscribe();\n depContainer.unregisterConsumer(consumerId);\n registry.release(sub.Type, sub.key, false, sub.refId);\n }\n subs.clear();\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [consumerId, registry]);\n\n return [state, trackedBloc] as UseBlocReturn<T, ExtractState<T>>;\n}\n\n// ---------------------------------------------------------------------------\n// Per-consumer store object.\n// ---------------------------------------------------------------------------\n\nconst noop = (): void => {};\n\n/**\n * Everything one `useBloc` call keeps between renders; one per mounted hook.\n *\n * `version` is the `useSyncExternalStore` snapshot: a plain number, so\n * `getSnapshot` is stable and never allocates. `bump` increments it BEFORE\n * calling `notify` — uSES requires `getSnapshot()` to already reflect the\n * change by the time it is notified, and the channel delivers deferred, so\n * doing it the other way round silently drops renders.\n */\ninterface Consumer {\n /** Stable id for the structural container's consumer registry. */\n id: string;\n /** Registry refIds for the primary bloc and its tracked deps. Derived once\n * from `id` so the acquire and release sites can never drift apart — a\n * mismatch would leak the ref and keep the bloc alive past unmount. */\n primaryRefId: string;\n depRefId: string;\n /** The live container this consumer currently reads and subscribes to.\n * Written by the render memo, which is its single writer. */\n container: StateContainer;\n version: number;\n /** uSES's wake callback; `noop` while unsubscribed. */\n notify: () => void;\n bump: () => void;\n getSnapshot: () => number;\n getServerSnapshot: () => number;\n /** Channel interest: expanded leaf paths, or ALL_PATHS in select-mode. */\n interest: PathSet;\n /** Raw tracked leaf paths from the latest render (skeleton registration). */\n paths: PathSet;\n isSelectMode: boolean;\n /** Last select-mode result, compared before waking. */\n selection: unknown[] | null;\n session: Map<StateContainer, SessionEntry>;\n depSubs: Map<StateContainer, DepSub>;\n lastReconcile: ReconcileSignature | null;\n /** Latest option callbacks, refreshed every render. Typed loosely: the\n * hook's `T` is per call site, and the consumer outlives any one call. */\n select: ((state: any, bloc: any) => unknown[]) | undefined;\n onMount: ((bloc: any) => void) | undefined;\n onUnmount: ((bloc: any) => void) | undefined;\n /** Latest args plus the structural key cached against their identity. */\n ownArgs: unknown;\n ownArgsKeyFor: unknown;\n ownArgsKey: string | undefined;\n providerArgs: unknown;\n providerArgsKeyFor: unknown;\n providerArgsKey: string | undefined;\n /** Current render's tracking proxy; `null` outside a tracked render. Shared\n * with the bloc's tracked proxy and dep wrappers, which read `.current`. */\n tracked: { current: unknown };\n /** Freezes the current render's proxy tree; run by the commit effect. */\n disarm: (() => void) | null;\n proxyCache: ProxyCache | null;\n /** Dep handle -> session-bound wrapper (see `makeDepWrapper`). */\n depWrappers: Map<object, unknown> | null;\n rebindNonce: number;\n /** Instance actually owned (ref held) by the ownership layout-effect, read\n * by its cleanup so onUnmount always fires with the owned instance. */\n ownedBloc: unknown;\n}\n\nfunction createConsumer(): Consumer {\n const id = `useBloc-${nextConsumerId++}`;\n const consumer: Consumer = {\n id,\n primaryRefId: `useBloc@${id}`,\n depRefId: `useBloc@${id}:dep`,\n // Assigned by the render memo before any read; never observed unset.\n container: null as unknown as StateContainer,\n version: 0,\n notify: noop,\n bump: () => {\n consumer.version++;\n consumer.notify();\n },\n getSnapshot: () => consumer.version,\n getServerSnapshot: () => consumer.version,\n interest: emptyPathSet(),\n paths: emptyPathSet(),\n isSelectMode: false,\n selection: null,\n session: new Map(),\n depSubs: new Map(),\n lastReconcile: null,\n select: undefined,\n onMount: undefined,\n onUnmount: undefined,\n ownArgs: undefined,\n ownArgsKeyFor: ARGS_UNSET,\n ownArgsKey: undefined,\n providerArgs: undefined,\n providerArgsKeyFor: ARGS_UNSET,\n providerArgsKey: undefined,\n tracked: { current: null },\n disarm: null,\n proxyCache: null,\n depWrappers: null,\n rebindNonce: 0,\n ownedBloc: null,\n };\n return consumer;\n}\n\n// Own args win over provider args; provider args win over no args. Read off\n// the consumer (refreshed every render) so the render-time acquire and the\n// layout effect resolve args identically — the effect re-creates the instance\n// when the rendered entry was disposed (StrictMode remount), and dropping args\n// there would run `init(undefined)`.\nconst resolveEffectiveArgs = (consumer: Consumer): unknown =>\n consumer.ownArgs !== undefined ? consumer.ownArgs : consumer.providerArgs;\n\n// ---------------------------------------------------------------------------\n// Cross-bloc session types + dep-handle wrapper.\n// ---------------------------------------------------------------------------\n\n/**\n * One entry in a consumer's per-render session map. Discriminated on `kind`:\n * the primary bloc is managed by its own dedicated subscription, while dep\n * entries carry the registry coordinates the reconcile needs to release their\n * ref.\n */\ntype SessionEntry =\n | {\n kind: 'primary';\n /** Tracked leaf paths recorded against the primary this render. */\n paths: PathSet;\n }\n | {\n kind: 'dep';\n /** Tracked leaf paths recorded against this dep this render. */\n paths: PathSet;\n /** Constructor for registry release. */\n Type: StateContainerConstructor;\n /** Resolved instance key for registry release. */\n key: string;\n /** refId held for this dep (released on drop/unmount). */\n refId: string;\n /** Construction args, used by the reconcile pass to acquire the ref. */\n args: unknown;\n };\n\n/** A live dep-channel subscription tracked between renders for reconciliation. */\ninterface DepSub {\n unsubscribe: () => void;\n interestRef: { current: PathSet };\n Type: StateContainerConstructor;\n key: string;\n refId: string;\n args: unknown;\n}\n\n/**\n * Snapshot of one dep's shape from the last FULL reconcile, compared against\n * the current session entry to decide whether the dep-reconcile layout effect\n * can short-circuit (see `Consumer.lastReconcile`).\n */\ninterface ReconcileDepSignature {\n paths: PathSet;\n key: string;\n refId: string;\n args: unknown;\n}\n\n/** Snapshot of the last FULL dep-reconcile layout effect run. */\ninterface ReconcileSignature {\n /** The primary container this signature was captured against (identity\n * check — a rebind/re-key swaps this even if the tracked paths happen to\n * be textually identical, and must never be mistaken for \"unchanged\"). */\n primaryContainer: StateContainer;\n primaryPaths: PathSet;\n deps: Map<StateContainer, ReconcileDepSignature>;\n}\n\n/** Per-access options shared by both dep accessors. */\ninterface DepAccessOptionsLike {\n args?: unknown;\n}\n\n/** Structural shape of a branded `depend()` handle as seen from React. */\ninterface DepHandleLike {\n track(options?: DepAccessOptionsLike): [unknown, StateContainer];\n untracked(options?: DepAccessOptionsLike): StateContainer;\n readonly [DEP_BRAND]: {\n Type: StateContainerConstructor;\n defaultArgs?: unknown;\n };\n}\n\n/**\n * Build the per-consumer wrapper that replaces a branded dep handle inside a\n * tracked getter's `this`. The wrapper exposes the same accessors as the core\n * handle and overrides `.track()`:\n *\n * - **Inside a render** (`consumer.tracked.current != null`): resolve (ENSURE,\n * no ref) the dep, `trackRender` its state, merge the recorded paths into the\n * session entry, build/reuse a tracked proxy for the dep so its OWN getters\n * track too, and return `[trackedValue, depProxy]`. The ownership ref is taken\n * by the layout-effect reconcile pass, not here.\n * - **Outside a render**: degrade to live `[dep.state, dep]` — matches the core\n * base impl, safe in event handlers/effects/methods.\n *\n * `.untracked()` always returns the live instance with no subscription.\n *\n * Args resolve at call time (`options.args ?? defaultArgs`), so a single handle\n * can resolve different dep instances across calls; tracked-proxy state is\n * therefore cached per resolved instance, not per handle. Guards against a\n * container re-entering tracking within the same render (mutual A↔B deps): if\n * the dep already has a non-primary session entry this render, reuse its proxy\n * + union its paths instead of re-acquiring.\n */\nfunction makeDepWrapper(\n handle: DepHandleLike,\n registry: StateContainerRegistry,\n consumer: Consumer,\n onDepHandle: (handle: object) => unknown,\n): DepHandleLike {\n const brand = handle[DEP_BRAND];\n const refId = consumer.depRefId;\n // Per-resolved-instance tracked-state ref + proxy. Call-time args mean one\n // handle can resolve several instances, so cache is keyed by the instance.\n const perDep = new Map<\n StateContainer,\n { ref: { current: unknown }; proxy: StateContainer }\n >();\n // One ProxyCache shared across every instance this handle resolves to\n // (call-time args can resolve different dep instances across calls) — safe\n // because ProxyCache's internal map is keyed by target object identity, so\n // unrelated instances' objects never collide in it.\n const proxyCache = new ProxyCache();\n\n const resolve = (options?: DepAccessOptionsLike) => {\n const args = options?.args ?? brand.defaultArgs;\n const key = registry.resolveKey(brand.Type, undefined, args);\n const dep = registry.ensure(\n brand.Type,\n key,\n args,\n ) as unknown as StateContainer;\n return { dep, key, args };\n };\n\n const wrapper = {\n untracked: (options?: DepAccessOptionsLike) => resolve(options).dep,\n track: (options?: DepAccessOptionsLike) => {\n const { dep, key, args } = resolve(options);\n\n // Outside a render: live values, no subscription (core base behavior).\n if (consumer.tracked.current == null) {\n return [dep.state, dep];\n }\n\n const session = consumer.session;\n const existing = session.get(dep);\n\n // Render only ENSUREs the dep instance (via `resolve()` above); it does\n // NOT take a ref. Ownership is claimed by the layout-effect reconcile\n // pass-2 the first commit it sees this dep, and released on drop/unmount.\n // This keeps acquire/release paired so an uncommitted render can't leak a\n // dep ref (R4).\n\n const tracked = trackRender(dep.state, dep.interner, proxyCache);\n let cache = perDep.get(dep);\n if (cache === undefined) {\n const ref = { current: tracked.value as unknown };\n cache = { ref, proxy: buildTrackedProxy(dep, ref, onDepHandle).proxy };\n perDep.set(dep, cache);\n } else {\n cache.ref.current = tracked.value;\n }\n\n if (existing !== undefined) {\n // Re-entry this render (`.track()` twice, or a mutual cycle): union the\n // new paths into the existing entry rather than re-acquiring.\n existing.paths = unionPaths(existing.paths, tracked.paths);\n } else {\n session.set(dep, {\n kind: 'dep',\n paths: tracked.paths,\n Type: brand.Type,\n key,\n refId,\n args,\n });\n }\n\n return [tracked.value, cache.proxy];\n },\n } as DepHandleLike;\n\n Object.defineProperty(wrapper, DEP_BRAND, {\n value: brand,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n return wrapper;\n}\n\n/** Union two PathSets (ALL_PATHS dominates). */\nfunction unionPaths(a: PathSet, b: PathSet): PathSet {\n if (a === ALL_PATHS || b === ALL_PATHS) return ALL_PATHS;\n const out = new Set<number>(a as Set<number>);\n for (const id of b as Set<number>) out.add(id);\n return out;\n}\n\nconst shallowArrayEqual = (a: unknown[], b: unknown[]): boolean => {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) return false;\n }\n return true;\n};\n\n/**\n * Expand a PathSet to include an *ancestor-watch* id for every ancestor of\n * every tracked leaf.\n *\n * The auto-tracker records leaf paths (e.g. `'items.length'`), but\n * `StructuralContainer.patch` can only mark the parent (`'items'`) when it\n * replaces a value atomically (arrays, `null`, primitives — it can't see\n * inside). Without expansion, a subscriber with interest `{'items.length'}`\n * would miss a `patch`-triggered atomic-replacement of `items`.\n *\n * Ancestors are added under the interner's *ancestor-watch* lane\n * (`ancestorWatchIds`), NOT as normal ids. The source emits a matching\n * ancestor-watch mark only for paths it replaces atomically — never for a\n * plain-object structural pulse-up. So `{'items.length'}` wakes when the array\n * `items` is replaced, but `{'user.email'}` does NOT wake when a sibling\n * `user.name` changes and pulses `user` up: pulse-up `user` is a normal id and\n * the ancestor-watch `user` only intersects another ancestor-watch `user`.\n *\n * Example: leaf `'a.b.c'` adds ancestor-watch ids for `'a.b'` and `'a'` (but\n * NOT the `''` root — a root change is covered by `ALL_PATHS` from the source,\n * and `''` would wake this consumer on every field change).\n *\n * Returns `paths` itself when no leaf has an ancestor (top-level fields only);\n * the set is frozen by then (see `Consumer.disarm`), so sharing it is safe.\n */\nfunction expandWithAncestors(paths: PathSet, interner: PathInterner): PathSet {\n if (paths === ALL_PATHS) return ALL_PATHS;\n const leafPaths = paths as Set<number>;\n let expanded: Set<number> | undefined;\n for (const id of leafPaths) {\n const watch = interner.ancestorWatchIds(id);\n if (watch.length === 0) continue;\n expanded ??= new Set<number>(leafPaths);\n for (let i = 0; i < watch.length; i++) expanded.add(watch[i]);\n }\n return expanded ?? paths;\n}\n","import { useEffect, useId, useRef } from 'react';\nimport { APPLY_DEPS, REMOVE_DEPS_OWNER, type StateContainer } from '@blac/core';\n\n/**\n * Feed live values from the component into a bloc's `deps` lane.\n *\n * Each calling component is one owner: its slice is shallow-merged into the\n * bloc's merged `deps` view, and withdrawn when the component unmounts. The\n * bloc observes changes through `onDepsChanged`.\n *\n * ```tsx\n * const [state, bloc] = useBloc(CanvasCubit);\n * useBlocDeps(bloc, { canvas, onTick });\n * ```\n *\n * Use this for values a bloc must *read* but should not own — DOM handles,\n * callbacks, values from other hooks. Pass identity-stable values (`useMemo` /\n * `useCallback`) as you would to any hook; a new function identity each render\n * counts as a change.\n */\nexport function useBlocDeps<D extends object>(\n bloc: StateContainer<any, any, D>,\n slice: Partial<D>,\n): void {\n const ownerId = useId();\n const latest = useRef(slice);\n latest.current = slice;\n\n // `APPLY_DEPS` shallow-compares and no-ops on an unchanged slice, so applying\n // on every commit is cheaper than asking callers to maintain a dep array.\n useEffect(() => {\n bloc[APPLY_DEPS](ownerId, latest.current);\n });\n\n useEffect(() => {\n return () => {\n bloc[REMOVE_DEPS_OWNER](ownerId);\n };\n }, [bloc, ownerId]);\n}\n"],"mappings":"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"}
|