Skip to content

Instance management

The registry is a global singleton that manages the lifecycle of state container instances. It handles creation, sharing, ref counting, and disposal. Every signature on this page is quoted from the @blac/core source.

The mental model: a shared library with checkouts

Section titled “The mental model: a shared library with checkouts”

Think of the registry as a lending library for bloc instances:

  • Each (class, instanceKey) pair is one book on the shelf. Ask for the same pair twice and you get the same physical book — that is how two components share state without prop-drilling.
  • A consumer that needs a book checks it out (acquire) and returns it (release) when done. Every checkout is a reference (a “ref”).
  • The library keeps a count of who has each book checked out. When the count drops to zero, the book is discarded (dispose) — its state is gone and any timers or subscriptions it set up are torn down.
  • keepAlive marks a book as a permanent reference copy: it stays on the shelf even at zero checkouts.

Two consequences fall straight out of this model:

  1. Sharing is automatic. The first acquire of a key creates the instance; every later acquire of the same key reuses it. You never wire instances together by hand.
  2. Cleanup is automatic — but only if every checkout is returned. A missing release is like never returning a library book: the count never reaches zero, the instance never disposes, and its memory (and subscriptions) leak.

When you call useBloc(CounterCubit) in React:

  1. The hook calls acquire(CounterCubit), which either creates a new instance or returns the existing one.
  2. The ref count increments (+1). Each consumer gets its own ref (a unique refId), so two components reading the same bloc count as two refs.
  3. When the component unmounts, release(CounterCubit) decrements the ref count (-1).
  4. At ref count zero, the instance is disposed (unless keepAlive is set).

This means shared instances are automatically cleaned up when no component needs them.

acquire ───▶ ref count 1 (created, init() runs, 'created' fires)
acquire ───▶ ref count 2 (same instance reused — shared)
release ───▶ ref count 1 (still alive, one consumer remains)
release ───▶ ref count 0 ─┬─ keepAlive? yes ─▶ stays alive
└─ keepAlive? no ─▶ dispose()
├─ 'dispose' system event fires
├─ subscriptions/handlers torn down
├─ entry removed from registry
└─ ensure-created deps with 0 refs cascade-dispose

Create or return an existing instance, incrementing the ref count.

function acquire<T extends StateContainerConstructor>(
BlocClass: T,
opts?: { args?: ExtractArgs<T>; refId?: string },
): InstanceType<T>;
ParameterTypeRequiredDescription
BlocClassT extends StateContainerConstructoryesThe state-container class to acquire an instance of.
opts.argsExtractArgs<T>noSerializable construction data passed to init(args). Derives the instance key.
opts.refIdstringnoCaller-supplied ref identifier. Used by useBloc to pair with release. Rarely set manually.

Returns: InstanceType<T> — the live instance (newly created or existing).

Behavior. The instance key is derived from opts.args (static key if declared, else the structural hash, else 'default'). If no instance exists for the resolved key, acquire creates one, calls init(args), and fires the internal 'created' event. If an instance already exists, it is returned as-is. The ref count is always incremented. Every acquire must have a matching release — a missing release is the canonical instance leak. In React, useBloc owns this pair; call acquire directly only in server-side or scripting contexts.

import {
function acquire<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
refId?: string;
}): InstanceType<T>

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
,
function release<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
refId?: string;
forceDispose?: boolean;
}): void

Release a reference to an instance. Instance identity is derived purely from args (must match the args it was acquired with).

@param ― opts - args derives the instance key (must match acquire); refId picks the ref to drop, otherwise one arbitrary ref goes; forceDispose disposes immediately regardless of remaining refs.

