# anneal *soften the spine so the blade doesn't shatter* CSP/Actor model architecture for TypeScript applications. Generic building blocks for actor-model concurrency. Each type maps to a common async communication pattern, eliminating the per-actor boilerplate of defining message types, resolver callbacks, and caller wrappers. ## Naming Three libraries, three languages, three diseases, one cure. - **actor** (Go) - names Go's missing enforcement. Go has channels and goroutines but no structure for the actor pattern. You reinvent it every time. - **pickle** (Rust) - names Rust's encrustation. `Arc>`, lifetime annotations, `&self` vs `&mut self` - the borrow checker fights you when all you need is message-passing. - **anneal** (TypeScript) - names TypeScript's brittleness. async/await and Promises shatter under composition. Callbacks nest, errors vanish, backpressure doesn't exist. The sword analogy: the actor loop is the tempered cutting edge - hard, precise, one message at a time. The async integration layer around it is the annealed spine - flexible, absorbs shock without shattering. A good sword needs both. ## Architecture Guide These libraries implement the intra-domain actor patterns described in [Moxie Architecture Patterns](https://git.smesh.lol/moxie/blob/docs/MOXIE_ARCHITECTURE_PATTERNS.md) - specifically sections 3.1-3.6 (the actor loop, typed operation channels, synchronous-mutation rule, construction-is-spawning, post-mortem queries, context-aware calls). The five channel types (Func, Query, Proc, Signal, Inbox) are a direct realization of the typed operation channel taxonomy from section 3.2. The architecture document covers the full CSP/Actor/DDD model including inter-domain patterns (spawn boundaries, worker pools, pipelines, supervision, shutdown) which apply at the process level in Moxie but whose structural principles - state ownership, backpressure by buffer, bilateral protocol changes, measurement before tuning - apply identically when building actor systems in any language. ## Types | Type | Caller | Actor | Pattern | |------|--------|-------|---------| | `Func` | `call(req) -> Promise` | `msg.req`, `msg.reply(resp)` | function call | | `Query` | `call() -> Promise` | `msg.reply(resp)` | getter | | `Proc` | `call(req) -> Promise` | `msg.req`, `msg.done()` | setter / command | | `Signal` | `call() -> Promise` | `msg.done()` | trigger / reset | | `Inbox` | `send(v)` / `trySend(v)` | receive in select | fire-and-forget | | `Lifecycle` | `stop() -> Promise` | `.on()` in select | shutdown | All synchronous types resolve the caller's Promise only after the actor has processed the message. This eliminates races where a buffered write appears to succeed but the actor hasn't seen it yet. ## Example ```typescript import { Signal, Query, Lifecycle, spawn, loop, select, STOP } from 'anneal'; class Counter { private inc = new Signal(); private get = new Query(); private lc = new Lifecycle(); constructor() { spawn(this.lc, async () => { let n = 0; await loop( this.lc.on(() => STOP), this.inc.on(msg => { n++; msg.done(); }), this.get.on(msg => { msg.reply(n); }), ); }); } incr() { return this.inc.call(); } value() { return this.get.call(); } stop() { return this.lc.stop(); } } const c = new Counter(); await c.incr(); await c.incr(); await c.incr(); console.log(await c.value()); // 3 await c.stop(); ``` ## Design Principles - **No shared mutable state.** The actor function owns all mutable state. Callers interact through typed channel objects. - **Synchronous resolution.** The caller's Promise resolves only after the actor has processed the message. Use `Inbox` for intentionally async paths. - **Sequential processing.** Messages are handled one at a time. No concurrent access to actor state, ever. - **Composable.** Use the channel types you need. No base class to extend, no framework, no registration. ## When to use what - **Func** - caller needs a computed result based on arguments (map lookup, filtered query) - **Query** - caller needs current state, no arguments (get count, get config) - **Proc** - caller needs to mutate state and wait for confirmation (set value, record event) - **Signal** - caller needs to trigger an action and wait (reset, flush, increment) - **Inbox** - caller must not block (logging, metrics, notifications). Use `trySend` to drop on backpressure. ## Cancellation All synchronous types accept an optional `AbortSignal`: ```typescript const ctrl = new AbortController(); setTimeout(() => ctrl.abort(), 1000); const result = await func.call(req, ctrl.signal); ``` If the signal fires before the actor processes the message, the caller's Promise rejects. The actor may still process the message - same semantics as Go's `CallCtx` and Rust's `call_timeout`. ## Select priority Cases are checked in declaration order. Place the lifecycle case first for reliable shutdown under load: ```typescript await loop( this.lc.on(() => STOP), // checked first this.work.on(msg => { ... }), ); ``` ## Install ``` npm install anneal ``` ## License AGPL-3.0-or-later