Skip to content

Types

The complete type toolkit exported from @blac/core. These utilities let you derive the state, args, deps, and instance shapes of a container class without hand-writing them, and the branded-ID helpers give you nominal InstanceId strings. Every signature on this page is quoted verbatim from source.

For a task-oriented walkthrough of typing blocs (generic parameters, inference, common pitfalls) see TypeScript. This page is the reference: one heading per export, the exact signature, what it does, and a small example.

All examples assume this setup, which is hidden from the rendered snippet but type-checked:

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

Cubit<S> is a StateContainer<S> with emit / patch exposed as public mutation surface. Today it adds nothing structurally beyond StateContainer — both are inherited from the underlying StructuralContainer<S>. Kept as a real class (not a type alias) because downstream code does instance instanceof Cubit checks.

The class body is intentionally empty: a no-op emit override would still go through applyState, and patch is inherited from StructuralContainer (path-diffed, microtask-flushed). A caller that wants "skip if no real change" patch semantics can wrap patch themselves or call emit after a manual equality check.

Cubit
} from '@blac/core';
interface
interface CounterState
CounterState
{
CounterState.count: number
count
: number;
CounterState.label: string
label
: string;
}
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> with emit / patch exposed as public mutation surface. Today it adds nothing structurally beyond StateContainer — both are inherited from the underlying StructuralContainer<S>. Kept as a real class (not a type alias) because downstream code does instance instanceof Cubit checks.

The class body is intentionally empty: a no-op emit override would still go through applyState, and patch is inherited from StructuralContainer (path-diffed, microtask-flushed). A caller that wants "skip if no real change" patch semantics can wrap patch themselves or call emit after a manual equality check.

Cubit
<
interface CounterState
CounterState
> {
constructor() {
super({
CounterState.count: number
count
: 0,
CounterState.label: string
label
: 'idle' });
}
}

A StateContainer (and therefore Cubit) carries three type parameters: S (state), Args (serializable construction/identity data), and Deps (injected non-serializable handles). These utilities pull each one back out of a container class (the constructor, e.g. typeof CounterCubit), so you never have to re-declare a state shape that already lives on the class.

Signature

export type ExtractState<T> =
T extends StateContainerConstructor<infer S> ? Readonly<S> : never;

Extracts the state type from a container constructor as a Readonly<S>. This is the type you get back from useBloc and from the state getter — read-only, because state is immutable from the outside. Resolves to never if T is not a container constructor.

type S =
type ExtractState<T> = T extends StateContainerConstructor<infer S extends object> ? Readonly<S> : never

Extract the state type from a StateContainer

@templateT - The StateContainer type

ExtractState
<typeof
class CounterCubit
CounterCubit
>;
type S = {
readonly count: number;
readonly label: string;
}

Signature

export type ExtractStateMutable<T> =
T extends StateContainerConstructor<infer S> ? S : never;

The same as ExtractState, but without the Readonly<> wrapper — the raw S as the class declared it. Reach for this only when you genuinely need a mutable view (for example, building the next state object before you emit it); prefer ExtractState everywhere a value is being read.

type Draft =
type ExtractStateMutable<T> = T extends StateContainerConstructor<infer S extends object> ? S : never
ExtractStateMutable
<typeof
class CounterCubit
CounterCubit
>;
type Draft = CounterState

Signature

export type ExtractArgs<T> = T extends new () => StateContainer<
any,
infer A,
any
>
? A
: void;

Extracts the Args type — the serializable data a bloc is constructed/identified with (see Passing Inputs). Falls back to void when the class declares no args. Note the match is against a zero-argument constructor shape (new () =>), which is how container subclasses are written.

interface
interface UserState
UserState
{
UserState.name: string
name
: string;
}
class
class UserCubit
UserCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> with emit / patch exposed as public mutation surface. Today it adds nothing structurally beyond StateContainer — both are inherited from the underlying StructuralContainer<S>. Kept as a real class (not a type alias) because downstream code does instance instanceof Cubit checks.

The class body is intentionally empty: a no-op emit override would still go through applyState, and patch is inherited from StructuralContainer (path-diffed, microtask-flushed). A caller that wants "skip if no real change" patch semantics can wrap patch themselves or call emit after a manual equality check.

Cubit
<
interface UserState
UserState
, {
userId: string
userId
: string }> {
constructor() {
super({
UserState.name: string
name
: '' });
}
}
type A =
type ExtractArgs<T> = T extends new () => StateContainer<any, infer A, any> ? A : void