release
} from '@blac/core';
import {
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
class
class CounterCubit
CounterCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<{
count: number
count
: number }> {
constructor() {
super({
count: number
count
: 0 });
}
CounterCubit.increment: () => void
increment
= () => this.
Cubit<{ count: number; }, void, Record<string, never>>.update(fn: (state: {
count: number;
}) => {
count: number;
}): void
update
((
s: {
count: number;
}
s
) => ({
count: number
count
:
s: {
count: number;
}
s
.
count: number
count
+ 1 }));
}
// --- outside React ---
const
const counter: CounterCubit
counter
=
acquire<typeof CounterCubit>(BlocClass: typeof CounterCubit, opts?: {
args?: void | undefined;
refId?: string;
} | undefined): CounterCubit

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
(
class CounterCubit
CounterCubit
);
const counter: CounterCubit
counter
.
CounterCubit.increment: () => void
increment
();
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
const counter: CounterCubit
counter
.
StateContainer<{ count: number; }, void, Record<string, never>>.state: {
count: number;
}
state
.
count: number
count
); // => 1
release<typeof CounterCubit>(BlocClass: typeof CounterCubit, opts?: {
args?: void | undefined;
refId?: string;
forceDispose?: boolean;
} | undefined): void

Release a reference to an instance. Instance identity is derived purely from args (must match the args it was acquired with).

@param ― opts - args derives the instance key (must match acquire); refId picks the ref to drop, otherwise one arbitrary ref goes; forceDispose disposes immediately regardless of remaining refs.

release
(
class CounterCubit
CounterCubit
); // must pair with acquire

Create or return an existing instance without taking a ref.

function ensure<T extends StateContainerConstructor>(
BlocClass: T,
opts?: { args?: ExtractArgs<T> },
): InstanceType<T>;
ParameterTypeRequiredDescription
BlocClassT extends StateContainerConstructoryesThe state-container class.
opts.argsExtractArgs<T>noConstruction data for init(args) and key derivation.

Returns: InstanceType<T> — the live instance.

Behavior. Like acquire, but takes no ref — the instance can be disposed by another caller while you hold the returned reference. Use ensure inside other cubits (via depend()) or in tooling that only needs the instance to exist without pinning its lifecycle. Because no ref is taken, no matching release is needed.

import {
function ensure<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
}): InstanceType<T>

Ensure an instance exists without taking ownership (no ref added). Instance identity is derived purely from args, matching acquire.

ensure
} from '@blac/core';
import {
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
class
class AuthCubit
AuthCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<{
userId: string | null
userId
: string | null }> {
constructor() {
super({
userId: string | null
userId
: null });
}
}
// ensure creates AuthCubit if it's not alive yet, but does not keep it alive
const
const auth: AuthCubit
auth
=
ensure<typeof AuthCubit>(BlocClass: typeof AuthCubit, opts?: {
args?: void | undefined;
} | undefined): AuthCubit

Ensure an instance exists without taking ownership (no ref added). Instance identity is derived purely from args, matching acquire.

ensure
(
class AuthCubit
AuthCubit
);
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
const auth: AuthCubit
auth
.
StateContainer<{ userId: string | null; }, void, Record<string, never>>.state: {
userId: string | null;
}
state
.
userId: string | null
userId
); // no release needed

Return an existing instance without taking a ref. Throws if the instance does not exist.

function borrow<T extends StateContainerConstructor>(
BlocClass: T,
opts?: { args?: ExtractArgs<T> },
): InstanceType<T>;
ParameterTypeRequiredDescription
BlocClassT extends StateContainerConstructoryesThe state-container class.
opts.argsExtractArgs<T>noArgs identifying the instance. Defaults to the 'default' key.

Returns: InstanceType<T> — the live instance.

Behavior. Does not create the instance if it is absent; throws an Error instead. Use borrow when the absence of an instance is a programming error — it makes the failure loud and immediate rather than silently returning undefined. No ref is taken, so no release is needed.

