npm package: @silyze/background-runner
Description: A library that gives you the ability to run functions in the background
Author: Mihail Mojsoski
Latest version: 1.0.0

# Background Runner

**Background Runner** is a tiny, _type‑safe_ framework for executing tasks off the main call‑site – whether that means a local queue, a TCP worker, or a fully remote job system.

It gives you three primitives:

| Primitive          | Purpose                                                                                        |
| ------------------ | ---------------------------------------------------------------------------------------------- |
| `handler(...)`     | Declare a **job** with a name and an async function.                                           |
| `registry(...)`    | Collect many job handlers into a typed **registry**.                                           |
| `BackgroundRunner` | Wire a **transport** (how jobs are delivered) to a registry and expose `run(name, ...params)`. |

Everything is inferred so you can only call jobs that exist, with the right parameter shapes – no schema files or codegen needed.

---

## Features

- **End‑to‑end type‑safety** – Job names and parameter tuples are checked at compile time.
- **Pluggable transport** – Swap `ExecuteInstantly` for a queue, WebSocket, or message broker without touching caller code.
- **Zero dependencies** – Ships as pure TypeScript typings + \~1 kB runtime.
- **Structured clone** example shows deep‑copy to keep workers pure.

---

## Installation

```bash
npm install @silyze/background-runner
```

Requires TypeScript 5.2+ and Node 18+ (tested on 20 LTS).

---

## Quick start

```ts
import {
  handler,
  registry,
  BackgroundRunner,
  JobHandler,
  RegistryName,
  MaybeRegistryJobHandler,
  JobTransport,
} from "@silyze/background-runner";

/**
 * A naïve transport that just calls the handler immediately.
 * Replace this with a RabbitMQ or Redis implementation later.
 */
function ExecuteInstantly<
  R extends JobHandler<string, any>[],
  N extends RegistryName<R>
>(
  getJobHandler: <F extends N>(name: F) => MaybeRegistryJobHandler<F, R>
): JobTransport<R, N> {
  return {
    handleJob(name, params) {
      const jobHandler = getJobHandler(name);
      // optional: network hop, retry logic, etc.
      // @ts-ignore – deep copy keeps job code pure
      jobHandler?.handler(...structuredClone(params));
    },
  };
}

/** Job definitions */
const consoleRegistry = registry(
  handler("log", async (msg: string) => console.log(msg)),
  handler("error", async (msg: string) => console.error(msg)),
  handler("warn", async (msg: string) => console.warn(msg))
);

/** Runner bound to our transport */
const consoleRunner = new BackgroundRunner(consoleRegistry, ExecuteInstantly);

/** Fire‑and‑forget jobs */
consoleRunner.run("log", "Hello background world!");
consoleRunner.run("error", "Something went wrong…");
consoleRunner.run("warn", "Heads‑up!");
```

---

## API Reference

### Types & Helpers

| Symbol                    | Description                                                       |
| ------------------------- | ----------------------------------------------------------------- |
| `SerializableValue`       | JSON‑friendly primitives plus nested arrays/objects.              |
| `JobHandler<N,P>`         | `{ name: N; handler: (...params: P) => void\|Promise }`.          |
| `ParamsOf<JobHandler>`    | Extracts the parameter tuple _P_.                                 |
| `RegistryName<Registry>`  | Union of job names inside a registry.                             |
| `MaybeRegistryJobHandler` | Safely looks up a handler by name, returns `undefined` if absent. |
| `RegistryJobHandler`      | Same lookup but `never` when not found (internal).                |

### `handler(name, fn)`

Create a job handler.

```ts
const sendEmail = handler("send-email", async (to: string, body: string) => {
  /* … */
});
```

### `registry(...handlers)`

Bundle handlers so they can share a runner.

```ts
const mailRegistry = registry(
  sendEmail,
  handler("ping", () => {})
);
```

### `JobTransport`

Interface a transport must fulfil:

```ts
interface JobTransport<Registry, Name> {
  handleJob(name: Name, params: ParamsTuple): void;
}
```

Implementations typically **serialize & enqueue** the call, then a worker dequeues and executes.

### `BackgroundRunner`

```ts
new BackgroundRunner(registry, transportFactory);
```

- `registry` – Output of `registry(...)`.
- `transportFactory(getJobHandler)` – Function that returns a `JobTransport`. You receive a safe `getJobHandler` helper for worker‑side lookup.

The runner exposes:

```ts
run(name, ...params): void
```

Type checking guarantees `name` exists and `params` match the handler signature.

---

## Building custom transports

Because `BackgroundRunner` provides a registry‑aware `getJobHandler`, your transport doesn’t need reflection. A minimal queue example:

```ts
function ViaInMemoryQueue<
  R extends JobHandler<string, any>[],
  N extends RegistryName<R>
>(
  getJobHandler: <F extends N>(name: F) => MaybeRegistryJobHandler<F, R>
): JobTransport<R, N> {
  const queue: { name: N; params: SerializableValue[] }[] = [];

  return {
    handleJob(name, params) {
      queue.push({ name, params });
    },
  };

  // somewhere else (worker loop)
  setInterval(() => {
    const job = queue.shift();
    if (!job) return;
    getJobHandler(job.name)?.handler(...job.params);
  }, 100);
}
```