Extract the args type (serializable construction/identity data) from a StateContainer subclass.

@templateT - The StateContainer constructor type

ExtractArgs
<typeof
class UserCubit
UserCubit
>;
type A = {
userId: string;
}

Signature

export type ExtractDeps<T> = T extends new () => StateContainer<
any,
any,
infer D
>
? D
: Record<string, never>;

Extracts the Deps type — the injected non-serializable handles (clients, services, other blocs) a container depends on. Falls back to Record<string, never> (the “no deps” shape) when none are declared.

interface
interface FeedState
FeedState
{
FeedState.posts: string[]
posts
: string[];
}
class
class FeedCubit
FeedCubit
extends
class Cubit<S extends object = any, Args = void, Deps extends object = Record<string, never>>

Cubit<S> is a StateContainer<S> with emit / patch exposed as public mutation surface. Today it adds nothing structurally beyond StateContainer — both are inherited from the underlying StructuralContainer<S>. Kept as a real class (not a type alias) because downstream code does instance instanceof Cubit checks.

The class body is intentionally empty: a no-op emit override would still go through applyState, and patch is inherited from StructuralContainer (path-diffed, microtask-flushed). A caller that wants "skip if no real change" patch semantics can wrap patch themselves or call emit after a manual equality check.

Cubit
<
interface FeedState
FeedState
, void, {
api: {
fetch(): void;
}
api
: {
function fetch(): void
fetch
(): void } }> {
constructor() {
super({
FeedState.posts: string[]
posts
: [] });
}
}
type D =
type ExtractDeps<T> = T extends new () => StateContainer<any, any, infer D> ? D : Record<string, never>

Extract the deps type (injected non-serializable handles) from a StateContainer subclass.

@templateT - The StateContainer constructor type

ExtractDeps
<typeof
class FeedCubit
FeedCubit
>;
type D = {
api: {
fetch(): void;
};
}

Signature

export type ExtractConstructorArgs<T> = T extends new (...args: infer P) => any
? P
: never[];

Extracts the runtime constructor parameter tuple from any class — this is plain TypeScript constructor inference, not BlaC’s Args. Use it when you need the literal constructor(...) parameters of a class. Resolves to never[] for non-constructors.

class
class Point
Point
{
constructor(
public
Point.x: number
x
: number,
public
Point.y: number
y
: number,
) {}
}
type P =
type ExtractConstructorArgs<T> = T extends new (...args: infer P) => any ? P : never[]

Extract constructor argument types from a class

@templateT - The class type

ExtractConstructorArgs
<typeof
class Point
Point
>;
type P = [x: number, y: number]

These describe the instance a container class produces, and the constructor shape (including the static registry methods like acquire and release) that @blac/core and @blac/react use to type their entry points.

Signature

export type BlocInstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;

Resolves a constructor type to its instance type, including abstract classes. It is the abstract-aware sibling of TypeScript’s built-in InstanceType<T> (which rejects abstract constructors). Because Cubit and StateContainer are abstract, this is the safe choice for “the instance of this class.”

type
type Instance = CounterCubit
Instance
=
type BlocInstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any

Extract instance type from an abstract class constructor

@templateT - The abstract class constructor type

BlocInstanceType
<typeof
class CounterCubit
CounterCubit
>;

Signature

export type BlocConstructor<
S extends object = any,
T extends new (...args: any[]) => StateContainer<S, any, any> = new (
...args: any[]
) => StateContainer<S, any, any>,
> = (new (...args: any[]) => InstanceType<T>) & {
keepAlive?: boolean;
};

A constructor type for a StateContainer subclass, with an optional keepAlive static flag. Any ordinary Cubit / Bloc subclass satisfies BlocConstructor directly. There is no static registry surface on the type itself — acquire, borrow, borrowSafe, ensure, and release are standalone functions imported from @blac/core, not static methods on the class. Their parameter type is the lighter StateContainerConstructor. See Instance Management for what those functions do.

// A helper that only needs "some container class" uses StateContainerConstructor.
// A plain Cubit/Bloc subclass satisfies this directly.
function
function describe(Bloc: StateContainerConstructor): StateContainer<any, any, any>
describe
(
type Bloc: StateContainerConstructor
Bloc
:
type StateContainerConstructor<S extends object = any> = new (...args: any[]) => StateContainer<S, any, any>

Constructor type for StateContainer classes

@templateS - State type managed by the container

StateContainerConstructor
) {
const
const inst: StateContainer<any, any, any>
inst
=
acquire<StateContainerConstructor>(BlocClass: StateContainerConstructor, opts?: {
args?: any;
refId?: string;
} | undefined): StateContainer<any, any, any>

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).

