You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@blac/core

Package Overview
Dependencies
Maintainers
1
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@blac/core

A lightweight, flexible state management library for JavaScript/TypeScript applications focusing on predictable state transitions.

Source
npmnpm
Version
2.0.0-rc-4
Version published
Weekly downloads
8
-60%
Maintainers
1
Weekly downloads
 
Created
Source

@blac/core

A lightweight, flexible state management library for JavaScript/TypeScript applications focusing on predictable state transitions.

Features

  • 🔄 Predictable unidirectional data flow
  • 🧩 Modular architecture with Blocs and Cubits
  • 🧪 Unit test friendly
  • 🔒 Isolated state instances when needed
  • 🔌 Plugin system for extensibility

Installation

Install @blac/core using your favorite package manager:

# pnpm
pnpm add @blac/core

# yarn
yarn add @blac/core

# npm
npm install @blac/core

Core Concepts

Blocs and Cubits

Cubit: A simple state container with methods to emit new states.

class CounterCubit extends Cubit<number> {
  constructor() {
    super(0); // Initial state
  }

  increment = () => {
    this.emit(this.state + 1);
  }

  decrement = () => {
    this.emit(this.state - 1);
  }
}

Bloc: More powerful state container that uses an event-handler pattern for type-safe, event-driven state transitions. Events (instances of classes) are dispatched via this.add(), and handlers are registered using this.on(EventClass, handler).

// Define event classes
class IncrementEvent { constructor(public readonly amount: number = 1) {} }
class DecrementEvent { constructor(public readonly amount: number = 1) {} }

// Optional: Union type for all events
type CounterEvent = IncrementEvent | DecrementEvent;

class CounterBloc extends Bloc<number, CounterEvent> {
  constructor() {
    super(0); // Initial state

    // Register event handlers
    this.on(IncrementEvent, (event, emit) => {
      emit(this.state + event.amount);
    });

    this.on(DecrementEvent, (event, emit) => {
      emit(this.state - event.amount);
    });
  }

  // Helper methods to dispatch event instances (optional)
  increment = (amount = 1) => {
    this.add(new IncrementEvent(amount));
  }

  decrement = (amount = 1) => {
    this.add(new DecrementEvent(amount));
  }
}

Important: Arrow Functions Required

All methods in Bloc or Cubit classes must use arrow function syntax (method = () => {}) instead of the traditional method syntax (method() {}). This is because arrow functions automatically bind this to the class instance. Without this binding, methods called from React components would lose their context and could not access instance properties like this.state or this.emit().

State Management Patterns

Shared State (Default)

By default, bloc instances are shared across all consumers:

class GlobalCounterCubit extends Cubit<number> {
  constructor() {
    super(0);
  }
  
  increment = () => {
    this.emit(this.state + 1);
  }
}

Isolated State

When each consumer needs its own state instance:

class LocalCounterCubit extends Cubit<number> {
  static isolated = true; // Each consumer gets its own instance
  
  constructor() {
    super(0);
  }
  
  increment = () => {
    this.emit(this.state + 1);
  }
}

Persistent State

Keep state alive even when no consumers are using it:

class PersistentCounterCubit extends Cubit<number> {
  static keepAlive = true; // State persists even when no consumers
  
  constructor() {
    super(0);
  }
  
  increment = () => {
    this.emit(this.state + 1);
  }
}

Advanced Usage

Custom Plugins

Create plugins to add functionality like logging, persistence, or analytics:

import { BlacPlugin, BlacLifecycleEvent, BlocBase } from '@blac/core';

class LoggerPlugin implements BlacPlugin {
  name = 'LoggerPlugin';
  
  onEvent(event: BlacLifecycleEvent, bloc: BlocBase, params?: any) {
    if (event === BlacLifecycleEvent.STATE_CHANGED) {
      console.log(`[${bloc._name}] State changed:`, bloc.state);
    }
  }
}

// Add the plugin to Blac
import { Blac } from '@blac/core';
Blac.addPlugin(new LoggerPlugin());

Using Props with Blocs

Blocs can be designed to accept properties through their constructor, allowing for configurable instances. Here's an example of a UserProfileBloc that takes a userId prop:

import { Bloc } from '@blac/core'; // Or your specific import path

// Define props interface (optional, but good practice)
interface UserProfileProps {
  userId: string;
}

// Define state interface
interface UserProfileState {
  loading: boolean;
  userData: { id: string; name: string; bio?: string } | null;
  error: string | null;
}

// Define Event Classes for UserProfileBloc
class UserProfileFetchEvent {}
class UserProfileDataLoadedEvent { constructor(public readonly data: any) {} }
class UserProfileErrorEvent { constructor(public readonly error: string) {} }

type UserProfileEvents = UserProfileFetchEvent | UserProfileDataLoadedEvent | UserProfileErrorEvent;

class UserProfileBloc extends Bloc<UserProfileState, UserProfileEvents, UserProfileProps> {
  private userId: string;

  constructor(props: UserProfileProps) {
    super({ loading: true, userData: null, error: null }); // Initial state
    this.userId = props.userId;
    this._name = `UserProfileBloc_${this.userId}`;

    // Register event handlers
    this.on(UserProfileFetchEvent, this.handleFetchUserProfile);
    this.on(UserProfileDataLoadedEvent, (event, emit) => {
      emit({ ...this.state, loading: false, userData: event.data, error: null });
    });
    this.on(UserProfileErrorEvent, (event, emit) => {
      emit({ ...this.state, loading: false, error: event.error });
    });

    // Initial fetch
    this.add(new UserProfileFetchEvent());
  }

  private handleFetchUserProfile = async (_event: UserProfileFetchEvent, emit: (state: UserProfileState) => void) => {
    // Emit loading state directly if not already covered by initial state or another event
    // For this example, constructor sets loading: true, so an immediate emit here might be redundant
    // unless an event handler could set loading to false before this runs.
    // emit({ ...this.state, loading: true }); // Ensure loading is true
    try {
      await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate API call
      const mockUserData = { id: this.userId, name: `User ${this.userId}`, bio: 'Loves Blac states!' };
      this.add(new UserProfileDataLoadedEvent(mockUserData));
    } catch (e:any) {
      this.add(new UserProfileErrorEvent(e.message || 'Failed to fetch user profile'));
    }
  }
  
  // Public method to re-trigger fetch if needed
  refetchUserProfile = () => {
    this.add(new UserProfileFetchEvent());
  }
}

API Reference

Core Classes

  • BlocBase<S, P>: Base class for state containers
  • Cubit<S, P>: Simple state container with emit() and patch()
  • Bloc<S, E, P>: Event-driven state container with on(EventClass, handler) and add(eventInstance) methods.
  • Blac: Singleton manager for all Bloc instances

React Hooks

  • useBloc<B>(BlocClass, options?): Connect a component to a Bloc

Lifecycle Events

  • BLOC_CREATED: When a new Bloc is instantiated
  • BLOC_DISPOSED: When a Bloc is disposed
  • LISTENER_ADDED: When a state listener is added
  • LISTENER_REMOVED: When a state listener is removed
  • STATE_CHANGED: When state is updated
  • BLOC_CONSUMER_ADDED: When a new consumer starts using a Bloc
  • BLOC_CONSUMER_REMOVED: When a consumer stops using a Bloc

License

This project is licensed under the MIT License - see the LICENSE file for details.

Keywords

typescript

FAQs

Package last updated on 17 Jun 2025

Did you know?

Socket

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.

Install

Related posts