npm package: @siggn/core
Description: A lightweight message bus system for Typescript
Author: Guilherme Guerreiro
Latest version: 0.2.0

[![npm version](https://badge.fury.io/js/%40siggn%2Fcore.svg)](https://www.npmjs.com/package/%40siggn%2Fcore)

# @siggn/core

A lightweight and type-safe event bus system for TypeScript projects.

## Features

- **Type-Safe**: Leverages TypeScript to ensure that message payloads are correct at compile and
  runtime.
- **Lightweight**: Zero dependencies and a minimal API surface.
- **Simple API**: Easy to learn and use, with a clear and concise API.

## Installation

You can install the package using your favorite package manager:

```bash
npm install @siggn/core
```

```bash
yarn add @siggn/core
```

```bash
pnpm add @siggn/core
```

## Usage

Here's a basic example of how to use Siggn:

```typescript
import { Siggn } from '@siggn/core';

// 1. Define your message types
type Message =
  | { type: 'user_created'; userId: string; name: string }
  | { type: 'user_deleted'; userId: string };

// 2. Create a new Siggn instance
const siggn = new Siggn<Message>();

// 3. Subscribe to events
// Use a unique ID for each subscriber to manage subscriptions
const subscriberId = 'analytics-service';

siggn.subscribe(subscriberId, 'user_created', (msg) => {
  console.log(`[Analytics] New user created: ${msg.name} (ID: ${msg.userId})`);
});

// 4. Publish events
siggn.publish({ type: 'user_created', userId: '123', name: 'John Doe' });
// Output: [Analytics] New user created: John Doe (ID: 123)

// 5. Unsubscribe from all events for a given ID
siggn.unsubscribe(subscriberId);

siggn.publish({ type: 'user_created', userId: '456', name: 'Jane Doe' });
// No output, because the subscriber was removed.
```

### Using the `make` helper

The `make` method simplifies managing subscriptions for a specific component or service.

```typescript
const userComponent = siggn.make('user-component');

userComponent.subscribe('user_deleted', (msg) => {
  console.log(`[UI] User ${msg.userId} was deleted. Updating view...`);
});

siggn.publish({ type: 'user_deleted', userId: '123' });
// Output: [UI] User 123 was deleted. Updating view...

// Unsubscribe from all subscriptions made by 'user-component'
userComponent.unsubscribe();
```

### Subscribing to multiple events

You can use `subscribeMany` to group subscriptions for a single subscriber.

```typescript
const auditService = siggn.make('audit-service');

auditService.subscribeMany((subscribe) => {
  subscribe('user_created', (msg) => {
    console.log(`[Audit] User created: ${msg.name}`);
  });
  subscribe('user_deleted', (msg) => {
    console.log(`[Audit] User deleted: ${msg.userId}`);
  });
});

siggn.publish({ type: 'user_created', userId: '789', name: 'Peter Pan' });
siggn.publish({ type: 'user_deleted', userId: '123' });

// Unsubscribe from all audit-service events
auditService.unsubscribe();
```

### Subscribing to all events

If you need to listen to every message that passes through the bus, regardless of its type, you can
use `subscribeAll`. This is useful for cross-cutting concerns like logging or debugging.

```typescript
const logger = siggn.make('logger-service');

logger.subscribeAll((msg) => {
  console.log(`[Logger] Received event of type: ${msg.type}`);
});

siggn.publish({ type: 'user_created', userId: '789', name: 'Peter Pan' });
// Output: [Logger] Received event of type: user_created

siggn.publish({ type: 'user_deleted', userId: '123' });
// Output: [Logger] Received event of type: user_deleted

// Unsubscribe from all logger-service events
logger.unsubscribe();
```

### Extending message types with `createClone`

The `createClone` method allows you to create a new, independent `Siggn` instance that inherits the
message types of its parent. This is useful for creating specialized message buses that extend a
base set of events without affecting the parent bus.

```typescript
// Continuing with the previous `Message` type...
const baseSiggn = new Siggn<Message>();

// 1. Define a new set of messages for a specialized module
type AdminMessage = { type: 'admin_login'; adminId: string };

// 2. Create a child bus that understands both `Message` and `AdminMessage`
const adminSiggn = baseSiggn.createClone<AdminMessage>();

// 3. Subscribe to events on the child bus
adminSiggn.subscribe('audit-log', 'user_created', (msg) => {
  console.log(`[Admin Audit] User created: ${msg.name}`);
});

adminSiggn.subscribe('auth-service', 'admin_login', (msg) => {
  console.log(`[Admin Auth] Admin logged in: ${msg.adminId}`);
});

// 4. Publish events on the child bus
adminSiggn.publish({ type: 'user_created', userId: 'abc', name: 'Alice' });
// Output: [Admin Audit] User created: Alice

adminSiggn.publish({ type: 'admin_login', adminId: 'xyz' });
// Output: [Admin Auth] Admin logged in: xyz

// Note: The parent and child buses are independent.
// Publishing on the parent does not affect the child's subscribers.
baseSiggn.publish({ type: 'user_created', userId: 'def', name: 'Bob' });
// No output, because the subscription is on `adminSiggn`.
```

### Automatic Garbage Collection

Siggn is designed to prevent memory leaks even if subscriptions are not explicitly unsubscribed. It achieves this through the use of `WeakRef` and `FinalizationRegistry`:

While explicit `unsubscribe` calls are still good practice for immediate cleanup and control, Siggn provides a robust safety net against common memory leak scenarios.

### Middleware

You can use middleware to intercept messages before they are delivered to subscribers. This is useful for logging, validation, or modifying messages.

```typescript
const siggn = new Siggn<Message>();

// Register a middleware
const unsubscribeMiddleware = siggn.use(async (msg, next) => {
  console.log('Middleware received:', msg);
  
  // You can perform async operations
  await someAsyncCheck(msg);
  
  // Call next() to proceed to the next middleware or deliver the message
  next();
});

// Unregister the middleware when no longer needed
unsubscribeMiddleware();
```

## API

### `new Siggn<T>()`

Creates a new message bus instance.

- `T`: A union type representing all possible messages.

### `publish(msg)`

Publishes a message to all relevant subscribers.

- `msg`: The message object to be published. It must conform to the `Msg` type.

### `subscribe(id, type, callback)`

Subscribes a callback to a specific message type.

- `id`: A unique `SiggnId` (string) to identify the subscriber.
- `type`: The `type` of the message to subscribe to.
- `callback`: A function that executes when a message of the specified `type` is published. It receives the message as its only argument.

### `subscribeAll(id, callback)`

Subscribes a callback to all message types.

- `id`: A unique `SiggnId` to identify the subscriber.
- `callback`: A function that executes for every message published. It receives the message as its only argument.

### `unsubscribe(id)`

Removes all subscriptions (specific and global) associated with a subscriber ID.

- `id`: The `SiggnId` of the subscriber to remove.

### `unsubscribeGlobal(id)`

Removes a global subscription for a given subscriber ID.

- `id`: The `SiggnId` of the global subscriber to remove.

### `make(id)`

Returns a helper object with `subscribe`, `subscribeMany`, `subscribeAll`, and `unsubscribe` methods pre-bound to the provided ID.

- `id`: A `SiggnId` to pre-bind to the subscription methods.

### `subscribeMany(id, setup)`

Subscribes to multiple message types for a single ID.

- `id`: A `SiggnId` to identify the subscriber.
- `setup`: A function that receives a `subscribe` helper to register multiple subscriptions under the same `id`.

### `createClone<C>()`

Creates a new, independent `Siggn` instance that inherits the parent's message types and adds new ones.

- `C`: A union type of additional message types for the new instance.

### `use(middleware)`

Registers a middleware function.

- `middleware`: A function `(msg, next) => void | Promise<void>`.
- Returns: A function to unregister the middleware.

### `subscriptionsCount()`

Returns the total number of subscriptions.

## License

This project is licensed under the [MIT License](LICENSE).