@blac/core 2.0.22 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/StateContainer-BUyuxKb3.cjs +667 -0
- package/dist/StateContainer-BUyuxKb3.cjs.map +1 -0
- package/dist/StateContainer-B_7UHc3I.js +632 -0
- package/dist/StateContainer-B_7UHc3I.js.map +1 -0
- package/dist/StateContainer-Bh2ipNYj.js +632 -0
- package/dist/StateContainer-Bh2ipNYj.js.map +1 -0
- package/dist/StateContainer-ClzwKnQZ.cjs +667 -0
- package/dist/StateContainer-ClzwKnQZ.cjs.map +1 -0
- package/dist/StateContainer-DiQATvZh.js +632 -0
- package/dist/StateContainer-DiQATvZh.js.map +1 -0
- package/dist/StateContainer-j16zciex.cjs +667 -0
- package/dist/StateContainer-j16zciex.cjs.map +1 -0
- package/dist/StateContainerRegistry-BX-DlHXO.js +747 -0
- package/dist/StateContainerRegistry-BX-DlHXO.js.map +1 -0
- package/dist/StateContainerRegistry-Bf5VuVjm.js +747 -0
- package/dist/StateContainerRegistry-Bf5VuVjm.js.map +1 -0
- package/dist/StateContainerRegistry-BvSRfhKv.cjs +866 -0
- package/dist/StateContainerRegistry-BvSRfhKv.cjs.map +1 -0
- package/dist/StateContainerRegistry-C8jxrcqk.cjs +866 -0
- package/dist/StateContainerRegistry-C8jxrcqk.cjs.map +1 -0
- package/dist/StateContainerRegistry-DjQQtlCW.cjs +870 -0
- package/dist/StateContainerRegistry-DjQQtlCW.cjs.map +1 -0
- package/dist/StateContainerRegistry-gQbEutkN.js +751 -0
- package/dist/StateContainerRegistry-gQbEutkN.js.map +1 -0
- package/dist/config.d.cts +10 -6
- package/dist/config.d.ts +10 -6
- package/dist/constants.d.cts +12 -17
- package/dist/constants.d.ts +12 -17
- package/dist/core/StateContainer.d.ts +16 -34
- package/dist/core/StateContainerRegistry.d.ts +57 -174
- package/dist/core/symbols.d.ts +7 -4
- package/dist/decorators/blac.d.ts +5 -2
- package/dist/ensure-Bi_yXk6f.js +46 -0
- package/dist/ensure-Bi_yXk6f.js.map +1 -0
- package/dist/ensure-Bx41d-il.cjs +63 -0
- package/dist/ensure-Bx41d-il.cjs.map +1 -0
- package/dist/ensure-C7UQ_mKM.js +46 -0
- package/dist/ensure-C7UQ_mKM.js.map +1 -0
- package/dist/ensure-D1D4WjYf.cjs +63 -0
- package/dist/ensure-D1D4WjYf.cjs.map +1 -0
- package/dist/ensure-DBnuRJ0R.cjs +63 -0
- package/dist/ensure-DBnuRJ0R.cjs.map +1 -0
- package/dist/ensure-NTB9nnnj.js +46 -0
- package/dist/ensure-NTB9nnnj.js.map +1 -0
- package/dist/index.cjs +70 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -7
- package/dist/index.d.ts +2 -7
- package/dist/index.js +53 -26
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +10 -0
- package/dist/internal.d.cts +8 -0
- package/dist/internal.d.ts +8 -0
- package/dist/internal.js +3 -0
- package/dist/plugin/BlacPlugin.d.ts +8 -24
- package/dist/plugin/PluginManager.d.ts +41 -64
- package/dist/plugins.cjs +67 -87
- package/dist/plugins.cjs.map +1 -1
- package/dist/plugins.d.cts +7 -6
- package/dist/plugins.d.ts +7 -6
- package/dist/plugins.js +67 -87
- package/dist/plugins.js.map +1 -1
- package/dist/registry/config.d.ts +3 -0
- package/dist/testing.cjs +37 -35
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +5 -11
- package/dist/testing.d.ts +5 -11
- package/dist/testing.js +28 -26
- package/dist/testing.js.map +1 -1
- package/dist/types/branded.d.ts +3 -10
- package/dist/types/utilities.d.ts +0 -18
- package/dist/utils/idGenerator.d.ts +6 -13
- package/dist/utils/static-props.d.ts +9 -35
- package/dist/utils/structural-key.d.ts +0 -5
- package/dist/watch/watch.d.ts +4 -9
- package/package.json +15 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import { a as
|
|
1
|
+
import { _ as configureBlac, b as shallowEqualState, f as isExcludedFromDevTools, m as BLAC_STATIC_PROPS, n as globalRegistry, o as ON_DISPOSE, p as isKeepAliveClass, t as StateContainerRegistry, u as getBlacName, v as getBlacConfig, y as resetBlacConfig } from "./StateContainerRegistry-gQbEutkN.js";
|
|
2
|
+
import { a as getStats, i as getRegistry, n as StateContainer, o as setRegistry } from "./StateContainer-Bh2ipNYj.js";
|
|
3
|
+
import { n as acquire, r as resolveInstanceKey, t as ensure } from "./ensure-Bi_yXk6f.js";
|
|
3
4
|
import { ALL_PATHS, ALL_PATHS as ALL_PATHS$1 } from "@dirtytalk/structural";
|
|
5
|
+
//#region src/core/Cubit.ts
|
|
6
|
+
/**
|
|
7
|
+
* `Cubit<S>` is a `StateContainer<S>` that exposes mutation publicly.
|
|
8
|
+
*
|
|
9
|
+
* That is the whole difference, and it is a real one: on `StateContainer`
|
|
10
|
+
* `emit` / `patch` / `update` are `protected`, so a container mutates itself
|
|
11
|
+
* from its own methods and callers go through the API it chooses to publish.
|
|
12
|
+
* `Cubit` re-declares the three as `public` for the cases where a caller
|
|
13
|
+
* legitimately drives state from outside — test helpers, devtools
|
|
14
|
+
* time-travel, benchmarks.
|
|
15
|
+
*
|
|
16
|
+
* Reach for `StateContainer` when business logic should live in the class,
|
|
17
|
+
* and `Cubit` when the caller owns the transitions.
|
|
18
|
+
*/
|
|
19
|
+
var Cubit = class extends StateContainer {
|
|
20
|
+
emit(next) {
|
|
21
|
+
super.emit(next);
|
|
22
|
+
}
|
|
23
|
+
patch(partial) {
|
|
24
|
+
super.patch(partial);
|
|
25
|
+
}
|
|
26
|
+
update(fn) {
|
|
27
|
+
super.update(fn);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
4
31
|
//#region src/registry/borrow.ts
|
|
5
32
|
function resolveBorrowKey(BlocClass, target) {
|
|
6
33
|
return getRegistry().resolveKey(BlocClass, void 0, target?.args);
|
|
@@ -104,11 +131,11 @@ function register(BlocClass) {
|
|
|
104
131
|
*/
|
|
105
132
|
function blac(options) {
|
|
106
133
|
return function(target, _context) {
|
|
107
|
-
if (
|
|
108
|
-
if (
|
|
109
|
-
if (
|
|
110
|
-
if (
|
|
111
|
-
if (
|
|
134
|
+
if (options.keepAlive !== void 0) target[BLAC_STATIC_PROPS.KEEP_ALIVE] = options.keepAlive;
|
|
135
|
+
if (options.excludeFromDevTools) target[BLAC_STATIC_PROPS.EXCLUDE_FROM_DEVTOOLS] = true;
|
|
136
|
+
if (typeof options.equality === "function") target[BLAC_STATIC_PROPS.EQUALITY] = options.equality;
|
|
137
|
+
if (typeof options.key === "function") target[BLAC_STATIC_PROPS.KEY] = options.key;
|
|
138
|
+
if (typeof options.name === "string") target[BLAC_STATIC_PROPS.BLAC_NAME] = options.name;
|
|
112
139
|
return target;
|
|
113
140
|
};
|
|
114
141
|
}
|
|
@@ -138,8 +165,7 @@ function instance(BlocClass, args) {
|
|
|
138
165
|
function isBlocRef(input) {
|
|
139
166
|
return typeof input === "object" && input !== null && BLOC_REF_MARKER in input;
|
|
140
167
|
}
|
|
141
|
-
function toWatchTarget(input) {
|
|
142
|
-
const registry = getRegistry();
|
|
168
|
+
function toWatchTarget(registry, input) {
|
|
143
169
|
if (isBlocRef(input)) return {
|
|
144
170
|
blocClass: input.blocClass,
|
|
145
171
|
key: input.instanceId,
|
|
@@ -157,8 +183,8 @@ let watchRefSeq = 0;
|
|
|
157
183
|
* through so a not-yet-created instance is initialized correctly. The
|
|
158
184
|
* caller is responsible for releasing `refId` in cleanup.
|
|
159
185
|
*/
|
|
160
|
-
function resolveBloc(target, refId) {
|
|
161
|
-
return
|
|
186
|
+
function resolveBloc(registry, target, refId) {
|
|
187
|
+
return registry.acquire(target.blocClass, target.key, {
|
|
162
188
|
countRef: true,
|
|
163
189
|
refId,
|
|
164
190
|
args: target.args
|
|
@@ -168,8 +194,7 @@ function resolveBloc(target, refId) {
|
|
|
168
194
|
* Passive lookup for `{ create: false }`: never creates the instance and
|
|
169
195
|
* never takes a ref. Returns `undefined` when no instance currently exists.
|
|
170
196
|
*/
|
|
171
|
-
function resolveBlocPassive(target) {
|
|
172
|
-
const registry = getRegistry();
|
|
197
|
+
function resolveBlocPassive(registry, target) {
|
|
173
198
|
try {
|
|
174
199
|
return registry.acquire(target.blocClass, target.key, {
|
|
175
200
|
canCreate: false,
|
|
@@ -188,10 +213,11 @@ function watchImpl(blocsOrBloc, callback, options) {
|
|
|
188
213
|
const inputs = isSingle ? [blocsOrBloc] : blocsOrBloc;
|
|
189
214
|
const registry = getRegistry();
|
|
190
215
|
const create = options?.create ?? true;
|
|
191
|
-
const targets = inputs.map(toWatchTarget);
|
|
216
|
+
const targets = inputs.map((input) => toWatchTarget(registry, input));
|
|
192
217
|
const refIds = targets.map(() => `_watch_${watchRefSeq++}`);
|
|
218
|
+
const resolveAt = (index) => create ? resolveBloc(registry, targets[index], refIds[index]) : resolveBlocPassive(registry, targets[index]);
|
|
193
219
|
let disposed = false;
|
|
194
|
-
const instances =
|
|
220
|
+
const instances = [];
|
|
195
221
|
const channelUnsubs = [];
|
|
196
222
|
const disposedUnsubs = [];
|
|
197
223
|
const cleanup = () => {
|
|
@@ -201,7 +227,7 @@ function watchImpl(blocsOrBloc, callback, options) {
|
|
|
201
227
|
for (const unsub of disposedUnsubs) unsub?.();
|
|
202
228
|
channelUnsubs.length = 0;
|
|
203
229
|
disposedUnsubs.length = 0;
|
|
204
|
-
if (create) for (let i = 0; i <
|
|
230
|
+
if (create) for (let i = 0; i < instances.length; i++) registry.release(targets[i].blocClass, targets[i].key, false, refIds[i]);
|
|
205
231
|
};
|
|
206
232
|
const runCallback = () => {
|
|
207
233
|
if (disposed) return;
|
|
@@ -213,7 +239,7 @@ function watchImpl(blocsOrBloc, callback, options) {
|
|
|
213
239
|
};
|
|
214
240
|
const resubscribeAt = (index) => {
|
|
215
241
|
if (disposed) return;
|
|
216
|
-
instances[index] =
|
|
242
|
+
instances[index] = resolveAt(index);
|
|
217
243
|
subscribeAt(index);
|
|
218
244
|
subscribeDisposeAt(index);
|
|
219
245
|
runCallback();
|
|
@@ -225,23 +251,24 @@ function watchImpl(blocsOrBloc, callback, options) {
|
|
|
225
251
|
queueMicrotask(() => resubscribeAt(index));
|
|
226
252
|
});
|
|
227
253
|
};
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
254
|
+
try {
|
|
255
|
+
for (let i = 0; i < targets.length; i++) instances.push(resolveAt(i));
|
|
256
|
+
for (let i = 0; i < instances.length; i++) subscribeAt(i);
|
|
257
|
+
for (let i = 0; i < instances.length; i++) subscribeDisposeAt(i);
|
|
258
|
+
runCallback();
|
|
259
|
+
} catch (error) {
|
|
260
|
+
cleanup();
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
231
263
|
return cleanup;
|
|
232
264
|
}
|
|
233
265
|
const watch = Object.assign(watchImpl, { STOP });
|
|
234
266
|
//#endregion
|
|
235
267
|
//#region src/types/branded.ts
|
|
236
|
-
/**
|
|
237
|
-
* Create a branded InstanceId from a string
|
|
238
|
-
* @param id - The string ID to brand
|
|
239
|
-
* @returns Branded InstanceId
|
|
240
|
-
*/
|
|
241
268
|
function instanceId(id) {
|
|
242
269
|
return id;
|
|
243
270
|
}
|
|
244
271
|
//#endregion
|
|
245
|
-
export { ALL_PATHS,
|
|
272
|
+
export { ALL_PATHS, Cubit, StateContainer, StateContainerRegistry, acquire, blac, borrow, borrowSafe, clear, clearAll, configureBlac, ensure, forEach, getAll, getBlacConfig, getBlacName, getRefCount, getRefIds, getRegistry, getStats, globalRegistry, hasInstance, instance, instanceId, isExcludedFromDevTools, isKeepAliveClass, register, release, resetBlacConfig, resolveInstanceKey, setRegistry, shallowEqualState, watch };
|
|
246
273
|
|
|
247
274
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["ALL_PATHS"],"sources":["../src/registry/borrow.ts","../src/registry/release.ts","../src/registry/queries.ts","../src/registry/management.ts","../src/decorators/blac.ts","../src/watch/watch.ts","../src/types/branded.ts"],"sourcesContent":["import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n} from '../types/utilities';\n\n/**\n * How to identify the instance to borrow: the same `args` shape `useBloc`\n * accepts, resolved through the class's `static key(args)` so call sites use\n * the *same* shape they pass to `useBloc` instead of hand-deriving the key.\n *\n * `borrowSafe(ChannelBloc, { args: { channelId } })` and\n * `useBloc(ChannelBloc, { args: { channelId } })` therefore always agree.\n */\nexport type BorrowTarget<T extends StateContainerConstructor> = {\n args?: ExtractArgs<T>;\n};\n\nfunction resolveBorrowKey<T extends StateContainerConstructor>(\n BlocClass: T,\n target: BorrowTarget<T> | undefined,\n): string {\n return getRegistry().resolveKey(BlocClass, undefined, target?.args);\n}\n\nexport function borrow<T extends StateContainerConstructor>(\n BlocClass: T,\n target?: BorrowTarget<T>,\n): InstanceType<T> {\n return getRegistry().borrow(BlocClass, resolveBorrowKey(BlocClass, target));\n}\n\nexport function borrowSafe<T extends StateContainerConstructor>(\n BlocClass: T,\n target?: BorrowTarget<T>,\n):\n | { error: Error; instance: null }\n | { error: null; instance: InstanceType<T> } {\n return getRegistry().borrowSafe(\n BlocClass,\n resolveBorrowKey(BlocClass, target),\n );\n}\n","import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n} from '../types/utilities';\n\n/**\n * Release a reference to an instance. Instance identity is derived purely from\n * `args` (must match the `args` it was acquired with).\n *\n * @param opts - `args` derives the instance key (must match acquire);\n * `refId` picks the ref to drop, otherwise one arbitrary ref goes;\n * `forceDispose` disposes immediately regardless of remaining refs.\n */\nexport function release<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T>; refId?: string; forceDispose?: boolean },\n): void {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n registry.release(BlocClass, key, opts?.forceDispose ?? false, opts?.refId);\n}\n","import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n InstanceReadonlyState,\n} from '../types/utilities';\n\n/**\n * @public\n */\nexport function hasInstance<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T> },\n): boolean {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.hasInstance(BlocClass, key);\n}\n\n/**\n * @public\n */\nexport function getRefCount<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T> },\n): number {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.getRefCount(BlocClass, key);\n}\n\n/**\n * @public\n */\nexport function getRefIds<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T> },\n): string[] {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.getRefIds(BlocClass, key);\n}\n\n/**\n * @public\n */\nexport function getAll<T extends StateContainerConstructor>(\n BlocClass: T,\n): InstanceReadonlyState<T>[] {\n return getRegistry().getAll(BlocClass);\n}\n\n/**\n * @public\n */\nexport function forEach<T extends StateContainerConstructor>(\n BlocClass: T,\n callback: (instance: InstanceReadonlyState<T>) => void,\n): void {\n getRegistry().forEach(BlocClass, callback);\n}\n","import { getRegistry } from './config';\nimport type { StateContainerConstructor } from '../types/utilities';\n\nexport function clear<T extends StateContainerConstructor>(BlocClass: T): void {\n getRegistry().clear(BlocClass);\n}\n\nexport function clearAll(): void {\n getRegistry().clearAll();\n}\n\n/**\n * @public\n */\nexport function register<T extends StateContainerConstructor>(\n BlocClass: T,\n): void {\n getRegistry().register(BlocClass);\n}\n","import { BLAC_STATIC_PROPS } from '../constants';\nimport type { EqualityFn } from '../config';\n\n/**\n * Configuration options for the `@blac` decorator. Any combination may be\n * given at once.\n */\nexport interface BlacOptions {\n /** Mark bloc to never be auto-disposed when ref count reaches 0 */\n keepAlive?: true;\n /** Exclude bloc from DevTools tracking (prevents infinite loops) */\n excludeFromDevTools?: true;\n /**\n * Override the global equality check for this bloc.\n * Return `true` to skip the emit (states considered equal).\n */\n equality?: EqualityFn;\n /**\n * Override the default structural-hash identity key.\n * The function receives `args` and returns the instance key string.\n * Distinct return values → distinct instances; identical values → shared instance.\n * Non-identity fields can be excluded so they don't fork new instances.\n */\n key?: (args: any) => string;\n /**\n * Explicit, minification-safe identity for this bloc class. Falls back to\n * `constructor.name` when unset, which collides across classes once a\n * minifier shortens class names.\n */\n name?: string;\n}\n\n/**\n * Decorator to configure StateContainer classes.\n *\n * @example Decorator syntax (requires experimentalDecorators or TC39 decorators)\n * ```ts\n * @blac({ keepAlive: true })\n * class AuthBloc extends Cubit<AuthState> {}\n *\n * @blac({ excludeFromDevTools: true })\n * class InternalBloc extends Cubit<InternalState> {}\n *\n * @blac({ keepAlive: true, key: (args) => args.userId })\n * class UserBloc extends Cubit<UserState> {}\n * ```\n *\n * @example Function syntax (no decorator support needed)\n * ```ts\n * const AuthBloc = blac({ keepAlive: true })(\n * class extends Cubit<AuthState> {}\n * );\n * ```\n */\nexport function blac(options: BlacOptions) {\n return function <T extends new (...args: any[]) => any>(\n target: T,\n _context?: ClassDecoratorContext,\n ): T {\n if ('keepAlive' in options && options.keepAlive) {\n (target as any)[BLAC_STATIC_PROPS.KEEP_ALIVE] = true;\n }\n if ('excludeFromDevTools' in options && options.excludeFromDevTools) {\n (target as any)[BLAC_STATIC_PROPS.EXCLUDE_FROM_DEVTOOLS] = true;\n }\n if ('equality' in options && typeof options.equality === 'function') {\n (target as any)[BLAC_STATIC_PROPS.EQUALITY] = options.equality;\n }\n if ('key' in options && typeof options.key === 'function') {\n (target as any)[BLAC_STATIC_PROPS.KEY] = options.key;\n }\n if ('name' in options && typeof options.name === 'string') {\n (target as any)[BLAC_STATIC_PROPS.BLAC_NAME] = options.name;\n }\n return target;\n };\n}\n","import { ALL_PATHS } from '@dirtytalk/structural';\nimport { ON_DISPOSE } from '../core/symbols';\nimport { getRegistry } from '../registry';\nimport { resolveInstanceKey } from '../registry/acquire';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n StateContainerInstance,\n} from '../types/utilities';\n\nconst STOP: unique symbol = Symbol('watch.STOP');\ntype StopSymbol = typeof STOP;\n\nconst BLOC_REF_MARKER = Symbol('BlocRef');\n\n/**\n * Reference to a specific bloc instance, identified by class + the resolved\n * key derived from its `args`.\n */\nexport interface BlocRef<T extends StateContainerConstructor> {\n [BLOC_REF_MARKER]: true;\n blocClass: T;\n /** @internal The resolved instance key (derived from `args`). */\n instanceId: string;\n /**\n * @internal The `args` used to derive `instanceId`. Forwarded to\n * `registry.acquire` so a not-yet-created instance is initialized with\n * these args instead of `undefined`.\n */\n args?: unknown;\n}\n\n/**\n * Create a reference to a specific bloc instance, identified by its `args`.\n * The key is derived the same way `useBloc`/`acquire` derive it.\n *\n * @example\n * ```ts\n * watch(instance(UserBloc, { userId: 'user-123' }), (userBloc) => {\n * console.log(userBloc.state.name);\n * });\n * ```\n */\nexport function instance<T extends StateContainerConstructor>(\n BlocClass: T,\n args?: ExtractArgs<T>,\n): BlocRef<T> {\n return {\n [BLOC_REF_MARKER]: true,\n blocClass: BlocClass,\n instanceId: resolveInstanceKey(BlocClass, args),\n args,\n };\n}\n\nfunction isBlocRef(\n input: unknown,\n): input is BlocRef<StateContainerConstructor> {\n return (\n typeof input === 'object' && input !== null && BLOC_REF_MARKER in input\n );\n}\n\ntype BlocInput = StateContainerConstructor | BlocRef<StateContainerConstructor>;\n\n/**\n * Options for `watch`.\n */\nexport interface WatchOptions {\n /**\n * When `false`, `watch` observes passively instead of owning the instance:\n * it does not create a missing instance, and it does not take a real\n * ownership ref on an existing one (so it never keeps an otherwise-\n * unreferenced instance alive).\n *\n * If the instance does not exist yet when `watch` is called, the callback\n * simply never fires for it — `watch` does not poll or wait for a later\n * creation. Call `watch` again once the instance is known to exist (e.g.\n * after some other owner has acquired it).\n *\n * Defaults to `true`.\n */\n create?: boolean;\n}\n\ntype ExtractInstance<T> =\n T extends BlocRef<infer C>\n ? InstanceType<C>\n : T extends StateContainerConstructor\n ? InstanceType<T>\n : never;\n\ntype ExtractInstances<T extends readonly BlocInput[]> = {\n [K in keyof T]: ExtractInstance<T[K]>;\n};\n\n/**\n * Watch function signature for single bloc.\n */\nexport interface WatchSingleFn {\n <T extends StateContainerConstructor>(\n bloc: T | BlocRef<T>,\n callback: (bloc: InstanceType<T>) => void | StopSymbol,\n options?: WatchOptions,\n ): () => void;\n\n STOP: StopSymbol;\n}\n\n/**\n * Watch function signature for multiple blocs.\n */\nexport interface WatchMultipleFn {\n <T extends readonly BlocInput[]>(\n blocs: T,\n callback: (blocs: ExtractInstances<T>) => void | StopSymbol,\n options?: WatchOptions,\n ): () => void;\n\n STOP: StopSymbol;\n}\n\n/**\n * Combined watch function type.\n */\nexport interface WatchFn extends WatchSingleFn {\n <T extends readonly BlocInput[]>(\n blocs: T,\n callback: (blocs: ExtractInstances<T>) => void | StopSymbol,\n options?: WatchOptions,\n ): () => void;\n}\n\n/**\n * Class + resolved key + args needed to (re-)acquire a specific instance.\n * `watch` keeps this around per input so it can re-acquire the same logical\n * instance if the underlying container is disposed elsewhere.\n */\ninterface WatchTarget {\n blocClass: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\nfunction toWatchTarget(input: BlocInput): WatchTarget {\n const registry = getRegistry();\n if (isBlocRef(input)) {\n return {\n blocClass: input.blocClass,\n key: input.instanceId,\n args: input.args,\n };\n }\n return {\n blocClass: input,\n key: registry.resolveKey(input, undefined, undefined),\n args: undefined,\n };\n}\n\nlet watchRefSeq = 0;\n\n/**\n * Acquire a real ref (countRef: true) for the target, carrying its `args`\n * through so a not-yet-created instance is initialized correctly. The\n * caller is responsible for releasing `refId` in cleanup.\n */\nfunction resolveBloc(\n target: WatchTarget,\n refId: string,\n): StateContainerInstance {\n const registry = getRegistry();\n return registry.acquire(target.blocClass, target.key, {\n countRef: true,\n refId,\n args: target.args,\n });\n}\n\n/**\n * Passive lookup for `{ create: false }`: never creates the instance and\n * never takes a ref. Returns `undefined` when no instance currently exists.\n */\nfunction resolveBlocPassive(\n target: WatchTarget,\n): StateContainerInstance | undefined {\n const registry = getRegistry();\n try {\n return registry.acquire(target.blocClass, target.key, {\n canCreate: false,\n countRef: false,\n args: target.args,\n });\n } catch {\n return undefined;\n }\n}\n\nfunction isArray(input: unknown): input is readonly BlocInput[] {\n return Array.isArray(input);\n}\n\n/**\n * Watch one or more blocs for state changes.\n *\n * Thin wrapper around `container.channel.subscribe(ALL_PATHS, ...)`. The\n * callback fires once immediately, then on every state change of any of the\n * passed blocs. Returning `watch.STOP` from the callback tears down all\n * subscriptions.\n *\n * Note: subscriptions are microtask-deferred (per the DirtyChannel default\n * scheduler), so callbacks land asynchronously after `emit()`.\n *\n * By default `watch` creates the instance if it does not exist and holds a\n * real ownership ref until `unwatch`, matching `acquire`. Pass\n * `{ create: false }` to observe passively instead — see `WatchOptions`.\n *\n * @example Single bloc\n * ```ts\n * const unwatch = watch(UserBloc, (userBloc) => {\n * console.log(userBloc.state.name);\n * });\n * ```\n *\n * @example Multiple blocs\n * ```ts\n * const unwatch = watch(\n * [UserBloc, SettingsBloc] as const,\n * ([userBloc, settingsBloc]) => {\n * console.log(userBloc.state.name, settingsBloc.state.theme);\n * }\n * );\n * ```\n *\n * @example With specific instance\n * ```ts\n * const unwatch = watch(\n * instance(UserBloc, { userId: 'user-123' }),\n * (userBloc) => {\n * console.log(userBloc.state.name);\n * }\n * );\n * ```\n *\n * @example Stop watching from callback\n * ```ts\n * const unwatch = watch(UserBloc, (userBloc) => {\n * if (userBloc.state.done) {\n * return watch.STOP;\n * }\n * });\n * ```\n */\nfunction watchImpl<T extends StateContainerConstructor>(\n bloc: T | BlocRef<T>,\n callback: (bloc: InstanceType<T>) => void | StopSymbol,\n options?: WatchOptions,\n): () => void;\n\nfunction watchImpl<T extends readonly BlocInput[]>(\n blocs: T,\n callback: (blocs: ExtractInstances<T>) => void | StopSymbol,\n options?: WatchOptions,\n): () => void;\n\nfunction watchImpl(\n blocsOrBloc: BlocInput | readonly BlocInput[],\n callback: (blocsOrBloc: any) => void | StopSymbol,\n options?: WatchOptions,\n): () => void {\n const isSingle = !isArray(blocsOrBloc);\n const inputs = isSingle ? [blocsOrBloc] : blocsOrBloc;\n const registry = getRegistry();\n const create = options?.create ?? true;\n\n const targets = inputs.map(toWatchTarget);\n const refIds = targets.map(() => `_watch_${watchRefSeq++}`);\n\n let disposed = false;\n const instances: Array<StateContainerInstance | undefined> = targets.map(\n (target, i) =>\n create ? resolveBloc(target, refIds[i]) : resolveBlocPassive(target),\n );\n const channelUnsubs: Array<(() => void) | undefined> = [];\n const disposedUnsubs: Array<(() => void) | undefined> = [];\n\n const cleanup = () => {\n if (disposed) return;\n disposed = true;\n for (const unsub of channelUnsubs) unsub?.();\n for (const unsub of disposedUnsubs) unsub?.();\n channelUnsubs.length = 0;\n disposedUnsubs.length = 0;\n if (create) {\n for (let i = 0; i < targets.length; i++) {\n registry.release(\n targets[i].blocClass,\n targets[i].key,\n false,\n refIds[i],\n );\n }\n }\n };\n\n const runCallback = () => {\n if (disposed) return;\n if (instances.some((i) => i === undefined)) return;\n const arg = isSingle ? instances[0] : instances;\n const result = callback(arg);\n if (result === STOP) cleanup();\n };\n\n const subscribeAt = (index: number) => {\n channelUnsubs[index] = instances[index]?.channel.subscribe(\n () => ALL_PATHS,\n runCallback,\n );\n };\n\n // On external dispose, tear down the stale subscription and re-acquire +\n // resubscribe (microtask-deferred, so it never runs mid-mutation of the\n // registry that triggered the dispose, e.g. `clearAll()`), then notify.\n const resubscribeAt = (index: number) => {\n if (disposed) return;\n instances[index] = create\n ? resolveBloc(targets[index], refIds[index])\n : resolveBlocPassive(targets[index]);\n subscribeAt(index);\n subscribeDisposeAt(index);\n runCallback();\n };\n\n const subscribeDisposeAt = (index: number) => {\n disposedUnsubs[index] = instances[index]?.[ON_DISPOSE](() => {\n channelUnsubs[index]?.();\n channelUnsubs[index] = undefined;\n queueMicrotask(() => resubscribeAt(index));\n });\n };\n\n for (let i = 0; i < instances.length; i++) {\n subscribeAt(i);\n }\n\n for (let i = 0; i < instances.length; i++) {\n subscribeDisposeAt(i);\n }\n\n // Fire once immediately so the consumer sees the current state.\n runCallback();\n\n return cleanup;\n}\n\nexport const watch: WatchFn = Object.assign(watchImpl, { STOP }) as WatchFn;\n","declare const brand: unique symbol;\n\n/**\n * Utility type for creating branded/nominal types.\n * Prevents accidental type confusion between similar primitive types.\n * @typeParam T - The base type\n * @typeParam B - The brand identifier\n */\nexport type Brand<T, B> = T & { [brand]: B };\n\n/**\n * Branded string type for type-safe IDs.\n * @typeParam B - The brand identifier\n */\nexport type BrandedId<B> = Brand<string, B>;\n\n/**\n * Branded string type for state container instance IDs\n */\nexport type InstanceId = Brand<string, 'InstanceId'>;\n\n/**\n * Create a branded InstanceId from a string\n * @param id - The string ID to brand\n * @returns Branded InstanceId\n */\nexport function instanceId(id: string): InstanceId {\n return id as InstanceId;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["ALL_PATHS"],"sources":["../src/core/Cubit.ts","../src/registry/borrow.ts","../src/registry/release.ts","../src/registry/queries.ts","../src/registry/management.ts","../src/decorators/blac.ts","../src/watch/watch.ts","../src/types/branded.ts"],"sourcesContent":["import { StateContainer } from './StateContainer';\nimport type { DeepPartial } from '@dirtytalk/structural';\n\n/**\n * `Cubit<S>` is a `StateContainer<S>` that exposes mutation publicly.\n *\n * That is the whole difference, and it is a real one: on `StateContainer`\n * `emit` / `patch` / `update` are `protected`, so a container mutates itself\n * from its own methods and callers go through the API it chooses to publish.\n * `Cubit` re-declares the three as `public` for the cases where a caller\n * legitimately drives state from outside — test helpers, devtools\n * time-travel, benchmarks.\n *\n * Reach for `StateContainer` when business logic should live in the class,\n * and `Cubit` when the caller owns the transitions.\n */\nexport abstract class Cubit<\n S extends object = any,\n Args = void,\n Deps extends object = Record<string, never>,\n> extends StateContainer<S, Args, Deps> {\n override emit(next: S): void {\n super.emit(next);\n }\n\n override patch(partial: DeepPartial<S>): void {\n super.patch(partial);\n }\n\n override update(fn: (state: S) => S): void {\n super.update(fn);\n }\n}\n","import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n} from '../types/utilities';\n\n/**\n * How to identify the instance to borrow: the same `args` shape `useBloc`\n * accepts, resolved through the class's `static key(args)` so call sites use\n * the *same* shape they pass to `useBloc` instead of hand-deriving the key.\n *\n * `borrowSafe(ChannelBloc, { args: { channelId } })` and\n * `useBloc(ChannelBloc, { args: { channelId } })` therefore always agree.\n */\nexport type BorrowTarget<T extends StateContainerConstructor> = {\n args?: ExtractArgs<T>;\n};\n\nfunction resolveBorrowKey<T extends StateContainerConstructor>(\n BlocClass: T,\n target: BorrowTarget<T> | undefined,\n): string {\n return getRegistry().resolveKey(BlocClass, undefined, target?.args);\n}\n\nexport function borrow<T extends StateContainerConstructor>(\n BlocClass: T,\n target?: BorrowTarget<T>,\n): InstanceType<T> {\n return getRegistry().borrow(BlocClass, resolveBorrowKey(BlocClass, target));\n}\n\nexport function borrowSafe<T extends StateContainerConstructor>(\n BlocClass: T,\n target?: BorrowTarget<T>,\n):\n | { error: Error; instance: null }\n | { error: null; instance: InstanceType<T> } {\n return getRegistry().borrowSafe(\n BlocClass,\n resolveBorrowKey(BlocClass, target),\n );\n}\n","import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n} from '../types/utilities';\n\n/**\n * Release a reference to an instance. Instance identity is derived purely from\n * `args` (must match the `args` it was acquired with).\n *\n * @param opts - `args` derives the instance key (must match acquire);\n * `refId` picks the ref to drop, otherwise one arbitrary ref goes;\n * `forceDispose` disposes immediately regardless of remaining refs.\n */\nexport function release<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T>; refId?: string; forceDispose?: boolean },\n): void {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n registry.release(BlocClass, key, opts?.forceDispose ?? false, opts?.refId);\n}\n","import { getRegistry } from './config';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n InstanceReadonlyState,\n} from '../types/utilities';\n\n/**\n * @public\n */\nexport function hasInstance<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T> },\n): boolean {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.hasInstance(BlocClass, key);\n}\n\n/**\n * @public\n */\nexport function getRefCount<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T> },\n): number {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.getRefCount(BlocClass, key);\n}\n\n/**\n * @public\n */\nexport function getRefIds<T extends StateContainerConstructor>(\n BlocClass: T,\n opts?: { args?: ExtractArgs<T> },\n): string[] {\n const registry = getRegistry();\n const key = registry.resolveKey(BlocClass, undefined, opts?.args);\n return registry.getRefIds(BlocClass, key);\n}\n\n/**\n * @public\n */\nexport function getAll<T extends StateContainerConstructor>(\n BlocClass: T,\n): InstanceReadonlyState<T>[] {\n return getRegistry().getAll(BlocClass);\n}\n\n/**\n * @public\n */\nexport function forEach<T extends StateContainerConstructor>(\n BlocClass: T,\n callback: (instance: InstanceReadonlyState<T>) => void,\n): void {\n getRegistry().forEach(BlocClass, callback);\n}\n","import { getRegistry } from './config';\nimport type { StateContainerConstructor } from '../types/utilities';\n\nexport function clear<T extends StateContainerConstructor>(BlocClass: T): void {\n getRegistry().clear(BlocClass);\n}\n\nexport function clearAll(): void {\n getRegistry().clearAll();\n}\n\n/**\n * @public\n */\nexport function register<T extends StateContainerConstructor>(\n BlocClass: T,\n): void {\n getRegistry().register(BlocClass);\n}\n","import { BLAC_STATIC_PROPS } from '../constants';\nimport type { EqualityFn } from '../config';\n\n/**\n * Configuration options for the `@blac` decorator. Any combination may be\n * given at once.\n */\nexport interface BlacOptions {\n /**\n * Keep the bloc alive when its ref count reaches 0. `false` turns off a\n * `keepAlive` inherited from a base class.\n */\n keepAlive?: boolean;\n /** Exclude bloc from DevTools tracking (prevents infinite loops) */\n excludeFromDevTools?: true;\n /**\n * Override the global equality check for this bloc.\n * Return `true` to skip the emit (states considered equal).\n */\n equality?: EqualityFn;\n /**\n * Override the default structural-hash identity key.\n * The function receives `args` and returns the instance key string.\n * Distinct return values → distinct instances; identical values → shared instance.\n * Non-identity fields can be excluded so they don't fork new instances.\n */\n key?: (args: any) => string;\n /**\n * Explicit, minification-safe identity for this bloc class. Falls back to\n * `constructor.name` when unset, which collides across classes once a\n * minifier shortens class names.\n */\n name?: string;\n}\n\n/**\n * Decorator to configure StateContainer classes.\n *\n * @example Decorator syntax (requires experimentalDecorators or TC39 decorators)\n * ```ts\n * @blac({ keepAlive: true })\n * class AuthBloc extends Cubit<AuthState> {}\n *\n * @blac({ excludeFromDevTools: true })\n * class InternalBloc extends Cubit<InternalState> {}\n *\n * @blac({ keepAlive: true, key: (args) => args.userId })\n * class UserBloc extends Cubit<UserState> {}\n * ```\n *\n * @example Function syntax (no decorator support needed)\n * ```ts\n * const AuthBloc = blac({ keepAlive: true })(\n * class extends Cubit<AuthState> {}\n * );\n * ```\n */\nexport function blac(options: BlacOptions) {\n return function <T extends new (...args: any[]) => any>(\n target: T,\n _context?: ClassDecoratorContext,\n ): T {\n if (options.keepAlive !== undefined) {\n (target as any)[BLAC_STATIC_PROPS.KEEP_ALIVE] = options.keepAlive;\n }\n if (options.excludeFromDevTools) {\n (target as any)[BLAC_STATIC_PROPS.EXCLUDE_FROM_DEVTOOLS] = true;\n }\n if (typeof options.equality === 'function') {\n (target as any)[BLAC_STATIC_PROPS.EQUALITY] = options.equality;\n }\n if (typeof options.key === 'function') {\n (target as any)[BLAC_STATIC_PROPS.KEY] = options.key;\n }\n if (typeof options.name === 'string') {\n (target as any)[BLAC_STATIC_PROPS.BLAC_NAME] = options.name;\n }\n return target;\n };\n}\n","import { ALL_PATHS } from '@dirtytalk/structural';\nimport { ON_DISPOSE } from '../core/symbols';\nimport { getRegistry } from '../registry';\nimport { resolveInstanceKey } from '../registry/acquire';\nimport type { StateContainerRegistry } from '../core/StateContainerRegistry';\nimport type {\n ExtractArgs,\n StateContainerConstructor,\n StateContainerInstance,\n} from '../types/utilities';\n\nconst STOP: unique symbol = Symbol('watch.STOP');\ntype StopSymbol = typeof STOP;\n\nconst BLOC_REF_MARKER = Symbol('BlocRef');\n\n/**\n * Reference to a specific bloc instance, identified by class + the resolved\n * key derived from its `args`.\n */\nexport interface BlocRef<T extends StateContainerConstructor> {\n [BLOC_REF_MARKER]: true;\n blocClass: T;\n /** @internal The resolved instance key (derived from `args`). */\n instanceId: string;\n /**\n * @internal The `args` used to derive `instanceId`. Forwarded to\n * `registry.acquire` so a not-yet-created instance is initialized with\n * these args instead of `undefined`.\n */\n args?: unknown;\n}\n\n/**\n * Create a reference to a specific bloc instance, identified by its `args`.\n * The key is derived the same way `useBloc`/`acquire` derive it.\n *\n * @example\n * ```ts\n * watch(instance(UserBloc, { userId: 'user-123' }), (userBloc) => {\n * console.log(userBloc.state.name);\n * });\n * ```\n */\nexport function instance<T extends StateContainerConstructor>(\n BlocClass: T,\n args?: ExtractArgs<T>,\n): BlocRef<T> {\n return {\n [BLOC_REF_MARKER]: true,\n blocClass: BlocClass,\n instanceId: resolveInstanceKey(BlocClass, args),\n args,\n };\n}\n\nfunction isBlocRef(\n input: unknown,\n): input is BlocRef<StateContainerConstructor> {\n return (\n typeof input === 'object' && input !== null && BLOC_REF_MARKER in input\n );\n}\n\ntype BlocInput = StateContainerConstructor | BlocRef<StateContainerConstructor>;\n\n/**\n * Options for `watch`.\n */\nexport interface WatchOptions {\n /**\n * When `false`, `watch` observes passively: it neither creates a missing\n * instance nor takes a real ownership ref on an existing one. If the\n * instance does not exist yet, the callback simply never fires — `watch`\n * does not poll; call it again once the instance exists.\n *\n * Defaults to `true`.\n */\n create?: boolean;\n}\n\ntype ExtractInstance<T> =\n T extends BlocRef<infer C>\n ? InstanceType<C>\n : T extends StateContainerConstructor\n ? InstanceType<T>\n : never;\n\ntype ExtractInstances<T extends readonly BlocInput[]> = {\n [K in keyof T]: ExtractInstance<T[K]>;\n};\n\n/**\n * Watch function signature for single bloc.\n */\nexport interface WatchSingleFn {\n <T extends StateContainerConstructor>(\n bloc: T | BlocRef<T>,\n callback: (bloc: InstanceType<T>) => void | StopSymbol,\n options?: WatchOptions,\n ): () => void;\n\n STOP: StopSymbol;\n}\n\n/**\n * Watch function signature for multiple blocs.\n */\nexport interface WatchMultipleFn {\n <T extends readonly BlocInput[]>(\n blocs: T,\n callback: (blocs: ExtractInstances<T>) => void | StopSymbol,\n options?: WatchOptions,\n ): () => void;\n\n STOP: StopSymbol;\n}\n\n/**\n * Combined watch function type.\n */\nexport interface WatchFn extends WatchSingleFn {\n <T extends readonly BlocInput[]>(\n blocs: T,\n callback: (blocs: ExtractInstances<T>) => void | StopSymbol,\n options?: WatchOptions,\n ): () => void;\n}\n\n/**\n * Class + resolved key + args needed to (re-)acquire a specific instance.\n * `watch` keeps this around per input so it can re-acquire the same logical\n * instance if the underlying container is disposed elsewhere.\n */\ninterface WatchTarget {\n blocClass: StateContainerConstructor;\n key: string;\n args: unknown;\n}\n\nfunction toWatchTarget(\n registry: StateContainerRegistry,\n input: BlocInput,\n): WatchTarget {\n if (isBlocRef(input)) {\n return {\n blocClass: input.blocClass,\n key: input.instanceId,\n args: input.args,\n };\n }\n return {\n blocClass: input,\n key: registry.resolveKey(input, undefined, undefined),\n args: undefined,\n };\n}\n\nlet watchRefSeq = 0;\n\n/**\n * Acquire a real ref (countRef: true) for the target, carrying its `args`\n * through so a not-yet-created instance is initialized correctly. The\n * caller is responsible for releasing `refId` in cleanup.\n */\nfunction resolveBloc(\n registry: StateContainerRegistry,\n target: WatchTarget,\n refId: string,\n): StateContainerInstance {\n return registry.acquire(target.blocClass, target.key, {\n countRef: true,\n refId,\n args: target.args,\n });\n}\n\n/**\n * Passive lookup for `{ create: false }`: never creates the instance and\n * never takes a ref. Returns `undefined` when no instance currently exists.\n */\nfunction resolveBlocPassive(\n registry: StateContainerRegistry,\n target: WatchTarget,\n): StateContainerInstance | undefined {\n try {\n return registry.acquire(target.blocClass, target.key, {\n canCreate: false,\n countRef: false,\n args: target.args,\n });\n } catch {\n return undefined;\n }\n}\n\nfunction isArray(input: unknown): input is readonly BlocInput[] {\n return Array.isArray(input);\n}\n\n/**\n * Watch one or more blocs for state changes.\n *\n * Thin wrapper around `container.channel.subscribe(ALL_PATHS, ...)`. The\n * callback fires once immediately, then on every state change of any of the\n * passed blocs. Returning `watch.STOP` from the callback tears down all\n * subscriptions. Subscriptions are microtask-deferred, so callbacks land\n * asynchronously after `emit()`.\n *\n * By default `watch` creates the instance if it does not exist and holds a\n * real ownership ref until `unwatch`, matching `acquire`. Pass\n * `{ create: false }` to observe passively instead — see `WatchOptions`.\n *\n * @example Single bloc\n * ```ts\n * const unwatch = watch(UserBloc, (userBloc) => {\n * console.log(userBloc.state.name);\n * });\n * ```\n *\n * @example Multiple blocs\n * ```ts\n * const unwatch = watch(\n * [UserBloc, SettingsBloc] as const,\n * ([userBloc, settingsBloc]) => {\n * console.log(userBloc.state.name, settingsBloc.state.theme);\n * }\n * );\n * ```\n *\n * @example With specific instance\n * ```ts\n * const unwatch = watch(\n * instance(UserBloc, { userId: 'user-123' }),\n * (userBloc) => {\n * console.log(userBloc.state.name);\n * }\n * );\n * ```\n *\n * @example Stop watching from callback\n * ```ts\n * const unwatch = watch(UserBloc, (userBloc) => {\n * if (userBloc.state.done) {\n * return watch.STOP;\n * }\n * });\n * ```\n */\nfunction watchImpl<T extends StateContainerConstructor>(\n bloc: T | BlocRef<T>,\n callback: (bloc: InstanceType<T>) => void | StopSymbol,\n options?: WatchOptions,\n): () => void;\n\nfunction watchImpl<T extends readonly BlocInput[]>(\n blocs: T,\n callback: (blocs: ExtractInstances<T>) => void | StopSymbol,\n options?: WatchOptions,\n): () => void;\n\nfunction watchImpl(\n blocsOrBloc: BlocInput | readonly BlocInput[],\n callback: (blocsOrBloc: any) => void | StopSymbol,\n options?: WatchOptions,\n): () => void {\n const isSingle = !isArray(blocsOrBloc);\n const inputs = isSingle ? [blocsOrBloc] : blocsOrBloc;\n const registry = getRegistry();\n const create = options?.create ?? true;\n\n const targets = inputs.map((input) => toWatchTarget(registry, input));\n const refIds = targets.map(() => `_watch_${watchRefSeq++}`);\n const resolveAt = (index: number) =>\n create\n ? resolveBloc(registry, targets[index], refIds[index])\n : resolveBlocPassive(registry, targets[index]);\n\n let disposed = false;\n const instances: Array<StateContainerInstance | undefined> = [];\n const channelUnsubs: Array<(() => void) | undefined> = [];\n const disposedUnsubs: Array<(() => void) | undefined> = [];\n\n const cleanup = () => {\n if (disposed) return;\n disposed = true;\n for (const unsub of channelUnsubs) unsub?.();\n for (const unsub of disposedUnsubs) unsub?.();\n channelUnsubs.length = 0;\n disposedUnsubs.length = 0;\n if (create) {\n for (let i = 0; i < instances.length; i++) {\n registry.release(\n targets[i].blocClass,\n targets[i].key,\n false,\n refIds[i],\n );\n }\n }\n };\n\n const runCallback = () => {\n if (disposed) return;\n if (instances.some((i) => i === undefined)) return;\n const arg = isSingle ? instances[0] : instances;\n const result = callback(arg);\n if (result === STOP) cleanup();\n };\n\n const subscribeAt = (index: number) => {\n channelUnsubs[index] = instances[index]?.channel.subscribe(\n () => ALL_PATHS,\n runCallback,\n );\n };\n\n // On external dispose, tear down the stale subscription and re-acquire +\n // resubscribe (microtask-deferred, so it never runs mid-mutation of the\n // registry that triggered the dispose, e.g. `clearAll()`), then notify.\n const resubscribeAt = (index: number) => {\n if (disposed) return;\n instances[index] = resolveAt(index);\n subscribeAt(index);\n subscribeDisposeAt(index);\n runCallback();\n };\n\n const subscribeDisposeAt = (index: number) => {\n disposedUnsubs[index] = instances[index]?.[ON_DISPOSE](() => {\n channelUnsubs[index]?.();\n channelUnsubs[index] = undefined;\n queueMicrotask(() => resubscribeAt(index));\n });\n };\n\n try {\n for (let i = 0; i < targets.length; i++) instances.push(resolveAt(i));\n for (let i = 0; i < instances.length; i++) subscribeAt(i);\n for (let i = 0; i < instances.length; i++) subscribeDisposeAt(i);\n // Fire once immediately so the consumer sees the current state.\n runCallback();\n } catch (error) {\n cleanup();\n throw error;\n }\n\n return cleanup;\n}\n\nexport const watch: WatchFn = Object.assign(watchImpl, { STOP }) as WatchFn;\n","declare const brand: unique symbol;\n\n/**\n * Nominal type helper, preventing accidental confusion between similar\n * primitive types.\n * @typeParam T - The base type\n * @typeParam B - The brand identifier\n */\nexport type Brand<T, B> = T & { [brand]: B };\n\n/**\n * Branded string type for type-safe IDs.\n * @typeParam B - The brand identifier\n */\nexport type BrandedId<B> = Brand<string, B>;\n\n/** Branded string type for state container instance IDs. */\nexport type InstanceId = Brand<string, 'InstanceId'>;\n\nexport function instanceId(id: string): InstanceId {\n return id as InstanceId;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgBA,IAAsB,QAAtB,cAIU,eAA8B;CACtC,KAAc,MAAe;EAC3B,MAAM,KAAK,IAAI;CACjB;CAEA,MAAe,SAA+B;EAC5C,MAAM,MAAM,OAAO;CACrB;CAEA,OAAgB,IAA2B;EACzC,MAAM,OAAO,EAAE;CACjB;AACF;;;ACdA,SAAS,iBACP,WACA,QACQ;CACR,OAAO,YAAY,EAAE,WAAW,WAAW,KAAA,GAAW,QAAQ,IAAI;AACpE;AAEA,SAAgB,OACd,WACA,QACiB;CACjB,OAAO,YAAY,EAAE,OAAO,WAAW,iBAAiB,WAAW,MAAM,CAAC;AAC5E;AAEA,SAAgB,WACd,WACA,QAG6C;CAC7C,OAAO,YAAY,EAAE,WACnB,WACA,iBAAiB,WAAW,MAAM,CACpC;AACF;;;;;;;;;;;AC5BA,SAAgB,QACd,WACA,MACM;CACN,MAAM,WAAW,YAAY;CAC7B,MAAM,MAAM,SAAS,WAAW,WAAW,KAAA,GAAW,MAAM,IAAI;CAChE,SAAS,QAAQ,WAAW,KAAK,MAAM,gBAAgB,OAAO,MAAM,KAAK;AAC3E;;;;;;ACXA,SAAgB,YACd,WACA,MACS;CACT,MAAM,WAAW,YAAY;CAC7B,MAAM,MAAM,SAAS,WAAW,WAAW,KAAA,GAAW,MAAM,IAAI;CAChE,OAAO,SAAS,YAAY,WAAW,GAAG;AAC5C;;;;AAKA,SAAgB,YACd,WACA,MACQ;CACR,MAAM,WAAW,YAAY;CAC7B,MAAM,MAAM,SAAS,WAAW,WAAW,KAAA,GAAW,MAAM,IAAI;CAChE,OAAO,SAAS,YAAY,WAAW,GAAG;AAC5C;;;;AAKA,SAAgB,UACd,WACA,MACU;CACV,MAAM,WAAW,YAAY;CAC7B,MAAM,MAAM,SAAS,WAAW,WAAW,KAAA,GAAW,MAAM,IAAI;CAChE,OAAO,SAAS,UAAU,WAAW,GAAG;AAC1C;;;;AAKA,SAAgB,OACd,WAC4B;CAC5B,OAAO,YAAY,EAAE,OAAO,SAAS;AACvC;;;;AAKA,SAAgB,QACd,WACA,UACM;CACN,YAAY,EAAE,QAAQ,WAAW,QAAQ;AAC3C;;;ACzDA,SAAgB,MAA2C,WAAoB;CAC7E,YAAY,EAAE,MAAM,SAAS;AAC/B;AAEA,SAAgB,WAAiB;CAC/B,YAAY,EAAE,SAAS;AACzB;;;;AAKA,SAAgB,SACd,WACM;CACN,YAAY,EAAE,SAAS,SAAS;AAClC;;;;;;;;;;;;;;;;;;;;;;;;;ACuCA,SAAgB,KAAK,SAAsB;CACzC,OAAO,SACL,QACA,UACG;EACH,IAAI,QAAQ,cAAc,KAAA,GACxB,OAAgB,kBAAkB,cAAc,QAAQ;EAE1D,IAAI,QAAQ,qBACV,OAAgB,kBAAkB,yBAAyB;EAE7D,IAAI,OAAO,QAAQ,aAAa,YAC9B,OAAgB,kBAAkB,YAAY,QAAQ;EAExD,IAAI,OAAO,QAAQ,QAAQ,YACzB,OAAgB,kBAAkB,OAAO,QAAQ;EAEnD,IAAI,OAAO,QAAQ,SAAS,UAC1B,OAAgB,kBAAkB,aAAa,QAAQ;EAEzD,OAAO;CACT;AACF;;;ACpEA,MAAM,OAAsB,OAAO,YAAY;AAG/C,MAAM,kBAAkB,OAAO,SAAS;;;;;;;;;;;;AA8BxC,SAAgB,SACd,WACA,MACY;CACZ,OAAO;GACJ,kBAAkB;EACnB,WAAW;EACX,YAAY,mBAAmB,WAAW,IAAI;EAC9C;CACF;AACF;AAEA,SAAS,UACP,OAC6C;CAC7C,OACE,OAAO,UAAU,YAAY,UAAU,QAAQ,mBAAmB;AAEtE;AA8EA,SAAS,cACP,UACA,OACa;CACb,IAAI,UAAU,KAAK,GACjB,OAAO;EACL,WAAW,MAAM;EACjB,KAAK,MAAM;EACX,MAAM,MAAM;CACd;CAEF,OAAO;EACL,WAAW;EACX,KAAK,SAAS,WAAW,OAAO,KAAA,GAAW,KAAA,CAAS;EACpD,MAAM,KAAA;CACR;AACF;AAEA,IAAI,cAAc;;;;;;AAOlB,SAAS,YACP,UACA,QACA,OACwB;CACxB,OAAO,SAAS,QAAQ,OAAO,WAAW,OAAO,KAAK;EACpD,UAAU;EACV;EACA,MAAM,OAAO;CACf,CAAC;AACH;;;;;AAMA,SAAS,mBACP,UACA,QACoC;CACpC,IAAI;EACF,OAAO,SAAS,QAAQ,OAAO,WAAW,OAAO,KAAK;GACpD,WAAW;GACX,UAAU;GACV,MAAM,OAAO;EACf,CAAC;CACH,QAAQ;EACN;CACF;AACF;AAEA,SAAS,QAAQ,OAA+C;CAC9D,OAAO,MAAM,QAAQ,KAAK;AAC5B;AA+DA,SAAS,UACP,aACA,UACA,SACY;CACZ,MAAM,WAAW,CAAC,QAAQ,WAAW;CACrC,MAAM,SAAS,WAAW,CAAC,WAAW,IAAI;CAC1C,MAAM,WAAW,YAAY;CAC7B,MAAM,SAAS,SAAS,UAAU;CAElC,MAAM,UAAU,OAAO,KAAK,UAAU,cAAc,UAAU,KAAK,CAAC;CACpE,MAAM,SAAS,QAAQ,UAAU,UAAU,eAAe;CAC1D,MAAM,aAAa,UACjB,SACI,YAAY,UAAU,QAAQ,QAAQ,OAAO,MAAM,IACnD,mBAAmB,UAAU,QAAQ,MAAM;CAEjD,IAAI,WAAW;CACf,MAAM,YAAuD,CAAC;CAC9D,MAAM,gBAAiD,CAAC;CACxD,MAAM,iBAAkD,CAAC;CAEzD,MAAM,gBAAgB;EACpB,IAAI,UAAU;EACd,WAAW;EACX,KAAK,MAAM,SAAS,eAAe,QAAQ;EAC3C,KAAK,MAAM,SAAS,gBAAgB,QAAQ;EAC5C,cAAc,SAAS;EACvB,eAAe,SAAS;EACxB,IAAI,QACF,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KACpC,SAAS,QACP,QAAQ,GAAG,WACX,QAAQ,GAAG,KACX,OACA,OAAO,EACT;CAGN;CAEA,MAAM,oBAAoB;EACxB,IAAI,UAAU;EACd,IAAI,UAAU,MAAM,MAAM,MAAM,KAAA,CAAS,GAAG;EAG5C,IADe,SADH,WAAW,UAAU,KAAK,SAE7B,MAAM,MAAM,QAAQ;CAC/B;CAEA,MAAM,eAAe,UAAkB;EACrC,cAAc,SAAS,UAAU,QAAQ,QAAQ,gBACzCA,aACN,WACF;CACF;CAKA,MAAM,iBAAiB,UAAkB;EACvC,IAAI,UAAU;EACd,UAAU,SAAS,UAAU,KAAK;EAClC,YAAY,KAAK;EACjB,mBAAmB,KAAK;EACxB,YAAY;CACd;CAEA,MAAM,sBAAsB,UAAkB;EAC5C,eAAe,SAAS,UAAU,SAAS,kBAAkB;GAC3D,cAAc,SAAS;GACvB,cAAc,SAAS,KAAA;GACvB,qBAAqB,cAAc,KAAK,CAAC;EAC3C,CAAC;CACH;CAEA,IAAI;EACF,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,UAAU,KAAK,UAAU,CAAC,CAAC;EACpE,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,YAAY,CAAC;EACxD,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,mBAAmB,CAAC;EAE/D,YAAY;CACd,SAAS,OAAO;EACd,QAAQ;EACR,MAAM;CACR;CAEA,OAAO;AACT;AAEA,MAAa,QAAiB,OAAO,OAAO,WAAW,EAAE,KAAK,CAAC;;;AC3U/D,SAAgB,WAAW,IAAwB;CACjD,OAAO;AACT"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_StateContainerRegistry = require("./StateContainerRegistry-DjQQtlCW.cjs");
|
|
3
|
+
const require_StateContainer = require("./StateContainer-BUyuxKb3.cjs");
|
|
4
|
+
exports.APPLY_DEPS = require_StateContainerRegistry.APPLY_DEPS;
|
|
5
|
+
exports.DEP_BRAND = require_StateContainer.DEP_BRAND;
|
|
6
|
+
exports.INIT_CONFIG = require_StateContainerRegistry.INIT_CONFIG;
|
|
7
|
+
exports.INSERT_INSTANCE = require_StateContainerRegistry.INSERT_INSTANCE;
|
|
8
|
+
exports.ON_DISPOSE = require_StateContainerRegistry.ON_DISPOSE;
|
|
9
|
+
exports.REMOVE_DEPS_OWNER = require_StateContainerRegistry.REMOVE_DEPS_OWNER;
|
|
10
|
+
exports.WITH_TRACKED_STATE = require_StateContainerRegistry.WITH_TRACKED_STATE;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework hooks used by `@blac/react` and the testing helpers. Not public
|
|
3
|
+
* API; may change in any release.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
export { DEP_BRAND } from './core/StateContainer';
|
|
8
|
+
export { APPLY_DEPS, REMOVE_DEPS_OWNER, INIT_CONFIG, INSERT_INSTANCE, ON_DISPOSE, WITH_TRACKED_STATE, } from './core/symbols';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework hooks used by `@blac/react` and the testing helpers. Not public
|
|
3
|
+
* API; may change in any release.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
export { DEP_BRAND } from './core/StateContainer';
|
|
8
|
+
export { APPLY_DEPS, REMOVE_DEPS_OWNER, INIT_CONFIG, INSERT_INSTANCE, ON_DISPOSE, WITH_TRACKED_STATE, } from './core/symbols';
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as INSERT_INSTANCE, i as INIT_CONFIG, l as WITH_TRACKED_STATE, o as ON_DISPOSE, r as APPLY_DEPS, s as REMOVE_DEPS_OWNER } from "./StateContainerRegistry-gQbEutkN.js";
|
|
2
|
+
import { t as DEP_BRAND } from "./StateContainer-Bh2ipNYj.js";
|
|
3
|
+
export { APPLY_DEPS, DEP_BRAND, INIT_CONFIG, INSERT_INSTANCE, ON_DISPOSE, REMOVE_DEPS_OWNER, WITH_TRACKED_STATE };
|
|
@@ -17,15 +17,10 @@ export interface InstanceMetadata {
|
|
|
17
17
|
args?: unknown;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Per-event context delivered to plugin hooks.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* arguments, never via the context object.
|
|
25
|
-
*
|
|
26
|
-
* The `container` field is the focal bloc for the event. For `onInstall`
|
|
27
|
-
* (which fires once at install time, not bound to any container) the
|
|
28
|
-
* `container` field is `undefined`.
|
|
20
|
+
* Per-event context delivered to plugin hooks. Identifies the focal
|
|
21
|
+
* container for the event (`undefined` only for `onInstall`, which fires
|
|
22
|
+
* once at install time before any container is known); per-event data (e.g.
|
|
23
|
+
* the changed `PathSet`) is passed via method arguments, not this object.
|
|
29
24
|
*/
|
|
30
25
|
export interface PluginContext {
|
|
31
26
|
/**
|
|
@@ -53,21 +48,10 @@ export interface PluginContext {
|
|
|
53
48
|
/**
|
|
54
49
|
* BlaC plugin hook surface.
|
|
55
50
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* and
|
|
59
|
-
*
|
|
60
|
-
* Hook firing model:
|
|
61
|
-
* - `onCreated` fires synchronously when a container is first acquired.
|
|
62
|
-
* - `onStateChange` fires once per channel flush (microtask-coalesced).
|
|
63
|
-
* `prev` is the state before the first emit of the flush; `next` is the
|
|
64
|
-
* state at flush time; `paths` is the set of paths marked during the flush.
|
|
65
|
-
* - `onDestroyed` fires synchronously when a container is disposed (after
|
|
66
|
-
* its system 'dispose' event).
|
|
67
|
-
*
|
|
68
|
-
* The ref/deps hooks (`onRefAcquired`, `onRefReleased`, `onDepsChanged`) are
|
|
69
|
-
* devtools-only — they drive devtools-connect and are orthogonal to the
|
|
70
|
-
* state-change event payload.
|
|
51
|
+
* `onStateChange` fires once per channel flush (microtask-coalesced) with
|
|
52
|
+
* the changed `PathSet`; lifecycle hooks (`onCreated`, `onDestroyed`) fire
|
|
53
|
+
* synchronously and carry no `paths`. The ref/deps hooks (`onRefAcquired`,
|
|
54
|
+
* `onRefReleased`, `onDepsChanged`) are devtools-only.
|
|
71
55
|
*/
|
|
72
56
|
export interface BlacPlugin {
|
|
73
57
|
readonly name: string;
|
|
@@ -3,25 +3,16 @@ import type { BlacPlugin, PluginConfig } from './BlacPlugin';
|
|
|
3
3
|
/**
|
|
4
4
|
* Manages plugin lifecycle for the BlaC state management system.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Hooks into registry lifecycle events (synchronous) and per-container
|
|
7
|
+
* channel flushes (microtask-coalesced): while at least one installed
|
|
8
|
+
* plugin implements `onStateChange`, the manager subscribes each container
|
|
9
|
+
* with `ALL_PATHS` interest and dispatches
|
|
10
|
+
* `onStateChange(ctx, prev, next, paths)` on every flush.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 2. **Per-container channel flushes** — microtask-coalesced. For each
|
|
13
|
-
* container, the manager subscribes once at create-time with
|
|
14
|
-
* `ALL_PATHS` interest and stashes `prevState`. On every flush it
|
|
15
|
-
* captures the new state + the changed `PathSet` and dispatches
|
|
16
|
-
* `onStateChange(ctx, prev, next, paths)` to every enabled plugin.
|
|
17
|
-
*
|
|
18
|
-
* The `ALL_PATHS` subscription cost: plugins counted as a consumer with
|
|
19
|
-
* `ALL_PATHS` interest will defeat the single-consumer-skip optimization
|
|
20
|
-
* in `StructuralContainer`. This is the intended trade-off — devtools /
|
|
21
|
-
* persist plugins genuinely want every change, and stateful plugins
|
|
22
|
-
* (logging) can decode `paths` via `container.interner.lookup(id)` to log
|
|
23
|
-
* only relevant fields. Plugins that want low overhead should remain
|
|
24
|
-
* uninstalled or environment-gated.
|
|
12
|
+
* `ALL_PATHS` interest defeats the single-consumer-skip optimization in
|
|
13
|
+
* `StructuralContainer` — an intended trade-off for plugins that genuinely
|
|
14
|
+
* want every change (devtools/persist). Plugins that want low overhead
|
|
15
|
+
* should remain uninstalled or environment-gated.
|
|
25
16
|
*
|
|
26
17
|
* @example
|
|
27
18
|
* ```ts
|
|
@@ -34,19 +25,18 @@ export declare class PluginManager {
|
|
|
34
25
|
private registry;
|
|
35
26
|
private lifecycleUnsubscribers;
|
|
36
27
|
/**
|
|
37
|
-
* Per-container channel-bridge bookkeeping
|
|
38
|
-
* torn down at `disposed
|
|
39
|
-
* snapshot
|
|
28
|
+
* Per-container channel-bridge bookkeeping, subscribed at `created` and
|
|
29
|
+
* torn down at `disposed`/`destroy()`. Holds the rolling `prevState`
|
|
30
|
+
* snapshot handed to plugins on each flush.
|
|
40
31
|
*
|
|
41
|
-
* Strong, not a WeakMap
|
|
42
|
-
*
|
|
43
|
-
* the
|
|
32
|
+
* Strong, not a `WeakMap`, because `destroy()` must enumerate live bridges
|
|
33
|
+
* to unsubscribe them; entries are removed on `disposed`, so this never
|
|
34
|
+
* outlives the registry's own reference.
|
|
44
35
|
*/
|
|
45
36
|
private containerBridges;
|
|
46
37
|
/**
|
|
47
|
-
* One `PluginContext` per container
|
|
48
|
-
* `container
|
|
49
|
-
* safe to reuse instead of rebuilding its method table on every dispatch.
|
|
38
|
+
* One `PluginContext` per container, safe to reuse since it closes only
|
|
39
|
+
* over `registry`/`container`, both stable for the container's lifetime.
|
|
50
40
|
*/
|
|
51
41
|
private contextCache;
|
|
52
42
|
/**
|
|
@@ -92,59 +82,46 @@ export declare class PluginManager {
|
|
|
92
82
|
/**
|
|
93
83
|
* Wire registry lifecycle events into plugin dispatch.
|
|
94
84
|
*
|
|
95
|
-
* `onStateChange` is
|
|
96
|
-
* that event lacks the `PathSet` payload. Instead
|
|
97
|
-
*
|
|
98
|
-
* `(
|
|
85
|
+
* `onStateChange` is not wired through `registry.on('stateChanged', …)` —
|
|
86
|
+
* that event lacks the `PathSet` payload. Instead each `created` subscribes
|
|
87
|
+
* to the container's channel directly for `paths`, pairing it with
|
|
88
|
+
* `(prev, next)` from the per-container snapshot.
|
|
99
89
|
*/
|
|
100
90
|
private setupLifecycleHooks;
|
|
101
91
|
/**
|
|
102
|
-
* Subscribe to the container's channel with `ALL_PATHS` interest
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* Per-container bookkeeping is stored in `containerBridges`, a strong `Map`
|
|
108
|
-
* keyed by the container. Entries are removed explicitly — on the
|
|
109
|
-
* container's `disposed` event, or for all of them in `destroy()`.
|
|
92
|
+
* Subscribe to the container's channel with `ALL_PATHS` interest so it
|
|
93
|
+
* fires on every flush. `prev` is the snapshot from the previous flush (or
|
|
94
|
+
* create-time for the first); the channel's `paths` is passed straight
|
|
95
|
+
* through to plugins.
|
|
110
96
|
*/
|
|
111
97
|
private attachStateBridge;
|
|
112
98
|
private detachStateBridge;
|
|
99
|
+
private hasStateChangePlugin;
|
|
100
|
+
private detachAllStateBridges;
|
|
113
101
|
/**
|
|
114
|
-
* Backfill a newly-installed plugin with
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* `attachStateBridge`) and, if the plugin implements `onCreated`, invoking
|
|
119
|
-
* it directly for each instance. Scoped to this single plugin only — the
|
|
120
|
-
* broadcast `notifyPlugins` would re-notify *every* plugin of *every*
|
|
121
|
-
* instance, which is wrong here.
|
|
102
|
+
* Backfill a newly-installed plugin with existing instances: attach the
|
|
103
|
+
* state bridge (idempotent) if it implements `onStateChange`, and invoke
|
|
104
|
+
* `onCreated` for each. Scoped to this plugin only — `notifyPlugins` would
|
|
105
|
+
* wrongly re-notify every plugin of every instance.
|
|
122
106
|
*/
|
|
123
107
|
private backfillPlugin;
|
|
124
108
|
/**
|
|
125
|
-
* Channel-flush callback.
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* Note: `prev` is captured once and reused across plugins — every
|
|
130
|
-
* plugin sees the same `prev`/`next`/`paths` regardless of dispatch
|
|
131
|
-
* order, matching the "snapshot prev once per flush" invariant.
|
|
109
|
+
* Channel-flush callback. Hands `(prev, next, paths)` to every enabled
|
|
110
|
+
* plugin's `onStateChange`, then updates `prevState` for the next flush.
|
|
111
|
+
* `prev` is captured once and reused across plugins, so every plugin sees
|
|
112
|
+
* the same values regardless of dispatch order.
|
|
132
113
|
*/
|
|
133
114
|
private dispatchStateChange;
|
|
134
115
|
/**
|
|
135
|
-
* Notify all plugins of a lifecycle event.
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* `ctx.container`), so plugins can reach the focal bloc through
|
|
139
|
-
* `ctx.container`. The context is built lazily on the first matching
|
|
140
|
-
* hook and reused for every other plugin in this dispatch — zero
|
|
141
|
-
* builds if no enabled plugin implements `hookName`.
|
|
116
|
+
* Notify all plugins of a lifecycle event. Builds a `PluginContext` lazily
|
|
117
|
+
* on the first matching hook and reuses it for the rest of this dispatch —
|
|
118
|
+
* zero builds if no enabled plugin implements `hookName`.
|
|
142
119
|
*/
|
|
143
120
|
private notifyPlugins;
|
|
144
121
|
/**
|
|
145
122
|
* Get the `PluginContext` for a focal container, building it once and
|
|
146
|
-
* reusing it thereafter
|
|
147
|
-
* dies
|
|
123
|
+
* reusing it thereafter (evicted via the `WeakMap` when the container
|
|
124
|
+
* dies). `undefined` (install-time) is never cached.
|
|
148
125
|
*/
|
|
149
126
|
private buildContext;
|
|
150
127
|
private createContext;
|
|
@@ -153,7 +130,7 @@ export declare class PluginManager {
|
|
|
153
130
|
*/
|
|
154
131
|
private shouldEnablePlugin;
|
|
155
132
|
/**
|
|
156
|
-
*
|
|
133
|
+
* Same rule as `IS_DEV`: an unknown environment counts as production.
|
|
157
134
|
*/
|
|
158
135
|
private getCurrentEnvironment;
|
|
159
136
|
}
|