blackbox 0.10.11
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.effectscarries anEffectTraceper effect:ran(with the inputrunreceived and the previous one),unchanged, orwaiting. 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.
ClockBoxtests moved tofake_async; a graph built insidefakeAsyncruns 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
seqcounter 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 onplaying), the last one stamped by the device (DateTime? lastTare) — and, when several answers are needed at once, a sealed state machine with a purenext(state, event)and one line of glue. Deliberately noeventcell and noStateMachineBox.doc/coffee-brewing.mdfollows suit (tared→lastTare). DebounceBox<T>andHysteresisBox. 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 forwindow— 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}): aboolwhose 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 == offis abooldebounce. 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
Timeracting 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 ofcomputeand indispose; nonow()incompute; 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, thenHysteresisBox.ClockBoxnow 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 isfalseuntil the moment,truefrom then on: one pump, at the boundary; a past moment istruefrom 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 vialisten/BoxObserverand 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 forat. - Born from the coffee modeling session (
doc/coffee-brewing.md): itsStageClockobserver, promoted the moment the field asked for a per-reader schedule.
- ARCHITECTURE.md: "Time is delivered, never read" now names
ClockBoxas 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, soonlyWhenReadyon 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 viad.context.
- A multibox cell is born ready —
0.10.8 #
Pipeline:optional:renamed toonFailure: 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.addno longer forwards the wire-levelonErrorhandler: one error concept per pipeline — retries andonFailureat the step, reading policy at the wire.
0.10.7 #
Pipelinereborn: the graph as a function.Pipeline extends Graph— same wires, same map, sameown(...), plus exactly one verb.start()is overridden to run once and returnFuture<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 neveradd-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: nper step — the runner re-drives the step'srefresh().optional: trueper 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 (
whenReadyOrNullon a nullable source is ambiguous — useonlyWhenReadyoroutputOf).
0.10.6 #
whenReadyrenamed toonlyWhenReady(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.Outputclasses now compare by content (==/hashCode;AsyncErrorcompares 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.
FlowBoxdeprecation 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 viawhenReady, 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/FlowStatedeprecated, 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 ordinaryBoxwith 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:
.latereadiness is decided by the output type: nullable output → ready immediately with null (null is a value); consumers only wait on a non-nullable output withoutinitialValue.- 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. updateInputForTestis 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 viawhenReadyremains 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 —whenReadyon 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
Cachedocstring 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).
previousis gone from everycomputeand theMultiBoxcompute:compute(input),compute()for no-input boxes,void compute(input)for multiboxes. Three field reports showedpreviouslying (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 astate(...)cell (boxes) or a private field (multiboxes).- Deprecated legacy deleted:
Persisted,AsyncPersisted,AsyncManagedCache,ManagedCachemixins,ValueStateBox/valueBox,LateAsyncBox, and the deprecatedMultiBox.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 wherecontext.boxis 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.inputgetter — the current input, mirroringBox.input: readable from buttons, listeners and helpers (insidecomputeit 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
previousfrom compute signatures, drop the deprecated legacy, add lints that guard the law.
0.9.4 #
addMultiBoxno longer requiresinput:— omit it for a self-driven module (MultiBox<void>or nullable input) that lives off its own streams: the graph delivers a singlenullinput on the first pump, socomputeruns exactly once to start the module..addMultiBox(billing)replaces the last dummy in the system,input: (d) {}. Omittinginput: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 indispose(), after every box has stopped, in reverse registration order. Kills the composition-wrapper class whose only job was to remember twoclose()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 throughconnect()(auto-released before the next compute and on dispose, as before); everything else lives in a field and is released at the top ofcomputeand indispose— same as in ordinary boxes. Also removes the name collision with UI read-tracking.
0.9.2 #
Box.late(initialValue:)/AsyncBox.late(initialValue:)(andCachedBox.late/CachedValueBox.late) — create a box without an input; the graph delivers the first one. Kills the dummy-input problem: no moreMyBox(input: (config: null, ...))seeds in composition roots. Until the first input a sync late box showsinitialValue(ornullfor nullable outputs) or has no output yet (dependents wait,valuethrows a clearStateError); an async late box showsAsyncLoading/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-rolledexportslist to keep in sync.LateAsyncBoxis deprecated — extendAsyncBoxand use thesuper.late(initialValue:)constructor; same behavior, one class fewer.
0.9.1 #
- Added
ProvidableBox— the common marker of everything deliverable through a BoxProvider.OutputSourceandMultiBoximplement it, so a composite (e.g. a player) goes intoBoxProviderand comes out viacontext.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) => keybinds 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 cachedfetch. OneCache(ttl: ..., persist: 'menu')declaration replaces theAsyncPersisted+AsyncManagedCachemixin pair; the contract lives in the names (fetch= "go get fresh, the cache decides when";computeon a cached box is sealed). In-memory TTL by default;persist:adds a disk slot (instant cold start, disk timestamp drives expiration);persistFor: (input) => keykeeps 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
inputgetter 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
AsyncLoadingwith the previous data first) — the search-box pattern;persist:/persistFor:behave as on sync boxes. - Added
CachedValueBox— the sync twin ofCachedBox(theManagedCachesemantics, declaratively): the value is always readable synchronously starting frominitialValue/the disk slot,fetchruns 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).codecForfalls back to identity for nullable primitives and to an assignable registered codec (e.g. aServicecodec servesService?).- Breaking: removed
FlowBox.stategetter (collided with thestate(...)declarator) — usevalue. - 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 whenmapis omitted.MultiBox.child(initial)now declares aChildCell— the outward twin of a state cell: an ordinaryOutputSourcethe graph/UI observe, with no public setter (only the owning multibox writes viadispatch); distinct by default; disposed with the multibox.ValueStateBox/valueBoxare deprecated (child(initial)replaces the composite-leaf use;state(...)replaces the internal-memory use);dispatchAsyncremoved — 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
initialValueforPersistedandAsyncPersisted, matchingManagedCache.initialValueis only the first-boot fallback. PreviouslyinitialValuesilently shadowed the persisted value. - A persist-key change (
persistKeyForreturning a new key after an input change) now re-initializes the box in the new slot:onFirstComputeruns again with the new slot's cached value (ornullfor 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). AsyncPersistedsevers the old slot's state on rekey: it never appears aspreviousDatain loading/error outputs of the new slot.ManagedCacheon a slot switch adopts the new slot's cached value, or falls back to the constructorinitialValuefor an empty slot; an in-flight fetch for the previous input is invalidated and a fetch for the new input is started.ValueStateBoxcomposed withPersistednow actually restores: the persisted value becomes the effective initial input. The documentedThemeBoxpattern previously never restored from disk.
- Restore precedence is now uniform: a disk-cached value wins over
- Added:
MultiBox<I>— composite black box: a single graph-driven input and N observable child cells. Children are owned viachild(...)(late final status = child(valueBox(...))), driven only through the guardeddispatch/dispatchAsyncbridge (asserts ownership in debug), and disposed together with the multibox.track(...)registers per-input-cycle cancels (auto-released before everycomputeand on dispose). Wire withGraphBuilder.addMultiBox(mb, input: ..., onError: ...)—onErrormirrorsadd.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. Passdistinct: falsefor values mutated in place.resolvePreviousForInput(I input, O? previous)— protected hook on sync and async boxes that maps the effectivepreviousvalue 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()withgraph.dispose()is safe.
- 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
0.7.1 #
- Added:
DependencyResolver.whenReadyOrNull<T>(source)— returns the source value when ready, ornullotherwise, 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. ComplementswhenReady<T>.
0.7.0 #
- Breaking:
- Renamed the async cache mixin
ManagedCache→AsyncManagedCache. Updatewith AsyncPersisted<...>, ManagedCache<...>towith AsyncPersisted<...>, AsyncManagedCache<...>. AsyncManagedCacheis now relaxed toon _AsyncBoxBaseand works standalone (in-memory TTL) withoutAsyncPersisted.
- Renamed the async cache mixin
- Added:
- New sync
ManagedCache<I, O>mixin onBox<I, O>— sync always-available value backed by an asyncfetch(input). Provides TTL, background refresh on access,refresh(),invalidateCache(), fail-open error handling, and automatic re-fetch on input change. Compose withPersistedto persist the cached value. - Sync
ManagedCachecomposed withPersistedprefers the disk-cached value overinitialValueon boot —initialValuebecomes the empty- disk fallback rather than a default that overwrites cached state.
- New sync
0.6.0 #
- Breaking:
- Removed
listenSync()andlistenAsync()— uselisten()instead. - Renamed
SyncOutput→SyncData. - Renamed
prepare()→onFirstCompute(). - Renamed
shouldEmitLoadingBeforeCompute()→shouldEmitLoading(). - Removed
Runtimeclasses — state management inlined into box base classes. - Sync
action()now returnsvoidinstead ofFuture<void>. AsyncBox.lateinit()replaced byLateAsyncBoxclass.
- Removed
- Added:
listen()accepts{bool skipFirst}to skip the immediate callback with current state.beforeCompute()hook on sync boxes (symmetric with async).Persistedmixin now supports dynamic rekey when input changes (symmetric withAsyncPersisted).
- Changed:
LateAsyncBoxextracted into its own file.- Async box base simplified: removed
_initialized,_pendingListeners,_requireInput.
0.5.1 #
- Fixed:
ManagedCacheno 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
persistKeyparameter from all box constructors (Box,NoInputBox,AsyncBox,NoInputAsyncBox). - Removed
CachedAsyncSupportmixin — replaced byManagedCache. - Removed
persistenceKey,persistedAt, andclearPersistedValue()from_AsyncBoxBase— use mixin members instead.
- Removed
- Added:
Persisted<I, O>mixin for sync boxes — addwith Persisted<I, O>and implementpersistKeyFor(I input).AsyncPersisted<I, O>mixin for async boxes — save/restore only.ManagedCache<I, O>mixin (onAsyncPersisted) — 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:
AsyncPersistedfor save/restore, addManagedCachefor 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
CachedAsyncSupportfor 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.valueOrNullandOutputSource.requireValuefor ergonomic ready-value access. - Improve not-ready
StateErrormessages to include the current output state.
0.4.2 #
- Add
GraphBuilder.addEffect(...)for explicit fire-and-forget graph effects withcurrentandpreviousinputs.
0.4.1 #
- Add
awaitNextValueAfterAction(...)as a@visibleForTestinghelper 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. UseAsyncBox.lateinit()for deferred initialization. _SyncBoxBase._runtimeis nowlate final(non-nullable)
- Removed
0.3.2 #
- Fixed:
AsyncBox.lateinit()now returnsAsyncLoadingfromoutputandlistenbefore initialization (instead of throwingStateError)- Pending listeners are automatically flushed to the runtime when the box receives its first input
0.3.1 #
- Fixed:
Graph.start()no longer crashes onlateinitboxes — defers subscription until runtime is created by first pump cycle
0.3.0 #
- Breaking:
when()loading callback signature:() → R→(T? previousData) → Rwhen()error callback signature:(Object, StackTrace?) → R→(Object, StackTrace?, T? previousData) → R
- Added:
AsyncLoading.previousData— carries last known value during refreshAsyncError.previousData— carries last known value on error after refresh
0.2.0 #
- Breaking:
- Removed
LazyBox— usepersistKeyparameter on Box/AsyncBox constructors - Removed
computeValue()—NoInputBoxandNoInputAsyncBoxnow usecompute()directly - Renamed
dependencies:parameter toinput:in Graph.add() - Renamed
d.ready()tod.whenReady()in DependencyResolver - Removed
d.output()from DependencyResolver (usebox.outputdirectly) - Removed public
inputgetter from Box/AsyncBox
- Removed
- Added:
prepare(I input, O? previous)lifecycle hook — called once before first computedispose()lifecycle hook — called by Graph.dispose() for resource cleanuppersistKeyparameter on Box/AsyncBox constructors for built-in persistenceBlackboxPersistence.registerCodec<T>()for global codec registry- Graph signal tracing:
build(trace: true)for console output,onTrace:for custom handler PumpTrace/BoxTracedata classes for programmatic trace access
- Changed:
- Box hierarchy refactored: shared
_SyncBoxBase/_AsyncBoxBaseinternal base classes NoInputBox<O>andNoInputAsyncBox<O>are now independent fromBox<I,O>/AsyncBox<I,O>(both extend shared base)- All box types use
compute()as the override method name
- Box hierarchy refactored: shared
0.1.0 #
- Breaking:
Connector->GraphConnectorBuilder->GraphBuilderconnect(...)->add(...)connectWith(...)->addWith(...)PipelineBuilder.addWithDependencies(...)->addWith(...)FlowBoxBuilder()is now created viaFlowBox.builder()
- Changed:
- Updated docs, tests, and examples to the new graph/flow builder API
0.0.7 #
- Renamed:
- StateObserver -> FlowBox
- Breaking:
FlowBox<S>now requiresS extends FlowState
- Added:
FlowBoxBuilder.onLoading(...)andonError(...)for reacting toAsyncLoadingandAsyncError
- Changed:
- FlowBox is now a sync box without input (
Box<O>) onLoading(...)andonError(...)are compile-time restricted to async sources only
- FlowBox is now a sync box without input (
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