Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/stream_core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## Upcoming

### 💥 BREAKING CHANGES

- `SharedEmitter` and `StateEmitter` now implement `Stream<T>` directly instead of exposing a `stream` getter
- Removed `stream` getter from `SharedEmitter` and `StateEmitter`

### ✨ Features

- Added `hasListener` and `isClosed` properties to `SharedEmitter`
- Added `asSharedEmitter()` and `asStateEmitter()` extension methods for read-only views
- Added `update`, `getAndUpdate`, `updateAndGet` extension methods on `MutableStateEmitter`
- Added `StreamEvent` base interface and `EventResolver` for event transformation

## 0.3.3

### ✨ Features
Expand Down
1 change: 1 addition & 0 deletions packages/stream_core/lib/src/utils.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export 'utils/comparable_extensions.dart';
export 'utils/comparable_field.dart';
export 'utils/disposable.dart';
export 'utils/event_emitter.dart';
export 'utils/lifecycle_state_provider.dart';
export 'utils/list_extensions.dart';
export 'utils/network_state_provider.dart';
Expand Down
85 changes: 85 additions & 0 deletions packages/stream_core/lib/src/utils/event_emitter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import 'shared_emitter.dart';

/// Base interface for events that can be emitted through an [EventEmitter].
///
/// Implement this interface to create custom event types:
///
/// ```dart
/// class UserLoggedIn implements StreamEvent {
/// final String userId;
/// UserLoggedIn(this.userId);
/// }
/// ```
abstract interface class StreamEvent {}

/// A function that inspects an [event] and optionally transforms it.
///
/// Returns a transformed event if the resolver handles it, or `null` to
/// pass the event to the next resolver in the chain.
///
/// ```dart
/// final resolver = (event) {
/// if (event is GenericEvent) return SpecificEvent(event.data);
/// return null; // Let other resolvers handle it
/// };
/// ```
typedef EventResolver<T extends StreamEvent> = T? Function(T event);

/// A read-only event emitter constrained to [StreamEvent] subtypes.
///
/// Type alias for [SharedEmitter] that enforces event type safety.
///
/// See also:
/// - [MutableEventEmitter] for the mutable variant with resolver support.
typedef EventEmitter<T extends StreamEvent> = SharedEmitter<T>;

/// A mutable event emitter with resolver support for event transformation.
///
/// Extends [SharedEmitterImpl] to apply a chain of [EventResolver]s before
/// emitting events. Each resolver inspects the event and can transform it
/// into a more specific type. The first resolver to return a non-null result
/// determines the final emitted event.
///
/// ```dart
/// final emitter = MutableEventEmitter<WsEvent>(
/// resolvers: [
/// (event) => event is RawEvent ? ParsedEvent(event.data) : null,
/// ],
/// );
///
/// emitter.on<ParsedEvent>((event) {
/// print('Received parsed: ${event.data}');
/// });
///
/// emitter.emit(RawEvent(data)); // Transformed and emitted as ParsedEvent
/// ```
///
/// See also:
/// - [EventEmitter] for the read-only interface.
/// - [EventResolver] for the resolver function signature.
final class MutableEventEmitter<T extends StreamEvent>
extends SharedEmitterImpl<T> implements MutableSharedEmitter<T> {
/// Creates a [MutableEventEmitter] with optional event [resolvers].
///
/// Resolvers are applied in order to each emitted event until one returns
/// a non-null result. Supports synchronous or asynchronous emission via
/// [sync], and can replay the last [replay] events to new subscribers.
MutableEventEmitter({
super.replay = 0,
super.sync = false,
Iterable<EventResolver<T>>? resolvers,
}) : _resolvers = resolvers ?? const {};

final Iterable<EventResolver<T>> _resolvers;

@override
void emit(T value) {
for (final resolver in _resolvers) {
final result = resolver(value);
if (result != null) return super.emit(result);
}

// No resolver matched — emit the event as-is.
return super.emit(value);
}
}
Loading