# 🌀 Daemonizer

A minimal async control flow framework for writing browser and Node.js daemons.

Daemonizer helps you manage long-running async event loops, safely respond to cancellation, and yield control to the macro task queue—without starving the event loop.

---

## 🚀 Installation

```bash
yarn add daemonizer
# or
npm install daemonizer
```

---

## ✨ Features

- ✅ **Abort-aware event loop**
- ✅ **Task group with lifecycle tracking**
- ✅ **Yielding mechanism to avoid blocking**
- ✅ **Bounded queue for backpressure**
- ✅ **Works in both Node.js and browser**

---

## 🧪 Try in Your Browser

Run the daemon in your browser with no setup:  
👉 [examples/example.html](./examples/example.html)

Open your browser console and watch `tick:` messages stream in real time!

```html
<!-- examples/example.html -->
<!DOCTYPE html>
<html>
  <body>
    <script type="module">
      import { Daemon } from "https://esm.sh/@on-the-ground/daemonizer@latest";

      ///////////// Daemon Example /////////////

      let controller = new AbortController();
      const daemon = new Daemon(controller.signal, async (_signal, event) => {
        await new Promise((r) => setTimeout(r, 1000));
        console.log("tick:", event);
      });

      for (let i = 1; i <= 5; i++) {
        await daemon.pushEvent(i);
      }

      setTimeout(() => controller.abort(), 3000);

      console.log("waiting the daemon down");
      await daemon.close();
      console.log("the daemon got down");

      const daemon = new Daemon(signal, handler);
      await daemon.push(msg);
      await daemon.close();
      // Results:
      // waiting the daemon down
      // tick: 1
      // tick: 2
      // the daemon got down
      // tick: 3 <- long running task, use strictInterval to abort it
    </script>
  </body>
</html>
```

---

## 🧠 API Overview

### `Daemon` – The Core Abstraction

```ts
import { Daemon } from "@on-the-ground/daemonizer";

const daemon = new Daemon(signal, async (msg) => {
  // Your background task handler
  console.log("received:", msg);
});

// Push a task into the daemon's queue
await daemon.push({ type: "log", content: "hello" });

// Gracefully shut down when you're done
await daemon.close();
```

#### ✅ Features

- Runs background tasks with structured concurrency
- Backpressure-safe via internal bounded queue
- Auto-shuts down when `AbortSignal` is aborted
- One-liner setup: no boilerplate, no ceremony

---

### 🧰 Low-level Tools (also exported)

### `launchEventLoop(signal, taskGroup, events, handler)`

Runs a long-lived, abortable event loop over an `AsyncIterable`.  
Automatically yields to the macro task queue to prevent starvation.

### `TaskGroup`

Tracks the lifecycle of multiple concurrent async tasks.

- `add(n = 1)`
- `done()`
- `wait(): Promise<void>`

### `MacroTaskYielder`

Yields only if enough time has passed since the last yield.

### `BoundedQueue<T>`

A fixed-capacity async queue. Backpressure-aware and safe for multiple consumers.

---

## 🌐 Compatibility

Daemonizer is fully compatible with:

- ✅ Node.js (v16+)
- ✅ Modern browsers (via bundlers like Vite, Webpack, etc.)

No external runtime dependencies.

---

## 📜 License

MIT © 2025 Joohyung Park  
[github.com/on-the-ground/daemonizer](https://github.com/on-the-ground/daemonizer)

---

> _"The name was subconsciously inspired by countless replays of Judas Priest’s 'Demonizer'."_
