🌀 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
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
Open your browser console and watch tick:
messages stream in real time!
<!-- 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
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
"The name was subconsciously inspired by countless replays of Judas Priest’s 'Demonizer'."