/** * Generic per-instance **observe** bus. Observe-class * services (devtools inspector, audit, sync-dirty notification) register * handlers against named lifecycle points instead of the kernel naming each * service. Mirrors the registry pattern of {@link WriteHookRegistry} but is * internal and keyed by lifecycle point. * * OBSERVE SEMANTICS: handlers react to a write that already happened. A * handler throw is warned, not propagated — it can never abort a write. Write- * *gating* services (guards, periods) need a throw-propagating gate bus. * Add observe points by extending {@link LifecycleEventMap}. Write-*gating* * services use the sibling gate API on this same class * (`registerGate`/`dispatchGate`, throw-propagating); see {@link GateEventMap}. * * @module */ import type { WriteEvent } from './write-hooks.js'; import type { Role } from '../../kernel/types.js'; /** Typed map of OBSERVE lifecycle point → event payload. Extend by adding keys. */ export interface LifecycleEventMap { afterPut: WriteEvent; afterDelete: WriteEvent; } export type LifecyclePoint = keyof LifecycleEventMap; export type BusHandler
= (event: LifecycleEventMap[P]) => void | Promise = (event: GateEventMap[P]) => void | Promise (point: P, handler: BusHandler ): Unsubscribe;
/** Cheap gate for the write path — true when any handler is registered for the point. */
hasHandlers(point: LifecyclePoint): boolean;
/**
* True while one or more dispatches are in flight. Backed by a depth counter
* so that two concurrent async dispatches (`Promise.all([put('a'), put('b')])`
* each captured `busAfterPut=true` at their respective put() tops while depth
* was 0) both proceed independently — the counter stays > 0 until BOTH finish,
* so any nested write attempted by a handler still sees `dispatching === true`
* and is suppressed by the write-path gate in `collection.ts`
* (`busAfterPut = hasHandlers('afterPut') && !dispatching`). Re-entrancy
* suppression lives exclusively on that write-path gate; concurrent independent
* dispatches must not drop each other's events.
*/
get dispatching(): boolean;
/**
* Dispatch in registration order, awaited. Per-handler errors are warned, not
* thrown — an observe handler must never abort a completed write. A
* re-entrancy guard suppresses nested firing so a handler that itself writes
* cannot loop (same rationale as WriteHookRegistry.#suppressed).
*/
dispatch (point: P, event: LifecycleEventMap[P]): Promise (point: P, handler: GateHandler , opts?: GateRegisterOptions): Unsubscribe;
/** Cheap gate for the write path — true when any gate handler is registered for the point. */
hasGateHandlers(point: GatePoint): boolean;
/**
* True when at least one gate handler at `point` needs the prior record
* (the default). False when the point has no handlers or every handler
* registered with `needsPrior: false` — the write path then skips the
* prior-read before dispatching (#267 prior-read elision).
*/
gateNeedsPrior(point: GatePoint): boolean;
/**
* Run gate handlers in registration order, awaited. Unlike `dispatch`
* (observe), a handler throw is NOT swallowed — it PROPAGATES, aborting the
* write before it reaches the store. The first throw stops the remaining
* handlers (fail-fast). This is the seam guards/periods migrate onto.
*
* Note: gate handlers are validators that read, not write. A gate handler
* that writes back into the same collection would re-enter the write path
* and re-dispatch this point; loop-suppression for that case is deferred to
* the migration slice (contract: gate handlers must not perform writes that
* re-trigger their own point).
*/
dispatchGate (point: P, event: GateEventMap[P]): Promise