import {
function borrow<T extends StateContainerConstructor>(BlocClass: T, target?: BorrowTarget<T>): InstanceType<T>
borrow
} from '@blac/core';
import {
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
class
class CounterCubit
CounterCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<{
count: number
count
: number }> {
constructor() {
super({
count: number
count
: 0 });
}
}
// Safe read-only access — no acquire/release pair needed
function
function readCount(): number
readCount
(): number {
return
borrow<typeof CounterCubit>(BlocClass: typeof CounterCubit, target?: BorrowTarget<typeof CounterCubit> | undefined): CounterCubit
borrow
(
class CounterCubit
CounterCubit
).
StateContainer<{ count: number; }, void, Record<string, never>>.state: {
count: number;
}
state
.
count: number
count
;
}

Return an existing instance without taking a ref. Returns an error object instead of throwing.

function borrowSafe<T extends StateContainerConstructor>(
BlocClass: T,
opts?: { args?: ExtractArgs<T> },
):
| { error: Error; instance: null }
| { error: null; instance: InstanceType<T> };
ParameterTypeRequiredDescription
BlocClassT extends StateContainerConstructoryesThe state-container class.
opts.argsExtractArgs<T>noArgs identifying the instance. Defaults to the 'default' key.

Returns: { error: null; instance: InstanceType<T> } when the instance exists, or { error: Error; instance: null } when it does not.

Behavior. Identical to borrow but returns a discriminated union instead of throwing. Prefer borrowSafe in code paths where absence is expected (e.g. optional integrations) and borrow where absence is always a bug.

import {
function borrowSafe<T extends StateContainerConstructor>(BlocClass: T, target?: BorrowTarget<T>): {
error: Error;
instance: null;
} | {
error: null;
instance: InstanceType<T>;
}
borrowSafe
} from '@blac/core';
import {
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
class
class AuthCubit
AuthCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<{
userId: string | null
userId
: string | null }> {
constructor() {
super({
userId: string | null
userId
: null });
}
}
const {
const error: Error | null
error
,
const instance: AuthCubit | null
instance
} =
borrowSafe<typeof AuthCubit>(BlocClass: typeof AuthCubit, target?: BorrowTarget<typeof AuthCubit> | undefined): {
error: Error;
instance: null;
} | {
error: null;
instance: AuthCubit;
}
borrowSafe
(
class AuthCubit
AuthCubit
);
if (
const error: Error | null
error
) {
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
('Auth not initialized yet');
} else {
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
const instance: AuthCubit
instance
.
StateContainer<{ userId: string | null; }, void, Record<string, never>>.state: {
userId: string | null;
}
state
.
userId: string | null
userId
);
}

Decrement the ref count. Dispose the instance when count reaches zero.

function release<T extends StateContainerConstructor>(
BlocClass: T,
opts?: { args?: ExtractArgs<T>; refId?: string; forceDispose?: boolean },
): void;
ParameterTypeRequiredDescription
BlocClassT extends StateContainerConstructoryesThe state-container class.
opts.argsExtractArgs<T>noThe args used when acquire was called. Must resolve to the same key.
opts.refIdstringnoThe ref identifier passed to acquire. Rarely set manually.
opts.forceDisposebooleannoWhen true, dispose immediately even if keepAlive is set. Default false.

Returns: void.

Behavior. release is idempotent for an already-dropped ref — releasing more times than you acquired won’t throw, it just no-ops once the count is gone. The args you release with must resolve to the same key you acquired with; a mismatch leaves the original ref dangling forever. At ref count zero, the instance is disposed synchronously (unless keepAlive is set or forceDispose is false).

import {
function acquire<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
refId?: string;
}): InstanceType<T>

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
,
function release<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
refId?: string;
forceDispose?: boolean;
}): void

Release a reference to an instance. Instance identity is derived purely from args (must match the args it was acquired with).

@param ― opts - args derives the instance key (must match acquire); refId picks the ref to drop, otherwise one arbitrary ref goes; forceDispose disposes immediately regardless of remaining refs.

