@blac/react 2.1.2 → 2.1.4

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) Facebook, Inc. and its affiliates.
3
+ Copyright (c) 2025 Brendan Mullins
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  React bindings for BlaC — `useBloc` hook with proxy-based automatic re-render optimization.
4
4
 
5
- **[Documentation](https://blac-docs.pages.dev/react/getting-started)** · **[npm](https://www.npmjs.com/package/@blac/react)**
5
+ **[Documentation](https://blac.42.dog/react/getting-started)** · **[npm](https://www.npmjs.com/package/@blac/react)**
6
6
 
7
7
  > [!WARNING]
8
8
  > **BlaC v2 is in pre-release (beta).** While in beta, **breaking API changes may
@@ -343,7 +343,7 @@ class UserCardCubit extends Cubit<UserCardState, { userId: string }> {
343
343
  import { renderWithBloc } from '@blac/react/testing';
344
344
  ```
345
345
 
346
- See the [testing docs](https://blac-docs.pages.dev/testing/react) for details.
346
+ See the [testing docs](https://blac.42.dog/testing/react) for details.
347
347
 
348
348
  ## License
349
349
 
@@ -0,0 +1,51 @@
1
+ let react = require("react");
2
+ let _blac_core = require("@blac/core");
3
+ let react_jsx_runtime = require("react/jsx-runtime");
4
+ //#region src/RegistryProvider.tsx
5
+ const RegistryContext = (0, react.createContext)(null);
6
+ /**
7
+ * Scopes descendant `useBloc` calls to a specific registry instead of the
8
+ * module-global default.
9
+ *
10
+ * Useful for test isolation, SSR isolation, and micro-frontends that must
11
+ * not share bloc instances with the host page.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <RegistryProvider registry={new StateContainerRegistry()}>
16
+ * <App />
17
+ * </RegistryProvider>
18
+ * ```
19
+ * @public
20
+ */
21
+ function RegistryProvider({ registry, children }) {
22
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegistryContext.Provider, {
23
+ value: registry,
24
+ children
25
+ });
26
+ }
27
+ /**
28
+ * The registry `useBloc` uses here: the nearest {@link RegistryProvider}'s,
29
+ * else `getRegistry()`. The plain `@blac/core` helpers (`acquire`, `ensure`,
30
+ * `watch`, …) can't read React context and always use `getRegistry()`; call
31
+ * this registry's methods directly to stay inside a provider's scope.
32
+ * @public
33
+ */
34
+ function useRegistry() {
35
+ return (0, react.useContext)(RegistryContext) ?? (0, _blac_core.getRegistry)();
36
+ }
37
+ //#endregion
38
+ Object.defineProperty(exports, "RegistryProvider", {
39
+ enumerable: true,
40
+ get: function() {
41
+ return RegistryProvider;
42
+ }
43
+ });
44
+ Object.defineProperty(exports, "useRegistry", {
45
+ enumerable: true,
46
+ get: function() {
47
+ return useRegistry;
48
+ }
49
+ });
50
+
51
+ //# sourceMappingURL=RegistryProvider-B9-mQQRg.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegistryProvider-B9-mQQRg.cjs","names":[],"sources":["../src/RegistryProvider.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { getRegistry, type StateContainerRegistry } from '@blac/core';\n\nconst RegistryContext = createContext<StateContainerRegistry | null>(null);\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\n/**\n * The registry `useBloc` uses here: the nearest {@link RegistryProvider}'s,\n * else `getRegistry()`. The plain `@blac/core` helpers (`acquire`, `ensure`,\n * `watch`, …) can't read React context and always use `getRegistry()`; call\n * this registry's methods directly to stay inside a provider's scope.\n * @public\n */\nexport function useRegistry(): StateContainerRegistry {\n return useContext(RegistryContext) ?? getRegistry();\n}\n"],"mappings":";;;;AAQA,MAAM,mBAAA,GAAA,MAAA,eAA+D,IAAI;;;;;;;;;;;;;;;;AA0BzE,SAAgB,iBAAiB,EAC/B,UACA,YACsC;CACtC,OACE,iBAAA,GAAA,kBAAA,KAAC,gBAAgB,UAAjB;EAA0B,OAAO;EAC9B;CACuB,CAAA;AAE9B;;;;;;;;AASA,SAAgB,cAAsC;CACpD,QAAA,GAAA,MAAA,YAAkB,eAAe,MAAA,GAAA,WAAA,aAAiB;AACpD"}
@@ -0,0 +1,40 @@
1
+ import { createContext, useContext } from "react";
2
+ import { getRegistry } from "@blac/core";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/RegistryProvider.tsx
5
+ const RegistryContext = createContext(null);
6
+ /**
7
+ * Scopes descendant `useBloc` calls to a specific registry instead of the
8
+ * module-global default.
9
+ *
10
+ * Useful for test isolation, SSR isolation, and micro-frontends that must
11
+ * not share bloc instances with the host page.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <RegistryProvider registry={new StateContainerRegistry()}>
16
+ * <App />
17
+ * </RegistryProvider>
18
+ * ```
19
+ * @public
20
+ */
21
+ function RegistryProvider({ registry, children }) {
22
+ return /* @__PURE__ */ jsx(RegistryContext.Provider, {
23
+ value: registry,
24
+ children
25
+ });
26
+ }
27
+ /**
28
+ * The registry `useBloc` uses here: the nearest {@link RegistryProvider}'s,
29
+ * else `getRegistry()`. The plain `@blac/core` helpers (`acquire`, `ensure`,
30
+ * `watch`, …) can't read React context and always use `getRegistry()`; call
31
+ * this registry's methods directly to stay inside a provider's scope.
32
+ * @public
33
+ */
34
+ function useRegistry() {
35
+ return useContext(RegistryContext) ?? getRegistry();
36
+ }
37
+ //#endregion
38
+ export { useRegistry as n, RegistryProvider as t };
39
+
40
+ //# sourceMappingURL=RegistryProvider-DS4ltjlH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegistryProvider-DS4ltjlH.js","names":[],"sources":["../src/RegistryProvider.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { getRegistry, type StateContainerRegistry } from '@blac/core';\n\nconst RegistryContext = createContext<StateContainerRegistry | null>(null);\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\n/**\n * The registry `useBloc` uses here: the nearest {@link RegistryProvider}'s,\n * else `getRegistry()`. The plain `@blac/core` helpers (`acquire`, `ensure`,\n * `watch`, …) can't read React context and always use `getRegistry()`; call\n * this registry's methods directly to stay inside a provider's scope.\n * @public\n */\nexport function useRegistry(): StateContainerRegistry {\n return useContext(RegistryContext) ?? getRegistry();\n}\n"],"mappings":";;;;AAQA,MAAM,kBAAkB,cAA6C,IAAI;;;;;;;;;;;;;;;;AA0BzE,SAAgB,iBAAiB,EAC/B,UACA,YACsC;CACtC,OACE,oBAAC,gBAAgB,UAAjB;EAA0B,OAAO;EAC9B;CACuB,CAAA;AAE9B;;;;;;;;AASA,SAAgB,cAAsC;CACpD,OAAO,WAAW,eAAe,KAAK,YAAY;AACpD"}
@@ -14,6 +14,7 @@ export declare class DepSession {
14
14
  private readonly consumerId;
15
15
  private readonly onChange;
16
16
  private primary;
17
+ private primaryParts;
17
18
  private primaryPaths;
18
19
  private entries;
19
20
  private subs;
@@ -26,6 +27,11 @@ export declare class DepSession {
26
27
  private get refId();
27
28
  begin(primary: StateContainer, paths: PathSet): void;
28
29
  record(dep: StateContainer, paths: PathSet, Type: StateContainerConstructor, key: string, args: unknown, disarm: () => void): void;
30
+ /**
31
+ * Merge each container's per-call sets once the render's reads are done.
32
+ * Returns the primary's paths, widened by deps that resolved to it.
33
+ */
34
+ settle(): PathSet;
29
35
  /** Stops every dep tracked this render from recording further reads. */
30
36
  disarm(): void;
31
37
  /** Forces the next `reconcile` to run in full. */
@@ -14,6 +14,7 @@ export declare class DepSession {
14
14
  private readonly consumerId;
15
15
  private readonly onChange;
16
16
  private primary;
17
+ private primaryParts;
17
18
  private primaryPaths;
18
19
  private entries;
19
20
  private subs;
@@ -26,6 +27,11 @@ export declare class DepSession {
26
27
  private get refId();
27
28
  begin(primary: StateContainer, paths: PathSet): void;
28
29
  record(dep: StateContainer, paths: PathSet, Type: StateContainerConstructor, key: string, args: unknown, disarm: () => void): void;
30
+ /**
31
+ * Merge each container's per-call sets once the render's reads are done.
32
+ * Returns the primary's paths, widened by deps that resolved to it.
33
+ */
34
+ settle(): PathSet;
29
35
  /** Stops every dep tracked this render from recording further reads. */
30
36
  disarm(): void;
31
37
  /** Forces the next `reconcile` to run in full. */
package/dist/index.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_RegistryProvider = require("./RegistryProvider-F2qb_prD.cjs");
2
+ const require_RegistryProvider = require("./RegistryProvider-B9-mQQRg.cjs");
3
3
  let react = require("react");
4
+ let _blac_core = require("@blac/core");
4
5
  let _dirtytalk_structural = require("@dirtytalk/structural");
5
6
  let react_jsx_runtime = require("react/jsx-runtime");
6
7
  let _blac_core_internal = require("@blac/core/internal");
@@ -152,6 +153,7 @@ var DepSession = class {
152
153
  this.consumerId = consumerId;
153
154
  this.onChange = onChange;
154
155
  this.primary = null;
156
+ this.primaryParts = [];
155
157
  this.primaryPaths = NO_PATHS$1;
156
158
  this.entries = null;
157
159
  this.subs = null;
@@ -168,24 +170,34 @@ var DepSession = class {
168
170
  this.entries?.clear();
169
171
  if (this.disarms !== null) this.disarms.length = 0;
170
172
  this.primary = primary;
171
- this.primaryPaths = paths;
173
+ this.primaryParts = [paths];
172
174
  }
173
175
  record(dep, paths, Type, key, args, disarm) {
174
176
  (this.disarms ??= []).push(disarm);
175
177
  if (dep === this.primary) {
176
- this.primaryPaths = unionPaths(this.primaryPaths, paths);
178
+ this.primaryParts.push(paths);
177
179
  return;
178
180
  }
179
181
  const entries = this.entries ??= /* @__PURE__ */ new Map();
180
182
  const existing = entries.get(dep);
181
- if (existing !== void 0) existing.paths = unionPaths(existing.paths, paths);
183
+ if (existing !== void 0) existing.parts.push(paths);
182
184
  else entries.set(dep, {
185
+ parts: [paths],
183
186
  paths,
184
187
  Type,
185
188
  key,
186
189
  args
187
190
  });
188
191
  }
192
+ /**
193
+ * Merge each container's per-call sets once the render's reads are done.
194
+ * Returns the primary's paths, widened by deps that resolved to it.
195
+ */
196
+ settle() {
197
+ this.primaryPaths = mergeParts(this.primaryParts);
198
+ if (this.entries !== null) for (const entry of this.entries.values()) entry.paths = mergeParts(entry.parts);
199
+ return this.primaryPaths;
200
+ }
189
201
  /** Stops every dep tracked this render from recording further reads. */
190
202
  disarm() {
191
203
  const disarms = this.disarms;
@@ -273,10 +285,13 @@ var DepSession = class {
273
285
  function sameEntry(a, b) {
274
286
  return (0, _dirtytalk_structural.pathSetEquals)(a.paths, b.paths) && a.key === b.key && Object.is(a.args, b.args);
275
287
  }
276
- function unionPaths(a, b) {
277
- if (a === _dirtytalk_structural.ALL_PATHS || b === _dirtytalk_structural.ALL_PATHS) return _dirtytalk_structural.ALL_PATHS;
278
- const out = new Set(a);
279
- for (const id of b) out.add(id);
288
+ function mergeParts(parts) {
289
+ if (parts.length === 1) return parts[0];
290
+ const out = /* @__PURE__ */ new Set();
291
+ for (const part of parts) {
292
+ if (part === _dirtytalk_structural.ALL_PATHS) return _dirtytalk_structural.ALL_PATHS;
293
+ for (const id of part) out.add(id);
294
+ }
280
295
  return out;
281
296
  }
282
297
  /**
@@ -290,7 +305,7 @@ function unionPaths(a, b) {
290
305
  */
291
306
  function makeDepWrapper(handle, registry, session, tracked, onDepHandle) {
292
307
  const brand = handle[_blac_core_internal.DEP_BRAND];
293
- const perDep = /* @__PURE__ */ new Map();
308
+ const perDep = /* @__PURE__ */ new WeakMap();
294
309
  const proxyCache = new _dirtytalk_structural.ProxyCache();
295
310
  const resolve = (options) => {
296
311
  const args = options?.args ?? brand.defaultArgs;
@@ -357,11 +372,8 @@ let nextConsumerId = 0;
357
372
  * fires *before* the registry releases its ref, so the bloc is still
358
373
  * alive when the callback runs.
359
374
  *
360
- * Per-mount private instance:
361
- * ```ts
362
- * const id = useId();
363
- * const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });
364
- * ```
375
+ * Per-mount private instance: mark the class `@blac({ isolated: true })`, or
376
+ * pass a unique key arg yourself (e.g. `useId()` in `args`).
365
377
  *
366
378
  * @typeParam T - The state container constructor type (inferred from BlocClass)
367
379
  * @param BlocClass - The state container class to connect to
@@ -396,7 +408,8 @@ function useBloc(BlocClass, options) {
396
408
  consumer.ownArgs = options?.args;
397
409
  consumer.providerArgs = useProvidedArgs(BlocClass);
398
410
  const effectiveArgs = resolveEffectiveArgs(consumer);
399
- const instanceKey = registry.resolveKey(BlocClass, void 0, effectiveArgs);
411
+ const sharedKey = registry.resolveKey(BlocClass, void 0, effectiveArgs);
412
+ const instanceKey = (0, _blac_core.isIsolatedClass)(BlocClass) ? `${sharedKey}#${consumer.id}` : sharedKey;
400
413
  if (consumer.BlocClass !== BlocClass || consumer.instanceKey !== instanceKey || consumer.registry !== registry || consumer.boundNonce !== consumer.rebindNonce) bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);
401
414
  (0, react.useSyncExternalStore)(consumer.subscribe, consumer.getSnapshot, consumer.getSnapshot);
402
415
  const container = consumer.container;
@@ -574,6 +587,7 @@ function commit(consumer) {
574
587
  }
575
588
  const primary = consumer.container;
576
589
  if (primary.state !== consumer.renderedState) consumer.bump();
590
+ consumer.paths = deps.settle();
577
591
  if (deps.isUnchanged()) return;
578
592
  primary.registerConsumerPaths(consumer.id, consumer.paths);
579
593
  setInterest(consumer, consumer.paths);
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["WITH_TRACKED_STATE","DEP_BRAND","NO_PATHS","ALL_PATHS","DEP_BRAND","ProxyCache","useRegistry","ALL_PATHS","ProxyCache","APPLY_DEPS","REMOVE_DEPS_OWNER"],"sources":["../src/BlocProvider.tsx","../src/buildTrackedProxy.ts","../src/depSession.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 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 * Each provider copies its parent's map and adds its own entry, so nested\n * providers for different blocs 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. Own `args` on the `useBloc` call always win.\n *\n * Multiple `BlocProvider` wrappers for different bloc classes compose: each\n * merges its entry into the inherited map, so nested providers for different\n * 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 args content, not identity, so an inline `args={{ ... }}`\n // literal does not rebuild the map (and re-render every consumer) on\n // every render, while a real field change still reaches `useProvidedArgs`.\n const argsKey = JSON.stringify(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 { DEP_BRAND, WITH_TRACKED_STATE } from '@blac/core/internal';\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;\ntype Method = (...args: unknown[]) => 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// Methods on the prototype, so a mount allocates the handler and the proxy\n// only, not a set of closures.\nclass BlocHandler implements ProxyHandler<object> {\n // Bound methods are cached so `bloc.method` keeps a stable identity across\n // reads — an unstable one would defeat memoisation in consumers. Keyed by\n // the underlying function too, so a reassigned method is re-bound.\n private boundMethods:\n | Map<string | symbol, { fn: Method; bound: Method }>\n | undefined = undefined;\n\n constructor(\n private readonly getters: Map<string | symbol, Getter>,\n private readonly tracksState: boolean,\n private readonly trackedStateRef: { current: unknown },\n private readonly onDepHandle: ((handle: object) => unknown) | undefined,\n ) {}\n\n get(target: object, key: string | symbol): unknown {\n const getter = this.getters.get(key);\n if (getter !== undefined) {\n const tracked = this.trackedStateRef.current;\n // `[WITH_TRACKED_STATE]` invokes the getter with `this` = the real\n // instance, so no per-read thunk is needed.\n return this.wrapDepHandle(\n tracked == null || !this.tracksState\n ? getter.call(target)\n : (target as TrackedStateTarget)[WITH_TRACKED_STATE](\n tracked,\n getter,\n this.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 const fn = value as Method;\n this.boundMethods ??= new Map();\n let cached = this.boundMethods.get(key);\n if (cached === undefined || cached.fn !== fn) {\n cached = { fn, bound: fn.bind(target) };\n this.boundMethods.set(key, cached);\n }\n return this.wrapDepHandle(cached.bound);\n }\n return this.wrapDepHandle(value);\n }\n\n set(target: object, key: string | symbol, value: unknown): boolean {\n return Reflect.set(target, key, value, target);\n }\n\n private wrapDepHandle(value: unknown): unknown {\n if (\n this.onDepHandle !== undefined &&\n (typeof value === 'function' || typeof value === 'object') &&\n value !== null &&\n (value as Record<symbol, unknown>)[DEP_BRAND] !== undefined\n ) {\n return this.onDepHandle(value as object);\n }\n return value;\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 the 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\n * report the render's tracking proxy for the call's duration; nested getter\n * chains stay tracked because the override remains set.\n *\n * One allocation per bloc acquisition (inside `useMemo`), so the proxy is\n * stable across renders.\n *\n * @param onDepHandle - Invoked when a read returns a branded dep handle;\n * returns the value (the session-bound wrapper) to expose in its place.\n * Must cache 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): 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 handler = new BlocHandler(\n collectGetters(Object.getPrototypeOf(instance) as object),\n supportsTrackedState(instance),\n trackedStateRef,\n onDepHandle,\n );\n return new Proxy(instance, handler as ProxyHandler<T>);\n}\n","import type {\n StateContainer,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport { DEP_BRAND } from '@blac/core/internal';\nimport {\n ALL_PATHS,\n emptyPathSet,\n pathSetEquals,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { buildTrackedProxy } from './buildTrackedProxy';\n\nconst NO_PATHS: PathSet = emptyPathSet();\n\ninterface DepEntry {\n paths: PathSet;\n Type: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\ninterface DepSub {\n subscription: PathSubscription;\n registry: StateContainerRegistry;\n Type: StateContainerConstructor;\n key: string;\n}\n\n/**\n * The containers one `useBloc` consumer reads during a render: its primary\n * bloc plus every dep reached through `this.<handle>.track()`.\n *\n * The render records entries; the commit calls `reconcile`, which subscribes\n * and takes a ref on new deps and drops the ones no longer read. `dispose`\n * releases everything. Refs are only taken at commit, so an uncommitted render\n * never leaks one.\n */\nexport class DepSession {\n // Most consumers read no deps, so everything dep-related is allocated on the\n // first dep a render records.\n private primary: StateContainer | null = null;\n private primaryPaths: PathSet = NO_PATHS;\n private entries: Map<StateContainer, DepEntry> | null = null;\n private subs: Map<StateContainer, DepSub> | null = null;\n private reconciled = false;\n private lastPrimary: StateContainer | null = null;\n private lastPrimaryPaths: PathSet = NO_PATHS;\n private lastEntries: Map<StateContainer, DepEntry> | null = null;\n private disarms: Array<() => void> | null = null;\n\n constructor(\n private readonly consumerId: string,\n private readonly onChange: () => void,\n ) {}\n\n private get refId(): string {\n return `useBloc@${this.consumerId}:dep`;\n }\n\n begin(primary: StateContainer, paths: PathSet): void {\n this.entries?.clear();\n if (this.disarms !== null) this.disarms.length = 0;\n this.primary = primary;\n this.primaryPaths = paths;\n }\n\n record(\n dep: StateContainer,\n paths: PathSet,\n Type: StateContainerConstructor,\n key: string,\n args: unknown,\n disarm: () => void,\n ): void {\n (this.disarms ??= []).push(disarm);\n // A dep that resolves to the primary only widens the primary's entry.\n if (dep === this.primary) {\n this.primaryPaths = unionPaths(this.primaryPaths, paths);\n return;\n }\n const entries = (this.entries ??= new Map());\n const existing = entries.get(dep);\n if (existing !== undefined) {\n existing.paths = unionPaths(existing.paths, paths);\n } else {\n entries.set(dep, { paths, Type, key, args });\n }\n }\n\n /** Stops every dep tracked this render from recording further reads. */\n disarm(): void {\n const disarms = this.disarms;\n if (disarms === null) return;\n for (const disarm of disarms) disarm();\n disarms.length = 0;\n }\n\n /** Forces the next `reconcile` to run in full. */\n invalidate(): void {\n this.reconciled = false;\n }\n\n isUnchanged(): boolean {\n if (\n !this.reconciled ||\n this.lastPrimary !== this.primary ||\n !pathSetEquals(this.lastPrimaryPaths, this.primaryPaths)\n ) {\n return false;\n }\n const last = this.lastEntries;\n const entries = this.entries;\n if ((last?.size ?? 0) !== (entries?.size ?? 0)) return false;\n if (last === null || entries === null) return true;\n for (const [container, entry] of entries) {\n const prev = last.get(container);\n if (prev === undefined || !sameEntry(prev, entry)) return false;\n }\n return true;\n }\n\n reconcile(registry: StateContainerRegistry): void {\n const { consumerId } = this;\n const entries = this.entries;\n this.reconciled = true;\n this.lastPrimary = this.primary;\n this.lastPrimaryPaths = this.primaryPaths;\n this.lastEntries =\n entries === null || entries.size === 0 ? null : new Map(entries);\n\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) {\n if (entries?.has(dep) !== true) {\n this.drop(dep, sub);\n subs.delete(dep);\n }\n }\n }\n if (entries === null) return;\n\n for (const [dep, entry] of entries) {\n // StrictMode's simulated unmount can dispose a dep this render captured;\n // re-render so the session resolves the live instance.\n if (dep.$blac.disposed) {\n this.onChange();\n continue;\n }\n dep.registerConsumerPaths(consumerId, entry.paths);\n const existing = subs?.get(dep);\n if (existing) {\n existing.subscription.setInterest(entry.paths);\n continue;\n }\n const refId = this.refId;\n const live = registry.acquire(entry.Type, entry.key, {\n canCreate: true,\n countRef: true,\n refId,\n args: entry.args,\n });\n // The key now maps to another instance; re-render against that one.\n if (live !== dep) {\n registry.release(entry.Type, entry.key, false, refId);\n this.onChange();\n continue;\n }\n const subscription = dep.subscribePaths(this.onChange);\n subscription.setInterest(entry.paths);\n (this.subs ??= new Map()).set(dep, {\n subscription,\n registry,\n Type: entry.Type,\n key: entry.key,\n });\n }\n }\n\n dispose(): void {\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) this.drop(dep, sub);\n subs.clear();\n }\n this.reconciled = false;\n }\n\n private drop(dep: StateContainer, sub: DepSub): void {\n sub.subscription.unsubscribe();\n dep.unregisterConsumer(this.consumerId);\n sub.registry.release(sub.Type, sub.key, false, this.refId);\n }\n}\n\nfunction sameEntry(a: DepEntry, b: DepEntry): boolean {\n return (\n pathSetEquals(a.paths, b.paths) &&\n a.key === b.key &&\n Object.is(a.args, b.args)\n );\n}\n\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\ninterface DepAccessOptionsLike {\n args?: unknown;\n}\n\n/** Structural shape of a branded `depend()` handle as seen from React. */\nexport interface 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 * Wrap a dep handle read inside a tracked getter. During a render (`tracked`\n * is set) `.track()` records the dep's read paths into `session` and returns\n * a tracked proxy, so the dep's own getters track too. Outside a render it\n * returns the live `[state, dep]`, like the core handle.\n *\n * Args resolve per call, so one handle can reach several instances; proxies\n * are cached per resolved instance.\n */\nexport function makeDepWrapper(\n handle: DepHandleLike,\n registry: StateContainerRegistry,\n session: DepSession,\n tracked: { current: unknown },\n onDepHandle: (handle: object) => unknown,\n): DepHandleLike {\n const brand = handle[DEP_BRAND];\n const perDep = new Map<\n StateContainer,\n { ref: { current: unknown }; proxy: StateContainer }\n >();\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.acquire(brand.Type, key, {\n canCreate: true,\n countRef: false,\n args,\n sweepIfUnowned: tracked.current != null,\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 if (tracked.current == null) return [dep.state, dep];\n\n const result = trackRender(dep.state, dep.interner, proxyCache);\n let cache = perDep.get(dep);\n if (cache === undefined) {\n const ref = { current: result.value as unknown };\n cache = { ref, proxy: buildTrackedProxy(dep, ref, onDepHandle) };\n perDep.set(dep, cache);\n } else {\n cache.ref.current = result.value;\n }\n session.record(dep, result.paths, brand.Type, key, args, result.disarm);\n return [result.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","import { useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport {\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 trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { useProvidedArgs } from './BlocProvider';\nimport { useRegistry } from './RegistryProvider';\nimport { buildTrackedProxy } from './buildTrackedProxy';\nimport { DepSession, makeDepWrapper, type DepHandleLike } from './depSession';\nimport type { UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\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.\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]`\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 const registry = useRegistry();\n\n const consumerRef = useRef<Consumer | null>(null);\n const consumer = (consumerRef.current ??= createConsumer());\n\n consumer.select = options?.select;\n consumer.onMount = options?.onMount;\n consumer.onUnmount = options?.onUnmount;\n\n consumer.ownArgs = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n consumer.providerArgs = useProvidedArgs(BlocClass);\n const effectiveArgs = resolveEffectiveArgs(consumer);\n const instanceKey = registry.resolveKey(BlocClass, undefined, effectiveArgs);\n\n // A memo kept on the consumer rather than in `useMemo`: no hook, deps array\n // or factory closure per mount. `rebindNonce` is bumped by the commit when\n // the rendered instance was disposed and recreated before commit (a\n // same-commit owner handoff, or StrictMode's double effect); the tracked\n // proxy can't be retargeted in place. Keyed on `BlocClass` as well as the\n // key: classes without args or a `static key` share the default key, so\n // `useBloc(cond ? A : B)` must still re-subscribe on a class swap.\n if (\n consumer.BlocClass !== BlocClass ||\n consumer.instanceKey !== instanceKey ||\n consumer.registry !== registry ||\n consumer.boundNonce !== consumer.rebindNonce\n ) {\n bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);\n }\n\n useSyncExternalStore(\n consumer.subscribe,\n consumer.getSnapshot,\n consumer.getSnapshot,\n );\n\n const container = consumer.container;\n const rawState = container.state as ExtractState<T>;\n consumer.renderedState = rawState;\n const select = consumer.select;\n consumer.isSelectMode = select !== undefined;\n let state: ExtractState<T>;\n if (select !== undefined) {\n state = rawState;\n // Recomputed every render: the selector may close over props.\n consumer.selection = select(rawState, container as InstanceState<T>);\n setInterest(consumer, 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 // Run at commit, so reads that outlive the render (effects, handlers)\n // don't add paths.\n consumer.disarm = tracked.disarm;\n // `tracked.paths` is still empty here and fills during JSX, so paths are\n // registered at commit, not now.\n consumer.deps.begin(container, tracked.paths);\n }\n\n // Stable callbacks that read this render's inputs from the consumer.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.commit);\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.mount, NO_DEPS);\n\n return [state, consumer.trackedBloc] as UseBlocReturn<T, ExtractState<T>>;\n}\n\nconst noop = (): void => {};\n\n// Initial `interest` and `paths`; neither is ever mutated in place.\nconst NO_PATHS: PathSet = emptyPathSet();\nconst NO_DEPS: [] = [];\n\n/**\n * Everything one `useBloc` call keeps between renders. Its callbacks are\n * created once, so a render allocates no closures.\n *\n * `bump` increments `version` before calling `notify`: uSES requires\n * `getSnapshot()` to reflect the change by the time it is notified.\n */\ninterface Consumer {\n id: string;\n primaryRefId: string;\n // The render's inputs, read by the commit of that render.\n BlocClass: StateContainerConstructor;\n instanceKey: string;\n registry: StateContainerRegistry;\n /** Written only by `bind`. */\n container: StateContainer;\n trackedBloc: unknown;\n subscribe: (onStoreChange: () => void) => () => void;\n boundNonce: number;\n rebindNonce: number;\n renderedState: unknown;\n version: number;\n /** `noop` while unsubscribed. */\n notify: () => void;\n bump: () => void;\n getSnapshot: () => number;\n onChange: () => void;\n unsubscribe: () => void;\n commit: () => void;\n mount: () => () => void;\n /** The committed `paths`, or `ALL_PATHS` in select mode. */\n interest: PathSet;\n /** `null` while unsubscribed. */\n subscription: PathSubscription | null;\n subscribedTo: StateContainer | null;\n paths: PathSet;\n isSelectMode: boolean;\n selection: unknown[] | null;\n deps: DepSession;\n // Typed loosely: `T` is per call site.\n select: ((state: any, bloc: any) => unknown[]) | undefined;\n onMount: ((bloc: any) => void) | undefined;\n onUnmount: ((bloc: any) => void) | undefined;\n ownArgs: unknown;\n providerArgs: unknown;\n /** The render's tracking proxy; `null` outside a tracked render. */\n tracked: { current: unknown };\n disarm: (() => void) | null;\n proxyCache: ProxyCache | null;\n // The instance the consumer holds a ref on, claimed at commit so acquire\n // and release pair exactly.\n ownedClass: StateContainerConstructor | null;\n ownedKey: string;\n ownedRegistry: StateContainerRegistry | null;\n ownedBloc: unknown;\n}\n\nfunction createConsumer(): Consumer {\n const id = `useBloc-${nextConsumerId++}`;\n const consumer: Consumer = {\n id,\n primaryRefId: `useBloc@${id}`,\n BlocClass: null as unknown as StateContainerConstructor,\n instanceKey: '',\n registry: null as unknown as StateContainerRegistry,\n container: null as unknown as StateContainer,\n trackedBloc: null,\n subscribe: null as unknown as Consumer['subscribe'],\n boundNonce: 0,\n rebindNonce: 0,\n renderedState: undefined,\n version: 0,\n notify: noop,\n bump: () => {\n consumer.version++;\n consumer.notify();\n },\n getSnapshot: () => consumer.version,\n onChange: () => onChange(consumer),\n unsubscribe: () => unsubscribe(consumer),\n commit: () => commit(consumer),\n mount: () => teardown,\n interest: NO_PATHS,\n subscription: null,\n subscribedTo: null,\n paths: NO_PATHS,\n isSelectMode: false,\n selection: null,\n deps: null as unknown as DepSession,\n select: undefined,\n onMount: undefined,\n onUnmount: undefined,\n ownArgs: undefined,\n providerArgs: undefined,\n tracked: { current: null },\n disarm: null,\n proxyCache: null,\n ownedClass: null,\n ownedKey: '',\n ownedRegistry: null,\n ownedBloc: null,\n };\n const teardown = (): void => {\n release(consumer);\n consumer.deps.dispose();\n };\n consumer.deps = new DepSession(id, consumer.bump);\n return consumer;\n}\n\nfunction bind(\n consumer: Consumer,\n BlocClass: StateContainerConstructor,\n instanceKey: string,\n registry: StateContainerRegistry,\n args: unknown,\n): void {\n // No ref during render; the commit claims it. The registry sweeps the\n // instance if no commit ever does (SSR, discarded renders).\n const instance = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: false,\n args: args as ExtractArgs<StateContainerConstructor>,\n sweepIfUnowned: true,\n }) as unknown as StateContainer;\n\n // Dep proxies get `onDepHandle` too, so nested `.track()` calls (A→B→C)\n // record into this consumer's session.\n let depWrappers: Map<object, unknown> | undefined;\n const onDepHandle = (handle: object): unknown => {\n depWrappers ??= new Map();\n const cached = depWrappers.get(handle);\n if (cached !== undefined) return cached;\n const wrapper = makeDepWrapper(\n handle as DepHandleLike,\n registry,\n consumer.deps,\n consumer.tracked,\n onDepHandle,\n );\n depWrappers.set(handle, wrapper);\n return wrapper;\n };\n\n consumer.BlocClass = BlocClass;\n consumer.instanceKey = instanceKey;\n consumer.registry = registry;\n consumer.boundNonce = consumer.rebindNonce;\n consumer.container = instance;\n consumer.trackedBloc = buildTrackedProxy(\n instance,\n consumer.tracked,\n onDepHandle,\n );\n // A new identity per instance, so uSES re-subscribes.\n consumer.subscribe = (onStoreChange) => subscribe(consumer, onStoreChange);\n}\n\nfunction subscribe(consumer: Consumer, onStoreChange: () => void): () => void {\n consumer.notify = onStoreChange;\n const container = consumer.container;\n const subscription = container.subscribePaths(consumer.onChange);\n subscription.setInterest(consumer.interest);\n consumer.subscription = subscription;\n consumer.subscribedTo = container;\n container.registerConsumerPaths(consumer.id, consumer.paths);\n // An emit raised during render is still pending in the channel, so its\n // next flush delivers it to this subscriber.\n return consumer.unsubscribe;\n}\n\nfunction unsubscribe(consumer: Consumer): void {\n consumer.notify = noop;\n consumer.subscription?.unsubscribe();\n consumer.subscription = null;\n consumer.subscribedTo?.unregisterConsumer(consumer.id);\n consumer.subscribedTo = null;\n}\n\nfunction onChange(consumer: Consumer): void {\n if (consumer.isSelectMode) {\n const select = consumer.select;\n const container = consumer.subscribedTo;\n if (select && container !== null) {\n const next = select(container.state, container);\n const prev = consumer.selection;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n consumer.selection = next;\n }\n }\n consumer.bump();\n}\n\nfunction claim(consumer: Consumer): void {\n const { BlocClass, instanceKey, registry } = consumer;\n if (\n consumer.ownedClass === BlocClass &&\n consumer.ownedKey === instanceKey &&\n consumer.ownedRegistry === registry\n ) {\n return;\n }\n release(consumer);\n const live = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: true,\n refId: consumer.primaryRefId,\n args: resolveEffectiveArgs(\n consumer,\n ) as ExtractArgs<StateContainerConstructor>,\n });\n consumer.ownedClass = BlocClass;\n consumer.ownedKey = instanceKey;\n consumer.ownedRegistry = registry;\n consumer.ownedBloc = live;\n consumer.onMount?.(live);\n // The render captured a stale instance; re-render against the live one.\n if (live !== (consumer.container as unknown)) {\n consumer.rebindNonce++;\n consumer.bump();\n }\n}\n\nfunction release(consumer: Consumer): void {\n const { ownedClass, ownedRegistry } = consumer;\n if (ownedClass === null || ownedRegistry === null) return;\n // Before release, so the bloc is still alive in the callback.\n consumer.onUnmount?.(consumer.ownedBloc);\n consumer.ownedClass = null;\n consumer.ownedRegistry = null;\n ownedRegistry.release(\n ownedClass,\n consumer.ownedKey,\n false,\n consumer.primaryRefId,\n );\n}\n\nfunction commit(consumer: Consumer): void {\n claim(consumer);\n // Getters called after commit read live state, not this render's proxy.\n consumer.tracked.current = null;\n const disarm = consumer.disarm;\n if (disarm !== null) {\n consumer.disarm = null;\n disarm();\n }\n const deps = consumer.deps;\n deps.disarm();\n if (consumer.isSelectMode) {\n // A later switch back to auto-track must reconcile in full.\n deps.invalidate();\n return;\n }\n const primary = consumer.container;\n // A change between render and commit was diffed against the previous\n // interest, which may lack paths only this render read.\n if (primary.state !== consumer.renderedState) consumer.bump();\n if (deps.isUnchanged()) return;\n\n primary.registerConsumerPaths(consumer.id, consumer.paths);\n setInterest(consumer, consumer.paths);\n deps.reconcile(consumer.registry);\n}\n\nconst setInterest = (consumer: Consumer, interest: PathSet): void => {\n consumer.interest = interest;\n consumer.subscription?.setInterest(interest);\n};\n\n// Own args win over provider args. Shared by render and the ownership effect,\n// which recreates a disposed instance and must not drop its args.\nconst resolveEffectiveArgs = (consumer: Consumer): unknown =>\n consumer.ownArgs !== undefined ? consumer.ownArgs : consumer.providerArgs;\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","import { useEffect, useId, useRef } from 'react';\nimport type { StateContainer } from '@blac/core';\nimport { APPLY_DEPS, REMOVE_DEPS_OWNER } from '@blac/core/internal';\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 on unmount. The bloc observes\n * 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","NO_PATHS","ALL_PATHS","DEP_BRAND","ProxyCache","useRegistry","ALL_PATHS","ProxyCache","APPLY_DEPS","REMOVE_DEPS_OWNER"],"sources":["../src/BlocProvider.tsx","../src/buildTrackedProxy.ts","../src/depSession.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 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 * Each provider copies its parent's map and adds its own entry, so nested\n * providers for different blocs 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. Own `args` on the `useBloc` call always win.\n *\n * Multiple `BlocProvider` wrappers for different bloc classes compose: each\n * merges its entry into the inherited map, so nested providers for different\n * 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 args content, not identity, so an inline `args={{ ... }}`\n // literal does not rebuild the map (and re-render every consumer) on\n // every render, while a real field change still reaches `useProvidedArgs`.\n const argsKey = JSON.stringify(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 { DEP_BRAND, WITH_TRACKED_STATE } from '@blac/core/internal';\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;\ntype Method = (...args: unknown[]) => 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// Methods on the prototype, so a mount allocates the handler and the proxy\n// only, not a set of closures.\nclass BlocHandler implements ProxyHandler<object> {\n // Bound methods are cached so `bloc.method` keeps a stable identity across\n // reads — an unstable one would defeat memoisation in consumers. Keyed by\n // the underlying function too, so a reassigned method is re-bound.\n private boundMethods:\n | Map<string | symbol, { fn: Method; bound: Method }>\n | undefined = undefined;\n\n constructor(\n private readonly getters: Map<string | symbol, Getter>,\n private readonly tracksState: boolean,\n private readonly trackedStateRef: { current: unknown },\n private readonly onDepHandle: ((handle: object) => unknown) | undefined,\n ) {}\n\n get(target: object, key: string | symbol): unknown {\n const getter = this.getters.get(key);\n if (getter !== undefined) {\n const tracked = this.trackedStateRef.current;\n // `[WITH_TRACKED_STATE]` invokes the getter with `this` = the real\n // instance, so no per-read thunk is needed.\n return this.wrapDepHandle(\n tracked == null || !this.tracksState\n ? getter.call(target)\n : (target as TrackedStateTarget)[WITH_TRACKED_STATE](\n tracked,\n getter,\n this.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 const fn = value as Method;\n this.boundMethods ??= new Map();\n let cached = this.boundMethods.get(key);\n if (cached === undefined || cached.fn !== fn) {\n cached = { fn, bound: fn.bind(target) };\n this.boundMethods.set(key, cached);\n }\n return this.wrapDepHandle(cached.bound);\n }\n return this.wrapDepHandle(value);\n }\n\n set(target: object, key: string | symbol, value: unknown): boolean {\n return Reflect.set(target, key, value, target);\n }\n\n private wrapDepHandle(value: unknown): unknown {\n if (\n this.onDepHandle !== undefined &&\n (typeof value === 'function' || typeof value === 'object') &&\n value !== null &&\n (value as Record<symbol, unknown>)[DEP_BRAND] !== undefined\n ) {\n return this.onDepHandle(value as object);\n }\n return value;\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 the 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\n * report the render's tracking proxy for the call's duration; nested getter\n * chains stay tracked because the override remains set.\n *\n * One allocation per bloc acquisition (inside `useMemo`), so the proxy is\n * stable across renders.\n *\n * @param onDepHandle - Invoked when a read returns a branded dep handle;\n * returns the value (the session-bound wrapper) to expose in its place.\n * Must cache 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): 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 handler = new BlocHandler(\n collectGetters(Object.getPrototypeOf(instance) as object),\n supportsTrackedState(instance),\n trackedStateRef,\n onDepHandle,\n );\n return new Proxy(instance, handler as ProxyHandler<T>);\n}\n","import type {\n StateContainer,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport { DEP_BRAND } from '@blac/core/internal';\nimport {\n ALL_PATHS,\n emptyPathSet,\n pathSetEquals,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { buildTrackedProxy } from './buildTrackedProxy';\n\nconst NO_PATHS: PathSet = emptyPathSet();\n\ninterface DepEntry {\n // One live set per `.track()` call, merged at commit: reads keep landing in\n // each call's own set until the render ends.\n parts: PathSet[];\n paths: PathSet;\n Type: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\ninterface DepSub {\n subscription: PathSubscription;\n registry: StateContainerRegistry;\n Type: StateContainerConstructor;\n key: string;\n}\n\n/**\n * The containers one `useBloc` consumer reads during a render: its primary\n * bloc plus every dep reached through `this.<handle>.track()`.\n *\n * The render records entries; the commit calls `reconcile`, which subscribes\n * and takes a ref on new deps and drops the ones no longer read. `dispose`\n * releases everything. Refs are only taken at commit, so an uncommitted render\n * never leaks one.\n */\nexport class DepSession {\n // Most consumers read no deps, so everything dep-related is allocated on the\n // first dep a render records.\n private primary: StateContainer | null = null;\n private primaryParts: PathSet[] = [];\n private primaryPaths: PathSet = NO_PATHS;\n private entries: Map<StateContainer, DepEntry> | null = null;\n private subs: Map<StateContainer, DepSub> | null = null;\n private reconciled = false;\n private lastPrimary: StateContainer | null = null;\n private lastPrimaryPaths: PathSet = NO_PATHS;\n private lastEntries: Map<StateContainer, DepEntry> | null = null;\n private disarms: Array<() => void> | null = null;\n\n constructor(\n private readonly consumerId: string,\n private readonly onChange: () => void,\n ) {}\n\n private get refId(): string {\n return `useBloc@${this.consumerId}:dep`;\n }\n\n begin(primary: StateContainer, paths: PathSet): void {\n this.entries?.clear();\n if (this.disarms !== null) this.disarms.length = 0;\n this.primary = primary;\n this.primaryParts = [paths];\n }\n\n record(\n dep: StateContainer,\n paths: PathSet,\n Type: StateContainerConstructor,\n key: string,\n args: unknown,\n disarm: () => void,\n ): void {\n (this.disarms ??= []).push(disarm);\n // A dep that resolves to the primary only widens the primary's entry.\n if (dep === this.primary) {\n this.primaryParts.push(paths);\n return;\n }\n const entries = (this.entries ??= new Map());\n const existing = entries.get(dep);\n if (existing !== undefined) {\n existing.parts.push(paths);\n } else {\n entries.set(dep, { parts: [paths], paths, Type, key, args });\n }\n }\n\n /**\n * Merge each container's per-call sets once the render's reads are done.\n * Returns the primary's paths, widened by deps that resolved to it.\n */\n settle(): PathSet {\n this.primaryPaths = mergeParts(this.primaryParts);\n if (this.entries !== null) {\n for (const entry of this.entries.values()) {\n entry.paths = mergeParts(entry.parts);\n }\n }\n return this.primaryPaths;\n }\n\n /** Stops every dep tracked this render from recording further reads. */\n disarm(): void {\n const disarms = this.disarms;\n if (disarms === null) return;\n for (const disarm of disarms) disarm();\n disarms.length = 0;\n }\n\n /** Forces the next `reconcile` to run in full. */\n invalidate(): void {\n this.reconciled = false;\n }\n\n isUnchanged(): boolean {\n if (\n !this.reconciled ||\n this.lastPrimary !== this.primary ||\n !pathSetEquals(this.lastPrimaryPaths, this.primaryPaths)\n ) {\n return false;\n }\n const last = this.lastEntries;\n const entries = this.entries;\n if ((last?.size ?? 0) !== (entries?.size ?? 0)) return false;\n if (last === null || entries === null) return true;\n for (const [container, entry] of entries) {\n const prev = last.get(container);\n if (prev === undefined || !sameEntry(prev, entry)) return false;\n }\n return true;\n }\n\n reconcile(registry: StateContainerRegistry): void {\n const { consumerId } = this;\n const entries = this.entries;\n this.reconciled = true;\n this.lastPrimary = this.primary;\n this.lastPrimaryPaths = this.primaryPaths;\n this.lastEntries =\n entries === null || entries.size === 0 ? null : new Map(entries);\n\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) {\n if (entries?.has(dep) !== true) {\n this.drop(dep, sub);\n subs.delete(dep);\n }\n }\n }\n if (entries === null) return;\n\n for (const [dep, entry] of entries) {\n // StrictMode's simulated unmount can dispose a dep this render captured;\n // re-render so the session resolves the live instance.\n if (dep.$blac.disposed) {\n this.onChange();\n continue;\n }\n dep.registerConsumerPaths(consumerId, entry.paths);\n const existing = subs?.get(dep);\n if (existing) {\n existing.subscription.setInterest(entry.paths);\n continue;\n }\n const refId = this.refId;\n const live = registry.acquire(entry.Type, entry.key, {\n canCreate: true,\n countRef: true,\n refId,\n args: entry.args,\n });\n // The key now maps to another instance; re-render against that one.\n if (live !== dep) {\n registry.release(entry.Type, entry.key, false, refId);\n this.onChange();\n continue;\n }\n const subscription = dep.subscribePaths(this.onChange);\n subscription.setInterest(entry.paths);\n (this.subs ??= new Map()).set(dep, {\n subscription,\n registry,\n Type: entry.Type,\n key: entry.key,\n });\n }\n }\n\n dispose(): void {\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) this.drop(dep, sub);\n subs.clear();\n }\n this.reconciled = false;\n }\n\n private drop(dep: StateContainer, sub: DepSub): void {\n sub.subscription.unsubscribe();\n dep.unregisterConsumer(this.consumerId);\n sub.registry.release(sub.Type, sub.key, false, this.refId);\n }\n}\n\nfunction sameEntry(a: DepEntry, b: DepEntry): boolean {\n return (\n pathSetEquals(a.paths, b.paths) &&\n a.key === b.key &&\n Object.is(a.args, b.args)\n );\n}\n\nfunction mergeParts(parts: PathSet[]): PathSet {\n if (parts.length === 1) return parts[0];\n const out = new Set<number>();\n for (const part of parts) {\n if (part === ALL_PATHS) return ALL_PATHS;\n for (const id of part) out.add(id);\n }\n return out;\n}\n\ninterface DepAccessOptionsLike {\n args?: unknown;\n}\n\n/** Structural shape of a branded `depend()` handle as seen from React. */\nexport interface 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 * Wrap a dep handle read inside a tracked getter. During a render (`tracked`\n * is set) `.track()` records the dep's read paths into `session` and returns\n * a tracked proxy, so the dep's own getters track too. Outside a render it\n * returns the live `[state, dep]`, like the core handle.\n *\n * Args resolve per call, so one handle can reach several instances; proxies\n * are cached per resolved instance.\n */\nexport function makeDepWrapper(\n handle: DepHandleLike,\n registry: StateContainerRegistry,\n session: DepSession,\n tracked: { current: unknown },\n onDepHandle: (handle: object) => unknown,\n): DepHandleLike {\n const brand = handle[DEP_BRAND];\n // Weak, so instances a getter stops reaching (e.g. by switching args) can\n // be collected once disposed.\n const perDep = new WeakMap<\n StateContainer,\n { ref: { current: unknown }; proxy: StateContainer }\n >();\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.acquire(brand.Type, key, {\n canCreate: true,\n countRef: false,\n args,\n sweepIfUnowned: tracked.current != null,\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 if (tracked.current == null) return [dep.state, dep];\n\n const result = trackRender(dep.state, dep.interner, proxyCache);\n let cache = perDep.get(dep);\n if (cache === undefined) {\n const ref = { current: result.value as unknown };\n cache = { ref, proxy: buildTrackedProxy(dep, ref, onDepHandle) };\n perDep.set(dep, cache);\n } else {\n cache.ref.current = result.value;\n }\n session.record(dep, result.paths, brand.Type, key, args, result.disarm);\n return [result.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","import { useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport {\n isIsolatedClass,\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 trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { useProvidedArgs } from './BlocProvider';\nimport { useRegistry } from './RegistryProvider';\nimport { buildTrackedProxy } from './buildTrackedProxy';\nimport { DepSession, makeDepWrapper, type DepHandleLike } from './depSession';\nimport type { UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\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.\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: mark the class `@blac({ isolated: true })`, or\n * pass a unique key arg yourself (e.g. `useId()` in `args`).\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]`\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 const registry = useRegistry();\n\n const consumerRef = useRef<Consumer | null>(null);\n const consumer = (consumerRef.current ??= createConsumer());\n\n consumer.select = options?.select;\n consumer.onMount = options?.onMount;\n consumer.onUnmount = options?.onUnmount;\n\n consumer.ownArgs = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n consumer.providerArgs = useProvidedArgs(BlocClass);\n const effectiveArgs = resolveEffectiveArgs(consumer);\n const sharedKey = registry.resolveKey(BlocClass, undefined, effectiveArgs);\n const instanceKey = isIsolatedClass(BlocClass)\n ? `${sharedKey}#${consumer.id}`\n : sharedKey;\n\n // A memo kept on the consumer rather than in `useMemo`: no hook, deps array\n // or factory closure per mount. `rebindNonce` is bumped by the commit when\n // the rendered instance was disposed and recreated before commit (a\n // same-commit owner handoff, or StrictMode's double effect); the tracked\n // proxy can't be retargeted in place. Keyed on `BlocClass` as well as the\n // key: classes without args or a `static key` share the default key, so\n // `useBloc(cond ? A : B)` must still re-subscribe on a class swap.\n if (\n consumer.BlocClass !== BlocClass ||\n consumer.instanceKey !== instanceKey ||\n consumer.registry !== registry ||\n consumer.boundNonce !== consumer.rebindNonce\n ) {\n bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);\n }\n\n useSyncExternalStore(\n consumer.subscribe,\n consumer.getSnapshot,\n consumer.getSnapshot,\n );\n\n const container = consumer.container;\n const rawState = container.state as ExtractState<T>;\n consumer.renderedState = rawState;\n const select = consumer.select;\n consumer.isSelectMode = select !== undefined;\n let state: ExtractState<T>;\n if (select !== undefined) {\n state = rawState;\n // Recomputed every render: the selector may close over props.\n consumer.selection = select(rawState, container as InstanceState<T>);\n setInterest(consumer, 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 // Run at commit, so reads that outlive the render (effects, handlers)\n // don't add paths.\n consumer.disarm = tracked.disarm;\n // `tracked.paths` is still empty here and fills during JSX, so paths are\n // registered at commit, not now.\n consumer.deps.begin(container, tracked.paths);\n }\n\n // Stable callbacks that read this render's inputs from the consumer.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.commit);\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.mount, NO_DEPS);\n\n return [state, consumer.trackedBloc] as UseBlocReturn<T, ExtractState<T>>;\n}\n\nconst noop = (): void => {};\n\n// Initial `interest` and `paths`; neither is ever mutated in place.\nconst NO_PATHS: PathSet = emptyPathSet();\nconst NO_DEPS: [] = [];\n\n/**\n * Everything one `useBloc` call keeps between renders. Its callbacks are\n * created once, so a render allocates no closures.\n *\n * `bump` increments `version` before calling `notify`: uSES requires\n * `getSnapshot()` to reflect the change by the time it is notified.\n */\ninterface Consumer {\n id: string;\n primaryRefId: string;\n // The render's inputs, read by the commit of that render.\n BlocClass: StateContainerConstructor;\n instanceKey: string;\n registry: StateContainerRegistry;\n /** Written only by `bind`. */\n container: StateContainer;\n trackedBloc: unknown;\n subscribe: (onStoreChange: () => void) => () => void;\n boundNonce: number;\n rebindNonce: number;\n renderedState: unknown;\n version: number;\n /** `noop` while unsubscribed. */\n notify: () => void;\n bump: () => void;\n getSnapshot: () => number;\n onChange: () => void;\n unsubscribe: () => void;\n commit: () => void;\n mount: () => () => void;\n /** The committed `paths`, or `ALL_PATHS` in select mode. */\n interest: PathSet;\n /** `null` while unsubscribed. */\n subscription: PathSubscription | null;\n subscribedTo: StateContainer | null;\n paths: PathSet;\n isSelectMode: boolean;\n selection: unknown[] | null;\n deps: DepSession;\n // Typed loosely: `T` is per call site.\n select: ((state: any, bloc: any) => unknown[]) | undefined;\n onMount: ((bloc: any) => void) | undefined;\n onUnmount: ((bloc: any) => void) | undefined;\n ownArgs: unknown;\n providerArgs: unknown;\n /** The render's tracking proxy; `null` outside a tracked render. */\n tracked: { current: unknown };\n disarm: (() => void) | null;\n proxyCache: ProxyCache | null;\n // The instance the consumer holds a ref on, claimed at commit so acquire\n // and release pair exactly.\n ownedClass: StateContainerConstructor | null;\n ownedKey: string;\n ownedRegistry: StateContainerRegistry | null;\n ownedBloc: unknown;\n}\n\nfunction createConsumer(): Consumer {\n const id = `useBloc-${nextConsumerId++}`;\n const consumer: Consumer = {\n id,\n primaryRefId: `useBloc@${id}`,\n BlocClass: null as unknown as StateContainerConstructor,\n instanceKey: '',\n registry: null as unknown as StateContainerRegistry,\n container: null as unknown as StateContainer,\n trackedBloc: null,\n subscribe: null as unknown as Consumer['subscribe'],\n boundNonce: 0,\n rebindNonce: 0,\n renderedState: undefined,\n version: 0,\n notify: noop,\n bump: () => {\n consumer.version++;\n consumer.notify();\n },\n getSnapshot: () => consumer.version,\n onChange: () => onChange(consumer),\n unsubscribe: () => unsubscribe(consumer),\n commit: () => commit(consumer),\n mount: () => teardown,\n interest: NO_PATHS,\n subscription: null,\n subscribedTo: null,\n paths: NO_PATHS,\n isSelectMode: false,\n selection: null,\n deps: null as unknown as DepSession,\n select: undefined,\n onMount: undefined,\n onUnmount: undefined,\n ownArgs: undefined,\n providerArgs: undefined,\n tracked: { current: null },\n disarm: null,\n proxyCache: null,\n ownedClass: null,\n ownedKey: '',\n ownedRegistry: null,\n ownedBloc: null,\n };\n const teardown = (): void => {\n release(consumer);\n consumer.deps.dispose();\n };\n consumer.deps = new DepSession(id, consumer.bump);\n return consumer;\n}\n\nfunction bind(\n consumer: Consumer,\n BlocClass: StateContainerConstructor,\n instanceKey: string,\n registry: StateContainerRegistry,\n args: unknown,\n): void {\n // No ref during render; the commit claims it. The registry sweeps the\n // instance if no commit ever does (SSR, discarded renders).\n const instance = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: false,\n args: args as ExtractArgs<StateContainerConstructor>,\n sweepIfUnowned: true,\n }) as unknown as StateContainer;\n\n // Dep proxies get `onDepHandle` too, so nested `.track()` calls (A→B→C)\n // record into this consumer's session.\n let depWrappers: Map<object, unknown> | undefined;\n const onDepHandle = (handle: object): unknown => {\n depWrappers ??= new Map();\n const cached = depWrappers.get(handle);\n if (cached !== undefined) return cached;\n const wrapper = makeDepWrapper(\n handle as DepHandleLike,\n registry,\n consumer.deps,\n consumer.tracked,\n onDepHandle,\n );\n depWrappers.set(handle, wrapper);\n return wrapper;\n };\n\n consumer.BlocClass = BlocClass;\n consumer.instanceKey = instanceKey;\n consumer.registry = registry;\n consumer.boundNonce = consumer.rebindNonce;\n consumer.container = instance;\n consumer.trackedBloc = buildTrackedProxy(\n instance,\n consumer.tracked,\n onDepHandle,\n );\n // A new identity per instance, so uSES re-subscribes.\n consumer.subscribe = (onStoreChange) => subscribe(consumer, onStoreChange);\n}\n\nfunction subscribe(consumer: Consumer, onStoreChange: () => void): () => void {\n consumer.notify = onStoreChange;\n const container = consumer.container;\n const subscription = container.subscribePaths(consumer.onChange);\n subscription.setInterest(consumer.interest);\n consumer.subscription = subscription;\n consumer.subscribedTo = container;\n container.registerConsumerPaths(consumer.id, consumer.paths);\n // An emit raised during render is still pending in the channel, so its\n // next flush delivers it to this subscriber.\n return consumer.unsubscribe;\n}\n\nfunction unsubscribe(consumer: Consumer): void {\n consumer.notify = noop;\n consumer.subscription?.unsubscribe();\n consumer.subscription = null;\n consumer.subscribedTo?.unregisterConsumer(consumer.id);\n consumer.subscribedTo = null;\n}\n\nfunction onChange(consumer: Consumer): void {\n if (consumer.isSelectMode) {\n const select = consumer.select;\n const container = consumer.subscribedTo;\n if (select && container !== null) {\n const next = select(container.state, container);\n const prev = consumer.selection;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n consumer.selection = next;\n }\n }\n consumer.bump();\n}\n\nfunction claim(consumer: Consumer): void {\n const { BlocClass, instanceKey, registry } = consumer;\n if (\n consumer.ownedClass === BlocClass &&\n consumer.ownedKey === instanceKey &&\n consumer.ownedRegistry === registry\n ) {\n return;\n }\n release(consumer);\n const live = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: true,\n refId: consumer.primaryRefId,\n args: resolveEffectiveArgs(\n consumer,\n ) as ExtractArgs<StateContainerConstructor>,\n });\n consumer.ownedClass = BlocClass;\n consumer.ownedKey = instanceKey;\n consumer.ownedRegistry = registry;\n consumer.ownedBloc = live;\n consumer.onMount?.(live);\n // The render captured a stale instance; re-render against the live one.\n if (live !== (consumer.container as unknown)) {\n consumer.rebindNonce++;\n consumer.bump();\n }\n}\n\nfunction release(consumer: Consumer): void {\n const { ownedClass, ownedRegistry } = consumer;\n if (ownedClass === null || ownedRegistry === null) return;\n // Before release, so the bloc is still alive in the callback.\n consumer.onUnmount?.(consumer.ownedBloc);\n consumer.ownedClass = null;\n consumer.ownedRegistry = null;\n ownedRegistry.release(\n ownedClass,\n consumer.ownedKey,\n false,\n consumer.primaryRefId,\n );\n}\n\nfunction commit(consumer: Consumer): void {\n claim(consumer);\n // Getters called after commit read live state, not this render's proxy.\n consumer.tracked.current = null;\n const disarm = consumer.disarm;\n if (disarm !== null) {\n consumer.disarm = null;\n disarm();\n }\n const deps = consumer.deps;\n deps.disarm();\n if (consumer.isSelectMode) {\n // A later switch back to auto-track must reconcile in full.\n deps.invalidate();\n return;\n }\n const primary = consumer.container;\n // A change between render and commit was diffed against the previous\n // interest, which may lack paths only this render read.\n if (primary.state !== consumer.renderedState) consumer.bump();\n consumer.paths = deps.settle();\n if (deps.isUnchanged()) return;\n\n primary.registerConsumerPaths(consumer.id, consumer.paths);\n setInterest(consumer, consumer.paths);\n deps.reconcile(consumer.registry);\n}\n\nconst setInterest = (consumer: Consumer, interest: PathSet): void => {\n consumer.interest = interest;\n consumer.subscription?.setInterest(interest);\n};\n\n// Own args win over provider args. Shared by render and the ownership effect,\n// which recreates a disposed instance and must not drop its args.\nconst resolveEffectiveArgs = (consumer: Consumer): unknown =>\n consumer.ownArgs !== undefined ? consumer.ownArgs : consumer.providerArgs;\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","import { useEffect, useId, useRef } from 'react';\nimport type { StateContainer } from '@blac/core';\nimport { APPLY_DEPS, REMOVE_DEPS_OWNER } from '@blac/core/internal';\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 on unmount. The bloc observes\n * 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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"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { n as useRegistry, t as RegistryProvider } from "./RegistryProvider-BjSXfLeK.js";
1
+ import { n as useRegistry, t as RegistryProvider } from "./RegistryProvider-DS4ltjlH.js";
2
2
  import { createContext, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from "react";
3
+ import { isIsolatedClass } from "@blac/core";
3
4
  import { ALL_PATHS, ProxyCache, emptyPathSet, pathSetEquals, trackRender, untracked } from "@dirtytalk/structural";
4
5
  import { jsx } from "react/jsx-runtime";
5
6
  import { APPLY_DEPS, DEP_BRAND, REMOVE_DEPS_OWNER, WITH_TRACKED_STATE } from "@blac/core/internal";
@@ -151,6 +152,7 @@ var DepSession = class {
151
152
  this.consumerId = consumerId;
152
153
  this.onChange = onChange;
153
154
  this.primary = null;
155
+ this.primaryParts = [];
154
156
  this.primaryPaths = NO_PATHS$1;
155
157
  this.entries = null;
156
158
  this.subs = null;
@@ -167,24 +169,34 @@ var DepSession = class {
167
169
  this.entries?.clear();
168
170
  if (this.disarms !== null) this.disarms.length = 0;
169
171
  this.primary = primary;
170
- this.primaryPaths = paths;
172
+ this.primaryParts = [paths];
171
173
  }
172
174
  record(dep, paths, Type, key, args, disarm) {
173
175
  (this.disarms ??= []).push(disarm);
174
176
  if (dep === this.primary) {
175
- this.primaryPaths = unionPaths(this.primaryPaths, paths);
177
+ this.primaryParts.push(paths);
176
178
  return;
177
179
  }
178
180
  const entries = this.entries ??= /* @__PURE__ */ new Map();
179
181
  const existing = entries.get(dep);
180
- if (existing !== void 0) existing.paths = unionPaths(existing.paths, paths);
182
+ if (existing !== void 0) existing.parts.push(paths);
181
183
  else entries.set(dep, {
184
+ parts: [paths],
182
185
  paths,
183
186
  Type,
184
187
  key,
185
188
  args
186
189
  });
187
190
  }
191
+ /**
192
+ * Merge each container's per-call sets once the render's reads are done.
193
+ * Returns the primary's paths, widened by deps that resolved to it.
194
+ */
195
+ settle() {
196
+ this.primaryPaths = mergeParts(this.primaryParts);
197
+ if (this.entries !== null) for (const entry of this.entries.values()) entry.paths = mergeParts(entry.parts);
198
+ return this.primaryPaths;
199
+ }
188
200
  /** Stops every dep tracked this render from recording further reads. */
189
201
  disarm() {
190
202
  const disarms = this.disarms;
@@ -272,10 +284,13 @@ var DepSession = class {
272
284
  function sameEntry(a, b) {
273
285
  return pathSetEquals(a.paths, b.paths) && a.key === b.key && Object.is(a.args, b.args);
274
286
  }
275
- function unionPaths(a, b) {
276
- if (a === ALL_PATHS || b === ALL_PATHS) return ALL_PATHS;
277
- const out = new Set(a);
278
- for (const id of b) out.add(id);
287
+ function mergeParts(parts) {
288
+ if (parts.length === 1) return parts[0];
289
+ const out = /* @__PURE__ */ new Set();
290
+ for (const part of parts) {
291
+ if (part === ALL_PATHS) return ALL_PATHS;
292
+ for (const id of part) out.add(id);
293
+ }
279
294
  return out;
280
295
  }
281
296
  /**
@@ -289,7 +304,7 @@ function unionPaths(a, b) {
289
304
  */
290
305
  function makeDepWrapper(handle, registry, session, tracked, onDepHandle) {
291
306
  const brand = handle[DEP_BRAND];
292
- const perDep = /* @__PURE__ */ new Map();
307
+ const perDep = /* @__PURE__ */ new WeakMap();
293
308
  const proxyCache = new ProxyCache();
294
309
  const resolve = (options) => {
295
310
  const args = options?.args ?? brand.defaultArgs;
@@ -356,11 +371,8 @@ let nextConsumerId = 0;
356
371
  * fires *before* the registry releases its ref, so the bloc is still
357
372
  * alive when the callback runs.
358
373
  *
359
- * Per-mount private instance:
360
- * ```ts
361
- * const id = useId();
362
- * const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });
363
- * ```
374
+ * Per-mount private instance: mark the class `@blac({ isolated: true })`, or
375
+ * pass a unique key arg yourself (e.g. `useId()` in `args`).
364
376
  *
365
377
  * @typeParam T - The state container constructor type (inferred from BlocClass)
366
378
  * @param BlocClass - The state container class to connect to
@@ -395,7 +407,8 @@ function useBloc(BlocClass, options) {
395
407
  consumer.ownArgs = options?.args;
396
408
  consumer.providerArgs = useProvidedArgs(BlocClass);
397
409
  const effectiveArgs = resolveEffectiveArgs(consumer);
398
- const instanceKey = registry.resolveKey(BlocClass, void 0, effectiveArgs);
410
+ const sharedKey = registry.resolveKey(BlocClass, void 0, effectiveArgs);
411
+ const instanceKey = isIsolatedClass(BlocClass) ? `${sharedKey}#${consumer.id}` : sharedKey;
399
412
  if (consumer.BlocClass !== BlocClass || consumer.instanceKey !== instanceKey || consumer.registry !== registry || consumer.boundNonce !== consumer.rebindNonce) bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);
400
413
  useSyncExternalStore(consumer.subscribe, consumer.getSnapshot, consumer.getSnapshot);
401
414
  const container = consumer.container;
@@ -573,6 +586,7 @@ function commit(consumer) {
573
586
  }
574
587
  const primary = consumer.container;
575
588
  if (primary.state !== consumer.renderedState) consumer.bump();
589
+ consumer.paths = deps.settle();
576
590
  if (deps.isUnchanged()) return;
577
591
  primary.registerConsumerPaths(consumer.id, consumer.paths);
578
592
  setInterest(consumer, consumer.paths);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["NO_PATHS"],"sources":["../src/BlocProvider.tsx","../src/buildTrackedProxy.ts","../src/depSession.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 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 * Each provider copies its parent's map and adds its own entry, so nested\n * providers for different blocs 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. Own `args` on the `useBloc` call always win.\n *\n * Multiple `BlocProvider` wrappers for different bloc classes compose: each\n * merges its entry into the inherited map, so nested providers for different\n * 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 args content, not identity, so an inline `args={{ ... }}`\n // literal does not rebuild the map (and re-render every consumer) on\n // every render, while a real field change still reaches `useProvidedArgs`.\n const argsKey = JSON.stringify(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 { DEP_BRAND, WITH_TRACKED_STATE } from '@blac/core/internal';\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;\ntype Method = (...args: unknown[]) => 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// Methods on the prototype, so a mount allocates the handler and the proxy\n// only, not a set of closures.\nclass BlocHandler implements ProxyHandler<object> {\n // Bound methods are cached so `bloc.method` keeps a stable identity across\n // reads — an unstable one would defeat memoisation in consumers. Keyed by\n // the underlying function too, so a reassigned method is re-bound.\n private boundMethods:\n | Map<string | symbol, { fn: Method; bound: Method }>\n | undefined = undefined;\n\n constructor(\n private readonly getters: Map<string | symbol, Getter>,\n private readonly tracksState: boolean,\n private readonly trackedStateRef: { current: unknown },\n private readonly onDepHandle: ((handle: object) => unknown) | undefined,\n ) {}\n\n get(target: object, key: string | symbol): unknown {\n const getter = this.getters.get(key);\n if (getter !== undefined) {\n const tracked = this.trackedStateRef.current;\n // `[WITH_TRACKED_STATE]` invokes the getter with `this` = the real\n // instance, so no per-read thunk is needed.\n return this.wrapDepHandle(\n tracked == null || !this.tracksState\n ? getter.call(target)\n : (target as TrackedStateTarget)[WITH_TRACKED_STATE](\n tracked,\n getter,\n this.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 const fn = value as Method;\n this.boundMethods ??= new Map();\n let cached = this.boundMethods.get(key);\n if (cached === undefined || cached.fn !== fn) {\n cached = { fn, bound: fn.bind(target) };\n this.boundMethods.set(key, cached);\n }\n return this.wrapDepHandle(cached.bound);\n }\n return this.wrapDepHandle(value);\n }\n\n set(target: object, key: string | symbol, value: unknown): boolean {\n return Reflect.set(target, key, value, target);\n }\n\n private wrapDepHandle(value: unknown): unknown {\n if (\n this.onDepHandle !== undefined &&\n (typeof value === 'function' || typeof value === 'object') &&\n value !== null &&\n (value as Record<symbol, unknown>)[DEP_BRAND] !== undefined\n ) {\n return this.onDepHandle(value as object);\n }\n return value;\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 the 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\n * report the render's tracking proxy for the call's duration; nested getter\n * chains stay tracked because the override remains set.\n *\n * One allocation per bloc acquisition (inside `useMemo`), so the proxy is\n * stable across renders.\n *\n * @param onDepHandle - Invoked when a read returns a branded dep handle;\n * returns the value (the session-bound wrapper) to expose in its place.\n * Must cache 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): 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 handler = new BlocHandler(\n collectGetters(Object.getPrototypeOf(instance) as object),\n supportsTrackedState(instance),\n trackedStateRef,\n onDepHandle,\n );\n return new Proxy(instance, handler as ProxyHandler<T>);\n}\n","import type {\n StateContainer,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport { DEP_BRAND } from '@blac/core/internal';\nimport {\n ALL_PATHS,\n emptyPathSet,\n pathSetEquals,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { buildTrackedProxy } from './buildTrackedProxy';\n\nconst NO_PATHS: PathSet = emptyPathSet();\n\ninterface DepEntry {\n paths: PathSet;\n Type: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\ninterface DepSub {\n subscription: PathSubscription;\n registry: StateContainerRegistry;\n Type: StateContainerConstructor;\n key: string;\n}\n\n/**\n * The containers one `useBloc` consumer reads during a render: its primary\n * bloc plus every dep reached through `this.<handle>.track()`.\n *\n * The render records entries; the commit calls `reconcile`, which subscribes\n * and takes a ref on new deps and drops the ones no longer read. `dispose`\n * releases everything. Refs are only taken at commit, so an uncommitted render\n * never leaks one.\n */\nexport class DepSession {\n // Most consumers read no deps, so everything dep-related is allocated on the\n // first dep a render records.\n private primary: StateContainer | null = null;\n private primaryPaths: PathSet = NO_PATHS;\n private entries: Map<StateContainer, DepEntry> | null = null;\n private subs: Map<StateContainer, DepSub> | null = null;\n private reconciled = false;\n private lastPrimary: StateContainer | null = null;\n private lastPrimaryPaths: PathSet = NO_PATHS;\n private lastEntries: Map<StateContainer, DepEntry> | null = null;\n private disarms: Array<() => void> | null = null;\n\n constructor(\n private readonly consumerId: string,\n private readonly onChange: () => void,\n ) {}\n\n private get refId(): string {\n return `useBloc@${this.consumerId}:dep`;\n }\n\n begin(primary: StateContainer, paths: PathSet): void {\n this.entries?.clear();\n if (this.disarms !== null) this.disarms.length = 0;\n this.primary = primary;\n this.primaryPaths = paths;\n }\n\n record(\n dep: StateContainer,\n paths: PathSet,\n Type: StateContainerConstructor,\n key: string,\n args: unknown,\n disarm: () => void,\n ): void {\n (this.disarms ??= []).push(disarm);\n // A dep that resolves to the primary only widens the primary's entry.\n if (dep === this.primary) {\n this.primaryPaths = unionPaths(this.primaryPaths, paths);\n return;\n }\n const entries = (this.entries ??= new Map());\n const existing = entries.get(dep);\n if (existing !== undefined) {\n existing.paths = unionPaths(existing.paths, paths);\n } else {\n entries.set(dep, { paths, Type, key, args });\n }\n }\n\n /** Stops every dep tracked this render from recording further reads. */\n disarm(): void {\n const disarms = this.disarms;\n if (disarms === null) return;\n for (const disarm of disarms) disarm();\n disarms.length = 0;\n }\n\n /** Forces the next `reconcile` to run in full. */\n invalidate(): void {\n this.reconciled = false;\n }\n\n isUnchanged(): boolean {\n if (\n !this.reconciled ||\n this.lastPrimary !== this.primary ||\n !pathSetEquals(this.lastPrimaryPaths, this.primaryPaths)\n ) {\n return false;\n }\n const last = this.lastEntries;\n const entries = this.entries;\n if ((last?.size ?? 0) !== (entries?.size ?? 0)) return false;\n if (last === null || entries === null) return true;\n for (const [container, entry] of entries) {\n const prev = last.get(container);\n if (prev === undefined || !sameEntry(prev, entry)) return false;\n }\n return true;\n }\n\n reconcile(registry: StateContainerRegistry): void {\n const { consumerId } = this;\n const entries = this.entries;\n this.reconciled = true;\n this.lastPrimary = this.primary;\n this.lastPrimaryPaths = this.primaryPaths;\n this.lastEntries =\n entries === null || entries.size === 0 ? null : new Map(entries);\n\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) {\n if (entries?.has(dep) !== true) {\n this.drop(dep, sub);\n subs.delete(dep);\n }\n }\n }\n if (entries === null) return;\n\n for (const [dep, entry] of entries) {\n // StrictMode's simulated unmount can dispose a dep this render captured;\n // re-render so the session resolves the live instance.\n if (dep.$blac.disposed) {\n this.onChange();\n continue;\n }\n dep.registerConsumerPaths(consumerId, entry.paths);\n const existing = subs?.get(dep);\n if (existing) {\n existing.subscription.setInterest(entry.paths);\n continue;\n }\n const refId = this.refId;\n const live = registry.acquire(entry.Type, entry.key, {\n canCreate: true,\n countRef: true,\n refId,\n args: entry.args,\n });\n // The key now maps to another instance; re-render against that one.\n if (live !== dep) {\n registry.release(entry.Type, entry.key, false, refId);\n this.onChange();\n continue;\n }\n const subscription = dep.subscribePaths(this.onChange);\n subscription.setInterest(entry.paths);\n (this.subs ??= new Map()).set(dep, {\n subscription,\n registry,\n Type: entry.Type,\n key: entry.key,\n });\n }\n }\n\n dispose(): void {\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) this.drop(dep, sub);\n subs.clear();\n }\n this.reconciled = false;\n }\n\n private drop(dep: StateContainer, sub: DepSub): void {\n sub.subscription.unsubscribe();\n dep.unregisterConsumer(this.consumerId);\n sub.registry.release(sub.Type, sub.key, false, this.refId);\n }\n}\n\nfunction sameEntry(a: DepEntry, b: DepEntry): boolean {\n return (\n pathSetEquals(a.paths, b.paths) &&\n a.key === b.key &&\n Object.is(a.args, b.args)\n );\n}\n\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\ninterface DepAccessOptionsLike {\n args?: unknown;\n}\n\n/** Structural shape of a branded `depend()` handle as seen from React. */\nexport interface 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 * Wrap a dep handle read inside a tracked getter. During a render (`tracked`\n * is set) `.track()` records the dep's read paths into `session` and returns\n * a tracked proxy, so the dep's own getters track too. Outside a render it\n * returns the live `[state, dep]`, like the core handle.\n *\n * Args resolve per call, so one handle can reach several instances; proxies\n * are cached per resolved instance.\n */\nexport function makeDepWrapper(\n handle: DepHandleLike,\n registry: StateContainerRegistry,\n session: DepSession,\n tracked: { current: unknown },\n onDepHandle: (handle: object) => unknown,\n): DepHandleLike {\n const brand = handle[DEP_BRAND];\n const perDep = new Map<\n StateContainer,\n { ref: { current: unknown }; proxy: StateContainer }\n >();\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.acquire(brand.Type, key, {\n canCreate: true,\n countRef: false,\n args,\n sweepIfUnowned: tracked.current != null,\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 if (tracked.current == null) return [dep.state, dep];\n\n const result = trackRender(dep.state, dep.interner, proxyCache);\n let cache = perDep.get(dep);\n if (cache === undefined) {\n const ref = { current: result.value as unknown };\n cache = { ref, proxy: buildTrackedProxy(dep, ref, onDepHandle) };\n perDep.set(dep, cache);\n } else {\n cache.ref.current = result.value;\n }\n session.record(dep, result.paths, brand.Type, key, args, result.disarm);\n return [result.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","import { useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport {\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 trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { useProvidedArgs } from './BlocProvider';\nimport { useRegistry } from './RegistryProvider';\nimport { buildTrackedProxy } from './buildTrackedProxy';\nimport { DepSession, makeDepWrapper, type DepHandleLike } from './depSession';\nimport type { UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\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.\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]`\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 const registry = useRegistry();\n\n const consumerRef = useRef<Consumer | null>(null);\n const consumer = (consumerRef.current ??= createConsumer());\n\n consumer.select = options?.select;\n consumer.onMount = options?.onMount;\n consumer.onUnmount = options?.onUnmount;\n\n consumer.ownArgs = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n consumer.providerArgs = useProvidedArgs(BlocClass);\n const effectiveArgs = resolveEffectiveArgs(consumer);\n const instanceKey = registry.resolveKey(BlocClass, undefined, effectiveArgs);\n\n // A memo kept on the consumer rather than in `useMemo`: no hook, deps array\n // or factory closure per mount. `rebindNonce` is bumped by the commit when\n // the rendered instance was disposed and recreated before commit (a\n // same-commit owner handoff, or StrictMode's double effect); the tracked\n // proxy can't be retargeted in place. Keyed on `BlocClass` as well as the\n // key: classes without args or a `static key` share the default key, so\n // `useBloc(cond ? A : B)` must still re-subscribe on a class swap.\n if (\n consumer.BlocClass !== BlocClass ||\n consumer.instanceKey !== instanceKey ||\n consumer.registry !== registry ||\n consumer.boundNonce !== consumer.rebindNonce\n ) {\n bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);\n }\n\n useSyncExternalStore(\n consumer.subscribe,\n consumer.getSnapshot,\n consumer.getSnapshot,\n );\n\n const container = consumer.container;\n const rawState = container.state as ExtractState<T>;\n consumer.renderedState = rawState;\n const select = consumer.select;\n consumer.isSelectMode = select !== undefined;\n let state: ExtractState<T>;\n if (select !== undefined) {\n state = rawState;\n // Recomputed every render: the selector may close over props.\n consumer.selection = select(rawState, container as InstanceState<T>);\n setInterest(consumer, 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 // Run at commit, so reads that outlive the render (effects, handlers)\n // don't add paths.\n consumer.disarm = tracked.disarm;\n // `tracked.paths` is still empty here and fills during JSX, so paths are\n // registered at commit, not now.\n consumer.deps.begin(container, tracked.paths);\n }\n\n // Stable callbacks that read this render's inputs from the consumer.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.commit);\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.mount, NO_DEPS);\n\n return [state, consumer.trackedBloc] as UseBlocReturn<T, ExtractState<T>>;\n}\n\nconst noop = (): void => {};\n\n// Initial `interest` and `paths`; neither is ever mutated in place.\nconst NO_PATHS: PathSet = emptyPathSet();\nconst NO_DEPS: [] = [];\n\n/**\n * Everything one `useBloc` call keeps between renders. Its callbacks are\n * created once, so a render allocates no closures.\n *\n * `bump` increments `version` before calling `notify`: uSES requires\n * `getSnapshot()` to reflect the change by the time it is notified.\n */\ninterface Consumer {\n id: string;\n primaryRefId: string;\n // The render's inputs, read by the commit of that render.\n BlocClass: StateContainerConstructor;\n instanceKey: string;\n registry: StateContainerRegistry;\n /** Written only by `bind`. */\n container: StateContainer;\n trackedBloc: unknown;\n subscribe: (onStoreChange: () => void) => () => void;\n boundNonce: number;\n rebindNonce: number;\n renderedState: unknown;\n version: number;\n /** `noop` while unsubscribed. */\n notify: () => void;\n bump: () => void;\n getSnapshot: () => number;\n onChange: () => void;\n unsubscribe: () => void;\n commit: () => void;\n mount: () => () => void;\n /** The committed `paths`, or `ALL_PATHS` in select mode. */\n interest: PathSet;\n /** `null` while unsubscribed. */\n subscription: PathSubscription | null;\n subscribedTo: StateContainer | null;\n paths: PathSet;\n isSelectMode: boolean;\n selection: unknown[] | null;\n deps: DepSession;\n // Typed loosely: `T` is per call site.\n select: ((state: any, bloc: any) => unknown[]) | undefined;\n onMount: ((bloc: any) => void) | undefined;\n onUnmount: ((bloc: any) => void) | undefined;\n ownArgs: unknown;\n providerArgs: unknown;\n /** The render's tracking proxy; `null` outside a tracked render. */\n tracked: { current: unknown };\n disarm: (() => void) | null;\n proxyCache: ProxyCache | null;\n // The instance the consumer holds a ref on, claimed at commit so acquire\n // and release pair exactly.\n ownedClass: StateContainerConstructor | null;\n ownedKey: string;\n ownedRegistry: StateContainerRegistry | null;\n ownedBloc: unknown;\n}\n\nfunction createConsumer(): Consumer {\n const id = `useBloc-${nextConsumerId++}`;\n const consumer: Consumer = {\n id,\n primaryRefId: `useBloc@${id}`,\n BlocClass: null as unknown as StateContainerConstructor,\n instanceKey: '',\n registry: null as unknown as StateContainerRegistry,\n container: null as unknown as StateContainer,\n trackedBloc: null,\n subscribe: null as unknown as Consumer['subscribe'],\n boundNonce: 0,\n rebindNonce: 0,\n renderedState: undefined,\n version: 0,\n notify: noop,\n bump: () => {\n consumer.version++;\n consumer.notify();\n },\n getSnapshot: () => consumer.version,\n onChange: () => onChange(consumer),\n unsubscribe: () => unsubscribe(consumer),\n commit: () => commit(consumer),\n mount: () => teardown,\n interest: NO_PATHS,\n subscription: null,\n subscribedTo: null,\n paths: NO_PATHS,\n isSelectMode: false,\n selection: null,\n deps: null as unknown as DepSession,\n select: undefined,\n onMount: undefined,\n onUnmount: undefined,\n ownArgs: undefined,\n providerArgs: undefined,\n tracked: { current: null },\n disarm: null,\n proxyCache: null,\n ownedClass: null,\n ownedKey: '',\n ownedRegistry: null,\n ownedBloc: null,\n };\n const teardown = (): void => {\n release(consumer);\n consumer.deps.dispose();\n };\n consumer.deps = new DepSession(id, consumer.bump);\n return consumer;\n}\n\nfunction bind(\n consumer: Consumer,\n BlocClass: StateContainerConstructor,\n instanceKey: string,\n registry: StateContainerRegistry,\n args: unknown,\n): void {\n // No ref during render; the commit claims it. The registry sweeps the\n // instance if no commit ever does (SSR, discarded renders).\n const instance = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: false,\n args: args as ExtractArgs<StateContainerConstructor>,\n sweepIfUnowned: true,\n }) as unknown as StateContainer;\n\n // Dep proxies get `onDepHandle` too, so nested `.track()` calls (A→B→C)\n // record into this consumer's session.\n let depWrappers: Map<object, unknown> | undefined;\n const onDepHandle = (handle: object): unknown => {\n depWrappers ??= new Map();\n const cached = depWrappers.get(handle);\n if (cached !== undefined) return cached;\n const wrapper = makeDepWrapper(\n handle as DepHandleLike,\n registry,\n consumer.deps,\n consumer.tracked,\n onDepHandle,\n );\n depWrappers.set(handle, wrapper);\n return wrapper;\n };\n\n consumer.BlocClass = BlocClass;\n consumer.instanceKey = instanceKey;\n consumer.registry = registry;\n consumer.boundNonce = consumer.rebindNonce;\n consumer.container = instance;\n consumer.trackedBloc = buildTrackedProxy(\n instance,\n consumer.tracked,\n onDepHandle,\n );\n // A new identity per instance, so uSES re-subscribes.\n consumer.subscribe = (onStoreChange) => subscribe(consumer, onStoreChange);\n}\n\nfunction subscribe(consumer: Consumer, onStoreChange: () => void): () => void {\n consumer.notify = onStoreChange;\n const container = consumer.container;\n const subscription = container.subscribePaths(consumer.onChange);\n subscription.setInterest(consumer.interest);\n consumer.subscription = subscription;\n consumer.subscribedTo = container;\n container.registerConsumerPaths(consumer.id, consumer.paths);\n // An emit raised during render is still pending in the channel, so its\n // next flush delivers it to this subscriber.\n return consumer.unsubscribe;\n}\n\nfunction unsubscribe(consumer: Consumer): void {\n consumer.notify = noop;\n consumer.subscription?.unsubscribe();\n consumer.subscription = null;\n consumer.subscribedTo?.unregisterConsumer(consumer.id);\n consumer.subscribedTo = null;\n}\n\nfunction onChange(consumer: Consumer): void {\n if (consumer.isSelectMode) {\n const select = consumer.select;\n const container = consumer.subscribedTo;\n if (select && container !== null) {\n const next = select(container.state, container);\n const prev = consumer.selection;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n consumer.selection = next;\n }\n }\n consumer.bump();\n}\n\nfunction claim(consumer: Consumer): void {\n const { BlocClass, instanceKey, registry } = consumer;\n if (\n consumer.ownedClass === BlocClass &&\n consumer.ownedKey === instanceKey &&\n consumer.ownedRegistry === registry\n ) {\n return;\n }\n release(consumer);\n const live = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: true,\n refId: consumer.primaryRefId,\n args: resolveEffectiveArgs(\n consumer,\n ) as ExtractArgs<StateContainerConstructor>,\n });\n consumer.ownedClass = BlocClass;\n consumer.ownedKey = instanceKey;\n consumer.ownedRegistry = registry;\n consumer.ownedBloc = live;\n consumer.onMount?.(live);\n // The render captured a stale instance; re-render against the live one.\n if (live !== (consumer.container as unknown)) {\n consumer.rebindNonce++;\n consumer.bump();\n }\n}\n\nfunction release(consumer: Consumer): void {\n const { ownedClass, ownedRegistry } = consumer;\n if (ownedClass === null || ownedRegistry === null) return;\n // Before release, so the bloc is still alive in the callback.\n consumer.onUnmount?.(consumer.ownedBloc);\n consumer.ownedClass = null;\n consumer.ownedRegistry = null;\n ownedRegistry.release(\n ownedClass,\n consumer.ownedKey,\n false,\n consumer.primaryRefId,\n );\n}\n\nfunction commit(consumer: Consumer): void {\n claim(consumer);\n // Getters called after commit read live state, not this render's proxy.\n consumer.tracked.current = null;\n const disarm = consumer.disarm;\n if (disarm !== null) {\n consumer.disarm = null;\n disarm();\n }\n const deps = consumer.deps;\n deps.disarm();\n if (consumer.isSelectMode) {\n // A later switch back to auto-track must reconcile in full.\n deps.invalidate();\n return;\n }\n const primary = consumer.container;\n // A change between render and commit was diffed against the previous\n // interest, which may lack paths only this render read.\n if (primary.state !== consumer.renderedState) consumer.bump();\n if (deps.isUnchanged()) return;\n\n primary.registerConsumerPaths(consumer.id, consumer.paths);\n setInterest(consumer, consumer.paths);\n deps.reconcile(consumer.registry);\n}\n\nconst setInterest = (consumer: Consumer, interest: PathSet): void => {\n consumer.interest = interest;\n consumer.subscription?.setInterest(interest);\n};\n\n// Own args win over provider args. Shared by render and the ownership effect,\n// which recreates a disposed instance and must not drop its args.\nconst resolveEffectiveArgs = (consumer: Consumer): unknown =>\n consumer.ownArgs !== undefined ? consumer.ownArgs : consumer.providerArgs;\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","import { useEffect, useId, useRef } from 'react';\nimport type { StateContainer } from '@blac/core';\nimport { APPLY_DEPS, REMOVE_DEPS_OWNER } from '@blac/core/internal';\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 on unmount. The bloc observes\n * 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.js","names":["NO_PATHS"],"sources":["../src/BlocProvider.tsx","../src/buildTrackedProxy.ts","../src/depSession.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 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 * Each provider copies its parent's map and adds its own entry, so nested\n * providers for different blocs 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. Own `args` on the `useBloc` call always win.\n *\n * Multiple `BlocProvider` wrappers for different bloc classes compose: each\n * merges its entry into the inherited map, so nested providers for different\n * 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 args content, not identity, so an inline `args={{ ... }}`\n // literal does not rebuild the map (and re-render every consumer) on\n // every render, while a real field change still reaches `useProvidedArgs`.\n const argsKey = JSON.stringify(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 { DEP_BRAND, WITH_TRACKED_STATE } from '@blac/core/internal';\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;\ntype Method = (...args: unknown[]) => 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// Methods on the prototype, so a mount allocates the handler and the proxy\n// only, not a set of closures.\nclass BlocHandler implements ProxyHandler<object> {\n // Bound methods are cached so `bloc.method` keeps a stable identity across\n // reads — an unstable one would defeat memoisation in consumers. Keyed by\n // the underlying function too, so a reassigned method is re-bound.\n private boundMethods:\n | Map<string | symbol, { fn: Method; bound: Method }>\n | undefined = undefined;\n\n constructor(\n private readonly getters: Map<string | symbol, Getter>,\n private readonly tracksState: boolean,\n private readonly trackedStateRef: { current: unknown },\n private readonly onDepHandle: ((handle: object) => unknown) | undefined,\n ) {}\n\n get(target: object, key: string | symbol): unknown {\n const getter = this.getters.get(key);\n if (getter !== undefined) {\n const tracked = this.trackedStateRef.current;\n // `[WITH_TRACKED_STATE]` invokes the getter with `this` = the real\n // instance, so no per-read thunk is needed.\n return this.wrapDepHandle(\n tracked == null || !this.tracksState\n ? getter.call(target)\n : (target as TrackedStateTarget)[WITH_TRACKED_STATE](\n tracked,\n getter,\n this.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 const fn = value as Method;\n this.boundMethods ??= new Map();\n let cached = this.boundMethods.get(key);\n if (cached === undefined || cached.fn !== fn) {\n cached = { fn, bound: fn.bind(target) };\n this.boundMethods.set(key, cached);\n }\n return this.wrapDepHandle(cached.bound);\n }\n return this.wrapDepHandle(value);\n }\n\n set(target: object, key: string | symbol, value: unknown): boolean {\n return Reflect.set(target, key, value, target);\n }\n\n private wrapDepHandle(value: unknown): unknown {\n if (\n this.onDepHandle !== undefined &&\n (typeof value === 'function' || typeof value === 'object') &&\n value !== null &&\n (value as Record<symbol, unknown>)[DEP_BRAND] !== undefined\n ) {\n return this.onDepHandle(value as object);\n }\n return value;\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 the 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\n * report the render's tracking proxy for the call's duration; nested getter\n * chains stay tracked because the override remains set.\n *\n * One allocation per bloc acquisition (inside `useMemo`), so the proxy is\n * stable across renders.\n *\n * @param onDepHandle - Invoked when a read returns a branded dep handle;\n * returns the value (the session-bound wrapper) to expose in its place.\n * Must cache 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): 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 handler = new BlocHandler(\n collectGetters(Object.getPrototypeOf(instance) as object),\n supportsTrackedState(instance),\n trackedStateRef,\n onDepHandle,\n );\n return new Proxy(instance, handler as ProxyHandler<T>);\n}\n","import type {\n StateContainer,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport { DEP_BRAND } from '@blac/core/internal';\nimport {\n ALL_PATHS,\n emptyPathSet,\n pathSetEquals,\n trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { buildTrackedProxy } from './buildTrackedProxy';\n\nconst NO_PATHS: PathSet = emptyPathSet();\n\ninterface DepEntry {\n // One live set per `.track()` call, merged at commit: reads keep landing in\n // each call's own set until the render ends.\n parts: PathSet[];\n paths: PathSet;\n Type: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\ninterface DepSub {\n subscription: PathSubscription;\n registry: StateContainerRegistry;\n Type: StateContainerConstructor;\n key: string;\n}\n\n/**\n * The containers one `useBloc` consumer reads during a render: its primary\n * bloc plus every dep reached through `this.<handle>.track()`.\n *\n * The render records entries; the commit calls `reconcile`, which subscribes\n * and takes a ref on new deps and drops the ones no longer read. `dispose`\n * releases everything. Refs are only taken at commit, so an uncommitted render\n * never leaks one.\n */\nexport class DepSession {\n // Most consumers read no deps, so everything dep-related is allocated on the\n // first dep a render records.\n private primary: StateContainer | null = null;\n private primaryParts: PathSet[] = [];\n private primaryPaths: PathSet = NO_PATHS;\n private entries: Map<StateContainer, DepEntry> | null = null;\n private subs: Map<StateContainer, DepSub> | null = null;\n private reconciled = false;\n private lastPrimary: StateContainer | null = null;\n private lastPrimaryPaths: PathSet = NO_PATHS;\n private lastEntries: Map<StateContainer, DepEntry> | null = null;\n private disarms: Array<() => void> | null = null;\n\n constructor(\n private readonly consumerId: string,\n private readonly onChange: () => void,\n ) {}\n\n private get refId(): string {\n return `useBloc@${this.consumerId}:dep`;\n }\n\n begin(primary: StateContainer, paths: PathSet): void {\n this.entries?.clear();\n if (this.disarms !== null) this.disarms.length = 0;\n this.primary = primary;\n this.primaryParts = [paths];\n }\n\n record(\n dep: StateContainer,\n paths: PathSet,\n Type: StateContainerConstructor,\n key: string,\n args: unknown,\n disarm: () => void,\n ): void {\n (this.disarms ??= []).push(disarm);\n // A dep that resolves to the primary only widens the primary's entry.\n if (dep === this.primary) {\n this.primaryParts.push(paths);\n return;\n }\n const entries = (this.entries ??= new Map());\n const existing = entries.get(dep);\n if (existing !== undefined) {\n existing.parts.push(paths);\n } else {\n entries.set(dep, { parts: [paths], paths, Type, key, args });\n }\n }\n\n /**\n * Merge each container's per-call sets once the render's reads are done.\n * Returns the primary's paths, widened by deps that resolved to it.\n */\n settle(): PathSet {\n this.primaryPaths = mergeParts(this.primaryParts);\n if (this.entries !== null) {\n for (const entry of this.entries.values()) {\n entry.paths = mergeParts(entry.parts);\n }\n }\n return this.primaryPaths;\n }\n\n /** Stops every dep tracked this render from recording further reads. */\n disarm(): void {\n const disarms = this.disarms;\n if (disarms === null) return;\n for (const disarm of disarms) disarm();\n disarms.length = 0;\n }\n\n /** Forces the next `reconcile` to run in full. */\n invalidate(): void {\n this.reconciled = false;\n }\n\n isUnchanged(): boolean {\n if (\n !this.reconciled ||\n this.lastPrimary !== this.primary ||\n !pathSetEquals(this.lastPrimaryPaths, this.primaryPaths)\n ) {\n return false;\n }\n const last = this.lastEntries;\n const entries = this.entries;\n if ((last?.size ?? 0) !== (entries?.size ?? 0)) return false;\n if (last === null || entries === null) return true;\n for (const [container, entry] of entries) {\n const prev = last.get(container);\n if (prev === undefined || !sameEntry(prev, entry)) return false;\n }\n return true;\n }\n\n reconcile(registry: StateContainerRegistry): void {\n const { consumerId } = this;\n const entries = this.entries;\n this.reconciled = true;\n this.lastPrimary = this.primary;\n this.lastPrimaryPaths = this.primaryPaths;\n this.lastEntries =\n entries === null || entries.size === 0 ? null : new Map(entries);\n\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) {\n if (entries?.has(dep) !== true) {\n this.drop(dep, sub);\n subs.delete(dep);\n }\n }\n }\n if (entries === null) return;\n\n for (const [dep, entry] of entries) {\n // StrictMode's simulated unmount can dispose a dep this render captured;\n // re-render so the session resolves the live instance.\n if (dep.$blac.disposed) {\n this.onChange();\n continue;\n }\n dep.registerConsumerPaths(consumerId, entry.paths);\n const existing = subs?.get(dep);\n if (existing) {\n existing.subscription.setInterest(entry.paths);\n continue;\n }\n const refId = this.refId;\n const live = registry.acquire(entry.Type, entry.key, {\n canCreate: true,\n countRef: true,\n refId,\n args: entry.args,\n });\n // The key now maps to another instance; re-render against that one.\n if (live !== dep) {\n registry.release(entry.Type, entry.key, false, refId);\n this.onChange();\n continue;\n }\n const subscription = dep.subscribePaths(this.onChange);\n subscription.setInterest(entry.paths);\n (this.subs ??= new Map()).set(dep, {\n subscription,\n registry,\n Type: entry.Type,\n key: entry.key,\n });\n }\n }\n\n dispose(): void {\n const subs = this.subs;\n if (subs !== null) {\n for (const [dep, sub] of subs) this.drop(dep, sub);\n subs.clear();\n }\n this.reconciled = false;\n }\n\n private drop(dep: StateContainer, sub: DepSub): void {\n sub.subscription.unsubscribe();\n dep.unregisterConsumer(this.consumerId);\n sub.registry.release(sub.Type, sub.key, false, this.refId);\n }\n}\n\nfunction sameEntry(a: DepEntry, b: DepEntry): boolean {\n return (\n pathSetEquals(a.paths, b.paths) &&\n a.key === b.key &&\n Object.is(a.args, b.args)\n );\n}\n\nfunction mergeParts(parts: PathSet[]): PathSet {\n if (parts.length === 1) return parts[0];\n const out = new Set<number>();\n for (const part of parts) {\n if (part === ALL_PATHS) return ALL_PATHS;\n for (const id of part) out.add(id);\n }\n return out;\n}\n\ninterface DepAccessOptionsLike {\n args?: unknown;\n}\n\n/** Structural shape of a branded `depend()` handle as seen from React. */\nexport interface 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 * Wrap a dep handle read inside a tracked getter. During a render (`tracked`\n * is set) `.track()` records the dep's read paths into `session` and returns\n * a tracked proxy, so the dep's own getters track too. Outside a render it\n * returns the live `[state, dep]`, like the core handle.\n *\n * Args resolve per call, so one handle can reach several instances; proxies\n * are cached per resolved instance.\n */\nexport function makeDepWrapper(\n handle: DepHandleLike,\n registry: StateContainerRegistry,\n session: DepSession,\n tracked: { current: unknown },\n onDepHandle: (handle: object) => unknown,\n): DepHandleLike {\n const brand = handle[DEP_BRAND];\n // Weak, so instances a getter stops reaching (e.g. by switching args) can\n // be collected once disposed.\n const perDep = new WeakMap<\n StateContainer,\n { ref: { current: unknown }; proxy: StateContainer }\n >();\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.acquire(brand.Type, key, {\n canCreate: true,\n countRef: false,\n args,\n sweepIfUnowned: tracked.current != null,\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 if (tracked.current == null) return [dep.state, dep];\n\n const result = trackRender(dep.state, dep.interner, proxyCache);\n let cache = perDep.get(dep);\n if (cache === undefined) {\n const ref = { current: result.value as unknown };\n cache = { ref, proxy: buildTrackedProxy(dep, ref, onDepHandle) };\n perDep.set(dep, cache);\n } else {\n cache.ref.current = result.value;\n }\n session.record(dep, result.paths, brand.Type, key, args, result.disarm);\n return [result.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","import { useLayoutEffect, useRef, useSyncExternalStore } from 'react';\nimport {\n isIsolatedClass,\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 trackRender,\n ProxyCache,\n type PathSet,\n type PathSubscription,\n} from '@dirtytalk/structural';\nimport { useProvidedArgs } from './BlocProvider';\nimport { useRegistry } from './RegistryProvider';\nimport { buildTrackedProxy } from './buildTrackedProxy';\nimport { DepSession, makeDepWrapper, type DepHandleLike } from './depSession';\nimport type { UseBlocOptions, UseBlocReturn } from './types';\n\nlet nextConsumerId = 0;\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.\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: mark the class `@blac({ isolated: true })`, or\n * pass a unique key arg yourself (e.g. `useId()` in `args`).\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]`\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 const registry = useRegistry();\n\n const consumerRef = useRef<Consumer | null>(null);\n const consumer = (consumerRef.current ??= createConsumer());\n\n consumer.select = options?.select;\n consumer.onMount = options?.onMount;\n consumer.onUnmount = options?.onUnmount;\n\n consumer.ownArgs = (options as { args?: ExtractArgs<T> } | undefined)?.args;\n consumer.providerArgs = useProvidedArgs(BlocClass);\n const effectiveArgs = resolveEffectiveArgs(consumer);\n const sharedKey = registry.resolveKey(BlocClass, undefined, effectiveArgs);\n const instanceKey = isIsolatedClass(BlocClass)\n ? `${sharedKey}#${consumer.id}`\n : sharedKey;\n\n // A memo kept on the consumer rather than in `useMemo`: no hook, deps array\n // or factory closure per mount. `rebindNonce` is bumped by the commit when\n // the rendered instance was disposed and recreated before commit (a\n // same-commit owner handoff, or StrictMode's double effect); the tracked\n // proxy can't be retargeted in place. Keyed on `BlocClass` as well as the\n // key: classes without args or a `static key` share the default key, so\n // `useBloc(cond ? A : B)` must still re-subscribe on a class swap.\n if (\n consumer.BlocClass !== BlocClass ||\n consumer.instanceKey !== instanceKey ||\n consumer.registry !== registry ||\n consumer.boundNonce !== consumer.rebindNonce\n ) {\n bind(consumer, BlocClass, instanceKey, registry, effectiveArgs);\n }\n\n useSyncExternalStore(\n consumer.subscribe,\n consumer.getSnapshot,\n consumer.getSnapshot,\n );\n\n const container = consumer.container;\n const rawState = container.state as ExtractState<T>;\n consumer.renderedState = rawState;\n const select = consumer.select;\n consumer.isSelectMode = select !== undefined;\n let state: ExtractState<T>;\n if (select !== undefined) {\n state = rawState;\n // Recomputed every render: the selector may close over props.\n consumer.selection = select(rawState, container as InstanceState<T>);\n setInterest(consumer, 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 // Run at commit, so reads that outlive the render (effects, handlers)\n // don't add paths.\n consumer.disarm = tracked.disarm;\n // `tracked.paths` is still empty here and fills during JSX, so paths are\n // registered at commit, not now.\n consumer.deps.begin(container, tracked.paths);\n }\n\n // Stable callbacks that read this render's inputs from the consumer.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.commit);\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(consumer.mount, NO_DEPS);\n\n return [state, consumer.trackedBloc] as UseBlocReturn<T, ExtractState<T>>;\n}\n\nconst noop = (): void => {};\n\n// Initial `interest` and `paths`; neither is ever mutated in place.\nconst NO_PATHS: PathSet = emptyPathSet();\nconst NO_DEPS: [] = [];\n\n/**\n * Everything one `useBloc` call keeps between renders. Its callbacks are\n * created once, so a render allocates no closures.\n *\n * `bump` increments `version` before calling `notify`: uSES requires\n * `getSnapshot()` to reflect the change by the time it is notified.\n */\ninterface Consumer {\n id: string;\n primaryRefId: string;\n // The render's inputs, read by the commit of that render.\n BlocClass: StateContainerConstructor;\n instanceKey: string;\n registry: StateContainerRegistry;\n /** Written only by `bind`. */\n container: StateContainer;\n trackedBloc: unknown;\n subscribe: (onStoreChange: () => void) => () => void;\n boundNonce: number;\n rebindNonce: number;\n renderedState: unknown;\n version: number;\n /** `noop` while unsubscribed. */\n notify: () => void;\n bump: () => void;\n getSnapshot: () => number;\n onChange: () => void;\n unsubscribe: () => void;\n commit: () => void;\n mount: () => () => void;\n /** The committed `paths`, or `ALL_PATHS` in select mode. */\n interest: PathSet;\n /** `null` while unsubscribed. */\n subscription: PathSubscription | null;\n subscribedTo: StateContainer | null;\n paths: PathSet;\n isSelectMode: boolean;\n selection: unknown[] | null;\n deps: DepSession;\n // Typed loosely: `T` is per call site.\n select: ((state: any, bloc: any) => unknown[]) | undefined;\n onMount: ((bloc: any) => void) | undefined;\n onUnmount: ((bloc: any) => void) | undefined;\n ownArgs: unknown;\n providerArgs: unknown;\n /** The render's tracking proxy; `null` outside a tracked render. */\n tracked: { current: unknown };\n disarm: (() => void) | null;\n proxyCache: ProxyCache | null;\n // The instance the consumer holds a ref on, claimed at commit so acquire\n // and release pair exactly.\n ownedClass: StateContainerConstructor | null;\n ownedKey: string;\n ownedRegistry: StateContainerRegistry | null;\n ownedBloc: unknown;\n}\n\nfunction createConsumer(): Consumer {\n const id = `useBloc-${nextConsumerId++}`;\n const consumer: Consumer = {\n id,\n primaryRefId: `useBloc@${id}`,\n BlocClass: null as unknown as StateContainerConstructor,\n instanceKey: '',\n registry: null as unknown as StateContainerRegistry,\n container: null as unknown as StateContainer,\n trackedBloc: null,\n subscribe: null as unknown as Consumer['subscribe'],\n boundNonce: 0,\n rebindNonce: 0,\n renderedState: undefined,\n version: 0,\n notify: noop,\n bump: () => {\n consumer.version++;\n consumer.notify();\n },\n getSnapshot: () => consumer.version,\n onChange: () => onChange(consumer),\n unsubscribe: () => unsubscribe(consumer),\n commit: () => commit(consumer),\n mount: () => teardown,\n interest: NO_PATHS,\n subscription: null,\n subscribedTo: null,\n paths: NO_PATHS,\n isSelectMode: false,\n selection: null,\n deps: null as unknown as DepSession,\n select: undefined,\n onMount: undefined,\n onUnmount: undefined,\n ownArgs: undefined,\n providerArgs: undefined,\n tracked: { current: null },\n disarm: null,\n proxyCache: null,\n ownedClass: null,\n ownedKey: '',\n ownedRegistry: null,\n ownedBloc: null,\n };\n const teardown = (): void => {\n release(consumer);\n consumer.deps.dispose();\n };\n consumer.deps = new DepSession(id, consumer.bump);\n return consumer;\n}\n\nfunction bind(\n consumer: Consumer,\n BlocClass: StateContainerConstructor,\n instanceKey: string,\n registry: StateContainerRegistry,\n args: unknown,\n): void {\n // No ref during render; the commit claims it. The registry sweeps the\n // instance if no commit ever does (SSR, discarded renders).\n const instance = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: false,\n args: args as ExtractArgs<StateContainerConstructor>,\n sweepIfUnowned: true,\n }) as unknown as StateContainer;\n\n // Dep proxies get `onDepHandle` too, so nested `.track()` calls (A→B→C)\n // record into this consumer's session.\n let depWrappers: Map<object, unknown> | undefined;\n const onDepHandle = (handle: object): unknown => {\n depWrappers ??= new Map();\n const cached = depWrappers.get(handle);\n if (cached !== undefined) return cached;\n const wrapper = makeDepWrapper(\n handle as DepHandleLike,\n registry,\n consumer.deps,\n consumer.tracked,\n onDepHandle,\n );\n depWrappers.set(handle, wrapper);\n return wrapper;\n };\n\n consumer.BlocClass = BlocClass;\n consumer.instanceKey = instanceKey;\n consumer.registry = registry;\n consumer.boundNonce = consumer.rebindNonce;\n consumer.container = instance;\n consumer.trackedBloc = buildTrackedProxy(\n instance,\n consumer.tracked,\n onDepHandle,\n );\n // A new identity per instance, so uSES re-subscribes.\n consumer.subscribe = (onStoreChange) => subscribe(consumer, onStoreChange);\n}\n\nfunction subscribe(consumer: Consumer, onStoreChange: () => void): () => void {\n consumer.notify = onStoreChange;\n const container = consumer.container;\n const subscription = container.subscribePaths(consumer.onChange);\n subscription.setInterest(consumer.interest);\n consumer.subscription = subscription;\n consumer.subscribedTo = container;\n container.registerConsumerPaths(consumer.id, consumer.paths);\n // An emit raised during render is still pending in the channel, so its\n // next flush delivers it to this subscriber.\n return consumer.unsubscribe;\n}\n\nfunction unsubscribe(consumer: Consumer): void {\n consumer.notify = noop;\n consumer.subscription?.unsubscribe();\n consumer.subscription = null;\n consumer.subscribedTo?.unregisterConsumer(consumer.id);\n consumer.subscribedTo = null;\n}\n\nfunction onChange(consumer: Consumer): void {\n if (consumer.isSelectMode) {\n const select = consumer.select;\n const container = consumer.subscribedTo;\n if (select && container !== null) {\n const next = select(container.state, container);\n const prev = consumer.selection;\n if (prev !== null && shallowArrayEqual(prev, next)) return;\n consumer.selection = next;\n }\n }\n consumer.bump();\n}\n\nfunction claim(consumer: Consumer): void {\n const { BlocClass, instanceKey, registry } = consumer;\n if (\n consumer.ownedClass === BlocClass &&\n consumer.ownedKey === instanceKey &&\n consumer.ownedRegistry === registry\n ) {\n return;\n }\n release(consumer);\n const live = registry.acquire(BlocClass, instanceKey, {\n canCreate: true,\n countRef: true,\n refId: consumer.primaryRefId,\n args: resolveEffectiveArgs(\n consumer,\n ) as ExtractArgs<StateContainerConstructor>,\n });\n consumer.ownedClass = BlocClass;\n consumer.ownedKey = instanceKey;\n consumer.ownedRegistry = registry;\n consumer.ownedBloc = live;\n consumer.onMount?.(live);\n // The render captured a stale instance; re-render against the live one.\n if (live !== (consumer.container as unknown)) {\n consumer.rebindNonce++;\n consumer.bump();\n }\n}\n\nfunction release(consumer: Consumer): void {\n const { ownedClass, ownedRegistry } = consumer;\n if (ownedClass === null || ownedRegistry === null) return;\n // Before release, so the bloc is still alive in the callback.\n consumer.onUnmount?.(consumer.ownedBloc);\n consumer.ownedClass = null;\n consumer.ownedRegistry = null;\n ownedRegistry.release(\n ownedClass,\n consumer.ownedKey,\n false,\n consumer.primaryRefId,\n );\n}\n\nfunction commit(consumer: Consumer): void {\n claim(consumer);\n // Getters called after commit read live state, not this render's proxy.\n consumer.tracked.current = null;\n const disarm = consumer.disarm;\n if (disarm !== null) {\n consumer.disarm = null;\n disarm();\n }\n const deps = consumer.deps;\n deps.disarm();\n if (consumer.isSelectMode) {\n // A later switch back to auto-track must reconcile in full.\n deps.invalidate();\n return;\n }\n const primary = consumer.container;\n // A change between render and commit was diffed against the previous\n // interest, which may lack paths only this render read.\n if (primary.state !== consumer.renderedState) consumer.bump();\n consumer.paths = deps.settle();\n if (deps.isUnchanged()) return;\n\n primary.registerConsumerPaths(consumer.id, consumer.paths);\n setInterest(consumer, consumer.paths);\n deps.reconcile(consumer.registry);\n}\n\nconst setInterest = (consumer: Consumer, interest: PathSet): void => {\n consumer.interest = interest;\n consumer.subscription?.setInterest(interest);\n};\n\n// Own args win over provider args. Shared by render and the ownership effect,\n// which recreates a disposed instance and must not drop its args.\nconst resolveEffectiveArgs = (consumer: Consumer): unknown =>\n consumer.ownArgs !== undefined ? consumer.ownArgs : consumer.providerArgs;\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","import { useEffect, useId, useRef } from 'react';\nimport type { StateContainer } from '@blac/core';\nimport { APPLY_DEPS, REMOVE_DEPS_OWNER } from '@blac/core/internal';\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 on unmount. The bloc observes\n * 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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"}
package/dist/testing.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_RegistryProvider = require("./RegistryProvider-F2qb_prD.cjs");
2
+ const require_RegistryProvider = require("./RegistryProvider-B9-mQQRg.cjs");
3
+ let react = require("react");
3
4
  let react_jsx_runtime = require("react/jsx-runtime");
4
5
  let _blac_core_testing = require("@blac/core/testing");
5
6
  let _testing_library_react = require("@testing-library/react");
@@ -28,6 +29,36 @@ function renderWithRegistry(ui, setup) {
28
29
  return registry;
29
30
  }));
30
31
  }
32
+ function seedRegistry(seeds) {
33
+ return (0, _blac_core_testing.withTestRegistry)((registry) => {
34
+ for (const [BlocClass, options] of seeds) (0, _blac_core_testing.registerOverride)(BlocClass, (0, _blac_core_testing.createCubitStub)(BlocClass, options), options?.args);
35
+ return registry;
36
+ });
37
+ }
38
+ /**
39
+ * Renders `ui` in a fresh registry holding a stub of each seeded bloc.
40
+ * Each seed is `[BlocClass, { args, state, methods, deps }?]`.
41
+ */
42
+ function renderWithBlocs(ui, seeds) {
43
+ return renderInRegistry(ui, seedRegistry(seeds));
44
+ }
45
+ function SeededRegistry({ seeds, children }) {
46
+ const [registry] = (0, react.useState)(() => seedRegistry(seeds));
47
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_RegistryProvider.RegistryProvider, {
48
+ registry,
49
+ children
50
+ });
51
+ }
52
+ /**
53
+ * Storybook decorator: renders each story in its own registry holding a stub
54
+ * of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.
55
+ */
56
+ function withBlocs(seeds) {
57
+ return (Story) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SeededRegistry, {
58
+ seeds,
59
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Story, {})
60
+ });
61
+ }
31
62
  function renderInRegistry(ui, registry) {
32
63
  return (0, _testing_library_react.render)(ui, { wrapper: ({ children }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_RegistryProvider.RegistryProvider, {
33
64
  registry,
@@ -36,6 +67,8 @@ function renderInRegistry(ui, registry) {
36
67
  }
37
68
  //#endregion
38
69
  exports.renderWithBloc = renderWithBloc;
70
+ exports.renderWithBlocs = renderWithBlocs;
39
71
  exports.renderWithRegistry = renderWithRegistry;
72
+ exports.withBlocs = withBlocs;
40
73
 
41
74
  //# sourceMappingURL=testing.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.cjs","names":["RegistryProvider"],"sources":["../src/testing.tsx"],"sourcesContent":["import type { ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n withTestRegistry,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\nimport { RegistryProvider } from './RegistryProvider';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n /** Args used to resolve which instance the stub is registered under. */\n args?: ExtractArgs<T>;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, args, ...stubOptions } = options;\n\n const { registry, instance } = withTestRegistry((registry) => {\n const instance = createCubitStub(BlocClass, {\n ...stubOptions,\n args,\n } as any);\n registerOverride(BlocClass, instance, args);\n return { registry, instance };\n });\n\n return { ...renderInRegistry(ui, registry), bloc: instance };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const registry = withTestRegistry((registry) => {\n setup(registry);\n return registry;\n });\n return renderInRegistry(ui, registry);\n}\n\nfunction renderInRegistry(\n ui: ReactElement,\n registry: StateContainerRegistry,\n): RenderResult {\n return render(ui, {\n wrapper: ({ children }) => (\n <RegistryProvider registry={registry}>{children}</RegistryProvider>\n ),\n });\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,MAAM,GAAG,gBAAgB;CAElD,MAAM,EAAE,UAAU,cAAA,GAAA,mBAAA,mBAA+B,aAAa;EAC5D,MAAM,YAAA,GAAA,mBAAA,iBAA2B,WAAW;GAC1C,GAAG;GACH;EACF,CAAQ;EACR,CAAA,GAAA,mBAAA,kBAAiB,WAAW,UAAU,IAAI;EAC1C,OAAO;GAAE;GAAU;EAAS;CAC9B,CAAC;CAED,OAAO;EAAE,GAAG,iBAAiB,IAAI,QAAQ;EAAG,MAAM;CAAS;AAC7D;AAEA,SAAgB,mBACd,IACA,OACc;CAKd,OAAO,iBAAiB,KAAA,GAAA,mBAAA,mBAJW,aAAa;EAC9C,MAAM,QAAQ;EACd,OAAO;CACT,CACmC,CAAC;AACtC;AAEA,SAAS,iBACP,IACA,UACc;CACd,QAAA,GAAA,uBAAA,QAAc,IAAI,EAChB,UAAU,EAAE,eACV,iBAAA,GAAA,kBAAA,KAACA,yBAAAA,kBAAD;EAA4B;EAAW;CAA2B,CAAA,EAEtE,CAAC;AACH"}
1
+ {"version":3,"file":"testing.cjs","names":["RegistryProvider"],"sources":["../src/testing.tsx"],"sourcesContent":["import { useState, type ComponentType, type ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n withTestRegistry,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\nimport { RegistryProvider } from './RegistryProvider';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n /** Args used to resolve which instance the stub is registered under. */\n args?: ExtractArgs<T>;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, args, ...stubOptions } = options;\n\n const { registry, instance } = withTestRegistry((registry) => {\n const instance = createCubitStub(BlocClass, {\n ...stubOptions,\n args,\n } as any);\n registerOverride(BlocClass, instance, args);\n return { registry, instance };\n });\n\n return { ...renderInRegistry(ui, registry), bloc: instance };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const registry = withTestRegistry((registry) => {\n setup(registry);\n return registry;\n });\n return renderInRegistry(ui, registry);\n}\n\ntype BlocSeeds<T extends readonly StateContainerConstructor[]> = {\n [K in keyof T]: [bloc: T[K], options?: CubitStubOptions<T[K]>];\n};\n\nfunction seedRegistry<T extends readonly StateContainerConstructor[]>(\n seeds: BlocSeeds<T>,\n): StateContainerRegistry {\n return withTestRegistry((registry) => {\n for (const [BlocClass, options] of seeds as ReadonlyArray<\n [StateContainerConstructor, CubitStubOptions<any>?]\n >) {\n registerOverride(\n BlocClass,\n createCubitStub(BlocClass, options),\n options?.args,\n );\n }\n return registry;\n });\n}\n\n/**\n * Renders `ui` in a fresh registry holding a stub of each seeded bloc.\n * Each seed is `[BlocClass, { args, state, methods, deps }?]`.\n */\nexport function renderWithBlocs<\n const T extends readonly StateContainerConstructor[],\n>(ui: ReactElement, seeds: BlocSeeds<T>): RenderResult {\n return renderInRegistry(ui, seedRegistry(seeds));\n}\n\nfunction SeededRegistry({\n seeds,\n children,\n}: {\n seeds: BlocSeeds<readonly StateContainerConstructor[]>;\n children: ReactElement;\n}): ReactElement {\n const [registry] = useState(() => seedRegistry(seeds));\n return <RegistryProvider registry={registry}>{children}</RegistryProvider>;\n}\n\n/**\n * Storybook decorator: renders each story in its own registry holding a stub\n * of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.\n */\nexport function withBlocs<const T extends readonly StateContainerConstructor[]>(\n seeds: BlocSeeds<T>,\n): (Story: ComponentType) => ReactElement {\n return (Story) => (\n <SeededRegistry seeds={seeds}>\n <Story />\n </SeededRegistry>\n );\n}\n\nfunction renderInRegistry(\n ui: ReactElement,\n registry: StateContainerRegistry,\n): RenderResult {\n return render(ui, {\n wrapper: ({ children }) => (\n <RegistryProvider registry={registry}>{children}</RegistryProvider>\n ),\n });\n}\n"],"mappings":";;;;;;;AAwBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,MAAM,GAAG,gBAAgB;CAElD,MAAM,EAAE,UAAU,cAAA,GAAA,mBAAA,mBAA+B,aAAa;EAC5D,MAAM,YAAA,GAAA,mBAAA,iBAA2B,WAAW;GAC1C,GAAG;GACH;EACF,CAAQ;EACR,CAAA,GAAA,mBAAA,kBAAiB,WAAW,UAAU,IAAI;EAC1C,OAAO;GAAE;GAAU;EAAS;CAC9B,CAAC;CAED,OAAO;EAAE,GAAG,iBAAiB,IAAI,QAAQ;EAAG,MAAM;CAAS;AAC7D;AAEA,SAAgB,mBACd,IACA,OACc;CAKd,OAAO,iBAAiB,KAAA,GAAA,mBAAA,mBAJW,aAAa;EAC9C,MAAM,QAAQ;EACd,OAAO;CACT,CACmC,CAAC;AACtC;AAMA,SAAS,aACP,OACwB;CACxB,QAAA,GAAA,mBAAA,mBAAyB,aAAa;EACpC,KAAK,MAAM,CAAC,WAAW,YAAY,OAGjC,CAAA,GAAA,mBAAA,kBACE,YAAA,GAAA,mBAAA,iBACgB,WAAW,OAAO,GAClC,SAAS,IACX;EAEF,OAAO;CACT,CAAC;AACH;;;;;AAMA,SAAgB,gBAEd,IAAkB,OAAmC;CACrD,OAAO,iBAAiB,IAAI,aAAa,KAAK,CAAC;AACjD;AAEA,SAAS,eAAe,EACtB,OACA,YAIe;CACf,MAAM,CAAC,aAAA,GAAA,MAAA,gBAA2B,aAAa,KAAK,CAAC;CACrD,OAAO,iBAAA,GAAA,kBAAA,KAACA,yBAAAA,kBAAD;EAA4B;EAAW;CAA2B,CAAA;AAC3E;;;;;AAMA,SAAgB,UACd,OACwC;CACxC,QAAQ,UACN,iBAAA,GAAA,kBAAA,KAAC,gBAAD;EAAuB;YACrB,iBAAA,GAAA,kBAAA,KAAC,OAAD,CAAQ,CAAA;CACM,CAAA;AAEpB;AAEA,SAAS,iBACP,IACA,UACc;CACd,QAAA,GAAA,uBAAA,QAAc,IAAI,EAChB,UAAU,EAAE,eACV,iBAAA,GAAA,kBAAA,KAACA,yBAAAA,kBAAD;EAA4B;EAAW;CAA2B,CAAA,EAEtE,CAAC;AACH"}
@@ -1,4 +1,4 @@
1
- import type { ReactElement } from 'react';
1
+ import { type ComponentType, type ReactElement } from 'react';
2
2
  import type { RenderResult } from '@testing-library/react';
3
3
  import type { ExtractArgs, StateContainerConstructor, StateContainerRegistry } from '@blac/core';
4
4
  import { type CubitStubOptions } from '@blac/core/testing';
@@ -11,4 +11,17 @@ export declare function renderWithBloc<T extends StateContainerConstructor>(ui:
11
11
  bloc: InstanceType<T>;
12
12
  };
13
13
  export declare function renderWithRegistry(ui: ReactElement, setup: (registry: StateContainerRegistry) => void): RenderResult;
14
+ type BlocSeeds<T extends readonly StateContainerConstructor[]> = {
15
+ [K in keyof T]: [bloc: T[K], options?: CubitStubOptions<T[K]>];
16
+ };
17
+ /**
18
+ * Renders `ui` in a fresh registry holding a stub of each seeded bloc.
19
+ * Each seed is `[BlocClass, { args, state, methods, deps }?]`.
20
+ */
21
+ export declare function renderWithBlocs<const T extends readonly StateContainerConstructor[]>(ui: ReactElement, seeds: BlocSeeds<T>): RenderResult;
22
+ /**
23
+ * Storybook decorator: renders each story in its own registry holding a stub
24
+ * of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.
25
+ */
26
+ export declare function withBlocs<const T extends readonly StateContainerConstructor[]>(seeds: BlocSeeds<T>): (Story: ComponentType) => ReactElement;
14
27
  export {};
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ReactElement } from 'react';
1
+ import { type ComponentType, type ReactElement } from 'react';
2
2
  import type { RenderResult } from '@testing-library/react';
3
3
  import type { ExtractArgs, StateContainerConstructor, StateContainerRegistry } from '@blac/core';
4
4
  import { type CubitStubOptions } from '@blac/core/testing';
@@ -11,4 +11,17 @@ export declare function renderWithBloc<T extends StateContainerConstructor>(ui:
11
11
  bloc: InstanceType<T>;
12
12
  };
13
13
  export declare function renderWithRegistry(ui: ReactElement, setup: (registry: StateContainerRegistry) => void): RenderResult;
14
+ type BlocSeeds<T extends readonly StateContainerConstructor[]> = {
15
+ [K in keyof T]: [bloc: T[K], options?: CubitStubOptions<T[K]>];
16
+ };
17
+ /**
18
+ * Renders `ui` in a fresh registry holding a stub of each seeded bloc.
19
+ * Each seed is `[BlocClass, { args, state, methods, deps }?]`.
20
+ */
21
+ export declare function renderWithBlocs<const T extends readonly StateContainerConstructor[]>(ui: ReactElement, seeds: BlocSeeds<T>): RenderResult;
22
+ /**
23
+ * Storybook decorator: renders each story in its own registry holding a stub
24
+ * of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.
25
+ */
26
+ export declare function withBlocs<const T extends readonly StateContainerConstructor[]>(seeds: BlocSeeds<T>): (Story: ComponentType) => ReactElement;
14
27
  export {};
package/dist/testing.js CHANGED
@@ -1,4 +1,5 @@
1
- import { t as RegistryProvider } from "./RegistryProvider-BjSXfLeK.js";
1
+ import { t as RegistryProvider } from "./RegistryProvider-DS4ltjlH.js";
2
+ import { useState } from "react";
2
3
  import { jsx } from "react/jsx-runtime";
3
4
  import { createCubitStub, registerOverride, withTestRegistry } from "@blac/core/testing";
4
5
  import { render } from "@testing-library/react";
@@ -27,6 +28,36 @@ function renderWithRegistry(ui, setup) {
27
28
  return registry;
28
29
  }));
29
30
  }
31
+ function seedRegistry(seeds) {
32
+ return withTestRegistry((registry) => {
33
+ for (const [BlocClass, options] of seeds) registerOverride(BlocClass, createCubitStub(BlocClass, options), options?.args);
34
+ return registry;
35
+ });
36
+ }
37
+ /**
38
+ * Renders `ui` in a fresh registry holding a stub of each seeded bloc.
39
+ * Each seed is `[BlocClass, { args, state, methods, deps }?]`.
40
+ */
41
+ function renderWithBlocs(ui, seeds) {
42
+ return renderInRegistry(ui, seedRegistry(seeds));
43
+ }
44
+ function SeededRegistry({ seeds, children }) {
45
+ const [registry] = useState(() => seedRegistry(seeds));
46
+ return /* @__PURE__ */ jsx(RegistryProvider, {
47
+ registry,
48
+ children
49
+ });
50
+ }
51
+ /**
52
+ * Storybook decorator: renders each story in its own registry holding a stub
53
+ * of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.
54
+ */
55
+ function withBlocs(seeds) {
56
+ return (Story) => /* @__PURE__ */ jsx(SeededRegistry, {
57
+ seeds,
58
+ children: /* @__PURE__ */ jsx(Story, {})
59
+ });
60
+ }
30
61
  function renderInRegistry(ui, registry) {
31
62
  return render(ui, { wrapper: ({ children }) => /* @__PURE__ */ jsx(RegistryProvider, {
32
63
  registry,
@@ -34,6 +65,6 @@ function renderInRegistry(ui, registry) {
34
65
  }) });
35
66
  }
36
67
  //#endregion
37
- export { renderWithBloc, renderWithRegistry };
68
+ export { renderWithBloc, renderWithBlocs, renderWithRegistry, withBlocs };
38
69
 
39
70
  //# sourceMappingURL=testing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","names":[],"sources":["../src/testing.tsx"],"sourcesContent":["import type { ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n withTestRegistry,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\nimport { RegistryProvider } from './RegistryProvider';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n /** Args used to resolve which instance the stub is registered under. */\n args?: ExtractArgs<T>;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, args, ...stubOptions } = options;\n\n const { registry, instance } = withTestRegistry((registry) => {\n const instance = createCubitStub(BlocClass, {\n ...stubOptions,\n args,\n } as any);\n registerOverride(BlocClass, instance, args);\n return { registry, instance };\n });\n\n return { ...renderInRegistry(ui, registry), bloc: instance };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const registry = withTestRegistry((registry) => {\n setup(registry);\n return registry;\n });\n return renderInRegistry(ui, registry);\n}\n\nfunction renderInRegistry(\n ui: ReactElement,\n registry: StateContainerRegistry,\n): RenderResult {\n return render(ui, {\n wrapper: ({ children }) => (\n <RegistryProvider registry={registry}>{children}</RegistryProvider>\n ),\n });\n}\n"],"mappings":";;;;;AAwBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,MAAM,GAAG,gBAAgB;CAElD,MAAM,EAAE,UAAU,aAAa,kBAAkB,aAAa;EAC5D,MAAM,WAAW,gBAAgB,WAAW;GAC1C,GAAG;GACH;EACF,CAAQ;EACR,iBAAiB,WAAW,UAAU,IAAI;EAC1C,OAAO;GAAE;GAAU;EAAS;CAC9B,CAAC;CAED,OAAO;EAAE,GAAG,iBAAiB,IAAI,QAAQ;EAAG,MAAM;CAAS;AAC7D;AAEA,SAAgB,mBACd,IACA,OACc;CAKd,OAAO,iBAAiB,IAJP,kBAAkB,aAAa;EAC9C,MAAM,QAAQ;EACd,OAAO;CACT,CACmC,CAAC;AACtC;AAEA,SAAS,iBACP,IACA,UACc;CACd,OAAO,OAAO,IAAI,EAChB,UAAU,EAAE,eACV,oBAAC,kBAAD;EAA4B;EAAW;CAA2B,CAAA,EAEtE,CAAC;AACH"}
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../src/testing.tsx"],"sourcesContent":["import { useState, type ComponentType, type ReactElement } from 'react';\nimport type { RenderResult } from '@testing-library/react';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n StateContainerRegistry,\n} from '@blac/core';\nimport {\n registerOverride,\n createCubitStub,\n withTestRegistry,\n type CubitStubOptions,\n} from '@blac/core/testing';\nimport { render } from '@testing-library/react';\nimport { RegistryProvider } from './RegistryProvider';\n\ninterface RenderWithBlocOptions<\n T extends StateContainerConstructor,\n> extends CubitStubOptions<T> {\n bloc: T;\n /** Args used to resolve which instance the stub is registered under. */\n args?: ExtractArgs<T>;\n}\n\nexport function renderWithBloc<T extends StateContainerConstructor>(\n ui: ReactElement,\n options: RenderWithBlocOptions<T>,\n): RenderResult & { bloc: InstanceType<T> } {\n const { bloc: BlocClass, args, ...stubOptions } = options;\n\n const { registry, instance } = withTestRegistry((registry) => {\n const instance = createCubitStub(BlocClass, {\n ...stubOptions,\n args,\n } as any);\n registerOverride(BlocClass, instance, args);\n return { registry, instance };\n });\n\n return { ...renderInRegistry(ui, registry), bloc: instance };\n}\n\nexport function renderWithRegistry(\n ui: ReactElement,\n setup: (registry: StateContainerRegistry) => void,\n): RenderResult {\n const registry = withTestRegistry((registry) => {\n setup(registry);\n return registry;\n });\n return renderInRegistry(ui, registry);\n}\n\ntype BlocSeeds<T extends readonly StateContainerConstructor[]> = {\n [K in keyof T]: [bloc: T[K], options?: CubitStubOptions<T[K]>];\n};\n\nfunction seedRegistry<T extends readonly StateContainerConstructor[]>(\n seeds: BlocSeeds<T>,\n): StateContainerRegistry {\n return withTestRegistry((registry) => {\n for (const [BlocClass, options] of seeds as ReadonlyArray<\n [StateContainerConstructor, CubitStubOptions<any>?]\n >) {\n registerOverride(\n BlocClass,\n createCubitStub(BlocClass, options),\n options?.args,\n );\n }\n return registry;\n });\n}\n\n/**\n * Renders `ui` in a fresh registry holding a stub of each seeded bloc.\n * Each seed is `[BlocClass, { args, state, methods, deps }?]`.\n */\nexport function renderWithBlocs<\n const T extends readonly StateContainerConstructor[],\n>(ui: ReactElement, seeds: BlocSeeds<T>): RenderResult {\n return renderInRegistry(ui, seedRegistry(seeds));\n}\n\nfunction SeededRegistry({\n seeds,\n children,\n}: {\n seeds: BlocSeeds<readonly StateContainerConstructor[]>;\n children: ReactElement;\n}): ReactElement {\n const [registry] = useState(() => seedRegistry(seeds));\n return <RegistryProvider registry={registry}>{children}</RegistryProvider>;\n}\n\n/**\n * Storybook decorator: renders each story in its own registry holding a stub\n * of each seeded bloc. Seeds are `[BlocClass, { args, state, methods, deps }?]`.\n */\nexport function withBlocs<const T extends readonly StateContainerConstructor[]>(\n seeds: BlocSeeds<T>,\n): (Story: ComponentType) => ReactElement {\n return (Story) => (\n <SeededRegistry seeds={seeds}>\n <Story />\n </SeededRegistry>\n );\n}\n\nfunction renderInRegistry(\n ui: ReactElement,\n registry: StateContainerRegistry,\n): RenderResult {\n return render(ui, {\n wrapper: ({ children }) => (\n <RegistryProvider registry={registry}>{children}</RegistryProvider>\n ),\n });\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,eACd,IACA,SAC0C;CAC1C,MAAM,EAAE,MAAM,WAAW,MAAM,GAAG,gBAAgB;CAElD,MAAM,EAAE,UAAU,aAAa,kBAAkB,aAAa;EAC5D,MAAM,WAAW,gBAAgB,WAAW;GAC1C,GAAG;GACH;EACF,CAAQ;EACR,iBAAiB,WAAW,UAAU,IAAI;EAC1C,OAAO;GAAE;GAAU;EAAS;CAC9B,CAAC;CAED,OAAO;EAAE,GAAG,iBAAiB,IAAI,QAAQ;EAAG,MAAM;CAAS;AAC7D;AAEA,SAAgB,mBACd,IACA,OACc;CAKd,OAAO,iBAAiB,IAJP,kBAAkB,aAAa;EAC9C,MAAM,QAAQ;EACd,OAAO;CACT,CACmC,CAAC;AACtC;AAMA,SAAS,aACP,OACwB;CACxB,OAAO,kBAAkB,aAAa;EACpC,KAAK,MAAM,CAAC,WAAW,YAAY,OAGjC,iBACE,WACA,gBAAgB,WAAW,OAAO,GAClC,SAAS,IACX;EAEF,OAAO;CACT,CAAC;AACH;;;;;AAMA,SAAgB,gBAEd,IAAkB,OAAmC;CACrD,OAAO,iBAAiB,IAAI,aAAa,KAAK,CAAC;AACjD;AAEA,SAAS,eAAe,EACtB,OACA,YAIe;CACf,MAAM,CAAC,YAAY,eAAe,aAAa,KAAK,CAAC;CACrD,OAAO,oBAAC,kBAAD;EAA4B;EAAW;CAA2B,CAAA;AAC3E;;;;;AAMA,SAAgB,UACd,OACwC;CACxC,QAAQ,UACN,oBAAC,gBAAD;EAAuB;YACrB,oBAAC,OAAD,CAAQ,CAAA;CACM,CAAA;AAEpB;AAEA,SAAS,iBACP,IACA,UACc;CACd,OAAO,OAAO,IAAI,EAChB,UAAU,EAAE,eACV,oBAAC,kBAAD;EAA4B;EAAW;CAA2B,CAAA,EAEtE,CAAC;AACH"}
@@ -21,11 +21,8 @@ import type { UseBlocOptions, UseBlocReturn } from './types';
21
21
  * fires *before* the registry releases its ref, so the bloc is still
22
22
  * alive when the callback runs.
23
23
  *
24
- * Per-mount private instance:
25
- * ```ts
26
- * const id = useId();
27
- * const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });
28
- * ```
24
+ * Per-mount private instance: mark the class `@blac({ isolated: true })`, or
25
+ * pass a unique key arg yourself (e.g. `useId()` in `args`).
29
26
  *
30
27
  * @typeParam T - The state container constructor type (inferred from BlocClass)
31
28
  * @param BlocClass - The state container class to connect to
package/dist/useBloc.d.ts CHANGED
@@ -21,11 +21,8 @@ import type { UseBlocOptions, UseBlocReturn } from './types';
21
21
  * fires *before* the registry releases its ref, so the bloc is still
22
22
  * alive when the callback runs.
23
23
  *
24
- * Per-mount private instance:
25
- * ```ts
26
- * const id = useId();
27
- * const [state, bloc] = useBloc(MyBloc, { args: { _id: id } });
28
- * ```
24
+ * Per-mount private instance: mark the class `@blac({ isolated: true })`, or
25
+ * pass a unique key arg yourself (e.g. `useId()` in `args`).
29
26
  *
30
27
  * @typeParam T - The state container constructor type (inferred from BlocClass)
31
28
  * @param BlocClass - The state container class to connect to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blac/react",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "license": "MIT",
5
5
  "author": "Brendan Mullins <jsnanigans@gmail.com>",
6
6
  "description": "React bindings for BlaC — useBloc hook with automatic re-render optimization",
@@ -78,13 +78,13 @@
78
78
  }
79
79
  ],
80
80
  "dependencies": {
81
- "@dirtytalk/structural": "^0.1.4"
81
+ "@dirtytalk/structural": "^0.1.5"
82
82
  },
83
83
  "peerDependencies": {
84
84
  "@testing-library/react": "^14.0.0 || ^15.0.0 || ^16.0.0",
85
85
  "@types/react": "^18.0.0 || ^19.0.0",
86
86
  "react": "^18.0.0 || ^19.0.0",
87
- "@blac/core": "^2.1.1"
87
+ "@blac/core": "^2.1.3"
88
88
  },
89
89
  "peerDependenciesMeta": {
90
90
  "@types/react": {
@@ -121,7 +121,7 @@
121
121
  "vite": "npm:@voidzero-dev/vite-plus-core@0.1.24",
122
122
  "vite-plus": "^0.1.24",
123
123
  "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.24",
124
- "@blac/core": "2.1.1"
124
+ "@blac/core": "2.1.3"
125
125
  },
126
126
  "scripts": {
127
127
  "dev": "vp pack --watch",