@rotorsoft/act 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/@types/act.d.ts +2 -1
  4. package/dist/@types/act.d.ts.map +1 -1
  5. package/dist/@types/builders/builder-utils.d.ts.map +1 -1
  6. package/dist/@types/internal/config.d.ts +8 -0
  7. package/dist/@types/internal/config.d.ts.map +1 -1
  8. package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
  9. package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
  10. package/dist/@types/types/action.d.ts +2 -2
  11. package/dist/@types/types/ports.d.ts +3 -3
  12. package/dist/@types/types/reaction.d.ts +2 -1
  13. package/dist/@types/types/reaction.d.ts.map +1 -1
  14. package/dist/adapter.cjs +3 -3
  15. package/dist/adapter.js +2 -2
  16. package/dist/{chunk-2377SSHX.cjs → chunk-7VYSJIXD.cjs} +1 -1
  17. package/dist/chunk-7VYSJIXD.cjs.map +1 -0
  18. package/dist/{chunk-VOVHDDET.cjs → chunk-LN3OP4OL.cjs} +27 -18
  19. package/dist/chunk-LN3OP4OL.cjs.map +1 -0
  20. package/dist/{chunk-HACJYR7Z.cjs → chunk-PZQXTGWF.cjs} +4 -4
  21. package/dist/{chunk-HACJYR7Z.cjs.map → chunk-PZQXTGWF.cjs.map} +1 -1
  22. package/dist/{chunk-KXC4ILVY.js → chunk-QPVUARKL.js} +12 -3
  23. package/dist/{chunk-KXC4ILVY.js.map → chunk-QPVUARKL.js.map} +1 -1
  24. package/dist/{chunk-SRZIVZIA.js → chunk-UXDNSZAD.js} +2 -2
  25. package/dist/{chunk-GWFLHWZV.js → chunk-W3PHEA5Q.js} +1 -1
  26. package/dist/chunk-W3PHEA5Q.js.map +1 -0
  27. package/dist/index.cjs +288 -262
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +44 -18
  30. package/dist/index.js.map +1 -1
  31. package/dist/test/index.cjs +5 -5
  32. package/dist/test/index.js +3 -3
  33. package/dist/types/index.cjs +2 -2
  34. package/dist/types/index.js +1 -1
  35. package/package.json +1 -1
  36. package/dist/chunk-2377SSHX.cjs.map +0 -1
  37. package/dist/chunk-GWFLHWZV.js.map +0 -1
  38. package/dist/chunk-VOVHDDET.cjs.map +0 -1
  39. /package/dist/{chunk-SRZIVZIA.js.map → chunk-UXDNSZAD.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/types/act.ts","../src/types/errors.ts","../src/types/ports.ts","../src/types/reaction.ts","../src/types/schemas.ts","../src/internal/zod.ts","../src/sensitive.ts","../src/types/index.ts"],"sourcesContent":["/**\n * Public contracts of the orchestrator: the options `act().build()` takes,\n * the lifecycle events an `Act` emits, and the public defaults behind them.\n * The `Act` class implements them in `../act.ts`; their Zod schemas and\n * resolvers live in `../internal/config.ts`.\n */\nimport type {\n CloseResult,\n Correlator,\n Schema,\n Schemas,\n Snapshot,\n} from \"./action.js\";\nimport type { Cache, Store, StoreNotification } from \"./ports.js\";\nimport type { BlockedLease, Drain, Lease } from \"./reaction.js\";\nimport type { SchemaRegister } from \"./registry.js\";\n\n/**\n * Default debounce window (ms) for `settle()` when neither the per-call\n * `SettleOptions.debounceMs` nor `ActOptions.settleDebounceMs` is set.\n * Coalesces commits in the same tick and small bursts.\n */\nexport const DEFAULT_SETTLE_DEBOUNCE_MS = 10;\n/**\n * Default LRU cap for the subscribed-streams cache. Apps that mint many\n * dynamic targets (one per aggregate) should override it with\n * `ActOptions.maxSubscribedStreams`.\n */\nexport const DEFAULT_MAX_SUBSCRIBED_STREAMS = 1000;\n\n/** Per-Act ports bag. Both required together — a shared cache across stores would collide on stream keys. */\nexport type Scoped = {\n readonly store: Store;\n readonly cache: Cache;\n};\n\n/** Circuit breaker state, reported with every `error` lifecycle event. */\nexport type CircuitState = \"closed\" | \"open\" | \"half-open\";\n\n/** Public, all-optional circuit-breaker options bag for `act().build()`. */\nexport type CircuitBreakerOptions = {\n failureThreshold?: number;\n cooldownMs?: number;\n};\n\n/** Resolved autoclose configuration after validation + default expansion. */\nexport type AutocloseConfig = {\n autocloseWindow?: { start: number; end: number; timeZone: string };\n};\n\n/**\n * Lifecycle events emitted by {@link Act}, mapped to their payload type.\n * Drives the typing of `emit` / `on` / `off` — the event-name argument\n * narrows its payload at the call site.\n *\n * The first parameter is kept (unused) for arity compatibility: `committed`\n * carries snapshots of whichever state each action targeted, so its honest\n * element type is `Snapshot<Schema, TEvents>` — the register map itself was\n * never the shape of any snapshot's state.\n */\nexport type ActLifecycleEvents<\n _TSchemaReg extends SchemaRegister<TActions>,\n TEvents extends Schemas,\n TActions extends Schemas,\n> = {\n committed: Snapshot<Schema, TEvents>[];\n acked: Lease[];\n blocked: BlockedLease[];\n settled: Drain<TEvents>;\n closed: CloseResult;\n /**\n * A **different process** committed an event to the same backing store.\n *\n * Fires only when the configured store implements\n * {@link Store.notify} and there is at least one registered reaction.\n * The orchestrator uses the same signal internally to wake `settle()`\n * (unless `autoSettle: false`); listeners get the raw payload for SSE\n * fan-out, dashboards, and audit logs.\n *\n * Local commits do *not* fire `notified` (use `committed` for those):\n * stores self-filter their own writes so this channel has a clean\n * cross-process semantic.\n */\n notified: StoreNotification;\n /**\n * A stream's sensitive-data payload was wiped via {@link Act.forget}.\n * Fires exactly once per successful `forget(stream)` call — idempotent\n * second calls (no PII left on the stream) return `eventCount: 0` and\n * do NOT re-emit. Apps that never call `forget()` never see this event.\n *\n * Listeners use it for the compliance side of GDPR / CCPA: audit log,\n * downstream cache busts, projection-side wipes that the framework\n * doesn't reach (e.g., search indexes, ETL caches). The framework's own\n * cache is invalidated by `forget()` itself before the event fires.\n */\n forgotten: { stream: string; at: Date; eventCount: number };\n /**\n * A store operation failed during the drain loop. Fires on\n * every failed drain cycle — typically a {@link StoreError} from a\n * degraded backend — carrying the orchestrator circuit breaker's state\n * after the failure (`open` means the drain loop has backed off and will\n * retry after the cooldown). Listen to alert on a degraded store; the\n * framework logs the same error regardless. Emitted only when a listener\n * is registered (Node's `EventEmitter` throws on an unhandled `\"error\"`).\n */\n error: { error: unknown; circuit: CircuitState };\n};\n\n/**\n * Options for {@link Act} construction (passed via {@link ActBuilder.build}).\n *\n * @property maxSubscribedStreams - Cap for the LRU tracking what each\n * dynamically resolved reaction target was last subscribed at. Statically\n * declared targets are held outside it and never evicted, so their lane\n * and priority stay owned by the build-time subscribe.\n * Default: {@link DEFAULT_MAX_SUBSCRIBED_STREAMS}.\n * @property settleDebounceMs - Debounce window (ms) used by `settle()` when\n * the caller doesn't pass `SettleOptions.debounceMs`. Tune this once per\n * Act instance instead of threading the value through every call site.\n * Default: {@link DEFAULT_SETTLE_DEBOUNCE_MS}.\n * @property autoSettle - Settle on its own after a change that frees\n * reaction work: a commit, `reset()`, `unblock()`, `restore()` or a\n * notification from another process. Set `false` when you walk the\n * pipeline yourself with `correlate()` / `drain()` (tests that check its\n * steps, tools); the event-sourcing guide's settle section lists the\n * timers that still run. Default: `true`.\n */\nexport type ActOptions<TLanes extends string = string> = {\n readonly maxSubscribedStreams?: number;\n readonly settleDebounceMs?: number;\n readonly autoSettle?: boolean;\n /**\n * Per-Act ports. When set, this Act runs against the\n * provided store + cache instead of the singletons — threaded via\n * AsyncLocalStorage so internals are unchanged. Both are required\n * together (a shared cache across distinct stores would collide on\n * stream keys). Omit for the singleton path.\n */\n readonly scoped?: Scoped;\n /**\n * Correlation-id generator for originating actions. When\n * omitted, Act uses {@link default_correlator}, which produces a\n * readable, time-monotonic-within-window, lowercase id of the form\n * `{state[:4]}-{action[:4]}-{ts}{rnd}` (18 chars).\n *\n * Reactions inherit `reactingTo.meta.correlation` so the chain stays\n * intact — the delegate is only consulted on originating commits and\n * for the close-the-books transaction.\n */\n readonly correlator?: Correlator;\n /** Restrict this process to a subset of declared lanes. */\n readonly onlyLanes?: ReadonlyArray<TLanes>;\n /**\n * Subscribe to {@link Store.notify} on this instance. Defaults\n * to `true`. Set `false` on instances that only commit and never\n * react — the subscriber-connection budget is the practical scaling\n * ceiling for the notify/listen pattern, and writer-only fleets\n * spend it for nothing when they subscribe to a channel they never\n * read. Commits still emit notifications (that's part of the\n * store's commit protocol); only the subscriber side is gated.\n */\n readonly listen?: boolean;\n /**\n * Run the local reaction pipeline on this instance. Defaults\n * to `true`. Set `false` on writer-only or sidecar instances: drain\n * controllers' auto-cycle workers don't start, `correlate()` /\n * `drain()` / `settle()` become no-ops, and the notify handler\n * skips its drain-wakeup arm (but still emits the `notified`\n * lifecycle event so observability sidecars work).\n */\n readonly drain?: boolean;\n /**\n * Orchestrator circuit breaker for the drain loop. After\n * `failureThreshold` consecutive store failures the breaker opens and\n * the drain loop skips `claim()` for `cooldownMs` instead of hammering a\n * down backend, then allows a half-open trial. Out-of-range values throw\n * a `ZodError` at `act().build()`. Defaults: threshold 5, cooldown 30s.\n */\n readonly circuitBreaker?: CircuitBreakerOptions;\n /**\n * Optional off-hours window restricting when autoclose evaluates. A\n * synthesized autoclose reaction that triggers outside the window\n * defers to the next instant the window opens — derived from the\n * window itself, no polling cadence. Hours are `[0, 23]` integers in\n * `timeZone` (IANA, default `\"UTC\"`, DST-correct); `start > end` is\n * an overnight window (e.g. `{ start: 22, end: 6 }`). Omit to\n * evaluate regardless of clock time.\n */\n readonly autocloseWindow?: {\n readonly start: number;\n readonly end: number;\n readonly timeZone?: string;\n };\n};\n","import type {\n Actor,\n DeferWhen,\n Message,\n Schema,\n Schemas,\n Snapshot,\n Target,\n} from \"./action.js\";\n\n/**\n * @packageDocumentation\n * @module act/types\n * @category Types\n * Application error type constants and error classes for the Act Framework.\n *\n * - `ERR_VALIDATION`: Schema validation error\n * - `ERR_PRECONDITION`: A precondition refused the action\n * - `ERR_CONCURRENCY`: Optimistic concurrency validation error on commits\n */\nexport const Errors = {\n ValidationError: \"ERR_VALIDATION\",\n PreconditionError: \"ERR_PRECONDITION\",\n ConcurrencyError: \"ERR_CONCURRENCY\",\n StreamClosedError: \"ERR_STREAM_CLOSED\",\n NonRetryableError: \"ERR_NON_RETRYABLE\",\n StoreError: \"ERR_STORE\",\n} as const;\n\n/**\n * Thrown when an action or event payload fails Zod schema validation.\n *\n * This error indicates that data doesn't match the expected schema defined\n * for an action or event. The `details` property contains the Zod validation\n * error with specific information about what failed.\n *\n * @example Catching validation errors\n * ```typescript\n * import { ValidationError } from \"@rotorsoft/act\";\n *\n * try {\n * await app.do(\"createUser\", target, {\n * email: \"invalid-email\", // Missing @ symbol\n * age: -5 // Negative age\n * });\n * } catch (error) {\n * if (error instanceof ValidationError) {\n * console.error(\"Validation failed for:\", error.target);\n * console.error(\"Invalid payload:\", error.payload);\n * console.error(\"Validation details:\", error.details);\n * // details contains Zod error with field-level info\n * }\n * }\n * ```\n *\n * @see {@link https://zod.dev | Zod documentation} for validation details\n */\nexport class ValidationError extends Error {\n /** The type of target being validated (e.g., \"action\", \"event\") */\n public readonly target: string;\n /** The invalid payload that failed validation */\n public readonly payload: any;\n /** Zod validation error details */\n public readonly details: any;\n\n constructor(target: string, payload: any, details: any) {\n super(`Invalid ${target} payload`);\n this.name = Errors.ValidationError;\n this.target = target;\n this.payload = payload;\n this.details = details;\n }\n}\n\n/**\n * Thrown when a precondition from `.requires([...])` refuses an action,\n * with the message `\"<action> failed precondition: <description>\"`.\n *\n * Preconditions are what the caller must meet for the action to run now.\n * They're checked after loading the current state and before the handler\n * emits anything, so nothing is stored. Invariants (what must hold after\n * every action) live in the state's schema instead; a broken one throws\n * {@link ValidationError}.\n *\n * @template TState - State schema type\n * @template TEvents - Event schemas type\n * @template TActions - Action schemas type\n * @template TKey - Action name\n * @template TActor - Actor type extending base Actor\n *\n * @example Catching a refused action\n * ```typescript\n * import { PreconditionError } from \"@rotorsoft/act\";\n *\n * try {\n * await app.do(\"withdraw\",\n * { stream: \"account-123\", actor: { id: \"user1\", name: \"Alice\" } },\n * { amount: 1000 }\n * );\n * } catch (error) {\n * if (error instanceof PreconditionError) {\n * console.error(\"Action:\", error.action);\n * console.error(\"Reason:\", error.description);\n * console.error(\"Current state:\", error.snapshot.state);\n * console.error(\"Attempted payload:\", error.payload);\n * }\n * }\n * ```\n *\n * @see {@link Precondition} for declaring them\n */\nexport class PreconditionError<\n TState extends Schema,\n TEvents extends Schemas,\n TActions extends Schemas,\n TKey extends keyof TActions,\n TActor extends Actor = Actor,\n> extends Error {\n /** The action that was attempted */\n readonly action: TKey;\n /** The action payload that was provided */\n readonly payload: Readonly<TActions[TKey]>;\n /** The target stream and actor context */\n readonly target: Target<TActor>;\n /** The current state snapshot when the precondition was checked */\n readonly snapshot: Snapshot<TState, TEvents>;\n /** Human-readable description of the precondition that refused the action */\n readonly description: string;\n\n constructor(\n action: TKey,\n payload: Readonly<TActions[TKey]>,\n target: Target<TActor>,\n snapshot: Snapshot<TState, TEvents>,\n description: string\n ) {\n super(`${action as string} failed precondition: ${description}`);\n this.name = Errors.PreconditionError;\n this.action = action;\n this.payload = payload;\n this.target = target;\n this.snapshot = snapshot;\n this.description = description;\n }\n}\n\n/**\n * Thrown when optimistic concurrency control detects a conflict.\n *\n * This error occurs when trying to commit events to a stream that has been\n * modified by another process since it was last loaded. The version number\n * doesn't match expectations, indicating a concurrent modification.\n *\n * This is a normal occurrence in distributed systems. Declare a retry\n * budget on the action and the orchestrator reloads the state and runs it\n * again; a conflict on a caller-pinned `expectedVersion` is never retried.\n *\n * @example Declaring the retry on the action\n * ```typescript\n * const Account = state({ Account: schema })\n * // ...\n * .on({ transfer: TransferSchema }, {\n * maxRetries: 3,\n * backoff: { strategy: \"exponential\", baseMs: 100 },\n * })\n * .emit(\"TransferSent\");\n * ```\n *\n * @see {@link Store.commit} for version checking details\n */\nexport class ConcurrencyError extends Error {\n /** The stream that had the concurrent modification */\n public readonly stream: string;\n /** The actual current version in the store */\n public readonly lastVersion: number;\n /** The events that were being committed */\n public readonly events: Message<Schemas, keyof Schemas>[];\n /** The version number that was expected */\n public readonly expectedVersion: number;\n\n constructor(\n stream: string,\n lastVersion: number,\n events: Message<Schemas, keyof Schemas>[],\n expectedVersion: number\n ) {\n // Message lists stream + event names only. Payloads remain accessible\n // via `error.events` for callers who need them — keeping them out of\n // the message avoids MB-scale strings on contended writes and keeps\n // potentially-sensitive data out of log streams.\n super(\n `Concurrency error committing \"${events\n .map((e) => `${stream}.${e.name}`)\n .join(\n \", \"\n )}\". Expected version ${expectedVersion} but found version ${lastVersion}.`\n );\n this.name = Errors.ConcurrencyError;\n this.stream = stream;\n this.lastVersion = lastVersion;\n this.events = events;\n this.expectedVersion = expectedVersion;\n }\n}\n\n/**\n * Thrown when attempting to write to a stream that has been closed\n * with a tombstone event.\n *\n * A tombstoned stream is permanently closed — no further actions can\n * be executed against it. The only way to reopen a tombstoned stream\n * is through `Act.close()` with a `restart` callback.\n *\n * @example\n * ```typescript\n * import { StreamClosedError } from \"@rotorsoft/act\";\n *\n * try {\n * await app.do(\"updateTicket\", target, payload);\n * } catch (error) {\n * if (error instanceof StreamClosedError) {\n * console.error(`Stream ${error.stream} is closed`);\n * }\n * }\n * ```\n *\n * @see {@link Act.close} for closing streams\n */\nexport class StreamClosedError extends Error {\n /** The stream that is closed */\n public readonly stream: string;\n\n constructor(stream: string) {\n super(`Stream \"${stream}\" is closed (tombstoned)`);\n this.name = Errors.StreamClosedError;\n this.stream = stream;\n }\n}\n\n/**\n * Thrown by a {@link Store} adapter when an infrastructure operation fails\n * for a reason that is *not* a domain condition — a dropped connection, a\n * transaction rollback, a query timeout. It is the typed boundary between\n * \"the store is unavailable/degraded\" and the domain errors above\n * ({@link ConcurrencyError}, {@link StreamClosedError}), which describe\n * legitimate outcomes the caller should branch on.\n *\n * Adapters wrap their driver errors in `StoreError` (preserving the\n * original via `cause`) so the orchestrator can distinguish a degraded\n * backend from \"no work\" and react accordingly — see the drain circuit\n * breaker, which trips on repeated `StoreError`s and surfaces an\n * `error` lifecycle event instead of silently spinning on a down\n * database.\n *\n * @example\n * ```typescript\n * app.on(\"error\", ({ error, circuit }) => {\n * if (error instanceof StoreError)\n * alert(`store ${error.operation} failing; circuit=${circuit}`);\n * });\n * ```\n */\nexport class StoreError extends Error {\n /** The store operation that failed (e.g. `\"claim\"`, `\"ack\"`, `\"commit\"`). */\n public readonly operation: string;\n\n constructor(operation: string, options?: { cause?: unknown }) {\n super(`Store operation \"${operation}\" failed`, options);\n this.name = Errors.StoreError;\n this.operation = operation;\n }\n}\n\n/**\n * Thrown by a reaction handler to signal that the failure is permanent\n * and the drain pipeline should block the stream immediately, without\n * consuming the rest of the `maxRetries` budget.\n *\n * The drain finalizer detects `instanceof NonRetryableError` and forces\n * `block = options.blockOnError` regardless of `lease.retry`. When\n * `blockOnError` is `false`, behavior is unchanged (drain keeps retrying\n * forever) — the class never overrides the operator's explicit \"never\n * block\" choice.\n *\n * Use this for failures the handler *knows* won't get better on retry:\n * a 4xx from a webhook, a `ZodError` on malformed input, a \"user\n * deleted\" 404 from a downstream API. Use regular `Error` (or a\n * subclass) for transient failures so the existing retry-with-backoff\n * loop applies.\n *\n * @example Wrapping a permanent downstream error\n * ```typescript\n * import { NonRetryableError } from \"@rotorsoft/act\";\n *\n * .on(\"OrderConfirmed\")\n * .do(async function notifyPartner(event) {\n * const res = await fetch(url, ...);\n * if (res.status >= 400 && res.status < 500) {\n * throw new NonRetryableError(\n * `webhook ${url} responded ${res.status}`,\n * { cause: await res.text() }\n * );\n * }\n * if (!res.ok) throw new Error(`webhook ${url} responded ${res.status}`);\n * })\n * ```\n */\nexport class NonRetryableError extends Error {\n /** The original failure, if any. Mirrors the standard `Error.cause` shape. */\n public override readonly cause?: unknown;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n this.name = Errors.NonRetryableError;\n this.cause = options?.cause;\n }\n}\n\n/**\n * The control-flow signal a reaction handler throws to *defer* itself.\n * Unlike an error, a defer is not a failure: the dispatcher\n * (`build_handle`) catches it and produces a\n * `HandleResult.defer` — the triggering events stay pending (watermark not\n * advanced), `retry` is not bumped, and the drain re-visits the stream at the\n * resolved due-time.\n *\n * `DeferSignal` is the **imperative escape hatch** and is re-exported from the\n * package root, so reaction code can throw it directly when a static\n * `.defer(when)` step isn't expressive enough (a deadline computed from loaded\n * state, say). It carries the *unresolved* {@link DeferWhen}; the dispatcher\n * resolves it against the triggering event it is already dispatching (via\n * `resolve_defer_at`), which is what anchors `{ after }` and the `at` function\n * form to that event and keeps the due-time derivable. Fully dynamic times go\n * through `{ at: someDate }`.\n *\n * Modeled as an `Error` subclass — like `NonRetryableError` — so it rides the\n * existing `try/catch` in the handler loop instead of needing a separate\n * return channel through every dispatcher signature. The compiled autoclose\n * reaction throws the same signal.\n */\n\nexport class DeferSignal extends Error {\n /**\n * The unresolved schedule. The dispatcher turns this into an absolute\n * due-time by resolving it against the triggering event.\n */\n readonly when: DeferWhen;\n\n constructor(when: DeferWhen) {\n super(\"reaction deferred\");\n this.name = \"DeferSignal\";\n this.when = when;\n }\n}\n","/**\n * @packageDocumentation\n * @module act/types\n * @category Types\n * Types and interfaces for event store ports and disposables in the Act Framework.\n */\nimport type {\n Committed,\n EventMeta,\n EventSource,\n Message,\n Query,\n Schema,\n Schemas,\n} from \"./action.js\";\nimport type { BlockedLease, Lease } from \"./reaction.js\";\n\n/**\n * A function that disposes of a resource asynchronously.\n * @returns Promise that resolves when disposal is complete.\n */\nexport type Disposer = () => Promise<void>;\n\n/**\n * An object that can be disposed of asynchronously.\n */\nexport type Disposable = { dispose: Disposer };\n\n// ---------------------------------------------------------------------------\n// Logger\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal logger port compatible with pino, winston, bunyan, and console.\n *\n * Each log method accepts either:\n * - `(msg: string)` — plain message\n * - `(obj: unknown, msg?: string)` — structured data with optional message\n *\n * Implementations should respect `level` to gate output.\n *\n * @see {@link ConsoleLogger} for the default implementation\n * @see {@link https://www.npmjs.com/package/@rotorsoft/act-pino | @rotorsoft/act-pino} for the Pino adapter\n */\nexport interface Logger extends Disposable {\n level: string;\n fatal(obj: unknown, msg?: string): void;\n fatal(msg: string): void;\n error(obj: unknown, msg?: string): void;\n error(msg: string): void;\n warn(obj: unknown, msg?: string): void;\n warn(msg: string): void;\n info(obj: unknown, msg?: string): void;\n info(msg: string): void;\n debug(obj: unknown, msg?: string): void;\n debug(msg: string): void;\n trace(obj: unknown, msg?: string): void;\n trace(msg: string): void;\n child(bindings: Record<string, unknown>): Logger;\n}\n\n// ---------------------------------------------------------------------------\n// Store\n// ---------------------------------------------------------------------------\n\n/**\n * Result of a {@link Store.subscribe} call.\n *\n * Named rather than written inline because three places state it — the port,\n * the drain's helper, and every adapter — and they drifted apart the moment\n * one of them grew a field.\n */\nexport type SubscribeResult = {\n subscribed: number;\n watermark: number;\n /**\n * Whether the caller holds the correlation lease and should scan.\n *\n * `undefined` when no `correlator` was supplied — no lease was requested,\n * so there is no answer and the caller scans as it always has.\n *\n * Acquiring MUST be atomic. A read-then-write lets two workers both see\n * an expired lease and both believe they hold it, which is precisely the\n * duplication the lease removes.\n *\n * Released by expiry only: a crash and a clean stop then take the same\n * path, so the recovery path is the common one. A holder can still hand\n * it back early by renewing with a minimal `millis`.\n */\n correlating?: boolean;\n /**\n * The correlate checkpoint: how far `correlate` has **read** the\n * event log, or `-1` when it has never advanced.\n *\n * Distinct from a subscription's `at` (how far a *target* has been\n * processed) — a run of events resolving to no target moves this and no\n * watermark. It rides `subscribe`'s existing return and is advanced by\n * the same call's `correlated_at` argument, so maintaining it costs no\n * store round trip of its own: `correlate` already calls `subscribe`\n * with the targets each scan discovered.\n *\n * Adapters keep it in their own relation, never as a subscription, so no\n * stream-scoped surface (`prioritize`, `reset`, `unblock`,\n * `query_streams`, `blocked_streams`) ever counts it.\n *\n * **Keyed per correlator** when a `correlator` is supplied, and\n * shared otherwise. Correlators that look for different things read the\n * log for different reasons, so one inheriting another's position at cold\n * start could skip events it needed. A key with no row yet falls back to\n * the shared value, so an upgrade does not re-read history.\n */\n correlated_at: number;\n};\n\n/**\n * Result of a {@link Store.truncate} operation, keyed by stream name.\n * Each entry contains the number of deleted events and the committed\n * seed event (snapshot or tombstone).\n *\n * Windowed entries (targets carrying a `before` boundary) echo the\n * boundary back as `before`, and `committed` is the **surviving\n * boundary `__snapshot__`** — an event the app wrote earlier, not a\n * new seed. Windowed no-ops (no qualifying snapshot) are absent from\n * the map entirely.\n */\nexport type TruncateResult = Map<\n string,\n {\n deleted: number;\n committed: Committed<Schemas, keyof Schemas>;\n before?: Date;\n }\n>;\n\n/**\n * Payload delivered by {@link Store.notify} when a **different process**\n * commits one or more events to the same backing store.\n *\n * Notifications are emitted **per stream a commit appends to**, not per event —\n * a commit of N events to one stream produces one notification carrying all N\n * `events`, and a commit across streams one for each, with that stream's events.\n * This minimizes wire wakeups and lets handlers reason about atomic batches.\n *\n * Stores that implement `notify` self-filter their own commits — handlers\n * receive notifications only for cross-process activity. This is the signal\n * that lets a horizontally-scaled Act deployment wake `settle()` immediately\n * on remote commits, instead of waiting for the next poll/debounce cycle.\n *\n * @property stream - Stream that was committed to\n * @property events - Events in this commit (id + name), in commit order\n */\nexport type StoreNotification = {\n readonly stream: string;\n readonly events: ReadonlyArray<{\n readonly id: number;\n readonly name: string;\n }>;\n};\n\n/**\n * Disposer returned by {@link Store.notify} subscriptions. Releases the\n * underlying listener (e.g., the dedicated PG `LISTEN` client). May be\n * synchronous or asynchronous — callers should `await` either way.\n */\nexport type NotifyDisposer = () => void | Promise<void>;\n\n/**\n * Subscription position for a registered stream.\n *\n * Streamed by {@link Store.query_streams} to power operational dashboards\n * (projection lag, blocked subscriptions, in-flight leases). The shape\n * mirrors what every adapter already tracks on its `streams` table.\n *\n * @property stream - The subscription target (projection or reaction stream)\n * @property source - Optional source (literal name or regex pattern) the reaction consumes from\n * @property at - Last processed event id watermark (-1 for fresh streams)\n * @property retry - Current retry counter\n * @property blocked - True when the stream is blocked by a poison message\n * @property error - Last error message (empty string when none)\n * @property leased_by - Current lease holder UUID (when leased)\n * @property leased_until - Lease expiration timestamp (when leased)\n * @property priority - Scheduling priority (default 0). Biases the\n * lagging-frontier `claim()` ordering — see {@link Store.prioritize}.\n * @property lane - Drain lane bound to the stream\n * @property deferred_at - Persisted next-visit time (ms since epoch) when\n * the stream is held out of {@link claim} by a `defer` outcome; omitted\n * when the stream carries no active defer schedule. Read at cold start to\n * re-seed the in-process defer timer so an idle deferred stream re-arms\n * its drain across a restart — the schedule outlives the process\n * memory it was derived from.\n */\nexport type StreamPosition = {\n readonly stream: string;\n readonly source?: string;\n readonly at: number;\n readonly retry: number;\n readonly blocked: boolean;\n readonly error: string;\n readonly priority: number;\n readonly leased_by?: string;\n readonly leased_until?: Date;\n readonly lane?: string;\n readonly deferred_at?: number;\n /**\n * The stream's **work mark** — see {@link SubscribeInput.correlated_at}.\n * `at < correlated_at` is the same predicate {@link Store.claim} applies,\n * so a reader can tell \"behind the head\" (`at` lags an event id) from\n * \"has unconsumed work\" without probing the event log. `undefined` means\n * the row carries no mark yet: unknown, not \"no work\".\n */\n readonly correlated_at?: number;\n};\n\n/**\n * Filter options for {@link Store.query_streams}.\n *\n * Mirrors the {@link Query} pattern used by {@link Store.query} — pass\n * filters server-side to keep the cost low on large tables (e.g., dynamic\n * reactions producing one subscription per aggregate).\n *\n * **What the store can filter:** the columns it actually persists —\n * `stream`, `source`, `blocked`. Higher-level classification (\"is this a\n * projection vs a reaction?\", \"is this a static or dynamic resolver?\")\n * is an orchestrator concern; the streams table doesn't store kinds.\n * Layer that on top by joining results with `Act`'s built-in registry.\n *\n * @property stream - Stream-name filter. Interpreted as a regex by\n * default — same anchor semantics across all stores (PG `~`, SQLite\n * anchor-aware `LIKE`, InMemory JS `RegExp`). **Anchors are caller-\n * controlled**: `^foo` for prefix, `foo$` for suffix, `^foo$` for\n * whole-string match. A plain string `foo` is a substring match\n * (matches any stream containing `foo`). Pass `stream_exact: true`\n * for a fast literal-equality comparison that skips regex compilation.\n * **Portable grammar:** the subset guaranteed to match identically on\n * every adapter is `^` / `$` anchors, `.` (any single character),\n * `.*` (any run of characters), and literal characters. Adapters\n * whose native engine compiles richer regex (PG POSIX, InMemory\n * `RegExp`) MAY accept more; an adapter that cannot express a pattern\n * exactly MUST throw `ValidationError` rather than silently\n * approximate — a silently-wrong match is the worst failure mode when\n * the filter drives `reset` / `unblock`. Pinned by the TCK's\n * stream-filter-grammar suite.\n * @property stream_exact - Treat `stream` as a literal string instead\n * of a regex.\n * @property source - Source-stream filter. Same anchor semantics and\n * portable grammar as `stream`. Useful to isolate dynamic-reaction\n * subscriptions tied to a particular aggregate stream. Pass\n * `source_exact: true` for literal equality.\n * @property source_exact - Use exact match instead of pattern match for\n * `source`.\n * @property source_matches - **Best-effort** reverse-match narrowing: prefer\n * streams whose stored `source` **pattern matches at least one** of the\n * given names (i.e. `name ~ source`, the inverse of `source` above which\n * is `source ~ pattern`). A subscription with an absent or empty `source`\n * (no source constraint — it consumes from every stream) ALWAYS qualifies,\n * regardless of the names. For a caller that wants only the subscriptions\n * that could consume from a set of streams; the framework itself doesn't\n * use it. **Callers must treat it as a hint, not an exact filter:** a\n * store that can't express reverse-regex (e.g. an anchor-aware `LIKE`\n * approximation) MAY return a superset — typically by ignoring the filter\n * entirely — so callers that need exactness re-verify in process. Stores\n * that honor it server-side advertise `source_matches` in the TCK's\n * `StoreCapabilities`, which gates the narrowing-behavior tests;\n * correctness never depends on the flag, only the row count fetched.\n * @property blocked - Restrict to blocked (`true`) or unblocked (`false`)\n * streams. Omit for all.\n * @property lane - Restrict to streams in this drain lane. Exact match.\n * @property after - Keyset pagination cursor: returns only streams with\n * `stream > after` (lexicographic). Pass the last seen `stream` to fetch\n * the next page.\n * @property limit - Max rows to return (default: 100). A bound, applied\n * before a row is emitted: `limit: 0` returns none. A non-negative integer: other values aren't validated, and adapters treat them differently.\n */\nexport type QueryStreams = {\n readonly stream?: string;\n readonly stream_exact?: boolean;\n readonly source?: string;\n readonly source_exact?: boolean;\n readonly source_matches?: ReadonlyArray<string>;\n readonly blocked?: boolean;\n readonly lane?: string;\n readonly after?: string;\n readonly limit?: number;\n};\n\n/**\n * Result of a {@link Store.query_streams} call.\n *\n * @property maxEventId - Highest event id in the store (-1 when empty).\n * An upper bound on how far any subscription could be behind — **not** a\n * lag measurement. A subscription's watermark advances only over events\n * that resolve to it, so a reader handling a subset of a state's events\n * sits permanently below this with nothing pending. For \"does this\n * subscription have work waiting?\", compare its `at` against its own\n * `correlated_at` (see {@link StreamPosition.correlated_at}); fall back to\n * this only for a row that carries no mark yet.\n * @property count - Number of stream positions delivered to the callback.\n */\nexport type QueryStreamsResult = {\n readonly maxEventId: number;\n readonly count: number;\n};\n\n/**\n * Filter shape for bulk operations on registered streams. Used by\n * {@link Store.prioritize}, {@link Store.reset}, and {@link Store.unblock}\n * — anything that operates on a set of streams selected by pattern or\n * exact match rather than enumerated by name.\n *\n * Same shape as {@link QueryStreams} but without pagination — bulk\n * UPDATEs don't paginate. Empty filter (`{}`) matches **every**\n * registered stream; treat as a footgun and use sparingly.\n *\n * @property stream - Stream-name filter (regex by default; `stream_exact`\n * for equality). Same portable grammar as {@link QueryStreams.stream}:\n * `^` / `$` anchors, `.`, `.*`, and literal characters are guaranteed\n * across adapters; anything richer either matches with full regex\n * semantics or throws `ValidationError` — never a silent\n * approximation, since these filters drive bulk `reset` / `unblock`.\n * @property stream_exact - Exact-match instead of regex.\n * @property source - Source-stream filter (regex by default;\n * `source_exact` for equality). Same portable grammar as `stream`.\n * @property source_exact - Exact-match instead of regex.\n * @property blocked - Restrict to blocked / unblocked streams. Omit\n * for both.\n */\nexport type StreamFilter = Pick<\n QueryStreams,\n \"stream\" | \"stream_exact\" | \"source\" | \"source_exact\" | \"blocked\" | \"lane\"\n>;\n\n/**\n * Alias of {@link StreamFilter}. Retained for backward compatibility\n * with code that imports the filter by its prioritize-specific name.\n * Prefer `StreamFilter` for new code.\n */\nexport type PrioritizeFilter = StreamFilter;\n\n/**\n * Framework-internal event names — written by the runtime, not by user\n * code. Snapshots are seeded by `truncate()`; tombstones by `close()`.\n */\nexport type FrameworkEventName = typeof SNAP_EVENT | typeof TOMBSTONE_EVENT;\n\n/**\n * Union of all event names valid for a given schema set: user-declared\n * event names plus the framework-internal markers. Used by\n * {@link QueryStatsOptions.exclude} so callers can mix domain events and\n * framework markers in the same filter list without `as string` casts\n * or stringly-typed mistakes (e.g. `\"__tombsotne__\"` typos fail at\n * compile time).\n *\n * @template E - Event schemas; defaults to {@link Schemas}.\n */\nexport type EventName<E extends Schemas = Schemas> =\n | (keyof E & string)\n | FrameworkEventName;\n\n/**\n * Per-stream aggregated stats returned by {@link Store.query_stats}.\n *\n * `head` and `tail` follow the **git-log convention**, not the Unix\n * `head`/`tail` convention:\n * - `head` — the **latest** event (highest id), always present.\n * - `tail` — the **earliest** event (lowest id), opt-in via\n * {@link QueryStatsOptions.tail}.\n *\n * `head`/`tail` never carry `pii`. `query_stats` is an operator-introspection\n * surface with no actor context and no disclosure gate, so — like any\n * un-gated read — it omits the pii sidecar on every adapter. Route\n * pii through `load` (gated) instead.\n *\n * @template E - Event schemas; defaults to {@link Schemas} when the caller\n * does not narrow.\n * @property head - Latest non-excluded event for the stream.\n * @property tail - Earliest non-excluded event for the stream, when\n * `options.tail` is true.\n * @property count - Total non-excluded event count for the stream, when\n * `options.count` is true.\n * @property names - Sparse map of event name → count of events with\n * that name, when `options.names` is true. Keys are typed as\n * {@link EventName | EventName<E>} so typos on lookup\n * (e.g. `stats.names?.[\"TicktOpened\"]`) fail at compile time when the\n * caller narrows `E`. Empty object never returned — a stream with no\n * matching events is absent from the result map entirely.\n */\nexport type StreamStats<E extends Schemas = Schemas> = {\n readonly head: Committed<E, keyof E>;\n readonly tail?: Committed<E, keyof E>;\n readonly count?: number;\n readonly names?: Readonly<Partial<Record<EventName<E>, number>>>;\n};\n\n/**\n * Options for {@link Store.query_stats}. All stat fields default to\n * `false` except `head`, which is always returned.\n *\n * **Cost model:** With no opt-in flags (or `tail` alone), each requested\n * stat resolves via an index-backed lookup — O(K) cost where K is the\n * number of matched streams. Setting `count` and/or `names` triggers a\n * full event scan over the matched streams (O(N) where N is total events);\n * both share the same scan and so requesting one is the same cost as\n * requesting both.\n *\n * @template E - Event schemas; defaults to {@link Schemas}. When the caller\n * narrows `E`, `exclude` is type-checked against the schema's event names\n * — typos like `[\"TOMBSTON_EVENT\"]` fail at compile time.\n * @property tail - Include the earliest non-excluded event per stream.\n * Cheap when alone (indexed); free when `count`/`names` also set\n * (already scanning).\n * @property count - Include the total non-excluded event count per stream.\n * Triggers full scan.\n * @property names - Include a `name → count` map per stream. Triggers\n * full scan (shares cost with `count`).\n * @property exclude - Event names to skip — e.g.\n * `[TOMBSTONE_EVENT, SNAP_EVENT]` to ignore framework markers. Applies\n * to all returned stats (head, tail, count, names) consistently.\n * @property before - Time-travel cutoff: only consider events with\n * `id < before`. Omitted = current state. Useful for \"what did this\n * stream look like at event N?\" historical queries without changing\n * the call shape. Cheap on both code paths (cheap-heads path narrows\n * the index scan; full-scan path adds a `WHERE id < ?` predicate).\n * @property after - Keyset pagination cursor: return only streams whose\n * name sorts strictly after this value (lexicographic). Pass the last\n * stream name from the previous page to fetch the next. Pairs with\n * `limit`; the result `Map` preserves stream-name order so the next\n * cursor is `[...result.keys()].at(-1)`.\n * @property limit - Maximum number of streams to return; omitted, every\n * matching stream (see the writing-a-store guide § Paginating\n * `query_stats`). A bound, applied before a stream is added to the\n * result: `limit: 0` returns none. A non-negative integer: other values aren't validated, and adapters treat them differently.\n */\nexport type QueryStatsOptions<E extends Schemas = Schemas> = {\n readonly tail?: boolean;\n readonly count?: boolean;\n readonly names?: boolean;\n readonly exclude?: ReadonlyArray<EventName<E>>;\n readonly before?: number;\n readonly after?: string;\n readonly limit?: number;\n};\n\n/**\n * One target stream to register with {@link Store.subscribe}.\n */\nexport type SubscribeInput = {\n readonly stream: string;\n readonly source?: string;\n /**\n * Optional scheduling priority for the lagging-frontier `claim()`\n * ordering. Default `0`. When the same stream is subscribed by multiple\n * reactions with different priorities, implementations must keep the\n * **maximum** so the highest-priority reaction wins. Use\n * {@link Store.prioritize} for runtime overrides that ignore this max —\n * operator-driven changes.\n */\n readonly priority?: number;\n /**\n * Drain lane. Default `\"default\"`.\n *\n * Written on the same rule as {@link priority}: the lane rides the max,\n * so a subscribe whose priority is **at or above** the stored priority\n * sets the lane, and one below it leaves the lane alone. The highest\n * priority registered for a stream therefore owns its lane, durably —\n * the same outcome the correlate scan applies within one pass, made a\n * property of the row so a caller that has forgotten what a stream\n * carries cannot re-lane it by resolving to it at a lower priority.\n *\n * Re-laning stays restart-driven: a restart re-subscribes at the same\n * declared priority, and equal priority writes the lane. A stream whose\n * stored priority was raised out of band by {@link Store.prioritize}\n * keeps its lane until a subscribe reaches that priority.\n */\n readonly lane?: string;\n /**\n * Highest event id observed to resolve to this target — the stream's\n * **work mark**. Applied as `correlated_at = GREATEST(correlated_at, N)`\n * for every value including zero and negatives; omitted leaves the stored\n * value untouched.\n *\n * A mark is an assertion about the log: `correlate` sets it to the id of a\n * real event that resolved to this target, so `at < correlated_at` implies a\n * fetch returns work. `correlate` is the only component entitled to write\n * it — the store cannot resolve user-code targets, and `notify` is\n * best-effort — which is what the name records.\n *\n * Eligibility follows from it: a stream is claimable iff `at < correlated_at`.\n * `NULL` means the row has never been correlated, so it is not claimable\n * until `correlate` marks it.\n */\n readonly correlated_at?: number;\n};\n\n/**\n * One append of a {@link Store.commit}: the events to append to one stream\n * and, optionally, the version the stream must be at.\n *\n * @template E - Event schemas\n */\nexport type Append<E extends Schemas> = {\n /** The stream the events go to; named at most once per commit. */\n readonly stream: string;\n /** The events to append, at least one. */\n readonly msgs: readonly Message<E, keyof E>[];\n /** The version the stream must be at; `-1` expects an empty stream, `undefined` skips the check. */\n readonly expectedVersion?: number;\n};\n\n/**\n * Interface for event store implementations.\n *\n * The Store interface defines the contract for persistence adapters in Act.\n * Implementations must provide event storage, querying, and distributed processing\n * capabilities through leasing and watermark tracking.\n *\n * Act includes two built-in implementations:\n * - **InMemoryStore**: For development and testing\n * - **PostgresStore**: For production use with PostgreSQL\n *\n * Custom stores can be implemented for other databases or event log systems.\n *\n * @example Using a custom store\n * ```typescript\n * import { store } from \"@rotorsoft/act\";\n * import { PostgresStore } from \"@rotorsoft/act-pg\";\n *\n * // Replace the default in-memory store\n * store(new PostgresStore({\n * host: \"localhost\",\n * port: 5432,\n * database: \"myapp\",\n * user: \"postgres\",\n * password: \"secret\"\n * }));\n *\n * const app = act()\n * .withState(Counter)\n * .build();\n * ```\n *\n * @see {@link InMemoryStore} for the default implementation\n * @see {@link PostgresStore} for the PostgreSQL implementation\n */\nexport interface Store extends Disposable, EventSource {\n /**\n * Initializes or resets the store.\n *\n * Used primarily for testing to ensure a clean state between tests.\n * For production stores, this might create necessary tables or indexes.\n *\n * @example\n * ```typescript\n * // Reset store between tests\n * beforeEach(async () => {\n * await store().seed();\n * });\n * ```\n */\n seed: () => Promise<void>;\n /**\n * Drops all data from the store.\n *\n * Dangerous operation that deletes all events and state. Use with extreme caution,\n * primarily for testing or development environments.\n *\n * @example\n * ```typescript\n * // Clean up after tests\n * afterAll(async () => {\n * await store().drop();\n * });\n * ```\n */\n drop: () => Promise<void>;\n\n /**\n * Appends events to one or more streams atomically: every append lands,\n * or none does.\n *\n * Each append names its stream, its events and, optionally, the version\n * the stream must be at. The store must:\n * - check every `expectedVersion` against its own stream before\n * appending anything (`-1` expects an empty stream, `undefined` skips\n * the check), and throw {@link ConcurrencyError} naming the first\n * stream that moved;\n * - refuse a stream named twice and an append with no events (a plain\n * `Error`), so a commit is never a version check alone;\n * - assign global ids in append order, then event order, and make the\n * events visible in that order;\n * - continue each stream's versions from its head, and attach `meta` to\n * every event.\n *\n * No appends at all commits nothing and returns `[]`.\n *\n * @template E - Event schemas\n * @param appends - One {@link Append} per stream\n * @param meta - Event metadata (correlation, causation), shared by every event\n * @returns The committed events of every append, in append order\n *\n * @throws {ConcurrencyError} If a stream's version doesn't match its `expectedVersion`\n *\n * @example\n * ```typescript\n * const events = await store().commit(\n * [{ stream: \"user-123\", msgs: [{ name: \"UserCreated\", data: { email: \"user@example.com\" } }], expectedVersion: -1 }],\n * { correlation: \"req-456\", causation: { action: {...} } }\n * );\n * ```\n */\n commit: <E extends Schemas>(\n appends: readonly Append<E>[],\n meta: EventMeta\n ) => Promise<Committed<E, keyof E>[]>;\n\n /**\n * Queries events from the store with optional filtering.\n *\n * Calls the callback for each matching event. The callback approach allows\n * processing large result sets without loading everything into memory.\n *\n * **The adapter returns what it stored.** A `Date` is stored as its ISO\n * string and comes back as that string; the framework turns the fields an\n * event's schema declares `z.date()` back into `Date`s, so an ISO-shaped\n * `z.string()` stays a string. See the Store contract in\n * `docs/docs/architecture/extension-points.md`.\n *\n * @template E - Event schemas\n * @param callback - Function invoked for each matching event\n * @param query - Optional filter criteria; see {@link Query} for the fields.\n * @returns Total number of events processed\n *\n * @example Query all events for a stream\n * ```typescript\n * let count = 0;\n * await store().query(\n * (event) => {\n * console.log(event.name, event.data);\n * count++;\n * },\n * { stream: \"user-123\" }\n * );\n * console.log(`Found ${count} events`);\n * ```\n */\n query: <E extends Schemas>(\n callback: (event: Committed<E, keyof E>) => void,\n query?: Query\n ) => Promise<number>;\n\n /**\n * Atomically discovers and leases streams for reaction processing.\n *\n * Atomically discovers a stream and acquires a lease in one round-trip,\n * eliminating the race that exists when discovery and locking are separate\n * calls (a competing worker can grab the stream between the two).\n *\n * PostgresStore uses `FOR UPDATE SKIP LOCKED` for zero-contention competing\n * consumer semantics — workers never block each other, each grabbing different\n * streams atomically. InMemoryStore fuses its poll+lease logic equivalently.\n *\n * Claim reads only the subscription rows; it never matches a `source`\n * against the event log. Correlate does that when it decides which targets\n * to mark, and the drain uses the same rule when it fetches the claimed\n * stream's events. A **literal** source (no anchor — the common case, and\n * every autoclose/dynamic-resolver source) matches by string equality: the\n * fast, index-friendly path, and exact so `\"s1\"` never matches `\"s12\"`. A\n * **pattern** source (starting with `^` or ending with `$`, e.g. a static\n * `^(A|B)$` reaction) is compiled as a regex and matched against candidate\n * stream names. Adapters that cannot faithfully run an arbitrary regex\n * (SQLite) reject a non-portable pattern at {@link subscribe} time rather\n * than silently never fetching its events.\n *\n * Every granted lease **counts against the stream's retry budget**: claim\n * increments the stream's retry counter and only {@link ack} resets it, so\n * a timed-out lease reclaimed by any worker marches the stream toward\n * `blockOnError` exactly like a handler failure.\n *\n * Used by `Act.drain()` as the primary stream acquisition method.\n *\n * @param lagging - Max streams from the lagging frontier (ascending watermark)\n * @param leading - Max streams from the leading frontier (descending watermark)\n * @param by - Unique lease holder identifier (UUID)\n * @param millis - How long the lease stands, in milliseconds. It bounds\n * the expiry only — the holder (`by`) is recorded whatever the value,\n * so a zero-length lease is granted, expires immediately, and its\n * {@link ack} still lands. A store that skipped recording the holder\n * would hand out a lease whose every ack is dropped, parking the\n * watermark and redelivering every event on every drain.\n * @param lane - Optional lane filter\n * @returns Array of successfully leased streams with metadata\n *\n * @example\n * ```typescript\n * const leased = await store().claim(5, 5, randomUUID(), 10000);\n * leased.forEach(({ stream, at, lagging }) => {\n * console.log(`Leased ${stream} at ${at} (lagging: ${lagging})`);\n * });\n * ```\n *\n * @see {@link subscribe} for registering new streams (used by correlate)\n * @see {@link ack} for acknowledging completion\n * @see {@link block} for blocking failed streams\n */\n claim: (\n lagging: number,\n leading: number,\n by: string,\n millis: number,\n lane?: string\n ) => Promise<Lease[]>;\n\n /**\n * Registers streams for event processing.\n *\n * Upserts stream entries so they become visible to {@link claim}. Used by\n * `correlate()` to register dynamically discovered reaction target streams.\n *\n * Also returns the current maximum watermark across all subscribed streams,\n * used internally for correlation checkpoint initialization on cold start.\n *\n * @param streams - Streams to register with optional source hint — a\n * literal stream name (matched by equality when the drain fetches the\n * stream's events) or a regex pattern (compiled and matched against\n * stream names); non-portable patterns are rejected here on SQLite.\n *\n * **At most one entry per stream.** A subscription row is keyed by\n * stream and its `source` is set once at creation, so a second entry\n * for the same stream could only ever contribute its priority — and\n * an adapter that merges the batch in one statement cannot do even\n * that (`UPDATE ... FROM` may touch a target row only once, so\n * Postgres picks an arbitrary source row). Callers de-duplicate\n * before calling; adapters may assume uniqueness.\n * @returns `subscribed` count of newly registered streams, `watermark` max `at` across all streams\n *\n * @example\n * ```typescript\n * const { subscribed, watermark } = await store().subscribe([\n * { stream: \"stats-user-1\", source: \"user-1\" },\n * { stream: \"stats-user-2\", source: \"user-2\", priority: 10 },\n * ]);\n * ```\n *\n * @see {@link claim} for discovering and leasing registered streams\n * @see {@link prioritize} for changing priority after subscription\n */\n subscribe: (\n streams: SubscribeInput[],\n /**\n * Advance the correlate checkpoint to this event id — correlate's own\n * watermark, in the same id space as a subscription's `at`.\n *\n * `correlate` is the only component that knows how far it has read, and\n * it already calls `subscribe` with the targets a scan discovered, so the\n * checkpoint is written by its own producer in its own call. Nothing else\n * may set it: a checkpoint derived from processing progress runs *ahead*\n * of the read cursor and silently skips discovery.\n *\n * **Persists only when greater than the stored value.** A lower or equal\n * value is ignored rather than written, so a worker whose cursor lags\n * cannot rewind it and re-sending the same value is a no-op. Omitted\n * leaves it untouched.\n */\n correlated_at?: number,\n /**\n * Identifies the calling correlator, and asks for the correlation lease.\n *\n * Correlation is duplicated work without it: every worker keeps its own\n * in-memory scan position, so N workers wake on the same commit and each\n * reads the whole range and writes the same marks — measured at exactly\n * N reads and N mark-writes per committed event. The marks are\n * idempotent, so this is waste rather than error, but the writes land on\n * the rows {@link claim} locks and churn the partial index the design\n * depends on.\n *\n * It rides `subscribe` rather than a verb of its own because correlate\n * already calls this every pass: the lease is a property of the\n * checkpoint write that already happens.\n *\n * - `key` scopes the lease to interchangeable correlators. Workers\n * running the same application are; a worker deployed with a subset of\n * the reactions is not, and must scan for itself or its targets are\n * never marked. Callers derive it from the registry; stores treat it\n * as opaque, and it also selects which checkpoint row this caller\n * reads and advances.\n * - `by` identifies this worker. Passing the same value renews rather\n * than fails, so acquiring and extending are one call.\n * - `millis` is how long the lease is held from now.\n *\n * Omitted entirely, no lease is taken and the caller reads and advances\n * the shared checkpoint.\n */\n correlator?: { key: string; by: string; millis: number }\n ) => Promise<SubscribeResult>;\n\n /**\n * Finalizes leased streams **atomically**: acknowledges the ones\n * that processed successfully and persists defer schedules for the ones\n * that chose to be re-visited later — one call, one transaction.\n *\n * Every entry advances the watermark to `at` (the last event handled this\n * cycle) and releases the lease. An entry without {@link Lease.due} is a\n * plain **ack**: it also resets `retry` to `-1` and clears any schedule. An\n * entry *with* `due` additionally **defers** the remainder: the stream's\n * `deferred_at` is set to `due` (ms since epoch) and `retry` to the entry's\n * own {@link Lease.retry}. Advance and defer are independent legs, so a\n * partial-progress backoff/defer advances past the events it handled — that\n * prefix never re-runs — while the failing tail waits for `deferred_at` (a\n * hold passes `at` = the current watermark, making the advance a no-op). A\n * caller deferring a *deliberate* re-visit passes `retry: -1` (the same\n * semantics as {@link defer} — a defer is not a failure); a caller pacing a\n * *retry* backoff passes the climbing counter so the retry budget keeps\n * accruing across windows until it blocks. All-or-nothing is the contract: a\n * failure must leave every watermark and every schedule untouched, so a drain\n * cycle's outcomes can never land partially (an acked close request must not\n * survive a lost defer, and vice versa).\n *\n * @param leases - Leases to finalize; `due`-carrying entries defer, the\n * rest ack. **At most one entry per stream.** Each lease is for one\n * stream, and an adapter that applies the batch in one statement cannot\n * apply two entries to the same row (`UPDATE … FROM` may touch a target\n * row once, so Postgres keeps an arbitrary one). Callers de-duplicate\n * before calling, as the drain does; adapters may assume uniqueness.\n * @returns The acknowledged leases (deferred entries are not returned)\n *\n * @example\n * ```typescript\n * const leased = await store().claim(5, 5, randomUUID(), 10000);\n * // Ack most streams at ID 150; hold order-42 until half past\n * await store().ack(leased.map(l =>\n * l.stream === \"order-42\"\n * ? { ...l, due: Date.now() + 30 * 60_000 }\n * : { ...l, at: 150 }\n * ));\n * ```\n *\n * @see {@link claim} for acquiring leases\n * @see {@link defer} for the standalone (operator-facing) schedule write\n */\n ack: (leases: Lease[]) => Promise<Lease[]>;\n\n /**\n * Blocks streams after persistent processing failures.\n *\n * Blocked streams won't be returned by {@link claim} until manually unblocked.\n * This prevents poison messages from repeatedly failing and consuming resources.\n *\n * Streams are typically blocked when:\n * - Max retries reached\n * - `blockOnError` option is true\n * - Handler throws an error\n *\n * @param leases - Leases to block with error messages. **At most one entry\n * per stream**, for the same reason as {@link ack}; adapters may assume\n * uniqueness.\n * @returns Blocked leases\n *\n * @example\n * ```typescript\n * try {\n * await processEvents(lease);\n * await store().ack([lease]);\n * } catch (error) {\n * if (lease.retry >= 3) {\n * await store().block([{\n * ...lease,\n * error: error.message\n * }]);\n * }\n * }\n * ```\n *\n * @see {@link claim} for lease management\n */\n block: (leases: BlockedLease[]) => Promise<BlockedLease[]>;\n\n /**\n * Operator verb: bulk-pause streams until a future time without advancing\n * their watermark. The drain itself never calls this — a reaction's defer\n * outcome is persisted atomically by {@link ack} via due-marked leases;\n * this standalone verb exists for operator-driven scheduling (\"hold every\n * `webhook-.*` stream until the maintenance window ends\"), completing the\n * recovery family: **reset / unblock / prioritize / defer**.\n *\n * Sets `deferred_at` on each matched stream. {@link claim} **skips** any\n * stream whose `deferred_at` is still in the future, so a paused stream\n * is not re-claimed (and `retry` is never bumped) until the due-time\n * passes, at which point the same pending events are re-delivered. Unlike\n * in-process backoff, this is durable, shared store state — every\n * competing worker honors the skip.\n *\n * {@link claim} treats a deferred stream as due once the **claiming\n * caller's** clock reaches `deferred_at`, not a clock of the store's own\n * (such as a database `NOW()`): the orchestrator's defer timer wakes on\n * the caller's clock, and the claim it triggers must agree with it.\n *\n * The schedule is cleared whenever the watermark moves or the stream is\n * recovered: {@link ack}, {@link block}, {@link reset}, and {@link unblock}\n * all reset `deferred_at`. Re-deferring simply overwrites it.\n *\n * Accepts an explicit list of stream names or a {@link StreamFilter}\n * (regex by default), the same shape as {@link reset}/{@link unblock}.\n *\n * @param input - Stream names or a {@link StreamFilter} selecting streams\n * @param deferred_at - Wall-clock time (ms since epoch) to revisit the streams\n * @returns Count of streams whose `deferred_at` was set\n *\n * @example\n * ```typescript\n * // Pause every webhook delivery stream during downstream maintenance\n * await store().defer({ stream: \"^webhook-\" }, Date.now() + 30 * 60_000);\n * ```\n */\n defer: (\n input: string[] | StreamFilter,\n deferred_at: number\n ) => Promise<number>;\n\n /**\n * Resets watermarks for the given streams to -1, making them eligible\n * for replay from the beginning. Also clears retry, blocked, error,\n * and lease state so the streams can be claimed immediately.\n *\n * **Prefer `Act.reset()` over calling this directly.** This primitive\n * only resets the store; it does not raise the orchestrator's internal\n * drain-armed flag, so a settled `Act` instance will short-circuit and\n * skip the replay. `Act.reset()` wraps this and arms the flag.\n *\n * Accepts either an explicit list of stream names or a\n * {@link StreamFilter} for bulk operations (e.g., \"rebuild every\n * blocked stream\"). The filter form is the same shape used by\n * {@link prioritize} and {@link query_streams}. An empty filter\n * (`{}`) matches every registered stream — typically a footgun for\n * `reset`; prefer narrower filters like `{ blocked: true }`.\n *\n * @param input - Stream names or a {@link StreamFilter}\n * @returns Count of streams that were actually reset\n *\n * @example\n * ```typescript\n * // By name\n * await app.reset([\"my-projection\"]);\n *\n * // By filter — rebuild every blocked stream in a projection family\n * await app.reset({ stream: \"^proj-orders-\", blocked: true });\n *\n * // Low-level (does NOT trigger replay on settled apps)\n * await store().reset([\"my-projection\"]);\n * ```\n *\n * @see {@link Act.reset} for the high-level rebuild API that wraps\n * this primitive and schedules the replay\n */\n reset: (input: string[] | StreamFilter) => Promise<number>;\n\n /**\n * Clears the blocked flag on streams without replaying their history.\n * Sets `blocked = false`, `retry = -1` (no attempt yet), `error = null`, and\n * clears any lease bookkeeping. The `at` watermark stays where it\n * was — the stream resumes from the next event after the last\n * successful ack, not from zero.\n *\n * The distinction from {@link reset} matters: `reset()` is for\n * projection rebuilds (replay from event 0); `unblock()` is for\n * recovering from a poison message after the operator fixes the\n * underlying issue. Use `unblock()` when you don't want to re-process\n * history.\n *\n * **Prefer `Act.unblock()` over calling this directly.** Like\n * `reset()`, this primitive doesn't raise the orchestrator's internal\n * drain-armed flag — a settled `Act` instance will short-circuit and\n * skip the resume. `Act.unblock()` wraps this and arms the flag.\n *\n * Only streams that were actually blocked at call time count toward\n * the return value; already-unblocked streams and unknown stream\n * names are silently skipped. The atomic single-statement update\n * makes the call safe to issue concurrently with `claim()` — workers\n * holding a `FOR UPDATE SKIP LOCKED` lock won't see partial state.\n *\n * Accepts either an explicit list of stream names or a\n * {@link StreamFilter} for bulk recovery (e.g., \"unblock every\n * blocked order projection\"). The `blocked = true` predicate is\n * always applied — passing `blocked: false` in the filter matches\n * nothing. An empty filter (`{}`) means \"unblock everything that's\n * blocked,\" which is a sane post-incident bulk recovery.\n *\n * @param input - Stream names or a {@link StreamFilter}\n * @returns Count of streams that were actually flipped (were blocked)\n *\n * @example\n * ```typescript\n * // By name (single targeted recovery)\n * await app.unblock([\"webhooks-out-customer-42\"]);\n *\n * // By filter — unblock every blocked stream in a family\n * await app.unblock({ stream: \"^webhooks-out-\" });\n *\n * // Post-incident: unblock everything that's blocked\n * await app.unblock({});\n *\n * // Low-level (does NOT trigger resume on settled apps)\n * await store().unblock([\"webhooks-out-customer-42\"]);\n * ```\n *\n * @see {@link Act.unblock} for the high-level recovery API\n * @see {@link reset} for the rebuild-from-zero alternative\n */\n unblock: (input: string[] | StreamFilter) => Promise<number>;\n\n /**\n * Bulk-update the scheduling priority of streams matching a filter.\n *\n * Used by {@link Act.prioritize} for operator runtime control over\n * lagging-frontier `claim()` ordering. Unlike {@link subscribe},\n * which keeps the per-stream priority at the `max()` of all\n * registered reactions targeting that stream, `prioritize` sets the\n * priority **directly** to `priority` for matching rows — letting\n * operators override the build-time scheduling policy.\n *\n * Filter semantics mirror {@link query_streams}: `stream`/`source`\n * are regex by default, exact with the `*_exact` flags. `blocked`\n * restricts to blocked or unblocked rows. Omitted fields don't\n * filter. An **empty filter** (`{}`) updates every registered\n * stream — useful for \"reset all priorities to N\" but a footgun\n * otherwise.\n *\n * @param filter - {@link PrioritizeFilter} selecting which streams\n * to update. Required (use `{}` to target all).\n * @param priority - New priority value. Set as-is — no `max()`,\n * no clamp.\n * @returns Count of streams whose priority was changed.\n *\n * @example Boost a specific replay\n * ```typescript\n * await store().prioritize(\n * { stream: \"^projection-orders$\", stream_exact: false },\n * 10\n * );\n * ```\n *\n * @see {@link Act.prioritize} for the orchestrator-level wrapper\n * @see {@link claim} for how priority biases stream scheduling\n */\n prioritize: (filter: StreamFilter, priority: number) => Promise<number>;\n\n /**\n * Atomically truncates streams and seeds each with a final event.\n *\n * For each **full** target (no `before`), in a single transaction:\n * 1. Deletes all events for the stream\n * 2. Inserts a `__snapshot__` (when `snapshot` is provided) or\n * `__tombstone__` event as the sole event on the stream\n *\n * **Subscriptions are not touched**, so the event log and the\n * subscriptions can live on different systems. A retired stream's\n * subscription is inert (commits to a tombstoned stream are refused, so its\n * work mark never rises and {@link claim} never returns it) and keeps each\n * consumer's final watermark. Deleting those rows is optional housekeeping.\n\n * A **windowed** target (`before` set) is a pure prefix delete behind\n * a real snapshot the app wrote — no seed, no tombstone, and the\n * streams table is left untouched. The store finds the closest safe\n * boundary — the latest `__snapshot__` with `created < before` and,\n * when `max_id` is given, `id <= max_id` — and deletes events with\n * `id <` that snapshot's id, keeping the snapshot + tail. No\n * qualifying snapshot ⇒ no-op (the stream is absent from the result).\n * Because `load()` resets state at each snapshot on replay, events\n * below the boundary contribute nothing to any load result — deleting\n * them cannot change what `load()` returns. `snapshot`/`meta` must be\n * omitted on windowed targets; `before` takes precedence when both\n * appear.\n *\n * **Windowed targets are applied before full ones**, and each full target\n * pairs its own delete with its own seed. Both only matter for inputs the\n * framework never produces — `close-cycle` dedups by stream before calling\n * — but `Store` is a public port, so the order is contract rather than\n * accident: a stream listed as both windowed and full ends with its\n * FULL entry in the result, and a stream listed twice as full leaves\n * exactly one seed carrying the last target's snapshot, reporting only that\n * pass's own delete count. Deleting once for a deduped set and seeding once\n * per entry would write two `version: 0` rows, which the SQL adapters'\n * unique `(stream, version)` constraint rejects outright.\n *\n * @param targets - Streams to truncate; full targets carry optional\n * snapshot state and meta, windowed targets carry `before` (and\n * optionally `max_id`, the min consumer watermark cap)\n * @returns Map keyed by stream name, each entry with `deleted` count\n * and `committed` event (the new seed, or the surviving boundary\n * snapshot on windowed entries, which also echo `before`)\n *\n * @see {@link Act.close} for the high-level close-the-books API that\n * orchestrates safety checks, archive callbacks, and atomic\n * truncate+seed\n */\n truncate: (\n targets: Array<{\n stream: string;\n snapshot?: Schema;\n meta?: EventMeta;\n before?: Date;\n max_id?: number;\n }>\n ) => Promise<TruncateResult>;\n\n /**\n * Atomically wipe the store and commit a fresh sequence of events\n * inside a single transaction.\n *\n * **Capability-gated.** Adapters may or may not implement restore.\n * Third-party stores that can't atomically wipe-and-rebuild in one\n * transaction can omit it.\n *\n * **Driver pattern.** The adapter is a thin transactional wrapper:\n * open the transaction (PG `BEGIN`, SQLite `BEGIN IMMEDIATE`, an\n * in-process snapshot for {@link InMemoryStore}), truncate the\n * events + streams/subscriptions tables, hand the orchestrator a\n * per-event insert callback by invoking `driver(callback)`, then\n * commit or roll back. Any throw inside `driver` rolls back the\n * transaction — the store ends byte-for-byte unchanged from the\n * pre-call state.\n *\n * The framework's scan loop (in `internal/event-sourcing.ts`) is\n * what calls `callback` repeatedly: it iterates the source,\n * validates each event, applies `drop_snapshots`, fires\n * `on_progress`, rewrites `meta.causation.event.id` through the\n * per-call `old → new` map, and counts kept/dropped. Adapters\n * never see that logic — their job is the transaction lifecycle\n * plus the adapter-specific `callback` body.\n *\n * **Lossless `created`.** The `callback` receives the event's\n * original timestamp; adapters write it through verbatim. This is\n * the property that makes restore a viable backup/migration\n * primitive — distinct from {@link commit}, which always stamps\n * `now()`.\n *\n * **Renumbered `id`.** Adapters reseed ids densely (`1..N` on\n * PG/SQLite, `0..N-1` on InMemory). The source's original ids are\n * used by the orchestrator as causation lookup keys but never\n * written through.\n *\n * **No subscription preservation.** Both the events and the\n * streams/subscriptions tables are wiped. Reactions re-subscribe\n * via the orchestrator's normal `correlate()` path on the next\n * settle cycle.\n *\n * **Correlate checkpoints reset.** Every correlate checkpoint (the shared\n * one and each keyed correlator's) is an event id, so restore sets them\n * back to -1 in the same transaction; left at their old values they would\n * sit above every renumbered event and correlate would never resolve the\n * restored events' reactions. Correlator leases are kept. The\n * `Act` that ran the restore restarts its own correlation; any other\n * process running against the same store must be restarted, as for any\n * offline restore.\n *\n * **Cache.** Restore does not touch the {@link Cache} port —\n * callers must `cache().clear()` after restore to avoid serving\n * stale snapshots. Documented; not enforced.\n *\n * @param driver - Orchestrator-supplied iteration callback. The\n * adapter calls `driver(callback)` exactly once, from inside its\n * transaction. The `callback` argument is the adapter's per-event\n * insert hook — it receives the event with `meta.causation`\n * already rewritten to the new id space and returns the new id\n * the adapter assigned. The driver is purely transactional from\n * the adapter's perspective — kept/dropped counts and timing live\n * in `Act.restore`.\n *\n * @see {@link Act.restore} for the public entry point.\n * @see {@link truncate} for the single-stream snapshot/tombstone\n * primitive (different operation — restore wipes the whole store)\n */\n restore?: (\n driver: (\n callback: (event: Committed<Schemas, keyof Schemas>) => Promise<number>\n ) => Promise<void>\n ) => Promise<void>;\n\n /**\n * Streams registered subscription positions to a callback, plus the\n * highest event id in the store.\n *\n * Read-only introspection for operational dashboards (Store /\n * Subscriptions tab, projection lag, blocked subscriptions). Avoids\n * forcing apps to open a second connection and run raw SQL against\n * adapter-specific schemas.\n *\n * Mirrors the {@link Store.query} callback pattern: the callback is\n * invoked once per matching position, allowing large result sets to be\n * processed without buffering. Results are ordered by `stream` name; use\n * `query.after` (last seen stream name) for keyset pagination on big\n * tables (dynamic reactions can produce one subscription per aggregate).\n *\n * @param callback - Invoked once per matching {@link StreamPosition}.\n * @param query - Optional {@link QueryStreams} filter (default `limit: 100`).\n * @returns `maxEventId` and the `count` of positions emitted.\n *\n * @example List blocked streams with their lag\n * ```typescript\n * const { maxEventId } = await store().query_streams(\n * (s) => console.log(`${s.stream}: lag=${maxEventId - s.at} ${s.error}`),\n * { blocked: true, limit: 50 }\n * );\n * ```\n */\n query_streams: (\n callback: (position: StreamPosition) => void,\n query?: QueryStreams\n ) => Promise<QueryStreamsResult>;\n\n /**\n * Per-stream aggregated stats — single round trip per adapter.\n *\n * Returns the latest event (`head`) plus opt-in extras (`tail`, `count`,\n * `names`) for each stream selected by `input`. Streams with no\n * qualifying events are absent from the result map.\n *\n * **Cost model.** With no opt-in flags, the call uses an index-backed\n * head lookup per stream — O(K) where K is the number of matched\n * streams. `tail` alone stays in the cheap tier. Setting `count` and/or\n * `names` triggers a full event scan over the matched streams (O(N)\n * where N is total events); both stats share that scan, so requesting\n * one or both is the same cost.\n *\n * **`input`.** Either an explicit `string[]` of stream names, or a\n * narrow event-stream selector `{ stream?, stream_exact? }` for\n * pattern-based or exact-name matching. **Subscription-level filters\n * (`source`, `blocked`) are intentionally not accepted here** — they\n * describe subscriptions, not events, and conflating the two would\n * silently exclude unsubscribed event streams. For\n * \"stats for all blocked subscriptions\" compose explicitly:\n * `query_streams({blocked: true})` → collect names → `query_stats(names)`.\n *\n * **`head` vs `tail` naming.** Follows the git-log convention: `head`\n * is the latest event (highest id), `tail` is the earliest (lowest id).\n * This is the **opposite** of the Unix `head`/`tail` commands.\n *\n * **Framework markers.** Snapshots (`__snapshot__`) and tombstones\n * (`__tombstone__`) are real events and are included by default —\n * intentional, so schema-evolution tooling can count them. To exclude\n * them, pass them in `options.exclude` (typed against {@link EventName})\n * so typos are compile-time errors.\n *\n * **Snapshot counts come from `names`.** When `names: true` and snapshots\n * are not in `exclude`, `result.names[\"__snapshot__\"]` is the snapshot\n * count for that stream — no separate field needed. Validates snapshot\n * policy at scale: `names[\"__snapshot__\"] / count` should match the\n * configured snap predicate's expected ratio.\n *\n * **Time travel.** `options.before` narrows to events with `id < before`,\n * answering \"what did this stream look like at event N?\" without\n * special call shape.\n *\n * **Ordering + pagination.** The returned `Map` is ordered by stream\n * name. `options.after` (exclusive cursor) + `options.limit` keyset-\n * paginate over that order — pass the last key of one page as the\n * `after` of the next. `limit` defaults to unbounded, so callers that\n * omit it keep receiving every matching stream in one call.\n *\n * @example Cheap heads — close-cycle pattern (one round trip, no scan)\n * ```typescript\n * const stats = await store().query_stats(streams, {\n * exclude: [TOMBSTONE_EVENT],\n * });\n * for (const [stream, { head }] of stats) {\n * // head.id, head.version, head.name\n * }\n * ```\n *\n * @template E - Event schemas. Narrow at the call site to type-check\n * `exclude` against your event names (typos fail at compile time).\n *\n * @param input - Stream names or a filter selecting the streams to stat.\n * @param options - Opt-in stat fields, event-name exclusions, and\n * time-travel cutoff. See {@link QueryStatsOptions}.\n * @returns Map keyed by stream name. Streams with no qualifying events\n * (after `exclude` and `before` are applied) are absent.\n *\n * @see {@link QueryStatsOptions} for the cost-aware option surface\n * @see {@link StreamStats} for the per-stream result shape\n * @see {@link EventName} for the typed exclude entries\n */\n query_stats: <E extends Schemas>(\n input: string[] | Pick<StreamFilter, \"stream\" | \"stream_exact\">,\n options?: QueryStatsOptions<E>\n ) => Promise<Map<string, StreamStats<E>>>;\n\n /**\n * Optional cross-process commit notifications.\n *\n * When implemented, the {@link Act} orchestrator subscribes once at build\n * time and routes notifications to wake `settle()` automatically — so a\n * remote worker's commit triggers reactions on this process without\n * waiting for the debounce/poll cycle. Subscribers also receive each\n * notification on the `\"notified\"` lifecycle event for fan-out\n * (SSE pushes, audit logs, dashboards).\n *\n * **Self-filtering contract:** implementations must skip their own\n * commits. The handler fires only for commits originating from a\n * **different process** writing to the same backing store. This keeps\n * the local fast path (`do()` already arms drain) free of duplicate\n * wake-ups and gives `\"notified\"` a clean cross-process semantic.\n * Enforced by the `notify` capability cases in `@rotorsoft/act-tck`,\n * along with one-notification-per-commit batch delivery.\n *\n * **Hint, not a contract:** the orchestrator never depends on `notify`\n * for correctness. If absent, dropped, or the store omits it, the\n * existing debounce/poll path still drains correctly — `notify` only\n * lowers cross-process p99 reaction latency.\n *\n * Adapter status (Act 0.x):\n * - {@link PostgresStore}: implemented via `LISTEN`/`NOTIFY` on the\n * `act_commit` channel\n * - {@link InMemoryStore}: not implemented (single-process — no remote\n * writers exist)\n * - `SqliteStore`: not implemented (single-node by design)\n *\n * @param handler Callback invoked once per remote commit\n * @returns Disposer releasing the underlying listener\n */\n notify?: (\n handler: (notification: StoreNotification) => void\n ) => NotifyDisposer | Promise<NotifyDisposer>;\n\n /**\n * Wipe the sensitive-data payload for every event on the stream — the\n * physical-erasure side of the sensitive-data epic. Sets the\n * adapter's PII column (or equivalent) to `NULL` for the stream's\n * events; `events.data` and the rest of the row are never touched.\n *\n * Returns the count of rows wiped. Idempotent: a second call on an\n * already-wiped stream returns `0` without error.\n *\n * Capability-gated via `pii_isolation` in `@rotorsoft/act-tck`'s\n * `StoreCapabilities`. Adapters that can't UPDATE rows (Kafka,\n * append-only object-storage logs) declare `pii_isolation: false`\n * and omit this method; `app.forget(stream)` throws on those\n * adapters at orchestrator level.\n *\n * Append-only invariant on `events.data` is preserved — only the\n * separately-isolated PII column is mutated. Disk reclamation is\n * adapter-dependent (PG autovacuum reclaims lazily; SQLite needs\n * `PRAGMA incremental_vacuum` or `VACUUM`). For strict-deletion\n * jurisdictions the production checklist documents the operator\n * step.\n *\n * Encryption-at-rest is the operator's DB-layer concern (pgcrypto,\n * RDS TDE, Cloud SQL TDE, SQLite SEE) — not an application-level\n * port. The framework's job is isolation + erasure.\n *\n * @param stream Target stream\n * @returns Count of events whose PII column was set to NULL\n */\n forget_pii?: (stream: string) => Promise<number>;\n}\n\n// ---------------------------------------------------------------------------\n// Cache\n// ---------------------------------------------------------------------------\n\n/**\n * A cached snapshot entry for a stream.\n *\n * Carries its own `stream` key so an entry is self-describing when it\n * travels away from the cache map — state projections flush entries\n * outward as-is, one per dirty stream.\n *\n * @template TState - The state schema type\n */\nexport interface CacheEntry<TState extends Schema> {\n readonly stream: string;\n readonly state: TState;\n readonly version: number;\n readonly event_id: number;\n readonly patches: number;\n readonly snaps: number;\n}\n\n/**\n * Cache port for storing stream snapshots in-process.\n *\n * Implementations should provide fast key-value access with bounded memory.\n * The async interface is forward-compatible with external caches (e.g., Redis).\n *\n * @template TState - The state schema type\n */\nexport interface Cache extends Disposable {\n get<TState extends Schema>(\n stream: string\n ): Promise<CacheEntry<TState> | undefined>;\n set<TState extends Schema>(\n stream: string,\n entry: CacheEntry<TState>\n ): Promise<void>;\n invalidate(stream: string): Promise<void>;\n clear(): Promise<void>;\n}\n\n/**\n * Event name used internally for snapshot events in the event store.\n * Snapshot events store a full state checkpoint, enabling efficient cold-start\n * recovery without replaying the entire event stream.\n */\nexport const SNAP_EVENT = \"__snapshot__\";\n\n/**\n * Event name used internally for tombstone events in the event store.\n * A tombstone marks a stream as permanently closed — no further writes\n * are permitted until the stream is explicitly restarted via `close()`.\n *\n * @see {@link Act.close} for the close-the-books API\n */\nexport const TOMBSTONE_EVENT = \"__tombstone__\";\n\n/**\n * How many positions `query_streams` returns when the caller gives no limit.\n */\nexport const DEFAULT_QUERY_STREAMS_LIMIT = 100;\n","/**\n * @packageDocumentation\n * @module act/types\n * @category Types\n * Types for reactions, leases, and fetch results in the Act Framework.\n */\nimport type {\n Actor,\n BackoffOptions,\n Committed,\n IAct,\n Query,\n Schema,\n Schemas,\n Snapshot,\n} from \"./action.js\";\n\n/**\n * Reaction handler function that processes committed events.\n *\n * Reaction handlers respond to events asynchronously. They can:\n * - Perform side effects (send emails, call APIs, log, etc.)\n * - Return an action tuple to trigger another action\n * - Return `void` or `undefined` for side-effect-only reactions\n *\n * Handlers are called during drain cycles and support automatic retries\n * with configurable error handling.\n *\n * @template TEvents - Event schemas\n * @template TKey - Event name\n * @template TActions - Action schemas (defaults to Schemas for stored reactions)\n * @template TActor - Actor type extending base Actor\n * @param event - The committed event that triggered this reaction\n * @param stream - The target stream name for this reaction\n * @returns Promise resolving to an action tuple or void\n *\n * @example Side effect only\n * ```typescript\n * const sendEmail: ReactionHandler<Events, \"UserCreated\"> = async (event) => {\n * await emailService.send(event.data.email, \"Welcome!\");\n * };\n * ```\n *\n * @see {@link Reaction} for complete reaction configuration\n */\nexport type ReactionHandler<\n TEvents extends Schemas,\n TKey extends keyof TEvents,\n TActions extends Schemas = Schemas,\n TActor extends Actor = Actor,\n> = (\n event: Committed<TEvents, TKey>,\n stream: string,\n app: IAct<TEvents, TActions, TActor>\n) => Promise<Snapshot<Schema, TEvents> | void>;\n\n/**\n * Resolver for determining which stream a reaction should target.\n *\n * Resolvers enable dynamic reaction routing based on event content. They can be:\n * - **Static**: Always route to the same target stream\n * - **Dynamic**: Determine target based on event data at runtime\n *\n * Resolvers can also specify source streams for optimization, allowing the drain\n * process to efficiently fetch only relevant events. An optional `priority`\n * biases the lagging-frontier `claim()` ordering — see {@link Resolved.priority}.\n *\n * @template TEvents - Event schemas\n * @template TKey - Event name\n * @param event - The committed event (for dynamic resolvers)\n * @returns Target stream configuration or undefined to skip\n *\n * @example Static target\n * ```typescript\n * .on(\"UserCreated\")\n * .do(sendWelcomeEmail)\n * .to(\"email-queue\") // Static target\n * ```\n *\n * @see {@link Reaction} for complete reaction configuration\n * @see {@link Resolved} for the resolved-target shape\n */\nexport type ReactionResolver<\n TEvents extends Schemas,\n TKey extends keyof TEvents,\n TLane extends string = string,\n> =\n | Resolved<TLane> // static\n | ((event: Committed<TEvents, TKey>) => Resolved<TLane> | undefined); // dynamic\n\n/**\n * Resolver output shape — what `.to(...)` returns for a static or dynamic\n * resolver.\n *\n * @property target - Stream name that processes this reaction\n * @property source - Optional source the target consumes from. A\n * **literal** name (no `^` at the start or `$` at the end) is matched by string\n * equality in the store's claim/fetch path — the fast, index-friendly\n * path, and exact so `\"s1\"` never matches `\"s12\"`. A name carrying an\n * anchor (e.g. `^(A|B)$`) is compiled as a `RegExp` and matched\n * against candidate streams with the caller's own anchoring\n * @property priority - Optional scheduling hint. The lagging-frontier\n * `claim()` orders streams by `priority DESC, at ASC`, so a higher value\n * makes the stream win lease slots ahead of equal-watermark peers under\n * saturation. Default `0` — behavior identical to current dual-frontier.\n * Only meaningful when `streamLimit` is binding (more candidate streams\n * than the worker can claim per cycle); idle systems are unaffected.\n * See `libs/act-pg/PERFORMANCE.md` for the benchmark that motivated this\n * knob.\n * @property lane - Optional drain lane. Defaults to `\"default\"`.\n */\nexport type Resolved<TLane extends string = string> = {\n readonly target: string;\n readonly source?: string;\n readonly priority?: number;\n readonly lane?: TLane;\n};\n\n/**\n * Build-time configuration for a drain lane.\n *\n * @property name - Lane name (`\"default\"` is reserved for the implicit lane)\n * @property leaseMillis - Lease window for `claim()` calls in this lane\n * @property streamLimit - Max streams claimed per cycle in this lane\n * @property cycleMs - Cycle frequency for this lane's controller\n */\nexport type LaneConfig<TName extends string = string> = {\n readonly name: TName;\n readonly leaseMillis?: number;\n readonly streamLimit?: number;\n readonly cycleMs?: number;\n};\n\n/**\n * Options for reaction processing.\n *\n * For the shared retry-pacing shape see {@link BackoffOptions} and\n * {@link BackoffStrategy} in `./action.js`.\n *\n * The backoff schedule is persisted on the stream, so every competing\n * worker honors it and `retry` advances once per real attempt.\n *\n * @property blockOnError - Whether to block on error.\n * @property maxRetries - Retries after the first attempt: a failing stream\n * is attempted `maxRetries + 1` times before it blocks.\n * @property backoff - Optional retry pacing. When omitted, a retry waits a\n * fixed 10 seconds. Either way, no worker re-attempts the stream before\n * the delay; the lease never sets retry timing.\n */\nexport type ReactionOptions = {\n readonly blockOnError: boolean;\n readonly maxRetries: number;\n readonly backoff?: BackoffOptions;\n};\n\n/**\n * Distributive mapped type that produces a proper discriminated union of\n * committed events. Unlike `Committed<TEvents, keyof TEvents>` (where\n * `name` and `data` are independent unions), each variant correlates\n * `name` with its corresponding `data` — enabling `switch (event.name)`\n * to narrow both fields correctly.\n *\n * @template TEvents - Event schemas\n *\n * @example Exhaustive switch\n * ```typescript\n * for (const event of events) {\n * switch (event.name) {\n * case \"TicketOpened\":\n * event.data; // typed as TicketOpened's schema\n * break;\n * case \"TicketClosed\":\n * event.data; // typed as TicketClosed's schema\n * break;\n * default:\n * const _: never = event; // compile error if a case is missing\n * }\n * }\n * ```\n */\nexport type BatchEvent<TEvents extends Schemas> = {\n [K in keyof TEvents]: Committed<TEvents, K>;\n}[keyof TEvents];\n\n/**\n * Batch handler for projections that processes multiple events in a single call.\n *\n * Receives the full ordered array of all event types declared on the projection,\n * enabling bulk DB operations (batch INSERT/UPDATE) in a single transaction.\n * The handler is always called when defined — even for a single event.\n *\n * @template TEvents - Event schemas (all events declared on the projection)\n * @param events - Ordered array of committed events (discriminated union)\n * @param stream - The target stream name\n *\n * @see {@link BatchEvent} for the discriminated union type\n */\nexport type BatchHandler<TEvents extends Schemas> = (\n events: ReadonlyArray<BatchEvent<TEvents>>,\n stream: string\n) => Promise<void>;\n\n/**\n * Options for `projection(name).of(state, options)` — both deterministic:\n * behavior is a pure function of the event sequence and these two bounds.\n *\n * @property flushEvery - Events folded between flush rounds (default 1000)\n * @property maxCachedStates - LRU bound on in-memory folded states; the\n * evictee is flushed before it is dropped (default 10000)\n */\nexport type FoldOptions = Readonly<{\n flushEvery?: number;\n maxCachedStates?: number;\n}>;\n\n/**\n * Defines a reaction to an event.\n * @template TEvents - Event schemas.\n * @template TKey - Event name.\n * @template TActions - Action schemas.\n * @template TActor - Actor type extending base Actor.\n * @property handler - The reaction handler.\n * @property resolver - The reaction resolver.\n * @property options - The reaction options.\n */\nexport type Reaction<\n TEvents extends Schemas,\n TKey extends keyof TEvents = keyof TEvents,\n TActions extends Schemas = Schemas,\n TActor extends Actor = Actor,\n> = {\n readonly handler: ReactionHandler<TEvents, TKey, TActions, TActor>;\n /**\n * Mutable so the builder's `.do()` → `.to()` chain can patch the resolver\n * in place (registered once with the default `_this_` resolver in `.do()`,\n * overwritten in `.to()` if present). After build-time the field is\n * effectively immutable; runtime consumers only read it.\n */\n resolver: ReactionResolver<TEvents, TKey>;\n readonly options: ReactionOptions;\n};\n\n/**\n * Payload for a reaction.\n * @template TEvents - Event schemas.\n * @property handler - The reaction handler.\n * @property resolver - The reaction resolver.\n * @property options - The reaction options.\n * @property event - The committed event triggering the reaction.\n * @property source - The source stream.\n */\nexport type ReactionPayload<TEvents extends Schemas> = Reaction<TEvents> & {\n readonly event: Committed<TEvents, keyof TEvents>;\n readonly source?: string;\n};\n\n/**\n * Result of fetching events from the store for processing.\n * @template TEvents - Event schemas.\n * @property stream - The stream name\n * @property source - The source (literal name or regex pattern), or undefined when sourcing from all streams.\n * @property at - The last event sequence number processed by the stream.\n * @property lagging - Whether the stream is lagging behind.\n * @property events - The list of next committed events to be processed by the stream.\n */\nexport type Fetch<TEvents extends Schemas> = Array<{\n readonly stream: string;\n readonly source?: string;\n readonly at: number;\n readonly lagging: boolean;\n readonly events: Committed<TEvents, keyof TEvents>[];\n /**\n * Why this stream's read failed, when it did. Present only on failure,\n * and then `events` is empty.\n *\n * Reads are per-stream but ran under one `Promise.all`, so a single\n * stream's failure rejected the whole cycle and every healthy stream\n * leased alongside it got nothing — a corrupt `pii` payload on one\n * aggregate stalled unrelated reactions. Isolating the failure\n * here keeps the blast radius on the stream that caused it: the cycle\n * records the error, submits no ack (so the watermark holds), and the\n * stream accrues `retry` until `blockOnError` quarantines it the usual\n * way — visible in `blocked_streams()` instead of silent.\n */\n readonly error?: string;\n}>;\n\n/**\n * Lease information for distributed stream processing.\n *\n * Leases prevent concurrent processing of the same stream by multiple workers.\n * When a worker acquires a lease, it has exclusive rights to process events\n * for that stream until the lease expires or is acknowledged.\n *\n * The drain process uses leases to:\n * - Prevent race conditions in distributed setups\n * - Track processing progress (watermark)\n * - Manage retries on failures\n * - Balance load between lagging and leading streams\n *\n * @property stream - The target stream name being processed\n * @property source - Optional source the subscription consumes from (literal name or regex pattern)\n * @property at - Watermark: last successfully processed event ID\n * @property by - Unique identifier of the lease holder (UUID)\n * @property retry - Number of retry attempts (0 = first attempt)\n * @property lagging - Whether this stream is behind (lagging frontier)\n * @property lane - Drain lane the stream is bound to\n *\n * @example\n * ```typescript\n * app.on(\"acked\", (leases) => {\n * leases.forEach(lease => {\n * console.log(`Processed ${lease.stream} up to event ${lease.at}`);\n * });\n * });\n *\n * app.on(\"blocked\", (blocked) => {\n * blocked.forEach(({ stream, retry, error }) => {\n * console.error(`Stream ${stream} blocked after ${retry} retries: ${error}`);\n * });\n * });\n * ```\n *\n * @see {@link Drain} for drain cycle results\n */\nexport type Lease = {\n readonly stream: string;\n readonly source?: string;\n readonly at: number;\n readonly by: string;\n readonly retry: number;\n readonly lagging: boolean;\n readonly lane?: string;\n /**\n * Defer marker on the finalize path. When set on a lease passed to\n * {@link Store.ack}, the entry is *deferred* alongside its watermark\n * advance: the adapter advances the watermark to `at` (the last event\n * handled this cycle) **and** persists `due` (ms since epoch) as the\n * stream's `deferred_at`, setting `retry` to the entry's own value —\n * atomically with the other entries' acks. Advance and defer are\n * independent legs, so a partial-progress defer keeps the handled prefix\n * (it never re-runs on redelivery) while the remainder waits for `due`.\n * Deferred entries are not part of ack's return value. Never set\n * on leases returned by `claim` or carried by lifecycle events.\n */\n readonly due?: number;\n};\n\n/**\n * A {@link Lease} augmented with the failure reason that pushed it past\n * its retry budget. Yielded by {@link Drain.blocked}, emitted on the\n * `\"blocked\"` lifecycle event, and accepted by {@link Store.block}.\n */\nexport type BlockedLease = Lease & { readonly error: string };\n\n/**\n * Options for draining events from the store.\n * @property streamLimit - Maximum number of streams to fetch.\n * @property eventLimit - Maximum number of events to fetch per stream.\n * @property leaseMillis - Maximum lease duration (in milliseconds).\n */\nexport type DrainOptions = {\n readonly streamLimit?: number;\n readonly eventLimit?: number;\n readonly leaseMillis?: number;\n};\n\n/**\n * Drain results\n * @property fetched - The fetched events.\n * @property leased - The leased events.\n * @property acked - The acked events.\n * @property blocked - The blocked events (with error).\n */\nexport type Drain<TEvents extends Schemas> = {\n readonly fetched: Fetch<TEvents>;\n readonly leased: Lease[];\n readonly acked: Lease[];\n readonly blocked: BlockedLease[];\n};\n\n/**\n * Options for the debounced settle cycle.\n *\n * Extends {@link DrainOptions} with parameters that control the debounce\n * window, the correlation query, and the maximum number of correlate→drain\n * passes.\n *\n * @property debounceMs - Debounce window in milliseconds (default: 10). A\n * pass already scheduled keeps its time unless this is shorter.\n * @property correlate - Only `limit` is used: the most events each\n * correlation scan reads (default 100). Every pass scans from the\n * correlation checkpoint, which every reaction shares, so filters and\n * `after` are ignored.\n * @property maxPasses - Cap on correlate→drain loops (default: `Infinity`).\n * Settle exits early as soon as a pass makes no progress (it read no\n * events and leased no streams), so the cap only matters in\n * pathological cases. It caps one settle: a reaction chain that keeps\n * committing schedules new passes of its own, which `maxPasses` doesn't\n * cover. `shutdown()` stops any of them after the pass in flight.\n */\nexport type SettleOptions = DrainOptions & {\n readonly debounceMs?: number;\n readonly correlate?: Query;\n readonly maxPasses?: number;\n};\n\n/**\n * Options for {@link IAct.shutdown}.\n *\n * @property graceMs - How long teardown waits for drain cycles already in\n * flight, in milliseconds. A reaction handler parked on an `await` (an\n * HTTP call, a database write) holds its stream's lease until it acks, so\n * abandoning it means the replacement worker cannot claim that stream\n * until the lease expires — up to `leaseMillis` of dead time per in-flight\n * stream on every rolling deploy, with the completing handler's round of\n * work discarded and redelivered.\n *\n * Defaults to the largest `leaseMillis` configured across the Act's lanes\n * (capped at 30s), because a lane's lease is already the operator's\n * statement of how long a handler may legitimately hold a stream. Lanes\n * that pinned no lease contribute the same 10s default `drain()` uses.\n *\n * `0` means: stop scheduling and return without\n * waiting. Whatever the budget, it is a ceiling and not a delay — teardown\n * proceeds the moment the last in-flight cycle finishes.\n */\nexport type ShutdownOptions = {\n readonly graceMs?: number;\n};\n\n/**\n * Name of the implicit lane every reaction lands in unless its `.to({lane})`\n * declaration says otherwise. An Act that never calls `.withLane(...)` has\n * only this lane.\n *\n * Persisted on `streams.lane` and threaded as the strict-typed default in\n * builder generics — `lane?: TLanes` always includes `\"default\"`.\n */\nexport const DEFAULT_LANE = \"default\";\n","import { type ZodObject, type ZodRawShape, z } from \"zod\";\nimport { _mark_sensitive } from \"../sensitive.js\";\n\n/**\n * @packageDocumentation\n * @module act/types\n * @category Types\n * Zod schemas and helpers for the Act Framework.\n */\n\n/**\n * An empty Zod schema (no properties).\n */\nexport const ZodEmpty = z.record(z.string(), z.never());\n\nexport { pii_fields, sensitiveFields } from \"../sensitive.js\";\n\n/**\n * Sentinel placed in `event.data[field]` when the caller isn't authorized to\n * see the sensitive field — either `.discloses(predicate)` returned `false`,\n * or no predicate was declared (framework default-deny). Recoverable: a\n * properly-authorized read returns the plaintext.\n */\nexport const REDACTED = \"[REDACTED]\" as const;\n\n/**\n * Sentinel placed in `event.data[field]` when the underlying PII payload has\n * been wiped via `Store.forget_pii(stream)` — the row's pii column is `NULL`\n * and the original plaintext is gone forever. Irrecoverable.\n */\nexport const SHREDDED = \"[SHREDDED]\" as const;\n\n/**\n * Mark a Zod schema as sensitive. Returns the same schema instance — the\n * marker is stamped on the schema's definition, so the static type is\n * preserved, the call site reads as a pure annotation, and the marker\n * survives any refinement chained after the call.\n *\n * Idempotent: re-wrapping an already-sensitive schema is a no-op.\n *\n * The marker is what the orchestrator inspects to split event payloads into\n * `data` + `pii` on commit, gate reads via `.discloses`, and strip handler\n * payloads. Part of the sensitive-data foundation.\n *\n * @example\n * ```ts\n * import { z } from \"zod\";\n * import { state, sensitive } from \"@rotorsoft/act\";\n *\n * const UserRegistered = z.object({\n * email: sensitive(z.string()),\n * name: sensitive(z.string()),\n * plan: z.enum([\"free\", \"pro\"]), // not sensitive — stays in events.data\n * });\n * ```\n *\n * @param schema - The Zod schema to mark sensitive.\n * @returns The same schema instance, unmodified at the type level.\n */\nexport function sensitive<T extends z.ZodType>(schema: T): T {\n // The marker lives on the def, so it survives the clone Zod produces for\n // any refinement chained after this call (`sensitive(z.string()).min(1)`);\n // without it the field would be written as plaintext into `events.data`.\n _mark_sensitive(schema);\n return schema;\n}\n\n/**\n * Zod schema for an actor (user, system, etc.).\n */\nexport const ActorSchema = z\n .object({\n id: z.string(),\n name: z.string(),\n })\n .loose()\n .readonly();\n\n/**\n * Zod schema for a target (stream and actor info).\n */\nexport const TargetSchema = z\n .object({\n stream: z.string(),\n actor: ActorSchema,\n expectedVersion: z.number().optional(),\n })\n .loose()\n .readonly();\n\n/**\n * Zod schema for causation event metadata.\n */\nexport const CausationEventSchema = z.object({\n id: z.number(),\n name: z.string(),\n stream: z.string(),\n});\n\n/**\n * Zod schema for event metadata (correlation and causation).\n */\nexport const EventMetaSchema = z\n .object({\n correlation: z.string(),\n causation: z.object({\n action: TargetSchema.and(z.object({ name: z.string() })).optional(),\n event: CausationEventSchema.optional(),\n }),\n })\n .readonly();\n\n/**\n * Zod schema for committed event metadata (id, stream, version, created, meta).\n */\nexport const CommittedMetaSchema = z\n .object({\n id: z.number(),\n stream: z.string(),\n version: z.number(),\n created: z.date(),\n meta: EventMetaSchema,\n })\n .readonly();\n\n/**\n * Type representing the full state schema for a domain.\n * @property events - Map of event names to Zod schemas.\n * @property actions - Map of action names to Zod schemas.\n * @property state - Zod schema for the state object.\n */\nexport type StateSchema = Readonly<{\n events: Record<string, ZodObject<ZodRawShape> | typeof ZodEmpty>;\n actions: Record<string, ZodObject<ZodRawShape> | typeof ZodEmpty>;\n state: ZodObject<ZodRawShape>;\n}>;\n\n/**\n * Query options for event store queries.\n */\nexport const QuerySchema = z\n .object({\n stream: z.string().optional(),\n names: z.string().array().optional(),\n before: z.number().optional(),\n after: z.number().optional(),\n limit: z.number().optional(),\n created_before: z.date().optional(),\n created_after: z.date().optional(),\n backward: z.boolean().optional(),\n correlation: z.string().optional(),\n with_snaps: z.boolean().optional(),\n with_pii: z.boolean().optional(),\n stream_exact: z.boolean().optional(),\n })\n .readonly();\n","/**\n * The one place that reads Zod's internals.\n *\n * Several features look inside a field's schema: whether it was marked\n * `sensitive(...)`, whether its value is a string, whether it holds a date to\n * revive. They must agree on which wrappers they see through, so they all ask\n * this module rather than each walking the schema its own way. The date\n * reviver makes one exception on purpose, named where it makes it: it keeps\n * a nullable's null.\n * `architecture.spec` keeps every other file away from `_zod`.\n */\nimport type { z } from \"zod\";\n\n/** A Zod schema's definition, which every wrapper and marker lives on. */\ntype Def = Record<PropertyKey, any>;\n\n/** The schema's definition (Zod 4 keeps it under `_zod.def`). */\nexport const def_of = (schema: unknown): Def | undefined =>\n (schema as { _zod?: { def?: Def } } | undefined)?._zod?.def;\n\n/**\n * The wrappers that hold one inner schema and produce its value.\n * `z.success()` and `z.promise()` keep an `innerType` too, but their value is\n * a boolean or a promise, not the inner schema's.\n */\nconst SINGLE_INNER: readonly string[] = [\n \"optional\",\n \"nullable\",\n \"default\",\n \"prefault\",\n \"nonoptional\",\n \"readonly\",\n \"catch\",\n];\n\n/** The inner schema of a wrapper in {@link SINGLE_INNER}, or `undefined`. */\nexport const inner_of = (def: Def): z.ZodType | undefined =>\n SINGLE_INNER.includes(def.type) ? def.innerType : undefined;\n\n/**\n * The schemas a wrapper holds, or none for anything that isn't one:\n * - a single-inner wrapper ({@link inner_of}) holds its inner schema;\n * - a pipe holds its input and its output: `.transform()`, `.pipe()` and\n * `z.preprocess()` all build one;\n * - `z.lazy()` holds what its getter returns.\n */\nexport function wrapped(schema: unknown): z.ZodType[] {\n const def = def_of(schema);\n if (!def) return [];\n if (def.type === \"pipe\") return [def.in, def.out];\n if (def.type === \"lazy\") return [def.getter()];\n const inner = inner_of(def);\n return inner ? [inner] : [];\n}\n\n/**\n * True when `test` holds for the schema or any schema it wraps, at any depth.\n * Each schema is visited once, so a lazy schema that returns itself ends.\n */\nexport function some_layer(\n schema: unknown,\n test: (def: Def) => boolean\n): boolean {\n const seen = new Set<unknown>();\n const visit = (s: unknown): boolean => {\n if (seen.has(s)) return false;\n seen.add(s);\n const def = def_of(s);\n return !!def && (test(def) || wrapped(s).some(visit));\n };\n return visit(schema);\n}\n\n/**\n * The schema that describes the value a schema produces: through every\n * wrapper to its innermost schema, taking a pipe's output side. A transform's\n * output can't be known, so the walk stops at it.\n */\nexport function output_of(schema: unknown): unknown {\n const seen = new Set<unknown>();\n let cur = schema;\n while (!seen.has(cur)) {\n seen.add(cur);\n const def = def_of(cur);\n if (!def) return cur;\n const next = def.type === \"pipe\" ? def.out : wrapped(cur)[0];\n if (!next) return cur;\n cur = next;\n }\n return cur;\n}\n","/**\n * The sensitive-data marker: the symbol `sensitive(zodType)` stamps on a\n * schema's definition, and the lookups that read it. The public\n * `sensitive()` and the two sentinels live in `types/schemas.ts`, which\n * re-exports `sensitiveFields` from here. The transforms that apply the marker at\n * commit, load and dispatch live in `internal/pii.ts`.\n */\nimport type { z } from \"zod\";\nimport { def_of, some_layer } from \"./internal/zod.js\";\n\n/**\n * Marker key stamped onto a schema's own `def`. Zod clones a schema on every\n * refinement (`.min()`, `.email()`, `.trim()`, `.describe()`, `.refine()`,\n * …) via `{...def}`, which copies own symbol keys — so a marker on the def\n * survives the whole chain. Wrappers (`.optional()`, `.transform()`,\n * `z.preprocess()`, `z.lazy()`, …) build a new schema around the marked one;\n * `is_pii` looks through them.\n */\nconst _SENSITIVE = Symbol.for(\"act.sensitive\");\n\n/**\n * Stamp the def-level marker. Called by the public `sensitive()`. Throws on a\n * schema with no Zod 4 definition: marking nothing would store the field in\n * plain text.\n */\nexport function _mark_sensitive(schema: z.ZodType): void {\n const def = def_of(schema);\n if (!def) throw new Error(\"sensitive() takes a Zod 4 schema\");\n def[_SENSITIVE] = true;\n}\n\n/**\n * True when the schema, or any schema it wraps, was marked via\n * `sensitive(...)`: behind every wrapper, on either side of a pipe, and\n * inside a lazy schema.\n */\nconst is_pii = (schema: z.ZodType): boolean =>\n some_layer(schema, (def) => def[_SENSITIVE] === true);\n\n/**\n * Derive an event's sensitive fields, as the declared schema of each.\n *\n * Walks the top-level shape of a `z.object({...})` and keeps the keys whose\n * schema was marked via `sensitive(...)`, behind any wrapper (see `is_pii`). Returns an empty object for non-object schemas or events\n * with no sensitive fields — the common-case zero-cost path.\n *\n * Only the top-level shape is walked. Sensitive fields nested inside a\n * `z.object` declared inside the event payload would require recursive\n * descent; that's deferred until a real callsite needs it.\n *\n * A union event has no top-level shape, so the options are walked and merged:\n * a key sensitive in any variant must be split, because the stored payload\n * could be that variant.\n * The first variant to declare a key wins, which only matters to a caller that\n * wants the schema rather than the name.\n *\n * Returning the schemas rather than just the names is what lets a caller do\n * something per field — the event builder asks each one whether it holds a\n * date, so the `pii` sidecar's dates can be revived like any other.\n */\nexport function pii_schemas(schema: z.ZodType): Record<string, z.ZodType> {\n const shape = (schema as { shape?: Record<string, z.ZodType> }).shape;\n if (shape && typeof shape === \"object\") {\n const fields: Record<string, z.ZodType> = {};\n for (const key of Object.keys(shape))\n if (is_pii(shape[key])) fields[key] = shape[key];\n return fields;\n }\n const options = (schema as { options?: unknown }).options;\n if (Array.isArray(options)) {\n const fields: Record<string, z.ZodType> = {};\n for (const option of options)\n for (const [key, field] of Object.entries(\n pii_schemas(option as z.ZodType)\n ))\n fields[key] ??= field;\n return fields;\n }\n return {};\n}\n\n/**\n * The top-level field names of a schema marked with `sensitive()`. Adapters\n * and tooling use it to reflect sensitivity on their own wire surface (the\n * act-http OpenAPI emitter marks these fields `writeOnly`). Returns `[]`\n * for a schema with no sensitive fields.\n */\nexport function sensitiveFields(schema: z.ZodType): readonly string[] {\n return Object.keys(pii_schemas(schema));\n}\n\n/**\n * @deprecated Use {@link sensitiveFields}. Removed in 3.0.\n */\nexport const pii_fields = sensitiveFields;\n","/**\n * @packageDocumentation\n * @module act/types\n * Barrel file for Act Framework core types.\n *\n * Re-exports all major type definitions for actions, errors, ports, reactions, registries, and schemas.\n * Also defines common environment and log level types/constants for configuration and logging.\n *\n * @remarks\n * Import from this module to access all core framework types in one place.\n */\nexport * from \"./act.js\";\nexport type * from \"./action.js\";\nexport type * from \"./audit.js\";\nexport type {\n ActBuilder,\n ActionBuilder,\n Projection,\n ProjectionBuilder,\n Slice,\n SliceBuilder,\n StateBuilder,\n} from \"./builders.js\";\nexport * from \"./errors.js\";\nexport type * from \"./ports.js\";\nexport {\n DEFAULT_QUERY_STREAMS_LIMIT,\n SNAP_EVENT,\n TOMBSTONE_EVENT,\n} from \"./ports.js\";\nexport type * from \"./reaction.js\";\nexport { DEFAULT_LANE } from \"./reaction.js\";\nexport type * from \"./registry.js\";\nexport * from \"./schemas.js\";\n\n/**\n * Supported runtime environments for the framework.\n * - `development`: Local development\n * - `test`: Automated testing\n * - `staging`: Pre-production\n * - `production`: Live/production\n */\nexport const Environments = [\n \"development\",\n \"test\",\n \"staging\",\n \"production\",\n] as const;\n\n/**\n * Type representing a valid environment string.\n */\nexport type Environment = (typeof Environments)[number];\n\n/**\n * Supported log levels for framework logging.\n * - `fatal`, `error`, `warn`, `info`, `debug`, `trace`\n */\nexport const LogLevels = [\n \"fatal\",\n \"error\",\n \"warn\",\n \"info\",\n \"debug\",\n \"trace\",\n] as const;\n\n/**\n * Type representing a valid log level string.\n */\nexport type LogLevel = (typeof LogLevels)[number];\n\n/**\n * List of exit codes for process termination, as passed to\n * {@link disposeAndExit} by the signal handlers and by callers that shut\n * down on their own.\n */\nexport const ExitCodes = [\"ERROR\", \"EXIT\"] as const;\n\n/**\n * Type for allowed exit codes.\n *\n * - `\"ERROR\"` — abnormal termination (uncaught exception, unhandled rejection)\n * - `\"EXIT\"` — clean shutdown (SIGINT, SIGTERM, or manual trigger)\n */\nexport type ExitCode = (typeof ExitCodes)[number];\n\n/**\n * The validated `package.json` metadata `config()` reads.\n */\nexport type Package = {\n name: string;\n version: string;\n description?: string;\n author?: { name: string; email?: string } | string;\n license?: string;\n dependencies?: Record<string, string>;\n};\n\n/** The validated Act Framework configuration `config()` returns. */\nexport type Config = Package & {\n env: Environment;\n logLevel: LogLevel;\n logSingleLine: boolean;\n sleepMs: number;\n};\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/act-root/act-root/libs/act/dist/chunk-VOVHDDET.cjs","../src/adapters/console-logger.ts","../src/internal/lru-map.ts","../src/adapters/in-memory-cache.ts","../src/config.ts","../src/utils.ts","../src/adapters/in-memory-store.ts","../src/disposers.ts","../src/internal/config.ts","../src/scoped.ts","../src/ports.ts"],"names":["z","correlated_at","pkg"],"mappings":"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T;AAee;AACN,EAAA;AACR;AAEsB;AACd,EAAA;AACT;AAiBa;AAEP;AACQ,EAAA;AACH,IAAA;AACT,EAAA;AACY,EAAA;AACH,IAAA;AACT,EAAA;AACF;AA2BsB;AACP,EAAA;AAIL,IAAA;AACJ,MAAA;AACF,IAAA;AACA,IAAA;AACF,EAAA;AAKM,EAAA;AACK,EAAA;AACH,IAAA;AACK,IAAA;AACb,EAAA;AACe,EAAA;AACN,EAAA;AACX;AA2BE;AAEY,EAAA;AACL,EAAA;AACT;AVizBiB;AACA;AI3iCJ;AACH,EAAA;AACG,EAAA;AACED,EAAAA;AAEV,EAAA;AAIQ,EAAA;AACGA,EAAAA;AACf;AAeK;AACE,EAAA;AACG,EAAA;AACI,EAAA;AACf;AAcM;AACA,EAAA;AACU,IAAA;AACyB,IAAA;AAC7B,MAAA;AACG,MAAA;AACX,IAAA;AACY,IAAA;AACJ,MAAA;AACI,MAAA;AACZ,IAAA;AACOE,IAAAA;AACK,EAAA;AACZ,IAAA;AACO,IAAA;AACT,EAAA;AACF;AAGI;AAOe;AACL,EAAA;AACA,EAAA;AACGF,EAAAA;AACJ,EAAA;AACZ;AAEiB;AAEL;AACK;AAMZ;AACU;AAEJ;AAMR;AAiCkB;AACf,EAAA;AACU,IAAA;AACX,MAAA;AACU,MAAA;AACV,MAAA;AACF,IAAA;AACI,IAAA;AAII,MAAA;AAMA,MAAA;AACJ,QAAA;AAEF,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AACO,EAAA;AACT;AJg9BiB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/act-root/act-root/libs/act/dist/chunk-VOVHDDET.cjs","sourcesContent":[null,"/**\n * @module adapters/console-logger\n *\n * High-performance console logger inspired by pino's design:\n * - Numeric level comparison for O(1) gating\n * - stdout.write() in production for raw JSON lines (no console overhead)\n * - Colorized single-line output in development\n * - No-op method replacement when level is above threshold\n * - Child logger support with merged bindings\n */\nimport type { Logger } from \"../types/index.js\";\n\nconst LEVEL_VALUES: Record<string, number> = {\n fatal: 60,\n error: 50,\n warn: 40,\n info: 30,\n debug: 20,\n trace: 10,\n};\n\nconst LEVEL_COLORS: Record<string, string> = {\n fatal: \"\\x1b[41m\\x1b[37m\", // white on red bg\n error: \"\\x1b[31m\", // red\n warn: \"\\x1b[33m\", // yellow\n info: \"\\x1b[32m\", // green\n debug: \"\\x1b[36m\", // cyan\n trace: \"\\x1b[90m\", // gray\n};\n\nconst RESET = \"\\x1b[0m\";\n\nconst noop = () => {};\n\n/**\n * Default console logger for the Act framework.\n *\n * Production mode emits newline-delimited JSON (compatible with GCP, AWS\n * CloudWatch, Datadog, and other structured log ingestion systems).\n *\n * Development mode emits colorized, human-readable output.\n */\nexport class ConsoleLogger implements Logger {\n level: string;\n private readonly _pretty: boolean;\n\n readonly fatal: Logger[\"fatal\"];\n readonly error: Logger[\"error\"];\n readonly warn: Logger[\"warn\"];\n readonly info: Logger[\"info\"];\n readonly debug: Logger[\"debug\"];\n readonly trace: Logger[\"trace\"];\n\n constructor(\n options: {\n level?: string;\n pretty?: boolean;\n bindings?: Record<string, unknown>;\n } = {}\n ) {\n const {\n level = \"info\",\n pretty = process.env.NODE_ENV !== \"production\",\n bindings,\n } = options;\n this._pretty = pretty;\n this.level = level;\n\n const threshold = LEVEL_VALUES[level] ?? 30;\n const write = pretty\n ? this._pretty_write.bind(this, bindings)\n : this._json_write.bind(this, bindings);\n\n // Assign methods — noop when level is gated (like pino's level-based replacement)\n this.fatal = write.bind(this, \"fatal\", 60); // fatal is always enabled\n this.error = threshold <= 50 ? write.bind(this, \"error\", 50) : noop;\n this.warn = threshold <= 40 ? write.bind(this, \"warn\", 40) : noop;\n this.info = threshold <= 30 ? write.bind(this, \"info\", 30) : noop;\n this.debug = threshold <= 20 ? write.bind(this, \"debug\", 20) : noop;\n this.trace = threshold <= 10 ? write.bind(this, \"trace\", 10) : noop;\n }\n\n /** No-op — `console.log` has no resources to release. */\n async dispose(): Promise<void> {}\n\n /** @inheritDoc */\n child(bindings: Record<string, unknown>): Logger {\n return new ConsoleLogger({\n level: this.level,\n pretty: this._pretty,\n bindings,\n });\n }\n\n private _json_write(\n bindings: Record<string, unknown> | undefined,\n level: string,\n _num: number,\n obj_or_msg: unknown,\n msg?: string\n ): void {\n let obj: Record<string, unknown>;\n let message: string | undefined;\n\n if (typeof obj_or_msg === \"string\") {\n message = obj_or_msg;\n obj = {};\n } else if (obj_or_msg instanceof Error) {\n // Error instances spread to `{}` — capture the salient fields\n // explicitly so structured log aggregators see them.\n message = msg ?? obj_or_msg.message;\n obj = {\n error: { message: obj_or_msg.message, name: obj_or_msg.name },\n stack: obj_or_msg.stack,\n };\n } else if (obj_or_msg !== null && typeof obj_or_msg === \"object\") {\n message = msg;\n obj = { ...(obj_or_msg as Record<string, unknown>) };\n } else {\n message = msg;\n obj = { value: obj_or_msg };\n }\n\n const entry = Object.assign({ level, time: Date.now() }, bindings, obj);\n if (message) entry.msg = message;\n\n let line: string;\n try {\n line = JSON.stringify(entry);\n } catch {\n // Cyclic or unserializable payload — emit a minimal line rather\n // than crash the log call site.\n line = JSON.stringify({\n level,\n time: entry.time,\n msg: message ?? \"[unserializable]\",\n unserializable: true,\n });\n }\n process.stdout.write(line + \"\\n\");\n }\n\n private _pretty_write(\n bindings: Record<string, unknown> | undefined,\n level: string,\n _num: number,\n obj_or_msg: unknown,\n msg?: string\n ): void {\n const color = LEVEL_COLORS[level];\n const tag = `${color}${level.toUpperCase().padEnd(5)}${RESET}`;\n const ts = new Date().toISOString().slice(11, 23); // HH:mm:ss.SSS\n\n let message: string;\n let data: string | undefined;\n\n if (typeof obj_or_msg === \"string\") {\n message = obj_or_msg;\n } else if (obj_or_msg instanceof Error) {\n // Error instances don't serialize their `message`/`stack` via\n // JSON.stringify — `JSON.stringify(err) === \"{}\"`. Render the\n // message inline and the stack as the data payload so operators\n // see something useful instead of an empty object.\n message = msg ?? obj_or_msg.message;\n data = obj_or_msg.stack;\n } else {\n message = msg ?? \"\";\n if (obj_or_msg !== undefined && obj_or_msg !== null) {\n try {\n data = JSON.stringify(obj_or_msg);\n } catch {\n data = \"[unserializable]\";\n }\n }\n }\n\n const bind_str =\n bindings && Object.keys(bindings).length\n ? ` ${JSON.stringify(bindings)}`\n : \"\";\n\n const parts = [ts, tag, message, data, bind_str].filter(Boolean);\n process.stdout.write(parts.join(\" \") + \"\\n\");\n }\n}\n","/**\n * Tiny bounded LRU map built on insertion-ordered `Map`. Caps memory in\n * long-running orchestrators that mint large numbers of keys:\n *\n * - {@link InMemoryCache}: stream → state checkpoint\n * - correlate's dynamic subscriptions (stream → priority and lane) and its\n * compiled source patterns\n *\n * Apps with millions of dynamic streams (one target per aggregate) can't\n * afford an unbounded map: eviction is required.\n */\n\n/**\n * Bounded LRU map. `get()` promotes; `has()` does not. `set()` always\n * promotes and evicts the oldest entry when at capacity.\n */\nexport class LruMap<K, V> {\n private readonly _entries = new Map<K, V>();\n private readonly _max_size: number;\n\n constructor(maxSize: number) {\n this._max_size = maxSize;\n }\n\n get(key: K): V | undefined {\n const v = this._entries.get(key);\n if (v === undefined) return undefined;\n // promote: delete + re-insert moves to most-recent position\n this._entries.delete(key);\n this._entries.set(key, v);\n return v;\n }\n\n has(key: K): boolean {\n return this._entries.has(key);\n }\n\n set(key: K, value: V): void {\n this._entries.delete(key);\n if (this._entries.size >= this._max_size) {\n // size >= maxSize ≥ 1 → at least one entry exists → next().value\n // is the oldest key (asserted with `!`).\n const oldest = this._entries.keys().next().value!;\n this._entries.delete(oldest);\n }\n this._entries.set(key, value);\n }\n\n delete(key: K): boolean {\n return this._entries.delete(key);\n }\n\n clear(): void {\n this._entries.clear();\n }\n\n get size(): number {\n return this._entries.size;\n }\n}\n","import { LruMap } from \"../internal/lru-map.js\";\nimport type { Cache, CacheEntry, Schema } from \"../types/index.js\";\n\n/**\n * In-memory LRU cache for stream snapshots.\n *\n * Backed by an internal `LruMap` for O(1) get/set with LRU eviction.\n * Configurable `maxSize` bounds memory usage.\n *\n * @example\n * ```typescript\n * import { cache } from \"@rotorsoft/act\";\n * import { InMemoryCache } from \"@rotorsoft/act\";\n *\n * cache(new InMemoryCache({ maxSize: 500 }));\n * ```\n */\nexport class InMemoryCache implements Cache {\n // CacheEntry<any> lets `get<TState>` and `set<TState>` flow without casts:\n // any is bidirectionally compatible with the per-call TState binding, while\n // the public Cache interface still presents a typed surface to callers.\n private readonly _entries: LruMap<string, CacheEntry<any>>;\n\n constructor(options?: { maxSize?: number }) {\n this._entries = new LruMap(options?.maxSize ?? 1000);\n }\n\n /** @inheritDoc */\n async get<TState extends Schema>(\n stream: string\n ): Promise<CacheEntry<TState> | undefined> {\n return this._entries.get(stream);\n }\n\n /** @inheritDoc */\n async set<TState extends Schema>(\n stream: string,\n entry: CacheEntry<TState>\n ): Promise<void> {\n this._entries.set(stream, entry);\n }\n\n /** @inheritDoc */\n async invalidate(stream: string): Promise<void> {\n this._entries.delete(stream);\n }\n\n /** @inheritDoc */\n async clear(): Promise<void> {\n this._entries.clear();\n }\n\n /** @inheritDoc */\n async dispose(): Promise<void> {\n this._entries.clear();\n }\n\n /** Current number of entries held by the LRU. */\n get size(): number {\n return this._entries.size;\n }\n}\n","/**\n * @packageDocumentation\n * Configuration utilities for Act Framework environment, logging, and package metadata.\n *\n * Provides type-safe configuration loading and validation using Zod schemas.\n *\n * @module config\n */\nimport * as fs from \"node:fs\";\nimport { z } from \"zod\";\nimport { log } from \"./ports.js\";\nimport {\n type Config,\n type Environment,\n Environments,\n type LogLevel,\n LogLevels,\n type Package,\n} from \"./types/index.js\";\nimport { validate } from \"./utils.js\";\n\n/**\n * Zod schema for the package.json fields {@link config} validates.\n */\nexport const package_schema = z.object({\n name: z.string().min(1),\n version: z.string().min(1),\n description: z.string().min(1).optional(),\n author: z\n .object({ name: z.string().min(1), email: z.string().optional() })\n .optional()\n .or(z.string().min(1))\n .optional(),\n license: z.string().min(1).optional(),\n dependencies: z.record(z.string(), z.string()).optional(),\n}) satisfies z.ZodType<Package>;\n\n/**\n * Fallback package metadata when `package.json` can't be read at module\n * load — happens when the framework is consumed from a CWD that doesn't\n * have one (bundled CLIs, Lambda layers, embedded scripts) or when the\n * file exists but is malformed. `name` and `version` also fall back one by\n * one when the file has no valid value for them.\n *\n * The values are deliberately synthetic so callers spot them immediately:\n * `config().name === \"act-fallback\"` is a runtime signal that the framework\n * couldn't load the host project's package.json.\n *\n * @internal\n */\nconst FALLBACK_PACKAGE: Package = {\n name: \"act-fallback\",\n version: \"0.0.0-fallback\",\n description: \"Synthetic fallback — package.json could not be loaded\",\n};\n\n/**\n * Loads the local package.json as a Package object, keeping each field only\n * when it is valid: `npm init -y` writes an empty `description` and\n * `author`, and a bare `{\"type\":\"module\"}` has no name or version. A\n * missing or invalid `name` / `version` falls back to\n * {@link FALLBACK_PACKAGE}. On a read or parse failure, falls back entirely\n * and stashes the error so {@link config} can surface it on first access —\n * we can't call `log()` here because the logger port memoizes on first\n * call and locking it at module load defeats user injection.\n *\n * @internal\n */\nconst get_package = (): Package => {\n try {\n const raw = JSON.parse(fs.readFileSync(\"package.json\").toString());\n const pkg: Record<string, unknown> = {\n name: FALLBACK_PACKAGE.name,\n version: FALLBACK_PACKAGE.version,\n };\n for (const [key, schema] of Object.entries(package_schema.shape)) {\n const field = schema.safeParse(raw?.[key]);\n if (field.success && field.data !== undefined) pkg[key] = field.data;\n }\n return pkg as Package;\n } catch (err) {\n pkg_load_error = err;\n return FALLBACK_PACKAGE;\n }\n};\n\n/** Stashed read/parse error from {@link get_package}, surfaced by config(). */\nlet pkg_load_error: unknown;\n\n/**\n * Zod schema for the full Act Framework configuration object.\n * Includes package metadata, environment, logging, and timing options.\n * @internal\n */\nconst BaseSchema = package_schema.extend({\n env: z.enum(Environments),\n logLevel: z.enum(LogLevels),\n logSingleLine: z.boolean(),\n sleepMs: z.number().int().min(0).max(5000),\n}) satisfies z.ZodType<Config>;\n\nconst { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;\n\nconst env = (NODE_ENV || \"development\") as Environment;\nconst logLevel = (LOG_LEVEL ||\n (NODE_ENV === \"test\"\n ? \"fatal\"\n : NODE_ENV === \"production\"\n ? \"info\"\n : \"trace\")) as LogLevel;\nconst logSingleLine = (LOG_SINGLE_LINE || \"true\") === \"true\";\nconst sleepMs = parseInt(NODE_ENV === \"test\" ? \"0\" : (SLEEP_MS ?? \"100\"), 10);\n\nconst pkg = get_package();\n\n// Lazily validated on first call. Cannot run validate() at module load\n// because of a utils.ts <-> config.ts cycle (utils imports config for\n// sleep()'s default). Inputs are frozen after import, so the cached\n// result is stable for the life of the process.\nlet _validated: Config | undefined;\n\n/**\n * Gets the current Act Framework configuration.\n *\n * Configuration is loaded from package.json and environment variables, providing\n * type-safe access to application metadata and runtime settings.\n *\n * **Environment Variables:**\n * - `NODE_ENV`: \"development\" | \"test\" | \"staging\" | \"production\" (default: \"development\")\n * - `LOG_LEVEL`: \"fatal\" | \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\"\n * - `LOG_SINGLE_LINE`: \"true\" | \"false\" (default: \"true\")\n * - `SLEEP_MS`: Milliseconds for sleep utility (default: 100, 0 for tests)\n *\n * **Defaults by environment:**\n * - test: logLevel=\"error\", sleepMs=0\n * - production: logLevel=\"info\"\n * - development: logLevel=\"trace\"\n *\n * @returns The validated configuration object\n *\n * @example Basic usage\n * ```typescript\n * import { config } from \"@rotorsoft/act\";\n *\n * const cfg = config();\n * console.log(`App: ${cfg.name} v${cfg.version}`);\n * console.log(`Environment: ${cfg.env}`);\n * console.log(`Log level: ${cfg.logLevel}`);\n * ```\n *\n * @see {@link Config} for configuration type\n */\nexport const config = (): Config => {\n if (!_validated) {\n _validated = validate(\n \"config\",\n { ...pkg, env, logLevel, logSingleLine, sleepMs },\n BaseSchema\n );\n if (pkg_load_error) {\n // Surface the fallback once, after _validated is set so the\n // recursive log() → config() call short-circuits. log() resolves\n // through the port singleton — respects user injection and level.\n const msg =\n pkg_load_error instanceof Error\n ? pkg_load_error.message\n : typeof pkg_load_error === \"string\"\n ? pkg_load_error\n : \"unknown error\";\n log().warn(\n `[act] Could not read package.json (${msg}); using synthetic ` +\n `name=\"${FALLBACK_PACKAGE.name}\" version=\"${FALLBACK_PACKAGE.version}\".`\n );\n pkg_load_error = undefined;\n }\n }\n return _validated;\n};\n","import { prettifyError, ZodError, type ZodType } from \"zod\";\nimport { config } from \"./config.js\";\nimport { ValidationError } from \"./types/index.js\";\n\n/**\n * @module utils\n * @category Utilities\n *\n * Small utilities used across the framework:\n * - {@link validate} — parse a payload against a Zod schema, throwing\n * {@link ValidationError} on failure.\n * - {@link sleep} — async delay (default duration from `config().sleepMs`).\n */\n\n/**\n * Parse `payload` against `schema`, returning the validated value or throwing\n * a {@link ValidationError} with prettified Zod details. When `schema` is\n * omitted, returns `payload` unchanged. The framework calls this for every\n * `app.do()` action, every emitted event, and every state init.\n *\n * @example\n * ```typescript\n * const UserSchema = z.object({ email: z.string().email() });\n * const user = validate(\"User\", { email: \"alice@example.com\" }, UserSchema);\n * ```\n *\n * @see {@link ValidationError}\n */\nexport const validate = <S>(\n target: string,\n payload: Readonly<S>,\n schema?: ZodType<S>\n): Readonly<S> => {\n try {\n return schema ? schema.parse(payload) : payload;\n } catch (error) {\n if (error instanceof ZodError) {\n throw new ValidationError(target, payload, prettifyError(error));\n }\n throw new ValidationError(target, payload, error);\n }\n};\n\n/**\n * Pause for `ms` milliseconds (or `config().sleepMs` when omitted — `100ms`\n * in dev, `0ms` in tests). Used by adapters to simulate async I/O.\n *\n * @example\n * ```typescript\n * await sleep(); // default delay from config\n * await sleep(500); // explicit 500ms\n * ```\n */\nexport async function sleep(ms?: number) {\n return new Promise((resolve) => setTimeout(resolve, ms ?? config().sleepMs));\n}\n","/**\n * @packageDocumentation\n * @module act/adapters\n * In-memory event store adapter for development and tests.\n *\n * @category Adapters\n */\n\nimport {\n checkAppends,\n checkVersion,\n fairSlots,\n seedEvent,\n} from \"../adapter.js\";\nimport type {\n Append,\n BlockedLease,\n Committed,\n EventMeta,\n Lease,\n Query,\n QueryStatsOptions,\n QueryStreams,\n QueryStreamsResult,\n Schema,\n Schemas,\n Store,\n StreamFilter,\n StreamPosition,\n StreamStats,\n SubscribeInput,\n} from \"../types/index.js\";\nimport {\n DEFAULT_LANE,\n DEFAULT_QUERY_STREAMS_LIMIT,\n SNAP_EVENT,\n} from \"../types/index.js\";\nimport { sleep } from \"../utils.js\";\n\n/**\n * One subscription row: watermark, lease, retry and schedule. Its state is\n * private and changes only through the methods below, which carry the\n * row's rules; the store reads it through the getters.\n */\nclass InMemoryStream {\n readonly stream: string;\n readonly source: string | undefined;\n private _at = -1;\n private _retry = -1;\n private _blocked = false;\n private _error = \"\";\n private _leased_by: string | undefined = undefined;\n private _leased_until: Date | undefined = undefined;\n private _priority: number;\n private _lane: string;\n // While in the future, the stream is held out of `claim`.\n private _deferred_at: number | undefined = undefined;\n // Work mark; `undefined` = never correlated, so `claim` skips it.\n private _correlated_at: number | undefined = undefined;\n\n constructor(\n stream: string,\n source: string | undefined,\n priority = 0,\n lane: string = DEFAULT_LANE\n ) {\n this.stream = stream;\n this.source = source;\n this._priority = priority;\n this._lane = lane;\n }\n\n get at() {\n return this._at;\n }\n\n get retry() {\n return this._retry;\n }\n\n get blocked() {\n return this._blocked;\n }\n\n get error() {\n return this._error;\n }\n\n get leased_by() {\n return this._leased_by;\n }\n\n get leased_until() {\n return this._leased_until;\n }\n\n get priority() {\n return this._priority;\n }\n\n get lane() {\n return this._lane;\n }\n\n get deferred_at() {\n return this._deferred_at;\n }\n\n get correlated_at() {\n return this._correlated_at;\n }\n\n get is_available() {\n return (\n !this._blocked &&\n (!this._leased_until || this._leased_until <= new Date()) &&\n (!this._deferred_at || this._deferred_at <= Date.now())\n );\n }\n\n /**\n * A later subscribe of this row keeps the maximum priority, and takes the\n * new lane only at or above the stored priority (compared before the\n * bump), so a lower-priority resolution can't re-lane the stream. The\n * lane is replaced on every qualifying subscribe, so the current config\n * wins on restart.\n */\n resubscribe(priority: number, lane: string) {\n if (priority >= this._priority) this._lane = lane;\n if (priority > this._priority) this._priority = priority;\n }\n\n /** Operator override; ignores the subscribe-time `max()`. */\n prioritize(priority: number) {\n this._priority = priority;\n }\n\n /** A mark never regresses, including at zero and negatives. */\n mark(correlated_at: number) {\n if (\n this._correlated_at === undefined ||\n correlated_at > this._correlated_at\n )\n this._correlated_at = correlated_at;\n }\n\n defer(deferred_at: number) {\n this._deferred_at = deferred_at;\n // A defer is not a failure, so the redelivery is a fresh attempt.\n this._retry = -1;\n }\n\n // The lease this row hands back, at the given watermark.\n private _lease(lease: Lease, at: number) {\n return {\n stream: this.stream,\n source: this.source,\n at,\n by: lease.by,\n retry: this._retry,\n lagging: lease.lagging,\n lane: this._lane,\n };\n }\n\n lease(lease: Lease, millis: number): Lease {\n // The holder is recorded even at zero `millis`: `ack` is gated on it,\n // so a lease with no holder would drop every ack.\n this._leased_by = lease.by;\n this._leased_until = new Date(Date.now() + millis);\n this._retry = this._retry + 1;\n return this._lease(lease, lease.at);\n }\n\n /** Ack, or with `due`, also persist the schedule. See {@link Store.ack}. */\n ack(lease: Lease) {\n if (this._leased_by === lease.by) {\n this._leased_by = undefined;\n this._leased_until = undefined;\n if (lease.due !== undefined) {\n // Advance past the handled prefix and schedule the re-visit with the\n // caller's retry (-1 for a defer, the climbing count for a backoff).\n this._at = lease.at;\n this._retry = lease.retry;\n this._deferred_at = lease.due;\n return undefined;\n }\n this._retry = -1;\n this._at = lease.at;\n this._deferred_at = undefined;\n return this._lease(lease, this._at);\n }\n }\n\n block(lease: Lease, error: string) {\n // Re-blocking is a no-op, like the SQL `AND blocked = false` guard.\n if (this._leased_by === lease.by && !this._blocked) {\n this._blocked = true;\n this._error = error;\n this._deferred_at = undefined;\n return { ...this._lease(lease, this._at), error };\n }\n }\n\n // Clears the block, the lease and the schedule; retry back to -1.\n private _release() {\n this._retry = -1;\n this._blocked = false;\n this._error = \"\";\n this._leased_by = undefined;\n this._leased_until = undefined;\n this._deferred_at = undefined;\n }\n\n /** Retry resets to -1 (released); the next claim makes it 0. */\n reset() {\n this._at = -1;\n this._release();\n }\n\n /** True only when the stream was blocked. */\n unblock(): boolean {\n if (!this._blocked) return false;\n this._release();\n return true;\n }\n}\n\n/**\n * Tests a name against a stream or source filter: exact, or a regex\n * compiled once.\n */\nconst name_test = (\n pattern: string,\n exact?: boolean\n): ((name: string) => boolean) => {\n if (exact) return (name) => name === pattern;\n const re = new RegExp(pattern);\n return (name) => re.test(name);\n};\n\n/**\n * In-memory event store: the default when no other store is injected.\n * For development, tests and examples; all data is lost when the process\n * exits. Use {@link PostgresStore} in production.\n *\n * `Store.notify` is not implemented: a single process has no remote writer\n * to hear from, and local commits already arm the drain.\n *\n * @example Using in tests\n * ```typescript\n * import { store } from \"@rotorsoft/act\";\n *\n * describe(\"Counter\", () => {\n * beforeEach(async () => {\n * // Reset store between tests\n * await store().seed();\n * });\n *\n * it(\"increments\", async () => {\n * await app.do(\"increment\", target, { by: 5 });\n * const snapshot = await app.load(Counter, \"counter-1\");\n * expect(snapshot.state.count).toBe(5);\n * });\n * });\n * ```\n *\n * @see {@link Store} for the interface definition\n * @see {@link PostgresStore} for production use\n * @see {@link store} for injecting stores\n *\n * @category Adapters\n */\nexport class InMemoryStore implements Store {\n private _events: Committed<Schemas, keyof Schemas>[] = [];\n // Never reused. Deletions punch holes, so ids are not array indexes.\n private _next_id = 0;\n private _streams: Map<string, InMemoryStream> = new Map();\n // Correlate checkpoint: how far the log has been READ.\n private _correlated_at = -1;\n // Per-correlator checkpoint and lease; keyless callers use `_correlated_at`.\n private _correlators = new Map<\n string,\n { at: number; by: string; until: number }\n >();\n private _stream_versions: Map<string, number> = new Map();\n // stream → (event_id → cloned pii). Two levels so `forget_pii` drops one\n // inner Map. Absence means no PII.\n private _pii: Map<string, Map<number, Record<string, unknown>>> = new Map();\n\n private _reset_indexes() {\n this._events.length = 0;\n this._next_id = 0;\n this._correlated_at = -1;\n this._stream_versions.clear();\n this._pii.clear();\n }\n\n // Stores a cloned copy, so caller-side mutation can't reach it.\n private _put_pii(stream: string, id: number, pii: Record<string, unknown>) {\n let per_stream = this._pii.get(stream);\n if (!per_stream) {\n per_stream = new Map();\n this._pii.set(stream, per_stream);\n }\n per_stream.set(id, structuredClone(pii));\n }\n\n // First index whose id is greater than `after` (ids are sorted, not dense).\n private _first_index_after(after: number): number {\n let lo = 0;\n let hi = this._events.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (this._events[mid].id > after) hi = mid;\n else lo = mid + 1;\n }\n return lo;\n }\n\n // Allocation-free for events without PII, the common case.\n private _with_pii<E extends Schemas>(\n e: Committed<E, keyof E>,\n wanted = true\n ): Committed<E, keyof E> {\n if (!wanted) return e;\n const pii = this._pii.get(e.stream)?.get(e.id);\n return pii ? ({ ...e, pii } as Committed<E, keyof E>) : e;\n }\n\n async dispose() {\n await sleep();\n this._reset_indexes();\n }\n\n async seed() {\n await sleep();\n }\n\n async drop() {\n await sleep();\n this._reset_indexes();\n this._streams = new Map();\n }\n\n private _in_query(\n query: Query\n ): (e: Committed<Schemas, keyof Schemas>) => boolean {\n const stream_ok = query.stream\n ? name_test(query.stream, query.stream_exact)\n : undefined;\n return (e) => {\n if (stream_ok && !stream_ok(e.stream)) return false;\n if (query.names && !query.names.includes(e.name as string)) return false;\n if (query.correlation && e.meta?.correlation !== query.correlation)\n return false;\n if (e.name === SNAP_EVENT && !query.with_snaps) return false;\n // `created` is not monotonic with `id` (restore keeps it), so time\n // bounds filter; they never end the scan.\n if (query.created_after && e.created <= query.created_after) return false;\n if (query.created_before && e.created >= query.created_before)\n return false;\n return true;\n };\n }\n\n // Index of the stream's latest snapshot, or -1; an explicit `after` wins.\n private _snap_floor(query: Query | undefined): number {\n if (\n !query?.with_snaps ||\n !query.stream_exact ||\n query.stream === undefined ||\n query.after !== undefined\n )\n return -1;\n for (let j = this._events.length - 1; j >= 0; j--) {\n const e = this._events[j];\n if (e.stream === query.stream && e.name === SNAP_EVENT) return j;\n }\n return -1;\n }\n\n async query<E extends Schemas>(\n callback: (event: Committed<E, keyof E>) => void,\n query?: Query\n ) {\n await sleep();\n // The id bounds (`after`, `before`, the snapshot floor) are one index\n // window over the id-ordered log; every other filter is per event.\n const floor = this._snap_floor(query);\n const lo = floor >= 0 ? floor : this._first_index_after(query?.after ?? -1);\n const hi =\n query?.before !== undefined\n ? this._first_index_after(query.before - 1)\n : this._events.length;\n const with_pii = query?.with_pii !== false;\n const in_query = this._in_query(query ?? {});\n let count = 0;\n for (let k = 0; k < hi - lo; k++) {\n const e = this._events[query?.backward ? hi - 1 - k : lo + k];\n if (!in_query(e)) continue;\n await Promise.resolve(\n callback(this._with_pii(e as Committed<E, keyof E>, with_pii))\n );\n count++;\n if (query?.limit && count >= query.limit) break;\n }\n return count;\n }\n\n async commit<E extends Schemas>(\n appends: readonly Append<E>[],\n meta: EventMeta\n ) {\n await sleep();\n checkAppends(appends);\n // Every version is checked before anything is appended, so a stale\n // append leaves every stream as it was.\n for (const append of appends)\n checkVersion(append, this._stream_versions.get(append.stream) ?? -1);\n const created = new Date();\n return appends.flatMap(({ stream, msgs }) => {\n let version = this._stream_versions.get(stream) ?? -1;\n const committed = msgs.map(({ name, data, pii }) => {\n const c: Committed<E, keyof E> = {\n id: this._next_id++,\n stream,\n version: ++version,\n created,\n name,\n data,\n meta,\n };\n // Stored pii-less, so `forget_pii` never touches the event row.\n this._events.push(c as Committed<Schemas, keyof Schemas>);\n if (pii != null) this._put_pii(stream, c.id, pii);\n return this._with_pii(c);\n });\n this._stream_versions.set(stream, version);\n return committed;\n });\n }\n\n async claim(\n lagging: number,\n leading: number,\n by: string,\n millis: number,\n lane?: string\n ) {\n await sleep();\n // Eligibility is `at < correlated_at` on the row alone; the event log\n // is never read. An unmarked row is not claimable.\n const has_work = (s: InMemoryStream): boolean =>\n s.correlated_at !== undefined && s.at < s.correlated_at;\n const available = [...this._streams.values()].filter(\n (s) =>\n s.is_available && has_work(s) && (lane === undefined || s.lane === lane)\n );\n // Lagging: priority DESC, then watermark ASC, like the PG SQL. The\n // `fair` slots ignore priority so sustained high-priority load can't\n // starve a default-priority stream.\n const fair = fairSlots(lagging);\n const by_priority = [...available].sort(\n (a, b) => b.priority - a.priority || a.at - b.at\n );\n const priority_slice = by_priority.slice(0, lagging - fair);\n const picked = new Set(priority_slice.map((s) => s.stream));\n const fair_slice = [...available]\n .sort((a, b) => a.at - b.at)\n .filter((s) => !picked.has(s.stream))\n .slice(0, fair);\n const lag = [...priority_slice, ...fair_slice].map((s) => ({\n stream: s.stream,\n source: s.source,\n at: s.at,\n lagging: true,\n }));\n const lead = available\n .sort((a, b) => b.at - a.at)\n .slice(0, leading)\n .map((s) => ({\n stream: s.stream,\n source: s.source,\n at: s.at,\n lagging: false,\n }));\n const seen = new Set<string>();\n const combined = [...lag, ...lead].filter((p) => {\n if (seen.has(p.stream)) return false;\n seen.add(p.stream);\n return true;\n });\n return combined\n .map((p) =>\n this._streams.get(p.stream)?.lease({ ...p, by, retry: 0 }, millis)\n )\n .filter((l) => !!l);\n }\n\n async subscribe(\n streams: SubscribeInput[],\n correlated_at?: number,\n correlator?: { key: string; by: string; millis: number }\n ) {\n await sleep();\n\n // The checkpoint is monotonic. With a correlator, position and lease\n // are per key.\n let correlating: boolean | undefined;\n let keyed_at: number | undefined;\n if (correlator) {\n const now = Date.now();\n const held = this._correlators.get(correlator.key);\n // A key with no row yet inherits the shared position, so a new\n // correlator does not re-read history.\n const at = Math.max(held?.at ?? this._correlated_at, correlated_at ?? -1);\n keyed_at = at;\n if (!held || held.until < now || held.by === correlator.by) {\n correlating = true;\n this._correlators.set(correlator.key, {\n at,\n by: correlator.by,\n // Release outright, or a successor in the same ms is still refused.\n until: correlator.millis > 0 ? now + correlator.millis : 0,\n });\n } else {\n // Refused, but the position still advances: this caller may already\n // have scanned and marked.\n correlating = false;\n held.at = at;\n }\n // How far any correlator has read.\n if (correlated_at !== undefined && correlated_at > this._correlated_at)\n this._correlated_at = correlated_at;\n } else if (\n correlated_at !== undefined &&\n correlated_at > this._correlated_at\n )\n this._correlated_at = correlated_at;\n\n let subscribed = 0;\n for (const {\n stream,\n source,\n priority = 0,\n lane = DEFAULT_LANE,\n correlated_at,\n } of streams) {\n const existing = this._streams.get(stream);\n if (existing) {\n existing.resubscribe(priority, lane);\n if (correlated_at !== undefined) existing.mark(correlated_at);\n } else {\n const created = new InMemoryStream(stream, source, priority, lane);\n if (correlated_at !== undefined) created.mark(correlated_at);\n this._streams.set(stream, created);\n subscribed++;\n }\n }\n let watermark = -1;\n for (const s of this._streams.values()) {\n if (s.at > watermark) watermark = s.at;\n }\n return {\n subscribed,\n watermark,\n correlated_at: keyed_at ?? this._correlated_at,\n ...(correlating === undefined ? {} : { correlating }),\n };\n }\n\n async ack(leases: Lease[]) {\n await sleep();\n // One synchronous pass: the in-memory form of the single transaction.\n return leases\n .map((l) => this._streams.get(l.stream)?.ack(l))\n .filter((l) => !!l);\n }\n\n async block(leases: BlockedLease[]) {\n await sleep();\n return leases\n .map((l) => this._streams.get(l.stream)?.block(l, l.error))\n .filter((l) => !!l);\n }\n\n async defer(input: string[] | StreamFilter, deferred_at: number) {\n await sleep();\n const selected = this._select(input);\n for (const s of selected) s.defer(deferred_at);\n return selected.length;\n }\n\n // De-duplicated like the SQL `stream = ANY(...)`; unknown names skipped.\n private _select(input: string[] | StreamFilter): InMemoryStream[] {\n if (Array.isArray(input))\n return [...new Set(input)]\n .map((name) => this._streams.get(name))\n .filter((s) => s !== undefined);\n return [...this._streams.values()].filter(this._filter_predicate(input));\n }\n\n private _filter_predicate(\n filter: StreamFilter\n ): (s: InMemoryStream) => boolean {\n const stream_ok =\n filter.stream !== undefined\n ? name_test(filter.stream, filter.stream_exact)\n : undefined;\n const source_ok =\n filter.source !== undefined\n ? name_test(filter.source, filter.source_exact)\n : undefined;\n return (s) => {\n if (stream_ok && !stream_ok(s.stream)) return false;\n if (source_ok && (s.source === undefined || !source_ok(s.source)))\n return false;\n if (filter.blocked !== undefined && s.blocked !== filter.blocked)\n return false;\n if (filter.lane !== undefined && s.lane !== filter.lane) return false;\n return true;\n };\n }\n\n async reset(input: string[] | StreamFilter) {\n await sleep();\n const selected = this._select(input);\n for (const s of selected) s.reset();\n return selected.length;\n }\n\n async forget_pii(stream: string): Promise<number> {\n await sleep();\n const count = this._pii.get(stream)?.size ?? 0;\n this._pii.delete(stream);\n return count;\n }\n\n async unblock(input: string[] | StreamFilter) {\n await sleep();\n const selected = this._select(\n Array.isArray(input) ? input : { ...input, blocked: true }\n );\n return selected.filter((s) => s.unblock()).length;\n }\n\n async prioritize(filter: StreamFilter, priority: number) {\n await sleep();\n const changed = this._select(filter).filter((s) => s.priority !== priority);\n for (const s of changed) s.prioritize(priority);\n return changed.length;\n }\n\n async query_streams(\n callback: (position: StreamPosition) => void,\n query?: QueryStreams\n ): Promise<QueryStreamsResult> {\n await sleep();\n const limit = query?.limit ?? DEFAULT_QUERY_STREAMS_LIMIT;\n const after = query?.after;\n const source_matches = query?.source_matches;\n const matches = this._filter_predicate(query ?? {});\n // Reverse-match: the stored `source` pattern must match a requested name.\n const reverse_match = (source: string): boolean => {\n const re = new RegExp(source);\n return source_matches!.some((name) => re.test(name));\n };\n\n // The default sort is code-unit order, which agrees with the `<=`\n // cursor and the SQL binary collation; `localeCompare` would not.\n const sorted = [...this._streams.keys()]\n .sort()\n .map((stream) => this._streams.get(stream) as InMemoryStream);\n\n let count = 0;\n for (const s of sorted) {\n if (after !== undefined && s.stream <= after) continue;\n if (!matches(s)) continue;\n if (source_matches !== undefined) {\n // No source consumes every stream, so it matches any name.\n if (s.source && !reverse_match(s.source)) continue;\n }\n // Before emitting, so `limit: 0` returns none.\n if (count >= limit) break;\n callback({\n stream: s.stream,\n source: s.source,\n at: s.at,\n retry: s.retry,\n blocked: s.blocked,\n error: s.error,\n priority: s.priority,\n leased_by: s.leased_by,\n leased_until: s.leased_until,\n lane: s.lane,\n deferred_at: s.deferred_at,\n correlated_at: s.correlated_at,\n });\n count++;\n }\n return { maxEventId: this._events.at(-1)?.id ?? -1, count };\n }\n\n async query_stats<E extends Schemas>(\n input: string[] | Pick<StreamFilter, \"stream\" | \"stream_exact\">,\n options?: QueryStatsOptions<E>\n ): Promise<Map<string, StreamStats<E>>> {\n await sleep();\n const exclude = new Set<string>(options?.exclude ?? []);\n const want_tail = options?.tail ?? false;\n const want_count = options?.count ?? false;\n const want_names = options?.names ?? false;\n const before = options?.before;\n const after = options?.after;\n const limit = options?.limit;\n\n const array_targets = Array.isArray(input) ? new Set(input) : null;\n const stream_ok =\n !Array.isArray(input) && input.stream !== undefined\n ? name_test(input.stream, input.stream_exact)\n : undefined;\n\n const scope_cache = new Map<string, boolean>();\n const in_scope = (stream: string): boolean => {\n const cached = scope_cache.get(stream);\n if (cached !== undefined) return cached;\n const ok = array_targets\n ? array_targets.has(stream)\n : (stream_ok?.(stream) ?? true);\n scope_cache.set(stream, ok);\n return ok;\n };\n\n type Acc = {\n head: Committed<Schemas, keyof Schemas>;\n tail?: Committed<Schemas, keyof Schemas>;\n count: number;\n names?: Record<string, number>;\n };\n const acc = new Map<string, Acc>();\n for (const e of this._events) {\n if (before !== undefined && e.id >= before) continue;\n if (!in_scope(e.stream)) continue;\n if (exclude.has(e.name as string)) continue;\n let a = acc.get(e.stream);\n if (!a) {\n a = { head: e, count: 0 };\n if (want_tail) a.tail = e;\n if (want_names) a.names = {};\n acc.set(e.stream, a);\n }\n a.head = e;\n a.count++;\n if (want_names) {\n const n = String(e.name);\n a.names![n] = (a.names![n] ?? 0) + 1;\n }\n }\n\n // Code-unit order, to agree with the `<=` cursor (see query_streams).\n const ordered = [...acc.keys()].sort();\n const out = new Map<string, StreamStats<E>>();\n for (const stream of ordered) {\n if (limit !== undefined && out.size >= limit) break;\n if (after !== undefined && stream <= after) continue;\n const a = acc.get(stream)!;\n const stats: {\n head: Committed<Schemas, keyof Schemas>;\n tail?: Committed<Schemas, keyof Schemas>;\n count?: number;\n names?: Record<string, number>;\n } = { head: a.head };\n if (want_tail) stats.tail = a.tail;\n if (want_count) stats.count = a.count;\n if (want_names) stats.names = a.names;\n out.set(stream, stats as StreamStats<E>);\n }\n return out;\n }\n\n async truncate(\n targets: Array<{\n stream: string;\n snapshot?: Schema;\n meta?: EventMeta;\n before?: Date;\n max_id?: number;\n }>\n ) {\n await sleep();\n const result = new Map<\n string,\n {\n deleted: number;\n committed: Committed<Schemas, keyof Schemas>;\n before?: Date;\n }\n >();\n\n // Per target, like the SQL adapters: a later target sees what an\n // earlier one in the same call already pruned.\n const windowed = targets.filter((t) => t.before !== undefined);\n for (const { stream, before, max_id } of windowed) {\n let boundary: Committed<Schemas, keyof Schemas> | undefined;\n for (const e of this._events) {\n if (\n e.stream === stream &&\n e.name === SNAP_EVENT &&\n e.created < before! &&\n (max_id === undefined || e.id <= max_id) &&\n (!boundary || e.id > boundary.id)\n )\n boundary = e;\n }\n if (!boundary) continue; // no qualifying snapshot → no-op\n const below = boundary.id;\n let deleted = 0;\n this._events = this._events.filter((e) => {\n if (e.stream !== stream || e.id >= below) return true;\n this._pii.get(stream)?.delete(e.id);\n deleted++;\n return false;\n });\n result.set(stream, { deleted, committed: boundary, before });\n }\n\n const full = targets.filter((t) => t.before === undefined);\n // Per target, like the SQL adapters, so a stream listed twice gets one seed.\n for (const { stream, snapshot, meta } of full) {\n let deleted = 0;\n this._events = this._events.filter((e) => {\n if (e.stream !== stream) return true;\n deleted++;\n return false;\n });\n // Subscriptions untouched; `seed()` reaps inert ones.\n this._stream_versions.delete(stream);\n this._pii.delete(stream);\n const event: Committed<Schemas, keyof Schemas> = {\n id: this._next_id++,\n stream,\n version: 0,\n created: new Date(),\n ...seedEvent(snapshot, meta),\n };\n this._events.push(event);\n this._stream_versions.set(stream, 0);\n result.set(stream, { deleted, committed: event });\n }\n return result;\n }\n\n /** A throw inside the driver restores the captured state. Ids restart at 0. */\n async restore(\n driver: (\n callback: (event: Committed<Schemas, keyof Schemas>) => Promise<number>\n ) => Promise<void>\n ): Promise<void> {\n await sleep();\n const prev_events = this._events;\n const prev_next_id = this._next_id;\n const prev_streams = this._streams;\n const prev_stream_versions = this._stream_versions;\n const prev_pii = this._pii;\n const prev_correlated_at = this._correlated_at;\n const prev_correlators = new Map(\n [...this._correlators].map(([k, v]) => [k, { ...v }])\n );\n // Renumbered from 0, so checkpoints restart too; leases are kept.\n this._correlated_at = -1;\n for (const c of this._correlators.values()) c.at = -1;\n this._events = [];\n this._next_id = 0;\n this._streams = new Map();\n this._stream_versions = new Map();\n this._pii = new Map();\n try {\n await driver(async (event) => {\n const id = this._next_id++;\n // Split `pii` out like `commit`, so `forget_pii` erases restored PII.\n const { pii, ...rest } = event;\n const committed: Committed<Schemas, keyof Schemas> = { ...rest, id };\n this._events.push(committed);\n if (pii != null) this._put_pii(event.stream, id, pii);\n // Last event per stream wins.\n this._stream_versions.set(event.stream, event.version);\n return id;\n });\n } catch (err) {\n this._events = prev_events;\n this._next_id = prev_next_id;\n this._streams = prev_streams;\n this._stream_versions = prev_stream_versions;\n this._pii = prev_pii;\n this._correlated_at = prev_correlated_at;\n this._correlators = prev_correlators;\n throw err;\n }\n }\n}\n","import type { Disposer } from \"./types/index.js\";\n\n/**\n * The shape {@link register_weak_disposer} needs from a `WeakRef`. Declared\n * structurally so tests can hand in a stub that reports its target as\n * collected — real garbage collection is not schedulable, and a guarantee\n * about releasing memory deserves a deterministic test.\n */\nexport type RefLike<T extends object> = { deref: () => T | undefined };\n\n/**\n * A registered cleanup function, optionally tied to the lifetime of the\n * object it cleans up. `ref` present means \"run this only while its target\n * is still reachable\" — the entry holds no strong reference to that target,\n * so registering never keeps it alive. The target is handed back to `run`,\n * so a weak disposer never has to re-check what the registry just proved.\n */\ntype Entry = {\n readonly run: (target: never) => Promise<void>;\n readonly ref?: RefLike<object>;\n};\n\n/** Registered cleanup functions, executed in reverse order during shutdown. */\nconst entries: Entry[] = [];\n\n/**\n * Drop entries whose target has been collected.\n *\n * Called on every registration so the registry stays proportional to the\n * live objects rather than to every object ever built. That matters for apps\n * that mint short-lived Acts — one per tenant, per request, per test — where\n * the registry would otherwise grow without bound for the process lifetime.\n */\nconst prune = (): void => {\n for (let i = entries.length - 1; i >= 0; i--) {\n const { ref } = entries[i];\n if (ref && !ref.deref()) entries.splice(i, 1);\n }\n};\n\n/** Register a cleanup function that runs unconditionally at teardown. */\nexport const register_disposer = (run: Disposer): void => {\n prune();\n entries.push({ run });\n};\n\n/**\n * Register a cleanup function bound to `ref`'s target, held weakly.\n *\n * The registry never retains the target, so an object that becomes\n * unreachable is collectable whether or not it was cleaned up first, and its\n * entry is skipped at teardown — there is nothing left to clean up.\n */\nexport const register_weak_disposer = <T extends object>(\n ref: RefLike<T>,\n run: (target: T) => Promise<void>\n): void => {\n prune();\n entries.push({\n run: run as (target: never) => Promise<void>,\n ref: ref as RefLike<object>,\n });\n};\n\n/**\n * Run every live disposer in reverse registration order, sequentially, so a\n * disposer can rely on later-registered ones having already finished.\n *\n * Registrations are left in place, so a second teardown call re-runs them,\n * exactly as before this registry learned about weak entries.\n */\nexport const run_disposers = async (): Promise<void> => {\n for (const { run, ref } of [...entries].reverse()) {\n if (!ref) {\n await (run as Disposer)();\n continue;\n }\n const target = ref.deref();\n if (target) await (run as (t: object) => Promise<void>)(target);\n }\n};\n","/**\n * The single home for every builder-facing config bag in act-core: one\n * section per bag, each with its `DEFAULT_*` constants, an internal\n * `<Type>OptionsSchema`, the inferred `<Type>Config` and a\n * `resolve<Type>Config`, so misconfiguration throws a `ZodError` at the\n * entry point. Validation rejects only what is broken (`NaN`, `±Infinity`,\n * nonsensical negatives) and never newly rejects a value that works. The\n * rules are in .claude/rules/config-validation.md.\n */\n\nimport { z } from \"zod\";\nimport type {\n ActOptions,\n AutocloseConfig,\n CircuitBreakerOptions,\n} from \"../types/act.js\";\nimport type {\n ActionOptions,\n BackoffOptions,\n DrainOptions,\n FoldOptions,\n LaneConfig,\n ReactionOptions,\n SettleOptions,\n ShutdownOptions,\n} from \"../types/index.js\";\n\n// Backoff — retry pacing. Nested inside the reaction/action bags below; also\n// resolvable on its own (`resolveBackoffConfig`) for direct callers.\n\n/**\n * Rejects an off-union `strategy`, a non-finite/negative `baseMs`, or a\n * non-finite/non-positive `maxMs`. Zod 4's `z.number()` rejects\n * `NaN`/`±Infinity` by default, so `.min(0)` / `.gt(0)` also close the\n * non-finite gap. No `maxMs >= baseMs` constraint — a sub-`baseMs` cap on\n * `exponential` is documented behavior, not an error.\n */\nconst BackoffOptionsSchema = z.object({\n strategy: z.enum([\"fixed\", \"linear\", \"exponential\"]),\n baseMs: z.number().min(0),\n maxMs: z.number().gt(0).optional(),\n jitter: z.boolean().optional(),\n});\n\n/**\n * Retry pacing for a reaction that declares no backoff, and for projections\n * and failed fetches, which have no options. Ten seconds, like the default\n * lease; the lease itself only keeps two workers off one stream.\n */\nexport const DEFAULT_BACKOFF: BackoffOptions = {\n strategy: \"fixed\",\n baseMs: 10_000,\n};\n\n/** Validate a backoff bag, or pass `undefined` through. Throws `ZodError`. */\nexport function resolveBackoffConfig(\n options: BackoffOptions | undefined\n): BackoffOptions | undefined {\n return options === undefined\n ? undefined\n : BackoffOptionsSchema.parse(options);\n}\n\n// Reaction — `.do(handler, options)`. `maxRetries` gates the poison-quarantine\n// decision (`retry >= maxRetries`), so a `NaN` here silently disables\n// `blockOnError` (a poison message retries forever). Validate the whole bag.\n\n/** Retries a reaction gets before it blocks, unless `.do()` says otherwise. */\nexport const DEFAULT_MAX_RETRIES = 3;\n/** Whether a reaction blocks its stream once retries run out. */\nexport const DEFAULT_BLOCK_ON_ERROR = true;\n\nconst ReactionOptionsSchema = z.object({\n blockOnError: z.boolean(),\n maxRetries: z.number().int().min(0),\n backoff: BackoffOptionsSchema.optional(),\n});\n\n/** Resolved reaction options. */\nexport type ReactionConfig = z.infer<typeof ReactionOptionsSchema>;\n\n/** Validate a fully-defaulted reaction options bag. Throws `ZodError`. */\nexport function resolveReactionConfig(\n options: ReactionOptions\n): ReactionConfig {\n return ReactionOptionsSchema.parse(options);\n}\n\n// Action — `.on(entry, options)`. `maxRetries` gates the command retry loop's\n// exit (`attempt >= maxRetries`), so a `NaN` here can spin the loop forever on\n// a contended stream. All fields optional (the argument itself is optional).\n\nconst ActionOptionsSchema = z.object({\n maxRetries: z.number().int().min(0).optional(),\n backoff: BackoffOptionsSchema.optional(),\n});\n\n/** Resolved action options. */\nexport type ActionConfig = z.infer<typeof ActionOptionsSchema>;\n\n/** Validate an action options bag. Throws `ZodError`. */\nexport function resolveActionConfig(options: ActionOptions): ActionConfig {\n return ActionOptionsSchema.parse(options);\n}\n\n// Lane — `.withLane({...})`. Per-lane overrides of the drain budget. Time\n// knobs (`leaseMillis` / `cycleMs`) accept any non-negative ms; `streamLimit`\n// is an integer count. Reject NaN/Infinity/negative; allow 0 (as today).\n\nconst LaneConfigSchema = z.object({\n name: z.string().min(1),\n leaseMillis: z.number().min(0).optional(),\n streamLimit: z.number().int().min(0).optional(),\n cycleMs: z.number().min(0).optional(),\n});\n\n/** Validate a lane config. Throws `ZodError`. Returns the input shape. */\nexport function resolveLaneConfig<TName extends string>(\n options: LaneConfig<TName>\n): LaneConfig<TName> {\n LaneConfigSchema.parse(options);\n return options;\n}\n\n// Drain / Settle — runtime knobs for `drain(...)` / `settle(...)`. Parsed once\n// per call (cheap). Reject NaN/Infinity/negative; allow 0. `correlate` is a\n// pass-through query filter (validated by the store), so it is not reshaped.\n\n/** Streams a drain claims per cycle. */\nexport const DEFAULT_STREAM_LIMIT = 10;\n/** Events a drain reads per stream per cycle. */\nexport const DEFAULT_EVENT_LIMIT = 10;\n/** How long a drain holds a stream's lease (ms). */\nexport const DEFAULT_LEASE_MILLIS = 10_000;\n\nconst DrainOptionsSchema = z.object({\n streamLimit: z.number().int().min(0).optional(),\n eventLimit: z.number().int().min(0).optional(),\n leaseMillis: z.number().min(0).optional(),\n});\n\n/** Validate drain options, or pass `undefined` through. Throws `ZodError`. */\nexport function resolveDrainConfig(\n options: DrainOptions | undefined\n): DrainOptions | undefined {\n if (options === undefined) return undefined;\n DrainOptionsSchema.parse(options);\n return options;\n}\n\nconst SettleOptionsSchema = DrainOptionsSchema.extend({\n debounceMs: z.number().min(0).optional(),\n // `maxPasses` defaults to Infinity (no cap) when omitted; a present value is\n // a non-negative integer — `0` is legal and means \"run no passes\".\n //\n // `Infinity` is accepted explicitly. It is the documented default\n // (`Act.settle`'s doc-comment and `SettleOptions.maxPasses` both name it),\n // and `settle.ts` destructures `maxPasses = Infinity`, so the one value the\n // docs advertise was the one value `z.number().int()` refused — Zod 4\n // rejects `±Infinity`, and an integer check cannot pass it either. Wrapper\n // code normalizing an optional cap (`maxPasses: opts.maxPasses ?? Infinity`)\n // is the natural way to hit it. Accepting it also honors this module's own\n // rule that a value which works today is never newly rejected.\n maxPasses: z\n .union([z.number().int().min(0), z.literal(Number.POSITIVE_INFINITY)])\n .optional(),\n}).loose();\n\n/** Validate settle options, or pass `undefined` through. Throws `ZodError`. */\nexport function resolveSettleConfig(\n options: SettleOptions | undefined\n): SettleOptions | undefined {\n if (options === undefined) return undefined;\n SettleOptionsSchema.parse(options);\n return options;\n}\n\n// Shutdown — the grace budget `shutdown(...)` gives in-flight drain cycles.\n\n/**\n * Ceiling on the derived shutdown grace budget. Without a cap, a lane\n * configured with a long lease (minutes, for a genuinely slow integration)\n * would let one parked handler hold a rolling deploy open for that whole\n * lease. 30s is the longest `leaseMillis` the production checklist\n * recommends, so it is the point past which \"graceful\" stops being the\n * operator's intent.\n */\nexport const MAX_SHUTDOWN_GRACE_MS = 30_000;\n\nconst ShutdownOptionsSchema = z\n .object({\n // `0` is legal and is exactly today's behavior: stop scheduling and\n // return without waiting.\n graceMs: z.number().min(0).optional(),\n })\n .loose();\n\n/** Validate shutdown options, or pass `undefined` through. Throws `ZodError`. */\nexport function resolveShutdownConfig(\n options: ShutdownOptions | undefined\n): ShutdownOptions | undefined {\n if (options === undefined) return undefined;\n ShutdownOptionsSchema.parse(options);\n return options;\n}\n\n// Act — the top-level `act().build(options)` scalar knobs. The nested bags\n// (`autocloseWindow`, `circuitBreaker`) are resolved by their own resolvers\n// below; the `scoped`/`correlator` fields are objects/functions passed\n// through untouched (`.loose()`), so only the scalar knobs are checked here.\n\nconst ActOptionsSchema = z\n .object({\n maxSubscribedStreams: z.number().int().min(1).optional(),\n settleDebounceMs: z.number().int().min(0).optional(),\n autoSettle: z.boolean().optional(),\n })\n .loose();\n\n/** Validate the scalar `ActOptions` knobs at build. Throws `ZodError`. */\nexport function resolveActConfig(\n options: ActOptions | undefined\n): ActOptions | undefined {\n if (options === undefined) return undefined;\n ActOptionsSchema.parse(options);\n return options;\n}\n\n// Autoclose — the off-hours window on `ActOptions`.\n// The window *logic* (DST, hour math) stays in `autoclose-window.ts`; only\n// the schema, defaults, and resolver live here.\n\n/** Default IANA zone for `autocloseWindow` when the operator omits one. */\nconst DEFAULT_AUTOCLOSE_WINDOW_TZ = \"UTC\";\n\n/** True when `tz` is a zone the runtime's `Intl` accepts. */\nfunction is_valid_time_zone(tz: string): boolean {\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone: tz });\n return true;\n } catch {\n return false;\n }\n}\n\nconst AutocloseWindowSchema = z\n .object({\n start: z\n .number()\n .int()\n .min(0)\n .max(23, { message: \"autocloseWindow.start must be an hour in [0, 23]\" }),\n end: z\n .number()\n .int()\n .min(0)\n .max(23, { message: \"autocloseWindow.end must be an hour in [0, 23]\" }),\n timeZone: z\n .string()\n .refine(is_valid_time_zone, {\n message: \"autocloseWindow.timeZone must be a valid IANA time zone\",\n })\n .default(DEFAULT_AUTOCLOSE_WINDOW_TZ),\n })\n .refine((w) => w.start !== w.end, {\n message:\n \"autocloseWindow.start and end must differ — an empty window disables autoclose\",\n });\n\nconst AutocloseOptionsSchema = z.object({\n autocloseWindow: AutocloseWindowSchema.optional(),\n}) satisfies z.ZodType<AutocloseConfig>;\n\n/** Validate + default the autoclose knobs on `ActOptions`. Throws `ZodError`. */\nexport function resolveAutocloseConfig(\n options: ActOptions | undefined\n): AutocloseConfig {\n return AutocloseOptionsSchema.parse({\n autocloseWindow: options?.autocloseWindow,\n });\n}\n\n// CircuitBreaker — the store-op breaker on `ActOptions`. The `CircuitBreaker`\n// state machine stays in `circuit-breaker.ts` and imports the resolved type.\n\nconst DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;\nconst DEFAULT_CIRCUIT_COOLDOWN_MS = 30_000;\n\nconst CircuitBreakerOptionsSchema = z.object({\n failureThreshold: z\n .number()\n .int()\n .min(1)\n .default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),\n cooldownMs: z\n .number()\n .int()\n .min(100)\n .max(3_600_000)\n .default(DEFAULT_CIRCUIT_COOLDOWN_MS),\n}) satisfies z.ZodType<CircuitBreakerConfig, CircuitBreakerOptions>;\n\n/** Resolved circuit-breaker configuration. */\nexport type CircuitBreakerConfig = Required<CircuitBreakerOptions>;\n\n/** Parse + apply defaults. Throws `ZodError` on out-of-range values. */\nexport const resolveCircuitBreakerConfig = (\n options?: CircuitBreakerOptions\n): CircuitBreakerConfig => CircuitBreakerOptionsSchema.parse(options ?? {});\n\n// Fold — `projection(name).of(state)` batch-fold knobs.\n\nconst DEFAULT_FOLD_FLUSH_EVERY = 1_000;\nconst DEFAULT_MAX_CACHED_STATES = 10_000;\n\nconst FoldOptionsSchema = z.object({\n flushEvery: z.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),\n maxCachedStates: z.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES),\n});\n\n/** Resolved fold configuration. */\nexport type FoldConfig = z.infer<typeof FoldOptionsSchema>;\n\n/** Validate + default the fold knobs. Throws `ZodError`. */\nexport function resolveFoldConfig(options: FoldOptions): FoldConfig {\n return FoldOptionsSchema.parse(options);\n}\n\n/**\n * Refuses a store written for `commit(stream, msgs, meta, expectedVersion)`,\n * the shape before 2.0. Left alone, a wrapper of that shape forwards its\n * arguments, so commits work, and then treats the appends as a stream name,\n * sending events and their sensitive fields wherever that name goes.\n */\nexport function check_store(store: { commit: { length: number } }): void {\n if (store.commit.length === 4)\n throw new Error(\n \"This store's commit takes (stream, msgs, meta, expectedVersion), the shape before @rotorsoft/act 2.0. Upgrade it, and every @rotorsoft/act-* package, together: https://rotorsoft.github.io/act-root/docs/guides/migrating-to-2.x\"\n );\n}\n","/**\n * @module scoped\n * @category Internal\n *\n * Ambient execution context — every `AsyncLocalStorage` the framework owns\n * lives here, and so does every operation on one. No other module calls\n * `.run()` or `.getStore()`; they ask for a runner or a reader instead, so\n * this file is the single place to look at the abstraction.\n *\n * Two contexts:\n *\n * - **ports** — the active Act's store/cache bag, so `store()` / `cache()`\n * resolve per-Act rather than per-process. Installed by the\n * orchestrator via {@link make_run_scoped}, read by the port singletons\n * via {@link current_ports}.\n * - **reaction** — the event a handler is processing, so a dispatch made\n * anywhere inside that handler can thread `reactingTo` whichever `IAct`\n * reference made the call. Installed via {@link run_reacting},\n * read at the orchestrator boundary via {@link current_reacting}.\n *\n * Keeping the mechanics here keeps ambient state out of `internal/`, whose\n * modules are stateless implementations that receive what they need.\n *\n * @internal\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { Actor, Committed, IAct, Schemas, Scoped } from \"./types/index.js\";\n\n/**\n * What an Act actually installs in its frame: the ports a caller passed,\n * plus per-Act settings that runtime code has to read from the *running*\n * Act rather than from whatever closure it was built in.\n *\n * `autoclose_window` is here because the autoclose reactions are\n * synthesized once into the shared registry and handed by reference to\n * every Act built from that builder, so a window captured at synthesis is\n * the first tenant's window for everyone. The frame is per-Act, so\n * reading it here is what makes the setting per-Act too.\n *\n * Wider than {@link Scoped} on purpose: `Scoped` types what a caller\n * *passes* through `ActOptions.scoped` and is public; this is what the\n * orchestrator *installs* and is not.\n *\n * @internal\n */\nexport type ActFrame = Scoped & {\n readonly autoclose_window?: {\n readonly start: number;\n readonly end: number;\n readonly timeZone: string;\n };\n};\n\n/**\n * AsyncLocalStorage carrying the active Act's ports.\n *\n * Exported for in-repo tooling that measures the context itself (the\n * scope-overhead bench). NOT public: `ports.ts` no longer re-exports it and\n * `index.ts` reaches this module only for the `Scoped` type, so it is not\n * importable from the package. Everything else uses the helpers below.\n */\nexport const scoped = new AsyncLocalStorage<ActFrame>();\n\n/**\n * The reaction currently running, as a box the handler can empty.\n *\n * A frame captured by work the handler started and did not await outlives the\n * handler and can never be unbound. The *frame* is unreclaimable, but the\n * *box inside it* is not: clearing `event` as the handler settles turns every\n * later read through that frame into \"no reaction\", without anyone having to\n * ask a second question.\n *\n * Not reachable from `index.ts`.\n */\ntype Reacting = { event: Committed<Schemas, string> | undefined };\n\nconst reacting = new AsyncLocalStorage<Reacting>();\n\n/**\n * Builds the runner an Act uses to enter its own port scope.\n *\n * Every Act gets a frame, including one built without `ActOptions.scoped` —\n * that one carries the singleton adapters (see `unscoped_ports` in `ports.ts`).\n * Entering unconditionally is what makes an Act's ports its own: a runner that\n * collapsed to `fn()` for a singleton Act did not leave whatever frame it was\n * called from, so dispatching into a shared Act from inside a tenant's handler\n * resolved `store()` to that tenant and wrote the shared Act's events into the\n * tenant's log.\n *\n * @internal\n */\nexport function make_run_scoped(\n bag: ActFrame\n): <T>(fn: () => Promise<T>) => Promise<T> {\n return (fn) => scoped.run(bag, fn);\n}\n\n/**\n * The running Act's off-hours autoclose window, or `undefined` when it\n * declared none (or when read outside any Act).\n *\n * Read at resolution time rather than captured at synthesis: the autoclose\n * reactions belong to the shared registry, and only the frame knows which\n * Act is running them.\n *\n * @internal\n */\nexport function current_autoclose_window(): ActFrame[\"autoclose_window\"] {\n return scoped.getStore()?.autoclose_window;\n}\n\n/**\n * The active Act's ports, or `undefined` outside any Act.\n *\n * Every Act runs in a frame, so this is `undefined` only for a call made\n * outside one — `store()` and `cache()` fall back to the singleton adapters\n * there, which is what a bare `store()` in application setup expects.\n *\n * @internal\n */\nexport function current_ports(): Scoped | undefined {\n return scoped.getStore();\n}\n\n/**\n * Runs `fn` with `event` installed as the triggering-event context.\n *\n * Entered per payload rather than per lease: the context has to unwind with\n * the handler so it never reaches the drain cycle, and work a handler started\n * without awaiting has to resume into its own event's frame.\n *\n * @internal\n */\nfunction run_reacting<T>(\n event: Committed<Schemas, string>,\n fn: () => Promise<T>\n): Promise<T> {\n const box: Reacting = { event };\n return reacting.run(box, async () => {\n try {\n return await fn();\n } finally {\n // Anything still running keeps this frame; from here it reads empty.\n box.event = undefined;\n }\n });\n}\n\n/**\n * The event being reacted to, or `undefined` outside a running handler —\n * including inside work that outlived one.\n *\n * @internal\n */\nexport function current_reacting(): Committed<Schemas, string> | undefined {\n return reacting.getStore()?.event;\n}\n\n/**\n * Everything a reaction handler runs inside: the `IAct` facade it is handed\n * as its third argument, and the triggering-event context that every dispatch\n * made within it resolves — including one through a captured `app`.\n *\n * Built here rather than in the dispatcher so the dispatcher never has to\n * know how either half works; it receives this whole and calls it.\n *\n * @internal\n */\nexport type ReactionScope<\n TEvents extends Schemas,\n TActions extends Schemas,\n TActor extends Actor = Actor,\n> = {\n readonly app: IAct<TEvents, TActions, TActor>;\n readonly run: <T>(\n event: Committed<Schemas, string>,\n fn: () => Promise<T>\n ) => Promise<T>;\n};\n\n/**\n * Assembles the reaction scope from the orchestrator's bound `IAct` methods.\n *\n * @internal\n */\nexport function make_reaction_scope<\n TEvents extends Schemas,\n TActions extends Schemas,\n TActor extends Actor = Actor,\n>(\n app: IAct<TEvents, TActions, TActor>\n): ReactionScope<TEvents, TActions, TActor> {\n return { app, run: run_reacting };\n}\n","import { ConsoleLogger } from \"./adapters/console-logger.js\";\nimport { InMemoryCache } from \"./adapters/in-memory-cache.js\";\nimport { InMemoryStore } from \"./adapters/in-memory-store.js\";\nimport { config } from \"./config.js\";\nimport { register_disposer, run_disposers } from \"./disposers.js\";\nimport { check_store } from \"./internal/config.js\";\nimport { current_ports } from \"./scoped.js\";\nimport type {\n Cache,\n Disposable,\n Disposer,\n ExitCode,\n Logger,\n Scoped,\n Store,\n} from \"./types/index.js\";\n\n/**\n * Port/adapter infrastructure for the Act framework.\n *\n * All infrastructure concerns (logging, storage, caching) are managed as\n * singleton adapters injected via port functions. Each port follows the same\n * pattern: first call wins with a sensible default, optional adapter injection.\n *\n * - `log()` — structured logging (default: ConsoleLogger)\n * - `store()` — event persistence (default: InMemoryStore)\n * - `cache()` — state checkpoints (default: InMemoryCache)\n * - `dispose()` — register cleanup functions for graceful shutdown\n *\n * @module ports\n */\n\n// ---------------------------------------------------------------------------\n// Port factory\n// ---------------------------------------------------------------------------\n\n/**\n * Factory function that creates or returns the injected adapter.\n * @internal\n */\ntype Injector<Port extends Disposable> = (adapter?: Port) => Port;\n\n/** Singleton adapter registry, keyed by injector function name. */\nconst adapters = new Map<string, Disposable>();\n\n/**\n * Creates a singleton port with optional adapter injection.\n *\n * The first call initializes the adapter (using the provided adapter or the\n * injector's default). Subsequent calls return the cached singleton. Adapters\n * are disposed in reverse registration order during {@link disposeAndExit}.\n *\n * @param injector - Named function that creates the default adapter\n * @returns Port function: call with no args to get the singleton, or pass an\n * adapter on the first call to override the default\n *\n * @example\n * ```typescript\n * const store = port(function store(adapter?: Store) {\n * return adapter || new InMemoryStore();\n * });\n * const s = store(); // InMemoryStore\n * ```\n */\nexport function port<Port extends Disposable>(injector: Injector<Port>) {\n return (adapter?: Port): Port => {\n if (!adapters.has(injector.name)) {\n const injected = injector(adapter);\n adapters.set(injector.name, injected);\n // log() is now in adapters (or this IS the log port we just registered),\n // so the recursive call resolves immediately. Routing through the logger\n // means level gating (e.g. silenced in tests at fatal) takes effect.\n log().info(`[act] + ${injector.name}:${injected.constructor.name}`);\n }\n return adapters.get(injector.name) as Port;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Ports: log, store, cache\n// ---------------------------------------------------------------------------\n\n/**\n * Gets or injects the singleton logger.\n *\n * By default, Act uses a built-in {@link ConsoleLogger} that emits JSON lines\n * in production (compatible with GCP, AWS CloudWatch, Datadog) and colorized\n * output in development — zero external dependencies.\n *\n * For pino, inject a `PinoLogger` from `@rotorsoft/act-pino` before building\n * your application.\n *\n * @param adapter - Optional logger implementation to inject\n * @returns The singleton logger instance\n *\n * @example Default console logger\n * ```typescript\n * import { log } from \"@rotorsoft/act\";\n * const logger = log();\n * logger.info(\"Application started\");\n * ```\n *\n * @see {@link Logger} for the interface contract\n * @see {@link ConsoleLogger} for the default implementation\n */\nexport const log = port(function log(adapter?: Logger) {\n const cfg = config();\n return (\n adapter ||\n new ConsoleLogger({\n level: cfg.logLevel,\n pretty: cfg.env !== \"production\",\n })\n );\n});\n\n/**\n * Gets or injects the singleton event store.\n *\n * By default, Act uses an {@link InMemoryStore} suitable for development and\n * testing. For production, inject a persistent store like `PostgresStore` from\n * `@rotorsoft/act-pg` before building your application.\n *\n * **Important:** Store injection must happen before creating any Act instances.\n * Once set, the store cannot be changed without restarting the application.\n *\n * @param adapter - Optional store implementation to inject\n * @returns The singleton store instance\n *\n * @example Default in-memory store\n * ```typescript\n * import { store } from \"@rotorsoft/act\";\n * const s = store();\n * ```\n *\n * @see {@link Store} for the interface contract\n * @see {@link InMemoryStore} for the default implementation\n */\n// ALS check lives outside `port()` — its cache fires only once, so the\n// per-call branch on a scoped Act has to be in the public wrapper.\nconst _store = port(function store(adapter?: Store): Store {\n if (adapter) check_store(adapter);\n return adapter ?? new InMemoryStore();\n});\n\nexport const store = ((adapter?: Store): Store => {\n return current_ports()?.store ?? _store(adapter);\n}) as (adapter?: Store) => Store;\n\n/**\n * Gets or injects the singleton cache.\n *\n * By default, Act uses an {@link InMemoryCache} (LRU, maxSize 1000). For\n * distributed deployments, inject a Redis-backed cache before building your\n * application.\n *\n * @param adapter - Optional cache implementation to inject\n * @returns The singleton cache instance\n *\n * @see {@link Cache} for the interface contract\n * @see {@link InMemoryCache} for the default implementation\n */\nconst _cache = port(function cache(adapter?: Cache) {\n return adapter ?? new InMemoryCache();\n});\n\nexport const cache = ((adapter?: Cache): Cache => {\n return current_ports()?.cache ?? _cache(adapter);\n}) as (adapter?: Cache) => Cache;\n\n/**\n * The ports bag an Act without `ActOptions.scoped` runs in: the singleton\n * adapters, read through the same frame a scoped Act uses.\n *\n * Every Act entering a frame is what keeps its ports its own. Without one, a\n * shared Act called from inside a tenant's handler inherited that tenant's\n * frame and committed to the tenant's store.\n *\n * The properties are getters on purpose. The adapters are resolved lazily and\n * injected after `act().build()` in the normal case — `store(new PgStore())`\n * in application setup, or a test's `beforeEach` — so capturing them when the\n * bag is made would pin whatever existed at build time. They read the raw\n * resolvers rather than the public `store()`/`cache()`, which consult the\n * frame this bag *is* and would recurse.\n */\nexport const unscoped_ports = (): Scoped => DEFAULT_SCOPE;\n\nconst DEFAULT_SCOPE: Scoped = {\n get store() {\n return _store();\n },\n get cache() {\n return _cache();\n },\n};\n\n// ---------------------------------------------------------------------------\n// Disposal\n// ---------------------------------------------------------------------------\n\n/**\n * Registered cleanup functions live in `disposers.ts`, which holds\n * lifetime-bound entries weakly so registering never pins its target for the\n * process lifetime. The public surface here is unchanged.\n */\n\n/**\n * Disposes all registered adapters and disposers, then exits the process.\n *\n * Execution order:\n * 1. Custom disposers (registered via {@link dispose}) — in reverse order\n * 2. Port adapters (log, store, cache) — in reverse registration order\n * 3. Adapter registry is cleared\n * 4. Process exits (skipped in test environment)\n *\n * In production, `\"ERROR\"` exits are silently ignored to avoid crashing on\n * transient failures (e.g. an uncaught promise in a non-critical path).\n *\n * @param code - Exit code: `\"EXIT\"` for clean shutdown (exit 0),\n * `\"ERROR\"` for abnormal termination (exit 1)\n */\nexport async function disposeAndExit(code: ExitCode = \"EXIT\"): Promise<void> {\n if (code === \"ERROR\" && config().env === \"production\") {\n // Surface the swallow so incident triage can see it. Without this\n // log the framework looks unresponsive after an uncaught exception\n // in prod — exactly when operators most need a breadcrumb.\n log().warn(\n \"disposeAndExit('ERROR') ignored in production — process kept alive\"\n );\n return;\n }\n\n // Run sequentially in reverse registration order so a disposer can rely on\n // later-registered disposers (and adapters on later-registered adapters)\n // having already finished — Promise.all would race them.\n await run_disposers();\n for (const adapter of [...adapters.values()].reverse()) {\n await adapter.dispose();\n log().info(`[act] - ${adapter.constructor.name}`);\n }\n adapters.clear();\n config().env !== \"test\" && process.exit(code === \"ERROR\" ? 1 : 0);\n}\n\n/**\n * Registers a cleanup function for graceful shutdown.\n *\n * Disposers are called automatically on SIGINT, SIGTERM, uncaught exceptions,\n * and unhandled rejections. They execute in reverse registration order before\n * port adapters are disposed.\n *\n * @param disposer - Async function to call during cleanup. Omit to get a\n * reference to {@link disposeAndExit} without registering.\n * @returns Function to manually trigger disposal and exit\n *\n * @example\n * ```typescript\n * import { dispose } from \"@rotorsoft/act\";\n *\n * const db = connectDatabase();\n * dispose(async () => await db.close());\n *\n * // In tests\n * afterAll(async () => await dispose()());\n * ```\n *\n * @see {@link disposeAndExit} for the full shutdown sequence\n */\nexport function dispose(\n disposer?: Disposer\n): (code?: ExitCode) => Promise<void> {\n disposer && register_disposer(disposer);\n return disposeAndExit;\n}\n"]}