release
} from '@blac/core';
import {
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
class
class SessionCubit
SessionCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<{
token: string | null
token
: string | null }, {
scope: string
scope
: string }> {
static
SessionCubit.key: (a: {
scope: string;
}) => string
key
= (
a: {
scope: string;
}
a
: {
scope: string
scope
: string }) =>
a: {
scope: string;
}
a
.
scope: string
scope
;
constructor() {
super({
token: string | null
token
: null });
}
}
const
const session: SessionCubit
session
=
acquire<typeof SessionCubit>(BlocClass: typeof SessionCubit, opts?: {
args?: {
scope: string;
} | undefined;
refId?: string;
} | undefined): SessionCubit

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
(
class SessionCubit
SessionCubit
, {
args?: {
scope: string;
} | undefined
args
: {
scope: string
scope
: 'main' } });
// ... use session ...
release<typeof SessionCubit>(BlocClass: typeof SessionCubit, opts?: {
args?: {
scope: string;
} | undefined;
refId?: string;
forceDispose?: boolean;
} | undefined): void

Release a reference to an instance. Instance identity is derived purely from args (must match the args it was acquired with).

@param ― opts - args derives the instance key (must match acquire); refId picks the ref to drop, otherwise one arbitrary ref goes; forceDispose disposes immediately regardless of remaining refs.

release
(
class SessionCubit
SessionCubit
, {
args?: {
scope: string;
} | undefined
args
: {
scope: string
scope
: 'main' } }); // args must match acquire's

To manage distinct named instances, declare an Args shape and pass args to any registry function. The instance key is derived from those args — a static key if the class declares one, otherwise the structural hash of the args:

import {
function acquire<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
refId?: string;
}): InstanceType<T>

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
,
function release<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
refId?: string;
forceDispose?: boolean;
}): void

Release a reference to an instance. Instance identity is derived purely from args (must match the args it was acquired with).

@param ― opts - args derives the instance key (must match acquire); refId picks the ref to drop, otherwise one arbitrary ref goes; forceDispose disposes immediately regardless of remaining refs.

release
} from '@blac/core';
import {
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
class
class EditorCubit
EditorCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<{
content: string
content
: string }, {
docId: string
docId
: string }> {
static
EditorCubit.key: (a: {
docId: string;
}) => string
key
= (
a: {
docId: string;
}
a
: {
docId: string
docId
: string }) =>
a: {
docId: string;
}
a
.
docId: string
docId
;
constructor() {
super({
content: string
content
: '' });
}
}
const
const editor1: EditorCubit
editor1
=
acquire<typeof EditorCubit>(BlocClass: typeof EditorCubit, opts?: {
args?: {
docId: string;
} | undefined;
refId?: string;
} | undefined): EditorCubit

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
(
class EditorCubit
EditorCubit
, {
args?: {
docId: string;
} | undefined
args
: {
docId: string
docId
: 'doc-42' } });
const
const editor2: EditorCubit
editor2
=
acquire<typeof EditorCubit>(BlocClass: typeof EditorCubit, opts?: {
args?: {
docId: string;
} | undefined;
refId?: string;
} | undefined): EditorCubit

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
(
class EditorCubit
EditorCubit
, {
args?: {
docId: string;
} | undefined
args
: {
docId: string
docId
: 'doc-99' } });
// These are different instances
const
const areDifferent: boolean
areDifferent
=
const editor1: EditorCubit
editor1
!==
const editor2: EditorCubit
editor2
; // true
release<typeof EditorCubit>(BlocClass: typeof EditorCubit, opts?: {
args?: {
docId: string;
} | undefined;
refId?: string;
forceDispose?: boolean;
} | undefined): void

Release a reference to an instance. Instance identity is derived purely from args (must match the args it was acquired with).

@param ― opts - args derives the instance key (must match acquire); refId picks the ref to drop, otherwise one arbitrary ref goes; forceDispose disposes immediately regardless of remaining refs.

release
(
class EditorCubit
EditorCubit
, {
args?: {
docId: string;
} | undefined
args
: {
docId: string
docId
: 'doc-42' } });
release<typeof EditorCubit>(BlocClass: typeof EditorCubit, opts?: {
args?: {
docId: string;
} | undefined;
refId?: string;
forceDispose?: boolean;
} | undefined): void

Release a reference to an instance. Instance identity is derived purely from args (must match the args it was acquired with).

@param ― opts - args derives the instance key (must match acquire); refId picks the ref to drop, otherwise one arbitrary ref goes; forceDispose disposes immediately regardless of remaining refs.

release
(
class EditorCubit
EditorCubit
, {
args?: {
docId: string;
} | undefined
args
: {
docId: string
docId
: 'doc-99' } });

In React, pass the same args:

const [state] = useBloc(EditorCubit, { args: { docId: 'doc-42' } });

args are the only way to get distinct instances — the meaningful value keys the instance and feeds init(args), so you never pass the same id twice:

// The meaningful value keys the instance AND feeds init(args)
const [s] = useBloc(UserCardCubit, { args: { userId } });

Identity precedence: own args (via static key(args), else structural hash of args) > <BlocProvider> context args > 'default'.

The resolved key is the registry’s single source of truth — acquire and release both run their args through the same resolution, so a ref taken under a given args key is dropped under the same key. See Passing Inputs for the full model and Configuration for static key.

import {
function hasInstance<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
}): boolean