@paramBlocClass - The StateContainer class constructor

@paramopts.args - Construction/identity args; derives the instance key

@paramopts.refId - Named reference ID for debugging; auto-generated if omitted

acquire
(
type Bloc: StateContainerConstructor
Bloc
);
release<StateContainerConstructor>(BlocClass: StateContainerConstructor, opts?: {
args?: any;
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).

@paramopts.args - Construction/identity args; derives the instance key

@paramopts.refId - The specific ref to drop; drops one arbitrary ref if omitted

@paramopts.forceDispose - Force immediate disposal regardless of refs

release
(
type Bloc: StateContainerConstructor
Bloc
);
return
const inst: StateContainer<any, any, any>
inst
;
}
function describe(Bloc: StateContainerConstructor): StateContainer<any, any, any>
describe
(
class CounterCubit
CounterCubit
);
// BlocConstructor is an alias for a constructable StateContainer with an
// optional keepAlive flag — any Cubit/Bloc subclass satisfies it directly.
type CounterCtor =
type BlocConstructor<S extends object = any, T extends new (...args: any[]) => StateContainer<S, any, any> = new (...args: any[]) => StateContainer<S, any, any>> = (new (...args: any[]) => InstanceType<T>) & {
keepAlive?: boolean;
}

Constructor type for StateContainer classes. Used for type-safe hook parameters.

@templateTBloc - The StateContainer instance type

BlocConstructor
<
interface CounterState
CounterState
>;
type CounterCtor = (new (...args: any[]) => StateContainer<CounterState, any, any>) & {
keepAlive?: boolean;
}

Signature

export type InstanceReadonlyState<T extends StateContainerConstructor = any> =
Omit<InstanceType<T>, 'state'> & { state: ExtractState<T> };

The instance type of a container class with its state property narrowed to the Readonly state for that class. Useful when you want the full instance (methods, getters) but with a precisely-typed, read-only state.

type
type ReadonlyInstance = Omit<CounterCubit, "state"> & {
state: Readonly<CounterState>;
}
ReadonlyInstance
=
type InstanceReadonlyState<T extends StateContainerConstructor = any> = Omit<InstanceType<T>, "state"> & {
state: ExtractState<T>;
}
InstanceReadonlyState
<typeof
class CounterCubit
CounterCubit
>;
declare const
const c: ReadonlyInstance
c
:
type ReadonlyInstance = Omit<CounterCubit, "state"> & {
state: Readonly<CounterState>;
}
ReadonlyInstance
;
const c: ReadonlyInstance
c
.
state: Readonly<CounterState>
state
.count;
count: number

Signature

export type InstanceState<T extends StateContainerConstructor = any> = Omit<
InstanceType<T>,
'state'
> & { state: ExtractStateMutable<T> };

Like InstanceReadonlyState, but the state property is the mutable S rather than Readonly<S>. Reach for it only when you specifically need a writable view of state on a typed instance.

type
type MutableInstance = Omit<CounterCubit, "state"> & {
state: CounterState;
}
MutableInstance
=
type InstanceState<T extends StateContainerConstructor = any> = Omit<InstanceType<T>, "state"> & {
state: ExtractStateMutable<T>;
}
InstanceState
<typeof
class CounterCubit
CounterCubit
>;

Signature

export type StateContainerInstance<S extends object = any> = Omit<
StateContainer<S, any, any>,
'state'
> & { state: Readonly<S> };

A StateContainer instance keyed by its state type S rather than by a concrete class, with state narrowed to Readonly<S>. Use it when you want to describe “any container holding this state shape” without pinning a specific subclass.

interface
interface CounterState
CounterState
{
CounterState.count: number
count
: number;
CounterState.label: string
label
: string;
}
function
function readCount(c: StateContainerInstance<CounterState>): number
readCount
(
c: StateContainerInstance<CounterState>
c
:
type StateContainerInstance<S extends object = any> = Omit<StateContainer<S, any, any>, "state"> & {
state: Readonly<S>;
}
StateContainerInstance
<
interface CounterState
CounterState
>) {
return
c: StateContainerInstance<CounterState>
c
.
state: Readonly<CounterState>
state
.
count: number
count
;
}

Signature

export type StateContainerConstructor<S extends object = any> = new (
...args: any[]
) => StateContainer<S, any, any>;

