raquo/Airstream
65.3
Adequate · 20 September 2026
15.4k
lines of production code
Scala
primary language
1
measurement over time
What this system is
Airstream is a reactive programming library for Scala and Scala.js that manages asynchronous data flows through Signals, EventStreams, and Vars. It provides a comprehensive set of operators for combining, filtering, and transforming these streams, along with specialized tools for handling state, timing, and web-specific interactions like DOM events and HTTP requests. The system emphasizes deterministic transaction ordering, robust error handling, and efficient memory management through a dynamic subscription ownership model.
How it got here
2017–2018 — Airstream feature expansion and cleanup
14 changes.
This period focused on expanding the Airstream reactive library with new features such as structured error handling, event buses, dynamic subscription ownership, and state management types. The work also included removing the experimental Airstream package from the Laminar codebase and adding comprehensive test coverage for the new functionality. Additionally, the project upgraded its build configuration to Scala 3 and scala-js-dom 2.0.0.
2020–2021 — reactive primitives and web integration
16 changes.
This period focused on expanding the reactive core with N-ary combinators, explicit flatten strategies, and key-based split operators, while introducing web-specific streams for HTTP, DOM, and storage. Significant internal refactoring improved parent-child tracking reliability and transaction ordering, supported by comprehensive test coverage across all new and existing components.
2023–2024 — operator expansion and Scala 3 macros
13 changes.
This period focused on significantly expanding the library's operator set, introducing new capabilities for deduplication, timing, and asynchronous status tracking. It also delivered comprehensive Scala 3 support through macro-based pattern matching and ensured API parity with Scala 2.13 via compatibility stubs.
2026 — error recovery and dynamic imports
6 changes.
This period focused on enhancing robustness by introducing explicit error recovery mechanisms for map, scan, and reduce operations, allowing streams to handle exceptions gracefully. It also added support for dynamic module loading via new dynamicImport methods to enable code splitting and reduced initial load times. Additionally, the codebase was refactored to use Tuplez for better tuple composition and expanded test coverage for split and scan functionalities.
Features
Add dynamic import support for Signals and EventStreams
New inline methods \dynamicImport\ are now available on \Signal\ and \EventStream\ (both on the companion objects and as instance methods). These methods leverage Scala.js's \js.dynamicImport\ to create boundaries for progressive module loading, allowing code to be loaded asynchronously only when executed. This enables developers to split bundles and reduce initial load times by deferring the download of specific resources until they are actually needed.
src/main/scala-3/com/raquo/airstream/dynamicImport · high confidence
Introduce EventBus, WriteBus, and EventBusStream for event handling
This change introduces the core event bus components: EventBus, which combines a write interface (WriteBus) with an event stream; WriteBus, which allows emitting events and managing source streams with ownership; and EventBusStream, which handles the internal stream logic and transaction management. These classes provide a way to emit events to multiple buses in a single transaction, support error handling via emitTry, and allow composing and filtering writers. The implementation includes checks to prevent duplicate emissions within a transaction and integrates with the existing transaction system.
src/main/scala/com/raquo/airstream/eventbus · high confidence
Introduce dynamic subscription ownership with reusable activation and live transfer
The ownership model now supports subscriptions that can be activated and deactivated repeatedly via the new DynamicOwner and DynamicSubscription classes, allowing resources to be cleanly managed during component mount/unmount cycles without permanent disposal. A new TransferableSubscription enables seamless movement of active subscriptions between owners without triggering unnecessary activation or deactivation callbacks, optimizing performance for scenarios like DOM element reordering. Additionally, a OneTimeOwner variant prevents usage after being killed, improving safety for one-off resource lifecycles.
src/main/scala/com/raquo/airstream/ownership · high confidence
Introduce sbt source generators for N-ary combine and tuple operators
The build now uses sbt source generators to automatically produce N-arity \combine\, \combineWith\, \mapN\, \filterN\, and \sample\ methods for Signals and EventStreams, along with corresponding tests and helper extensions for option tuples. This eliminates the need for manually written boilerplate for each arity, ensuring consistent API coverage and reducing maintenance overhead for these reactive composition features.
project · high confidence
Introduce structured error handling and unhandled error reporting
Airstream now provides a dedicated error handling system via the new \AirstreamError\ type and \RecoverOps\. Users can catch and transform errors using operators like \recover\, \recoverIgnoreErrors\, \recoverToTry\, and \recoverToEither\, or observe errors without affecting propagation via \tapEachError\. For errors that escape the observable chain, the library now exposes an unhandled error reporting mechanism with configurable callbacks (defaulting to \console.error\), allowing developers to register custom handlers or safely rethrow errors for testing.
src/main/scala/com/raquo/airstream/core · high confidence
New Status tracking API for asynchronous operators
This change introduces a new \Status\ type and associated operators (\AsyncStatusObservable\, \FlatMapStatusObservable\) that allow users to track the lifecycle of asynchronous operations. Instead of just receiving the final output, users can now observe a stream of \Status\[In, Out\]\ events, which are either \Pending\ (waiting for the async result) or \Resolved\ (containing the input, the output, and an occurrence index). This enables UI patterns like showing a loading state while an async operation is in progress and displaying the result once it arrives, with helper methods like \toPendingOption\ and \toResolvedOption\ to easily extract the relevant data.
src/main/scala/com/raquo/airstream/status · high confidence
New custom signal and stream sources for external integration
Users can now easily create custom reactive sources from external systems using the new \CustomSignalSource\ and \CustomStreamSource\ classes in the \airstream.custom\ package. These classes implement the \CustomSource\ trait, allowing developers to define start, stop, and value/error firing logic via a configuration object. The \CustomStreamSource\ specifically addresses transaction ordering issues (issue \#144) by collecting events fired synchronously during \onStart\ and resolving them deterministically, ensuring consistent ordering when merging multiple streams. Deprecated factory methods \CustomSignalSource.apply\ and \CustomStreamSource.apply\ are marked for removal in favor of \Signal.fromCustomSource\ and \EventStream.fromCustomSource\.
src/main/scala/com/raquo/airstream/custom · high confidence
New distinct operators for filtering consecutive duplicate events
Airstream introduces a new \distinct\ operator family across EventStreams, Signals, and StrictSignals, allowing you to filter out consecutive duplicate values. The new \DistinctOps\ trait provides several methods: \distinct\ (using standard equality), \distinctBy\ (using a key function), \distinctByRef\ (using reference equality), and \distinctNoneOnly\ (collapsing consecutive \None\ events). These operators are implemented via new \DistinctStream\ and \DistinctSignal\ classes that track the last seen value and only emit when the \isSame\ comparison returns false. This provides a more flexible and performant way to deduplicate streams compared to previous approaches, with support for custom comparison logic and error handling.
src/main/scala/com/raquo/airstream/distinct · high confidence
New extension operators for Boolean, Option, Either, Try, Status, and Tuple types
The \airstream/extensions\ package now provides a comprehensive set of extension methods for common Scala types, allowing users to transform and split observables and streams more expressively. Boolean observables gain \invert\ (aliased as \not\), \mapTrueToSome\, \mapFalseToSome\, \mapTrueToSeq\, \mapFalseToSeq\, \foldBoolean\, and \splitBoolean\. Option observables and streams receive \mapSome\, \mapSomeToOption\, \mapSomeToSeq\, \mapFilterSome\, \foldOption\, \mapToRight\, \mapToLeft\, \someOrElse\, \collectSome\, and \splitOption\ (with a deprecated two-argument version). Either types get \mapRight\, \mapLeft\, \foldEither\, \swap\, \mapToOption\, \mapLeftToOption\, \rightOrElse\, \leftOrElse\, \collectLeft\, \collectRight\, and \splitEither\. Try observables and streams include \mapSuccess\, \mapFailure\, \foldTry\, \mapToEither\, \successOrElse\, \throwFailure\, \collectSuccess\, and \collectFailure\. Status observables and streams add \mapOutput\, \mapInput\, \mapResolved\, \mapPending\, \foldStatus\, \collectOutput\, \collectResolved\, \collectPending\, \collectPendingInput\, and \splitStatus\. Additionally, generated helpers for tuples provide \mapN\ and \filterN\ on signals and streams of tuples up to arity 16, and \mapSomes\, \tupledSomes\, and \splitOptions\ for observables of tuples of options.
src/main/scala/com/raquo/airstream/extensions · high confidence
New macro-based splitMatchOne and splitMatchSeq APIs with StrictSignal callbacks
A new \ObservableMacroImplicits\ trait introduces \splitMatchOne\ and \splitMatchSeq\ extension methods that use macros to fuse pattern-matching clauses into efficient observable transformations. These methods allow users to split observables by matching values against specific cases, types, or values, with a new \handleRest\ clause for fallback handling. A key behavioral change is that all handler callbacks now receive \StrictSignal\ instances instead of plain \Signal\ or tuples, enabling synchronous value access via \.now()\ and aligning with the broader \split\*\ API updates.
src/main/scala-3/com/raquo/airstream/core · high confidence
New split operators and key-based extraction for signals and vars
The \split\ package now provides a unified API for splitting collections of observables by key. You can use \splitSeq\ and \splitSeqByIndex\ on Observables and Vars to create keyed child signals or derived vars, allowing you to manage individual items within a collection. New operators like \splitOne\ allow splitting single-item observables, while \splitOption\ and \splitSomeSeq\ handle optional collections. The \KeyedStrictSignal\ and \KeyedDerivedVar\ types expose the split key, enabling pattern matching with extractors like \withKey\. Duplicate key detection is now configurable via \DuplicateKeysConfig\, which warns by default to aid debugging but can be disabled for performance on large lists.
src/main/scala/com/raquo/airstream/split · high confidence
New splitMatchOne and splitMatchSeq macros with ergonomic Scala 3 syntax
This change introduces new macro-based observables, \splitMatchOne\ and \splitMatchSeq\, which allow users to split an observable or sequence of observables based on pattern matching clauses (e.g., \handleCase\, \handleType\, \handleValue\, \handleRest\) at compile time. The implementation uses Scala 3 macros to generate efficient match blocks, preserving compiler exhaustiveness and unreachable-case checks. Additionally, the \withKey\ and \varWithKey\ extractors are now declared as \infix\, enabling more natural pattern matching syntax like \signal withKey id\ in Scala 3.9+.
src/main/scala-3/com/raquo/airstream/split · high confidence
New state management types: Var, StrictSignal, and derived state lenses
The library introduces a new \state\ package containing \Var\ (a writable source of state), \StrictSignal\ (a signal with an immediately available current value), and derived state types like \DerivedVar\ and \LazyDerivedVar\ for creating bidirectional lenses into parent state. This change also adds \LazyStrictSignal\ for lazy evaluation of signal chains, \Val\ for constant values, and \ObservedSignal\/\OwnedSignal\ for managing subscriptions, fundamentally restructuring how state is modeled and accessed compared to previous versions.
src/main/scala/com/raquo/airstream/state · high confidence
New stream operators and signal-stream conversion utilities in the misc package
The \airstream.misc\ package now includes several new stream transformation classes: \CollectStream\ for filtering and mapping events using a function that returns \Option\, \DropStream\ to skip events while a condition holds, \FilterStream\ to emit only events passing a predicate, and \TakeStream\ to emit events until a condition fails. Additionally, \SignalFromStream\ allows creating a Signal from an EventStream with a provided initial value, and \StreamFromSignal\ converts a Signal into an EventStream, supporting modes to either include or exclude the initial value on start.
src/main/scala/com/raquo/airstream/misc · high confidence
New timing operators and async stream implementations
This update introduces several new timing-based operators and stream types to the library. Users can now throttle events to the browser's animation frame via \AnimationFrameStream\, or control event frequency with \ThrottleStream\ (supporting leading-edge emission) and \DebounceStream\. Additional timing controls include \DelayStream\ for fixed delays and \SyncDelayStream\ for synchronizing emissions with another observable. The package also adds \PeriodicStream\ for generating periodic values based on a state function, and new \JsPromiseStream\ and \JsPromiseSignal\ classes that wrap JavaScript promises, evaluating them lazily only when the stream or signal is started.
src/main/scala/com/raquo/airstream/timing · high confidence
New web integration streams and storage variables
This release introduces new web-specific reactive components: FetchStream for making HTTP requests via the modern Fetch API (with convenience helpers for DELETE, PATCH, and QUERY methods), AjaxStream as a replacement for the legacy AjaxEventStream, DomEventStream for binding to DOM events, and WebStorageVar for persisting state in LocalStorage and SessionStorage with cross-tab syncing support.
src/main/scala/com/raquo/airstream/web · high confidence
Removals
Removal of experimental Airstream state propagation library
The experimental \airstream\ package has been removed from the \laminar\ codebase. This deletion eliminates the previously available experimental state propagation features, including reactive signals (\Signal\, \ComputedSignal\, \CombineSignal\), variable management (\Var\), and batch update capabilities (\batchUpdate\). Users relying on these experimental APIs for state management will no longer have access to this functionality.
src/main/scala/com/raquo/laminar · high confidence
Behavioural changes
N-ary combine and sample-combine operators with consistent transaction ordering
The \combine\ package introduces N-ary combinators (\CombineStreamN\, \CombineSignalN\, \SampleCombineStreamN\, \SampleCombineSignalN\) that allow combining or sampling multiple streams and signals using a single combinator function, replacing the need for nested binary operations. Additionally, \MergeStream\ now enforces a deterministic emission order for events fired during \onStart\ by prioritizing sources based on their topological rank and argument index, ensuring that simultaneous start emissions are processed in a predictable sequence rather than arrival order.
src/main/scala/com/raquo/airstream/combine · high confidence
New flatten strategies and make-before-break switching semantics
The library introduces a new \flatten\ package containing explicit strategies for flattening observables: \SwitchStream\, \ConcurrentStream\, \SwitchSignal\, and \SwitchSignalStream\, along with their corresponding \FlattenStrategy\ objects. This replaces the previous implicit \flatMap\/\flatten\ approach with explicit \flatMapSwitch\, \flatMapMerge\, \flattenSwitch\, and \flattenMerge\ methods to clarify intent. A key behavioral change is the implementation of 'make-before-break' semantics in \SwitchSignal\ and \SwitchSignalStream\: when switching between inner signals, the previous signal is kept active until the new one is fully started, preventing common ancestor signals from being briefly stopped during transitions.
src/main/scala/com/raquo/airstream/flatten · high confidence
New utility classes and feature flags for migration safety
This release introduces new internal utilities to support the V18 migration and improve robustness. A new \FeatureFlags\ object provides temporary boolean switches (e.g., \V18\_EVENTBUS\_ISSTARTED\_FIX\_155\, \V18\_TRX\_ONSTART\_FIX\_144\) that allow users to revert to pre-V18 behavior if they encounter issues during migration; these flags are deprecated and will be removed in a future version. Additionally, new utility classes \JsPriorityQueue\ and \JsResilientIterator\ are added to handle priority-based ordering and safe iteration over mutable JavaScript arrays respectively, while the \util\ package exposes helper methods like \hasDuplicateKeys\ and \tryOrFailure\ for internal use.
src/main/scala/com/raquo/airstream/util · high confidence
Refactored internal observer and parent-tracking architecture
The internal implementation of signal and stream parent-child relationships has been restructured to improve reliability and reduce overhead. New base traits (InternalNextErrorObserver, InternalTryObserver, InternalParentObserver) standardize how internal observers handle values and errors, while SingleParentSignal and MultiParentSignal now use lazy evaluation and explicit update-ID tracking to prevent synchronization bugs during restarts. This change ensures that child signals correctly sync with their parents and eliminates redundant initial value evaluations, resulting in more predictable reactive behavior.
src/main/scala/com/raquo/airstream/common · high confidence
Refactored map operations into dedicated MapOps trait and MapSignal/MapStream classes
The map-related functionality for EventStreams and Signals has been reorganized into a new \MapOps\ trait and specific implementation classes (\MapSignal\, \MapStream\). This change introduces explicit error recovery logic for mapped observables: if the mapping function or the parent observable throws an error, the system can now optionally handle it via a \recover\ partial function, allowing users to substitute a fallback value, swallow the error, or let it propagate. The \MapOps\ trait defines the standard \map\, \mapTo\, \mapToStrict\, \mapToUnit\, and \tapEach\ methods, while the new classes encapsulate the underlying signal/stream logic and error handling behavior.
src/main/scala/com/raquo/airstream/map · high confidence
Regenerated combine and sample operations for Signals and EventStreams
The generated API files for combining and sampling Signals and EventStreams have been updated to use the Tuplez 0.5.0 library (specifically the Composition type) for handling tuple composition in methods like combineWith, withCurrentValueOf, and sample. This change replaces the previous implicit lookup mechanism with inheritance-based composition, which resolves compile-time regressions associated with N-arity overloads while maintaining the same user-facing combinators for merging up to eight sources.
src/main/scala/com/raquo/airstream/combine/generated · high confidence
Reworked scanLeft and reduceLeft operators with error recovery
The scan and reduce operations on Signals and EventStreams have been restructured to provide better control over error handling. New methods like \scanLeftGenerated\ and \scanLeftGeneratedRecover\ allow users to derive the initial accumulator value from the parent's initial state, while \scanLeftRecover\ and \reduceLeftRecover\ introduce a \resumeOnError\ flag that lets streams recover from exceptions in the combine function by using the last successful value. The previous \foldLeft\ and \foldLeftRecover\ methods are now deprecated in favor of these new, more explicit naming conventions.
src/main/scala/com/raquo/airstream/scan · high confidence
Scala 2.13 compatibility stubs and infix extractor support
This update adds Scala 2.13-specific source files to maintain API parity with Scala 3 while respecting language limitations. The \dynamicImport\ operator is explicitly disabled for Scala 2.13 as it relies on Scala 3's \inline\ feature, resulting in empty trait stubs for \DynamicImportSignalObjectOps\, \DynamicImportSignalOps\, \DynamicImportStreamObjectOps\, and \DynamicImportStreamOps\. Additionally, the \withKey\ and \varWithKey\ extractors are implemented for Scala 2.13 to allow named-key extraction in pattern matches, though they do not support the \infix\ syntax required by Scala 3.9.
src/main/scala-2.13 · high confidence
Test coverage
Added comprehensive error handling tests for EventStream, Signal, and Observer; Added comprehensive test coverage for the flatten package; Added generated tests for combining up to 22 signals and streams; Added test coverage for Airstream extension operators; Added test coverage for combine, merge, and sample-combine behaviors; Added test coverage for core Airstream reactive streams; Added test coverage for ownership and subscription lifecycle; Added test coverage for scanLeft and reduceLeft behavior; Added test coverage for state management components; Added test coverage for timing operators and lazy future evaluation; Added test fixtures for lifecycle and ownership testing; Added test infrastructure and coverage for debugging and async utilities; Added tests for EventBus emission and source wiring behavior; Added tests for FetchStream abort behavior and WebStorageVar; Added tests for distinct stream and signal behavior; Added tests for extending observables from third-party packages; Added tests for split signal functionality; Added tests for split-match macro behavior and API contracts; Added tests for stream filtering, splitting, and side-effect operators; Added tests for syntax conversions and tuple operations; Added unit tests for JsPriorityQueue and JsResilientIterator; Removed experimental Airstream tests.
Dependencies
Upgrade to Scala.js DOM 2.0.0 and Scala 3
The build configuration has been updated to use scala-js-dom 2.0.0 and Scala 3 as the default version, while maintaining cross-compilation support for Scala 2.13. This change also introduces source map generation pointing to GitHub for better debugging in CI environments and configures the test environment to use JSDOM.
(dependencies) · high confidence
Housekeeping
Initial project scaffolding and documentation
The repository is initialized with core documentation files including a comprehensive README, a pre-v0.11.0 changelog, a contributing guide, and an MIT license. Build configuration is established via \release.sbt\, and development hygiene is enforced through a \.gitignore\ file and a \.scalafmt.conf\ configuration. Repository access control is defined in a \CODEOWNERS\ file.
(repo-wide) · high confidence
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
How this codebase got here
Baseline
- First survey — no prior run to compare against. CAI 65.
Lenses
- Code Health 97
- Architecture 99
- Maturity 62
- Readiness 55
- Security 78
Changes since last survey
- 300 commits — 232 feature/other, 68 fixes
By area
- src/main — 192 commits
- (root) — 65 commits
- src/test — 29 commits
- project/Versions.scala — 8 commits
- .github/workflows — 3 commits
- project/plugins.sbt — 2 commits
- project/GenerateCombinableSignal.scala — 1 commit
Notable commits
- fix: API: EventStream.delay(100, event) now evaluates the event by-name, only when emitting it. Fixes #138
- fix: API: More granular blocks for removing observers from observables during a transaction. Fixes #95
- fix: API: Replace flatMap method with flatMapSwitch etc. Fixes #110
- fix: API: Signals do not perform == check anymore. Fixes #19
- fix: Big: Work out restart/syncing bugs and ergonomic issues:
- fix: Build: Bump to Scala 3.2.0 (and thus Scala.js 1.9.0) to avoid sourcemaps off-by-one bug
- fix: Build: Upgrade to Scala 3.3.7; work around the js.Promise.then docs bug
- fix: Fix Scala 2.12 compilation
- fix: Fix: Add max depth limit to transactions. Fixes https://github.com/raquo/Laminar/issues/116
- fix: Fix: Bump Scala 3 version, stop removing -scalajs compiler flag
- fix: Fix: Deactivating a DynamicSubscription while activating another DynamicSubscription causes this deactivation to be delayed. Fixes #145
- fix: Fix: Debug logging format
- fix: Fix: EventBus.apply should have parens
- fix: Fix: EventBus.emit should allow batch emits of unrelated types
- fix: Fix: FetchStream abort stream issues. #157
- fix: Fix: FetchStream does not emit events if initialized outside of a transaction. Fixes #106.
- fix: Fix: Forgot to bump scala-js-dom dependency as well
- fix: Fix: Forgot to bump tuplez dependency to Scala 3.2.0 as well
- fix: Fix: Forgot to fix EventStream's fireValue for #95
- fix: Fix: LazyStrictSignals that depend on other LazyStrictSignals can correctly pull current value from their ultimate non-lazy parent.
- …and 280 more
Written by watchdog.canine.dev from the codebase's own history, inside the signed delivery this page is composed from.
Survey your own repository
raquo/Airstream was measured the same way every project in this corpus was: the same rubric, at a pinned commit, with the result published in full. Point a surveyor at a repository you know and see whether you agree with it.
About this page
- The score is its most recent published measurement, taken on 20 September 2026 at a pinned commit. It is not a live figure and does not change until the project is measured again.
- Measured at commit 5be97bf855eef6b76caebec50740af6588a12e64 — the exact code this score is about.
- Scored under rubric-2026.09.15 — the same rubric and the same method as every other entry in this index.
- Measured by watchdog.canine.dev using codehealth-analyzer preprod-b51f968c9b10.