@public

hasInstance
,
function getRefCount<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
}): number

@public

getRefCount
,
function getAll<T extends StateContainerConstructor>(BlocClass: T): InstanceReadonlyState<T>[]

@public

getAll
,
function forEach<T extends StateContainerConstructor>(BlocClass: T, callback: (instance: InstanceReadonlyState<T>) => void): void

@public

forEach
,
function getStats(): {
registeredTypes: number;
totalInstances: number;
typeBreakdown: Record<string, number>;
}

@public

getStats
,
} from '@blac/core';
import {
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
class
class CounterCubit
CounterCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<{
count: number
count
: number }> {
constructor() {
super({
count: number
count
: 0 });
}
}
const
const _a: boolean
_a
=
hasInstance<typeof CounterCubit>(BlocClass: typeof CounterCubit, opts?: {
args?: void | undefined;
} | undefined): boolean

@public

hasInstance
(
class CounterCubit
CounterCubit
); // boolean
const
const _b: number
_b
=
getRefCount<typeof CounterCubit>(BlocClass: typeof CounterCubit, opts?: {
args?: void | undefined;
} | undefined): number

@public

getRefCount
(
class CounterCubit
CounterCubit
); // number of distinct active refs
const
const _c: InstanceReadonlyState<typeof CounterCubit>[]
_c
=
getAll<typeof CounterCubit>(BlocClass: typeof CounterCubit): InstanceReadonlyState<typeof CounterCubit>[]

@public

getAll
(
class CounterCubit
CounterCubit
); // all instances of this class
forEach<typeof CounterCubit>(BlocClass: typeof CounterCubit, callback: (instance: InstanceReadonlyState<typeof CounterCubit>) => void): void

@public

forEach
(
class CounterCubit
CounterCubit
, (
_inst: InstanceReadonlyState<typeof CounterCubit>
_inst
) => {}); // iterate instances
const
const _d: {
registeredTypes: number;
totalInstances: number;
typeBreakdown: Record<string, number>;
}
_d
=
function getStats(): {
registeredTypes: number;
totalInstances: number;
typeBreakdown: Record<string, number>;
}

@public

getStats
(); // { registeredTypes, totalInstances, typeBreakdown }
import {
function clear<T extends StateContainerConstructor>(BlocClass: T): void
clear
,
function clearAll(): void
clearAll
} from '@blac/core';
import {
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
class
class CounterCubit
CounterCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<{
count: number
count
: number }> {
constructor() {
super({
count: number
count
: 0 });
}
}
clear<typeof CounterCubit>(BlocClass: typeof CounterCubit): void
clear
(
class CounterCubit
CounterCubit
); // dispose and remove all instances of this class
function clearAll(): void
clearAll
(); // dispose and remove everything

In React, useBloc handles acquire and release automatically. You rarely call registry functions directly.

Outside React (tests, scripts, server-side code), manage the lifecycle manually:

import {
function acquire<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
refId?: string;
}): InstanceType<T>

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
,
function release<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
refId?: string;
forceDispose?: boolean;
}): void

