
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@jacobtipp/bloc
Advanced tools
@jacobtipp/bloc is an npm package that helps implement the BLoC (Business Logic only Components) pattern.
rxjs is a peerDependency
npm install @jacobtipp/bloc rxjs
A cubit is a class that extends BlocBase and is a primitive type of Bloc that can be extended to manage any type of state.

This is similar to stores like Zustand. Your Cubit encapsulates state and you have methods of the Cubit that mutate the state by invoking this.emit.
import { Cubit } from "@jacobtipp/bloc"
export class CounterCubit extends Cubit<number> {
constructor() {
super(0);
}
increment(): void {
this.emit(this.state + 1);
}
decrement(): void {
this.emit(this.state - 1);
}
}
function main() {
const counterCubit = new CounterCubit();
console.log(counterCubit.state); // 0
counterCubit.increment();
console.log(counterCubit.state); // 1
// cubit is closed and disposed, it will no longer emit new state and all observers will be unsubscribed
cubit.close();
}
When a Cubit emits a new state, a Change occurs. We can observe all changes for a given Cubit by overriding onChange.
import { Cubit } from "@jacobtipp/bloc"
export class CounterCubit extends Cubit<number> {
constructor() {
super(0);
}
protected override onChange(change: Change<number>): void {
console.log(change); // { current: 0, nextState: 1 }
}
increment(): void {
this.emit(this.state + 1);
}
decrement(): void {
this.emit(this.state - 1);
}
}
A Bloc is a more advanced class which relies on events to trigger state changes rather than functions. Bloc also extends BlocBase which means it has a similar public API as Cubit. However, rather than calling a function on a Bloc and directly emitting a new state, Blocs receive events and convert the incoming events into outgoing states.

import { Bloc } from "@jacobtipp/bloc"
/// The events which `CounterBloc` will react to.
abstract class CounterEvent {
protected _!: void
}
class CounterIncrement extends CounterEvent {}
class CounterDecrement extends CounterEvent {}
export class CounterBloc extends Bloc<CounterEvent, number> {
constructor() {
super(0);
this.on(CounterIncrement, (event, emit) => emit(this.state + 1));
this.on(CounterDecrement, (event, emit) => emit(this.state - 1));
}
}
async function main() {
const delay = (n) => new Promise((resolve) => setTimeout(resolve, n));
const counterBloc = new CounterBloc();
console.log(counterBloc.state); // 0
counterBloc.add(new CounterIncrement());
// wait for next tick in event loop
await delay(0);
console.log(counterBloc.state); // 1
counterBloc.close(); // close the bloc when no longer needed
}
Similar to Cubits, a Bloc can be observed by overriding onChange, however, because Bloc is event-driven, we are also able to capture information about what triggered the state change.
We can do this by overriding onTransition.
The change from one state to another is called a Transition. A Transition consists of the current state, the event, and the next state.
import { Bloc } from "@jacobtipp/bloc"
export class CounterBloc extends Bloc<CounterEvent, number> {
constructor() {
super(0);
this.on(CounterIncrement, (event, emit) => emit(this.state + 1));
this.on(CounterDecrement, (event, emit) => emit(this.state - 1));
}
protected override onTransition(transition: Transition<CounterEvent, number>): void {
console.log(transition); // { currentState: 0, event: CounterIncrement, nextState: 1 }
}
}
Both Bloc and Cubit have an addError and onError method. We can indicate that an error has occurred by calling addError from anywhere inside our Bloc. We can then react to all errors by overriding onError just as with Cubit.
import { Bloc } from "@jacobtipp/bloc"
export function assertIsError(error: unknown): asserts error is Error {
if (!(error instanceof Error)) {
throw error;
}
}
export class CounterBloc extends Bloc<CounterEvent, number> {
constructor(private readonly counterRepo: CounterRepository) {
super(0);
this.on(CounterIncrement, this.onIncrement);
}
private onIncrement(event: CounterIncrement, emit: Emitter<number>) {
try {
await this.counterRepo.increment()
this.emit(this.state + 1)
} catch (e: unknown) {
assertIsError(e);
addError(e);
}
}
protected override onError(error: Error): void {
console.log(error: Error) // Error object with stack trace
}
}
BlocObserver is a class that you can extend to listen to all Blocs/Cubits that exist at runtime.
This allows you to trace multiple lifecycle events for all Blocs/Cubits.
// app-bloc-observer.tsx
export class AppBlocObserver extends BlocObserver {
override onCreate(bloc: BlocBase<any>): void {
console.log(`onCreate: ${bloc.name}`);
}
override onEvent(bloc: Bloc<any, any>, event: any): void {
console.log(`onEvent: ${bloc.name}`)
}
override onError(bloc: BlocBase<any>, error: any): void {
console.log(`onError: ${bloc.name}`, error);
}
override onChange(bloc: BlocBase<any>, change: Change<any>): void {
console.log(`onChange: ${bloc.name}`, change);
}
override onTransition(
bloc: Bloc<any, any>,
transition: Transition<any, any>
): void {
console.log(`onTransition: ${bloc.name}`, transition)
}
override onClose(bloc: BlocBase<any>): void {
console.log(`onClose: ${bloc.name}`);
}
}
// main.tsx
// composition root of your application
BlocObserver.observer = new AppBlocObserver();
bloc has an event sink that allows us to control and transform the incoming flow of events.
For example, if we were building a real-time search, we would probably want to debounce the requests to the backend in order to avoid getting rate-limited as well as to cut down on cost/load on the backend.
With bloc we can provide a custom EventTransformer to change the way incoming events are processed by the bloc.
Event Transformation is only available for Bloc and not Cubit.
import { Bloc, EventMapper, EventTransformer } from '@jacobtipp/bloc';
import { Observable, debounceTime, mergeMap } from 'rxjs';
// A debounce event transformer
export const debounce =
<Event>(duration: number): EventTransformer<Event> =>
(events$: Observable<Event>, mapper: EventMapper<Event>): Observable<Event> =>
events$.pipe(debounceTime(duration), mergeMap(mapper));
export class CounterBloc extends Bloc<CounterEvent, number> {
constructor() {
super(0);
// apply the debounce event transformer to the event handler
this.on(CounterIncrement, (event, emit) => emit(this.state + 1), debounce(200));
}
}
⚠️ Please refer to the bloc-concurrency package, which has commonly used Event Transformers.
Both Bloc and Cubit extend BlocBase which exposes a state$ property, which is an rxjs observable stream. This can be used to directly subscribe to the state stream.
function main() {
const counterCubit = new CounterCubit();
counterCubit.state$.subscribe((state) => {
console.log(state);
});
counterCubit.increment();
cubit.close();
}
FAQs
## Introduction
We found that @jacobtipp/bloc demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.