diff --git a/doc/directory_feeds b/doc/directory_feeds index 62d62e3..4b6b6fb 100644 --- a/doc/directory_feeds +++ b/doc/directory_feeds @@ -2,4 +2,12 @@ mirrors.dart /home/platform/Devboxes/sdk/sdk/lib/mirrors mirrors.h mirrors.cc /home/platform/Devboxes/sdk/runtime/lib -/home/platform/Devboxes/fake_reflection \ No newline at end of file +/home/platform/Devboxes/fake_reflection + + + +Abstract language agnostic specification in YAML +/home/platform/Devboxes/api/laravel + +PHP reference implementation +/home/platform/Devboxes/resources/laravel_framework/src/Illuminate \ No newline at end of file diff --git a/packages/contracts/lib/contracts.dart b/packages/contracts/lib/contracts.dart index 473fb3f..02e2ff5 100644 --- a/packages/contracts/lib/contracts.dart +++ b/packages/contracts/lib/contracts.dart @@ -1,71 +1,228 @@ -/// Platform Contracts Library +/// Support 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. +/// This library provides a set of interfaces that define the core contracts +/// used throughout the framework. These contracts establish a standard way +/// of implementing common functionality like array conversion, JSON serialization, +/// HTML rendering, and HTTP response handling. +library contracts; -// Level 0: Core Foundation Contracts +// Auth contracts +export 'src/auth/auth_factory.dart'; +export 'src/auth/authenticatable.dart'; +export 'src/auth/can_reset_password.dart'; +export 'src/auth/guard.dart'; +export 'src/auth/must_verify_email.dart'; +export 'src/auth/password_broker.dart'; +export 'src/auth/password_broker_factory.dart'; +export 'src/auth/stateful_guard.dart'; +export 'src/auth/supports_basic_auth.dart'; +export 'src/auth/user_provider.dart'; -// Container contracts (from packages/container) +// Auth Access contracts +export 'src/auth/access/authorizable.dart'; +export 'src/auth/access/authorization_exception.dart'; +export 'src/auth/access/gate.dart'; + +// Auth Middleware contracts +export 'src/auth/middleware/authenticates_requests.dart'; + +// Broadcasting contracts +export 'src/broadcasting/broadcast_exception.dart'; +export 'src/broadcasting/broadcast_factory.dart'; +export 'src/broadcasting/broadcaster.dart'; +export 'src/broadcasting/has_broadcast_channel.dart'; +export 'src/broadcasting/should_be_unique.dart' show BroadcastShouldBeUnique; +export 'src/broadcasting/should_broadcast.dart'; +export 'src/broadcasting/should_broadcast_now.dart'; + +// Bus contracts +export 'src/bus/dispatcher.dart' show BusDispatcher; +export 'src/bus/queueing_dispatcher.dart'; + +// Cache contracts +export 'src/cache/cache_factory.dart'; +export 'src/cache/cache_interface.dart'; +export 'src/cache/lock.dart'; +export 'src/cache/lock_provider.dart'; +export 'src/cache/lock_timeout_exception.dart' show CacheLockTimeoutException; +export 'src/cache/repository.dart'; +export 'src/cache/store.dart'; + +// Config contracts +export 'src/config/repository.dart'; + +// Console contracts +export 'src/console/application.dart'; +export 'src/console/isolatable.dart'; +export 'src/console/kernel.dart'; +export 'src/console/prompts_for_missing_input.dart'; + +// Container contracts +export 'src/container/binding_resolution_exception.dart'; +export 'src/container/circular_dependency_exception.dart'; export 'src/container/container.dart'; +export 'src/container/container_contract.dart'; +export 'src/container/container_interface.dart'; +export 'src/container/contextual_binding_contract.dart'; -// Reflection contracts (from packages/container) -export 'src/reflection/reflection.dart'; +// Cookie contracts +export 'src/cookie/cookie_factory.dart' show CookieFactory; +export 'src/cookie/queueing_factory.dart'; -// Pipeline contracts (from packages/pipeline) +// Database contracts +export 'src/database/model_identifier.dart'; +export 'src/database/query/builder.dart'; +export 'src/database/query/expression.dart'; +export 'src/database/query/condition_expression.dart'; + +// Database Eloquent contracts +export 'src/database/eloquent/builder.dart'; +export 'src/database/eloquent/castable.dart'; +export 'src/database/eloquent/casts_attributes.dart'; +export 'src/database/eloquent/casts_inbound_attributes.dart'; +export 'src/database/eloquent/deviates_castable_attributes.dart'; +export 'src/database/eloquent/serializes_castable_attributes.dart'; +export 'src/database/eloquent/supports_partial_relations.dart'; + +// Database Events contracts +export 'src/database/events/migration_event.dart'; + +// Debug contracts +export 'src/debug/exception_handler.dart'; + +// Encryption contracts +export 'src/encryption/decrypt_exception.dart'; +export 'src/encryption/encrypt_exception.dart'; +export 'src/encryption/encrypter.dart'; +export 'src/encryption/string_encrypter.dart'; + +// Events contracts +export 'src/events/dispatcher.dart' show EventDispatcher; +export 'src/events/should_dispatch_after_commit.dart'; +export 'src/events/should_handle_events_after_commit.dart'; + +// Filesystem contracts +export 'src/filesystem/cloud.dart'; +export 'src/filesystem/file_not_found_exception.dart'; +export 'src/filesystem/filesystem_factory.dart' show FilesystemFactory; +export 'src/filesystem/filesystem.dart'; +export 'src/filesystem/lock_timeout_exception.dart' + show FilesystemLockTimeoutException; + +// Foundation contracts +export 'src/foundation/application.dart'; +export 'src/foundation/caches_configuration.dart'; +export 'src/foundation/caches_routes.dart'; +export 'src/foundation/exception_renderer.dart'; +export 'src/foundation/maintenance_mode.dart'; + +// Hashing contracts +export 'src/hashing/hasher.dart'; + +// HTTP contracts +export 'src/http/kernel.dart'; +export 'src/http/request.dart'; +export 'src/http/response.dart'; + +// Mail contracts +export 'src/mail/attachable.dart'; +export 'src/mail/mail_factory.dart' show MailFactory; +export 'src/mail/mailable.dart'; +export 'src/mail/mail_queue.dart'; +export 'src/mail/mailer.dart'; + +// Notifications contracts +export 'src/notifications/dispatcher.dart' show NotificationDispatcher; +export 'src/notifications/factory.dart'; + +// Pagination contracts +export 'src/pagination/cursor_paginator.dart'; +export 'src/pagination/length_aware_paginator.dart'; +export 'src/pagination/paginator.dart'; + +// Pipeline contracts +export 'src/pipeline/hub.dart'; export 'src/pipeline/pipeline.dart'; -// Level 1: Infrastructure Contracts +// Process contracts +export 'src/process/invoked_process.dart'; +export 'src/process/process_result.dart'; -// 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) +// Queue contracts +export 'src/queue/clearable_queue.dart'; +export 'src/queue/entity_not_found_exception.dart'; +export 'src/queue/entity_resolver.dart'; +export 'src/queue/queue_factory.dart' show QueueFactory; +export 'src/queue/job.dart'; +export 'src/queue/monitor.dart'; export 'src/queue/queue.dart'; +export 'src/queue/queueable_collection.dart'; +export 'src/queue/queueable_entity.dart'; +export 'src/queue/should_be_encrypted.dart'; +export 'src/queue/should_be_unique.dart' show QueueShouldBeUnique; +export 'src/queue/should_be_unique_until_processing.dart'; +export 'src/queue/should_queue.dart'; +export 'src/queue/should_queue_after_commit.dart'; -// Level 3: HTTP Layer Contracts +// Redis contracts +export 'src/redis/connection.dart'; +export 'src/redis/connector.dart'; +export 'src/redis/redis_factory.dart' show RedisFactory; +export 'src/redis/limiter_timeout_exception.dart'; -// Routing contracts (from packages/route) -export 'src/routing/routing.dart'; +// Reflection contracts +export 'src/reflection/reflected_class.dart'; +export 'src/reflection/reflected_function.dart'; +export 'src/reflection/reflected_instance.dart'; +export 'src/reflection/reflected_parameter.dart'; +export 'src/reflection/reflected_type.dart'; +export 'src/reflection/reflection.dart'; +export 'src/reflection/reflector_contract.dart'; +export 'src/reflection/reflector.dart'; -// HTTP contracts (from packages/core) -export 'src/http/http.dart'; +// Routing contracts +export 'src/routing/binding_registrar.dart'; +export 'src/routing/registrar.dart'; +export 'src/routing/response_factory.dart'; +export 'src/routing/url_generator.dart'; +export 'src/routing/url_routable.dart'; -// Testing Contracts +// Session contracts +export 'src/session/session.dart'; +export 'src/session/middleware/authenticates_sessions.dart'; -// Testing contracts (from packages/testing) -export 'src/testing/testing.dart'; +// Support contracts +export 'src/support/arrayable.dart'; +export 'src/support/can_be_escaped_when_cast_to_string.dart'; +export 'src/support/deferrable_provider.dart'; +export 'src/support/deferring_displayable_value.dart'; +export 'src/support/htmlable.dart'; +export 'src/support/jsonable.dart'; +export 'src/support/message_bag.dart'; +export 'src/support/message_provider.dart'; +export 'src/support/renderable.dart'; +export 'src/support/responsable.dart'; +export 'src/support/validated_data.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 +// Translation contracts +export 'src/translation/has_locale_preference.dart'; +export 'src/translation/loader.dart'; +export 'src/translation/translator.dart'; -// 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 +// Validation contracts +export 'src/validation/data_aware_rule.dart'; +export 'src/validation/validation_factory.dart' show ValidationFactory; +export 'src/validation/implicit_rule.dart'; +export 'src/validation/invokable_rule.dart'; +export 'src/validation/rule.dart'; +export 'src/validation/uncompromised_verifier.dart'; +export 'src/validation/validates_when_resolved.dart'; +export 'src/validation/validation_rule.dart'; +export 'src/validation/validator.dart'; +export 'src/validation/validator_aware_rule.dart'; + +// View contracts +export 'src/view/engine.dart'; +export 'src/view/view_factory.dart' show ViewFactory; +export 'src/view/view.dart'; +export 'src/view/view_compilation_exception.dart'; diff --git a/packages/contracts/lib/src/auth/access/authorizable.dart b/packages/contracts/lib/src/auth/access/authorizable.dart new file mode 100644 index 0000000..781762d --- /dev/null +++ b/packages/contracts/lib/src/auth/access/authorizable.dart @@ -0,0 +1,40 @@ +/// Interface for authorization capabilities. +/// +/// This contract defines how an entity can be checked for specific abilities +/// or permissions. It's typically implemented by user models to provide +/// authorization functionality. +abstract class Authorizable { + /// Determine if the entity has a given ability. + /// + /// Example: + /// ```dart + /// class User implements Authorizable { + /// @override + /// Future can(dynamic abilities, [dynamic arguments = const []]) async { + /// if (abilities is String) { + /// // Check single ability + /// return await checkAbility(abilities, arguments); + /// } else if (abilities is Iterable) { + /// // Check multiple abilities + /// for (var ability in abilities) { + /// if (!await checkAbility(ability, arguments)) { + /// return false; + /// } + /// } + /// return true; + /// } + /// return false; + /// } + /// } + /// + /// // Usage + /// if (await user.can('edit-post', post)) { + /// // User can edit the post + /// } + /// + /// if (await user.can(['edit-post', 'delete-post'], post)) { + /// // User can both edit and delete the post + /// } + /// ``` + Future can(dynamic abilities, [dynamic arguments = const []]); +} diff --git a/packages/contracts/lib/src/auth/access/authorization_exception.dart b/packages/contracts/lib/src/auth/access/authorization_exception.dart new file mode 100644 index 0000000..f75614f --- /dev/null +++ b/packages/contracts/lib/src/auth/access/authorization_exception.dart @@ -0,0 +1,26 @@ +/// Exception thrown when authorization fails. +/// +/// This exception is thrown when an authorization check fails, typically +/// when a user attempts to perform an action they are not authorized to do. +class AuthorizationException implements Exception { + /// The message describing why authorization failed. + final String message; + + /// The code associated with the authorization failure. + final String? code; + + /// Create a new authorization exception. + /// + /// Example: + /// ```dart + /// throw AuthorizationException( + /// 'User is not authorized to edit posts', + /// code: 'posts.edit.unauthorized', + /// ); + /// ``` + const AuthorizationException(this.message, {this.code}); + + @override + String toString() => + 'AuthorizationException: $message${code != null ? ' (code: $code)' : ''}'; +} diff --git a/packages/contracts/lib/src/auth/access/gate.dart b/packages/contracts/lib/src/auth/access/gate.dart new file mode 100644 index 0000000..87a734e --- /dev/null +++ b/packages/contracts/lib/src/auth/access/gate.dart @@ -0,0 +1,161 @@ +/// Interface for authorization management. +/// +/// This contract defines how authorization rules and policies should be managed +/// and checked. It provides methods for defining abilities, registering policies, +/// and performing authorization checks. +abstract class Gate { + /// Determine if a given ability has been defined. + /// + /// Example: + /// ```dart + /// if (gate.has('edit-posts')) { + /// print('Edit posts ability is defined'); + /// } + /// ``` + bool has(String ability); + + /// Define a new ability. + /// + /// Example: + /// ```dart + /// gate.define('edit-post', (user, post) async { + /// return post.userId == user.id; + /// }); + /// ``` + Gate define(String ability, Function callback); + + /// Define abilities for a resource. + /// + /// Example: + /// ```dart + /// gate.resource('posts', Post, { + /// 'view': (user, post) async => true, + /// 'create': (user) async => user.isAdmin, + /// 'update': (user, post) async => post.userId == user.id, + /// 'delete': (user, post) async => user.isAdmin, + /// }); + /// ``` + Gate resource(String name, Type resourceClass, + [Map? abilities]); + + /// Define a policy class for a given class type. + /// + /// Example: + /// ```dart + /// gate.policy(Post, PostPolicy); + /// ``` + Gate policy(Type class_, Type policy); + + /// Register a callback to run before all Gate checks. + /// + /// Example: + /// ```dart + /// gate.before((user, ability) { + /// if (user.isAdmin) return true; + /// }); + /// ``` + Gate before(Function callback); + + /// Register a callback to run after all Gate checks. + /// + /// Example: + /// ```dart + /// gate.after((user, ability, result, arguments) { + /// logAuthCheck(user, ability, result); + /// }); + /// ``` + Gate after(Function callback); + + /// Determine if all of the given abilities should be granted for the current user. + /// + /// Example: + /// ```dart + /// if (await gate.allows('edit-post', [post])) { + /// // User can edit the post + /// } + /// ``` + Future allows(dynamic ability, [dynamic arguments = const []]); + + /// Determine if any of the given abilities should be denied for the current user. + /// + /// Example: + /// ```dart + /// if (await gate.denies('edit-post', [post])) { + /// // User cannot edit the post + /// } + /// ``` + Future denies(dynamic ability, [dynamic arguments = const []]); + + /// Determine if all of the given abilities should be granted for the current user. + /// + /// Example: + /// ```dart + /// if (await gate.check(['edit-post', 'delete-post'], [post])) { + /// // User can both edit and delete the post + /// } + /// ``` + Future check(dynamic abilities, [dynamic arguments = const []]); + + /// Determine if any one of the given abilities should be granted for the current user. + /// + /// Example: + /// ```dart + /// if (await gate.any(['edit-post', 'view-post'], [post])) { + /// // User can either edit or view the post + /// } + /// ``` + Future any(dynamic abilities, [dynamic arguments = const []]); + + /// Determine if the given ability should be granted for the current user. + /// + /// Example: + /// ```dart + /// if (await gate.authorize('edit-post', [post])) { + /// // User is authorized to edit the post + /// } + /// ``` + Future authorize(String ability, [dynamic arguments = const []]); + + /// Inspect the user for the given ability. + /// + /// Example: + /// ```dart + /// if (await gate.inspect('edit-post', [post])) { + /// // User has the ability to edit the post + /// } + /// ``` + Future inspect(String ability, [dynamic arguments = const []]); + + /// Get the raw result from the authorization callback. + /// + /// Example: + /// ```dart + /// var result = await gate.raw('edit-post', [post]); + /// ``` + Future raw(String ability, [dynamic arguments = const []]); + + /// Get a policy instance for a given class. + /// + /// Example: + /// ```dart + /// var policy = gate.getPolicyFor(Post); + /// ``` + dynamic getPolicyFor(dynamic class_); + + /// Get a gate instance for the given user. + /// + /// Example: + /// ```dart + /// var userGate = gate.forUser(user); + /// ``` + Gate forUser(dynamic user); + + /// Get all of the defined abilities. + /// + /// Example: + /// ```dart + /// var abilities = gate.abilities(); + /// print('Defined abilities: ${abilities.keys.join(', ')}'); + /// ``` + Map abilities(); +} diff --git a/packages/contracts/lib/src/auth/auth_factory.dart b/packages/contracts/lib/src/auth/auth_factory.dart new file mode 100644 index 0000000..29d07b5 --- /dev/null +++ b/packages/contracts/lib/src/auth/auth_factory.dart @@ -0,0 +1,28 @@ +import 'guard.dart'; +import 'stateful_guard.dart'; + +/// Interface for creating authentication guard instances. +/// +/// This contract defines how authentication guards should be created and managed. +/// It provides methods for getting guard instances and setting the default guard. +abstract class AuthFactory { + /// Get a guard instance by name. + /// + /// Example: + /// ```dart + /// // Get the default guard + /// var guard = factory.guard(); + /// + /// // Get a specific guard + /// var apiGuard = factory.guard('api'); + /// ``` + dynamic guard([String? name]); + + /// Set the default guard the factory should serve. + /// + /// Example: + /// ```dart + /// factory.shouldUse('web'); + /// ``` + void shouldUse(String name); +} diff --git a/packages/contracts/lib/src/auth/authenticatable.dart b/packages/contracts/lib/src/auth/authenticatable.dart new file mode 100644 index 0000000..818b62d --- /dev/null +++ b/packages/contracts/lib/src/auth/authenticatable.dart @@ -0,0 +1,84 @@ +/// Interface for objects that can be authenticated. +/// +/// This contract defines the methods that an authenticatable entity +/// (like a User model) must implement to work with the authentication system. +abstract class Authenticatable { + /// Get the name of the unique identifier for the user. + /// + /// Example: + /// ```dart + /// class User implements Authenticatable { + /// @override + /// String getAuthIdentifierName() => 'id'; + /// } + /// ``` + String getAuthIdentifierName(); + + /// Get the unique identifier for the user. + /// + /// Example: + /// ```dart + /// class User implements Authenticatable { + /// @override + /// dynamic getAuthIdentifier() => id; + /// } + /// ``` + dynamic getAuthIdentifier(); + + /// Get the name of the password attribute for the user. + /// + /// Example: + /// ```dart + /// class User implements Authenticatable { + /// @override + /// String getAuthPasswordName() => 'password'; + /// } + /// ``` + String getAuthPasswordName(); + + /// Get the password for the user. + /// + /// Example: + /// ```dart + /// class User implements Authenticatable { + /// @override + /// String? getAuthPassword() => password; + /// } + /// ``` + String? getAuthPassword(); + + /// Get the "remember me" token value. + /// + /// Example: + /// ```dart + /// class User implements Authenticatable { + /// @override + /// String? getRememberToken() => rememberToken; + /// } + /// ``` + String? getRememberToken(); + + /// Set the "remember me" token value. + /// + /// Example: + /// ```dart + /// class User implements Authenticatable { + /// @override + /// void setRememberToken(String? value) { + /// rememberToken = value; + /// } + /// } + /// ``` + void setRememberToken(String? value); + + /// Get the column name for the "remember me" token. + /// + /// Example: + /// ```dart + /// class User implements Authenticatable { + /// @override + /// String getRememberTokenName() => 'remember_token'; + /// } + /// ``` + String getRememberTokenName(); +} diff --git a/packages/contracts/lib/src/auth/can_reset_password.dart b/packages/contracts/lib/src/auth/can_reset_password.dart new file mode 100644 index 0000000..6a82939 --- /dev/null +++ b/packages/contracts/lib/src/auth/can_reset_password.dart @@ -0,0 +1,35 @@ +/// Interface for password reset functionality. +/// +/// This contract defines how password reset functionality should be handled +/// for users that can reset their passwords. It provides methods for getting +/// the email address for password resets and sending reset notifications. +abstract class CanResetPassword { + /// Get the e-mail address where password reset links are sent. + /// + /// Example: + /// ```dart + /// class User implements CanResetPassword { + /// @override + /// String getEmailForPasswordReset() => email; + /// } + /// ``` + String getEmailForPasswordReset(); + + /// Send the password reset notification. + /// + /// Example: + /// ```dart + /// class User implements CanResetPassword { + /// @override + /// Future sendPasswordResetNotification(String token) async { + /// await notificationService.send( + /// PasswordResetNotification( + /// user: this, + /// token: token, + /// ), + /// ); + /// } + /// } + /// ``` + Future sendPasswordResetNotification(String token); +} diff --git a/packages/contracts/lib/src/auth/guard.dart b/packages/contracts/lib/src/auth/guard.dart new file mode 100644 index 0000000..b7379bd --- /dev/null +++ b/packages/contracts/lib/src/auth/guard.dart @@ -0,0 +1,81 @@ +import 'authenticatable.dart'; + +/// Interface for authentication guards. +/// +/// This contract defines the methods that an authentication guard must implement +/// to provide user authentication functionality. +abstract class Guard { + /// Determine if the current user is authenticated. + /// + /// Example: + /// ```dart + /// if (guard.check()) { + /// print('User is authenticated'); + /// } + /// ``` + bool check(); + + /// Determine if the current user is a guest. + /// + /// Example: + /// ```dart + /// if (guard.guest()) { + /// print('User is not authenticated'); + /// } + /// ``` + bool guest(); + + /// Get the currently authenticated user. + /// + /// Example: + /// ```dart + /// var user = guard.user(); + /// if (user != null) { + /// print('Hello ${user.name}'); + /// } + /// ``` + Authenticatable? user(); + + /// Get the ID for the currently authenticated user. + /// + /// Example: + /// ```dart + /// var userId = guard.id(); + /// if (userId != null) { + /// print('User ID: $userId'); + /// } + /// ``` + dynamic id(); + + /// Validate a user's credentials. + /// + /// Example: + /// ```dart + /// var credentials = { + /// 'email': 'user@example.com', + /// 'password': 'password123' + /// }; + /// if (guard.validate(credentials)) { + /// print('Credentials are valid'); + /// } + /// ``` + bool validate([Map credentials = const {}]); + + /// Determine if the guard has a user instance. + /// + /// Example: + /// ```dart + /// if (guard.hasUser()) { + /// print('Guard has a user instance'); + /// } + /// ``` + bool hasUser(); + + /// Set the current user. + /// + /// Example: + /// ```dart + /// guard.setUser(user); + /// ``` + Guard setUser(Authenticatable user); +} diff --git a/packages/contracts/lib/src/auth/middleware/authenticates_requests.dart b/packages/contracts/lib/src/auth/middleware/authenticates_requests.dart new file mode 100644 index 0000000..c46d2fa --- /dev/null +++ b/packages/contracts/lib/src/auth/middleware/authenticates_requests.dart @@ -0,0 +1,6 @@ +/// Interface for middleware that authenticates requests. +/// +/// This contract serves as a marker interface for middleware that handles +/// authentication of incoming requests. While it doesn't define any methods, +/// it provides a way to identify middleware that performs authentication. +abstract class AuthenticatesRequests {} diff --git a/packages/contracts/lib/src/auth/must_verify_email.dart b/packages/contracts/lib/src/auth/must_verify_email.dart new file mode 100644 index 0000000..5932599 --- /dev/null +++ b/packages/contracts/lib/src/auth/must_verify_email.dart @@ -0,0 +1,60 @@ +/// Interface for email verification functionality. +/// +/// This contract defines how email verification should be handled for users +/// that require email verification. It provides methods for checking and +/// updating verification status, as well as sending verification notifications. +abstract class MustVerifyEmail { + /// Determine if the user has verified their email address. + /// + /// Example: + /// ```dart + /// class User implements MustVerifyEmail { + /// @override + /// bool hasVerifiedEmail() { + /// return emailVerifiedAt != null; + /// } + /// } + /// ``` + bool hasVerifiedEmail(); + + /// Mark the given user's email as verified. + /// + /// Example: + /// ```dart + /// class User implements MustVerifyEmail { + /// @override + /// Future markEmailAsVerified() async { + /// emailVerifiedAt = DateTime.now(); + /// await save(); + /// return true; + /// } + /// } + /// ``` + Future markEmailAsVerified(); + + /// Send the email verification notification. + /// + /// Example: + /// ```dart + /// class User implements MustVerifyEmail { + /// @override + /// Future sendEmailVerificationNotification() async { + /// await notificationService.send( + /// EmailVerificationNotification(user: this), + /// ); + /// } + /// } + /// ``` + Future sendEmailVerificationNotification(); + + /// Get the email address that should be used for verification. + /// + /// Example: + /// ```dart + /// class User implements MustVerifyEmail { + /// @override + /// String getEmailForVerification() => email; + /// } + /// ``` + String getEmailForVerification(); +} diff --git a/packages/contracts/lib/src/auth/password_broker.dart b/packages/contracts/lib/src/auth/password_broker.dart new file mode 100644 index 0000000..8203e9b --- /dev/null +++ b/packages/contracts/lib/src/auth/password_broker.dart @@ -0,0 +1,69 @@ +/// Interface for password reset functionality. +/// +/// This contract defines how password reset operations should be handled, +/// including sending reset links and performing password resets. +abstract class PasswordBroker { + /// Constant representing a successfully sent reminder. + static const String resetLinkSent = 'passwords.sent'; + + /// Constant representing a successfully reset password. + static const String passwordReset = 'passwords.reset'; + + /// Constant representing the user not found response. + static const String invalidUser = 'passwords.user'; + + /// Constant representing an invalid token. + static const String invalidToken = 'passwords.token'; + + /// Constant representing a throttled reset attempt. + static const String resetThrottled = 'passwords.throttled'; + + /// Send a password reset link to a user. + /// + /// Example: + /// ```dart + /// var credentials = {'email': 'user@example.com'}; + /// var status = await broker.sendResetLink( + /// credentials, + /// (user) async { + /// // Custom notification logic + /// }, + /// ); + /// + /// if (status == PasswordBroker.resetLinkSent) { + /// // Reset link was sent successfully + /// } + /// ``` + Future sendResetLink( + Map credentials, [ + void Function(dynamic user)? callback, + ]); + + /// Reset the password for the given token. + /// + /// Example: + /// ```dart + /// var credentials = { + /// 'email': 'user@example.com', + /// 'password': 'newpassword', + /// 'token': 'reset-token' + /// }; + /// + /// var status = await broker.reset( + /// credentials, + /// (user) async { + /// // Set the new password + /// await user.setPassword(credentials['password']); + /// await user.save(); + /// }, + /// ); + /// + /// if (status == PasswordBroker.passwordReset) { + /// // Password was reset successfully + /// } + /// ``` + Future reset( + Map credentials, + void Function(dynamic user) callback, + ); +} diff --git a/packages/contracts/lib/src/auth/password_broker_factory.dart b/packages/contracts/lib/src/auth/password_broker_factory.dart new file mode 100644 index 0000000..1db0e10 --- /dev/null +++ b/packages/contracts/lib/src/auth/password_broker_factory.dart @@ -0,0 +1,23 @@ +import 'password_broker.dart'; + +/// Interface for creating password broker instances. +/// +/// This contract defines how password broker instances should be created +/// and managed, allowing for multiple password broker configurations. +abstract class PasswordBrokerFactory { + /// Get a password broker instance by name. + /// + /// Example: + /// ```dart + /// // Get the default broker + /// var broker = factory.broker(); + /// + /// // Get a specific broker + /// var adminBroker = factory.broker('admins'); + /// + /// var status = await broker.sendResetLink({ + /// 'email': 'user@example.com' + /// }); + /// ``` + PasswordBroker broker([String? name]); +} diff --git a/packages/contracts/lib/src/auth/stateful_guard.dart b/packages/contracts/lib/src/auth/stateful_guard.dart new file mode 100644 index 0000000..0b58e13 --- /dev/null +++ b/packages/contracts/lib/src/auth/stateful_guard.dart @@ -0,0 +1,88 @@ +import 'authenticatable.dart'; +import 'guard.dart'; + +/// Interface for stateful authentication guards. +/// +/// This contract extends the base Guard interface to add methods for +/// maintaining authentication state, such as login/logout functionality +/// and "remember me" capabilities. +abstract class StatefulGuard implements Guard { + /// Attempt to authenticate a user using the given credentials. + /// + /// Example: + /// ```dart + /// var credentials = { + /// 'email': 'user@example.com', + /// 'password': 'password123' + /// }; + /// if (await guard.attempt(credentials, remember: true)) { + /// // User is authenticated and will be remembered + /// } + /// ``` + Future attempt([ + Map credentials = const {}, + bool remember = false, + ]); + + /// Log a user into the application without sessions or cookies. + /// + /// Example: + /// ```dart + /// var credentials = { + /// 'email': 'user@example.com', + /// 'password': 'password123' + /// }; + /// if (await guard.once(credentials)) { + /// // User is authenticated for this request only + /// } + /// ``` + Future once([Map credentials = const {}]); + + /// Log a user into the application. + /// + /// Example: + /// ```dart + /// await guard.login(user, remember: true); + /// ``` + Future login(Authenticatable user, [bool remember = false]); + + /// Log the given user ID into the application. + /// + /// Example: + /// ```dart + /// var user = await guard.loginUsingId(1, remember: true); + /// if (user != null) { + /// // User was logged in successfully + /// } + /// ``` + Future loginUsingId(dynamic id, [bool remember = false]); + + /// Log the given user ID into the application without sessions or cookies. + /// + /// Example: + /// ```dart + /// var user = await guard.onceUsingId(1); + /// if (user != null) { + /// // User was logged in for this request only + /// } + /// ``` + Future onceUsingId(dynamic id); + + /// Determine if the user was authenticated via "remember me" cookie. + /// + /// Example: + /// ```dart + /// if (guard.viaRemember()) { + /// // User was authenticated using remember me cookie + /// } + /// ``` + bool viaRemember(); + + /// Log the user out of the application. + /// + /// Example: + /// ```dart + /// await guard.logout(); + /// ``` + Future logout(); +} diff --git a/packages/contracts/lib/src/auth/supports_basic_auth.dart b/packages/contracts/lib/src/auth/supports_basic_auth.dart new file mode 100644 index 0000000..79d7c79 --- /dev/null +++ b/packages/contracts/lib/src/auth/supports_basic_auth.dart @@ -0,0 +1,65 @@ +import '../http/response.dart'; + +/// Interface for HTTP Basic Authentication support. +/// +/// This contract defines how HTTP Basic Authentication should be handled, +/// providing methods for both stateful and stateless authentication attempts. +abstract class SupportsBasicAuth { + /// Attempt to authenticate using HTTP Basic Auth. + /// + /// Example: + /// ```dart + /// class ApiGuard implements SupportsBasicAuth { + /// @override + /// Future basic([ + /// String field = 'email', + /// Map extraConditions = const {}, + /// ]) async { + /// var credentials = getBasicAuthCredentials(); + /// if (await validateCredentials(credentials)) { + /// return null; // Authentication successful + /// } + /// return Response( + /// content: 'Unauthorized', + /// status: 401, + /// headers: { + /// 'WWW-Authenticate': 'Basic realm="API Access"' + /// }, + /// ); + /// } + /// } + /// ``` + Future basic([ + String field = 'email', + Map extraConditions = const {}, + ]); + + /// Perform a stateless HTTP Basic login attempt. + /// + /// Example: + /// ```dart + /// class ApiGuard implements SupportsBasicAuth { + /// @override + /// Future onceBasic([ + /// String field = 'email', + /// Map extraConditions = const {}, + /// ]) async { + /// var credentials = getBasicAuthCredentials(); + /// if (await validateCredentials(credentials)) { + /// return null; // Authentication successful + /// } + /// return Response( + /// content: 'Unauthorized', + /// status: 401, + /// headers: { + /// 'WWW-Authenticate': 'Basic realm="API Access"' + /// }, + /// ); + /// } + /// } + /// ``` + Future onceBasic([ + String field = 'email', + Map extraConditions = const {}, + ]); +} diff --git a/packages/contracts/lib/src/auth/user_provider.dart b/packages/contracts/lib/src/auth/user_provider.dart new file mode 100644 index 0000000..b96ea76 --- /dev/null +++ b/packages/contracts/lib/src/auth/user_provider.dart @@ -0,0 +1,86 @@ +import 'authenticatable.dart'; + +/// Interface for retrieving and validating users. +/// +/// This contract defines how users should be retrieved from storage +/// and how their credentials should be validated. +abstract class UserProvider { + /// Retrieve a user by their unique identifier. + /// + /// Example: + /// ```dart + /// var user = await provider.retrieveById(1); + /// if (user != null) { + /// print('Found user: ${user.getAuthIdentifier()}'); + /// } + /// ``` + Future retrieveById(dynamic identifier); + + /// Retrieve a user by their unique identifier and "remember me" token. + /// + /// Example: + /// ```dart + /// var user = await provider.retrieveByToken(1, 'remember-token'); + /// if (user != null) { + /// print('Found user by remember token'); + /// } + /// ``` + Future retrieveByToken(dynamic identifier, String token); + + /// Update the "remember me" token for the given user in storage. + /// + /// Example: + /// ```dart + /// await provider.updateRememberToken(user, 'new-remember-token'); + /// ``` + Future updateRememberToken(Authenticatable user, String token); + + /// Retrieve a user by the given credentials. + /// + /// Example: + /// ```dart + /// var credentials = { + /// 'email': 'user@example.com', + /// 'password': 'password123' + /// }; + /// var user = await provider.retrieveByCredentials(credentials); + /// if (user != null) { + /// print('Found user by credentials'); + /// } + /// ``` + Future retrieveByCredentials( + Map credentials); + + /// Validate a user against the given credentials. + /// + /// Example: + /// ```dart + /// var credentials = { + /// 'email': 'user@example.com', + /// 'password': 'password123' + /// }; + /// if (await provider.validateCredentials(user, credentials)) { + /// print('Credentials are valid'); + /// } + /// ``` + Future validateCredentials( + Authenticatable user, + Map credentials, + ); + + /// Rehash the user's password if required and supported. + /// + /// Example: + /// ```dart + /// await provider.rehashPasswordIfRequired( + /// user, + /// credentials, + /// force: true, + /// ); + /// ``` + Future rehashPasswordIfRequired( + Authenticatable user, + Map credentials, [ + bool force = false, + ]); +} diff --git a/packages/contracts/lib/src/broadcasting/broadcast_exception.dart b/packages/contracts/lib/src/broadcasting/broadcast_exception.dart new file mode 100644 index 0000000..f59b077 --- /dev/null +++ b/packages/contracts/lib/src/broadcasting/broadcast_exception.dart @@ -0,0 +1,19 @@ +/// Exception thrown when broadcasting fails. +/// +/// This exception is thrown when there is an error during broadcasting, +/// such as connection issues or invalid channel configurations. +class BroadcastException implements Exception { + /// The message describing why broadcasting failed. + final String message; + + /// Create a new broadcast exception. + /// + /// Example: + /// ```dart + /// throw BroadcastException('Failed to connect to broadcasting server'); + /// ``` + const BroadcastException(this.message); + + @override + String toString() => 'BroadcastException: $message'; +} diff --git a/packages/contracts/lib/src/broadcasting/broadcast_factory.dart b/packages/contracts/lib/src/broadcasting/broadcast_factory.dart new file mode 100644 index 0000000..b5f1ee7 --- /dev/null +++ b/packages/contracts/lib/src/broadcasting/broadcast_factory.dart @@ -0,0 +1,19 @@ +import 'broadcaster.dart'; + +/// Interface for creating broadcaster instances. +/// +/// This contract defines how broadcaster instances should be created and managed. +/// It provides methods for getting broadcaster instances by name. +abstract class BroadcastFactory { + /// Get a broadcaster implementation by name. + /// + /// Example: + /// ```dart + /// // Get the default broadcaster + /// var broadcaster = factory.connection(); + /// + /// // Get a specific broadcaster + /// var pusherBroadcaster = factory.connection('pusher'); + /// ``` + Future connection([String? name]); +} diff --git a/packages/contracts/lib/src/broadcasting/broadcaster.dart b/packages/contracts/lib/src/broadcasting/broadcaster.dart new file mode 100644 index 0000000..273f99c --- /dev/null +++ b/packages/contracts/lib/src/broadcasting/broadcaster.dart @@ -0,0 +1,48 @@ +import '../http/request.dart'; +import 'broadcast_exception.dart'; + +/// Interface for broadcasting functionality. +/// +/// This contract defines how broadcasting should be handled, +/// providing methods for authentication and event broadcasting. +abstract class Broadcaster { + /// Authenticate the incoming request for a given channel. + /// + /// Example: + /// ```dart + /// var result = await broadcaster.auth(request); + /// if (result != null) { + /// // Request is authenticated + /// } + /// ``` + Future auth(Request request); + + /// Return the valid authentication response. + /// + /// Example: + /// ```dart + /// var response = await broadcaster.validAuthenticationResponse( + /// request, + /// authResult, + /// ); + /// ``` + Future validAuthenticationResponse(Request request, dynamic result); + + /// Broadcast the given event. + /// + /// Example: + /// ```dart + /// await broadcaster.broadcast( + /// ['private-orders.1', 'private-orders.2'], + /// 'OrderShipped', + /// {'orderId': 1}, + /// ); + /// ``` + /// + /// Throws a [BroadcastException] if broadcasting fails. + Future broadcast( + List channels, + String event, [ + Map payload = const {}, + ]); +} diff --git a/packages/contracts/lib/src/broadcasting/has_broadcast_channel.dart b/packages/contracts/lib/src/broadcasting/has_broadcast_channel.dart new file mode 100644 index 0000000..4a057c2 --- /dev/null +++ b/packages/contracts/lib/src/broadcasting/has_broadcast_channel.dart @@ -0,0 +1,40 @@ +/// Interface for entities that have associated broadcast channels. +/// +/// This contract defines how entities should specify their broadcast channel +/// route definitions and names. It's typically implemented by models that +/// need to be associated with specific broadcast channels. +abstract class HasBroadcastChannel { + /// Get the broadcast channel route definition associated with the entity. + /// + /// Example: + /// ```dart + /// class Order implements HasBroadcastChannel { + /// final int id; + /// + /// Order(this.id); + /// + /// @override + /// String broadcastChannelRoute() { + /// return 'orders.{order}'; + /// } + /// } + /// ``` + String broadcastChannelRoute(); + + /// Get the broadcast channel name associated with the entity. + /// + /// Example: + /// ```dart + /// class Order implements HasBroadcastChannel { + /// final int id; + /// + /// Order(this.id); + /// + /// @override + /// String broadcastChannel() { + /// return 'orders.$id'; + /// } + /// } + /// ``` + String broadcastChannel(); +} diff --git a/packages/contracts/lib/src/broadcasting/should_be_unique.dart b/packages/contracts/lib/src/broadcasting/should_be_unique.dart new file mode 100644 index 0000000..443994e --- /dev/null +++ b/packages/contracts/lib/src/broadcasting/should_be_unique.dart @@ -0,0 +1,21 @@ +/// Interface for broadcasts that should be unique. +/// +/// This contract serves as a marker interface for broadcasts that should +/// be unique. While it doesn't define any methods, implementing this interface +/// signals that the broadcast should be unique and any duplicate broadcasts +/// should be prevented. +/// +/// Example: +/// ```dart +/// class OrderShippedEvent implements ShouldBroadcast, ShouldBeUnique { +/// final int orderId; +/// +/// OrderShippedEvent(this.orderId); +/// +/// @override +/// dynamic broadcastOn() { +/// return 'orders.$orderId'; +/// } +/// } +/// ``` +abstract class ShouldBeUnique {} diff --git a/packages/contracts/lib/src/broadcasting/should_broadcast.dart b/packages/contracts/lib/src/broadcasting/should_broadcast.dart new file mode 100644 index 0000000..ed826d3 --- /dev/null +++ b/packages/contracts/lib/src/broadcasting/should_broadcast.dart @@ -0,0 +1,29 @@ +/// Interface for events that should be broadcast. +/// +/// This contract defines how events should specify their broadcast channels. +/// It's implemented by event classes that need to be broadcast to specific channels. +abstract class ShouldBroadcast { + /// Get the channels the event should broadcast on. + /// + /// Example: + /// ```dart + /// class OrderShippedEvent implements ShouldBroadcast { + /// final int orderId; + /// + /// OrderShippedEvent(this.orderId); + /// + /// @override + /// dynamic broadcastOn() { + /// // Return a single channel + /// return 'orders.$orderId'; + /// + /// // Or return multiple channels + /// return [ + /// 'orders.$orderId', + /// 'admin.orders', + /// ]; + /// } + /// } + /// ``` + dynamic broadcastOn(); +} diff --git a/packages/contracts/lib/src/broadcasting/should_broadcast_now.dart b/packages/contracts/lib/src/broadcasting/should_broadcast_now.dart new file mode 100644 index 0000000..958db58 --- /dev/null +++ b/packages/contracts/lib/src/broadcasting/should_broadcast_now.dart @@ -0,0 +1,23 @@ +import 'should_broadcast.dart'; + +/// Interface for events that should be broadcast immediately. +/// +/// This contract extends [ShouldBroadcast] and serves as a marker interface +/// for events that should be broadcast immediately rather than being queued. +/// While it doesn't define any additional methods, implementing this interface +/// signals that the event should bypass the queue. +/// +/// Example: +/// ```dart +/// class OrderShippedEvent implements ShouldBroadcastNow { +/// final int orderId; +/// +/// OrderShippedEvent(this.orderId); +/// +/// @override +/// dynamic broadcastOn() { +/// return 'orders.$orderId'; +/// } +/// } +/// ``` +abstract class ShouldBroadcastNow implements ShouldBroadcast {} 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/bus/dispatcher.dart b/packages/contracts/lib/src/bus/dispatcher.dart new file mode 100644 index 0000000..177cf48 --- /dev/null +++ b/packages/contracts/lib/src/bus/dispatcher.dart @@ -0,0 +1,81 @@ +/// Interface for command bus dispatching. +/// +/// This contract defines how commands should be dispatched to their handlers. +/// It provides methods for both synchronous and asynchronous command handling, +/// as well as configuration of command handling pipelines. +abstract class Dispatcher { + /// Dispatch a command to its appropriate handler. + /// + /// Example: + /// ```dart + /// var result = await dispatcher.dispatch( + /// CreateOrderCommand(items: items), + /// ); + /// ``` + Future dispatch(dynamic command); + + /// Dispatch a command to its appropriate handler in the current process. + /// + /// Queueable jobs will be dispatched to the "sync" queue. + /// + /// Example: + /// ```dart + /// var result = await dispatcher.dispatchSync( + /// CreateOrderCommand(items: items), + /// ); + /// ``` + Future dispatchSync(dynamic command, [dynamic handler]); + + /// Dispatch a command to its appropriate handler in the current process. + /// + /// Example: + /// ```dart + /// var result = await dispatcher.dispatchNow( + /// CreateOrderCommand(items: items), + /// ); + /// ``` + Future dispatchNow(dynamic command, [dynamic handler]); + + /// Determine if the given command has a handler. + /// + /// Example: + /// ```dart + /// if (dispatcher.hasCommandHandler(command)) { + /// print('Handler exists for command'); + /// } + /// ``` + bool hasCommandHandler(dynamic command); + + /// Retrieve the handler for a command. + /// + /// Example: + /// ```dart + /// var handler = dispatcher.getCommandHandler(command); + /// if (handler != null) { + /// print('Found handler: ${handler.runtimeType}'); + /// } + /// ``` + dynamic getCommandHandler(dynamic command); + + /// Set the pipes commands should be piped through before dispatching. + /// + /// Example: + /// ```dart + /// dispatcher.pipeThrough([ + /// TransactionPipe(), + /// LoggingPipe(), + /// ]); + /// ``` + Dispatcher pipeThrough(List pipes); + + /// Map a command to a handler. + /// + /// Example: + /// ```dart + /// dispatcher.map({ + /// CreateOrderCommand: CreateOrderHandler, + /// UpdateOrderCommand: UpdateOrderHandler, + /// }); + /// ``` + Dispatcher map(Map commandMap); +} diff --git a/packages/contracts/lib/src/bus/queueing_dispatcher.dart b/packages/contracts/lib/src/bus/queueing_dispatcher.dart new file mode 100644 index 0000000..22829a4 --- /dev/null +++ b/packages/contracts/lib/src/bus/queueing_dispatcher.dart @@ -0,0 +1,40 @@ +import 'dispatcher.dart'; + +/// Interface for queueing command bus dispatching. +/// +/// This contract extends the base [Dispatcher] to add queueing functionality, +/// allowing commands to be dispatched to queues and processed in batches. +abstract class QueueingDispatcher implements Dispatcher { + /// Attempt to find the batch with the given ID. + /// + /// Example: + /// ```dart + /// var batch = await dispatcher.findBatch('batch-123'); + /// if (batch != null) { + /// print('Found batch with ${batch.jobs.length} jobs'); + /// } + /// ``` + Future findBatch(String batchId); + + /// Create a new batch of queueable jobs. + /// + /// Example: + /// ```dart + /// var batch = await dispatcher.batch([ + /// ProcessOrderCommand(orderId: 1), + /// ProcessOrderCommand(orderId: 2), + /// ProcessOrderCommand(orderId: 3), + /// ]); + /// ``` + Future batch(dynamic jobs); + + /// Dispatch a command to its appropriate handler behind a queue. + /// + /// Example: + /// ```dart + /// await dispatcher.dispatchToQueue( + /// ProcessLargeOrderCommand(orderId: 1), + /// ); + /// ``` + Future dispatchToQueue(dynamic command); +} diff --git a/packages/contracts/lib/src/cache/cache_factory.dart b/packages/contracts/lib/src/cache/cache_factory.dart new file mode 100644 index 0000000..4aefc5c --- /dev/null +++ b/packages/contracts/lib/src/cache/cache_factory.dart @@ -0,0 +1,19 @@ +import 'repository.dart'; + +/// Interface for creating cache store instances. +/// +/// This contract defines how cache store instances should be created and managed. +/// It provides methods for getting cache store instances by name. +abstract class CacheFactory { + /// Get a cache store instance by name. + /// + /// Example: + /// ```dart + /// // Get the default store + /// var store = factory.store(); + /// + /// // Get a specific store + /// var redisStore = factory.store('redis'); + /// ``` + Future store([String? name]); +} diff --git a/packages/contracts/lib/src/cache/cache_interface.dart b/packages/contracts/lib/src/cache/cache_interface.dart new file mode 100644 index 0000000..206c104 --- /dev/null +++ b/packages/contracts/lib/src/cache/cache_interface.dart @@ -0,0 +1,75 @@ +/// PSR-16 Cache Interface. +/// +/// This is a Dart implementation of the PSR-16 CacheInterface. +/// It provides a simple caching interface for storing and retrieving values +/// by key. +abstract class CacheInterface { + /// Fetches a value from the cache. + /// + /// Example: + /// ```dart + /// var value = await cache.get('key', defaultValue: 'default'); + /// ``` + Future get(String key, {T? defaultValue}); + + /// Persists data in the cache, uniquely referenced by a key. + /// + /// Example: + /// ```dart + /// await cache.set('key', 'value', ttl: Duration(minutes: 10)); + /// ``` + Future set(String key, dynamic value, {Duration? ttl}); + + /// Delete an item from the cache by its unique key. + /// + /// Example: + /// ```dart + /// await cache.delete('key'); + /// ``` + Future delete(String key); + + /// Wipes clean the entire cache's keys. + /// + /// Example: + /// ```dart + /// await cache.clear(); + /// ``` + Future clear(); + + /// Obtains multiple cache items by their unique keys. + /// + /// Example: + /// ```dart + /// var values = await cache.getMultiple(['key1', 'key2']); + /// ``` + Future> getMultiple(Iterable keys); + + /// Persists a set of key => value pairs in the cache. + /// + /// Example: + /// ```dart + /// await cache.setMultiple({ + /// 'key1': 'value1', + /// 'key2': 'value2', + /// }); + /// ``` + Future setMultiple(Map values, {Duration? ttl}); + + /// Deletes multiple cache items in a single operation. + /// + /// Example: + /// ```dart + /// await cache.deleteMultiple(['key1', 'key2']); + /// ``` + Future deleteMultiple(Iterable keys); + + /// Determines whether an item is present in the cache. + /// + /// Example: + /// ```dart + /// if (await cache.has('key')) { + /// print('Cache has key'); + /// } + /// ``` + Future has(String key); +} diff --git a/packages/contracts/lib/src/cache/lock.dart b/packages/contracts/lib/src/cache/lock.dart new file mode 100644 index 0000000..e7c6f17 --- /dev/null +++ b/packages/contracts/lib/src/cache/lock.dart @@ -0,0 +1,45 @@ +/// Interface for cache locks. +/// +/// This contract defines how cache locks should behave, providing methods +/// for acquiring, releasing, and managing locks. +abstract class Lock { + /// Attempt to acquire the lock. + /// + /// Example: + /// ```dart + /// if (await lock.acquire()) { + /// try { + /// // Process that requires locking + /// } finally { + /// await lock.release(); + /// } + /// } + /// ``` + Future acquire(); + + /// Release the lock. + /// + /// Example: + /// ```dart + /// await lock.release(); + /// ``` + Future release(); + + /// Get the owner value of the lock. + /// + /// Example: + /// ```dart + /// var owner = lock.owner(); + /// ``` + String? owner(); + + /// Attempt to acquire the lock for the given number of seconds. + /// + /// Example: + /// ```dart + /// if (await lock.block(5)) { + /// // Lock was acquired + /// } + /// ``` + Future block(int seconds); +} diff --git a/packages/contracts/lib/src/cache/lock_provider.dart b/packages/contracts/lib/src/cache/lock_provider.dart new file mode 100644 index 0000000..1605902 --- /dev/null +++ b/packages/contracts/lib/src/cache/lock_provider.dart @@ -0,0 +1,30 @@ +import 'lock.dart'; + +/// Interface for creating lock instances. +/// +/// This contract defines how lock instances should be created and restored, +/// providing methods for getting new locks and restoring existing ones. +abstract class LockProvider { + /// Get a lock instance. + /// + /// Example: + /// ```dart + /// var lock = provider.lock( + /// 'processing-order-1', + /// seconds: 60, + /// owner: 'worker-1', + /// ); + /// ``` + Lock lock(String name, {int seconds = 0, String? owner}); + + /// Restore a lock instance using the owner identifier. + /// + /// Example: + /// ```dart + /// var lock = provider.restoreLock( + /// 'processing-order-1', + /// 'worker-1', + /// ); + /// ``` + Lock restoreLock(String name, String owner); +} diff --git a/packages/contracts/lib/src/cache/lock_timeout_exception.dart b/packages/contracts/lib/src/cache/lock_timeout_exception.dart new file mode 100644 index 0000000..4d9becb --- /dev/null +++ b/packages/contracts/lib/src/cache/lock_timeout_exception.dart @@ -0,0 +1,19 @@ +/// Exception thrown when a lock operation times out. +/// +/// This exception is thrown when attempting to acquire a lock that could not +/// be obtained within the specified timeout period. +class LockTimeoutException implements Exception { + /// The message describing why the lock operation timed out. + final String message; + + /// Create a new lock timeout exception. + /// + /// Example: + /// ```dart + /// throw LockTimeoutException('Could not acquire lock "users" within 5 seconds'); + /// ``` + const LockTimeoutException(this.message); + + @override + String toString() => 'LockTimeoutException: $message'; +} diff --git a/packages/contracts/lib/src/cache/repository.dart b/packages/contracts/lib/src/cache/repository.dart new file mode 100644 index 0000000..f65f567 --- /dev/null +++ b/packages/contracts/lib/src/cache/repository.dart @@ -0,0 +1,110 @@ +import 'cache_interface.dart'; +import 'store.dart'; + +/// Interface for cache operations. +/// +/// This contract extends the PSR-16 CacheInterface to add additional +/// functionality specific to Laravel's caching system. +abstract class CacheRepository extends CacheInterface { + /// Retrieve an item from the cache and delete it. + /// + /// Example: + /// ```dart + /// var value = await cache.pull('key', defaultValue: 'default'); + /// ``` + Future pull(String key, {T? defaultValue}); + + /// Store an item in the cache. + /// + /// Example: + /// ```dart + /// await cache.put('key', 'value', ttl: Duration(minutes: 10)); + /// ``` + Future put(String key, dynamic value, {Duration? ttl}); + + /// Store an item in the cache if the key does not exist. + /// + /// Example: + /// ```dart + /// var added = await cache.add('key', 'value', ttl: Duration(minutes: 10)); + /// ``` + Future add(String key, dynamic value, {Duration? ttl}); + + /// Increment the value of an item in the cache. + /// + /// Example: + /// ```dart + /// var newValue = await cache.increment('visits'); + /// ``` + Future increment(String key, [int value = 1]); + + /// Decrement the value of an item in the cache. + /// + /// Example: + /// ```dart + /// var newValue = await cache.decrement('remaining'); + /// ``` + Future decrement(String key, [int value = 1]); + + /// Store an item in the cache indefinitely. + /// + /// Example: + /// ```dart + /// await cache.forever('key', 'value'); + /// ``` + Future forever(String key, dynamic value); + + /// Get an item from the cache, or execute the callback and store the result. + /// + /// Example: + /// ```dart + /// var value = await cache.remember( + /// 'key', + /// Duration(minutes: 10), + /// () async => await computeValue(), + /// ); + /// ``` + Future remember( + String key, + Duration? ttl, + Future Function() callback, + ); + + /// Get an item from the cache, or execute the callback and store the result forever. + /// + /// Example: + /// ```dart + /// var value = await cache.sear( + /// 'key', + /// () async => await computeValue(), + /// ); + /// ``` + Future sear(String key, Future Function() callback); + + /// Get an item from the cache, or execute the callback and store the result forever. + /// + /// Example: + /// ```dart + /// var value = await cache.rememberForever( + /// 'key', + /// () async => await computeValue(), + /// ); + /// ``` + Future rememberForever(String key, Future Function() callback); + + /// Remove an item from the cache. + /// + /// Example: + /// ```dart + /// await cache.forget('key'); + /// ``` + Future forget(String key); + + /// Get the cache store implementation. + /// + /// Example: + /// ```dart + /// var store = cache.getStore(); + /// ``` + CacheStore getStore(); +} diff --git a/packages/contracts/lib/src/cache/store.dart b/packages/contracts/lib/src/cache/store.dart new file mode 100644 index 0000000..fc09b9d --- /dev/null +++ b/packages/contracts/lib/src/cache/store.dart @@ -0,0 +1,91 @@ +/// Interface for cache store implementations. +/// +/// This contract defines how cache stores should handle low-level cache operations. +/// It provides methods for storing, retrieving, and managing cached items at the +/// storage level. +abstract class CacheStore { + /// Retrieve an item from the cache by key. + /// + /// Example: + /// ```dart + /// var value = await store.get('key'); + /// ``` + Future get(String key); + + /// Retrieve multiple items from the cache by key. + /// + /// Items not found in the cache will have a null value. + /// + /// Example: + /// ```dart + /// var values = await store.many(['key1', 'key2']); + /// ``` + Future> many(List keys); + + /// Store an item in the cache for a given number of seconds. + /// + /// Example: + /// ```dart + /// await store.put('key', 'value', 600); // 10 minutes + /// ``` + Future put(String key, dynamic value, int seconds); + + /// Store multiple items in the cache for a given number of seconds. + /// + /// Example: + /// ```dart + /// await store.putMany({ + /// 'key1': 'value1', + /// 'key2': 'value2', + /// }, 600); // 10 minutes + /// ``` + Future putMany(Map values, int seconds); + + /// Increment the value of an item in the cache. + /// + /// Example: + /// ```dart + /// var newValue = await store.increment('visits'); + /// ``` + Future increment(String key, [int value = 1]); + + /// Decrement the value of an item in the cache. + /// + /// Example: + /// ```dart + /// var newValue = await store.decrement('remaining'); + /// ``` + Future decrement(String key, [int value = 1]); + + /// Store an item in the cache indefinitely. + /// + /// Example: + /// ```dart + /// await store.forever('key', 'value'); + /// ``` + Future forever(String key, dynamic value); + + /// Remove an item from the cache. + /// + /// Example: + /// ```dart + /// await store.forget('key'); + /// ``` + Future forget(String key); + + /// Remove all items from the cache. + /// + /// Example: + /// ```dart + /// await store.flush(); + /// ``` + Future flush(); + + /// Get the cache key prefix. + /// + /// Example: + /// ```dart + /// var prefix = store.getPrefix(); + /// ``` + String getPrefix(); +} diff --git a/packages/contracts/lib/src/config/repository.dart b/packages/contracts/lib/src/config/repository.dart new file mode 100644 index 0000000..67e214b --- /dev/null +++ b/packages/contracts/lib/src/config/repository.dart @@ -0,0 +1,64 @@ +/// Interface for configuration repository. +/// +/// This contract defines the standard way to interact with configuration values +/// in the application. It provides methods to get, set, and manipulate +/// configuration values in a consistent manner. +abstract class Repository { + /// Determine if the given configuration value exists. + /// + /// Example: + /// ```dart + /// if (config.has('database.default')) { + /// // Use the database configuration + /// } + /// ``` + bool has(String key); + + /// Get the specified configuration value. + /// + /// Returns [defaultValue] if the key doesn't exist. + /// + /// Example: + /// ```dart + /// var dbHost = config.get('database.connections.mysql.host', 'localhost'); + /// ``` + T? get(String key, [T? defaultValue]); + + /// Get all of the configuration items. + /// + /// Example: + /// ```dart + /// var allConfig = config.all(); + /// print('Database host: ${allConfig['database']['connections']['mysql']['host']}'); + /// ``` + Map all(); + + /// Set a given configuration value. + /// + /// Example: + /// ```dart + /// config.set('app.timezone', 'UTC'); + /// config.set('services.aws', { + /// 'key': 'your-key', + /// 'secret': 'your-secret', + /// 'region': 'us-east-1', + /// }); + /// ``` + void set(String key, dynamic value); + + /// Prepend a value onto an array configuration value. + /// + /// Example: + /// ```dart + /// config.prepend('app.providers', MyServiceProvider); + /// ``` + void prepend(String key, dynamic value); + + /// Push a value onto an array configuration value. + /// + /// Example: + /// ```dart + /// config.push('app.providers', MyServiceProvider); + /// ``` + void push(String key, dynamic value); +} diff --git a/packages/contracts/lib/src/console/application.dart b/packages/contracts/lib/src/console/application.dart new file mode 100644 index 0000000..304c98d --- /dev/null +++ b/packages/contracts/lib/src/console/application.dart @@ -0,0 +1,22 @@ +/// Interface for console application. +/// +/// This contract defines how console commands should be executed and +/// their output managed at the application level. +abstract class ConsoleApplication { + /// Run an Artisan console command by name. + /// + /// Example: + /// ```dart + /// var status = await app.call('migrate', ['--force']); + /// ``` + Future call(String command, + [List parameters = const [], dynamic outputBuffer]); + + /// Get the output from the last command. + /// + /// Example: + /// ```dart + /// var lastOutput = app.output(); + /// ``` + String output(); +} diff --git a/packages/contracts/lib/src/console/isolatable.dart b/packages/contracts/lib/src/console/isolatable.dart new file mode 100644 index 0000000..bcc643a --- /dev/null +++ b/packages/contracts/lib/src/console/isolatable.dart @@ -0,0 +1,16 @@ +/// Interface for console commands that can be run in isolation. +/// +/// This contract serves as a marker interface for console commands that can +/// be run in isolation. While it doesn't define any methods, implementing +/// this interface signals that the command can be safely executed in an +/// isolated environment. +/// +/// Example: +/// ```dart +/// class ImportDataCommand implements Isolatable { +/// Future handle() async { +/// // Command can be run in isolation +/// } +/// } +/// ``` +abstract class Isolatable {} diff --git a/packages/contracts/lib/src/console/kernel.dart b/packages/contracts/lib/src/console/kernel.dart new file mode 100644 index 0000000..94661de --- /dev/null +++ b/packages/contracts/lib/src/console/kernel.dart @@ -0,0 +1,63 @@ +/// Interface for console command kernel. +/// +/// This contract defines how console commands should be handled and managed. +/// It provides methods for bootstrapping, handling commands, and managing +/// command output. +abstract class ConsoleKernel { + /// Bootstrap the application for artisan commands. + /// + /// Example: + /// ```dart + /// await kernel.bootstrap(); + /// ``` + Future bootstrap(); + + /// Handle an incoming console command. + /// + /// Example: + /// ```dart + /// var status = await kernel.handle(input, output); + /// ``` + Future handle(dynamic input, [dynamic output]); + + /// Run an Artisan console command by name. + /// + /// Example: + /// ```dart + /// var status = await kernel.call('migrate', ['--force']); + /// ``` + Future call(String command, + [List parameters = const [], dynamic outputBuffer]); + + /// Queue an Artisan console command by name. + /// + /// Example: + /// ```dart + /// var dispatch = await kernel.queue('email:send', ['user@example.com']); + /// ``` + Future queue(String command, [List parameters = const []]); + + /// Get all of the commands registered with the console. + /// + /// Example: + /// ```dart + /// var commands = kernel.all(); + /// ``` + Map all(); + + /// Get the output for the last run command. + /// + /// Example: + /// ```dart + /// var lastOutput = kernel.output(); + /// ``` + String output(); + + /// Terminate the application. + /// + /// Example: + /// ```dart + /// await kernel.terminate(input, 0); + /// ``` + Future terminate(dynamic input, int status); +} diff --git a/packages/contracts/lib/src/console/prompts_for_missing_input.dart b/packages/contracts/lib/src/console/prompts_for_missing_input.dart new file mode 100644 index 0000000..c82ec29 --- /dev/null +++ b/packages/contracts/lib/src/console/prompts_for_missing_input.dart @@ -0,0 +1,16 @@ +/// Interface for commands that prompt for missing input. +/// +/// This contract serves as a marker interface for console commands that should +/// prompt for missing input arguments or options. While it doesn't define any +/// methods, implementing this interface signals that the command should +/// interactively prompt the user when required input is missing. +/// +/// Example: +/// ```dart +/// class CreateUserCommand implements PromptsForMissingInput { +/// Future handle() async { +/// // Command will prompt for missing input +/// } +/// } +/// ``` +abstract class PromptsForMissingInput {} diff --git a/packages/contracts/lib/src/container/binding_resolution_exception.dart b/packages/contracts/lib/src/container/binding_resolution_exception.dart new file mode 100644 index 0000000..d16e574 --- /dev/null +++ b/packages/contracts/lib/src/container/binding_resolution_exception.dart @@ -0,0 +1,30 @@ +/// Exception thrown when the container fails to resolve a binding. +class BindingResolutionException implements Exception { + /// The message describing the binding resolution failure. + final String message; + + /// The original error that caused the binding resolution failure, if any. + final Object? originalError; + + /// The stack trace associated with the original error, if any. + final StackTrace? stackTrace; + + /// Creates a new [BindingResolutionException]. + /// + /// The [message] parameter describes what went wrong during binding resolution. + /// Optionally, you can provide the [originalError] and its [stackTrace] for + /// more detailed debugging information. + const BindingResolutionException( + this.message, { + this.originalError, + this.stackTrace, + }); + + @override + String toString() { + if (originalError != null) { + return 'BindingResolutionException: $message\nCaused by: $originalError'; + } + return 'BindingResolutionException: $message'; + } +} diff --git a/packages/contracts/lib/src/container/circular_dependency_exception.dart b/packages/contracts/lib/src/container/circular_dependency_exception.dart new file mode 100644 index 0000000..e75b494 --- /dev/null +++ b/packages/contracts/lib/src/container/circular_dependency_exception.dart @@ -0,0 +1,43 @@ +/// Exception thrown when a circular dependency is detected in the container. +/// +/// This exception is thrown when the container detects a circular dependency +/// while trying to resolve a type. A circular dependency occurs when type A +/// depends on type B which depends on type A, either directly or through +/// a chain of other dependencies. +/// +/// Example: +/// ```dart +/// class A { +/// A(B b); +/// } +/// +/// class B { +/// B(A a); // Circular dependency: A -> B -> A +/// } +/// +/// try { +/// container.make('A'); +/// } on CircularDependencyException catch (e) { +/// print(e.message); // "Circular dependency detected while resolving [A -> B -> A]" +/// print(e.path); // ["A", "B", "A"] +/// } +/// ``` +class CircularDependencyException implements Exception { + /// The error message describing the circular dependency. + final String message; + + /// The path of dependencies that form the circle. + final List path; + + /// Creates a new [CircularDependencyException]. + /// + /// The [path] parameter should contain the list of types in the order they + /// were encountered while resolving dependencies, with the last type being + /// the one that completes the circle. + CircularDependencyException(this.path) + : message = + 'Circular dependency detected while resolving [${path.join(" -> ")}]'; + + @override + String toString() => message; +} diff --git a/packages/contracts/lib/src/container/container.dart b/packages/contracts/lib/src/container/container.dart index 88578ff..e04ff5e 100644 --- a/packages/contracts/lib/src/container/container.dart +++ b/packages/contracts/lib/src/container/container.dart @@ -1,3 +1,107 @@ -/// Container package contracts -export 'container_contract.dart'; -export 'contextual_binding_contract.dart'; +import 'binding_resolution_exception.dart'; +import 'container_interface.dart'; +import 'contextual_binding_builder.dart'; + +/// Interface for the IoC container. +/// +/// This contract defines the interface for the Inversion of Control container, +/// which provides dependency injection and service location capabilities. +/// It extends the basic [ContainerInterface] with additional functionality +/// for binding, resolving, and managing services. +abstract class Container implements ContainerInterface { + /// Determine if the given abstract type has been bound. + bool bound(String abstract); + + /// Alias a type to a different name. + /// + /// Throws [ArgumentError] if the alias would cause a circular reference. + void alias(String abstract, String alias); + + /// Assign a set of tags to a given binding. + void tag(dynamic abstracts, List tags); + + /// Resolve all of the bindings for a given tag. + Iterable tagged(String tag); + + /// Register a binding with the container. + /// + /// The [concrete] parameter can be a Type, a factory function, or null. + /// If [shared] is true, the same instance will be returned for subsequent + /// resolutions of this binding. + void bind(String abstract, dynamic concrete, {bool shared = false}); + + /// Bind a callback to resolve with [call]. + void bindMethod(dynamic method, Function callback); + + /// Register a binding if it hasn't already been registered. + void bindIf(String abstract, dynamic concrete, {bool shared = false}); + + /// Register a shared binding in the container. + void singleton(String abstract, [dynamic concrete]); + + /// Register a shared binding if it hasn't already been registered. + void singletonIf(String abstract, [dynamic concrete]); + + /// Register a scoped binding in the container. + void scoped(String abstract, [dynamic concrete]); + + /// Register a scoped binding if it hasn't already been registered. + void scopedIf(String abstract, [dynamic concrete]); + + /// "Extend" an abstract type in the container. + /// + /// Throws [ArgumentError] if the abstract type isn't registered. + void extend(String abstract, Function(dynamic service) closure); + + /// Register an existing instance as shared in the container. + T instance(String abstract, T instance); + + /// Add a contextual binding to the container. + void addContextualBinding( + String concrete, + String abstract, + dynamic implementation, + ); + + /// Define a contextual binding. + ContextualBindingBuilder when(dynamic concrete); + + /// Get a factory function to resolve the given type from the container. + Function factory(String abstract); + + /// Flush the container of all bindings and resolved instances. + void flush(); + + /// Resolve the given type from the container. + /// + /// Throws [BindingResolutionException] if the type cannot be resolved. + T make(String abstract, {Map? parameters}); + + /// Call the given callback / class@method and inject its dependencies. + dynamic call( + dynamic callback, { + Map? parameters, + String? defaultMethod, + }); + + /// Determine if the given abstract type has been resolved. + bool resolved(String abstract); + + /// Register a new before resolving callback. + void beforeResolving( + dynamic abstract, [ + void Function(Container container, String abstract)? callback, + ]); + + /// Register a new resolving callback. + void resolving( + dynamic abstract, [ + void Function(dynamic instance, Container container)? callback, + ]); + + /// Register a new after resolving callback. + void afterResolving( + dynamic abstract, [ + void Function(dynamic instance, Container container)? callback, + ]); +} diff --git a/packages/contracts/lib/src/container/container_interface.dart b/packages/contracts/lib/src/container/container_interface.dart new file mode 100644 index 0000000..1b0291b --- /dev/null +++ b/packages/contracts/lib/src/container/container_interface.dart @@ -0,0 +1,27 @@ +/// Interface for service containers. +/// +/// This contract defines the basic interface that any service container +/// must implement. It follows the PSR-11 ContainerInterface specification +/// from PHP-FIG, adapted for Dart. +abstract class ContainerInterface { + /// Finds an entry of the container by its identifier and returns it. + /// + /// Example: + /// ```dart + /// var logger = container.get('logger'); + /// ``` + /// + /// Throws [BindingResolutionException] if the identifier is not found. + dynamic get(String id); + + /// Returns true if the container can return an entry for the given identifier. + /// Returns false otherwise. + /// + /// Example: + /// ```dart + /// if (container.has('logger')) { + /// // Use the logger service + /// } + /// ``` + bool has(String id); +} diff --git a/packages/contracts/lib/src/container/contextual_binding_builder.dart b/packages/contracts/lib/src/container/contextual_binding_builder.dart new file mode 100644 index 0000000..e29086f --- /dev/null +++ b/packages/contracts/lib/src/container/contextual_binding_builder.dart @@ -0,0 +1,25 @@ +/// Interface for building contextual bindings in the container. +/// +/// This contract allows for fluent configuration of contextual bindings, +/// which are used to specify different concrete implementations for a +/// dependency based on the context in which it is being resolved. +abstract class ContextualBindingBuilder { + /// Define the abstract target that is being contextualized. + /// + /// This method specifies which abstract type or interface should be + /// bound differently in the given context. + /// + /// Example: + /// ```dart + /// container.when([UserController]).needs(Logger).give(FileLogger); + /// ``` + ContextualBindingBuilder needs(dynamic abstract); + + /// Define the concrete implementation that should be used. + /// + /// This method specifies the concrete implementation that should be + /// used when resolving the abstract type in the given context. + /// + /// The implementation can be either a concrete type or a factory function. + void give(dynamic implementation); +} diff --git a/packages/contracts/lib/src/cookie/cookie_factory.dart b/packages/contracts/lib/src/cookie/cookie_factory.dart new file mode 100644 index 0000000..462db88 --- /dev/null +++ b/packages/contracts/lib/src/cookie/cookie_factory.dart @@ -0,0 +1,67 @@ +/// Interface for cookie factory. +/// +/// This contract defines the standard way to create cookie instances +/// in the application. It provides methods to create and expire cookies +/// with various attributes. +abstract class CookieFactory { + /// Create a new cookie instance. + /// + /// Example: + /// ```dart + /// var cookie = cookies.make( + /// 'preferences', + /// 'theme=dark', + /// minutes: 60, + /// secure: true, + /// sameSite: 'Lax' + /// ); + /// ``` + dynamic make( + String name, + String value, { + int minutes = 0, + String? path, + String? domain, + bool? secure, + bool httpOnly = true, + bool raw = false, + String? sameSite, + }); + + /// Create a cookie that lasts "forever" (five years). + /// + /// Example: + /// ```dart + /// var cookie = cookies.forever( + /// 'user_id', + /// '12345', + /// secure: true, + /// sameSite: 'Strict' + /// ); + /// ``` + dynamic forever( + String name, + String value, { + String? path, + String? domain, + bool? secure, + bool httpOnly = true, + bool raw = false, + String? sameSite, + }); + + /// Expire the given cookie. + /// + /// Creates a new cookie instance that will expire the cookie + /// when sent to the browser. + /// + /// Example: + /// ```dart + /// var cookie = cookies.forget('session_id'); + /// ``` + dynamic forget( + String name, { + String? path, + String? domain, + }); +} diff --git a/packages/contracts/lib/src/cookie/queueing_factory.dart b/packages/contracts/lib/src/cookie/queueing_factory.dart new file mode 100644 index 0000000..a7a12ca --- /dev/null +++ b/packages/contracts/lib/src/cookie/queueing_factory.dart @@ -0,0 +1,14 @@ +import 'cookie_factory.dart'; + +/// Interface for queueing cookie factory. +abstract class QueueingFactory extends CookieFactory { + /// Queue a cookie to send with the next response. + void queue(String name, String value, + [Map options = const {}]); + + /// Remove a cookie from the queue. + void unqueue(String name); + + /// Get the queued cookies. + Map getQueuedCookies(); +} diff --git a/packages/contracts/lib/src/database/eloquent/builder.dart b/packages/contracts/lib/src/database/eloquent/builder.dart new file mode 100644 index 0000000..fe050d5 --- /dev/null +++ b/packages/contracts/lib/src/database/eloquent/builder.dart @@ -0,0 +1,8 @@ +import '../query/builder.dart'; + +/// Interface for Eloquent query builder. +/// +/// This contract serves as a marker interface for Eloquent query builders. +/// While it doesn't define any methods, it exists to improve IDE support +/// and type safety when working with Eloquent query builders. +abstract class EloquentBuilder extends QueryBuilder {} diff --git a/packages/contracts/lib/src/database/eloquent/castable.dart b/packages/contracts/lib/src/database/eloquent/castable.dart new file mode 100644 index 0000000..79f3670 --- /dev/null +++ b/packages/contracts/lib/src/database/eloquent/castable.dart @@ -0,0 +1,40 @@ +import 'casts_attributes.dart'; +import 'casts_inbound_attributes.dart'; + +/// Interface for classes that can specify their own casting behavior. +/// +/// This contract defines how a class can specify which caster should be used +/// when casting its values to and from the database. +abstract class Castable { + /// Get the name of the caster class to use when casting from / to this cast target. + /// + /// Example: + /// ```dart + /// class Location implements Castable { + /// final double lat; + /// final double lng; + /// + /// Location(this.lat, this.lng); + /// + /// @override + /// dynamic castUsing(List arguments) { + /// return LocationCaster(); + /// } + /// } + /// + /// class LocationCaster implements CastsAttributes> { + /// @override + /// Location? get(dynamic model, String key, dynamic value, Map attributes) { + /// if (value == null) return null; + /// return Location(value['lat'], value['lng']); + /// } + /// + /// @override + /// dynamic set(dynamic model, String key, Location? value, Map attributes) { + /// if (value == null) return null; + /// return {'lat': value.lat, 'lng': value.lng}; + /// } + /// } + /// ``` + dynamic castUsing(List arguments); +} diff --git a/packages/contracts/lib/src/database/eloquent/casts_attributes.dart b/packages/contracts/lib/src/database/eloquent/casts_attributes.dart new file mode 100644 index 0000000..426284d --- /dev/null +++ b/packages/contracts/lib/src/database/eloquent/casts_attributes.dart @@ -0,0 +1,52 @@ +/// Interface for custom attribute casting. +/// +/// This contract defines how model attributes should be cast to and from +/// their database representation. It provides methods for transforming +/// attributes when they are retrieved from or set on a model. +abstract class CastsAttributes { + /// Transform the attribute from the underlying model values. + /// + /// Example: + /// ```dart + /// class JsonCast implements CastsAttributes, String> { + /// @override + /// Map? get( + /// Model model, + /// String key, + /// dynamic value, + /// Map attributes, + /// ) { + /// return value != null ? jsonDecode(value) : null; + /// } + /// } + /// ``` + TGet? get( + dynamic model, + String key, + dynamic value, + Map attributes, + ); + + /// Transform the attribute to its underlying model values. + /// + /// Example: + /// ```dart + /// class JsonCast implements CastsAttributes, String> { + /// @override + /// dynamic set( + /// Model model, + /// String key, + /// String? value, + /// Map attributes, + /// ) { + /// return value != null ? jsonEncode(value) : null; + /// } + /// } + /// ``` + dynamic set( + dynamic model, + String key, + TSet? value, + Map attributes, + ); +} diff --git a/packages/contracts/lib/src/database/eloquent/casts_inbound_attributes.dart b/packages/contracts/lib/src/database/eloquent/casts_inbound_attributes.dart new file mode 100644 index 0000000..1e4a1a3 --- /dev/null +++ b/packages/contracts/lib/src/database/eloquent/casts_inbound_attributes.dart @@ -0,0 +1,29 @@ +/// Interface for inbound attribute casting. +/// +/// This contract defines how model attributes should be cast when they are +/// set on a model. Unlike [CastsAttributes], this interface only handles +/// the transformation of values being set, not retrieved. +abstract class CastsInboundAttributes { + /// Transform the attribute to its underlying model values. + /// + /// Example: + /// ```dart + /// class PasswordCast implements CastsInboundAttributes { + /// @override + /// dynamic set( + /// dynamic model, + /// String key, + /// dynamic value, + /// Map attributes, + /// ) { + /// return value != null ? hashPassword(value) : null; + /// } + /// } + /// ``` + dynamic set( + dynamic model, + String key, + dynamic value, + Map attributes, + ); +} diff --git a/packages/contracts/lib/src/database/eloquent/deviates_castable_attributes.dart b/packages/contracts/lib/src/database/eloquent/deviates_castable_attributes.dart new file mode 100644 index 0000000..a41c793 --- /dev/null +++ b/packages/contracts/lib/src/database/eloquent/deviates_castable_attributes.dart @@ -0,0 +1,56 @@ +/// Interface for incrementing and decrementing castable attributes. +/// +/// This contract defines how model attributes should be modified when +/// performing increment and decrement operations. It allows custom casts +/// to handle these operations in a way that makes sense for their data type. +abstract class DeviatesCastableAttributes { + /// Increment the attribute. + /// + /// Example: + /// ```dart + /// class JsonCounterCast implements DeviatesCastableAttributes { + /// @override + /// dynamic increment( + /// dynamic model, + /// String key, + /// dynamic value, + /// Map attributes, + /// ) { + /// var data = jsonDecode(attributes[key] ?? '{"count": 0}'); + /// data['count'] += value; + /// return jsonEncode(data); + /// } + /// } + /// ``` + dynamic increment( + dynamic model, + String key, + dynamic value, + Map attributes, + ); + + /// Decrement the attribute. + /// + /// Example: + /// ```dart + /// class JsonCounterCast implements DeviatesCastableAttributes { + /// @override + /// dynamic decrement( + /// dynamic model, + /// String key, + /// dynamic value, + /// Map attributes, + /// ) { + /// var data = jsonDecode(attributes[key] ?? '{"count": 0}'); + /// data['count'] -= value; + /// return jsonEncode(data); + /// } + /// } + /// ``` + dynamic decrement( + dynamic model, + String key, + dynamic value, + Map attributes, + ); +} diff --git a/packages/contracts/lib/src/database/eloquent/serializes_castable_attributes.dart b/packages/contracts/lib/src/database/eloquent/serializes_castable_attributes.dart new file mode 100644 index 0000000..12a8e9b --- /dev/null +++ b/packages/contracts/lib/src/database/eloquent/serializes_castable_attributes.dart @@ -0,0 +1,29 @@ +/// Interface for serializing castable attributes. +/// +/// This contract defines how model attributes should be serialized when +/// converting a model to an array or JSON. It allows custom casts to +/// control how their values are represented in array/JSON form. +abstract class SerializesCastableAttributes { + /// Serialize the attribute when converting the model to an array. + /// + /// Example: + /// ```dart + /// class DateCast implements SerializesCastableAttributes { + /// @override + /// dynamic serialize( + /// dynamic model, + /// String key, + /// dynamic value, + /// Map attributes, + /// ) { + /// return value?.toIso8601String(); + /// } + /// } + /// ``` + dynamic serialize( + dynamic model, + String key, + dynamic value, + Map attributes, + ); +} diff --git a/packages/contracts/lib/src/database/eloquent/supports_partial_relations.dart b/packages/contracts/lib/src/database/eloquent/supports_partial_relations.dart new file mode 100644 index 0000000..9846607 --- /dev/null +++ b/packages/contracts/lib/src/database/eloquent/supports_partial_relations.dart @@ -0,0 +1,40 @@ +/// Interface for models that support partial relations. +/// +/// This contract defines how models should handle one-of-many relationships, +/// which are used to retrieve a single record from a one-to-many relationship +/// based on some aggregate condition. +abstract class SupportsPartialRelations { + /// Indicate that the relation is a single result of a larger one-to-many relationship. + /// + /// Example: + /// ```dart + /// // Get the user's latest post + /// user.ofMany('created_at', 'MAX', 'posts'); + /// + /// // Get the user's most expensive order + /// user.ofMany('total', 'MAX', 'orders'); + /// ``` + dynamic ofMany([ + String column = 'id', + String aggregate = 'MAX', + String? relation, + ]); + + /// Determine whether the relationship is a one-of-many relationship. + /// + /// Example: + /// ```dart + /// if (user.latestPost.isOneOfMany()) { + /// // Handle one-of-many relationship + /// } + /// ``` + bool isOneOfMany(); + + /// Get the one of many inner join subselect query builder instance. + /// + /// Example: + /// ```dart + /// var subQuery = user.latestPost.getOneOfManySubQuery(); + /// ``` + dynamic getOneOfManySubQuery(); +} diff --git a/packages/contracts/lib/src/database/events/migration_event.dart b/packages/contracts/lib/src/database/events/migration_event.dart new file mode 100644 index 0000000..ad90293 --- /dev/null +++ b/packages/contracts/lib/src/database/events/migration_event.dart @@ -0,0 +1,4 @@ +/// Interface for migration events. +/// +/// This contract serves as a marker interface for migration events. +abstract class MigrationEvent {} diff --git a/packages/contracts/lib/src/database/model_identifier.dart b/packages/contracts/lib/src/database/model_identifier.dart new file mode 100644 index 0000000..ff57fcb --- /dev/null +++ b/packages/contracts/lib/src/database/model_identifier.dart @@ -0,0 +1,35 @@ +/// Class for model serialization. +/// +/// This class is used to identify models during serialization. +class ModelIdentifier { + /// The class name of the model. + final String className; + + /// The unique identifier of the model. + /// + /// This may be either a single ID or an array of IDs. + final dynamic id; + + /// The relationships loaded on the model. + final List relations; + + /// The connection name of the model. + final String? connection; + + /// The class name of the model collection. + String? collectionClass; + + /// Create a new model identifier. + ModelIdentifier( + this.className, + this.id, + this.relations, + this.connection, + ); + + /// Specify the collection class that should be used when serializing / restoring collections. + ModelIdentifier useCollectionClass(String? collectionClass) { + this.collectionClass = collectionClass; + return this; + } +} diff --git a/packages/contracts/lib/src/database/query/builder.dart b/packages/contracts/lib/src/database/query/builder.dart new file mode 100644 index 0000000..e99833a --- /dev/null +++ b/packages/contracts/lib/src/database/query/builder.dart @@ -0,0 +1,6 @@ +/// Interface for database query builder. +/// +/// This contract serves as a marker interface for query builders. +/// While it doesn't define any methods, it exists to improve IDE support +/// and type safety when working with query builders. +abstract class QueryBuilder {} diff --git a/packages/contracts/lib/src/database/query/condition_expression.dart b/packages/contracts/lib/src/database/query/condition_expression.dart new file mode 100644 index 0000000..9c8fbe9 --- /dev/null +++ b/packages/contracts/lib/src/database/query/condition_expression.dart @@ -0,0 +1,6 @@ +import 'expression.dart'; + +/// Interface for database query condition expressions. +/// +/// This contract serves as a marker interface for condition expressions. +abstract class ConditionExpression extends Expression {} diff --git a/packages/contracts/lib/src/database/query/expression.dart b/packages/contracts/lib/src/database/query/expression.dart new file mode 100644 index 0000000..72d6fd8 --- /dev/null +++ b/packages/contracts/lib/src/database/query/expression.dart @@ -0,0 +1,7 @@ +/// Interface for database query expressions. +/// +/// This contract defines how raw SQL expressions should be handled. +abstract class Expression { + /// Get the value of the expression. + dynamic getValue(dynamic grammar); +} diff --git a/packages/contracts/lib/src/debug/exception_handler.dart b/packages/contracts/lib/src/debug/exception_handler.dart new file mode 100644 index 0000000..97d931a --- /dev/null +++ b/packages/contracts/lib/src/debug/exception_handler.dart @@ -0,0 +1,25 @@ +import 'dart:async'; +import 'package:meta/meta.dart'; + +/// Interface for handling exceptions. +abstract class ExceptionHandler { + /// Report or log an exception. + /// + /// @throws Exception + FutureOr report(Object error, [StackTrace? stackTrace]); + + /// Determine if the exception should be reported. + bool shouldReport(Object error); + + /// Render an exception into an HTTP response. + /// + /// @throws Exception + FutureOr render(dynamic request, Object error, + [StackTrace? stackTrace]); + + /// Render an exception to the console. + /// + /// This method is not meant to be used or overwritten outside the framework. + @protected + void renderForConsole(dynamic output, Object error, [StackTrace? stackTrace]); +} diff --git a/packages/contracts/lib/src/encryption/decrypt_exception.dart b/packages/contracts/lib/src/encryption/decrypt_exception.dart new file mode 100644 index 0000000..41ef9de --- /dev/null +++ b/packages/contracts/lib/src/encryption/decrypt_exception.dart @@ -0,0 +1,2 @@ +/// Exception thrown during decryption. +class DecryptException implements Exception {} diff --git a/packages/contracts/lib/src/encryption/encrypt_exception.dart b/packages/contracts/lib/src/encryption/encrypt_exception.dart new file mode 100644 index 0000000..1398347 --- /dev/null +++ b/packages/contracts/lib/src/encryption/encrypt_exception.dart @@ -0,0 +1,2 @@ +/// Exception thrown during encryption. +class EncryptException implements Exception {} diff --git a/packages/contracts/lib/src/encryption/encrypter.dart b/packages/contracts/lib/src/encryption/encrypter.dart new file mode 100644 index 0000000..e0310fa --- /dev/null +++ b/packages/contracts/lib/src/encryption/encrypter.dart @@ -0,0 +1,21 @@ +/// Interface for encryption. +abstract class Encrypter { + /// Encrypt the given value. + /// + /// @throws EncryptException + String encrypt(dynamic value, [bool serialize = true]); + + /// Decrypt the given value. + /// + /// @throws DecryptException + dynamic decrypt(String payload, [bool unserialize = true]); + + /// Get the encryption key that the encrypter is currently using. + String getKey(); + + /// Get the current encryption key and all previous encryption keys. + List getAllKeys(); + + /// Get the previous encryption keys. + List getPreviousKeys(); +} diff --git a/packages/contracts/lib/src/encryption/string_encrypter.dart b/packages/contracts/lib/src/encryption/string_encrypter.dart new file mode 100644 index 0000000..54e8b89 --- /dev/null +++ b/packages/contracts/lib/src/encryption/string_encrypter.dart @@ -0,0 +1,12 @@ +/// Interface for string encryption. +abstract class StringEncrypter { + /// Encrypt a string without serialization. + /// + /// @throws EncryptException + String encryptString(String value); + + /// Decrypt the given string without unserialization. + /// + /// @throws DecryptException + String decryptString(String payload); +} diff --git a/packages/contracts/lib/src/events/dispatcher.dart b/packages/contracts/lib/src/events/dispatcher.dart new file mode 100644 index 0000000..8420763 --- /dev/null +++ b/packages/contracts/lib/src/events/dispatcher.dart @@ -0,0 +1,30 @@ +/// Interface for event dispatching. +abstract class Dispatcher { + /// Register an event listener with the dispatcher. + void listen(dynamic events, [dynamic listener]); + + /// Determine if a given event has listeners. + bool hasListeners(String eventName); + + /// Register an event subscriber with the dispatcher. + void subscribe(dynamic subscriber); + + /// Dispatch an event until the first non-null response is returned. + dynamic until(dynamic event, [dynamic payload = const []]); + + /// Dispatch an event and call the listeners. + List? dispatch(dynamic event, + [dynamic payload = const [], bool halt = false]); + + /// Register an event and payload to be fired later. + void push(String event, [List payload = const []]); + + /// Flush a set of pushed events. + void flush(String event); + + /// Remove a set of listeners from the dispatcher. + void forget(String event); + + /// Forget all of the queued listeners. + void forgetPushed(); +} 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 2fda513..0000000 --- a/packages/contracts/lib/src/events/event_dispatcher_contract.dart +++ /dev/null @@ -1,193 +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. -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. -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. -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. -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. -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. -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/events/should_dispatch_after_commit.dart b/packages/contracts/lib/src/events/should_dispatch_after_commit.dart new file mode 100644 index 0000000..9abfc88 --- /dev/null +++ b/packages/contracts/lib/src/events/should_dispatch_after_commit.dart @@ -0,0 +1,5 @@ +/// Interface for events that should be dispatched after database commit. +/// +/// This contract serves as a marker interface for events that should +/// only be dispatched after their database transaction has been committed. +abstract class ShouldDispatchAfterCommit {} diff --git a/packages/contracts/lib/src/events/should_handle_events_after_commit.dart b/packages/contracts/lib/src/events/should_handle_events_after_commit.dart new file mode 100644 index 0000000..2e486fb --- /dev/null +++ b/packages/contracts/lib/src/events/should_handle_events_after_commit.dart @@ -0,0 +1,5 @@ +/// Interface for events that should be handled after database commit. +/// +/// This contract serves as a marker interface for events that should +/// only be handled after their database transaction has been committed. +abstract class ShouldHandleEventsAfterCommit {} diff --git a/packages/contracts/lib/src/filesystem/cloud.dart b/packages/contracts/lib/src/filesystem/cloud.dart new file mode 100644 index 0000000..27ee352 --- /dev/null +++ b/packages/contracts/lib/src/filesystem/cloud.dart @@ -0,0 +1,7 @@ +import 'filesystem.dart'; + +/// Interface for cloud filesystem operations. +abstract class Cloud extends Filesystem { + /// Get the URL for the file at the given path. + String url(String path); +} diff --git a/packages/contracts/lib/src/filesystem/file_not_found_exception.dart b/packages/contracts/lib/src/filesystem/file_not_found_exception.dart new file mode 100644 index 0000000..d408d22 --- /dev/null +++ b/packages/contracts/lib/src/filesystem/file_not_found_exception.dart @@ -0,0 +1,2 @@ +/// Exception thrown when a file is not found. +class FileNotFoundException implements Exception {} diff --git a/packages/contracts/lib/src/filesystem/filesystem.dart b/packages/contracts/lib/src/filesystem/filesystem.dart new file mode 100644 index 0000000..2b1321b --- /dev/null +++ b/packages/contracts/lib/src/filesystem/filesystem.dart @@ -0,0 +1,83 @@ +import 'dart:async'; + +/// Interface for filesystem operations. +abstract class Filesystem { + /// The public visibility setting. + static const String visibilityPublic = 'public'; + + /// The private visibility setting. + static const String visibilityPrivate = 'private'; + + /// Get the full path to the file at the given relative path. + String path(String path); + + /// Determine if a file exists. + Future exists(String path); + + /// Get the contents of a file. + Future get(String path); + + /// Get a resource to read the file. + Future>?> readStream(String path); + + /// Write the contents of a file. + Future put(String path, dynamic contents, + [Map options = const {}]); + + /// Store the uploaded file on the disk. + Future putFile(String path, + [dynamic file, Map options = const {}]); + + /// Store the uploaded file on the disk with a given name. + Future putFileAs(String path, dynamic file, + [String? name, Map options = const {}]); + + /// Write a new file using a stream. + Future writeStream(String path, Stream> resource, + [Map options = const {}]); + + /// Get the visibility for the given path. + Future getVisibility(String path); + + /// Set the visibility for the given path. + Future setVisibility(String path, String visibility); + + /// Prepend to a file. + Future prepend(String path, String data); + + /// Append to a file. + Future append(String path, String data); + + /// Delete the file at a given path. + Future delete(dynamic paths); + + /// Copy a file to a new location. + Future copy(String from, String to); + + /// Move a file to a new location. + Future move(String from, String to); + + /// Get the file size of a given file. + Future size(String path); + + /// Get the file's last modification time. + Future lastModified(String path); + + /// Get an array of all files in a directory. + Future> files([String? directory, bool recursive = false]); + + /// Get all of the files from the given directory (recursive). + Future> allFiles([String? directory]); + + /// Get all of the directories within a given directory. + Future> directories([String? directory, bool recursive = false]); + + /// Get all (recursive) of the directories within a given directory. + Future> allDirectories([String? directory]); + + /// Create a directory. + Future makeDirectory(String path); + + /// Recursively delete a directory. + Future deleteDirectory(String directory); +} diff --git a/packages/contracts/lib/src/filesystem/filesystem_factory.dart b/packages/contracts/lib/src/filesystem/filesystem_factory.dart new file mode 100644 index 0000000..95ff64b --- /dev/null +++ b/packages/contracts/lib/src/filesystem/filesystem_factory.dart @@ -0,0 +1,7 @@ +import 'filesystem.dart'; + +/// Interface for filesystem factory. +abstract class FilesystemFactory { + /// Get a filesystem implementation. + Filesystem disk([String? name]); +} diff --git a/packages/contracts/lib/src/filesystem/lock_timeout_exception.dart b/packages/contracts/lib/src/filesystem/lock_timeout_exception.dart new file mode 100644 index 0000000..1dfee63 --- /dev/null +++ b/packages/contracts/lib/src/filesystem/lock_timeout_exception.dart @@ -0,0 +1,2 @@ +/// Exception thrown when a filesystem lock operation times out. +class LockTimeoutException implements Exception {} diff --git a/packages/contracts/lib/src/foundation/application.dart b/packages/contracts/lib/src/foundation/application.dart new file mode 100644 index 0000000..f30013b --- /dev/null +++ b/packages/contracts/lib/src/foundation/application.dart @@ -0,0 +1,100 @@ +import '../container/container.dart'; + +/// Interface for the application. +abstract class Application extends Container { + /// Get the version number of the application. + String version(); + + /// Get the base path of the installation. + String basePath([String path = '']); + + /// Get the path to the bootstrap directory. + String bootstrapPath([String path = '']); + + /// Get the path to the application configuration files. + String configPath([String path = '']); + + /// Get the path to the database directory. + String databasePath([String path = '']); + + /// Get the path to the language files. + String langPath([String path = '']); + + /// Get the path to the public directory. + String publicPath([String path = '']); + + /// Get the path to the resources directory. + String resourcePath([String path = '']); + + /// Get the path to the storage directory. + String storagePath([String path = '']); + + /// Get or check the current application environment. + dynamic environment(List environments); + + /// Determine if the application is running in the console. + bool runningInConsole(); + + /// Determine if the application is running unit tests. + bool runningUnitTests(); + + /// Determine if the application is running with debug mode enabled. + bool hasDebugModeEnabled(); + + /// Get an instance of the maintenance mode manager implementation. + dynamic maintenanceMode(); + + /// Determine if the application is currently down for maintenance. + bool isDownForMaintenance(); + + /// Register all of the configured providers. + void registerConfiguredProviders(); + + /// Register a service provider with the application. + dynamic register(dynamic provider, [bool force = false]); + + /// Register a deferred provider and service. + void registerDeferredProvider(String provider, [String? service]); + + /// Resolve a service provider instance from the class name. + dynamic resolveProvider(String provider); + + /// Boot the application's service providers. + void boot(); + + /// Register a new boot listener. + void booting(Function callback); + + /// Register a new "booted" listener. + void booted(Function callback); + + /// Run the given array of bootstrap classes. + void bootstrapWith(List bootstrappers); + + /// Get the current application locale. + String getLocale(); + + /// Get the application namespace. + String getNamespace(); + + /// Get the registered service provider instances if any exist. + List getProviders(dynamic provider); + + /// Determine if the application has been bootstrapped before. + bool hasBeenBootstrapped(); + + /// Load and boot all of the remaining deferred providers. + void loadDeferredProviders(); + + /// Set the current application locale. + void setLocale(String locale); + + /// Determine if middleware has been disabled for the application. + bool shouldSkipMiddleware(); + + /// Register a terminating callback with the application. + Application terminating(dynamic callback); + + /// Terminate the application. + void terminate(); +} diff --git a/packages/contracts/lib/src/foundation/caches_configuration.dart b/packages/contracts/lib/src/foundation/caches_configuration.dart new file mode 100644 index 0000000..69107d9 --- /dev/null +++ b/packages/contracts/lib/src/foundation/caches_configuration.dart @@ -0,0 +1,11 @@ +/// Interface for configuration caching. +abstract class CachesConfiguration { + /// Determine if the application configuration is cached. + bool configurationIsCached(); + + /// Get the path to the configuration cache file. + String getCachedConfigPath(); + + /// Get the path to the cached services.php file. + String getCachedServicesPath(); +} diff --git a/packages/contracts/lib/src/foundation/caches_routes.dart b/packages/contracts/lib/src/foundation/caches_routes.dart new file mode 100644 index 0000000..02b40f9 --- /dev/null +++ b/packages/contracts/lib/src/foundation/caches_routes.dart @@ -0,0 +1,8 @@ +/// Interface for route caching. +abstract class CachesRoutes { + /// Determine if the application routes are cached. + bool routesAreCached(); + + /// Get the path to the routes cache file. + String getCachedRoutesPath(); +} diff --git a/packages/contracts/lib/src/foundation/exception_renderer.dart b/packages/contracts/lib/src/foundation/exception_renderer.dart new file mode 100644 index 0000000..9a22e2f --- /dev/null +++ b/packages/contracts/lib/src/foundation/exception_renderer.dart @@ -0,0 +1,5 @@ +/// Interface for rendering exceptions as HTML. +abstract class ExceptionRenderer { + /// Renders the given exception as HTML. + String render(Object throwable); +} diff --git a/packages/contracts/lib/src/foundation/maintenance_mode.dart b/packages/contracts/lib/src/foundation/maintenance_mode.dart new file mode 100644 index 0000000..f9423dc --- /dev/null +++ b/packages/contracts/lib/src/foundation/maintenance_mode.dart @@ -0,0 +1,14 @@ +/// Interface for maintenance mode management. +abstract class MaintenanceMode { + /// Take the application down for maintenance. + void activate(Map payload); + + /// Take the application out of maintenance. + void deactivate(); + + /// Determine if the application is currently down for maintenance. + bool active(); + + /// Get the data array which was provided when the application was placed into maintenance. + Map data(); +} diff --git a/packages/contracts/lib/src/hashing/hasher.dart b/packages/contracts/lib/src/hashing/hasher.dart new file mode 100644 index 0000000..484275c --- /dev/null +++ b/packages/contracts/lib/src/hashing/hasher.dart @@ -0,0 +1,16 @@ +/// Interface for hashing. +abstract class Hasher { + /// Get information about the given hashed value. + Map info(String hashedValue); + + /// Hash the given value. + String make(String value, [Map options = const {}]); + + /// Check the given plain value against a hash. + bool check(String value, String hashedValue, + [Map options = const {}]); + + /// Check if the given hash has been hashed using the given options. + bool needsRehash(String hashedValue, + [Map options = const {}]); +} 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/http/kernel.dart b/packages/contracts/lib/src/http/kernel.dart new file mode 100644 index 0000000..2c222c7 --- /dev/null +++ b/packages/contracts/lib/src/http/kernel.dart @@ -0,0 +1,16 @@ +import '../foundation/application.dart'; + +/// Interface for HTTP kernel. +abstract class Kernel { + /// Bootstrap the application for HTTP requests. + void bootstrap(); + + /// Handle an incoming HTTP request. + dynamic handle(dynamic request); + + /// Perform any final actions for the request lifecycle. + void terminate(dynamic request, dynamic response); + + /// Get the application instance. + Application getApplication(); +} diff --git a/packages/contracts/lib/src/http/request.dart b/packages/contracts/lib/src/http/request.dart new file mode 100644 index 0000000..a1e3f71 --- /dev/null +++ b/packages/contracts/lib/src/http/request.dart @@ -0,0 +1,32 @@ +/// Abstract representation of an HTTP request. +/// +/// This class serves as a base contract for HTTP requests across the framework. +/// Concrete implementations will provide the actual request handling logic. +abstract class Request { + /// Get the request method. + String get method; + + /// Get the request URI. + Uri get uri; + + /// Get all request headers. + Map> get headers; + + /// Get the request body. + dynamic get body; + + /// Get a request header value. + String? header(String name); + + /// Get a query parameter value. + String? query(String name); + + /// Get all query parameters. + Map get queryParameters; + + /// Determine if the request is AJAX. + bool get isAjax; + + /// Determine if the request expects JSON. + bool get expectsJson; +} diff --git a/packages/contracts/lib/src/http/response.dart b/packages/contracts/lib/src/http/response.dart new file mode 100644 index 0000000..cee3a77 --- /dev/null +++ b/packages/contracts/lib/src/http/response.dart @@ -0,0 +1,48 @@ +/// Abstract representation of an HTTP response. +/// +/// This class serves as a base contract for HTTP responses across the framework. +/// Concrete implementations will provide the actual response handling logic. +abstract class Response { + /// Get the response status code. + int get statusCode; + + /// Set the response status code. + set statusCode(int value); + + /// Get all response headers. + Map> get headers; + + /// Get the response body. + dynamic get body; + + /// Set the response body. + set body(dynamic value); + + /// Set a response header. + void header(String name, String value); + + /// Remove a response header. + void removeHeader(String name); + + /// Set the content type header. + void contentType(String value); + + /// Get a response header value. + String? getHeader(String name); + + /// Determine if the response has a given header. + bool hasHeader(String name); + + /// Set the response content. + void setContent(dynamic content); + + /// Get the response content. + dynamic getContent(); + + /// Convert the response to bytes. + List toBytes(); + + /// Convert the response to a string. + @override + String toString(); +} diff --git a/packages/contracts/lib/src/mail/attachable.dart b/packages/contracts/lib/src/mail/attachable.dart new file mode 100644 index 0000000..ce45f0d --- /dev/null +++ b/packages/contracts/lib/src/mail/attachable.dart @@ -0,0 +1,5 @@ +/// Interface for mail attachments. +abstract class Attachable { + /// Get an attachment instance for this entity. + dynamic toMailAttachment(); +} diff --git a/packages/contracts/lib/src/mail/mail_factory.dart b/packages/contracts/lib/src/mail/mail_factory.dart new file mode 100644 index 0000000..207da00 --- /dev/null +++ b/packages/contracts/lib/src/mail/mail_factory.dart @@ -0,0 +1,7 @@ +import 'mailer.dart'; + +/// Interface for mail factory. +abstract class MailFactory { + /// Get a mailer instance by name. + Mailer mailer([String? name]); +} diff --git a/packages/contracts/lib/src/mail/mail_queue.dart b/packages/contracts/lib/src/mail/mail_queue.dart new file mode 100644 index 0000000..83bcdd9 --- /dev/null +++ b/packages/contracts/lib/src/mail/mail_queue.dart @@ -0,0 +1,8 @@ +/// Interface for queued mail sending. +abstract class MailQueue { + /// Queue a new e-mail message for sending. + dynamic queue(dynamic view, [String? queue]); + + /// Queue a new e-mail message for sending after (n) seconds. + dynamic later(dynamic delay, dynamic view, [String? queue]); +} diff --git a/packages/contracts/lib/src/mail/mailable.dart b/packages/contracts/lib/src/mail/mailable.dart new file mode 100644 index 0000000..222422e --- /dev/null +++ b/packages/contracts/lib/src/mail/mailable.dart @@ -0,0 +1,26 @@ +/// Interface for mail messages. +abstract class Mailable { + /// Send the message using the given mailer. + dynamic send(dynamic mailer); + + /// Queue the given message. + dynamic queue(dynamic queue); + + /// Deliver the queued message after (n) seconds. + dynamic later(dynamic delay, dynamic queue); + + /// Set the CC recipients of the message. + Mailable cc(dynamic address, [String? name]); + + /// Set the BCC recipients of the message. + Mailable bcc(dynamic address, [String? name]); + + /// Set the recipients of the message. + Mailable to(dynamic address, [String? name]); + + /// Set the locale of the message. + Mailable locale(String locale); + + /// Set the name of the mailer that should be used to send the message. + Mailable mailer(String mailer); +} diff --git a/packages/contracts/lib/src/mail/mailer.dart b/packages/contracts/lib/src/mail/mailer.dart new file mode 100644 index 0000000..4c953f5 --- /dev/null +++ b/packages/contracts/lib/src/mail/mailer.dart @@ -0,0 +1,19 @@ +/// Interface for mail sending. +abstract class Mailer { + /// Begin the process of mailing a mailable class instance. + dynamic to(dynamic users); + + /// Begin the process of mailing a mailable class instance. + dynamic bcc(dynamic users); + + /// Send a new message with only a raw text part. + dynamic raw(String text, dynamic callback); + + /// Send a new message using a view. + dynamic send(dynamic view, + [Map data = const {}, dynamic callback]); + + /// Send a new message synchronously using a view. + dynamic sendNow(dynamic mailable, + [Map data = const {}, dynamic callback]); +} 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/notifications/dispatcher.dart b/packages/contracts/lib/src/notifications/dispatcher.dart new file mode 100644 index 0000000..cf1a263 --- /dev/null +++ b/packages/contracts/lib/src/notifications/dispatcher.dart @@ -0,0 +1,9 @@ +/// Interface for notification dispatching. +abstract class Dispatcher { + /// Send the given notification to the given notifiable entities. + void send(dynamic notifiables, dynamic notification); + + /// Send the given notification immediately. + void sendNow(dynamic notifiables, dynamic notification, + [List? channels]); +} diff --git a/packages/contracts/lib/src/notifications/factory.dart b/packages/contracts/lib/src/notifications/factory.dart new file mode 100644 index 0000000..30d9cd3 --- /dev/null +++ b/packages/contracts/lib/src/notifications/factory.dart @@ -0,0 +1,11 @@ +/// Interface for notification factory. +abstract class Factory { + /// Get a channel instance by name. + dynamic channel([String? name]); + + /// Send the given notification to the given notifiable entities. + void send(dynamic notifiables, dynamic notification); + + /// Send the given notification immediately. + void sendNow(dynamic notifiables, dynamic notification); +} diff --git a/packages/contracts/lib/src/pagination/cursor_paginator.dart b/packages/contracts/lib/src/pagination/cursor_paginator.dart new file mode 100644 index 0000000..da0a59f --- /dev/null +++ b/packages/contracts/lib/src/pagination/cursor_paginator.dart @@ -0,0 +1,50 @@ +/// Interface for cursor-based pagination. +abstract class CursorPaginator { + /// Get the URL for a given cursor. + String url(dynamic cursor); + + /// Add a set of query string values to the paginator. + CursorPaginator appends(dynamic key, [String? value]); + + /// Get / set the URL fragment to be appended to URLs. + dynamic fragment([String? fragment]); + + /// Add all current query string values to the paginator. + CursorPaginator withQueryString(); + + /// Get the URL for the previous page, or null. + String? previousPageUrl(); + + /// The URL for the next page, or null. + String? nextPageUrl(); + + /// Get all of the items being paginated. + List items(); + + /// Get the "cursor" of the previous set of items. + dynamic previousCursor(); + + /// Get the "cursor" of the next set of items. + dynamic nextCursor(); + + /// Determine how many items are being shown per page. + int perPage(); + + /// Get the current cursor being paginated. + dynamic cursor(); + + /// Determine if there are enough items to split into multiple pages. + bool hasPages(); + + /// Get the base path for paginator generated URLs. + String? path(); + + /// Determine if the list of items is empty or not. + bool isEmpty(); + + /// Determine if the list of items is not empty. + bool isNotEmpty(); + + /// Render the paginator using a given view. + String render([String? view, Map data = const {}]); +} diff --git a/packages/contracts/lib/src/pagination/length_aware_paginator.dart b/packages/contracts/lib/src/pagination/length_aware_paginator.dart new file mode 100644 index 0000000..bda3a2a --- /dev/null +++ b/packages/contracts/lib/src/pagination/length_aware_paginator.dart @@ -0,0 +1,13 @@ +import 'paginator.dart'; + +/// Interface for pagination with total count awareness. +abstract class LengthAwarePaginator extends Paginator { + /// Create a range of pagination URLs. + List getUrlRange(int start, int end); + + /// Determine the total number of items in the data store. + int total(); + + /// Get the page number of the last available page. + int lastPage(); +} diff --git a/packages/contracts/lib/src/pagination/paginator.dart b/packages/contracts/lib/src/pagination/paginator.dart new file mode 100644 index 0000000..b49056a --- /dev/null +++ b/packages/contracts/lib/src/pagination/paginator.dart @@ -0,0 +1,50 @@ +/// Interface for pagination. +abstract class Paginator { + /// Get the URL for a given page. + String url(int page); + + /// Add a set of query string values to the paginator. + Paginator appends(dynamic key, [String? value]); + + /// Get / set the URL fragment to be appended to URLs. + dynamic fragment([String? fragment]); + + /// The URL for the next page, or null. + String? nextPageUrl(); + + /// Get the URL for the previous page, or null. + String? previousPageUrl(); + + /// Get all of the items being paginated. + List items(); + + /// Get the "index" of the first item being paginated. + int? firstItem(); + + /// Get the "index" of the last item being paginated. + int? lastItem(); + + /// Determine how many items are being shown per page. + int perPage(); + + /// Determine the current page being paginated. + int currentPage(); + + /// Determine if there are enough items to split into multiple pages. + bool hasPages(); + + /// Determine if there are more items in the data store. + bool hasMorePages(); + + /// Get the base path for paginator generated URLs. + String? path(); + + /// Determine if the list of items is empty or not. + bool isEmpty(); + + /// Determine if the list of items is not empty. + bool isNotEmpty(); + + /// Render the paginator using a given view. + String render([String? view, Map data = const {}]); +} diff --git a/packages/contracts/lib/src/pipeline/hub.dart b/packages/contracts/lib/src/pipeline/hub.dart new file mode 100644 index 0000000..44f7fa9 --- /dev/null +++ b/packages/contracts/lib/src/pipeline/hub.dart @@ -0,0 +1,5 @@ +/// Interface for pipeline management. +abstract class Hub { + /// Send an object through one of the available pipelines. + dynamic pipe(dynamic object, [String? pipeline]); +} diff --git a/packages/contracts/lib/src/pipeline/pipeline.dart b/packages/contracts/lib/src/pipeline/pipeline.dart index a834069..d7995e3 100644 --- a/packages/contracts/lib/src/pipeline/pipeline.dart +++ b/packages/contracts/lib/src/pipeline/pipeline.dart @@ -1,2 +1,14 @@ -/// Pipeline package contracts -export 'pipeline_contract.dart'; +/// Interface for pipeline processing. +abstract class Pipeline { + /// Set the traveler object being sent on the pipeline. + Pipeline send(dynamic traveler); + + /// Set the stops of the pipeline. + Pipeline through(dynamic stops); + + /// Set the method to call on the stops. + Pipeline via(String method); + + /// Run the pipeline with a final destination callback. + dynamic then(Function destination); +} 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/invoked_process.dart b/packages/contracts/lib/src/process/invoked_process.dart new file mode 100644 index 0000000..17bb295 --- /dev/null +++ b/packages/contracts/lib/src/process/invoked_process.dart @@ -0,0 +1,28 @@ +import 'process_result.dart'; + +/// Interface for running processes. +abstract class InvokedProcess { + /// Get the process ID if the process is still running. + int? id(); + + /// Send a signal to the process. + InvokedProcess signal(int signal); + + /// Determine if the process is still running. + bool running(); + + /// Get the standard output for the process. + String output(); + + /// Get the error output for the process. + String errorOutput(); + + /// Get the latest standard output for the process. + String latestOutput(); + + /// Get the latest error output for the process. + String latestErrorOutput(); + + /// Wait for the process to finish. + ProcessResult wait([Function? output]); +} 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/process/process_result.dart b/packages/contracts/lib/src/process/process_result.dart new file mode 100644 index 0000000..bc3e7c5 --- /dev/null +++ b/packages/contracts/lib/src/process/process_result.dart @@ -0,0 +1,32 @@ +/// Interface for process execution results. +abstract class ProcessResult { + /// Get the original command executed by the process. + String command(); + + /// Determine if the process was successful. + bool successful(); + + /// Determine if the process failed. + bool failed(); + + /// Get the exit code of the process. + int? exitCode(); + + /// Get the standard output of the process. + String output(); + + /// Determine if the output contains the given string. + bool seeInOutput(String output); + + /// Get the error output of the process. + String errorOutput(); + + /// Determine if the error output contains the given string. + bool seeInErrorOutput(String output); + + /// Throw an exception if the process failed. + ProcessResult throwException([Function? callback]); + + /// Throw an exception if the process failed and the given condition is true. + ProcessResult throwIf(bool condition, [Function? callback]); +} diff --git a/packages/contracts/lib/src/queue/clearable_queue.dart b/packages/contracts/lib/src/queue/clearable_queue.dart new file mode 100644 index 0000000..4bfd14e --- /dev/null +++ b/packages/contracts/lib/src/queue/clearable_queue.dart @@ -0,0 +1,5 @@ +/// Interface for queues that can be cleared. +abstract class ClearableQueue { + /// Delete all of the jobs from the queue. + int clear(String queue); +} diff --git a/packages/contracts/lib/src/queue/entity_not_found_exception.dart b/packages/contracts/lib/src/queue/entity_not_found_exception.dart new file mode 100644 index 0000000..fbff691 --- /dev/null +++ b/packages/contracts/lib/src/queue/entity_not_found_exception.dart @@ -0,0 +1,14 @@ +/// Exception thrown when a queued entity cannot be found. +class EntityNotFoundException implements Exception { + /// The class name of the entity. + final String type; + + /// The ID of the entity. + final dynamic id; + + /// Create a new entity not found exception. + EntityNotFoundException(this.type, this.id); + + @override + String toString() => 'No query results for model [$type] $id'; +} diff --git a/packages/contracts/lib/src/queue/entity_resolver.dart b/packages/contracts/lib/src/queue/entity_resolver.dart new file mode 100644 index 0000000..3469b3f --- /dev/null +++ b/packages/contracts/lib/src/queue/entity_resolver.dart @@ -0,0 +1,5 @@ +/// Interface for resolving queue entities. +abstract class EntityResolver { + /// Resolve the entity for the given ID. + dynamic resolve(String type, dynamic id); +} diff --git a/packages/contracts/lib/src/queue/job.dart b/packages/contracts/lib/src/queue/job.dart new file mode 100644 index 0000000..28df16b --- /dev/null +++ b/packages/contracts/lib/src/queue/job.dart @@ -0,0 +1,68 @@ +/// Interface for queue jobs. +abstract class Job { + /// Get the UUID of the job. + String? uuid(); + + /// Get the job identifier. + String getJobId(); + + /// Get the decoded body of the job. + Map payload(); + + /// Fire the job. + void fire(); + + /// Release the job back into the queue after (n) seconds. + void release([int delay = 0]); + + /// Determine if the job was released back into the queue. + bool isReleased(); + + /// Delete the job from the queue. + void delete(); + + /// Determine if the job has been deleted. + bool isDeleted(); + + /// Determine if the job has been deleted or released. + bool isDeletedOrReleased(); + + /// Get the number of times the job has been attempted. + int attempts(); + + /// Determine if the job has been marked as a failure. + bool hasFailed(); + + /// Mark the job as "failed". + void markAsFailed(); + + /// Delete the job, call the "failed" method, and raise the failed job event. + void fail([dynamic error]); + + /// Get the number of times to attempt a job. + int? maxTries(); + + /// Get the maximum number of exceptions allowed, regardless of attempts. + int? maxExceptions(); + + /// Get the number of seconds the job can run. + int? timeout(); + + /// Get the timestamp indicating when the job should timeout. + int? retryUntil(); + + /// Get the name of the queued job class. + String getName(); + + /// Get the resolved name of the queued job class. + String resolveName(); + + /// Get the name of the connection the job belongs to. + String getConnectionName(); + + /// Get the name of the queue the job belongs to. + String getQueue(); + + /// Get the raw body string for the job. + String getRawBody(); +} diff --git a/packages/contracts/lib/src/queue/monitor.dart b/packages/contracts/lib/src/queue/monitor.dart new file mode 100644 index 0000000..2a97592 --- /dev/null +++ b/packages/contracts/lib/src/queue/monitor.dart @@ -0,0 +1,11 @@ +/// Interface for queue monitoring. +abstract class Monitor { + /// Register a callback to be executed on every iteration through the queue loop. + void looping(dynamic callback); + + /// Register a callback to be executed when a job fails after the maximum number of retries. + void failing(dynamic callback); + + /// Register a callback to be executed when a daemon queue is stopping. + void stopping(dynamic callback); +} diff --git a/packages/contracts/lib/src/queue/queue.dart b/packages/contracts/lib/src/queue/queue.dart index 2501fd2..5e5d86a 100644 --- a/packages/contracts/lib/src/queue/queue.dart +++ b/packages/contracts/lib/src/queue/queue.dart @@ -1,2 +1,34 @@ -/// Queue package contracts -export 'queue_contract.dart'; +/// Interface for queue management. +abstract class Queue { + /// Get the size of the queue. + int size([String? queue]); + + /// Push a new job onto the queue. + dynamic push(dynamic job, [dynamic data = '', String? queue]); + + /// Push a new job onto the queue. + dynamic pushOn(String queue, dynamic job, [dynamic data = '']); + + /// Push a raw payload onto the queue. + dynamic pushRaw(String payload, + [String? queue, Map options = const {}]); + + /// Push a new job onto the queue after (n) seconds. + dynamic later(dynamic delay, dynamic job, [dynamic data = '', String? queue]); + + /// Push a new job onto a specific queue after (n) seconds. + dynamic laterOn(String queue, dynamic delay, dynamic job, + [dynamic data = '']); + + /// Push an array of jobs onto the queue. + dynamic bulk(List jobs, [dynamic data = '', String? queue]); + + /// Pop the next job off of the queue. + dynamic pop([String? queue]); + + /// Get the connection name for the queue. + String getConnectionName(); + + /// Set the connection name for the queue. + Queue setConnectionName(String name); +} 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/queue/queue_factory.dart b/packages/contracts/lib/src/queue/queue_factory.dart new file mode 100644 index 0000000..ce92972 --- /dev/null +++ b/packages/contracts/lib/src/queue/queue_factory.dart @@ -0,0 +1,7 @@ +import 'queue.dart'; + +/// Interface for queue factory. +abstract class QueueFactory { + /// Resolve a queue connection instance. + Queue connection([String? name]); +} diff --git a/packages/contracts/lib/src/queue/queueable_collection.dart b/packages/contracts/lib/src/queue/queueable_collection.dart new file mode 100644 index 0000000..4f253a8 --- /dev/null +++ b/packages/contracts/lib/src/queue/queueable_collection.dart @@ -0,0 +1,14 @@ +/// Interface for queueable collections. +abstract class QueueableCollection { + /// Get the type of the entities being queued. + String? getQueueableClass(); + + /// Get the identifiers for all of the entities. + List getQueueableIds(); + + /// Get the relationships of the entities being queued. + List getQueueableRelations(); + + /// Get the connection of the entities being queued. + String? getQueueableConnection(); +} diff --git a/packages/contracts/lib/src/queue/queueable_entity.dart b/packages/contracts/lib/src/queue/queueable_entity.dart new file mode 100644 index 0000000..b15d3a2 --- /dev/null +++ b/packages/contracts/lib/src/queue/queueable_entity.dart @@ -0,0 +1,11 @@ +/// Interface for queueable entities. +abstract class QueueableEntity { + /// Get the queueable identity for the entity. + dynamic getQueueableId(); + + /// Get the relationships for the entity. + List getQueueableRelations(); + + /// Get the connection of the entity. + String? getQueueableConnection(); +} diff --git a/packages/contracts/lib/src/queue/should_be_encrypted.dart b/packages/contracts/lib/src/queue/should_be_encrypted.dart new file mode 100644 index 0000000..23f099f --- /dev/null +++ b/packages/contracts/lib/src/queue/should_be_encrypted.dart @@ -0,0 +1,2 @@ +/// Marker interface to indicate that a queued job should be encrypted. +abstract class ShouldBeEncrypted {} diff --git a/packages/contracts/lib/src/queue/should_be_unique.dart b/packages/contracts/lib/src/queue/should_be_unique.dart new file mode 100644 index 0000000..f926449 --- /dev/null +++ b/packages/contracts/lib/src/queue/should_be_unique.dart @@ -0,0 +1,2 @@ +/// Marker interface to indicate that a queued job should be unique. +abstract class ShouldBeUnique {} diff --git a/packages/contracts/lib/src/queue/should_be_unique_until_processing.dart b/packages/contracts/lib/src/queue/should_be_unique_until_processing.dart new file mode 100644 index 0000000..3a84a7a --- /dev/null +++ b/packages/contracts/lib/src/queue/should_be_unique_until_processing.dart @@ -0,0 +1,4 @@ +import 'should_be_unique.dart'; + +/// Marker interface to indicate that a queued job should be unique until processing begins. +abstract class ShouldBeUniqueUntilProcessing extends ShouldBeUnique {} diff --git a/packages/contracts/lib/src/queue/should_queue.dart b/packages/contracts/lib/src/queue/should_queue.dart new file mode 100644 index 0000000..e3ada9a --- /dev/null +++ b/packages/contracts/lib/src/queue/should_queue.dart @@ -0,0 +1,2 @@ +/// Marker interface to indicate that a class should be queued. +abstract class ShouldQueue {} diff --git a/packages/contracts/lib/src/queue/should_queue_after_commit.dart b/packages/contracts/lib/src/queue/should_queue_after_commit.dart new file mode 100644 index 0000000..16dbd7d --- /dev/null +++ b/packages/contracts/lib/src/queue/should_queue_after_commit.dart @@ -0,0 +1,4 @@ +import 'should_queue.dart'; + +/// Marker interface to indicate that a job should be queued after database transactions are committed. +abstract class ShouldQueueAfterCommit extends ShouldQueue {} diff --git a/packages/contracts/lib/src/redis/connection.dart b/packages/contracts/lib/src/redis/connection.dart new file mode 100644 index 0000000..d585cad --- /dev/null +++ b/packages/contracts/lib/src/redis/connection.dart @@ -0,0 +1,11 @@ +/// Interface for Redis connections. +abstract class Connection { + /// Subscribe to a set of given channels for messages. + void subscribe(dynamic channels, Function callback); + + /// Subscribe to a set of given channels with wildcards. + void psubscribe(dynamic channels, Function callback); + + /// Run a command against the Redis database. + dynamic command(String method, [List parameters = const []]); +} diff --git a/packages/contracts/lib/src/redis/connector.dart b/packages/contracts/lib/src/redis/connector.dart new file mode 100644 index 0000000..902ba77 --- /dev/null +++ b/packages/contracts/lib/src/redis/connector.dart @@ -0,0 +1,14 @@ +import 'connection.dart'; + +/// Interface for Redis connectors. +abstract class Connector { + /// Create a connection to a Redis cluster. + Connection connect(Map config, Map options); + + /// Create a connection to a Redis instance. + Connection connectToCluster( + Map config, + Map clusterOptions, + Map options, + ); +} diff --git a/packages/contracts/lib/src/redis/limiter_timeout_exception.dart b/packages/contracts/lib/src/redis/limiter_timeout_exception.dart new file mode 100644 index 0000000..d6c0761 --- /dev/null +++ b/packages/contracts/lib/src/redis/limiter_timeout_exception.dart @@ -0,0 +1,2 @@ +/// Exception thrown when a Redis rate limiter times out. +class LimiterTimeoutException implements Exception {} diff --git a/packages/contracts/lib/src/redis/redis_factory.dart b/packages/contracts/lib/src/redis/redis_factory.dart new file mode 100644 index 0000000..62aa6e7 --- /dev/null +++ b/packages/contracts/lib/src/redis/redis_factory.dart @@ -0,0 +1,5 @@ +/// Interface for Redis factory. +abstract class RedisFactory { + /// Get a Redis connection by name. + dynamic connection([String? name]); +} diff --git a/packages/contracts/lib/src/reflection/reflected_class.dart b/packages/contracts/lib/src/reflection/reflected_class.dart new file mode 100644 index 0000000..82707f5 --- /dev/null +++ b/packages/contracts/lib/src/reflection/reflected_class.dart @@ -0,0 +1,61 @@ +import 'reflected_function.dart'; +import 'reflected_parameter.dart'; +import 'reflected_type.dart'; + +/// Interface for class reflection information. +abstract class ReflectedClass { + /// Get the name of the class. + String get name; + + /// Get the qualified name of the class (including namespace). + String get qualifiedName; + + /// Get the type represented by this class. + Type get type; + + /// Get the constructor methods of the class. + List get constructors; + + /// Get all methods defined in the class. + List get methods; + + /// Get all instance properties defined in the class. + List get properties; + + /// Get the parent class if any. + ReflectedClass? get parent; + + /// Get all interfaces implemented by this class. + List get interfaces; + + /// Get all attributes applied to this class. + List get attributes; + + /// Check if this class has a specific attribute. + bool hasAttribute(Type attributeType); + + /// Get all attributes of a specific type. + List getAttributes(Type attributeType); + + /// Create a new instance of the class. + /// + /// If [constructorName] is provided, uses the named constructor. + /// The [arguments] map contains the arguments to pass to the constructor. + dynamic newInstance( + [String? constructorName, Map? arguments]); + + /// Determine if this class is abstract. + bool get isAbstract; + + /// Determine if this class implements the given interface. + bool implementsInterface(Type interfaceType); + + /// Determine if this class extends the given class. + bool extendsClass(Type classType); + + /// Get a method by name. + ReflectedFunction? getMethod(String name); + + /// Get a property by name. + ReflectedParameter? getProperty(String name); +} diff --git a/packages/contracts/lib/src/reflection/reflected_function.dart b/packages/contracts/lib/src/reflection/reflected_function.dart new file mode 100644 index 0000000..2c1e8c3 --- /dev/null +++ b/packages/contracts/lib/src/reflection/reflected_function.dart @@ -0,0 +1,56 @@ +import 'reflected_parameter.dart'; +import 'reflected_type.dart'; + +/// Interface for function reflection information. +abstract class ReflectedFunction { + /// Get the name of the function. + String get name; + + /// Get the qualified name of the function (including class name if a method). + String get qualifiedName; + + /// Get the return type of the function. + ReflectedType get returnType; + + /// Get the parameters of the function. + List get parameters; + + /// Get all attributes applied to this function. + List get attributes; + + /// Check if this function has a specific attribute. + bool hasAttribute(Type attributeType); + + /// Get all attributes of a specific type. + List getAttributes(Type attributeType); + + /// Invoke the function with the given arguments. + /// + /// The [target] parameter is required for instance methods. + /// The [arguments] map contains the arguments to pass to the function. + dynamic invoke([dynamic target, Map? arguments]); + + /// Determine if this function is static. + bool get isStatic; + + /// Determine if this function is a constructor. + bool get isConstructor; + + /// Determine if this function is abstract. + bool get isAbstract; + + /// Get the number of required parameters. + int get requiredParameterCount; + + /// Get the number of optional parameters. + int get optionalParameterCount; + + /// Get whether this function has named parameters. + bool get hasNamedParameters; + + /// Get whether this function is async. + bool get isAsync; + + /// Get whether this function is a generator. + bool get isGenerator; +} diff --git a/packages/contracts/lib/src/reflection/reflected_instance.dart b/packages/contracts/lib/src/reflection/reflected_instance.dart new file mode 100644 index 0000000..a989325 --- /dev/null +++ b/packages/contracts/lib/src/reflection/reflected_instance.dart @@ -0,0 +1,52 @@ +import 'reflected_class.dart'; +import 'reflected_function.dart'; +import 'reflected_parameter.dart'; +import 'reflected_type.dart'; + +/// Interface for instance reflection information. +abstract class ReflectedInstance { + /// Get the actual instance being reflected. + dynamic get instance; + + /// Get the class information for this instance. + ReflectedClass get reflectedClass; + + /// Get the type information for this instance. + ReflectedType get type; + + /// Get all attributes applied to this instance. + List get attributes; + + /// Check if this instance has a specific attribute. + bool hasAttribute(Type attributeType); + + /// Get all attributes of a specific type. + List getAttributes(Type attributeType); + + /// Get the value of a property by name. + dynamic getProperty(String name); + + /// Set the value of a property by name. + void setProperty(String name, dynamic value); + + /// Invoke a method on this instance. + dynamic invoke(String methodName, [Map? arguments]); + + /// Get all property values as a map. + Map toMap(); + + /// Get all methods that can be invoked on this instance. + List get methods; + + /// Get all properties that can be accessed on this instance. + List get properties; + + /// Get whether this instance has a specific property. + bool hasProperty(String name); + + /// Get whether this instance has a specific method. + bool hasMethod(String name); + + /// Get whether this instance is of a specific type. + bool isInstanceOf(Type type); +} diff --git a/packages/contracts/lib/src/reflection/reflected_parameter.dart b/packages/contracts/lib/src/reflection/reflected_parameter.dart new file mode 100644 index 0000000..47fea8f --- /dev/null +++ b/packages/contracts/lib/src/reflection/reflected_parameter.dart @@ -0,0 +1,46 @@ +import 'reflected_type.dart'; + +/// Interface for parameter reflection information. +abstract class ReflectedParameter { + /// Get the name of the parameter. + String get name; + + /// Get the type of the parameter. + ReflectedType get type; + + /// Get all attributes applied to this parameter. + List get attributes; + + /// Check if this parameter has a specific attribute. + bool hasAttribute(Type attributeType); + + /// Get all attributes of a specific type. + List getAttributes(Type attributeType); + + /// Determine if this parameter is optional. + bool get isOptional; + + /// Determine if this parameter is named. + bool get isNamed; + + /// Determine if this parameter is required. + bool get isRequired; + + /// Get the default value of the parameter if it has one. + dynamic get defaultValue; + + /// Get whether this parameter has a default value. + bool get hasDefaultValue; + + /// Get the position of this parameter in the parameter list. + int get position; + + /// Get whether this parameter is variadic (accepts variable number of arguments). + bool get isVariadic; + + /// Get whether this parameter is nullable. + bool get isNullable; + + /// Get the metadata annotations applied to this parameter. + List get annotations; +} diff --git a/packages/contracts/lib/src/reflection/reflected_type.dart b/packages/contracts/lib/src/reflection/reflected_type.dart new file mode 100644 index 0000000..6121583 --- /dev/null +++ b/packages/contracts/lib/src/reflection/reflected_type.dart @@ -0,0 +1,56 @@ +/// Interface for type reflection information. +abstract class ReflectedType { + /// Get the name of the type. + String get name; + + /// Get the qualified name of the type (including namespace). + String get qualifiedName; + + /// Get the actual Type object. + Type get type; + + /// Get all attributes applied to this type. + List get attributes; + + /// Check if this type has a specific attribute. + bool hasAttribute(Type attributeType); + + /// Get all attributes of a specific type. + List getAttributes(Type attributeType); + + /// Determine if this type is nullable. + bool get isNullable; + + /// Determine if this type is a class. + bool get isClass; + + /// Determine if this type is an interface. + bool get isInterface; + + /// Determine if this type is an enum. + bool get isEnum; + + /// Determine if this type is a mixin. + bool get isMixin; + + /// Determine if this type is abstract. + bool get isAbstract; + + /// Get the generic type arguments if this is a generic type. + List get typeArguments; + + /// Get whether this type has generic type parameters. + bool get isGeneric; + + /// Get the base type if this is a derived type. + ReflectedType? get baseType; + + /// Get whether this type is assignable to another type. + bool isAssignableTo(Type other); + + /// Get whether this type is a subtype of another type. + bool isSubtypeOf(Type other); + + /// Get whether this type is a supertype of another type. + bool isSupertypeOf(Type other); +} diff --git a/packages/contracts/lib/src/reflection/reflector.dart b/packages/contracts/lib/src/reflection/reflector.dart new file mode 100644 index 0000000..a1cb175 --- /dev/null +++ b/packages/contracts/lib/src/reflection/reflector.dart @@ -0,0 +1,32 @@ +import 'reflected_class.dart'; +import 'reflected_function.dart'; +import 'reflected_instance.dart'; +import 'reflected_parameter.dart'; +import 'reflected_type.dart'; + +/// Interface for reflection operations. +/// +/// This contract defines the core reflection capabilities needed by the framework, +/// particularly for dependency injection and service container functionality. +abstract class Reflector { + /// Get reflection information for a class. + ReflectedClass reflectClass(Type type); + + /// Get reflection information for a function. + ReflectedFunction reflectFunction(Function function); + + /// Get reflection information for an instance. + ReflectedInstance reflectInstance(dynamic instance); + + /// Get reflection information for a type. + ReflectedType reflectType(Type type); + + /// Get reflection information for a parameter. + ReflectedParameter reflectParameter(dynamic parameter); + + /// Determine if a type has a given attribute. + bool hasAttribute(Type type, Type attributeType); + + /// Get all attributes of a given type from a type. + List getAttributes(Type type, Type attributeType); +} diff --git a/packages/contracts/lib/src/routing/binding_registrar.dart b/packages/contracts/lib/src/routing/binding_registrar.dart new file mode 100644 index 0000000..902f161 --- /dev/null +++ b/packages/contracts/lib/src/routing/binding_registrar.dart @@ -0,0 +1,8 @@ +/// Interface for route binding registration. +abstract class BindingRegistrar { + /// Add a new route parameter binder. + void bind(String key, dynamic binder); + + /// Get the binding callback for a given binding. + Function getBindingCallback(String key); +} diff --git a/packages/contracts/lib/src/routing/registrar.dart b/packages/contracts/lib/src/routing/registrar.dart new file mode 100644 index 0000000..3a63707 --- /dev/null +++ b/packages/contracts/lib/src/routing/registrar.dart @@ -0,0 +1,36 @@ +/// Interface for route registration. +abstract class Registrar { + /// Register a new GET route with the router. + dynamic get(String uri, dynamic action); + + /// Register a new POST route with the router. + dynamic post(String uri, dynamic action); + + /// Register a new PUT route with the router. + dynamic put(String uri, dynamic action); + + /// Register a new DELETE route with the router. + dynamic delete(String uri, dynamic action); + + /// Register a new PATCH route with the router. + dynamic patch(String uri, dynamic action); + + /// Register a new OPTIONS route with the router. + dynamic options(String uri, dynamic action); + + /// Register a new route with the given verbs. + dynamic match(dynamic methods, String uri, dynamic action); + + /// Route a resource to a controller. + dynamic resource(String name, String controller, + [Map options = const {}]); + + /// Create a route group with shared attributes. + void group(Map attributes, dynamic routes); + + /// Substitute the route bindings onto the route. + dynamic substituteBindings(dynamic route); + + /// Substitute the implicit model bindings for the route. + void substituteImplicitBindings(dynamic route); +} diff --git a/packages/contracts/lib/src/routing/response_factory.dart b/packages/contracts/lib/src/routing/response_factory.dart new file mode 100644 index 0000000..b82a88e --- /dev/null +++ b/packages/contracts/lib/src/routing/response_factory.dart @@ -0,0 +1,82 @@ +/// Interface for HTTP response creation. +abstract class ResponseFactory { + /// Create a new response instance. + dynamic make( + [dynamic content = '', + int status = 200, + Map headers = const {}]); + + /// Create a new "no content" response. + dynamic noContent( + [int status = 204, Map headers = const {}]); + + /// Create a new response for a given view. + dynamic view(dynamic view, + [Map data = const {}, + int status = 200, + Map headers = const {}]); + + /// Create a new JSON response instance. + dynamic json( + [dynamic data = const {}, + int status = 200, + Map headers = const {}, + int options = 0]); + + /// Create a new JSONP response instance. + dynamic jsonp(String callback, + [dynamic data = const {}, + int status = 200, + Map headers = const {}, + int options = 0]); + + /// Create a new streamed response instance. + dynamic stream(Function callback, + [int status = 200, Map headers = const {}]); + + /// Create a new streamed response instance as a file download. + dynamic streamDownload(Function callback, + [String? name, + Map headers = const {}, + String disposition = 'attachment']); + + /// Create a new file download response. + dynamic download(dynamic file, + [String? name, + Map headers = const {}, + String disposition = 'attachment']); + + /// Return the raw contents of a binary file. + dynamic file(dynamic file, [Map headers = const {}]); + + /// Create a new redirect response to the given path. + dynamic redirectTo(String path, + [int status = 302, + Map headers = const {}, + bool? secure]); + + /// Create a new redirect response to a named route. + dynamic redirectToRoute(String route, + [dynamic parameters = const {}, + int status = 302, + Map headers = const {}]); + + /// Create a new redirect response to a controller action. + dynamic redirectToAction(dynamic action, + [dynamic parameters = const {}, + int status = 302, + Map headers = const {}]); + + /// Create a new redirect response, while putting the current URL in the session. + dynamic redirectGuest(String path, + [int status = 302, + Map headers = const {}, + bool? secure]); + + /// Create a new redirect response to the previously intended location. + dynamic redirectToIntended( + [String default_ = '/', + int status = 302, + Map headers = const {}, + bool? secure]); +} 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/routing/url_generator.dart b/packages/contracts/lib/src/routing/url_generator.dart new file mode 100644 index 0000000..7364de6 --- /dev/null +++ b/packages/contracts/lib/src/routing/url_generator.dart @@ -0,0 +1,47 @@ +/// Interface for URL generation. +abstract class UrlGenerator { + /// Get the current URL for the request. + String current(); + + /// Get the URL for the previous request. + String previous([dynamic fallback = false]); + + /// Generate an absolute URL to the given path. + String to(String path, [dynamic extra = const [], bool? secure]); + + /// Generate a secure, absolute URL to the given path. + String secure(String path, [List parameters = const []]); + + /// Generate the URL to an application asset. + String asset(String path, [bool? secure]); + + /// Get the URL to a named route. + String route(String name, + [dynamic parameters = const [], bool absolute = true]); + + /// Create a signed route URL for a named route. + String signedRoute(String name, + [dynamic parameters = const [], + dynamic expiration, + bool absolute = true]); + + /// Create a temporary signed route URL for a named route. + String temporarySignedRoute(String name, dynamic expiration, + [dynamic parameters = const [], bool absolute = true]); + + /// Get the URL to a controller action. + String action(dynamic action, + [dynamic parameters = const [], bool absolute = true]); + + /// Get the root controller namespace. + String getRootControllerNamespace(); + + /// Set the root controller namespace. + UrlGenerator setRootControllerNamespace(String rootNamespace); + + /// Generate a URL with query string. + String query(String path, + [Map query = const {}, + dynamic extra = const [], + bool? secure]); +} diff --git a/packages/contracts/lib/src/routing/url_routable.dart b/packages/contracts/lib/src/routing/url_routable.dart new file mode 100644 index 0000000..dae61ae --- /dev/null +++ b/packages/contracts/lib/src/routing/url_routable.dart @@ -0,0 +1,15 @@ +/// Interface for URL routable models. +abstract class UrlRoutable { + /// Get the value of the model's route key. + dynamic getRouteKey(); + + /// Get the route key for the model. + String getRouteKeyName(); + + /// Retrieve the model for a bound value. + dynamic resolveRouteBinding(dynamic value, [String? field]); + + /// Retrieve the child model for a bound value. + dynamic resolveChildRouteBinding(String childType, dynamic value, + [String? field]); +} diff --git a/packages/contracts/lib/src/session/middleware/authenticates_sessions.dart b/packages/contracts/lib/src/session/middleware/authenticates_sessions.dart new file mode 100644 index 0000000..9b951f4 --- /dev/null +++ b/packages/contracts/lib/src/session/middleware/authenticates_sessions.dart @@ -0,0 +1,2 @@ +/// Marker interface to indicate that a middleware authenticates sessions. +abstract class AuthenticatesSessions {} diff --git a/packages/contracts/lib/src/session/session.dart b/packages/contracts/lib/src/session/session.dart new file mode 100644 index 0000000..95d3745 --- /dev/null +++ b/packages/contracts/lib/src/session/session.dart @@ -0,0 +1,80 @@ +/// Interface for session management. +abstract class Session { + /// Get the name of the session. + String getName(); + + /// Set the name of the session. + void setName(String name); + + /// Get the current session ID. + String getId(); + + /// Set the session ID. + void setId(String id); + + /// Start the session, reading the data from a handler. + bool start(); + + /// Save the session data to storage. + void save(); + + /// Get all of the session data. + Map all(); + + /// Checks if a key exists. + bool exists(dynamic key); + + /// Checks if a key is present and not null. + bool has(dynamic key); + + /// Get an item from the session. + dynamic get(String key, [dynamic default_]); + + /// Get the value of a given key and then forget it. + dynamic pull(String key, [dynamic default_]); + + /// Put a key / value pair or array of key / value pairs in the session. + void put(dynamic key, [dynamic value]); + + /// Get the CSRF token value. + String token(); + + /// Regenerate the CSRF token value. + void regenerateToken(); + + /// Remove an item from the session, returning its value. + dynamic remove(String key); + + /// Remove one or many items from the session. + void forget(dynamic keys); + + /// Remove all of the items from the session. + void flush(); + + /// Flush the session data and regenerate the ID. + bool invalidate(); + + /// Generate a new session identifier. + bool regenerate([bool destroy = false]); + + /// Generate a new session ID for the session. + bool migrate([bool destroy = false]); + + /// Determine if the session has been started. + bool isStarted(); + + /// Get the previous URL from the session. + String? previousUrl(); + + /// Set the "previous" URL in the session. + void setPreviousUrl(String url); + + /// Get the session handler instance. + dynamic getHandler(); + + /// Determine if the session handler needs a request. + bool handlerNeedsRequest(); + + /// Set the request on the handler instance. + void setRequestOnHandler(dynamic request); +} diff --git a/packages/contracts/lib/src/support/arrayable.dart b/packages/contracts/lib/src/support/arrayable.dart new file mode 100644 index 0000000..f8ff873 --- /dev/null +++ b/packages/contracts/lib/src/support/arrayable.dart @@ -0,0 +1,30 @@ +/// Interface for objects that can be converted to an array. +/// +/// This contract defines a standard way for objects to be converted +/// to array representation, which is useful for serialization, +/// data transfer, and other operations requiring array format. +abstract class Arrayable { + /// Get the instance as an array. + /// + /// Implementations should convert their internal state to a Map/array + /// representation that can be easily serialized or manipulated. + /// + /// Example: + /// ```dart + /// class User implements Arrayable { + /// final String name; + /// final int age; + /// + /// User(this.name, this.age); + /// + /// @override + /// Map toArray() { + /// return { + /// 'name': name, + /// 'age': age, + /// }; + /// } + /// } + /// ``` + Map toArray(); +} diff --git a/packages/contracts/lib/src/support/can_be_escaped_when_cast_to_string.dart b/packages/contracts/lib/src/support/can_be_escaped_when_cast_to_string.dart new file mode 100644 index 0000000..65d0add --- /dev/null +++ b/packages/contracts/lib/src/support/can_be_escaped_when_cast_to_string.dart @@ -0,0 +1,37 @@ +/// Interface for objects that can control their string escaping behavior. +/// +/// This contract allows objects to specify whether their string representation +/// should be HTML escaped when converted to a string. This is particularly +/// useful for objects that may contain HTML content that should sometimes be +/// escaped and other times rendered as-is. +abstract class CanBeEscapedWhenCastToString { + /// Indicate that the object's string representation should be escaped when toString is invoked. + /// + /// Example: + /// ```dart + /// class HtmlContent implements CanBeEscapedWhenCastToString { + /// final String content; + /// bool _escape = false; + /// + /// HtmlContent(this.content); + /// + /// @override + /// CanBeEscapedWhenCastToString escapeWhenCastingToString([bool escape = true]) { + /// _escape = escape; + /// return this; + /// } + /// + /// @override + /// String toString() { + /// if (_escape) { + /// return content + /// .replaceAll('&', '&') + /// .replaceAll('<', '<') + /// .replaceAll('>', '>'); + /// } + /// return content; + /// } + /// } + /// ``` + CanBeEscapedWhenCastToString escapeWhenCastingToString([bool escape = true]); +} diff --git a/packages/contracts/lib/src/support/deferrable_provider.dart b/packages/contracts/lib/src/support/deferrable_provider.dart new file mode 100644 index 0000000..c36f4e1 --- /dev/null +++ b/packages/contracts/lib/src/support/deferrable_provider.dart @@ -0,0 +1,28 @@ +/// Interface for service providers that support deferred loading. +/// +/// This contract defines a standard way for service providers to specify +/// which services they provide. This information is used by the service +/// container to determine when a provider should be loaded, enabling +/// lazy loading of services for better performance. +/// +/// Example: +/// ```dart +/// class CacheServiceProvider implements DeferrableProvider { +/// @override +/// List provides() { +/// return [ +/// 'cache', +/// 'cache.store', +/// 'memcached.connector', +/// ]; +/// } +/// } +/// ``` +abstract class DeferrableProvider { + /// Get the services provided by the provider. + /// + /// Returns a list of service identifiers that this provider can resolve. + /// These identifiers are used by the service container to determine + /// when this provider should be loaded. + List provides(); +} diff --git a/packages/contracts/lib/src/support/deferring_displayable_value.dart b/packages/contracts/lib/src/support/deferring_displayable_value.dart new file mode 100644 index 0000000..f783f3e --- /dev/null +++ b/packages/contracts/lib/src/support/deferring_displayable_value.dart @@ -0,0 +1,32 @@ +import 'htmlable.dart'; + +/// Interface for values that defer their display representation. +/// +/// This contract is used for objects that need to defer the resolution of their +/// displayable value until it's actually needed. This can be useful for lazy +/// loading of expensive-to-compute display values or for values that might +/// change based on runtime conditions. +/// +/// Example: +/// ```dart +/// class LazyHtmlContent implements DeferringDisplayableValue { +/// final Function _valueFactory; +/// +/// LazyHtmlContent(this._valueFactory); +/// +/// @override +/// dynamic resolveDisplayableValue() { +/// final value = _valueFactory(); +/// if (value is Htmlable) { +/// return value; +/// } +/// return value.toString(); +/// } +/// } +/// ``` +abstract class DeferringDisplayableValue { + /// Resolve the displayable value that the class is deferring. + /// + /// Returns either an [Htmlable] object or a [String]. + dynamic resolveDisplayableValue(); +} diff --git a/packages/contracts/lib/src/support/htmlable.dart b/packages/contracts/lib/src/support/htmlable.dart new file mode 100644 index 0000000..abd401b --- /dev/null +++ b/packages/contracts/lib/src/support/htmlable.dart @@ -0,0 +1,41 @@ +/// Interface for objects that can be converted to HTML. +/// +/// This contract defines a standard way for objects to be converted +/// to their HTML string representation. This is particularly useful +/// for components that need to render HTML content while ensuring +/// proper escaping and formatting. +/// +/// Example: +/// ```dart +/// class HtmlComponent implements Htmlable { +/// final String content; +/// final Map attributes; +/// +/// HtmlComponent(this.content, this.attributes); +/// +/// @override +/// String toHtml() { +/// final attrs = attributes.entries +/// .map((e) => '${e.key}="${escapeHtml(e.value)}"') +/// .join(' '); +/// return '
${escapeHtml(content)}
'; +/// } +/// +/// String escapeHtml(String text) { +/// return text +/// .replaceAll('&', '&') +/// .replaceAll('<', '<') +/// .replaceAll('>', '>') +/// .replaceAll('"', '"') +/// .replaceAll("'", '''); +/// } +/// } +/// ``` +abstract class Htmlable { + /// Get content as a string of HTML. + /// + /// This method should return valid HTML markup. Implementations + /// should ensure proper escaping of content to prevent XSS attacks + /// and maintain valid HTML structure. + String toHtml(); +} diff --git a/packages/contracts/lib/src/support/jsonable.dart b/packages/contracts/lib/src/support/jsonable.dart new file mode 100644 index 0000000..9c48526 --- /dev/null +++ b/packages/contracts/lib/src/support/jsonable.dart @@ -0,0 +1,30 @@ +/// Interface for objects that can be converted to JSON. +/// +/// This contract defines a standard way for objects to be converted +/// to their JSON string representation, which is useful for serialization +/// and data transfer operations. +abstract class Jsonable { + /// Convert the object to its JSON representation. + /// + /// The [options] parameter can be used to customize the JSON encoding process. + /// Implementations may define their own options to control the output format. + /// + /// Example: + /// ```dart + /// class User implements Jsonable { + /// final String name; + /// final int age; + /// + /// User(this.name, this.age); + /// + /// @override + /// String toJson([Map? options]) { + /// return json.encode({ + /// 'name': name, + /// 'age': age, + /// }); + /// } + /// } + /// ``` + String toJson([Map? options]); +} diff --git a/packages/contracts/lib/src/support/message_bag.dart b/packages/contracts/lib/src/support/message_bag.dart new file mode 100644 index 0000000..c98f4e2 --- /dev/null +++ b/packages/contracts/lib/src/support/message_bag.dart @@ -0,0 +1,68 @@ +import 'arrayable.dart'; + +/// Interface for storing and retrieving messages. +/// +/// This contract defines a standard way to store, retrieve, and manipulate +/// messages (such as validation errors or notifications) in a structured way. +abstract class MessageBag implements Arrayable { + /// Get the keys present in the message bag. + List keys(); + + /// Add a message to the bag. + /// + /// Returns this instance for method chaining. + MessageBag add(String key, String message); + + /// Merge a new array of messages into the bag. + /// + /// The [messages] parameter can be either a MessageProvider or a Map. + /// Returns this instance for method chaining. + MessageBag merge(dynamic messages); + + /// Determine if messages exist for a given key. + /// + /// The [key] parameter can be either a single key or a list of keys. + bool has(dynamic key); + + /// Get the first message from the bag for a given key. + /// + /// If [key] is null, returns the first message from any key. + /// The [format] parameter can be used to format the message string. + String? first([String? key, String? format]); + + /// Get all of the messages from the bag for a given key. + /// + /// The [format] parameter can be used to format the message strings. + List get(String key, [String? format]); + + /// Get all of the messages for every key in the bag. + /// + /// The [format] parameter can be used to format the message strings. + Map> all([String? format]); + + /// Remove a message from the bag. + /// + /// Returns this instance for method chaining. + MessageBag forget(String key); + + /// Get the raw messages in the container. + Map> getMessages(); + + /// Get the default message format. + String getFormat(); + + /// Set the default message format. + /// + /// Returns this instance for method chaining. + /// Default format is ':message'. + MessageBag setFormat([String format = ':message']); + + /// Determine if the message bag has any messages. + bool get isEmpty; + + /// Determine if the message bag has any messages. + bool get isNotEmpty; + + /// Get the number of messages in the container. + int get length; +} diff --git a/packages/contracts/lib/src/support/message_provider.dart b/packages/contracts/lib/src/support/message_provider.dart new file mode 100644 index 0000000..eb5efcf --- /dev/null +++ b/packages/contracts/lib/src/support/message_provider.dart @@ -0,0 +1,27 @@ +import 'message_bag.dart'; + +/// Interface for objects that provide messages. +/// +/// This contract defines a standard way for objects to provide +/// access to their messages through a MessageBag instance. +/// This is particularly useful for validation results, form processing, +/// and other scenarios where multiple messages need to be managed. +/// +/// Example: +/// ```dart +/// class ValidationResult implements MessageProvider { +/// final MessageBag _messages; +/// +/// ValidationResult(this._messages); +/// +/// @override +/// MessageBag getMessageBag() => _messages; +/// } +/// ``` +abstract class MessageProvider { + /// Get the messages for the instance. + /// + /// Returns a MessageBag instance containing all messages + /// associated with this provider. + MessageBag getMessageBag(); +} diff --git a/packages/contracts/lib/src/support/renderable.dart b/packages/contracts/lib/src/support/renderable.dart new file mode 100644 index 0000000..b752a4d --- /dev/null +++ b/packages/contracts/lib/src/support/renderable.dart @@ -0,0 +1,29 @@ +/// Interface for objects that can be rendered to a string. +/// +/// This contract defines a standard way for objects to be rendered +/// into their string representation. This is particularly useful +/// for views, templates, and other UI components that need to +/// produce output for display. +/// +/// Example: +/// ```dart +/// class Template implements Renderable { +/// final String template; +/// final Map data; +/// +/// Template(this.template, this.data); +/// +/// @override +/// String render() { +/// // Process template with data and return result +/// return processTemplate(template, data); +/// } +/// } +/// ``` +abstract class Renderable { + /// Get the evaluated contents of the object. + /// + /// This method should return the final string representation + /// of the object after all processing and evaluation is complete. + String render(); +} diff --git a/packages/contracts/lib/src/support/responsable.dart b/packages/contracts/lib/src/support/responsable.dart new file mode 100644 index 0000000..99d18ca --- /dev/null +++ b/packages/contracts/lib/src/support/responsable.dart @@ -0,0 +1,38 @@ +import '../http/request.dart'; +import '../http/response.dart'; + +/// Interface for objects that can be converted to HTTP responses. +/// +/// This contract defines a standard way for objects to be converted +/// into HTTP responses. This is particularly useful for API resources, +/// view models, and other objects that need to be sent as HTTP responses. +/// +/// Example: +/// ```dart +/// class UserResource implements Responsable { +/// final User user; +/// +/// UserResource(this.user); +/// +/// @override +/// Response toResponse(Request request) { +/// return JsonResponse({ +/// 'id': user.id, +/// 'name': user.name, +/// 'email': user.email, +/// '_links': { +/// 'self': '/api/users/${user.id}', +/// }, +/// }); +/// } +/// } +/// ``` +abstract class Responsable { + /// Create an HTTP response that represents the object. + /// + /// This method allows objects to define their own custom response + /// transformation logic. The [request] parameter provides context + /// about the current HTTP request, which can be used to customize + /// the response format (e.g., JSON vs HTML based on Accept header). + Response toResponse(Request request); +} 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/support/validated_data.dart b/packages/contracts/lib/src/support/validated_data.dart new file mode 100644 index 0000000..7e34937 --- /dev/null +++ b/packages/contracts/lib/src/support/validated_data.dart @@ -0,0 +1,20 @@ +/// Interface for validated data that can be accessed like an array. +abstract class ValidatedData { + /// Get the instance as an array. + Map toArray(); + + /// Determine if an offset exists. + bool containsKey(String key); + + /// Get an item at a given offset. + dynamic operator [](String key); + + /// Set the item at a given offset. + void operator []=(String key, dynamic value); + + /// Remove an item at a given offset. + void remove(String key); + + /// Get an iterator for the data. + Iterator> get iterator; +} 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/lib/src/translation/has_locale_preference.dart b/packages/contracts/lib/src/translation/has_locale_preference.dart new file mode 100644 index 0000000..4c1f9fe --- /dev/null +++ b/packages/contracts/lib/src/translation/has_locale_preference.dart @@ -0,0 +1,5 @@ +/// Interface for entities that have a preferred locale. +abstract class HasLocalePreference { + /// Get the preferred locale of the entity. + String? preferredLocale(); +} diff --git a/packages/contracts/lib/src/translation/loader.dart b/packages/contracts/lib/src/translation/loader.dart new file mode 100644 index 0000000..8f06479 --- /dev/null +++ b/packages/contracts/lib/src/translation/loader.dart @@ -0,0 +1,14 @@ +/// Interface for translation loaders. +abstract class Loader { + /// Load the messages for the given locale. + Map load(String locale, String group, [String? namespace]); + + /// Add a new namespace to the loader. + void addNamespace(String namespace, String hint); + + /// Add a new JSON path to the loader. + void addJsonPath(String path); + + /// Get an array of all the registered namespaces. + Map namespaces(); +} diff --git a/packages/contracts/lib/src/translation/translator.dart b/packages/contracts/lib/src/translation/translator.dart new file mode 100644 index 0000000..7c7143a --- /dev/null +++ b/packages/contracts/lib/src/translation/translator.dart @@ -0,0 +1,16 @@ +/// Interface for translation services. +abstract class Translator { + /// Get the translation for a given key. + dynamic get(String key, + [Map replace = const {}, String? locale]); + + /// Get a translation according to an integer value. + String choice(String key, dynamic number, + [Map replace = const {}, String? locale]); + + /// Get the default locale being used. + String getLocale(); + + /// Set the default locale. + void setLocale(String locale); +} diff --git a/packages/contracts/lib/src/validation/data_aware_rule.dart b/packages/contracts/lib/src/validation/data_aware_rule.dart new file mode 100644 index 0000000..6cf4d89 --- /dev/null +++ b/packages/contracts/lib/src/validation/data_aware_rule.dart @@ -0,0 +1,5 @@ +/// Interface for validation rules that need access to all data being validated. +abstract class DataAwareRule { + /// Set the data under validation. + DataAwareRule setData(Map data); +} diff --git a/packages/contracts/lib/src/validation/implicit_rule.dart b/packages/contracts/lib/src/validation/implicit_rule.dart new file mode 100644 index 0000000..1152138 --- /dev/null +++ b/packages/contracts/lib/src/validation/implicit_rule.dart @@ -0,0 +1,5 @@ +import 'rule.dart'; + +/// Interface for implicit validation rules. +/// @deprecated see ValidationRule +abstract class ImplicitRule extends Rule {} diff --git a/packages/contracts/lib/src/validation/invokable_rule.dart b/packages/contracts/lib/src/validation/invokable_rule.dart new file mode 100644 index 0000000..c040183 --- /dev/null +++ b/packages/contracts/lib/src/validation/invokable_rule.dart @@ -0,0 +1,6 @@ +/// Interface for invokable validation rules. +/// @deprecated see ValidationRule +abstract class InvokableRule { + /// Run the validation rule. + void call(String attribute, dynamic value, Function fail); +} diff --git a/packages/contracts/lib/src/validation/rule.dart b/packages/contracts/lib/src/validation/rule.dart new file mode 100644 index 0000000..1a371ce --- /dev/null +++ b/packages/contracts/lib/src/validation/rule.dart @@ -0,0 +1,9 @@ +/// Interface for validation rules. +/// @deprecated see ValidationRule +abstract class Rule { + /// Determine if the validation rule passes. + bool passes(String attribute, dynamic value); + + /// Get the validation error message. + dynamic message(); +} diff --git a/packages/contracts/lib/src/validation/uncompromised_verifier.dart b/packages/contracts/lib/src/validation/uncompromised_verifier.dart new file mode 100644 index 0000000..eb59004 --- /dev/null +++ b/packages/contracts/lib/src/validation/uncompromised_verifier.dart @@ -0,0 +1,5 @@ +/// Interface for verifying data against known data leaks. +abstract class UncompromisedVerifier { + /// Verify that the given data has not been compromised in data leaks. + bool verify(Map data); +} diff --git a/packages/contracts/lib/src/validation/validates_when_resolved.dart b/packages/contracts/lib/src/validation/validates_when_resolved.dart new file mode 100644 index 0000000..cb4f7d8 --- /dev/null +++ b/packages/contracts/lib/src/validation/validates_when_resolved.dart @@ -0,0 +1,5 @@ +/// Interface for objects that validate when resolved. +abstract class ValidatesWhenResolved { + /// Validate the given class instance. + void validateResolved(); +} diff --git a/packages/contracts/lib/src/validation/validation_factory.dart b/packages/contracts/lib/src/validation/validation_factory.dart new file mode 100644 index 0000000..5ef6500 --- /dev/null +++ b/packages/contracts/lib/src/validation/validation_factory.dart @@ -0,0 +1,21 @@ +import 'validator.dart'; + +/// Interface for validation factory. +abstract class ValidationFactory { + /// Create a new Validator instance. + Validator make( + Map data, + Map rules, [ + Map messages = const {}, + Map attributes = const {}, + ]); + + /// Register a custom validator extension. + void extend(String rule, dynamic extension, [String? message]); + + /// Register a custom implicit validator extension. + void extendImplicit(String rule, dynamic extension, [String? message]); + + /// Register a custom implicit validator message replacer. + void replacer(String rule, dynamic replacer); +} diff --git a/packages/contracts/lib/src/validation/validation_rule.dart b/packages/contracts/lib/src/validation/validation_rule.dart new file mode 100644 index 0000000..e78c1ce --- /dev/null +++ b/packages/contracts/lib/src/validation/validation_rule.dart @@ -0,0 +1,5 @@ +/// Interface for validation rules. +abstract class ValidationRule { + /// Run the validation rule. + void validate(String attribute, dynamic value, Function fail); +} diff --git a/packages/contracts/lib/src/validation/validator.dart b/packages/contracts/lib/src/validation/validator.dart new file mode 100644 index 0000000..841e09a --- /dev/null +++ b/packages/contracts/lib/src/validation/validator.dart @@ -0,0 +1,25 @@ +import '../support/message_provider.dart'; + +/// Interface for validation. +abstract class Validator implements MessageProvider { + /// Run the validator's rules against its data. + Map validate(); + + /// Get the attributes and values that were validated. + Map validated(); + + /// Determine if the data fails the validation rules. + bool fails(); + + /// Get the failed validation rules. + Map failed(); + + /// Add conditions to a given field based on a callback. + Validator sometimes(dynamic attribute, dynamic rules, Function callback); + + /// Add an after validation callback. + Validator after(dynamic callback); + + /// Get all of the validation error messages. + dynamic errors(); +} diff --git a/packages/contracts/lib/src/validation/validator_aware_rule.dart b/packages/contracts/lib/src/validation/validator_aware_rule.dart new file mode 100644 index 0000000..28ecc72 --- /dev/null +++ b/packages/contracts/lib/src/validation/validator_aware_rule.dart @@ -0,0 +1,7 @@ +import 'validator.dart'; + +/// Interface for validation rules that need access to the validator instance. +abstract class ValidatorAwareRule { + /// Set the current validator. + ValidatorAwareRule setValidator(Validator validator); +} diff --git a/packages/contracts/lib/src/view/engine.dart b/packages/contracts/lib/src/view/engine.dart new file mode 100644 index 0000000..279bb65 --- /dev/null +++ b/packages/contracts/lib/src/view/engine.dart @@ -0,0 +1,5 @@ +/// Interface for view engines. +abstract class Engine { + /// Get the evaluated contents of the view. + String get(String path, [Map data = const {}]); +} diff --git a/packages/contracts/lib/src/view/view.dart b/packages/contracts/lib/src/view/view.dart new file mode 100644 index 0000000..c844115 --- /dev/null +++ b/packages/contracts/lib/src/view/view.dart @@ -0,0 +1,13 @@ +import '../support/renderable.dart'; + +/// Interface for views. +abstract class View extends Renderable { + /// Get the name of the view. + String name(); + + /// Add a piece of data to the view. + View withData(dynamic key, [dynamic value]); + + /// Get the array of view data. + Map getData(); +} diff --git a/packages/contracts/lib/src/view/view_compilation_exception.dart b/packages/contracts/lib/src/view/view_compilation_exception.dart new file mode 100644 index 0000000..b8983bd --- /dev/null +++ b/packages/contracts/lib/src/view/view_compilation_exception.dart @@ -0,0 +1,11 @@ +/// Exception thrown when view compilation fails. +class ViewCompilationException implements Exception { + /// The message describing the compilation error. + final String message; + + /// Create a new view compilation exception. + ViewCompilationException(this.message); + + @override + String toString() => 'ViewCompilationException: $message'; +} diff --git a/packages/contracts/lib/src/view/view_factory.dart b/packages/contracts/lib/src/view/view_factory.dart new file mode 100644 index 0000000..d9d568e --- /dev/null +++ b/packages/contracts/lib/src/view/view_factory.dart @@ -0,0 +1,32 @@ +import 'view.dart'; + +/// Interface for view factory. +abstract class ViewFactory { + /// Determine if a given view exists. + bool exists(String view); + + /// Get the evaluated view contents for the given path. + View file(String path, + [Map data = const {}, + Map mergeData = const {}]); + + /// Get the evaluated view contents for the given view. + View make(String view, + [Map data = const {}, + Map mergeData = const {}]); + + /// Add a piece of shared data to the environment. + dynamic share(dynamic key, [dynamic value]); + + /// Register a view composer event. + List composer(dynamic views, dynamic callback); + + /// Register a view creator event. + List creator(dynamic views, dynamic callback); + + /// Add a new namespace to the loader. + ViewFactory addNamespace(String namespace, dynamic hints); + + /// Replace the namespace hints for the given namespace. + ViewFactory replaceNamespace(String namespace, dynamic hints); +} diff --git a/packages/support/pubspec.yaml b/packages/support/pubspec.yaml index 33091fe..71ee7ae 100644 --- a/packages/support/pubspec.yaml +++ b/packages/support/pubspec.yaml @@ -1,17 +1,24 @@ name: platform_support -description: The Support Package for the Protevus Platform -version: 0.0.1 -homepage: https://protevus.com -documentation: https://docs.protevus.com -repository: https://github.com/protevus/platformo +description: Core support utilities and helper functions for the framework +version: 1.0.0 +homepage: https://github.com/yourusername/platform environment: - sdk: ^3.4.2 + sdk: '>=3.0.0 <4.0.0' -# Add regular dependencies here. dependencies: - # path: ^1.8.0 + collection: ^1.17.0 + meta: ^1.9.0 + path: ^1.8.0 + crypto: ^3.0.0 + uuid: ^4.0.0 + intl: ^0.18.0 dev_dependencies: - lints: ^3.0.0 + lints: ^2.1.0 test: ^1.24.0 + +topics: + - framework + - utilities + - support diff --git a/packages/support/specification.yaml b/packages/support/specification.yaml new file mode 100644 index 0000000..fb6c5dc --- /dev/null +++ b/packages/support/specification.yaml @@ -0,0 +1,286 @@ +# Laravel Illuminate Support Package Specification + +name: support +description: Core support utilities and helper functions for the framework +version: 1.0.0 + +dependencies: + required: + collections: ^1.0.0 + contracts: ^1.0.0 + macroable: ^1.0.0 + +components: + # Core String Manipulation + Str: + description: String manipulation utilities + methods: + - after + - before + - between + - camel + - contains + - endsWith + - finish + - is + - isAscii + - kebab + - length + - limit + - lower + - orderedUuid + - padBoth + - padLeft + - padRight + - plural + - random + - replace + - replaceArray + - replaceFirst + - replaceLast + - singular + - slug + - snake + - start + - startsWith + - studly + - title + - ucfirst + - upper + - uuid + - words + + # Service Provider System + ServiceProvider: + description: Base service provider for package registration and bootstrapping + methods: + - register + - boot + - provides + - when + - defer + + # Facade System + Facade: + description: Base facade class for static proxy interface + methods: + - getFacadeAccessor + - getFacadeRoot + - clearResolvedInstance + - setFacadeApplication + - resolveFacadeInstance + + # Collection Handling + Collection: + description: Wrapper for array manipulation with fluent interface + methods: + - all + - average + - chunk + - collapse + - combine + - concat + - contains + - count + - diff + - each + - every + - except + - filter + - first + - flatMap + - flatten + - flip + - forget + - get + - groupBy + - has + - implode + - intersect + - isEmpty + - isNotEmpty + - keyBy + - keys + - last + - map + - mapInto + - mapSpread + - mapToGroups + - mapWithKeys + - max + - median + - merge + - min + - mode + - only + - pad + - partition + - pipe + - pluck + - random + - reduce + - reject + - reverse + - search + - shift + - shuffle + - slice + - sort + - sortBy + - sortByDesc + - splice + - split + - sum + - take + - tap + - times + - toArray + - toJson + - transform + - union + - unique + - values + - when + - where + - whereIn + - whereNotIn + - zip + + # Helper Traits + traits: + Macroable: + description: Allows dynamic method registration on classes + methods: + - macro + - mixin + - hasMacro + - flushMacros + + Conditionable: + description: Adds fluent conditional execution + methods: + - when + - unless + + Tappable: + description: Provides tap helper for debugging and chaining + methods: + - tap + + # Optional Features + optional: + - name: HtmlString + description: HTML string wrapper that prevents double encoding + + - name: MessageBag + description: Error message container + + - name: Optional + description: Nullable object wrapper + + - name: Pluralizer + description: Word pluralization utilities + +# Helper Functions +helpers: + array: + - array_add + - array_collapse + - array_divide + - array_dot + - array_except + - array_first + - array_flatten + - array_forget + - array_get + - array_has + - array_last + - array_only + - array_pluck + - array_prepend + - array_pull + - array_random + - array_set + - array_sort + - array_sort_recursive + - array_where + - array_wrap + + string: + - camel_case + - class_basename + - e + - ends_with + - kebab_case + - preg_replace_array + - snake_case + - starts_with + - str_after + - str_before + - str_contains + - str_finish + - str_is + - str_limit + - str_plural + - str_random + - str_singular + - str_slug + - str_start + - studly_case + - title_case + + misc: + - app + - auth + - back + - base_path + - bcrypt + - blank + - broadcast + - cache + - config + - cookie + - csrf_field + - csrf_token + - dd + - decrypt + - dispatch + - encrypt + - env + - event + - factory + - filled + - info + - logger + - method_field + - now + - old + - optional + - policy + - redirect + - report + - request + - rescue + - resolve + - response + - retry + - session + - tap + - throw_if + - throw_unless + - today + - trans + - trans_choice + - url + - validator + - view + - with + +# Testing Utilities +testing: + fakes: + - EventFake + - MailFake + - NotificationFake + - QueueFake + - BusFake