Release a reference to an instance. Instance identity is derived purely from args (must match the args it was acquired with).

@param ― opts - args derives the instance key (must match acquire); refId picks the ref to drop, otherwise one arbitrary ref goes; forceDispose disposes immediately regardless of remaining refs.

release
} from '@blac/core';
import {
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
class
class CounterCubit
CounterCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<{
count: number
count
: number }> {
constructor() {
super({
count: number
count
: 0 });
}
CounterCubit.increment: () => void
increment
= () => this.
Cubit<{ count: number; }, void, Record<string, never>>.update(fn: (state: {
count: number;
}) => {
count: number;
}): void
update
((
s: {
count: number;
}
s
) => ({
count: number
count
:
s: {
count: number;
}
s
.
count: number
count
+ 1 }));
}
const
const counter: CounterCubit
counter
=
acquire<typeof CounterCubit>(BlocClass: typeof CounterCubit, opts?: {
args?: void | undefined;
refId?: string;
} | undefined): CounterCubit

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
(
class CounterCubit
CounterCubit
);
const counter: CounterCubit
counter
.
CounterCubit.increment: () => void
increment
();
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
(
const counter: CounterCubit
counter
.
StateContainer<{ count: number; }, void, Record<string, never>>.state: {
count: number;
}
state
.
count: number
count
);
release<typeof CounterCubit>(BlocClass: typeof CounterCubit, opts?: {
args?: void | undefined;
refId?: string;
forceDispose?: boolean;
} | undefined): void

Release a reference to an instance. Instance identity is derived purely from args (must match the args it was acquired with).

@param ― opts - args derives the instance key (must match acquire); refId picks the ref to drop, otherwise one arbitrary ref goes; forceDispose disposes immediately regardless of remaining refs.

release
(
class CounterCubit
CounterCubit
);
  • Configuration — keepAlive, static key, and the circuit breakers that catch leaks
  • Passing Inputs — args and the full instance-identity model
  • System Events — the dispose event that fires when an instance is torn down
  • Testing core logic — using clear/clearAll to isolate the registry between tests

For the full FAQ see Troubleshooting. Below are the instance-management-specific problems.

Symptom: A component unmounts and remounts and finds the bloc in its initial state — any previous state is gone.

Cause: When the last consumer releases, the instance is disposed immediately (ref count 0 → dispose). The next consumer acquires a brand-new instance with fresh state.

Fix: If state must persist across unmounts, mark the class keepAlive:

import {
function blac(options: BlacOptions): <T extends new (...args: any[]) => any>(target: T, _context?: ClassDecoratorContext) => T

Decorator to configure StateContainer classes.

@example

Decorator syntax (requires experimentalDecorators or TC39 decorators)

@blac ― ({ keepAlive: true }) class AuthBloc extends Cubit {}

@blac ― ({ excludeFromDevTools: true }) class InternalBloc extends Cubit {}

@blac ―

({ keepAlive: true, key: (args) => args.userId }) class UserBloc extends Cubit {}

@example

Function syntax (no decorator support needed)

const AuthBloc = blac({ keepAlive: true })(
class extends Cubit<AuthState> {}
);

blac
,
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
interface
interface SessionState
SessionState
{
SessionState.token: string | null
token
: string | null;
}
@
function blac(options: BlacOptions): <T extends new (...args: any[]) => any>(target: T, _context?: ClassDecoratorContext) => T

Decorator to configure StateContainer classes.

@example

Decorator syntax (requires experimentalDecorators or TC39 decorators)

@blac ― ({ keepAlive: true }) class AuthBloc extends Cubit {}

@blac ― ({ excludeFromDevTools: true }) class InternalBloc extends Cubit {}

@blac ―

({ keepAlive: true, key: (args) => args.userId }) class UserBloc extends Cubit {}

@example

Function syntax (no decorator support needed)

const AuthBloc = blac({ keepAlive: true })(
class extends Cubit<AuthState> {}
);

blac
({
BlacOptions.keepAlive?: boolean | undefined

Keep the bloc alive when its ref count reaches 0. false turns off a keepAlive inherited from a base class.

keepAlive
: true })
class
class SessionCubit
SessionCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<
interface SessionState
SessionState
> {
constructor() {
super({
SessionState.token: string | null
token
: null });
}
}

keepAlive instances are never auto-disposed at ref count 0 — tear them down explicitly with release(Class, { forceDispose: true }) or clear(Class) in teardown. See Configuration.

Symptom: DevTools shows an instance that should have been disposed is still alive, or ref count never returns to 0.

Cause: An acquire outside React has no matching release. Every call to acquire increments the ref count; without a matching release the count never reaches zero.

Fix: Pair every acquire with a release, ideally in a try/finally. If you only need to read without owning the lifecycle, use borrow or ensure — neither takes a ref:

import {
function acquire<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
refId?: string;
}): InstanceType<T>

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
,
function borrow<T extends StateContainerConstructor>(BlocClass: T, target?: BorrowTarget<T>): InstanceType<T>
borrow
,
function release<T extends StateContainerConstructor>(BlocClass: T, opts?: {
args?: ExtractArgs<T>;
refId?: string;
forceDispose?: boolean;
}): void

Release a reference to an instance. Instance identity is derived purely from args (must match the args it was acquired with).

@param ― opts - args derives the instance key (must match acquire); refId picks the ref to drop, otherwise one arbitrary ref goes; forceDispose disposes immediately regardless of remaining refs.

release
} from '@blac/core';
import {
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
} from '@blac/core';
class
class CounterCubit
CounterCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> that exposes mutation publicly.

