Sign in

    incr

    Salsa-inspired incremental recomputation library with automatic dependency tracking, backdating, and durability-based verification skipping

    incremental
    salsa
    memoization
    reactive
    dependency-tracking
    backdating
    durability
    Download zip
    Author
    Version
    0.9.0
    License
    Apache-2.0
    Last updated
    4 months ago
    Downloads
    31K

    #incr

    A Salsa- and Build Systems à la Carte-inspired incremental recomputation library for MoonBit.

    incr tracks dependencies automatically, memoizes derived values, and skips unnecessary work when inputs change — so you write straight-line code and the runtime figures out what to recompute.

    Salsa informs the demand-driven incremental recomputation model. Build Systems à la Carte informs the separation between task meaning, store/trace data, scheduler, and rebuilder strategy; see the build-oriented boundary design and internal evaluation boundaries.

    Core primitives:

    • Input[T] — input cells you write to directly
    • Derived[T] — derived computations that memoize and auto-track dependencies
    • DerivedMap[K, V] — keyed memoization, one derived value per key, created lazily
    • Backdating + durability — skip downstream work when values didn't really change or inputs rarely do

    Advanced features (push-reactive EagerDerived[T] / Effect, reachable lazy ReachableDerived[T], field-level InputField[T], side-channel Accumulator[T], Datalog Relation / MapRelation / fixpoint, batching, cycle-safe reads) are covered in docs/.

    Naming note: the target facade names above are the recommended API. Legacy compatibility names remain available during migration: Signal, Memo, HybridMemo, Reactive, MemoMap, TrackedCell, and Database. The naming direction is recorded in ADR 2026-05-21.

    #Live practical demo

    Want to see the runtime before reading the API? Try the live typed spreadsheet.

    Edit one cell. The sheet routes the edit through MoonBit operations, formula cells track the other cells they read, and trace/evidence panels separate formulas that recomputed from values that actually changed. It is a small example of the same shape behind editor state, build-like pipelines, and reactive app models: derived values stay fresh without recalculating everything.

    #Installation

    Add incr to the import list of your moon.pkg.json:

    { "import": ["dowdiness/incr"] }

    The core library packages use only moonbitlang/core; optional repository demos may declare extra dependencies such as Rabbita.

    #Quick Start

    let rt = Runtime()

    // Create inputs
    let x = Input(rt, 10, label="x")
    let y = Input(rt, 20, label="y")

    // Create derived computations
    let sum = Derived(rt, () => x.get() + y.get(), label="sum")

    // `.get()` is only legal inside another derived computation.
    // Outside the graph, use `read_or_abort()` or `read()`.
    inspect(sum.read_or_abort(), content="30")

    // Update an input — downstream derived values recompute on the next read
    x.set(5)
    inspect(sum.read_or_abort(), content="25")

    When a group of cells or long-lived reads shares a lifetime, construct cells through a Scope (scope.input(...), scope.derived(...)) and register watches with scope.add_watch(...) so one scope.dispose() tears the group down. See Getting Started.

    Note on the example above: It is nocheck because the snippet omits imports and a test wrapper. The same construction is checked in docs/target_api_examples.mbt.md and exercised end-to-end by tests/quickstart_test.mbt — if you edit the example, update those in lockstep.

    #Mental Model

    incr has three coordinated modes:

    • Pull-first by default: Input + Derived verify recorded dependency traces lazily on read.
    • Push-first when requested: EagerDerived + Effect recompute eagerly when upstream inputs change.
    • Hybrid reachability: ReachableDerived is still lazy like Derived, but stays reachable through downstream push subscribers and Watch roots.

    In the vocabulary of "Build Systems à la Carte", the pull engine is a suspending scheduler plus a revision-based verifying trace rebuilder:

    • A Derived compute closure is the task.
    • The runtime records the dependencies read by the last successful compute.
    • An Input::set(...) bumps a revision; it does not eagerly recompute the pull graph.
    • A later Derived::read() / get() verifies the recorded trace on demand.
    • If no dependency changed, the compute closure does not run — the green path.
    • If a dependency changed, the closure reruns and records a new trace — the red path.
    • If the red path produces an equal value, backdating preserves changed_at, so downstream derived values still skip work.

    The push engine uses a different contract: EagerDerived computes immediately at construction, then recomputes during Input::set(...) / batch commit propagation in topological-level order. Reads return the already-maintained cached value.

    Hard guarantees to rely on:

    • Dynamic dependencies are supported: conditional reads replace the dependency trace on each successful recompute.
    • Failed computations and cycle errors do not install a new valid trace.
    • Pull mode verifies traces, not dirty flags; push dirty state is not the source of truth for Derived correctness.
    • ReachableDerived is hybrid only in reachability/GC behavior; recomputation is still the same lazy revision check as Derived.
    • Cross-session/content-addressed caching is not automatic. See the constructive traces feasibility note for why it remains opt-in research.

    #Which mode should I use?

    NeedUse
    Default cached computation, especially if it may not be read after every input writeDerived
    One memoized value per semantic key, created lazilyDerivedMap
    Field-level invalidation inside a larger objectInputField
    UI-facing value that should stay eagerly current after input writesEagerDerived
    Side effect that should run eagerly when dependencies changeEffect
    Lazy derived value that must stay alive through downstream push subscribers or long-lived watchesReachableDerived
    Relational/fixpoint computationRelation / MapRelation

    When unsure, start with Derived. Move to EagerDerived only when the consumer really benefits from push-first maintenance.

    #Learn More

    Full documentation index: docs/README.md.

    #Development

    moon check # Type-check the workspace moon build # Build the workspace moon test # Run all workspace tests (~878 test blocks) moon bench # Run benchmarks (always pass --release for representative numbers)

    Contributor and coding-agent guidance lives in AGENTS.md.

    #Supported targets

    Builds and tests pass on the WASM-GC backend (the default for moon test). Other MoonBit backends are not currently exercised in CI; treat them as unverified.

    #License

    Apache-2.0

    AcceptStatus

    Status of a single committed-revision transition through the accept gate. Mirrors the spec's state-machine Status column.

    AcceptedDerived

    A success-gated derived value. Construct with AcceptedDerived::AcceptedDerived (owns its candidate compute), AcceptedDerived::from_candidate (wraps an existing candidate Derived), or Scope::accepted_derived — all V : Eq. For candidate values that are not Eq but carry a Revision, use the BackdateEq tier (AcceptedDerived::accepted_memo / Scope::accepted_memo), which gates acceptance by revision identity.

    AcceptedSnapshot

    A coherent view of one committed revision: the current candidate result, the retained accepted value, and the transition status. current carries the domain Result[V, E] (the read-error channel lives on the accessor return type, not here).

    Accumulator

    A side-channel collector for values pushed by memo compute closures.

    Typed buffers (per_memo, prev_push_sets) live on the handle; runtime holds only type-erased closures in SlotMeta. See the design doc docs/superpowers/specs/2026-04-19-accumulator-api-design.md.

    AccumulatorId

    A unique identifier for an Accumulator in the runtime.

    Monotonically allocated by the runtime; never reused across dispose/new. Stale references to disposed accumulators resolve to "slot disposed" at verify time rather than aliasing to a recycled slot.

    Each AccumulatorId is scoped to its originating runtime via runtime_id.

    BackdateEq

    A backdate-aware equality check for memo outputs.

    The default implementation compares changed_at revisions: two values are considered "backdate equal" (i.e., the memo should not advance its changed_at) when both carry the same revision stamp. Override to provide custom logic.

    Supertrait

    Requires HasChangedAt — the type must expose a changed_at revision.

    CellId

    A unique identifier for a cell (signal or memo) in the runtime.

    Cell IDs are monotonically increasing integers allocated by the runtime. They serve as direct array indices for O(1) cell lookup.

    Each CellId is scoped to its originating runtime via runtime_id. This prevents cells from different runtimes being incorrectly queried against each other.

    CellInfo

    Structured metadata about a cell in the dependency graph.

    This structure provides a uniform view of both Signal and Memo cells. For signals, the dependencies array will be empty.

    Fields

    • label: Optional human-readable name for debugging and introspection
    • id: The unique identifier for this cell
    • changed_at: When this cell's value last actually changed
    • verified_at: When this cell was last confirmed up-to-date
    • durability: How often this cell is expected to change
    • dependencies: Cell IDs this cell depends on (empty for signals)

    CycleError

    Error type for cycle detection during memo computation.

    Returned by get_result() methods when a memo transitively depends on itself. Use cell(), path(), and format_path() to inspect or render the cycle. Labels are snapshotted at construction time so format_path is pure-value — rendering needs no runtime handle and reflects the cell labels at the moment the cycle was detected, even if those cells are later renamed or disposed.

    Example

    match memo.get_result() {
    Ok(value) => println("Got: " + value.to_string())
    Err(err) => {
    println("Cycle at cell " + err.cell().to_string())
    println(err.format_path())
    }
    }

    Derived

    Target-name lazy derived cell facade over Memo.

    DerivedAbortedEvent

    Event emitted when a derived recompute raises a catchable error.

    DerivedCompletedEvent

    Event emitted when a derived recompute completes successfully.

    DerivedEnteringEvent

    Event emitted when a derived recompute starts.

    DerivedEvent

    Derived recompute lifecycle event delivered by Runtime::on_derived_event.

    DerivedMap

    Target-name keyed derived facade over MemoMap.

    Durability

    Classifies how often an input is expected to change.

    Durability enables a powerful optimization: when only low-durability inputs change, memos that depend solely on high-durability inputs skip verification entirely.

    Levels

    • Low: Frequently changing values (user input, source text)
    • Medium: Moderately stable values
    • High: Rarely changing values (configuration, schemas)

    Inherited Durability

    Memos inherit the minimum durability of their dependencies. A memo depending on both Low and High durability signals has Low durability.

    EagerDerived

    Target-name eager derived cell facade over Reactive.

    Effect

    User-facing push-mode effect.

    Runs the provided function immediately on creation to establish dependencies, then re-runs whenever any dependency changes.

    FunctionalRelation

    A Datalog functional relation: a key-value map with delta tracking for fixpoint evaluation.

    Unlike Relation[T] (a HashSet), FunctionalRelation[K, V] uses @hashmap.HashMap[K, V] and supports replacing values for existing keys.

    Three layers:
    • current — materialized post-drain (readable via get()/iter())
    • delta — frontier for current fixpoint iteration
    • staged_delta — staging buffer during fixpoint

    • insert() adds to frontier delta outside fixpoint, staged delta during fixpoint
    • get() reads current (the materialized post-drain map)
    • iter() iterates current; records a dependency for pull verification
    • delta_iter() iterates delta; used by rule bodies

    FunctionalRelationId

    GcRole

    Categorizes a cell's role in garbage collection.

    • Source: Input cells (signals, relations) — no upstream deps, never collected
    • Interior: Derived cells (memos, reactives) — has deps, collectible when unobserved
    • Root: Terminal cells (effects) — keeps upstream alive, never collected

    HasChangedAt

    A type that carries its own changed_at revision stamp.

    Implement this on value types that embed a Revision tracking when their content last changed. Used in combination with BackdateEq to enable O(1) backdate decisions based on revision comparison rather than O(N) structural equality.

    HybridMemo

    User-facing hybrid memo cell.

    Verifies lazily on get(). When verified_at >= current_revision, get() returns the cached value immediately without any dep walk. Staleness is detected purely via the revision counter — no dirty flag.

    Input

    Target-name input cell facade over Signal.

    InputField

    Target-name field-level input facade over TrackedCell.

    InternId

    A stable integer key returned by InternTable::intern.

    Same value always maps to the same InternId within a table. IDs are monotonically increasing and can be used as array indices or MemoMap keys.

    InternTable

    A grow-only interning table that assigns stable InternId keys to values.

    Interning guarantees that equal values always receive the same InternId, enabling cheap identity comparison (id1 == id2) instead of deep structural equality. Used by the bidirectional type-checker as MemoMap keys for cross-revision cache stability.

    MapRelation

    Target-name facade for a Datalog functional relation.

    Memo

    A derived computation with automatic dependency tracking and memoization.

    Memos are the interior nodes of the dependency graph. They compute a value from other signals and memos, cache the result, and only recompute when their dependencies actually change.

    Key Features

    • Lazy evaluation: The compute function is not called until get().
    • Automatic tracking: Dependencies are discovered by intercepting get() calls.
    • Memoization: Results are cached; repeated get() calls return the cached value.
    • Backdating: If a recomputation produces the same value, downstream memos skip work.

    Example

    let rt = Runtime()
    let a = Signal(rt, 10)
    let b = Signal(rt, 20)
    let sum = Memo(rt, () => a.get() + b.get())
    let observer = sum.observe()

    inspect(observer.get(), content="30") // Computes and caches

    a.set(15)
    inspect(observer.get(), content="35") // Recomputes because `a` changed
    observer.dispose()

    MemoAbortedEvent

    using @dowdiness/incr/cells { type DerivedAbortedEvent as MemoAbortedEvent }

    Deprecated alias of DerivedAbortedEvent. Renamed in 0.8.0.

    MemoCompletedEvent

    using @dowdiness/incr/cells { type DerivedCompletedEvent as MemoCompletedEvent }

    Deprecated alias of DerivedCompletedEvent. Renamed in 0.8.0.

    MemoEnteringEvent

    using @dowdiness/incr/cells { type DerivedEnteringEvent as MemoEnteringEvent }

    Deprecated alias of DerivedEnteringEvent. Renamed in 0.8.0.

    MemoEvent

    using @dowdiness/incr/cells { type DerivedEvent as MemoEvent }

    Deprecated alias of DerivedEvent. Renamed in 0.8.0; kept for source compatibility and scheduled for removal in a future major release.

    MemoId

    MemoMap

    A keyed memoization map backed by one Memo per key.

    MemoMap[K, V] provides a minimal parameterized-query pattern: each key gets its own lazily-created Memo[V], and subsequent reads of that key reuse the cached memoized value.

    Reading a key requires K : Hash + Eq and V : Eq, matching the underlying HashMap lookup and Memo value semantics.

    Example

    let rt = Runtime()
    let source = Signal(rt, 10)
    let by_id = MemoMap(rt, (id : Int) => source.get() + id)

    inspect(by_id.get(1), content="11")
    inspect(by_id.get(1), content="11") // cache hit for key=1
    inspect(by_id.get(2), content="12") // separate memo for key=2

    Observer

    A typed keep-alive handle for reading computed values from outside the dependency graph.

    Observer is NOT a cell -- it has no SoA slot, no CellRef variant, and no subscriber set. It is a lightweight ref-counted handle that:
    1. Increments gc_root_counts for the target cell (keeps it alive during gc)
    2. Provides a typed getter for reading the cell's value
    3. Decrements gc_root_counts on dispose (allows gc to collect)

    ReachableDerived

    Target-name reachable lazy derived cell facade over HybridMemo.

    Reactive

    User-facing push-mode reactive cell.

    Recomputed eagerly when any upstream cell changes. Value is cached; consecutive reads without upstream changes do not trigger recomputation.

    ReactiveId

    ReadError

    Error type for the public read channel.

    A read asks for a stored graph snapshot, so the only failures it can report are mechanism failures — intrinsic to the read itself and unrecoverable by any domain reader:

    • Cycle — the cell transitively depends on itself.
    • Disposed — the cell being read has been disposed.

    Domain fallibility (a parse error, a validation failure) is not a read error: it belongs in the value as Result[V, E] (see Derived::fallible), where it is cached, change-detected, and replayed like any other value. A retained compute raise Failure is a defect, not a domain error, and still aborts.

    Cross-runtime misuse and strict-context misuse are programmer defects and abort rather than surfacing here.

    See docs/design/specs/2026-05-28-honest-read-error-ownership.md.

    Relation

    A Datalog relation: a set of tuples with delta tracking for fixpoint evaluation.

    Relation[T] holds typed current and delta sets via Ref. The Runtime sees only type-erased closures on RelationData.

    • insert() adds to frontier delta outside fixpoint, and to staged delta during fixpoint
    • contains() checks current (the materialized post-drain set)
    • iter() iterates current; records a dependency for pull verification
    • delta_iter() iterates delta; used by rule bodies

    RelationId

    Revision

    A monotonically increasing revision counter representing the system's logical clock.

    Revisions are bumped each time an input signal changes. Every cell tracks two revision timestamps:

    • changed_at: When this cell's value last actually changed
    • verified_at: When this cell was last confirmed up-to-date

    A cell is stale when verified_at < current_revision. A cell has changed (relative to an observer) when changed_at > observer.verified_at.

    RuleId

    Runtime

    Central coordinator for the incremental computation framework.

    The Runtime manages all bookkeeping for dependency tracking, revision counting, and batch operations. Every Signal and Memo is associated with exactly one Runtime.

    Pointer chase tradeoff

    All sub-struct field accesses (e.g. self.core.revision.current_revision) require one extra pointer dereference compared to the old flat layout. MoonBit structs are heap-allocated, so RuntimeCore is a separate heap object. In hot loops (verify dep-walk, push propagation), the sub-struct pointers are cache-hot since they are dereferenced on every iteration.

    Example

    let rt = Runtime()

    let x = Signal(rt, 10)

    let doubled = Memo(rt, () => x.get() * 2)

    RuntimeId

    A unique identifier for a Runtime.

    Runtime ids are monotonically allocated when a Runtime is created and scope every CellId / AccumulatorId to its originating runtime.

    A RuntimeId is an introspection / debug identity: compare it for equality ("are these two runtimes the same?"), use it as a map key, or display it. It is not a stable application key across program runs, and carries no ordering or arithmetic meaning — that is why it is a nominal wrapper rather than a bare Int.

    Scope

    Hierarchical cell ownership with bulk disposal.

    Scope owns cells and child scopes. Disposing a scope disposes all owned cells and children recursively. This enables UI component lifecycle patterns: create cells during mount, dispose the scope on unmount.

    Disposal Order

    1. Children (bottom-up, recursively)
    2. Dispose hooks (empty in Layer 2; populated by Scope::observe in Layer 4)
    3. Owned cells

    Example

    let scope = Scope::new(rt) let local = scope.signal(42) let derived = scope.memo(fn() { local.get() * 2 }) // Component unmounts — one cleanup call scope.dispose()

    Signal

    An input cell with an externally-set value.

    Signals are the leaves of the dependency graph — values you control directly. The T: Eq constraint enables same-value optimization: setting a signal to its current value is a no-op (no revision bump, no downstream recomputation).

    Example

    let rt = Runtime()
    let count = Signal(rt, 0)
    count.set(5)
    inspect(count.get(), content="5")

    SignalId

    TrackedCell

    A named, tracked input cell — a thin wrapper around Signal[T].

    TrackedCell provides the same functionality as Signal but is intended for use cases where you want a more structured, named input cell. All operations delegate directly to the inner Signal.

    Example

    let rt = Runtime()
    let cell = TrackedCell(rt, 0, label="counter")
    cell.set(5)
    inspect(cell.get(), content="5")

    Watch

    A typed keep-alive handle that reads Result values from outside the graph.

    Watch has the same GC-rooting semantics as Observer, but preserves read errors as values for target-facade read APIs.

    Database

    pub(open) trait Database {
    fn runtime(Self) ->
    Runtime

    }

    An incremental database providing access to its runtime.

    This is the core trait that all incremental systems must implement. It connects user-defined database types to the incr runtime, enabling generic helper functions like create_signal, create_memo, and create_memo_map.

    This trait corresponds to Salsa's #[salsa::db] trait, but without macro magic — users implement it explicitly.

    Example

    struct MyDb {
    rt : @incr.Runtime
    }

    impl @incr.Database for MyDb with fn runtime(self) {
    self.rt
    }

    Freshness

    pub(open) trait Freshness {
    fn is_fresh(Self) -> Bool
    }

    Target-name freshness trait for readable incremental nodes.

    Freshness mirrors Readable during the compatibility window while using target API vocabulary. Use it for new generic code that only needs to ask whether a handle is current with its runtime's latest revision.
    impl Freshness for Derived[T]
    impl Freshness for Input[T]
    impl Freshness for InputField[T]

    InputFieldOwner

    pub(open) trait InputFieldOwner {
    fn cell_ids(Self) -> Array[
    CellId
    ]
    }

    Target-name contract for types that own InputField fields.

    The returned cell IDs must be stable across calls and belong to the runtime of any scope they are registered with.

    Readable

    pub(open) trait Readable {
    fn is_up_to_date(Self) -> Bool
    }

    A readable incremental node (Signal or Memo).

    This trait provides a common interface for checking whether a node's cached value is current with the latest revision. Both Signal and Memo implement this trait.

    Use the trait bound in generic functions to write code that works with any readable node:

    fn[R : Readable] check(node : R) -> Bool {
    node.is_up_to_date()
    }
    impl Readable for HybridMemo[T]
    impl Readable for Memo[T]
    impl Readable for Signal[T]
    impl Readable for TrackedCell[T]

    RuntimeContext

    pub(open) trait RuntimeContext {
    fn runtime(Self) ->
    Runtime

    }

    A lightweight provider for target-facade constructors.

    RuntimeContext intentionally mirrors Database::runtime without changing the existing Database trait or its compatibility helper surface.

    Trackable

    pub(open) trait Trackable {
    fn cell_ids(Self) -> Array[
    CellId
    ]
    }

    Contract for types that contain TrackedCell fields.

    Implementing this trait declares that a type manages field-level incremental state. The trait requires a single method that returns all CellIds owned by the struct, enabling the runtime to perform operations like GC root scanning and bulk introspection.

    A future MoonBit macro system could auto-derive this trait.

    The ordering of CellIds returned by cell_ids() must be stable across calls (i.e., always return fields in the same order).

    DURABILITY_COUNT

    let DURABILITY_COUNT : Int

    The number of durability levels (3: Low, Medium, High).

    Must equal Durability::High.index() + 1. Adding a new Durability variant requires updating both Durability::index() and this constant; failing to do so will silently under-size the durability_last_changed array in Runtime, causing incorrect fast-path revision checks.

    MAX_CYCLE_DISPLAY_STEPS

    let MAX_CYCLE_DISPLAY_STEPS : Int

    Maximum number of path entries rendered by CycleError::format_path, and the number of labels snapshotted at error-construction time.

    Cycles longer than this are truncated in the formatted output with "→ ...", but CycleError::path() still returns the full untruncated sequence.

    add_input_fields

    fn[T : InputFieldOwner] add_input_fields(scope :
    Scope
    , owner : T) -> Unit

    Registers all cells from an InputFieldOwner with a scope for bulk disposal.

    add_tracked

    fn[T : Trackable] add_tracked(scope :
    Scope
    , tracked : T) -> Unit

    Registers all cells from a Trackable struct with a scope for bulk disposal.

    When the scope is disposed, all registered cells are disposed. This is the recommended way to manage TrackedCell lifetimes.

    Example

    let scope = Scope::new(rt)
    let tracked = MyTracked::new(rt)
    add_tracked(scope, tracked)
    scope.dispose() // disposes all tracked cells

    batch

    fn[Db : Database] batch(db : Db, f : () -> Unit raise?) -> Unit raise?

    Executes a batch of signal updates using the database's runtime.

    All signal updates inside the closure are deferred until the batch ends, resulting in a single revision bump. If the closure raises, pending writes are rolled back and the error is re-raised.

    Parameters

    • db: Any type implementing Database
    • f: The closure containing signal updates

    batch_result

    fn[Db : Database] batch_result(db : Db, f : () -> Unit raise?) -> Result[Unit, Error]

    Runs a batch and returns raised errors as Result.

    create_accumulator

    fn[Db : Database, T : Eq] create_accumulator(db : Db, label? : String) ->
    Accumulator
    [T]

    Creates an accumulator bound to the database's underlying runtime. Convenience mirror of create_signal / create_memo.

    create_derived

    fn[Ctx : RuntimeContext, T : Eq] create_derived(ctx : Ctx, f : () -> T raise Failure, label? : String) ->
    Derived
    [T]

    Creates a target-name lazy derived facade using the context runtime.

    create_derived_map

    fn[Ctx : RuntimeContext, K : Hash + Eq, V] create_derived_map(ctx : Ctx, f : (K) -> V raise Failure, label? : String) ->
    DerivedMap
    [K, V]

    Creates a target-name keyed derived facade using the context runtime.

    create_eager_derived

    fn[Ctx : RuntimeContext, T : Eq] create_eager_derived(ctx : Ctx, compute : () -> T) ->
    EagerDerived
    [T]

    Creates a target-name eager derived facade using the context runtime.

    create_hybrid_memo

    fn[Db : Database, T : Eq] create_hybrid_memo(db : Db, f : () -> T raise Failure, label? : String) ->
    HybridMemo
    [T]

    Creates a new hybrid memo using the database's runtime.

    HybridMemo[T] uses the same lazy revision-based verification as Memo[T] — there is no separate dirty flag. The "hybrid" refers to reachability: the memo participates in push_reachable_count, so a live Reactive or Effect observer downstream keeps the memo and its upstream cells alive across Runtime::gc().

    Use HybridMemo when:
    • The memo sits between push-reactive nodes and pull-based memos
    • You want push notifications to reach downstream reactives without eagerly recomputing intermediate derived values

    Parameters

    • db: Any type implementing Database
    • f: The compute function for the hybrid memo
    • label: An optional human-readable name for debugging

    Returns

    A new hybrid memo associated with the database's runtime

    create_input

    fn[Ctx : RuntimeContext, T] create_input(ctx : Ctx, value : T, durability? :
    Durability
    , label? : String) ->
    Input
    [T]

    Creates a target-name input facade using the context runtime.

    create_input_field

    fn[Ctx : RuntimeContext, T] create_input_field(ctx : Ctx, value : T, durability? :
    Durability
    , label? : String) ->
    InputField
    [T]

    Creates a target-name input-field facade using the context runtime.

    create_memo

    fn[Db : Database, T : Eq] create_memo(db : Db, f : () -> T raise Failure, label? : String) ->
    Memo
    [T]

    Creates a new memo using the database's runtime.

    Uses Memo::new internally — requires T : Eq for backdating via structural equality. For other backdating strategies, use the Memo constructors directly:
    • Memo::new_memo for BackdateEq-based (revision) backdating
    • Memo::new_no_backdate for no backdating (no trait constraint on T)

    Parameters

    • db: Any type implementing Database
    • f: The compute function for the memo
    • label: An optional human-readable name for debugging

    Returns

    A new memo associated with the database's runtime

    create_memo_map

    fn[Db : Database, K : Hash + Eq, V] create_memo_map(db : Db, f : (K) -> V raise Failure, label? : String) ->
    MemoMap
    [K, V]

    Creates a new keyed memoization map using the database's runtime.

    Each key gets its own lazily-created memo. Repeated reads of the same key reuse that key's cached memoized value.

    Parameters

    • db: Any type implementing Database
    • f: The compute function for each key
    • label: An optional label applied to each per-key memo

    Returns

    A new memo map associated with the database's runtime

    create_reachable_derived

    fn[Ctx : RuntimeContext, T : Eq] create_reachable_derived(ctx : Ctx, f : () -> T raise Failure, label? : String) ->
    ReachableDerived
    [T]

    Creates a target-name reachable lazy derived facade using the context runtime.

    create_scope

    fn[Db : Database] create_scope(db : Db) ->
    Scope

    Creates a new root scope using the database's runtime.

    Cells created via the scope's constructors are automatically disposed when the scope is disposed. Use scope.child() for nested scopes.

    Parameters

    • db: Any type implementing Database

    Returns

    A new root scope associated with the database's runtime

    create_signal

    fn[Db : Database, T] create_signal(db : Db, value : T, durability? :
    Durability
    , label? : String) ->
    Signal
    [T]

    Creates a new signal using the database's runtime.

    Parameters

    • db: Any type implementing Database
    • value: The initial value of the signal
    • durability: How often this signal is expected to change (default: Low)
    • label: An optional human-readable name for debugging

    Returns

    A new signal associated with the database's runtime

    create_tracked_cell

    fn[Db : Database, T] create_tracked_cell(db : Db, value : T, durability? :
    Durability
    , label? : String) ->
    TrackedCell
    [T]

    Creates a new TrackedCell using the database's runtime.

    Follows the same pattern as create_signal and create_memo.

    Parameters

    • db: Any type implementing Database
    • value: The initial value of the TrackedCell
    • durability: How often this cell is expected to change (default: Low)
    • label: An optional human-readable name for debugging

    Returns

    A new TrackedCell associated with the database's runtime

    gc_tracked

    #deprecated("Use `add_tracked(scope, tracked)` instead — `gc_tracked` is a no-op kept for source compatibility")
    fn[T : Trackable] gc_tracked(rt :
    Runtime
    , tracked : T) -> Unit

    Deprecated: Use add_tracked(scope, tracked) instead for lifecycle management.

    This function was originally intended to mark TrackedCell fields as GC roots. However, TrackedCells are Sources with no downstream dependency edges, so marking them as roots keeps nothing alive. Use add_tracked(scope, tracked) to register all of a struct's TrackedCells with a scope for bulk disposal.

    This function is a no-op and will be removed in a future version.

    Source Files