The minimal constructor type for a container class, parameterized by state S. It is the constraint the extraction utilities (ExtractState, ExtractStateMutable, InstanceReadonlyState, InstanceState) match against. Unlike BlocConstructor it carries no static registry methods — use it where you only care that something is a container class.

const
const Ctor: StateContainerConstructor<CounterState>
Ctor
:
type StateContainerConstructor<S extends object = any> = new (...args: any[]) => StateContainer<S, any, any>

Constructor type for StateContainer classes

@templateS - State type managed by the container

StateContainerConstructor
<
interface CounterState
CounterState
> =
class CounterCubit
CounterCubit
;

BlaC tags instance-identity strings with a brand so a plain string cannot be passed where an InstanceId is expected. The brand is a compile-time-only phantom property keyed by a unique symbol; it has no runtime footprint — branded values are just strings at runtime.

Signature

declare const brand: unique symbol;
export type Brand<T, B> = T & { [brand]: B };

The general nominal-typing helper: intersects a base type T with a phantom property under a private unique symbol, tagged by the brand identifier B. Two Brands with different B are incompatible even when T is identical, which is what prevents accidental mixing of similar primitive types.

type
type UserId = string & {
[brand]: "UserId";
}
UserId
=
type Brand<T, B> = T & {
[brand]: B;
}

Utility type for creating branded/nominal types. Prevents accidental type confusion between similar primitive types.

@templateT - The base type

@templateB - The brand identifier

Brand
<string, 'UserId'>;
type
type OrderId = string & {
[brand]: "OrderId";
}
OrderId
=
type Brand<T, B> = T & {
[brand]: B;
}

Utility type for creating branded/nominal types. Prevents accidental type confusion between similar primitive types.

@templateT - The base type

@templateB - The brand identifier

Brand
<string, 'OrderId'>;
declare const
const u: UserId
u
:
type UserId = string & {
[brand]: "UserId";
}
UserId
;
declare function
function loadOrder(id: OrderId): void
loadOrder
(
id: OrderId
id
:
type OrderId = string & {
[brand]: "OrderId";
}
OrderId
): void;
function loadOrder(id: OrderId): void
loadOrder
(u);
Error ts(2345) ― Argument of type 'UserId' is not assignable to parameter of type 'OrderId'. Type 'UserId' is not assignable to type '{ [brand]: "OrderId"; }'. Types of property '[brand]' are incompatible. Type '"UserId"' is not assignable to type '"OrderId"'.

Signature

export type BrandedId<B> = Brand<string, B>;

A convenience alias for the common case of branding a string: BrandedId<B> is exactly Brand<string, B>. Use it whenever the base type is a string ID.

type
type SessionId = string & {
[brand]: "SessionId";
}
SessionId
=
type BrandedId<B> = string & {
[brand]: B;
}

Branded string type for type-safe IDs.

@templateB - The brand identifier

BrandedId
<'SessionId'>;

Signature

export type InstanceId = Brand<string, 'InstanceId'>;

The branded string type BlaC uses for state-container instance identities — BrandedId<'InstanceId'> by another name. Registry and identity APIs accept and return this rather than a bare string, so an arbitrary string cannot stand in for an instance key by accident.

declare function
function lookup(id: InstanceId): void
lookup
(
id: InstanceId
id
:
type InstanceId = string & {
[brand]: "InstanceId";
}

Branded string type for state container instance IDs

InstanceId
): void;
function lookup(id: InstanceId): void
lookup
('not-branded');
Error ts(2345) ― Argument of type 'string' is not assignable to parameter of type 'InstanceId'. Type 'string' is not assignable to type '{ [brand]: "InstanceId"; }'.

Signature

export function instanceId(id: string): InstanceId {
return id as InstanceId;
}

The value-level helper that brands a plain string as an InstanceId. It is a pure cast at runtime (returns the input unchanged); its only job is to give you a typed InstanceId to hand to APIs that require one, without writing the as InstanceId assertion yourself.

const
const id: InstanceId
id
:
type InstanceId = string & {
[brand]: "InstanceId";
}

Branded string type for state container instance IDs

InstanceId
=
function instanceId(id: string): InstanceId

Create a branded InstanceId from a string

@paramid - The string ID to brand

@returnsBranded InstanceId

instanceId
('user-42');
  • TypeScript — typing blocs end to end: generics, inference, and pitfalls
  • Cubit — the class these utilities extract types from
  • Instance Managementacquire / borrow / release and the registry surface that BlocConstructor describes
  • Passing Inputs — the args / deps identity model that ExtractArgs and ExtractDeps read
  • useBloc — where ExtractState shows up as the hook’s return type