That is the whole difference, and it is a real one: on StateContainer emit / patch / update are protected, so a container mutates itself from its own methods and callers go through the API it chooses to publish. Cubit re-declares the three as public for the cases where a caller legitimately drives state from outside — test helpers, devtools time-travel, benchmarks.

Reach for StateContainer when business logic should live in the class, and Cubit when the caller owns the transitions.

Cubit
<{
count: number
count
: number }> {
constructor() {
super({
count: number
count
: 0 });
}
}
// Leaks — no release
function
function readOnceBad(): number
readOnceBad
() {
const
const c: CounterCubit
c
=
acquire<typeof CounterCubit>(BlocClass: typeof CounterCubit, opts?: {
args?: void | undefined;
refId?: string;
} | undefined): CounterCubit

Acquire an instance with ref tracking (ownership semantics). Instance identity is derived purely from args (via the class's static key(args), a structural hash of args, or the default sentinel when there are none).

@param ― BlocClass - The StateContainer class constructor

@param ― opts - args derives the instance key; refId names the reference for debugging and is auto-generated when omitted.

acquire
(
class CounterCubit
CounterCubit
);
return
const c: CounterCubit
c
.
StateContainer<{ count: number; }, void, Record<string, never>>.state: {
count: number;
}
state
.
count: number
count
;
// BUG: ref count never drops to 0
}
// Fixed — borrow takes no ref
function
function readOnce(): number
readOnce
() {
return
borrow<typeof CounterCubit>(BlocClass: typeof CounterCubit, target?: BorrowTarget<typeof CounterCubit> | undefined): CounterCubit
borrow
(
class CounterCubit
CounterCubit
).
StateContainer<{ count: number; }, void, Record<string, never>>.state: {
count: number;
}
state
.
count: number
count
;
}

Circuit breaker threw (“max instances” / “max refs”)

Section titled “Circuit breaker threw (“max instances” / “max refs”)”

Symptom: acquire throws with a message like “exceeded the maximum live instances” or “exceeded the maximum refs.”

Cause: Almost always a leak — an unstable instance key (non-serializable args) spawning endless instances, or a missing release accumulating refs. The limit is not too low; the leak is real.

Fix: Find and fix the leak before raising the limit. Common causes: non-serializable values in args (fix: move to deps), acquire without release (fix: pair them), args that differ structurally each render (fix: normalise types). See Configuration: circuit breakers and Troubleshooting.