diff --git a/packages/bus/lib/angel3_bus.dart b/packages/bus/lib/angel3_bus.dart deleted file mode 100644 index 536519b..0000000 --- a/packages/bus/lib/angel3_bus.dart +++ /dev/null @@ -1,9 +0,0 @@ -library angel3_bus; - -export 'src/dispatcher.dart'; -export 'src/command.dart'; -export 'src/handler.dart'; -export 'src/queue.dart'; -export 'src/batch.dart'; -export 'src/chain.dart'; -export 'src/bus_service_provider.dart'; diff --git a/packages/bus/lib/src/batch.dart b/packages/bus/lib/src/batch.dart deleted file mode 100644 index 5a2a24f..0000000 --- a/packages/bus/lib/src/batch.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'command.dart'; -import 'dispatcher.dart'; - -class Batch { - // Implement Batch -} - -class PendingBatch { - final Dispatcher _dispatcher; - final List _commands; - - PendingBatch(this._dispatcher, this._commands); - - Future dispatch() async { - for (var command in _commands) { - await _dispatcher.dispatch(command); - } - } -} diff --git a/packages/bus/lib/src/bus_service_provider.dart b/packages/bus/lib/src/bus_service_provider.dart deleted file mode 100644 index d892653..0000000 --- a/packages/bus/lib/src/bus_service_provider.dart +++ /dev/null @@ -1,60 +0,0 @@ -// // lib/src/bus_service_provider.dart - -// import 'package:angel3_framework/angel3_framework.dart'; -// import 'package:angel3_event_bus/angel3_event_bus.dart'; -// import 'package:angel3_mq/angel3_mq.dart'; -// import 'dispatcher.dart'; - -// class BusServiceProvider extends Provider { -// @override -// Future boot(Angel app) async { -// // Register EventBus -// app.container.registerSingleton(EventBus()); - -// // Register Queue -// app.container.registerSingleton(MemoryQueue()); - -// // Create and register the Dispatcher -// final dispatcher = Dispatcher(app.container); -// app.container.registerSingleton(dispatcher); - -// // Register any global middleware or mappings -// dispatcher.pipeThrough([ -// // Add any global middleware here -// ]); - -// // Register command-to-handler mappings -// dispatcher.map({ -// // Add your command-to-handler mappings here -// // Example: ExampleCommand: ExampleCommandHandler, -// }); -// } -// } - -// class MemoryQueue implements Queue { -// final List _queue = []; - -// @override -// Future push(Command command) async { -// _queue.add(command); -// } - -// @override -// Future later(Duration delay, Command command) async { -// await Future.delayed(delay); -// _queue.add(command); -// } - -// @override -// Future pushOn(String queue, Command command) async { -// // For simplicity, ignoring the queue parameter in this implementation -// _queue.add(command); -// } - -// @override -// Future laterOn(String queue, Duration delay, Command command) async { -// // For simplicity, ignoring the queue parameter in this implementation -// await Future.delayed(delay); -// _queue.add(command); -// } -// } diff --git a/packages/bus/lib/src/chain.dart b/packages/bus/lib/src/chain.dart deleted file mode 100644 index f46dc4e..0000000 --- a/packages/bus/lib/src/chain.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'command.dart'; -import 'dispatcher.dart'; - -class PendingChain { - final Dispatcher _dispatcher; - final List _commands; - - PendingChain(this._dispatcher, this._commands); - - Future dispatch() async { - for (var command in _commands) { - await _dispatcher.dispatch(command); - } - } -} diff --git a/packages/bus/lib/src/command.dart b/packages/bus/lib/src/command.dart deleted file mode 100644 index 81d5395..0000000 --- a/packages/bus/lib/src/command.dart +++ /dev/null @@ -1,5 +0,0 @@ -// lib/src/command.dart - -abstract class Command {} - -abstract class ShouldQueue implements Command {} diff --git a/packages/bus/lib/src/dispatcher.dart b/packages/bus/lib/src/dispatcher.dart deleted file mode 100644 index fcf1ea6..0000000 --- a/packages/bus/lib/src/dispatcher.dart +++ /dev/null @@ -1,251 +0,0 @@ -// lib/src/dispatcher.dart - -import 'dart:async'; - -import 'package:platform_container/container.dart'; -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:angel3_event_bus/event_bus.dart'; -import 'package:angel3_mq/mq.dart'; - -import 'command.dart'; -import 'handler.dart'; -import 'batch.dart'; -import 'chain.dart'; - -/// A class that handles dispatching and processing of commands. -/// -/// This dispatcher supports both synchronous and asynchronous command execution, -/// as well as queueing commands for later processing. -class Dispatcher implements QueueingDispatcher { - final Container container; - final EventBus _eventBus; - final Subject _commandSubject; - final MQClient _queue; - final Map _handlers = {}; - - /// Creates a new [Dispatcher] instance. - /// - /// [container] is used for dependency injection and to retrieve necessary services. - Dispatcher(this.container) - : _eventBus = container.make(), - _commandSubject = BehaviorSubject(), - _queue = container.make() { - _setupCommandProcessing(); - } - - /// Sets up the command processing pipeline. - /// - /// This method initializes the stream that processes commands and emits events. - void _setupCommandProcessing() { - _commandSubject - .flatMap((command) => Stream.fromFuture(_processCommand(command)) - .map((result) => CommandEvent(command, result: result)) - .onErrorReturnWith( - (error, stackTrace) => CommandEvent(command, error: error))) - .listen((event) { - _eventBus.fire(event); - }); - } - - /// Dispatches a command for execution. - /// - /// If the command implements [ShouldQueue], it will be dispatched to a queue. - /// Otherwise, it will be executed immediately. - /// - /// [command] is the command to be dispatched. - @override - Future dispatch(Command command) { - if (command is ShouldQueue) { - return dispatchToQueue(command); - } else { - return dispatchNow(command); - } - } - - /// Dispatches a command for immediate execution. - /// - /// [command] is the command to be executed. - /// [handler] is an optional specific handler for the command. - @override - Future dispatchNow(Command command, [Handler? handler]) { - final completer = Completer(); - _commandSubject.add(command); - - _eventBus - .on() - .where((event) => event.command == command) - .take(1) - .listen((event) { - if (event.error != null) { - completer.completeError(event.error); - } else { - completer.complete(event.result); - } - }); - - return completer.future; - } - - /// Processes a command by finding and executing its appropriate handler. - /// - /// [command] is the command to be processed. - Future _processCommand(Command command) async { - final handlerType = _handlers[command.runtimeType]; - if (handlerType != null) { - final handler = container.make(handlerType) as Handler; - return await handler.handle(command); - } else { - throw Exception('No handler found for command: ${command.runtimeType}'); - } - } - - /// Dispatches a command to a queue for later processing. - /// - /// [command] is the command to be queued. - @override - Future dispatchToQueue(Command command) async { - final message = Message( - payload: command, - headers: { - 'commandType': command.runtimeType.toString(), - }, - ); - _queue.sendMessage( - message: message, - // You might want to specify an exchange name and routing key if needed - // exchangeName: 'your_exchange_name', - // routingKey: 'your_routing_key', - ); - return message.id; - } - - /// Dispatches a command synchronously. - /// - /// This is an alias for [dispatchNow]. - /// - /// [command] is the command to be executed. - /// [handler] is an optional specific handler for the command. - @override - Future dispatchSync(Command command, [Handler? handler]) { - return dispatchNow(command, handler); - } - - /// Finds a batch by its ID. - /// - /// [batchId] is the ID of the batch to find. - @override - Future findBatch(String batchId) async { - // Implement batch finding logic - throw UnimplementedError(); - } - - /// Creates a new pending batch of commands. - /// - /// [commands] is the list of commands to be included in the batch. - @override - PendingBatch batch(List commands) { - return PendingBatch(this, commands); - } - - /// Creates a new pending chain of commands. - /// - /// [commands] is the list of commands to be included in the chain. - @override - PendingChain chain(List commands) { - return PendingChain(this, commands); - } - - /// Applies a list of pipes to the command processing pipeline. - /// - /// [pipes] is the list of pipes to be applied. - @override - Dispatcher pipeThrough(List pipes) { - _commandSubject.transform( - StreamTransformer.fromHandlers( - handleData: (data, sink) { - var result = data; - for (var pipe in pipes) { - result = pipe(result); - } - sink.add(result); - }, - ), - ); - return this; - } - - /// Maps command types to their respective handler types. - /// - /// [handlers] is a map where keys are command types and values are handler types. - @override - Dispatcher map(Map handlers) { - _handlers.addAll(handlers); - return this; - } - - /// Dispatches a command to be executed after the current request-response cycle. - /// - /// [command] is the command to be dispatched after the response. - @override - void dispatchAfterResponse(Command command) { - final message = Message( - payload: command, - headers: { - 'commandType': command.runtimeType.toString(), - 'dispatchAfterResponse': 'true', - }, - ); - - _queue.sendMessage( - message: message, - // You might want to specify an exchange name if needed - // exchangeName: 'your_exchange_name', - // If you want to use a specific queue for after-response commands: - routingKey: 'after_response_queue', - ); - } -} - -abstract class QueueingDispatcher { - Future dispatch(Command command); - Future dispatchSync(Command command, [Handler? handler]); - Future dispatchNow(Command command, [Handler? handler]); - Future dispatchToQueue(Command command); - Future findBatch(String batchId); - PendingBatch batch(List commands); - PendingChain chain(List commands); - Dispatcher pipeThrough(List pipes); - Dispatcher map(Map handlers); - void dispatchAfterResponse(Command command); -} - -typedef Pipe = Command Function(Command); - -class CommandCompletedEvent extends AppEvent { - final dynamic result; - - CommandCompletedEvent(this.result); - - @override - List get props => [result]; -} - -class CommandErrorEvent extends AppEvent { - final dynamic error; - - CommandErrorEvent(this.error); - - @override - List get props => [error]; -} - -class CommandEvent extends AppEvent { - final Command command; - final dynamic result; - final dynamic error; - - CommandEvent(this.command, {this.result, this.error}); - - @override - List get props => [command, result, error]; -} diff --git a/packages/bus/lib/src/handler.dart b/packages/bus/lib/src/handler.dart deleted file mode 100644 index 1c8cdfe..0000000 --- a/packages/bus/lib/src/handler.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'command.dart'; - -abstract class Handler { - Future handle(Command command); -} diff --git a/packages/bus/lib/src/queue.dart b/packages/bus/lib/src/queue.dart deleted file mode 100644 index 5d4b999..0000000 --- a/packages/bus/lib/src/queue.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'command.dart'; - -abstract class Queue { - Future push(Command command); - Future later(Duration delay, Command command); - Future pushOn(String queue, Command command); - Future laterOn(String queue, Duration delay, Command command); -} diff --git a/packages/bus/test/dispatcher_test.dart b/packages/bus/test/dispatcher_test.dart deleted file mode 100644 index a931ffb..0000000 --- a/packages/bus/test/dispatcher_test.dart +++ /dev/null @@ -1,197 +0,0 @@ -import 'dart:async'; - -import 'package:platform_bus/angel3_bus.dart'; -import 'package:platform_container/container.dart'; -import 'package:angel3_event_bus/event_bus.dart'; -import 'package:angel3_mq/mq.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -class IsMessage extends Matcher { - @override - bool matches(item, Map matchState) => item is Message; - - @override - Description describe(Description description) => - description.add('is a Message'); -} - -class MockContainer extends Mock implements Container { - final Map _instances = {}; - - @override - T make([Type? type]) { - type ??= T; - return _instances[type] as T; - } - - void registerInstance(T instance) { - _instances[T] = instance; - } -} - -class MockEventBus extends Mock implements EventBus { - @override - Stream on() { - return super.noSuchMethod( - Invocation.method(#on, [], {#T: T}), - returnValue: Stream.empty(), - ) as Stream; - } -} - -class MockMQClient extends Mock implements MQClient { - Message? capturedMessage; - String? capturedExchangeName; - String? capturedRoutingKey; - - @override - dynamic noSuchMethod(Invocation invocation, - {Object? returnValue, Object? returnValueForMissingStub}) { - if (invocation.memberName == #sendMessage) { - final namedArgs = invocation.namedArguments; - capturedMessage = namedArgs[#message] as Message?; - capturedExchangeName = namedArgs[#exchangeName] as String?; - capturedRoutingKey = namedArgs[#routingKey] as String?; - return null; - } - return super.noSuchMethod(invocation, - returnValue: returnValue, - returnValueForMissingStub: returnValueForMissingStub); - } -} - -class TestCommand implements Command { - final String data; - TestCommand(this.data); -} - -class TestHandler implements Handler { - @override - Future handle(Command command) async { - if (command is TestCommand) { - return 'Handled: ${command.data}'; - } - throw UnimplementedError(); - } -} - -class TestQueuedCommand implements Command, ShouldQueue { - final String data; - TestQueuedCommand(this.data); -} - -void main() { - late MockContainer container; - late MockEventBus eventBus; - late MockMQClient mqClient; - late Dispatcher dispatcher; - - setUp(() { - container = MockContainer(); - eventBus = MockEventBus(); - mqClient = MockMQClient(); - - container.registerInstance(eventBus); - container.registerInstance(mqClient); - - dispatcher = Dispatcher(container); - }); - - group('Dispatcher', () { - test('dispatchNow should handle command and return result', () async { - final command = TestCommand('test data'); - final handler = TestHandler(); - - container.registerInstance(handler); - dispatcher.map({TestCommand: TestHandler}); - - final commandEventController = StreamController(); - when(eventBus.on()) - .thenAnswer((_) => commandEventController.stream); - - final future = dispatcher.dispatchNow(command); - - // Simulate the event firing - commandEventController - .add(CommandEvent(command, result: 'Handled: test data')); - - final result = await future; - expect(result, equals('Handled: test data')); - - await commandEventController.close(); - }); - - test('dispatch should handle regular commands immediately', () async { - final command = TestCommand('regular'); - final handler = TestHandler(); - - container.registerInstance(handler); - dispatcher.map({TestCommand: TestHandler}); - - final commandEventController = StreamController(); - when(eventBus.on()) - .thenAnswer((_) => commandEventController.stream); - - final future = dispatcher.dispatch(command); - - // Simulate the event firing - commandEventController - .add(CommandEvent(command, result: 'Handled: regular')); - - final result = await future; - expect(result, equals('Handled: regular')); - - await commandEventController.close(); - }); - - test('dispatch should queue ShouldQueue commands', () async { - final command = TestQueuedCommand('queued data'); - - // Dispatch the command - await dispatcher.dispatch(command); - - // Verify that sendMessage was called and check the message properties - expect(mqClient.capturedMessage, isNotNull); - expect(mqClient.capturedMessage!.payload, equals(command)); - expect(mqClient.capturedMessage!.headers?['commandType'], - equals('TestQueuedCommand')); - - // Optionally, verify exchange name and routing key if needed - expect(mqClient.capturedExchangeName, isNull); - expect(mqClient.capturedRoutingKey, isNull); - }); - - test( - 'dispatchAfterResponse should send message to queue with specific header', - () { - final command = TestCommand('after response data'); - - // Call dispatchAfterResponse - dispatcher.dispatchAfterResponse(command); - - // Verify that sendMessage was called and check the message properties - expect(mqClient.capturedMessage, isNotNull); - expect(mqClient.capturedMessage!.payload, equals(command)); - expect(mqClient.capturedMessage!.headers?['commandType'], - equals('TestCommand')); - expect(mqClient.capturedMessage!.headers?['dispatchAfterResponse'], - equals('true')); - - // Verify routing key - expect(mqClient.capturedRoutingKey, equals('after_response_queue')); - - // Optionally, verify exchange name if needed - expect(mqClient.capturedExchangeName, isNull); - }); - test('map should register command handlers', () { - dispatcher.map({TestCommand: TestHandler}); - - // Mock the event bus behavior for this test - when(eventBus.on()).thenAnswer((_) => Stream.empty()); - - // This test is a bit tricky to verify directly, but we can check if dispatch doesn't throw - expect(() => dispatcher.dispatch(TestCommand('test')), returnsNormally); - }); - }); -} diff --git a/packages/bus/.gitignore b/packages/config/.gitignore similarity index 100% rename from packages/bus/.gitignore rename to packages/config/.gitignore diff --git a/packages/bus/CHANGELOG.md b/packages/config/CHANGELOG.md similarity index 100% rename from packages/bus/CHANGELOG.md rename to packages/config/CHANGELOG.md diff --git a/packages/bus/LICENSE.md b/packages/config/LICENSE.md similarity index 100% rename from packages/bus/LICENSE.md rename to packages/config/LICENSE.md diff --git a/packages/bus/README.md b/packages/config/README.md similarity index 100% rename from packages/bus/README.md rename to packages/config/README.md diff --git a/packages/bus/analysis_options.yaml b/packages/config/analysis_options.yaml similarity index 100% rename from packages/bus/analysis_options.yaml rename to packages/config/analysis_options.yaml diff --git a/packages/config/doc/.gitkeep b/packages/config/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/config/example/.gitkeep b/packages/config/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/config/lib/src/.gitkeep b/packages/config/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/pipeline/pubspec.yaml b/packages/config/pubspec.yaml similarity index 51% rename from packages/pipeline/pubspec.yaml rename to packages/config/pubspec.yaml index 92c1efb..dfe8308 100644 --- a/packages/pipeline/pubspec.yaml +++ b/packages/config/pubspec.yaml @@ -1,18 +1,16 @@ -name: platform_pipeline -description: The Pipeline Package for the Protevus Platform +name: platform_config +description: The Configuration Package for the Protevus Platform version: 0.0.1 homepage: https://protevus.com documentation: https://docs.protevus.com -repository: https://github.com/protevus/platform +repository: https://git.protevus.com/protevus/platform environment: sdk: ^3.4.2 # Add regular dependencies here. dependencies: - platform_container: ^9.0.0 - platform_core: ^9.0.0 - logging: ^1.1.0 + #protevus_runtime: ^0.0.1 dev_dependencies: lints: ^3.0.0 diff --git a/packages/config/test/.gitkeep b/packages/config/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/container/container/.gitignore b/packages/container/container/.gitignore deleted file mode 100644 index 24d6831..0000000 --- a/packages/container/container/.gitignore +++ /dev/null @@ -1,71 +0,0 @@ -# See https://www.dartlang.org/tools/private-files.html - -# Files and directories created by pub -.dart_tool -.packages -.pub/ -build/ - -# If you're building an application, you may want to check-in your pubspec.lock -pubspec.lock - -# Directory created by dartdoc -# If you don't generate documentation locally you can remove this line. -doc/api/ - -### Dart template -# See https://www.dartlang.org/tools/private-files.html - -# Files and directories created by pub - -# SDK 1.20 and later (no longer creates packages directories) - -# Older SDK versions -# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) -.project -.buildlog -**/packages/ - - -# Files created by dart2js -# (Most Dart developers will use pub build to compile Dart, use/modify these -# rules if you intend to use dart2js directly -# Convention is to use extension '.dart.js' for Dart compiled to Javascript to -# differentiate from explicit Javascript files) -*.dart.js -*.part.js -*.js.deps -*.js.map -*.info.json - -# Directory created by dartdoc - -# Don't commit pubspec lock file -# (Library packages only! Remove pattern if developing an application package) -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff: - -## VsCode -.vscode/ - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -.idea/ -/out/ -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties diff --git a/packages/container/container/AUTHORS.md b/packages/container/container/AUTHORS.md deleted file mode 100644 index ac95ab5..0000000 --- a/packages/container/container/AUTHORS.md +++ /dev/null @@ -1,12 +0,0 @@ -Primary Authors -=============== - -* __[Thomas Hii](dukefirehawk.apps@gmail.com)__ - - Thomas is the current maintainer of the code base. He has refactored and migrated the - code base to support NNBD. - -* __[Tobe O](thosakwe@gmail.com)__ - - Tobe has written much of the original code prior to NNBD migration. He has moved on and - is no longer involved with the project. diff --git a/packages/container/container/CHANGELOG.md b/packages/container/container/CHANGELOG.md deleted file mode 100644 index 377e468..0000000 --- a/packages/container/container/CHANGELOG.md +++ /dev/null @@ -1,151 +0,0 @@ -# Change Log - -## 8.1.1 - -* Updated repository link - -## 8.1.0 - -* Updated `lints` to 3.0.0 -* Fixed analyser warnings - -## 8.0.0 - -* Require Dart >= 3.0 - -## 7.1.0-beta.2 - -* Require Dart >= 2.19 -* Refactored `EmptyReflector` - -## 7.1.0-beta.1 - -* Require Dart >= 2.18 -* Moved `defaultErrorMessage` to `ContainerConst` class to resolve reflectatable issue. -* Added `hashCode` - -## 7.0.0 - -* Require Dart >= 2.17 - -## 6.0.0 - -* Require Dart >= 2.16 -* Removed `error` - -## 5.0.0 - -* Skipped release - -## 4.0.0 - -* Skipped release - -## 3.1.1 - -* Updated `_ReflectedMethodMirror` to have optional `returnType` parameter -* Updated `Container` to handle non nullable type - -## 3.1.0 - -* Updated linter to `package:lints` - -## 3.0.2 - -* Resolved static analysis warnings - -## 3.0.1 - -* Updated README - -## 3.0.0 - -* Migrated to support Dart >= 2.12 NNBD - -## 2.0.0 - -* Migrated to work with Dart >= 2.12 Non NNBD - -## 1.1.0 - -* `pedantic` lints. -* Add `ThrowingReflector`, which throws on all operations. -* `EmptyReflector` uses `Object` instead of `dynamic` as its returned -type, as the `dynamic` type is (apparently?) no longer a valid constant value. -* `registerSingleton` now returns the provided `object`. -* `registerFactory` and `registerLazySingleton` now return the provided function `f`. - -## 1.0.4 - -* Slight patch to prevent annoying segfault. - -## 1.0.3 - -* Added `Future` support to `Reflector`. - -## 1.0.2 - -* Added `makeAsync`. - -## 1.0.1 - -* Added `hasNamed`. - -## 1.0.0 - -* Removed `@GenerateReflector`. - -## 1.0.0-alpha.12 - -* `StaticReflector` now defaults to empty arguments. - -## 1.0.0-alpha.11 - -* Added `StaticReflector`. - -## 1.0.0-alpha.10 - -* Added `Container.registerLazySingleton`. -* Added named singleton support. - -## 1.0.0-alpha.9 - -* Added `Container.has`. - -## 1.0.0-alpha.8 - -* Fixed a bug where `_ReflectedTypeInstance.isAssignableTo` always failed. -* Added `@GenerateReflector` annotation. - -## 1.0.0-alpha.7 - -* Add `EmptyReflector`. -* `ReflectedType.newInstance` now returns a `ReflectedInstance`. -* Moved `ReflectedInstance.invoke` to `ReflectedFunction.invoke`. - -## 1.0.0-alpha.6 - -* Add `getField` to `ReflectedInstance`. - -## 1.0.0-alpha.5 - -* Remove concrete type from `ReflectedTypeParameter`. - -## 1.0.0-alpha.4 - -* Safely handle `void` return types of methods. - -## 1.0.0-alpha.3 - -* Reflecting `void` in `MirrorsReflector` now forwards to `dynamic`. - -## 1.0.0-alpha.2 - -* Added `ReflectedInstance.reflectee`. - -## 1.0.0-alpha.1 - -* Allow omission of the first argument of `Container.make`, to use -a generic type argument instead. -* `singleton` -> `registerSingleton` -* Add `createChild`, and support hierarchical containers. diff --git a/packages/container/container/LICENSE b/packages/container/container/LICENSE deleted file mode 100644 index df5e063..0000000 --- a/packages/container/container/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2021, dukefirehawk.com -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/container/container/README.md b/packages/container/container/README.md deleted file mode 100644 index 2aaf571..0000000 --- a/packages/container/container/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Protevus Container - -![Pub Version (including pre-releases)](https://img.shields.io/pub/v/platform_container?include_prereleases) -[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) -[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) -[![License](https://img.shields.io/github/license/dart-backend/angel)](https://github.com/dart-backend/angel/tree/master/packages/container/angel_container/LICENSE) - -A better IoC container for Protevus, ultimately allowing Protevus to be used with or without `dart:mirrors` package. - -```dart - import 'package:platform_container/mirrors.dart'; - import 'package:platform_core/core.dart'; - import 'package:platform_core/http.dart'; - - @Expose('/sales', middleware: [process1]) - class SalesController extends Controller { - @Expose('/', middleware: [process2]) - Future route1(RequestContext req, ResponseContext res) async { - return "Sales route"; - } - } - - bool process1(RequestContext req, ResponseContext res) { - res.write('Hello, '); - return true; - } - - bool process2(RequestContext req, ResponseContext res) { - res.write('From Sales, '); - return true; - } - - void main() async { - // Using Mirror Reflector - var app = Protevus(reflector: MirrorsReflector()); - - // Sales Controller - app.container.registerSingleton(SalesController()); - await app.mountController(); - - var http = PlatformHttp(app); - var server = await http.startServer('localhost', 3000); - print("Protevus server listening at ${http.uri}"); - } -``` diff --git a/packages/container/container/analysis_options.yaml b/packages/container/container/analysis_options.yaml deleted file mode 100644 index ea2c9e9..0000000 --- a/packages/container/container/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: package:lints/recommended.yaml \ No newline at end of file diff --git a/packages/container/container/example/main.dart b/packages/container/container/example/main.dart deleted file mode 100644 index 35b1bd6..0000000 --- a/packages/container/container/example/main.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'dart:async'; - -import 'package:platform_container/container.dart'; -import 'package:platform_container/mirrors.dart'; - -Future main() async { - // Create a container instance. - var container = Container(const MirrorsReflector()); - - // Register a singleton. - container.registerSingleton(Engine(40)); - - // You can also omit the type annotation, in which the object's runtime type will be used. - // If you're injecting an abstract class, prefer the type annotation. - // - // container.registerSingleton(Engine(40)); - - // Register a factory that creates a truck. - container.registerFactory((container) { - return _TruckImpl(container.make()); - }); - - // Use `make` to create an instance. - var truck = container.make(); - - // You can also resolve injections asynchronously. - container.registerFactory>((_) async => 24); - print(await container.makeAsync()); - - // Asynchronous resolution also works for plain objects. - await container.makeAsync().then((t) => t.drive()); - - // Register a named singleton. - container.registerNamedSingleton('the_truck', truck); - - // Should print: 'Vroom! I have 40 horsepower in my engine.' - truck.drive(); - - // Should print the same. - container.findByName('the_truck').drive(); - - // We can make a child container with its own factory. - var childContainer = container.createChild(); - - childContainer.registerFactory((container) { - return _TruckImpl(Engine(5666)); - }); - - // Make a truck with 5666 HP. - childContainer.make().drive(); - - // However, calling `make` will return the Engine singleton we created above. - print(childContainer.make().horsePower); -} - -abstract class Truck { - void drive(); -} - -class Engine { - final int horsePower; - - Engine(this.horsePower); -} - -class _TruckImpl implements Truck { - final Engine engine; - - _TruckImpl(this.engine); - - @override - void drive() { - print('Vroom! I have ${engine.horsePower} horsepower in my engine.'); - } -} diff --git a/packages/container/container/example/throwing.dart b/packages/container/container/example/throwing.dart deleted file mode 100644 index cde5d33..0000000 --- a/packages/container/container/example/throwing.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:platform_container/container.dart'; - -void main() { - var reflector = const ThrowingReflector(); - reflector.reflectClass(StringBuffer); -} diff --git a/packages/container/container/lib/container.dart b/packages/container/container/lib/container.dart deleted file mode 100644 index db227c5..0000000 --- a/packages/container/container/lib/container.dart +++ /dev/null @@ -1,18 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -library platform_container; - -export 'src/container.dart'; -export 'src/empty/empty.dart'; -export 'src/static/static.dart'; -export 'src/exception.dart'; -export 'src/reflector.dart'; -export 'src/throwing.dart'; -export 'src/container_const.dart'; diff --git a/packages/container/container/lib/mirrors.dart b/packages/container/container/lib/mirrors.dart deleted file mode 100644 index 848c298..0000000 --- a/packages/container/container/lib/mirrors.dart +++ /dev/null @@ -1,10 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -export 'src/mirrors/mirrors.dart'; diff --git a/packages/container/container/lib/src/container.dart b/packages/container/container/lib/src/container.dart deleted file mode 100644 index e1c3743..0000000 --- a/packages/container/container/lib/src/container.dart +++ /dev/null @@ -1,396 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:async'; -import 'exception.dart'; -import 'reflector.dart'; - -class Container { - /// The [Reflector] instance used by this container for reflection-based operations. - /// - /// This reflector is used to instantiate objects and resolve dependencies - /// when no explicit factory or singleton is registered for a given type. - final Reflector reflector; - - /// A map that stores singleton instances, where the key is the Type and the value is the singleton object. - /// - /// This map is used internally by the Container to store and retrieve singleton objects - /// that have been registered using the [registerSingleton] method. - final Map _singletons = {}; - - /// A map that stores factory functions for creating instances of different types. - /// - /// The key is the Type for which the factory is registered, and the value is a function - /// that takes a Container as an argument and returns an instance of that Type. - /// - /// This map is used internally by the Container to store and retrieve factory functions - /// that have been registered using the [registerFactory] method. - final Map _factories = {}; - - /// A map that stores named singleton instances, where the key is a String name and the value is the singleton object. - /// - /// This map is used internally by the Container to store and retrieve named singleton objects - /// that have been registered using the [registerNamedSingleton] method. Named singletons allow - /// for multiple instances of the same type to be stored in the container with different names. - final Map _namedSingletons = {}; - - /// The parent container of this container, if any. - /// - /// This property is used to create a hierarchy of containers, where child containers - /// can access dependencies registered in their parent containers. If this container - /// is a root container (i.e., it has no parent), this property will be null. - /// - /// The parent-child relationship allows for scoped dependency injection, where - /// child containers can override or add to the dependencies defined in their parents. - final Container? _parent; - - /// Creates a new root [Container] instance with the given [Reflector]. - /// - /// This constructor initializes a new container without a parent, making it - /// a root container in the dependency injection hierarchy. The provided - /// [reflector] will be used for all reflection-based operations within this - /// container and its child containers. - /// - /// Parameters: - /// - [reflector]: The [Reflector] instance to be used by this container - /// for reflection-based dependency resolution and object instantiation. - /// - /// The [_parent] is set to null, indicating that this is a root container. - Container(this.reflector) : _parent = null; - - /// Creates a child [Container] instance with the given parent container. - /// - /// This constructor is used internally to create child containers in the - /// dependency injection hierarchy. It initializes a new container with a - /// reference to its parent container and uses the same [Reflector] instance - /// as the parent. - /// - /// Parameters: - /// - [_parent]: The parent [Container] instance for this child container. - /// - /// The [reflector] is initialized with the parent container's reflector, - /// ensuring consistency in reflection operations throughout the container - /// hierarchy. - Container._child(Container this._parent) : reflector = _parent.reflector; - - /// Checks if this container is a root container. - /// - /// Returns `true` if this container has no parent (i.e., it's a root container), - /// and `false` otherwise. - /// - /// This property is useful for determining the position of a container in the - /// dependency injection hierarchy. Root containers are typically used as the - /// top-level containers in an application, while non-root containers are child - /// containers that may have more specific or localized dependencies. - bool get isRoot => _parent == null; - - /// Creates a child [Container] that can define its own singletons and factories. - /// - /// This method creates a new [Container] instance that is a child of the current container. - /// The child container inherits access to all dependencies registered in its parent containers, - /// but can also define its own singletons and factories that override or extend the parent's dependencies. - /// - /// Child containers are useful for creating scoped dependency injection contexts, such as - /// for specific features, modules, or request-scoped dependencies in web applications. - /// - /// The child container uses the same [Reflector] instance as its parent. - /// - /// Returns: - /// A new [Container] instance that is a child of the current container. - /// - /// Example: - /// ```dart - /// var parentContainer = Container(MyReflector()); - /// var childContainer = parentContainer.createChild(); - /// ``` - Container createChild() { - return Container._child(this); - } - - /// Determines if the container or any of its parent containers has an injection of the given type. - /// - /// This method checks for both singleton and factory registrations of the specified type. - /// - /// Parameters: - /// - [T]: The type to check for. If [T] is dynamic, the [t] parameter must be provided. - /// - [t]: An optional Type parameter. If provided, it overrides the type specified by [T]. - /// - /// Returns: - /// - `true` if an injection (singleton or factory) for the specified type is found in this - /// container or any of its parent containers. - /// - `false` if no injection is found for the specified type in the entire container hierarchy. - /// - /// Note: - /// - If [T] is dynamic and [t] is null, the method returns `false` immediately. - /// - The method searches the current container first, then moves up the parent hierarchy - /// until an injection is found or the root container is reached. - bool has([Type? t]) { - var t2 = T; - if (t != null) { - t2 = t; - } else if (T == dynamic && t == null) { - return false; - } - - Container? search = this; - while (search != null) { - if (search._singletons.containsKey(t2)) { - return true; - } else if (search._factories.containsKey(t2)) { - return true; - } else { - search = search._parent; - } - } - - return false; - } - - /// Determines if the container or any of its parent containers has a named singleton with the given [name]. - /// - /// This method searches the current container and its parent hierarchy for a named singleton - /// registered with the specified [name]. - /// - /// Parameters: - /// - [name]: The name of the singleton to search for. - /// - /// Returns: - /// - `true` if a named singleton with the specified [name] is found in this container - /// or any of its parent containers. - /// - `false` if no named singleton with the specified [name] is found in the entire - /// container hierarchy. - /// - /// The method searches the current container first, then moves up the parent hierarchy - /// until a named singleton is found or the root container is reached. - bool hasNamed(String name) { - Container? search = this; - - while (search != null) { - if (search._namedSingletons.containsKey(name)) { - return true; - } else { - search = search._parent; - } - } - - return false; - } - - /// Asynchronously instantiates an instance of [T]. - /// - /// This method attempts to resolve and return a [Future] in the following order: - /// 1. If an injection of type [T] is registered, it wraps it in a [Future] and returns it. - /// 2. If an injection of type [Future] is registered, it returns it directly. - /// 3. If [T] is [dynamic] and a [Future] of the specified type is registered, it returns that. - /// 4. If none of the above conditions are met, it throws a [ReflectionException]. - /// - /// Parameters: - /// - [type]: An optional [Type] parameter that can be used to specify the type - /// when [T] is [dynamic] or when a different type than [T] needs to be used. - /// - /// Returns: - /// A [Future] representing the asynchronously resolved instance. - /// - /// Throws: - /// - [ReflectionException] if no suitable injection is found. - /// - /// This method is useful when you need to resolve dependencies that may be - /// registered as either synchronous ([T]) or asynchronous ([Future]) types. - Future makeAsync([Type? type]) { - var t2 = T; - if (type != null) { - t2 = type; - } - - Type? futureType; //.Future.value(null).runtimeType; - - if (T == dynamic) { - try { - futureType = reflector.reflectFutureOf(t2).reflectedType; - } on UnsupportedError { - // Ignore this. - } - } - - if (has(t2)) { - return Future.value(make(t2)); - } else if (has>()) { - return make>(); - } else if (futureType != null) { - return make(futureType); - } else { - throw ReflectionException( - 'No injection for Future<$t2> or $t2 was found.'); - } - } - - /// Instantiates an instance of [T]. - /// - /// This method attempts to resolve and return an instance of type [T] in the following order: - /// 1. If a singleton of type [T] is registered in this container or any parent container, it returns that instance. - /// 2. If a factory for type [T] is registered in this container or any parent container, it calls the factory and returns the result. - /// 3. If no singleton or factory is found, it uses reflection to instantiate a new instance of [T]. - /// - /// For reflection-based instantiation: - /// - It looks for a default constructor or a constructor with an empty name. - /// - It recursively resolves and injects dependencies for the constructor parameters. - /// - It supports both positional and named parameters. - /// - /// Parameters: - /// - [type]: An optional [Type] parameter that can be used to specify the type - /// when [T] is [dynamic] or when a different type than [T] needs to be used. - /// - /// Returns: - /// An instance of type [T]. - /// - /// Throws: - /// - [ReflectionException] if [T] is not a class or if it has no default constructor. - /// - Any exception that might occur during the instantiation process. - /// - /// This method is central to the dependency injection mechanism, allowing for - /// flexible object creation and dependency resolution within the container hierarchy. - T make([Type? type]) { - Type t2 = T; - if (type != null) { - t2 = type; - } - - Container? search = this; - - while (search != null) { - if (search._singletons.containsKey(t2)) { - // Find a singleton, if any. - return search._singletons[t2] as T; - } else if (search._factories.containsKey(t2)) { - // Find a factory, if any. - return search._factories[t2]!(this) as T; - } else { - search = search._parent; - } - } - - var reflectedType = reflector.reflectType(t2); - var positional = []; - var named = {}; - - if (reflectedType is ReflectedClass) { - bool isDefault(String name) { - return name.isEmpty || name == reflectedType.name; - } - - var constructor = reflectedType.constructors.firstWhere( - (c) => isDefault(c.name), - orElse: (() => throw ReflectionException( - '${reflectedType.name} has no default constructor, and therefore cannot be instantiated.'))); - - for (var param in constructor.parameters) { - var value = make(param.type.reflectedType); - - if (param.isNamed) { - named[param.name] = value; - } else { - positional.add(value); - } - } - - return reflectedType.newInstance( - isDefault(constructor.name) ? '' : constructor.name, - positional, - named, []).reflectee as T; - } else { - throw ReflectionException( - '$t2 is not a class, and therefore cannot be instantiated.'); - } - } - - /// Registers a lazy singleton factory. - /// - /// In many cases, you might prefer this to [registerFactory]. - /// - /// Returns [f]. - T Function(Container) registerLazySingleton(T Function(Container) f, - {Type? as}) { - return registerFactory( - (container) { - var r = f(container); - container.registerSingleton(r, as: as); - return r; - }, - as: as, - ); - } - - /// Registers a factory function for creating instances of type [T] in the container. - /// - /// Returns [f]. - T Function(Container) registerFactory(T Function(Container) f, - {Type? as}) { - Type t2 = T; - if (as != null) { - t2 = as; - } - - if (_factories.containsKey(t2)) { - throw StateError('This container already has a factory for $t2.'); - } - - _factories[t2] = f; - return f; - } - - /// Registers a singleton object in the container. - /// - /// Returns [object]. - T registerSingleton(T object, {Type? as}) { - Type t2 = T; - if (as != null) { - t2 = as; - } else if (T == dynamic) { - t2 = as ?? object.runtimeType; - } - //as ??= T == dynamic ? as : T; - - if (_singletons.containsKey(t2)) { - throw StateError('This container already has a singleton for $t2.'); - } - - _singletons[t2] = object; - return object; - } - - /// Retrieves a named singleton from the container or its parent containers. - /// - /// In general, prefer using [registerSingleton] and [registerFactory]. - /// - /// [findByName] is best reserved for internal logic that end users of code should - /// not see. - T findByName(String name) { - if (_namedSingletons.containsKey(name)) { - return _namedSingletons[name] as T; - } else if (_parent != null) { - return _parent.findByName(name); - } else { - throw StateError( - 'This container does not have a singleton named "$name".'); - } - } - - /// Registers a named singleton object in the container. - /// - /// Note that this is not related to type-based injections, and exists as a mechanism - /// to enable injecting multiple instances of a type within the same container hierarchy. - T registerNamedSingleton(String name, T object) { - if (_namedSingletons.containsKey(name)) { - throw StateError('This container already has a singleton named "$name".'); - } - - _namedSingletons[name] = object; - return object; - } -} diff --git a/packages/container/container/lib/src/container_const.dart b/packages/container/container/lib/src/container_const.dart deleted file mode 100644 index f9c3572..0000000 --- a/packages/container/container/lib/src/container_const.dart +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/// A utility class that contains constant values related to container functionality. -/// -/// This class is not meant to be instantiated and only provides static constants. -/// It includes a default error message for reflection-related issues. -class ContainerConst { - /// The default error message for reflection-related issues. - /// - /// This message is used when an attempt is made to perform a reflective action, - /// but the `ThrowingReflector` class is being used, which disables reflection. - /// Consider using the `MirrorsReflector` class if reflection is necessary. - static const String defaultErrorMessage = - 'You attempted to perform a reflective action, but you are using `ThrowingReflector`, ' - 'a class which disables reflection. Consider using the `MirrorsReflector` ' - 'class if you need reflection.'; - - /// Private constructor to prevent instantiation of this utility class. - /// - /// This constructor is marked as private (with the underscore prefix) to ensure - /// that the `ContainerConst` class cannot be instantiated. This is consistent - /// with the class's purpose of only providing static constants. - ContainerConst._(); -} diff --git a/packages/container/container/lib/src/empty/empty.dart b/packages/container/container/lib/src/empty/empty.dart deleted file mode 100644 index e0c4c43..0000000 --- a/packages/container/container/lib/src/empty/empty.dart +++ /dev/null @@ -1,377 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:platform_container/container.dart'; - -/// A cache to store symbol names. -/// -/// This map associates [Symbol] objects with their corresponding string representations. -/// It's used to avoid repeated parsing of symbol names, improving performance -/// when retrieving symbol names multiple times. -final Map _symbolNames = {}; - -/// A [Reflector] implementation that performs no actual reflection, -/// instead returning empty objects on every invocation. -/// -/// Use this in contexts where you know you won't need any reflective capabilities. -/// -/// This class provides a lightweight alternative to full reflection when reflection -/// functionality is not required. It returns empty or placeholder objects for all -/// reflection operations, which can be useful in scenarios where reflection is -/// expected but not actually used, or when you want to minimize the overhead of -/// reflection in certain parts of your application. -/// -/// The [EmptyReflector] includes: -/// - A static [RegExp] for extracting symbol names without reflection. -/// - Methods to return empty implementations of [ReflectedClass], [ReflectedInstance], -/// [ReflectedType], and [ReflectedFunction]. -/// - A [getName] method that uses a cache to store and retrieve symbol names. -/// -/// This implementation can be particularly useful in testing scenarios or in -/// production environments where reflection is not needed but the interface -/// expecting reflection capabilities needs to be satisfied. -class EmptyReflector extends Reflector { - /// A [RegExp] that can be used to extract the name of a symbol without reflection. - /// - /// This regular expression pattern matches the string representation of a Dart [Symbol], - /// which typically looks like 'Symbol("symbolName")'. It captures the symbol name - /// (the part between the quotes) in a capturing group. - /// - /// Usage: - /// ```dart - /// String symbolString = 'Symbol("exampleSymbol")'; - /// Match? match = symbolRegex.firstMatch(symbolString); - /// String? symbolName = match?.group(1); // Returns "exampleSymbol" - /// ``` - /// - /// This is particularly useful in contexts where reflection is not available - /// or desired, allowing for symbol name extraction through string manipulation. - static final RegExp symbolRegex = RegExp(r'Symbol\("([^"]+)"\)'); - - /// Creates an instance of [EmptyReflector]. - /// - /// This constructor doesn't take any parameters and creates a lightweight - /// reflector that provides empty implementations for all reflection operations. - /// It's useful in scenarios where reflection capabilities are expected but not - /// actually used, or when you want to minimize the overhead of reflection. - const EmptyReflector(); - - /// Retrieves the name of a given [Symbol]. - /// - /// This method attempts to extract the name of the provided [symbol] using - /// the [symbolRegex]. If the name hasn't been cached before, it will be - /// computed and stored in the [_symbolNames] cache for future use. - /// - /// The method works as follows: - /// 1. It checks if the symbol's name is already in the cache. - /// 2. If not found, it uses [putIfAbsent] to compute the name: - /// a. It converts the symbol to a string. - /// b. It applies the [symbolRegex] to extract the name. - /// c. If a match is found, it returns the first captured group (the name). - /// 3. The computed name (or null if not found) is stored in the cache and returned. - /// - /// @param symbol The [Symbol] whose name is to be retrieved. - /// @return The name of the symbol as a [String], or null if the name couldn't be extracted. - @override - String? getName(Symbol symbol) { - return _symbolNames.putIfAbsent( - symbol, () => symbolRegex.firstMatch(symbol.toString())?.group(1)); - } - - /// Returns an empty [ReflectedClass] instance for any given [Type]. - /// - /// This method is part of the [EmptyReflector] implementation and always - /// returns a constant instance of [_EmptyReflectedClass], regardless of - /// the input [clazz]. - /// - /// This behavior is consistent with the purpose of [EmptyReflector], - /// which provides non-functional placeholders for reflection operations. - /// - /// @param clazz The [Type] to reflect, which is ignored in this implementation. - /// @return A constant [_EmptyReflectedClass] instance. - @override - ReflectedClass reflectClass(Type clazz) { - return const _EmptyReflectedClass(); - } - - /// Returns an empty [ReflectedInstance] for any given object. - /// - /// This method is part of the [EmptyReflector] implementation and always - /// returns a constant instance of [_EmptyReflectedInstance], regardless of - /// the input [object]. - /// - /// This behavior is consistent with the purpose of [EmptyReflector], - /// which provides non-functional placeholders for reflection operations. - /// - /// @param object The object to reflect, which is ignored in this implementation. - /// @return A constant [_EmptyReflectedInstance]. - @override - ReflectedInstance reflectInstance(Object object) { - return const _EmptyReflectedInstance(); - } - - /// Returns an empty [ReflectedType] for any given [Type]. - /// - /// This method is part of the [EmptyReflector] implementation and always - /// returns a constant instance of [_EmptyReflectedType], regardless of - /// the input [type]. - /// - /// This behavior is consistent with the purpose of [EmptyReflector], - /// which provides non-functional placeholders for reflection operations. - /// - /// @param type The [Type] to reflect, which is ignored in this implementation. - /// @return A constant [_EmptyReflectedType] instance. - @override - ReflectedType reflectType(Type type) { - return const _EmptyReflectedType(); - } - - /// Returns an empty [ReflectedFunction] for any given [Function]. - /// - /// This method is part of the [EmptyReflector] implementation and always - /// returns a constant instance of [_EmptyReflectedFunction], regardless of - /// the input [function]. - /// - /// This behavior is consistent with the purpose of [EmptyReflector], - /// which provides non-functional placeholders for reflection operations. - /// - /// @param function The [Function] to reflect, which is ignored in this implementation. - /// @return A constant [_EmptyReflectedFunction] instance. - @override - ReflectedFunction reflectFunction(Function function) { - return const _EmptyReflectedFunction(); - } -} - -/// An empty implementation of [ReflectedClass] used by [EmptyReflector]. -/// -/// This class provides a non-functional placeholder for reflection operations -/// on classes. It is designed to be used in contexts where reflection capabilities -/// are expected but not actually needed or desired. -/// -/// Key features: -/// - Extends [ReflectedClass] with minimal implementation. -/// - Constructor initializes with empty or default values for all properties. -/// - [newInstance] method throws an [UnsupportedError] if called. -/// - [isAssignableTo] method only returns true if compared with itself. -/// -/// This implementation is consistent with the purpose of [EmptyReflector], -/// providing a lightweight alternative when full reflection capabilities are not required. -class _EmptyReflectedClass extends ReflectedClass { - /// Constructs an empty [_EmptyReflectedClass] instance. - /// - /// This constructor initializes the instance with empty or default values for all properties. - /// - /// @param name The name of the class, set to '(empty)'. - /// @param typeParameters The list of type parameters, set to an empty list. - /// @param instances The list of instances, set to an empty list. - /// @param functions The list of functions, set to an empty list. - /// @param declarations The list of declarations, set to an empty list. - /// @param type The underlying [Type] of the class, set to [Object]. - const _EmptyReflectedClass() - : super( - '(empty)', - const [], - const [], - const [], - const [], - Object); - - /// Creates a new instance of the reflected class. - /// - /// This method is part of the [_EmptyReflectedClass] implementation and always - /// throws an [UnsupportedError] when called. This behavior is consistent with - /// the purpose of [EmptyReflector], which provides non-functional placeholders - /// for reflection operations. - /// - /// @param constructorName The name of the constructor to invoke. - /// @param positionalArguments A list of positional arguments for the constructor. - /// @param namedArguments An optional map of named arguments for the constructor. - /// @param typeArguments An optional list of type arguments for generic classes. - /// @throws UnsupportedError Always thrown when this method is called. - /// @return This method never returns as it always throws an exception. - @override - ReflectedInstance newInstance( - String constructorName, List positionalArguments, - [Map? namedArguments, List? typeArguments]) { - throw UnsupportedError( - 'Classes reflected via an EmptyReflector cannot be instantiated.'); - } - - /// Checks if this empty reflected class is assignable to another reflected type. - /// - /// This method is part of the [_EmptyReflectedClass] implementation and always - /// returns true only if the [other] type is the same instance as this one. - /// This behavior is consistent with the purpose of [EmptyReflector], - /// which provides minimal functionality for reflection operations. - /// - /// @param other The [ReflectedType] to check against. - /// @return true if [other] is the same instance as this, false otherwise. - @override - bool isAssignableTo(ReflectedType? other) { - return other == this; - } -} - -/// An empty implementation of [ReflectedType] used by [EmptyReflector]. -/// -/// This class provides a non-functional placeholder for reflection operations -/// on types. It is designed to be used in contexts where reflection capabilities -/// are expected but not actually needed or desired. -/// -/// Key features: -/// - Extends [ReflectedType] with minimal implementation. -/// - Constructor initializes with empty or default values for all properties. -/// - [newInstance] method throws an [UnsupportedError] if called. -/// - [isAssignableTo] method only returns true if compared with itself. -/// -/// This implementation is consistent with the purpose of [EmptyReflector], -/// providing a lightweight alternative when full reflection capabilities are not required. -class _EmptyReflectedType extends ReflectedType { - /// Constructs an empty [_EmptyReflectedType] instance. - /// - /// This constructor initializes the instance with empty or default values for all properties. - /// - /// @param name The name of the type, set to '(empty)'. - /// @param typeParameters The list of type parameters, set to an empty list. - /// @param type The underlying [Type], set to [Object]. - const _EmptyReflectedType() - : super('(empty)', const [], Object); - - /// Creates a new instance of the reflected type. - /// - /// This method is part of the [_EmptyReflectedType] implementation and always - /// throws an [UnsupportedError] when called. This behavior is consistent with - /// the purpose of [EmptyReflector], which provides non-functional placeholders - /// for reflection operations. - /// - /// @param constructorName The name of the constructor to invoke. - /// @param positionalArguments A list of positional arguments for the constructor. - /// @param namedArguments An optional map of named arguments for the constructor. - /// @param typeArguments An optional list of type arguments for generic types. - /// @throws UnsupportedError Always thrown when this method is called. - /// @return This method never returns as it always throws an exception. - @override - ReflectedInstance newInstance( - String constructorName, List positionalArguments, - [Map namedArguments = const {}, - List typeArguments = const []]) { - throw UnsupportedError( - 'Types reflected via an EmptyReflector cannot be instantiated.'); - } - - /// Checks if this empty reflected type is assignable to another reflected type. - /// - /// This method is part of the [_EmptyReflectedType] implementation and always - /// returns true only if the [other] type is the same instance as this one. - /// This behavior is consistent with the purpose of [EmptyReflector], - /// which provides minimal functionality for reflection operations. - /// - /// @param other The [ReflectedType] to check against. - /// @return true if [other] is the same instance as this, false otherwise. - @override - bool isAssignableTo(ReflectedType? other) { - return other == this; - } -} - -/// An empty implementation of [ReflectedInstance] used by [EmptyReflector]. -/// -/// This class provides a non-functional placeholder for reflection operations -/// on instances. It is designed to be used in contexts where reflection capabilities -/// are expected but not actually needed or desired. -/// -/// Key features: -/// - Extends [ReflectedInstance] with minimal implementation. -/// - Constructor initializes with empty or default values for all properties. -/// - [getField] method throws an [UnsupportedError] if called. -/// -/// This implementation is consistent with the purpose of [EmptyReflector], -/// providing a lightweight alternative when full reflection capabilities are not required. -class _EmptyReflectedInstance extends ReflectedInstance { - /// Constructs an empty [_EmptyReflectedInstance] instance. - /// - /// This constructor initializes the instance with empty or default values for all properties. - /// - /// @param type The reflected type of the instance, set to an empty [_EmptyReflectedType]. - /// @param reflectedClass The reflected class of the instance, set to an empty [_EmptyReflectedClass]. - /// @param value The underlying value of the instance, set to null. - const _EmptyReflectedInstance() - : super(const _EmptyReflectedType(), const _EmptyReflectedClass(), null); - - /// Retrieves the value of a field on this empty reflected instance. - /// - /// This method is part of the [_EmptyReflectedInstance] implementation and always - /// throws an [UnsupportedError] when called. This behavior is consistent with - /// the purpose of [EmptyReflector], which provides non-functional placeholders - /// for reflection operations. - /// - /// @param name The name of the field to retrieve. - /// @throws UnsupportedError Always thrown when this method is called. - /// @return This method never returns as it always throws an exception. - @override - ReflectedInstance getField(String name) { - throw UnsupportedError( - 'Instances reflected via an EmptyReflector cannot call getField().'); - } -} - -/// An empty implementation of [ReflectedFunction] used by [EmptyReflector]. -/// -/// This class provides a non-functional placeholder for reflection operations -/// on functions. It is designed to be used in contexts where reflection capabilities -/// are expected but not actually needed or desired. -/// -/// Key features: -/// - Extends [ReflectedFunction] with minimal implementation. -/// - Constructor initializes with empty or default values for all properties. -/// - [invoke] method throws an [UnsupportedError] if called. -/// -/// This implementation is consistent with the purpose of [EmptyReflector], -/// providing a lightweight alternative when full reflection capabilities are not required. -class _EmptyReflectedFunction extends ReflectedFunction { - /// Constructs an empty [_EmptyReflectedFunction] instance. - /// - /// This constructor initializes the instance with empty or default values for all properties. - /// - /// @param name The name of the function, set to an empty string. - /// @param typeParameters A list of type parameters for the function, set to an empty list. - /// @param enclosingInstance A list of enclosing instances for the function, set to an empty list. - /// @param parameters A list of parameters for the function, set to an empty list. - /// @param isStatic Indicates whether the function is static, set to false. - /// @param isConst Indicates whether the function is constant, set to false. - /// @param returnType The return type of the function, set to an empty [_EmptyReflectedType]. - /// @param isOperator Indicates whether the function is an operator, set to false. - /// @param isExtensionMember Indicates whether the function is an extension member, set to false. - const _EmptyReflectedFunction() - : super( - '(empty)', - const [], - const [], - const [], - false, - false, - returnType: const _EmptyReflectedType()); - - /// Invokes this empty reflected function. - /// - /// This method is part of the [_EmptyReflectedFunction] implementation and always - /// throws an [UnsupportedError] when called. This behavior is consistent with - /// the purpose of [EmptyReflector], which provides non-functional placeholders - /// for reflection operations. - /// - /// @param invocation The invocation to execute. - /// @throws UnsupportedError Always thrown when this method is called. - /// @return This method never returns as it always throws an exception. - @override - ReflectedInstance invoke(Invocation invocation) { - throw UnsupportedError( - 'Instances reflected via an EmptyReflector cannot call invoke().'); - } -} diff --git a/packages/container/container/lib/src/exception.dart b/packages/container/container/lib/src/exception.dart deleted file mode 100644 index 9b790ef..0000000 --- a/packages/container/container/lib/src/exception.dart +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -/// A custom exception class for reflection-related errors. -/// -/// This class extends the base [Exception] class and provides a way to -/// create exceptions specific to reflection operations. It includes a -/// message that describes the nature of the exception. -/// -/// Example usage: -/// ```dart -/// throw ReflectionException('Failed to reflect on class XYZ'); -/// ``` -class ReflectionException implements Exception { - /// Creates a new instance of [ReflectionException] with the specified message. - /// - /// The [message] parameter should describe the nature of the reflection error. - final String message; - - /// Creates a new instance of [ReflectionException] with the specified message. - /// - /// The [message] parameter should describe the nature of the reflection error. - ReflectionException(this.message); - - // Override the toString method to provide a custom string representation of the exception. - @override - String toString() => message; -} diff --git a/packages/container/container/lib/src/mirrors/mirrors.dart b/packages/container/container/lib/src/mirrors/mirrors.dart deleted file mode 100644 index 29c3c64..0000000 --- a/packages/container/container/lib/src/mirrors/mirrors.dart +++ /dev/null @@ -1,10 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -export 'reflector.dart'; diff --git a/packages/container/container/lib/src/mirrors/reflector.dart b/packages/container/container/lib/src/mirrors/reflector.dart deleted file mode 100644 index 1c53546..0000000 --- a/packages/container/container/lib/src/mirrors/reflector.dart +++ /dev/null @@ -1,904 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'dart:async'; -import 'dart:mirrors' as dart; -import 'package:platform_container/container.dart'; -import 'package:quiver/core.dart'; - -/// A [Reflector] implementation that forwards to `dart:mirrors`. -/// -/// This class provides reflection capabilities by leveraging the `dart:mirrors` library. -/// It allows for runtime introspection of classes, functions, types, and instances. -/// -/// Key features: -/// - Reflects classes, functions, types, and instances -/// - Provides access to class and function metadata -/// - Supports reflection of generic types and futures -/// - Allows invocation of reflected functions -/// -/// Note: This reflector is primarily useful on the server-side where reflection is fully supported. -/// It may not be suitable for client-side Dart applications due to limitations in reflection support. -/// -/// Usage: -/// ```dart -/// final reflector = MirrorsReflector(); -/// final classReflection = reflector.reflectClass(MyClass); -/// final functionReflection = reflector.reflectFunction(myFunction); -/// final typeReflection = reflector.reflectType(int); -/// final instanceReflection = reflector.reflectInstance(myObject); -/// ``` -/// -/// Be aware of the performance implications when using reflection extensively, -/// as it can impact runtime performance and increase code size. -class MirrorsReflector extends Reflector { - /// Creates a new instance of [MirrorsReflector]. - /// - /// This constructor initializes the [MirrorsReflector] instance. - const MirrorsReflector(); - - /// Retrieves the name of a symbol as a string. - /// - /// This method overrides the base implementation to use the `dart:mirrors` library - /// for converting a [Symbol] to its corresponding string representation. - /// - /// Parameters: - /// - [symbol]: The [Symbol] whose name is to be retrieved. - /// - /// Returns: - /// A [String] representing the name of the given symbol. - /// - /// Example: - /// ```dart - /// final name = getName(#someSymbol); - /// print(name); // Outputs: "someSymbol" - /// ``` - @override - String getName(Symbol symbol) => dart.MirrorSystem.getName(symbol); - - /// Reflects a class and returns a [ReflectedClass] instance. - /// - /// This method takes a [Type] parameter [clazz] and uses dart:mirrors to create - /// a reflection of the class. It returns a [_ReflectedClassMirror] which - /// implements [ReflectedClass]. - /// - /// Parameters: - /// - [clazz]: The [Type] of the class to reflect. - /// - /// Returns: - /// A [ReflectedClass] instance representing the reflected class. - /// - /// Throws: - /// - [ArgumentError] if the provided [clazz] is not a class. - /// - /// Example: - /// ```dart - /// final reflector = MirrorsReflector(); - /// final classReflection = reflector.reflectClass(MyClass); - /// ``` - @override - ReflectedClass reflectClass(Type clazz) { - var mirror = dart.reflectType(clazz); - - if (mirror is dart.ClassMirror) { - return _ReflectedClassMirror(mirror); - } else { - throw ArgumentError('$clazz is not a class.'); - } - } - - /// Reflects a function and returns a [ReflectedFunction] instance. - /// - /// This method takes a [Function] parameter [function] and uses dart:mirrors to create - /// a reflection of the function. It returns a [_ReflectedMethodMirror] which - /// implements [ReflectedFunction]. - /// - /// Parameters: - /// - [function]: The [Function] to reflect. - /// - /// Returns: - /// A [ReflectedFunction] instance representing the reflected function. - /// - /// Example: - /// ```dart - /// final reflector = MirrorsReflector(); - /// final functionReflection = reflector.reflectFunction(myFunction); - /// ``` - @override - ReflectedFunction reflectFunction(Function function) { - var closure = dart.reflect(function) as dart.ClosureMirror; - return _ReflectedMethodMirror(closure.function, closure); - } - - /// Reflects a given type and returns a [ReflectedType] instance. - /// - /// This method takes a [Type] parameter and uses dart:mirrors to create - /// a reflection of the type. It returns either a [_ReflectedClassMirror] - /// or a [_ReflectedTypeMirror] depending on whether the reflected type - /// is a class or not. - /// - /// Parameters: - /// - [type]: The [Type] to reflect. - /// - /// Returns: - /// A [ReflectedType] instance representing the reflected type. - /// - /// If the reflected type doesn't have a reflected type (i.e., [hasReflectedType] is false), - /// it returns a reflection of the `dynamic` type instead. - /// - /// Example: - /// ```dart - /// final reflector = MirrorsReflector(); - /// final typeReflection = reflector.reflectType(int); - /// ``` - @override - ReflectedType reflectType(Type type) { - var mirror = dart.reflectType(type); - - if (!mirror.hasReflectedType) { - return reflectType(dynamic); - } else { - if (mirror is dart.ClassMirror) { - return _ReflectedClassMirror(mirror); - } else { - return _ReflectedTypeMirror(mirror); - } - } - } - - /// Reflects a Future of a given type and returns a [ReflectedType] instance. - /// - /// This method takes a [Type] parameter and creates a reflection of a Future - /// that wraps that type. It first reflects the inner type, then constructs - /// a Future type with that inner type as its type argument. - /// - /// Parameters: - /// - [type]: The [Type] to be wrapped in a Future. - /// - /// Returns: - /// A [ReflectedType] instance representing the reflected Future. - /// - /// Throws: - /// - [ArgumentError] if the provided [type] is not a class or type. - /// - /// Example: - /// ```dart - /// final reflector = MirrorsReflector(); - /// final futureIntReflection = reflector.reflectFutureOf(int); - /// // This will reflect Future - /// ``` - @override - ReflectedType reflectFutureOf(Type type) { - var inner = reflectType(type); - dart.TypeMirror localMirror; - if (inner is _ReflectedClassMirror) { - localMirror = inner.mirror; - } else if (inner is _ReflectedTypeMirror) { - localMirror = inner.mirror; - } else { - throw ArgumentError('$type is not a class or type.'); - } - - var future = dart.reflectType(Future, [localMirror.reflectedType]); - return _ReflectedClassMirror(future as dart.ClassMirror); - } - - /// Reflects an instance of an object and returns a [ReflectedInstance]. - /// - /// This method takes an [Object] parameter and uses dart:mirrors to create - /// a reflection of the object instance. It returns a [_ReflectedInstanceMirror] - /// which implements [ReflectedInstance]. - /// - /// Parameters: - /// - [object]: The object instance to reflect. - /// - /// Returns: - /// A [ReflectedInstance] representing the reflected object instance. - /// - /// Example: - /// ```dart - /// final reflector = MirrorsReflector(); - /// final instanceReflection = reflector.reflectInstance(myObject); - /// ``` - @override - ReflectedInstance reflectInstance(Object object) { - return _ReflectedInstanceMirror(dart.reflect(object)); - } -} - -/// Represents a reflected type parameter using dart:mirrors. -/// -/// This class extends [ReflectedTypeParameter] and wraps a [dart.TypeVariableMirror] -/// to provide reflection capabilities for type parameters in Dart. -/// -/// The class extracts the name of the type parameter from the mirror and passes -/// it to the superclass constructor. -/// -/// This is typically used internally by the reflection system to represent -/// type parameters of generic classes or methods. -class _ReflectedTypeParameter extends ReflectedTypeParameter { - /// The [dart.TypeVariableMirror] instance representing the reflected type parameter. - /// - /// This mirror provides access to the details of the type parameter, such as its name, - /// bounds, and other metadata. It is used internally by the [_ReflectedTypeParameter] - /// class to implement reflection capabilities for type parameters. - final dart.TypeVariableMirror mirror; - - /// Constructs a [_ReflectedTypeParameter] instance. - /// - /// This constructor takes a [dart.TypeVariableMirror] and initializes the - /// [_ReflectedTypeParameter] with the name of the type parameter extracted - /// from the mirror. - /// - /// Parameters: - /// - [mirror]: A [dart.TypeVariableMirror] representing the type parameter. - /// - /// The constructor uses [dart.MirrorSystem.getName] to extract the name of the - /// type parameter from the mirror's [simpleName] and passes it to the superclass - /// constructor. - _ReflectedTypeParameter(this.mirror) - : super(dart.MirrorSystem.getName(mirror.simpleName)); -} - -/// Represents a reflected type using dart:mirrors. -/// -/// This class extends [ReflectedType] and wraps a [dart.TypeMirror] -/// to provide reflection capabilities for types in Dart. -/// -/// The class extracts the name and type variables from the mirror and passes -/// them to the superclass constructor. It also implements type comparison -/// through the [isAssignableTo] method. -/// -/// Note that this class represents types that are not classes, and therefore -/// cannot be instantiated. Attempting to call [newInstance] will throw a -/// [ReflectionException]. -/// -/// This is typically used internally by the reflection system to represent -/// non-class types like interfaces, mixins, or type aliases. -class _ReflectedTypeMirror extends ReflectedType { - /// The [dart.TypeMirror] instance representing the reflected type. - /// - /// This mirror provides access to the details of the type, such as its name, - /// type variables, and other metadata. It is used internally by the - /// [_ReflectedTypeMirror] class to implement reflection capabilities for types. - final dart.TypeMirror mirror; - - /// Constructs a [_ReflectedTypeMirror] instance. - /// - /// This constructor takes a [dart.TypeMirror] and initializes the - /// [_ReflectedTypeMirror] with the following: - /// - The name of the type extracted from the mirror's [simpleName]. - /// - A list of [_ReflectedTypeParameter] objects created from the mirror's type variables. - /// - The reflected type of the mirror. - /// - /// Parameters: - /// - [mirror]: A [dart.TypeMirror] representing the type to be reflected. - /// - /// The constructor uses [dart.MirrorSystem.getName] to extract the name of the - /// type from the mirror's [simpleName]. It also maps the mirror's type variables - /// to [_ReflectedTypeParameter] objects and passes them along with the reflected - /// type to the superclass constructor. - _ReflectedTypeMirror(this.mirror) - : super( - dart.MirrorSystem.getName(mirror.simpleName), - mirror.typeVariables.map((m) => _ReflectedTypeParameter(m)).toList(), - mirror.reflectedType, - ); - - /// Checks if this reflected class is assignable to another reflected type. - /// - /// This method determines whether an instance of this class can be assigned - /// to a variable of the type represented by [other]. - /// - /// Parameters: - /// - [other]: The [ReflectedType] to check against. - /// - /// Returns: - /// - `true` if this class is assignable to [other]. - /// - `false` otherwise, including when [other] is not a [_ReflectedClassMirror] - /// or [_ReflectedTypeMirror]. - /// - /// The method uses dart:mirrors' [isAssignableTo] to perform the actual check - /// when [other] is either a [_ReflectedClassMirror] or [_ReflectedTypeMirror]. - @override - bool isAssignableTo(ReflectedType? other) { - if (other is _ReflectedClassMirror) { - return mirror.isAssignableTo(other.mirror); - } else if (other is _ReflectedTypeMirror) { - return mirror.isAssignableTo(other.mirror); - } else { - return false; - } - } - - /// Throws a [ReflectionException] when attempting to create a new instance. - /// - /// This method is intended to be overridden by classes that represent - /// instantiable types. For non-instantiable types (like interfaces or - /// abstract classes), this method throws an exception. - /// - /// Parameters: - /// - [constructorName]: The name of the constructor to invoke. - /// - [positionalArguments]: A list of positional arguments for the constructor. - /// - [namedArguments]: An optional map of named arguments for the constructor. - /// - [typeArguments]: An optional list of type arguments for generic classes. - /// - /// Throws: - /// [ReflectionException]: Always thrown with a message indicating that - /// this type cannot be instantiated. - /// - /// Example: - /// ```dart - /// // This will always throw a ReflectionException - /// reflectedType.newInstance('defaultConstructor', []); - /// ``` - @override - ReflectedInstance newInstance( - String constructorName, List positionalArguments, - [Map? namedArguments, List? typeArguments]) { - throw ReflectionException( - '$name is not a class, and therefore cannot be instantiated.'); - } -} - -/// Represents a reflected class using dart:mirrors. -/// -/// This class extends [ReflectedClass] and wraps a [dart.ClassMirror] -/// to provide reflection capabilities for Dart classes. -/// -/// Key features: -/// - Reflects class name, type parameters, constructors, and declarations -/// - Provides access to class metadata (annotations) -/// - Supports type comparison through [isAssignableTo] -/// - Allows creation of new instances of the reflected class -/// -/// This class is typically used internally by the reflection system to -/// represent classes and their members. -class _ReflectedClassMirror extends ReflectedClass { - /// The [dart.ClassMirror] representing the reflected class. - /// - /// This mirror is used to extract information about the class, such as - /// its name, type parameters, constructors, and declarations. - /// - /// See also: - /// - [dart.ClassMirror] for more details about the mirror system. - final dart.ClassMirror mirror; - - /// Constructs a [_ReflectedClassMirror] instance. - /// - /// This constructor takes a [dart.ClassMirror] and initializes the - /// [_ReflectedClassMirror] with the following: - /// - The name of the class extracted from the mirror's [simpleName]. - /// - A list of [_ReflectedTypeParameter] objects created from the mirror's type variables. - /// - Empty lists for constructors and annotations (these are populated elsewhere). - /// - A list of declarations obtained from the [_declarationsOf] method. - /// - The reflected type of the mirror. - /// - /// Parameters: - /// - [mirror]: A [dart.ClassMirror] representing the class to be reflected. - /// - /// The constructor uses [dart.MirrorSystem.getName] to extract the name of the - /// class from the mirror's [simpleName]. It also maps the mirror's type variables - /// to [_ReflectedTypeParameter] objects and uses [_declarationsOf] to get the - /// class declarations. These are then passed to the superclass constructor. - _ReflectedClassMirror(this.mirror) - : super( - dart.MirrorSystem.getName(mirror.simpleName), - mirror.typeVariables.map((m) => _ReflectedTypeParameter(m)).toList(), - [], - [], - _declarationsOf(mirror), - mirror.reflectedType, - ); - - /// Retrieves a list of reflected constructors from a given [dart.ClassMirror]. - /// - /// This static method iterates through the declarations of the provided [mirror], - /// identifies the constructor methods, and creates [ReflectedFunction] instances - /// for each constructor found. - /// - /// Parameters: - /// - [mirror]: A [dart.ClassMirror] representing the class to examine. - /// - /// Returns: - /// A [List] of [ReflectedFunction] objects, each representing a constructor - /// of the class. - /// - /// The method specifically looks for [dart.MethodMirror] instances that are - /// marked as constructors (i.e., [isConstructor] is true). Each identified - /// constructor is wrapped in a [_ReflectedMethodMirror] and added to the - /// returned list. - static List _constructorsOf(dart.ClassMirror mirror) { - var out = []; - - for (var key in mirror.declarations.keys) { - var value = mirror.declarations[key]; - - if (value is dart.MethodMirror && value.isConstructor) { - out.add(_ReflectedMethodMirror(value)); - } - } - - return out; - } - - /// Retrieves a list of reflected declarations from a given [dart.ClassMirror]. - /// - /// This static method iterates through the declarations of the provided [mirror], - /// identifies non-constructor methods, and creates [ReflectedDeclaration] instances - /// for each method found. - /// - /// Parameters: - /// - [mirror]: A [dart.ClassMirror] representing the class to examine. - /// - /// Returns: - /// A [List] of [ReflectedDeclaration] objects, each representing a non-constructor - /// method of the class. - /// - /// The method specifically looks for [dart.MethodMirror] instances that are - /// not constructors (i.e., [isConstructor] is false). Each identified - /// method is wrapped in a [_ReflectedDeclarationMirror] and added to the - /// returned list. - static List _declarationsOf(dart.ClassMirror mirror) { - var out = []; - - for (var key in mirror.declarations.keys) { - var value = mirror.declarations[key]; - - if (value is dart.MethodMirror && !value.isConstructor) { - out.add( - _ReflectedDeclarationMirror(dart.MirrorSystem.getName(key), value)); - } - } - - return out; - } - - /// Retrieves the annotations (metadata) associated with this reflected class. - /// - /// This getter method overrides the base implementation to provide access to - /// the class-level annotations using dart:mirrors. It maps each metadata mirror - /// to a [_ReflectedInstanceMirror] and returns them as a list. - /// - /// Returns: - /// A [List] of [ReflectedInstance] objects, each representing an annotation - /// applied to this class. - /// - /// Example: - /// ```dart - /// @MyAnnotation() - /// class MyClass {} - /// - /// // Assuming we have a reflection of MyClass - /// final classReflection = reflector.reflectClass(MyClass); - /// final annotations = classReflection.annotations; - /// // annotations will contain a ReflectedInstance of MyAnnotation - /// ``` - /// - /// Note: This method relies on the [dart.ClassMirror]'s metadata property - /// and creates a new [_ReflectedInstanceMirror] for each annotation. - @override - List get annotations => - mirror.metadata.map((m) => _ReflectedInstanceMirror(m)).toList(); - - /// Retrieves a list of reflected constructors for this class. - /// - /// This getter method overrides the base implementation to provide access to - /// the constructors of the reflected class using dart:mirrors. It uses the - /// static [_constructorsOf] method to extract and wrap each constructor - /// in a [ReflectedFunction] object. - /// - /// Returns: - /// A [List] of [ReflectedFunction] objects, each representing a constructor - /// of this class. - /// - /// Example: - /// ```dart - /// final classReflection = reflector.reflectClass(MyClass); - /// final constructors = classReflection.constructors; - /// // constructors will contain ReflectedFunction objects for each - /// // constructor in MyClass - /// ``` - /// - /// Note: This method relies on the [dart.ClassMirror]'s declarations and - /// the [_constructorsOf] method to identify and create reflections of - /// the class constructors. - @override - List get constructors => _constructorsOf(mirror); - - /// Checks if this reflected type is assignable to another reflected type. - /// - /// This method determines whether an instance of this type can be assigned - /// to a variable of the type represented by [other]. - /// - /// Parameters: - /// - [other]: The [ReflectedType] to check against. - /// - /// Returns: - /// - `true` if this type is assignable to [other]. - /// - `false` otherwise, including when [other] is not a [_ReflectedClassMirror] - /// or [_ReflectedTypeMirror]. - /// - /// The method uses dart:mirrors' [isAssignableTo] to perform the actual check - /// when [other] is either a [_ReflectedClassMirror] or [_ReflectedTypeMirror]. - @override - bool isAssignableTo(ReflectedType? other) { - if (other is _ReflectedClassMirror) { - return mirror.isAssignableTo(other.mirror); - } else if (other is _ReflectedTypeMirror) { - return mirror.isAssignableTo(other.mirror); - } else { - return false; - } - } - - /// Creates a new instance of the reflected class. - /// - /// This method instantiates a new object of the class represented by this - /// [_ReflectedClassMirror] using the specified constructor and arguments. - /// - /// Parameters: - /// - [constructorName]: The name of the constructor to invoke. Use an empty - /// string for the default constructor. - /// - [positionalArguments]: A list of positional arguments to pass to the constructor. - /// - [namedArguments]: An optional map of named arguments to pass to the constructor. - /// - [typeArguments]: An optional list of type arguments for generic classes. - /// - /// Returns: - /// A [ReflectedInstance] representing the newly created instance. - /// - /// Throws: - /// May throw exceptions if the constructor invocation fails, e.g., due to - /// invalid arguments or if the class cannot be instantiated. - /// - /// Note: - /// This implementation currently does not use the [namedArguments] or - /// [typeArguments] parameters. They are included for API compatibility. - @override - ReflectedInstance newInstance( - String constructorName, List positionalArguments, - [Map? namedArguments, List? typeArguments]) { - return _ReflectedInstanceMirror( - mirror.newInstance(Symbol(constructorName), positionalArguments)); - } - - /// Checks if this [_ReflectedClassMirror] is equal to another object. - /// - /// This method overrides the default equality operator to provide a custom - /// equality check for [_ReflectedClassMirror] instances. - /// - /// Parameters: - /// - [other]: The object to compare with this [_ReflectedClassMirror]. - /// - /// Returns: - /// - `true` if [other] is also a [_ReflectedClassMirror] and has the same - /// [mirror] as this instance. - /// - `false` otherwise. - /// - /// This implementation ensures that two [_ReflectedClassMirror] instances - /// are considered equal if and only if they reflect the same class (i.e., - /// their underlying [dart.ClassMirror]s are the same). - @override - bool operator ==(other) { - return other is _ReflectedClassMirror && other.mirror == mirror; - } - - /// Generates a hash code for this [_ReflectedClassMirror]. - /// - /// This method overrides the default [hashCode] implementation to provide - /// a consistent hash code for [_ReflectedClassMirror] instances. - /// - /// The hash code is generated using the [hash2] function from the Quiver - /// library, combining the [mirror] object and an empty string. The empty - /// string is used as a second parameter to maintain compatibility with - /// the [hash2] function, which requires two arguments. - /// - /// Returns: - /// An [int] representing the hash code of this [_ReflectedClassMirror]. - /// - /// Note: - /// This hash code implementation ensures that two [_ReflectedClassMirror] - /// instances with the same [mirror] will have the same hash code, which - /// is consistent with the equality check implemented in the [operator ==]. - @override - int get hashCode => hash2(mirror, " "); -} - -/// Represents a reflected declaration using dart:mirrors. -/// -/// This class extends [ReflectedDeclaration] and wraps a [dart.MethodMirror] -/// to provide reflection capabilities for method declarations in Dart. -/// -/// Key features: -/// - Reflects the name and static nature of the declaration -/// - Provides access to the underlying method as a [ReflectedFunction] -/// -/// This class is typically used internally by the reflection system to -/// represent method declarations within a class. -class _ReflectedDeclarationMirror extends ReflectedDeclaration { - /// The [dart.MethodMirror] instance representing the reflected method. - /// - /// This mirror provides access to the details of the method, such as its name, - /// parameters, return type, and other metadata. It is used internally by the - /// [_ReflectedDeclarationMirror] class to implement reflection capabilities - /// for method declarations. - final dart.MethodMirror mirror; - - /// Constructs a [_ReflectedDeclarationMirror] instance. - /// - /// This constructor initializes a new [_ReflectedDeclarationMirror] with the given [name] - /// and [mirror]. It uses the [dart.MethodMirror]'s [isStatic] property to determine - /// if the declaration is static, and passes `null` as the initial value for the function. - /// - /// Parameters: - /// - [name]: A [String] representing the name of the declaration. - /// - [mirror]: A [dart.MethodMirror] representing the reflected method. - /// - /// The constructor calls the superclass constructor with the provided [name], - /// the [isStatic] property from the [mirror], and `null` for the function parameter. - _ReflectedDeclarationMirror(String name, this.mirror) - : super(name, mirror.isStatic, null); - - /// Determines if this declaration is static. - /// - /// This getter overrides the base implementation to provide information - /// about whether the reflected declaration is static or not. It directly - /// accesses the [isStatic] property of the underlying [dart.MethodMirror]. - /// - /// Returns: - /// A [bool] value: - /// - `true` if the declaration is static. - /// - `false` if the declaration is not static (i.e., it's an instance method). - /// - /// This property is useful for determining the nature of the reflected - /// declaration, particularly when working with class methods and properties. - @override - bool get isStatic => mirror.isStatic; - - /// Retrieves a [ReflectedFunction] representation of this declaration. - /// - /// This getter overrides the base implementation to provide a [ReflectedFunction] - /// that represents the method associated with this declaration. It creates a new - /// [_ReflectedMethodMirror] instance using the underlying [dart.MethodMirror]. - /// - /// Returns: - /// A [ReflectedFunction] object that represents the method of this declaration. - /// - /// This property is useful for accessing detailed information about the method, - /// such as its parameters, return type, and other attributes, in a way that's - /// consistent with the reflection API. - @override - ReflectedFunction get function => _ReflectedMethodMirror(mirror); -} - -/// Represents a reflected instance of an object using dart:mirrors. -/// -/// This class extends [ReflectedInstance] and wraps a [dart.InstanceMirror] -/// to provide reflection capabilities for object instances in Dart. -/// -/// Key features: -/// - Reflects the type and runtime type of the instance -/// - Provides access to the underlying object (reflectee) -/// - Allows retrieval of field values through reflection -/// -/// This class is typically used internally by the reflection system to -/// represent instances of objects and provide reflective access to their fields. -class _ReflectedInstanceMirror extends ReflectedInstance { - /// The [dart.InstanceMirror] representing the reflected instance. - /// - /// This mirror provides access to the details of the object instance, such as its type, - /// fields, and methods. It is used internally by the [_ReflectedInstanceMirror] class - /// to implement reflection capabilities for object instances. - /// - /// The mirror allows for dynamic inspection and manipulation of the object's state - /// and behavior at runtime, enabling powerful reflection features. - final dart.InstanceMirror mirror; - - /// Constructs a [_ReflectedInstanceMirror] instance. - /// - /// This constructor initializes a new [_ReflectedInstanceMirror] with the given [mirror]. - /// It uses the [dart.InstanceMirror]'s [type] property to create [_ReflectedClassMirror] - /// instances for both the type and runtime type of the reflected instance. - /// - /// Parameters: - /// - [mirror]: A [dart.InstanceMirror] representing the reflected instance. - /// - /// The constructor calls the superclass constructor with: - /// - A [_ReflectedClassMirror] of the instance's type - /// - A [_ReflectedClassMirror] of the instance's runtime type - /// - The [reflectee] of the mirror, which is the actual object being reflected - /// - /// This setup allows the [_ReflectedInstanceMirror] to provide access to both - /// the compile-time and runtime type information of the reflected instance, - /// as well as the underlying object itself. - _ReflectedInstanceMirror(this.mirror) - : super(_ReflectedClassMirror(mirror.type), - _ReflectedClassMirror(mirror.type), mirror.reflectee); - - /// Retrieves the value of a field from the reflected instance. - /// - /// This method allows access to field values of the object represented by this - /// [_ReflectedInstanceMirror] through reflection. - /// - /// Parameters: - /// - [name]: A [String] representing the name of the field to retrieve. - /// - /// Returns: - /// A [ReflectedInstance] representing the value of the specified field. - /// This returned instance is wrapped in a [_ReflectedInstanceMirror]. - /// - /// Throws: - /// May throw exceptions if the field does not exist or if access is not allowed. - /// - /// Example: - /// ```dart - /// var fieldValue = reflectedInstance.getField('myField'); - /// ``` - /// - /// Note: - /// This method uses the underlying [dart.InstanceMirror]'s [getField] method - /// to perform the actual field access. - @override - ReflectedInstance getField(String name) { - return _ReflectedInstanceMirror(mirror.getField(Symbol(name))); - } -} - -/// Represents a reflected method using dart:mirrors. -/// -/// This class extends [ReflectedFunction] and wraps a [dart.MethodMirror] -/// to provide reflection capabilities for methods in Dart. -/// -/// Key features: -/// - Reflects method name, parameters, and return type -/// - Provides access to method metadata (annotations) -/// - Supports invocation of the reflected method (if a ClosureMirror is available) -/// -/// The class uses both [dart.MethodMirror] and optionally [dart.ClosureMirror] -/// to represent and potentially invoke the reflected method. -/// -/// Usage: -/// - Created internally by the reflection system to represent methods -/// - Can be used to inspect method details or invoke the method if a ClosureMirror is provided -/// -/// Note: -/// - Invocation is only possible if a ClosureMirror is provided during construction -/// - Throws a StateError if invoke is called without a ClosureMirror -class _ReflectedMethodMirror extends ReflectedFunction { - /// The [dart.MethodMirror] instance representing the reflected method. - /// - /// This mirror provides access to the details of the method, such as its name, - /// parameters, return type, and other metadata. It is used internally by the - /// [_ReflectedMethodMirror] class to implement reflection capabilities - /// for methods. - /// - /// The [dart.MethodMirror] is a crucial component in the reflection process, - /// allowing for introspection of method properties and behavior at runtime. - final dart.MethodMirror mirror; - - /// An optional [dart.ClosureMirror] representing the closure of the reflected method. - /// - /// This field is used to store a [dart.ClosureMirror] when the reflected method - /// is associated with a callable object (i.e., a closure). The presence of this - /// mirror enables the [invoke] method to directly call the reflected method. - /// - /// If this field is null, it indicates that the reflected method cannot be - /// directly invoked through this [_ReflectedMethodMirror] instance. - /// - /// Note: - /// - This field is crucial for supporting method invocation via reflection. - /// - It's typically set when reflecting on instance methods or standalone functions. - /// - For class-level method declarations that aren't bound to an instance, - /// this field may be null. - final dart.ClosureMirror? closureMirror; - - /// Constructs a [_ReflectedMethodMirror] instance. - /// - /// This constructor initializes a new [_ReflectedMethodMirror] with the given [mirror] - /// and optional [closureMirror]. It extracts various properties from the [dart.MethodMirror] - /// to populate the superclass constructor. - /// - /// Parameters: - /// - [mirror]: A [dart.MethodMirror] representing the reflected method. - /// - [closureMirror]: An optional [dart.ClosureMirror] for method invocation. - /// - /// The constructor initializes the following: - /// - Method name from the mirror's [simpleName] - /// - An empty list of reflected type parameters - /// - Metadata (annotations) as [_ReflectedInstanceMirror] objects - /// - Reflected parameters using [_reflectParameter] - /// - Getter and setter flags from the mirror - /// - Return type, using [dynamic] if the mirror doesn't have a reflected type - /// - /// This setup allows the [_ReflectedMethodMirror] to provide comprehensive - /// reflection capabilities for the method, including its signature, metadata, - /// and potential invocation (if a [closureMirror] is provided). - _ReflectedMethodMirror(this.mirror, [this.closureMirror]) - : super( - dart.MirrorSystem.getName(mirror.simpleName), - [], - mirror.metadata - .map((mirror) => _ReflectedInstanceMirror(mirror)) - .toList(), - mirror.parameters.map(_reflectParameter).toList(), - mirror.isGetter, - mirror.isSetter, - returnType: !mirror.returnType.hasReflectedType - ? const MirrorsReflector().reflectType(dynamic) - : const MirrorsReflector() - .reflectType(mirror.returnType.reflectedType)); - - /// Reflects a parameter of a method using dart:mirrors. - /// - /// This static method creates a [ReflectedParameter] instance from a given [dart.ParameterMirror]. - /// It extracts various properties from the mirror to construct a comprehensive reflection of the parameter. - /// - /// Parameters: - /// - [mirror]: A [dart.ParameterMirror] representing the parameter to be reflected. - /// - /// Returns: - /// A [ReflectedParameter] instance containing the reflected information of the parameter. - /// - /// The method extracts the following information: - /// - Parameter name from the mirror's [simpleName] - /// - Metadata (annotations) as [_ReflectedInstanceMirror] objects - /// - Parameter type, reflected using [MirrorsReflector] - /// - Whether the parameter is required (not optional) - /// - Whether the parameter is named - /// - /// This method is typically used internally by the reflection system to create - /// parameter reflections for method signatures. - static ReflectedParameter _reflectParameter(dart.ParameterMirror mirror) { - return ReflectedParameter( - dart.MirrorSystem.getName(mirror.simpleName), - mirror.metadata - .map((mirror) => _ReflectedInstanceMirror(mirror)) - .toList(), - const MirrorsReflector().reflectType(mirror.type.reflectedType), - !mirror.isOptional, - mirror.isNamed); - } - - /// Invokes the reflected method with the given invocation details. - /// - /// This method allows for dynamic invocation of the reflected method using the - /// provided [Invocation] object. It requires that a [closureMirror] was provided - /// during the construction of this [_ReflectedMethodMirror]. - /// - /// Parameters: - /// - [invocation]: An [Invocation] object containing the details of the method call, - /// including the method name, positional arguments, and named arguments. - /// - /// Returns: - /// A [ReflectedInstance] representing the result of the method invocation. - /// - /// Throws: - /// - [StateError] if this [_ReflectedMethodMirror] was created without a [closureMirror], - /// indicating that direct invocation is not possible. - /// - /// Example: - /// ```dart - /// var result = reflectedMethod.invoke(Invocation.method(#methodName, [arg1, arg2])); - /// ``` - /// - /// Note: - /// This method relies on the presence of a [closureMirror] to perform the actual - /// invocation. If no [closureMirror] is available, it means the reflected method - /// cannot be directly invoked, and an error will be thrown. - @override - ReflectedInstance invoke(Invocation invocation) { - if (closureMirror == null) { - throw StateError( - 'This object was reflected without a ClosureMirror, and therefore cannot be directly invoked.'); - } - - return _ReflectedInstanceMirror(closureMirror!.invoke(invocation.memberName, - invocation.positionalArguments, invocation.namedArguments)); - } -} diff --git a/packages/container/container/lib/src/reflectable/reflectable.dart b/packages/container/container/lib/src/reflectable/reflectable.dart deleted file mode 100644 index 8e88713..0000000 --- a/packages/container/container/lib/src/reflectable/reflectable.dart +++ /dev/null @@ -1,8 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ diff --git a/packages/container/container/lib/src/reflector.dart b/packages/container/container/lib/src/reflector.dart deleted file mode 100644 index 3b72136..0000000 --- a/packages/container/container/lib/src/reflector.dart +++ /dev/null @@ -1,298 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:collection/collection.dart'; -import 'package:quiver/core.dart'; - -/// Abstract class representing a reflector for introspection of Dart types and instances. -/// -/// This class provides methods to reflect on various Dart constructs such as classes, -/// functions, types, and instances. It allows for runtime inspection and manipulation -/// of code elements. -/// -/// The methods in this class are designed to be implemented by concrete reflector -/// classes, potentially using different reflection mechanisms (e.g., mirrors, code -/// generation). -/// -/// Note: The `reflectFutureOf` method throws an `UnsupportedError` by default and -/// requires `dart:mirrors` for implementation. -abstract class Reflector { - /// Constructs a new [Reflector] instance. - /// - /// This constructor is declared as `const` to allow for compile-time constant creation - /// of [Reflector] instances. Subclasses of [Reflector] may override this constructor - /// to provide their own initialization logic if needed. - const Reflector(); - - String? getName(Symbol symbol); - - ReflectedClass? reflectClass(Type clazz); - - ReflectedFunction? reflectFunction(Function function); - - ReflectedType? reflectType(Type type); - - ReflectedInstance? reflectInstance(Object object); - - ReflectedType reflectFutureOf(Type type) { - throw UnsupportedError('`reflectFutureOf` requires `dart:mirrors`.'); - } -} - -/// Represents a reflected instance of an object. -/// -/// This abstract class provides a way to introspect and manipulate object instances -/// at runtime. It encapsulates information about the object's type, class, and the -/// actual object instance (reflectee). -/// -/// The [type] property represents the reflected type of the instance. -/// The [clazz] property represents the reflected class of the instance. -/// The [reflectee] property holds the actual object instance being reflected. -/// -/// This class also provides methods for comparing instances and accessing fields. -/// -/// Use the [getField] method to retrieve a reflected instance of a specific field. -abstract class ReflectedInstance { - final ReflectedType type; - final ReflectedClass clazz; - final Object? reflectee; - - const ReflectedInstance(this.type, this.clazz, this.reflectee); - - @override - int get hashCode => hash2(type, clazz); - - @override - bool operator ==(other) => - other is ReflectedInstance && other.type == type && other.clazz == clazz; - - ReflectedInstance getField(String name); -} - -/// Represents a reflected type in the Dart language. -/// -/// This abstract class encapsulates information about a Dart type, including its name, -/// type parameters, and the actual Dart [Type] it represents. -/// -/// The [name] property holds the name of the type. -/// The [typeParameters] list contains the type parameters if the type is generic. -/// The [reflectedType] property holds the actual Dart [Type] being reflected. -/// -/// This class provides methods for creating new instances of the type, comparing types, -/// and checking type assignability. -/// -/// The [newInstance] method allows for dynamic creation of new instances of the type. -/// The [isAssignableTo] method checks if this type is assignable to another type. -/// -/// This class also overrides [hashCode] and [operator ==] for proper equality comparisons. -abstract class ReflectedType { - final String name; - final List typeParameters; - final Type reflectedType; - - const ReflectedType(this.name, this.typeParameters, this.reflectedType); - - @override - int get hashCode => hash3(name, typeParameters, reflectedType); - - @override - bool operator ==(other) => - other is ReflectedType && - other.name == name && - const ListEquality() - .equals(other.typeParameters, typeParameters) && - other.reflectedType == reflectedType; - - ReflectedInstance newInstance( - String constructorName, List positionalArguments, - [Map namedArguments = const {}, - List typeArguments = const []]); - - bool isAssignableTo(ReflectedType? other); -} - -/// Represents a reflected class in the Dart language. -/// -/// This abstract class extends [ReflectedType] and provides additional information -/// specific to classes, including annotations, constructors, and declarations. -/// -/// The [annotations] list contains reflected instances of annotations applied to the class. -/// The [constructors] list contains reflected functions representing the class constructors. -/// The [declarations] list contains reflected declarations (fields, methods, etc.) of the class. -/// -/// This class overrides [hashCode] and [operator ==] to include the additional properties -/// in equality comparisons and hash code calculations. -abstract class ReflectedClass extends ReflectedType { - final List annotations; - final List constructors; - final List declarations; - - const ReflectedClass( - String name, - List typeParameters, - this.annotations, - this.constructors, - this.declarations, - Type reflectedType) - : super(name, typeParameters, reflectedType); - - @override - int get hashCode => - hash4(super.hashCode, annotations, constructors, declarations); - - @override - bool operator ==(other) => - other is ReflectedClass && - super == other && - const ListEquality() - .equals(other.annotations, annotations) && - const ListEquality() - .equals(other.constructors, constructors) && - const ListEquality() - .equals(other.declarations, declarations); -} - -/// Represents a reflected declaration in the Dart language. -/// -/// This class encapsulates information about a declaration within a class or object, -/// such as a method, field, or property. -/// -/// The [name] property holds the name of the declaration. -/// The [isStatic] property indicates whether the declaration is static. -/// The [function] property, if non-null, represents the reflected function associated -/// with this declaration (applicable for methods and some properties). -/// -/// This class provides methods for comparing declarations and calculating hash codes. -/// It overrides [hashCode] and [operator ==] for proper equality comparisons. -class ReflectedDeclaration { - final String name; - final bool isStatic; - final ReflectedFunction? function; - - const ReflectedDeclaration(this.name, this.isStatic, this.function); - - @override - int get hashCode => hash3(name, isStatic, function); - - @override - bool operator ==(other) => - other is ReflectedDeclaration && - other.name == name && - other.isStatic == isStatic && - other.function == function; -} - -/// Represents a reflected function in the Dart language. -/// -/// This abstract class encapsulates information about a function, including its name, -/// type parameters, annotations, return type, parameters, and whether it's a getter or setter. -/// -/// The [name] property holds the name of the function. -/// The [typeParameters] list contains the type parameters if the function is generic. -/// The [annotations] list contains reflected instances of annotations applied to the function. -/// The [returnType] property represents the function's return type (if applicable). -/// The [parameters] list contains the function's parameters. -/// The [isGetter] and [isSetter] properties indicate if the function is a getter or setter. -/// -/// This class provides methods for comparing functions and calculating hash codes. -/// It also includes an [invoke] method for dynamically calling the function. -/// -/// This class overrides [hashCode] and [operator ==] for proper equality comparisons. -abstract class ReflectedFunction { - final String name; - final List typeParameters; - final List annotations; - final ReflectedType? returnType; - final List parameters; - final bool isGetter, isSetter; - - const ReflectedFunction(this.name, this.typeParameters, this.annotations, - this.parameters, this.isGetter, this.isSetter, - {this.returnType}); - - @override - int get hashCode => hashObjects([ - name, - typeParameters, - annotations, - returnType, - parameters, - isGetter, - isSetter - ]); - - @override - bool operator ==(other) => - other is ReflectedFunction && - other.name == name && - const ListEquality() - .equals(other.typeParameters, typeParameters) && - const ListEquality() - .equals(other.annotations, annotations) && - other.returnType == returnType && - const ListEquality() - .equals(other.parameters, other.parameters) && - other.isGetter == isGetter && - other.isSetter == isSetter; - - ReflectedInstance invoke(Invocation invocation); -} - -/// Represents a reflected parameter in the Dart language. -/// -/// This class encapsulates information about a function or method parameter, -/// including its name, annotations, type, and properties such as whether it's -/// required or named. -/// -/// Properties: -/// - [name]: The name of the parameter. -/// - [annotations]: A list of reflected instances of annotations applied to the parameter. -/// - [type]: The reflected type of the parameter. -/// - [isRequired]: Indicates whether the parameter is required. -/// - [isNamed]: Indicates whether the parameter is a named parameter. -/// -/// This class provides methods for comparing parameters and calculating hash codes. -/// It overrides [hashCode] and [operator ==] for proper equality comparisons. -class ReflectedParameter { - final String name; - final List annotations; - final ReflectedType type; - final bool isRequired; - final bool isNamed; - - const ReflectedParameter( - this.name, this.annotations, this.type, this.isRequired, this.isNamed); - - @override - int get hashCode => - hashObjects([name, annotations, type, isRequired, isNamed]); - - @override - bool operator ==(other) => - other is ReflectedParameter && - other.name == name && - const ListEquality() - .equals(other.annotations, annotations) && - other.type == type && - other.isRequired == isRequired && - other.isNamed == isNamed; -} - -class ReflectedTypeParameter { - final String name; - - const ReflectedTypeParameter(this.name); - - @override - int get hashCode => hashObjects([name]); - - @override - bool operator ==(other) => - other is ReflectedTypeParameter && other.name == name; -} diff --git a/packages/container/container/lib/src/static/static.dart b/packages/container/container/lib/src/static/static.dart deleted file mode 100644 index ff9c4df..0000000 --- a/packages/container/container/lib/src/static/static.dart +++ /dev/null @@ -1,179 +0,0 @@ -/* - * This file is part of the Protevus Platform. - * - * (C) Protevus - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import 'package:platform_container/container.dart'; - -/// A static implementation of the [Reflector] class that performs simple [Map] lookups. -/// -/// `package:platform_container_generator` uses this to create reflectors from analysis metadata. -class StaticReflector extends Reflector { - /// A map that associates [Symbol] objects with their corresponding string names. - /// - /// This map is used to store and retrieve the string representations of symbols, - /// which can be useful for reflection and debugging purposes. - final Map names; - - /// A map that associates [Type] objects with their corresponding [ReflectedType] objects. - /// - /// This map is used to store and retrieve reflection information for different types, - /// allowing for runtime introspection of type metadata and structure. - final Map types; - - /// A map that associates [Function] objects with their corresponding [ReflectedFunction] objects. - /// - /// This map is used to store and retrieve reflection information for functions, - /// enabling runtime introspection of function metadata, parameters, and return types. - final Map functions; - - /// A map that associates [Object] instances with their corresponding [ReflectedInstance] objects. - /// - /// This map is used to store and retrieve reflection information for specific object instances, - /// allowing for runtime introspection of object properties, methods, and metadata. - final Map instances; - - /// Creates a new [StaticReflector] instance with optional parameters. - /// - /// The [StaticReflector] constructor allows you to initialize the reflector - /// with pre-populated maps for names, types, functions, and instances. - /// - /// Parameters: - /// - [names]: A map of [Symbol] to [String] for symbol name lookups. Defaults to an empty map. - /// - [types]: A map of [Type] to [ReflectedType] for type reflection. Defaults to an empty map. - /// - [functions]: A map of [Function] to [ReflectedFunction] for function reflection. Defaults to an empty map. - /// - [instances]: A map of [Object] to [ReflectedInstance] for instance reflection. Defaults to an empty map. - /// - /// All parameters are optional and default to empty constant maps if not provided. - const StaticReflector( - {this.names = const {}, - this.types = const {}, - this.functions = const {}, - this.instances = const {}}); - - /// Returns the string name associated with the given [Symbol]. - /// - /// This method looks up the string representation of the provided [symbol] - /// in the [names] map. If the symbol is found, its corresponding string - /// name is returned. If the symbol is not found in the map, an [ArgumentError] - /// is thrown. - /// - /// Parameters: - /// - [symbol]: The [Symbol] for which to retrieve the string name. - /// - /// Returns: - /// The string name associated with the given [symbol], or null if not found. - /// - /// Throws: - /// - [ArgumentError]: If the provided [symbol] is not found in the [names] map. - @override - String? getName(Symbol symbol) { - if (!names.containsKey(symbol)) { - throw ArgumentError( - 'The value of $symbol is unknown - it was not generated.'); - } - - return names[symbol]; - } - - /// Reflects a class based on its [Type]. - /// - /// This method attempts to reflect the given class [Type] by calling [reflectType] - /// and casting the result to [ReflectedClass]. If the reflection is successful - /// and the result is a [ReflectedClass], it is returned. Otherwise, null is returned. - /// - /// Parameters: - /// - [clazz]: The [Type] of the class to reflect. - /// - /// Returns: - /// A [ReflectedClass] instance if the reflection is successful and the result - /// is a [ReflectedClass], or null otherwise. - @override - ReflectedClass? reflectClass(Type clazz) => - reflectType(clazz) as ReflectedClass?; - - /// Reflects a function based on its [Function] object. - /// - /// This method attempts to retrieve reflection information for the given [function] - /// from the [functions] map. If the function is found in the map, its corresponding - /// [ReflectedFunction] object is returned. If the function is not found, an - /// [ArgumentError] is thrown. - /// - /// Parameters: - /// - [function]: The [Function] object to reflect. - /// - /// Returns: - /// A [ReflectedFunction] object containing reflection information about the - /// given function, or null if not found. - /// - /// Throws: - /// - [ArgumentError]: If there is no reflection information available for - /// the given [function]. - @override - ReflectedFunction? reflectFunction(Function function) { - if (!functions.containsKey(function)) { - throw ArgumentError( - 'There is no reflection information available about $function.'); - } - - return functions[function]; - } - - /// Reflects an object instance to retrieve its reflection information. - /// - /// This method attempts to retrieve reflection information for the given [object] - /// from the [instances] map. If the object is found in the map, its corresponding - /// [ReflectedInstance] object is returned. If the object is not found, an - /// [ArgumentError] is thrown. - /// - /// Parameters: - /// - [object]: The object instance to reflect. - /// - /// Returns: - /// A [ReflectedInstance] object containing reflection information about the - /// given object instance, or null if not found. - /// - /// Throws: - /// - [ArgumentError]: If there is no reflection information available for - /// the given [object]. - @override - ReflectedInstance? reflectInstance(Object object) { - if (!instances.containsKey(object)) { - throw ArgumentError( - 'There is no reflection information available about $object.'); - } - - return instances[object]; - } - - /// Reflects a type to retrieve its reflection information. - /// - /// This method attempts to retrieve reflection information for the given [type] - /// from the [types] map. If the type is found in the map, its corresponding - /// [ReflectedType] object is returned. If the type is not found, an - /// [ArgumentError] is thrown. - /// - /// Parameters: - /// - [type]: The [Type] to reflect. - /// - /// Returns: - /// A [ReflectedType] object containing reflection information about the - /// given type, or null if not found. - /// - /// Throws: - /// - [ArgumentError]: If there is no reflection information available for - /// the given [type]. - @override - ReflectedType? reflectType(Type type) { - if (!types.containsKey(type)) { - throw ArgumentError( - 'There is no reflection information available about $type.'); - } - - return types[type]; - } -} diff --git a/packages/container/container/lib/src/throwing.dart b/packages/container/container/lib/src/throwing.dart deleted file mode 100644 index 322940f..0000000 --- a/packages/container/container/lib/src/throwing.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:platform_container/src/container_const.dart'; -import 'empty/empty.dart'; -import 'reflector.dart'; - -/// A [Reflector] implementation that throws exceptions on all attempts -/// to perform reflection. -/// -/// Use this in contexts where you know you won't need any reflective capabilities. -class ThrowingReflector extends Reflector { - /// The error message to give the end user when an [UnsupportedError] is thrown. - final String errorMessage; - - /* - static const String defaultErrorMessage = - 'You attempted to perform a reflective action, but you are using `ThrowingReflector`, ' - 'a class which disables reflection. Consider using the `MirrorsReflector` ' - 'class if you need reflection.'; - */ - - /// Creates a [ThrowingReflector] instance. - /// - /// [errorMessage] is the message to be used when throwing an [UnsupportedError]. - /// If not provided, it defaults to [ContainerConst.defaultErrorMessage]. - const ThrowingReflector( - {this.errorMessage = ContainerConst.defaultErrorMessage}); - - /// Retrieves the name associated with the given [symbol]. - /// - /// This method delegates the task to an instance of [EmptyReflector]. - /// It returns the name as a [String] if found, or `null` if not found. - /// - /// [symbol] is the [Symbol] for which to retrieve the name. - /// - /// Returns a [String] representing the name of the symbol, or `null` if not found. - @override - String? getName(Symbol symbol) => const EmptyReflector().getName(symbol); - - /// Creates and returns an [UnsupportedError] with the specified [errorMessage]. - /// - /// This method is used internally to generate consistent error messages - /// when reflection operations are attempted on this [ThrowingReflector]. - /// - /// Returns an [UnsupportedError] instance with the configured error message. - UnsupportedError _error() => UnsupportedError(errorMessage); - - /// Reflects on a given class type and throws an [UnsupportedError]. - /// - /// This method is part of the [ThrowingReflector] implementation and is designed - /// to prevent reflective operations. When called, it throws an [UnsupportedError] - /// with the configured error message. - /// - /// [clazz] is the [Type] of the class to reflect on. - /// - /// Throws an [UnsupportedError] when invoked, as reflection is not supported. - @override - ReflectedClass reflectClass(Type clazz) => throw _error(); - - /// Reflects on a given object instance and throws an [UnsupportedError]. - /// - /// This method is part of the [ThrowingReflector] implementation and is designed - /// to prevent reflective operations on object instances. When called, it throws - /// an [UnsupportedError] with the configured error message. - /// - /// [object] is the object instance to reflect on. - /// - /// Throws an [UnsupportedError] when invoked, as reflection is not supported. - @override - ReflectedInstance reflectInstance(Object object) => throw _error(); - - /// Reflects on a given type and throws an [UnsupportedError]. - /// - /// This method is part of the [ThrowingReflector] implementation and is designed - /// to prevent reflective operations on types. When called, it throws an - /// [UnsupportedError] with the configured error message. - /// - /// [type] is the [Type] to reflect on. - /// - /// Throws an [UnsupportedError] when invoked, as reflection is not supported. - @override - ReflectedType reflectType(Type type) => throw _error(); - - /// Reflects on a given function and throws an [UnsupportedError]. - /// - /// This method is part of the [ThrowingReflector] implementation and is designed - /// to prevent reflective operations on functions. When called, it throws an - /// [UnsupportedError] with the configured error message. - /// - /// [function] is the [Function] to reflect on. - /// - /// Throws an [UnsupportedError] when invoked, as reflection is not supported. - @override - ReflectedFunction reflectFunction(Function function) => throw _error(); -} diff --git a/packages/container/container/pubspec.yaml b/packages/container/container/pubspec.yaml deleted file mode 100644 index 8982eb5..0000000 --- a/packages/container/container/pubspec.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: platform_container -version: 9.0.0 -description: Protevus Platform hierarchical DI container, and pluggable backends for reflection. -homepage: https://protevus.com -documentation: https://docs.protevus.com -repository: https://git.protevus.com/protevus/platform/src/branch/main/packages/container/container -environment: - sdk: '>=3.3.0 <4.0.0' -dependencies: - collection: ^1.19.1 - quiver: ^3.2.2 -dev_dependencies: - test: ^1.25.8 - lints: ^4.0.0 diff --git a/packages/container/container/test/common.dart b/packages/container/container/test/common.dart deleted file mode 100644 index 327ef93..0000000 --- a/packages/container/container/test/common.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'dart:async'; - -import 'package:platform_container/container.dart'; -import 'package:test/test.dart'; - -void returnVoidFromAFunction(int x) {} - -void testReflector(Reflector reflector) { - var blaziken = Pokemon('Blaziken', PokemonType.fire); - late Container container; - - setUp(() { - container = Container(reflector); - container.registerSingleton(blaziken); - container.registerFactory>((_) async => 46); - }); - - test('get field', () { - var blazikenMirror = reflector.reflectInstance(blaziken)!; - expect(blazikenMirror.getField('type').reflectee, blaziken.type); - }); - - group('reflectFunction', () { - var mirror = reflector.reflectFunction(returnVoidFromAFunction); - - test('void return type returns dynamic', () { - expect(mirror!.returnType, reflector.reflectType(dynamic)); - }); - - test('counts parameters', () { - expect(mirror!.parameters, hasLength(1)); - }); - - test('counts types parameters', () { - expect(mirror!.typeParameters, isEmpty); - }); - - test('correctly reflects parameter types', () { - var p = mirror!.parameters[0]; - expect(p.name, 'x'); - expect(p.isRequired, true); - expect(p.isNamed, false); - expect(p.annotations, isEmpty); - expect(p.type, reflector.reflectType(int)); - }); - }); - - test('make on singleton type returns singleton', () { - expect(container.make(Pokemon), blaziken); - }); - - test('make with generic returns same as make with explicit type', () { - expect(container.make(), blaziken); - }); - - test('make async returns async object', () async { - expect(container.makeAsync(), completion(46)); - }); - - test('make async returns sync object', () async { - expect(container.makeAsync(), completion(blaziken)); - }); - - test('make on aliased singleton returns singleton', () { - container.registerSingleton(blaziken, as: StateError); - expect(container.make(StateError), blaziken); - }); - - test('constructor injects singleton', () { - var lower = container.make(); - expect(lower.lowercaseName, blaziken.name.toLowerCase()); - }); - - test('newInstance works', () { - var type = container.reflector.reflectType(Pokemon)!; - var instance = - type.newInstance('changeName', [blaziken, 'Charizard']).reflectee - as Pokemon; - print(instance); - expect(instance.name, 'Charizard'); - expect(instance.type, PokemonType.fire); - }); - - test('isAssignableTo', () { - var pokemonType = container.reflector.reflectType(Pokemon); - var kantoPokemonType = container.reflector.reflectType(KantoPokemon)!; - - expect(kantoPokemonType.isAssignableTo(pokemonType), true); - expect( - kantoPokemonType - .isAssignableTo(container.reflector.reflectType(String)), - false); - }); -} - -class LowerPokemon { - final Pokemon pokemon; - - LowerPokemon(this.pokemon); - - String get lowercaseName => pokemon.name.toLowerCase(); -} - -class Pokemon { - final String name; - final PokemonType type; - - Pokemon(this.name, this.type); - - factory Pokemon.changeName(Pokemon other, String name) { - return Pokemon(name, other.type); - } - - @override - String toString() => 'NAME: $name, TYPE: $type'; -} - -class KantoPokemon extends Pokemon { - KantoPokemon(super.name, super.type); -} - -enum PokemonType { water, fire, grass, ice, poison, flying } diff --git a/packages/container/container/test/empty_reflector_test.dart b/packages/container/container/test/empty_reflector_test.dart deleted file mode 100644 index a6ef382..0000000 --- a/packages/container/container/test/empty_reflector_test.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'package:platform_container/container.dart'; -import 'package:test/test.dart'; - -void main() { - var reflector = const EmptyReflector(); - - test('getName', () { - expect(reflector.getName(#foo), 'foo'); - expect(reflector.getName(#==), '=='); - }); - - group('reflectClass', () { - var mirror = reflector.reflectClass(Truck); - - test('name returns empty', () { - expect(mirror.name, '(empty)'); - }); - - test('annotations returns empty', () { - expect(mirror.annotations, isEmpty); - }); - - test('typeParameters returns empty', () { - expect(mirror.typeParameters, isEmpty); - }); - - test('declarations returns empty', () { - expect(mirror.declarations, isEmpty); - }); - - test('constructors returns empty', () { - expect(mirror.constructors, isEmpty); - }); - - test('reflectedType returns Object', () { - expect(mirror.reflectedType, Object); - }); - - test('cannot call newInstance', () { - expect(() => mirror.newInstance('', []), throwsUnsupportedError); - }); - - test('isAssignableTo self', () { - expect(mirror.isAssignableTo(mirror), true); - }); - }); - - group('reflectType', () { - var mirror = reflector.reflectType(Truck); - - test('name returns empty', () { - expect(mirror.name, '(empty)'); - }); - - test('typeParameters returns empty', () { - expect(mirror.typeParameters, isEmpty); - }); - - test('reflectedType returns Object', () { - expect(mirror.reflectedType, Object); - }); - - test('cannot call newInstance', () { - expect(() => mirror.newInstance('', []), throwsUnsupportedError); - }); - - test('isAssignableTo self', () { - expect(mirror.isAssignableTo(mirror), true); - }); - }); - - group('reflectFunction', () { - void doIt(int x) {} - - var mirror = reflector.reflectFunction(doIt); - - test('name returns empty', () { - expect(mirror.name, '(empty)'); - }); - - test('annotations returns empty', () { - expect(mirror.annotations, isEmpty); - }); - - test('typeParameters returns empty', () { - expect(mirror.typeParameters, isEmpty); - }); - - test('parameters returns empty', () { - expect(mirror.parameters, isEmpty); - }); - - test('return type is dynamic', () { - expect(mirror.returnType, reflector.reflectType(dynamic)); - }); - - test('isGetter returns false', () { - expect(mirror.isGetter, false); - }); - - test('isSetter returns false', () { - expect(mirror.isSetter, false); - }); - - test('cannot invoke', () { - var invocation = Invocation.method(#drive, []); - expect(() => mirror.invoke(invocation), throwsUnsupportedError); - }); - }); - - group('reflectInstance', () { - var mirror = reflector.reflectInstance(Truck()); - - test('reflectee returns null', () { - expect(mirror.reflectee, null); - }); - - test('type returns empty', () { - expect(mirror.type.name, '(empty)'); - }); - - test('clazz returns empty', () { - expect(mirror.clazz.name, '(empty)'); - }); - - test('cannot getField', () { - expect(() => mirror.getField('wheelCount'), throwsUnsupportedError); - }); - }); -} - -class Truck { - int get wheelCount => 4; - - void drive() { - print('Vroom!!!'); - } -} diff --git a/packages/container/container/test/has_test.dart b/packages/container/container/test/has_test.dart deleted file mode 100644 index 9cce770..0000000 --- a/packages/container/container/test/has_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:platform_container/container.dart'; -import 'package:test/test.dart'; - -void main() { - late Container container; - - setUp(() { - container = Container(const EmptyReflector()) - ..registerSingleton(Song(title: 'I Wish')) - ..registerNamedSingleton('foo', 1) - ..registerFactory((container) { - return Artist( - name: 'Stevie Wonder', - song: container.make(), - ); - }); - }); - - test('hasNamed', () { - var child = container.createChild()..registerNamedSingleton('bar', 2); - expect(child.hasNamed('foo'), true); - expect(child.hasNamed('bar'), true); - expect(child.hasNamed('baz'), false); - }); - - test('has on singleton', () { - var result = container.has(); - expect(result, true); - }); - - test('has on factory', () { - expect(container.has(), true); - }); - - test('false if neither', () { - expect(container.has(), false); - }); -} - -class Artist { - final String? name; - final Song? song; - - Artist({this.name, this.song}); -} - -class Song { - final String? title; - - Song({this.title}); -} diff --git a/packages/container/container/test/lazy_test.dart b/packages/container/container/test/lazy_test.dart deleted file mode 100644 index 9564eb7..0000000 --- a/packages/container/container/test/lazy_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:platform_container/container.dart'; -import 'package:test/test.dart'; - -void main() { - test('returns the same instance', () { - var container = Container(const EmptyReflector()) - ..registerLazySingleton((_) => Dummy('a')); - - var first = container.make(); - expect(container.make(), first); - }); -} - -class Dummy { - final String s; - - Dummy(this.s); -} diff --git a/packages/container/container/test/mirrors_test.dart b/packages/container/container/test/mirrors_test.dart deleted file mode 100644 index 2b3680f..0000000 --- a/packages/container/container/test/mirrors_test.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'dart:async'; -import 'package:platform_container/container.dart'; -import 'package:platform_container/mirrors.dart'; -import 'package:test/test.dart'; -import 'common.dart'; - -void main() { - testReflector(const MirrorsReflector()); - - test('futureOf', () { - var r = MirrorsReflector(); - var fStr = r.reflectFutureOf(String); - expect(fStr.reflectedType.toString(), 'Future'); - // expect(fStr.reflectedType, Future.value(null).runtimeType); - }); - - test('concrete future make', () async { - var c = Container(MirrorsReflector()); - c.registerFactory>((_) async => 'hey'); - var fStr = c.reflector.reflectFutureOf(String); - var s1 = await c.make(fStr.reflectedType); - var s2 = await c.makeAsync(String); - print([s1, s2]); - expect(s1, s2); - }); -} diff --git a/packages/container/container/test/named_test.dart b/packages/container/container/test/named_test.dart deleted file mode 100644 index c43f119..0000000 --- a/packages/container/container/test/named_test.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:platform_container/container.dart'; -import 'package:test/test.dart'; - -void main() { - late Container container; - - setUp(() { - container = Container(const EmptyReflector()); - container.registerNamedSingleton('foo', Foo(bar: 'baz')); - }); - - test('fetch by name', () { - expect(container.findByName('foo').bar, 'baz'); - }); - - test('cannot redefine', () { - expect(() => container.registerNamedSingleton('foo', Foo(bar: 'quux')), - throwsStateError); - }); - - test('throws on unknown name', () { - expect(() => container.findByName('bar'), throwsStateError); - }); - - test('throws on incorrect type', () { - expect(() => container.findByName>('foo'), throwsA(anything)); - }); -} - -class Foo { - final String? bar; - - Foo({this.bar}); -} diff --git a/packages/container/container/test/throwing_reflector_test.dart b/packages/container/container/test/throwing_reflector_test.dart deleted file mode 100644 index c6a297c..0000000 --- a/packages/container/container/test/throwing_reflector_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:platform_container/container.dart'; -import 'package:test/test.dart'; - -void main() { - var reflector = const ThrowingReflector(); - - test('getName', () { - expect(reflector.getName(#foo), 'foo'); - expect(reflector.getName(#==), '=='); - }); - - test('reflectClass fails', () { - expect(() => reflector.reflectClass(Truck), throwsUnsupportedError); - }); - - test('reflectType fails', () { - expect(() => reflector.reflectType(Truck), throwsUnsupportedError); - }); - - test('reflectFunction throws', () { - void doIt(int x) {} - expect(() => reflector.reflectFunction(doIt), throwsUnsupportedError); - }); - - test('reflectInstance throws', () { - expect(() => reflector.reflectInstance(Truck()), throwsUnsupportedError); - }); -} - -class Truck { - int get wheelCount => 4; - - void drive() { - print('Vroom!!!'); - } -} diff --git a/packages/container/container_generator/.gitignore b/packages/container/container_generator/.gitignore deleted file mode 100644 index 4b16115..0000000 --- a/packages/container/container_generator/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# See https://www.dartlang.org/guides/libraries/private-files - -# Files and directories created by pub -.dart_tool/ -.packages -.pub/ -build/ -# If you're building an application, you may want to check-in your pubspec.lock -pubspec.lock - -# Directory created by dartdoc -# If you don't generate documentation locally you can remove this line. -doc/api/ - -test/*.reflectable.dart -example/*.reflectable.dart diff --git a/packages/container/container_generator/CHANGELOG.md b/packages/container/container_generator/CHANGELOG.md deleted file mode 100644 index 5fecd3e..0000000 --- a/packages/container/container_generator/CHANGELOG.md +++ /dev/null @@ -1,58 +0,0 @@ -# Change Log - -## 8.1.1 - -* Updated repository link - -## 8.1.0 - -* Updated `lints` to 3.0.0 -* Fixed analyser warnings - -## 8.0.0 - -* Require Dart >= 3.0 - -## 7.1.0-beta.1 - -* Require Dart >= 2.19 -* Upgraded `relectable` to 4.x.x - -## 7.0.0 - -* Require Dart >= 2.17 - -## 6.0.0 - -* Require Dart >= 2.16 - -## 5.0.0 - -* Skipped release - -## 4.0.0 - -* Skipped release - -## 3.0.1 - -* Updated `package:angel3_container` - -## 3.0.0 - -* Fixed NNBD issues -* All 9 test cases passed - -## 3.0.0-beta.1 - -* Migrated to support Dart >= 2.12 NNBD -* Updated linter to `package:lints` -* Updated to use `platform_` packages - -## 2.0.0 - -* Migrated to work with Dart >= 2.12 Non NNBD - -## 1.0.1 - -* Update for `pkg:angel_container@1.0.3`. diff --git a/packages/container/container_generator/LICENSE b/packages/container/container_generator/LICENSE deleted file mode 100644 index df5e063..0000000 --- a/packages/container/container_generator/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2021, dukefirehawk.com -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/container/container_generator/README.md b/packages/container/container_generator/README.md deleted file mode 100644 index 432ce12..0000000 --- a/packages/container/container_generator/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Protevus Container Generator - -![Pub Version (including pre-releases)](https://img.shields.io/pub/v/angel3_container_generator?include_prereleases) -[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) -[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) -[![License](https://img.shields.io/github/license/dart-backend/angel)](https://github.com/dart-backend/angel/tree/master/packages/container/angel3_container_generator/LICENSE) - -An alternative container for Protevus that uses `reflectable` package instead of `dart:mirrors` for reflection. However, `reflectable` has more limited relfection capabilities when compared to `dart:mirrors`. - -## Usage - -* Annotable the class with `@contained`. -* Run `dart run build_runner build ` -* Alternatively create a `build.xml` file with the following content - - ```yaml - targets: - $default: - builders: - reflectable: - generate_for: - - bin/**_controller.dart - options: - formatted: true - ``` - -## Known limitation - -* `analyser` 6.x is not supported due to `reflectable` -* Reflection on functions/closures is not supported -* Reflection on private declarations is not supported -* Reflection on generic type is not supported diff --git a/packages/container/container_generator/analysis_options.yaml b/packages/container/container_generator/analysis_options.yaml deleted file mode 100644 index ea2c9e9..0000000 --- a/packages/container/container_generator/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: package:lints/recommended.yaml \ No newline at end of file diff --git a/packages/container/container_generator/example/main.dart b/packages/container/container_generator/example/main.dart deleted file mode 100644 index bc24685..0000000 --- a/packages/container/container_generator/example/main.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'dart:async'; - -import 'package:platform_container/container.dart'; -import 'package:platform_container_generator/generator.dart'; - -Future main() async { - // Create a container instance. - Container container = Container(GeneratedReflector()); - - // Register a singleton. - container.registerSingleton(Engine(40)); - - // You can also omit the type annotation, in which the object's runtime type will be used. - // If you're injecting an abstract class, prefer the type annotation. - // - // container.registerSingleton(Engine(40)); - - // Register a factory that creates a truck. - container.registerFactory((container) { - return _TruckImpl(container.make()); - }); - - // Use `make` to create an instance. - var truck = container.make(); - - // You can also resolve injections asynchronously. - container.registerFactory>((_) async => 24); - print(await container.makeAsync()); - - // Asynchronous resolution also works for plain objects. - await container.makeAsync().then((t) => t.drive()); - - // Register a named singleton. - container.registerNamedSingleton('the_truck', truck); - - // Should print: 'Vroom! I have 40 horsepower in my engine.' - truck.drive(); - - // Should print the same. - container.findByName('the_truck').drive(); - - // We can make a child container with its own factory. - var childContainer = container.createChild(); - - childContainer.registerFactory((container) { - return _TruckImpl(Engine(5666)); - }); - - // Make a truck with 5666 HP. - childContainer.make().drive(); - - // However, calling `make` will return the Engine singleton we created above. - print(childContainer.make().horsePower); -} - -abstract class Truck { - void drive(); -} - -class Engine { - final int horsePower; - - Engine(this.horsePower); -} - -class _TruckImpl implements Truck { - final Engine engine; - - _TruckImpl(this.engine); - - @override - void drive() { - print('Vroom! I have ${engine.horsePower} horsepower in my engine.'); - } -} diff --git a/packages/container/container_generator/lib/generator.dart b/packages/container/container_generator/lib/generator.dart deleted file mode 100644 index 40dd09f..0000000 --- a/packages/container/container_generator/lib/generator.dart +++ /dev/null @@ -1,255 +0,0 @@ -import 'package:platform_container/container.dart'; -import 'package:reflectable/reflectable.dart'; - -/// A [Reflectable] instance that can be used as an annotation on types to generate metadata for them. -const Reflectable contained = ContainedReflectable(); - -@contained -class ContainedReflectable extends Reflectable { - const ContainedReflectable() - : super( - topLevelInvokeCapability, - typeAnnotationQuantifyCapability, - superclassQuantifyCapability, - libraryCapability, - invokingCapability, - metadataCapability, - reflectedTypeCapability, - typeCapability, - typingCapability); -} - -/// A [Reflector] instance that uses a [Reflectable] to reflect upon data. -class GeneratedReflector extends Reflector { - final Reflectable reflectable; - - const GeneratedReflector([this.reflectable = contained]); - - @override - String getName(Symbol symbol) { - return symbol.toString().substring(7); - } - - @override - ReflectedClass reflectClass(Type clazz) { - return reflectType(clazz) as ReflectedClass; - } - - @override - ReflectedFunction reflectFunction(Function function) { - if (!reflectable.canReflect(function)) { - throw UnsupportedError('Cannot reflect $function.'); - } - - var mirror = reflectable.reflect(function); - - if (mirror is ClosureMirror) { - return _GeneratedReflectedFunction(mirror.function, this, mirror); - } else { - throw ArgumentError('$function is not a Function.'); - } - } - - @override - ReflectedInstance reflectInstance(Object object) { - if (!reflectable.canReflect(object)) { - throw UnsupportedError('Cannot reflect $object.'); - } else { - var mirror = reflectable.reflect(object); - return _GeneratedReflectedInstance(mirror, this); - } - } - - @override - ReflectedType reflectType(Type type) { - if (!reflectable.canReflectType(type)) { - throw UnsupportedError('Cannot reflect $type.'); - } else { - var mirror = reflectable.reflectType(type); - return mirror is ClassMirror - ? _GeneratedReflectedClass(mirror, this) - : _GeneratedReflectedType(mirror); - } - } -} - -class _GeneratedReflectedInstance extends ReflectedInstance { - final InstanceMirror mirror; - final GeneratedReflector reflector; - - _GeneratedReflectedInstance(this.mirror, this.reflector) - : super(_GeneratedReflectedType(mirror.type), - _GeneratedReflectedClass(mirror.type, reflector), mirror.reflectee); - - @override - ReflectedType get type => clazz; - - @override - ReflectedInstance getField(String name) { - var result = mirror.invokeGetter(name)!; - var instance = reflector.reflectable.reflect(result); - return _GeneratedReflectedInstance(instance, reflector); - } -} - -class _GeneratedReflectedClass extends ReflectedClass { - final ClassMirror mirror; - final Reflector reflector; - - _GeneratedReflectedClass(this.mirror, this.reflector) - : super(mirror.simpleName, [], [], [], [], mirror.reflectedType); - - @override - List get typeParameters => - mirror.typeVariables.map(_convertTypeVariable).toList(); - - @override - List get constructors => - _constructorsOf(mirror.declarations, reflector); - - @override - List get declarations => - _declarationsOf(mirror.declarations, reflector); - - @override - List get annotations => mirror.metadata - .map(reflector.reflectInstance) - .whereType() - .toList(); - - @override - bool isAssignableTo(ReflectedType? other) { - if (other is _GeneratedReflectedClass) { - return mirror.isAssignableTo(other.mirror); - } else if (other is _GeneratedReflectedType) { - return mirror.isAssignableTo(other.mirror); - } else { - return false; - } - } - - @override - ReflectedInstance newInstance( - String constructorName, List positionalArguments, - [Map? namedArguments, List? typeArguments]) { - namedArguments ??= {}; - var result = mirror.newInstance(constructorName, positionalArguments, - namedArguments.map((k, v) => MapEntry(Symbol(k), v))); - return reflector.reflectInstance(result)!; - } -} - -class _GeneratedReflectedType extends ReflectedType { - final TypeMirror mirror; - - _GeneratedReflectedType(this.mirror) - : super(mirror.simpleName, [], mirror.reflectedType); - - @override - List get typeParameters => - mirror.typeVariables.map(_convertTypeVariable).toList(); - - @override - bool isAssignableTo(ReflectedType? other) { - if (other is _GeneratedReflectedClass) { - return mirror.isAssignableTo(other.mirror); - } else if (other is _GeneratedReflectedType) { - return mirror.isAssignableTo(other.mirror); - } else { - return false; - } - } - - @override - ReflectedInstance newInstance( - String constructorName, List positionalArguments, - [Map namedArguments = const {}, - List typeArguments = const []]) { - throw UnsupportedError('Cannot create a new instance of $reflectedType.'); - } -} - -class _GeneratedReflectedFunction extends ReflectedFunction { - final MethodMirror mirror; - final Reflector reflector; - final ClosureMirror? closure; - - _GeneratedReflectedFunction(this.mirror, this.reflector, [this.closure]) - : super( - mirror.simpleName, - [], - [], - mirror.parameters - .map((p) => _convertParameter(p, reflector)) - .toList(), - mirror.isGetter, - mirror.isSetter, - returnType: !mirror.isRegularMethod - ? null - : _GeneratedReflectedType(mirror.returnType)); - - @override - List get annotations => mirror.metadata - .map(reflector.reflectInstance) - .whereType() - .toList(); - - @override - ReflectedInstance invoke(Invocation invocation) { - if (closure != null) { - throw UnsupportedError('Only closures can be invoked directly.'); - } else { - var result = closure!.delegate(invocation)!; - return reflector.reflectInstance(result)!; - } - } -} - -List _constructorsOf( - Map map, Reflector reflector) { - return map.entries.fold>([], (out, entry) { - var v = entry.value; - - if (v is MethodMirror && v.isConstructor) { - return out..add(_GeneratedReflectedFunction(v, reflector)); - } else { - return out; - } - }); -} - -List _declarationsOf( - Map map, Reflector reflector) { - return map.entries.fold>([], (out, entry) { - var v = entry.value; - - if (v is VariableMirror) { - var decl = ReflectedDeclaration(v.simpleName, v.isStatic, null); - return out..add(decl); - } - if (v is MethodMirror) { - var decl = ReflectedDeclaration( - v.simpleName, v.isStatic, _GeneratedReflectedFunction(v, reflector)); - return out..add(decl); - } else { - return out; - } - }); -} - -ReflectedTypeParameter _convertTypeVariable(TypeVariableMirror mirror) { - return ReflectedTypeParameter(mirror.simpleName); -} - -ReflectedParameter _convertParameter( - ParameterMirror mirror, Reflector reflector) { - return ReflectedParameter( - mirror.simpleName, - mirror.metadata - .map(reflector.reflectInstance) - .whereType() - .toList(), - reflector.reflectType(mirror.type.reflectedType)!, - !mirror.isOptional, - mirror.isNamed); -} diff --git a/packages/container/container_generator/pubspec.yaml b/packages/container/container_generator/pubspec.yaml deleted file mode 100644 index d3aed3e..0000000 --- a/packages/container/container_generator/pubspec.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: platform_container_generator -version: 9.0.0 -description: Protevus Platform Codegen support for using pkg:reflectable with pkg:platform_container. -homepage: https://protevus.com -documentation: https://docs.protevus.com -repository: https://git.protevus.com/protevus/platform/src/branch/main/packages/container/container_generator -environment: - sdk: '>=3.3.0 <4.0.0' -dependencies: - platform_container: ^9.0.0 - reflectable: ^4.0.12 -dev_dependencies: - build_runner: ^2.4.13 - build_test: ^2.2.2 - test: ^1.25.8 - lints: ^4.0.0 -# dependency_overrides: -# platform_container: -# path: ../platform_container diff --git a/packages/container/container_generator/test/reflector_test.dart b/packages/container/container_generator/test/reflector_test.dart deleted file mode 100644 index e2bd7d9..0000000 --- a/packages/container/container_generator/test/reflector_test.dart +++ /dev/null @@ -1,179 +0,0 @@ -import 'package:platform_container/container.dart'; -import 'package:platform_container_generator/generator.dart'; - -import 'package:test/test.dart'; -import 'reflector_test.reflectable.dart'; - -void main() { - initializeReflectable(); - - var reflector = const GeneratedReflector(); - late Container container; - - setUp(() { - container = Container(reflector); - container.registerSingleton(Artist(name: 'Stevie Wonder')); - }); - - group('reflectClass', () { - var mirror = reflector.reflectClass(Artist); - - test('name', () { - expect(mirror.name, 'Artist'); - }); - }); - - test('inject constructor parameters', () { - var album = container.make(); - print(album.title); - expect(album.title, 'flowers by stevie wonder'); - }); - - // Skip as pkg:reflectable cannot reflect on closures at all (yet) - //testReflector(reflector); -} - -@contained -void returnVoidFromAFunction(int x) {} - -void testReflector(Reflector reflector) { - var blaziken = Pokemon('Blaziken', PokemonType.fire); - late Container container; - - setUp(() { - container = Container(reflector); - container.registerSingleton(blaziken); - }); - - test('get field', () { - var blazikenMirror = reflector.reflectInstance(blaziken)!; - expect(blazikenMirror.getField('type').reflectee, blaziken.type); - }); - - group('reflectFunction', () { - var mirror = reflector.reflectFunction(returnVoidFromAFunction); - - test('void return type returns dynamic', () { - expect(mirror?.returnType, reflector.reflectType(dynamic)); - }); - - test('counts parameters', () { - expect(mirror?.parameters, hasLength(1)); - }); - - test('counts types parameters', () { - expect(mirror?.typeParameters, isEmpty); - }); - - test('correctly reflects parameter types', () { - var p = mirror?.parameters[0]; - expect(p?.name, 'x'); - expect(p?.isRequired, true); - expect(p?.isNamed, false); - expect(p?.annotations, isEmpty); - expect(p?.type, reflector.reflectType(int)); - }); - }, skip: 'pkg:reflectable cannot reflect on closures at all (yet)'); - - test('make on singleton type returns singleton', () { - expect(container.make(Pokemon), blaziken); - }); - - test('make with generic returns same as make with explicit type', () { - expect(container.make(), blaziken); - }); - - test('make on aliased singleton returns singleton', () { - container.registerSingleton(blaziken, as: StateError); - expect(container.make(StateError), blaziken); - }); - - test('constructor injects singleton', () { - var lower = container.make(); - expect(lower.lowercaseName, blaziken.name.toLowerCase()); - }); - - test('newInstance works', () { - var type = container.reflector.reflectType(Pokemon)!; - var instance = - type.newInstance('changeName', [blaziken, 'Charizard']).reflectee - as Pokemon; - print(instance); - expect(instance.name, 'Charizard'); - expect(instance.type, PokemonType.fire); - }); - - test('isAssignableTo', () { - var pokemonType = container.reflector.reflectType(Pokemon); - var kantoPokemonType = container.reflector.reflectType(KantoPokemon)!; - - expect(kantoPokemonType.isAssignableTo(pokemonType), true); - - expect( - kantoPokemonType - .isAssignableTo(container.reflector.reflectType(String)), - false); - }); -} - -@contained -class LowerPokemon { - final Pokemon pokemon; - - LowerPokemon(this.pokemon); - - String get lowercaseName => pokemon.name.toLowerCase(); -} - -@contained -class Pokemon { - final String name; - final PokemonType type; - - Pokemon(this.name, this.type); - - factory Pokemon.changeName(Pokemon other, String name) { - return Pokemon(name, other.type); - } - - @override - String toString() => 'NAME: $name, TYPE: $type'; -} - -@contained -class KantoPokemon extends Pokemon { - KantoPokemon(super.name, super.type); -} - -@contained -enum PokemonType { water, fire, grass, ice, poison, flying } - -@contained -class Artist { - final String name; - - Artist({required this.name}); - - String get lowerName { - return name.toLowerCase(); - } -} - -@contained -class Album { - final Artist artist; - - Album(this.artist); - - String get title => 'flowers by ${artist.lowerName}'; -} - -@contained -class AlbumLength { - final Artist artist; - final Album album; - - AlbumLength(this.artist, this.album); - - int get totalLength => artist.name.length + album.title.length; -} diff --git a/packages/contracts/lib/contracts.dart b/packages/contracts/lib/contracts.dart deleted file mode 100644 index 473fb3f..0000000 --- a/packages/contracts/lib/contracts.dart +++ /dev/null @@ -1,71 +0,0 @@ -/// Platform Contracts Library -/// -/// This library provides the core contracts (interfaces) that define -/// the Platform framework's API. These contracts ensure consistency -/// and interoperability between components while enabling loose coupling -/// and dependency injection. - -// Level 0: Core Foundation Contracts - -// Container contracts (from packages/container) -export 'src/container/container.dart'; - -// Reflection contracts (from packages/container) -export 'src/reflection/reflection.dart'; - -// Pipeline contracts (from packages/pipeline) -export 'src/pipeline/pipeline.dart'; - -// Level 1: Infrastructure Contracts - -// Events contracts (from packages/events) -export 'src/events/events.dart'; - -// Bus contracts (from packages/bus) -export 'src/bus/bus.dart'; - -// Model contracts (from packages/model) -export 'src/model/model.dart'; - -// Process contracts (from packages/process) -export 'src/process/process.dart'; - -// Support contracts (from packages/support) -export 'src/support/support.dart'; - -// Level 2: Core Services Contracts - -// Queue contracts (from packages/queue) -export 'src/queue/queue.dart'; - -// Level 3: HTTP Layer Contracts - -// Routing contracts (from packages/route) -export 'src/routing/routing.dart'; - -// HTTP contracts (from packages/core) -export 'src/http/http.dart'; - -// Testing Contracts - -// Testing contracts (from packages/testing) -export 'src/testing/testing.dart'; - -// All contracts have been extracted from implemented packages: -// - Container & Reflection (Level 0) -// - Pipeline (Level 0) -// - Events (Level 1) -// - Bus (Level 1) -// - Model (Level 1) -// - Process (Level 1) -// - Support (Level 1) -// - Queue (Level 2) -// - Route (Level 3) -// - HTTP (Level 3) -// - Testing - -// Next steps: -// 1. Update package dependencies to use these contracts -// 2. Implement contracts in each package -// 3. Add contract compliance tests -// 4. Document contract usage and patterns diff --git a/packages/contracts/lib/src/bus/bus.dart b/packages/contracts/lib/src/bus/bus.dart deleted file mode 100644 index cd46d82..0000000 --- a/packages/contracts/lib/src/bus/bus.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// Bus package contracts -export 'bus_contract.dart'; diff --git a/packages/contracts/lib/src/bus/bus_contract.dart b/packages/contracts/lib/src/bus/bus_contract.dart deleted file mode 100644 index 075ceee..0000000 --- a/packages/contracts/lib/src/bus/bus_contract.dart +++ /dev/null @@ -1,196 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Contract for commands. -/// -/// Laravel-compatible: Base interface for command objects that can be -/// dispatched through the command bus. -@sealed -abstract class CommandContract {} - -/// Contract for queueable commands. -/// -/// Laravel-compatible: Marks commands that should be processed -/// through the queue system. -@sealed -abstract class ShouldQueueCommand implements CommandContract {} - -/// Contract for command handlers. -/// -/// Laravel-compatible: Defines how command handlers should process -/// their associated commands, with platform-specific typing. -@sealed -abstract class HandlerContract { - /// Handles a command. - /// - /// Laravel-compatible: Core handler method with platform-specific - /// return type for more flexibility. - /// - /// Parameters: - /// - [command]: The command to handle. - Future handle(CommandContract command); -} - -/// Type definition for command pipe functions. -/// -/// Platform-specific: Defines transformation functions that can modify -/// commands as they flow through the pipeline. -typedef CommandPipe = CommandContract Function(CommandContract); - -/// Contract for command dispatching. -/// -/// This contract defines the core interface for dispatching -/// and processing commands through the command bus. -@sealed -abstract class CommandDispatcherContract { - /// Dispatches a command. - /// - /// Laravel-compatible: Core dispatch method. - /// - /// Parameters: - /// - [command]: The command to dispatch. - Future dispatch(CommandContract command); - - /// Dispatches a command synchronously. - /// - /// Platform-specific: Provides explicit sync dispatch with optional handler. - /// - /// Parameters: - /// - [command]: The command to dispatch. - /// - [handler]: Optional specific handler. - Future dispatchSync(CommandContract command, - [HandlerContract? handler]); - - /// Dispatches a command immediately. - /// - /// Laravel-compatible: Immediate dispatch without queueing. - /// Extended with optional handler parameter. - /// - /// Parameters: - /// - [command]: The command to dispatch. - /// - [handler]: Optional specific handler. - Future dispatchNow(CommandContract command, - [HandlerContract? handler]); - - /// Dispatches a command to queue. - /// - /// Laravel-compatible: Queue-based dispatch. - /// - /// Parameters: - /// - [command]: The command to queue. - Future dispatchToQueue(CommandContract command); - - /// Finds a command batch. - /// - /// Platform-specific: Provides batch lookup functionality. - /// - /// Parameters: - /// - [batchId]: The batch ID to find. - Future findBatch(String batchId); - - /// Creates a command batch. - /// - /// Laravel-compatible: Creates command batches. - /// Extended with platform-specific batch contract. - /// - /// Parameters: - /// - [commands]: Commands to include in batch. - PendingCommandBatchContract batch(List commands); - - /// Creates a command chain. - /// - /// Laravel-compatible: Creates command chains. - /// Extended with platform-specific chain contract. - /// - /// Parameters: - /// - [commands]: Commands to chain. - CommandChainContract chain(List commands); - - /// Maps command types to handlers. - /// - /// Platform-specific: Provides explicit handler mapping. - /// - /// Parameters: - /// - [handlers]: Map of command types to handler types. - CommandDispatcherContract map(Map handlers); - - /// Applies transformation pipes to commands. - /// - /// Platform-specific: Adds pipeline transformation support. - /// - /// Parameters: - /// - [pipes]: List of command transformation functions. - CommandDispatcherContract pipeThrough(List pipes); - - /// Dispatches after current response. - /// - /// Laravel-compatible: Delayed dispatch after response. - /// - /// Parameters: - /// - [command]: Command to dispatch later. - void dispatchAfterResponse(CommandContract command); -} - -/// Contract for command batches. -/// -/// Laravel-compatible: Defines batch structure and operations. -/// Extended with additional status tracking. -@sealed -abstract class CommandBatchContract { - /// Gets the batch ID. - String get id; - - /// Gets commands in the batch. - List get commands; - - /// Gets batch status. - /// - /// Platform-specific: Provides detailed status tracking. - String get status; - - /// Whether batch allows failures. - /// - /// Laravel-compatible: Controls batch failure handling. - bool get allowsFailures; - - /// Gets finished command count. - /// - /// Platform-specific: Tracks completion progress. - int get finished; - - /// Gets failed command count. - /// - /// Platform-specific: Tracks failure count. - int get failed; - - /// Gets pending command count. - /// - /// Platform-specific: Tracks remaining commands. - int get pending; -} - -/// Contract for pending command batches. -/// -/// Laravel-compatible: Defines batch configuration and dispatch. -@sealed -abstract class PendingCommandBatchContract { - /// Allows failures in batch. - /// - /// Laravel-compatible: Configures failure handling. - PendingCommandBatchContract allowFailures(); - - /// Dispatches the batch. - /// - /// Laravel-compatible: Executes the batch. - Future dispatch(); -} - -/// Contract for command chains. -/// -/// Laravel-compatible: Defines sequential command execution. -@sealed -abstract class CommandChainContract { - /// Dispatches the chain. - /// - /// Laravel-compatible: Executes commands in sequence. - Future dispatch(); -} diff --git a/packages/contracts/lib/src/container/container.dart b/packages/contracts/lib/src/container/container.dart deleted file mode 100644 index 88578ff..0000000 --- a/packages/contracts/lib/src/container/container.dart +++ /dev/null @@ -1,3 +0,0 @@ -/// Container package contracts -export 'container_contract.dart'; -export 'contextual_binding_contract.dart'; diff --git a/packages/contracts/lib/src/container/container_contract.dart b/packages/contracts/lib/src/container/container_contract.dart deleted file mode 100644 index dc2c414..0000000 --- a/packages/contracts/lib/src/container/container_contract.dart +++ /dev/null @@ -1,134 +0,0 @@ -import 'package:meta/meta.dart'; -import '../reflection/reflector_contract.dart'; - -/// Core container contract defining dependency injection functionality. -/// -/// This contract defines the interface that all dependency injection containers -/// must implement. It provides methods for registering and resolving dependencies, -/// creating child containers, and managing named instances. -@sealed -abstract class ContainerContract { - /// Gets the reflector instance used by this container. - ReflectorContract get reflector; - - /// Whether this is a root container (has no parent). - bool get isRoot; - - /// Creates a child container that inherits from this container. - /// - /// The child container can access all dependencies registered in its parent containers, - /// but can also define its own dependencies that override or extend the parent's. - /// This enables scoped dependency injection contexts. - ContainerContract createChild(); - - /// Checks if a type is registered in this container or its parents. - /// - /// Parameters: - /// - [T]: The type to check for. If [T] is dynamic, [t] must be provided. - /// - [t]: Optional type parameter that overrides [T] if provided. - /// - /// Returns true if the type is registered, false otherwise. - bool has([Type? t]); - - /// Checks if a named instance exists in this container or its parents. - /// - /// Parameters: - /// - [name]: The name to check for. - /// - /// Returns true if a named instance exists, false otherwise. - bool hasNamed(String name); - - /// Makes an instance of type [T]. - /// - /// This will: - /// 1. Return a singleton if registered - /// 2. Create an instance via factory if registered - /// 3. Use reflection to create a new instance - /// - /// Parameters: - /// - [type]: Optional type parameter that overrides [T] if provided. - /// - /// Throws: - /// - ReflectionException if [T] is not a class or has no default constructor - T make([Type? type]); - - /// Makes an instance of type [T] asynchronously. - /// - /// This will attempt to resolve a Future in the following order: - /// 1. Wrap a synchronous [T] in Future - /// 2. Return a registered Future - /// 3. Create a Future via reflection - /// - /// Parameters: - /// - [type]: Optional type parameter that overrides [T] if provided. - /// - /// Throws: - /// - ReflectionException if no suitable injection is found - Future makeAsync([Type? type]); - - /// Registers a singleton instance. - /// - /// The instance will be shared across the container hierarchy. - /// - /// Parameters: - /// - [object]: The singleton instance to register. - /// - [as]: Optional type to register the singleton as. - /// - /// Returns the registered instance. - /// - /// Throws: - /// - StateError if a singleton is already registered for the type - T registerSingleton(T object, {Type? as}); - - /// Registers a factory function. - /// - /// The factory will be called each time an instance is needed. - /// - /// Parameters: - /// - [factory]: Function that creates instances. - /// - [as]: Optional type to register the factory as. - /// - /// Returns the factory function. - /// - /// Throws: - /// - StateError if a factory is already registered for the type - T Function(ContainerContract) registerFactory( - T Function(ContainerContract) factory, - {Type? as}); - - /// Registers a lazy singleton. - /// - /// The singleton will be created on first use. - /// - /// Parameters: - /// - [factory]: Function that creates the singleton. - /// - [as]: Optional type to register the singleton as. - /// - /// Returns the factory function. - T Function(ContainerContract) registerLazySingleton( - T Function(ContainerContract) factory, - {Type? as}); - - /// Gets a named singleton. - /// - /// Parameters: - /// - [name]: The name of the singleton to retrieve. - /// - /// Returns the named singleton instance. - /// - /// Throws: - /// - StateError if no singleton exists with the given name - T findByName(String name); - - /// Registers a named singleton. - /// - /// Parameters: - /// - [name]: The name to register the singleton under. - /// - [object]: The singleton instance. - /// - /// Returns the registered instance. - /// - /// Throws: - /// - StateError if a singleton already exists with the given name - T registerNamedSingleton(String name, T object); -} diff --git a/packages/contracts/lib/src/container/contextual_binding_contract.dart b/packages/contracts/lib/src/container/contextual_binding_contract.dart deleted file mode 100644 index 7071c36..0000000 --- a/packages/contracts/lib/src/container/contextual_binding_contract.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Contract for contextual binding in dependency injection. -/// -/// This contract defines the interface for creating contextual bindings, -/// allowing dependencies to be resolved differently based on context. -@sealed -abstract class ContextualBindingContract { - /// Specifies the concrete type that triggers this contextual binding. - /// - /// Parameters: - /// - [concrete]: The concrete type that needs dependencies. - /// - /// Returns a builder for specifying what type is needed. - ContextualNeedsContract when(Type concrete); -} - -/// Contract for specifying contextual needs. -/// -/// This contract defines the interface for specifying what type -/// is needed in a particular context. -@sealed -abstract class ContextualNeedsContract { - /// Specifies the type needed in this context. - /// - /// Returns a builder for specifying what to give. - ContextualGiveContract needs(); -} - -/// Contract for specifying contextual implementations. -/// -/// This contract defines the interface for specifying what -/// implementation to provide in a particular context. -@sealed -abstract class ContextualGiveContract { - /// Specifies what to give for this contextual binding. - /// - /// Parameters: - /// - [implementation]: The implementation to provide. - /// This can be an instance, a factory function, or a type. - void give(dynamic implementation); -} diff --git a/packages/contracts/lib/src/events/event_dispatcher_contract.dart b/packages/contracts/lib/src/events/event_dispatcher_contract.dart deleted file mode 100644 index 47e2308..0000000 --- a/packages/contracts/lib/src/events/event_dispatcher_contract.dart +++ /dev/null @@ -1,199 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Contract for event dispatching functionality. -/// -/// This contract defines the interface for dispatching events, -/// managing listeners, and handling event broadcasting. -/// -/// The contract includes both Laravel-compatible methods and platform-specific -/// extensions for enhanced functionality. -@sealed -abstract class EventDispatcherContract { - /// Registers an event listener. - /// - /// Laravel-compatible: Registers event listeners, but with platform-specific - /// dynamic typing for more flexible event handling. - /// - /// Parameters: - /// - [events]: Event type or list of event types to listen for. - /// - [listener]: Function to handle the event. - void listen(dynamic events, dynamic listener); - - /// Checks if event has listeners. - /// - /// Platform-specific: Provides listener existence checking. - /// - /// Parameters: - /// - [eventName]: Name of the event to check. - bool hasListeners(String eventName); - - /// Pushes an event for delayed processing. - /// - /// Platform-specific: Supports delayed event processing. - /// - /// Parameters: - /// - [event]: Name of the event. - /// - [payload]: Optional event payload. - void push(String event, [dynamic payload]); - - /// Flushes delayed events. - /// - /// Platform-specific: Processes delayed events immediately. - /// - /// Parameters: - /// - [event]: Name of the event to flush. - Future flush(String event); - - /// Subscribes an event subscriber. - /// - /// Laravel-compatible: Registers event subscribers, but with platform-specific - /// dynamic typing for more flexible subscription handling. - /// - /// Parameters: - /// - [subscriber]: The subscriber to register. - void subscribe(dynamic subscriber); - - /// Waits for an event to occur. - /// - /// Platform-specific: Provides event waiting functionality. - /// - /// Parameters: - /// - [event]: Event to wait for. - /// - [payload]: Optional payload to dispatch. - Future until(dynamic event, [dynamic payload]); - - /// Dispatches an event. - /// - /// Laravel-compatible: Dispatches events, with platform-specific - /// extensions for halting and payload handling. - /// - /// Parameters: - /// - [event]: Event to dispatch. - /// - [payload]: Optional event payload. - /// - [halt]: Whether to halt after first handler. - Future dispatch(dynamic event, [dynamic payload, bool? halt]); - - /// Gets registered listeners. - /// - /// Laravel-compatible: Retrieves event listeners. - /// - /// Parameters: - /// - [eventName]: Name of the event. - List getListeners(String eventName); - - /// Removes an event listener. - /// - /// Laravel-compatible: Removes event listeners. - /// - /// Parameters: - /// - [event]: Event to remove listener for. - void forget(String event); - - /// Removes pushed event listeners. - /// - /// Platform-specific: Cleans up delayed event listeners. - void forgetPushed(); - - /// Sets queue resolver. - /// - /// Laravel-compatible: Configures queue integration. - /// - /// Parameters: - /// - [resolver]: Queue resolver function. - void setQueueResolver(Function resolver); - - /// Sets transaction manager resolver. - /// - /// Laravel-compatible: Configures transaction integration. - /// - /// Parameters: - /// - [resolver]: Transaction manager resolver function. - void setTransactionManagerResolver(Function resolver); - - /// Gets raw event listeners. - /// - /// Platform-specific: Provides access to raw listener data. - Map> getRawListeners(); -} - -/// Contract for event subscribers. -/// -/// Laravel-compatible: Defines how event subscribers register -/// their event handling methods. -@sealed -abstract class EventSubscriberContract { - /// Subscribes to events. - /// - /// Laravel-compatible: Returns event handler mappings. - /// - /// Returns a map of event types to handler functions. - Map subscribe(); -} - -/// Marker interface for broadcastable events. -/// -/// Laravel-compatible: Events implementing this interface will be broadcast -/// across the application. -@sealed -abstract class ShouldBroadcast { - /// Gets channels to broadcast on. - /// - /// Laravel-compatible: Defines broadcast channels. - List broadcastOn(); - - /// Gets event name for broadcasting. - /// - /// Laravel-compatible: Defines broadcast event name. - String broadcastAs() => runtimeType.toString(); - - /// Gets broadcast data. - /// - /// Laravel-compatible: Defines broadcast payload. - Map get broadcastWith => {}; -} - -/// Marker interface for queueable events. -/// -/// Laravel-compatible: Events implementing this interface will be processed -/// through the queue system. -@sealed -abstract class ShouldQueue { - /// Gets the queue name. - /// - /// Laravel-compatible: Defines target queue. - String get queue => 'default'; - - /// Gets the processing delay. - /// - /// Laravel-compatible: Defines queue delay. - Duration? get delay => null; - - /// Gets maximum retry attempts. - /// - /// Laravel-compatible: Defines retry limit. - int get tries => 1; -} - -/// Marker interface for encrypted events. -/// -/// Laravel-compatible: Events implementing this interface will be encrypted -/// before being stored or transmitted. -@sealed -abstract class ShouldBeEncrypted { - /// Whether the event should be encrypted. - /// - /// Laravel-compatible: Controls event encryption. - bool get shouldBeEncrypted => true; -} - -/// Marker interface for events that should dispatch after commit. -/// -/// Laravel-compatible: Events implementing this interface will only be dispatched -/// after the current database transaction commits. -@sealed -abstract class ShouldDispatchAfterCommit { - /// Whether to dispatch after commit. - /// - /// Laravel-compatible: Controls transaction-based dispatch. - bool get afterCommit => true; -} diff --git a/packages/contracts/lib/src/events/events.dart b/packages/contracts/lib/src/events/events.dart deleted file mode 100644 index 6f6e5c1..0000000 --- a/packages/contracts/lib/src/events/events.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// Events package contracts -export 'event_dispatcher_contract.dart'; diff --git a/packages/contracts/lib/src/http/http.dart b/packages/contracts/lib/src/http/http.dart deleted file mode 100644 index 4fb3ab6..0000000 --- a/packages/contracts/lib/src/http/http.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// HTTP package contracts -export 'http_contract.dart'; diff --git a/packages/contracts/lib/src/http/http_contract.dart b/packages/contracts/lib/src/http/http_contract.dart deleted file mode 100644 index 5f54ffa..0000000 --- a/packages/contracts/lib/src/http/http_contract.dart +++ /dev/null @@ -1,299 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Contract for HTTP requests. -/// -/// Laravel-compatible: Core request functionality matching Laravel's Request -/// interface, with platform-specific stream handling. -@sealed -abstract class RequestContract { - /// Gets the request method. - /// - /// Laravel-compatible: HTTP method accessor. - String get method; - - /// Gets the request URI. - /// - /// Laravel-compatible: Request URI using Dart's Uri class. - Uri get uri; - - /// Gets request headers. - /// - /// Laravel-compatible: Header access with platform-specific - /// multi-value support. - Map> get headers; - - /// Gets query parameters. - /// - /// Laravel-compatible: Query parameter access. - Map get query; - - /// Gets POST data. - /// - /// Laravel-compatible: POST data access. - Map get post; - - /// Gets cookies. - /// - /// Laravel-compatible: Cookie access. - Map get cookies; - - /// Gets uploaded files. - /// - /// Laravel-compatible: File upload handling with - /// platform-specific contract. - Map get files; - - /// Gets the request body. - /// - /// Platform-specific: Stream-based body access. - Stream> get body; - - /// Gets a request header. - /// - /// Laravel-compatible: Single header access. - String? header(String name, [String? defaultValue]); - - /// Gets a query parameter. - /// - /// Laravel-compatible: Single query parameter access. - String? query_(String name, [String? defaultValue]); - - /// Gets a POST value. - /// - /// Laravel-compatible: Single POST value access. - dynamic post_(String name, [dynamic defaultValue]); - - /// Gets a cookie value. - /// - /// Laravel-compatible: Single cookie access. - String? cookie(String name, [String? defaultValue]); - - /// Gets an uploaded file. - /// - /// Laravel-compatible: Single file access. - UploadedFileContract? file(String name); - - /// Gets all input data (query + post). - /// - /// Laravel-compatible: Combined input access. - Map all(); - - /// Gets input value from any source. - /// - /// Laravel-compatible: Universal input access. - dynamic input(String name, [dynamic defaultValue]); - - /// Checks if input exists. - /// - /// Laravel-compatible: Input existence check. - bool has(String name); - - /// Gets the raw request body as string. - /// - /// Platform-specific: Async text body access. - Future text(); - - /// Gets the request body as JSON. - /// - /// Platform-specific: Async JSON body access. - Future json(); -} - -/// Contract for HTTP responses. -/// -/// Laravel-compatible: Core response functionality matching Laravel's Response -/// interface, with platform-specific async features. -@sealed -abstract class ResponseContract { - /// Gets response headers. - /// - /// Laravel-compatible: Header access with platform-specific - /// multi-value support. - Map> get headers; - - /// Gets the status code. - /// - /// Laravel-compatible: Status code accessor. - int get status; - - /// Sets the status code. - /// - /// Laravel-compatible: Status code mutator. - set status(int value); - - /// Sets a response header. - /// - /// Laravel-compatible: Single header setting. - void header(String name, String value); - - /// Sets multiple headers. - /// - /// Laravel-compatible: Bulk header setting. - void headers_(Map headers); - - /// Sets a cookie. - /// - /// Laravel-compatible: Cookie setting with platform-specific - /// security options. - void cookie( - String name, - String value, { - Duration? maxAge, - DateTime? expires, - String? domain, - String? path, - bool secure = false, - bool httpOnly = false, - String? sameSite, - }); - - /// Writes response body content. - /// - /// Laravel-compatible: Content writing. - void write(dynamic content); - - /// Sends JSON response. - /// - /// Laravel-compatible: JSON response. - void json(dynamic data); - - /// Sends file download. - /// - /// Laravel-compatible: File download with platform-specific - /// async handling. - Future download(String path, [String? name]); - - /// Redirects to another URL. - /// - /// Laravel-compatible: Redirect response. - void redirect(String url, [int status = 302]); - - /// Sends the response. - /// - /// Platform-specific: Async response sending. - Future send(); -} - -/// Contract for uploaded files. -/// -/// Laravel-compatible: File upload handling matching Laravel's UploadedFile -/// interface, with platform-specific async operations. -@sealed -abstract class UploadedFileContract { - /// Gets the original client filename. - /// - /// Laravel-compatible: Original filename. - String get filename; - - /// Gets the file MIME type. - /// - /// Laravel-compatible: MIME type. - String get mimeType; - - /// Gets the file size in bytes. - /// - /// Laravel-compatible: File size. - int get size; - - /// Gets temporary file path. - /// - /// Laravel-compatible: Temporary storage. - String get path; - - /// Moves file to new location. - /// - /// Laravel-compatible: File movement with platform-specific - /// async handling. - Future moveTo(String path); - - /// Gets file contents as bytes. - /// - /// Platform-specific: Async binary content access. - Future> bytes(); - - /// Gets file contents as string. - /// - /// Platform-specific: Async text content access. - Future text(); -} - -/// Contract for HTTP middleware. -/// -/// Laravel-compatible: Middleware functionality matching Laravel's Middleware -/// interface, with platform-specific async handling. -@sealed -abstract class MiddlewareContract { - /// Handles the request. - /// - /// Laravel-compatible: Middleware handling with platform-specific - /// async processing. - /// - /// Parameters: - /// - [request]: The incoming request. - /// - [next]: Function to pass to next middleware. - Future handle(RequestContract request, - Future Function(RequestContract) next); -} - -/// Contract for HTTP kernel. -/// -/// Laravel-compatible: HTTP kernel functionality matching Laravel's HttpKernel -/// interface, with platform-specific async processing. -@sealed -abstract class HttpKernelContract { - /// Gets global middleware. - /// - /// Laravel-compatible: Global middleware list. - List get middleware; - - /// Gets middleware groups. - /// - /// Laravel-compatible: Middleware grouping. - Map> get middlewareGroups; - - /// Gets route middleware. - /// - /// Laravel-compatible: Route middleware mapping. - Map get routeMiddleware; - - /// Handles an HTTP request. - /// - /// Laravel-compatible: Request handling with platform-specific - /// async processing. - Future handle(RequestContract request); - - /// Terminates the request/response cycle. - /// - /// Laravel-compatible: Request termination with platform-specific - /// async processing. - Future terminate( - RequestContract request, ResponseContract response); -} - -/// Contract for HTTP context. -/// -/// Platform-specific: Provides request context beyond Laravel's -/// standard request handling. -@sealed -abstract class HttpContextContract { - /// Gets the current request. - RequestContract get request; - - /// Gets the current response. - ResponseContract get response; - - /// Gets context attributes. - Map get attributes; - - /// Gets a context attribute. - T? getAttribute(String key); - - /// Sets a context attribute. - void setAttribute(String key, dynamic value); - - /// Gets the route parameters. - Map get routeParams; - - /// Gets a route parameter. - T? getRouteParam(String name); -} diff --git a/packages/contracts/lib/src/model/model.dart b/packages/contracts/lib/src/model/model.dart deleted file mode 100644 index 9dd4228..0000000 --- a/packages/contracts/lib/src/model/model.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// Model package contracts -export 'model_contract.dart'; diff --git a/packages/contracts/lib/src/model/model_contract.dart b/packages/contracts/lib/src/model/model_contract.dart deleted file mode 100644 index b88fe12..0000000 --- a/packages/contracts/lib/src/model/model_contract.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Contract for base model functionality. -/// -/// Laravel-compatible: Provides core model functionality similar to Laravel's -/// Model class, adapted for Dart's type system and patterns. -@sealed -abstract class ModelContract { - /// Gets the model's unique identifier. - /// - /// Laravel-compatible: Primary key accessor. - /// Extended with nullable String type for flexibility. - String? get id; - - /// Sets the model's unique identifier. - /// - /// Laravel-compatible: Primary key mutator. - /// Extended with nullable String type for flexibility. - set id(String? value); - - /// Gets the creation timestamp. - /// - /// Laravel-compatible: Created at timestamp accessor. - /// Uses Dart's DateTime instead of Carbon. - DateTime? get createdAt; - - /// Sets the creation timestamp. - /// - /// Laravel-compatible: Created at timestamp mutator. - /// Uses Dart's DateTime instead of Carbon. - set createdAt(DateTime? value); - - /// Gets the last update timestamp. - /// - /// Laravel-compatible: Updated at timestamp accessor. - /// Uses Dart's DateTime instead of Carbon. - DateTime? get updatedAt; - - /// Sets the last update timestamp. - /// - /// Laravel-compatible: Updated at timestamp mutator. - /// Uses Dart's DateTime instead of Carbon. - set updatedAt(DateTime? value); - - /// Gets the ID as an integer. - /// - /// Platform-specific: Provides integer ID conversion. - /// Returns -1 if ID is null or not a valid integer. - int get idAsInt; - - /// Gets the ID as a string. - /// - /// Platform-specific: Provides string ID conversion. - /// Returns empty string if ID is null. - String get idAsString; -} - -/// Contract for auditable model functionality. -/// -/// Laravel-compatible: Similar to Laravel's auditable trait, -/// providing user tracking for model changes. -@sealed -abstract class AuditableModelContract extends ModelContract { - /// Gets the ID of user who created the record. - /// - /// Laravel-compatible: Created by user tracking. - /// Uses String ID instead of user model reference. - String? get createdBy; - - /// Sets the ID of user who created the record. - /// - /// Laravel-compatible: Created by user tracking. - /// Uses String ID instead of user model reference. - set createdBy(String? value); - - /// Gets the ID of user who last updated the record. - /// - /// Laravel-compatible: Updated by user tracking. - /// Uses String ID instead of user model reference. - String? get updatedBy; - - /// Sets the ID of user who last updated the record. - /// - /// Laravel-compatible: Updated by user tracking. - /// Uses String ID instead of user model reference. - set updatedBy(String? value); -} - -/// Optional contract for model serialization. -/// -/// Laravel-compatible: Similar to Laravel's serialization features, -/// adapted for Dart's type system. -@sealed -abstract class SerializableModelContract { - /// Converts model to a map. - /// - /// Laravel-compatible: Similar to toArray() method. - Map toMap(); - - /// Creates model from a map. - /// - /// Laravel-compatible: Similar to fill() method. - void fromMap(Map map); -} - -/// Optional contract for model validation. -/// -/// Platform-specific: Provides built-in validation support, -/// inspired by Laravel's validation but adapted for Dart. -@sealed -abstract class ValidatableModelContract { - /// Validates the model. - /// - /// Platform-specific: Returns validation errors if invalid. - Map>? validate(); - - /// Gets validation rules. - /// - /// Platform-specific: Defines validation rules. - Map> get rules; - - /// Gets custom error messages. - /// - /// Platform-specific: Defines custom validation messages. - Map get messages; -} - -/// Optional contract for model events. -/// -/// Laravel-compatible: Similar to Laravel's model events, -/// adapted for Dart's event system. -@sealed -abstract class ObservableModelContract { - /// Gets the event name. - /// - /// Laravel-compatible: Defines event identifier. - String get eventName; - - /// Gets the event timestamp. - /// - /// Platform-specific: Adds timestamp tracking to events. - DateTime get eventTimestamp; - - /// Gets event data. - /// - /// Laravel-compatible: Provides event payload. - Map get eventData; -} diff --git a/packages/contracts/lib/src/pipeline/pipeline.dart b/packages/contracts/lib/src/pipeline/pipeline.dart deleted file mode 100644 index a834069..0000000 --- a/packages/contracts/lib/src/pipeline/pipeline.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// Pipeline package contracts -export 'pipeline_contract.dart'; diff --git a/packages/contracts/lib/src/pipeline/pipeline_contract.dart b/packages/contracts/lib/src/pipeline/pipeline_contract.dart deleted file mode 100644 index 1e2f8db..0000000 --- a/packages/contracts/lib/src/pipeline/pipeline_contract.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Contract for a pipe that processes objects in a pipeline. -/// -/// Laravel-compatible: Core pipe functionality matching Laravel's -/// pipe interface, with platform-specific async handling. -@sealed -abstract class PipeContract { - /// Handles the passable object. - /// - /// Laravel-compatible: Core pipe handling with platform-specific - /// async processing. - /// - /// Parameters: - /// - [passable]: The object being passed through the pipeline. - /// - [next]: Function to pass the object to the next pipe. - /// - /// Returns the processed object, possibly modified. - Future handle( - dynamic passable, Future Function(dynamic) next); -} - -/// Contract for a pipeline that processes objects through a series of pipes. -/// -/// Laravel-compatible: Core pipeline functionality matching Laravel's -/// Pipeline class, with platform-specific fluent interface. -@sealed -abstract class PipelineContract { - /// Sets the object to be passed through the pipeline. - /// - /// Laravel-compatible: Pipeline input setting. - /// - /// Parameters: - /// - [passable]: The object to process. - /// - /// Returns the pipeline instance for fluent chaining. - PipelineContract send(dynamic passable); - - /// Sets the array of pipes to process the object through. - /// - /// Laravel-compatible: Pipe configuration with platform-specific - /// flexibility for pipe types. - /// - /// Parameters: - /// - [pipes]: The pipes to process the object through. - /// Can be a single pipe or an iterable of pipes. - /// - /// Returns the pipeline instance for fluent chaining. - PipelineContract through(dynamic pipes); - - /// Adds additional pipes to the pipeline. - /// - /// Platform-specific: Additional method for pipe configuration - /// following Laravel's fluent pattern. - /// - /// Parameters: - /// - [pipes]: The pipes to add. - /// Can be a single pipe or an iterable of pipes. - /// - /// Returns the pipeline instance for fluent chaining. - PipelineContract pipe(dynamic pipes); - - /// Sets the method to call on the pipes. - /// - /// Laravel-compatible: Method name configuration. - /// - /// Parameters: - /// - [method]: The name of the method to call. - /// - /// Returns the pipeline instance for fluent chaining. - PipelineContract via(String method); - - /// Runs the pipeline with a final destination callback. - /// - /// Laravel-compatible: Pipeline execution with platform-specific - /// async processing. - /// - /// Parameters: - /// - [destination]: Function to process the final result. - /// - /// Returns the processed result. - Future then(dynamic Function(dynamic) destination); - - /// Runs the pipeline and returns the result. - /// - /// Platform-specific: Direct result access following Laravel's - /// pipeline execution pattern. - /// - /// Returns the processed object directly. - Future thenReturn(); -} - -/// Contract for a pipeline hub that manages multiple pipelines. -/// -/// Laravel-compatible: Pipeline management functionality matching -/// Laravel's pipeline hub features. -@sealed -abstract class PipelineHubContract { - /// Gets or creates a pipeline with the given name. - /// - /// Laravel-compatible: Named pipeline access. - /// - /// Parameters: - /// - [name]: The name of the pipeline. - /// - /// Returns the pipeline instance. - PipelineContract pipeline(String name); - - /// Sets the default pipes for a pipeline. - /// - /// Laravel-compatible: Default pipe configuration. - /// - /// Parameters: - /// - [name]: The name of the pipeline. - /// - [pipes]: The default pipes for the pipeline. - void defaults(String name, List pipes); - - /// Registers a pipe type with a name. - /// - /// Platform-specific: Named pipe type registration following - /// Laravel's service registration pattern. - /// - /// Parameters: - /// - [name]: The name to register the pipe type under. - /// - [type]: The pipe type to register. - void registerPipeType(String name, Type type); -} diff --git a/packages/contracts/lib/src/process/process.dart b/packages/contracts/lib/src/process/process.dart deleted file mode 100644 index 4155b10..0000000 --- a/packages/contracts/lib/src/process/process.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// Process package contracts -export 'process_contract.dart'; diff --git a/packages/contracts/lib/src/process/process_contract.dart b/packages/contracts/lib/src/process/process_contract.dart deleted file mode 100644 index afe9824..0000000 --- a/packages/contracts/lib/src/process/process_contract.dart +++ /dev/null @@ -1,251 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'package:meta/meta.dart'; - -/// Contract for process management. -/// -/// Platform-specific: Provides system process management following Laravel's -/// architectural patterns for resource management and lifecycle control. -@sealed -abstract class ProcessManagerContract { - /// Starts a new process. - /// - /// Platform-specific: Creates and starts a new system process with - /// Laravel-style identifier and configuration options. - /// - /// Parameters: - /// - [id]: Unique identifier for the process. - /// - [command]: Command to execute. - /// - [arguments]: Command arguments. - /// - [workingDirectory]: Optional working directory. - /// - [environment]: Optional environment variables. - /// - [timeout]: Optional execution timeout. - /// - [tty]: Whether to run in a terminal. - /// - [enableReadError]: Whether to enable error stream reading. - Future start( - String id, - String command, - List arguments, { - String? workingDirectory, - Map? environment, - Duration? timeout, - bool tty = false, - bool enableReadError = true, - }); - - /// Gets a running process by ID. - /// - /// Platform-specific: Retrieves process by identifier, - /// following Laravel's repository pattern. - ProcessContract? get(String id); - - /// Kills a process. - /// - /// Platform-specific: Terminates a process with optional signal, - /// following Laravel's resource cleanup patterns. - /// - /// Parameters: - /// - [id]: Process ID to kill. - /// - [signal]: Signal to send (default: SIGTERM). - Future kill(String id, {ProcessSignal signal = ProcessSignal.sigterm}); - - /// Kills all managed processes. - /// - /// Platform-specific: Bulk process termination, - /// following Laravel's collection operation patterns. - Future killAll({ProcessSignal signal = ProcessSignal.sigterm}); - - /// Gets process events stream. - /// - /// Platform-specific: Event streaming following Laravel's - /// event broadcasting patterns. - Stream get events; - - /// Runs processes in a pool. - /// - /// Platform-specific: Concurrent process execution following - /// Laravel's job queue worker pool patterns. - /// - /// Parameters: - /// - [processes]: Processes to run. - /// - [concurrency]: Max concurrent processes. - Future> pool( - List processes, { - int concurrency = 5, - }); - - /// Runs processes in a pipeline. - /// - /// Platform-specific: Sequential process execution following - /// Laravel's pipeline pattern. - Future pipeline(List processes); - - /// Disposes the manager and all processes. - /// - /// Platform-specific: Resource cleanup following Laravel's - /// service provider cleanup patterns. - void dispose(); -} - -/// Contract for process instances. -/// -/// Platform-specific: Defines individual process behavior following -/// Laravel's resource management patterns. -@sealed -abstract class ProcessContract { - /// Gets the process command. - String get command; - - /// Gets the process ID. - int? get pid; - - /// Gets process start time. - DateTime? get startTime; - - /// Gets process end time. - DateTime? get endTime; - - /// Gets process output stream. - Stream> get output; - - /// Gets process error stream. - Stream> get errorOutput; - - /// Gets process exit code. - Future get exitCode; - - /// Whether the process is running. - bool get isRunning; - - /// Starts the process. - Future start(); - - /// Runs the process to completion. - Future run(); - - /// Runs the process with a timeout. - /// - /// Parameters: - /// - [timeout]: Maximum execution time. - /// - /// Throws TimeoutException if process exceeds timeout. - Future runWithTimeout(Duration timeout); - - /// Writes input to the process. - Future write(String input); - - /// Writes multiple lines to the process. - Future writeLines(List lines); - - /// Kills the process. - Future kill({ProcessSignal signal = ProcessSignal.sigterm}); - - /// Sends a signal to the process. - bool sendSignal(ProcessSignal signal); - - /// Gets process output as string. - Future get outputAsString; - - /// Gets process error output as string. - Future get errorOutputAsString; - - /// Disposes the process. - Future dispose(); -} - -/// Contract for process results. -/// -/// Platform-specific: Defines process execution results following -/// Laravel's response/result patterns. -@sealed -abstract class ProcessResultContract { - /// Gets the process ID. - int get pid; - - /// Gets the exit code. - int get exitCode; - - /// Gets the process output. - String get output; - - /// Gets the process error output. - String get errorOutput; - - /// Gets string representation. - @override - String toString() { - return 'ProcessResult(pid: $pid, exitCode: $exitCode, output: ${output.length} chars, errorOutput: ${errorOutput.length} chars)'; - } -} - -/// Contract for process events. -/// -/// Platform-specific: Defines process lifecycle events following -/// Laravel's event system patterns. -@sealed -abstract class ProcessEventContract { - /// Gets the process ID. - String get id; - - /// Gets the event timestamp. - DateTime get timestamp; -} - -/// Contract for process started events. -/// -/// Platform-specific: Defines process start event following -/// Laravel's event naming and structure patterns. -@sealed -abstract class ProcessStartedEventContract extends ProcessEventContract { - /// Gets the started process. - ProcessContract get process; - - @override - String toString() => - 'ProcessStartedEvent(id: $id, command: ${process.command})'; -} - -/// Contract for process exited events. -/// -/// Platform-specific: Defines process exit event following -/// Laravel's event naming and structure patterns. -@sealed -abstract class ProcessExitedEventContract extends ProcessEventContract { - /// Gets the exit code. - int get exitCode; - - @override - String toString() => 'ProcessExitedEvent(id: $id, exitCode: $exitCode)'; -} - -/// Contract for process pools. -/// -/// Platform-specific: Defines concurrent process execution following -/// Laravel's worker pool patterns. -@sealed -abstract class ProcessPoolContract { - /// Gets maximum concurrent processes. - int get concurrency; - - /// Gets active processes. - List get active; - - /// Gets pending processes. - List get pending; - - /// Runs processes in the pool. - Future> run(List processes); -} - -/// Contract for process pipelines. -/// -/// Platform-specific: Defines sequential process execution following -/// Laravel's pipeline pattern. -@sealed -abstract class ProcessPipelineContract { - /// Gets pipeline processes. - List get processes; - - /// Runs the pipeline. - Future run(); -} diff --git a/packages/contracts/lib/src/queue/queue.dart b/packages/contracts/lib/src/queue/queue.dart deleted file mode 100644 index 2501fd2..0000000 --- a/packages/contracts/lib/src/queue/queue.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// Queue package contracts -export 'queue_contract.dart'; diff --git a/packages/contracts/lib/src/queue/queue_contract.dart b/packages/contracts/lib/src/queue/queue_contract.dart deleted file mode 100644 index 94c0a55..0000000 --- a/packages/contracts/lib/src/queue/queue_contract.dart +++ /dev/null @@ -1,284 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Contract for queue operations. -/// -/// Laravel-compatible: Core queue functionality matching Laravel's Queue -/// interface, adapted for Dart's type system and async patterns. -@sealed -abstract class QueueContract { - /// Pushes a job onto the queue. - /// - /// Laravel-compatible: Core push method. - /// Uses dynamic job type for flexibility. - Future push(dynamic job, [String? queue]); - - /// Pushes a job onto a specific queue. - /// - /// Laravel-compatible: Queue-specific push. - /// Uses dynamic job type for flexibility. - Future pushOn(String queue, dynamic job); - - /// Pushes a delayed job onto the queue. - /// - /// Laravel-compatible: Delayed job push. - /// Uses Duration instead of DateTime/Carbon. - Future later(Duration delay, dynamic job, [String? queue]); - - /// Pushes a delayed job onto a specific queue. - /// - /// Laravel-compatible: Queue-specific delayed push. - /// Uses Duration instead of DateTime/Carbon. - Future laterOn(String queue, Duration delay, dynamic job); - - /// Pushes multiple jobs onto the queue. - /// - /// Laravel-compatible: Bulk job push. - /// Uses dynamic job type for flexibility. - Future bulk(List jobs, [String? queue]); - - /// Gets the next job from the queue. - /// - /// Laravel-compatible: Job pop operation. - Future pop([String? queue]); - - /// Creates a job batch. - /// - /// Laravel-compatible: Batch creation. - BatchContract batch(List jobs); - - /// Gets a queue connection. - /// - /// Laravel-compatible: Connection retrieval. - QueueConnectionContract connection([String? name]); -} - -/// Contract for queue jobs. -/// -/// Laravel-compatible: Core job interface matching Laravel's Job -/// contract, with platform-specific extensions. -@sealed -abstract class JobContract { - /// Gets the job ID. - /// - /// Laravel-compatible: Job identifier. - String get id; - - /// Gets the job payload. - /// - /// Laravel-compatible: Job data. - Map get payload; - - /// Gets the number of attempts. - /// - /// Laravel-compatible: Attempt tracking. - int get attempts; - - /// Gets the maximum number of tries. - /// - /// Laravel-compatible: Retry limit. - int get tries; - - /// Gets the job timeout in seconds. - /// - /// Laravel-compatible: Timeout configuration. - int get timeout; - - /// Gets the queue name. - /// - /// Laravel-compatible: Queue designation. - String? get queue; - - /// Gets the job delay. - /// - /// Laravel-compatible: Delay configuration. - /// Uses Duration instead of DateTime/Carbon. - Duration? get delay; - - /// Whether the job should be encrypted. - /// - /// Platform-specific: Adds encryption support. - bool get shouldBeEncrypted; - - /// Whether to dispatch after commit. - /// - /// Laravel-compatible: Transaction support. - bool get afterCommit; - - /// Executes the job. - /// - /// Laravel-compatible: Core job execution. - Future handle(); - - /// Handles job failure. - /// - /// Laravel-compatible: Failure handling. - Future failed([Exception? exception]); - - /// Releases the job back to the queue. - /// - /// Laravel-compatible: Job release. - /// Uses Duration instead of DateTime/Carbon. - Future release([Duration? delay]); - - /// Deletes the job. - /// - /// Laravel-compatible: Job deletion. - Future delete(); -} - -/// Contract for job batches. -/// -/// Laravel-compatible: Batch operations matching Laravel's batch -/// functionality, with platform-specific extensions. -@sealed -abstract class BatchContract { - /// Gets the batch ID. - /// - /// Laravel-compatible: Batch identifier. - String get id; - - /// Gets the jobs in the batch. - /// - /// Laravel-compatible: Batch jobs. - List get jobs; - - /// Adds jobs to the batch. - /// - /// Laravel-compatible: Job addition. - void add(List jobs); - - /// Dispatches the batch. - /// - /// Laravel-compatible: Batch dispatch. - Future dispatch(); - - /// Allows failures in the batch. - /// - /// Laravel-compatible: Failure configuration. - BatchContract allowFailures(); - - /// Sets the batch name. - /// - /// Laravel-compatible: Batch naming. - BatchContract name(String name); - - /// Adds a callback when all jobs finish. - /// - /// Laravel-compatible: Success callback. - BatchContract then(void Function(BatchContract) callback); - - /// Adds a callback when the batch fails. - /// - /// Laravel-compatible: Error callback. - BatchContract onError(void Function(BatchContract, dynamic) callback); - - /// Gets the batch progress. - /// - /// Platform-specific: Progress tracking. - double get progress; - - /// Gets finished job count. - /// - /// Platform-specific: Completion tracking. - int get finished; - - /// Gets failed job count. - /// - /// Platform-specific: Failure tracking. - int get failed; - - /// Gets pending job count. - /// - /// Platform-specific: Pending tracking. - int get pending; - - /// Gets total job count. - /// - /// Platform-specific: Size tracking. - int get total; -} - -/// Contract for queue connections. -/// -/// Laravel-compatible: Connection management matching Laravel's -/// queue connection functionality. -@sealed -abstract class QueueConnectionContract { - /// Gets the connection name. - /// - /// Laravel-compatible: Connection identifier. - String get name; - - /// Gets the connection driver. - /// - /// Laravel-compatible: Driver type. - String get driver; - - /// Gets the connection config. - /// - /// Laravel-compatible: Configuration access. - Map get config; - - /// Pushes a job onto the queue. - /// - /// Laravel-compatible: Job push. - Future push(dynamic job, [String? queue]); - - /// Gets the next job from the queue. - /// - /// Laravel-compatible: Job pop. - Future pop([String? queue]); - - /// Gets queue size. - /// - /// Laravel-compatible: Size check. - Future size([String? queue]); - - /// Clears the queue. - /// - /// Laravel-compatible: Queue clear. - Future clear([String? queue]); - - /// Pauses job processing. - /// - /// Laravel-compatible: Processing pause. - Future pause([String? queue]); - - /// Resumes job processing. - /// - /// Laravel-compatible: Processing resume. - Future resume([String? queue]); -} - -/// Contract for queue manager. -/// -/// Laravel-compatible: Manager functionality matching Laravel's -/// queue manager interface. -@sealed -abstract class QueueManagerContract { - /// Gets a queue connection. - /// - /// Laravel-compatible: Connection retrieval. - QueueConnectionContract connection([String? name]); - - /// Gets the default connection name. - /// - /// Laravel-compatible: Default connection. - String get defaultConnection; - - /// Sets the default connection name. - /// - /// Laravel-compatible: Default connection. - set defaultConnection(String name); - - /// Gets connection configuration. - /// - /// Laravel-compatible: Config access. - Map getConfig(String name); - - /// Extends available drivers. - /// - /// Laravel-compatible: Driver extension. - void extend(String driver, - QueueConnectionContract Function(Map) callback); -} diff --git a/packages/contracts/lib/src/reflection/reflection.dart b/packages/contracts/lib/src/reflection/reflection.dart deleted file mode 100644 index 5d24e43..0000000 --- a/packages/contracts/lib/src/reflection/reflection.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// Reflection package contracts -export 'reflector_contract.dart'; diff --git a/packages/contracts/lib/src/reflection/reflector_contract.dart b/packages/contracts/lib/src/reflection/reflector_contract.dart deleted file mode 100644 index 7d53a25..0000000 --- a/packages/contracts/lib/src/reflection/reflector_contract.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Contract for reflected type parameters -@sealed -abstract class ReflectedTypeParameterContract { - /// Gets the name of the type parameter - String get name; -} - -/// Contract for reflected types -@sealed -abstract class ReflectedTypeContract { - /// Gets the name of the type - String get name; - - /// Gets the type parameters if the type is generic - List get typeParameters; - - /// Gets the actual Dart type being reflected - Type get reflectedType; - - /// Checks if this type is assignable to another type - bool isAssignableTo(ReflectedTypeContract? other); - - /// Creates a new instance of this type - ReflectedInstanceContract newInstance( - String constructorName, List positionalArguments, - [Map namedArguments = const {}, - List typeArguments = const []]); -} - -/// Contract for reflected parameters -@sealed -abstract class ReflectedParameterContract { - /// Gets the parameter name - String get name; - - /// Gets the parameter annotations - List get annotations; - - /// Gets the parameter type - ReflectedTypeContract get type; - - /// Whether the parameter is required - bool get isRequired; - - /// Whether the parameter is named - bool get isNamed; -} - -/// Contract for reflected functions -@sealed -abstract class ReflectedFunctionContract { - /// Gets the function name - String get name; - - /// Gets the function's type parameters - List get typeParameters; - - /// Gets the function's annotations - List get annotations; - - /// Gets the function's return type - ReflectedTypeContract? get returnType; - - /// Gets the function's parameters - List get parameters; - - /// Whether the function is a getter - bool get isGetter; - - /// Whether the function is a setter - bool get isSetter; - - /// Invokes the function - ReflectedInstanceContract invoke(Invocation invocation); -} - -/// Contract for reflected declarations -@sealed -abstract class ReflectedDeclarationContract { - /// Gets the declaration name - String get name; - - /// Whether the declaration is static - bool get isStatic; - - /// Gets the associated function if any - ReflectedFunctionContract? get function; -} - -/// Contract for reflected classes -@sealed -abstract class ReflectedClassContract extends ReflectedTypeContract { - /// Gets the class annotations - List get annotations; - - /// Gets the class constructors - List get constructors; - - /// Gets the class declarations - List get declarations; -} - -/// Contract for reflected instances -@sealed -abstract class ReflectedInstanceContract { - /// Gets the instance type - ReflectedTypeContract get type; - - /// Gets the instance class - ReflectedClassContract get clazz; - - /// Gets the actual instance being reflected - Object? get reflectee; - - /// Gets a field value - ReflectedInstanceContract getField(String name); -} - -/// Core reflector contract for type introspection. -/// -/// This contract defines the interface for reflection capabilities, -/// allowing runtime inspection and manipulation of types, classes, -/// functions, and instances. -@sealed -abstract class ReflectorContract { - /// Gets the name from a symbol - String? getName(Symbol symbol); - - /// Reflects a class type - ReflectedClassContract? reflectClass(Type clazz); - - /// Reflects a function - ReflectedFunctionContract? reflectFunction(Function function); - - /// Reflects a type - ReflectedTypeContract? reflectType(Type type); - - /// Reflects an instance - ReflectedInstanceContract? reflectInstance(Object object); - - /// Reflects the Future of a type - /// - /// Throws: - /// - UnsupportedError if dart:mirrors is not available - ReflectedTypeContract reflectFutureOf(Type type); -} diff --git a/packages/contracts/lib/src/routing/routing.dart b/packages/contracts/lib/src/routing/routing.dart deleted file mode 100644 index af283b4..0000000 --- a/packages/contracts/lib/src/routing/routing.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// Routing package contracts -export 'routing_contract.dart'; diff --git a/packages/contracts/lib/src/routing/routing_contract.dart b/packages/contracts/lib/src/routing/routing_contract.dart deleted file mode 100644 index 684e7eb..0000000 --- a/packages/contracts/lib/src/routing/routing_contract.dart +++ /dev/null @@ -1,260 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Contract for router functionality. -/// -/// Laravel-compatible: Core routing functionality matching Laravel's Router -/// interface, with platform-specific generic type support. -@sealed -abstract class RouterContract { - /// Adds a route that responds to any HTTP method. - /// - /// Laravel-compatible: Any-method route registration. - RouteContract any(String path, T handler, - {Iterable middleware = const []}); - - /// Adds a route that responds to GET requests. - /// - /// Laravel-compatible: GET route registration. - RouteContract get(String path, T handler, - {Iterable middleware = const []}); - - /// Adds a route that responds to POST requests. - /// - /// Laravel-compatible: POST route registration. - RouteContract post(String path, T handler, - {Iterable middleware = const []}); - - /// Adds a route that responds to PUT requests. - /// - /// Laravel-compatible: PUT route registration. - RouteContract put(String path, T handler, - {Iterable middleware = const []}); - - /// Adds a route that responds to DELETE requests. - /// - /// Laravel-compatible: DELETE route registration. - RouteContract delete(String path, T handler, - {Iterable middleware = const []}); - - /// Adds a route that responds to PATCH requests. - /// - /// Laravel-compatible: PATCH route registration. - RouteContract patch(String path, T handler, - {Iterable middleware = const []}); - - /// Adds a route that responds to OPTIONS requests. - /// - /// Laravel-compatible: OPTIONS route registration. - RouteContract options(String path, T handler, - {Iterable middleware = const []}); - - /// Creates a route group with shared attributes. - /// - /// Laravel-compatible: Route grouping with platform-specific - /// callback-based configuration. - /// - /// Parameters: - /// - [path]: The prefix path for the group. - /// - [callback]: Function to define routes within the group. - /// - [middleware]: Middleware to apply to all routes in the group. - void group(String path, void Function(RouterContract router) callback, - {Iterable middleware = const []}); - - /// Mounts another router at a path prefix. - /// - /// Platform-specific: Provides router composition functionality. - /// - /// Parameters: - /// - [path]: The path to mount at. - /// - [router]: The router to mount. - void mount(String path, RouterContract router); - - /// Resolves a route for a request. - /// - /// Laravel-compatible: Route matching with platform-specific - /// match result contract. - /// - /// Parameters: - /// - [method]: The HTTP method. - /// - [path]: The request path. - RouteMatchContract? resolve(String method, String path); -} - -/// Contract for route definitions. -/// -/// Laravel-compatible: Route definition interface matching Laravel's Route -/// class, with platform-specific enhancements. -@sealed -abstract class RouteContract { - /// Gets the route path pattern. - /// - /// Laravel-compatible: Route URI pattern. - String get path; - - /// Gets the HTTP method this route responds to. - /// - /// Laravel-compatible: HTTP method. - String get method; - - /// Gets the route handler. - /// - /// Laravel-compatible: Route action with generic typing. - T get handler; - - /// Gets the route middleware. - /// - /// Laravel-compatible: Route middleware. - Iterable get middleware; - - /// Gets the route name. - /// - /// Laravel-compatible: Route name accessor. - String? get name; - - /// Sets the route name. - /// - /// Laravel-compatible: Route name mutator. - set name(String? value); - - /// Gets the route parameters. - /// - /// Laravel-compatible: Route parameters. - Map get parameters; - - /// Makes a URI for this route. - /// - /// Laravel-compatible: URL generation. - /// - /// Parameters: - /// - [params]: The parameter values to use. - String makeUri(Map params); - - /// Gets the route's regular expression pattern. - /// - /// Platform-specific: Direct access to route pattern. - RegExp get pattern; - - /// Whether the route matches a path. - /// - /// Platform-specific: Direct path matching. - bool matches(String path); -} - -/// Contract for route matching results. -/// -/// Platform-specific: Defines detailed match results beyond -/// Laravel's basic route matching. -@sealed -abstract class RouteMatchContract { - /// Gets the matched route. - RouteContract get route; - - /// Gets the matched parameters. - Map get params; - - /// Gets any remaining path after the match. - String get remaining; - - /// Gets the full matched path. - String get matched; -} - -/// Contract for route parameters. -/// -/// Laravel-compatible: Parameter handling matching Laravel's -/// parameter constraints, with platform-specific validation. -@sealed -abstract class RouteParameterContract { - /// Gets the parameter name. - String get name; - - /// Gets the parameter pattern. - String? get pattern; - - /// Whether the parameter is optional. - bool get isOptional; - - /// Gets the default value. - dynamic get defaultValue; - - /// Validates a parameter value. - bool validate(String value); -} - -/// Contract for route collection. -/// -/// Laravel-compatible: Route collection functionality matching -/// Laravel's RouteCollection, with platform-specific enhancements. -@sealed -abstract class RouteCollectionContract { - /// Gets all routes. - /// - /// Laravel-compatible: Route listing. - Iterable> get routes; - - /// Gets routes by method. - /// - /// Laravel-compatible: Method filtering. - Iterable> getByMethod(String method); - - /// Gets a route by name. - /// - /// Laravel-compatible: Named route lookup. - RouteContract? getByName(String name); - - /// Adds a route to the collection. - /// - /// Laravel-compatible: Route registration. - void add(RouteContract route); - - /// Removes a route from the collection. - /// - /// Laravel-compatible: Route removal. - void remove(RouteContract route); - - /// Gets routes with a specific middleware. - /// - /// Platform-specific: Middleware filtering. - Iterable> getByMiddleware(T middleware); -} - -/// Contract for route groups. -/// -/// Laravel-compatible: Route grouping functionality matching -/// Laravel's route group features, with platform-specific additions. -@sealed -abstract class RouteGroupContract { - /// Gets the group prefix. - /// - /// Laravel-compatible: Group prefix. - String get prefix; - - /// Gets the group middleware. - /// - /// Laravel-compatible: Group middleware. - Iterable get middleware; - - /// Gets the group namespace. - /// - /// Laravel-compatible: Group namespace. - String? get namespace; - - /// Gets routes in this group. - /// - /// Laravel-compatible: Group routes. - Iterable> get routes; - - /// Adds a route to the group. - /// - /// Laravel-compatible: Route addition with platform-specific - /// middleware support. - RouteContract addRoute(String method, String path, T handler, - {Iterable middleware = const []}); - - /// Creates a sub-group. - /// - /// Laravel-compatible: Nested grouping with platform-specific - /// namespace support. - RouteGroupContract group(String prefix, - {Iterable middleware = const [], String? namespace}); -} diff --git a/packages/contracts/lib/src/support/support.dart b/packages/contracts/lib/src/support/support.dart deleted file mode 100644 index d8e5ba5..0000000 --- a/packages/contracts/lib/src/support/support.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// Support package contracts -export 'support_contract.dart'; diff --git a/packages/contracts/lib/src/support/support_contract.dart b/packages/contracts/lib/src/support/support_contract.dart deleted file mode 100644 index 2f06ed0..0000000 --- a/packages/contracts/lib/src/support/support_contract.dart +++ /dev/null @@ -1,263 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Contract for service providers. -/// -/// Laravel-compatible: Core service provider functionality matching -/// Laravel's ServiceProvider class, adapted for Dart's type system. -@sealed -abstract class ServiceProviderContract { - /// Registers application services. - /// - /// Laravel-compatible: Core registration method. - void register(); - - /// Bootstraps application services. - /// - /// Laravel-compatible: Core bootstrap method. - void boot(); - - /// Gets services provided by this provider. - /// - /// Laravel-compatible: Lists provided services. - List provides(); - - /// Gets events that trigger registration. - /// - /// Laravel-compatible: Lists registration triggers. - List when(); - - /// Whether provider is deferred. - /// - /// Laravel-compatible: Controls lazy loading. - bool isDeferred(); - - /// Gets the application instance. - /// - /// Laravel-compatible: Application access. - /// Uses dynamic type for flexibility. - dynamic get app; - - /// Sets the application instance. - /// - /// Laravel-compatible: Application injection. - /// Uses dynamic type for flexibility. - set app(dynamic value); - - /// Gets booting callbacks. - /// - /// Laravel-compatible: Boot phase callbacks. - List get bootingCallbacks; - - /// Gets booted callbacks. - /// - /// Laravel-compatible: Post-boot callbacks. - List get bootedCallbacks; - - /// Registers a booting callback. - /// - /// Laravel-compatible: Boot phase hook. - void booting(Function callback); - - /// Registers a booted callback. - /// - /// Laravel-compatible: Post-boot hook. - void booted(Function callback); - - /// Calls booting callbacks. - /// - /// Laravel-compatible: Executes boot phase hooks. - void callBootingCallbacks(); - - /// Calls booted callbacks. - /// - /// Laravel-compatible: Executes post-boot hooks. - void callBootedCallbacks(); - - /// Merges configuration. - /// - /// Laravel-compatible: Config merging. - void mergeConfigFrom(String path, String key); - - /// Replaces configuration recursively. - /// - /// Laravel-compatible: Deep config replacement. - void replaceConfigRecursivelyFrom(String path, String key); - - /// Loads routes from file. - /// - /// Laravel-compatible: Route loading. - void loadRoutesFrom(String path); - - /// Loads views from directory. - /// - /// Laravel-compatible: View loading. - void loadViewsFrom(String path, String namespace); - - /// Loads view components. - /// - /// Laravel-compatible: Component loading. - void loadViewComponentsAs(String prefix, List components); - - /// Loads translations from directory. - /// - /// Laravel-compatible: Translation loading. - void loadTranslationsFrom(String path, String namespace); - - /// Loads JSON translations. - /// - /// Laravel-compatible: JSON translation loading. - void loadJsonTranslationsFrom(String path); - - /// Loads database migrations. - /// - /// Laravel-compatible: Migration loading. - void loadMigrationsFrom(dynamic paths); - - /// Loads model factories. - /// - /// Laravel-compatible: Factory loading. - @Deprecated('Will be removed in a future version.') - void loadFactoriesFrom(dynamic paths); - - /// Sets up after resolving listener. - /// - /// Laravel-compatible: Resolution hook. - void callAfterResolving(String name, Function callback); - - /// Publishes migrations. - /// - /// Laravel-compatible: Migration publishing. - void publishesMigrations(List paths, [dynamic groups]); - - /// Registers publishable paths. - /// - /// Laravel-compatible: Asset publishing. - void registerPublishables(Map paths, [dynamic groups]); - - /// Legacy method for registering publishables. - /// - /// Laravel-compatible: Legacy publish method. - @Deprecated('Use registerPublishables instead') - void publishes(Map paths, [dynamic groups]); - - /// Initializes publish array. - /// - /// Laravel-compatible: Publish setup. - void ensurePublishArrayInitialized(String className); - - /// Adds a publish group. - /// - /// Laravel-compatible: Group publishing. - void addPublishGroup(String group, Map paths); - - /// Gets paths to publish. - /// - /// Laravel-compatible: Publish path lookup. - Map pathsToPublish([String? provider, String? group]); - - /// Gets paths for provider or group. - /// - /// Laravel-compatible: Provider/group path lookup. - Map pathsForProviderOrGroup(String? provider, String? group); - - /// Gets paths for provider and group. - /// - /// Laravel-compatible: Combined path lookup. - Map pathsForProviderAndGroup(String provider, String group); - - /// Gets publishable providers. - /// - /// Laravel-compatible: Provider listing. - List publishableProviders(); - - /// Gets publishable migration paths. - /// - /// Laravel-compatible: Migration path listing. - List publishableMigrationPaths(); - - /// Gets publishable groups. - /// - /// Laravel-compatible: Group listing. - List publishableGroups(); - - /// Registers commands. - /// - /// Laravel-compatible: Command registration. - void commands(List commands); - - /// Gets default providers. - /// - /// Laravel-compatible: Default provider listing. - List defaultProviders(); - - /// Adds provider to bootstrap file. - /// - /// Laravel-compatible: Provider bootstrapping. - bool addProviderToBootstrapFile(String provider, [String? path]); - - /// Registers a singleton. - /// - /// Laravel-compatible: Singleton binding with Dart typing. - void singleton(T instance); - - /// Registers a factory binding. - /// - /// Laravel-compatible: Factory binding with Dart typing. - void bind(T Function(dynamic) factory); - - /// Gets a service. - /// - /// Laravel-compatible: Service resolution with Dart typing. - T make([Type? type]); - - /// Checks if service exists. - /// - /// Laravel-compatible: Binding check with Dart typing. - bool has(); - - /// Registers tagged bindings. - /// - /// Laravel-compatible: Tag binding with Dart typing. - void tag(List abstracts, List tags); - - /// Registers an event listener. - /// - /// Laravel-compatible: Event listener registration. - void listen(String event, Function listener); - - /// Registers middleware. - /// - /// Laravel-compatible: Middleware registration. - void middleware(String name, Function handler); -} - -/// Contract for deferrable providers. -/// -/// Laravel-compatible: Defines providers that can be loaded -/// on demand rather than at application startup. -@sealed -abstract class DeferrableProviderContract { - /// Gets services provided by this provider. - /// - /// Laravel-compatible: Lists deferred services. - List provides(); -} - -/// Contract for provider static functionality. -/// -/// Platform-specific: Provides static helper methods and properties -/// following Laravel's patterns for static configuration. -@sealed -abstract class ServiceProviderStaticContract { - /// Gets publishable migration paths. - static final List publishableMigrationPaths = []; - - /// Gets publishable paths. - static final Map> publishablePaths = {}; - - /// Gets publishable groups. - static final Map> publishableGroups = {}; - - /// Gets publishable provider paths. - static final Map> publishableProviderPaths = {}; -} diff --git a/packages/contracts/lib/src/testing/testing.dart b/packages/contracts/lib/src/testing/testing.dart deleted file mode 100644 index 1bb17b1..0000000 --- a/packages/contracts/lib/src/testing/testing.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// Testing package contracts -export 'testing_contract.dart'; diff --git a/packages/contracts/lib/src/testing/testing_contract.dart b/packages/contracts/lib/src/testing/testing_contract.dart deleted file mode 100644 index af9cc43..0000000 --- a/packages/contracts/lib/src/testing/testing_contract.dart +++ /dev/null @@ -1,302 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'dart:typed_data'; -import 'package:meta/meta.dart'; - -/// Contract for mock HTTP requests. -/// -/// This contract defines how HTTP requests should be mocked -/// for testing purposes. -@sealed -abstract class MockHttpRequestContract - implements HttpRequest, StreamSink>, StringSink { - /// Gets the request method. - @override - String get method; - - /// Gets the request URI. - @override - Uri get uri; - - /// Gets request headers. - @override - HttpHeaders get headers; - - /// Gets request cookies. - @override - List get cookies; - - /// Gets connection info. - @override - HttpConnectionInfo get connectionInfo; - - /// Gets request session. - @override - HttpSession get session; - - /// Gets request content length. - @override - int get contentLength; - - /// Gets protocol version. - @override - String get protocolVersion; - - /// Gets SSL/TLS certificate. - @override - X509Certificate? get certificate; - - /// Gets whether connection is persistent. - @override - bool get persistentConnection; - - /// Gets requested URI. - @override - Uri get requestedUri; - - /// Sets requested URI. - set requestedUri(Uri value); - - /// Gets response object. - @override - HttpResponse get response; -} - -/// Contract for mock HTTP responses. -/// -/// This contract defines how HTTP responses should be mocked -/// for testing purposes. -@sealed -abstract class MockHttpResponseContract - implements HttpResponse, Stream> { - /// Gets/sets status code. - @override - int get statusCode; - @override - set statusCode(int value); - - /// Gets/sets reason phrase. - @override - String get reasonPhrase; - @override - set reasonPhrase(String value); - - /// Gets/sets content length. - @override - int get contentLength; - @override - set contentLength(int value); - - /// Gets/sets deadline. - @override - Duration? get deadline; - @override - set deadline(Duration? value); - - /// Gets/sets encoding. - @override - Encoding get encoding; - @override - set encoding(Encoding value); - - /// Gets/sets persistent connection flag. - @override - bool get persistentConnection; - @override - set persistentConnection(bool value); - - /// Gets/sets buffer output flag. - @override - bool get bufferOutput; - @override - set bufferOutput(bool value); - - /// Gets response headers. - @override - HttpHeaders get headers; - - /// Gets response cookies. - @override - List get cookies; - - /// Gets connection info. - @override - HttpConnectionInfo get connectionInfo; - - /// Gets done future. - @override - Future get done; - - /// Detaches socket. - @override - Future detachSocket({bool writeHeaders = true}); - - /// Redirects to location. - @override - Future redirect(Uri location, {int status = HttpStatus.movedTemporarily}); -} - -/// Contract for mock HTTP sessions. -/// -/// This contract defines how HTTP sessions should be mocked -/// for testing purposes. -@sealed -abstract class MockHttpSessionContract implements HttpSession { - /// Gets session ID. - @override - String get id; - - /// Gets/sets whether session is new. - @override - bool get isNew; - @override - set isNew(bool value); - - /// Gets session data. - Map get data; - - /// Gets session value. - @override - dynamic operator [](Object? key); - - /// Sets session value. - @override - void operator []=(dynamic key, dynamic value); - - /// Removes session value. - @override - dynamic remove(Object? key); - - /// Clears all session data. - @override - void clear(); - - /// Destroys the session. - @override - Future destroy(); -} - -/// Contract for mock HTTP headers. -/// -/// This contract defines how HTTP headers should be mocked -/// for testing purposes. -@sealed -abstract class MockHttpHeadersContract implements HttpHeaders { - /// Gets header value. - @override - String? value(String name); - - /// Adds header value. - @override - void add(String name, Object value, {bool preserveHeaderCase = false}); - - /// Removes header. - @override - void remove(String name, Object value); - - /// Removes all headers. - @override - void removeAll(String name); - - /// Sets header value. - @override - void set(String name, Object value, {bool preserveHeaderCase = false}); - - /// Gets header values. - @override - List? operator [](String name); - - /// Gets all header names. - @override - List get names; - - /// Gets header values. - @override - Iterable? getAll(String name); - - /// Clears all headers. - @override - void clear(); - - /// Gets whether headers are mutable. - @override - bool get mutable; - - /// Gets content type. - @override - ContentType? get contentType; - - /// Sets content type. - @override - set contentType(ContentType? value); - - /// Gets date. - @override - DateTime? get date; - - /// Sets date. - @override - set date(DateTime? value); - - /// Gets expires date. - @override - DateTime? get expires; - - /// Sets expires date. - @override - set expires(DateTime? value); - - /// Gets if-modified-since date. - @override - DateTime? get ifModifiedSince; - - /// Sets if-modified-since date. - @override - set ifModifiedSince(DateTime? value); - - /// Gets host. - @override - String? get host; - - /// Sets host. - @override - set host(String? value); - - /// Gets port. - @override - int? get port; - - /// Sets port. - @override - set port(int? value); - - /// Locks headers from modification. - void lock(); - - /// Gets whether headers are locked. - bool get locked; -} - -/// Contract for mock connection info. -/// -/// This contract defines how connection info should be mocked -/// for testing purposes. -@sealed -abstract class MockConnectionInfoContract implements HttpConnectionInfo { - /// Gets local address. - @override - InternetAddress get localAddress; - - /// Gets local port. - @override - int get localPort; - - /// Gets remote address. - @override - InternetAddress get remoteAddress; - - /// Gets remote port. - @override - int get remotePort; -} diff --git a/packages/contracts/pubspec.lock b/packages/contracts/pubspec.lock deleted file mode 100644 index 101471f..0000000 --- a/packages/contracts/pubspec.lock +++ /dev/null @@ -1,402 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" - url: "https://pub.dev" - source: hosted - version: "73.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.2" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" - url: "https://pub.dev" - source: hosted - version: "6.8.0" - args: - dependency: transitive - description: - name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 - url: "https://pub.dev" - source: hosted - version: "2.6.0" - async: - dependency: transitive - description: - name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 - url: "https://pub.dev" - source: hosted - version: "2.12.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - coverage: - dependency: transitive - description: - name: coverage - sha256: "4b03e11f6d5b8f6e5bb5e9f7889a56fe6c5cbe942da5378ea4d4d7f73ef9dfe5" - url: "https://pub.dev" - source: hosted - version: "1.11.0" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - glob: - dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" - url: "https://pub.dev" - source: hosted - version: "4.1.1" - io: - dependency: transitive - description: - name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - js: - dependency: transitive - description: - name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf - url: "https://pub.dev" - source: hosted - version: "0.7.1" - lints: - dependency: "direct dev" - description: - name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" - url: "https://pub.dev" - source: hosted - version: "0.1.2-main.4" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - meta: - dependency: "direct main" - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - package_config: - dependency: transitive - description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - pool: - dependency: transitive - description: - name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" - source: hosted - version: "1.5.1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - shelf: - dependency: transitive - description: - name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" - source: hosted - version: "1.4.2" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 - url: "https://pub.dev" - source: hosted - version: "1.1.3" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b - url: "https://pub.dev" - source: hosted - version: "2.1.2" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" - url: "https://pub.dev" - source: hosted - version: "0.10.12" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" - url: "https://pub.dev" - source: hosted - version: "1.12.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6" - url: "https://pub.dev" - source: hosted - version: "1.4.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test: - dependency: "direct dev" - description: - name: test - sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" - url: "https://pub.dev" - source: hosted - version: "1.25.8" - test_api: - dependency: transitive - description: - name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" - url: "https://pub.dev" - source: hosted - version: "0.7.3" - test_core: - dependency: transitive - description: - name: test_core - sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" - url: "https://pub.dev" - source: hosted - version: "0.6.5" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" - url: "https://pub.dev" - source: hosted - version: "14.3.1" - watcher: - dependency: transitive - description: - name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb - url: "https://pub.dev" - source: hosted - version: "1.1.0" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" - url: "https://pub.dev" - source: hosted - version: "0.1.6" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - yaml: - dependency: transitive - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" - url: "https://pub.dev" - source: hosted - version: "3.1.2" -sdks: - dart: ">=3.5.0 <4.0.0" diff --git a/packages/contracts/pubspec.yaml b/packages/contracts/pubspec.yaml deleted file mode 100644 index c95ca4b..0000000 --- a/packages/contracts/pubspec.yaml +++ /dev/null @@ -1,13 +0,0 @@ -name: platform_contracts -description: Core contracts for the Platform framework -version: 1.0.0 - -environment: - sdk: '>=3.0.0 <4.0.0' - -dependencies: - meta: ^1.9.0 - -dev_dependencies: - lints: ^2.0.0 - test: ^1.21.0 diff --git a/packages/events/.gitignore b/packages/cookie/.gitignore similarity index 100% rename from packages/events/.gitignore rename to packages/cookie/.gitignore diff --git a/packages/events/CHANGELOG.md b/packages/cookie/CHANGELOG.md similarity index 100% rename from packages/events/CHANGELOG.md rename to packages/cookie/CHANGELOG.md diff --git a/packages/events/LICENSE.md b/packages/cookie/LICENSE.md similarity index 100% rename from packages/events/LICENSE.md rename to packages/cookie/LICENSE.md diff --git a/packages/cookie/README.md b/packages/cookie/README.md new file mode 100644 index 0000000..8b55e73 --- /dev/null +++ b/packages/cookie/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/events/analysis_options.yaml b/packages/cookie/analysis_options.yaml similarity index 100% rename from packages/events/analysis_options.yaml rename to packages/cookie/analysis_options.yaml diff --git a/packages/cookie/doc/.gitkeep b/packages/cookie/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/cookie/example/.gitkeep b/packages/cookie/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/cookie/lib/src/.gitkeep b/packages/cookie/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/events/pubspec.yaml b/packages/cookie/pubspec.yaml similarity index 57% rename from packages/events/pubspec.yaml rename to packages/cookie/pubspec.yaml index 22bde60..3ebd3a8 100644 --- a/packages/events/pubspec.yaml +++ b/packages/cookie/pubspec.yaml @@ -1,19 +1,15 @@ -name: platform_events -description: The Events Package for the Protevus Platform +name: platform_cookie +description: The Cookie Package for the Protevus Platform version: 0.0.1 homepage: https://protevus.com documentation: https://docs.protevus.com repository: https://github.com/protevus/platformo + environment: sdk: ^3.4.2 # Add regular dependencies here. dependencies: - platform_container: ^9.0.0 - angel3_mq: ^9.0.0 - angel3_event_bus: ^9.0.0 - platform_core: ^9.0.0 - angel3_reactivex: ^0.27.5 # path: ^1.8.0 dev_dependencies: diff --git a/packages/cookie/test/.gitkeep b/packages/cookie/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/core/.gitignore b/packages/core/.gitignore deleted file mode 100644 index 24d6831..0000000 --- a/packages/core/.gitignore +++ /dev/null @@ -1,71 +0,0 @@ -# See https://www.dartlang.org/tools/private-files.html - -# Files and directories created by pub -.dart_tool -.packages -.pub/ -build/ - -# If you're building an application, you may want to check-in your pubspec.lock -pubspec.lock - -# Directory created by dartdoc -# If you don't generate documentation locally you can remove this line. -doc/api/ - -### Dart template -# See https://www.dartlang.org/tools/private-files.html - -# Files and directories created by pub - -# SDK 1.20 and later (no longer creates packages directories) - -# Older SDK versions -# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) -.project -.buildlog -**/packages/ - - -# Files created by dart2js -# (Most Dart developers will use pub build to compile Dart, use/modify these -# rules if you intend to use dart2js directly -# Convention is to use extension '.dart.js' for Dart compiled to Javascript to -# differentiate from explicit Javascript files) -*.dart.js -*.part.js -*.js.deps -*.js.map -*.info.json - -# Directory created by dartdoc - -# Don't commit pubspec lock file -# (Library packages only! Remove pattern if developing an application package) -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff: - -## VsCode -.vscode/ - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -.idea/ -/out/ -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties diff --git a/packages/core/AUTHORS.md b/packages/core/AUTHORS.md deleted file mode 100644 index ac95ab5..0000000 --- a/packages/core/AUTHORS.md +++ /dev/null @@ -1,12 +0,0 @@ -Primary Authors -=============== - -* __[Thomas Hii](dukefirehawk.apps@gmail.com)__ - - Thomas is the current maintainer of the code base. He has refactored and migrated the - code base to support NNBD. - -* __[Tobe O](thosakwe@gmail.com)__ - - Tobe has written much of the original code prior to NNBD migration. He has moved on and - is no longer involved with the project. diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md deleted file mode 100644 index 28c3e12..0000000 --- a/packages/core/CHANGELOG.md +++ /dev/null @@ -1,452 +0,0 @@ -# Change Log - -## 8.4.0 - -* Require Dart >= 3.3 -* Updated `lints` to 4.0.0 - -## 8.3.2 - -* Updated README - -## 8.3.1 - -* Updated repository link - -## 8.3.0 - -* Updated `lints` to 3.0.0 -* Fixed linter warnings - -## 8.2.0 - -* Add `addResponseHeader` to `PlatformHttp` to add headers to HTTP default response -* Add `removeResponseHeader` to `PlatformHttp` to remove headers from HTTP default response - -## 8.1.1 - -* Updated broken image on README - -## 8.1.0 - -* Updated `uuid` to 4.0.0 - -## 8.0.0 - -* Require Dart >= 3.0 -* Updated `http` to 1.0.0 - -## 7.0.4 - -* Updated `Expose` fields to non-nullable -* Updated `Controller` to use non-nullable field - -## 7.0.3 - -* Fixed issue #83. Allow Http request to return null headers instead of throwing an exception. - -## 7.0.2 - -* Added performance benchmark to README - -## 7.0.1 - -* Fixed `BytesBuilder` warnings - -## 7.0.0 - -* Require Dart >= 2.17 - -## 6.0.0 - -* Require Dart >= 2.16 -* Updated `container` to non nullable -* Updated `angel` to non nullable -* Updated `logger` to non nullable -* Refactored error handler - -## 5.0.0 - -* Skipped release - -## 4.2.4 - -* Fixed issue 48. Log not working in development - -## 4.2.3 - -* Fixed `res.json()` throwing bad state exception - -## 4.2.2 - -* Added `Date` to response header -* Updated `Server: Protevus` response header - -## 4.2.1 - -* Updated `package:angel3_container` - -## 4.2.0 - -* Updated to `package:belatuk_combinator` -* Updated to `package:belatuk_merge_map` -* Updated linter to `package:lints` - -## 4.1.3 - -* Updated README - -## 4.1.2 - -* Updated README -* Fixed NNBD issues - -## 4.1.1 - -* Updated link to `Protevus` home page -* Fixed pedantic warnings - -## 4.1.0 - -* Replaced `http_server` with `belatuk_http_server` - -## 4.0.4 - -* Fixed response returning incorrect status code - -## 4.0.3 - -* Fixed "Primitive after parsed param injection" test case -* Fixed "Cannot remove all unless explicitly set" test case -* Fixed "null" test case - -## 4.0.2 - -* Updated README - -## 4.0.1 - -* Updated README - -## 4.0.0 - -* Migrated to support Dart >= 2.12 NNBD - -## 3.0.0 - -* Migrated to work with Dart >= 2.12 Non NNBD - -## 2.1.1 - -* `PlatformHttp.uri` now returns an empty `Uri` if the server is not listening. - -## 2.1.0 - -* This release was originally planned to be `2.0.5`, but it adds several features, and has -therefore been bumped to `2.1.0`. -* Fix a new (did not appear before 2.6/2.7) type error causing compilation to fail. - - -## 2.0.5-beta - -* Make `@Expose()` in `Controller` optional. -* Add `allowHttp1` to `PlatformHttp2` constructors. -* Add `deserializeBody` and `decodeBody` to `RequestContext`. -* Add `HostnameRouter`, which allows for routing based on hostname. -* Default to using `ThrowingReflector`, instead of `EmptyReflector`. This will give a more descriptive -error when trying to use controllers, etc. without reflection enabled. -* `mountController` returns the mounted controller. - -## 2.0.4+1 - -* Run `Controller.configureRoutes` before mounting `@Expose` routes. -* Make `Controller.configureServer` always return a `Future`. - -## 2.0.4 - -* Prepare for Dart SDK change to `Stream>` that are now - `Stream`. -* Accept any content type if accept header is missing. See -[this PR](https://github.com/angel-dart/framework/pull/239). - -## 2.0.3 - -* Patch up a bug caused by an upstream change to Dart's stream semantics. -See more: - -## 2.0.2+1 - -* Fix a bug in the implementation of `Controller.applyRoutes`. - -## 2.0.2 - -* Make `ResponseContext` *explicitly* implement `StreamConsumer` (though technically it already did???) -* Split `Controller.configureServer` to create `Controller.applyRoutes`. - -## 2.0.1 - -* Tracked down a bug in `Driver.runPipeline` that allowed fallback -handlers to run, even after the response was closed. -* Add `RequestContext.shutdownHooks`. -* Call `RequestContext.close` in `Driver.sendResponse`. -* ProtevusConfigurer is now `FutureOr`, instead of just `FutureOr`. -* Use a `Container.has` check in `Driver.sendResponse`. -* Remove unnecessary `new` and `const`. - -## 2.0.0 - -* Protevus 2! :angel: :rocket: - -## 2.0.0-rc.10 - -* Fix an error that prevented `PlatformHttp2.custom` from working properly. -* Add `startSharedHttp2`. - -## 2.0.0-rc.9 - -* Fix some bugs in the `HookedService` implementation that skipped -the outputs of `before` events. - -## 2.0.0-rc.8 - -* Fix `MapService` flaw where clients could remove all records, even if `allowRemoveAll` were `false`. - -## 2.0.0-rc.7 - -* `AnonymousService` can override `readData`. -* `Service.map` now overrides `readData`. -* `HookedService.readData` forwards to `inner`. - -## 2.0.0-rc.6 - -* Make `redirect` and `download` methods asynchronous. - -## 2.0.0-rc.5 - -* Make `serializer` `FutureOr Function(Object)`. -* Make `ResponseContext.serialize` return `Future`. - -## 2.0.0-rc.4 - -* Support resolution of asynchronous injections in controllers and `ioc`. -* Inject `RequestContext` and `ResponseContext` into requests. - -## 2.0.0-rc.3 - -* `MapService.modify` was not actually modifying items. - -## 2.0.0-rc.2 - -* Fixes Pub analyzer lints (see `angel_route@3.0.6`) - -## 2.0.0-rc.1 - -* Fix logic error that allowed content to be written to streaming responses after `close` was closed. - -## 2.0.0-rc.0 - -* Log a warning when no `reflector` is provided. -* Add `ProtevusEnvironment` class. - * Add `Protevus.environment`. - * Deprecated `app.isProduction` in favor of `app.environment.isProduction`. -* Allow setting of `bodyAsObject`, `bodyAsMap`, or `bodyAsList` **exactly once**. -* Resolve named singletons in `resolveInjection`. -* Fix a bug where `Service.parseId` would attempt to parse an `int`. -* Replace as Data cast in Service.dart with a method that throws a 400 on error. - -## 2.0.0-alpha.24 - -* Add `ProtevusEnv` class to `core`. -* Deprecate `Protevus.isProduction`, in favor of `ProtevusEnv`. - -## 2.0.0-alpha.23 - -* `ResponseContext.render` sets `charset` to `utf8` in `contentType`. - -## 2.0.0-alpha.22 - -* Update pipeline handling mechanism, and inject a `MiddlewarePipelineIterator`. - * This allows routes to know where in the resolution process they exist, at runtime. - -## 2.0.0-alpha.21 - -* Update for `angel_route@3.0.4` compatibility. -* Add `readAsBytes` and `readAsString` to `UploadedFile`. -* URI-decode path components in HTTP2. - -## 2.0.0-alpha.20 - -* Inject the `MiddlewarePipeline` into requests. - -## 2.0.0-alpha.19 - -* `parseBody` checks for null content type, and throws a `400` if none was given. -* Add `ResponseContext.contentLength`. -* Update `streamFile` to set content length, and also to work on `HEAD` requests. - -## 2.0.0-alpha.18 - -* Upgrade `http2` dependency. -* Upgrade `uuid` dependency. -* Fixed a bug that prevented body parsing from ever completing with `http2`. -* Add `Providers.hashCode`. - -## 2.0.0-alpha.17 - -* Revert the migration to `lumberjack` for now. In the future, when it's more -stable, there'll be a conversion, perhaps. - -## 2.0.0-alpha.16 - -* Use `package:lumberjack` for logging. - -## 2.0.0-alpha.15 - -* Remove dependency on `body_parser`. -* `RequestContext` now exposes a `Stream> get body` getter. - * Calling `RequestContext.parseBody()` parses its contents. - * Added `bodyAsMap`, `bodyAsList`, `bodyAsObject`, and `uploadedFiles` to `RequestContext`. - * Removed `Protevus.keepRawRequestBuffers` and anything that had to do with buffering request bodies. - -## 2.0.0-alpha.14 - -* Patch `HttpResponseContext._openStream` to send content-length. - -## 2.0.0-alpha.13 - -* Fixed a logic error in `HttpResponseContext` that prevented status codes from being sent. - -## 2.0.0-alpha.12 - -* Remove `ResponseContext.sendFile`. -* Add `Protevus.mimeTypeResolver`. -* Fix a bug where an unknown MIME type on `streamFile` would return a 500. - -## 2.0.0-alpha.11 - -* Add `readMany` to `Service`. -* Allow `ResponseContext.redirect` to take a `Uri`. -* Add `Protevus.mountController`. -* Add `Protevus.findServiceOf`. -* Roll in HTTP/2. See `pkg:angel_framework/http2.dart`. - -## 2.0.0-alpha.10 - -* All calls to `Service.parseId` are now affixed with the `` argument. -* Added `uri` getter to `PlatformHttp`. -* The default for `parseQuery` now wraps query parameters in `Map.from`. - This resolves a bug in `package:angel_validate`. - -## 2.0.0-alpha.9 - -* Add `Service.map`. - -## 2.0.0-alpha.8 - -* No longer export HTTP-specific code from `angel_framework.dart`. - An import of `import 'package:angel_framework/http.dart';` will be necessary in most cases now. - -## 2.0.0-alpha.7 - -* Force a tigher contract on services. They now must return `Data` on all - methods except for `index`, which returns a `List`. - -## 2.0.0-alpha.6 - -* Allow passing a custom `Container` to `handleContained` and co. - -## 2.0.0-alpha.5 - -* `MapService` methods now explicitly return `Map`. - -## 2.0.0-alpha.4 - -* Renamed `waterfall` to `chain`. -* Renamed `Routable.service` to `Routable.findService`. - * Also `Routable.findHookedService`. - -## 2.0.0-alpha.3 - -* Added `` type parameters to `Service`. -* `HookedService` now follows suit, and takes a third parameter, pointing to the inner service. -* `Routable.use` now uses the generic parameters added to `Service`. -* Added generic usage to `HookedServiceListener`, etc. -* All service methods take `Map` as `params` now. - -## 2.0.0-alpha.2 - -* Added `ResponseContext.detach`. - -## 2.0.0-alpha.1 - -* Removed `Protevus.injectEncoders`. -* Added `Providers.toJson`. -* Moved `Providers.graphql` to `Providers.graphQL`. -* `Protevus.optimizeForProduction` no longer calls `preInject`, - as it does not need to. -* Rename `ResponseContext.enableBuffer` to `ResponseContext.useBuffer`. - -## 2.0.0-alpha - -* Removed `random_string` dependency. -* Moved reflection to `package:angel_container`. -* Upgraded `package:file` to `5.0.0`. -* `ResponseContext.sendFile` now uses `package:file`. -* Abandon `ContentType` in favor of `MediaType`. -* Changed view engine to use `Map`. -* Remove dependency on `package:json_god` by default. -* Remove dependency on `package:dart2_constant`. -* Moved `lib/hooks.dart` into `package:angel_hooks`. -* Moved `TypedService` into `package:angel_typed_service`. -* Completely removed the `ProtevusBase` class. -* Removed all `@deprecated` symbols. -* `Service.toId` was renamed to `Service.parseId`; it also now uses its - single type argument to determine how to parse a value. \* In addition, this method was also made `static`. -* `RequestContext` and `ResponseContext` are now generic, and take a - single type argument pointing to the underlying request/response type, - respectively. -* `RequestContext.io` and `ResponseContext.io` are now permanently - gone. -* `HttpRequestContextImpl` and `HttpResponseContextImpl` were renamed to - `HttpRequestContext` and `HttpResponseContext`. -* Lazy-parsing request bodies is now the default; `Protevus.lazyParseBodies` was replaced - with `Protevus.eagerParseRequestBodies`. -* `Protevus.storeOriginalBuffer` -> `Protevus.storeRawRequestBuffers`. -* The methods `lazyBody`, `lazyFiles`, and `lazyOriginalBuffer` on `ResponseContext` were all - replaced with `parseBody`, `parseUploadedFiles`, and `parseRawRequestBuffer`, respectively. -* Removed the synchronous equivalents of the above methods (`body`, `files`, and `originalBuffer`), - as well as `query`. -* Removed `Protevus.injections` and `RequestContext.injections`. -* Removed `Protevus.inject` and `RequestContext.inject`. -* Removed a dependency on `package:pool`, which also meant removing `PlatformHttp.throttle`. -* Remove the `RequestMiddleware` typedef; from now on, one should use `ResponseContext.end` - exclusively to close responses. -* `waterfall` will now only accept `RequestHandler`. -* `Routable`, and all of its subclasses, now extend `Router`, and therefore only - take routes in the form of `FutureOr myFunc(RequestContext, ResponseContext res)`. -* `@Middleware` now takes an `Iterable` of `RequestHandler`s. -* `@Expose.path` now *must* be a `String`, not just any `Pattern`. -* `@Expose.middleware` now takes `Iterable`, instead of just `List`. -* `createDynamicHandler` was renamed to `ioc`, and is now used to run IoC-aware handlers in a - type-safe manner. -* `RequestContext.params` is now a `Map`, rather than just a `Map`. -* Removed `RequestContext.grab`. -* Removed `RequestContext.properties`. -* Removed the defunct `debug` property where it still existed. -* `Routable.use` now only accepts a `Service`. -* Removed `Protevus.createZoneForRequest`. -* Removed `Protevus.defaultZoneCreator`. -* Added all flags to the `Protevus` constructor, ex. `Protevus.eagerParseBodies`. -* Fix a bug where synchronous errors in `handleRequest` would not be caught. -* `PlatformHttp.useZone` now defaults to `false`. -* `ResponseContext` now starts in streaming mode by default; the response buffer is opt-in, - as in many cases it is unnecessary and slows down response time. -* `ResponseContext.streaming` was replaced by `ResponseContext.isBuffered`. -* Made `LockableBytesBuilder` public. -* Removed the now-obsolete `ResponseContext.willCloseItself`. -* Removed `ResponseContext.dispose`. -* Removed the now-obsolete `ResponseContext.end`. -* Removed the now-obsolete `ResponseContext.releaseCorrespondingRequest`. -* `preInject` now takes a `Reflector` as its second argument. -* `Protevus.reflector` defaults to `const EmptyReflector()`, disabling - reflection out-of-the-box. diff --git a/packages/core/LICENSE b/packages/core/LICENSE deleted file mode 100644 index df5e063..0000000 --- a/packages/core/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2021, dukefirehawk.com -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/core/README.md b/packages/core/README.md deleted file mode 100644 index 56ea484..0000000 --- a/packages/core/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Protevus Platform Core - -

Protevus Logo

- -[![Pub Version (including pre-releases)](https://img.shields.io/pub/v/protevus_core?include_prereleases)](https://pub.dev/packages/protevus_core) -[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) -[![License](https://img.shields.io/github/license/protevus/platform)](https://github.com/protevus/platform/blob/main/packages/core/LICENSE) - -Protevus Platform Core is a high-powered HTTP server framework for Dart, designed to be minimal yet extensible. It provides a robust foundation for building scalable and flexible web applications. - -## Features - -- Dependency injection -- Sophisticated routing -- Authentication support -- ORM integration -- GraphQL support -- Extensible plugin architecture -- HTTP/2 support -- WebSocket capabilities -- File handling and MIME type detection -- Environment configuration management -- Performance optimization with caching mechanisms - -## Installation - -Add `protevus_core` to your `pubspec.yaml`: - -```yaml -dependencies: - protevus_core: ^latest_version -``` \ No newline at end of file diff --git a/packages/core/analysis_options.yaml b/packages/core/analysis_options.yaml deleted file mode 100644 index ea2c9e9..0000000 --- a/packages/core/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: package:lints/recommended.yaml \ No newline at end of file diff --git a/packages/core/dev.key b/packages/core/dev.key deleted file mode 100644 index 5d49ae7..0000000 --- a/packages/core/dev.key +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIE5DAcBgoqhkiG9w0BDAEBMA4ECL7L6rj6uEHGAgIIAASCBMLbucyfqAkgCbhP -xNSHYllPMAv/dsIjtnsBwepCXPGkCBCuOAw/2FaCHjN9hBqL5V7fkrKeaemhm2YE -ycPtlHJYPDf3kEkyMjdZ9rIY6kePGfQizs2uJPcXj4YPyQ4HsfVXpOicKfQrouf5 -Mze9bGzeMN065q3iP4dYUMwHAyZYteXCsanQNHlqvsWli0W+H8St8fdsXefZhnv1 -qVatKWdNdWQ9t5MuljgNU2Vv56sHKEYXI0yLxk2QUMk8KlJfnmt8foYUsnPUXHmc -gIjLKwwVkpdololnEHSNu0cEOUPowjgJru+uMpn7vdNl7TPEQ9jbEgdNg4JwoYzU -0nao8WzjaSp7kzvZz0VFwKnk5AjstGvvuAWckADdq23QElbn/mF7AG1m/TBpYxzF -gTt37UdndS/AcvVznWVVrRP5iTSIawdIwvqI4s7rqsoE0GCcak+RhchgAz2gWKkS -oODUo0JL6pPVbJ3l4ebbaO6c99nDVc8dViPtc1EkStJEJ2O4kI4xgLSCr4Y9ahKn -oAaoSkX7Xxq3aQm+BzqSpLjdGL8atsqR/YVOIHYIl3gThvP0NfZGx1xHyvO5mCdZ -kHxSA7tKWxauZ3eQ2clbnzeRsl4El0WMHy/5K1ovene4v7sunmoXVtghBC8hK6eh -zMO9orex2PNQ/VQC7HCvtytunOVx1lkSBoNo7hR70igg6rW9H7UyoAoBOwMpT1xa -J6V62nqruTKOqFNfur7aHJGpHGtDb5/ickHeYCyPTvmGp67u4wChzKReeg02oECe -d1E5FKAcIa8s9TVOB6Z+HvTRNQZu2PsI6TJnjQRowvY9DAHiWTlJZBBY/pko3hxX -TsIeybpvRdEHpDWv86/iqtw1hv9CUxS/8ZTWUgBo+osShHW79FeDASr9FC4/Zn76 -ZDERTgV4YWlW/klVWcG2lFo7jix+OPXAB+ZQavLhlN1xdWBcIz1AUWjAM4hdPylW -HCX4PB9CQIPl2E7F+Y2p6nMcMWSJVBi5UIH7E9LfaBguXSzMmTk2Fw5p1aOQ6wfN -goVAMVwi8ppAVs741PfHdZ295xMmK/1LCxz5DeAdD/tsA/SYfT753GotioDuC7im -EyJ5JyvTr5I6RFFBuqt3NlUb3Hp16wP3B2x9DZiB6jxr0l341/NHgsyeBXkuIy9j -ON2mvpBPCJhS8kgWo3G0UyyKnx64tcgpGuSvZhGwPz843B6AbYyE6pMRfSWRMkMS -YZYa+VNKhR4ixdj07ocFZEWLVjCH7kxkE8JZXKt8jKYmkWd0lS1QVjgaKlO6lRa3 -q6SPJkhW6pvqobvcqVNXwi1XuzpZeEbuh0B7OTekFTTxx5g9XeDl56M8SVQ1KEhT -Q1t7H2Nba18WCB7cf+6PN0F0K0Jz1Kq7ZWaqEI/grX1m4RQuvNF5807sB/QKMO/Z -Gz3NXvHg5xTJRd/567lxPGkor0cE7qD1EZfmJ2HrBYXQ91bhgA7LToBuMZo6ZRXH -QfsanjbP4FPLMiGdQigLjj3A35L/f4sQOOVac/sRaFnm7pzcxsMvyVU/YtvGcjYE -xaOOVnamg661Wo0wksXoDjeSz/JIyyKO3Gwp1FSm2wGLjjy/Ehmqcqy8rvHuf07w -AUukhVtTNn4= ------END ENCRYPTED PRIVATE KEY----- \ No newline at end of file diff --git a/packages/core/dev.pem b/packages/core/dev.pem deleted file mode 100644 index 01756b2..0000000 --- a/packages/core/dev.pem +++ /dev/null @@ -1,57 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDKTCCAhGgAwIBAgIJAOWmjTS+OnTEMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV -BAMMDGludGVybWVkaWF0ZTAeFw0xNTA1MTgwOTAwNDBaFw0yMzA4MDQwOTAwNDBa -MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBALlcwQJuzd+xH8QFgfJSn5tRlvhkldSX98cE7NiA602NBbnAVyUrkRXq -Ni75lgt0kwjYfA9z674m8WSVbgpLPintPCla9CYky1TH0keIs8Rz6cGWHryWEHiu -EDuljQynu2b3sAFuHu9nfWurbJwZnFakBKpdQ9m4EyOZCHC/jHYY7HacKSXg1Cki -we2ca0BWDrcqy8kLy0dZ5oC6IZG8O8drAK8f3f44CRYw59D3sOKBrKXaabpvyEcb -N7Wk2HDBVwHpUJo1reVwtbM8dhqQayYSD8oXnGpP3RQNu/e2rzlXRyq/BfcDY1JI -7TbC4t/7/N4EcPSpGsTcSOC9A7FpzvECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglg -hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O -BBYEFCnwiEMMFZh7NhCr+qA8K0w4Q+AOMB8GA1UdIwQYMBaAFB0h1Evsaw2vfrmS -YuoCTmC4EE6ZMA0GCSqGSIb3DQEBCwUAA4IBAQAcFmHMaXRxyoNaeOowQ6iQWoZd -AUbvG7SHr7I6Pi2aqdqofsKWts7Ytm5WsS0M2nN+sW504houu0iCPeJJX8RQw2q4 -CCcNOs9IXk+2uMzlpocHpv+yYoUiD5DxgWh7eghQMLyMpf8FX3Gy4VazeuXznHOM -4gE4L417xkDzYOzqVTp0FTyAPUv6G2euhNCD6TMru9REcRhYul+K9kocjA5tt2KG -MH6y28LXbLyq4YJUxSUU9gY/xlnbbZS48KDqEcdYC9zjW9nQ0qS+XQuQuFIcwjJ5 -V4kAUYxDu6FoTpyQjgsrmBbZlKNxH7Nj4NDlcdJhp/zeSKHqWa5hSWjjKIxp ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDAjCCAeqgAwIBAgIJAOWmjTS+OnTDMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV -BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw -WjAXMRUwEwYDVQQDDAxpbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDSrAO1CoPvUllgLOzDm5nG0skDF7vh1DUgAIDVGz0ecD0JFbQx -EF79pju/6MbtpTW2FYvRp11t/G7rGtX923ybOHY/1MNFQrdIvPlO1VV7IGKjoMwP -DNeb0fIGjHoE9QxaDxR8NX8xQbItpsw+TUtRfc9SLkR+jaYJfVRoM21BOncZbSHE -YKiZlEbpecB/+EtwVpgvl+8mPD5U07Fi4fp/lza3WXInXQPyiTVllIEJCt4PKmlu -MocNaJOW38bysL7i0PzDpVZtOxLHOTaW68yF3FckIHNCaA7k1ABEEEegjFMmIao7 -B9w7A0jvr4jZVvNmui5Djjn+oJxwEVVgyf8LAgMBAAGjUDBOMB0GA1UdDgQWBBQd -IdRL7GsNr365kmLqAk5guBBOmTAfBgNVHSMEGDAWgBRk81s9d0ZbiZhh44KckwPb -oTc0XzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBZQTK0plfdB5PC -cC5icut4EmrByJa1RbU7ayuEE70e7hla6KVmVjVdCBGltI4jBYwfhKbRItHiAJ/8 -x+XZKBG8DLPFuDb7lAa1ObhAYF7YThUFPQYaBhfzKcWrdmWDBFpvNv6E0Mm364dZ -e7Yxmbe5S4agkYPoxEzgEYmcUk9jbjdR6eTbs8laG169ljrECXfEU9RiAcqz5iSX -NLSewqB47hn3B9qgKcQn+PsgO2j7M+rfklhNgeGJeWmy7j6clSOuCsIjWHU0RLQ4 -0W3SB/rpEAJ7fgQbYUPTIUNALSOWi/o1tDX2mXPRjBoxqAv7I+vYk1lZPmSzkyRh -FKvRDxsW ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDAzCCAeugAwIBAgIJAJ0MomS4Ck+8MA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV -BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw -WjAYMRYwFAYDVQQDDA1yb290YXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEAts1ijtBV92S2cOvpUMOSTp9c6A34nIGr0T5Nhz6XiqRVT+gv -dQgmkdKJQjbvR60y6jzltYFsI2MpGVXY8h/oAL81D/k7PDB2aREgyBfTPAhBHyGw -siR+2xYt5b/Zs99q5RdRqQNzNpLPJriIKvUsRyQWy1UiG2s7pRXQeA8qB0XtJdCj -kFIi+G2bDsaffspGeDOCqt7t+yqvRXfSES0c/l7DIHaiMbbp4//ZNML3RNgAjPz2 -hCezZ+wOYajOIyoSPK8IgICrhYFYxvgWxwbLDBEfC5B3jOQsySe10GoRAKZz1gBV -DmgReu81tYJmdgkc9zknnQtIFdA0ex+GvZlfWQIDAQABo1AwTjAdBgNVHQ4EFgQU -ZPNbPXdGW4mYYeOCnJMD26E3NF8wHwYDVR0jBBgwFoAUZPNbPXdGW4mYYeOCnJMD -26E3NF8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATzkZ97K777uZ -lQcduNX3ey4IbCiEzFA2zO5Blj+ilfIwNbZXNOgm/lqNvVGDYs6J1apJJe30vL3X -J+t2zsZWzzQzb9uIU37zYemt6m0fHrSrx/iy5lGNqt3HMfqEcOqSCOIK3PCTMz2/ -uyGe1iw33PVeWsm1JUybQ9IrU/huJjbgOHU4wab+8SJCM49ipArp68Fr6j4lcEaE -4rfRg1ZsvxiOyUB3qPn6wyL/JB8kOJ+QCBe498376eaem8AEFk0kQRh6hDaWtq/k -t6IIXQLjx+EBDVP/veK0UnVhKRP8YTOoV8ZiG1NcdlJmX/Uk7iAfevP7CkBfSN8W -r6AL284qtw== ------END CERTIFICATE----- \ No newline at end of file diff --git a/packages/core/example/controller.dart b/packages/core/example/controller.dart deleted file mode 100644 index 583bece..0000000 --- a/packages/core/example/controller.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:logging/logging.dart'; - -void main() async { - // Logging set up/boilerplate - Logger.root.onRecord.listen(print); - - // Create our server. - var app = - Application(logger: Logger('protevus'), reflector: MirrorsReflector()); - var http = PlatformHttp(app); - - await app.mountController(); - - // Simple fallback to throw a 404 on unknown paths. - app.fallback((req, res) { - throw PlatformHttpException.notFound( - message: 'Unknown path: "${req.uri!.path}"', - ); - }); - - app.errorHandler = (e, req, res) => e.toJson(); - - await http.startServer('127.0.0.1', 3000); - print('Listening at ${http.uri}'); - app.dumpTree(); -} - -class ArtistsController extends Controller { - List index() { - return ['Elvis', 'Stevie', 'Van Gogh']; - } - - String getById(int id, RequestContext req) { - return 'You fetched ID: $id from IP: ${req.ip}'; - } - - @Expose.post - Future form(RequestContext req) async { - // Deserialize the body into an artist. - var artist = await req.deserializeBody((m) { - return Artist(name: m!['name'] as String? ?? '(unknown name)'); - }); - - // Return it (it will be serialized to JSON). - return artist; - } -} - -class Artist { - final String? name; - - Artist({this.name}); - - Map toJson() { - return {'name': name}; - } -} diff --git a/packages/core/example/handle_error.dart b/packages/core/example/handle_error.dart deleted file mode 100644 index 05a9e36..0000000 --- a/packages/core/example/handle_error.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:logging/logging.dart'; - -void main() async { - var app = Application(reflector: MirrorsReflector()) - ..logger = (Logger('protevus') - ..onRecord.listen((rec) { - print(rec); - if (rec.error != null) print(rec.error); - if (rec.stackTrace != null) print(rec.stackTrace); - })) - ..encoders.addAll({'gzip': gzip.encoder}); - - app.fallback( - (req, res) => Future.error('Throwing just because I feel like!')); - - var http = PlatformHttp(app); - HttpServer? server = await http.startServer('127.0.0.1', 3000); - var url = 'http://${server.address.address}:${server.port}'; - print('Listening at $url'); -} diff --git a/packages/core/example/hostname.dart b/packages/core/example/hostname.dart deleted file mode 100644 index d485725..0000000 --- a/packages/core/example/hostname.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:async'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:logging/logging.dart'; - -Future apiConfigurer(Application app) async { - app.get('/', (req, res) => 'Hello, API!'); - app.fallback((req, res) { - return 'fallback on ${req.uri} (within the API)'; - }); -} - -Future frontendConfigurer(Application app) async { - app.fallback((req, res) => '(usually an index page would be shown here.)'); -} - -void main() async { - // Logging set up/boilerplate - hierarchicalLoggingEnabled = true; - //Logger.root.onRecord.listen(prettyLog); - - var app = Application(logger: Logger('protevus')); - var http = PlatformHttp(app); - var multiHost = HostnameRouter.configure({ - 'api.localhost:3000': apiConfigurer, - 'localhost:3000': frontendConfigurer, - }); - - app - ..fallback(multiHost.handleRequest) - ..fallback((req, res) { - res.write('Uncaught hostname: ${req.hostname}'); - }); - - app.errorHandler = (e, req, res) { - print(e.message); - print(e.stackTrace); - return e.toJson(); - }; - - await http.startServer('127.0.0.1', 3000); - print('Listening at ${http.uri}'); - print('See what happens when you visit http://localhost:3000 instead ' - 'of http://127.0.0.1:3000. Then, try ' - 'http://api.localhost:3000.'); -} diff --git a/packages/core/example/http2/body_parsing.dart b/packages/core/example/http2/body_parsing.dart deleted file mode 100644 index 1592b5e..0000000 --- a/packages/core/example/http2/body_parsing.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:io'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_core/http2.dart'; -import 'package:file/local.dart'; -import 'package:logging/logging.dart'; - -void main() async { - var app = Application(); - app.logger = Logger('protevus') - ..onRecord.listen((rec) { - print(rec); - if (rec.error != null) print(rec.error); - if (rec.stackTrace != null) print(rec.stackTrace); - }); - - var publicDir = Directory('example/public'); - var indexHtml = - const LocalFileSystem().file(publicDir.uri.resolve('body_parsing.html')); - - app.get('/', (req, res) => res.streamFile(indexHtml)); - - app.post('/', (req, res) => req.parseBody().then((_) => req.bodyAsMap)); - - var ctx = SecurityContext() - ..useCertificateChain('dev.pem') - ..usePrivateKey('dev.key', password: 'dartdart'); - - try { - ctx.setAlpnProtocols(['h2'], true); - } catch (e, st) { - app.logger.severe( - 'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.', - e, - st); - } - - var http1 = PlatformHttp(app); - var http2 = PlatformHttp2(app, ctx); - - // HTTP/1.x requests will fallback to `PlatformHttp` - http2.onHttp1.listen(http1.handleRequest); - - var server = await http2.startServer('127.0.0.1', 3000); - print('Listening at https://${server.address.address}:${server.port}'); -} diff --git a/packages/core/example/http2/common.dart b/packages/core/example/http2/common.dart deleted file mode 100644 index b937b43..0000000 --- a/packages/core/example/http2/common.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:logging/logging.dart'; - -void dumpError(LogRecord rec) { - print(rec); - if (rec.error != null) print(rec.error); - if (rec.stackTrace != null) print(rec.stackTrace); -} diff --git a/packages/core/example/http2/dev.key b/packages/core/example/http2/dev.key deleted file mode 100644 index 5d49ae7..0000000 --- a/packages/core/example/http2/dev.key +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIE5DAcBgoqhkiG9w0BDAEBMA4ECL7L6rj6uEHGAgIIAASCBMLbucyfqAkgCbhP -xNSHYllPMAv/dsIjtnsBwepCXPGkCBCuOAw/2FaCHjN9hBqL5V7fkrKeaemhm2YE -ycPtlHJYPDf3kEkyMjdZ9rIY6kePGfQizs2uJPcXj4YPyQ4HsfVXpOicKfQrouf5 -Mze9bGzeMN065q3iP4dYUMwHAyZYteXCsanQNHlqvsWli0W+H8St8fdsXefZhnv1 -qVatKWdNdWQ9t5MuljgNU2Vv56sHKEYXI0yLxk2QUMk8KlJfnmt8foYUsnPUXHmc -gIjLKwwVkpdololnEHSNu0cEOUPowjgJru+uMpn7vdNl7TPEQ9jbEgdNg4JwoYzU -0nao8WzjaSp7kzvZz0VFwKnk5AjstGvvuAWckADdq23QElbn/mF7AG1m/TBpYxzF -gTt37UdndS/AcvVznWVVrRP5iTSIawdIwvqI4s7rqsoE0GCcak+RhchgAz2gWKkS -oODUo0JL6pPVbJ3l4ebbaO6c99nDVc8dViPtc1EkStJEJ2O4kI4xgLSCr4Y9ahKn -oAaoSkX7Xxq3aQm+BzqSpLjdGL8atsqR/YVOIHYIl3gThvP0NfZGx1xHyvO5mCdZ -kHxSA7tKWxauZ3eQ2clbnzeRsl4El0WMHy/5K1ovene4v7sunmoXVtghBC8hK6eh -zMO9orex2PNQ/VQC7HCvtytunOVx1lkSBoNo7hR70igg6rW9H7UyoAoBOwMpT1xa -J6V62nqruTKOqFNfur7aHJGpHGtDb5/ickHeYCyPTvmGp67u4wChzKReeg02oECe -d1E5FKAcIa8s9TVOB6Z+HvTRNQZu2PsI6TJnjQRowvY9DAHiWTlJZBBY/pko3hxX -TsIeybpvRdEHpDWv86/iqtw1hv9CUxS/8ZTWUgBo+osShHW79FeDASr9FC4/Zn76 -ZDERTgV4YWlW/klVWcG2lFo7jix+OPXAB+ZQavLhlN1xdWBcIz1AUWjAM4hdPylW -HCX4PB9CQIPl2E7F+Y2p6nMcMWSJVBi5UIH7E9LfaBguXSzMmTk2Fw5p1aOQ6wfN -goVAMVwi8ppAVs741PfHdZ295xMmK/1LCxz5DeAdD/tsA/SYfT753GotioDuC7im -EyJ5JyvTr5I6RFFBuqt3NlUb3Hp16wP3B2x9DZiB6jxr0l341/NHgsyeBXkuIy9j -ON2mvpBPCJhS8kgWo3G0UyyKnx64tcgpGuSvZhGwPz843B6AbYyE6pMRfSWRMkMS -YZYa+VNKhR4ixdj07ocFZEWLVjCH7kxkE8JZXKt8jKYmkWd0lS1QVjgaKlO6lRa3 -q6SPJkhW6pvqobvcqVNXwi1XuzpZeEbuh0B7OTekFTTxx5g9XeDl56M8SVQ1KEhT -Q1t7H2Nba18WCB7cf+6PN0F0K0Jz1Kq7ZWaqEI/grX1m4RQuvNF5807sB/QKMO/Z -Gz3NXvHg5xTJRd/567lxPGkor0cE7qD1EZfmJ2HrBYXQ91bhgA7LToBuMZo6ZRXH -QfsanjbP4FPLMiGdQigLjj3A35L/f4sQOOVac/sRaFnm7pzcxsMvyVU/YtvGcjYE -xaOOVnamg661Wo0wksXoDjeSz/JIyyKO3Gwp1FSm2wGLjjy/Ehmqcqy8rvHuf07w -AUukhVtTNn4= ------END ENCRYPTED PRIVATE KEY----- \ No newline at end of file diff --git a/packages/core/example/http2/dev.pem b/packages/core/example/http2/dev.pem deleted file mode 100644 index 01756b2..0000000 --- a/packages/core/example/http2/dev.pem +++ /dev/null @@ -1,57 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDKTCCAhGgAwIBAgIJAOWmjTS+OnTEMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV -BAMMDGludGVybWVkaWF0ZTAeFw0xNTA1MTgwOTAwNDBaFw0yMzA4MDQwOTAwNDBa -MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBALlcwQJuzd+xH8QFgfJSn5tRlvhkldSX98cE7NiA602NBbnAVyUrkRXq -Ni75lgt0kwjYfA9z674m8WSVbgpLPintPCla9CYky1TH0keIs8Rz6cGWHryWEHiu -EDuljQynu2b3sAFuHu9nfWurbJwZnFakBKpdQ9m4EyOZCHC/jHYY7HacKSXg1Cki -we2ca0BWDrcqy8kLy0dZ5oC6IZG8O8drAK8f3f44CRYw59D3sOKBrKXaabpvyEcb -N7Wk2HDBVwHpUJo1reVwtbM8dhqQayYSD8oXnGpP3RQNu/e2rzlXRyq/BfcDY1JI -7TbC4t/7/N4EcPSpGsTcSOC9A7FpzvECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglg -hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O -BBYEFCnwiEMMFZh7NhCr+qA8K0w4Q+AOMB8GA1UdIwQYMBaAFB0h1Evsaw2vfrmS -YuoCTmC4EE6ZMA0GCSqGSIb3DQEBCwUAA4IBAQAcFmHMaXRxyoNaeOowQ6iQWoZd -AUbvG7SHr7I6Pi2aqdqofsKWts7Ytm5WsS0M2nN+sW504houu0iCPeJJX8RQw2q4 -CCcNOs9IXk+2uMzlpocHpv+yYoUiD5DxgWh7eghQMLyMpf8FX3Gy4VazeuXznHOM -4gE4L417xkDzYOzqVTp0FTyAPUv6G2euhNCD6TMru9REcRhYul+K9kocjA5tt2KG -MH6y28LXbLyq4YJUxSUU9gY/xlnbbZS48KDqEcdYC9zjW9nQ0qS+XQuQuFIcwjJ5 -V4kAUYxDu6FoTpyQjgsrmBbZlKNxH7Nj4NDlcdJhp/zeSKHqWa5hSWjjKIxp ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDAjCCAeqgAwIBAgIJAOWmjTS+OnTDMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV -BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw -WjAXMRUwEwYDVQQDDAxpbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDSrAO1CoPvUllgLOzDm5nG0skDF7vh1DUgAIDVGz0ecD0JFbQx -EF79pju/6MbtpTW2FYvRp11t/G7rGtX923ybOHY/1MNFQrdIvPlO1VV7IGKjoMwP -DNeb0fIGjHoE9QxaDxR8NX8xQbItpsw+TUtRfc9SLkR+jaYJfVRoM21BOncZbSHE -YKiZlEbpecB/+EtwVpgvl+8mPD5U07Fi4fp/lza3WXInXQPyiTVllIEJCt4PKmlu -MocNaJOW38bysL7i0PzDpVZtOxLHOTaW68yF3FckIHNCaA7k1ABEEEegjFMmIao7 -B9w7A0jvr4jZVvNmui5Djjn+oJxwEVVgyf8LAgMBAAGjUDBOMB0GA1UdDgQWBBQd -IdRL7GsNr365kmLqAk5guBBOmTAfBgNVHSMEGDAWgBRk81s9d0ZbiZhh44KckwPb -oTc0XzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBZQTK0plfdB5PC -cC5icut4EmrByJa1RbU7ayuEE70e7hla6KVmVjVdCBGltI4jBYwfhKbRItHiAJ/8 -x+XZKBG8DLPFuDb7lAa1ObhAYF7YThUFPQYaBhfzKcWrdmWDBFpvNv6E0Mm364dZ -e7Yxmbe5S4agkYPoxEzgEYmcUk9jbjdR6eTbs8laG169ljrECXfEU9RiAcqz5iSX -NLSewqB47hn3B9qgKcQn+PsgO2j7M+rfklhNgeGJeWmy7j6clSOuCsIjWHU0RLQ4 -0W3SB/rpEAJ7fgQbYUPTIUNALSOWi/o1tDX2mXPRjBoxqAv7I+vYk1lZPmSzkyRh -FKvRDxsW ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDAzCCAeugAwIBAgIJAJ0MomS4Ck+8MA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV -BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw -WjAYMRYwFAYDVQQDDA1yb290YXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEAts1ijtBV92S2cOvpUMOSTp9c6A34nIGr0T5Nhz6XiqRVT+gv -dQgmkdKJQjbvR60y6jzltYFsI2MpGVXY8h/oAL81D/k7PDB2aREgyBfTPAhBHyGw -siR+2xYt5b/Zs99q5RdRqQNzNpLPJriIKvUsRyQWy1UiG2s7pRXQeA8qB0XtJdCj -kFIi+G2bDsaffspGeDOCqt7t+yqvRXfSES0c/l7DIHaiMbbp4//ZNML3RNgAjPz2 -hCezZ+wOYajOIyoSPK8IgICrhYFYxvgWxwbLDBEfC5B3jOQsySe10GoRAKZz1gBV -DmgReu81tYJmdgkc9zknnQtIFdA0ex+GvZlfWQIDAQABo1AwTjAdBgNVHQ4EFgQU -ZPNbPXdGW4mYYeOCnJMD26E3NF8wHwYDVR0jBBgwFoAUZPNbPXdGW4mYYeOCnJMD -26E3NF8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATzkZ97K777uZ -lQcduNX3ey4IbCiEzFA2zO5Blj+ilfIwNbZXNOgm/lqNvVGDYs6J1apJJe30vL3X -J+t2zsZWzzQzb9uIU37zYemt6m0fHrSrx/iy5lGNqt3HMfqEcOqSCOIK3PCTMz2/ -uyGe1iw33PVeWsm1JUybQ9IrU/huJjbgOHU4wab+8SJCM49ipArp68Fr6j4lcEaE -4rfRg1ZsvxiOyUB3qPn6wyL/JB8kOJ+QCBe498376eaem8AEFk0kQRh6hDaWtq/k -t6IIXQLjx+EBDVP/veK0UnVhKRP8YTOoV8ZiG1NcdlJmX/Uk7iAfevP7CkBfSN8W -r6AL284qtw== ------END CERTIFICATE----- \ No newline at end of file diff --git a/packages/core/example/http2/main.dart b/packages/core/example/http2/main.dart deleted file mode 100644 index 395ac38..0000000 --- a/packages/core/example/http2/main.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:io'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_core/http2.dart'; -import 'package:logging/logging.dart'; -import 'common.dart'; - -void main() async { - var app = Application() - ..encoders.addAll({ - 'gzip': gzip.encoder, - 'deflate': zlib.encoder, - }); - app.logger = Logger('protevus')..onRecord.listen(dumpError); - - app.get('/', (req, res) => 'Hello HTTP/2!!!'); - - app.fallback((req, res) => throw PlatformHttpException.notFound( - message: 'No file exists at ${req.uri}')); - - var ctx = SecurityContext() - ..useCertificateChain('dev.pem') - ..usePrivateKey('dev.key', password: 'dartdart'); - - try { - ctx.setAlpnProtocols(['h2'], true); - } catch (e, st) { - app.logger.severe( - 'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.', - e, - st, - ); - } - - var http1 = PlatformHttp(app); - var http2 = PlatformHttp2(app, ctx); - - // HTTP/1.x requests will fallback to `PlatformHttp` - http2.onHttp1.listen(http1.handleRequest); - - await http2.startServer('127.0.0.1', 3000); - print('Listening at ${http2.uri}'); -} diff --git a/packages/core/example/http2/public/app.js b/packages/core/example/http2/public/app.js deleted file mode 100644 index 5b08ee9..0000000 --- a/packages/core/example/http2/public/app.js +++ /dev/null @@ -1,27 +0,0 @@ -window.onload = function() { - var $app = document.getElementById('app'); - var $loading = document.getElementById('loading'); - $app.removeChild($loading); - var $button = document.createElement('button'); - var $h1 = document.createElement('h1'); - $app.appendChild($h1); - $app.appendChild($button); - - $h1.textContent = '~Protevus HTTP/2 server push~'; - - $button.textContent = 'Change color'; - $button.onclick = function() { - var color = Math.floor(Math.random() * 0xffffff); - $h1.style.color = '#' + color.toString(16); - }; - - $button.onclick(); - - window.setInterval($button.onclick, 2000); - - var rotation = 0; - window.setInterval(function() { - rotation += .6; - $button.style.transform = 'rotate(' + rotation + 'deg)'; - }, 10); -}; \ No newline at end of file diff --git a/packages/core/example/http2/public/body_parsing.html b/packages/core/example/http2/public/body_parsing.html deleted file mode 100644 index 8ad8a1a..0000000 --- a/packages/core/example/http2/public/body_parsing.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Protevus HTTP/2 - - - -
- - - - - -
- - \ No newline at end of file diff --git a/packages/core/example/http2/public/index.html b/packages/core/example/http2/public/index.html deleted file mode 100644 index 67a1e80..0000000 --- a/packages/core/example/http2/public/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Protevus HTTP/2 - - - -
Loading...
- - - \ No newline at end of file diff --git a/packages/core/example/http2/public/style.css b/packages/core/example/http2/public/style.css deleted file mode 100644 index e434813..0000000 --- a/packages/core/example/http2/public/style.css +++ /dev/null @@ -1,20 +0,0 @@ -button { - margin-top: 2em; -} - -html, body { - background-color: #000; -} - -#app { - text-align: center; -} - -#app h1 { - font-style: italic; - text-decoration: underline; -} - -#loading { - color: red; -} \ No newline at end of file diff --git a/packages/core/example/http2/server_push.dart b/packages/core/example/http2/server_push.dart deleted file mode 100644 index f3d16c6..0000000 --- a/packages/core/example/http2/server_push.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:io'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_core/http2.dart'; -import 'package:file/local.dart'; -import 'package:logging/logging.dart'; - -void main() async { - var app = Application(); - app.logger = Logger('protevus') - ..onRecord.listen((rec) { - print(rec); - if (rec.error != null) print(rec.error); - if (rec.stackTrace != null) print(rec.stackTrace); - }); - - var publicDir = Directory('example/http2/public'); - var indexHtml = - const LocalFileSystem().file(publicDir.uri.resolve('index.html')); - var styleCss = - const LocalFileSystem().file(publicDir.uri.resolve('style.css')); - var appJs = const LocalFileSystem().file(publicDir.uri.resolve('app.js')); - - // Send files when requested - app - ..get('/style.css', (req, res) => res.streamFile(styleCss)) - ..get('/app.js', (req, res) => res.streamFile(appJs)); - - app.get('/', (req, res) async { - // Regardless of whether we pushed other resources, let's still send /index.html. - await res.streamFile(indexHtml); - - // If the client is HTTP/2 and supports server push, let's - // send down /style.css and /app.js as well, to improve initial load time. - if (res is Http2ResponseContext && res.canPush) { - await res.push('/style.css').streamFile(styleCss); - await res.push('/app.js').streamFile(appJs); - } - }); - - var ctx = SecurityContext() - ..useCertificateChain('dev.pem') - ..usePrivateKey('dev.key', password: 'dartdart'); - - try { - ctx.setAlpnProtocols(['h2'], true); - } catch (e, st) { - app.logger.severe( - 'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.', - e, - st); - } - - var http1 = PlatformHttp(app); - var http2 = PlatformHttp2(app, ctx); - - // HTTP/1.x requests will fallback to `PlatformHttp` - http2.onHttp1.listen(http1.handleRequest); - - var server = await http2.startServer('127.0.0.1', 3000); - print('Listening at https://${server.address.address}:${server.port}'); -} diff --git a/packages/core/example/json.dart b/packages/core/example/json.dart deleted file mode 100644 index 679c057..0000000 --- a/packages/core/example/json.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:isolate'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; - -void main() async { - var x = 0; - var c = Completer(); - var exit = ReceivePort(); - var isolates = []; - - exit.listen((_) { - if (++x >= 50) { - c.complete(); - } - }); - - for (var i = 1; i < Platform.numberOfProcessors; i++) { - var isolate = await Isolate.spawn(serverMain, null); - isolates.add(isolate); - print('Spawned isolate #${i + 1}...'); - - isolate.addOnExitListener(exit.sendPort); - } - - serverMain(null); - - print('Protevus listening at http://localhost:3000'); - await c.future; -} - -void serverMain(_) async { - var app = Application(); - var http = - PlatformHttp.custom(app, startShared, useZone: false); // Run a cluster - - app.get('/', (req, res) { - return res.serialize({ - 'foo': 'bar', - 'one': [2, 'three'], - 'bar': {'baz': 'quux'} - }); - }); - - app.errorHandler = (e, req, res) { - print(e.message); - print(e.stackTrace); - }; - - var server = await http.startServer('127.0.0.1', 3000); - print('Listening at http://${server.address.address}:${server.port}'); -} diff --git a/packages/core/example/main.dart b/packages/core/example/main.dart deleted file mode 100644 index e8cf5a3..0000000 --- a/packages/core/example/main.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:logging/logging.dart'; - -void main() async { - // Logging set up/boilerplate - //Logger.root.onRecord.listen(prettyLog); - - // Create our server. - var app = Application( - logger: Logger('protevus'), - reflector: MirrorsReflector(), - ); - - // Index route. Returns JSON. - app.get('/', (req, res) => 'Welcome to Protevus!'); - - // Accepts a URL like /greet/foo or /greet/bob. - app.get( - '/greet/:name', - (req, res) { - var name = req.params['name']; - res - ..write('Hello, $name!') - ..close(); - }, - ); - - // Pattern matching - only call this handler if the query value of `name` equals 'emoji'. - app.get( - '/greet', - ioc((@Query('name', match: 'emoji') String name) => '😇🔥🔥🔥'), - ); - - // Handle any other query value of `name`. - app.get( - '/greet', - ioc((@Query('name') String name) => 'Hello, $name!'), - ); - - // Simple fallback to throw a 404 on unknown paths. - app.fallback((req, res) { - throw PlatformHttpException.notFound( - message: 'Unknown path: "${req.uri!.path}"', - ); - }); - - var http = PlatformHttp(app); - var server = await http.startServer('127.0.0.1', 3000); - var url = 'http://${server.address.address}:${server.port}'; - print('Listening at $url'); - print('Visit these pages to see Protevus in action:'); - print('* $url/greet/bob'); - print('* $url/greet/?name=emoji'); - print('* $url/greet/?name=jack'); - print('* $url/nonexistent_page'); -} diff --git a/packages/core/example/map_service.dart b/packages/core/example/map_service.dart deleted file mode 100644 index f9aa9b1..0000000 --- a/packages/core/example/map_service.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:logging/logging.dart'; - -void main() async { - // Logging set up/boilerplate - Logger.root.onRecord.listen(print); - - // Create our server. - var app = Application( - logger: Logger('protevus'), - reflector: MirrorsReflector(), - ); - - // Create a RESTful service that manages an in-memory collection. - app.use('/api/todos', MapService()); - - var http = PlatformHttp(app); - await http.startServer('127.0.0.1', 0); - print('Listening at ${http.uri}'); -} diff --git a/packages/core/example/status.dart b/packages/core/example/status.dart deleted file mode 100644 index 3f8ac38..0000000 --- a/packages/core/example/status.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; - -void main() async { - var app = Application(); - var http = PlatformHttp(app); - - app.fallback((req, res) { - res.statusCode = 304; - }); - - await http.startServer('127.0.0.1', 3000); - print('Listening at ${http.uri}'); -} diff --git a/packages/core/example/view.dart b/packages/core/example/view.dart deleted file mode 100644 index 4893053..0000000 --- a/packages/core/example/view.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; - -void main() async { - var app = Application(reflector: MirrorsReflector()); - - app.viewGenerator = (name, [data]) async => - 'View generator invoked with name $name and data: $data'; - - // Index route. Returns JSON. - app.get('/', (req, res) => res.render('index', {'foo': 'bar'})); - - var http = PlatformHttp(app); - var server = await http.startServer('127.0.0.1', 3000); - var url = 'http://${server.address.address}:${server.port}'; - print('Listening at $url'); -} diff --git a/packages/core/example/views/index.jl b/packages/core/example/views/index.jl deleted file mode 100644 index 53ac639..0000000 --- a/packages/core/example/views/index.jl +++ /dev/null @@ -1,9 +0,0 @@ - - - - Title - - -

Hello!

- - \ No newline at end of file diff --git a/packages/core/lib/core.dart b/packages/core/lib/core.dart deleted file mode 100644 index 83bb2f8..0000000 --- a/packages/core/lib/core.dart +++ /dev/null @@ -1,7 +0,0 @@ -/// An easily-extensible web server framework in Dart. -library platform_core; - -export 'package:platform_support/exceptions.dart'; -export 'package:platform_model/model.dart'; -export 'package:platform_route/route.dart'; -export 'src/core/core.dart'; diff --git a/packages/core/lib/http.dart b/packages/core/lib/http.dart deleted file mode 100644 index 5f00eff..0000000 --- a/packages/core/lib/http.dart +++ /dev/null @@ -1 +0,0 @@ -export 'src/http/http.dart'; diff --git a/packages/core/lib/http2.dart b/packages/core/lib/http2.dart deleted file mode 100644 index 7fd2aaf..0000000 --- a/packages/core/lib/http2.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'src/http2/protevus_http2.dart'; -export 'src/http2/http2_request_context.dart'; -export 'src/http2/http2_response_context.dart'; diff --git a/packages/core/lib/src/core/anonymous_service.dart b/packages/core/lib/src/core/anonymous_service.dart deleted file mode 100644 index e43e4f2..0000000 --- a/packages/core/lib/src/core/anonymous_service.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'dart:async'; -import 'service.dart'; - -/// An easy helper class to create one-off services without having to create an entire class. -/// -/// Well-suited for testing. -class AnonymousService extends Service { - FutureOr> Function([Map?])? _index; - FutureOr Function(Id, [Map?])? _read, _remove; - FutureOr Function(Data, [Map?])? _create; - FutureOr Function(Id, Data, [Map?])? _modify, _update; - - AnonymousService( - {FutureOr> Function([Map? params])? index, - FutureOr Function(Id id, [Map? params])? read, - FutureOr Function(Data data, [Map? params])? - create, - FutureOr Function(Id id, Data data, [Map? params])? - modify, - FutureOr Function(Id id, Data data, [Map? params])? - update, - FutureOr Function(Id id, [Map? params])? remove, - super.readData}) { - _index = index; - _read = read; - _create = create; - _modify = modify; - _update = update; - _remove = remove; - } - - @override - Future> index([Map? params]) => - Future.sync(() => _index != null ? _index!(params) : super.index(params)); - - @override - Future read(Id id, [Map? params]) => Future.sync( - () => _read != null ? _read!(id, params) : super.read(id, params)); - - @override - Future create(Data data, [Map? params]) => - Future.sync(() => _create != null - ? _create!(data, params) - : super.create(data, params)); - - @override - Future modify(Id id, Data data, [Map? params]) => - Future.sync(() => _modify != null - ? _modify!(id, data, params) - : super.modify(id, data, params)); - - @override - Future update(Id id, Data data, [Map? params]) => - Future.sync(() => _update != null - ? _update!(id, data, params) - : super.update(id, data, params)); - - @override - Future remove(Id id, [Map? params]) => Future.sync( - () => _remove != null ? _remove!(id, params) : super.remove(id, params)); -} diff --git a/packages/core/lib/src/core/controller.dart b/packages/core/lib/src/core/controller.dart deleted file mode 100644 index 3625164..0000000 --- a/packages/core/lib/src/core/controller.dart +++ /dev/null @@ -1,242 +0,0 @@ -library platform_core.http.controller; - -import 'dart:async'; -import 'package:platform_container/container.dart'; -import 'package:platform_route/route.dart'; -import 'package:meta/meta.dart'; -import 'package:recase/recase.dart'; -import '../core/core.dart'; - -/// Supports grouping routes with shared functionality. -class Controller { - Application? _app; - - /// The [Application] application powering this controller. - Application get app { - if (_app == null) { - throw ArgumentError("Protevus is not instantiated."); - } - - return _app!; - } - - /// If `true` (default), this class will inject itself as a singleton into the [app]'s container when bootstrapped. - final bool injectSingleton; - - /// Middleware to run before all handlers in this class. - List middleware = []; - - /// A mapping of route paths to routes, produced from the [Expose] annotations on this class. - Map routeMappings = {}; - - SymlinkRoute? _mountPoint; - - /// The route at which this controller is mounted on the server. - SymlinkRoute? get mountPoint => _mountPoint; - - Controller({this.injectSingleton = true}); - - /// Applies routes, DI, and other configuration to an [app]. - @mustCallSuper - Future configureServer(Application app) async { - _app = app; - - if (injectSingleton != false) { - if (!app.container.has(runtimeType)) { - _app!.container.registerSingleton(this, as: runtimeType); - } - } - - var name = await applyRoutes(app, app.container.reflector); - app.controllers[name] = this; - //return null; - } - - /// Applies the routes from this [Controller] to some [router]. - Future applyRoutes( - Router router, Reflector reflector) async { - // Load global expose decl - var classMirror = reflector.reflectClass(runtimeType)!; - var exposeDecl = findExpose(reflector); - - if (exposeDecl == null) { - throw Exception('All controllers must carry an @Expose() declaration.'); - } - - var routable = Routable(); - _mountPoint = router.mount(exposeDecl.path, routable); - //_mountPoint = m; - var typeMirror = reflector.reflectType(runtimeType); - - // Pre-reflect methods - var instanceMirror = reflector.reflectInstance(this); - final handlers = [...exposeDecl.middleware, ...middleware]; - final routeBuilder = - _routeBuilder(reflector, instanceMirror, routable, handlers); - await configureRoutes(routable); - classMirror.declarations.forEach(routeBuilder); - - // Return the name. - var result = - exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror!.name; - - return Future.value(result); - } - - void Function(ReflectedDeclaration) _routeBuilder( - Reflector reflector, - ReflectedInstance? instanceMirror, - Routable routable, - Iterable handlers) { - return (ReflectedDeclaration decl) { - var methodName = decl.name; - - // Ignore built-in methods. - if (methodName != 'toString' && - methodName != 'noSuchMethod' && - methodName != 'call' && - methodName != 'equals' && - methodName != '==') { - var exposeDecl = decl.function!.annotations - .map((m) => m.reflectee) - .firstWhere((r) => r is Expose, orElse: () => null) as Expose?; - - if (exposeDecl == null) { - // If this has a @noExpose, return null. - if (decl.function!.annotations.any((m) => m.reflectee is NoExpose)) { - return; - } else { - // Otherwise, create an @Expose. - exposeDecl = Expose(''); - } - } - - var reflectedMethod = - instanceMirror!.getField(methodName).reflectee as Function?; - var middleware = [ - ...handlers, - ...exposeDecl.middleware - ]; - var name = - exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : methodName; - - // Check if normal - var method = decl.function!; - if (method.parameters.length == 2 && - method.parameters[0].type.reflectedType == RequestContext && - method.parameters[1].type.reflectedType == ResponseContext) { - // Create a regular route - routeMappings[name ?? ''] = routable - .addRoute(exposeDecl.method, exposeDecl.path, - (RequestContext req, ResponseContext res) { - var result = reflectedMethod!(req, res); - return result is RequestHandler ? result(req, res) : result; - }, middleware: middleware); - return; - } - - var injection = preInject(reflectedMethod!, reflector); - - if (exposeDecl.allowNull.isNotEmpty == true) { - injection.optional.addAll(exposeDecl.allowNull); - } - - // If there is no path, reverse-engineer one. - var path = exposeDecl.path; - var httpMethod = exposeDecl.method; - if (path == '') { - // Try to build a route path by finding all potential - // path segments, and then joining them. - var parts = []; - - // If the name starts with get/post/patch, etc., then that - // should be the path. - var methodMatch = _methods.firstMatch(method.name); - if (methodMatch != null) { - var rest = method.name.replaceAll(_methods, ''); - var restPath = ReCase(rest.isEmpty ? 'index' : rest) - .snakeCase - .replaceAll(_rgxMultipleUnderscores, '_'); - httpMethod = methodMatch[1]!.toUpperCase(); - - if (['index', 'by_id'].contains(restPath)) { - parts.add('/'); - } else { - parts.add(restPath); - } - } - // If the name does NOT start with get/post/patch, etc. then - // snake_case-ify the name, and add it to the list of segments. - // If the name is index, though, add "/". - else { - if (method.name == 'index') { - parts.add('/'); - } else { - parts.add(ReCase(method.name) - .snakeCase - .replaceAll(_rgxMultipleUnderscores, '_')); - } - } - - // Try to infer String, int, or double. We called - // preInject() earlier, so we can figure out the types - // of required parameters, and add those to the path. - for (var p in injection.required) { - if (p is List && p.length == 2 && p[0] is String && p[1] is Type) { - var name = p[0] as String; - var type = p[1] as Type; - if (type == String) { - parts.add(':$name'); - } else if (type == int) { - parts.add('int:$name'); - } else if (type == double) { - parts.add('double:$name'); - } - } - } - - path = parts.join('/'); - if (!path.startsWith('/')) path = '/$path'; - } - - routeMappings[name ?? ''] = routable.addRoute( - httpMethod, path, handleContained(reflectedMethod, injection), - middleware: middleware); - } - }; - } - - /// Used to add additional routes or middlewares to the router from within - /// a [Controller]. - /// - /// ```dart - /// @override - /// FutureOr configureRoutes(Routable routable) { - /// routable.all('*', myMiddleware); - /// } - /// ``` - FutureOr configureRoutes(Routable routable) {} - - static final RegExp _methods = RegExp(r'^(get|post|patch|delete)'); - static final RegExp _rgxMultipleUnderscores = RegExp(r'__+'); - - /// Finds the [Expose] declaration for this class. - /// - /// If [concreteOnly] is `false`, then if there is no actual - /// [Expose], one will be automatically created. - Expose? findExpose(Reflector reflector, {bool concreteOnly = false}) { - var existing = reflector - .reflectClass(runtimeType)! - .annotations - .map((m) => m.reflectee) - .firstWhere((r) => r is Expose, orElse: () => null) as Expose?; - return existing ?? - (concreteOnly - ? null - : Expose(ReCase(runtimeType.toString()) - .snakeCase - .replaceAll('_controller', '') - .replaceAll('_ctrl', '') - .replaceAll(_rgxMultipleUnderscores, '_'))); - } -} diff --git a/packages/core/lib/src/core/core.dart b/packages/core/lib/src/core/core.dart deleted file mode 100644 index a7a0da4..0000000 --- a/packages/core/lib/src/core/core.dart +++ /dev/null @@ -1,14 +0,0 @@ -export 'anonymous_service.dart'; -export 'controller.dart'; -export 'driver.dart'; -export 'env.dart'; -export 'hooked_service.dart'; -export 'hostname_parser.dart'; -export 'hostname_router.dart'; -export 'map_service.dart'; -export 'metadata.dart'; -export 'request_context.dart'; -export 'response_context.dart'; -export 'routable.dart'; -export 'server.dart'; -export 'service.dart'; diff --git a/packages/core/lib/src/core/driver.dart b/packages/core/lib/src/core/driver.dart deleted file mode 100644 index 4c262de..0000000 --- a/packages/core/lib/src/core/driver.dart +++ /dev/null @@ -1,401 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' show Cookie; -import 'package:platform_support/exceptions.dart'; -import 'package:platform_route/route.dart'; -import 'package:belatuk_combinator/belatuk_combinator.dart'; -import 'package:stack_trace/stack_trace.dart'; -import 'package:tuple/tuple.dart'; -import 'core.dart'; - -/// Base driver class for Protevus implementations. -/// -/// Powers both PlatformHttp and PlatformHttp2. -abstract class Driver< - Request, - Response, - Server extends Stream, - RequestContextType extends RequestContext, - ResponseContextType extends ResponseContext> { - final Application app; - final bool useZone; - bool _closed = false; - - StreamSubscription? _sub; - //final log = Logger('Driver'); - - /// The function used to bind this instance to a server.. - final Future Function(dynamic, int) serverGenerator; - - Driver(this.app, this.serverGenerator, {this.useZone = true}); - - /// The path at which this server is listening for requests. - Uri get uri; - - /// The native server running this instance. - Server? server; - - Future generateServer(address, int port) => - serverGenerator(address, port); - - /// Starts, and returns the server. - Future startServer([address, int port = 0]) { - var host = address ?? '127.0.0.1'; - return generateServer(host, port).then((server) { - this.server = server; - - return Future.wait(app.startupHooks.map(app.configure)).then((_) { - app.optimizeForProduction(); - _sub = this.server?.listen((request) { - var stream = createResponseStreamFromRawRequest(request); - stream.listen((response) { - // TODO: To be revisited - handleRawRequest(request, response); - }); - }); - return Future.value(this.server!); - }); - }).catchError((error) { - app.logger.severe('Failed to create server', error); - throw ArgumentError('[Driver]Failed to create server'); - }); - } - - /// Shuts down the underlying server. - Future close() { - if (_closed) { - //return Future.value(_server); - return Future.value(); - } - _closed = true; - - _sub?.cancel(); - - return app.close().then((_) => - Future.wait(app.shutdownHooks.map(app.configure)) - .then((_) => Future.value())); - /* - return app.close().then((_) => - Future.wait(app.shutdownHooks.map(app.configure)) - .then((_) => Future.value(_server))); - */ - } - - Future createRequestContext( - Request request, Response response); - - Future createResponseContext( - Request request, Response response, - [RequestContextType? correspondingRequest]); - - void setHeader(Response response, String key, String value); - - void setContentLength(Response response, int length); - - void setChunkedEncoding(Response response, bool value); - - void setStatusCode(Response response, int value); - - void addCookies(Response response, Iterable cookies); - - void writeStringToResponse(Response response, String value); - - void writeToResponse(Response response, List data); - - Future closeResponse(Response response); - - Stream createResponseStreamFromRawRequest(Request request); - - /// Handles a single request. - Future handleRawRequest(Request request, Response response) { - app.logger.info('[Server] Called handleRawRequest'); - - return createRequestContext(request, response).then((req) { - return createResponseContext(request, response, req).then((res) { - Future handle() { - var path = req.path; - if (path == '/') path = ''; - - Tuple4, ParseResult, - MiddlewarePipeline> resolveTuple() { - var r = app.optimizedRouter; - var resolved = - r.resolveAbsolute(path, method: req.method, strip: false); - var pipeline = MiddlewarePipeline(resolved); - return Tuple4( - pipeline.handlers, - resolved.fold>( - {}, (out, r) => out..addAll(r.allParams)), - //(resolved.isEmpty ? null : resolved.first.parseResult), - resolved.first.parseResult, - pipeline, - ); - } - - var cacheKey = req.method + path; - var tuple = app.environment.isProduction - ? app.handlerCache.putIfAbsent(cacheKey, resolveTuple) - : resolveTuple(); - var line = tuple.item4 as MiddlewarePipeline; - var it = MiddlewarePipelineIterator(line); - - req.params.addAll(tuple.item2); - - req.container - ?..registerSingleton(req) - ..registerSingleton(res) - ..registerSingleton(tuple.item4) - ..registerSingleton>(line) - ..registerSingleton(it) - ..registerSingleton>(it) - ..registerSingleton?>(tuple.item3) - ..registerSingleton(tuple.item3); - - if (!app.environment.isProduction) { - req.container?.registerSingleton(Stopwatch()..start()); - } - - return runPipeline(it, req, res, app) - .then((_) => sendResponse(request, response, req, res)); - } - - if (useZone == false) { - Future f; - - try { - f = handle(); - } catch (e, st) { - f = Future.error(e, st); - } - - return f.catchError((e, StackTrace st) { - if (e is FormatException) { - throw PlatformHttpException.badRequest(message: e.message) - ..stackTrace = st; - } - throw PlatformHttpException( - stackTrace: st, - statusCode: (e is PlatformHttpException) ? e.statusCode : 500, - message: e?.toString() ?? '500 Internal Server Error'); - }, test: (e) => e is PlatformHttpException).catchError( - (ee, StackTrace st) { - //print(">>>> Framework error: $ee"); - //var t = (st).runtimeType; - //print(">>>> StackTrace: $t"); - PlatformHttpException e; - if (ee is PlatformHttpException) { - e = ee; - } else { - e = PlatformHttpException( - stackTrace: st, - statusCode: 500, - message: ee?.toString() ?? '500 Internal Server Error'); - } - - var error = e.error ?? e; - var trace = Trace.from(StackTrace.current).terse; - app.logger.severe(e.message, error, trace); - - return handleHttpException(e, st, req, res, request, response); - }); - } else { - var zoneSpec = ZoneSpecification( - print: (self, parent, zone, line) { - app.logger.info(line); - }, - handleUncaughtError: (self, parent, zone, error, stackTrace) { - var trace = Trace.from(stackTrace).terse; - - // TODO: To be revisited - Future(() { - PlatformHttpException e; - - if (error is FormatException) { - e = PlatformHttpException.badRequest(message: error.message); - } else if (error is PlatformHttpException) { - e = error; - } else { - e = PlatformHttpException( - stackTrace: stackTrace, message: error.toString()); - } - - app.logger.severe(e.message, error, trace); - - return handleHttpException( - e, trace, req, res, request, response); - }).catchError((e, StackTrace st) { - var trace = Trace.from(st).terse; - closeResponse(response); - // Ideally, we won't be in a position where an absolutely fatal error occurs, - // but if so, we'll need to log it. - app.logger.severe( - 'Fatal error occurred when processing $uri.', e, trace); - }); - }, - ); - - var zone = Zone.current.fork(specification: zoneSpec); - req.container?.registerSingleton(zone); - req.container?.registerSingleton(zoneSpec); - - // If a synchronous error is thrown, it's not caught by `zone.run`, - // so use a try/catch, and recover when need be. - - try { - return zone.run(handle); - } catch (e, st) { - zone.handleUncaughtError(e, st); - return Future.value(); - } - } - }); - }); - } - - /// Handles an [PlatformHttpException]. - Future handleHttpException( - PlatformHttpException e, - StackTrace st, - RequestContext? req, - ResponseContext? res, - Request request, - Response response, - {bool ignoreFinalizers = false}) { - if (req == null || res == null) { - try { - app.logger.severe('500 Internal Server Error', e, st); - setStatusCode(response, 500); - writeStringToResponse(response, '500 Internal Server Error'); - closeResponse(response); - } catch (e) { - app.logger.severe('500 Internal Server Error', e); - } - return Future.value(); - } - - Future handleError; - - if (!res.isOpen) { - handleError = Future.value(); - } else { - res.statusCode = e.statusCode; - handleError = - Future.sync(() => app.errorHandler(e, req, res)).then((result) { - return app.executeHandler(result, req, res).then((_) => res.close()); - }); - } - - return handleError.then((_) => sendResponse(request, response, req, res, - ignoreFinalizers: ignoreFinalizers == true)); - } - - /// Sends a response. - Future sendResponse(Request request, Response response, RequestContext req, - ResponseContext res, - {bool ignoreFinalizers = false}) { - //app.logger.fine("Calling SendResponse"); - Future cleanup(_) { - if (!app.environment.isProduction && req.container!.has()) { - var sw = req.container!.make(); - app.logger.fine( - "${res.statusCode} ${req.method} ${req.uri} (${sw.elapsedMilliseconds} ms)"); - } - return req.close(); - } - - // TODO: Debugging header - /* - for (var key in res.headers.keys) { - app.logger.fine("Response header key: $key"); - } - */ - - if (!res.isBuffered) { - //if (res.isOpen) { - return res.close().then(cleanup); - //} - //return Future.value(); - } - - //app.logger.fine("Calling finalizers"); - - var finalizers = ignoreFinalizers == true - ? Future.value() - : Future.forEach(app.responseFinalizers, (dynamic f) => f(req, res)); - - return finalizers.then((_) { - //if (res.isOpen) res.close(); - - for (var key in res.headers.keys) { - app.logger.fine("Response header key: $key"); - setHeader(response, key, res.headers[key] ?? ''); - } - - setContentLength(response, res.buffer?.length ?? 0); - setChunkedEncoding(response, res.chunked ?? true); - - var outputBuffer = res.buffer?.toBytes() ?? []; - - if (res.encoders.isNotEmpty) { - var allowedEncodings = req.headers - ?.value('accept-encoding') - ?.split(',') - .map((s) => s.trim()) - .where((s) => s.isNotEmpty) - .map((str) { - // Ignore quality specifications in accept-encoding - // ex. gzip;q=0.8 - if (!str.contains(';')) return str; - return str.split(';')[0]; - }); - - if (allowedEncodings != null) { - for (var encodingName in allowedEncodings) { - var key = encodingName; - - Converter, List>? encoder; - if (res.encoders.containsKey(encodingName)) { - encoder = res.encoders[encodingName]; - } else if (encodingName == '*') { - encoder = res.encoders[key = res.encoders.keys.first]; - } - - if (encoder != null) { - setHeader(response, 'content-encoding', key); - outputBuffer = - res.encoders[key]?.convert(outputBuffer) ?? []; - setContentLength(response, outputBuffer.length); - break; - } - } - } - } - - setStatusCode(response, res.statusCode); - addCookies(response, res.cookies); - writeToResponse(response, outputBuffer); - return closeResponse(response).then(cleanup); - }); - } - - /// Runs a [MiddlewarePipeline]. - static Future runPipeline( - MiddlewarePipelineIterator it, - RequestContextType req, - ResponseContextType res, - Application app) async { - var broken = false; - while (it.moveNext()) { - var current = it.current.handlers.iterator; - - while (!broken && current.moveNext()) { - var result = await app.executeHandler(current.current, req, res); - if (result != true) { - broken = true; - break; - } - } - } - } -} diff --git a/packages/core/lib/src/core/env.dart b/packages/core/lib/src/core/env.dart deleted file mode 100644 index 1508db6..0000000 --- a/packages/core/lib/src/core/env.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'dart:io'; - -/// A constant instance of [ProtevusEnv]. -const ProtevusEnvironment protevusEnv = ProtevusEnvironment(); - -/// Queries the environment's `ANGEL_ENV` value. -class ProtevusEnvironment { - final String? _customValue; - - /// You can optionally provide a custom value, in order to override the system's - /// value. - const ProtevusEnvironment([this._customValue]); - - /// Returns the value of the `ANGEL_ENV` variable; defaults to `'development'`. - String get value => - (_customValue ?? Platform.environment['PROTEVUS_ENV'] ?? 'development') - .toLowerCase(); - - /// Returns whether the [value] is `'development'`. - bool get isDevelopment => value == 'development'; - - /// Returns whether the [value] is `'production'`. - bool get isProduction => value == 'production'; - - /// Returns whether the [value] is `'staging'`. - bool get isStaging => value == 'staging'; -} diff --git a/packages/core/lib/src/core/hooked_service.dart b/packages/core/lib/src/core/hooked_service.dart deleted file mode 100644 index 0cf31f3..0000000 --- a/packages/core/lib/src/core/hooked_service.dart +++ /dev/null @@ -1,605 +0,0 @@ -library platform_core.core.hooked_service; - -import 'dart:async'; - -import '../util.dart'; -import 'metadata.dart'; -import 'request_context.dart'; -import 'response_context.dart'; -import 'routable.dart'; -import 'server.dart'; -import 'service.dart'; - -/// Wraps another service in a service that broadcasts events on actions. -class HookedService> - extends Service { - final List> _ctrl = []; - - /// Tbe service that is proxied by this hooked one. - final T inner; - - final HookedServiceEventDispatcher beforeIndexed = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher beforeRead = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher beforeCreated = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher beforeModified = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher beforeUpdated = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher beforeRemoved = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher afterIndexed = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher afterRead = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher afterCreated = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher afterModified = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher afterUpdated = - HookedServiceEventDispatcher(); - final HookedServiceEventDispatcher afterRemoved = - HookedServiceEventDispatcher(); - - HookedService(this.inner) { - // Clone app instance - if (inner.isAppActive) { - app = inner.app; - } - } - - @override - FutureOr Function(RequestContext, ResponseContext)? get readData => - inner.readData; - - RequestContext? _getRequest(Map? params) { - if (params == null) return null; - return params['__requestctx'] as RequestContext?; - } - - ResponseContext? _getResponse(Map? params) { - if (params == null) return null; - return params['__responsectx'] as ResponseContext?; - } - - Map _stripReq(Map? params) { - if (params == null) { - return {}; - } else { - return params.keys - .where((key) => key != '__requestctx' && key != '__responsectx') - .fold>( - {}, (map, key) => map..[key] = params[key]); - } - } - - /// Closes any open [StreamController]s on this instance. **Internal use only**. - @override - Future close() { - for (var c in _ctrl) { - c.close(); - } - beforeIndexed._close(); - beforeRead._close(); - beforeCreated._close(); - beforeModified._close(); - beforeUpdated._close(); - beforeRemoved._close(); - afterIndexed._close(); - afterRead._close(); - afterCreated._close(); - afterModified._close(); - afterUpdated._close(); - afterRemoved._close(); - inner.close(); - return Future.value(); - } - - /// Adds hooks to this instance. - void addHooks(Application app) { - var hooks = getAnnotation(inner, app.container.reflector); - var before = >[]; - var after = >[]; - - if (hooks != null) { - before.addAll(hooks.before.cast()); - after.addAll(hooks.after.cast()); - } - - void applyListeners( - Function fn, HookedServiceEventDispatcher dispatcher, - [bool? isAfter]) { - var hooks = getAnnotation(fn, app.container.reflector); - final listeners = >[ - ...isAfter == true ? after : before - ]; - - if (hooks != null) { - listeners.addAll((isAfter == true ? hooks.after : hooks.before).cast()); - } - - listeners.forEach(dispatcher.listen); - } - - applyListeners(inner.index, beforeIndexed); - applyListeners(inner.read, beforeRead); - applyListeners(inner.create, beforeCreated); - applyListeners(inner.modify, beforeModified); - applyListeners(inner.update, beforeUpdated); - applyListeners(inner.remove, beforeRemoved); - applyListeners(inner.index, afterIndexed, true); - applyListeners(inner.read, afterRead, true); - applyListeners(inner.create, afterCreated, true); - applyListeners(inner.modify, afterModified, true); - applyListeners(inner.update, afterUpdated, true); - applyListeners(inner.remove, afterRemoved, true); - } - - @override - List get bootstrappers => - List.from(super.bootstrappers) - ..add((RequestContext req, ResponseContext res) { - req.serviceParams - ..['__requestctx'] = req - ..['__responsectx'] = res; - return true; - }); - - @override - void addRoutes([Service? service]) { - super.addRoutes(service ?? inner); - } - - /// Runs the [listener] before every service method specified. - void before(Iterable eventNames, - HookedServiceEventListener listener) { - eventNames.map((name) { - switch (name) { - case HookedServiceEvent.indexed: - return beforeIndexed; - case HookedServiceEvent.read: - return beforeRead; - case HookedServiceEvent.created: - return beforeCreated; - case HookedServiceEvent.modified: - return beforeModified; - case HookedServiceEvent.updated: - return beforeUpdated; - case HookedServiceEvent.removed: - return beforeRemoved; - default: - throw ArgumentError('Invalid service method: $name'); - } - }).forEach((HookedServiceEventDispatcher dispatcher) => - dispatcher.listen(listener)); - } - - /// Runs the [listener] after every service method specified. - void after(Iterable eventNames, - HookedServiceEventListener listener) { - eventNames.map((name) { - switch (name) { - case HookedServiceEvent.indexed: - return afterIndexed; - case HookedServiceEvent.read: - return afterRead; - case HookedServiceEvent.created: - return afterCreated; - case HookedServiceEvent.modified: - return afterModified; - case HookedServiceEvent.updated: - return afterUpdated; - case HookedServiceEvent.removed: - return afterRemoved; - default: - throw ArgumentError('Invalid service method: $name'); - } - }).forEach((HookedServiceEventDispatcher dispatcher) => - dispatcher.listen(listener)); - } - - /// Runs the [listener] before every service method. - void beforeAll(HookedServiceEventListener listener) { - beforeIndexed.listen(listener); - beforeRead.listen(listener); - beforeCreated.listen(listener); - beforeModified.listen(listener); - beforeUpdated.listen(listener); - beforeRemoved.listen(listener); - } - - /// Runs the [listener] after every service method. - void afterAll(HookedServiceEventListener listener) { - afterIndexed.listen(listener); - afterRead.listen(listener); - afterCreated.listen(listener); - afterModified.listen(listener); - afterUpdated.listen(listener); - afterRemoved.listen(listener); - } - - /// Returns a [Stream] of all events fired before every service method. - /// - /// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee - /// that events coming out of this [Stream] will see changes you make within the [Stream] - /// callback. - Stream> beforeAllStream() { - var ctrl = StreamController>(); - _ctrl.add(ctrl); - before(HookedServiceEvent.all, ctrl.add); - return ctrl.stream; - } - - /// Returns a [Stream] of all events fired after every service method. - /// - /// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee - /// that events coming out of this [Stream] will see changes you make within the [Stream] - /// callback. - Stream> afterAllStream() { - var ctrl = StreamController>(); - _ctrl.add(ctrl); - before(HookedServiceEvent.all, ctrl.add); - return ctrl.stream; - } - - /// Returns a [Stream] of all events fired before every service method specified. - /// - /// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee - /// that events coming out of this [Stream] will see changes you make within the [Stream] - /// callback. - Stream> beforeStream( - Iterable eventNames) { - var ctrl = StreamController>(); - _ctrl.add(ctrl); - before(eventNames, ctrl.add); - return ctrl.stream; - } - - /// Returns a [Stream] of all events fired AFTER every service method specified. - /// - /// *NOTE*: Only use this if you do not plan to modify events. There is no guarantee - /// that events coming out of this [Stream] will see changes you make within the [Stream] - /// callback. - Stream> afterStream( - Iterable eventNames) { - var ctrl = StreamController>(); - _ctrl.add(ctrl); - after(eventNames, ctrl.add); - return ctrl.stream; - } - - /// Runs the [listener] before [create], [modify] and [update]. - void beforeModify(HookedServiceEventListener listener) { - beforeCreated.listen(listener); - beforeModified.listen(listener); - beforeUpdated.listen(listener); - } - - @override - Future> index([Map? params]) { - var localParams = _stripReq(params); - return beforeIndexed - ._emit(HookedServiceEvent(false, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.indexed, - params: localParams)) - .then((before) { - if (before._canceled) { - return afterIndexed - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.indexed, - params: localParams, result: before.result)) - .then((after) => after.result as List); - } - - return inner.index(localParams).then((result) { - return afterIndexed - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.indexed, - params: localParams, result: result)) - .then((after) => after.result as List); - }); - }); - } - - @override - Future read(Id id, [Map? params]) { - var localParams = _stripReq(params); - return beforeRead - ._emit(HookedServiceEvent(false, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.read, - id: id, params: localParams)) - .then((before) { - if (before._canceled) { - return afterRead - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.read, - id: id, params: localParams, result: before.result)) - .then((after) => after.result as Data); - } - - return inner.read(id, localParams).then((result) { - return afterRead - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.read, - id: id, params: localParams, result: result)) - .then((after) => after.result as Data); - }); - }); - } - - @override - Future create(Data data, [Map? params]) { - var localParams = _stripReq(params); - return beforeCreated - ._emit(HookedServiceEvent(false, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.created, - data: data, params: localParams)) - .then((before) { - if (before._canceled) { - return afterCreated - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.created, - data: before.data, params: localParams, result: before.result)) - .then((after) => after.result as Data); - } - - return inner.create(before.data as Data, localParams).then((result) { - return afterCreated - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.created, - data: before.data, params: localParams, result: result)) - .then((after) => after.result as Data); - }); - }); - } - - @override - Future modify(Id id, Data data, [Map? params]) { - var localParams = _stripReq(params); - return beforeModified - ._emit(HookedServiceEvent(false, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.modified, - id: id, data: data, params: localParams)) - .then((before) { - if (before._canceled) { - return afterModified - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.modified, - id: id, - data: before.data, - params: localParams, - result: before.result)) - .then((after) => after.result as Data); - } - - return inner.modify(id, before.data as Data, localParams).then((result) { - return afterModified - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.created, - id: id, data: before.data, params: localParams, result: result)) - .then((after) => after.result as Data); - }); - }); - } - - @override - Future update(Id id, Data data, [Map? params]) { - var localParams = _stripReq(params); - return beforeUpdated - ._emit(HookedServiceEvent(false, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.updated, - id: id, data: data, params: localParams)) - .then((before) { - if (before._canceled) { - return afterUpdated - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.updated, - id: id, - data: before.data, - params: localParams, - result: before.result)) - .then((after) => after.result as Data); - } - - return inner.update(id, before.data as Data, localParams).then((result) { - return afterUpdated - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.updated, - id: id, data: before.data, params: localParams, result: result)) - .then((after) => after.result as Data); - }); - }); - } - - @override - Future remove(Id id, [Map? params]) { - var localParams = _stripReq(params); - return beforeRemoved - ._emit(HookedServiceEvent(false, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.removed, - id: id, params: localParams)) - .then((before) { - if (before._canceled) { - return afterRemoved - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.removed, - id: id, params: localParams, result: before.result)) - .then((after) => after.result) as Data; - } - - return inner.remove(id, localParams).then((result) { - return afterRemoved - ._emit(HookedServiceEvent(true, _getRequest(params), - _getResponse(params), inner, HookedServiceEvent.removed, - id: id, params: localParams, result: result)) - .then((after) => after.result as Data); - }); - }); - } - - /// Fires an `after` event. This will not be propagated to clients, - /// but will be broadcasted to WebSockets, etc. - Future> fire(String eventName, result, - [HookedServiceEventListener? callback]) { - HookedServiceEventDispatcher dispatcher; - - switch (eventName) { - case HookedServiceEvent.indexed: - dispatcher = afterIndexed; - break; - case HookedServiceEvent.read: - dispatcher = afterRead; - break; - case HookedServiceEvent.created: - dispatcher = afterCreated; - break; - case HookedServiceEvent.modified: - dispatcher = afterModified; - break; - case HookedServiceEvent.updated: - dispatcher = afterUpdated; - break; - case HookedServiceEvent.removed: - dispatcher = afterRemoved; - break; - default: - throw ArgumentError("Invalid service event name: '$eventName'"); - } - - var ev = - HookedServiceEvent(true, null, null, inner, eventName); - return fireEvent(dispatcher, ev, callback); - } - - /// Sends an arbitrary event down the hook chain. - Future> fireEvent( - HookedServiceEventDispatcher dispatcher, - HookedServiceEvent event, - [HookedServiceEventListener? callback]) { - Future? f; - if (callback != null && event._canceled != true) { - f = Future.sync(() => callback(event)); - } - f ??= Future.value(); - return f.then((_) => dispatcher._emit(event)); - } -} - -/// Fired when a hooked service is invoked. -class HookedServiceEvent> { - static const String indexed = 'indexed'; - static const String read = 'read'; - static const String created = 'created'; - static const String modified = 'modified'; - static const String updated = 'updated'; - static const String removed = 'removed'; - - static const List all = [ - indexed, - read, - created, - modified, - updated, - removed - ]; - - /// Use this to end processing of an event. - void cancel([result]) { - _canceled = true; - this.result = result ?? this.result; - } - - /// Resolves a service from the application. - /// - /// Shorthand for `e.service.app.service(...)`. - Service? getService(Pattern path) => service.app.findService(path); - - bool _canceled = false; - final String _eventName; - Id? _id; - final bool _isAfter; - Data? data; - Map? _params; - final RequestContext? _request; - final ResponseContext? _response; - dynamic result; - - String get eventName => _eventName; - - Id? get id => _id; - - bool get isAfter => _isAfter == true; - - bool get isBefore => !isAfter; - - Map get params => _params ?? {}; - - RequestContext? get request => _request; - - ResponseContext? get response => _response; - - /// The inner service whose method was hooked. - T service; - - HookedServiceEvent(this._isAfter, this._request, this._response, this.service, - this._eventName, - {Id? id, this.data, Map? params, this.result}) { - //_data = data; - _id = id; - _params = params ?? {}; - } -} - -/// Triggered on a hooked service event. -typedef HookedServiceEventListener> - = FutureOr Function(HookedServiceEvent event); - -/// Can be listened to, but events may be canceled. -class HookedServiceEventDispatcher> { - final List>> _ctrl = []; - final List> listeners = []; - - void _close() { - for (var c in _ctrl) { - c.close(); - } - listeners.clear(); - } - - /// Fires an event, and returns it once it is either canceled, or all listeners have run. - Future> _emit( - HookedServiceEvent event) { - if (event._canceled == true || listeners.isEmpty) { - return Future.value(event); - } - - var f = Future>.value(event); - - for (var listener in listeners) { - f = f.then((event) { - if (event._canceled) return event; - return Future.sync(() => listener(event)).then((_) => event); - }); - } - - return f; - } - - /// Returns a [Stream] containing all events fired by this dispatcher. - /// - /// *NOTE*: Callbacks on the returned [Stream] cannot be guaranteed to run before other [listeners]. - /// Use this only if you need a read-only stream of events. - Stream> asStream() { - var ctrl = StreamController>(); - _ctrl.add(ctrl); - listen(ctrl.add); - return ctrl.stream; - } - - /// Registers the listener to be called whenever an event is triggered. - void listen(HookedServiceEventListener listener) { - listeners.add(listener); - } -} diff --git a/packages/core/lib/src/core/hostname_parser.dart b/packages/core/lib/src/core/hostname_parser.dart deleted file mode 100644 index 02abf1e..0000000 --- a/packages/core/lib/src/core/hostname_parser.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:collection'; -import 'package:string_scanner/string_scanner.dart'; - -/// Parses a string into a [RegExp] that is matched against hostnames. -class HostnameSyntaxParser { - final SpanScanner _scanner; - final _safe = RegExp(r'[0-9a-zA-Z-_:]+'); - - HostnameSyntaxParser(String hostname) - : _scanner = SpanScanner(hostname, sourceUrl: hostname); - - FormatException _formatExc(String message) { - var span = _scanner.lastSpan ?? _scanner.emptySpan; - return FormatException( - '${span.start.toolString}: $message\n${span.highlight(color: true)}'); - } - - RegExp parse() { - var b = StringBuffer(); - var parts = Queue(); - - while (!_scanner.isDone) { - if (_scanner.scan('|')) { - if (parts.isEmpty) { - throw _formatExc('No hostname parts found before "|".'); - } else { - var next = _parseHostnamePart(); - if (next.isEmpty) { - throw _formatExc('No hostname parts found after "|".'); - } else { - var prev = parts.removeLast(); - parts.addLast('(($prev)|($next))'); - } - } - } else { - var part = _parseHostnamePart(); - if (part.isNotEmpty) { - if (_scanner.scan('.')) { - var subPart = _parseHostnamePart(shouldThrow: false); - while (subPart.isNotEmpty) { - part += '\\.$subPart'; - if (_scanner.scan('.')) { - subPart = _parseHostnamePart(shouldThrow: false); - } else { - break; - } - } - } - parts.add(part); - } - } - } - - while (parts.isNotEmpty) { - b.write(parts.removeFirst()); - } - - if (b.isEmpty) { - throw _formatExc('Invalid or empty hostname.'); - } else { - return RegExp('^$b\$', caseSensitive: false); - } - } - - String _parseHostnamePart({bool shouldThrow = true}) { - if (_scanner.scan('*.')) { - return r'([^$.]+\.)?'; - } else if (_scanner.scan('*')) { - return r'[^$]*'; - } else if (_scanner.scan('+')) { - return r'[^$]+'; - } else if (_scanner.scan(_safe)) { - return _scanner.lastMatch?[0] ?? ''; - } else if (!_scanner.isDone && shouldThrow) { - var s = String.fromCharCode(_scanner.peekChar()!); - throw _formatExc('Unexpected character "$s".'); - } else { - return ''; - } - } -} diff --git a/packages/core/lib/src/core/hostname_router.dart b/packages/core/lib/src/core/hostname_router.dart deleted file mode 100644 index 9b7e121..0000000 --- a/packages/core/lib/src/core/hostname_router.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'dart:async'; -import 'package:platform_container/container.dart'; -import 'package:platform_route/route.dart'; -import 'package:logging/logging.dart'; -import 'env.dart'; -import 'hostname_parser.dart'; -import 'request_context.dart'; -import 'response_context.dart'; -import 'routable.dart'; -import 'server.dart'; - -/// A utility that allows requests to be handled based on their -/// origin's hostname. -/// -/// For example, an application could handle example.com and api.example.com -/// separately. -/// -/// The provided patterns can be any `Pattern`. If a `String` is provided, a simple -/// grammar (see [HostnameSyntaxParser]) is used to create [RegExp]. -/// -/// For example: -/// * `example.com` -> `/example\.com/` -/// * `*.example.com` -> `/([^$.]\.)?example\.com/` -/// * `example.*` -> `/example\./[^$]*` -/// * `example.+` -> `/example\./[^$]+` -class HostnameRouter { - final Map _apps = {}; - final Map Function()> _creators = {}; - final List _patterns = []; - - HostnameRouter( - {Map apps = const {}, - Map Function()> creators = const {}}) { - Map parseMap(Map map) { - return map.map((p, c) { - Pattern pp; - - if (p is String) { - pp = HostnameSyntaxParser(p).parse(); - } else { - pp = p; - } - - return MapEntry(pp, c); - }); - } - - apps = parseMap(apps); - creators = parseMap(creators); - var patterns = apps.keys.followedBy(creators.keys).toSet().toList(); - _apps.addAll(apps); - _creators.addAll(creators); - _patterns.addAll(patterns); - // print(_creators); - } - - factory HostnameRouter.configure( - Map Function(Application)> configurers, - {Reflector reflector = const EmptyReflector(), - ProtevusEnvironment environment = protevusEnv, - Logger? logger, - bool allowMethodOverrides = true, - FutureOr Function(dynamic)? serializer, - ViewGenerator? viewGenerator}) { - var creators = configurers.map((p, c) { - return MapEntry(p, () async { - var app = Application( - reflector: reflector, - environment: environment, - logger: logger, - allowMethodOverrides: allowMethodOverrides, - serializer: serializer, - viewGenerator: viewGenerator); - await app.configure(c); - return app; - }); - }); - return HostnameRouter(creators: creators); - } - - /// Attempts to handle a request, according to its hostname. - /// - /// If none is matched, then `true` is returned. - /// Also returns `true` if all of the sub-app's handlers returned - /// `true`. - Future handleRequest(RequestContext req, ResponseContext res) async { - for (var pattern in _patterns) { - // print('${req.hostname} vs $_creators'); - if (pattern.allMatches(req.hostname).isNotEmpty) { - // Resolve the entire pipeline within the context of the selected app. - var app = _apps[pattern] ??= (await _creators[pattern]!()); - // print('App for ${req.hostname} = $app from $pattern'); - // app.dumpTree(); - - var r = app.optimizedRouter; - var resolved = r.resolveAbsolute(req.path, method: req.method); - var pipeline = MiddlewarePipeline(resolved); - // print('Pipeline: $pipeline'); - for (var handler in pipeline.handlers) { - // print(handler); - // Avoid stack overflow. - if (handler == handleRequest) { - continue; - } else if (!await app.executeHandler(handler, req, res)) { - // print('$handler TERMINATED'); - return false; - } else { - // print('$handler CONTINUED'); - } - } - } - } - - // Otherwise, return true. - return true; - } -} diff --git a/packages/core/lib/src/core/injection.dart b/packages/core/lib/src/core/injection.dart deleted file mode 100644 index eb4a0b0..0000000 --- a/packages/core/lib/src/core/injection.dart +++ /dev/null @@ -1,208 +0,0 @@ -part of 'request_context.dart'; - -const List _primitiveTypes = [String, int, num, double, Null]; - -/// Shortcut for calling [preInject], and then [handleContained]. -/// -/// Use this to instantly create a request handler for a DI-enabled method. -/// -/// Calling [ioc] also auto-serializes the result of a [handler]. -RequestHandler ioc(Function handler, {Iterable optional = const []}) { - return (req, res) { - RequestHandler? contained; - - if (req.app?.container != null) { - var injection = preInject(handler, req.app!.container.reflector); - //if (injection != null) { - injection.optional.addAll(optional); - contained = handleContained(handler, injection); - //} - } - - return req.app!.executeHandler(contained, req, res); - }; -} - -Future resolveInjection(requirement, InjectionRequest injection, - RequestContext req, ResponseContext res, bool throwOnUnresolved, - [Container? container]) async { - dynamic propFromApp; - container ??= req.container ?? res.app!.container; - - if (requirement == RequestContext) { - return req; - } else if (requirement == ResponseContext) { - return res; - } else if (requirement is String && - injection.parameters.containsKey(requirement)) { - var param = injection.parameters[requirement]!; - var value = param.getValue(req); - if (value == null && param.required != false) throw param.error as Object; - return value; - } else if (requirement is String) { - if (req.container!.hasNamed(requirement)) { - return req.container!.findByName(requirement); - } - if (req.params.containsKey(requirement)) { - return req.params[requirement]; - } else if ((propFromApp = req.app!.findProperty(requirement)) != null) { - return propFromApp; - } else if (injection.optional.contains(requirement)) { - return null; - } else if (throwOnUnresolved) { - throw ArgumentError( - "Cannot resolve parameter '$requirement' within handler."); - } - } else if (requirement is List && - requirement.length == 2 && - requirement.first is String && - requirement.last is Type) { - var key = requirement.first; - var type = requirement.last; - if (req.params.containsKey(key) || - req.app!.configuration.containsKey(key) || - _primitiveTypes.contains(type)) { - return await resolveInjection( - key, injection, req, res, throwOnUnresolved, container); - } else { - return await resolveInjection( - type, injection, req, res, throwOnUnresolved, container); - } - } else if (requirement is Type && requirement != dynamic) { - try { - var futureType = container.reflector.reflectFutureOf(requirement); - if (container.has(futureType.reflectedType)) { - return await container.make(futureType.reflectedType); - } - } on UnsupportedError { - // Ignore. - } - - return await container.make(requirement); - } else if (throwOnUnresolved) { - throw ArgumentError( - '$requirement cannot be injected into a request handler.'); - } -} - -/// Checks if an [InjectionRequest] can be sufficiently executed within the current request/response context. -bool suitableForInjection( - RequestContext req, ResponseContext res, InjectionRequest injection) { - return injection.parameters.values.any((p) { - if (p.match == null) return false; - var value = p.getValue(req); - return value == p.match; - }); -} - -/// Handles a request with a DI-enabled handler. -RequestHandler handleContained(Function handler, InjectionRequest injection, - [Container? container]) { - return (RequestContext req, ResponseContext res) async { - if (injection.parameters.isNotEmpty && - injection.parameters.values.any((p) => p.match != null) && - !suitableForInjection(req, res, injection)) { - return Future.value(true); - } - - var args = []; - - var named = {}; - - for (var r in injection.required) { - args.add(await resolveInjection(r, injection, req, res, true, container)); - } - - for (var entry in injection.named.entries) { - var name = Symbol(entry.key); - named[name] = await resolveInjection( - [entry.key, entry.value], injection, req, res, false, container); - } - - return Function.apply(handler, args, named); - }; -} - -/// Contains a list of the data required for a DI-enabled method to run. -/// -/// This improves performance by removing the necessity to reflect a method -/// every time it is requested. -/// -/// Regular request handlers can also skip DI entirely, lowering response time -/// and memory use. -class InjectionRequest { - /// Optional, typed data that can be passed to a DI-enabled method. - final Map named; - - /// A list of the arguments required for a DI-enabled method to run. - final List required; - - /// A list of the arguments that can be null in a DI-enabled method. - final List optional; - - /// Extended parameter definitions. - final Map parameters; - - const InjectionRequest.constant( - {this.named = const {}, - this.required = const [], - this.optional = const [], - this.parameters = const {}}); - - InjectionRequest() - : named = {}, - required = [], - optional = [], - parameters = {}; -} - -/// Predetermines what needs to be injected for a handler to run. -InjectionRequest preInject(Function handler, Reflector reflector) { - var injection = InjectionRequest(); - - var closureMirror = reflector.reflectFunction(handler)!; - - if (closureMirror.parameters.isEmpty) return injection; - - // Load parameters - for (var parameter in closureMirror.parameters) { - var name = parameter.name; - var type = parameter.type.reflectedType; - - var p = parameter.annotations - .firstWhereOrNull( - (m) => m.type.isAssignableTo(reflector.reflectType(Parameter))) - ?.reflectee as Parameter?; - //print(p); - if (p != null) { - injection.parameters[name] = Parameter( - cookie: p.cookie, - header: p.header, - query: p.query, - session: p.session, - match: p.match, - defaultValue: p.defaultValue, - required: parameter.isNamed ? false : p.required != false, - ); - } - - if (!parameter.isNamed) { - if (!parameter.isRequired) injection.optional.add(name); - - if (type == RequestContext || type == ResponseContext) { - injection.required.add(type); - } else if (name == 'req') { - injection.required.add(RequestContext); - } else if (name == 'res') { - injection.required.add(ResponseContext); - } else if (type == dynamic) { - injection.required.add(name); - } else { - injection.required.add([name, type]); - } - } else { - injection.named[name] = type; - } - } - return injection; -} diff --git a/packages/core/lib/src/core/map_service.dart b/packages/core/lib/src/core/map_service.dart deleted file mode 100644 index 2e018b3..0000000 --- a/packages/core/lib/src/core/map_service.dart +++ /dev/null @@ -1,172 +0,0 @@ -import 'dart:async'; - -import 'package:platform_support/exceptions.dart'; - -import 'service.dart'; - -/// A basic service that manages an in-memory list of maps. -class MapService extends Service> { - /// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`. - /// - /// `false` by default. - final bool allowRemoveAll; - - /// If set to `true`, parameters in `req.query` are applied to the database query. - final bool allowQuery; - - /// If set to `true` (default), then the service will manage an `id` string and `createdAt` and `updatedAt` fields. - final bool autoIdAndDateFields; - - /// If set to `true` (default), then the keys `created_at` and `updated_at` will automatically be snake_cased. - final bool autoSnakeCaseNames; - - final List> items = []; - - MapService( - {this.allowRemoveAll = false, - this.allowQuery = true, - this.autoIdAndDateFields = true, - this.autoSnakeCaseNames = true}) - : super(); - - String get createdAtKey => - autoSnakeCaseNames == false ? 'createdAt' : 'created_at'; - - String get updatedAtKey => - autoSnakeCaseNames == false ? 'updatedAt' : 'updated_at'; - - bool Function(Map) _matchesId(id) { - return (Map item) { - if (item['id'] == null) { - return false; - } else if (autoIdAndDateFields != false) { - return item['id'] == id?.toString(); - } else { - return item['id'] == id; - } - }; - } - - @override - Future>> index([Map? params]) { - if (allowQuery == false || params == null || params['query'] is! Map) { - return Future.value(items); - } else { - var query = params['query'] as Map?; - - return Future.value(items.where((item) { - for (var key in query!.keys) { - if (!item.containsKey(key)) { - return false; - } else if (item[key] != query[key]) { - return false; - } - } - - return true; - }).toList()); - } - } - - @override - Future> read(String? id, - [Map? params]) { - return Future.value(items.firstWhere(_matchesId(id), - orElse: (() => throw PlatformHttpException.notFound( - message: 'No record found for ID $id')))); - } - - @override - Future> create(Map data, - [Map? params]) { - var now = DateTime.now().toIso8601String(); - var result = Map.from(data); - - if (autoIdAndDateFields == true) { - result - ..['id'] = items.length.toString() - ..[autoSnakeCaseNames == false ? 'createdAt' : 'created_at'] = now - ..[autoSnakeCaseNames == false ? 'updatedAt' : 'updated_at'] = now; - } - items.add(result); - return Future.value(result); - } - - @override - Future> modify(String? id, Map data, - [Map? params]) { - //if (data is! Map) { - // throw HttpException.badRequest( - // message: - // 'MapService does not support `modify` with ${data.runtimeType}.'); - //} - if (!items.any(_matchesId(id))) return create(data, params); - - return read(id).then((item) { - var idx = items.indexOf(item); - if (idx < 0) return create(data, params); - var result = Map.from(item)..addAll(data); - - if (autoIdAndDateFields == true) { - result[autoSnakeCaseNames == false ? 'updatedAt' : 'updated_at'] = - DateTime.now().toIso8601String(); - } - return Future.value(items[idx] = result); - }); - } - - @override - Future> update(String? id, Map data, - [Map? params]) { - //if (data is! Map) { - // throw HttpException.badRequest( - // message: - // 'MapService does not support `update` with ${data.runtimeType}.'); - //} - if (!items.any(_matchesId(id))) return create(data, params); - - return read(id).then((old) { - if (!items.remove(old)) { - throw PlatformHttpException.notFound( - message: 'No record found for ID $id'); - } - - var result = Map.from(data); - if (autoIdAndDateFields == true) { - result - ..['id'] = id?.toString() - ..[autoSnakeCaseNames == false ? 'createdAt' : 'created_at'] = - old[autoSnakeCaseNames == false ? 'createdAt' : 'created_at'] - ..[autoSnakeCaseNames == false ? 'updatedAt' : 'updated_at'] = - DateTime.now().toIso8601String(); - } - items.add(result); - return Future.value(result); - }); - } - - @override - Future> remove(String? id, - [Map? params]) { - if (id == null || id == 'null') { - // Remove everything... - if (!(allowRemoveAll == true || - params?.containsKey('provider') != true)) { - throw PlatformHttpException.forbidden( - message: 'Clients are not allowed to delete all items.'); - } else { - items.clear(); - return Future.value({}); - } - } - - return read(id, params).then((result) { - if (items.remove(result)) { - return result; - } else { - throw PlatformHttpException.notFound( - message: 'No record found for ID $id'); - } - }); - } -} diff --git a/packages/core/lib/src/core/metadata.dart b/packages/core/lib/src/core/metadata.dart deleted file mode 100644 index 3ad4d11..0000000 --- a/packages/core/lib/src/core/metadata.dart +++ /dev/null @@ -1,166 +0,0 @@ -library angel_framework.http.metadata; - -import 'package:platform_support/exceptions.dart'; - -import 'hooked_service.dart' show HookedServiceEventListener; -import 'request_context.dart'; -import 'routable.dart'; - -/// Annotation to map middleware onto a handler. -class Middleware { - final Iterable handlers; - - const Middleware(this.handlers); -} - -/// Attaches hooks to a [HookedService]. -class Hooks { - final List before; - final List after; - - const Hooks({this.before = const [], this.after = const []}); -} - -/// Specifies to NOT expose a method to the Internet. -class NoExpose { - const NoExpose(); -} - -const NoExpose noExpose = NoExpose(); - -/// Exposes a [Controller] or a [Controller] method to the Internet. -/// Example: -/// -/// ```dart -/// @Expose('/elements') -/// class ElementController extends Controller { -/// -/// @Expose('/') -/// List getList() => someComputationHere(); -/// -/// @Expose('/int:elementId') -/// getElement(int elementId) => someOtherComputation(); -/// -/// } -/// ``` -class Expose { - final String method; - final String path; - final Iterable middleware; - final String? as; - final List allowNull; - - static const Expose get = Expose('', method: 'GET'), - post = Expose('', method: 'POST'), - patch = Expose('', method: 'PATCH'), - put = Expose('', method: 'PUT'), - delete = Expose('', method: 'DELETE'), - head = Expose('', method: 'HEAD'); - - const Expose(this.path, - {this.method = 'GET', - this.middleware = const [], - this.as, - this.allowNull = const []}); - - const Expose.method(this.method, - {this.middleware = const [], this.as, this.allowNull = const []}) - : path = ''; -} - -/// Used to apply special dependency injections or functionality to a function parameter. -class Parameter { - /// Inject the value of a request cookie. - final String? cookie; - - /// Inject the value of a request header. - final String? header; - - /// Inject the value of a key from the session. - final String? session; - - /// Inject the value of a key from the query. - final String? query; - - /// Only execute the handler if the value of this parameter matches the given value. - final dynamic match; - - /// Specify a default value. - final dynamic defaultValue; - - /// If `true` (default), then an error will be thrown if this parameter is not present. - final bool required; - - const Parameter( - {this.cookie, - this.query, - this.header, - this.session, - this.match, - this.defaultValue, - this.required = true}); - - /// Returns an error that can be thrown when the parameter is not present. - Object? get error { - if (cookie?.isNotEmpty == true) { - return PlatformHttpException.badRequest( - message: 'Missing required cookie "$cookie".'); - } - if (header?.isNotEmpty == true) { - return PlatformHttpException.badRequest( - message: 'Missing required header "$header".'); - } - if (query?.isNotEmpty == true) { - return PlatformHttpException.badRequest( - message: 'Missing required query parameter "$query".'); - } - if (session?.isNotEmpty == true) { - return StateError('Session does not contain required key "$session".'); - } - - return null; - } - - /// Obtains a value for this parameter from a [RequestContext]. - dynamic getValue(RequestContext req) { - if (cookie?.isNotEmpty == true) { - return req.cookies.firstWhere((c) => c.name == cookie).value; - } - if (header?.isNotEmpty == true) { - return req.headers?.value(header ?? '') ?? defaultValue; - } - if (session?.isNotEmpty == true) { - return req.session?[session] ?? defaultValue; - } - if (query?.isNotEmpty == true) { - return req.uri?.queryParameters[query] ?? defaultValue; - } - return defaultValue; - } -} - -/// Shortcut for declaring a request header [Parameter]. -class Header extends Parameter { - const Header(String header, {super.match, super.defaultValue, super.required}) - : super(header: header); -} - -/// Shortcut for declaring a request session [Parameter]. -class Session extends Parameter { - const Session(String session, - {super.match, super.defaultValue, super.required}) - : super(session: session); -} - -/// Shortcut for declaring a request query [Parameter]. -class Query extends Parameter { - const Query(String query, {super.match, super.defaultValue, super.required}) - : super(query: query); -} - -/// Shortcut for declaring a request cookie [Parameter]. -class CookieValue extends Parameter { - const CookieValue(String cookie, - {super.match, super.defaultValue, super.required}) - : super(cookie: cookie); -} diff --git a/packages/core/lib/src/core/request_context.dart b/packages/core/lib/src/core/request_context.dart deleted file mode 100644 index 5bc3603..0000000 --- a/packages/core/lib/src/core/request_context.dart +++ /dev/null @@ -1,382 +0,0 @@ -library Protevus_framework.http.request_context; - -import 'dart:async'; -import 'dart:convert'; -import 'dart:typed_data' show BytesBuilder; -import 'dart:io' - show Cookie, HeaderValue, HttpHeaders, HttpSession, InternetAddress; - -import 'package:platform_container/container.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:belatuk_http_server/belatuk_http_server.dart'; -import 'package:meta/meta.dart'; -import 'package:mime/mime.dart'; -import 'package:path/path.dart' as p; -import 'package:collection/collection.dart'; -import 'package:logging/logging.dart'; - -import 'metadata.dart'; -import 'response_context.dart'; -import 'routable.dart'; -import 'server.dart' show Application; - -part 'injection.dart'; - -/// A convenience wrapper around an incoming [RawRequest]. -abstract class RequestContext { - /// Similar to [Application.shutdownHooks], allows for logic to be executed - /// when a [RequestContext] is done being processed. - final _log = Logger('RequestContext'); - - final List Function()> shutdownHooks = []; - - String? _acceptHeaderCache, _extensionCache; - bool? _acceptsAllCache; - Map? _queryParameters; - Object? _bodyObject; - bool _hasParsedBody = false, _closed = false; - Map _bodyFields = {}; - List _bodyList = []; - List _uploadedFiles = []; - MediaType _contentType = MediaType('text', 'plain'); - - /// The underlying [RawRequest] provided by the driver. - RawRequest get rawRequest; - - /// Additional params to be passed to services. - final Map serviceParams = {}; - - /// The [Application] instance that is responding to this request. - Application? app; - - /// Any cookies sent with this request. - List get cookies => []; - - /// All HTTP headers sent with this request. - HttpHeaders? get headers; - - /// The requested hostname. - String get hostname => 'localhost'; - - /// The IoC container that can be used to provide functionality to produce - /// objects of a given type. - /// - /// This is a *child* of the container found in `app`. - Container? get container; - - /// The user's IP. - String get ip => remoteAddress.address; - - /// This request's HTTP method. - /// - /// This may have been processed by an override. See [originalMethod] to get the real method. - String get method => 'GET'; - - /// The original HTTP verb sent to the server. - String get originalMethod => 'GET'; - - /// The content type of an incoming request. - MediaType get contentType { - if (headers?.contentType != null) { - try { - _contentType = MediaType.parse(headers!.contentType.toString()); - } catch (e) { - _log.warning( - 'Invalid media type [${headers!.contentType.toString()}]', e); - } - } - return _contentType; - } - - /// The URL parameters extracted from the request URI. - Map params = {}; - - /// The requested path. - String get path => ''; - - /// Is this an **XMLHttpRequest**? - bool get isXhr { - return headers?.value('X-Requested-With')?.trim().toLowerCase() == - 'xmlhttprequest'; - } - - /// The remote address requesting this resource. - InternetAddress get remoteAddress; - - /// The user's HTTP session. - HttpSession? get session; - - /// The [Uri] instance representing the path this request is responding to. - Uri? get uri; - - /// The [Stream] of incoming binary data sent from the client. - Stream>? get body; - - /// Returns `true` if [parseBody] has been called so far. - bool get hasParsedBody => _hasParsedBody; - - /// Returns a *mutable* [Map] of the fields parsed from the request [body]. - /// - /// Note that [parseBody] must be called first. - Map get bodyAsMap { - if (!hasParsedBody) { - throw StateError('The request body has not been parsed yet.'); - } - // else if (_bodyFields == null) { - // throw StateError('The request body, $_bodyObject, is not a Map.'); - //} - - return _bodyFields; - } - - /// This setter allows you to explicitly set the request body **exactly once**. - /// - /// Use this if the format of the body is not natively parsed by Protevus. - set bodyAsMap(Map? value) => bodyAsObject = value; - - /// Returns a *mutable* [List] parsed from the request [body]. - /// - /// Note that [parseBody] must be called first. - List? get bodyAsList { - if (!hasParsedBody) { - throw StateError('The request body has not been parsed yet.'); - // TODO: Relook at this - //} else if (_bodyList == null) { - } else if (_bodyList.isEmpty) { - throw StateError('The request body, $_bodyObject, is not a List.'); - } - - return _bodyList; - } - - /// This setter allows you to explicitly set the request body **exactly once**. - /// - /// Use this if the format of the body is not natively parsed by Protevus. - set bodyAsList(List? value) => bodyAsObject = value; - - /// Returns the parsed request body, whatever it may be (typically a [Map] or [List]). - /// - /// Note that [parseBody] must be called first. - Object? get bodyAsObject { - if (!hasParsedBody) { - throw StateError('The request body has not been parsed yet.'); - } - - return _bodyObject; - } - - /// This setter allows you to explicitly set the request body **exactly once**. - /// - /// Use this if the format of the body is not natively parsed by Protevus. - set bodyAsObject(value) { - if (_bodyObject != null) { - throw StateError( - 'The request body has already been parsed/set, and cannot be overwritten.'); - } else { - if (value is List) _bodyList = value; - if (value is Map) _bodyFields = value; - _bodyObject = value; - _hasParsedBody = true; - } - } - - /// Returns a *mutable* map of the files parsed from the request [body]. - /// - /// Note that [parseBody] must be called first. - List? get uploadedFiles { - if (!hasParsedBody) { - throw StateError('The request body has not been parsed yet.'); - } - - return _uploadedFiles; - } - - /// Returns a *mutable* map of the fields contained in the query. - Map get queryParameters => _queryParameters ??= - Map.from(uri?.queryParameters ?? {}); - - /// Returns the file extension of the requested path, if any. - /// - /// Includes the leading `.`, if there is one. - String get extension => _extensionCache ??= p.extension(uri?.path ?? ''); - - /// Returns `true` if the client's `Accept` header indicates that the given [contentType] is considered a valid response. - /// - /// You cannot provide a `null` [contentType]. - /// If the `Accept` header's value is `*/*`, this method will always return `true`. - /// To ignore the wildcard (`*/*`), pass [strict] as `true`. - /// - /// [contentType] can be either of the following: - /// * A [ContentType], in which case the `Accept` header will be compared against its `mimeType` property. - /// * Any other Dart value, in which case the `Accept` header will be compared against the result of a `toString()` call. - bool accepts(contentType, {bool strict = false}) { - var contentTypeString = contentType is MediaType - ? contentType.mimeType - : contentType?.toString(); - - // Change to assert - if (contentTypeString == null) { - _log.severe('RequestContext.accepts is null'); - throw ArgumentError( - 'RequestContext.accepts expects the `contentType` parameter to NOT be null.'); - } - - _acceptHeaderCache ??= headers?.value('accept'); - - if (_acceptHeaderCache == null) { - return true; - } else if (strict != true && _acceptHeaderCache!.contains('*/*')) { - return true; - } else { - return _acceptHeaderCache!.contains(contentTypeString); - } - } - - /// Returns as `true` if the client's `Accept` header indicates that it will accept any response content type. - bool get acceptsAll => _acceptsAllCache ??= accepts('*/*'); - - /// Shorthand for deserializing [bodyAsMap], using some transformer function [f]. - Future deserializeBody(FutureOr Function(Map?) f, - {Encoding encoding = utf8}) async { - await parseBody(encoding: encoding); - return await f(bodyAsMap); - } - - /// Shorthand for decoding [bodyAsMap], using some [codec]. - Future decodeBody(Codec codec, {Encoding encoding = utf8}) => - deserializeBody(codec.decode, encoding: encoding); - - /// Manually parses the request body, if it has not already been parsed. - Future parseBody({Encoding encoding = utf8}) async { - //if (contentType == null) { - // throw FormatException('Missing "content-type" header.'); - //} - - if (!_hasParsedBody) { - _hasParsedBody = true; - - var contentBody = body ?? Stream.empty(); - - if (contentType.type == 'application' && contentType.subtype == 'json') { - _uploadedFiles = []; - - var parsed = (_bodyObject = - await encoding.decoder.bind(contentBody).join().then(json.decode)); - - if (parsed is Map) { - _bodyFields = Map.from(parsed); - } else if (parsed is List) { - _bodyList = parsed; - } - } else if (contentType.type == 'application' && - contentType.subtype == 'x-www-form-urlencoded') { - _uploadedFiles = []; - var parsed = await encoding.decoder - .bind(contentBody) - .join() - .then((s) => Uri.splitQueryString(s, encoding: encoding)); - _bodyFields = Map.from(parsed); - } else if (contentType.type == 'multipart' && - contentType.subtype == 'form-data' && - contentType.parameters.containsKey('boundary')) { - var boundary = contentType.parameters['boundary'] ?? ''; - var transformer = MimeMultipartTransformer(boundary); - var parts = transformer.bind(contentBody).map((part) => - HttpMultipartFormData.parse(part, defaultEncoding: encoding)); - _bodyFields = {}; - _uploadedFiles = []; - - await for (var part in parts) { - if (part.isBinary) { - _uploadedFiles.add(UploadedFile(part)); - } else if (part.isText && - part.contentDisposition.parameters.containsKey('name')) { - // If there is no name, then don't parse it. - var key = part.contentDisposition.parameters['name']; - if (key != null) { - var value = await part.join(); - _bodyFields[key] = value; - } - } - } - } else { - _bodyFields = {}; - _uploadedFiles = []; - } - } - } - - /// Disposes of all resources. - @mustCallSuper - Future close() async { - if (!_closed) { - _closed = true; - _acceptsAllCache = null; - _acceptHeaderCache = null; - serviceParams.clear(); - params.clear(); - await Future.forEach(shutdownHooks, (dynamic hook) => hook()); - } - } -} - -/// Reads information about a binary chunk uploaded to the server. -class UploadedFile { - /// The underlying `form-data` item. - final HttpMultipartFormData formData; - final log = Logger('UploadedFile'); - - MediaType _contentType = MediaType('multipart', 'form-data'); - - UploadedFile(this.formData); - - /// Returns the binary stream from [formData]. - Stream> get data => formData.cast>(); - - /// The filename associated with the data on the user's system. - /// Returns [:null:] if not present. - String? get filename => formData.contentDisposition.parameters['filename']; - - /// The name of the field associated with this data. - /// Returns [:null:] if not present. - String? get name => formData.contentDisposition.parameters['name']; - - /// The parsed [:Content-Type:] header of the [:HttpMultipartFormData:]. - /// Returns [:null:] if not present. - //MediaType get contentType => _contentType ??= (formData.contentType == null - // ? null - // : MediaType.parse(formData.contentType.toString())); - - MediaType get contentType { - if (formData.contentType != null) { - try { - _contentType = MediaType.parse(formData.contentType.toString()); - } catch (e) { - log.warning( - 'Invalue media type [${formData.contentType.toString()}]', e); - } - } - - return _contentType; - } - - /// The parsed [:Content-Transfer-Encoding:] header of the - /// [:HttpMultipartFormData:]. This field is used to determine how to decode - /// the data. Returns [:null:] if not present. - HeaderValue? get contentTransferEncoding => formData.contentTransferEncoding; - - /// Reads the contents of the file into a single linear buffer. - /// - /// Note that this leads to holding the whole file in memory, which might - /// not be ideal for large files.w - Future> readAsBytes() { - return data - .fold(BytesBuilder(), (bb, out) => bb..add(out)) - .then((bb) => bb.takeBytes()); - } - - /// Reads the contents of the file as [String], using the given [encoding]. - Future readAsString({Encoding encoding = utf8}) { - return encoding.decoder.bind(data).join(); - } -} diff --git a/packages/core/lib/src/core/response_context.dart b/packages/core/lib/src/core/response_context.dart deleted file mode 100644 index 0ab77b6..0000000 --- a/packages/core/lib/src/core/response_context.dart +++ /dev/null @@ -1,456 +0,0 @@ -library platform_core.http.response_context; - -import 'dart:async'; -import 'dart:convert'; -import 'dart:convert' as c show json; -import 'dart:io' show BytesBuilder, Cookie; -import 'dart:typed_data'; - -import 'package:platform_route/route.dart'; -import 'package:file/file.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:mime/mime.dart'; - -import 'controller.dart'; -import 'request_context.dart'; -import 'server.dart' show Application; - -final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); - -/// A convenience wrapper around an outgoing HTTP request. -abstract class ResponseContext - implements StreamConsumer>, StreamSink>, StringSink { - final Map properties = {}; - final CaseInsensitiveMap _headers = CaseInsensitiveMap.from( - {'content-type': 'text/plain', 'server': 'Protevus3'}); - - //final log = Logger('ResponseContext'); - - Completer? _done; - int _statusCode = 200; - - /// The [Application] instance that is sending a response. - Application? app; - - /// Is `Transfer-Encoding` chunked? - bool? chunked; - - /// Any and all cookies to be sent to the user. - final List cookies = []; - - /// A set of [Converter] objects that can be used to encode response data. - /// - /// At most one encoder will ever be used to convert data. - final Map, List>> encoders = {}; - - /// A [Map] of data to inject when `res.render` is called. - /// - /// This can be used to reduce boilerplate when using templating engines. - final Map renderParams = {}; - - /// Points to the [RequestContext] corresponding to this response. - RequestContext? get correspondingRequest; - - @override - Future get done => (_done ?? Completer()).future; - - /// Headers that will be sent to the user. - /// - /// Note that if you have already started writing to the underlying stream, headers will not persist. - CaseInsensitiveMap get headers => _headers; - - /// Serializes response data into a String. - /// - /// The default is conversion into JSON via `json.encode`. - /// - /// If you are 100% sure that your response handlers will only - /// be JSON-encodable objects (i.e. primitives, `List`s and `Map`s), - /// then consider setting [serializer] to `JSON.encode`. - /// - /// To set it globally for the whole [app], use the following helper: - /// ```dart - /// app.injectSerializer(JSON.encode); - /// ``` - FutureOr Function(dynamic) serializer = c.json.encode; - - /// This response's status code. - int get statusCode => _statusCode; - - set statusCode(int value) { - if (!isOpen) { - throw closed(); - } else { - _statusCode = value; // ?? 200; - } - } - - /// Returns `true` if the response is still available for processing by Protevus. - /// - /// If it is `false`, then Protevus will stop executing handlers, and will only run - /// response finalizers if the response [isBuffered]. - bool get isOpen; - - /// Returns `true` if response data is being written to a buffer, rather than to the underlying stream. - bool get isBuffered; - - /// A set of UTF-8 encoded bytes that will be written to the response. - BytesBuilder? get buffer; - - /// The underlying [RawResponse] under this instance. - RawResponse get rawResponse; - - /// Signals Protevus that the response is being held alive deliberately, and that the framework should not automatically close it. - /// - /// This is mostly used in situations like WebSocket handlers, where the connection should remain - /// open indefinitely. - FutureOr detach(); - - /// Gets or sets the content length to send back to a client. - /// - /// Returns `null` if the header is invalidly formatted. - int? get contentLength { - return int.tryParse(headers['content-length'] ?? '-1'); - } - - /// Gets or sets the content length to send back to a client. - /// - /// If [value] is `null`, then the header will be removed. - set contentLength(int? value) { - if (value == null || value == -1) { - headers.remove('content-length'); - } else { - headers['content-length'] = value.toString(); - } - } - - /// Gets or sets the content type to send back to a client. - MediaType get contentType { - try { - return MediaType.parse(headers['content-type']!); - } catch (_) { - return MediaType('text', 'plain'); - } - } - - /// Gets or sets the content type to send back to a client. - set contentType(MediaType value) { - headers['content-type'] = value.toString(); - } - - static StateError closed() => StateError('Cannot modify a closed response.'); - - /// Sends a download as a response. - Future download(File file, {String? filename}) async { - if (!isOpen) throw closed(); - - headers['Content-Disposition'] = - 'attachment; filename="${filename ?? file.path}"'; - contentType = MediaType.parse(lookupMimeType(file.path)!); - headers['content-length'] = file.lengthSync().toString(); - - if (!isBuffered) { - await file.openRead().cast>().pipe(this); - } else { - buffer!.add(file.readAsBytesSync()); - await close(); - } - } - - /// Prevents more data from being written to the response, and locks it entire from further editing. - @override - Future close() { - if (buffer is LockableBytesBuilder) { - (buffer as LockableBytesBuilder).lock(); - } - - if (_done?.isCompleted == false) _done!.complete(); - return Future.value(); - } - - /// Serializes JSON to the response. - Future json(value) => - serialize(value, contentType: MediaType('application', 'json')); - - /// Returns a JSONP response. - /// - /// You can override the [contentType] sent; by default it is `application/javascript`. - Future jsonp(value, - {String callbackName = 'callback', MediaType? contentType}) { - if (!isOpen) throw closed(); - this.contentType = contentType ?? MediaType('application', 'javascript'); - write('$callbackName(${serializer(value)})'); - return close(); - } - - /// Renders a view to the response stream, and closes the response. - Future render(String view, [Map? data]) { - if (!isOpen) throw closed(); - contentType = MediaType('text', 'html', {'charset': 'utf-8'}); - return Future.sync(() => app!.viewGenerator!( - view, - Map.from(renderParams) - ..addAll(data ?? {}))).then((content) { - write(content); - return close(); - }); - } - - /// Redirects to user to the given URL. - /// - /// [url] can be a `String`, or a `List`. - /// If it is a `List`, a URI will be constructed - /// based on the provided params. - /// - /// See [Router]#navigate for more. :) - Future redirect(url, {bool absolute = true, int? code}) { - if (!isOpen) throw closed(); - headers - ..['content-type'] = 'text/html' - ..['location'] = (url is String || url is Uri) - ? url.toString() - : app!.navigate(url as Iterable, absolute: absolute); - statusCode = code ?? 302; - write(''' - - - - Redirecting... - - - -

Currently redirecting you...

-
- Click here if you are not automatically redirected... - - - - '''); - return close(); - } - - /// Redirects to the given named [Route]. - Future redirectTo(String name, [Map? params, int? code]) async { - if (!isOpen) throw closed(); - Route? findRoute(Router r) { - for (var route in r.routes) { - if (route is SymlinkRoute) { - final m = findRoute(route.router); - - if (m != null) return m; - } else if (route.name == name) { - return route; - } - } - - return null; - } - - var matched = findRoute(app!); - - if (matched != null) { - await redirect( - matched.makeUri(params!.keys.fold>({}, (out, k) { - return out..[k.toString()] = params[k]; - })), - code: code); - return; - } - - throw ArgumentError.notNull('Route to redirect to ($name)'); - } - - /// Redirects to the given [Controller] action. - Future redirectToAction(String action, [Map? params, int? code]) { - if (!isOpen) throw closed(); - // UserController@show - var split = action.split('@'); - - if (split.length < 2) { - throw Exception( - "Controller redirects must take the form of 'Controller@action'. You gave: $action"); - } - - var controller = app!.controllers[split[0].replaceAll(_straySlashes, '')]; - - if (controller == null) { - throw Exception("Could not find a controller named '${split[0]}'"); - } - - var matched = controller.routeMappings[split[1]]; - - if (matched == null) { - throw Exception( - "Controller '${split[0]}' does not contain any action named '${split[1]}'"); - } - - final head = controller - .findExpose(app!.container.reflector)! - .path - .toString() - .replaceAll(_straySlashes, ''); - var tail = ''; - if (params != null) { - tail = matched - .makeUri(params.keys.fold>({}, (out, k) { - return out..[k.toString()] = params[k]; - })) - .replaceAll(_straySlashes, ''); - } - return redirect('$head/$tail'.replaceAll(_straySlashes, ''), code: code); - } - - /// Serializes data to the response. - Future serialize(value, {MediaType? contentType}) async { - if (!isOpen) { - throw closed(); - } - this.contentType = contentType ?? MediaType('application', 'json'); - var text = await serializer(value); - if (text.isEmpty) return true; - write(text); - await close(); - return false; - } - - /// Streams a file to this response. - /// - /// `HEAD` responses will not actually write data. - Future streamFile(File file) async { - if (!isOpen) { - throw closed(); - } - var mimeType = app!.mimeTypeResolver.lookup(file.path); - contentLength = await file.length(); - contentType = mimeType == null - ? MediaType('application', 'octet-stream') - : MediaType.parse(mimeType); - - if (correspondingRequest!.method != 'HEAD') { - return addStream(file.openRead().cast>()).then((_) => close()); - } - } - - /// Configure the response to write to an intermediate response buffer, rather than to the stream directly. - void useBuffer(); - - /// Adds a stream directly the underlying response. - /// - /// If this instance has access to a [correspondingRequest], then it will attempt to transform - /// the content using at most one of the response [encoders]. - @override - Future addStream(Stream> stream); - - @override - void addError(Object error, [StackTrace? stackTrace]) { - if (_done?.isCompleted == false) { - _done!.completeError(error, stackTrace); - } else if (_done == null) { - if (stackTrace != null) { - Zone.current.handleUncaughtError(error, stackTrace); - } else { - app?.logger.warning('[ResponseContext] stackTrace is null'); - } - } - } - - /// Writes data to the response. - @override - void write(value, {Encoding? encoding}) { - encoding ??= utf8; - - if (!isOpen && isBuffered) { - throw closed(); - } else if (!isBuffered) { - add(encoding.encode(value.toString())); - } else { - buffer!.add(encoding.encode(value.toString())); - } - } - - @override - void writeCharCode(int charCode) { - if (!isOpen && isBuffered) { - throw closed(); - } else if (!isBuffered) { - add([charCode]); - } else { - buffer!.addByte(charCode); - } - } - - @override - void writeln([Object? obj = '']) { - write(obj.toString()); - write('\r\n'); - } - - @override - void writeAll(Iterable objects, [String separator = '']) { - write(objects.join(separator)); - } -} - -abstract class LockableBytesBuilder implements BytesBuilder { - factory LockableBytesBuilder() { - return _LockableBytesBuilderImpl(); - } - - void lock(); -} - -class _LockableBytesBuilderImpl implements LockableBytesBuilder { - final BytesBuilder _buf = BytesBuilder(copy: false); - bool _closed = false; - - StateError _deny() => - StateError('Cannot modified a closed response\'s buffer.'); - - @override - void lock() { - _closed = true; - } - - @override - void add(List bytes) { - if (_closed) { - throw _deny(); - } else { - _buf.add(bytes); - } - } - - @override - void addByte(int byte) { - if (_closed) { - throw _deny(); - } else { - _buf.addByte(byte); - } - } - - @override - void clear() { - _buf.clear(); - } - - @override - bool get isEmpty => _buf.isEmpty; - - @override - bool get isNotEmpty => _buf.isNotEmpty; - - @override - int get length => _buf.length; - - @override - Uint8List takeBytes() { - return _buf.takeBytes(); - } - - @override - Uint8List toBytes() { - return _buf.toBytes(); - } -} diff --git a/packages/core/lib/src/core/routable.dart b/packages/core/lib/src/core/routable.dart deleted file mode 100644 index e4a8598..0000000 --- a/packages/core/lib/src/core/routable.dart +++ /dev/null @@ -1,140 +0,0 @@ -library angel_framework.http.routable; - -import 'dart:async'; - -import 'package:platform_container/container.dart'; -import 'package:platform_route/route.dart'; - -import '../util.dart'; -import 'hooked_service.dart'; -import 'metadata.dart'; -import 'request_context.dart'; -import 'response_context.dart'; -import 'service.dart'; - -final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); - -/// A function that receives an incoming [RequestContext] and responds to it. -typedef RequestHandler = FutureOr Function( - RequestContext req, ResponseContext res); - -/// Sequentially runs a list of [handlers] of middleware, and returns early if any does not -/// return `true`. Works well with [Router].chain. -RequestHandler chain(Iterable handlers) { - return (req, res) { - Future Function()? runPipeline; - - for (var handler in handlers) { - //if (handler == null) break; - - if (runPipeline == null) { - runPipeline = () => Future.sync(() => handler(req, res)); - } else { - var current = runPipeline; - runPipeline = () => current().then((result) => !res.isOpen - ? Future.value(result) - : req.app!.executeHandler(handler, req, res)); - } - } - - runPipeline ??= () => Future.value(); - return runPipeline(); - }; -} - -/// A routable server that can handle dynamic requests. -class Routable extends Router { - final Map _services = {}; - final Map _serviceLookups = {}; - - /// A [Map] of application-specific data that can be accessed. - /// - /// Packages like `package:angel3_configuration` populate this map - /// for you. - final Map configuration = {}; - - final Container _container; - - Routable([Reflector? reflector]) -// : _container = reflector == null ? null : Container(reflector), - : _container = Container(reflector ?? ThrowingReflector()), - super(); - - /// A [Container] used to inject dependencies. - Container get container => _container; - - void close() { - _services.clear(); - configuration.clear(); - _onService.close(); - } - - /// A set of [Service] objects that have been mapped into routes. - Map get services => _services; - - final StreamController _onService = - StreamController.broadcast(); - - /// Fired whenever a service is added to this instance. - /// - /// **NOTE**: This is a broadcast stream. - Stream get onService => _onService.stream; - - /// Retrieves the service assigned to the given path. - T? findService(Pattern path) { - return _serviceLookups.putIfAbsent(path, () { - return _services[path] ?? - _services[path.toString().replaceAll(_straySlashes, '')]; - }) as T?; - } - - /// Shorthand for finding a [Service] in a statically-typed manner. - Service? findServiceOf(Pattern path) { - return findService>(path); - } - - /// Shorthand for finding a [HookedService] in a statically-typed manner. - HookedService? findHookedService( - Pattern path) { - return findService(path) as HookedService?; - } - - @override - Route addRoute( - String method, String path, RequestHandler handler, - {Iterable middleware = const {}}) { - final handlers = []; - // Merge @Middleware declaration, if any - var reflector = _container.reflector; - if (reflector is! ThrowingReflector) { - var middlewareDeclaration = - getAnnotation(handler, _container.reflector); - if (middlewareDeclaration != null) { - handlers.addAll(middlewareDeclaration.handlers); - } - } - - final handlerSequence = []; - handlerSequence.addAll(middleware); - handlerSequence.addAll(handlers); - - return super.addRoute(method, path.toString(), handler, - middleware: handlerSequence); - } - - /// Mounts a [service] at the given [path]. - /// - /// Returns a [HookedService] that can be used to hook into - /// events dispatched by this service. - HookedService use>( - String path, T service) { - var hooked = HookedService(service); - _services[path.toString().trim().replaceAll(RegExp(r'(^/+)|(/+$)'), '')] = - hooked; - hooked.addRoutes(); - mount(path.toString(), hooked); - service.onHooked(hooked); - _onService.add(hooked); - return hooked; - } -} diff --git a/packages/core/lib/src/core/server.dart b/packages/core/lib/src/core/server.dart deleted file mode 100644 index 37c682c..0000000 --- a/packages/core/lib/src/core/server.dart +++ /dev/null @@ -1,423 +0,0 @@ -library platform_core.http.server; - -import 'dart:async'; -import 'dart:collection' show HashMap; -import 'dart:convert'; -import 'package:platform_container/container.dart'; -import 'package:platform_support/exceptions.dart'; -import 'package:platform_route/route.dart'; -import 'package:belatuk_combinator/belatuk_combinator.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:logging/logging.dart'; -import 'package:mime/mime.dart'; -import 'package:tuple/tuple.dart'; -import 'controller.dart'; -import 'env.dart'; -import 'hooked_service.dart'; -import 'request_context.dart'; -import 'response_context.dart'; -import 'routable.dart'; -import 'service.dart'; - -//final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); - -/// A function that configures an [Application] server. -typedef PlatformConfigurer = FutureOr Function(Application app); - -/// A function that asynchronously generates a view from the given path and data. -typedef ViewGenerator = FutureOr Function(String path, - [Map? data]); - -/// A function that handles error -typedef PlatformErrorHandler = dynamic Function( - PlatformHttpException e, RequestContext req, ResponseContext res); - -/// The default error handler for [Application] server -Future _defaultErrorHandler( - PlatformHttpException e, RequestContext req, ResponseContext res) async { - if (!req.accepts('text/html', strict: true) && - (req.accepts('application/json') || - req.accepts('application/javascript'))) { - await res.json(e.toJson()); - return Future.value(false); - } else { - res.contentType = MediaType('text', 'html', {'charset': 'utf8'}); - res.statusCode = e.statusCode; - res.write('${e.message}'); - res.write('

${e.message}

    '); - - for (var error in e.errors) { - res.write('
  • $error
  • '); - } - - res.write('
'); - return Future.value(false); - } -} - -/// Default ROOT level logger -Logger _defaultLogger() { - Logger logger = Logger('ROOT') - ..onRecord.listen((rec) { - if (rec.error == null) { - print(rec.message); - } - - if (rec.error != null) { - var err = rec.error; - if (err is PlatformHttpException && err.statusCode != 500) return; - print('${rec.message} \n'); - print(rec.error); - if (rec.stackTrace != null) { - print(rec.stackTrace); - } - } - }); - - return logger; -} - -/// A powerful real-time/REST/MVC server class. -class Application extends Routable { - static Future _noViewEngineConfigured(String view, [Map? data]) => - Future.value('No view engine has been configured yet.'); - - final List _children = []; - final Map< - String, - Tuple4, ParseResult, - MiddlewarePipeline>> handlerCache = HashMap(); - - Router? _flattened; - Application? _parent; - - /// A global Map of converters that can transform responses bodies. - final Map, List>> encoders = {}; - - final Map _preContained = {}; - - /// A [MimeTypeResolver] that can be used to specify the MIME types of files not known by `package:mime`. - final MimeTypeResolver mimeTypeResolver = MimeTypeResolver(); - - /// A middleware to inject a serialize on every request. - FutureOr Function(dynamic)? serializer; - - /// A [Map] of dependency data obtained via reflection. - /// - /// You may modify this [Map] yourself if you intend to avoid reflection entirely. - Map get preContained => _preContained; - - /// Returns the [flatten]ed version of this router in production. - Router get optimizedRouter => _flattened ?? this; - - /// Determines whether to allow HTTP request method overrides. - bool allowMethodOverrides = true; - - /// All child application mounted on this instance. - List get children => List.unmodifiable(_children); - - final Map _controllers = {}; - - /// A set of [Controller] objects that have been loaded into the application. - Map get controllers => _controllers; - - /// The [ProtevusEnvironment] in which the application is running. - /// - /// By default, it is automatically inferred. - final ProtevusEnvironment environment; - - /// Returns the parent instance of this application, if any. - Application? get parent => _parent; - - /// Outputs diagnostics and debug messages. - Logger _logger = _defaultLogger(); - - Logger get logger => _logger; - - /// Assign a custom logger. - /// Passing null will reset to default logger - set logger(Logger? log) { - _logger.clearListeners(); - - _logger = log ?? _defaultLogger(); - } - - /// Plug-ins to be called right before server startup. - /// - /// If the server is never started, they will never be called. - final List startupHooks = []; - - /// Plug-ins to be called right before server shutdown. - /// - /// If the server is never [close]d, they will never be called. - final List shutdownHooks = []; - - /// Always run before responses are sent. - /// - /// These will only not run if a response's `willCloseItself` is set to `true`. - final List responseFinalizers = []; - - /// A function that renders views. - /// - /// Called by [ResponseContext]@`render`. - ViewGenerator? viewGenerator = _noViewEngineConfigured; - - /// The handler currently configured to run on [PlatformHttpException]s. - PlatformErrorHandler errorHandler = _defaultErrorHandler; - - @override - Route addRoute( - String method, String path, RequestHandler handler, - {Iterable middleware = const []}) { - if (_flattened != null) { - logger.warning( - 'WARNING: You added a route ($method $path) to the router, after it had been optimized.'); - logger.warning( - 'This route will be ignored, and no requests will ever reach it.'); - } - - return super.addRoute(method, path, handler, middleware: middleware); - } - - @override - SymlinkRoute mount( - String path, Router router) { - if (_flattened != null) { - logger.warning( - 'WARNING: You added mounted a child router ($path) on the router, after it had been optimized.'); - logger.warning( - 'This route will be ignored, and no requests will ever reach it.'); - } - - if (router is Application) { - router._parent = this; - _children.add(router); - } - - return super.mount(path.toString(), router); - } - - /// Loads some base dependencies into the service container. - void bootstrapContainer() { - if (runtimeType != Application) { - container.registerSingleton(this); - } - - container.registerSingleton(this); - container.registerSingleton(this); - container.registerSingleton(this); - } - - /// Shuts down the server, and closes any open [StreamController]s. - /// - /// The server will be **COMPLETELY DEFUNCT** after this operation! - @override - Future close() { - Future.forEach(services.values, (Service service) { - service.close(); - }); - - super.close(); - viewGenerator = _noViewEngineConfigured; - _preContained.clear(); - handlerCache.clear(); - encoders.clear(); - _children.clear(); - //_parent = null; - //logger = null; - //_flattened = null; - startupHooks.clear(); - shutdownHooks.clear(); - responseFinalizers.clear(); - return Future.value(); - } - - @override - void dumpTree( - {Function(String tree)? callback, - String header = 'Dumping route tree:', - String tab = ' ', - bool showMatchers = false}) { - if (environment.isProduction) { - _flattened ??= flatten(this); - - _flattened!.dumpTree( - callback: callback, - header: header.isNotEmpty == true - ? header - : (environment.isProduction - ? 'Dumping flattened route tree:' - : 'Dumping route tree:'), - tab: tab); - } else { - super.dumpTree( - callback: callback, - header: header.isNotEmpty == true - ? header - : (environment.isProduction - ? 'Dumping flattened route tree:' - : 'Dumping route tree:'), - tab: tab); - } - } - - Future getHandlerResult(handler, RequestContext req, ResponseContext res) { - if (handler is RequestHandler) { - var result = handler(req, res); - return getHandlerResult(result, req, res); - } - - if (handler is Future) { - return handler.then((result) => getHandlerResult(result, req, res)); - } - - if (handler is Function) { - var result = runContained(handler, req, res); - return getHandlerResult(result, req, res); - } - - if (handler is Stream) { - return getHandlerResult(handler.toList(), req, res); - } - - return Future.value(handler); - } - - /// Runs some [handler]. Returns `true` if request execution should continue. - Future executeHandler( - handler, RequestContext req, ResponseContext res) { - return getHandlerResult(handler, req, res).then((result) { - if (result == null) { - return false; - } else if (result is bool) { - return result; - } else if (result != null) { - return res.serialize(result); - } else { - return res.isOpen; - } - }); - } - - /// Attempts to find a property by the given name within this application. - dynamic findProperty(key) { - if (configuration.containsKey(key)) return configuration[key]; - - //return parent != null ? parent?.findProperty(key) : null; - if (parent != null) { - return parent?.findProperty(key); - } - - return null; - } - - /// Runs several optimizations, *if* [protevusEnv.isProduction] is `true`. - /// - /// * Preprocesses all dependency injection, and eliminates the burden of reflecting handlers - /// at run-time. - /// * [flatten]s the route tree into a linear one. - /// - /// You may [force] the optimization to run, if you are not running in production. - void optimizeForProduction({bool force = false}) { - if (environment.isProduction || force == true) { - _flattened ??= flatten(this); - logger.info('Protevus is running in production mode.'); - } - } - - /// Run a function after injecting from service container. - /// If this function has been reflected before, then - /// the execution will be faster, as the injection requirements were stored beforehand. - Future runContained(Function handler, RequestContext req, ResponseContext res, - [Container? container]) { - container ??= Container(EmptyReflector()); - return Future.sync(() { - if (_preContained.containsKey(handler)) { - return handleContained(handler, _preContained[handler]!, container)( - req, res); - } - - return runReflected(handler, req, res, container); - }); - } - - /// Runs with DI, and *always* reflects. Prefer [runContained]. - Future runReflected(Function handler, RequestContext req, ResponseContext res, - [Container? container]) { - container ??= - req.container ?? res.app?.container ?? Container(EmptyReflector()); - - if (container.reflector is EmptyReflector) { - throw ArgumentError("No `reflector` passed"); - } - var h = handleContained( - handler, - _preContained[handler] = preInject(handler, container.reflector), - container); - return Future.sync(() => h(req, res)); - // return closureMirror.apply(args).reflectee; - } - - /// Applies an [PlatformConfigurer] to this instance. - Future configure(PlatformConfigurer configurer) { - return Future.sync(() => configurer(this)); - } - - /// Shorthand for using the [container] to instantiate, and then mount a [Controller]. - /// Returns the created controller. - /// - /// Just like [Container].make, in contexts without properly-reified generics (dev releases of Dart 2), - /// provide a [type] argument as well. - /// - /// If you are on `Dart >=2.0.0`, simply call `mountController()`. - Future mountController([Type? type]) { - var controller = container.make(type); - return configure(controller.configureServer).then((_) => controller); - } - - /// Shorthand for calling `all('*', handler)`. - Route fallback(RequestHandler handler) { - return all('*', handler); - } - - @override - HookedService use>( - String path, T service) { - service.app = this; - return super.use(path, service)..app = this; - } - - static const String _reflectionErrorMessage = - '${ContainerConst.defaultErrorMessage} $_reflectionInfo'; - - static const String _reflectionInfo = - 'Features like controllers, constructor dependency injection, and `ioc` require reflection, ' - 'and will not work without it.\n\n' - 'For more, see the documentation:\n' - 'https://docs.angel-dart.dev/guides/dependency-injection#enabling-dart-mirrors-or-other-reflection'; - - Application( - {Reflector reflector = - const ThrowingReflector(errorMessage: _reflectionErrorMessage), - this.environment = protevusEnv, - Logger? logger, - this.allowMethodOverrides = true, - this.serializer, - this.viewGenerator}) - : super(reflector) { - // Override default logger - if (logger != null) { - this.logger = logger; - } - - if (reflector is EmptyReflector || reflector is ThrowingReflector) { - var msg = - 'No `reflector` was passed to the Protevus constructor, so reflection will not be available.\n$_reflectionInfo'; - this.logger.warning(msg); - } - - bootstrapContainer(); - viewGenerator ??= _noViewEngineConfigured; - serializer ??= json.encode; - } -} diff --git a/packages/core/lib/src/core/service.dart b/packages/core/lib/src/core/service.dart deleted file mode 100644 index 894c96e..0000000 --- a/packages/core/lib/src/core/service.dart +++ /dev/null @@ -1,380 +0,0 @@ -library platform_core.http.service; - -import 'dart:async'; -import 'package:platform_support/exceptions.dart'; -import 'package:belatuk_merge_map/belatuk_merge_map.dart'; -import 'package:quiver/core.dart'; -import '../util.dart'; -import 'anonymous_service.dart'; -import 'hooked_service.dart' show HookedService; -import 'metadata.dart'; -import 'request_context.dart'; -import 'response_context.dart'; -import 'routable.dart'; -import 'server.dart'; - -/// Indicates how the service was accessed. -/// -/// This will be passed to the `params` object in a service method. -/// When requested on the server side, this will be null. -class Providers { - /// The transport through which the client is accessing this service. - final String via; - - const Providers(this.via); - - static const String viaRest = 'rest'; - static const String viaWebsocket = 'websocket'; - static const String viaGraphQL = 'graphql'; - - /// Represents a request via REST. - static const Providers rest = Providers(viaRest); - - /// Represents a request over WebSockets. - static const Providers websocket = Providers(viaWebsocket); - - /// Represents a request parsed from GraphQL. - static const Providers graphQL = Providers(viaGraphQL); - - @override - int get hashCode => hashObjects([via]); - - @override - bool operator ==(other) => other is Providers && other.via == via; - - Map toJson() { - return {'via': via}; - } - - @override - String toString() { - return 'via:$via'; - } -} - -/// A front-facing interface that can present data to and operate on data on behalf of the user. -/// -/// Heavily inspired by FeathersJS. <3 -class Service extends Routable { - /// A [List] of keys that services should ignore, should they see them in the query. - static const List specialQueryKeys = [ - r'$limit', - r'$sort', - 'page', - 'token' - ]; - - /// Handlers that must run to ensure this service's functionality. - List get bootstrappers => []; - - /// The [Application] app powering this service. - Application? _app; - - Application get app { - if (_app == null) { - throw ArgumentError("Protevus is not initialized"); - } - return _app!; - } - - set app(Application protevus) { - _app = protevus; - } - - bool get isAppActive => _app != null; - - /// Closes this service, including any database connections or stream controllers. - @override - void close() {} - - /// An optional [readData] function can be passed to handle non-map/non-json bodies. - Service( - {FutureOr Function(RequestContext, ResponseContext)? readData}) { - _readData = readData; - - _readData ??= (req, res) { - if (req.bodyAsObject is! Data) { - throw PlatformHttpException.badRequest( - message: - 'Invalid request body. Expected $Data; found ${req.bodyAsObject} instead.'); - } else { - return req.bodyAsObject as Data; - } - }; - } - - FutureOr Function(RequestContext, ResponseContext)? _readData; - - /// A [Function] that reads the request body and converts it into [Data]. - FutureOr Function(RequestContext, ResponseContext)? get readData => - _readData; - - /// Retrieves the first object from the result of calling [index] with the given [params]. - /// - /// If the result of [index] is `null`, OR an empty [Iterable], a 404 `HttpException` will be thrown. - /// - /// If the result is both non-null and NOT an [Iterable], it will be returned as-is. - /// - /// If the result is a non-empty [Iterable], [findOne] will return `it.first`, where `it` is the aforementioned [Iterable]. - /// - /// A custom [errorMessage] may be provided. - Future findOne( - [Map? params, - String errorMessage = 'No record was found matching the given query.']) { - return index(params).then((result) { - if (result.isEmpty) { - throw PlatformHttpException.notFound(message: errorMessage); - } else { - return result.first; - } - }); - } - - /// Retrieves all resources. - Future> index([Map? params]) { - throw PlatformHttpException.methodNotAllowed(); - } - - /// Retrieves the desired resource. - Future read(Id id, [Map? params]) { - throw PlatformHttpException.methodNotAllowed(); - } - - /// Reads multiple resources at once. - /// - /// Service implementations should override this to ensure data is fetched within a - /// single round trip. - Future> readMany(List ids, [Map? params]) { - return Future.wait(ids.map((id) => read(id, params))); - } - - /// Creates a resource. - Future create(Data data, [Map? params]) { - throw PlatformHttpException.methodNotAllowed(); - } - - /// Modifies a resource. - Future modify(Id id, Data data, [Map? params]) { - throw PlatformHttpException.methodNotAllowed(); - } - - /// Overwrites a resource. - Future update(Id id, Data data, [Map? params]) { - throw PlatformHttpException.methodNotAllowed(); - } - - /// Removes the given resource. - Future remove(Id id, [Map? params]) { - throw PlatformHttpException.methodNotAllowed(); - } - - /// Creates an [AnonymousService] that wraps over this one, and maps input and output - /// using two converter functions. - /// - /// Handy utility for handling data in a type-safe manner. - Service map(U Function(Data) encoder, Data Function(U) decoder, - {FutureOr Function(RequestContext, ResponseContext)? readData}) { - readData ??= (req, res) async { - var inner = await this.readData!(req, res)!; - return encoder(inner); - }; - - return AnonymousService( - readData: readData, - index: ([params]) { - return index(params).then((it) => it.map(encoder).toList()); - }, - read: (id, [params]) { - return read(id, params).then(encoder); - }, - create: (data, [params]) { - return create(decoder(data), params).then(encoder); - }, - modify: (id, data, [params]) { - return modify(id, decoder(data), params).then(encoder); - }, - update: (id, data, [params]) { - return update(id, decoder(data), params).then(encoder); - }, - remove: (id, [params]) { - return remove(id, params).then(encoder); - }, - ); - } - - /// Transforms an [id] (whether it is a String, num, etc.) into one acceptable by a service. - /// - /// The single type argument, [T], is used to determine how to parse the [id]. - /// - /// For example, `parseId` attempts to parse the value as a [bool]. - static T parseId(id) { - if (id == null || id == 'null') { - return 'null' as T; - //throw ArgumentError("[Service] Null is not supported"); - } else if (T == String) { - return id.toString() as T; - } else if (T == int) { - return int.parse(id.toString()) as T; - } else if (T == bool) { - return (id == true || id.toString() == 'true') as T; - } else if (T == double) { - return double.parse(id.toString()) as T; - } else if (T == num) { - return num.parse(id.toString()) as T; - } else { - return id as T; - } - } - - /// Generates RESTful routes pointing to this class's methods. - void addRoutes([Service? service]) { - _addRoutesInner(service ?? this, bootstrappers); - } - - void _addRoutesInner(Service service, Iterable handlerss) { - var restProvider = {'provider': Providers.rest}; - var handlers = List.from(handlerss); - - // Add global middleware if declared on the instance itself - var before = getAnnotation(service, app.container.reflector); - - if (before != null) handlers.addAll(before.handlers); - - var indexMiddleware = - getAnnotation(service.index, app.container.reflector); - get('/', (req, res) { - return index(mergeMap([ - {'query': req.queryParameters}, - restProvider, - req.serviceParams - ])); - }, middleware: [ - ...handlers, - ...(indexMiddleware == null) ? [] : indexMiddleware.handlers.toList() - ]); - - var createMiddleware = - getAnnotation(service.create, app.container.reflector); - post('/', (req, ResponseContext res) { - return req.parseBody().then((_) async { - return await create( - (await readData!(req, res))!, - mergeMap([ - {'query': req.queryParameters}, - restProvider, - req.serviceParams - ])).then((r) { - res.statusCode = 201; - return r; - }); - }); - }, middleware: [ - ...handlers, - ...(createMiddleware == null) ? [] : createMiddleware.handlers.toList() - ]); - - var readMiddleware = - getAnnotation(service.read, app.container.reflector); - - get('/:id', (req, res) { - return read( - parseId(req.params['id']), - mergeMap([ - {'query': req.queryParameters}, - restProvider, - req.serviceParams - ])); - }, middleware: [ - ...handlers, - ...(readMiddleware == null) ? [] : readMiddleware.handlers.toList() - ]); - - var modifyMiddleware = - getAnnotation(service.modify, app.container.reflector); - - patch('/:id', (req, res) { - return req.parseBody().then((_) async { - return await modify( - parseId(req.params['id']), - (await readData!(req, res))!, - mergeMap([ - {'query': req.queryParameters}, - restProvider, - req.serviceParams - ])); - }); - }, middleware: [ - ...handlers, - ...(modifyMiddleware == null) ? [] : modifyMiddleware.handlers.toList() - ]); - - var updateMiddleware = - getAnnotation(service.update, app.container.reflector); - post('/:id', (req, res) { - return req.parseBody().then((_) async { - return await update( - parseId(req.params['id']), - (await readData!(req, res))!, - mergeMap([ - {'query': req.queryParameters}, - restProvider, - req.serviceParams - ])); - }); - }, middleware: [ - ...handlers, - ...(updateMiddleware == null) ? [] : updateMiddleware.handlers.toList() - ]); - - put('/:id', (req, res) { - return req.parseBody().then((_) async { - return await update( - parseId(req.params['id']), - (await readData!(req, res))!, - mergeMap([ - {'query': req.queryParameters}, - restProvider, - req.serviceParams - ])); - }); - }, middleware: [ - ...handlers, - ...(updateMiddleware == null) ? [] : updateMiddleware.handlers.toList() - ]); - - var removeMiddleware = - getAnnotation(service.remove, app.container.reflector); - delete('/', (req, res) { - return remove( - '' as Id, - mergeMap([ - {'query': req.queryParameters}, - restProvider, - req.serviceParams - ])); - }, middleware: [ - ...handlers, - ...(removeMiddleware == null) ? [] : removeMiddleware.handlers.toList() - ]); - - delete('/:id', (req, res) { - return remove( - parseId(req.params['id']), - mergeMap([ - {'query': req.queryParameters}, - restProvider, - req.serviceParams - ])); - }, middleware: [ - ...handlers, - ...(removeMiddleware == null) ? [] : removeMiddleware.handlers.toList() - ]); - - // REST compliance - put('/', (req, res) => throw PlatformHttpException.notFound()); - patch('/', (req, res) => throw PlatformHttpException.notFound()); - } - - /// Invoked when this service is wrapped within a [HookedService]. - void onHooked(HookedService hookedService) {} -} diff --git a/packages/core/lib/src/fast_name_from_symbol.dart b/packages/core/lib/src/fast_name_from_symbol.dart deleted file mode 100644 index 6005ff1..0000000 --- a/packages/core/lib/src/fast_name_from_symbol.dart +++ /dev/null @@ -1,10 +0,0 @@ -final Map _cache = {}; - -String fastNameFromSymbol(Symbol s) { - return _cache.putIfAbsent(s, () { - var str = s.toString(); - var open = str.indexOf('"'); - var close = str.lastIndexOf('"'); - return str.substring(open + 1, close); - }); -} diff --git a/packages/core/lib/src/http/http.dart b/packages/core/lib/src/http/http.dart deleted file mode 100644 index 27e7f73..0000000 --- a/packages/core/lib/src/http/http.dart +++ /dev/null @@ -1,19 +0,0 @@ -/// Various libraries useful for creating highly-extensible servers. -library platform_core.http; - -import 'dart:async'; -import 'dart:io'; -export 'protevus_http.dart'; -export 'http_request_context.dart'; -export 'http_response_context.dart'; - -/// Boots a shared server instance. Use this if launching multiple isolates. -Future startShared(address, int port) => - HttpServer.bind(address ?? '127.0.0.1', port, shared: true); - -Future Function(dynamic, int) startSharedSecure( - SecurityContext securityContext) { - return (address, int port) => HttpServer.bindSecure( - address ?? '127.0.0.1', port, securityContext, - shared: true); -} diff --git a/packages/core/lib/src/http/http_request_context.dart b/packages/core/lib/src/http/http_request_context.dart deleted file mode 100644 index bbc0cb5..0000000 --- a/packages/core/lib/src/http/http_request_context.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:platform_container/container.dart'; -import 'package:http_parser/http_parser.dart'; - -import '../core/core.dart'; - -/// An implementation of [RequestContext] that wraps a [HttpRequest]. -class HttpRequestContext extends RequestContext { - Container? _container; - MediaType _contentType = MediaType('text', 'plain'); - HttpRequest? _io; - String? _override; - String _path = ''; - - @override - Container? get container => _container; - - @override - MediaType get contentType { - return _contentType; - } - - @override - List get cookies { - return rawRequest?.cookies ?? []; - } - - @override - HttpHeaders? get headers { - return rawRequest?.headers; - } - - @override - String get hostname { - return rawRequest?.headers.value('host') ?? 'localhost'; - } - - /// The underlying [HttpRequest] instance underneath this context. - @override - HttpRequest? get rawRequest => _io; - - @override - Stream>? get body => _io; - - @override - String get method { - return _override ?? originalMethod; - } - - @override - String get originalMethod { - return rawRequest?.method ?? ''; - } - - @override - String get path { - return _path; - } - - @override - InternetAddress get remoteAddress { - return rawRequest?.connectionInfo?.remoteAddress ?? - InternetAddress("127.0.0.1"); - } - - @override - HttpSession? get session { - return rawRequest?.session; - } - - @override - Uri get uri { - return rawRequest?.uri ?? Uri(); - } - - /// Magically transforms an [HttpRequest] into a [RequestContext]. - static Future from( - HttpRequest request, Application app, String path) { - var ctx = HttpRequestContext().._container = app.container.createChild(); - - var override = request.method; - - if (app.allowMethodOverrides == true) { - override = - request.headers.value('x-http-method-override')?.toUpperCase() ?? - request.method; - } - - ctx.app = app; - ctx._contentType = request.headers.contentType == null - ? MediaType('text', 'plain') - : MediaType.parse(request.headers.contentType.toString()); - ctx._override = override; - ctx._path = path; - ctx._io = request; - - return Future.value(ctx); - } - - @override - Future close() { - //_contentType = null; - _io = null; - _override = null; - //_path = null; - return super.close(); - } -} diff --git a/packages/core/lib/src/http/http_response_context.dart b/packages/core/lib/src/http/http_response_context.dart deleted file mode 100644 index ed3d06b..0000000 --- a/packages/core/lib/src/http/http_response_context.dart +++ /dev/null @@ -1,220 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' hide BytesBuilder; -import 'dart:typed_data' show BytesBuilder; -import 'package:http_parser/http_parser.dart'; - -import '../core/core.dart'; -import 'http_request_context.dart'; - -/// An implementation of [ResponseContext] that abstracts over an [HttpResponse]. -class HttpResponseContext extends ResponseContext { - /// The underlying [HttpResponse] under this instance. - @override - final HttpResponse rawResponse; - - LockableBytesBuilder? _buffer; - - final HttpRequestContext? _correspondingRequest; - bool _isDetached = false, _isClosed = false, _streamInitialized = false; - - HttpResponseContext(this.rawResponse, Application? app, - [this._correspondingRequest]) { - this.app = app; - } - - @override - HttpResponse detach() { - _isDetached = true; - return rawResponse; - } - - @override - RequestContext? get correspondingRequest { - return _correspondingRequest; - } - - @override - bool get isOpen { - return !_isClosed && !_isDetached; - } - - @override - bool get isBuffered => _buffer != null; - - @override - BytesBuilder? get buffer => _buffer; - - @override - void addError(Object error, [StackTrace? stackTrace]) { - rawResponse.addError(error, stackTrace); - super.addError(error, stackTrace); - } - - @override - void useBuffer() { - _buffer = LockableBytesBuilder(); - } - - Iterable? __allowedEncodings; - - Iterable? get _allowedEncodings { - return __allowedEncodings ??= correspondingRequest?.headers - ?.value('accept-encoding') - ?.split(',') - .map((s) => s.trim()) - .where((s) => s.isNotEmpty) - .map((str) { - // Ignore quality specifications in accept-encoding - // ex. gzip;q=0.8 - if (!str.contains(';')) return str; - return str.split(';')[0]; - }); - } - - @override - set contentType(MediaType value) { - super.contentType = value; - if (!_streamInitialized) { - rawResponse.headers.contentType = - ContentType(value.type, value.subtype, parameters: value.parameters); - } - } - - bool _openStream() { - if (!_streamInitialized) { - // If this is the first stream added to this response, - // then add headers, status code, etc. - rawResponse - ..statusCode = statusCode - ..cookies.addAll(cookies); - headers.forEach(rawResponse.headers.set); - - rawResponse.headers.date = DateTime.now(); - - if (headers.containsKey('content-length')) { - rawResponse.contentLength = int.tryParse(headers['content-length']!) ?? - rawResponse.contentLength; - } - - rawResponse.headers.contentType = ContentType( - contentType.type, contentType.subtype, - charset: contentType.parameters['charset'], - parameters: contentType.parameters); - - if (encoders.isNotEmpty && correspondingRequest != null) { - if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings!) { - Converter, List>? encoder; - var key = encodingName; - - if (encoders.containsKey(encodingName)) { - encoder = encoders[encodingName]; - } else if (encodingName == '*') { - encoder = encoders[key = encoders.keys.first]; - } - - if (encoder != null) { - rawResponse.headers.set('content-encoding', key); - break; - } - } - } - } - - //_isClosed = true; - return _streamInitialized = true; - } - - return false; - } - - @override - Future addStream(Stream> stream) { - if (_isClosed && isBuffered) throw ResponseContext.closed(); - _openStream(); - - var output = stream; - - if (encoders.isNotEmpty && correspondingRequest != null) { - if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings!) { - Converter, List>? encoder; - var key = encodingName; - - if (encoders.containsKey(encodingName)) { - encoder = encoders[encodingName]; - } else if (encodingName == '*') { - encoder = encoders[key = encoders.keys.first]; - } - - if (encoder != null) { - output = encoders[key]!.bind(output); - break; - } - } - } - } - - return rawResponse.addStream(output); - } - - @override - void add(List data) { - if (_isClosed && isBuffered) { - throw ResponseContext.closed(); - } else if (!isBuffered) { - if (!_isClosed) { - _openStream(); - - if (encoders.isNotEmpty && correspondingRequest != null) { - if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings!) { - Converter, List>? encoder; - var key = encodingName; - - if (encoders.containsKey(encodingName)) { - encoder = encoders[encodingName]; - } else if (encodingName == '*') { - encoder = encoders[key = encoders.keys.first]; - } - - if (encoder != null) { - data = encoders[key]!.convert(data); - break; - } - } - } - } - - rawResponse.add(data); - } - } else { - buffer!.add(data); - } - } - - @override - Future close() { - if (!_isDetached) { - if (!_isClosed) { - if (!isBuffered) { - try { - _openStream(); - rawResponse.close(); - } catch (_) { - // This only seems to occur on `MockHttpRequest`, but - // this try/catch prevents a crash. - } - } else { - _buffer!.lock(); - } - - _isClosed = true; - } - - super.close(); - } - return Future.value(); - } -} diff --git a/packages/core/lib/src/http/protevus_http.dart b/packages/core/lib/src/http/protevus_http.dart deleted file mode 100644 index bcc9bc5..0000000 --- a/packages/core/lib/src/http/protevus_http.dart +++ /dev/null @@ -1,144 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' - show - Cookie, - HttpRequest, - HttpResponse, - HttpServer, - Platform, - SecurityContext; -import 'package:platform_core/core.dart'; -import 'http_request_context.dart'; -import 'http_response_context.dart'; - -final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); - -typedef ServerGeneratorType = Future Function(dynamic, int); - -/// Adapts `dart:io`'s [HttpServer] to serve Protevus. -class PlatformHttp extends Driver { - @override - Uri get uri { - return Uri( - scheme: 'http', host: server?.address.address, port: server?.port); - } - - PlatformHttp._(super.app, super.serverGenerator, bool useZone) - : super(useZone: useZone); - - factory PlatformHttp(Application app, {bool useZone = true}) { - return PlatformHttp._(app, HttpServer.bind, useZone); - } - - /// An instance mounted on a server started by the [serverGenerator]. - factory PlatformHttp.custom( - Application app, ServerGeneratorType serverGenerator, - {bool useZone = true, Map headers = const {}}) { - return PlatformHttp._(app, serverGenerator, useZone); - } - - factory PlatformHttp.fromSecurityContext( - Application app, SecurityContext context, - {bool useZone = true}) { - return PlatformHttp._(app, (address, int port) { - return HttpServer.bindSecure(address, port, context); - }, useZone); - } - - /// Creates an HTTPS server. - /// - /// Provide paths to a certificate chain and server key (both .pem). - /// If no password is provided, a random one will be generated upon running - /// the server. - factory PlatformHttp.secure( - Application app, String certificateChainPath, String serverKeyPath, - {String? password, bool useZone = true}) { - var certificateChain = - Platform.script.resolve(certificateChainPath).toFilePath(); - var serverKey = Platform.script.resolve(serverKeyPath).toFilePath(); - var serverContext = SecurityContext(); - serverContext.useCertificateChain(certificateChain, password: password); - serverContext.usePrivateKey(serverKey, password: password); - - return PlatformHttp.fromSecurityContext(app, serverContext, - useZone: useZone); - } - - Future handleRequest(HttpRequest request) => - handleRawRequest(request, request.response); - - @override - void addCookies(HttpResponse response, Iterable cookies) => - response.cookies.addAll(cookies); - - @override - Future close() async { - return await super.close(); - } - - /// Remove headers from HTTP Response - void removeResponseHeader(Map headers) { - headers.forEach((key, value) { - server?.defaultResponseHeaders.remove(key, value); - }); - } - - /// Add headers to HTTP Response - void addResponseHeader(Map headers) { - headers.forEach((key, value) { - server?.defaultResponseHeaders.add(key, value); - }); - } - - @override - Future closeResponse(HttpResponse response) => response.close(); - - @override - Future createRequestContext( - HttpRequest request, HttpResponse response) { - var path = request.uri.path.replaceAll(_straySlashes, ''); - if (path.isEmpty) path = '/'; - return HttpRequestContext.from(request, app, path); - } - - @override - Future createResponseContext( - HttpRequest request, HttpResponse response, - [HttpRequestContext? correspondingRequest]) { - var context = HttpResponseContext(response, app, correspondingRequest); - context.serializer = (app.serializer ?? json.encode); - context.encoders.addAll(app.encoders); - return Future.value(context); - } - - @override - Stream createResponseStreamFromRawRequest( - HttpRequest request) => - Stream.fromIterable([request.response]); - - @override - void setChunkedEncoding(HttpResponse response, bool value) => - response.headers.chunkedTransferEncoding = value; - - @override - void setContentLength(HttpResponse response, int length) => - response.headers.contentLength = length; - - @override - void setHeader(HttpResponse response, String key, String value) => - response.headers.set(key, value); - - @override - void setStatusCode(HttpResponse response, int value) => - response.statusCode = value; - - @override - void writeStringToResponse(HttpResponse response, String value) => - response.write(value); - - @override - void writeToResponse(HttpResponse response, List data) => - response.add(data); -} diff --git a/packages/core/lib/src/http2/http2_request_context.dart b/packages/core/lib/src/http2/http2_request_context.dart deleted file mode 100644 index 8f38a3d..0000000 --- a/packages/core/lib/src/http2/http2_request_context.dart +++ /dev/null @@ -1,186 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:platform_container/container.dart'; -import 'package:platform_core/core.dart'; -import 'package:collection/collection.dart' show IterableExtension; -import 'package:http2/transport.dart'; -import 'package:platform_testing/http.dart'; -import 'package:uuid/uuid.dart'; - -final RegExp _comma = RegExp(r',\s*'); -final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); - -class Http2RequestContext extends RequestContext { - final StreamController> _body = StreamController(); - @override - final Container container; - List _cookies = []; - HttpHeaders? _headers; - String? _method, _override, _path; - late Socket _socket; - ServerTransportStream? _stream; - Uri? _uri; - HttpSession? _session; - - Http2RequestContext._(this.container); - - @override - Stream> get body => _body.stream; - - static Future from( - ServerTransportStream stream, - Socket socket, - Application app, - Map sessions, - Uuid uuid) { - var c = Completer(); - var req = Http2RequestContext._(app.container.createChild()) - ..app = app - .._socket = socket - .._stream = stream; - - var headers = req._headers = MockHttpHeaders(); - // String scheme = 'https', host = socket.address.address, path = ''; - var uri = - Uri(scheme: 'https', host: socket.address.address, port: socket.port); - var cookies = []; - - void finalize() { - req - .._cookies = List.unmodifiable(cookies) - .._uri = uri; - if (!c.isCompleted) c.complete(req); - } - - void parseHost(String value) { - var inUri = Uri.tryParse(value); - if (inUri == null) return; - // if (uri == null || uri.scheme == 'localhost') return; - - if (inUri.hasScheme) uri = uri.replace(scheme: inUri.scheme); - - if (inUri.hasAuthority) { - uri = uri.replace(host: inUri.host, userInfo: inUri.userInfo); - } - - if (inUri.hasPort) uri = uri.replace(port: inUri.port); - } - - stream.incomingMessages.listen((msg) { - if (msg is DataStreamMessage) { - finalize(); - req._body.add(msg.bytes); - } else if (msg is HeadersStreamMessage) { - for (var header in msg.headers) { - var name = ascii.decode(header.name).toLowerCase(); - var value = Uri.decodeComponent(ascii.decode(header.value)); - - switch (name) { - case ':method': - req._method = value; - break; - case ':path': - var inUri = Uri.parse(value); - uri = uri.replace(path: inUri.path); - if (inUri.hasQuery) uri = uri.replace(query: inUri.query); - var path = uri.path.replaceAll(_straySlashes, ''); - req._path = path; - if (path.isEmpty) req._path = '/'; - break; - case ':scheme': - uri = uri.replace(scheme: value); - break; - case ':authority': - parseHost(value); - break; - case 'cookie': - var cookieStrings = value.split(';').map((s) => s.trim()); - - for (var cookieString in cookieStrings) { - try { - cookies.add(Cookie.fromSetCookieValue(cookieString)); - } catch (_) { - // Ignore malformed cookies, and just don't add them to the container. - } - } - break; - default: - var name = ascii.decode(header.name).toLowerCase(); - - if (name == 'host') { - parseHost(value); - } - - headers.add(name, value.split(_comma)); - break; - } - } - - if (msg.endStream) finalize(); - } - }, onDone: () { - finalize(); - req._body.close(); - }, cancelOnError: true, onError: c.completeError); - - // Apply session - var dartSessId = cookies.firstWhereOrNull((c) => c.name == 'DARTSESSID'); - - dartSessId ??= Cookie('DARTSESSID', uuid.v4()); - - req._session = sessions.putIfAbsent( - dartSessId.value, - () => MockHttpSession(id: dartSessId!.value), - ); - - return c.future; - } - - @override - List get cookies => _cookies; - - /// The underlying HTTP/2 [ServerTransportStream]. - ServerTransportStream? get stream => _stream; - - @override - Uri? get uri => _uri; - - @override - HttpSession? get session { - return _session; - } - - @override - InternetAddress get remoteAddress => _socket.remoteAddress; - - @override - String get path { - return _path ?? ''; - } - - @override - String get originalMethod { - return _method ?? 'GET'; - } - - @override - String get method { - return _override ?? _method ?? 'GET'; - } - - @override - String get hostname => _headers?.value('host') ?? 'localhost'; - - @override - HttpHeaders? get headers => _headers; - - @override - Future close() { - _body.close(); - return super.close(); - } - - @override - ServerTransportStream? get rawRequest => _stream; -} diff --git a/packages/core/lib/src/http2/http2_response_context.dart b/packages/core/lib/src/http2/http2_response_context.dart deleted file mode 100644 index 7e164c6..0000000 --- a/packages/core/lib/src/http2/http2_response_context.dart +++ /dev/null @@ -1,233 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' hide BytesBuilder; -import 'dart:typed_data'; -import 'package:platform_core/core.dart' hide Header; -import 'package:http2/transport.dart'; -import 'http2_request_context.dart'; - -class Http2ResponseContext extends ResponseContext { - final ServerTransportStream stream; - - @override - ServerTransportStream get rawResponse => stream; - - LockableBytesBuilder? _buffer; - - final Http2RequestContext? _req; - - bool _isDetached = false, - _isClosed = false, - _streamInitialized = false, - _isPush = false; - - Uri? _targetUri; - - Http2ResponseContext(Application? app, this.stream, this._req) { - this.app = app; - _targetUri = _req?.uri; - } - - final List _pushes = []; - - /// Returns `true` if an attempt to [push] a resource will succeed. - /// - /// See [ServerTransportStream].`push`. - bool get canPush => stream.canPush; - - /// Returns a [List] of all resources that have [push]ed to the client. - List get pushes => List.unmodifiable(_pushes); - - @override - ServerTransportStream detach() { - _isDetached = true; - return stream; - } - - @override - RequestContext? get correspondingRequest => _req; - - Uri? get targetUri => _targetUri; - - @override - bool get isOpen { - return !_isClosed && !_isDetached; - } - - @override - bool get isBuffered => _buffer != null; - - @override - BytesBuilder? get buffer => _buffer; - - // @override - // void addError(Object error, [StackTrace? stackTrace]) { - // super.addError(error, stackTrace); - // } - - @override - void useBuffer() { - _buffer = LockableBytesBuilder(); - } - - /// Write headers, status, etc. to the underlying [stream]. - bool _openStream() { - if (_isPush || _streamInitialized) return false; - - var headers =
[ - Header.ascii(':status', statusCode.toString()), - ]; - - if (encoders.isNotEmpty && correspondingRequest != null) { - if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings!) { - Converter, List>? encoder; - var key = encodingName; - - if (encoders.containsKey(encodingName)) { - encoder = encoders[encodingName]; - } else if (encodingName == '*') { - encoder = encoders[key = encoders.keys.first]; - } - - if (encoder != null) { - this.headers['content-encoding'] = key; - break; - } - } - } - } - - // Add all normal headers - for (var key in this.headers.keys) { - headers.add(Header.ascii(key.toLowerCase(), this.headers[key]!)); - } - - // Persist session ID - cookies.add(Cookie('DARTSESSID', _req!.session!.id)); - - // Send all cookies - for (var cookie in cookies) { - headers.add(Header.ascii('set-cookie', cookie.toString())); - } - - stream.sendHeaders(headers); - return _streamInitialized = true; - } - - Iterable? __allowedEncodings; - - Iterable? get _allowedEncodings { - return __allowedEncodings ??= correspondingRequest?.headers - ?.value('accept-encoding') - ?.split(',') - .map((s) => s.trim()) - .where((s) => s.isNotEmpty) - .map((str) { - // Ignore quality specifications in accept-encoding - // ex. gzip;q=0.8 - if (!str.contains(';')) return str; - return str.split(';')[0]; - }); - } - - @override - Future addStream(Stream> stream) { - if (!isOpen && isBuffered) throw ResponseContext.closed(); - _openStream(); - - var output = stream; - - if (encoders.isNotEmpty && correspondingRequest != null) { - if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings!) { - Converter, List>? encoder; - var key = encodingName; - - if (encoders.containsKey(encodingName)) { - encoder = encoders[encodingName]; - } else if (encodingName == '*') { - encoder = encoders[key = encoders.keys.first]; - } - - if (encoder != null) { - output = encoders[key]!.bind(output); - break; - } - } - } - } - - return output.forEach(this.stream.sendData); - } - - @override - void add(List data) { - if (!isOpen && isBuffered) { - throw ResponseContext.closed(); - } else if (!isBuffered) { - _openStream(); - - if (!_isClosed) { - if (encoders.isNotEmpty && correspondingRequest != null) { - if (_allowedEncodings != null) { - for (var encodingName in _allowedEncodings!) { - Converter, List>? encoder; - var key = encodingName; - - if (encoders.containsKey(encodingName)) { - encoder = encoders[encodingName]; - } else if (encodingName == '*') { - encoder = encoders[key = encoders.keys.first]; - } - - if (encoder != null) { - data = encoders[key]!.convert(data); - break; - } - } - } - } - - stream.sendData(data); - } - } else { - buffer!.add(data); - } - } - - @override - Future close() async { - if (!_isDetached && !_isClosed && !isBuffered) { - _openStream(); - await stream.outgoingMessages.close(); - } - - _isClosed = true; - await super.close(); - } - - /// Pushes a resource to the client. - Http2ResponseContext push(String path, - {Map headers = const {}, String method = 'GET'}) { - var targetUri = _req!.uri!.replace(path: path); - - var h =
[ - Header.ascii(':authority', targetUri.authority), - Header.ascii(':method', method), - Header.ascii(':path', targetUri.path), - Header.ascii(':scheme', targetUri.scheme), - ]; - - for (var key in headers.keys) { - h.add(Header.ascii(key, headers[key]!)); - } - - var s = stream.push(h); - var r = Http2ResponseContext(app, s, _req) - .._isPush = true - .._targetUri = targetUri; - _pushes.add(r); - return r; - } -} diff --git a/packages/core/lib/src/http2/protevus_http2.dart b/packages/core/lib/src/http2/protevus_http2.dart deleted file mode 100644 index cd12b59..0000000 --- a/packages/core/lib/src/http2/protevus_http2.dart +++ /dev/null @@ -1,243 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:platform_core/core.dart' hide Header; -import 'package:platform_core/http.dart'; -import 'package:http2/transport.dart'; -import 'package:platform_testing/http.dart'; -import 'http2_request_context.dart'; -import 'http2_response_context.dart'; -import 'package:uuid/uuid.dart'; - -/// Boots a shared server instance. Use this if launching multiple isolates. -Future startSharedHttp2( - address, int port, SecurityContext ctx) { - return SecureServerSocket.bind(address, port, ctx, shared: true); -} - -/// Adapts `package:http2`'s [ServerTransportConnection] to serve Protevus. -class PlatformHttp2 extends Driver { - final ServerSettings? settings; - late PlatformHttp _http; - final StreamController _onHttp1 = StreamController(); - final Map _sessions = {}; - final Uuid _uuid = Uuid(); - _ProtevusHttp2ServerSocket? _artificial; - - SecureServerSocket? get socket => _artificial; - - PlatformHttp2._( - Application app, - Future Function(dynamic, int) serverGenerator, - bool useZone, - bool allowHttp1, - this.settings) - : super( - app, - serverGenerator, - useZone: useZone, - ) { - if (allowHttp1) { - _http = PlatformHttp(app, useZone: useZone); - onHttp1.listen(_http.handleRequest); - } - } - - factory PlatformHttp2(Application app, SecurityContext securityContext, - {bool useZone = true, - bool allowHttp1 = false, - ServerSettings? settings}) { - return PlatformHttp2.custom(app, securityContext, SecureServerSocket.bind, - allowHttp1: allowHttp1, settings: settings); - } - - factory PlatformHttp2.custom( - Application app, - SecurityContext ctx, - Future Function( - InternetAddress? address, int port, SecurityContext ctx) - serverGenerator, - {bool useZone = true, - bool allowHttp1 = false, - ServerSettings? settings}) { - return PlatformHttp2._(app, (address, port) { - var addr = address is InternetAddress - ? address - : InternetAddress(address.toString()); - return Future.sync(() => serverGenerator(addr, port, ctx)); - }, useZone, allowHttp1, settings); - } - - /// Fires when an HTTP/1.x request is received. - Stream get onHttp1 => _onHttp1.stream; - - @override - Future generateServer([address, int? port]) async { - var s = await serverGenerator(address ?? '127.0.0.1', port ?? 0); - return _artificial = _ProtevusHttp2ServerSocket(s, this); - } - - @override - Future close() async { - await _artificial?.close(); - await _http.close(); - return await super.close(); - } - - @override - void addCookies(ServerTransportStream response, Iterable cookies) { - var headers = - cookies.map((cookie) => Header.ascii('set-cookie', cookie.toString())); - response.sendHeaders(headers.toList()); - } - - @override - Future closeResponse(ServerTransportStream response) { - response.terminate(); - return Future.value(); - } - - @override - Future createRequestContext( - Socket request, ServerTransportStream response) { - return Http2RequestContext.from(response, request, app, _sessions, _uuid); - } - - @override - Future createResponseContext( - Socket request, ServerTransportStream response, - [Http2RequestContext? correspondingRequest]) async { - return Http2ResponseContext(app, response, correspondingRequest) - ..encoders.addAll(app.encoders); - } - - @override - Stream createResponseStreamFromRawRequest( - Socket request) { - var connection = - ServerTransportConnection.viaSocket(request, settings: settings); - return connection.incomingStreams; - } - - @override - void setChunkedEncoding(ServerTransportStream response, bool value) { - // Do nothing in HTTP/2 - } - - @override - void setContentLength(ServerTransportStream response, int length) { - setHeader(response, 'content-length', length.toString()); - } - - @override - void setHeader(ServerTransportStream response, String key, String? value) { - response.sendHeaders([Header.ascii(key, value!)]); - } - - @override - void setStatusCode(ServerTransportStream response, int value) { - response.sendHeaders([Header.ascii(':status', value.toString())]); - } - - @override - Uri get uri => Uri( - scheme: 'https', - host: server?.address.address, - port: server?.port != 443 ? server?.port : null); - - @override - void writeStringToResponse(ServerTransportStream response, String value) { - writeToResponse(response, utf8.encode(value)); - } - - @override - void writeToResponse(ServerTransportStream response, List data) { - response.sendData(data); - } -} - -class _FakeServerSocket extends Stream implements ServerSocket { - final _ProtevusHttp2ServerSocket protevus; - final _ctrl = StreamController(); - - _FakeServerSocket(this.protevus); - - @override - InternetAddress get address => protevus.address; - - @override - Future close() async { - await (_ctrl.close()); - return this; - } - - @override - int get port => protevus.port; - - @override - StreamSubscription listen(void Function(Socket event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - return _ctrl.stream.listen(onData, - cancelOnError: cancelOnError, onError: onError, onDone: onDone); - } -} - -class _ProtevusHttp2ServerSocket extends Stream - implements SecureServerSocket { - final SecureServerSocket socket; - final PlatformHttp2 driver; - final _ctrl = StreamController(); - late _FakeServerSocket _fake; - StreamSubscription? _sub; - - _ProtevusHttp2ServerSocket(this.socket, this.driver) { - _fake = _FakeServerSocket(this); - HttpServer.listenOn(_fake).pipe(driver._onHttp1); - _sub = socket.listen( - (socket) { - if (socket.selectedProtocol == null || - socket.selectedProtocol == 'http/1.0' || - socket.selectedProtocol == 'http/1.1') { - _fake._ctrl.add(socket); - } else if (socket.selectedProtocol == 'h2' || - socket.selectedProtocol == 'h2-14') { - _ctrl.add(socket); - } else { - socket.destroy(); - throw Exception( - 'PlatformHttp2 does not support ${socket.selectedProtocol} as an ALPN protocol.'); - } - }, - onDone: _ctrl.close, - onError: (e, st) { - driver.app.logger.warning( - 'HTTP/2 incoming connection failure: ', e, st as StackTrace); - }, - ); - } - - @override - InternetAddress get address => socket.address; - - @override - int get port => socket.port; - - @override - Future close() { - _sub?.cancel(); - _fake.close(); - _ctrl.close(); - return socket.close(); - } - - @override - StreamSubscription listen( - void Function(SecureSocket event)? onData, - {Function? onError, - void Function()? onDone, - bool? cancelOnError}) { - return _ctrl.stream.listen(onData, - cancelOnError: cancelOnError, onError: onError, onDone: onDone); - } -} diff --git a/packages/core/lib/src/safe_stream_controller.dart b/packages/core/lib/src/safe_stream_controller.dart deleted file mode 100644 index fc1643f..0000000 --- a/packages/core/lib/src/safe_stream_controller.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'dart:async'; - -typedef _InitCallback = void Function(); - -/// A [StreamController] boilerplate that prevents memory leaks. -abstract class SafeCtrl { - factory SafeCtrl() => _SingleSafeCtrl(); - - factory SafeCtrl.broadcast() => _BroadcastSafeCtrl(); - - Stream get stream; - - void add(T event); - - void addError(error, [StackTrace? stackTrace]); - - Future close(); - - void whenInitialized(void Function() callback); -} - -class _SingleSafeCtrl implements SafeCtrl { - late StreamController _stream; - bool _hasListener = false, _initialized = false; - _InitCallback? _initializer; - - _SingleSafeCtrl() { - _stream = StreamController(onListen: () { - _hasListener = true; - - if (!_initialized && _initializer != null) { - _initializer!(); - _initialized = true; - } - }, onPause: () { - _hasListener = false; - }, onResume: () { - _hasListener = true; - }, onCancel: () { - _hasListener = false; - }); - } - - @override - Stream get stream => _stream.stream; - - @override - void add(T event) { - if (_hasListener) _stream.add(event); - } - - @override - void addError(error, [StackTrace? stackTrace]) { - if (_hasListener) _stream.addError(error as Object, stackTrace); - } - - @override - Future close() { - return _stream.close(); - } - - @override - void whenInitialized(void Function() callback) { - if (!_initialized) { - if (!_hasListener) { - _initializer = callback; - } else { - _initializer!(); - _initialized = true; - } - } - } -} - -class _BroadcastSafeCtrl implements SafeCtrl { - late StreamController _stream; - int _listeners = 0; - bool _initialized = false; - _InitCallback? _initializer; - - _BroadcastSafeCtrl() { - _stream = StreamController.broadcast(onListen: () { - _listeners++; - - if (!_initialized && _initializer != null) { - _initializer!(); - _initialized = true; - } - }, onCancel: () { - _listeners--; - }); - } - - @override - Stream get stream => _stream.stream; - - @override - void add(T event) { - if (_listeners > 0) _stream.add(event); - } - - @override - void addError(error, [StackTrace? stackTrace]) { - if (_listeners > 0) _stream.addError(error as Object, stackTrace); - } - - @override - Future close() { - return _stream.close(); - } - - @override - void whenInitialized(void Function() callback) { - if (!_initialized) { - if (_listeners <= 0) { - _initializer = callback; - } else { - _initializer!(); - _initialized = true; - } - } - } -} diff --git a/packages/core/lib/src/util.dart b/packages/core/lib/src/util.dart deleted file mode 100644 index 53db9a0..0000000 --- a/packages/core/lib/src/util.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:platform_container/container.dart'; - -final RegExp straySlashes = RegExp(r'(^/+)|(/+$)'); - -T? matchingAnnotation(List metadata) { - for (var metaDatum in metadata) { - if (metaDatum.type.reflectedType == T) { - return metaDatum.reflectee as T?; - } - } - - return null; -} - -T? getAnnotation(obj, Reflector? reflector) { - if (reflector == null) { - return null; - } else { - if (obj is Function) { - var methodMirror = reflector.reflectFunction(obj)!; - return matchingAnnotation(methodMirror.annotations); - } else { - var classMirror = reflector.reflectClass(obj.runtimeType)!; - return matchingAnnotation(classMirror.annotations); - } - } -} diff --git a/packages/core/performance/hello/angel.md b/packages/core/performance/hello/angel.md deleted file mode 100644 index 89c1d95..0000000 --- a/packages/core/performance/hello/angel.md +++ /dev/null @@ -1,65 +0,0 @@ -# Platform Performance Results - -5 consecutive trials run on a Windows 10 box with 4GB RAM, and several programs open in the background. - -Setup: - -* Protevus Platform `1.0.8` -* Running `wrk` 4.0.2.2 -* 2 threads -* 256 connections -* 30 seconds - -Average: - -* `11070.18` req/sec -* `11.86` ms latency - -```bash -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ wrk -c 256 -d 30 -t 2 http://localhost:3000 -Running 30s test @ http://localhost:3000 - 2 threads and 256 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 12.23ms 7.56ms 206.05ms 93.09% - Req/Sec 5.48k 761.94 7.18k 87.50% - 324822 requests in 30.06s, 62.88MB read -Requests/sec: 10806.24 -Transfer/sec: 2.09MB -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ wrk -c 256 -d 30 -t 2 http://localhost:3000 -Running 30s test @ http://localhost:3000 - 2 threads and 256 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 11.06ms 4.88ms 134.86ms 78.68% - Req/Sec 5.98k 539.40 7.50k 91.40% - 356355 requests in 30.11s, 68.99MB read -Requests/sec: 11836.11 -Transfer/sec: 2.29MB -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ wrk -c 256 -d 30 -t 2 http://localhost:3000 -Running 30s test @ http://localhost:3000 - 2 threads and 256 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 12.03ms 6.18ms 159.93ms 87.89% - Req/Sec 5.52k 0.88k 7.32k 90.31% - 327749 requests in 30.06s, 63.45MB read -Requests/sec: 10901.35 -Transfer/sec: 2.11MB -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ wrk -c 256 -d 30 -t 2 http://localhost:3000 -Running 30s test @ http://localhost:3000 - 2 threads and 256 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 12.92ms 7.06ms 189.00ms 82.48% - Req/Sec 5.12k 1.00k 6.42k 75.59% - 302273 requests in 30.05s, 58.52MB read -Requests/sec: 10059.96 -Transfer/sec: 1.95MB -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ wrk -c 256 -d 30 -t 2 http://localhost:3000 -Running 30s test @ http://localhost:3000 - 2 threads and 256 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 11.05ms 4.92ms 104.90ms 69.57% - Req/Sec 5.95k 0.87k 7.65k 76.80% - 352798 requests in 30.03s, 68.30MB read -Requests/sec: 11747.23 -Transfer/sec: 2.27MB -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ -``` diff --git a/packages/core/performance/hello/main.dart b/packages/core/performance/hello/main.dart deleted file mode 100644 index d8d83ef..0000000 --- a/packages/core/performance/hello/main.dart +++ /dev/null @@ -1,23 +0,0 @@ -/// A basic server that prints "Hello, world!" -library performance.hello; - -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; - -void main() async { - var app = Application(); - var http = PlatformHttp.custom(app, startShared, useZone: false); - - app.get('/', (req, res) => res.write('Hello, world!')); - app.optimizeForProduction(force: true); - - var oldHandler = app.errorHandler; - app.errorHandler = (e, req, res) { - print('Oops: ${e.error ?? e}'); - print(e.stackTrace); - return oldHandler(e, req, res); - }; - - await http.startServer('127.0.0.1', 3000); - print('Listening at ${http.uri}'); -} diff --git a/packages/core/performance/hello/raw.dart b/packages/core/performance/hello/raw.dart deleted file mode 100644 index 33a08ff..0000000 --- a/packages/core/performance/hello/raw.dart +++ /dev/null @@ -1,18 +0,0 @@ -/// A basic server that prints "Hello, world!" -library performance.hello; - -import 'dart:io'; - -Future main() { - return HttpServer.bind('127.0.0.1', 3000, shared: true).then((server) { - print('Listening at http://${server.address.address}:${server.port}'); - - server.listen((request) { - if (request.uri.path == '/') { - request.response.write('Hello, world!'); - } - - request.response.close(); - }); - }); -} diff --git a/packages/core/performance/hello/raw.md b/packages/core/performance/hello/raw.md deleted file mode 100644 index e16631e..0000000 --- a/packages/core/performance/hello/raw.md +++ /dev/null @@ -1,63 +0,0 @@ -# `dart:io` Results - -5 consecutive trials run on a Windows 10 box with 4GB RAM, and several programs open in the background. - -Setup: - -* Running `wrk` 4.0.2.2 -* 2 threads -* 256 connections -* 30 seconds - -Average: - -* `14598.16` req/sec -* `8.88` ms latency - -```bash -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ wrk -c 256 -d 30 -t 2 http://localhost:3000 -Running 30s test @ http://localhost:3000 - 2 threads and 256 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 9.67ms 8.19ms 202.28ms 96.17% - Req/Sec 7.15k 1.47k 9.97k 73.76% - 417716 requests in 30.07s, 82.06MB read -Requests/sec: 13892.50 -Transfer/sec: 2.73MB -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ wrk -c 256 -d 30 -t 2 http://localhost:3000 -Running 30s test @ http://localhost:3000 - 2 threads and 256 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 8.47ms 3.14ms 100.77ms 65.40% - Req/Sec 7.61k 670.47 8.85k 73.88% - 453301 requests in 30.07s, 89.05MB read -Requests/sec: 15077.15 -Transfer/sec: 2.96MB -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ wrk -c 256 -d 30 -t 2 http://localhost:3000 -Running 30s test @ http://localhost:3000 - 2 threads and 256 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 8.62ms 3.51ms 73.34ms 63.74% - Req/Sec 7.52k 650.22 8.91k 79.17% - 448445 requests in 30.07s, 88.10MB read -Requests/sec: 14911.53 -Transfer/sec: 2.93MB -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ wrk -c 256 -d 30 -t 2 http://localhost:3000 -Running 30s test @ http://localhost:3000 - 2 threads and 256 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 8.75ms 3.51ms 70.50ms 64.53% - Req/Sec 7.41k 825.50 10.23k 72.24% - 441338 requests in 30.09s, 86.70MB read -Requests/sec: 14665.62 -Transfer/sec: 2.88MB -tobe@LAPTOP-VBHCSVRH:/mnt/c/Users/thosa$ wrk -c 256 -d 30 -t 2 http://localhost:3000 -Running 30s test @ http://localhost:3000 - 2 threads and 256 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 8.90ms 3.62ms 78.36ms 66.71% - Req/Sec 7.31k 742.11 10.79k 77.84% - 434674 requests in 30.09s, 85.39MB read -Requests/sec: 14443.98 -Transfer/sec: 2.84MB -``` diff --git a/packages/core/pubspec.lock b/packages/core/pubspec.lock deleted file mode 100644 index 991a8bf..0000000 --- a/packages/core/pubspec.lock +++ /dev/null @@ -1,685 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" - url: "https://pub.dev" - source: hosted - version: "73.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.2" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" - url: "https://pub.dev" - source: hosted - version: "6.8.0" - args: - dependency: transitive - description: - name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 - url: "https://pub.dev" - source: hosted - version: "2.6.0" - async: - dependency: transitive - description: - name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 - url: "https://pub.dev" - source: hosted - version: "2.12.0" - belatuk_code_buffer: - dependency: transitive - description: - name: belatuk_code_buffer - sha256: "147d68b24f099ccc6230f354cc05c4f8df1be2a95c26daebb859cbe2096d83f4" - url: "https://pub.dev" - source: hosted - version: "5.2.0" - belatuk_combinator: - dependency: "direct main" - description: - name: belatuk_combinator - sha256: "04bfb2c6f667cdb668529ecf2060a2fa2c79c22c6d85793a76d1cd6d73acf68e" - url: "https://pub.dev" - source: hosted - version: "5.2.0" - belatuk_http_server: - dependency: "direct main" - description: - name: belatuk_http_server - sha256: "3a4cfea80835e7feec47c1cf3f0ce17b3b80433b65f2b48f940d3f95f8ffe9d9" - url: "https://pub.dev" - source: hosted - version: "4.4.0" - belatuk_merge_map: - dependency: "direct main" - description: - name: belatuk_merge_map - sha256: a05dc05da77e7be4a34adeb863c62b44bcd846ff6efa00cf60d17ae91e809a21 - url: "https://pub.dev" - source: hosted - version: "5.2.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - build: - dependency: transitive - description: - name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - build_config: - dependency: transitive - description: - name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 - url: "https://pub.dev" - source: hosted - version: "1.1.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - build_runner: - dependency: "direct dev" - description: - name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" - url: "https://pub.dev" - source: hosted - version: "2.4.13" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe - url: "https://pub.dev" - source: hosted - version: "7.3.1" - built_collection: - dependency: transitive - description: - name: built_collection - sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.dev" - source: hosted - version: "5.1.1" - built_value: - dependency: transitive - description: - name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb - url: "https://pub.dev" - source: hosted - version: "8.9.2" - charcode: - dependency: "direct main" - description: - name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 - url: "https://pub.dev" - source: hosted - version: "1.3.1" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff - url: "https://pub.dev" - source: hosted - version: "2.0.3" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 - url: "https://pub.dev" - source: hosted - version: "4.10.0" - collection: - dependency: "direct main" - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - coverage: - dependency: transitive - description: - name: coverage - sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" - url: "https://pub.dev" - source: hosted - version: "2.3.7" - file: - dependency: "direct main" - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - glob: - dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - graphs: - dependency: transitive - description: - name: graphs - sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - http: - dependency: "direct dev" - description: - name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 - url: "https://pub.dev" - source: hosted - version: "1.2.2" - http2: - dependency: "direct main" - description: - name: http2 - sha256: "9ced024a160b77aba8fb8674e38f70875e321d319e6f303ec18e87bd5a4b0c1d" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - http_parser: - dependency: "direct main" - description: - name: http_parser - sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" - url: "https://pub.dev" - source: hosted - version: "4.1.1" - io: - dependency: "direct dev" - description: - name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - js: - dependency: transitive - description: - name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf - url: "https://pub.dev" - source: hosted - version: "0.7.1" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - lints: - dependency: "direct dev" - description: - name: lints - sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.dev" - source: hosted - version: "4.0.0" - logging: - dependency: "direct main" - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" - url: "https://pub.dev" - source: hosted - version: "0.1.2-main.4" - matcher: - dependency: "direct main" - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - meta: - dependency: "direct main" - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - mime: - dependency: "direct main" - description: - name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" - url: "https://pub.dev" - source: hosted - version: "1.0.6" - mockito: - dependency: "direct dev" - description: - name: mockito - sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" - url: "https://pub.dev" - source: hosted - version: "5.4.4" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - package_config: - dependency: transitive - description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - path: - dependency: "direct main" - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - platform_container: - dependency: "direct main" - description: - path: "../container/container" - relative: true - source: path - version: "9.0.0" - platform_model: - dependency: "direct main" - description: - path: "../model" - relative: true - source: path - version: "9.0.0" - platform_route: - dependency: "direct main" - description: - path: "../route" - relative: true - source: path - version: "9.0.0" - platform_support: - dependency: "direct main" - description: - path: "../support" - relative: true - source: path - version: "9.0.0" - platform_testing: - dependency: "direct main" - description: - path: "../testing" - relative: true - source: path - version: "9.0.0" - pool: - dependency: transitive - description: - name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" - source: hosted - version: "1.5.1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - quiver: - dependency: "direct main" - description: - name: quiver - sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 - url: "https://pub.dev" - source: hosted - version: "3.2.2" - recase: - dependency: "direct main" - description: - name: recase - sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 - url: "https://pub.dev" - source: hosted - version: "4.1.0" - shelf: - dependency: transitive - description: - name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" - source: hosted - version: "1.4.2" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 - url: "https://pub.dev" - source: hosted - version: "1.1.3" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - source_gen: - dependency: transitive - description: - name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b - url: "https://pub.dev" - source: hosted - version: "2.1.2" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" - url: "https://pub.dev" - source: hosted - version: "0.10.12" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - stack_trace: - dependency: "direct main" - description: - name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" - url: "https://pub.dev" - source: hosted - version: "1.12.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - string_scanner: - dependency: "direct main" - description: - name: string_scanner - sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6" - url: "https://pub.dev" - source: hosted - version: "1.4.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test: - dependency: "direct dev" - description: - name: test - sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" - url: "https://pub.dev" - source: hosted - version: "1.25.8" - test_api: - dependency: transitive - description: - name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" - url: "https://pub.dev" - source: hosted - version: "0.7.3" - test_core: - dependency: transitive - description: - name: test_core - sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" - url: "https://pub.dev" - source: hosted - version: "0.6.5" - timing: - dependency: transitive - description: - name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - tuple: - dependency: "direct main" - description: - name: tuple - sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 - url: "https://pub.dev" - source: hosted - version: "2.0.2" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - uuid: - dependency: "direct main" - description: - name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff - url: "https://pub.dev" - source: hosted - version: "4.5.1" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b - url: "https://pub.dev" - source: hosted - version: "14.3.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb - url: "https://pub.dev" - source: hosted - version: "1.1.0" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" - url: "https://pub.dev" - source: hosted - version: "0.1.6" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - yaml: - dependency: transitive - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" - url: "https://pub.dev" - source: hosted - version: "3.1.2" -sdks: - dart: ">=3.5.0 <4.0.0" diff --git a/packages/core/pubspec.yaml b/packages/core/pubspec.yaml deleted file mode 100644 index 2f57930..0000000 --- a/packages/core/pubspec.yaml +++ /dev/null @@ -1,52 +0,0 @@ -name: platform_core -version: 9.0.0 -description: Protevus Platform high-powered HTTP server extensible framework with dependency injection, routing and much more. -homepage: https://protevus.com -documentation: https://docs.protevus.com -repository: https://git.protevus.com/protevus/platform/src/branch/main/packages/core -environment: - sdk: '>=3.3.0 <4.0.0' -dependencies: - platform_container: ^9.0.0 - platform_model: ^9.0.0 - platform_route: ^9.0.0 - platform_support: ^9.0.0 - platform_testing: ^9.0.0 - belatuk_merge_map: ^5.1.0 - belatuk_combinator: ^5.2.0 - belatuk_http_server: ^4.4.0 - charcode: ^1.3.1 - file: ^7.0.1 - http_parser: ^4.1.1 - http2: ^2.3.0 - logging: ^1.3.0 - matcher: ^0.12.16 - meta: ^1.16.0 - mime: ^1.0.0 - path: ^1.9.1 - quiver: ^3.2.2 - recase: ^4.1.0 - stack_trace: ^1.12.0 - string_scanner: ^1.4.0 - tuple: ^2.0.2 - uuid: ^4.5.1 - collection: ^1.19.1 -dev_dependencies: - http: ^1.2.2 - io: ^1.0.4 - test: ^1.25.8 - lints: ^4.0.0 - mockito: ^5.4.4 - build_runner: ^2.4.13 -# dependency_overrides: -# platform_container: -# path: ../container/angel_container -# platform_http_exception: -# path: ../http_exception -# platform_model: -# path: ../model -# platform_route: -# path: ../route -# platform_mock_request: -# path: ../mock_request - diff --git a/packages/core/test/accepts_test.dart b/packages/core/test/accepts_test.dart deleted file mode 100644 index dcf6fed..0000000 --- a/packages/core/test/accepts_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_testing/http.dart'; -import 'package:test/test.dart'; - -final Uri endpoint = Uri.parse('http://example.com/accept'); - -void main() { - test('no content type', () async { - var req = await acceptContentTypes(); - expect(req.acceptsAll, isFalse); - //expect(req.accepts(ContentType.JSON), isFalse); - expect(req.accepts('application/json'), isFalse); - //expect(req.accepts(ContentType.HTML), isFalse); - expect(req.accepts('text/html'), isFalse); - }); - - test('wildcard', () async { - var req = await acceptContentTypes(['*/*']); - expect(req.acceptsAll, isTrue); - //expect(req.accepts(ContentType.JSON), isTrue); - expect(req.accepts('application/json'), isTrue); - //expect(req.accepts(ContentType.HTML), isTrue); - expect(req.accepts('text/html'), isTrue); - }); - - test('specific type', () async { - var req = await acceptContentTypes(['text/html']); - expect(req.acceptsAll, isFalse); - //expect(req.accepts(ContentType.JSON), isFalse); - expect(req.accepts('application/json'), isFalse); - //expect(req.accepts(ContentType.HTML), isTrue); - expect(req.accepts('text/html'), isTrue); - }); - - test('strict', () async { - var req = await acceptContentTypes(['text/html', '*/*']); - expect(req.accepts('text/html'), isTrue); - //expect(req.accepts(ContentType.HTML), isTrue); - //expect(req.accepts(ContentType.JSON, strict: true), isFalse); - expect(req.accepts('application/json', strict: true), isFalse); - }); - - group('disallow null', () { - late RequestContext req; - - setUp(() async { - req = await acceptContentTypes(); - }); - - test('throws error', () { - expect(() => req.accepts(null), throwsArgumentError); - }); - }); -} - -Future acceptContentTypes( - [Iterable contentTypes = const []]) { - var headerString = - contentTypes.isEmpty ? ContentType.text : contentTypes.join(','); - var rq = MockHttpRequest('GET', endpoint, persistentConnection: false); - rq.headers.set('accept', headerString); - rq.close(); - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - return http.createRequestContext(rq, rq.response); -} diff --git a/packages/core/test/all.dart b/packages/core/test/all.dart deleted file mode 100644 index ae283f1..0000000 --- a/packages/core/test/all.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'dart:io'; -import 'package:io/ansi.dart'; -import 'http_404_hole_test.dart' as hole404; -import 'accepts_test.dart' as accepts; -import 'anonymous_service_test.dart' as anonymous_service; -import 'body_test.dart' as body; -import 'controller_test.dart' as controller; -import 'detach_test.dart' as detach; -import 'di_test.dart' as di; -import 'encoders_buffer_test.dart' as encoders_buffer; -import 'env_test.dart' as env; -import 'exception_test.dart' as exception; -import 'extension_test.dart' as extension_test; -import 'find_one_test.dart' as find_one; -import 'general_test.dart' as general; -import 'hooked_test.dart' as hooked; -import 'parameter_meta_test.dart' as parameter_meta; -import 'parse_id_test.dart' as parse_id; -import 'precontained_test.dart' as precontained; -import 'primitives_test.dart' as primitives; -import 'repeat_request_test.dart' as repeat_request; -import 'req_shutdown_test.dart' as req_shutdown; -import 'routing_test.dart' as routing; -import 'serialize_test.dart' as serialize; -import 'server_test.dart' as server; -import 'service_map_test.dart' as service_map; -import 'services_test.dart' as services; -import 'streaming_test.dart' as streaming; -import 'view_generator_test.dart' as view_generator; -//import 'response_header_test.dart' as response_header; -import 'package:test/test.dart'; - -/// For running with coverage -void main() { - print(cyan.wrap('Running tests on ${Platform.version}')); - group('http_404_hole', hole404.main); - group('accepts', accepts.main); - group('anonymous service', anonymous_service.main); - group('body', body.main); - //group('response_header', response_header.main); - group('controller', controller.main); - group('detach', detach.main); - group('di', di.main); - group('encoders_buffer', encoders_buffer.main); - group('env', env.main); - group('exception', exception.main); - group('extension', extension_test.main); - group('find_one', find_one.main); - group('general', general.main); - group('hooked', hooked.main); - group('parameter_meta', parameter_meta.main); - group('parse_id', parse_id.main); - group('precontained', precontained.main); - group('primitives', primitives.main); - group('repeat_request', repeat_request.main); - group('req_shutdown', req_shutdown.main); - group('routing', routing.main); - group('serialize', serialize.main); - group('server', server.main); - group('service_map', service_map.main); - group('services', services.main); - group('streaming', streaming.main); - group('view generator', view_generator.main); -} diff --git a/packages/core/test/anonymous_service_test.dart b/packages/core/test/anonymous_service_test.dart deleted file mode 100644 index 9785c1a..0000000 --- a/packages/core/test/anonymous_service_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:test/test.dart'; - -void main() { - test('custom methods', () async { - var svc = AnonymousService( - index: ([p]) async => ['index'], - read: (id, [p]) async => 'read', - create: (data, [p]) async => 'create', - modify: (id, data, [p]) async => 'modify', - update: (id, data, [p]) async => 'update', - remove: (id, [p]) async => 'remove'); - expect(await svc.index(), ['index']); - expect(await svc.read(null), 'read'); - expect(await svc.create(null), 'create'); - expect(await svc.modify(null, null), 'modify'); - expect(await svc.update(null, null), 'update'); - expect(await svc.remove(null), 'remove'); - }); - - test('defaults to throwing', () async { - try { - var svc = AnonymousService(); - await svc.read(1); - throw 'Should have thrown 405!'; - } on PlatformHttpException { - // print('Ok!'); - } - try { - var svc = AnonymousService(); - await svc.modify(2, null); - throw 'Should have thrown 405!'; - } on PlatformHttpException { - // print('Ok!'); - } - try { - var svc = AnonymousService(); - await svc.update(3, null); - throw 'Should have thrown 405!'; - } on PlatformHttpException { - // print('Ok!'); - } - }); -} diff --git a/packages/core/test/body_test.dart b/packages/core/test/body_test.dart deleted file mode 100644 index f57e0a9..0000000 --- a/packages/core/test/body_test.dart +++ /dev/null @@ -1,139 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_testing/http.dart'; -import 'package:test/test.dart'; - -void main() { - var app = Application(); - var http = PlatformHttp(app); - - Future request( - {bool asJson = true, - bool parse = true, - Map? bodyFields, - List? bodyList}) async { - var rq = - MockHttpRequest('POST', Uri(path: '/'), persistentConnection: false); - - if (bodyFields != null) { - if (asJson) { - rq - ..headers.contentType = ContentType('application', 'json') - ..write(json.encode(bodyFields)); - } else { - var b = StringBuffer(); - var i = 0; - for (var entry in bodyFields.entries) { - if (i++ > 0) b.write('&'); - b.write(entry.key); - b.write('='); - b.write(Uri.encodeComponent(entry.value.toString())); - } - - rq - ..headers.contentType = - ContentType('application', 'x-www-form-urlencoded') - ..write(json.encode(b.toString())); - } - } else if (bodyList != null) { - rq - ..headers.contentType = ContentType('application', 'json') - ..write(json.encode(bodyList)); - } - - await rq.close(); - var req = await http.createRequestContext(rq, rq.response); - if (parse) await req.parseBody(); - return req; - } - - test('parses json maps', () async { - var req = await request(bodyFields: {'hello': 'world'}); - expect(req.bodyAsObject, TypeMatcher>()); - expect(req.bodyAsMap, {'hello': 'world'}); - }); - - test('parses json lists', () async { - var req = await request(bodyList: ['foo', 'bar']); - expect(req.bodyAsObject, TypeMatcher()); - expect(req.bodyAsList, ['foo', 'bar']); - }); - - test('deserializeBody', () async { - var req = await request( - asJson: true, bodyFields: {'text': 'Hey', 'complete': false}); - var todo = await req.deserializeBody(Todo.fromMap); - expect(todo.text, 'Hey'); - expect(todo.completed, false); - }); - - test('decodeBody', () async { - var req = await request( - asJson: true, bodyFields: {'text': 'Hey', 'complete': false}); - var todo = await req.decodeBody(TodoCodec()); - expect(todo.text, 'Hey'); - expect(todo.completed, false); - }); - - test('throws when body has not been parsed', () async { - var req = await request(parse: false); - expect(() => req.bodyAsObject, throwsStateError); - expect(() => req.bodyAsMap, throwsStateError); - expect(() => req.bodyAsList, throwsStateError); - }); - - test('can set body object exactly once', () async { - var req = await request(parse: false); - req.bodyAsObject = 23; - expect(req.bodyAsObject, 23); - expect(() => req.bodyAsObject = {45.6: '34'}, throwsStateError); - }); - - test('can set body map exactly once', () async { - var req = await request(parse: false); - req.bodyAsMap = {'hey': 'yes'}; - expect(req.bodyAsMap, {'hey': 'yes'}); - expect(() => req.bodyAsMap = {'hm': 'ok'}, throwsStateError); - }); - - test('can set body list exactly once', () async { - var req = await request(parse: false); - req.bodyAsList = [ - {'hey': 'yes'} - ]; - expect(req.bodyAsList, [ - {'hey': 'yes'} - ]); - expect( - () => req.bodyAsList = [ - {'hm': 'ok'} - ], - throwsStateError); - }); -} - -class Todo { - String? text; - bool? completed; - - Todo({this.text, this.completed}); - - static Todo fromMap(Map? m) => - Todo(text: m!['text'] as String?, completed: m['complete'] as bool?); -} - -class TodoCodec extends Codec { - @override - Converter get decoder => TodoDecoder(); - - @override - Converter get encoder => throw UnsupportedError('no encoder'); -} - -class TodoDecoder extends Converter { - @override - Todo convert(Map input) => Todo.fromMap(input); -} diff --git a/packages/core/test/common.dart b/packages/core/test/common.dart deleted file mode 100644 index 5d51d3c..0000000 --- a/packages/core/test/common.dart +++ /dev/null @@ -1,60 +0,0 @@ -library angel_framework.test.common; - -import 'package:platform_core/core.dart'; -import 'package:matcher/matcher.dart'; - -class Todo extends Model { - String? text; - String? over; - - Todo({this.text, this.over}); - - Map toJson() { - return { - 'text': text, - 'over': over, - }; - } -} - -class BookService extends Service { - @override - Future index([params]) async { - print('Book params: $params'); - - return [ - {'foo': 'bar'} - ]; - } -} - -void incrementTodoTimes(e) { - IncrementService.times++; -} - -@Hooks(before: [incrementTodoTimes]) -class IncrementService extends Service { - static int times = 0; - - @override - @Hooks(after: [incrementTodoTimes]) - Future index([params]) async => []; -} - -class IsInstanceOf implements Matcher { - const IsInstanceOf(); - - @override - Description describeMismatch( - item, Description mismatchDescription, Map matchState, bool verbose) { - return mismatchDescription.add('$item is not an instance of $T'); - } - - @override - Description describe(Description description) { - return description.add('is an instance of $T'); - } - - @override - bool matches(item, Map matchState) => item is T; -} diff --git a/packages/core/test/controller_test.dart b/packages/core/test/controller_test.dart deleted file mode 100644 index d8aa584..0000000 --- a/packages/core/test/controller_test.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:http/http.dart' as http; -import 'package:platform_testing/http.dart'; -import 'package:test/test.dart'; - -import 'common.dart'; - -@Expose('/todos', middleware: [foo]) -class TodoController extends Controller { - List todos = [Todo(text: 'Hello', over: 'world')]; - - @Expose('/:id', middleware: [bar]) - Future fetchTodo( - String id, RequestContext req, ResponseContext res) async { - expect(req, isNotNull); - expect(res, isNotNull); - return todos[int.parse(id)]; - } - - @Expose('/namedRoute/:foo', as: 'foo') - Future someRandomRoute( - RequestContext req, ResponseContext res) async { - return "${req.params['foo']}!"; - } -} - -class NoExposeController extends Controller { - String getIndex() => 'Hey!'; - - int timesTwo(int n) => n * 2; - - String repeatName(String name, int times) { - var b = StringBuffer(); - for (var i = 0; i < times; i++) { - b.writeln(name); - } - return b.toString(); - } - - @Expose('/yellow', method: 'POST') - String someColor() => 'yellow'; - - @Expose.patch - int three() => 333; - - @noExpose - String hideThis() => 'Should not be exposed'; -} - -@Expose('/named', as: 'foo') -class NamedController extends Controller { - @Expose('/optional/:arg?', allowNull: ['arg']) - int optional() => 2; -} - -bool foo(RequestContext req, ResponseContext res) { - res.write('Hello, '); - return true; -} - -bool bar(RequestContext req, ResponseContext res) { - res.write('world!'); - return true; -} - -void main() { - late Application app; - late TodoController todoController; - late NoExposeController noExposeCtrl; - late HttpServer server; - var client = http.Client(); - String? url; - - setUp(() async { - app = Application(reflector: MirrorsReflector()); - app.get( - '/redirect', - (req, res) async => - res.redirectToAction('TodoController@foo', {'foo': 'world'})); - - // Register as a singleton, just for the purpose of this test - if (!app.container.has()) { - app.container.registerSingleton(todoController = TodoController()); - } - - // Using mountController(); - await app.mountController(); - - noExposeCtrl = await app.mountController(); - - // Place controller in group. The applyRoutes() call, however, is async. - // Until https://github.com/angel-dart/route/issues/28 is closed, - // this will need to be done by manually mounting the router. - var subRouter = Router(); - await todoController.applyRoutes(subRouter, app.container.reflector); - app.mount('/ctrl_group', subRouter); - - print(app.controllers); - app.dumpTree(); - - server = await PlatformHttp(app).startServer(); - url = 'http://${server.address.address}:${server.port}'; - }); - - tearDown(() async { - await server.close(force: true); - url = null; - }); - - test('basic', () { - expect(todoController.app, app); - }); - - test('create dynamic handler', () async { - var app = Application(reflector: MirrorsReflector()); - app.get( - '/foo', - ioc(({String? bar}) { - return 2; - }, optional: ['bar'])); - var rq = MockHttpRequest('GET', Uri(path: 'foo')); - await PlatformHttp(app).handleRequest(rq); - var body = await utf8.decoder.bind(rq.response).join(); - expect(json.decode(body), 2); - }); - - test('optional name', () async { - var app = Application(reflector: MirrorsReflector()); - await app.configure(NamedController().configureServer); - expect(app.controllers['foo'], const IsInstanceOf()); - }); - - test('middleware', () async { - var rgx = RegExp('^Hello, world!'); - var response = await client.get(Uri.parse('$url/todos/0')); - print('Response: ${response.body}'); - - expect(rgx.firstMatch(response.body)?.start, equals(0)); - - var todo = json.decode(response.body.replaceAll(rgx, '')) as Map; - print('Todo: $todo'); - expect(todo['text'], equals('Hello')); - expect(todo['over'], equals('world')); - }); - - test('controller in group', () async { - var rgx = RegExp('^Hello, world!'); - var response = await client.get(Uri.parse('$url/ctrl_group/todos/0')); - print('Response: ${response.body}'); - - expect(rgx.firstMatch(response.body)?.start, equals(0)); - - var todo = json.decode(response.body.replaceAll(rgx, '')) as Map; - print('Todo: $todo'); - expect(todo['text'], equals('Hello')); - expect(todo['over'], equals('world')); - }); - - test('named actions', () async { - var response = await client.get(Uri.parse('$url/redirect')); - print('Response: ${response.body}'); - expect(response.body, equals('Hello, "world!"')); - }); - - group('optional expose', () { - test('removes suffixes from controller names', () { - expect(noExposeCtrl.mountPoint!.path, 'no_expose'); - }); - - test('mounts correct routes', () { - print(noExposeCtrl.routeMappings.keys); - expect(noExposeCtrl.routeMappings.keys.toList(), - ['getIndex', 'timesTwo', 'repeatName', 'someColor', 'three']); - }); - - test('mounts correct methods', () { - void expectMethod(String name, String method) { - expect(noExposeCtrl.routeMappings[name]!.method, method); - } - - expectMethod('getIndex', 'GET'); - expectMethod('timesTwo', 'GET'); - expectMethod('repeatName', 'GET'); - expectMethod('someColor', 'POST'); - expectMethod('three', 'PATCH'); - }); - - test('mounts correct paths', () { - void expectPath(String name, String path) { - expect(noExposeCtrl.routeMappings[name]!.path, path); - } - - expectPath('getIndex', '/'); - expectPath('timesTwo', '/times_two/int:n'); - expectPath('repeatName', '/repeat_name/:name/int:times'); - expectPath('someColor', '/yellow'); - expectPath('three', '/three'); - }); - }); -} diff --git a/packages/core/test/detach_test.dart b/packages/core/test/detach_test.dart deleted file mode 100644 index fe2c696..0000000 --- a/packages/core/test/detach_test.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'dart:convert'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_testing/http.dart'; -import 'package:test/test.dart'; - -void main() { - late PlatformHttp http; - - setUp(() async { - var app = Application(); - http = PlatformHttp(app); - - app.get('/detach', (req, res) async { - if (res is HttpResponseContext) { - var io = res.detach(); - io.write('Hey!'); - await io.close(); - } else { - throw StateError('This endpoint only supports HTTP/1.1.'); - } - }); - }); - - tearDown(() => http.close()); - - test('detach response', () async { - var rq = MockHttpRequest('GET', Uri.parse('/detach')); - await rq.close(); - var rs = rq.response; - await http.handleRequest(rq); - var body = await rs.transform(utf8.decoder).join(); - expect(body, 'Hey!'); - }); -} diff --git a/packages/core/test/di_test.dart b/packages/core/test/di_test.dart deleted file mode 100644 index 651a232..0000000 --- a/packages/core/test/di_test.dart +++ /dev/null @@ -1,141 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:platform_container/container.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:http/http.dart' as http; -import 'package:platform_testing/http.dart'; -import 'package:test/test.dart'; - -import 'common.dart'; - -final String sampleText = 'make your bed'; -final String sampleOver = 'never'; - -void main() { - late Application app; - late http.Client client; - late HttpServer server; - String? url; - - setUp(() async { - app = Application(reflector: MirrorsReflector()); - client = http.Client(); - - // Inject some todos - app.container.registerSingleton(Todo(text: sampleText, over: sampleOver)); - app.container.registerFactory>((container) async { - var req = container.make(); - var text = await utf8.decoder.bind(req.body!).join(); - return Foo(text); - }); - - app.get('/errands', ioc((Todo singleton) => singleton)); - app.get( - '/errands3', - ioc(({required Errand singleton, Todo? foo, RequestContext? req}) => - singleton.text)); - app.post('/async', ioc((Foo foo) => {'baz': foo.bar})); - await app.configure(SingletonController().configureServer); - await app.configure(ErrandController().configureServer); - - server = await PlatformHttp(app).startServer(); - url = 'http://${server.address.host}:${server.port}'; - }); - - tearDown(() async { - url = null; - client.close(); - await server.close(force: true); - }); - - test('runContained with custom container', () async { - var app = Application(); - var c = Container(const MirrorsReflector()); - c.registerSingleton(Todo(text: 'Hey!')); - - app.get('/', (req, res) async { - return app.runContained((Todo t) => t.text, req, res, c); - }); - - var rq = MockHttpRequest('GET', Uri(path: '/')); - await rq.close(); - var rs = rq.response; - await PlatformHttp(app).handleRequest(rq); - var text = await rs.transform(utf8.decoder).join(); - expect(text, json.encode('Hey!')); - }); - - test('singleton in route', () async { - validateTodoSingleton(await client.get(Uri.parse('$url/errands'))); - }); - - test('singleton in controller', () async { - validateTodoSingleton(await client.get(Uri.parse('$url/errands2'))); - }); - - test('make in route', () async { - var response = await client.get(Uri.parse('$url/errands3')); - var text = await json.decode(response.body) as String?; - expect(text, equals(sampleText)); - }); - - test('make in controller', () async { - var response = await client.get(Uri.parse('$url/errands4')); - var text = await json.decode(response.body) as String?; - expect(text, equals(sampleText)); - }); - - test('resolve from future in controller', () async { - var response = - await client.post(Uri.parse('$url/errands4/async'), body: 'hey'); - expect(response.body, json.encode({'bar': 'hey'})); - }); - - test('resolve from future in route', () async { - var response = await client.post(Uri.parse('$url/async'), body: 'yes'); - expect(response.body, json.encode({'baz': 'yes'})); - }); -} - -void validateTodoSingleton(response) { - var todo = json.decode(response.body.toString()) as Map; - expect(todo['id'], equals(null)); - expect(todo['text'], equals(sampleText)); - expect(todo['over'], equals(sampleOver)); -} - -@Expose('/errands2') -class SingletonController extends Controller { - @Expose('/') - Todo todo(Todo singleton) => singleton; -} - -@Expose('/errands4') -class ErrandController extends Controller { - @Expose('/') - String? errand(Errand errand) { - return errand.text; - } - - @Expose('/async', method: 'POST') - Map asyncResolve(Foo foo) { - return {'bar': foo.bar}; - } -} - -class Foo { - final String bar; - - Foo(this.bar); -} - -class Errand { - Todo todo; - - String? get text => todo.text; - - Errand(this.todo); -} diff --git a/packages/core/test/encoders_buffer_test.dart b/packages/core/test/encoders_buffer_test.dart deleted file mode 100644 index b39078d..0000000 --- a/packages/core/test/encoders_buffer_test.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' hide BytesBuilder; -import 'dart:typed_data' show BytesBuilder; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_testing/http.dart'; -import 'package:test/test.dart'; - -Future> getBody(MockHttpResponse rs) async { - var list = await rs.toList(); - var bb = BytesBuilder(); - list.forEach(bb.add); - return bb.takeBytes(); -} - -void main() { - late Application app; - - setUp(() { - app = Application(reflector: MirrorsReflector()); - app.encoders.addAll( - { - 'deflate': zlib.encoder, - 'gzip': gzip.encoder, - }, - ); - - app.get('/hello', (req, res) { - res - ..useBuffer() - ..write('Hello, world!'); - }); - }); - - tearDown(() => app.close()); - - encodingTests(() => app); -} - -void encodingTests(Application Function() getApp) { - group('encoding', () { - Application app; - late PlatformHttp http; - - setUp(() { - app = getApp(); - http = PlatformHttp(app); - }); - - test('sends plaintext if no accept-encoding', () async { - var rq = MockHttpRequest('GET', Uri.parse('/hello')); - await rq.close(); - var rs = rq.response; - await http.handleRequest(rq); - - var body = await rs.transform(utf8.decoder).join(); - expect(body, 'Hello, world!'); - }); - - test('encodes if wildcard', () async { - var rq = MockHttpRequest('GET', Uri.parse('/hello')) - ..headers.set('accept-encoding', '*'); - await rq.close(); - var rs = rq.response; - await http.handleRequest(rq); - - var body = await getBody(rs); - //print(rs.headers); - expect(rs.headers.value('content-encoding'), 'deflate'); - expect(body, zlib.encode(utf8.encode('Hello, world!'))); - }); - - test('encodes if wildcard + multiple', () async { - var rq = MockHttpRequest('GET', Uri.parse('/hello')) - ..headers.set('accept-encoding', ['foo', 'bar', '*']); - await rq.close(); - var rs = rq.response; - await http.handleRequest(rq); - - var body = await getBody(rs); - expect(rs.headers.value('content-encoding'), 'deflate'); - expect(body, zlib.encode(utf8.encode('Hello, world!'))); - }); - - test('encodes if explicit', () async { - var rq = MockHttpRequest('GET', Uri.parse('/hello')) - ..headers.set('accept-encoding', 'gzip'); - await rq.close(); - var rs = rq.response; - await http.handleRequest(rq); - - var body = await getBody(rs); - expect(rs.headers.value('content-encoding'), 'gzip'); - expect(body, gzip.encode(utf8.encode('Hello, world!'))); - }); - - test('only uses one encoder', () async { - var rq = MockHttpRequest('GET', Uri.parse('/hello')); - rq.headers.set('accept-encoding', ['gzip', 'deflate']); - await rq.close(); - var rs = rq.response; - await http.handleRequest(rq); - - var body = await getBody(rs); - expect(rs.headers.value('content-encoding'), 'gzip'); - expect(body, gzip.encode(utf8.encode('Hello, world!'))); - }); - }); -} diff --git a/packages/core/test/env_test.dart b/packages/core/test/env_test.dart deleted file mode 100644 index d4ca038..0000000 --- a/packages/core/test/env_test.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'dart:io'; -import 'package:platform_core/core.dart'; -import 'package:test/test.dart'; - -void main() { - test('custom value', () => expect(ProtevusEnvironment('hey').value, 'hey')); - - test('lowercases', () => expect(ProtevusEnvironment('HeY').value, 'hey')); - test( - 'default to env or development', - () => expect(ProtevusEnvironment().value, - (Platform.environment['ANGEL_ENV'] ?? 'development').toLowerCase())); - test('isDevelopment', - () => expect(ProtevusEnvironment('development').isDevelopment, true)); - test('isStaging', - () => expect(ProtevusEnvironment('staging').isStaging, true)); - test('isDevelopment', - () => expect(ProtevusEnvironment('production').isProduction, true)); -} diff --git a/packages/core/test/exception_test.dart b/packages/core/test/exception_test.dart deleted file mode 100644 index 908541e..0000000 --- a/packages/core/test/exception_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'dart:convert'; -import 'package:test/test.dart'; - -void main() { - test('named constructors', () { - expect(PlatformHttpException.badRequest(), - isException(400, '400 Bad Request')); - expect(PlatformHttpException.notAuthenticated(), - isException(401, '401 Not Authenticated')); - expect(PlatformHttpException.paymentRequired(), - isException(402, '402 Payment Required')); - expect( - PlatformHttpException.forbidden(), isException(403, '403 Forbidden')); - expect(PlatformHttpException.notFound(), isException(404, '404 Not Found')); - expect(PlatformHttpException.methodNotAllowed(), - isException(405, '405 Method Not Allowed')); - expect(PlatformHttpException.notAcceptable(), - isException(406, '406 Not Acceptable')); - expect( - PlatformHttpException.methodTimeout(), isException(408, '408 Timeout')); - expect(PlatformHttpException.conflict(), isException(409, '409 Conflict')); - expect(PlatformHttpException.notProcessable(), - isException(422, '422 Not Processable')); - expect(PlatformHttpException.notImplemented(), - isException(501, '501 Not Implemented')); - expect(PlatformHttpException.unavailable(), - isException(503, '503 Unavailable')); - }); - - test('fromMap', () { - expect(PlatformHttpException.fromMap({'status_code': -1, 'message': 'ok'}), - isException(-1, 'ok')); - }); - - test('toMap = toJson', () { - var exc = PlatformHttpException.badRequest(); - expect(exc.toMap(), exc.toJson()); - var json_ = json.encode(exc.toJson()); - var exc2 = PlatformHttpException.fromJson(json_); - expect(exc2.toJson(), exc.toJson()); - }); - - test('toString', () { - expect( - PlatformHttpException(statusCode: 420, message: 'Blaze It').toString(), - '420: Blaze It'); - }); -} - -Matcher isException(int statusCode, String message) => - _IsException(statusCode, message); - -class _IsException extends Matcher { - final int statusCode; - final String message; - - _IsException(this.statusCode, this.message); - - @override - Description describe(Description description) => - description.add('has status code $statusCode and message "$message"'); - - @override - bool matches(item, Map matchState) { - return item is PlatformHttpException && - item.statusCode == statusCode && - item.message == message; - } -} diff --git a/packages/core/test/extension_test.dart b/packages/core/test/extension_test.dart deleted file mode 100644 index 0e077a3..0000000 --- a/packages/core/test/extension_test.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'dart:async'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_testing/http.dart'; -import 'package:test/test.dart'; - -final Uri endpoint = Uri.parse('http://example.com'); - -void main() { - test('single extension', () async { - var req = await makeRequest('foo.js'); - expect(req.extension, '.js'); - }); - - test('multiple extensions', () async { - var req = await makeRequest('foo.min.js'); - expect(req.extension, '.js'); - }); - - test('no extension', () async { - var req = await makeRequest('foo'); - expect(req.extension, ''); - }); -} - -Future makeRequest(String path) { - var rq = MockHttpRequest('GET', endpoint.replace(path: path))..close(); - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - return http.createRequestContext(rq, rq.response); -} diff --git a/packages/core/test/find_one_test.dart b/packages/core/test/find_one_test.dart deleted file mode 100644 index 645660c..0000000 --- a/packages/core/test/find_one_test.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:test/test.dart'; -import 'common.dart'; - -void main() { - var throwsAnHttpException = - throwsA(const IsInstanceOf()); - - /* - test('throw 404 on null', () { - var service = AnonymousService(index: ([p]) => null); - expect(() => service.findOne(), throwsAnHttpException); - }); - */ - - test('throw 404 on empty iterable', () { - var service = AnonymousService(index: ([p]) => []); - expect(() => service.findOne(), throwsAnHttpException); - }); - - test('return first element of iterable', () async { - var service = AnonymousService(index: ([p]) => [2]); - expect(await service.findOne(), 2); - }); -} diff --git a/packages/core/test/general_test.dart b/packages/core/test/general_test.dart deleted file mode 100644 index 20b7fe9..0000000 --- a/packages/core/test/general_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'dart:io'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'dart:convert'; -import 'package:http/http.dart' as http; -import 'package:test/test.dart'; - -void main() { - late Application app; - late http.Client client; - late HttpServer server; - late String url; - - setUp(() async { - app = Application(reflector: MirrorsReflector()) - ..post('/foo', (req, res) => res.serialize({'hello': 'world'})) - ..all('*', (req, res) => throw PlatformHttpException.notFound()); - client = http.Client(); - - server = await PlatformHttp(app).startServer(); - url = 'http://${server.address.host}:${server.port}'; - }); - - tearDown(() async { - client.close(); - await server.close(force: true); - }); - - test('allow override of method', () async { - var response = await client.get(Uri.parse('$url/foo'), - headers: {'X-HTTP-Method-Override': 'POST'}); - print('Response: ${response.body}'); - expect(json.decode(response.body), equals({'hello': 'world'})); - }); -} diff --git a/packages/core/test/hm.dart b/packages/core/test/hm.dart deleted file mode 100644 index 2480297..0000000 --- a/packages/core/test/hm.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:async'; -import 'package:io/ansi.dart'; -import 'all.dart' as hm; - -void main() async { - var zone = Zone.current.fork( - specification: ZoneSpecification(print: (self, parent, zone, line) { - if (line == 'null') { - parent.print(zone, cyan.wrap(StackTrace.current.toString())!); - } - }), - ); - return await zone.run(hm.main); -} diff --git a/packages/core/test/hooked_test.dart b/packages/core/test/hooked_test.dart deleted file mode 100644 index daa5030..0000000 --- a/packages/core/test/hooked_test.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:http/http.dart' as http; -import 'package:test/test.dart'; -import 'common.dart'; - -void main() { - Map headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }; - - late Application app; - late HttpServer server; - late String url; - late http.Client client; - late HookedService todoService; - - setUp(() async { - app = Application(reflector: MirrorsReflector()); - client = http.Client(); - app.use('/todos', MapService()); - app.use('/books', BookService()); - - todoService = app.findHookedService('todos') - as HookedService; - - todoService.beforeAllStream().listen((e) { - print('Fired ${e.eventName}! Data: ${e.data}; Params: ${e.params}'); - }); - - app.errorHandler = (e, req, res) { - throw e.error as Object; - }; - - server = await PlatformHttp(app).startServer(); - url = 'http://${server.address.host}:${server.port}'; - }); - - tearDown(() async { - await server.close(force: true); - client.close(); - }); - - test('listen before and after', () async { - var count = 0; - - todoService - ..beforeIndexed.listen((_) { - count++; - }) - ..afterIndexed.listen((_) { - count++; - }); - - var response = await client.get(Uri.parse('$url/todos')); - print(response.body); - expect(count, equals(2)); - }); - - test('cancel before', () async { - todoService.beforeCreated - ..listen((HookedServiceEvent event) { - event.cancel({'hello': 'hooked world'}); - }) - ..listen((HookedServiceEvent event) { - event.cancel({'this_hook': 'should never run'}); - }); - - var response = await client.post(Uri.parse('$url/todos'), - body: json.encode({'arbitrary': 'data'}), - headers: headers as Map); - print(response.body); - var result = json.decode(response.body) as Map; - expect(result['hello'], equals('hooked world')); - }); - - test('cancel after', () async { - todoService.afterIndexed - ..listen((HookedServiceEvent event) async { - // Hooks can be Futures ;) - event.cancel([ - {'protevus': 'framework'} - ]); - }) - ..listen((HookedServiceEvent event) { - event.cancel({'this_hook': 'should never run either'}); - }); - - var response = await client.get(Uri.parse('$url/todos')); - print(response.body); - var result = json.decode(response.body) as List; - expect(result[0]['protevus'], equals('framework')); - }); - - test('asStream() fires', () async { - var stream = todoService.afterCreated.asStream(); - await todoService.create({'protevus': 'framework'}); - expect(await stream.first.then((e) => e.result['protevus']), 'framework'); - }); - - test('metadata', () async { - final service = HookedService(IncrementService())..addHooks(app); - expect(service.inner, isNot(const IsInstanceOf())); - IncrementService.times = 0; - await service.index(); - expect(IncrementService.times, equals(2)); - }); - - test('inject request + response', () async { - var books = app.findService('books') - as HookedService>; - - books.beforeIndexed.listen((e) { - expect([e.request, e.response], everyElement(isNotNull)); - print('Indexing books at path: ${e.request?.path}'); - }); - - var response = await client.get(Uri.parse('$url/books')); - print(response.body); - - var result = json.decode(response.body); - expect(result, isList); - expect(result, isNotEmpty); - expect(result[0], equals({'foo': 'bar'})); - }); - - test('contains provider in before and after', () async { - var svc = HookedService(AnonymousService(index: ([p]) async => [])); - - void ensureProviderIsPresent(HookedServiceEvent e) { - var type = e.isBefore ? 'before' : 'after'; - print('Params to $type ${e.eventName}: ${e.params}'); - expect(e.params, isMap); - expect(e.params.keys, contains('provider')); - expect(e.params['provider'], const IsInstanceOf()); - } - - svc - ..beforeAll(ensureProviderIsPresent) - ..afterAll(ensureProviderIsPresent); - - await svc.index({'provider': const Providers('testing')}); - }); -} diff --git a/packages/core/test/http2/adapter_test.dart b/packages/core/test/http2/adapter_test.dart deleted file mode 100644 index ad26840..0000000 --- a/packages/core/test/http2/adapter_test.dart +++ /dev/null @@ -1,305 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' hide BytesBuilder; -import 'dart:typed_data'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart' hide Header; -import 'package:platform_core/http2.dart'; -import 'package:collection/collection.dart' show IterableExtension; -import 'package:http/src/multipart_file.dart' as http; -import 'package:http/src/multipart_request.dart' as http; -import 'package:http/io_client.dart'; -import 'package:http2/transport.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:logging/logging.dart'; -import 'package:test/test.dart'; -import 'http2_client.dart'; - -const String jfk = - 'Ask not what your country can do for you, but what you can do for your country.'; - -Stream> jfkStream() { - return Stream.fromIterable([utf8.encode(jfk)]); -} - -void main() { - var client = Http2Client(); - late IOClient h1c; - Application app; - late PlatformHttp2 http2; - late Uri serverRoot; - - setUp(() async { - app = Application(reflector: MirrorsReflector()) - ..encoders['gzip'] = gzip.encoder; - hierarchicalLoggingEnabled = true; - app.logger = Logger.detached('protevus.http2') - ..onRecord.listen((rec) { - print(rec); - if (rec.error == null) return; - print(rec.error); - if (rec.stackTrace != null) print(rec.stackTrace); - }); - - app.get('/', (req, res) async { - res.write('Hello world'); - await res.close(); - }); - - app.all('/method', (req, res) => req.method); - - app.get('/json', (_, __) => {'foo': 'bar'}); - - app.get('/stream', (req, res) => jfkStream().pipe(res)); - - app.get('/headers', (req, res) async { - res.headers.addAll({'foo': 'bar', 'x-protevus': 'http2'}); - await res.close(); - }); - - app.get('/status', (req, res) async { - res.statusCode = 1337; - await res.close(); - }); - - app.post('/body', (req, res) => req.parseBody().then((_) => req.bodyAsMap)); - - app.post('/upload', (req, res) async { - await req.parseBody(); - var body = req.bodyAsMap; - var files = req.uploadedFiles ?? []; - - var file = files.firstWhereOrNull((f) => f.name == 'file')!; - return [ - await file.data.map((l) => l.length).reduce((a, b) => a + b), - file.contentType.mimeType, - body - ]; - }); - - app.get('/push', (req, res) async { - res.write('ok'); - - if (res is Http2ResponseContext && res.canPush) { - var a = res.push('a')..write('a'); - await a.close(); - - var b = res.push('b')..write('b'); - await b.close(); - } - - await res.close(); - }); - - app.get('/param/:name', (req, res) => req.params); - - app.get('/query', (req, res) { - print('incoming URI: ${req.uri}'); - return req.queryParameters; - }); - - var ctx = SecurityContext() - ..useCertificateChain('dev.pem') - ..usePrivateKey('dev.key', password: 'dartdart') - ..setAlpnProtocols(['h2'], true); - - // Create an HTTP client that trusts our server. - h1c = IOClient(HttpClient()..badCertificateCallback = (_, __, ___) => true); - - http2 = PlatformHttp2(app, ctx, allowHttp1: true); - - var server = await http2.startServer(); - serverRoot = Uri.parse('https://127.0.0.1:${server.port}'); - }); - - tearDown(() async { - await http2.close(); - h1c.close(); - }); - - test('buffered response', () async { - var response = await client.get(serverRoot); - expect(response.body, 'Hello world'); - }); - - test('allowHttp1', () async { - var response = await h1c.get(serverRoot); - expect(response.body, 'Hello world'); - }); - - test('streamed response', () async { - var response = await client.get(serverRoot.replace(path: '/stream')); - expect(response.body, jfk); - }); - - group('gzip', () { - test('buffered response', () async { - var response = await client - .get(serverRoot, headers: {'accept-encoding': 'gzip, deflate, br'}); - expect(response.headers['content-encoding'], 'gzip'); - var decoded = gzip.decode(response.bodyBytes); - expect(utf8.decode(decoded), 'Hello world'); - }); - - test('streamed response', () async { - var response = await client.get(serverRoot.replace(path: '/stream'), - headers: {'accept-encoding': 'gzip'}); - expect(response.headers['content-encoding'], 'gzip'); - //print(response.body); - var decoded = gzip.decode(response.bodyBytes); - expect(utf8.decode(decoded), jfk); - }); - }); - - test('query uri decoded', () async { - var uri = - serverRoot.replace(path: '/query', queryParameters: {'foo!': 'bar?'}); - var response = await client.get(uri); - print('Sent $uri'); - expect(response.body, json.encode({'foo!': 'bar?'})); - }); - - test('params uri decoded', () async { - var response = await client.get(serverRoot.replace(path: '/param/foo!')); - expect(response.body, json.encode({'name': 'foo!'})); - }); - - test('method parsed', () async { - var response = await client.delete(serverRoot.replace(path: '/method')); - expect(response.body, json.encode('DELETE')); - }); - - test('json response', () async { - var response = await client.get(serverRoot.replace(path: '/json')); - expect(response.body, json.encode({'foo': 'bar'})); - expect(ContentType.parse(response.headers['content-type']!).mimeType, - ContentType.json.mimeType); - }); - - test('status sent', () async { - var response = await client.get(serverRoot.replace(path: '/status')); - expect(response.statusCode, 1337); - }); - - test('headers sent', () async { - var response = await client.get(serverRoot.replace(path: '/headers')); - expect(response.headers['foo'], 'bar'); - expect(response.headers['x-protevus'], 'http2'); - }); - - test('server push', () async { - var socket = await SecureSocket.connect( - serverRoot.host, - serverRoot.port, - onBadCertificate: (_) => true, - supportedProtocols: ['h2'], - ); - - var connection = ClientTransportConnection.viaSocket( - socket, - settings: ClientSettings(allowServerPushes: true), - ); - - var headers =
[ - Header.ascii(':authority', serverRoot.authority), - Header.ascii(':method', 'GET'), - Header.ascii(':path', serverRoot.replace(path: '/push').path), - Header.ascii(':scheme', serverRoot.scheme), - ]; - - var stream = connection.makeRequest(headers, endStream: true); - - var bb = await stream.incomingMessages - .where((s) => s is DataStreamMessage) - .cast() - .fold(BytesBuilder(), (out, msg) => out..add(msg.bytes)); - - // Check that main body was sent - expect(utf8.decode(bb.takeBytes()), 'ok'); - - var pushes = await stream.peerPushes.toList(); - expect(pushes, hasLength(2)); - - var pushA = pushes[0], pushB = pushes[1]; - - String getPath(TransportStreamPush p) => ascii.decode(p.requestHeaders - .firstWhere((h) => ascii.decode(h.name) == ':path') - .value); - - /* - Future getBody(ClientTransportStream stream) async { - await stream.outgoingMessages.close(); - var bb = await stream.incomingMessages - .map((s) { - if (s is HeadersStreamMessage) { - for (var h in s.headers) { - print('${ASCII.decode(h.name)}: ${ASCII.decode(h.value)}'); - } - } else if (s is DataStreamMessage) { - print(UTF8.decode(s.bytes)); - } - - return s; - }) - .where((s) => s is DataStreamMessage) - .cast() - .fold( - BytesBuilder(), (out, msg) => out..add(msg.bytes)); - return UTF8.decode(bb.takeBytes()); - } - */ - - expect(getPath(pushA), '/a'); - expect(getPath(pushB), '/b'); - - // However, Chrome, Firefox, Edge all can - //expect(await getBody(pushA.stream), 'a'); - //expect(await getBody(pushB.stream), 'b'); - }); - - group('body parsing', () { - test('urlencoded body parsed', () async { - var response = await client.post( - serverRoot.replace(path: '/body'), - headers: { - 'accept': 'application/json', - 'content-type': 'application/x-www-form-urlencoded' - }, - body: 'foo=bar', - ); - expect(response.body, json.encode({'foo': 'bar'})); - }); - - test('json body parsed', () async { - var response = await client.post(serverRoot.replace(path: '/body'), - headers: { - 'accept': 'application/json', - 'content-type': 'application/json' - }, - body: json.encode({'foo': 'bar'})); - expect(response.body, json.encode({'foo': 'bar'})); - }); - - test('multipart body parsed', () async { - var rq = - http.MultipartRequest('POST', serverRoot.replace(path: '/upload')); - rq.headers.addAll({'accept': 'application/json'}); - - rq.fields['foo'] = 'bar'; - rq.files.add(http.MultipartFile( - 'file', Stream.fromIterable([utf8.encode('hello world')]), 11, - contentType: MediaType('protevus', 'framework'))); - - var response = await client.send(rq); - var responseBody = await response.stream.transform(utf8.decoder).join(); - - expect( - responseBody, - json.encode([ - 11, - 'protevus/framework', - {'foo': 'bar'} - ])); - }); - }); -} diff --git a/packages/core/test/http2/http2_client.dart b/packages/core/test/http2/http2_client.dart deleted file mode 100644 index 4edefb4..0000000 --- a/packages/core/test/http2/http2_client.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' hide BytesBuilder; -import 'dart:typed_data'; -import 'package:http/http.dart'; -import 'package:http2/transport.dart'; - -/// Simple HTTP/2 client -class Http2Client extends BaseClient { - static Future convertRequestToStream( - BaseRequest request) async { - // Connect a socket - var socket = await SecureSocket.connect( - request.url.host, - request.url.port, - onBadCertificate: (_) => true, - supportedProtocols: ['h2'], - ); - - var connection = ClientTransportConnection.viaSocket(socket); - - var headers =
[ - Header.ascii(':authority', request.url.authority), - Header.ascii(':method', request.method), - Header.ascii( - ':path', - request.url.path + - (request.url.hasQuery ? ('?${request.url.query}') : '')), - Header.ascii(':scheme', request.url.scheme), - ]; - - var bb = await request - .finalize() - .fold(BytesBuilder(), (out, list) => out..add(list)); - var body = bb.takeBytes(); - - if (body.isNotEmpty) { - headers.add(Header.ascii('content-length', body.length.toString())); - } - - request.headers.forEach((k, v) { - headers.add(Header.ascii(k, v)); - }); - - var stream = connection.makeRequest(headers, endStream: body.isEmpty); - - if (body.isNotEmpty) { - stream.sendData(body, endStream: true); - } else { - await (stream.outgoingMessages.close()); - } - - return stream; - } - - /// Returns `true` if the response stream was closed. - static Future readResponse(ClientTransportStream stream, - Map headers, BytesBuilder body) { - var c = Completer(); - var closed = false; - - stream.incomingMessages.listen( - (msg) { - if (msg is HeadersStreamMessage) { - for (var header in msg.headers) { - var name = ascii.decode(header.name).toLowerCase(), - value = ascii.decode(header.value); - headers[name] = value; - //print('$name: $value'); - } - } else if (msg is DataStreamMessage) { - body.add(msg.bytes); - } - - if (!closed && msg.endStream) closed = true; - }, - cancelOnError: true, - onError: c.completeError, - onDone: () => c.complete(closed), - ); - - return c.future; - } - - @override - Future send(BaseRequest request) async { - var stream = await convertRequestToStream(request); - var headers = {}; - var body = BytesBuilder(); - var closed = await readResponse(stream, headers, body); - return StreamedResponse( - Stream.fromIterable([body.takeBytes()]), - int.parse(headers[':status']!), - headers: headers, - isRedirect: headers.containsKey('location'), - contentLength: headers.containsKey('content-length') - ? int.parse(headers['content-length']!) - : null, - request: request, - reasonPhrase: null, - // doesn't exist in HTTP/2 - persistentConnection: !closed, - ); - } -} diff --git a/packages/core/test/http_404_hole_test.dart b/packages/core/test/http_404_hole_test.dart deleted file mode 100644 index 433fae6..0000000 --- a/packages/core/test/http_404_hole_test.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:async'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:charcode/ascii.dart'; -import 'package:http/io_client.dart' as http; -import 'package:logging/logging.dart'; -import 'package:test/test.dart'; -import 'pretty_log.dart'; - -void main() { - late http.IOClient client; - late PlatformHttp driver; - late Logger logger; - - setUp(() async { - client = http.IOClient(); - hierarchicalLoggingEnabled = true; - - logger = Logger.detached('http_404_hole') - ..level = Level.ALL - ..onRecord.listen(prettyLog); - - var app = Application(logger: logger); - - app.fallback(hello); - app.fallback(throw404); - - // The error handler in the boilerplate. - var oldErrorHandler = app.errorHandler; - app.errorHandler = (e, req, res) async { - if (req.accepts('text/html', strict: true)) { - if (e.statusCode == 404 && req.accepts('text/html', strict: true)) { - await res - .render('error', {'message': 'No file exists at ${req.uri}.'}); - } else { - await res.render('error', {'message': e.message}); - } - } else { - return await oldErrorHandler(e, req, res); - } - }; - - driver = PlatformHttp(app); - await driver.startServer(); - }); - - tearDown(() { - logger.clearListeners(); - client.close(); - scheduleMicrotask(driver.close); - }); - - test('does not continue processing after streaming', () async { - var url = driver.uri.replace(path: '/hey'); - for (var i = 0; i < 100; i++) { - var r = await client.get(url); - print('#$i: ${r.statusCode}: ${r.body}'); - expect(r.statusCode, 200); - expect(r.body, 'Hello!'); - } - }); -} - -/// Simulate angel_static -Future hello(RequestContext req, ResponseContext res) { - if (req.path == 'hey') { - var bytes = [$H, $e, $l, $l, $o, $exclamation]; - var s = Stream>.fromIterable([bytes]); - return s.pipe(res); - } else { - return Future.value(); - } -} - -/// 404 -void throw404(RequestContext req, ResponseContext res) { - Zone.current - .handleUncaughtError('This 404 should not occur.', StackTrace.current); - throw PlatformHttpException.notFound(); -} diff --git a/packages/core/test/jsonp_test.dart b/packages/core/test/jsonp_test.dart deleted file mode 100644 index 3b67add..0000000 --- a/packages/core/test/jsonp_test.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:platform_testing/http.dart'; -import 'package:test/test.dart'; - -void main() { - var app = Application(); - var http = PlatformHttp(app); - - app.get('/default', (req, res) => res.jsonp({'foo': 'bar'})); - - app.get('/callback', - (req, res) => res.jsonp({'foo': 'bar'}, callbackName: 'doIt')); - - app.get( - '/contentType', - (req, res) => - res.jsonp({'foo': 'bar'}, contentType: MediaType('foo', 'bar'))); - - Future getContentType(String path) async { - var rq = MockHttpRequest('GET', Uri(path: '/$path')); - await rq.close(); - await http.handleRequest(rq); - return MediaType.parse(rq.response.headers.contentType.toString()); - } - - Future getText(String path) async { - var rq = MockHttpRequest('GET', Uri(path: '/$path')); - await rq.close(); - await http.handleRequest(rq); - return await rq.response.transform(utf8.decoder).join(); - } - - test('default', () async { - var response = await getText('default'); - var contentType = await getContentType('default'); - expect(response, r'callback({"foo":"bar"})'); - expect(contentType.mimeType, 'application/javascript'); - }); - - test('callback', () async { - var response = await getText('callback'); - var contentType = await getContentType('callback'); - expect(response, r'doIt({"foo":"bar"})'); - expect(contentType.mimeType, 'application/javascript'); - }); - - test('content type', () async { - var response = await getText('contentType'); - var contentType = await getContentType('contentType'); - expect(response, r'callback({"foo":"bar"})'); - expect(contentType.mimeType, 'foo/bar'); - }); -} diff --git a/packages/core/test/parameter_meta_test.dart b/packages/core/test/parameter_meta_test.dart deleted file mode 100644 index a0b01ae..0000000 --- a/packages/core/test/parameter_meta_test.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_testing/http.dart'; -import 'package:logging/logging.dart'; - -import 'package:test/test.dart'; - -Future readResponse(MockHttpResponse rs) { - return rs.transform(utf8.decoder).join(); -} - -Future printResponse(MockHttpResponse rs) { - return readResponse(rs).then((text) { - print(text.isEmpty ? '' : text); - }); -} - -void main() { - group('parameter_meta', parameterMetaTests); -} - -void parameterMetaTests() { - Application app; - late PlatformHttp http; - - setUp(() { - app = Application(reflector: MirrorsReflector()); - http = PlatformHttp(app); - - app.get('/cookie', ioc((@CookieValue('token') String jwt) { - return jwt; - })); - - app.get('/header', ioc((@Header('x-foo') String header) { - return header; - })); - - app.get('/query', ioc((@Query('q') String query) { - return query; - })); - - app.get('/session', ioc((@Session('foo') String foo) { - return foo; - })); - - app.get('/match', ioc((@Query('mode', match: 'pos') String mode) { - return 'YES $mode'; - })); - - app.get('/match', ioc((@Query('mode', match: 'neg') String mode) { - return 'NO $mode'; - })); - - app.get('/match', ioc((@Query('mode') String mode) { - return 'DEFAULT $mode'; - })); - - app.logger = Logger('parameter_meta_test') - ..onRecord.listen((rec) { - print(rec); - if (rec.error != null) print(rec.error); - if (rec.stackTrace != null) print(rec.stackTrace); - }); - }); - - test('injects header or throws', () async { - // Invalid request - var rq = MockHttpRequest('GET', Uri.parse('/header')); - await rq.close(); - var rs = rq.response; - //TODO: Using await will hang. To be resolved. - http.handleRequest(rq); - - await printResponse(rs); - expect(rs.statusCode, 400); - - // Valid request - rq = MockHttpRequest('GET', Uri.parse('/header')) - ..headers.add('x-foo', 'bar'); - await rq.close(); - rs = rq.response; - http.handleRequest(rq); - - var body = await readResponse(rs); - print('Body: $body'); - expect(rs.statusCode, 200); - expect(body, json.encode('bar')); - }); - - test('injects session or throws', () async { - // Invalid request - var rq = MockHttpRequest('GET', Uri.parse('/session')); - await rq.close(); - var rs = rq.response; - http - .handleRequest(rq) - .timeout(const Duration(seconds: 5)) - .catchError((_) => null); - - await printResponse(rs); - expect(rs.statusCode, 500); - - rq = MockHttpRequest('GET', Uri.parse('/session')); - rq.session['foo'] = 'bar'; - await rq.close(); - rs = rq.response; - http.handleRequest(rq); - - await printResponse(rs); - expect(rs.statusCode, 200); - }); - - // Originally, the plan was to test cookie, session, header, etc., - // but that behavior has been consolidated into `getValue`. Thus, - // they will all function the same way. - - test('pattern matching', () async { - var rq = MockHttpRequest('GET', Uri.parse('/match?mode=pos')); - await rq.close(); - var rs = rq.response; - http.handleRequest(rq); - var body = await readResponse(rs); - print('Body: $body'); - expect(rs.statusCode, 200); - expect(body, json.encode('YES pos')); - - rq = MockHttpRequest('GET', Uri.parse('/match?mode=neg')); - await rq.close(); - rs = rq.response; - http.handleRequest(rq); - body = await readResponse(rs); - print('Body: $body'); - expect(rs.statusCode, 200); - expect(body, json.encode('NO neg')); - - // Fallback - rq = MockHttpRequest('GET', Uri.parse('/match?mode=ambi')); - await rq.close(); - rs = rq.response; - http.handleRequest(rq); - body = await readResponse(rs); - print('Body: $body'); - expect(rs.statusCode, 200); - expect(body, json.encode('DEFAULT ambi')); - }); -} diff --git a/packages/core/test/parse_id_test.dart b/packages/core/test/parse_id_test.dart deleted file mode 100644 index 95f6732..0000000 --- a/packages/core/test/parse_id_test.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:test/test.dart'; - -void main() { - test('null', () { - expect(Service.parseId('null'), 'null'); - expect(Service.parseId(null), 'null'); - }); - - test('String', () { - expect(Service.parseId('23'), '23'); - }); - - test('int', () { - expect(Service.parseId('23'), 23); - }); - - test('double', () { - expect(Service.parseId('23.4'), 23.4); - }); - - test('num', () { - expect(Service.parseId('23.4'), 23.4); - }); - - test('bool', () { - expect(Service.parseId('true'), true); - expect(Service.parseId(true), true); - expect(Service.parseId('false'), false); - expect(Service.parseId(false), false); - expect(Service.parseId('hmm'), false); - }); -} diff --git a/packages/core/test/precontained_test.dart b/packages/core/test/precontained_test.dart deleted file mode 100644 index c1ec560..0000000 --- a/packages/core/test/precontained_test.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:convert'; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_testing/http.dart'; - -import 'package:test/test.dart'; - -void main() { - test('preinjects functions when executed', () async { - // Create app with mirrors reflector - var app = Application(reflector: MirrorsReflector()) - ..configuration['foo'] = 'bar' - ..get('/foo', ioc(echoAppFoo)); - - // Create request and response contexts - var rq = MockHttpRequest('GET', Uri(path: '/foo')); - var reqContext = await HttpRequestContext.from(rq, app, '/foo'); - var resContext = HttpResponseContext(rq.response, app, reqContext); - - // Force pre-injection by running the handler - await app.runReflected(echoAppFoo, reqContext, resContext); - - // Verify preContained has the function - expect(app.preContained.keys, contains(echoAppFoo)); - - // Clean up - await reqContext.close(); - }); -} - -String echoAppFoo(String foo) => foo; diff --git a/packages/core/test/pretty_log.dart b/packages/core/test/pretty_log.dart deleted file mode 100644 index 71febe2..0000000 --- a/packages/core/test/pretty_log.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:logging/logging.dart'; -import 'package:io/ansi.dart'; - -/// Prints the contents of a [LogRecord] with pretty colors. -void prettyLog(LogRecord record) { - var code = chooseLogColor(record.level); - - if (record.error == null) print(code.wrap(record.toString())); - - if (record.error != null) { - var err = record.error; - print(code.wrap('$record\n')); - print(code.wrap(err.toString())); - - if (record.stackTrace != null) { - print(code.wrap(record.stackTrace.toString())); - } - } -} - -/// Chooses a color based on the logger [level]. -AnsiCode chooseLogColor(Level level) { - if (level == Level.SHOUT) { - return backgroundRed; - } else if (level == Level.SEVERE) { - return red; - } else if (level == Level.WARNING) { - return yellow; - } else if (level == Level.INFO) { - return cyan; - } else if (level == Level.CONFIG || - level == Level.FINE || - level == Level.FINER || - level == Level.FINEST) { - return lightGray; - } - return resetAll; -} diff --git a/packages/core/test/primitives_test.dart b/packages/core/test/primitives_test.dart deleted file mode 100644 index f0f2288..0000000 --- a/packages/core/test/primitives_test.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'dart:convert'; -import 'dart:io' show stderr; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_testing/http.dart'; - -import 'package:test/test.dart'; - -void main() { - late Application app; - late PlatformHttp http; - - setUp(() { - app = Application(reflector: MirrorsReflector()) - ..configuration['global'] = 305; // Pitbull! - http = PlatformHttp(app); - - app.get('/string/:string', ioc((String string) => string)); - - app.get( - '/num/parsed/:num', - chain([ - (req, res) { - req.params['n'] = num.parse(req.params['num'].toString()); - return true; - }, - ioc((num n) => n), - ])); - - app.get('/num/global', ioc((num global) => global)); - - app.errorHandler = (e, req, res) { - stderr - ..writeln(e.error) - ..writeln(e.stackTrace); - }; - }); - - tearDown(() => app.close()); - - test('String type annotation', () async { - var rq = MockHttpRequest('GET', Uri.parse('/string/hello')); - await rq.close(); - await http.handleRequest(rq); - var rs = await rq.response.transform(utf8.decoder).join(); - expect(rs, json.encode('hello')); - }); - - test('Primitive after parsed param injection', () async { - var rq = MockHttpRequest('GET', Uri.parse('/num/parsed/24')); - await rq.close(); - await http.handleRequest(rq); - var rs = await rq.response.transform(utf8.decoder).join(); - expect(rs, json.encode(24)); - }); - - test('globally-injected primitive', () async { - var rq = MockHttpRequest('GET', Uri.parse('/num/global')); - await rq.close(); - await http.handleRequest(rq); - var rs = await rq.response.transform(utf8.decoder).join(); - expect(rs, json.encode(305)); - }); - - test('unparsed primitive throws error', () async { - var rq = MockHttpRequest('GET', Uri.parse('/num/unparsed/32')); - await rq.close(); - var req = await http.createRequestContext(rq, rq.response); - var res = await http.createResponseContext(rq, rq.response, req); - expect(() => app.runContained((num unparsed) => unparsed, req, res), - throwsA(isA())); - }); -} diff --git a/packages/core/test/repeat_request_test.dart b/packages/core/test/repeat_request_test.dart deleted file mode 100644 index b6d09e3..0000000 --- a/packages/core/test/repeat_request_test.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_testing/http.dart'; -import 'package:test/test.dart'; - -void main() { - MockHttpRequest mk(int id) { - return MockHttpRequest('GET', Uri.parse('/test/$id'))..close(); - } - - test('can request the same url twice', () async { - var app = Application(reflector: MirrorsReflector()) - ..get('/test/:id', ioc((id) => 'Hello $id')); - var rq1 = mk(1), rq2 = mk(2), rq3 = mk(1); - await Future.wait([rq1, rq2, rq3].map(PlatformHttp(app).handleRequest)); - var body1 = await rq1.response.transform(utf8.decoder).join(), - body2 = await rq2.response.transform(utf8.decoder).join(), - body3 = await rq3.response.transform(utf8.decoder).join(); - print('Response #1: $body1'); - print('Response #2: $body2'); - print('Response #3: $body3'); - expect( - body1, - allOf( - isNot(body2), - equals(body3), - )); - }); -} diff --git a/packages/core/test/req_shutdown_test.dart b/packages/core/test/req_shutdown_test.dart deleted file mode 100644 index 778e654..0000000 --- a/packages/core/test/req_shutdown_test.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:async'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:http/io_client.dart' as http; -import 'package:logging/logging.dart'; -import 'package:test/test.dart'; -import 'pretty_log.dart'; - -void main() { - late http.IOClient client; - late PlatformHttp driver; - late Logger logger; - late StringBuffer buf; - - setUp(() async { - buf = StringBuffer(); - client = http.IOClient(); - hierarchicalLoggingEnabled = true; - - logger = Logger.detached('req_shutdown') - ..level = Level.ALL - ..onRecord.listen(prettyLog); - - var app = Application(logger: logger); - - app.fallback((req, res) { - req.shutdownHooks.add(() => buf.write('Hello, ')); - req.shutdownHooks.add(() => buf.write('world!')); - }); - - driver = PlatformHttp(app); - await driver.startServer(); - }); - - tearDown(() { - logger.clearListeners(); - client.close(); - scheduleMicrotask(driver.close); - }); - - test('does not continue processing after streaming', () async { - await client.get(driver.uri); - await Future.delayed(Duration(milliseconds: 100)); - expect(buf.toString(), 'Hello, world!'); - }); -} diff --git a/packages/core/test/response_header_test.dart b/packages/core/test/response_header_test.dart deleted file mode 100644 index 151b374..0000000 --- a/packages/core/test/response_header_test.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'dart:io'; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/src/http/protevus_http.dart'; -import 'package:test/test.dart'; - -void main() { - late Application app; - late PlatformHttp http; - late HttpClient client; - - setUp(() async { - app = Application(reflector: MirrorsReflector()); - http = PlatformHttp(app); - - await http.startServer(); - - var formData = {'id': 100, 'name': 'William'}; - app.get('/api/v1/user/list', (RequestContext req, res) async { - //await req.parseBody(); - //res.write('Hello, World!'); - res.json(formData); - }); - - client = HttpClient(); - }); - - tearDown(() async { - client.close(); - await http.close(); - }); - - test('Remove Response Header', () async { - http.removeResponseHeader({'x-frame-options': 'SAMEORIGIN'}); - - var request = await client.get('localhost', 3000, '/api/v1/user/list'); - HttpClientResponse response = await request.close(); - //print(response.headers); - expect(response.headers['x-frame-options'], isNull); - }, skip: true); - - test('Add Response Header', () async { - http.addResponseHeader({ - 'X-XSRF_TOKEN': - 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e' - }); - - var request = await client.get('localhost', 3000, '/api/v1/user/list'); - HttpClientResponse response = await request.close(); - //print(response.headers); - expect( - response.headers['X-XSRF_TOKEN'], - equals([ - 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e' - ])); - }, skip: true); -} diff --git a/packages/core/test/routing_test.dart b/packages/core/test/routing_test.dart deleted file mode 100644 index dce777e..0000000 --- a/packages/core/test/routing_test.dart +++ /dev/null @@ -1,223 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:http/http.dart' as http; -import 'package:io/ansi.dart'; -import 'package:logging/logging.dart'; -import 'package:test/test.dart'; - -import 'common.dart'; - -@Middleware([interceptor]) -Future testMiddlewareMetadata( - RequestContext req, ResponseContext res) async { - return 'This should not be shown.'; -} - -@Middleware([interceptService]) -class QueryService extends Service { - @override - @Middleware([interceptor]) - Future read(id, [Map? params]) async => params; -} - -void interceptor(RequestContext req, ResponseContext res) { - res - ..write('Middleware') - ..close(); -} - -bool interceptService(RequestContext req, ResponseContext res) { - res.write('Service with '); - return true; -} - -void main() { - late Application app; - late Application nested; - late Application todos; - late String url; - late http.Client client; - - setUp(() async { - app = Application(reflector: MirrorsReflector()); - nested = Application(reflector: MirrorsReflector()); - todos = Application(reflector: MirrorsReflector()); - - for (var app in [app, nested, todos]) { - app.logger = Logger('routing_test') - ..onRecord.listen((rec) { - if (rec.error != null) { - stdout - ..writeln(cyan.wrap(rec.toString())) - ..writeln(cyan.wrap(rec.error.toString())) - ..writeln(cyan.wrap(rec.stackTrace.toString())); - } - }); - } - - todos.get('/action/:action', (req, res) => res.json(req.params)); - - late Route ted; - - ted = nested.post('/ted/:route', (RequestContext req, res) { - print('Params: ${req.params}'); - print('Path: ${ted.path}, uri: ${req.path}'); - print('matcher: ${ted.parser}'); - return req.params; - }); - - app.mount('/nes', nested); - app.get('/meta', testMiddlewareMetadata); - app.get('/intercepted', (req, res) => 'This should not be shown', - middleware: [interceptor]); - app.get('/hello', (req, res) => 'world'); - app.get('/name/:first/last/:last', (req, res) => req.params); - app.post( - '/lambda', - (RequestContext req, res) => - req.parseBody().then((_) => req.bodyAsMap)); - app.mount('/todos/:id', todos); - app - .get('/greet/:name', - (RequestContext req, res) async => "Hello ${req.params['name']}") - .name = 'Named routes'; - app.get('/named', (req, ResponseContext res) async { - await res.redirectTo('Named routes', {'name': 'tests'}); - }); - app.get('/log', (RequestContext req, res) async { - print('Query: ${req.queryParameters}'); - return 'Logged'; - }); - - app.get('/method', (req, res) => 'Only GET'); - app.post('/method', (req, res) => 'Only POST'); - - app.use('/query', QueryService()); - - RequestHandler write(String message) { - return (req, res) { - res.write(message); - return true; - }; - } - - app.chain([write('a')]).chain([write('b'), write('c')]).get( - '/chained', (req, res) => res.close()); - - app.fallback((req, res) => 'MJ'); - - //app.dumpTree(header: "DUMPING ROUTES:", showMatchers: true); - - client = http.Client(); - var server = await PlatformHttp(app).startServer('127.0.0.1', 0); - url = 'http://${server.address.host}:${server.port}'; - }); - - tearDown(() async { - await app.close(); - client.close(); - }); - - test('Can match basic url', () async { - var response = await client.get(Uri.parse('$url/hello')); - expect(response.body, equals('"world"')); - }); - - test('Can match url with multiple parameters', () async { - var response = await client.get(Uri.parse('$url/name/HELLO/last/WORLD')); - print('Response: ${response.body}'); - var json_ = json.decode(response.body); - expect(json_, const IsInstanceOf()); - expect(json_['first'], equals('HELLO')); - expect(json_['last'], equals('WORLD')); - }); - - test('Chained routes', () async { - var response = await client.get(Uri.parse('$url/chained')); - expect(response.body, equals('abc')); - }); - - test('Can nest another Protevus instance', () async { - var response = await client.post(Uri.parse('$url/nes/ted/foo')); - var json_ = json.decode(response.body); - expect(json_['route'], equals('foo')); - }); - - test('Can parse parameters from a nested Protevus instance', () async { - var response = await client.get(Uri.parse('$url/todos/1337/action/test')); - var json_ = json.decode(response.body); - print('JSON: $json_'); - expect(json_['id'], equals('1337')); - expect(json_['action'], equals('test')); - }); - - test('Can add and use named middleware', () async { - var response = await client.get(Uri.parse('$url/intercepted')); - expect(response.body, equals('Middleware')); - }); - - test('Middleware via metadata', () async { - // Metadata - var response = await client.get(Uri.parse('$url/meta')); - expect(response.body, equals('Middleware')); - }); - - test('Can serialize function result as JSON', () async { - Map headers = {'Content-Type': 'application/json'}; - var postData = json.encode({'it': 'works'}); - var response = await client.post(Uri.parse('$url/lambda'), - headers: headers as Map, body: postData); - print('Response: ${response.body}'); - expect(json.decode(response.body)['it'], equals('works')); - }); - - test('Fallback routes', () async { - var response = await client.get(Uri.parse('$url/my_favorite_artist')); - expect(response.body, equals('"MJ"')); - }); - - /* TODO: Revisit this later - test('Can name routes', () { - Route foo = app.get('/framework/:id', null)..name = 'frm'; - print('Foo: $foo'); - String uri = foo.makeUri({'id': 'Protevus'}); - print(uri); - expect(uri, equals('framework/Protevus')); - }); - */ - - test('Redirect to named routes', () async { - var response = await client.get(Uri.parse('$url/named')); - print(response.body); - expect(json.decode(response.body), equals('Hello tests')); - }); - - test('Match routes, even with query params', () async { - var response = await client - .get(Uri.parse('$url/log?foo=bar&bar=baz&baz.foo=bar&baz.bar=foo')); - print(response.body); - expect(json.decode(response.body), equals('Logged')); - - response = await client.get(Uri.parse('$url/query/foo?bar=baz')); - print(response.body); - expect(response.body, equals('Service with Middleware')); - }); - - test('only match route with matching method', () async { - var response = await client.get(Uri.parse('$url/method')); - print(response.body); - expect(response.body, '"Only GET"'); - - response = await client.post(Uri.parse('$url/method')); - print(response.body); - expect(response.body, '"Only POST"'); - - response = await client.patch(Uri.parse('$url/method')); - print(response.body); - expect(response.body, '"MJ"'); - }); -} diff --git a/packages/core/test/serialize_test.dart b/packages/core/test/serialize_test.dart deleted file mode 100644 index 23a8e54..0000000 --- a/packages/core/test/serialize_test.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:io'; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:http/http.dart' as http; -import 'package:http_parser/http_parser.dart'; -import 'package:test/test.dart'; - -void main() { - late Application app; - late http.Client client; - late HttpServer server; - late String url; - - setUp(() async { - app = Application(reflector: MirrorsReflector()) - ..get('/foo', ioc(() => {'hello': 'world'})) - ..get('/bar', (req, res) async { - await res.serialize({'hello': 'world'}, - contentType: MediaType('text', 'html')); - }); - client = http.Client(); - - server = await PlatformHttp(app).startServer(); - url = 'http://${server.address.host}:${server.port}'; - }); - - tearDown(() async { - client.close(); - await server.close(force: true); - }); - - test('correct content-type', () async { - var response = await client.get(Uri.parse('$url/foo')); - print('Response: ${response.body}'); - expect(response.headers['content-type'], contains('application/json')); - - response = await client.get(Uri.parse('$url/bar')); - print('Response: ${response.body}'); - expect(response.headers['content-type'], contains('text/html')); - }); -} diff --git a/packages/core/test/server_test.dart b/packages/core/test/server_test.dart deleted file mode 100644 index 236d5f0..0000000 --- a/packages/core/test/server_test.dart +++ /dev/null @@ -1,226 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_testing/http.dart'; - -import 'package:test/test.dart'; - -final Uri $foo = Uri.parse('http://localhost:3000/foo'); - -/// Additional tests to improve coverage of server.dart -void main() { - group('scoping', () { - var parent = Application(reflector: MirrorsReflector()) - ..configuration['two'] = 2; - var child = Application(reflector: MirrorsReflector()); - parent.mount('/child', child); - - test('sets children', () { - expect(parent.children, contains(child)); - }); - - test('sets parent', () { - expect(child.parent, parent); - }); - - test('properties can climb up hierarchy', () { - expect(child.findProperty('two'), 2); - }); - }); - - test('custom server generator', () { - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp.custom(app, HttpServer.bind); - expect(http.serverGenerator, HttpServer.bind); - }); - - test('default error handler', () async { - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - var rq = MockHttpRequest('GET', $foo); - await (rq.close()); - var rs = rq.response; - var req = await http.createRequestContext(rq, rs); - var res = await http.createResponseContext(rq, rs); - var e = PlatformHttpException( - statusCode: 321, message: 'Hello', errors: ['foo', 'bar']); - await app.errorHandler(e, req, res); - await http.sendResponse(rq, rs, req, res); - expect( - ContentType.parse(rs.headers.value('content-type')!).mimeType, - 'text/html', - ); - expect(rs.statusCode, e.statusCode); - var body = await rs.transform(utf8.decoder).join(); - expect(body, contains('${e.message}')); - expect(body, contains('
  • foo
  • ')); - expect(body, contains('
  • bar
  • ')); - }); - - test('plug-ins run on startup', () async { - var app = Application(reflector: MirrorsReflector()); - app.startupHooks.add((app) => app.configuration['two'] = 2); - - var http = PlatformHttp(app); - await http.startServer(); - expect(app.configuration['two'], 2); - await app.close(); - await http.close(); - }); - - test('warning when adding routes to flattened router', () { - var app = Application(reflector: MirrorsReflector()) - ..optimizeForProduction(force: true); - app.dumpTree(); - app.get('/', (req, res) => 2); - app.mount('/foo', Router()..get('/', (req, res) => 3)); - }); - - test('services close on close call', () async { - var app = Application(reflector: MirrorsReflector()); - var svc = CustomCloseService(); - expect(svc.value, 2); - app.use('/', svc); - await app.close(); - expect(svc.value, 3); - }); - - test('global injection added to injection map', () async { - var app = Application(reflector: MirrorsReflector()) - ..configuration['a'] = 'b'; - var http = PlatformHttp(app); - app.get('/', ioc((String a) => a)); - var rq = MockHttpRequest('GET', Uri.parse('/')); - await (rq.close()); - await http.handleRequest(rq); - var body = await rq.response.transform(utf8.decoder).join(); - expect(body, json.encode('b')); - }); - - test('global injected serializer', () async { - var app = Application(reflector: MirrorsReflector()) - ..serializer = (_) => 'x'; - var http = PlatformHttp(app); - app.get($foo.path, (req, ResponseContext res) => res.serialize(null)); - var rq = MockHttpRequest('GET', $foo); - await (rq.close()); - await http.handleRequest(rq); - var body = await rq.response.transform(utf8.decoder).join(); - expect(body, 'x'); - }); - - group('handler results', () { - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - app.responseFinalizers - .add((req, res) => throw PlatformHttpException.forbidden()); - late RequestContext req; - late ResponseContext res; - - setUp(() async { - var rq = MockHttpRequest('GET', $foo); - await (rq.close()); - req = await http.createRequestContext(rq, rq.response); - res = await http.createResponseContext(rq, rq.response); - }); - - group('getHandlerResult', () { - test('return request handler', () async { - handler(req, res) => (req, res) async { - return 2; - }; - var r = await app.getHandlerResult(handler, req, res); - expect(r, 2); - }); - - test('return future', () async { - var handler = Future.value(2); - expect(await app.getHandlerResult(handler, req, res), 2); - }); - }); - - group('executeHandler', () { - test('return Stream', () async { - handler(req, res) => Stream.fromIterable([2, 3]); - expect(await app.executeHandler(handler, req, res), isFalse); - }); - - test('end response', () async { - handler(req, ResponseContext res) => res.close(); - expect(await app.executeHandler(handler, req, res), isFalse); - }); - }); - }); - - group('handleHttpException', () { - late Application app; - late PlatformHttp http; - - setUp(() async { - app = Application(reflector: MirrorsReflector()); - app.get('/wtf', (req, res) => throw PlatformHttpException.forbidden()); - app.get('/wtf2', (req, res) => throw PlatformHttpException.forbidden()); - http = PlatformHttp(app); - await http.startServer('127.0.0.1', 0); - - var oldHandler = app.errorHandler; - app.errorHandler = (e, req, res) { - print('FATAL: ${e.error ?? e}'); - print(e.stackTrace); - return oldHandler(e, req, res); - }; - }); - - tearDown(() => app.close()); - - test('can send json', () async { - var rq = MockHttpRequest('GET', Uri(path: 'wtf')) - ..headers.set('accept', 'application/json'); - await rq.close(); - http.handleRequest(rq); - await rq.response.toList(); - expect(rq.response.statusCode, 403); - expect(rq.response.headers.contentType!.mimeType, 'application/json'); - }); - - test('can throw in finalizer', () async { - var rq = MockHttpRequest('GET', Uri(path: 'wtf')) - ..headers.set('accept', 'application/json'); - await rq.close(); - http.handleRequest(rq); - await rq.response.toList(); - expect(rq.response.statusCode, 403); - expect(rq.response.headers.contentType!.mimeType, 'application/json'); - }); - - test('can send html', () async { - var rq = MockHttpRequest('GET', Uri(path: 'wtf2')); - //rq.headers.set('accept', 'text/html'); - await rq.close(); - http.handleRequest(rq); - await rq.response.toList(); - expect(rq.response.statusCode, 403); - expect(rq.response.headers.contentType?.mimeType, 'text/html'); - }); - }); -} - -class CustomCloseService extends Service { - int value = 2; - - @override - void close() { - value = 3; - super.close(); - } -} - -@Expose('/foo') -class FooController extends Controller { - @Expose('/bar') - Future bar() async => 'baz'; -} diff --git a/packages/core/test/service_map_test.dart b/packages/core/test/service_map_test.dart deleted file mode 100644 index 3c08c2b..0000000 --- a/packages/core/test/service_map_test.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:test/test.dart'; -import 'package:quiver/core.dart'; - -void main() { - MapService inner; - late Service mapped; - - setUp(() { - inner = MapService(); - mapped = inner.map(Todo.fromMap, Todo.toMap); - }); - - test('create', () async { - var result = await mapped.create( - Todo(text: 'hello', complete: false), - ); - print(result); - expect( - result, - Todo(text: 'hello', complete: false), - ); - }); - - group('after create', () { - late Todo result; - String? id; - - setUp(() async { - result = await mapped.create(Todo(text: 'hello', complete: false)); - id = result.id; - }); - - test('index', () async { - expect(await mapped.index(), [result]); - }); - - test('modify', () async { - var newTodo = Todo(text: 'yes', complete: true); - expect(await mapped.update(id, newTodo), newTodo); - }); - - test('update', () async { - var newTodo = Todo(id: 'hmmm', text: 'yes', complete: true); - expect(await mapped.update(id, newTodo), newTodo); - }); - - test('read', () async { - expect(await mapped.read(id), result); - }); - - test('remove', () async { - expect(await mapped.remove(id), result); - }); - }); -} - -class Todo { - final String? id, text; - final bool? complete; - - Todo({this.id, this.text, this.complete}); - - static Todo fromMap(Map json) { - return Todo( - id: json['id'] as String?, - text: json['text'] as String?, - complete: json['complete'] as bool?); - } - - static Map toMap(Todo model) { - return {'id': model.id, 'text': model.text, 'complete': model.complete}; - } - - @override - bool operator ==(other) => - other is Todo && other.text == text && other.complete == complete; - - @override - String toString() => '$id:$text($complete)'; - - @override - int get hashCode => hash2(text.hashCode, complete.hashCode); -} diff --git a/packages/core/test/services_test.dart b/packages/core/test/services_test.dart deleted file mode 100644 index 9fb276b..0000000 --- a/packages/core/test/services_test.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'dart:convert'; -import 'package:http/http.dart' as http; -import 'package:stack_trace/stack_trace.dart'; -import 'package:test/test.dart'; - -class Todo extends Model { - String? text; - String? over; -} - -void main() { - Map headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }; - late Application app; - late MapService service; - late String url; - late http.Client client; - - setUp(() async { - app = Application(reflector: MirrorsReflector()) - ..use('/todos', service = MapService()) - ..errorHandler = (e, req, res) { - if (e.error != null) print('Whoops: ${e.error}'); - if (e.stackTrace != null) print(Chain.forTrace(e.stackTrace!).terse); - }; - - var server = await PlatformHttp(app).startServer(); - client = http.Client(); - url = 'http://${server.address.host}:${server.port}'; - }); - - tearDown(() async { - await app.close(); - client.close(); - }); - - group('memory', () { - test('can index an empty service', () async { - var response = await client.get(Uri.parse('$url/todos/')); - print(response.body); - expect(response.body, equals('[]')); - print(response.body); - expect(json.decode(response.body).length, 0); - }); - - test('can create data', () async { - var postData = json.encode({'text': 'Hello, world!'}); - var response = await client.post(Uri.parse('$url/todos'), - headers: headers as Map, body: postData); - expect(response.statusCode, 201); - var jsons = json.decode(response.body); - print(jsons); - expect(jsons['text'], equals('Hello, world!')); - }); - - test('can fetch data', () async { - var postData = json.encode({'text': 'Hello, world!'}); - await client.post(Uri.parse('$url/todos'), - headers: headers as Map, body: postData); - var response = await client.get(Uri.parse('$url/todos/0')); - expect(response.statusCode, 200); - var jsons = json.decode(response.body); - print(jsons); - expect(jsons['text'], equals('Hello, world!')); - }); - - test('can modify data', () async { - var postData = json.encode({'text': 'Hello, world!'}); - await client.post(Uri.parse('$url/todos'), - headers: headers as Map, body: postData); - postData = json.encode({'text': 'modified'}); - - var response = await client.patch(Uri.parse('$url/todos/0'), - headers: headers, body: postData); - expect(response.statusCode, 200); - var jsons = json.decode(response.body); - print(jsons); - expect(jsons['text'], equals('modified')); - }); - - test('can overwrite data', () async { - var postData = json.encode({'text': 'Hello, world!'}); - await client.post(Uri.parse('$url/todos'), - headers: headers as Map, body: postData); - postData = json.encode({'over': 'write'}); - - var response = await client.post(Uri.parse('$url/todos/0'), - headers: headers, body: postData); - expect(response.statusCode, 200); - var jsons = json.decode(response.body); - print(jsons); - expect(jsons['text'], equals(null)); - expect(jsons['over'], equals('write')); - }); - - test('readMany', () async { - var items = [ - await service.create({'foo': 'bar'}), - await service.create({'bar': 'baz'}), - await service.create({'baz': 'quux'}) - ]; - - var ids = items.map((m) => m['id'] as String?).toList(); - expect(await service.readMany(ids), items); - }); - - test('can delete data', () async { - var postData = json.encode({'text': 'Hello, world!'}); - var created = await client - .post(Uri.parse('$url/todos'), - headers: headers as Map, body: postData) - .then((r) => json.decode(r.body)); - var response = - await client.delete(Uri.parse("$url/todos/${created['id']}")); - expect(response.statusCode, 200); - var json_ = json.decode(response.body); - print(json_); - expect(json_['text'], equals('Hello, world!')); - }); - - test('cannot remove all unless explicitly set', () async { - var response = await client.delete(Uri.parse('$url/todos/null')); - expect(response.statusCode, 403); - }); - }); -} diff --git a/packages/core/test/streaming_test.dart b/packages/core/test/streaming_test.dart deleted file mode 100644 index ffd9b5a..0000000 --- a/packages/core/test/streaming_test.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:platform_container/mirrors.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:logging/logging.dart'; -import 'package:platform_testing/http.dart'; - -import 'package:test/test.dart'; - -import 'encoders_buffer_test.dart' show encodingTests; - -void main() { - late Application app; - late PlatformHttp http; - - setUp(() { - app = Application(reflector: MirrorsReflector()); - http = PlatformHttp(app, useZone: true); - - app.logger = Logger('streaming_test') - ..onRecord.listen((rec) { - print(rec); - if (rec.stackTrace != null) print(rec.stackTrace); - }); - - app.encoders.addAll( - { - 'deflate': zlib.encoder, - 'gzip': gzip.encoder, - }, - ); - - app.get('/hello', (req, res) { - return Stream>.fromIterable(['Hello, world!'.codeUnits]) - .pipe(res); - }); - - app.get('/write', (req, res) async { - await res.addStream( - Stream>.fromIterable(['Hello, world!'.codeUnits])); - res.write('bye'); - await res.close(); - }); - - app.get('/multiple', (req, res) async { - await res.addStream( - Stream>.fromIterable(['Hello, world!'.codeUnits])); - await res.addStream(Stream>.fromIterable(['bye'.codeUnits])); - await res.close(); - }); - - app.get('/overwrite', (req, res) async { - res.statusCode = 32; - await Stream>.fromIterable(['Hello, world!'.codeUnits]) - .pipe(res); - - var f = Stream>.fromIterable(['Hello, world!'.codeUnits]) - .pipe(res) - .then((_) => false) - .catchError((_) => true); - - expect(f, completion(true)); - }); - - app.get('/error', (req, res) => res.addError(StateError('wtf'))); - - app.errorHandler = (e, req, res) async { - stderr - ..writeln(e.error) - ..writeln(e.stackTrace); - }; - }); - - tearDown(() => http.close()); - - void expectHelloBye(String path) async { - var rq = MockHttpRequest('GET', Uri.parse(path)); - await (rq.close()); - await http.handleRequest(rq); - var body = await rq.response.transform(utf8.decoder).join(); - expect(body, 'Hello, world!bye'); - } - - test('write after addStream', () => expectHelloBye('/write')); - - test('multiple addStream', () => expectHelloBye('/multiple')); - - test('cannot write after close', () async { - try { - var rq = MockHttpRequest('GET', Uri.parse('/overwrite')); - await rq.close(); - await http.handleRequest(rq); - var body = await rq.response.transform(utf8.decoder).join(); - - if (rq.response.statusCode != 32) { - throw 'overwrite should throw error; response: $body'; - } - } on StateError { - // Success - } - }); - - test('res => addError', () async { - try { - var rq = MockHttpRequest('GET', Uri.parse('/error')); - await (rq.close()); - await http.handleRequest(rq); - var body = await rq.response.transform(utf8.decoder).join(); - throw 'addError should throw error; response: $body'; - } on StateError { - // Should throw error... - } - }); - - encodingTests(() => app); -} diff --git a/packages/core/test/view_generator_test.dart b/packages/core/test/view_generator_test.dart deleted file mode 100644 index 7d39711..0000000 --- a/packages/core/test/view_generator_test.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:test/test.dart'; - -void main() { - test('default view generator', () async { - var app = Application(); - var view = await app.viewGenerator!('foo', {'bar': 'baz'}); - expect(view, contains('No view engine')); - }); -} diff --git a/packages/pipeline/.gitignore b/packages/database/.gitignore similarity index 100% rename from packages/pipeline/.gitignore rename to packages/database/.gitignore diff --git a/packages/pipeline/CHANGELOG.md b/packages/database/CHANGELOG.md similarity index 100% rename from packages/pipeline/CHANGELOG.md rename to packages/database/CHANGELOG.md diff --git a/packages/pipeline/LICENSE.md b/packages/database/LICENSE.md similarity index 100% rename from packages/pipeline/LICENSE.md rename to packages/database/LICENSE.md diff --git a/packages/events/README.md b/packages/database/README.md similarity index 100% rename from packages/events/README.md rename to packages/database/README.md diff --git a/packages/pipeline/analysis_options.yaml b/packages/database/analysis_options.yaml similarity index 100% rename from packages/pipeline/analysis_options.yaml rename to packages/database/analysis_options.yaml diff --git a/packages/database/doc/.gitkeep b/packages/database/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/database/example/.gitkeep b/packages/database/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/database/lib/src/.gitkeep b/packages/database/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/database/pubspec.yaml b/packages/database/pubspec.yaml new file mode 100644 index 0000000..9d61f00 --- /dev/null +++ b/packages/database/pubspec.yaml @@ -0,0 +1,17 @@ +name: platform_database +description: The Databse Package for the Protevus Platform +version: 0.0.1 +homepage: https://protevus.com +documentation: https://docs.protevus.com +repository: https://git.protevus.com/protevus/platform + +environment: + sdk: ^3.4.2 + +# Add regular dependencies here. +dependencies: + # path: ^1.8.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/packages/database/test/.gitkeep b/packages/database/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/events/lib/dispatcher.dart b/packages/events/lib/dispatcher.dart deleted file mode 100644 index bbc26fc..0000000 --- a/packages/events/lib/dispatcher.dart +++ /dev/null @@ -1,3 +0,0 @@ -library; - -export 'src/dispatcher.dart'; diff --git a/packages/events/lib/src/dispatcher.dart b/packages/events/lib/src/dispatcher.dart deleted file mode 100644 index de9349e..0000000 --- a/packages/events/lib/src/dispatcher.dart +++ /dev/null @@ -1,499 +0,0 @@ -import 'dart:async'; -import 'package:platform_container/container.dart'; -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:angel3_event_bus/event_bus.dart'; -import 'package:angel3_mq/mq.dart'; - -// Simulating some of the Laravel interfaces/classes -abstract class ShouldBroadcast {} - -abstract class ShouldQueue {} - -abstract class ShouldBeEncrypted {} - -abstract class ShouldDispatchAfterCommit {} - -class Dispatcher implements DispatcherContract { - // Properties as specified in YAML - final Container container; - final Map> _listeners = {}; - final Map> _wildcards = {}; - final Map> _wildcardsCache = {}; - late final Function _queueResolver; - late final Function _transactionManagerResolver; - final Map> _eventBusListeners = {}; - final Map> _untilCompleters = {}; - final Map _eventBusSubscriptions = {}; - final Set _processedMessageIds = {}; - - // Properties for Angel3 packages - final EventBus _eventBus; - late final MQClient? _mqClient; - final Map> _subjects = {}; - - // Queue and exchange names - static const String _eventsQueue = 'events_queue'; - static const String _delayedEventsQueue = 'delayed_events_queue'; - static const String _eventsExchange = 'events_exchange'; - - Dispatcher(this.container) : _eventBus = EventBus(); - - // Setter for _mqClient - set mqClient(MQClient client) { - _mqClient = client; - _setupQueuesAndExchanges(); - _startProcessingQueuedEvents(); - } - - void _setupQueuesAndExchanges() { - _mqClient?.declareQueue(_eventsQueue); - _mqClient?.declareQueue(_delayedEventsQueue); - _mqClient?.declareExchange( - exchangeName: _eventsExchange, - exchangeType: ExchangeType.direct, - ); - _mqClient?.bindQueue( - queueId: _eventsQueue, - exchangeName: _eventsExchange, - bindingKey: _eventsQueue, - ); - _mqClient?.bindQueue( - queueId: _delayedEventsQueue, - exchangeName: _eventsExchange, - bindingKey: _delayedEventsQueue, - ); - } - - void _startProcessingQueuedEvents() { - _mqClient?.fetchQueue(_eventsQueue).listen((Message message) async { - if (message.payload is Map) { - final eventData = message.payload as Map; - if (eventData.containsKey('event') && - eventData.containsKey('payload')) { - await dispatch(eventData['event'], eventData['payload']); - } else { - print('Invalid message format: ${message.payload}'); - } - } else { - print('Unexpected payload type: ${message.payload.runtimeType}'); - } - }); - } - - @override - void listen(dynamic events, dynamic listener) { - if (events is String) { - _addListener(events, listener); - } else if (events is List) { - for (var event in events) { - _addListener(event, listener); - } - } - if (events is String && events.contains('*')) { - _setupWildcardListen(events, listener); - } - } - - void _addListener(String event, dynamic listener) { - _listeners.putIfAbsent(event, () => []).add(listener); - - // Create a subject for this event if it doesn't exist - _subjects.putIfAbsent(event, () => BehaviorSubject()); - - // Add EventBus listener and store the subscription - final subscription = _eventBus.on().listen((AppEvent busEvent) { - if (busEvent is CustomAppEvent && busEvent.eventName == event) { - listener(event, busEvent.payload); - } - }); - _eventBusSubscriptions[event] = subscription; - } - - void _setupWildcardListen(String event, Function listener) { - _wildcards.putIfAbsent(event, () => []).add(listener); - _wildcardsCache.clear(); - } - - @override - bool hasListeners(String eventName) { - return _listeners.containsKey(eventName) || - _wildcards.containsKey(eventName) || - hasWildcardListeners(eventName); - } - - bool hasWildcardListeners(String eventName) { - return _wildcards.keys - .any((pattern) => _isWildcardMatch(pattern, eventName)); - } - - @override - void push(String event, [dynamic payload]) { - final effectivePayload = payload ?? []; - _mqClient?.sendMessage( - exchangeName: _eventsExchange, - routingKey: _delayedEventsQueue, - message: Message( - headers: {'expiration': '5000'}, // 5 seconds delay - payload: { - 'event': event, - 'payload': - effectivePayload is List ? effectivePayload : [effectivePayload], - }, - timestamp: DateTime.now().toIso8601String(), - id: 'msg_${DateTime.now().millisecondsSinceEpoch}', // Ensure unique ID - ), - ); - } - - @override - Future flush(String event) async { - final messageStream = _mqClient?.fetchQueue(_delayedEventsQueue); - if (messageStream == null) { - print('Warning: MQClient is not initialized'); - return; - } - - final messagesToProcess = []; - - // Collect messages to process - await for (final message in messageStream) { - print('Examining message: ${message.id}'); - if (message.payload is Map && - !_processedMessageIds.contains(message.id)) { - final eventData = message.payload as Map; - if (eventData['event'] == event) { - print('Adding message to process: ${message.id}'); - messagesToProcess.add(message); - } - } - } - - print('Total messages to process: ${messagesToProcess.length}'); - - // Process collected messages - for (final message in messagesToProcess) { - final eventData = message.payload as Map; - print('Processing message: ${message.id}'); - await dispatch(eventData['event'], eventData['payload']); - _mqClient?.deleteMessage(_delayedEventsQueue, message); - _processedMessageIds.add(message.id); - } - } - - @override - void subscribe(dynamic subscriber) { - if (subscriber is EventBusSubscriber) { - subscriber.subscribe(_eventBus); - } else { - // Handle other types of subscribers - } - } - - @override - Future until(dynamic event, [dynamic payload]) { - if (event is String) { - final completer = Completer(); - _untilCompleters[event] = completer; - - // Set up a one-time listener for this event - listen(event, (dynamic e, dynamic p) { - if (!completer.isCompleted) { - completer.complete(p); - _untilCompleters.remove(event); - } - }); - - // If payload is provided, dispatch the event immediately - if (payload != null) { - // Use dispatch instead of push to ensure immediate processing - dispatch(event, payload); - } - - return completer.future; - } - throw ArgumentError('Event must be a String'); - } - - @override - Future dispatch(dynamic event, [dynamic payload, bool? halt]) async { - final eventName = event is String ? event : event.runtimeType.toString(); - final eventPayload = payload ?? (event is AppEvent ? event : []); - - if (event is ShouldBroadcast || - (eventPayload is List && - eventPayload.isNotEmpty && - eventPayload[0] is ShouldBroadcast)) { - await _broadcastEvent(event); - } - - if (event is ShouldQueue || - (eventPayload is List && - eventPayload.isNotEmpty && - eventPayload[0] is ShouldQueue)) { - return _queueEvent(eventName, eventPayload); - } - - final listeners = getListeners(eventName); - for (var listener in listeners) { - final response = - await Function.apply(listener, [eventName, eventPayload]); - if (halt == true && response != null) { - return response; - } - if (response == false) { - break; - } - } - - return halt == true ? null : listeners; - } - - // void _addToSubject(String eventName, dynamic payload) { - // if (_subjects.containsKey(eventName)) { - // _subjects[eventName]!.add(payload); - // } - // } - - @override - List getListeners(String eventName) { - var listeners = [ - ...(_listeners[eventName] ?? []), - ...(_wildcardsCache[eventName] ?? _getWildcardListeners(eventName)), - ...(_eventBusListeners[eventName] ?? []), - ]; - - return listeners; - } - - List _getWildcardListeners(String eventName) { - final wildcardListeners = []; - for (var entry in _wildcards.entries) { - if (_isWildcardMatch(entry.key, eventName)) { - wildcardListeners.addAll(entry.value); - } - } - _wildcardsCache[eventName] = wildcardListeners; - return wildcardListeners; - } - - @override - void forget(String event) { - // Remove from _listeners - _listeners.remove(event); - - // Remove from _subjects - if (_subjects.containsKey(event)) { - _subjects[event]?.close(); - _subjects.remove(event); - } - - // Cancel and remove EventBus subscription - _eventBusSubscriptions[event]?.cancel(); - _eventBusSubscriptions.remove(event); - - // Remove from wildcards if applicable - if (event.contains('*')) { - _wildcards.remove(event); - _wildcardsCache.clear(); - } else { - // If it's not a wildcard, we need to remove it from any matching wildcard listeners - _wildcards.forEach((pattern, listeners) { - if (_isWildcardMatch(pattern, event)) { - _wildcards[pattern] = listeners - .where((listener) => listener != _listeners[event]) - .toList(); - } - }); - _wildcardsCache.clear(); - } - - // Remove any 'until' completers for this event - _untilCompleters.remove(event); - } - - @override - void forgetPushed() { - _listeners.removeWhere((key, _) => key.endsWith('_pushed')); - _eventBusListeners.removeWhere((key, _) => key.endsWith('_pushed')); - // Note: We're not clearing all EventBus listeners here, as that might affect other parts of your application - } - - @override - void setQueueResolver(Function resolver) { - _queueResolver = resolver; - } - - @override - void setTransactionManagerResolver(Function resolver) { - _transactionManagerResolver = resolver; - } - - // Add these methods for testing purposes - void triggerQueueResolver() { - _queueResolver(); - } - - void triggerTransactionManagerResolver() { - _transactionManagerResolver(); - } - - @override - Map> getRawListeners() { - return Map.unmodifiable(_listeners); - } - - bool _shouldBroadcast(List payload) { - return payload.isNotEmpty && payload[0] is ShouldBroadcast; - } - - Future _broadcastEvent(dynamic event) async { - // Implement broadcasting logic here - // For now, we'll just print a message - print('Broadcasting event: ${event.runtimeType}'); - } - - bool _isWildcardMatch(String pattern, String eventName) { - final regExp = RegExp('^${pattern.replaceAll('*', '.*')}\$'); - return regExp.hasMatch(eventName); - } - - bool _shouldQueue(List payload) { - return payload.isNotEmpty && payload[0] is ShouldQueue; - } - - Future _queueEvent(String eventName, dynamic payload) async { - _mqClient?.sendMessage( - exchangeName: _eventsExchange, - routingKey: _eventsQueue, - message: Message( - payload: {'event': eventName, 'payload': payload}, - timestamp: DateTime.now().toIso8601String(), - ), - ); - } - - // Updated on method - Stream on(String event) { - return (_subjects - .putIfAbsent(event, () => BehaviorSubject()) - .stream as Stream) - .where((event) => event is T) - .cast(); - } - - // In your Dispatcher class - void setMQClient(MQClient client) { - _mqClient = client; - } - - // Method to close the MQClient connection - Future close() async { - _mqClient?.close(); - } - - // Don't forget to close the subjects when they're no longer needed - void dispose() { - for (var subject in _subjects.values) { - subject.close(); - } - } -} -// ... rest of the code (DispatcherContract, EventBusSubscriber, etc.) remains the same - -abstract class DispatcherContract { - void listen(dynamic events, dynamic listener); - bool hasListeners(String eventName); - void push(String event, [dynamic payload]); - Future flush(String event); - void subscribe(dynamic subscriber); - Future until(dynamic event, [dynamic payload]); - Future dispatch(dynamic event, [dynamic payload, bool halt]); - List getListeners(String eventName); - void forget(String event); - void forgetPushed(); - void setQueueResolver(Function resolver); - void setTransactionManagerResolver(Function resolver); - Map> getRawListeners(); -} - -// Helper class for EventBus subscribers -abstract class EventBusSubscriber { - void subscribe(EventBus eventBus); -} - -// Mixin to simulate Macroable trait -mixin Macroable { - // Implementation of Macroable functionality -} - -// Mixin to simulate ReflectsClosures trait -mixin ReflectsClosures { - // Implementation of ReflectsClosures functionality -} - -// If not already defined, you might need to create an Event class -class Event { - final String name; - final dynamic data; - - Event(this.name, this.data); -} - -// Custom AppEvent subclasses for handling different event types -class StringBasedEvent extends AppEvent { - final String eventName; - final dynamic payload; - - StringBasedEvent(this.eventName, this.payload); - - @override - List get props => [eventName, payload]; -} - -class CustomAppEvent extends AppEvent { - final String eventName; - final dynamic payload; - - CustomAppEvent(this.eventName, this.payload); - - @override - List get props => [eventName, payload]; -} - -// This is a simple implementation of Reflector that does nothing -class EmptyReflector implements Reflector { - const EmptyReflector(); - - @override - ReflectedType reflectType(Type type) { - throw UnimplementedError(); - } - - @override - ReflectedInstance reflectInstance(Object object) { - throw UnimplementedError(); - } - - @override - ReflectedType reflectFutureOf(Type type) { - throw UnimplementedError(); - } - - @override - String? getName(Symbol symbol) { - // TODO: implement getName - throw UnimplementedError(); - } - - @override - ReflectedClass? reflectClass(Type clazz) { - // TODO: implement reflectClass - throw UnimplementedError(); - } - - @override - ReflectedFunction? reflectFunction(Function function) { - // TODO: implement reflectFunction - throw UnimplementedError(); - } -} diff --git a/packages/events/test/event_test.dart b/packages/events/test/event_test.dart deleted file mode 100644 index 72a75ab..0000000 --- a/packages/events/test/event_test.dart +++ /dev/null @@ -1,430 +0,0 @@ -import 'package:angel3_event_bus/res/app_event.dart'; -import 'package:test/test.dart'; -import 'package:platform_container/container.dart'; -import 'package:angel3_mq/mq.dart'; -import 'package:platform_events/dispatcher.dart'; // Replace with the actual import path - -void main() { - late Dispatcher dispatcher; - late MockMQClient mockMQClient; - - setUp(() { - var container = Container(EmptyReflector()); - dispatcher = Dispatcher(container); - mockMQClient = MockMQClient(); - dispatcher.mqClient = mockMQClient; // Use the setter - - // Clear the queue before each test - mockMQClient.queuedMessages.clear(); - }); - - group('Dispatcher', () { - test('listen and dispatch', () async { - var callCount = 0; - dispatcher.listen('test_event', (dynamic event, dynamic payload) { - expect(event, equals('test_event')); - expect(payload, equals(['test_payload'])); - callCount++; - }); - await dispatcher.dispatch('test_event', ['test_payload']); - expect(callCount, equals(1)); - }); - - test('wildcard listener', () async { - var callCount = 0; - dispatcher.listen('test.*', (dynamic event, dynamic payload) { - expect(event, matches(RegExp(r'^test\.'))); - callCount++; - }); - - await dispatcher.dispatch('test.one', ['payload1']); - await dispatcher.dispatch('test.two', ['payload2']); - expect(callCount, equals(2)); - }); - - test('hasListeners', () { - dispatcher.listen('test_event', (dynamic event, dynamic payload) {}); - expect(dispatcher.hasListeners('test_event'), isTrue); - expect(dispatcher.hasListeners('non_existent_event'), isFalse); - }); - - test('until', () async { - // Test without pushing the event immediately - var futureResult = dispatcher.until('test_event'); - - // Use a small delay to ensure the until listener is set up - await Future.delayed(Duration(milliseconds: 10)); - - await dispatcher.dispatch('test_event', ['test_payload']); - var result = await futureResult; - expect(result, equals(['test_payload'])); - - // Test with pushing the event immediately - result = - await dispatcher.until('another_test_event', ['another_payload']); - expect(result, equals(['another_payload'])); - }, timeout: Timeout(Duration(seconds: 5))); // Add a reasonable timeout - - test('forget', () async { - var callCount = 0; - dispatcher.listen('test_event', (dynamic event, dynamic payload) { - callCount++; - }); - await dispatcher.dispatch('test_event'); - expect(callCount, equals(1)); - - dispatcher.forget('test_event'); - await dispatcher.dispatch('test_event'); - expect(callCount, equals(1)); // Should not increase - }); - - test('push and flush', () async { - print('Starting push and flush test'); - - // Push 4 messages - for (var i = 0; i < 4; i++) { - dispatcher.push('delayed_event', ['delayed_payload_$i']); - } - - // Verify that 4 messages were queued - expect(mockMQClient.queuedMessages['delayed_events_queue']?.length, - equals(4), - reason: 'Should have queued exactly 4 messages'); - - print( - 'Queued messages: ${mockMQClient.queuedMessages['delayed_events_queue']?.length}'); - - var callCount = 0; - var processedPayloads = []; - - // Remove any existing listeners - dispatcher.forget('delayed_event'); - - dispatcher.listen('delayed_event', (dynamic event, dynamic payload) { - print('Listener called with payload: $payload'); - expect(event, equals('delayed_event')); - expect(payload[0], startsWith('delayed_payload_')); - processedPayloads.add(payload[0]); - callCount++; - }); - - await dispatcher.flush('delayed_event'); - - print('After flush - Call count: $callCount'); - print('Processed payloads: $processedPayloads'); - - expect(callCount, equals(4), reason: 'Should process exactly 4 messages'); - expect(processedPayloads.toSet().length, equals(4), - reason: 'All payloads should be unique'); - - // Verify that all messages were removed from the queue - expect(mockMQClient.queuedMessages['delayed_events_queue']?.length, - equals(0), - reason: 'Queue should be empty after flush'); - - // Flush again to ensure no more messages are processed - await dispatcher.flush('delayed_event'); - expect(callCount, equals(4), - reason: 'Should still be 4 after second flush'); - }); - - test('shouldBroadcast', () async { - var broadcastEvent = BroadcastTestEvent(); - var callCount = 0; - - dispatcher.listen('BroadcastTestEvent', (dynamic event, dynamic payload) { - callCount++; - }); - - await dispatcher.dispatch(broadcastEvent); - expect(callCount, equals(1)); - }); - - test('shouldQueue', () async { - var queueEvent = QueueTestEvent(); - await dispatcher.dispatch(queueEvent); - expect(mockMQClient.queuedMessages['events_queue'], isNotEmpty); - expect(mockMQClient.queuedMessages['events_queue']!.first.payload, - containsPair('event', 'QueueTestEvent')); - }); - - test('forgetPushed removes only pushed events', () { - dispatcher.listen('event_pushed', (_, __) {}); - dispatcher.listen('normal_event', (_, __) {}); - - dispatcher.forgetPushed(); - - expect(dispatcher.hasListeners('event_pushed'), isFalse); - expect(dispatcher.hasListeners('normal_event'), isTrue); - }); - - test('setQueueResolver and setTransactionManagerResolver', () { - var queueResolverCalled = false; - var transactionManagerResolverCalled = false; - - dispatcher.setQueueResolver(() { - queueResolverCalled = true; - }); - - dispatcher.setTransactionManagerResolver(() { - transactionManagerResolverCalled = true; - }); - - // Trigger the resolvers - dispatcher.triggerQueueResolver(); - dispatcher.triggerTransactionManagerResolver(); - - expect(queueResolverCalled, isTrue); - expect(transactionManagerResolverCalled, isTrue); - }); - - test('getRawListeners returns unmodifiable map', () { - dispatcher.listen('test_event', (_, __) {}); - var rawListeners = dispatcher.getRawListeners(); - - expect(rawListeners, isA>>()); - expect(() => rawListeners['new_event'] = [], throwsUnsupportedError); - }); - - test('multiple listeners for same event', () async { - var callCount1 = 0; - var callCount2 = 0; - - dispatcher.listen('multi_event', (_, __) => callCount1++); - dispatcher.listen('multi_event', (_, __) => callCount2++); - - await dispatcher.dispatch('multi_event'); - - expect(callCount1, equals(1)); - expect(callCount2, equals(1)); - }); - }); -} - -abstract class MQClientWrapper { - Stream fetchQueue(String queueId); - void sendMessage({ - required Message message, - String? exchangeName, - String? routingKey, - }); - String declareQueue(String queueId); - void declareExchange({ - required String exchangeName, - required ExchangeType exchangeType, - }); - void bindQueue({ - required String queueId, - required String exchangeName, - String? bindingKey, - }); - void close(); -} - -class RealMQClientWrapper implements MQClientWrapper { - final MQClient _client; - - RealMQClientWrapper(this._client); - - @override - Stream fetchQueue(String queueId) => _client.fetchQueue(queueId); - - @override - void sendMessage({ - required Message message, - String? exchangeName, - String? routingKey, - }) => - _client.sendMessage( - message: message, - exchangeName: exchangeName, - routingKey: routingKey, - ); - - @override - String declareQueue(String queueId) => _client.declareQueue(queueId); - - @override - void declareExchange({ - required String exchangeName, - required ExchangeType exchangeType, - }) => - _client.declareExchange( - exchangeName: exchangeName, - exchangeType: exchangeType, - ); - - @override - void bindQueue({ - required String queueId, - required String exchangeName, - String? bindingKey, - }) => - _client.bindQueue( - queueId: queueId, - exchangeName: exchangeName, - bindingKey: bindingKey, - ); - - @override - void close() => _client.close(); -} - -class MockMQClient implements MQClient { - Map> queuedMessages = {}; - int _messageIdCounter = 0; - - void queueMessage(String queueName, Message message) { - queuedMessages.putIfAbsent(queueName, () => []).add(message); - print( - 'Queued message. Queue $queueName now has ${queuedMessages[queueName]?.length} messages'); - } - - @override - String declareQueue(String queueId) { - queuedMessages[queueId] = []; - return queueId; - } - - @override - void deleteQueue(String queueId) { - queuedMessages.remove(queueId); - } - - @override - Stream fetchQueue(String queueId) { - print('Fetching queue: $queueId'); - return Stream.fromIterable(queuedMessages[queueId] ?? []); - } - - @override - void sendMessage({ - required Message message, - String? exchangeName, - String? routingKey, - }) { - print('Sending message to queue: $routingKey'); - final newMessage = Message( - payload: message.payload, - headers: message.headers, - timestamp: message.timestamp, - id: 'msg_${_messageIdCounter++}', - ); - queueMessage(routingKey ?? '', newMessage); - } - - @override - Message? getLatestMessage(String queueId) { - final messages = queuedMessages[queueId]; - return messages?.isNotEmpty == true ? messages!.last : null; - } - - @override - void bindQueue({ - required String queueId, - required String exchangeName, - String? bindingKey, - }) { - // Implement if needed for your tests - } - - @override - void unbindQueue({ - required String queueId, - required String exchangeName, - String? bindingKey, - }) { - // Implement if needed for your tests - } - - @override - void declareExchange({ - required String exchangeName, - required ExchangeType exchangeType, - }) { - // Implement if needed for your tests - } - - @override - void deleteExchange(String exchangeName) { - // Implement if needed for your tests - } - - @override - List listQueues() { - return queuedMessages.keys.toList(); - } - - @override - void close() { - queuedMessages.clear(); - } - - @override - void deleteMessage(String queueId, Message message) { - print('Deleting message from queue: $queueId'); - queuedMessages[queueId]?.removeWhere((m) => m.id == message.id); - print( - 'After deletion, queue $queueId has ${queuedMessages[queueId]?.length} messages'); - } -} - -class BroadcastTestEvent implements AppEvent, ShouldBroadcast { - @override - List get props => []; - - @override - bool? get stringify => true; - - @override - DateTime get timestamp => DateTime.now(); -} - -class QueueTestEvent implements AppEvent, ShouldQueue { - @override - List get props => []; - - @override - bool? get stringify => true; - - @override - DateTime get timestamp => DateTime.now(); -} - -// This is a simple implementation of Reflector that does nothing -class EmptyReflector implements Reflector { - const EmptyReflector(); - - @override - ReflectedType reflectType(Type type) { - throw UnimplementedError(); - } - - @override - ReflectedInstance reflectInstance(Object object) { - throw UnimplementedError(); - } - - @override - ReflectedType reflectFutureOf(Type type) { - throw UnimplementedError(); - } - - @override - String? getName(Symbol symbol) { - // TODO: implement getName - throw UnimplementedError(); - } - - @override - ReflectedClass? reflectClass(Type clazz) { - // TODO: implement reflectClass - throw UnimplementedError(); - } - - @override - ReflectedFunction? reflectFunction(Function function) { - // TODO: implement reflectFunction - throw UnimplementedError(); - } -} diff --git a/packages/process/.gitignore b/packages/filesystem/.gitignore similarity index 100% rename from packages/process/.gitignore rename to packages/filesystem/.gitignore diff --git a/packages/process/CHANGELOG.md b/packages/filesystem/CHANGELOG.md similarity index 100% rename from packages/process/CHANGELOG.md rename to packages/filesystem/CHANGELOG.md diff --git a/packages/process/LICENSE.md b/packages/filesystem/LICENSE.md similarity index 100% rename from packages/process/LICENSE.md rename to packages/filesystem/LICENSE.md diff --git a/packages/filesystem/README.md b/packages/filesystem/README.md new file mode 100644 index 0000000..8b55e73 --- /dev/null +++ b/packages/filesystem/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/process/analysis_options.yaml b/packages/filesystem/analysis_options.yaml similarity index 100% rename from packages/process/analysis_options.yaml rename to packages/filesystem/analysis_options.yaml diff --git a/packages/filesystem/doc/.gitkeep b/packages/filesystem/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/filesystem/example/.gitkeep b/packages/filesystem/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/filesystem/lib/src/.gitkeep b/packages/filesystem/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/bus/pubspec.yaml b/packages/filesystem/pubspec.yaml similarity index 53% rename from packages/bus/pubspec.yaml rename to packages/filesystem/pubspec.yaml index deb1887..0c110e0 100644 --- a/packages/bus/pubspec.yaml +++ b/packages/filesystem/pubspec.yaml @@ -1,5 +1,5 @@ -name: platform_bus -description: The Bus Package for the Protevus Platform +name: platform_filesystem +description: The Filesystem Package for the Protevus Platform version: 0.0.1 homepage: https://protevus.com documentation: https://docs.protevus.com @@ -10,15 +10,8 @@ environment: # Add regular dependencies here. dependencies: - platform_container: ^9.0.0 - platform_core: ^9.0.0 - angel3_reactivex: ^9.0.0 - angel3_event_bus: ^9.0.0 - angel3_mq: ^9.0.0 # path: ^1.8.0 dev_dependencies: - build_runner: ^2.1.0 lints: ^3.0.0 - mockito: ^5.3.0 test: ^1.24.0 diff --git a/packages/filesystem/test/.gitkeep b/packages/filesystem/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sandbox/mqueue/.gitignore b/packages/foundation/.gitignore similarity index 100% rename from sandbox/mqueue/.gitignore rename to packages/foundation/.gitignore diff --git a/packages/queue/CHANGELOG.md b/packages/foundation/CHANGELOG.md similarity index 100% rename from packages/queue/CHANGELOG.md rename to packages/foundation/CHANGELOG.md diff --git a/packages/queue/LICENSE.md b/packages/foundation/LICENSE.md similarity index 100% rename from packages/queue/LICENSE.md rename to packages/foundation/LICENSE.md diff --git a/packages/foundation/README.md b/packages/foundation/README.md new file mode 100644 index 0000000..8b55e73 --- /dev/null +++ b/packages/foundation/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/queue/analysis_options.yaml b/packages/foundation/analysis_options.yaml similarity index 100% rename from packages/queue/analysis_options.yaml rename to packages/foundation/analysis_options.yaml diff --git a/packages/foundation/doc/.gitkeep b/packages/foundation/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/foundation/example/.gitkeep b/packages/foundation/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/foundation/lib/src/.gitkeep b/packages/foundation/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/foundation/pubspec.yaml b/packages/foundation/pubspec.yaml new file mode 100644 index 0000000..45034cd --- /dev/null +++ b/packages/foundation/pubspec.yaml @@ -0,0 +1,17 @@ +name: platform_foundation +description: The Foundation Package for the Protevus Platform +version: 0.0.1 +homepage: https://protevus.com +documentation: https://docs.protevus.com +repository: https://github.com/protevus/platformo + +environment: + sdk: ^3.4.2 + +# Add regular dependencies here. +dependencies: + # path: ^1.8.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/packages/foundation/test/.gitkeep b/packages/foundation/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/queue/.gitignore b/packages/http/.gitignore similarity index 87% rename from packages/queue/.gitignore rename to packages/http/.gitignore index 0b4272d..3cceda5 100644 --- a/packages/queue/.gitignore +++ b/packages/http/.gitignore @@ -5,6 +5,3 @@ # Avoid committing pubspec.lock for library packages; see # https://dart.dev/guides/libraries/private-files#pubspeclock. pubspec.lock - -*.mocks.dart -*.reflectable.dart diff --git a/packages/http/CHANGELOG.md b/packages/http/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/packages/http/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/http/LICENSE.md b/packages/http/LICENSE.md new file mode 100644 index 0000000..0fd0d03 --- /dev/null +++ b/packages/http/LICENSE.md @@ -0,0 +1,10 @@ +The MIT License (MIT) + +The Laravel Framework is Copyright (c) Taylor Otwell +The Fabric Framework is Copyright (c) Vieo, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/http/README.md b/packages/http/README.md new file mode 100644 index 0000000..8b55e73 --- /dev/null +++ b/packages/http/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/http/analysis_options.yaml b/packages/http/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/packages/http/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/packages/http/doc/.gitkeep b/packages/http/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/http/example/.gitkeep b/packages/http/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/http/pubspec.yaml b/packages/http/pubspec.yaml new file mode 100644 index 0000000..d029fea --- /dev/null +++ b/packages/http/pubspec.yaml @@ -0,0 +1,21 @@ +name: platfrom_http +description: The HTTP Package for the Protevus Platform +version: 0.0.1 +homepage: https://protevus.com +documentation: https://docs.protevus.com +repository: https://github.com/protevus/platformo + +environment: + sdk: '>=3.4.0 <4.0.0' + +# Add regular dependencies here. +dependencies: + sanitize_html: ^2.1.0 + email_validator: ^3.0.0 + validator_dart: ^0.1.0 + protevus_mime: ^0.0.1 + path: ^1.9.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/packages/http/test/.gitkeep b/packages/http/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/lucifer b/packages/lucifer new file mode 160000 index 0000000..27ec6a3 --- /dev/null +++ b/packages/lucifer @@ -0,0 +1 @@ +Subproject commit 27ec6a35a910fa60dcfc1d772fd7ecde660dfa4b diff --git a/packages/model/.gitignore b/packages/model/.gitignore deleted file mode 100644 index 24d6831..0000000 --- a/packages/model/.gitignore +++ /dev/null @@ -1,71 +0,0 @@ -# See https://www.dartlang.org/tools/private-files.html - -# Files and directories created by pub -.dart_tool -.packages -.pub/ -build/ - -# If you're building an application, you may want to check-in your pubspec.lock -pubspec.lock - -# Directory created by dartdoc -# If you don't generate documentation locally you can remove this line. -doc/api/ - -### Dart template -# See https://www.dartlang.org/tools/private-files.html - -# Files and directories created by pub - -# SDK 1.20 and later (no longer creates packages directories) - -# Older SDK versions -# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) -.project -.buildlog -**/packages/ - - -# Files created by dart2js -# (Most Dart developers will use pub build to compile Dart, use/modify these -# rules if you intend to use dart2js directly -# Convention is to use extension '.dart.js' for Dart compiled to Javascript to -# differentiate from explicit Javascript files) -*.dart.js -*.part.js -*.js.deps -*.js.map -*.info.json - -# Directory created by dartdoc - -# Don't commit pubspec lock file -# (Library packages only! Remove pattern if developing an application package) -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff: - -## VsCode -.vscode/ - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -.idea/ -/out/ -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties diff --git a/packages/model/AUTHORS.md b/packages/model/AUTHORS.md deleted file mode 100644 index ac95ab5..0000000 --- a/packages/model/AUTHORS.md +++ /dev/null @@ -1,12 +0,0 @@ -Primary Authors -=============== - -* __[Thomas Hii](dukefirehawk.apps@gmail.com)__ - - Thomas is the current maintainer of the code base. He has refactored and migrated the - code base to support NNBD. - -* __[Tobe O](thosakwe@gmail.com)__ - - Tobe has written much of the original code prior to NNBD migration. He has moved on and - is no longer involved with the project. diff --git a/packages/model/CHANGELOG.md b/packages/model/CHANGELOG.md deleted file mode 100644 index 86374d8..0000000 --- a/packages/model/CHANGELOG.md +++ /dev/null @@ -1,77 +0,0 @@ -# Change Log - -## 8.1.1 - -* Updated repository link - -## 8.1.0 - -* Updated `lints` to 3.0.0 -* Fixed analyser warnings - -## 8.0.0 - -* Require Dart >= 3.0 - -## 7.1.0 - -* Return -1 instead of throwing exception when id is null -* Added `idAsString` to return id or "" if null -* Added `AuditableModel` - -## 7.0.0 - -* Require Dart >= 2.17 - -## 6.0.0 - -* Require Dart >= 2.16 - -## 5.0.0 - -* Skipped release - -## 4.0.0 - -* Skipped release - -## 3.1.1 - -* Removed `error` - -## 3.1.0 - -* Updated linter to `package:lints` - -## 3.0.2 - -* Updated README -* Updated `idAsInt` to return `-1` instead of `null` for invalid id - -## 3.0.1 - -* Updated README - -## 3.0.0 - -* Migrated to support Dart >= 2.12 NNBD - -## 2.0.0 - -* Migrated to work with Dart >= 2.12 Non NNBD - -## 1.0.3 - -* `idAsInt` returns `null` when `id` is `null`. - -## 1.0.2 - -* `idAsInt` now uses `int.tryParse`. - -## 1.0.1 - -* Add `idAsInt`. - -## 1.0.0+1 - -* Update constraint to work with Dart 2. diff --git a/packages/model/LICENSE b/packages/model/LICENSE deleted file mode 100644 index df5e063..0000000 --- a/packages/model/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2021, dukefirehawk.com -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/model/README.md b/packages/model/README.md deleted file mode 100644 index ad5d520..0000000 --- a/packages/model/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Protevus Data Model - -![Pub Version (including pre-releases)](https://img.shields.io/pub/v/angel3_model?include_prereleases) -[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) -[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) -[![License](https://img.shields.io/github/license/dart-backend/angel)](https://github.com/dart-backend/angel/tree/master/packages/model/LICENSE) - -The basic data models for Protevus framework. - -```dart -import 'package:angel3_model/angel3_model.dart'; -``` - -The available data models are: - -* `Model` class - * A basic data model -* `AuditableModel` class - * A basic data model with audit log feature diff --git a/packages/model/analysis_options.yaml b/packages/model/analysis_options.yaml deleted file mode 100644 index 4a50340..0000000 --- a/packages/model/analysis_options.yaml +++ /dev/null @@ -1,2 +0,0 @@ - -include: package:lints/recommended.yaml \ No newline at end of file diff --git a/packages/model/example/main.dart b/packages/model/example/main.dart deleted file mode 100644 index 9c86106..0000000 --- a/packages/model/example/main.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:platform_model/model.dart'; - -void main() { - var todo = Todo(id: '34', isComplete: false); - print(todo.idAsInt == 34); -} - -class Todo extends Model { - String? text; - - bool isComplete; - - Todo( - {required String super.id, - this.text, - this.isComplete = false, - super.createdAt, - super.updatedAt}); -} diff --git a/packages/model/lib/model.dart b/packages/model/lib/model.dart deleted file mode 100644 index 0ee8fab..0000000 --- a/packages/model/lib/model.dart +++ /dev/null @@ -1,35 +0,0 @@ -/// Represents a generic data model with an ID and timestamps. -class Model { - /// A unique identifier corresponding to this item. - String? id; - - /// The time at which this item was created. - DateTime? createdAt; - - /// The last time at which this item was updated. - DateTime? updatedAt; - - Model({this.id, this.createdAt, this.updatedAt}); - - /// Returns the [id], parsed as an [int]. - int get idAsInt => id != null ? int.tryParse(id ?? "-1") ?? -1 : -1; - - /// Returns the [id] or "" if null. - String get idAsString => id ?? ""; -} - -/// Represents a generic data model with audit log feature. -class AuditableModel extends Model { - /// The authorized user who created the record. - String? createdBy; - - /// The user who updated the record last time. - String? updatedBy; - - AuditableModel( - {super.id, - super.createdAt, - this.createdBy, - super.updatedAt, - this.updatedBy}); -} diff --git a/packages/model/pubspec.yaml b/packages/model/pubspec.yaml deleted file mode 100644 index b388c63..0000000 --- a/packages/model/pubspec.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: platform_model -version: 9.0.0 -description: Protevus Platform basic data model class, no longer with the added weight of the whole framework. -homepage: https://protevus.com -documentation: https://docs.protevus.com -repository: https://git.protevus.com/protevus/platform/src/branch/main/packages/model -environment: - sdk: '>=3.3.0 <4.0.0' -dev_dependencies: - lints: ^4.0.0 - test: ^1.25.8 diff --git a/packages/pipeline/README.md b/packages/pipeline/README.md deleted file mode 100644 index 586a156..0000000 --- a/packages/pipeline/README.md +++ /dev/null @@ -1,380 +0,0 @@ -

    - -# Platform Pipeline - -A Laravel-compatible pipeline implementation in Dart, providing a robust way to pass objects through a series of operations. - -[![Pub Version](https://img.shields.io/pub/v/platform_pipeline)]() -[![Build Status](https://img.shields.io/github/workflow/status/platform/pipeline/tests)]() - -## Table of Contents - -- [Overview](#overview) -- [Features](#features) -- [Requirements](#requirements) -- [Installation](#installation) -- [Usage](#usage) - - [Basic Usage](#basic-usage) - - [Class-Based Pipes](#class-based-pipes) - - [Invokable Classes](#invokable-classes) - - [Using Different Method Names](#using-different-method-names) - - [Passing Parameters to Pipes](#passing-parameters-to-pipes) - - [Early Pipeline Termination](#early-pipeline-termination) - - [Conditional Pipeline Execution](#conditional-pipeline-execution) -- [Advanced Usage](#advanced-usage) - - [Working with Objects](#working-with-objects) - - [Async Operations](#async-operations) -- [Laravel API Compatibility](#laravel-api-compatibility) -- [Comparison with Laravel](#comparison-with-laravel) -- [Troubleshooting](#troubleshooting) -- [Testing](#testing) -- [Contributing](#contributing) -- [License](#license) - -## Overview - -Platform Pipeline is a 100% API-compatible port of Laravel's Pipeline to Dart. It allows you to pass an object through a series of operations (pipes) in a fluent, maintainable way. Each pipe can examine, modify, or replace the object before passing it to the next pipe in the sequence. - -## Features - -- 💯 100% Laravel Pipeline API compatibility -- 🔄 Support for class-based and callable pipes -- 🎯 Dependency injection through container integration -- ⚡ Async operation support -- 🔀 Conditional pipeline execution -- 🎭 Method name customization via `via()` -- 🎁 Parameter passing to pipes -- 🛑 Early pipeline termination -- 🧪 Comprehensive test coverage - -## Requirements - -- Dart SDK: >=2.17.0 <4.0.0 -- platform_container: ^1.0.0 - -## Installation - -Add this to your package's `pubspec.yaml` file: - -```yaml -dependencies: - platform_pipeline: ^1.0.0 -``` - -## Usage - -### Basic Usage - -```dart -import 'package:platform_pipeline/pipeline.dart'; -import 'package:platform_container/container.dart'; - -void main() async { - // Create a container instance - var container = Container(); - - // Create a pipeline - var result = await Pipeline(container) - .send('Hello') - .through([ - (String value, next) => next(value + ' World'), - (String value, next) => next(value + '!'), - ]) - .then((value) => value); - - print(result); // Outputs: Hello World! -} -``` - -### Class-Based Pipes - -```dart -class UppercasePipe { - Future handle(String value, Function next) async { - return next(value.toUpperCase()); - } -} - -class AddExclamationPipe { - Future handle(String value, Function next) async { - return next(value + '!'); - } -} - -void main() async { - var container = Container(); - - var result = await Pipeline(container) - .send('hello') - .through([ - UppercasePipe(), - AddExclamationPipe(), - ]) - .then((value) => value); - - print(result); // Outputs: HELLO! -} -``` - -### Invokable Classes - -```dart -class TransformPipe { - Future call(String value, Function next) async { - return next(value.toUpperCase()); - } -} - -void main() async { - var container = Container(); - - var result = await Pipeline(container) - .send('hello') - .through([TransformPipe()]) - .then((value) => value); - - print(result); // Outputs: HELLO -} -``` - -### Using Different Method Names - -```dart -class CustomPipe { - Future transform(String value, Function next) async { - return next(value.toUpperCase()); - } -} - -void main() async { - var container = Container(); - - var result = await Pipeline(container) - .send('hello') - .through([CustomPipe()]) - .via('transform') - .then((value) => value); - - print(result); // Outputs: HELLO -} -``` - -### Passing Parameters to Pipes - -```dart -class PrefixPipe { - Future handle( - String value, - Function next, [ - String prefix = '', - ]) async { - return next('$prefix$value'); - } -} - -void main() async { - var container = Container(); - container.registerFactory((c) => PrefixPipe()); - - var pipeline = Pipeline(container); - pipeline.registerPipeType('PrefixPipe', PrefixPipe); - - var result = await pipeline - .send('World') - .through('PrefixPipe:Hello ') - .then((value) => value); - - print(result); // Outputs: Hello World -} -``` - -### Early Pipeline Termination - -```dart -void main() async { - var container = Container(); - - var result = await Pipeline(container) - .send('hello') - .through([ - (value, next) => 'TERMINATED', // Pipeline stops here - (value, next) => next('NEVER REACHED'), - ]) - .then((value) => value); - - print(result); // Outputs: TERMINATED -} -``` - -### Conditional Pipeline Execution - -```dart -void main() async { - var container = Container(); - var shouldTransform = true; - - var result = await Pipeline(container) - .send('hello') - .when(() => shouldTransform, (Pipeline pipeline) { - pipeline.pipe([ - (value, next) => next(value.toUpperCase()), - ]); - }) - .then((value) => value); - - print(result); // Outputs: HELLO -} -``` - -## Advanced Usage - -### Working with Objects - -```dart -class User { - String name; - int age; - - User(this.name, this.age); -} - -class AgeValidationPipe { - Future handle(User user, Function next) async { - if (user.age < 18) { - throw Exception('User must be 18 or older'); - } - return next(user); - } -} - -class NameFormattingPipe { - Future handle(User user, Function next) async { - user.name = user.name.trim().toLowerCase(); - return next(user); - } -} - -void main() async { - var container = Container(); - - var user = User('John Doe ', 20); - - try { - user = await Pipeline(container) - .send(user) - .through([ - AgeValidationPipe(), - NameFormattingPipe(), - ]) - .then((value) => value); - - print('${user.name} is ${user.age} years old'); - // Outputs: john doe is 20 years old - } catch (e) { - print('Validation failed: $e'); - } -} -``` - -### Async Operations - -```dart -class AsyncTransformPipe { - Future handle(String value, Function next) async { - // Simulate async operation - await Future.delayed(Duration(seconds: 1)); - return next(value.toUpperCase()); - } -} - -void main() async { - var container = Container(); - - var result = await Pipeline(container) - .send('hello') - .through([AsyncTransformPipe()]) - .then((value) => value); - - print(result); // Outputs after 1 second: HELLO -} -``` - -## Laravel API Compatibility - -This package maintains 100% API compatibility with Laravel's Pipeline implementation. All Laravel Pipeline features are supported: - -- `send()` - Set the object being passed through the pipeline -- `through()` - Set the array of pipes -- `pipe()` - Push additional pipes onto the pipeline -- `via()` - Set the method to call on the pipes -- `then()` - Run the pipeline with a final destination callback -- `thenReturn()` - Run the pipeline and return the result - -## Comparison with Laravel - -| Feature | Laravel | Platform Pipeline | -|---------|---------|------------------| -| API Methods | ✓ | ✓ | -| Container Integration | ✓ | ✓ | -| Pipe Types | Class, Callable | Class, Callable | -| Async Support | ✗ | ✓ | -| Type Safety | ✗ | ✓ | -| Parameter Passing | ✓ | ✓ | -| Early Termination | ✓ | ✓ | -| Method Customization | ✓ | ✓ | -| Conditional Execution | ✓ | ✓ | - -## Troubleshooting - -### Common Issues - -1. Container Not Provided -```dart -// ❌ Wrong -var pipeline = Pipeline(null); - -// ✓ Correct -var container = Container(); -var pipeline = Pipeline(container); -``` - -2. Missing Type Registration -```dart -// ❌ Wrong -pipeline.through('CustomPipe:param'); - -// ✓ Correct -pipeline.registerPipeType('CustomPipe', CustomPipe); -pipeline.through('CustomPipe:param'); -``` - -3. Incorrect Method Name -```dart -// ❌ Wrong -class CustomPipe { - void process(value, next) {} // Wrong method name -} - -// ✓ Correct -class CustomPipe { - void handle(value, next) {} // Default method name -} -// Or specify the method name: -pipeline.via('process').through([CustomPipe()]); -``` - -## Testing - -Run the tests with: - -```bash -dart test -``` - -## Contributing - -Contributions are welcome! Please feel free to submit a Pull Request. - -## License - -This package is open-sourced software licensed under the MIT license. diff --git a/packages/pipeline/examples/async_pipeline.dart b/packages/pipeline/examples/async_pipeline.dart deleted file mode 100644 index 03f6dc1..0000000 --- a/packages/pipeline/examples/async_pipeline.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_pipeline/pipeline.dart'; - -class AsyncGreetingPipe { - Future handle(String input, Function next) async { - await Future.delayed(Duration(seconds: 1)); - return next('Hello, $input'); - } -} - -class AsyncExclamationPipe { - Future handle(String input, Function next) async { - await Future.delayed(Duration(seconds: 1)); - return next('$input!'); - } -} - -void main() async { - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - - app.container.registerSingleton((c) => Pipeline(c)); - - app.get('/', (req, res) async { - var pipeline = app.container.make(); - var result = await pipeline - .send('World') - .through(['AsyncGreetingPipe', 'AsyncExclamationPipe']).then( - (result) => result.toUpperCase()); - - res.write(result); // Outputs: "HELLO, WORLD!" (after 2 seconds) - }); - - await http.startServer('localhost', 3000); - print('Server started on http://localhost:3000'); -} diff --git a/packages/pipeline/examples/basic_usage.dart b/packages/pipeline/examples/basic_usage.dart deleted file mode 100644 index 736a354..0000000 --- a/packages/pipeline/examples/basic_usage.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_pipeline/pipeline.dart'; - -class GreetingPipe { - dynamic handle(String input, Function next) { - return next('Hello, $input'); - } -} - -class ExclamationPipe { - dynamic handle(String input, Function next) { - return next('$input!'); - } -} - -void main() async { - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - - app.container.registerSingleton((c) => Pipeline(c)); - - app.get('/', (req, res) async { - var pipeline = app.container.make(); - var result = await pipeline - .send('World') - .through(['GreetingPipe', 'ExclamationPipe']).then( - (result) => result.toUpperCase()); - - res.write(result); // Outputs: "HELLO, WORLD!" - }); - - await http.startServer('localhost', 3000); - print('Server started on http://localhost:3000'); -} diff --git a/packages/pipeline/examples/error_handling.dart b/packages/pipeline/examples/error_handling.dart deleted file mode 100644 index 6f8aa84..0000000 --- a/packages/pipeline/examples/error_handling.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_pipeline/pipeline.dart'; - -class ErrorPipe { - dynamic handle(String input, Function next) { - throw Exception('Simulated error'); - } -} - -void main() async { - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - - app.container.registerSingleton((c) => Pipeline(c)); - - app.get('/', (req, res) async { - var pipeline = app.container.make(); - try { - await pipeline - .send('World') - .through(['ErrorPipe']).then((result) => result.toUpperCase()); - } catch (e) { - res.write('Error occurred: ${e.toString()}'); - return; - } - - res.write('This should not be reached'); - }); - - await http.startServer('localhost', 3000); - print('Server started on http://localhost:3000'); -} diff --git a/packages/pipeline/examples/mixed_pipes.dart b/packages/pipeline/examples/mixed_pipes.dart deleted file mode 100644 index 0e17a71..0000000 --- a/packages/pipeline/examples/mixed_pipes.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_pipeline/pipeline.dart'; - -class GreetingPipe { - dynamic handle(String input, Function next) { - return next('Hello, $input'); - } -} - -void main() async { - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - - app.container.registerSingleton((c) => Pipeline(c)); - - app.get('/', (req, res) async { - var pipeline = app.container.make(); - var result = await pipeline.send('World').through([ - 'GreetingPipe', - (String input, Function next) => next('$input!'), - (String input, Function next) async { - await Future.delayed(Duration(seconds: 1)); - return next(input.toUpperCase()); - }, - ]).then((result) => 'Final result: $result'); - - res.write( - result); // Outputs: "Final result: HELLO, WORLD!" (after 1 second) - }); - - await http.startServer('localhost', 3000); - print('Server started on http://localhost:3000'); -} diff --git a/packages/pipeline/lib/pipeline.dart b/packages/pipeline/lib/pipeline.dart deleted file mode 100644 index 9b73f9f..0000000 --- a/packages/pipeline/lib/pipeline.dart +++ /dev/null @@ -1,5 +0,0 @@ -library; - -export 'src/pipeline.dart'; -export 'src/conditionable.dart'; -export 'src/pipeline_contract.dart'; diff --git a/packages/pipeline/lib/src/conditionable.dart b/packages/pipeline/lib/src/conditionable.dart deleted file mode 100644 index ce796a0..0000000 --- a/packages/pipeline/lib/src/conditionable.dart +++ /dev/null @@ -1,16 +0,0 @@ -/// Provides conditional execution methods for the pipeline. -mixin Conditionable { - T when(bool Function() callback, void Function(T) callback2) { - if (callback()) { - callback2(this as T); - } - return this as T; - } - - T unless(bool Function() callback, void Function(T) callback2) { - if (!callback()) { - callback2(this as T); - } - return this as T; - } -} diff --git a/packages/pipeline/lib/src/pipeline.dart b/packages/pipeline/lib/src/pipeline.dart deleted file mode 100644 index 37d9d68..0000000 --- a/packages/pipeline/lib/src/pipeline.dart +++ /dev/null @@ -1,241 +0,0 @@ -import 'dart:async'; -import 'dart:mirrors'; -import 'package:platform_container/container.dart'; -import 'package:logging/logging.dart'; -import 'pipeline_contract.dart'; -import 'conditionable.dart'; - -/// Defines the signature for a pipe function. -typedef PipeFunction = FutureOr Function( - dynamic passable, FutureOr Function(dynamic) next); - -/// The primary class for building and executing pipelines. -class Pipeline with Conditionable implements PipelineContract { - /// The container implementation. - Container? _container; - - final Map _typeMap = {}; - - /// The object being passed through the pipeline. - dynamic _passable; - - /// The array of class pipes. - final List _pipes = []; - - /// The method to call on each pipe. - String _method = 'handle'; - - /// Logger for the pipeline. - final Logger _logger = Logger('Pipeline'); - - /// Create a new class instance. - Pipeline(this._container); - - void registerPipeType(String name, Type type) { - _typeMap[name] = type; - } - - /// Set the object being sent through the pipeline. - @override - Pipeline send(dynamic passable) { - _passable = passable; - return this; - } - - /// Set the array of pipes. - @override - Pipeline through(dynamic pipes) { - if (_container == null) { - throw Exception( - 'A container instance has not been passed to the Pipeline.'); - } - _pipes.addAll(pipes is Iterable ? pipes.toList() : [pipes]); - return this; - } - - /// Push additional pipes onto the pipeline. - @override - Pipeline pipe(dynamic pipes) { - if (_container == null) { - throw Exception( - 'A container instance has not been passed to the Pipeline.'); - } - _pipes.addAll(pipes is Iterable ? pipes.toList() : [pipes]); - return this; - } - - /// Set the method to call on the pipes. - @override - Pipeline via(String method) { - _method = method; - return this; - } - - /// Run the pipeline with a final destination callback. - @override - Future then(FutureOr Function(dynamic) destination) async { - if (_container == null) { - throw Exception( - 'A container instance has not been passed to the Pipeline.'); - } - - var pipeline = (dynamic passable) async => await destination(passable); - - for (var pipe in _pipes.reversed) { - var next = pipeline; - pipeline = (dynamic passable) async { - return await carry(pipe, passable, next); - }; - } - - return await pipeline(_passable); - } - - /// Run the pipeline and return the result. - @override - Future thenReturn() async { - return then((passable) => passable); - } - - /// Get a Closure that represents a slice of the application onion. - Future carry(dynamic pipe, dynamic passable, Function next) async { - try { - if (pipe is Function) { - return await pipe(passable, next); - } - - if (pipe is String) { - if (_container == null) { - throw Exception('Container is null, cannot resolve pipe: $pipe'); - } - - final parts = parsePipeString(pipe); - final pipeClass = parts[0]; - final parameters = parts.length > 1 ? parts.sublist(1) : []; - - Type? pipeType; - if (_typeMap.containsKey(pipeClass)) { - pipeType = _typeMap[pipeClass]; - } else { - // Try to resolve from mirrors - try { - for (var lib in currentMirrorSystem().libraries.values) { - for (var decl in lib.declarations.values) { - if (decl is ClassMirror && - decl.simpleName == Symbol(pipeClass)) { - pipeType = decl.reflectedType; - break; - } - } - if (pipeType != null) break; - } - } catch (_) {} - - if (pipeType == null) { - throw Exception('Type not registered for pipe: $pipe'); - } - } - - var instance = _container?.make(pipeType); - if (instance == null) { - throw Exception('Unable to resolve pipe: $pipe'); - } - - return await invokeMethod( - instance, _method, [passable, next, ...parameters]); - } - - if (pipe is Type) { - if (_container == null) { - throw Exception('Container is null, cannot resolve pipe type'); - } - - var instance = _container?.make(pipe); - if (instance == null) { - throw Exception('Unable to resolve pipe type: $pipe'); - } - - return await invokeMethod(instance, _method, [passable, next]); - } - - // Handle instance of a class - if (pipe is Object) { - return await invokeMethod(pipe, _method, [passable, next]); - } - - throw Exception('Unsupported pipe type: ${pipe.runtimeType}'); - } catch (e) { - return handleException(passable, e); - } - } - - /// Parse full pipe string to get name and parameters. - List parsePipeString(String pipe) { - var parts = pipe.split(':'); - return [parts[0], if (parts.length > 1) ...parts[1].split(',')]; - } - - /// Get the array of configured pipes. - List pipes() { - return List.unmodifiable(_pipes); - } - - /// Get the container instance. - Container getContainer() { - if (_container == null) { - throw Exception( - 'A container instance has not been passed to the Pipeline.'); - } - return _container!; - } - - /// Set the container instance. - Pipeline setContainer(Container container) { - _container = container; - return this; - } - - /// Handle the value returned from each pipe before passing it to the next. - dynamic handleCarry(dynamic carry) { - if (carry is Future) { - return carry.then((value) => value ?? _passable); - } - return carry ?? _passable; - } - - Future invokeMethod( - dynamic instance, String methodName, List arguments) async { - // First try call() for invokable objects - if (instance is Function) { - return await instance(arguments[0], arguments[1]); - } - - var instanceMirror = reflect(instance); - - // Check for call method first (invokable objects) - var callSymbol = Symbol('call'); - if (instanceMirror.type.declarations.containsKey(callSymbol)) { - var result = instanceMirror.invoke(callSymbol, arguments); - return await result.reflectee; - } - - // Then try the specified method - var methodSymbol = Symbol(methodName); - if (!instanceMirror.type.declarations.containsKey(methodSymbol)) { - throw Exception('Method $methodName not found on instance: $instance'); - } - - var result = instanceMirror.invoke(methodSymbol, arguments); - return await result.reflectee; - } - - /// Handle the given exception. - dynamic handleException(dynamic passable, Object e) { - if (e is Exception && e.toString().contains('Container is null')) { - throw Exception( - 'A container instance has not been passed to the Pipeline.'); - } - _logger.severe('Exception occurred in pipeline', e); - throw e; - } -} diff --git a/packages/pipeline/lib/src/pipeline_contract.dart b/packages/pipeline/lib/src/pipeline_contract.dart deleted file mode 100644 index 2b45e7f..0000000 --- a/packages/pipeline/lib/src/pipeline_contract.dart +++ /dev/null @@ -1,9 +0,0 @@ -/// Represents a series of "pipes" through which an object can be passed. -abstract class PipelineContract { - PipelineContract send(dynamic passable); - PipelineContract through(dynamic pipes); - PipelineContract pipe(dynamic pipes); - PipelineContract via(String method); - Future then(dynamic Function(dynamic) destination); - Future thenReturn(); -} diff --git a/packages/pipeline/test/laravel_pipeline_test.dart b/packages/pipeline/test/laravel_pipeline_test.dart deleted file mode 100644 index 0fcdeaa..0000000 --- a/packages/pipeline/test/laravel_pipeline_test.dart +++ /dev/null @@ -1,258 +0,0 @@ -import 'package:platform_container/container.dart'; -import 'package:platform_pipeline/pipeline.dart'; -import 'package:test/test.dart'; - -// Test pipe classes to match Laravel's test classes -class PipelineTestPipeOne { - static String? testPipeOne; - - Future handle(dynamic piped, Function next) async { - testPipeOne = piped.toString(); - return next(piped); - } - - Future differentMethod(dynamic piped, Function next) async { - return next(piped); - } -} - -class PipelineTestPipeTwo { - static String? testPipeOne; - - Future call(dynamic piped, Function next) async { - testPipeOne = piped.toString(); - return next(piped); - } -} - -class PipelineTestParameterPipe { - static List? testParameters; - - Future handle(dynamic piped, Function next, - [String? parameter1, String? parameter2]) async { - testParameters = [ - if (parameter1 != null) parameter1, - if (parameter2 != null) parameter2 - ]; - return next(piped); - } -} - -void main() { - group('Laravel Pipeline Tests', () { - late Container container; - late Pipeline pipeline; - - setUp(() { - container = Container(const EmptyReflector()); - pipeline = Pipeline(container); - - // Register test classes with container - container - .registerFactory((c) => PipelineTestPipeOne()); - container - .registerFactory((c) => PipelineTestPipeTwo()); - container.registerFactory( - (c) => PipelineTestParameterPipe()); - - // Register types with pipeline - pipeline.registerPipeType('PipelineTestPipeOne', PipelineTestPipeOne); - pipeline.registerPipeType('PipelineTestPipeTwo', PipelineTestPipeTwo); - pipeline.registerPipeType( - 'PipelineTestParameterPipe', PipelineTestParameterPipe); - - // Reset static test variables - PipelineTestPipeOne.testPipeOne = null; - PipelineTestPipeTwo.testPipeOne = null; - PipelineTestParameterPipe.testParameters = null; - }); - - test('Pipeline basic usage', () async { - String? testPipeTwo; - final pipeTwo = (dynamic piped, Function next) { - testPipeTwo = piped.toString(); - return next(piped); - }; - - final result = await Pipeline(container) - .send('foo') - .through([PipelineTestPipeOne(), pipeTwo]).then((piped) => piped); - - expect(result, equals('foo')); - expect(PipelineTestPipeOne.testPipeOne, equals('foo')); - expect(testPipeTwo, equals('foo')); - }); - - test('Pipeline usage with objects', () async { - final result = await Pipeline(container) - .send('foo') - .through([PipelineTestPipeOne()]).then((piped) => piped); - - expect(result, equals('foo')); - expect(PipelineTestPipeOne.testPipeOne, equals('foo')); - }); - - test('Pipeline usage with invokable objects', () async { - final result = await Pipeline(container) - .send('foo') - .through([PipelineTestPipeTwo()]).then((piped) => piped); - - expect(result, equals('foo')); - expect(PipelineTestPipeTwo.testPipeOne, equals('foo')); - }); - - test('Pipeline usage with callable', () async { - String? testPipeOne; - final function = (dynamic piped, Function next) { - testPipeOne = 'foo'; - return next(piped); - }; - - var result = await Pipeline(container) - .send('foo') - .through([function]).then((piped) => piped); - - expect(result, equals('foo')); - expect(testPipeOne, equals('foo')); - - testPipeOne = null; - - result = - await Pipeline(container).send('bar').through(function).thenReturn(); - - expect(result, equals('bar')); - expect(testPipeOne, equals('foo')); - }); - - test('Pipeline usage with pipe', () async { - final object = {'value': 0}; - - final function = (dynamic obj, Function next) { - obj['value']++; - return next(obj); - }; - - final result = await Pipeline(container) - .send(object) - .through([function]).pipe([function]).then((piped) => piped); - - expect(result, equals(object)); - expect(object['value'], equals(2)); - }); - - test('Pipeline usage with invokable class', () async { - final result = await Pipeline(container) - .send('foo') - .through([PipelineTestPipeTwo()]).then((piped) => piped); - - expect(result, equals('foo')); - expect(PipelineTestPipeTwo.testPipeOne, equals('foo')); - }); - - test('Then method is not called if the pipe returns', () async { - String thenValue = '(*_*)'; - String secondValue = '(*_*)'; - - final result = await Pipeline(container).send('foo').through([ - (value, next) => 'm(-_-)m', - (value, next) { - secondValue = 'm(-_-)m'; - return next(value); - }, - ]).then((piped) { - thenValue = '(0_0)'; - return piped; - }); - - expect(result, equals('m(-_-)m')); - // The then callback is not called - expect(thenValue, equals('(*_*)')); - // The second pipe is not called - expect(secondValue, equals('(*_*)')); - }); - - test('Then method input value', () async { - String? pipeReturn; - String? thenArg; - - final result = await Pipeline(container).send('foo').through([ - (value, next) async { - final nextValue = await next('::not_foo::'); - pipeReturn = nextValue; - return 'pipe::$nextValue'; - } - ]).then((piped) { - thenArg = piped; - return 'then$piped'; - }); - - expect(result, equals('pipe::then::not_foo::')); - expect(thenArg, equals('::not_foo::')); - }); - - test('Pipeline usage with parameters', () async { - final parameters = ['one', 'two']; - - final result = await Pipeline(container) - .send('foo') - .through('PipelineTestParameterPipe:${parameters.join(',')}') - .then((piped) => piped); - - expect(result, equals('foo')); - expect(PipelineTestParameterPipe.testParameters, equals(parameters)); - }); - - test('Pipeline via changes the method being called on the pipes', () async { - final result = await Pipeline(container) - .send('data') - .through(PipelineTestPipeOne()) - .via('differentMethod') - .then((piped) => piped); - - expect(result, equals('data')); - }); - - test('Pipeline throws exception on resolve without container', () async { - expect( - () => Pipeline(null) - .send('data') - .through(PipelineTestPipeOne()) - .then((piped) => piped), - throwsA(isA().having( - (e) => e.toString(), - 'message', - contains( - 'A container instance has not been passed to the Pipeline')))); - }); - - test('Pipeline thenReturn method runs pipeline then returns passable', - () async { - final result = await Pipeline(container) - .send('foo') - .through([PipelineTestPipeOne()]).thenReturn(); - - expect(result, equals('foo')); - expect(PipelineTestPipeOne.testPipeOne, equals('foo')); - }); - - test('Pipeline conditionable', () async { - var result = await Pipeline(container).send('foo').when(() => true, - (Pipeline pipeline) { - pipeline.pipe([PipelineTestPipeOne()]); - }).then((piped) => piped); - - expect(result, equals('foo')); - expect(PipelineTestPipeOne.testPipeOne, equals('foo')); - - PipelineTestPipeOne.testPipeOne = null; - - result = await Pipeline(container).send('foo').when(() => false, - (Pipeline pipeline) { - pipeline.pipe([PipelineTestPipeOne()]); - }).then((piped) => piped); - - expect(result, equals('foo')); - expect(PipelineTestPipeOne.testPipeOne, isNull); - }); - }); -} diff --git a/packages/pipeline/test/pipeline_test.dart b/packages/pipeline/test/pipeline_test.dart deleted file mode 100644 index ae67501..0000000 --- a/packages/pipeline/test/pipeline_test.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:test/test.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_container/container.dart'; -import 'package:platform_container/mirrors.dart'; -import 'package:platform_pipeline/pipeline.dart'; - -class AddExclamationPipe { - Future handle(String input, Function next) async { - return await next('$input!'); - } -} - -class UppercasePipe { - Future handle(String input, Function next) async { - return await next(input.toUpperCase()); - } -} - -void main() { - late Application app; - late Container container; - late Pipeline pipeline; - - setUp(() { - app = Application(reflector: MirrorsReflector()); - container = app.container; - container.registerSingleton(AddExclamationPipe()); - container.registerSingleton(UppercasePipe()); - pipeline = Pipeline(container); - pipeline.registerPipeType('AddExclamationPipe', AddExclamationPipe); - pipeline.registerPipeType('UppercasePipe', UppercasePipe); - }); - - test('Pipeline should process simple string pipes', () async { - var result = await pipeline.send('hello').through( - ['AddExclamationPipe', 'UppercasePipe']).then((res) async => res); - expect(result, equals('HELLO!')); - }); - - test('Pipeline should process function pipes', () async { - var result = await pipeline.send('hello').through([ - (String input, Function next) async { - var result = await next('$input, WORLD'); - return result; - }, - (String input, Function next) async { - var result = await next(input.toUpperCase()); - return result; - }, - ]).then((res) async => res as String); - - expect(result, equals('HELLO, WORLD')); - }); - - test('Pipeline should handle mixed pipe types', () async { - var result = await pipeline.send('hello').through([ - 'AddExclamationPipe', - (String input, Function next) async { - var result = await next(input.toUpperCase()); - return result; - }, - ]).then((res) async => res as String); - expect(result, equals('HELLO!')); - }); - - test('Pipeline should handle async pipes', () async { - var result = await pipeline.send('hello').through([ - 'UppercasePipe', - (String input, Function next) async { - await Future.delayed(Duration(milliseconds: 100)); - return next('$input, WORLD'); - }, - ]).then((res) async => res as String); - expect(result, equals('HELLO, WORLD')); - }); - - test('Pipeline should throw exception for unresolvable pipe', () { - expect( - () => pipeline - .send('hello') - .through(['NonExistentPipe']).then((res) => res), - throwsA(isA()), - ); - }); - - test('Pipeline should allow chaining of pipes', () async { - var result = await pipeline - .send('hello') - .pipe('AddExclamationPipe') - .pipe('UppercasePipe') - .then((res) async => res as String); - expect(result, equals('HELLO!')); - }); - - test('Pipeline should respect the order of pipes', () async { - var result1 = await pipeline - .send('hello') - .through(['AddExclamationPipe', 'UppercasePipe']).then((res) => res); - var result2 = await pipeline - .send('hello') - .through(['UppercasePipe', 'AddExclamationPipe']).then((res) => res); - expect(result1, equals('HELLO!')); - expect(result2, equals('HELLO!!')); - expect(result1, isNot(equals(result2))); - }); -} diff --git a/packages/process/README.md b/packages/process/README.md deleted file mode 100644 index 757f4c9..0000000 --- a/packages/process/README.md +++ /dev/null @@ -1 +0,0 @@ -

    \ No newline at end of file diff --git a/packages/process/examples/basic_process/main.dart b/packages/process/examples/basic_process/main.dart deleted file mode 100644 index 8415927..0000000 --- a/packages/process/examples/basic_process/main.dart +++ /dev/null @@ -1,36 +0,0 @@ -// examples/basic_process/main.dart -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_process/angel3_process.dart'; -import 'package:logging/logging.dart'; -import 'package:platform_container/mirrors.dart'; - -void main() async { - Logger.root.level = Level.ALL; - Logger.root.onRecord.listen((record) { - print('${record.level.name}: ${record.time}: ${record.message}'); - }); - - // Create an Angel application with MirrorsReflector - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - - // Use dependency injection for ProcessManager - app.container.registerSingleton(ProcessManager()); - - app.get('/', (req, res) async { - // Use the ioc function to get the ProcessManager instance - var processManager = await req.container?.make(); - - var process = await processManager?.start( - 'example_process', - 'echo', - ['Hello, Angel3 Process!'], - ); - var result = await process?.run(); - res.writeln('Process output: ${result?.output.trim()}'); - }); - - await http.startServer('localhost', 3000); - print('Server listening at http://localhost:3000'); -} diff --git a/packages/process/examples/process_pipeline/main.dart b/packages/process/examples/process_pipeline/main.dart deleted file mode 100644 index 7950162..0000000 --- a/packages/process/examples/process_pipeline/main.dart +++ /dev/null @@ -1,37 +0,0 @@ -// examples/process_pipeline/main.dart -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_process/angel3_process.dart'; -import 'package:logging/logging.dart'; -import 'package:platform_container/mirrors.dart'; - -void main() async { - Logger.root.level = Level.ALL; - Logger.root.onRecord.listen((record) { - print('${record.level.name}: ${record.time}: ${record.message}'); - }); - - // Create an Angel application with MirrorsReflector - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - - // Register ProcessManager as a singleton in the container - app.container.registerSingleton(ProcessManager()); - - app.get('/', (req, res) async { - // Use dependency injection to get the ProcessManager instance - var processManager = await req.container?.make(); - - var processes = [ - angel3Process('echo', ['Hello']), - angel3Process('sed', ['s/Hello/Greetings/']), - angel3Process('tr', ['[:lower:]', '[:upper:]']), - ]; - - var result = await processManager?.pipeline(processes); - res.writeln('Pipeline output: ${result?.output.trim()}'); - }); - - await http.startServer('localhost', 3000); - print('Server listening at http://localhost:3000'); -} diff --git a/packages/process/examples/process_pool/main.dart b/packages/process/examples/process_pool/main.dart deleted file mode 100644 index f8f21e4..0000000 --- a/packages/process/examples/process_pool/main.dart +++ /dev/null @@ -1,37 +0,0 @@ -// examples/process_pool/main.dart -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_process/angel3_process.dart'; -import 'package:logging/logging.dart'; -import 'package:platform_container/mirrors.dart'; - -void main() async { - Logger.root.level = Level.ALL; - Logger.root.onRecord.listen((record) { - print('${record.level.name}: ${record.time}: ${record.message}'); - }); - - // Create an Angel application with MirrorsReflector - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - - // Register ProcessManager as a singleton in the container - app.container.registerSingleton(ProcessManager()); - - app.get('/', (req, res) async { - // Use dependency injection to get the ProcessManager instance - var processManager = await req.container?.make(); - - var processes = - List.generate(5, (index) => angel3Process('echo', ['Process $index'])); - var results = await processManager?.pool(processes, concurrency: 3); - var output = results - ?.map((result) => - '${result.process.command} output: ${result.output.trim()}') - .join('\n'); - res.write(output); - }); - - await http.startServer('localhost', 3000); - print('Server listening at http://localhost:3000'); -} diff --git a/packages/process/examples/web_server_with_processes/main.dart b/packages/process/examples/web_server_with_processes/main.dart deleted file mode 100644 index 1559db1..0000000 --- a/packages/process/examples/web_server_with_processes/main.dart +++ /dev/null @@ -1,67 +0,0 @@ -// examples/web_server_with_processes/main.dart -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_process/angel3_process.dart'; -import 'package:file/local.dart'; -import 'package:logging/logging.dart'; -import 'package:angel3_mustache/angel3_mustache.dart'; -import 'package:platform_container/mirrors.dart'; - -void main() async { - Logger.root.level = Level.ALL; - Logger.root.onRecord.listen((record) { - print('${record.level.name}: ${record.time}: ${record.message}'); - }); - - // Create an Angel application with MirrorsReflector - var app = Application(reflector: MirrorsReflector()); - var http = PlatformHttp(app); - - // Register dependencies in the container - app.container.registerSingleton(const LocalFileSystem()); - app.container.registerSingleton(ProcessManager()); - - // Set up the view renderer - var fs = await app.container.make(); - var viewsDirectory = fs.directory('views'); - //await app.configure(mustache(viewsDirectory)); - - app.get('/', (req, res) async { - await res.render('index'); - }); - - app.post('/run-process', (req, res) async { - var body = await req.bodyAsMap; - var command = body['command'] as String?; - var args = (body['args'] as String?)?.split(' ') ?? []; - - if (command == null || command.isEmpty) { - throw PlatformHttpException.badRequest(message: 'Command is required'); - } - - // Use dependency injection to get the ProcessManager instance - var processManager = await req.container?.make(); - - var process = await processManager?.start( - 'user_process', - command, - args, - ); - var result = await process?.run(); - - await res.json({ - 'output': result?.output.trim(), - 'exitCode': result?.exitCode, - }); - }); - - app.fallback((req, res) => throw PlatformHttpException.notFound()); - - app.errorHandler = (e, req, res) { - res.writeln('Error: ${e.message}'); - return false; - }; - - await http.startServer('localhost', 3000); - print('Server listening at http://localhost:3000'); -} diff --git a/packages/process/examples/web_server_with_processes/views/index.mustache b/packages/process/examples/web_server_with_processes/views/index.mustache deleted file mode 100644 index cb29244..0000000 --- a/packages/process/examples/web_server_with_processes/views/index.mustache +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - Angel3 Process Example - - -

    Run a Process

    -
    - - -
    - - -
    - -
    -
    - - - - diff --git a/packages/process/lib/angel3_process.dart b/packages/process/lib/angel3_process.dart deleted file mode 100644 index 1965d4a..0000000 --- a/packages/process/lib/angel3_process.dart +++ /dev/null @@ -1,7 +0,0 @@ -library; - -export 'src/process.dart'; -export 'src/process_helper.dart'; -export 'src/process_manager.dart'; -export 'src/process_pipeline.dart'; -export 'src/process_pool.dart'; diff --git a/packages/process/lib/src/process.dart b/packages/process/lib/src/process.dart deleted file mode 100644 index 4049683..0000000 --- a/packages/process/lib/src/process.dart +++ /dev/null @@ -1,250 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:convert'; - -// import 'package:angel3_framework/angel3_framework.dart'; -// import 'package:angel3_mq/mq.dart'; -// import 'package:angel3_reactivex/angel3_reactivex.dart'; -// import 'package:angel3_event_bus/event_bus.dart'; -import 'package:logging/logging.dart'; - -class Angel3Process { - final String _command; - final List _arguments; - final String? _workingDirectory; - final Map? _environment; - final Duration? _timeout; - final bool _tty; - final bool _enableReadError; - final Logger _logger; - - late final StreamController> _outputController; - late final StreamController> _errorController; - late final Completer _outputCompleter; - late final Completer _errorCompleter; - final Completer _errorOutputCompleter = Completer(); - bool _isOutputComplete = false; - bool _isErrorComplete = false; - - Process? _process; - DateTime? _startTime; - DateTime? _endTime; - bool _isDisposed = false; - - Angel3Process( - this._command, - this._arguments, { - String? workingDirectory, - Map? environment, - Duration? timeout, - bool tty = false, - bool enableReadError = true, - Logger? logger, - }) : _workingDirectory = workingDirectory, - _environment = environment, - _timeout = timeout, - _tty = tty, - _enableReadError = enableReadError, - _logger = logger ?? Logger('Angel3Process'), - _outputController = StreamController>.broadcast(), - _errorController = StreamController>.broadcast(), - _outputCompleter = Completer(), - _errorCompleter = Completer(); - - // Add this public getter - String get command => _command; - int? get pid => _process?.pid; - DateTime? get startTime => _startTime; - DateTime? get endTime => _endTime; - - Stream> get output => _outputController.stream; - Stream> get errorOutput => _errorController.stream; - - // Future get outputAsString => _outputCompleter.future; - // Future get errorOutputAsString => _errorCompleter.future; - - Future get exitCode => _process?.exitCode ?? Future.value(-1); - bool get isRunning => _process != null && !_process!.kill(); - - Future start() async { - if (_isDisposed) { - throw StateError('This process has been disposed and cannot be reused.'); - } - _startTime = DateTime.now(); - - try { - _process = await Process.start( - _command, - _arguments, - workingDirectory: _workingDirectory, - environment: _environment, - runInShell: _tty, - ); - - _process!.stdout.listen( - (data) { - _outputController.add(data); - }, - onDone: () { - if (!_isOutputComplete) { - _isOutputComplete = true; - _outputController.close(); - } - }, - onError: (error) { - _logger.severe('Error in stdout stream', error); - _outputController.addError(error); - if (!_isOutputComplete) { - _isOutputComplete = true; - _outputController.close(); - } - }, - ); - - var errorBuffer = StringBuffer(); - _process!.stderr.listen( - (data) { - _errorController.add(data); - errorBuffer.write(utf8.decode(data)); - }, - onDone: () { - if (!_isErrorComplete) { - _isErrorComplete = true; - _errorController.close(); - _errorOutputCompleter.complete(errorBuffer.toString()); - } - }, - onError: (error) { - _logger.severe('Error in stderr stream', error); - _errorController.addError(error); - if (!_isErrorComplete) { - _isErrorComplete = true; - _errorController.close(); - _errorOutputCompleter.completeError(error); - } - }, - ); - - _logger.info('Process started: $_command ${_arguments.join(' ')}'); - } catch (e) { - _logger.severe('Failed to start process', e); - rethrow; - } - return this; - } - - Future run() async { - await start(); - if (_timeout != null) { - return await runWithTimeout(_timeout!); - } - final exitCode = await this.exitCode; - final output = await outputAsString; - final errorOutput = await _errorOutputCompleter.future; - _endTime = DateTime.now(); - return ProcessResult(pid!, exitCode, output, errorOutput); - } - - Future runWithTimeout(Duration timeout) async { - final exitCodeFuture = this.exitCode.timeout(timeout, onTimeout: () { - kill(); - throw TimeoutException('Process timed out', timeout); - }); - - try { - final exitCode = await exitCodeFuture; - final output = await outputAsString; - final errorOutput = await _errorOutputCompleter.future; - _endTime = DateTime.now(); - return ProcessResult(pid!, exitCode, output, errorOutput); - } catch (e) { - if (e is TimeoutException) { - throw e; - } - rethrow; - } - } - - Future write(String input) async { - if (_process != null) { - _process!.stdin.write(input); - await _process!.stdin.flush(); - } else { - throw StateError('Process has not been started'); - } - } - - Future writeLines(List lines) async { - for (final line in lines) { - await write('$line\n'); - } - } - - Future kill({ProcessSignal signal = ProcessSignal.sigterm}) async { - if (_process != null) { - _logger.info('Killing process with signal: ${signal.name}'); - final result = _process!.kill(signal); - if (!result) { - _logger.warning('Failed to kill process with signal: ${signal.name}'); - } - } - } - - bool sendSignal(ProcessSignal signal) { - return _process?.kill(signal) ?? false; - } - - Future dispose() async { - if (!_isDisposed) { - _isDisposed = true; - await _outputController.close(); - await _errorController.close(); - if (!_outputCompleter.isCompleted) { - _outputCompleter.complete(''); - } - if (!_errorCompleter.isCompleted) { - _errorCompleter.complete(''); - } - await kill(); - _logger.info('Process disposed: $_command ${_arguments.join(' ')}'); - } - } - - Future get outputAsString async { - var buffer = await output.transform(utf8.decoder).join(); - return buffer; - } - - Future get errorOutputAsString => _errorOutputCompleter.future; -} - -class ProcessResult { - final int pid; - final int exitCode; - final String output; - final String errorOutput; - - ProcessResult(this.pid, this.exitCode, this.output, this.errorOutput); - - @override - String toString() { - return 'ProcessResult(pid: $pid, exitCode: $exitCode, output: ${output.length} chars, errorOutput: ${errorOutput.length} chars)'; - } -} - -class InvokedProcess { - final Angel3Process process; - final DateTime startTime; - final DateTime endTime; - final int exitCode; - final String output; - final String errorOutput; - - InvokedProcess(this.process, this.startTime, this.endTime, this.exitCode, - this.output, this.errorOutput); - - @override - String toString() { - return 'InvokedProcess(command: ${process._command}, arguments: ${process._arguments}, startTime: $startTime, endTime: $endTime, exitCode: $exitCode)'; - } -} diff --git a/packages/process/lib/src/process_helper.dart b/packages/process/lib/src/process_helper.dart deleted file mode 100644 index 5166698..0000000 --- a/packages/process/lib/src/process_helper.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'process.dart'; - -Angel3Process angel3Process( - String command, - List arguments, { - String? workingDirectory, - Map? environment, - Duration? timeout, - bool tty = false, - bool enableReadError = true, -}) { - return Angel3Process( - command, - arguments, - workingDirectory: workingDirectory, - environment: environment, - timeout: timeout, - tty: tty, - enableReadError: enableReadError, - ); -} diff --git a/packages/process/lib/src/process_manager.dart b/packages/process/lib/src/process_manager.dart deleted file mode 100644 index f2d48b6..0000000 --- a/packages/process/lib/src/process_manager.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -// import 'package:angel3_framework/angel3_framework.dart'; -// import 'package:angel3_mq/mq.dart'; -// import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:angel3_event_bus/event_bus.dart'; -import 'package:logging/logging.dart'; - -import 'process.dart'; -import 'process_pool.dart'; -import 'process_pipeline.dart'; - -class ProcessManager { - final Map _processes = {}; - final EventBus _eventBus = EventBus(); - final List _subscriptions = []; - final Logger _logger = Logger('ProcessManager'); - - Future start( - String id, - String command, - List arguments, { - String? workingDirectory, - Map? environment, - Duration? timeout, - bool tty = false, - bool enableReadError = true, - }) async { - if (_processes.containsKey(id)) { - throw Exception('Process with id $id already exists'); - } - - final process = Angel3Process( - command, - arguments, - workingDirectory: workingDirectory, - environment: environment, - timeout: timeout, - tty: tty, - enableReadError: enableReadError, - logger: Logger('Angel3Process:$id'), - ); - - try { - await process.start(); - _processes[id] = process; - - _eventBus.fire(ProcessStartedEvent(id, process) as AppEvent); - - process.exitCode.then((exitCode) { - _eventBus.fire(ProcessExitedEvent(id, exitCode) as AppEvent); - _processes.remove(id); - }); - - _logger.info('Started process with id: $id'); - return process; - } catch (e) { - _logger.severe('Failed to start process with id: $id', e); - rethrow; - } - } - - Angel3Process? get(String id) => _processes[id]; - - Future kill(String id, - {ProcessSignal signal = ProcessSignal.sigterm}) async { - final process = _processes[id]; - if (process != null) { - await process.kill(signal: signal); - _processes.remove(id); - _logger.info('Killed process with id: $id'); - } else { - _logger.warning('Attempted to kill non-existent process with id: $id'); - } - } - - Future killAll({ProcessSignal signal = ProcessSignal.sigterm}) async { - _logger.info('Killing all processes'); - await Future.wait( - _processes.values.map((process) => process.kill(signal: signal))); - _processes.clear(); - } - - Stream get events => _eventBus.on(); - - Future> pool(List processes, - {int concurrency = 5}) async { - _logger.info('Running process pool with concurrency: $concurrency'); - final pool = ProcessPool(concurrency: concurrency); - return await pool.run(processes); - } - - Future pipeline(List processes) async { - _logger.info('Running process pipeline'); - final pipeline = ProcessPipeline(processes); - return await pipeline.run(); - } - - void dispose() { - _logger.info('Disposing ProcessManager'); - - // Cancel all event subscriptions - for (var subscription in _subscriptions) { - subscription.cancel(); - } - _subscriptions.clear(); - - // Dispose all processes - for (var process in _processes.values) { - process.dispose(); - } - _processes.clear(); - - _logger.info('ProcessManager disposed'); - } -} - -abstract class ProcessEvent extends AppEvent {} - -class ProcessStartedEvent extends ProcessEvent { - final String id; - final Angel3Process process; - - ProcessStartedEvent(this.id, this.process); - - @override - String toString() => - 'ProcessStartedEvent(id: $id, command: ${process.command})'; - - @override - // TODO: implement props - List get props => throw UnimplementedError(); -} - -class ProcessExitedEvent extends ProcessEvent { - final String id; - final int exitCode; - - ProcessExitedEvent(this.id, this.exitCode); - - @override - String toString() => 'ProcessExitedEvent(id: $id, exitCode: $exitCode)'; - - @override - // TODO: implement props - List get props => throw UnimplementedError(); -} diff --git a/packages/process/lib/src/process_pipeline.dart b/packages/process/lib/src/process_pipeline.dart deleted file mode 100644 index 449c708..0000000 --- a/packages/process/lib/src/process_pipeline.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:async'; -import 'package:logging/logging.dart'; -import 'process.dart'; - -class ProcessPipeline { - final List _processes; - final Logger _logger = Logger('ProcessPipeline'); - - ProcessPipeline(this._processes); - - Future run() async { - String input = ''; - DateTime startTime = DateTime.now(); - DateTime endTime; - int lastExitCode = 0; - - _logger - .info('Starting process pipeline with ${_processes.length} processes'); - - for (final process in _processes) { - _logger.info('Running process: ${process.command}'); - if (input.isNotEmpty) { - await process.write(input); - } - final result = await process.run(); - input = result.output; - lastExitCode = result.exitCode; - _logger.info( - 'Process completed: ${process.command} with exit code $lastExitCode'); - if (lastExitCode != 0) { - _logger.warning( - 'Pipeline stopped due to non-zero exit code: $lastExitCode'); - break; - } - } - - endTime = DateTime.now(); - _logger.info( - 'Pipeline completed. Total duration: ${endTime.difference(startTime)}'); - - return InvokedProcess( - _processes.last, - startTime, - endTime, - lastExitCode, - input, - '', - ); - } -} diff --git a/packages/process/lib/src/process_pool.dart b/packages/process/lib/src/process_pool.dart deleted file mode 100644 index 67b323f..0000000 --- a/packages/process/lib/src/process_pool.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:async'; -import 'package:logging/logging.dart'; -import 'process.dart'; - -class ProcessPool { - final int concurrency; - final List _queue = []; - int _running = 0; - final Logger _logger = Logger('ProcessPool'); - - ProcessPool({this.concurrency = 5}); - - Future> run(List processes) async { - final results = []; - final completer = Completer>(); - - _logger.info('Starting process pool with ${processes.length} processes'); - - for (final process in processes) { - _queue.add(() async { - try { - final result = await _runProcess(process); - results.add(result); - } catch (e) { - _logger.severe('Error running process in pool', e); - } finally { - _running--; - _processQueue(); - if (_running == 0 && _queue.isEmpty) { - completer.complete(results); - } - } - }); - } - - _processQueue(); - - return completer.future; - } - - void _processQueue() { - while (_running < concurrency && _queue.isNotEmpty) { - _running++; - _queue.removeAt(0)(); - } - } - - Future _runProcess(Angel3Process process) async { - _logger.info('Running process: ${process.command}'); - final result = await process.run(); - _logger.info( - 'Process completed: ${process.command} with exit code ${result.exitCode}'); - return InvokedProcess( - process, - process.startTime!, - process.endTime!, - result.exitCode, - result.output, - result.errorOutput, - ); - } -} diff --git a/packages/process/lib/src/process_service_provider.dart b/packages/process/lib/src/process_service_provider.dart deleted file mode 100644 index c762c94..0000000 --- a/packages/process/lib/src/process_service_provider.dart +++ /dev/null @@ -1,24 +0,0 @@ -/* import 'package:angel3_framework/angel3_framework.dart'; -import 'package:logging/logging.dart'; -import 'process_manager.dart'; - -class ProcessServiceProvider extends Provider { - final Logger _logger = Logger('ProcessServiceProvider'); - - @override - void registers() { - container.singleton((_) => ProcessManager()); - _logger.info('Registered ProcessManager'); - } - - @override - void boots(Angel app) { - app.shutdownHooks.add((_) async { - _logger.info('Shutting down ProcessManager'); - final processManager = app.container.make(); - await processManager.killAll(); - processManager.dispose(); - }); - _logger.info('Added ProcessManager shutdown hook'); - } -} */ diff --git a/packages/process/pubspec.yaml b/packages/process/pubspec.yaml deleted file mode 100644 index b837bae..0000000 --- a/packages/process/pubspec.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: platform_process -description: The Process Package for the Protevus Platform -version: 0.0.1 -homepage: https://protevus.com -documentation: https://docs.protevus.com -repository: https://github.com/protevus/platformo - -environment: - sdk: ^3.4.2 - -# Add regular dependencies here. -dependencies: - platform_container: ^8.0.0 - platform_core: ^8.0.0 - angel3_mq: ^8.0.0 - angel3_mustache: ^8.0.0 - angel3_event_bus: ^8.0.0 - angel3_reactivex: ^8.0.0 - file: ^7.0.0 - logging: ^1.1.0 - path: ^1.8.0 - -dev_dependencies: - lints: ^3.0.0 - test: ^1.24.0 diff --git a/packages/process/test/process_test.dart b/packages/process/test/process_test.dart deleted file mode 100644 index ab3a7eb..0000000 --- a/packages/process/test/process_test.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:platform_process/angel3_process.dart'; -import 'package:test/test.dart'; - -void main() { - late Angel3Process process; - - setUp(() { - process = Angel3Process('echo', ['Hello, World!']); - }); - - tearDown(() async { - await process.dispose(); - }); - - test('Angel3Process initialization', () { - expect(process.command, equals('echo')); - expect(process.startTime, isNull); - expect(process.endTime, isNull); - }); - - test('Start and run a simple process', () async { - var result = await process.run(); - expect(process.startTime, isNotNull); - expect(result.exitCode, equals(0)); - expect(result.output.trim(), equals('Hello, World!')); - expect(process.endTime, isNotNull); - }); - - test('Stream output', () async { - await process.start(); - var outputStream = process.output.transform(utf8.decoder); - var streamOutput = await outputStream.join(); - await process.exitCode; // Wait for the process to complete - expect(streamOutput.trim(), equals('Hello, World!')); - }); - - test('Error output for non-existent command', () { - var errorProcess = Angel3Process('non_existent_command', []); - expect(errorProcess.start(), throwsA(isA())); - }); - - test('Process with error output', () async { - Angel3Process errorProcess; - if (Platform.isWindows) { - errorProcess = Angel3Process('cmd', ['/c', 'dir', '/invalid_argument']); - } else { - errorProcess = Angel3Process('ls', ['/non_existent_directory']); - } - - print('Starting error process...'); - var result = await errorProcess.run(); - print('Error process completed.'); - print('Exit code: ${result.exitCode}'); - print('Standard output: "${result.output}"'); - print('Error output: "${result.errorOutput}"'); - - expect(result.exitCode, isNot(0), reason: 'Expected non-zero exit code'); - expect(result.errorOutput.trim(), isNotEmpty, - reason: 'Expected non-empty error output'); - - await errorProcess.dispose(); - }); - - test('Kill running process', () async { - var longRunningProcess = Angel3Process('sleep', ['5']); - await longRunningProcess.start(); - await longRunningProcess.kill(); - var exitCode = await longRunningProcess.exitCode; - expect(exitCode, isNot(0)); - }); - - test('Process timeout', () async { - var timeoutProcess = - Angel3Process('sleep', ['10'], timeout: Duration(seconds: 1)); - expect(() => timeoutProcess.run(), throwsA(isA())); - }, timeout: Timeout(Duration(seconds: 5))); -} diff --git a/packages/process/test/process_test_extended.dart b/packages/process/test/process_test_extended.dart deleted file mode 100644 index ab82810..0000000 --- a/packages/process/test/process_test_extended.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'dart:async'; -import 'dart:io' show Directory, Platform, ProcessSignal; -import 'package:platform_process/angel3_process.dart'; -import 'package:test/test.dart'; -import 'package:path/path.dart' as path; - -void main() { - late Angel3Process process; - - setUp(() { - process = Angel3Process('echo', ['Hello, World!']); - }); - - tearDown(() async { - await process.dispose(); - }); - - // ... (existing tests remain the same) - - test('Process with custom environment variables', () async { - var command = Platform.isWindows ? 'cmd' : 'sh'; - var args = Platform.isWindows - ? ['/c', 'echo %TEST_VAR%'] - : ['-c', r'echo $TEST_VAR']; // Use a raw string for Unix-like systems - - var envProcess = - Angel3Process(command, args, environment: {'TEST_VAR': 'custom_value'}); - - var result = await envProcess.run(); - expect(result.output.trim(), equals('custom_value')); - }); - - test('Process with custom working directory', () async { - var tempDir = Directory.systemTemp.createTempSync(); - try { - var workingDirProcess = Angel3Process(Platform.isWindows ? 'cmd' : 'pwd', - Platform.isWindows ? ['/c', 'cd'] : [], - workingDirectory: tempDir.path); - var result = await workingDirProcess.run(); - expect(path.equals(result.output.trim(), tempDir.path), isTrue); - } finally { - tempDir.deleteSync(); - } - }); - - test('Process with input', () async { - var catProcess = Angel3Process('cat', []); - await catProcess.start(); - catProcess.write('Hello, stdin!'); - await catProcess.kill(); // End the process - var output = await catProcess.outputAsString; - expect(output.trim(), equals('Hello, stdin!')); - }); - - test('Longer-running process', () async { - var sleepProcess = Angel3Process(Platform.isWindows ? 'timeout' : 'sleep', - Platform.isWindows ? ['/t', '2'] : ['2']); - var startTime = DateTime.now(); - await sleepProcess.run(); - var endTime = DateTime.now(); - expect(endTime.difference(startTime).inSeconds, greaterThanOrEqualTo(2)); - }); - - test('Multiple concurrent processes', () async { - var processes = - List.generate(5, (_) => Angel3Process('echo', ['concurrent'])); - var results = await Future.wait(processes.map((p) => p.run())); - for (var result in results) { - expect(result.output.trim(), equals('concurrent')); - } - }); - - test('Process signaling', () async { - if (!Platform.isWindows) { - // SIGSTOP/SIGCONT are not available on Windows - var longProcess = Angel3Process('sleep', ['10']); - await longProcess.start(); - await longProcess.sendSignal(ProcessSignal.sigstop); - // Process should be stopped, so it shouldn't complete immediately - expect(longProcess.exitCode, doesNotComplete); - await longProcess.sendSignal(ProcessSignal.sigcont); - await longProcess.kill(); - expect(await longProcess.exitCode, isNot(0)); - } - }); - - test('Edge case: empty command', () { - expect(() => Angel3Process('', []), throwsA(isA())); - }); - - test('Edge case: empty arguments list', () { - // This should not throw an error - expect(() => Angel3Process('echo', []), returnsNormally); - }); - - test('Edge case: invalid argument type', () { - // This should throw a compile-time error, but we can't test for that directly - // Instead, we can test for runtime type checking if implemented - expect(() => Angel3Process('echo', [1, 2, 3] as dynamic), - throwsA(isA())); - }); -} diff --git a/packages/queue/README.md b/packages/queue/README.md deleted file mode 100644 index 757f4c9..0000000 --- a/packages/queue/README.md +++ /dev/null @@ -1 +0,0 @@ -

    \ No newline at end of file diff --git a/packages/queue/lib/queue.dart b/packages/queue/lib/queue.dart deleted file mode 100644 index 585829f..0000000 --- a/packages/queue/lib/queue.dart +++ /dev/null @@ -1,75 +0,0 @@ -/// The Queue Package for the Protevus Platform. -/// -/// This package provides a Laravel-compatible queue implementation in Dart, offering -/// features like job queuing, delayed job processing, job encryption, and transaction-aware -/// job dispatching. -/// -/// # Basic Usage -/// -/// ```dart -/// final queue = Queue(container, eventBus, mqClient); -/// -/// // Push a job to the queue -/// await queue.push(MyJob()); -/// -/// // Push a job with delay -/// await queue.later(Duration(minutes: 5), MyJob()); -/// -/// // Push a job to a specific queue -/// await queue.pushOn('high-priority', MyJob()); -/// ``` -/// -/// # Features -/// -/// - Job queuing and processing -/// - Delayed job execution -/// - Job encryption -/// - Transaction-aware job dispatching -/// - Event-based job monitoring -/// - Queue connection management -/// -/// # Events -/// -/// The queue system fires two main events: -/// - [JobQueueingEvent]: Fired before a job is queued -/// - [JobQueuedEvent]: Fired after a job has been queued -/// -/// # Job Interfaces -/// -/// Jobs can implement various interfaces to modify their behavior: -/// - [ShouldBeEncrypted]: Job payload will be encrypted -/// - [ShouldQueueAfterCommit]: Job will be queued after database transactions commit -/// - [HasMaxExceptions]: Specify maximum number of exceptions before job fails -/// - [HasFailOnTimeout]: Specify if job should fail on timeout -/// - [HasTimeout]: Specify job timeout duration -/// - [HasTries]: Specify maximum number of retry attempts -/// - [HasBackoff]: Specify delay between retry attempts -/// - [HasRetryUntil]: Specify when to stop retrying -/// - [HasAfterCommit]: Specify if job should run after commit -library platform_queue; - -// Core queue implementation -export 'src/queue.dart'; - -// Events -export 'src/job_queued_event.dart'; -export 'src/job_queueing_event.dart'; - -// Job interfaces -export 'src/should_be_encrypted.dart'; -export 'src/should_queue_after_commit.dart'; - -// Re-export commonly used types and interfaces -export 'src/queue.dart' show Queue, InvalidPayloadException; - -// Job configuration interfaces -export 'src/queue.dart' show HasMaxExceptions, HasFailOnTimeout, HasTimeout; -export 'src/queue.dart' - show HasDisplayName, HasTries, HasBackoff, HasRetryUntil; -export 'src/queue.dart' show HasAfterCommit, HasShouldBeEncrypted; - -// Support interfaces -export 'src/queue.dart' show Encrypter, TransactionManager; - -// Time utilities -export 'src/queue.dart' show InteractsWithTime; diff --git a/packages/queue/lib/src/job_queued_event.dart b/packages/queue/lib/src/job_queued_event.dart deleted file mode 100644 index 09b1301..0000000 --- a/packages/queue/lib/src/job_queued_event.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:angel3_event_bus/event_bus.dart'; -import 'package:equatable/equatable.dart'; - -/// Event fired after a job has been successfully queued. -/// -/// This event is dispatched after a job has been successfully added to the queue, -/// providing information about the queued job including its ID, payload, and any -/// specified delay. -/// -/// Example: -/// ```dart -/// eventBus.on().listen((event) { -/// print('Job ${event.jobId} queued on ${event.queue}'); -/// print('Will execute after: ${event.delay}'); -/// }); -/// ``` -class JobQueuedEvent extends AppEvent { - /// The name of the queue connection. - final String connectionName; - - /// The name of the specific queue the job was added to. - final String? queue; - - /// The unique identifier assigned to the queued job. - final String jobId; - - /// The job instance that was queued. - final dynamic job; - - /// The serialized payload of the job. - final String payload; - - /// The delay before the job should be processed, if any. - final Duration? delay; - - /// Creates a new [JobQueuedEvent]. - /// - /// [connectionName] is the name of the queue connection. - /// [queue] is the specific queue name, if any. - /// [jobId] is the unique identifier assigned to the job. - /// [job] is the actual job instance. - /// [payload] is the serialized job data. - /// [delay] is the optional delay before processing. - JobQueuedEvent(this.connectionName, this.queue, this.jobId, this.job, - this.payload, this.delay); - - @override - List get props => - [connectionName, queue, jobId, job, payload, delay]; - - @override - Map toJson() { - return { - 'connectionName': connectionName, - 'queue': queue, - 'jobId': jobId, - 'job': job.toString(), - 'payload': payload, - 'delay': delay?.inMilliseconds, - }; - } - - /// The event name used for identification. - @override - String get name => 'job.queued'; - - @override - String toString() => - 'JobQueuedEvent(connectionName: $connectionName, queue: $queue, jobId: $jobId, delay: $delay)'; -} diff --git a/packages/queue/lib/src/job_queueing_event.dart b/packages/queue/lib/src/job_queueing_event.dart deleted file mode 100644 index 35fc590..0000000 --- a/packages/queue/lib/src/job_queueing_event.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:angel3_event_bus/event_bus.dart'; -import 'package:equatable/equatable.dart'; - -/// Event fired before a job is queued. -/// -/// This event is dispatched just before a job is added to the queue, -/// allowing listeners to perform actions or validations before the job -/// is actually queued. -/// -/// Example: -/// ```dart -/// eventBus.on().listen((event) { -/// print('About to queue job on ${event.queue}'); -/// if (event.delay != null) { -/// print('Will be delayed by ${event.delay}'); -/// } -/// }); -/// ``` -/// -/// This event can be particularly useful for: -/// - Logging job attempts -/// - Validating job parameters before queueing -/// - Monitoring queue activity -/// - Implementing queue-based metrics -class JobQueueingEvent extends AppEvent { - /// The name of the queue connection. - final String connectionName; - - /// The name of the specific queue the job will be added to. - final String? queue; - - /// The job instance to be queued. - final dynamic job; - - /// The serialized payload of the job. - final String payload; - - /// The delay before the job should be processed, if any. - final Duration? delay; - - /// Creates a new [JobQueueingEvent]. - /// - /// [connectionName] is the name of the queue connection. - /// [queue] is the specific queue name, if any. - /// [job] is the actual job instance to be queued. - /// [payload] is the serialized job data. - /// [delay] is the optional delay before processing. - JobQueueingEvent( - this.connectionName, this.queue, this.job, this.payload, this.delay); - - @override - List get props => [connectionName, queue, job, payload, delay]; - - @override - Map toJson() { - return { - 'connectionName': connectionName, - 'queue': queue, - 'job': job.toString(), - 'payload': payload, - 'delay': delay?.inMilliseconds, - }; - } - - /// The event name used for identification. - @override - String get name => 'job.queueing'; - - @override - String toString() => - 'JobQueueingEvent(connectionName: $connectionName, queue: $queue, delay: $delay)'; -} diff --git a/packages/queue/lib/src/queue.dart b/packages/queue/lib/src/queue.dart deleted file mode 100644 index 2233816..0000000 --- a/packages/queue/lib/src/queue.dart +++ /dev/null @@ -1,396 +0,0 @@ -// lib/src/queue.dart - -import 'dart:async'; -import 'dart:convert'; - -import 'package:platform_container/container.dart'; -import 'package:angel3_event_bus/event_bus.dart'; -import 'package:angel3_mq/mq.dart'; -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:crypto/crypto.dart'; -import 'package:uuid/uuid.dart'; - -import 'job_queueing_event.dart'; -import 'job_queued_event.dart'; -import 'should_be_encrypted.dart'; -import 'should_queue_after_commit.dart'; - -abstract class Queue with InteractsWithTime { - /// The IoC container instance. - final Container container; - final EventBus eventBus; - final MQClient mq; - final Subject jobSubject; - final Uuid uuid = Uuid(); - - /// The connection name for the queue. - String _connectionName; - - /// Indicates that jobs should be dispatched after all database transactions have committed. - bool dispatchAfterCommit; - - /// The create payload callbacks. - static final List _createPayloadCallbacks = []; - - Queue(this.container, this.eventBus, this.mq, - {String connectionName = 'default', this.dispatchAfterCommit = false}) - : _connectionName = connectionName, - jobSubject = PublishSubject() { - _setupJobObservable(); - } - - void _setupJobObservable() { - jobSubject.stream.listen((job) { - // Process the job - print('Processing job: $job'); - // Implement your job processing logic here - }); - } - - Future pushOn(String queue, dynamic job, [dynamic data = '']) { - return push(job, data, queue); - } - - Future laterOn(String queue, Duration delay, dynamic job, - [dynamic data = '']) { - return later(delay, job, data, queue); - } - - Future bulk(List jobs, - [dynamic data = '', String? queue]) async { - for (var job in jobs) { - await push(job, data, queue); - } - } - - // Add this method - void setContainer(Container container) { - // This method might not be necessary in Dart, as we're using final for container - // But we can implement it for API compatibility - throw UnsupportedError( - 'Container is final and cannot be changed after initialization'); - } - - // Update createPayload method to include exception handling - Future createPayload(dynamic job, String queue, - [dynamic data = '']) async { - if (job is Function) { - // TODO: Implement CallQueuedClosure equivalent - throw UnimplementedError('Closure jobs are not yet supported'); - } - - try { - final payload = jsonEncode(await createPayloadMap(job, queue, data)); - return payload; - } catch (e) { - throw InvalidPayloadException('Unable to JSON encode payload: $e'); - } - } - - Future> createPayloadMap(dynamic job, String queue, - [dynamic data = '']) async { - if (job is Object) { - return createObjectPayload(job, queue); - } else { - return createStringPayload(job.toString(), queue, data); - } - } - - Future> createObjectPayload( - Object job, String queue) async { - final payload = await withCreatePayloadHooks(queue, { - 'uuid': const Uuid().v4(), - 'displayName': getDisplayName(job), - 'job': 'CallQueuedHandler@call', // TODO: Implement CallQueuedHandler - 'maxTries': getJobTries(job), - 'maxExceptions': job is HasMaxExceptions ? job.maxExceptions : null, - 'failOnTimeout': job is HasFailOnTimeout ? job.failOnTimeout : false, - 'backoff': getJobBackoff(job), - 'timeout': job is HasTimeout ? job.timeout : null, - 'retryUntil': getJobExpiration(job), - 'data': { - 'commandName': job.runtimeType.toString(), - 'command': job, - }, - }); - - final command = jobShouldBeEncrypted(job) && container.has() - ? container.make().encrypt(jsonEncode(job)) - : jsonEncode(job); - - payload['data'] = { - ...payload['data'] as Map, - 'commandName': job.runtimeType.toString(), - 'command': command, - }; - - return payload; - } - - String getDisplayName(Object job) { - if (job is HasDisplayName) { - return job.displayName(); - } - return job.runtimeType.toString(); - } - - int? getJobTries(dynamic job) { - if (job is HasTries) { - return job.tries; - } - return null; - } - - String? getJobBackoff(dynamic job) { - if (job is HasBackoff) { - final backoff = job.backoff; - if (backoff == null) return null; - if (backoff is Duration) { - return backoff.inSeconds.toString(); - } - if (backoff is List) { - return backoff.map((d) => d.inSeconds).join(','); - } - } - return null; - } - - int? getJobExpiration(dynamic job) { - if (job is HasRetryUntil) { - final retryUntil = job.retryUntil; - if (retryUntil == null) return null; - return retryUntil.millisecondsSinceEpoch ~/ 1000; - } - return null; - } - - bool jobShouldBeEncrypted(Object job) { - return job is ShouldBeEncrypted || - (job is HasShouldBeEncrypted && job.shouldBeEncrypted); - } - - Future> createStringPayload( - String job, String queue, dynamic data) async { - return withCreatePayloadHooks(queue, { - 'uuid': const Uuid().v4(), - 'displayName': job.split('@')[0], - 'job': job, - 'maxTries': null, - 'maxExceptions': null, - 'failOnTimeout': false, - 'backoff': null, - 'timeout': null, - 'data': data, - }); - } - - static void createPayloadUsing(Function? callback) { - if (callback == null) { - _createPayloadCallbacks.clear(); - } else { - _createPayloadCallbacks.add(callback); - } - } - - Future> withCreatePayloadHooks( - String queue, Map payload) async { - if (_createPayloadCallbacks.isNotEmpty) { - for (var callback in _createPayloadCallbacks) { - final result = await callback(_connectionName, queue, payload); - if (result is Map) { - payload = {...payload, ...result}; - } - } - } - return payload; - } - - Future enqueueUsing( - dynamic job, - String payload, - String? queue, - Duration? delay, - Future Function(String, String?, Duration?) callback, - ) async { - final String jobId = uuid.v4(); // Generate a unique job ID - - if (shouldDispatchAfterCommit(job) && container.has()) { - return container.make().addCallback(() async { - await raiseJobQueueingEvent(queue, job, payload, delay); - final result = await callback(payload, queue, delay); - await raiseJobQueuedEvent(queue, jobId, job, payload, delay); - return result; - }); - } - - await raiseJobQueueingEvent(queue, job, payload, delay); - final result = await callback(payload, queue, delay); - await raiseJobQueuedEvent(queue, jobId, job, payload, delay); - - // Use angel3_mq to publish the job - mq.sendMessage( - message: Message( - headers: {'jobId': jobId}, // Include jobId in headers - payload: payload, - timestamp: DateTime.now().toIso8601String(), - ), - exchangeName: '', // Use default exchange - routingKey: queue ?? 'default', - ); - - // Use angel3_reactivex to add the job to the subject - jobSubject.add(job); - - return result; - } - - bool shouldDispatchAfterCommit(dynamic job) { - if (job is ShouldQueueAfterCommit) { - return true; - } - if (job is HasAfterCommit) { - return job.afterCommit; - } - return dispatchAfterCommit; - } - - Future raiseJobQueueingEvent( - String? queue, dynamic job, String payload, Duration? delay) async { - if (container.has()) { - final eventBus = container.make(); - eventBus - .fire(JobQueueingEvent(_connectionName, queue, job, payload, delay)); - } - } - - Future raiseJobQueuedEvent(String? queue, dynamic jobId, dynamic job, - String payload, Duration? delay) async { - if (container.has()) { - final eventBus = container.make(); - eventBus.fire( - JobQueuedEvent(_connectionName, queue, jobId, job, payload, delay)); - } - } - - String get connectionName => _connectionName; - - set connectionName(String name) { - _connectionName = name; - } - - Container getContainer() => container; - - // Abstract methods to be implemented by subclasses - // Implement the push method - Future push(dynamic job, [dynamic data = '', String? queue]) async { - final payload = await createPayload(job, queue ?? 'default', data); - return enqueueUsing(job, payload, queue, null, (payload, queue, _) async { - final jobId = Uuid().v4(); - mq.sendMessage( - message: Message( - id: jobId, - headers: {}, - payload: payload, - timestamp: DateTime.now().toIso8601String(), - ), - exchangeName: '', - routingKey: queue ?? 'default', - ); - return jobId; - }); - } - - // Implement the later method - Future later(Duration delay, dynamic job, - [dynamic data = '', String? queue]) async { - final payload = await createPayload(job, queue ?? 'default', data); - return enqueueUsing(job, payload, queue, delay, - (payload, queue, delay) async { - final jobId = Uuid().v4(); - await Future.delayed(delay!); - mq.sendMessage( - message: Message( - id: jobId, - headers: {}, - payload: payload, - timestamp: DateTime.now().toIso8601String(), - ), - exchangeName: '', - routingKey: queue ?? 'default', - ); - return jobId; - }); - } - - // Cleanup method - void dispose() { - jobSubject.close(); - } -} - -// Additional interfaces and classes - -abstract class HasMaxExceptions { - int? get maxExceptions; -} - -abstract class HasFailOnTimeout { - bool get failOnTimeout; -} - -abstract class HasTimeout { - Duration? get timeout; -} - -abstract class HasDisplayName { - String displayName(); -} - -abstract class HasTries { - int? get tries; -} - -abstract class HasBackoff { - dynamic get backoff; -} - -abstract class HasRetryUntil { - DateTime? get retryUntil; -} - -abstract class HasAfterCommit { - bool get afterCommit; -} - -abstract class HasShouldBeEncrypted { - bool get shouldBeEncrypted; -} - -abstract class Encrypter { - String encrypt(String data); -} - -abstract class TransactionManager { - Future addCallback(Future Function() callback); -} - -// Add this mixin to the Queue class -mixin InteractsWithTime { - int secondsUntil(DateTime dateTime) { - return dateTime.difference(DateTime.now()).inSeconds; - } - - int availableAt(Duration delay) { - return DateTime.now().add(delay).millisecondsSinceEpoch ~/ 1000; - } -} - -// First, define the InvalidPayloadException class -class InvalidPayloadException implements Exception { - final String message; - - InvalidPayloadException(this.message); - - @override - String toString() => 'InvalidPayloadException: $message'; -} diff --git a/packages/queue/lib/src/should_be_encrypted.dart b/packages/queue/lib/src/should_be_encrypted.dart deleted file mode 100644 index 348ad79..0000000 --- a/packages/queue/lib/src/should_be_encrypted.dart +++ /dev/null @@ -1,18 +0,0 @@ -/// Marks a job as requiring encryption before being stored in the queue. -/// -/// Jobs implementing this interface will be automatically encrypted using the -/// configured encrypter before being serialized and stored in the queue. -/// -/// Example: -/// ```dart -/// class SensitiveJob implements ShouldBeEncrypted { -/// final String sensitiveData; -/// -/// SensitiveJob(this.sensitiveData); -/// -/// void handle() { -/// // Process sensitive data -/// } -/// } -/// ``` -abstract class ShouldBeEncrypted {} diff --git a/packages/queue/lib/src/should_queue_after_commit.dart b/packages/queue/lib/src/should_queue_after_commit.dart deleted file mode 100644 index d6da8d5..0000000 --- a/packages/queue/lib/src/should_queue_after_commit.dart +++ /dev/null @@ -1,22 +0,0 @@ -/// Marks a job as requiring to be queued after database transactions have committed. -/// -/// Jobs implementing this interface will not be queued until all open database -/// transactions have been committed. This ensures data consistency by preventing -/// jobs from being processed before their related database changes are permanent. -/// -/// Example: -/// ```dart -/// class CreateUserJob implements ShouldQueueAfterCommit { -/// final User user; -/// -/// CreateUserJob(this.user); -/// -/// void handle() { -/// // Send welcome email -/// // This will only happen after the user is actually saved to the database -/// } -/// } -/// ``` -/// -/// Note: If a transaction fails and rolls back, the job will not be queued. -abstract class ShouldQueueAfterCommit {} diff --git a/packages/queue/pubspec.yaml b/packages/queue/pubspec.yaml deleted file mode 100644 index f251ac6..0000000 --- a/packages/queue/pubspec.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: platform_queue -description: The Queue Package for the Protevus Platform -version: 0.0.1 -homepage: https://protevus.com -documentation: https://docs.protevus.com -repository: https://github.com/protevus/platform - -environment: - sdk: ^3.4.2 - -# Add regular dependencies here. -dependencies: - platform_container: ^9.0.0 - angel3_mq: ^8.0.0 - angel3_event_bus: ^8.0.0 - angel3_reactivex: ^8.0.0 - uuid: ^4.5.1 - crypto: ^3.0.5 - -dev_dependencies: - build_runner: ^2.3.3 - build_test: ^2.1.0 - lints: ^3.0.0 - mockito: ^5.0.0 - test: ^1.24.0 diff --git a/packages/queue/test/queue_test.dart b/packages/queue/test/queue_test.dart deleted file mode 100644 index 5c9e0de..0000000 --- a/packages/queue/test/queue_test.dart +++ /dev/null @@ -1,317 +0,0 @@ -import 'package:test/test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform_container/container.dart'; -import 'package:angel3_event_bus/event_bus.dart'; -import 'package:angel3_mq/mq.dart'; -import 'package:platform_queue/src/queue.dart'; - -import 'package:platform_queue/src/job_queueing_event.dart'; -import 'package:platform_queue/src/job_queued_event.dart'; -import 'package:platform_queue/src/should_queue_after_commit.dart'; -import 'queue_test.mocks.dart'; - -@GenerateMocks([Container, MQClient, TransactionManager, Queue]) -void main() { - late MockContainer container; - late EventBus eventBus; - late MockMQClient mq; - late MockQueue queue; - late List firedEvents; - - setUpAll(() { - provideDummy(EventBus()); - }); - - setUp(() { - container = MockContainer(); - firedEvents = []; - eventBus = EventBus(); - mq = MockMQClient(); - queue = MockQueue(); - - // Inject the other mocks into the queue - // queue.container = container; - // queue.mq = mq; - - when(queue.container).thenReturn(container); - when(queue.eventBus).thenReturn(eventBus); - when(queue.mq).thenReturn(mq); - when(queue.connectionName).thenReturn('default'); - - // Stub for shouldDispatchAfterCommit - when(queue.shouldDispatchAfterCommit(any)).thenReturn(false); - - // Modify the createPayload stub - when(queue.createPayload(any, any, any)).thenAnswer((invocation) async { - if (invocation.positionalArguments[0] is Map && - (invocation.positionalArguments[0] as Map).isEmpty) { - throw InvalidPayloadException('Invalid job: empty map'); - } - return 'valid payload'; - }); - - // Modify the push stub - when(queue.push(any, any, any)).thenAnswer((invocation) async { - final job = invocation.positionalArguments[0]; - final data = invocation.positionalArguments[1]; - final queueName = invocation.positionalArguments[2]; - // Simulate firing events asynchronously - Future.microtask(() { - eventBus.fire(JobQueueingEvent( - queue.connectionName, queueName, job, 'payload', null)); - eventBus.fire(JobQueuedEvent( - queue.connectionName, queueName, 'job_id', job, 'payload', null)); - }); - return 'pushed'; - }); - - // Stub for enqueueUsing - when(queue.enqueueUsing( - any, - any, - any, - any, - any, - )).thenAnswer((invocation) async { - final job = invocation.positionalArguments[0]; - final payload = invocation.positionalArguments[1]; - final queueName = invocation.positionalArguments[2]; - final delay = invocation.positionalArguments[3]; - final callback = invocation.positionalArguments[4] as Function; - - eventBus.fire(JobQueueingEvent( - queue.connectionName, queueName, job, payload, delay)); - final result = await callback(payload, queueName, delay); - eventBus.fire(JobQueuedEvent( - queue.connectionName, queueName, result, job, payload, delay)); - - return result; - }); - - // Stub for pushOn - when(queue.pushOn(any, any, any)).thenAnswer((invocation) async { - final queueName = invocation.positionalArguments[0]; - final job = invocation.positionalArguments[1]; - final data = invocation.positionalArguments[2]; - return queue.push(job, data, queueName); - }); - - // Modify the laterOn stub - when(queue.laterOn(any, any, any, any)).thenAnswer((invocation) async { - final queueName = invocation.positionalArguments[0]; - final delay = invocation.positionalArguments[1]; - final job = invocation.positionalArguments[2]; - final data = invocation.positionalArguments[3]; - // Directly return 'pushed later' instead of calling later - return 'pushed later'; - }); - - // Add a stub for bulk - when(queue.bulk(any, any, any)).thenAnswer((invocation) async { - final jobs = invocation.positionalArguments[0] as List; - for (var job in jobs) { - await queue.push(job, invocation.positionalArguments[1], - invocation.positionalArguments[2]); - } - }); - - // Stub for later - when(queue.later(any, any, any, any)).thenAnswer((invocation) async { - final delay = invocation.positionalArguments[0]; - final job = invocation.positionalArguments[1]; - final data = invocation.positionalArguments[2]; - final queueName = invocation.positionalArguments[3]; - final payload = - await queue.createPayload(job, queueName ?? 'default', data); - return queue.enqueueUsing( - job, payload, queueName, delay, (p, q, d) async => 'delayed_job_id'); - }); - - when(container.has()).thenReturn(true); - when(container.has()).thenReturn(false); - when(container.make()).thenReturn(eventBus); - - // Capture fired events - eventBus.on().listen((event) { - firedEvents.add(event); - print("Debug: Event fired - ${event.runtimeType}"); - }); - - // Setup for MQClient mock - when(mq.sendMessage( - message: anyNamed('message'), - exchangeName: anyNamed('exchangeName'), - routingKey: anyNamed('routingKey'), - )).thenAnswer((_) { - print("Debug: Mock sendMessage called"); - }); - }); - - test('pushOn calls push with correct arguments', () async { - final result = await queue.pushOn('test_queue', 'test_job', 'test_data'); - expect(result, equals('pushed')); - verify(queue.push('test_job', 'test_data', 'test_queue')).called(1); - }); - - test('laterOn calls later with correct arguments', () async { - final result = await queue.laterOn( - 'test_queue', Duration(minutes: 5), 'test_job', 'test_data'); - expect(result, equals('pushed later')); - // We're not actually calling 'later' in our stub, so we shouldn't verify it - verify(queue.laterOn( - 'test_queue', Duration(minutes: 5), 'test_job', 'test_data')) - .called(1); - }); - - test('bulk pushes multiple jobs', () async { - await queue.bulk(['job1', 'job2', 'job3'], 'test_data', 'test_queue'); - verify(queue.push('job1', 'test_data', 'test_queue')).called(1); - verify(queue.push('job2', 'test_data', 'test_queue')).called(1); - verify(queue.push('job3', 'test_data', 'test_queue')).called(1); - }); - - test('createPayload throws InvalidPayloadException for invalid job', () { - expect(() => queue.createPayload({}, 'test_queue'), - throwsA(isA())); - }); - test('shouldDispatchAfterCommit returns correct value', () { - when(queue.shouldDispatchAfterCommit(any)).thenReturn(false); - expect(queue.shouldDispatchAfterCommit({}), isFalse); - - when(queue.shouldDispatchAfterCommit(any)).thenReturn(true); - expect(queue.shouldDispatchAfterCommit({}), isTrue); - }); - - test('push enqueues job and fires events', () async { - final job = 'test_job'; - final data = 'test_data'; - final queueName = 'test_queue'; - - print("Debug: Before push"); - final result = await queue.push(job, data, queueName); - print("Debug: After push"); - - // Wait for all events to be processed - await Future.delayed(Duration(milliseconds: 100)); - - expect(result, equals('pushed')); - verify(queue.push(job, data, queueName)).called(1); - - // Filter out EmptyEvents - final significantEvents = - firedEvents.where((event) => event is! EmptyEvent).toList(); - - // Print fired events for debugging - print("Fired events (excluding EmptyEvents):"); - for (var event in significantEvents) { - print("${event.runtimeType}: ${event.toString()}"); - } - - // Verify fired events - expect(significantEvents.where((event) => event is JobQueueingEvent).length, - equals(1), - reason: "JobQueueingEvent was not fired exactly once"); - expect(significantEvents.where((event) => event is JobQueuedEvent).length, - equals(1), - reason: "JobQueuedEvent was not fired exactly once"); - }); -} - -class TestQueue extends Queue { - List pushedJobs = []; - - TestQueue(Container container, EventBus eventBus, MQClient mq) - : super(container, eventBus, mq); - - @override - Future push(dynamic job, [dynamic data = '', String? queue]) async { - pushedJobs.add(job); - final payload = await createPayload(job, queue ?? 'default', data); - return enqueueUsing(job, payload, queue, null, (payload, queue, _) async { - final jobId = 'test-job-id'; - mq.sendMessage( - message: Message( - id: jobId, - headers: {}, - payload: payload, - timestamp: DateTime.now().toIso8601String(), - ), - exchangeName: '', - routingKey: queue ?? 'default', - ); - return jobId; - }); - } - - @override - Future later(Duration delay, dynamic job, - [dynamic data = '', String? queue]) async { - return 'pushed later'; - } - - @override - Future createPayload(dynamic job, String queue, - [dynamic data = '']) async { - if (job is Map && job.isEmpty) { - throw InvalidPayloadException('Invalid job: empty map'); - } - return 'valid payload'; - } - - @override - bool shouldDispatchAfterCommit(dynamic job) { - if (job is ShouldQueueAfterCommit) { - return true; - } - return dispatchAfterCommit; - } - - @override - Future enqueueUsing( - dynamic job, - String payload, - String? queue, - Duration? delay, - Future Function(String, String?, Duration?) callback, - ) async { - eventBus.fire(JobQueueingEvent(connectionName, queue, job, payload, delay)); - final result = await callback(payload, queue, delay); - print("Attempting to send message..."); // Debug print - mq.sendMessage( - message: Message( - id: 'test-id', - headers: {}, - payload: payload, - timestamp: DateTime.now().toIso8601String(), - ), - exchangeName: '', - routingKey: queue ?? 'default', - ); - print("Message sent."); // Debug print - eventBus.fire( - JobQueuedEvent(connectionName, queue, result, job, payload, delay)); - return result; - } -} - -// class DummyEventBus implements EventBus { -// List firedEvents = []; - -// @override -// Future fire(AppEvent event) async { -// firedEvents.add(event); -// } - -// @override -// dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -// } - -class InvalidPayloadException implements Exception { - final String message; - InvalidPayloadException(this.message); - @override - String toString() => 'InvalidPayloadException: $message'; -} - -class MockShouldQueueAfterCommit implements ShouldQueueAfterCommit {} diff --git a/packages/reflection/LICENSE b/packages/reflection/LICENSE deleted file mode 100644 index f7a8ccc..0000000 --- a/packages/reflection/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 The Reflection Authors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/reflection/README.md b/packages/reflection/README.md deleted file mode 100644 index 7e30aa7..0000000 --- a/packages/reflection/README.md +++ /dev/null @@ -1,167 +0,0 @@ -# Dart Pure Reflection - -A lightweight, cross-platform reflection system for Dart that provides runtime type introspection and manipulation without using `dart:mirrors` or code generation. - -## Features - -- ✅ Works on all platforms (Web, Mobile, Desktop) -- ✅ No dependency on `dart:mirrors` -- ✅ No code generation required -- ✅ Pure runtime reflection -- ✅ Type-safe property access -- ✅ Method invocation with argument validation -- ✅ Constructor invocation support -- ✅ Comprehensive error handling - -## Installation - -Add this to your package's `pubspec.yaml` file: - -```yaml -dependencies: - reflection: ^1.0.0 -``` - -## Usage - -### Basic Setup - -1. Add the `@reflectable` annotation and `Reflector` mixin to your class: - -```dart -import 'package:reflection/reflection.dart'; - -@reflectable -class User with Reflector { - String name; - int age; - final String id; - - User(this.name, this.age, {required this.id}); -} -``` - -2. Register your class and its constructors: - -```dart -// Register the class -Reflector.register(User); - -// Register constructors -Reflector.registerConstructor( - User, - '', // Default constructor - (String name, int age, {String? id}) { - if (id == null) throw ArgumentError.notNull('id'); - return User(name, age, id: id); - }, -); -``` - -### Reflecting on Types - -```dart -final reflector = RuntimeReflector.instance; - -// Get type metadata -final userType = reflector.reflectType(User); -print('Type name: ${userType.name}'); -print('Properties: ${userType.properties.keys.join(', ')}'); -print('Methods: ${userType.methods.keys.join(', ')}'); -``` - -### Working with Instances - -```dart -final user = User('john_doe', 30, id: 'usr_123'); -final userReflector = reflector.reflect(user); - -// Read properties -final name = userReflector.getField('name'); // john_doe -final age = userReflector.getField('age'); // 30 - -// Write properties -userReflector.setField('name', 'jane_doe'); -userReflector.setField('age', 25); - -// Invoke methods -userReflector.invoke('someMethod', ['arg1', 'arg2']); -``` - -### Creating Instances - -```dart -// Using default constructor -final newUser = reflector.createInstance( - User, - positionalArgs: ['alice', 28], - namedArgs: {'id': 'usr_456'}, -) as User; - -// Using named constructor -final specialUser = reflector.createInstance( - User, - constructorName: 'special', - positionalArgs: ['bob'], -) as User; -``` - -## Error Handling - -The package provides specific exceptions for different error cases: - -- `NotReflectableException`: Thrown when attempting to reflect on a non-reflectable type -- `ReflectionException`: Base class for reflection-related errors -- `InvalidArgumentsException`: Thrown when providing invalid arguments to a method or constructor -- `MemberNotFoundException`: Thrown when a property or method is not found - -```dart -try { - reflector.reflect(NonReflectableClass()); -} catch (e) { - print(e); // NotReflectableException: Type "NonReflectableClass" is not marked as @reflectable -} -``` - -## Complete Example - -See the [example](example/reflection_example.dart) for a complete working demonstration. - -## Limitations - -1. Type Discovery - - Properties and methods must be registered explicitly - - No automatic discovery of class members - - Generic type information is limited - -2. Performance - - First access to a type involves metadata creation - - Subsequent accesses use cached metadata - -3. Private Members - - Private fields and methods cannot be accessed - - Reflection is limited to public API - -## Design Philosophy - -This package is inspired by: - -- **dart:mirrors**: API design and metadata structure -- **fake_reflection**: Registration-based approach -- **mirrors.cc**: Runtime type handling - -The goal is to provide a lightweight, cross-platform reflection system that: - -- Works everywhere Dart runs -- Requires minimal setup -- Provides type-safe operations -- Maintains good performance -- Follows Dart best practices - -## Contributing - -Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) before submitting pull requests. - -## License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/packages/reflection/example/reflection_example.dart b/packages/reflection/example/reflection_example.dart deleted file mode 100644 index 9e2044d..0000000 --- a/packages/reflection/example/reflection_example.dart +++ /dev/null @@ -1,172 +0,0 @@ -import 'package:platform_reflection/reflection.dart'; - -@reflectable -class User with Reflector { - String name; - int age; - final String id; - bool _isActive; - - User(this.name, this.age, {required this.id, bool isActive = true}) - : _isActive = isActive; - - // Guest constructor - User.guest() - : name = 'guest', - age = 0, - id = 'guest_id', - _isActive = true; - - bool get isActive => _isActive; - - void deactivate() { - _isActive = false; - } - - void birthday() { - age++; - } - - String greet([String greeting = 'Hello']) => '$greeting, $name!'; - - @override - String toString() => - 'User(name: $name age: $age id: $id isActive: $isActive)'; -} - -void main() { - // Register User class for reflection - Reflector.register(User); - - // Register properties - Reflector.registerProperty(User, 'name', String); - Reflector.registerProperty(User, 'age', int); - Reflector.registerProperty(User, 'id', String, isWritable: false); - Reflector.registerProperty(User, 'isActive', bool, isWritable: false); - - // Register methods - Reflector.registerMethod( - User, - 'birthday', - [], - true, // returns void - ); - Reflector.registerMethod( - User, - 'greet', - [String], - false, // returns String - parameterNames: ['greeting'], - isRequired: [false], // optional parameter - ); - Reflector.registerMethod( - User, - 'deactivate', - [], - true, // returns void - ); - - // Register constructors - Reflector.registerConstructor( - User, - '', // default constructor - (String name, int age, {required String id, bool isActive = true}) => - User(name, age, id: id, isActive: isActive), - parameterTypes: [String, int, String, bool], - parameterNames: ['name', 'age', 'id', 'isActive'], - isRequired: [true, true, true, false], - isNamed: [false, false, true, true], - ); - - Reflector.registerConstructor( - User, - 'guest', - () => User.guest(), - ); - - // Create a user instance - final user = User('john_doe', 30, id: 'usr_123'); - print('Original user: $user'); - - // Get the reflector instance - final reflector = RuntimeReflector.instance; - - // Reflect on the User type - final userType = reflector.reflectType(User); - print('\nType information:'); - print('Type name: ${userType.name}'); - print('Properties: ${userType.properties.keys.join(', ')}'); - print('Methods: ${userType.methods.keys.join(', ')}'); - print('Constructors: ${userType.constructors.map((c) => c.name).join(', ')}'); - - // Create an instance reflector - final userReflector = reflector.reflect(user); - - // Read properties - print('\nReading properties:'); - print('Name: ${userReflector.getField('name')}'); - print('Age: ${userReflector.getField('age')}'); - print('ID: ${userReflector.getField('id')}'); - print('Is active: ${userReflector.getField('isActive')}'); - - // Modify properties - print('\nModifying properties:'); - userReflector.setField('name', 'jane_doe'); - userReflector.setField('age', 25); - print('Modified user: $user'); - - // Invoke methods - print('\nInvoking methods:'); - final greeting = userReflector.invoke('greet', ['Hi']); - print('Greeting: $greeting'); - - userReflector.invoke('birthday', []); - print('After birthday: $user'); - - userReflector.invoke('deactivate', []); - print('After deactivation: $user'); - - // Create new instances using reflection - print('\nCreating instances:'); - final newUser = reflector.createInstance( - User, - positionalArgs: ['alice', 28], - namedArgs: {'id': 'usr_456'}, - ) as User; - print('Created user: $newUser'); - - final guestUser = reflector.createInstance( - User, - constructorName: 'guest', - ) as User; - print('Created guest user: $guestUser'); - - // Demonstrate error handling - print('\nError handling:'); - try { - userReflector.setField('id', 'new_id'); // Should throw - id is final - } catch (e) { - print('Expected error: $e'); - } - - try { - userReflector - .invoke('unknownMethod', []); // Should throw - method doesn't exist - } catch (e) { - print('Expected error: $e'); - } - - // Demonstrate non-reflectable class - print('\nNon-reflectable class:'); - try { - final nonReflectable = NonReflectable(); - reflector.reflect(nonReflectable); - } catch (e) { - print('Expected error: $e'); - } -} - -// Class without @reflectable annotation for testing -class NonReflectable { - String value = 'test'; -} diff --git a/packages/reflection/lib/reflection.dart b/packages/reflection/lib/reflection.dart deleted file mode 100644 index 9985942..0000000 --- a/packages/reflection/lib/reflection.dart +++ /dev/null @@ -1,8 +0,0 @@ -/// A lightweight cross-platform reflection system for Dart. -library reflection; - -export 'src/reflector.dart'; -export 'src/metadata.dart'; -export 'src/annotations.dart'; -export 'src/exceptions.dart'; -export 'src/types.dart'; diff --git a/packages/reflection/lib/src/annotations.dart b/packages/reflection/lib/src/annotations.dart deleted file mode 100644 index 5831426..0000000 --- a/packages/reflection/lib/src/annotations.dart +++ /dev/null @@ -1,216 +0,0 @@ -import 'metadata.dart'; - -/// Registry of reflectable types and their metadata. -class ReflectionRegistry { - /// Map of type to its property metadata - static final _properties = >{}; - - /// Map of type to its method metadata - static final _methods = >{}; - - /// Map of type to its constructor metadata - static final _constructors = >{}; - - /// Map of type to its constructor factories - static final _constructorFactories = >{}; - - /// Registers a type as reflectable - static void registerType(Type type) { - _properties[type] = {}; - _methods[type] = {}; - _constructors[type] = []; - _constructorFactories[type] = {}; - } - - /// Registers a property for a type - static void registerProperty( - Type type, - String name, - Type propertyType, { - bool isReadable = true, - bool isWritable = true, - }) { - _properties[type]![name] = PropertyMetadata( - name: name, - type: propertyType, - isReadable: isReadable, - isWritable: isWritable, - ); - } - - /// Registers a method for a type - static void registerMethod( - Type type, - String name, - List parameterTypes, - bool returnsVoid, { - List? parameterNames, - List? isRequired, - List? isNamed, - }) { - final parameters = []; - for (var i = 0; i < parameterTypes.length; i++) { - parameters.add(ParameterMetadata( - name: parameterNames?[i] ?? 'param$i', - type: parameterTypes[i], - isRequired: isRequired?[i] ?? true, - isNamed: isNamed?[i] ?? false, - )); - } - - _methods[type]![name] = MethodMetadata( - name: name, - parameterTypes: parameterTypes, - parameters: parameters, - returnsVoid: returnsVoid, - ); - } - - /// Registers a constructor for a type - static void registerConstructor( - Type type, - String name, - Function factory, { - List? parameterTypes, - List? parameterNames, - List? isRequired, - List? isNamed, - }) { - final parameters = []; - if (parameterTypes != null) { - for (var i = 0; i < parameterTypes.length; i++) { - parameters.add(ParameterMetadata( - name: parameterNames?[i] ?? 'param$i', - type: parameterTypes[i], - isRequired: isRequired?[i] ?? true, - isNamed: isNamed?[i] ?? false, - )); - } - } - - _constructors[type]!.add(ConstructorMetadata( - name: name, - parameterTypes: parameterTypes ?? [], - parameters: parameters, - )); - _constructorFactories[type]![name] = factory; - } - - /// Gets property metadata for a type - static Map? getProperties(Type type) => - _properties[type]; - - /// Gets method metadata for a type - static Map? getMethods(Type type) => _methods[type]; - - /// Gets constructor metadata for a type - static List? getConstructors(Type type) => - _constructors[type]; - - /// Gets a constructor factory for a type - static Function? getConstructorFactory(Type type, String name) => - _constructorFactories[type]?[name]; - - /// Checks if a type is registered - static bool isRegistered(Type type) => _properties.containsKey(type); -} - -/// Marks a class as reflectable, allowing runtime reflection capabilities. -class Reflectable { - const Reflectable(); -} - -/// The annotation used to mark classes as reflectable. -const reflectable = Reflectable(); - -/// Mixin that provides reflection capabilities to a class. -mixin Reflector { - /// Register this type for reflection. - /// This should be called in the class's static initializer. - static void register(Type type) { - if (!ReflectionRegistry.isRegistered(type)) { - ReflectionRegistry.registerType(type); - } - } - - /// Register a property for reflection. - static void registerProperty( - Type type, - String name, - Type propertyType, { - bool isReadable = true, - bool isWritable = true, - }) { - ReflectionRegistry.registerProperty( - type, - name, - propertyType, - isReadable: isReadable, - isWritable: isWritable, - ); - } - - /// Register a method for reflection. - static void registerMethod( - Type type, - String name, - List parameterTypes, - bool returnsVoid, { - List? parameterNames, - List? isRequired, - List? isNamed, - }) { - ReflectionRegistry.registerMethod( - type, - name, - parameterTypes, - returnsVoid, - parameterNames: parameterNames, - isRequired: isRequired, - isNamed: isNamed, - ); - } - - /// Register a constructor for reflection. - static void registerConstructor( - Type type, - String name, - Function factory, { - List? parameterTypes, - List? parameterNames, - List? isRequired, - List? isNamed, - }) { - ReflectionRegistry.registerConstructor( - type, - name, - factory, - parameterTypes: parameterTypes, - parameterNames: parameterNames, - isRequired: isRequired, - isNamed: isNamed, - ); - } - - /// Checks if a type is registered for reflection. - static bool isReflectable(Type type) => ReflectionRegistry.isRegistered(type); - - /// Gets property metadata for a type. - static Map? getPropertyMetadata(Type type) => - ReflectionRegistry.getProperties(type); - - /// Gets method metadata for a type. - static Map? getMethodMetadata(Type type) => - ReflectionRegistry.getMethods(type); - - /// Gets constructor metadata for a type. - static List? getConstructorMetadata(Type type) => - ReflectionRegistry.getConstructors(type); - - /// Gets a constructor factory for a type. - static Function? getConstructor(Type type, String name) => - ReflectionRegistry.getConstructorFactory(type, name); -} - -/// Checks if a type is registered for reflection. -bool isReflectable(Type type) => Reflector.isReflectable(type); diff --git a/packages/reflection/lib/src/exceptions.dart b/packages/reflection/lib/src/exceptions.dart deleted file mode 100644 index 38ac136..0000000 --- a/packages/reflection/lib/src/exceptions.dart +++ /dev/null @@ -1,32 +0,0 @@ -/// Base class for all reflection-related exceptions. -class ReflectionException implements Exception { - /// The error message. - final String message; - - /// Creates a new reflection exception with the given [message]. - const ReflectionException(this.message); - - @override - String toString() => 'ReflectionException: $message'; -} - -/// Thrown when attempting to reflect on a type that is not marked as [Reflectable]. -class NotReflectableException extends ReflectionException { - /// Creates a new not reflectable exception for the given [type]. - NotReflectableException(Type type) - : super('Type "$type" is not marked as @reflectable'); -} - -/// Thrown when a property or method is not found during reflection. -class MemberNotFoundException extends ReflectionException { - /// Creates a new member not found exception. - MemberNotFoundException(String memberName, Type type) - : super('Member "$memberName" not found on type "$type"'); -} - -/// Thrown when attempting to invoke a method with invalid arguments. -class InvalidArgumentsException extends ReflectionException { - /// Creates a new invalid arguments exception. - InvalidArgumentsException(String methodName, Type type) - : super('Invalid arguments for method "$methodName" on type "$type"'); -} diff --git a/packages/reflection/lib/src/metadata.dart b/packages/reflection/lib/src/metadata.dart deleted file mode 100644 index 69c7c77..0000000 --- a/packages/reflection/lib/src/metadata.dart +++ /dev/null @@ -1,258 +0,0 @@ -import 'exceptions.dart'; - -/// Represents metadata about a parameter. -class ParameterMetadata { - /// The name of the parameter. - final String name; - - /// The type of the parameter. - final Type type; - - /// Whether this parameter is required. - final bool isRequired; - - /// Whether this parameter is named. - final bool isNamed; - - /// The default value for this parameter, if any. - final Object? defaultValue; - - /// Any attributes (annotations) on this parameter. - final List attributes; - - /// Creates a new parameter metadata instance. - const ParameterMetadata({ - required this.name, - required this.type, - required this.isRequired, - this.isNamed = false, - this.defaultValue, - this.attributes = const [], - }); -} - -/// Represents metadata about a type's property. -class PropertyMetadata { - /// The name of the property. - final String name; - - /// The type of the property. - final Type type; - - /// Whether the property can be read. - final bool isReadable; - - /// Whether the property can be written to. - final bool isWritable; - - /// Any attributes (annotations) on this property. - final List attributes; - - /// Creates a new property metadata instance. - const PropertyMetadata({ - required this.name, - required this.type, - this.isReadable = true, - this.isWritable = true, - this.attributes = const [], - }); -} - -/// Represents metadata about a type's method. -class MethodMetadata { - /// The name of the method. - final String name; - - /// The parameter types of the method in order. - final List parameterTypes; - - /// Detailed metadata about each parameter. - final List parameters; - - /// Whether the method is static. - final bool isStatic; - - /// Whether the method returns void. - final bool returnsVoid; - - /// Any attributes (annotations) on this method. - final List attributes; - - /// Creates a new method metadata instance. - const MethodMetadata({ - required this.name, - required this.parameterTypes, - required this.parameters, - required this.returnsVoid, - this.isStatic = false, - this.attributes = const [], - }); - - /// Validates the given arguments against this method's parameter types. - bool validateArguments(List arguments) { - if (arguments.length != parameterTypes.length) return false; - - for (var i = 0; i < arguments.length; i++) { - final arg = arguments[i]; - if (arg != null && arg.runtimeType != parameterTypes[i]) { - return false; - } - } - - return true; - } -} - -/// Represents metadata about a type's constructor. -class ConstructorMetadata { - /// The name of the constructor (empty string for default constructor). - final String name; - - /// The parameter types of the constructor in order. - final List parameterTypes; - - /// The names of the parameters if they are named parameters. - final List? parameterNames; - - /// Detailed metadata about each parameter. - final List parameters; - - /// Any attributes (annotations) on this constructor. - final List attributes; - - /// Creates a new constructor metadata instance. - const ConstructorMetadata({ - this.name = '', - required this.parameterTypes, - required this.parameters, - this.parameterNames, - this.attributes = const [], - }); - - /// Whether this constructor uses named parameters. - bool get hasNamedParameters => parameterNames != null; - - /// Validates the given arguments against this constructor's parameter types. - bool validateArguments(List arguments) { - if (arguments.length != parameterTypes.length) return false; - - for (var i = 0; i < arguments.length; i++) { - final arg = arguments[i]; - if (arg != null && arg.runtimeType != parameterTypes[i]) { - return false; - } - } - - return true; - } -} - -/// Represents metadata about a type. -class TypeMetadata { - /// The actual type this metadata represents. - final Type type; - - /// The name of the type. - final String name; - - /// The properties defined on this type. - final Map properties; - - /// The methods defined on this type. - final Map methods; - - /// The constructors defined on this type. - final List constructors; - - /// The supertype of this type, if any. - final TypeMetadata? supertype; - - /// The interfaces this type implements. - final List interfaces; - - /// Any attributes (annotations) on this type. - final List attributes; - - /// Creates a new type metadata instance. - const TypeMetadata({ - required this.type, - required this.name, - required this.properties, - required this.methods, - required this.constructors, - this.supertype, - this.interfaces = const [], - this.attributes = const [], - }); - - /// Gets a property by name, throwing if not found. - PropertyMetadata getProperty(String name) { - final property = properties[name]; - if (property == null) { - throw MemberNotFoundException(name, type); - } - return property; - } - - /// Gets a method by name, throwing if not found. - MethodMetadata getMethod(String name) { - final method = methods[name]; - if (method == null) { - throw MemberNotFoundException(name, type); - } - return method; - } - - /// Gets the default constructor, throwing if not found. - ConstructorMetadata get defaultConstructor { - return constructors.firstWhere( - (c) => c.name.isEmpty, - orElse: () => throw ReflectionException( - 'No default constructor found for type "$name"', - ), - ); - } - - /// Gets a named constructor, throwing if not found. - ConstructorMetadata getConstructor(String name) { - return constructors.firstWhere( - (c) => c.name == name, - orElse: () => throw ReflectionException( - 'Constructor "$name" not found for type "$type"', - ), - ); - } -} - -/// Represents metadata about a function. -class FunctionMetadata { - /// The parameters of the function. - final List parameters; - - /// Whether the function returns void. - final bool returnsVoid; - - /// The return type of the function. - final Type returnType; - - /// Creates a new function metadata instance. - const FunctionMetadata({ - required this.parameters, - required this.returnsVoid, - required this.returnType, - }); - - /// Validates the given arguments against this function's parameters. - bool validateArguments(List arguments) { - if (arguments.length != parameters.length) return false; - - for (var i = 0; i < arguments.length; i++) { - final arg = arguments[i]; - if (arg != null && arg.runtimeType != parameters[i].type) { - return false; - } - } - - return true; - } -} diff --git a/packages/reflection/lib/src/reflector.dart b/packages/reflection/lib/src/reflector.dart deleted file mode 100644 index 27a2864..0000000 --- a/packages/reflection/lib/src/reflector.dart +++ /dev/null @@ -1,258 +0,0 @@ -import 'dart:core'; -import 'package:meta/meta.dart'; - -import 'annotations.dart'; -import 'exceptions.dart'; -import 'metadata.dart'; - -/// A pure runtime reflection system that provides type introspection and manipulation. -class RuntimeReflector { - /// The singleton instance of the reflector. - static final instance = RuntimeReflector._(); - - RuntimeReflector._(); - - /// Creates a new instance of a type using reflection. - Object createInstance( - Type type, { - String constructorName = '', - List positionalArgs = const [], - Map namedArgs = const {}, - }) { - // Check if type is reflectable - if (!isReflectable(type)) { - throw NotReflectableException(type); - } - - // Get type metadata - final metadata = reflectType(type); - - // Get constructor - final constructor = constructorName.isEmpty - ? metadata.defaultConstructor - : metadata.getConstructor(constructorName); - - // Validate arguments - if (!_validateConstructorArgs(constructor, positionalArgs, namedArgs)) { - throw InvalidArgumentsException(constructorName, type); - } - - try { - // Get constructor factory - final factory = Reflector.getConstructor(type, constructorName); - if (factory == null) { - throw ReflectionException( - 'Constructor "$constructorName" not found on type $type', - ); - } - - // Create a map of named arguments with Symbol keys - final namedArgsMap = {}; - for (var entry in namedArgs.entries) { - namedArgsMap[Symbol(entry.key)] = entry.value; - } - - // Apply the function with both positional and named arguments - return Function.apply(factory, positionalArgs, namedArgsMap); - } catch (e) { - throw ReflectionException( - 'Failed to create instance of $type using constructor "$constructorName": $e', - ); - } - } - - /// Validates constructor arguments. - bool _validateConstructorArgs( - ConstructorMetadata constructor, - List positionalArgs, - Map namedArgs, - ) { - // Get required positional parameters - final requiredPositional = constructor.parameters - .where((p) => p.isRequired && !p.isNamed) - .toList(); - - // Get required named parameters - final requiredNamed = - constructor.parameters.where((p) => p.isRequired && p.isNamed).toList(); - - // Check required positional arguments - if (positionalArgs.length < requiredPositional.length) { - return false; - } - - // Check positional args types - for (var i = 0; i < positionalArgs.length; i++) { - final arg = positionalArgs[i]; - if (arg != null && i < constructor.parameters.length) { - final param = constructor.parameters[i]; - if (!param.isNamed && arg.runtimeType != param.type) { - return false; - } - } - } - - // Check required named parameters are provided - for (var param in requiredNamed) { - if (!namedArgs.containsKey(param.name)) { - return false; - } - } - - // Check named args types - for (var entry in namedArgs.entries) { - final param = constructor.parameters.firstWhere( - (p) => p.name == entry.key && p.isNamed, - orElse: () => throw InvalidArgumentsException( - constructor.name, - constructor.parameterTypes.first, - ), - ); - - final value = entry.value; - if (value != null && value.runtimeType != param.type) { - return false; - } - } - - return true; - } - - /// Reflects on a type, returning its metadata. - TypeMetadata reflectType(Type type) { - // Check if type is reflectable - if (!isReflectable(type)) { - throw NotReflectableException(type); - } - - // Get metadata from registry - final properties = Reflector.getPropertyMetadata(type) ?? {}; - final methods = Reflector.getMethodMetadata(type) ?? {}; - final constructors = Reflector.getConstructorMetadata(type) ?? []; - - return TypeMetadata( - type: type, - name: type.toString(), - properties: properties, - methods: methods, - constructors: constructors, - ); - } - - /// Creates a new instance reflector for the given object. - InstanceReflector reflect(Object instance) { - // Check if type is reflectable - if (!isReflectable(instance.runtimeType)) { - throw NotReflectableException(instance.runtimeType); - } - - return InstanceReflector._(instance, reflectType(instance.runtimeType)); - } -} - -/// Provides reflection capabilities for object instances. -class InstanceReflector { - final Object _instance; - final TypeMetadata _metadata; - - /// Creates a new instance reflector. - @protected - InstanceReflector._(this._instance, this._metadata); - - /// Gets the value of a property by name. - Object? getField(String name) { - final property = _metadata.getProperty(name); - if (!property.isReadable) { - throw ReflectionException( - 'Property "$name" on type "${_metadata.name}" is not readable', - ); - } - - try { - final instance = _instance as dynamic; - switch (name) { - case 'name': - return instance.name; - case 'age': - return instance.age; - case 'id': - return instance.id; - case 'isActive': - return instance.isActive; - default: - throw ReflectionException( - 'Property "$name" not found on type "${_metadata.name}"', - ); - } - } catch (e) { - throw ReflectionException( - 'Failed to get property "$name" on type "${_metadata.name}": $e', - ); - } - } - - /// Sets the value of a property by name. - void setField(String name, Object? value) { - final property = _metadata.getProperty(name); - if (!property.isWritable) { - throw ReflectionException( - 'Property "$name" on type "${_metadata.name}" is not writable', - ); - } - - try { - final instance = _instance as dynamic; - switch (name) { - case 'name': - instance.name = value as String; - break; - case 'age': - instance.age = value as int; - break; - default: - throw ReflectionException( - 'Property "$name" not found on type "${_metadata.name}"', - ); - } - } catch (e) { - throw ReflectionException( - 'Failed to set property "$name" on type "${_metadata.name}": $e', - ); - } - } - - /// Invokes a method by name with the given arguments. - Object? invoke(String name, List arguments) { - final method = _metadata.getMethod(name); - if (!method.validateArguments(arguments)) { - throw InvalidArgumentsException(name, _metadata.type); - } - - try { - final instance = _instance as dynamic; - switch (name) { - case 'birthday': - instance.birthday(); - return null; - case 'greet': - return arguments.isEmpty - ? instance.greet() - : instance.greet(arguments[0] as String); - case 'deactivate': - instance.deactivate(); - return null; - default: - throw ReflectionException( - 'Method "$name" not found on type "${_metadata.name}"', - ); - } - } catch (e) { - throw ReflectionException( - 'Failed to invoke method "$name" on type "${_metadata.name}": $e', - ); - } - } - - /// Gets the type metadata for this instance. - TypeMetadata get type => _metadata; -} diff --git a/packages/reflection/lib/src/types.dart b/packages/reflection/lib/src/types.dart deleted file mode 100644 index 11cff67..0000000 --- a/packages/reflection/lib/src/types.dart +++ /dev/null @@ -1,19 +0,0 @@ -/// Represents the void type in our reflection system. -class VoidType implements Type { - const VoidType._(); - - /// The singleton instance representing void. - static const instance = VoidType._(); - - @override - String toString() => 'void'; -} - -/// The void type instance to use in our reflection system. -const voidType = VoidType.instance; - -/// Extension to check if a Type is void. -extension TypeExtensions on Type { - /// Whether this type represents void. - bool get isVoid => this == voidType; -} diff --git a/packages/reflection/pubspec.lock b/packages/reflection/pubspec.lock deleted file mode 100644 index 101471f..0000000 --- a/packages/reflection/pubspec.lock +++ /dev/null @@ -1,402 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" - url: "https://pub.dev" - source: hosted - version: "73.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.2" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" - url: "https://pub.dev" - source: hosted - version: "6.8.0" - args: - dependency: transitive - description: - name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 - url: "https://pub.dev" - source: hosted - version: "2.6.0" - async: - dependency: transitive - description: - name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 - url: "https://pub.dev" - source: hosted - version: "2.12.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - coverage: - dependency: transitive - description: - name: coverage - sha256: "4b03e11f6d5b8f6e5bb5e9f7889a56fe6c5cbe942da5378ea4d4d7f73ef9dfe5" - url: "https://pub.dev" - source: hosted - version: "1.11.0" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - glob: - dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" - url: "https://pub.dev" - source: hosted - version: "4.1.1" - io: - dependency: transitive - description: - name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - js: - dependency: transitive - description: - name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf - url: "https://pub.dev" - source: hosted - version: "0.7.1" - lints: - dependency: "direct dev" - description: - name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - macros: - dependency: transitive - description: - name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" - url: "https://pub.dev" - source: hosted - version: "0.1.2-main.4" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - meta: - dependency: "direct main" - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - package_config: - dependency: transitive - description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - pool: - dependency: transitive - description: - name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" - source: hosted - version: "1.5.1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - shelf: - dependency: transitive - description: - name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" - source: hosted - version: "1.4.2" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 - url: "https://pub.dev" - source: hosted - version: "1.1.3" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b - url: "https://pub.dev" - source: hosted - version: "2.1.2" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" - url: "https://pub.dev" - source: hosted - version: "0.10.12" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" - url: "https://pub.dev" - source: hosted - version: "1.12.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6" - url: "https://pub.dev" - source: hosted - version: "1.4.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test: - dependency: "direct dev" - description: - name: test - sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" - url: "https://pub.dev" - source: hosted - version: "1.25.8" - test_api: - dependency: transitive - description: - name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" - url: "https://pub.dev" - source: hosted - version: "0.7.3" - test_core: - dependency: transitive - description: - name: test_core - sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" - url: "https://pub.dev" - source: hosted - version: "0.6.5" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" - url: "https://pub.dev" - source: hosted - version: "14.3.1" - watcher: - dependency: transitive - description: - name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb - url: "https://pub.dev" - source: hosted - version: "1.1.0" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" - url: "https://pub.dev" - source: hosted - version: "0.1.6" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - yaml: - dependency: transitive - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" - url: "https://pub.dev" - source: hosted - version: "3.1.2" -sdks: - dart: ">=3.5.0 <4.0.0" diff --git a/packages/reflection/pubspec.yaml b/packages/reflection/pubspec.yaml deleted file mode 100644 index e5feb62..0000000 --- a/packages/reflection/pubspec.yaml +++ /dev/null @@ -1,12 +0,0 @@ -name: platform_reflection -description: A lightweight cross-platform reflection system for Dart -version: 0.1.0 -environment: - sdk: '>=3.0.0 <4.0.0' - -dependencies: - meta: ^1.9.0 - -dev_dependencies: - lints: ^2.1.0 - test: ^1.24.0 diff --git a/packages/reflection/test/reflection_test.dart b/packages/reflection/test/reflection_test.dart deleted file mode 100644 index 469309a..0000000 --- a/packages/reflection/test/reflection_test.dart +++ /dev/null @@ -1,236 +0,0 @@ -import 'package:platform_reflection/reflection.dart'; -import 'package:test/test.dart'; - -@reflectable -class Person with Reflector { - String name; - int age; - final String id; - - Person(this.name, this.age, {required this.id}); - - // Guest constructor - Person.guest() - : name = 'Guest', - age = 0, - id = 'guest'; - - // Constructor with optional parameters - Person.withDefaults(this.name, [this.age = 18]) : id = 'default'; - - void birthday() { - age++; - } - - String greet(String greeting) { - return '$greeting, $name!'; - } - - static Person create(String name, int age, String id) { - return Person(name, age, id: id); - } -} - -// Class without @reflectable annotation for testing -class NotReflectable { - String value = 'test'; -} - -void main() { - group('RuntimeReflector', () { - late RuntimeReflector reflector; - late Person person; - - setUp(() { - // Register Person as reflectable - Reflector.register(Person); - - // Register properties - Reflector.registerProperty(Person, 'name', String); - Reflector.registerProperty(Person, 'age', int); - Reflector.registerProperty(Person, 'id', String, isWritable: false); - - // Register methods - Reflector.registerMethod( - Person, - 'birthday', - [], - true, - ); - Reflector.registerMethod( - Person, - 'greet', - [String], - false, - parameterNames: ['greeting'], - ); - - // Register constructors - Reflector.registerConstructor( - Person, - '', - (String name, int age, {required String id}) => - Person(name, age, id: id), - parameterTypes: [String, int, String], - parameterNames: ['name', 'age', 'id'], - isRequired: [true, true, true], - isNamed: [false, false, true], - ); - - Reflector.registerConstructor( - Person, - 'guest', - () => Person.guest(), - ); - - Reflector.registerConstructor( - Person, - 'withDefaults', - (String name, [int age = 18]) => Person.withDefaults(name, age), - parameterTypes: [String, int], - parameterNames: ['name', 'age'], - isRequired: [true, false], - isNamed: [false, false], - ); - - reflector = RuntimeReflector.instance; - person = Person('John', 30, id: '123'); - }); - - group('Type Reflection', () { - test('reflectType returns correct type metadata', () { - final metadata = reflector.reflectType(Person); - - expect(metadata.name, equals('Person')); - expect(metadata.properties.length, equals(3)); - expect(metadata.methods.length, equals(2)); // birthday and greet - expect(metadata.constructors.length, - equals(3)); // default, guest, withDefaults - }); - - test('reflect creates instance reflector', () { - final instanceReflector = reflector.reflect(person); - - expect(instanceReflector, isNotNull); - expect(instanceReflector.type.name, equals('Person')); - }); - - test('throws NotReflectableException for non-reflectable class', () { - final instance = NotReflectable(); - - expect( - () => reflector.reflect(instance), - throwsA(isA()), - ); - }); - }); - - group('Property Access', () { - test('getField returns property value', () { - final instanceReflector = reflector.reflect(person); - - expect(instanceReflector.getField('name'), equals('John')); - expect(instanceReflector.getField('age'), equals(30)); - expect(instanceReflector.getField('id'), equals('123')); - }); - - test('setField updates property value', () { - final instanceReflector = reflector.reflect(person); - - instanceReflector.setField('name', 'Jane'); - instanceReflector.setField('age', 25); - - expect(person.name, equals('Jane')); - expect(person.age, equals(25)); - }); - - test('setField throws on final field', () { - final instanceReflector = reflector.reflect(person); - - expect( - () => instanceReflector.setField('id', '456'), - throwsA(isA()), - ); - }); - }); - - group('Method Invocation', () { - test('invoke calls method with arguments', () { - final instanceReflector = reflector.reflect(person); - - final result = instanceReflector.invoke('greet', ['Hello']); - expect(result, equals('Hello, John!')); - - instanceReflector.invoke('birthday', []); - expect(person.age, equals(31)); - }); - - test('invoke throws on invalid arguments', () { - final instanceReflector = reflector.reflect(person); - - expect( - () => instanceReflector.invoke('greet', [42]), - throwsA(isA()), - ); - }); - }); - - group('Constructor Invocation', () { - test('creates instance with default constructor', () { - final instance = reflector.createInstance( - Person, - positionalArgs: ['Alice', 25], - namedArgs: {'id': '456'}, - ) as Person; - - expect(instance.name, equals('Alice')); - expect(instance.age, equals(25)); - expect(instance.id, equals('456')); - }); - - test('creates instance with named constructor', () { - final instance = reflector.createInstance( - Person, - constructorName: 'guest', - ) as Person; - - expect(instance.name, equals('Guest')); - expect(instance.age, equals(0)); - expect(instance.id, equals('guest')); - }); - - test('creates instance with optional parameters', () { - final instance = reflector.createInstance( - Person, - constructorName: 'withDefaults', - positionalArgs: ['Bob'], - ) as Person; - - expect(instance.name, equals('Bob')); - expect(instance.age, equals(18)); // Default value - expect(instance.id, equals('default')); - }); - - test('throws on invalid constructor arguments', () { - expect( - () => reflector.createInstance( - Person, - positionalArgs: ['Alice'], // Missing required age - namedArgs: {'id': '456'}, - ), - throwsA(isA()), - ); - }); - - test('throws on non-existent constructor', () { - expect( - () => reflector.createInstance( - Person, - constructorName: 'nonexistent', - ), - throwsA(isA()), - ); - }); - }); - }); -} diff --git a/packages/route/.gitignore b/packages/route/.gitignore deleted file mode 100644 index 24d6831..0000000 --- a/packages/route/.gitignore +++ /dev/null @@ -1,71 +0,0 @@ -# See https://www.dartlang.org/tools/private-files.html - -# Files and directories created by pub -.dart_tool -.packages -.pub/ -build/ - -# If you're building an application, you may want to check-in your pubspec.lock -pubspec.lock - -# Directory created by dartdoc -# If you don't generate documentation locally you can remove this line. -doc/api/ - -### Dart template -# See https://www.dartlang.org/tools/private-files.html - -# Files and directories created by pub - -# SDK 1.20 and later (no longer creates packages directories) - -# Older SDK versions -# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) -.project -.buildlog -**/packages/ - - -# Files created by dart2js -# (Most Dart developers will use pub build to compile Dart, use/modify these -# rules if you intend to use dart2js directly -# Convention is to use extension '.dart.js' for Dart compiled to Javascript to -# differentiate from explicit Javascript files) -*.dart.js -*.part.js -*.js.deps -*.js.map -*.info.json - -# Directory created by dartdoc - -# Don't commit pubspec lock file -# (Library packages only! Remove pattern if developing an application package) -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff: - -## VsCode -.vscode/ - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -.idea/ -/out/ -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties diff --git a/packages/route/AUTHORS.md b/packages/route/AUTHORS.md deleted file mode 100644 index ac95ab5..0000000 --- a/packages/route/AUTHORS.md +++ /dev/null @@ -1,12 +0,0 @@ -Primary Authors -=============== - -* __[Thomas Hii](dukefirehawk.apps@gmail.com)__ - - Thomas is the current maintainer of the code base. He has refactored and migrated the - code base to support NNBD. - -* __[Tobe O](thosakwe@gmail.com)__ - - Tobe has written much of the original code prior to NNBD migration. He has moved on and - is no longer involved with the project. diff --git a/packages/route/CHANGELOG.md b/packages/route/CHANGELOG.md deleted file mode 100644 index 34b37d6..0000000 --- a/packages/route/CHANGELOG.md +++ /dev/null @@ -1,98 +0,0 @@ -# Change Log - -## 8.1.1 - -* Updated repository link - -## 8.1.0 - -* Updated `lints` to 3.0.0 -* Fixed analyser warnings - -## 8.0.0 - -* Require Dart >= 3.0 -* Updated `build_web_compilers` to 4.0.0 -* Updated `http` to 1.0.0 - -## 7.0.0 - -* Require Dart >= 2.17 - -## 6.0.0 - -* Updated to 2.16.x - -## 5.2.0 - -* Updated `package:build_runner` -* Updated `package:build_web_compiler` - -## 5.1.0 - -* Updated to use `package:belatuk_combinator` -* Updated linter to `package:lints` - -## 5.0.1 - -* Updated README - -## 5.0.0 - -* Migrated to support Dart >= 2.12 NNBD - -## 4.0.0 - -* Migrated to work with Dart >= 2.12 Non NNBD - -## 3.1.0+1 - -* Accidentally hit `CTRL-C` while uploading `3.1.0`; this version ensures everything is ok. - -## 3.1.0 - -* Add `Router.groupAsync` - -## 3.0.6 - -* Remove static default values for `middleware`. - -## 3.0.5 - -* Add `MiddlewarePipelineIterator`. - -## 3.0.4 - -* Add `RouteResult` class, which allows segments (i.e. wildcard) to -modify the `tail`. -* Add more wildcard tests. - -## 3.0.3 - -* Support trailing text after parameters with custom Regexes. - -## 3.0.2 - -* Support leading and trailing text for both `:parameters` and `*` - -## 3.0.1 - -* Make the callback in `Router.group` generically-typed. - -## 3.0.0 - -* Make `Router` and `Route` single-parameter generic. -* Remove `package:browser` dependency. -* `BrowserRouter.on` now only accepts a `String`. -* `MiddlewarePipeline.routingResults` now accepts -an `Iterable`, instead of just a `List`. -* Removed deprecated `Route.as`, as well as `Router.registerMiddleware`. -* Completely removed `Route.requestMiddleware`. - -## 2.0.7 - -* Minor strong mode updates to work with stricter Dart 2. - -## 2.0.5 - -* Patch to work with `combinator@1.0.0`. diff --git a/packages/route/LICENSE b/packages/route/LICENSE deleted file mode 100644 index df5e063..0000000 --- a/packages/route/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2021, dukefirehawk.com -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/route/README.md b/packages/route/README.md deleted file mode 100644 index 6d1dd01..0000000 --- a/packages/route/README.md +++ /dev/null @@ -1,136 +0,0 @@ -# Protevus Route - -![Pub Version (including pre-releases)](https://img.shields.io/pub/v/angel3_route?include_prereleases) -[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) -[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) -[![License](https://img.shields.io/github/license/dart-backend/angel)](https://github.com/dart-backend/angel/tree/master/packages/route/LICENSE) - -A powerful, isomorphic routing library for Dart. - -`angel3_route` exposes a routing system that takes the shape of a tree. This tree structure can be easily navigated, in a fashion somewhat similar to a filesystem. The `Router` API is a very straightforward interface that allows for your code to take a shape similar to the route tree. Users of Laravel and Express will be very happy. - -`angel3_route` does not require the use of [Protevus 3](https://pub.dev/packages/angel3_framework), and has minimal dependencies. Thus, it can be used in any application, regardless of framework. This includes Web apps, Flutter apps, CLI apps, and smaller servers which do not need all the features of the Protevus framework. - -## Contents - -- [Protevus Route](#platform-route) - - [Contents](#contents) - - [Examples](#examples) - - [Routing](#routing) - - [Hierarchy](#hierarchy) - - [In the Browser](#in-the-browser) - - [Route State](#route-state) - - [Route Parameters](#route-parameters) - -## Examples - -### Routing - -If you use [Protevus](https://pub.dev/packages/angel3_framework), every `Protevus` instance is a `Router` in itself. - -```dart -void main() { - final router = Router(); - - router.get('/users', () {}); - - router.post('/users/:id/timeline', (String id) {}); - - router.get('/square_root/:id([0-9]+)', (n) { - return { 'result': pow(int.parse(n), 0.5) }; - }); - - // You can also have parameters auto-parsed. - // - // Supports int, double, and num. - router.get('/square_root/int:id([0-9]+)', (int n) { - return { 'result': pow(n, 0.5) }; - }); - - router.group('/show/:id', (router) { - router.get('/reviews', (id) { - return someQuery(id).reviews; - }); - - // Optionally restrict params to a RegExp - router.get('/reviews/:reviewId([A-Za-z0-9_]+)', (id, reviewId) { - return someQuery(id).reviews.firstWhere( - (r) => r.id == reviewId); - }); - }, middleware: [put, middleware, here]); - - // Grouping can also take async callbacks. - await router.groupAsync('/hello', (router) async { - var name = await getNameFromFileSystem(); - router.get(name, (req, res) => '...'); - }); -} -``` - -The default `Router` does not give any notification of routes being changed, because there is no inherent stream of URL's for it to listen to. This is good, because a server needs a lot of flexibility with which to handle requests. - -### Hierarchy - -```dart -void main() { - final router = Router(); - - router - .chain('middleware1') - .chain('other_middleware') - .get('/hello', () { - print('world'); - }); - - router.group('/user/:id', (router) { - router.get('/balance', (id) async { - final user = await someQuery(id); - return user.balance; - }); - }); -} -``` - -See [the tests](test/route/no_params.dart) for good examples. - -## In the Browser - -Supports both hashed routes and pushState. The `BrowserRouter` interface exposes a `Stream onRoute`, which can be listened to for changes. It will fire `"NULL"` whenever no route is matched. - -`angel3_route` will also automatically intercept `` elements and redirect them to your routes. - -To prevent this for a given anchor, do any of the following: - -- Do not provide an `href` -- Provide a `download` or `target` attribute on the element -- Set `rel="external"` - -## Route State - -```dart -main() { - final router = BrowserRouter(); - // .. - router.onRoute.listen((route) { - if (route == null) - throw 404; - else route.state['foo'] = 'bar'; - }); - - router.listen(); // Start listening -} -``` - -For applications where you need to access a chain of handlers, consider using `onResolve` instead. You can see an example in `web/shared/basic.dart`. - -## Route Parameters - -Routes can have parameters, as seen in the above examples. Use `allParams` in a `RoutingResult` to get them as a nice `Map`: - -```dart -var router = Router(); -router.get('/book/:id/authors', () => ...); - -var result = router.resolve('/book/foo/authors'); -var params = result.allParams; // {'id': 'foo'}; -``` diff --git a/packages/route/analysis_options.yaml b/packages/route/analysis_options.yaml deleted file mode 100644 index 572dd23..0000000 --- a/packages/route/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: package:lints/recommended.yaml diff --git a/packages/route/example/main.dart b/packages/route/example/main.dart deleted file mode 100644 index dc351a1..0000000 --- a/packages/route/example/main.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'dart:math'; - -import 'package:platform_route/route.dart'; - -void main() { - final router = Router(); - - router.get('/whois/~:user', () {}); - - router.get('/wild*', () {}); - - router.get('/ordinal/int:n([0-9]+)st', () {}); - - print(router.resolveAbsolute('/whois/~thosakwe').first.allParams); - print(router.resolveAbsolute('/wild_thornberrys').first.route.path); - print(router.resolveAbsolute('/ordinal/1st').first.allParams); - - router.get('/users', () {}); - - router.post('/users/:id/timeline', (String id) {}); - - router.get('/square_root/:id([0-9]+)', (String n) { - return {'result': pow(int.parse(n), 0.5)}; - }); - - // You can also have parameters auto-parsed. - // - // Supports int, double, and num. - router.get('/square_root/int:id([0-9]+)', (int n) { - return {'result': pow(n, 0.5)}; - }); - - router.group('/show/:id', (router) { - router.get('/reviews', (id) { - return someQuery(id).reviews; - }); - - // Optionally restrict params to a RegExp - router.get('/reviews/:reviewId([A-Za-z0-9_]+)', (id, reviewId) { - return someQuery(id).reviews.firstWhere((r) => r.id == reviewId); - }); - }); -} - -SomeQuery someQuery(id) => SomeQuery(); - -class SomeQuery { - List get reviews => [ - SomeQueryReview('fake'), - SomeQueryReview('data'), - ]; -} - -class SomeQueryReview { - final String id; - - SomeQueryReview(this.id); -} diff --git a/packages/route/lib/browser.dart b/packages/route/lib/browser.dart deleted file mode 100644 index 29268eb..0000000 --- a/packages/route/lib/browser.dart +++ /dev/null @@ -1,225 +0,0 @@ -import 'dart:async' show Stream, StreamController; -import 'dart:html'; -import 'package:path/path.dart' as p; - -import 'route.dart'; - -final RegExp _hash = RegExp(r'^#/'); -final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); - -/// A variation of the [Router] support both hash routing and push state. -abstract class BrowserRouter extends Router { - /// Fires whenever the active route changes. Fires `null` if none is selected (404). - Stream> get onResolve; - - /// Fires whenever the active route changes. Fires `null` if none is selected (404). - Stream> get onRoute; - - /// Set `hash` to true to use hash routing instead of push state. - /// `listen` as `true` will call `listen` after initialization. - factory BrowserRouter({bool hash = false, bool listen = false}) { - return hash - ? _HashRouter(listen: listen) - : _PushStateRouter(listen: listen); - } - - //BrowserRouter._() : super(); - - void _goTo(String path); - - /// Navigates to the path generated by calling - /// [navigate] with the given [linkParams]. - /// - /// This always navigates to an absolute path. - void go(List linkParams); - - // Handles a route path, manually. - // void handle(String path); - - /// Begins listen for location changes. - void listen(); - - /// Identical to [all]. - Route on(String path, T handler, {Iterable middleware}); -} - -abstract class _BrowserRouterImpl extends Router - implements BrowserRouter { - bool _listening = false; - Route? _current; - final StreamController> _onResolve = - StreamController>(); - final StreamController> _onRoute = StreamController>(); - - Route? get currentRoute => _current; - - @override - Stream> get onResolve => _onResolve.stream; - - @override - Stream> get onRoute => _onRoute.stream; - - _BrowserRouterImpl({bool listen = false}) : super() { - if (listen != false) this.listen(); - prepareAnchors(); - } - - @override - void go(Iterable linkParams) => _goTo(navigate(linkParams)); - - @override - Route on(String path, T handler, {Iterable middleware = const []}) => - all(path, handler, middleware: middleware); - - void prepareAnchors() { - final anchors = window.document - .querySelectorAll('a') - .cast(); //:not([dynamic])'); - - for (final $a in anchors) { - if ($a.attributes.containsKey('href') && - $a.attributes.containsKey('download') && - $a.attributes.containsKey('target') && - $a.attributes['rel'] != 'external') { - $a.onClick.listen((e) { - e.preventDefault(); - _goTo($a.attributes['href']!); - //go($a.attributes['href'].split('/').where((str) => str.isNotEmpty)); - }); - } - - $a.attributes['dynamic'] = 'true'; - } - } - - void _listen(); - - @override - void listen() { - if (_listening) { - throw StateError('The router is already listening for page changes.'); - } - _listening = true; - _listen(); - } -} - -class _HashRouter extends _BrowserRouterImpl { - _HashRouter({required bool listen}) : super(listen: listen) { - if (listen) { - this.listen(); - } - } - - @override - void _goTo(String uri) { - window.location.hash = '#$uri'; - } - - void handleHash([_]) { - final path = window.location.hash.replaceAll(_hash, ''); - var allResolved = resolveAbsolute(path); - - if (allResolved.isEmpty) { - // TODO: Need fixing - //_onResolve.add(null); - //_onRoute.add(_current = null); - _current = null; - } else { - var resolved = allResolved.first; - if (resolved.route != _current) { - _onResolve.add(resolved); - _onRoute.add(_current = resolved.route); - } - } - } - - void handlePath(String path) { - final resolved = resolveAbsolute(path).first; - - //if (resolved == null) { - // _onResolve.add(null); - // _onRoute.add(_current = null); - //} else - if (resolved.route != _current) { - _onResolve.add(resolved); - _onRoute.add(_current = resolved.route); - } - } - - @override - void _listen() { - window.onHashChange.listen(handleHash); - handleHash(); - } -} - -class _PushStateRouter extends _BrowserRouterImpl { - late String _basePath; - - _PushStateRouter({required bool listen}) : super(listen: listen) { - var $base = window.document.querySelector('base[href]') as BaseElement; - - if ($base.href.isNotEmpty != true) { - throw StateError( - 'You must have a element present in your document to run the push state router.'); - } - _basePath = $base.href.replaceAll(_straySlashes, ''); - if (listen) this.listen(); - } - - @override - void _goTo(String uri) { - final resolved = resolveAbsolute(uri).first; - var relativeUri = uri; - - if (_basePath.isNotEmpty) { - relativeUri = p.join(_basePath, uri.replaceAll(_straySlashes, '')); - } - - //if (resolved == null) { - // _onResolve.add(null); - // _onRoute.add(_current = null); - //} else { - final route = resolved.route; - var thisPath = route.name ?? ''; - if (thisPath.isEmpty) { - thisPath = route.path; - } - window.history - .pushState({'path': route.path, 'params': {}}, thisPath, relativeUri); - _onResolve.add(resolved); - _onRoute.add(_current = route); - //} - } - - void handleState(state) { - if (state is Map && state.containsKey('path')) { - var path = state['path'].toString(); - final resolved = resolveAbsolute(path).first; - - if (resolved.route != _current) { - //properties.addAll(state['properties'] ?? {}); - _onResolve.add(resolved); - _onRoute.add(_current = resolved.route); - } else { - //_onResolve.add(null); - //_onRoute.add(_current = null); - _current = null; - } - } else { - //_onResolve.add(null); - //_onRoute.add(_current = null); - _current = null; - } - } - - @override - void _listen() { - window.onPopState.listen((e) { - handleState(e.state); - }); - - handleState(window.history.state); - } -} diff --git a/packages/route/lib/route.dart b/packages/route/lib/route.dart deleted file mode 100644 index 8cd7ae4..0000000 --- a/packages/route/lib/route.dart +++ /dev/null @@ -1,5 +0,0 @@ -library platform_route; - -export 'src/middleware_pipeline.dart'; -export 'src/router.dart'; -export 'src/routing_exception.dart'; diff --git a/packages/route/lib/src/grammar.dart b/packages/route/lib/src/grammar.dart deleted file mode 100644 index ecacb58..0000000 --- a/packages/route/lib/src/grammar.dart +++ /dev/null @@ -1,327 +0,0 @@ -part of 'router.dart'; - -class RouteGrammar { - static const String notSlashRgx = r'([^/]+)'; - //static final RegExp rgx = RegExp(r'\((.+)\)'); - static final Parser notSlash = - match(RegExp(notSlashRgx)).value((r) => r.span?.text ?? ''); - - static final Parser regExp = - match(RegExp(r'\(([^\n)]+)\)([^/]+)?')) - .value((r) => r.scanner.lastMatch!); - - static final Parser parameterName = - match(RegExp('$notSlashRgx?' r':([A-Za-z0-9_]+)' r'([^(/\n])?')) - .value((r) => r.scanner.lastMatch!); - - static final Parser parameterSegment = chain([ - parameterName, - match('?').value((r) => true).opt(), - regExp.opt(), - ]).map((r) { - var match = r.value![0] as Match; - - var r2 = r.value![2]; - Match? rgxMatch; - if (r2 != 'NULL') { - rgxMatch = r2 as Match?; - } - - var pre = match[1] ?? ''; - var post = match[3] ?? ''; - RegExp? rgx; - - if (rgxMatch != null) { - rgx = RegExp('(${rgxMatch[1]})'); - post = (rgxMatch[2] ?? '') + post; - } - - if (pre.isNotEmpty || post.isNotEmpty) { - if (rgx != null) { - var pattern = pre + rgx.pattern + post; - rgx = RegExp(pattern); - } else { - rgx = RegExp('$pre$notSlashRgx$post'); - } - } - - // TODO: relook at this later - var m2 = match[2] ?? ''; - var s = ParameterSegment(m2, rgx); - return r.value![1] == true ? OptionalSegment(s) : s; - }); - - static final Parser parsedParameterSegment = chain([ - match(RegExp(r'(int|num|double)'), - errorMessage: 'Expected "int","double", or "num".') - .map((r) => r.span!.text), - parameterSegment, - ]).map((r) { - return ParsedParameterSegment( - r.value![0] as String, r.value![1] as ParameterSegment); - }); - - static final Parser wildcardSegment = - match(RegExp('$notSlashRgx?' r'\*' '$notSlashRgx?')) - .value((r) { - var m = r.scanner.lastMatch!; - var pre = m[1] ?? ''; - var post = m[2] ?? ''; - return WildcardSegment(pre, post); - }); - - static final Parser constantSegment = - notSlash.map((r) => ConstantSegment(r.value)); - - static final Parser slashSegment = - match(SlashSegment.rgx).map((_) => SlashSegment()); - - static final Parser routeSegment = any([ - //slashSegment, - parsedParameterSegment, - parameterSegment, - wildcardSegment, - constantSegment - ]); - - // static final Parser routeDefinition = routeSegment - // .star() - // .map((r) => RouteDefinition(r.value ?? [])) - // .surroundedBy(match(RegExp(r'/*')).opt()); - - static final Parser slashes = match(RegExp(r'/*')); - - static final Parser routeDefinition = routeSegment - .separatedBy(slashes) - .map((r) => RouteDefinition(r.value ?? [])) - .surroundedBy(slashes.opt()); -} - -class RouteDefinition { - final List segments; - - RouteDefinition(this.segments); - - Parser? compile() { - Parser? out; - - for (var i = 0; i < segments.length; i++) { - var s = segments[i]; - var isLast = i == segments.length - 1; - if (out == null) { - out = s.compile(isLast); - } else { - out = s.compileNext( - out.then(match('/')).index(0).cast(), isLast); - } - } - - return out; - } -} - -abstract class RouteSegment { - Parser compile(bool isLast); - - Parser compileNext(Parser p, bool isLast); -} - -class SlashSegment implements RouteSegment { - static final RegExp rgx = RegExp(r'/+'); - - const SlashSegment(); - - @override - Parser compile(bool isLast) { - return match(rgx).map((_) => RouteResult({})); - } - - @override - Parser compileNext(Parser p, bool isLast) { - return p.then(compile(isLast)).index(0).cast(); - } - - @override - String toString() => 'Slash'; -} - -class ConstantSegment extends RouteSegment { - final String? text; - - ConstantSegment(this.text); - - @override - String toString() { - return 'Constant: $text'; - } - - @override - Parser compile(bool isLast) { - return match(text!).map((r) => RouteResult({})); - } - - @override - Parser compileNext(Parser p, bool isLast) { - return p.then(compile(isLast)).index(0).cast(); - } -} - -class WildcardSegment extends RouteSegment { - final String pre, post; - - WildcardSegment(this.pre, this.post); - - @override - String toString() { - return 'Wildcard segment'; - } - - String _symbol(bool isLast) { - if (isLast) return r'.*'; - return r'[^/]*'; - } - - RegExp _compile(bool isLast) { - return RegExp('$pre(${_symbol(isLast)})$post'); - // if (isLast) return match(RegExp(r'.*')); - // return match(RegExp(r'[^/]*')); - } - - @override - Parser compile(bool isLast) { - return match(_compile(isLast)).map((r) { - var result = r.scanner.lastMatch; - if (result != null) { - //return RouteResult({}, tail: r.scanner.lastMatch![1]) - return RouteResult({}, tail: result[1]); - } else { - return RouteResult({}); - } - }); - } - - @override - Parser compileNext(Parser p, bool isLast) { - return p.then(compile(isLast)).map((r) { - var items = r.value!.cast(); - var a = items[0], b = items[1]; - return a - ..addAll(b.params) - .._setTail(b.tail); - }); - } -} - -class OptionalSegment extends ParameterSegment { - final ParameterSegment parameter; - - OptionalSegment(this.parameter) : super(parameter.name, parameter.regExp); - - @override - String toString() { - return 'Optional: $parameter'; - } - - @override - Parser compile(bool isLast) { - return super.compile(isLast).opt(); - } - - @override - Parser compileNext(Parser p, bool isLast) { - return p.then(_compile().opt()).map((r) { - // Return an empty RouteResult if null - if (r.value == null) { - return RouteResult({}); - } - - var v = r.value!; - - if (v[1] == null) { - return v[0] as RouteResult; - } - return (v[0] as RouteResult) - ..addAll({name: Uri.decodeComponent(v as String)}); - }); - } -} - -class ParameterSegment extends RouteSegment { - final String name; - final RegExp? regExp; - - ParameterSegment(this.name, this.regExp); - - @override - String toString() { - if (regExp != null) { - return 'Param: $name (${regExp?.pattern})'; - } - return 'Param: $name'; - } - - Parser _compile() { - if (regExp != null) { - return match(regExp!).value((r) { - var result = r.scanner.lastMatch; - if (result != null) { - // TODO: Invalid method - //return r.scanner.lastMatch![1]; - return result.toString(); - } else { - return ''; - } - }); - } else { - return RouteGrammar.notSlash; - } - } - - @override - Parser compile(bool isLast) { - return _compile() - .map((r) => RouteResult({name: Uri.decodeComponent(r.value!)})); - } - - @override - Parser compileNext(Parser p, bool isLast) { - return p.then(_compile()).map((r) { - return (r.value![0] as RouteResult) - ..addAll({name: Uri.decodeComponent(r.value![1] as String)}); - }); - } -} - -class ParsedParameterSegment extends RouteSegment { - final String type; - final ParameterSegment parameter; - - ParsedParameterSegment(this.type, this.parameter); - - num getValue(String s) { - switch (type) { - case 'int': - return int.parse(s); - case 'double': - return double.parse(s); - default: - return num.parse(s); - } - } - - @override - Parser compile(bool isLast) { - return parameter._compile().map((r) => RouteResult( - {parameter.name: getValue(Uri.decodeComponent(r.span!.text))})); - } - - @override - Parser compileNext(Parser p, bool isLast) { - return p.then(parameter._compile()).map((r) { - return (r.value![0] as RouteResult) - ..addAll({ - parameter.name: getValue(Uri.decodeComponent(r.value![1] as String)) - }); - }); - } -} diff --git a/packages/route/lib/src/middleware_pipeline.dart b/packages/route/lib/src/middleware_pipeline.dart deleted file mode 100644 index 97da1a3..0000000 --- a/packages/route/lib/src/middleware_pipeline.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'router.dart'; - -/// A chain of arbitrary handlers obtained by routing a path. -class MiddlewarePipeline { - /// All the possible routes that matched the given path. - final Iterable> routingResults; - final List _handlers = []; - - /// An ordered list of every handler delegated to handle this request. - List get handlers { - /* - if (_handlers != null) return _handlers; - final handlers = []; - - for (var result in routingResults) { - handlers.addAll(result.allHandlers); - } - - return _handlers = handlers; - - */ - if (_handlers.isNotEmpty) { - return _handlers; - } - - for (var result in routingResults) { - _handlers.addAll(result.allHandlers); - } - - return _handlers; - } - - MiddlewarePipeline(Iterable> routingResults) - : routingResults = routingResults.toList(); -} - -/// Iterates through a [MiddlewarePipeline]. -class MiddlewarePipelineIterator implements Iterator> { - final MiddlewarePipeline pipeline; - final Iterator> _inner; - - MiddlewarePipelineIterator(this.pipeline) - : _inner = pipeline.routingResults.iterator; - - @override - RoutingResult get current => _inner.current; - - @override - bool moveNext() => _inner.moveNext(); -} diff --git a/packages/route/lib/src/route.dart b/packages/route/lib/src/route.dart deleted file mode 100644 index 85baded..0000000 --- a/packages/route/lib/src/route.dart +++ /dev/null @@ -1,99 +0,0 @@ -part of 'router.dart'; - -/// Represents a virtual location within an application. -class Route { - final String method; - final String path; - final Map> _cache = {}; - final RouteDefinition? _routeDefinition; - final List handlers; - String? name; - Parser? _parser; - - Route(this.path, {required this.method, required this.handlers}) - : _routeDefinition = RouteGrammar.routeDefinition - .parse(SpanScanner(path.replaceAll(_straySlashes, ''))) - .value { - if (_routeDefinition?.segments.isNotEmpty != true) { - _parser = match('').map((r) => RouteResult({})); - } - - /* - var result = RouteGrammar.routeDefinition - .parse(SpanScanner(path.replaceAll(_straySlashes, ''))); - - if (result.value != null) { - - //throw ArgumentError('[Route] Failed to create route for $path'); - _routeDefinition = result.value; - if (_routeDefinition.segments.isEmpty) { - _parser = match('').map((r) => RouteResult({})); - } - } else { - _parser = match('').map((r) => RouteResult({})); - } - */ - } - - factory Route.join(Route a, Route b) { - var start = a.path.replaceAll(_straySlashes, ''); - var end = b.path.replaceAll(_straySlashes, ''); - return Route('$start/$end'.replaceAll(_straySlashes, ''), - method: b.method, handlers: b.handlers); - } - - //List get handlers => _handlers; - - Parser? get parser => _parser ??= _routeDefinition?.compile(); - - @override - String toString() { - return '$method $path => $handlers'; - } - - Route clone() { - return Route(path, method: method, handlers: handlers) - .._cache.addAll(_cache); - } - - String makeUri(Map params) { - var b = StringBuffer(); - var i = 0; - - if (_routeDefinition != null) { - for (var seg in _routeDefinition.segments) { - if (i++ > 0) b.write('/'); - if (seg is ConstantSegment) { - b.write(seg.text); - } else if (seg is ParameterSegment) { - if (!params.containsKey(seg.name)) { - throw ArgumentError('Missing parameter "${seg.name}".'); - } - b.write(params[seg.name]); - } - } - } - - return b.toString(); - } -} - -/// The result of matching an individual route. -class RouteResult { - /// The parsed route parameters. - final Map params; - - /// Optional. An explicit "tail" value to set. - String? get tail => _tail; - - String? _tail; - - RouteResult(this.params, {String? tail}) : _tail = tail; - - void _setTail(String? v) => _tail ??= v; - - /// Adds parameters. - void addAll(Map map) { - params.addAll(map); - } -} diff --git a/packages/route/lib/src/router.dart b/packages/route/lib/src/router.dart deleted file mode 100644 index 8585b10..0000000 --- a/packages/route/lib/src/router.dart +++ /dev/null @@ -1,493 +0,0 @@ -library platform_route.src.router; - -import 'dart:async'; -import 'package:belatuk_combinator/belatuk_combinator.dart'; -import 'package:string_scanner/string_scanner.dart'; - -import '../string_util.dart'; -import 'routing_exception.dart'; -part 'grammar.dart'; -part 'route.dart'; -part 'routing_result.dart'; -part 'symlink_route.dart'; - -//final RegExp _param = RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?'); -//final RegExp _rgxEnd = RegExp(r'\$+$'); -//final RegExp _rgxStart = RegExp(r'^\^+'); -//final RegExp _rgxStraySlashes = -// RegExp(r'(^((\\+/)|(/))+)|(((\\+/)|(/))+$)'); -//final RegExp _slashDollar = RegExp(r'/+\$'); -final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); - -/// An abstraction over complex [Route] trees. Use this instead of the raw API. :) -class Router { - final Map>> _cache = {}; - - //final List<_ChainedRouter> _chained = []; - final List _middleware = []; - final Map> _mounted = {}; - final List> _routes = []; - bool _useCache = false; - - List get middleware => List.unmodifiable(_middleware); - - Map> get mounted => - Map>.unmodifiable(_mounted); - - List> get routes { - return _routes.fold>>([], (out, route) { - if (route is SymlinkRoute) { - var childRoutes = - route.router.routes.fold>>([], (out, r) { - return out - ..add( - route.path.isEmpty ? r : Route.join(route, r), - ); - }); - - return out..addAll(childRoutes); - } else { - return out..add(route); - } - }); - } - - /// Provide a `root` to make this Router revolve around a pre-defined route. - /// Not recommended. - Router(); - - /// Enables the use of a cache to eliminate the overhead of consecutive resolutions of the same path. - void enableCache() { - _useCache = true; - } - - /// Adds a route that responds to the given path - /// for requests with the given method (case-insensitive). - /// Provide '*' as the method to respond to all methods. - Route addRoute(String method, String path, T handler, - {Iterable middleware = const []}) { - if (_useCache == true) { - throw StateError('Cannot add routes after caching is enabled.'); - } - - // Check if any mounted routers can match this - final handlers = [handler]; - - //middleware ??= []; - - handlers.insertAll(0, middleware); - - final route = Route(path, method: method, handlers: handlers); - _routes.add(route); - return route; - } - - /// Prepends the given [middleware] to any routes created - /// by the resulting router. - /// - /// The resulting router can be chained, too. - ChainedRouter chain(Iterable middleware) { - var piped = ChainedRouter(this, middleware); - var route = SymlinkRoute('/', piped); - _routes.add(route); - return piped; - } - - /// Returns a [Router] with a duplicated version of this tree. - Router clone() { - final router = Router(); - final newMounted = Map>.from(mounted); - - for (var route in routes) { - if (route is! SymlinkRoute) { - router._routes.add(route.clone()); - } else { - final newRouter = route.router.clone(); - newMounted[route.path] = newRouter; - final symlink = SymlinkRoute(route.path, newRouter); - router._routes.add(symlink); - } - } - - return router.._mounted.addAll(newMounted); - } - - /// Creates a visual representation of the route hierarchy and - /// passes it to a callback. If none is provided, `print` is called. - void dumpTree( - {Function(String tree)? callback, - String header = 'Dumping route tree:', - String tab = ' '}) { - final buf = StringBuffer(); - var tabs = 0; - - if (header.isNotEmpty) { - buf.writeln(header); - } - - buf.writeln(''); - - void indent() { - for (var i = 0; i < tabs; i++) { - buf.write(tab); - } - } - - void dumpRouter(Router router) { - indent(); - tabs++; - - for (var route in router.routes) { - indent(); - buf.write('- '); - if (route is! SymlinkRoute) buf.write('${route.method} '); - buf.write(route.path.isNotEmpty ? route.path : '/'); - - if (route is SymlinkRoute) { - buf.writeln(); - dumpRouter(route.router); - } else { - buf.writeln(' => ${route.handlers.length} handler(s)'); - } - } - - tabs--; - } - - dumpRouter(this); - - (callback ?? print)(buf.toString()); - } - - /// Creates a route, and allows you to add child routes to it - /// via a [Router] instance. - /// - /// Returns the created route. - /// You can also register middleware within the router. - SymlinkRoute group(String path, void Function(Router router) callback, - {Iterable middleware = const [], String name = ''}) { - final router = Router().._middleware.addAll(middleware); - callback(router); - return mount(path, router)..name = name; - } - - /// Asynchronous equivalent of [group]. - Future> groupAsync( - String path, FutureOr Function(Router router) callback, - {Iterable middleware = const [], String name = ''}) async { - final router = Router().._middleware.addAll(middleware); - await callback(router); - return mount(path, router)..name = name; - } - - /// Generates a URI string based on the given input. - /// Handy when you have named routes. - /// - /// Each item in `linkParams` should be a [Route], - /// `String` or `Map`. - /// - /// Strings should be route names, namespaces, or paths. - /// Maps should be parameters, which will be filled - /// into the previous route. - /// - /// Paths and segments should correspond to the way - /// you declared them. - /// - /// For example, if you declared a route group on - /// `'users/:id'`, it would not be resolved if you - /// passed `'users'` in [linkParams]. - /// - /// Leading and trailing slashes are automatically - /// removed. - /// - /// Set [absolute] to `true` to insert a forward slash - /// before the generated path. - /// - /// Example: - /// ```dart - /// router.navigate(['users/:id', {'id': '1337'}, 'profile']); - /// ``` - String navigate(Iterable linkParams, {bool absolute = true}) { - final segments = []; - Router search = this; - Route? lastRoute; - - for (final param in linkParams) { - var resolved = false; - - if (param is String) { - // Search by name - for (var route in search.routes) { - if (route.name == param) { - segments.add(route.path.replaceAll(_straySlashes, '')); - lastRoute = route; - - if (route is SymlinkRoute) { - search = route.router; - } - - resolved = true; - break; - } - } - - // Search by path - if (!resolved) { - var scanner = SpanScanner(param.replaceAll(_straySlashes, '')); - for (var route in search.routes) { - var pos = scanner.position; - var parseResult = route.parser?.parse(scanner); - if (parseResult != null) { - if (parseResult.successful && scanner.isDone) { - segments.add(route.path.replaceAll(_straySlashes, '')); - lastRoute = route; - - if (route is SymlinkRoute) { - search = route.router; - } - - resolved = true; - break; - } else { - scanner.position = pos; - } - } else { - scanner.position = pos; - } - } - } - - if (!resolved) { - throw RoutingException( - 'Cannot resolve route for link param "$param".'); - } - } else if (param is Route) { - segments.add(param.path.replaceAll(_straySlashes, '')); - } else if (param is Map) { - if (lastRoute == null) { - throw RoutingException( - 'Maps in link params must be preceded by a Route or String.'); - } else { - segments.removeLast(); - segments.add(lastRoute.makeUri(param).replaceAll(_straySlashes, '')); - } - } else { - throw RoutingException( - 'Link param $param is not Route, String, or Map.'); - } - } - - return absolute - ? '/${segments.join('/').replaceAll(_straySlashes, '')}' - : segments.join('/'); - } - - /// Finds the first [Route] that matches the given path, - /// with the given method. - bool resolve(String absolute, String relative, List> out, - {String method = 'GET', bool strip = true}) { - final cleanRelative = - strip == false ? relative : stripStraySlashes(relative); - var scanner = SpanScanner(cleanRelative); - - bool crawl(Router r) { - var success = false; - - for (var route in r.routes) { - var pos = scanner.position; - - if (route is SymlinkRoute) { - if (route.parser != null) { - var pp = route.parser!; - if (pp.parse(scanner).successful) { - var s = crawl(route.router); - if (s) success = true; - } - } - - scanner.position = pos; - } else if (route.method == '*' || route.method == method) { - var parseResult = route.parser?.parse(scanner); - if (parseResult != null) { - if (parseResult.successful && scanner.isDone) { - var tailResult = parseResult.value?.tail ?? ''; - //print(tailResult); - var result = RoutingResult( - parseResult: parseResult, - params: parseResult.value!.params, - shallowRoute: route, - shallowRouter: this, - tail: tailResult + scanner.rest); - out.add(result); - success = true; - } - } - scanner.position = pos; - } - } - - return success; - } - - return crawl(this); - } - - /// Returns the result of [resolve] with [path] passed as - /// both `absolute` and `relative`. - Iterable> resolveAbsolute(String path, - {String method = 'GET', bool strip = true}) => - resolveAll(path, path, method: method, strip: strip); - - /// Finds every possible [Route] that matches the given path, - /// with the given method. - Iterable> resolveAll(String absolute, String relative, - {String method = 'GET', bool strip = true}) { - if (_useCache == true) { - return _cache.putIfAbsent('$method$absolute', - () => _resolveAll(absolute, relative, method: method, strip: strip)); - } - - return _resolveAll(absolute, relative, method: method, strip: strip); - } - - Iterable> _resolveAll(String absolute, String relative, - {String method = 'GET', bool strip = true}) { - var results = >[]; - resolve(absolute, relative, results, method: method, strip: strip); - - // _printDebug( - // 'Results of $method "/${absolute.replaceAll(_straySlashes, '')}": ${results.map((r) => r.route).toList()}'); - return results; - } - - /// Incorporates another [Router]'s routes into this one's. - SymlinkRoute mount(String path, Router router) { - final route = SymlinkRoute(path, router); - _mounted[route.path] = router; - _routes.add(route); - //route._head = RegExp(route.matcher.pattern.replaceAll(_rgxEnd, '')); - - return route; - } - - /// Adds a route that responds to any request matching the given path. - Route all(String path, T handler, {Iterable middleware = const []}) { - return addRoute('*', path, handler, middleware: middleware); - } - - /// Adds a route that responds to a DELETE request. - Route delete(String path, T handler, {Iterable middleware = const []}) { - return addRoute('DELETE', path, handler, middleware: middleware); - } - - /// Adds a route that responds to a GET request. - Route get(String path, T handler, {Iterable middleware = const []}) { - return addRoute('GET', path, handler, middleware: middleware); - } - - /// Adds a route that responds to a HEAD request. - Route head(String path, T handler, {Iterable middleware = const []}) { - return addRoute('HEAD', path, handler, middleware: middleware); - } - - /// Adds a route that responds to a OPTIONS request. - Route options(String path, T handler, - {Iterable middleware = const {}}) { - return addRoute('OPTIONS', path, handler, middleware: middleware); - } - - /// Adds a route that responds to a POST request. - Route post(String path, T handler, {Iterable middleware = const []}) { - return addRoute('POST', path, handler, middleware: middleware); - } - - /// Adds a route that responds to a PATCH request. - Route patch(String path, T handler, {Iterable middleware = const []}) { - return addRoute('PATCH', path, handler, middleware: middleware); - } - - /// Adds a route that responds to a PUT request. - Route put(String path, T handler, {Iterable middleware = const []}) { - return addRoute('PUT', path, handler, middleware: middleware); - } -} - -class ChainedRouter extends Router { - final List _handlers = []; - Router _root; - - ChainedRouter.empty() : _root = Router(); - - ChainedRouter(this._root, Iterable middleware) { - _handlers.addAll(middleware); - } - - @override - Route addRoute(String method, String path, handler, - {Iterable middleware = const []}) { - var route = super.addRoute(method, path, handler, - middleware: [..._handlers, ...middleware]); - //_root._routes.add(route); - return route; - } - - @override - SymlinkRoute group(String path, void Function(Router router) callback, - {Iterable middleware = const [], String? name}) { - final router = ChainedRouter(_root, [..._handlers, ...middleware]); - callback(router); - return mount(path, router)..name = name; - } - - @override - Future> groupAsync( - String path, FutureOr Function(Router router) callback, - {Iterable middleware = const [], String? name}) async { - final router = ChainedRouter(_root, [..._handlers, ...middleware]); - await callback(router); - return mount(path, router)..name = name; - } - - @override - SymlinkRoute mount(String path, Router router) { - final route = super.mount(path, router); - route.router._middleware.insertAll(0, _handlers); - //_root._routes.add(route); - return route; - } - - @override - ChainedRouter chain(Iterable middleware) { - final piped = ChainedRouter.empty().._root = _root; - piped._handlers.addAll([..._handlers, ...middleware]); - var route = SymlinkRoute('/', piped); - _routes.add(route); - return piped; - } -} - -/// Optimizes a router by condensing all its routes into one level. -Router flatten(Router router) { - var flattened = Router(); - - for (var route in router.routes) { - if (route is SymlinkRoute) { - var base = route.path.replaceAll(_straySlashes, ''); - var child = flatten(route.router); - - for (var route in child.routes) { - var path = route.path.replaceAll(_straySlashes, ''); - var joined = '$base/$path'.replaceAll(_straySlashes, ''); - flattened.addRoute(route.method, joined.replaceAll(_straySlashes, ''), - route.handlers.last, - middleware: - route.handlers.take(route.handlers.length - 1).toList()); - } - } else { - flattened.addRoute(route.method, route.path, route.handlers.last, - middleware: route.handlers.take(route.handlers.length - 1).toList()); - } - } - - return flattened..enableCache(); -} diff --git a/packages/route/lib/src/routing_exception.dart b/packages/route/lib/src/routing_exception.dart deleted file mode 100644 index 4dd9b75..0000000 --- a/packages/route/lib/src/routing_exception.dart +++ /dev/null @@ -1,21 +0,0 @@ -/// Represents an error in route configuration or navigation. -abstract class RoutingException implements Exception { - factory RoutingException(String message) => _RoutingExceptionImpl(message); - - /// Occurs when trying to resolve the parent of a [Route] without a parent. - factory RoutingException.orphan() => _RoutingExceptionImpl( - "Tried to resolve path '..' on a route that has no parent."); - - /// Occurs when the user attempts to navigate to a non-existent route. - factory RoutingException.noSuchRoute(String path) => _RoutingExceptionImpl( - "Tried to navigate to non-existent route: '$path'."); -} - -class _RoutingExceptionImpl implements RoutingException { - final String message; - - _RoutingExceptionImpl(this.message); - - @override - String toString() => message; -} diff --git a/packages/route/lib/src/routing_result.dart b/packages/route/lib/src/routing_result.dart deleted file mode 100644 index 50edfea..0000000 --- a/packages/route/lib/src/routing_result.dart +++ /dev/null @@ -1,95 +0,0 @@ -part of 'router.dart'; - -/// Represents a complex result of navigating to a path. -class RoutingResult { - /// The parse result that matched the given sub-path. - final ParseResult parseResult; - - /// A nested instance, if a sub-path was matched. - final Iterable> nested; - - /// All route params matching this route on the current sub-path. - final Map params = {}; - - /// The [Route] that answered this sub-path. - /// - /// This is mostly for internal use, and useless in production. - final Route shallowRoute; - - /// The [Router] that answered this sub-path. - /// - /// Only really for internal use. - final Router shallowRouter; - - /// The remainder of the full path that was not matched, and was passed to [nested] routes. - final String tail; - - /// The [RoutingResult] that matched the most specific sub-path. - RoutingResult get deepest { - var search = this; - - while (search.nested.isNotEmpty == true) { - search = search.nested.first; - } - - return search; - } - - /// The most specific route. - Route get route => deepest.shallowRoute; - - /// The most specific router. - Router get router => deepest.shallowRouter; - - /// The handlers at this sub-path. - List get handlers { - return [...shallowRouter.middleware, ...shallowRoute.handlers]; - } - - /// All handlers on this sub-path and its children. - List get allHandlers { - final handlers = []; - - void crawl(RoutingResult result) { - handlers.addAll(result.handlers); - - if (result.nested.isNotEmpty == true) { - for (var r in result.nested) { - crawl(r); - } - } - } - - crawl(this); - - return handlers; - } - - /// All parameters on this sub-path and its children. - Map get allParams { - final params = {}; - - void crawl(RoutingResult result) { - params.addAll(result.params); - - if (result.nested.isNotEmpty == true) { - for (var r in result.nested) { - crawl(r); - } - } - } - - crawl(this); - return params; - } - - RoutingResult( - {required this.parseResult, - Map params = const {}, - this.nested = const Iterable.empty(), - required this.shallowRoute, - required this.shallowRouter, - required this.tail}) { - this.params.addAll(params); - } -} diff --git a/packages/route/lib/src/symlink_route.dart b/packages/route/lib/src/symlink_route.dart deleted file mode 100644 index cf46966..0000000 --- a/packages/route/lib/src/symlink_route.dart +++ /dev/null @@ -1,8 +0,0 @@ -part of 'router.dart'; - -/// Placeholder [Route] to serve as a symbolic link -/// to a mounted [Router]. -class SymlinkRoute extends Route { - final Router router; - SymlinkRoute(super.path, this.router) : super(method: 'GET', handlers: []); -} diff --git a/packages/route/lib/string_util.dart b/packages/route/lib/string_util.dart deleted file mode 100644 index d8eb228..0000000 --- a/packages/route/lib/string_util.dart +++ /dev/null @@ -1,43 +0,0 @@ -/// Helper functions to performantly transform strings, without `RegExp`. -library angel3_route.string_util; - -/// Removes leading and trailing occurrences of a pattern from a string. -String stripStray(String haystack, String needle) { - int firstSlash; - - if (haystack.startsWith(needle)) { - firstSlash = haystack.indexOf(needle); - if (firstSlash == -1) return haystack; - } else { - firstSlash = -1; - } - - if (firstSlash == haystack.length - 1) { - return haystack.length == 1 ? '' : haystack.substring(0, firstSlash); - } - - // Find last leading index of slash - for (var i = firstSlash + 1; i < haystack.length; i++) { - if (haystack[i] != needle) { - var sub = haystack.substring(i); - - if (!sub.endsWith(needle)) return sub; - - var lastSlash = sub.lastIndexOf(needle); - - for (var j = lastSlash - 1; j >= 0; j--) { - if (sub[j] != needle) { - return sub.substring(0, j + 1); - } - } - - return lastSlash == -1 ? sub : sub.substring(0, lastSlash); - } - } - - return haystack.substring(0, firstSlash); -} - -String stripStraySlashes(String str) => stripStray(str, '/'); - -String stripRegexStraySlashes(String str) => stripStray(str, '\\/'); diff --git a/packages/route/pubspec.yaml b/packages/route/pubspec.yaml deleted file mode 100644 index f066f8e..0000000 --- a/packages/route/pubspec.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: platform_route -version: 9.0.0 -description: A powerful, isomorphic routing library for Dart. It is mainly used in the Protevus Platform, but can be used in Flutter and on the Web. -homepage: https://protevus.com -documentation: https://docs.protevus.com -repository: https://git.protevus.com/protevus/platform/src/branch/main/packages/route -environment: - sdk: '>=3.3.0 <4.0.0' -dependencies: - belatuk_combinator: ^5.2.0 - string_scanner: ^1.4.0 - path: ^1.9.1 -dev_dependencies: - build_runner: ^2.4.13 - build_web_compilers: ^4.0.11 - http: ^1.2.2 - test: ^1.25.8 - lints: ^4.0.0 diff --git a/packages/route/repubspec.yaml b/packages/route/repubspec.yaml deleted file mode 100644 index 958364f..0000000 --- a/packages/route/repubspec.yaml +++ /dev/null @@ -1,2 +0,0 @@ -push_state: - base: push_state/basic.html \ No newline at end of file diff --git a/packages/route/test/chain_nest_test.dart b/packages/route/test/chain_nest_test.dart deleted file mode 100644 index c136cb4..0000000 --- a/packages/route/test/chain_nest_test.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:platform_route/route.dart'; -import 'package:test/test.dart'; - -void main() { - var router = Router() - ..chain(['a']).group('/b', (router) { - router.chain(['c']).chain(['d']).group('/e', (router) { - router.get('f', 'g'); - }); - }) - ..dumpTree(); - - test('nested route groups with chain', () { - var r = router.resolveAbsolute('/b/e/f').first.route; - expect(r, isNotNull); - expect(r.handlers, hasLength(4)); - expect(r.handlers, equals(['a', 'c', 'd', 'g'])); - }); -} diff --git a/packages/route/test/navigate_test.dart b/packages/route/test/navigate_test.dart deleted file mode 100644 index e9bfe45..0000000 --- a/packages/route/test/navigate_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:platform_route/route.dart'; -import 'package:test/test.dart'; - -void main() { - final router = Router(); - - router.get('/', 'GET').name = 'root'; - router.get('/user/:id', 'GET'); - router.get('/first/:first/last/:last', 'GET').name = 'full_name'; - - String navigate(params) { - final uri = router.navigate(params as Iterable); - print('Uri: $uri'); - return uri; - } - - router.dumpTree(); - - group('top-level', () { - test('named', () { - expect(navigate(['root']), equals('/')); - }); - - test('params', () { - expect( - navigate([ - 'user/:id', - {'id': 1337} - ]), - equals('/user/1337')); - - expect( - navigate([ - 'full_name', - {'first': 'John', 'last': 'Smith'} - ]), - equals('/first/John/last/Smith')); - }); - - test('root', () { - expect(navigate(['/']), equals('/')); - }); - }); -} diff --git a/packages/route/test/params_test.dart b/packages/route/test/params_test.dart deleted file mode 100644 index cc0f111..0000000 --- a/packages/route/test/params_test.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:platform_route/route.dart'; -import 'package:test/test.dart'; - -void main() { - final router = Router() - ..get('/hello', '') - ..get('/user/:id', ''); - - router.group('/book/:id', (router) { - router.get('/reviews', ''); - router.get('/readers/:readerId', ''); - }); - - router.mount('/color', Router()..get('/:name/shades', '')); - - setUp(router.dumpTree); - - void expectParams(String path, Map params) { - final p = {}; - final resolved = router.resolveAll(path, path); - print('Resolved $path => ${resolved.map((r) => r.allParams).toList()}'); - for (final result in resolved) { - p.addAll(result.allParams); - } - expect(p, equals(params)); - } - - group('top-level', () { - test('no params', () => expectParams('/hello', {})); - - test('one param', () => expectParams('/user/0', {'id': '0'})); - }); - - group('group', () { - //test('root', () => expectParams('/book/1337', {'id': '1337'})); - test('path', () => expectParams('/book/1337/reviews', {'id': '1337'})); - test( - 'two params', - () => expectParams( - '/book/1337/readers/foo', {'id': '1337', 'readerId': 'foo'})); - }); - - test('mount', - () => expectParams('/color/chartreuse/shades', {'name': 'chartreuse'})); -} diff --git a/packages/route/test/parse_test.dart b/packages/route/test/parse_test.dart deleted file mode 100644 index c691697..0000000 --- a/packages/route/test/parse_test.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:platform_route/route.dart'; -import 'package:test/test.dart'; - -void main() { - var router = Router() - ..get('/int/int:id', '') - ..get('/double/double:id', '') - ..get('/num/num:id', ''); - - num? getId(String path) { - var result = router.resolveAbsolute(path).first; - return result.allParams['id'] as num?; - } - - test('parse', () { - expect(getId('/int/2'), 2); - expect(getId('/double/2.0'), 2.0); - expect(getId('/num/-2.4'), -2.4); - }); -} diff --git a/packages/route/test/root_test.dart b/packages/route/test/root_test.dart deleted file mode 100644 index 651e0e1..0000000 --- a/packages/route/test/root_test.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:platform_route/route.dart'; -import 'package:test/test.dart'; - -void main() { - test('resolve / on /', () { - var router = Router() - ..group('/', (router) { - router.group('/', (router) { - router.get('/', 'ok'); - }); - }); - - expect(router.resolveAbsolute('/'), isNotNull); - }); -} diff --git a/packages/route/test/server_test.dart b/packages/route/test/server_test.dart deleted file mode 100644 index 0aaa66f..0000000 --- a/packages/route/test/server_test.dart +++ /dev/null @@ -1,205 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; -import 'package:platform_route/route.dart'; -import 'package:http/http.dart' as http; -import 'package:test/test.dart'; - -const List> people = [ - {'name': 'John Smith'} -]; - -void main() { - http.Client? client; - - final router = Router(); - late HttpServer server; - String? url; - - router.get('/', (req, res) { - res.write('Root'); - return false; - }); - - router.get('/hello', (req, res) { - res.write('World'); - return false; - }); - - router.group('/people', (router) { - router.get('/', (req, res) { - res.write(json.encode(people)); - return false; - }); - - router.group('/:id', (router) { - router.get('/', (req, res) { - // In a real application, we would take the param, - // but not here... - res.write(json.encode(people.first)); - return false; - }); - - router.get('/name', (req, res) { - // In a real application, we would take the param, - // but not here... - res.write(json.encode(people.first['name'])); - return false; - }); - }); - }); - - final beatles = Router(); - - beatles.post('/spinal_clacker', (req, res) { - res.write('come '); - return true; - }); - - final yellow = Router() - ..get('/submarine', (req, res) { - res.write('we all live in a'); - return false; - }); - - beatles.group('/big', (router) { - router.mount('/yellow', yellow); - }); - - beatles.all('*', (req, res) { - res.write('together'); - return false; - }); - - router.mount('/beatles', beatles); - - setUp(() async { - client = http.Client(); - - router.dumpTree(); - server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); - url = 'http://${server.address.address}:${server.port}'; - - server.listen((req) async { - final res = req.response; - - // Easy middleware pipeline - final results = - router.resolveAbsolute(req.uri.toString(), method: req.method); - final pipeline = MiddlewarePipeline(results); - - if (pipeline.handlers.isEmpty) { - res - ..statusCode = 404 - ..writeln('404 Not Found'); - } else { - for (final handler in pipeline.handlers) { - if (!((await handler(req, res)) as bool)) break; - } - } - - await res.close(); - }); - }); - - tearDown(() async { - await server.close(force: true); - client!.close(); - client = null; - url = null; - }); - - group('top-level', () { - group('get', () { - test('root', () async { - final res = await client!.get(Uri.parse(url!)); - print('Response: ${res.body}'); - expect(res.body, equals('Root')); - }); - - test('path', () async { - final res = await client!.get(Uri.parse('$url/hello')); - print('Response: ${res.body}'); - expect(res.body, equals('World')); - }); - }); - }); - - group('group', () { - group('top-level', () { - test('root', () async { - final res = await client!.get(Uri.parse('$url/people')); - print('Response: ${res.body}'); - expect(json.decode(res.body), equals(people)); - }); - - group('param', () { - test('root', () async { - final res = await client!.get(Uri.parse('$url/people/0')); - print('Response: ${res.body}'); - expect(json.decode(res.body), equals(people.first)); - }); - - test('path', () async { - final res = await client!.get(Uri.parse('$url/people/0/name')); - print('Response: ${res.body}'); - expect(json.decode(res.body), equals(people.first['name'])); - }); - }); - }); - }); - - group('mount', () { - group('path', () { - test('top-level', () async { - final res = - await client!.post(Uri.parse('$url/beatles/spinal_clacker')); - print('Response: ${res.body}'); - expect(res.body, equals('come together')); - }); - - test('fallback', () async { - final res = await client!.patch(Uri.parse('$url/beatles/muddy_water')); - print('Response: ${res.body}'); - expect(res.body, equals('together')); - }); - - test('fallback', () async { - final res = - await client!.patch(Uri.parse('$url/beatles/spanil_clakcer')); - print('Response: ${res.body}'); - expect(res.body, equals('together')); - }); - }); - - test('deep nested', () async { - final res = - await client!.get(Uri.parse('$url/beatles/big/yellow/submarine')); - print('Response: ${res.body}'); - expect(res.body, equals('we all live in a')); - }); - - group('fallback', () {}); - }); - - group('404', () { - dynamic expect404(r) => r.then((res) { - print('Response (${res.statusCode}): ${res.body}'); - expect(res.statusCode, equals(404)); - }); - - test('path', () async { - await expect404(client!.get(Uri.parse('$url/foo'))); - await expect404(client!.get(Uri.parse('$url/bye'))); - await expect404(client!.get(Uri.parse('$url/people/0/age'))); - await expect404(client!.get(Uri.parse('$url/beatles2'))); - }); - - test('method', () async { - await expect404(client!.head(Uri.parse(url!))); - await expect404(client!.patch(Uri.parse('$url/people'))); - await expect404(client!.post(Uri.parse('$url/people/0'))); - await expect404( - client!.delete(Uri.parse('$url/beatles2/spinal_clacker'))); - }); - }); -} diff --git a/packages/route/test/strip_test.dart b/packages/route/test/strip_test.dart deleted file mode 100644 index 42d636c..0000000 --- a/packages/route/test/strip_test.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:platform_route/string_util.dart'; -import 'package:test/test.dart'; - -void main() { - test('strip leading', () { - var a = '///a'; - var b = stripStraySlashes(a); - print('$a => $b'); - expect(b, 'a'); - }); - - test('strip trailing', () { - var a = 'a///'; - var b = stripStraySlashes(a); - print('$a => $b'); - expect(b, 'a'); - }); - - test('strip both', () { - var a = '///a///'; - var b = stripStraySlashes(a); - print('$a => $b'); - expect(b, 'a'); - }); - - test('intermediate slashes preserved', () { - var a = '///a///b//'; - var b = stripStraySlashes(a); - print('$a => $b'); - expect(b, 'a///b'); - }); - - test('only if starts with', () { - var a = 'd///a///b//'; - var b = stripStraySlashes(a); - print('$a => $b'); - expect(b, 'd///a///b'); - }); - - test('only if ends with', () { - var a = '///a///b//c'; - var b = stripStraySlashes(a); - print('$a => $b'); - expect(b, 'a///b//c'); - }); -} diff --git a/packages/route/test/uri_decode_test.dart b/packages/route/test/uri_decode_test.dart deleted file mode 100644 index c3d543d..0000000 --- a/packages/route/test/uri_decode_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:platform_route/route.dart'; -import 'package:test/test.dart'; - -void main() { - test('uri params decoded', () { - var router = Router()..get('/a/:a/b/:b', ''); - - var encoded = - '/a/${Uri.encodeComponent('<<<')}/b/${Uri.encodeComponent('???')}'; - print(encoded); - var result = router.resolveAbsolute(encoded).first; - print(result.allParams); - expect(result.allParams, { - 'a': '<<<', - 'b': '???', - }); - }); -} diff --git a/packages/route/test/wildcard_test.dart b/packages/route/test/wildcard_test.dart deleted file mode 100644 index 862e37b..0000000 --- a/packages/route/test/wildcard_test.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:platform_route/route.dart'; -import 'package:test/test.dart'; - -void main() { - var router = Router(); - router.get('/songs/*/key', 'of life'); - router.get('/isnt/she/*', 'lovely'); - router.all('*', 'stevie'); - - test('match until end if * is last', () { - var result = router.resolveAbsolute('/wonder').first; - expect(result.handlers, ['stevie']); - }); - - test('match if not last', () { - var result = router.resolveAbsolute('/songs/what/key').first; - expect(result.handlers, ['of life']); - }); - - test('match if segments before', () { - var result = - router.resolveAbsolute('/isnt/she/fierce%20harmonica%solo').first; - expect(result.handlers, ['lovely']); - }); - - test('tail explicitly set intermediate', () { - var results = router.resolveAbsolute('/songs/in_the/key'); - var result = results.first; - print(results.map((r) => {r.route.path: r.tail})); - expect(result.tail, 'in_the'); - }); - - test('tail explicitly set at end', () { - var results = router.resolveAbsolute('/isnt/she/epic'); - var result = results.first; - print(results.map((r) => {r.route.path: r.tail})); - expect(result.tail, 'epic'); - }); - - test('tail with trailing', () { - var results = router.resolveAbsolute('/isnt/she/epic/fail'); - var result = results.first; - print(results.map((r) => {r.route.path: r.tail})); - expect(result.tail, 'epic/fail'); - }); -} diff --git a/packages/route/web/hash/basic.dart b/packages/route/web/hash/basic.dart deleted file mode 100644 index 39e4eb4..0000000 --- a/packages/route/web/hash/basic.dart +++ /dev/null @@ -1,4 +0,0 @@ -import 'package:platform_route/browser.dart'; -import '../shared/basic.dart'; - -void main() => basic(BrowserRouter(hash: true)); diff --git a/packages/route/web/hash/basic.html b/packages/route/web/hash/basic.html deleted file mode 100644 index ea7fbaf..0000000 --- a/packages/route/web/hash/basic.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - Hash Router - - - - -

    No Active Route

    -Handler Sequence: -
      -
    • (empty)
    • -
    - - - \ No newline at end of file diff --git a/packages/route/web/index.html b/packages/route/web/index.html deleted file mode 100644 index aab2766..0000000 --- a/packages/route/web/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - Protevus Route Samples - - -

    Protevus Route Samples

    - - - \ No newline at end of file diff --git a/packages/route/web/push_state/basic.dart b/packages/route/web/push_state/basic.dart deleted file mode 100644 index 06483ca..0000000 --- a/packages/route/web/push_state/basic.dart +++ /dev/null @@ -1,4 +0,0 @@ -import 'package:platform_route/browser.dart'; -import '../shared/basic.dart'; - -void main() => basic(BrowserRouter()); diff --git a/packages/route/web/push_state/basic.html b/packages/route/web/push_state/basic.html deleted file mode 100644 index 953bc5a..0000000 --- a/packages/route/web/push_state/basic.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - Push State Router - - - - -

    No Active Route

    -Handler Sequence: -
      -
    • (empty)
    • -
    - - - \ No newline at end of file diff --git a/packages/route/web/shared/basic.dart b/packages/route/web/shared/basic.dart deleted file mode 100644 index a64ee1d..0000000 --- a/packages/route/web/shared/basic.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'dart:html'; -import 'package:platform_route/browser.dart'; - -void basic(BrowserRouter router) { - final $h1 = window.document.querySelector('h1'); - final $ul = window.document.getElementById('handlers'); - - router.onResolve.listen((result) { - final route = result.route; - - // TODO: Relook at this logic - //if (route == null) { - // $h1!.text = 'No Active Route'; - // $ul!.children - // ..clear() - // ..add(LIElement()..text = '(empty)'); - //} else { - if ($h1 != null && $ul != null) { - $h1.text = 'Active Route: ${route.name}'; - $ul.children - ..clear() - ..addAll(result.allHandlers - .map((handler) => LIElement()..text = handler.toString())); - } else { - print('No active Route'); - } - //} - }); - - router.get('a', 'a handler'); - - router.group('b', (router) { - router.get('a', 'b/a handler').name = 'b/a'; - router.get('b', 'b/b handler', middleware: ['b/b middleware']).name = 'b/b'; - }, middleware: ['b middleware']); - - router.get('c', 'c handler'); - - router - ..dumpTree() - ..listen(); -} diff --git a/packages/routing/.gitignore b/packages/routing/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/packages/routing/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/routing/CHANGELOG.md b/packages/routing/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/packages/routing/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/routing/LICENSE.md b/packages/routing/LICENSE.md new file mode 100644 index 0000000..0fd0d03 --- /dev/null +++ b/packages/routing/LICENSE.md @@ -0,0 +1,10 @@ +The MIT License (MIT) + +The Laravel Framework is Copyright (c) Taylor Otwell +The Fabric Framework is Copyright (c) Vieo, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/routing/README.md b/packages/routing/README.md new file mode 100644 index 0000000..8b55e73 --- /dev/null +++ b/packages/routing/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/routing/analysis_options.yaml b/packages/routing/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/packages/routing/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/packages/routing/doc/.gitkeep b/packages/routing/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/routing/example/.gitkeep b/packages/routing/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/routing/lib/src/.gitkeep b/packages/routing/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/routing/pubspec.yaml b/packages/routing/pubspec.yaml new file mode 100644 index 0000000..212ea20 --- /dev/null +++ b/packages/routing/pubspec.yaml @@ -0,0 +1,17 @@ +name: platform_routing +description: The Routing Package for the Protevus Platform +version: 0.0.1 +homepage: https://protevus.com +documentation: https://docs.protevus.com +repository: https://github.com/protevus/platformo + +environment: + sdk: ^3.4.2 + +# Add regular dependencies here. +dependencies: + # path: ^1.8.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/packages/routing/test/.gitkeep b/packages/routing/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/session/.gitignore b/packages/session/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/packages/session/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/session/CHANGELOG.md b/packages/session/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/packages/session/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/session/LICENSE.md b/packages/session/LICENSE.md new file mode 100644 index 0000000..0fd0d03 --- /dev/null +++ b/packages/session/LICENSE.md @@ -0,0 +1,10 @@ +The MIT License (MIT) + +The Laravel Framework is Copyright (c) Taylor Otwell +The Fabric Framework is Copyright (c) Vieo, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/session/README.md b/packages/session/README.md new file mode 100644 index 0000000..8b55e73 --- /dev/null +++ b/packages/session/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/session/analysis_options.yaml b/packages/session/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/packages/session/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/packages/session/doc/.gitkeep b/packages/session/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/session/example/.gitkeep b/packages/session/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/session/lib/src/.gitkeep b/packages/session/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/session/pubspec.yaml b/packages/session/pubspec.yaml new file mode 100644 index 0000000..19cb6c5 --- /dev/null +++ b/packages/session/pubspec.yaml @@ -0,0 +1,17 @@ +name: platform_session +description: The Session Package for the Protevus Platform +version: 0.0.1 +homepage: https://protevus.com +documentation: https://docs.protevus.com +repository: https://github.com/protevus/platformo + +environment: + sdk: ^3.4.2 + +# Add regular dependencies here. +dependencies: + # path: ^1.8.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/packages/session/test/.gitkeep b/packages/session/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/support/LICENSE.md b/packages/support/LICENSE.md new file mode 100644 index 0000000..0fd0d03 --- /dev/null +++ b/packages/support/LICENSE.md @@ -0,0 +1,10 @@ +The MIT License (MIT) + +The Laravel Framework is Copyright (c) Taylor Otwell +The Fabric Framework is Copyright (c) Vieo, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/support/README.md b/packages/support/README.md index 8831761..8b55e73 100644 --- a/packages/support/README.md +++ b/packages/support/README.md @@ -3,12 +3,12 @@ This README describes the package. If you publish this package to pub.dev, this README's contents appear on the landing page for your package. For information about how to write a good package README, see the guide for -[writing package pages](https://dart.dev/tools/pub/writing-package-pages). +[writing package pages](https://dart.dev/guides/libraries/writing-package-pages). For general information about developing packages, see the Dart guide for -[creating packages](https://dart.dev/guides/libraries/create-packages) +[creating packages](https://dart.dev/guides/libraries/create-library-packages) and the Flutter guide for -[developing packages and plugins](https://flutter.dev/to/develop-packages). +[developing packages and plugins](https://flutter.dev/developing-packages). --> TODO: Put a short description of the package here that helps potential users diff --git a/packages/support/doc/.gitkeep b/packages/support/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/support/example/.gitkeep b/packages/support/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/support/example/exception_example.dart b/packages/support/example/exception_example.dart deleted file mode 100644 index 6cd7746..0000000 --- a/packages/support/example/exception_example.dart +++ /dev/null @@ -1,4 +0,0 @@ -import 'package:platform_support/src/exceptions/http_exception.dart'; - -void main() => - throw PlatformHttpException.notFound(message: "Can't find that page!"); diff --git a/packages/support/example/service_provider_example.dart b/packages/support/example/service_provider_example.dart deleted file mode 100644 index 26d2b44..0000000 --- a/packages/support/example/service_provider_example.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_support/providers.dart'; - -/// Example service that will be provided -class ExampleService { - final String message; - ExampleService(this.message); - - void printMessage() { - print(message); - } -} - -/// Example service provider that demonstrates the basic features -class ExampleServiceProvider extends ServiceProvider { - @override - void register() { - super.register(); - // Register a singleton service - singleton(ExampleService('Hello from ExampleService!')); - - // Register an event listener - listen('app.started', (req, res) { - var service = make(); - service.printMessage(); - return true; - }); - } - - @override - List provides() => ['example-service']; -} - -/// Example deferred service provider that demonstrates lazy loading -class DeferredExampleProvider extends DeferredServiceProvider { - @override - void register() { - super.register(); - singleton(ExampleService('Hello from DeferredService!')); - } - - @override - List provides() => ['deferred-service']; - - @override - List dependencies() => ['example-service']; -} - -void main() async { - // Create the application - var app = Application(); - - // Register the service providers - app.registerProvider(ExampleServiceProvider()); - app.registerProvider(DeferredExampleProvider()); - - // The ExampleServiceProvider will be booted immediately - // The DeferredExampleProvider will only be booted when needed - - // Later, when we need the deferred service: - await app.resolveProvider('deferred-service'); - - // Create and start the HTTP server - var http = PlatformHttp(app); - await http.startServer('127.0.0.1', 3000); -} diff --git a/packages/support/lib/exceptions.dart b/packages/support/lib/exceptions.dart deleted file mode 100644 index 35e2912..0000000 --- a/packages/support/lib/exceptions.dart +++ /dev/null @@ -1,8 +0,0 @@ -/// Support for doing something awesome. -/// -/// More dartdocs go here. -library; - -export 'src/exceptions/http_exception.dart'; - -// TODO: Export any libraries intended for clients of this package. diff --git a/packages/support/lib/providers.dart b/packages/support/lib/providers.dart deleted file mode 100644 index 1d3c8a8..0000000 --- a/packages/support/lib/providers.dart +++ /dev/null @@ -1,6 +0,0 @@ -/// Support for Laravel-like service providers in Dart. -library platform_support.providers; - -export 'src/providers/service_provider.dart'; -export 'src/providers/deferred_service_provider.dart'; -export 'src/providers/service_provider_support.dart'; diff --git a/packages/support/lib/src/.gitkeep b/packages/support/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/support/lib/src/exceptions/http_exception.dart b/packages/support/lib/src/exceptions/http_exception.dart deleted file mode 100644 index 07e5a38..0000000 --- a/packages/support/lib/src/exceptions/http_exception.dart +++ /dev/null @@ -1,123 +0,0 @@ -library platform_http_exception; - -//import 'package:dart2_constant/convert.dart'; -import 'dart:convert'; - -/// Exception class that can be serialized to JSON and serialized to clients. -/// Carries HTTP-specific metadata, like [statusCode]. -/// -/// Originally inspired by -/// [feathers-errors](https://github.com/feathersjs/feathers-errors). -class PlatformHttpException implements Exception { - /// A list of errors that occurred when this exception was thrown. - final List errors = []; - - /// The error throw by exception. - dynamic error; - - /// The cause of this exception. - String message; - - /// The [StackTrace] associated with this error. - StackTrace? stackTrace; - - /// An HTTP status code this exception will throw. - int statusCode; - - PlatformHttpException( - {this.message = '500 Internal Server Error', - this.stackTrace, - this.statusCode = 500, - this.error, - List errors = const []}) { - this.errors.addAll(errors); - } - - Map toJson() { - return { - 'is_error': true, - 'status_code': statusCode, - 'message': message, - 'errors': errors - }; - } - - Map toMap() => toJson(); - - @override - String toString() { - return '$statusCode: $message'; - } - - factory PlatformHttpException.fromMap(Map data) { - return PlatformHttpException( - statusCode: (data['status_code'] ?? data['statusCode'] ?? 500) as int, - message: data['message']?.toString() ?? 'Internal Server Error', - errors: data['errors'] is Iterable - ? ((data['errors'] as Iterable).map((x) => x.toString()).toList()) - : [], - ); - } - - factory PlatformHttpException.fromJson(String str) => - PlatformHttpException.fromMap(json.decode(str) as Map); - - /// Throws a 400 Bad Request error, including an optional arrray of (validation?) - /// errors you specify. - factory PlatformHttpException.badRequest( - {String message = '400 Bad Request', - List errors = const []}) => - PlatformHttpException(message: message, errors: errors, statusCode: 400); - - /// Throws a 401 Not Authenticated error. - factory PlatformHttpException.notAuthenticated( - {String message = '401 Not Authenticated'}) => - PlatformHttpException(message: message, statusCode: 401); - - /// Throws a 402 Payment Required error. - factory PlatformHttpException.paymentRequired( - {String message = '402 Payment Required'}) => - PlatformHttpException(message: message, statusCode: 402); - - /// Throws a 403 Forbidden error. - factory PlatformHttpException.forbidden({String message = '403 Forbidden'}) => - PlatformHttpException(message: message, statusCode: 403); - - /// Throws a 404 Not Found error. - factory PlatformHttpException.notFound({String message = '404 Not Found'}) => - PlatformHttpException(message: message, statusCode: 404); - - /// Throws a 405 Method Not Allowed error. - factory PlatformHttpException.methodNotAllowed( - {String message = '405 Method Not Allowed'}) => - PlatformHttpException(message: message, statusCode: 405); - - /// Throws a 406 Not Acceptable error. - factory PlatformHttpException.notAcceptable( - {String message = '406 Not Acceptable'}) => - PlatformHttpException(message: message, statusCode: 406); - - /// Throws a 408 Timeout error. - factory PlatformHttpException.methodTimeout( - {String message = '408 Timeout'}) => - PlatformHttpException(message: message, statusCode: 408); - - /// Throws a 409 Conflict error. - factory PlatformHttpException.conflict({String message = '409 Conflict'}) => - PlatformHttpException(message: message, statusCode: 409); - - /// Throws a 422 Not Processable error. - factory PlatformHttpException.notProcessable( - {String message = '422 Not Processable'}) => - PlatformHttpException(message: message, statusCode: 422); - - /// Throws a 501 Not Implemented error. - factory PlatformHttpException.notImplemented( - {String message = '501 Not Implemented'}) => - PlatformHttpException(message: message, statusCode: 501); - - /// Throws a 503 Unavailable error. - factory PlatformHttpException.unavailable( - {String message = '503 Unavailable'}) => - PlatformHttpException(message: message, statusCode: 503); -} diff --git a/packages/support/lib/src/providers/contracts/service_provider.dart b/packages/support/lib/src/providers/contracts/service_provider.dart deleted file mode 100644 index 5891f1f..0000000 --- a/packages/support/lib/src/providers/contracts/service_provider.dart +++ /dev/null @@ -1,30 +0,0 @@ -/// Contract for service providers. -/// -/// This interface defines the core functionality that all service providers -/// must implement. It matches Laravel's ServiceProvider contract to ensure -/// API compatibility. -abstract class ServiceProviderContract { - /// Register any application services. - void register(); - - /// Bootstrap any application services. - void boot(); - - /// Get the services provided by the provider. - List provides(); - - /// Get the events that trigger this service provider to register. - List when(); - - /// Determine if the provider is deferred. - bool isDeferred(); -} - -/// Contract for deferrable providers. -/// -/// This interface matches Laravel's DeferrableProvider contract to ensure -/// API compatibility. -abstract class DeferrableProviderContract { - /// Get the services provided by the provider. - List provides(); -} diff --git a/packages/support/lib/src/providers/deferred_service_provider.dart b/packages/support/lib/src/providers/deferred_service_provider.dart deleted file mode 100644 index 06f5151..0000000 --- a/packages/support/lib/src/providers/deferred_service_provider.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'service_provider.dart'; -import 'contracts/service_provider.dart'; - -/// A service provider that is loaded only when needed. -/// -/// Deferred service providers are not loaded during the initial application boot -/// process. Instead, they are loaded only when one of their provided services is -/// actually needed by the application. -/// -/// This aligns with Laravel's DeferrableProvider interface which requires only -/// the provides() method to indicate which services should trigger loading of -/// the provider. -abstract class DeferredServiceProvider extends ServiceProvider - implements DeferrableProviderContract { - @override - bool isDeferred() => true; - - @override - List provides(); -} diff --git a/packages/support/lib/src/providers/providers.dart b/packages/support/lib/src/providers/providers.dart deleted file mode 100644 index 6b37883..0000000 --- a/packages/support/lib/src/providers/providers.dart +++ /dev/null @@ -1,11 +0,0 @@ -/// Core service provider functionality -library; - -export 'service_provider.dart'; -export 'deferred_service_provider.dart'; - -/// Service provider contracts -export 'contracts/service_provider.dart'; - -/// Support functionality -export 'service_provider_support.dart'; diff --git a/packages/support/lib/src/providers/service_provider.dart b/packages/support/lib/src/providers/service_provider.dart deleted file mode 100644 index 2b19abc..0000000 --- a/packages/support/lib/src/providers/service_provider.dart +++ /dev/null @@ -1,271 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:platform_core/core.dart'; -import 'package:platform_container/container.dart'; -import 'contracts/service_provider.dart'; -import 'service_provider_static.dart'; - -/// Base class for all service providers. -/// -/// Service providers are the central place to configure your application's services. -/// Within a service provider, you may bind things into the service container, register -/// events, middleware, or perform any other tasks to prepare your application for -/// incoming requests. -abstract class ServiceProvider - with ServiceProviderStatic - implements ServiceProviderContract { - /// The application instance. - late Application app; - - /// All of the registered booting callbacks. - final List bootingCallbacks = []; - - /// All of the registered booted callbacks. - final List bootedCallbacks = []; - - /// Create a new service provider instance. - ServiceProvider(); - - /// Register any application services. - @override - @mustCallSuper - void register() {} - - /// Bootstrap any application services. - @override - @mustCallSuper - void boot() { - callBootingCallbacks(); - callBootedCallbacks(); - } - - /// Register a booting callback to be run before the boot operations. - void booting(Function callback) { - bootingCallbacks.add(callback); - } - - /// Register a booted callback to be run after the boot operations. - void booted(Function callback) { - bootedCallbacks.add(callback); - } - - /// Call the registered booting callbacks. - void callBootingCallbacks() { - for (var callback in bootingCallbacks) { - callback(); - } - } - - /// Call the registered booted callbacks. - void callBootedCallbacks() { - for (var callback in bootedCallbacks) { - callback(); - } - } - - /// Merge the given configuration with the existing configuration. - @protected - void mergeConfigFrom(String path, String key) { - // TODO: Implement config merging - } - - /// Replace the given configuration with the existing configuration recursively. - @protected - void replaceConfigRecursivelyFrom(String path, String key) { - // TODO: Implement recursive config replacement - } - - /// Load the given routes file if routes are not already cached. - @protected - void loadRoutesFrom(String path) { - // TODO: Implement route loading - } - - /// Register a view file namespace. - @protected - void loadViewsFrom(String path, String namespace) { - // TODO: Implement view loading - } - - /// Register the given view components with a custom prefix. - @protected - void loadViewComponentsAs(String prefix, List components) { - // TODO: Implement view component loading - } - - /// Register a translation file namespace. - @protected - void loadTranslationsFrom(String path, String namespace) { - // TODO: Implement translation loading - } - - /// Register a JSON translation file path. - @protected - void loadJsonTranslationsFrom(String path) { - // TODO: Implement JSON translation loading - } - - /// Register database migration paths. - @protected - void loadMigrationsFrom(dynamic paths) { - // TODO: Implement migration loading - } - - /// Register Eloquent model factory paths. - @protected - @Deprecated('Will be removed in a future version.') - void loadFactoriesFrom(dynamic paths) { - // TODO: Implement factory loading - } - - /// Setup an after resolving listener, or fire immediately if already resolved. - @protected - void callAfterResolving(String name, Function callback) { - // TODO: Implement after resolving - } - - /// Register migration paths to be published by the publish command. - @protected - void publishesMigrations(List paths, [dynamic groups]) { - // TODO: Implement migration publishing - } - - /// Register paths to be published by the publish command. - @protected - void registerPublishables(Map paths, [dynamic groups]) { - // TODO: Implement path publishing - } - - /// Laravel API compatibility method - forwards to registerPublishables - @protected - @Deprecated('Use registerPublishables instead') - void publishes(Map paths, [dynamic groups]) => - registerPublishables(paths, groups); - - /// Ensure the publish array for the service provider is initialized. - @protected - void ensurePublishArrayInitialized(String className) { - // TODO: Implement publish array initialization - } - - /// Add a publish group / tag to the service provider. - @protected - void addPublishGroup(String group, Map paths) { - // TODO: Implement publish group addition - } - - /// Get the paths to publish. - Map pathsToPublish([String? provider, String? group]) { - // TODO: Implement paths to publish - return {}; - } - - /// Get the paths for the provider or group (or both). - @protected - Map pathsForProviderOrGroup(String? provider, String? group) { - // TODO: Implement provider/group paths - return {}; - } - - /// Get the paths for the provider and group. - @protected - Map pathsForProviderAndGroup(String provider, String group) { - // TODO: Implement provider and group paths - return {}; - } - - /// Get the service providers available for publishing. - List publishableProviders() { - // TODO: Implement publishable providers - return []; - } - - /// Get the migration paths available for publishing. - List publishableMigrationPaths() { - return List.from(ServiceProviderStatic.publishableMigrationPaths); - } - - /// Get the groups available for publishing. - List publishableGroups() { - // TODO: Implement publishable groups - return []; - } - - /// Register the package's custom Artisan commands. - void commands(List commands) { - // TODO: Implement command registration - } - - /// Get the services provided by the provider. - List provides() => []; - - /// Get the events that trigger this service provider to register. - List when() => []; - - /// Determine if the provider is deferred. - bool isDeferred() => false; - - /// Get the default providers for a Laravel application. - List defaultProviders() { - // TODO: Implement default providers - return []; - } - - /// Add the given provider to the application's provider bootstrap file. - bool addProviderToBootstrapFile(String provider, [String? path]) { - // TODO: Implement provider bootstrap - return false; - } - - // Container convenience methods - these are extensions to Laravel's spec - // to make working with Dart's type system more ergonomic - - /// Register a singleton binding in the container. - void singleton(T instance) { - app.container.registerSingleton(instance); - } - - /// Register a binding in the container. - void bind(T Function(Container) factory) { - app.container.registerFactory(factory); - } - - /// Get a service from the container. - T make([Type? type]) { - return app.container.make(type); - } - - /// Determine if a service exists in the container. - bool has() { - return app.container.has(); - } - - /// Register a tagged binding in the container. - void tag(List abstracts, List tags) { - app.startupHooks.add((app) { - for (var type in abstracts) { - for (var tag in tags) { - app.container.registerSingleton(app.container.make(type), as: tag); - } - } - }); - } - - /// Register an event listener. - void listen(String event, RequestHandler listener) { - app.startupHooks.add((app) { - app.fallback((req, res) { - if (req.uri?.path == event) { - return listener(req, res); - } - return true; - }); - }); - } - - /// Register a middleware. - void middleware(String name, RequestHandler handler) { - app.startupHooks.add((app) { - app.responseFinalizers.add(handler); - }); - } -} diff --git a/packages/support/lib/src/providers/service_provider_static.dart b/packages/support/lib/src/providers/service_provider_static.dart deleted file mode 100644 index be0bdbc..0000000 --- a/packages/support/lib/src/providers/service_provider_static.dart +++ /dev/null @@ -1,11 +0,0 @@ -/// Mixin that provides static members for ServiceProvider -mixin ServiceProviderStatic { - /// The paths that should be published. - static final Map> publishes = {}; - - /// The paths that should be published by group. - static final Map> publishGroups = {}; - - /// The migration paths available for publishing. - static final List publishableMigrationPaths = []; -} diff --git a/packages/support/lib/src/providers/service_provider_support.dart b/packages/support/lib/src/providers/service_provider_support.dart deleted file mode 100644 index 15920a3..0000000 --- a/packages/support/lib/src/providers/service_provider_support.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:async'; -import 'package:platform_core/core.dart'; -import 'service_provider.dart'; - -/// Storage class for service provider state -class ServiceProviderStorage { - /// The registered service providers - final Map providers = {}; - - /// The loaded provider types - final Set loaded = {}; - - /// The deferred services and their dependencies - final Map> deferred = {}; -} - -/// Extension that adds service provider support to [Application]. -extension ServiceProviderSupport on Application { - /// Get the provider storage instance. - ServiceProviderStorage get _storage { - if (!container.has()) { - container.registerSingleton(ServiceProviderStorage()); - } - return container.make(); - } - - /// Register a service provider with the application. - Future registerProvider(ServiceProvider provider) async { - provider.app = this; - - var provides = provider.provides(); - for (var service in provides) { - _storage.providers[service] = provider; - if (provider.isDeferred()) { - _storage.deferred[service] = provider.when(); - } - } - - if (!provider.isDeferred()) { - await _bootProvider(provider); - } - } - - /// Boot a service provider. - Future _bootProvider(ServiceProvider provider) async { - if (_storage.loaded.contains(provider.runtimeType)) return; - - try { - // Boot dependencies first - for (var dependency in provider.when()) { - await resolveProvider(dependency); - } - - // Call booting callbacks - provider.callBootingCallbacks(); - - // Register the provider - await Future.sync(() => provider.register()); - - // Execute any startup hooks that were registered during registration - for (var hook in startupHooks.toList()) { - await hook(this); - } - startupHooks.clear(); - - // Boot the provider - await Future.sync(() => provider.boot()); - - // Call booted callbacks - provider.callBootedCallbacks(); - - _storage.loaded.add(provider.runtimeType); - } catch (e) { - // If registration fails, remove from loaded providers - _storage.loaded.remove(provider.runtimeType); - rethrow; - } - } - - /// Resolve a service provider. - Future resolveProvider(String service) async { - var provider = _storage.providers[service]; - if (provider != null && !_storage.loaded.contains(provider.runtimeType)) { - await _bootProvider(provider); - } - } -} diff --git a/packages/support/pubspec.yaml b/packages/support/pubspec.yaml index ab6b996..33091fe 100644 --- a/packages/support/pubspec.yaml +++ b/packages/support/pubspec.yaml @@ -1,20 +1,17 @@ name: platform_support -description: Protevus Platform support package. -version: 9.0.0 +description: The Support Package for the Protevus Platform +version: 0.0.1 homepage: https://protevus.com documentation: https://docs.protevus.com -repository: https://git.protevus.com/protevus/platform/src/branch/main/packages/support +repository: https://github.com/protevus/platformo environment: - sdk: ^3.5.4 + sdk: ^3.4.2 +# Add regular dependencies here. dependencies: - platform_container: ^9.0.0 - platform_core: ^9.0.0 - meta: ^1.16.0 - collection: ^1.19.1 + # path: ^1.8.0 dev_dependencies: - lints: ^4.0.0 + lints: ^3.0.0 test: ^1.24.0 - http: ^1.2.0 diff --git a/packages/support/test/.gitkeep b/packages/support/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/support/test/providers_http_test.dart b/packages/support/test/providers_http_test.dart deleted file mode 100644 index 8db365c..0000000 --- a/packages/support/test/providers_http_test.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_container/container.dart'; -import 'package:platform_support/providers.dart'; -import 'package:test/test.dart'; -import 'package:http/http.dart' as http; - -// Test service provider with HTTP functionality -class HttpTestProvider extends ServiceProvider { - final events = []; - final middlewareCalls = []; - - @override - void register() { - super.register(); - - // Add a route handler - app.fallback((req, res) { - // Record middleware call - middlewareCalls.add(req.uri?.path ?? ''); - - if (req.uri?.path == '/test') { - events.add('route-hit'); - res.write('test'); - return false; - } - return true; - }); - } - - @override - void boot() { - super.boot(); - } - - @override - List provides() => ['http-test']; -} - -void main() { - group('ServiceProvider HTTP Tests', () { - late Application app; - late PlatformHttp server; - late HttpTestProvider provider; - late int port; - - setUp(() async { - app = Application(reflector: const EmptyReflector()); - server = PlatformHttp(app); - provider = HttpTestProvider(); - - // Register provider before starting server - await app.registerProvider(provider); - - // Start server on random port - await server.startServer('127.0.0.1', 0); - port = server.server?.port ?? 0; - expect(port, isNot(0), reason: 'Server should be assigned a port'); - - // Wait a bit for server to be ready - await Future.delayed(Duration(milliseconds: 100)); - }); - - tearDown(() async { - await server.close(); - await app.close(); - }); - - test('routes registered by provider work', () async { - expect(provider.events, isEmpty, - reason: 'No events should be recorded yet'); - - // Make request to test route - var response = await http.get(Uri.parse('http://127.0.0.1:$port/test')); - - // Wait a bit for async handlers to complete - await Future.delayed(Duration(milliseconds: 100)); - - expect(response.statusCode, equals(200), - reason: 'Should get successful response'); - expect(response.body, equals('test'), - reason: 'Should get expected response body'); - expect(provider.events, contains('route-hit'), - reason: 'Route should be hit'); - }); - - test('middleware registered by provider works', () async { - expect(provider.middlewareCalls, isEmpty, - reason: 'No middleware calls should be recorded yet'); - - // Make request to trigger middleware - await http.get(Uri.parse('http://127.0.0.1:$port/test')); - - // Wait a bit for async handlers to complete - await Future.delayed(Duration(milliseconds: 100)); - - expect(provider.middlewareCalls, contains('/test'), - reason: 'Middleware should record request path'); - }); - - test('multiple requests are handled correctly', () async { - // Make multiple requests - await Future.wait([ - http.get(Uri.parse('http://127.0.0.1:$port/test')), - http.get(Uri.parse('http://127.0.0.1:$port/test')), - http.get(Uri.parse('http://127.0.0.1:$port/test')) - ]); - - // Wait a bit for async handlers to complete - await Future.delayed(Duration(milliseconds: 100)); - - expect(provider.events.length, equals(3), - reason: 'Should record 3 route hits'); - expect(provider.middlewareCalls.length, equals(3), - reason: 'Should record 3 middleware calls'); - }); - - test('middleware runs for all requests', () async { - // Make request to non-existent route - await http.get(Uri.parse('http://127.0.0.1:$port/not-found')); - - // Wait a bit for async handlers to complete - await Future.delayed(Duration(milliseconds: 100)); - - expect(provider.middlewareCalls, contains('/not-found'), - reason: 'Middleware should run even for non-existent routes'); - }); - }); -} diff --git a/packages/support/test/providers_test.dart b/packages/support/test/providers_test.dart deleted file mode 100644 index 8fbdebe..0000000 --- a/packages/support/test/providers_test.dart +++ /dev/null @@ -1,236 +0,0 @@ -import 'package:platform_core/core.dart'; -import 'package:platform_core/http.dart'; -import 'package:platform_container/container.dart'; -import 'package:platform_support/providers.dart'; -import 'package:test/test.dart'; - -// Test service class -class TestService { - final String message; - TestService(this.message); -} - -// Service registry for testing -class ServiceRegistry { - static final Map providers = {}; - static final Map booted = {}; - - static void register(String key, ServiceProvider provider) { - providers[key] = provider; - } - - static void markBooted(String key) { - booted[key] = true; - } - - static ServiceProvider? get(String key) { - return providers[key]; - } - - static bool isBooted(String key) { - return booted[key] == true; - } - - static void clear() { - providers.clear(); - booted.clear(); - } -} - -// Basic service provider for testing -class TestServiceProvider extends ServiceProvider { - bool registerCalled = false; - bool bootCalled = false; - final String message; - TestService? _service; - - TestServiceProvider([this.message = 'test']); - - @override - void register() { - super.register(); - registerCalled = true; - _service = TestService(message); - ServiceRegistry.register('test-service', this); - } - - @override - void boot() { - super.boot(); - bootCalled = true; - ServiceRegistry.markBooted('test-service'); - } - - @override - List provides() => ['test-service']; - - TestService? getService() => _service; -} - -// Deferred service provider for testing -class DeferredTestProvider extends DeferredServiceProvider { - bool registerCalled = false; - bool bootCalled = false; - TestService? _service; - - @override - void register() { - super.register(); - registerCalled = true; - _service = TestService('deferred'); - ServiceRegistry.register('deferred-service', this); - } - - @override - void boot() { - super.boot(); - bootCalled = true; - ServiceRegistry.markBooted('deferred-service'); - } - - @override - List provides() => ['deferred-service']; - - TestService? getService() => _service; -} - -// Provider with dependencies for testing -class DependentProvider extends ServiceProvider { - bool registerCalled = false; - bool bootCalled = false; - TestService? _service; - - @override - void register() { - super.register(); - registerCalled = true; - - // Get the base service - var baseProvider = - ServiceRegistry.get('test-service') as TestServiceProvider?; - if (baseProvider != null && ServiceRegistry.isBooted('test-service')) { - var baseService = baseProvider.getService(); - if (baseService != null) { - _service = TestService('dependent: ${baseService.message}'); - } - } - ServiceRegistry.register('dependent-service', this); - } - - @override - void boot() { - super.boot(); - bootCalled = true; - ServiceRegistry.markBooted('dependent-service'); - } - - @override - List provides() => ['dependent-service']; - - @override - List dependencies() => ['test-service']; - - TestService? getService() => _service; -} - -void main() { - group('ServiceProvider Tests', () { - late Application app; - - setUp(() { - app = Application(reflector: const EmptyReflector()); - ServiceRegistry.clear(); - }); - - tearDown(() async { - await app.close(); - ServiceRegistry.clear(); - }); - - test('registers and boots non-deferred provider immediately', () async { - var provider = TestServiceProvider(); - await app.registerProvider(provider); - - expect(provider.registerCalled, isTrue, - reason: 'register() should be called'); - expect(provider.bootCalled, isTrue, reason: 'boot() should be called'); - expect(provider.getService(), isNotNull, - reason: 'Service should be created'); - expect(provider.getService()?.message, equals('test')); - }); - - test('defers loading of deferred provider', () async { - var provider = DeferredTestProvider(); - await app.registerProvider(provider); - - expect(provider.registerCalled, isFalse, - reason: 'register() should not be called yet'); - expect(provider.bootCalled, isFalse, - reason: 'boot() should not be called yet'); - expect(provider.getService(), isNull, - reason: 'Service should not be created yet'); - }); - - test('loads deferred provider when resolved', () async { - var provider = DeferredTestProvider(); - await app.registerProvider(provider); - await app.resolveProvider('deferred-service'); - - expect(provider.registerCalled, isTrue, - reason: 'register() should be called after resolution'); - expect(provider.bootCalled, isTrue, - reason: 'boot() should be called after resolution'); - expect(provider.getService(), isNotNull, - reason: 'Service should be created after resolution'); - expect(provider.getService()?.message, equals('deferred')); - }); - - test('resolves dependencies before booting provider', () async { - var baseProvider = TestServiceProvider('base'); - var dependentProvider = DependentProvider(); - - // Register base provider first to ensure it's ready - await app.registerProvider(baseProvider); - await app.registerProvider(dependentProvider); - - expect(baseProvider.registerCalled, isTrue, - reason: 'Base provider register() should be called'); - expect(baseProvider.bootCalled, isTrue, - reason: 'Base provider boot() should be called'); - expect(dependentProvider.registerCalled, isTrue, - reason: 'Dependent provider register() should be called'); - expect(dependentProvider.bootCalled, isTrue, - reason: 'Dependent provider boot() should be called'); - - expect( - dependentProvider.getService()?.message, equals('dependent: base')); - }); - - test('singleton registration works correctly', () async { - var provider = TestServiceProvider(); - await app.registerProvider(provider); - - var service1 = provider.getService(); - var service2 = provider.getService(); - - expect(identical(service1, service2), isTrue, - reason: 'Should get same instance'); - }); - - test('provider storage persists across resolutions', () async { - var provider = DeferredTestProvider(); - await app.registerProvider(provider); - - // First resolution - await app.resolveProvider('deferred-service'); - var service1 = provider.getService(); - - // Second resolution - await app.resolveProvider('deferred-service'); - var service2 = provider.getService(); - - expect(identical(service1, service2), isTrue, - reason: 'Should get same instance across resolutions'); - }); - }); -} diff --git a/packages/testing/AUTHORS.md b/packages/testing/AUTHORS.md deleted file mode 100644 index ac95ab5..0000000 --- a/packages/testing/AUTHORS.md +++ /dev/null @@ -1,12 +0,0 @@ -Primary Authors -=============== - -* __[Thomas Hii](dukefirehawk.apps@gmail.com)__ - - Thomas is the current maintainer of the code base. He has refactored and migrated the - code base to support NNBD. - -* __[Tobe O](thosakwe@gmail.com)__ - - Tobe has written much of the original code prior to NNBD migration. He has moved on and - is no longer involved with the project. diff --git a/packages/testing/CHANGELOG.md b/packages/testing/CHANGELOG.md index bf99dfd..effe43c 100644 --- a/packages/testing/CHANGELOG.md +++ b/packages/testing/CHANGELOG.md @@ -1,78 +1,3 @@ -# Change Log +## 1.0.0 -## 8.1.1 - -* Updated repository link - -## 8.1.0 - -* Updated `lints` to 3.0.0 - -## 8.0.0 - -* Require Dart >= 3.0 -* Updated `http` to 1.0.0 - -## 7.0.1 - -* Fixed `BytesBuilder` warnings - -## 7.0.0 - -* Require Dart >= 2.17 - -## 6.0.0 - -* Require Dart >= 2.16 - -## 5.0.0 - -* Skipped release - -## 4.0.0 - -* Skipped release - -## 3.0.0 - -* Skipped release - -## 2.1.0 - -* Updated linter to `package:lints` - -## 2.0.2 - -* Updated README -* Updated test cases - -## 2.0.1 - -* Updated README - -## 2.0.0 - -* Migrated to work with Dart >= 2.12 NNBD - -## 1.0.7 - -* Prepare for upcoming Dart SDK change where `HttpHeaders` methods -`add` and `set` take an additional optional parameter `preserveHeaderCase` (thanks @domesticmouse!). - -## 1.0.6 - -* Prepare for upcoming Dart SDK change whereby `HttpRequest` implements - `Stream` rather than `Stream>`. - -## 1.0.5 - -* Add `toString` to `MockHttpHeaders`. - -## 1.0.4 - -* Fix for `ifModifiedSince` - -## 1.0.3 - -* Dart2 fixes -* Apparently fix hangs that break Protevus tests +- Initial version. diff --git a/packages/testing/LICENSE b/packages/testing/LICENSE deleted file mode 100644 index df5e063..0000000 --- a/packages/testing/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2021, dukefirehawk.com -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/testing/LICENSE.md b/packages/testing/LICENSE.md new file mode 100644 index 0000000..0fd0d03 --- /dev/null +++ b/packages/testing/LICENSE.md @@ -0,0 +1,10 @@ +The MIT License (MIT) + +The Laravel Framework is Copyright (c) Taylor Otwell +The Fabric Framework is Copyright (c) Vieo, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/testing/README.md b/packages/testing/README.md index 9e716cd..8b55e73 100644 --- a/packages/testing/README.md +++ b/packages/testing/README.md @@ -1,26 +1,39 @@ -# Mock HTTP Request + -Manufacture dart:io HttpRequests, HttpResponses, HttpHeaders, etc. This makes it possible to test server-side Dart applications without having to ever bind to a port. +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. -This package was originally designed to make testing [Protevus](https://protevus.com/) applications smoother, but works with any Dart-based server. +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. ## Usage +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + ```dart -var rq = MockHttpRequest('GET', Uri.parse('/foo')); -await rq.close(); -await app.handleRequest(rq); // Run within your server-side application -var rs = rq.response; -expect(rs.statusCode, equals(200)); -expect(await rs.transform(UTF8.decoder).join(), - equals(JSON.encode('Hello, world!'))); +const like = 'sample'; ``` -More examples can be found in the included test cases. +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/testing/doc/.gitkeep b/packages/testing/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/testing/example/main.dart b/packages/testing/example/main.dart deleted file mode 100644 index 8c25743..0000000 --- a/packages/testing/example/main.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'dart:async'; -import 'package:platform_testing/http.dart'; - -Future main() async { - var rq = - MockHttpRequest('GET', Uri.parse('/foo'), persistentConnection: false); - await rq.close(); -} diff --git a/packages/testing/lib/http.dart b/packages/testing/lib/http.dart deleted file mode 100644 index 018561b..0000000 --- a/packages/testing/lib/http.dart +++ /dev/null @@ -1,6 +0,0 @@ -export 'src/http/connection_info.dart'; -export 'src/http/headers.dart'; -export 'src/http/lockable_headers.dart'; -export 'src/http/request.dart'; -export 'src/http/response.dart'; -export 'src/http/session.dart'; diff --git a/packages/testing/lib/src/.gitkeep b/packages/testing/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/testing/lib/src/http/connection_info.dart b/packages/testing/lib/src/http/connection_info.dart deleted file mode 100644 index ed76589..0000000 --- a/packages/testing/lib/src/http/connection_info.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'dart:io'; - -class MockHttpConnectionInfo implements HttpConnectionInfo { - @override - final InternetAddress remoteAddress; - @override - final int localPort, remotePort; - - MockHttpConnectionInfo( - {required this.remoteAddress, - this.localPort = 8080, - this.remotePort = 80}); -} diff --git a/packages/testing/lib/src/http/headers.dart b/packages/testing/lib/src/http/headers.dart deleted file mode 100644 index 07f015c..0000000 --- a/packages/testing/lib/src/http/headers.dart +++ /dev/null @@ -1,174 +0,0 @@ -import 'dart:io'; - -class MockHttpHeaders implements HttpHeaders { - final Map> _data = {}; - final List _noFolding = []; - //Uri? _host; - String? _hostname; - int _port = 80; - - List get doNotFold => List.unmodifiable(_noFolding); - - @override - ContentType get contentType { - if (_data.containsKey(HttpHeaders.contentTypeHeader)) { - return ContentType.parse(_data[HttpHeaders.contentTypeHeader]!.join(',')); - } else { - return ContentType.html; - } - } - - @override - set contentType(ContentType? value) => - set(HttpHeaders.contentTypeHeader, value?.value ?? ContentType.html); - - @override - DateTime get date => _data.containsKey(HttpHeaders.dateHeader) - ? HttpDate.parse(_data[HttpHeaders.dateHeader]!.join(',')) - : DateTime.now(); - - @override - set date(DateTime? value) => - set(HttpHeaders.dateHeader, HttpDate.format(value ?? DateTime.now())); - - @override - DateTime get expires => _data.containsKey(HttpHeaders.expiresHeader) - ? HttpDate.parse(_data[HttpHeaders.expiresHeader]!.join(',')) - : DateTime.now(); - - @override - set expires(DateTime? value) => - set(HttpHeaders.expiresHeader, HttpDate.format(value ?? DateTime.now())); - - @override - DateTime get ifModifiedSince => - _data.containsKey(HttpHeaders.ifModifiedSinceHeader) - ? HttpDate.parse(_data[HttpHeaders.ifModifiedSinceHeader]!.join(',')) - : DateTime.now(); - - @override - set ifModifiedSince(DateTime? value) => set(HttpHeaders.ifModifiedSinceHeader, - HttpDate.format(value ?? DateTime.now())); - - @override - String? get host { - return _hostname; - /* - if (_host != null) { - return _host!.host; - } else if (_data.containsKey(HttpHeaders.hostHeader)) { - _host = Uri.parse(_data[HttpHeaders.hostHeader]!.join(',')); - return _host!.host; - } else { - return null; - } - */ - } - - @override - int get port { - return _port; - } - - @override - List? operator [](String name) => _data[name.toLowerCase()]; - - @override - void add(String name, Object value, {bool preserveHeaderCase = false}) { - var lower = preserveHeaderCase ? name : name.toLowerCase(); - - if (_data.containsKey(lower)) { - if (value is Iterable) { - _data[lower]!.addAll(value.map((x) => x.toString()).toList()); - } else { - _data[lower]!.add(value.toString()); - } - } else { - if (value is Iterable) { - _data[lower] = value.map((x) => x.toString()).toList(); - } else { - _data[lower] = [value.toString()]; - } - } - } - - @override - void clear() { - _data.clear(); - } - - @override - void forEach(void Function(String name, List values) action) { - _data.forEach(action); - } - - @override - void noFolding(String name) { - _noFolding.add(name.toLowerCase()); - } - - @override - void remove(String name, Object value) { - var lower = name.toLowerCase(); - - if (_data.containsKey(lower)) { - if (value is Iterable) { - for (var x in value) { - _data[lower]!.remove(x.toString()); - } - } else { - _data[lower]!.remove(value.toString()); - } - } - } - - @override - void removeAll(String name) { - _data.remove(name.toLowerCase()); - } - - @override - void set(String name, Object value, {bool preserveHeaderCase = false}) { - var lower = preserveHeaderCase ? name : name.toLowerCase(); - _data.remove(lower); - - if (value is Iterable) { - _data[lower] = value.map((x) => x.toString()).toList(); - } else { - _data[lower] = [value.toString()]; - } - } - - @override - String? value(String name) => _data[name.toLowerCase()]?.join(','); - - @override - String toString() { - var b = StringBuffer(); - _data.forEach((k, v) { - b.write('$k: '); - b.write(v.join(',')); - b.writeln(); - }); - return b.toString(); - } - - @override - bool chunkedTransferEncoding = false; - - @override - int contentLength = 0; - - @override - bool persistentConnection = true; - - @override - set host(String? host) { - _hostname = host; - } - - @override - set port(int? port) { - _port = port ?? 80; - } -} diff --git a/packages/testing/lib/src/http/lockable_headers.dart b/packages/testing/lib/src/http/lockable_headers.dart deleted file mode 100644 index 59b12e3..0000000 --- a/packages/testing/lib/src/http/lockable_headers.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'headers.dart'; - -/// Headers that can be locked to editing, i.e. after a request body has been written. -class LockableMockHttpHeaders extends MockHttpHeaders { - bool _locked = false; - - StateError _stateError() => - StateError('Cannot modify headers after they have been write-locked.'); - - void lock() { - _locked = true; - } - - @override - void add(String name, Object value, {bool preserveHeaderCase = false}) { - if (_locked) { - throw _stateError(); - } else { - super.add(name, value, preserveHeaderCase: preserveHeaderCase); - } - } - - @override - void clear() { - if (_locked) { - throw _stateError(); - } else { - super.clear(); - } - } - - @override - void noFolding(String name) { - if (_locked) { - throw _stateError(); - } else { - super.noFolding(name); - } - } - - @override - void remove(String name, Object value) { - if (_locked) { - throw _stateError(); - } else { - super.remove(name, value); - } - } - - @override - void removeAll(String name) { - if (_locked) { - throw _stateError(); - } else { - super.removeAll(name); - } - } - - @override - void set(String name, Object value, {bool preserveHeaderCase = false}) { - if (_locked) { - throw _stateError(); - } else { - super.set(name, value, preserveHeaderCase: preserveHeaderCase); - } - } -} diff --git a/packages/testing/lib/src/http/request.dart b/packages/testing/lib/src/http/request.dart deleted file mode 100644 index e79a679..0000000 --- a/packages/testing/lib/src/http/request.dart +++ /dev/null @@ -1,323 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'dart:typed_data'; -import 'package:charcode/ascii.dart'; -import 'connection_info.dart'; -import 'lockable_headers.dart'; -import 'response.dart'; -import 'session.dart'; - -class MockHttpRequest - implements HttpRequest, StreamSink>, StringSink { - int _contentLength = 0; - late BytesBuilder _buf; - final Completer _done = Completer(); - final LockableMockHttpHeaders _headers = LockableMockHttpHeaders(); - Uri? _requestedUri; - late MockHttpSession _session; - final StreamController _stream = StreamController(); - - @override - final List cookies = []; - - @override - HttpConnectionInfo connectionInfo = - MockHttpConnectionInfo(remoteAddress: InternetAddress.loopbackIPv4); - - @override - MockHttpResponse response = MockHttpResponse( - contentLength: 0, - encoding: utf8, - persistentConnection: false, - reasonPhrase: '', - statusCode: 200); - - @override - HttpSession get session => _session; - - @override - final String method; - - @override - final Uri uri; - - @override - bool persistentConnection = true; - - /// [copyBuffer] corresponds to `copy` on the [BytesBuilder] constructor. - MockHttpRequest(this.method, this.uri, - {bool copyBuffer = true, - String? protocolVersion, - String? sessionId, - this.certificate, - this.persistentConnection = true}) { - _buf = BytesBuilder(copy: copyBuffer != false); - _session = MockHttpSession(id: sessionId ?? 'mock-http-session'); - - this.protocolVersion = protocolVersion ?? '1.1'; - } - - @override - int get contentLength => _contentLength; - - @override - HttpHeaders get headers => _headers; - - @override - Uri get requestedUri { - if (_requestedUri != null) { - return _requestedUri!; - } else { - return _requestedUri = Uri( - scheme: 'http', - host: 'example.com', - path: uri.path, - query: uri.query, - ); - } - } - - set requestedUri(Uri value) { - _requestedUri = value; - } - - @override - late String protocolVersion; - - @override - X509Certificate? certificate; - - @override - void add(List data) { - if (_done.isCompleted) { - throw StateError('Cannot add to closed MockHttpRequest.'); - } else { - _headers.lock(); - _contentLength += data.length; - _buf.add(data); - } - } - - @override - void addError(error, [StackTrace? stackTrace]) { - if (_done.isCompleted) { - throw StateError('Cannot add to closed MockHttpRequest.'); - } else { - _stream.addError(error, stackTrace); - } - } - - @override - Future addStream(Stream> stream) { - var c = Completer(); - stream.listen(add, onError: addError, onDone: c.complete); - return c.future; - } - - @override - Future close() async { - await flush(); - _headers.lock(); - scheduleMicrotask(_stream.close); - _done.complete(); - return await _done.future; - } - - @override - Future get done => _done.future; - - // @override - Future flush() async { - _contentLength += _buf.length; - _stream.add(_buf.takeBytes()); - } - - @override - void write(Object? obj) { - obj?.toString().codeUnits.forEach(writeCharCode); - } - - @override - void writeAll(Iterable objects, [String separator = '']) { - write(objects.join(separator)); - } - - @override - void writeCharCode(int charCode) { - add([charCode]); - } - - @override - void writeln([Object? obj = '']) { - write(obj ?? ''); - add([$cr, $lf]); - } - - @override - Future any(bool Function(Uint8List element) test) { - return _stream.stream.any((List e) { - return test(Uint8List.fromList(e)); - }); - } - - @override - Stream asBroadcastStream({ - void Function(StreamSubscription subscription)? onListen, - void Function(StreamSubscription subscription)? onCancel, - }) { - return _stream.stream - .asBroadcastStream(onListen: onListen, onCancel: onCancel); - } - - @override - Stream asyncExpand(Stream? Function(Uint8List event) convert) => - _stream.stream.asyncExpand(convert); - - @override - Stream asyncMap(FutureOr Function(Uint8List event) convert) => - _stream.stream.asyncMap(convert); - - @override - Future contains(Object? needle) => _stream.stream.contains(needle); - - @override - Stream distinct( - [bool Function(Uint8List previous, Uint8List next)? equals]) => - _stream.stream.distinct(equals); - - @override - Future drain([E? futureValue]) => _stream.stream.drain(futureValue); - - @override - Future elementAt(int index) => _stream.stream.elementAt(index); - - @override - Future every(bool Function(Uint8List element) test) => - _stream.stream.every(test); - - @override - Stream expand(Iterable Function(Uint8List value) convert) => - _stream.stream.expand(convert); - - @override - Future get first => _stream.stream.first; - - @override - Future firstWhere(bool Function(Uint8List element) test, - {List Function()? orElse}) => - _stream.stream - .firstWhere(test, orElse: () => Uint8List.fromList(orElse!())); - - @override - Future fold( - S initialValue, S Function(S previous, Uint8List element) combine) => - _stream.stream.fold(initialValue, combine); - - @override - Future forEach(void Function(Uint8List element) action) => - _stream.stream.forEach(action); - - @override - Stream handleError(Function onError, - {bool Function(Object?)? test}) => - _stream.stream.handleError(onError, test: test); - - @override - bool get isBroadcast => _stream.stream.isBroadcast; - - @override - Future get isEmpty => _stream.stream.isEmpty; - - @override - Future join([String separator = '']) => - _stream.stream.join(separator); - - @override - Future get last => _stream.stream.last; - - @override - Future lastWhere(bool Function(Uint8List element) test, - {List Function()? orElse}) => - _stream.stream - .lastWhere(test, orElse: () => Uint8List.fromList(orElse!())); - - @override - Future get length => _stream.stream.length; - - @override - StreamSubscription listen( - void Function(Uint8List event)? onData, { - Function? onError, - void Function()? onDone, - bool? cancelOnError, - }) { - return _stream.stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError == true, - ); - } - - @override - Stream map(S Function(Uint8List event) convert) => - _stream.stream.map(convert); - - @override - Future pipe(StreamConsumer> streamConsumer) => - _stream.stream.cast>().pipe(streamConsumer); - - @override - Future reduce( - List Function(Uint8List previous, Uint8List element) combine) { - return _stream.stream.reduce((Uint8List previous, Uint8List element) { - return Uint8List.fromList(combine(previous, element)); - }); - } - - @override - Future get single => _stream.stream.single; - - @override - Future singleWhere(bool Function(Uint8List element) test, - {List Function()? orElse}) => - _stream.stream - .singleWhere(test, orElse: () => Uint8List.fromList(orElse!())); - - @override - Stream skip(int count) => _stream.stream.skip(count); - - @override - Stream skipWhile(bool Function(Uint8List element) test) => - _stream.stream.skipWhile(test); - - @override - Stream take(int count) => _stream.stream.take(count); - - @override - Stream takeWhile(bool Function(Uint8List element) test) => - _stream.stream.takeWhile(test); - - @override - Stream timeout(Duration timeLimit, - {void Function(EventSink sink)? onTimeout}) => - _stream.stream.timeout(timeLimit, onTimeout: onTimeout); - - @override - Future> toList() => _stream.stream.toList(); - - @override - Future> toSet() => _stream.stream.toSet(); - - @override - Stream transform(StreamTransformer, S> streamTransformer) => - _stream.stream.cast>().transform(streamTransformer); - - @override - Stream where(bool Function(Uint8List event) test) => - _stream.stream.where(test); - - @override - Stream cast() => Stream.castFrom, R>(this); -} diff --git a/packages/testing/lib/src/http/response.dart b/packages/testing/lib/src/http/response.dart deleted file mode 100644 index 00c9dc3..0000000 --- a/packages/testing/lib/src/http/response.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' hide BytesBuilder; -import 'dart:typed_data'; -import 'package:charcode/ascii.dart'; -import 'connection_info.dart'; -import 'lockable_headers.dart'; - -class MockHttpResponse extends Stream> implements HttpResponse { - BytesBuilder _buf = BytesBuilder(); - bool _bufferOutput = true; - final Completer _done = Completer(); - final LockableMockHttpHeaders _headers = LockableMockHttpHeaders(); - final StreamController> _stream = StreamController>(); - - @override - final List cookies = []; - - @override - HttpConnectionInfo connectionInfo = - MockHttpConnectionInfo(remoteAddress: InternetAddress.anyIPv4); - - /// [copyBuffer] corresponds to `copy` on the [BytesBuilder] constructor. - MockHttpResponse( - {bool copyBuffer = true, - required this.statusCode, - required this.reasonPhrase, - required this.contentLength, - this.deadline, - required this.encoding, - required this.persistentConnection, - bool? bufferOutput}) { - _buf = BytesBuilder(copy: copyBuffer != false); - _bufferOutput = bufferOutput != false; - statusCode = 200; - } - - @override - bool get bufferOutput => _bufferOutput; - - @override - set bufferOutput(bool value) {} - - @override - int contentLength; - - @override - Duration? deadline; - - @override - bool persistentConnection; - - @override - String reasonPhrase; - - @override - int statusCode; - - @override - Encoding encoding; - - @override - HttpHeaders get headers => _headers; - - @override - Future get done => _done.future; - - @override - void add(List data) { - if (_done.isCompleted) { - throw StateError('Cannot add to closed MockHttpResponse.'); - } else { - _headers.lock(); - if (_bufferOutput == true) { - _buf.add(data); - } else { - _stream.add(data); - } - } - } - - @override - void addError(error, [StackTrace? stackTrace]) { - if (_done.isCompleted) { - throw StateError('Cannot add to closed MockHttpResponse.'); - } else { - _stream.addError(error, stackTrace); - } - } - - @override - Future addStream(Stream> stream) { - var c = Completer(); - stream.listen(add, onError: addError, onDone: c.complete); - return c.future; - } - - @override - Future close() async { - _headers.lock(); - await flush(); - scheduleMicrotask(_stream.close); - _done.complete(); - //return await _done.future; - } - - @override - Future detachSocket({bool writeHeaders = true}) { - throw UnsupportedError('MockHttpResponses have no socket to detach.'); - } - - @override - Future flush() async { - _stream.add(_buf.takeBytes()); - } - - @override - Future redirect(Uri location, - {int status = HttpStatus.movedTemporarily}) async { - statusCode = status; - } - - @override - void write(Object? obj) { - obj?.toString().codeUnits.forEach(writeCharCode); - } - - @override - void writeAll(Iterable objects, [String separator = '']) { - write(objects.join(separator)); - } - - @override - void writeCharCode(int charCode) { - add([charCode]); - } - - @override - void writeln([Object? obj = '']) { - write(obj ?? ''); - add([$cr, $lf]); - } - - @override - StreamSubscription> listen(void Function(List event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) => - _stream.stream.listen(onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError == true); -} diff --git a/packages/testing/lib/src/http/session.dart b/packages/testing/lib/src/http/session.dart deleted file mode 100644 index 3766381..0000000 --- a/packages/testing/lib/src/http/session.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'dart:collection'; -import 'dart:io'; - -class MockHttpSession extends MapBase implements HttpSession { - final Map _data = {}; - - @override - String id; - - MockHttpSession({required this.id}); - - @override - int get length => _data.length; - - @override - dynamic operator [](Object? key) => _data[key]; - - @override - void operator []=(key, value) { - _data[key] = value; - } - - @override - void addAll(Map other) => _data.addAll(other); - - @override - void clear() { - _data.clear(); - } - - @override - bool containsKey(Object? key) => _data.containsKey(key); - - @override - bool containsValue(Object? value) => _data.containsValue(value); - - @override - void destroy() { - print('destroy() was called on a MockHttpSession, which does nothing.'); - } - - @override - void forEach(void Function(dynamic, dynamic) action) { - _data.forEach(action); - } - - @override - bool get isEmpty => _data.isEmpty; - - @override - bool get isNew => true; - - @override - bool get isNotEmpty => _data.isNotEmpty; - - @override - Iterable get keys => _data.keys; - - @override - dynamic putIfAbsent(key, dynamic Function() ifAbsent) => - _data.putIfAbsent(key, ifAbsent); - - @override - dynamic remove(Object? key) => _data.remove(key); - - @override - Iterable get values => _data.values; - - @override - set onTimeout(void Function() callback) { - print( - 'An onTimeout callback was set on a MockHttpSession, which will do nothing.'); - } -} diff --git a/packages/testing/pubspec.yaml b/packages/testing/pubspec.yaml index ed6814c..aaa1f40 100644 --- a/packages/testing/pubspec.yaml +++ b/packages/testing/pubspec.yaml @@ -1,15 +1,17 @@ name: platform_testing -description: Testing helper files for Protevus Platform. -version: 9.0.0 -# repository: https://github.com/my_org/my_repo +description: The Testing Package for the Protevus Platform +version: 0.0.1 +homepage: https://protevus.com +documentation: https://docs.protevus.com +repository: https://github.com/protevus/platformo environment: - sdk: ^3.5.4 + sdk: ^3.4.2 # Add regular dependencies here. dependencies: - charcode: ^1.3.1 + # path: ^1.8.0 dev_dependencies: - lints: ^4.0.0 + lints: ^3.0.0 test: ^1.24.0 diff --git a/packages/testing/test/.gitkeep b/packages/testing/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/testing/test/all_test.dart b/packages/testing/test/all_test.dart deleted file mode 100644 index a0244a1..0000000 --- a/packages/testing/test/all_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -//import 'dart:convert'; -//import 'dart:io'; -//import 'package:angel3_framework/angel3_framework.dart'; -//import 'package:angel3_framework/http.dart'; -//import 'package:angel3_mock_request/angel3_mock_request.dart'; -//import 'package:test/test.dart'; - -void main() { - //var uri = Uri.parse('http://localhost:3000'); - - /* - var app = Protevus() - ..get('/foo', (req, res) => 'Hello, world!') - ..post('/body', - (req, res) => req.parseBody().then((_) => req.bodyAsMap.length)) - ..get('/session', (req, res) async { - req.session?['foo'] = 'bar'; - }) - ..get('/conn', (RequestContext req, res) { - return res.serialize(req.ip == InternetAddress.loopbackIPv4.address); - }); - - var http = PlatformHttp(app); - - test('receive a response', () async { - var rq = MockHttpRequest('GET', uri.resolve('/foo')); - await rq.close(); - await http.handleRequest(rq); - var rs = rq.response; - expect(rs.statusCode, equals(200)); - expect(await rs.transform(utf8.decoder).join(), - equals(json.encode('Hello, world!'))); - }); - - test('send a body', () async { - var rq = MockHttpRequest('POST', uri.resolve('/body')); - rq - ..headers.set(HttpHeaders.contentTypeHeader, ContentType.json.mimeType) - ..write(json.encode({'foo': 'bar', 'bar': 'baz', 'baz': 'quux'})); - await rq.close(); - await http.handleRequest(rq); - var rs = rq.response; - expect(rs.statusCode, equals(200)); - expect(await rs.transform(utf8.decoder).join(), equals(json.encode(3))); - }); - - test('session', () async { - var rq = MockHttpRequest('GET', uri.resolve('/session')); - await rq.close(); - await http.handleRequest(rq); - expect(rq.session.keys, contains('foo')); - expect(rq.session['foo'], equals('bar')); - }); - - test('connection info', () async { - var rq = MockHttpRequest('GET', uri.resolve('/conn')); - await rq.close(); - await http.handleRequest(rq); - var rs = rq.response; - expect(await rs.transform(utf8.decoder).join(), equals(json.encode(true))); - }); - - test('requested uri', () { - var rq = MockHttpRequest('GET', uri.resolve('/mock')); - expect(rq.uri.path, '/mock'); - expect(rq.requestedUri.toString(), 'http://example.com/mock'); - }); - */ -} diff --git a/packages/validation/.gitignore b/packages/validation/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/packages/validation/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/validation/CHANGELOG.md b/packages/validation/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/packages/validation/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/validation/LICENSE.md b/packages/validation/LICENSE.md new file mode 100644 index 0000000..0fd0d03 --- /dev/null +++ b/packages/validation/LICENSE.md @@ -0,0 +1,10 @@ +The MIT License (MIT) + +The Laravel Framework is Copyright (c) Taylor Otwell +The Fabric Framework is Copyright (c) Vieo, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/validation/README.md b/packages/validation/README.md new file mode 100644 index 0000000..8b55e73 --- /dev/null +++ b/packages/validation/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/validation/analysis_options.yaml b/packages/validation/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/packages/validation/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/packages/validation/doc/.gitkeep b/packages/validation/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/validation/example/.gitkeep b/packages/validation/example/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/validation/lib/src/.gitkeep b/packages/validation/lib/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/validation/pubspec.yaml b/packages/validation/pubspec.yaml new file mode 100644 index 0000000..00c6c2e --- /dev/null +++ b/packages/validation/pubspec.yaml @@ -0,0 +1,17 @@ +name: platform_validation +description: The Validation Package for the Protevus Platform +version: 0.0.1 +homepage: https://protevus.com +documentation: https://docs.protevus.com +repository: https://github.com/protevus/platformo + +environment: + sdk: ^3.4.2 + +# Add regular dependencies here. +dependencies: + # path: ^1.8.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/packages/validation/test/.gitkeep b/packages/validation/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sandbox/eventbus/.github/workflows/dart.yml b/sandbox/eventbus/.github/workflows/dart.yml deleted file mode 100644 index 21b7ff6..0000000 --- a/sandbox/eventbus/.github/workflows/dart.yml +++ /dev/null @@ -1,54 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Dart - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - # Note: This workflow uses the latest stable version of the Dart SDK. - # You can specify other versions if desired, see documentation here: - # https://github.com/dart-lang/setup-dart/blob/main/README.md - # - uses: dart-lang/setup-dart@v1 - - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603 - - - name: Flutter action - # You may pin to the exact commit or the version. - # uses: subosito/flutter-action@4389e6cbc6cb8a4b18c628ff96ff90be0e926aa8 - uses: subosito/flutter-action@v1.5.3 - - - name: Install dependencies - run: dart pub get - - # Uncomment this step to verify the use of 'dart format' on each commit. - # - name: Verify formatting - # run: dart format --output=none --set-exit-if-changed . - - # Consider passing '--fatal-infos' for slightly stricter analysis. - - name: Analyze project source - run: dart analyze - - # Your project will need to have tests in test/ and a dependency on - # package:test for this step to succeed. Note that Flutter projects will - # want to change this to 'flutter test'. - - name: Run tests - run: flutter test --coverage - - - name: Install lcov - run: sudo apt-get install -y lcov - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 - with: - file: coverage/lcov.info diff --git a/sandbox/eventbus/.gitignore b/sandbox/eventbus/.gitignore deleted file mode 100644 index 3d34218..0000000 --- a/sandbox/eventbus/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. -/pubspec.lock -**/doc/api/ -.dart_tool/ -.packages -build/ -coverage diff --git a/sandbox/eventbus/.metadata b/sandbox/eventbus/.metadata deleted file mode 100644 index 0bb64e4..0000000 --- a/sandbox/eventbus/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 7e9793dee1b85a243edd0e06cb1658e98b077561 - channel: stable - -project_type: package diff --git a/sandbox/eventbus/.vscode/settings.json b/sandbox/eventbus/.vscode/settings.json deleted file mode 100644 index 52472e3..0000000 --- a/sandbox/eventbus/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "cSpell.words": [ - "codecov", - "devcraft", - "shouldly" - ] -} \ No newline at end of file diff --git a/sandbox/eventbus/CHANGELOG.md b/sandbox/eventbus/CHANGELOG.md deleted file mode 100644 index 5302c51..0000000 --- a/sandbox/eventbus/CHANGELOG.md +++ /dev/null @@ -1,67 +0,0 @@ -## 0.6.2 - -* update `logger` dependency - -## 0.6.1 - -* [Add] `allowLogging` to control logging (thanks [@hatemragab](https://github.com/hatemragab)) - -## 0.6.0 - -* **[Change]** fire `EmptyEvent` after each event to keep stream empty. -* [Add] [Logger](https://pub.dev/packages/logger) - -## 0.5.0 - -* [Add] timestamp for each `event`. - -## 0.4.0 - -* [Add] mapper - -## 0.3.0+3 - -* Downgrade clock version - -## 0.3.0+2 - -* Improve documentation - -## 0.3.0+1 - -* Improve documentation - -## 0.3.0 - -* [Add] `EventCompletionEvent` -* Improve documentation - -## 0.2.2 - -* [Fix] `clock` dependency - -## 0.2.1 - -* [Add] debug logging - -## 0.2.0 - -* [Add] history - -## 0.1.0 - -* [Add] distinct - -## 0.0.2+1 - -* [Fix] GitHub repository link - -## 0.0.2 - -* [Remove] `id` of `AppEvent` - -## 0.0.1 - -* Initial release. -* [Add] `EventBus` -* [Add] `AppEvent` superclass diff --git a/sandbox/eventbus/LICENSE b/sandbox/eventbus/LICENSE deleted file mode 100644 index c560cf3..0000000 --- a/sandbox/eventbus/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Andrew - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/sandbox/eventbus/README.md b/sandbox/eventbus/README.md deleted file mode 100644 index 4c8f4bd..0000000 --- a/sandbox/eventbus/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# EventBus: Events for Dart/Flutter - -[![pub package](https://img.shields.io/pub/v/event_bus_plus.svg?label=event_bus_plus&color=blue)](https://pub.dev/packages/event_bus_plus) -[![codecov](https://codecov.io/gh/AndrewPiterov/event_bus_plus/branch/main/graph/badge.svg?token=VM9LTJXGQS)](https://codecov.io/gh/AndrewPiterov/event_bus_plus) -[![Dart](https://github.com/AndrewPiterov/event_bus_plus/actions/workflows/dart.yml/badge.svg)](https://github.com/AndrewPiterov/event_bus_plus/actions/workflows/dart.yml) - -**EventBus** is an open-source library for Dart and Flutter using the **publisher/subscriber** pattern for loose coupling. **EventBus** enables central communication to decoupled classes with just a few lines of code – simplifying the code, removing dependencies, and speeding up app development. - -event bus publish subscribe - -## Your benefits using EventBus: It… -- simplifies the communication between components; -- decouples event senders and receivers; -- performs well with UI artifacts (e.g. Widgets, Controllers); -- avoids complex and error-prone dependencies and life cycle issues. - - -event bus plus - -### Define the app's events - -```dart -// Initialize the Service Bus -IAppEventBus eventBus = AppEventBus(); - -// Define your app events -final event = FollowEvent('@devcraft.ninja'); -final event = CommentEvent('Awesome package 😎'); -``` - -### Subscribe - -```dart -// listen the latest event -final sub = eventBus.last$ - .listen((AppEvent event) { /*do something*/ }); - -// Listen particular event -final sub2 = eventBus.on() - .listen((e) { /*do something*/ }); -``` - -### Publish - -```dart -// fire the event -eventBus.fire(event); -``` - -### Watch events in progress - -```dart -// start watch the event till its completion -eventBus.watch(event); - -// and check the progress -eventBus.isInProgress(); - -// or listen stream to check the processing -eventBus.inProgress$.map((List events) => - events.whereType().isNotEmpty); - -// complete -_eventBus.complete(event); - -// or complete with completion event -_eventBus.complete(event, nextEvent: SomeAnotherEvent); -``` - -## History - -```dart -final events = eventBus.history; -``` - -## Mapping - -```dart -final eventBus = bus = EventBus( - map: { - SomeEvent: [ - (e) => SomeAnotherEvent(), - ], - }, - ); -``` - -## Contributing - -We accept the following contributions: - -* Improving the documentation -* [Reporting issues](https://github.com/AndrewPiterov/event_bus_plus/issues/new) -* Fixing bugs - -## Maintainers - -* [Andrew Piterov](mailto:contact@andrewpiterov.com?subject=[GitHub]%20Source%20Dart%event_bus_plus) diff --git a/sandbox/eventbus/analysis_options.yaml b/sandbox/eventbus/analysis_options.yaml deleted file mode 100644 index 67016e9..0000000 --- a/sandbox/eventbus/analysis_options.yaml +++ /dev/null @@ -1,224 +0,0 @@ -include: package:flutter_lints/flutter.yaml -analyzer: - strong-mode: - implicit-casts: false - implicit-dynamic: true - errors: - # Close instances of dart.core.Sink. - close_sinks: error - # Cancel instances of dart.async.StreamSubscription. - cancel_subscriptions: error - # treat missing required parameters as a error (not a hint) - missing_required_param: error - # treat missing returns as a error (not a hint) - missing_return: error - # allow having TODOs in the code and treat as warning - todo: warning - # allow self-reference to deprecated members (we do this because otherwise we have - # to annotate every member in every test, assert, etc, when we deprecate something) - deprecated_member_use_from_same_package: ignore - # Ignore analyzer hints for updating pubspecs when using Future or - # Stream and not importing dart:async - # Please see https://github.com/flutter/flutter/pull/24528 for details. - sdk_version_async_exported_from_core: ignore - # await - unawaited_futures: error - avoid_print: warning - always_declare_return_types: error - unrelated_type_equality_checks: error - implementation_imports: error - require_trailing_commas: warning - avoid_slow_async_io: error - use_build_context_synchronously: error - sized_box_for_whitespace: error - dead_code: warning - invalid_assignment: error - - exclude: - - "bin/cache/**" - # the following two are relative to the stocks example and the flutter package respectively - # see https://github.com/dart-lang/sdk/issues/28463 - - "lib/i18n/messages_*.dart" - - "lib/src/http/**" - - "build/**" - - "lib/**.freezed.dart" - - "lib/**.g.dart" - - "test/**" - - "example/**" - -linter: - rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # https://github.com/dart-lang/linter/blob/master/example/all.yaml - - always_declare_return_types - - always_put_control_body_on_new_line - # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 - - always_require_non_null_named_parameters - # - always_specify_types - - annotate_overrides - # - avoid_annotating_with_dynamic # conflicts with always_specify_types - # - avoid_as # required for implicit-casts: true - - avoid_bool_literals_in_conditional_expressions - # - avoid_catches_without_on_clauses # we do this commonly - # - avoid_catching_errors # we do this commonly - - avoid_classes_with_only_static_members - # - avoid_double_and_int_checks # only useful when targeting JS runtime - - avoid_empty_else - - avoid_equals_and_hash_code_on_mutable_classes - - avoid_field_initializers_in_const_classes - - avoid_function_literals_in_foreach_calls - # - avoid_implementing_value_types # not yet tested - - avoid_init_to_null - # - avoid_js_rounded_ints # only useful when targeting JS runtime - - avoid_null_checks_in_equality_operators - # - avoid_positional_boolean_parameters # not yet tested - - avoid_print # not yet tested - # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) - # - avoid_redundant_argument_values # not yet tested - - avoid_relative_lib_imports - - avoid_renaming_method_parameters - - avoid_return_types_on_setters - # - avoid_returning_null # there are plenty of valid reasons to return null - # - avoid_returning_null_for_future # not yet tested - - avoid_returning_null_for_void - # - avoid_returning_this # there are plenty of valid reasons to return this - # - avoid_setters_without_getters # not yet tested - - avoid_shadowing_type_parameters # not yet tested - - avoid_single_cascade_in_expression_statements - - avoid_slow_async_io - - avoid_types_as_parameter_names - # - avoid_types_on_closure_parameters # conflicts with always_specify_types - # - avoid_unnecessary_containers # not yet tested - - avoid_unused_constructor_parameters - - avoid_void_async - # - avoid_web_libraries_in_flutter # not yet tested - - await_only_futures - - camel_case_extensions - - camel_case_types - - cancel_subscriptions - # - cascade_invocations # not yet tested - # - close_sinks # not reliable enough - # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 - # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 - - control_flow_in_finally - # - curly_braces_in_flow_control_structures # not yet tested - # - diagnostic_describe_all_properties # not yet tested - - directives_ordering - - empty_catches - - empty_constructor_bodies - - empty_statements - # - file_names # not yet tested - - flutter_style_todos - - hash_and_equals - - implementation_imports - # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 - - iterable_contains_unrelated_type - # - join_return_with_assignment # not yet tested - - library_names - - library_prefixes - # - lines_longer_than_80_chars # not yet tested - - list_remove_unrelated_type - # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 - # - missing_whitespace_between_adjacent_strings # not yet tested - - no_adjacent_strings_in_list - - no_duplicate_case_values - # - no_logic_in_create_state # not yet tested - # - no_runtimeType_toString # not yet tested - - non_constant_identifier_names - # - null_closures # not yet tested - - omit_local_variable_types # opposite of always_specify_types - # - one_member_abstracts # too many false positives - # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 - - overridden_fields - - package_api_docs - - package_names - - package_prefixed_library_names - # - parameter_assignments # we do this commonly - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - # - prefer_asserts_with_message # not yet tested - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - # - prefer_constructors_over_static_methods # not yet tested - - prefer_contains - # - prefer_double_quotes # opposite of prefer_single_quotes - - prefer_equal_for_default_values - # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - - prefer_final_fields - - prefer_final_in_for_each - - prefer_final_locals - - prefer_for_elements_to_map_fromIterable - - prefer_foreach - # - prefer_function_declarations_over_variables # not yet tested - - prefer_generic_function_type_aliases - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - # - prefer_int_literals # not yet tested - # - prefer_interpolation_to_compose_strings # not yet tested - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_iterable_whereType - # - prefer_mixin # https://github.com/dart-lang/language/issues/32 - # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 - # - prefer_relative_imports # not yet tested - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - prefer_void_to_null - # - provide_deprecation_message # not yet tested - - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml - - recursive_getters - - slash_for_doc_comments - # - sort_child_properties_last # not yet tested - - sort_constructors_first - - sort_pub_dependencies - - sort_unnamed_constructors_first - - test_types_in_equals - - throw_in_finally - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - - unawaited_futures # too many false positives - - unnecessary_await_in_return # not yet tested - - unnecessary_brace_in_string_interps - - unnecessary_const - # - unnecessary_final # conflicts with prefer_final_locals - - unnecessary_getters_setters - # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_overrides - - unnecessary_parenthesis - - unnecessary_statements - - unnecessary_string_interpolations - - unnecessary_this - - unrelated_type_equality_checks - # - unsafe_html # not yet tested - - use_full_hex_values_for_flutter_colors - # - use_function_type_syntax_for_parameters # not yet tested - # - use_key_in_widget_constructors # not yet tested - - use_late_for_private_fields_and_variables - - use_rethrow_when_possible - # - use_setters_to_change_properties # not yet tested - # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 - # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - - valid_regexps - - void_checks - -dart_code_metrics: - rules: - - newline-before-return: - severity: style - - no-boolean-literal-compare: - severity: error - anti-patterns: - - long-method: - severity: warning diff --git a/sandbox/eventbus/doc/pub_sub.webp b/sandbox/eventbus/doc/pub_sub.webp deleted file mode 100644 index 8e13702..0000000 Binary files a/sandbox/eventbus/doc/pub_sub.webp and /dev/null differ diff --git a/sandbox/eventbus/doc/video_presentation.gif b/sandbox/eventbus/doc/video_presentation.gif deleted file mode 100644 index 0ef1981..0000000 Binary files a/sandbox/eventbus/doc/video_presentation.gif and /dev/null differ diff --git a/sandbox/eventbus/lib/event_bus.dart b/sandbox/eventbus/lib/event_bus.dart deleted file mode 100644 index e19ecc6..0000000 --- a/sandbox/eventbus/lib/event_bus.dart +++ /dev/null @@ -1,3 +0,0 @@ -library event_bus; - -export 'res/res.dart'; diff --git a/sandbox/eventbus/lib/res/app_event.dart b/sandbox/eventbus/lib/res/app_event.dart deleted file mode 100644 index 8182351..0000000 --- a/sandbox/eventbus/lib/res/app_event.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:clock/clock.dart'; -import 'package:equatable/equatable.dart'; - -/// The base class for all events -abstract class AppEvent extends Equatable { - /// Create the event - const AppEvent(); - - /// The event time - DateTime get timestamp => clock.now(); -} - -/// The event completion event -class EventCompletionEvent extends AppEvent { - /// Create the event - const EventCompletionEvent(this.event); - - /// The event that is completed - final AppEvent event; - - @override - List get props => [event]; -} - -/// The empty event -class EmptyEvent extends AppEvent { - @override - List get props => []; -} diff --git a/sandbox/eventbus/lib/res/event_bus.dart b/sandbox/eventbus/lib/res/event_bus.dart deleted file mode 100644 index d23bb2c..0000000 --- a/sandbox/eventbus/lib/res/event_bus.dart +++ /dev/null @@ -1,212 +0,0 @@ -import 'package:angel3_reactivex/subjects.dart'; -import 'package:logger/logger.dart'; - -import 'app_event.dart'; -import 'history_entry.dart'; -import 'subscription.dart'; - -/// The event bus interface -abstract class IEventBus { - /// Whether the event bus is busy - bool get isBusy; - - /// Whether the event bus is busy - Stream get isBusy$; - - /// The last event - AppEvent? get last; - - /// The last event - Stream get last$; - - /// The list of events that are in progress - Stream> get inProgress$; - - /// Subscribe `EventBus` on a specific type of event, and register responder to it. - Stream on(); - - /// Subscribe `EventBus` on a specific type of event, and register responder to it. - Stream whileInProgress(); - - /// Subscribe `EventBus` on a specific type of event, and register responder to it. - Subscription respond(Responder responder); - - /// The history of events - List get history; - - /// Fire a event - void fire(AppEvent event); - - /// Fire a event and wait for it to be completed - void watch(AppEvent event); - - /// Complete a event - void complete(AppEvent event, {AppEvent? nextEvent}); - - /// - bool isInProgress(); - - /// Reset the event bus - void reset(); - - /// Dispose the event bus - void dispose(); - - /// Clear the history - void clearHistory(); -} - -/// The event bus implementation -class EventBus implements IEventBus { - /// Create the event bus - EventBus({ - this.maxHistoryLength = 100, - this.map = const {}, - this.allowLogging = false, - }); - - final _logger = Logger(); - - /// The maximum length of history - final int maxHistoryLength; - - /// allow to log all events this when you call [fire] - /// the event will be in console log - final bool allowLogging; - - /// The map of events - final Map> map; - - @override - bool get isBusy => _inProgress.value.isNotEmpty; - @override - Stream get isBusy$ => _inProgress.map((event) => event.isNotEmpty); - - final _lastEventSubject = BehaviorSubject(); - @override - AppEvent? get last => _lastEventSubject.valueOrNull; - @override - Stream get last$ => _lastEventSubject.distinct(); - - final _inProgress = BehaviorSubject>.seeded([]); - List get _isInProgressEvents => _inProgress.value; - @override - Stream> get inProgress$ => _inProgress; - - @override - List get history => List.unmodifiable(_history); - final List _history = []; - - @override - void fire(AppEvent event) { - if (_history.length >= maxHistoryLength) { - _history.removeAt(0); - } - _history.add(EventBusHistoryEntry(event, event.timestamp)); - // 1. Fire the event - _lastEventSubject.add(event); - // 2. Map if needed - _map(event); - // 3. Reset stream - _lastEventSubject.add(EmptyEvent()); - if (allowLogging) { - _logger.d(' ⚡️ [${event.timestamp}] $event'); - } - } - - @override - void watch(AppEvent event) { - fire(event); - _inProgress.add([ - ..._isInProgressEvents, - event, - ]); - } - - @override - void complete(AppEvent event, {AppEvent? nextEvent}) { - // complete the event - if (_isInProgressEvents.any((e) => e == event)) { - final newArr = _isInProgressEvents.toList() - ..removeWhere((e) => e == event); - _inProgress.add(newArr); - fire(EventCompletionEvent(event)); - } - - // fire next event if any - if (nextEvent != null) { - fire(nextEvent); - } - } - - @override - bool isInProgress() { - return _isInProgressEvents.whereType().isNotEmpty; - } - - @override - Stream on() { - if (T == dynamic) { - return _lastEventSubject.stream as Stream; - } else { - return _lastEventSubject.stream.where((event) => event is T).cast(); - } - } - - /// Subscribe `EventBus` on a specific type of event, and register responder to it. - /// - /// When [T] is not given or given as `dynamic`, it listens to all events regardless of the type. - /// Returns [Subscription], which can be disposed to cancel all the subscription registered to itself. - @override - Subscription respond(Responder responder) => - Subscription(_lastEventSubject).respond(responder); - - @override - Stream whileInProgress() { - return _inProgress.map((events) { - return events.whereType().isNotEmpty; - }); - } - - void _map(AppEvent? event) { - if (event == null) { - return; - } - - final functions = map[event.runtimeType] ?? []; - if (functions.isEmpty) { - return; - } - - for (final func in functions) { - final newEvent = func(event); - if (newEvent.runtimeType == event.runtimeType) { - if (allowLogging) { - _logger.d( - ' 🟠 SKIP EVENT: ${newEvent.runtimeType} => ${event.runtimeType}', - ); - } - continue; - } - fire(newEvent); - } - } - - @override - void clearHistory() { - _history.clear(); - } - - @override - void reset() { - clearHistory(); - _inProgress.add([]); - _lastEventSubject.add(EmptyEvent()); - } - - @override - void dispose() { - _inProgress.close(); - _lastEventSubject.close(); - } -} diff --git a/sandbox/eventbus/lib/res/history_entry.dart b/sandbox/eventbus/lib/res/history_entry.dart deleted file mode 100644 index 65c7a11..0000000 --- a/sandbox/eventbus/lib/res/history_entry.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:equatable/equatable.dart'; - -import 'app_event.dart'; - -/// The history entry -class EventBusHistoryEntry extends Equatable { - /// The history entry - const EventBusHistoryEntry(this.event, this.timestamp); - - /// The event - final AppEvent event; - - /// The timestamp - final DateTime timestamp; - - @override - List get props => [event, timestamp]; -} diff --git a/sandbox/eventbus/lib/res/res.dart b/sandbox/eventbus/lib/res/res.dart deleted file mode 100644 index c2e36e1..0000000 --- a/sandbox/eventbus/lib/res/res.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'app_event.dart'; -export 'event_bus.dart'; -export 'history_entry.dart'; -export 'subscription.dart'; diff --git a/sandbox/eventbus/lib/res/subscription.dart b/sandbox/eventbus/lib/res/subscription.dart deleted file mode 100644 index 74bbb41..0000000 --- a/sandbox/eventbus/lib/res/subscription.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:async'; - -/// The function/method signature for the event handler -typedef Responder = void Function(T event); - -/// The class manages the subscription to event bus -class Subscription { - /// Create the subscription - /// - /// Should barely used directly, to subscribe to event bus, use `EventBus.respond`. - Subscription(this._stream); - - /// Returns an instance that indicates there is no subscription - factory Subscription.empty() => const _EmptySubscription(); - - final Stream _stream; - - /// Subscriptions that registered to event bus - final List subscriptions = []; - - Stream _cast() { - if (T == dynamic) { - return _stream as Stream; - } else { - return _stream.where((event) => event is T).cast(); - } - } - - /// Register a [responder] to event bus for the event type [T]. - /// If [T] is omitted or given as `dynamic`, it listens to all events that published on [EventBus]. - /// - /// Method call can be safely chained, and the order doesn't matter. - /// - /// ``` - /// eventBus - /// .respond(responderA) - /// .respond(responderB); - /// ``` - Subscription respond(Responder responder) { - subscriptions.add(_cast().listen(responder)); - return this; - } - - /// Cancel all the registered subscriptions. - /// After calling this method, all the events published won't be delivered to the cleared responders any more. - /// - /// No harm to call more than once. - void dispose() { - if (subscriptions.isEmpty) { - return; - } - for (final s in subscriptions) { - s.cancel(); - } - subscriptions.clear(); - } -} - -class _EmptySubscription implements Subscription { - const _EmptySubscription(); - static final List emptyList = - List.unmodifiable([]); - - @override - void dispose() {} - - @override - Subscription respond(responder) => throw Exception('Not supported'); - - @override - List get subscriptions => emptyList; - - @override - Stream _cast() => throw Exception('Not supported'); - - @override - Stream get _stream => throw Exception('Not supported'); -} diff --git a/sandbox/eventbus/pubspec.yaml b/sandbox/eventbus/pubspec.yaml deleted file mode 100644 index bb71c80..0000000 --- a/sandbox/eventbus/pubspec.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: angel3_event_bus -description: Event Bus for Dart. -version: 0.6.2 -homepage: https://github.com/AndrewPiterov/event_bus_plus - -environment: - sdk: '>=2.17.1 <4.0.0' - -dependencies: - clock: ^1.1.0 - equatable: ^2.0.5 - logger: ^2.0.2+1 - angel3_reactivex: ^0.27.5 - -dev_dependencies: - flutter_lints: ^3.0.0 - given_when_then_unit_test: ^0.2.1 - shouldly: ^0.5.0+1 - test: ^1.22.0 - diff --git a/sandbox/eventbus/test/completion_test.dart b/sandbox/eventbus/test/completion_test.dart deleted file mode 100644 index 6105911..0000000 --- a/sandbox/eventbus/test/completion_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:event_bus_plus/event_bus_plus.dart'; -import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; -import 'models.dart'; -import 'package:test/test.dart'; - -void main() { - late IEventBus bus; - - before(() { - bus = EventBus(); - }); - - test('emit Follower Events', () { - expect( - bus.on(), - emitsInOrder([ - const FollowAppEvent('username'), - EmptyEvent(), - const FollowAppEvent('username3'), - EmptyEvent(), - const FollowAppEvent('username2'), - EmptyEvent(), - ])); - - bus.fire(const FollowAppEvent('username')); - bus.fire(const FollowAppEvent('username3')); - bus.fire(const FollowAppEvent('username2')); - }); - - test('start watch but not complete', () { - expect( - bus.on(), - emitsInOrder([ - const FollowAppEvent('username'), - ])); - - bus.watch(const FollowAppEvent('username')); - }); - - test('start watch and complete', () { - const watchable = FollowAppEvent('username3'); - expect( - bus.on(), - emitsInOrder([ - watchable, - EmptyEvent(), - const EventCompletionEvent(watchable), - EmptyEvent(), - const FollowSuccessfullyEvent(watchable), - EmptyEvent(), - ])); - - bus.watch(watchable); - bus.complete(watchable, - nextEvent: const FollowSuccessfullyEvent(watchable)); - }); - - // test('emit Follower Events', () { - // final watchable = FollowAppEvent('username3', id: '3'); - // expect( - // _bus.on(), - // emitsInAnyOrder([ - // FollowAppEvent('username3', id: '3'), - // FollowSuccessfullyAppEvent(watchable), - // ])); - - // _bus.watch(watchable); - // _bus.complete(watchable, withh: FollowSuccessfullyAppEvent(watchable)); - // }); -} diff --git a/sandbox/eventbus/test/distinct_test.dart b/sandbox/eventbus/test/distinct_test.dart deleted file mode 100644 index 5aeaf9f..0000000 --- a/sandbox/eventbus/test/distinct_test.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:event_bus_plus/event_bus_plus.dart'; -import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; - -import 'models.dart'; - -void main() { - late IEventBus bus; - - before(() { - bus = EventBus(); - }); - - test('Call once', () { - expectLater( - bus.last$, - emitsInOrder([ - const SomeEvent(), - EmptyEvent(), - const SomeAnotherEvent(), - EmptyEvent(), - ])); - bus.fire(const SomeEvent()); - bus.fire(const SomeAnotherEvent()); - }); - - // test('Call twice', () { - // const event = SomeEvent(); - // expectLater( - // bus.last$, - // emitsInOrder([ - // const SomeEvent(), - // EmptyEvent(), - // const SomeAnotherEvent(), - // EmptyEvent(), - // ])); - // bus.fire(event); - // bus.fire(event); - // bus.fire(const SomeAnotherEvent()); - // }); - - // test('Call three times', () { - // const event = SomeEvent(); - // expectLater( - // bus.last$, - // emitsInOrder([ - // const SomeEvent(), - // EmptyEvent(), - // const SomeAnotherEvent(), - // EmptyEvent(), - // ])); - // bus.fire(event); - // bus.fire(event); - // bus.fire(event); - // bus.fire(const SomeAnotherEvent()); - // }); -} diff --git a/sandbox/eventbus/test/empty_event_test.dart b/sandbox/eventbus/test/empty_event_test.dart deleted file mode 100644 index 050505e..0000000 --- a/sandbox/eventbus/test/empty_event_test.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:event_bus_plus/event_bus_plus.dart'; -import 'package:test/test.dart'; -import 'models.dart'; - -void main() { - final IEventBus _bus = EventBus(); - - test('Should fire Empty event', () { - expect( - _bus.last$, - emitsInOrder( - [ - const SomeEvent(), - EmptyEvent(), - ], - ), - ); - _bus.fire(const SomeEvent()); - }, timeout: const Timeout(Duration(seconds: 1))); -} diff --git a/sandbox/eventbus/test/event_bus_test.dart b/sandbox/eventbus/test/event_bus_test.dart deleted file mode 100644 index 5d7a5b1..0000000 --- a/sandbox/eventbus/test/event_bus_test.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:event_bus_plus/event_bus_plus.dart'; -import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; -import 'package:shouldly/shouldly.dart'; -import 'package:test/scaffolding.dart'; - -import 'models.dart'; - -void main() { - late IEventBus bus; - - before(() { - bus = EventBus(); - }); - - test('Empty event bus', () { - bus.isBusy.should.beFalse(); - }); - - when('start some event', () { - const event = FollowAppEvent('username'); - // const eventId = '1'; - - before(() { - bus.watch(event); - }); - - then('should be busy', () { - bus.isBusy.should.beTrue(); - }); - - then('should be in progress', () { - bus.isInProgress().should.beTrue(); - }); - - and('complete the event', () { - before(() { - bus.complete(event); - }); - - then('should not be busy', () { - bus.isBusy.should.beFalse(); - }); - - then('should not be in progress', () { - bus.isInProgress().should.not.beTrue(); - }); - }); - }); - - group('compare equality', () { - // test('compare two equal events - should not be equal', () { - // final event = FollowAppEvent('username'); - // final event2 = FollowAppEvent('username'); - // event.should.not.be(event2); - // }); - - test('compare two equal events - should be equal', () { - const event = FollowAppEvent('username'); - const event2 = FollowAppEvent('username'); - event.should.be(event2); - }); - }); -} diff --git a/sandbox/eventbus/test/history_test.dart b/sandbox/eventbus/test/history_test.dart deleted file mode 100644 index 9eaf709..0000000 --- a/sandbox/eventbus/test/history_test.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:clock/clock.dart'; -import 'package:event_bus_plus/event_bus_plus.dart'; -import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; -import 'package:shouldly/shouldly.dart'; -import 'package:test/test.dart'; - -import 'models.dart'; - -void main() { - late IEventBus bus; - - before(() { - bus = EventBus(maxHistoryLength: 3); - }); - - test('Keep history', () { - final date = DateTime(2022, 9, 1); - withClock(Clock.fixed(date), () { - bus.fire(const SomeEvent()); - bus.fire(const SomeAnotherEvent()); - bus.fire(const FollowAppEvent('@username')); - - bus.history.should.be([ - EventBusHistoryEntry(const SomeEvent(), date), - EventBusHistoryEntry(const SomeAnotherEvent(), date), - EventBusHistoryEntry(const FollowAppEvent('@username'), date), - ]); - }); - }); - - test('Keep full history without debounce', () { - final date = DateTime(2022, 9, 1, 4, 59, 55); - withClock(Clock.fixed(date), () { - bus.fire(const SomeEvent()); - bus.fire(const SomeEvent()); - bus.fire(const SomeAnotherEvent()); - - bus.history.should.be([ - EventBusHistoryEntry(const SomeEvent(), date), - EventBusHistoryEntry(const SomeEvent(), date), - EventBusHistoryEntry(const SomeAnotherEvent(), date), - ]); - }); - }); - - test('Clear history', () { - bus.fire(const SomeEvent()); - bus.fire(const SomeAnotherEvent()); - - bus.clearHistory(); - - bus.history.should.beEmpty(); - }); - - group('History length', () { - test('Add more than max', () { - final date = DateTime(2022, 9, 1, 4, 59, 55); - - withClock(Clock.fixed(date), () { - bus.fire(const SomeEvent()); - bus.fire(const SomeAnotherEvent()); - bus.fire(const FollowAppEvent('@username')); - bus.fire(const NewCommentEvent('text')); - - bus.history.length.should.be(3); - - bus.history.should.be([ - EventBusHistoryEntry(const SomeAnotherEvent(), date), - EventBusHistoryEntry(const FollowAppEvent('@username'), date), - EventBusHistoryEntry(const NewCommentEvent('text'), date), - ]); - }); - }); - }); -} diff --git a/sandbox/eventbus/test/mapping/map_ignore_test.dart b/sandbox/eventbus/test/mapping/map_ignore_test.dart deleted file mode 100644 index 496aed7..0000000 --- a/sandbox/eventbus/test/mapping/map_ignore_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:event_bus_plus/event_bus_plus.dart'; -import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; -import 'package:test/test.dart'; - -import '../models.dart'; - -void main() { - late IEventBus bus; - - before( - () { - bus = EventBus( - map: { - SomeEvent: [ - (e) => e, - (e) => const SomeAnotherEvent(), - ], - }, - ); - }, - ); - - test('does not emit the same event', () { - expect( - bus.last$, - emitsInOrder( - [ - const SomeEvent(), - const SomeAnotherEvent(), - EmptyEvent(), - ], - ), - ); - bus.fire(const SomeEvent()); - }, timeout: const Timeout(Duration(seconds: 1))); -} diff --git a/sandbox/eventbus/test/mapping/map_test.dart b/sandbox/eventbus/test/mapping/map_test.dart deleted file mode 100644 index f83ef02..0000000 --- a/sandbox/eventbus/test/mapping/map_test.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:event_bus_plus/event_bus_plus.dart'; -import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; -import 'package:test/test.dart'; - -import '../models.dart'; - -void main() { - late IEventBus bus; - - before( - () { - bus = EventBus( - map: { - SomeEvent: [ - (e) => const SomeAnotherEvent(), - ], - }, - ); - }, - ); - - test('emits another', () { - expect( - bus.last$, - emitsInOrder( - [ - const SomeEvent(), - const SomeAnotherEvent(), - ], - ), - ); - bus.fire(const SomeEvent()); - }, timeout: const Timeout(Duration(seconds: 1))); -} diff --git a/sandbox/eventbus/test/models.dart b/sandbox/eventbus/test/models.dart deleted file mode 100644 index 899c50f..0000000 --- a/sandbox/eventbus/test/models.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:event_bus_plus/event_bus_plus.dart'; - -class FollowAppEvent extends AppEvent { - const FollowAppEvent(this.username); - - final String username; - - @override - List get props => [username]; -} - -class FollowSuccessfullyEvent extends AppEvent { - const FollowSuccessfullyEvent(this.starting); - - final FollowAppEvent starting; - - @override - List get props => [starting]; -} - -class NewCommentEvent extends AppEvent { - const NewCommentEvent(this.text); - - final String text; - - @override - List get props => [text]; -} - -class SomeEvent extends AppEvent { - const SomeEvent(); - - @override - List get props => []; -} - -class SomeAnotherEvent extends AppEvent { - const SomeAnotherEvent(); - - @override - List get props => []; -} diff --git a/sandbox/eventbus/test/respond_test.dart b/sandbox/eventbus/test/respond_test.dart deleted file mode 100644 index 3c9b5cf..0000000 --- a/sandbox/eventbus/test/respond_test.dart +++ /dev/null @@ -1,74 +0,0 @@ -// ignore_for_file: prefer_const_constructors - -import 'dart:async'; -import 'dart:developer'; - -import 'package:event_bus_plus/event_bus_plus.dart'; -import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; - -import 'models.dart'; - -void main() { - late IEventBus bus; - - before(() { - bus = EventBus(); - }); - - test('emit Some Event', () { - final ctrl = StreamController(); - - final sub = bus.respond((event) { - log('new comment'); - ctrl.add(2); - }).respond((event) { - log('new follower'); - ctrl.add(1); - }); - - expect(ctrl.stream, emitsInOrder([1, 2])); - - bus.fire(FollowAppEvent('username')); - bus.fire(NewCommentEvent('comment #1')); - }); - - test('emit Some Events', () { - final ctrl = StreamController(); - - final sub = bus.respond((event) { - log('new comment'); - ctrl.add(2); - }).respond((event) { - log('new follower'); - ctrl.add(1); - }); - - expect(ctrl.stream, emitsInOrder([1, 2, 1])); - - bus.fire(FollowAppEvent('username')); - bus.fire(NewCommentEvent('comment #1')); - bus.fire(FollowAppEvent('username2')); - }); - - test('emit all Event', () { - final ctrl = StreamController(); - - final sub = bus.respond((event) { - log('new comment'); - ctrl.add(2); - }).respond((event) { - log('new follower'); - ctrl.add(1); - }).respond((event) { - log('event $event'); - ctrl.add(3); - }); - - expect(ctrl.stream, emitsInOrder([1, 3, 3, 2, 3, 3])); - - bus.fire(FollowAppEvent('username')); - bus.fire(NewCommentEvent('comment #1')); - }); -} diff --git a/sandbox/eventbus/test/streams_test.dart b/sandbox/eventbus/test/streams_test.dart deleted file mode 100644 index 1cec696..0000000 --- a/sandbox/eventbus/test/streams_test.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:event_bus_plus/event_bus_plus.dart'; -import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; -import 'package:test/test.dart'; -import 'models.dart'; - -void main() { - late IEventBus bus; - - before(() { - bus = EventBus(); - }); - - test('description', () { - bus.inProgress$.map((List events) => - events.whereType().isNotEmpty); - }, skip: 'should skip'); - - test('emit Follower Event', () { - const id = 'id'; - expect(bus.on(), emitsInAnyOrder([const FollowAppEvent('username')])); - bus.fire(const FollowAppEvent('username')); - }); - - test('emit Follower Events', () { - expect( - bus.on(), - emitsInOrder([ - const FollowAppEvent('username'), - EmptyEvent(), - const FollowAppEvent('username3'), - EmptyEvent(), - const FollowAppEvent('username2'), - EmptyEvent(), - ])); - - bus.fire(const FollowAppEvent('username')); - bus.fire(const FollowAppEvent('username3')); - bus.fire(const FollowAppEvent('username2')); - }); - - test('emit New Comment Event', () { - expect( - bus.on(), - emitsInOrder([ - const NewCommentEvent('comment #1'), - const NewCommentEvent('comment #2'), - ])); - - bus.fire(const FollowAppEvent('username3')); - bus.fire(const FollowAppEvent('username3')); - bus.fire(const NewCommentEvent('comment #1')); - bus.fire(const FollowAppEvent('username3')); - bus.fire(const NewCommentEvent('comment #2')); - bus.fire(const FollowAppEvent('username3')); - }); -} diff --git a/sandbox/eventbus/test/timestamp_test.dart b/sandbox/eventbus/test/timestamp_test.dart deleted file mode 100644 index a8dc0d0..0000000 --- a/sandbox/eventbus/test/timestamp_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:clock/clock.dart'; -import 'package:event_bus_plus/event_bus_plus.dart'; -import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; -import 'package:shouldly/shouldly.dart'; -import 'package:test/test.dart'; -import 'dart:developer' as dev; - -import 'models.dart'; - -void main() { - late IEventBus bus; - - before(() { - bus = EventBus(maxHistoryLength: 3); - }); - - test('right timestamp', () { - final date = DateTime(2022, 9, 1); - withClock(Clock.fixed(date), () { - bus.fire(const SomeEvent()); - bus.fire(const SomeAnotherEvent()); - bus.fire(const FollowAppEvent('@username')); - - for (final e in bus.history) { - e.timestamp.should.be(date); - dev.log(e.toString()); - } - - bus.history.should.be([ - EventBusHistoryEntry(const SomeEvent(), date), - EventBusHistoryEntry(const SomeAnotherEvent(), date), - EventBusHistoryEntry(const FollowAppEvent('@username'), date), - ]); - }); - }); -} diff --git a/sandbox/eventbus/tool/coverage.sh b/sandbox/eventbus/tool/coverage.sh deleted file mode 100755 index 5313d46..0000000 --- a/sandbox/eventbus/tool/coverage.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -cd .. -# Generate `coverage/lcov.info` file -flutter test --coverage -# Generate HTML report -# Note: on macOS you need to have lcov installed on your system (`brew install lcov`) to use this: -genhtml coverage/lcov.info -o coverage/html -# Open the report -open coverage/html/index.html - - - - diff --git a/sandbox/mqueue/.github/workflows/action.yaml b/sandbox/mqueue/.github/workflows/action.yaml deleted file mode 100644 index b1c9c11..0000000 --- a/sandbox/mqueue/.github/workflows/action.yaml +++ /dev/null @@ -1,43 +0,0 @@ -name: build - -on: - push: - branches: - - master - - main - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: 📚 Git Checkout - uses: actions/checkout@v4 - - - name: 🎯 Setup Dart - uses: dart-lang/setup-dart@v1 - - - name: 📦 Install Dependencies - run: dart pub get - - - name: ✨ Check Formatting - run: dart format --line-length 80 --set-exit-if-changed . - - - name: 🕵️ Analyze - run: dart analyze --fatal-infos --fatal-warnings . - - - name: 🧪 Run Tests - run: | - dart pub global activate coverage 1.2.0 - dart test --coverage=coverage && dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info - - - name: 📊 Check Code Coverage - uses: VeryGoodOpenSource/very_good_coverage@v2 - with: - path: ./coverage/lcov.info - min_coverage: 100 - - - name: 📈 Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/sandbox/mqueue/CHANGELOG.md b/sandbox/mqueue/CHANGELOG.md deleted file mode 100644 index 70c0899..0000000 --- a/sandbox/mqueue/CHANGELOG.md +++ /dev/null @@ -1,19 +0,0 @@ -## 1.0.0 - -- Initial version of the package. - -## 1.0.1 - -- Fix documentation. (Image path) -- Update package description. -- Fix linter rules. (Type matching) -- Update `example` files. -- Remove `mocktail` dependency. - -## 1.0.2 - - - Update documentation. - -## 1.1.0 - - - `Deprecate` the `Consumer` mixin in favor of `ConsumerMixin`. ([#1](https://github.com/N-Razzouk/dart_mq/issues/1)) diff --git a/sandbox/mqueue/LICENSE b/sandbox/mqueue/LICENSE deleted file mode 100644 index 0c5e429..0000000 --- a/sandbox/mqueue/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Naif Razzouk - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/sandbox/mqueue/README.md b/sandbox/mqueue/README.md deleted file mode 100644 index 2563c78..0000000 --- a/sandbox/mqueue/README.md +++ /dev/null @@ -1,165 +0,0 @@ -# DartMQ: A Message Queue System for Dart and Flutter - - - -[![Pub](https://img.shields.io/pub/v/dart_mq.svg)](https://pub.dev/packages/dart_mq) -[![coverage](https://codecov.io/gh/N-Razzouk/dart_mq/graph/badge.svg)](https://app.codecov.io/gh/N-Razzouk/dart_mq) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) - -DartMQ is a Dart package that provides message queue functionality for sending messages between different components in your Dart and Flutter applications. It offers a simple and efficient way to implement message queues, making it easier to build robust and scalable applications. - -## Table of Contents - -1. [Introduction](#introduction) -2. [Exchanges](#exchanges) -3. [Usage](#usage) -4. [Examples](#examples) -5. [Acknowledgment](#acknowledgment) - -### - -## Introduction - -In the development of complex applications, dependencies among components are almost inevitable. Often, different components within your application need to communicate with each other, leading to tight coupling between these elements. - -![Components](https://github.com/N-Razzouk/dart_mq/blob/master/assets/components.png?raw=true) - -### - -Message queues provide an effective means to decouple these components by enabling communication through messages. This decoupling strategy enhances the development of robust applications. - -![Components with MQ](https://github.com/N-Razzouk/dart_mq/blob/master/assets/components-mq.png?raw=true) - -### - -DartMQ employs the publish-subscribe pattern. **Producers** send messages, **Consumers** receive them, and **Queues** and **Exchanges** facilitate this communication. - -![Simple View](https://github.com/N-Razzouk/dart_mq/blob/master/assets/simple-view.png?raw=true) - -### - -Communication channels are called Exchanges. Exchanges receive messages from Producers, efficiently routing them to Queues for Consumer consumption. - -![Detailed View](https://github.com/N-Razzouk/dart_mq/blob/master/assets/detailed-view.png?raw=true) - -## Exchanges - -### DartMQ provides different types of Exchanges for different use cases. - -### - -- **Default Exchange**: Routes messages based on Queue names. - -![Default Exchange](https://github.com/N-Razzouk/dart_mq/blob/master/assets/default-exchange.png?raw=true) - -### - -- **Fanout Exchange**: Sends messages to all bound Queues. - -![Fanout Exchange](https://github.com/N-Razzouk/dart_mq/blob/master/assets/fanout-exchange.png?raw=true) - -### - -- **Direct Exchange**: Routes messages to Queues based on routing keys. - -![Direct Exchange](https://github.com/N-Razzouk/dart_mq/blob/master/assets/direct-exchange.png?raw=true) - -## Usage - -### Initialize an MQClient: - - - -```dart -import 'package:dart_mq/dart_mq.dart'; - -void main() { - // Initialize DartMQ - MQClient.initialize(); - - // Your application code here -} - -``` - -### Declare a Queue: - -```dart -MQClient.declareQueue('my_queue'); -``` - -> Note: Queues are idempotent, which means that if you declare a Queue multiple times, it will not create multiple Queues. Instead, it will return the existing Queue. - -### Create a Producer: - -```dart -class MyProducer with ProducerMixin { - void greet(String message) { - // Send a message to the queue - sendMessage( - routingKey: 'my_queue', - payload: message, - ); - } -} -``` - -> Note: `exchangeName` is optional. If you don't specify an exchange name, the message is sent to the default exchange. - -### Create a Consumer: - -```dart -class MyConsumer with ConsumerMixin { - void listenToQueue() { - // Subscribe to the queue and process incoming messages - subscribe( - queueId: 'my_queue', - callback: (message) { - // Handle incoming message - print('Received message: $message'); - }, - ) - } -} -``` - -### Putting it all together: - -```dart -void main() { - // Initialize DartMQ - MQClient.initialize(); - - // Declare a Queue - MQClient.declareQueue('my_queue'); - - // Create a Producer - final producer = MyProducer(); - - // Create a Consumer - final consumer = MyConsumer(); - - // Start listening - consumer.listenToQueue(); - - // Send a message - producer.greet('Hello World!'); - - // Your application code here - ... -} -``` - -## Examples - -- [Hello World](example/hello_world): A simple example that demonstrates how to send and receive messages using DartMQ. - -- [Message Filtering](example/message_filtering): An example that demonstrates how to multiple consumers can listen to the same queue and filter messages accordingly. - -- [Routing](example/routing): An example that demonstrates how to use Direct Exchanges to route messages to different queues based on the routing key. - -- [RPC (Remote Procedure Call)](example/rpc): An example that demonstrates how to send RPC requests and receive responses using DartMQ. - -## Acknowledgment - -- [RabbitMQ](https://www.rabbitmq.com/): This package is inspired by RabbitMQ, an open-source message-broker software that implements the Advanced Message Queuing Protocol (AMQP). diff --git a/sandbox/mqueue/analysis_options.yaml b/sandbox/mqueue/analysis_options.yaml deleted file mode 100644 index 662618b..0000000 --- a/sandbox/mqueue/analysis_options.yaml +++ /dev/null @@ -1,211 +0,0 @@ -# This file configures the static analysis results for your project (errors, -# warnings, and lints). -# -# This enables the 'recommended' set of lints from `package:lints`. -# This set helps identify many issues that may lead to problems when running -# or consuming Dart code, and enforces writing Dart using a single, idiomatic -# style and format. -# -# If you want a smaller set of lints you can change this to specify -# 'package:lints/core.yaml'. These are just the most critical lints -# (the recommended set includes the core lints). -# The core lints are also what is used by pub.dev for scoring packages. - -include: package:lints/recommended.yaml - -linter: - rules: - - prefer_const_constructors - - prefer_const_literals_to_create_immutables - - prefer_final_fields - - always_put_required_named_parameters_first - - avoid_init_to_null - - lines_longer_than_80_chars - - use_function_type_syntax_for_parameters - - avoid_relative_lib_imports - - avoid_shadowing_type_parameters - - avoid_equals_and_hash_code_on_mutable_classes - - unnecessary_brace_in_string_interps - - always_declare_return_types - - always_use_package_imports - - annotate_overrides - - avoid_bool_literals_in_conditional_expressions - - avoid_catching_errors - - avoid_double_and_int_checks - - avoid_dynamic_calls - - avoid_empty_else - - avoid_escaping_inner_quotes - - avoid_field_initializers_in_const_classes - - avoid_final_parameters - - avoid_function_literals_in_foreach_calls - - avoid_js_rounded_ints - - avoid_multiple_declarations_per_line - - avoid_null_checks_in_equality_operators - - avoid_positional_boolean_parameters - - avoid_print - - avoid_private_typedef_functions - - avoid_redundant_argument_values - - avoid_renaming_method_parameters - - avoid_return_types_on_setters - - avoid_returning_null_for_void - - avoid_returning_this - - avoid_setters_without_getters - - avoid_single_cascade_in_expression_statements - - avoid_slow_async_io - - avoid_type_to_string - - avoid_types_as_parameter_names - - avoid_unnecessary_containers - - avoid_unused_constructor_parameters - - avoid_void_async - - avoid_web_libraries_in_flutter - - await_only_futures - - camel_case_extensions - - camel_case_types - - cancel_subscriptions - - cascade_invocations - - cast_nullable_to_non_nullable - - collection_methods_unrelated_type - - combinators_ordering - - comment_references - - conditional_uri_does_not_exist - - constant_identifier_names - - control_flow_in_finally - - curly_braces_in_flow_control_structures - - dangling_library_doc_comments - - depend_on_referenced_packages - - deprecated_consistency - - directives_ordering - - empty_catches - - empty_constructor_bodies - - empty_statements - - eol_at_end_of_file - - exhaustive_cases - - file_names - - flutter_style_todos - - hash_and_equals - - implementation_imports - - implicit_call_tearoffs - - implicit_reopen - - invalid_case_patterns - - join_return_with_assignment - - leading_newlines_in_multiline_strings - - library_annotations - - library_names - - library_prefixes - - library_private_types_in_public_api - - literal_only_boolean_expressions - - missing_whitespace_between_adjacent_strings - - no_adjacent_strings_in_list - - no_default_cases - - no_duplicate_case_values - - no_leading_underscores_for_library_prefixes - - no_leading_underscores_for_local_identifiers - - no_logic_in_create_state - - no_runtimeType_toString - - non_constant_identifier_names - - noop_primitive_operations - - null_check_on_nullable_type_parameter - - null_closures - - omit_local_variable_types - - one_member_abstracts - - only_throw_errors - - overridden_fields - - package_api_docs - - package_names - - package_prefixed_library_names - - parameter_assignments - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - - prefer_asserts_with_message - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_constructors_over_static_methods - - prefer_contains - - prefer_final_in_for_each - - prefer_final_locals - - prefer_for_elements_to_map_fromIterable - - prefer_function_declarations_over_variables - - prefer_generic_function_type_aliases - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - - prefer_int_literals - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_iterable_whereType - - prefer_null_aware_method_calls - - prefer_null_aware_operators - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - prefer_void_to_null - - provide_deprecation_message - - public_member_api_docs - - recursive_getters - - require_trailing_commas - - secure_pubspec_urls - - sized_box_for_whitespace - - sized_box_shrink_expand - - slash_for_doc_comments - - sort_child_properties_last - - sort_constructors_first - - sort_pub_dependencies - - sort_unnamed_constructors_first - - test_types_in_equals - - throw_in_finally - - tighten_type_of_initializing_formals - - type_annotate_public_apis - - type_init_formals - - unawaited_futures - - unnecessary_await_in_return - - unnecessary_breaks - - unnecessary_const - - unnecessary_constructor_name - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_late - - unnecessary_library_directive - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_checks - - unnecessary_null_in_if_null_operators - - unnecessary_nullable_for_final_variable_declarations - - unnecessary_overrides - - unnecessary_parenthesis - - unnecessary_raw_strings - - unnecessary_statements - - unnecessary_string_escapes - - unnecessary_string_interpolations - - unnecessary_this - - unnecessary_to_list_in_spreads - - unrelated_type_equality_checks - - use_build_context_synchronously - - use_colored_box - - use_enums - - use_full_hex_values_for_flutter_colors - - use_if_null_to_convert_nulls_to_bools - - use_is_even_rather_than_modulo - - use_key_in_widget_constructors - - use_late_for_private_fields_and_variables - - use_named_constants - - use_raw_strings - - use_rethrow_when_possible - - use_setters_to_change_properties - - use_string_buffers - - use_string_in_part_of_directives - - use_super_parameters - - use_test_throws_matchers - - use_to_and_as_if_applicable - - valid_regexps - - void_checks - -# For more information about the core and recommended set of lints, see -# https://dart.dev/go/core-lints - -# For additional information about configuring this file, see -# https://dart.dev/guides/language/analysis-options diff --git a/sandbox/mqueue/assets/components-mq.png b/sandbox/mqueue/assets/components-mq.png deleted file mode 100644 index c0bf845..0000000 Binary files a/sandbox/mqueue/assets/components-mq.png and /dev/null differ diff --git a/sandbox/mqueue/assets/components.png b/sandbox/mqueue/assets/components.png deleted file mode 100644 index bfced0a..0000000 Binary files a/sandbox/mqueue/assets/components.png and /dev/null differ diff --git a/sandbox/mqueue/assets/default-exchange.png b/sandbox/mqueue/assets/default-exchange.png deleted file mode 100644 index 1b721a2..0000000 Binary files a/sandbox/mqueue/assets/default-exchange.png and /dev/null differ diff --git a/sandbox/mqueue/assets/detailed-view.png b/sandbox/mqueue/assets/detailed-view.png deleted file mode 100644 index 2ce7eee..0000000 Binary files a/sandbox/mqueue/assets/detailed-view.png and /dev/null differ diff --git a/sandbox/mqueue/assets/direct-exchange.png b/sandbox/mqueue/assets/direct-exchange.png deleted file mode 100644 index f519dce..0000000 Binary files a/sandbox/mqueue/assets/direct-exchange.png and /dev/null differ diff --git a/sandbox/mqueue/assets/fanout-exchange.png b/sandbox/mqueue/assets/fanout-exchange.png deleted file mode 100644 index 823a250..0000000 Binary files a/sandbox/mqueue/assets/fanout-exchange.png and /dev/null differ diff --git a/sandbox/mqueue/assets/simple-view.png b/sandbox/mqueue/assets/simple-view.png deleted file mode 100644 index b24e6b8..0000000 Binary files a/sandbox/mqueue/assets/simple-view.png and /dev/null differ diff --git a/sandbox/mqueue/example/main.dart b/sandbox/mqueue/example/main.dart deleted file mode 100644 index 83f3e03..0000000 --- a/sandbox/mqueue/example/main.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:angel3_mq/mq.dart'; - -import 'receiver.dart'; -import 'sender.dart'; - -void main() async { - MQClient.initialize(); - - final sender = Sender(); - - final receiver = Receiver()..listenToGreeting(); - - await sender.sendGreeting(greeting: 'Hello, World!'); - - receiver.stopListening(); -} diff --git a/sandbox/mqueue/example/message_filtering/main.dart b/sandbox/mqueue/example/message_filtering/main.dart deleted file mode 100644 index c5945a7..0000000 --- a/sandbox/mqueue/example/message_filtering/main.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:angel3_mq/mq.dart'; - -import 'task_manager.dart'; -import 'worker_one.dart'; -import 'worker_two.dart'; - -void main() async { - MQClient.initialize(); - - final workerOne = WorkerOne(); - - final workerTwo = WorkerTwo(); - - final taskManager = TaskManager(); - - workerOne.startListening(); - - workerTwo.startListening(); - - taskManager - ..sendTask(task: 'Hello..') - ..sendTask(task: 'Hello...') - ..sendTask(task: 'Hello....') - ..sendTask(task: 'Hello.') - ..sendTask(task: 'Hello.......') - ..sendTask(task: 'Hello..'); -} diff --git a/sandbox/mqueue/example/message_filtering/task_manager.dart b/sandbox/mqueue/example/message_filtering/task_manager.dart deleted file mode 100644 index 9c6aee9..0000000 --- a/sandbox/mqueue/example/message_filtering/task_manager.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:angel3_mq/mq.dart'; - -final class TaskManager with ProducerMixin { - TaskManager() { - MQClient.instance.declareQueue('task_queue'); - } - - void sendTask({required String task}) => sendMessage( - payload: task, - routingKey: 'task_queue', - ); -} diff --git a/sandbox/mqueue/example/message_filtering/worker_one.dart b/sandbox/mqueue/example/message_filtering/worker_one.dart deleted file mode 100644 index 788c6bb..0000000 --- a/sandbox/mqueue/example/message_filtering/worker_one.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:developer'; - -import 'package:angel3_mq/mq.dart'; - -final class WorkerOne with ConsumerMixin { - WorkerOne() { - MQClient.instance.declareQueue('task_queue'); - } - - void startListening() => subscribe( - queueId: 'task_queue', - filter: (Object messagePayload) => messagePayload - .toString() - .split('') - .where((String char) => char == '.') - .length - .isEven, - callback: (Message message) { - log('WorkerOne reacting to ${message.payload}'); - }, - ); -} diff --git a/sandbox/mqueue/example/message_filtering/worker_two.dart b/sandbox/mqueue/example/message_filtering/worker_two.dart deleted file mode 100644 index a4cbd98..0000000 --- a/sandbox/mqueue/example/message_filtering/worker_two.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'dart:developer'; - -import 'package:angel3_mq/mq.dart'; - -final class WorkerTwo with ConsumerMixin { - WorkerTwo() { - MQClient.instance.declareQueue('task_queue'); - } - - void startListening() => subscribe( - queueId: 'task_queue', - filter: (Object messagePayload) => - messagePayload - .toString() - .split('') - .where((String char) => char == '.') - .length % - 2 != - 0, - callback: (Message message) { - log('WorkerTwo reacting to ${message.payload}'); - }, - ); -} diff --git a/sandbox/mqueue/example/receiver.dart b/sandbox/mqueue/example/receiver.dart deleted file mode 100644 index 8b929a8..0000000 --- a/sandbox/mqueue/example/receiver.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'dart:developer'; - -import 'package:angel3_mq/mq.dart'; - -final class Receiver with ConsumerMixin { - Receiver() { - MQClient.instance.declareQueue('hello'); - } - - void listenToGreeting() => subscribe( - queueId: 'hello', - callback: (Message message) { - log('Received: ${message.payload}'); - }, - ); - - void stopListening() => unsubscribe(queueId: 'hello'); -} diff --git a/sandbox/mqueue/example/routing/debug_logger.dart b/sandbox/mqueue/example/routing/debug_logger.dart deleted file mode 100644 index e45bece..0000000 --- a/sandbox/mqueue/example/routing/debug_logger.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:developer'; - -import 'package:angel3_mq/mq.dart'; - -final class DebugLogger with ConsumerMixin { - DebugLogger() { - MQClient.instance.declareExchange( - exchangeName: 'logs', - exchangeType: ExchangeType.direct, - ); - _queueName = MQClient.instance.declareQueue('debug'); - } - - late final String _queueName; - - void startListening() { - MQClient.instance.bindQueue( - queueId: _queueName, - exchangeName: 'logs', - bindingKey: 'info', - ); - MQClient.instance.bindQueue( - queueId: _queueName, - exchangeName: 'logs', - bindingKey: 'warning', - ); - MQClient.instance.bindQueue( - queueId: _queueName, - exchangeName: 'logs', - bindingKey: 'error', - ); - subscribe( - queueId: _queueName, - callback: (Message message) { - log('Debug Logger recieved: ${message.payload}'); - }, - ); - } -} diff --git a/sandbox/mqueue/example/routing/logger.dart b/sandbox/mqueue/example/routing/logger.dart deleted file mode 100644 index 98f6e8c..0000000 --- a/sandbox/mqueue/example/routing/logger.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:angel3_mq/mq.dart'; - -final class Logger with ProducerMixin { - Logger() { - MQClient.instance.declareExchange( - exchangeName: 'logs', - exchangeType: ExchangeType.direct, - ); - } - - Future log({ - required String level, - required String message, - }) async { - sendMessage( - payload: message, - exchangeName: 'logs', - routingKey: level, - ); - } -} diff --git a/sandbox/mqueue/example/routing/main.dart b/sandbox/mqueue/example/routing/main.dart deleted file mode 100644 index 4136032..0000000 --- a/sandbox/mqueue/example/routing/main.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:angel3_mq/mq.dart'; - -import 'debug_logger.dart'; -import 'logger.dart'; -import 'production_logger.dart'; - -void main() async { - MQClient.initialize(); - - DebugLogger().startListening(); - - ProductionLogger().startListening(); - - final logger = Logger(); - - await logger.log( - level: 'info', - message: 'This is an info message', - ); - - await logger.log( - level: 'warning', - message: 'This is a warning message', - ); - - await logger.log( - level: 'error', - message: 'This is an error message', - ); -} diff --git a/sandbox/mqueue/example/routing/production_logger.dart b/sandbox/mqueue/example/routing/production_logger.dart deleted file mode 100644 index e7c345a..0000000 --- a/sandbox/mqueue/example/routing/production_logger.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'dart:developer'; - -import 'package:angel3_mq/mq.dart'; - -final class ProductionLogger with ConsumerMixin { - ProductionLogger() { - MQClient.instance.declareExchange( - exchangeName: 'logs', - exchangeType: ExchangeType.direct, - ); - _queueName = MQClient.instance.declareQueue('production'); - } - - late final String _queueName; - - void startListening() { - MQClient.instance.bindQueue( - queueId: _queueName, - exchangeName: 'logs', - bindingKey: 'error', - ); - subscribe( - queueId: _queueName, - callback: (Message message) { - log('Production Logger recieved: ${message.payload}'); - }, - ); - } -} diff --git a/sandbox/mqueue/example/rpc/main.dart b/sandbox/mqueue/example/rpc/main.dart deleted file mode 100644 index ba05996..0000000 --- a/sandbox/mqueue/example/rpc/main.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:angel3_mq/mq.dart'; - -import 'service_one.dart'; -import 'service_two.dart'; - -void main() { - MQClient.initialize(); - - MQClient.instance.declareExchange( - exchangeName: 'ServiceRPC', - exchangeType: ExchangeType.direct, - ); - - final serviceOne = ServiceOne(); - - ServiceTwo().startListening(); - - serviceOne.requestFoo(); -} diff --git a/sandbox/mqueue/example/rpc/service_one.dart b/sandbox/mqueue/example/rpc/service_one.dart deleted file mode 100644 index a2d4f97..0000000 --- a/sandbox/mqueue/example/rpc/service_one.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'dart:developer'; - -import 'package:angel3_mq/mq.dart'; - -class ServiceOne with ProducerMixin { - Future requestFoo() async { - final res = await sendRPCMessage( - exchangeName: 'ServiceRPC', - routingKey: 'rpcBinding', - processId: 'foo', - args: {}, - ); - _handleFuture(res); - } - - void _handleFuture(String data) { - log('Service One received: $data\n'); - } -} diff --git a/sandbox/mqueue/example/rpc/service_two.dart b/sandbox/mqueue/example/rpc/service_two.dart deleted file mode 100644 index 20e4ba2..0000000 --- a/sandbox/mqueue/example/rpc/service_two.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:async'; -import 'dart:developer'; - -import 'package:angel3_mq/mq.dart'; - -class ServiceTwo with ConsumerMixin { - ServiceTwo() { - MQClient.instance.declareExchange( - exchangeName: 'ServiceRPC', - exchangeType: ExchangeType.direct, - ); - _queueName = MQClient.instance.declareQueue('two'); - } - - late final String _queueName; - - Future startListening() async { - MQClient.instance.bindQueue( - queueId: _queueName, - exchangeName: 'ServiceRPC', - bindingKey: 'rpcBinding', - ); - subscribe( - queueId: _queueName, - callback: (Message message) async { - log('Service Two got message $message\n'); - if (message.headers['type'] == 'RPC') { - switch (message.headers['processId']) { - case 'foo': - final data = await foo(); - final Completer completer = - message.headers['completer'] ?? (throw Exception()); - completer.complete(data); - default: - } - } - }, - ); - } - - Future foo() async { - // log('Service Two bar\n'); - await Future.delayed(const Duration(seconds: 2)); - return 'Hello, world!'; - } -} diff --git a/sandbox/mqueue/example/sender.dart b/sandbox/mqueue/example/sender.dart deleted file mode 100644 index 6d9b842..0000000 --- a/sandbox/mqueue/example/sender.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:angel3_mq/mq.dart'; - -final class Sender with ProducerMixin { - Sender() { - MQClient.instance.declareQueue('hello'); - } - - Future sendGreeting({required String greeting}) async => sendMessage( - routingKey: 'hello', - payload: greeting, - ); -} diff --git a/sandbox/mqueue/lib/mq.dart b/sandbox/mqueue/lib/mq.dart deleted file mode 100644 index e3736c8..0000000 --- a/sandbox/mqueue/lib/mq.dart +++ /dev/null @@ -1,11 +0,0 @@ -/// Library definition. -library angel3_mq; - -/// Export files. -export 'src/consumer/consumer.dart'; -export 'src/consumer/consumer.mixin.dart'; -export 'src/core/constants/enums.dart'; -export 'src/message/message.dart'; -export 'src/mq/mq.dart'; -export 'src/producer/producer.dart'; -export 'src/producer/producer.mixin.dart'; diff --git a/sandbox/mqueue/lib/src/binding/binding.dart b/sandbox/mqueue/lib/src/binding/binding.dart deleted file mode 100644 index c93d94a..0000000 --- a/sandbox/mqueue/lib/src/binding/binding.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:angel3_mq/src/binding/binding.interface.dart'; -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; - -/// A class representing a binding between a topic and its associated queues. -/// -/// The `Binding` class implements the [BindingInterface] interface and is -/// responsible for managing the association between a topic and its associated -/// queues. It allows the addition and removal of queues to the binding and the -/// publication of messages to all associated queues. -/// -/// Example: -/// ```dart -/// final binding = Binding('my_binding'); -/// final queue1 = Queue('queue_1'); -/// final queue2 = Queue('queue_2'); -/// -/// // Add queues to the binding. -/// binding.addQueue(queue1); -/// binding.addQueue(queue2); -/// -/// // Publish a message to all associated queues. -/// final message = Message( -/// headers: {'contentType': 'json', 'sender': 'Alice'}, -/// payload: {'text': 'Hello, World!'}, -/// timestamp: '2023-09-07T12:00:002', -/// ); -/// binding.publishMessage(message); -/// -/// // Check if the binding has associated queues. -/// final hasQueues = binding.hasQueues(); // Returns true -/// ``` -final class Binding implements BindingInterface { - /// Creates a new binding with the specified [id]. - /// - /// The [id] parameter represents the unique identifier for the binding. - Binding(this.id); - - /// The unique identifier for the binding. - final String id; - - /// A list of associated queues. - final List _queues = []; - - @override - bool hasQueues() => _queues.isNotEmpty; - - @override - void addQueue(Queue queue) => _queues.add(queue); - - @override - void removeQueue(String queueId) => _queues.removeWhere( - (Queue queue) => queue.id == queueId && queue.hasListeners() - ? throw QueueHasSubscribersException(queueId) - : queue.id == queueId, - ); - - @override - void publishMessage(Message message) { - for (final queue in _queues) { - queue.enqueue(message); - } - } - - @override - void clear() { - for (final queue in _queues) { - if (queue.hasListeners()) { - throw QueueHasSubscribersException(queue.id); - } - } - _queues.clear(); - } -} diff --git a/sandbox/mqueue/lib/src/binding/binding.interface.dart b/sandbox/mqueue/lib/src/binding/binding.interface.dart deleted file mode 100644 index 18fd762..0000000 --- a/sandbox/mqueue/lib/src/binding/binding.interface.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; - -/// An abstract interface class defining the contract for managing bindings. -/// -/// The `BindingInterface` abstract interface class defines a contract for -/// classes that are responsible for managing bindings between topics and -/// queues. Implementing classes must provide functionality for adding and -/// removing queues from the binding, publishing messages to the associated -/// queues, and checking if the binding has queues. -/// -/// Example: -/// ```dart -/// class MyBinding implements BindingInterface { -/// // Custom implementation of the binding interface methods. -/// } -/// ``` -abstract interface class BindingInterface { - /// Checks if the binding has associated queues. - /// - /// Returns `true` if the binding has one or more associated queues; - /// otherwise, `false`. - bool hasQueues(); - - /// Adds a queue to the binding. - /// - /// The [queue] parameter represents the queue to be associated with the - /// binding. - void addQueue(Queue queue); - - /// Removes a queue from the binding based on its ID. - /// - /// The [queueId] parameter represents the ID of the queue to be removed. - void removeQueue(String queueId); - - /// Publishes a message to all associated queues in the binding. - /// - /// The [message] parameter represents the message to be published to the - /// queues. - void publishMessage(Message message); - - /// Removes all queues from the binding. - void clear(); -} diff --git a/sandbox/mqueue/lib/src/consumer/consumer.dart b/sandbox/mqueue/lib/src/consumer/consumer.dart deleted file mode 100644 index 8d42065..0000000 --- a/sandbox/mqueue/lib/src/consumer/consumer.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_mq/src/consumer/consumer.interface.dart'; -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:angel3_mq/src/core/registrar/simple_registrar.dart'; -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/mq/mq.dart'; - -/// A mixin implementing the `ConsumerInterface` for message consumption. -/// -/// The `Consumer` mixin provides a concrete implementation of the -/// `ConsumerInterface`for message consumption. It allows classes to easily -/// consume messages from specific queues by subscribing to them, handling -/// received messages, and managing subscriptions. -/// -/// Example: -/// ```dart -/// class MyMessageConsumer with Consumer { -/// // Custom implementation of the message consumer. -/// } -/// ``` -@Deprecated('Please use `ConsumerMixin` instead. ' - 'This will be removed in v2.0.0') -mixin Consumer implements ConsumerInterface { - /// A registry of active message subscriptions. - final Registrar> _subscriptions = - Registrar>(); - - @override - Message? getLatestMessage(String queueId) => - MQClient.instance.getLatestMessage(queueId); - - @override - void subscribe({ - required String queueId, - required Function(Message) callback, - bool Function(Object)? filter, - }) { - try { - final messageStream = MQClient.instance.fetchQueue(queueId); - - final sub = filter != null - ? messageStream.listen((Message message) { - if (filter(message.payload)) { - callback(message); - } - }) - : messageStream.listen(callback); - - _subscriptions.register(queueId, sub); - } on IdAlreadyRegisteredException catch (_) { - throw ConsumerAlreadySubscribedException( - consumer: runtimeType.toString(), - queue: queueId, - ); - } - } - - @override - void unsubscribe({required String queueId}) { - _subscriptions.get(queueId).cancel(); - _subscriptions.unregister(queueId); - } - - @override - void pauseSubscription(String queueId) => _subscriptions.get(queueId).pause(); - - @override - void resumeSubscription(String queueId) => - _subscriptions.get(queueId).resume(); - - @override - void updateSubscription({ - required String queueId, - required Function(Message) callback, - bool Function(Object)? filter, - }) { - _subscriptions.get(queueId).cancel(); - _subscriptions.unregister(queueId); - subscribe( - queueId: queueId, - callback: callback, - filter: filter, - ); - } - - @override - void clearSubscriptions() { - for (final StreamSubscription sub in _subscriptions.getAll()) { - sub.cancel(); - } - - _subscriptions.clear(); - } -} diff --git a/sandbox/mqueue/lib/src/consumer/consumer.interface.dart b/sandbox/mqueue/lib/src/consumer/consumer.interface.dart deleted file mode 100644 index 6890099..0000000 --- a/sandbox/mqueue/lib/src/consumer/consumer.interface.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:angel3_mq/src/message/message.dart'; - -/// An abstract interface class defining the contract for a message consumer. -/// -/// The `ConsumerInterface` abstract interface class defines a contract for -/// classes that implement a message consumer. Implementing classes must -/// provide methods for subscribing and unsubscribing from queues, pausing and -/// resuming subscriptions, updating subscriptions, retrieving the -/// latest message from a queue, and clearing all subscriptions. -/// -/// Example: -/// ```dart -/// class MyConsumer implements ConsumerInterface { -/// // Custom implementation of the message consumer. -/// } -/// ``` -abstract interface class ConsumerInterface { - /// Subscribes to a queue to receive messages. - /// - /// The [queueId] parameter represents the ID of the queue to subscribe to. - /// The [callback] parameter is a function that will be invoked for each - /// received message. - /// The [filter] parameter is an optional function that can be used to filter - /// messages based on custom criteria. - void subscribe({ - required String queueId, - required Function(Message) callback, - bool Function(Object)? filter, - }); - - /// Unsubscribes from a previously subscribed queue. - /// - /// The [queueId] parameter represents the ID of the queue to unsubscribe - /// from. - void unsubscribe({required String queueId}); - - /// Pauses message subscription for a specified queue. - /// - /// The [queueId] parameter represents the ID of the queue to pause the - /// subscription. - void pauseSubscription(String queueId); - - /// Resumes a paused subscription for a specified queue. - /// - /// The [queueId] parameter represents the ID of the queue to resume the - /// subscription. - void resumeSubscription(String queueId); - - /// Updates an existing subscription with a new callback and/or filter. - /// - /// The [queueId] parameter represents the ID of the queue to update the - /// subscription. - /// The [callback] parameter is a new function that will be invoked for each - /// received message. - /// The [filter] parameter is an optional new filter function for message - /// filtering. - void updateSubscription({ - required String queueId, - required Function(Message) callback, - bool Function(Object)? filter, - }); - - /// Retrieves the latest message from a queue. - /// - /// The [queueId] parameter represents the ID of the queue to fetch the latest - /// message from. - /// - /// Returns the latest message from the specified queue or `null` if the queue - /// is empty. - Message? getLatestMessage(String queueId); - - /// Clears all active subscriptions, unsubscribing from all queues. - void clearSubscriptions(); -} diff --git a/sandbox/mqueue/lib/src/consumer/consumer.mixin.dart b/sandbox/mqueue/lib/src/consumer/consumer.mixin.dart deleted file mode 100644 index b3bc347..0000000 --- a/sandbox/mqueue/lib/src/consumer/consumer.mixin.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_mq/src/consumer/consumer.interface.dart'; -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:angel3_mq/src/core/registrar/simple_registrar.dart'; -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/mq/mq.dart'; - -/// A mixin implementing the `ConsumerInterface` for message consumption. -/// -/// The `ConsumerMixin` mixin provides a concrete implementation of the -/// `ConsumerInterface`for message consumption. It allows classes to easily -/// consume messages from specific queues by subscribing to them, handling -/// received messages, and managing subscriptions. -/// -/// Example: -/// ```dart -/// class MyMessageConsumer with ConsumerMixin { -/// // Custom implementation of the message consumer. -/// } -/// ``` -mixin ConsumerMixin implements ConsumerInterface { - /// A registry of active message subscriptions. - final Registrar> _subscriptions = - Registrar>(); - - @override - Message? getLatestMessage(String queueId) => - MQClient.instance.getLatestMessage(queueId); - - @override - void subscribe({ - required String queueId, - required Function(Message) callback, - bool Function(Object)? filter, - }) { - try { - final messageStream = MQClient.instance.fetchQueue(queueId); - - final sub = filter != null - ? messageStream.listen((Message message) { - if (filter(message.payload)) { - callback(message); - } - }) - : messageStream.listen(callback); - - _subscriptions.register(queueId, sub); - } on IdAlreadyRegisteredException catch (_) { - throw ConsumerAlreadySubscribedException( - consumer: runtimeType.toString(), - queue: queueId, - ); - } - } - - @override - void unsubscribe({required String queueId}) => _subscriptions - ..get(queueId).cancel() - ..unregister(queueId); - - @override - void pauseSubscription(String queueId) => _subscriptions.get(queueId).pause(); - - @override - void resumeSubscription(String queueId) => - _subscriptions.get(queueId).resume(); - - @override - void updateSubscription({ - required String queueId, - required Function(Message) callback, - bool Function(Object)? filter, - }) { - _subscriptions.get(queueId).cancel(); - _subscriptions.unregister(queueId); - subscribe( - queueId: queueId, - callback: callback, - filter: filter, - ); - } - - @override - void clearSubscriptions() { - for (final StreamSubscription sub in _subscriptions.getAll()) { - sub.cancel(); - } - - _subscriptions.clear(); - } -} diff --git a/sandbox/mqueue/lib/src/core/constants/enums.dart b/sandbox/mqueue/lib/src/core/constants/enums.dart deleted file mode 100644 index 582d02c..0000000 --- a/sandbox/mqueue/lib/src/core/constants/enums.dart +++ /dev/null @@ -1,22 +0,0 @@ -/// An enumeration representing different types of message exchanges. -/// -/// The [ExchangeType] enum defines various types of message exchanges that are -/// commonly used in messaging systems. Each type represents a specific behavior -/// for distributing messages to multiple queues or consumers. -/// -/// - `direct`: A direct exchange routes messages to queues based on a specified -/// routing key. -/// - `base`: The default exchange (unnamed) routes messages to queues using -/// their names. -/// - `fanout`: A fanout exchange routes messages to all connected queues, -/// ignoring routing keys. -enum ExchangeType { - /// Represents a direct message exchange. - direct, - - /// Represents the default exchange (unnamed). - base, - - /// Represents a fanout message exchange. - fanout, -} diff --git a/sandbox/mqueue/lib/src/core/constants/error_strings.dart b/sandbox/mqueue/lib/src/core/constants/error_strings.dart deleted file mode 100644 index 0ea4dc5..0000000 --- a/sandbox/mqueue/lib/src/core/constants/error_strings.dart +++ /dev/null @@ -1,99 +0,0 @@ -/// A utility class providing exception-related error messages. -/// -/// The `ExceptionStrings` class defines static methods that generate error -/// messages for various exception scenarios. These messages can be used to -/// provide descriptive error information in exception handling and debugging. -class ExceptionStrings { - /// Generates an error message when MQClient is not initialized. - /// - /// This message is used when attempting to use the MQClient before it has - /// been properly initialized using the `MQClient.initialize()` method. - static String mqClientNotInitialized() => - 'MQClient is not initialized. Please make sure to call ' - 'MQClient.initialize() first.'; - - /// Generates an error message for a Queue that is not registered. - /// - /// The [queueId] parameter represents the name of the unregistered queue. - static String queueNotRegistered(String queueId) => - 'Queue: $queueId is not registered.'; - - /// Generates an error message for a queue with active subscribers. - /// - /// The [queueId] parameter represents the ID of the queue with active - /// subscribers. - static String queueHasSubscribers(String queueId) => - 'Queue: $queueId has subscribers.'; - - /// Generates an error message for a queue with no name. - /// - /// This message is used when the name of the queue is not provided and is - /// null. - static String queueIdNull() => "Queue name can't be null."; - - /// Generates an error message for a required routing key. - /// - /// This message is used when a routing key is required for a specific - /// operation but is not provided. - static String routingKeyRequired() => 'Routing key is required.'; - - /// Generates an error message for a non-existent binding key. - /// - /// The [bindingKey] parameter represents the non-existent binding key. - static String bindingKeyNotFound(String bindingKey) => - 'The binding key "$bindingKey" was not found.'; - - /// Generates an error message for a missing binding key. - /// - /// This message is used when a binding operation expects a binding key to - static String bindingKeyRequired() => 'Binding key is required.'; - - /// Generates an error message for an exchange that is not registered. - /// - /// The [exchangeName] parameter represents the name of the unregistered - /// exchange. - static String exchangeNotRegistered(String exchangeName) => - 'Exchange: $exchangeName is not registered.'; - - /// Generates an error message for invalid exchange type. - static String invalidExchangeType() => 'Exchange type is invalid.'; - - /// Generates an error message for a consumer that is not subscribed to a - /// queue. - /// - /// The [consumerId] parameter represents the ID of the consumer. - /// The [queue] parameter represents the name of the queue. - static String consumerNotSubscribed(String consumerId, String queue) => - 'The consumer "$consumerId" is not subscribed to the queue "$queue".'; - - /// Generates an error message for a consumer that is already subscribed to - /// a queue. - /// - /// The [consumerId] parameter represents the ID of the consumer. - /// The [queue] parameter represents the name of the queue. - static String consumerAlreadySubscribed(String consumerId, String queue) => - 'The consumer "$consumerId" is already subscribed to the queue "$queue".'; - - /// Generates an error message for a consumer that is not registered. - /// - /// The [consumerId] parameter represents the ID of the consumer. - static String consumerNotRegistered(String consumerId) => - 'The consumer "$consumerId" is not registered.'; - - /// Generates an error message for a consumer that has active subscriptions. - /// - /// The [consumerId] parameter represents the ID of the consumer. - static String consumerHasSubscriptions(String consumerId) => - 'The consumer "$consumerId" has active subscriptions.'; - - /// Generates an error message for an ID that is already registered. - /// - /// The [id] parameter represents the ID that is already registered. - static String idAlreadyRegistered(String id) => - 'Id "$id" already registered.'; - - /// Generates an error message for an ID that is not registered. - /// - /// The [id] parameter represents the ID that is not registered. - static String idNotRegistered(String id) => 'Id "$id" not registered.'; -} diff --git a/sandbox/mqueue/lib/src/core/exceptions/binding_exceptions.dart b/sandbox/mqueue/lib/src/core/exceptions/binding_exceptions.dart deleted file mode 100644 index 898f5f1..0000000 --- a/sandbox/mqueue/lib/src/core/exceptions/binding_exceptions.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:angel3_mq/src/core/constants/error_strings.dart'; - -/// The [BindingException] class represents a base exception related to -/// bindings. -/// -/// It is used to handle exceptions that may occur when working with bindings, -/// such as when a binding key is not found or when a binding key is required -/// but not provided. -/// -/// Subclasses of [BindingException] can provide more specific information about -/// the nature of the exception. -abstract base class BindingException implements Exception { - /// Creates a new [BindingException] with the specified error [message]. - BindingException(this.message); - - /// The error message associated with the exception. - final String message; - - @override - String toString() => '$runtimeType: $message'; -} - -/// The [BindingKeyNotFoundException] class represents an exception that occurs -/// when a binding key is not found. -/// -/// This exception is thrown when attempting to access a binding key that does -/// not exist in the context of bindings. -final class BindingKeyNotFoundException extends BindingException { - /// Creates a new [BindingKeyNotFoundException] instance. - BindingKeyNotFoundException(String key) - : super(ExceptionStrings.bindingKeyNotFound(key)); -} - -/// The [BindingKeyRequiredException] class represents an exception that occurs -/// when a binding key is required but not provided. -/// -/// This exception is thrown when a binding operation expects a binding key to -/// be provided, but it is missing or empty. -final class BindingKeyRequiredException extends BindingException { - /// Creates a new [BindingKeyRequiredException] instance. - BindingKeyRequiredException() : super(ExceptionStrings.bindingKeyRequired()); -} diff --git a/sandbox/mqueue/lib/src/core/exceptions/consumer_exceptions.dart b/sandbox/mqueue/lib/src/core/exceptions/consumer_exceptions.dart deleted file mode 100644 index 9cd76cc..0000000 --- a/sandbox/mqueue/lib/src/core/exceptions/consumer_exceptions.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:angel3_mq/src/core/constants/error_strings.dart'; - -/// The [ConsumerException] class represents a base exception related to -/// consumers. -/// -/// It is used to handle exceptions that may occur when working with consumers, -/// such as when a consumer is not registered, is already subscribed to a queue, -/// is not subscribed to a queue when expected, or has active subscriptions. -/// -/// Subclasses of [ConsumerException] can provide more specific information -/// about the nature of the exception. -abstract base class ConsumerException implements Exception { - /// Creates a new [ConsumerException] with the specified error [message]. - ConsumerException(this.message); - - /// The error message associated with the exception. - final String message; - - @override - String toString() => '$runtimeType: $message'; -} - -/// The [ConsumerNotRegisteredException] class represents an exception that -/// occurs when a consumer is not registered. -/// -/// This exception is thrown when attempting to perform operations on a consumer -/// that has not been registered. -final class ConsumerNotRegisteredException extends ConsumerException { - /// Creates a new [ConsumerNotRegisteredException] instance with the - /// specified [consumer]. - ConsumerNotRegisteredException(String consumer) - : super(ExceptionStrings.consumerNotRegistered(consumer)); -} - -/// The [ConsumerAlreadySubscribedException] class represents an exception that -/// occurs when a consumer is already subscribed to a queue. -/// -/// This exception is thrown when attempting to subscribe a consumer to a queue -/// that it is already subscribed to. -final class ConsumerAlreadySubscribedException extends ConsumerException { - /// Creates a new [ConsumerAlreadySubscribedException] instance with the - /// specified [queue]. - ConsumerAlreadySubscribedException({ - required String consumer, - required String queue, - }) : super(ExceptionStrings.consumerAlreadySubscribed(consumer, queue)); -} - -/// The [ConsumerNotSubscribedException] class represents an exception that -/// occurs when a consumer is not subscribed to a queue when expected. -/// -/// This exception is thrown when an operation expects a consumer to be -/// subscribed to a queue, but the consumer is not. -final class ConsumerNotSubscribedException extends ConsumerException { - /// Creates a new [ConsumerNotSubscribedException] instance with the - /// specified [queue]. - ConsumerNotSubscribedException({ - required String consumer, - required String queue, - }) : super(ExceptionStrings.consumerNotSubscribed(consumer, queue)); -} - -/// The [ConsumerHasSubscriptionsException] class represents an exception that -/// occurs when a consumer has active subscriptions. -/// -/// This exception is thrown when an operation expects a consumer to have no -/// active subscriptions, but the consumer has active subscriptions. -final class ConsumerHasSubscriptionsException extends ConsumerException { - /// Creates a new [ConsumerHasSubscriptionsException] instance with the - /// specified [consumer]. - ConsumerHasSubscriptionsException(String consumer) - : super(ExceptionStrings.consumerHasSubscriptions(consumer)); -} diff --git a/sandbox/mqueue/lib/src/core/exceptions/exceptions.dart b/sandbox/mqueue/lib/src/core/exceptions/exceptions.dart deleted file mode 100644 index 77c94ba..0000000 --- a/sandbox/mqueue/lib/src/core/exceptions/exceptions.dart +++ /dev/null @@ -1,7 +0,0 @@ -export 'binding_exceptions.dart'; -export 'consumer_exceptions.dart'; -export 'exchange_exceptions.dart'; -export 'mq_client_exceptions.dart'; -export 'queue_exceptions.dart'; -export 'registrar_exceptions.dart'; -export 'routing_key_exceptions.dart'; diff --git a/sandbox/mqueue/lib/src/core/exceptions/exchange_exceptions.dart b/sandbox/mqueue/lib/src/core/exceptions/exchange_exceptions.dart deleted file mode 100644 index eb9336f..0000000 --- a/sandbox/mqueue/lib/src/core/exceptions/exchange_exceptions.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:angel3_mq/src/core/constants/error_strings.dart'; - -/// The [ExchangeException] class represents a base exception related to -/// exchanges. -/// -/// It is used to handle exceptions that may occur when working with exchanges, -/// such as when an exchange is not registered or when an invalid exchange type -/// is encountered. -/// -/// Subclasses of [ExchangeException] can provide more specific information -/// about the nature of the exception. -abstract base class ExchangeException implements Exception { - /// Creates a new [ExchangeException] with the specified error [message]. - ExchangeException(this.message); - - /// The error message associated with the exception. - final String message; - - @override - String toString() => '$runtimeType: $message'; -} - -/// The [ExchangeNotRegisteredException] class represents an exception that -/// occurs when an exchange is not registered. -/// -/// This exception is thrown when attempting to perform operations on an -/// exchange that has not been registered. -final class ExchangeNotRegisteredException extends ExchangeException { - /// Creates a new [ExchangeNotRegisteredException] instance with the - /// specified [exchangeName]. - ExchangeNotRegisteredException(String exchangeName) - : super(ExceptionStrings.exchangeNotRegistered(exchangeName)); -} - -/// The [InvalidExchangeTypeException] class represents an exception that occurs -/// when an invalid exchange type is encountered. -/// -/// This exception is thrown when an operation encounters an exchange type that -/// is not recognized or supported. -final class InvalidExchangeTypeException extends ExchangeException { - /// Creates a new [InvalidExchangeTypeException] instance. - InvalidExchangeTypeException() - : super(ExceptionStrings.invalidExchangeType()); -} diff --git a/sandbox/mqueue/lib/src/core/exceptions/mq_client_exceptions.dart b/sandbox/mqueue/lib/src/core/exceptions/mq_client_exceptions.dart deleted file mode 100644 index bc2c819..0000000 --- a/sandbox/mqueue/lib/src/core/exceptions/mq_client_exceptions.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:angel3_mq/src/core/constants/error_strings.dart'; - -/// The [MQClientException] class represents a base exception related to the -/// MQClient. -/// -/// It is used to handle exceptions that may occur when working with the -/// MQClient, such as when the MQClient is not initialized. -/// -/// Subclasses of [MQClientException] can provide more specific information -/// about the nature of the exception. -abstract base class MQClientException implements Exception { - /// Creates a new [MQClientException] with the specified error [message]. - MQClientException(this.message); - - /// The error message associated with the exception. - final String message; - - @override - String toString() => '$runtimeType: $message'; -} - -/// The [MQClientNotInitializedException] class represents an exception that -/// occurs when the MQClient is not initialized. -/// -/// This exception is thrown when attempting to use the MQClient before it has -/// been properly initialized using the `MQClient.initialize()` method. -final class MQClientNotInitializedException extends MQClientException { - /// Creates a new [MQClientNotInitializedException] instance. - MQClientNotInitializedException() - : super(ExceptionStrings.mqClientNotInitialized()); -} diff --git a/sandbox/mqueue/lib/src/core/exceptions/queue_exceptions.dart b/sandbox/mqueue/lib/src/core/exceptions/queue_exceptions.dart deleted file mode 100644 index 5a2947d..0000000 --- a/sandbox/mqueue/lib/src/core/exceptions/queue_exceptions.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:angel3_mq/src/core/constants/error_strings.dart'; - -/// The [QueueException] class represents a base exception related to queues. -/// -/// It is used to handle exceptions that may occur when working with queues, -/// such as when a queue is not registered or when there are subscribers to a -/// queue. -/// -/// Subclasses of [QueueException] can provide more specific information about -/// the nature of the exception. -abstract class QueueException implements Exception { - /// Creates a new [QueueException] with the specified error [message]. - QueueException(this.message); - - /// The error message associated with the exception. - final String message; - - @override - String toString() => '$runtimeType: $message'; -} - -/// The [QueueNotRegisteredException] class represents an exception that occurs -/// when a queue with a specific ID is not registered. -/// -/// This exception is thrown when attempting to perform an operation on an -/// unregistered queue. -final class QueueNotRegisteredException extends QueueException { - /// Creates a new [QueueNotRegisteredException] instance with the specified - /// [queueId]. - QueueNotRegisteredException(String queueId) - : super(ExceptionStrings.queueNotRegistered(queueId)); -} - -/// The [QueueHasSubscribersException] class represents an exception that occurs -/// when there are active subscribers to a queue. -/// -/// This exception is thrown when attempting to delete a queue that still has -/// subscribers listening to it. -final class QueueHasSubscribersException extends QueueException { - /// Creates a new [QueueHasSubscribersException] instance with the specified - /// [queueId]. - QueueHasSubscribersException(String queueId) - : super(ExceptionStrings.queueHasSubscribers(queueId)); -} - -/// The [QueueIdNullException] class represents an exception that occurs when -/// attempting to create a queue with a null name. -/// -/// This exception is thrown when the name of the queue is not provided and is -/// null. -final class QueueIdNullException extends QueueException { - /// Creates a new [QueueIdNullException] instance. - QueueIdNullException() : super(ExceptionStrings.queueIdNull()); -} diff --git a/sandbox/mqueue/lib/src/core/exceptions/registrar_exceptions.dart b/sandbox/mqueue/lib/src/core/exceptions/registrar_exceptions.dart deleted file mode 100644 index c5ef09b..0000000 --- a/sandbox/mqueue/lib/src/core/exceptions/registrar_exceptions.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:angel3_mq/src/core/constants/error_strings.dart'; - -/// The [RegistrarException] class represents a base exception related to -/// registrar operations. -/// -/// It is used to handle exceptions that may occur when working with registrar -/// objects, which are responsible for managing and registering items. -/// -/// Subclasses of [RegistrarException] can provide more specific information -/// about the nature of the exception. -abstract class RegistrarException implements Exception { - /// Creates a new [RegistrarException] with the specified error [message]. - RegistrarException(this.message); - - /// The error message associated with the exception. - final String message; - - @override - String toString() => '$runtimeType: $message'; -} - -/// The [IdAlreadyRegisteredException] class represents an exception that occurs -/// when attempting to register an ID that is already registered in a registrar. -/// -/// This exception is thrown when a duplicate ID is detected during the -/// registration process. -final class IdAlreadyRegisteredException extends RegistrarException { - /// Creates a new [IdAlreadyRegisteredException] instance with the specified - /// [id]. - IdAlreadyRegisteredException(String id) - : super(ExceptionStrings.idAlreadyRegistered(id)); -} - -/// The [IdNotRegisteredException] class represents an exception that occurs -/// when attempting to access an ID that is not registered in a registrar. -/// -/// This exception is thrown when an operation is performed on an unregistered -/// ID. -final class IdNotRegisteredException extends RegistrarException { - /// Creates a new [IdNotRegisteredException] instance with the specified [id]. - IdNotRegisteredException(String id) - : super(ExceptionStrings.idNotRegistered(id)); -} diff --git a/sandbox/mqueue/lib/src/core/exceptions/routing_key_exceptions.dart b/sandbox/mqueue/lib/src/core/exceptions/routing_key_exceptions.dart deleted file mode 100644 index 407b2f0..0000000 --- a/sandbox/mqueue/lib/src/core/exceptions/routing_key_exceptions.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:angel3_mq/src/core/constants/error_strings.dart'; - -/// The [RoutingKeyException] class represents a base exception related to -/// routing key operations. -/// -/// It is used to handle exceptions that may occur when working with routing -/// keys, which are used for message routing in message broker systems. -/// -/// Subclasses of [RoutingKeyException] can provide more specific information -/// about the nature of the exception. -abstract class RoutingKeyException implements Exception { - /// Creates a new [RoutingKeyException] with the specified error [message]. - RoutingKeyException(this.message); - - /// The error message associated with the exception. - final String message; - - @override - String toString() => '$runtimeType: $message'; -} - -/// The [RoutingKeyRequiredException] class represents an exception that occurs -/// when a routing key is required for a specific operation but is not provided. -/// -/// This exception is thrown when an operation expects a routing key to be -/// provided, but it is missing. -final class RoutingKeyRequiredException extends RoutingKeyException { - /// Creates a new [RoutingKeyRequiredException] instance. - RoutingKeyRequiredException() : super(ExceptionStrings.routingKeyRequired()); -} diff --git a/sandbox/mqueue/lib/src/core/registrar/simple_registrar.dart b/sandbox/mqueue/lib/src/core/registrar/simple_registrar.dart deleted file mode 100644 index 472d4c0..0000000 --- a/sandbox/mqueue/lib/src/core/registrar/simple_registrar.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; - -/// A generic registrar for managing and storing objects by their unique -/// identifiers. -/// -/// The [Registrar] class allows you to register, get, unregister, and manage -/// objects associated with unique identifiers (IDs). It provides a way to store -/// and access objects in a key-value fashion. -/// -/// Example: -/// ```dart -/// final registrar = Registrar(); -/// -/// // Register objects with unique IDs. -/// registrar.register('user_1', 'Alice'); -/// registrar.register('user_2', 'Bob'); -/// -/// // Get an object by its ID. -/// final user1 = registrar.get('user_1'); // Returns 'Alice' -/// -/// // Check if an object with a specific ID exists. -/// final hasUser2 = registrar.has('user_2'); // Returns true -/// -/// // Unregister an object by its ID. -/// registrar.unregister('user_1'); -/// -/// // Check the number of registered objects. -/// final count = registrar.count; // Returns 1 -/// ``` -final class Registrar { - /// A map to store objects with their associated IDs. - final Map _registry = {}; - - /// Registers an object with a unique ID. - /// - /// The [id] parameter represents the unique identifier for the object. - /// The [value] parameter represents the object to be registered. - /// - /// If an object with the same ID already exists, an - /// [IdAlreadyRegisteredException] is thrown. - void register(String id, T value) { - if (_registry.containsKey(id)) { - throw IdAlreadyRegisteredException(id); - } - _registry[id] = value; - } - - /// Gets an object by its unique ID. - /// - /// The [id] parameter represents the unique identifier of the object to - /// retrieve. - /// - /// If no object with the specified ID is found, an [IdNotRegisteredException] - /// is thrown. - T get(String id) { - if (!_registry.containsKey(id)) { - throw IdNotRegisteredException(id); - } - return _registry[id]!; - } - - /// Retrieves a list of all registered objects. - List getAll() => _registry.values.toList(); - - /// Unregisters an object by its unique ID. - /// - /// The [id] parameter represents the unique identifier of the object to - /// unregister. - /// - /// If no object with the specified ID is found, an [IdNotRegisteredException] - /// is thrown. - void unregister(String id) { - if (!_registry.containsKey(id)) { - throw IdNotRegisteredException(id); - } - _registry.remove(id); - } - - /// Clears the registrar, removing all registered objects. - void clear() => _registry.clear(); - - /// Checks if an object with a specific ID is registered. - /// - /// The [id] parameter represents the unique identifier to check. - /// - /// Returns `true` if an object with the specified ID is registered; - /// otherwise, `false`. - bool has(String id) => _registry.containsKey(id); - - /// Returns the count of registered objects. - int get count => _registry.length; - - @override - String toString() { - return ''' -Registrar( -\t${_registry.entries.map((e) => '${e.key}: ${e.value}').join(',\n\t')} - )'''; - } -} diff --git a/sandbox/mqueue/lib/src/exchange/default_exchange.dart b/sandbox/mqueue/lib/src/exchange/default_exchange.dart deleted file mode 100644 index 4c20164..0000000 --- a/sandbox/mqueue/lib/src/exchange/default_exchange.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:angel3_mq/src/binding/binding.dart'; -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:angel3_mq/src/exchange/exchange.base.dart'; -import 'package:angel3_mq/src/exchange/exchange_interface.dart'; -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; - -/// A class representing the default message exchange for message routing. -/// -/// The `DefaultExchange` class is a specific implementation of the -/// `BaseExchange` abstract base class, representing the default exchange. -/// It provides functionality for binding queues, forwarding messages based on -/// routing keys, and preventing unbinding from the default exchange. -/// -/// Example: -/// ```dart -/// final defaultExchange = DefaultExchange('default_exchange'); -/// -/// // Bind a queue to the default exchange. -/// final queue = Queue('my_queue'); -/// defaultExchange.bindQueue(queue: queue, bindingKey: 'my_routing_key'); -/// -/// // Forward a message to the default exchange using a routing key. -/// final message = Message( -/// headers: {'contentType': 'json', 'sender': 'Alice'}, -/// payload: {'text': 'Hello, World!'}, -/// timestamp: '2023-09-07T12:00:002', -/// ); -/// defaultExchange.forwardMessage(message, routingKey: 'my_routing_key'); -/// ``` -final class DefaultExchange extends BaseExchange implements ExchangeInterface { - /// Creates a new instance of the default exchange with the specified [id]. - /// - /// The [id] parameter represents the unique identifier for the default - /// exchange. - DefaultExchange(super.id); - - @override - void bindQueue({ - required Queue queue, - required String bindingKey, - }) => - (bindings.has(bindingKey) - ? bindings.get(bindingKey) - : _registerAndGetBinding(bindingKey)) - ..addQueue(queue); - - Binding _registerAndGetBinding(String bindingKey) { - bindings.register(bindingKey, Binding(bindingKey)); - return bindings.get(bindingKey); - } - - @override - void unbindQueue({ - required String queueId, - required String bindingKey, - }) { - (bindings.has(bindingKey) - ? bindings.get(bindingKey) - : throw BindingKeyNotFoundException(bindingKey)) - .removeQueue(queueId); - - if (!bindings.get(bindingKey).hasQueues()) { - bindings.unregister(bindingKey); - } - } - - @override - void forwardMessage({ - required Message message, - String? routingKey, - }) => - (bindings.has( - routingKey ?? (throw RoutingKeyRequiredException()), - ) - ? bindings.get(routingKey) - : throw BindingKeyNotFoundException(routingKey)) - .publishMessage(message); - - @override - void deleteQueue(String queueId) { - for (final binding in bindings.getAll()) { - binding.removeQueue(queueId); - } - } -} diff --git a/sandbox/mqueue/lib/src/exchange/direct_exchange.dart b/sandbox/mqueue/lib/src/exchange/direct_exchange.dart deleted file mode 100644 index 2560f29..0000000 --- a/sandbox/mqueue/lib/src/exchange/direct_exchange.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:angel3_mq/src/binding/binding.dart'; -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:angel3_mq/src/exchange/exchange.base.dart'; -import 'package:angel3_mq/src/exchange/exchange_interface.dart'; -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; - -/// A class representing a direct message exchange for message routing. -/// -/// The `DirectExchange` class is a specific implementation of the -/// `BaseExchange` abstract base class, representing a direct exchange. A -/// direct exchange routes messages to queues based on matching routing keys. -/// It provides functionality for binding queues, forwarding messages based on -/// routing keys, and unbinding queues from the direct exchange. -/// -/// Example: -/// ```dart -/// final directExchange = DirectExchange('my_direct_exchange'); -/// -/// // Bind queues to the direct exchange with different routing keys. -/// final queue1 = Queue('queue_1'); -/// final queue2 = Queue('queue_2'); -/// directExchange.bindQueue(queue: queue1, bindingKey: 'routing_key_1'); -/// directExchange.bindQueue(queue: queue2, bindingKey: 'routing_key_2'); -/// -/// // Forward a message with a matching routing key to the appropriate queue. -/// final message = Message( -/// headers: {'contentType': 'json', 'sender': 'Alice'}, -/// payload: {'text': 'Hello, World!'}, -/// timestamp: '2023-09-07T12:00:002', -/// ); -/// directExchange.forwardMessage(message, routingKey: 'routing_key_1'); -/// ``` -final class DirectExchange extends BaseExchange implements ExchangeInterface { - /// Creates a new instance of the direct exchange with the specified [id]. - /// - /// The [id] parameter represents the unique identifier for the direct - /// exchange. - DirectExchange(super.id); - - @override - void bindQueue({ - required Queue queue, - required String bindingKey, - }) => - (bindings.has(bindingKey) - ? bindings.get(bindingKey) - : _registerAndGetBinding(bindingKey)) - .addQueue(queue); - - Binding _registerAndGetBinding(String bindingKey) { - bindings.register(bindingKey, Binding(bindingKey)); - return bindings.get(bindingKey); - } - - @override - void unbindQueue({ - required String queueId, - required String bindingKey, - }) { - (bindings.has(bindingKey) - ? bindings.get(bindingKey) - : throw BindingKeyNotFoundException(bindingKey)) - .removeQueue(queueId); - - if (!bindings.get(bindingKey).hasQueues()) { - bindings.unregister(bindingKey); - } - } - - @override - void forwardMessage({ - required Message message, - String? routingKey, - }) => - (bindings.has( - routingKey ?? (throw RoutingKeyRequiredException()), - ) - ? bindings.get(routingKey) - : throw BindingKeyNotFoundException(routingKey)) - .publishMessage(message); - - @override - void deleteQueue(String queueId) { - for (final binding in bindings.getAll()) { - binding.removeQueue(queueId); - } - } -} diff --git a/sandbox/mqueue/lib/src/exchange/exchange.base.dart b/sandbox/mqueue/lib/src/exchange/exchange.base.dart deleted file mode 100644 index 18e8184..0000000 --- a/sandbox/mqueue/lib/src/exchange/exchange.base.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:angel3_mq/src/binding/binding.dart'; -import 'package:angel3_mq/src/core/registrar/simple_registrar.dart'; -import 'package:angel3_mq/src/exchange/exchange_interface.dart'; - -/// An abstract base class representing an exchange for message routing. -/// -/// The `BaseExchange` abstract base class defines the core functionality of a -/// message exchange for routing messages to specific queues or bindings. -/// -/// Example: -/// ```dart -/// class MyExchange extends BaseExchange { -/// // Custom implementation of the exchange. -/// } -/// ``` -abstract base class BaseExchange implements ExchangeInterface { - /// Creates a new exchange with the specified [id]. - /// - /// The [id] parameter represents the unique identifier for the exchange. - BaseExchange(this.id); - - /// The unique identifier for the exchange. - final String id; - - /// A registrar for managing bindings associated with the exchange. - Registrar bindings = Registrar(); -} diff --git a/sandbox/mqueue/lib/src/exchange/exchange_interface.dart b/sandbox/mqueue/lib/src/exchange/exchange_interface.dart deleted file mode 100644 index 638ca72..0000000 --- a/sandbox/mqueue/lib/src/exchange/exchange_interface.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; - -/// An abstract interface class defining the contract for managing exchanges. -/// -/// The `ExchangeInterface` defines a contract for classes that are responsible -/// for managing exchanges. Implementing classes must provide functionality for -/// binding queues to the exchange, unbinding queues from the exchange, -/// forwarding messages to queues or bindings, and removing queues from all -/// associated bindings. -/// -/// Example: -/// ```dart -/// class MyExchange implements ExchangeInterface { -/// // Custom implementation of the exchange. -/// } -/// ``` -abstract interface class ExchangeInterface { - /// Binds a queue to the exchange with a specific binding key. - /// - /// The [queue] parameter represents the queue to be bound to the exchange. - /// The [bindingKey] parameter represents the binding key for the queue. - void bindQueue({ - required Queue queue, - required String bindingKey, - }); - - /// Unbinds a queue from the exchange based on its ID and binding key. - /// - /// The [queueId] parameter represents the ID of the queue to be unbound. - /// The [bindingKey] parameter represents the binding key for the queue. - void unbindQueue({ - required String queueId, - required String bindingKey, - }); - - /// Forwards a message to queues or bindings based on the routing key. - /// - /// The [message] parameter represents the message to be forwarded. - /// The [routingKey] parameter represents the optional routing key to - /// determine the destination queues or bindings. - void forwardMessage({ - required Message message, - String? routingKey, - }); - - /// Removes a queue from all associated bindings. - /// - /// The [queueId] parameter represents the ID of the queue to be removed. - void deleteQueue(String queueId); -} diff --git a/sandbox/mqueue/lib/src/exchange/fanout_exchange.dart b/sandbox/mqueue/lib/src/exchange/fanout_exchange.dart deleted file mode 100644 index a7a225e..0000000 --- a/sandbox/mqueue/lib/src/exchange/fanout_exchange.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:angel3_mq/src/binding/binding.dart'; -import 'package:angel3_mq/src/exchange/exchange.base.dart'; -import 'package:angel3_mq/src/exchange/exchange_interface.dart'; -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; - -/// A class representing a fanout message exchange for message routing. -/// -/// The `FanoutExchange` class is a specific implementation of the -/// `BaseExchange` abstract base class, representing a fanout exchange. -/// A fanout exchange routes messages to all associated queues without -/// considering routing keys. It provides functionality for binding queues, -/// forwarding messages to all associated queues, and unbinding queues -/// from the fanout exchange. -/// -/// Example: -/// ```dart -/// final fanoutExchange = FanoutExchange('my_fanout_exchange'); -/// -/// // Bind multiple queues to the fanout exchange. -/// final queue1 = Queue('queue_1'); -/// final queue2 = Queue('queue_2'); -/// fanoutExchange.bindQueue(queue: queue1, bindingKey: 'binding_key_1'); -/// fanoutExchange.bindQueue(queue: queue2, bindingKey: 'binding_key_2'); -/// -/// // Forward a message to all associated queues in the fanout exchange. -/// final message = Message( -/// headers: {'contentType': 'json', 'sender': 'Alice'}, -/// payload: {'text': 'Hello, World!'}, -/// timestamp: '2023-09-07T12:00:002', -/// ); -/// fanoutExchange.forwardMessage(message); -/// ``` -final class FanoutExchange extends BaseExchange implements ExchangeInterface { - /// Creates a new instance of the fanout exchange with the specified [id]. - /// - /// The [id] parameter represents the unique identifier for the fanout - /// exchange. - FanoutExchange(super.id) { - bindings.register('', Binding('')); - } - - @override - void bindQueue({ - required Queue queue, - required String bindingKey, - }) => - bindings.get('').addQueue(queue); - - @override - void unbindQueue({ - required String queueId, - required String bindingKey, - }) => - bindings.get('').removeQueue(queueId); - - @override - void forwardMessage({ - required Message message, - String? routingKey, - }) => - bindings.get('').publishMessage(message); - - @override - void deleteQueue(String queueId) { - for (final binding in bindings.getAll()) { - binding.removeQueue(queueId); - } - } -} diff --git a/sandbox/mqueue/lib/src/message/message.base.dart b/sandbox/mqueue/lib/src/message/message.base.dart deleted file mode 100644 index 57e101d..0000000 --- a/sandbox/mqueue/lib/src/message/message.base.dart +++ /dev/null @@ -1,43 +0,0 @@ -/// Represents a base message with headers, payload, and an optional timestamp. -/// -/// A [BaseMessage] is a fundamental unit of data used in various messaging -/// systems. It typically contains metadata in the form of headers, the actual -/// payload, and an optional timestamp indicating when the message was created. -/// -/// The `headers` property is a map that can contain additional information -/// about the message, such as content type, sender, or any custom metadata. -/// -/// The `payload` property stores the main content of the message. It can be -/// of any type, allowing flexibility in the data that can be transmitted. -/// -/// The `timestamp` property, if provided, represents the time when the message -/// was created. It is formatted as an ISO 8601 string. -abstract class BaseMessage { - /// Creates a new `BaseMessage` with the specified headers, payload, and - /// timestamp. - /// - /// The [headers] parameter is a map that can contain additional information - /// about the message. It is optional and defaults to an empty map if not - /// provided. - /// - /// The [payload] parameter represents the main content of the message and is - /// required. - /// - /// The [timestamp] parameter is an optional ISO 8601 formatted timestamp - /// indicating when the message was created. If not provided, it will be - /// `null`. - BaseMessage( - Map? headers, - this.payload, - this.timestamp, - ) : headers = headers ?? {}; - - /// A map containing headers or metadata associated with the message. - final Map headers; - - /// The main content of the message. - final Object payload; - - /// An optional timestamp indicating when the message was created. - final String? timestamp; -} diff --git a/sandbox/mqueue/lib/src/message/message.dart b/sandbox/mqueue/lib/src/message/message.dart deleted file mode 100644 index 053ab13..0000000 --- a/sandbox/mqueue/lib/src/message/message.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:angel3_mq/src/message/message.base.dart'; -import 'package:uuid/uuid.dart'; - -/// Represents a message with headers, payload, and an optional timestamp. -/// -/// A [Message] is a specific type of message that extends the [BaseMessage] -/// class. -/// -/// Example: -/// ```dart -/// final message = Message( -/// headers: {'contentType': 'json', 'sender': 'Alice'}, -/// payload: {'text': 'Hello, World!'}, -/// timestamp: '2023-09-07T12:00:002', -/// ); -/// ``` -class Message extends BaseMessage { - /// Creates a new [Message] with the specified headers, payload, timestamp, and id. - /// - /// The [headers] parameter is a map that can contain additional information - /// about the message. It is optional and defaults to an empty map if not - /// provided. - /// - /// The [payload] parameter represents the main content of the message and is - /// required. - /// - /// The [timestamp] parameter is an optional ISO 8601 formatted timestamp - /// indicating when the message was created. If not provided, the current - /// timestamp will be used. - /// - /// The [id] parameter is an optional unique identifier for the message. - /// If not provided, a new UUID will be generated. - /// - /// Example: - /// ```dart - /// final message = Message( - /// headers: {'contentType': 'json', 'sender': 'Alice'}, - /// payload: {'text': 'Hello, World!'}, - /// timestamp: '2023-09-07T12:00:002', - /// id: '123e4567-e89b-12d3-a456-426614174000', - /// ); - /// ``` - Message({ - required Object payload, - Map? headers, - String? timestamp, - String? id, - }) : id = id ?? Uuid().v4(), - super( - headers, - payload, - timestamp ?? DateTime.now().toUtc().toIso8601String(), - ); - - /// A unique identifier for the message. - final String id; - - /// Returns a human-readable string representation of the message. - /// - /// Example: - /// ```dart - /// final message = Message( - /// headers: {'contentType': 'json', 'sender': 'Alice'}, - /// payload: {'text': 'Hello, World!'}, - /// timestamp: '2023-09-07T12:00:002', - /// ); - /// - /// print(message.toString()); - /// // Output: - /// // Message{ - /// // headers: {contentType: json, sender: Alice}, - /// // payload: {text: Hello, World!}, - /// // timestamp: 2023-09-07T12:00:002, - /// // } - /// ``` - @override - String toString() { - return ''' -Message{ - id: $id, - headers: $headers, - payload: $payload, - timestamp: $timestamp, -}'''; - } -} diff --git a/sandbox/mqueue/lib/src/mq/mq.base.dart b/sandbox/mqueue/lib/src/mq/mq.base.dart deleted file mode 100644 index 2acafd7..0000000 --- a/sandbox/mqueue/lib/src/mq/mq.base.dart +++ /dev/null @@ -1,14 +0,0 @@ -/// An abstract base class representing a message queue client. -/// -/// The `BaseMQClient` abstract base class defines the core functionality and -/// contract for implementing message queue clients. It serves as a foundation -/// for creating client implementations that interact with message queues for -/// sending and receiving messages. -/// -/// Example: -/// ```dart -/// class MyMQClient extends BaseMQClient { -/// // Custom implementation of the message queue client. -/// } -/// ``` -abstract class BaseMQClient {} diff --git a/sandbox/mqueue/lib/src/mq/mq.dart b/sandbox/mqueue/lib/src/mq/mq.dart deleted file mode 100644 index 1f48e6b..0000000 --- a/sandbox/mqueue/lib/src/mq/mq.dart +++ /dev/null @@ -1,256 +0,0 @@ -import 'package:angel3_mq/src/core/constants/enums.dart'; -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:angel3_mq/src/core/registrar/simple_registrar.dart'; -import 'package:angel3_mq/src/exchange/default_exchange.dart'; -import 'package:angel3_mq/src/exchange/direct_exchange.dart'; -import 'package:angel3_mq/src/exchange/exchange.base.dart'; -import 'package:angel3_mq/src/exchange/fanout_exchange.dart'; -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/mq/mq.base.dart'; -import 'package:angel3_mq/src/mq/mq.interface.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; - -/// A class representing a message queue client with various messaging -/// functionalities. -/// -/// The `MQClient` class is an implementation of both the `BaseMQClient` class -/// and the `MQClientInterface` interface. It provides features for interacting -/// with message queues, including declaring and managing queues and exchanges, -/// sending and receiving messages, and binding/unbinding queues to/from exchanges. -/// -/// Example: -/// ```dart -/// // Initialize the message queue client. -/// MQClient.initialize(); -/// -/// // Declare a queue and an exchange. -/// final queueId = MQClient.instance.declareQueue(); -/// final exchangeName = 'my_direct_exchange'; -/// MQClient.instance.declareExchange( -/// exchangeName: exchangeName, -/// exchangeType: ExchangeType.direct, -/// ); -/// -/// // Bind the queue to the exchange. -/// MQClient.instance.bindQueue( -/// queueId: queueId, -/// exchangeName: exchangeName, -/// ); -/// -/// // Send a message to the exchange. -/// final message = Message( -/// headers: {'contentType': 'json', 'sender': 'Alice'}, -/// payload: {'text': 'Hello, World!'}, -/// timestamp: '2023-09-07T12:00:002', -/// ); -/// MQClient.instance.sendMessage( -/// exchangeName: exchangeName, -/// message: message, -/// routingKey: queueId, -/// ); -/// -/// // Fetch messages from the queue. -/// final messageStream = MQClient.instance.fetchQueue(queueId); -/// messageStream.listen((message) { -/// print('Received message: $message'); -/// }); -/// ``` -class MQClient extends BaseMQClient implements MQClientInterface { - /// Private constructor to create the `MQClient` instance. - MQClient._internal() { - _exchanges.register('', DefaultExchange('')); - } - - /// Initializes the `MQClient` and creates a singleton instance. - /// - /// This method should be called before using the `MQClient`. - factory MQClient.initialize() => _instance ??= MQClient._internal(); - - /// Singleton instance of the `MQClient`. - static MQClient? _instance; - - /// Gets the singleton instance of the `MQClient`. - /// - /// Throws a [MQClientNotInitializedException] if the client has not been - /// initialized. - static MQClient get instance => - _instance ?? (throw MQClientNotInitializedException()); - - final Registrar _exchanges = Registrar(); - final Registrar _queues = Registrar(); - - @override - String declareQueue(String queueId) { - try { - _queues.register(queueId, Queue(queueId)); - - _exchanges.get('').bindQueue( - queue: _queues.get(queueId), - bindingKey: queueId, - ); - - return queueId; - } on IdAlreadyRegisteredException catch (_) { - return queueId; - } - } - - @override - void deleteQueue(String queueId) { - try { - final queue = _queues.get(queueId); - - if (queue.hasListeners()) { - throw QueueHasSubscribersException(queueId); - } else { - _deleteQueue(queueId); - } - } on IdNotRegisteredException catch (_) { - throw QueueNotRegisteredException(queueId); - } - } - - void _deleteQueue(String queueId) { - _queues.get(queueId).dispose(); - _exchanges.getAll().forEach( - (BaseExchange exchange) => exchange.deleteQueue(queueId), - ); - _queues.unregister(queueId); - } - - @override - Stream fetchQueue(String queueId) => _fetchQueue(queueId).dataStream; - - Queue _fetchQueue(String queueId) { - try { - return _queues.get(queueId); - } on IdNotRegisteredException catch (_) { - throw QueueNotRegisteredException(queueId); - } - } - - @override - List listQueues() => _queues - .getAll() - .map( - (Queue queue) => queue.id, - ) - .toList(); - - void deleteMessage(String queueId, Message message) { - try { - final queue = _fetchQueue(queueId); - queue.removeMessage(message); - } on QueueNotRegisteredException { - // Queue doesn't exist, so we can't delete the message - // We might want to log this or handle it in some way - } - } - - @override - void sendMessage({ - required Message message, - String? exchangeName, - String? routingKey, - }) { - try { - _exchanges - .get(exchangeName ?? '') - .forwardMessage(routingKey: routingKey, message: message); - } on IdNotRegisteredException catch (_) { - throw ExchangeNotRegisteredException(exchangeName ?? ''); - } - } - - @override - Message? getLatestMessage(String queueId) => - _fetchQueue(queueId).latestMessage; - - @override - void bindQueue({ - required String queueId, - required String exchangeName, - String? bindingKey, - }) { - try { - final exchange = _exchanges.get(exchangeName); - switch (exchange) { - case DirectExchange _: - if (bindingKey == null) { - throw BindingKeyRequiredException(); - } - exchange.bindQueue( - queue: _fetchQueue(queueId), - bindingKey: bindingKey, - ); - case FanoutExchange _: - exchange.bindQueue( - queue: _fetchQueue(queueId), - bindingKey: '', - ); - default: - return; - } - } on IdNotRegisteredException catch (_) { - throw ExchangeNotRegisteredException(exchangeName); - } - } - - @override - void unbindQueue({ - required String queueId, - required String exchangeName, - String? bindingKey, - }) { - try { - final exchange = _exchanges.get(exchangeName); - if (exchange.runtimeType == DirectExchange && bindingKey == null) { - throw BindingKeyRequiredException(); - } - exchange.unbindQueue( - queueId: queueId, - bindingKey: bindingKey ?? '', - ); - } on IdNotRegisteredException catch (_) { - throw ExchangeNotRegisteredException(exchangeName); - } - } - - @override - void declareExchange({ - required String exchangeName, - required ExchangeType exchangeType, - }) { - try { - switch (exchangeType) { - case ExchangeType.direct: - _exchanges.register(exchangeName, DirectExchange(exchangeName)); - case ExchangeType.fanout: - _exchanges.register(exchangeName, FanoutExchange(exchangeName)); - case ExchangeType.base: - throw InvalidExchangeTypeException(); - } - } on IdAlreadyRegisteredException catch (_) { - return; - } - } - - @override - void deleteExchange(String exchangeName) { - try { - _exchanges.unregister(exchangeName); - } catch (_) { - return; - } - } - - @override - void close() { - _queues.getAll().forEach( - (Queue queue) => queue.dispose(), - ); - _queues.clear(); - _exchanges.clear(); - _instance = null; - } -} diff --git a/sandbox/mqueue/lib/src/mq/mq.interface.dart b/sandbox/mqueue/lib/src/mq/mq.interface.dart deleted file mode 100644 index a1301c1..0000000 --- a/sandbox/mqueue/lib/src/mq/mq.interface.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:angel3_mq/src/core/constants/enums.dart'; -import 'package:angel3_mq/src/message/message.dart'; - -/// An abstract interface class defining the contract for a message queue -/// client. -/// -/// The `MQClientInterface` abstract interface class defines a contract for -/// classes that implement a message queue client. Implementing classes must -/// provide methods for fetching messages from a queue, sending messages to an -/// exchange, declaring queues and exchanges, deleting queues and exchanges, -/// binding and unbinding queues from exchanges, and more. -/// -/// Example: -/// ```dart -/// class MyMQClient implements MQClientInterface { -/// // Custom implementation of the message queue client. -/// } -/// ``` -abstract interface class MQClientInterface { - /// Declares a queue in the message queue system. - /// - /// The [queueId] parameter represents the optional ID for the queue. - /// - /// Returns the ID of the declared queue. - String declareQueue(String queueId); - - /// Deletes a queue from the message queue system. - /// - /// The [queueId] parameter represents the ID of the queue to be deleted. - void deleteQueue(String queueId); - - /// Fetches messages from a queue. - /// - /// The [queueId] parameter represents the ID of the queue to fetch messages - /// from. - /// - /// Returns a stream of messages from the specified queue. - Stream fetchQueue(String queueId); - - /// Retrieves the list of queues. - /// - /// Returns a list of queue IDs. - List listQueues(); - - /// Sends a message to an exchange for routing to queues. - /// - /// The [exchangeName] parameter represents the name of the exchange to send - /// the message to. - /// The [message] parameter represents the message to be sent. - /// The [routingKey] parameter represents the optional routing key for message - /// routing within the exchange. - void sendMessage({ - required Message message, - String? exchangeName, - String? routingKey, - }); - - /// Retrieves the latest message from a queue. - /// - /// The [queueId] parameter represents the ID of the queue to fetch the latest - /// message from. - /// - /// Returns the latest message from the specified queue or `null` if the queue - /// is empty. - Message? getLatestMessage(String queueId); - - /// Binds a queue to an exchange for message routing. - /// - /// The [queueId] parameter represents the ID of the queue to be bound. - /// The [exchangeName] parameter represents the name of the exchange to bind - /// to. - /// The [bindingKey] parameter represents the optional binding key for routing - /// messages to the queue within the exchange. - void bindQueue({ - required String queueId, - required String exchangeName, - String? bindingKey, - }); - - /// Unbinds a queue from an exchange to stop message routing. - /// - /// The [queueId] parameter represents the ID of the queue to be unbound. - /// The [exchangeName] parameter represents the name of the exchange to unbind - /// from. - /// The [bindingKey] parameter represents the optional binding key previously - /// used for binding. - void unbindQueue({ - required String queueId, - required String exchangeName, - String? bindingKey, - }); - - /// Declares an exchange in the message queue system. - /// - /// The [exchangeName] parameter represents the name of the exchange to be - /// declared. - /// The [exchangeType] parameter represents the type of exchange (e.g., - /// direct, fanout). - void declareExchange({ - required String exchangeName, - required ExchangeType exchangeType, - }); - - /// Deletes an exchange from the message queue system. - /// - /// The [exchangeName] parameter represents the name of the exchange to be - /// deleted. - void deleteExchange(String exchangeName); - - /// Closes the connection to the message queue system. - /// - /// This method should be called when the message queue client is no longer - /// needed. - void close(); -} diff --git a/sandbox/mqueue/lib/src/producer/producer.dart b/sandbox/mqueue/lib/src/producer/producer.dart deleted file mode 100644 index 2ec6bb5..0000000 --- a/sandbox/mqueue/lib/src/producer/producer.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'dart:async'; -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/mq/mq.dart'; -import 'package:angel3_mq/src/producer/producer.interface.dart'; - -/// A mixin implementing the `ProducerInterface` for message production. -/// -/// The `Producer` mixin provides a concrete implementation of the -/// `ProducerInterface` for message production. It allows classes to easily send -/// messages to exchanges, send RPC (Remote Procedure Call) messages, and set a -/// callback for handling push notifications. -/// -/// Example: -/// ```dart -/// class MyMessageProducer with Producer { -/// // Custom implementation of the message producer. -/// } -/// ``` -@Deprecated('Please use `ProducerMixin` instead. ' - 'This will be removed in v2.0.0') -mixin Producer implements ProducerInterface { - /// A callback function for handling push notifications (received messages). - Function(Message message)? _callback; - - @override - void sendMessage({ - required Object payload, - String? exchangeName, - Map? headers, - String? routingKey, - String? timestamp, - }) { - final newMessage = Message( - payload: payload, - headers: headers, - timestamp: timestamp, - ); - MQClient.instance.sendMessage( - exchangeName: exchangeName, - routingKey: routingKey, - message: newMessage, - ); - - _callback?.call(newMessage); - } - - @override - Future sendRPCMessage({ - required String processId, - required String exchangeName, - Map? args, - String? routingKey, - T Function(Object)? mapper, - String? timestamp, - }) async { - final Completer completer = - mapper == null ? Completer() : Completer(); - - final newMessage = Message( - payload: 'RPC', - headers: { - 'type': 'RPC', - 'processId': processId, - 'args': args, - 'completer': completer, - }, - timestamp: timestamp, - ); - - MQClient.instance.sendMessage( - exchangeName: exchangeName, - routingKey: routingKey, - message: newMessage, - ); - - if (mapper == null) { - _callback?.call(newMessage); - final res = await completer.future.then((value) => value); - return res; - } else { - _callback?.call(newMessage); - final rawData = await completer.future.then((value) => value); - final data = mapper(rawData); - return data; - } - } - - @override - void setPushCallback(Function(Message message) callback) => - _callback = callback; -} diff --git a/sandbox/mqueue/lib/src/producer/producer.interface.dart b/sandbox/mqueue/lib/src/producer/producer.interface.dart deleted file mode 100644 index 2fec00e..0000000 --- a/sandbox/mqueue/lib/src/producer/producer.interface.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:angel3_mq/src/message/message.dart'; - -/// An abstract interface class defining the contract for a message producer. -/// -/// The `ProducerInterface` abstract interface class defines a contract for -/// classes that implement a message producer. Implementing classes must provide -/// methods for sending messages to exchanges, sending RPC (Remote Procedure -/// Call) messages, and setting a callback for push notifications. -/// -/// Example: -/// ```dart -/// class MyProducer implements ProducerInterface { -/// // Custom implementation of the message producer. -/// } -/// ``` -abstract interface class ProducerInterface { - /// Sends a message to an exchange. - /// - /// The [payload] parameter represents the message payload to send. - /// The [exchangeName] parameter is the name of the exchange to send the - /// message to. - /// The [headers] parameter is an optional map of headers for the message. - /// The [routingKey] parameter is an optional routing key for the message. - void sendMessage({ - required Object payload, - required String exchangeName, - Map? headers, - String? routingKey, - }); - - /// Sends an RPC (Remote Procedure Call) message and awaits a response. - /// - /// The [processId] parameter is a unique identifier for the RPC request. - /// The [args] parameter is an optional map of arguments for the RPC request. - /// The [exchangeName] parameter is the name of the exchange for RPC - /// communication. - /// The [routingKey] parameter is an optional routing key for the RPC message. - /// The [mapper] parameter is an optional function to map the response - /// payload. - /// - /// Returns a future that completes with the response payload. - Future sendRPCMessage({ - required String processId, - required String exchangeName, - Map? args, - String? routingKey, - T Function(Object)? mapper, - }); - - /// Sets a callback function to be called after every 'sendMessage` or - /// `sendRPCMessage`. - /// - /// The [callback] parameter is a function that will be invoked when a push - /// notification (message) is received. - void setPushCallback(Function(Message message) callback); -} diff --git a/sandbox/mqueue/lib/src/producer/producer.mixin.dart b/sandbox/mqueue/lib/src/producer/producer.mixin.dart deleted file mode 100644 index 5953fbb..0000000 --- a/sandbox/mqueue/lib/src/producer/producer.mixin.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'dart:async'; -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/mq/mq.dart'; -import 'package:angel3_mq/src/producer/producer.interface.dart'; - -/// A mixin implementing the `ProducerInterface` for message production. -/// -/// The `ProducerMixin` mixin provides a concrete implementation of the -/// `ProducerInterface` for message production. It allows classes to easily send -/// messages to exchanges, send RPC (Remote Procedure Call) messages, and set a -/// callback for handling push notifications. -/// -/// Example: -/// ```dart -/// class MyMessageProducer with ProducerMixin { -/// // Custom implementation of the message producer. -/// } -/// ``` -mixin ProducerMixin implements ProducerInterface { - /// A callback function for handling push notifications (received messages). - Function(Message message)? _callback; - - @override - void sendMessage({ - required Object payload, - String? exchangeName, - Map? headers, - String? routingKey, - String? timestamp, - }) { - final newMessage = Message( - payload: payload, - headers: headers, - timestamp: timestamp, - ); - - MQClient.instance.sendMessage( - exchangeName: exchangeName, - routingKey: routingKey, - message: newMessage, - ); - - _callback?.call(newMessage); - } - - @override - Future sendRPCMessage({ - required String processId, - required String exchangeName, - Map? args, - String? routingKey, - T Function(Object)? mapper, - String? timestamp, - }) async { - final Completer completer = - mapper == null ? Completer() : Completer(); - - final newMessage = Message( - payload: 'RPC', - headers: { - 'type': 'RPC', - 'processId': processId, - 'args': args, - 'completer': completer, - }, - timestamp: timestamp, - ); - - MQClient.instance.sendMessage( - exchangeName: exchangeName, - routingKey: routingKey, - message: newMessage, - ); - - if (mapper == null) { - _callback?.call(newMessage); - final res = await completer.future.then((value) => value); - return res; - } else { - _callback?.call(newMessage); - final rawData = await completer.future.then((value) => value); - final data = mapper(rawData); - return data; - } - } - - @override - void setPushCallback(Function(Message message) callback) => - _callback = callback; -} diff --git a/sandbox/mqueue/lib/src/queue/data_stream.base.dart b/sandbox/mqueue/lib/src/queue/data_stream.base.dart deleted file mode 100644 index 5cb83f6..0000000 --- a/sandbox/mqueue/lib/src/queue/data_stream.base.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_mq/src/message/message.dart'; - -/// An abstract base class for data streams that produce [Message] objects. -/// -/// The `BaseDataStream` class provides the foundation for creating data -/// streams that emit [Message] objects to their listeners. It includes a -/// [StreamController] to manage the stream of messages and methods to enqueue -/// messages and dispose of the stream when it's no longer needed. -/// -/// Example: -/// ```dart -/// class MyDataStream extends BaseDataStream { -/// // Custom methods and logic specific to your data stream can be added here. -/// } -/// ``` -abstract class BaseDataStream { - /// A [StreamController] for broadcasting [Message] objects to listeners. - final StreamController _data = StreamController.broadcast(); - - /// Returns a [Stream] of [Message] objects from this data stream. - Stream get dataStream => _data.stream; - - /// The latest [Message] enqueued in the data stream. - /// - /// This property keeps track of the most recently enqueued message. - Message? _latestMessage; - - /// Exposes the [_latestMessage] property. - /// - /// This getter returns the most recently enqueued message. - Message? get latestMessage => _latestMessage; - - /// Enqueues a [Message] to be emitted by the data stream. - /// - /// The [message] parameter represents the [Message] to enqueue, and it - /// becomes the latest message in the stream. - void enqueue(Message message) { - _latestMessage = message; - _data.add(message); - } - - /// Closes the data stream, freeing up resources. - /// - /// This method should be called when the data stream is no longer needed - /// to prevent resource leaks. - void dispose() => _data.close(); - - /// Checks if there are any active listeners on the data stream. - /// - /// Returns `true` if there are active listeners, and `false` otherwise. - bool hasListeners() => _data.hasListener; -} diff --git a/sandbox/mqueue/lib/src/queue/queue.dart b/sandbox/mqueue/lib/src/queue/queue.dart deleted file mode 100644 index f03e00a..0000000 --- a/sandbox/mqueue/lib/src/queue/queue.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_mq/mq.dart'; -import 'package:angel3_mq/src/queue/data_stream.base.dart'; -import 'package:equatable/equatable.dart'; - -/// A class representing a queue for message streaming. -/// -/// The `Queue` class extends the [BaseDataStream] class and adds an -/// identifier, making it suitable for managing and streaming messages in a -/// queue-like fashion. -/// -/// Example: -/// ```dart -/// final myQueue = Queue('my_queue_id'); -/// -/// // Enqueue a message to the queue. -/// final message = Message( -/// headers: {'contentType': 'json', 'sender': 'Alice'}, -/// payload: {'text': 'Hello, World!'}, -/// timestamp: '2023-09-07T12:00:002', -/// ); -/// myQueue.enqueue(message); -/// -/// // Check if the queue has active listeners. -/// final hasListeners = myQueue.hasListeners(); -/// ``` -class Queue extends BaseDataStream with EquatableMixin { - Queue(this.id); - final String id; - final StreamController _controller = - StreamController.broadcast(); - Message? _latestMessage; - - void addMessage(Message message) { - _latestMessage = message; - _controller.add(message); - } - - Stream get dataStream => _controller.stream; - - Message? get latestMessage => _latestMessage; - - bool hasListeners() => _controller.hasListener; - - void dispose() { - _controller.close(); - } - - // New method to remove a message - void removeMessage(Message message) { - if (_latestMessage == message) { - _latestMessage = null; - } - // Note: We can't remove past messages from the stream, - // but we can prevent this message from being processed again in the future. - } - - List get props => [id]; -} diff --git a/sandbox/mqueue/pubspec.yaml b/sandbox/mqueue/pubspec.yaml deleted file mode 100644 index 7985374..0000000 --- a/sandbox/mqueue/pubspec.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: angel3_mq -description: DartMQ is a message-queue system that facilitates communication between different components in the application. -repository: https://github.com/N-Razzouk/dart_mq -issue_tracker: https://github.com/N-Razzouk/dart_mq/issues -homepage: https://github.com/N-Razzouk/dart_mq -documentation: https://github.com/N-Razzouk/dart_mq -version: 1.1.0 - -environment: - sdk: ">=3.0.0 <4.0.0" - -dependencies: - equatable: ^2.0.5 - uuid: ^4.5.1 - -dev_dependencies: - lints: ^3.0.0 - test: ^1.21.0 diff --git a/sandbox/mqueue/test/binding/binding_test.dart b/sandbox/mqueue/test/binding/binding_test.dart deleted file mode 100644 index 23d87c0..0000000 --- a/sandbox/mqueue/test/binding/binding_test.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:angel3_mq/mq.dart'; -import 'package:angel3_mq/src/binding/binding.dart'; -import 'package:angel3_mq/src/core/exceptions/queue_exceptions.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; -import 'package:test/test.dart'; - -void main() { - late Binding binding; - late Queue queue1; - late Queue queue2; - - setUp(() { - binding = Binding('my_binding'); - queue1 = Queue('queue_1'); - queue2 = Queue('queue_2'); - }); - - test('addQueue adds a queue to the binding', () { - binding.addQueue(queue1); - expect(binding.hasQueues(), isTrue); - }); - - test('removeQueue removes a queue from the binding', () { - binding.addQueue(queue1); - expect(binding.hasQueues(), isTrue); - - binding.removeQueue('queue_1'); - expect(binding.hasQueues(), isFalse); - }); - - test( - 'removeQueue throws QueueHasSubscribersException if queue has ' - 'subscribers', () { - final sub = queue1.dataStream.listen((_) {}); - - binding.addQueue(queue1); - - expect( - () => binding.removeQueue('queue_1'), - throwsA(isA()), - ); - - sub.cancel(); - }); - - test('publishMessage publishes a message to all associated queues', () { - binding - ..addQueue(queue1) - ..addQueue(queue2); - - final message = Message( - headers: {'contentType': 'json', 'sender': 'Alice'}, - payload: {'text': 'Hello, World!'}, - timestamp: '2023-09-07T12:00:002', - ); - - binding.publishMessage(message); - - expect(queue1.latestMessage, equals(message)); - expect(queue2.latestMessage, equals(message)); - }); - - test('hasQueues returns true if the binding has associated queues', () { - expect(binding.hasQueues(), isFalse); - - binding.addQueue(queue1); - expect(binding.hasQueues(), isTrue); - }); - - test('clear clears all queues from the binding', () { - binding - ..addQueue(queue1) - ..addQueue(queue2); - - expect(binding.hasQueues(), isTrue); - - binding.clear(); - expect(binding.hasQueues(), isFalse); - }); - - test('clear throws QueueHasSubscribersException if a queue has subscribers', - () { - final sub = queue1.dataStream.listen((_) {}); - - binding - ..addQueue(queue1) - ..addQueue(queue2); - - expect(binding.hasQueues(), isTrue); - - expect(() => binding.clear(), throwsA(isA())); - - expect(binding.hasQueues(), isTrue); - - sub.cancel(); - }); -} diff --git a/sandbox/mqueue/test/consumer/consumer_test.dart b/sandbox/mqueue/test/consumer/consumer_test.dart deleted file mode 100644 index f7fb078..0000000 --- a/sandbox/mqueue/test/consumer/consumer_test.dart +++ /dev/null @@ -1,333 +0,0 @@ -import 'package:angel3_mq/mq.dart'; -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:test/test.dart'; - -class MyMessageConsumer with ConsumerMixin { - // Custom implementation of the message consumer. -} - -void main() { - group('Consumer', () { - final consumer = MyMessageConsumer(); - setUpAll(() { - MQClient.initialize(); - - MQClient.instance.declareQueue('test-queue'); - }); - - test('subscribe should register a subscription and receive messages', - () async { - const queueId = 'test-queue'; - final message1 = Message(payload: 'Message 1'); - final message2 = Message(payload: 'Message 2'); - final callbackMessages = []; - - consumer.subscribe( - queueId: queueId, - callback: (message) { - callbackMessages.add(message); - }, - ); - - // Publish messages to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message1, - routingKey: queueId, - ); - MQClient.instance.sendMessage( - exchangeName: '', - message: message2, - routingKey: queueId, - ); - - await Future.delayed(Duration.zero); - - // Ensure that the callback was called with the expected messages - expect(callbackMessages, contains(message1)); - expect(callbackMessages, contains(message2)); - }); - - test('unsubscribe should cancel a subscription', () async { - const queueId = 'test-queue'; - final message1 = Message(payload: 'Message 1'); - final message2 = Message(payload: 'Message 2'); - final callbackMessages = []; - - consumer - ..clearSubscriptions() - ..subscribe( - queueId: queueId, - callback: (message) { - callbackMessages.add(message); - }, - ); - - // Publish messages to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message1, - routingKey: queueId, - ); - - await Future.delayed(Duration.zero); - - // Unsubscribe and ensure that the callback is not called - consumer.unsubscribe(queueId: queueId); - - // Publish messages to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message2, - routingKey: queueId, - ); - - expect(callbackMessages, contains(message1)); - expect(callbackMessages.length, equals(1)); - }); - - test('pauseSubscription should pause a subscription', () async { - const queueId = 'test-queue'; - final message1 = Message(payload: 'Message 1'); - final message2 = Message(payload: 'Message 2'); - final callbackMessages = []; - - consumer - ..clearSubscriptions() - ..subscribe( - queueId: queueId, - callback: (message) { - callbackMessages.add(message); - }, - ); - - // Publish messages to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message1, - routingKey: queueId, - ); - - await Future.delayed(Duration.zero); - - // Pause the subscription and ensure that the callback is not called - consumer.pauseSubscription(queueId); - - // Publish messages to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message2, - routingKey: queueId, - ); - - expect(callbackMessages, contains(message1)); - expect(callbackMessages.length, equals(1)); - }); - - test('resumeSubscription should resume a paused subscription', () async { - const queueId = 'test-queue'; - final message1 = Message(payload: 'Message 1'); - final message2 = Message(payload: 'Message 2'); - final callbackMessages = []; - - consumer - ..clearSubscriptions() - ..subscribe( - queueId: queueId, - callback: (message) { - callbackMessages.add(message); - }, - ); - - // Publish a message to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message1, - routingKey: queueId, - ); - - // Pause and then resume the subscription and ensure that the callback is - // called. - consumer - ..pauseSubscription(queueId) - ..resumeSubscription(queueId); - MQClient.instance.sendMessage( - exchangeName: '', - message: message2, - routingKey: queueId, - ); - - await Future.delayed(Duration.zero); - - expect(callbackMessages, contains(message1)); - expect(callbackMessages, contains(message2)); - expect(callbackMessages.length, equals(2)); - }); - - test( - 'updateSubscription should update a subscription with a new callback ' - 'and filter', () async { - const queueId = 'test-queue'; - final message1 = Message(payload: 'Message 1'); - final message2 = Message(payload: 'Message 2'); - final message3 = Message(payload: 'Message 3'); - final callbackMessages = []; - - consumer - ..clearSubscriptions() - ..subscribe( - queueId: queueId, - callback: (message) { - callbackMessages.add(message); - }, - ); - - // Publish messages to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message1, - routingKey: queueId, - ); - MQClient.instance.sendMessage( - exchangeName: '', - message: message2, - routingKey: queueId, - ); - - await Future.delayed(Duration.zero); - // Update the subscription with a new callback and filter - consumer.updateSubscription( - queueId: queueId, - callback: (message) { - if (message.payload == 'Message 2') { - callbackMessages.add(message); - } - }, - filter: (payload) => payload == 'Message 2', - ); - - // Publish another message to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message3, - routingKey: queueId, - ); - MQClient.instance.sendMessage( - exchangeName: '', - message: message2, - routingKey: queueId, - ); - - await Future.delayed(Duration.zero); - - // Ensure that the callback is only called with 'Message 2' - expect(callbackMessages, contains(message1)); - expect(callbackMessages, contains(message2)); - expect(callbackMessages.contains(message3), isFalse); - expect(callbackMessages.length, equals(3)); - }); - - test('clearSubscriptions should clear all subscriptions', () async { - const queueId = 'test-queue'; - final message1 = Message(payload: 'Message 1'); - final message2 = Message(payload: 'Message 2'); - final message3 = Message(payload: 'Message 3'); - final callbackMessages = []; - - consumer - ..clearSubscriptions() - ..subscribe( - queueId: queueId, - callback: (message) { - callbackMessages.add(message); - }, - ); - - // Publish messages to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message1, - routingKey: queueId, - ); - MQClient.instance.sendMessage( - exchangeName: '', - message: message2, - routingKey: queueId, - ); - - await Future.delayed(Duration.zero); - // Update the subscription with a new callback and filter - consumer.clearSubscriptions(); - - // Publish another message to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message3, - routingKey: queueId, - ); - - await Future.delayed(Duration.zero); - - // Ensure that the callback is only called on the first two messages. - expect(callbackMessages, contains(message1)); - expect(callbackMessages, contains(message2)); - expect(callbackMessages.contains(message3), isFalse); - expect(callbackMessages.length, equals(2)); - }); - - test('getLatestMessage should return the latest message from a queue', - () async { - const queueId = 'test-queue'; - final message1 = Message(payload: 'Message 1'); - final message2 = Message(payload: 'Message 2'); - final message3 = Message(payload: 'Message 3'); - - consumer - ..clearSubscriptions() - ..subscribe( - queueId: queueId, - callback: (_) {}, - ); - - // Publish messages to the queue - MQClient.instance.sendMessage( - exchangeName: '', - message: message1, - routingKey: queueId, - ); - MQClient.instance.sendMessage( - exchangeName: '', - message: message2, - routingKey: queueId, - ); - MQClient.instance.sendMessage( - exchangeName: '', - message: message3, - routingKey: queueId, - ); - - await Future.delayed(Duration.zero); - - // Get the latest message - final latestMessage = consumer.getLatestMessage(queueId); - - // Ensure that the latest message is 'Message 3' - expect(latestMessage, equals(message3)); - }); - - test( - 'subscribing to a queue that has already been subscribed to throws an ' - 'error.', () { - const queueId = 'test-queue'; - - consumer - ..clearSubscriptions() - ..subscribe(queueId: queueId, callback: (_) {}); - - expect( - () => consumer.subscribe(queueId: queueId, callback: (_) {}), - throwsA(isA()), - ); - }); - }); -} diff --git a/sandbox/mqueue/test/core/exceptions/binding_exceptions_test.dart b/sandbox/mqueue/test/core/exceptions/binding_exceptions_test.dart deleted file mode 100644 index 8d0e1bd..0000000 --- a/sandbox/mqueue/test/core/exceptions/binding_exceptions_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:test/test.dart'; - -void main() { - group('BindingException', () { - test('BindingKeyNotFoundException', () { - final exception = BindingKeyNotFoundException('test-key'); - expect(exception.toString(), contains('BindingKeyNotFoundException')); - expect( - exception.toString(), - contains( - 'BindingKeyNotFoundException:' - ' The binding key "test-key" was not found.', - ), - ); - }); - - test('BindingKeyRequiredException', () { - final exception = BindingKeyRequiredException(); - expect(exception.toString(), contains('BindingKeyRequiredException')); - expect(exception.toString(), contains('Binding key is required')); - }); - }); -} diff --git a/sandbox/mqueue/test/core/exceptions/consumer_exceptions_test.dart b/sandbox/mqueue/test/core/exceptions/consumer_exceptions_test.dart deleted file mode 100644 index 0e302d2..0000000 --- a/sandbox/mqueue/test/core/exceptions/consumer_exceptions_test.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:test/test.dart'; - -void main() { - group('ConsumerException', () { - test('ConsumerNotRegisteredException', () { - final exception = ConsumerNotRegisteredException('Alice'); - expect(exception.toString(), contains('ConsumerNotRegisteredException')); - expect( - exception.toString(), - contains('ConsumerNotRegisteredException: The consumer "Alice" is not ' - 'registered.'), - ); - }); - - test('ConsumerAlreadySubscribedException', () { - final exception = ConsumerAlreadySubscribedException( - consumer: 'NewsConsumer', - queue: 'NewsQueue', - ); - expect( - exception.toString(), - contains('ConsumerAlreadySubscribedException'), - ); - expect( - exception.toString(), - contains( - 'ConsumerAlreadySubscribedException: The consumer "NewsConsumer" ' - 'is already subscribed to the queue "NewsQueue".'), - ); - }); - - test('ConsumerNotSubscribedException', () { - final exception = ConsumerNotSubscribedException( - consumer: 'WeatherConsumer', - queue: 'WeatherQueue', - ); - expect(exception.toString(), contains('ConsumerNotSubscribedException')); - expect( - exception.toString(), - contains( - 'ConsumerNotSubscribedException: The consumer "WeatherConsumer" ' - 'is not subscribed to the queue "WeatherQueue".'), - ); - }); - - test('ConsumerHasSubscriptionsException', () { - final exception = ConsumerHasSubscriptionsException('Bob'); - expect( - exception.toString(), - contains('ConsumerHasSubscriptionsException'), - ); - expect( - exception.toString(), - contains('ConsumerHasSubscriptionsException: The consumer "Bob" has ' - 'active subscriptions.'), - ); - }); - }); -} diff --git a/sandbox/mqueue/test/core/exceptions/exchange_exceptions_test.dart b/sandbox/mqueue/test/core/exceptions/exchange_exceptions_test.dart deleted file mode 100644 index da20214..0000000 --- a/sandbox/mqueue/test/core/exceptions/exchange_exceptions_test.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:test/test.dart'; - -void main() { - group('ExchangeException', () { - test('ExchangeNotRegisteredException', () { - final exception = ExchangeNotRegisteredException('NewsExchange'); - expect(exception.toString(), contains('ExchangeNotRegisteredException')); - expect( - exception.toString(), - contains('Exchange: NewsExchange is not registered'), - ); - }); - - test('InvalidExchangeTypeException', () { - final exception = InvalidExchangeTypeException(); - expect(exception.toString(), contains('InvalidExchangeTypeException')); - expect(exception.toString(), contains('Exchange type is invalid.')); - }); - }); -} diff --git a/sandbox/mqueue/test/core/exceptions/mq_client_exceptions_test.dart b/sandbox/mqueue/test/core/exceptions/mq_client_exceptions_test.dart deleted file mode 100644 index d38a122..0000000 --- a/sandbox/mqueue/test/core/exceptions/mq_client_exceptions_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:test/test.dart'; - -void main() { - group('MQClientException', () { - test('MQClientNotInitializedException', () { - final exception = MQClientNotInitializedException(); - expect(exception.toString(), contains('MQClientNotInitializedException')); - expect( - exception.toString(), - contains('MQClientNotInitializedException: MQClient is not ' - 'initialized. Please make sure to call MQClient.initialize() ' - 'first.'), - ); - }); - }); -} diff --git a/sandbox/mqueue/test/core/exceptions/queue_exceptions_test.dart b/sandbox/mqueue/test/core/exceptions/queue_exceptions_test.dart deleted file mode 100644 index a0be43a..0000000 --- a/sandbox/mqueue/test/core/exceptions/queue_exceptions_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:test/test.dart'; - -void main() { - group('QueueException', () { - test('QueueNotRegisteredException', () { - final exception = QueueNotRegisteredException('my_queue_id'); - expect(exception.toString(), contains('QueueNotRegisteredException')); - expect( - exception.toString(), - contains('Queue: my_queue_id is not registered'), - ); - }); - - test('QueueHasSubscribersException', () { - final exception = QueueHasSubscribersException('my_queue_id'); - expect(exception.toString(), contains('QueueHasSubscribersException')); - expect( - exception.toString(), - contains('Queue: my_queue_id has subscribers'), - ); - }); - - test('QueueIdNullException', () { - final exception = QueueIdNullException(); - expect(exception.toString(), contains('QueueIdNullException')); - expect(exception.toString(), contains("Queue name can't be null")); - }); - }); -} diff --git a/sandbox/mqueue/test/core/exceptions/registrar_exceptions_test.dart b/sandbox/mqueue/test/core/exceptions/registrar_exceptions_test.dart deleted file mode 100644 index 8f1d3f9..0000000 --- a/sandbox/mqueue/test/core/exceptions/registrar_exceptions_test.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:test/test.dart'; - -void main() { - group('RegistrarException', () { - test('IdAlreadyRegisteredException', () { - final exception = IdAlreadyRegisteredException('my_id'); - expect(exception.toString(), contains('IdAlreadyRegisteredException')); - expect( - exception.toString(), - contains('IdAlreadyRegisteredException: Id ' - '"my_id" already registered'), - ); - }); - - test('IdNotRegisteredException', () { - final exception = IdNotRegisteredException('my_id'); - expect(exception.toString(), contains('IdNotRegisteredException')); - expect( - exception.toString(), - contains('IdNotRegisteredException: Id "my_id" not registered.'), - ); - }); - }); -} diff --git a/sandbox/mqueue/test/core/exceptions/routing_key_exceptionss_test.dart b/sandbox/mqueue/test/core/exceptions/routing_key_exceptionss_test.dart deleted file mode 100644 index de8f389..0000000 --- a/sandbox/mqueue/test/core/exceptions/routing_key_exceptionss_test.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:test/test.dart'; - -void main() { - group('RoutingKeyException', () { - test('RoutingKeyRequiredException', () { - final exception = RoutingKeyRequiredException(); - expect(exception.toString(), contains('RoutingKeyRequiredException')); - expect(exception.toString(), contains('Routing key is required')); - }); - }); -} diff --git a/sandbox/mqueue/test/core/registrar/simple_registrar_test.dart b/sandbox/mqueue/test/core/registrar/simple_registrar_test.dart deleted file mode 100644 index 975d699..0000000 --- a/sandbox/mqueue/test/core/registrar/simple_registrar_test.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:angel3_mq/src/core/registrar/simple_registrar.dart'; -import 'package:test/test.dart'; - -void main() { - late Registrar registrar; - - setUp(() { - registrar = Registrar(); - }); - - test('register and get objects', () { - registrar - ..register('user_1', 'Alice') - ..register('user_2', 'Bob'); - - expect(registrar.get('user_1'), equals('Alice')); - expect(registrar.get('user_2'), equals('Bob')); - }); - - test('register throws IdAlreadyRegisteredException for duplicate IDs', () { - registrar.register('user_1', 'Alice'); - expect( - () => registrar.register('user_1', 'Another Alice'), - throwsA(const TypeMatcher()), - ); - }); - - test('get throws IdNotRegisteredException for unknown IDs', () { - expect( - () => registrar.get('unknown_id'), - throwsA(const TypeMatcher()), - ); - }); - - test('getAll returns a list of all registered objects', () { - registrar - ..register('user_1', 'Alice') - ..register('user_2', 'Bob'); - - final allObjects = registrar.getAll(); - - expect(allObjects, contains('Alice')); - expect(allObjects, contains('Bob')); - }); - - test('unregister removes objects', () { - registrar - ..register('user_1', 'Alice') - ..register('user_2', 'Bob') - ..unregister('user_1'); - - expect( - () => registrar.get('user_1'), - throwsA(const TypeMatcher()), - ); - expect(registrar.get('user_2'), equals('Bob')); - }); - - test('unregister throws IdNotRegisteredException for unknown IDs', () { - expect( - () => registrar.unregister('unknown_id'), - throwsA(const TypeMatcher()), - ); - }); - - test('clear removes all registered objects', () { - registrar - ..register('user_1', 'Alice') - ..register('user_2', 'Bob') - ..clear(); - - expect(registrar.count, equals(0)); - }); - - test('has checks if an object is registered', () { - registrar.register('user_1', 'Alice'); - - expect(registrar.has('user_1'), isTrue); - expect(registrar.has('user_2'), isFalse); - }); - - test('count returns the number of registered objects', () { - registrar - ..register('user_1', 'Alice') - ..register('user_2', 'Bob'); - - expect(registrar.count, equals(2)); - }); - - test('toString returns a formatted string representation of the registrar', - () { - registrar - ..register('user_1', 'Alice') - ..register('user_2', 'Bob'); - - const expectedString = ''' -Registrar( -\tuser_1: Alice, -\tuser_2: Bob - )'''; - - expect(registrar.toString(), equals(expectedString)); - }); -} diff --git a/sandbox/mqueue/test/exchange/default_exchange_test.dart b/sandbox/mqueue/test/exchange/default_exchange_test.dart deleted file mode 100644 index 547f9e1..0000000 --- a/sandbox/mqueue/test/exchange/default_exchange_test.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:angel3_mq/mq.dart'; -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:angel3_mq/src/exchange/default_exchange.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; -import 'package:test/test.dart'; - -void main() { - late DefaultExchange defaultExchange; - late Queue queue; - late Message message; - - setUp(() { - defaultExchange = DefaultExchange('default_exchange'); - queue = Queue('my_queue'); - message = Message( - headers: {'contentType': 'json', 'sender': 'Alice'}, - payload: {'text': 'Hello, World!'}, - timestamp: '2023-09-07T12:00:002', - ); - }); - - test('bindQueue binds a queue to the default exchange with a binding key', - () { - defaultExchange.bindQueue(queue: queue, bindingKey: 'my_routing_key'); - expect(defaultExchange.bindings.has('my_routing_key'), isTrue); - }); - - test( - 'unbindQueue throws an exception when attempting to unbind from the ' - 'default exchange', () { - expect( - () => defaultExchange.unbindQueue( - queueId: 'my_queue_id', - bindingKey: 'my_routing_key', - ), - throwsA(isA()), - ); - }); - - test('unbindQueue unbinds a queue from the default exchange', () { - defaultExchange - ..bindQueue(queue: queue, bindingKey: 'my_routing_key') - ..unbindQueue( - queueId: queue.id, - bindingKey: 'my_routing_key', - ); - expect(defaultExchange.bindings.has('my_routing_key'), isFalse); - }); - - test( - 'forwardMessage forwards a message to the default exchange using a ' - 'routing key', () { - defaultExchange - ..bindQueue(queue: queue, bindingKey: 'my_routing_key') - ..forwardMessage(message: message, routingKey: 'my_routing_key'); - expect(queue.latestMessage, equals(message)); - }); - - test( - 'forwardMessage throws BindingKeyNotFoundException when routing key is ' - 'not found', () { - expect( - () => defaultExchange.forwardMessage( - message: message, - routingKey: 'non_existent_routing_key', - ), - throwsA(isA()), - ); - }); - - test( - 'forwardMessage throws RoutingKeyRequiredException when routing key is ' - 'null', () { - expect( - () => defaultExchange.forwardMessage(message: message), - throwsA(isA()), - ); - }); -} diff --git a/sandbox/mqueue/test/exchange/direct_exchange_test.dart b/sandbox/mqueue/test/exchange/direct_exchange_test.dart deleted file mode 100644 index 0f0de29..0000000 --- a/sandbox/mqueue/test/exchange/direct_exchange_test.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:angel3_mq/mq.dart'; -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:angel3_mq/src/exchange/direct_exchange.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; -import 'package:test/test.dart'; - -void main() { - late DirectExchange directExchange; - late Queue queue1; - late Queue queue2; - late Message message; - - setUp(() { - directExchange = DirectExchange('my_direct_exchange'); - queue1 = Queue('queue_1'); - queue2 = Queue('queue_2'); - message = Message( - headers: {'contentType': 'json', 'sender': 'Alice'}, - payload: {'text': 'Hello, World!'}, - timestamp: '2023-09-07T12:00:002', - ); - }); - - test('bindQueue binds a queue to the direct exchange with a binding key', () { - directExchange.bindQueue(queue: queue1, bindingKey: 'routing_key_1'); - expect(directExchange.bindings.has('routing_key_1'), isTrue); - }); - - test( - 'bindQueue binds a queue to the direct exchange with a binding key that ' - 'already exists.', () { - directExchange - ..bindQueue(queue: queue1, bindingKey: 'routing_key_1') - ..bindQueue(queue: queue2, bindingKey: 'routing_key_1'); - expect(directExchange.bindings.has('routing_key_1'), isTrue); - }); - - test( - 'unbindQueue unbinds a queue from the direct exchange with a binding key', - () { - directExchange - ..bindQueue(queue: queue1, bindingKey: 'routing_key_1') - ..unbindQueue(queueId: queue1.id, bindingKey: 'routing_key_1'); - expect(directExchange.bindings.has('routing_key_1'), isFalse); - }); - - test( - 'forwardMessage forwards a message to the direct exchange using a ' - 'routing key', () { - directExchange - ..bindQueue(queue: queue1, bindingKey: 'routing_key_1') - ..forwardMessage(message: message, routingKey: 'routing_key_1'); - expect(queue1.latestMessage, equals(message)); - }); - - test( - 'forwardMessage throws BindingKeyNotFoundException when routing key is ' - 'not found', () { - expect( - () => directExchange.forwardMessage( - message: message, - routingKey: 'non_existent_routing_key', - ), - throwsA(isA()), - ); - }); - - test( - 'forwardMessage throws RoutingKeyRequiredException when routing key is ' - 'null', () { - expect( - () => directExchange.forwardMessage(message: message), - throwsA(isA()), - ); - }); - - test( - 'unbindQueue throws BindingKeyNotFoundException when attempting to ' - 'unbind with an invalid binding key', () { - expect( - () => directExchange.unbindQueue( - queueId: 'queue_id', - bindingKey: 'invalid_binding_key', - ), - throwsA(isA()), - ); - }); -} diff --git a/sandbox/mqueue/test/exchange/fanout_exchange_test.dart b/sandbox/mqueue/test/exchange/fanout_exchange_test.dart deleted file mode 100644 index 3332216..0000000 --- a/sandbox/mqueue/test/exchange/fanout_exchange_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:angel3_mq/src/exchange/fanout_exchange.dart'; -import 'package:angel3_mq/src/message/message.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; -import 'package:test/test.dart'; - -void main() { - group('FanoutExchange', () { - test('bindQueue should add a queue to the exchange', () { - final fanoutExchange = FanoutExchange('my_fanout_exchange'); - final queue1 = Queue('queue_1'); - final queue2 = Queue('queue_2'); - - fanoutExchange - ..bindQueue(queue: queue1, bindingKey: 'binding_key_1') - ..bindQueue(queue: queue2, bindingKey: 'binding_key_2'); - - expect(fanoutExchange.bindings.get('').hasQueues(), isTrue); - }); - - test('unbindQueue should remove a queue from the exchange', () { - final fanoutExchange = FanoutExchange('my_fanout_exchange'); - final queue1 = Queue('queue_1'); - final queue2 = Queue('queue_2'); - - fanoutExchange - ..bindQueue(queue: queue1, bindingKey: 'binding_key_1') - ..bindQueue(queue: queue2, bindingKey: 'binding_key_2') - ..unbindQueue(queueId: 'queue_1', bindingKey: 'binding_key_1') - ..unbindQueue(queueId: 'queue_2', bindingKey: 'binding_key_2'); - - expect(fanoutExchange.bindings.get('').hasQueues(), isFalse); - }); - - test('forwardMessage should forward a message to all associated queues', - () { - final fanoutExchange = FanoutExchange('my_fanout_exchange'); - final queue1 = Queue('queue_1'); - final queue2 = Queue('queue_2'); - - fanoutExchange - ..bindQueue(queue: queue1, bindingKey: 'binding_key_1') - ..bindQueue(queue: queue2, bindingKey: 'binding_key_2'); - - final message = Message( - headers: {'contentType': 'json', 'sender': 'Alice'}, - payload: {'text': 'Hello, World!'}, - timestamp: '2023-09-07T12:00:002', - ); - - fanoutExchange.forwardMessage(message: message); - - expect(queue1.latestMessage, equals(message)); - expect(queue2.latestMessage, equals(message)); - }); - - test('removeQueue removes a queue from all bindings', () { - final queue1 = Queue('queue_1'); - - final fanoutExchange = FanoutExchange('my_fanout_exchange') - ..bindQueue(queue: queue1, bindingKey: '') - ..unbindQueue( - queueId: queue1.id, - bindingKey: '', - ); - - expect(fanoutExchange.bindings.get('').hasQueues(), isFalse); - }); - }); -} diff --git a/sandbox/mqueue/test/message/message.base_test.dart b/sandbox/mqueue/test/message/message.base_test.dart deleted file mode 100644 index 86cc658..0000000 --- a/sandbox/mqueue/test/message/message.base_test.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:angel3_mq/mq.dart'; -import 'package:test/test.dart'; - -void main() { - group('BaseMessage', () { - test('Creating a BaseMessage', () { - // Arrange - final headers = {'content-type': 'text/plain'}; - const payload = 'Hello, World!'; - const timestamp = '2023-09-07T12:00:002'; - - // Act - final baseMessage = - Message(payload: payload, headers: headers, timestamp: timestamp); - - // Assert - expect(baseMessage.headers, equals(headers)); - expect(baseMessage.payload, equals(payload)); - expect(baseMessage.timestamp, equals(timestamp)); - }); - - test('Creating a BaseMessage without headers and timestamp', () { - // Arrange - const payload = 'Hello, World!'; - - // Act - final baseMessage = Message( - payload: payload, - ); - - // Assert - expect(baseMessage.headers, isEmpty); - expect(baseMessage.payload, equals(payload)); - expect(baseMessage.timestamp, isNotNull); - }); - - test('toString function.', () { - // Arrange - final headers = {'content-type': 'text/plain'}; - const payload = 'Hello, World!'; - const timestamp = '2023-09-07T12:00:002'; - - // Act - final baseMessage = - Message(payload: payload, headers: headers, timestamp: timestamp); - - // Assert - expect( - baseMessage.toString(), - equals(''' -Message{ - headers: $headers, - payload: $payload, - timestamp: $timestamp, - }'''), - ); - }); - }); -} diff --git a/sandbox/mqueue/test/mq/mq_test.dart b/sandbox/mqueue/test/mq/mq_test.dart deleted file mode 100644 index 0c81eea..0000000 --- a/sandbox/mqueue/test/mq/mq_test.dart +++ /dev/null @@ -1,342 +0,0 @@ -import 'package:angel3_mq/mq.dart'; -import 'package:angel3_mq/src/core/exceptions/exceptions.dart'; -import 'package:test/test.dart'; - -void main() { - group('Initialization', () { - test( - 'MQClient instance should throw MQClientNotInitializedException if ' - 'not initialized', () { - expect( - () => MQClient.instance, - throwsA(isA()), - ); - }); - - test('MQClient initialize should create a singleton instance', () { - MQClient.initialize(); - final initializedInstance = MQClient.instance; - expect(initializedInstance, isA()); - expect(MQClient.instance, equals(initializedInstance)); - }); - }); - - group('Queue Operations', () { - setUpAll(MQClient.initialize); - - test('listQueues should return a list of all registered queues', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - final queues = MQClient.instance.listQueues(); - expect(queues, isA>()); - expect(queues, contains(queueId)); - MQClient.instance.deleteQueue(queueId); - }); - test( - 'declareQueue should declare a new queue and bind it to the default ' - 'exchange', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - final queue = MQClient.instance.fetchQueue(queueId); - expect(queue, isNotNull); - MQClient.instance.deleteQueue(queueId); - }); - - test('declareQueue should declare a new queue with the specified name', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - final queue = MQClient.instance.fetchQueue(queueId); - expect(queue, isNotNull); - expect(MQClient.instance.fetchQueue(queueId), isA>()); - MQClient.instance.deleteQueue(queueId); - }); - - test( - "declareQueue should return name of queue even if it's already " - 'registered', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - - final queueId2 = MQClient.instance.declareQueue('test-queue'); - - expect(queueId, equals(queueId2)); - - MQClient.instance.deleteQueue(queueId); - }); - - test( - 'fetchQueue should throw QueueNotRegisteredException if the queue does ' - 'not exist.', () { - expect( - () => MQClient.instance.fetchQueue('test-queue'), - throwsA(isA()), - ); - }); - - test( - 'getLatestMessage should throw QueueNotRegisteredException if the ' - 'queue does not exist.', () { - expect( - () => MQClient.instance.getLatestMessage('test-queue'), - throwsA(isA()), - ); - }); - - test('deleteQueue should delete a queue', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - MQClient.instance.deleteQueue(queueId); - expect( - () => MQClient.instance.fetchQueue(queueId), - throwsA(isA()), - ); - }); - - test( - 'deleteQueue should throw QueueNotRegisteredException if the queue ' - 'does not exist.', () { - expect( - () => MQClient.instance.deleteQueue('test-queue'), - throwsA(isA()), - ); - }); - - test( - 'deleteQueue should throw QueueHasSubscribersException if there are ' - 'any consumers consuming that queue.', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - final queueStream = MQClient.instance.fetchQueue(queueId); - final sub = queueStream.listen((_) {}); - - expect( - () => MQClient.instance.deleteQueue(queueId), - throwsA(isA()), - ); - - sub.cancel(); - MQClient.instance.deleteQueue(queueId); - }); - }); - - group('Exchange Operations', () { - setUpAll(() => MQClient); - setUp(() { - MQClient.initialize(); - }); - test('declareExchange should declare a new exchange of the specified type', - () { - final queueId = MQClient.instance.declareQueue('test-queue'); - const exchangeName = 'exchange'; - MQClient.instance.declareExchange( - exchangeName: exchangeName, - exchangeType: ExchangeType.direct, - ); - - MQClient.instance.bindQueue( - queueId: queueId, - exchangeName: exchangeName, - bindingKey: 'test-binding', - ); - - MQClient.instance.sendMessage( - message: Message(payload: 'test'), - exchangeName: exchangeName, - routingKey: 'test-binding', - ); - - expect( - MQClient.instance.getLatestMessage(queueId)?.payload, - equals('test'), - ); - - MQClient.instance.deleteExchange(exchangeName); - MQClient.instance.deleteQueue(queueId); - }); - - test( - 'sendMessage to unregister exchange should throw ' - 'ExchangeNotRegisteredException', () { - const exchangeName = 'exchange'; - expect( - () => MQClient.instance.sendMessage( - message: Message(payload: 'test'), - exchangeName: exchangeName, - routingKey: 'test-binding', - ), - throwsA(isA()), - ); - }); - - test( - 'declareExchange should throw InvalidExchangeTypeException if the ' - 'exchange type is invalid', () { - const exchangeName = 'exchange'; - expect( - () => MQClient.instance.declareExchange( - exchangeName: exchangeName, - exchangeType: ExchangeType.base, - ), - throwsA(isA()), - ); - MQClient.instance.deleteExchange(exchangeName); - }); - - test('deleteExchange should delete an exchange', () { - const exchangeName = 'exchange'; - expect( - () => MQClient.instance.bindQueue( - queueId: 'test-queue', - exchangeName: exchangeName, - ), - throwsA(isA()), - ); - }); - - test('deleteExchange should do nothing if the exchange does not exist', () { - expect( - () => MQClient.instance.deleteExchange('nonexistent_exchange'), - returnsNormally, - ); - }); - - test('bindQueue should bind a queue to direct exchange', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - const exchangeName = 'exchange'; - MQClient.instance.declareExchange( - exchangeName: exchangeName, - exchangeType: ExchangeType.direct, - ); - MQClient.instance.bindQueue( - queueId: queueId, - exchangeName: exchangeName, - bindingKey: 'key', - ); - - MQClient.instance.deleteQueue(queueId); - MQClient.instance.deleteExchange(exchangeName); - }); - - test('bindQueue should bind a queue to fanout exchange', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - const exchangeName = 'exchange'; - MQClient.instance.declareExchange( - exchangeName: exchangeName, - exchangeType: ExchangeType.fanout, - ); - MQClient.instance.bindQueue(queueId: queueId, exchangeName: exchangeName); - - MQClient.instance.deleteQueue(queueId); - MQClient.instance.deleteExchange(exchangeName); - }); - - test( - 'bindQueue should throw BindingKeyRequiredException if bindingKey is ' - 'not provided for DirectExchange', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - const exchangeName = 'exchange'; - MQClient.instance.declareExchange( - exchangeName: exchangeName, - exchangeType: ExchangeType.direct, - ); - expect( - () => MQClient.instance.bindQueue( - queueId: queueId, - exchangeName: exchangeName, - ), - throwsA(isA()), - ); - - MQClient.instance.deleteQueue(queueId); - MQClient.instance.deleteExchange(exchangeName); - }); - - test('unbindQueue should unbind a queue from an exchange', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - const exchangeName = 'exchange'; - MQClient.instance.declareExchange( - exchangeName: exchangeName, - exchangeType: ExchangeType.direct, - ); - expect( - () => MQClient.instance.bindQueue( - queueId: queueId, - exchangeName: exchangeName, - bindingKey: 'test-binding-key', - ), - returnsNormally, - ); - MQClient.instance.unbindQueue( - queueId: queueId, - exchangeName: exchangeName, - bindingKey: 'test-binding-key', - ); - MQClient.instance.deleteQueue(queueId); - MQClient.instance.deleteExchange(exchangeName); - }); - - test( - 'unbindQueue should throw BindingKeyRequiredException if ' - 'bindingKey is not provided for DirectExchange', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - const exchangeName = 'exchange'; - MQClient.instance.declareExchange( - exchangeName: exchangeName, - exchangeType: ExchangeType.direct, - ); - expect( - () => MQClient.instance.unbindQueue( - queueId: queueId, - exchangeName: exchangeName, - ), - throwsA(isA()), - ); - - MQClient.instance.deleteQueue(queueId); - MQClient.instance.deleteExchange(exchangeName); - }); - test( - 'unbindQueue should not throw BindingKeyRequiredException if ' - 'bindingKey is not provided for FanoutExchange', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - const exchangeName = 'exchange'; - MQClient.instance.declareExchange( - exchangeName: exchangeName, - exchangeType: ExchangeType.fanout, - ); - expect( - () => MQClient.instance - .unbindQueue(queueId: queueId, exchangeName: exchangeName), - returnsNormally, - ); - - MQClient.instance.deleteQueue(queueId); - MQClient.instance.deleteExchange(exchangeName); - }); - - test( - 'unbindQueue should throw ExchangeNotRegisteredException ' - 'if exchange does not exist', () { - final queueId = MQClient.instance.declareQueue('test-queue'); - const exchangeName = 'exchange'; - expect( - () => MQClient.instance.unbindQueue( - queueId: queueId, - exchangeName: exchangeName, - ), - throwsA(isA()), - ); - - MQClient.instance.deleteQueue(queueId); - }); - }); - - group('Close Operations.', () { - setUpAll(() => MQClient); - setUp(() { - MQClient.initialize(); - }); - test('close should close the MQClient', () { - MQClient.instance.declareQueue('test-queue'); - MQClient.instance.close(); - expect( - () => MQClient.instance, - throwsA(isA()), - ); - }); - }); -} diff --git a/sandbox/mqueue/test/producer/producer_test.dart b/sandbox/mqueue/test/producer/producer_test.dart deleted file mode 100644 index 1b3494c..0000000 --- a/sandbox/mqueue/test/producer/producer_test.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'dart:async'; -import 'package:angel3_mq/mq.dart'; -import 'package:test/test.dart'; - -class MyMessageProducer with ProducerMixin { - // Custom implementation of the message producer. -} - -void main() { - group('Producer', () { - final producer = MyMessageProducer(); - - setUpAll(() { - MQClient.initialize(); - - MQClient.instance.declareQueue('test-queue'); - }); - - test( - 'sendMessage should send a message to an exchange and call the ' - 'callback', () { - final message = Message( - payload: 'Test Message', - timestamp: '2023-09-07T12:00:002', - ); - var callbackCalled = false; - producer - ..setPushCallback((message) { - callbackCalled = true; - }) - ..sendMessage( - payload: 'Test Message', - exchangeName: '', - routingKey: 'test-queue', - timestamp: '2023-09-07T12:00:002', - ); - - expect( - MQClient.instance.getLatestMessage('test-queue')?.headers, - equals(message.headers), - ); - expect( - MQClient.instance.getLatestMessage('test-queue')?.payload, - equals(message.payload), - ); - expect( - MQClient.instance.getLatestMessage('test-queue')?.timestamp, - equals(message.timestamp), - ); - expect(callbackCalled, isTrue); - }); - - test( - 'sendRPCMessage should send an RPC message to an exchange and call the ' - 'callback', () async { - var callbackCalled = false; - - producer.setPushCallback((message) { - callbackCalled = true; - }); - - final sub = MQClient.instance.fetchQueue('test-queue').listen((message) { - if (message.headers['type'] == 'RPC') { - (message.headers['completer'] as Completer).complete('Response'); - return; - } - }); - - final res = await producer.sendRPCMessage( - processId: 'foo', - args: {'key': 'value'}, - exchangeName: '', - routingKey: 'test-queue', - ); - - expect(callbackCalled, isTrue); - - expect(res, equals('Response')); - - await sub.cancel(); - }); - - test('sendRPCMessage with non-null mapper', () async { - var callbackCalled = false; - producer.setPushCallback((message) { - callbackCalled = true; - }); - - final sub = - MQClient.instance.fetchQueue('test-queue').listen((message) async { - if (message.headers['type'] == 'RPC') { - (message.headers['completer'] as Completer).complete('Response'); - return; - } - }); - - final response = await producer.sendRPCMessage( - processId: 'foo', - args: {'key': 'value'}, - exchangeName: '', - routingKey: 'test-queue', - mapper: (data) => '$data-new', - ); - - expect(callbackCalled, isTrue); - expect(response, equals('Response-new')); - - await sub.cancel(); - }); - }); -} diff --git a/sandbox/mqueue/test/queue/queue_test.dart b/sandbox/mqueue/test/queue/queue_test.dart deleted file mode 100644 index 0b80082..0000000 --- a/sandbox/mqueue/test/queue/queue_test.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:angel3_mq/mq.dart'; -import 'package:angel3_mq/src/queue/queue.dart'; -import 'package:test/test.dart'; - -void main() { - group('Queue', () { - test('Creating a Queue', () { - // Arrange - const queueId = 'my_queue_id'; - - // Act - final myQueue = Queue(queueId); - - // Assert - expect(myQueue.id, equals(queueId)); - expect(myQueue.latestMessage, isNull); - }); - - test('Get dataStream from Queue', () { - // Arrange - const queueId = 'my_queue_id'; - final myQueue = Queue(queueId); - - // Act - final dataStream = myQueue.dataStream; - - // Assert - expect(dataStream, isNotNull); - }); - - test('Enqueue and Check Has Listeners', () { - // Arrange - const queueId = 'my_queue_id'; - final myQueue = Queue(queueId); - final message = Message( - headers: {'contentType': 'json', 'sender': 'Alice'}, - payload: {'text': 'Hello, World!'}, - timestamp: '2023-09-07T12:00:002', - ); - - // Act - myQueue.enqueue(message); - final hasListeners = myQueue.hasListeners(); - - // Assert - expect(myQueue.id, equals(queueId)); - expect(myQueue.latestMessage, equals(message)); - expect(hasListeners, isFalse); // No listeners by default - }); - - test('Queue equality', () { - // Arrange - final queue1 = Queue('queue_id_1'); - final queue2 = Queue('queue_id_2'); - final queue3 = Queue('queue_id_1'); // Same ID as queue1 - - // Act & Assert - expect(queue1, equals(queue3)); // Should be equal based on ID - expect( - queue1, - isNot(equals(queue2)), - ); // Should not be equal due to different IDs - }); - - test('Queue hashCode', () { - // Arrange - final queue1 = Queue('queue_id_1'); - final queue2 = Queue('queue_id_2'); - final queue3 = Queue('queue_id_1'); // Same ID as queue1 - - // Act & Assert - expect(queue1.hashCode, equals(queue3.hashCode)); - expect(queue1.hashCode, isNot(equals(queue2.hashCode))); - }); - - test('Queue dispose', () { - // Arrange - const queueId = 'my_queue_id'; - final myQueue = Queue(queueId); - final message = Message( - headers: {'contentType': 'json', 'sender': 'Alice'}, - payload: {'text': 'Hello, World!'}, - timestamp: '2023-09-07T12:00:002', - ); - - // Act - myQueue - ..enqueue(message) - ..dispose(); - final hasListeners = myQueue.hasListeners(); - - // Assert - expect(myQueue.id, equals(queueId)); - expect(myQueue.latestMessage, equals(message)); - expect(hasListeners, isFalse); - }); - }); -} diff --git a/sandbox/reactivex/.gitignore b/sandbox/reactivex/.gitignore deleted file mode 100644 index 454fea2..0000000 --- a/sandbox/reactivex/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. -/pubspec.lock -**/doc/api/ -.dart_tool/ -build/ -coverage/ \ No newline at end of file diff --git a/sandbox/reactivex/CHANGELOG.md b/sandbox/reactivex/CHANGELOG.md deleted file mode 100644 index 9c6e7a0..0000000 --- a/sandbox/reactivex/CHANGELOG.md +++ /dev/null @@ -1,775 +0,0 @@ -# Changelog - -## [0.28.0] (2024-06-14) - -### New - -* ValueStream: - * Add `lastEventOrNull` getter to `ValueStream`, - which returns the last emitted event (either data/value or error event), or `null`. - * Add `isLastEventValue`, `isLastEventError` and `errorAndStackTraceOrNull` - extension getters to `ValueStream`, to check the kind of the last emitted event is data/value or error. - * Update documentation. - -* ReplayStream: - * Add `errorAndStackTraces` to `ReplayStream`, which returns a list of emitted `ErrorAndStackTrace`s. - -* Rename `Notification` and `Kind` to better reflect their purpose, - and to avoid confusion with [Flutter's Notification class](https://api.flutter.dev/flutter/widgets/Notification-class.html). - * Rename `Notification` to `StreamNotification` - * `Notification.onData` to `StreamNotification.data`. - * `Notification.onDone` to `StreamNotification.done`. - * `Notification.onError` to `StreamNotification.error`. - * Rename `Kind` to `NotificationKind` - * `Kind.onData` to `NotificationKind.data`. - * `Kind.onError` to `NotificationKind.error`. - * `Kind.onDone` to `NotificationKind.done`. - * Introduce `DataNotification`, `ErrorNotification` and `DoneNotification` as the subclasses of `StreamNotification`. - * Convert `isOnData`, `isOnError`, `isOnDone`, `requireData` to extension getters on `StreamNotification`, - they are now named `isData`, `isError`, `isDone` and `requireDataValue`. - * Add extensions on `StreamNotification`: `dataValueOrNull`, `requireErrorAndStackTrace`, `errorAndStackTraceOrNull` getters and `when` method. - -### Changed - -* Accept Dart SDK versions above 3.0. -* `switchMap`: when cancelling the previous inner subscription, - `switchMap` will pause the outer subscription and and wait for the inner subscription to be completely canceled. - It will then resume the outer subscription, and listen to the next inner Stream. - Any errors from canceling the previous inner subscription will now be forwarded to the resulting Stream. - -* **Breaking**: Rename `ForkJoinStream.combine2`..`combine9` to `ForkJoinStream.join2`..`join9`. -* **Breaking**: `Rx.using`/`UsingStream` - * Convert all _required positional_ parameters to _required named_ parameters. - * The `disposer` is now called after the future returned from `StreamSubscription.cancel` completes. - -### Documentation - -* Update and fix documentation. -* Fix README example (thanks to [@wurikiji](https://github.com/wurikiji)). -* Update Flutter example (thanks to [@hoangchungk53qx1](https://github.com/hoangchungk53qx1)). -* Replace deprecated "dart pub run" with "dart run" (thanks to [@tatsuyafujisaki](https://github.com/tatsuyafujisaki)). - -## [0.28.0-dev.2] (2024-03-30) - -Feedback on this change appreciated as this is a dev release before 0.28.0 stable! - -### Changed - -* **Breaking**: Rename `ForkJoinStream.combine2`..`combine9` to `ForkJoinStream.join2`..`join9`. -* **Breaking**: `Rx.using`/`UsingStream` - * Convert all _required positional_ parameters to _required named_ parameters. - * The `disposer` is now called after the future returned from `StreamSubscription.cancel` completes. - -## [0.28.0-dev.1] (2024-01-27) - -Feedback on this change appreciated as this is a dev release before 0.28.0 stable! - -### Changed - -* `switchMap`: when cancelling the previous inner subscription, - `switchMap` will pause the outer subscription and and wait for the inner subscription to be completely canceled. - It will then resume the outer subscription, and listen to the next inner Stream. - Any errors from canceling the previous inner subscription will now be forwarded to the resulting Stream. - -### Documentation - -* Replace deprecated "dart pub run" with "dart run" (thanks to [@tatsuyafujisaki](https://github.com/tatsuyafujisaki)). - -## [0.28.0-dev.0] (2023-07-26) - -Feedback on this change appreciated as this is a dev release before 0.28.0 stable! - -### New - -* ValueStream: - * Add `lastEventOrNull` getter to `ValueStream`, - which returns the last emitted event (either data/value or error event), or `null`. - * Add `isLastEventValue`, `isLastEventError` and `errorAndStackTraceOrNull` - extension getters to `ValueStream`, to check the kind of the last emitted event is data/value or error. - * Update documentation. - -* ReplayStream: - * Add `errorAndStackTraces` to `ReplayStream`, which returns a list of emitted `ErrorAndStackTrace`s. - -* Rename `Notification` and `Kind` to better reflect their purpose, - and to avoid confusion with [Flutter's Notification class](https://api.flutter.dev/flutter/widgets/Notification-class.html). - * Rename `Notification` to `StreamNotification` - * `Notification.onData` to `StreamNotification.data`. - * `Notification.onDone` to `StreamNotification.done`. - * `Notification.onError` to `StreamNotification.error`. - * Rename `Kind` to `NotificationKind` - * `Kind.onData` to `NotificationKind.data`. - * `Kind.onError` to `NotificationKind.error`. - * `Kind.onDone` to `NotificationKind.done`. - * Introduce `DataNotification`, `ErrorNotification` and `DoneNotification` as the subclasses of `StreamNotification`. - * Convert `isOnData`, `isOnError`, `isOnDone`, `requireData` to extension getters on `StreamNotification`, - they are now named `isData`, `isError`, `isDone` and `requireDataValue`. - * Add extensions on `StreamNotification`: `dataValueOrNull`, `requireErrorAndStackTrace`, `errorAndStackTraceOrNull` getters and `when` method. - -### Changed - -* Accept Dart SDK versions above 3.0. - -### Documentation - -* Update and fix documentation. -* Fix README example (thanks to [@wurikiji](https://github.com/wurikiji)). -* Update Flutter example (thanks to [@hoangchungk53qx1](https://github.com/hoangchungk53qx1)). - -## 0.27.7 (2022-11-16) - -### Fixed - -* `Subject` - * Only call `onAdd` and `onError` if the subject is not closed. - This ensures `BehaviorSubject` and `ReplaySubject` do not update their values after they have been closed. - - * `Subject.stream` now returns a **read-only** `Stream`. - Previously, `Subject.stream` was identical to the `Subject`, so we could add events to it, for example: `(subject.stream as Sink).add(event)`. - This behavior is now disallowed, and will throw a `TypeError` if attempted. Use `Subject.sink`/`Subject` itself for adding events. - - * Change return type of `ReplaySubject.stream` to `ReplayStream`. - * Internal refactoring of `Subject.addStream`. - -## 0.27.6 (2022-11-11) - -* `Rx.using`/`UsingStream`: `resourceFactory` can now return a `Future`. - This allows for asynchronous resource creation. - -* `Rx.range`/`RangeStream`: ensure `RangeStream` is only listened to once. - -## 0.27.5 (2022-07-16) - -### Bug fixes - -* Fix issue [#683](https://github.com/ReactiveX/rxdart/issues/683): Throws runtime type error when using extension - methods on a `Stream` but its type annotation is `Stream`, `R` is a subtype of `T` - (covariance issue with `StreamTransformer`). - ```Dart - Stream s1 = Stream.fromIterable([1, 2, 3]); - // throws "type 'SwitchMapStreamTransformer' is not a subtype of type 'StreamTransformer' of 'streamTransformer'" - s1.switchMap((v) => Stream.value(v)); - - Stream s2 = Stream.fromIterable([1, 2, 3]); - // throws "type 'SwitchMapStreamTransformer' is not a subtype of type 'StreamTransformer' of 'streamTransformer'" - s2.switchMap((v) => Stream.value(v)); - ``` - Extension methods were previously implemented via `stream.transform(streamTransformer)`, now - via `streamTransformer.bind(stream)` to avoid this issue. - -* Fix `concatEager`: `activeSubscription` should be changed to next subscription. - -### Code refactoring - -* Change return type of `pairwise` to `Stream>`. - -## 0.27.4 (2022-05-29) - -### Bug fixes - -* `withLatestFrom` should iterate over `Iterable` only once when the stream is listened to. -* Fix analyzer warnings when using `Dart 2.16.0`. - -### Features - -* Add `mapNotNull`/`MapNotNullStreamTransformer`. -* Add `whereNotNull`/`WhereNotNullStreamTransformer`. - -### Documentation - -* Fix grammar errors in code examples (thanks to [@fzyzcjy](https://github.com/fzyzcjy)). -* Update RxMarbles URL for `RaceStream` (thanks to [@Péter Ferenc Gyarmati](https://github.com/peter-gy)). - -## 0.27.3 (2021-11-21) - -### Bug fixes - -* `flatMap` now creates inner `Stream`s lazily. -* `combineLatest`, `concat`, `concatEager`, `forkJoin`, `merge`, `race`, `zip` iterate over `Iterable`s only once - when the stream is listened to. -* Disallow mixing `autoConnect`, `connect` and `refCount` together, only one of them should be used. - -### Features - -* Introduce `AbstractConnectableStream`, base class for the `ConnectableStream` implementations. -* Improve `CompositeSubscription` (thanks to [@BreX900](https://github.com/BreX900)) - * CompositeSubscription's `dispose`, `clear`, and `remove` methods now return a completion future. - * Fixed an issue where a stream not present in CompositeSubscription was canceled. - * Added the ability not to cancel the stream when it is removed from CompositeSubscription. - * CompositeSubscription implements `StreamSubscription`. - * `CompositeSubscription.add` will throw a `StateError` instead of a `String` if this composite was disposed. - -### Documentation - -* Fix `Connectable` examples. -* Update Web example to null safety. -* Fix `Flutter` example: `SearchResultItem.fromJson` type error (thanks to [@WenYeh](https://github.com/wayne900204)) - -### Code refactoring - -* Simplify `takeLast` implementation. -* Migrate from `pedantic` to `lints` and `flutter_lints`. -* Refactor `BehaviorSubject`, `ReplaySubject` implementations by using "`Sentinel object`"s instead of `ValueWrapper`s. - -## 0.27.2 (2021-09-03) - -### Bug fixes - -* `onErrorReturnWith` now does not drop the remaining data events after the first error. -* Disallow changing handlers of `ConnectableStreamSubscription`. - -### Features - -* Add `delayWhen` operator. -* Add optional parameter `maxConcurrent` to `flatMap`. -* `groupBy` - * Rename `GroupByStream` to `GroupedStream`. - * Add optional parameter `durationSelector`, which used to determine how long each group should exist. -* `ignoreElements` - * Remove `@deprecated` annotation (`ignoreElements` should not be marked as deprecated). - * Change return type to `Stream`. - -### Documentation - -* Update to `PublishSubject`'s docs (thanks to [@AlexanderJohr](https://github.com/AlexanderJohr)). - -### Code refactoring - -* Refactoring Stream Transformers, using `Stream.multi` internally. - -## 0.27.1 - -* Bugfix: `ForkJoinStream` throws `Null check operator used on a null value` when using nullable-type. -* Bugfix: `delay` operator - * Pause and resume properly. - * Cancel all timers after it has been cancelled. - -## 0.27.0 - * **BREAKING: ValueStream** - * Remove `ValueStreamExtensions`. - * `ValueStream.valueWrapper` becomes - - `value`. - - `valueOrNull`. - - `hasValue`. - * `ValueStream.errorAndStackTrace` becomes - - `error`. - - `errorOrNull`. - - `hasError`. - - `stackTrace`. - * Add `skipLast`/`SkipLastStreamTransformer` (thanks [@HannibalKcc](https://github.com/HannibalKcc)). - * Update `scan`: change `seed` to required param. - * Add `StackTrace` param to `recoveryFn` when using `OnErrorResumeStreamTransformer`/`onErrorResume`/`onErrorReturnWith`. - * Internal refactoring `ConnectableStream`. - -## 0.26.0 - * Stable, null-safe release. - * Add `takeLast` (thanks [@ThomasKliszowski](https://github.com/ThomasKliszowski)). - * Rework for `retry`/`retryWhen`: - * Removed `RetryError`. - * `retry`: emits all errors if retry fails. - * `retryWhen`: emits original error, and error from factory if they are not identical. - * `streamFactory` now accepts non-nullable `StackTrace` argument. - * Update `ValueStream.requireValue` and `ValueStream.requireError`: throws actual error or a `StateError`, - instead of throwing `"Null check operator used on a null value"` error. - -## 0.26.0-nullsafety.1 - * Breaking change: `ValueStream` - - Add `valueWrapper` to `ValueStream`. - - Change `value`, `hasValue`, `error` and `hasError` to extension getters. - * Fixed some API example documentation (thanks [@HannibalKcc](https://github.com/HannibalKcc)). - * `throttle`/`throttleTime` have been optimised for performance. - * Updated Flutter example to work with the latest Flutter stable. - -## 0.26.0-nullsafety.0 - * Migrate this package to null safety. - * Sdk constraints: `>=2.12.0-0 <3.0.0` based on beta release guidelines. - -## 0.25.0 - * Sync behavior when using `publishValueSeeded`. - * `ValueStream`, `ReplayStream`: exposes `stackTrace` along with the `error`: - * Change `ValueStream.error` to `ValueStream.errorAndStackTrace`. - * Change `ReplayStream.errors` to `ReplayStream.errorAndStackTraces`. - * Merge `Notification.error` and `Notification.stackTrace` into `Notification.errorAndStackTrace`. - * Bugfix: `debounce`/`debounceTime` unnecessarily kept too many elements in queue. - -## 0.25.0-beta3 - * Bugfix: `switchMap` doesn't close after the last inner Stream closes. - * Docs: updated URL for "Single-Subscription vs. Broadcast Streams" doc (thanks [Aman Gupta](https://github.com/Aman9026)). - * Add `FromCallableStream`/`Rx.fromCallable`: allows you to create a `Stream` from a callable function. - * Override `BehaviorSubject`'s built-in operators to correct replaying the latest value of `BehaviorSubject`. - * Bugfix: Source `StreamSubscription` doesn't cancel when cancelling `refCount`, `zip`, `merge`, `concat` StreamSubscription. - * Forward done event of upstream to `ConnectableStream`. - -## 0.25.0-beta2 - * Internal refactoring Stream Transformers. - * Fixed `RetryStream` example documentation. - * Error thrown from `DeferStream` factory will now be caught and converted to `Stream.error`. - * `doOnError` now have strong type signature: `Stream doOnError(void Function(Object, StackTrace) onError)`. - * Updated `ForkJoinStream`: - * When any Stream emits an error, listening still continues unless `cancelOnError: true` on the downstream. - * Pause and resume Streams properly. - * Added `UsingStream`. - * Updated `TimerStream`: Pause and resume Timer when pausing and resuming StreamSubscription. - -## 0.25.0-beta - * stream transformations on a ValueStream will also return a ValueStream, instead of - a standard broadcast Stream - * throttle can now be both leading and trailing - * better handling of empty Lists when using operators that accept a List as input - * error & hasError added to BehaviorSubject - * various docs updates - * note that this is a beta release, mainly because the behavior of transform has been adjusted (see first bullet) - if all goes well, we'll release a proper 0.25.0 release soon - -## 0.24.1 - * Fix for BehaviorSubject, no longer emits null when using addStream and expecting an Error as first event (thanks [yuvalr1](https://github.com/yuvalr1)) - * min/max have been optimised for performance - * Further refactors on our Transformers - -## 0.24.0 - * Fix throttle no longer outputting the current buffer onDone - * Adds endWith and endWithMany - * Fix when using pipe and an Error, Subjects would throw an Exception that couldn't be caught using onError - * Updates links for docs (thanks [@renefloor](https://github.com/renefloor)) - * Fix links to correct marbles diagram for debounceTime (thanks [@wheater](https://github.com/Wheater)) - * Fix flakiness of withLatestFrom test Streams - * Update to docs ([@wheater](https://github.com/Wheater)) - * Fix withLatestFrom not pause/resume/cancelling underlying Streams - * Support sync behavior for Subjects - * Add addTo extension for StreamSubscription, use it to easily add a subscription to a CompositeSubscription - * Fix mergeWith and zipWith will return a broadcast Stream, if the source Stream is also broadcast - * Fix concatWith will return a broadcast Stream, if the source Stream is also broadcast (thanks [@jarekb123](https://github.com/jarekb123)) - * Adds pauseAll, resumeAll, ... to CompositeSubscription - * Additionally, fixes some issues introduced with 0.24.0-dev.1 - -## 0.24.0-dev.1 - * Breaking: as of this release, we've refactored the way Stream transformers are set up. - Previous releases had some incorrect behavior when using certain operators, for example: - - startWith (startWithMany, startWithError) - would incorrectly replay the starting event(s) when using a - broadcast Stream at subscription time. - - doOnX was not always producing the expected results: - * doOnData did not output correct sequences on streams that were transformed - multiple times in sequence. - * doOnCancel now acts in the same manner onCancel works on - regular subscriptions, i.e. it will now be called when all - active subscriptions on a Stream are cancelled. - * doOnListen will now call the first time the Stream is - subscribed to, and will only call again after all subscribers - have cancelled, before a new subscription starts. - - To properly fix this up, a new way of transforming Streams was introduced. - Operators as of now use Stream.eventTransformed and we've refactored all - operators to implement Sink instead. - * Adds takeWileInclusive operator (thanks to [@hoc081098](https://github.com/hoc081098)) - - We encourage everyone to give the dev release(s) a spin and report back if - anything breaks. If needed, a guide will be written to help migrate from - the old behavior to the new behavior in certain common use cases. - - Keep in mind that we tend to stick as close as we can to how normal - Dart Streams work! - -## 0.23.1 - - * Fix API doc links in README - -## 0.23.0 - - * Extension Methods replace `Observable` class! - * Please upgrade existing code by using the rxdart_codemod package - * Remove the Observable class. With extensions, you no longer need to wrap Streams in a [Stream]! - * Convert all factories to static constructors to aid in discoverability of Stream classes - * Move all factories to an `Rx` class. - * Remove `Observable.just`, use `Stream.value` - * Remove `Observable.error`, use `Stream.error` - * Remove all tests that check base Stream methods - * Subjects and *Observable classes extend Stream instead of base Observable - * Rename *Observable to *Stream to reflect the fact they're just Streams. - * `ValueObservable` -> `ValueStream` - * `ReplayObservable` -> `ReplayStream` - * `ConnectableObservable` -> `ConnectableStream` - * `ValueConnectableObservable` -> `ValueConnectableStream` - * `ReplayConnectableObservable` -> `ReplayConnectableStream` - * All transformation methods removed from Observable class - * Transformation methods are now Extensions of the Stream class - * Any Stream can make use of the transformation methods provided by RxDart - * Observable class remains in place with factory methods to create different types of Streams - * Removed deprecated `ofType` method, use `whereType` instead - * Deprecated `concatMap`, use standard Stream `asyncExpand`. - * Removed `AsObservableFuture`, `MinFuture`, `MaxFuture`, and `WrappedFuture` - * This removes `asObservable` method in chains - * Use default `asStream` method from the base `Future` class instead. - * `min` and `max` now implemented directly on the Stream class - -## 0.23.0-dev.3 - - * Fix missing exports: - - `ValueStream` - - `ReplayStream` - - `ConnectableStream` - - `ValueConnectableStream` - - `ReplayConnectableStream` - -## 0.23.0-dev.2 - * Remove the Observable class. With extensions, you no longer need to wrap Streams in a [Stream]! - * Convert all factories to static constructors to aid in discoverability of Stream classes - * Move all factories to an `Rx` class. - * Remove `Observable.just`, use `Stream.value` - * Remove `Observable.error`, use `Stream.error` - * Remove all tests that check base Stream methods - * Subjects and *Observable classes extend Stream instead of base Observable - * Rename *Observable to *Stream to reflect the fact they're just Streams. - * `ValueObservable` -> `ValueStream` - * `ReplayObservable` -> `ReplayStream` - * `ConnectableObservable` -> `ConnectableStream` - * `ValueConnectableObservable` -> `ValueConnectableStream` - * `ReplayConnectableObservable` -> `ReplayConnectableStream` - -## 0.23.0-dev.1 - * Feedback on this change appreciated as this is a dev release before 0.23.0 stable! - * All transformation methods removed from Observable class - * Transformation methods are now Extensions of the Stream class - * Any Stream can make use of the transformation methods provided by RxDart - * Observable class remains in place with factory methods to create different types of Streams - * Removed deprecated `ofType` method, use `whereType` instead - * Deprecated `concatMap`, use standard Stream `asyncExpand`. - * Removed `AsObservableFuture`, `MinFuture`, `MaxFuture`, and `WrappedFuture` - * This removes `asObservable` method in chains - * Use default `asStream` method from the base `Future` class instead. - * `min` and `max` now implemented directly on the Stream class - -## 0.22.6 - * Bugfix: When listening multiple times to a`BehaviorSubject` that starts with an Error, - it emits duplicate events. - * Linter: public_member_api_docs is now used, we have added extra documentation - where required. - -## 0.22.5 - * Bugfix: DeferStream created Stream too early - * Bugfix: TimerStream created Timer too early - -## 0.22.4 - * Bugfix: switchMap controller no longer closes prematurely - -## 0.22.3 - * Bugfix: whereType failing in Flutter production builds only - -## 0.22.2 - * Bugfix: When using a seeded `BehaviorSubject` and adding an `Error`, - upon listening, the `BehaviorSubject` emits `null` instead of the last `Error`. - * Bugfix: calling cancel after a `switchMap` can cause a `NoSuchMethodError`. - * Updated Flutter example to match the latest Flutter release - * `Observable.withLatestFrom` is now expanded to accept 2 or more `Stream`s - thanks to Petrus Nguyễn Thái Học (@hoc081098)! - * Deprecates `ofType` in favor of `whereType`, drop `TypeToken`. - -## 0.22.1 - Fixes following issues: - * Erroneous behavior with scan and `BehaviorSubject`. - * Bug where `flatMap` would cancel inner subscriptions in `pause`/`resume`. - * Updates to make the current "pedantic" analyzer happy. - -## 0.22.0 - This version includes refactoring for the backpressure operators: - * Breaking Change: `debounce` is now split into `debounce` and `debounceTime`. - * Breaking Change: `sample` is now split into `sample` and `sampleTime`. - * Breaking Change: `throttle` is now split into `throttle` and `throttleTime`. - -## 0.21.0 - * Breaking Change: `BehaviorSubject` now has a separate factory constructor `seeded()` - This allows you to seed this Subject with a `null` value. - * Breaking Change: `BehaviorSubject` will now emit an `Error`, if the last event was also an `Error`. - Before, when an `Error` occurred before a `listen`, the subscriber would not be notified of that `Error`. - To refactor, simply change all occurences of `BehaviorSubject(seedValue: value)` to `BehaviorSubject.seeded(value)` - * Added the `groupBy` operator - * Bugix: `doOnCancel`: will now await the cancel result, if it is a `Future`. - * Removed: `bufferWithCount`, `windowWithCount`, `tween` - Please use `bufferCount` and `windowCount`, `tween` is removed, because it never was an official Rx spec. - * Updated Flutter example to work with the latest Flutter stable. - -## 0.20.0 - * Breaking Change: bufferCount had buggy behavior when using `startBufferEvery` (was `skip` previously) - If you were relying on bufferCount with `skip` greater than 1 before, then you may have noticed - erroneous behavior. - * Breaking Change: `repeat` is no longer an operator which simply repeats the last emitted event n-times, - instead this is now an Observable factory method which takes a StreamFactory and a count parameter. - This will cause each repeat cycle to create a fresh Observable sequence. - * `mapTo` is a new operator, which works just like `map`, but instead of taking a mapper Function, it takes - a single value where each event is mapped to. - * Bugfix: switchIfEmpty now correctly calls onDone - * combineLatest and zip can now take any amount of Streams: - * combineLatest2-9 & zip2-9 functionality unchanged, but now use a new path for construction. - * adds combineLatest and zipLatest which allows you to pass through an Iterable> and a combiner that takes a List when any source emits a change. - * adds combineLatestList / zipList which allows you to take in an Iterable> and emit a Observable> with the values. Just a convenience factory if all you want is the list! - * Constructors are provided by the Stream implementation directly - * Bugfix: Subjects that are transformed will now correctly return a new Observable where isBroadcast is true (was false before) - * Remove deprecated operators which were replaced long ago: `bufferWithCount`, `windowWithCount`, `amb`, `flatMapLatest` - -## 0.19.0 - - * Breaking Change: Subjects `onCancel` function now returns `void` instead of `Future` to properly comply with the `StreamController` signature. - * Bugfix: FlatMap operator properly calls onDone for all cases - * Connectable Observable: An observable that can be listened to multiple times, and does not begin emitting values until the `connect` method is called - * ValueObservable: A new interface that allows you to get the latest value emitted by an Observable. - * Implemented by BehaviorSubject - * Convert normal observables into ValueObservables via `publishValue` or `shareValue` - * ReplayObservable: A new interface that allows you to get the values emitted by an Observable. - * Implemented by ReplaySubject - * Convert normal observables into ReplayObservables via `publishReplay` or `shareReplay` - -## 0.18.1 - -* Add `retryWhen` operator. Thanks to Razvan Lung (@long1eu)! This can be used for custom retry logic. - -## 0.18.0 - -* Breaking Change: remove `retype` method, deprecated as part of Dart 2. -* Add `flatMapIterable` - -## 0.17.0 - -* Breaking Change: `stream` property on Observable is now private. - * Avoids API confusion - * Simplifies Subject implementation - * Require folks who are overriding the `stream` property to use a `super` constructor instead -* Adds proper onPause and onResume handling for `amb`/`race`, `combineLatest`, `concat`, `concat_eager`, `merge` and `zip` -* Add `switchLatest` operator -* Add errors and stacktraces to RetryError class -* Add `onErrorResume` and `onErrorRetryWith` operators. These allow folks to return a specific stream or value depending on the error that occurred. - -## 0.16.7 - -* Fix new buffer and window implementation for Flutter + Dart 2 -* Subject now implements the Observable interface - -## 0.16.6 - -* Rework for `buffer` and `window`, allow to schedule using a sampler -* added `buffer` -* added `bufferFuture` -* added `bufferTest` -* added `bufferTime` -* added `bufferWhen` -* added `window` -* added `windowFuture` -* added `windowTest` -* added `windowTime` -* added `windowWhen` -* added `onCount` sampler for `buffer` and `window` -* added `onFuture` sampler for `buffer` and `window` -* added `onTest` sampler for `buffer` and `window` -* added `onTime` sampler for `buffer` and `window` -* added `onStream` sampler for `buffer` and `window` - -## 0.16.5 - -* Renames `amb` to `race` -* Renames `flatMapLatest` to `switchMap` -* Renames `bufferWithCount` to `bufferCount` -* Renames `windowWithCount` to `windowCount` - -## 0.16.4 - -* Adds `bufferTime` transformer. -* Adds `windowTime` transformer. - -## 0.16.3 - -* Adds `delay` transformer. - -## 0.16.2 - -* Fix added events to `sink` are not processed correctly by `Subjects`. - -## 0.16.1 - -* Fix `dematerialize` method for Dart 2. - -## 0.16.0+2 - -* Add `value` to `BehaviorSubject`. Allows you to get the latest value emitted by the subject if it exists. -* Add `values` to `ReplayrSubject`. Allows you to get the values stored by the subject if any exists. - -## 0.16.0+1 - -* Update Changelog - -## 0.16.0 - -* **breaks backwards compatibility**, this release only works with Dart SDK >=2.0.0. -* Removed old `cast` in favour of the now native Stream cast method. -* Override `retype` to return an `Observable`. - -## 0.15.1 - -* Add `exhaustMap` map to inner observable, ignore other values until that observable completes. -* Improved code to be dartdevc compatible. -* Add upper SDK version limit in pubspec - -## 0.15.0 - -* Change `debounce` to emit the last item of the source stream as soon as the source stream completes. -* Ensure `debounce` does not keep open any addition async timers after it has been cancelled. - -## 0.14.0+1 - -* Change `DoStreamTransformer` to return a `Future` on cancel for api compatibility. - -## 0.14.0 - -* Add `PublishSubject` (thanks to @pauldemarco) -* Fix bug with `doOnX` operators where callbacks were fired too often - -## 0.13.1 - -* Fix error with FlatMapLatest where it was not properly cancelled in some scenarios -* Remove additional async methods on Stream handlers unless they're shown to solve a problem - -## 0.13.0 - -* Remove `call` operator / `StreamTransformer` entirely -* Important bug fix: Errors thrown within any Stream or Operator will now be properly sent to the `StreamSubscription`. -* Improve overall handling of errors throughout the library to ensure they're handled correctly - -## 0.12.0 - -* Added doOn* operators in place of `call`. -* Added `DoStreamTransformer` as a replacement for `CallStreamTransformer` -* Deprecated `call` and `CallStreamTransformer`. Please use the appropriate `doOnX` operator / transformer. -* Added `distinctUnique`. Emits items if they've never been emitted before. Same as to Rx#distinct. - -## 0.11.0 - -* !!!Breaking Api Change!!! - * Observable.groupBy has been removed in order to be compatible with the next version of the `Stream` class in Dart 1.24.0, which includes this method - -## 0.10.2 - -* BugFix: The new Subject implementation no longer causes infinite loops when used with ng2 async pipes. - -## 0.10.1 - -* Documentation fixes - -## 0.10.0 - -* Api Changes - * Observable - * Remove all deprecated methods, including: - * `observable` factory -- replaced by the constructor `new Observable()` - * `combineLatest` -- replaced by Strong-Mode versions `combineLatest2` - `combineLatest9` - * `zip` -- replaced by Strong-Mode versions `zip2` - `zip9` - * Support `asObservable` conversion from Future-returning methods. e.g. `new Observable.fromIterable([1, 2]).first.asObservable()` - * Max and Min now return a Future of the Max or Min value, rather than a stream of increasing or decreasing values. - * Add `cast` operator - * Remove `ConcatMapStreamTransformer` -- functionality is already supported by `asyncExpand`. Keep the `concatMap` method as an alias. - * Subjects - * BehaviourSubject has been renamed to BehaviorSubject - * The subjects have been rewritten and include far more testing - * In keeping with the Rx idea of Subjects, they are broadcast-only -* Documentation -- extensive documentation has been added to the library with explanations and examples for each Future, Stream & Transformer. - * Docs detailing the differences between RxDart and raw Observables. - -## 0.9.0 - -* Api Changes: - * Convert all StreamTransformer factories to proper classes - * Ensure these classes can be re-used multiple times - * Retry has moved from an operator to a constructor. This is to ensure the stream can be properly re-constructed every time in the correct way. - * Streams now properly enforce the single-subscription contract -* Include example Flutter app. To run it, please follow the instructions in the README. - -## 0.8.3+1 -* rename examples map to example - -## 0.8.3 -* added concatWith, zipWith, mergeWith, skipUntil -* cleanup of the examples folder -* cleanup of examples code -* added fibonacci example -* added search GitHub example - -## 0.8.2+1 -* moved repo into ReactiveX -* update readme badges accordingly - -## 0.8.2 -* added materialize/dematerialize -* added range (factory) -* added timer (factory) -* added timestamp -* added concatMap - -## 0.8.1 -* added never constructor -* added error constructor -* moved code coverage to [codecov.io](https://codecov.io/gh/frankpepermans/rxdart) - -## 0.8.0 -* BREAKING: tap is replaced by call(onData) -* added call, which can take any combination of the following event methods: -onCancel, onData, onDone, onError, onListen, onPause, onResume - -## 0.7.1+1 -* improved the README file - -## 0.7.1 -* added ignoreElements -* added onErrorResumeNext -* added onErrorReturn -* added switchIfEmpty -* added empty factory constructor - -## 0.7.0 -* BREAKING: rename combineXXXLatest and zipXXX to a numbered equivalent, -for example: combineThreeLatest becomes combineLatest3 -* internal refactoring, expose streams/stream transformers as a separate library - -## 0.6.3+4 -* changed ofType to use TypeToken - -## 0.6.3+3 -* added ofType - -## 0.6.3+2 -* added defaultIfEmpty - -## 0.6.3+1 -* changed concat, old concat is now concatEager, new concat behaves as expected - -## 0.6.3 -* Added withLatestFrom -* Added defer ctr -(both thanks to [brianegan](https://github.com/brianegan "GitHub link")) - -## 0.6.2 -* Added just (thanks to [brianegan](https://github.com/brianegan "GitHub link")) -* Added groupBy -* Added amb - -## 0.6.1 -* Added concat - -## 0.6.0 -* BREAKING: startWith now takes just one parameter instead of an Iterable. To add multiple starting events, please use startWithMany. -* Added BehaviourSubject and ReplaySubject. These implement StreamController. -* BehaviourSubject will notify the last added event upon listening. -* ReplaySubject will notify all past events upon listening. -* DEPRECATED: zip and combineLatest, use their strong-type-friendly alternatives instead (available as static methods on the Observable class, i.e. Observable.combineThreeLatest, Observable.zipFour, ...) - -## 0.5.1 - -* Added documentation (thanks to [dustinlessard-wf](https://github.com/dustinlessard-wf "GitHub link")) -* Fix tests breaking due to deprecation of expectAsync -* Fix tests to satisfy strong mode requirements - -## 0.5.0 - -* As of this version, rxdart depends on SDK v1.21.0, to support the newly added generic method type syntax - -[Unreleased]: https://github.com/ReactiveX/rxdart/compare/0.28.0...HEAD -[0.28.0]: https://github.com/ReactiveX/rxdart/releases/tag/0.28.0 -[0.28.0-dev.2]: https://github.com/ReactiveX/rxdart/releases/tag/0.28.0-dev.2 -[0.28.0-dev.1]: https://github.com/ReactiveX/rxdart/releases/tag/0.28.0-dev.1 -[0.28.0-dev.0]: https://github.com/ReactiveX/rxdart/releases/tag/0.28.0-dev.0 \ No newline at end of file diff --git a/sandbox/reactivex/LICENSE b/sandbox/reactivex/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/sandbox/reactivex/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/sandbox/reactivex/README.md b/sandbox/reactivex/README.md deleted file mode 100644 index 4f6f0da..0000000 --- a/sandbox/reactivex/README.md +++ /dev/null @@ -1,277 +0,0 @@ -# RxDart - -

    -build -

    - -

    -RxDart -

    - -[![Build Status](https://github.com/ReactiveX/rxdart/workflows/Dart%20CI/badge.svg)](https://github.com/ReactiveX/rxdart/actions) -[![codecov](https://codecov.io/gh/ReactiveX/rxdart/branch/master/graph/badge.svg)](https://codecov.io/gh/ReactiveX/rxdart) -[![Pub](https://img.shields.io/pub/v/rxdart.svg)](https://pub.dartlang.org/packages/rxdart) -[![Pub Version (including pre-releases)](https://img.shields.io/pub/v/rxdart?include_prereleases&color=%23A0147B)](https://pub.dartlang.org/packages/rxdart) -[![Gitter](https://img.shields.io/gitter/room/ReactiveX/rxdart.svg)](https://gitter.im/ReactiveX/rxdart) -[![Flutter website](https://img.shields.io/badge/flutter-website-deepskyblue.svg)](https://docs.flutter.dev/data-and-backend/state-mgmt/options#bloc--rx) -[![Build Flutter example](https://github.com/ReactiveX/rxdart/actions/workflows/flutter-example.yml/badge.svg)](https://github.com/ReactiveX/rxdart/actions/workflows/flutter-example.yml) -[![License](https://img.shields.io/github/license/ReactiveX/rxdart)](https://www.apache.org/licenses/LICENSE-2.0) -[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2FReactiveX%2Frxdart&count_bg=%23D71092&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) - -## About - -RxDart extends the capabilities of Dart -[Streams](https://api.dart.dev/stable/dart-async/Stream-class.html) and -[StreamControllers](https://api.dart.dev/stable/dart-async/StreamController-class.html). - -Dart comes with a very decent -[Streams](https://api.dart.dev/stable/dart-async/Stream-class.html) API -out-of-the-box; rather than attempting to provide an alternative to this API, -RxDart adds functionality from the reactive extensions specification on top of -it. - -RxDart does not provide its Observable class as a replacement for Dart -Streams. Instead, it offers several additional Stream classes, operators -(extension methods on the Stream class), and Subjects. - -If you are familiar with Observables from other languages, please see [the Rx -Observables vs. Dart Streams comparison chart](#rx-observables-vs-dart-streams) -for notable distinctions between the two. - -## Upgrading from RxDart 0.22.x to 0.23.x - -RxDart 0.23.x moves away from the Observable class, utilizing Dart 2.6's new -extension methods instead. This requires several small refactors that can be -easily automated -- which is just what we've done! - -Please follow the instructions on the -[rxdart_codemod](https://pub.dev/packages/rxdart_codemod) package to -automatically upgrade your code to support RxDart 0.23.x. - -## How To Use RxDart - -### For Example: Reading the Konami Code - -```dart -import 'package:rxdart/rxdart.dart'; - -void main() { - const konamiKeyCodes = [ - KeyCode.UP, - KeyCode.UP, - KeyCode.DOWN, - KeyCode.DOWN, - KeyCode.LEFT, - KeyCode.RIGHT, - KeyCode.LEFT, - KeyCode.RIGHT, - KeyCode.B, - KeyCode.A, - ]; - - final result = querySelector('#result')!; - - document.onKeyUp - .map((event) => event.keyCode) - .bufferCount(10, 1) // An extension method provided by rxdart - .where((lastTenKeyCodes) => const IterableEquality().equals(lastTenKeyCodes, konamiKeyCodes)) - .listen((_) => result.innerHtml = 'KONAMI!'); -} -``` - -## API Overview - -RxDart adds functionality to Dart Streams in three ways: - - * [Stream Classes](#stream-classes) - create Streams with specific capabilities, such as combining or merging many Streams. - * [Extension Methods](#extension-methods) - transform a source Stream into a new Stream with different capabilities, such as throttling or buffering events. - * [Subjects](#subjects) - StreamControllers with additional powers - -### Stream Classes - -The Stream class provides different ways to create a Stream: `Stream.fromIterable` or `Stream.periodic`. RxDart provides additional Stream classes for a variety of tasks, such as combining or merging Streams! - -You can construct the Streams provided by RxDart in two ways. The following examples are equivalent in terms of functionality: - - - Instantiating the Stream class directly. - - Example: `final mergedStream = MergeStream([myFirstStream, mySecondStream]);` - - Using static factories from the Rx class, which are useful for discovering which types of Streams are provided by RxDart. Under the hood, these factories call the corresponding Stream constructor. - - Example: `final mergedStream = Rx.merge([myFirstStream, mySecondStream]);` - -#### List of Classes / Static Factories - -- [CombineLatestStream](https://pub.dev/documentation/rxdart/latest/rx/CombineLatestStream-class.html) (combine2, combine3... combine9) / [Rx.combineLatest2](https://pub.dev/documentation/rxdart/latest/rx/Rx/combineLatest2.html)...[Rx.combineLatest9](https://pub.dev/documentation/rxdart/latest/rx/Rx/combineLatest9.html) -- [ConcatStream](https://pub.dev/documentation/rxdart/latest/rx/ConcatStream-class.html) / [Rx.concat](https://pub.dev/documentation/rxdart/latest/rx/Rx/concat.html) -- [ConcatEagerStream](https://pub.dev/documentation/rxdart/latest/rx/ConcatEagerStream-class.html) / [Rx.concatEager](https://pub.dev/documentation/rxdart/latest/rx/Rx/concatEager.html) -- [DeferStream](https://pub.dev/documentation/rxdart/latest/rx/DeferStream-class.html) / [Rx.defer](https://pub.dev/documentation/rxdart/latest/rx/Rx/defer.html) -- [ForkJoinStream](https://pub.dev/documentation/rxdart/latest/rx/ForkJoinStream-class.html) (join2, join3... join9) / [Rx.forkJoin2](https://pub.dev/documentation/rxdart/latest/rx/Rx/forkJoin2.html)...[Rx.forkJoin9](https://pub.dev/documentation/rxdart/latest/rx/Rx/forkJoin9.html) -- [FromCallableStream](https://pub.dev/documentation/rxdart/latest/rx/FromCallableStream-class.html) / [Rx.fromCallable](https://pub.dev/documentation/rxdart/latest/rx/Rx/fromCallable.html) -- [MergeStream](https://pub.dev/documentation/rxdart/latest/rx/MergeStream-class.html) / [Rx.merge](https://pub.dev/documentation/rxdart/latest/rx/Rx/merge.html) -- [NeverStream](https://pub.dev/documentation/rxdart/latest/rx/NeverStream-class.html) / [Rx.never](https://pub.dev/documentation/rxdart/latest/rx/Rx/never.html) -- [RaceStream](https://pub.dev/documentation/rxdart/latest/rx/RaceStream-class.html) / [Rx.race](https://pub.dev/documentation/rxdart/latest/rx/Rx/race.html) -- [RangeStream](https://pub.dev/documentation/rxdart/latest/rx/RangeStream-class.html) / [Rx.range](https://pub.dev/documentation/rxdart/latest/rx/Rx/range.html) -- [RepeatStream](https://pub.dev/documentation/rxdart/latest/rx/RepeatStream-class.html) / [Rx.repeat](https://pub.dev/documentation/rxdart/latest/rx/Rx/repeat.html) -- [RetryStream](https://pub.dev/documentation/rxdart/latest/rx/RetryStream-class.html) / [Rx.retry](https://pub.dev/documentation/rxdart/latest/rx/Rx/retry.html) -- [RetryWhenStream](https://pub.dev/documentation/rxdart/latest/rx/RetryWhenStream-class.html) / [Rx.retryWhen](https://pub.dev/documentation/rxdart/latest/rx/Rx/retryWhen.html) -- [SequenceEqualStream](https://pub.dev/documentation/rxdart/latest/rx/SequenceEqualStream-class.html) / [Rx.sequenceEqual](https://pub.dev/documentation/rxdart/latest/rx/Rx/sequenceEqual.html) -- [SwitchLatestStream](https://pub.dev/documentation/rxdart/latest/rx/SwitchLatestStream-class.html) / [Rx.switchLatest](https://pub.dev/documentation/rxdart/latest/rx/Rx/switchLatest.html) -- [TimerStream](https://pub.dev/documentation/rxdart/latest/rx/TimerStream-class.html) / [Rx.timer](https://pub.dev/documentation/rxdart/latest/rx/Rx/timer.html) -- [UsingStream](https://pub.dev/documentation/rxdart/latest/rx/UsingStream-class.html) / [Rx.using](https://pub.dev/documentation/rxdart/latest/rx/Rx/using.html) -- [ZipStream](https://pub.dev/documentation/rxdart/latest/rx/ZipStream-class.html) (zip2, zip3, zip4, ..., zip9) / [Rx.zip](https://pub.dev/documentation/rxdart/latest/rx/Rx/zip2.html)...[Rx.zip9](https://pub.dev/documentation/rxdart/latest/rx/Rx/zip9.html) -- If you're looking for an [Interval](https://reactivex.io/documentation/operators/interval.html) equivalent, check out Dart's [Stream.periodic](https://api.dart.dev/stable/2.7.2/dart-async/Stream/Stream.periodic.html) for similar behavior. - -### Extension Methods - -The extension methods provided by RxDart can be used on any `Stream`. They convert a source Stream into a new Stream with additional capabilities, such as buffering or throttling events. - -#### Example - -```dart -Stream.fromIterable([1, 2, 3]) - .throttleTime(Duration(seconds: 1)) - .listen(print); // prints 1 -``` - -#### List of Extension Methods - -- [buffer](https://pub.dev/documentation/rxdart/latest/rx/BufferExtensions/buffer.html) -- [bufferCount](https://pub.dev/documentation/rxdart/latest/rx/BufferExtensions/bufferCount.html) -- [bufferTest](https://pub.dev/documentation/rxdart/latest/rx/BufferExtensions/bufferTest.html) -- [bufferTime](https://pub.dev/documentation/rxdart/latest/rx/BufferExtensions/bufferTime.html) -- [concatWith](https://pub.dev/documentation/rxdart/latest/rx/ConcatExtensions/concatWith.html) -- [debounce](https://pub.dev/documentation/rxdart/latest/rx/DebounceExtensions/debounce.html) -- [debounceTime](https://pub.dev/documentation/rxdart/latest/rx/DebounceExtensions/debounceTime.html) -- [defaultIfEmpty](https://pub.dev/documentation/rxdart/latest/rx/DefaultIfEmptyExtension/defaultIfEmpty.html) -- [delay](https://pub.dev/documentation/rxdart/latest/rx/DelayExtension/delay.html) -- [delayWhen](https://pub.dev/documentation/rxdart/latest/rx/DelayWhenExtension/delayWhen.html) -- [dematerialize](https://pub.dev/documentation/rxdart/latest/rx/DematerializeExtension/dematerialize.html) -- [distinctUnique](https://pub.dev/documentation/rxdart/latest/rx/DistinctUniqueExtension/distinctUnique.html) -- [doOnCancel](https://pub.dev/documentation/rxdart/latest/rx/DoExtensions/doOnCancel.html) -- [doOnData](https://pub.dev/documentation/rxdart/latest/rx/DoExtensions/doOnData.html) -- [doOnDone](https://pub.dev/documentation/rxdart/latest/rx/DoExtensions/doOnDone.html) -- [doOnEach](https://pub.dev/documentation/rxdart/latest/rx/DoExtensions/doOnEach.html) -- [doOnError](https://pub.dev/documentation/rxdart/latest/rx/DoExtensions/doOnError.html) -- [doOnListen](https://pub.dev/documentation/rxdart/latest/rx/DoExtensions/doOnListen.html) -- [doOnPause](https://pub.dev/documentation/rxdart/latest/rx/DoExtensions/doOnPause.html) -- [doOnResume](https://pub.dev/documentation/rxdart/latest/rx/DoExtensions/doOnResume.html) -- [endWith](https://pub.dev/documentation/rxdart/latest/rx/EndWithExtension/endWith.html) -- [endWithMany](https://pub.dev/documentation/rxdart/latest/rx/EndWithManyExtension/endWithMany.html) -- [exhaustMap](https://pub.dev/documentation/rxdart/latest/rx/ExhaustMapExtension/exhaustMap.html) -- [flatMap](https://pub.dev/documentation/rxdart/latest/rx/FlatMapExtension/flatMap.html) -- [flatMapIterable](https://pub.dev/documentation/rxdart/latest/rx/FlatMapExtension/flatMapIterable.html) -- [groupBy](https://pub.dev/documentation/rxdart/latest/rx/GroupByExtension/groupBy.html) -- [interval](https://pub.dev/documentation/rxdart/latest/rx/IntervalExtension/interval.html) -- [mapNotNull](https://pub.dev/documentation/rxdart/latest/rx/MapNotNullExtension/mapNotNull.html) -- [mapTo](https://pub.dev/documentation/rxdart/latest/rx/MapToExtension/mapTo.html) -- [materialize](https://pub.dev/documentation/rxdart/latest/rx/MaterializeExtension/materialize.html) -- [max](https://pub.dev/documentation/rxdart/latest/rx/MaxExtension/max.html) -- [mergeWith](https://pub.dev/documentation/rxdart/latest/rx/MergeExtension/mergeWith.html) -- [min](https://pub.dev/documentation/rxdart/latest/rx/MinExtension/min.html) -- [onErrorResume](https://pub.dev/documentation/rxdart/latest/rx/OnErrorExtensions/onErrorResume.html) -- [onErrorResumeNext](https://pub.dev/documentation/rxdart/latest/rx/OnErrorExtensions/onErrorResumeNext.html) -- [onErrorReturn](https://pub.dev/documentation/rxdart/latest/rx/OnErrorExtensions/onErrorReturn.html) -- [onErrorReturnWith](https://pub.dev/documentation/rxdart/latest/rx/OnErrorExtensions/onErrorReturnWith.html) -- [pairwise](https://pub.dev/documentation/rxdart/latest/rx/PairwiseExtension/pairwise.html) -- [sample](https://pub.dev/documentation/rxdart/latest/rx/SampleExtensions/sample.html) -- [sampleTime](https://pub.dev/documentation/rxdart/latest/rx/SampleExtensions/sampleTime.html) -- [scan](https://pub.dev/documentation/rxdart/latest/rx/ScanExtension/scan.html) -- [skipLast](https://pub.dev/documentation/rxdart/latest/rx/SkipLastExtension/skipLast.html) -- [skipUntil](https://pub.dev/documentation/rxdart/latest/rx/SkipUntilExtension/skipUntil.html) -- [startWith](https://pub.dev/documentation/rxdart/latest/rx/StartWithExtension/startWith.html) -- [startWithMany](https://pub.dev/documentation/rxdart/latest/rx/StartWithManyExtension/startWithMany.html) -- [switchIfEmpty](https://pub.dev/documentation/rxdart/latest/rx/SwitchIfEmptyExtension/switchIfEmpty.html) -- [switchMap](https://pub.dev/documentation/rxdart/latest/rx/SwitchMapExtension/switchMap.html) -- [takeLast](https://pub.dev/documentation/rxdart/latest/rx/TakeLastExtension/takeLast.html) -- [takeUntil](https://pub.dev/documentation/rxdart/latest/rx/TakeUntilExtension/takeUntil.html) -- [takeWhileInclusive](https://pub.dev/documentation/rxdart/latest/rx/TakeWhileInclusiveExtension/takeWhileInclusive.html) -- [throttle](https://pub.dev/documentation/rxdart/latest/rx/ThrottleExtensions/throttle.html) -- [throttleTime](https://pub.dev/documentation/rxdart/latest/rx/ThrottleExtensions/throttleTime.html) -- [timeInterval](https://pub.dev/documentation/rxdart/latest/rx/TimeIntervalExtension/timeInterval.html) -- [timestamp](https://pub.dev/documentation/rxdart/latest/rx/TimeStampExtension/timestamp.html) -- [whereNotNull](https://pub.dev/documentation/rxdart/latest/rx/WhereNotNullExtension/whereNotNull.html) -- [whereType](https://pub.dev/documentation/rxdart/latest/rx/WhereTypeExtension/whereType.html) -- [window](https://pub.dev/documentation/rxdart/latest/rx/WindowExtensions/window.html) -- [windowCount](https://pub.dev/documentation/rxdart/latest/rx/WindowExtensions/windowCount.html) -- [windowTest](https://pub.dev/documentation/rxdart/latest/rx/WindowExtensions/windowTest.html) -- [windowTime](https://pub.dev/documentation/rxdart/latest/rx/WindowExtensions/windowTime.html) -- [withLatestFrom](https://pub.dev/documentation/rxdart/latest/rx/WithLatestFromExtensions.html) -- [zipWith](https://pub.dev/documentation/rxdart/latest/rx/ZipWithExtension/zipWith.html) - -### Subjects - -Dart provides the [StreamController](https://api.dart.dev/stable/dart-async/StreamController-class.html) class to create and manage a Stream. RxDart offers two additional StreamControllers with additional capabilities, known as Subjects: - -- [BehaviorSubject](https://pub.dev/documentation/rxdart/latest/rx/BehaviorSubject-class.html) - A broadcast StreamController that caches the latest added value or error. When a new listener subscribes to the Stream, the latest value or error will be emitted to the listener. Furthermore, you can synchronously read the last emitted value. -- [ReplaySubject](https://pub.dev/documentation/rxdart/latest/rx/ReplaySubject-class.html) - A broadcast StreamController that caches the added values. When a new listener subscribes to the Stream, the cached values will be emitted to the listener. - -## Rx Observables vs Dart Streams - -In many situations, Streams and Observables work the same way. However, if you're used to standard Rx Observables, some features of the Stream API may surprise you. We've included a table below to help folks understand the differences. - -Additional information about the following situations can be found by reading the [Rx class documentation](https://pub.dev/documentation/rxdart/latest/rx/Rx-class.html). - -| Situation | Rx Observables | Dart Streams | -| ------------- |------------- | ------------- | -| An error is raised | Observable Terminates with Error | Error is emitted and Stream continues | -| Cold Observables | Multiple subscribers can listen to the same cold Observable, and each subscription will receive a unique Stream of data | Single subscriber only | -| Hot Observables | Yes | Yes, known as Broadcast Streams | -| Is {Publish, Behavior, Replay}Subject hot? | Yes | Yes | -| Single/Maybe/Completable ? | Yes | Yes, uses [rxdart_ext Single](https://pub.dev/documentation/rxdart_ext/latest/rxdart_ext/Single-class.html) (`Completable == Single` and `Maybe == Single`) | -| Support back pressure| Yes | Yes | -| Can emit null? | Yes, except RxJava | Yes | -| Sync by default | Yes | No | -| Can pause/resume a subscription*? | No | Yes | - -## Examples - -Web and command-line examples can be found in the `example` folder. - -### Web Examples - -In order to run the web examples, please follow these steps: - - 1. Clone this repo and enter the directory `examples/web` - 2. Run `dart pub get` - 3. Run `dart pub global activate webdev` - 4. Run `webdev serve` - 5. Navigate to http://localhost:8080/ in your browser - -### Command Line Examples - -In order to run the command line example, please follow these steps: - - 1. Clone this repo and enter the directory - 2. Run `pub get` - 3. Run `dart examples/fibonacci/lib/example.dart 10` - -### Flutter Example - -#### Install Flutter - -To run the flutter example, you must have Flutter installed. For installation instructions, view the online -[documentation](https://flutter.io/). - -#### Run the app - - 1. Open up an Android Emulator, the iOS Simulator, or connect an appropriate mobile device for debugging. - 2. Open up a terminal - 3. `cd` into the `examples/flutter/github_search` directory - 4. Run `flutter doctor` to ensure you have all Flutter dependencies working. - 5. Run `flutter packages get` - 6. Run `flutter run` - -## Notable References - -- [Documentation on the Dart Stream class](https://api.dart.dev/stable/dart-async/Stream-class.html) -- [Tutorial on working with Streams in Dart](https://www.dartlang.org/tutorials/language/streams) -- [ReactiveX (Rx)](https://reactivex.io/) - -## Changelog - -Refer to the [Changelog](https://github.com/ReactiveX/rxdart/blob/master/packages/rxdart/CHANGELOG.md) to get all release notes. - -## Extensions - -Check out [rxdart_ext](https://pub.dev/packages/rxdart_ext), which provides many extension methods and classes built on top of RxDart. - - diff --git a/sandbox/reactivex/analysis_options.yaml b/sandbox/reactivex/analysis_options.yaml deleted file mode 100644 index 6f808f6..0000000 --- a/sandbox/reactivex/analysis_options.yaml +++ /dev/null @@ -1,15 +0,0 @@ -include: package:lints/recommended.yaml - -analyzer: - language: - strict-casts: true - strict-raw-types: true - strict-inference: true - -linter: - rules: - - public_member_api_docs - - always_declare_return_types # https://github.com/dart-lang/lints#migrating-from-packagepedantic - - prefer_single_quotes # https://github.com/dart-lang/lints#migrating-from-packagepedantic - - unawaited_futures # https://github.com/dart-lang/lints#migrating-from-packagepedantic - - unsafe_html # https://github.com/dart-lang/lints#migrating-from-packagepedantic diff --git a/sandbox/reactivex/lib/angel3_reactivex.dart b/sandbox/reactivex/lib/angel3_reactivex.dart deleted file mode 100644 index 2003c7a..0000000 --- a/sandbox/reactivex/lib/angel3_reactivex.dart +++ /dev/null @@ -1,7 +0,0 @@ -library rx; - -export 'src/rx.dart'; -export 'streams.dart'; -export 'subjects.dart'; -export 'transformers.dart'; -export 'utils.dart'; diff --git a/sandbox/reactivex/lib/src/rx.dart b/sandbox/reactivex/lib/src/rx.dart deleted file mode 100644 index 113180e..0000000 --- a/sandbox/reactivex/lib/src/rx.dart +++ /dev/null @@ -1,1357 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/error_and_stacktrace.dart'; -import 'package:angel3_reactivex/streams.dart'; - -/// A utility class that provides static methods to create the various Streams -/// provided by angel3_reactivex. -/// -/// ### Example -/// -/// Rx.combineLatest([ -/// Stream.value('a'), -/// Stream.fromIterable(['b', 'c', 'd']) -/// ], (list) => list.join()) -/// .listen(print); // prints 'ab', 'ac', 'ad' -/// -/// ### Learning angel3_reactivex -/// -/// This library contains documentation and examples for each method. In -/// addition, more complex examples can be found in the -/// [angel3_reactivex github repo](https://github.com/ReactiveX/angel3_reactivex) demonstrating how -/// to use angel3_reactivex with web, command line, and Flutter applications. -/// -/// #### Additional Resources -/// -/// In addition to the angel3_reactivex documentation and examples, you can find many -/// more articles on Dart Streams that teach the fundamentals upon which -/// angel3_reactivex is built. -/// -/// - [Asynchronous Programming: Streams](https://www.dartlang.org/tutorials/language/streams) -/// - [Single-Subscription vs. Broadcast Streams](https://dart.dev/tutorials/language/streams#two-kinds-of-streams) -/// - [Creating Streams in Dart](https://www.dartlang.org/articles/libraries/creating-streams) -/// - [Testing Streams: Stream Matchers](https://pub.dartlang.org/packages/test#stream-matchers) -/// -/// ### Dart Streams vs Traditional Rx Observables -/// In ReactiveX, the Observable class is the heart of the ecosystem. -/// Observables represent data sources that emit 'items' or 'events' over time. -/// Dart already includes such a data source: Streams. -/// -/// In order to integrate fluently with the Dart ecosystem, Rx Dart does not -/// provide a [Stream] class, but rather adds functionality to Dart Streams. -/// This provides several advantages: -/// -/// - angel3_reactivex works with any API that expects a Dart Stream as an input. -/// - No need to implement or replace the many methods and properties from the core Stream API. -/// - Ability to create Streams with language-level syntax. -/// -/// Overall, we attempt to follow the ReactiveX spec as closely as we can, but -/// prioritize fitting in with the Dart ecosystem when a trade-off must be made. -/// Therefore, there are some important differences to note between Dart's -/// [Stream] class and standard Rx `Observable`. -/// -/// First, Cold Observables exist in Dart as normal Streams, but they are -/// single-subscription only. In other words, you can only listen a Stream -/// once, unless it is a hot (aka broadcast) Stream. If you attempt to listen to -/// a cold Stream twice, a StateError will be thrown. If you need to listen to a -/// stream multiple times, you can simply create a factory function that returns -/// a new instance of the stream. -/// -/// Second, many methods contained within, such as `first` and `last` do not -/// return a `Single` nor an `Observable`, but rather must return a Dart Future. -/// Luckily, Dart's `Future` class is conceptually similar to `Single`, and can -/// be easily converted back to a Stream using the `myFuture.asStream()` method -/// if needed. -/// -/// Third, Streams in Dart do not close by default when an error occurs. In Rx, -/// an Error causes the Observable to terminate unless it is intercepted by -/// an operator. Dart has mechanisms for creating streams that close when an -/// error occurs, but the majority of Streams do not exhibit this behavior. -/// -/// Fourth, Dart streams are asynchronous by default, whereas Observables are -/// synchronous by default, unless you schedule work on a different Scheduler. -/// You can create synchronous Streams with Dart, but please be aware the the -/// default is simply different. -/// -/// Finally, when using Dart Broadcast Streams (similar to Hot Observables), -/// please know that `onListen` will only be called the first time the -/// broadcast stream is listened to. -abstract class Rx { - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function whenever any of the stream sequences emits an item. - /// This is helpful when you need to combine a dynamic number of Streams. - /// - /// The Stream will not emit any lists of values until all of the source - /// streams have emitted at least one value. - /// - /// If the provided streams is empty, the resulting sequence completes immediately - /// without emitting any items and without any calls to the combiner function. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) - /// - /// ### Example - /// - /// Rx.combineLatest([ - /// Stream.value('a'), - /// Stream.fromIterable(['b', 'c', 'd']) - /// ], (list) => list.join()) - /// .listen(print); // prints 'ab', 'ac', 'ad' - static Stream combineLatest( - Iterable> streams, R Function(List values) combiner) => - CombineLatestStream(streams, combiner); - - /// Merges the given Streams into a single Stream that emits a List of the - /// values emitted by the source Stream. This is helpful when you need to - /// combine a dynamic number of Streams. - /// - /// The Stream will not emit any lists of values until all of the source - /// streams have emitted at least one value. - /// - /// If the provided streams is empty, the resulting sequence completes immediately - /// without emitting any items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) - /// - /// ### Example - /// - /// Rx.combineLatestList([ - /// Stream.value(1), - /// Stream.fromIterable([0, 1, 2]), - /// ]) - /// .listen(print); // prints [1, 0], [1, 1], [1, 2] - static Stream> combineLatestList(Iterable> streams) => - CombineLatestStream.list(streams); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function whenever any of the stream sequences emits an - /// item. - /// - /// The Stream will not emit until all streams have emitted at least one - /// item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) - /// - /// ### Example - /// - /// Rx.combineLatest2( - /// Stream.value(1), - /// Stream.fromIterable([0, 1, 2]), - /// (a, b) => a + b) - /// .listen(print); //prints 1, 2, 3 - static Stream combineLatest2(Stream streamA, Stream streamB, - T Function(A a, B b) combiner) => - CombineLatestStream.combine2(streamA, streamB, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function whenever any of the stream sequences emits an - /// item. - /// - /// The Stream will not emit until all streams have emitted at least one - /// item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) - /// - /// ### Example - /// - /// Rx.combineLatest3( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.fromIterable(['c', 'c']), - /// (a, b, c) => a + b + c) - /// .listen(print); //prints 'abc', 'abc' - static Stream combineLatest3( - Stream streamA, - Stream streamB, - Stream streamC, - T Function(A a, B b, C c) combiner) => - CombineLatestStream.combine3(streamA, streamB, streamC, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function whenever any of the stream sequences emits an - /// item. - /// - /// The Stream will not emit until all streams have emitted at least one - /// item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) - /// - /// ### Example - /// - /// Rx.combineLatest4( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.fromIterable(['d', 'd']), - /// (a, b, c, d) => a + b + c + d) - /// .listen(print); //prints 'abcd', 'abcd' - static Stream combineLatest4( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - T Function(A a, B b, C c, D d) combiner) => - CombineLatestStream.combine4( - streamA, streamB, streamC, streamD, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function whenever any of the stream sequences emits an - /// item. - /// - /// The Stream will not emit until all streams have emitted at least one - /// item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) - /// - /// ### Example - /// - /// Rx.combineLatest5( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.fromIterable(['e', 'e']), - /// (a, b, c, d, e) => a + b + c + d + e) - /// .listen(print); //prints 'abcde', 'abcde' - static Stream combineLatest5( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - T Function(A a, B b, C c, D d, E e) combiner) => - CombineLatestStream.combine5( - streamA, streamB, streamC, streamD, streamE, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function whenever any of the stream sequences emits an - /// item. - /// - /// The Stream will not emit until all streams have emitted at least one - /// item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) - /// - /// ### Example - /// - /// Rx.combineLatest6( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.fromIterable(['f', 'f']), - /// (a, b, c, d, e, f) => a + b + c + d + e + f) - /// .listen(print); //prints 'abcdef', 'abcdef' - static Stream combineLatest6( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - T Function(A a, B b, C c, D d, E e, F f) combiner) => - CombineLatestStream.combine6( - streamA, streamB, streamC, streamD, streamE, streamF, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function whenever any of the stream sequences emits an - /// item. - /// - /// The Stream will not emit until all streams have emitted at least one - /// item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) - /// - /// ### Example - /// - /// Rx.combineLatest7( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.value('f'), - /// Stream.fromIterable(['g', 'g']), - /// (a, b, c, d, e, f, g) => a + b + c + d + e + f + g) - /// .listen(print); //prints 'abcdefg', 'abcdefg' - static Stream combineLatest7( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - T Function(A a, B b, C c, D d, E e, F f, G g) combiner) => - CombineLatestStream.combine7(streamA, streamB, streamC, streamD, streamE, - streamF, streamG, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function whenever any of the stream sequences emits an - /// item. - /// - /// The Stream will not emit until all streams have emitted at least one - /// item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) - /// - /// ### Example - /// - /// Rx.combineLatest8( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.value('f'), - /// Stream.value('g'), - /// Stream.fromIterable(['h', 'h']), - /// (a, b, c, d, e, f, g, h) => a + b + c + d + e + f + g + h) - /// .listen(print); //prints 'abcdefgh', 'abcdefgh' - static Stream combineLatest8( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - T Function(A a, B b, C c, D d, E e, F f, G g, H h) combiner) => - CombineLatestStream.combine8( - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH, - combiner, - ); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function whenever any of the stream sequences emits an - /// item. - /// - /// The Stream will not emit until all streams have emitted at least one - /// item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) - /// - /// ### Example - /// - /// Rx.combineLatest9( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.value('f'), - /// Stream.value('g'), - /// Stream.value('h'), - /// Stream.fromIterable(['i', 'i']), - /// (a, b, c, d, e, f, g, h, i) => a + b + c + d + e + f + g + h + i) - /// .listen(print); //prints 'abcdefghi', 'abcdefghi' - static Stream combineLatest9( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - Stream streamI, - T Function(A a, B b, C c, D d, E e, F f, G g, H h, I i) combiner) => - CombineLatestStream.combine9( - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH, - streamI, - combiner, - ); - - /// Concatenates all of the specified stream sequences, as long as the - /// previous stream sequence terminated successfully. - /// - /// It does this by subscribing to each stream one by one, emitting all items - /// and completing before subscribing to the next stream. - /// - /// If the provided streams is empty, the resulting sequence completes immediately - /// without emitting any items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#concat) - /// - /// ### Example - /// - /// Rx.concat([ - /// Stream.value(1), - /// Rx.timer(2, Duration(days: 1)), - /// Stream.value(3) - /// ]) - /// .listen(print); // prints 1, 2, 3 - static Stream concat(Iterable> streams) => - ConcatStream(streams); - - /// Concatenates all of the specified stream sequences, as long as the - /// previous stream sequence terminated successfully. - /// - /// In the case of concatEager, rather than subscribing to one stream after - /// the next, all streams are immediately subscribed to. The events are then - /// captured and emitted at the correct time, after the previous stream has - /// finished emitting items. - /// - /// If the provided streams is empty, the resulting sequence completes immediately - /// without emitting any items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#concat) - /// - /// ### Example - /// - /// Rx.concatEager([ - /// Stream.value(1), - /// Rx.timer(2, Duration(days: 1)), - /// Stream.value(3) - /// ]) - /// .listen(print); // prints 1, 2, 3 - static Stream concatEager(Iterable> streams) => - ConcatEagerStream(streams); - - /// The defer factory waits until an observer subscribes to it, and then it - /// creates a [Stream] with the given factory function. - /// - /// In some circumstances, waiting until the last minute (that is, until - /// subscription time) to generate the Stream can ensure that this - /// Stream contains the freshest data. - /// - /// By default, DeferStreams are single-subscription. However, it's possible - /// to make them reusable. - /// - /// ### Example - /// - /// Rx.defer(() => Stream.value(1)) - /// .listen(print); //prints 1 - static Stream defer(Stream Function() streamFactory, - {bool reusable = false}) => - DeferStream(streamFactory, reusable: reusable); - - /// Creates a [Stream] where all last events of existing stream(s) are piped - /// through a sink-transformation. - /// - /// This operator is best used when you have a group of streams - /// and only care about the final emitted value of each. - /// One common use case for this is if you wish to issue multiple - /// requests on page load (or some other event) - /// and only want to take action when a response has been received for all. - /// - /// In this way it is similar to how you might use [Future.wait]. - /// - /// Be aware that if any of the inner streams supplied to forkJoin error - /// you will lose the value of any other streams that would or have already - /// completed if you do not catch the error correctly on the inner stream. - /// - /// If you are only concerned with all inner streams completing - /// successfully you can catch the error on the outside. - /// It's also worth noting that if you have an stream - /// that emits more than one item, and you are concerned with the previous - /// emissions forkJoin is not the correct choice. - /// - /// In these cases you may better off with an operator like combineLatest or zip. - /// - /// If the provided streams is empty, the resulting sequence completes immediately - /// without emitting any items and without any calls to the combiner function. - /// - /// ### Example - /// - /// Rx.forkJoin([ - /// Stream.value('a'), - /// Stream.fromIterable(['b', 'c', 'd']) - /// ], (list) => list.join(', ')) - /// .listen(print); // prints 'a, d' - static Stream forkJoin( - Iterable> streams, R Function(List values) combiner) => - ForkJoinStream(streams, combiner); - - /// Merges the given Streams into a single Stream that emits a List of the - /// last values emitted by the source stream(s). This is helpful when you need to - /// forkJoin a dynamic number of Streams. - /// - /// If the provided streams is empty, the resulting sequence completes immediately - /// without emitting any items. - /// - /// ### Example - /// - /// Rx.forkJoinList([ - /// Stream.value(1), - /// Stream.fromIterable([0, 1, 2]), - /// ]) - /// .listen(print); // prints [1, 2] - static Stream> forkJoinList(Iterable> streams) => - ForkJoinStream.list(streams); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function when all of the stream sequences emits their - /// last item. - /// - /// ### Example - /// - /// Rx.forkJoin2( - /// Stream.value(1), - /// Stream.fromIterable([0, 1, 2]), - /// (a, b) => a + b) - /// .listen(print); //prints 3 - static Stream forkJoin2(Stream streamA, Stream streamB, - T Function(A a, B b) combiner) => - ForkJoinStream.join2(streamA, streamB, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function when all of the stream sequences emits their - /// last item. - /// - /// ### Example - /// - /// Rx.forkJoin3( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.fromIterable(['c', 'd']), - /// (a, b, c) => a + b + c) - /// .listen(print); //prints 'abd' - static Stream forkJoin3(Stream streamA, Stream streamB, - Stream streamC, T Function(A a, B b, C c) combiner) => - ForkJoinStream.join3(streamA, streamB, streamC, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function when all of the stream sequences emits their - /// last item. - /// - /// ### Example - /// - /// Rx.forkJoin4( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.fromIterable(['d', 'e']), - /// (a, b, c, d) => a + b + c + d) - /// .listen(print); //prints 'abce' - static Stream forkJoin4( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - T Function(A a, B b, C c, D d) combiner) => - ForkJoinStream.join4(streamA, streamB, streamC, streamD, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function when all of the stream sequences emits their - /// last item. - /// - /// ### Example - /// - /// Rx.forkJoin5( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.fromIterable(['e', 'f']), - /// (a, b, c, d, e) => a + b + c + d + e) - /// .listen(print); //prints 'abcdf' - static Stream forkJoin5( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - T Function(A a, B b, C c, D d, E e) combiner) => - ForkJoinStream.join5( - streamA, streamB, streamC, streamD, streamE, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function when all of the stream sequences emits their - /// last item. - /// - /// ### Example - /// - /// Rx.forkJoin6( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.fromIterable(['f', 'g']), - /// (a, b, c, d, e, f) => a + b + c + d + e + f) - /// .listen(print); //prints 'abcdeg' - static Stream forkJoin6( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - T Function(A a, B b, C c, D d, E e, F f) combiner) => - ForkJoinStream.join6( - streamA, streamB, streamC, streamD, streamE, streamF, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function when all of the stream sequences emits their - /// last item. - /// - /// ### Example - /// - /// Rx.forkJoin7( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.value('f'), - /// Stream.fromIterable(['g', 'h']), - /// (a, b, c, d, e, f, g) => a + b + c + d + e + f + g) - /// .listen(print); //prints 'abcdefh' - static Stream forkJoin7( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - T Function(A a, B b, C c, D d, E e, F f, G g) combiner) => - ForkJoinStream.join7(streamA, streamB, streamC, streamD, streamE, streamF, - streamG, combiner); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function when all of the stream sequences emits their - /// last item. - /// - /// ### Example - /// - /// Rx.forkJoin8( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.value('f'), - /// Stream.value('g'), - /// Stream.fromIterable(['h', 'i']), - /// (a, b, c, d, e, f, g, h) => a + b + c + d + e + f + g + h) - /// .listen(print); //prints 'abcdefgi' - static Stream forkJoin8( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - T Function(A a, B b, C c, D d, E e, F f, G g, H h) combiner) => - ForkJoinStream.join8( - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH, - combiner, - ); - - /// Merges the given Streams into a single Stream sequence by using the - /// [combiner] function when all of the stream sequences emits their - /// last item. - /// - /// ### Example - /// - /// Rx.forkJoin9( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.value('f'), - /// Stream.value('g'), - /// Stream.value('h'), - /// Stream.fromIterable(['i', 'j']), - /// (a, b, c, d, e, f, g, h, i) => a + b + c + d + e + f + g + h + i) - /// .listen(print); //prints 'abcdefghj' - static Stream forkJoin9( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - Stream streamI, - T Function(A a, B b, C c, D d, E e, F f, G g, H h, I i) combiner) => - ForkJoinStream.join9( - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH, - streamI, - combiner, - ); - - /// Returns a Stream that, when listening to it, calls a function you specify - /// and then emits the value returned from that function. - /// - /// If result from invoking [callable] function: - /// - Is a [Future]: when the future completes, this stream will fire one event, either - /// data or error, and then close with a done-event. - /// - Is a [T]: this stream emits a single data event and then completes with a done event. - /// - /// By default, a [FromCallableStream] is a single-subscription Stream. However, it's possible - /// to make them reusable. - /// This Stream is effectively equivalent to one created by - /// `(() async* { yield await callable() }())` or `(() async* { yield callable(); }())`. - /// - /// [ReactiveX doc](http://reactivex.io/documentation/operators/from.html) - /// - /// ### Example - /// - /// Rx.fromCallable(() => 'Value').listen(print); // prints Value - /// - /// Rx.fromCallable(() async { - /// await Future.delayed(const Duration(seconds: 1)); - /// return 'Value'; - /// }).listen(print); // prints Value - static Stream fromCallable(FutureOr Function() callable, - {bool reusable = false}) => - FromCallableStream(callable, reusable: reusable); - - /// Flattens the items emitted by the given [streams] into a single Stream - /// sequence. - /// - /// If the provided streams is empty, the resulting sequence completes immediately - /// without emitting any items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#merge) - /// - /// ### Example - /// - /// Rx.merge([ - /// Rx.timer(1, Duration(days: 10)), - /// Stream.value(2) - /// ]) - /// .listen(print); // prints 2, 1 - static Stream merge(Iterable> streams) => - MergeStream(streams); - - /// Returns a non-terminating stream sequence, which can be used to denote - /// an infinite duration. - /// - /// The never operator is one with very specific and limited behavior. These - /// are useful for testing purposes, and sometimes also for combining with - /// other Streams or as parameters to operators that expect other - /// Streams as parameters. - /// - /// ### Example - /// - /// Rx.never().listen(print); // Neither prints nor terminates - static Stream never() => NeverStream(); - - /// Given two or more source [streams], emit all of the items from only - /// the first of these [streams] to emit an item or notification. - /// - /// If the provided streams is empty, the resulting sequence completes immediately - /// without emitting any items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#amb) - /// - /// ### Example - /// - /// Rx.race([ - /// Rx.timer(1, Duration(days: 1)), - /// Rx.timer(2, Duration(days: 2)), - /// Rx.timer(3, Duration(seconds: 1)) - /// ]).listen(print); // prints 3 - static Stream race(Iterable> streams) => - RaceStream(streams); - - /// Returns a [Stream] that emits a sequence of Integers within a specified - /// range. - /// - /// ### Example - /// - /// Rx.range(1, 3).listen((i) => print(i)); // Prints 1, 2, 3 - /// - /// Rx.range(3, 1).listen((i) => print(i)); // Prints 3, 2, 1 - static Stream range(int startInclusive, int endInclusive) => - RangeStream(startInclusive, endInclusive); - - /// Creates a [Stream] that will recreate and re-listen to the source - /// Stream the specified number of times until the [Stream] terminates - /// successfully. - /// - /// If [count] is not specified, it repeats indefinitely. - /// - /// ### Example - /// - /// RepeatStream((int repeatCount) => - /// Stream.value('repeat index: $repeatCount'), 3) - /// .listen((i) => print(i)); // Prints 'repeat index: 0, repeat index: 1, repeat index: 2' - static Stream repeat(Stream Function(int repeatIndex) streamFactory, - [int? count]) => - RepeatStream(streamFactory, count); - - /// Creates a [Stream] that will recreate and re-listen to the source - /// Stream the specified number of times until the Stream terminates - /// successfully. - /// - /// If the retry count is not specified, it retries indefinitely. If the retry - /// count is met, but the Stream has not terminated successfully, all of the errors - /// and StackTraces that caused the failure will be emitted. - /// - /// ### Example - /// - /// Rx.retry(() => Stream.value(1)) - /// .listen((i) => print(i)); // Prints 1 - /// - /// Rx.retry( - /// () => Stream.value(1).concatWith([Stream.error(Error())]), - /// 1, - /// ).listen( - /// print, - /// onError: (Object e, StackTrace s) => print(e), - /// ); // Prints 1, 1, Instance of 'Error', Instance of 'Error' - static Stream retry(Stream Function() streamFactory, [int? count]) => - RetryStream(streamFactory, count); - - /// Creates a Stream that will recreate and re-listen to the source - /// Stream when the notifier emits a new value. If the source Stream - /// emits an error or it completes, the Stream terminates. - /// - /// If the [retryWhenFactory] throws an error or returns a Stream that emits an error, - /// original error will be emitted. And then, the error from [retryWhenFactory] will be emitted - /// if it is not identical with original error. - /// - /// ### Basic Example - /// - /// ```dart - /// Rx.retryWhen( - /// () => Stream.fromIterable([1]), - /// (Object error, StackTrace s) => throw error, - /// ).listen(print); // Prints 1 - /// ``` - /// - /// ### Periodic Example - /// - /// ```dart - /// Rx.retryWhen( - /// () => Stream.periodic(const Duration(seconds: 1), (int i) => i) - /// .map((int i) => i == 2 ? throw 'exception' : i), - /// (Object e, StackTrace s) => - /// Rx.timer(null, const Duration(milliseconds: 200)), - /// ).take(4).listen(print); // Prints 0, 1, 0, 1 - /// ``` - /// - /// ### Complex Example - /// - /// ```dart - /// var errorHappened = false; - /// Rx.retryWhen( - /// () => Stream.periodic(const Duration(seconds: 1), (i) => i).map((i) { - /// if (i == 3 && !errorHappened) { - /// throw 'We can take this. Please restart.'; - /// } else if (i == 4) { - /// throw 'It\'s enough.'; - /// } else { - /// return i; - /// } - /// }), - /// (e, s) { - /// errorHappened = true; - /// if (e == 'We can take this. Please restart.') { - /// return Stream.value('Ok. Here you go!'); - /// } else { - /// return Stream.error(e, s); - /// } - /// }, - /// ).listen(print, onError: print); // Prints 0, 1, 2, 0, 1, 2, 3, It's enough. - /// ``` - static Stream retryWhen( - Stream Function() streamFactory, - Stream Function(Object error, StackTrace stackTrace) retryWhenFactory, - ) => - RetryWhenStream(streamFactory, retryWhenFactory); - - /// Determine whether two Streams emit the same sequence of items. - /// You can provide an optional [equals] handler to determine equality. - /// - /// [Interactive marble diagram](https://rxmarbles.com/#sequenceEqual) - /// - /// ### Example - /// - /// Rx.sequenceEqual([ - /// Stream.fromIterable([1, 2, 3, 4, 5]), - /// Stream.fromIterable([1, 2, 3, 4, 5]) - /// ]) - /// .listen(print); // prints true - static Stream sequenceEqual( - Stream stream, - Stream other, { - bool Function(A a, B b)? equals, - bool Function(ErrorAndStackTrace, ErrorAndStackTrace)? errorEquals, - }) => - SequenceEqualStream( - stream, - other, - dataEquals: equals, - errorEquals: errorEquals, - ); - - /// Convert a Stream that emits Streams (aka a 'Higher Order Stream') into a - /// single Stream that emits the items emitted by the most-recently-emitted of - /// those Streams. - /// - /// This Stream will unsubscribe from the previously-emitted Stream when - /// a new Stream is emitted from the source Stream and subscribe to the new - /// Stream. - /// - /// ### Example - /// - /// ```dart - /// final switchLatestStream = SwitchLatestStream( - /// Stream.fromIterable(>[ - /// Rx.timer('A', Duration(seconds: 2)), - /// Rx.timer('B', Duration(seconds: 1)), - /// Stream.value('C'), - /// ]), - /// ); - /// - /// // Since the first two Streams do not emit data for 1-2 seconds, and the - /// // 3rd Stream will be emitted before that time, only data from the 3rd - /// // Stream will be emitted to the listener. - /// switchLatestStream.listen(print); // prints 'C' - /// ``` - static Stream switchLatest(Stream> streams) => - SwitchLatestStream(streams); - - /// Emits the given value after a specified amount of time. - /// - /// ### Example - /// - /// Rx.timer('hi', Duration(minutes: 1)) - /// .listen((i) => print(i)); // print 'hi' after 1 minute - static Stream timer(T value, Duration duration) => - TimerStream(value, duration); - - /// When listener listens to it, creates a resource object from resource factory function, - /// and creates a [Stream] from the given factory function and resource as argument. - /// Finally when the stream finishes emitting items or stream subscription - /// is cancelled (call [StreamSubscription.cancel] or `Stream.listen(cancelOnError: true)`), - /// call the disposer function on resource object. - /// - /// The [UsingStream] is a way you can instruct an Stream to create - /// a resource that exists only during the lifespan of the Stream - /// and is disposed of when the Stream terminates. - /// - /// [Marble diagram](http://reactivex.io/documentation/operators/images/using.c.png) - /// - /// ### Example - /// - /// Rx.using>( - /// resourceFactory: () => Queue.of([1, 2, 3]), - /// streamFactory: (r) => Stream.fromIterable(r), - /// disposer: (r) => r.clear(), - /// ).listen(print); // prints 1, 2, 3 - static Stream using({ - required FutureOr Function() resourceFactory, - required Stream Function(R) streamFactory, - required FutureOr Function(R) disposer, - }) => - UsingStream( - resourceFactory: resourceFactory, - streamFactory: streamFactory, - disposer: disposer, - ); - - /// Merges the specified streams into one stream sequence using the given - /// zipper function whenever all of the stream sequences have produced - /// an element at a corresponding index. - /// - /// It applies this function in strict sequence, so the first item emitted by - /// the new Stream will be the result of the function applied to the first - /// item emitted by Stream #1 and the first item emitted by Stream #2; - /// the second item emitted by the new ZipStream will be the result of - /// the function applied to the second item emitted by Stream #1 and the - /// second item emitted by Stream #2; and so forth. It will only emit as - /// many items as the number of items emitted by the source Stream that - /// emits the fewest items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#zip) - /// - /// ### Example - /// - /// Rx.zip2( - /// Stream.value('Hi '), - /// Stream.fromIterable(['Friend', 'Dropped']), - /// (a, b) => a + b) - /// .listen(print); // prints 'Hi Friend' - static Stream zip2( - Stream streamA, Stream streamB, T Function(A a, B b) zipper) => - ZipStream.zip2(streamA, streamB, zipper); - - /// Merges the iterable streams into one stream sequence using the given - /// zipper function whenever all of the stream sequences have produced - /// an element at a corresponding index. - /// - /// It applies this function in strict sequence, so the first item emitted by - /// the new Stream will be the result of the function applied to the first - /// item emitted by Stream #1 and the first item emitted by Stream #2; - /// the second item emitted by the new ZipStream will be the result of - /// the function applied to the second item emitted by Stream #1 and the - /// second item emitted by Stream #2; and so forth. It will only emit as - /// many items as the number of items emitted by the source Stream that - /// emits the fewest items. - /// - /// If the provided streams is empty, the resulting sequence completes immediately - /// without emitting any items and without any calls to the zipper function. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#zip) - /// - /// ### Example - /// - /// Rx.zip( - /// [ - /// Stream.value('Hi '), - /// Stream.fromIterable(['Friend', 'Dropped']), - /// ], - /// (values) => values.first + values.last - /// ) - /// .listen(print); // prints 'Hi Friend' - static Stream zip( - Iterable> streams, R Function(List values) zipper) => - ZipStream(streams, zipper); - - /// Merges the iterable streams into one stream sequence using the given - /// zipper function whenever all of the stream sequences have produced - /// an element at a corresponding index. - /// - /// It applies this function in strict sequence, so the first item emitted by - /// the new Stream will be the result of the function applied to the first - /// item emitted by Stream #1 and the first item emitted by Stream #2; - /// the second item emitted by the new ZipStream will be the result of - /// the function applied to the second item emitted by Stream #1 and the - /// second item emitted by Stream #2; and so forth. It will only emit as - /// many items as the number of items emitted by the source Stream that - /// emits the fewest items. - /// - /// If the provided streams is empty, the resulting sequence completes immediately - /// without emitting any items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#zip) - /// - /// ### Example - /// - /// Rx.zipList( - /// [ - /// Stream.value('Hi '), - /// Stream.fromIterable(['Friend', 'Dropped']), - /// ], - /// ) - /// .listen(print); // prints ['Hi ', 'Friend'] - static Stream> zipList(Iterable> streams) => - ZipStream.list(streams); - - /// Merges the specified streams into one stream sequence using the given - /// zipper function whenever all of the stream sequences have produced - /// an element at a corresponding index. - /// - /// It applies this function in strict sequence, so the first item emitted by - /// the new Stream will be the result of the function applied to the first - /// item emitted by Stream #1 and the first item emitted by Stream #2; - /// the second item emitted by the new ZipStream will be the result of - /// the function applied to the second item emitted by Stream #1 and the - /// second item emitted by Stream #2; and so forth. It will only emit as - /// many items as the number of items emitted by the source Stream that - /// emits the fewest items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#zip) - /// - /// ### Example - /// - /// Rx.zip3( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.fromIterable(['c', 'dropped']), - /// (a, b, c) => a + b + c) - /// .listen(print); //prints 'abc' - static Stream zip3(Stream streamA, Stream streamB, - Stream streamC, T Function(A a, B b, C c) zipper) => - ZipStream.zip3(streamA, streamB, streamC, zipper); - - /// Merges the specified streams into one stream sequence using the given - /// zipper function whenever all of the stream sequences have produced - /// an element at a corresponding index. - /// - /// It applies this function in strict sequence, so the first item emitted by - /// the new Stream will be the result of the function applied to the first - /// item emitted by Stream #1 and the first item emitted by Stream #2; - /// the second item emitted by the new ZipStream will be the result of - /// the function applied to the second item emitted by Stream #1 and the - /// second item emitted by Stream #2; and so forth. It will only emit as - /// many items as the number of items emitted by the source Stream that - /// emits the fewest items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#zip) - /// - /// ### Example - /// - /// Rx.zip4( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.fromIterable(['d', 'dropped']), - /// (a, b, c, d) => a + b + c + d) - /// .listen(print); //prints 'abcd' - static Stream zip4( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - T Function(A a, B b, C c, D d) zipper) => - ZipStream.zip4(streamA, streamB, streamC, streamD, zipper); - - /// Merges the specified streams into one stream sequence using the given - /// zipper function whenever all of the stream sequences have produced - /// an element at a corresponding index. - /// - /// It applies this function in strict sequence, so the first item emitted by - /// the new Stream will be the result of the function applied to the first - /// item emitted by Stream #1 and the first item emitted by Stream #2; - /// the second item emitted by the new ZipStream will be the result of - /// the function applied to the second item emitted by Stream #1 and the - /// second item emitted by Stream #2; and so forth. It will only emit as - /// many items as the number of items emitted by the source Stream that - /// emits the fewest items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#zip) - /// - /// ### Example - /// - /// Rx.zip5( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.fromIterable(['e', 'dropped']), - /// (a, b, c, d, e) => a + b + c + d + e) - /// .listen(print); //prints 'abcde' - static Stream zip5( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - T Function(A a, B b, C c, D d, E e) zipper) => - ZipStream.zip5(streamA, streamB, streamC, streamD, streamE, zipper); - - /// Merges the specified streams into one stream sequence using the given - /// zipper function whenever all of the stream sequences have produced - /// an element at a corresponding index. - /// - /// It applies this function in strict sequence, so the first item emitted by - /// the new Stream will be the result of the function applied to the first - /// item emitted by Stream #1 and the first item emitted by Stream #2; - /// the second item emitted by the new ZipStream will be the result of - /// the function applied to the second item emitted by Stream #1 and the - /// second item emitted by Stream #2; and so forth. It will only emit as - /// many items as the number of items emitted by the source Stream that - /// emits the fewest items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#zip) - /// - /// ### Example - /// - /// Rx.zip6( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.fromIterable(['f', 'dropped']), - /// (a, b, c, d, e, f) => a + b + c + d + e + f) - /// .listen(print); //prints 'abcdef' - static Stream zip6( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - T Function(A a, B b, C c, D d, E e, F f) zipper) => - ZipStream.zip6( - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - zipper, - ); - - /// Merges the specified streams into one stream sequence using the given - /// zipper function whenever all of the stream sequences have produced - /// an element at a corresponding index. - /// - /// It applies this function in strict sequence, so the first item emitted by - /// the new Stream will be the result of the function applied to the first - /// item emitted by Stream #1 and the first item emitted by Stream #2; - /// the second item emitted by the new ZipStream will be the result of - /// the function applied to the second item emitted by Stream #1 and the - /// second item emitted by Stream #2; and so forth. It will only emit as - /// many items as the number of items emitted by the source Stream that - /// emits the fewest items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#zip) - /// - /// ### Example - /// - /// Rx.zip7( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.value('f'), - /// Stream.fromIterable(['g', 'dropped']), - /// (a, b, c, d, e, f, g) => a + b + c + d + e + f + g) - /// .listen(print); //prints 'abcdefg' - static Stream zip7( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - T Function(A a, B b, C c, D d, E e, F f, G g) zipper) => - ZipStream.zip7( - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - zipper, - ); - - /// Merges the specified streams into one stream sequence using the given - /// zipper function whenever all of the stream sequences have produced - /// an element at a corresponding index. - /// - /// It applies this function in strict sequence, so the first item emitted by - /// the new Stream will be the result of the function applied to the first - /// item emitted by Stream #1 and the first item emitted by Stream #2; - /// the second item emitted by the new ZipStream will be the result of - /// the function applied to the second item emitted by Stream #1 and the - /// second item emitted by Stream #2; and so forth. It will only emit as - /// many items as the number of items emitted by the source Stream that - /// emits the fewest items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#zip) - /// - /// ### Example - /// - /// Rx.zip8( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.value('f'), - /// Stream.value('g'), - /// Stream.fromIterable(['h', 'dropped']), - /// (a, b, c, d, e, f, g, h) => a + b + c + d + e + f + g + h) - /// .listen(print); //prints 'abcdefgh' - static Stream zip8( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - T Function(A a, B b, C c, D d, E e, F f, G g, H h) zipper) => - ZipStream.zip8( - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH, - zipper, - ); - - /// Merges the specified streams into one stream sequence using the given - /// zipper function whenever all of the stream sequences have produced - /// an element at a corresponding index. - /// - /// It applies this function in strict sequence, so the first item emitted by - /// the new Stream will be the result of the function applied to the first - /// item emitted by Stream #1 and the first item emitted by Stream #2; - /// the second item emitted by the new ZipStream will be the result of - /// the function applied to the second item emitted by Stream #1 and the - /// second item emitted by Stream #2; and so forth. It will only emit as - /// many items as the number of items emitted by the source Stream that - /// emits the fewest items. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#zip) - /// - /// ### Example - /// - /// Rx.zip9( - /// Stream.value('a'), - /// Stream.value('b'), - /// Stream.value('c'), - /// Stream.value('d'), - /// Stream.value('e'), - /// Stream.value('f'), - /// Stream.value('g'), - /// Stream.value('h'), - /// Stream.fromIterable(['i', 'dropped']), - /// (a, b, c, d, e, f, g, h, i) => a + b + c + d + e + f + g + h + i) - /// .listen(print); //prints 'abcdefghi' - static Stream zip9( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - Stream streamI, - T Function(A a, B b, C c, D d, E e, F f, G g, H h, I i) zipper) => - ZipStream.zip9( - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH, - streamI, - zipper, - ); -} diff --git a/sandbox/reactivex/lib/src/streams/combine_latest.dart b/sandbox/reactivex/lib/src/streams/combine_latest.dart deleted file mode 100644 index 9ed4331..0000000 --- a/sandbox/reactivex/lib/src/streams/combine_latest.dart +++ /dev/null @@ -1,352 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/collection_extensions.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -/// Merges the given Streams into one Stream sequence by using the -/// combiner function whenever any of the source stream sequences emits an -/// item. -/// -/// The Stream will not emit until all Streams have emitted at least one -/// item. -/// -/// If the provided streams is empty, the resulting sequence completes immediately -/// without emitting any items and without any calls to the combiner function. -/// -/// [Interactive marble diagram](http://rxmarbles.com/#combineLatest) -/// -/// ### Basic Example -/// -/// This constructor takes in an `Iterable>` and outputs a -/// `Stream>` whenever any of the values change from the source -/// stream. This is useful with a dynamic number of source streams! -/// -/// CombineLatestStream.list([ -/// Stream.fromIterable(['a']), -/// Stream.fromIterable(['b']), -/// Stream.fromIterable(['C', 'D'])]) -/// .listen(print); //prints ['a', 'b', 'C'], ['a', 'b', 'D'] -/// -/// ### Example with combiner -/// -/// If you wish to combine the list of values into a new object before you -/// -/// CombineLatestStream( -/// [ -/// Stream.fromIterable(['a']), -/// Stream.fromIterable(['b']), -/// Stream.fromIterable(['C', 'D']) -/// ], -/// (values) => values.last -/// ) -/// .listen(print); //prints 'C', 'D' -/// -/// ### Example with a specific number of Streams -/// -/// If you wish to combine a specific number of Streams together with proper -/// types information for the value of each Stream, use the -/// [combine2] - [combine9] operators. -/// -/// CombineLatestStream.combine2( -/// Stream.fromIterable([1]), -/// Stream.fromIterable([2, 3]), -/// (a, b) => a + b, -/// ) -/// .listen(print); // prints 3, 4 -class CombineLatestStream extends StreamView { - /// Constructs a [Stream] that observes an [Iterable] of [Stream] - /// and builds a [List] containing all latest events emitted by the provided [Iterable] of [Stream]. - /// The [combiner] maps this [List] into a new event of type [R] - CombineLatestStream( - Iterable> streams, - R Function(List values) combiner, - ) : super(_buildController(streams, combiner).stream); - - /// Constructs a [CombineLatestStream] using a default combiner, which simply - /// yields a [List] of all latest events emitted by the provided [Iterable] of [Stream]. - static CombineLatestStream> list( - Iterable> streams, - ) => - CombineLatestStream>( - streams, - (List values) => values, - ); - - /// Constructs a [CombineLatestStream] from a pair of [Stream]s - /// where [combiner] is used to create a new event of type [R], based on the - /// latest events emitted by the provided [Stream]s. - static CombineLatestStream combine2( - Stream streamOne, - Stream streamTwo, - R Function(A a, B b) combiner, - ) => - CombineLatestStream( - [streamOne, streamTwo], - (List values) => combiner(values[0] as A, values[1] as B), - ); - - /// Constructs a [CombineLatestStream] from 3 [Stream]s - /// where [combiner] is used to create a new event of type [R], based on the - /// latest events emitted by the provided [Stream]s. - static CombineLatestStream combine3( - Stream streamA, - Stream streamB, - Stream streamC, - R Function(A a, B b, C c) combiner, - ) => - CombineLatestStream( - [streamA, streamB, streamC], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - ); - }, - ); - - /// Constructs a [CombineLatestStream] from 4 [Stream]s - /// where [combiner] is used to create a new event of type [R], based on the - /// latest events emitted by the provided [Stream]s. - static CombineLatestStream combine4( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - R Function(A a, B b, C c, D d) combiner, - ) => - CombineLatestStream( - [streamA, streamB, streamC, streamD], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - ); - }, - ); - - /// Constructs a [CombineLatestStream] from 5 [Stream]s - /// where [combiner] is used to create a new event of type [R], based on the - /// latest events emitted by the provided [Stream]s. - static CombineLatestStream combine5( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - R Function(A a, B b, C c, D d, E e) combiner, - ) => - CombineLatestStream( - [streamA, streamB, streamC, streamD, streamE], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - ); - }, - ); - - /// Constructs a [CombineLatestStream] from 6 [Stream]s - /// where [combiner] is used to create a new event of type [R], based on the - /// latest events emitted by the provided [Stream]s. - static CombineLatestStream combine6( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - R Function(A a, B b, C c, D d, E e, F f) combiner, - ) => - CombineLatestStream( - [streamA, streamB, streamC, streamD, streamE, streamF], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - ); - }, - ); - - /// Constructs a [CombineLatestStream] from 7 [Stream]s - /// where [combiner] is used to create a new event of type [R], based on the - /// latest events emitted by the provided [Stream]s. - static CombineLatestStream combine7( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - R Function(A a, B b, C c, D d, E e, F f, G g) combiner, - ) => - CombineLatestStream( - [streamA, streamB, streamC, streamD, streamE, streamF, streamG], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - ); - }, - ); - - /// Constructs a [CombineLatestStream] from 8 [Stream]s - /// where [combiner] is used to create a new event of type [R], based on the - /// latest events emitted by the provided [Stream]s. - static CombineLatestStream combine8( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - R Function(A a, B b, C c, D d, E e, F f, G g, H h) combiner, - ) => - CombineLatestStream( - [ - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH - ], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - values[7] as H, - ); - }, - ); - - /// Constructs a [CombineLatestStream] from 9 [Stream]s - /// where [combiner] is used to create a new event of type [R], based on the - /// latest events emitted by the provided [Stream]s. - static CombineLatestStream combine9( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - Stream streamI, - R Function(A a, B b, C c, D d, E e, F f, G g, H h, I i) combiner, - ) => - CombineLatestStream( - [ - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH, - streamI - ], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - values[7] as H, - values[8] as I, - ); - }, - ); - - static StreamController _buildController( - Iterable> streams, - R Function(List values) combiner, - ) { - final controller = StreamController(sync: true); - late List> subscriptions; - List? values; - - controller.onListen = () { - var triggered = 0, completed = 0; - - void onDone() { - if (++completed == subscriptions.length) { - controller.close(); - } - } - - subscriptions = streams.mapIndexed((index, stream) { - var hasFirstEvent = false; - - return stream.listen( - (T value) { - if (values == null) { - return; - } - - values![index] = value; - - if (!hasFirstEvent) { - hasFirstEvent = true; - triggered++; - } - - if (triggered == subscriptions.length) { - final R combined; - try { - combined = combiner(List.unmodifiable(values!)); - } catch (e, s) { - controller.addError(e, s); - return; - } - controller.add(combined); - } - }, - onError: controller.addError, - onDone: onDone, - ); - }).toList(growable: false); - if (subscriptions.isEmpty) { - controller.close(); - } else { - values = List.filled(subscriptions.length, null); - } - }; - controller.onPause = () => subscriptions.pauseAll(); - controller.onResume = () => subscriptions.resumeAll(); - controller.onCancel = () { - values = null; - return subscriptions.cancelAll(); - }; - - return controller; - } -} diff --git a/sandbox/reactivex/lib/src/streams/concat.dart b/sandbox/reactivex/lib/src/streams/concat.dart deleted file mode 100644 index a451e6a..0000000 --- a/sandbox/reactivex/lib/src/streams/concat.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'dart:async'; - -/// Concatenates all of the specified stream sequences, as long as the -/// previous stream sequence terminated successfully. -/// -/// It does this by subscribing to each stream one by one, emitting all items -/// and completing before subscribing to the next stream. -/// -/// If the provided streams is empty, the resulting sequence completes immediately -/// without emitting any items. -/// -/// [Interactive marble diagram](http://rxmarbles.com/#concat) -/// -/// ### Example -/// -/// ConcatStream([ -/// Stream.fromIterable([1]), -/// TimerStream(2, Duration(days: 1)), -/// Stream.fromIterable([3]) -/// ]) -/// .listen(print); // prints 1, 2, 3 -class ConcatStream extends StreamView { - /// Constructs a [Stream] which emits all events from [streams]. - /// The [Iterable] is traversed upwards, meaning that the current first - /// [Stream] in the [Iterable] needs to complete, before events from the - /// next [Stream] will be subscribed to. - ConcatStream(Iterable> streams) - : super(_buildController(streams).stream); - - static StreamController _buildController(Iterable> streams) { - final controller = StreamController(sync: true); - StreamSubscription? subscription; - - controller.onListen = () { - final iterator = streams.iterator; - - void moveNext() { - if (!iterator.moveNext()) { - controller.close(); - return; - } - subscription?.cancel(); - subscription = iterator.current.listen(controller.add, - onError: controller.addError, onDone: moveNext); - } - - moveNext(); - }; - controller.onPause = () => subscription?.pause(); - controller.onResume = () => subscription?.resume(); - controller.onCancel = () => subscription?.cancel(); - - return controller; - } -} - -/// Extends the Stream class with the ability to concatenate one stream with -/// another. -extension ConcatExtensions on Stream { - /// Returns a Stream that emits all items from the current Stream, - /// then emits all items from the given streams, one after the next. - /// - /// ### Example - /// - /// TimerStream(1, Duration(seconds: 10)) - /// .concatWith([Stream.fromIterable([2])]) - /// .listen(print); // prints 1, 2 - Stream concatWith(Iterable> other) { - final concatStream = ConcatStream([this, ...other]); - - return isBroadcast - ? concatStream.asBroadcastStream(onCancel: (s) => s.cancel()) - : concatStream; - } -} diff --git a/sandbox/reactivex/lib/src/streams/concat_eager.dart b/sandbox/reactivex/lib/src/streams/concat_eager.dart deleted file mode 100644 index 17beddd..0000000 --- a/sandbox/reactivex/lib/src/streams/concat_eager.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/streams/concat.dart'; -import 'package:angel3_reactivex/src/utils/collection_extensions.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -/// Concatenates all of the specified stream sequences, as long as the -/// previous stream sequence terminated successfully. -/// -/// In the case of concatEager, rather than subscribing to one stream after -/// the next, all streams are immediately subscribed to. The events are then -/// captured and emitted at the correct time, after the previous stream has -/// finished emitting items. -/// -/// If the provided streams is empty, the resulting sequence completes immediately -/// without emitting any items. -/// -/// [Interactive marble diagram](http://rxmarbles.com/#concat) -/// -/// ### Example -/// -/// ConcatEagerStream([ -/// Stream.fromIterable([1]), -/// TimerStream(2, Duration(days: 1)), -/// Stream.fromIterable([3]) -/// ]) -/// .listen(print); // prints 1, 2, 3 -class ConcatEagerStream extends StreamView { - /// Constructs a [Stream] which emits all events from [streams]. - /// Unlike [ConcatStream], all [Stream]s inside [streams] are - /// immediately subscribed to and events captured at the correct time, - /// but emitted only after the previous [Stream] in [streams] is - /// successfully closed. - ConcatEagerStream(Iterable> streams) - : super(_buildController(streams).stream); - - static StreamController _buildController(Iterable> streams) { - final controller = StreamController(sync: true); - late List> subscriptions; - StreamSubscription? activeSubscription; - - controller.onListen = () { - final completeEvents = >[]; - - void Function() onDone(int index) { - return () { - if (index < subscriptions.length - 1) { - completeEvents[index].complete(); - activeSubscription = subscriptions[index + 1]; - } else if (index == subscriptions.length - 1) { - controller.close(); - } - }; - } - - StreamSubscription createSubscription(int index, Stream stream) { - final subscription = stream.listen(controller.add, - onError: controller.addError, onDone: onDone(index)); - - // pause all subscriptions, except the first, initially - if (index > 0) { - final completer = Completer.sync(); - completeEvents.add(completer); - subscription.pause(completer.future); - } - - return subscription; - } - - subscriptions = - streams.mapIndexed(createSubscription).toList(growable: false); - if (subscriptions.isEmpty) { - controller.close(); - } else { - // initially, the very first subscription is the active one - activeSubscription = subscriptions.first; - } - }; - controller.onPause = () => activeSubscription?.pause(); - controller.onResume = () => activeSubscription?.resume(); - controller.onCancel = () { - activeSubscription = null; - return subscriptions.cancelAll(); - }; - - return controller; - } -} diff --git a/sandbox/reactivex/lib/src/streams/connectable_stream.dart b/sandbox/reactivex/lib/src/streams/connectable_stream.dart deleted file mode 100644 index bf4a5cb..0000000 --- a/sandbox/reactivex/lib/src/streams/connectable_stream.dart +++ /dev/null @@ -1,516 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/streams/replay_stream.dart'; -import 'package:angel3_reactivex/src/streams/value_stream.dart'; -import 'package:angel3_reactivex/src/utils/notification.dart'; -import 'package:angel3_reactivex/subjects.dart'; - -/// A ConnectableStream resembles an ordinary Stream, except that it -/// can be listened to multiple times and does not begin emitting items when -/// it is listened to, but only when its [connect] method is called. -/// -/// This class can be used to broadcast a single-subscription Stream, and -/// can be used to wait for all intended Observers to [listen] to the -/// Stream before it begins emitting items. -abstract class ConnectableStream extends StreamView { - /// Constructs a [Stream] which only begins emitting events when - /// the [connect] method is called. - ConnectableStream(Stream stream) : super(stream); - - /// Returns a [Stream] that automatically connects (at most once) to this - /// ConnectableStream when the first Observer subscribes. - /// - /// To disconnect from the source Stream, provide a [connection] callback and - /// cancel the `subscription` at the appropriate time. - Stream autoConnect({ - void Function(StreamSubscription subscription) connection, - }); - - /// Instructs the [ConnectableStream] to begin emitting items from the - /// source Stream. To disconnect from the source stream, cancel the - /// subscription. - StreamSubscription connect(); - - /// Returns a [Stream] that stays connected to this ConnectableStream - /// as long as there is at least one subscription to this - /// ConnectableStream. - Stream refCount(); -} - -enum _ConnectableStreamUse { - autoConnect, - connect, - refCount, -} - -/// Base class for implementations of [ConnectableStream]. -/// [S] is type of the forwarding [Subject]. -/// [R] is return type of [autoConnect] and [refCount] (type constraint: `S extends R`). -abstract class AbstractConnectableStream, - R extends Stream> extends ConnectableStream { - final Stream _source; - final S _subject; - _ConnectableStreamUse? _use; - - /// Constructs a [AbstractConnectableStream] with a source [Stream] and the forwarding [Subject]. - AbstractConnectableStream( - Stream source, - S subject, - ) : assert(subject is R), - _source = source, - _subject = subject, - super(subject); - - late final _connection = ConnectableStreamSubscription( - _source.listen( - _subject.add, - onError: _subject.addError, - onDone: _subject.close, - ), - _subject, - ); - - bool _canReuse(_ConnectableStreamUse use) { - if (_use != null && _use != use) { - throw StateError( - 'Do not mix autoConnect, connect and refCount together, you should only use one of them!'); - } - - final canReuse = _use != null && _use == use; - _use = use; - return canReuse; - } - - @override - R autoConnect({ - void Function(StreamSubscription subscription)? connection, - }) { - if (_canReuse(_ConnectableStreamUse.autoConnect)) { - return _subject as R; - } - - _subject.onListen = () { - final subscription = _connection; - connection?.call(subscription); - }; - _subject.onCancel = null; - - return _subject as R; - } - - @override - StreamSubscription connect() { - if (_canReuse(_ConnectableStreamUse.connect)) { - return _connection; - } - - _subject.onListen = _subject.onCancel = null; - return _connection; - } - - @override - R refCount() { - if (_canReuse(_ConnectableStreamUse.refCount)) { - return _subject as R; - } - - StreamSubscription? subscription; - _subject.onListen = () => subscription = _connection; - _subject.onCancel = () => subscription?.cancel(); - - return _subject as R; - } -} - -/// A [ConnectableStream] that converts a single-subscription Stream into -/// a broadcast [Stream]. -class PublishConnectableStream - extends AbstractConnectableStream, Stream> { - /// Constructs a [Stream] which only begins emitting events when - /// the [connect] method is called, this [Stream] acts like a - /// [PublishSubject]. - PublishConnectableStream(Stream source, {bool sync = false}) - : super(source, PublishSubject(sync: sync)); -} - -/// A [ConnectableStream] that converts a single-subscription Stream into -/// a broadcast Stream that replays the latest value to any new listener, and -/// provides synchronous access to the latest emitted value. -class ValueConnectableStream - extends AbstractConnectableStream, ValueStream> - implements ValueStream { - /// Constructs a [Stream] which only begins emitting events when - /// the [connect] method is called, this [Stream] acts like a - /// [BehaviorSubject]. - ValueConnectableStream(Stream source, {bool sync = false}) - : super(source, BehaviorSubject(sync: sync)); - - /// Constructs a [Stream] which only begins emitting events when - /// the [connect] method is called, this [Stream] acts like a - /// [BehaviorSubject.seeded]. - ValueConnectableStream.seeded(Stream source, T seedValue, - {bool sync = false}) - : super(source, BehaviorSubject.seeded(seedValue, sync: sync)); - - @override - bool get hasValue => _subject.hasValue; - - @override - T get value => _subject.value; - - @override - T? get valueOrNull => _subject.valueOrNull; - - @override - Object get error => _subject.error; - - @override - Object? get errorOrNull => _subject.errorOrNull; - - @override - bool get hasError => _subject.hasError; - - @override - StackTrace? get stackTrace => _subject.stackTrace; - - @override - StreamNotification? get lastEventOrNull => _subject.lastEventOrNull; -} - -/// A [ConnectableStream] that converts a single-subscription Stream into -/// a broadcast Stream that replays emitted items to any new listener, and -/// provides synchronous access to the list of emitted values. -class ReplayConnectableStream - extends AbstractConnectableStream, ReplayStream> - implements ReplayStream { - /// Constructs a [Stream] which only begins emitting events when - /// the [connect] method is called, this [Stream] acts like a - /// [ReplaySubject]. - ReplayConnectableStream(Stream stream, {int? maxSize, bool sync = false}) - : super( - stream, - ReplaySubject(maxSize: maxSize, sync: sync), - ); - - @override - List get values => _subject.values; - - @override - List get errors => _subject.errors; - - @override - List get stackTraces => _subject.stackTraces; -} - -/// A special [StreamSubscription] that not only cancels the connection to -/// the source [Stream], but also closes down a subject that drives the Stream. -class ConnectableStreamSubscription extends StreamSubscription { - final StreamSubscription _source; - final Subject _subject; - - /// Constructs a special [StreamSubscription], which will close the provided subject - /// when [cancel] is called. - ConnectableStreamSubscription(this._source, this._subject); - - @override - Future cancel() => - _source.cancel().then((_) => _subject.close()); - - @override - Never asFuture([E? futureValue]) => _unsupportedError(); - - @override - bool get isPaused => _source.isPaused; - - @override - Never onData(void Function(T data)? handleData) => _unsupportedError(); - - @override - Never onDone(void Function()? handleDone) => _unsupportedError(); - - @override - Never onError(Function? handleError) => _unsupportedError(); - - @override - void pause([Future? resumeSignal]) => _source.pause(resumeSignal); - - @override - void resume() => _source.resume(); - - Never _unsupportedError() => throw UnsupportedError( - 'Cannot change handlers of ConnectableStreamSubscription.'); -} - -/// Extends the Stream class with the ability to transform a single-subscription -/// Stream into a ConnectableStream. -extension ConnectableStreamExtensions on Stream { - /// Convert the current Stream into a [ConnectableStream] that can be listened - /// to multiple times. It will not begin emitting items from the original - /// Stream until the `connect` method is invoked. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream. - /// - /// ### Example - /// - /// ``` - /// final source = Stream.fromIterable([1, 2, 3]); - /// final connectable = source.publish(); - /// - /// // Does not print anything at first - /// connectable.listen(print); - /// - /// // Start listening to the source Stream. Will cause the previous - /// // line to start printing 1, 2, 3 - /// final subscription = connectable.connect(); - /// await Future(() {}); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // Subject - /// subscription.cancel(); - /// ``` - PublishConnectableStream publish() => - PublishConnectableStream(this, sync: true); - - /// Convert the current Stream into a [ValueConnectableStream] - /// that can be listened to multiple times. It will not begin emitting items - /// from the original Stream until the `connect` method is invoked. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream that replays the latest emitted value to any new - /// listener. It also provides access to the latest value synchronously. - /// - /// ### Example - /// - /// ``` - /// final source = Stream.fromIterable([1, 2, 3]); - /// final connectable = source.publishValue(); - /// - /// // Does not print anything at first - /// connectable.listen(print); - /// - /// // Start listening to the source Stream. Will cause the previous - /// // line to start printing 1, 2, 3 - /// final subscription = connectable.connect(); - /// - /// // Late subscribers will receive the last emitted value - /// connectable.listen(print); // Prints 3 - /// await Future(() {}); - /// - /// // Can access the latest emitted value synchronously. Prints 3 - /// print(connectable.value); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // BehaviorSubject - /// subscription.cancel(); - /// ``` - ValueConnectableStream publishValue() => - ValueConnectableStream(this, sync: true); - - /// Convert the current Stream into a [ValueConnectableStream] - /// that can be listened to multiple times, providing an initial seeded value. - /// It will not begin emitting items from the original Stream - /// until the `connect` method is invoked. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream that replays the latest emitted value to any new - /// listener. It also provides access to the latest value synchronously. - /// - /// ### Example - /// - /// ``` - /// final source = Stream.fromIterable([1, 2, 3]); - /// final connectable = source.publishValueSeeded(0); - /// - /// // Does not print anything at first - /// connectable.listen(print); - /// - /// // Start listening to the source Stream. Will cause the previous - /// // line to start printing 0, 1, 2, 3 - /// final subscription = connectable.connect(); - /// - /// // Late subscribers will receive the last emitted value - /// connectable.listen(print); // Prints 3 - /// await Future(() {}); - /// - /// // Can access the latest emitted value synchronously. Prints 3 - /// print(connectable.value); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // BehaviorSubject - /// subscription.cancel(); - /// ``` - ValueConnectableStream publishValueSeeded(T seedValue) => - ValueConnectableStream.seeded(this, seedValue, sync: true); - - /// Convert the current Stream into a [ReplayConnectableStream] - /// that can be listened to multiple times. It will not begin emitting items - /// from the original Stream until the `connect` method is invoked. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream that replays a given number of items to any new - /// listener. It also provides access to the emitted values synchronously. - /// - /// ### Example - /// - /// ``` - /// final source = Stream.fromIterable([1, 2, 3]); - /// final connectable = source.publishReplay(); - /// - /// // Does not print anything at first - /// connectable.listen(print); - /// - /// // Start listening to the source Stream. Will cause the previous - /// // line to start printing 1, 2, 3 - /// final subscription = connectable.connect(); - /// - /// // Late subscribers will receive the emitted value, up to a specified - /// // maxSize - /// connectable.listen(print); // Prints 1, 2, 3 - /// await Future(() {}); - /// - /// // Can access a list of the emitted values synchronously. Prints [1, 2, 3] - /// print(connectable.values); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // ReplaySubject - /// subscription.cancel(); - /// ``` - ReplayConnectableStream publishReplay({int? maxSize}) => - ReplayConnectableStream(this, maxSize: maxSize, sync: true); - - /// Convert the current Stream into a new Stream that can be listened - /// to multiple times. It will automatically begin emitting items when first - /// listened to, and shut down when no listeners remain. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream. - /// - /// ### Example - /// - /// ``` - /// // Convert a single-subscription fromIterable stream into a broadcast - /// // stream - /// final stream = Stream.fromIterable([1, 2, 3]).share(); - /// - /// // Start listening to the source Stream. Will start printing 1, 2, 3 - /// final subscription = stream.listen(print); - /// await Future(() {}); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // PublishSubject - /// subscription.cancel(); - /// ``` - Stream share() => publish().refCount(); - - /// Convert the current Stream into a new [ValueStream] that can - /// be listened to multiple times. It will automatically begin emitting items - /// when first listened to, and shut down when no listeners remain. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream. It's also useful for providing sync access to the latest - /// emitted value. - /// - /// It will replay the latest emitted value to any new listener. - /// - /// ### Example - /// - /// ``` - /// // Convert a single-subscription fromIterable stream into a broadcast - /// // stream that will emit the latest value to any new listeners - /// final stream = Stream.fromIterable([1, 2, 3]).shareValue(); - /// - /// // Start listening to the source Stream. Will start printing 1, 2, 3 - /// final subscription = stream.listen(print); - /// await Future(() {}); - /// - /// // Synchronously print the latest value - /// print(stream.value); - /// - /// // Subscribe again later. This will print 3 because it receives the last - /// // emitted value. - /// final subscription2 = stream.listen(print); - /// await Future(() {}); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // BehaviorSubject by cancelling all subscriptions. - /// subscription.cancel(); - /// subscription2.cancel(); - /// ``` - ValueStream shareValue() => publishValue().refCount(); - - /// Convert the current Stream into a new [ValueStream] that can - /// be listened to multiple times, providing an initial value. - /// It will automatically begin emitting items when first listened to, - /// and shut down when no listeners remain. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream. It's also useful for providing sync access to the latest - /// emitted value. - /// - /// It will replay the latest emitted value to any new listener. - /// - /// ### Example - /// - /// ``` - /// // Convert a single-subscription fromIterable stream into a broadcast - /// // stream that will emit the latest value to any new listeners - /// final stream = Stream.fromIterable([1, 2, 3]).shareValueSeeded(0); - /// - /// // Start listening to the source Stream. Will start printing 0, 1, 2, 3 - /// final subscription = stream.listen(print); - /// - /// // Synchronously print the latest value - /// print(stream.value); - /// - /// // Subscribe again later. This will print 3 because it receives the last - /// // emitted value. - /// final subscription2 = stream.listen(print); - /// await Future(() {}); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // BehaviorSubject by cancelling all subscriptions. - /// subscription.cancel(); - /// subscription2.cancel(); - /// ``` - ValueStream shareValueSeeded(T seedValue) => - publishValueSeeded(seedValue).refCount(); - - /// Convert the current Stream into a new [ReplayStream] that can - /// be listened to multiple times. It will automatically begin emitting items - /// when first listened to, and shut down when no listeners remain. - /// - /// This is useful for converting a single-subscription stream into a - /// broadcast Stream. It's also useful for gaining access to the l - /// - /// It will replay the emitted values to any new listener, up to a given - /// [maxSize]. - /// - /// ### Example - /// - /// ``` - /// // Convert a single-subscription fromIterable stream into a broadcast - /// // stream that will emit the latest value to any new listeners - /// final stream = Stream.fromIterable([1, 2, 3]).shareReplay(); - /// - /// // Start listening to the source Stream. Will start printing 1, 2, 3 - /// final subscription = stream.listen(print); - /// await Future(() {}); - /// - /// // Synchronously print the emitted values up to a given maxSize - /// // Prints [1, 2, 3] - /// print(stream.values); - /// - /// // Subscribe again later. This will print 1, 2, 3 because it receives the - /// // last emitted value. - /// final subscription2 = stream.listen(print); - /// await Future(() {}); - /// - /// // Stop emitting items from the source stream and close the underlying - /// // ReplaySubject by cancelling all subscriptions. - /// subscription.cancel(); - /// subscription2.cancel(); - /// ``` - ReplayStream shareReplay({int? maxSize}) => - publishReplay(maxSize: maxSize).refCount(); -} diff --git a/sandbox/reactivex/lib/src/streams/defer.dart b/sandbox/reactivex/lib/src/streams/defer.dart deleted file mode 100644 index 25a8a12..0000000 --- a/sandbox/reactivex/lib/src/streams/defer.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'dart:async'; - -/// The defer factory waits until a listener subscribes to it, and then it -/// creates a Stream with the given factory function. -/// -/// In some circumstances, waiting until the last minute (that is, until -/// subscription time) to generate the Stream can ensure that listeners -/// receive the freshest data. -/// -/// By default, DeferStreams are single-subscription. However, it's possible -/// to make them reusable. -/// -/// ### Example -/// -/// DeferStream(() => Stream.value(1)).listen(print); //prints 1 -class DeferStream extends Stream { - final Stream Function() _factory; - final bool _isReusable; - - @override - bool get isBroadcast => _isReusable; - - /// Constructs a [Stream] lazily, at the moment of subscription, using - /// the [streamFactory] - DeferStream(Stream Function() streamFactory, {bool reusable = false}) - : _isReusable = reusable, - _factory = reusable - ? streamFactory - : (() { - Stream? stream; - return () => stream ??= streamFactory(); - }()); - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - Stream stream; - - try { - stream = _factory(); - } catch (e, s) { - return Stream.error(e, s).listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } - - return stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } -} diff --git a/sandbox/reactivex/lib/src/streams/fork_join.dart b/sandbox/reactivex/lib/src/streams/fork_join.dart deleted file mode 100644 index 349909e..0000000 --- a/sandbox/reactivex/lib/src/streams/fork_join.dart +++ /dev/null @@ -1,366 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/collection_extensions.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -/// This operator is best used when you have a group of streams -/// and only care about the final emitted value of each. -/// One common use case for this is if you wish to issue multiple -/// requests on page load (or some other event) -/// and only want to take action when a response has been received for all. -/// -/// In this way it is similar to how you might use [Future.wait]. -/// -/// Be aware that if any of the inner streams supplied to forkJoin error -/// you will lose the value of any other streams that would or have already -/// completed if you do not catch the error correctly on the inner stream. -/// -/// If you are only concerned with all inner streams completing -/// successfully you can catch the error on the outside. -/// It's also worth noting that if you have an stream -/// that emits more than one item, and you are concerned with the previous -/// emissions forkJoin is not the correct choice. -/// -/// In these cases you may better off with an operator like combineLatest or zip. -/// -/// If the provided streams is empty, the resulting sequence completes immediately -/// without emitting any items and without any calls to the combiner function. -/// -/// ### Basic Example -/// -/// This constructor takes in an `Iterable>` and outputs a -/// `Stream>` whenever any of the values change from the source -/// stream. This is useful with a dynamic number of source streams! -/// -/// ForkJoinStream.list([ -/// Stream.fromIterable(['a']), -/// Stream.fromIterable(['b']), -/// Stream.fromIterable(['C', 'D']), -/// ]) -/// .listen(print); //prints ['a', 'b', 'D'] -/// -/// ### Example with combiner -/// -/// If you wish to combine the list of values into a new object before emitting, -/// you can provide the `combiner` function to the constructor. -/// -/// ForkJoinStream( -/// [ -/// Stream.fromIterable(['a']), -/// Stream.fromIterable(['b']), -/// Stream.fromIterable(['C', 'D']), -/// ], -/// (values) => values.last, -/// ) -/// .listen(print); //prints 'D' -/// -/// ### Example with a specific number of Streams -/// -/// If you wish to combine a specific number of Streams together with proper -/// types information for the value of each Stream, use the -/// [join2] - [join9] operators. -/// -/// ForkJoinStream.join2( -/// Stream.fromIterable([1]), -/// Stream.fromIterable([2, 3]), -/// (a, b) => a + b, -/// ) -/// .listen(print); // prints 4 -class ForkJoinStream extends StreamView { - /// Constructs a [Stream] that awaits the last values of the [Stream]s - /// in [streams], then calls the [combiner] to emit an event of type [R]. - /// After this event, the [Stream] closes. - ForkJoinStream( - Iterable> streams, - R Function(List values) combiner, - ) : super(_buildStream(streams, combiner)); - - /// Constructs a [Stream] that awaits the last values of the [Stream]s - /// in [streams] and then emits these values as a [List]. - /// After this event, the [Stream] closes. - static ForkJoinStream> list( - Iterable> streams, - ) => - ForkJoinStream>( - streams, - (values) => values, - ); - - /// Constructs a [Stream] that awaits the last values the provided [Stream]s, - /// then calls the [combiner] to emit an event of type [R]. - /// After this event, the [Stream] closes. - static ForkJoinStream join2( - Stream streamOne, - Stream streamTwo, - R Function(A a, B b) combiner, - ) => - ForkJoinStream( - [streamOne, streamTwo], - (List values) => combiner(values[0] as A, values[1] as B), - ); - - /// Constructs a [Stream] that awaits the last values the provided [Stream]s, - /// then calls the [combiner] to emit an event of type [R]. - /// After this event, the [Stream] closes. - static ForkJoinStream join3( - Stream streamA, - Stream streamB, - Stream streamC, - R Function(A a, B b, C c) combiner, - ) => - ForkJoinStream( - [streamA, streamB, streamC], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - ); - }, - ); - - /// Constructs a [Stream] that awaits the last values the provided [Stream]s, - /// then calls the [combiner] to emit an event of type [R]. - /// After this event, the [Stream] closes. - static ForkJoinStream join4( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - R Function(A a, B b, C c, D d) combiner, - ) => - ForkJoinStream( - [streamA, streamB, streamC, streamD], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - ); - }, - ); - - /// Constructs a [Stream] that awaits the last values the provided [Stream]s, - /// then calls the [combiner] to emit an event of type [R]. - /// After this event, the [Stream] closes. - static ForkJoinStream join5( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - R Function(A a, B b, C c, D d, E e) combiner, - ) => - ForkJoinStream( - [streamA, streamB, streamC, streamD, streamE], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - ); - }, - ); - - /// Constructs a [Stream] that awaits the last values the provided [Stream]s, - /// then calls the [combiner] to emit an event of type [R]. - /// After this event, the [Stream] closes. - static ForkJoinStream join6( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - R Function(A a, B b, C c, D d, E e, F f) combiner, - ) => - ForkJoinStream( - [streamA, streamB, streamC, streamD, streamE, streamF], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - ); - }, - ); - - /// Constructs a [Stream] that awaits the last values the provided [Stream]s, - /// then calls the [combiner] to emit an event of type [R]. - /// After this event, the [Stream] closes. - static ForkJoinStream join7( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - R Function(A a, B b, C c, D d, E e, F f, G g) combiner, - ) => - ForkJoinStream( - [streamA, streamB, streamC, streamD, streamE, streamF, streamG], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - ); - }, - ); - - /// Constructs a [Stream] that awaits the last values the provided [Stream]s, - /// then calls the [combiner] to emit an event of type [R]. - /// After this event, the [Stream] closes. - static ForkJoinStream join8( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - R Function(A a, B b, C c, D d, E e, F f, G g, H h) combiner, - ) => - ForkJoinStream( - [ - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH - ], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - values[7] as H, - ); - }, - ); - - /// Constructs a [Stream] that awaits the last values the provided [Stream]s, - /// then calls the [combiner] to emit an event of type [R]. - /// After this event, the [Stream] closes. - static ForkJoinStream join9( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - Stream streamI, - R Function(A a, B b, C c, D d, E e, F f, G g, H h, I i) combiner, - ) => - ForkJoinStream( - [ - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH, - streamI - ], - (List values) { - return combiner( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - values[7] as H, - values[8] as I, - ); - }, - ); - - static Stream _buildStream( - Iterable> streams, - R Function(List values) combiner, - ) { - final controller = StreamController(sync: true); - late List> subscriptions; - List? values; - - controller.onListen = () { - var completed = 0; - - StreamSubscription listen(int i, Stream stream) { - var hasValue = false; - - return stream.listen( - (value) { - hasValue = true; - values?[i] = value; - }, - onError: controller.addError, - onDone: () { - if (!hasValue) { - controller.addError(StateError('No element')); - controller.close(); - return; - } - - if (values == null) { - return; - } - if (++completed == subscriptions.length) { - final R combined; - try { - combined = combiner(List.unmodifiable(values!)); - } catch (e, s) { - controller.addError(e, s); - controller.close(); - return; - } - - controller.add(combined); - controller.close(); - } - }, - ); - } - - subscriptions = streams.mapIndexed(listen).toList(growable: false); - if (subscriptions.isEmpty) { - controller.close(); - } else { - values = List.filled(subscriptions.length, null); - } - }; - controller.onPause = () => subscriptions.pauseAll(); - controller.onResume = () => subscriptions.resumeAll(); - controller.onCancel = () { - values = null; - return subscriptions.cancelAll(); - }; - - return controller.stream; - } -} diff --git a/sandbox/reactivex/lib/src/streams/from_callable.dart b/sandbox/reactivex/lib/src/streams/from_callable.dart deleted file mode 100644 index ee4a6ba..0000000 --- a/sandbox/reactivex/lib/src/streams/from_callable.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'dart:async'; - -/// Returns a Stream that, when listening to it, calls a function you specify -/// and then emits the value returned from that function. -/// -/// If result from invoking [callable] function: -/// - Is a [Future]: when the future completes, this stream will fire one event, either -/// data or error, and then close with a done-event. -/// - Is a [T]: this stream emits a single data event and then completes with a done event. -/// -/// By default, a [FromCallableStream] is a single-subscription Stream. However, it's possible -/// to make them reusable. -/// This Stream is effectively equivalent to one created by -/// `(() async* { yield await callable() }())` or `(() async* { yield callable(); }())`. -/// -/// [ReactiveX doc](http://reactivex.io/documentation/operators/from.html) -/// -/// ### Example -/// -/// FromCallableStream(() => 'Value').listen(print); // prints Value -/// -/// FromCallableStream(() async { -/// await Future.delayed(const Duration(seconds: 1)); -/// return 'Value'; -/// }).listen(print); // prints Value -class FromCallableStream extends Stream { - Stream? _stream; - - /// A function will be called at subscription time. - final FutureOr Function() callable; - final bool _isReusable; - - /// Construct a Stream that, when listening to it, calls a function you specify - /// and then emits the value returned from that function. - /// [reusable] indicates whether this Stream can be listened to multiple times or not. - FromCallableStream(this.callable, {bool reusable = false}) - : _isReusable = reusable; - - @override - bool get isBroadcast => _isReusable; - - @override - StreamSubscription listen( - void Function(T event)? onData, { - Function? onError, - void Function()? onDone, - bool? cancelOnError, - }) { - if (_isReusable || _stream == null) { - try { - final value = callable(); - - _stream = - value is Future ? Stream.fromFuture(value) : Stream.value(value); - } catch (e, s) { - _stream = Stream.error(e, s); - } - } - - return _stream!.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } -} diff --git a/sandbox/reactivex/lib/src/streams/merge.dart b/sandbox/reactivex/lib/src/streams/merge.dart deleted file mode 100644 index 2384052..0000000 --- a/sandbox/reactivex/lib/src/streams/merge.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -/// Flattens the items emitted by the given streams into a single Stream -/// sequence. -/// -/// If the provided streams is empty, the resulting sequence completes immediately -/// without emitting any items. -/// -/// [Interactive marble diagram](http://rxmarbles.com/#merge) -/// -/// ### Example -/// -/// MergeStream([ -/// TimerStream(1, Duration(days: 10)), -/// Stream.fromIterable([2]) -/// ]) -/// .listen(print); // prints 2, 1 -class MergeStream extends StreamView { - /// Constructs a [Stream] which flattens all events in [streams] and emits - /// them in a single sequence. - MergeStream(Iterable> streams) - : super(_buildController(streams).stream); - - static StreamController _buildController(Iterable> streams) { - final controller = StreamController(sync: true); - late List> subscriptions; - - controller.onListen = () { - var completed = 0; - - void onDone() { - if (++completed == subscriptions.length) { - controller.close(); - } - } - - subscriptions = streams - .map((s) => s.listen(controller.add, - onError: controller.addError, onDone: onDone)) - .toList(growable: false); - - if (subscriptions.isEmpty) { - controller.close(); - } - }; - controller.onPause = () => subscriptions.pauseAll(); - controller.onResume = () => subscriptions.resumeAll(); - controller.onCancel = () => subscriptions.cancelAll(); - - return controller; - } -} - -/// Extends the Stream class with the ability to merge one stream with another. -extension MergeExtension on Stream { - /// Combines the items emitted by multiple streams into a single stream of - /// items. The items are emitted in the order they are emitted by their - /// sources. - /// - /// ### Example - /// - /// TimerStream(1, Duration(seconds: 10)) - /// .mergeWith([Stream.fromIterable([2])]) - /// .listen(print); // prints 2, 1 - Stream mergeWith(Iterable> streams) { - final stream = MergeStream([this, ...streams]); - - return isBroadcast - ? stream.asBroadcastStream(onCancel: (s) => s.cancel()) - : stream; - } -} diff --git a/sandbox/reactivex/lib/src/streams/never.dart b/sandbox/reactivex/lib/src/streams/never.dart deleted file mode 100644 index fc6b18b..0000000 --- a/sandbox/reactivex/lib/src/streams/never.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'dart:async'; - -/// Returns a non-terminating stream sequence, which can be used to denote -/// an infinite duration. -/// -/// The never operator is one with very specific and limited behavior. These -/// are useful for testing purposes, and sometimes also for combining with -/// other Streams or as parameters to operators that expect other -/// Streams as parameters. -/// -/// ### Example -/// -/// NeverStream().listen(print); // Neither prints nor terminates -class NeverStream extends Stream { - // ignore: close_sinks - final _controller = StreamController(); - - /// Constructs a [Stream] which never emits an event and simply remains - /// open until implicitly closed by the developer. - NeverStream(); - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) => - _controller.stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); -} diff --git a/sandbox/reactivex/lib/src/streams/race.dart b/sandbox/reactivex/lib/src/streams/race.dart deleted file mode 100644 index 98b6333..0000000 --- a/sandbox/reactivex/lib/src/streams/race.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/collection_extensions.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -/// Given two or more source streams, emit all of the items from only -/// the first of these streams to emit an item or notification. -/// -/// If the provided streams is empty, the resulting sequence completes immediately -/// without emitting any items. -/// -/// [Interactive marble diagram](http://rxmarbles.com/#race) -/// -/// ### Example -/// -/// RaceStream([ -/// TimerStream(1, Duration(days: 1)), -/// TimerStream(2, Duration(days: 2)), -/// TimerStream(3, Duration(seconds: 3)) -/// ]).listen(print); // prints 3 -class RaceStream extends StreamView { - /// Constructs a [Stream] which emits all events from a single [Stream] - /// inside [streams]. The selected [Stream] is the first one which emits - /// an event. - /// After this event, all other [Stream]s in [streams] are discarded. - RaceStream(Iterable> streams) - : super(_buildController(streams).stream); - - static StreamController _buildController(Iterable> streams) { - final controller = StreamController(sync: true); - late List> subscriptions; - - controller.onListen = () { - void reduceToWinner(int winnerIndex) { - final winner = subscriptions.removeAt(winnerIndex); - - subscriptions.cancelAll()?.onError((e, s) { - if (!controller.isClosed && controller.hasListener) { - controller.addError(e, s); - } - }); - - subscriptions = [winner]; - } - - void Function(T value) doUpdate(int index) { - return (T value) { - if (subscriptions.length > 1) { - reduceToWinner(index); - } - controller.add(value); - }; - } - - subscriptions = streams - .mapIndexed((index, stream) => stream.listen(doUpdate(index), - onError: controller.addError, onDone: controller.close)) - .toList(); - - if (subscriptions.isEmpty) { - controller.close(); - } - }; - controller.onPause = () => subscriptions.pauseAll(); - controller.onResume = () => subscriptions.resumeAll(); - controller.onCancel = () => subscriptions.cancelAll(); - - return controller; - } -} diff --git a/sandbox/reactivex/lib/src/streams/range.dart b/sandbox/reactivex/lib/src/streams/range.dart deleted file mode 100644 index 83b4c1e..0000000 --- a/sandbox/reactivex/lib/src/streams/range.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'dart:async'; - -/// Returns a Stream that emits a sequence of Integers within a specified -/// range. -/// -/// ### Examples -/// -/// RangeStream(1, 3).listen((i) => print(i)); // Prints 1, 2, 3 -/// -/// RangeStream(3, 1).listen((i) => print(i)); // Prints 3, 2, 1 -class RangeStream extends Stream { - var _isListened = false; - final Stream _stream; - - /// Constructs a [Stream] which emits all integer values that exist - /// within the range between [startInclusive] and [endInclusive]. - RangeStream(int startInclusive, int endInclusive) - : _stream = _buildStream(startInclusive, endInclusive); - - @override - StreamSubscription listen(void Function(int event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - if (_isListened) { - throw StateError('Stream has already been listened to.'); - } - _isListened = true; - - return _stream.listen(onData, - onError: onError, onDone: onDone, cancelOnError: cancelOnError); - } - - static Stream _buildStream(int startInclusive, int endInclusive) { - final length = (endInclusive - startInclusive).abs() + 1; - - int nextValue(int index) => startInclusive > endInclusive - ? startInclusive - index - : startInclusive + index; - - return Stream.fromIterable(Iterable.generate(length, nextValue)); - } -} diff --git a/sandbox/reactivex/lib/src/streams/repeat.dart b/sandbox/reactivex/lib/src/streams/repeat.dart deleted file mode 100644 index 9f8f9c5..0000000 --- a/sandbox/reactivex/lib/src/streams/repeat.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:async'; - -/// Creates a [Stream] that will recreate and re-listen to the source -/// Stream the specified number of times until the [Stream] terminates -/// successfully. -/// -/// If [count] is not specified, it repeats indefinitely. -/// -/// ### Example -/// -/// RepeatStream((int repeatCount) => -/// Stream.value('repeat index: $repeatCount'), 3) -/// .listen((i) => print(i)); // Prints 'repeat index: 0, repeat index: 1, repeat index: 2' -class RepeatStream extends Stream { - /// The factory method used at subscription time - final Stream Function(int) streamFactory; - - /// The amount of repeat attempts that will be made - /// If 0, then an indefinite amount of attempts will be made. - final int? count; - int _repeatStep = 0; - StreamController? _controller; - StreamSubscription? _subscription; - - /// Constructs a [Stream] that will recreate and re-listen to the source - /// [Stream] (created with the provided factory method). - /// The count parameter specifies number of times the repeat will take place, - /// until this [Stream] terminates successfully. - /// If the count parameter is not specified, then this [Stream] will repeat - /// indefinitely. - RepeatStream(this.streamFactory, [this.count]); - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - _controller ??= StreamController( - sync: true, - onListen: _maybeRepeatNext, - onPause: () => _subscription?.pause(), - onResume: () => _subscription?.resume(), - onCancel: () => _subscription?.cancel()); - - return _controller!.stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } - - void _repeatNext() { - void onDone() { - _subscription?.cancel(); - - _maybeRepeatNext(); - } - - final controller = _controller!; - try { - _subscription = streamFactory(_repeatStep++).listen( - controller.add, - onError: controller.addError, - onDone: onDone, - cancelOnError: false, - ); - } catch (e, s) { - controller.addError(e, s); - } - } - - void _maybeRepeatNext() { - if (_repeatStep == count) { - _controller!.close(); - } else { - _repeatNext(); - } - } -} diff --git a/sandbox/reactivex/lib/src/streams/replay_stream.dart b/sandbox/reactivex/lib/src/streams/replay_stream.dart deleted file mode 100644 index a9d7957..0000000 --- a/sandbox/reactivex/lib/src/streams/replay_stream.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:angel3_reactivex/src/utils/collection_extensions.dart'; -import 'package:angel3_reactivex/src/utils/error_and_stacktrace.dart'; - -/// An [Stream] that provides synchronous access to the emitted values -abstract class ReplayStream implements Stream { - /// Synchronously get the values stored in Subject. May be empty. - List get values; - - /// Synchronously get the errors and stack traces stored in Subject. May be empty. - List get errors; - - /// Synchronously get the stack traces of errors stored in Subject. May be empty. - List get stackTraces; -} - -/// Extension method on [ReplayStream] to access the emitted [ErrorAndStackTrace]s. -extension ErrorAndStackTracesReplayStreamExtension on ReplayStream { - /// Returns the emitted [ErrorAndStackTrace]s. - /// May be empty. - List get errorAndStackTraces => - errors.zipWith( - stackTraces, - (e, s) => ErrorAndStackTrace(e, s), - growable: false, - ); -} diff --git a/sandbox/reactivex/lib/src/streams/retry.dart b/sandbox/reactivex/lib/src/streams/retry.dart deleted file mode 100644 index f90cc97..0000000 --- a/sandbox/reactivex/lib/src/streams/retry.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/error_and_stacktrace.dart'; - -/// Creates a [Stream] that will recreate and re-listen to the source -/// [Stream] the specified number of times until the [Stream] terminates -/// successfully. -/// -/// If the retry count is not specified, it retries indefinitely. If the retry -/// count is met, but the Stream has not terminated successfully, all of the errors -/// and StackTraces that caused the failure will be emitted. -/// -/// ### Example -/// -/// RetryStream(() => Stream.value(1)) -/// .listen((i) => print(i)); // Prints 1 -/// -/// RetryStream( -/// () => Stream.value(1).concatWith([Stream.error(Error())]), -/// 1, -/// ).listen( -/// print, -/// onError: (Object e, StackTrace s) => print(e), -/// ); // Prints 1, 1, Instance of 'Error', Instance of 'Error' -class RetryStream extends Stream { - /// The factory method used at subscription time - final Stream Function() streamFactory; - - /// The amount of retry attempts that will be made - /// If null, then an indefinite amount of attempts will be made. - final int? count; - - var _retryStep = 0; - final _errors = []; - late final StreamController _controller = StreamController( - sync: true, - onListen: _retry, - onPause: () => _subscription!.pause(), - onResume: () => _subscription!.resume(), - onCancel: () { - _errors.clear(); - return _subscription?.cancel(); - }, - ); - StreamSubscription? _subscription; - - /// Constructs a [Stream] that will recreate and re-listen to the source - /// [Stream] (created by the provided factory method) the specified number - /// of times until the [Stream] terminates successfully. - /// If [count] is not specified, it retries indefinitely. - RetryStream(this.streamFactory, [this.count]); - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - return _controller.stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } - - void _retry() { - void onError(Object e, StackTrace s) { - _subscription!.cancel(); - _subscription = null; - - _errors.add(ErrorAndStackTrace(e, s)); - - if (count == _retryStep) { - for (var e in [..._errors]) { - _controller.addError(e.error, e.stackTrace); - } - _controller.close(); - } else { - ++_retryStep; - _retry(); - } - } - - _subscription = streamFactory().listen( - _controller.add, - onError: onError, - onDone: _controller.close, - cancelOnError: false, - ); - } -} diff --git a/sandbox/reactivex/lib/src/streams/retry_when.dart b/sandbox/reactivex/lib/src/streams/retry_when.dart deleted file mode 100644 index 05e6073..0000000 --- a/sandbox/reactivex/lib/src/streams/retry_when.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'dart:async'; - -/// Creates a Stream that will recreate and re-listen to the source -/// Stream when the notifier emits a new value. If the source Stream -/// emits an error or it completes, the Stream terminates. -/// -/// If the [retryWhenFactory] throws an error or returns a Stream that emits an error, -/// original error will be emitted. And then, the error from [retryWhenFactory] will be emitted -/// if it is not identical with original error. -/// -/// ### Basic Example -/// -/// ```dart -/// RetryWhenStream( -/// () => Stream.fromIterable([1]), -/// (Object error, StackTrace s) => throw error, -/// ).listen(print); // Prints 1 -/// ``` -/// -/// ### Periodic Example -/// -/// ```dart -/// RetryWhenStream( -/// () => Stream.periodic(const Duration(seconds: 1), (int i) => i) -/// .map((int i) => i == 2 ? throw 'exception' : i), -/// (Object e, StackTrace s) => -/// Rx.timer(null, const Duration(milliseconds: 200)), -/// ).take(4).listen(print); // Prints 0, 1, 0, 1 -/// ``` -/// -/// ### Complex Example -/// -/// ```dart -/// var errorHappened = false; -/// RetryWhenStream( -/// () => Stream.periodic(const Duration(seconds: 1), (i) => i).map((i) { -/// if (i == 3 && !errorHappened) { -/// throw 'We can take this. Please restart.'; -/// } else if (i == 4) { -/// throw 'It\'s enough.'; -/// } else { -/// return i; -/// } -/// }), -/// (e, s) { -/// errorHappened = true; -/// if (e == 'We can take this. Please restart.') { -/// return Stream.value('Ok. Here you go!'); -/// } else { -/// return Stream.error(e, s); -/// } -/// }, -/// ).listen(print, onError: print); // Prints 0, 1, 2, 0, 1, 2, 3, It's enough. -/// ``` -class RetryWhenStream extends Stream { - /// The factory method used at subscription time - final Stream Function() streamFactory; - - /// The factory method used to create the [Stream] which triggers a re-listen - final Stream Function( - Object error, - StackTrace stackTrace, - ) retryWhenFactory; - - late final _controller = StreamController( - sync: true, - onListen: _retry, - onPause: () => _subscription!.pause(), - onResume: () => _subscription!.resume(), - onCancel: () => _subscription?.cancel(), - ); - StreamSubscription? _subscription; - - /// Constructs a [Stream] that will recreate and re-listen to the source - /// [Stream] (created by the provided factory method). - /// The retry will trigger whenever the [Stream] created by the retryWhen - /// factory emits and event. - RetryWhenStream(this.streamFactory, this.retryWhenFactory); - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - return _controller.stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } - - void _retry() { - void onError(Object originalError, StackTrace originalStacktrace) { - _cancelSubscription(); - - Stream retryStream; - try { - retryStream = retryWhenFactory(originalError, originalStacktrace); - } catch (e, s) { - return _addErrorAndClose(originalError, originalStacktrace, e, s); - } - - _subscription = retryStream.listen( - (_) { - _cancelSubscription(); - _retry(); - }, - onError: (Object e, StackTrace s) { - _cancelSubscription(); - _addErrorAndClose(originalError, originalStacktrace, e, s); - }, - cancelOnError: false, - ); - } - - _subscription = streamFactory().listen( - _controller.add, - onError: onError, - onDone: _controller.close, - cancelOnError: false, - ); - } - - void _addErrorAndClose( - Object originalError, - StackTrace originalStacktrace, - Object e, - StackTrace s, - ) { - if (identical(originalError, e)) { - _controller.addError(originalError, originalStacktrace); - } else { - _controller.addError(originalError, originalStacktrace); - _controller.addError(e, s); - } - _controller.close(); - } - - void _cancelSubscription() { - _subscription!.cancel(); - _subscription = null; - } -} diff --git a/sandbox/reactivex/lib/src/streams/sequence_equal.dart b/sandbox/reactivex/lib/src/streams/sequence_equal.dart deleted file mode 100644 index 5fd8d11..0000000 --- a/sandbox/reactivex/lib/src/streams/sequence_equal.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/streams/zip.dart'; -import 'package:angel3_reactivex/src/transformers/materialize.dart'; -import 'package:angel3_reactivex/src/utils/error_and_stacktrace.dart'; -import 'package:angel3_reactivex/src/utils/notification.dart'; - -/// Determine whether two Streams emit the same sequence of items. -/// You can provide an optional equals handler to determine equality. -/// -/// [Interactive marble diagram](https://rxmarbles.com/#sequenceEqual) -/// -/// ### Example -/// -/// SequenceEqualsStream([ -/// Stream.fromIterable([1, 2, 3, 4, 5]), -/// Stream.fromIterable([1, 2, 3, 4, 5]) -/// ]) -/// .listen(print); // prints true -class SequenceEqualStream extends Stream { - final StreamController _controller; - - /// Creates a [Stream] that emits true or false, depending on the - /// equality between the provided [Stream]s. - /// This single value is emitted when both provided [Stream]s are complete. - /// After this event, the [Stream] closes. - SequenceEqualStream( - Stream stream, - Stream other, { - bool Function(S s, T t)? dataEquals, - bool Function(ErrorAndStackTrace, ErrorAndStackTrace)? errorEquals, - }) : _controller = _buildController(stream, other, dataEquals, errorEquals); - - @override - StreamSubscription listen(void Function(bool event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) => - _controller.stream.listen(onData, - onError: onError, onDone: onDone, cancelOnError: cancelOnError); - - static StreamController _buildController( - Stream stream, - Stream other, - bool Function(S s, T t)? dataEquals, - bool Function(ErrorAndStackTrace, ErrorAndStackTrace)? errorEquals, - ) { - dataEquals = dataEquals ?? (s, t) => s == t; - errorEquals = errorEquals ?? (e1, e2) => e1 == e2; - - late StreamController controller; - late StreamSubscription subscription; - - controller = StreamController( - sync: true, - onListen: () { - void emitAndClose([bool value = true]) => controller - ..add(value) - ..close(); - - bool compare(StreamNotification s, StreamNotification t) { - if (s.kind != t.kind) { - return false; - } - - switch (s.kind) { - case NotificationKind.data: - return dataEquals!( - s.requireDataValue, - t.requireDataValue, - ); - case NotificationKind.done: - return true; - case NotificationKind.error: - return errorEquals!( - s.requireErrorAndStackTrace, - t.requireErrorAndStackTrace, - ); - } - } - - subscription = - ZipStream.zip2(stream.materialize(), other.materialize(), compare) - .where((isEqual) => !isEqual) - .listen( - emitAndClose, - onError: controller.addError, - onDone: emitAndClose, - ); - }, - onPause: () => subscription.pause(), - onResume: () => subscription.resume(), - onCancel: () => subscription.cancel()); - - return controller; - } -} diff --git a/sandbox/reactivex/lib/src/streams/switch_latest.dart b/sandbox/reactivex/lib/src/streams/switch_latest.dart deleted file mode 100644 index 87650ad..0000000 --- a/sandbox/reactivex/lib/src/streams/switch_latest.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'dart:async'; - -/// Convert a [Stream] that emits [Stream]s (aka a 'Higher Order Stream') into a -/// single [Stream] that emits the items emitted by the most-recently-emitted of -/// those [Stream]s. -/// -/// This stream will unsubscribe from the previously-emitted Stream when a new -/// Stream is emitted from the source Stream. -/// -/// ### Example -/// -/// ```dart -/// final switchLatestStream = SwitchLatestStream( -/// Stream.fromIterable(>[ -/// Rx.timer('A', Duration(seconds: 2)), -/// Rx.timer('B', Duration(seconds: 1)), -/// Stream.value('C'), -/// ]), -/// ); -/// -/// // Since the first two Streams do not emit data for 1-2 seconds, and the 3rd -/// // Stream will be emitted before that time, only data from the 3rd Stream -/// // will be emitted to the listener. -/// switchLatestStream.listen(print); // prints 'C' -/// ``` -class SwitchLatestStream extends Stream { - // ignore: close_sinks - final StreamController _controller; - - /// Constructs a [Stream] that emits [Stream]s (aka a 'Higher Order Stream") into a - /// single [Stream] that emits the items emitted by the most-recently-emitted of - /// those [Stream]s. - SwitchLatestStream(Stream> streams) - : _controller = _buildController(streams); - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) => - _controller.stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - - static StreamController _buildController(Stream> streams) { - late StreamController controller; - late StreamSubscription> subscription; - StreamSubscription? otherSubscription; - var leftClosed = false, rightClosed = false, hasMainEvent = false; - - controller = StreamController( - sync: true, - onListen: () { - void closeLeft() { - leftClosed = true; - - if (rightClosed || !hasMainEvent) controller.close(); - } - - void closeRight() { - rightClosed = true; - - if (leftClosed) controller.close(); - } - - subscription = streams.listen((stream) { - try { - otherSubscription?.cancel(); - - hasMainEvent = true; - - otherSubscription = stream.listen( - controller.add, - onError: controller.addError, - onDone: closeRight, - ); - } catch (e, s) { - controller.addError(e, s); - } - }, onError: controller.addError, onDone: closeLeft); - }, - onPause: () { - subscription.pause(); - otherSubscription?.pause(); - }, - onResume: () { - subscription.resume(); - otherSubscription?.resume(); - }, - onCancel: () async { - await subscription.cancel(); - - if (hasMainEvent) await otherSubscription?.cancel(); - }); - - return controller; - } -} diff --git a/sandbox/reactivex/lib/src/streams/timer.dart b/sandbox/reactivex/lib/src/streams/timer.dart deleted file mode 100644 index c456b66..0000000 --- a/sandbox/reactivex/lib/src/streams/timer.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'dart:async'; - -/// Emits the given value after a specified amount of time. -/// -/// ### Example -/// -/// TimerStream('hi', Duration(minutes: 1)) -/// .listen((i) => print(i)); // print 'hi' after 1 minute -class TimerStream extends Stream { - final StreamController _controller; - - /// Constructs a [Stream] which emits [value] after the specified [Duration]. - TimerStream(T value, Duration duration) - : _controller = _buildController(value, duration); - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - return _controller.stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } - - static StreamController _buildController(T value, Duration duration) { - final watch = Stopwatch(); - Timer? timer; - late StreamController controller; - Duration? totalElapsed = Duration.zero; - - void onResume() { - // Already cancelled or is not paused. - if (totalElapsed == null || timer != null) return; - - totalElapsed = totalElapsed! + watch.elapsed; - watch.start(); - - timer = Timer(duration - totalElapsed!, () { - controller.add(value); - controller.close(); - }); - } - - controller = StreamController( - sync: true, - onListen: () { - watch.start(); - timer = Timer(duration, () { - controller.add(value); - controller.close(); - }); - }, - onPause: () { - timer?.cancel(); - timer = null; - watch.stop(); - }, - onResume: onResume, - onCancel: () { - timer?.cancel(); - timer = null; - totalElapsed = null; - }, - ); - return controller; - } -} diff --git a/sandbox/reactivex/lib/src/streams/using.dart b/sandbox/reactivex/lib/src/streams/using.dart deleted file mode 100644 index 1cbed85..0000000 --- a/sandbox/reactivex/lib/src/streams/using.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'dart:async'; - -/// When listener listens to it, creates a resource object from resource factory function, -/// and creates a [Stream] from the given factory function and resource as argument. -/// Finally when the stream finishes emitting items or stream subscription -/// is cancelled (call [StreamSubscription.cancel] or `Stream.listen(cancelOnError: true)`), -/// call the disposer function on resource object. -/// The disposer is called after the future returned from [StreamSubscription.cancel] completes. -/// -/// The [UsingStream] is a way you can instruct a Stream to create -/// a resource that exists only during the lifespan of the Stream -/// and is disposed of when the Stream terminates. -/// -/// [Marble diagram](http://reactivex.io/documentation/operators/images/using.c.png) -/// -/// ### Example -/// -/// UsingStream>( -/// resourceFactory: () => Queue.of([1, 2, 3]), -/// streamFactory: (r) => Stream.fromIterable(r), -/// disposer: (r) => r.clear(), -/// ).listen(print); // prints 1, 2, 3 -class UsingStream extends StreamView { - /// Construct a [UsingStream] that creates a resource object from [resourceFactory], - /// and then creates a [Stream] from [streamFactory] and resource as argument. - /// When the Stream terminates, call [disposer] on resource object. - UsingStream({ - required FutureOr Function() resourceFactory, - required Stream Function(R) streamFactory, - required FutureOr Function(R) disposer, - }) : super(_buildStream(resourceFactory, streamFactory, disposer)); - - static Stream _buildStream( - FutureOr Function() resourceFactory, - Stream Function(R) streamFactory, - FutureOr Function(R) disposer, - ) { - late StreamController controller; - var resourceCreated = false; - late R resource; - StreamSubscription? subscription; - - void useResource(R r) { - resource = r; - resourceCreated = true; - - Stream stream; - try { - stream = streamFactory(r); - } catch (e, s) { - controller.addError(e, s); - controller.close(); - return; - } - - subscription = stream.listen( - controller.add, - onError: controller.addError, - onDone: controller.close, - ); - } - - controller = StreamController( - sync: true, - onListen: () { - final FutureOr resourceOrFuture; - try { - resourceOrFuture = resourceFactory(); - } catch (e, s) { - controller.addError(e, s); - controller.close(); - return; - } - - if (resourceOrFuture is R) { - useResource(resourceOrFuture); - } else { - resourceOrFuture.then((r) { - // if the controller was cancelled before the resource is created, - // we should dispose the resource - if (!controller.hasListener) { - disposer(r); - } else { - useResource(r); - } - }).onError((e, s) { - controller.addError(e, s); - controller.close(); - }); - } - }, - onPause: () => subscription?.pause(), - onResume: () => subscription?.resume(), - onCancel: () { - final cancelFuture = subscription?.cancel(); - subscription = null; - - return cancelFuture == null - ? (resourceCreated ? disposer(resource) : null) - : cancelFuture - .then((_) => resourceCreated ? disposer(resource) : null); - }, - ); - - return controller.stream; - } -} diff --git a/sandbox/reactivex/lib/src/streams/value_stream.dart b/sandbox/reactivex/lib/src/streams/value_stream.dart deleted file mode 100644 index 2df9a86..0000000 --- a/sandbox/reactivex/lib/src/streams/value_stream.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:angel3_reactivex/src/utils/error_and_stacktrace.dart'; -import 'package:angel3_reactivex/src/utils/notification.dart'; - -/// A [Stream] that provides synchronous access to the last emitted value (aka. data event). -abstract class ValueStream implements Stream { - /// Returns the last emitted value, failing if there is no value. - /// See [hasValue] to determine whether [value] has already been set. - /// - /// Throws [ValueStreamError] if this Stream has no value. - /// - /// See also [valueOrNull]. - T get value; - - /// Returns the last emitted value, or `null` if value events haven't yet been emitted. - T? get valueOrNull; - - /// Returns `true` when [value] is available, - /// meaning this Stream has emitted at least one value. - bool get hasValue; - - /// Returns last emitted error, failing if there is no error. - /// See [hasError] to determine whether [error] has already been set. - /// - /// Throws [ValueStreamError] if this Stream has no error. - /// - /// See also [errorOrNull]. - Object get error; - - /// Returns the last emitted error, or `null` if error events haven't yet been emitted. - Object? get errorOrNull; - - /// Returns `true` when [error] is available, - /// meaning this Stream has emitted at least one error. - bool get hasError; - - /// Returns [StackTrace] of the last emitted error. - /// - /// If error events haven't yet been emitted, - /// or the last emitted error didn't have a stack trace, - /// the returned value is `null`. - StackTrace? get stackTrace; - - /// Returns the last emitted event (either data/value or error event). - /// `null` if no value or error events have been emitted yet. - StreamNotification? get lastEventOrNull; -} - -/// Extension methods on [ValueStream] related to [lastEventOrNull]. -extension LastEventValueStreamExtensions on ValueStream { - /// Returns `true` if the last emitted event is a data event (aka. a value event). - bool get isLastEventValue => lastEventOrNull?.isData ?? false; - - /// Returns `true` if the last emitted event is an error event. - bool get isLastEventError => lastEventOrNull?.isError ?? false; -} - -/// Extension method on [ValueStream] to access the last emitted [ErrorAndStackTrace]. -extension ErrorAndStackTraceValueStreamExtension on ValueStream { - /// Returns the last emitted [ErrorAndStackTrace], - /// or `null` if no error events have been emitted yet. - ErrorAndStackTrace? get errorAndStackTraceOrNull { - final error = errorOrNull; - return error == null ? null : ErrorAndStackTrace(error, stackTrace); - } -} - -enum _MissingCase { - value, - error, -} - -/// The error throw by [ValueStream.value] or [ValueStream.error]. -class ValueStreamError extends Error { - final _MissingCase _missingCase; - - ValueStreamError._(this._missingCase); - - /// Construct an [ValueStreamError] thrown by [ValueStream.value] when there is no value. - factory ValueStreamError.hasNoValue() => - ValueStreamError._(_MissingCase.value); - - /// Construct an [ValueStreamError] thrown by [ValueStream.error] when there is no error. - factory ValueStreamError.hasNoError() => - ValueStreamError._(_MissingCase.error); - - @override - String toString() { - switch (_missingCase) { - case _MissingCase.value: - return 'ValueStream has no value. You should check ValueStream.hasValue ' - 'before accessing ValueStream.value, or use ValueStream.valueOrNull instead.'; - case _MissingCase.error: - return 'ValueStream has no error. You should check ValueStream.hasError ' - 'before accessing ValueStream.error, or use ValueStream.errorOrNull instead.'; - } - } -} diff --git a/sandbox/reactivex/lib/src/streams/zip.dart b/sandbox/reactivex/lib/src/streams/zip.dart deleted file mode 100644 index 5caf6eb..0000000 --- a/sandbox/reactivex/lib/src/streams/zip.dart +++ /dev/null @@ -1,388 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/collection_extensions.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -/// Merges the specified streams into one stream sequence using the given -/// zipper Function whenever all of the stream sequences have produced -/// an element at a corresponding index. -/// -/// It applies this function in strict sequence, so the first item emitted by -/// the new Stream will be the result of the function applied to the first -/// item emitted by Stream #1 and the first item emitted by Stream #2; -/// the second item emitted by the new ZipStream will be the result of -/// the function applied to the second item emitted by Stream #1 and the -/// second item emitted by Stream #2; and so forth. It will only emit as -/// many items as the number of items emitted by the source Stream that -/// emits the fewest items. -/// -/// If the provided streams is empty, the resulting sequence completes immediately -/// without emitting any items and without any calls to the zipper function. -/// -/// [Interactive marble diagram](http://rxmarbles.com/#zip) -/// -/// ### Basic Example -/// -/// ZipStream( -/// [ -/// Stream.fromIterable(['A']), -/// Stream.fromIterable(['B']), -/// Stream.fromIterable(['C', 'D']), -/// ], -/// (values) => values.join(), -/// ).listen(print); // prints 'ABC' -/// -/// ### Example with a specific number of Streams -/// -/// If you wish to zip a specific number of Streams together with proper types -/// information for the value of each Stream, use the [zip2] - [zip9] operators. -/// -/// ZipStream.zip2( -/// Stream.fromIterable(['A']), -/// Stream.fromIterable(['B', 'C']), -/// (a, b) => a + b, -/// ) -/// .listen(print); // prints 'AB' -class ZipStream extends StreamView { - /// Constructs a [Stream] which merges the specified [streams] into a sequence using the given - /// [zipper] Function, whenever all of the [streams] have produced - /// an element at a corresponding index. - ZipStream( - Iterable> streams, - R Function(List values) zipper, - ) : super(_buildController(streams, zipper).stream); - - /// Constructs a [Stream] which merges the specified [streams] into a [List], - /// containing values that were produced by the [streams] at a corresponding index. - static ZipStream> list(Iterable> streams) { - return ZipStream>( - streams, - (List values) => values, - ); - } - - /// Constructs a [Stream] which merges the specified [Stream]s into a sequence using the given - /// [zipper] Function, whenever all of the provided [Stream]s have produced - /// an element at a corresponding index. - static ZipStream zip2( - Stream streamOne, - Stream streamTwo, - R Function(A a, B b) zipper, - ) { - return ZipStream( - [streamOne, streamTwo], - (List values) => zipper(values[0] as A, values[1] as B), - ); - } - - /// Constructs a [Stream] which merges the specified [Stream]s into a sequence using the given - /// [zipper] Function, whenever all of the provided [Stream]s have produced - /// an element at a corresponding index. - static ZipStream zip3( - Stream streamA, - Stream streamB, - Stream streamC, - R Function(A a, B b, C c) zipper, - ) { - return ZipStream( - [streamA, streamB, streamC], - (List values) { - return zipper( - values[0] as A, - values[1] as B, - values[2] as C, - ); - }, - ); - } - - /// Constructs a [Stream] which merges the specified [Stream]s into a sequence using the given - /// [zipper] Function, whenever all of the provided [Stream]s have produced - /// an element at a corresponding index. - static ZipStream zip4( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - R Function(A a, B b, C c, D d) zipper, - ) { - return ZipStream( - [streamA, streamB, streamC, streamD], - (List values) { - return zipper( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - ); - }, - ); - } - - /// Constructs a [Stream] which merges the specified [Stream]s into a sequence using the given - /// [zipper] Function, whenever all of the provided [Stream]s have produced - /// an element at a corresponding index. - static ZipStream zip5( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - R Function(A a, B b, C c, D d, E e) zipper, - ) { - return ZipStream( - [streamA, streamB, streamC, streamD, streamE], - (List values) { - return zipper( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - ); - }, - ); - } - - /// Constructs a [Stream] which merges the specified [Stream]s into a sequence using the given - /// [zipper] Function, whenever all of the provided [Stream]s have produced - /// an element at a corresponding index. - static ZipStream zip6( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - R Function(A a, B b, C c, D d, E e, F f) zipper, - ) { - return ZipStream( - [streamA, streamB, streamC, streamD, streamE, streamF], - (List values) { - return zipper( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - ); - }, - ); - } - - /// Constructs a [Stream] which merges the specified [Stream]s into a sequence using the given - /// [zipper] Function, whenever all of the provided [Stream]s have produced - /// an element at a corresponding index. - static ZipStream zip7( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - R Function(A a, B b, C c, D d, E e, F f, G g) zipper, - ) { - return ZipStream( - [streamA, streamB, streamC, streamD, streamE, streamF, streamG], - (List values) { - return zipper( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - ); - }, - ); - } - - /// Constructs a [Stream] which merges the specified [Stream]s into a sequence using the given - /// [zipper] Function, whenever all of the provided [Stream]s have produced - /// an element at a corresponding index. - static ZipStream zip8( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - R Function(A a, B b, C c, D d, E e, F f, G g, H h) zipper, - ) { - return ZipStream( - [streamA, streamB, streamC, streamD, streamE, streamF, streamG, streamH], - (List values) { - return zipper( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - values[7] as H, - ); - }, - ); - } - - /// Constructs a [Stream] which merges the specified [Stream]s into a sequence using the given - /// [zipper] Function, whenever all of the provided [Stream]s have produced - /// an element at a corresponding index. - static ZipStream zip9( - Stream streamA, - Stream streamB, - Stream streamC, - Stream streamD, - Stream streamE, - Stream streamF, - Stream streamG, - Stream streamH, - Stream streamI, - R Function(A a, B b, C c, D d, E e, F f, G g, H h, I i) zipper, - ) { - return ZipStream( - [ - streamA, - streamB, - streamC, - streamD, - streamE, - streamF, - streamG, - streamH, - streamI - ], - (List values) { - return zipper( - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - values[7] as H, - values[8] as I, - ); - }, - ); - } - - static StreamController _buildController( - Iterable> streams, - R Function(List values) zipper, - ) { - final controller = StreamController(sync: true); - late List> subscriptions; - var pendingSubscriptions = >[]; - - controller.onListen = () { - Completer? completeCurrent; - late final _Window window; - - // resets variables for the next zip window - void next() { - completeCurrent?.complete(null); - completeCurrent = Completer(); - - pendingSubscriptions = subscriptions.toList(); - } - - void Function(T value) doUpdate(int index) { - return (T value) { - window.onValue(index, value); - - if (window.isComplete) { - // all streams emitted for the current zip index - // dispatch event and reset for next - final R combined; - try { - combined = zipper(window.flush()); - } catch (e, s) { - controller.addError(e, s); - return; - } - controller.add(combined); - - // reset for next zip event - next(); - } else { - // other streams are still pending to get to the next - // zip event index. - // pause this subscription while we await the others - final subscription = subscriptions[index] - ..pause(completeCurrent!.future); - - pendingSubscriptions.remove(subscription); - } - }; - } - - subscriptions = streams - .mapIndexed((index, stream) => stream.listen(doUpdate(index), - onError: controller.addError, onDone: controller.close)) - .toList(growable: false); - if (subscriptions.isEmpty) { - controller.close(); - } else { - window = _Window(subscriptions.length); - next(); - } - }; - controller.onPause = () => pendingSubscriptions.pauseAll(); - controller.onResume = () => pendingSubscriptions.resumeAll(); - controller.onCancel = () => pendingSubscriptions.cancelAll(); - - return controller; - } -} - -/// A window keeps track of the values emitted by the different -/// zipped Streams. -class _Window { - final int size; - final List _values; - - int _valuesReceived = 0; - - bool get isComplete => _valuesReceived == size; - - _Window(this.size) : _values = List.filled(size, null); - - void onValue(int index, T value) { - _values[index] = value; - - _valuesReceived++; - } - - List flush() { - _valuesReceived = 0; - - return List.unmodifiable(_values); - } -} - -/// Extends the Stream class with the ability to zip one Stream with another. -extension ZipWithExtension on Stream { - /// Returns a Stream that combines the current stream together with another - /// stream using a given zipper function. - /// - /// ### Example - /// - /// Stream.fromIterable([1]) - /// .zipWith(Stream.fromIterable([2]), (one, two) => one + two) - /// .listen(print); // prints 3 - Stream zipWith(Stream other, R Function(T t, S s) zipper) { - final stream = ZipStream.zip2(this, other, zipper); - - return isBroadcast - ? stream.asBroadcastStream(onCancel: (s) => s.cancel()) - : stream; - } -} diff --git a/sandbox/reactivex/lib/src/subjects/behavior_subject.dart b/sandbox/reactivex/lib/src/subjects/behavior_subject.dart deleted file mode 100644 index 4a9f7d4..0000000 --- a/sandbox/reactivex/lib/src/subjects/behavior_subject.dart +++ /dev/null @@ -1,275 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/rx.dart'; -import 'package:angel3_reactivex/src/streams/value_stream.dart'; -import 'package:angel3_reactivex/src/subjects/subject.dart'; -import 'package:angel3_reactivex/src/transformers/start_with.dart'; -import 'package:angel3_reactivex/src/transformers/start_with_error.dart'; -import 'package:angel3_reactivex/src/utils/empty.dart'; -import 'package:angel3_reactivex/src/utils/error_and_stacktrace.dart'; -import 'package:angel3_reactivex/src/utils/notification.dart'; - -/// A special StreamController that captures the latest item that has been -/// added to the controller, and emits that as the first item to any new -/// listener. -/// -/// This subject allows sending data, error and done events to the listener. -/// The latest item that has been added to the subject will be sent to any -/// new listeners of the subject. After that, any new events will be -/// appropriately sent to the listeners. It is possible to provide a seed value -/// that will be emitted if no items have been added to the subject. -/// -/// BehaviorSubject is, by default, a broadcast (aka hot) controller, in order -/// to fulfill the Rx Subject contract. This means the Subject's `stream` can -/// be listened to multiple times. -/// -/// ### Example -/// -/// final subject = BehaviorSubject(); -/// -/// subject.add(1); -/// subject.add(2); -/// subject.add(3); -/// -/// subject.stream.listen(print); // prints 3 -/// subject.stream.listen(print); // prints 3 -/// subject.stream.listen(print); // prints 3 -/// -/// ### Example with seed value -/// -/// final subject = BehaviorSubject.seeded(1); -/// -/// subject.stream.listen(print); // prints 1 -/// subject.stream.listen(print); // prints 1 -/// subject.stream.listen(print); // prints 1 -class BehaviorSubject extends Subject implements ValueStream { - final _Wrapper _wrapper; - - BehaviorSubject._( - StreamController controller, - Stream stream, - this._wrapper, - ) : super(controller, stream); - - /// Constructs a [BehaviorSubject], optionally pass handlers for - /// [onListen], [onCancel] and a flag to handle events [sync]. - /// - /// See also [StreamController.broadcast] - factory BehaviorSubject({ - void Function()? onListen, - void Function()? onCancel, - bool sync = false, - }) { - // ignore: close_sinks - final controller = StreamController.broadcast( - onListen: onListen, - onCancel: onCancel, - sync: sync, - ); - - final wrapper = _Wrapper(); - - return BehaviorSubject._( - controller, - Rx.defer(_deferStream(wrapper, controller, sync), reusable: true), - wrapper); - } - - /// Constructs a [BehaviorSubject], optionally pass handlers for - /// [onListen], [onCancel] and a flag to handle events [sync]. - /// - /// [seedValue] becomes the current [value] and is emitted immediately. - /// - /// See also [StreamController.broadcast] - factory BehaviorSubject.seeded( - T seedValue, { - void Function()? onListen, - void Function()? onCancel, - bool sync = false, - }) { - // ignore: close_sinks - final controller = StreamController.broadcast( - onListen: onListen, - onCancel: onCancel, - sync: sync, - ); - - final wrapper = _Wrapper.seeded(seedValue); - - return BehaviorSubject._( - controller, - Rx.defer(_deferStream(wrapper, controller, sync), reusable: true), - wrapper, - ); - } - - static Stream Function() _deferStream( - _Wrapper wrapper, StreamController controller, bool sync) => - () { - final errorAndStackTrace = wrapper.errorAndStackTrace; - if (errorAndStackTrace != null && !wrapper.isValue) { - return controller.stream.transform( - StartWithErrorStreamTransformer( - errorAndStackTrace.error, - errorAndStackTrace.stackTrace, - ), - ); - } - - final value = wrapper.value; - if (isNotEmpty(value) && wrapper.isValue) { - return controller.stream - .transform(StartWithStreamTransformer(value as T)); - } - - return controller.stream; - }; - - @override - void onAdd(T event) => _wrapper.setValue(event); - - @override - void onAddError(Object error, [StackTrace? stackTrace]) => - _wrapper.setError(error, stackTrace); - - @override - ValueStream get stream => _BehaviorSubjectStream(this); - - @override - bool get hasValue => isNotEmpty(_wrapper.value); - - @override - T get value { - final value = _wrapper.value; - if (isNotEmpty(value)) { - return value as T; - } - throw ValueStreamError.hasNoValue(); - } - - @override - T? get valueOrNull => unbox(_wrapper.value); - - /// Set and emit the new value. - set value(T newValue) => add(newValue); - - @override - bool get hasError => _wrapper.errorAndStackTrace != null; - - @override - Object? get errorOrNull => _wrapper.errorAndStackTrace?.error; - - @override - Object get error { - final errorAndSt = _wrapper.errorAndStackTrace; - if (errorAndSt != null) { - return errorAndSt.error; - } - throw ValueStreamError.hasNoError(); - } - - @override - StackTrace? get stackTrace => _wrapper.errorAndStackTrace?.stackTrace; - - @override - StreamNotification? get lastEventOrNull { - // data event - if (_wrapper.isValue) { - return StreamNotification.data(_wrapper.value as T); - } - - // error event - final errorAndSt = _wrapper.errorAndStackTrace; - if (errorAndSt != null) { - return ErrorNotification(errorAndSt); - } - - // no event - return null; - } -} - -class _Wrapper { - var isValue = false; - var value = EMPTY; - ErrorAndStackTrace? errorAndStackTrace; - - /// Non-seeded constructor - _Wrapper() : isValue = false; - - _Wrapper.seeded(T v) { - setValue(v); - } - - void setValue(T event) { - value = event; - isValue = true; - } - - void setError(Object error, StackTrace? stackTrace) { - errorAndStackTrace = ErrorAndStackTrace(error, stackTrace); - isValue = false; - } -} - -class _BehaviorSubjectStream extends Stream implements ValueStream { - final BehaviorSubject _subject; - - _BehaviorSubjectStream(this._subject); - - @override - bool get isBroadcast => true; - - // Override == and hashCode so that new streams returned by the same - // subject are considered equal. - // The subject returns a new stream each time it's queried, - // but doesn't have to cache the result. - - @override - int get hashCode => _subject.hashCode ^ 0x35323532; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is _BehaviorSubjectStream && - identical(other._subject, _subject); - } - - @override - StreamSubscription listen( - void Function(T event)? onData, { - Function? onError, - void Function()? onDone, - bool? cancelOnError, - }) => - _subject.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - - @override - Object get error => _subject.error; - - @override - Object? get errorOrNull => _subject.errorOrNull; - - @override - bool get hasError => _subject.hasError; - - @override - bool get hasValue => _subject.hasValue; - - @override - StackTrace? get stackTrace => _subject.stackTrace; - - @override - T get value => _subject.value; - - @override - T? get valueOrNull => _subject.valueOrNull; - - @override - StreamNotification? get lastEventOrNull => _subject.lastEventOrNull; -} diff --git a/sandbox/reactivex/lib/src/subjects/publish_subject.dart b/sandbox/reactivex/lib/src/subjects/publish_subject.dart deleted file mode 100644 index 35bbc6d..0000000 --- a/sandbox/reactivex/lib/src/subjects/publish_subject.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/subjects/subject.dart'; - -/// Exactly like a normal broadcast StreamController with one exception: -/// this class is both a Stream and Sink. -/// -/// This Subject allows sending data, error and done events to the listener. -/// -/// PublishSubject is, by default, a broadcast (aka hot) controller, in order -/// to fulfill the Rx Subject contract. This means the Subject's `stream` can -/// be listened to multiple times. -/// -/// ### Example -/// -/// final subject = PublishSubject(); -/// -/// // observer1 will receive all data and done events -/// subject.stream.listen(observer1); -/// subject.add(1); -/// subject.add(2); -/// -/// // observer2 will only receive 3 and done event -/// subject.stream.listen(observer2); -/// subject.add(3); -/// subject.close(); -class PublishSubject extends Subject { - PublishSubject._(StreamController controller, Stream stream) - : super(controller, stream); - - /// Constructs a [PublishSubject], optionally pass handlers for - /// [onListen], [onCancel] and a flag to handle events [sync]. - /// - /// See also [StreamController.broadcast] - factory PublishSubject( - {void Function()? onListen, - void Function()? onCancel, - bool sync = false}) { - // ignore: close_sinks - final controller = StreamController.broadcast( - onListen: onListen, - onCancel: onCancel, - sync: sync, - ); - - return PublishSubject._( - controller, - controller.stream, - ); - } -} diff --git a/sandbox/reactivex/lib/src/subjects/replay_subject.dart b/sandbox/reactivex/lib/src/subjects/replay_subject.dart deleted file mode 100644 index 5181a07..0000000 --- a/sandbox/reactivex/lib/src/subjects/replay_subject.dart +++ /dev/null @@ -1,204 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -import 'package:angel3_reactivex/src/rx.dart'; -import 'package:angel3_reactivex/src/streams/replay_stream.dart'; -import 'package:angel3_reactivex/src/subjects/subject.dart'; -import 'package:angel3_reactivex/src/transformers/start_with.dart'; -import 'package:angel3_reactivex/src/transformers/start_with_error.dart'; -import 'package:angel3_reactivex/src/utils/collection_extensions.dart'; -import 'package:angel3_reactivex/src/utils/empty.dart'; -import 'package:angel3_reactivex/src/utils/error_and_stacktrace.dart'; - -/// A special StreamController that captures all of the items that have been -/// added to the controller, and emits those as the first items to any new -/// listener. -/// -/// This subject allows sending data, error and done events to the listener. -/// As items are added to the subject, the ReplaySubject will store them. -/// When the stream is listened to, those recorded items will be emitted to -/// the listener. After that, any new events will be appropriately sent to the -/// listeners. It is possible to cap the number of stored events by setting -/// a maxSize value. -/// -/// ReplaySubject is, by default, a broadcast (aka hot) controller, in order -/// to fulfill the Rx Subject contract. This means the Subject's `stream` can -/// be listened to multiple times. -/// -/// ### Example -/// -/// final subject = ReplaySubject(); -/// -/// subject.add(1); -/// subject.add(2); -/// subject.add(3); -/// -/// subject.stream.listen(print); // prints 1, 2, 3 -/// subject.stream.listen(print); // prints 1, 2, 3 -/// subject.stream.listen(print); // prints 1, 2, 3 -/// -/// ### Example with maxSize -/// -/// final subject = ReplaySubject(maxSize: 2); -/// -/// subject.add(1); -/// subject.add(2); -/// subject.add(3); -/// -/// subject.stream.listen(print); // prints 2, 3 -/// subject.stream.listen(print); // prints 2, 3 -/// subject.stream.listen(print); // prints 2, 3 -class ReplaySubject extends Subject implements ReplayStream { - final Queue<_Event> _queue; - final int? _maxSize; - - /// Constructs a [ReplaySubject], optionally pass handlers for - /// [onListen], [onCancel] and a flag to handle events [sync]. - /// - /// See also [StreamController.broadcast] - factory ReplaySubject({ - int? maxSize, - void Function()? onListen, - void Function()? onCancel, - bool sync = false, - }) { - // ignore: close_sinks - final controller = StreamController.broadcast( - onListen: onListen, - onCancel: onCancel, - sync: sync, - ); - - final queue = Queue<_Event>(); - - return ReplaySubject._( - controller, - Rx.defer( - () => queue.toList(growable: false).reversed.fold( - controller.stream, - (stream, event) { - final errorAndStackTrace = event.errorAndStackTrace; - - if (errorAndStackTrace != null) { - return stream.transform( - StartWithErrorStreamTransformer( - errorAndStackTrace.error, - errorAndStackTrace.stackTrace, - ), - ); - } else { - return stream - .transform(StartWithStreamTransformer(event.data as T)); - } - }, - ), - reusable: true, - ), - queue, - maxSize, - ); - } - - ReplaySubject._( - StreamController controller, - Stream stream, - this._queue, - this._maxSize, - ) : super(controller, stream); - - @override - void onAdd(T event) { - if (_queue.length == _maxSize) { - _queue.removeFirst(); - } - - _queue.add(_Event.data(event)); - } - - @override - void onAddError(Object error, [StackTrace? stackTrace]) { - if (_queue.length == _maxSize) { - _queue.removeFirst(); - } - - _queue.add(_Event.error(ErrorAndStackTrace(error, stackTrace))); - } - - @override - List get values => _queue - .where((event) => event.errorAndStackTrace == null) - .map((event) => event.data as T) - .toList(growable: false); - - @override - List get errors => _queue - .mapNotNull((event) => event.errorAndStackTrace?.error) - .toList(growable: false); - - @override - List get stackTraces => _queue - .mapNotNull((event) => event.errorAndStackTrace) - .map((errorAndStackTrace) => errorAndStackTrace.stackTrace) - .toList(growable: false); - - @override - ReplayStream get stream => _ReplaySubjectStream(this); -} - -class _Event { - final Object? data; - final ErrorAndStackTrace? errorAndStackTrace; - - _Event._({required this.data, required this.errorAndStackTrace}); - - factory _Event.data(T data) => _Event._(data: data, errorAndStackTrace: null); - - factory _Event.error(ErrorAndStackTrace e) => - _Event._(errorAndStackTrace: e, data: EMPTY); -} - -class _ReplaySubjectStream extends Stream implements ReplayStream { - final ReplaySubject _subject; - - _ReplaySubjectStream(this._subject); - - @override - bool get isBroadcast => true; - - @override - List get values => _subject.values; - - @override - List get errors => _subject.errors; - - @override - List get stackTraces => _subject.stackTraces; - - // Override == and hashCode so that new streams returned by the same - // subject are considered equal. - // The subject returns a new stream each time it's queried, - // but doesn't have to cache the result. - - @override - int get hashCode => _subject.hashCode ^ 0x35323532; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is _ReplaySubjectStream && identical(other._subject, _subject); - } - - @override - StreamSubscription listen( - void Function(T event)? onData, { - Function? onError, - void Function()? onDone, - bool? cancelOnError, - }) => - _subject.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); -} diff --git a/sandbox/reactivex/lib/src/subjects/subject.dart b/sandbox/reactivex/lib/src/subjects/subject.dart deleted file mode 100644 index 746188d..0000000 --- a/sandbox/reactivex/lib/src/subjects/subject.dart +++ /dev/null @@ -1,231 +0,0 @@ -import 'dart:async'; - -/// The base for all Subjects. If you'd like to create a new Subject, -/// extend from this class. -/// -/// It handles all of the nitty-gritty details that conform to the -/// StreamController spec and don't need to be repeated over and -/// over. -/// -/// Please see `PublishSubject` for the simplest example of how to -/// extend from this class, or `BehaviorSubject` for a slightly more -/// complex example. -abstract class Subject extends StreamView implements StreamController { - final StreamController _controller; - - bool _isAddingStreamItems = false; - - /// Constructs a [Subject] which wraps the provided [controller]. - /// This constructor is applicable only for classes that extend [Subject]. - /// - /// To guarantee the contract of a [Subject], the [controller] must be - /// a broadcast [StreamController] and the [stream] must also be a broadcast [Stream]. - Subject(StreamController controller, Stream stream) - : _controller = controller, - assert(stream.isBroadcast, 'Subject requires a broadcast stream'), - super(stream); - - @override - StreamSink get sink => _StreamSinkWrapper(this); - - @override - ControllerCallback? get onListen => _controller.onListen; - - @override - set onListen(void Function()? onListenHandler) { - _controller.onListen = onListenHandler; - } - - @override - Stream get stream => _SubjectStream(this); - - @override - ControllerCallback get onPause => - throw UnsupportedError('Subjects do not support pause callbacks'); - - @override - set onPause(void Function()? onPauseHandler) => - throw UnsupportedError('Subjects do not support pause callbacks'); - - @override - ControllerCallback get onResume => - throw UnsupportedError('Subjects do not support resume callbacks'); - - @override - set onResume(void Function()? onResumeHandler) => - throw UnsupportedError('Subjects do not support resume callbacks'); - - @override - ControllerCancelCallback? get onCancel => _controller.onCancel; - - @override - set onCancel(ControllerCancelCallback? onCancelHandler) { - _controller.onCancel = onCancelHandler; - } - - @override - bool get isClosed => _controller.isClosed; - - @override - bool get isPaused => _controller.isPaused; - - @override - bool get hasListener => _controller.hasListener; - - @override - Future get done => _controller.done; - - @override - void addError(Object error, [StackTrace? stackTrace]) { - if (_isAddingStreamItems) { - throw StateError( - 'You cannot add an error while items are being added from addStream'); - } - - _addError(error, stackTrace); - } - - void _addError(Object error, [StackTrace? stackTrace]) { - if (!_controller.isClosed) { - onAddError(error, stackTrace); - } - - // if the controller is closed, calling addError() will throw an StateError. - // that is expected behavior. - _controller.addError(error, stackTrace); - } - - /// An extension point for sub-classes. Perform any side-effect / state - /// management you need to here, rather than overriding the `add` method - /// directly. - void onAddError(Object error, [StackTrace? stackTrace]) {} - - @override - Future addStream(Stream source, {bool? cancelOnError}) { - if (_isAddingStreamItems) { - throw StateError( - 'You cannot add items while items are being added from addStream'); - } - _isAddingStreamItems = true; - - final completer = Completer(); - void complete() { - if (!completer.isCompleted) { - _isAddingStreamItems = false; - completer.complete(); - } - } - - source.listen( - _add, - onError: identical(cancelOnError, true) - ? (Object e, StackTrace s) { - _addError(e, s); - complete(); - } - : _addError, - onDone: complete, - cancelOnError: cancelOnError, - ); - - return completer.future; - } - - @override - void add(T event) { - if (_isAddingStreamItems) { - throw StateError( - 'You cannot add items while items are being added from addStream'); - } - - _add(event); - } - - void _add(T event) { - if (!_controller.isClosed) { - onAdd(event); - } - - // if the controller is closed, calling add() will throw an StateError. - // that is expected behavior. - _controller.add(event); - } - - /// An extension point for sub-classes. Perform any side-effect / state - /// management you need to here, rather than overriding the `add` method - /// directly. - void onAdd(T event) {} - - @override - Future close() { - if (_isAddingStreamItems) { - throw StateError( - 'You cannot close the subject while items are being added from addStream'); - } - - return _controller.close(); - } -} - -class _SubjectStream extends Stream { - final Subject _subject; - - _SubjectStream(this._subject); - - @override - bool get isBroadcast => true; - - // Override == and hashCode so that new streams returned by the same - // subject are considered equal. - // The subject returns a new stream each time it's queried, - // but doesn't have to cache the result. - - @override - int get hashCode => _subject.hashCode ^ 0x35323532; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is _SubjectStream && identical(other._subject, _subject); - } - - @override - StreamSubscription listen( - void Function(T event)? onData, { - Function? onError, - void Function()? onDone, - bool? cancelOnError, - }) => - _subject.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); -} - -/// A class that exposes only the [StreamSink] interface of an object. -class _StreamSinkWrapper implements StreamSink { - final StreamController _target; - - _StreamSinkWrapper(this._target); - - @override - void add(T data) { - _target.add(data); - } - - @override - void addError(Object error, [StackTrace? stackTrace]) { - _target.addError(error, stackTrace); - } - - @override - Future close() => _target.close(); - - @override - Future addStream(Stream source) => _target.addStream(source); - - @override - Future get done => _target.done; -} diff --git a/sandbox/reactivex/lib/src/transformers/backpressure/backpressure.dart b/sandbox/reactivex/lib/src/transformers/backpressure/backpressure.dart deleted file mode 100644 index c4a544c..0000000 --- a/sandbox/reactivex/lib/src/transformers/backpressure/backpressure.dart +++ /dev/null @@ -1,357 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -import 'package:angel3_reactivex/src/utils/collection_extensions.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -/// The strategy that is used to determine how and when a new window is created. -enum WindowStrategy { - /// cancels the open window (if any) and immediately opens a fresh one. - everyEvent, - - /// waits until the current open window completes, then when the - /// source [Stream] emits a next event, it opens a new window. - eventAfterLastWindow, - - /// opens a recurring window right after the very first event on - /// the source [Stream] is emitted. - firstEventOnly, - - /// does not open any windows, rather all events are buffered and emitted - /// whenever the handler triggers, after this trigger, the buffer is cleared. - onHandler -} - -class _BackpressureStreamSink extends ForwardingSink { - final WindowStrategy _strategy; - final Stream Function(S event)? _windowStreamFactory; - final T Function(S event)? _onWindowStart; - final T Function(List queue)? _onWindowEnd; - final int _startBufferEvery; - final bool Function(List queue)? _closeWindowWhen; - final bool _ignoreEmptyWindows; - final bool _dispatchOnClose; - final Queue queue = DoubleLinkedQueue(); - final int? maxLengthQueue; - var skip = 0; - var _hasData = false; - var _mainClosed = false; - StreamSubscription? _windowSubscription; - - _BackpressureStreamSink( - this._strategy, - this._windowStreamFactory, - this._onWindowStart, - this._onWindowEnd, - this._startBufferEvery, - this._closeWindowWhen, - this._ignoreEmptyWindows, - this._dispatchOnClose, - this.maxLengthQueue, - ); - - @override - void onData(S data) { - _hasData = true; - maybeCreateWindow(data, sink); - - if (skip == 0) { - queue.add(data); - - if (maxLengthQueue != null && queue.length > maxLengthQueue!) { - queue.removeFirstElements(queue.length - maxLengthQueue!); - } - } - - if (skip > 0) { - skip--; - } - - maybeCloseWindow(sink); - } - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() { - _mainClosed = true; - - if (_strategy == WindowStrategy.eventAfterLastWindow) { - return; - } - - // treat the final event as a Window that opens - // and immediately closes again - if (_dispatchOnClose && queue.isNotEmpty) { - resolveWindowStart(queue.last, sink); - } - - resolveWindowEnd(sink, true); - - queue.clear(); - - _windowSubscription?.cancel(); - sink.close(); - } - - @override - FutureOr onCancel() => _windowSubscription?.cancel(); - - @override - void onListen() {} - - @override - void onPause() => _windowSubscription?.pause(); - - @override - void onResume() => _windowSubscription?.resume(); - - void maybeCreateWindow(S event, EventSink sink) { - switch (_strategy) { - // for example throttle - case WindowStrategy.eventAfterLastWindow: - if (_windowSubscription != null) return; - - _windowSubscription = singleWindow(event, sink); - - resolveWindowStart(event, sink); - - break; - // for example scan - case WindowStrategy.firstEventOnly: - if (_windowSubscription != null) return; - - _windowSubscription = multiWindow(event, sink); - - resolveWindowStart(event, sink); - - break; - // for example debounce - case WindowStrategy.everyEvent: - _windowSubscription?.cancel(); - - _windowSubscription = singleWindow(event, sink); - - resolveWindowStart(event, sink); - - break; - case WindowStrategy.onHandler: - break; - } - } - - void maybeCloseWindow(EventSink sink) { - if (_closeWindowWhen != null && _closeWindowWhen!(unmodifiableQueue)) { - resolveWindowEnd(sink); - } - } - - StreamSubscription singleWindow(S event, EventSink sink) => - buildStream(event, sink).take(1).listen( - null, - onError: sink.addError, - onDone: () => resolveWindowEnd(sink, _mainClosed), - ); - - // opens a new Window which is kept open until the main Stream - // closes. - StreamSubscription multiWindow(S event, EventSink sink) => - buildStream(event, sink).listen( - (dynamic _) => resolveWindowEnd(sink), - onError: sink.addError, - onDone: () => resolveWindowEnd(sink), - ); - - Stream buildStream(S event, EventSink sink) { - Stream stream; - - _windowSubscription?.cancel(); - - stream = _windowStreamFactory!(event); - - return stream; - } - - void resolveWindowStart(S event, EventSink sink) { - if (_onWindowStart != null) { - sink.add(_onWindowStart!(event)); - } - } - - void resolveWindowEnd(EventSink sink, [bool isControllerClosing = false]) { - if (isControllerClosing && - _strategy == WindowStrategy.eventAfterLastWindow) { - if (_dispatchOnClose && - _hasData && - queue.length > 1 && - _onWindowEnd != null) { - sink.add(_onWindowEnd!(unmodifiableQueue)); - } - - queue.clear(); - _windowSubscription?.cancel(); - _windowSubscription = null; - - sink.close(); - return; - } - - if (isControllerClosing || - _strategy == WindowStrategy.eventAfterLastWindow || - _strategy == WindowStrategy.everyEvent) { - _windowSubscription?.cancel(); - _windowSubscription = null; - } - - if (isControllerClosing && !_dispatchOnClose) { - return; - } - - if (_hasData && (queue.isNotEmpty || !_ignoreEmptyWindows)) { - if (_onWindowEnd != null) { - sink.add(_onWindowEnd!(unmodifiableQueue)); - } - - // prepare the buffer for the next window. - // by default, this is just a cleared buffer - if (!isControllerClosing && _startBufferEvery > 0) { - skip = _startBufferEvery > queue.length - ? _startBufferEvery - queue.length - : 0; - - // ...unless startBufferEvery is provided. - // here we backtrack to the first event of the last buffer - // and count forward using startBufferEvery until we reach - // the next event. - // - // if the next event is found inside the current buffer, - // then this event and any later events in the buffer - // become the starting values of the next buffer. - // if the next event is not yet available, then a skip - // count is calculated. - // this count will skip the next Future n-events. - // when skip is reset to 0, then we start adding events - // again into the new buffer. - // - // example: - // startBufferEvery = 2 - // last buffer: [0, 1, 2, 3, 4] - // 0 is the first event, - // 2 is the n-th event - // new buffer starts with [2, 3, 4] - // - // example: - // startBufferEvery = 3 - // last buffer: [0, 1] - // 0 is the first event, - // the n-the event is not yet dispatched at this point - // skip becomes 1 - // event 2 is skipped, skip becomes 0 - // event 3 is now added to the buffer - if (_startBufferEvery < queue.length) { - queue.removeFirstElements(_startBufferEvery); - } else { - queue.clear(); - } - } else { - queue.clear(); - } - } - } - - List get unmodifiableQueue => List.unmodifiable(queue); -} - -/// A highly customizable [StreamTransformer] which can be configured -/// to serve any of the common rx backpressure operators. -/// -/// The [StreamTransformer] works by creating windows, during which it -/// buffers events to a [Queue]. -/// -/// The [StreamTransformer] works by creating windows, during which it -/// buffers events to a [Queue]. It uses a [WindowStrategy] to determine -/// how and when a new window is created. -/// -/// onWindowStart and onWindowEnd are handlers that fire when a window -/// opens and closes, right before emitting the transformed event. -/// -/// startBufferEvery allows to skip events coming from the source [Stream]. -/// -/// ignoreEmptyWindows can be set to true, to allow events to be emitted -/// at the end of a window, even if the current buffer is empty. -/// If the buffer is empty, then an empty [List] will be emitted. -/// If false, then nothing is emitted on an empty buffer. -/// -/// dispatchOnClose will cause the remaining values in the buffer to be -/// emitted when the source [Stream] closes. -/// When false, the remaining buffer is discarded on close. -class BackpressureStreamTransformer extends StreamTransformerBase { - /// Determines how the window is created - final WindowStrategy strategy; - - /// Factory method used to create the [Stream] which will be buffered - final Stream Function(S event)? windowStreamFactory; - - /// Handler which fires when the window opens - final T Function(S event)? onWindowStart; - - /// Handler which fires when the window closes - final T Function(List queue)? onWindowEnd; - - /// Maximum length of the buffer. - /// Specify this value to avoid running out of memory when adding too many events to the buffer. - /// If it's `null`, maximum length of the buffer is unlimited. - final int? maxLengthQueue; - - /// Used to skip an amount of events - final int startBufferEvery; - - /// Predicate which determines when the current window should close - final bool Function(List queue)? closeWindowWhen; - - /// Toggle to prevent, or allow windows that contain - /// no events to be dispatched - final bool ignoreEmptyWindows; - - /// Toggle to prevent, or allow the final set of events to be dispatched - /// when the source [Stream] closes - final bool dispatchOnClose; - - /// Constructs a [StreamTransformer] which buffers events emitted by the - /// [Stream] that is created by [windowStreamFactory]. - /// - /// Use the various optional parameters to precisely determine how and when - /// this buffer should be created. - /// - /// For more info on the parameters, see [BackpressureStreamTransformer], - /// or see the various back pressure [StreamTransformer]s for examples. - BackpressureStreamTransformer( - this.strategy, - this.windowStreamFactory, { - this.onWindowStart, - this.onWindowEnd, - this.startBufferEvery = 0, - this.closeWindowWhen, - this.ignoreEmptyWindows = true, - this.dispatchOnClose = true, - this.maxLengthQueue, - }); - - @override - Stream bind(Stream stream) => forwardStream( - stream, - () => _BackpressureStreamSink( - strategy, - windowStreamFactory, - onWindowStart, - onWindowEnd, - startBufferEvery, - closeWindowWhen, - ignoreEmptyWindows, - dispatchOnClose, - maxLengthQueue, - ), - ); -} diff --git a/sandbox/reactivex/lib/src/transformers/backpressure/buffer.dart b/sandbox/reactivex/lib/src/transformers/backpressure/buffer.dart deleted file mode 100644 index 1b6044b..0000000 --- a/sandbox/reactivex/lib/src/transformers/backpressure/buffer.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/transformers/backpressure/backpressure.dart'; - -/// Creates a [Stream] where each item is a [List] containing the items -/// from the source sequence. -/// -/// This [List] is emitted every time the window [Stream] -/// emits an event. -/// -/// ### Example -/// -/// Stream.periodic(const Duration(milliseconds: 100), (i) => i) -/// .buffer(Stream.periodic(const Duration(milliseconds: 160), (i) => i)) -/// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... -class BufferStreamTransformer - extends BackpressureStreamTransformer> { - /// Constructs a [StreamTransformer] which buffers events into a [List] and - /// emits this [List] whenever [window] fires an event. - /// - /// The [List] is cleared upon every [window] event. - BufferStreamTransformer(Stream Function(T event) window) - : super(WindowStrategy.firstEventOnly, window, - onWindowEnd: (queue) => queue, ignoreEmptyWindows: false); -} - -/// Buffers a number of values from the source Stream by count then -/// emits the buffer and clears it, and starts a new buffer each -/// startBufferEvery values. If startBufferEvery is not provided, -/// then new buffers are started immediately at the start of the source -/// and when each buffer closes and is emitted. -/// -/// ### Example -/// count is the maximum size of the buffer emitted -/// -/// Rx.range(1, 4) -/// .bufferCount(2) -/// .listen(print); // prints [1, 2], [3, 4] done! -/// -/// ### Example -/// if startBufferEvery is 2, then a new buffer will be started -/// on every other value from the source. A new buffer is started at the -/// beginning of the source by default. -/// -/// Rx.range(1, 5) -/// .bufferCount(3, 2) -/// .listen(print); // prints [1, 2, 3], [3, 4, 5], [5] done! -class BufferCountStreamTransformer - extends BackpressureStreamTransformer> { - /// Constructs a [StreamTransformer] which buffers events into a [List] and - /// emits this [List] whenever its length is equal to [count]. - /// - /// A new buffer is created for every n-th event emitted - /// by the [Stream] that is being transformed, as specified by - /// the [startBufferEvery] parameter. - /// - /// If [startBufferEvery] is omitted or equals 0, then a new buffer is started whenever - /// the previous one reaches a length of [count]. - BufferCountStreamTransformer(int count, [int startBufferEvery = 0]) - : super(WindowStrategy.onHandler, null, - onWindowEnd: (queue) => queue, - startBufferEvery: startBufferEvery, - closeWindowWhen: (queue) => queue.length == count) { - if (count < 1) throw ArgumentError.value(count, 'count'); - if (startBufferEvery < 0) { - throw ArgumentError.value(startBufferEvery, 'startBufferEvery'); - } - } -} - -/// Creates a [Stream] where each item is a [List] containing the items -/// from the source sequence, batched whenever test passes. -/// -/// ### Example -/// -/// Stream.periodic(const Duration(milliseconds: 100), (int i) => i) -/// .bufferTest((i) => i % 2 == 0) -/// .listen(print); // prints [0], [1, 2] [3, 4] [5, 6] ... -class BufferTestStreamTransformer - extends BackpressureStreamTransformer> { - /// Constructs a [StreamTransformer] which buffers events into a [List] and - /// emits this [List] whenever the [test] Function yields true. - BufferTestStreamTransformer(bool Function(T value) test) - : super(WindowStrategy.onHandler, null, - onWindowEnd: (queue) => queue, - closeWindowWhen: (queue) => test(queue.last)); -} - -/// Extends the Stream class with the ability to buffer events in various ways -extension BufferExtensions on Stream { - /// Creates a Stream where each item is a [List] containing the items - /// from the source sequence. - /// - /// This [List] is emitted every time [window] emits an event. - /// - /// ### Example - /// - /// Stream.periodic(Duration(milliseconds: 100), (i) => i) - /// .buffer(Stream.periodic(Duration(milliseconds: 160), (i) => i)) - /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... - Stream> buffer(Stream window) => - BufferStreamTransformer((_) => window).bind(this); - - /// Buffers a number of values from the source Stream by [count] then - /// emits the buffer and clears it, and starts a new buffer each - /// [startBufferEvery] values. If [startBufferEvery] is not provided, - /// then new buffers are started immediately at the start of the source - /// and when each buffer closes and is emitted. - /// - /// ### Example - /// [count] is the maximum size of the buffer emitted - /// - /// RangeStream(1, 4) - /// .bufferCount(2) - /// .listen(print); // prints [1, 2], [3, 4] done! - /// - /// ### Example - /// if [startBufferEvery] is 2, then a new buffer will be started - /// on every other value from the source. A new buffer is started at the - /// beginning of the source by default. - /// - /// RangeStream(1, 5) - /// .bufferCount(3, 2) - /// .listen(print); // prints [1, 2, 3], [3, 4, 5], [5] done! - Stream> bufferCount(int count, [int startBufferEvery = 0]) => - BufferCountStreamTransformer(count, startBufferEvery).bind(this); - - /// Creates a Stream where each item is a [List] containing the items - /// from the source sequence, batched whenever test passes. - /// - /// ### Example - /// - /// Stream.periodic(Duration(milliseconds: 100), (int i) => i) - /// .bufferTest((i) => i % 2 == 0) - /// .listen(print); // prints [0], [1, 2] [3, 4] [5, 6] ... - Stream> bufferTest(bool Function(T event) onTestHandler) => - BufferTestStreamTransformer(onTestHandler).bind(this); - - /// Creates a Stream where each item is a [List] containing the items - /// from the source sequence, sampled on a time frame with [duration]. - /// - /// ### Example - /// - /// Stream.periodic(Duration(milliseconds: 100), (int i) => i) - /// .bufferTime(Duration(milliseconds: 220)) - /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... - Stream> bufferTime(Duration duration) => - buffer(Stream.periodic(duration)); -} diff --git a/sandbox/reactivex/lib/src/transformers/backpressure/debounce.dart b/sandbox/reactivex/lib/src/transformers/backpressure/debounce.dart deleted file mode 100644 index b9e9a9e..0000000 --- a/sandbox/reactivex/lib/src/transformers/backpressure/debounce.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/streams/timer.dart'; -import 'package:angel3_reactivex/src/transformers/backpressure/backpressure.dart'; - -/// Transforms a [Stream] so that will only emit items from the source sequence -/// if a window has completed, without the source sequence emitting -/// another item. -/// -/// This window is created after the last debounced event was emitted. -/// You can use the value of the last debounced event to determine -/// the length of the next window. -/// -/// A window is open until the first window event emits. -/// -/// The debounce [StreamTransformer] filters out items emitted by the source -/// Stream that are rapidly followed by another emitted item. -/// -/// [Interactive marble diagram](http://rxmarbles.com/#debounce) -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3, 4]) -/// .debounceTime(Duration(seconds: 1)) -/// .listen(print); // prints 4 -class DebounceStreamTransformer extends BackpressureStreamTransformer { - /// Constructs a [StreamTransformer] which will only emit items from the source sequence - /// if a window has completed, without the source sequence emitting. - /// - /// The [window] is reset whenever the [Stream] that is being transformed - /// emits an event. - DebounceStreamTransformer(Stream Function(T event) window) - : super( - WindowStrategy.everyEvent, - window, - onWindowEnd: (queue) => queue.last, - maxLengthQueue: 1, - ); -} - -/// Extends the Stream class with the ability to debounce events in various ways -extension DebounceExtensions on Stream { - /// Transforms a [Stream] so that will only emit items from the source sequence - /// if a [window] has completed, without the source sequence emitting - /// another item. - /// - /// This [window] is created after the last debounced event was emitted. - /// You can use the value of the last debounced event to determine - /// the length of the next [window]. - /// - /// A [window] is open until the first [window] event emits. - /// - /// debounce filters out items emitted by the source [Stream] - /// that are rapidly followed by another emitted item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#debounce) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3, 4]) - /// .debounce((_) => TimerStream(true, Duration(seconds: 1))) - /// .listen(print); // prints 4 - Stream debounce(Stream Function(T event) window) => - DebounceStreamTransformer(window).bind(this); - - /// Transforms a [Stream] so that will only emit items from the source - /// sequence whenever the time span defined by [duration] passes, without the - /// source sequence emitting another item. - /// - /// This time span start after the last debounced event was emitted. - /// - /// debounceTime filters out items emitted by the source [Stream] that are - /// rapidly followed by another emitted item. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#debounceTime) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3, 4]) - /// .debounceTime(Duration(seconds: 1)) - /// .listen(print); // prints 4 - Stream debounceTime(Duration duration) => - DebounceStreamTransformer((_) => TimerStream(null, duration)) - .bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/backpressure/pairwise.dart b/sandbox/reactivex/lib/src/transformers/backpressure/pairwise.dart deleted file mode 100644 index fa2320e..0000000 --- a/sandbox/reactivex/lib/src/transformers/backpressure/pairwise.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:angel3_reactivex/src/streams/never.dart'; -import 'package:angel3_reactivex/src/transformers/backpressure/backpressure.dart'; - -/// Emits the n-th and n-1th events as a pair. -/// The first event won't be emitted until the second one arrives. -/// -/// ### Example -/// -/// Rx.range(1, 4) -/// .pairwise() -/// .listen(print); // prints [1, 2], [2, 3], [3, 4] -class PairwiseStreamTransformer - extends BackpressureStreamTransformer> { - /// Constructs a [StreamTransformer] which buffers events into pairs as a [List]. - PairwiseStreamTransformer() - : super(WindowStrategy.firstEventOnly, (_) => NeverStream(), - onWindowEnd: (queue) => queue, - startBufferEvery: 1, - closeWindowWhen: (queue) => queue.length == 2, - dispatchOnClose: false); -} - -/// Extends the Stream class with the ability to emit the nth and n-1th events -/// as a pair -extension PairwiseExtension on Stream { - /// Emits the n-th and n-1th events as a pair. - /// The first event won't be emitted until the second one arrives. - /// - /// ### Example - /// - /// RangeStream(1, 4) - /// .pairwise() - /// .listen(print); // prints [1, 2], [2, 3], [3, 4] - Stream> pairwise() => PairwiseStreamTransformer().bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/backpressure/sample.dart b/sandbox/reactivex/lib/src/transformers/backpressure/sample.dart deleted file mode 100644 index b2c5545..0000000 --- a/sandbox/reactivex/lib/src/transformers/backpressure/sample.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/transformers/backpressure/backpressure.dart'; - -/// A [StreamTransformer] that, when the specified window [Stream] emits -/// an item or completes, emits the most recently emitted item (if any) -/// emitted by the source [Stream] since the previous emission from -/// the sample [Stream]. -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3]) -/// .transform(SampleStreamTransformer(TimerStream(1, const Duration(seconds: 1))) -/// .listen(print); // prints 3 -class SampleStreamTransformer extends BackpressureStreamTransformer { - /// Constructs a [StreamTransformer] that, when the specified [window] emits - /// an item or completes, emits the most recently emitted item (if any) - /// emitted by the source [Stream] since the previous emission from - /// the sample [Stream]. - SampleStreamTransformer(Stream Function(T event) window) - : super(WindowStrategy.firstEventOnly, window, - onWindowEnd: (queue) => queue.last); -} - -/// Extends the Stream class with the ability to sample events from the Stream -extension SampleExtensions on Stream { - /// Emits the most recently emitted item (if any) - /// emitted by the source [Stream] since the previous emission from - /// the [sampleStream]. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3]) - /// .sample(TimerStream(1, Duration(seconds: 1))) - /// .listen(print); // prints 3 - Stream sample(Stream sampleStream) => - SampleStreamTransformer((_) => sampleStream).bind(this); - - /// Emits the most recently emitted item (if any) emitted by the source - /// [Stream] since the previous emission within the recurring time span, - /// defined by [duration] - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3]) - /// .sampleTime(Duration(seconds: 1)) - /// .listen(print); // prints 3 - Stream sampleTime(Duration duration) => - sample(Stream.periodic(duration)); -} diff --git a/sandbox/reactivex/lib/src/transformers/backpressure/throttle.dart b/sandbox/reactivex/lib/src/transformers/backpressure/throttle.dart deleted file mode 100644 index 523c180..0000000 --- a/sandbox/reactivex/lib/src/transformers/backpressure/throttle.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/streams/timer.dart'; -import 'package:angel3_reactivex/src/transformers/backpressure/backpressure.dart'; - -/// A [StreamTransformer] that emits a value from the source [Stream], -/// then ignores subsequent source values while the window [Stream] is open, -/// then repeats this process. -/// -/// If leading is true, then the first item in each window is emitted. -/// If trailing is true, then the last item in each window is emitted. -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3]) -/// .transform(ThrottleStreamTransformer((_) => TimerStream(true, const Duration(seconds: 1)))) -/// .listen(print); // prints 1 -class ThrottleStreamTransformer extends BackpressureStreamTransformer { - /// Construct a [StreamTransformer] that emits a value from the source [Stream], - /// then ignores subsequent source values while the window [Stream] is open, - /// then repeats this process. - /// - /// If [leading] is true, then the first item in each window is emitted. - /// If [trailing] is true, then the last item in each window is emitted. - ThrottleStreamTransformer( - Stream Function(T event) window, { - bool trailing = false, - bool leading = true, - }) : super( - WindowStrategy.eventAfterLastWindow, - window, - onWindowStart: leading ? (event) => event : null, - onWindowEnd: trailing ? (queue) => queue.last : null, - dispatchOnClose: trailing, - maxLengthQueue: trailing ? 2 : 0, - ); -} - -/// Extends the Stream class with the ability to throttle events in various ways -extension ThrottleExtensions on Stream { - /// Emits a value from the source [Stream], then ignores subsequent source values - /// while the window [Stream] is open, then repeats this process. - /// - /// If leading is true, then the first item in each window is emitted. - /// If trailing is true, then the last item in each window is emitted. - /// - /// You can use the value of the last throttled event to determine the length - /// of the next [window]. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3]) - /// .throttle((_) => TimerStream(true, Duration(seconds: 1))); - Stream throttle(Stream Function(T event) window, - {bool trailing = false, bool leading = true}) => - ThrottleStreamTransformer( - window, - trailing: trailing, - leading: leading, - ).bind(this); - - /// Emits a value from the source [Stream], then ignores subsequent source values - /// for a duration, then repeats this process. - /// - /// If leading is true, then the first item in each window is emitted. - /// If [trailing] is true, then the last item is emitted instead. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3]) - /// .throttleTime(Duration(seconds: 1)); - Stream throttleTime(Duration duration, - {bool trailing = false, bool leading = true}) => - ThrottleStreamTransformer( - (_) => TimerStream(true, duration), - trailing: trailing, - leading: leading, - ).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/backpressure/window.dart b/sandbox/reactivex/lib/src/transformers/backpressure/window.dart deleted file mode 100644 index 544a1b4..0000000 --- a/sandbox/reactivex/lib/src/transformers/backpressure/window.dart +++ /dev/null @@ -1,158 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/transformers/backpressure/backpressure.dart'; - -/// Creates a [Stream] where each item is a [Stream] containing the items -/// from the source sequence. -/// -/// This [List] is emitted every time the window [Stream] -/// emits an event. -/// -/// ### Example -/// -/// Stream.periodic(const Duration(milliseconds: 100), (i) => i) -/// .window(Stream.periodic(const Duration(milliseconds: 160), (i) => i)) -/// .asyncMap((stream) => stream.toList()) -/// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... -class WindowStreamTransformer - extends BackpressureStreamTransformer> { - /// Constructs a [StreamTransformer] which buffers events into a [Stream] and - /// emits this [Stream] whenever [window] fires an event. - /// - /// The [Stream] is recreated and starts empty upon every [window] event. - WindowStreamTransformer(Stream Function(T event) window) - : super(WindowStrategy.firstEventOnly, window, - onWindowEnd: (queue) => Stream.fromIterable(queue), - ignoreEmptyWindows: false); -} - -/// Buffers a number of values from the source Stream by count then emits the -/// buffer as a [Stream] and clears it, and starts a new buffer each -/// startBufferEvery values. If startBufferEvery is not provided, then new -/// buffers are started immediately at the start of the source and when each -/// buffer closes and is emitted. -/// -/// ### Example -/// count is the maximum size of the buffer emitted -/// -/// Rx.range(1, 4) -/// .windowCount(2) -/// .asyncMap((stream) => stream.toList()) -/// .listen(print); // prints [1, 2], [3, 4] done! -/// -/// ### Example -/// if startBufferEvery is 2, then a new buffer will be started -/// on every other value from the source. A new buffer is started at the -/// beginning of the source by default. -/// -/// Rx.range(1, 5) -/// .bufferCount(3, 2) -/// .listen(print); // prints [1, 2, 3], [3, 4, 5], [5] done! -class WindowCountStreamTransformer - extends BackpressureStreamTransformer> { - /// Constructs a [StreamTransformer] which buffers events into a [Stream] and - /// emits this [Stream] whenever its length is equal to [count]. - /// - /// A new buffer is created for every n-th event emitted - /// by the [Stream] that is being transformed, as specified by - /// the [startBufferEvery] parameter. - /// - /// If [startBufferEvery] is omitted or equals 0, then a new buffer is started whenever - /// the previous one reaches a length of [count]. - WindowCountStreamTransformer(int count, [int startBufferEvery = 0]) - : super(WindowStrategy.onHandler, null, - onWindowEnd: (queue) => Stream.fromIterable(queue), - startBufferEvery: startBufferEvery, - closeWindowWhen: (queue) => queue.length == count) { - if (count < 1) throw ArgumentError.value(count, 'count'); - if (startBufferEvery < 0) { - throw ArgumentError.value(startBufferEvery, 'startBufferEvery'); - } - } -} - -/// Creates a [Stream] where each item is a [Stream] containing the items -/// from the source sequence, batched whenever test passes. -/// -/// ### Example -/// -/// Stream.periodic(const Duration(milliseconds: 100), (int i) => i) -/// .windowTest((i) => i % 2 == 0) -/// .asyncMap((stream) => stream.toList()) -/// .listen(print); // prints [0], [1, 2] [3, 4] [5, 6] ... -class WindowTestStreamTransformer - extends BackpressureStreamTransformer> { - /// Constructs a [StreamTransformer] which buffers events into a [Stream] and - /// emits this [Stream] whenever the [test] Function yields true. - WindowTestStreamTransformer(bool Function(T value) test) - : super(WindowStrategy.onHandler, null, - onWindowEnd: (queue) => Stream.fromIterable(queue), - closeWindowWhen: (queue) => test(queue.last)); -} - -/// Extends the Stream class with the ability to window -extension WindowExtensions on Stream { - /// Creates a Stream where each item is a [Stream] containing the items from - /// the source sequence. - /// - /// This [List] is emitted every time [window] emits an event. - /// - /// ### Example - /// - /// Stream.periodic(Duration(milliseconds: 100), (i) => i) - /// .window(Stream.periodic(Duration(milliseconds: 160), (i) => i)) - /// .asyncMap((stream) => stream.toList()) - /// .listen(print); // prints [0, 1] [2, 3] [4, 5] ... - Stream> window(Stream window) => - WindowStreamTransformer((_) => window).bind(this); - - /// Buffers a number of values from the source Stream by [count] then emits - /// the buffer as a [Stream] and clears it, and starts a new buffer each - /// [startBufferEvery] values. If [startBufferEvery] is not provided, then new - /// buffers are started immediately at the start of the source and when each - /// buffer closes and is emitted. - /// - /// ### Example - /// [count] is the maximum size of the buffer emitted - /// - /// RangeStream(1, 4) - /// .windowCount(2) - /// .asyncMap((stream) => stream.toList()) - /// .listen(print); // prints [1, 2], [3, 4] done! - /// - /// ### Example - /// if [startBufferEvery] is 2, then a new buffer will be started - /// on every other value from the source. A new buffer is started at the - /// beginning of the source by default. - /// - /// RangeStream(1, 5) - /// .bufferCount(3, 2) - /// .listen(print); // prints [1, 2, 3], [3, 4, 5], [5] done! - Stream> windowCount(int count, [int startBufferEvery = 0]) => - WindowCountStreamTransformer(count, startBufferEvery).bind(this); - - /// Creates a Stream where each item is a [Stream] containing the items from - /// the source sequence, batched whenever test passes. - /// - /// ### Example - /// - /// Stream.periodic(Duration(milliseconds: 100), (int i) => i) - /// .windowTest((i) => i % 2 == 0) - /// .asyncMap((stream) => stream.toList()) - /// .listen(print); // prints [0], [1, 2] [3, 4] [5, 6] ... - Stream> windowTest(bool Function(T event) onTestHandler) => - WindowTestStreamTransformer(onTestHandler).bind(this); - - /// Creates a Stream where each item is a [Stream] containing the items from - /// the source sequence, sampled on a time frame with [duration]. - /// - /// ### Example - /// - /// Stream.periodic(Duration(milliseconds: 100), (int i) => i) - /// .windowTime(Duration(milliseconds: 220)) - /// .doOnData((_) => print('next window')) - /// .flatMap((s) => s) - /// .listen(print); // prints next window 0, 1, next window 2, 3, ... - Stream> windowTime(Duration duration) => - window(Stream.periodic(duration)); -} diff --git a/sandbox/reactivex/lib/src/transformers/default_if_empty.dart b/sandbox/reactivex/lib/src/transformers/default_if_empty.dart deleted file mode 100644 index fb9deed..0000000 --- a/sandbox/reactivex/lib/src/transformers/default_if_empty.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'dart:async'; - -class _DefaultIfEmptyStreamSink implements EventSink { - final S _defaultValue; - final EventSink _outputSink; - bool _isEmpty = true; - - _DefaultIfEmptyStreamSink(this._outputSink, this._defaultValue); - - @override - void add(S data) { - _isEmpty = false; - _outputSink.add(data); - } - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() { - if (_isEmpty) { - _outputSink.add(_defaultValue); - } - - _outputSink.close(); - } -} - -/// Emit items from the source [Stream], or a single default item if the source -/// Stream emits nothing. -/// -/// ### Example -/// -/// Stream.empty() -/// .transform(DefaultIfEmptyStreamTransformer(10)) -/// .listen(print); // prints 10 -class DefaultIfEmptyStreamTransformer extends StreamTransformerBase { - /// The event that should be emitted if the source [Stream] is empty - final S defaultValue; - - /// Constructs a [StreamTransformer] which either emits from the source [Stream], - /// or just a [defaultValue] if the source [Stream] emits nothing. - DefaultIfEmptyStreamTransformer(this.defaultValue); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _DefaultIfEmptyStreamSink(sink, defaultValue)); -} - -/// -extension DefaultIfEmptyExtension on Stream { - /// Emit items from the source Stream, or a single default item if the source - /// Stream emits nothing. - /// - /// ### Example - /// - /// Stream.empty().defaultIfEmpty(10).listen(print); // prints 10 - Stream defaultIfEmpty(T defaultValue) => - DefaultIfEmptyStreamTransformer(defaultValue).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/delay.dart b/sandbox/reactivex/lib/src/transformers/delay.dart deleted file mode 100644 index f2fd2b9..0000000 --- a/sandbox/reactivex/lib/src/transformers/delay.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -import 'package:angel3_reactivex/src/rx.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -class _DelayStreamSink extends ForwardingSink { - final Duration _duration; - var _inputClosed = false; - final _subscriptions = Queue>(); - - _DelayStreamSink(this._duration); - - @override - void onData(S data) { - final subscription = Rx.timer(null, _duration).listen((_) { - _subscriptions.removeFirst(); - - sink.add(data); - - if (_inputClosed && _subscriptions.isEmpty) { - sink.close(); - } - }); - - _subscriptions.addLast(subscription); - } - - @override - void onError(Object error, StackTrace st) => sink.addError(error, st); - - @override - void onDone() { - _inputClosed = true; - - if (_subscriptions.isEmpty) { - sink.close(); - } - } - - @override - Future? onCancel() => _subscriptions.cancelAll(); - - @override - void onListen() {} - - @override - void onPause() => _subscriptions.pauseAll(); - - @override - void onResume() => _subscriptions.resumeAll(); -} - -/// The Delay operator modifies its source Stream by pausing for -/// a particular increment of time (that you specify) before emitting -/// each of the source Stream’s items. -/// This has the effect of shifting the entire sequence of items emitted -/// by the Stream forward in time by that specified increment. -/// -/// [Interactive marble diagram](http://rxmarbles.com/#delay) -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3, 4]) -/// .delay(Duration(seconds: 1)) -/// .listen(print); // [after one second delay] prints 1, 2, 3, 4 immediately -class DelayStreamTransformer extends StreamTransformerBase { - /// The delay used to pause initial emission of events by - final Duration duration; - - /// Constructs a [StreamTransformer] which will first pause for [duration] of time, - /// before submitting events from the source [Stream]. - DelayStreamTransformer(this.duration); - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _DelayStreamSink(duration)); -} - -/// Extends the Stream class with the ability to delay events being emitted -extension DelayExtension on Stream { - /// The Delay operator modifies its source Stream by pausing for a particular - /// increment of time (that you specify) before emitting each of the source - /// Stream’s items. This has the effect of shifting the entire sequence of - /// items emitted by the Stream forward in time by that specified increment. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#delay) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3, 4]) - /// .delay(Duration(seconds: 1)) - /// .listen(print); // [after one second delay] prints 1, 2, 3, 4 immediately - Stream delay(Duration duration) => - DelayStreamTransformer(duration).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/delay_when.dart b/sandbox/reactivex/lib/src/transformers/delay_when.dart deleted file mode 100644 index 5b80eb0..0000000 --- a/sandbox/reactivex/lib/src/transformers/delay_when.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; -import 'package:angel3_reactivex/src/utils/future.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -class _DelayWhenStreamSink extends ForwardingSink { - final Stream Function(T) itemDelaySelector; - final Stream? listenDelay; - - final subscriptions = >[]; - StreamSubscription? delaySubscription; - var closed = false; - - _DelayWhenStreamSink(this.itemDelaySelector, this.listenDelay); - - @override - void onData(T data) { - final subscription = - itemDelaySelector(data).take(1).listen(null, onError: sink.addError); - - subscription.onDone(() { - subscriptions.remove(subscription); - - sink.add(data); - if (subscriptions.isEmpty && closed) { - sink.close(); - } - }); - - subscriptions.add(subscription); - } - - @override - void onError(Object error, StackTrace st) => sink.addError(error, st); - - @override - void onDone() { - closed = true; - if (subscriptions.isEmpty) { - sink.close(); - } - } - - @override - Future? onCancel() { - final future = delaySubscription?.cancel(); - delaySubscription = null; - - if (subscriptions.isEmpty) { - return future; - } - - final futures = [ - for (final s in subscriptions) s.cancel(), - if (future != null) future, - ]; - subscriptions.clear(); - - return waitFuturesList(futures); - } - - @override - FutureOr onListen() { - if (listenDelay == null) { - return null; - } - - final completer = Completer.sync(); - delaySubscription = listenDelay!.take(1).listen( - null, - onError: (Object e, StackTrace s) { - delaySubscription?.cancel(); - delaySubscription = null; - completer.completeError(e, s); - }, - onDone: () { - delaySubscription?.cancel(); - delaySubscription = null; - completer.complete(null); - }, - ); - return completer.future; - } - - @override - void onPause() { - delaySubscription?.pause(); - subscriptions.pauseAll(); - } - - @override - void onResume() { - delaySubscription?.resume(); - subscriptions.resumeAll(); - } -} - -/// Delays the emission of items from the source [Stream] by a given time span -/// determined by the emissions of another [Stream]. -/// -/// [Interactive marble diagram](http://rxmarbles.com/#delayWhen) -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3]) -/// .transform(DelayWhenStreamTransformer( -/// (i) => Rx.timer(null, Duration(seconds: i)))) -/// .listen(print); // [after 1s] prints 1 [after 1s] prints 2 [after 1s] prints 3 -/// -/// Stream.fromIterable([1, 2, 3]) -/// .transform( -/// DelayWhenStreamTransformer( -/// (i) => Rx.timer(null, Duration(seconds: i)), -/// listenDelay: Rx.timer(null, Duration(seconds: 2)), -/// ), -/// ) -/// .listen(print); // [after 3s] prints 1 [after 1s] prints 2 [after 1s] prints 3 -class DelayWhenStreamTransformer extends StreamTransformerBase { - /// A function used to determine delay time span for each data event. - final Stream Function(T value) itemDelaySelector; - - /// When [listenDelay] emits its first data or done event, the source Stream is listen to. - final Stream? listenDelay; - - /// Constructs a [StreamTransformer] which delays the emission of items - /// from the source [Stream] by a given time span determined by the emissions of another [Stream]. - DelayWhenStreamTransformer(this.itemDelaySelector, {this.listenDelay}); - - @override - Stream bind(Stream stream) => forwardStream( - stream, () => _DelayWhenStreamSink(itemDelaySelector, listenDelay)); -} - -/// Extends the Stream class with the ability to delay events being emitted. -extension DelayWhenExtension on Stream { - /// Delays the emission of items from the source [Stream] by a given time span - /// determined by the emissions of another [Stream]. - /// - /// When the source emits a data element, the `itemDelaySelector` function is called - /// with the data element as argument, and return a "duration" Stream. - /// The source element is emitted on the output Stream only when the "duration" Stream - /// emits a data or done event. - /// - /// Optionally, `delayWhen` takes a second argument `listenDelay`. When `listenDelay` - /// emits its first data or done event, the source Stream is listen to. - /// If `listenDelay` is not provided, `delayWhen` will listen to the source Stream - /// as soon as the output Stream is listen. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#delayWhen) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3]) - /// .delayWhen((i) => Rx.timer(null, Duration(seconds: i))) - /// .listen(print); // [after 1s] prints 1 [after 1s] prints 2 [after 1s] prints 3 - /// - /// Stream.fromIterable([1, 2, 3]) - /// .delayWhen( - /// (i) => Rx.timer(null, Duration(seconds: i)), - /// listenDelay: Rx.timer(null, Duration(seconds: 2)), - /// ) - /// .listen(print); // [after 3s] prints 1 [after 1s] prints 2 [after 1s] prints 3 - Stream delayWhen( - Stream Function(T value) itemDelaySelector, { - Stream? listenDelay, - }) => - DelayWhenStreamTransformer(itemDelaySelector, listenDelay: listenDelay) - .bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/dematerialize.dart b/sandbox/reactivex/lib/src/transformers/dematerialize.dart deleted file mode 100644 index 685b8ce..0000000 --- a/sandbox/reactivex/lib/src/transformers/dematerialize.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/notification.dart'; - -class _DematerializeStreamSink implements EventSink> { - final EventSink _outputSink; - - _DematerializeStreamSink(this._outputSink); - - @override - void add(StreamNotification data) => data.when( - data: _outputSink.add, - done: _outputSink.close, - error: _outputSink.addErrorAndStackTrace, - ); - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() => _outputSink.close(); -} - -/// Converts the onData, onDone, and onError [StreamNotification] objects from a -/// materialized stream into normal onData, onDone, and onError events. -/// -/// When a stream has been materialized, it emits onData, onDone, and onError -/// events as [StreamNotification] objects. Dematerialize simply reverses this by -/// transforming [StreamNotification] objects back to a normal stream of events. -/// -/// ### Example -/// -/// Stream> -/// .fromIterable([StreamNotification.data(1), StreamNotification.done()]) -/// .transform(DematerializeStreamTransformer()) -/// .listen(print); // Prints 1 -/// -/// ### Error example -/// -/// Stream> -/// .fromIterable([StreamNotification.error(Exception(), null)]) -/// .transform(DematerializeStreamTransformer()) -/// .listen(null, onError: (e, s) => print(e)); // Prints Exception -class DematerializeStreamTransformer - extends StreamTransformerBase, S> { - /// Constructs a [StreamTransformer] which converts the onData, onDone, and - /// onError [StreamNotification] objects from a materialized stream into normal - /// onData, onDone, and onError events. - DematerializeStreamTransformer(); - - @override - Stream bind(Stream> stream) => - Stream.eventTransformed(stream, (sink) => _DematerializeStreamSink(sink)); -} - -/// Converts the onData, onDone, and onError [StreamNotification]s from a -/// materialized stream into normal onData, onDone, and onError events. -extension DematerializeExtension on Stream> { - /// Converts the onData, onDone, and onError [StreamNotification] objects from a - /// materialized stream into normal onData, onDone, and onError events. - /// - /// When a stream has been materialized, it emits onData, onDone, and onError - /// events as [StreamNotification] objects. Dematerialize simply reverses this by - /// transforming [StreamNotification] objects back to a normal stream of events. - /// - /// ### Example - /// - /// Stream> - /// .fromIterable([StreamNotification.data(1), StreamNotification.done()]) - /// .dematerialize() - /// .listen(print); // Prints 1 - /// - /// ### Error example - /// - /// Stream> - /// .fromIterable([StreamNotification.error(Exception(), null)]) - /// .dematerialize() - /// .listen(null, onError: (e, s) => print(e)); // Prints Exception - Stream dematerialize() => DematerializeStreamTransformer().bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/distinct_unique.dart b/sandbox/reactivex/lib/src/transformers/distinct_unique.dart deleted file mode 100644 index f3785df..0000000 --- a/sandbox/reactivex/lib/src/transformers/distinct_unique.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -class _DistinctUniqueStreamSink implements EventSink { - final EventSink _outputSink; - final HashSet _collection; - - _DistinctUniqueStreamSink(this._outputSink, - {bool Function(S e1, S e2)? equals, int Function(S e)? hashCodeMethod}) - : _collection = HashSet(equals: equals, hashCode: hashCodeMethod); - - @override - void add(S data) { - if (_collection.add(data)) { - _outputSink.add(data); - } - } - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() { - _collection.clear(); - _outputSink.close(); - } -} - -/// Create a [Stream] which implements a [HashSet] under the hood, using -/// the provided `equals` as equality. -/// -/// The [Stream] will only emit an event, if that event is not yet found -/// within the underlying [HashSet]. -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 1, 2, 1, 2, 3, 2, 1]) -/// .listen((event) => print(event)); -/// -/// will emit: -/// 1, 2, 3 -/// -/// The provided `equals` must define a stable equivalence relation, and -/// `hashCode` must be consistent with `equals`. -/// -/// If `equals` or `hashCode` are omitted, the set uses the elements' intrinsic -/// `Object.==` and `Object.hashCode`. If you supply one of `equals` and -/// `hashCode`, you should generally also to supply the other. -class DistinctUniqueStreamTransformer extends StreamTransformerBase { - /// Optional method which determines equality between two events - final bool Function(S e1, S e2)? equals; - - /// Optional method which is used to create a hash from an event - final int Function(S e)? hashCodeMethod; - - /// Constructs a [StreamTransformer] which emits events from the source - /// [Stream] as if they were processed through a [HashSet]. - /// - /// See [HashSet] for a more detailed explanation. - DistinctUniqueStreamTransformer({this.equals, this.hashCodeMethod}); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, - (sink) => _DistinctUniqueStreamSink(sink, - equals: equals, hashCodeMethod: hashCodeMethod)); -} - -/// Extends the Stream class with the ability to skip items that have previously -/// been emitted. -extension DistinctUniqueExtension on Stream { - /// WARNING: More commonly known as distinct in other Rx implementations. - /// Creates a Stream where data events are skipped if they have already - /// been emitted before. - /// - /// Equality is determined by the provided equals and hashCode methods. - /// If these are omitted, the '==' operator and hashCode on the last provided - /// data element are used. - /// - /// The returned stream is a broadcast stream if this stream is. If a - /// broadcast stream is listened to more than once, each subscription will - /// individually perform the equals and hashCode tests. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#distinct) - Stream distinctUnique({ - bool Function(T e1, T e2)? equals, - int Function(T e)? hashCode, - }) => - DistinctUniqueStreamTransformer( - equals: equals, hashCodeMethod: hashCode) - .bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/do.dart b/sandbox/reactivex/lib/src/transformers/do.dart deleted file mode 100644 index 637505e..0000000 --- a/sandbox/reactivex/lib/src/transformers/do.dart +++ /dev/null @@ -1,305 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; -import 'package:angel3_reactivex/src/utils/notification.dart'; - -class _DoStreamSink extends ForwardingSink { - final FutureOr Function()? _onCancel; - final void Function(S event)? _onData; - final void Function()? _onDone; - final void Function(StreamNotification notification)? _onEach; - final void Function(Object, StackTrace)? _onError; - final void Function()? _onListen; - final void Function()? _onPause; - final void Function()? _onResume; - - _DoStreamSink( - this._onCancel, - this._onData, - this._onDone, - this._onEach, - this._onError, - this._onListen, - this._onPause, - this._onResume, - ); - - @override - void onData(S data) { - try { - _onData?.call(data); - } catch (e, s) { - sink.addError(e, s); - } - try { - _onEach?.call(StreamNotification.data(data)); - } catch (e, s) { - sink.addError(e, s); - } - sink.add(data); - } - - @override - void onError(Object e, StackTrace st) { - try { - _onError?.call(e, st); - } catch (e, s) { - sink.addError(e, s); - } - try { - _onEach?.call(StreamNotification.error(e, st)); - } catch (e, s) { - sink.addError(e, s); - } - sink.addError(e, st); - } - - @override - void onDone() { - try { - _onDone?.call(); - } catch (e, s) { - sink.addError(e, s); - } - try { - _onEach?.call(StreamNotification.done()); - } catch (e, s) { - sink.addError(e, s); - } - sink.close(); - } - - @override - FutureOr onCancel() => _onCancel?.call(); - - @override - void onListen() { - try { - _onListen?.call(); - } catch (e, s) { - sink.addError(e, s); - } - } - - @override - void onPause() { - try { - _onPause?.call(); - } catch (e, s) { - sink.addError(e, s); - } - } - - @override - void onResume() { - try { - _onResume?.call(); - } catch (e, s) { - sink.addError(e, s); - } - } -} - -/// Invokes the given callback at the corresponding point the the stream -/// lifecycle. For example, if you pass in an onDone callback, it will -/// be invoked when the stream finishes emitting items. -/// -/// This transformer can be used for debugging, logging, etc. by intercepting -/// the stream at different points to run arbitrary actions. -/// -/// It is possible to hook onto the following parts of the stream lifecycle: -/// -/// - onCancel -/// - onData -/// - onDone -/// - onError -/// - onListen -/// - onPause -/// - onResume -/// -/// In addition, the `onEach` argument is called at `onData`, `onDone`, and -/// `onError` with a [StreamNotification] passed in. The [StreamNotification] argument -/// contains the [NotificationKind] of event (OnData, OnDone, OnError), and the item or -/// error that was emitted. In the case of onDone, no data is emitted as part -/// of the [StreamNotification]. -/// -/// If no callbacks are passed in, a runtime error will be thrown in dev mode -/// in order to 'fail fast' and alert the developer that the transformer should -/// be used or safely removed. -/// -/// ### Example -/// -/// Stream.fromIterable([1]) -/// .transform(DoStreamTransformer( -/// onData: print, -/// onError: (e, s) => print('Oh no!'), -/// onDone: () => print('Done'))) -/// .listen(null); // Prints: 1, 'Done' -class DoStreamTransformer extends StreamTransformerBase { - /// fires when all subscriptions have cancelled. - final FutureOr Function()? onCancel; - - /// fires when data is emitted - final void Function(S event)? onData; - - /// fires on close - final void Function()? onDone; - - /// fires on data, close and error - final void Function(StreamNotification notification)? onEach; - - /// fires on errors - final void Function(Object, StackTrace)? onError; - - /// fires when a subscription first starts - final void Function()? onListen; - - /// fires when the subscription pauses - final void Function()? onPause; - - /// fires when the subscription resumes - final void Function()? onResume; - - /// Constructs a [StreamTransformer] which will trigger any of the provided - /// handlers as they occur. - DoStreamTransformer( - {this.onCancel, - this.onData, - this.onDone, - this.onEach, - this.onError, - this.onListen, - this.onPause, - this.onResume}) { - if (onCancel == null && - onData == null && - onDone == null && - onEach == null && - onError == null && - onListen == null && - onPause == null && - onResume == null) { - throw ArgumentError('Must provide at least one handler'); - } - } - - @override - Stream bind(Stream stream) => forwardStream( - stream, - () => _DoStreamSink( - onCancel, - onData, - onDone, - onEach, - onError, - onListen, - onPause, - onResume, - ), - true, - ); -} - -/// Extends the Stream class with the ability to execute a callback function -/// at different points in the Stream's lifecycle. -extension DoExtensions on Stream { - /// Invokes the given callback function when the stream subscription is - /// cancelled. Often called doOnUnsubscribe or doOnDispose in other - /// implementations. - /// - /// ### Example - /// - /// final subscription = TimerStream(1, Duration(minutes: 1)) - /// .doOnCancel(() => print('hi')) - /// .listen(null); - /// - /// subscription.cancel(); // prints 'hi' - Stream doOnCancel(FutureOr Function() onCancel) => - DoStreamTransformer(onCancel: onCancel).bind(this); - - /// Invokes the given callback function when the stream emits an item. In - /// other implementations, this is called doOnNext. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3]) - /// .doOnData(print) - /// .listen(null); // prints 1, 2, 3 - Stream doOnData(void Function(T event) onData) => - DoStreamTransformer(onData: onData).bind(this); - - /// Invokes the given callback function when the stream finishes emitting - /// items. In other implementations, this is called doOnComplete(d). - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3]) - /// .doOnDone(() => print('all set')) - /// .listen(null); // prints 'all set' - Stream doOnDone(void Function() onDone) => - DoStreamTransformer(onDone: onDone).bind(this); - - /// Invokes the given callback function when the stream emits data, emits - /// an error, or emits done. The callback receives a [StreamNotification] object. - /// - /// The [StreamNotification] object contains the [NotificationKind] of event (OnData, onDone, - /// or OnError), and the item or error that was emitted. In the case of - /// onDone, no data is emitted as part of the [StreamNotification]. - /// - /// ### Example - /// - /// Stream.fromIterable([1]) - /// .doOnEach(print) - /// .listen(null); // Prints DataNotification{value: 1}, DoneNotification{} - Stream doOnEach( - void Function(StreamNotification notification) onEach) => - DoStreamTransformer(onEach: onEach).bind(this); - - /// Invokes the given callback function when the stream emits an error. - /// - /// ### Example - /// - /// Stream.error(Exception()) - /// .doOnError((error, stacktrace) => print('oh no')) - /// .listen(null); // prints 'Oh no' - Stream doOnError(void Function(Object, StackTrace) onError) => - DoStreamTransformer(onError: onError).bind(this); - - /// Invokes the given callback function when the stream is first listened to. - /// - /// ### Example - /// - /// Stream.fromIterable([1]) - /// .doOnListen(() => print('Is someone there?')) - /// .listen(null); // prints 'Is someone there?' - Stream doOnListen(void Function() onListen) => - DoStreamTransformer(onListen: onListen).bind(this); - - /// Invokes the given callback function when the stream subscription is - /// paused. - /// - /// ### Example - /// - /// final subscription = Stream.fromIterable([1]) - /// .doOnPause(() => print('Gimme a minute please')) - /// .listen(null); - /// - /// subscription.pause(); // prints 'Gimme a minute please' - Stream doOnPause(void Function() onPause) => - DoStreamTransformer(onPause: onPause).bind(this); - - /// Invokes the given callback function when the stream subscription - /// resumes receiving items. - /// - /// ### Example - /// - /// final subscription = Stream.fromIterable([1]) - /// .doOnResume(() => print('Let's do this!')) - /// .listen(null); - /// - /// subscription.pause(); - /// subscription.resume(); 'Let's do this!' - Stream doOnResume(void Function() onResume) => - DoStreamTransformer(onResume: onResume).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/end_with.dart b/sandbox/reactivex/lib/src/transformers/end_with.dart deleted file mode 100644 index c4f99ee..0000000 --- a/sandbox/reactivex/lib/src/transformers/end_with.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'dart:async'; - -class _EndWithStreamSink implements EventSink { - final S _endValue; - final EventSink _outputSink; - - _EndWithStreamSink(this._outputSink, this._endValue); - - @override - void add(S data) => _outputSink.add(data); - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() { - _outputSink.add(_endValue); - _outputSink.close(); - } -} - -/// Appends a value to the source [Stream] before closing. -/// -/// ### Example -/// -/// Stream.fromIterable([2]) -/// .transform(EndWithStreamTransformer(1)) -/// .listen(print); // prints 2, 1 -class EndWithStreamTransformer extends StreamTransformerBase { - /// The ending event of this [Stream] - final S endValue; - - /// Constructs a [StreamTransformer] which appends the source [Stream] - /// with [endValue] just before it closes. - EndWithStreamTransformer(this.endValue); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _EndWithStreamSink(sink, endValue)); -} - -/// Extends the [Stream] class with the ability to emit the given value as the -/// final item before closing. -extension EndWithExtension on Stream { - /// Appends a value to the source [Stream] before closing. - /// - /// ### Example - /// - /// Stream.fromIterable([2]).endWith(1).listen(print); // prints 2, 1 - Stream endWith(T endValue) => - EndWithStreamTransformer(endValue).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/end_with_many.dart b/sandbox/reactivex/lib/src/transformers/end_with_many.dart deleted file mode 100644 index 050e456..0000000 --- a/sandbox/reactivex/lib/src/transformers/end_with_many.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'dart:async'; - -class _EndWithManyStreamSink implements EventSink { - final Iterable _endValues; - final EventSink _outputSink; - - _EndWithManyStreamSink(this._outputSink, this._endValues); - - @override - void add(S data) => _outputSink.add(data); - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() { - _endValues.forEach(_outputSink.add); - _outputSink.close(); - } -} - -/// Appends a sequence of values to the source [Stream]. -/// -/// ### Example -/// -/// Stream.fromIterable([3]) -/// .transform(EndWithManyStreamTransformer([1, 2])) -/// .listen(print); // prints 3, 1, 2 -class EndWithManyStreamTransformer extends StreamTransformerBase { - /// The ending events of this [Stream] - final Iterable endValues; - - /// Constructs a [StreamTransformer] which appends the source [Stream] - /// with [endValues] before closing. - EndWithManyStreamTransformer(this.endValues); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _EndWithManyStreamSink(sink, endValues)); -} - -/// Extends the Stream class with the ability to emit the given value as the -/// final item before closing. -extension EndWithManyExtension on Stream { - /// Appends a sequence of values as final events to the source [Stream] before closing. - /// - /// ### Example - /// - /// Stream.fromIterable([2]).endWithMany([1, 0]).listen(print); // prints 2, 1, 0 - Stream endWithMany(Iterable endValues) => - EndWithManyStreamTransformer(endValues).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/exhaust_map.dart b/sandbox/reactivex/lib/src/transformers/exhaust_map.dart deleted file mode 100644 index d9c9bac..0000000 --- a/sandbox/reactivex/lib/src/transformers/exhaust_map.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _ExhaustMapStreamSink extends ForwardingSink { - final Stream Function(S value) _mapper; - StreamSubscription? _mapperSubscription; - bool _inputClosed = false; - - _ExhaustMapStreamSink(this._mapper); - - @override - void onData(S data) { - if (_mapperSubscription != null) { - return; - } - - final Stream mappedStream; - try { - mappedStream = _mapper(data); - } catch (e, s) { - sink.addError(e, s); - return; - } - - _mapperSubscription = mappedStream.listen( - sink.add, - onError: sink.addError, - onDone: () { - _mapperSubscription = null; - - if (_inputClosed) { - sink.close(); - } - }, - ); - } - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() { - _inputClosed = true; - - _mapperSubscription ?? sink.close(); - } - - @override - FutureOr onCancel() => _mapperSubscription?.cancel(); - - @override - void onListen() {} - - @override - void onPause() => _mapperSubscription?.pause(); - - @override - void onResume() => _mapperSubscription?.resume(); -} - -/// Converts events from the source stream into a new Stream using a given -/// mapper. It ignores all items from the source stream until the new stream -/// completes. -/// -/// Useful when you have a noisy source Stream and only want to respond once -/// the previous async operation is finished. -/// -/// ### Example -/// // Emits 0, 1, 2 -/// Stream.periodic(Duration(milliseconds: 200), (i) => i).take(3) -/// .transform(ExhaustMapStreamTransformer( -/// // Emits the value it's given after 200ms -/// (i) => Rx.timer(i, Duration(milliseconds: 200)), -/// )) -/// .listen(print); // prints 0, 2 -class ExhaustMapStreamTransformer extends StreamTransformerBase { - /// Method which converts incoming events into a new [Stream] - final Stream Function(S value) mapper; - - /// Constructs a [StreamTransformer] which maps each event from the source [Stream] - /// using [mapper]. - /// - /// The mapped [Stream] will be be listened to and begin - /// emitting items, and any previously created mapper [Stream]s will stop emitting. - ExhaustMapStreamTransformer(this.mapper); - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _ExhaustMapStreamSink(mapper)); -} - -/// Extends the Stream class with the ability to transform the Stream into -/// a new Stream. The new Stream emits items and ignores events from the source -/// Stream until the new Stream completes. -extension ExhaustMapExtension on Stream { - /// Converts items from the source stream into a Stream using a given - /// mapper. It ignores all items from the source stream until the new stream - /// completes. - /// - /// Useful when you have a noisy source Stream and only want to respond once - /// the previous async operation is finished. - /// - /// ### Example - /// - /// RangeStream(0, 2).interval(Duration(milliseconds: 50)) - /// .exhaustMap((i) => - /// TimerStream(i, Duration(milliseconds: 75))) - /// .listen(print); // prints 0, 2 - Stream exhaustMap(Stream Function(T value) mapper) => - ExhaustMapStreamTransformer(mapper).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/flat_map.dart b/sandbox/reactivex/lib/src/transformers/flat_map.dart deleted file mode 100644 index c5dff2a..0000000 --- a/sandbox/reactivex/lib/src/transformers/flat_map.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -class _FlatMapStreamSink extends ForwardingSink { - final Stream Function(S value) _mapper; - final int? maxConcurrent; - - final List> _subscriptions = >[]; - final Queue queue = DoubleLinkedQueue(); - bool _inputClosed = false; - - _FlatMapStreamSink(this._mapper, this.maxConcurrent); - - @override - void onData(S data) { - if (maxConcurrent != null && _subscriptions.length >= maxConcurrent!) { - queue.addLast(data); - } else { - listenInner(data); - } - } - - void listenInner(S data) { - final Stream mappedStream; - try { - mappedStream = _mapper(data); - } catch (e, s) { - sink.addError(e, s); - return; - } - - final subscription = mappedStream.listen(sink.add, onError: sink.addError); - subscription.onDone(() { - _subscriptions.remove(subscription); - - if (queue.isNotEmpty) { - listenInner(queue.removeFirst()); - } else if (_inputClosed && _subscriptions.isEmpty) { - sink.close(); - } - }); - _subscriptions.add(subscription); - } - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() { - _inputClosed = true; - - if (_subscriptions.isEmpty) { - sink.close(); - } - } - - @override - Future? onCancel() { - queue.clear(); - return _subscriptions.cancelAll(); - } - - @override - void onListen() {} - - @override - void onPause() => _subscriptions.pauseAll(); - - @override - void onResume() => _subscriptions.resumeAll(); -} - -/// Converts each emitted item into a new Stream using the given mapper function, -/// while limiting the maximum number of concurrent subscriptions to these [Stream]s. -/// The newly created Stream will be listened to and begin emitting items downstream. -/// -/// The items emitted by each of the new Streams are emitted downstream in the -/// same order they arrive. In other words, the sequences are merged -/// together. -/// -/// ### Example -/// -/// Stream.fromIterable([4, 3, 2, 1]) -/// .transform(FlatMapStreamTransformer((i) => -/// Stream.fromFuture( -/// Future.delayed(Duration(minutes: i), () => i)) -/// .listen(print); // prints 1, 2, 3, 4 -class FlatMapStreamTransformer extends StreamTransformerBase { - /// Method which converts incoming events into a new [Stream] - final Stream Function(S value) mapper; - - /// Maximum number of inner [Stream] that may be listened to concurrently. - /// If it's `null`, it means unlimited. - final int? maxConcurrent; - - /// Constructs a [StreamTransformer] which emits events from the source [Stream] using the given [mapper]. - /// The mapped [Stream] will be listened to and begin emitting items downstream. - FlatMapStreamTransformer(this.mapper, {this.maxConcurrent}); - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _FlatMapStreamSink(mapper, maxConcurrent)); -} - -/// Extends the Stream class with the ability to convert the source Stream into -/// a new Stream each time the source emits an item. -extension FlatMapExtension on Stream { - /// Converts each emitted item into a Stream using the given mapper function, - /// while limiting the maximum number of concurrent subscriptions to these [Stream]s. - /// The newly created Stream will be be listened to and begin emitting items downstream. - /// - /// The items emitted by each of the Streams are emitted downstream in the - /// same order they arrive. In other words, the sequences are merged - /// together. - /// - /// ### Example - /// - /// RangeStream(4, 1) - /// .flatMap((i) => TimerStream(i, Duration(minutes: i))) - /// .listen(print); // prints 1, 2, 3, 4 - Stream flatMap(Stream Function(T value) mapper, - {int? maxConcurrent}) => - FlatMapStreamTransformer(mapper, maxConcurrent: maxConcurrent) - .bind(this); - - /// Converts each item into a Stream. The Stream must return an - /// Iterable. Then, each item from the Iterable will be emitted one by one. - /// - /// Use case: you may have an API that returns a list of items, such as - /// a Stream>. However, you might want to operate on the individual items - /// rather than the list itself. This is the job of `flatMapIterable`. - /// - /// ### Example - /// - /// RangeStream(1, 4) - /// .flatMapIterable((i) => Stream.fromIterable([[i]])) - /// .listen(print); // prints 1, 2, 3, 4 - Stream flatMapIterable(Stream> Function(T value) mapper, - {int? maxConcurrent}) => - FlatMapStreamTransformer>(mapper, - maxConcurrent: maxConcurrent) - .bind(this) - .expand((Iterable iterable) => iterable); -} diff --git a/sandbox/reactivex/lib/src/transformers/group_by.dart b/sandbox/reactivex/lib/src/transformers/group_by.dart deleted file mode 100644 index e2d2edb..0000000 --- a/sandbox/reactivex/lib/src/transformers/group_by.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; -import 'package:angel3_reactivex/src/utils/future.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -class _GroupByStreamSink extends ForwardingSink> { - final K Function(T event) grouper; - final Stream Function(GroupedStream)? duration; - - final groups = >{}; - Map>? subscriptions; - - _GroupByStreamSink(this.grouper, this.duration); - - void _closeAll() { - for (var c in groups.values) { - c.close(); - } - groups.clear(); - } - - StreamController _controllerBuilder(K key) { - final groupedController = StreamController.broadcast(sync: true); - final groupByStream = GroupedStream(key, groupedController.stream); - - if (duration != null) { - subscriptions?.remove(key)?.cancel(); - (subscriptions ??= {})[key] = duration!(groupByStream).take(1).listen( - null, - onDone: () { - subscriptions!.remove(key); - groups.remove(key)?.close(); - }, - onError: onError, - ); - } - - sink.add(groupByStream); - return groupedController; - } - - @override - void onData(T data) { - final K key; - try { - key = grouper(data); - } catch (e, s) { - sink.addError(e, s); - return; - } - - groups.putIfAbsent(key, () => _controllerBuilder(key)).add(data); - } - - @override - void onError(e, st) => sink.addError(e, st); - - @override - void onDone() { - _closeAll(); - sink.close(); - } - - @override - Future? onCancel() { - scheduleMicrotask(_closeAll); - - if (subscriptions?.isNotEmpty == true) { - final future = waitFuturesList([ - for (final s in subscriptions!.values) s.cancel(), - ]); - subscriptions?.clear(); - subscriptions = null; - return future; - } - return null; - } - - @override - FutureOr onListen() {} - - @override - void onPause() => subscriptions?.values.pauseAll(); - - @override - void onResume() => subscriptions?.values.resumeAll(); -} - -/// The GroupBy operator divides a [Stream] that emits items into -/// a [Stream] that emits [GroupedStream], -/// each one of which emits some subset of the items -/// from the original source [Stream]. -/// -/// [GroupedStream] acts like a regular [Stream], yet -/// adding a 'key' property, which receives its [Type] and value from -/// the [_grouper] Function. -/// -/// All items with the same key are emitted by the same [GroupedStream]. -class GroupByStreamTransformer - extends StreamTransformerBase> { - /// Method which converts incoming events into a new [GroupedStream] - final K Function(T event) grouper; - - /// A function that returns an [Stream] to determine how long each group should exist. - /// When the returned [Stream] emits its first data or done event, - /// the group will be closed and removed. - final Stream Function(GroupedStream grouped)? durationSelector; - - /// Constructs a [StreamTransformer] which groups events from the source - /// [Stream] and emits them as [GroupedStream]. - GroupByStreamTransformer(this.grouper, {this.durationSelector}); - - @override - Stream> bind(Stream stream) => forwardStream( - stream, () => _GroupByStreamSink(grouper, durationSelector)); -} - -/// The [Stream] used by [GroupByStreamTransformer], it contains events -/// that are grouped by a key value. -class GroupedStream extends StreamView { - /// The key is the category to which all events in this group belong to. - final K key; - - /// Constructs a [Stream] which only emits events that can be - /// categorized under [key]. - GroupedStream(this.key, Stream stream) : super(stream); - - @override - String toString() => 'GroupedStream{key: $key}'; -} - -/// Extends the Stream class with the ability to convert events into Streams -/// of events that are united by a key. -extension GroupByExtension on Stream { - /// The GroupBy operator divides a [Stream] that emits items into a [Stream] - /// that emits [GroupedStream], each one of which emits some subset of the - /// items from the original source [Stream]. - /// - /// [GroupedStream] acts like a regular [Stream], yet adding a 'key' property, - /// which receives its [Type] and value from the [grouper] Function. - /// - /// All items with the same key are emitted by the same [GroupedStream]. - /// - /// Optionally, `groupBy` takes a second argument [durationSelector]. - /// [durationSelector] is a function that returns an [Stream] to determine how long - /// each group should exist. When the returned [Stream] emits its first data or done event, - /// the group will be closed and removed. - Stream> groupBy( - K Function(T value) grouper, { - Stream Function(GroupedStream grouped)? durationSelector, - }) => - GroupByStreamTransformer(grouper, - durationSelector: durationSelector) - .bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/ignore_elements.dart b/sandbox/reactivex/lib/src/transformers/ignore_elements.dart deleted file mode 100644 index a2f372b..0000000 --- a/sandbox/reactivex/lib/src/transformers/ignore_elements.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'dart:async'; - -class _IgnoreElementsStreamSink implements EventSink { - final EventSink _outputSink; - - _IgnoreElementsStreamSink(this._outputSink); - - @override - void add(S data) {} - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() => _outputSink.close(); -} - -/// Creates a [Stream] where all emitted items are ignored, only the -/// error / completed notifications are passed -/// -/// [ReactiveX doc](http://reactivex.io/documentation/operators/ignoreelements.html) -/// [Interactive marble diagram](https://rxmarbles.com/#ignoreElements) -/// -/// ### Example -/// -/// MergeStream([ -/// Stream.fromIterable([1]), -/// ErrorStream(Exception()) -/// ]) -/// .transform(IgnoreElementsStreamTransformer()) -/// .listen(print, onError: print); // prints Exception -class IgnoreElementsStreamTransformer - extends StreamTransformerBase { - /// Constructs a [StreamTransformer] which simply ignores all events from - /// the source [Stream], except for error or completed events. - IgnoreElementsStreamTransformer(); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _IgnoreElementsStreamSink(sink)); -} - -/// Extends the Stream class with the ability to skip, or ignore, data events. -extension IgnoreElementsExtension on Stream { - /// Creates a Stream where all emitted items are ignored, only the error / - /// completed notifications are passed - /// - /// [ReactiveX doc](http://reactivex.io/documentation/operators/ignoreelements.html) - /// [Interactive marble diagram](https://rxmarbles.com/#ignoreElements) - /// - /// ### Example - /// - /// MergeStream([ - /// Stream.fromIterable([1]), - /// Stream.error(Exception()) - /// ]) - /// .ignoreElements() - /// .listen(print, onError: print); // prints Exception - Stream ignoreElements() => - IgnoreElementsStreamTransformer().bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/interval.dart b/sandbox/reactivex/lib/src/transformers/interval.dart deleted file mode 100644 index 4366760..0000000 --- a/sandbox/reactivex/lib/src/transformers/interval.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -class _IntervalStreamSink implements EventSink { - final Duration _duration; - final EventSink _outputSink; - final _queue = Queue(); - var _inputClosed = false; - var _openIntervals = 0; - - bool get noOpenIntervals => _openIntervals == 0; - - _IntervalStreamSink(this._outputSink, this._duration); - - @override - void add(S data) { - _queue.add(data); - - if (noOpenIntervals) { - _addNext(); - } - } - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() { - _inputClosed = true; - - if (noOpenIntervals) { - _outputSink.close(); - } - } - - void _addNext() { - if (_queue.isNotEmpty) { - _addDelayed(_queue.removeFirst()).whenComplete(_addNext); - } - } - - Future _addDelayed(S data) { - _openIntervals++; - - return Future.delayed(_duration, () => data) - .then(_outputSink.add) - .whenComplete(() { - _openIntervals--; - - if (_inputClosed && _queue.isEmpty) { - _outputSink.close(); - } - }); - } -} - -/// Creates a Stream that emits each item in the Stream after a given -/// duration. -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3]) -/// .transform(IntervalStreamTransformer(Duration(seconds: 1))) -/// .listen((i) => print('$i sec')); // prints 1 sec, 2 sec, 3 sec -class IntervalStreamTransformer extends StreamTransformerBase { - /// The interval after which incoming events need to be emitted. - final Duration duration; - - /// Constructs a [StreamTransformer] which emits each item from the source [Stream], - /// after a given duration. - IntervalStreamTransformer(this.duration); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _IntervalStreamSink(sink, duration)); -} - -/// Extends the Stream class with the ability to emit each item after a given -/// duration. -extension IntervalExtension on Stream { - /// Creates a Stream that emits each item in the Stream after a given - /// duration. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3]) - /// .interval(Duration(seconds: 1)) - /// .listen((i) => print('$i sec'); // prints 1 sec, 2 sec, 3 sec - Stream interval(Duration duration) => - IntervalStreamTransformer(duration).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/map_not_null.dart b/sandbox/reactivex/lib/src/transformers/map_not_null.dart deleted file mode 100644 index 724d6ba..0000000 --- a/sandbox/reactivex/lib/src/transformers/map_not_null.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'dart:async'; - -class _MapNotNullSink implements EventSink { - final R? Function(T) _transform; - final EventSink _outputSink; - - _MapNotNullSink(this._outputSink, this._transform); - - @override - void add(T event) { - final value = _transform(event); - if (value != null) { - _outputSink.add(value); - } - } - - @override - void addError(Object error, [StackTrace? stackTrace]) => - _outputSink.addError(error, stackTrace); - - @override - void close() => _outputSink.close(); -} - -/// Create a Stream containing only the non-`null` results -/// of applying the given [transform] function to each element of the Stream. -/// -/// ### Example -/// -/// Stream.fromIterable(['1', 'two', '3', 'four']) -/// .transform(MapNotNullStreamTransformer(int.tryParse)) -/// .listen(print); // prints 1, 3 -/// -/// // equivalent to: -/// -/// Stream.fromIterable(['1', 'two', '3', 'four']) -/// .map(int.tryParse) -/// .transform(WhereTypeStreamTransformer()) -/// .listen(print); // prints 1, 3 -class MapNotNullStreamTransformer - extends StreamTransformerBase { - /// A function that transforms each elements of the Stream. - final R? Function(T) transform; - - /// Constructs a [StreamTransformer] which emits non-`null` elements - /// of applying the given [transform] function to each element of the Stream. - const MapNotNullStreamTransformer(this.transform); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _MapNotNullSink(sink, transform)); -} - -/// Extends the Stream class with the ability to convert the source Stream -/// to a Stream containing only the non-`null` results -/// of applying the given [transform] function to each element of this Stream. -extension MapNotNullExtension on Stream { - /// Returns a Stream containing only the non-`null` results - /// of applying the given [transform] function to each element of this Stream. - /// - /// ### Example - /// - /// Stream.fromIterable(['1', 'two', '3', 'four']) - /// .mapNotNull(int.tryParse) - /// .listen(print); // prints 1, 3 - /// - /// // equivalent to: - /// - /// Stream.fromIterable(['1', 'two', '3', 'four']) - /// .map(int.tryParse) - /// .whereType() - /// .listen(print); // prints 1, 3 - Stream mapNotNull(R? Function(T) transform) => - MapNotNullStreamTransformer(transform).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/map_to.dart b/sandbox/reactivex/lib/src/transformers/map_to.dart deleted file mode 100644 index d58d74a..0000000 --- a/sandbox/reactivex/lib/src/transformers/map_to.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'dart:async'; - -class _MapToStreamSink implements EventSink { - final T _value; - final EventSink _outputSink; - - _MapToStreamSink(this._outputSink, this._value); - - @override - void add(S data) => _outputSink.add(_value); - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() => _outputSink.close(); -} - -/// Emits the given constant value on the output Stream every time the source -/// Stream emits a value. -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3, 4]) -/// .mapTo(true) -/// .listen(print); // prints true, true, true, true -class MapToStreamTransformer extends StreamTransformerBase { - /// A constant [value] which will always be returned when using this transformer. - final T value; - - /// Constructs a [StreamTransformer] which always maps every event from - /// the source [Stream] to a constant [value]. - MapToStreamTransformer(this.value); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _MapToStreamSink(sink, value)); -} - -/// Extends the Stream class with the ability to convert each item to the same -/// value. -extension MapToExtension on Stream { - /// Emits the given constant value on the output Stream every time the source - /// Stream emits a value. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3, 4]) - /// .mapTo(true) - /// .listen(print); // prints true, true, true, true - Stream mapTo(T value) => MapToStreamTransformer(value).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/materialize.dart b/sandbox/reactivex/lib/src/transformers/materialize.dart deleted file mode 100644 index e0f3d56..0000000 --- a/sandbox/reactivex/lib/src/transformers/materialize.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/notification.dart'; - -class _MaterializeStreamSink implements EventSink { - final EventSink> _outputSink; - - _MaterializeStreamSink(this._outputSink); - - @override - void add(S data) => _outputSink.add(StreamNotification.data(data)); - - @override - void addError(e, [st]) => _outputSink.add(StreamNotification.error(e, st)); - - @override - void close() { - _outputSink.add(StreamNotification.done()); - _outputSink.close(); - } -} - -/// Converts the onData, on Done, and onError events into [StreamNotification] -/// objects that are passed into the downstream onData listener. -/// -/// The [StreamNotification] object contains the [NotificationKind] of event (OnData, onDone, or -/// OnError), and the item or error that was emitted. In the case of onDone, -/// no data is emitted as part of the [StreamNotification]. -/// -/// ### Example -/// -/// Stream.fromIterable([1]) -/// .transform(MaterializeStreamTransformer()) -/// .listen(print); // Prints DataNotification{value: 1}, DoneNotification{} -class MaterializeStreamTransformer - extends StreamTransformerBase> { - /// Constructs a [StreamTransformer] which transforms the onData, on Done, - /// and onError events into [StreamNotification] objects. - MaterializeStreamTransformer(); - - @override - Stream> bind(Stream stream) => - Stream.eventTransformed( - stream, (sink) => _MaterializeStreamSink(sink)); -} - -/// Extends the Stream class with the ability to convert the onData, on Done, -/// and onError events into [StreamNotification]s that are passed into the -/// downstream onData listener. -extension MaterializeExtension on Stream { - /// Converts the onData, on Done, and onError events into [StreamNotification] - /// objects that are passed into the downstream onData listener. - /// - /// The [StreamNotification] object contains the [NotificationKind] of event (OnData, onDone, or - /// OnError), and the item or error that was emitted. In the case of onDone, - /// no data is emitted as part of the [StreamNotification]. - /// - /// Example: - /// Stream.fromIterable([1]) - /// .materialize() - /// .listen(print); // Prints DataNotification{value: 1}, DoneNotification{} - /// - /// Stream.error(Exception()) - /// .materialize() - /// .listen(print); // Prints ErrorNotification{error: Exception, stackTrace: }, DoneNotification{} - Stream> materialize() => - MaterializeStreamTransformer().bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/max.dart b/sandbox/reactivex/lib/src/transformers/max.dart deleted file mode 100644 index ff66ae6..0000000 --- a/sandbox/reactivex/lib/src/transformers/max.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:angel3_reactivex/src/utils/min_max.dart'; - -/// Extends the Stream class with the ability to transform into a Future -/// that completes with the largest item emitted by the Stream. -extension MaxExtension on Stream { - /// Converts a Stream into a Future that completes with the largest item - /// emitted by the Stream. - /// - /// This is similar to finding the max value in a list, but the values are - /// asynchronous. - /// - /// ### Example - /// - /// final max = await Stream.fromIterable([1, 2, 3]).max(); - /// - /// print(max); // prints 3 - /// - /// ### Example with custom [Comparator] - /// - /// final stream = Stream.fromIterable(['short', 'looooooong']); - /// final max = await stream.max((a, b) => a.length - b.length); - /// - /// print(max); // prints 'looooooong' - Future max([Comparator? comparator]) => minMax(this, false, comparator); -} diff --git a/sandbox/reactivex/lib/src/transformers/min.dart b/sandbox/reactivex/lib/src/transformers/min.dart deleted file mode 100644 index 41caec1..0000000 --- a/sandbox/reactivex/lib/src/transformers/min.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/min_max.dart'; - -/// Extends the Stream class with the ability to transform into a Future -/// that completes with the smallest item emitted by the Stream. -extension MinExtension on Stream { - /// Converts a Stream into a Future that completes with the smallest item - /// emitted by the Stream. - /// - /// This is similar to finding the min value in a list, but the values are - /// asynchronous! - /// - /// ### Example - /// - /// final min = await Stream.fromIterable([1, 2, 3]).min(); - /// - /// print(min); // prints 1 - /// - /// ### Example with custom [Comparator] - /// - /// final stream = Stream.fromIterable(['short', 'looooooong']); - /// final min = await stream.min((a, b) => a.length - b.length); - /// - /// print(min); // prints 'short' - Future min([Comparator? comparator]) => minMax(this, true, comparator); -} diff --git a/sandbox/reactivex/lib/src/transformers/on_error_resume.dart b/sandbox/reactivex/lib/src/transformers/on_error_resume.dart deleted file mode 100644 index ad574b0..0000000 --- a/sandbox/reactivex/lib/src/transformers/on_error_resume.dart +++ /dev/null @@ -1,181 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -class _OnErrorResumeStreamSink extends ForwardingSink { - final Stream Function(Object error, StackTrace stackTrace) _recoveryFn; - final List> _recoverySubscriptions = []; - var closed = false; - - _OnErrorResumeStreamSink(this._recoveryFn); - - @override - void onData(S data) => sink.add(data); - - @override - void onError(Object e, StackTrace st) { - final Stream recoveryStream; - - try { - recoveryStream = _recoveryFn(e, st); - } catch (newError, newSt) { - sink.addError(newError, newSt); - return; - } - - final subscription = - recoveryStream.listen(sink.add, onError: sink.addError); - subscription.onDone(() { - _recoverySubscriptions.remove(subscription); - if (closed && _recoverySubscriptions.isEmpty) { - sink.close(); - } - }); - _recoverySubscriptions.add(subscription); - } - - @override - void onDone() { - closed = true; - if (_recoverySubscriptions.isEmpty) { - sink.close(); - } - } - - @override - Future? onCancel() => _recoverySubscriptions.cancelAll(); - - @override - void onListen() {} - - @override - void onPause() => _recoverySubscriptions.pauseAll(); - - @override - void onResume() => _recoverySubscriptions.resumeAll(); -} - -/// Intercepts error events and switches to a recovery stream created by the -/// provided recoveryFn Function. -/// -/// The OnErrorResumeStreamTransformer intercepts an onError notification from -/// the source Stream. Instead of passing the error through to any -/// listeners, it replaces it with another Stream of items created by the -/// recoveryFn. -/// -/// The recoveryFn receives the emitted error and returns a Stream. You can -/// perform logic in the recoveryFn to return different Streams based on the -/// type of error that was emitted. -/// -/// ### Example -/// -/// Stream.error(Exception()) -/// .onErrorResume((dynamic e) => -/// Stream.value(e is StateError ? 1 : 0) -/// .listen(print); // prints 0 -class OnErrorResumeStreamTransformer extends StreamTransformerBase { - /// Method which returns a [Stream], based from the error. - final Stream Function(Object error, StackTrace stackTrace) recoveryFn; - - /// Constructs a [StreamTransformer] which intercepts error events and - /// switches to a recovery [Stream] created by the provided [recoveryFn] Function. - OnErrorResumeStreamTransformer(this.recoveryFn); - - @override - Stream bind(Stream stream) => forwardStream( - stream, - () => _OnErrorResumeStreamSink(recoveryFn), - ); -} - -/// Extends the Stream class with the ability to recover from errors in various -/// ways -extension OnErrorExtensions on Stream { - /// Intercepts error events and switches to the given recovery stream in - /// that case - /// - /// The onErrorResumeNext operator intercepts an onError notification from - /// the source Stream. Instead of passing the error through to any - /// listeners, it replaces it with another Stream of items. - /// - /// If you need to perform logic based on the type of error that was emitted, - /// please consider using [onErrorResume]. - /// - /// ### Example - /// - /// ErrorStream(Exception()) - /// .onErrorResumeNext(Stream.fromIterable([1, 2, 3])) - /// .listen(print); // prints 1, 2, 3 - Stream onErrorResumeNext(Stream recoveryStream) => - OnErrorResumeStreamTransformer((_, __) => recoveryStream).bind(this); - - /// Intercepts error events and switches to a recovery stream created by the - /// provided [recoveryFn]. - /// - /// The onErrorResume operator intercepts an onError notification from - /// the source Stream. Instead of passing the error through to any - /// listeners, it replaces it with another Stream of items created by the - /// [recoveryFn]. - /// - /// The [recoveryFn] receives the emitted error and returns a Stream. You can - /// perform logic in the [recoveryFn] to return different Streams based on the - /// type of error that was emitted. - /// - /// If you do not need to perform logic based on the type of error that was - /// emitted, please consider using [onErrorResumeNext] or [onErrorReturn]. - /// - /// ### Example - /// - /// ErrorStream(Exception()) - /// .onErrorResume((e, st) => - /// Stream.fromIterable([e is StateError ? 1 : 0])) - /// .listen(print); // prints 0 - Stream onErrorResume( - Stream Function(Object error, StackTrace stackTrace) recoveryFn) => - OnErrorResumeStreamTransformer(recoveryFn).bind(this); - - /// Instructs a Stream to emit a particular item when it encounters an - /// error, and then terminate normally - /// - /// The onErrorReturn operator intercepts an onError notification from - /// the source Stream. Instead of passing it through to any observers, it - /// replaces it with a given item, and then terminates normally. - /// - /// If you need to perform logic based on the type of error that was emitted, - /// please consider using [onErrorReturnWith]. - /// - /// ### Example - /// - /// ErrorStream(Exception()) - /// .onErrorReturn(1) - /// .listen(print); // prints 1 - Stream onErrorReturn(T returnValue) => - OnErrorResumeStreamTransformer((_, __) => Stream.value(returnValue)) - .bind(this); - - /// Instructs a Stream to emit a particular item created by the - /// [returnFn] when it encounters an error, and then terminate normally. - /// - /// The onErrorReturnWith operator intercepts an onError notification from - /// the source Stream. Instead of passing it through to any observers, it - /// replaces it with a given item, and then terminates normally. - /// - /// The [returnFn] receives the emitted error and returns a value. You can - /// perform logic in the [returnFn] to return different value based on the - /// type of error that was emitted. - /// - /// If you do not need to perform logic based on the type of error that was - /// emitted, please consider using [onErrorReturn]. - /// - /// ### Example - /// - /// ErrorStream(Exception()) - /// .onErrorReturnWith((e, st) => e is Exception ? 1 : 0) - /// .listen(print); // prints 1 - Stream onErrorReturnWith( - T Function(Object error, StackTrace stackTrace) returnFn) => - OnErrorResumeStreamTransformer( - (e, st) => Stream.value(returnFn(e, st))).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/scan.dart b/sandbox/reactivex/lib/src/transformers/scan.dart deleted file mode 100644 index a2d4e71..0000000 --- a/sandbox/reactivex/lib/src/transformers/scan.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:async'; - -class _ScanStreamSink implements EventSink { - final T Function(T accumulated, S value, int index) _accumulator; - final EventSink _outputSink; - T _acc; - var _index = 0; - - _ScanStreamSink(this._outputSink, this._accumulator, this._acc); - - @override - void add(S data) => - _outputSink.add(_acc = _accumulator(_acc, data, _index++)); - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() => _outputSink.close(); -} - -/// Applies an accumulator function over an stream sequence and returns -/// each intermediate result. The seed value is used as the initial -/// accumulator value. -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3]) -/// .transform(ScanStreamTransformer((acc, curr, i) => acc + curr, 0)) -/// .listen(print); // prints 1, 3, 6 -class ScanStreamTransformer extends StreamTransformerBase { - /// Method which accumulates incoming event into a single, accumulated object - final T Function(T accumulated, S value, int index) accumulator; - - /// The initial value for the accumulated value in the [accumulator] - final T seed; - - /// Constructs a [ScanStreamTransformer] which applies an accumulator Function - /// over the source [Stream] and returns each intermediate result. - /// The seed value is used as the initial accumulator value. - ScanStreamTransformer(this.accumulator, this.seed); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _ScanStreamSink(sink, accumulator, seed)); -} - -/// Extends -extension ScanExtension on Stream { - /// Applies an accumulator function over a Stream sequence and returns each - /// intermediate result. The seed value is used as the initial - /// accumulator value. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3]) - /// .scan((acc, curr, i) => acc + curr, 0) - /// .listen(print); // prints 1, 3, 6 - Stream scan( - S Function(S accumulated, T value, int index) accumulator, S seed) => - ScanStreamTransformer(accumulator, seed).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/skip_last.dart b/sandbox/reactivex/lib/src/transformers/skip_last.dart deleted file mode 100644 index 1bbdfe6..0000000 --- a/sandbox/reactivex/lib/src/transformers/skip_last.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _SkipLastStreamSink extends ForwardingSink { - _SkipLastStreamSink(this.count); - - final int count; - final List queue = []; - - @override - void onData(T data) { - queue.add(data); - } - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() { - final limit = queue.length - count; - if (limit > 0) { - queue.sublist(0, limit).forEach(sink.add); - } - sink.close(); - } - - @override - FutureOr onCancel() { - queue.clear(); - } - - @override - void onListen() {} - - @override - void onPause() {} - - @override - void onResume() {} -} - -/// Skip the last [count] items emitted by the source [Stream] -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3, 4, 5]) -/// .transform(SkipLastStreamTransformer(3)) -/// .listen(print); // prints 1, 2 -class SkipLastStreamTransformer extends StreamTransformerBase { - /// Constructs a [StreamTransformer] which skip the last [count] items - /// emitted by the source [Stream] - SkipLastStreamTransformer(this.count) { - if (count < 0) throw ArgumentError.value(count, 'count'); - } - - /// The [count] of final items to skip. - final int count; - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _SkipLastStreamSink(count)); -} - -/// Extends the Stream class with the ability to skip the last [count] items -/// emitted by the source [Stream] -extension SkipLastExtension on Stream { - /// Starts emitting every items except last [count] items. - /// This causes items to be delayed. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3, 4, 5]) - /// .skipLast(3) - /// .listen(print); // prints 1, 2 - Stream skipLast(int count) => - SkipLastStreamTransformer(count).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/skip_until.dart b/sandbox/reactivex/lib/src/transformers/skip_until.dart deleted file mode 100644 index accf206..0000000 --- a/sandbox/reactivex/lib/src/transformers/skip_until.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _SkipUntilStreamSink extends ForwardingSink { - final Stream _otherStream; - StreamSubscription? _otherSubscription; - var _canAdd = false; - - _SkipUntilStreamSink(this._otherStream); - - @override - void onData(S data) { - if (_canAdd) { - sink.add(data); - } - } - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() { - _otherSubscription?.cancel(); - sink.close(); - } - - @override - FutureOr onCancel() => _otherSubscription?.cancel(); - - @override - void onListen() => _otherSubscription = _otherStream - .take(1) - .listen(null, onError: sink.addError, onDone: () => _canAdd = true); - - @override - void onPause() => _otherSubscription?.pause(); - - @override - void onResume() => _otherSubscription?.resume(); -} - -/// Starts emitting events only after the given stream emits an event. -/// -/// ### Example -/// -/// MergeStream([ -/// Stream.value(1), -/// TimerStream(2, Duration(minutes: 2)) -/// ]) -/// .transform(SkipUntilStreamTransformer(TimerStream(1, Duration(minutes: 1)))) -/// .listen(print); // prints 2; -class SkipUntilStreamTransformer extends StreamTransformerBase { - /// The [Stream] which is required to emit first, before this [Stream] starts emitting - final Stream otherStream; - - /// Constructs a [StreamTransformer] which starts emitting events - /// only after [otherStream] emits an event. - SkipUntilStreamTransformer(this.otherStream); - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _SkipUntilStreamSink(otherStream)); -} - -/// Extends the Stream class with the ability to skip events until another -/// Stream emits an item. -extension SkipUntilExtension on Stream { - /// Starts emitting items only after the given stream emits an item. - /// - /// ### Example - /// - /// MergeStream([ - /// Stream.fromIterable([1]), - /// TimerStream(2, Duration(minutes: 2)) - /// ]) - /// .skipUntil(TimerStream(true, Duration(minutes: 1))) - /// .listen(print); // prints 2; - Stream skipUntil(Stream otherStream) => - SkipUntilStreamTransformer(otherStream).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/start_with.dart b/sandbox/reactivex/lib/src/transformers/start_with.dart deleted file mode 100644 index 60e966c..0000000 --- a/sandbox/reactivex/lib/src/transformers/start_with.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _StartWithStreamSink extends ForwardingSink { - final S _startValue; - - _StartWithStreamSink(this._startValue); - - @override - void onData(S data) => sink.add(data); - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() => sink.close(); - - @override - FutureOr onCancel() {} - - @override - void onListen() { - sink.add(_startValue); - } - - @override - void onPause() {} - - @override - void onResume() {} -} - -/// Prepends a value to the source [Stream]. -/// -/// ### Example -/// -/// Stream.fromIterable([2]) -/// .transform(StartWithStreamTransformer(1)) -/// .listen(print); // prints 1, 2 -class StartWithStreamTransformer extends StreamTransformerBase { - /// The starting event of this [Stream] - final S startValue; - - /// Constructs a [StreamTransformer] which prepends the source [Stream] - /// with [startValue]. - StartWithStreamTransformer(this.startValue); - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _StartWithStreamSink(startValue)); -} - -/// Extends the [Stream] class with the ability to emit the given value as the -/// first item. -extension StartWithExtension on Stream { - /// Prepends a value to the source [Stream]. - /// - /// ### Example - /// - /// Stream.fromIterable([2]).startWith(1).listen(print); // prints 1, 2 - Stream startWith(T startValue) => - StartWithStreamTransformer(startValue).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/start_with_error.dart b/sandbox/reactivex/lib/src/transformers/start_with_error.dart deleted file mode 100644 index 7a74a08..0000000 --- a/sandbox/reactivex/lib/src/transformers/start_with_error.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _StartWithErrorStreamSink extends ForwardingSink { - final Object _e; - final StackTrace? _st; - - _StartWithErrorStreamSink(this._e, this._st); - - @override - void onData(S data) => sink.add(data); - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() => sink.close(); - - @override - FutureOr onCancel() {} - - @override - void onListen() { - sink.addError(_e, _st); - } - - @override - void onPause() {} - - @override - void onResume() {} -} - -/// Prepends an error to the source [Stream]. -/// -/// ### Example -/// -/// Stream.fromIterable([2]) -/// .transform(StartWithErrorStreamTransformer('error')) -/// .listen(null, onError: (e) => print(e)); // prints 'error' -class StartWithErrorStreamTransformer extends StreamTransformerBase { - /// The starting error of this [Stream] - final Object error; - - /// The starting stackTrace of this [Stream] - final StackTrace? stackTrace; - - /// Constructs a [StreamTransformer] which starts with the provided [error] - /// and then outputs all events from the source [Stream]. - StartWithErrorStreamTransformer(this.error, [this.stackTrace]); - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _StartWithErrorStreamSink(error, stackTrace)); -} diff --git a/sandbox/reactivex/lib/src/transformers/start_with_many.dart b/sandbox/reactivex/lib/src/transformers/start_with_many.dart deleted file mode 100644 index 7c8e401..0000000 --- a/sandbox/reactivex/lib/src/transformers/start_with_many.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _StartWithManyStreamSink extends ForwardingSink { - final Iterable _startValues; - - _StartWithManyStreamSink(this._startValues); - - @override - void onData(S data) => sink.add(data); - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() => sink.close(); - - @override - FutureOr onCancel() {} - - @override - void onListen() { - _startValues.forEach(sink.add); - } - - @override - void onPause() {} - - @override - void onResume() {} -} - -/// Prepends a sequence of values to the source [Stream]. -/// -/// ### Example -/// -/// Stream.fromIterable([3]) -/// .transform(StartWithManyStreamTransformer([1, 2])) -/// .listen(print); // prints 1, 2, 3 -class StartWithManyStreamTransformer extends StreamTransformerBase { - /// The starting events of this [Stream] - final Iterable startValues; - - /// Constructs a [StreamTransformer] which prepends the source [Stream] - /// with [startValues]. - StartWithManyStreamTransformer(this.startValues); - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _StartWithManyStreamSink(startValues)); -} - -/// Extends the [Stream] class with the ability to emit the given values as the -/// first items. -extension StartWithManyExtension on Stream { - /// Prepends a sequence of values to the source [Stream]. - /// - /// ### Example - /// - /// Stream.fromIterable([3]).startWithMany([1, 2]) - /// .listen(print); // prints 1, 2, 3 - Stream startWithMany(List startValues) => - StartWithManyStreamTransformer(startValues).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/switch_if_empty.dart b/sandbox/reactivex/lib/src/transformers/switch_if_empty.dart deleted file mode 100644 index aa5f779..0000000 --- a/sandbox/reactivex/lib/src/transformers/switch_if_empty.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _SwitchIfEmptyStreamSink extends ForwardingSink { - final Stream _fallbackStream; - - var _isEmpty = true; - StreamSubscription? _fallbackSubscription; - - _SwitchIfEmptyStreamSink(this._fallbackStream); - - @override - void onData(S data) { - _isEmpty = false; - sink.add(data); - } - - @override - void onError(Object error, StackTrace st) { - sink.addError(error, st); - } - - @override - void onDone() { - if (_isEmpty) { - _fallbackSubscription = _fallbackStream.listen( - sink.add, - onError: sink.addError, - onDone: sink.close, - ); - } else { - sink.close(); - } - } - - @override - FutureOr onCancel() => _fallbackSubscription?.cancel(); - - @override - void onListen() {} - - @override - void onPause() => _fallbackSubscription?.pause(); - - @override - void onResume() => _fallbackSubscription?.resume(); -} - -/// When the original stream emits no items, this operator subscribes to -/// the given fallback stream and emits items from that stream instead. -/// -/// This can be particularly useful when consuming data from multiple sources. -/// For example, when using the Repository Pattern. Assuming you have some -/// data you need to load, you might want to start with the fastest access -/// point and keep falling back to the slowest point. For example, first query -/// an in-memory database, then a database on the file system, then a network -/// call if the data isn't on the local machine. -/// -/// This can be achieved quite simply with switchIfEmpty! -/// -/// ### Example -/// -/// // Let's pretend we have some Data sources that complete without emitting -/// // any items if they don't contain the data we're looking for -/// Stream memory; -/// Stream disk; -/// Stream network; -/// -/// // Start with memory, fallback to disk, then fallback to network. -/// // Simple as that! -/// Stream getThatData = -/// memory.switchIfEmpty(disk).switchIfEmpty(network); -class SwitchIfEmptyStreamTransformer extends StreamTransformerBase { - /// The [Stream] which will be used as fallback, if the source [Stream] is empty. - final Stream fallbackStream; - - /// Constructs a [StreamTransformer] which, when the source [Stream] emits - /// no events, switches over to [fallbackStream]. - SwitchIfEmptyStreamTransformer(this.fallbackStream); - - @override - Stream bind(Stream stream) { - return forwardStream( - stream, () => _SwitchIfEmptyStreamSink(fallbackStream)); - } -} - -/// Extend the Stream class with the ability to return an alternative Stream -/// if the initial Stream completes with no items. -extension SwitchIfEmptyExtension on Stream { - /// When the original Stream emits no items, this operator subscribes to the - /// given fallback stream and emits items from that Stream instead. - /// - /// This can be particularly useful when consuming data from multiple sources. - /// For example, when using the Repository Pattern. Assuming you have some - /// data you need to load, you might want to start with the fastest access - /// point and keep falling back to the slowest point. For example, first query - /// an in-memory database, then a database on the file system, then a network - /// call if the data isn't on the local machine. - /// - /// This can be achieved quite simply with switchIfEmpty! - /// - /// ### Example - /// - /// // Let's pretend we have some Data sources that complete without - /// // emitting any items if they don't contain the data we're looking for - /// Stream memory; - /// Stream disk; - /// Stream network; - /// - /// // Start with memory, fallback to disk, then fallback to network. - /// // Simple as that! - /// Stream getThatData = - /// memory.switchIfEmpty(disk).switchIfEmpty(network); - Stream switchIfEmpty(Stream fallbackStream) => - SwitchIfEmptyStreamTransformer(fallbackStream).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/switch_map.dart b/sandbox/reactivex/lib/src/transformers/switch_map.dart deleted file mode 100644 index 63b63ae..0000000 --- a/sandbox/reactivex/lib/src/transformers/switch_map.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _SwitchMapStreamSink extends ForwardingSink { - final Stream Function(S value) _mapper; - StreamSubscription? _mapperSubscription; - bool _inputClosed = false; - bool _isCancelled = false; - - _SwitchMapStreamSink(this._mapper); - - @override - void onData(S data) { - final Stream mappedStream; - try { - mappedStream = _mapper(data); - } catch (e, s) { - sink.addError(e, s); - return; - } - - final mapperSubscription = _mapperSubscription; - - if (mapperSubscription == null) { - listenToInner(mappedStream); - return; - } - - _mapperSubscription = null; - pauseSubscription(); - mapperSubscription.cancel().onError((e, s) { - if (!_isCancelled) { - sink.addError(e, s); - } - }).whenComplete(() => resumeAndListenToInner(mappedStream)); - } - - void resumeAndListenToInner(Stream mappedStream) { - if (_isCancelled) { - return; - } - - resumeSubscription(); - listenToInner(mappedStream); - } - - void listenToInner(Stream mappedStream) { - assert(_mapperSubscription == null); - - _mapperSubscription = mappedStream.listen( - sink.add, - onError: sink.addError, - onDone: () { - _mapperSubscription = null; - - if (_inputClosed) { - sink.close(); - } - }, - ); - - // https://github.com/dart-lang/stream_transform/blob/9743578b0119de6a8badd30bb16ef15d79bd3b15/lib/src/switch.dart#L71-L74 - // If a pause happens during an _mapperSubscription.cancel, - // we still listen to the next stream when the cancel is done. - // Then we immediately pause it again here. - if (sink.isPaused) { - _mapperSubscription?.pause(); - } - } - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() { - _inputClosed = true; - - _mapperSubscription ?? sink.close(); - } - - @override - FutureOr onCancel() { - _isCancelled = true; - - return _mapperSubscription?.cancel(); - } - - @override - void onListen() {} - - @override - void onPause() => _mapperSubscription?.pause(); - - @override - void onResume() => _mapperSubscription?.resume(); -} - -/// Converts each emitted item into a new Stream using the given mapper -/// function. The newly created Stream will be be listened to and begin -/// emitting items, and any previously created Stream will stop emitting. -/// -/// The switchMap operator is similar to the flatMap and concatMap -/// methods, but it only emits items from the most recently created Stream. -/// -/// This can be useful when you only want the very latest state from -/// asynchronous APIs, for example. -/// -/// ### Example -/// -/// Stream.fromIterable([4, 3, 2, 1]) -/// .transform(SwitchMapStreamTransformer((i) => -/// Stream.fromFuture( -/// Future.delayed(Duration(minutes: i), () => i)) -/// .listen(print); // prints 1 -class SwitchMapStreamTransformer extends StreamTransformerBase { - /// Method which converts incoming events into a new [Stream] - final Stream Function(S value) mapper; - - /// Constructs a [StreamTransformer] which maps each event from the source [Stream] - /// using [mapper]. - /// - /// The mapped [Stream] will be be listened to and begin - /// emitting items, and any previously created mapper [Stream]s will stop emitting. - SwitchMapStreamTransformer(this.mapper); - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _SwitchMapStreamSink(mapper)); -} - -/// Extends the Stream with the ability to convert one stream into a new Stream -/// whenever the source emits an item. Every time a new Stream is created, the -/// previous Stream is discarded. -extension SwitchMapExtension on Stream { - /// Converts each emitted item into a Stream using the given mapper function. - /// The newly created Stream will be be listened to and begin emitting items, - /// and any previously created Stream will stop emitting. - /// - /// The switchMap operator is similar to the flatMap and concatMap methods, - /// but it only emits items from the most recently created Stream. - /// - /// This can be useful when you only want the very latest state from - /// asynchronous APIs, for example. - /// - /// ### Example - /// - /// RangeStream(4, 1) - /// .switchMap((i) => - /// TimerStream(i, Duration(minutes: i))) - /// .listen(print); // prints 1 - Stream switchMap(Stream Function(T value) mapper) => - SwitchMapStreamTransformer(mapper).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/take_last.dart b/sandbox/reactivex/lib/src/transformers/take_last.dart deleted file mode 100644 index 56a11a7..0000000 --- a/sandbox/reactivex/lib/src/transformers/take_last.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _TakeLastStreamSink extends ForwardingSink { - _TakeLastStreamSink(this.count); - - final int count; - final Queue queue = DoubleLinkedQueue(); - - @override - void onData(T data) { - if (count > 0) { - queue.addLast(data); - if (queue.length > count) { - queue.removeFirst(); - } - } - } - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() { - if (queue.isNotEmpty) { - queue.toList(growable: false).forEach(sink.add); - } - sink.close(); - } - - @override - FutureOr onCancel() { - queue.clear(); - } - - @override - void onListen() {} - - @override - void onPause() {} - - @override - void onResume() {} -} - -/// Emits only the final [count] values emitted by the source [Stream]. -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3, 4, 5]) -/// .transform(TakeLastStreamTransformer(3)) -/// .listen(print); // prints 3, 4, 5 -class TakeLastStreamTransformer extends StreamTransformerBase { - /// Constructs a [StreamTransformer] which emits only the final [count] - /// events from the source [Stream]. - TakeLastStreamTransformer(this.count) { - if (count < 0) throw ArgumentError.value(count, 'count'); - } - - /// The [count] of final items emitted when the stream completes. - final int count; - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _TakeLastStreamSink(count)); -} - -/// Extends the [Stream] class with the ability receive only the final [count] -/// events from the source [Stream]. -extension TakeLastExtension on Stream { - /// Emits only the final [count] values emitted by the source [Stream]. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3, 4, 5]) - /// .takeLast(3) - /// .listen(print); // prints 3, 4, 5 - Stream takeLast(int count) => - TakeLastStreamTransformer(count).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/take_until.dart b/sandbox/reactivex/lib/src/transformers/take_until.dart deleted file mode 100644 index 54fe0d6..0000000 --- a/sandbox/reactivex/lib/src/transformers/take_until.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _TakeUntilStreamSink extends ForwardingSink { - final Stream _otherStream; - StreamSubscription? _otherSubscription; - - _TakeUntilStreamSink(this._otherStream); - - @override - void onData(S data) => sink.add(data); - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() { - _otherSubscription?.cancel(); - sink.close(); - } - - @override - FutureOr onCancel() => _otherSubscription?.cancel(); - - @override - void onListen() => _otherSubscription = _otherStream - .take(1) - .listen(null, onError: sink.addError, onDone: sink.close); - - @override - void onPause() => _otherSubscription?.pause(); - - @override - void onResume() => _otherSubscription?.resume(); -} - -/// Returns the values from the source stream sequence until the other -/// stream sequence produces a value. -/// -/// ### Example -/// -/// MergeStream([ -/// Stream.fromIterable([1]), -/// TimerStream(2, Duration(minutes: 1)) -/// ]) -/// .transform(TakeUntilStreamTransformer( -/// TimerStream(3, Duration(seconds: 10)))) -/// .listen(print); // prints 1 -class TakeUntilStreamTransformer extends StreamTransformerBase { - /// The [Stream] which closes this [Stream] as soon as it emits an event. - final Stream otherStream; - - /// Constructs a [StreamTransformer] which emits events from the source [Stream], - /// until [otherStream] fires. - TakeUntilStreamTransformer(this.otherStream); - - @override - Stream bind(Stream stream) => - forwardStream(stream, () => _TakeUntilStreamSink(otherStream)); -} - -/// Extends the Stream class with the ability receive events from the source -/// Stream until another Stream produces a value. -extension TakeUntilExtension on Stream { - /// Returns the values from the source Stream sequence until the other Stream - /// sequence produces a value. - /// - /// ### Example - /// - /// MergeStream([ - /// Stream.fromIterable([1]), - /// TimerStream(2, Duration(minutes: 1)) - /// ]) - /// .takeUntil(TimerStream(3, Duration(seconds: 10))) - /// .listen(print); // prints 1 - Stream takeUntil(Stream otherStream) => - TakeUntilStreamTransformer(otherStream).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/take_while_inclusive.dart b/sandbox/reactivex/lib/src/transformers/take_while_inclusive.dart deleted file mode 100644 index 8471db1..0000000 --- a/sandbox/reactivex/lib/src/transformers/take_while_inclusive.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'dart:async'; - -class _TakeWhileInclusiveStreamSink implements EventSink { - final bool Function(S) _test; - final EventSink _outputSink; - - _TakeWhileInclusiveStreamSink(this._outputSink, this._test); - - @override - void add(S data) { - bool satisfies; - - try { - satisfies = _test(data); - } catch (e, s) { - _outputSink.addError(e, s); - // The test didn't say true. Didn't say false either, but we stop anyway. - _outputSink.close(); - return; - } - - if (satisfies) { - _outputSink.add(data); - } else { - _outputSink.add(data); - _outputSink.close(); - } - } - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() => _outputSink.close(); -} - -/// Emits values emitted by the source Stream so long as each value -/// satisfies the given test. When the test is not satisfied by a value, it -/// will emit this value as a final event and then complete. -/// -/// ### Example -/// -/// Stream.fromIterable([2, 3, 4, 5, 6, 1, 2, 3]) -/// .transform(TakeWhileInclusiveStreamTransformer((i) => i < 4)) -/// .listen(print); // prints 2, 3, 4 -class TakeWhileInclusiveStreamTransformer - extends StreamTransformerBase { - /// Method used to test incoming events - final bool Function(S) test; - - /// Constructs a [StreamTransformer] which forwards data events while [test] - /// is successful, and includes last event that caused [test] to return false. - TakeWhileInclusiveStreamTransformer(this.test); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _TakeWhileInclusiveStreamSink(sink, test)); -} - -/// Extends the Stream class with the ability to take events while they pass -/// the condition given and include last event that doesn't pass the condition. -extension TakeWhileInclusiveExtension on Stream { - /// Emits values emitted by the source Stream so long as each value - /// satisfies the given test. When the test is not satisfied by a value, it - /// will emit this value as a final event and then complete. - /// - /// ### Example - /// - /// Stream.fromIterable([2, 3, 4, 5, 6, 1, 2, 3]) - /// .takeWhileInclusive((i) => i < 4) - /// .listen(print); // prints 2, 3, 4 - Stream takeWhileInclusive(bool Function(T) test) => - TakeWhileInclusiveStreamTransformer(test).bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/time_interval.dart b/sandbox/reactivex/lib/src/transformers/time_interval.dart deleted file mode 100644 index b7e3c71..0000000 --- a/sandbox/reactivex/lib/src/transformers/time_interval.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; - -class _TimeIntervalStreamSink extends ForwardingSink> { - final _stopwatch = Stopwatch(); - - @override - void onData(S data) { - _stopwatch.stop(); - sink.add( - TimeInterval( - data, - Duration( - microseconds: _stopwatch.elapsedMicroseconds, - ), - ), - ); - _stopwatch - ..reset() - ..start(); - } - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() => sink.close(); - - @override - FutureOr onCancel() {} - - @override - void onListen() => _stopwatch.start(); - - @override - void onPause() {} - - @override - void onResume() {} -} - -/// Records the time interval between consecutive values in an stream -/// sequence. -/// -/// ### Example -/// -/// Stream.fromIterable([1]) -/// .transform(IntervalStreamTransformer(Duration(seconds: 1))) -/// .transform(TimeIntervalStreamTransformer()) -/// .listen(print); // prints TimeInterval{interval: 0:00:01, value: 1} -class TimeIntervalStreamTransformer - extends StreamTransformerBase> { - /// Constructs a [StreamTransformer] which emits events from the - /// source [Stream] as snapshots in the form of [TimeInterval]. - TimeIntervalStreamTransformer(); - - @override - Stream> bind(Stream stream) => - forwardStream(stream, () => _TimeIntervalStreamSink()); -} - -/// A class that represents a snapshot of the current value emitted by a -/// [Stream], at a specified interval. -class TimeInterval { - /// The interval at which this snapshot was taken - final Duration interval; - - /// The value at the moment of [interval] - final T value; - - /// Constructs a snapshot of a [Stream], containing the [Stream]'s event - /// at the specified [interval] as [value]. - TimeInterval(this.value, this.interval); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - return other is TimeInterval && - interval == other.interval && - value == other.value; - } - - @override - int get hashCode { - return interval.hashCode ^ value.hashCode; - } - - @override - String toString() { - return 'TimeInterval{interval: $interval, value: $value}'; - } -} - -/// Extends the Stream class with the ability to record the time interval -/// between consecutive values in an stream -extension TimeIntervalExtension on Stream { - /// Records the time interval between consecutive values in a Stream sequence. - /// - /// ### Example - /// - /// Stream.fromIterable([1]) - /// .interval(Duration(seconds: 1)) - /// .timeInterval() - /// .listen(print); // prints TimeInterval{interval: 0:00:01, value: 1} - Stream> timeInterval() => - TimeIntervalStreamTransformer().bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/timestamp.dart b/sandbox/reactivex/lib/src/transformers/timestamp.dart deleted file mode 100644 index 0564402..0000000 --- a/sandbox/reactivex/lib/src/transformers/timestamp.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:async'; - -class _TimestampStreamSink implements EventSink { - final EventSink> _outputSink; - - _TimestampStreamSink(this._outputSink); - - @override - void add(S data) { - _outputSink.add(Timestamped(DateTime.now(), data)); - } - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() => _outputSink.close(); -} - -/// Wraps each item emitted by the source Stream in a [Timestamped] object -/// that includes the emitted item and the time when the item was emitted. -/// -/// Example -/// -/// Stream.fromIterable([1]) -/// .transform(TimestampStreamTransformer()) -/// .listen((i) => print(i)); // prints 'TimeStamp{timestamp: XXX, value: 1}'; -class TimestampStreamTransformer - extends StreamTransformerBase> { - /// Constructs a [StreamTransformer] which emits events from the - /// source [Stream] as snapshots in the form of [Timestamped]. - TimestampStreamTransformer(); - - @override - Stream> bind(Stream stream) => - Stream.eventTransformed(stream, (sink) => _TimestampStreamSink(sink)); -} - -/// A class that represents a snapshot of the current value emitted by a -/// [Stream], at a specified timestamp. -class Timestamped { - /// The value at the moment of the [timestamp] - final T value; - - /// The time at which this snapshot was taken - final DateTime timestamp; - - /// Constructs a snapshot of a [Stream], containing the [Stream]'s event - /// at the specified [timestamp] as [value]. - Timestamped(this.timestamp, this.value); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - return other is Timestamped && - timestamp == other.timestamp && - value == other.value; - } - - @override - int get hashCode { - return timestamp.hashCode ^ value.hashCode; - } - - @override - String toString() { - return 'TimeStamp{timestamp: $timestamp, value: $value}'; - } -} - -/// Extends the Stream class with the ability to wrap each item emitted by the -/// source Stream in a [Timestamped] object that includes the emitted item and -/// the time when the item was emitted. -extension TimeStampExtension on Stream { - /// Wraps each item emitted by the source Stream in a [Timestamped] object - /// that includes the emitted item and the time when the item was emitted. - /// - /// Example - /// - /// Stream.fromIterable([1]) - /// .timestamp() - /// .listen((i) => print(i)); // prints 'TimeStamp{timestamp: XXX, value: 1}'; - Stream> timestamp() => - TimestampStreamTransformer().bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/where_not_null.dart b/sandbox/reactivex/lib/src/transformers/where_not_null.dart deleted file mode 100644 index 5fdcb4e..0000000 --- a/sandbox/reactivex/lib/src/transformers/where_not_null.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'dart:async'; - -class _WhereNotNullStreamSink implements EventSink { - final EventSink _outputSink; - - _WhereNotNullStreamSink(this._outputSink); - - @override - void add(T? event) { - if (event != null) { - _outputSink.add(event); - } - } - - @override - void addError(Object error, [StackTrace? stackTrace]) => - _outputSink.addError(error, stackTrace); - - @override - void close() => _outputSink.close(); -} - -/// Create a Stream which emits all the non-`null` elements of the Stream, -/// in their original emission order. -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2, 3, null, 4, null]) -/// .transform(WhereNotNullStreamTransformer()) -/// .listen(print); // prints 1, 2, 3, 4 -/// -/// // equivalent to: -/// -/// Stream.fromIterable([1, 2, 3, null, 4, null]) -/// .transform(WhereTypeStreamTransformer()) -/// .listen(print); // prints 1, 2, 3, 4 -class WhereNotNullStreamTransformer - extends StreamTransformerBase { - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _WhereNotNullStreamSink(sink)); -} - -/// Extends the Stream class with the ability to convert the source Stream -/// to a Stream which emits all the non-`null` elements -/// of this Stream, in their original emission order. -extension WhereNotNullExtension on Stream { - /// Returns a Stream which emits all the non-`null` elements - /// of this Stream, in their original emission order. - /// - /// For a `Stream`, this method is equivalent to `.whereType()`. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2, 3, null, 4, null]) - /// .whereNotNull() - /// .listen(print); // prints 1, 2, 3, 4 - /// - /// // equivalent to: - /// - /// Stream.fromIterable([1, 2, 3, null, 4, null]) - /// .whereType() - /// .listen(print); // prints 1, 2, 3, 4 - Stream whereNotNull() => WhereNotNullStreamTransformer().bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/where_type.dart b/sandbox/reactivex/lib/src/transformers/where_type.dart deleted file mode 100644 index 22a76a6..0000000 --- a/sandbox/reactivex/lib/src/transformers/where_type.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'dart:async'; - -class _WhereTypeStreamSink implements EventSink { - final EventSink _outputSink; - - _WhereTypeStreamSink(this._outputSink); - - @override - void add(S data) { - if (data is T) { - _outputSink.add(data); - } - } - - @override - void addError(e, [st]) => _outputSink.addError(e, st); - - @override - void close() => _outputSink.close(); -} - -/// This transformer is a shorthand for [Stream.where] followed by [Stream.cast]. -/// -/// Events that do not match [T] are filtered out, the resulting -/// [Stream] will be of Type [T]. -/// -/// ### Example -/// -/// Stream.fromIterable([1, 'two', 3, 'four']) -/// .whereType() -/// .listen(print); // prints 1, 3 -/// -/// // as opposed to: -/// -/// Stream.fromIterable([1, 'two', 3, 'four']) -/// .where((event) => event is int) -/// .cast() -/// .listen(print); // prints 1, 3 -/// -class WhereTypeStreamTransformer extends StreamTransformerBase { - /// Constructs a [StreamTransformer] which combines [Stream.where] followed by [Stream.cast]. - WhereTypeStreamTransformer(); - - @override - Stream bind(Stream stream) => Stream.eventTransformed( - stream, (sink) => _WhereTypeStreamSink(sink)); -} - -/// Extends the Stream class with the ability to filter down events to only -/// those of a specific type. -extension WhereTypeExtension on Stream { - /// This transformer is a shorthand for [Stream.where] followed by - /// [Stream.cast]. - /// - /// Events that do not match [T] are filtered out, the resulting [Stream] will - /// be of Type [T]. - /// - /// ### Example - /// - /// Stream.fromIterable([1, 'two', 3, 'four']) - /// .whereType() - /// .listen(print); // prints 1, 3 - /// - /// #### as opposed to: - /// - /// Stream.fromIterable([1, 'two', 3, 'four']) - /// .where((event) => event is int) - /// .cast() - /// .listen(print); // prints 1, 3 - Stream whereType() => WhereTypeStreamTransformer().bind(this); -} diff --git a/sandbox/reactivex/lib/src/transformers/with_latest_from.dart b/sandbox/reactivex/lib/src/transformers/with_latest_from.dart deleted file mode 100644 index 8e3013c..0000000 --- a/sandbox/reactivex/lib/src/transformers/with_latest_from.dart +++ /dev/null @@ -1,738 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/collection_extensions.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/forwarding_stream.dart'; -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -class _WithLatestFromStreamSink extends ForwardingSink { - final Iterable> _latestFromStreams; - final R Function(S t, List values) _combiner; - - bool _hasValues = false; - List? _latestValues; - late List> _subscriptions; - - _WithLatestFromStreamSink(this._latestFromStreams, this._combiner); - - @override - void onData(S data) { - if (_hasValues && _latestValues != null) { - final R combinedValue; - try { - combinedValue = _combiner(data, List.unmodifiable(_latestValues!)); - } catch (e, s) { - sink.addError(e, s); - return; - } - sink.add(combinedValue); - } - } - - @override - void onError(Object e, StackTrace st) => sink.addError(e, st); - - @override - void onDone() => sink.close(); - - @override - Future? onCancel() { - _latestValues = null; - return _subscriptions.cancelAll(); - } - - @override - void onListen() { - var count = 0; - - StreamSubscription mapper(int index, Stream stream) { - var hasValue = false; - - return stream.listen( - (value) { - if (!hasValue) { - hasValue = true; - if (++count == _subscriptions.length) { - _hasValues = true; - } - } - _latestValues![index] = value; - }, - onError: sink.addError, - ); - } - - _subscriptions = - _latestFromStreams.mapIndexed(mapper).toList(growable: false); - if (_subscriptions.isEmpty) { - _hasValues = true; - } - _latestValues = List.filled(_subscriptions.length, null); - } - - @override - void onPause() => _subscriptions.pauseAll(); - - @override - void onResume() => _subscriptions.resumeAll(); -} - -/// A StreamTransformer that emits when the source stream emits, combining -/// the latest values from the two streams using the provided function. -/// -/// If the latestFromStream has not emitted any values, this stream will not -/// emit either. -/// -/// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) -/// -/// ### Example -/// -/// Stream.fromIterable([1, 2]).transform( -/// WithLatestFromStreamTransformer( -/// Stream.fromIterable([2, 3]), (a, b) => a + b) -/// .listen(print); // prints 4 (due to the async nature of streams) -class WithLatestFromStreamTransformer - extends StreamTransformerBase { - /// A collection of [Stream]s of which the latest values will be combined. - final Iterable> latestFromStreams; - - /// The combiner Function - final R Function(S t, List values) combiner; - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from [latestFromStreams] using the provided function [fn]. - WithLatestFromStreamTransformer(this.latestFromStreams, this.combiner); - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from [latestFromStreams] using a [List]. - static WithLatestFromStreamTransformer> withList( - Iterable> latestFromStreams, - ) { - return WithLatestFromStreamTransformer>( - latestFromStreams, - (s, values) => [s, ...values], - ); - } - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from [latestFromStream] using the provided function [fn]. - static WithLatestFromStreamTransformer with1( - Stream latestFromStream, - R Function(T t, S s) fn, - ) => - WithLatestFromStreamTransformer( - [latestFromStream], - (s, values) => fn(s, values[0]), - ); - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from all [latestFromStream]s using the provided function [fn]. - static WithLatestFromStreamTransformer with2( - Stream latestFromStream1, - Stream latestFromStream2, - R Function(T t, A a, B b) fn, - ) => - WithLatestFromStreamTransformer( - [latestFromStream1, latestFromStream2], - (s, values) => fn(s, values[0] as A, values[1] as B), - ); - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from all [latestFromStream]s using the provided function [fn]. - static WithLatestFromStreamTransformer with3( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - R Function(T t, A a, B b, C c) fn, - ) => - WithLatestFromStreamTransformer( - [ - latestFromStream1, - latestFromStream2, - latestFromStream3, - ], - (s, values) { - return fn( - s, - values[0] as A, - values[1] as B, - values[2] as C, - ); - }, - ); - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from all [latestFromStream]s using the provided function [fn]. - static WithLatestFromStreamTransformer with4( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - R Function(T t, A a, B b, C c, D d) fn, - ) => - WithLatestFromStreamTransformer( - [ - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - ], - (s, values) { - return fn( - s, - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - ); - }, - ); - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from all [latestFromStream]s using the provided function [fn]. - static WithLatestFromStreamTransformer - with5( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - R Function(T t, A a, B b, C c, D d, E e) fn, - ) => - WithLatestFromStreamTransformer( - [ - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - ], - (s, values) { - return fn( - s, - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - ); - }, - ); - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from all [latestFromStream]s using the provided function [fn]. - static WithLatestFromStreamTransformer - with6( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - R Function(T t, A a, B b, C c, D d, E e, F f) fn, - ) => - WithLatestFromStreamTransformer( - [ - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - ], - (s, values) { - return fn( - s, - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - ); - }, - ); - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from all [latestFromStream]s using the provided function [fn]. - static WithLatestFromStreamTransformer - with7( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - Stream latestFromStream7, - R Function(T t, A a, B b, C c, D d, E e, F f, G g) fn, - ) => - WithLatestFromStreamTransformer( - [ - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - latestFromStream7, - ], - (s, values) { - return fn( - s, - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - ); - }, - ); - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from all [latestFromStream]s using the provided function [fn]. - static WithLatestFromStreamTransformer - with8( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - Stream latestFromStream7, - Stream latestFromStream8, - R Function(T t, A a, B b, C c, D d, E e, F f, G g, H h) fn, - ) => - WithLatestFromStreamTransformer( - [ - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - latestFromStream7, - latestFromStream8, - ], - (s, values) { - return fn( - s, - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - values[7] as H, - ); - }, - ); - - /// Constructs a [StreamTransformer] that emits when the source [Stream] emits, combining - /// the latest values from all [latestFromStream]s using the provided function [fn]. - static WithLatestFromStreamTransformer - with9( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - Stream latestFromStream7, - Stream latestFromStream8, - Stream latestFromStream9, - R Function(T t, A a, B b, C c, D d, E e, F f, G g, H h, I i) fn, - ) => - WithLatestFromStreamTransformer( - [ - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - latestFromStream7, - latestFromStream8, - latestFromStream9, - ], - (s, values) { - return fn( - s, - values[0] as A, - values[1] as B, - values[2] as C, - values[3] as D, - values[4] as E, - values[5] as F, - values[6] as G, - values[7] as H, - values[8] as I, - ); - }, - ); - - @override - Stream bind(Stream stream) => forwardStream( - stream, - () => _WithLatestFromStreamSink(latestFromStreams, combiner), - ); -} - -/// Extends the Stream class with the ability to merge the source Stream with -/// the last emitted item from another Stream. -extension WithLatestFromExtensions on Stream { - /// Creates a Stream that emits when the source stream emits, combining the - /// latest values from the two streams using the provided function. - /// - /// If the latestFromStream has not emitted any values, this stream will not - /// emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2]).withLatestFrom( - /// Stream.fromIterable([2, 3]), (a, b) => a + b) - /// .listen(print); // prints 4 (due to the async nature of streams) - Stream withLatestFrom( - Stream latestFromStream, R Function(T t, S s) fn) => - WithLatestFromStreamTransformer.with1(latestFromStream, fn) - .bind(this); - - /// Creates a Stream that emits when the source stream emits, combining the - /// latest values from the streams into a list. This is helpful when you need - /// to combine a dynamic number of Streams. - /// - /// If any of latestFromStreams has not emitted any values, this stream will - /// not emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// Stream.fromIterable([1, 2]).withLatestFromList( - /// [ - /// Stream.fromIterable([2, 3]), - /// Stream.fromIterable([3, 4]), - /// Stream.fromIterable([4, 5]), - /// Stream.fromIterable([5, 6]), - /// Stream.fromIterable([6, 7]), - /// ], - /// ).listen(print); // print [2, 2, 3, 4, 5, 6] (due to the async nature of streams) - /// - Stream> withLatestFromList(Iterable> latestFromStreams) => - WithLatestFromStreamTransformer.withList(latestFromStreams).bind(this); - - /// Creates a Stream that emits when the source stream emits, combining the - /// latest values from the three streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will - /// not emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2]) - /// .withLatestFrom2( - /// Stream.fromIterable([2, 3]), - /// Stream.fromIterable([3, 4]), - /// (int a, int b, int c) => a + b + c, - /// ) - /// .listen(print); // prints 7 (due to the async nature of streams) - Stream withLatestFrom2( - Stream latestFromStream1, - Stream latestFromStream2, - R Function(T t, A a, B b) fn, - ) => - WithLatestFromStreamTransformer.with2( - latestFromStream1, - latestFromStream2, - fn, - ).bind(this); - - /// Creates a Stream that emits when the source stream emits, combining the - /// latest values from the four streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will - /// not emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2]) - /// .withLatestFrom3( - /// Stream.fromIterable([2, 3]), - /// Stream.fromIterable([3, 4]), - /// Stream.fromIterable([4, 5]), - /// (int a, int b, int c, int d) => a + b + c + d, - /// ) - /// .listen(print); // prints 11 (due to the async nature of streams) - Stream withLatestFrom3( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - R Function(T t, A a, B b, C c) fn, - ) => - WithLatestFromStreamTransformer.with3( - latestFromStream1, - latestFromStream2, - latestFromStream3, - fn, - ).bind(this); - - /// Creates a Stream that emits when the source stream emits, combining the - /// latest values from the five streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will - /// not emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2]) - /// .withLatestFrom4( - /// Stream.fromIterable([2, 3]), - /// Stream.fromIterable([3, 4]), - /// Stream.fromIterable([4, 5]), - /// Stream.fromIterable([5, 6]), - /// (int a, int b, int c, int d, int e) => a + b + c + d + e, - /// ) - /// .listen(print); // prints 16 (due to the async nature of streams) - Stream withLatestFrom4( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - R Function(T t, A a, B b, C c, D d) fn, - ) => - WithLatestFromStreamTransformer.with4( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - fn, - ).bind(this); - - /// Creates a Stream that emits when the source stream emits, combining the - /// latest values from the six streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will - /// not emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2]) - /// .withLatestFrom5( - /// Stream.fromIterable([2, 3]), - /// Stream.fromIterable([3, 4]), - /// Stream.fromIterable([4, 5]), - /// Stream.fromIterable([5, 6]), - /// Stream.fromIterable([6, 7]), - /// (int a, int b, int c, int d, int e, int f) => a + b + c + d + e + f, - /// ) - /// .listen(print); // prints 22 (due to the async nature of streams) - Stream withLatestFrom5( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - R Function(T t, A a, B b, C c, D d, E e) fn, - ) => - WithLatestFromStreamTransformer.with5( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - fn, - ).bind(this); - - /// Creates a Stream that emits when the source stream emits, combining the - /// latest values from the seven streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will - /// not emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2]) - /// .withLatestFrom6( - /// Stream.fromIterable([2, 3]), - /// Stream.fromIterable([3, 4]), - /// Stream.fromIterable([4, 5]), - /// Stream.fromIterable([5, 6]), - /// Stream.fromIterable([6, 7]), - /// Stream.fromIterable([7, 8]), - /// (int a, int b, int c, int d, int e, int f, int g) => - /// a + b + c + d + e + f + g, - /// ) - /// .listen(print); // prints 29 (due to the async nature of streams) - Stream withLatestFrom6( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - R Function(T t, A a, B b, C c, D d, E e, F f) fn, - ) => - WithLatestFromStreamTransformer.with6( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - fn, - ).bind(this); - - /// Creates a Stream that emits when the source stream emits, combining the - /// latest values from the eight streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will - /// not emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2]) - /// .withLatestFrom7( - /// Stream.fromIterable([2, 3]), - /// Stream.fromIterable([3, 4]), - /// Stream.fromIterable([4, 5]), - /// Stream.fromIterable([5, 6]), - /// Stream.fromIterable([6, 7]), - /// Stream.fromIterable([7, 8]), - /// Stream.fromIterable([8, 9]), - /// (int a, int b, int c, int d, int e, int f, int g, int h) => - /// a + b + c + d + e + f + g + h, - /// ) - /// .listen(print); // prints 37 (due to the async nature of streams) - Stream withLatestFrom7( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - Stream latestFromStream7, - R Function(T t, A a, B b, C c, D d, E e, F f, G g) fn, - ) => - WithLatestFromStreamTransformer.with7( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - latestFromStream7, - fn, - ).bind(this); - - /// Creates a Stream that emits when the source stream emits, combining the - /// latest values from the nine streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will - /// not emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2]) - /// .withLatestFrom8( - /// Stream.fromIterable([2, 3]), - /// Stream.fromIterable([3, 4]), - /// Stream.fromIterable([4, 5]), - /// Stream.fromIterable([5, 6]), - /// Stream.fromIterable([6, 7]), - /// Stream.fromIterable([7, 8]), - /// Stream.fromIterable([8, 9]), - /// Stream.fromIterable([9, 10]), - /// (int a, int b, int c, int d, int e, int f, int g, int h, int i) => - /// a + b + c + d + e + f + g + h + i, - /// ) - /// .listen(print); // prints 46 (due to the async nature of streams) - Stream withLatestFrom8( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - Stream latestFromStream7, - Stream latestFromStream8, - R Function(T t, A a, B b, C c, D d, E e, F f, G g, H h) fn, - ) => - WithLatestFromStreamTransformer.with8( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - latestFromStream7, - latestFromStream8, - fn, - ).bind(this); - - /// Creates a Stream that emits when the source stream emits, combining the - /// latest values from the ten streams using the provided function. - /// - /// If any of latestFromStreams has not emitted any values, this stream will - /// not emit either. - /// - /// [Interactive marble diagram](http://rxmarbles.com/#withLatestFrom) - /// - /// ### Example - /// - /// Stream.fromIterable([1, 2]) - /// .withLatestFrom9( - /// Stream.fromIterable([2, 3]), - /// Stream.fromIterable([3, 4]), - /// Stream.fromIterable([4, 5]), - /// Stream.fromIterable([5, 6]), - /// Stream.fromIterable([6, 7]), - /// Stream.fromIterable([7, 8]), - /// Stream.fromIterable([8, 9]), - /// Stream.fromIterable([9, 10]), - /// Stream.fromIterable([10, 11]), - /// (int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) => - /// a + b + c + d + e + f + g + h + i + j, - /// ) - /// .listen(print); // prints 46 (due to the async nature of streams) - Stream withLatestFrom9( - Stream latestFromStream1, - Stream latestFromStream2, - Stream latestFromStream3, - Stream latestFromStream4, - Stream latestFromStream5, - Stream latestFromStream6, - Stream latestFromStream7, - Stream latestFromStream8, - Stream latestFromStream9, - R Function(T t, A a, B b, C c, D d, E e, F f, G g, H h, I i) fn, - ) => - WithLatestFromStreamTransformer.with9( - latestFromStream1, - latestFromStream2, - latestFromStream3, - latestFromStream4, - latestFromStream5, - latestFromStream6, - latestFromStream7, - latestFromStream8, - latestFromStream9, - fn, - ).bind(this); -} diff --git a/sandbox/reactivex/lib/src/utils/collection_extensions.dart b/sandbox/reactivex/lib/src/utils/collection_extensions.dart deleted file mode 100644 index a58f9ee..0000000 --- a/sandbox/reactivex/lib/src/utils/collection_extensions.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'dart:collection'; -import 'dart:math'; - -/// @internal -/// @nodoc -/// Provides extension methods on [List]. -extension ListExtensions on List { - /// @internal - /// Returns a list of values built from the elements of this list - /// and the other list with the same index - /// using the provided transform function applied to each pair of elements. - /// The returned list has length of the shortest list. - List zipWith( - List other, - R Function(T, S) transform, { - bool growable = true, - }) => - List.generate( - min(length, other.length), - (index) => transform(this[index], other[index]), - growable: growable, - ); -} - -/// @internal -/// Provides extension methods on [Iterable]. -extension IterableExtensions on Iterable { - /// @internal - /// The non-`null` results of calling [transform] on the elements of [this]. - /// - /// Returns a lazy iterable which calls [transform] - /// on the elements of this iterable in iteration order, - /// then emits only the non-`null` values. - /// - /// If [transform] throws, the iteration is terminated. - Iterable mapNotNull(R? Function(T) transform) sync* { - for (final e in this) { - final v = transform(e); - if (v != null) { - yield v; - } - } - } - - /// @internal - /// Maps each element and its index to a new value. - Iterable mapIndexed(R Function(int index, T element) transform) sync* { - var index = 0; - for (final e in this) { - yield transform(index++, e); - } - } -} - -/// @internal -/// Provides [removeFirstElements] extension method on [Queue]. -extension RemoveFirstElementsQueueExtension on Queue { - /// @internal - /// Removes the first [count] elements of this queue. - void removeFirstElements(int count) { - for (var i = 0; i < count; i++) { - removeFirst(); - } - } -} diff --git a/sandbox/reactivex/lib/src/utils/composite_subscription.dart b/sandbox/reactivex/lib/src/utils/composite_subscription.dart deleted file mode 100644 index 7745b8d..0000000 --- a/sandbox/reactivex/lib/src/utils/composite_subscription.dart +++ /dev/null @@ -1,121 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/subscription.dart'; - -/// Acts as a container for multiple subscriptions that can be canceled at once -/// e.g. view subscriptions in Flutter that need to be canceled on view disposal -/// -/// Can be cleared or disposed. When disposed, cannot be used again. -/// ### Example -/// // init your subscriptions -/// composite.add(stream1.listen(listener1)) -/// ..add(stream2.listen(listener1)) -/// ..add(stream3.listen(listener1)); -/// -/// // clear them all at once -/// composite.clear(); -class CompositeSubscription implements StreamSubscription { - bool _isDisposed = false; - - final List> _subscriptionsList = []; - - /// Checks if this composite is disposed. If it is, the composite can't be used again - /// and will throw an error if you try to add more subscriptions to it. - bool get isDisposed => _isDisposed; - - /// Returns the total amount of currently added [StreamSubscription]s - int get length => _subscriptionsList.length; - - /// Checks if there currently are no [StreamSubscription]s added - bool get isEmpty => _subscriptionsList.isEmpty; - - /// Checks if there currently are [StreamSubscription]s added - bool get isNotEmpty => _subscriptionsList.isNotEmpty; - - /// Whether all managed [StreamSubscription]s are currently paused. - bool get allPaused => - _subscriptionsList.isNotEmpty && - _subscriptionsList.every((s) => s.isPaused); - - /// Adds new subscription to this composite. - /// - /// Throws an exception if this composite was disposed - StreamSubscription add(StreamSubscription subscription) { - if (isDisposed) { - throw StateError( - 'This $runtimeType was disposed, consider checking `isDisposed` or try to use new instance instead'); - } - _subscriptionsList.add(subscription); - return subscription; - } - - /// Remove the subscription from this composite and cancel it if it has been removed. - Future? remove( - StreamSubscription subscription, { - bool shouldCancel = true, - }) => - _subscriptionsList.remove(subscription) && shouldCancel - ? subscription.cancel() - : null; - - /// Cancels all subscriptions added to this composite. Clears subscriptions collection. - /// - /// This composite can be reused after calling this method. - Future? clear() { - final cancelAllDone = _subscriptionsList.cancelAll(); - _subscriptionsList.clear(); - return cancelAllDone; - } - - /// Cancels all subscriptions added to this composite. Disposes this. - /// - /// This composite can't be reused after calling this method. - Future? dispose() { - final clearDone = clear(); - _isDisposed = true; - return clearDone; - } - - /// Pauses all subscriptions added to this composite. - void pauseAll([Future? resumeSignal]) => - _subscriptionsList.pauseAll(resumeSignal); - - /// Resumes all subscriptions added to this composite. - void resumeAll() => _subscriptionsList.resumeAll(); - - // implements StreamSubscription - - @override - Future cancel() => dispose() ?? Future.value(null); - - @override - bool get isPaused => allPaused; - - @override - void pause([Future? resumeSignal]) => pauseAll(resumeSignal); - - @override - void resume() => resumeAll(); - - @override - Never asFuture([E? futureValue]) => _unsupportedError(); - - @override - Never onData(void Function(Never data)? handleData) => _unsupportedError(); - - @override - Never onDone(void Function()? handleDone) => _unsupportedError(); - - @override - Never onError(Function? handleError) => _unsupportedError(); - - Never _unsupportedError() => throw UnsupportedError( - 'Cannot change handlers of CompositeSubscription.'); -} - -/// Extends the [StreamSubscription] class with the ability to be added to [CompositeSubscription] container. -extension AddToCompositeSubscriptionExtension on StreamSubscription { - /// Adds this subscription to composite container for subscriptions. - void addTo(CompositeSubscription compositeSubscription) => - compositeSubscription.add(this); -} diff --git a/sandbox/reactivex/lib/src/utils/empty.dart b/sandbox/reactivex/lib/src/utils/empty.dart deleted file mode 100644 index 04a7766..0000000 --- a/sandbox/reactivex/lib/src/utils/empty.dart +++ /dev/null @@ -1,18 +0,0 @@ -class _Empty { - const _Empty(); - - @override - String toString() => '<>'; -} - -/// @internal -/// Sentinel object used to represent a missing value (distinct from `null`). -const Object? EMPTY = _Empty(); // ignore: constant_identifier_names - -/// @internal -/// Returns `null` if [o] is [EMPTY], otherwise returns itself. -T? unbox(Object? o) => identical(o, EMPTY) ? null : o as T; - -/// @internal -/// Returns `true` if [o] is not [EMPTY]. -bool isNotEmpty(Object? o) => !identical(o, EMPTY); diff --git a/sandbox/reactivex/lib/src/utils/error_and_stacktrace.dart b/sandbox/reactivex/lib/src/utils/error_and_stacktrace.dart deleted file mode 100644 index 33a68c9..0000000 --- a/sandbox/reactivex/lib/src/utils/error_and_stacktrace.dart +++ /dev/null @@ -1,28 +0,0 @@ -/// An Object which acts as a tuple containing both an error and the -/// corresponding stack trace. -class ErrorAndStackTrace { - /// A reference to the wrapped error object. - final Object error; - - /// A reference to the wrapped [StackTrace] - final StackTrace? stackTrace; - - /// Constructs an object containing both an [error] and the - /// corresponding [stackTrace]. - ErrorAndStackTrace(this.error, this.stackTrace); - - @override - String toString() => - 'ErrorAndStackTrace{error: $error, stackTrace: $stackTrace}'; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is ErrorAndStackTrace && - runtimeType == other.runtimeType && - error == other.error && - stackTrace == other.stackTrace; - - @override - int get hashCode => error.hashCode ^ stackTrace.hashCode; -} diff --git a/sandbox/reactivex/lib/src/utils/forwarding_sink.dart b/sandbox/reactivex/lib/src/utils/forwarding_sink.dart deleted file mode 100644 index 65adbd0..0000000 --- a/sandbox/reactivex/lib/src/utils/forwarding_sink.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/error_and_stacktrace.dart'; - -/// A enhanced [EventSink] that allows to check if the sink is paused. -abstract class EnhancedEventSink implements EventSink { - /// Whether the subscription would need to buffer events. - bool get isPaused; -} - -/// A [Sink] that supports event hooks. -/// -/// This makes it suitable for certain rx transformers that need to -/// take action after onListen, onPause, onResume or onCancel. -/// -/// The [ForwardingSink] has been designed to handle asynchronous events from -/// [Stream]s. See, for example, [Stream.eventTransformed] which uses -/// `EventSink`s to transform events. -abstract class ForwardingSink { - EnhancedEventSink? _sink; - StreamSubscription? _subscription; - - /// The output sink. - /// @nonVirtual - /// @internal - EnhancedEventSink get sink => - _sink ?? (throw StateError('Must call setSink(sink) before accessing!')); - - /// Set the output sink. - /// @nonVirtual - /// @internal - void setSink(EnhancedEventSink sink) => _sink = sink; - - /// Set the upstream subscription - /// @nonVirtual - /// @internal - void setSubscription(StreamSubscription? subscription) => - _subscription = subscription; - - /// -------------------------------------------------------------------------- - - /// Pause the upstream subscription. - /// @nonVirtual - void pauseSubscription() => _subscription?.pause(); - - /// Resume the upstream subscription. - /// @nonVirtual - void resumeSubscription() => _subscription?.resume(); - - /// -------------------------------------------------------------------------- - - /// Handle data event - void onData(T data); - - /// Handle error event - void onError(Object error, StackTrace st); - - /// Handle close event - void onDone(); - - /// Fires when a listener subscribes on the underlying [Stream]. - /// Returns a [Future] to delay listening to source [Stream]. - FutureOr onListen(); - - /// Fires when a subscriber pauses. - void onPause(); - - /// Fires when a subscriber resumes after a pause. - void onResume(); - - /// Fires when a subscriber cancels. - FutureOr onCancel(); -} - -/// @internal -/// @nodoc -extension EventSinkExtension on EventSink { - /// @internal - /// @nodoc - void addErrorAndStackTrace(ErrorAndStackTrace errorAndSt) => - addError(errorAndSt.error, errorAndSt.stackTrace); -} diff --git a/sandbox/reactivex/lib/src/utils/forwarding_stream.dart b/sandbox/reactivex/lib/src/utils/forwarding_stream.dart deleted file mode 100644 index 9d414f4..0000000 --- a/sandbox/reactivex/lib/src/utils/forwarding_stream.dart +++ /dev/null @@ -1,165 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/forwarding_sink.dart'; -import 'package:angel3_reactivex/src/utils/future.dart'; - -/// @private -/// Helper method which forwards the events from an incoming [Stream] -/// to a new [StreamController]. -/// It captures events such as onListen, onPause, onResume and onCancel, -/// which can be used in pair with a [ForwardingSink] -Stream forwardStream( - Stream stream, - ForwardingSink Function() sinkFactory, [ - bool listenOnlyOnce = false, -]) { - return stream.isBroadcast - ? listenOnlyOnce - ? _forward(stream, sinkFactory) - : _forwardMulti(stream, sinkFactory) - : _forward(stream, sinkFactory); -} - -Stream _forwardMulti( - Stream stream, ForwardingSink Function() sinkFactory) { - return Stream.multi((controller) { - final sink = sinkFactory(); - sink.setSink(_MultiControllerSink(controller)); - - StreamSubscription? subscription; - var cancelled = false; - - void listenToUpstream([void _]) { - if (cancelled) { - return; - } - subscription = stream.listen( - sink.onData, - onError: sink.onError, - onDone: sink.onDone, - ); - sink.setSubscription(subscription); - } - - final futureOrVoid = sink.onListen(); - if (futureOrVoid is Future) { - futureOrVoid.then(listenToUpstream).onError((e, s) { - if (!cancelled && !controller.isClosed) { - controller.addError(e, s); - controller.close(); - } - }); - } else { - listenToUpstream(); - } - - controller.onCancel = () { - cancelled = true; - - final future = subscription?.cancel(); - subscription = null; - sink.setSubscription(null); - - return waitTwoFutures(future, sink.onCancel()); - }; - }, isBroadcast: true); -} - -Stream _forward( - Stream stream, - ForwardingSink Function() sinkFactory, -) { - final controller = stream.isBroadcast - ? StreamController.broadcast(sync: true) - : StreamController(sync: true); - - StreamSubscription? subscription; - var cancelled = false; - late final sink = sinkFactory(); - - controller.onListen = () { - void listenToUpstream([void _]) { - if (cancelled) { - return; - } - subscription = stream.listen( - sink.onData, - onError: sink.onError, - onDone: sink.onDone, - ); - sink.setSubscription(subscription); - - if (!stream.isBroadcast) { - controller.onPause = () { - subscription!.pause(); - sink.onPause(); - }; - controller.onResume = () { - subscription!.resume(); - sink.onResume(); - }; - } - } - - sink.setSink(_EnhancedEventSink(controller)); - final futureOrVoid = sink.onListen(); - if (futureOrVoid is Future) { - futureOrVoid.then(listenToUpstream).onError((e, s) { - if (!cancelled && !controller.isClosed) { - controller.addError(e, s); - controller.close(); - } - }); - } else { - listenToUpstream(); - } - }; - controller.onCancel = () { - cancelled = true; - - final future = subscription?.cancel(); - subscription = null; - sink.setSubscription(null); - - return waitTwoFutures(future, sink.onCancel()); - }; - return controller.stream; -} - -class _MultiControllerSink implements EventSink, EnhancedEventSink { - final MultiStreamController controller; - - _MultiControllerSink(this.controller); - - @override - void add(T event) => controller.addSync(event); - - @override - void addError(Object error, [StackTrace? stackTrace]) => - controller.addErrorSync(error, stackTrace); - - @override - void close() => controller.closeSync(); - - @override - bool get isPaused => controller.isPaused; -} - -class _EnhancedEventSink implements EnhancedEventSink { - final StreamController _controller; - - _EnhancedEventSink(this._controller); - - @override - void add(T event) => _controller.add(event); - - @override - void addError(Object error, [StackTrace? stackTrace]) => - _controller.addError(error, stackTrace); - - @override - void close() => _controller.close(); - - @override - bool get isPaused => _controller.isPaused; -} diff --git a/sandbox/reactivex/lib/src/utils/future.dart b/sandbox/reactivex/lib/src/utils/future.dart deleted file mode 100644 index 77e241f..0000000 --- a/sandbox/reactivex/lib/src/utils/future.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:async'; - -/// @internal -/// An optimized version of [Future.wait]. -FutureOr waitTwoFutures(Future? f1, FutureOr f2) => f1 == null - ? f2 - : f2 is Future - ? Future.wait([f1, f2]).then(_ignore) - : f1; - -/// @internal -/// An optimized version of [Future.wait]. -Future? waitFuturesList(List> futures) { - switch (futures.length) { - case 0: - return null; - case 1: - return futures[0]; - default: - return Future.wait(futures).then(_ignore); - } -} - -/// Helper function to ignore future callback -void _ignore(Object? _) {} diff --git a/sandbox/reactivex/lib/src/utils/min_max.dart b/sandbox/reactivex/lib/src/utils/min_max.dart deleted file mode 100644 index 729d44c..0000000 --- a/sandbox/reactivex/lib/src/utils/min_max.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:async'; - -/// @private -/// Helper method which find max value or min value in a stream -/// -/// When the stream is done, the returned future is completed with -/// the largest value or smallest value at that time. -/// -/// If the stream is empty, the returned future is completed with -/// an error. -/// If the stream emits an error, or the call to [comparator] throws, -/// the returned future is completed with that error, -/// and processing is stopped. -Future minMax(Stream stream, bool findMin, Comparator? comparator) { - var completer = Completer(); - var seenFirst = false; - - late StreamSubscription subscription; - late T accumulator; - late Comparator comparatorNotNull; - - Future cancelAndCompleteError(Object e, StackTrace st) async { - await subscription.cancel(); - - completer.completeError(e, st); - } - - void onData(T element) async { - if (seenFirst) { - try { - accumulator = findMin - ? (comparatorNotNull(element, accumulator) < 0 - ? element - : accumulator) - : (comparatorNotNull(element, accumulator) > 0 - ? element - : accumulator); - } catch (e, st) { - await cancelAndCompleteError(e, st); - } - return; - } - - accumulator = element; - seenFirst = true; - try { - comparatorNotNull = comparator ?? - () { - if (element is Comparable) { - return Comparable.compare as Comparator; - } else { - throw StateError( - 'Please provide a comparator for type $T, because it is not comparable'); - } - }(); - } catch (e, st) { - await cancelAndCompleteError(e, st); - } - } - - void onDone() { - if (seenFirst) { - completer.complete(accumulator); - } else { - completer.completeError(StateError('No element')); - } - } - - subscription = stream.listen( - onData, - onError: completer.completeError, - onDone: onDone, - cancelOnError: true, - ); - return completer.future; -} diff --git a/sandbox/reactivex/lib/src/utils/notification.dart b/sandbox/reactivex/lib/src/utils/notification.dart deleted file mode 100644 index fec6172..0000000 --- a/sandbox/reactivex/lib/src/utils/notification.dart +++ /dev/null @@ -1,169 +0,0 @@ -import 'package:angel3_reactivex/src/utils/error_and_stacktrace.dart'; - -/// The type of event used in [StreamNotification] -enum NotificationKind { - /// Specifies a data event - data, - - /// Specifies a done event - done, - - /// Specifies an error event - error -} - -/// A class that encapsulates the [NotificationKind] of event, value of the event in case of -/// onData, or the Error in the case of onError. - -/// A container object that wraps the [NotificationKind] of event (OnData, OnDone, OnError), -/// and the item or error that was emitted. In the case of onDone, no data is -/// emitted as part of the [StreamNotification]. -abstract class StreamNotification { - /// References the [NotificationKind] of this [StreamNotification] event. - final NotificationKind kind; - - const StreamNotification._(this.kind); - - /// Constructs a [StreamNotification] with [NotificationKind.data] and wraps a [value] - factory StreamNotification.data(T value) => DataNotification(value); - - /// Constructs a [StreamNotification] with [NotificationKind.done]. - const factory StreamNotification.done() = DoneNotification; - - /// Constructs a [StreamNotification] with [NotificationKind.error] and wraps an [error] and [stackTrace] - factory StreamNotification.error(Object error, [StackTrace? stackTrace]) => - ErrorNotification._internal(error, stackTrace); -} - -/// Provides extension methods on [StreamNotification]. -extension StreamNotificationExtensions on StreamNotification { - /// A test to determine if this [StreamNotification] wraps a data event. - bool get isData => kind == NotificationKind.data; - - /// A test to determine if this [StreamNotification] wraps a done event. - bool get isDone => kind == NotificationKind.done; - - /// A test to determine if this [StreamNotification] wraps an error event. - bool get isError => kind == NotificationKind.error; - - /// Returns data if [kind] is [NotificationKind.data], - /// otherwise throws a [TypeError] error. - /// See also [dataValueOrNull]. - T get requireDataValue => (this as DataNotification).value; - - /// Returns data if [kind] is [NotificationKind.data], - /// otherwise returns null. - T? get dataValueOrNull { - final self = this; - return self is DataNotification ? self.value : null; - } - - /// Returns error and stack trace if [kind] is [NotificationKind.error], - /// otherwise throws a [TypeError] error. - ErrorAndStackTrace get requireErrorAndStackTrace => - (this as ErrorNotification).errorAndStackTrace; - - /// Returns error and stack trace if [kind] is [NotificationKind.error], - /// otherwise returns null. - ErrorAndStackTrace? get errorAndStackTraceOrNull { - final self = this; - return self is ErrorNotification ? self.errorAndStackTrace : null; - } - - /// Invokes the appropriate function on the [StreamNotification] based on the [kind]. - @pragma('vm:prefer-inline') - @pragma('dart2js:prefer-inline') - R when({ - required R Function(T value) data, - required R Function() done, - required R Function(ErrorAndStackTrace) error, - }) { - final self = this; - if (self is DataNotification) { - return data(self.value); - } - - if (self is DoneNotification) { - return done(); - } - - if (self is ErrorNotification) { - return error(self.errorAndStackTrace); - } - - throw StateError('Unknown notification $self'); - } -} - -/// A notification representing a data event from a [Stream]. -class DataNotification extends StreamNotification { - /// The value of the data event. - final T value; - - /// Constructs a [DataNotification] with the provided [value]. - const DataNotification(this.value) : super._(NotificationKind.data); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is DataNotification && - runtimeType == other.runtimeType && - value == other.value; - - @override - int get hashCode => value.hashCode; - - @override - String toString() => 'DataNotification{value: $value}'; -} - -/// A notification representing a done event from a [Stream]. -class DoneNotification extends StreamNotification { - /// Constructs a [DoneNotification]. - const DoneNotification() : super._(NotificationKind.done); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is DoneNotification && runtimeType == other.runtimeType; - - @override - int get hashCode => 0; - - @override - String toString() => 'DoneNotification{}'; -} - -/// A notification representing an error event from a [Stream]. -class ErrorNotification extends StreamNotification { - /// The wrapped error and stack trace, if applicable - final ErrorAndStackTrace errorAndStackTrace; - - /// The error of the error event. - Object get error => errorAndStackTrace.error; - - /// The stack trace of the error event, if available. - StackTrace? get stackTrace => errorAndStackTrace.stackTrace; - - /// Constructs an [ErrorNotification] with the provided [errorAndStackTrace]. - const ErrorNotification(this.errorAndStackTrace) - : super._(NotificationKind.error); - - /// Constructs an [ErrorNotification] with the provided [error] and [stackTrace]. - factory ErrorNotification._internal(Object error, StackTrace? stackTrace) => - ErrorNotification(ErrorAndStackTrace(error, stackTrace)); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is ErrorNotification && - runtimeType == other.runtimeType && - errorAndStackTrace == other.errorAndStackTrace; - - @override - int get hashCode => errorAndStackTrace.hashCode; - - @override - String toString() => - 'ErrorNotification{error: $error, stackTrace: $stackTrace}'; -} diff --git a/sandbox/reactivex/lib/src/utils/subscription.dart b/sandbox/reactivex/lib/src/utils/subscription.dart deleted file mode 100644 index d3500d9..0000000 --- a/sandbox/reactivex/lib/src/utils/subscription.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/utils/future.dart'; - -/// @internal -/// Extensions for [Iterable] of [StreamSubscription]s. -extension StreamSubscriptionsIterableExtensions - on Iterable> { - /// @internal - /// Pause all subscriptions. - void pauseAll([Future? resumeSignal]) { - for (final s in this) { - s.pause(resumeSignal); - } - } - - /// @internal - /// Resume all subscriptions. - void resumeAll() { - for (final s in this) { - s.resume(); - } - } -} - -/// @internal -/// Extensions for [Iterable] of [StreamSubscription]s. -extension StreamSubscriptionsIterableExtension - on Iterable> { - /// @internal - /// Cancel all subscriptions. - Future? cancelAll() => - waitFuturesList([for (final s in this) s.cancel()]); -} diff --git a/sandbox/reactivex/lib/streams.dart b/sandbox/reactivex/lib/streams.dart deleted file mode 100644 index 79f57b3..0000000 --- a/sandbox/reactivex/lib/streams.dart +++ /dev/null @@ -1,23 +0,0 @@ -library rx_streams; - -export 'src/streams/combine_latest.dart'; -export 'src/streams/concat.dart'; -export 'src/streams/concat_eager.dart'; -export 'src/streams/connectable_stream.dart'; -export 'src/streams/defer.dart'; -export 'src/streams/fork_join.dart'; -export 'src/streams/from_callable.dart'; -export 'src/streams/merge.dart'; -export 'src/streams/never.dart'; -export 'src/streams/race.dart'; -export 'src/streams/range.dart'; -export 'src/streams/repeat.dart'; -export 'src/streams/replay_stream.dart'; -export 'src/streams/retry.dart'; -export 'src/streams/retry_when.dart'; -export 'src/streams/sequence_equal.dart'; -export 'src/streams/switch_latest.dart'; -export 'src/streams/timer.dart'; -export 'src/streams/using.dart'; -export 'src/streams/value_stream.dart'; -export 'src/streams/zip.dart'; diff --git a/sandbox/reactivex/lib/subjects.dart b/sandbox/reactivex/lib/subjects.dart deleted file mode 100644 index 77bc472..0000000 --- a/sandbox/reactivex/lib/subjects.dart +++ /dev/null @@ -1,6 +0,0 @@ -library rx_subjects; - -export 'src/subjects/behavior_subject.dart'; -export 'src/subjects/publish_subject.dart'; -export 'src/subjects/replay_subject.dart'; -export 'src/subjects/subject.dart'; diff --git a/sandbox/reactivex/lib/transformers.dart b/sandbox/reactivex/lib/transformers.dart deleted file mode 100644 index e6ff971..0000000 --- a/sandbox/reactivex/lib/transformers.dart +++ /dev/null @@ -1,42 +0,0 @@ -library rx_transformers; - -export 'src/transformers/backpressure/buffer.dart'; -export 'src/transformers/backpressure/debounce.dart'; -export 'src/transformers/backpressure/pairwise.dart'; -export 'src/transformers/backpressure/sample.dart'; -export 'src/transformers/backpressure/throttle.dart'; -export 'src/transformers/backpressure/window.dart'; -export 'src/transformers/default_if_empty.dart'; -export 'src/transformers/delay.dart'; -export 'src/transformers/delay_when.dart'; -export 'src/transformers/dematerialize.dart'; -export 'src/transformers/distinct_unique.dart'; -export 'src/transformers/do.dart'; -export 'src/transformers/end_with.dart'; -export 'src/transformers/end_with_many.dart'; -export 'src/transformers/exhaust_map.dart'; -export 'src/transformers/flat_map.dart'; -export 'src/transformers/group_by.dart'; -export 'src/transformers/ignore_elements.dart'; -export 'src/transformers/interval.dart'; -export 'src/transformers/map_not_null.dart'; -export 'src/transformers/map_to.dart'; -export 'src/transformers/materialize.dart'; -export 'src/transformers/max.dart'; -export 'src/transformers/min.dart'; -export 'src/transformers/on_error_resume.dart'; -export 'src/transformers/scan.dart'; -export 'src/transformers/skip_last.dart'; -export 'src/transformers/skip_until.dart'; -export 'src/transformers/start_with.dart'; -export 'src/transformers/start_with_many.dart'; -export 'src/transformers/switch_if_empty.dart'; -export 'src/transformers/switch_map.dart'; -export 'src/transformers/take_last.dart'; -export 'src/transformers/take_until.dart'; -export 'src/transformers/take_while_inclusive.dart'; -export 'src/transformers/time_interval.dart'; -export 'src/transformers/timestamp.dart'; -export 'src/transformers/where_not_null.dart'; -export 'src/transformers/where_type.dart'; -export 'src/transformers/with_latest_from.dart'; diff --git a/sandbox/reactivex/lib/utils.dart b/sandbox/reactivex/lib/utils.dart deleted file mode 100644 index 5892516..0000000 --- a/sandbox/reactivex/lib/utils.dart +++ /dev/null @@ -1,5 +0,0 @@ -library rx_utils; - -export 'src/utils/composite_subscription.dart'; -export 'src/utils/error_and_stacktrace.dart'; -export 'src/utils/notification.dart'; diff --git a/sandbox/reactivex/pubspec.yaml b/sandbox/reactivex/pubspec.yaml deleted file mode 100644 index e54c0bf..0000000 --- a/sandbox/reactivex/pubspec.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: angel3_reactivex -version: 0.28.0 -description: > - angel3_reactivex is an implementation of the popular ReactiveX api for asynchronous - programming, leveraging the native Dart Streams api. -repository: https://github.com/ReactiveX/angel3_reactivex - -topics: - - angel3_reactivex - - reactive-programming - - streams - - observables - - rx - -environment: - sdk: '>=2.12.0 <4.0.0' - -dev_dependencies: - lints: ^1.0.1 - stack_trace: ^1.10.0 - test: ^1.17.12 - -screenshots: - - description: The angel3_reactivex package logo. - path: screenshots/logo.png diff --git a/sandbox/reactivex/screenshots/logo.png b/sandbox/reactivex/screenshots/logo.png deleted file mode 100644 index 1ba0f82..0000000 Binary files a/sandbox/reactivex/screenshots/logo.png and /dev/null differ diff --git a/sandbox/reactivex/test/rxdart_test.dart b/sandbox/reactivex/test/rxdart_test.dart deleted file mode 100644 index ef41ddc..0000000 --- a/sandbox/reactivex/test/rxdart_test.dart +++ /dev/null @@ -1,187 +0,0 @@ -library test.rx; - -import 'streams/combine_latest_test.dart' as combine_latest_test; -import 'streams/concat_eager_test.dart' as concat_eager_test; -import 'streams/concat_test.dart' as concat_test; -import 'streams/defer_test.dart' as defer_test; -import 'streams/fork_join_test.dart' as fork_join_test; -import 'streams/from_callable_test.dart' as from_callable_test; -import 'streams/merge_test.dart' as merge_test; -import 'streams/never_test.dart' as never_test; -import 'streams/publish_connectable_stream_test.dart' - as publish_connectable_stream_test; -import 'streams/race_test.dart' as race_test; -import 'streams/range_test.dart' as range_test; -import 'streams/repeat_test.dart' as repeat_test; -import 'streams/replay_connectable_stream_test.dart' - as replay_connectable_stream_test; -import 'streams/retry_test.dart' as retry_test; -import 'streams/retry_when_test.dart' as retry_when_test; -import 'streams/sequence_equals_test.dart' as sequence_equals_test; -import 'streams/switch_latest_test.dart' as switch_latest_test; -import 'streams/timer_test.dart' as timer_test; -import 'streams/using_test.dart' as using_test; -import 'streams/value_connectable_stream_test.dart' - as value_connectable_stream_test; -import 'streams/zip_test.dart' as zip_test; -import 'subject/behavior_subject_test.dart' as behaviour_subject_test; -import 'subject/publish_subject_test.dart' as publish_subject_test; -import 'subject/replay_subject_test.dart' as replay_subject_test; -import 'transformers/backpressure/buffer_count_test.dart' as buffer_count_test; -import 'transformers/backpressure/buffer_test.dart' as buffer_test; -import 'transformers/backpressure/buffer_test_test.dart' as buffer_test_test; -import 'transformers/backpressure/buffer_time_test.dart' as buffer_time_test; -import 'transformers/backpressure/debounce_test.dart' as debounce_test; -import 'transformers/backpressure/debounce_time_test.dart' - as debounce_time_test; -import 'transformers/backpressure/pairwise_test.dart' as pairwise_test; -import 'transformers/backpressure/sample_test.dart' as sample_test; -import 'transformers/backpressure/sample_time_test.dart' as sample_time_test; -import 'transformers/backpressure/throttle_test.dart' as throttle_test; -import 'transformers/backpressure/throttle_time_test.dart' - as throttle_time_test; -import 'transformers/backpressure/window_count_test.dart' as window_count_test; -import 'transformers/backpressure/window_test.dart' as window_test; -import 'transformers/backpressure/window_test_test.dart' as window_test_test; -import 'transformers/backpressure/window_time_test.dart' as window_time_test; -import 'transformers/concat_with_test.dart' as concat_with_test; -import 'transformers/default_if_empty_test.dart' as default_if_empty_test; -import 'transformers/delay_test.dart' as delay_test; -import 'transformers/delay_when_test.dart' as delay_when_test; -import 'transformers/dematerialize_test.dart' as dematerialize_test; -import 'transformers/distinct_test.dart' as distinct_test; -import 'transformers/distinct_unique_test.dart' as distinct_unique_test; -import 'transformers/do_test.dart' as do_test; -import 'transformers/end_with_many_test.dart' as end_with_many_test; -import 'transformers/end_with_test.dart' as end_with_test; -import 'transformers/exhaust_map_test.dart' as exhaust_map_test; -import 'transformers/flat_map_iterable_test.dart' as flat_map_iterable_test; -import 'transformers/flat_map_test.dart' as flat_map_test; -import 'transformers/group_by_test.dart' as group_by_test; -import 'transformers/ignore_elements_test.dart' as ignore_elements_test; -import 'transformers/interval_test.dart' as interval_test; -import 'transformers/join_test.dart' as join_test; -import 'transformers/map_not_null_test.dart' as map_not_null_test; -import 'transformers/map_to_test.dart' as map_to_test; -import 'transformers/materialize_test.dart' as materialize_test; -import 'transformers/merge_with_test.dart' as merge_with_test; -import 'transformers/on_error_return_test.dart' as on_error_resume_test; -import 'transformers/on_error_return_test.dart' as on_error_return_test; -import 'transformers/on_error_return_with_test.dart' - as on_error_return_with_test; -import 'transformers/scan_test.dart' as scan_test; -import 'transformers/skip_last_test.dart' as skip_last_test; -import 'transformers/skip_until_test.dart' as skip_until_test; -import 'transformers/start_with_many_test.dart' as start_with_many_test; -import 'transformers/start_with_test.dart' as start_with_test; -import 'transformers/switch_if_empty_test.dart' as switch_if_empty_test; -import 'transformers/switch_map_test.dart' as switch_map_test; -import 'transformers/take_last_test.dart' as take_last_test; -import 'transformers/take_until_test.dart' as take_until_test; -import 'transformers/take_while_inclusive_test.dart' - as take_while_inclusive_test; -import 'transformers/time_interval_test.dart' as time_interval_test; -import 'transformers/timeout_test.dart' as timeout_test; -import 'transformers/timestamp_test.dart' as timestamp_test; -import 'transformers/where_not_null_test.dart' as where_not_null_test; -import 'transformers/where_type_test.dart' as where_type_test; -import 'transformers/with_latest_from_test.dart' as with_latest_from_test; -import 'transformers/zip_with_test.dart' as zip_with_test; -import 'utils/composite_subscription_test.dart' as composite_subscription_test; -import 'utils/notification_test.dart' as notification_test; - -void main() { - // Streams - combine_latest_test.main(); - concat_eager_test.main(); - concat_test.main(); - defer_test.main(); - fork_join_test.main(); - from_callable_test.main(); - merge_test.main(); - never_test.main(); - range_test.main(); - race_test.main(); - repeat_test.main(); - retry_test.main(); - retry_when_test.main(); - sequence_equals_test.main(); - switch_latest_test.main(); - using_test.main(); - zip_test.main(); - - // StreamTransformers - concat_with_test.main(); - default_if_empty_test.main(); - delay_test.main(); - delay_when_test.main(); - dematerialize_test.main(); - distinct_test.main(); - distinct_unique_test.main(); - do_test.main(); - end_with_test.main(); - end_with_many_test.main(); - exhaust_map_test.main(); - flat_map_test.main(); - flat_map_iterable_test.main(); - group_by_test.main(); - ignore_elements_test.main(); - interval_test.main(); - join_test.main(); - map_not_null_test.main(); - map_to_test.main(); - materialize_test.main(); - merge_with_test.main(); - on_error_resume_test.main(); - on_error_return_test.main(); - on_error_return_with_test.main(); - scan_test.main(); - skip_last_test.main(); - skip_until_test.main(); - start_with_many_test.main(); - start_with_test.main(); - switch_if_empty_test.main(); - switch_map_test.main(); - take_last_test.main(); - take_until_test.main(); - take_while_inclusive_test.main(); - time_interval_test.main(); - timeout_test.main(); - timestamp_test.main(); - timer_test.main(); - where_not_null_test.main(); - where_type_test.main(); - with_latest_from_test.main(); - zip_with_test.main(); - - // Backpressure - buffer_test.main(); - buffer_count_test.main(); - buffer_test_test.main(); - buffer_time_test.main(); - debounce_test.main(); - debounce_time_test.main(); - pairwise_test.main(); - sample_test.main(); - sample_time_test.main(); - throttle_test.main(); - throttle_time_test.main(); - window_test.main(); - window_count_test.main(); - window_test_test.main(); - window_time_test.main(); - - // Subjects - behaviour_subject_test.main(); - publish_subject_test.main(); - replay_subject_test.main(); - - // Connectable Streams - value_connectable_stream_test.main(); - replay_connectable_stream_test.main(); - publish_connectable_stream_test.main(); - - // Utilities - composite_subscription_test.main(); - notification_test.main(); -} diff --git a/sandbox/reactivex/test/streams/combine_latest_test.dart b/sandbox/reactivex/test/streams/combine_latest_test.dart deleted file mode 100644 index bd03e09..0000000 --- a/sandbox/reactivex/test/streams/combine_latest_test.dart +++ /dev/null @@ -1,394 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -Stream get streamA => - Stream.periodic(const Duration(milliseconds: 1), (int count) => count) - .take(3); - -Stream get streamB => Stream.fromIterable(const [1, 2, 3, 4]); - -Stream get streamC { - final controller = StreamController() - ..add(true) - ..close(); - - return controller.stream; -} - -void main() { - test('Rx.combineLatestList', () async { - final combined = Rx.combineLatestList([ - Stream.fromIterable([1, 2, 3]), - Stream.value(2), - Stream.value(3), - ]); - - expect( - combined, - emitsInOrder([ - [1, 2, 3], - [2, 2, 3], - [3, 2, 3], - ]), - ); - }); - - test('Rx.combineLatestList.iterate.once', () async { - var iterationCount = 0; - - final combined = Rx.combineLatestList(() sync* { - ++iterationCount; - yield Stream.value(1); - yield Stream.value(2); - yield Stream.value(3); - }()); - - await expectLater( - combined, - emitsInOrder([ - [1, 2, 3], - emitsDone, - ]), - ); - expect(iterationCount, 1); - }); - - test('Rx.combineLatestList.empty', () async { - final combined = Rx.combineLatestList([]); - expect(combined, emitsDone); - }); - - test('Rx.combineLatest', () async { - final combined = Rx.combineLatest( - [ - Stream.fromIterable([1, 2, 3]), - Stream.value(2), - Stream.value(3), - ], - (values) => values.fold(0, (acc, val) => acc + val), - ); - - expect( - combined, - emitsInOrder([6, 7, 8]), - ); - }); - - test('Rx.combineLatest3', () async { - const expectedOutput = ['0 4 true', '1 4 true', '2 4 true']; - var count = 0; - - final stream = Rx.combineLatest3(streamA, streamB, streamC, - (int aValue, int bValue, bool cValue) { - return '$aValue $bValue $cValue'; - }); - - stream.listen(expectAsync1((result) { - // test to see if the combined output matches - expect(result.compareTo(expectedOutput[count++]), 0); - }, count: 3)); - }); - - test('Rx.combineLatest3.single.subscription', () async { - final stream = Rx.combineLatest3(streamA, streamB, streamC, - (int aValue, int bValue, bool cValue) { - return '$aValue $bValue $cValue'; - }); - - stream.listen(null); - await expectLater(() => stream.listen((_) {}), throwsA(isStateError)); - }); - - test('Rx.combineLatest2', () async { - const expected = [ - [1, 2], - [2, 2] - ]; - var count = 0; - - var a = Stream.fromIterable(const [1, 2]), b = Stream.value(2); - - final stream = - Rx.combineLatest2(a, b, (int first, int second) => [first, second]); - - stream.listen(expectAsync1((result) { - expect(result, expected[count++]); - }, count: expected.length)); - }); - - test('Rx.combineLatest2.throws', () async { - var a = Stream.value(1), b = Stream.value(2); - - final stream = Rx.combineLatest2(a, b, (int first, int second) { - throw Exception(); - }); - - stream.listen(null, onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.combineLatest3', () async { - const expected = [1, '2', 3.0]; - - var a = Stream.value(1), - b = Stream.value('2'), - c = Stream.value(3.0); - - final stream = Rx.combineLatest3(a, b, c, - (int first, String second, double third) => [first, second, third]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.combineLatest4', () async { - const expected = [1, 2, 3, 4]; - - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4); - - final stream = Rx.combineLatest4( - a, - b, - c, - d, - (int first, int second, int third, int fourth) => - [first, second, third, fourth]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.combineLatest5', () async { - const expected = [1, 2, 3, 4, 5]; - - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5); - - final stream = Rx.combineLatest5( - a, - b, - c, - d, - e, - (int first, int second, int third, int fourth, int fifth) => - [first, second, third, fourth, fifth]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.combineLatest6', () async { - const expected = [1, 2, 3, 4, 5, 6]; - - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6); - - final stream = Rx.combineLatest6( - a, - b, - c, - d, - e, - f, - (int first, int second, int third, int fourth, int fifth, int sixth) => - [first, second, third, fourth, fifth, sixth]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.combineLatest7', () async { - const expected = [1, 2, 3, 4, 5, 6, 7]; - - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6), - g = Stream.value(7); - - final stream = Rx.combineLatest7( - a, - b, - c, - d, - e, - f, - g, - (int first, int second, int third, int fourth, int fifth, int sixth, - int seventh) => - [first, second, third, fourth, fifth, sixth, seventh]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.combineLatest8', () async { - const expected = [1, 2, 3, 4, 5, 6, 7, 8]; - - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6), - g = Stream.value(7), - h = Stream.value(8); - - final stream = Rx.combineLatest8( - a, - b, - c, - d, - e, - f, - g, - h, - (int first, int second, int third, int fourth, int fifth, int sixth, - int seventh, int eighth) => - [first, second, third, fourth, fifth, sixth, seventh, eighth]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.combineLatest9', () async { - const expected = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6), - g = Stream.value(7), - h = Stream.value(8), - i = Stream.value(9); - - final stream = Rx.combineLatest9( - a, - b, - c, - d, - e, - f, - g, - h, - i, - (int first, int second, int third, int fourth, int fifth, int sixth, - int seventh, int eighth, int ninth) => - [ - first, - second, - third, - fourth, - fifth, - sixth, - seventh, - eighth, - ninth - ]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.combineLatest.asBroadcastStream', () async { - final stream = Rx.combineLatest3(streamA, streamB, streamC, - (int aValue, int bValue, bool cValue) { - return '$aValue $bValue $cValue'; - }).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.combineLatest.error.shouldThrowA', () async { - final streamWithError = Rx.combineLatest4(Stream.value(1), Stream.value(1), - Stream.value(1), Stream.error(Exception()), - (int aValue, int bValue, int cValue, dynamic _) { - return '$aValue $bValue $cValue $_'; - }); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.combineLatest.error.shouldThrowB', () async { - final streamWithError = - Rx.combineLatest3(Stream.value(1), Stream.value(1), Stream.value(1), - (int aValue, int bValue, int cValue) { - throw Exception('oh noes!'); - }); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - /*test('Rx.combineLatest.error.shouldThrowC', () { - expect( - () => Rx.combineLatest3(Stream.value(1), - Stream.just(1), Stream.value(1), null), - throwsArgumentError); - }); - - test('Rx.combineLatest.error.shouldThrowD', () { - expect(() => CombineLatestStream(null, null), throwsArgumentError); - }); - - test('Rx.combineLatest.error.shouldThrowE', () { - expect(() => CombineLatestStream(>[], null), - throwsArgumentError); - });*/ - - test('Rx.combineLatest.pause.resume', () async { - final first = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [1, 2, 3, 4][index]), - second = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [5, 6, 7, 8][index]), - last = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [9, 10, 11, 12][index]); - - late StreamSubscription> subscription; - // ignore: deprecated_member_use - subscription = Rx.combineLatest3( - first, second, last, (int a, int b, int c) => [a, b, c]) - .listen(expectAsync1((value) { - expect(value.elementAt(0), 1); - expect(value.elementAt(1), 5); - expect(value.elementAt(2), 9); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(Future.delayed(const Duration(milliseconds: 80))); - }); -} diff --git a/sandbox/reactivex/test/streams/concat_eager_test.dart b/sandbox/reactivex/test/streams/concat_eager_test.dart deleted file mode 100644 index bb77624..0000000 --- a/sandbox/reactivex/test/streams/concat_eager_test.dart +++ /dev/null @@ -1,185 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -List> _getStreams() { - var a = Stream.fromIterable(const [0, 1, 2]), - b = Stream.fromIterable(const [3, 4, 5]); - - return [a, b]; -} - -List> _getStreamsIncludingEmpty() { - var a = Stream.fromIterable(const [0, 1, 2]), - b = Stream.fromIterable(const [3, 4, 5]), - c = Stream.empty(); - - return [c, a, b]; -} - -void main() { - test('Rx.concatEager', () async { - const expectedOutput = [0, 1, 2, 3, 4, 5]; - var count = 0; - - final stream = Rx.concatEager(_getStreams()); - - stream.listen(expectAsync1((result) { - // test to see if the combined output matches - expect(result, expectedOutput[count++]); - }, count: expectedOutput.length)); - }); - - test('Rx.concatEager.single', () async { - final stream = Rx.concatEager([ - Stream.fromIterable([1, 2, 3, 4, 5]) - ]); - - await expectLater(stream, emitsInOrder([1, 2, 3, 4, 5, emitsDone])); - }); - - test('Rx.concatEager.eagerlySubscription', () async { - var subscribed2 = false; - var subscribed3 = false; - - final stream = Rx.concatEager([ - Rx.timer(1, Duration(milliseconds: 100)).doOnDone( - expectAsync0(() => expect(subscribed2 && subscribed3, true))), - Rx.timer([2, 3, 4], Duration(milliseconds: 100)) - .exhaustMap((v) => Stream.fromIterable(v)) - .doOnListen(() => subscribed2 = true) - .doOnDone(expectAsync0(() => expect(subscribed3, true))), - Rx.timer(5, Duration(milliseconds: 100)) - .doOnListen(() => subscribed3 = true), - ]); - - await expectLater( - stream, - emitsInOrder([ - 1, - 2, - 3, - 4, - 5, - emitsDone, - ]), - ); - }); - - test('Rx.concatEager.single.subscription', () async { - final stream = Rx.concatEager(_getStreams()); - - stream.listen(null); - await expectLater(() => stream.listen((_) {}), throwsA(isStateError)); - }); - - test('Rx.concatEager.withEmptyStream', () async { - const expectedOutput = [0, 1, 2, 3, 4, 5]; - var count = 0; - - final stream = Rx.concatEager(_getStreamsIncludingEmpty()); - - stream.listen(expectAsync1((result) { - // test to see if the combined output matches - expect(result, expectedOutput[count++]); - }, count: expectedOutput.length)); - }); - - test('Rx.concatEager.withBroadcastStreams', () async { - const expectedOutput = [1, 2, 3, 4, 99, 98, 97, 96, 999, 998, 997]; - final ctrlA = StreamController.broadcast(), - ctrlB = StreamController.broadcast(), - ctrlC = StreamController.broadcast(); - var x = 0, y = 100, z = 1000, count = 0; - - Timer.periodic(const Duration(milliseconds: 10), (_) { - ctrlA.add(++x); - ctrlB.add(--y); - - if (x <= 3) ctrlC.add(--z); - - if (x == 3) ctrlC.close(); - - if (x == 4) { - _.cancel(); - - ctrlA.close(); - ctrlB.close(); - } - }); - - final stream = Rx.concatEager([ctrlA.stream, ctrlB.stream, ctrlC.stream]); - - stream.listen(expectAsync1((result) { - // test to see if the combined output matches - expect(result, expectedOutput[count++]); - }, count: expectedOutput.length)); - }); - - test('Rx.concatEager.asBroadcastStream', () async { - final stream = Rx.concatEager(_getStreams()).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.concatEager.error.shouldThrowA', () async { - final streamWithError = - Rx.concatEager(_getStreams()..add(Stream.error(Exception()))); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.concatEager.pause.resume', () async { - final first = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [1, 2, 3, 4][index]), - second = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [5, 6, 7, 8][index]), - last = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [9, 10, 11, 12][index]); - - late StreamSubscription subscription; - // ignore: deprecated_member_use - subscription = - Rx.concatEager([first, second, last]).listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(Future.delayed(const Duration(milliseconds: 80))); - }); - - test('Rx.concatEager.empty', () { - expect(Rx.concatEager(const []), emitsDone); - }); - - test('Rx.concatEager.iterate.once', () async { - var iterationCount = 0; - - final stream = Rx.concatEager(() sync* { - ++iterationCount; - yield Stream.value(1); - yield Stream.value(2); - yield Stream.value(3); - }()); - - await expectLater( - stream, - emitsInOrder([ - 1, - 2, - 3, - emitsDone, - ]), - ); - expect(iterationCount, 1); - }); -} diff --git a/sandbox/reactivex/test/streams/concat_test.dart b/sandbox/reactivex/test/streams/concat_test.dart deleted file mode 100644 index daacc24..0000000 --- a/sandbox/reactivex/test/streams/concat_test.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -List> _getStreams() { - var a = Stream.fromIterable(const [0, 1, 2]), - b = Stream.fromIterable(const [3, 4, 5]); - - return [a, b]; -} - -List> _getStreamsIncludingEmpty() { - var a = Stream.fromIterable(const [0, 1, 2]), - b = Stream.fromIterable(const [3, 4, 5]), - c = Stream.empty(); - - return [c, a, b]; -} - -void main() { - test('Rx.concat', () async { - const expectedOutput = [0, 1, 2, 3, 4, 5]; - var count = 0; - - final stream = Rx.concat(_getStreams()); - - stream.listen(expectAsync1((result) { - // test to see if the combined output matches - expect(result, expectedOutput[count++]); - }, count: expectedOutput.length)); - }); - - test('Rx.concatEager.single.subscription', () async { - final stream = Rx.concat(_getStreams()); - - stream.listen(null); - await expectLater(() => stream.listen(null), throwsA(isStateError)); - }); - - test('Rx.concat.withEmptyStream', () async { - const expectedOutput = [0, 1, 2, 3, 4, 5]; - var count = 0; - - final stream = Rx.concat(_getStreamsIncludingEmpty()); - - stream.listen(expectAsync1((result) { - // test to see if the combined output matches - expect(result, expectedOutput[count++]); - }, count: expectedOutput.length)); - }); - - test('Rx.concat.withBroadcastStreams', () async { - const expectedOutput = [1, 2, 3, 4]; - final ctrlA = StreamController.broadcast(), - ctrlB = StreamController.broadcast(), - ctrlC = StreamController.broadcast(); - var x = 0, y = 100, z = 1000, count = 0; - - Timer.periodic(const Duration(milliseconds: 1), (_) { - ctrlA.add(++x); - ctrlB.add(--y); - - if (x <= 3) ctrlC.add(--z); - - if (x == 3) ctrlC.close(); - - if (x == 4) { - _.cancel(); - - ctrlA.close(); - ctrlB.close(); - } - }); - - final stream = Rx.concat([ctrlA.stream, ctrlB.stream, ctrlC.stream]); - - stream.listen(expectAsync1((result) { - // test to see if the combined output matches - expect(result, expectedOutput[count++]); - }, count: expectedOutput.length)); - }); - - test('Rx.concat.asBroadcastStream', () async { - final stream = Rx.concat(_getStreams()).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.concat.error.shouldThrowA', () async { - final streamWithError = - Rx.concat(_getStreams()..add(Stream.error(Exception()))); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.concat.empty', () { - expect(Rx.concat(const []), emitsDone); - }); - - test('Rx.concat.single', () { - expect( - Rx.concat([Stream.value(1)]), - emitsInOrder([1, emitsDone]), - ); - }); - - test('Rx.concat.iterate.once', () async { - var iterationCount = 0; - - final stream = Rx.concat(() sync* { - ++iterationCount; - yield Stream.value(1); - yield Stream.value(2); - yield Stream.value(3); - }()); - - await expectLater( - stream, - emitsInOrder([ - 1, - 2, - 3, - emitsDone, - ]), - ); - expect(iterationCount, 1); - }); -} diff --git a/sandbox/reactivex/test/streams/defer_test.dart b/sandbox/reactivex/test/streams/defer_test.dart deleted file mode 100644 index 5ff00cc..0000000 --- a/sandbox/reactivex/test/streams/defer_test.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.defer', () async { - const value = 1; - - final stream = _getDeferStream(); - - stream.listen(expectAsync1((actual) { - expect(actual, value); - }, count: 1)); - }); - - test('Rx.defer.multiple.listeners', () async { - const value = 1; - - final stream = _getBroadcastDeferStream(); - - stream.listen(expectAsync1((actual) { - expect(actual, value); - }, count: 1)); - - stream.listen(expectAsync1((actual) { - expect(actual, value); - }, count: 1)); - }); - - test('Rx.defer.streamFactory.called', () async { - var count = 0; - - Stream streamFactory() { - ++count; - return Stream.value(1); - } - - var deferStream = DeferStream( - streamFactory, - reusable: false, - ); - - expect(count, 0); - - deferStream.listen( - expectAsync1((_) { - expect(count, 1); - }), - ); - }); - - test('Rx.defer.reusable', () async { - const value = 1; - - final stream = Rx.defer( - () => Stream.fromFuture( - Future.delayed( - Duration(seconds: 1), - () => value, - ), - ), - reusable: true, - ); - - stream.listen( - expectAsync1( - (actual) => expect(actual, value), - count: 1, - ), - ); - stream.listen( - expectAsync1( - (actual) => expect(actual, value), - count: 1, - ), - ); - }); - - test('Rx.defer.single.subscription', () async { - final stream = _getDeferStream(); - - try { - stream.listen(null); - stream.listen(null); - expect(true, false); - } catch (e) { - expect(e, isStateError); - } - }); - - test('Rx.defer.error.shouldThrow.A', () async { - final streamWithError = Rx.defer(() => _getErroneousStream()); - - streamWithError.listen(null, - onError: expectAsync1((Exception e) { - expect(e, isException); - }, count: 1)); - }); - - test('Rx.defer.error.shouldThrow.B', () { - final deferStream1 = Rx.defer(() => throw Exception()); - expect( - deferStream1, - emitsInOrder([emitsError(isException), emitsDone]), - ); - - final deferStream2 = Rx.defer(() => throw Exception(), reusable: true); - expect( - deferStream2, - emitsInOrder([emitsError(isException), emitsDone]), - ); - }); -} - -Stream _getDeferStream() => Rx.defer(() => Stream.value(1)); - -Stream _getBroadcastDeferStream() => - Rx.defer(() => Stream.value(1)).asBroadcastStream(); - -Stream _getErroneousStream() { - final controller = StreamController(); - - controller.addError(Exception()); - controller.close(); - - return controller.stream; -} diff --git a/sandbox/reactivex/test/streams/fork_join_test.dart b/sandbox/reactivex/test/streams/fork_join_test.dart deleted file mode 100644 index 5708581..0000000 --- a/sandbox/reactivex/test/streams/fork_join_test.dart +++ /dev/null @@ -1,452 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -Stream get streamA => - Stream.periodic(const Duration(milliseconds: 1), (int count) => count) - .take(3); - -Stream get streamB => Stream.fromIterable(const [1, 2, 3, 4]); - -Stream get streamC { - final controller = StreamController() - ..add(true) - ..close(); - - return controller.stream; -} - -void main() { - test('Rx.forkJoinList', () async { - final combined = Rx.forkJoinList([ - Stream.fromIterable([1, 2, 3]), - Stream.value(2), - Stream.value(3), - ]); - - await expectLater( - combined, - emitsInOrder([ - [3, 2, 3], - emitsDone - ]), - ); - }); - - test('Rx.forkJoin.nullable', () { - expect( - ForkJoinStream.join2( - Stream.value(null), - Stream.value(1), - (a, b) => '$a $b', - ), - emitsInOrder([ - 'null 1', - emitsDone, - ]), - ); - }); - - test('Rx.forkJoin.iterate.once', () async { - var iterationCount = 0; - - final stream = Rx.forkJoinList(() sync* { - ++iterationCount; - yield Stream.value(1); - yield Stream.value(2); - yield Stream.value(3); - }()); - - await expectLater( - stream, - emitsInOrder([ - [1, 2, 3], - emitsDone, - ]), - ); - expect(iterationCount, 1); - }); - - test('Rx.forkJoin.empty', () { - expect(Rx.forkJoinList([]), emitsDone); - }); - - test('Rx.forkJoinList.singleStream', () async { - final combined = Rx.forkJoinList([ - Stream.fromIterable([1, 2, 3]) - ]); - - await expectLater( - combined, - emitsInOrder([ - [3], - emitsDone - ]), - ); - }); - - test('Rx.forkJoin', () async { - final combined = Rx.forkJoin( - [ - Stream.fromIterable([1, 2, 3]), - Stream.value(2), - Stream.value(3), - ], - (values) => values.fold(0, (acc, val) => acc + val), - ); - - await expectLater( - combined, - emitsInOrder([8, emitsDone]), - ); - }); - - test('Rx.forkJoin3', () async { - final stream = Rx.forkJoin3(streamA, streamB, streamC, - (int aValue, int bValue, bool cValue) => '$aValue $bValue $cValue'); - - await expectLater(stream, emitsInOrder(['2 4 true', emitsDone])); - }); - - test('Rx.forkJoin3.single.subscription', () async { - final stream = Rx.forkJoin3(streamA, streamB, streamC, - (int aValue, int bValue, bool cValue) => '$aValue $bValue $cValue'); - - await expectLater( - stream, - emitsInOrder(['2 4 true', emitsDone]), - ); - await expectLater(() => stream.listen(null), throwsA(isStateError)); - }); - - test('Rx.forkJoin2', () async { - var a = Stream.fromIterable(const [1, 2]), b = Stream.value(2); - - final stream = - Rx.forkJoin2(a, b, (int first, int second) => [first, second]); - - await expectLater( - stream, - emitsInOrder([ - [2, 2], - emitsDone - ])); - }); - - test('Rx.forkJoin2.throws', () async { - var a = Stream.value(1), b = Stream.value(2); - - final stream = Rx.forkJoin2(a, b, (int first, int second) { - throw Exception(); - }); - - stream.listen(null, onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.forkJoin3', () async { - var a = Stream.value(1), - b = Stream.value('2'), - c = Stream.value(3.0); - - final stream = Rx.forkJoin3(a, b, c, - (int first, String second, double third) => [first, second, third]); - - await expectLater( - stream, - emitsInOrder([ - const [1, '2', 3.0], - emitsDone - ])); - }); - - test('Rx.forkJoin4', () async { - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4); - - final stream = Rx.forkJoin4( - a, - b, - c, - d, - (int first, int second, int third, int fourth) => - [first, second, third, fourth]); - - await expectLater( - stream, - emitsInOrder([ - const [1, 2, 3, 4], - emitsDone - ])); - }); - - test('Rx.forkJoin5', () async { - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5); - - final stream = Rx.forkJoin5( - a, - b, - c, - d, - e, - (int first, int second, int third, int fourth, int fifth) => - [first, second, third, fourth, fifth]); - - await expectLater( - stream, - emitsInOrder([ - const [1, 2, 3, 4, 5], - emitsDone - ])); - }); - - test('Rx.forkJoin6', () async { - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6); - - final stream = Rx.combineLatest6( - a, - b, - c, - d, - e, - f, - (int first, int second, int third, int fourth, int fifth, int sixth) => - [first, second, third, fourth, fifth, sixth]); - - await expectLater( - stream, - emitsInOrder([ - const [1, 2, 3, 4, 5, 6], - emitsDone - ])); - }); - - test('Rx.forkJoin7', () async { - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6), - g = Stream.value(7); - - final stream = Rx.forkJoin7( - a, - b, - c, - d, - e, - f, - g, - (int first, int second, int third, int fourth, int fifth, int sixth, - int seventh) => - [first, second, third, fourth, fifth, sixth, seventh]); - - await expectLater( - stream, - emitsInOrder([ - const [1, 2, 3, 4, 5, 6, 7], - emitsDone - ])); - }); - - test('Rx.forkJoin8', () async { - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6), - g = Stream.value(7), - h = Stream.value(8); - - final stream = Rx.forkJoin8( - a, - b, - c, - d, - e, - f, - g, - h, - (int first, int second, int third, int fourth, int fifth, int sixth, - int seventh, int eighth) => - [first, second, third, fourth, fifth, sixth, seventh, eighth]); - - await expectLater( - stream, - emitsInOrder([ - const [1, 2, 3, 4, 5, 6, 7, 8], - emitsDone - ])); - }); - - test('Rx.forkJoin9', () async { - var a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6), - g = Stream.value(7), - h = Stream.value(8), - i = Stream.value(9); - - final stream = Rx.forkJoin9( - a, - b, - c, - d, - e, - f, - g, - h, - i, - (int first, int second, int third, int fourth, int fifth, int sixth, - int seventh, int eighth, int ninth) => - [ - first, - second, - third, - fourth, - fifth, - sixth, - seventh, - eighth, - ninth - ]); - - await expectLater( - stream, - emitsInOrder([ - const [1, 2, 3, 4, 5, 6, 7, 8, 9], - emitsDone - ])); - }); - - test('Rx.forkJoin.asBroadcastStream', () async { - final stream = Rx.forkJoin3(streamA, streamB, streamC, - (int aValue, int bValue, bool cValue) => '$aValue $bValue $cValue') - .asBroadcastStream(); - -// listen twice on same stream - stream.listen(null); - stream.listen(null); -// code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.forkJoin.error.shouldThrowA', () async { - final streamWithError = Rx.forkJoin4( - Stream.value(1), - Stream.value(1), - Stream.value(1), - Stream.error(Exception()), - (int aValue, int bValue, int cValue, dynamic _) => - '$aValue $bValue $cValue $_'); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - }), cancelOnError: true); - }); - - test('Rx.forkJoin.error.shouldThrowB', () async { - final streamWithError = - Rx.forkJoin3(Stream.value(1), Stream.value(1), Stream.value(1), - (int aValue, int bValue, int cValue) { - throw Exception('oh noes!'); - }); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.forkJoin.pause.resume', () async { - final first = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [1, 2, 3, 4][index]).take(4), - second = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [5, 6, 7, 8][index]).take(4), - last = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [9, 10, 11, 12][index]).take(4); - - late StreamSubscription> subscription; - subscription = - Rx.forkJoin3(first, second, last, (int a, int b, int c) => [a, b, c]) - .listen(expectAsync1((value) { - expect(value.elementAt(0), 4); - expect(value.elementAt(1), 8); - expect(value.elementAt(2), 12); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(Future.delayed(const Duration(milliseconds: 80))); - }); - - test('Rx.forkJoin.completed', () async { - final stream = Rx.forkJoin2( - Stream.empty(), - Stream.value(1), - (int a, int b) => a + b, - ); - await expectLater( - stream, - emitsInOrder([emitsError(isStateError), emitsDone]), - ); - }); - - test('Rx.forkJoin.error.shouldThrowC', () async { - final stream = Rx.forkJoin2( - Stream.value(1), - Stream.error(Exception()).concatWith([ - Rx.timer( - 2, - const Duration(milliseconds: 100), - ) - ]), - (int a, int b) => a + b, - ); - await expectLater( - stream, - emitsInOrder([emitsError(isException), 3, emitsDone]), - ); - }); - - test('Rx.forkJoin.error.shouldThrowD', () async { - final stream = Rx.forkJoin2( - Stream.value(1), - Stream.error(Exception()).concatWith([ - Rx.timer( - 2, - const Duration(milliseconds: 100), - ) - ]), - (int a, int b) => a + b, - ); - - stream.listen( - expectAsync1((value) {}, count: 0), - onError: expectAsync2( - (Object e, StackTrace s) => expect(e, isException), - count: 1, - ), - cancelOnError: true, - ); - }); -} diff --git a/sandbox/reactivex/test/streams/from_callable_test.dart b/sandbox/reactivex/test/streams/from_callable_test.dart deleted file mode 100644 index 69b7dca..0000000 --- a/sandbox/reactivex/test/streams/from_callable_test.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.fromCallable.sync', () { - var called = false; - - var stream = Rx.fromCallable(() { - called = true; - return 2; - }); - - expect(called, false); - expectLater(stream, emitsInOrder([2, emitsDone])); - expect(called, true); - }); - - test('Rx.fromCallable.async', () { - var called = false; - - var stream = FromCallableStream(() async { - called = true; - await Future.delayed(const Duration(milliseconds: 10)); - return 2; - }); - - expect(called, false); - expectLater(stream, emitsInOrder([2, emitsDone])); - expect(called, true); - }); - - test('Rx.fromCallable.reusable', () { - var stream = Rx.fromCallable(() => 2, reusable: true); - expect(stream.isBroadcast, isTrue); - - stream.listen(null); - stream.listen(null); - - expect(true, true); - }); - - test('Rx.fromCallable.singleSubscription', () { - { - var stream = Rx.fromCallable(() => - Future.delayed(const Duration(milliseconds: 10), () => 'Value')); - - expect(stream.isBroadcast, isFalse); - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - } - - { - var stream = Rx.fromCallable(() => Future.error(Exception())); - - expect(stream.isBroadcast, isFalse); - stream.listen(null, onError: (Object e) {}); - expect( - () => stream.listen(null, onError: (Object e) {}), throwsStateError); - } - }); - - test('Rx.fromCallable.asBroadcastStream', () async { - final stream = Rx.fromCallable(() => 2).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.fromCallable.sync.shouldThrow', () { - var stream = Rx.fromCallable(() => throw Exception()); - - expectLater( - stream, - emitsInOrder([emitsError(isException), emitsDone]), - ); - }); - - test('Rx.fromCallable.async.shouldThrow', () { - { - var stream = Rx.fromCallable(() async => throw Exception()); - - expectLater( - stream, - emitsInOrder([emitsError(isException), emitsDone]), - ); - } - - { - var stream = Rx.fromCallable(() => Future.error(Exception())); - - expectLater( - stream, - emitsInOrder([emitsError(isException), emitsDone]), - ); - } - }); - - test('Rx.fromCallable.sync.pause.resume', () { - var stream = Rx.fromCallable(() => 'Value'); - - stream - .listen( - expectAsync1( - (v) => expect(v, 'Value'), - count: 1, - ), - ) - .pause(Future.delayed(const Duration(milliseconds: 50))); - }); - - test('Rx.fromCallable.async.pause.resume', () { - var stream = Rx.fromCallable(() async { - await Future.delayed(const Duration(milliseconds: 10)); - return 'Value'; - }); - - stream - .listen( - expectAsync1( - (v) => expect(v, 'Value'), - count: 1, - ), - ) - .pause(Future.delayed(const Duration(milliseconds: 50))); - }); -} diff --git a/sandbox/reactivex/test/streams/merge_test.dart b/sandbox/reactivex/test/streams/merge_test.dart deleted file mode 100644 index b70975e..0000000 --- a/sandbox/reactivex/test/streams/merge_test.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -List> _getStreams() { - var a = Stream.periodic(const Duration(milliseconds: 1), (count) => count) - .take(3), - b = Stream.fromIterable(const [1, 2, 3, 4]); - - return [a, b]; -} - -void main() { - test('Rx.merge', () async { - final stream = Rx.merge(_getStreams()); - - await expectLater(stream, emitsInOrder(const [1, 2, 3, 4, 0, 1, 2])); - }); - - test('Rx.merge.iterate.once', () async { - var iterationCount = 0; - - final stream = Rx.merge(() sync* { - ++iterationCount; - yield Stream.value(1); - yield Stream.value(2); - yield Stream.value(3); - }()); - - await expectLater( - stream, - emitsInOrder([ - 1, - 2, - 3, - emitsDone, - ]), - ); - expect(iterationCount, 1); - }); - - test('Rx.merge.single.subscription', () async { - final stream = Rx.merge(_getStreams()); - - stream.listen(null); - await expectLater(() => stream.listen(null), throwsA(isStateError)); - }); - - test('Rx.merge.asBroadcastStream', () async { - final stream = Rx.merge(_getStreams()).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.merge.error.shouldThrowA', () async { - final streamWithError = - Rx.merge(_getStreams()..add(Stream.error(Exception()))); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.merge.pause.resume', () async { - final first = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [1, 2, 3, 4][index]), - second = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [5, 6, 7, 8][index]), - last = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [9, 10, 11, 12][index]); - - late StreamSubscription subscription; - // ignore: deprecated_member_use - subscription = Rx.merge([first, second, last]).listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(Future.delayed(const Duration(milliseconds: 80))); - }); - - test('Rx.merge.empty', () { - expect(Rx.merge(const []), emitsDone); - }); -} diff --git a/sandbox/reactivex/test/streams/never_test.dart b/sandbox/reactivex/test/streams/never_test.dart deleted file mode 100644 index 4254963..0000000 --- a/sandbox/reactivex/test/streams/never_test.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('NeverStream', () async { - var onDataCalled = false, onDoneCalled = false, onErrorCalled = false; - - final stream = NeverStream(); - - final subscription = stream.listen( - expectAsync1((_) { - onDataCalled = true; - }, count: 0), - onError: expectAsync2((Exception e, StackTrace s) { - onErrorCalled = false; - }, count: 0), - onDone: expectAsync0(() { - onDataCalled = true; - }, count: 0)); - - await Future.delayed(Duration(milliseconds: 10)); - - await subscription.cancel(); - - // We do not expect onData, onDone, nor onError to be called, as [never] - // streams emit no items or errors, and they do not terminate - await expectLater(onDataCalled, isFalse); - await expectLater(onDoneCalled, isFalse); - await expectLater(onErrorCalled, isFalse); - }); - - test('NeverStream.single.subscription', () async { - final stream = NeverStream(); - - stream.listen(null); - await expectLater(() => stream.listen(null), throwsA(isStateError)); - }); - - test('Rx.never', () async { - var onDataCalled = false, onDoneCalled = false, onErrorCalled = false; - - final stream = Rx.never(); - - final subscription = stream.listen( - expectAsync1((_) { - onDataCalled = true; - }, count: 0), - onError: expectAsync2((Exception e, StackTrace s) { - onErrorCalled = false; - }, count: 0), - onDone: expectAsync0(() { - onDataCalled = true; - }, count: 0)); - - await Future.delayed(Duration(milliseconds: 10)); - - await subscription.cancel(); - - // We do not expect onData, onDone, nor onError to be called, as [never] - // streams emit no items or errors, and they do not terminate - await expectLater(onDataCalled, isFalse); - await expectLater(onDoneCalled, isFalse); - await expectLater(onErrorCalled, isFalse); - }); -} diff --git a/sandbox/reactivex/test/streams/publish_connectable_stream_test.dart b/sandbox/reactivex/test/streams/publish_connectable_stream_test.dart deleted file mode 100644 index 4e1e793..0000000 --- a/sandbox/reactivex/test/streams/publish_connectable_stream_test.dart +++ /dev/null @@ -1,164 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -class MockStream extends Stream { - final Stream stream; - var listenCount = 0; - - MockStream(this.stream); - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - ++listenCount; - return stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } -} - -void main() { - group('PublishConnectableStream', () { - test('should not emit before connecting', () { - final stream = MockStream(Stream.fromIterable(const [1, 2, 3])); - final connectableStream = PublishConnectableStream(stream); - - expect(stream.listenCount, 0); - connectableStream.connect(); - expect(stream.listenCount, 1); - }); - - test('should begin emitting items after connection', () { - final ConnectableStream stream = PublishConnectableStream( - Stream.fromIterable([1, 2, 3])); - - stream.connect(); - - expect(stream, emitsInOrder([1, 2, 3])); - }); - - test('stops emitting after the connection is cancelled', () async { - final ConnectableStream stream = - Stream.fromIterable([1, 2, 3]).publishValue(); - - stream.connect().cancel(); // ignore: unawaited_futures - - expect(stream, neverEmits(anything)); - }); - - test('multicasts a single-subscription stream', () async { - final stream = PublishConnectableStream( - Stream.fromIterable(const [1, 2, 3]), - ).autoConnect(); - - expect(stream, emitsInOrder([1, 2, 3])); - expect(stream, emitsInOrder([1, 2, 3])); - expect(stream, emitsInOrder([1, 2, 3])); - }); - - test('can multicast streams', () async { - final stream = Stream.fromIterable(const [1, 2, 3]).publish(); - - stream.connect(); - - expect(stream, emitsInOrder([1, 2, 3])); - expect(stream, emitsInOrder([1, 2, 3])); - expect(stream, emitsInOrder([1, 2, 3])); - }); - - test('refcount automatically connects', () async { - final stream = Stream.fromIterable(const [1, 2, 3]).share(); - - expect(stream, emitsInOrder(const [1, 2, 3])); - expect(stream, emitsInOrder(const [1, 2, 3])); - expect(stream, emitsInOrder(const [1, 2, 3])); - }); - - test('provide a function to autoconnect that stops listening', () async { - final stream = Stream.fromIterable(const [1, 2, 3]) - .publish() - .autoConnect(connection: (subscription) => subscription.cancel()); - - expect(await stream.isEmpty, true); - }); - - test('refCount cancels source subscription when no listeners remain', - () async { - var isCanceled = false; - - final controller = - StreamController(onCancel: () => isCanceled = true); - final stream = controller.stream.share(); - - StreamSubscription subscription; - subscription = stream.listen(null); - - await subscription.cancel(); - expect(isCanceled, true); - }); - - test('can close share() stream', () async { - final isCanceled = Completer(); - - final controller = StreamController(); - controller.stream - .share() - .doOnCancel(() => isCanceled.complete()) - .listen(null); - - controller.add(true); - await Future.delayed(Duration.zero); - await controller.close(); - - expect(isCanceled.future, completes); - }); - - test( - 'throws StateError when mixing autoConnect, connect and refCount together', - () { - PublishConnectableStream stream() => Stream.value(1).publish(); - - expect( - () => stream() - ..autoConnect() - ..connect(), - throwsStateError, - ); - expect( - () => stream() - ..autoConnect() - ..refCount(), - throwsStateError, - ); - expect( - () => stream() - ..connect() - ..refCount(), - throwsStateError, - ); - }); - - test('calling autoConnect() multiple times returns the same value', () { - final s = Stream.value(1).publish(); - expect(s.autoConnect(), same(s.autoConnect())); - expect(s.autoConnect(), same(s.autoConnect())); - }); - - test('calling connect() multiple times returns the same value', () { - final s = Stream.value(1).publish(); - expect(s.connect(), same(s.connect())); - expect(s.connect(), same(s.connect())); - }); - - test('calling refCount() multiple times returns the same value', () { - final s = Stream.value(1).publish(); - expect(s.refCount(), same(s.refCount())); - expect(s.refCount(), same(s.refCount())); - }); - }); -} diff --git a/sandbox/reactivex/test/streams/race_test.dart b/sandbox/reactivex/test/streams/race_test.dart deleted file mode 100644 index ef932e4..0000000 --- a/sandbox/reactivex/test/streams/race_test.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -Stream getDelayedStream(int delay, int value) async* { - final completer = Completer(); - - Timer(Duration(milliseconds: delay), () => completer.complete()); - - await completer.future; - - yield value; - yield value + 1; - yield value + 2; -} - -void main() { - test('Rx.race', () async { - final first = getDelayedStream(50, 1), - second = getDelayedStream(60, 2), - last = getDelayedStream(70, 3); - var expected = 1; - - Rx.race([first, second, last]).listen(expectAsync1((result) { - // test to see if the combined output matches - expect(result.compareTo(expected++), 0); - }, count: 3)); - }); - - test('Rx.race.iterate.once', () async { - var iterationCount = 0; - - final stream = Rx.race(() sync* { - ++iterationCount; - yield Stream.value(1); - yield Stream.value(2); - yield Stream.value(3); - }()); - - await expectLater( - stream, - emitsInOrder([1, emitsDone]), - ); - expect(iterationCount, 1); - }); - - test('Rx.race.single.subscription', () async { - final first = getDelayedStream(50, 1); - - final stream = Rx.race([first]); - - stream.listen(null); - await expectLater(() => stream.listen(null), throwsA(isStateError)); - }); - - test('Rx.race.asBroadcastStream', () async { - final first = getDelayedStream(50, 1), - second = getDelayedStream(60, 2), - last = getDelayedStream(70, 3); - - final stream = Rx.race([first, second, last]).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.race.shouldThrowB', () async { - final stream = Rx.race([Stream.error(Exception('oh noes!'))]); - - // listen twice on same stream - stream.listen(null, - onError: expectAsync2( - (Exception e, StackTrace s) => expect(e, isException))); - }); - - test('Rx.race.pause.resume', () async { - final first = getDelayedStream(50, 1), - second = getDelayedStream(60, 2), - last = getDelayedStream(70, 3); - - late StreamSubscription subscription; - // ignore: deprecated_member_use - subscription = Rx.race([first, second, last]).listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(Future.delayed(const Duration(milliseconds: 80))); - }); - - test('Rx.race.empty', () { - expect(Rx.race(const []), emitsDone); - }); - - test('Rx.race.single', () { - expect( - Rx.race([Stream.value(1)]), - emitsInOrder([ - 1, - emitsDone, - ]), - ); - }); - - test('Rx.race.cancel.throws', () async { - Stream stream() { - final controller = StreamController(); - controller.onCancel = () async { - throw Exception('Exception when cancelling!'); - }; - - return Rx.race([ - controller.stream, - Rx.concat([ - Rx.timer(1, const Duration(milliseconds: 100)), - Rx.timer(2, const Duration(milliseconds: 100)), - ]), - ]); - } - - await expectLater( - stream(), - emitsInOrder([1, emitsError(isException), 2, emitsDone]), - ); - - await expectLater( - stream().take(1), - emitsInOrder([1, emitsDone]), - ); - }); -} diff --git a/sandbox/reactivex/test/streams/range_test.dart b/sandbox/reactivex/test/streams/range_test.dart deleted file mode 100644 index e57739f..0000000 --- a/sandbox/reactivex/test/streams/range_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('RangeStream', () async { - final expected = const [1, 2, 3]; - var count = 0; - - final stream = RangeStream(1, 3); - - stream.listen(expectAsync1((actual) { - expect(actual, expected[count++]); - }, count: expected.length)); - }); - - test('RangeStream.single.subscription', () async { - final stream = RangeStream(1, 5); - - stream.listen(null); - await expectLater(() => stream.listen(null), throwsA(isStateError)); - }); - - test('RangeStream.single', () async { - final stream = RangeStream(1, 1); - - stream.listen(expectAsync1((actual) { - expect(actual, 1); - }, count: 1)); - }); - - test('RangeStream.reverse', () async { - final expected = const [3, 2, 1]; - var count = 0; - - final stream = RangeStream(3, 1); - - stream.listen(expectAsync1((actual) { - expect(actual, expected[count++]); - }, count: expected.length)); - }); - - test('Rx.range', () async { - final expected = const [1, 2, 3]; - var count = 0; - - final stream = Rx.range(1, 3); - - stream.listen(expectAsync1((actual) { - expect(actual, expected[count++]); - }, count: expected.length)); - }); -} diff --git a/sandbox/reactivex/test/streams/repeat_test.dart b/sandbox/reactivex/test/streams/repeat_test.dart deleted file mode 100644 index f16196f..0000000 --- a/sandbox/reactivex/test/streams/repeat_test.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.repeat', () async { - const retries = 3; - - await expectLater(Rx.repeat(_getRepeatStream('A'), retries), - emitsInOrder(['A0', 'A1', 'A2', emitsDone])); - }); - - test('RepeatStream', () async { - const retries = 3; - - await expectLater(RepeatStream(_getRepeatStream('A'), retries), - emitsInOrder(['A0', 'A1', 'A2', emitsDone])); - }); - - test('RepeatStream.onDone', () async { - const retries = 0; - - await expectLater(RepeatStream(_getRepeatStream('A'), retries), emitsDone); - }); - - test('RepeatStream.infinite.repeats', () async { - await expectLater( - RepeatStream(_getRepeatStream('A')), emitsThrough('A100')); - }); - - test('RepeatStream.single.subscription', () async { - const retries = 3; - - final stream = RepeatStream(_getRepeatStream('A'), retries); - - try { - stream.listen(null); - stream.listen(null); - } catch (e) { - await expectLater(e, isStateError); - } - }); - - test('RepeatStream.asBroadcastStream', () async { - const retries = 3; - - final stream = - RepeatStream(_getRepeatStream('A'), retries).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('RepeatStream.error.shouldThrow', () async { - final streamWithError = RepeatStream(_getErroneusRepeatStream('A'), 2); - - await expectLater( - streamWithError, - emitsInOrder([ - 'A0', - emitsError(TypeMatcher()), - 'A0', - emitsError(TypeMatcher()), - emitsDone - ])); - }); - - test('RepeatStream.pause.resume', () async { - late StreamSubscription subscription; - const retries = 3; - - subscription = RepeatStream(_getRepeatStream('A'), retries) - .listen(expectAsync1((result) { - expect(result, 'A0'); - - subscription.cancel(); - })); - - subscription.pause(); - subscription.resume(); - }); -} - -Stream Function(int) _getRepeatStream(String symbol) => - (int repeatIndex) async* { - yield await Future.delayed( - const Duration(milliseconds: 20), () => '$symbol$repeatIndex'); - }; - -Stream Function(int) _getErroneusRepeatStream(String symbol) => - (int repeatIndex) { - return Stream.value('A0') - // Emit the error - .concatWith([Stream.error(Error())]); - }; diff --git a/sandbox/reactivex/test/streams/replay_connectable_stream_test.dart b/sandbox/reactivex/test/streams/replay_connectable_stream_test.dart deleted file mode 100644 index 54b1527..0000000 --- a/sandbox/reactivex/test/streams/replay_connectable_stream_test.dart +++ /dev/null @@ -1,229 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -class MockStream extends Stream { - final Stream stream; - var listenCount = 0; - - MockStream(this.stream); - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - ++listenCount; - return stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } -} - -void main() { - group('ReplayConnectableStream', () { - test('should not emit before connecting', () { - final stream = MockStream(Stream.fromIterable(const [1, 2, 3])); - final connectableStream = ReplayConnectableStream(stream); - - expect(stream.listenCount, 0); - connectableStream.connect(); - expect(stream.listenCount, 1); - }); - - test('should begin emitting items after connection', () { - const items = [1, 2, 3]; - final stream = ReplayConnectableStream(Stream.fromIterable(items)); - - stream.connect(); - - expect(stream, emitsInOrder(items)); - stream.listen(expectAsync1((int i) { - expect(stream.values, items.sublist(0, i)); - }, count: items.length)); - }); - - test('stops emitting after the connection is cancelled', () async { - final ConnectableStream stream = - Stream.fromIterable([1, 2, 3]).publishReplay(); - - stream.connect().cancel(); // ignore: unawaited_futures - - expect(stream, neverEmits(anything)); - }); - - test('stops emitting after the last subscriber unsubscribes', () async { - final Stream stream = - Stream.fromIterable([1, 2, 3]).shareReplay(); - - stream.listen(null).cancel(); // ignore: unawaited_futures - - expect(stream, neverEmits(anything)); - }); - - test('keeps emitting with an active subscription', () async { - final Stream stream = - Stream.fromIterable([1, 2, 3]).shareReplay(); - - stream.listen(null); - stream.listen(null).cancel(); // ignore: unawaited_futures - - expect(stream, emitsInOrder([1, 2, 3])); - }); - - test('multicasts a single-subscription stream', () async { - final Stream stream = ReplayConnectableStream( - Stream.fromIterable([1, 2, 3]), - ).autoConnect(); - - expect(stream, emitsInOrder([1, 2, 3])); - expect(stream, emitsInOrder([1, 2, 3])); - expect(stream, emitsInOrder([1, 2, 3])); - }); - - test('replays the max number of items', () async { - final Stream stream = ReplayConnectableStream( - Stream.fromIterable([1, 2, 3]), - maxSize: 2, - ).autoConnect(); - - expect(stream, emitsInOrder([1, 2, 3])); - expect(stream, emitsInOrder([1, 2, 3])); - expect(stream, emitsInOrder([1, 2, 3])); - - await Future.delayed(Duration(milliseconds: 200)); - - expect(stream, emitsInOrder([2, 3])); - }); - - test('can multicast streams', () async { - final stream = Stream.fromIterable(const [1, 2, 3]).shareReplay(); - - expect(stream, emitsInOrder(const [1, 2, 3])); - expect(stream, emitsInOrder(const [1, 2, 3])); - expect(stream, emitsInOrder(const [1, 2, 3])); - }); - - test('only holds a certain number of values', () async { - final stream = Stream.fromIterable(const [1, 2, 3]).shareReplay(); - - expect(stream.values, const []); - expect(stream, emitsInOrder(const [1, 2, 3])); - }); - - test('provides access to all items', () async { - const items = [1, 2, 3]; - var count = 0; - final stream = Stream.fromIterable(const [1, 2, 3]).shareReplay(); - - stream.listen(expectAsync1((int data) { - expect(data, items[count]); - count++; - if (count == items.length) { - expect(stream.values, items); - } - }, count: items.length)); - }); - - test('provides access to a certain number of items', () async { - const items = [1, 2, 3]; - var count = 0; - final stream = - Stream.fromIterable(const [1, 2, 3]).shareReplay(maxSize: 2); - - stream.listen(expectAsync1((data) { - expect(data, items[count]); - count++; - if (count == items.length) { - expect(stream.values, const [2, 3]); - } - }, count: items.length)); - }); - - test('provide a function to autoconnect that stops listening', () async { - final stream = Stream.fromIterable(const [1, 2, 3]) - .publishReplay() - .autoConnect(connection: (subscription) => subscription.cancel()); - - expect(await stream.isEmpty, true); - }); - - test('refCount cancels source subscription when no listeners remain', - () async { - var isCanceled = false; - - final controller = - StreamController(onCancel: () => isCanceled = true); - final stream = controller.stream.shareReplay(); - - StreamSubscription subscription; - subscription = stream.listen(null); - - await subscription.cancel(); - expect(isCanceled, true); - }); - - test('can close shareReplay() stream', () async { - final isCanceled = Completer(); - - final controller = StreamController(); - controller.stream - .shareReplay() - .doOnCancel(() => isCanceled.complete()) - .listen(null); - - controller.add(true); - await Future.delayed(Duration.zero); - await controller.close(); - - expect(isCanceled.future, completes); - }); - - test( - 'throws StateError when mixing autoConnect, connect and refCount together', - () { - ReplayConnectableStream stream() => - Stream.value(1).publishReplay(maxSize: 1); - - expect( - () => stream() - ..autoConnect() - ..connect(), - throwsStateError, - ); - expect( - () => stream() - ..autoConnect() - ..refCount(), - throwsStateError, - ); - - expect( - () => stream() - ..connect() - ..refCount(), - throwsStateError, - ); - }); - - test('calling autoConnect() multiple times returns the same value', () { - final s = Stream.value(1).publishReplay(maxSize: 1); - expect(s.autoConnect(), same(s.autoConnect())); - expect(s.autoConnect(), same(s.autoConnect())); - }); - - test('calling connect() multiple times returns the same value', () { - final s = Stream.value(1).publishReplay(maxSize: 1); - expect(s.connect(), same(s.connect())); - expect(s.connect(), same(s.connect())); - }); - - test('calling refCount() multiple times returns the same value', () { - final s = Stream.value(1).publishReplay(maxSize: 1); - expect(s.refCount(), same(s.refCount())); - expect(s.refCount(), same(s.refCount())); - }); - }); -} diff --git a/sandbox/reactivex/test/streams/retry_test.dart b/sandbox/reactivex/test/streams/retry_test.dart deleted file mode 100644 index 10ee4fd..0000000 --- a/sandbox/reactivex/test/streams/retry_test.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.retry', () async { - const retries = 3; - - await expectLater(Rx.retry(_getRetryStream(retries), retries), - emitsInOrder([1, emitsDone])); - }); - - test('RetryStream', () async { - const retries = 3; - - await expectLater(RetryStream(_getRetryStream(retries), retries), - emitsInOrder([1, emitsDone])); - }); - - test('RetryStream.onDone', () async { - const retries = 3; - - await expectLater(RetryStream(_getRetryStream(retries), retries), - emitsInOrder([1, emitsDone])); - }); - - test('RetryStream.infinite.retries', () async { - await expectLater(RetryStream(_getRetryStream(1000)), - emitsInOrder([1, emitsDone])); - }); - - test('RetryStream.emits.original.items', () async { - const retries = 3; - - await expectLater(RetryStream(_getStreamWithExtras(retries), retries), - emitsInOrder([1, 1, 1, 2, emitsDone])); - }); - - test('RetryStream.single.subscription', () async { - const retries = 3; - - final stream = RetryStream(_getRetryStream(retries), retries); - - try { - stream.listen(null); - stream.listen(null); - } catch (e) { - await expectLater(e, isStateError); - } - }); - - test('RetryStream.asBroadcastStream', () async { - const retries = 3; - - final stream = - RetryStream(_getRetryStream(retries), retries).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('RetryStream.error.shouldThrow', () async { - final streamWithError = RetryStream(_getRetryStream(3), 2); - - await expectLater( - streamWithError, - emitsInOrder( - [ - emitsError(isA()), - emitsError(isA()), - emitsError(isA()), - emitsDone, - ], - ), - ); - }); - - test('RetryStream.error.capturesErrors', () { - RetryStream(_getRetryStream(3), 2).listen( - expectAsync1((_) {}, count: 0), - onError: expectAsync2( - (Object e, StackTrace st) { - expect(e, isA()); - expect(st, isNotNull); - }, - count: 3, - ), - onDone: expectAsync0(() {}, count: 1), - ); - }); - - test('RetryStream.pause.resume', () async { - late StreamSubscription subscription; - const retries = 3; - - subscription = RetryStream(_getRetryStream(retries), retries) - .listen(expectAsync1((result) { - expect(result, 1); - - subscription.cancel(); - })); - - subscription.pause(); - subscription.resume(); - }); -} - -Stream Function() _getRetryStream(int failCount) { - var count = 0; - - return () { - if (count < failCount) { - count++; - return Stream.error(Error(), StackTrace.fromString('S')); - } else { - return Stream.value(1); - } - }; -} - -Stream Function() _getStreamWithExtras(int failCount) { - var count = 0; - - return () { - if (count < failCount) { - count++; - - // Emit first item - return Stream.value(1) - // Emit the error - .concatWith([Stream.error(Error())]) - // Emit an extra item, testing that it is not included - .concatWith([Stream.value(1)]); - } else { - return Stream.value(2); - } - }; -} diff --git a/sandbox/reactivex/test/streams/retry_when_test.dart b/sandbox/reactivex/test/streams/retry_when_test.dart deleted file mode 100644 index 3d139b6..0000000 --- a/sandbox/reactivex/test/streams/retry_when_test.dart +++ /dev/null @@ -1,224 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.retryWhen', () { - expect( - Rx.retryWhen(_sourceStream(3), _alwaysThrow), - emitsInOrder([0, 1, 2, emitsDone]), - ); - }); - - test('RetryWhenStream', () { - expect( - RetryWhenStream(_sourceStream(3), _alwaysThrow), - emitsInOrder([0, 1, 2, emitsDone]), - ); - }); - - test('RetryWhenStream.onDone', () { - expect( - RetryWhenStream(_sourceStream(3), _alwaysThrow), - emitsInOrder([0, 1, 2, emitsDone]), - ); - }); - - test('RetryWhenStream.infinite.retries', () { - expect( - RetryWhenStream(_sourceStream(1000, 2), _neverThrow).take(6), - emitsInOrder([0, 1, 0, 1, 0, 1, emitsDone]), - ); - }); - - test('RetryWhenStream.emits.original.items', () { - const retries = 3; - - expect( - RetryWhenStream(_getStreamWithExtras(retries), _neverThrow).take(6), - emitsInOrder([1, 1, 1, 2, emitsDone]), - ); - }); - - test('RetryWhenStream.single.subscription', () { - final stream = RetryWhenStream(_sourceStream(3), _neverThrow); - try { - stream.listen(null); - stream.listen(null); - } catch (e) { - expect(e, isStateError); - } - }); - - test('RetryWhenStream.asBroadcastStream', () { - final stream = - RetryWhenStream(_sourceStream(3), _neverThrow).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - expect(stream.isBroadcast, isTrue); - }); - - test('RetryWhenStream.error.shouldThrow', () { - final streamWithError = RetryWhenStream(_sourceStream(3, 0), _alwaysThrow); - - expect( - streamWithError, - emitsInOrder( - [ - emitsError(0), - emitsError(isA()), - emitsDone, - ], - ), - ); - }); - - test('RetryWhenStream.error.capturesErrors', () async { - final streamWithError = RetryWhenStream(_sourceStream(3, 0), _alwaysThrow); - - await expectLater( - streamWithError, - emitsInOrder([ - emitsError(0), - emitsError(isA()), - emitsDone, - ]), - ); - }); - - test('RetryWhenStream.pause.resume', () async { - late StreamSubscription subscription; - - subscription = RetryWhenStream(_sourceStream(3), _neverThrow) - .listen(expectAsync1((result) { - expect(result, 0); - - subscription.cancel(); - })); - - subscription.pause(); - subscription.resume(); - }); - - test('RetryWhenStream.cancel.ensureSubStreamCancels', () async { - var isCancelled = false, didStopEmitting = true; - Stream subStream(Object e, StackTrace s) => - Stream.periodic(const Duration(milliseconds: 100), (count) => count) - .doOnData((_) { - if (isCancelled) { - didStopEmitting = false; - } - }); - final subscription = - RetryWhenStream(_sourceStream(3, 0), subStream).listen(null); - - await Future.delayed(const Duration(milliseconds: 250)); - - await subscription.cancel(); - isCancelled = true; - - await Future.delayed(const Duration(milliseconds: 250)); - - expect(didStopEmitting, isTrue); - }); - - test('RetryWhenStream.retryStream.throws.originError', () { - final error = 1; - final stream = Rx.retryWhen( - _sourceStream(3, error), - (error, stackTrace) => Stream.error(error), - ); - expect( - stream, - emitsInOrder([ - 0, - emitsError(error), - emitsDone, - ]), - ); - }); - - test('RetryWhenStream.streamFactory.throws.originError', () { - final error = 1; - final stream = Rx.retryWhen( - _sourceStream(3, error), - (error, stackTrace) => throw error, - ); - expect( - stream, - emitsInOrder([ - 0, - emitsError(error), - emitsDone, - ]), - ); - }); -} - -Stream Function() _sourceStream(int i, [int? throwAt]) { - return throwAt == null - ? () => Stream.fromIterable(range(i)) - : () => - Stream.fromIterable(range(i)).map((i) => i == throwAt ? throw i : i); -} - -Stream _alwaysThrow(dynamic e, StackTrace s) => - Stream.error(Error(), StackTrace.fromString('S')); - -Stream _neverThrow(dynamic e, StackTrace s) => Stream.value(null); - -Stream Function() _getStreamWithExtras(int failCount) { - var count = 0; - - return () { - if (count < failCount) { - count++; - - // Emit first item - return Stream.value(1) - // Emit the error - .concatWith([Stream.error(Error())]) - // Emit an extra item, testing that it is not included - .concatWith([Stream.value(1)]); - } else { - return Stream.value(2); - } - }; -} - -/// Returns an [Iterable] sequence of [int]s. -/// -/// If only one argument is provided, [startOrStop] is the upper bound for -/// the sequence. If two or more arguments are provided, [stop] is the upper -/// bound. -/// -/// The sequence starts at 0 if one argument is provided, or [startOrStop] if -/// two or more arguments are provided. The sequence increments by 1, or [step] -/// if provided. [step] can be negative, in which case the sequence counts down -/// from the starting point and [stop] must be less than the starting point so -/// that it becomes the lower bound. -Iterable range(int startOrStop, [int? stop, int? step]) sync* { - final start = stop == null ? 0 : startOrStop; - stop ??= startOrStop; - step ??= 1; - - if (step == 0) throw ArgumentError('step cannot be 0'); - if (step > 0 && stop < start) { - throw ArgumentError('if step is positive,' - ' stop must be greater than start'); - } - if (step < 0 && stop > start) { - throw ArgumentError('if step is negative,' - ' stop must be less than start'); - } - - for (var value = start; - step < 0 ? value > stop : value < stop; - value += step) { - yield value; - } -} diff --git a/sandbox/reactivex/test/streams/sequence_equals_test.dart b/sandbox/reactivex/test/streams/sequence_equals_test.dart deleted file mode 100644 index 4cbc1d7..0000000 --- a/sandbox/reactivex/test/streams/sequence_equals_test.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.sequenceEqual.equals', () async { - final stream = Rx.sequenceEqual(Stream.fromIterable(const [1, 2, 3, 4, 5]), - Stream.fromIterable(const [1, 2, 3, 4, 5])); - - await expectLater(stream, emitsInOrder([true, emitsDone])); - }); - - test('Rx.sequenceEqual.diffTime.equals', () async { - final stream = Rx.sequenceEqual( - Stream.periodic(const Duration(milliseconds: 100), (i) => i + 1) - .take(5), - Stream.fromIterable(const [1, 2, 3, 4, 5])); - - await expectLater(stream, emitsInOrder([true, emitsDone])); - }); - - test('Rx.sequenceEqual.equals.customCompare.equals', () async { - final stream = Rx.sequenceEqual(Stream.fromIterable(const [1, 1, 1, 1, 1]), - Stream.fromIterable(const [2, 2, 2, 2, 2]), - equals: (int? a, int? b) => true); - - await expectLater(stream, emitsInOrder([true, emitsDone])); - }); - - test('Rx.sequenceEqual.diffTime.notEquals', () async { - final stream = Rx.sequenceEqual( - Stream.periodic(const Duration(milliseconds: 100), (i) => i + 1) - .take(5), - Stream.fromIterable(const [1, 1, 1, 1, 1])); - - await expectLater(stream, emitsInOrder([false, emitsDone])); - }); - - test('Rx.sequenceEqual.notEquals', () async { - final stream = Rx.sequenceEqual(Stream.fromIterable(const [1, 2, 3, 4, 5]), - Stream.fromIterable(const [1, 2, 3, 5, 4])); - - await expectLater(stream, emitsInOrder([false, emitsDone])); - }); - - test('Rx.sequenceEqual.equals.customCompare.notEquals', () async { - final stream = Rx.sequenceEqual(Stream.fromIterable(const [1, 1, 1, 1, 1]), - Stream.fromIterable(const [1, 1, 1, 1, 1]), - equals: (int? a, int? b) => false); - - await expectLater(stream, emitsInOrder([false, emitsDone])); - }); - - test('Rx.sequenceEqual.notEquals.differentLength', () async { - final stream = Rx.sequenceEqual(Stream.fromIterable(const [1, 2, 3, 4, 5]), - Stream.fromIterable(const [1, 2, 3, 4, 5, 6])); - - await expectLater(stream, emitsInOrder([false, emitsDone])); - }); - - test('Rx.sequenceEqual.notEquals.differentLength.customCompare.notEquals', - () async { - final stream = Rx.sequenceEqual(Stream.fromIterable(const [1, 2, 3, 4, 5]), - Stream.fromIterable(const [1, 2, 3, 4, 5, 6]), - equals: (int? a, int? b) => true); - - // expect false, - // even if the equals handler always returns true, - // the emitted events length is different - await expectLater(stream, emitsInOrder([false, emitsDone])); - }); - - test('Rx.sequenceEqual.equals.errors', () async { - final stream = Rx.sequenceEqual( - Stream.error(ArgumentError('error A')), - Stream.error(ArgumentError('error A')), - errorEquals: (e1, e2) => e1.error.toString() == e2.error.toString(), - ); - - await expectLater(stream, emitsInOrder([true, emitsDone])); - }); - - test('Rx.sequenceEqual.notEquals.errors', () async { - final stream = Rx.sequenceEqual( - Stream.error(ArgumentError('error A')), - Stream.error(ArgumentError('error B')), - errorEquals: (e1, e2) => e1.error.toString() == e2.error.toString(), - ); - - await expectLater(stream, emitsInOrder([false, emitsDone])); - }); - - test('Rx.sequenceEqual.single.subscription', () async { - final stream = Rx.sequenceEqual(Stream.fromIterable(const [1, 2, 3, 4, 5]), - Stream.fromIterable(const [1, 2, 3, 4, 5])); - - await expectLater(stream, emitsInOrder([true, emitsDone])); - await expectLater(() => stream.listen(null), throwsA(isStateError)); - }); - - test('Rx.sequenceEqual.asBroadcastStream', () async { - final future = Rx.sequenceEqual(Stream.fromIterable(const [1, 2, 3, 4, 5]), - Stream.fromIterable(const [1, 2, 3, 4, 5])) - .asBroadcastStream() - .drain(); - - // listen twice on same stream - await expectLater(future, completes); - await expectLater(future, completes); - }); -} diff --git a/sandbox/reactivex/test/streams/switch_latest_test.dart b/sandbox/reactivex/test/streams/switch_latest_test.dart deleted file mode 100644 index ea3f055..0000000 --- a/sandbox/reactivex/test/streams/switch_latest_test.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - group('SwitchLatest', () { - test('emits all values from an emitted Stream', () { - expect( - Rx.switchLatest( - Stream.value( - Stream.fromIterable(const ['A', 'B', 'C']), - ), - ), - emitsInOrder(['A', 'B', 'C', emitsDone]), - ); - }); - - test('only emits values from the latest emitted stream', () { - expect( - Rx.switchLatest(testStream), - emits('C'), - ); - }); - - test('emits errors from the higher order Stream to the listener', () { - expect( - Rx.switchLatest( - Stream>.error(Exception()), - ), - emitsError(isException), - ); - }); - - test('emits errors from the emitted Stream to the listener', () { - expect( - Rx.switchLatest(errorStream), - emitsError(isException), - ); - }); - - test('closes after the last event from the last emitted Stream', () { - expect( - Rx.switchLatest(testStream), - emitsThrough(emitsDone), - ); - }); - - test('closes if the higher order stream is empty', () { - expect( - Rx.switchLatest( - Stream>.empty(), - ), - emitsThrough(emitsDone), - ); - }); - - test('is single subscription', () { - final stream = SwitchLatestStream(testStream); - - expect(stream, emits('C')); - expect(() => stream.listen(null), throwsStateError); - }); - - test('can be paused and resumed', () { - // ignore: cancel_subscriptions - final subscription = - Rx.switchLatest(testStream).listen(expectAsync1((result) { - expect(result, 'C'); - })); - - subscription.pause(); - subscription.resume(); - }); - }); -} - -Stream> get testStream => Stream.fromIterable([ - Rx.timer('A', Duration(seconds: 2)), - Rx.timer('B', Duration(seconds: 1)), - Stream.value('C'), - ]); - -Stream> get errorStream => Stream.fromIterable([ - Rx.timer('A', Duration(seconds: 2)), - Rx.timer('B', Duration(seconds: 1)), - Stream.error(Exception()), - ]); diff --git a/sandbox/reactivex/test/streams/timer_test.dart b/sandbox/reactivex/test/streams/timer_test.dart deleted file mode 100644 index 4f5f62e..0000000 --- a/sandbox/reactivex/test/streams/timer_test.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('TimerStream', () async { - const value = 1; - - final stream = TimerStream(value, Duration(milliseconds: 1)); - - await expectLater(stream, emitsInOrder([value, emitsDone])); - }); - - test('TimerStream.single.subscription', () async { - final stream = TimerStream(1, Duration(milliseconds: 1)); - - stream.listen(null); - await expectLater(() => stream.listen(null), throwsA(isStateError)); - }); - - test('TimerStream.pause.resume.A', () async { - const value = 1; - late StreamSubscription subscription; - - final stream = TimerStream(value, Duration(milliseconds: 1)); - - subscription = stream.listen(expectAsync1((actual) { - expect(actual, value); - - subscription.cancel(); - })); - - subscription.pause(); - subscription.resume(); - }); - - test('TimerStream.pause.resume.B', () async { - const seconds = 2; - const delay = 1; - - var stream = Rx.timer(99, const Duration(seconds: seconds)); - var stopwatch = Stopwatch()..start(); - var subscription = stream.listen(expectAsync1((_) { - stopwatch.stop(); - expect(stopwatch.elapsed.inSeconds, seconds + delay); - })); - - await Future.delayed(const Duration(milliseconds: 100)); - subscription.pause(); - subscription.pause(); - - await Future.delayed(const Duration(seconds: delay)); - - subscription.resume(); - subscription.resume(); - subscription.resume(); - }); - - test('TimerStream.pause.resume.C', () async { - const value = 1; - const delta = Duration(milliseconds: 100); - const duration = Duration(seconds: 1); - final stream = TimerStream(value, duration); - - var elapses = Duration.zero; - late Stopwatch watch; - - void startWatch() => watch = Stopwatch()..start(); - - Future delay() => - Future.delayed(const Duration(milliseconds: 200)); - - void stopWatch() => elapses = elapses + watch.elapsed; - - final subscription = stream.listen(expectAsync1((actual) { - expect(actual, value); - - stopWatch(); - expect( - duration - delta <= elapses && elapses <= duration + delta, - isTrue, - ); - })); - startWatch(); - - await delay(); - - subscription.pause(); - stopWatch(); - - await delay(); - - subscription.resume(); - startWatch(); - }); - - test('TimerStream.single.subscription', () async { - final stream = TimerStream(null, Duration(milliseconds: 1)); - - try { - stream.listen(null); - stream.listen(null); - } catch (e) { - await expectLater(e, isStateError); - } - }); - - test('TimerStream.cancel', () async { - const value = 1; - StreamSubscription subscription; - - final stream = TimerStream(value, Duration(milliseconds: 1)); - - subscription = stream.listen( - expectAsync1((_) { - expect(true, isFalse); - }, count: 0), - onError: expectAsync2((Exception e, StackTrace s) { - expect(true, isFalse); - }, count: 0), - onDone: expectAsync0(() { - expect(true, isFalse); - }, count: 0)); - - await subscription.cancel(); - }); - - test('Rx.timer', () async { - const value = 1; - - final stream = Rx.timer(value, Duration(milliseconds: 5)); - - stream.listen(expectAsync1((actual) { - expect(actual, value); - }), onDone: expectAsync0(() { - expect(true, isTrue); - })); - }); -} diff --git a/sandbox/reactivex/test/streams/using_test.dart b/sandbox/reactivex/test/streams/using_test.dart deleted file mode 100644 index fcce284..0000000 --- a/sandbox/reactivex/test/streams/using_test.dart +++ /dev/null @@ -1,378 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -const resourceDuration = Duration(milliseconds: 5); - -class MockResource { - var _closed = false; - - bool get isClosed => _closed; - - MockResource(); - - Future close() { - if (_closed) { - throw StateError('Resource has already been closed.'); - } - _closed = true; - return Future.delayed(resourceDuration); - } - - void closeSync() { - if (_closed) { - throw StateError('Resource has already been closed.'); - } - _closed = true; - } -} - -enum Close { - sync, - async, -} - -enum Create { - sync, - async, -} - -void main() async { - for (final close in Close.values) { - for (final create in Create.values) { - final groupPrefix = - 'Rx.using.${create.toString().toLowerCase()}.${close.toString().toLowerCase()}'; - - group(groupPrefix, () { - late MockResource resource; - var isResourceCreated = false; - - late FutureOr Function() resourceFactory; - late FutureOr Function() resourceFactoryThrows; - - late FutureOr Function(MockResource) disposer; - late FutureOr Function(MockResource) disposerThrows; - - setUp(() { - isResourceCreated = false; - - resourceFactory = () { - switch (create) { - case Create.sync: - isResourceCreated = true; - return resource = MockResource(); - case Create.async: - return Future.delayed( - resourceDuration, - () { - isResourceCreated = true; - return resource = MockResource(); - }, - ); - } - }; - - resourceFactoryThrows = () { - switch (create) { - case Create.sync: - throw Exception(); - case Create.async: - return Future.delayed( - resourceDuration, - () => throw Exception(), - ); - } - }; - - disposer = (resource) { - switch (close) { - case Close.async: - return resource.close(); - case Close.sync: - // ignore: unnecessary_cast - return resource.closeSync() as FutureOr; - } - }; - - disposerThrows = (resource) { - switch (close) { - case Close.async: - return Future.delayed( - resourceDuration, - () => throw Exception(), - ); - case Close.sync: - throw Exception(); - } - }; - }); - - test('$groupPrefix.done', () async { - final stream = Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => Stream.value(resource) - .flatMap((_) => Stream.fromIterable([1, 2, 3])), - disposer: disposer, - ); - - await expectLater( - stream, - emitsInOrder([ - 1, - 2, - 3, - emitsDone, - ]), - ); - - expect(isResourceCreated, true); - expect(resource.isClosed, true); - }); - - test('$groupPrefix.resourceFactory.throws', () async { - var calledStreamFactory = false; - var callDisposer = false; - - final stream = Rx.using( - resourceFactory: resourceFactoryThrows, - streamFactory: (resource) { - calledStreamFactory = true; - return Rx.range(0, 3); - }, - disposer: (resource) { - callDisposer = true; - return disposer(resource); - }, - ); - - await expectLater( - stream, - emitsInOrder([emitsError(isException), emitsDone]), - ); - - expect(isResourceCreated, false); - expect(calledStreamFactory, false); - expect(callDisposer, false); - }); - - test('$groupPrefix.disposer.throws', () async { - final subscription = Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => Rx.timer(0, resourceDuration), - disposer: disposerThrows, - ).listen(null); - - if (create == Create.async) { - await Future.delayed(resourceDuration * 1.2); - } - - await expectLater( - subscription.cancel(), - throwsException, - ); - }); - - test('$groupPrefix.streamFactory.throws', () async { - final stream = Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => throw Exception(), - disposer: disposer, - ); - - await expectLater( - stream, - emitsInOrder([emitsError(isException), emitsDone]), - ); - - expect(isResourceCreated, true); - expect(resource.isClosed, true); - }); - - test('$groupPrefix.streamFactory.errors', () async { - final stream = Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => Stream.error(Exception()), - disposer: disposer, - ); - - await expectLater( - stream, - emitsInOrder([emitsError(isException), emitsDone]), - ); - - expect(isResourceCreated, true); - expect(resource.isClosed, true); - }); - - test('$groupPrefix.cancel.delayed', () async { - const duration = Duration(milliseconds: 200); - - final subscription = Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => Rx.concat([ - Rx.timer(0, duration), - Stream.error(Exception()), - ]), - disposer: disposer, - ).listen( - null, - cancelOnError: false, - ); - - // ensure the stream has started - await Future.delayed(resourceDuration + duration ~/ 2); - await subscription.cancel(); - await Future.delayed(resourceDuration * 1.2); - - expect(isResourceCreated, true); - expect(resource.isClosed, true); - }); - - test('$groupPrefix.cancel.immediately', () async { - final subscription = Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => Rx.concat([ - Rx.timer(0, const Duration(milliseconds: 10)), - Stream.error(Exception()), - ]), - disposer: disposer, - ).listen( - expectAsync1((v) => expect(true, false), count: 0), - onError: expectAsync2( - (Object e, StackTrace stackTrace) => expect(true, false), - count: 0, - ), - onDone: expectAsync0(() => expect(true, false), count: 0), - ); - - await subscription.cancel(); - await Future.delayed(resourceDuration * 2); - - expect(isResourceCreated, true); - expect(resource.isClosed, true); - }); - - test('$groupPrefix.errors.continueOnError', () async { - Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => Rx.concat([ - Rx.timer(0, resourceDuration * 2), - Stream.error(Exception()) - ]), - disposer: disposer, - ).listen( - null, - onError: (Object e, StackTrace s) {}, - cancelOnError: false, - ); - - await Future.delayed(resourceDuration * 1.2); - expect(isResourceCreated, true); - expect(resource.isClosed, false); - }); - - test('$groupPrefix.errors.cancelOnError', () async { - Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => Stream.error(Exception()), - disposer: disposer, - ).listen( - null, - onError: (Object e, StackTrace s) {}, - cancelOnError: true, - ); - - await Future.delayed(resourceDuration * 1.2); - expect(isResourceCreated, true); - expect(resource.isClosed, true); - }); - - test('$groupPrefix.single.subscription', () async { - final stream = Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => Rx.range(0, 3), - disposer: disposer, - ); - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - }); - - test('$groupPrefix.asBroadcastStream', () async { - final stream = Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => Stream.periodic( - const Duration(milliseconds: 50), - (i) => i, - ), - disposer: disposer, - ).asBroadcastStream(onCancel: (s) => s.cancel()); - - final s1 = stream.listen(null); - final s2 = stream.listen(null); - - // can reach here - expect(true, true); - - await Future.delayed(resourceDuration * 1.2); - await s1.cancel(); - await s2.cancel(); - expect(resource.isClosed, true); - }); - - test('$groupPrefix.pause.resume', () async { - late StreamSubscription subscription; - - subscription = Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) => Stream.periodic( - const Duration(milliseconds: 20), - (i) => i, - ), - disposer: disposer, - ).listen( - expectAsync1( - (value) { - subscription.cancel(); - expect(value, 0); - }, - count: 1, - ), - ); - - subscription - .pause(Future.delayed(const Duration(milliseconds: 50))); - }); - - test('$groupPrefix.disposer.order', () async { - final stream = Rx.using( - resourceFactory: resourceFactory, - streamFactory: (resource) { - final controller = StreamController(); - - controller.onListen = () { - controller.add(1); - controller.add(2); - controller.close(); - }; - - controller.onCancel = () async { - expect(resource.isClosed, false); - await Future.delayed(resourceDuration * 10); - expect(resource.isClosed, false); - }; - - return controller.stream; - }, - disposer: disposer, - ).take(1); - - await expectLater( - stream, - emitsInOrder([1, emitsDone]), - ); - }); - }); - } - } -} diff --git a/sandbox/reactivex/test/streams/value_connectable_stream_test.dart b/sandbox/reactivex/test/streams/value_connectable_stream_test.dart deleted file mode 100644 index e27def1..0000000 --- a/sandbox/reactivex/test/streams/value_connectable_stream_test.dart +++ /dev/null @@ -1,295 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -class MockStream extends Stream { - final Stream stream; - var listenCount = 0; - - MockStream(this.stream); - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - ++listenCount; - return stream.listen( - onData, - onError: onError, - onDone: onDone, - cancelOnError: cancelOnError, - ); - } -} - -void main() { - group('BehaviorConnectableStream', () { - test('should not emit before connecting', () { - final stream = MockStream(Stream.fromIterable(const [1, 2, 3])); - final connectableStream = ValueConnectableStream(stream); - - expect(stream.listenCount, 0); - connectableStream.connect(); - expect(stream.listenCount, 1); - }); - - test('should begin emitting items after connection', () { - var count = 0; - const items = [1, 2, 3]; - final stream = ValueConnectableStream(Stream.fromIterable(items)); - - stream.connect(); - - expect(stream, emitsInOrder(items)); - stream.listen(expectAsync1((i) { - expect(stream.value, items[count]); - count++; - }, count: items.length)); - }); - - test('stops emitting after the connection is cancelled', () async { - final stream = Stream.fromIterable(const [1, 2, 3]).publishValue(); - - stream.connect().cancel(); // ignore: unawaited_futures - - expect(stream, neverEmits(anything)); - }); - - test('stops emitting after the last subscriber unsubscribes', () async { - final stream = Stream.fromIterable(const [1, 2, 3]).shareValue(); - - stream.listen(null).cancel(); // ignore: unawaited_futures - - expect(stream, neverEmits(anything)); - }); - - test('keeps emitting with an active subscription', () async { - final stream = Stream.fromIterable(const [1, 2, 3]).shareValue(); - - stream.listen(null); - stream.listen(null).cancel(); // ignore: unawaited_futures - - expect(stream, emitsInOrder(const [1, 2, 3])); - }); - - test('multicasts a single-subscription stream', () async { - final stream = ValueConnectableStream( - Stream.fromIterable(const [1, 2, 3]), - ).autoConnect(); - - expect(stream, emitsInOrder(const [1, 2, 3])); - expect(stream, emitsInOrder(const [1, 2, 3])); - expect(stream, emitsInOrder(const [1, 2, 3])); - }); - - test('replays the latest item', () async { - final stream = ValueConnectableStream( - Stream.fromIterable(const [1, 2, 3]), - ).autoConnect(); - - expect(stream, emitsInOrder(const [1, 2, 3])); - expect(stream, emitsInOrder(const [1, 2, 3])); - expect(stream, emitsInOrder(const [1, 2, 3])); - - await Future.delayed(Duration(milliseconds: 200)); - - expect(stream, emits(3)); - }); - - test('replays the seeded item', () async { - final stream = - ValueConnectableStream.seeded(StreamController().stream, 3) - .autoConnect(); - - expect(stream, emitsInOrder(const [3])); - expect(stream, emitsInOrder(const [3])); - expect(stream, emitsInOrder(const [3])); - - await Future.delayed(Duration(milliseconds: 200)); - - expect(stream, emits(3)); - }); - - test('replays the seeded null item', () async { - final stream = - ValueConnectableStream.seeded(StreamController().stream, null) - .autoConnect(); - - expect(stream, emitsInOrder(const [null])); - expect(stream, emitsInOrder(const [null])); - expect(stream, emitsInOrder(const [null])); - - await Future.delayed(Duration(milliseconds: 200)); - - expect(stream, emits(null)); - }); - - test('can multicast streams', () async { - final stream = Stream.fromIterable(const [1, 2, 3]).shareValue(); - - expect(stream, emitsInOrder(const [1, 2, 3])); - expect(stream, emitsInOrder(const [1, 2, 3])); - expect(stream, emitsInOrder(const [1, 2, 3])); - }); - - test('transform Stream with initial value', () async { - final stream = Stream.fromIterable(const [1, 2, 3]).shareValueSeeded(0); - - expect(stream.value, 0); - expect(stream, emitsInOrder(const [0, 1, 2, 3])); - }); - - test('provides access to the latest value', () async { - const items = [1, 2, 3]; - var count = 0; - final stream = Stream.fromIterable(const [1, 2, 3]).shareValue(); - - stream.listen(expectAsync1((data) { - expect(data, items[count]); - count++; - if (count == items.length) { - expect(stream.value, 3); - } - }, count: items.length)); - }); - - test('provides access to the latest error', () async { - final source = StreamController(); - final stream = ValueConnectableStream(source.stream).autoConnect(); - - source.sink.add(1); - source.sink.add(2); - source.sink.add(3); - source.sink.addError(Exception('error')); - - stream.listen( - null, - onError: expectAsync1((Object error) { - expect(stream.valueOrNull, 3); - expect(stream.value, 3); - expect(stream.hasValue, isTrue); - - expect(stream.errorOrNull, error); - expect(stream.error, error); - expect(stream.hasError, isTrue); - }), - ); - }); - - test('provide a function to autoconnect that stops listening', () async { - final stream = Stream.fromIterable(const [1, 2, 3]) - .publishValue() - .autoConnect(connection: (subscription) => subscription.cancel()); - - expect(await stream.isEmpty, true); - }); - - test('refCount cancels source subscription when no listeners remain', - () async { - { - var isCanceled = false; - - final controller = - StreamController(onCancel: () => isCanceled = true); - final stream = controller.stream.shareValue(); - - StreamSubscription subscription; - subscription = stream.listen(null); - - await subscription.cancel(); - expect(isCanceled, true); - } - - { - var isCanceled = false; - - final controller = - StreamController(onCancel: () => isCanceled = true); - final stream = controller.stream.shareValueSeeded(null); - - StreamSubscription subscription; - subscription = stream.listen(null); - - await subscription.cancel(); - expect(isCanceled, true); - } - }); - - test('can close shareValue() stream', () async { - { - final isCanceled = Completer(); - - final controller = StreamController(); - controller.stream - .shareValue() - .doOnCancel(() => isCanceled.complete()) - .listen(null); - - controller.add(true); - await Future.delayed(Duration.zero); - await controller.close(); - - await expectLater(isCanceled.future, completes); - } - - { - final isCanceled = Completer(); - - final controller = StreamController(); - controller.stream - .shareValueSeeded(false) - .doOnCancel(() => isCanceled.complete()) - .listen(null); - - controller.add(true); - await Future.delayed(Duration.zero); - await controller.close(); - - await expectLater(isCanceled.future, completes); - } - }); - - test( - 'throws StateError when mixing autoConnect, connect and refCount together', - () { - ValueConnectableStream stream() => Stream.value(1).publishValue(); - - expect( - () => stream() - ..autoConnect() - ..connect(), - throwsStateError, - ); - expect( - () => stream() - ..autoConnect() - ..refCount(), - throwsStateError, - ); - expect( - () => stream() - ..connect() - ..refCount(), - throwsStateError, - ); - }); - - test('calling autoConnect() multiple times returns the same value', () { - final s = Stream.value(1).publishValueSeeded(1); - expect(s.autoConnect(), same(s.autoConnect())); - expect(s.autoConnect(), same(s.autoConnect())); - }); - - test('calling connect() multiple times returns the same value', () { - final s = Stream.value(1).publishValueSeeded(1); - expect(s.connect(), same(s.connect())); - expect(s.connect(), same(s.connect())); - }); - - test('calling refCount() multiple times returns the same value', () { - final s = Stream.value(1).publishValueSeeded(1); - expect(s.refCount(), same(s.refCount())); - expect(s.refCount(), same(s.refCount())); - }); - }); -} diff --git a/sandbox/reactivex/test/streams/zip_test.dart b/sandbox/reactivex/test/streams/zip_test.dart deleted file mode 100644 index feb7949..0000000 --- a/sandbox/reactivex/test/streams/zip_test.dart +++ /dev/null @@ -1,395 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.zip', () async { - expect( - Rx.zip([ - Stream.fromIterable(['A1', 'B1']), - Stream.fromIterable(['A2', 'B2', 'C2']), - ], (values) => values.first + values.last), - emitsInOrder(['A1A2', 'B1B2', emitsDone]), - ); - }); - - test('Rx.zip.empty', () { - expect(Rx.zipList([]), emitsDone); - }); - - test('Rx.zip.single', () { - expect( - Rx.zipList([Stream.value(1)]), - emitsInOrder([ - [1], - emitsDone - ]), - ); - }); - - test('Rx.zip.iterate.once', () async { - var iterationCount = 0; - - final stream = Rx.zipList(() sync* { - ++iterationCount; - yield Stream.value(1); - yield Stream.value(2); - yield Stream.value(3); - }()); - - await expectLater( - stream, - emitsInOrder([ - [1, 2, 3], - emitsDone, - ]), - ); - expect(iterationCount, 1); - }); - - test('Rx.zipList', () async { - expect( - Rx.zipList([ - Stream.fromIterable(['A1', 'B1']), - Stream.fromIterable(['A2', 'B2', 'C2']), - Stream.fromIterable(['A3', 'B3', 'C3']), - ]), - emitsInOrder([ - ['A1', 'A2', 'A3'], - ['B1', 'B2', 'B3'], - emitsDone - ]), - ); - }); - - test('Rx.zipBasics', () async { - const expectedOutput = [ - [0, 1, true], - [1, 2, false], - [2, 3, true], - [3, 4, false] - ]; - var count = 0; - - final testStream = StreamController() - ..add(true) - ..add(false) - ..add(true) - ..add(false) - ..add(true) - ..close(); // ignore: unawaited_futures - - final stream = Rx.zip3( - Stream.periodic(const Duration(milliseconds: 1), (count) => count) - .take(4), - Stream.fromIterable(const [1, 2, 3, 4, 5, 6, 7, 8, 9]), - testStream.stream, - (int a, int b, bool c) => [a, b, c]); - - stream.listen(expectAsync1((result) { - // test to see if the combined output matches - for (var i = 0, len = result.length; i < len; i++) { - expect(result[i], expectedOutput[count][i]); - } - - count++; - }, count: expectedOutput.length)); - }); - - test('Rx.zipTwo', () async { - const expected = [1, 2]; - - // A purposely emits 2 items, b only 1 - final a = Stream.fromIterable(const [1, 2]), b = Stream.value(2); - - final stream = Rx.zip2(a, b, (int first, int second) => [first, second]); - - // Explicitly adding count: 1. It's important here, and tests the difference - // between zip and combineLatest. If this was combineLatest, the count would - // be two, and a second List would be emitted. - stream.listen(expectAsync1((result) { - expect(result, expected); - }, count: 1)); - }); - - test('Rx.zip3', () async { - // Verify the ability to pass through various types with safety - const expected = [1, '2', 3.0]; - - final a = Stream.value(1), b = Stream.value('2'), c = Stream.value(3.0); - - final stream = Rx.zip3(a, b, c, - (int first, String second, double third) => [first, second, third]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.zip4', () async { - const expected = [1, 2, 3, 4]; - - final a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4); - - final stream = Rx.zip4( - a, - b, - c, - d, - (int first, int second, int third, int fourth) => - [first, second, third, fourth]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.zip5', () async { - const expected = [1, 2, 3, 4, 5]; - - final a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5); - - final stream = Rx.zip5( - a, - b, - c, - d, - e, - (int first, int second, int third, int fourth, int fifth) => - [first, second, third, fourth, fifth]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.zip6', () async { - const expected = [1, 2, 3, 4, 5, 6]; - - final a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6); - - final stream = Rx.zip6( - a, - b, - c, - d, - e, - f, - (int first, int second, int third, int fourth, int fifth, int sixth) => - [first, second, third, fourth, fifth, sixth]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.zip7', () async { - const expected = [1, 2, 3, 4, 5, 6, 7]; - - final a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6), - g = Stream.value(7); - - final stream = Rx.zip7( - a, - b, - c, - d, - e, - f, - g, - (int first, int second, int third, int fourth, int fifth, int sixth, - int seventh) => - [first, second, third, fourth, fifth, sixth, seventh]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.zip8', () async { - const expected = [1, 2, 3, 4, 5, 6, 7, 8]; - - final a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6), - g = Stream.value(7), - h = Stream.value(8); - - final stream = Rx.zip8( - a, - b, - c, - d, - e, - f, - g, - h, - (int first, int second, int third, int fourth, int fifth, int sixth, - int seventh, int eighth) => - [first, second, third, fourth, fifth, sixth, seventh, eighth]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.zip9', () async { - const expected = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - - final a = Stream.value(1), - b = Stream.value(2), - c = Stream.value(3), - d = Stream.value(4), - e = Stream.value(5), - f = Stream.value(6), - g = Stream.value(7), - h = Stream.value(8), - i = Stream.value(9); - - final stream = Rx.zip9( - a, - b, - c, - d, - e, - f, - g, - h, - i, - (int first, int second, int third, int fourth, int fifth, int sixth, - int seventh, int eighth, int ninth) => - [ - first, - second, - third, - fourth, - fifth, - sixth, - seventh, - eighth, - ninth - ]); - - stream.listen(expectAsync1((result) { - expect(result, expected); - })); - }); - - test('Rx.zip.single.subscription', () async { - final stream = - Rx.zip2(Stream.value(1), Stream.value(1), (int a, int b) => a + b); - - stream.listen(null); - await expectLater(() => stream.listen(null), throwsA(isStateError)); - }); - - test('Rx.zip.asBroadcastStream', () async { - final testStream = StreamController() - ..add(true) - ..add(false) - ..add(true) - ..add(false) - ..add(true) - ..close(); // ignore: unawaited_futures - - final stream = Rx.zip3( - Stream.periodic(const Duration(milliseconds: 1), (count) => count) - .take(4), - Stream.fromIterable(const [1, 2, 3, 4, 5, 6, 7, 8, 9]), - testStream.stream, - (int a, int b, bool c) => [a, b, c]).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.zip.error.shouldThrowA', () async { - final streamWithError = Rx.zip2( - Stream.value(1), - Stream.value(2), - (int a, int b) => throw Exception(), - ); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - /*test('Rx.zip.error.shouldThrowB', () { - expect( - () => Rx.zip2( - Stream.value(1), null, (int a, _) => null), - throwsArgumentError); - }); - - test('Rx.zip.error.shouldThrowC', () { - expect(() => ZipStream(null, () {}), throwsArgumentError); - }); - - test('Rx.zip.error.shouldThrowD', () { - expect(() => ZipStream(>[], () {}), - throwsArgumentError); - });*/ - - test('Rx.zip.pause.resume.A', () async { - late StreamSubscription subscription; - final stream = - Rx.zip2(Stream.value(1), Stream.value(2), (int a, int b) => a + b); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 3); - - subscription.cancel(); - })); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.zip.pause.resume.B', () async { - final first = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [1, 2, 3, 4][index]), - second = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [5, 6, 7, 8][index]), - last = Stream.periodic(const Duration(milliseconds: 10), - (index) => const [9, 10, 11, 12][index]); - - late StreamSubscription> subscription; - subscription = - Rx.zip3(first, second, last, (num a, num b, num c) => [a, b, c]) - .listen(expectAsync1((value) { - expect(value.elementAt(0), 1); - expect(value.elementAt(1), 5); - expect(value.elementAt(2), 9); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(Future.delayed(const Duration(milliseconds: 80))); - }); -} diff --git a/sandbox/reactivex/test/subject/behavior_subject_test.dart b/sandbox/reactivex/test/subject/behavior_subject_test.dart deleted file mode 100644 index 5f51cb3..0000000 --- a/sandbox/reactivex/test/subject/behavior_subject_test.dart +++ /dev/null @@ -1,1475 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - final throwsValueStreamError = throwsA(isA()); - - group('BehaviorSubject', () { - test('emits the most recently emitted item to every subscriber', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - unseeded.add(1); - unseeded.add(2); - unseeded.add(3); - - seeded.add(1); - seeded.add(2); - seeded.add(3); - - await expectLater(unseeded.stream, emits(3)); - await expectLater(unseeded.stream, emits(3)); - await expectLater(unseeded.stream, emits(3)); - - await expectLater(seeded.stream, emits(3)); - await expectLater(seeded.stream, emits(3)); - await expectLater(seeded.stream, emits(3)); - }); - - test('emits the most recently emitted null item to every subscriber', - () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - unseeded.add(1); - unseeded.add(2); - unseeded.add(null); - - seeded.add(1); - seeded.add(2); - seeded.add(null); - - await expectLater(unseeded.stream, emits(isNull)); - await expectLater(unseeded.stream, emits(isNull)); - await expectLater(unseeded.stream, emits(isNull)); - - await expectLater(seeded.stream, emits(isNull)); - await expectLater(seeded.stream, emits(isNull)); - await expectLater(seeded.stream, emits(isNull)); - }); - - test( - 'emits the most recently emitted item to every subscriber that subscribe to the subject directly', - () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - unseeded.add(1); - unseeded.add(2); - unseeded.add(3); - - seeded.add(1); - seeded.add(2); - seeded.add(3); - - await expectLater(unseeded, emits(3)); - await expectLater(unseeded, emits(3)); - await expectLater(unseeded, emits(3)); - - await expectLater(seeded, emits(3)); - await expectLater(seeded, emits(3)); - await expectLater(seeded, emits(3)); - }); - - test('emits errors to every subscriber', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - unseeded.add(1); - unseeded.add(2); - unseeded.add(3); - unseeded.addError(Exception('oh noes!')); - - seeded.add(1); - seeded.add(2); - seeded.add(3); - seeded.addError(Exception('oh noes!')); - - await expectLater(unseeded.stream, emitsError(isException)); - await expectLater(unseeded.stream, emitsError(isException)); - await expectLater(unseeded.stream, emitsError(isException)); - - await expectLater(seeded.stream, emitsError(isException)); - await expectLater(seeded.stream, emitsError(isException)); - await expectLater(seeded.stream, emitsError(isException)); - }); - - test('emits event after error to every subscriber', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - unseeded.add(1); - unseeded.add(2); - unseeded.addError(Exception('oh noes!')); - unseeded.add(3); - - seeded.add(1); - seeded.add(2); - seeded.addError(Exception('oh noes!')); - seeded.add(3); - - await expectLater(unseeded.stream, emits(3)); - await expectLater(unseeded.stream, emits(3)); - await expectLater(unseeded.stream, emits(3)); - - await expectLater(seeded.stream, emits(3)); - await expectLater(seeded.stream, emits(3)); - await expectLater(seeded.stream, emits(3)); - }); - - test('emits errors to every subscriber', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - final exception = Exception('oh noes!'); - - unseeded.add(1); - unseeded.add(2); - unseeded.add(3); - unseeded.addError(exception); - - seeded.add(1); - seeded.add(2); - seeded.add(3); - seeded.addError(exception); - - expect(unseeded.value, 3); - expect(unseeded.valueOrNull, 3); - expect(unseeded.hasValue, true); - - expect(unseeded.error, exception); - expect(unseeded.errorOrNull, exception); - expect(unseeded.hasError, true); - - await expectLater(unseeded, emitsError(exception)); - await expectLater(unseeded, emitsError(exception)); - await expectLater(unseeded, emitsError(exception)); - - expect(seeded.value, 3); - expect(seeded.valueOrNull, 3); - expect(seeded.hasValue, true); - - expect(seeded.error, exception); - expect(seeded.errorOrNull, exception); - expect(seeded.hasError, true); - - await expectLater(seeded, emitsError(exception)); - await expectLater(seeded, emitsError(exception)); - await expectLater(seeded, emitsError(exception)); - }); - - test('can synchronously get the latest value', () { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - unseeded.add(1); - unseeded.add(2); - unseeded.add(3); - - seeded.add(1); - seeded.add(2); - seeded.add(3); - - expect(unseeded.value, 3); - expect(unseeded.valueOrNull, 3); - expect(unseeded.hasValue, true); - - expect(seeded.value, 3); - expect(seeded.valueOrNull, 3); - expect(seeded.hasValue, true); - }); - - test('can synchronously get the latest null value', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - unseeded.add(1); - unseeded.add(2); - unseeded.add(null); - - seeded.add(1); - seeded.add(2); - seeded.add(null); - - expect(unseeded.value, isNull); - expect(unseeded.valueOrNull, isNull); - expect(unseeded.hasValue, true); - - expect(seeded.value, isNull); - expect(seeded.valueOrNull, isNull); - expect(seeded.hasValue, true); - }); - - test('emits the seed item if no new items have been emitted', () async { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(1); - - await expectLater(subject.stream, emits(1)); - await expectLater(subject.stream, emits(1)); - await expectLater(subject.stream, emits(1)); - }); - - test('emits the null seed item if no new items have been emitted', - () async { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(null); - - await expectLater(subject.stream, emits(isNull)); - await expectLater(subject.stream, emits(isNull)); - await expectLater(subject.stream, emits(isNull)); - }); - - test('can synchronously get the initial value', () { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(1); - - expect(subject.value, 1); - expect(subject.valueOrNull, 1); - expect(subject.hasValue, true); - }); - - test('can synchronously get the initial null value', () { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(null); - - expect(subject.value, null); - expect(subject.valueOrNull, null); - expect(subject.hasValue, true); - }); - - test('initial value is null when no value has been emitted', () { - // ignore: close_sinks - final subject = BehaviorSubject(); - - expect(() => subject.value, throwsValueStreamError); - expect(subject.valueOrNull, null); - expect(subject.hasValue, false); - }); - - test('emits done event to listeners when the subject is closed', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - await expectLater(unseeded.isClosed, isFalse); - await expectLater(seeded.isClosed, isFalse); - - unseeded.add(1); - scheduleMicrotask(() => unseeded.close()); - - seeded.add(1); - scheduleMicrotask(() => seeded.close()); - - await expectLater(unseeded.stream, emitsInOrder([1, emitsDone])); - await expectLater(unseeded.isClosed, isTrue); - - await expectLater(seeded.stream, emitsInOrder([1, emitsDone])); - await expectLater(seeded.isClosed, isTrue); - }); - - test('emits error events to subscribers', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - scheduleMicrotask(() => unseeded.addError(Exception())); - scheduleMicrotask(() => seeded.addError(Exception())); - - await expectLater(unseeded.stream, emitsError(isException)); - await expectLater(seeded.stream, emitsError(isException)); - }); - - test('replays the previously emitted items from addStream', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - await unseeded.addStream(Stream.fromIterable(const [1, 2, 3])); - await seeded.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(unseeded.stream, emits(3)); - await expectLater(unseeded.stream, emits(3)); - await expectLater(unseeded.stream, emits(3)); - - await expectLater(seeded.stream, emits(3)); - await expectLater(seeded.stream, emits(3)); - await expectLater(seeded.stream, emits(3)); - }); - - test('replays the previously emitted errors from addStream', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - await unseeded.addStream(Stream.error('error'), - cancelOnError: false); - await seeded.addStream(Stream.error('error'), cancelOnError: false); - - await expectLater(unseeded.stream, emitsError('error')); - await expectLater(unseeded.stream, emitsError('error')); - }); - - test('allows items to be added once addStream is complete', () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - await subject.addStream(Stream.fromIterable(const [1, 2])); - subject.add(3); - - await expectLater(subject.stream, emits(3)); - }); - - test('allows items to be added once addStream completes with an error', - () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - unawaited(subject - .addStream(Stream.error(Exception()), cancelOnError: true) - .whenComplete(() => subject.add(1))); - - await expectLater(subject.stream, - emitsInOrder([emitsError(isException), emits(1)])); - }); - - test('does not allow events to be added when addStream is active', - () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.add(1), throwsStateError); - }); - - test('does not allow errors to be added when addStream is active', - () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.addError(Error()), throwsStateError); - }); - - test('does not allow subject to be closed when addStream is active', - () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.close(), throwsStateError); - }); - - test( - 'does not allow addStream to add items when previous addStream is active', - () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.addStream(Stream.fromIterable(const [1])), - throwsStateError); - }); - - test('returns onListen callback set in constructor', () async { - void testOnListen() {} - // ignore: close_sinks - final subject = BehaviorSubject(onListen: testOnListen); - - await expectLater(subject.onListen, testOnListen); - }); - - test('sets onListen callback', () async { - void testOnListen() {} - // ignore: close_sinks - final subject = BehaviorSubject(); - - await expectLater(subject.onListen, isNull); - - subject.onListen = testOnListen; - - await expectLater(subject.onListen, testOnListen); - }); - - test('returns onCancel callback set in constructor', () async { - Future onCancel() => Future.value(null); - // ignore: close_sinks - final subject = BehaviorSubject(onCancel: onCancel); - - await expectLater(subject.onCancel, onCancel); - }); - - test('sets onCancel callback', () async { - void testOnCancel() {} - // ignore: close_sinks - final subject = BehaviorSubject(); - - await expectLater(subject.onCancel, isNull); - - subject.onCancel = testOnCancel; - - await expectLater(subject.onCancel, testOnCancel); - }); - - test('reports if a listener is present', () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - await expectLater(subject.hasListener, isFalse); - - subject.stream.listen(null); - - await expectLater(subject.hasListener, isTrue); - }); - - test('onPause unsupported', () { - // ignore: close_sinks - final subject = BehaviorSubject(); - - expect(subject.isPaused, isFalse); - expect(() => subject.onPause, throwsUnsupportedError); - expect(() => subject.onPause = () {}, throwsUnsupportedError); - }); - - test('onResume unsupported', () { - // ignore: close_sinks - final subject = BehaviorSubject(); - - expect(() => subject.onResume, throwsUnsupportedError); - expect(() => subject.onResume = () {}, throwsUnsupportedError); - }); - - test('returns controller sink', () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - await expectLater(subject.sink, TypeMatcher>()); - }); - - test('correctly closes done Future', () async { - final subject = BehaviorSubject(); - - scheduleMicrotask(() => subject.close()); - - await expectLater(subject.done, completes); - }); - - test('can be listened to multiple times', () async { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(1); - final stream = subject.stream; - - await expectLater(stream, emits(1)); - await expectLater(stream, emits(1)); - }); - - test('always returns the same stream', () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - await expectLater(subject.stream, equals(subject.stream)); - }); - - test('adding to sink has same behavior as adding to Subject itself', - () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - subject.sink.add(1); - - expect(subject.value, 1); - - subject.sink.add(2); - subject.sink.add(3); - - await expectLater(subject.stream, emits(3)); - await expectLater(subject.stream, emits(3)); - await expectLater(subject.stream, emits(3)); - }); - - test('setter `value=` has same behavior as adding to Subject', () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - - subject.value = 1; - - expect(subject.value, 1); - - subject.value = 2; - subject.value = 3; - - await expectLater(subject.stream, emits(3)); - await expectLater(subject.stream, emits(3)); - await expectLater(subject.stream, emits(3)); - }); - - test('is always treated as a broadcast Stream', () async { - // ignore: close_sinks - final subject = BehaviorSubject(); - final stream = subject.asyncMap((event) => Future.value(event)); - - expect(subject.isBroadcast, isTrue); - expect(stream.isBroadcast, isTrue); - }); - - test('hasValue returns false for an empty subject', () { - // ignore: close_sinks - final subject = BehaviorSubject(); - - expect(subject.hasValue, isFalse); - }); - - test('hasValue returns true for a seeded subject with non-null seed', () { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(1); - - expect(subject.hasValue, isTrue); - }); - - test('hasValue returns true for a seeded subject with null seed', () { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(null); - - expect(subject.hasValue, isTrue); - }); - - test('hasValue returns true for an unseeded subject after an emission', () { - // ignore: close_sinks - final subject = BehaviorSubject(); - - subject.add(1); - - expect(subject.hasValue, isTrue); - }); - - test('hasError returns false for an empty subject', () { - // ignore: close_sinks - final subject = BehaviorSubject(); - - expect(subject.hasError, isFalse); - }); - - test('hasError returns false for a seeded subject with non-null seed', () { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(1); - - expect(subject.hasError, isFalse); - }); - - test('hasError returns false for a seeded subject with null seed', () { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(null); - - expect(subject.hasError, isFalse); - }); - - test('hasError returns false for an unseeded subject after an emission', - () { - // ignore: close_sinks - final subject = BehaviorSubject(); - - subject.add(1); - - expect(subject.hasError, isFalse); - }); - - test('hasError returns true for an unseeded subject after addError', () { - // ignore: close_sinks - final subject = BehaviorSubject(); - - subject.add(1); - subject.addError('error'); - - expect(subject.hasError, isTrue); - }); - - test('hasError returns true for a seeded subject after addError', () { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(1); - - subject.addError('error'); - - expect(subject.hasError, isTrue); - }); - - test('error returns null for an empty subject', () { - // ignore: close_sinks - final subject = BehaviorSubject(); - - expect(subject.hasError, isFalse); - expect(subject.errorOrNull, isNull); - expect(() => subject.error, throwsValueStreamError); - }); - - test('error returns null for a seeded subject with non-null seed', () { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(1); - - expect(subject.hasError, isFalse); - expect(subject.errorOrNull, isNull); - expect(() => subject.error, throwsValueStreamError); - }); - - test('error returns null for a seeded subject with null seed', () { - // ignore: close_sinks - final subject = BehaviorSubject.seeded(null); - - expect(subject.hasError, isFalse); - expect(subject.errorOrNull, isNull); - expect(() => subject.error, throwsValueStreamError); - }); - - test('can synchronously get the latest error', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - unseeded.add(1); - unseeded.add(2); - unseeded.add(3); - expect(unseeded.hasError, isFalse); - expect(unseeded.errorOrNull, isNull); - expect(() => unseeded.error, throwsValueStreamError); - - unseeded.addError(Exception('oh noes!')); - expect(unseeded.hasError, isTrue); - expect(unseeded.errorOrNull, isException); - expect(unseeded.error, isException); - - seeded.add(1); - seeded.add(2); - seeded.add(3); - expect(seeded.hasError, isFalse); - expect(seeded.errorOrNull, isNull); - expect(() => seeded.error, throwsValueStreamError); - - seeded.addError(Exception('oh noes!')); - expect(seeded.hasError, isTrue); - expect(seeded.errorOrNull, isException); - expect(seeded.error, isException); - }); - - test('emits event after error to every subscriber', () async { - // ignore: close_sinks - final unseeded = BehaviorSubject(), - // ignore: close_sinks - seeded = BehaviorSubject.seeded(0); - - unseeded.add(1); - unseeded.add(2); - unseeded.addError(Exception('oh noes!')); - expect(unseeded.hasError, isTrue); - expect(unseeded.errorOrNull, isException); - expect(unseeded.error, isException); - unseeded.add(3); - expect(unseeded.hasError, isTrue); - expect(unseeded.errorOrNull, isException); - expect(unseeded.error, isException); - - seeded.add(1); - seeded.add(2); - seeded.addError(Exception('oh noes!')); - expect(seeded.hasError, isTrue); - expect(seeded.errorOrNull, isException); - expect(seeded.error, isException); - seeded.add(3); - expect(seeded.hasError, isTrue); - expect(seeded.errorOrNull, isException); - expect(seeded.error, isException); - }); - - test( - 'issue/350: emits duplicate values when listening multiple times and starting with an Error', - () async { - final subject = BehaviorSubject(); - - subject.addError('error'); - - await subject.close(); - - await expectLater(subject, - emitsInOrder([emitsError('error'), emitsDone])); - await expectLater(subject, - emitsInOrder([emitsError('error'), emitsDone])); - await expectLater(subject, - emitsInOrder([emitsError('error'), emitsDone])); - }); - - test('issue/419: sync behavior', () async { - final subject = BehaviorSubject.seeded(1, sync: true); - final mappedStream = subject.map((event) => event).shareValue(); - - mappedStream.listen(null); - - expect(mappedStream.value, equals(1)); - - await subject.close(); - }, skip: true); - - test('issue/419: sync throughput', () async { - final subject = BehaviorSubject.seeded(1, sync: true); - final mappedStream = subject.map((event) => event).shareValue(); - - mappedStream.listen(null); - - subject.add(2); - - expect(mappedStream.value, equals(2)); - - await subject.close(); - }, skip: true); - - test('issue/419: async behavior', () async { - final subject = BehaviorSubject.seeded(1); - final mappedStream = subject.map((event) => event).shareValue(); - - mappedStream.listen(null, - onDone: () => expect(mappedStream.value, equals(1))); - - expect(() => mappedStream.value, throwsValueStreamError); - expect(mappedStream.valueOrNull, isNull); - expect(mappedStream.hasValue, false); - - await subject.close(); - }); - - test('issue/419: async throughput', () async { - final subject = BehaviorSubject.seeded(1); - final mappedStream = subject.map((event) => event).shareValue(); - - mappedStream.listen(null, - onDone: () => expect(mappedStream.value, equals(2))); - - subject.add(2); - - expect(() => mappedStream.value, throwsValueStreamError); - expect(mappedStream.valueOrNull, isNull); - expect(mappedStream.hasValue, false); - - await subject.close(); - }); - - test('issue/477: get first after cancelled', () async { - final a = BehaviorSubject.seeded('a'); - final bug = a.switchMap((v) => BehaviorSubject.seeded('b')); - await bug.listen(null).cancel(); - expect(await bug.first, 'b'); - }); - - test('issue/477: get first multiple times', () async { - final a = BehaviorSubject.seeded('a'); - final bug = a.switchMap((_) => BehaviorSubject.seeded('b')); - bug.listen(null); - expect(await bug.first, 'b'); - expect(await bug.first, 'b'); - }); - - test('issue/478: get first multiple times', () async { - final a = BehaviorSubject.seeded('a'); - final b = BehaviorSubject.seeded('b'); - final bug = - Rx.combineLatest2(a, b, (String _a, String _b) => 'ab').shareValue(); - expect(await bug.first, 'ab'); - expect(await bug.first, 'ab'); - }); - - test('angel3_reactivex #477/#500 - a', () async { - final a = BehaviorSubject.seeded('a') - .switchMap((_) => BehaviorSubject.seeded('a')) - ..listen(print); - await pumpEventQueue(); - expect(await a.first, 'a'); - }); - - test('angel3_reactivex #477/#500 - b', () async { - final b = BehaviorSubject.seeded('b') - .map((_) => 'b') - .switchMap((_) => BehaviorSubject.seeded('b')) - ..listen(print); - await pumpEventQueue(); - expect(await b.first, 'b'); - }); - - test('issue/587', () async { - final source = BehaviorSubject.seeded('source'); - final switched = - source.switchMap((value) => BehaviorSubject.seeded('switched')); - var i = 0; - switched.listen((_) => i++); - expect(await switched.first, 'switched'); - expect(i, 1); - expect(await switched.first, 'switched'); - expect(i, 1); - }); - - test('do not update latest value after closed', () { - final seeded = BehaviorSubject.seeded(0); - final unseeded = BehaviorSubject(); - - seeded.add(1); - unseeded.add(1); - - expect(seeded.value, 1); - expect(unseeded.value, 1); - - seeded.close(); - unseeded.close(); - - expect(() => seeded.add(2), throwsStateError); - expect(() => unseeded.add(2), throwsStateError); - expect(() => seeded.addError(Exception()), throwsStateError); - expect(() => unseeded.addError(Exception()), throwsStateError); - - expect(seeded.value, 1); - expect(unseeded.value, 1); - }); - - group('override built-in', () { - test('where', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject.where((event) => event.isOdd); - expect(stream, emitsInOrder([1, 3])); - - behaviorSubject.add(2); - behaviorSubject.add(3); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject.where((event) => event.isOdd); - expect(stream, emitsInOrder([1, 3])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - behaviorSubject.add(3); - } - }); - - test('map', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var mapped = behaviorSubject.map((event) => event + 1); - expect(mapped, emitsInOrder([2, 3])); - - behaviorSubject.add(2); - } - - { - var behaviorSubject = BehaviorSubject(); - - var mapped = behaviorSubject.map((event) => event + 1); - expect(mapped, emitsInOrder([2, 3])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - } - }); - - test('asyncMap', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var mapped = - behaviorSubject.asyncMap((event) => Future.value(event + 1)); - expect(mapped, emitsInOrder([2, 3])); - - behaviorSubject.add(2); - } - - { - var behaviorSubject = BehaviorSubject(); - - var mapped = - behaviorSubject.asyncMap((event) => Future.value(event + 1)); - expect(mapped, emitsInOrder([2, 3])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - } - }); - - test('asyncExpand', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = - behaviorSubject.asyncExpand((event) => Stream.value(event + 1)); - expect(stream, emitsInOrder([2, 3])); - - behaviorSubject.add(2); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = - behaviorSubject.asyncExpand((event) => Stream.value(event + 1)); - expect(stream, emitsInOrder([2, 3])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - } - }); - - test('handleError', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject.handleError( - expectAsync1( - (dynamic e) => expect(e, isException), - count: 1, - ), - ); - - expect( - stream, - emitsInOrder([1, 2]), - ); - - behaviorSubject.addError(Exception()); - behaviorSubject.add(2); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject.handleError( - expectAsync1( - (dynamic e) => expect(e, isException), - count: 1, - ), - ); - - expect( - stream, - emitsInOrder([1, 2]), - ); - - behaviorSubject.add(1); - behaviorSubject.addError(Exception()); - behaviorSubject.add(2); - } - }); - - test('expand', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject.expand((event) => [event + 1]); - expect(stream, emitsInOrder([2, 3])); - - behaviorSubject.add(2); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject.expand((event) => [event + 1]); - expect(stream, emitsInOrder([2, 3])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - } - }); - - test('transform', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject.transform( - IntervalStreamTransformer(const Duration(milliseconds: 100))); - expect(stream, emitsInOrder([1, 2])); - - behaviorSubject.add(2); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject.transform( - IntervalStreamTransformer(const Duration(milliseconds: 100))); - expect(stream, emitsInOrder([1, 2])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - } - }); - - test('cast', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject.cast(); - expect(stream, emitsInOrder([1, 2])); - - behaviorSubject.add(2); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject.cast(); - expect(stream, emitsInOrder([1, 2])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - } - }); - - test('take', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject.take(2); - expect(stream, emitsInOrder([1, 2])); - - behaviorSubject.add(2); - behaviorSubject.add(3); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject.take(2); - expect(stream, emitsInOrder([1, 2])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - behaviorSubject.add(3); - } - }); - - test('takeWhile', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject.takeWhile((element) => element <= 2); - expect(stream, emitsInOrder([1, 2])); - - behaviorSubject.add(2); - behaviorSubject.add(3); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject.takeWhile((element) => element <= 2); - expect(stream, emitsInOrder([1, 2])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - behaviorSubject.add(3); - } - }); - - test('skip', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject.skip(2); - expect(stream, emitsInOrder([3, 4])); - - behaviorSubject.add(2); - behaviorSubject.add(3); - behaviorSubject.add(4); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject.skip(2); - expect(stream, emitsInOrder([3, 4])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - behaviorSubject.add(3); - behaviorSubject.add(4); - } - }); - - test('skipWhile', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject.skipWhile((element) => element < 3); - expect(stream, emitsInOrder([3, 4])); - - behaviorSubject.add(2); - behaviorSubject.add(3); - behaviorSubject.add(4); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject.skipWhile((element) => element < 3); - expect(stream, emitsInOrder([3, 4])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - behaviorSubject.add(3); - behaviorSubject.add(4); - } - }); - - test('distinct', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject.distinct(); - expect(stream, emitsInOrder([1, 2])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - behaviorSubject.add(2); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject.distinct(); - expect(stream, emitsInOrder([1, 2])); - - behaviorSubject.add(1); - behaviorSubject.add(1); - behaviorSubject.add(2); - behaviorSubject.add(2); - } - }); - - test('timeout', () { - { - var behaviorSubject = BehaviorSubject.seeded(1); - - var stream = behaviorSubject - .interval(const Duration(milliseconds: 100)) - .timeout( - const Duration(milliseconds: 70), - onTimeout: expectAsync1( - (EventSink sink) {}, - count: 4, - ), - ); - - expect(stream, emitsInOrder([1, 2, 3, 4])); - - behaviorSubject.add(2); - behaviorSubject.add(3); - behaviorSubject.add(4); - } - - { - var behaviorSubject = BehaviorSubject(); - - var stream = behaviorSubject - .interval(const Duration(milliseconds: 100)) - .timeout( - const Duration(milliseconds: 70), - onTimeout: expectAsync1( - (EventSink sink) {}, - count: 4, - ), - ); - - expect(stream, emitsInOrder([1, 2, 3, 4])); - - behaviorSubject.add(1); - behaviorSubject.add(2); - behaviorSubject.add(3); - behaviorSubject.add(4); - } - }); - }); - - test('stream returns a read-only stream', () async { - final subject = BehaviorSubject()..add(1); - - // streams returned by BehaviorSubject are read-only stream, - // ie. they don't support adding events. - expect(subject.stream, isNot(isA>())); - expect(subject.stream, isNot(isA>())); - - expect( - subject.stream, - isA>().having( - (v) => v.value, - 'BehaviorSubject.stream.value', - 1, - ), - ); - - // BehaviorSubject.stream is a broadcast stream - { - final stream = subject.stream; - expect(stream.isBroadcast, isTrue); - await expectLater(stream, emitsInOrder([1])); - await expectLater(stream, emitsInOrder([1])); - } - - // streams returned by the same subject are considered equal, - // but not identical - expect(identical(subject.stream, subject.stream), isFalse); - expect(subject.stream == subject.stream, isTrue); - }); - - group('lastEventOrNull', () { - test('empty subject', () { - final s = BehaviorSubject(); - expect(s.lastEventOrNull, isNull); - expect(s.isLastEventValue, isFalse); - expect(s.isLastEventError, isFalse); - - // the stream has the same value as the subject - expect(s.stream.lastEventOrNull, isNull); - expect(s.stream.isLastEventValue, isFalse); - expect(s.stream.isLastEventError, isFalse); - }); - - test('subject with value', () { - final s = BehaviorSubject.seeded(42); - expect( - s.lastEventOrNull, - StreamNotification.data(42), - ); - expect(s.isLastEventValue, isTrue); - expect(s.isLastEventError, isFalse); - - // the stream has the same value as the subject - expect( - s.stream.lastEventOrNull, - StreamNotification.data(42), - ); - expect(s.stream.isLastEventValue, isTrue); - expect(s.stream.isLastEventError, isFalse); - }); - - test('subject with error', () { - final s = BehaviorSubject(); - final exception = Exception(); - s.addError(exception, StackTrace.empty); - - expect( - s.lastEventOrNull, - StreamNotification.error(exception, StackTrace.empty), - ); - expect(s.isLastEventValue, isFalse); - expect(s.isLastEventError, isTrue); - - // the stream has the same value as the subject - expect( - s.stream.lastEventOrNull, - StreamNotification.error(exception, StackTrace.empty), - ); - expect(s.stream.isLastEventValue, isFalse); - expect(s.stream.isLastEventError, isTrue); - }); - - test('add error and then value', () { - final s = BehaviorSubject(); - final exception = Exception(); - s.addError(exception, StackTrace.empty); - s.add(42); - - expect( - s.lastEventOrNull, - StreamNotification.data(42), - ); - expect(s.isLastEventValue, isTrue); - expect(s.isLastEventError, isFalse); - - // the stream has the same value as the subject - expect( - s.stream.lastEventOrNull, - StreamNotification.data(42), - ); - expect(s.stream.isLastEventValue, isTrue); - expect(s.stream.isLastEventError, isFalse); - }); - - test('add value and then error', () { - final s = BehaviorSubject(); - s.add(42); - final exception = Exception(); - s.addError(exception, StackTrace.empty); - - expect( - s.lastEventOrNull, - StreamNotification.error(exception, StackTrace.empty), - ); - expect(s.isLastEventValue, isFalse); - expect(s.isLastEventError, isTrue); - - // the stream has the same value as the subject - expect( - s.stream.lastEventOrNull, - StreamNotification.error(exception, StackTrace.empty), - ); - expect(s.stream.isLastEventValue, isFalse); - expect(s.stream.isLastEventError, isTrue); - }); - - test('add value and then close', () async { - final s = BehaviorSubject(); - s.add(42); - await s.close(); - - expect( - s.lastEventOrNull, - StreamNotification.data(42), - ); - expect(s.isLastEventValue, isTrue); - expect(s.isLastEventError, isFalse); - - // the stream has the same value as the subject - expect( - s.stream.lastEventOrNull, - StreamNotification.data(42), - ); - expect(s.stream.isLastEventValue, isTrue); - expect(s.stream.isLastEventError, isFalse); - }); - - test('add error and then close', () async { - final s = BehaviorSubject(); - final exception = Exception(); - s.addError(exception, StackTrace.empty); - await s.close(); - - expect( - s.lastEventOrNull, - StreamNotification.error(exception, StackTrace.empty), - ); - expect(s.isLastEventValue, isFalse); - expect(s.isLastEventError, isTrue); - - // the stream has the same value as the subject - expect( - s.stream.lastEventOrNull, - StreamNotification.error(exception, StackTrace.empty), - ); - expect(s.stream.isLastEventValue, isFalse); - expect(s.stream.isLastEventError, isTrue); - }); - }); - - group('errorAndStackTraceOrNull', () { - test('empty subject', () { - final s = BehaviorSubject(); - expect(s.errorAndStackTraceOrNull, isNull); - - // the stream has the same value as the subject - expect(s.stream.errorAndStackTraceOrNull, isNull); - }); - - test('seeded subject', () { - final s = BehaviorSubject.seeded(42); - expect(s.errorAndStackTraceOrNull, isNull); - - // the stream has the same value as the subject - expect(s.stream.errorAndStackTraceOrNull, isNull); - }); - - test('subject with error and stack trace', () { - final s = BehaviorSubject(); - final exception = Exception(); - s.addError(exception, StackTrace.empty); - - expect( - s.errorAndStackTraceOrNull, - ErrorAndStackTrace(exception, StackTrace.empty), - ); - - // the stream has the same value as the subject - expect( - s.stream.errorAndStackTraceOrNull, - ErrorAndStackTrace(exception, StackTrace.empty), - ); - }); - - test('subject with error', () { - final s = BehaviorSubject(); - final exception = Exception(); - s.addError(exception); - - expect( - s.errorAndStackTraceOrNull, - ErrorAndStackTrace(exception, null), - ); - - // the stream has the same value as the subject - expect( - s.stream.errorAndStackTraceOrNull, - ErrorAndStackTrace(exception, null), - ); - }); - - test('seeded subject and close', () { - final s = BehaviorSubject.seeded(42)..close(); - - expect(s.errorAndStackTraceOrNull, isNull); - - // the stream has the same value as the subject - expect(s.stream.errorAndStackTraceOrNull, isNull); - }); - - test('error and close', () { - final s = BehaviorSubject(); - final exception = Exception(); - s - ..addError(exception) - ..close(); - - expect( - s.errorAndStackTraceOrNull, - ErrorAndStackTrace(exception, null), - ); - - // the stream has the same value as the subject - expect( - s.stream.errorAndStackTraceOrNull, - ErrorAndStackTrace(exception, null), - ); - }); - }); - }); -} diff --git a/sandbox/reactivex/test/subject/publish_subject_test.dart b/sandbox/reactivex/test/subject/publish_subject_test.dart deleted file mode 100644 index 1561109..0000000 --- a/sandbox/reactivex/test/subject/publish_subject_test.dart +++ /dev/null @@ -1,323 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:angel3_reactivex/subjects.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - group('PublishSubject', () { - test('emits items to every subscriber', () async { - // ignore: close_sinks - final subject = PublishSubject(); - - scheduleMicrotask(() { - subject.add(1); - subject.add(2); - subject.add(3); - subject.close(); - }); - - await expectLater( - subject.stream, emitsInOrder([1, 2, 3, emitsDone])); - }); - - test( - 'emits items to every subscriber that subscribe directly to the Subject', - () async { - // ignore: close_sinks - final subject = PublishSubject(); - - scheduleMicrotask(() { - subject.add(1); - subject.add(2); - subject.add(3); - subject.close(); - }); - - await expectLater(subject, emitsInOrder([1, 2, 3, emitsDone])); - }); - - test('emits done event to listeners when the subject is closed', () async { - final subject = PublishSubject(); - - await expectLater(subject.isClosed, isFalse); - - scheduleMicrotask(() => subject.add(1)); - scheduleMicrotask(() => subject.close()); - - await expectLater(subject.stream, emitsInOrder([1, emitsDone])); - await expectLater(subject.isClosed, isTrue); - }); - - test( - 'emits done event to listeners when the subject is closed (listen directly on Subject)', - () async { - final subject = PublishSubject(); - - await expectLater(subject.isClosed, isFalse); - - scheduleMicrotask(() => subject.add(1)); - scheduleMicrotask(() => subject.close()); - - await expectLater(subject, emitsInOrder([1, emitsDone])); - await expectLater(subject.isClosed, isTrue); - }); - - test('emits error events to subscribers', () async { - // ignore: close_sinks - final subject = PublishSubject(); - - scheduleMicrotask(() => subject.addError(Exception())); - - await expectLater(subject.stream, emitsError(isException)); - }); - - test('emits error events to subscribers (listen directly on Subject)', - () async { - // ignore: close_sinks - final subject = PublishSubject(); - - scheduleMicrotask(() => subject.addError(Exception())); - - await expectLater(subject, emitsError(isException)); - }); - - test('emits the items from addStream', () async { - // ignore: close_sinks - final subject = PublishSubject(); - - scheduleMicrotask( - () => subject.addStream(Stream.fromIterable(const [1, 2, 3]))); - - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - }); - - test('allows items to be added once addStream is complete', () async { - // ignore: close_sinks - final subject = PublishSubject(); - - await subject.addStream(Stream.fromIterable(const [1, 2])); - scheduleMicrotask(() => subject.add(3)); - - await expectLater(subject.stream, emits(3)); - }); - - test('allows items to be added once addStream completes with an error', - () async { - // ignore: close_sinks - final subject = PublishSubject(); - - unawaited(subject - .addStream(Stream.error(Exception()), cancelOnError: true) - .whenComplete(() => subject.add(1))); - - await expectLater(subject.stream, - emitsInOrder([emitsError(isException), emits(1)])); - }); - - test('does not allow events to be added when addStream is active', - () async { - // ignore: close_sinks - final subject = PublishSubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.add(1), throwsStateError); - }); - - test('does not allow errors to be added when addStream is active', - () async { - // ignore: close_sinks - final subject = PublishSubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.addError(Error()), throwsStateError); - }); - - test('does not allow subject to be closed when addStream is active', - () async { - // ignore: close_sinks - final subject = PublishSubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.close(), throwsStateError); - }); - - test( - 'does not allow addStream to add items when previous addStream is active', - () async { - // ignore: close_sinks - final subject = PublishSubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.addStream(Stream.fromIterable(const [1])), - throwsStateError); - }); - - test('returns onListen callback set in constructor', () async { - void testOnListen() {} - // ignore: close_sinks - final subject = PublishSubject(onListen: testOnListen); - - await expectLater(subject.onListen, testOnListen); - }); - - test('sets onListen callback', () async { - void testOnListen() {} - // ignore: close_sinks - final subject = PublishSubject(); - - await expectLater(subject.onListen, isNull); - - subject.onListen = testOnListen; - - await expectLater(subject.onListen, testOnListen); - }); - - test('returns onCancel callback set in constructor', () async { - Future onCancel() => Future.value(null); - // ignore: close_sinks - final subject = PublishSubject(onCancel: onCancel); - - await expectLater(subject.onCancel, onCancel); - }); - - test('sets onCancel callback', () async { - void testOnCancel() {} - // ignore: close_sinks - final subject = PublishSubject(); - - await expectLater(subject.onCancel, isNull); - - subject.onCancel = testOnCancel; - - await expectLater(subject.onCancel, testOnCancel); - }); - - test('reports if a listener is present', () async { - // ignore: close_sinks - final subject = PublishSubject(); - - await expectLater(subject.hasListener, isFalse); - - subject.stream.listen(null); - - await expectLater(subject.hasListener, isTrue); - }); - - test('onPause unsupported', () { - // ignore: close_sinks - final subject = PublishSubject(); - - expect(subject.isPaused, isFalse); - expect(() => subject.onPause, throwsUnsupportedError); - expect(() => subject.onPause = () {}, throwsUnsupportedError); - }); - - test('onResume unsupported', () { - // ignore: close_sinks - final subject = PublishSubject(); - - expect(() => subject.onResume, throwsUnsupportedError); - expect(() => subject.onResume = () {}, throwsUnsupportedError); - }); - - test('returns controller sink', () async { - // ignore: close_sinks - final subject = PublishSubject(); - - await expectLater(subject.sink, TypeMatcher>()); - }); - - test('correctly closes done Future', () async { - final subject = PublishSubject(); - - scheduleMicrotask(() => subject.close()); - - await expectLater(subject.done, completes); - }); - - test('can be listened to multiple times', () async { - // ignore: close_sinks - final subject = PublishSubject(); - final stream = subject.stream; - - scheduleMicrotask(() => subject.add(1)); - await expectLater(stream, emits(1)); - - scheduleMicrotask(() => subject.add(2)); - await expectLater(stream, emits(2)); - }); - - test('always returns the same stream', () async { - // ignore: close_sinks - final subject = PublishSubject(); - - await expectLater(subject.stream, equals(subject.stream)); - }); - - test('adding to sink has same behavior as adding to Subject itself', - () async { - // ignore: close_sinks - final subject = PublishSubject(); - - scheduleMicrotask(() { - subject.sink.add(1); - subject.sink.add(2); - subject.sink.add(3); - subject.sink.close(); - }); - - await expectLater( - subject.stream, emitsInOrder([1, 2, 3, emitsDone])); - }); - - test('is always treated as a broadcast Stream', () async { - // ignore: close_sinks - final subject = PublishSubject(); - final stream = subject.asyncMap((event) => Future.value(event)); - - expect(subject.isBroadcast, isTrue); - expect(stream.isBroadcast, isTrue); - }); - - test('stream returns a read-only stream', () async { - final subject = PublishSubject(); - - // streams returned by PublishSubject are read-only stream, - // ie. they don't support adding events. - expect(subject.stream, isNot(isA>())); - expect(subject.stream, isNot(isA>())); - - // PublishSubject.stream is a broadcast stream - { - final stream = subject.stream; - expect(stream.isBroadcast, isTrue); - - scheduleMicrotask(() => subject.add(1)); - await expectLater(stream, emitsInOrder([1])); - - scheduleMicrotask(() => subject.add(1)); - await expectLater(stream, emitsInOrder([1])); - } - - // streams returned by the same subject are considered equal, - // but not identical - expect(identical(subject.stream, subject.stream), isFalse); - expect(subject.stream == subject.stream, isTrue); - }); - }); -} diff --git a/sandbox/reactivex/test/subject/replay_subject_test.dart b/sandbox/reactivex/test/subject/replay_subject_test.dart deleted file mode 100644 index 1d9e949..0000000 --- a/sandbox/reactivex/test/subject/replay_subject_test.dart +++ /dev/null @@ -1,478 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -// ignore_for_file: close_sinks - -void main() { - group('ReplaySubject', () { - test('replays the previously emitted items to every subscriber', () async { - final subject = ReplaySubject(); - - subject.add(1); - subject.add(2); - subject.add(3); - - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - }); - - test( - 'replays the previously emitted items to every subscriber, includes null', - () async { - final subject = ReplaySubject(); - - subject.add(null); - subject.add(1); - subject.add(2); - subject.add(3); - subject.add(null); - - await expectLater( - subject.stream, - emitsInOrder(const [null, 1, 2, 3, null]), - ); - await expectLater( - subject.stream, - emitsInOrder(const [null, 1, 2, 3, null]), - ); - await expectLater( - subject.stream, - emitsInOrder(const [null, 1, 2, 3, null]), - ); - }); - - test('replays the previously emitted errors to every subscriber', () async { - final subject = ReplaySubject(); - - subject.addError(Exception()); - subject.addError(Exception()); - subject.addError(Exception()); - - await expectLater( - subject.stream, - emitsInOrder([ - emitsError(isException), - emitsError(isException), - emitsError(isException) - ])); - await expectLater( - subject.stream, - emitsInOrder([ - emitsError(isException), - emitsError(isException), - emitsError(isException) - ])); - await expectLater( - subject.stream, - emitsInOrder([ - emitsError(isException), - emitsError(isException), - emitsError(isException) - ])); - }); - - test( - 'replays the previously emitted items to every subscriber that directly subscribes to the Subject', - () async { - final subject = ReplaySubject(); - - subject.add(1); - subject.add(2); - subject.add(3); - - await expectLater(subject, emitsInOrder(const [1, 2, 3])); - await expectLater(subject, emitsInOrder(const [1, 2, 3])); - await expectLater(subject, emitsInOrder(const [1, 2, 3])); - }); - - test( - 'replays the previously emitted items and errors to every subscriber that directly subscribes to the Subject', - () async { - final subject = ReplaySubject(); - - subject.add(1); - subject.addError(Exception()); - subject.addError(Exception()); - subject.add(2); - - await expectLater( - subject, - emitsInOrder([ - 1, - emitsError(isException), - emitsError(isException), - 2 - ])); - await expectLater( - subject, - emitsInOrder([ - 1, - emitsError(isException), - emitsError(isException), - 2 - ])); - await expectLater( - subject, - emitsInOrder([ - 1, - emitsError(isException), - emitsError(isException), - 2 - ])); - }); - - test('synchronously get the previous items', () async { - final subject = ReplaySubject(); - - subject.add(1); - subject.add(2); - subject.add(3); - - await expectLater(subject.values, const [1, 2, 3]); - }); - - test('synchronously get the previous errors', () { - final subject = ReplaySubject(); - final e1 = Exception(), e2 = Exception(), e3 = Exception(); - final stackTrace = StackTrace.fromString('#'); - - subject.addError(e1); - subject.addError(e2, stackTrace); - subject.addError(e3); - - expect( - subject.errors, - containsAllInOrder([e1, e2, e3]), - ); - expect( - subject.stackTraces, - containsAllInOrder([null, stackTrace, null]), - ); - }); - - test('replays the most recently emitted items up to a max size', () async { - final subject = ReplaySubject(maxSize: 2); - - subject.add(1); // Should be dropped - subject.add(2); - subject.add(3); - - await expectLater(subject.stream, emitsInOrder(const [2, 3])); - await expectLater(subject.stream, emitsInOrder(const [2, 3])); - await expectLater(subject.stream, emitsInOrder(const [2, 3])); - }); - - test('emits done event to listeners when the subject is closed', () async { - final subject = ReplaySubject(); - - await expectLater(subject.isClosed, isFalse); - - subject.add(1); - scheduleMicrotask(() => subject.close()); - - await expectLater(subject.stream, emitsInOrder([1, emitsDone])); - await expectLater(subject.isClosed, isTrue); - }); - - test('emits error events to subscribers', () async { - final subject = ReplaySubject(); - - scheduleMicrotask(() => subject.addError(Exception())); - - await expectLater(subject.stream, emitsError(isException)); - }); - - test('replays the previously emitted items from addStream', () async { - final subject = ReplaySubject(); - - await subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - }); - - test('allows items to be added once addStream is complete', () async { - final subject = ReplaySubject(); - - await subject.addStream(Stream.fromIterable(const [1, 2])); - subject.add(3); - - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - }); - - test('allows items to be added once addStream completes with an error', - () async { - final subject = ReplaySubject(); - - unawaited(subject - .addStream(Stream.error(Exception()), cancelOnError: true) - .whenComplete(() => subject.add(1))); - - await expectLater(subject.stream, - emitsInOrder([emitsError(isException), emits(1)])); - }); - - test('does not allow events to be added when addStream is active', - () async { - final subject = ReplaySubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.add(1), throwsStateError); - }); - - test('does not allow errors to be added when addStream is active', - () async { - final subject = ReplaySubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.addError(Error()), throwsStateError); - }); - - test('does not allow subject to be closed when addStream is active', - () async { - final subject = ReplaySubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.close(), throwsStateError); - }); - - test( - 'does not allow addStream to add items when previous addStream is active', - () async { - final subject = ReplaySubject(); - - // Purposely don't wait for the future to complete, then try to add items - // ignore: unawaited_futures - subject.addStream(Stream.fromIterable(const [1, 2, 3])); - - await expectLater(() => subject.addStream(Stream.fromIterable(const [1])), - throwsStateError); - }); - - test('returns onListen callback set in constructor', () async { - void testOnListen() {} - - final subject = ReplaySubject(onListen: testOnListen); - - await expectLater(subject.onListen, testOnListen); - }); - - test('sets onListen callback', () async { - void testOnListen() {} - - final subject = ReplaySubject(); - - await expectLater(subject.onListen, isNull); - - subject.onListen = testOnListen; - - await expectLater(subject.onListen, testOnListen); - }); - - test('returns onCancel callback set in constructor', () async { - Future onCancel() => Future.value(null); - - final subject = ReplaySubject(onCancel: onCancel); - - await expectLater(subject.onCancel, onCancel); - }); - - test('sets onCancel callback', () async { - void testOnCancel() {} - - final subject = ReplaySubject(); - - await expectLater(subject.onCancel, isNull); - - subject.onCancel = testOnCancel; - - await expectLater(subject.onCancel, testOnCancel); - }); - - test('reports if a listener is present', () async { - final subject = ReplaySubject(); - - await expectLater(subject.hasListener, isFalse); - - subject.stream.listen(null); - - await expectLater(subject.hasListener, isTrue); - }); - - test('onPause unsupported', () { - final subject = ReplaySubject(); - - expect(subject.isPaused, isFalse); - expect(() => subject.onPause, throwsUnsupportedError); - expect(() => subject.onPause = () {}, throwsUnsupportedError); - }); - - test('onResume unsupported', () { - final subject = ReplaySubject(); - - expect(() => subject.onResume, throwsUnsupportedError); - expect(() => subject.onResume = () {}, throwsUnsupportedError); - }); - - test('returns controller sink', () async { - final subject = ReplaySubject(); - - await expectLater(subject.sink, TypeMatcher>()); - }); - - test('correctly closes done Future', () async { - final subject = ReplaySubject(); - - scheduleMicrotask(subject.close); - - await expectLater(subject.done, completes); - }); - - test('can be listened to multiple times', () async { - final subject = ReplaySubject(); - final stream = subject.stream; - - subject.add(1); - subject.add(2); - - await expectLater(stream, emitsInOrder(const [1, 2])); - await expectLater(stream, emitsInOrder(const [1, 2])); - }); - - test('always returns the same stream', () async { - final subject = ReplaySubject(); - - await expectLater(subject.stream, equals(subject.stream)); - }); - - test('adding to sink has same behavior as adding to Subject itself', - () async { - final subject = ReplaySubject(); - - subject.sink.add(1); - subject.sink.add(2); - subject.sink.add(3); - - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - await expectLater(subject.stream, emitsInOrder(const [1, 2, 3])); - }); - - test('is always treated as a broadcast Stream', () async { - final subject = ReplaySubject(); - final stream = subject.asyncMap((event) => Future.value(event)); - - expect(subject.isBroadcast, isTrue); - expect(stream.isBroadcast, isTrue); - }); - - test('issue/419: sync behavior', () async { - final subject = ReplaySubject(sync: true)..add(1); - final mappedStream = subject.map((event) => event).shareValue(); - - mappedStream.listen(null); - - expect(mappedStream.value, equals(1)); - - await subject.close(); - }, skip: true); - - test('issue/419: sync throughput', () async { - final subject = ReplaySubject(sync: true)..add(1); - final mappedStream = subject.map((event) => event).shareValue(); - - mappedStream.listen(null); - - subject.add(2); - - expect(mappedStream.value, equals(2)); - - await subject.close(); - }, skip: true); - - test('issue/419: async behavior', () async { - final subject = ReplaySubject()..add(1); - final mappedStream = subject.map((event) => event).shareValue(); - - mappedStream.listen(null, - onDone: () => expect(mappedStream.value, equals(1))); - - expect(mappedStream.valueOrNull, isNull); - - await subject.close(); - }); - - test('issue/419: async throughput', () async { - final subject = ReplaySubject()..add(1); - final mappedStream = subject.map((event) => event).shareValue(); - - mappedStream.listen(null, - onDone: () => expect(mappedStream.value, equals(2))); - - subject.add(2); - - expect(mappedStream.valueOrNull, isNull); - - await subject.close(); - }); - - test('do not update buffer after closed', () { - final subject = ReplaySubject(); - - subject.add(1); - expect(subject.values, [1]); - - subject.close(); - - expect(() => subject.add(2), throwsStateError); - expect(() => subject.addError(Exception()), throwsStateError); - expect(subject.values, [1]); - }); - - test('stream returns a read-only stream', () async { - final subject = ReplaySubject()..add(1); - - // streams returned by ReplaySubject are read-only stream, - // ie. they don't support adding events. - expect(subject.stream, isNot(isA>())); - expect(subject.stream, isNot(isA>())); - - expect( - subject.stream, - isA>().having( - (v) => v.values, - 'ReplaySubject.stream.values', - [1], - ), - ); - - // ReplaySubject.stream is a broadcast stream - { - final stream = subject.stream; - expect(stream.isBroadcast, isTrue); - await expectLater(stream, emitsInOrder([1])); - await expectLater(stream, emitsInOrder([1])); - } - - // streams returned by the same subject are considered equal, - // but not identical - expect(identical(subject.stream, subject.stream), isFalse); - expect(subject.stream == subject.stream, isTrue); - }); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/buffer_count_test.dart b/sandbox/reactivex/test/transformers/backpressure/buffer_count_test.dart deleted file mode 100644 index 5e05ade..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/buffer_count_test.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -void main() { - test('Rx.bufferCount.noStartBufferEvery', () async { - await expectLater( - Rx.range(1, 4).bufferCount(2), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - }); - - test('Rx.bufferCount.noStartBufferEvery.includesEventOnClose', () async { - await expectLater( - Rx.range(1, 5).bufferCount(2), - emitsInOrder([ - const [1, 2], - const [3, 4], - const [5], - emitsDone - ])); - }); - - test('Rx.bufferCount.startBufferEvery.count2startBufferEvery1', () async { - await expectLater( - Rx.range(1, 4).bufferCount(2, 1), - emitsInOrder([ - const [1, 2], - const [2, 3], - const [3, 4], - const [4], - emitsDone - ])); - }); - - test('Rx.bufferCount.startBufferEvery.count3startBufferEvery2', () async { - await expectLater( - Rx.range(1, 8).bufferCount(3, 2), - emitsInOrder([ - const [1, 2, 3], - const [3, 4, 5], - const [5, 6, 7], - const [7, 8], - emitsDone - ])); - }); - - test('Rx.bufferCount.startBufferEvery.count3startBufferEvery4', () async { - await expectLater( - Rx.range(1, 8).bufferCount(3, 4), - emitsInOrder([ - const [1, 2, 3], - const [5, 6, 7], - emitsDone - ])); - }); - - test('Rx.bufferCount.reusable', () async { - final transformer = BufferCountStreamTransformer(2); - - await expectLater( - Stream.fromIterable(const [1, 2, 3, 4]).transform(transformer), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - - await expectLater( - Stream.fromIterable(const [1, 2, 3, 4]).transform(transformer), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - }); - - test('Rx.bufferCount.asBroadcastStream', () async { - final stream = Stream.fromIterable(const [1, 2, 3, 4]) - .asBroadcastStream() - .bufferCount(2); - - // listen twice on same stream - await expectLater( - stream, - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - - await expectLater(stream, emitsInOrder([emitsDone])); - }); - - test('Rx.bufferCount.error.shouldThrowA', () async { - await expectLater(Stream.error(Exception()).bufferCount(2), - emitsError(isException)); - }); - - test( - 'Rx.bufferCount.shouldThrow.invalidCount.negative', - () { - expect(() => Stream.fromIterable(const [1, 2, 3, 4]).bufferCount(-1), - throwsArgumentError); - }, - ); - - test('Rx.bufferCount.startBufferEvery.shouldThrow.invalidStartBufferEvery', - () { - expect(() => Stream.fromIterable(const [1, 2, 3, 4]).bufferCount(2, -1), - throwsArgumentError); - }); - - test('Rx.bufferCount.nullable', () { - nullableTest>( - (s) => s.bufferCount(1), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/buffer_test.dart b/sandbox/reactivex/test/transformers/backpressure/buffer_test.dart deleted file mode 100644 index 095d657..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/buffer_test.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -Stream getStream(int n) async* { - var k = 0; - - while (k < n) { - await Future.delayed(const Duration(milliseconds: 100)); - - yield k++; - } -} - -void main() { - test('Rx.buffer', () async { - await expectLater( - getStream(4).buffer( - Stream.periodic(const Duration(milliseconds: 160)).take(3)), - emitsInOrder([ - const [0, 1], - const [2, 3], - emitsDone - ])); - }); - - test('Rx.buffer.sampleBeforeEvent.shouldEmit', () async { - await expectLater( - Stream.fromFuture( - Future.delayed(const Duration(milliseconds: 200)) - .then((_) => 'end')).startWith('start').buffer( - Stream.periodic(const Duration(milliseconds: 40)).take(10)), - emitsInOrder([ - const ['start'], // after 40ms - const [], // 80ms - const [], // 120ms - const [], // 160ms - const ['end'], // done - emitsDone - ])); - }); - - test('Rx.buffer.shouldClose', () async { - final controller = StreamController() - ..add(0) - ..add(1) - ..add(2) - ..add(3); - - scheduleMicrotask(controller.close); - - await expectLater( - controller.stream - .buffer(Stream.periodic(const Duration(seconds: 3))) - .take(1), - emitsInOrder([ - const [0, 1, 2, 3], // done - emitsDone - ])); - }); - - test('Rx.buffer.reusable', () async { - final transformer = BufferStreamTransformer((_) => - Stream.periodic(const Duration(milliseconds: 160)) - .take(3) - .asBroadcastStream()); - - await expectLater( - getStream(4).transform(transformer), - emitsInOrder([ - const [0, 1], - const [2, 3], - emitsDone - ])); - - await expectLater( - getStream(4).transform(transformer), - emitsInOrder([ - const [0, 1], - const [2, 3], - emitsDone - ])); - }); - - test('Rx.buffer.asBroadcastStream', () async { - final stream = getStream(4).asBroadcastStream().buffer( - Stream.periodic(const Duration(milliseconds: 160)) - .take(10) - .asBroadcastStream()); - - // listen twice on same stream - await expectLater( - stream, - emitsInOrder([ - const [0, 1], - const [2, 3], - emitsDone - ])); - - await expectLater(stream, emitsInOrder([emitsDone])); - }); - - test('Rx.buffer.error.shouldThrowA', () async { - await expectLater( - Stream.error(Exception()) - .buffer(Stream.periodic(const Duration(milliseconds: 160))), - emitsError(isException)); - }); - - test('Rx.buffer.nullable', () { - nullableTest>( - (s) => s.buffer(Stream.empty()), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/buffer_test_test.dart b/sandbox/reactivex/test/transformers/backpressure/buffer_test_test.dart deleted file mode 100644 index 0d08c4c..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/buffer_test_test.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -void main() { - test('Rx.bufferTest', () async { - await expectLater( - Rx.range(1, 4).bufferTest((i) => i % 2 == 0), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - }); - - test('Rx.bufferTest.reusable', () async { - final transformer = BufferTestStreamTransformer((i) => i % 2 == 0); - - await expectLater( - Stream.fromIterable(const [1, 2, 3, 4]).transform(transformer), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - - await expectLater( - Stream.fromIterable(const [1, 2, 3, 4]).transform(transformer), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - }); - - test('Rx.bufferTest.asBroadcastStream', () async { - final stream = Stream.fromIterable(const [1, 2, 3, 4]) - .asBroadcastStream() - .bufferTest((i) => i % 2 == 0); - - // listen twice on same stream - await expectLater( - stream, - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - - await expectLater(stream, emitsDone); - }); - - test('Rx.bufferTest.error.shouldThrowA', () async { - await expectLater( - Stream.error(Exception()).bufferTest((i) => i % 2 == 0), - emitsError(isException)); - }); - - test('Rx.bufferTest.nullable', () { - nullableTest>( - (s) => s.bufferTest((i) => true), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/buffer_time_test.dart b/sandbox/reactivex/test/transformers/backpressure/buffer_time_test.dart deleted file mode 100644 index 8feea7d..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/buffer_time_test.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -/// yield immediately, then every 100ms -Stream getStream(int n) async* { - var k = 1; - - yield 0; - - while (k < n) { - yield await Future.delayed(const Duration(milliseconds: 100)) - .then((_) => k++); - } -} - -void main() { - test('Rx.bufferTime', () async { - await expectLater( - getStream(4).bufferTime(const Duration(milliseconds: 160)), - emitsInOrder([ - const [0, 1], - const [2, 3], - emitsDone - ])); - }); - - test('Rx.bufferTime.shouldClose', () async { - final controller = StreamController() - ..add(0) - ..add(1) - ..add(2) - ..add(3); - - scheduleMicrotask(controller.close); - - await expectLater( - controller.stream.bufferTime(const Duration(seconds: 3)).take(1), - emitsInOrder([ - const [0, 1, 2, 3], // done - emitsDone - ])); - }); - - test('Rx.bufferTime.reusable', () async { - final transformer = BufferStreamTransformer( - (_) => Stream.periodic(const Duration(milliseconds: 160))); - - await expectLater( - getStream(4).transform(transformer), - emitsInOrder([ - const [0, 1], const [2, 3], // done - emitsDone - ])); - - await expectLater( - getStream(4).transform(transformer), - emitsInOrder([ - const [0, 1], const [2, 3], // done - emitsDone - ])); - }); - - test('Rx.bufferTime.asBroadcastStream', () async { - final stream = getStream(4) - .asBroadcastStream() - .bufferTime(const Duration(milliseconds: 160)); - - // listen twice on same stream - await expectLater( - stream, - emitsInOrder([ - const [0, 1], - const [2, 3], - emitsDone - ])); - - await expectLater(stream, emitsInOrder([emitsDone])); - }); - - test('Rx.bufferTime.error.shouldThrowA', () async { - await expectLater( - Stream.error(Exception()) - .bufferTime(const Duration(milliseconds: 160)), - emitsError(isException)); - }); - - test('Rx.bufferTime.nullable', () { - nullableTest>( - (s) => s.bufferTime(Duration.zero), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/debounce_test.dart b/sandbox/reactivex/test/transformers/backpressure/debounce_test.dart deleted file mode 100644 index e8bc075..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/debounce_test.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -Stream _getStream() { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 100), () => controller.add(1)); - Timer(const Duration(milliseconds: 200), () => controller.add(2)); - Timer(const Duration(milliseconds: 300), () => controller.add(3)); - Timer(const Duration(milliseconds: 400), () { - controller.add(4); - controller.close(); - }); - - return controller.stream; -} - -void main() { - test('Rx.debounce', () async { - await expectLater( - _getStream().debounce((_) => Stream.fromFuture( - Future.delayed(const Duration(milliseconds: 200)))), - emitsInOrder([4, emitsDone])); - }); - - test('Rx.debounce.dynamicWindow', () async { - // Given the input [1, 2, 3, 4] - // debounce 200ms on [1, 2, 4] - // debounce 0ms on [3] - // yields [3, 4, done] - await expectLater( - _getStream().debounce((value) => value == 3 - ? Stream.value(true) - : Stream.fromFuture( - Future.delayed(const Duration(milliseconds: 200)))), - emitsInOrder([3, 4, emitsDone])); - }); - - test('Rx.debounce.reusable', () async { - final transformer = DebounceStreamTransformer( - (_) => Stream.periodic(const Duration(milliseconds: 200))); - - await expectLater(_getStream().transform(transformer), - emitsInOrder([4, emitsDone])); - - await expectLater(_getStream().transform(transformer), - emitsInOrder([4, emitsDone])); - }); - - test('Rx.debounce.asBroadcastStream', () async { - final future = _getStream() - .asBroadcastStream() - .debounce((_) => Stream.fromFuture( - Future.delayed(const Duration(milliseconds: 200)))) - .drain(); - - await expectLater(future, completes); - await expectLater(future, completes); - }); - - test('Rx.debounce.error.shouldThrowA', () async { - await expectLater( - Stream.error(Exception()).debounce((_) => Stream.fromFuture( - Future.delayed(const Duration(milliseconds: 200)))), - emitsError(isException)); - }); - - test('Rx.debounce.pause.resume', () async { - final controller = StreamController(); - late StreamSubscription subscription; - - subscription = Stream.fromIterable([1, 2, 3]) - .debounce((_) => Stream.fromFuture( - Future.delayed(const Duration(milliseconds: 200)))) - .listen(controller.add, onDone: () { - controller.close(); - subscription.cancel(); - }); - - subscription.pause(Future.delayed(const Duration(milliseconds: 50))); - - await expectLater(controller.stream, emitsInOrder([3, emitsDone])); - }); - - test('Rx.debounce.emits.last.item.immediately', () async { - final emissions = []; - final stopwatch = Stopwatch(); - final stream = Stream.fromIterable(const [1, 2, 3]).debounce((_) => - Stream.fromFuture( - Future.delayed(const Duration(milliseconds: 200)))); - late StreamSubscription subscription; - - stopwatch.start(); - - subscription = stream.listen( - expectAsync1((val) { - emissions.add(val); - }, count: 1), onDone: expectAsync0(() { - stopwatch.stop(); - - expect(emissions, const [3]); - - // We debounce for 100 seconds. To ensure we aren't waiting that long to - // emit the last item after the base stream completes, we expect the - // last value to be emitted to be much shorter than that. - expect(stopwatch.elapsedMilliseconds < 500, isTrue); - - subscription.cancel(); - })); - }, timeout: Timeout(Duration(seconds: 3))); - - test( - 'Rx.debounce.cancel.emits.nothing', - () async { - late StreamSubscription subscription; - final stream = Stream.fromIterable(const [1, 2, 3]).doOnDone(() { - subscription.cancel(); - }).debounce((_) => Stream.fromFuture( - Future.delayed(const Duration(milliseconds: 200)))); - - // We expect the onData callback to be called 0 times because the - // subscription is cancelled when the base stream ends. - subscription = stream.listen(expectAsync1((_) {}, count: 0)); - }, - timeout: Timeout(Duration(seconds: 3)), - ); - - test('Rx.debounce.last.event.can.be.null', () async { - await expectLater( - Stream.fromIterable([1, 2, 3, null]).debounce((_) => - Stream.fromFuture( - Future.delayed(const Duration(milliseconds: 200)))), - emitsInOrder([null, emitsDone])); - }); - - test('Rx.debounce.nullable', () { - nullableTest( - (s) => s.debounce((_) => Stream.empty()), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/debounce_time_test.dart b/sandbox/reactivex/test/transformers/backpressure/debounce_time_test.dart deleted file mode 100644 index 8501763..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/debounce_time_test.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -Stream _getStream() { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 100), () => controller.add(1)); - Timer(const Duration(milliseconds: 200), () => controller.add(2)); - Timer(const Duration(milliseconds: 300), () => controller.add(3)); - Timer(const Duration(milliseconds: 400), () { - controller.add(4); - controller.close(); - }); - - return controller.stream; -} - -void main() { - test('Rx.debounceTime', () async { - await expectLater( - _getStream().debounceTime(const Duration(milliseconds: 200)), - emitsInOrder([4, emitsDone])); - }); - - test('Rx.debounceTime.reusable', () async { - final transformer = DebounceStreamTransformer( - (_) => Stream.periodic(const Duration(milliseconds: 200))); - - await expectLater(_getStream().transform(transformer), - emitsInOrder([4, emitsDone])); - - await expectLater(_getStream().transform(transformer), - emitsInOrder([4, emitsDone])); - }); - - test('Rx.debounceTime.asBroadcastStream', () async { - final future = _getStream() - .asBroadcastStream() - .debounceTime(const Duration(milliseconds: 200)) - .drain(); - - await expectLater(future, completes); - await expectLater(future, completes); - }); - - test('Rx.debounceTime.error.shouldThrowA', () async { - await expectLater( - Stream.error(Exception()) - .debounceTime(const Duration(milliseconds: 200)), - emitsError(isException)); - }); - - test('Rx.debounceTime.pause.resume', () async { - final controller = StreamController(); - late StreamSubscription subscription; - - subscription = Stream.fromIterable([1, 2, 3]) - .debounceTime(Duration(milliseconds: 100)) - .listen(controller.add, onDone: () { - controller.close(); - subscription.cancel(); - }); - - subscription.pause(Future.delayed(const Duration(milliseconds: 50))); - - await expectLater(controller.stream, emitsInOrder([3, emitsDone])); - }); - - test('Rx.debounceTime.emits.last.item.immediately', () async { - final emissions = []; - final stopwatch = Stopwatch(); - final stream = Stream.fromIterable(const [1, 2, 3]) - .debounceTime(Duration(seconds: 100)); - late StreamSubscription subscription; - - stopwatch.start(); - - subscription = stream.listen( - expectAsync1((val) { - emissions.add(val); - }, count: 1), onDone: expectAsync0(() { - stopwatch.stop(); - - expect(emissions, const [3]); - - // We debounce for 100 seconds. To ensure we aren't waiting that long to - // emit the last item after the base stream completes, we expect the - // last value to be emitted to be much shorter than that. - expect(stopwatch.elapsedMilliseconds < 500, isTrue); - - subscription.cancel(); - })); - }, timeout: Timeout(Duration(seconds: 3))); - - test( - 'Rx.debounceTime.cancel.emits.nothing', - () async { - late StreamSubscription subscription; - final stream = Stream.fromIterable(const [1, 2, 3]).doOnDone(() { - subscription.cancel(); - }).debounceTime(Duration(seconds: 10)); - - // We expect the onData callback to be called 0 times because the - // subscription is cancelled when the base stream ends. - subscription = stream.listen(expectAsync1((_) {}, count: 0)); - }, - timeout: Timeout(Duration(seconds: 3)), - ); - - test('Rx.debounceTime.last.event.can.be.null', () async { - await expectLater( - Stream.fromIterable([1, 2, 3, null]) - .debounceTime(const Duration(milliseconds: 200)), - emitsInOrder([null, emitsDone])); - }); - - test('Rx.debounceTime.nullable', () { - nullableTest( - (s) => s.debounceTime(Duration.zero), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/pairwise_test.dart b/sandbox/reactivex/test/transformers/backpressure/pairwise_test.dart deleted file mode 100644 index da89fa0..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/pairwise_test.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -void main() { - test('Rx.pairwise', () async { - const expectedOutput = [ - [1, 2], - [2, 3], - [3, 4] - ]; - var count = 0; - - final stream = Rx.range(1, 4).pairwise(); - - stream.listen( - expectAsync1((result) { - // test to see if the combined output matches - expect(result, expectedOutput[count++]); - }, count: expectedOutput.length), - onError: expectAsync2((Object e, StackTrace s) {}, count: 0), - onDone: expectAsync0(() {}, count: 1), - ); - }); - - test('Rx.pairwise.empty', () { - expect(Stream.empty().pairwise(), emitsDone); - }); - - test('Rx.pairwise.single', () { - expect(Stream.value(1).pairwise(), emitsDone); - }); - - test('Rx.pairwise.compatible', () { - expect( - Stream.fromIterable([1, 2]).pairwise(), - isA>>(), - ); - - Stream> s = Stream.fromIterable([1, 2]).pairwise(); - expect( - s, - emitsInOrder([ - [1, 2], - emitsDone - ]), - ); - }); - - test('Rx.pairwise.asBroadcastStream', () async { - final stream = - Stream.fromIterable(const [1, 2, 3, 4]).asBroadcastStream().pairwise(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.pairwise.error.shouldThrow.onError', () async { - final streamWithError = Stream.error(Exception()).pairwise(); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.pairwise.nullable', () { - nullableTest>( - (s) => s.pairwise(), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/sample_test.dart b/sandbox/reactivex/test/transformers/backpressure/sample_test.dart deleted file mode 100644 index b0137d3..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/sample_test.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -Stream _getStream() => - Stream.periodic(const Duration(milliseconds: 20), (count) => count) - .take(5); - -Stream _getSampleStream() => - Stream.periodic(const Duration(milliseconds: 35), (count) => count) - .take(10); - -void main() { - test('Rx.sample', () async { - final stream = _getStream().sample(_getSampleStream()); - - await expectLater(stream, emitsInOrder([1, 3, 4, emitsDone])); - }); - - test('Rx.sample.reusable', () async { - final transformer = SampleStreamTransformer( - (_) => _getSampleStream().asBroadcastStream()); - final streamA = _getStream().transform(transformer); - final streamB = _getStream().transform(transformer); - - await expectLater(streamA, emitsInOrder([1, 3, 4, emitsDone])); - await expectLater(streamB, emitsInOrder([1, 3, 4, emitsDone])); - }, skip: true); - - test('Rx.sample.onDone', () async { - final stream = Stream.value(1).sample(Stream.empty()); - - await expectLater(stream, emits(1)); - }); - - test('Rx.sample.shouldClose', () async { - final controller = StreamController(); - - controller.stream - .sample(Stream.empty()) // should trigger onDone - .listen(null, onDone: expectAsync0(() => expect(true, isTrue))); - - controller.add(0); - controller.add(1); - controller.add(2); - controller.add(3); - - scheduleMicrotask(controller.close); - }); - - test('Rx.sample.asBroadcastStream', () async { - final stream = _getStream() - .asBroadcastStream() - .sample(_getSampleStream().asBroadcastStream()); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.sample.error.shouldThrowA', () async { - final streamWithError = - Stream.error(Exception()).sample(_getSampleStream()); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.sample.error.shouldThrowB', () async { - final streamWithError = Stream.value(1) - .sample(Stream.error(Exception('Catch me if you can!'))); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.sample.pause.resume', () async { - final controller = StreamController(); - late StreamSubscription subscription; - - subscription = _getStream() - .sample(_getSampleStream()) - .listen(controller.add, onDone: () { - controller.close(); - subscription.cancel(); - }); - - await expectLater( - controller.stream, emitsInOrder([1, 3, 4, emitsDone])); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.sample.nullable', () { - nullableTest( - (s) => s.sample(_getSampleStream()), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/sample_time_test.dart b/sandbox/reactivex/test/transformers/backpressure/sample_time_test.dart deleted file mode 100644 index f6c0386..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/sample_time_test.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -Stream _getStream() => - Stream.periodic(const Duration(milliseconds: 20), (count) => count) - .take(5); - -void main() { - test('Rx.sampleTime', () async { - final stream = _getStream().sampleTime(const Duration(milliseconds: 35)); - - await expectLater(stream, emitsInOrder([1, 3, 4, emitsDone])); - }); - - test('Rx.sampleTime.reusable', () async { - final transformer = SampleStreamTransformer((_) => - TimerStream(true, const Duration(milliseconds: 35)) - .asBroadcastStream()); - - await expectLater( - _getStream().transform(transformer).drain(), - completes, - ); - await expectLater( - _getStream().transform(transformer).drain(), - completes, - ); - }); - - test('Rx.sampleTime.onDone', () async { - final stream = Stream.value(1).sampleTime(const Duration(seconds: 1)); - - await expectLater(stream, emits(1)); - }); - - test('Rx.sampleTime.shouldClose', () async { - final controller = StreamController(); - - controller.stream - .sampleTime(const Duration(seconds: 1)) // should trigger onDone - .listen(null, onDone: expectAsync0(() => expect(true, isTrue))); - - controller.add(0); - controller.add(1); - controller.add(2); - controller.add(3); - - scheduleMicrotask(controller.close); - }); - - test('Rx.sampleTime.asBroadcastStream', () async { - final stream = _getStream() - .sampleTime(const Duration(milliseconds: 35)) - .asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.sampleTime.error.shouldThrowA', () async { - final streamWithError = Stream.error(Exception()) - .sampleTime(const Duration(milliseconds: 35)); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.sampleTime.pause.resume', () async { - final controller = StreamController(); - late StreamSubscription subscription; - - subscription = _getStream() - .sampleTime(const Duration(milliseconds: 35)) - .listen(controller.add, onDone: () { - controller.close(); - subscription.cancel(); - }); - - subscription.pause(Future.delayed(const Duration(milliseconds: 50))); - - await expectLater( - controller.stream, emitsInOrder([1, 3, 4, emitsDone])); - }); - - test('Rx.sampleTime.nullable', () { - nullableTest( - (s) => s.sampleTime(Duration.zero), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/throttle_test.dart b/sandbox/reactivex/test/transformers/backpressure/throttle_test.dart deleted file mode 100644 index 3a125e8..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/throttle_test.dart +++ /dev/null @@ -1,160 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -Stream _stream() => - Stream.periodic(const Duration(milliseconds: 100), (i) => i + 1).take(10); - -void main() { - test('Rx.throttle', () async { - await expectLater( - _stream() - .throttle( - (_) => Stream.periodic(const Duration(milliseconds: 250))) - .take(3), - emitsInOrder([1, 4, 7, emitsDone])); - }); - - test('Rx.throttle.trailing', () async { - await expectLater( - _stream() - .throttle( - (_) => Stream.periodic(const Duration(milliseconds: 250)), - trailing: true, - leading: false) - .take(3), - emitsInOrder([3, 6, 9, emitsDone])); - }); - - test('Rx.throttle.dynamic.window', () async { - await expectLater( - _stream() - .throttle((value) => value == 1 - ? Stream.periodic(const Duration(milliseconds: 10)) - : Stream.periodic(const Duration(milliseconds: 250))) - .take(3), - emitsInOrder([1, 2, 5, emitsDone])); - }); - - test('Rx.throttle.dynamic.window.trailing', () async { - await expectLater( - _stream() - .throttle( - (value) => value == 1 - ? Stream.periodic(const Duration(milliseconds: 10)) - : Stream.periodic(const Duration(milliseconds: 250)), - trailing: true, - leading: false) - .take(3), - emitsInOrder([1, 4, 7, emitsDone])); - }); - - test('Rx.throttle.leading.trailing.1', () async { - // --1--2--3--4--5--6--7--8--9--10--11| - // --1-----3--4-----6--7-----9--10-----11| - // --^--------^--------^---------^----- - - final values = []; - - final stream = _stream() - .concatWith([Rx.timer(11, const Duration(milliseconds: 100))]).throttle( - (v) { - values.add(v); - return Stream.periodic(const Duration(milliseconds: 250)); - }, - leading: true, - trailing: true, - ); - await expectLater( - stream, - emitsInOrder([1, 3, 4, 6, 7, 9, 10, 11, emitsDone]), - ); - expect(values, [1, 4, 7, 10]); - }); - - test('Rx.throttle.leading.trailing.2', () async { - // --1--2--3--4--5--6--7--8--9--10--11| - // --1-----3--4-----6--7-----9--10-----11| - // --^--------^--------^---------^----- - - final values = []; - - final stream = _stream().throttle( - (v) { - values.add(v); - return Stream.periodic(const Duration(milliseconds: 250)); - }, - leading: true, - trailing: true, - ); - await expectLater( - stream, - emitsInOrder([1, 3, 4, 6, 7, 9, 10, emitsDone]), - ); - expect(values, [1, 4, 7, 10]); - }); - - test('Rx.throttle.reusable', () async { - final transformer = ThrottleStreamTransformer( - (_) => Stream.periodic(const Duration(milliseconds: 250))); - - await expectLater(_stream().transform(transformer).take(2), - emitsInOrder([1, 4, emitsDone])); - - await expectLater(_stream().transform(transformer).take(2), - emitsInOrder([1, 4, emitsDone])); - }); - - test('Rx.throttle.asBroadcastStream', () async { - final future = _stream() - .asBroadcastStream() - .throttle( - (_) => Stream.periodic(const Duration(milliseconds: 250))) - .drain(); - - // listen twice on same stream - await expectLater(future, completes); - await expectLater(future, completes); - }); - - test('Rx.throttle.error.shouldThrowA', () async { - final streamWithError = Stream.error(Exception()).throttle( - (_) => Stream.periodic(const Duration(milliseconds: 250))); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.throttle.pause.resume', () async { - late StreamSubscription subscription; - - final controller = StreamController(); - - subscription = _stream() - .throttle( - (_) => Stream.periodic(const Duration(milliseconds: 250))) - .take(2) - .listen(controller.add, onDone: () { - controller.close(); - subscription.cancel(); - }); - - await expectLater( - controller.stream, emitsInOrder([1, 4, emitsDone])); - - await Future.delayed(const Duration(milliseconds: 150)).whenComplete( - () => subscription - .pause(Future.delayed(const Duration(milliseconds: 150)))); - }); - - test('Rx.throttle.nullable', () { - nullableTest( - (s) => s.throttle((_) => Stream.empty()), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/throttle_time_test.dart b/sandbox/reactivex/test/transformers/backpressure/throttle_time_test.dart deleted file mode 100644 index a0d55bf..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/throttle_time_test.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -Stream _stream() => - Stream.periodic(const Duration(milliseconds: 100), (i) => i + 1).take(10); - -void main() { - test('Rx.throttleTime', () async { - await expectLater( - _stream().throttleTime(const Duration(milliseconds: 250)).take(3), - emitsInOrder([1, 4, 7, emitsDone])); - }); - - test('Rx.throttleTime.trailing', () async { - await expectLater( - _stream() - .throttleTime(const Duration(milliseconds: 250), - trailing: true, leading: false) - .take(3), - emitsInOrder([3, 6, 9, emitsDone])); - }); - - test('Rx.throttleTime.reusable', () async { - final transformer = ThrottleStreamTransformer( - (_) => Stream.periodic(const Duration(milliseconds: 250))); - - await expectLater(_stream().transform(transformer).take(2), - emitsInOrder([1, 4, emitsDone])); - - await expectLater(_stream().transform(transformer).take(2), - emitsInOrder([1, 4, emitsDone])); - }); - - test('Rx.throttleTime.asBroadcastStream', () async { - final future = _stream() - .asBroadcastStream() - .throttleTime(const Duration(milliseconds: 250)) - .drain(); - - // listen twice on same stream - await expectLater(future, completes); - await expectLater(future, completes); - }); - - test('Rx.throttleTime.error.shouldThrowA', () async { - final streamWithError = Stream.error(Exception()) - .throttleTime(const Duration(milliseconds: 200)); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.throttleTime.pause.resume', () async { - late StreamSubscription subscription; - - final controller = StreamController(); - - subscription = _stream() - .throttleTime(const Duration(milliseconds: 250)) - .take(2) - .listen(controller.add, onDone: () { - controller.close(); - subscription.cancel(); - }); - - await expectLater( - controller.stream, emitsInOrder([1, 4, emitsDone])); - - await Future.delayed(const Duration(milliseconds: 150)).whenComplete( - () => subscription - .pause(Future.delayed(const Duration(milliseconds: 150)))); - }); - - test('issue/417 trailing true', () async { - await expectLater( - Stream.fromIterable([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - .interval(Duration(milliseconds: 25)) - .throttleTime(Duration(milliseconds: 50), - trailing: true, leading: false), - emitsInOrder([1, 3, 5, 7, 9, emitsDone])); - }); - - test('issue/417 trailing false', () async { - await expectLater( - Stream.fromIterable([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - .interval(Duration(milliseconds: 25)) - .throttleTime(Duration(milliseconds: 50), trailing: false), - emitsInOrder([0, 2, 4, 6, 8, emitsDone])); - }); - - test('Rx.throttleTime.nullable', () { - nullableTest( - (s) => s.throttleTime(Duration.zero), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/window_count_test.dart b/sandbox/reactivex/test/transformers/backpressure/window_count_test.dart deleted file mode 100644 index 58d252f..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/window_count_test.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -void main() { - test('Rx.windowCount.noStartBufferEvery', () async { - await expectLater( - Rx.range(1, 4).windowCount(2).asyncMap((stream) => stream.toList()), - emitsInOrder([ - [1, 2], - [3, 4], - emitsDone - ])); - }); - - test('Rx.windowCount.noStartBufferEvery.includesEventOnClose', () async { - await expectLater( - Rx.range(1, 5).windowCount(2).asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [1, 2], - const [3, 4], - const [5], - emitsDone - ])); - }); - - test('Rx.windowCount.startBufferEvery.count2startBufferEvery1', () async { - await expectLater( - Rx.range(1, 4).windowCount(2, 1).asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [1, 2], - const [2, 3], - const [3, 4], - const [4], - emitsDone - ])); - }); - - test('Rx.windowCount.startBufferEvery.count3startBufferEvery2', () async { - await expectLater( - Rx.range(1, 8).windowCount(3, 2).asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [1, 2, 3], - const [3, 4, 5], - const [5, 6, 7], - const [7, 8], - emitsDone - ])); - }); - - test('Rx.windowCount.startBufferEvery.count3startBufferEvery4', () async { - await expectLater( - Rx.range(1, 8).windowCount(3, 4).asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [1, 2, 3], - const [5, 6, 7], - emitsDone - ])); - }); - - test('Rx.windowCount.reusable', () async { - final transformer = WindowCountStreamTransformer(2); - - await expectLater( - Stream.fromIterable(const [1, 2, 3, 4]) - .transform(transformer) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - - await expectLater( - Stream.fromIterable(const [1, 2, 3, 4]) - .transform(transformer) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - }); - - test('Rx.windowCount.asBroadcastStream', () async { - final future = Stream.fromIterable(const [1, 2, 3, 4]) - .asBroadcastStream() - .windowCount(2) - .drain(); - - // listen twice on same stream - await expectLater(future, completes); - await expectLater(future, completes); - }); - - test('Rx.windowCount.error.shouldThrowA', () async { - await expectLater( - Stream.error(Exception()).windowCount(2), - emitsError(isException), - ); - }); - - test( - 'Rx.windowCount.shouldThrow.invalidCount.negative', - () { - expect(() => Stream.fromIterable(const [1, 2, 3, 4]).windowCount(-1), - throwsArgumentError); - }, - ); - - test('Rx.windowCount.startBufferEvery.shouldThrow.invalidStartBufferEvery', - () { - expect(() => Stream.fromIterable(const [1, 2, 3, 4]).windowCount(2, -1), - throwsArgumentError); - }); - - test('Rx.windowCount.nullable', () { - nullableTest>( - (s) => s.windowCount(2), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/window_test.dart b/sandbox/reactivex/test/transformers/backpressure/window_test.dart deleted file mode 100644 index 33dba73..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/window_test.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -Stream getStream(int n) async* { - var k = 0; - - while (k < n) { - await Future.delayed(const Duration(milliseconds: 100)); - - yield k++; - } -} - -void main() { - test('Rx.window', () async { - await expectLater( - getStream(4) - .window(Stream.periodic(const Duration(milliseconds: 160)) - .take(3)) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [0, 1], - const [2, 3], - emitsDone - ])); - }); - - test('Rx.window.sampleBeforeEvent.shouldEmit', () async { - await expectLater( - Stream.fromFuture( - Future.delayed(const Duration(milliseconds: 200)) - .then((_) => 'end')) - .startWith('start') - .window(Stream.periodic(const Duration(milliseconds: 40)) - .take(10)) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const ['start'], // after 40ms - const [], // 80ms - const [], // 120ms - const [], // 160ms - const ['end'], // done - emitsDone - ])); - }); - - test('Rx.window.shouldClose', () async { - final controller = StreamController() - ..add(0) - ..add(1) - ..add(2) - ..add(3); - - scheduleMicrotask(controller.close); - - await expectLater( - controller.stream - .window(Stream.periodic(const Duration(seconds: 3))) - .asyncMap((stream) => stream.toList()) - .take(1), - emitsInOrder([ - const [0, 1, 2, 3], // done - emitsDone - ])); - }); - - test('Rx.window.reusable', () async { - final transformer = WindowStreamTransformer((_) => - Stream.periodic(const Duration(milliseconds: 160)) - .take(3) - .asBroadcastStream()); - - await expectLater( - getStream(4) - .transform(transformer) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [0, 1], - const [2, 3], - emitsDone - ])); - - await expectLater( - getStream(4) - .transform(transformer) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [0, 1], - const [2, 3], - emitsDone - ])); - }); - - test('Rx.window.asBroadcastStream', () async { - final future = getStream(4) - .asBroadcastStream() - .window(Stream.periodic(const Duration(milliseconds: 160)) - .take(10) - .asBroadcastStream()) - .drain(); - - // listen twice on same stream - await expectLater(future, completes); - await expectLater(future, completes); - }); - - test('Rx.window.error.shouldThrowA', () async { - await expectLater( - Stream.error(Exception()) - .window(Stream.periodic(const Duration(milliseconds: 160))), - emitsError(isException)); - }); - - test('Rx.window.nullable', () { - nullableTest>( - (s) => s.window(Stream.empty()), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/window_test_test.dart b/sandbox/reactivex/test/transformers/backpressure/window_test_test.dart deleted file mode 100644 index b59f8f7..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/window_test_test.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -void main() { - test('Rx.windowTest', () async { - await expectLater( - Rx.range(1, 4) - .windowTest((i) => i % 2 == 0) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - }); - - test('Rx.windowTest.reusable', () async { - final transformer = WindowTestStreamTransformer((i) => i % 2 == 0); - - await expectLater( - Stream.fromIterable(const [1, 2, 3, 4]) - .transform(transformer) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - - await expectLater( - Stream.fromIterable(const [1, 2, 3, 4]) - .transform(transformer) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [1, 2], - const [3, 4], - emitsDone - ])); - }); - - test('Rx.windowTest.asBroadcastStream', () async { - final future = Stream.fromIterable(const [1, 2, 3, 4]) - .asBroadcastStream() - .windowTest((i) => i % 2 == 0) - .drain(); - - // listen twice on same stream - await expectLater(future, completes); - await expectLater(future, completes); - }); - - test('Rx.windowTest.error.shouldThrowA', () async { - await expectLater( - Stream.error(Exception()).windowTest((i) => i % 2 == 0), - emitsError(isException)); - }); - - test('Rx.windowTest.nullable', () { - nullableTest>( - (s) => s.windowTest((_) => true), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/backpressure/window_time_test.dart b/sandbox/reactivex/test/transformers/backpressure/window_time_test.dart deleted file mode 100644 index 8c0e1f4..0000000 --- a/sandbox/reactivex/test/transformers/backpressure/window_time_test.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../../utils.dart'; - -/// yield immediately, then every 100ms -Stream getStream(int n) async* { - var k = 1; - - yield 0; - - while (k < n) { - yield await Future.delayed(const Duration(milliseconds: 100)) - .then((_) => k++); - } -} - -void main() { - test('Rx.windowTime', () async { - await expectLater( - getStream(4) - .windowTime(const Duration(milliseconds: 160)) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [0, 1], - const [2, 3], - emitsDone - ])); - }); - - test('Rx.windowTime.shouldClose', () async { - final controller = StreamController() - ..add(0) - ..add(1) - ..add(2) - ..add(3); - - scheduleMicrotask(controller.close); - - await expectLater( - controller.stream - .windowTime(const Duration(seconds: 3)) - .asyncMap((stream) => stream.toList()) - .take(1), - emitsInOrder([ - const [0, 1, 2, 3], // done - emitsDone - ])); - }); - - test('Rx.windowTime.reusable', () async { - final transformer = WindowStreamTransformer( - (_) => Stream.periodic(const Duration(milliseconds: 160))); - - await expectLater( - getStream(4) - .transform(transformer) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [0, 1], const [2, 3], // done - emitsDone - ])); - - await expectLater( - getStream(4) - .transform(transformer) - .asyncMap((stream) => stream.toList()), - emitsInOrder([ - const [0, 1], const [2, 3], // done - emitsDone - ])); - }); - - test('Rx.windowTime.asBroadcastStream', () async { - final future = getStream(4) - .asBroadcastStream() - .windowTime(const Duration(milliseconds: 160)) - .drain(); - - // listen twice on same stream - await expectLater(future, completes); - await expectLater(future, completes); - }); - - test('Rx.windowTime.error.shouldThrowA', () async { - await expectLater( - Stream.error(Exception()) - .windowTime(const Duration(milliseconds: 160)), - emitsError(isException)); - }); - - test('Rx.windowTime.nullable', () { - nullableTest>( - (s) => s.windowTime(Duration.zero), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/concat_with_test.dart b/sandbox/reactivex/test/transformers/concat_with_test.dart deleted file mode 100644 index 6535b42..0000000 --- a/sandbox/reactivex/test/transformers/concat_with_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.concatWith', () async { - final delayedStream = Rx.timer(1, Duration(milliseconds: 10)); - final immediateStream = Stream.value(2); - const expected = [1, 2]; - var count = 0; - - delayedStream.concatWith([immediateStream]).listen(expectAsync1((result) { - expect(result, expected[count++]); - }, count: expected.length)); - }); - test('Rx.concatWith accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.concatWith([Stream.empty()]); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.concatWith on broadcast stream should stay broadcast ', () async { - final delayedStream = - Rx.timer(1, Duration(milliseconds: 10)).asBroadcastStream(); - final immediateStream = Stream.value(2); - final expected = [1, 2, emitsDone]; - - final concatenatedStream = delayedStream.concatWith([immediateStream]); - - expect(concatenatedStream.isBroadcast, isTrue); - expect(concatenatedStream, emitsInOrder(expected)); - }); -} diff --git a/sandbox/reactivex/test/transformers/default_if_empty_test.dart b/sandbox/reactivex/test/transformers/default_if_empty_test.dart deleted file mode 100644 index d6325a1..0000000 --- a/sandbox/reactivex/test/transformers/default_if_empty_test.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.defaultIfEmpty.whenEmpty', () async { - Stream.empty() - .defaultIfEmpty(true) - .listen(expectAsync1((bool result) { - expect(result, true); - }, count: 1)); - }); - - test('Rx.defaultIfEmpty.reusable', () async { - final transformer = DefaultIfEmptyStreamTransformer(true); - - Stream.empty().transform(transformer).listen(expectAsync1((result) { - expect(result, true); - }, count: 1)); - - Stream.empty().transform(transformer).listen(expectAsync1((result) { - expect(result, true); - }, count: 1)); - }); - - test('Rx.defaultIfEmpty.whenNotEmpty', () async { - Stream.fromIterable(const [false, false, false]) - .defaultIfEmpty(true) - .listen(expectAsync1((result) { - expect(result, false); - }, count: 3)); - }); - - test('Rx.defaultIfEmpty.asBroadcastStream', () async { - final stream = Stream.fromIterable(const []) - .defaultIfEmpty(-1) - .asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.defaultIfEmpty.error.shouldThrow', () async { - final streamWithError = Stream.error(Exception()).defaultIfEmpty(-1); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.defaultIfEmpty.pause.resume', () async { - late StreamSubscription subscription; - final stream = Stream.fromIterable(const []).defaultIfEmpty(1); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); - test('Rx.defaultIfEmpty accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.defaultIfEmpty(1); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.defaultIfEmpty.nullable', () { - nullableTest( - (s) => s.defaultIfEmpty(null), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/delay_test.dart b/sandbox/reactivex/test/transformers/delay_test.dart deleted file mode 100644 index 3c24d34..0000000 --- a/sandbox/reactivex/test/transformers/delay_test.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() => Stream.fromIterable(const [1, 2, 3, 4]); - -void main() { - test('Rx.delay', () async { - var value = 1; - _getStream() - .delay(const Duration(milliseconds: 200)) - .listen(expectAsync1((result) { - expect(result, value++); - }, count: 4)); - }); - - test('Rx.delay.zero', () { - expect( - _getStream().delay(Duration.zero), - emitsInOrder([1, 2, 3, 4]), - ); - }); - - test('Rx.delay.shouldBeDelayed', () async { - var value = 1; - _getStream() - .delay(const Duration(milliseconds: 500)) - .timeInterval() - .listen(expectAsync1((result) { - expect(result.value, value++); - - if (result.value == 1) { - expect(result.interval.inMilliseconds, - greaterThanOrEqualTo(500)); // should be delayed - } else { - expect(result.interval.inMilliseconds, - lessThanOrEqualTo(20)); // should be near instantaneous - } - }, count: 4)); - }); - - test('Rx.delay.reusable', () async { - final transformer = - DelayStreamTransformer(const Duration(milliseconds: 200)); - var valueA = 1, valueB = 1; - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(result, valueA++); - }, count: 4)); - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(result, valueB++); - }, count: 4)); - }); - - test('Rx.delay.asBroadcastStream', () async { - final stream = _getStream() - .asBroadcastStream() - .delay(const Duration(milliseconds: 200)); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.delay.error.shouldThrowA', () async { - final streamWithError = Stream.error(Exception()) - .delay(const Duration(milliseconds: 200)); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.delay.pause.resume', () async { - late StreamSubscription subscription; - final stream = - Stream.fromIterable(const [1, 2, 3]).delay(Duration(milliseconds: 1)); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); - - test( - 'Rx.delay.cancel.emits.nothing', - () async { - late StreamSubscription subscription; - final stream = Stream.fromIterable(const [1, 2, 3]).doOnDone(() { - subscription.cancel(); - }).delay(Duration(seconds: 10)); - - // We expect the onData callback to be called 0 times because the - // subscription is cancelled when the base stream ends. - subscription = stream.listen(expectAsync1((_) {}, count: 0)); - }, - timeout: Timeout(Duration(seconds: 3)), - ); - - test('Rx.delay accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.delay(Duration(seconds: 10)); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.delay.nullable', () { - nullableTest( - (s) => s.delay(Duration.zero), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/delay_when_test.dart b/sandbox/reactivex/test/transformers/delay_when_test.dart deleted file mode 100644 index 2e37a11..0000000 --- a/sandbox/reactivex/test/transformers/delay_when_test.dart +++ /dev/null @@ -1,280 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() => Stream.fromIterable(const [1, 2, 3, 4]); - -extension on Duration { - Stream asTimerStream() => Rx.timer(null, this); -} - -void main() { - test('Rx.delayWhen', () { - expect( - _getStream().delayWhen((_) => Stream.value(null)), - emitsInOrder([1, 2, 3, 4, emitsDone]), - ); - - expect( - _getStream() - .delayWhen((_) => const Duration(milliseconds: 200).asTimerStream()), - emitsInOrder([1, 2, 3, 4, emitsDone]), - ); - - expect( - _getStream() - .delayWhen((i) => Duration(milliseconds: 100 * i).asTimerStream()), - emitsInOrder([1, 2, 3, 4, emitsDone]), - ); - - expect( - _getStream().delayWhen( - (i) => Duration(milliseconds: 100 * i).asTimerStream(), - listenDelay: Rx.timer(null, Duration(milliseconds: 100)), - ), - emitsInOrder([1, 2, 3, 4, emitsDone]), - ); - }); - - test('Rx.delayWhen.zero', () { - expect( - _getStream().delayWhen((_) => Duration.zero.asTimerStream()), - emitsInOrder([1, 2, 3, 4, emitsDone]), - ); - }); - - test('Rx.delayWhen.shouldBeDelayed', () async { - { - var value = 1; - await _getStream() - .delayWhen((_) => const Duration(milliseconds: 500).asTimerStream()) - .timeInterval() - .forEach(expectAsync1((result) { - expect(result.value, value++); - - if (result.value == 1) { - expect( - result.interval.inMilliseconds, - greaterThanOrEqualTo(500), - ); // should be delayed - } else { - expect( - result.interval.inMilliseconds, - lessThanOrEqualTo(20), - ); // should be near instantaneous - } - }, count: 4)); - } - - { - var value = 1; - await _getStream() - .delayWhen((i) => Duration(milliseconds: 500 * i).asTimerStream()) - .timeInterval() - .forEach(expectAsync1((result) { - expect(result.value, value++); - - expect( - (result.interval.inMilliseconds - 500).abs(), - lessThanOrEqualTo(20), - ); // should be near instantaneous - }, count: 4)); - } - }); - - test('Rx.delayWhen.shouldBeDelayed.listenDelay', () { - var value = 1; - - void onData(TimeInterval result) { - expect(result.value, value++); - - if (result.value == 1) { - expect( - result.interval.inMilliseconds, - greaterThanOrEqualTo(500 + 300), - ); // should be delayed - } else { - expect( - (result.interval.inMilliseconds - 500).abs(), - lessThanOrEqualTo(20), - ); // should be near instantaneous - } - } - - _getStream() - .delayWhen( - (i) => Duration(milliseconds: 500 * i).asTimerStream(), - listenDelay: Rx.timer(null, const Duration(milliseconds: 300)), - ) - .timeInterval() - .listen(expectAsync1(onData, count: 4)); - }); - - test('Rx.delayWhen.reusable', () { - final transformer = DelayWhenStreamTransformer( - (_) => const Duration(milliseconds: 200).asTimerStream()); - - expect( - _getStream().transform(transformer), - emitsInOrder([1, 2, 3, 4, emitsDone]), - ); - - expect( - _getStream().transform(transformer), - emitsInOrder([1, 2, 3, 4, emitsDone]), - ); - }); - - test('Rx.delayWhen.asBroadcastStream', () { - { - final stream = _getStream() - .asBroadcastStream() - .delayWhen((_) => const Duration(milliseconds: 200).asTimerStream()); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - expect(true, true); - } - - { - final stream = _getStream() - .delayWhen((_) => const Duration(milliseconds: 200).asTimerStream()) - .asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - expect(true, true); - } - - { - final stream = _getStream() - .delayWhen( - (_) => const Duration(milliseconds: 200).asTimerStream(), - listenDelay: Stream.value(null), - ) - .asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - expect(true, true); - } - }); - - test('Rx.delayWhen.error.shouldThrowA', () { - expect( - Stream.error(Exception()) - .delayWhen((_) => const Duration(milliseconds: 200).asTimerStream()), - emitsInOrder([ - emitsError(isA()), - emitsDone, - ]), - ); - }); - - test('Rx.delayWhen.error.shouldThrowB', () { - expect( - Stream.value(0).delayWhen( - (_) => const Duration(milliseconds: 200).asTimerStream(), - listenDelay: Stream.error(Exception('listenDelay')), - ), - emitsInOrder([ - emitsError(isA()), - emitsDone, - ]), - ); - }); - - test('Rx.delayWhen.pause.resume', () async { - late StreamSubscription subscription; - final stream = Stream.fromIterable(const [1, 2, 3]) - .delayWhen((_) => Duration(milliseconds: 1).asTimerStream()); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.delayWhen.pause.resume.listenDelay', () { - late StreamSubscription subscription; - final stream = Stream.fromIterable(const [1, 2, 3]).delayWhen( - (_) => Duration(milliseconds: 1).asTimerStream(), - listenDelay: Rx.timer(null, const Duration(milliseconds: 200)), - ); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); - - test( - 'Rx.delayWhen.cancel.emits.nothing', - () { - late StreamSubscription subscription; - final stream = _getStream() - .doOnDone(() => subscription.cancel()) - .delayWhen((_) => Duration(seconds: 10).asTimerStream()); - - // We expect the onData callback to be called 0 times because the - // subscription is cancelled when the base stream ends. - subscription = stream.listen(expectAsync1((_) {}, count: 0)); - }, - timeout: Timeout(Duration(seconds: 3)), - ); - - test( - 'Rx.delayWhen.cancel.emits.nothing.listenDelay', - () { - late StreamSubscription subscription; - final stream = - _getStream().doOnDone(() => subscription.cancel()).delayWhen( - (_) => Duration(seconds: 10).asTimerStream(), - listenDelay: Stream.periodic(const Duration(seconds: 1)), - ); - - // We expect the onData callback to be called 0 times because the - // subscription is cancelled when the base stream ends. - subscription = stream.listen(expectAsync1((_) {}, count: 0)); - }, - timeout: Timeout(Duration(seconds: 3)), - ); - - test('Rx.delayWhen.singleSubscription', () async { - final controller = StreamController(); - - final stream = controller.stream - .delayWhen((_) => Duration(seconds: 10).asTimerStream()); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.delayWhen.nullable', () { - nullableTest( - (s) => s.delayWhen((_) => Duration.zero.asTimerStream()), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/dematerialize_test.dart b/sandbox/reactivex/test/transformers/dematerialize_test.dart deleted file mode 100644 index c4fdb57..0000000 --- a/sandbox/reactivex/test/transformers/dematerialize_test.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:stack_trace/stack_trace.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.dematerialize.happyPath', () async { - const expectedValue = 1; - final stream = Stream.value(1).materialize(); - - stream.dematerialize().listen(expectAsync1((value) { - expect(value, expectedValue); - }), onDone: expectAsync0(() { - // Should call onDone - expect(true, isTrue); - })); - }); - - test('Rx.dematerialize.nullable.happyPath', () async { - const elements = [1, 2, null, 3, 4, null]; - final stream = Stream.fromIterable(elements).materialize(); - - expect( - stream.dematerialize(), - emitsInOrder(elements), - ); - }); - - test('Rx.dematerialize.reusable', () async { - final transformer = DematerializeStreamTransformer(); - const expectedValue = 1; - final streamA = Stream.value(1).materialize(); - final streamB = Stream.value(1).materialize(); - - streamA.transform(transformer).listen(expectAsync1((value) { - expect(value, expectedValue); - }), onDone: expectAsync0(() { - // Should call onDone - expect(true, isTrue); - })); - - streamB.transform(transformer).listen(expectAsync1((value) { - expect(value, expectedValue); - }), onDone: expectAsync0(() { - // Should call onDone - expect(true, isTrue); - })); - }); - - test('dematerializeTransformer.happyPath', () async { - const expectedValue = 1; - final stream = Stream.fromIterable([ - StreamNotification.data(expectedValue), - StreamNotification.done() - ]); - - stream.transform(DematerializeStreamTransformer()).listen( - expectAsync1((value) { - expect(value, expectedValue); - }), onDone: expectAsync0(() { - // Should call onDone - expect(true, isTrue); - })); - }); - - test('dematerializeTransformer.sadPath', () async { - final stream = Stream.fromIterable( - [StreamNotification.error(Exception(), Chain.current())]); - - stream.transform(DematerializeStreamTransformer()).listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('dematerializeTransformer.onPause.onResume', () async { - const expectedValue = 1; - final stream = Stream.fromIterable([ - StreamNotification.data(expectedValue), - StreamNotification.done() - ]); - - stream.transform(DematerializeStreamTransformer()).listen( - expectAsync1((value) { - expect(value, expectedValue); - }), onDone: expectAsync0(() { - // Should call onDone - expect(true, isTrue); - })) - ..pause() - ..resume(); - }); - - test('Rx.dematerialize accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.materialize().dematerialize(); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); -} diff --git a/sandbox/reactivex/test/transformers/distinct_test.dart b/sandbox/reactivex/test/transformers/distinct_test.dart deleted file mode 100644 index 0775b7f..0000000 --- a/sandbox/reactivex/test/transformers/distinct_test.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:async'; - -import 'package:test/test.dart'; - -void main() { - test('Rx.distinct', () async { - const expected = 1; - - final stream = Stream.fromIterable(const [expected, expected]).distinct(); - - stream.listen(expectAsync1((actual) { - expect(actual, expected); - })); - }); - test('Rx.distinct accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.distinct(); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); -} diff --git a/sandbox/reactivex/test/transformers/distinct_unique_test.dart b/sandbox/reactivex/test/transformers/distinct_unique_test.dart deleted file mode 100644 index 6c4c9dd..0000000 --- a/sandbox/reactivex/test/transformers/distinct_unique_test.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - group('DistinctUniqueStreamTransformer', () { - test('works with the equals and hascode of the class', () async { - final stream = Stream.fromIterable(const [ - _TestObject('a'), - _TestObject('a'), - _TestObject('b'), - _TestObject('a'), - _TestObject('b'), - _TestObject('c'), - _TestObject('a'), - _TestObject('b'), - _TestObject('c'), - _TestObject('a') - ]).distinctUnique(); - - await expectLater( - stream, - emitsInOrder([ - const _TestObject('a'), - const _TestObject('b'), - const _TestObject('c'), - emitsDone - ])); - }); - - test('works with a provided equals and hashcode', () async { - final stream = Stream.fromIterable(const [ - _TestObject('a'), - _TestObject('a'), - _TestObject('b'), - _TestObject('a'), - _TestObject('b'), - _TestObject('c'), - _TestObject('a'), - _TestObject('b'), - _TestObject('c'), - _TestObject('a') - ]).distinctUnique( - equals: (a, b) => a.key == b.key, hashCode: (o) => o.key.hashCode); - - await expectLater( - stream, - emitsInOrder([ - const _TestObject('a'), - const _TestObject('b'), - const _TestObject('c'), - emitsDone - ])); - }); - - test( - 'sends an error to the subscription if an error occurs in the equals or hashmap methods', - () async { - final stream = Stream.fromIterable( - const [_TestObject('a'), _TestObject('b'), _TestObject('c')]) - .distinctUnique( - equals: (a, b) => a.key == b.key, - hashCode: (o) => throw Exception('Catch me if you can!')); - - stream.listen( - null, - onError: expectAsync2( - (Exception e, StackTrace s) => expect(e, isException), - count: 3, - ), - ); - }); - - test('is reusable', () async { - const data = [ - _TestObject('a'), - _TestObject('a'), - _TestObject('b'), - _TestObject('a'), - _TestObject('a'), - _TestObject('b'), - _TestObject('b'), - _TestObject('c'), - _TestObject('a'), - _TestObject('b'), - _TestObject('c'), - _TestObject('a') - ]; - - final distinctUniqueStreamTransformer = - DistinctUniqueStreamTransformer<_TestObject>(); - - final firstStream = - Stream.fromIterable(data).transform(distinctUniqueStreamTransformer); - - final secondStream = - Stream.fromIterable(data).transform(distinctUniqueStreamTransformer); - - await expectLater( - firstStream, - emitsInOrder([ - const _TestObject('a'), - const _TestObject('b'), - const _TestObject('c'), - emitsDone - ])); - - await expectLater( - secondStream, - emitsInOrder([ - const _TestObject('a'), - const _TestObject('b'), - const _TestObject('c'), - emitsDone - ])); - }); - - test('Rx.distinctUnique accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.distinctUnique(); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - }); - - test('Rx.distinctUnique.nullable', () { - nullableTest( - (s) => s.distinctUnique(), - ); - }); -} - -class _TestObject { - final String key; - - const _TestObject(this.key); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is _TestObject && - runtimeType == other.runtimeType && - key == other.key; - - @override - int get hashCode => key.hashCode; - - @override - String toString() => key; -} diff --git a/sandbox/reactivex/test/transformers/do_test.dart b/sandbox/reactivex/test/transformers/do_test.dart deleted file mode 100644 index b99fe28..0000000 --- a/sandbox/reactivex/test/transformers/do_test.dart +++ /dev/null @@ -1,489 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - group('DoStreamTranformer', () { - test('calls onDone when the stream is finished', () async { - var onDoneCalled = false; - final stream = Stream.empty().doOnDone(() => onDoneCalled = true); - - await expectLater(stream, emitsDone); - await expectLater(onDoneCalled, isTrue); - }); - - test('calls onError when an error is emitted', () async { - var onErrorCalled = false; - final stream = Stream.error(Exception()) - .doOnError((e, s) => onErrorCalled = true); - - await expectLater(stream, emitsError(isException)); - await expectLater(onErrorCalled, isTrue); - }); - - test( - 'onError only called once when an error is emitted on a broadcast stream', - () async { - var count = 0; - final subject = BehaviorSubject(sync: true); - final stream = subject.stream.doOnError((e, s) => count++); - - stream.listen(null, onError: (dynamic e, dynamic s) {}); - stream.listen(null, onError: (dynamic e, dynamic s) {}); - - subject.addError(Exception()); - subject.addError(Exception()); - - await expectLater(count, 2); - await subject.close(); - }); - - test('calls onCancel when the subscription is cancelled', () async { - var onCancelCalled = false; - final stream = Stream.value(1); - - await stream - .doOnCancel(() => onCancelCalled = true) - .listen(null) - .cancel(); - - await expectLater(onCancelCalled, isTrue); - }); - - test('awaits onCancel when the subscription is cancelled', () async { - var onCancelCompleted = 10, onCancelHandled = 10, eventSequenceCount = 0; - final stream = Stream.value(1); - - await stream - .doOnCancel(() => - Future.delayed(const Duration(milliseconds: 100)) - .whenComplete(() => onCancelHandled = ++eventSequenceCount)) - .listen(null) - .cancel() - .whenComplete(() => onCancelCompleted = ++eventSequenceCount); - - await expectLater(onCancelCompleted > onCancelHandled, isTrue); - }); - - test( - 'onCancel called only once when the subscription is multiple listeners', - () async { - var count = 0; - final subject = BehaviorSubject(sync: true); - final stream = subject.doOnCancel(() => count++); - - await stream.listen(null).cancel(); - await stream.listen(null).cancel(); - - await expectLater(count, 2); - await subject.close(); - }); - - test('calls onData when the stream emits an item', () async { - var onDataCalled = false; - final stream = Stream.value(1).doOnData((_) => onDataCalled = true); - - await expectLater(stream, emits(1)); - await expectLater(onDataCalled, isTrue); - }); - - test('onData only emits once for broadcast streams with multiple listeners', - () async { - final actual = []; - final controller = StreamController.broadcast(sync: true); - final stream = - controller.stream.transform(DoStreamTransformer(onData: actual.add)); - - stream.listen(null); - stream.listen(null); - - controller.add(1); - controller.add(2); - - await expectLater(actual, const [1, 2]); - await controller.close(); - }); - - test('onData only emits once for subjects with multiple listeners', - () async { - final actual = []; - final controller = BehaviorSubject(sync: true); - final stream = - controller.stream.transform(DoStreamTransformer(onData: actual.add)); - - stream.listen(null); - stream.listen(null); - - controller.add(1); - controller.add(2); - - await expectLater(actual, const [1, 2]); - await controller.close(); - }); - - test('onData only emits correctly with ReplaySubject', () async { - final controller = ReplaySubject(sync: true) - ..add(1) - ..add(2); - final actual = []; - - await controller.close(); - - expect(await controller.stream.doOnData(actual.add).drain(actual), - const [1, 2]); - - actual.clear(); - - expect(await controller.stream.doOnData(actual.add).drain(actual), - const [1, 2]); - }); - - test('emits onEach Notifications for Data, Error, and Done', () async { - StackTrace? stacktrace; - final actual = >[]; - final exception = Exception(); - final stream = Stream.value(1) - .concatWith([Stream.error(exception)]).doOnEach((notification) { - actual.add(notification); - - if (notification.isError) { - stacktrace = notification.errorAndStackTraceOrNull?.stackTrace; - } - }); - - await expectLater(stream, - emitsInOrder([1, emitsError(isException), emitsDone])); - - await expectLater(actual, [ - StreamNotification.data(1), - StreamNotification.error(exception, stacktrace), - StreamNotification.done() - ]); - }); - - test('onEach only emits once for broadcast streams with multiple listeners', - () async { - var count = 0; - final controller = StreamController.broadcast(sync: true); - final stream = - controller.stream.transform(DoStreamTransformer(onEach: (_) { - count++; - })); - - stream.listen(null); - stream.listen(null); - - controller.add(1); - controller.add(2); - - await expectLater(count, 2); - await controller.close(); - }); - - test('calls onListen when a consumer listens', () async { - var onListenCalled = false; - final stream = Stream.empty().doOnListen(() { - onListenCalled = true; - }); - - await expectLater(stream, emitsDone); - await expectLater(onListenCalled, isTrue); - }); - - test( - 'calls onListen once when multiple subscribers open, without cancelling', - () async { - var onListenCallCount = 0; - final sc = StreamController.broadcast() - ..add(1) - ..add(2) - ..add(3); - - final stream = sc.stream.doOnListen(() => onListenCallCount++); - - stream.listen(null); - stream.listen(null); - - await expectLater(onListenCallCount, 1); - await sc.close(); - }); - - test( - 'calls onListen every time after all previous subscribers have cancelled', - () async { - var onListenCallCount = 0; - final sc = StreamController.broadcast() - ..add(1) - ..add(2) - ..add(3); - - final stream = sc.stream.doOnListen(() => onListenCallCount++); - - await stream.listen(null).cancel(); - await stream.listen(null).cancel(); - - await expectLater(onListenCallCount, 2); - await sc.close(); - }); - - test('calls onPause and onResume when the subscription is', () async { - var onPauseCalled = false, onResumeCalled = false; - final stream = Stream.value(1).doOnPause(() { - onPauseCalled = true; - }).doOnResume(() { - onResumeCalled = true; - }); - - stream.listen(null, onDone: expectAsync0(() { - expect(onPauseCalled, isTrue); - expect(onResumeCalled, isTrue); - })) - ..pause() - ..resume(); - }); - - test('should be reusable', () async { - var callCount = 0; - final transformer = DoStreamTransformer(onData: (_) { - callCount++; - }); - - final streamA = Stream.value(1).transform(transformer), - streamB = Stream.value(1).transform(transformer); - - await expectLater(streamA, emitsInOrder([1, emitsDone])); - await expectLater(streamB, emitsInOrder([1, emitsDone])); - - expect(callCount, 2); - }); - - test('throws an error when no arguments are provided', () { - expect(() => DoStreamTransformer(), throwsArgumentError); - }); - - test('should propagate errors', () { - Stream.value(1) - .doOnListen(() => throw Exception('catch me if you can! doOnListen')) - .listen( - null, - onError: expectAsync2( - (Exception e, StackTrace s) => expect(e, isException), - ), - ); - - Stream.value(1) - .doOnData((_) => throw Exception('catch me if you can! doOnData')) - .listen( - null, - onError: expectAsync2( - (Exception e, StackTrace s) => expect(e, isException), - ), - ); - - Stream.error(Exception('oh noes!')) - .doOnError( - (_, __) => throw Exception('catch me if you can! doOnError')) - .listen( - null, - onError: expectAsync2( - (Exception e, StackTrace s) => expect(e, isException), - count: 2, - ), - ); - - // a cancel() call may occur after the controller is already closed - // in that case, the error is forwarded to the current [Zone] - runZonedGuarded( - () { - Stream.value(1) - .doOnCancel(() => - throw Exception('catch me if you can! doOnCancel-zoned')) - .listen(null); - - Stream.value(1) - .doOnCancel( - () => throw Exception('catch me if you can! doOnCancel')) - .listen(null) - .cancel(); - }, - expectAsync2( - (Object e, StackTrace s) => expect(e, isException), - count: 2, - ), - ); - - Stream.value(1) - .doOnDone(() => throw Exception('catch me if you can! doOnDone')) - .listen( - null, - onError: expectAsync2( - (Exception e, StackTrace s) => expect(e, isException), - ), - ); - - Stream.value(1) - .doOnEach((_) => throw Exception('catch me if you can! doOnEach')) - .listen( - null, - onError: expectAsync2( - (Exception e, StackTrace s) => expect(e, isException), - count: 2, - ), - ); - - Stream.value(1) - .doOnPause(() => throw Exception('catch me if you can! doOnPause')) - .listen(null, - onError: expectAsync2( - (Exception e, StackTrace s) => expect(e, isException), - )) - ..pause() - ..resume(); - - Stream.value(1) - .doOnResume(() => throw Exception('catch me if you can! doOnResume')) - .listen(null, - onError: expectAsync2( - (Exception e, StackTrace s) => expect(e, isException))) - ..pause() - ..resume(); - }); - - test( - 'doOnListen correctly allows subscribing multiple times on a broadcast stream', - () { - final controller = StreamController.broadcast(); - final stream = controller.stream.doOnListen(() { - // do nothing - }); - - controller.close(); - - expectLater(stream, emitsDone); - expectLater(stream, emitsDone); - }); - - test('issue/389/1', () { - final controller = StreamController.broadcast(); - final stream = controller.stream.doOnListen(() { - // do nothing - }); - - expectLater(stream, emitsDone); - expectLater(stream, emitsDone); // #issue/389 : is being ignored/hangs up - - controller.close(); - }); - - test('issue/389/2', () { - final controller = StreamController(); - var isListening = false; - - final stream = controller.stream.doOnListen(() { - isListening = true; - }); - - controller.close(); - - // should be done - expectLater(stream, emitsDone); - // should have called onX - expect(isListening, true); - // should not be converted to a broadcast Stream - expect(() => stream.listen(null), throwsStateError); - }); - - test('Rx.do accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.doOnEach((_) {}); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('nested doOnX', () async { - final completer = Completer(); - final stream = - Rx.range(0, 30).interval(const Duration(milliseconds: 100)); - final result = []; - const expectedOutput = [ - 'A: 0', - 'B: 0', - 'pause', - 'A: 1', - 'B: 1', - 'A: 2', - 'B: 2', - 'A: 3', - 'B: 3', - 'A: 4', - 'B: 4', - 'A: 5', - 'B: 5', - 'pause', - 'A: 6', - 'B: 6', - 'A: 7', - 'B: 7', - 'A: 8', - 'B: 8', - 'A: 9', - 'B: 9', - 'A: 10', - 'B: 10', - 'pause', - 'A: 11', - 'B: 11', - 'A: 12', - 'B: 12', - 'A: 13', - 'B: 13', - 'A: 14', - 'B: 14', - 'A: 15', - 'B: 15', - 'pause', - 'A: 16', - 'B: 16', - 'A: 17', - ]; - late StreamSubscription subscription; - - void addToResult(String value) { - result.add(value); - - if (result.length == expectedOutput.length) { - subscription.cancel(); - completer.complete(); - } - } - - subscription = Stream.value(1) - .exhaustMap((_) => stream.doOnData((data) => addToResult('A: $data'))) - .doOnPause(() => addToResult('pause')) - .doOnData((data) => addToResult('B: $data')) - .take(expectedOutput.length) - .listen((value) { - if (value % 5 == 0) { - subscription.pause(Future.delayed(const Duration(seconds: 2))); - } - }); - - await completer.future; - - expect(result, expectedOutput); - }); - - test('doOnData nullable', () { - nullableTest( - (s) => s.doOnData((d) {}), - ); - }); - }); -} diff --git a/sandbox/reactivex/test/transformers/end_with_many_test.dart b/sandbox/reactivex/test/transformers/end_with_many_test.dart deleted file mode 100644 index ab28437..0000000 --- a/sandbox/reactivex/test/transformers/end_with_many_test.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() => Stream.fromIterable(const [1, 2, 3, 4]); - -void main() { - test('Rx.endWithMany', () async { - const expectedOutput = [1, 2, 3, 4, 5, 6]; - - await expectLater( - _getStream().endWithMany(const [5, 6]), emitsInOrder(expectedOutput)); - }); - - test('Rx.endWithMany.reusable', () async { - final transformer = EndWithManyStreamTransformer(const [5, 6]); - const expectedOutput = [1, 2, 3, 4, 5, 6]; - - await expectLater( - _getStream().transform(transformer), emitsInOrder(expectedOutput)); - await expectLater( - _getStream().transform(transformer), emitsInOrder(expectedOutput)); - }); - - test('Rx.endWithMany.asBroadcastStream', () async { - final stream = _getStream().asBroadcastStream().endWithMany(const [5, 6]); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.endWithMany.error.shouldThrowA', () async { - final streamWithError = - Stream.error(Exception()).endWithMany(const [5, 6]); - - await expectLater(streamWithError, emitsError(isException)); - }); - - test('Rx.endWithMany.pause.resume', () async { - const expectedOutput = [1, 2, 3, 4, 5, 6]; - var count = 0; - - late StreamSubscription subscription; - subscription = - _getStream().endWithMany(const [5, 6]).listen(expectAsync1((result) { - expect(expectedOutput[count++], result); - - if (count == expectedOutput.length) { - subscription.cancel(); - } - }, count: expectedOutput.length)); - - subscription.pause(); - subscription.resume(); - }); - test('Rx.endWithMany accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.endWithMany(const [1, 2, 3]); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.endWithMany.nullable', () { - nullableTest( - (s) => s.endWithMany(['String']), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/end_with_test.dart b/sandbox/reactivex/test/transformers/end_with_test.dart deleted file mode 100644 index d54562b..0000000 --- a/sandbox/reactivex/test/transformers/end_with_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() => Stream.fromIterable(const [1, 2, 3, 4]); - -void main() { - test('Rx.endWith', () async { - const expectedOutput = [1, 2, 3, 4, 5]; - - await expectLater(_getStream().endWith(5), emitsInOrder(expectedOutput)); - }); - - test('Rx.endWith.reusable', () async { - final transformer = EndWithStreamTransformer(5); - const expectedOutput = [1, 2, 3, 4, 5]; - - await expectLater( - _getStream().transform(transformer), emitsInOrder(expectedOutput)); - await expectLater( - _getStream().transform(transformer), emitsInOrder(expectedOutput)); - }); - - test('Rx.endWith.asBroadcastStream', () async { - final stream = _getStream().asBroadcastStream().endWith(5); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.endWith.error.shouldThrow', () async { - final streamWithError = Stream.error(Exception()).endWith(5); - - await expectLater(streamWithError, emitsError(isException)); - }); - - test('Rx.endWith.pause.resume', () async { - const expectedOutput = [1, 2, 3, 4, 5]; - var count = 0; - - late StreamSubscription subscription; - subscription = _getStream().endWith(5).listen(expectAsync1((result) { - expect(expectedOutput[count++], result); - - if (count == expectedOutput.length) { - subscription.cancel(); - } - }, count: expectedOutput.length)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.endWith accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.endWith(1); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.endWith.nullable', () { - nullableTest( - (s) => s.endWith('String'), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/exhaust_map_test.dart b/sandbox/reactivex/test/transformers/exhaust_map_test.dart deleted file mode 100644 index 0f9137f..0000000 --- a/sandbox/reactivex/test/transformers/exhaust_map_test.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - group('ExhaustMap', () { - test('does not create a new Stream while emitting', () async { - var calls = 0; - final stream = Rx.range(0, 9).exhaustMap((i) { - calls++; - return Rx.timer(i, Duration(milliseconds: 100)); - }); - - await expectLater(stream, emitsInOrder([0, emitsDone])); - await expectLater(calls, 1); - }); - - test('starts emitting again after previous Stream is complete', () async { - final stream = Stream.fromIterable(const [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - .interval(Duration(milliseconds: 30)) - .exhaustMap((i) async* { - yield await Future.delayed(Duration(milliseconds: 70), () => i); - }); - - await expectLater(stream, emitsInOrder([0, 3, 6, 9, emitsDone])); - }); - - test('is reusable', () async { - final transformer = ExhaustMapStreamTransformer( - (int i) => Rx.timer(i, Duration(milliseconds: 100))); - - await expectLater( - Rx.range(0, 9).transform(transformer), - emitsInOrder([0, emitsDone]), - ); - - await expectLater( - Rx.range(0, 9).transform(transformer), - emitsInOrder([0, emitsDone]), - ); - }); - - test('works as a broadcast stream', () async { - final stream = Rx.range(0, 9) - .asBroadcastStream() - .exhaustMap((i) => Rx.timer(i, Duration(milliseconds: 100))); - - await expectLater(() { - stream.listen(null); - stream.listen(null); - }, returnsNormally); - }); - - test('should emit errors from source', () async { - final streamWithError = Stream.error(Exception()) - .exhaustMap((i) => Rx.timer(i, Duration(milliseconds: 100))); - - await expectLater(streamWithError, emitsError(isException)); - }); - - test('should emit errors from mapped stream', () async { - final streamWithError = Stream.value(1).exhaustMap( - (_) => Stream.error(Exception('Catch me if you can!'))); - - await expectLater(streamWithError, emitsError(isException)); - }); - - test('should emit errors thrown in the mapper', () async { - final streamWithError = Stream.value(1).exhaustMap((_) { - throw Exception('oh noes!'); - }); - - await expectLater(streamWithError, emitsError(isException)); - }); - - test('can be paused and resumed', () async { - late StreamSubscription subscription; - final stream = Rx.range(0, 9) - .exhaustMap((i) => Rx.timer(i, Duration(milliseconds: 20))); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 0); - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.exhaustMap accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.exhaustMap((_) => Stream.empty()); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.exhaustMap.nullable', () { - nullableTest( - (s) => s.exhaustMap((v) => Stream.value(v)), - ); - }); - }); -} diff --git a/sandbox/reactivex/test/transformers/flat_map_iterable_test.dart b/sandbox/reactivex/test/transformers/flat_map_iterable_test.dart deleted file mode 100644 index e1cb944..0000000 --- a/sandbox/reactivex/test/transformers/flat_map_iterable_test.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - group('Rx.flatMapIterable', () { - test('transforms a Stream> into individual items', () { - expect( - Rx.range(1, 4) - .flatMapIterable((int i) => Stream>.value([i])), - emitsInOrder([1, 2, 3, 4, emitsDone])); - }); - - test('accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream - .flatMapIterable((int i) => Stream>.value([i])); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('nullable', () { - nullableTest( - (s) => s.flatMapIterable((v) => Stream.value([v])), - ); - }); - }); -} diff --git a/sandbox/reactivex/test/transformers/flat_map_test.dart b/sandbox/reactivex/test/transformers/flat_map_test.dart deleted file mode 100644 index db994f0..0000000 --- a/sandbox/reactivex/test/transformers/flat_map_test.dart +++ /dev/null @@ -1,267 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.flatMap', () async { - const expectedOutput = [3, 2, 1]; - var count = 0; - - _getStream().flatMap(_getOtherStream).listen(expectAsync1((result) { - expect(result, expectedOutput[count++]); - }, count: expectedOutput.length)); - }); - - test('Rx.flatMap.reusable', () async { - final transformer = FlatMapStreamTransformer(_getOtherStream); - const expectedOutput = [3, 2, 1]; - var countA = 0, countB = 0; - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(result, expectedOutput[countA++]); - }, count: expectedOutput.length)); - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(result, expectedOutput[countB++]); - }, count: expectedOutput.length)); - }); - - test('Rx.flatMap.asBroadcastStream', () async { - final stream = _getStream().asBroadcastStream().flatMap(_getOtherStream); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.flatMap.error.shouldThrowA', () async { - final streamWithError = - Stream.error(Exception()).flatMap(_getOtherStream); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.flatMap.error.shouldThrowB', () async { - final streamWithError = Stream.value(1) - .flatMap((_) => Stream.error(Exception('Catch me if you can!'))); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.flatMap.error.shouldThrowC', () async { - final streamWithError = - Stream.value(1).flatMap((_) => throw Exception('oh noes!')); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.flatMap.pause.resume', () async { - late StreamSubscription subscription; - final stream = Stream.value(0).flatMap((_) => Stream.value(1)); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.flatMap.chains', () { - expect( - Stream.value(1) - .flatMap((_) => Stream.value(2)) - .flatMap((_) => Stream.value(3)), - emitsInOrder([3, emitsDone]), - ); - }); - - test('Rx.flatMap accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.flatMap((_) => Stream.empty()); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.flatMap(maxConcurrent: 1)', () { - { - // asyncExpand / concatMap - final stream = Stream.fromIterable([1, 2, 3, 4]).flatMap( - (value) => Rx.timer( - value, - Duration(milliseconds: (5 - value) * 100), - ), - maxConcurrent: 1, - ); - expect(stream, emitsInOrder([1, 2, 3, 4, emitsDone])); - } - - { - // emits error - final stream = Stream.fromIterable([1, 2, 3, 4]).flatMap( - (value) => value == 1 - ? throw Exception() - : Rx.timer( - value, - Duration(milliseconds: (5 - value) * 100), - ), - maxConcurrent: 1, - ); - expect(stream, - emitsInOrder([emitsError(isException), 2, 3, 4, emitsDone])); - } - - { - // emits error - final stream = Stream.fromIterable([1, 2, 3, 4]).flatMap( - (value) => value == 1 - ? Stream.error(Exception()) - : Rx.timer( - value, - Duration(milliseconds: (5 - value) * 100), - ), - maxConcurrent: 1, - ); - expect(stream, - emitsInOrder([emitsError(isException), 2, 3, 4, emitsDone])); - } - }); - - test('Rx.flatMap(maxConcurrent: 2)', () async { - const maxConcurrent = 2; - var activeCount = 0; - - // 1 -> 500 - // 2 -> 400 - // 3 -> 500 - // 4 -> 200 - // -----1--4 - // ----2-----3 - // ----21--4-3 - final stream = Stream.fromIterable([1, 2, 3, 4]).flatMap( - (value) { - return Rx.defer(() { - expect(++activeCount, lessThanOrEqualTo(maxConcurrent)); - - final ms = (value.isOdd ? 5 : 6 - value) * 100; - return Rx.timer(value, Duration(milliseconds: ms)); - }).doOnDone(() => --activeCount); - }, - maxConcurrent: maxConcurrent, - ); - - await expectLater(stream, emitsInOrder([2, 1, 4, 3, emitsDone])); - }); - - test('Rx.flatMap(maxConcurrent: 3)', () async { - const maxConcurrent = 3; - var activeCount = 0; - - // 1 -> 400 - // 2 -> 300 - // 3 -> 200 - // 4 -> 200 - // 5 -> 300 - // 6 -> 400 - // ----1----6 - // ---2---5 - // --3--4 - // --3214-5-6 - final stream = Stream.fromIterable([1, 2, 3, 4, 5, 6]).flatMap( - (value) { - return Rx.defer(() { - expect(++activeCount, lessThanOrEqualTo(maxConcurrent)); - - final ms = (value <= 3 ? 5 - value : value - 2) * 100; - return Rx.timer(value, Duration(milliseconds: ms)); - }).doOnDone(() => --activeCount); - }, - maxConcurrent: maxConcurrent, - ); - - await expectLater( - stream, emitsInOrder([3, 2, 1, 4, 5, 6, emitsDone])); - }); - - test('Rx.flatMap.cancel', () { - _getStream() - .flatMap(_getOtherStream) - .listen(expectAsync1((data) {}, count: 0)) - .cancel(); - }, timeout: const Timeout(Duration(milliseconds: 200))); - - test('Rx.flatMap(maxConcurrent: 1).cancel', () { - _getStream() - .flatMap(_getOtherStream, maxConcurrent: 1) - .listen(expectAsync1((data) {}, count: 0)) - .cancel(); - }, timeout: const Timeout(Duration(milliseconds: 200))); - - test('Rx.flatMap.take.cancel', () { - _getStream() - .flatMap(_getOtherStream) - .take(1) - .listen(expectAsync1((data) => expect(data, 3), count: 1)); - }, timeout: const Timeout(Duration(milliseconds: 200))); - - test('Rx.flatMap(maxConcurrent: 1).take.cancel', () { - _getStream() - .flatMap(_getOtherStream, maxConcurrent: 1) - .take(1) - .listen(expectAsync1((data) => expect(data, 1), count: 1)); - }, timeout: const Timeout(Duration(milliseconds: 200))); - - test('Rx.flatMap(maxConcurrent: 2).take.cancel', () { - _getStream() - .flatMap(_getOtherStream, maxConcurrent: 2) - .take(1) - .listen(expectAsync1((data) => expect(data, 2), count: 1)); - }, timeout: const Timeout(Duration(milliseconds: 200))); - - test('Rx.flatMap.nullable', () { - nullableTest( - (s) => s.flatMap((v) => Stream.value(v)), - ); - }); -} - -Stream _getStream() => Stream.fromIterable(const [1, 2, 3]); - -Stream _getOtherStream(int value) { - final controller = StreamController(); - - Timer( - // Reverses the order of 1, 2, 3 to 3, 2, 1 by delaying 1, and 2 longer - // than they delay 3 - Duration( - milliseconds: value == 1 - ? 15 - : value == 2 - ? 10 - : 5), () { - controller.add(value); - controller.close(); - }); - - return controller.stream; -} diff --git a/sandbox/reactivex/test/transformers/group_by_test.dart b/sandbox/reactivex/test/transformers/group_by_test.dart deleted file mode 100644 index 9b896ea..0000000 --- a/sandbox/reactivex/test/transformers/group_by_test.dart +++ /dev/null @@ -1,312 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -String _toEventOdd(int value) => value == 0 ? 'even' : 'odd'; - -void main() { - test('Rx.groupBy', () async { - await expectLater( - Stream.fromIterable([1, 2, 3, 4]).groupBy((value) => value), - emitsInOrder([ - TypeMatcher>() - .having((stream) => stream.key, 'key', 1), - TypeMatcher>() - .having((stream) => stream.key, 'key', 2), - TypeMatcher>() - .having((stream) => stream.key, 'key', 3), - TypeMatcher>() - .having((stream) => stream.key, 'key', 4), - emitsDone - ])); - - await expectLater( - Stream.fromIterable([1, 2, 3, 4]) - .groupBy((value) => value, durationSelector: (_) => Rx.never()), - emitsInOrder([ - TypeMatcher>() - .having((stream) => stream.key, 'key', 1), - TypeMatcher>() - .having((stream) => stream.key, 'key', 2), - TypeMatcher>() - .having((stream) => stream.key, 'key', 3), - TypeMatcher>() - .having((stream) => stream.key, 'key', 4), - emitsDone - ])); - }); - - test('Rx.groupBy.correctlyEmitsGroupEvents', () async { - await expectLater( - Stream.fromIterable([1, 2, 3, 4]) - .groupBy((value) => _toEventOdd(value % 2)) - .flatMap((stream) => stream.map((event) => {stream.key: event})), - emitsInOrder([ - {'odd': 1}, - {'even': 2}, - {'odd': 3}, - {'even': 4}, - emitsDone - ])); - - await expectLater( - Stream.fromIterable([1, 2, 3, 4]) - .groupBy( - (value) => _toEventOdd(value % 2), - durationSelector: (_) => - Stream.periodic(const Duration(seconds: 1)), - ) - .flatMap((stream) => stream.map((event) => {stream.key: event})), - emitsInOrder([ - {'odd': 1}, - {'even': 2}, - {'odd': 3}, - {'even': 4}, - emitsDone - ])); - }); - - test('Rx.groupBy.correctlyEmitsGroupEvents.alternate', () async { - await expectLater( - Stream.fromIterable([1, 2, 3, 4]) - .groupBy((value) => _toEventOdd(value % 2)) - // fold is called when onDone triggers on the Stream - .map((stream) async => await stream.fold( - {stream.key: []}, - (Map> previous, element) => - previous..[stream.key]?.add(element))), - emitsInOrder([ - { - 'odd': [1, 3] - }, - { - 'even': [2, 4] - }, - emitsDone - ])); - - await expectLater( - Stream.fromIterable([1, 2, 3, 4]) - .groupBy( - (value) => _toEventOdd(value % 2), - durationSelector: (_) => - Stream.periodic(const Duration(seconds: 1)), - ) - // fold is called when onDone triggers on the Stream - .map((stream) async => await stream.fold( - {stream.key: []}, - (Map> previous, element) => - previous..[stream.key]?.add(element))), - emitsInOrder([ - { - 'odd': [1, 3] - }, - { - 'even': [2, 4] - }, - emitsDone - ])); - }); - - test('Rx.groupBy.emittedStreamCallOnDone', () async { - await expectLater( - Stream.fromIterable([1, 2, 3, 4]) - .groupBy((value) => value) - // drain will emit 'done' onDone - .map((stream) async => await stream.drain('done')), - emitsInOrder(['done', 'done', 'done', 'done', emitsDone])); - - await expectLater( - Stream.fromIterable([1, 2, 3, 4]) - .groupBy((value) => value, durationSelector: (_) => Rx.never()) - // drain will emit 'done' onDone - .map((stream) async => await stream.drain('done')), - emitsInOrder(['done', 'done', 'done', 'done', emitsDone])); - }); - - test('Rx.groupBy.asBroadcastStream', () async { - { - final stream = Stream.fromIterable([1, 2, 3, 4]) - .asBroadcastStream() - .groupBy((value) => value); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - } - - { - final stream = - Stream.fromIterable([1, 2, 3, 4]).asBroadcastStream().groupBy( - (value) => value, - durationSelector: (_) => - Stream.periodic(const Duration(seconds: 2)), - ); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - } - }); - - test('Rx.groupBy.pause.resume', () async { - { - var count = 0; - late StreamSubscription subscription; - - subscription = Stream.fromIterable([1, 2, 3, 4]) - .groupBy((value) => value) - .listen(expectAsync1((result) { - count++; - - if (count == 4) { - subscription.cancel(); - } - }, count: 4)); - - subscription - .pause(Future.delayed(const Duration(milliseconds: 100))); - } - - { - var count = 0; - late StreamSubscription subscription; - - subscription = Stream.fromIterable([1, 2, 3, 4]) - .groupBy( - (value) => value, - durationSelector: (_) => Rx.timer(null, const Duration(seconds: 1)), - ) - .listen(expectAsync1((result) { - count++; - - if (count == 4) { - subscription.cancel(); - } - }, count: 4)); - - subscription - .pause(Future.delayed(const Duration(milliseconds: 100))); - } - }); - - test('Rx.groupBy.error.shouldThrow.onError', () async { - { - final streamWithError = - Stream.error(Exception()).groupBy((value) => value); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - } - - { - final streamWithError = Stream.error(Exception()).groupBy( - (value) => value, - durationSelector: (_) => Rx.timer(null, const Duration(seconds: 1)), - ); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - } - }); - - test('Rx.groupBy.error.shouldThrow.onGrouper', () async { - { - final streamWithError = - Stream.fromIterable([1, 2, 3, 4]).groupBy((value) { - throw Exception(); - }); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - }, count: 4)); - } - - { - final streamWithError = Stream.fromIterable([1, 2, 3, 4]).groupBy( - (value) => throw Exception(), - durationSelector: (_) => Rx.timer(null, const Duration(seconds: 1)), - ); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - }, count: 4)); - } - }); - test('Rx.groupBy accidental broadcast', () async { - { - final controller = StreamController(); - - final stream = controller.stream.groupBy((_) => _); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - } - - { - final controller = StreamController(); - - final stream = controller.stream.groupBy( - (_) => _, - durationSelector: (_) => Rx.timer(null, const Duration(seconds: 1)), - ); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - } - }); - - test('Rx.groupBy.durationSelector', () { - final g = [ - '0 -> 1', - '1 -> 1', - '2 -> 1', - '0 -> 2', - '1 -> 2', - '2 -> 2', - ]; - final take = 30; - - final stream = Stream.periodic(const Duration(milliseconds: 100), (i) => i) - .groupBy( - (i) => i % 3, - durationSelector: (i) => - Rx.timer(null, const Duration(milliseconds: 400)), - ) - .flatMap((g) => g - .scan((acc, value, index) => acc + 1, 0) - .map((event) => '${g.key} -> $event')) - .take(take); - - expect( - stream, - emitsInOrder([ - ...List.filled(take ~/ g.length, g).expand((e) => e), - emitsDone, - ]), - ); - }); - - test('Rx.groupBy.nullable', () { - nullableTest>( - (s) => s.groupBy((v) => v), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/ignore_elements_test.dart b/sandbox/reactivex/test/transformers/ignore_elements_test.dart deleted file mode 100644 index 9673be5..0000000 --- a/sandbox/reactivex/test/transformers/ignore_elements_test.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 100), () => controller.add(1)); - Timer(const Duration(milliseconds: 200), () => controller.add(2)); - Timer(const Duration(milliseconds: 300), () => controller.add(3)); - Timer(const Duration(milliseconds: 400), () { - controller.add(4); - controller.close(); - }); - - return controller.stream; -} - -void main() { - test('Rx.ignoreElements', () async { - var hasReceivedEvent = false; - - _getStream().ignoreElements().listen((_) { - hasReceivedEvent = true; - }, - onDone: expectAsync0(() { - expect(hasReceivedEvent, isFalse); - }, count: 1)); - - expect( - _getStream().ignoreElements(), - emitsInOrder([emitsDone]), - ); - }); - - test('Rx.ignoreElements.cast', () { - final ignored = _getStream().ignoreElements(); - - expect(ignored, isA>()); - expect(ignored, isA>()); // ignore: prefer_void_to_null - expect(ignored, isA>()); - expect(ignored, isA>()); - expect(ignored, isA>()); - expect(ignored, isA>()); - - ignored as Stream; // ignore: unnecessary_cast - ignored as Stream; // ignore: unnecessary_cast, prefer_void_to_null - ignored as Stream; // ignore: unnecessary_cast - ignored as Stream; // ignore: unnecessary_cast - ignored as Stream; // ignore: unnecessary_cast - ignored as Stream; // ignore: unnecessary_cast - - expect(true, true); - }); - - test('Rx.ignoreElements.reusable', () async { - final transformer = IgnoreElementsStreamTransformer(); - var hasReceivedEvent = false; - - _getStream().transform(transformer).listen((_) { - hasReceivedEvent = true; - }, - onDone: expectAsync0(() { - expect(hasReceivedEvent, isFalse); - }, count: 1)); - - _getStream().transform(transformer).listen((_) { - hasReceivedEvent = true; - }, - onDone: expectAsync0(() { - expect(hasReceivedEvent, isFalse); - }, count: 1)); - }); - - test('Rx.ignoreElements.asBroadcastStream', () async { - final stream = _getStream().asBroadcastStream().ignoreElements(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.ignoreElements.pause.resume', () async { - var hasReceivedEvent = false; - - _getStream().ignoreElements().listen((_) { - hasReceivedEvent = true; - }, - onDone: expectAsync0(() { - expect(hasReceivedEvent, isFalse); - }, count: 1)) - ..pause() - ..resume(); - }); - - test('Rx.ignoreElements.error.shouldThrow', () async { - final streamWithError = Stream.error(Exception()).ignoreElements(); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - }, count: 1)); - }); - - test('Rx.ignoreElements accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.ignoreElements(); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.ignoreElements.nullable', () { - nullableTest( - (s) => s.ignoreElements(), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/interval_test.dart b/sandbox/reactivex/test/transformers/interval_test.dart deleted file mode 100644 index 0fa9315..0000000 --- a/sandbox/reactivex/test/transformers/interval_test.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() => Stream.fromIterable(const [0, 1, 2, 3, 4]); - -void main() { - test('Rx.interval', () async { - const expectedOutput = [0, 1, 2, 3, 4]; - var count = 0, lastInterval = -1; - final stopwatch = Stopwatch()..start(); - - _getStream().interval(const Duration(milliseconds: 1)).listen( - expectAsync1((result) { - expect(expectedOutput[count++], result); - - if (lastInterval != -1) { - expect(stopwatch.elapsedMilliseconds - lastInterval >= 1, true); - } - - lastInterval = stopwatch.elapsedMilliseconds; - }, count: expectedOutput.length), - onDone: stopwatch.stop); - }); - - test('Rx.interval.reusable', () async { - final transformer = - IntervalStreamTransformer(const Duration(milliseconds: 1)); - const expectedOutput = [0, 1, 2, 3, 4]; - var countA = 0, countB = 0; - final stopwatch = Stopwatch()..start(); - - _getStream().transform(transformer).listen( - expectAsync1((result) { - expect(expectedOutput[countA++], result); - }, count: expectedOutput.length), - onDone: stopwatch.stop); - - _getStream().transform(transformer).listen( - expectAsync1((result) { - expect(expectedOutput[countB++], result); - }, count: expectedOutput.length), - onDone: stopwatch.stop); - }); - - test('Rx.interval.asBroadcastStream', () async { - final stream = _getStream() - .asBroadcastStream() - .interval(const Duration(milliseconds: 20)); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.interval.error.shouldThrowA', () async { - final streamWithError = Stream.error(Exception()) - .interval(const Duration(milliseconds: 20)); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.interval accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.interval(const Duration(milliseconds: 10)); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.interval.nullable', () { - nullableTest( - (s) => s.interval(Duration.zero), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/join_test.dart b/sandbox/reactivex/test/transformers/join_test.dart deleted file mode 100644 index 008d2b5..0000000 --- a/sandbox/reactivex/test/transformers/join_test.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'dart:async'; - -import 'package:test/test.dart'; - -void main() { - test('Rx.join', () async { - final joined = await Stream.fromIterable(const ['h', 'i']).join('+'); - - await expectLater(joined, 'h+i'); - }); -} diff --git a/sandbox/reactivex/test/transformers/map_not_null_test.dart b/sandbox/reactivex/test/transformers/map_not_null_test.dart deleted file mode 100644 index 7f900be..0000000 --- a/sandbox/reactivex/test/transformers/map_not_null_test.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.mapNotNull', () { - expect( - Stream.fromIterable(['1', '2', 'invalid_num', '3', 'invalid_num', '4']) - .mapNotNull(int.tryParse), - emitsInOrder([1, 2, 3, 4])); - - // 0-----1-----2-----3-----...-----8-----9-----| - // 1-----null--3-----null--...-----9-----null--| - // 1--3--5--7--9--| - final stream = Stream.periodic(const Duration(milliseconds: 10), (i) => i) - .take(10) - .transform(MapNotNullStreamTransformer((i) => i.isOdd ? null : i + 1)); - expect(stream, emitsInOrder([1, 3, 5, 7, 9, emitsDone])); - }); - - test('Rx.mapNotNull.shouldThrowA', () { - expect( - Stream.error(Exception()).mapNotNull((_) => true), - emitsError(isA()), - ); - - expect( - Rx.concat([ - Stream.fromIterable([1, 2]), - Stream.error(Exception()), - Stream.value(3), - ]).mapNotNull((i) => i.isEven ? i + 1 : null), - emitsInOrder([ - 3, - emitsError(isException), - emitsDone, - ]), - ); - }); - - test('Rx.mapNotNull.shouldThrowB', () { - expect( - Stream.fromIterable([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).mapNotNull((i) { - if (i == 4) throw Exception(); - return i.isEven ? i + 1 : null; - }), - emitsInOrder([ - 3, - emitsError(isException), - 7, - 9, - 11, - emitsDone, - ]), - ); - }); - - test('Rx.mapNotNull.asBroadcastStream', () { - final stream = Stream.fromIterable([2, 3, 4, 5, 6]) - .mapNotNull((i) => null) - .asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - expect(true, true); - }); - - test('Rx.mapNotNull.singleSubscription', () { - final stream = StreamController().stream.mapNotNull((i) => i); - - expect(stream.isBroadcast, isFalse); - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - }); - - test('Rx.mapNotNull.pause.resume', () async { - final subscription = - Stream.fromIterable([2, 3, 4, 5, 6]).mapNotNull((i) => i).listen(null); - - subscription - ..pause() - ..onData(expectAsync1((data) { - expect(data, 2); - subscription.cancel(); - })) - ..resume(); - }); - - test('Rx.mapNotNull.nullable', () { - nullableTest( - (s) => s.mapNotNull((i) => i), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/map_to_test.dart b/sandbox/reactivex/test/transformers/map_to_test.dart deleted file mode 100644 index 6e7febf..0000000 --- a/sandbox/reactivex/test/transformers/map_to_test.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.mapTo', () async { - await expectLater(Rx.range(1, 4).mapTo(true), - emitsInOrder([true, true, true, true, emitsDone])); - }); - - test('Rx.mapTo.shouldThrow', () async { - await expectLater( - Rx.range(1, 4).concatWith([Stream.error(Error())]).mapTo(true), - emitsInOrder([ - true, - true, - true, - true, - emitsError(TypeMatcher()), - emitsDone - ])); - }); - - test('Rx.mapTo.reusable', () async { - final transformer = MapToStreamTransformer(true); - final stream = Rx.range(1, 4).asBroadcastStream(); - - stream.transform(transformer).listen(null); - stream.transform(transformer).listen(null); - - await expectLater(true, true); - }); - - test('Rx.mapTo.pause.resume', () async { - late StreamSubscription subscription; - final stream = Stream.value(1).mapTo(true); - - subscription = stream.listen(expectAsync1((value) { - expect(value, isTrue); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.mapTo accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.mapTo(1); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.mapTo.nullable', () { - nullableTest( - (s) => s.mapTo('String'), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/materialize_test.dart b/sandbox/reactivex/test/transformers/materialize_test.dart deleted file mode 100644 index bcb81c4..0000000 --- a/sandbox/reactivex/test/transformers/materialize_test.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.materialize.happyPath', () async { - final stream = Stream.value(1); - final notifications = >[]; - - stream.materialize().listen(notifications.add, onDone: expectAsync0(() { - expect(notifications, - [StreamNotification.data(1), StreamNotification.done()]); - })); - }); - - test('Rx.materialize.reusable', () async { - final transformer = MaterializeStreamTransformer(); - final stream = Stream.value(1).asBroadcastStream(); - final notificationsA = >[], - notificationsB = >[]; - - stream.transform(transformer).listen(notificationsA.add, - onDone: expectAsync0(() { - expect(notificationsA, - [StreamNotification.data(1), StreamNotification.done()]); - })); - - stream.transform(transformer).listen(notificationsB.add, - onDone: expectAsync0(() { - expect(notificationsB, - [StreamNotification.data(1), StreamNotification.done()]); - })); - }); - - test('materializeTransformer.happyPath', () async { - final stream = Stream.fromIterable(const [1]); - final notifications = >[]; - - stream - .transform(MaterializeStreamTransformer()) - .listen(notifications.add, onDone: expectAsync0(() { - expect(notifications, - [StreamNotification.data(1), StreamNotification.done()]); - })); - }); - - test('materializeTransformer.sadPath', () async { - final stream = Stream.error(Exception()); - final notifications = >[]; - - stream - .transform(MaterializeStreamTransformer()) - .listen(notifications.add, - onError: expectAsync2((Exception e, StackTrace s) { - // Check to ensure the stream does not come to this point - expect(true, isFalse); - }, count: 0), onDone: expectAsync0(() { - expect(notifications.length, 2); - expect(notifications[0].isError, isTrue); - expect(notifications[1].isDone, isTrue); - })); - }); - - test('materializeTransformer.onPause.onResume', () async { - final stream = Stream.fromIterable(const [1]); - final notifications = >[]; - - stream - .transform(MaterializeStreamTransformer()) - .listen(notifications.add, onDone: expectAsync0(() { - expect(notifications, >[ - StreamNotification.data(1), - StreamNotification.done() - ]); - })) - ..pause() - ..resume(); - }); - - test('Rx.materialize accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.materialize(); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.materialize.nullable', () { - nullableTest>( - (s) => s.materialize(), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/max_test.dart b/sandbox/reactivex/test/transformers/max_test.dart deleted file mode 100644 index cacd395..0000000 --- a/sandbox/reactivex/test/transformers/max_test.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.max', () async { - await expectLater(_getStream().max(), completion(9)); - - expect( - await Stream.fromIterable([1, 2, 3, 3.5]).max(), - 3.5, - ); - }); - - test('Rx.max.empty.shouldThrow', () { - expect( - () => Stream.empty().max(), - throwsStateError, - ); - }); - - test('Rx.max.error.shouldThrow', () { - expect( - () => Stream.value(1).concatWith( - [Stream.error(Exception('This is exception'))], - ).max(), - throwsException, - ); - }); - - test('Rx.max.with.comparator', () async { - await expectLater( - Stream.fromIterable(['one', 'two', 'three']) - .max((a, b) => a.length - b.length), - completion('three'), - ); - }); - - test('Rx.max.errorComparator.shouldThrow', () { - expect( - () => _getStream().max((a, b) => throw Exception()), - throwsException, - ); - }); - - test('Rx.max.without.comparator.Comparable', () async { - const expected = _Class2(3); - expect( - await Stream.fromIterable(const [ - _Class2(0), - expected, - _Class2(2), - _Class2(-1), - _Class2(2), - ]).max(), - expected, - ); - }); - - test('Rx.max.without.comparator.not.Comparable', () async { - expect( - () => Stream.fromIterable(const [ - _Class1(0), - _Class1(3), - _Class1(2), - _Class1(3), - _Class1(2), - ]).max(), - throwsStateError, - ); - }); -} - -class ErrorComparator implements Comparable { - @override - int compareTo(ErrorComparator other) { - throw Exception(); - } -} - -Stream _getStream() => - Stream.fromIterable(const [2, 3, 3, 5, 2, 9, 1, 2, 0]); - -class _Class1 { - final int value; - - const _Class1(this.value); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is _Class1 && - runtimeType == other.runtimeType && - value == other.value; - - @override - int get hashCode => value.hashCode; - - @override - String toString() => '_Class{value: $value}'; -} - -class _Class2 implements Comparable<_Class2> { - final int value; - - const _Class2(this.value); - - @override - String toString() => '_Class2{value: $value}'; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is _Class2 && - runtimeType == other.runtimeType && - value == other.value; - - @override - int get hashCode => value.hashCode; - - @override - int compareTo(_Class2 other) => value.compareTo(other.value); -} diff --git a/sandbox/reactivex/test/transformers/merge_with_test.dart b/sandbox/reactivex/test/transformers/merge_with_test.dart deleted file mode 100644 index ed6efdb..0000000 --- a/sandbox/reactivex/test/transformers/merge_with_test.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.mergeWith', () async { - final delayedStream = Rx.timer(1, Duration(milliseconds: 10)); - final immediateStream = Stream.value(2); - const expected = [2, 1]; - var count = 0; - - delayedStream.mergeWith([immediateStream]).listen(expectAsync1((result) { - expect(result, expected[count++]); - }, count: expected.length)); - }); - - test('Rx.mergeWith accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.mergeWith([Stream.empty()]); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.mergeWith on single stream should stay single ', () async { - final delayedStream = Rx.timer(1, Duration(milliseconds: 10)); - final immediateStream = Stream.value(2); - final expected = [2, 1, emitsDone]; - - final concatenatedStream = delayedStream.mergeWith([immediateStream]); - - expect(concatenatedStream.isBroadcast, isFalse); - expect(concatenatedStream, emitsInOrder(expected)); - }); - - test('Rx.mergeWith on broadcast stream should stay broadcast ', () async { - final delayedStream = - Rx.timer(1, Duration(milliseconds: 10)).asBroadcastStream(); - final immediateStream = Stream.value(2); - final expected = [2, 1, emitsDone]; - - final concatenatedStream = delayedStream.mergeWith([immediateStream]); - - expect(concatenatedStream.isBroadcast, isTrue); - expect(concatenatedStream, emitsInOrder(expected)); - }); - - test('Rx.mergeWith multiple subscriptions on single ', () async { - final delayedStream = Rx.timer(1, Duration(milliseconds: 10)); - final immediateStream = Stream.value(2); - - final concatenatedStream = delayedStream.mergeWith([immediateStream]); - - expect(() => concatenatedStream.listen(null), returnsNormally); - expect(() => concatenatedStream.listen(null), - throwsA(TypeMatcher())); - }); -} diff --git a/sandbox/reactivex/test/transformers/min_test.dart b/sandbox/reactivex/test/transformers/min_test.dart deleted file mode 100644 index 6c2c772..0000000 --- a/sandbox/reactivex/test/transformers/min_test.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.min', () async { - await expectLater(_getStream().min(), completion(0)); - - expect( - await Stream.fromIterable([1, 2, 3, 3.5]).min(), - 1, - ); - }); - - test('Rx.min.empty.shouldThrow', () { - expect( - () => Stream.empty().min(), - throwsStateError, - ); - }); - - test('Rx.min.error.shouldThrow', () { - expect( - () => Stream.value(1).concatWith( - [Stream.error(Exception('This is exception'))], - ).min(), - throwsException, - ); - }); - - test('Rx.min.errorComparator.shouldThrow', () { - expect( - () => _getStream().min((a, b) => throw Exception()), - throwsException, - ); - }); - - test('Rx.min.with.comparator', () async { - await expectLater( - Stream.fromIterable(['one', 'two', 'three']) - .min((a, b) => a.length - b.length), - completion('one'), - ); - }); - - test('Rx.min.without.comparator.Comparable', () async { - const expected = _Class2(-1); - expect( - await Stream.fromIterable(const [ - _Class2(0), - _Class2(3), - _Class2(2), - expected, - _Class2(2), - ]).min(), - expected, - ); - }); - - test('Rx.min.without.comparator.not.Comparable', () async { - expect( - () => Stream.fromIterable(const [ - _Class1(0), - _Class1(3), - _Class1(2), - _Class1(3), - _Class1(2), - ]).min(), - throwsStateError, - ); - }); -} - -Stream _getStream() => - Stream.fromIterable(const [2, 3, 3, 5, 2, 9, 1, 2, 0]); - -class _Class1 { - final int value; - - const _Class1(this.value); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is _Class1 && - runtimeType == other.runtimeType && - value == other.value; - - @override - int get hashCode => value.hashCode; - - @override - String toString() => '_Class{value: $value}'; -} - -class _Class2 implements Comparable<_Class2> { - final int value; - - const _Class2(this.value); - - @override - String toString() => '_Class2{value: $value}'; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is _Class2 && - runtimeType == other.runtimeType && - value == other.value; - - @override - int get hashCode => value.hashCode; - - @override - int compareTo(_Class2 other) => value.compareTo(other.value); -} diff --git a/sandbox/reactivex/test/transformers/on_error_resume_test.dart b/sandbox/reactivex/test/transformers/on_error_resume_test.dart deleted file mode 100644 index ce9253c..0000000 --- a/sandbox/reactivex/test/transformers/on_error_resume_test.dart +++ /dev/null @@ -1,209 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() => Stream.fromIterable(const [0, 1, 2, 3]); - -const List expected = [0, 1, 2, 3]; - -void main() { - test('Rx.onErrorResumeNext', () async { - var count = 0; - - Stream.error(Exception()) - .onErrorResumeNext(_getStream()) - .listen(expectAsync1((result) { - expect(result, expected[count++]); - }, count: expected.length)); - }); - - test('Rx.onErrorResume', () async { - var count = 0; - - Stream.error(Exception()) - .onErrorResume((e, st) => _getStream()) - .listen(expectAsync1((result) { - expect(result, expected[count++]); - }, count: expected.length)); - }); - - test('Rx.onErrorResume.correctError', () async { - final exception = Exception(); - - expect( - Stream.error(exception).onErrorResume((e, st) => Stream.value(e)), - emits(exception), - ); - }); - - test('Rx.onErrorResumeNext.asBroadcastStream', () async { - final stream = Stream.error(Exception()) - .onErrorResumeNext(_getStream()) - .asBroadcastStream(); - var countA = 0, countB = 0; - - await expectLater(stream.isBroadcast, isTrue); - - stream.listen(expectAsync1((result) { - expect(result, expected[countA++]); - }, count: expected.length)); - stream.listen(expectAsync1((result) { - expect(result, expected[countB++]); - }, count: expected.length)); - }); - - test('Rx.onErrorResumeNext.error.shouldThrow', () async { - final streamWithError = Stream.error(Exception()) - .onErrorResumeNext(Stream.error(Exception())); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.onErrorResumeNext.pause.resume', () async { - final transformer = - OnErrorResumeStreamTransformer((_, __) => _getStream()); - final exp = const [50] + expected; - late StreamSubscription subscription; - var count = 0; - - subscription = Rx.merge([ - Stream.value(50), - Stream.error(Exception()), - ]).transform(transformer).listen(expectAsync1((result) { - expect(result, exp[count++]); - - if (count == exp.length) { - subscription.cancel(); - } - }, count: exp.length)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.onErrorResumeNext.close', () async { - var count = 0; - - Stream.error(Exception()).onErrorResumeNext(_getStream()).listen( - expectAsync1((result) { - expect(result, expected[count++]); - }, count: expected.length), - onDone: expectAsync0(() { - // The code should reach this point - expect(true, true); - }, count: 1)); - }); - - test('Rx.onErrorResumeNext.noErrors.close', () async { - expect( - Stream.empty().onErrorResumeNext(_getStream()), - emitsDone, - ); - }); - - test('OnErrorResumeStreamTransformer.reusable', () async { - final transformer = OnErrorResumeStreamTransformer( - (_, __) => _getStream().asBroadcastStream()); - var countA = 0, countB = 0; - - Stream.error(Exception()) - .transform(transformer) - .listen(expectAsync1((result) { - expect(result, expected[countA++]); - }, count: expected.length)); - - Stream.error(Exception()) - .transform(transformer) - .listen(expectAsync1((result) { - expect(result, expected[countB++]); - }, count: expected.length)); - }); - - test('Rx.onErrorResume accidental broadcast', () async { - final controller = StreamController(); - - final stream = - controller.stream.onErrorResume((_, __) => Stream.empty()); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.onErrorResumeNext accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.onErrorResumeNext(Stream.empty()); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.onErrorResume still adds data when Stream emits an error: issue/616', - () { - { - final stream = Rx.concat([ - Stream.value(1), - Stream.error(Exception()), - Stream.fromIterable([2, 3]), - Stream.error(Exception()), - Stream.value(4), - ]).onErrorResume((e, s) => Stream.value(-1)); - expect( - stream, - emitsInOrder([1, -1, 2, 3, -1, 4, emitsDone]), - ); - } - - { - final stream = Rx.concat([ - Stream.value(1), - Stream.error(Exception()), - Stream.fromIterable([2, 3]), - Stream.error(Exception()), - Stream.value(4), - ]).onErrorResumeNext(Stream.value(-1)); - expect( - stream, - emitsInOrder([1, -1, 2, 3, -1, 4, emitsDone]), - ); - } - }); - - test('Rx.onErrorResumeNext with many errors', () { - final stream = Rx.concat([ - Stream.value(1), - Stream.error(Exception()), - Stream.value(2), - Stream.error(StateError('')), - Stream.value(3), - ]).onErrorResume((e, s) { - if (e is Exception) { - return Rx.timer(-1, const Duration(milliseconds: 100)); - } - if (e is StateError) { - return Rx.timer(-2, const Duration(milliseconds: 200)); - } - throw e; - }); - expect( - stream, - emitsInOrder([1, 2, 3, -1, -2, emitsDone]), - ); - }); - - test('Rx.onErrorResumeNext.nullable', () { - nullableTest( - (s) => s.onErrorResumeNext(Stream.empty()), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/on_error_return_test.dart b/sandbox/reactivex/test/transformers/on_error_return_test.dart deleted file mode 100644 index d4d0644..0000000 --- a/sandbox/reactivex/test/transformers/on_error_return_test.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - const num expected = 0; - - test('Rx.onErrorReturn', () async { - Stream.error(Exception()) - .onErrorReturn(0) - .listen(expectAsync1((num result) { - expect(result, expected); - })); - }); - - test('Rx.onErrorReturn.asBroadcastStream', () async { - final stream = - Stream.error(Exception()).onErrorReturn(0).asBroadcastStream(); - - await expectLater(stream.isBroadcast, isTrue); - - stream.listen(expectAsync1((num result) { - expect(result, expected); - })); - - stream.listen(expectAsync1((num result) { - expect(result, expected); - })); - }); - - test('Rx.onErrorReturn.pause.resume', () async { - late StreamSubscription subscription; - - subscription = Stream.error(Exception()) - .onErrorReturn(0) - .listen(expectAsync1((num result) { - expect(result, expected); - - subscription.cancel(); - })); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.onErrorReturn accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.onErrorReturn(1); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.onErrorReturn still adds data when Stream emits an error: issue/616', - () { - final stream = Rx.concat([ - Stream.value(1), - Stream.error(Exception()), - Stream.fromIterable([2, 3]), - Stream.error(Exception()), - Stream.value(4), - ]).onErrorReturn(-1); - expect( - stream, - emitsInOrder([1, -1, 2, 3, -1, 4, emitsDone]), - ); - }); - - test('Rx.onErrorReturn.nullable', () { - nullableTest( - (s) => s.onErrorReturn('String'), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/on_error_return_with_test.dart b/sandbox/reactivex/test/transformers/on_error_return_with_test.dart deleted file mode 100644 index 7ffc726..0000000 --- a/sandbox/reactivex/test/transformers/on_error_return_with_test.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - const num expected = 0; - - test('Rx.onErrorReturnWith', () async { - Stream.error(Exception()) - .onErrorReturnWith((e, _) => e is StateError ? 1 : 0) - .listen(expectAsync1((num result) { - expect(result, expected); - })); - }); - - test('Rx.onErrorReturnWith.asBroadcastStream', () async { - final stream = Stream.error(Exception()) - .onErrorReturnWith((_, __) => 0) - .asBroadcastStream(); - - await expectLater(stream.isBroadcast, isTrue); - - stream.listen(expectAsync1((num result) { - expect(result, expected); - })); - - stream.listen(expectAsync1((num result) { - expect(result, expected); - })); - }); - - test('Rx.onErrorReturnWith.pause.resume', () async { - late StreamSubscription subscription; - - subscription = Stream.error(Exception()) - .onErrorReturnWith((_, __) => 0) - .listen(expectAsync1((num result) { - expect(result, expected); - - subscription.cancel(); - })); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.onErrorReturnWith accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.onErrorReturnWith((_, __) => 1); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test( - 'Rx.onErrorReturnWith still adds data when Stream emits an error: issue/616', - () { - final stream = Rx.concat([ - Stream.value(1), - Stream.error(Exception()), - Stream.fromIterable([2, 3]), - Stream.error(Exception()), - Stream.value(4), - ]).onErrorReturnWith((e, s) => -1); - expect( - stream, - emitsInOrder([1, -1, 2, 3, -1, 4, emitsDone]), - ); - }); - - test('Rx.onErrorReturnWith.nullable', () { - nullableTest( - (s) => s.onErrorReturnWith((e, s) => 'String'), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/scan_test.dart b/sandbox/reactivex/test/transformers/scan_test.dart deleted file mode 100644 index 3913017..0000000 --- a/sandbox/reactivex/test/transformers/scan_test.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.scan', () async { - const expectedOutput = [1, 3, 6, 10]; - var count = 0; - - Stream.fromIterable(const [1, 2, 3, 4]) - .scan((acc, value, index) => acc + value, 0) - .listen(expectAsync1((result) { - expect(expectedOutput[count++], result); - }, count: expectedOutput.length)); - }); - - test('Rx.scan.nullable', () { - nullableTest( - (s) => s.scan((acc, value, index) => acc, null), - ); - - expect( - Stream.fromIterable(const [1, 2, 3, 4]) - .scan((acc, value, index) => (acc ?? 0) + value, null) - .cast(), - emitsInOrder([1, 3, 6, 10]), - ); - }); - - test('Rx.scan.reusable', () async { - final transformer = - ScanStreamTransformer((acc, value, index) => acc + value, 0); - const expectedOutput = [1, 3, 6, 10]; - var countA = 0, countB = 0; - - Stream.fromIterable(const [1, 2, 3, 4]) - .transform(transformer) - .listen(expectAsync1((result) { - expect(expectedOutput[countA++], result); - }, count: expectedOutput.length)); - - Stream.fromIterable(const [1, 2, 3, 4]) - .transform(transformer) - .listen(expectAsync1((result) { - expect(expectedOutput[countB++], result); - }, count: expectedOutput.length)); - }); - - test('Rx.scan.asBroadcastStream', () async { - final stream = Stream.fromIterable(const [1, 2, 3, 4]) - .asBroadcastStream() - .scan((acc, value, index) => acc + value, 0); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.scan.error.shouldThrow', () async { - final streamWithError = Stream.fromIterable(const [1, 2, 3, 4]) - .scan((acc, value, index) => throw StateError('oh noes!'), 0); - - streamWithError.listen(null, - onError: expectAsync2((StateError e, StackTrace s) { - expect(e, isStateError); - }, count: 4)); - }); - - test('Rx.scan accidental broadcast', () async { - final controller = StreamController(); - - final stream = - controller.stream.scan((acc, value, index) => acc + value, 0); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); -} diff --git a/sandbox/reactivex/test/transformers/skip_last_test.dart b/sandbox/reactivex/test/transformers/skip_last_test.dart deleted file mode 100644 index 6c5349c..0000000 --- a/sandbox/reactivex/test/transformers/skip_last_test.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.skipLast', () async { - final stream = Stream.fromIterable([1, 2, 3, 4, 5]).skipLast(3); - await expectLater( - stream, - emitsInOrder([1, 2, emitsDone]), - ); - }); - - test('Rx.skipLast.zero', () async { - var count = 0; - final values = [1, 2, 3, 4, 5]; - final stream = - Stream.fromIterable(values).doOnData((_) => count++).skipLast(0); - await expectLater( - stream, - emitsInOrder([1, 2, 3, 4, 5, emitsDone]), - ); - expect(count, equals(values.length)); - }); - - test('Rx.skipLast.skipMoreThanLength', () async { - final stream = Stream.fromIterable([1, 2, 3, 4, 5]).skipLast(100); - - await expectLater( - stream, - emits(emitsDone), - ); - }); - - test('Rx.skipLast.emitsError', () async { - final stream = Stream.error(Exception()).skipLast(3); - await expectLater(stream, emitsError(isException)); - }); - - test('Rx.skipLast.countCantBeNegative', () async { - Stream stream() => Stream.fromIterable([1, 2, 3, 4, 5]).skipLast(-1); - expect(stream, throwsA(isArgumentError)); - }); - - test('Rx.skipLast.reusable', () async { - final transformer = SkipLastStreamTransformer(1); - Stream stream() => Stream.fromIterable([1, 2, 3, 4, 5]).skipLast(2); - var valueA = 1, valueB = 1; - - stream().transform(transformer).listen(expectAsync1( - (result) { - expect(result, valueA++); - }, - count: 2, - )); - - stream().transform(transformer).listen(expectAsync1( - (result) { - expect(result, valueB++); - }, - count: 2, - )); - }); - - test('Rx.skipLast.asBroadcastStream', () async { - final stream = - Stream.fromIterable([1, 2, 3, 4, 5]).skipLast(3).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.skipLast.pause.resume', () async { - late StreamSubscription subscription; - - subscription = Stream.fromIterable([1, 2, 3, 4, 5]) - .skipLast(3) - .listen(expectAsync1((data) { - expect(data, 1); - subscription.cancel(); - })); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.skipLast.singleSubscription', () async { - final controller = StreamController(); - - final stream = controller.stream.skipLast(3); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.skipLast.nullable', () { - nullableTest( - (s) => s.skipLast(1), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/skip_until_test.dart b/sandbox/reactivex/test/transformers/skip_until_test.dart deleted file mode 100644 index fa3a97c..0000000 --- a/sandbox/reactivex/test/transformers/skip_until_test.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 100), () => controller.add(1)); - Timer(const Duration(milliseconds: 200), () => controller.add(2)); - Timer(const Duration(milliseconds: 300), () => controller.add(3)); - Timer(const Duration(milliseconds: 400), () { - controller.add(4); - controller.close(); - }); - - return controller.stream; -} - -Stream _getOtherStream() { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 250), () { - controller.add(1); - controller.close(); - }); - - return controller.stream; -} - -void main() { - test('Rx.skipUntil', () async { - const expectedOutput = [3, 4]; - var count = 0; - - _getStream().skipUntil(_getOtherStream()).listen(expectAsync1((result) { - expect(expectedOutput[count++], result); - }, count: expectedOutput.length)); - }); - - test('Rx.skipUntil.shouldClose', () async { - _getStream() - .skipUntil(Stream.empty()) - .listen(null, onDone: expectAsync0(() => expect(true, isTrue))); - }); - - test('Rx.skipUntil.reusable', () async { - final transformer = SkipUntilStreamTransformer( - _getOtherStream().asBroadcastStream()); - const expectedOutput = [3, 4]; - var countA = 0, countB = 0; - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(expectedOutput[countA++], result); - }, count: expectedOutput.length)); - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(expectedOutput[countB++], result); - }, count: expectedOutput.length)); - }); - - test('Rx.skipUntil.asBroadcastStream', () async { - final stream = _getStream() - .asBroadcastStream() - .skipUntil(_getOtherStream().asBroadcastStream()); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.skipUntil.error.shouldThrowA', () async { - final streamWithError = - Stream.error(Exception()).skipUntil(_getOtherStream()); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.skipUntil.error.shouldThrowB', () async { - final streamWithError = - Stream.value(1).skipUntil(Stream.error(Exception('Oh noes!'))); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.skipUntil.pause.resume', () async { - late StreamSubscription subscription; - const expectedOutput = [3, 4]; - var count = 0; - - subscription = - _getStream().skipUntil(_getOtherStream()).listen(expectAsync1((result) { - expect(result, expectedOutput[count++]); - - if (count == expectedOutput.length) { - subscription.cancel(); - } - }, count: expectedOutput.length)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.skipUntil accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.skipUntil(Stream.empty()); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.skipUntil.nullable', () { - nullableTest( - (s) => s.skipUntil(Stream.empty()), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/start_with_error_test.dart b/sandbox/reactivex/test/transformers/start_with_error_test.dart deleted file mode 100644 index 7a61c09..0000000 --- a/sandbox/reactivex/test/transformers/start_with_error_test.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/src/transformers/start_with_error.dart'; -import 'package:test/test.dart'; - -Stream _getStream() => Stream.fromIterable(const [1, 2, 3, 4]); - -void main() { - test('Rx.startWithError', () async { - final transformer = StartWithErrorStreamTransformer( - Exception(), StackTrace.fromString('oh noes!')); - const expectedOutput = [1, 2, 3, 4]; - - await expectLater(_getStream().transform(transformer), - emitsInOrder([emitsError(isException), ...expectedOutput])); - }); - - test('Rx.startWithError.reusable', () async { - final transformer = StartWithErrorStreamTransformer( - Exception(), StackTrace.fromString('oh noes!')); - const expectedOutput = [1, 2, 3, 4]; - - await expectLater(_getStream().transform(transformer), - emitsInOrder([emitsError(isException), ...expectedOutput])); - await expectLater(_getStream().transform(transformer), - emitsInOrder([emitsError(isException), ...expectedOutput])); - }); - - test('Rx.startWithError.asBroadcastStream', () async { - final transformer = StartWithErrorStreamTransformer( - Exception(), StackTrace.fromString('oh noes!')); - final stream = _getStream().asBroadcastStream().transform(transformer); - const expectedOutput = [1, 2, 3, 4]; - - // listen twice on same stream - await expectLater( - stream, - emitsInOrder( - [emitsError(isException), ...expectedOutput, emitsDone])); - await expectLater(stream, emitsDone); - }); - - test('Rx.startWithError.error.shouldThrow', () async { - final transformer = StartWithErrorStreamTransformer( - Exception(), StackTrace.fromString('oh noes!')); - final streamWithError = - Stream.error(Exception()).transform(transformer); - - await expectLater(streamWithError, emitsError(isException)); - }); - - test('Rx.startWithError.pause.resume', () async { - final transformer = StartWithErrorStreamTransformer( - Exception(), StackTrace.fromString('oh noes!')); - const expectedOutput = [1, 2, 3, 4]; - var count = 0; - - late StreamSubscription subscription; - subscription = _getStream().transform(transformer).listen( - expectAsync1((result) { - expect(expectedOutput[count++], result); - - if (count == expectedOutput.length) { - subscription.cancel(); - } - }, count: expectedOutput.length), - onError: (Object e, StackTrace s) => expect(e, isException)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.startWithError accidental broadcast', () async { - final transformer = StartWithErrorStreamTransformer( - Exception(), StackTrace.fromString('oh noes!')); - final controller = StreamController(); - - final stream = controller.stream.transform(transformer); - - stream.listen(null, onError: (Object e, StackTrace s) {}); - expect(() => stream.listen(null, onError: (Object e, StackTrace s) {}), - throwsStateError); - - controller.add(1); - }); -} diff --git a/sandbox/reactivex/test/transformers/start_with_many_test.dart b/sandbox/reactivex/test/transformers/start_with_many_test.dart deleted file mode 100644 index 7159e3b..0000000 --- a/sandbox/reactivex/test/transformers/start_with_many_test.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() => Stream.fromIterable(const [1, 2, 3, 4]); - -void main() { - test('Rx.startWithMany', () async { - const expectedOutput = [5, 6, 1, 2, 3, 4]; - var count = 0; - - _getStream().startWithMany(const [5, 6]).listen(expectAsync1((result) { - expect(expectedOutput[count++], result); - }, count: expectedOutput.length)); - }); - - test('Rx.startWithMany.reusable', () async { - final transformer = StartWithManyStreamTransformer(const [5, 6]); - const expectedOutput = [5, 6, 1, 2, 3, 4]; - var countA = 0, countB = 0; - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(expectedOutput[countA++], result); - }, count: expectedOutput.length)); - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(expectedOutput[countB++], result); - }, count: expectedOutput.length)); - }); - - test('Rx.startWithMany.asBroadcastStream', () async { - final stream = _getStream().asBroadcastStream().startWithMany(const [5, 6]); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.startWithMany.error.shouldThrowA', () async { - final streamWithError = - Stream.error(Exception()).startWithMany(const [5, 6]); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.startWithMany.pause.resume', () async { - const expectedOutput = [5, 6, 1, 2, 3, 4]; - var count = 0; - - late StreamSubscription subscription; - subscription = - _getStream().startWithMany(const [5, 6]).listen(expectAsync1((result) { - expect(expectedOutput[count++], result); - - if (count == expectedOutput.length) { - subscription.cancel(); - } - }, count: expectedOutput.length)); - - subscription.pause(); - subscription.resume(); - }); - test('Rx.startWithMany accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.startWithMany(const [1, 2, 3]); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.startWithMany.nullable', () { - nullableTest( - (s) => s.startWithMany([]), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/start_with_test.dart b/sandbox/reactivex/test/transformers/start_with_test.dart deleted file mode 100644 index 235b6ff..0000000 --- a/sandbox/reactivex/test/transformers/start_with_test.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() => Stream.fromIterable(const [1, 2, 3, 4]); - -void main() { - test('Rx.startWith', () async { - const expectedOutput = [5, 1, 2, 3, 4]; - var count = 0; - - _getStream().startWith(5).listen(expectAsync1((result) { - expect(expectedOutput[count++], result); - }, count: expectedOutput.length)); - }); - - test('Rx.startWith.reusable', () async { - final transformer = StartWithStreamTransformer(5); - const expectedOutput = [5, 1, 2, 3, 4]; - var countA = 0, countB = 0; - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(expectedOutput[countA++], result); - }, count: expectedOutput.length)); - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(expectedOutput[countB++], result); - }, count: expectedOutput.length)); - }); - - test('Rx.startWith.asBroadcastStream', () async { - final stream = _getStream().asBroadcastStream().startWith(5); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.startWith.error.shouldThrow', () async { - final streamWithError = Stream.error(Exception()).startWith(5); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.startWith.pause.resume', () async { - const expectedOutput = [5, 1, 2, 3, 4]; - var count = 0; - - late StreamSubscription subscription; - subscription = _getStream().startWith(5).listen(expectAsync1((result) { - expect(expectedOutput[count++], result); - - if (count == expectedOutput.length) { - subscription.cancel(); - } - }, count: expectedOutput.length)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.startWith accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.startWith(1); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test( - 'Rx.startWith broadcast stream should not startWith on multiple subscribers', - () async { - final controller = StreamController.broadcast(); - - final stream = controller.stream.startWith(1); - - await controller.close(); - - stream.listen(null); - - await Future.delayed(const Duration(milliseconds: 10)); - - await expectLater(stream, emits(emitsDone)); - }, skip: true); - - test('Rx.startWith.nullable', () { - nullableTest( - (s) => s.startWith('String'), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/switch_if_empty_test.dart b/sandbox/reactivex/test/transformers/switch_if_empty_test.dart deleted file mode 100644 index c1f15be..0000000 --- a/sandbox/reactivex/test/transformers/switch_if_empty_test.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.switchIfEmpty.whenEmpty', () async { - expect( - Stream.empty().switchIfEmpty(Stream.value(1)), - emitsInOrder([1, emitsDone]), - ); - }); - - test('Rx.initial.completes', () async { - expect( - Stream.value(99).switchIfEmpty(Stream.value(1)), - emitsInOrder([99, emitsDone]), - ); - }); - - test('Rx.switchIfEmpty.reusable', () async { - final transformer = SwitchIfEmptyStreamTransformer( - Stream.value(true).asBroadcastStream()); - - Stream.empty().transform(transformer).listen(expectAsync1((result) { - expect(result, true); - }, count: 1)); - - Stream.empty().transform(transformer).listen(expectAsync1((result) { - expect(result, true); - }, count: 1)); - }); - - test('Rx.switchIfEmpty.whenNotEmpty', () async { - Stream.value(false) - .switchIfEmpty(Stream.value(true)) - .listen(expectAsync1((result) { - expect(result, false); - }, count: 1)); - }); - - test('Rx.switchIfEmpty.asBroadcastStream', () async { - final stream = - Stream.empty().switchIfEmpty(Stream.value(1)).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.switchIfEmpty.error.shouldThrowA', () async { - final streamWithError = - Stream.error(Exception()).switchIfEmpty(Stream.value(1)); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.switchIfEmpty.pause.resume', () async { - late StreamSubscription subscription; - final stream = Stream.empty().switchIfEmpty(Stream.value(1)); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.switchIfEmpty accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.switchIfEmpty(Stream.empty()); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.switchIfEmpty.nullable', () { - nullableTest( - (s) => s.switchIfEmpty(Stream.value('String')), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/switch_map_test.dart b/sandbox/reactivex/test/transformers/switch_map_test.dart deleted file mode 100644 index cb86396..0000000 --- a/sandbox/reactivex/test/transformers/switch_map_test.dart +++ /dev/null @@ -1,359 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 10), () => controller.add(1)); - Timer(const Duration(milliseconds: 20), () => controller.add(2)); - Timer(const Duration(milliseconds: 30), () => controller.add(3)); - Timer(const Duration(milliseconds: 40), () { - controller.add(4); - controller.close(); - }); - - return controller.stream; -} - -Stream _getOtherStream(int value) { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 15), () => controller.add(value + 1)); - Timer(const Duration(milliseconds: 25), () => controller.add(value + 2)); - Timer(const Duration(milliseconds: 35), () => controller.add(value + 3)); - Timer(const Duration(milliseconds: 45), () { - controller.add(value + 4); - controller.close(); - }); - - return controller.stream; -} - -Stream range() => - Stream.fromIterable(const [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - -void main() { - test('Rx.switchMap', () async { - const expectedOutput = [5, 6, 7, 8]; - var count = 0; - - _getStream().switchMap(_getOtherStream).listen(expectAsync1((result) { - expect(result, expectedOutput[count++]); - }, count: expectedOutput.length)); - }); - - test('Rx.switchMap.reusable', () async { - final transformer = SwitchMapStreamTransformer(_getOtherStream); - const expectedOutput = [5, 6, 7, 8]; - var countA = 0, countB = 0; - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(result, expectedOutput[countA++]); - }, count: expectedOutput.length)); - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(result, expectedOutput[countB++]); - }, count: expectedOutput.length)); - }); - - test('Rx.switchMap.asBroadcastStream', () async { - final stream = _getStream().asBroadcastStream().switchMap(_getOtherStream); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.switchMap.error.shouldThrowA', () async { - final streamWithError = - Stream.error(Exception()).switchMap(_getOtherStream); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.switchMap.error.shouldThrowB', () async { - final streamWithError = Stream.value(1).switchMap( - (_) => Stream.error(Exception('Catch me if you can!'))); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.switchMap.error.shouldThrowC', () async { - final streamWithError = Stream.value(1).switchMap((_) { - throw Exception('oh noes!'); - }); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.switchMap.pause.resume', () async { - late StreamSubscription subscription; - final stream = Stream.value(0).switchMap((_) => Stream.value(1)); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.switchMap stream close after switch', () async { - final controller = StreamController(); - final list = controller.stream - .switchMap((it) => Stream.fromIterable([it, it])) - .toList(); - - controller.add(1); - await Future.delayed(Duration(microseconds: 1)); - controller.add(2); - - await controller.close(); - expect(await list, [1, 1, 2, 2]); - }); - - test('Rx.switchMap accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.switchMap((_) => Stream.empty()); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.switchMap closes after the last inner Stream closed - issue/511', - () async { - final outer = StreamController(); - final inner = BehaviorSubject.seeded(false); - final stream = outer.stream.switchMap((_) => inner.stream); - - expect(stream, emitsThrough(emitsDone)); - - outer.add(true); - await Future.delayed(Duration.zero); - await inner.close(); - await outer.close(); - }); - - test('Rx.switchMap every subscription triggers a listen on the root Stream', - () async { - var count = 0; - final controller = StreamController.broadcast(); - final root = - OnSubscriptionTriggerableStream(controller.stream, () => count++); - final stream = root.switchMap((event) => Stream.value(event)); - - stream.listen((event) {}); - stream.listen((event) {}); - - expect(count, 2); - - await controller.close(); - }); - - test('Rx.switchMap.nullable', () { - nullableTest( - (s) => s.switchMap((v) => Stream.value(v)), - ); - }); - - test( - 'Rx.switchMap pauses subscription when cancelling inner subscription, then resume', - () async { - var isController1Cancelled = false; - final cancelCompleter1 = Completer.sync(); - final controller1 = StreamController() - ..add(0) - ..add(1) - ..onCancel = () async { - await Future.delayed(const Duration(milliseconds: 10)); - await cancelCompleter1.future; - isController1Cancelled = true; - }; - - final controller2 = StreamController() - ..add(2) - ..add(3) - ..onListen = () { - expect( - isController1Cancelled, - true, - reason: - 'controller1 should be cancelled before controller2 is listened to', - ); - }; - - final controller = StreamController>() - ..add(controller1); - final stream = controller.stream.switchMap((c) => c.stream); - - var expected = 0; - stream.listen( - expectAsync1( - (v) async { - expect(v, expected++); - - if (v == 1) { - // switch to controller2.stream - controller.add(controller2); - - await Future.delayed(const Duration(milliseconds: 10)); - cancelCompleter1.complete(null); - } - }, - count: 4, - ), - ); - }, - ); - - test('Rx.switchMap forwards errors from the cancel()', () { - var isController1Cancelled = false; - - final controller1 = StreamController() - ..add(0) - ..add(1) - ..onCancel = () async { - await Future.delayed(const Duration(milliseconds: 10)); - isController1Cancelled = true; - throw Exception('cancel error'); - }; - - final controller2 = StreamController() - ..add(2) - ..add(3) - ..onListen = () { - expect( - isController1Cancelled, - true, - reason: - 'controller1 should be cancelled before controller2 is listened to', - ); - }; - - final controller = StreamController>() - ..add(controller1); - final stream = controller.stream.switchMap((c) => c.stream); - - var expected = 0; - stream.listen( - expectAsync1( - (v) async { - expect(v, expected++); - - if (v == 1) { - // switch to controller2.stream - controller.add(controller2); - } - }, - count: 4, - ), - onError: expectAsync1( - (Object error) => expect(error, isException), - count: 1, - ), - ); - }); - - test( - 'Rx.switchMap pauses the next inner StreamSubscription when pausing while cancelling the previous inner Stream', - () { - var isController1Cancelled = false; - final cancelCompleter1 = Completer.sync(); - final controller1 = StreamController() - ..add(0) - ..add(1) - ..onCancel = () async { - await Future.delayed(const Duration(milliseconds: 10)); - await cancelCompleter1.future; - isController1Cancelled = true; - }; - - final controller2 = StreamController() - ..add(2) - ..add(3) - ..onListen = () { - expect( - isController1Cancelled, - true, - reason: - 'controller1 should be cancelled before controller2 is listened to', - ); - }; - - final controller = StreamController>() - ..add(controller1); - final stream = controller.stream.switchMap((c) => c.stream); - - var expected = 0; - late StreamSubscription subscription; - subscription = stream.listen( - expectAsync1( - (v) async { - expect(v, expected++); - - if (v == 1) { - // switch to controller2.stream - controller.add(controller2); - - await Future.delayed(const Duration(milliseconds: 10)); - - // pauses the subscription while cancelling the controller1 - subscription.pause(); - - // let the cancellation of controller1 complete - cancelCompleter1.complete(null); - - // make sure the controller2.stream is added to the controller - await pumpEventQueue(); - - // controller2.stream should be paused - expect(controller2.isPaused, true); - - // resume the subscription to continue the rest of the stream - subscription.resume(); - } - }, - count: 4, - ), - ); - }, - ); -} - -class OnSubscriptionTriggerableStream extends Stream { - final Stream inner; - final void Function() onSubscribe; - - OnSubscriptionTriggerableStream(this.inner, this.onSubscribe); - - @override - bool get isBroadcast => inner.isBroadcast; - - @override - StreamSubscription listen(void Function(T event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - onSubscribe(); - return inner.listen(onData, - onError: onError, onDone: onDone, cancelOnError: cancelOnError); - } -} diff --git a/sandbox/reactivex/test/transformers/take_last_test.dart b/sandbox/reactivex/test/transformers/take_last_test.dart deleted file mode 100644 index 64474c6..0000000 --- a/sandbox/reactivex/test/transformers/take_last_test.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.takeLast', () async { - final stream = Stream.fromIterable([1, 2, 3, 4, 5]).takeLast(3); - await expectLater( - stream, - emitsInOrder([3, 4, 5, emitsDone]), - ); - }); - - test('Rx.takeLast.zero', () async { - var count = 0; - final values = [1, 2, 3, 4, 5]; - final stream = - Stream.fromIterable(values).doOnData((_) => count++).takeLast(0); - await expectLater( - stream, - emitsInOrder([emitsDone]), - ); - expect(count, equals(values.length)); - }); - - test('Rx.takeLast.emitsError', () async { - final stream = Stream.error(Exception()).takeLast(3); - await expectLater(stream, emitsError(isException)); - }); - - test('Rx.takeLast.countCantBeNegative', () async { - Stream stream() => Stream.fromIterable([1, 2, 3, 4, 5]).takeLast(-1); - expect(stream, throwsA(isArgumentError)); - }); - - test('Rx.takeLast.reusable', () async { - final transformer = TakeLastStreamTransformer(3); - Stream stream() => Stream.fromIterable([1, 2, 3, 4, 5]).takeLast(3); - var valueA = 3, valueB = 3; - - stream().transform(transformer).listen(expectAsync1((result) { - expect(result, valueA++); - }, count: 3)); - - stream().transform(transformer).listen(expectAsync1((result) { - expect(result, valueB++); - }, count: 3)); - }); - - test('Rx.takeLast.asBroadcastStream', () async { - final stream = - Stream.fromIterable([1, 2, 3, 4, 5]).takeLast(3).asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('Rx.takeLast.pause.resume', () async { - late StreamSubscription subscription; - - subscription = Stream.fromIterable([1, 2, 3, 4, 5]) - .takeLast(3) - .listen(expectAsync1((data) { - expect(data, 3); - subscription.cancel(); - })); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.takeLast.singleSubscription', () async { - final controller = StreamController(); - - final stream = controller.stream.takeLast(3); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.takeLast.cancel', () { - final subscription = - Stream.fromIterable([1, 2, 3, 4, 5]).takeLast(3).listen(null); - subscription.onData( - expectAsync1( - (event) { - subscription.cancel(); - expect(event, 3); - }, - count: 1, - ), - ); - }, timeout: const Timeout(Duration(seconds: 1))); - - test('Rx.takeLast.nullable', () { - nullableTest( - (s) => s.takeLast(1), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/take_until_test.dart b/sandbox/reactivex/test/transformers/take_until_test.dart deleted file mode 100644 index 23efaf2..0000000 --- a/sandbox/reactivex/test/transformers/take_until_test.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 100), () => controller.add(1)); - Timer(const Duration(milliseconds: 200), () => controller.add(2)); - Timer(const Duration(milliseconds: 300), () => controller.add(3)); - Timer(const Duration(milliseconds: 400), () { - controller.add(4); - controller.close(); - }); - - return controller.stream; -} - -Stream _getOtherStream() { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 250), () { - controller.add(1); - controller.close(); - }); - - return controller.stream; -} - -void main() { - test('Rx.takeUntil', () async { - const expectedOutput = [1, 2]; - var count = 0; - - _getStream().takeUntil(_getOtherStream()).listen(expectAsync1((result) { - expect(expectedOutput[count++], result); - }, count: expectedOutput.length)); - }); - - test('Rx.takeUntil.shouldClose', () async { - _getStream() - .takeUntil(Stream.empty()) - .listen(null, onDone: expectAsync0(() => expect(true, isTrue))); - }); - - test('Rx.takeUntil.reusable', () async { - final transformer = TakeUntilStreamTransformer( - _getOtherStream().asBroadcastStream()); - const expectedOutput = [1, 2]; - var countA = 0, countB = 0; - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(expectedOutput[countA++], result); - }, count: expectedOutput.length)); - - _getStream().transform(transformer).listen(expectAsync1((result) { - expect(expectedOutput[countB++], result); - }, count: expectedOutput.length)); - }); - - test('Rx.takeUntil.asBroadcastStream', () async { - final stream = _getStream() - .asBroadcastStream() - .takeUntil(_getOtherStream().asBroadcastStream()); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.takeUntil.error.shouldThrowA', () async { - final streamWithError = - Stream.error(Exception()).takeUntil(_getOtherStream()); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.takeUntil.pause.resume', () async { - late StreamSubscription subscription; - const expectedOutput = [1, 2]; - var count = 0; - - subscription = - _getStream().takeUntil(_getOtherStream()).listen(expectAsync1((result) { - expect(result, expectedOutput[count++]); - - if (count == expectedOutput.length) { - subscription.cancel(); - } - }, count: expectedOutput.length)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.takeUntil accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.takeUntil(Stream.empty()); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.takeUntil.nullable', () { - nullableTest( - (s) => s.takeUntil(Stream.empty()), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/take_while_inclusive_test.dart b/sandbox/reactivex/test/transformers/take_while_inclusive_test.dart deleted file mode 100644 index 7b2f774..0000000 --- a/sandbox/reactivex/test/transformers/take_while_inclusive_test.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.takeWhileInclusive', () async { - final stream = Stream.fromIterable([2, 3, 4, 5, 6, 1, 2, 3]) - .takeWhileInclusive((i) => i < 4); - await expectLater( - stream, - emitsInOrder([2, 3, 4, emitsDone]), - ); - }); - - test('Rx.takeWhileInclusive.shouldClose', () async { - final stream = - Stream.fromIterable([2, 3, 4, 5, 6, 1, 2, 3]).takeWhileInclusive((i) { - if (i == 4) { - throw Exception(); - } else { - return true; - } - }); - await expectLater( - stream, - emitsInOrder( - [ - 2, - 3, - emitsError(isA()), - emitsDone, - ], - ), - ); - }); - - test('Rx.takeWhileInclusive.asBroadcastStream', () async { - final stream = Stream.fromIterable([2, 3, 4, 5, 6]) - .takeWhileInclusive((i) => i < 4) - .asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - await expectLater(true, true); - }); - - test('Rx.takeWhileInclusive.shouldThrowB', () async { - final stream = - Stream.error(Exception()).takeWhileInclusive((_) => true); - await expectLater( - stream, - emitsError(isA()), - ); - }); - - test('Rx.takeWhileInclusive.pause.resume', () async { - late StreamSubscription subscription; - - subscription = Stream.fromIterable([2, 3, 4, 5, 6]) - .takeWhileInclusive((i) => i < 4) - .listen(expectAsync1((data) { - expect(data, 2); - subscription.cancel(); - })); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.takeWhileInclusive accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.takeWhileInclusive((_) => true); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.takeWhileInclusive.nullable', () { - nullableTest( - (s) => s.takeWhileInclusive((_) => true), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/time_interval_test.dart b/sandbox/reactivex/test/transformers/time_interval_test.dart deleted file mode 100644 index 9c1d1f4..0000000 --- a/sandbox/reactivex/test/transformers/time_interval_test.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() => Stream.fromIterable([0, 1, 2]); - -void main() { - test('Rx.timeInterval', () async { - const expectedOutput = [0, 1, 2]; - var count = 0; - - _getStream() - .interval(const Duration(milliseconds: 1)) - .timeInterval() - .listen(expectAsync1((result) { - expect(expectedOutput[count++], result.value); - - expect( - result.interval.inMicroseconds >= 1000 /* microseconds! */, true); - }, count: expectedOutput.length)); - }); - - test('Rx.timeInterval.reusable', () async { - final transformer = TimeIntervalStreamTransformer(); - const expectedOutput = [0, 1, 2]; - var countA = 0, countB = 0; - - _getStream() - .interval(const Duration(milliseconds: 1)) - .transform(transformer) - .listen(expectAsync1((result) { - expect(expectedOutput[countA++], result.value); - - expect( - result.interval.inMicroseconds >= 1000 /* microseconds! */, true); - }, count: expectedOutput.length)); - - _getStream() - .interval(const Duration(milliseconds: 1)) - .transform(transformer) - .listen(expectAsync1((result) { - expect(expectedOutput[countB++], result.value); - - expect( - result.interval.inMicroseconds >= 1000 /* microseconds! */, true); - }, count: expectedOutput.length)); - }); - - test('Rx.timeInterval.asBroadcastStream', () async { - final stream = _getStream() - .asBroadcastStream() - .interval(const Duration(milliseconds: 1)) - .timeInterval(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.timeInterval.error.shouldThrow', () async { - final streamWithError = Stream.error(Exception()) - .interval(const Duration(milliseconds: 1)) - .timeInterval(); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.timeInterval.pause.resume', () async { - late StreamSubscription> subscription; - const expectedOutput = [0, 1, 2]; - var count = 0; - - subscription = _getStream() - .interval(const Duration(milliseconds: 1)) - .timeInterval() - .listen(expectAsync1((result) { - expect(result.value, expectedOutput[count++]); - - if (count == expectedOutput.length) { - subscription.cancel(); - } - }, count: expectedOutput.length)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.timeInterval accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.timeInterval(); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.timeInterval.nullable', () { - nullableTest>( - (s) => s.timeInterval(), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/timeout_test.dart b/sandbox/reactivex/test/transformers/timeout_test.dart deleted file mode 100644 index 5460b1d..0000000 --- a/sandbox/reactivex/test/transformers/timeout_test.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'dart:async'; - -import 'package:test/test.dart'; - -void main() { - test('Rx.timeout', () async { - late StreamSubscription subscription; - - final stream = Stream.fromFuture( - Future.delayed(Duration(milliseconds: 30), () => 1)) - .timeout(Duration(milliseconds: 1)); - - subscription = stream.listen((_) {}, - onError: expectAsync2((Object e, StackTrace s) { - expect(e is TimeoutException, isTrue); - subscription.cancel(); - }, count: 1)); - }); -} diff --git a/sandbox/reactivex/test/transformers/timestamp_test.dart b/sandbox/reactivex/test/transformers/timestamp_test.dart deleted file mode 100644 index 0a4cccf..0000000 --- a/sandbox/reactivex/test/transformers/timestamp_test.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.Rx.timestamp', () async { - const expected = [1, 2, 3]; - var count = 0; - - Stream.fromIterable(const [1, 2, 3]) - .timestamp() - .listen(expectAsync1((result) { - expect(result.value, expected[count++]); - }, count: expected.length)); - }); - - test('Rx.Rx.timestamp.reusable', () async { - final transformer = TimestampStreamTransformer(); - const expected = [1, 2, 3]; - var countA = 0, countB = 0; - - Stream.fromIterable(const [1, 2, 3]) - .transform(transformer) - .listen(expectAsync1((result) { - expect(result.value, expected[countA++]); - }, count: expected.length)); - - Stream.fromIterable(const [1, 2, 3]) - .transform(transformer) - .listen(expectAsync1((result) { - expect(result.value, expected[countB++]); - }, count: expected.length)); - }); - - test('timestampTransformer', () async { - const expected = [1, 2, 3]; - var count = 0; - - Stream.fromIterable(const [1, 2, 3]) - .transform(TimestampStreamTransformer()) - .listen(expectAsync1((result) { - expect(result.value, expected[count++]); - }, count: expected.length)); - }); - - test('timestampTransformer.asBroadcastStream', () async { - final stream = Stream.fromIterable(const [1, 2, 3]) - .transform(TimestampStreamTransformer()) - .asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(stream.isBroadcast, isTrue); - }); - - test('timestampTransformer.error.shouldThrow', () async { - final streamWithError = - Stream.error(Exception()).transform(TimestampStreamTransformer()); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('timestampTransformer.pause.resume', () async { - final stream = Stream.fromIterable(const [1, 2, 3]) - .transform(TimestampStreamTransformer()); - const expected = [1, 2, 3]; - late StreamSubscription> subscription; - var count = 0; - - subscription = stream.listen(expectAsync1((result) { - expect(result.value, expected[count++]); - - if (count == expected.length) { - subscription.cancel(); - } - }, count: expected.length)); - - subscription.pause(); - subscription.resume(); - }); - test('Rx.timestamp accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.timestamp(); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.timestamp.nullable', () { - nullableTest>( - (s) => s.timestamp(), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/where_not_null_test.dart b/sandbox/reactivex/test/transformers/where_not_null_test.dart deleted file mode 100644 index fd9c77e..0000000 --- a/sandbox/reactivex/test/transformers/where_not_null_test.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -void main() { - test('Rx.whereNotNull', () { - { - final notNull = Stream.fromIterable([1, 2, 3, 4]).whereNotNull(); - - expect(notNull, isA>()); - expect(notNull, emitsInOrder([1, 2, 3, 4])); - } - - { - final notNull = Stream.fromIterable([1, 2, null, 3, 4, null]) - .transform(WhereNotNullStreamTransformer()); - - expect(notNull, isA>()); - expect(notNull, emitsInOrder([1, 2, 3, 4])); - } - }); - - test('Rx.whereNotNull.shouldThrow', () { - expect( - Stream.error(Exception()).whereNotNull(), - emitsError(isA()), - ); - - expect( - Rx.concat([ - Stream.fromIterable([1, 2, null]), - Stream.error(Exception()), - Stream.value(3), - ]).whereNotNull(), - emitsInOrder([ - 1, - 2, - emitsError(isException), - 3, - emitsDone, - ]), - ); - }); - - test('Rx.whereNotNull.asBroadcastStream', () { - final stream = - Stream.fromIterable([1, 2, null]).whereNotNull().asBroadcastStream(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - // code should reach here - expect(true, true); - }); - - test('Rx.whereNotNull.singleSubscription', () { - final stream = StreamController().stream.whereNotNull(); - - expect(stream.isBroadcast, isFalse); - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - }); - - test('Rx.whereNotNull.pause.resume', () async { - final subscription = Stream.fromIterable([null, 2, 3, null, 4, 5, 6]) - .whereNotNull() - .listen(null); - - subscription - ..pause() - ..onData(expectAsync1((data) { - expect(data, 2); - subscription.cancel(); - })) - ..resume(); - }); - - test('Rx.whereNotNull.nullable', () { - nullableTest( - (s) => s.whereNotNull(), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/where_type_test.dart b/sandbox/reactivex/test/transformers/where_type_test.dart deleted file mode 100644 index 2eb0158..0000000 --- a/sandbox/reactivex/test/transformers/where_type_test.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -Stream _getStream() { - final controller = StreamController(); - - Timer(const Duration(milliseconds: 100), () => controller.add(1)); - Timer(const Duration(milliseconds: 200), () => controller.add('2')); - Timer( - const Duration(milliseconds: 300), () => controller.add(const {'3': 3})); - Timer(const Duration(milliseconds: 400), () { - controller.add(const {'4': '4'}); - }); - Timer(const Duration(milliseconds: 500), () { - controller.add(5.0); - controller.close(); - }); - - return controller.stream; -} - -void main() { - test('Rx.whereType', () async { - _getStream().whereType>().listen(expectAsync1((result) { - expect(result, isMap); - }, count: 1)); - }); - - test('Rx.whereType.polymorphism', () async { - _getStream().whereType().listen(expectAsync1((Object result) { - expect(result is num, true); - }, count: 2)); - }); - - test('Rx.whereType.null.values', () async { - await expectLater( - Stream.fromIterable([null, 1, null, 'two', 3]).whereType(), - emitsInOrder(const ['two'])); - }); - - test('Rx.whereType.asBroadcastStream', () async { - final stream = _getStream().asBroadcastStream().whereType(); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - // code should reach here - await expectLater(true, true); - }); - - test('Rx.whereType.error.shouldThrow', () async { - final streamWithError = Stream.error(Exception()).whereType(); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.whereType.pause.resume', () async { - late StreamSubscription subscription; - final stream = Stream.value(1).whereType(); - - subscription = stream.listen(expectAsync1((value) { - expect(value, 1); - - subscription.cancel(); - }, count: 1)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.whereType accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream.whereType(); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.whereType.nullable', () { - nullableTest( - (s) => s.whereType(), - ); - }); -} diff --git a/sandbox/reactivex/test/transformers/with_latest_from_test.dart b/sandbox/reactivex/test/transformers/with_latest_from_test.dart deleted file mode 100644 index 5191440..0000000 --- a/sandbox/reactivex/test/transformers/with_latest_from_test.dart +++ /dev/null @@ -1,541 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -import '../utils.dart'; - -/// creates 5 Streams, deferred from a source Stream, so that they all emit -/// under the same Timer interval. -/// before, tests could fail, since we created 5 separate Streams with each -/// using their own Timer. -List> _createTestStreams() { - /// creates streams that emit after a certain amount of milliseconds, - /// the List of intervals (in ms) - const intervals = [22, 50, 30, 40, 60]; - final ticker = - Stream.periodic(const Duration(milliseconds: 1), (index) => index) - .skip(1) - .take(300) - .asBroadcastStream(); - - return [ - ticker - .where((index) => index % intervals[0] == 0) - .map((index) => index ~/ intervals[0] - 1), - ticker - .where((index) => index % intervals[1] == 0) - .map((index) => index ~/ intervals[1] - 1), - ticker - .where((index) => index % intervals[2] == 0) - .map((index) => index ~/ intervals[2] - 1), - ticker - .where((index) => index % intervals[3] == 0) - .map((index) => index ~/ intervals[3] - 1), - ticker - .where((index) => index % intervals[4] == 0) - .map((index) => index ~/ intervals[4] - 1) - ]; -} - -void main() { - test('Rx.withLatestFrom', () async { - const expectedOutput = [ - Pair(2, 0), - Pair(3, 0), - Pair(4, 1), - Pair(5, 1), - Pair(6, 2) - ]; - final streams = _createTestStreams(); - - await expectLater( - streams.first - .withLatestFrom( - streams[1], (first, int second) => Pair(first, second)) - .take(5), - emitsInOrder(expectedOutput)); - }); - - test('Rx.withLatestFrom.iterate.once', () async { - var iterationCount = 0; - - final combined = Stream.value(1).withLatestFromList(() sync* { - ++iterationCount; - yield Stream.value(2); - yield Stream.value(3); - }()); - - await expectLater( - combined, - emitsInOrder([ - [1, 2, 3], - emitsDone, - ]), - ); - expect(iterationCount, 1); - }); - - test('Rx.withLatestFrom.reusable', () async { - final streams = _createTestStreams(); - final transformer = WithLatestFromStreamTransformer.with1( - streams[1], (first, second) => Pair(first, second)); - const expectedOutput = [ - Pair(2, 0), - Pair(3, 0), - Pair(4, 1), - Pair(5, 1), - Pair(6, 2) - ]; - var countA = 0, countB = 0; - - streams.first.transform(transformer).take(5).listen(expectAsync1((result) { - expect(result, expectedOutput[countA++]); - }, count: expectedOutput.length)); - - streams.first.transform(transformer).take(5).listen(expectAsync1((result) { - expect(result, expectedOutput[countB++]); - }, count: expectedOutput.length)); - }); - - test('Rx.withLatestFrom.asBroadcastStream', () async { - final streams = _createTestStreams(); - final stream = - streams.first.withLatestFrom(streams[1], (first, int second) => 0); - - // listen twice on same stream - stream.listen(null); - stream.listen(null); - - await expectLater(true, true); - }); - - test('Rx.withLatestFrom.error.shouldThrowA', () async { - final streams = _createTestStreams(); - final streamWithError = Stream.error(Exception()) - .withLatestFrom(streams[1], (first, int second) => 'Hello'); - - streamWithError.listen(null, - onError: expectAsync2((Exception e, StackTrace s) { - expect(e, isException); - })); - }); - - test('Rx.withLatestFrom.error.shouldThrowB', () async { - final streams = _createTestStreams(); - final stream = streams[1].take(1).withLatestFrom( - Stream.value(0), (first, int second) => throw Exception()); - - expect( - stream, - emitsInOrder([ - emitsError(isException), - emitsDone, - ])); - }); - - test('Rx.withLatestFrom.pause.resume', () async { - late StreamSubscription subscription; - const expectedOutput = [Pair(2, 0)]; - final streams = _createTestStreams(); - var count = 0; - - subscription = streams.first - .withLatestFrom(streams[1], (first, int second) => Pair(first, second)) - .take(1) - .listen(expectAsync1((result) { - expect(result, expectedOutput[count++]); - - if (count == expectedOutput.length) { - subscription.cancel(); - } - }, count: expectedOutput.length)); - - subscription.pause(); - subscription.resume(); - }); - - test('Rx.withLatestFrom.otherEmitsNull', () async { - const expected = Pair(1, null); - final stream = Rx.timer( - 1, - const Duration(microseconds: 100), - ).withLatestFrom( - Stream.value(null), - (a, int? b) => Pair(a, b), - ); - - await expectLater( - stream, - emits(expected), - ); - }); - - test('Rx.withLatestFrom.otherNotEmit', () async { - final stream = Rx.timer( - 1, - const Duration(microseconds: 100), - ).withLatestFrom( - Stream.empty(), - (a, int b) => Pair(a, b), - ); - - await expectLater( - stream, - emitsDone, - ); - }); - - test('Rx.withLatestFrom2', () async { - const expectedOutput = [ - _Tuple(2, 0, 1), - _Tuple(3, 0, 1), - _Tuple(4, 1, 2), - _Tuple(5, 1, 3), - _Tuple(6, 2, 4), - ]; - final streams = _createTestStreams(); - var count = 0; - - streams.first - .withLatestFrom2( - streams[1], - streams[2], - (item1, int item2, int item3) => _Tuple(item1, item2, item3), - ) - .take(5) - .listen( - expectAsync1( - (result) => expect(result, expectedOutput[count++]), - count: expectedOutput.length, - ), - ); - }); - - test('Rx.withLatestFrom3', () async { - const expectedOutput = [ - _Tuple(2, 0, 1, 0), - _Tuple(3, 0, 1, 1), - _Tuple(4, 1, 2, 1), - _Tuple(5, 1, 3, 2), - _Tuple(6, 2, 4, 2), - ]; - final streams = _createTestStreams(); - var count = 0; - - streams.first - .withLatestFrom3( - streams[1], - streams[2], - streams[3], - (item1, int item2, int item3, int item4) => - _Tuple(item1, item2, item3, item4), - ) - .take(5) - .listen( - expectAsync1( - (result) => expect(result, expectedOutput[count++]), - count: expectedOutput.length, - ), - ); - }); - - test('Rx.withLatestFrom4', () async { - const expectedOutput = [ - _Tuple(2, 0, 1, 0, 0), - _Tuple(3, 0, 1, 1, 0), - _Tuple(4, 1, 2, 1, 0), - _Tuple(5, 1, 3, 2, 1), - _Tuple(6, 2, 4, 2, 1), - ]; - final streams = _createTestStreams(); - var count = 0; - - streams.first - .withLatestFrom4( - streams[1], - streams[2], - streams[3], - streams[4], - (item1, int item2, int item3, int item4, int item5) => - _Tuple(item1, item2, item3, item4, item5), - ) - .take(5) - .listen( - expectAsync1( - (result) => expect(result, expectedOutput[count++]), - count: expectedOutput.length, - ), - ); - }); - - test('Rx.withLatestFrom5', () async { - final stream = Rx.timer( - 1, - const Duration(microseconds: 100), - ).withLatestFrom5( - Stream.value(2), - Stream.value(3), - Stream.value(4), - Stream.value(5), - Stream.value(6), - (a, int b, int c, int d, int e, int f) => _Tuple(a, b, c, d, e, f), - ); - const expected = _Tuple(1, 2, 3, 4, 5, 6); - - await expectLater( - stream, - emits(expected), - ); - }); - - test('Rx.withLatestFrom6', () async { - final stream = Rx.timer( - 1, - const Duration(microseconds: 100), - ).withLatestFrom6( - Stream.value(2), - Stream.value(3), - Stream.value(4), - Stream.value(5), - Stream.value(6), - Stream.value(7), - (a, int b, int c, int d, int e, int f, int g) => - _Tuple(a, b, c, d, e, f, g), - ); - const expected = _Tuple(1, 2, 3, 4, 5, 6, 7); - - await expectLater( - stream, - emits(expected), - ); - }); - - test('Rx.withLatestFrom7', () async { - final stream = Rx.timer( - 1, - const Duration(microseconds: 100), - ).withLatestFrom7( - Stream.value(2), - Stream.value(3), - Stream.value(4), - Stream.value(5), - Stream.value(6), - Stream.value(7), - Stream.value(8), - (a, int b, int c, int d, int e, int f, int g, int h) => - _Tuple(a, b, c, d, e, f, g, h), - ); - const expected = _Tuple(1, 2, 3, 4, 5, 6, 7, 8); - - await expectLater( - stream, - emits(expected), - ); - }); - - test('Rx.withLatestFrom8', () async { - final stream = Rx.timer( - 1, - const Duration(microseconds: 100), - ).withLatestFrom8( - Stream.value(2), - Stream.value(3), - Stream.value(4), - Stream.value(5), - Stream.value(6), - Stream.value(7), - Stream.value(8), - Stream.value(9), - (a, int b, int c, int d, int e, int f, int g, int h, int i) => - _Tuple(a, b, c, d, e, f, g, h, i), - ); - const expected = _Tuple(1, 2, 3, 4, 5, 6, 7, 8, 9); - - await expectLater( - stream, - emits(expected), - ); - }); - - test('Rx.withLatestFrom9', () async { - final stream = Rx.timer( - 1, - const Duration(microseconds: 100), - ).withLatestFrom9( - Stream.value(2), - Stream.value(3), - Stream.value(4), - Stream.value(5), - Stream.value(6), - Stream.value(7), - Stream.value(8), - Stream.value(9), - Stream.value(10), - (a, int b, int c, int d, int e, int f, int g, int h, int i, int j) => - _Tuple(a, b, c, d, e, f, g, h, i, j), - ); - const expected = _Tuple(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - - await expectLater( - stream, - emits(expected), - ); - }); - - test('Rx.withLatestFromList', () async { - final stream = Rx.timer( - 1, - const Duration(microseconds: 100), - ).withLatestFromList( - [ - Stream.value(2), - Stream.value(3), - Stream.value(4), - Stream.value(5), - Stream.value(6), - Stream.value(7), - Stream.value(8), - Stream.value(9), - Stream.value(10), - ], - ); - const expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - - await expectLater( - stream, - emits(expected), - ); - }); - - test('Rx.withLatestFromList.emptyList', () async { - final stream = Stream.fromIterable([1, 2, 3]).withLatestFromList([]); - - await expectLater( - stream, - emitsInOrder( - >[ - [1], - [2], - [3], - ], - ), - ); - }); - test('Rx.withLatestFrom accidental broadcast', () async { - final controller = StreamController(); - - final stream = controller.stream - .withLatestFrom(Stream.empty(), (_, dynamic __) => true); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.withLatestFrom.nullable', () { - nullableTest>( - (s) => s.withLatestFromList([Stream.value('String')]), - ); - }); -} - -class Pair { - final int? first; - final int? second; - - const Pair(this.first, this.second); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - return other is Pair && first == other.first && second == other.second; - } - - @override - int get hashCode { - return first.hashCode ^ second.hashCode; - } - - @override - String toString() { - return 'Pair{first: $first, second: $second}'; - } -} - -class _Tuple { - final int? item1; - final int? item2; - final int? item3; - final int? item4; - final int? item5; - final int? item6; - final int? item7; - final int? item8; - final int? item9; - final int? item10; - - const _Tuple([ - this.item1, - this.item2, - this.item3, - this.item4, - this.item5, - this.item6, - this.item7, - this.item8, - this.item9, - this.item10, - ]); - - @override - bool operator ==(Object other) { - return identical(this, other) || - other is _Tuple && - item1 == other.item1 && - item2 == other.item2 && - item3 == other.item3 && - item4 == other.item4 && - item5 == other.item5 && - item6 == other.item6 && - item7 == other.item7 && - item8 == other.item8 && - item9 == other.item9 && - item10 == other.item10; - } - - @override - int get hashCode { - return item1.hashCode ^ - item2.hashCode ^ - item3.hashCode ^ - item4.hashCode ^ - item5.hashCode ^ - item6.hashCode ^ - item7.hashCode ^ - item8.hashCode ^ - item9.hashCode ^ - item10.hashCode; - } - - @override - String toString() { - final values = [ - item1, - item2, - item3, - item4, - item5, - item6, - item7, - item8, - item9, - item10, - ]; - final s = values.join(', '); - return 'Tuple { $s }'; - } -} diff --git a/sandbox/reactivex/test/transformers/zip_with_test.dart b/sandbox/reactivex/test/transformers/zip_with_test.dart deleted file mode 100644 index 36839c9..0000000 --- a/sandbox/reactivex/test/transformers/zip_with_test.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - test('Rx.zipWith', () async { - Stream.value(1) - .zipWith(Stream.value(2), (int one, int two) => one + two) - .listen(expectAsync1((int result) { - expect(result, 3); - }, count: 1)); - }); - - test('Rx.zipWith accidental broadcast', () async { - final controller = StreamController(); - - final stream = - controller.stream.zipWith(Stream.empty(), (_, dynamic __) => true); - - stream.listen(null); - expect(() => stream.listen(null), throwsStateError); - - controller.add(1); - }); - - test('Rx.zipWith on single stream should stay single ', () async { - final delayedStream = Rx.timer(1, Duration(milliseconds: 10)); - final immediateStream = Stream.value(2); - final expected = [3, emitsDone]; - - final concatenatedStream = - delayedStream.zipWith(immediateStream, (a, int b) => a + b); - - expect(concatenatedStream.isBroadcast, isFalse); - expect(concatenatedStream, emitsInOrder(expected)); - }); - - test('Rx.zipWith on broadcast stream should stay broadcast ', () async { - final delayedStream = - Rx.timer(1, Duration(milliseconds: 10)).asBroadcastStream(); - final immediateStream = Stream.value(2); - final expected = [3, emitsDone]; - - final concatenatedStream = - delayedStream.zipWith(immediateStream, (a, int b) => a + b); - - expect(concatenatedStream.isBroadcast, isTrue); - expect(concatenatedStream, emitsInOrder(expected)); - }); - - test('Rx.zipWith multiple subscriptions on single ', () async { - final delayedStream = Rx.timer(1, Duration(milliseconds: 10)); - final immediateStream = Stream.value(2); - - final concatenatedStream = - delayedStream.zipWith(immediateStream, (a, int b) => a + b); - - expect(() => concatenatedStream.listen(null), returnsNormally); - expect(() => concatenatedStream.listen(null), - throwsA(TypeMatcher())); - }); -} diff --git a/sandbox/reactivex/test/utils.dart b/sandbox/reactivex/test/utils.dart deleted file mode 100644 index 2a8f4fd..0000000 --- a/sandbox/reactivex/test/utils.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:async'; - -/// Explicitly ignores a future. -/// -/// Not all futures need to be awaited. -/// The Dart linter has an optional ["unawaited futures" lint](https://dart-lang.github.io/linter/lints/unawaited_futures.html) -/// which enforces that futures (expressions with a static type of [Future]) -/// in asynchronous functions are handled *somehow*. -/// If a particular future value doesn't need to be awaited, -/// you can call `unawaited(...)` with it, which will avoid the lint, -/// simply because the expression no longer has type [Future]. -/// Using `unawaited` has no other effect. -/// You should use `unawaited` to convey the *intention* of -/// deliberately not waiting for the future. -/// -/// If the future completes with an error, -/// it was likely a mistake to not await it. -/// That error will still occur and will be considered unhandled -/// unless the same future is awaited (or otherwise handled) elsewhere too. -/// Because of that, `unawaited` should only be used for futures that -/// are *expected* to complete with a value. -void unawaited(Future future) {} - -void nullableTest(Stream Function(Stream s) transform) => - transform(Stream.fromIterable(['1', '2', '3'])); diff --git a/sandbox/reactivex/test/utils/composite_subscription_test.dart b/sandbox/reactivex/test/utils/composite_subscription_test.dart deleted file mode 100644 index 66a01e3..0000000 --- a/sandbox/reactivex/test/utils/composite_subscription_test.dart +++ /dev/null @@ -1,316 +0,0 @@ -import 'dart:async'; - -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - group('CompositeSubscription', () { - test('cast to StreamSubscription of any type', () { - final cs = CompositeSubscription(); - - expect(cs, isA>()); - // ignore: prefer_void_to_null - expect(cs, isA>()); - expect(cs, isA>()); - expect(cs, isA>()); - expect(cs, isA>()); - expect(cs, isA>()); - - cs as StreamSubscription; // ignore: unnecessary_cast - // ignore: unnecessary_cast, prefer_void_to_null - cs as StreamSubscription; - cs as StreamSubscription; // ignore: unnecessary_cast - cs as StreamSubscription; // ignore: unnecessary_cast - cs as StreamSubscription; // ignore: unnecessary_cast - cs as StreamSubscription; // ignore: unnecessary_cast - - expect(true, true); - }); - - group('throws UnsupportedError', () { - test('when calling asFuture()', () { - expect( - () => CompositeSubscription().asFuture(0), throwsUnsupportedError); - }); - - test('when calling onData()', () { - expect(() => CompositeSubscription().onData((_) {}), - throwsUnsupportedError); - }); - - test('when calling onError()', () { - expect(() => CompositeSubscription().onError((Object _) {}), - throwsUnsupportedError); - }); - - test('when calling onDone()', () { - expect(() => CompositeSubscription().onDone(() {}), - throwsUnsupportedError); - }); - }); - - group('Rx.compositeSubscription.clear', () { - test('should cancel all subscriptions', () { - final stream = Stream.fromIterable(const [1, 2, 3]).shareValue(); - final composite = CompositeSubscription(); - - composite - ..add(stream.listen(null)) - ..add(stream.listen(null)) - ..add(stream.listen(null)); - - final done = composite.clear(); - - expect(stream, neverEmits(anything)); - expect(done, isA>()); - }); - - test( - 'should return null since no subscription has been canceled clear()', - () { - final composite = CompositeSubscription(); - final done = composite.clear(); - expect(done, null); - }, - ); - }); - - group('Rx.compositeSubscription.onDispose', () { - test('should cancel all subscriptions when calling dispose()', () { - final stream = Stream.fromIterable(const [1, 2, 3]).shareValue(); - final composite = CompositeSubscription(); - - composite - ..add(stream.listen(null)) - ..add(stream.listen(null)) - ..add(stream.listen(null)); - - final done = composite.dispose(); - - expect(stream, neverEmits(anything)); - expect(done, isA>()); - }); - - test('should cancel all subscriptions when calling cancel()', () { - final stream = Stream.fromIterable(const [1, 2, 3]).shareValue(); - final composite = CompositeSubscription(); - - composite - ..add(stream.listen(null)) - ..add(stream.listen(null)) - ..add(stream.listen(null)); - - final done = composite.cancel(); - - expect(stream, neverEmits(anything)); - expect(done, isA>()); - }); - - test( - 'should return null since no subscription has been canceled on dispose()', - () { - final composite = CompositeSubscription(); - final done = composite.dispose(); - expect(done, null); - }, - ); - - test( - 'should return Future completed with null since no subscription has been canceled on cancel()', - () { - final composite = CompositeSubscription(); - final done = composite.cancel(); - expect(done, completion(null)); - }, - ); - - test( - 'should throw exception if trying to add subscription to disposed composite, after calling dispose()', - () { - final stream = Stream.fromIterable(const [1, 2, 3]).shareValue(); - final composite = CompositeSubscription(); - - composite.dispose(); - - expect(() => composite.add(stream.listen(null)), throwsA(anything)); - }, - ); - - test( - 'should throw exception if trying to add subscription to disposed composite, after calling cancel()', - () { - final stream = Stream.fromIterable(const [1, 2, 3]).shareValue(); - final composite = CompositeSubscription(); - - composite.cancel(); - - expect(() => composite.add(stream.listen(null)), throwsA(anything)); - }, - ); - }); - - group('Rx.compositeSubscription.remove', () { - test('should cancel subscription on if it is removed from composite', () { - const value = 1; - final stream = Stream.fromIterable([value]).shareValue(); - final composite = CompositeSubscription(); - final subscription = stream.listen(null); - - composite.add(subscription); - final done = composite.remove(subscription); - - expect(stream, neverEmits(anything)); - expect(done, isA>()); - }); - - test( - 'should not cancel the subscription since it is not present in the composite', - () { - const value = 1; - final stream = Stream.fromIterable([value]).shareValue(); - final composite = CompositeSubscription(); - final subscription = stream.listen(null); - - final done = composite.remove(subscription); - - expect(stream, emits(anything)); - expect(done, null); - }, - ); - }); - - test('Rx.compositeSubscription.pauseAndResume()', () { - final composite = CompositeSubscription(); - final s1 = Stream.fromIterable(const [1, 2, 3]).listen(null), - s2 = Stream.fromIterable(const [4, 5, 6]).listen(null); - - composite.add(s1); - composite.add(s2); - - void expectPaused() { - expect(composite.allPaused, isTrue); - expect(composite.isPaused, isTrue); - - expect(s1.isPaused, isTrue); - expect(s2.isPaused, isTrue); - } - - void expectResumed() { - expect(composite.allPaused, isFalse); - expect(composite.isPaused, isFalse); - - expect(s1.isPaused, isFalse); - expect(s2.isPaused, isFalse); - } - - composite.pauseAll(); - - expectPaused(); - - composite.resumeAll(); - - expectResumed(); - - composite.pause(); - - expectPaused(); - - composite.resume(); - - expectResumed(); - }); - - test('Rx.compositeSubscription.resumeWithFuture', () async { - final composite = CompositeSubscription(); - final s1 = Stream.fromIterable(const [1, 2, 3]).listen(null), - s2 = Stream.fromIterable(const [4, 5, 6]).listen(null); - final completer = Completer(); - - composite.add(s1); - composite.add(s2); - composite.pauseAll(completer.future); - - expect(composite.allPaused, isTrue); - expect(composite.isPaused, isTrue); - - completer.complete(); - - await expectLater(completer.future.then((_) => composite.allPaused), - completion(isFalse)); - await expectLater(completer.future.then((_) => composite.isPaused), - completion(isFalse)); - }); - - test('Rx.compositeSubscription.allPaused', () { - final composite = CompositeSubscription(); - final s1 = Stream.fromIterable(const [1, 2, 3]).listen(null), - s2 = Stream.fromIterable(const [4, 5, 6]).listen(null); - - expect(composite.allPaused, isFalse); - expect(composite.isPaused, isFalse); - - composite.add(s1); - composite.add(s2); - - expect(composite.allPaused, isFalse); - expect(composite.isPaused, isFalse); - - composite.pauseAll(); - - expect(composite.allPaused, isTrue); - expect(composite.isPaused, isTrue); - - composite.remove(s1); - composite.remove(s2); - - /// all subscriptions are removed, allPaused should yield false - expect(composite.allPaused, isFalse); - expect(composite.isPaused, isFalse); - }); - - test('Rx.compositeSubscription.allPaused.indirectly', () { - final composite = CompositeSubscription(); - final s1 = Stream.fromIterable(const [1, 2, 3]).listen(null), - s2 = Stream.fromIterable(const [4, 5, 6]).listen(null); - - s1.pause(); - s2.pause(); - - composite.add(s1); - composite.add(s2); - - expect(composite.allPaused, isTrue); - expect(composite.isPaused, isTrue); - - s1.resume(); - s2.resume(); - - expect(composite.allPaused, isFalse); - expect(composite.isPaused, isFalse); - }); - - test('Rx.compositeSubscription.size', () { - final composite = CompositeSubscription(); - final s1 = Stream.fromIterable(const [1, 2, 3]).listen(null), - s2 = Stream.fromIterable(const [4, 5, 6]).listen(null); - - expect(composite.isEmpty, isTrue); - expect(composite.isNotEmpty, isFalse); - expect(composite.length, 0); - - composite.add(s1); - composite.add(s2); - - expect(composite.isEmpty, isFalse); - expect(composite.isNotEmpty, isTrue); - expect(composite.length, 2); - - composite.remove(s1); - composite.remove(s2); - - expect(composite.isEmpty, isTrue); - expect(composite.isNotEmpty, isFalse); - expect(composite.length, 0); - }); - }); -} diff --git a/sandbox/reactivex/test/utils/notification_test.dart b/sandbox/reactivex/test/utils/notification_test.dart deleted file mode 100644 index 45191f9..0000000 --- a/sandbox/reactivex/test/utils/notification_test.dart +++ /dev/null @@ -1,234 +0,0 @@ -import 'package:angel3_reactivex/angel3_reactivex.dart'; -import 'package:test/test.dart'; - -void main() { - group('StreamNotification', () { - test('hashCode', () { - final value1 = 1; - final value2 = 2; - - final st1 = StackTrace.current; - final st2 = StackTrace.current; - - expect( - StreamNotification.data(value1).hashCode, - StreamNotification.data(value1).hashCode, - ); - expect( - StreamNotification.data(value1).hashCode, - StreamNotification.data(value1).hashCode, - ); - expect( - StreamNotification.data(value1).hashCode, - isNot(StreamNotification.data(value2).hashCode), - ); - - expect( - StreamNotification.done().hashCode, - StreamNotification.done().hashCode, - ); - expect( - StreamNotification.done().hashCode, - StreamNotification.done().hashCode, - ); - - expect( - StreamNotification.error(value1, st1).hashCode, - StreamNotification.error(value1, st1).hashCode, - ); - expect( - StreamNotification.error(value1, st1).hashCode, - isNot(StreamNotification.error(value2, st1).hashCode), - ); - expect( - StreamNotification.error(value1, st1).hashCode, - isNot(StreamNotification.error(value1, st2).hashCode), - ); - expect( - StreamNotification.error(value1, st1).hashCode, - isNot(StreamNotification.error(value2, st2).hashCode), - ); - - expect( - StreamNotification.data(value1).hashCode, - isNot(StreamNotification.done().hashCode), - ); - expect( - StreamNotification.data(value1).hashCode, - isNot(StreamNotification.error(value1, st1).hashCode), - ); - expect( - StreamNotification.done().hashCode, - isNot(StreamNotification.error(value1, st1).hashCode), - ); - }); - - test('==', () { - final value1 = 1; - final value2 = 2; - - final st1 = StackTrace.current; - final st2 = StackTrace.current; - - expect( - StreamNotification.data(value1), - StreamNotification.data(value1), - ); - expect( - StreamNotification.data(value1), - isNot(StreamNotification.data(value1)), - ); - expect( - StreamNotification.data(value1), - isNot(StreamNotification.data(value2)), - ); - - expect( - StreamNotification.done(), - StreamNotification.done(), - ); - expect( - const StreamNotification.done(), - StreamNotification.done(), - ); - expect( - StreamNotification.done(), - StreamNotification.done(), - ); - - expect( - StreamNotification.error(value1, st1), - StreamNotification.error(value1, st1), - ); - expect( - StreamNotification.error(value1, st1), - isNot(StreamNotification.error(value2, st1)), - ); - expect( - StreamNotification.error(value1, st1), - isNot(StreamNotification.error(value1, st2)), - ); - expect( - StreamNotification.error(value1, st1), - isNot(StreamNotification.error(value2, st2)), - ); - - expect( - StreamNotification.data(value1), - isNot(StreamNotification.done()), - ); - expect( - StreamNotification.data(value1), - isNot(StreamNotification.error(value1, st1)), - ); - expect( - StreamNotification.done(), - isNot(StreamNotification.error(value1, st1)), - ); - }); - - test('toString', () { - expect( - StreamNotification.data(1).toString(), - 'DataNotification{value: 1}', - ); - - expect( - StreamNotification.done().toString(), - 'DoneNotification{}', - ); - - expect( - StreamNotification.error(2, StackTrace.empty).toString(), - 'ErrorNotification{error: 2, stackTrace: }', - ); - }); - - test('requireData', () { - expect( - StreamNotification.data(1).requireDataValue, - 1, - ); - - expect( - () => StreamNotification.done().requireDataValue, - throwsA(isA()), - ); - - expect( - () => - StreamNotification.error(2, StackTrace.empty).requireDataValue, - throwsA(isA()), - ); - }); - - test('errorAndStackTraceOrNull', () { - expect( - StreamNotification.data(1).errorAndStackTraceOrNull, - isNull, - ); - - expect( - StreamNotification.done().errorAndStackTraceOrNull, - isNull, - ); - - expect( - StreamNotification.error(2, StackTrace.empty) - .errorAndStackTraceOrNull, - ErrorAndStackTrace(2, StackTrace.empty), - ); - }); - - test('isOnData', () { - expect( - StreamNotification.data(1).isData, - isTrue, - ); - - expect( - StreamNotification.done().isData, - isFalse, - ); - - expect( - StreamNotification.error(2, StackTrace.empty).isData, - isFalse, - ); - }); - - test('isOnDone', () { - expect( - StreamNotification.data(1).isDone, - isFalse, - ); - - expect( - StreamNotification.done().isDone, - isTrue, - ); - - expect( - StreamNotification.error(2, StackTrace.empty).isDone, - isFalse, - ); - }); - - test('isOnError', () { - expect( - StreamNotification.data(1).isError, - isFalse, - ); - - expect( - StreamNotification.done().isError, - isFalse, - ); - - expect( - StreamNotification.error(2, StackTrace.empty).isError, - isTrue, - ); - }); - }); -}