Skip to main content

blackbox 0.10.11 copy "blackbox: ^0.10.11" to clipboard
blackbox: ^0.10.11 copied to clipboard

State management with one law: output = compute(input, state). Boxes with private memory, an explicit dependency graph you can render as a map, built-in persistence and caching. No code generation.

0.10.11 #

  • Effects are traced, and named. addEffect(..., name: 'spectrum') — the name labels the effect on the map (effect_1{{spectrum}}) and in the trace; unnamed effects are numbered in registration order. PumpTrace.effects carries an EffectTrace per effect: ran (with the input run received and the previous one), unchanged, or waiting. Multiboxes are traced too (BoxTrace.input — a multibox has no single output, so its computed line shows the input it was given). Paid for by a field session where an effect oscillated through the device it commanded: the loop closed outside the graph and the map could not show it — the trace is the place where the same effect running with alternating inputs becomes visible.
  • Virtual time is a contract. ClockBox tests moved to fake_async; a graph built inside fakeAsync runs entirely on virtual time — timers and pumps alike, including moments asked for lazily inside a wire. ARCHITECTURE.md "Time is delivered, never read" now carries the recipe.
  • Occurrences: ask what the box remembers. The chapter "Events are values with a sequence number" is withdrawn and rewritten. The field had hand-written the seq counter in three places and read it as a missing primitive; it was a missing question. An occurrence is modeled as the state the box keeps about it — a reason (StopReason), a count since a reset point (failures, zeroed on playing), the last one stamped by the device (DateTime? lastTare) — and, when several answers are needed at once, a sealed state machine with a pure next(state, event) and one line of glue. Deliberately no event cell and no StateMachineBox. doc/coffee-brewing.md follows suit (tared → lastTare).
  • DebounceBox<T> and HysteresisBox. The two time-shaped nodes the field kept hand-rolling (a metadata debouncer in the app, a five-second hysteresis in the player plugin). DebounceBox(window): the input once it has stopped changing for window — changes faster than the window collapse into the last; the first value passes at once; a value that never rests never gets out, by contract. HysteresisBox({on, off}): a bool whose switching up and down costs different time (off: 3s — up at once, down after three quiet seconds; on: 300ms — a spinner shown only if loading lasts); off from birth; on == off is a bool debounce. Both are ordinary boxes: a node on the map, traced, on virtual time in tests. The one subtlety — an instant switch catches memory up in a microtask, not a zero timer, or a fast dip slips through — lives in the library.
  • A timer is a button pressed by time. New ARCHITECTURE.md chapter: every time-shaped thing in the model is a Timer acting as a button — on a public cell (ClockBox), on private memory (DebounceBox, HysteresisBox), on the device (a fade). Three rules (field, cancelled at the top of compute and in dispose; no now() in compute; the callback never writes output), four cheaper checks to run before reaching for a delay (dedup, the key, intent, harmless flicker). No wire combinator, no default delay: a delay is a node. Sub-chapter The ring through the device: the loop an effect closes through the hardware it commands (spectrum ↔ player rebuild), the criterion (the input holds a value the effect's own command can change), and the two exits — intent instead of measurement, then HysteresisBox. ClockBox now owns every timer that delivers time as a value, not every timer.

0.10.10 #

  • ClockBox — the schedule lives in the subscription. The library home of "time is delivered, never read": one self-driven node owns every timer and hands time out as keyed cells, so each reader names the moments it wants and pays only for those.
    • at(moment) → a cell that is false until the moment, true from then on: one pump, at the boundary; a past moment is true from birth (a late reader sees a fired alarm, not a missed event).
    • every(period) → a cell carrying the latest tick's time: wiring it into the graph is choosing to pay one pump per tick; UI that only displays ticking subscribes via listen/BoxObserver and costs the graph nothing.
    • Cells are memoized by their key (honest slot keys); deliberately no after(Duration) — "after" hides its epoch, and a hidden epoch is a dishonest key. Compute the deadline from a delivered timestamp and ask for at.
    • Born from the coffee modeling session (doc/coffee-brewing.md): its StageClock observer, promoted the moment the field asked for a per-reader schedule.
  • ARCHITECTURE.md: "Time is delivered, never read" now names ClockBox as the clock's one owner, with the reader/cost table.

0.10.9 #

  • Docs: three contracts that were only readable in source are now in ARCHITECTURE.md — paid for by a modeling session (a brew-by-wire coffee application) that had to dig for every one of them:
    • A multibox cell is born ready — child(initial) has a value from construction, so onlyWhenReady on a cell never gates. Absence is a value the cell itself speaks (nullable reading, a phase cell); a real gate belongs to a box, never to a cell.
    • Borrowed wires are free — the first resolver word that touches a foreign source (another graph's box, a multibox cell) lazily registers it as a live dependency. add(...) is the driving verb; reading needs no declaration.
    • Pipeline's "first value" is literal — a nullable "null until done" result completes the run immediately; the result step's output must be the answer (non-nullable, input-gated). And run-constant parameters ride as context:, read via d.context.

0.10.8 #

  • Pipeline: optional: renamed to onFailure: FailurePolicy.fail | .skip — the contract in the name. The flag decides exactly one thing: what a step's standing error (after retries) does to the run. Whether readers proceed without the step was never this flag's business — that is the wire's word (onlyWhenReady / whenReadyOrNull / outputOf). "Optional" wrongly claimed the readers' side of the contract.
  • PipelineBuilder.add no longer forwards the wire-level onError handler: one error concept per pipeline — retries and onFailure at the step, reading policy at the wire.

0.10.7 #

  • Pipeline reborn: the graph as a function. Pipeline extends Graph — same wires, same map, same own(...), plus exactly one verb. start() is overridden to run once and return Future<R>; a second call returns the same future. The old two-class runner (run() + separate lifecycle) is replaced whole.
    • build(result: ...) — the result step is required by signature; a result that was never add-ed throws at build.
    • Fail-fast: a required step's AsyncError (after retries) completes the run with that error — the old version hung forever on an intermediate failure.
    • retry: n per step — the runner re-drives the step's refresh().
    • optional: true per step — its failure doesn't fail the run; whether readers proceed without it is the wire's word (whenReadyOrNull / outputOf).
    • timeout: belongs to the assembly — a pipeline that can hang is misassembled.
    • Auto-dispose in every outcome; own(...)-ed clients released.
  • ARCHITECTURE.md: "The graph as a function" chapter with the RAG example (the pipeline was the prototype the model grew from — returned home as a run mode), and the readiness-vs-nullability table in Flows (whenReadyOrNull on a nullable source is ambiguous — use onlyWhenReady or outputOf).

0.10.6 #

  • whenReady renamed to onlyWhenReady (old name deprecated, removal in 1.0): the name now carries the contract — not ready → the WHOLE node skips the pump. One required wire gates the entire snapshot; choosing the word per wire is choosing the node's policy.
  • d.outputOf(source) — the fold primitive: the source's phase (loading/error/data) as a value in the snapshot. Replaces the hand-rolled "whenReadyOrNull for the edge + .output for the status" incantation every fold grew. Wires carry values, never live objects.
  • Output classes now compare by content (==/hashCode; AsyncError compares by error + previousData, stack trace excluded) — phases in input records deduplicate pumps like any other value.
  • ARCHITECTURE.md: the Flows chapter — the kids-blocks taxonomy (Box / splitter / the fold as reverse splitter, whose fan-in lives in the wires because that is where the map is), the three resolver words as three answers to "what arrives when a source is not ready", the auth-wizard fold example, and why handing a live box into an input is a closed door.
  • FlowBox deprecation text rewritten to the honest history: it was the reverse splitter from before record inputs existed; record inputs absorbed its job. Its per-source maps also could not express priorities between sources.

0.10.5 #

  • The visibility rule, enforced and drawn. From a library review: "reading a public output is allowed; hiding a reactive link is not."
    • toMermaid() now draws borrowed sources (read via whenReady, declared on another graph) dashed; owned ones stay solid — the map shows what is owned and what is only watched.
    • Debug guard for the lifetime rule: wiring a box whose owning graph was disposed prints a loud warning (once per source) — a frozen reader is no longer silent.
    • FlowBox / FlowBoxBuilder / FlowState deprecated, removal in 1.0: FlowBox subscribes to sources directly, bypassing the graph — a second way to express a dependency, with edges invisible on the map. Its use case is an ordinary Box with a record input built from wires. Zero usages found in the field.
    • ARCHITECTURE.md: the visibility rule chapter (including the read-vs-command refinement: boxes may not react outside their input; commanding belongs to facades, passed per call). The module chapter now prescribes flat graphs with module-functions — the inner-graph module pattern did not survive the field and is withdrawn.

0.10.4 (docs) #

  • Field corrections to the Shared truth chapters, from the app that applied 0.10.3 whole:
    • .late readiness is decided by the output type: nullable output → ready immediately with null (null is a value); consumers only wait on a non-nullable output without initialValue.
    • The driving module takes the shared truth as a required constructor parameter — a ?? PremiumBox.late(...) default is a silent mode switch that can split the app into two truths without tripping the one-declarer guard.
    • updateInputForTest is the canonical way to move a shared truth in tests: the test plays the driving graph.
    • The "typed input for effects" pattern rewritten as a warning: its own canonical example was deleted in the field once every field failed the ring criterion. Such a box is scaffolding with a demolition date, not architecture.

0.10.3 #

  • One declarer per box: declaring a box (add/addMultiBox) on a second live graph now throws — declaring means driving the input and owning dispose, and the law allows one writer. Reading a foreign box via whenReady remains a subscription and stays legal. Re-declaring after the previous owner's dispose is allowed. Closes the API gap found by the "one fact, three graphs" field report.
  • ARCHITECTURE.md grew the missing chapters, all field-tested twice: the top floor (shared truth lives above the modules), shared-truth lifecycle (created above / driven by one / read by all, with the reader-nature table: graph → wire, non-graph → listen, other process → disk), feedback rings through memory, trigger-in-input vs condition-in-run, the coordinator (decision-as-state), time is delivered never read, "don't know" is a value, debug is data, no optimism, loud emptiness, external readers, and what a finished app map looks like.

0.10.2 #

  • Ownership fix: graph.dispose() now disposes only what was declared on the builder (add/addMultiBox). Sources registered lazily — whenReady on another graph's box — are unsubscribed, never disposed: they belong to whoever declared them. Cross-graph reads are now safe by construction (field report from a two-graph catalog/player app that had to route them through manual subscriptions).
  • ARCHITECTURE.md: three new field-proven rules — the three-roles question (cache / truth / small thing of its own), honest slot keys (if the answer depends on a value, the value is in the key; keys hold only things with ==), and the typed-input-for-effects pattern.

0.10.1 #

  • Docs now ship inside the package: doc/MODEL.md (the law), doc/ARCHITECTURE.md (the seven-floor application pattern), doc/MIGRATION.md (0.8/0.9 migration + script) — readable offline from the pub cache, greppable by tooling.
  • README: "Coming from Riverpod" and "Coming from MobX" mapping tables — port the roles (truth → cells, fetch → Cache, formula → compute), not the lines.
  • Fixed a stale Cache docstring that still showed the pre-0.10 spelling.

0.10.0 — the subtraction release #

Breaking. The road to 1.0 is subtraction; this release is most of it. The law is now the whole signature: output = compute(input, state).

  • previous is gone from every compute and the MultiBox compute: compute(input), compute() for no-input boxes, void compute(input) for multiboxes. Three field reports showed previous lying (two computes in one frame) and every real box that needed "what did I show before" ended up owning that memory anyway. Migration: delete the parameter; if you actually used it, hold the memory in a state(...) cell (boxes) or a private field (multiboxes).
  • Deprecated legacy deleted: Persisted, AsyncPersisted, AsyncManagedCache, ManagedCache mixins, ValueStateBox/valueBox, LateAsyncBox, and the deprecated MultiBox.track() alias. Their replacements (state(persist:), CachedBox/CachedValueBox + Cache, AsyncBox.late, connect) have been the documented spelling since 0.9.x. On-disk data is untouched — the envelope format and keys are the same, so persisted values written by old mixins are read by state cells and caches as before.
  • docs/MODEL.md status updated: the released API now matches the contract verbatim.

0.9.5 #

  • graph.box<T>() — type lookup for places where context.box is unavailable (background isolates, tests, composition code). Loud by design: throws on a missing type and on twins. Promoted from a hand-rolled extension in a production app.
  • MultiBox.input getter — the current input, mirroring Box.input: readable from buttons, listeners and helpers (inside compute it already holds the new input). Kills the manual mirror field (_streamUrl) every real multibox grew.
  • docs/ARCHITECTURE.md — the seven-floor application pattern (runtime / skeleton / module / leaf / effects / projection / UI), the resource ladder, the events-as-numbered-values pattern, ports and the headless graph. Distilled from a production radio app.
  • Feature freeze: from here to 1.0 the plan is subtraction only — remove previous from compute signatures, drop the deprecated legacy, add lints that guard the law.

0.9.4 #

  • addMultiBox no longer requires input: — omit it for a self-driven module (MultiBox<void> or nullable input) that lives off its own streams: the graph delivers a single null input on the first pump, so compute runs exactly once to start the module. .addMultiBox(billing) replaces the last dummy in the system, input: (d) {}. Omitting input: for a non-nullable input type asserts loudly.

0.9.3 #

  • graph.own(release) — the graph becomes the owner of resources created for it (HTTP clients, databases, files): they are released in dispose(), after every box has stopped, in reverse registration order. Kills the composition-wrapper class whose only job was to remember two close() calls: buildApp(ctx)..own(client.close). Own only what was created for the graph — injected gateways belong to their creators. Extends the existing rule (graph dies → everything its own dies) to non-box resources; no new concept.
  • MultiBox.track() is deprecated and gone from the public vocabulary. One resource rule for the whole library: streams feeding cells go through connect() (auto-released before the next compute and on dispose, as before); everything else lives in a field and is released at the top of compute and in dispose — same as in ordinary boxes. Also removes the name collision with UI read-tracking.

0.9.2 #

  • Box.late(initialValue:) / AsyncBox.late(initialValue:) (and CachedBox.late / CachedValueBox.late) — create a box without an input; the graph delivers the first one. Kills the dummy-input problem: no more MyBox(input: (config: null, ...)) seeds in composition roots. Until the first input a sync late box shows initialValue (or null for nullable outputs) or has no output yet (dependents wait, value throws a clear StateError); an async late box shows AsyncLoading/AsyncData(initialValue). Field report from a real radio app's ~280-line AppBox.
  • graph.boxes — boxes declared on the builder (add/addMultiBox), in declaration order. The provider list in one line: BoxProvider.multi(boxes: graph.boxes, child: ...) — no hand-rolled exports list to keep in sync.
  • LateAsyncBox is deprecated — extend AsyncBox and use the super.late(initialValue:) constructor; same behavior, one class fewer.

0.9.1 #

  • Added ProvidableBox — the common marker of everything deliverable through a BoxProvider. OutputSource and MultiBox implement it, so a composite (e.g. a player) goes into BoxProvider and comes out via context.box<PlayerBox>() like any other box — no second delivery mechanism (custom InheritedWidget) needed. Field report from a real audio-player app.

0.9.0 #

  • The model (docs/MODEL.md): a box is output = compute(input, state) — input is what it is given, state is what it remembers, output is what it shows; one writer per thing.
  • Added state(...) / StateCell<T> — declared box memory: a write re-runs compute and emits (equal writes are no-ops); persist: binds a global storage slot, persistFor: (input) => key binds a slot per input (re-slots on input change — no cross-slot leaks by construction); codec: overrides the registry locally.
  • Added CachedBox / NoInputCachedBox — an async box whose compute is a cached fetch. One Cache(ttl: ..., persist: 'menu') declaration replaces the AsyncPersisted + AsyncManagedCache mixin pair; the contract lives in the names (fetch = "go get fresh, the cache decides when"; compute on a cached box is sealed). In-memory TTL by default; persist: adds a disk slot (instant cold start, disk timestamp drives expiration); persistFor: (input) => key keeps one slot per input; refresh()/invalidateCache() are now available on every async box. Plain async boxes cannot declare a cache — only the named classes can. Mixins remain as the legacy spelling with identical semantics and on-disk format.
  • Added protected input getter on sync and async boxes — actions read the current input instead of caching it into fields inside compute.
  • Cells work on async boxes too: a write re-runs the async compute (emitting AsyncLoading with the previous data first) — the search-box pattern; persist:/persistFor: behave as on sync boxes.
  • Added CachedValueBox — the sync twin of CachedBox (the ManagedCache semantics, declaratively): the value is always readable synchronously starting from initialValue/the disk slot, fetch runs in the background, fetch errors are swallowed. refresh(), invalidateCache(), TTL-on-access included.
  • action(...) now batches: cell writes inside it emit once at the end (on async boxes — for the synchronous part of the body).
  • codecFor falls back to identity for nullable primitives and to an assignable registered codec (e.g. a Service codec serves Service?).
  • Breaking: removed FlowBox.state getter (collided with the state(...) declarator) — use value.
  • Graph tools:
    • graph.settled() — resolves when synchronous propagation is done; the test-friendly replacement for microtask-flushing loops.
    • graph.toMermaid() — renders the dependency graph as a Mermaid flowchart (edges recorded during pumps; multibox ownership dashed).
    • Pump-storm detector: a dependency cycle (or a box emitting a never-equal value each recompute) no longer freezes the isolate — after a bounded number of consecutive pump cycles the graph stops pumping and throws a diagnostic StateError.
  • Deprecated the legacy spelling (removal before 1.0): Persisted, AsyncPersisted, AsyncManagedCache, ManagedCache — each replaced by one declaration (state(persist:), CachedBox, CachedValueBox). Example apps ported; on-disk data stays compatible.
  • README rewritten around the model (three things, one law, three words); migration table from the 0.8 mixins included.
  • MultiBox.connect(stream, cell, {map}) — the one-word form of the dominant compute pattern (stream → output cell): subscribes, maps, dispatches, and auto-releases on the next input cycle and on dispose. Asserts on a type mismatch when map is omitted.
  • MultiBox.child(initial) now declares a ChildCell — the outward twin of a state cell: an ordinary OutputSource the graph/UI observe, with no public setter (only the owning multibox writes via dispatch); distinct by default; disposed with the multibox. ValueStateBox / valueBox are deprecated (child(initial) replaces the composite-leaf use; state(...) replaces the internal-memory use); dispatchAsync removed — an async child belongs in the graph, fed by a multibox output.

0.8.0 #

  • Breaking / Fixed (persistence):
    • Restore precedence is now uniform: a disk-cached value wins over initialValue for Persisted and AsyncPersisted, matching ManagedCache. initialValue is only the first-boot fallback. Previously initialValue silently shadowed the persisted value.
    • A persist-key change (persistKeyFor returning a new key after an input change) now re-initializes the box in the new slot: onFirstCompute runs again with the new slot's cached value (or null for an empty slot), and the old slot's value no longer leaks into — or gets saved under — the new key. Previously a key switch could expose and persist the previous slot's data (e.g. one user's cart saved under another user's key).
    • AsyncPersisted severs the old slot's state on rekey: it never appears as previousData in loading/error outputs of the new slot.
    • ManagedCache on a slot switch adopts the new slot's cached value, or falls back to the constructor initialValue for an empty slot; an in-flight fetch for the previous input is invalidated and a fetch for the new input is started.
    • ValueStateBox composed with Persisted now actually restores: the persisted value becomes the effective initial input. The documented ThemeBox pattern previously never restored from disk.
  • Added:
    • MultiBox<I> — composite black box: a single graph-driven input and N observable child cells. Children are owned via child(...) (late final status = child(valueBox(...))), driven only through the guarded dispatch/dispatchAsync bridge (asserts ownership in debug), and disposed together with the multibox. track(...) registers per-input-cycle cancels (auto-released before every compute and on dispose). Wire with GraphBuilder.addMultiBox(mb, input: ..., onError: ...) — onError mirrors add.
    • ValueStateBox<T> / valueBox<T> — identity-compute leaf cell. Distinct by default: pushing a value equal (==) to the current one is a no-op — no listener notifications, no graph pump. Native platform streams re-emit identical values constantly; distinct cells absorb that noise at the source. Pass distinct: false for values mutated in place.
    • resolvePreviousForInput(I input, O? previous) — protected hook on sync and async boxes that maps the effective previous value when a new input arrives; the persistence mixins use it for slot re-initialization.
  • Hardening:
    • Box disposal is idempotent and final: after dispose a box ignores input pushes and never notifies listeners again (late native events and in-flight async completions are swallowed); async dispose invalidates in-flight computes. Graph and MultiBox both go through the same idempotent path, so combining mb.dispose() with graph.dispose() is safe.

0.7.1 #

  • Added:
    • DependencyResolver.whenReadyOrNull<T>(source) — returns the source value when ready, or null otherwise, without skipping the pump cycle. Use when a dependent box accepts an optional input and should compute regardless of whether the upstream has produced data yet. Complements whenReady<T>.

0.7.0 #

  • Breaking:
    • Renamed the async cache mixin ManagedCache → AsyncManagedCache. Update with AsyncPersisted<...>, ManagedCache<...> to with AsyncPersisted<...>, AsyncManagedCache<...>.
    • AsyncManagedCache is now relaxed to on _AsyncBoxBase and works standalone (in-memory TTL) without AsyncPersisted.
  • Added:
    • New sync ManagedCache<I, O> mixin on Box<I, O> — sync always-available value backed by an async fetch(input). Provides TTL, background refresh on access, refresh(), invalidateCache(), fail-open error handling, and automatic re-fetch on input change. Compose with Persisted to persist the cached value.
    • Sync ManagedCache composed with Persisted prefers the disk-cached value over initialValue on boot — initialValue becomes the empty- disk fallback rather than a default that overwrites cached state.

0.6.0 #

  • Breaking:
    • Removed listenSync() and listenAsync() — use listen() instead.
    • Renamed SyncOutput → SyncData.
    • Renamed prepare() → onFirstCompute().
    • Renamed shouldEmitLoadingBeforeCompute() → shouldEmitLoading().
    • Removed Runtime classes — state management inlined into box base classes.
    • Sync action() now returns void instead of Future<void>.
    • AsyncBox.lateinit() replaced by LateAsyncBox class.
  • Added:
    • listen() accepts {bool skipFirst} to skip the immediate callback with current state.
    • beforeCompute() hook on sync boxes (symmetric with async).
    • Persisted mixin now supports dynamic rekey when input changes (symmetric with AsyncPersisted).
  • Changed:
    • LateAsyncBox extracted into its own file.
    • Async box base simplified: removed _initialized, _pendingListeners, _requireInput.

0.5.1 #

  • Fixed:
    • ManagedCache no longer starts duplicate stale-cache refreshes while the first refresh is still in flight.
    • refresh() now completes after the underlying async recompute finishes, not just after it is scheduled.

0.5.0 #

  • Breaking:
    • Removed persistKey parameter from all box constructors (Box, NoInputBox, AsyncBox, NoInputAsyncBox).
    • Removed CachedAsyncSupport mixin — replaced by ManagedCache.
    • Removed persistenceKey, persistedAt, and clearPersistedValue() from _AsyncBoxBase — use mixin members instead.
  • Added:
    • Persisted<I, O> mixin for sync boxes — add with Persisted<I, O> and implement persistKeyFor(I input).
    • AsyncPersisted<I, O> mixin for async boxes — save/restore only.
    • ManagedCache<I, O> mixin (on AsyncPersisted) — TTL, stale-while-refresh, refresh(), invalidateCache().
    • Lifecycle hooks on box base classes: resolveInitialValue(), onInitialized(), beforeCompute().
  • Changed:
    • Persistence is fully extracted from box base classes into opt-in mixins.
    • Cache management is a separate layer: AsyncPersisted for save/restore, add ManagedCache for TTL and refresh controls.
    • Persist key is resolved once at init and never changes — to switch keys, destroy the box and create a new one.

0.4.4 #

  • Add CachedAsyncSupport for persisted async cache with TTL-based lazy refresh.
  • Let async boxes control loading emission via shouldEmitLoadingBeforeCompute(...).
  • Persist timestamps alongside cached values while keeping legacy raw cache reads compatible.

0.4.3 #

  • Add OutputSource.valueOrNull and OutputSource.requireValue for ergonomic ready-value access.
  • Improve not-ready StateError messages to include the current output state.

0.4.2 #

  • Add GraphBuilder.addEffect(...) for explicit fire-and-forget graph effects with current and previous inputs.

0.4.1 #

  • Add awaitNextValueAfterAction(...) as a @visibleForTesting helper for box and graph assertions.
  • Refresh persistence and setup documentation across the package README.

0.4.0 #

  • Breaking:
    • Removed Box.lateinit() constructor — sync boxes always require input at construction. Use AsyncBox.lateinit() for deferred initialization.
    • _SyncBoxBase._runtime is now late final (non-nullable)

0.3.2 #

  • Fixed:
    • AsyncBox.lateinit() now returns AsyncLoading from output and listen before initialization (instead of throwing StateError)
    • Pending listeners are automatically flushed to the runtime when the box receives its first input

0.3.1 #

  • Fixed:
    • Graph.start() no longer crashes on lateinit boxes — defers subscription until runtime is created by first pump cycle

0.3.0 #

  • Breaking:
    • when() loading callback signature: () → R → (T? previousData) → R
    • when() error callback signature: (Object, StackTrace?) → R → (Object, StackTrace?, T? previousData) → R
  • Added:
    • AsyncLoading.previousData — carries last known value during refresh
    • AsyncError.previousData — carries last known value on error after refresh

0.2.0 #

  • Breaking:
    • Removed LazyBox — use persistKey parameter on Box/AsyncBox constructors
    • Removed computeValue() — NoInputBox and NoInputAsyncBox now use compute() directly
    • Renamed dependencies: parameter to input: in Graph.add()
    • Renamed d.ready() to d.whenReady() in DependencyResolver
    • Removed d.output() from DependencyResolver (use box.output directly)
    • Removed public input getter from Box/AsyncBox
  • Added:
    • prepare(I input, O? previous) lifecycle hook — called once before first compute
    • dispose() lifecycle hook — called by Graph.dispose() for resource cleanup
    • persistKey parameter on Box/AsyncBox constructors for built-in persistence
    • BlackboxPersistence.registerCodec<T>() for global codec registry
    • Graph signal tracing: build(trace: true) for console output, onTrace: for custom handler
    • PumpTrace / BoxTrace data classes for programmatic trace access
  • Changed:
    • Box hierarchy refactored: shared _SyncBoxBase/_AsyncBoxBase internal base classes
    • NoInputBox<O> and NoInputAsyncBox<O> are now independent from Box<I,O> / AsyncBox<I,O> (both extend shared base)
    • All box types use compute() as the override method name

0.1.0 #

  • Breaking:
    • Connector -> Graph
    • ConnectorBuilder -> GraphBuilder
    • connect(...) -> add(...)
    • connectWith(...) -> addWith(...)
    • PipelineBuilder.addWithDependencies(...) -> addWith(...)
    • FlowBoxBuilder() is now created via FlowBox.builder()
  • Changed:
    • Updated docs, tests, and examples to the new graph/flow builder API

0.0.7 #

  • Renamed:
    • StateObserver -> FlowBox
  • Breaking:
    • FlowBox<S> now requires S extends FlowState
  • Added:
    • FlowBoxBuilder.onLoading(...) and onError(...) for reacting to AsyncLoading and AsyncError
  • Changed:
    • FlowBox is now a sync box without input (Box<O>)
    • onLoading(...) and onError(...) are compile-time restricted to async sources only

0.0.4 #

  • Renamed:
    • Graph -> Connector
    • Flow -> StateObserver

0.0.3 #

  • Added GraphBuilder
  • Added Pipeline of Boxes

0.0.2 #

  • Updated docs

0.0.1 #

  • Initial release
2
likes
130
points
274
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

State management with one law: output = compute(input, state). Boxes with private memory, an explicit dependency graph you can render as a map, built-in persistence and caching. No code generation.

Repository (GitHub)
View/report issues

Topics

#reactive #state-management #data-flow #dependency-graph #computation-graph

License

MIT (license)

Dependencies

meta

More

Packages that depend on blackbox