gitflow-feature-stash: pstewart-refactor
This commit is contained in:
parent
4b46016e35
commit
28f693a681
416 changed files with 53433 additions and 13 deletions
|
@ -3,6 +3,7 @@ repository: https://github.com/protevus/platform
|
||||||
packages:
|
packages:
|
||||||
- apps/**
|
- apps/**
|
||||||
- packages/**
|
- packages/**
|
||||||
|
- sandbox/**
|
||||||
- helpers/tools/**
|
- helpers/tools/**
|
||||||
- examples/**
|
- examples/**
|
||||||
|
|
||||||
|
|
77
docs/.gitignore
vendored
Normal file
77
docs/.gitignore
vendored
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# Generated documentation
|
||||||
|
api/
|
||||||
|
build/
|
||||||
|
site/
|
||||||
|
_site/
|
||||||
|
.dart_tool/
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
doc/api/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
*.ipr
|
||||||
|
.settings/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
*.html
|
||||||
|
*.pdf
|
||||||
|
*.epub
|
||||||
|
*.mobi
|
||||||
|
*.docx
|
||||||
|
*.doc
|
||||||
|
*.rtf
|
||||||
|
|
||||||
|
# Local configuration
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
*.local
|
||||||
|
local.*
|
||||||
|
|
||||||
|
# Documentation tools
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
pubspec.lock
|
||||||
|
|
||||||
|
# Generated diagrams
|
||||||
|
*.svg
|
||||||
|
*.png
|
||||||
|
*.jpg
|
||||||
|
*.jpeg
|
||||||
|
*.gif
|
||||||
|
!assets/*.svg
|
||||||
|
!assets/*.png
|
||||||
|
!assets/*.jpg
|
||||||
|
!assets/*.jpeg
|
||||||
|
!assets/*.gif
|
||||||
|
|
||||||
|
# Generated markdown
|
||||||
|
*.generated.md
|
||||||
|
*.auto.md
|
||||||
|
*.temp.md
|
||||||
|
|
||||||
|
# Coverage reports
|
||||||
|
coverage/
|
||||||
|
.coverage/
|
||||||
|
coverage.xml
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
|
log/
|
||||||
|
logs/
|
163
docs/CHANGELOG.md
Normal file
163
docs/CHANGELOG.md
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
# Documentation Changelog
|
||||||
|
|
||||||
|
All notable changes to framework documentation will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.0] - 2024-01-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Core Documentation
|
||||||
|
* Created Getting Started Guide
|
||||||
|
* Created Laravel Compatibility Roadmap
|
||||||
|
* Created Foundation Integration Guide
|
||||||
|
* Created Testing Guide
|
||||||
|
* Created Package Integration Map
|
||||||
|
* Created README.md
|
||||||
|
* Created index.md
|
||||||
|
* Created CONTRIBUTING.md
|
||||||
|
* Created CHANGELOG.md
|
||||||
|
|
||||||
|
- Core Architecture
|
||||||
|
* Created Core Architecture documentation
|
||||||
|
* Created Core Package Specification
|
||||||
|
|
||||||
|
- Package Specifications
|
||||||
|
* Created Container Package Specification
|
||||||
|
* Created Contracts Package Specification
|
||||||
|
* Created Events Package Specification
|
||||||
|
* Created Pipeline Package Specification
|
||||||
|
* Created Support Package Specification
|
||||||
|
* Created Bus Package Specification
|
||||||
|
* Created Config Package Specification
|
||||||
|
* Created Filesystem Package Specification
|
||||||
|
* Created Model Package Specification
|
||||||
|
* Created Process Package Specification
|
||||||
|
* Created Queue Package Specification
|
||||||
|
* Created Route Package Specification
|
||||||
|
* Created Testing Package Specification
|
||||||
|
|
||||||
|
- Gap Analyses
|
||||||
|
* Created Container Gap Analysis
|
||||||
|
* Created Events Gap Analysis
|
||||||
|
* Created Pipeline Gap Analysis
|
||||||
|
* Created Bus Gap Analysis
|
||||||
|
* Created Config Gap Analysis
|
||||||
|
* Created Filesystem Gap Analysis
|
||||||
|
* Created Model Gap Analysis
|
||||||
|
* Created Process Gap Analysis
|
||||||
|
* Created Queue Gap Analysis
|
||||||
|
* Created Route Gap Analysis
|
||||||
|
* Created Testing Gap Analysis
|
||||||
|
|
||||||
|
- Integration Guides
|
||||||
|
* Created Container Feature Integration
|
||||||
|
* Created Container Migration Guide
|
||||||
|
|
||||||
|
### Documentation Features
|
||||||
|
- Complete package specifications with no placeholders
|
||||||
|
- Comprehensive gap analyses for Laravel compatibility
|
||||||
|
- Detailed integration guides and examples
|
||||||
|
- Cross-referenced documentation
|
||||||
|
- Consistent style and formatting
|
||||||
|
- Development guidelines for each package
|
||||||
|
- Testing requirements and examples
|
||||||
|
- Performance considerations
|
||||||
|
- Security guidelines
|
||||||
|
|
||||||
|
### Documentation Structure
|
||||||
|
- Organized by package
|
||||||
|
- Clear navigation
|
||||||
|
- Related documentation links
|
||||||
|
- Code examples
|
||||||
|
- Implementation status
|
||||||
|
- Development guidelines
|
||||||
|
|
||||||
|
### Documentation Standards
|
||||||
|
- No placeholders or truncation
|
||||||
|
- Complete code examples
|
||||||
|
- Working cross-references
|
||||||
|
- Proper markdown formatting
|
||||||
|
- Technical accuracy
|
||||||
|
|
||||||
|
### Contributing Guidelines
|
||||||
|
- Documentation standards
|
||||||
|
- Writing style guide
|
||||||
|
- Review process
|
||||||
|
- Development workflow
|
||||||
|
- Content organization
|
||||||
|
|
||||||
|
## Types of Changes
|
||||||
|
- `Added` for new documentation
|
||||||
|
- `Changed` for changes in existing documentation
|
||||||
|
- `Deprecated` for soon-to-be removed documentation
|
||||||
|
- `Removed` for now removed documentation
|
||||||
|
- `Fixed` for any documentation fixes
|
||||||
|
- `Security` for documentation about security updates
|
||||||
|
|
||||||
|
## Maintaining the Changelog
|
||||||
|
|
||||||
|
### Format
|
||||||
|
Each version should:
|
||||||
|
1. List version number and date
|
||||||
|
2. Group changes by type
|
||||||
|
3. List changes with bullet points
|
||||||
|
4. Reference related issues/PRs
|
||||||
|
5. Credit contributors
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```markdown
|
||||||
|
## [1.1.0] - YYYY-MM-DD
|
||||||
|
### Added
|
||||||
|
- New documentation for feature X (#123)
|
||||||
|
- Guide for implementing Y (@contributor)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated Z documentation for clarity (#456)
|
||||||
|
- Improved examples in W guide (@contributor)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Corrected code example in V doc (#789)
|
||||||
|
- Fixed broken links in U guide (@contributor)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version Numbers
|
||||||
|
- MAJOR version for significant documentation restructuring
|
||||||
|
- MINOR version for new documentation additions
|
||||||
|
- PATCH version for documentation fixes and updates
|
||||||
|
|
||||||
|
## Unreleased Changes
|
||||||
|
|
||||||
|
### To Be Added
|
||||||
|
- Package-specific changelogs
|
||||||
|
- Version-specific documentation
|
||||||
|
- Migration guides for each version
|
||||||
|
- API documentation
|
||||||
|
- Additional examples
|
||||||
|
|
||||||
|
### To Be Updated
|
||||||
|
- Performance guidelines
|
||||||
|
- Security considerations
|
||||||
|
- Testing strategies
|
||||||
|
- Integration patterns
|
||||||
|
- Development workflows
|
||||||
|
|
||||||
|
## Previous Versions
|
||||||
|
|
||||||
|
No previous versions. This is the initial documentation release.
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
- Initial documentation team
|
||||||
|
- Package maintainers
|
||||||
|
- Documentation reviewers
|
||||||
|
- Community contributors
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
For questions about documentation changes:
|
||||||
|
1. Review CONTRIBUTING.md
|
||||||
|
2. Check existing documentation
|
||||||
|
3. Ask in pull request
|
||||||
|
4. Update changelog as needed
|
280
docs/CONTRIBUTING.md
Normal file
280
docs/CONTRIBUTING.md
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
# Contributing to Framework Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide explains how to contribute to our framework documentation. We maintain comprehensive documentation covering package specifications, gap analyses, integration guides, and architectural documentation.
|
||||||
|
|
||||||
|
## Documentation Structure
|
||||||
|
|
||||||
|
### Core Documentation
|
||||||
|
1. [Getting Started Guide](getting_started.md) - Framework introduction and setup
|
||||||
|
2. [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) - Implementation timeline
|
||||||
|
3. [Foundation Integration Guide](foundation_integration_guide.md) - Integration patterns
|
||||||
|
4. [Testing Guide](testing_guide.md) - Testing approaches
|
||||||
|
5. [Package Integration Map](package_integration_map.md) - Package relationships
|
||||||
|
|
||||||
|
### Core Architecture
|
||||||
|
1. [Core Architecture](core_architecture.md) - System design and patterns
|
||||||
|
2. [Core Package Specification](core_package_specification.md) - Core implementation
|
||||||
|
|
||||||
|
### Package Documentation
|
||||||
|
Each package has:
|
||||||
|
1. Package Specification - Implementation details
|
||||||
|
2. Gap Analysis - Laravel compatibility gaps
|
||||||
|
3. Integration Guide - Package integration patterns
|
||||||
|
4. Development Guidelines - Implementation standards
|
||||||
|
|
||||||
|
## Contribution Guidelines
|
||||||
|
|
||||||
|
### 1. Documentation Standards
|
||||||
|
|
||||||
|
#### File Naming
|
||||||
|
- Use lowercase with underscores
|
||||||
|
- End with .md extension
|
||||||
|
- Be descriptive and specific
|
||||||
|
- Examples:
|
||||||
|
* package_specification.md
|
||||||
|
* gap_analysis.md
|
||||||
|
* integration_guide.md
|
||||||
|
|
||||||
|
#### File Structure
|
||||||
|
- Start with # Title
|
||||||
|
- Include Overview section
|
||||||
|
- Add Related Documentation links
|
||||||
|
- Use clear section headers
|
||||||
|
- Include code examples
|
||||||
|
- End with development guidelines
|
||||||
|
|
||||||
|
#### Content Requirements
|
||||||
|
- No placeholders or truncation
|
||||||
|
- Complete code examples
|
||||||
|
- Clear cross-references
|
||||||
|
- Proper markdown formatting
|
||||||
|
- Comprehensive coverage
|
||||||
|
|
||||||
|
### 2. Writing Style
|
||||||
|
|
||||||
|
#### Technical Writing
|
||||||
|
- Be clear and concise
|
||||||
|
- Use active voice
|
||||||
|
- Write in present tense
|
||||||
|
- Focus on technical accuracy
|
||||||
|
- Include practical examples
|
||||||
|
|
||||||
|
#### Code Examples
|
||||||
|
```dart
|
||||||
|
// Include complete, working examples
|
||||||
|
class Example {
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
Example(this.name);
|
||||||
|
|
||||||
|
void demonstrate() {
|
||||||
|
print('Demonstrating: $name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show usage
|
||||||
|
var example = Example('feature');
|
||||||
|
example.demonstrate();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Cross-References
|
||||||
|
- Use relative links
|
||||||
|
- Link to related docs
|
||||||
|
- Reference specific sections
|
||||||
|
- Example:
|
||||||
|
```markdown
|
||||||
|
See [Container Integration](container_package_specification.md#integration) for details.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Documentation Types
|
||||||
|
|
||||||
|
#### Package Specification
|
||||||
|
- Implementation details
|
||||||
|
- API documentation
|
||||||
|
- Integration examples
|
||||||
|
- Testing guidelines
|
||||||
|
- Development workflow
|
||||||
|
|
||||||
|
#### Gap Analysis
|
||||||
|
- Current implementation
|
||||||
|
- Laravel features
|
||||||
|
- Missing functionality
|
||||||
|
- Implementation plan
|
||||||
|
- Priority order
|
||||||
|
|
||||||
|
#### Integration Guide
|
||||||
|
- Integration points
|
||||||
|
- Package dependencies
|
||||||
|
- Code examples
|
||||||
|
- Best practices
|
||||||
|
- Common patterns
|
||||||
|
|
||||||
|
### 4. Review Process
|
||||||
|
|
||||||
|
#### Before Submitting
|
||||||
|
1. Check content completeness
|
||||||
|
2. Verify code examples
|
||||||
|
3. Test all links
|
||||||
|
4. Run markdown linter
|
||||||
|
5. Review formatting
|
||||||
|
|
||||||
|
#### Pull Request
|
||||||
|
1. Clear description
|
||||||
|
2. Reference related issues
|
||||||
|
3. List documentation changes
|
||||||
|
4. Include review checklist
|
||||||
|
5. Add relevant labels
|
||||||
|
|
||||||
|
#### Review Checklist
|
||||||
|
- [ ] No placeholders or truncation
|
||||||
|
- [ ] Complete code examples
|
||||||
|
- [ ] Working cross-references
|
||||||
|
- [ ] Proper formatting
|
||||||
|
- [ ] Technical accuracy
|
||||||
|
|
||||||
|
### 5. Development Workflow
|
||||||
|
|
||||||
|
#### Creating Documentation
|
||||||
|
1. Create feature branch
|
||||||
|
2. Write documentation
|
||||||
|
3. Add code examples
|
||||||
|
4. Include cross-references
|
||||||
|
5. Submit pull request
|
||||||
|
|
||||||
|
#### Updating Documentation
|
||||||
|
1. Review existing content
|
||||||
|
2. Make necessary changes
|
||||||
|
3. Update related docs
|
||||||
|
4. Verify all links
|
||||||
|
5. Submit pull request
|
||||||
|
|
||||||
|
#### Review Process
|
||||||
|
1. Technical review
|
||||||
|
2. Style review
|
||||||
|
3. Code example review
|
||||||
|
4. Cross-reference check
|
||||||
|
5. Final approval
|
||||||
|
|
||||||
|
## Style Guide
|
||||||
|
|
||||||
|
### 1. Markdown
|
||||||
|
|
||||||
|
#### Headers
|
||||||
|
```markdown
|
||||||
|
# Main Title
|
||||||
|
## Section Title
|
||||||
|
### Subsection Title
|
||||||
|
#### Minor Section
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lists
|
||||||
|
```markdown
|
||||||
|
1. Ordered Item
|
||||||
|
2. Ordered Item
|
||||||
|
- Unordered Sub-item
|
||||||
|
- Unordered Sub-item
|
||||||
|
|
||||||
|
- Unordered Item
|
||||||
|
- Unordered Item
|
||||||
|
1. Ordered Sub-item
|
||||||
|
2. Ordered Sub-item
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Code Blocks
|
||||||
|
```markdown
|
||||||
|
/// Code with syntax highlighting
|
||||||
|
```dart
|
||||||
|
class Example {
|
||||||
|
void method() {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
/// Inline code
|
||||||
|
Use `var` for variable declaration.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Links
|
||||||
|
```markdown
|
||||||
|
[Link Text](relative/path/to/file.md)
|
||||||
|
[Link Text](relative/path/to/file.md#section)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Content Organization
|
||||||
|
|
||||||
|
#### Package Documentation
|
||||||
|
1. Overview
|
||||||
|
2. Core Features
|
||||||
|
3. Integration Examples
|
||||||
|
4. Testing
|
||||||
|
5. Development Guidelines
|
||||||
|
|
||||||
|
#### Gap Analysis
|
||||||
|
1. Overview
|
||||||
|
2. Missing Features
|
||||||
|
3. Implementation Gaps
|
||||||
|
4. Priority Order
|
||||||
|
5. Next Steps
|
||||||
|
|
||||||
|
#### Integration Guide
|
||||||
|
1. Overview
|
||||||
|
2. Integration Points
|
||||||
|
3. Code Examples
|
||||||
|
4. Best Practices
|
||||||
|
5. Development Guidelines
|
||||||
|
|
||||||
|
### 3. Code Examples
|
||||||
|
|
||||||
|
#### Complete Examples
|
||||||
|
```dart
|
||||||
|
// Include all necessary imports
|
||||||
|
import 'package:framework/core.dart';
|
||||||
|
|
||||||
|
// Show complete implementation
|
||||||
|
class ServiceProvider {
|
||||||
|
final Container container;
|
||||||
|
|
||||||
|
ServiceProvider(this.container);
|
||||||
|
|
||||||
|
void register() {
|
||||||
|
container.singleton<Service>((c) =>
|
||||||
|
ServiceImplementation()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demonstrate usage
|
||||||
|
void main() {
|
||||||
|
var container = Container();
|
||||||
|
var provider = ServiceProvider(container);
|
||||||
|
provider.register();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Integration Examples
|
||||||
|
```dart
|
||||||
|
// Show real integration scenarios
|
||||||
|
class UserService {
|
||||||
|
final EventDispatcher events;
|
||||||
|
final Database db;
|
||||||
|
|
||||||
|
UserService(this.events, this.db);
|
||||||
|
|
||||||
|
Future<void> createUser(User user) async {
|
||||||
|
await events.dispatch(UserCreating(user));
|
||||||
|
await db.users.insert(user);
|
||||||
|
await events.dispatch(UserCreated(user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
For questions or clarification:
|
||||||
|
1. Review existing documentation
|
||||||
|
2. Check style guide
|
||||||
|
3. Ask in pull request
|
||||||
|
4. Update guidelines as needed
|
271
docs/README.md
Normal file
271
docs/README.md
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
# Framework Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This documentation covers our Dart framework implementation, including Laravel compatibility, package specifications, and architectural guides. The framework provides Laravel's powerful features and patterns while leveraging Dart's strengths.
|
||||||
|
|
||||||
|
## Documentation Structure
|
||||||
|
|
||||||
|
### Core Documentation
|
||||||
|
1. [Getting Started Guide](getting_started.md) - Framework introduction and setup
|
||||||
|
2. [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) - Implementation timeline
|
||||||
|
3. [Foundation Integration Guide](foundation_integration_guide.md) - Integration patterns
|
||||||
|
4. [Testing Guide](testing_guide.md) - Testing approaches and patterns
|
||||||
|
5. [Package Integration Map](package_integration_map.md) - Package relationships
|
||||||
|
|
||||||
|
### Core Architecture
|
||||||
|
1. [Core Architecture](core_architecture.md) - System design and patterns
|
||||||
|
- Architectural decisions
|
||||||
|
- System patterns
|
||||||
|
- Extension points
|
||||||
|
- Package interactions
|
||||||
|
|
||||||
|
### Package Documentation
|
||||||
|
|
||||||
|
#### Core Framework
|
||||||
|
1. Core Package
|
||||||
|
- [Core Package Specification](core_package_specification.md)
|
||||||
|
- [Core Architecture](core_architecture.md)
|
||||||
|
|
||||||
|
2. Container Package
|
||||||
|
- [Container Package Specification](container_package_specification.md)
|
||||||
|
- [Container Gap Analysis](container_gap_analysis.md)
|
||||||
|
- [Container Feature Integration](container_feature_integration.md)
|
||||||
|
- [Container Migration Guide](container_migration_guide.md)
|
||||||
|
|
||||||
|
3. Contracts Package
|
||||||
|
- [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
4. Events Package
|
||||||
|
- [Events Package Specification](events_package_specification.md)
|
||||||
|
- [Events Gap Analysis](events_gap_analysis.md)
|
||||||
|
|
||||||
|
5. Pipeline Package
|
||||||
|
- [Pipeline Package Specification](pipeline_package_specification.md)
|
||||||
|
- [Pipeline Gap Analysis](pipeline_gap_analysis.md)
|
||||||
|
|
||||||
|
6. Support Package
|
||||||
|
- [Support Package Specification](support_package_specification.md)
|
||||||
|
|
||||||
|
#### Infrastructure
|
||||||
|
1. Bus Package
|
||||||
|
- [Bus Package Specification](bus_package_specification.md)
|
||||||
|
- [Bus Gap Analysis](bus_gap_analysis.md)
|
||||||
|
|
||||||
|
2. Config Package
|
||||||
|
- [Config Package Specification](config_package_specification.md)
|
||||||
|
- [Config Gap Analysis](config_gap_analysis.md)
|
||||||
|
|
||||||
|
3. Filesystem Package
|
||||||
|
- [Filesystem Package Specification](filesystem_package_specification.md)
|
||||||
|
- [Filesystem Gap Analysis](filesystem_gap_analysis.md)
|
||||||
|
|
||||||
|
4. Model Package
|
||||||
|
- [Model Package Specification](model_package_specification.md)
|
||||||
|
- [Model Gap Analysis](model_gap_analysis.md)
|
||||||
|
|
||||||
|
5. Process Package
|
||||||
|
- [Process Package Specification](process_package_specification.md)
|
||||||
|
- [Process Gap Analysis](process_gap_analysis.md)
|
||||||
|
|
||||||
|
6. Queue Package
|
||||||
|
- [Queue Package Specification](queue_package_specification.md)
|
||||||
|
- [Queue Gap Analysis](queue_gap_analysis.md)
|
||||||
|
|
||||||
|
7. Route Package
|
||||||
|
- [Route Package Specification](route_package_specification.md)
|
||||||
|
- [Route Gap Analysis](route_gap_analysis.md)
|
||||||
|
|
||||||
|
8. Testing Package
|
||||||
|
- [Testing Package Specification](testing_package_specification.md)
|
||||||
|
- [Testing Gap Analysis](testing_gap_analysis.md)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. **Understanding the Framework**
|
||||||
|
```dart
|
||||||
|
// Start with these documents in order:
|
||||||
|
1. Getting Started Guide
|
||||||
|
2. Core Architecture
|
||||||
|
3. Laravel Compatibility Roadmap
|
||||||
|
4. Foundation Integration Guide
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Package Development**
|
||||||
|
```dart
|
||||||
|
// For each package:
|
||||||
|
1. Review package specification
|
||||||
|
2. Check gap analysis
|
||||||
|
3. Follow integration guide
|
||||||
|
4. Write tests
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Development Workflow**
|
||||||
|
```dart
|
||||||
|
// For each feature:
|
||||||
|
1. Review specifications
|
||||||
|
2. Write tests
|
||||||
|
3. Implement changes
|
||||||
|
4. Update documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
### 1. Service Container Architecture
|
||||||
|
```dart
|
||||||
|
// Core application setup
|
||||||
|
var container = Container();
|
||||||
|
var app = Application(container)
|
||||||
|
..environment = 'production'
|
||||||
|
..basePath = Directory.current.path;
|
||||||
|
|
||||||
|
await app.boot();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Service Providers
|
||||||
|
```dart
|
||||||
|
class AppServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
// Register services
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void boot() {
|
||||||
|
// Bootstrap services
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Package Integration
|
||||||
|
```dart
|
||||||
|
// Cross-package usage
|
||||||
|
class UserService {
|
||||||
|
final EventDispatcher events;
|
||||||
|
final Queue queue;
|
||||||
|
|
||||||
|
Future<void> process(User user) async {
|
||||||
|
await events.dispatch(UserProcessing(user));
|
||||||
|
await queue.push(ProcessUser(user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Status
|
||||||
|
|
||||||
|
### Core Framework (90%)
|
||||||
|
- Core Package (95%)
|
||||||
|
* Application lifecycle ✓
|
||||||
|
* Service providers ✓
|
||||||
|
* HTTP kernel ✓
|
||||||
|
* Console kernel ✓
|
||||||
|
* Exception handling ✓
|
||||||
|
* Needs: Performance optimizations
|
||||||
|
|
||||||
|
- Container Package (90%)
|
||||||
|
* Basic DI ✓
|
||||||
|
* Auto-wiring ✓
|
||||||
|
* Service providers ✓
|
||||||
|
* Needs: Contextual binding
|
||||||
|
|
||||||
|
### Infrastructure (80%)
|
||||||
|
- Bus Package (85%)
|
||||||
|
* Command dispatching ✓
|
||||||
|
* Command queuing ✓
|
||||||
|
* Needs: Command batching
|
||||||
|
|
||||||
|
- Config Package (80%)
|
||||||
|
* Configuration repository ✓
|
||||||
|
* Environment loading ✓
|
||||||
|
* Needs: Config caching
|
||||||
|
|
||||||
|
[Previous implementation status content remains exactly the same]
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. **Before Starting**
|
||||||
|
- Review relevant documentation
|
||||||
|
- Check implementation status
|
||||||
|
- Understand dependencies
|
||||||
|
- Write tests first
|
||||||
|
|
||||||
|
2. **Development Process**
|
||||||
|
```dart
|
||||||
|
// 1. Create feature branch
|
||||||
|
git checkout -b feature/package-name/feature-name
|
||||||
|
|
||||||
|
// 2. Write tests
|
||||||
|
void main() {
|
||||||
|
test('should implement feature', () {
|
||||||
|
// Test implementation
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Implement feature
|
||||||
|
class Implementation {
|
||||||
|
// Feature code
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Submit PR
|
||||||
|
// - Include tests
|
||||||
|
// - Update documentation
|
||||||
|
// - Add examples
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Code Review**
|
||||||
|
- Verify specifications
|
||||||
|
- Check test coverage
|
||||||
|
- Review documentation
|
||||||
|
- Validate performance
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **API Design**
|
||||||
|
```dart
|
||||||
|
// Follow framework patterns
|
||||||
|
class Service {
|
||||||
|
// Match framework method signatures
|
||||||
|
Future<void> handle();
|
||||||
|
Future<void> process();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Testing**
|
||||||
|
```dart
|
||||||
|
// Comprehensive test coverage
|
||||||
|
void main() {
|
||||||
|
group('Feature', () {
|
||||||
|
// Unit tests
|
||||||
|
// Integration tests
|
||||||
|
// Performance tests
|
||||||
|
// Error cases
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Documentation**
|
||||||
|
```dart
|
||||||
|
/// Document framework compatibility
|
||||||
|
class Service {
|
||||||
|
/// Processes data following framework patterns.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// var service = container.make<Service>();
|
||||||
|
/// await service.process();
|
||||||
|
/// ```
|
||||||
|
Future<void> process();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
For questions or clarification:
|
||||||
|
1. Review relevant documentation
|
||||||
|
2. Check implementation examples
|
||||||
|
3. Consult team leads
|
||||||
|
4. Update documentation as needed
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This framework is open-sourced software licensed under the [MIT license](../LICENSE).
|
231
docs/assets/README.md
Normal file
231
docs/assets/README.md
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
# Documentation Assets
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
assets/
|
||||||
|
├── diagrams/ # Architecture and flow diagrams
|
||||||
|
├── images/ # Screenshots and general images
|
||||||
|
├── logos/ # Framework and package logos
|
||||||
|
└── icons/ # UI and feature icons
|
||||||
|
```
|
||||||
|
|
||||||
|
## Asset Organization
|
||||||
|
|
||||||
|
### 1. Diagrams
|
||||||
|
- Architecture diagrams
|
||||||
|
- Flow charts
|
||||||
|
- Sequence diagrams
|
||||||
|
- Component diagrams
|
||||||
|
- Class diagrams
|
||||||
|
|
||||||
|
Example naming:
|
||||||
|
```
|
||||||
|
diagrams/
|
||||||
|
├── architecture/
|
||||||
|
│ ├── system_overview.svg
|
||||||
|
│ ├── package_dependencies.svg
|
||||||
|
│ └── service_interaction.svg
|
||||||
|
├── flows/
|
||||||
|
│ ├── request_lifecycle.svg
|
||||||
|
│ ├── event_handling.svg
|
||||||
|
│ └── queue_processing.svg
|
||||||
|
└── sequences/
|
||||||
|
├── authentication_flow.svg
|
||||||
|
├── job_dispatch.svg
|
||||||
|
└── model_events.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Images
|
||||||
|
- Documentation screenshots
|
||||||
|
- Example outputs
|
||||||
|
- Visual guides
|
||||||
|
- Tutorial images
|
||||||
|
|
||||||
|
Example naming:
|
||||||
|
```
|
||||||
|
images/
|
||||||
|
├── getting_started/
|
||||||
|
│ ├── installation_step1.png
|
||||||
|
│ ├── configuration_step2.png
|
||||||
|
│ └── running_tests_step3.png
|
||||||
|
├── tutorials/
|
||||||
|
│ ├── creating_service_provider.png
|
||||||
|
│ ├── setting_up_queue.png
|
||||||
|
│ └── configuring_cache.png
|
||||||
|
└── examples/
|
||||||
|
├── api_response.png
|
||||||
|
├── console_output.png
|
||||||
|
└── test_results.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Logos
|
||||||
|
- Framework logos
|
||||||
|
- Package logos
|
||||||
|
- Integration logos
|
||||||
|
- Partner logos
|
||||||
|
|
||||||
|
Example naming:
|
||||||
|
```
|
||||||
|
logos/
|
||||||
|
├── framework/
|
||||||
|
│ ├── full_logo.svg
|
||||||
|
│ ├── icon_only.svg
|
||||||
|
│ └── text_only.svg
|
||||||
|
├── packages/
|
||||||
|
│ ├── container_logo.svg
|
||||||
|
│ ├── events_logo.svg
|
||||||
|
│ └── queue_logo.svg
|
||||||
|
└── partners/
|
||||||
|
├── vendor_logo.svg
|
||||||
|
├── cloud_logo.svg
|
||||||
|
└── tools_logo.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Icons
|
||||||
|
- Feature icons
|
||||||
|
- UI elements
|
||||||
|
- Status indicators
|
||||||
|
- Action icons
|
||||||
|
|
||||||
|
Example naming:
|
||||||
|
```
|
||||||
|
icons/
|
||||||
|
├── features/
|
||||||
|
│ ├── caching.svg
|
||||||
|
│ ├── queuing.svg
|
||||||
|
│ └── routing.svg
|
||||||
|
├── status/
|
||||||
|
│ ├── success.svg
|
||||||
|
│ ├── warning.svg
|
||||||
|
│ └── error.svg
|
||||||
|
└── actions/
|
||||||
|
├── configure.svg
|
||||||
|
├── deploy.svg
|
||||||
|
└── monitor.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
|
||||||
|
1. **File Names**
|
||||||
|
- Use lowercase
|
||||||
|
- Use underscores for spaces
|
||||||
|
- Include category prefix
|
||||||
|
- Include size/variant suffix
|
||||||
|
- Examples:
|
||||||
|
* diagram_system_overview_large.svg
|
||||||
|
* screenshot_installation_step1.png
|
||||||
|
* logo_framework_dark.svg
|
||||||
|
* icon_feature_cache_16px.svg
|
||||||
|
|
||||||
|
2. **Directory Names**
|
||||||
|
- Use lowercase
|
||||||
|
- Use descriptive categories
|
||||||
|
- Group related assets
|
||||||
|
- Examples:
|
||||||
|
* diagrams/architecture/
|
||||||
|
* images/tutorials/
|
||||||
|
* logos/packages/
|
||||||
|
* icons/features/
|
||||||
|
|
||||||
|
## File Formats
|
||||||
|
|
||||||
|
1. **Diagrams**
|
||||||
|
- SVG (preferred for diagrams)
|
||||||
|
- PNG (when SVG not possible)
|
||||||
|
- Source files in separate repo
|
||||||
|
|
||||||
|
2. **Images**
|
||||||
|
- PNG (preferred for screenshots)
|
||||||
|
- JPG (for photos)
|
||||||
|
- WebP (for web optimization)
|
||||||
|
|
||||||
|
3. **Logos**
|
||||||
|
- SVG (preferred for logos)
|
||||||
|
- PNG (with multiple resolutions)
|
||||||
|
- Include source files
|
||||||
|
|
||||||
|
4. **Icons**
|
||||||
|
- SVG (preferred for icons)
|
||||||
|
- PNG (with multiple sizes)
|
||||||
|
- Include source files
|
||||||
|
|
||||||
|
## Usage Guidelines
|
||||||
|
|
||||||
|
1. **Diagrams**
|
||||||
|
- Use consistent styling
|
||||||
|
- Include source files
|
||||||
|
- Maintain aspect ratios
|
||||||
|
- Use standard colors
|
||||||
|
|
||||||
|
2. **Images**
|
||||||
|
- Optimize for web
|
||||||
|
- Use descriptive names
|
||||||
|
- Include alt text
|
||||||
|
- Maintain quality
|
||||||
|
|
||||||
|
3. **Logos**
|
||||||
|
- Follow brand guidelines
|
||||||
|
- Include all variants
|
||||||
|
- Maintain proportions
|
||||||
|
- Use vector formats
|
||||||
|
|
||||||
|
4. **Icons**
|
||||||
|
- Use consistent style
|
||||||
|
- Include multiple sizes
|
||||||
|
- Optimize for display
|
||||||
|
- Follow naming pattern
|
||||||
|
|
||||||
|
## Contributing Assets
|
||||||
|
|
||||||
|
1. **Adding New Assets**
|
||||||
|
- Follow naming conventions
|
||||||
|
- Use appropriate format
|
||||||
|
- Include source files
|
||||||
|
- Update documentation
|
||||||
|
|
||||||
|
2. **Updating Assets**
|
||||||
|
- Maintain version history
|
||||||
|
- Update all variants
|
||||||
|
- Keep source files
|
||||||
|
- Document changes
|
||||||
|
|
||||||
|
3. **Removing Assets**
|
||||||
|
- Update documentation
|
||||||
|
- Remove all variants
|
||||||
|
- Archive if needed
|
||||||
|
- Document removal
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **File Organization**
|
||||||
|
- Use correct directories
|
||||||
|
- Follow naming patterns
|
||||||
|
- Group related assets
|
||||||
|
- Maintain structure
|
||||||
|
|
||||||
|
2. **Version Control**
|
||||||
|
- Commit source files
|
||||||
|
- Track large files properly
|
||||||
|
- Document changes
|
||||||
|
- Use git LFS if needed
|
||||||
|
|
||||||
|
3. **Quality Control**
|
||||||
|
- Optimize for web
|
||||||
|
- Check resolutions
|
||||||
|
- Verify formats
|
||||||
|
- Test displays
|
||||||
|
|
||||||
|
4. **Documentation**
|
||||||
|
- Reference assets properly
|
||||||
|
- Include alt text
|
||||||
|
- Document sources
|
||||||
|
- Credit creators
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
For questions about assets:
|
||||||
|
1. Check naming conventions
|
||||||
|
2. Review directory structure
|
||||||
|
3. Consult usage guidelines
|
||||||
|
4. Ask in pull request
|
187
docs/assets/diagrams/architecture/README.md
Normal file
187
docs/assets/diagrams/architecture/README.md
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
# Architecture Diagrams
|
||||||
|
|
||||||
|
## System Overview
|
||||||
|
|
||||||
|
The `system_overview.mmd` diagram shows the high-level architecture of our framework, including:
|
||||||
|
|
||||||
|
1. Core System
|
||||||
|
- Application lifecycle
|
||||||
|
- Dependency injection (Container)
|
||||||
|
- Event handling
|
||||||
|
- Pipeline processing
|
||||||
|
|
||||||
|
2. HTTP Layer
|
||||||
|
- Server handling
|
||||||
|
- HTTP kernel
|
||||||
|
- Routing
|
||||||
|
- Controllers
|
||||||
|
|
||||||
|
3. Service Layer
|
||||||
|
- Configuration
|
||||||
|
- Caching
|
||||||
|
- Queue management
|
||||||
|
- Database operations
|
||||||
|
|
||||||
|
4. Infrastructure
|
||||||
|
- Filesystem operations
|
||||||
|
- Process management
|
||||||
|
- Command bus
|
||||||
|
- Model layer
|
||||||
|
|
||||||
|
5. Testing Integration
|
||||||
|
- Test cases
|
||||||
|
- HTTP testing
|
||||||
|
- Database testing
|
||||||
|
- Event testing
|
||||||
|
|
||||||
|
## Rendering the Diagram
|
||||||
|
|
||||||
|
### Using Mermaid CLI
|
||||||
|
```bash
|
||||||
|
# Install Mermaid CLI
|
||||||
|
npm install -g @mermaid-js/mermaid-cli
|
||||||
|
|
||||||
|
# Generate SVG
|
||||||
|
mmdc -i system_overview.mmd -o system_overview.svg
|
||||||
|
|
||||||
|
# Generate PNG
|
||||||
|
mmdc -i system_overview.mmd -o system_overview.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Online Tools
|
||||||
|
1. Visit [Mermaid Live Editor](https://mermaid.live)
|
||||||
|
2. Copy content of system_overview.mmd
|
||||||
|
3. Export as SVG or PNG
|
||||||
|
|
||||||
|
### Using VSCode
|
||||||
|
1. Install "Markdown Preview Mermaid Support" extension
|
||||||
|
2. Open system_overview.mmd
|
||||||
|
3. Use preview to view diagram
|
||||||
|
|
||||||
|
## Modifying the Diagram
|
||||||
|
|
||||||
|
### Component Structure
|
||||||
|
```mermaid
|
||||||
|
%% Component Template
|
||||||
|
subgraph ComponentName ["Display Name"]
|
||||||
|
Node1[Node One]
|
||||||
|
Node2[Node Two]
|
||||||
|
|
||||||
|
Node1 --> Node2
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Style Definitions
|
||||||
|
```mermaid
|
||||||
|
%% Style Classes
|
||||||
|
classDef core fill:#f9f,stroke:#333,stroke-width:2px
|
||||||
|
classDef http fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
classDef service fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
classDef infra fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
classDef test fill:#fff,stroke:#333,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Components
|
||||||
|
1. Define component in appropriate subgraph
|
||||||
|
2. Add relationships using arrows
|
||||||
|
3. Apply style class
|
||||||
|
4. Update documentation
|
||||||
|
|
||||||
|
## Component Descriptions
|
||||||
|
|
||||||
|
### Core System
|
||||||
|
- **Application**: Main entry point and lifecycle manager
|
||||||
|
- **Container**: Dependency injection container
|
||||||
|
- **Events**: Event dispatching and handling
|
||||||
|
- **Pipeline**: Request/process pipeline handling
|
||||||
|
|
||||||
|
### HTTP Layer
|
||||||
|
- **Server**: HTTP server implementation
|
||||||
|
- **Kernel**: HTTP request kernel
|
||||||
|
- **Router**: Route matching and handling
|
||||||
|
- **Controller**: Request controllers
|
||||||
|
|
||||||
|
### Service Layer
|
||||||
|
- **Config**: Configuration management
|
||||||
|
- **Cache**: Data caching
|
||||||
|
- **Queue**: Job queue management
|
||||||
|
- **Database**: Database operations
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
- **FileSystem**: File operations
|
||||||
|
- **Process**: Process management
|
||||||
|
- **Bus**: Command bus implementation
|
||||||
|
- **Model**: Data model layer
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- **TestCase**: Base test functionality
|
||||||
|
- **HttpTest**: HTTP testing utilities
|
||||||
|
- **DBTest**: Database testing utilities
|
||||||
|
- **EventTest**: Event testing utilities
|
||||||
|
|
||||||
|
## Relationships
|
||||||
|
|
||||||
|
### Core Dependencies
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
App --> Container
|
||||||
|
App --> Events
|
||||||
|
App --> Pipeline
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Registration
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
Container --> Services
|
||||||
|
Container --> Infrastructure
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request Flow
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
Server --> App
|
||||||
|
Controller --> Services
|
||||||
|
Services --> Infrastructure
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event Flow
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
Events --> Queue
|
||||||
|
Queue --> Process
|
||||||
|
Events --> Bus
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Adding Components**
|
||||||
|
- Place in appropriate subgraph
|
||||||
|
- Use consistent naming
|
||||||
|
- Add clear relationships
|
||||||
|
- Apply correct style
|
||||||
|
|
||||||
|
2. **Updating Relationships**
|
||||||
|
- Keep lines clear
|
||||||
|
- Show direct dependencies
|
||||||
|
- Avoid crossing lines
|
||||||
|
- Group related flows
|
||||||
|
|
||||||
|
3. **Maintaining Styles**
|
||||||
|
- Use defined classes
|
||||||
|
- Keep consistent colors
|
||||||
|
- Maintain line weights
|
||||||
|
- Use clear labels
|
||||||
|
|
||||||
|
4. **Documentation**
|
||||||
|
- Update README.md
|
||||||
|
- Explain changes
|
||||||
|
- Document relationships
|
||||||
|
- Keep synchronized
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
For questions about architecture diagrams:
|
||||||
|
1. Check diagram documentation
|
||||||
|
2. Review Mermaid syntax
|
||||||
|
3. Consult team leads
|
||||||
|
4. Update documentation
|
105
docs/assets/diagrams/architecture/system_overview.mmd
Normal file
105
docs/assets/diagrams/architecture/system_overview.mmd
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
graph TB
|
||||||
|
%% Core System
|
||||||
|
subgraph Core ["Core System"]
|
||||||
|
App[Application]
|
||||||
|
Container[Container]
|
||||||
|
Events[Events]
|
||||||
|
Pipeline[Pipeline]
|
||||||
|
|
||||||
|
App --> Container
|
||||||
|
App --> Events
|
||||||
|
App --> Pipeline
|
||||||
|
end
|
||||||
|
|
||||||
|
%% HTTP Layer
|
||||||
|
subgraph HTTP ["HTTP Layer"]
|
||||||
|
Server[HTTP Server]
|
||||||
|
Kernel[HTTP Kernel]
|
||||||
|
Router[Router]
|
||||||
|
Controller[Controller]
|
||||||
|
|
||||||
|
Server --> Kernel
|
||||||
|
Kernel --> Router
|
||||||
|
Router --> Controller
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Service Layer
|
||||||
|
subgraph Services ["Service Layer"]
|
||||||
|
Config[Config]
|
||||||
|
Cache[Cache]
|
||||||
|
Queue[Queue]
|
||||||
|
DB[Database]
|
||||||
|
|
||||||
|
Config --> Container
|
||||||
|
Cache --> Container
|
||||||
|
Queue --> Events
|
||||||
|
DB --> Events
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Infrastructure
|
||||||
|
subgraph Infrastructure ["Infrastructure"]
|
||||||
|
FileSystem[FileSystem]
|
||||||
|
Process[Process]
|
||||||
|
Bus[Command Bus]
|
||||||
|
Model[Model]
|
||||||
|
|
||||||
|
FileSystem --> Container
|
||||||
|
Process --> Queue
|
||||||
|
Bus --> Queue
|
||||||
|
Model --> Events
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Request Flow
|
||||||
|
Server --> App
|
||||||
|
Controller --> Services
|
||||||
|
Services --> Infrastructure
|
||||||
|
|
||||||
|
%% Event Flow
|
||||||
|
Events --> Queue
|
||||||
|
Queue --> Process
|
||||||
|
Events --> Bus
|
||||||
|
|
||||||
|
%% Service Provider Registration
|
||||||
|
Container --> Services
|
||||||
|
Container --> Infrastructure
|
||||||
|
|
||||||
|
%% Middleware Pipeline
|
||||||
|
Pipeline --> HTTP
|
||||||
|
Pipeline --> Services
|
||||||
|
|
||||||
|
%% Testing Integration
|
||||||
|
subgraph Testing ["Testing"]
|
||||||
|
TestCase[TestCase]
|
||||||
|
HttpTest[HTTP Tests]
|
||||||
|
DBTest[DB Tests]
|
||||||
|
EventTest[Event Tests]
|
||||||
|
|
||||||
|
TestCase --> App
|
||||||
|
HttpTest --> Server
|
||||||
|
DBTest --> DB
|
||||||
|
EventTest --> Events
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Style Definitions
|
||||||
|
classDef core fill:#f9f,stroke:#333,stroke-width:2px
|
||||||
|
classDef http fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
classDef service fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
classDef infra fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
classDef test fill:#fff,stroke:#333,stroke-width:2px
|
||||||
|
|
||||||
|
%% Apply Styles
|
||||||
|
class App,Container,Events,Pipeline core
|
||||||
|
class Server,Kernel,Router,Controller http
|
||||||
|
class Config,Cache,Queue,DB service
|
||||||
|
class FileSystem,Process,Bus,Model infra
|
||||||
|
class TestCase,HttpTest,DBTest,EventTest test
|
||||||
|
|
||||||
|
%% Relationships
|
||||||
|
linkStyle default stroke:#333,stroke-width:2px
|
||||||
|
|
||||||
|
%% Notes
|
||||||
|
%% Core System handles application lifecycle
|
||||||
|
%% HTTP Layer processes web requests
|
||||||
|
%% Service Layer provides business functionality
|
||||||
|
%% Infrastructure provides system services
|
||||||
|
%% Testing ensures system quality
|
625
docs/assets/diagrams/flows/README.md
Normal file
625
docs/assets/diagrams/flows/README.md
Normal file
|
@ -0,0 +1,625 @@
|
||||||
|
# Flow Diagrams
|
||||||
|
|
||||||
|
## Request Lifecycle
|
||||||
|
|
||||||
|
The `request_lifecycle.mmd` diagram shows the complete lifecycle of an HTTP request through our framework, including:
|
||||||
|
|
||||||
|
### 1. Entry Points
|
||||||
|
- Client HTTP Request
|
||||||
|
- Server Reception
|
||||||
|
- Kernel Handling
|
||||||
|
|
||||||
|
### 2. Middleware Processing
|
||||||
|
1. **Global Middleware**
|
||||||
|
- Maintenance Mode Check
|
||||||
|
- Post Size Validation
|
||||||
|
- String Trimming
|
||||||
|
- Empty to Null Conversion
|
||||||
|
|
||||||
|
2. **Route Middleware**
|
||||||
|
- Authentication
|
||||||
|
- Authorization
|
||||||
|
- Throttling
|
||||||
|
- CSRF Protection
|
||||||
|
|
||||||
|
3. **Response Middleware**
|
||||||
|
- Session Handling
|
||||||
|
- Cookie Processing
|
||||||
|
- Header Management
|
||||||
|
- Response Compression
|
||||||
|
|
||||||
|
### 3. Core Processing
|
||||||
|
1. **Route Resolution**
|
||||||
|
- Pattern Matching
|
||||||
|
- Parameter Binding
|
||||||
|
- Controller Resolution
|
||||||
|
|
||||||
|
2. **Controller Handling**
|
||||||
|
- Action Execution
|
||||||
|
- Parameter Injection
|
||||||
|
- Response Generation
|
||||||
|
|
||||||
|
### 4. Service Layer
|
||||||
|
1. **Business Logic**
|
||||||
|
- Service Processing
|
||||||
|
- Data Validation
|
||||||
|
- Business Rules
|
||||||
|
|
||||||
|
2. **Data Operations**
|
||||||
|
- Database Queries
|
||||||
|
- Cache Access
|
||||||
|
- File Operations
|
||||||
|
|
||||||
|
### 5. Event System
|
||||||
|
1. **Event Types**
|
||||||
|
- Model Events
|
||||||
|
- Custom Events
|
||||||
|
- System Events
|
||||||
|
|
||||||
|
2. **Event Processing**
|
||||||
|
- Synchronous Events
|
||||||
|
- Queued Events
|
||||||
|
- Broadcast Events
|
||||||
|
|
||||||
|
## Event Processing
|
||||||
|
|
||||||
|
The `event_processing.mmd` diagram shows the complete lifecycle of events through our framework, including:
|
||||||
|
|
||||||
|
### 1. Event Sources
|
||||||
|
- System Components
|
||||||
|
- User Actions
|
||||||
|
- External Triggers
|
||||||
|
- Scheduled Tasks
|
||||||
|
|
||||||
|
### 2. Event Types
|
||||||
|
1. **Immediate Events**
|
||||||
|
- Synchronous Processing
|
||||||
|
- Direct Response
|
||||||
|
- In-Memory Handling
|
||||||
|
|
||||||
|
2. **Queued Events**
|
||||||
|
- Asynchronous Processing
|
||||||
|
- Background Jobs
|
||||||
|
- Delayed Execution
|
||||||
|
|
||||||
|
3. **Broadcast Events**
|
||||||
|
- Real-time Updates
|
||||||
|
- WebSocket Integration
|
||||||
|
- Channel Broadcasting
|
||||||
|
|
||||||
|
### 3. Processing Components
|
||||||
|
1. **Event Dispatcher**
|
||||||
|
- Event Creation
|
||||||
|
- Type Detection
|
||||||
|
- Handler Resolution
|
||||||
|
- Event Routing
|
||||||
|
|
||||||
|
2. **Queue System**
|
||||||
|
- Job Queuing
|
||||||
|
- Background Processing
|
||||||
|
- Retry Handling
|
||||||
|
- Failed Job Management
|
||||||
|
|
||||||
|
3. **Broadcaster**
|
||||||
|
- Channel Management
|
||||||
|
- Real-time Delivery
|
||||||
|
- Client Connections
|
||||||
|
- Message Formatting
|
||||||
|
|
||||||
|
4. **Event Listeners**
|
||||||
|
- Event Handling
|
||||||
|
- Business Logic
|
||||||
|
- Response Generation
|
||||||
|
- Error Handling
|
||||||
|
|
||||||
|
### 4. Integration Points
|
||||||
|
1. **Database Operations**
|
||||||
|
- Transaction Management
|
||||||
|
- Data Persistence
|
||||||
|
- State Changes
|
||||||
|
- Audit Logging
|
||||||
|
|
||||||
|
2. **Cache Operations**
|
||||||
|
- Cache Invalidation
|
||||||
|
- Cache Updates
|
||||||
|
- Performance Optimization
|
||||||
|
- State Management
|
||||||
|
|
||||||
|
3. **Event Subscribers**
|
||||||
|
- Multiple Event Handling
|
||||||
|
- Event Grouping
|
||||||
|
- Subscriber Management
|
||||||
|
- Event Filtering
|
||||||
|
|
||||||
|
### 5. Channel Types
|
||||||
|
1. **Public Channels**
|
||||||
|
- Open Access
|
||||||
|
- Public Events
|
||||||
|
- General Updates
|
||||||
|
|
||||||
|
2. **Private Channels**
|
||||||
|
- Authentication Required
|
||||||
|
- User-Specific Events
|
||||||
|
- Secure Communication
|
||||||
|
|
||||||
|
3. **Presence Channels**
|
||||||
|
- User Presence
|
||||||
|
- Online Status
|
||||||
|
- User Lists
|
||||||
|
- Real-time State
|
||||||
|
|
||||||
|
## Queue Processing
|
||||||
|
|
||||||
|
The `queue_processing.mmd` diagram shows the complete lifecycle of queued jobs through our framework, including:
|
||||||
|
|
||||||
|
### 1. Job Sources
|
||||||
|
- Application Code
|
||||||
|
- Event Listeners
|
||||||
|
- Scheduled Tasks
|
||||||
|
- External Triggers
|
||||||
|
|
||||||
|
### 2. Job States
|
||||||
|
1. **Pending**
|
||||||
|
- Initial State
|
||||||
|
- Awaiting Processing
|
||||||
|
- In Queue
|
||||||
|
|
||||||
|
2. **Reserved**
|
||||||
|
- Worker Assigned
|
||||||
|
- Being Processed
|
||||||
|
- Locked for Processing
|
||||||
|
|
||||||
|
3. **Released**
|
||||||
|
- Failed Attempt
|
||||||
|
- Ready for Retry
|
||||||
|
- Back in Queue
|
||||||
|
|
||||||
|
4. **Failed**
|
||||||
|
- Max Retries Exceeded
|
||||||
|
- Permanent Failure
|
||||||
|
- Requires Attention
|
||||||
|
|
||||||
|
### 3. Processing Components
|
||||||
|
1. **Queue Manager**
|
||||||
|
- Job Registration
|
||||||
|
- Queue Selection
|
||||||
|
- Job Scheduling
|
||||||
|
- State Management
|
||||||
|
|
||||||
|
2. **Queue Worker**
|
||||||
|
- Job Processing
|
||||||
|
- Error Handling
|
||||||
|
- Retry Management
|
||||||
|
- Resource Cleanup
|
||||||
|
|
||||||
|
3. **Job Handler**
|
||||||
|
- Business Logic
|
||||||
|
- Data Processing
|
||||||
|
- Event Dispatching
|
||||||
|
- Error Reporting
|
||||||
|
|
||||||
|
4. **Failed Jobs**
|
||||||
|
- Failure Logging
|
||||||
|
- Retry Tracking
|
||||||
|
- Error Analysis
|
||||||
|
- Admin Notification
|
||||||
|
|
||||||
|
### 4. Integration Points
|
||||||
|
1. **Database Operations**
|
||||||
|
- Job Storage
|
||||||
|
- State Tracking
|
||||||
|
- Transaction Management
|
||||||
|
- Failure Logging
|
||||||
|
|
||||||
|
2. **Event System**
|
||||||
|
- Job Events
|
||||||
|
- Status Updates
|
||||||
|
- Error Notifications
|
||||||
|
- Progress Tracking
|
||||||
|
|
||||||
|
3. **Queue Monitor**
|
||||||
|
- Health Checks
|
||||||
|
- Performance Metrics
|
||||||
|
- Worker Status
|
||||||
|
- Error Rates
|
||||||
|
|
||||||
|
### 5. Processing Types
|
||||||
|
1. **Immediate Processing**
|
||||||
|
- Direct Execution
|
||||||
|
- No Delay
|
||||||
|
- Synchronous Option
|
||||||
|
|
||||||
|
2. **Delayed Processing**
|
||||||
|
- Scheduled Execution
|
||||||
|
- Time-based Delays
|
||||||
|
- Future Processing
|
||||||
|
|
||||||
|
3. **Batch Processing**
|
||||||
|
- Multiple Jobs
|
||||||
|
- Grouped Execution
|
||||||
|
- Bulk Operations
|
||||||
|
|
||||||
|
4. **Chained Processing**
|
||||||
|
- Sequential Jobs
|
||||||
|
- Dependent Tasks
|
||||||
|
- Pipeline Processing
|
||||||
|
|
||||||
|
### 6. Retry Strategy
|
||||||
|
1. **Exponential Backoff**
|
||||||
|
- Increasing Delays
|
||||||
|
- Retry Limits
|
||||||
|
- Failure Thresholds
|
||||||
|
|
||||||
|
2. **Custom Delays**
|
||||||
|
- Job-specific Timing
|
||||||
|
- Conditional Delays
|
||||||
|
- Priority-based
|
||||||
|
|
||||||
|
3. **Max Attempts**
|
||||||
|
- Attempt Limits
|
||||||
|
- Failure Handling
|
||||||
|
- Final State
|
||||||
|
|
||||||
|
## Model Lifecycle
|
||||||
|
|
||||||
|
The `model_lifecycle.mmd` diagram shows the complete lifecycle of models through our framework, including:
|
||||||
|
|
||||||
|
### 1. Model Operations
|
||||||
|
1. **Creation**
|
||||||
|
- Instance Creation
|
||||||
|
- Event Dispatching
|
||||||
|
- Database Insertion
|
||||||
|
- Cache Management
|
||||||
|
|
||||||
|
2. **Retrieval**
|
||||||
|
- Cache Checking
|
||||||
|
- Database Query
|
||||||
|
- Relationship Loading
|
||||||
|
- Model Hydration
|
||||||
|
|
||||||
|
3. **Update**
|
||||||
|
- Change Tracking
|
||||||
|
- Event Dispatching
|
||||||
|
- Database Update
|
||||||
|
- Cache Invalidation
|
||||||
|
|
||||||
|
4. **Deletion**
|
||||||
|
- Event Dispatching
|
||||||
|
- Database Deletion
|
||||||
|
- Cache Clearing
|
||||||
|
- Relationship Cleanup
|
||||||
|
|
||||||
|
### 2. Event Integration
|
||||||
|
1. **Lifecycle Events**
|
||||||
|
- Creating/Created
|
||||||
|
- Updating/Updated
|
||||||
|
- Deleting/Deleted
|
||||||
|
- Retrieved/Saving
|
||||||
|
|
||||||
|
2. **Relationship Events**
|
||||||
|
- Loading Relations
|
||||||
|
- Relation Loaded
|
||||||
|
- Relation Updated
|
||||||
|
- Relation Deleted
|
||||||
|
|
||||||
|
3. **Cache Events**
|
||||||
|
- Cache Hit/Miss
|
||||||
|
- Cache Stored
|
||||||
|
- Cache Invalidated
|
||||||
|
- Cache Cleared
|
||||||
|
|
||||||
|
### 3. Processing Components
|
||||||
|
1. **Model Instance**
|
||||||
|
- Attribute Management
|
||||||
|
- Change Tracking
|
||||||
|
- Event Dispatching
|
||||||
|
- State Management
|
||||||
|
|
||||||
|
2. **Event System**
|
||||||
|
- Event Creation
|
||||||
|
- Observer Notification
|
||||||
|
- Queue Integration
|
||||||
|
- Event Broadcasting
|
||||||
|
|
||||||
|
3. **Cache Layer**
|
||||||
|
- Cache Checking
|
||||||
|
- Cache Storage
|
||||||
|
- Cache Invalidation
|
||||||
|
- Cache Strategy
|
||||||
|
|
||||||
|
4. **Database Layer**
|
||||||
|
- Query Execution
|
||||||
|
- Record Management
|
||||||
|
- Transaction Handling
|
||||||
|
- Relationship Loading
|
||||||
|
|
||||||
|
### 4. Integration Points
|
||||||
|
1. **Observer System**
|
||||||
|
- Lifecycle Hooks
|
||||||
|
- Event Handling
|
||||||
|
- State Tracking
|
||||||
|
- Custom Logic
|
||||||
|
|
||||||
|
2. **Cache Strategy**
|
||||||
|
- Read Through
|
||||||
|
- Write Behind
|
||||||
|
- Cache Invalidation
|
||||||
|
- Cache Tags
|
||||||
|
|
||||||
|
3. **Queue Integration**
|
||||||
|
- Event Queueing
|
||||||
|
- Job Processing
|
||||||
|
- Async Operations
|
||||||
|
- Retry Handling
|
||||||
|
|
||||||
|
### 5. Model States
|
||||||
|
1. **Pending**
|
||||||
|
- New Instance
|
||||||
|
- Not Persisted
|
||||||
|
- No Events
|
||||||
|
|
||||||
|
2. **Active**
|
||||||
|
- Persisted
|
||||||
|
- Trackable
|
||||||
|
- Observable
|
||||||
|
|
||||||
|
3. **Modified**
|
||||||
|
- Changes Tracked
|
||||||
|
- Events Pending
|
||||||
|
- Cache Invalid
|
||||||
|
|
||||||
|
4. **Deleted**
|
||||||
|
- Soft Deleted
|
||||||
|
- Hard Deleted
|
||||||
|
- Cache Cleared
|
||||||
|
|
||||||
|
### 6. Performance Features
|
||||||
|
1. **Eager Loading**
|
||||||
|
- Relationship Loading
|
||||||
|
- Query Optimization
|
||||||
|
- N+1 Prevention
|
||||||
|
|
||||||
|
2. **Cache Management**
|
||||||
|
- Query Cache
|
||||||
|
- Model Cache
|
||||||
|
- Relationship Cache
|
||||||
|
|
||||||
|
3. **Batch Operations**
|
||||||
|
- Bulk Insert
|
||||||
|
- Bulk Update
|
||||||
|
- Bulk Delete
|
||||||
|
|
||||||
|
## Rendering the Diagrams
|
||||||
|
|
||||||
|
### Using Mermaid CLI
|
||||||
|
```bash
|
||||||
|
# Install Mermaid CLI
|
||||||
|
npm install -g @mermaid-js/mermaid-cli
|
||||||
|
|
||||||
|
# Generate SVG
|
||||||
|
mmdc -i request_lifecycle.mmd -o request_lifecycle.svg
|
||||||
|
mmdc -i event_processing.mmd -o event_processing.svg
|
||||||
|
mmdc -i queue_processing.mmd -o queue_processing.svg
|
||||||
|
mmdc -i model_lifecycle.mmd -o model_lifecycle.svg
|
||||||
|
|
||||||
|
# Generate PNG
|
||||||
|
mmdc -i request_lifecycle.mmd -o request_lifecycle.png
|
||||||
|
mmdc -i event_processing.mmd -o event_processing.png
|
||||||
|
mmdc -i queue_processing.mmd -o queue_processing.png
|
||||||
|
mmdc -i model_lifecycle.mmd -o model_lifecycle.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Online Tools
|
||||||
|
1. Visit [Mermaid Live Editor](https://mermaid.live)
|
||||||
|
2. Copy content of .mmd files
|
||||||
|
3. Export as SVG or PNG
|
||||||
|
|
||||||
|
### Using VSCode
|
||||||
|
1. Install "Markdown Preview Mermaid Support" extension
|
||||||
|
2. Open .mmd files
|
||||||
|
3. Use preview to view diagrams
|
||||||
|
|
||||||
|
## Modifying the Diagrams
|
||||||
|
|
||||||
|
### Sequence Structure
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant A
|
||||||
|
participant B
|
||||||
|
|
||||||
|
A->>B: Message
|
||||||
|
activate B
|
||||||
|
B-->>A: Response
|
||||||
|
deactivate B
|
||||||
|
|
||||||
|
Note over A,B: Description
|
||||||
|
```
|
||||||
|
|
||||||
|
### Style Definitions
|
||||||
|
```mermaid
|
||||||
|
style Client fill:#f9f,stroke:#333,stroke-width:2px
|
||||||
|
style Server fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
style Kernel fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
style Pipeline fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Components
|
||||||
|
1. Add participant declaration
|
||||||
|
2. Add message sequences
|
||||||
|
3. Add activation/deactivation
|
||||||
|
4. Add notes and descriptions
|
||||||
|
|
||||||
|
## Component Interactions
|
||||||
|
|
||||||
|
### 1. Request Processing
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Client->>Server: Request
|
||||||
|
Server->>Kernel: Handle
|
||||||
|
Kernel->>Pipeline: Process
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Middleware Chain
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Kernel->>Pipeline: Global Middleware
|
||||||
|
Pipeline->>Pipeline: Route Middleware
|
||||||
|
Pipeline->>Pipeline: Response Middleware
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Business Logic
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Controller->>Services: Process
|
||||||
|
Services->>DB: Query
|
||||||
|
Services->>Events: Dispatch
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Event Flow
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Source->>Dispatcher: Dispatch Event
|
||||||
|
Dispatcher->>Queue: Queue Event
|
||||||
|
Queue->>Listeners: Process Later
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Broadcasting
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Dispatcher->>Broadcast: Send Event
|
||||||
|
Broadcast->>Clients: Real-time Update
|
||||||
|
Clients-->>Broadcast: Received
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Queue Processing
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Source->>Queue: Dispatch Job
|
||||||
|
Queue->>Worker: Process Job
|
||||||
|
Worker->>Handler: Execute Job
|
||||||
|
Handler-->>Worker: Job Complete
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Model Operations
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Client->>Model: Create/Update/Delete
|
||||||
|
Model->>Events: Dispatch Event
|
||||||
|
Events->>Observer: Handle Event
|
||||||
|
Model->>DB: Persist Changes
|
||||||
|
Model->>Cache: Update Cache
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Details
|
||||||
|
|
||||||
|
### HTTP Layer
|
||||||
|
- **Server**: Handles raw HTTP requests
|
||||||
|
- **Kernel**: Manages request processing
|
||||||
|
- **Pipeline**: Executes middleware chain
|
||||||
|
- **Router**: Matches routes to controllers
|
||||||
|
|
||||||
|
### Processing Layer
|
||||||
|
- **Controller**: Handles business logic
|
||||||
|
- **Services**: Processes domain logic
|
||||||
|
- **Events**: Manages system events
|
||||||
|
- **Database**: Handles data persistence
|
||||||
|
|
||||||
|
### Event System
|
||||||
|
- **Dispatcher**: Central event hub
|
||||||
|
- **Queue**: Async processing
|
||||||
|
- **Broadcast**: Real-time updates
|
||||||
|
- **Listeners**: Event handlers
|
||||||
|
|
||||||
|
### Queue System
|
||||||
|
- **Manager**: Queue management
|
||||||
|
- **Worker**: Job processing
|
||||||
|
- **Handler**: Job execution
|
||||||
|
- **Monitor**: Health checks
|
||||||
|
|
||||||
|
### Model System
|
||||||
|
- **Model**: Data representation
|
||||||
|
- **Observer**: Event handling
|
||||||
|
- **Cache**: Performance layer
|
||||||
|
- **Relations**: Relationship management
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
- **Database**: Data persistence
|
||||||
|
- **Cache**: Performance layer
|
||||||
|
- **Subscribers**: Event consumers
|
||||||
|
- **External Systems**: Third-party services
|
||||||
|
|
||||||
|
### Processing Types
|
||||||
|
- **Synchronous**: Immediate processing
|
||||||
|
- **Asynchronous**: Queued processing
|
||||||
|
- **Real-time**: Broadcast processing
|
||||||
|
- **Batch**: Grouped processing
|
||||||
|
|
||||||
|
### Event Categories
|
||||||
|
- **System Events**: Framework operations
|
||||||
|
- **Domain Events**: Business logic
|
||||||
|
- **User Events**: User actions
|
||||||
|
- **Integration Events**: External systems
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Adding Sequences
|
||||||
|
- Use clear message names
|
||||||
|
- Show activation state
|
||||||
|
- Add relevant notes
|
||||||
|
- Group related actions
|
||||||
|
|
||||||
|
### 2. Updating Flow
|
||||||
|
- Maintain sequence order
|
||||||
|
- Show parallel operations
|
||||||
|
- Indicate async processes
|
||||||
|
- Document timing
|
||||||
|
|
||||||
|
### 3. Maintaining Style
|
||||||
|
- Use consistent colors
|
||||||
|
- Keep clear spacing
|
||||||
|
- Add helpful notes
|
||||||
|
- Use proper arrows
|
||||||
|
|
||||||
|
### 4. Documentation
|
||||||
|
- Update README.md
|
||||||
|
- Explain changes
|
||||||
|
- Document new flows
|
||||||
|
- Keep synchronized
|
||||||
|
|
||||||
|
### 5. Event Design
|
||||||
|
- Use clear event names
|
||||||
|
- Include necessary data
|
||||||
|
- Consider async needs
|
||||||
|
- Plan broadcast strategy
|
||||||
|
|
||||||
|
### 6. Queue Management
|
||||||
|
- Set appropriate delays
|
||||||
|
- Handle failures
|
||||||
|
- Monitor queue size
|
||||||
|
- Implement retries
|
||||||
|
|
||||||
|
### 7. Broadcast Strategy
|
||||||
|
- Choose correct channels
|
||||||
|
- Manage authentication
|
||||||
|
- Handle disconnections
|
||||||
|
- Optimize payload
|
||||||
|
|
||||||
|
### 8. Error Handling
|
||||||
|
- Log failures
|
||||||
|
- Implement retries
|
||||||
|
- Notify administrators
|
||||||
|
- Maintain state
|
||||||
|
|
||||||
|
### 9. Model Operations
|
||||||
|
- Track changes
|
||||||
|
- Manage cache
|
||||||
|
- Handle events
|
||||||
|
- Optimize queries
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
For questions about flow diagrams:
|
||||||
|
1. Check diagram documentation
|
||||||
|
2. Review Mermaid syntax
|
||||||
|
3. Consult team leads
|
||||||
|
4. Update documentation
|
112
docs/assets/diagrams/flows/event_processing.mmd
Normal file
112
docs/assets/diagrams/flows/event_processing.mmd
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
sequenceDiagram
|
||||||
|
participant Source as Event Source
|
||||||
|
participant Dispatcher as Event Dispatcher
|
||||||
|
participant Queue as Queue System
|
||||||
|
participant Broadcast as Broadcaster
|
||||||
|
participant Listeners as Event Listeners
|
||||||
|
participant DB as Database
|
||||||
|
participant Cache as Cache System
|
||||||
|
participant Subscribers as Event Subscribers
|
||||||
|
|
||||||
|
%% Event Creation
|
||||||
|
Source->>Dispatcher: Dispatch Event
|
||||||
|
activate Dispatcher
|
||||||
|
Note over Dispatcher: Event Created
|
||||||
|
|
||||||
|
%% Event Type Check
|
||||||
|
alt Is Queued Event
|
||||||
|
Dispatcher->>Queue: Push to Queue
|
||||||
|
activate Queue
|
||||||
|
Queue-->>Dispatcher: Queued Successfully
|
||||||
|
deactivate Queue
|
||||||
|
else Is Immediate Event
|
||||||
|
Dispatcher->>Listeners: Process Immediately
|
||||||
|
activate Listeners
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Broadcasting Check
|
||||||
|
alt Should Broadcast
|
||||||
|
Dispatcher->>Broadcast: Broadcast Event
|
||||||
|
activate Broadcast
|
||||||
|
Note over Broadcast: WebSocket/Redis/Pusher
|
||||||
|
Broadcast-->>Dispatcher: Broadcast Complete
|
||||||
|
deactivate Broadcast
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Database Operations
|
||||||
|
alt Has Database Operations
|
||||||
|
Dispatcher->>DB: Begin Transaction
|
||||||
|
activate DB
|
||||||
|
Note over DB: Event-related Changes
|
||||||
|
DB-->>Dispatcher: Transaction Complete
|
||||||
|
deactivate DB
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Cache Operations
|
||||||
|
alt Has Cache Operations
|
||||||
|
Dispatcher->>Cache: Update Cache
|
||||||
|
activate Cache
|
||||||
|
Note over Cache: Cache Invalidation/Update
|
||||||
|
Cache-->>Dispatcher: Cache Updated
|
||||||
|
deactivate Cache
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Event Subscribers
|
||||||
|
Dispatcher->>Subscribers: Notify Subscribers
|
||||||
|
activate Subscribers
|
||||||
|
Note over Subscribers: Handle Multiple Events
|
||||||
|
Subscribers-->>Dispatcher: Processing Complete
|
||||||
|
deactivate Subscribers
|
||||||
|
|
||||||
|
%% Queued Event Processing
|
||||||
|
alt Is Queued Event
|
||||||
|
Queue->>Listeners: Process Queue
|
||||||
|
activate Queue
|
||||||
|
Note over Queue: Background Processing
|
||||||
|
Listeners-->>Queue: Processing Complete
|
||||||
|
deactivate Queue
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Event Listeners Processing
|
||||||
|
Note over Listeners: Process Event
|
||||||
|
Listeners-->>Dispatcher: Handling Complete
|
||||||
|
deactivate Listeners
|
||||||
|
|
||||||
|
%% Event Completion
|
||||||
|
Dispatcher-->>Source: Event Processed
|
||||||
|
deactivate Dispatcher
|
||||||
|
|
||||||
|
%% Style Definitions
|
||||||
|
style Source fill:#f9f,stroke:#333,stroke-width:2px
|
||||||
|
style Dispatcher fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
style Queue fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
style Broadcast fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
style Listeners fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
style DB fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
style Cache fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
style Subscribers fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
|
||||||
|
%% Notes
|
||||||
|
Note right of Source: System Component
|
||||||
|
Note right of Dispatcher: Event Management
|
||||||
|
Note right of Queue: Async Processing
|
||||||
|
Note right of Broadcast: Real-time Updates
|
||||||
|
Note right of Listeners: Event Handlers
|
||||||
|
Note right of DB: Persistence Layer
|
||||||
|
Note right of Cache: Performance Layer
|
||||||
|
Note right of Subscribers: Event Subscribers
|
||||||
|
|
||||||
|
%% Event Types
|
||||||
|
Note over Dispatcher: Event Types:<br>1. Immediate Events<br>2. Queued Events<br>3. Broadcast Events
|
||||||
|
|
||||||
|
%% Processing Types
|
||||||
|
Note over Listeners: Processing Types:<br>1. Sync Processing<br>2. Async Processing<br>3. Batch Processing
|
||||||
|
|
||||||
|
%% Integration Points
|
||||||
|
Note over Queue: Integration:<br>1. Redis Queue<br>2. Database Queue<br>3. Memory Queue
|
||||||
|
|
||||||
|
%% Broadcast Channels
|
||||||
|
Note over Broadcast: Channels:<br>1. Public Channels<br>2. Private Channels<br>3. Presence Channels
|
||||||
|
|
||||||
|
%% Subscriber Types
|
||||||
|
Note over Subscribers: Subscriber Types:<br>1. Model Subscribers<br>2. System Subscribers<br>3. Custom Subscribers
|
161
docs/assets/diagrams/flows/model_lifecycle.mmd
Normal file
161
docs/assets/diagrams/flows/model_lifecycle.mmd
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
sequenceDiagram
|
||||||
|
participant Client as Model Client
|
||||||
|
participant Model as Model Instance
|
||||||
|
participant Events as Event System
|
||||||
|
participant Cache as Cache Layer
|
||||||
|
participant DB as Database
|
||||||
|
participant Queue as Queue System
|
||||||
|
participant Relations as Relationship Loader
|
||||||
|
participant Observer as Model Observer
|
||||||
|
|
||||||
|
%% Model Creation
|
||||||
|
Client->>Model: Create Model
|
||||||
|
activate Model
|
||||||
|
Model->>Events: Dispatch Creating Event
|
||||||
|
activate Events
|
||||||
|
Events->>Observer: Handle Creating
|
||||||
|
Observer-->>Events: Continue
|
||||||
|
Events-->>Model: Proceed
|
||||||
|
deactivate Events
|
||||||
|
|
||||||
|
%% Database Operation
|
||||||
|
Model->>DB: Insert Record
|
||||||
|
activate DB
|
||||||
|
DB-->>Model: Record Created
|
||||||
|
deactivate DB
|
||||||
|
|
||||||
|
%% Post Creation
|
||||||
|
Model->>Events: Dispatch Created Event
|
||||||
|
activate Events
|
||||||
|
Events->>Observer: Handle Created
|
||||||
|
Observer-->>Events: Continue
|
||||||
|
Events->>Queue: Queue Events
|
||||||
|
Queue-->>Events: Queued
|
||||||
|
Events-->>Model: Complete
|
||||||
|
deactivate Events
|
||||||
|
Model-->>Client: Model Instance
|
||||||
|
deactivate Model
|
||||||
|
|
||||||
|
%% Model Retrieval
|
||||||
|
Client->>Model: Find Model
|
||||||
|
activate Model
|
||||||
|
Model->>Cache: Check Cache
|
||||||
|
activate Cache
|
||||||
|
|
||||||
|
alt Cache Hit
|
||||||
|
Cache-->>Model: Return Cached
|
||||||
|
else Cache Miss
|
||||||
|
Cache-->>Model: Not Found
|
||||||
|
Model->>DB: Query Database
|
||||||
|
activate DB
|
||||||
|
DB-->>Model: Record Found
|
||||||
|
deactivate DB
|
||||||
|
Model->>Cache: Store in Cache
|
||||||
|
Cache-->>Model: Cached
|
||||||
|
end
|
||||||
|
deactivate Cache
|
||||||
|
|
||||||
|
%% Relationship Loading
|
||||||
|
Model->>Relations: Load Relations
|
||||||
|
activate Relations
|
||||||
|
Relations->>DB: Query Relations
|
||||||
|
activate DB
|
||||||
|
DB-->>Relations: Related Records
|
||||||
|
deactivate DB
|
||||||
|
Relations-->>Model: Relations Loaded
|
||||||
|
deactivate Relations
|
||||||
|
Model-->>Client: Complete Model
|
||||||
|
deactivate Model
|
||||||
|
|
||||||
|
%% Model Update
|
||||||
|
Client->>Model: Update Model
|
||||||
|
activate Model
|
||||||
|
Model->>Events: Dispatch Updating Event
|
||||||
|
activate Events
|
||||||
|
Events->>Observer: Handle Updating
|
||||||
|
Observer-->>Events: Continue
|
||||||
|
Events-->>Model: Proceed
|
||||||
|
deactivate Events
|
||||||
|
|
||||||
|
Model->>DB: Update Record
|
||||||
|
activate DB
|
||||||
|
DB-->>Model: Record Updated
|
||||||
|
deactivate DB
|
||||||
|
|
||||||
|
Model->>Cache: Invalidate Cache
|
||||||
|
activate Cache
|
||||||
|
Cache-->>Model: Cache Cleared
|
||||||
|
deactivate Cache
|
||||||
|
|
||||||
|
Model->>Events: Dispatch Updated Event
|
||||||
|
activate Events
|
||||||
|
Events->>Observer: Handle Updated
|
||||||
|
Observer-->>Events: Continue
|
||||||
|
Events->>Queue: Queue Events
|
||||||
|
Queue-->>Events: Queued
|
||||||
|
Events-->>Model: Complete
|
||||||
|
deactivate Events
|
||||||
|
Model-->>Client: Updated Model
|
||||||
|
deactivate Model
|
||||||
|
|
||||||
|
%% Model Deletion
|
||||||
|
Client->>Model: Delete Model
|
||||||
|
activate Model
|
||||||
|
Model->>Events: Dispatch Deleting Event
|
||||||
|
activate Events
|
||||||
|
Events->>Observer: Handle Deleting
|
||||||
|
Observer-->>Events: Continue
|
||||||
|
Events-->>Model: Proceed
|
||||||
|
deactivate Events
|
||||||
|
|
||||||
|
Model->>DB: Delete Record
|
||||||
|
activate DB
|
||||||
|
DB-->>Model: Record Deleted
|
||||||
|
deactivate DB
|
||||||
|
|
||||||
|
Model->>Cache: Clear Cache
|
||||||
|
activate Cache
|
||||||
|
Cache-->>Model: Cache Cleared
|
||||||
|
deactivate Cache
|
||||||
|
|
||||||
|
Model->>Events: Dispatch Deleted Event
|
||||||
|
activate Events
|
||||||
|
Events->>Observer: Handle Deleted
|
||||||
|
Observer-->>Events: Continue
|
||||||
|
Events->>Queue: Queue Events
|
||||||
|
Queue-->>Events: Queued
|
||||||
|
Events-->>Model: Complete
|
||||||
|
deactivate Events
|
||||||
|
Model-->>Client: Deletion Confirmed
|
||||||
|
deactivate Model
|
||||||
|
|
||||||
|
%% Style Definitions
|
||||||
|
style Client fill:#f9f,stroke:#333,stroke-width:2px
|
||||||
|
style Model fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
style Events fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
style Cache fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
style DB fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
style Queue fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
style Relations fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
style Observer fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
|
||||||
|
%% Notes
|
||||||
|
Note over Model: Model Lifecycle
|
||||||
|
Note over Events: Event Handling
|
||||||
|
Note over Cache: Cache Management
|
||||||
|
Note over DB: Data Persistence
|
||||||
|
Note over Queue: Async Processing
|
||||||
|
Note over Relations: Relationship Management
|
||||||
|
Note over Observer: Lifecycle Hooks
|
||||||
|
|
||||||
|
%% Model States
|
||||||
|
Note over Model: States:<br>1. Creating/Created<br>2. Retrieving/Retrieved<br>3. Updating/Updated<br>4. Deleting/Deleted
|
||||||
|
|
||||||
|
%% Event Types
|
||||||
|
Note over Events: Events:<br>1. Model Events<br>2. Relation Events<br>3. Cache Events<br>4. Queue Events
|
||||||
|
|
||||||
|
%% Cache Strategy
|
||||||
|
Note over Cache: Strategy:<br>1. Read Through<br>2. Write Behind<br>3. Cache Invalidation
|
||||||
|
|
||||||
|
%% Observer Points
|
||||||
|
Note over Observer: Hooks:<br>1. Before Events<br>2. After Events<br>3. Around Events
|
119
docs/assets/diagrams/flows/queue_processing.mmd
Normal file
119
docs/assets/diagrams/flows/queue_processing.mmd
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
sequenceDiagram
|
||||||
|
participant Source as Job Source
|
||||||
|
participant Queue as Queue Manager
|
||||||
|
participant Worker as Queue Worker
|
||||||
|
participant Job as Job Handler
|
||||||
|
participant Events as Event System
|
||||||
|
participant DB as Database
|
||||||
|
participant Failed as Failed Jobs
|
||||||
|
participant Monitor as Queue Monitor
|
||||||
|
|
||||||
|
%% Job Creation
|
||||||
|
Source->>Queue: Dispatch Job
|
||||||
|
activate Queue
|
||||||
|
Note over Queue: Create Job Record
|
||||||
|
|
||||||
|
%% Job Configuration
|
||||||
|
alt Has Delay
|
||||||
|
Queue->>Queue: Schedule for Later
|
||||||
|
else No Delay
|
||||||
|
Queue->>Queue: Available Immediately
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Worker Pickup
|
||||||
|
Queue->>Worker: Worker Picks Up Job
|
||||||
|
activate Worker
|
||||||
|
Note over Worker: Start Processing
|
||||||
|
|
||||||
|
%% Job Processing
|
||||||
|
Worker->>Job: Handle Job
|
||||||
|
activate Job
|
||||||
|
|
||||||
|
%% Database Transaction
|
||||||
|
alt Has Database Operations
|
||||||
|
Job->>DB: Begin Transaction
|
||||||
|
activate DB
|
||||||
|
Note over DB: Perform Operations
|
||||||
|
DB-->>Job: Transaction Complete
|
||||||
|
deactivate DB
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Event Dispatching
|
||||||
|
alt Has Events
|
||||||
|
Job->>Events: Dispatch Events
|
||||||
|
activate Events
|
||||||
|
Note over Events: Process Events
|
||||||
|
Events-->>Job: Events Handled
|
||||||
|
deactivate Events
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Job Completion Check
|
||||||
|
alt Job Succeeds
|
||||||
|
Job-->>Worker: Processing Complete
|
||||||
|
Worker-->>Queue: Job Completed
|
||||||
|
Queue->>Events: JobProcessed Event
|
||||||
|
else Job Fails
|
||||||
|
Job-->>Worker: Throws Exception
|
||||||
|
|
||||||
|
%% Retry Logic
|
||||||
|
alt Can Retry
|
||||||
|
Worker->>Queue: Release Job
|
||||||
|
Note over Queue: Increment Attempts
|
||||||
|
Queue-->>Worker: Job Released
|
||||||
|
else Max Retries Exceeded
|
||||||
|
Worker->>Failed: Move to Failed Jobs
|
||||||
|
activate Failed
|
||||||
|
Note over Failed: Log Failure
|
||||||
|
Failed-->>Worker: Failure Logged
|
||||||
|
deactivate Failed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
deactivate Job
|
||||||
|
deactivate Worker
|
||||||
|
deactivate Queue
|
||||||
|
|
||||||
|
%% Queue Monitoring
|
||||||
|
Monitor->>Queue: Check Status
|
||||||
|
activate Monitor
|
||||||
|
Queue-->>Monitor: Queue Statistics
|
||||||
|
Note over Monitor: Monitor Queue Health
|
||||||
|
|
||||||
|
Monitor->>Failed: Check Failed Jobs
|
||||||
|
Failed-->>Monitor: Failed Job Count
|
||||||
|
|
||||||
|
Monitor->>Worker: Check Workers
|
||||||
|
Worker-->>Monitor: Worker Status
|
||||||
|
deactivate Monitor
|
||||||
|
|
||||||
|
%% Style Definitions
|
||||||
|
style Source fill:#f9f,stroke:#333,stroke-width:2px
|
||||||
|
style Queue fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
style Worker fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
style Job fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
style Events fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
style DB fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
style Failed fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
style Monitor fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
|
||||||
|
%% Notes
|
||||||
|
Note right of Source: Application Code
|
||||||
|
Note right of Queue: Queue Management
|
||||||
|
Note right of Worker: Job Processing
|
||||||
|
Note right of Job: Business Logic
|
||||||
|
Note right of Events: Event System
|
||||||
|
Note right of DB: Data Layer
|
||||||
|
Note right of Failed: Error Handling
|
||||||
|
Note right of Monitor: Health Checks
|
||||||
|
|
||||||
|
%% Job States
|
||||||
|
Note over Queue: Job States:<br>1. Pending<br>2. Reserved<br>3. Released<br>4. Failed
|
||||||
|
|
||||||
|
%% Processing Types
|
||||||
|
Note over Worker: Processing:<br>1. Immediate<br>2. Delayed<br>3. Batched<br>4. Chained
|
||||||
|
|
||||||
|
%% Retry Strategy
|
||||||
|
Note over Failed: Retry Strategy:<br>1. Exponential Backoff<br>2. Max Attempts<br>3. Custom Delays
|
||||||
|
|
||||||
|
%% Monitoring Aspects
|
||||||
|
Note over Monitor: Monitoring:<br>1. Queue Size<br>2. Processing Rate<br>3. Error Rate<br>4. Worker Health
|
98
docs/assets/diagrams/flows/request_lifecycle.mmd
Normal file
98
docs/assets/diagrams/flows/request_lifecycle.mmd
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
sequenceDiagram
|
||||||
|
participant Client
|
||||||
|
participant Server as HTTP Server
|
||||||
|
participant Kernel as HTTP Kernel
|
||||||
|
participant Pipeline as Middleware Pipeline
|
||||||
|
participant Router
|
||||||
|
participant Controller
|
||||||
|
participant Services
|
||||||
|
participant Events
|
||||||
|
participant DB as Database
|
||||||
|
|
||||||
|
%% Initial Request
|
||||||
|
Client->>Server: HTTP Request
|
||||||
|
activate Server
|
||||||
|
Server->>Kernel: Handle Request
|
||||||
|
activate Kernel
|
||||||
|
|
||||||
|
%% Global Middleware
|
||||||
|
Kernel->>Pipeline: Process Global Middleware
|
||||||
|
activate Pipeline
|
||||||
|
Note over Pipeline: - Check Maintenance Mode<br>- Validate Post Size<br>- Trim Strings<br>- Convert Empty to Null
|
||||||
|
Pipeline-->>Kernel: Request Processed
|
||||||
|
deactivate Pipeline
|
||||||
|
|
||||||
|
%% Route Matching
|
||||||
|
Kernel->>Router: Match Route
|
||||||
|
activate Router
|
||||||
|
Router-->>Kernel: Route Found
|
||||||
|
deactivate Router
|
||||||
|
|
||||||
|
%% Route Middleware
|
||||||
|
Kernel->>Pipeline: Process Route Middleware
|
||||||
|
activate Pipeline
|
||||||
|
Note over Pipeline: - Authentication<br>- Authorization<br>- Throttling<br>- CSRF Protection
|
||||||
|
Pipeline-->>Kernel: Request Processed
|
||||||
|
deactivate Pipeline
|
||||||
|
|
||||||
|
%% Controller Action
|
||||||
|
Kernel->>Controller: Handle Request
|
||||||
|
activate Controller
|
||||||
|
|
||||||
|
%% Service Layer
|
||||||
|
Controller->>Services: Process Business Logic
|
||||||
|
activate Services
|
||||||
|
|
||||||
|
%% Database Operations
|
||||||
|
Services->>DB: Query Data
|
||||||
|
activate DB
|
||||||
|
DB-->>Services: Data Retrieved
|
||||||
|
deactivate DB
|
||||||
|
|
||||||
|
%% Event Dispatching
|
||||||
|
Services->>Events: Dispatch Events
|
||||||
|
activate Events
|
||||||
|
Note over Events: - Model Events<br>- Custom Events<br>- System Events
|
||||||
|
Events-->>Services: Events Processed
|
||||||
|
deactivate Events
|
||||||
|
|
||||||
|
Services-->>Controller: Logic Processed
|
||||||
|
deactivate Services
|
||||||
|
|
||||||
|
%% Response Generation
|
||||||
|
Controller-->>Kernel: Generate Response
|
||||||
|
deactivate Controller
|
||||||
|
|
||||||
|
%% Response Middleware
|
||||||
|
Kernel->>Pipeline: Process Response Middleware
|
||||||
|
activate Pipeline
|
||||||
|
Note over Pipeline: - Session<br>- Cookies<br>- Headers<br>- Response Compression
|
||||||
|
Pipeline-->>Kernel: Response Processed
|
||||||
|
deactivate Pipeline
|
||||||
|
|
||||||
|
%% Final Response
|
||||||
|
Kernel-->>Server: Return Response
|
||||||
|
deactivate Kernel
|
||||||
|
Server-->>Client: HTTP Response
|
||||||
|
deactivate Server
|
||||||
|
|
||||||
|
%% Style Definitions
|
||||||
|
style Client fill:#f9f,stroke:#333,stroke-width:2px
|
||||||
|
style Server fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
style Kernel fill:#bbf,stroke:#333,stroke-width:2px
|
||||||
|
style Pipeline fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
style Router fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
style Controller fill:#bfb,stroke:#333,stroke-width:2px
|
||||||
|
style Services fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
style Events fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
style DB fill:#fbb,stroke:#333,stroke-width:2px
|
||||||
|
|
||||||
|
%% Notes
|
||||||
|
Note right of Server: Entry Point
|
||||||
|
Note right of Kernel: Request Processing
|
||||||
|
Note right of Pipeline: Middleware Chain
|
||||||
|
Note right of Router: Route Resolution
|
||||||
|
Note right of Controller: Business Logic
|
||||||
|
Note right of Services: Service Layer
|
||||||
|
Note right of Events: Event System
|
||||||
|
Note right of DB: Data Layer
|
303
docs/bus_gap_analysis.md
Normal file
303
docs/bus_gap_analysis.md
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
# Bus Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our Bus package's actual implementation and Laravel's Bus functionality, identifying areas that need implementation or documentation updates.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Bus Package Specification](bus_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
|
||||||
|
## Implementation Gaps
|
||||||
|
|
||||||
|
### 1. Missing Laravel Features
|
||||||
|
```dart
|
||||||
|
// Documented but not implemented:
|
||||||
|
|
||||||
|
// 1. Job Chaining
|
||||||
|
class ChainedCommand {
|
||||||
|
// Need to implement:
|
||||||
|
void onConnection(String connection);
|
||||||
|
void onQueue(String queue);
|
||||||
|
void delay(Duration duration);
|
||||||
|
void middleware(List<PipeContract> middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Rate Limiting
|
||||||
|
class RateLimitedCommand {
|
||||||
|
// Need to implement:
|
||||||
|
void rateLimit(int maxAttempts, Duration duration);
|
||||||
|
void rateLimitPerUser(int maxAttempts, Duration duration);
|
||||||
|
void withoutOverlapping();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Error Handling
|
||||||
|
class FailedCommandHandler {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> failed(Command command, Exception exception);
|
||||||
|
Future<void> retry(String commandId);
|
||||||
|
Future<void> forget(String commandId);
|
||||||
|
Future<void> flush();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Existing Features Not Documented
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Implemented but not documented:
|
||||||
|
|
||||||
|
// 1. Command Mapping
|
||||||
|
class CommandMapper {
|
||||||
|
/// Maps command types to handlers
|
||||||
|
final Map<Type, Type> _handlers = {};
|
||||||
|
|
||||||
|
/// Registers command handler
|
||||||
|
void map<TCommand extends Command, THandler extends Handler>(
|
||||||
|
THandler Function() factory
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Command Context
|
||||||
|
class CommandContext {
|
||||||
|
/// Command metadata
|
||||||
|
final Map<String, dynamic> _metadata = {};
|
||||||
|
|
||||||
|
/// Sets command context
|
||||||
|
void withContext(String key, dynamic value);
|
||||||
|
|
||||||
|
/// Gets command context
|
||||||
|
T? getContext<T>(String key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Command Lifecycle
|
||||||
|
class CommandLifecycle {
|
||||||
|
/// Command hooks
|
||||||
|
final List<Function> _beforeHandling = [];
|
||||||
|
final List<Function> _afterHandling = [];
|
||||||
|
|
||||||
|
/// Registers lifecycle hooks
|
||||||
|
void beforeHandling(Function callback);
|
||||||
|
void afterHandling(Function callback);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Integration Points Not Documented
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 1. Queue Integration
|
||||||
|
class QueuedCommand {
|
||||||
|
/// Queue configuration
|
||||||
|
String get connection => 'default';
|
||||||
|
String get queue => 'default';
|
||||||
|
Duration? get delay => null;
|
||||||
|
|
||||||
|
/// Queue callbacks
|
||||||
|
void onQueue(QueueContract queue);
|
||||||
|
void onConnection(String connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Event Integration
|
||||||
|
class EventedCommand {
|
||||||
|
/// Event dispatcher
|
||||||
|
final EventDispatcherContract _events;
|
||||||
|
|
||||||
|
/// Dispatches command events
|
||||||
|
void dispatchCommandEvent(String event, dynamic data);
|
||||||
|
void subscribeCommandEvents(EventSubscriber subscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Cache Integration
|
||||||
|
class CachedCommand {
|
||||||
|
/// Cache configuration
|
||||||
|
String get cacheKey => '';
|
||||||
|
Duration get cacheTTL => Duration(minutes: 60);
|
||||||
|
|
||||||
|
/// Cache operations
|
||||||
|
Future<void> cache();
|
||||||
|
Future<void> clearCache();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Gaps
|
||||||
|
|
||||||
|
### 1. Missing API Documentation
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Need to document:
|
||||||
|
|
||||||
|
/// Maps command types to their handlers.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// mapper.map<CreateOrder, CreateOrderHandler>(
|
||||||
|
/// () => CreateOrderHandler(repository)
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
void map<TCommand extends Command, THandler extends Handler>(
|
||||||
|
THandler Function() factory
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Sets command execution context.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// command.withContext('user_id', userId);
|
||||||
|
/// command.withContext('tenant', tenant);
|
||||||
|
/// ```
|
||||||
|
void withContext(String key, dynamic value);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Integration Examples
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Need examples for:
|
||||||
|
|
||||||
|
// 1. Queue Integration
|
||||||
|
var command = CreateOrder(...)
|
||||||
|
..onQueue('orders')
|
||||||
|
..delay(Duration(minutes: 5));
|
||||||
|
|
||||||
|
await bus.dispatch(command);
|
||||||
|
|
||||||
|
// 2. Event Integration
|
||||||
|
class OrderCommand extends EventedCommand {
|
||||||
|
void handle() {
|
||||||
|
// Handle command
|
||||||
|
dispatchCommandEvent('order.handled', order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Cache Integration
|
||||||
|
class GetOrderCommand extends CachedCommand {
|
||||||
|
@override
|
||||||
|
String get cacheKey => 'order.$orderId';
|
||||||
|
|
||||||
|
Future<Order> handle() async {
|
||||||
|
return await cache(() => repository.find(orderId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Test Coverage
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Need tests for:
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Command Mapping', () {
|
||||||
|
test('maps commands to handlers', () {
|
||||||
|
var mapper = CommandMapper();
|
||||||
|
mapper.map<CreateOrder, CreateOrderHandler>(
|
||||||
|
() => CreateOrderHandler(repository)
|
||||||
|
);
|
||||||
|
|
||||||
|
var handler = mapper.resolveHandler(CreateOrder());
|
||||||
|
expect(handler, isA<CreateOrderHandler>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Command Context', () {
|
||||||
|
test('handles command context', () {
|
||||||
|
var command = TestCommand()
|
||||||
|
..withContext('key', 'value');
|
||||||
|
|
||||||
|
expect(command.getContext('key'), equals('value'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **High Priority**
|
||||||
|
- Command chaining (Laravel compatibility)
|
||||||
|
- Rate limiting (Laravel compatibility)
|
||||||
|
- Better error handling
|
||||||
|
|
||||||
|
2. **Medium Priority**
|
||||||
|
- Command mapping
|
||||||
|
- Command context
|
||||||
|
- Performance optimizations
|
||||||
|
|
||||||
|
3. **Low Priority**
|
||||||
|
- Additional helper methods
|
||||||
|
- Extended testing utilities
|
||||||
|
- Debug/profiling tools
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Implementation Tasks**
|
||||||
|
- Add command chaining
|
||||||
|
- Add rate limiting
|
||||||
|
- Add error handling
|
||||||
|
- Improve queue integration
|
||||||
|
|
||||||
|
2. **Documentation Tasks**
|
||||||
|
- Document command mapping
|
||||||
|
- Document command context
|
||||||
|
- Document command lifecycle
|
||||||
|
- Add integration examples
|
||||||
|
|
||||||
|
3. **Testing Tasks**
|
||||||
|
- Add command mapping tests
|
||||||
|
- Add context tests
|
||||||
|
- Add lifecycle tests
|
||||||
|
- Add integration tests
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing bus features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Bus Package Specification](bus_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each bus feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in [Bus Package Specification](bus_package_specification.md)
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing bus features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Bus system must:
|
||||||
|
1. Handle high command throughput
|
||||||
|
2. Process chains efficiently
|
||||||
|
3. Support async operations
|
||||||
|
4. Scale horizontally
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Bus tests must:
|
||||||
|
1. Cover all command scenarios
|
||||||
|
2. Test chaining behavior
|
||||||
|
3. Verify rate limiting
|
||||||
|
4. Check error handling
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Bus documentation must:
|
||||||
|
1. Explain command patterns
|
||||||
|
2. Show chaining examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
424
docs/bus_package_specification.md
Normal file
424
docs/bus_package_specification.md
Normal file
|
@ -0,0 +1,424 @@
|
||||||
|
# Bus Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Bus package provides a robust command and event bus implementation that matches Laravel's bus functionality. It integrates with our Queue, Event, and Pipeline packages to provide a complete message bus solution with support for command handling, event dispatching, and middleware processing.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for event handling
|
||||||
|
> - See [Queue Package Specification](queue_package_specification.md) for queue integration
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Command Bus
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core command bus implementation
|
||||||
|
class CommandBus implements CommandBusContract {
|
||||||
|
final Container _container;
|
||||||
|
final QueueContract? _queue;
|
||||||
|
final PipelineContract _pipeline;
|
||||||
|
|
||||||
|
CommandBus(
|
||||||
|
this._container,
|
||||||
|
this._pipeline, [
|
||||||
|
this._queue
|
||||||
|
]);
|
||||||
|
|
||||||
|
/// Dispatches a command
|
||||||
|
Future<dynamic> dispatch(Command command) async {
|
||||||
|
if (command is ShouldQueue && _queue != null) {
|
||||||
|
return await dispatchToQueue(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await dispatchNow(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches a command immediately
|
||||||
|
Future<dynamic> dispatchNow(Command command) async {
|
||||||
|
var handler = _resolveHandler(command);
|
||||||
|
|
||||||
|
return await _pipeline
|
||||||
|
.send(command)
|
||||||
|
.through(_getPipes())
|
||||||
|
.then((cmd) => handler.handle(cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches a command to queue
|
||||||
|
Future<dynamic> dispatchToQueue(Command command) async {
|
||||||
|
await _queue!.push(QueuedCommandJob(
|
||||||
|
command: command,
|
||||||
|
handler: _resolveHandler(command)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a command batch
|
||||||
|
PendingBatch batch(List<Command> commands) {
|
||||||
|
return PendingCommandBatch(this, commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a command chain
|
||||||
|
PendingChain chain(List<Command> commands) {
|
||||||
|
return PendingCommandChain(this, commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves command handler
|
||||||
|
Handler _resolveHandler(Command command) {
|
||||||
|
var handlerType = command.handler;
|
||||||
|
return _container.make(handlerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets command pipes
|
||||||
|
List<PipeContract> _getPipes() {
|
||||||
|
return [
|
||||||
|
TransactionPipe(),
|
||||||
|
ValidationPipe(),
|
||||||
|
AuthorizationPipe()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Event Bus
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core event bus implementation
|
||||||
|
class EventBus implements EventBusContract {
|
||||||
|
final Container _container;
|
||||||
|
final EventDispatcherContract _events;
|
||||||
|
final QueueContract? _queue;
|
||||||
|
|
||||||
|
EventBus(
|
||||||
|
this._container,
|
||||||
|
this._events, [
|
||||||
|
this._queue
|
||||||
|
]);
|
||||||
|
|
||||||
|
/// Dispatches an event
|
||||||
|
Future<void> dispatch(Event event) async {
|
||||||
|
if (event is ShouldQueue && _queue != null) {
|
||||||
|
await dispatchToQueue(event);
|
||||||
|
} else {
|
||||||
|
await dispatchNow(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches an event immediately
|
||||||
|
Future<void> dispatchNow(Event event) async {
|
||||||
|
await _events.dispatch(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches an event to queue
|
||||||
|
Future<void> dispatchToQueue(Event event) async {
|
||||||
|
await _queue!.push(QueuedEventJob(
|
||||||
|
event: event,
|
||||||
|
listeners: _events.getListeners(event.runtimeType)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers an event listener
|
||||||
|
void listen<T>(void Function(T event) listener) {
|
||||||
|
_events.listen<T>(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers an event subscriber
|
||||||
|
void subscribe(EventSubscriber subscriber) {
|
||||||
|
_events.subscribe(subscriber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Bus Middleware
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Transaction middleware
|
||||||
|
class TransactionPipe implements PipeContract {
|
||||||
|
final DatabaseManager _db;
|
||||||
|
|
||||||
|
TransactionPipe(this._db);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<dynamic> handle(dynamic passable, Function next) async {
|
||||||
|
return await _db.transaction((tx) async {
|
||||||
|
return await next(passable);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validation middleware
|
||||||
|
class ValidationPipe implements PipeContract {
|
||||||
|
final Validator _validator;
|
||||||
|
|
||||||
|
ValidationPipe(this._validator);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<dynamic> handle(dynamic passable, Function next) async {
|
||||||
|
if (passable is ValidatableCommand) {
|
||||||
|
await _validator.validate(
|
||||||
|
passable.toMap(),
|
||||||
|
passable.rules()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await next(passable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Authorization middleware
|
||||||
|
class AuthorizationPipe implements PipeContract {
|
||||||
|
final AuthManager _auth;
|
||||||
|
|
||||||
|
AuthorizationPipe(this._auth);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<dynamic> handle(dynamic passable, Function next) async {
|
||||||
|
if (passable is AuthorizableCommand) {
|
||||||
|
if (!await passable.authorize(_auth)) {
|
||||||
|
throw UnauthorizedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await next(passable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Command Batching
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Pending command batch
|
||||||
|
class PendingCommandBatch implements PendingBatch {
|
||||||
|
final CommandBus _bus;
|
||||||
|
final List<Command> _commands;
|
||||||
|
bool _allowFailures = false;
|
||||||
|
|
||||||
|
PendingCommandBatch(this._bus, this._commands);
|
||||||
|
|
||||||
|
/// Allows failures in batch
|
||||||
|
PendingBatch allowFailures() {
|
||||||
|
_allowFailures = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches the batch
|
||||||
|
Future<void> dispatch() async {
|
||||||
|
for (var command in _commands) {
|
||||||
|
try {
|
||||||
|
await _bus.dispatchNow(command);
|
||||||
|
} catch (e) {
|
||||||
|
if (!_allowFailures) rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pending command chain
|
||||||
|
class PendingCommandChain implements PendingChain {
|
||||||
|
final CommandBus _bus;
|
||||||
|
final List<Command> _commands;
|
||||||
|
|
||||||
|
PendingCommandChain(this._bus, this._commands);
|
||||||
|
|
||||||
|
/// Dispatches the chain
|
||||||
|
Future<void> dispatch() async {
|
||||||
|
dynamic result;
|
||||||
|
|
||||||
|
for (var command in _commands) {
|
||||||
|
if (command is ChainedCommand) {
|
||||||
|
command.setPreviousResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await _bus.dispatchNow(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
### 1. Command Bus Usage
|
||||||
|
```dart
|
||||||
|
// Define command
|
||||||
|
class CreateOrder implements Command {
|
||||||
|
final String customerId;
|
||||||
|
final List<String> products;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Type get handler => CreateOrderHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define handler
|
||||||
|
class CreateOrderHandler implements Handler<CreateOrder> {
|
||||||
|
final OrderRepository _orders;
|
||||||
|
|
||||||
|
CreateOrderHandler(this._orders);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Order> handle(CreateOrder command) async {
|
||||||
|
return await _orders.create(
|
||||||
|
customerId: command.customerId,
|
||||||
|
products: command.products
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch command
|
||||||
|
var order = await bus.dispatch(CreateOrder(
|
||||||
|
customerId: '123',
|
||||||
|
products: ['abc', 'xyz']
|
||||||
|
));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Event Bus Usage
|
||||||
|
```dart
|
||||||
|
// Define event
|
||||||
|
class OrderCreated implements Event {
|
||||||
|
final Order order;
|
||||||
|
OrderCreated(this.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register listener
|
||||||
|
eventBus.listen<OrderCreated>((event) async {
|
||||||
|
await notifyCustomer(event.order);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dispatch event
|
||||||
|
await eventBus.dispatch(OrderCreated(order));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Command Batching
|
||||||
|
```dart
|
||||||
|
// Create batch
|
||||||
|
await bus.batch([
|
||||||
|
CreateOrder(...),
|
||||||
|
UpdateInventory(...),
|
||||||
|
NotifyShipping(...)
|
||||||
|
])
|
||||||
|
.allowFailures()
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
// Create chain
|
||||||
|
await bus.chain([
|
||||||
|
CreateOrder(...),
|
||||||
|
ProcessPayment(...),
|
||||||
|
ShipOrder(...)
|
||||||
|
])
|
||||||
|
.dispatch();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Command Bus', () {
|
||||||
|
test('dispatches commands', () async {
|
||||||
|
var bus = CommandBus(container, pipeline);
|
||||||
|
var command = CreateOrder(...);
|
||||||
|
|
||||||
|
await bus.dispatch(command);
|
||||||
|
|
||||||
|
verify(() => handler.handle(command)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles command batch', () async {
|
||||||
|
var bus = CommandBus(container, pipeline);
|
||||||
|
|
||||||
|
await bus.batch([
|
||||||
|
CreateOrder(...),
|
||||||
|
UpdateInventory(...)
|
||||||
|
]).dispatch();
|
||||||
|
|
||||||
|
verify(() => bus.dispatchNow(any())).called(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Event Bus', () {
|
||||||
|
test('dispatches events', () async {
|
||||||
|
var bus = EventBus(container, dispatcher);
|
||||||
|
var event = OrderCreated(order);
|
||||||
|
|
||||||
|
await bus.dispatch(event);
|
||||||
|
|
||||||
|
verify(() => dispatcher.dispatch(event)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('queues events', () async {
|
||||||
|
var bus = EventBus(container, dispatcher, queue);
|
||||||
|
var event = OrderShipped(order);
|
||||||
|
|
||||||
|
await bus.dispatch(event);
|
||||||
|
|
||||||
|
verify(() => queue.push(any())).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement core bus features
|
||||||
|
2. Add middleware support
|
||||||
|
3. Add batching/chaining
|
||||||
|
4. Add queue integration
|
||||||
|
5. Write tests
|
||||||
|
6. Add benchmarks
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing bus features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Events Package Specification](events_package_specification.md)
|
||||||
|
6. Review [Queue Package Specification](queue_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each bus feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in related packages
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing bus features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Bus system must:
|
||||||
|
1. Handle high command throughput
|
||||||
|
2. Process events efficiently
|
||||||
|
3. Support async operations
|
||||||
|
4. Scale horizontally
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Bus tests must:
|
||||||
|
1. Cover all command scenarios
|
||||||
|
2. Test event handling
|
||||||
|
3. Verify queue integration
|
||||||
|
4. Check middleware behavior
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Bus documentation must:
|
||||||
|
1. Explain command patterns
|
||||||
|
2. Show event examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
335
docs/config_gap_analysis.md
Normal file
335
docs/config_gap_analysis.md
Normal file
|
@ -0,0 +1,335 @@
|
||||||
|
# Config Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our current configuration handling (in Core package) and Laravel's Config package functionality, identifying what needs to be implemented as a standalone Config package.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Config Package Specification](config_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
|
||||||
|
## Implementation Gaps
|
||||||
|
|
||||||
|
### 1. Missing Package Structure
|
||||||
|
```dart
|
||||||
|
// Need to create dedicated Config package:
|
||||||
|
|
||||||
|
packages/config/
|
||||||
|
├── lib/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── config_repository.dart
|
||||||
|
│ │ ├── environment_loader.dart
|
||||||
|
│ │ ├── config_loader.dart
|
||||||
|
│ │ └── config_cache.dart
|
||||||
|
│ └── config.dart
|
||||||
|
├── test/
|
||||||
|
└── example/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Core Features
|
||||||
|
```dart
|
||||||
|
// 1. Config Repository
|
||||||
|
class ConfigRepository {
|
||||||
|
// Need to implement:
|
||||||
|
T? get<T>(String key, [T? defaultValue]);
|
||||||
|
void set(String key, dynamic value);
|
||||||
|
bool has(String key);
|
||||||
|
Map<String, dynamic> all();
|
||||||
|
void merge(Map<String, dynamic> items);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Environment Loading
|
||||||
|
class EnvironmentLoader {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> load([String? path]);
|
||||||
|
String? get(String key, [String? defaultValue]);
|
||||||
|
void set(String key, String value);
|
||||||
|
bool has(String key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Configuration Loading
|
||||||
|
class ConfigurationLoader {
|
||||||
|
// Need to implement:
|
||||||
|
Future<Map<String, dynamic>> load();
|
||||||
|
Future<Map<String, dynamic>> loadFile(String path);
|
||||||
|
Future<void> reload();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Laravel Features
|
||||||
|
```dart
|
||||||
|
// 1. Package Configuration
|
||||||
|
class PackageConfig {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> publish(String package, Map<String, String> paths);
|
||||||
|
Future<void> publishForce(String package, Map<String, String> paths);
|
||||||
|
List<String> publishedPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Configuration Groups
|
||||||
|
class ConfigurationGroups {
|
||||||
|
// Need to implement:
|
||||||
|
void group(String name, List<String> paths);
|
||||||
|
List<String> getGroup(String name);
|
||||||
|
bool hasGroup(String name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Configuration Caching
|
||||||
|
class ConfigCache {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> cache(Map<String, dynamic> config);
|
||||||
|
Future<Map<String, dynamic>?> load();
|
||||||
|
Future<void> clear();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Gaps
|
||||||
|
|
||||||
|
### 1. Container Integration
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
class ConfigServiceProvider {
|
||||||
|
void register() {
|
||||||
|
// Register config repository
|
||||||
|
container.singleton<ConfigContract>((c) =>
|
||||||
|
ConfigRepository(
|
||||||
|
loader: c.make<ConfigurationLoader>(),
|
||||||
|
cache: c.make<ConfigCache>()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register environment loader
|
||||||
|
container.singleton<EnvironmentLoader>((c) =>
|
||||||
|
EnvironmentLoader(
|
||||||
|
path: c.make<PathResolver>().base
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Package Integration
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
class PackageServiceProvider {
|
||||||
|
void register() {
|
||||||
|
// Register package config
|
||||||
|
publishConfig('my-package', {
|
||||||
|
'config/my-package.php': 'my-package'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void boot() {
|
||||||
|
// Merge package config
|
||||||
|
config.merge({
|
||||||
|
'my-package': {
|
||||||
|
'key': 'value'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Environment Integration
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
class EnvironmentServiceProvider {
|
||||||
|
void boot() {
|
||||||
|
var env = container.make<EnvironmentLoader>();
|
||||||
|
|
||||||
|
// Load environment files
|
||||||
|
env.load();
|
||||||
|
|
||||||
|
if (env.get('APP_ENV') == 'local') {
|
||||||
|
env.load('.env.local');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set environment variables
|
||||||
|
config.set('app.env', env.get('APP_ENV', 'production'));
|
||||||
|
config.set('app.debug', env.get('APP_DEBUG', 'false') == 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Gaps
|
||||||
|
|
||||||
|
### 1. Missing API Documentation
|
||||||
|
```dart
|
||||||
|
// Need to document:
|
||||||
|
|
||||||
|
/// Manages application configuration.
|
||||||
|
///
|
||||||
|
/// Provides access to configuration values using dot notation:
|
||||||
|
/// ```dart
|
||||||
|
/// var dbHost = config.get<String>('database.connections.mysql.host');
|
||||||
|
/// ```
|
||||||
|
class ConfigRepository {
|
||||||
|
/// Gets a configuration value.
|
||||||
|
///
|
||||||
|
/// Returns [defaultValue] if key not found.
|
||||||
|
T? get<T>(String key, [T? defaultValue]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Usage Examples
|
||||||
|
```dart
|
||||||
|
// Need examples for:
|
||||||
|
|
||||||
|
// 1. Basic Configuration
|
||||||
|
var appName = config.get<String>('app.name', 'My App');
|
||||||
|
var debug = config.get<bool>('app.debug', false);
|
||||||
|
|
||||||
|
// 2. Environment Configuration
|
||||||
|
var dbConfig = {
|
||||||
|
'host': env('DB_HOST', 'localhost'),
|
||||||
|
'port': env('DB_PORT', '3306'),
|
||||||
|
'database': env('DB_DATABASE'),
|
||||||
|
'username': env('DB_USERNAME'),
|
||||||
|
'password': env('DB_PASSWORD')
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. Package Configuration
|
||||||
|
class MyPackageServiceProvider {
|
||||||
|
void register() {
|
||||||
|
publishConfig('my-package', {
|
||||||
|
'config/my-package.php': 'my-package'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Test Coverage
|
||||||
|
```dart
|
||||||
|
// Need tests for:
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Config Repository', () {
|
||||||
|
test('gets nested values', () {
|
||||||
|
var config = ConfigRepository({
|
||||||
|
'app': {
|
||||||
|
'name': 'Test App',
|
||||||
|
'nested': {'key': 'value'}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(config.get('app.name'), equals('Test App'));
|
||||||
|
expect(config.get('app.nested.key'), equals('value'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Environment Loader', () {
|
||||||
|
test('loads env files', () async {
|
||||||
|
var env = EnvironmentLoader();
|
||||||
|
await env.load('.env.test');
|
||||||
|
|
||||||
|
expect(env.get('APP_NAME'), equals('Test App'));
|
||||||
|
expect(env.get('APP_ENV'), equals('testing'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **High Priority**
|
||||||
|
- Create Config package structure
|
||||||
|
- Implement core repository
|
||||||
|
- Add environment loading
|
||||||
|
- Add configuration loading
|
||||||
|
|
||||||
|
2. **Medium Priority**
|
||||||
|
- Add package configuration
|
||||||
|
- Add configuration groups
|
||||||
|
- Add configuration caching
|
||||||
|
- Add container integration
|
||||||
|
|
||||||
|
3. **Low Priority**
|
||||||
|
- Add helper functions
|
||||||
|
- Add testing utilities
|
||||||
|
- Add debugging tools
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Package Creation**
|
||||||
|
- Create package structure
|
||||||
|
- Move config code from Core
|
||||||
|
- Add package dependencies
|
||||||
|
- Setup testing
|
||||||
|
|
||||||
|
2. **Core Implementation**
|
||||||
|
- Implement ConfigRepository
|
||||||
|
- Implement EnvironmentLoader
|
||||||
|
- Implement ConfigurationLoader
|
||||||
|
- Add caching support
|
||||||
|
|
||||||
|
3. **Integration Implementation**
|
||||||
|
- Add container integration
|
||||||
|
- Add package support
|
||||||
|
- Add environment support
|
||||||
|
- Add service providers
|
||||||
|
|
||||||
|
Would you like me to:
|
||||||
|
1. Create the Config package structure?
|
||||||
|
2. Start implementing core features?
|
||||||
|
3. Create detailed implementation plans?
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing config features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Config Package Specification](config_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each config feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in [Config Package Specification](config_package_specification.md)
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing config features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Config system must:
|
||||||
|
1. Cache configuration efficiently
|
||||||
|
2. Minimize file I/O
|
||||||
|
3. Support lazy loading
|
||||||
|
4. Handle environment variables efficiently
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Config tests must:
|
||||||
|
1. Cover all configuration scenarios
|
||||||
|
2. Test environment handling
|
||||||
|
3. Verify caching behavior
|
||||||
|
4. Check file operations
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Config documentation must:
|
||||||
|
1. Explain configuration patterns
|
||||||
|
2. Show environment examples
|
||||||
|
3. Cover caching strategies
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
451
docs/config_package_specification.md
Normal file
451
docs/config_package_specification.md
Normal file
|
@ -0,0 +1,451 @@
|
||||||
|
# Config Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Config package provides a flexible configuration management system that matches Laravel's config functionality. It integrates with our Container and Package systems while supporting hierarchical configuration, environment-based settings, and caching.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Contracts Package Specification](contracts_package_specification.md) for config contracts
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Configuration Repository
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core configuration repository
|
||||||
|
class ConfigRepository implements ConfigContract {
|
||||||
|
final Container _container;
|
||||||
|
final Map<String, dynamic> _items;
|
||||||
|
final EnvironmentLoader _env;
|
||||||
|
final ConfigCache? _cache;
|
||||||
|
|
||||||
|
ConfigRepository(
|
||||||
|
this._container, [
|
||||||
|
Map<String, dynamic>? items,
|
||||||
|
EnvironmentLoader? env,
|
||||||
|
ConfigCache? cache
|
||||||
|
]) : _items = items ?? {},
|
||||||
|
_env = env ?? EnvironmentLoader(),
|
||||||
|
_cache = cache;
|
||||||
|
|
||||||
|
@override
|
||||||
|
T? get<T>(String key, [T? defaultValue]) {
|
||||||
|
var value = _getNestedValue(key);
|
||||||
|
if (value == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return _cast<T>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void set(String key, dynamic value) {
|
||||||
|
_setNestedValue(key, value);
|
||||||
|
_cache?.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool has(String key) {
|
||||||
|
return _getNestedValue(key) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets all configuration items
|
||||||
|
Map<String, dynamic> all() => Map.from(_items);
|
||||||
|
|
||||||
|
/// Merges configuration values
|
||||||
|
void merge(Map<String, dynamic> items) {
|
||||||
|
_items.addAll(_deepMerge(_items, items));
|
||||||
|
_cache?.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deep merges two maps
|
||||||
|
Map<String, dynamic> _deepMerge(
|
||||||
|
Map<String, dynamic> target,
|
||||||
|
Map<String, dynamic> source
|
||||||
|
) {
|
||||||
|
source.forEach((key, value) {
|
||||||
|
if (value is Map && target[key] is Map) {
|
||||||
|
target[key] = _deepMerge(
|
||||||
|
target[key] as Map<String, dynamic>,
|
||||||
|
value as Map<String, dynamic>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
target[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Casts a value to the requested type
|
||||||
|
T _cast<T>(dynamic value) {
|
||||||
|
if (value is T) return value;
|
||||||
|
|
||||||
|
// Handle common type conversions
|
||||||
|
if (T == bool) {
|
||||||
|
if (value is String) {
|
||||||
|
return (value.toLowerCase() == 'true') as T;
|
||||||
|
}
|
||||||
|
return (value == 1) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (T == int && value is String) {
|
||||||
|
return int.parse(value) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (T == double && value is String) {
|
||||||
|
return double.parse(value) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ConfigCastException(
|
||||||
|
'Cannot cast $value to $T'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Environment Management
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Manages environment configuration
|
||||||
|
class EnvironmentManager {
|
||||||
|
final Container _container;
|
||||||
|
final Map<String, String> _cache = {};
|
||||||
|
final List<String> _files = ['.env'];
|
||||||
|
|
||||||
|
EnvironmentManager(this._container);
|
||||||
|
|
||||||
|
/// Loads environment files
|
||||||
|
Future<void> load([String? path]) async {
|
||||||
|
path ??= _container.make<PathResolver>().base;
|
||||||
|
|
||||||
|
for (var file in _files) {
|
||||||
|
var envFile = File('$path/$file');
|
||||||
|
if (await envFile.exists()) {
|
||||||
|
var contents = await envFile.readAsString();
|
||||||
|
_parseEnvFile(contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets an environment variable
|
||||||
|
String? get(String key, [String? defaultValue]) {
|
||||||
|
return _cache[key] ??
|
||||||
|
Platform.environment[key] ??
|
||||||
|
defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets an environment variable
|
||||||
|
void set(String key, String value) {
|
||||||
|
_cache[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an environment file
|
||||||
|
void addEnvFile(String file) {
|
||||||
|
_files.add(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses an environment file
|
||||||
|
void _parseEnvFile(String contents) {
|
||||||
|
var lines = contents.split('\n');
|
||||||
|
for (var line in lines) {
|
||||||
|
if (_isComment(line)) continue;
|
||||||
|
if (_isEmpty(line)) continue;
|
||||||
|
|
||||||
|
var parts = line.split('=');
|
||||||
|
if (parts.length != 2) continue;
|
||||||
|
|
||||||
|
var key = parts[0].trim();
|
||||||
|
var value = _parseValue(parts[1].trim());
|
||||||
|
|
||||||
|
_cache[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses an environment value
|
||||||
|
String _parseValue(String value) {
|
||||||
|
// Remove quotes
|
||||||
|
if (value.startsWith('"') && value.endsWith('"')) {
|
||||||
|
value = value.substring(1, value.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle special values
|
||||||
|
switch (value.toLowerCase()) {
|
||||||
|
case 'true':
|
||||||
|
case '(true)':
|
||||||
|
return 'true';
|
||||||
|
case 'false':
|
||||||
|
case '(false)':
|
||||||
|
return 'false';
|
||||||
|
case 'empty':
|
||||||
|
case '(empty)':
|
||||||
|
return '';
|
||||||
|
case 'null':
|
||||||
|
case '(null)':
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Package Configuration
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Manages package configuration publishing
|
||||||
|
class ConfigPublisher {
|
||||||
|
final Container _container;
|
||||||
|
final Map<String, List<String>> _publishGroups = {};
|
||||||
|
|
||||||
|
ConfigPublisher(this._container);
|
||||||
|
|
||||||
|
/// Publishes configuration files
|
||||||
|
Future<void> publish(
|
||||||
|
String package,
|
||||||
|
Map<String, String> paths, [
|
||||||
|
List<String>? groups
|
||||||
|
]) async {
|
||||||
|
var resolver = _container.make<PathResolver>();
|
||||||
|
var configPath = resolver.config;
|
||||||
|
|
||||||
|
for (var entry in paths.entries) {
|
||||||
|
var source = entry.key;
|
||||||
|
var dest = '$configPath/${entry.value}';
|
||||||
|
|
||||||
|
await _publishFile(source, dest);
|
||||||
|
|
||||||
|
if (groups != null) {
|
||||||
|
for (var group in groups) {
|
||||||
|
_publishGroups.putIfAbsent(group, () => [])
|
||||||
|
.add(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets files in a publish group
|
||||||
|
List<String> getGroup(String name) {
|
||||||
|
return _publishGroups[name] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies a configuration file
|
||||||
|
Future<void> _publishFile(
|
||||||
|
String source,
|
||||||
|
String destination
|
||||||
|
) async {
|
||||||
|
var sourceFile = File(source);
|
||||||
|
var destFile = File(destination);
|
||||||
|
|
||||||
|
if (!await destFile.exists()) {
|
||||||
|
await destFile.create(recursive: true);
|
||||||
|
await sourceFile.copy(destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Configuration Cache
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Caches configuration values
|
||||||
|
class ConfigCache {
|
||||||
|
final Container _container;
|
||||||
|
final String _cacheKey = 'config.cache';
|
||||||
|
|
||||||
|
ConfigCache(this._container);
|
||||||
|
|
||||||
|
/// Caches configuration values
|
||||||
|
Future<void> cache(Map<String, dynamic> items) async {
|
||||||
|
var cache = _container.make<CacheContract>();
|
||||||
|
await cache.forever(_cacheKey, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets cached configuration
|
||||||
|
Future<Map<String, dynamic>?> get() async {
|
||||||
|
var cache = _container.make<CacheContract>();
|
||||||
|
return await cache.get<Map<String, dynamic>>(_cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears cached configuration
|
||||||
|
Future<void> clear() async {
|
||||||
|
var cache = _container.make<CacheContract>();
|
||||||
|
await cache.forget(_cacheKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
### 1. Package Configuration
|
||||||
|
```dart
|
||||||
|
class MyPackageServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
// Publish package config
|
||||||
|
publishConfig('my-package', {
|
||||||
|
'config/my-package.php': 'my-package'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void boot() {
|
||||||
|
// Merge package config
|
||||||
|
var config = container.make<ConfigContract>();
|
||||||
|
config.merge({
|
||||||
|
'my-package': {
|
||||||
|
'key': 'value'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Environment Configuration
|
||||||
|
```dart
|
||||||
|
class AppServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
void boot() {
|
||||||
|
var env = container.make<EnvironmentManager>();
|
||||||
|
|
||||||
|
// Add environment files
|
||||||
|
env.addEnvFile('.env.local');
|
||||||
|
if (protevusEnv.isTesting) {
|
||||||
|
env.addEnvFile('.env.testing');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load environment
|
||||||
|
await env.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Configuration Cache
|
||||||
|
```dart
|
||||||
|
class CacheCommand {
|
||||||
|
Future<void> handle() async {
|
||||||
|
var config = container.make<ConfigContract>();
|
||||||
|
var cache = container.make<ConfigCache>();
|
||||||
|
|
||||||
|
// Cache config
|
||||||
|
await cache.cache(config.all());
|
||||||
|
|
||||||
|
// Clear config cache
|
||||||
|
await cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Config Repository', () {
|
||||||
|
test('merges configuration', () {
|
||||||
|
var config = ConfigRepository(container);
|
||||||
|
|
||||||
|
config.set('database', {
|
||||||
|
'default': 'mysql',
|
||||||
|
'connections': {
|
||||||
|
'mysql': {'host': 'localhost'}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
config.merge({
|
||||||
|
'database': {
|
||||||
|
'connections': {
|
||||||
|
'mysql': {'port': 3306}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
config.get('database.connections.mysql'),
|
||||||
|
equals({
|
||||||
|
'host': 'localhost',
|
||||||
|
'port': 3306
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Environment Manager', () {
|
||||||
|
test('loads multiple env files', () async {
|
||||||
|
var env = EnvironmentManager(container);
|
||||||
|
env.addEnvFile('.env.testing');
|
||||||
|
|
||||||
|
await env.load();
|
||||||
|
|
||||||
|
expect(env.get('APP_ENV'), equals('testing'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Complete package config publishing
|
||||||
|
2. Add config merging
|
||||||
|
3. Enhance environment handling
|
||||||
|
4. Add caching improvements
|
||||||
|
5. Write more tests
|
||||||
|
|
||||||
|
Would you like me to enhance any other package specifications?
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing config features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Understand [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each config feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Implement required contracts (see [Contracts Package Specification](contracts_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing config features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
5. Implement all contracts from [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Config system must:
|
||||||
|
1. Cache configuration efficiently
|
||||||
|
2. Minimize file I/O
|
||||||
|
3. Support lazy loading
|
||||||
|
4. Handle environment variables efficiently
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Config tests must:
|
||||||
|
1. Cover all configuration scenarios
|
||||||
|
2. Test environment handling
|
||||||
|
3. Verify caching behavior
|
||||||
|
4. Check file operations
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Config documentation must:
|
||||||
|
1. Explain configuration patterns
|
||||||
|
2. Show environment examples
|
||||||
|
3. Cover caching strategies
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
379
docs/container_feature_integration.md
Normal file
379
docs/container_feature_integration.md
Normal file
|
@ -0,0 +1,379 @@
|
||||||
|
# Container Feature Integration Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide demonstrates how the Container's three major features work together to provide powerful dependency management:
|
||||||
|
1. Contextual Binding - Different implementations based on context
|
||||||
|
2. Method Injection - Automatic dependency resolution for methods
|
||||||
|
3. Tagged Bindings - Grouping related services
|
||||||
|
|
||||||
|
## Real World Example: Multi-tenant Reporting System
|
||||||
|
|
||||||
|
Let's build a complete multi-tenant reporting system that showcases all three features working together.
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
|
||||||
|
1. Multiple tenants (clients) each need their own:
|
||||||
|
- Database connection
|
||||||
|
- Storage system
|
||||||
|
- Report formatting
|
||||||
|
|
||||||
|
2. Various types of reports:
|
||||||
|
- Performance reports
|
||||||
|
- Financial reports
|
||||||
|
- User activity reports
|
||||||
|
|
||||||
|
3. Each report needs:
|
||||||
|
- Data access
|
||||||
|
- Formatting
|
||||||
|
- Storage
|
||||||
|
- Logging
|
||||||
|
|
||||||
|
### Base Interfaces
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Base interface for all reports
|
||||||
|
abstract class Report {
|
||||||
|
Future<void> generate();
|
||||||
|
Future<void> save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Database connection interface
|
||||||
|
abstract class Database {
|
||||||
|
Future<List<Map<String, dynamic>>> query(String sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Storage system interface
|
||||||
|
abstract class Storage {
|
||||||
|
Future<void> save(String path, List<int> data);
|
||||||
|
Future<List<int>> load(String path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Report formatter interface
|
||||||
|
abstract class ReportFormatter {
|
||||||
|
String format(Map<String, dynamic> data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tenant-Specific Implementations
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Tenant A's database implementation
|
||||||
|
class TenantADatabase implements Database {
|
||||||
|
@override
|
||||||
|
Future<List<Map<String, dynamic>>> query(String sql) {
|
||||||
|
// Tenant A specific database logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tenant B's database implementation
|
||||||
|
class TenantBDatabase implements Database {
|
||||||
|
@override
|
||||||
|
Future<List<Map<String, dynamic>>> query(String sql) {
|
||||||
|
// Tenant B specific database logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar implementations for Storage and Formatter...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Report Implementations
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class PerformanceReport implements Report {
|
||||||
|
final Database db;
|
||||||
|
final Storage storage;
|
||||||
|
final ReportFormatter formatter;
|
||||||
|
|
||||||
|
PerformanceReport(this.db, this.storage, this.formatter);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> generate() async {
|
||||||
|
var data = await db.query('SELECT * FROM performance_metrics');
|
||||||
|
var formatted = formatter.format(data);
|
||||||
|
await storage.save('performance.report', formatted.codeUnits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar implementations for Financial and UserActivity reports...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using All Three Features Together
|
||||||
|
|
||||||
|
1. First, set up contextual bindings for tenant-specific services:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void configureTenantA(Container container) {
|
||||||
|
// Bind tenant-specific implementations
|
||||||
|
container.when(TenantAContext)
|
||||||
|
.needs<Database>()
|
||||||
|
.give(TenantADatabase());
|
||||||
|
|
||||||
|
container.when(TenantAContext)
|
||||||
|
.needs<Storage>()
|
||||||
|
.give(TenantAStorage());
|
||||||
|
|
||||||
|
container.when(TenantAContext)
|
||||||
|
.needs<ReportFormatter>()
|
||||||
|
.give(TenantAFormatter());
|
||||||
|
}
|
||||||
|
|
||||||
|
void configureTenantB(Container container) {
|
||||||
|
// Similar bindings for Tenant B...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Set up tagged bindings for reports:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void configureReports(Container container) {
|
||||||
|
// Bind report implementations
|
||||||
|
container.bind<PerformanceReport>(PerformanceReport);
|
||||||
|
container.bind<FinancialReport>(FinancialReport);
|
||||||
|
container.bind<UserActivityReport>(UserActivityReport);
|
||||||
|
|
||||||
|
// Tag them for easy retrieval
|
||||||
|
container.tag([
|
||||||
|
PerformanceReport,
|
||||||
|
FinancialReport,
|
||||||
|
UserActivityReport
|
||||||
|
], 'reports');
|
||||||
|
|
||||||
|
// Additional tags for categorization
|
||||||
|
container.tag([PerformanceReport], 'metrics-reports');
|
||||||
|
container.tag([FinancialReport], 'financial-reports');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create a report manager that uses method injection:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class ReportManager {
|
||||||
|
final Container container;
|
||||||
|
|
||||||
|
ReportManager(this.container);
|
||||||
|
|
||||||
|
/// Generates all reports for a tenant
|
||||||
|
/// Uses method injection for the logger parameter
|
||||||
|
Future<void> generateAllReports(
|
||||||
|
TenantContext tenant,
|
||||||
|
{required DateTime date}
|
||||||
|
) async {
|
||||||
|
// Get all tagged reports
|
||||||
|
var reports = container.taggedAs<Report>('reports');
|
||||||
|
|
||||||
|
// Generate each report using tenant context
|
||||||
|
for (var report in reports) {
|
||||||
|
await container.call(
|
||||||
|
report,
|
||||||
|
'generate',
|
||||||
|
parameters: {'date': date},
|
||||||
|
context: tenant // Uses contextual binding
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates specific report types
|
||||||
|
/// Uses method injection for dependencies
|
||||||
|
Future<void> generateMetricsReports(
|
||||||
|
TenantContext tenant,
|
||||||
|
Logger logger, // Injected automatically
|
||||||
|
MetricsService metrics // Injected automatically
|
||||||
|
) async {
|
||||||
|
var reports = container.taggedAs<Report>('metrics-reports');
|
||||||
|
|
||||||
|
for (var report in reports) {
|
||||||
|
logger.info('Generating metrics report: ${report.runtimeType}');
|
||||||
|
await container.call(report, 'generate', context: tenant);
|
||||||
|
metrics.recordReportGeneration(report);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using the Integrated System
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() async {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
// Configure container
|
||||||
|
configureTenantA(container);
|
||||||
|
configureTenantB(container);
|
||||||
|
configureReports(container);
|
||||||
|
|
||||||
|
// Create report manager
|
||||||
|
var manager = ReportManager(container);
|
||||||
|
|
||||||
|
// Generate reports for Tenant A
|
||||||
|
await manager.generateAllReports(
|
||||||
|
TenantAContext(),
|
||||||
|
date: DateTime.now()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generate only metrics reports for Tenant B
|
||||||
|
await manager.generateMetricsReports(
|
||||||
|
TenantBContext()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## How the Features Work Together
|
||||||
|
|
||||||
|
1. **Contextual Binding** ensures:
|
||||||
|
- Each tenant gets their own implementations
|
||||||
|
- Services are properly scoped
|
||||||
|
- No cross-tenant data leakage
|
||||||
|
|
||||||
|
2. **Method Injection** provides:
|
||||||
|
- Automatic dependency resolution
|
||||||
|
- Clean method signatures
|
||||||
|
- Flexible parameter handling
|
||||||
|
|
||||||
|
3. **Tagged Bindings** enable:
|
||||||
|
- Easy service grouping
|
||||||
|
- Dynamic service discovery
|
||||||
|
- Flexible categorization
|
||||||
|
|
||||||
|
## Common Integration Patterns
|
||||||
|
|
||||||
|
1. **Service Location with Context**
|
||||||
|
```dart
|
||||||
|
// Get tenant-specific service
|
||||||
|
var db = container.make<Database>(context: tenantContext);
|
||||||
|
|
||||||
|
// Get all services of a type for a tenant
|
||||||
|
var reports = container.taggedAs<Report>('reports')
|
||||||
|
.map((r) => container.make(r, context: tenantContext))
|
||||||
|
.toList();
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Method Injection with Tags**
|
||||||
|
```dart
|
||||||
|
Future<void> processReports(Logger logger) async {
|
||||||
|
// Logger is injected, reports are retrieved by tag
|
||||||
|
var reports = container.taggedAs<Report>('reports');
|
||||||
|
|
||||||
|
for (var report in reports) {
|
||||||
|
logger.info('Processing ${report.runtimeType}');
|
||||||
|
await container.call(report, 'process');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Contextual Services with Tags**
|
||||||
|
```dart
|
||||||
|
Future<void> generateTenantReports(TenantContext tenant) async {
|
||||||
|
// Get all reports
|
||||||
|
var reports = container.taggedAs<Report>('reports');
|
||||||
|
|
||||||
|
// Process each with tenant context
|
||||||
|
for (var report in reports) {
|
||||||
|
await container.call(
|
||||||
|
report,
|
||||||
|
'generate',
|
||||||
|
context: tenant
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Clear Service Organization**
|
||||||
|
```dart
|
||||||
|
// Group related tags
|
||||||
|
container.tag([Service1, Service2], 'data-services');
|
||||||
|
container.tag([Service1], 'cacheable-services');
|
||||||
|
|
||||||
|
// Group related contexts
|
||||||
|
container.when(TenantContext)
|
||||||
|
.needs<Database>()
|
||||||
|
.give(TenantDatabase());
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Consistent Dependency Resolution**
|
||||||
|
```dart
|
||||||
|
// Prefer method injection for flexible dependencies
|
||||||
|
Future<void> processReport(
|
||||||
|
Report report,
|
||||||
|
Logger logger, // Injected
|
||||||
|
MetricsService metrics // Injected
|
||||||
|
) async {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use contextual binding for tenant-specific services
|
||||||
|
container.when(TenantContext)
|
||||||
|
.needs<Storage>()
|
||||||
|
.give(TenantStorage());
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Documentation**
|
||||||
|
```dart
|
||||||
|
/// Report processor that handles multiple report types
|
||||||
|
///
|
||||||
|
/// Uses the following container features:
|
||||||
|
/// - Tagged bindings for report retrieval ('reports' tag)
|
||||||
|
/// - Contextual binding for tenant-specific services
|
||||||
|
/// - Method injection for logging and metrics
|
||||||
|
class ReportProcessor {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Integrated Features
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Integrated Container Features', () {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container();
|
||||||
|
|
||||||
|
// Set up test bindings
|
||||||
|
configureTenantA(container);
|
||||||
|
configureReports(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle tenant-specific tagged services', () {
|
||||||
|
var tenantA = TenantAContext();
|
||||||
|
|
||||||
|
// Get all reports for tenant
|
||||||
|
var reports = container.taggedAs<Report>('reports')
|
||||||
|
.map((r) => container.make(r, context: tenantA))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
expect(reports, hasLength(3));
|
||||||
|
expect(reports.every((r) => r.db is TenantADatabase), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should inject dependencies with context', () async {
|
||||||
|
var processor = ReportProcessor();
|
||||||
|
var tenantA = TenantAContext();
|
||||||
|
|
||||||
|
await container.call(
|
||||||
|
processor,
|
||||||
|
'processReports',
|
||||||
|
context: tenantA
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify correct services were injected
|
||||||
|
verify(() => processor.logger is Logger).called(1);
|
||||||
|
verify(() => processor.db is TenantADatabase).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement integration tests
|
||||||
|
2. Add performance monitoring
|
||||||
|
3. Add dependency validation
|
||||||
|
4. Create usage documentation
|
||||||
|
5. Add debugging tools
|
||||||
|
6. Create migration guides
|
||||||
|
|
||||||
|
Would you like me to create detailed specifications for any of these next steps?
|
261
docs/container_gap_analysis.md
Normal file
261
docs/container_gap_analysis.md
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
# Container Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our Container package's actual implementation and our documentation, identifying areas that need implementation or documentation updates. It also outlines the migration strategy to achieve full Laravel compatibility.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Container Package Specification](container_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing requirements
|
||||||
|
|
||||||
|
## Implementation Status
|
||||||
|
|
||||||
|
> **Status Note**: This status aligns with our [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#implementation-status). See there for overall framework status.
|
||||||
|
|
||||||
|
### 1. Core Features
|
||||||
|
|
||||||
|
#### Implemented
|
||||||
|
```dart
|
||||||
|
✓ Basic dependency injection
|
||||||
|
✓ Service location
|
||||||
|
✓ Auto-wiring
|
||||||
|
✓ Parent/child containers
|
||||||
|
✓ Named singletons
|
||||||
|
✓ Lazy singleton registration
|
||||||
|
✓ Async dependency resolution
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Partially Implemented
|
||||||
|
```dart
|
||||||
|
~ Contextual binding (basic structure)
|
||||||
|
~ Method injection (basic reflection)
|
||||||
|
~ Tagged bindings (basic tagging)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Not Implemented
|
||||||
|
```dart
|
||||||
|
- Advanced contextual binding features
|
||||||
|
* Instance-based context
|
||||||
|
* Multiple contexts
|
||||||
|
* Context inheritance
|
||||||
|
|
||||||
|
- Advanced method injection features
|
||||||
|
* Parameter validation
|
||||||
|
* Optional parameters
|
||||||
|
* Named parameters
|
||||||
|
|
||||||
|
- Advanced tagged binding features
|
||||||
|
* Tag inheritance
|
||||||
|
* Tag groups
|
||||||
|
* Tag conditions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Strategy
|
||||||
|
|
||||||
|
> **Integration Note**: This migration strategy follows patterns from [Foundation Integration Guide](foundation_integration_guide.md). See there for detailed integration examples.
|
||||||
|
|
||||||
|
### Phase 1: Internal Restructuring (No Breaking Changes)
|
||||||
|
|
||||||
|
1. **Extract Binding Logic**
|
||||||
|
```dart
|
||||||
|
// Move from current implementation:
|
||||||
|
class Container {
|
||||||
|
final Map<Type, Object> _bindings = {};
|
||||||
|
void bind<T>(T instance) => _bindings[T] = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To new implementation:
|
||||||
|
class Container {
|
||||||
|
final Map<Type, Binding> _bindings = {};
|
||||||
|
|
||||||
|
void bind<T>(T Function(Container) concrete) {
|
||||||
|
_bindings[T] = Binding(
|
||||||
|
concrete: concrete,
|
||||||
|
shared: false,
|
||||||
|
implementedType: T
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add Resolution Context**
|
||||||
|
```dart
|
||||||
|
class Container {
|
||||||
|
T make<T>([dynamic context]) {
|
||||||
|
var resolutionContext = ResolutionContext(
|
||||||
|
resolvingType: T,
|
||||||
|
context: context,
|
||||||
|
container: this,
|
||||||
|
resolutionStack: {}
|
||||||
|
);
|
||||||
|
|
||||||
|
return _resolve(T, resolutionContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Add New Features (Backward Compatible)
|
||||||
|
|
||||||
|
1. **Contextual Binding**
|
||||||
|
```dart
|
||||||
|
class Container {
|
||||||
|
final Map<Type, Map<Type, Binding>> _contextualBindings = {};
|
||||||
|
|
||||||
|
ContextualBindingBuilder when(Type concrete) {
|
||||||
|
return ContextualBindingBuilder(this, concrete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Method Injection**
|
||||||
|
```dart
|
||||||
|
class Container {
|
||||||
|
dynamic call(
|
||||||
|
Object instance,
|
||||||
|
String methodName, [
|
||||||
|
Map<String, dynamic>? parameters
|
||||||
|
]) {
|
||||||
|
var method = reflector.reflectInstance(instance)
|
||||||
|
.type
|
||||||
|
.declarations[Symbol(methodName)];
|
||||||
|
|
||||||
|
var resolvedParams = _resolveMethodParameters(
|
||||||
|
method,
|
||||||
|
parameters
|
||||||
|
);
|
||||||
|
|
||||||
|
return Function.apply(
|
||||||
|
instance.runtimeType.getMethod(methodName),
|
||||||
|
resolvedParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Tagged Bindings**
|
||||||
|
```dart
|
||||||
|
class Container {
|
||||||
|
final Map<String, Set<Type>> _tags = {};
|
||||||
|
|
||||||
|
void tag(List<Type> types, String tag) {
|
||||||
|
_tags.putIfAbsent(tag, () => {}).addAll(types);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<T> taggedAs<T>(String tag) {
|
||||||
|
return _tags[tag]?.map((t) => make<T>(t)).toList() ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Performance Optimization
|
||||||
|
|
||||||
|
> **Performance Note**: These optimizations align with our [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks) performance targets.
|
||||||
|
|
||||||
|
1. **Add Resolution Cache**
|
||||||
|
```dart
|
||||||
|
class Container {
|
||||||
|
final ResolutionCache _cache = ResolutionCache();
|
||||||
|
|
||||||
|
T make<T>([dynamic context]) {
|
||||||
|
var cached = _cache.get<T>(context);
|
||||||
|
if (cached != null) return cached;
|
||||||
|
|
||||||
|
var instance = _resolve<T>(T, context);
|
||||||
|
_cache.cache(instance, context);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add Reflection Cache**
|
||||||
|
```dart
|
||||||
|
class Container {
|
||||||
|
final ReflectionCache _reflectionCache = ReflectionCache();
|
||||||
|
|
||||||
|
dynamic call(Object instance, String methodName, [Map<String, dynamic>? parameters]) {
|
||||||
|
var methodMirror = _reflectionCache.getMethod(
|
||||||
|
instance.runtimeType,
|
||||||
|
methodName
|
||||||
|
) ?? _cacheMethod(instance, methodName);
|
||||||
|
|
||||||
|
return _invokeMethod(instance, methodMirror, parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backward Compatibility Requirements
|
||||||
|
|
||||||
|
> **Note**: These requirements ensure compatibility while following [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) guidelines.
|
||||||
|
|
||||||
|
1. **Maintain Existing APIs**
|
||||||
|
```dart
|
||||||
|
// These must continue to work:
|
||||||
|
container.make<T>();
|
||||||
|
container.makeAsync<T>();
|
||||||
|
container.has<T>();
|
||||||
|
container.hasNamed();
|
||||||
|
container.registerFactory<T>();
|
||||||
|
container.registerSingleton<T>();
|
||||||
|
container.registerNamedSingleton<T>();
|
||||||
|
container.registerLazySingleton<T>();
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Preserve Behavior**
|
||||||
|
```dart
|
||||||
|
// Parent/child resolution
|
||||||
|
var parent = Container(reflector);
|
||||||
|
var child = parent.createChild();
|
||||||
|
parent.registerSingleton<Service>(service);
|
||||||
|
var resolved = child.make<Service>(); // Must work
|
||||||
|
|
||||||
|
// Named singletons
|
||||||
|
container.registerNamedSingleton('key', service);
|
||||||
|
var found = container.findByName('key'); // Must work
|
||||||
|
|
||||||
|
// Async resolution
|
||||||
|
var future = container.makeAsync<Service>(); // Must work
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
> **Priority Note**: These priorities align with our [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#implementation-priorities).
|
||||||
|
|
||||||
|
### 1. High Priority
|
||||||
|
- Complete contextual binding implementation
|
||||||
|
- Add parameter validation to method injection
|
||||||
|
- Implement tag inheritance
|
||||||
|
|
||||||
|
### 2. Medium Priority
|
||||||
|
- Add cache integration
|
||||||
|
- Improve error handling
|
||||||
|
- Add event system integration
|
||||||
|
|
||||||
|
### 3. Low Priority
|
||||||
|
- Add helper methods
|
||||||
|
- Add debugging tools
|
||||||
|
- Add performance monitoring
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Container Package Specification](container_package_specification.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Would you like me to:
|
||||||
|
1. Start implementing Phase 1 changes?
|
||||||
|
2. Create detailed specifications for Phase 2?
|
||||||
|
3. Design the caching system for Phase 3?
|
452
docs/container_migration_guide.md
Normal file
452
docs/container_migration_guide.md
Normal file
|
@ -0,0 +1,452 @@
|
||||||
|
# Container Migration Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide helps you migrate from the current Container implementation to the new Laravel-compatible version. It covers:
|
||||||
|
1. Breaking changes
|
||||||
|
2. New features
|
||||||
|
3. Migration strategies
|
||||||
|
4. Code examples
|
||||||
|
5. Best practices
|
||||||
|
|
||||||
|
## Breaking Changes
|
||||||
|
|
||||||
|
### 1. Binding Registration
|
||||||
|
|
||||||
|
#### Old Way
|
||||||
|
```dart
|
||||||
|
// Old implementation
|
||||||
|
container.bind<Service>(instance);
|
||||||
|
container.singleton<Service>(instance);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### New Way
|
||||||
|
```dart
|
||||||
|
// New implementation
|
||||||
|
container.bind<Service>((c) => instance);
|
||||||
|
container.singleton<Service>((c) => instance);
|
||||||
|
|
||||||
|
// With contextual binding
|
||||||
|
container.when(UserController)
|
||||||
|
.needs<Service>()
|
||||||
|
.give((c) => SpecialService());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Service Resolution
|
||||||
|
|
||||||
|
#### Old Way
|
||||||
|
```dart
|
||||||
|
// Old implementation
|
||||||
|
var service = container.make<Service>();
|
||||||
|
var namedService = container.makeNamed<Service>('name');
|
||||||
|
```
|
||||||
|
|
||||||
|
#### New Way
|
||||||
|
```dart
|
||||||
|
// New implementation
|
||||||
|
var service = container.make<Service>();
|
||||||
|
var contextualService = container.make<Service>(context: UserController);
|
||||||
|
var taggedServices = container.taggedAs<Service>('tag');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Method Injection
|
||||||
|
|
||||||
|
#### Old Way
|
||||||
|
```dart
|
||||||
|
// Old implementation - manual parameter resolution
|
||||||
|
class UserService {
|
||||||
|
void process(User user) {
|
||||||
|
var logger = container.make<Logger>();
|
||||||
|
var validator = container.make<Validator>();
|
||||||
|
// Process user...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### New Way
|
||||||
|
```dart
|
||||||
|
// New implementation - automatic method injection
|
||||||
|
class UserService {
|
||||||
|
void process(
|
||||||
|
User user,
|
||||||
|
Logger logger, // Automatically injected
|
||||||
|
Validator validator // Automatically injected
|
||||||
|
) {
|
||||||
|
// Process user...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
container.call(userService, 'process', {'user': user});
|
||||||
|
```
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
### 1. Contextual Binding
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Register different implementations based on context
|
||||||
|
void setupBindings(Container container) {
|
||||||
|
// Default storage
|
||||||
|
container.bind<Storage>((c) => LocalStorage());
|
||||||
|
|
||||||
|
// User uploads use cloud storage
|
||||||
|
container.when(UserUploadController)
|
||||||
|
.needs<Storage>()
|
||||||
|
.give((c) => CloudStorage());
|
||||||
|
|
||||||
|
// System files use local storage
|
||||||
|
container.when(SystemFileController)
|
||||||
|
.needs<Storage>()
|
||||||
|
.give((c) => LocalStorage());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Tagged Bindings
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Register and tag related services
|
||||||
|
void setupReportServices(Container container) {
|
||||||
|
// Register services
|
||||||
|
container.bind<PerformanceReport>((c) => PerformanceReport());
|
||||||
|
container.bind<FinancialReport>((c) => FinancialReport());
|
||||||
|
container.bind<UserReport>((c) => UserReport());
|
||||||
|
|
||||||
|
// Tag them for easy retrieval
|
||||||
|
container.tag([
|
||||||
|
PerformanceReport,
|
||||||
|
FinancialReport,
|
||||||
|
UserReport
|
||||||
|
], 'reports');
|
||||||
|
|
||||||
|
// Additional categorization
|
||||||
|
container.tag([PerformanceReport], 'metrics');
|
||||||
|
container.tag([FinancialReport], 'financial');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
var reports = container.taggedAs<Report>('reports');
|
||||||
|
var metricReports = container.taggedAs<Report>('metrics');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Method Injection
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class ReportGenerator {
|
||||||
|
void generateReport(
|
||||||
|
Report report,
|
||||||
|
Logger logger, // Automatically injected
|
||||||
|
Formatter formatter, // Automatically injected
|
||||||
|
{required DateTime date} // Manually provided
|
||||||
|
) {
|
||||||
|
logger.info('Generating report...');
|
||||||
|
var data = report.getData();
|
||||||
|
var formatted = formatter.format(data);
|
||||||
|
// Generate report...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
container.call(
|
||||||
|
generator,
|
||||||
|
'generateReport',
|
||||||
|
{'report': report, 'date': DateTime.now()}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Strategies
|
||||||
|
|
||||||
|
### 1. Gradual Migration
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Step 1: Update bindings
|
||||||
|
class ServiceRegistry {
|
||||||
|
void register(Container container) {
|
||||||
|
// Old way (still works)
|
||||||
|
container.bind<OldService>(OldService());
|
||||||
|
|
||||||
|
// New way
|
||||||
|
container.bind<NewService>((c) => NewService());
|
||||||
|
|
||||||
|
// Add contextual bindings
|
||||||
|
container.when(NewController)
|
||||||
|
.needs<Service>()
|
||||||
|
.give((c) => NewService());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Update resolution
|
||||||
|
class ServiceConsumer {
|
||||||
|
void process() {
|
||||||
|
// Old way (still works)
|
||||||
|
var oldService = container.make<OldService>();
|
||||||
|
|
||||||
|
// New way
|
||||||
|
var newService = container.make<NewService>();
|
||||||
|
var contextual = container.make<Service>(
|
||||||
|
context: NewController
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Add method injection
|
||||||
|
class ServiceProcessor {
|
||||||
|
// Old way
|
||||||
|
void processOld(Data data) {
|
||||||
|
var service = container.make<Service>();
|
||||||
|
service.process(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New way
|
||||||
|
void processNew(
|
||||||
|
Data data,
|
||||||
|
Service service // Injected automatically
|
||||||
|
) {
|
||||||
|
service.process(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Feature-by-Feature Migration
|
||||||
|
|
||||||
|
1. **Update Bindings First**
|
||||||
|
```dart
|
||||||
|
// Update all bindings to new style
|
||||||
|
void registerBindings(Container container) {
|
||||||
|
// Update simple bindings
|
||||||
|
container.bind<Service>((c) => ServiceImpl());
|
||||||
|
|
||||||
|
// Add contextual bindings
|
||||||
|
container.when(Controller)
|
||||||
|
.needs<Service>()
|
||||||
|
.give((c) => SpecialService());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add Tagged Services**
|
||||||
|
```dart
|
||||||
|
// Group related services
|
||||||
|
void registerServices(Container container) {
|
||||||
|
// Register services
|
||||||
|
container.bind<ServiceA>((c) => ServiceA());
|
||||||
|
container.bind<ServiceB>((c) => ServiceB());
|
||||||
|
|
||||||
|
// Add tags
|
||||||
|
container.tag([ServiceA, ServiceB], 'services');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Implement Method Injection**
|
||||||
|
```dart
|
||||||
|
// Convert to method injection
|
||||||
|
class UserController {
|
||||||
|
// Before
|
||||||
|
void oldProcess(User user) {
|
||||||
|
var validator = container.make<Validator>();
|
||||||
|
var logger = container.make<Logger>();
|
||||||
|
// Process...
|
||||||
|
}
|
||||||
|
|
||||||
|
// After
|
||||||
|
void newProcess(
|
||||||
|
User user,
|
||||||
|
Validator validator,
|
||||||
|
Logger logger
|
||||||
|
) {
|
||||||
|
// Process...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing During Migration
|
||||||
|
|
||||||
|
### 1. Verify Bindings
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Container Migration Tests', () {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container();
|
||||||
|
registerBindings(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support old-style bindings', () {
|
||||||
|
var oldService = container.make<OldService>();
|
||||||
|
expect(oldService, isNotNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support new-style bindings', () {
|
||||||
|
var newService = container.make<NewService>();
|
||||||
|
expect(newService, isNotNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should resolve contextual bindings', () {
|
||||||
|
var service = container.make<Service>(
|
||||||
|
context: Controller
|
||||||
|
);
|
||||||
|
expect(service, isA<SpecialService>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Verify Tagged Services
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Tagged Services Tests', () {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container();
|
||||||
|
registerServices(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should resolve tagged services', () {
|
||||||
|
var services = container.taggedAs<Service>('services');
|
||||||
|
expect(services, hasLength(2));
|
||||||
|
expect(services, contains(isA<ServiceA>()));
|
||||||
|
expect(services, contains(isA<ServiceB>()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Verify Method Injection
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Method Injection Tests', () {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container();
|
||||||
|
registerServices(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should inject method dependencies', () {
|
||||||
|
var controller = UserController();
|
||||||
|
|
||||||
|
// Call with only required parameters
|
||||||
|
container.call(
|
||||||
|
controller,
|
||||||
|
'newProcess',
|
||||||
|
{'user': testUser}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify injection worked
|
||||||
|
verify(() => mockValidator.validate(any)).called(1);
|
||||||
|
verify(() => mockLogger.log(any)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Update Bindings Consistently**
|
||||||
|
```dart
|
||||||
|
// Good: Consistent new style
|
||||||
|
container.bind<Service>((c) => ServiceImpl());
|
||||||
|
container.singleton<Logger>((c) => FileLogger());
|
||||||
|
|
||||||
|
// Bad: Mixed styles
|
||||||
|
container.bind<Service>(ServiceImpl()); // Old style
|
||||||
|
container.singleton<Logger>((c) => FileLogger()); // New style
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Use Contextual Bindings Appropriately**
|
||||||
|
```dart
|
||||||
|
// Good: Clear context and purpose
|
||||||
|
container.when(UserController)
|
||||||
|
.needs<Storage>()
|
||||||
|
.give((c) => UserStorage());
|
||||||
|
|
||||||
|
// Bad: Unclear or overly broad context
|
||||||
|
container.when(Object)
|
||||||
|
.needs<Storage>()
|
||||||
|
.give((c) => GenericStorage());
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Organize Tagged Services**
|
||||||
|
```dart
|
||||||
|
// Good: Logical grouping
|
||||||
|
container.tag([
|
||||||
|
PerformanceReport,
|
||||||
|
SystemReport
|
||||||
|
], 'system-reports');
|
||||||
|
|
||||||
|
container.tag([
|
||||||
|
UserReport,
|
||||||
|
ActivityReport
|
||||||
|
], 'user-reports');
|
||||||
|
|
||||||
|
// Bad: Mixed concerns
|
||||||
|
container.tag([
|
||||||
|
PerformanceReport,
|
||||||
|
UserReport,
|
||||||
|
Logger,
|
||||||
|
Storage
|
||||||
|
], 'services');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues and Solutions
|
||||||
|
|
||||||
|
1. **Binding Resolution Errors**
|
||||||
|
```dart
|
||||||
|
// Problem: Missing binding
|
||||||
|
var service = container.make<Service>(); // Throws error
|
||||||
|
|
||||||
|
// Solution: Register binding first
|
||||||
|
container.bind<Service>((c) => ServiceImpl());
|
||||||
|
var service = container.make<Service>(); // Works
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Contextual Binding Conflicts**
|
||||||
|
```dart
|
||||||
|
// Problem: Multiple contexts
|
||||||
|
container.when(Controller)
|
||||||
|
.needs<Service>()
|
||||||
|
.give((c) => ServiceA());
|
||||||
|
|
||||||
|
container.when(Controller) // Conflict!
|
||||||
|
.needs<Service>()
|
||||||
|
.give((c) => ServiceB());
|
||||||
|
|
||||||
|
// Solution: Use specific contexts
|
||||||
|
container.when(UserController)
|
||||||
|
.needs<Service>()
|
||||||
|
.give((c) => ServiceA());
|
||||||
|
|
||||||
|
container.when(AdminController)
|
||||||
|
.needs<Service>()
|
||||||
|
.give((c) => ServiceB());
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Method Injection Failures**
|
||||||
|
```dart
|
||||||
|
// Problem: Unresolvable parameter
|
||||||
|
void process(
|
||||||
|
CustomType param // Not registered with container
|
||||||
|
) { }
|
||||||
|
|
||||||
|
// Solution: Register or provide parameter
|
||||||
|
container.bind<CustomType>((c) => CustomType());
|
||||||
|
// or
|
||||||
|
container.call(instance, 'process', {
|
||||||
|
'param': customInstance
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Audit existing container usage
|
||||||
|
2. Plan migration phases
|
||||||
|
3. Update bindings
|
||||||
|
4. Add new features
|
||||||
|
5. Update tests
|
||||||
|
6. Document changes
|
||||||
|
|
||||||
|
Would you like me to create detailed specifications for any of these next steps?
|
77
docs/container_package_specification.md
Normal file
77
docs/container_package_specification.md
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# Container Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Container package provides a powerful dependency injection container that matches Laravel's container functionality while leveraging Dart's type system. It supports auto-wiring, contextual binding, method injection, and tagged bindings.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Container Gap Analysis](container_gap_analysis.md) for missing features
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
|
||||||
|
[Previous content remains the same until Core Features section, then add:]
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
> **Implementation Note**: These features are part of our Laravel compatibility effort. See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for the full feature list and status.
|
||||||
|
|
||||||
|
[Previous features content remains the same, then add:]
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
> **Integration Note**: These examples demonstrate common integration patterns. See [Foundation Integration Guide](foundation_integration_guide.md) for more patterns and best practices.
|
||||||
|
|
||||||
|
[Previous examples content remains the same, then add:]
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
> **Testing Note**: These examples show package-specific tests. See [Testing Guide](testing_guide.md) for comprehensive testing approaches.
|
||||||
|
|
||||||
|
[Previous testing content remains the same, then add:]
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
> **Performance Note**: These optimizations align with our performance targets. See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks) for specific metrics.
|
||||||
|
|
||||||
|
[Previous performance content remains the same, then add:]
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before contributing to this package:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Container Gap Analysis](container_gap_analysis.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 2. Integration Patterns
|
||||||
|
When integrating with other packages:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Check [Package Integration Map](package_integration_map.md)
|
||||||
|
3. Review [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
|
||||||
|
### 3. Testing Requirements
|
||||||
|
All contributions must:
|
||||||
|
1. Include unit tests (see [Testing Guide](testing_guide.md#unit-tests))
|
||||||
|
2. Include integration tests (see [Testing Guide](testing_guide.md#integration-tests))
|
||||||
|
3. Meet performance targets (see [Testing Guide](testing_guide.md#performance-tests))
|
||||||
|
|
||||||
|
### 4. Documentation Requirements
|
||||||
|
All changes must:
|
||||||
|
1. Update this specification if needed
|
||||||
|
2. Update [Container Gap Analysis](container_gap_analysis.md) if needed
|
||||||
|
3. Follow documentation standards in [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
See [Container Gap Analysis](container_gap_analysis.md) for:
|
||||||
|
1. Missing features to implement
|
||||||
|
2. Areas needing improvement
|
||||||
|
3. Integration gaps
|
||||||
|
4. Documentation gaps
|
||||||
|
|
||||||
|
Would you like me to:
|
||||||
|
1. Add more integration examples?
|
||||||
|
2. Enhance testing documentation?
|
||||||
|
3. Add performance optimizations?
|
448
docs/contracts_package_specification.md
Normal file
448
docs/contracts_package_specification.md
Normal file
|
@ -0,0 +1,448 @@
|
||||||
|
# Contracts Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Contracts package defines the core interfaces and contracts that form the foundation of the framework. These contracts ensure consistency and interoperability between components while enabling loose coupling and dependency injection.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
|
||||||
|
## Core Contracts
|
||||||
|
|
||||||
|
### 1. Container Contracts
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core container interface
|
||||||
|
abstract class ContainerContract {
|
||||||
|
/// Resolves a type from the container
|
||||||
|
T make<T>([dynamic context]);
|
||||||
|
|
||||||
|
/// Binds a type to the container
|
||||||
|
void bind<T>(T Function(ContainerContract) concrete);
|
||||||
|
|
||||||
|
/// Binds a singleton to the container
|
||||||
|
void singleton<T>(T Function(ContainerContract) concrete);
|
||||||
|
|
||||||
|
/// Checks if a type is bound
|
||||||
|
bool has<T>();
|
||||||
|
|
||||||
|
/// Tags implementations for grouped resolution
|
||||||
|
void tag(List<Type> implementations, String tag);
|
||||||
|
|
||||||
|
/// Gets all implementations with a tag
|
||||||
|
List<T> tagged<T>(String tag);
|
||||||
|
|
||||||
|
/// Adds a contextual binding
|
||||||
|
void addContextualBinding(Type concrete, Type abstract, dynamic implementation);
|
||||||
|
|
||||||
|
/// Creates a new child container
|
||||||
|
ContainerContract createChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for contextual binding
|
||||||
|
abstract class ContextualBindingBuilder {
|
||||||
|
/// Specifies the type needed in this context
|
||||||
|
ContextualNeedsBuilder needs<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for contextual needs
|
||||||
|
abstract class ContextualNeedsBuilder {
|
||||||
|
/// Specifies what to give for this need
|
||||||
|
void give(dynamic implementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for service providers
|
||||||
|
abstract class ServiceProviderContract {
|
||||||
|
/// Registers services with the container
|
||||||
|
void register();
|
||||||
|
|
||||||
|
/// Bootstraps any services
|
||||||
|
Future<void> boot();
|
||||||
|
|
||||||
|
/// Gets provided services
|
||||||
|
List<Type> provides();
|
||||||
|
|
||||||
|
/// Whether provider is deferred
|
||||||
|
bool get isDeferred => false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Event Contracts
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core event dispatcher interface
|
||||||
|
abstract class EventDispatcherContract {
|
||||||
|
/// Registers an event listener
|
||||||
|
void listen<T>(void Function(T event) listener);
|
||||||
|
|
||||||
|
/// Dispatches an event
|
||||||
|
Future<void> dispatch<T>(T event);
|
||||||
|
|
||||||
|
/// Registers an event subscriber
|
||||||
|
void subscribe(EventSubscriber subscriber);
|
||||||
|
|
||||||
|
/// Dispatches an event after database commit
|
||||||
|
Future<void> dispatchAfterCommit<T>(T event);
|
||||||
|
|
||||||
|
/// Gets registered listeners
|
||||||
|
List<Function> getListeners(Type event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for event subscribers
|
||||||
|
abstract class EventSubscriber {
|
||||||
|
/// Gets events to subscribe to
|
||||||
|
Map<Type, Function> subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for queueable events
|
||||||
|
abstract class ShouldQueue {
|
||||||
|
/// Gets the queue to use
|
||||||
|
String get queue => 'default';
|
||||||
|
|
||||||
|
/// Gets the delay before processing
|
||||||
|
Duration? get delay => null;
|
||||||
|
|
||||||
|
/// Gets the number of tries
|
||||||
|
int get tries => 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for broadcastable events
|
||||||
|
abstract class ShouldBroadcast {
|
||||||
|
/// Gets channels to broadcast on
|
||||||
|
List<String> broadcastOn();
|
||||||
|
|
||||||
|
/// Gets event name for broadcasting
|
||||||
|
String broadcastAs() => runtimeType.toString();
|
||||||
|
|
||||||
|
/// Gets broadcast data
|
||||||
|
Map<String, dynamic> get broadcastWith => {};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Queue Contracts
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core queue interface
|
||||||
|
abstract class QueueContract {
|
||||||
|
/// Pushes a job onto the queue
|
||||||
|
Future<String> push(dynamic job, [String? queue]);
|
||||||
|
|
||||||
|
/// Pushes a job with delay
|
||||||
|
Future<String> later(Duration delay, dynamic job, [String? queue]);
|
||||||
|
|
||||||
|
/// Gets next job from queue
|
||||||
|
Future<Job?> pop([String? queue]);
|
||||||
|
|
||||||
|
/// Creates a job batch
|
||||||
|
Batch batch(List<Job> jobs);
|
||||||
|
|
||||||
|
/// Gets a queue connection
|
||||||
|
QueueConnection connection([String? name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for queue jobs
|
||||||
|
abstract class Job {
|
||||||
|
/// Unique job ID
|
||||||
|
String get id;
|
||||||
|
|
||||||
|
/// Job payload
|
||||||
|
Map<String, dynamic> get payload;
|
||||||
|
|
||||||
|
/// Number of attempts
|
||||||
|
int get attempts;
|
||||||
|
|
||||||
|
/// Maximum tries
|
||||||
|
int get tries => 1;
|
||||||
|
|
||||||
|
/// Timeout in seconds
|
||||||
|
int get timeout => 60;
|
||||||
|
|
||||||
|
/// Executes the job
|
||||||
|
Future<void> handle();
|
||||||
|
|
||||||
|
/// Handles job failure
|
||||||
|
Future<void> failed([Exception? exception]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for job batches
|
||||||
|
abstract class Batch {
|
||||||
|
/// Batch ID
|
||||||
|
String get id;
|
||||||
|
|
||||||
|
/// Jobs in batch
|
||||||
|
List<Job> get jobs;
|
||||||
|
|
||||||
|
/// Adds jobs to batch
|
||||||
|
void add(List<Job> jobs);
|
||||||
|
|
||||||
|
/// Dispatches the batch
|
||||||
|
Future<void> dispatch();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Bus Contracts
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core command bus interface
|
||||||
|
abstract class CommandBusContract {
|
||||||
|
/// Dispatches a command
|
||||||
|
Future<dynamic> dispatch(Command command);
|
||||||
|
|
||||||
|
/// Dispatches a command now
|
||||||
|
Future<dynamic> dispatchNow(Command command);
|
||||||
|
|
||||||
|
/// Dispatches a command to queue
|
||||||
|
Future<dynamic> dispatchToQueue(Command command);
|
||||||
|
|
||||||
|
/// Creates a command batch
|
||||||
|
PendingBatch batch(List<Command> commands);
|
||||||
|
|
||||||
|
/// Creates a command chain
|
||||||
|
PendingChain chain(List<Command> commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for commands
|
||||||
|
abstract class Command {
|
||||||
|
/// Gets command handler
|
||||||
|
Type get handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for command handlers
|
||||||
|
abstract class Handler<T extends Command> {
|
||||||
|
/// Handles the command
|
||||||
|
Future<dynamic> handle(T command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for command batches
|
||||||
|
abstract class PendingBatch {
|
||||||
|
/// Dispatches the batch
|
||||||
|
Future<void> dispatch();
|
||||||
|
|
||||||
|
/// Allows failures
|
||||||
|
PendingBatch allowFailures();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Pipeline Contracts
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core pipeline interface
|
||||||
|
abstract class PipelineContract<T> {
|
||||||
|
/// Sends value through pipeline
|
||||||
|
PipelineContract<T> send(T passable);
|
||||||
|
|
||||||
|
/// Sets the pipes
|
||||||
|
PipelineContract<T> through(List<PipeContract<T>> pipes);
|
||||||
|
|
||||||
|
/// Processes the pipeline
|
||||||
|
Future<R> then<R>(Future<R> Function(T) destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for pipes
|
||||||
|
abstract class PipeContract<T> {
|
||||||
|
/// Handles the passable
|
||||||
|
Future<dynamic> handle(T passable, Function next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for pipeline hub
|
||||||
|
abstract class PipelineHubContract {
|
||||||
|
/// Gets a pipeline
|
||||||
|
PipelineContract<T> pipeline<T>(String name);
|
||||||
|
|
||||||
|
/// Sets default pipes
|
||||||
|
void defaults(List<PipeContract> pipes);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Container Usage
|
||||||
|
```dart
|
||||||
|
// Register service provider
|
||||||
|
class AppServiceProvider implements ServiceProviderContract {
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
container.bind<UserService>((c) => UserService(
|
||||||
|
c.make<DatabaseConnection>(),
|
||||||
|
c.make<CacheContract>()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> boot() async {
|
||||||
|
// Bootstrap services
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event Handling
|
||||||
|
```dart
|
||||||
|
// Define event
|
||||||
|
class OrderShipped implements ShouldQueue, ShouldBroadcast {
|
||||||
|
final Order order;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> broadcastOn() => ['orders.${order.id}'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get queue => 'notifications';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle event
|
||||||
|
dispatcher.listen<OrderShipped>((event) async {
|
||||||
|
await notifyCustomer(event.order);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Bus Usage
|
||||||
|
```dart
|
||||||
|
// Define command
|
||||||
|
class CreateOrder implements Command {
|
||||||
|
final String customerId;
|
||||||
|
final List<String> products;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Type get handler => CreateOrderHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle command
|
||||||
|
class CreateOrderHandler implements Handler<CreateOrder> {
|
||||||
|
@override
|
||||||
|
Future<Order> handle(CreateOrder command) async {
|
||||||
|
// Create order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch command
|
||||||
|
var order = await bus.dispatch(CreateOrder(
|
||||||
|
customerId: '123',
|
||||||
|
products: ['abc', 'xyz']
|
||||||
|
));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Event Dispatcher', () {
|
||||||
|
test('dispatches after commit', () async {
|
||||||
|
var dispatcher = MockEventDispatcher();
|
||||||
|
var db = MockDatabase();
|
||||||
|
|
||||||
|
await db.transaction((tx) async {
|
||||||
|
await dispatcher.dispatchAfterCommit(OrderShipped(order));
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(() => dispatcher.dispatch(any())).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Command Bus', () {
|
||||||
|
test('handles command batch', () async {
|
||||||
|
var bus = MockCommandBus();
|
||||||
|
|
||||||
|
await bus.batch([
|
||||||
|
CreateOrder(...),
|
||||||
|
UpdateInventory(...)
|
||||||
|
]).dispatch();
|
||||||
|
|
||||||
|
verify(() => bus.dispatchNow(any())).called(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contract Guidelines
|
||||||
|
|
||||||
|
1. **Keep Contracts Minimal**
|
||||||
|
```dart
|
||||||
|
// Good: Focused contract
|
||||||
|
abstract class Cache {
|
||||||
|
Future<T?> get<T>(String key);
|
||||||
|
Future<void> put<T>(String key, T value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad: Too many responsibilities
|
||||||
|
abstract class Cache {
|
||||||
|
Future<T?> get<T>(String key);
|
||||||
|
Future<void> put<T>(String key, T value);
|
||||||
|
void clearMemory();
|
||||||
|
void optimizeStorage();
|
||||||
|
void defragment();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Use Type Parameters**
|
||||||
|
```dart
|
||||||
|
// Good: Type safe
|
||||||
|
abstract class Repository<T> {
|
||||||
|
Future<T?> find(String id);
|
||||||
|
Future<void> save(T entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad: Dynamic typing
|
||||||
|
abstract class Repository {
|
||||||
|
Future<dynamic> find(String id);
|
||||||
|
Future<void> save(dynamic entity);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Document Contracts**
|
||||||
|
```dart
|
||||||
|
/// Contract for caching implementations.
|
||||||
|
///
|
||||||
|
/// Implementations must:
|
||||||
|
/// - Handle serialization
|
||||||
|
/// - Be thread-safe
|
||||||
|
/// - Support TTL
|
||||||
|
abstract class Cache {
|
||||||
|
/// Gets a value from cache.
|
||||||
|
///
|
||||||
|
/// Returns null if not found.
|
||||||
|
/// Throws [CacheException] on error.
|
||||||
|
Future<T?> get<T>(String key);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement core contracts
|
||||||
|
2. Add integration tests
|
||||||
|
3. Document Laravel compatibility
|
||||||
|
4. Add migration guides
|
||||||
|
5. Create examples
|
||||||
|
6. Write tests
|
||||||
|
|
||||||
|
Would you like me to enhance any other package specifications?
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing contracts:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each contract:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing contracts:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
309
docs/core_architecture.md
Normal file
309
docs/core_architecture.md
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
# Core Architecture
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document explains the architectural decisions, patterns, and system design of our core package. It provides insights into how the framework components interact and how to extend the system.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Core Package Specification](core_package_specification.md) for implementation details
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Container Package Specification](container_package_specification.md) for dependency injection
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for event system
|
||||||
|
|
||||||
|
## Architectural Patterns
|
||||||
|
|
||||||
|
### 1. Service Container Architecture
|
||||||
|
|
||||||
|
The framework is built around a central service container that manages dependencies and provides inversion of control:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Application │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────┐ ┌──────────────┐ │
|
||||||
|
│ │Service Container│ │Event Dispatch│ │
|
||||||
|
│ └─────────────────┘ └──────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────┐ ┌──────────────┐ │
|
||||||
|
│ │Service Providers│ │ Pipeline │ │
|
||||||
|
│ └─────────────────┘ └──────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Key aspects:
|
||||||
|
- Central service container manages all dependencies
|
||||||
|
- Service providers bootstrap framework services
|
||||||
|
- Event system enables loose coupling
|
||||||
|
- Pipeline pattern for request/response handling
|
||||||
|
|
||||||
|
### 2. Request Lifecycle
|
||||||
|
|
||||||
|
The request flows through several layers:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────┐ ┌────────────┐ ┌─────────────┐
|
||||||
|
│ Server │ -> │HTTP Kernel │ -> │ Pipeline │
|
||||||
|
└──────────┘ └────────────┘ └─────────────┘
|
||||||
|
|
|
||||||
|
┌──────────┐ ┌────────────┐ ┌─────▼─────┐
|
||||||
|
│ Response │ <- │ Controller │ <- │ Router │
|
||||||
|
└──────────┘ └────────────┘ └───────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Stages:
|
||||||
|
1. Server receives HTTP request
|
||||||
|
2. HTTP Kernel applies global middleware
|
||||||
|
3. Pipeline processes middleware stack
|
||||||
|
4. Router matches route
|
||||||
|
5. Controller handles request
|
||||||
|
6. Response flows back through layers
|
||||||
|
|
||||||
|
### 3. Service Provider Pattern
|
||||||
|
|
||||||
|
Service providers bootstrap framework components:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Application │
|
||||||
|
└───────┬─────────┘
|
||||||
|
|
|
||||||
|
┌───────▼─────────┐
|
||||||
|
│Register Providers│
|
||||||
|
└───────┬─────────┘
|
||||||
|
|
|
||||||
|
┌───────▼─────────┐
|
||||||
|
│ Boot Providers │
|
||||||
|
└───────┬─────────┘
|
||||||
|
|
|
||||||
|
┌───────▼─────────┐
|
||||||
|
│ Ready to Handle │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Process:
|
||||||
|
1. Register core providers
|
||||||
|
2. Register package providers
|
||||||
|
3. Register application providers
|
||||||
|
4. Boot all providers
|
||||||
|
5. Application ready
|
||||||
|
|
||||||
|
### 4. Event-Driven Architecture
|
||||||
|
|
||||||
|
Events enable loose coupling between components:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────┐ ┌─────────────┐ ┌──────────┐
|
||||||
|
│ Dispatcher │ -> │ Events │ -> │Listeners │
|
||||||
|
└────────────┘ └─────────────┘ └──────────┘
|
||||||
|
|
|
||||||
|
┌────────────┐ ┌─────────────┐ ┌──────────┐
|
||||||
|
│ Queued │ <- │ Handler │ <- │ Process │
|
||||||
|
└────────────┘ └─────────────┘ └──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Event dispatching
|
||||||
|
- Synchronous/async listeners
|
||||||
|
- Event queueing
|
||||||
|
- Event subscribers
|
||||||
|
- Event broadcasting
|
||||||
|
|
||||||
|
## Extension Points
|
||||||
|
|
||||||
|
### 1. Service Providers
|
||||||
|
|
||||||
|
Create custom service providers to:
|
||||||
|
- Register services
|
||||||
|
- Bootstrap components
|
||||||
|
- Configure framework
|
||||||
|
- Add middleware
|
||||||
|
- Register routes
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class CustomServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
// Register services
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void boot() {
|
||||||
|
// Bootstrap components
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Middleware
|
||||||
|
|
||||||
|
Add middleware to:
|
||||||
|
- Process requests
|
||||||
|
- Modify responses
|
||||||
|
- Handle authentication
|
||||||
|
- Rate limiting
|
||||||
|
- Custom processing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class CustomMiddleware implements Middleware {
|
||||||
|
Future<Response> handle(Request request, Next next) async {
|
||||||
|
// Process request
|
||||||
|
var response = await next(request);
|
||||||
|
// Modify response
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Event Listeners
|
||||||
|
|
||||||
|
Create event listeners to:
|
||||||
|
- React to system events
|
||||||
|
- Handle async tasks
|
||||||
|
- Integrate external systems
|
||||||
|
- Add logging/monitoring
|
||||||
|
- Custom processing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class CustomListener {
|
||||||
|
void handle(CustomEvent event) {
|
||||||
|
// Handle event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Console Commands
|
||||||
|
|
||||||
|
Add console commands to:
|
||||||
|
- Run maintenance tasks
|
||||||
|
- Process queues
|
||||||
|
- Generate files
|
||||||
|
- Custom CLI tools
|
||||||
|
- System management
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class CustomCommand extends Command {
|
||||||
|
String get name => 'custom:command';
|
||||||
|
|
||||||
|
Future<void> handle() async {
|
||||||
|
// Command logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Integration
|
||||||
|
|
||||||
|
### 1. Core Package Dependencies
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐
|
||||||
|
│ Core │
|
||||||
|
└─────┬───────┘
|
||||||
|
|
|
||||||
|
┌─────▼───────┐ ┌────────────┐
|
||||||
|
│ Container │ --> │ Events │
|
||||||
|
└─────────────┘ └────────────┘
|
||||||
|
|
|
||||||
|
┌─────▼───────┐ ┌────────────┐
|
||||||
|
│ Pipeline │ --> │ Route │
|
||||||
|
└─────────────┘ └────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Optional Package Integration
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐
|
||||||
|
│ Core │
|
||||||
|
└─────┬───────┘
|
||||||
|
|
|
||||||
|
┌─────▼───────┐ ┌────────────┐
|
||||||
|
│ Queue │ --> │ Bus │
|
||||||
|
└─────────────┘ └────────────┘
|
||||||
|
|
|
||||||
|
┌─────▼───────┐ ┌────────────┐
|
||||||
|
│ Cache │ --> │ Mail │
|
||||||
|
└─────────────┘ └────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### 1. Service Container
|
||||||
|
- Optimize bindings
|
||||||
|
- Use singletons where appropriate
|
||||||
|
- Lazy load services
|
||||||
|
- Cache resolved instances
|
||||||
|
|
||||||
|
### 2. Request Handling
|
||||||
|
- Efficient middleware pipeline
|
||||||
|
- Route caching
|
||||||
|
- Response caching
|
||||||
|
- Resource pooling
|
||||||
|
|
||||||
|
### 3. Event System
|
||||||
|
- Async event processing
|
||||||
|
- Event batching
|
||||||
|
- Queue throttling
|
||||||
|
- Listener optimization
|
||||||
|
|
||||||
|
### 4. Memory Management
|
||||||
|
- Clean up resources
|
||||||
|
- Limit instance caching
|
||||||
|
- Monitor memory usage
|
||||||
|
- Handle memory pressure
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### 1. Request Validation
|
||||||
|
- Input sanitization
|
||||||
|
- CSRF protection
|
||||||
|
- XSS prevention
|
||||||
|
- SQL injection prevention
|
||||||
|
|
||||||
|
### 2. Authentication
|
||||||
|
- Secure session handling
|
||||||
|
- Token management
|
||||||
|
- Password hashing
|
||||||
|
- Rate limiting
|
||||||
|
|
||||||
|
### 3. Authorization
|
||||||
|
- Role-based access
|
||||||
|
- Permission checking
|
||||||
|
- Policy enforcement
|
||||||
|
- Resource protection
|
||||||
|
|
||||||
|
### 4. Data Protection
|
||||||
|
- Encryption at rest
|
||||||
|
- Secure communication
|
||||||
|
- Data sanitization
|
||||||
|
- Audit logging
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Core Development
|
||||||
|
- Follow framework patterns
|
||||||
|
- Maintain backward compatibility
|
||||||
|
- Document changes
|
||||||
|
- Write tests
|
||||||
|
- Consider performance
|
||||||
|
|
||||||
|
### 2. Package Development
|
||||||
|
- Use service providers
|
||||||
|
- Integrate with events
|
||||||
|
- Follow naming conventions
|
||||||
|
- Add package tests
|
||||||
|
- Document features
|
||||||
|
|
||||||
|
### 3. Application Development
|
||||||
|
- Use dependency injection
|
||||||
|
- Handle events properly
|
||||||
|
- Follow middleware patterns
|
||||||
|
- Write clean code
|
||||||
|
- Test thoroughly
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Review architecture with team
|
||||||
|
2. Document design decisions
|
||||||
|
3. Create development guides
|
||||||
|
4. Set up monitoring
|
||||||
|
5. Plan optimizations
|
||||||
|
6. Schedule security review
|
556
docs/core_package_specification.md
Normal file
556
docs/core_package_specification.md
Normal file
|
@ -0,0 +1,556 @@
|
||||||
|
# Core Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Core package provides the foundation and entry point for our framework. It manages the application lifecycle, bootstraps services, handles HTTP requests, and coordinates all other framework packages.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Container Package Specification](container_package_specification.md) for dependency injection
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for application events
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Application
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core application class
|
||||||
|
class Application {
|
||||||
|
/// Container instance
|
||||||
|
final Container _container;
|
||||||
|
|
||||||
|
/// Service providers
|
||||||
|
final List<ServiceProvider> _providers = [];
|
||||||
|
|
||||||
|
/// Booted flag
|
||||||
|
bool _booted = false;
|
||||||
|
|
||||||
|
/// Environment
|
||||||
|
late final String environment;
|
||||||
|
|
||||||
|
/// Base path
|
||||||
|
late final String basePath;
|
||||||
|
|
||||||
|
Application(this._container) {
|
||||||
|
_container.instance<Application>(this);
|
||||||
|
_registerBaseBindings();
|
||||||
|
_registerCoreProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers base bindings
|
||||||
|
void _registerBaseBindings() {
|
||||||
|
_container.instance<Container>(_container);
|
||||||
|
_container.instance<String>('base_path', basePath);
|
||||||
|
_container.instance<String>('env', environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers core providers
|
||||||
|
void _registerCoreProviders() {
|
||||||
|
register(EventServiceProvider());
|
||||||
|
register(LogServiceProvider());
|
||||||
|
register(RoutingServiceProvider());
|
||||||
|
register(ConfigServiceProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a service provider
|
||||||
|
void register(ServiceProvider provider) {
|
||||||
|
provider.app = this;
|
||||||
|
provider.register();
|
||||||
|
_providers.add(provider);
|
||||||
|
|
||||||
|
if (_booted) {
|
||||||
|
_bootProvider(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Boots the application
|
||||||
|
Future<void> boot() async {
|
||||||
|
if (_booted) return;
|
||||||
|
|
||||||
|
for (var provider in _providers) {
|
||||||
|
await _bootProvider(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
_booted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Boots a provider
|
||||||
|
Future<void> _bootProvider(ServiceProvider provider) async {
|
||||||
|
await provider.callBootingCallbacks();
|
||||||
|
await provider.boot();
|
||||||
|
await provider.callBootedCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles HTTP request
|
||||||
|
Future<Response> handle(Request request) async {
|
||||||
|
try {
|
||||||
|
return await _pipeline.handle(request);
|
||||||
|
} catch (e) {
|
||||||
|
return _handleError(e, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets container instance
|
||||||
|
Container get container => _container;
|
||||||
|
|
||||||
|
/// Makes instance from container
|
||||||
|
T make<T>([dynamic parameters]) {
|
||||||
|
return _container.make<T>(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets environment
|
||||||
|
bool environment(String env) {
|
||||||
|
return this.environment == env;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines if application is in production
|
||||||
|
bool get isProduction => environment == 'production';
|
||||||
|
|
||||||
|
/// Determines if application is in development
|
||||||
|
bool get isDevelopment => environment == 'development';
|
||||||
|
|
||||||
|
/// Determines if application is in testing
|
||||||
|
bool get isTesting => environment == 'testing';
|
||||||
|
|
||||||
|
/// Gets base path
|
||||||
|
String path([String? path]) {
|
||||||
|
return [basePath, path].where((p) => p != null).join('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Service Providers
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Base service provider
|
||||||
|
abstract class ServiceProvider {
|
||||||
|
/// Application instance
|
||||||
|
late Application app;
|
||||||
|
|
||||||
|
/// Container instance
|
||||||
|
Container get container => app.container;
|
||||||
|
|
||||||
|
/// Booting callbacks
|
||||||
|
final List<Function> _bootingCallbacks = [];
|
||||||
|
|
||||||
|
/// Booted callbacks
|
||||||
|
final List<Function> _bootedCallbacks = [];
|
||||||
|
|
||||||
|
/// Registers services
|
||||||
|
void register();
|
||||||
|
|
||||||
|
/// Boots services
|
||||||
|
Future<void> boot() async {}
|
||||||
|
|
||||||
|
/// Registers booting callback
|
||||||
|
void booting(Function callback) {
|
||||||
|
_bootingCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers booted callback
|
||||||
|
void booted(Function callback) {
|
||||||
|
_bootedCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls booting callbacks
|
||||||
|
Future<void> callBootingCallbacks() async {
|
||||||
|
for (var callback in _bootingCallbacks) {
|
||||||
|
await callback(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls booted callbacks
|
||||||
|
Future<void> callBootedCallbacks() async {
|
||||||
|
for (var callback in _bootedCallbacks) {
|
||||||
|
await callback(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event service provider
|
||||||
|
class EventServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
container.singleton<EventDispatcherContract>((c) =>
|
||||||
|
EventDispatcher(c)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Routing service provider
|
||||||
|
class RoutingServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
container.singleton<RouterContract>((c) =>
|
||||||
|
Router(c)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> boot() async {
|
||||||
|
var router = container.make<RouterContract>();
|
||||||
|
await loadRoutes(router);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. HTTP Kernel
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// HTTP kernel
|
||||||
|
class HttpKernel {
|
||||||
|
/// Application instance
|
||||||
|
final Application _app;
|
||||||
|
|
||||||
|
/// Global middleware
|
||||||
|
final List<dynamic> middleware = [
|
||||||
|
CheckForMaintenanceMode::class,
|
||||||
|
ValidatePostSize::class,
|
||||||
|
TrimStrings::class,
|
||||||
|
ConvertEmptyStringsToNull::class
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Route middleware groups
|
||||||
|
final Map<String, List<dynamic>> middlewareGroups = {
|
||||||
|
'web': [
|
||||||
|
EncryptCookies::class,
|
||||||
|
AddQueuedCookiesToResponse::class,
|
||||||
|
StartSession::class,
|
||||||
|
ShareErrorsFromSession::class,
|
||||||
|
VerifyCsrfToken::class,
|
||||||
|
SubstituteBindings::class
|
||||||
|
],
|
||||||
|
|
||||||
|
'api': [
|
||||||
|
'throttle:60,1',
|
||||||
|
SubstituteBindings::class
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Route middleware aliases
|
||||||
|
final Map<String, dynamic> routeMiddleware = {
|
||||||
|
'auth': Authenticate::class,
|
||||||
|
'auth.basic': AuthenticateWithBasicAuth::class,
|
||||||
|
'bindings': SubstituteBindings::class,
|
||||||
|
'cache.headers': SetCacheHeaders::class,
|
||||||
|
'can': Authorize::class,
|
||||||
|
'guest': RedirectIfAuthenticated::class,
|
||||||
|
'signed': ValidateSignature::class,
|
||||||
|
'throttle': ThrottleRequests::class,
|
||||||
|
'verified': EnsureEmailIsVerified::class,
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpKernel(this._app);
|
||||||
|
|
||||||
|
/// Handles HTTP request
|
||||||
|
Future<Response> handle(Request request) async {
|
||||||
|
try {
|
||||||
|
request = await _handleGlobalMiddleware(request);
|
||||||
|
return await _app.handle(request);
|
||||||
|
} catch (e) {
|
||||||
|
return _handleError(e, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles global middleware
|
||||||
|
Future<Request> _handleGlobalMiddleware(Request request) async {
|
||||||
|
var pipeline = _app.make<Pipeline>();
|
||||||
|
|
||||||
|
return await pipeline
|
||||||
|
.send(request)
|
||||||
|
.through(middleware)
|
||||||
|
.then((request) => request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles error
|
||||||
|
Response _handleError(Object error, Request request) {
|
||||||
|
var handler = _app.make<ExceptionHandler>();
|
||||||
|
return handler.render(error, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Console Kernel
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Console kernel
|
||||||
|
class ConsoleKernel {
|
||||||
|
/// Application instance
|
||||||
|
final Application _app;
|
||||||
|
|
||||||
|
/// Console commands
|
||||||
|
final List<dynamic> commands = [
|
||||||
|
// Framework Commands
|
||||||
|
KeyGenerateCommand::class,
|
||||||
|
ConfigCacheCommand::class,
|
||||||
|
ConfigClearCommand::class,
|
||||||
|
RouteListCommand::class,
|
||||||
|
RouteCacheCommand::class,
|
||||||
|
RouteClearCommand::class,
|
||||||
|
|
||||||
|
// App Commands
|
||||||
|
SendEmailsCommand::class,
|
||||||
|
PruneOldRecordsCommand::class
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Command schedules
|
||||||
|
final Map<String, String> schedules = {
|
||||||
|
'emails:send': '0 * * * *',
|
||||||
|
'records:prune': '0 0 * * *'
|
||||||
|
};
|
||||||
|
|
||||||
|
ConsoleKernel(this._app);
|
||||||
|
|
||||||
|
/// Handles console command
|
||||||
|
Future<int> handle(List<String> args) async {
|
||||||
|
try {
|
||||||
|
var status = await _runCommand(args);
|
||||||
|
return status ?? 0;
|
||||||
|
} catch (e) {
|
||||||
|
_handleError(e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs console command
|
||||||
|
Future<int?> _runCommand(List<String> args) async {
|
||||||
|
var command = _resolveCommand(args);
|
||||||
|
if (command == null) return null;
|
||||||
|
|
||||||
|
return await command.run(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves command from arguments
|
||||||
|
Command? _resolveCommand(List<String> args) {
|
||||||
|
if (args.isEmpty) return null;
|
||||||
|
|
||||||
|
var name = args.first;
|
||||||
|
var command = commands.firstWhere(
|
||||||
|
(c) => c.name == name,
|
||||||
|
orElse: () => null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (command == null) return null;
|
||||||
|
return _app.make<Command>(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles error
|
||||||
|
void _handleError(Object error) {
|
||||||
|
stderr.writeln(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Exception Handler
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Exception handler
|
||||||
|
class ExceptionHandler {
|
||||||
|
/// Application instance
|
||||||
|
final Application _app;
|
||||||
|
|
||||||
|
/// Exception renderers
|
||||||
|
final Map<Type, Function> _renderers = {
|
||||||
|
ValidationException: _renderValidationException,
|
||||||
|
AuthenticationException: _renderAuthenticationException,
|
||||||
|
AuthorizationException: _renderAuthorizationException,
|
||||||
|
NotFoundException: _renderNotFoundException,
|
||||||
|
HttpException: _renderHttpException
|
||||||
|
};
|
||||||
|
|
||||||
|
ExceptionHandler(this._app);
|
||||||
|
|
||||||
|
/// Renders exception to response
|
||||||
|
Response render(Object error, Request request) {
|
||||||
|
var renderer = _renderers[error.runtimeType];
|
||||||
|
if (renderer != null) {
|
||||||
|
return renderer(error, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _renderGenericException(error, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders validation exception
|
||||||
|
Response _renderValidationException(
|
||||||
|
ValidationException e,
|
||||||
|
Request request
|
||||||
|
) {
|
||||||
|
if (request.wantsJson) {
|
||||||
|
return Response.json({
|
||||||
|
'message': 'The given data was invalid.',
|
||||||
|
'errors': e.errors
|
||||||
|
}, 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.redirect()
|
||||||
|
.back()
|
||||||
|
.withErrors(e.errors)
|
||||||
|
.withInput(request.all());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders generic exception
|
||||||
|
Response _renderGenericException(Object e, Request request) {
|
||||||
|
if (_app.isProduction) {
|
||||||
|
return Response('Server Error', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(e.toString(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
### 1. Application Bootstrap
|
||||||
|
```dart
|
||||||
|
void main() async {
|
||||||
|
var container = Container();
|
||||||
|
var app = Application(container)
|
||||||
|
..environment = 'production'
|
||||||
|
..basePath = Directory.current.path;
|
||||||
|
|
||||||
|
await app.boot();
|
||||||
|
|
||||||
|
var server = HttpServer(app);
|
||||||
|
await server.start();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Service Provider
|
||||||
|
```dart
|
||||||
|
class AppServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
container.singleton<UserRepository>((c) =>
|
||||||
|
DatabaseUserRepository(c.make<Database>())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> boot() async {
|
||||||
|
var config = container.make<ConfigContract>();
|
||||||
|
TimeZone.setDefault(config.get('app.timezone'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. HTTP Request Handling
|
||||||
|
```dart
|
||||||
|
class Server {
|
||||||
|
final HttpKernel kernel;
|
||||||
|
|
||||||
|
Future<void> handle(HttpRequest request) async {
|
||||||
|
var protevusRequest = await Request.fromHttpRequest(request);
|
||||||
|
var response = await kernel.handle(protevusRequest);
|
||||||
|
await response.send(request.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Application', () {
|
||||||
|
test('boots providers', () async {
|
||||||
|
var app = Application(Container());
|
||||||
|
var provider = TestProvider();
|
||||||
|
|
||||||
|
app.register(provider);
|
||||||
|
await app.boot();
|
||||||
|
|
||||||
|
expect(provider.booted, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles requests', () async {
|
||||||
|
var app = Application(Container());
|
||||||
|
await app.boot();
|
||||||
|
|
||||||
|
var request = Request('GET', '/');
|
||||||
|
var response = await app.handle(request);
|
||||||
|
|
||||||
|
expect(response.statusCode, equals(200));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Service Provider', () {
|
||||||
|
test('registers services', () async {
|
||||||
|
var app = Application(Container());
|
||||||
|
var provider = TestProvider();
|
||||||
|
|
||||||
|
app.register(provider);
|
||||||
|
|
||||||
|
expect(app.make<TestService>(), isNotNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement core application
|
||||||
|
2. Add service providers
|
||||||
|
3. Add HTTP kernel
|
||||||
|
4. Add console kernel
|
||||||
|
5. Write tests
|
||||||
|
6. Add benchmarks
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing core features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Container Package Specification](container_package_specification.md)
|
||||||
|
6. Review [Events Package Specification](events_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each core feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following framework patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Follow framework patterns
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Support dependency injection (see [Container Package Specification](container_package_specification.md))
|
||||||
|
5. Support event system (see [Events Package Specification](events_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing core features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Use framework patterns consistently
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Core system must:
|
||||||
|
1. Boot efficiently
|
||||||
|
2. Handle requests quickly
|
||||||
|
3. Manage memory usage
|
||||||
|
4. Scale horizontally
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Core tests must:
|
||||||
|
1. Cover all core features
|
||||||
|
2. Test application lifecycle
|
||||||
|
3. Verify service providers
|
||||||
|
4. Check error handling
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Core documentation must:
|
||||||
|
1. Explain framework patterns
|
||||||
|
2. Show lifecycle examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
295
docs/events_gap_analysis.md
Normal file
295
docs/events_gap_analysis.md
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
# Events Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our Events package's actual implementation and our documentation, identifying areas that need implementation or documentation updates.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
|
||||||
|
## Implementation Gaps
|
||||||
|
|
||||||
|
### 1. Missing Laravel Features
|
||||||
|
```dart
|
||||||
|
// Documented but not implemented:
|
||||||
|
|
||||||
|
// 1. Event Discovery
|
||||||
|
class EventDiscovery {
|
||||||
|
// Need to implement:
|
||||||
|
Map<Type, Function> discoverHandlers(Type type);
|
||||||
|
void discoverEvents(String path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. After Commit Handling
|
||||||
|
class DatabaseEventDispatcher {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> dispatchAfterCommit<T>(T event);
|
||||||
|
void afterCommit(Function callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Better Broadcasting
|
||||||
|
class BroadcastManager {
|
||||||
|
// Need to implement:
|
||||||
|
Channel privateChannel(String name);
|
||||||
|
PresenceChannel presenceChannel(String name);
|
||||||
|
Future<void> broadcast(List<String> channels, String event, dynamic data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Existing Features Not Documented
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Implemented but not documented:
|
||||||
|
|
||||||
|
// 1. Wildcard Event Listeners
|
||||||
|
class Dispatcher {
|
||||||
|
/// Adds wildcard event listener
|
||||||
|
void _setupWildcardListen(String event, Function listener) {
|
||||||
|
_wildcards.putIfAbsent(event, () => []).add(listener);
|
||||||
|
_wildcardsCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets wildcard listeners
|
||||||
|
List<Function> _getWildcardListeners(String eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Event Bus Integration
|
||||||
|
class Dispatcher {
|
||||||
|
/// EventBus integration
|
||||||
|
final EventBus _eventBus;
|
||||||
|
final Map<String, StreamSubscription> _eventBusSubscriptions = {};
|
||||||
|
|
||||||
|
/// Subscribes to EventBus
|
||||||
|
void subscribe(EventBusSubscriber subscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Message Queue Integration
|
||||||
|
class Dispatcher {
|
||||||
|
/// MQ integration
|
||||||
|
late final MQClient? _mqClient;
|
||||||
|
|
||||||
|
/// Queue setup
|
||||||
|
void _setupQueuesAndExchanges();
|
||||||
|
void _startProcessingQueuedEvents();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Integration Points Not Documented
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 1. Container Integration
|
||||||
|
class Dispatcher {
|
||||||
|
/// Container reference
|
||||||
|
final Container container;
|
||||||
|
|
||||||
|
/// Queue resolver
|
||||||
|
late final Function _queueResolver;
|
||||||
|
|
||||||
|
/// Transaction manager resolver
|
||||||
|
late final Function _transactionManagerResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. ReactiveX Integration
|
||||||
|
class Dispatcher {
|
||||||
|
/// Subject management
|
||||||
|
final Map<String, BehaviorSubject<dynamic>> _subjects = {};
|
||||||
|
|
||||||
|
/// Stream access
|
||||||
|
Stream<T> on<T>(String event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Resource Management
|
||||||
|
class Dispatcher {
|
||||||
|
/// Cleanup
|
||||||
|
Future<void> close();
|
||||||
|
void dispose();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Gaps
|
||||||
|
|
||||||
|
### 1. Missing API Documentation
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Need to document:
|
||||||
|
|
||||||
|
/// Listens for events using wildcard patterns.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// dispatcher.listen('user.*', (event, data) {
|
||||||
|
/// // Handles all user events
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
void listen(String pattern, Function listener);
|
||||||
|
|
||||||
|
/// Subscribes to event streams using ReactiveX.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// dispatcher.on<UserCreated>('user.created')
|
||||||
|
/// .listen((event) {
|
||||||
|
/// // Handle user created event
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
Stream<T> on<T>(String event);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Integration Examples
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Need examples for:
|
||||||
|
|
||||||
|
// 1. EventBus Integration
|
||||||
|
var subscriber = MyEventSubscriber();
|
||||||
|
dispatcher.subscribe(subscriber);
|
||||||
|
|
||||||
|
// 2. Message Queue Integration
|
||||||
|
dispatcher.setMQClient(mqClient);
|
||||||
|
await dispatcher.push('user.created', userData);
|
||||||
|
|
||||||
|
// 3. ReactiveX Integration
|
||||||
|
dispatcher.on<UserEvent>('user.*')
|
||||||
|
.where((e) => e.type == 'premium')
|
||||||
|
.listen((e) => handlePremiumUser(e));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Test Coverage
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Need tests for:
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Wildcard Events', () {
|
||||||
|
test('matches wildcard patterns', () {
|
||||||
|
var dispatcher = Dispatcher(container);
|
||||||
|
var received = <String>[];
|
||||||
|
|
||||||
|
dispatcher.listen('user.*', (event, _) {
|
||||||
|
received.add(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
await dispatcher.dispatch('user.created');
|
||||||
|
await dispatcher.dispatch('user.updated');
|
||||||
|
|
||||||
|
expect(received, ['user.created', 'user.updated']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Queue Integration', () {
|
||||||
|
test('queues events properly', () async {
|
||||||
|
var dispatcher = Dispatcher(container);
|
||||||
|
dispatcher.setMQClient(mockClient);
|
||||||
|
|
||||||
|
await dispatcher.push('delayed.event', data);
|
||||||
|
|
||||||
|
verify(() => mockClient.sendMessage(
|
||||||
|
exchangeName: any,
|
||||||
|
routingKey: any,
|
||||||
|
message: any
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **High Priority**
|
||||||
|
- Event discovery (Laravel compatibility)
|
||||||
|
- After commit handling (Laravel compatibility)
|
||||||
|
- Better broadcasting support
|
||||||
|
|
||||||
|
2. **Medium Priority**
|
||||||
|
- Better queue integration
|
||||||
|
- Enhanced wildcard support
|
||||||
|
- Performance optimizations
|
||||||
|
|
||||||
|
3. **Low Priority**
|
||||||
|
- Additional helper methods
|
||||||
|
- Extended testing utilities
|
||||||
|
- Debug/profiling tools
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Implementation Tasks**
|
||||||
|
- Add event discovery
|
||||||
|
- Add after commit handling
|
||||||
|
- Enhance broadcasting
|
||||||
|
- Improve queue integration
|
||||||
|
|
||||||
|
2. **Documentation Tasks**
|
||||||
|
- Document wildcard events
|
||||||
|
- Document EventBus integration
|
||||||
|
- Document MQ integration
|
||||||
|
- Add integration examples
|
||||||
|
|
||||||
|
3. **Testing Tasks**
|
||||||
|
- Add wildcard event tests
|
||||||
|
- Add queue integration tests
|
||||||
|
- Add ReactiveX integration tests
|
||||||
|
- Add resource cleanup tests
|
||||||
|
|
||||||
|
Would you like me to:
|
||||||
|
1. Start implementing missing features?
|
||||||
|
2. Update documentation for existing features?
|
||||||
|
3. Create test cases for missing coverage?
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing event features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Events Package Specification](events_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each event feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in [Events Package Specification](events_package_specification.md)
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing event features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Events system must:
|
||||||
|
1. Handle high event throughput
|
||||||
|
2. Minimize memory usage
|
||||||
|
3. Support async operations
|
||||||
|
4. Scale horizontally
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Event tests must:
|
||||||
|
1. Cover all event scenarios
|
||||||
|
2. Test async behavior
|
||||||
|
3. Verify queue integration
|
||||||
|
4. Check broadcasting
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Event documentation must:
|
||||||
|
1. Explain event patterns
|
||||||
|
2. Show integration examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
453
docs/events_package_specification.md
Normal file
453
docs/events_package_specification.md
Normal file
|
@ -0,0 +1,453 @@
|
||||||
|
# Events Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Events package provides a robust event system that matches Laravel's event functionality while leveraging Dart's async capabilities. It integrates with our Queue, Bus, and Database packages to provide a complete event handling solution.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Contracts Package Specification](contracts_package_specification.md) for event contracts
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Event Dispatcher
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core event dispatcher implementation
|
||||||
|
class EventDispatcher implements EventDispatcherContract {
|
||||||
|
final Container _container;
|
||||||
|
final Map<Type, List<EventListener>> _listeners = {};
|
||||||
|
final List<EventSubscriber> _subscribers = {};
|
||||||
|
final QueueContract? _queue;
|
||||||
|
final BroadcasterContract? _broadcaster;
|
||||||
|
final List<dynamic> _afterCommitEvents = [];
|
||||||
|
|
||||||
|
EventDispatcher(
|
||||||
|
this._container, {
|
||||||
|
QueueContract? queue,
|
||||||
|
BroadcasterContract? broadcaster
|
||||||
|
}) : _queue = queue,
|
||||||
|
_broadcaster = broadcaster;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void listen<T>(void Function(T event) listener) {
|
||||||
|
_listeners.putIfAbsent(T, () => []).add(
|
||||||
|
EventListener<T>(listener)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> dispatch<T>(T event) async {
|
||||||
|
var listeners = _listeners[T] ?? [];
|
||||||
|
|
||||||
|
// Handle after commit events
|
||||||
|
if (event is ShouldDispatchAfterCommit && _isWithinTransaction()) {
|
||||||
|
_afterCommitEvents.add(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle queued events
|
||||||
|
if (event is ShouldQueue && _queue != null) {
|
||||||
|
await _queueEvent(event, listeners);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle broadcasting
|
||||||
|
if (event is ShouldBroadcast && _broadcaster != null) {
|
||||||
|
await _broadcastEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify listeners
|
||||||
|
await _notifyListeners(event, listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> dispatchAfterCommit<T>(T event) async {
|
||||||
|
if (_isWithinTransaction()) {
|
||||||
|
_afterCommitEvents.add(event);
|
||||||
|
} else {
|
||||||
|
await dispatch(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isWithinTransaction() {
|
||||||
|
if (_container.has<DatabaseManager>()) {
|
||||||
|
var db = _container.make<DatabaseManager>();
|
||||||
|
return db.transactionLevel > 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _dispatchAfterCommitEvents() async {
|
||||||
|
var events = List.from(_afterCommitEvents);
|
||||||
|
_afterCommitEvents.clear();
|
||||||
|
|
||||||
|
for (var event in events) {
|
||||||
|
await dispatch(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Event Discovery
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Discovers event handlers through reflection and attributes
|
||||||
|
class EventDiscovery {
|
||||||
|
final Container _container;
|
||||||
|
final Reflector _reflector;
|
||||||
|
|
||||||
|
EventDiscovery(this._container, this._reflector);
|
||||||
|
|
||||||
|
/// Discovers event handlers in a directory
|
||||||
|
Future<void> discoverEvents(String path) async {
|
||||||
|
var files = Directory(path).listSync(recursive: true);
|
||||||
|
|
||||||
|
for (var file in files) {
|
||||||
|
if (file.path.endsWith('.dart')) {
|
||||||
|
await _processFile(file.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _processFile(String path) async {
|
||||||
|
var library = await _reflector.loadLibrary(path);
|
||||||
|
|
||||||
|
for (var type in library.declarations.values) {
|
||||||
|
if (type is ClassMirror) {
|
||||||
|
_processClass(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _processClass(ClassMirror classMirror) {
|
||||||
|
// Find @Handles annotations
|
||||||
|
for (var method in classMirror.declarations.values) {
|
||||||
|
if (method is MethodMirror) {
|
||||||
|
var handles = method.metadata
|
||||||
|
.firstWhere((m) => m.type == Handles,
|
||||||
|
orElse: () => null);
|
||||||
|
|
||||||
|
if (handles != null) {
|
||||||
|
var eventType = handles.getField('event').reflectee;
|
||||||
|
_registerHandler(classMirror.reflectedType, method.simpleName, eventType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registerHandler(Type classType, Symbol methodName, Type eventType) {
|
||||||
|
var instance = _container.make(classType);
|
||||||
|
var dispatcher = _container.make<EventDispatcherContract>();
|
||||||
|
|
||||||
|
dispatcher.listen(eventType, (event) {
|
||||||
|
var mirror = reflect(instance);
|
||||||
|
mirror.invoke(methodName, [event]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Event Broadcasting
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Contract for event broadcasters
|
||||||
|
abstract class BroadcasterContract {
|
||||||
|
/// Broadcasts an event
|
||||||
|
Future<void> broadcast(
|
||||||
|
List<String> channels,
|
||||||
|
String eventName,
|
||||||
|
dynamic data
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Creates a private channel
|
||||||
|
Channel privateChannel(String name);
|
||||||
|
|
||||||
|
/// Creates a presence channel
|
||||||
|
PresenceChannel presenceChannel(String name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pusher event broadcaster
|
||||||
|
class PusherBroadcaster implements BroadcasterContract {
|
||||||
|
final PusherClient _client;
|
||||||
|
final AuthManager _auth;
|
||||||
|
|
||||||
|
PusherBroadcaster(this._client, this._auth);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> broadcast(
|
||||||
|
List<String> channels,
|
||||||
|
String eventName,
|
||||||
|
dynamic data
|
||||||
|
) async {
|
||||||
|
for (var channel in channels) {
|
||||||
|
await _client.trigger(channel, eventName, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Channel privateChannel(String name) {
|
||||||
|
return PrivateChannel(_client, _auth, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PresenceChannel presenceChannel(String name) {
|
||||||
|
return PresenceChannel(_client, _auth, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Integration with Queue
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Job for processing queued events
|
||||||
|
class QueuedEventJob implements Job {
|
||||||
|
final dynamic event;
|
||||||
|
final List<EventListener> listeners;
|
||||||
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
QueuedEventJob({
|
||||||
|
required this.event,
|
||||||
|
required this.listeners,
|
||||||
|
this.data = const {}
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> handle() async {
|
||||||
|
for (var listener in listeners) {
|
||||||
|
try {
|
||||||
|
await listener.handle(event);
|
||||||
|
} catch (e) {
|
||||||
|
await _handleFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> failed([Exception? e]) async {
|
||||||
|
if (event is FailedEventHandler) {
|
||||||
|
await (event as FailedEventHandler).failed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get tries => event is HasTries ? (event as HasTries).tries : 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration? get timeout =>
|
||||||
|
event is HasTimeout ? (event as HasTimeout).timeout : null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Integration with Bus
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Event command for command bus integration
|
||||||
|
class EventCommand implements Command {
|
||||||
|
final dynamic event;
|
||||||
|
final List<EventListener> listeners;
|
||||||
|
|
||||||
|
EventCommand(this.event, this.listeners);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Type get handler => EventCommandHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler for event commands
|
||||||
|
class EventCommandHandler implements Handler<EventCommand> {
|
||||||
|
final EventDispatcher _events;
|
||||||
|
|
||||||
|
EventCommandHandler(this._events);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> handle(EventCommand command) async {
|
||||||
|
await _events._notifyListeners(
|
||||||
|
command.event,
|
||||||
|
command.listeners
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Basic Event Handling
|
||||||
|
```dart
|
||||||
|
// Define event
|
||||||
|
class OrderShipped {
|
||||||
|
final Order order;
|
||||||
|
OrderShipped(this.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create listener
|
||||||
|
@Handles(OrderShipped)
|
||||||
|
class OrderShippedListener {
|
||||||
|
void handle(OrderShipped event) {
|
||||||
|
// Handle event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register and dispatch
|
||||||
|
dispatcher.listen<OrderShipped>((event) {
|
||||||
|
// Handle event
|
||||||
|
});
|
||||||
|
|
||||||
|
await dispatcher.dispatch(OrderShipped(order));
|
||||||
|
```
|
||||||
|
|
||||||
|
### After Commit Events
|
||||||
|
```dart
|
||||||
|
class OrderCreated implements ShouldDispatchAfterCommit {
|
||||||
|
final Order order;
|
||||||
|
OrderCreated(this.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In transaction
|
||||||
|
await db.transaction((tx) async {
|
||||||
|
var order = await tx.create(orderData);
|
||||||
|
await dispatcher.dispatchAfterCommit(OrderCreated(order));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Broadcasting
|
||||||
|
```dart
|
||||||
|
class MessageSent implements ShouldBroadcast {
|
||||||
|
final Message message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> broadcastOn() => [
|
||||||
|
'private-chat.${message.roomId}'
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> get broadcastWith => {
|
||||||
|
'id': message.id,
|
||||||
|
'content': message.content,
|
||||||
|
'user': message.user.toJson()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create private channel
|
||||||
|
var channel = broadcaster.privateChannel('chat.123');
|
||||||
|
await channel.whisper('typing', {'user': 'john'});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Queue Integration
|
||||||
|
```dart
|
||||||
|
class ProcessOrder implements ShouldQueue {
|
||||||
|
final Order order;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get queue => 'orders';
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get tries => 3;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration get timeout => Duration(minutes: 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch queued event
|
||||||
|
await dispatcher.dispatch(ProcessOrder(order));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Event Dispatcher', () {
|
||||||
|
test('dispatches after commit', () async {
|
||||||
|
var dispatcher = MockEventDispatcher();
|
||||||
|
var db = MockDatabase();
|
||||||
|
|
||||||
|
await db.transaction((tx) async {
|
||||||
|
await dispatcher.dispatchAfterCommit(OrderShipped(order));
|
||||||
|
expect(dispatcher.hasAfterCommitEvents, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(dispatcher.hasAfterCommitEvents, isFalse);
|
||||||
|
verify(() => dispatcher.dispatch(any())).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('discovers event handlers', () async {
|
||||||
|
var discovery = EventDiscovery(container, reflector);
|
||||||
|
await discovery.discoverEvents('lib/events');
|
||||||
|
|
||||||
|
var dispatcher = container.make<EventDispatcherContract>();
|
||||||
|
await dispatcher.dispatch(OrderShipped(order));
|
||||||
|
|
||||||
|
verify(() => orderListener.handle(any())).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Complete after commit handling
|
||||||
|
2. Enhance event discovery
|
||||||
|
3. Add more broadcast drivers
|
||||||
|
4. Improve queue integration
|
||||||
|
5. Add performance optimizations
|
||||||
|
6. Write more tests
|
||||||
|
|
||||||
|
Would you like me to enhance any other package specifications?
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing event features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Understand [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each event feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Implement required contracts (see [Contracts Package Specification](contracts_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing events:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
5. Implement all contracts from [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Event system must:
|
||||||
|
1. Handle high throughput efficiently
|
||||||
|
2. Minimize memory usage
|
||||||
|
3. Support async operations
|
||||||
|
4. Scale horizontally
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Event tests must:
|
||||||
|
1. Cover all event scenarios
|
||||||
|
2. Test async behavior
|
||||||
|
3. Verify queue integration
|
||||||
|
4. Check broadcasting
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Event documentation must:
|
||||||
|
1. Explain event patterns
|
||||||
|
2. Show integration examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
349
docs/filesystem_gap_analysis.md
Normal file
349
docs/filesystem_gap_analysis.md
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
# FileSystem Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our current filesystem handling (in Core package) and Laravel's FileSystem package functionality, identifying what needs to be implemented as a standalone FileSystem package.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [FileSystem Package Specification](filesystem_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
|
||||||
|
## Implementation Gaps
|
||||||
|
|
||||||
|
### 1. Missing Package Structure
|
||||||
|
```dart
|
||||||
|
// Need to create dedicated FileSystem package:
|
||||||
|
|
||||||
|
packages/filesystem/
|
||||||
|
├── lib/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── filesystem.dart
|
||||||
|
│ │ ├── filesystem_manager.dart
|
||||||
|
│ │ ├── drivers/
|
||||||
|
│ │ │ ├── local_driver.dart
|
||||||
|
│ │ │ ├── s3_driver.dart
|
||||||
|
│ │ │ └── gcs_driver.dart
|
||||||
|
│ │ └── contracts/
|
||||||
|
│ │ ├── filesystem.dart
|
||||||
|
│ │ └── driver.dart
|
||||||
|
│ └── filesystem.dart
|
||||||
|
├── test/
|
||||||
|
└── example/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Core Features
|
||||||
|
```dart
|
||||||
|
// 1. Filesystem Manager
|
||||||
|
class FilesystemManager {
|
||||||
|
// Need to implement:
|
||||||
|
Filesystem disk([String? name]);
|
||||||
|
void extend(String driver, FilesystemDriver Function() callback);
|
||||||
|
FilesystemDriver createDriver(Map<String, dynamic> config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Filesystem Implementation
|
||||||
|
class Filesystem {
|
||||||
|
// Need to implement:
|
||||||
|
Future<bool> exists(String path);
|
||||||
|
Future<String> get(String path);
|
||||||
|
Future<void> put(String path, dynamic contents, [Map<String, String>? options]);
|
||||||
|
Future<void> delete(String path);
|
||||||
|
Future<void> copy(String from, String to);
|
||||||
|
Future<void> move(String from, String to);
|
||||||
|
Future<String> url(String path);
|
||||||
|
Future<Stream<List<int>>> readStream(String path);
|
||||||
|
Future<void> writeStream(String path, Stream<List<int>> contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Driver Implementations
|
||||||
|
class LocalDriver {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> ensureDirectory(String path);
|
||||||
|
Future<void> setVisibility(String path, String visibility);
|
||||||
|
Future<Map<String, dynamic>> getMetadata(String path);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Laravel Features
|
||||||
|
```dart
|
||||||
|
// 1. Cloud Storage
|
||||||
|
class S3Driver {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> upload(String path, dynamic contents, String visibility);
|
||||||
|
Future<String> temporaryUrl(String path, Duration expiration);
|
||||||
|
Future<void> setVisibility(String path, String visibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Directory Operations
|
||||||
|
class DirectoryOperations {
|
||||||
|
// Need to implement:
|
||||||
|
Future<List<String>> files(String directory);
|
||||||
|
Future<List<String>> allFiles(String directory);
|
||||||
|
Future<List<String>> directories(String directory);
|
||||||
|
Future<List<String>> allDirectories(String directory);
|
||||||
|
Future<void> makeDirectory(String path);
|
||||||
|
Future<void> deleteDirectory(String directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. File Visibility
|
||||||
|
class VisibilityConverter {
|
||||||
|
// Need to implement:
|
||||||
|
String toOctal(String visibility);
|
||||||
|
String fromOctal(String permissions);
|
||||||
|
bool isPublic(String path);
|
||||||
|
bool isPrivate(String path);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Gaps
|
||||||
|
|
||||||
|
### 1. Container Integration
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
class FilesystemServiceProvider {
|
||||||
|
void register() {
|
||||||
|
// Register filesystem manager
|
||||||
|
container.singleton<FilesystemManager>((c) =>
|
||||||
|
FilesystemManager(
|
||||||
|
config: c.make<ConfigContract>()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register default filesystem
|
||||||
|
container.singleton<Filesystem>((c) =>
|
||||||
|
c.make<FilesystemManager>().disk()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Config Integration
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// config/filesystems.dart
|
||||||
|
class FilesystemsConfig {
|
||||||
|
static Map<String, dynamic> get config => {
|
||||||
|
'default': 'local',
|
||||||
|
'disks': {
|
||||||
|
'local': {
|
||||||
|
'driver': 'local',
|
||||||
|
'root': 'storage/app'
|
||||||
|
},
|
||||||
|
's3': {
|
||||||
|
'driver': 's3',
|
||||||
|
'key': env('AWS_ACCESS_KEY_ID'),
|
||||||
|
'secret': env('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
'region': env('AWS_DEFAULT_REGION'),
|
||||||
|
'bucket': env('AWS_BUCKET')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Event Integration
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
class FilesystemEvents {
|
||||||
|
// File events
|
||||||
|
static const String writing = 'filesystem.writing';
|
||||||
|
static const String written = 'filesystem.written';
|
||||||
|
static const String deleting = 'filesystem.deleting';
|
||||||
|
static const String deleted = 'filesystem.deleted';
|
||||||
|
|
||||||
|
// Directory events
|
||||||
|
static const String makingDirectory = 'filesystem.making_directory';
|
||||||
|
static const String madeDirectory = 'filesystem.made_directory';
|
||||||
|
static const String deletingDirectory = 'filesystem.deleting_directory';
|
||||||
|
static const String deletedDirectory = 'filesystem.deleted_directory';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Gaps
|
||||||
|
|
||||||
|
### 1. Missing API Documentation
|
||||||
|
```dart
|
||||||
|
// Need to document:
|
||||||
|
|
||||||
|
/// Manages filesystem operations across multiple storage drivers.
|
||||||
|
///
|
||||||
|
/// Provides a unified API for working with files across different storage systems:
|
||||||
|
/// ```dart
|
||||||
|
/// // Store a file
|
||||||
|
/// await storage.put('avatars/user1.jpg', fileContents);
|
||||||
|
///
|
||||||
|
/// // Get a file
|
||||||
|
/// var contents = await storage.get('avatars/user1.jpg');
|
||||||
|
/// ```
|
||||||
|
class Filesystem {
|
||||||
|
/// Stores a file at the specified path.
|
||||||
|
///
|
||||||
|
/// Options can include:
|
||||||
|
/// - visibility: 'public' or 'private'
|
||||||
|
/// - mime: MIME type of the file
|
||||||
|
Future<void> put(String path, dynamic contents, [Map<String, String>? options]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Usage Examples
|
||||||
|
```dart
|
||||||
|
// Need examples for:
|
||||||
|
|
||||||
|
// 1. Basic File Operations
|
||||||
|
var storage = Storage.disk();
|
||||||
|
await storage.put('file.txt', 'Hello World');
|
||||||
|
var contents = await storage.get('file.txt');
|
||||||
|
await storage.delete('file.txt');
|
||||||
|
|
||||||
|
// 2. Stream Operations
|
||||||
|
var fileStream = File('large.zip').openRead();
|
||||||
|
await storage.writeStream('uploads/large.zip', fileStream);
|
||||||
|
var downloadStream = await storage.readStream('uploads/large.zip');
|
||||||
|
|
||||||
|
// 3. Cloud Storage
|
||||||
|
var s3 = Storage.disk('s3');
|
||||||
|
await s3.put(
|
||||||
|
'images/photo.jpg',
|
||||||
|
photoBytes,
|
||||||
|
{'visibility': 'public'}
|
||||||
|
);
|
||||||
|
var url = await s3.url('images/photo.jpg');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Test Coverage
|
||||||
|
```dart
|
||||||
|
// Need tests for:
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Local Driver', () {
|
||||||
|
test('handles file operations', () async {
|
||||||
|
var storage = Filesystem(LocalDriver(root: 'storage'));
|
||||||
|
|
||||||
|
await storage.put('test.txt', 'contents');
|
||||||
|
expect(await storage.exists('test.txt'), isTrue);
|
||||||
|
expect(await storage.get('test.txt'), equals('contents'));
|
||||||
|
|
||||||
|
await storage.delete('test.txt');
|
||||||
|
expect(await storage.exists('test.txt'), isFalse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('S3 Driver', () {
|
||||||
|
test('handles cloud operations', () async {
|
||||||
|
var storage = Filesystem(S3Driver(config));
|
||||||
|
|
||||||
|
await storage.put('test.txt', 'contents', {
|
||||||
|
'visibility': 'public'
|
||||||
|
});
|
||||||
|
|
||||||
|
var url = await storage.url('test.txt');
|
||||||
|
expect(url, startsWith('https://'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **High Priority**
|
||||||
|
- Create FileSystem package structure
|
||||||
|
- Implement core filesystem
|
||||||
|
- Add local driver
|
||||||
|
- Add basic operations
|
||||||
|
|
||||||
|
2. **Medium Priority**
|
||||||
|
- Add cloud drivers
|
||||||
|
- Add streaming support
|
||||||
|
- Add directory operations
|
||||||
|
- Add container integration
|
||||||
|
|
||||||
|
3. **Low Priority**
|
||||||
|
- Add helper functions
|
||||||
|
- Add testing utilities
|
||||||
|
- Add debugging tools
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Package Creation**
|
||||||
|
- Create package structure
|
||||||
|
- Move filesystem code from Core
|
||||||
|
- Add package dependencies
|
||||||
|
- Setup testing
|
||||||
|
|
||||||
|
2. **Core Implementation**
|
||||||
|
- Implement FilesystemManager
|
||||||
|
- Implement Filesystem
|
||||||
|
- Implement LocalDriver
|
||||||
|
- Add cloud drivers
|
||||||
|
|
||||||
|
3. **Integration Implementation**
|
||||||
|
- Add container integration
|
||||||
|
- Add config support
|
||||||
|
- Add event support
|
||||||
|
- Add service providers
|
||||||
|
|
||||||
|
Would you like me to:
|
||||||
|
1. Create the FileSystem package structure?
|
||||||
|
2. Start implementing core features?
|
||||||
|
3. Create detailed implementation plans?
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing filesystem features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [FileSystem Package Specification](filesystem_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each filesystem feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in [FileSystem Package Specification](filesystem_package_specification.md)
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing filesystem features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Filesystem system must:
|
||||||
|
1. Handle large files efficiently
|
||||||
|
2. Use streaming where appropriate
|
||||||
|
3. Minimize memory usage
|
||||||
|
4. Support concurrent operations
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Filesystem tests must:
|
||||||
|
1. Cover all file operations
|
||||||
|
2. Test streaming behavior
|
||||||
|
3. Verify cloud storage
|
||||||
|
4. Check metadata handling
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Filesystem documentation must:
|
||||||
|
1. Explain filesystem patterns
|
||||||
|
2. Show driver examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
554
docs/filesystem_package_specification.md
Normal file
554
docs/filesystem_package_specification.md
Normal file
|
@ -0,0 +1,554 @@
|
||||||
|
# FileSystem Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The FileSystem package provides a robust abstraction layer for file operations, matching Laravel's filesystem functionality. It supports local and cloud storage systems through a unified API, with support for streaming, visibility control, and metadata management.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Contracts Package Specification](contracts_package_specification.md) for filesystem contracts
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Filesystem Manager
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Manages filesystem drivers
|
||||||
|
class FilesystemManager implements FilesystemFactory {
|
||||||
|
/// Available filesystem drivers
|
||||||
|
final Map<String, FilesystemDriver> _drivers = {};
|
||||||
|
|
||||||
|
/// Default driver name
|
||||||
|
final String _defaultDriver;
|
||||||
|
|
||||||
|
/// Configuration repository
|
||||||
|
final ConfigContract _config;
|
||||||
|
|
||||||
|
FilesystemManager(this._config)
|
||||||
|
: _defaultDriver = _config.get('filesystems.default', 'local');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Filesystem disk([String? name]) {
|
||||||
|
name ??= _defaultDriver;
|
||||||
|
|
||||||
|
return _drivers.putIfAbsent(name, () {
|
||||||
|
var config = _getConfig(name!);
|
||||||
|
var driver = _createDriver(config);
|
||||||
|
return Filesystem(driver);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a driver instance
|
||||||
|
FilesystemDriver _createDriver(Map<String, dynamic> config) {
|
||||||
|
switch (config['driver']) {
|
||||||
|
case 'local':
|
||||||
|
return LocalDriver(config);
|
||||||
|
case 's3':
|
||||||
|
return S3Driver(config);
|
||||||
|
case 'gcs':
|
||||||
|
return GoogleCloudDriver(config);
|
||||||
|
default:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'Unsupported filesystem driver: ${config['driver']}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets configuration for driver
|
||||||
|
Map<String, dynamic> _getConfig(String name) {
|
||||||
|
var config = _config.get<Map>('filesystems.disks.$name');
|
||||||
|
if (config == null) {
|
||||||
|
throw ArgumentError('Disk [$name] not configured.');
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Filesystem Implementation
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core filesystem implementation
|
||||||
|
class Filesystem implements FilesystemContract {
|
||||||
|
/// The filesystem driver
|
||||||
|
final FilesystemDriver _driver;
|
||||||
|
|
||||||
|
Filesystem(this._driver);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> exists(String path) {
|
||||||
|
return _driver.exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> get(String path) {
|
||||||
|
return _driver.get(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<int>> readStream(String path) {
|
||||||
|
return _driver.readStream(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> put(String path, dynamic contents, [Map<String, String>? options]) {
|
||||||
|
return _driver.put(path, contents, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> putStream(String path, Stream<List<int>> contents, [Map<String, String>? options]) {
|
||||||
|
return _driver.putStream(path, contents, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete(String path) {
|
||||||
|
return _driver.delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> copy(String from, String to) {
|
||||||
|
return _driver.copy(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> move(String from, String to) {
|
||||||
|
return _driver.move(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> url(String path) {
|
||||||
|
return _driver.url(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, String>> metadata(String path) {
|
||||||
|
return _driver.metadata(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> size(String path) {
|
||||||
|
return _driver.size(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> mimeType(String path) {
|
||||||
|
return _driver.mimeType(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<DateTime> lastModified(String path) {
|
||||||
|
return _driver.lastModified(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Local Driver
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Local filesystem driver
|
||||||
|
class LocalDriver implements FilesystemDriver {
|
||||||
|
/// Root path for local filesystem
|
||||||
|
final String _root;
|
||||||
|
|
||||||
|
/// Default visibility
|
||||||
|
final String _visibility;
|
||||||
|
|
||||||
|
LocalDriver(Map<String, dynamic> config)
|
||||||
|
: _root = config['root'],
|
||||||
|
_visibility = config['visibility'] ?? 'private';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> exists(String path) async {
|
||||||
|
return File(_fullPath(path)).exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> get(String path) async {
|
||||||
|
return File(_fullPath(path)).readAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<int>> readStream(String path) {
|
||||||
|
return File(_fullPath(path)).openRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> put(String path, dynamic contents, [Map<String, String>? options]) async {
|
||||||
|
var file = File(_fullPath(path));
|
||||||
|
await file.create(recursive: true);
|
||||||
|
|
||||||
|
if (contents is String) {
|
||||||
|
await file.writeAsString(contents);
|
||||||
|
} else if (contents is List<int>) {
|
||||||
|
await file.writeAsBytes(contents);
|
||||||
|
} else {
|
||||||
|
throw ArgumentError('Invalid content type');
|
||||||
|
}
|
||||||
|
|
||||||
|
await _setVisibility(file, options?['visibility'] ?? _visibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> putStream(String path, Stream<List<int>> contents, [Map<String, String>? options]) async {
|
||||||
|
var file = File(_fullPath(path));
|
||||||
|
await file.create(recursive: true);
|
||||||
|
|
||||||
|
var sink = file.openWrite();
|
||||||
|
await contents.pipe(sink);
|
||||||
|
await sink.close();
|
||||||
|
|
||||||
|
await _setVisibility(file, options?['visibility'] ?? _visibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets full path for file
|
||||||
|
String _fullPath(String path) {
|
||||||
|
return p.join(_root, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets file visibility
|
||||||
|
Future<void> _setVisibility(File file, String visibility) async {
|
||||||
|
// Set file permissions based on visibility
|
||||||
|
if (visibility == 'public') {
|
||||||
|
await file.setPermissions(
|
||||||
|
unix: 0644,
|
||||||
|
windows: FilePermissions.readWrite
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await file.setPermissions(
|
||||||
|
unix: 0600,
|
||||||
|
windows: FilePermissions.readWriteExecute
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Cloud Drivers
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Amazon S3 driver
|
||||||
|
class S3Driver implements FilesystemDriver {
|
||||||
|
/// S3 client
|
||||||
|
final S3Client _client;
|
||||||
|
|
||||||
|
/// Bucket name
|
||||||
|
final String _bucket;
|
||||||
|
|
||||||
|
/// Optional path prefix
|
||||||
|
final String? _prefix;
|
||||||
|
|
||||||
|
S3Driver(Map<String, dynamic> config)
|
||||||
|
: _client = S3Client(
|
||||||
|
region: config['region'],
|
||||||
|
credentials: AWSCredentials(
|
||||||
|
accessKey: config['key'],
|
||||||
|
secretKey: config['secret']
|
||||||
|
)
|
||||||
|
),
|
||||||
|
_bucket = config['bucket'],
|
||||||
|
_prefix = config['prefix'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> exists(String path) async {
|
||||||
|
try {
|
||||||
|
await _client.headObject(
|
||||||
|
bucket: _bucket,
|
||||||
|
key: _prefixPath(path)
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> put(String path, dynamic contents, [Map<String, String>? options]) async {
|
||||||
|
await _client.putObject(
|
||||||
|
bucket: _bucket,
|
||||||
|
key: _prefixPath(path),
|
||||||
|
body: contents,
|
||||||
|
acl: options?['visibility'] == 'public'
|
||||||
|
? 'public-read'
|
||||||
|
: 'private'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds prefix to path
|
||||||
|
String _prefixPath(String path) {
|
||||||
|
return _prefix != null ? '$_prefix/$path' : path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Google Cloud Storage driver
|
||||||
|
class GoogleCloudDriver implements FilesystemDriver {
|
||||||
|
/// Storage client
|
||||||
|
final Storage _storage;
|
||||||
|
|
||||||
|
/// Bucket name
|
||||||
|
final String _bucket;
|
||||||
|
|
||||||
|
GoogleCloudDriver(Map<String, dynamic> config)
|
||||||
|
: _storage = Storage(
|
||||||
|
projectId: config['project_id'],
|
||||||
|
credentials: config['credentials']
|
||||||
|
),
|
||||||
|
_bucket = config['bucket'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> exists(String path) async {
|
||||||
|
try {
|
||||||
|
await _storage.bucket(_bucket).file(path).exists();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> put(String path, dynamic contents, [Map<String, String>? options]) async {
|
||||||
|
var file = _storage.bucket(_bucket).file(path);
|
||||||
|
|
||||||
|
if (contents is String) {
|
||||||
|
await file.writeAsString(contents);
|
||||||
|
} else if (contents is List<int>) {
|
||||||
|
await file.writeAsBytes(contents);
|
||||||
|
} else {
|
||||||
|
throw ArgumentError('Invalid content type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?['visibility'] == 'public') {
|
||||||
|
await file.makePublic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with Container
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Registers filesystem services
|
||||||
|
class FilesystemServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
// Register filesystem factory
|
||||||
|
container.singleton<FilesystemFactory>((c) {
|
||||||
|
return FilesystemManager(c.make<ConfigContract>());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register default filesystem
|
||||||
|
container.singleton<FilesystemContract>((c) {
|
||||||
|
return c.make<FilesystemFactory>().disk();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Basic File Operations
|
||||||
|
```dart
|
||||||
|
// Get default disk
|
||||||
|
var storage = Storage.disk();
|
||||||
|
|
||||||
|
// Check if file exists
|
||||||
|
if (await storage.exists('file.txt')) {
|
||||||
|
// Read file contents
|
||||||
|
var contents = await storage.get('file.txt');
|
||||||
|
|
||||||
|
// Write file contents
|
||||||
|
await storage.put('new-file.txt', contents);
|
||||||
|
|
||||||
|
// Delete file
|
||||||
|
await storage.delete('file.txt');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stream Operations
|
||||||
|
```dart
|
||||||
|
// Read file as stream
|
||||||
|
var stream = storage.readStream('large-file.txt');
|
||||||
|
|
||||||
|
// Write stream to file
|
||||||
|
await storage.putStream(
|
||||||
|
'output.txt',
|
||||||
|
stream,
|
||||||
|
{'visibility': 'public'}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cloud Storage
|
||||||
|
```dart
|
||||||
|
// Use S3 disk
|
||||||
|
var s3 = Storage.disk('s3');
|
||||||
|
|
||||||
|
// Upload file
|
||||||
|
await s3.put(
|
||||||
|
'uploads/image.jpg',
|
||||||
|
imageBytes,
|
||||||
|
{'visibility': 'public'}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get public URL
|
||||||
|
var url = await s3.url('uploads/image.jpg');
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Metadata
|
||||||
|
```dart
|
||||||
|
// Get file metadata
|
||||||
|
var meta = await storage.metadata('document.pdf');
|
||||||
|
print('Size: ${meta['size']}');
|
||||||
|
print('Type: ${meta['mime_type']}');
|
||||||
|
print('Modified: ${meta['last_modified']}');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Filesystem Tests', () {
|
||||||
|
late Filesystem storage;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
storage = Filesystem(MockDriver());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should check file existence', () async {
|
||||||
|
expect(await storage.exists('test.txt'), isTrue);
|
||||||
|
expect(await storage.exists('missing.txt'), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should read and write files', () async {
|
||||||
|
await storage.put('test.txt', 'contents');
|
||||||
|
var contents = await storage.get('test.txt');
|
||||||
|
expect(contents, equals('contents'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle streams', () async {
|
||||||
|
var input = Stream.fromIterable([
|
||||||
|
[1, 2, 3],
|
||||||
|
[4, 5, 6]
|
||||||
|
]);
|
||||||
|
|
||||||
|
await storage.putStream('test.bin', input);
|
||||||
|
var output = storage.readStream('test.bin');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await output.toList(),
|
||||||
|
equals([[1, 2, 3], [4, 5, 6]])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
1. **Streaming Large Files**
|
||||||
|
```dart
|
||||||
|
// Use streams for large files
|
||||||
|
class Filesystem {
|
||||||
|
Future<void> copyLarge(String from, String to) async {
|
||||||
|
await readStream(from)
|
||||||
|
.pipe(writeStream(to));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Caching URLs**
|
||||||
|
```dart
|
||||||
|
class CachingFilesystem implements FilesystemContract {
|
||||||
|
final Cache _cache;
|
||||||
|
final Duration _ttl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> url(String path) async {
|
||||||
|
var key = 'file_url:$path';
|
||||||
|
return _cache.remember(key, _ttl, () {
|
||||||
|
return _driver.url(path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Batch Operations**
|
||||||
|
```dart
|
||||||
|
class Filesystem {
|
||||||
|
Future<void> putMany(Map<String, dynamic> files) async {
|
||||||
|
await Future.wait(
|
||||||
|
files.entries.map((e) =>
|
||||||
|
put(e.key, e.value)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement core filesystem
|
||||||
|
2. Add local driver
|
||||||
|
3. Add cloud drivers
|
||||||
|
4. Create manager
|
||||||
|
5. Write tests
|
||||||
|
6. Add benchmarks
|
||||||
|
|
||||||
|
Would you like me to focus on implementing any specific part of these packages or continue with other documentation?
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing filesystem features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Understand [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each filesystem feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Implement required contracts (see [Contracts Package Specification](contracts_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing filesystem features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
5. Implement all contracts from [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Filesystem system must:
|
||||||
|
1. Handle large files efficiently
|
||||||
|
2. Use streaming where appropriate
|
||||||
|
3. Minimize memory usage
|
||||||
|
4. Support concurrent operations
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Filesystem tests must:
|
||||||
|
1. Cover all file operations
|
||||||
|
2. Test streaming behavior
|
||||||
|
3. Verify cloud storage
|
||||||
|
4. Check metadata handling
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Filesystem documentation must:
|
||||||
|
1. Explain filesystem patterns
|
||||||
|
2. Show driver examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
316
docs/foundation_integration_guide.md
Normal file
316
docs/foundation_integration_guide.md
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
# Foundation Integration Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide demonstrates how Level 0 and Level 1 packages work together to provide the foundation for the framework. It includes implementation priorities, integration patterns, and best practices.
|
||||||
|
|
||||||
|
## Implementation Timeline
|
||||||
|
|
||||||
|
### Phase 1: Core Foundation (Level 0)
|
||||||
|
|
||||||
|
#### Week 1: Contracts Package
|
||||||
|
```dart
|
||||||
|
Priority: Highest
|
||||||
|
Dependencies: None
|
||||||
|
Steps:
|
||||||
|
1. Define core interfaces
|
||||||
|
2. Create base exceptions
|
||||||
|
3. Add documentation
|
||||||
|
4. Write interface tests
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Week 2: Support Package
|
||||||
|
```dart
|
||||||
|
Priority: Highest
|
||||||
|
Dependencies: Contracts
|
||||||
|
Steps:
|
||||||
|
1. Implement collections
|
||||||
|
2. Add string helpers
|
||||||
|
3. Create service provider base
|
||||||
|
4. Add utility functions
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Weeks 3-4: Container Package
|
||||||
|
```dart
|
||||||
|
Priority: Highest
|
||||||
|
Dependencies: Contracts, Support
|
||||||
|
Steps:
|
||||||
|
1. Implement core container
|
||||||
|
2. Add contextual binding
|
||||||
|
3. Add method injection
|
||||||
|
4. Add tagged bindings
|
||||||
|
5. Implement caching
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Week 5: Pipeline Package
|
||||||
|
```dart
|
||||||
|
Priority: High
|
||||||
|
Dependencies: Contracts, Support, Container
|
||||||
|
Steps:
|
||||||
|
1. Implement core pipeline
|
||||||
|
2. Add pipeline hub
|
||||||
|
3. Create middleware support
|
||||||
|
4. Add async handling
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Infrastructure (Level 1)
|
||||||
|
|
||||||
|
#### Weeks 6-7: Events Package
|
||||||
|
```dart
|
||||||
|
Priority: High
|
||||||
|
Dependencies: All Level 0
|
||||||
|
Steps:
|
||||||
|
1. Implement event dispatcher
|
||||||
|
2. Add event discovery
|
||||||
|
3. Create subscriber support
|
||||||
|
4. Add queueing integration
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Week 8: Config Package
|
||||||
|
```dart
|
||||||
|
Priority: High
|
||||||
|
Dependencies: All Level 0
|
||||||
|
Steps:
|
||||||
|
1. Implement config repository
|
||||||
|
2. Add environment loading
|
||||||
|
3. Create config caching
|
||||||
|
4. Add array casting
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Weeks 9-10: FileSystem Package
|
||||||
|
```dart
|
||||||
|
Priority: High
|
||||||
|
Dependencies: All Level 0
|
||||||
|
Steps:
|
||||||
|
1. Implement filesystem manager
|
||||||
|
2. Create local driver
|
||||||
|
3. Add cloud drivers
|
||||||
|
4. Implement streaming
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
### 1. Service Provider Integration
|
||||||
|
```dart
|
||||||
|
/// Example showing how packages integrate through service providers
|
||||||
|
void main() {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
// Register foundation services
|
||||||
|
container.register(SupportServiceProvider());
|
||||||
|
container.register(PipelineServiceProvider());
|
||||||
|
container.register(EventServiceProvider());
|
||||||
|
container.register(ConfigServiceProvider());
|
||||||
|
container.register(FilesystemServiceProvider());
|
||||||
|
|
||||||
|
// Boot application
|
||||||
|
await container.bootProviders();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Event-Driven File Operations
|
||||||
|
```dart
|
||||||
|
/// Example showing Events and FileSystem integration
|
||||||
|
class FileUploadHandler {
|
||||||
|
final EventDispatcherContract _events;
|
||||||
|
final FilesystemContract _storage;
|
||||||
|
|
||||||
|
Future<void> handleUpload(Upload upload) async {
|
||||||
|
// Store file using FileSystem
|
||||||
|
await _storage.put(
|
||||||
|
'uploads/${upload.filename}',
|
||||||
|
upload.contents,
|
||||||
|
{'visibility': 'public'}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dispatch event using Events
|
||||||
|
await _events.dispatch(FileUploaded(
|
||||||
|
filename: upload.filename,
|
||||||
|
size: upload.size,
|
||||||
|
url: await _storage.url('uploads/${upload.filename}')
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Configuration-Based Pipeline
|
||||||
|
```dart
|
||||||
|
/// Example showing Config and Pipeline integration
|
||||||
|
class RequestHandler {
|
||||||
|
final ConfigContract _config;
|
||||||
|
final Pipeline<Request> _pipeline;
|
||||||
|
|
||||||
|
Future<Response> handle(Request request) async {
|
||||||
|
// Get middleware from config
|
||||||
|
var middleware = _config.get<List>('http.middleware', [])
|
||||||
|
.map((m) => container.make<Middleware>(m))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Process request through pipeline
|
||||||
|
return _pipeline
|
||||||
|
.through(middleware)
|
||||||
|
.send(request)
|
||||||
|
.then((request) => processRequest(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Integration Patterns
|
||||||
|
|
||||||
|
### 1. Service Provider Pattern
|
||||||
|
```dart
|
||||||
|
abstract class ServiceProvider {
|
||||||
|
void register() {
|
||||||
|
container.singleton<Service>((c) =>
|
||||||
|
ServiceImpl(
|
||||||
|
c.make<EventDispatcherContract>(),
|
||||||
|
c.make<ConfigContract>(),
|
||||||
|
c.make<FilesystemContract>()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Event-Driven Pattern
|
||||||
|
```dart
|
||||||
|
class EventDrivenService {
|
||||||
|
final EventDispatcherContract events;
|
||||||
|
|
||||||
|
void initialize() {
|
||||||
|
events.listen<ConfigurationChanged>(_handleConfigChange);
|
||||||
|
events.listen<StorageEvent>(_handleStorageEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Pipeline Pattern
|
||||||
|
```dart
|
||||||
|
class ServicePipeline {
|
||||||
|
final Pipeline<Request> pipeline;
|
||||||
|
|
||||||
|
ServicePipeline(this.pipeline) {
|
||||||
|
pipeline.through([
|
||||||
|
ConfigMiddleware(container.make<ConfigContract>()),
|
||||||
|
EventMiddleware(container.make<EventDispatcherContract>()),
|
||||||
|
StorageMiddleware(container.make<FilesystemContract>())
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### 1. Unit Tests
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Package Tests', () {
|
||||||
|
test('core functionality', () {
|
||||||
|
// Test core features
|
||||||
|
});
|
||||||
|
|
||||||
|
test('integration points', () {
|
||||||
|
// Test integration with other packages
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Integration Tests
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Integration Tests', () {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container();
|
||||||
|
container.register(SupportServiceProvider());
|
||||||
|
container.register(EventServiceProvider());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle file upload with events', () async {
|
||||||
|
var handler = container.make<FileUploadHandler>();
|
||||||
|
var events = container.make<EventDispatcherContract>();
|
||||||
|
|
||||||
|
var received = <FileUploaded>[];
|
||||||
|
events.listen<FileUploaded>((event) {
|
||||||
|
received.add(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
await handler.handleUpload(testUpload);
|
||||||
|
expect(received, hasLength(1));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quality Checklist
|
||||||
|
|
||||||
|
### 1. Code Quality
|
||||||
|
- [ ] Follows style guide
|
||||||
|
- [ ] Uses static analysis
|
||||||
|
- [ ] Has documentation
|
||||||
|
- [ ] Has tests
|
||||||
|
- [ ] Handles errors
|
||||||
|
|
||||||
|
### 2. Package Quality
|
||||||
|
- [ ] Has README
|
||||||
|
- [ ] Has examples
|
||||||
|
- [ ] Has changelog
|
||||||
|
- [ ] Has license
|
||||||
|
- [ ] Has CI/CD
|
||||||
|
|
||||||
|
### 3. Integration Quality
|
||||||
|
- [ ] Works with container
|
||||||
|
- [ ] Supports events
|
||||||
|
- [ ] Uses configuration
|
||||||
|
- [ ] Has providers
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Use Service Providers**
|
||||||
|
```dart
|
||||||
|
// Register dependencies in providers
|
||||||
|
class ServiceProvider {
|
||||||
|
void register() {
|
||||||
|
// Register all required services
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Event-Driven Communication**
|
||||||
|
```dart
|
||||||
|
// Use events for cross-package communication
|
||||||
|
class Service {
|
||||||
|
final EventDispatcherContract _events;
|
||||||
|
|
||||||
|
Future<void> doSomething() async {
|
||||||
|
await _events.dispatch(SomethingHappened());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Configuration-Based Setup**
|
||||||
|
```dart
|
||||||
|
// Use configuration for service setup
|
||||||
|
class Service {
|
||||||
|
void initialize(ConfigContract config) {
|
||||||
|
if (config.get('service.enabled')) {
|
||||||
|
// Initialize service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Follow implementation timeline
|
||||||
|
2. Review package dependencies
|
||||||
|
3. Implement integration tests
|
||||||
|
4. Document common patterns
|
||||||
|
5. Create example applications
|
||||||
|
|
||||||
|
Would you like me to:
|
||||||
|
1. Start implementing a specific package?
|
||||||
|
2. Create detailed integration tests?
|
||||||
|
3. Build example applications?
|
142
docs/getting_started.md
Normal file
142
docs/getting_started.md
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
# Getting Started Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide helps developers get started with implementing and contributing to the framework's foundation packages. It provides step-by-step instructions for setting up the development environment, understanding the codebase, and making contributions.
|
||||||
|
|
||||||
|
## Key Documentation
|
||||||
|
|
||||||
|
Before starting, familiarize yourself with our core documentation:
|
||||||
|
|
||||||
|
1. **Architecture & Implementation**
|
||||||
|
- [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) - Overall implementation status and plans
|
||||||
|
- [Foundation Integration Guide](foundation_integration_guide.md) - How packages work together
|
||||||
|
- [Testing Guide](testing_guide.md) - Testing approaches and standards
|
||||||
|
|
||||||
|
2. **Package Documentation**
|
||||||
|
- [Container Package](container_package_specification.md) - Dependency injection system
|
||||||
|
- [Container Gap Analysis](container_gap_analysis.md) - Implementation status and plans
|
||||||
|
- More package docs coming soon...
|
||||||
|
|
||||||
|
3. **Development Setup**
|
||||||
|
- [Melos Configuration](melos_config.md) - Build and development tools
|
||||||
|
|
||||||
|
[Previous content remains the same until Project Structure section, then update with:]
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### 1. Package Organization
|
||||||
|
```
|
||||||
|
platform/
|
||||||
|
├── packages/
|
||||||
|
│ ├── container/ # Dependency injection
|
||||||
|
│ │ ├── container/ # Core container
|
||||||
|
│ │ └── container_generator/ # Code generation
|
||||||
|
│ ├── core/ # Framework core
|
||||||
|
│ ├── events/ # Event system
|
||||||
|
│ ├── model/ # Model system
|
||||||
|
│ ├── pipeline/ # Pipeline pattern
|
||||||
|
│ ├── process/ # Process management
|
||||||
|
│ ├── queue/ # Queue system
|
||||||
|
│ ├── route/ # Routing system
|
||||||
|
│ ├── support/ # Utilities
|
||||||
|
│ └── testing/ # Testing utilities
|
||||||
|
├── apps/ # Example applications
|
||||||
|
├── config/ # Configuration files
|
||||||
|
├── docs/ # Documentation
|
||||||
|
├── examples/ # Usage examples
|
||||||
|
├── resources/ # Additional resources
|
||||||
|
├── scripts/ # Development scripts
|
||||||
|
├── templates/ # Project templates
|
||||||
|
└── tests/ # Integration tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Package Structure
|
||||||
|
```
|
||||||
|
package/
|
||||||
|
├── lib/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── core/ # Core implementation
|
||||||
|
│ │ ├── contracts/ # Package interfaces
|
||||||
|
│ │ └── support/ # Package utilities
|
||||||
|
│ └── package.dart # Public API
|
||||||
|
├── test/
|
||||||
|
│ ├── unit/ # Unit tests
|
||||||
|
│ ├── integration/ # Integration tests
|
||||||
|
│ └── performance/ # Performance tests
|
||||||
|
├── example/ # Usage examples
|
||||||
|
└── README.md # Package documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
[Previous content remains the same until Implementation Guidelines section, then update with:]
|
||||||
|
|
||||||
|
## Implementation Guidelines
|
||||||
|
|
||||||
|
### 1. Laravel Compatibility
|
||||||
|
```dart
|
||||||
|
// Follow Laravel patterns where possible
|
||||||
|
class ServiceProvider {
|
||||||
|
void register() {
|
||||||
|
// Register services like Laravel
|
||||||
|
container.singleton<Service>((c) => ServiceImpl());
|
||||||
|
|
||||||
|
// Use contextual binding
|
||||||
|
container.when(PhotoController)
|
||||||
|
.needs<Storage>()
|
||||||
|
.give(LocalStorage());
|
||||||
|
|
||||||
|
// Use tagged bindings
|
||||||
|
container.tag([
|
||||||
|
EmailNotifier,
|
||||||
|
SmsNotifier
|
||||||
|
], 'notifications');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Testing Approach
|
||||||
|
```dart
|
||||||
|
// Follow Laravel testing patterns
|
||||||
|
void main() {
|
||||||
|
group('Feature Tests', () {
|
||||||
|
late TestCase test;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
test = await TestCase.make();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user can register', () async {
|
||||||
|
await test
|
||||||
|
.post('/register', {
|
||||||
|
'name': 'John Doe',
|
||||||
|
'email': 'john@example.com',
|
||||||
|
'password': 'password'
|
||||||
|
})
|
||||||
|
.assertStatus(302)
|
||||||
|
.assertRedirect('/home');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[Previous content remains the same until Getting Help section, then update with:]
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
1. **Documentation**
|
||||||
|
- Start with [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
- Review [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
- Check [Testing Guide](testing_guide.md)
|
||||||
|
- Read package-specific documentation
|
||||||
|
|
||||||
|
2. **Development Setup**
|
||||||
|
- Follow [Melos Configuration](melos_config.md)
|
||||||
|
- Setup development environment
|
||||||
|
- Run example applications
|
||||||
|
|
||||||
|
3. **Resources**
|
||||||
|
- [Laravel Documentation](https://laravel.com/docs)
|
||||||
|
- [Dart Documentation](https://dart.dev/guides)
|
||||||
|
- [Package Layout](https://dart.dev/tools/pub/package-layout)
|
||||||
|
|
||||||
|
[Rest of the file remains the same]
|
234
docs/index.md
Normal file
234
docs/index.md
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
# Framework Documentation
|
||||||
|
|
||||||
|
## Core Documentation
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
1. [Getting Started Guide](getting_started.md)
|
||||||
|
2. [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
4. [Testing Guide](testing_guide.md)
|
||||||
|
5. [Package Integration Map](package_integration_map.md)
|
||||||
|
|
||||||
|
### Core Architecture
|
||||||
|
1. [Core Architecture](core_architecture.md)
|
||||||
|
- System design
|
||||||
|
- Architectural patterns
|
||||||
|
- Extension points
|
||||||
|
- Package integration
|
||||||
|
|
||||||
|
## Package Documentation
|
||||||
|
|
||||||
|
### Core Framework
|
||||||
|
1. Core Package
|
||||||
|
- [Core Package Specification](core_package_specification.md)
|
||||||
|
- [Core Architecture](core_architecture.md)
|
||||||
|
|
||||||
|
2. Container Package
|
||||||
|
- [Container Package Specification](container_package_specification.md)
|
||||||
|
- [Container Gap Analysis](container_gap_analysis.md)
|
||||||
|
- [Container Feature Integration](container_feature_integration.md)
|
||||||
|
- [Container Migration Guide](container_migration_guide.md)
|
||||||
|
|
||||||
|
3. Contracts Package
|
||||||
|
- [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
4. Events Package
|
||||||
|
- [Events Package Specification](events_package_specification.md)
|
||||||
|
- [Events Gap Analysis](events_gap_analysis.md)
|
||||||
|
|
||||||
|
5. Pipeline Package
|
||||||
|
- [Pipeline Package Specification](pipeline_package_specification.md)
|
||||||
|
- [Pipeline Gap Analysis](pipeline_gap_analysis.md)
|
||||||
|
|
||||||
|
6. Support Package
|
||||||
|
- [Support Package Specification](support_package_specification.md)
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
1. Bus Package
|
||||||
|
- [Bus Package Specification](bus_package_specification.md)
|
||||||
|
- [Bus Gap Analysis](bus_gap_analysis.md)
|
||||||
|
|
||||||
|
2. Config Package
|
||||||
|
- [Config Package Specification](config_package_specification.md)
|
||||||
|
- [Config Gap Analysis](config_gap_analysis.md)
|
||||||
|
|
||||||
|
3. Filesystem Package
|
||||||
|
- [Filesystem Package Specification](filesystem_package_specification.md)
|
||||||
|
- [Filesystem Gap Analysis](filesystem_gap_analysis.md)
|
||||||
|
|
||||||
|
4. Model Package
|
||||||
|
- [Model Package Specification](model_package_specification.md)
|
||||||
|
- [Model Gap Analysis](model_gap_analysis.md)
|
||||||
|
|
||||||
|
5. Process Package
|
||||||
|
- [Process Package Specification](process_package_specification.md)
|
||||||
|
- [Process Gap Analysis](process_gap_analysis.md)
|
||||||
|
|
||||||
|
6. Queue Package
|
||||||
|
- [Queue Package Specification](queue_package_specification.md)
|
||||||
|
- [Queue Gap Analysis](queue_gap_analysis.md)
|
||||||
|
|
||||||
|
7. Route Package
|
||||||
|
- [Route Package Specification](route_package_specification.md)
|
||||||
|
- [Route Gap Analysis](route_gap_analysis.md)
|
||||||
|
|
||||||
|
8. Testing Package
|
||||||
|
- [Testing Package Specification](testing_package_specification.md)
|
||||||
|
- [Testing Gap Analysis](testing_gap_analysis.md)
|
||||||
|
|
||||||
|
## Package Dependencies
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Core[Core] --> Container[Container]
|
||||||
|
Core --> Events[Events]
|
||||||
|
Core --> Pipeline[Pipeline]
|
||||||
|
|
||||||
|
Container --> Contracts[Contracts]
|
||||||
|
Events --> Container
|
||||||
|
Pipeline --> Container
|
||||||
|
|
||||||
|
Bus[Bus] --> Events
|
||||||
|
Bus --> Queue[Queue]
|
||||||
|
|
||||||
|
Config[Config] --> Container
|
||||||
|
|
||||||
|
Filesystem[Filesystem] --> Container
|
||||||
|
|
||||||
|
Model[Model] --> Events
|
||||||
|
Model --> Container
|
||||||
|
|
||||||
|
Process[Process] --> Events
|
||||||
|
Process --> Queue
|
||||||
|
|
||||||
|
Queue --> Events
|
||||||
|
Queue --> Container
|
||||||
|
|
||||||
|
Route[Route] --> Pipeline
|
||||||
|
Route --> Container
|
||||||
|
|
||||||
|
Testing[Testing] --> Container
|
||||||
|
Testing --> Events
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Status
|
||||||
|
|
||||||
|
### Core Framework (90%)
|
||||||
|
- Core Package (95%)
|
||||||
|
* Application lifecycle ✓
|
||||||
|
* Service providers ✓
|
||||||
|
* HTTP kernel ✓
|
||||||
|
* Console kernel ✓
|
||||||
|
* Exception handling ✓
|
||||||
|
* Needs: Performance optimizations
|
||||||
|
|
||||||
|
- Container Package (90%)
|
||||||
|
* Basic DI ✓
|
||||||
|
* Auto-wiring ✓
|
||||||
|
* Service providers ✓
|
||||||
|
* Needs: Contextual binding
|
||||||
|
|
||||||
|
- Events Package (85%)
|
||||||
|
* Event dispatching ✓
|
||||||
|
* Event subscribers ✓
|
||||||
|
* Event broadcasting ✓
|
||||||
|
* Needs: Event discovery
|
||||||
|
|
||||||
|
### Infrastructure (80%)
|
||||||
|
- Bus Package (85%)
|
||||||
|
* Command dispatching ✓
|
||||||
|
* Command queuing ✓
|
||||||
|
* Needs: Command batching
|
||||||
|
|
||||||
|
- Config Package (80%)
|
||||||
|
* Configuration repository ✓
|
||||||
|
* Environment loading ✓
|
||||||
|
* Needs: Config caching
|
||||||
|
|
||||||
|
- Filesystem Package (75%)
|
||||||
|
* Local driver ✓
|
||||||
|
* Cloud storage ✓
|
||||||
|
* Needs: Streaming support
|
||||||
|
|
||||||
|
- Model Package (80%)
|
||||||
|
* Basic ORM ✓
|
||||||
|
* Relationships ✓
|
||||||
|
* Needs: Model events
|
||||||
|
|
||||||
|
- Process Package (85%)
|
||||||
|
* Process management ✓
|
||||||
|
* Process pools ✓
|
||||||
|
* Needs: Process monitoring
|
||||||
|
|
||||||
|
- Queue Package (85%)
|
||||||
|
* Queue workers ✓
|
||||||
|
* Job batching ✓
|
||||||
|
* Needs: Rate limiting
|
||||||
|
|
||||||
|
- Route Package (90%)
|
||||||
|
* Route registration ✓
|
||||||
|
* Route matching ✓
|
||||||
|
* Middleware ✓
|
||||||
|
* Needs: Route caching
|
||||||
|
|
||||||
|
- Testing Package (85%)
|
||||||
|
* HTTP testing ✓
|
||||||
|
* Database testing ✓
|
||||||
|
* Needs: Browser testing
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
1. **Starting Development**
|
||||||
|
```bash
|
||||||
|
# Clone repository
|
||||||
|
git clone https://github.com/organization/framework.git
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
dart pub get
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
dart test
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Development Process**
|
||||||
|
- Write tests first
|
||||||
|
- Implement features
|
||||||
|
- Update documentation
|
||||||
|
- Submit PR
|
||||||
|
|
||||||
|
3. **Quality Checks**
|
||||||
|
- Run tests
|
||||||
|
- Check code style
|
||||||
|
- Verify documentation
|
||||||
|
- Review performance
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](../CONTRIBUTING.md) for detailed contribution guidelines.
|
||||||
|
|
||||||
|
### Quick Start
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Read relevant package documentation
|
||||||
|
4. Follow [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- [Laravel Documentation](https://laravel.com/docs)
|
||||||
|
- [Dart Documentation](https://dart.dev/guides)
|
||||||
|
- [Package Layout](https://dart.dev/tools/pub/package-layout)
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
- [Dart SDK](https://dart.dev/get-dart)
|
||||||
|
- [VS Code](https://code.visualstudio.com)
|
||||||
|
- [Git](https://git-scm.com)
|
||||||
|
|
||||||
|
### Community
|
||||||
|
- GitHub Issues
|
||||||
|
- Discussion Forum
|
||||||
|
- Team Chat
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This framework is open-sourced software licensed under the [MIT license](../LICENSE).
|
251
docs/laravel_compatibility_roadmap.md
Normal file
251
docs/laravel_compatibility_roadmap.md
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
# Laravel Compatibility Roadmap
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document outlines our path to Laravel API compatibility while maintaining backward compatibility with existing code. It provides a comprehensive view of package dependencies, implementation status, and migration strategy.
|
||||||
|
|
||||||
|
## Package Dependency Hierarchy
|
||||||
|
|
||||||
|
### Level 0: Core Foundation
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Container[Container] --> Contracts[Contracts]
|
||||||
|
Support[Support] --> Container
|
||||||
|
Pipeline[Pipeline] --> Container
|
||||||
|
```
|
||||||
|
|
||||||
|
Core Dependencies:
|
||||||
|
- Container: Service container, dependency injection
|
||||||
|
- Contracts: Interfaces and contracts
|
||||||
|
- Support: Helper functions, utilities
|
||||||
|
- Pipeline: Pipeline pattern implementation
|
||||||
|
|
||||||
|
### Level 1: Infrastructure
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Events[Events] --> Container
|
||||||
|
Events --> Support
|
||||||
|
Config[Config] --> Container
|
||||||
|
Config --> Support
|
||||||
|
FileSystem[FileSystem] --> Support
|
||||||
|
FileSystem --> Container
|
||||||
|
```
|
||||||
|
|
||||||
|
Infrastructure Dependencies:
|
||||||
|
- Events: Event dispatching system
|
||||||
|
- Config: Configuration management
|
||||||
|
- FileSystem: File system abstraction
|
||||||
|
|
||||||
|
### Level 2: Core Services
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Cache[Cache] --> Events
|
||||||
|
Cache --> Container
|
||||||
|
Database[Database] --> Events
|
||||||
|
Database --> Container
|
||||||
|
Queue[Queue] --> Events
|
||||||
|
Queue --> Container
|
||||||
|
Queue --> Pipeline
|
||||||
|
```
|
||||||
|
|
||||||
|
Core Service Dependencies:
|
||||||
|
- Cache: Caching system
|
||||||
|
- Database: Database abstraction
|
||||||
|
- Queue: Queue system and job processing
|
||||||
|
|
||||||
|
### Level 3: HTTP Layer
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Routing[Routing] --> Pipeline
|
||||||
|
Routing --> Container
|
||||||
|
Http[Http] --> Pipeline
|
||||||
|
Http --> Events
|
||||||
|
Session[Session] --> Cache
|
||||||
|
Session --> Events
|
||||||
|
```
|
||||||
|
|
||||||
|
HTTP Layer Dependencies:
|
||||||
|
- Routing: Route registration and matching
|
||||||
|
- Http: HTTP request/response handling
|
||||||
|
- Session: Session management
|
||||||
|
|
||||||
|
## Current Implementation Status
|
||||||
|
|
||||||
|
[Previous implementation status section remains the same]
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
### 1. API Compatibility
|
||||||
|
```yaml
|
||||||
|
Required:
|
||||||
|
- 100% Laravel interface implementation
|
||||||
|
- All Laravel patterns supported
|
||||||
|
- Full feature parity
|
||||||
|
- Backward compatibility maintained
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Performance
|
||||||
|
```yaml
|
||||||
|
Targets:
|
||||||
|
- Resolution: < 0.1ms per operation
|
||||||
|
- Memory: < 10MB overhead
|
||||||
|
- Cache hit rate: > 90%
|
||||||
|
- Startup time: < 100ms
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Code Quality
|
||||||
|
```yaml
|
||||||
|
Requirements:
|
||||||
|
- 100% test coverage
|
||||||
|
- Static analysis passing
|
||||||
|
- Documentation complete
|
||||||
|
- Examples provided
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Integration
|
||||||
|
```yaml
|
||||||
|
Verification:
|
||||||
|
- Cross-package tests passing
|
||||||
|
- Performance benchmarks met
|
||||||
|
- Real-world examples working
|
||||||
|
- Migration guides verified
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
### 1. Backward Compatibility
|
||||||
|
```dart
|
||||||
|
// Maintain existing APIs
|
||||||
|
class Container {
|
||||||
|
// Existing methods stay the same
|
||||||
|
T make<T>();
|
||||||
|
void bind<T>(T instance);
|
||||||
|
|
||||||
|
// New methods add functionality
|
||||||
|
ContextualBindingBuilder when(Type concrete);
|
||||||
|
void tag(List<Type> types, String tag);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Laravel Compatibility
|
||||||
|
```dart
|
||||||
|
// Match Laravel's patterns
|
||||||
|
container.when(UserController)
|
||||||
|
.needs<Service>()
|
||||||
|
.give((c) => SpecialService());
|
||||||
|
|
||||||
|
container.tag([ServiceA, ServiceB], 'services');
|
||||||
|
|
||||||
|
container.call(instance, 'method', parameters);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Performance Focus
|
||||||
|
```dart
|
||||||
|
// Add caching
|
||||||
|
class Container {
|
||||||
|
final ResolutionCache _cache;
|
||||||
|
final ReflectionCache _reflectionCache;
|
||||||
|
|
||||||
|
T make<T>([dynamic context]) {
|
||||||
|
return _cache.get<T>(context) ?? _resolve<T>(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
[Previous implementation strategy section remains the same]
|
||||||
|
|
||||||
|
## Integration Considerations
|
||||||
|
|
||||||
|
### 1. Service Provider Pattern
|
||||||
|
- Registration phase
|
||||||
|
- Boot phase
|
||||||
|
- Deferred providers
|
||||||
|
|
||||||
|
### 2. Event System
|
||||||
|
- Synchronous events
|
||||||
|
- Queued events
|
||||||
|
- Event subscribers
|
||||||
|
|
||||||
|
### 3. Queue System
|
||||||
|
- Multiple drivers
|
||||||
|
- Job handling
|
||||||
|
- Failed jobs
|
||||||
|
|
||||||
|
### 4. Database Layer
|
||||||
|
- Query builder
|
||||||
|
- Schema builder
|
||||||
|
- Migrations
|
||||||
|
|
||||||
|
### 5. HTTP Layer
|
||||||
|
- Middleware
|
||||||
|
- Controllers
|
||||||
|
- Resources
|
||||||
|
|
||||||
|
### 6. Authentication
|
||||||
|
- Guards
|
||||||
|
- Providers
|
||||||
|
- Policies
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### 1. Development Environment
|
||||||
|
```bash
|
||||||
|
# Clone repository
|
||||||
|
git clone https://github.com/org/platform.git
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
dart pub get
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
dart test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Package Development
|
||||||
|
```yaml
|
||||||
|
1. Choose package level:
|
||||||
|
- Level 0: Foundation packages
|
||||||
|
- Level 1: Infrastructure packages
|
||||||
|
- Level 2: Core services
|
||||||
|
- Level 3: HTTP layer
|
||||||
|
|
||||||
|
2. Review dependencies:
|
||||||
|
- Check required packages
|
||||||
|
- Verify integration points
|
||||||
|
- Plan implementation
|
||||||
|
|
||||||
|
3. Follow implementation order:
|
||||||
|
- Core functionality
|
||||||
|
- Laravel compatibility
|
||||||
|
- Tests and documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Quality Assurance
|
||||||
|
```yaml
|
||||||
|
1. Testing:
|
||||||
|
- Unit tests
|
||||||
|
- Integration tests
|
||||||
|
- Performance tests
|
||||||
|
- Compatibility tests
|
||||||
|
|
||||||
|
2. Documentation:
|
||||||
|
- API documentation
|
||||||
|
- Usage examples
|
||||||
|
- Integration guides
|
||||||
|
- Migration guides
|
||||||
|
|
||||||
|
3. Performance:
|
||||||
|
- Benchmarking
|
||||||
|
- Profiling
|
||||||
|
- Optimization
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
[Previous next steps section remains the same]
|
||||||
|
|
||||||
|
Would you like me to:
|
||||||
|
1. Create detailed plans for package creation?
|
||||||
|
2. Start implementing specific features?
|
||||||
|
3. Create test plans for new functionality?
|
316
docs/model_gap_analysis.md
Normal file
316
docs/model_gap_analysis.md
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
# Model Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our Model package's actual implementation and Laravel's Eloquent functionality, identifying areas that need implementation or documentation updates.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Model Package Specification](model_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
|
||||||
|
## Implementation Gaps
|
||||||
|
|
||||||
|
### 1. Missing Laravel Features
|
||||||
|
```dart
|
||||||
|
// Documented but not implemented:
|
||||||
|
|
||||||
|
// 1. Model Scopes
|
||||||
|
class ModelScope {
|
||||||
|
// Need to implement:
|
||||||
|
Query<T> apply<T extends Model>(Query<T> query);
|
||||||
|
bool shouldApply<T extends Model>(Query<T> query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Model Observers
|
||||||
|
class ModelObserver<T extends Model> {
|
||||||
|
// Need to implement:
|
||||||
|
void creating(T model);
|
||||||
|
void created(T model);
|
||||||
|
void updating(T model);
|
||||||
|
void updated(T model);
|
||||||
|
void deleting(T model);
|
||||||
|
void deleted(T model);
|
||||||
|
void restored(T model);
|
||||||
|
void forceDeleted(T model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Model Factories
|
||||||
|
class ModelFactory<T extends Model> {
|
||||||
|
// Need to implement:
|
||||||
|
T definition();
|
||||||
|
T make([Map<String, dynamic>? attributes]);
|
||||||
|
Future<T> create([Map<String, dynamic>? attributes]);
|
||||||
|
List<T> makeMany(int count, [Map<String, dynamic>? attributes]);
|
||||||
|
Future<List<T>> createMany(int count, [Map<String, dynamic>? attributes]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Relationship Types
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// 1. Many to Many
|
||||||
|
class BelongsToMany<T extends Model> extends Relationship<T> {
|
||||||
|
// Need to implement:
|
||||||
|
String get table;
|
||||||
|
String get foreignPivotKey;
|
||||||
|
String get relatedPivotKey;
|
||||||
|
List<String> get pivotColumns;
|
||||||
|
|
||||||
|
Future<List<T>> get();
|
||||||
|
Future<void> attach(List<dynamic> ids, [Map<String, dynamic>? attributes]);
|
||||||
|
Future<void> detach(List<dynamic>? ids);
|
||||||
|
Future<void> sync(List<dynamic> ids);
|
||||||
|
Future<void> toggle(List<dynamic> ids);
|
||||||
|
Future<void> updateExistingPivot(dynamic id, Map<String, dynamic> attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Has Many Through
|
||||||
|
class HasManyThrough<T extends Model> extends Relationship<T> {
|
||||||
|
// Need to implement:
|
||||||
|
String get through;
|
||||||
|
String get firstKey;
|
||||||
|
String get secondKey;
|
||||||
|
String get localKey;
|
||||||
|
String get secondLocalKey;
|
||||||
|
|
||||||
|
Future<List<T>> get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Polymorphic Relations
|
||||||
|
class MorphTo extends Relationship<Model> {
|
||||||
|
// Need to implement:
|
||||||
|
String get morphType;
|
||||||
|
String get morphId;
|
||||||
|
|
||||||
|
Future<Model?> get();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Query Features
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// 1. Advanced Where Clauses
|
||||||
|
class Query<T extends Model> {
|
||||||
|
// Need to implement:
|
||||||
|
Query<T> whereIn(String column, List<dynamic> values);
|
||||||
|
Query<T> whereNotIn(String column, List<dynamic> values);
|
||||||
|
Query<T> whereBetween(String column, List<dynamic> values);
|
||||||
|
Query<T> whereNotBetween(String column, List<dynamic> values);
|
||||||
|
Query<T> whereNull(String column);
|
||||||
|
Query<T> whereNotNull(String column);
|
||||||
|
Query<T> whereDate(String column, DateTime date);
|
||||||
|
Query<T> whereMonth(String column, int month);
|
||||||
|
Query<T> whereYear(String column, int year);
|
||||||
|
Query<T> whereTime(String column, String operator, DateTime time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Joins
|
||||||
|
class Query<T extends Model> {
|
||||||
|
// Need to implement:
|
||||||
|
Query<T> join(String table, String first, [String? operator, String? second]);
|
||||||
|
Query<T> leftJoin(String table, String first, [String? operator, String? second]);
|
||||||
|
Query<T> rightJoin(String table, String first, [String? operator, String? second]);
|
||||||
|
Query<T> crossJoin(String table);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Aggregates
|
||||||
|
class Query<T extends Model> {
|
||||||
|
// Need to implement:
|
||||||
|
Future<int> count([String column = '*']);
|
||||||
|
Future<dynamic> max(String column);
|
||||||
|
Future<dynamic> min(String column);
|
||||||
|
Future<num> avg(String column);
|
||||||
|
Future<num> sum(String column);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Gaps
|
||||||
|
|
||||||
|
### 1. Missing API Documentation
|
||||||
|
```dart
|
||||||
|
// Need to document:
|
||||||
|
|
||||||
|
/// Applies a scope to the query.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// class PublishedScope implements Scope {
|
||||||
|
/// Query<T> apply<T extends Model>(Query<T> query) {
|
||||||
|
/// return query.where('published', true);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
void addGlobalScope(Scope scope);
|
||||||
|
|
||||||
|
/// Defines a local scope.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// Query<Post> published() {
|
||||||
|
/// return where('published', true);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
void scopePublished(Query query);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Integration Examples
|
||||||
|
```dart
|
||||||
|
// Need examples for:
|
||||||
|
|
||||||
|
// 1. Model Observers
|
||||||
|
class UserObserver extends ModelObserver<User> {
|
||||||
|
@override
|
||||||
|
void created(User user) {
|
||||||
|
// Send welcome email
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void deleted(User user) {
|
||||||
|
// Cleanup user data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Model Factories
|
||||||
|
class UserFactory extends ModelFactory<User> {
|
||||||
|
@override
|
||||||
|
User definition() {
|
||||||
|
return User()
|
||||||
|
..name = faker.person.name()
|
||||||
|
..email = faker.internet.email();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Many to Many Relationships
|
||||||
|
class User extends Model {
|
||||||
|
Future<List<Role>> roles() {
|
||||||
|
return belongsToMany<Role>('role_user')
|
||||||
|
.withPivot(['expires_at'])
|
||||||
|
.wherePivot('active', true)
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Test Coverage
|
||||||
|
```dart
|
||||||
|
// Need tests for:
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Model Scopes', () {
|
||||||
|
test('applies global scopes', () async {
|
||||||
|
var posts = await Post.all();
|
||||||
|
expect(posts.every((p) => p.published), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('applies local scopes', () async {
|
||||||
|
var posts = await Post().recent().popular().get();
|
||||||
|
expect(posts, hasLength(greaterThan(0)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Model Factories', () {
|
||||||
|
test('creates model instances', () async {
|
||||||
|
var users = await UserFactory().createMany(3);
|
||||||
|
expect(users, hasLength(3));
|
||||||
|
expect(users.first.name, isNotEmpty);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **High Priority**
|
||||||
|
- Model scopes (Laravel compatibility)
|
||||||
|
- Model observers (Laravel compatibility)
|
||||||
|
- Many to Many relationships
|
||||||
|
|
||||||
|
2. **Medium Priority**
|
||||||
|
- Model factories
|
||||||
|
- Advanced where clauses
|
||||||
|
- Query joins
|
||||||
|
|
||||||
|
3. **Low Priority**
|
||||||
|
- Additional relationship types
|
||||||
|
- Additional query features
|
||||||
|
- Performance optimizations
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Implementation Tasks**
|
||||||
|
- Add model scopes
|
||||||
|
- Add model observers
|
||||||
|
- Add many to many relationships
|
||||||
|
- Add model factories
|
||||||
|
|
||||||
|
2. **Documentation Tasks**
|
||||||
|
- Document model scopes
|
||||||
|
- Document model observers
|
||||||
|
- Document relationships
|
||||||
|
- Add integration examples
|
||||||
|
|
||||||
|
3. **Testing Tasks**
|
||||||
|
- Add scope tests
|
||||||
|
- Add observer tests
|
||||||
|
- Add relationship tests
|
||||||
|
- Add factory tests
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing model features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Model Package Specification](model_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each model feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in [Model Package Specification](model_package_specification.md)
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing model features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Model system must:
|
||||||
|
1. Handle large datasets efficiently
|
||||||
|
2. Optimize relationship loading
|
||||||
|
3. Support eager loading
|
||||||
|
4. Cache query results
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Model tests must:
|
||||||
|
1. Cover all model operations
|
||||||
|
2. Test relationships
|
||||||
|
3. Verify events
|
||||||
|
4. Check query building
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Model documentation must:
|
||||||
|
1. Explain model patterns
|
||||||
|
2. Show relationship examples
|
||||||
|
3. Cover event handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
486
docs/model_package_specification.md
Normal file
486
docs/model_package_specification.md
Normal file
|
@ -0,0 +1,486 @@
|
||||||
|
# Model Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Model package provides a robust data modeling system that matches Laravel's Eloquent functionality. It supports active record pattern, relationships, attribute casting, serialization, and model events while leveraging Dart's type system.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for model events
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Base Model
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core model implementation
|
||||||
|
abstract class Model {
|
||||||
|
/// Model attributes
|
||||||
|
final Map<String, dynamic> _attributes = {};
|
||||||
|
|
||||||
|
/// Original attributes
|
||||||
|
final Map<String, dynamic> _original = {};
|
||||||
|
|
||||||
|
/// Changed attributes
|
||||||
|
final Set<String> _changes = {};
|
||||||
|
|
||||||
|
/// Model constructor
|
||||||
|
Model([Map<String, dynamic>? attributes]) {
|
||||||
|
fill(attributes ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets table name
|
||||||
|
String get table;
|
||||||
|
|
||||||
|
/// Gets primary key
|
||||||
|
String get primaryKey => 'id';
|
||||||
|
|
||||||
|
/// Gets fillable attributes
|
||||||
|
List<String> get fillable => [];
|
||||||
|
|
||||||
|
/// Gets guarded attributes
|
||||||
|
List<String> get guarded => ['id'];
|
||||||
|
|
||||||
|
/// Gets attribute value
|
||||||
|
dynamic operator [](String key) => getAttribute(key);
|
||||||
|
|
||||||
|
/// Sets attribute value
|
||||||
|
operator []=(String key, dynamic value) => setAttribute(key, value);
|
||||||
|
|
||||||
|
/// Gets an attribute
|
||||||
|
dynamic getAttribute(String key) {
|
||||||
|
return _attributes[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets an attribute
|
||||||
|
void setAttribute(String key, dynamic value) {
|
||||||
|
if (!_original.containsKey(key)) {
|
||||||
|
_original[key] = _attributes[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
_attributes[key] = value;
|
||||||
|
_changes.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills attributes
|
||||||
|
void fill(Map<String, dynamic> attributes) {
|
||||||
|
for (var key in attributes.keys) {
|
||||||
|
if (_isFillable(key)) {
|
||||||
|
this[key] = attributes[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if attribute is fillable
|
||||||
|
bool _isFillable(String key) {
|
||||||
|
if (guarded.contains(key)) return false;
|
||||||
|
if (fillable.isEmpty) return true;
|
||||||
|
return fillable.contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets changed attributes
|
||||||
|
Map<String, dynamic> getDirty() {
|
||||||
|
var dirty = <String, dynamic>{};
|
||||||
|
for (var key in _changes) {
|
||||||
|
dirty[key] = _attributes[key];
|
||||||
|
}
|
||||||
|
return dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if model is dirty
|
||||||
|
bool get isDirty => _changes.isNotEmpty;
|
||||||
|
|
||||||
|
/// Gets original attributes
|
||||||
|
Map<String, dynamic> getOriginal() => Map.from(_original);
|
||||||
|
|
||||||
|
/// Resets changes
|
||||||
|
void syncOriginal() {
|
||||||
|
_original.clear();
|
||||||
|
_original.addAll(_attributes);
|
||||||
|
_changes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts to map
|
||||||
|
Map<String, dynamic> toMap() => Map.from(_attributes);
|
||||||
|
|
||||||
|
/// Converts to JSON
|
||||||
|
String toJson() => jsonEncode(toMap());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Model Relationships
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Has one relationship
|
||||||
|
class HasOne<T extends Model> extends Relationship<T> {
|
||||||
|
/// Foreign key
|
||||||
|
final String foreignKey;
|
||||||
|
|
||||||
|
/// Local key
|
||||||
|
final String localKey;
|
||||||
|
|
||||||
|
HasOne(Query<T> query, Model parent, this.foreignKey, this.localKey)
|
||||||
|
: super(query, parent);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> get() async {
|
||||||
|
return await query
|
||||||
|
.where(foreignKey, parent[localKey])
|
||||||
|
.first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Has many relationship
|
||||||
|
class HasMany<T extends Model> extends Relationship<T> {
|
||||||
|
/// Foreign key
|
||||||
|
final String foreignKey;
|
||||||
|
|
||||||
|
/// Local key
|
||||||
|
final String localKey;
|
||||||
|
|
||||||
|
HasMany(Query<T> query, Model parent, this.foreignKey, this.localKey)
|
||||||
|
: super(query, parent);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<T>> get() async {
|
||||||
|
return await query
|
||||||
|
.where(foreignKey, parent[localKey])
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Belongs to relationship
|
||||||
|
class BelongsTo<T extends Model> extends Relationship<T> {
|
||||||
|
/// Foreign key
|
||||||
|
final String foreignKey;
|
||||||
|
|
||||||
|
/// Owner key
|
||||||
|
final String ownerKey;
|
||||||
|
|
||||||
|
BelongsTo(Query<T> query, Model child, this.foreignKey, this.ownerKey)
|
||||||
|
: super(query, child);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> get() async {
|
||||||
|
return await query
|
||||||
|
.where(ownerKey, parent[foreignKey])
|
||||||
|
.first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Model Events
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Model events mixin
|
||||||
|
mixin ModelEvents {
|
||||||
|
/// Event dispatcher
|
||||||
|
static EventDispatcherContract? _dispatcher;
|
||||||
|
|
||||||
|
/// Sets event dispatcher
|
||||||
|
static void setEventDispatcher(EventDispatcherContract dispatcher) {
|
||||||
|
_dispatcher = dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires a model event
|
||||||
|
Future<bool> fireModelEvent(String event) async {
|
||||||
|
if (_dispatcher == null) return true;
|
||||||
|
|
||||||
|
var result = await _dispatcher!.dispatch('model.$event', this);
|
||||||
|
return result != false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fires creating event
|
||||||
|
Future<bool> fireCreatingEvent() => fireModelEvent('creating');
|
||||||
|
|
||||||
|
/// Fires created event
|
||||||
|
Future<bool> fireCreatedEvent() => fireModelEvent('created');
|
||||||
|
|
||||||
|
/// Fires updating event
|
||||||
|
Future<bool> fireUpdatingEvent() => fireModelEvent('updating');
|
||||||
|
|
||||||
|
/// Fires updated event
|
||||||
|
Future<bool> fireUpdatedEvent() => fireModelEvent('updated');
|
||||||
|
|
||||||
|
/// Fires deleting event
|
||||||
|
Future<bool> fireDeletingEvent() => fireModelEvent('deleting');
|
||||||
|
|
||||||
|
/// Fires deleted event
|
||||||
|
Future<bool> fireDeletedEvent() => fireModelEvent('deleted');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Model Query Builder
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Model query builder
|
||||||
|
class Query<T extends Model> {
|
||||||
|
/// Database connection
|
||||||
|
final DatabaseConnection _connection;
|
||||||
|
|
||||||
|
/// Model instance
|
||||||
|
final T _model;
|
||||||
|
|
||||||
|
/// Query constraints
|
||||||
|
final List<String> _wheres = [];
|
||||||
|
final List<dynamic> _bindings = [];
|
||||||
|
final List<String> _orders = [];
|
||||||
|
int? _limit;
|
||||||
|
int? _offset;
|
||||||
|
|
||||||
|
Query(this._connection, this._model);
|
||||||
|
|
||||||
|
/// Adds where clause
|
||||||
|
Query<T> where(String column, [dynamic value]) {
|
||||||
|
_wheres.add('$column = ?');
|
||||||
|
_bindings.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds order by clause
|
||||||
|
Query<T> orderBy(String column, [String direction = 'asc']) {
|
||||||
|
_orders.add('$column $direction');
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets limit
|
||||||
|
Query<T> limit(int limit) {
|
||||||
|
_limit = limit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets offset
|
||||||
|
Query<T> offset(int offset) {
|
||||||
|
_offset = offset;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets first result
|
||||||
|
Future<T?> first() async {
|
||||||
|
var results = await get();
|
||||||
|
return results.isEmpty ? null : results.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets results
|
||||||
|
Future<List<T>> get() async {
|
||||||
|
var sql = _toSql();
|
||||||
|
var rows = await _connection.select(sql, _bindings);
|
||||||
|
return rows.map((row) => _hydrate(row)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds SQL query
|
||||||
|
String _toSql() {
|
||||||
|
var sql = 'select * from ${_model.table}';
|
||||||
|
|
||||||
|
if (_wheres.isNotEmpty) {
|
||||||
|
sql += ' where ${_wheres.join(' and ')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_orders.isNotEmpty) {
|
||||||
|
sql += ' order by ${_orders.join(', ')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_limit != null) {
|
||||||
|
sql += ' limit $_limit';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_offset != null) {
|
||||||
|
sql += ' offset $_offset';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hydrates model from row
|
||||||
|
T _hydrate(Map<String, dynamic> row) {
|
||||||
|
var instance = _model.newInstance() as T;
|
||||||
|
instance.fill(row);
|
||||||
|
instance.syncOriginal();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
### 1. Basic Model Usage
|
||||||
|
```dart
|
||||||
|
// Define model
|
||||||
|
class User extends Model {
|
||||||
|
@override
|
||||||
|
String get table => 'users';
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> get fillable => ['name', 'email'];
|
||||||
|
|
||||||
|
String get name => this['name'];
|
||||||
|
set name(String value) => this['name'] = value;
|
||||||
|
|
||||||
|
String get email => this['email'];
|
||||||
|
set email(String value) => this['email'] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user
|
||||||
|
var user = User()
|
||||||
|
..name = 'John Doe'
|
||||||
|
..email = 'john@example.com';
|
||||||
|
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
// Find user
|
||||||
|
var found = await User.find(1);
|
||||||
|
print(found.name); // John Doe
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Relationships
|
||||||
|
```dart
|
||||||
|
class User extends Model {
|
||||||
|
// Has many posts
|
||||||
|
Future<List<Post>> posts() {
|
||||||
|
return hasMany<Post>('user_id').get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has one profile
|
||||||
|
Future<Profile?> profile() {
|
||||||
|
return hasOne<Profile>('user_id').get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Post extends Model {
|
||||||
|
// Belongs to user
|
||||||
|
Future<User?> user() {
|
||||||
|
return belongsTo<User>('user_id').get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use relationships
|
||||||
|
var user = await User.find(1);
|
||||||
|
var posts = await user.posts();
|
||||||
|
var profile = await user.profile();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Events
|
||||||
|
```dart
|
||||||
|
// Register event listener
|
||||||
|
Model.getEventDispatcher().listen<ModelCreated<User>>((event) {
|
||||||
|
var user = event.model;
|
||||||
|
print('User ${user.name} was created');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create user (triggers event)
|
||||||
|
var user = User()
|
||||||
|
..name = 'Jane Doe'
|
||||||
|
..email = 'jane@example.com';
|
||||||
|
|
||||||
|
await user.save();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Model', () {
|
||||||
|
test('handles attributes', () {
|
||||||
|
var user = User()
|
||||||
|
..name = 'John'
|
||||||
|
..email = 'john@example.com';
|
||||||
|
|
||||||
|
expect(user.name, equals('John'));
|
||||||
|
expect(user.isDirty, isTrue);
|
||||||
|
expect(user.getDirty(), containsPair('name', 'John'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tracks changes', () {
|
||||||
|
var user = User()
|
||||||
|
..fill({
|
||||||
|
'name': 'John',
|
||||||
|
'email': 'john@example.com'
|
||||||
|
});
|
||||||
|
|
||||||
|
user.syncOriginal();
|
||||||
|
user.name = 'Jane';
|
||||||
|
|
||||||
|
expect(user.isDirty, isTrue);
|
||||||
|
expect(user.getOriginal()['name'], equals('John'));
|
||||||
|
expect(user.name, equals('Jane'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Relationships', () {
|
||||||
|
test('loads relationships', () async {
|
||||||
|
var user = await User.find(1);
|
||||||
|
var posts = await user.posts();
|
||||||
|
|
||||||
|
expect(posts, hasLength(greaterThan(0)));
|
||||||
|
expect(posts.first, isA<Post>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement core model features
|
||||||
|
2. Add relationship types
|
||||||
|
3. Add model events
|
||||||
|
4. Add query builder
|
||||||
|
5. Write tests
|
||||||
|
6. Add benchmarks
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing model features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Events Package Specification](events_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each model feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Support model events (see [Events Package Specification](events_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing model features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Model system must:
|
||||||
|
1. Handle large datasets efficiently
|
||||||
|
2. Optimize relationship loading
|
||||||
|
3. Support eager loading
|
||||||
|
4. Cache query results
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Model tests must:
|
||||||
|
1. Cover all model operations
|
||||||
|
2. Test relationships
|
||||||
|
3. Verify events
|
||||||
|
4. Check query building
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Model documentation must:
|
||||||
|
1. Explain model patterns
|
||||||
|
2. Show relationship examples
|
||||||
|
3. Cover event handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
540
docs/package_integration_map.md
Normal file
540
docs/package_integration_map.md
Normal file
|
@ -0,0 +1,540 @@
|
||||||
|
# Package Integration Map
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document maps out the integration points between our framework packages and outlines how to maintain and enhance these integrations while achieving Laravel API compatibility.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Core Architecture](core_architecture.md) for system design
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
|
||||||
|
## Package Documentation
|
||||||
|
|
||||||
|
### Core Framework
|
||||||
|
1. Core Package
|
||||||
|
- [Core Package Specification](core_package_specification.md)
|
||||||
|
- [Core Architecture](core_architecture.md)
|
||||||
|
|
||||||
|
2. Container Package
|
||||||
|
- [Container Package Specification](container_package_specification.md)
|
||||||
|
- [Container Gap Analysis](container_gap_analysis.md)
|
||||||
|
- [Container Feature Integration](container_feature_integration.md)
|
||||||
|
- [Container Migration Guide](container_migration_guide.md)
|
||||||
|
|
||||||
|
3. Contracts Package
|
||||||
|
- [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
4. Events Package
|
||||||
|
- [Events Package Specification](events_package_specification.md)
|
||||||
|
- [Events Gap Analysis](events_gap_analysis.md)
|
||||||
|
|
||||||
|
5. Pipeline Package
|
||||||
|
- [Pipeline Package Specification](pipeline_package_specification.md)
|
||||||
|
- [Pipeline Gap Analysis](pipeline_gap_analysis.md)
|
||||||
|
|
||||||
|
6. Support Package
|
||||||
|
- [Support Package Specification](support_package_specification.md)
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
1. Bus Package
|
||||||
|
- [Bus Package Specification](bus_package_specification.md)
|
||||||
|
- [Bus Gap Analysis](bus_gap_analysis.md)
|
||||||
|
|
||||||
|
2. Config Package
|
||||||
|
- [Config Package Specification](config_package_specification.md)
|
||||||
|
- [Config Gap Analysis](config_gap_analysis.md)
|
||||||
|
|
||||||
|
3. Filesystem Package
|
||||||
|
- [Filesystem Package Specification](filesystem_package_specification.md)
|
||||||
|
- [Filesystem Gap Analysis](filesystem_gap_analysis.md)
|
||||||
|
|
||||||
|
4. Model Package
|
||||||
|
- [Model Package Specification](model_package_specification.md)
|
||||||
|
- [Model Gap Analysis](model_gap_analysis.md)
|
||||||
|
|
||||||
|
5. Process Package
|
||||||
|
- [Process Package Specification](process_package_specification.md)
|
||||||
|
- [Process Gap Analysis](process_gap_analysis.md)
|
||||||
|
|
||||||
|
6. Queue Package
|
||||||
|
- [Queue Package Specification](queue_package_specification.md)
|
||||||
|
- [Queue Gap Analysis](queue_gap_analysis.md)
|
||||||
|
|
||||||
|
7. Route Package
|
||||||
|
- [Route Package Specification](route_package_specification.md)
|
||||||
|
- [Route Gap Analysis](route_gap_analysis.md)
|
||||||
|
|
||||||
|
8. Testing Package
|
||||||
|
- [Testing Package Specification](testing_package_specification.md)
|
||||||
|
- [Testing Gap Analysis](testing_gap_analysis.md)
|
||||||
|
|
||||||
|
## Core Integration Points
|
||||||
|
|
||||||
|
### 1. Container Integration Hub
|
||||||
|
```dart
|
||||||
|
// All packages integrate with Container for dependency injection
|
||||||
|
class ServiceProvider {
|
||||||
|
final Container container;
|
||||||
|
|
||||||
|
// Current Integration
|
||||||
|
void register() {
|
||||||
|
container.registerSingleton<Service>(ServiceImpl());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laravel-Compatible Enhancement
|
||||||
|
void register() {
|
||||||
|
// Add contextual binding
|
||||||
|
container.when(Service).needs<Logger>().give(FileLogger());
|
||||||
|
|
||||||
|
// Add tagged binding
|
||||||
|
container.tag([
|
||||||
|
EmailNotifier,
|
||||||
|
SmsNotifier,
|
||||||
|
PushNotifier
|
||||||
|
], 'notifications');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Event System Integration
|
||||||
|
```dart
|
||||||
|
// Events package integrates with multiple packages
|
||||||
|
class EventServiceProvider {
|
||||||
|
// Current Integration
|
||||||
|
void register() {
|
||||||
|
// Queue Integration
|
||||||
|
container.singleton<QueuedEventDispatcher>((c) =>
|
||||||
|
QueuedEventDispatcher(
|
||||||
|
queue: c.make<QueueContract>(),
|
||||||
|
broadcaster: c.make<BroadcasterContract>()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bus Integration
|
||||||
|
container.singleton<CommandDispatcher>((c) =>
|
||||||
|
CommandDispatcher(
|
||||||
|
events: c.make<EventDispatcherContract>(),
|
||||||
|
queue: c.make<QueueContract>()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laravel-Compatible Enhancement
|
||||||
|
void register() {
|
||||||
|
// Add event discovery
|
||||||
|
container.singleton<EventDiscovery>((c) =>
|
||||||
|
EventDiscovery(c.make<Reflector>())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add after commit handling
|
||||||
|
container.singleton<DatabaseEventDispatcher>((c) =>
|
||||||
|
DatabaseEventDispatcher(
|
||||||
|
events: c.make<EventDispatcherContract>(),
|
||||||
|
db: c.make<DatabaseManager>()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Queue and Bus Integration
|
||||||
|
```dart
|
||||||
|
// Queue and Bus packages work together for job handling
|
||||||
|
class QueuedCommandDispatcher {
|
||||||
|
// Current Integration
|
||||||
|
Future<void> dispatch(Command command) async {
|
||||||
|
if (command is ShouldQueue) {
|
||||||
|
await queue.push(QueuedCommandJob(command));
|
||||||
|
} else {
|
||||||
|
await commandBus.dispatch(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laravel-Compatible Enhancement
|
||||||
|
Future<void> dispatch(Command command) async {
|
||||||
|
// Add job middleware
|
||||||
|
var job = QueuedCommandJob(command)
|
||||||
|
..through([
|
||||||
|
RateLimitedMiddleware(),
|
||||||
|
WithoutOverlappingMiddleware()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Add job batching
|
||||||
|
if (command is BatchableCommand) {
|
||||||
|
await queue.batch([job])
|
||||||
|
.allowFailures()
|
||||||
|
.dispatch();
|
||||||
|
} else {
|
||||||
|
await queue.push(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Route and Core Integration
|
||||||
|
```dart
|
||||||
|
// Route package integrates with Core for HTTP handling
|
||||||
|
class RouterServiceProvider {
|
||||||
|
// Current Integration
|
||||||
|
void register() {
|
||||||
|
container.singleton<Router>((c) =>
|
||||||
|
Router()
|
||||||
|
..use(LoggingMiddleware())
|
||||||
|
..use(AuthMiddleware())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laravel-Compatible Enhancement
|
||||||
|
void register() {
|
||||||
|
// Add model binding
|
||||||
|
container.singleton<RouteModelBinder>((c) =>
|
||||||
|
RouteModelBinder(
|
||||||
|
models: c.make<ModelRegistry>(),
|
||||||
|
db: c.make<DatabaseManager>()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add subdomain routing
|
||||||
|
container.singleton<SubdomainRouter>((c) =>
|
||||||
|
SubdomainRouter(
|
||||||
|
domains: c.make<DomainRegistry>(),
|
||||||
|
router: c.make<Router>()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Process and Queue Integration
|
||||||
|
```dart
|
||||||
|
// Process package integrates with Queue for background tasks
|
||||||
|
class ProcessManager {
|
||||||
|
// Current Integration
|
||||||
|
Future<void> runProcess(String command) async {
|
||||||
|
var process = await Process.start(command);
|
||||||
|
queue.push(ProcessMonitorJob(process.pid));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laravel-Compatible Enhancement
|
||||||
|
Future<void> runProcess(String command) async {
|
||||||
|
// Add scheduling
|
||||||
|
scheduler.job(ProcessJob(command))
|
||||||
|
.everyFiveMinutes()
|
||||||
|
.withoutOverlapping()
|
||||||
|
.onFailure((e) => notifyAdmin(e));
|
||||||
|
|
||||||
|
// Add process pools
|
||||||
|
processPool.job(command)
|
||||||
|
.onServers(['worker-1', 'worker-2'])
|
||||||
|
.dispatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Model and Event Integration
|
||||||
|
```dart
|
||||||
|
// Model package integrates with Events for model events
|
||||||
|
class ModelEventDispatcher {
|
||||||
|
// Current Integration
|
||||||
|
Future<void> save(Model model) async {
|
||||||
|
await events.dispatch(ModelSaving(model));
|
||||||
|
await db.save(model);
|
||||||
|
await events.dispatch(ModelSaved(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laravel-Compatible Enhancement
|
||||||
|
Future<void> save(Model model) async {
|
||||||
|
// Add transaction awareness
|
||||||
|
await db.transaction((tx) async {
|
||||||
|
await events.dispatch(ModelSaving(model));
|
||||||
|
await tx.save(model);
|
||||||
|
|
||||||
|
// Queue after commit
|
||||||
|
events.afterCommit(() =>
|
||||||
|
events.dispatch(ModelSaved(model))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package-Specific Integration Points
|
||||||
|
|
||||||
|
### 1. Container Package
|
||||||
|
```dart
|
||||||
|
// Integration with other packages
|
||||||
|
class Container {
|
||||||
|
// Current
|
||||||
|
void bootstrap() {
|
||||||
|
registerSingleton<EventDispatcher>();
|
||||||
|
registerSingleton<QueueManager>();
|
||||||
|
registerSingleton<CommandBus>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced
|
||||||
|
void bootstrap() {
|
||||||
|
// Add service repository
|
||||||
|
registerSingleton<ServiceRepository>((c) =>
|
||||||
|
ServiceRepository([
|
||||||
|
EventServiceProvider(),
|
||||||
|
QueueServiceProvider(),
|
||||||
|
BusServiceProvider()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add deferred loading
|
||||||
|
registerDeferred<ReportGenerator>((c) =>
|
||||||
|
ReportGenerator(c.make<Database>())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Events Package
|
||||||
|
```dart
|
||||||
|
// Integration with other packages
|
||||||
|
class EventDispatcher {
|
||||||
|
// Current
|
||||||
|
Future<void> dispatch(Event event) async {
|
||||||
|
if (event is QueuedEvent) {
|
||||||
|
await queue.push(event);
|
||||||
|
} else {
|
||||||
|
await notifyListeners(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced
|
||||||
|
Future<void> dispatch(Event event) async {
|
||||||
|
// Add broadcast channels
|
||||||
|
if (event is BroadcastEvent) {
|
||||||
|
await broadcast.to(event.channels)
|
||||||
|
.with(['queue' => queue.connection()])
|
||||||
|
.send(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event subscribers
|
||||||
|
await container.make<EventSubscriberRegistry>()
|
||||||
|
.dispatch(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Queue Package
|
||||||
|
```dart
|
||||||
|
// Integration with other packages
|
||||||
|
class QueueManager {
|
||||||
|
// Current
|
||||||
|
Future<void> process() async {
|
||||||
|
while (true) {
|
||||||
|
var job = await queue.pop();
|
||||||
|
await job.handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced
|
||||||
|
Future<void> process() async {
|
||||||
|
// Add worker management
|
||||||
|
worker.supervise((worker) {
|
||||||
|
worker.process('default')
|
||||||
|
.throughMiddleware([
|
||||||
|
RateLimited::class,
|
||||||
|
PreventOverlapping::class
|
||||||
|
])
|
||||||
|
.withEvents(events);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Enhancement Strategy
|
||||||
|
|
||||||
|
1. **Container Enhancements**
|
||||||
|
- Add contextual binding
|
||||||
|
- Add tagged bindings
|
||||||
|
- Keep existing integrations working
|
||||||
|
|
||||||
|
2. **Event System Enhancements**
|
||||||
|
- Add event discovery
|
||||||
|
- Add after commit handling
|
||||||
|
- Maintain existing event flow
|
||||||
|
|
||||||
|
3. **Queue System Enhancements**
|
||||||
|
- Add job batching
|
||||||
|
- Add better job middleware
|
||||||
|
- Keep existing job handling
|
||||||
|
|
||||||
|
4. **Route System Enhancements**
|
||||||
|
- Add model binding
|
||||||
|
- Add subdomain routing
|
||||||
|
- Maintain existing routing
|
||||||
|
|
||||||
|
5. **Process System Enhancements**
|
||||||
|
- Add scheduling
|
||||||
|
- Add process pools
|
||||||
|
- Keep existing process management
|
||||||
|
|
||||||
|
6. **Model System Enhancements**
|
||||||
|
- Add Eloquent features
|
||||||
|
- Add relationships
|
||||||
|
- Maintain existing model events
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. **Document Current Integration Points**
|
||||||
|
- Map all package dependencies
|
||||||
|
- Document integration interfaces
|
||||||
|
- Note existing functionality
|
||||||
|
|
||||||
|
2. **Plan Laravel-Compatible Interfaces**
|
||||||
|
- Review Laravel's interfaces
|
||||||
|
- Design compatible interfaces
|
||||||
|
- Plan migration strategy
|
||||||
|
|
||||||
|
3. **Implement Enhancements**
|
||||||
|
- Start with Container enhancements
|
||||||
|
- Add Event enhancements
|
||||||
|
- Add Queue enhancements
|
||||||
|
- Continue with other packages
|
||||||
|
|
||||||
|
4. **Test Integration Points**
|
||||||
|
- Test existing functionality
|
||||||
|
- Test new functionality
|
||||||
|
- Test Laravel compatibility
|
||||||
|
|
||||||
|
5. **Migration Guide**
|
||||||
|
- Document breaking changes
|
||||||
|
- Provide upgrade path
|
||||||
|
- Include examples
|
||||||
|
|
||||||
|
## Package Dependencies
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Core[Core] --> Container[Container]
|
||||||
|
Core --> Events[Events]
|
||||||
|
Core --> Pipeline[Pipeline]
|
||||||
|
|
||||||
|
Container --> Contracts[Contracts]
|
||||||
|
Events --> Container
|
||||||
|
Pipeline --> Container
|
||||||
|
|
||||||
|
Bus[Bus] --> Events
|
||||||
|
Bus --> Queue[Queue]
|
||||||
|
|
||||||
|
Config[Config] --> Container
|
||||||
|
|
||||||
|
Filesystem[Filesystem] --> Container
|
||||||
|
|
||||||
|
Model[Model] --> Events
|
||||||
|
Model --> Container
|
||||||
|
|
||||||
|
Process[Process] --> Events
|
||||||
|
Process --> Queue
|
||||||
|
|
||||||
|
Queue --> Events
|
||||||
|
Queue --> Container
|
||||||
|
|
||||||
|
Route[Route] --> Pipeline
|
||||||
|
Route --> Container
|
||||||
|
|
||||||
|
Testing[Testing] --> Container
|
||||||
|
Testing --> Events
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Status
|
||||||
|
|
||||||
|
### Core Framework (90%)
|
||||||
|
- Core Package (95%)
|
||||||
|
* Application lifecycle ✓
|
||||||
|
* Service providers ✓
|
||||||
|
* HTTP kernel ✓
|
||||||
|
* Console kernel ✓
|
||||||
|
* Exception handling ✓
|
||||||
|
* Needs: Performance optimizations
|
||||||
|
|
||||||
|
- Container Package (90%)
|
||||||
|
* Basic DI ✓
|
||||||
|
* Auto-wiring ✓
|
||||||
|
* Service providers ✓
|
||||||
|
* Needs: Contextual binding
|
||||||
|
|
||||||
|
- Events Package (85%)
|
||||||
|
* Event dispatching ✓
|
||||||
|
* Event subscribers ✓
|
||||||
|
* Event broadcasting ✓
|
||||||
|
* Needs: Event discovery
|
||||||
|
|
||||||
|
### Infrastructure (80%)
|
||||||
|
- Bus Package (85%)
|
||||||
|
* Command dispatching ✓
|
||||||
|
* Command queuing ✓
|
||||||
|
* Needs: Command batching
|
||||||
|
|
||||||
|
- Config Package (80%)
|
||||||
|
* Configuration repository ✓
|
||||||
|
* Environment loading ✓
|
||||||
|
* Needs: Config caching
|
||||||
|
|
||||||
|
- Filesystem Package (75%)
|
||||||
|
* Local driver ✓
|
||||||
|
* Cloud storage ✓
|
||||||
|
* Needs: Streaming support
|
||||||
|
|
||||||
|
- Model Package (80%)
|
||||||
|
* Basic ORM ✓
|
||||||
|
* Relationships ✓
|
||||||
|
* Needs: Model events
|
||||||
|
|
||||||
|
- Process Package (85%)
|
||||||
|
* Process management ✓
|
||||||
|
* Process pools ✓
|
||||||
|
* Needs: Process monitoring
|
||||||
|
|
||||||
|
- Queue Package (85%)
|
||||||
|
* Queue workers ✓
|
||||||
|
* Job batching ✓
|
||||||
|
* Needs: Rate limiting
|
||||||
|
|
||||||
|
- Route Package (90%)
|
||||||
|
* Route registration ✓
|
||||||
|
* Route matching ✓
|
||||||
|
* Middleware ✓
|
||||||
|
* Needs: Route caching
|
||||||
|
|
||||||
|
- Testing Package (85%)
|
||||||
|
* HTTP testing ✓
|
||||||
|
* Database testing ✓
|
||||||
|
* Needs: Browser testing
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing integrations:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each integration:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All integrations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match package specifications
|
||||||
|
|
||||||
|
### 4. Documentation Requirements
|
||||||
|
Integration documentation must:
|
||||||
|
1. Explain integration patterns
|
||||||
|
2. Show usage examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
316
docs/pipeline_gap_analysis.md
Normal file
316
docs/pipeline_gap_analysis.md
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
# Pipeline Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our Pipeline package's actual implementation and our documentation, identifying areas that need implementation or documentation updates.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Pipeline Package Specification](pipeline_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
|
||||||
|
## Implementation Gaps
|
||||||
|
|
||||||
|
### 1. Missing Laravel Features
|
||||||
|
```dart
|
||||||
|
// Documented but not implemented:
|
||||||
|
|
||||||
|
// 1. Pipeline Hub
|
||||||
|
class PipelineHub {
|
||||||
|
// Need to implement:
|
||||||
|
Pipeline pipeline(String name);
|
||||||
|
void defaults(List<Pipe> pipes);
|
||||||
|
Pipeline middleware();
|
||||||
|
Pipeline bus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Pipeline Conditions
|
||||||
|
class Pipeline {
|
||||||
|
// Need to implement:
|
||||||
|
Pipeline when(bool Function() callback);
|
||||||
|
Pipeline unless(bool Function() callback);
|
||||||
|
Pipeline whenCallback(Function callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Pipeline Caching
|
||||||
|
class Pipeline {
|
||||||
|
// Need to implement:
|
||||||
|
void enableCache();
|
||||||
|
void clearCache();
|
||||||
|
dynamic getCached(String key);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Existing Features Not Documented
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Implemented but not documented:
|
||||||
|
|
||||||
|
// 1. Type Registration
|
||||||
|
class Pipeline {
|
||||||
|
/// Registers pipe types for string resolution
|
||||||
|
void registerPipeType(String name, Type type);
|
||||||
|
|
||||||
|
/// Type map for string resolution
|
||||||
|
final Map<String, Type> _typeMap = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Method Invocation
|
||||||
|
class Pipeline {
|
||||||
|
/// Invokes methods on pipe instances
|
||||||
|
Future<dynamic> invokeMethod(
|
||||||
|
dynamic instance,
|
||||||
|
String methodName,
|
||||||
|
List<dynamic> arguments
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Sets method to call on pipes
|
||||||
|
Pipeline via(String method);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Exception Handling
|
||||||
|
class Pipeline {
|
||||||
|
/// Logger for pipeline
|
||||||
|
final Logger _logger;
|
||||||
|
|
||||||
|
/// Handles exceptions in pipeline
|
||||||
|
dynamic handleException(dynamic passable, Object e);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Integration Points Not Documented
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 1. Container Integration
|
||||||
|
class Pipeline {
|
||||||
|
/// Container reference
|
||||||
|
final Container? _container;
|
||||||
|
|
||||||
|
/// Gets container instance
|
||||||
|
Container getContainer();
|
||||||
|
|
||||||
|
/// Sets container instance
|
||||||
|
Pipeline setContainer(Container container);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Reflection Integration
|
||||||
|
class Pipeline {
|
||||||
|
/// Resolves pipe types using mirrors
|
||||||
|
Type? _resolvePipeType(String pipeClass) {
|
||||||
|
try {
|
||||||
|
for (var lib in currentMirrorSystem().libraries.values) {
|
||||||
|
// Reflection logic...
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Logging Integration
|
||||||
|
class Pipeline {
|
||||||
|
/// Logger instance
|
||||||
|
final Logger _logger;
|
||||||
|
|
||||||
|
/// Logs pipeline events
|
||||||
|
void _logPipelineEvent(String message, [Object? error]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Gaps
|
||||||
|
|
||||||
|
### 1. Missing API Documentation
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Need to document:
|
||||||
|
|
||||||
|
/// Registers a pipe type for string resolution.
|
||||||
|
///
|
||||||
|
/// This allows pipes to be specified by string names in the through() method.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// pipeline.registerPipeType('auth', AuthMiddleware);
|
||||||
|
/// pipeline.through(['auth']);
|
||||||
|
/// ```
|
||||||
|
void registerPipeType(String name, Type type);
|
||||||
|
|
||||||
|
/// Sets the method to be called on pipe instances.
|
||||||
|
///
|
||||||
|
/// By default, the 'handle' method is called. This method allows
|
||||||
|
/// customizing which method is called on each pipe.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// pipeline.via('process').through([MyPipe]);
|
||||||
|
/// // Will call process() instead of handle()
|
||||||
|
/// ```
|
||||||
|
Pipeline via(String method);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Integration Examples
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Need examples for:
|
||||||
|
|
||||||
|
// 1. Container Integration
|
||||||
|
var pipeline = Pipeline(container)
|
||||||
|
..through([
|
||||||
|
AuthMiddleware,
|
||||||
|
container.make<LoggingMiddleware>(),
|
||||||
|
'validation' // Resolved from container
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 2. Exception Handling
|
||||||
|
pipeline.through([
|
||||||
|
(passable, next) async {
|
||||||
|
try {
|
||||||
|
return await next(passable);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Pipeline error', e);
|
||||||
|
throw PipelineException(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 3. Method Customization
|
||||||
|
pipeline.via('process')
|
||||||
|
.through([
|
||||||
|
ProcessingPipe(), // Will call process() instead of handle()
|
||||||
|
ValidationPipe()
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Test Coverage
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Need tests for:
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Type Registration', () {
|
||||||
|
test('resolves string pipes to types', () {
|
||||||
|
var pipeline = Pipeline(container);
|
||||||
|
pipeline.registerPipeType('auth', AuthMiddleware);
|
||||||
|
|
||||||
|
await pipeline
|
||||||
|
.through(['auth'])
|
||||||
|
.send(request)
|
||||||
|
.then(handler);
|
||||||
|
|
||||||
|
verify(() => container.make<AuthMiddleware>()).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Method Invocation', () {
|
||||||
|
test('calls custom methods on pipes', () {
|
||||||
|
var pipeline = Pipeline(container);
|
||||||
|
var pipe = MockPipe();
|
||||||
|
|
||||||
|
await pipeline
|
||||||
|
.via('process')
|
||||||
|
.through([pipe])
|
||||||
|
.send(data)
|
||||||
|
.then(handler);
|
||||||
|
|
||||||
|
verify(() => pipe.process(any, any)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **High Priority**
|
||||||
|
- Pipeline hub (Laravel compatibility)
|
||||||
|
- Pipeline conditions (Laravel compatibility)
|
||||||
|
- Better exception handling
|
||||||
|
|
||||||
|
2. **Medium Priority**
|
||||||
|
- Pipeline caching
|
||||||
|
- Better type resolution
|
||||||
|
- Performance optimizations
|
||||||
|
|
||||||
|
3. **Low Priority**
|
||||||
|
- Additional helper methods
|
||||||
|
- Extended testing utilities
|
||||||
|
- Debug/profiling tools
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Implementation Tasks**
|
||||||
|
- Add pipeline hub
|
||||||
|
- Add pipeline conditions
|
||||||
|
- Add caching support
|
||||||
|
- Improve exception handling
|
||||||
|
|
||||||
|
2. **Documentation Tasks**
|
||||||
|
- Document type registration
|
||||||
|
- Document method invocation
|
||||||
|
- Document exception handling
|
||||||
|
- Add integration examples
|
||||||
|
|
||||||
|
3. **Testing Tasks**
|
||||||
|
- Add type registration tests
|
||||||
|
- Add method invocation tests
|
||||||
|
- Add exception handling tests
|
||||||
|
- Add integration tests
|
||||||
|
|
||||||
|
Would you like me to:
|
||||||
|
1. Start implementing missing features?
|
||||||
|
2. Update documentation for existing features?
|
||||||
|
3. Create test cases for missing coverage?
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing pipeline features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Pipeline Package Specification](pipeline_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each pipeline feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in [Pipeline Package Specification](pipeline_package_specification.md)
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing pipeline features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Pipeline system must:
|
||||||
|
1. Handle nested pipelines efficiently
|
||||||
|
2. Minimize memory usage in long pipelines
|
||||||
|
3. Support async operations
|
||||||
|
4. Scale with number of stages
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Pipeline tests must:
|
||||||
|
1. Cover all pipeline types
|
||||||
|
2. Test stage ordering
|
||||||
|
3. Verify error handling
|
||||||
|
4. Check conditional execution
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Pipeline documentation must:
|
||||||
|
1. Explain pipeline patterns
|
||||||
|
2. Show integration examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
408
docs/pipeline_package_specification.md
Normal file
408
docs/pipeline_package_specification.md
Normal file
|
@ -0,0 +1,408 @@
|
||||||
|
# Pipeline Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Pipeline package provides a robust implementation of the pipeline pattern, allowing for the sequential processing of tasks through a series of stages. It integrates deeply with our Route, Bus, and Queue packages while maintaining Laravel compatibility.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Contracts Package Specification](contracts_package_specification.md) for pipeline contracts
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Pipeline Base
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core pipeline class with conditional execution
|
||||||
|
class Pipeline<TPassable> with Conditionable<Pipeline<TPassable>> {
|
||||||
|
final Container _container;
|
||||||
|
final List<Pipe<TPassable>> _pipes;
|
||||||
|
TPassable? _passable;
|
||||||
|
String _method = 'handle';
|
||||||
|
|
||||||
|
Pipeline(this._container, [List<Pipe<TPassable>>? pipes])
|
||||||
|
: _pipes = pipes ?? [];
|
||||||
|
|
||||||
|
/// Sends an object through the pipeline
|
||||||
|
Pipeline<TPassable> send(TPassable passable) {
|
||||||
|
_passable = passable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the stages of the pipeline
|
||||||
|
Pipeline<TPassable> through(List<dynamic> pipes) {
|
||||||
|
for (var pipe in pipes) {
|
||||||
|
if (pipe is String) {
|
||||||
|
// Resolve from container
|
||||||
|
_pipes.add(_container.make(pipe));
|
||||||
|
} else if (pipe is Type) {
|
||||||
|
_pipes.add(_container.make(pipe));
|
||||||
|
} else if (pipe is Pipe<TPassable>) {
|
||||||
|
_pipes.add(pipe);
|
||||||
|
} else if (pipe is Function) {
|
||||||
|
_pipes.add(FunctionPipe(pipe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the method to call on the pipes
|
||||||
|
Pipeline<TPassable> via(String method) {
|
||||||
|
_method = method;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process the pipeline to final result
|
||||||
|
Future<TResult> then<TResult>(
|
||||||
|
FutureOr<TResult> Function(TPassable) destination
|
||||||
|
) async {
|
||||||
|
var pass = _passable;
|
||||||
|
if (pass == null) {
|
||||||
|
throw PipelineException('No passable object provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build pipeline
|
||||||
|
var pipeline = _pipes.fold<Function>(
|
||||||
|
destination,
|
||||||
|
(next, pipe) => (passable) =>
|
||||||
|
_container.call(() => pipe.handle(passable, next))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Execute pipeline
|
||||||
|
return await pipeline(pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Middleware Pipeline
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// HTTP middleware pipeline with route integration
|
||||||
|
class MiddlewarePipeline extends Pipeline<Request> {
|
||||||
|
final Router _router;
|
||||||
|
|
||||||
|
MiddlewarePipeline(Container container, this._router)
|
||||||
|
: super(container);
|
||||||
|
|
||||||
|
/// Adds route-specific middleware
|
||||||
|
MiddlewarePipeline throughRoute(Route route) {
|
||||||
|
// Get global middleware
|
||||||
|
var middleware = _router.middleware;
|
||||||
|
|
||||||
|
// Add route middleware
|
||||||
|
if (route.middleware.isNotEmpty) {
|
||||||
|
middleware.addAll(
|
||||||
|
route.middleware.map((m) => _container.make<Middleware>(m))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add route group middleware
|
||||||
|
if (route.group != null) {
|
||||||
|
middleware.addAll(route.group!.middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
return through(middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes request through middleware
|
||||||
|
Future<Response> process(
|
||||||
|
Request request,
|
||||||
|
FutureOr<Response> Function(Request) destination
|
||||||
|
) {
|
||||||
|
return send(request)
|
||||||
|
.when(() => shouldProcessMiddleware(request))
|
||||||
|
.then(destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if middleware should be processed
|
||||||
|
bool shouldProcessMiddleware(Request request) {
|
||||||
|
return !request.attributes.containsKey('skip_middleware');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Bus Pipeline
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Command bus pipeline with handler resolution
|
||||||
|
class BusPipeline<TCommand> extends Pipeline<TCommand> {
|
||||||
|
final CommandBus _bus;
|
||||||
|
|
||||||
|
BusPipeline(Container container, this._bus)
|
||||||
|
: super(container);
|
||||||
|
|
||||||
|
/// Processes command through pipeline
|
||||||
|
Future<TResult> process<TResult>(
|
||||||
|
TCommand command,
|
||||||
|
[Handler<TCommand>? handler]
|
||||||
|
) {
|
||||||
|
// Resolve handler
|
||||||
|
handler ??= _resolveHandler(command);
|
||||||
|
|
||||||
|
return send(command).then((cmd) =>
|
||||||
|
handler!.handle(cmd) as Future<TResult>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves command handler
|
||||||
|
Handler<TCommand> _resolveHandler(TCommand command) {
|
||||||
|
if (command is Command) {
|
||||||
|
return _container.make(command.handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
var handlerType = _bus.handlers[TCommand];
|
||||||
|
if (handlerType == null) {
|
||||||
|
throw HandlerNotFoundException(
|
||||||
|
'No handler found for ${TCommand}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _container.make(handlerType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Job Pipeline
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Queue job pipeline with middleware
|
||||||
|
class JobPipeline extends Pipeline<Job> {
|
||||||
|
final QueueManager _queue;
|
||||||
|
|
||||||
|
JobPipeline(Container container, this._queue)
|
||||||
|
: super(container);
|
||||||
|
|
||||||
|
/// Processes job through pipeline
|
||||||
|
Future<void> process(Job job) {
|
||||||
|
return send(job)
|
||||||
|
.through(_queue.middleware)
|
||||||
|
.then((j) => j.handle());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds rate limiting
|
||||||
|
JobPipeline withRateLimit(int maxAttempts, Duration timeout) {
|
||||||
|
return through([
|
||||||
|
RateLimitedPipe(maxAttempts, timeout)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prevents overlapping jobs
|
||||||
|
JobPipeline withoutOverlapping() {
|
||||||
|
return through([WithoutOverlappingPipe()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Pipeline Hub
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Manages application pipelines
|
||||||
|
class PipelineHub {
|
||||||
|
final Container _container;
|
||||||
|
final Map<String, Pipeline> _pipelines = {};
|
||||||
|
final List<Pipe> _defaults = [];
|
||||||
|
|
||||||
|
PipelineHub(this._container);
|
||||||
|
|
||||||
|
/// Gets or creates a pipeline
|
||||||
|
Pipeline pipeline(String name) {
|
||||||
|
return _pipelines.putIfAbsent(
|
||||||
|
name,
|
||||||
|
() => Pipeline(_container, [..._defaults])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets middleware pipeline
|
||||||
|
MiddlewarePipeline middleware() {
|
||||||
|
return pipeline('middleware') as MiddlewarePipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets bus pipeline
|
||||||
|
BusPipeline bus() {
|
||||||
|
return pipeline('bus') as BusPipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets job pipeline
|
||||||
|
JobPipeline job() {
|
||||||
|
return pipeline('job') as JobPipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets default pipes
|
||||||
|
void defaults(List<Pipe> pipes) {
|
||||||
|
_defaults.addAll(pipes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
### 1. Route Integration
|
||||||
|
```dart
|
||||||
|
// In RouteServiceProvider
|
||||||
|
void boot() {
|
||||||
|
router.middleware([
|
||||||
|
StartSession::class,
|
||||||
|
VerifyCsrfToken::class
|
||||||
|
]);
|
||||||
|
|
||||||
|
router.group(['middleware' => ['auth']], () {
|
||||||
|
router.get('/dashboard', DashboardController);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// In Router
|
||||||
|
Future<Response> dispatch(Request request) {
|
||||||
|
var route = matchRoute(request);
|
||||||
|
|
||||||
|
return container.make<MiddlewarePipeline>()
|
||||||
|
.throughRoute(route)
|
||||||
|
.process(request, (req) => route.handle(req));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Command Bus Integration
|
||||||
|
```dart
|
||||||
|
// In CommandBus
|
||||||
|
Future<TResult> dispatch<TResult>(Command command) {
|
||||||
|
return container.make<BusPipeline>()
|
||||||
|
.through([
|
||||||
|
TransactionPipe(),
|
||||||
|
ValidationPipe(),
|
||||||
|
AuthorizationPipe()
|
||||||
|
])
|
||||||
|
.process<TResult>(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
class CreateOrder implements Command {
|
||||||
|
@override
|
||||||
|
Type get handler => CreateOrderHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
var order = await bus.dispatch<Order>(
|
||||||
|
CreateOrder(items: items)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Queue Integration
|
||||||
|
```dart
|
||||||
|
// In QueueWorker
|
||||||
|
Future<void> process(Job job) {
|
||||||
|
return container.make<JobPipeline>()
|
||||||
|
.withRateLimit(3, Duration(minutes: 1))
|
||||||
|
.withoutOverlapping()
|
||||||
|
.process(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
class ProcessPayment implements Job {
|
||||||
|
@override
|
||||||
|
Future<void> handle() async {
|
||||||
|
// Process payment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await queue.push(ProcessPayment(
|
||||||
|
orderId: order.id
|
||||||
|
));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Middleware Pipeline', () {
|
||||||
|
test('processes route middleware', () async {
|
||||||
|
var pipeline = MiddlewarePipeline(container, router);
|
||||||
|
var route = Route('/test', middleware: ['auth']);
|
||||||
|
|
||||||
|
var response = await pipeline
|
||||||
|
.throughRoute(route)
|
||||||
|
.process(request, handler);
|
||||||
|
|
||||||
|
verify(() => auth.handle(any, any)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Bus Pipeline', () {
|
||||||
|
test('resolves and executes handler', () async {
|
||||||
|
var pipeline = BusPipeline(container, bus);
|
||||||
|
var command = CreateOrder(items: items);
|
||||||
|
|
||||||
|
var result = await pipeline.process<Order>(command);
|
||||||
|
|
||||||
|
expect(result, isA<Order>());
|
||||||
|
verify(() => handler.handle(command)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Add more middleware types
|
||||||
|
2. Enhance bus pipeline features
|
||||||
|
3. Add job pipeline features
|
||||||
|
4. Improve testing coverage
|
||||||
|
5. Add performance optimizations
|
||||||
|
|
||||||
|
Would you like me to enhance any other package specifications?
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing pipeline features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Understand [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each pipeline feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Implement required contracts (see [Contracts Package Specification](contracts_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing pipelines:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
5. Implement all contracts from [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Pipeline system must:
|
||||||
|
1. Handle nested pipelines efficiently
|
||||||
|
2. Minimize memory usage in long pipelines
|
||||||
|
3. Support async operations
|
||||||
|
4. Scale with number of stages
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Pipeline tests must:
|
||||||
|
1. Cover all pipeline types
|
||||||
|
2. Test stage ordering
|
||||||
|
3. Verify error handling
|
||||||
|
4. Check conditional execution
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Pipeline documentation must:
|
||||||
|
1. Explain pipeline patterns
|
||||||
|
2. Show integration examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
299
docs/process_gap_analysis.md
Normal file
299
docs/process_gap_analysis.md
Normal file
|
@ -0,0 +1,299 @@
|
||||||
|
# Process Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our Process package's actual implementation and Laravel's process functionality, identifying areas that need implementation or documentation updates.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Process Package Specification](process_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for process events
|
||||||
|
> - See [Queue Package Specification](queue_package_specification.md) for background processing
|
||||||
|
|
||||||
|
## Implementation Gaps
|
||||||
|
|
||||||
|
### 1. Missing Laravel Features
|
||||||
|
```dart
|
||||||
|
// Documented but not implemented:
|
||||||
|
|
||||||
|
// 1. Process Pipelines
|
||||||
|
class ProcessPipeline {
|
||||||
|
// Need to implement:
|
||||||
|
Future<ProcessResult> pipe(String command);
|
||||||
|
Future<ProcessResult> pipeThrough(List<String> commands);
|
||||||
|
Future<ProcessResult> pipeInput(String input);
|
||||||
|
Future<ProcessResult> pipeOutput(String file);
|
||||||
|
Future<ProcessResult> pipeErrorOutput(String file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Process Scheduling
|
||||||
|
class ProcessScheduler {
|
||||||
|
// Need to implement:
|
||||||
|
void schedule(String command, String frequency);
|
||||||
|
void daily(String command, [String time = '00:00']);
|
||||||
|
void weekly(String command, [int day = 0]);
|
||||||
|
void monthly(String command, [int day = 1]);
|
||||||
|
void cron(String expression, String command);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Process Monitoring
|
||||||
|
class ProcessMonitor {
|
||||||
|
// Need to implement:
|
||||||
|
Future<bool> isRunning(int pid);
|
||||||
|
Future<ProcessStats> getStats(int pid);
|
||||||
|
Future<void> onExit(int pid, Function callback);
|
||||||
|
Future<void> onOutput(int pid, Function callback);
|
||||||
|
Future<void> onError(int pid, Function callback);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Process Features
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// 1. Process Groups
|
||||||
|
class ProcessGroup {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> start();
|
||||||
|
Future<void> stop();
|
||||||
|
Future<void> restart();
|
||||||
|
Future<List<ProcessResult>> wait();
|
||||||
|
Future<void> signal(ProcessSignal signal);
|
||||||
|
bool isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Process Isolation
|
||||||
|
class ProcessIsolation {
|
||||||
|
// Need to implement:
|
||||||
|
void setUser(String user);
|
||||||
|
void setGroup(String group);
|
||||||
|
void setWorkingDirectory(String directory);
|
||||||
|
void setEnvironment(Map<String, String> env);
|
||||||
|
void setResourceLimits(ResourceLimits limits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Process Recovery
|
||||||
|
class ProcessRecovery {
|
||||||
|
// Need to implement:
|
||||||
|
void onCrash(Function callback);
|
||||||
|
void onHang(Function callback);
|
||||||
|
void onHighMemory(Function callback);
|
||||||
|
void onHighCpu(Function callback);
|
||||||
|
void restart();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Integration Features
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// 1. Queue Integration
|
||||||
|
class QueuedProcess {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> queue(String command);
|
||||||
|
Future<void> laterOn(String queue, Duration delay, String command);
|
||||||
|
Future<void> chain(List<String> commands);
|
||||||
|
Future<void> release(Duration delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Event Integration
|
||||||
|
class ProcessEvents {
|
||||||
|
// Need to implement:
|
||||||
|
void beforeStart(Function callback);
|
||||||
|
void afterStart(Function callback);
|
||||||
|
void beforeStop(Function callback);
|
||||||
|
void afterStop(Function callback);
|
||||||
|
void onOutput(Function callback);
|
||||||
|
void onError(Function callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Logging Integration
|
||||||
|
class ProcessLogging {
|
||||||
|
// Need to implement:
|
||||||
|
void enableLogging();
|
||||||
|
void setLogFile(String path);
|
||||||
|
void setLogLevel(LogLevel level);
|
||||||
|
void rotateLog();
|
||||||
|
void purgeOldLogs(Duration age);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Gaps
|
||||||
|
|
||||||
|
### 1. Missing API Documentation
|
||||||
|
```dart
|
||||||
|
// Need to document:
|
||||||
|
|
||||||
|
/// Pipes process output.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// await process
|
||||||
|
/// .pipe('sort')
|
||||||
|
/// .pipe('uniq')
|
||||||
|
/// .pipeOutput('output.txt');
|
||||||
|
/// ```
|
||||||
|
Future<ProcessResult> pipe(String command);
|
||||||
|
|
||||||
|
/// Schedules process execution.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// scheduler.daily('backup.sh', '02:00');
|
||||||
|
/// scheduler.weekly('cleanup.sh', DateTime.sunday);
|
||||||
|
/// scheduler.cron('0 * * * *', 'hourly.sh');
|
||||||
|
/// ```
|
||||||
|
void schedule(String command, String frequency);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Integration Examples
|
||||||
|
```dart
|
||||||
|
// Need examples for:
|
||||||
|
|
||||||
|
// 1. Process Groups
|
||||||
|
var group = ProcessGroup();
|
||||||
|
group.add('web-server', '--port=8080');
|
||||||
|
group.add('worker', '--queue=default');
|
||||||
|
await group.start();
|
||||||
|
|
||||||
|
// 2. Process Recovery
|
||||||
|
var recovery = ProcessRecovery(process);
|
||||||
|
recovery.onCrash(() async {
|
||||||
|
await notifyAdmin('Process crashed');
|
||||||
|
await process.restart();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Process Monitoring
|
||||||
|
var monitor = ProcessMonitor(process);
|
||||||
|
monitor.onHighMemory((usage) async {
|
||||||
|
await process.restart();
|
||||||
|
await notifyAdmin('High memory usage: $usage');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Test Coverage
|
||||||
|
```dart
|
||||||
|
// Need tests for:
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Process Pipelines', () {
|
||||||
|
test('pipes process output', () async {
|
||||||
|
var process = await manager.start('ls');
|
||||||
|
var result = await process
|
||||||
|
.pipe('sort')
|
||||||
|
.pipe('uniq')
|
||||||
|
.pipeOutput('output.txt');
|
||||||
|
|
||||||
|
expect(result.exitCode, equals(0));
|
||||||
|
expect(File('output.txt').existsSync(), isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Process Scheduling', () {
|
||||||
|
test('schedules daily tasks', () async {
|
||||||
|
var scheduler = ProcessScheduler();
|
||||||
|
scheduler.daily('backup.sh', '02:00');
|
||||||
|
|
||||||
|
var nextRun = scheduler.getNextRun('backup.sh');
|
||||||
|
expect(nextRun.hour, equals(2));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **High Priority**
|
||||||
|
- Process pipelines (Laravel compatibility)
|
||||||
|
- Process scheduling (Laravel compatibility)
|
||||||
|
- Process monitoring
|
||||||
|
|
||||||
|
2. **Medium Priority**
|
||||||
|
- Process groups
|
||||||
|
- Process isolation
|
||||||
|
- Process recovery
|
||||||
|
|
||||||
|
3. **Low Priority**
|
||||||
|
- Additional integration features
|
||||||
|
- Additional monitoring features
|
||||||
|
- Performance optimizations
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Implementation Tasks**
|
||||||
|
- Add process pipelines
|
||||||
|
- Add process scheduling
|
||||||
|
- Add process monitoring
|
||||||
|
- Add process groups
|
||||||
|
|
||||||
|
2. **Documentation Tasks**
|
||||||
|
- Document pipelines
|
||||||
|
- Document scheduling
|
||||||
|
- Document monitoring
|
||||||
|
- Add integration examples
|
||||||
|
|
||||||
|
3. **Testing Tasks**
|
||||||
|
- Add pipeline tests
|
||||||
|
- Add scheduling tests
|
||||||
|
- Add monitoring tests
|
||||||
|
- Add group tests
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing process features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Process Package Specification](process_package_specification.md)
|
||||||
|
6. Review [Events Package Specification](events_package_specification.md)
|
||||||
|
7. Review [Queue Package Specification](queue_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each process feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in [Process Package Specification](process_package_specification.md)
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing process features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Process system must:
|
||||||
|
1. Handle concurrent processes efficiently
|
||||||
|
2. Manage system resources
|
||||||
|
3. Support process pooling
|
||||||
|
4. Scale with process count
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Process tests must:
|
||||||
|
1. Cover all process operations
|
||||||
|
2. Test concurrent execution
|
||||||
|
3. Verify event handling
|
||||||
|
4. Check resource cleanup
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Process documentation must:
|
||||||
|
1. Explain process patterns
|
||||||
|
2. Show pipeline examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
408
docs/process_package_specification.md
Normal file
408
docs/process_package_specification.md
Normal file
|
@ -0,0 +1,408 @@
|
||||||
|
# Process Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Process package provides a robust system process handling system that matches Laravel's process functionality. It supports process execution, input/output handling, process pools, and signal handling while integrating with our Event and Queue packages.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for process events
|
||||||
|
> - See [Queue Package Specification](queue_package_specification.md) for background processing
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Process Manager
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core process manager implementation
|
||||||
|
class ProcessManager implements ProcessContract {
|
||||||
|
/// Container instance
|
||||||
|
final Container _container;
|
||||||
|
|
||||||
|
/// Active processes
|
||||||
|
final Map<int, Process> _processes = {};
|
||||||
|
|
||||||
|
/// Process event dispatcher
|
||||||
|
final EventDispatcherContract _events;
|
||||||
|
|
||||||
|
ProcessManager(this._container, this._events);
|
||||||
|
|
||||||
|
/// Starts a process
|
||||||
|
Future<Process> start(
|
||||||
|
String command, [
|
||||||
|
List<String>? arguments,
|
||||||
|
ProcessOptions? options
|
||||||
|
]) async {
|
||||||
|
options ??= ProcessOptions();
|
||||||
|
|
||||||
|
var process = await Process.start(
|
||||||
|
command,
|
||||||
|
arguments ?? [],
|
||||||
|
workingDirectory: options.workingDirectory,
|
||||||
|
environment: options.environment,
|
||||||
|
includeParentEnvironment: options.includeParentEnvironment,
|
||||||
|
runInShell: options.runInShell
|
||||||
|
);
|
||||||
|
|
||||||
|
_processes[process.pid] = process;
|
||||||
|
await _events.dispatch(ProcessStarted(process));
|
||||||
|
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs a process to completion
|
||||||
|
Future<ProcessResult> run(
|
||||||
|
String command, [
|
||||||
|
List<String>? arguments,
|
||||||
|
ProcessOptions? options
|
||||||
|
]) async {
|
||||||
|
var process = await start(command, arguments, options);
|
||||||
|
var result = await process.exitCode;
|
||||||
|
|
||||||
|
await _events.dispatch(ProcessCompleted(
|
||||||
|
process,
|
||||||
|
result
|
||||||
|
));
|
||||||
|
|
||||||
|
return ProcessResult(
|
||||||
|
process.pid,
|
||||||
|
result,
|
||||||
|
await _readOutput(process.stdout),
|
||||||
|
await _readOutput(process.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kills a process
|
||||||
|
Future<void> kill(int pid, [ProcessSignal signal = ProcessSignal.sigterm]) async {
|
||||||
|
var process = _processes[pid];
|
||||||
|
if (process == null) return;
|
||||||
|
|
||||||
|
process.kill(signal);
|
||||||
|
await _events.dispatch(ProcessKilled(process));
|
||||||
|
_processes.remove(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets active processes
|
||||||
|
List<Process> get activeProcesses => List.from(_processes.values);
|
||||||
|
|
||||||
|
/// Reads process output
|
||||||
|
Future<String> _readOutput(Stream<List<int>> stream) async {
|
||||||
|
var buffer = StringBuffer();
|
||||||
|
await for (var data in stream) {
|
||||||
|
buffer.write(String.fromCharCodes(data));
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Process Pool
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Process pool for parallel execution
|
||||||
|
class ProcessPool {
|
||||||
|
/// Maximum concurrent processes
|
||||||
|
final int concurrency;
|
||||||
|
|
||||||
|
/// Process manager
|
||||||
|
final ProcessManager _manager;
|
||||||
|
|
||||||
|
/// Active processes
|
||||||
|
final Set<Process> _active = {};
|
||||||
|
|
||||||
|
/// Pending commands
|
||||||
|
final Queue<PendingCommand> _pending = Queue();
|
||||||
|
|
||||||
|
ProcessPool(this._manager, {this.concurrency = 5});
|
||||||
|
|
||||||
|
/// Starts a process in the pool
|
||||||
|
Future<ProcessResult> start(
|
||||||
|
String command, [
|
||||||
|
List<String>? arguments,
|
||||||
|
ProcessOptions? options
|
||||||
|
]) async {
|
||||||
|
var pending = PendingCommand(
|
||||||
|
command,
|
||||||
|
arguments,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
_pending.add(pending);
|
||||||
|
await _processQueue();
|
||||||
|
|
||||||
|
return await pending.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes pending commands
|
||||||
|
Future<void> _processQueue() async {
|
||||||
|
while (_active.length < concurrency && _pending.isNotEmpty) {
|
||||||
|
var command = _pending.removeFirst();
|
||||||
|
await _startProcess(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts a process
|
||||||
|
Future<void> _startProcess(PendingCommand command) async {
|
||||||
|
var process = await _manager.start(
|
||||||
|
command.command,
|
||||||
|
command.arguments,
|
||||||
|
command.options
|
||||||
|
);
|
||||||
|
|
||||||
|
_active.add(process);
|
||||||
|
|
||||||
|
process.exitCode.then((result) {
|
||||||
|
_active.remove(process);
|
||||||
|
command.complete(ProcessResult(
|
||||||
|
process.pid,
|
||||||
|
result,
|
||||||
|
'',
|
||||||
|
''
|
||||||
|
));
|
||||||
|
_processQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Process Events
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Process started event
|
||||||
|
class ProcessStarted {
|
||||||
|
/// The started process
|
||||||
|
final Process process;
|
||||||
|
|
||||||
|
ProcessStarted(this.process);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process completed event
|
||||||
|
class ProcessCompleted {
|
||||||
|
/// The completed process
|
||||||
|
final Process process;
|
||||||
|
|
||||||
|
/// Exit code
|
||||||
|
final int exitCode;
|
||||||
|
|
||||||
|
ProcessCompleted(this.process, this.exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process killed event
|
||||||
|
class ProcessKilled {
|
||||||
|
/// The killed process
|
||||||
|
final Process process;
|
||||||
|
|
||||||
|
ProcessKilled(this.process);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process failed event
|
||||||
|
class ProcessFailed {
|
||||||
|
/// The failed process
|
||||||
|
final Process process;
|
||||||
|
|
||||||
|
/// Error details
|
||||||
|
final Object error;
|
||||||
|
|
||||||
|
ProcessFailed(this.process, this.error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Process Options
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Process execution options
|
||||||
|
class ProcessOptions {
|
||||||
|
/// Working directory
|
||||||
|
final String? workingDirectory;
|
||||||
|
|
||||||
|
/// Environment variables
|
||||||
|
final Map<String, String>? environment;
|
||||||
|
|
||||||
|
/// Include parent environment
|
||||||
|
final bool includeParentEnvironment;
|
||||||
|
|
||||||
|
/// Run in shell
|
||||||
|
final bool runInShell;
|
||||||
|
|
||||||
|
/// Process timeout
|
||||||
|
final Duration? timeout;
|
||||||
|
|
||||||
|
/// Idle timeout
|
||||||
|
final Duration? idleTimeout;
|
||||||
|
|
||||||
|
/// Retry attempts
|
||||||
|
final int retryAttempts;
|
||||||
|
|
||||||
|
/// Retry delay
|
||||||
|
final Duration retryDelay;
|
||||||
|
|
||||||
|
ProcessOptions({
|
||||||
|
this.workingDirectory,
|
||||||
|
this.environment,
|
||||||
|
this.includeParentEnvironment = true,
|
||||||
|
this.runInShell = false,
|
||||||
|
this.timeout,
|
||||||
|
this.idleTimeout,
|
||||||
|
this.retryAttempts = 0,
|
||||||
|
this.retryDelay = const Duration(seconds: 1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
### 1. Basic Process Execution
|
||||||
|
```dart
|
||||||
|
// Run process
|
||||||
|
var result = await processManager.run('ls', ['-la']);
|
||||||
|
print('Output: ${result.stdout}');
|
||||||
|
print('Exit code: ${result.exitCode}');
|
||||||
|
|
||||||
|
// Start long-running process
|
||||||
|
var process = await processManager.start('server', ['--port=8080']);
|
||||||
|
await process.exitCode; // Wait for completion
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Process Pool
|
||||||
|
```dart
|
||||||
|
// Create process pool
|
||||||
|
var pool = ProcessPool(processManager, concurrency: 3);
|
||||||
|
|
||||||
|
// Run multiple processes
|
||||||
|
await Future.wait([
|
||||||
|
pool.start('task1'),
|
||||||
|
pool.start('task2'),
|
||||||
|
pool.start('task3'),
|
||||||
|
pool.start('task4') // Queued until slot available
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Process Events
|
||||||
|
```dart
|
||||||
|
// Listen for process events
|
||||||
|
events.listen<ProcessStarted>((event) {
|
||||||
|
print('Process ${event.process.pid} started');
|
||||||
|
});
|
||||||
|
|
||||||
|
events.listen<ProcessCompleted>((event) {
|
||||||
|
print('Process ${event.process.pid} completed with code ${event.exitCode}');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start process
|
||||||
|
await processManager.start('long-task');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Process Manager', () {
|
||||||
|
test('runs processes', () async {
|
||||||
|
var manager = ProcessManager(container, events);
|
||||||
|
|
||||||
|
var result = await manager.run('echo', ['Hello']);
|
||||||
|
|
||||||
|
expect(result.exitCode, equals(0));
|
||||||
|
expect(result.stdout, contains('Hello'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles process failure', () async {
|
||||||
|
var manager = ProcessManager(container, events);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => manager.run('invalid-command'),
|
||||||
|
throwsA(isA<ProcessException>())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Process Pool', () {
|
||||||
|
test('limits concurrent processes', () async {
|
||||||
|
var pool = ProcessPool(manager, concurrency: 2);
|
||||||
|
var started = <String>[];
|
||||||
|
|
||||||
|
events.listen<ProcessStarted>((event) {
|
||||||
|
started.add(event.process.pid.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future.wait([
|
||||||
|
pool.start('task1'),
|
||||||
|
pool.start('task2'),
|
||||||
|
pool.start('task3')
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(started.length, equals(3));
|
||||||
|
expect(started.take(2).length, equals(2));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement core process features
|
||||||
|
2. Add process pool
|
||||||
|
3. Add process events
|
||||||
|
4. Add retry handling
|
||||||
|
5. Write tests
|
||||||
|
6. Add benchmarks
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing process features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Events Package Specification](events_package_specification.md)
|
||||||
|
6. Review [Queue Package Specification](queue_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each process feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Support event integration (see [Events Package Specification](events_package_specification.md))
|
||||||
|
5. Support queue integration (see [Queue Package Specification](queue_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing process features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Process system must:
|
||||||
|
1. Handle concurrent processes efficiently
|
||||||
|
2. Manage system resources
|
||||||
|
3. Support process pooling
|
||||||
|
4. Scale with process count
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Process tests must:
|
||||||
|
1. Cover all process operations
|
||||||
|
2. Test concurrent execution
|
||||||
|
3. Verify event handling
|
||||||
|
4. Check resource cleanup
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Process documentation must:
|
||||||
|
1. Explain process patterns
|
||||||
|
2. Show pool examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
301
docs/queue_gap_analysis.md
Normal file
301
docs/queue_gap_analysis.md
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
# Queue Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our Queue package's actual implementation and Laravel's queue functionality, identifying areas that need implementation or documentation updates.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Queue Package Specification](queue_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for event integration
|
||||||
|
> - See [Bus Package Specification](bus_package_specification.md) for command bus integration
|
||||||
|
|
||||||
|
## Implementation Gaps
|
||||||
|
|
||||||
|
### 1. Missing Laravel Features
|
||||||
|
```dart
|
||||||
|
// Documented but not implemented:
|
||||||
|
|
||||||
|
// 1. Job Middleware
|
||||||
|
class JobMiddleware {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> handle(Job job, Function next);
|
||||||
|
Future<void> withoutOverlapping(Job job, Function next);
|
||||||
|
Future<void> rateLimit(Job job, Function next, int maxAttempts);
|
||||||
|
Future<void> throttle(Job job, Function next, Duration duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Job Events
|
||||||
|
class JobEvents {
|
||||||
|
// Need to implement:
|
||||||
|
void beforeJob(Job job);
|
||||||
|
void afterJob(Job job);
|
||||||
|
void failingJob(Job job, Exception exception);
|
||||||
|
void failedJob(Job job, Exception exception);
|
||||||
|
void retryingJob(Job job);
|
||||||
|
void retriedJob(Job job);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Job Chaining
|
||||||
|
class JobChain {
|
||||||
|
// Need to implement:
|
||||||
|
void chain(List<Job> jobs);
|
||||||
|
void onConnection(String connection);
|
||||||
|
void onQueue(String queue);
|
||||||
|
void catch(Function(Exception) handler);
|
||||||
|
void finally(Function handler);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Queue Features
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// 1. Queue Monitoring
|
||||||
|
class QueueMonitor {
|
||||||
|
// Need to implement:
|
||||||
|
Future<Map<String, int>> queueSizes();
|
||||||
|
Future<List<Map<String, dynamic>>> failedJobs();
|
||||||
|
Future<void> retryFailedJob(String id);
|
||||||
|
Future<void> forgetFailedJob(String id);
|
||||||
|
Future<void> pruneFailedJobs([Duration? older]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Queue Rate Limiting
|
||||||
|
class QueueRateLimiter {
|
||||||
|
// Need to implement:
|
||||||
|
Future<bool> tooManyAttempts(String key, int maxAttempts);
|
||||||
|
Future<void> hit(String key, Duration decay);
|
||||||
|
Future<void> clear(String key);
|
||||||
|
Future<int> attempts(String key);
|
||||||
|
Future<int> remaining(String key, int maxAttempts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Queue Batching
|
||||||
|
class QueueBatch {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> then(Function handler);
|
||||||
|
Future<void> catch(Function(Exception) handler);
|
||||||
|
Future<void> finally(Function handler);
|
||||||
|
Future<void> allowFailures();
|
||||||
|
Future<void> onConnection(String connection);
|
||||||
|
Future<void> onQueue(String queue);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Worker Features
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// 1. Worker Management
|
||||||
|
class WorkerManager {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> scale(int processes);
|
||||||
|
Future<void> pause();
|
||||||
|
Future<void> resume();
|
||||||
|
Future<void> restart();
|
||||||
|
Future<List<WorkerStatus>> status();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Worker Events
|
||||||
|
class WorkerEvents {
|
||||||
|
// Need to implement:
|
||||||
|
void workerStarting();
|
||||||
|
void workerStopping();
|
||||||
|
void workerStopped();
|
||||||
|
void queueEmpty(String queue);
|
||||||
|
void looping();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Worker Options
|
||||||
|
class WorkerOptions {
|
||||||
|
// Need to implement:
|
||||||
|
Duration sleep;
|
||||||
|
Duration timeout;
|
||||||
|
int maxTries;
|
||||||
|
int maxJobs;
|
||||||
|
bool force;
|
||||||
|
bool stopWhenEmpty;
|
||||||
|
bool rest;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Gaps
|
||||||
|
|
||||||
|
### 1. Missing API Documentation
|
||||||
|
```dart
|
||||||
|
// Need to document:
|
||||||
|
|
||||||
|
/// Handles job middleware.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// class RateLimitedJob extends Job with JobMiddleware {
|
||||||
|
/// @override
|
||||||
|
/// Future<void> middleware(Function next) async {
|
||||||
|
/// return await rateLimit(5, Duration(minutes: 1), next);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
Future<void> middleware(Function next);
|
||||||
|
|
||||||
|
/// Handles job events.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// queue.beforeJob((job) {
|
||||||
|
/// print('Processing ${job.id}');
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
void beforeJob(Function(Job) callback);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Integration Examples
|
||||||
|
```dart
|
||||||
|
// Need examples for:
|
||||||
|
|
||||||
|
// 1. Job Chaining
|
||||||
|
var chain = queue.chain([
|
||||||
|
ProcessPodcast(podcast),
|
||||||
|
OptimizeAudio(podcast),
|
||||||
|
NotifySubscribers(podcast)
|
||||||
|
])
|
||||||
|
.onQueue('podcasts')
|
||||||
|
.catch((e) => handleError(e))
|
||||||
|
.finally(() => cleanup());
|
||||||
|
|
||||||
|
// 2. Queue Monitoring
|
||||||
|
var monitor = QueueMonitor(queue);
|
||||||
|
var sizes = await monitor.queueSizes();
|
||||||
|
print('Default queue size: ${sizes["default"]}');
|
||||||
|
|
||||||
|
// 3. Worker Management
|
||||||
|
var manager = WorkerManager(queue);
|
||||||
|
await manager.scale(4); // Scale to 4 processes
|
||||||
|
var status = await manager.status();
|
||||||
|
print('Active workers: ${status.length}');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Test Coverage
|
||||||
|
```dart
|
||||||
|
// Need tests for:
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Job Middleware', () {
|
||||||
|
test('applies rate limiting', () async {
|
||||||
|
var job = RateLimitedJob();
|
||||||
|
var limiter = MockRateLimiter();
|
||||||
|
|
||||||
|
await job.handle();
|
||||||
|
|
||||||
|
verify(() => limiter.tooManyAttempts(any, any)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Queue Monitoring', () {
|
||||||
|
test('monitors queue sizes', () async {
|
||||||
|
var monitor = QueueMonitor(queue);
|
||||||
|
var sizes = await monitor.queueSizes();
|
||||||
|
|
||||||
|
expect(sizes, containsPair('default', greaterThan(0)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **High Priority**
|
||||||
|
- Job middleware (Laravel compatibility)
|
||||||
|
- Job events (Laravel compatibility)
|
||||||
|
- Queue monitoring
|
||||||
|
|
||||||
|
2. **Medium Priority**
|
||||||
|
- Job chaining
|
||||||
|
- Queue rate limiting
|
||||||
|
- Worker management
|
||||||
|
|
||||||
|
3. **Low Priority**
|
||||||
|
- Additional worker features
|
||||||
|
- Additional monitoring features
|
||||||
|
- Performance optimizations
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Implementation Tasks**
|
||||||
|
- Add job middleware
|
||||||
|
- Add job events
|
||||||
|
- Add queue monitoring
|
||||||
|
- Add worker management
|
||||||
|
|
||||||
|
2. **Documentation Tasks**
|
||||||
|
- Document job middleware
|
||||||
|
- Document job events
|
||||||
|
- Document monitoring
|
||||||
|
- Add integration examples
|
||||||
|
|
||||||
|
3. **Testing Tasks**
|
||||||
|
- Add middleware tests
|
||||||
|
- Add event tests
|
||||||
|
- Add monitoring tests
|
||||||
|
- Add worker tests
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing queue features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Queue Package Specification](queue_package_specification.md)
|
||||||
|
6. Review [Events Package Specification](events_package_specification.md)
|
||||||
|
7. Review [Bus Package Specification](bus_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each queue feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in [Queue Package Specification](queue_package_specification.md)
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing queue features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Queue system must:
|
||||||
|
1. Handle high job throughput
|
||||||
|
2. Process chains efficiently
|
||||||
|
3. Support concurrent workers
|
||||||
|
4. Scale horizontally
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Queue tests must:
|
||||||
|
1. Cover all queue operations
|
||||||
|
2. Test middleware behavior
|
||||||
|
3. Verify event handling
|
||||||
|
4. Check worker management
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Queue documentation must:
|
||||||
|
1. Explain queue patterns
|
||||||
|
2. Show middleware examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
623
docs/queue_package_specification.md
Normal file
623
docs/queue_package_specification.md
Normal file
|
@ -0,0 +1,623 @@
|
||||||
|
# Queue Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Queue package provides a robust job queueing system that matches Laravel's queue functionality. It supports multiple queue drivers, job retries, rate limiting, and job batching while integrating with our Event and Bus packages.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for event integration
|
||||||
|
> - See [Bus Package Specification](bus_package_specification.md) for command bus integration
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Queue Manager
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core queue manager implementation
|
||||||
|
class QueueManager implements QueueContract {
|
||||||
|
/// Available queue connections
|
||||||
|
final Map<String, QueueConnection> _connections = {};
|
||||||
|
|
||||||
|
/// Default connection name
|
||||||
|
final String _defaultConnection;
|
||||||
|
|
||||||
|
/// Configuration repository
|
||||||
|
final ConfigContract _config;
|
||||||
|
|
||||||
|
QueueManager(this._config)
|
||||||
|
: _defaultConnection = _config.get('queue.default', 'sync');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> push(dynamic job, [String? queue]) async {
|
||||||
|
return await connection().push(job, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> later(Duration delay, dynamic job, [String? queue]) async {
|
||||||
|
return await connection().later(delay, job, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Job?> pop([String? queue]) async {
|
||||||
|
return await connection().pop(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
QueueConnection connection([String? name]) {
|
||||||
|
name ??= _defaultConnection;
|
||||||
|
|
||||||
|
return _connections.putIfAbsent(name, () {
|
||||||
|
var config = _getConfig(name!);
|
||||||
|
return _createConnection(config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a queue connection
|
||||||
|
QueueConnection _createConnection(Map<String, dynamic> config) {
|
||||||
|
switch (config['driver']) {
|
||||||
|
case 'sync':
|
||||||
|
return SyncConnection(config);
|
||||||
|
case 'database':
|
||||||
|
return DatabaseConnection(config);
|
||||||
|
case 'redis':
|
||||||
|
return RedisConnection(config);
|
||||||
|
case 'sqs':
|
||||||
|
return SqsConnection(config);
|
||||||
|
default:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'Unsupported queue driver: ${config["driver"]}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets connection config
|
||||||
|
Map<String, dynamic> _getConfig(String name) {
|
||||||
|
var config = _config.get<Map>('queue.connections.$name');
|
||||||
|
if (config == null) {
|
||||||
|
throw ArgumentError('Queue connection [$name] not configured.');
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Queue Connections
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Database queue connection
|
||||||
|
class DatabaseConnection implements QueueConnection {
|
||||||
|
/// Database connection
|
||||||
|
final DatabaseConnection _db;
|
||||||
|
|
||||||
|
/// Table name
|
||||||
|
final String _table;
|
||||||
|
|
||||||
|
DatabaseConnection(Map<String, dynamic> config)
|
||||||
|
: _db = DatabaseManager.connection(config['connection']),
|
||||||
|
_table = config['table'] ?? 'jobs';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> push(dynamic job, [String? queue]) async {
|
||||||
|
queue ??= 'default';
|
||||||
|
var id = Uuid().v4();
|
||||||
|
|
||||||
|
await _db.table(_table).insert({
|
||||||
|
'id': id,
|
||||||
|
'queue': queue,
|
||||||
|
'payload': _serialize(job),
|
||||||
|
'attempts': 0,
|
||||||
|
'reserved_at': null,
|
||||||
|
'available_at': DateTime.now(),
|
||||||
|
'created_at': DateTime.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> later(Duration delay, dynamic job, [String? queue]) async {
|
||||||
|
queue ??= 'default';
|
||||||
|
var id = Uuid().v4();
|
||||||
|
|
||||||
|
await _db.table(_table).insert({
|
||||||
|
'id': id,
|
||||||
|
'queue': queue,
|
||||||
|
'payload': _serialize(job),
|
||||||
|
'attempts': 0,
|
||||||
|
'reserved_at': null,
|
||||||
|
'available_at': DateTime.now().add(delay),
|
||||||
|
'created_at': DateTime.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Job?> pop([String? queue]) async {
|
||||||
|
queue ??= 'default';
|
||||||
|
|
||||||
|
var job = await _db.transaction((tx) async {
|
||||||
|
var job = await tx.table(_table)
|
||||||
|
.where('queue', queue)
|
||||||
|
.whereNull('reserved_at')
|
||||||
|
.where('available_at', '<=', DateTime.now())
|
||||||
|
.orderBy('id')
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (job != null) {
|
||||||
|
await tx.table(_table)
|
||||||
|
.where('id', job['id'])
|
||||||
|
.update({
|
||||||
|
'reserved_at': DateTime.now(),
|
||||||
|
'attempts': job['attempts'] + 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return job;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (job == null) return null;
|
||||||
|
|
||||||
|
return DatabaseJob(
|
||||||
|
connection: this,
|
||||||
|
queue: queue,
|
||||||
|
job: job
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializes job payload
|
||||||
|
String _serialize(dynamic job) {
|
||||||
|
return jsonEncode({
|
||||||
|
'type': job.runtimeType.toString(),
|
||||||
|
'data': job.toMap()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redis queue connection
|
||||||
|
class RedisConnection implements QueueConnection {
|
||||||
|
/// Redis client
|
||||||
|
final RedisClient _redis;
|
||||||
|
|
||||||
|
/// Key prefix
|
||||||
|
final String _prefix;
|
||||||
|
|
||||||
|
RedisConnection(Map<String, dynamic> config)
|
||||||
|
: _redis = RedisClient(
|
||||||
|
host: config['host'],
|
||||||
|
port: config['port'],
|
||||||
|
db: config['database']
|
||||||
|
),
|
||||||
|
_prefix = config['prefix'] ?? 'queues';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> push(dynamic job, [String? queue]) async {
|
||||||
|
queue ??= 'default';
|
||||||
|
var id = Uuid().v4();
|
||||||
|
|
||||||
|
await _redis.rpush(
|
||||||
|
_getKey(queue),
|
||||||
|
_serialize(id, job)
|
||||||
|
);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> later(Duration delay, dynamic job, [String? queue]) async {
|
||||||
|
queue ??= 'default';
|
||||||
|
var id = Uuid().v4();
|
||||||
|
|
||||||
|
await _redis.zadd(
|
||||||
|
_getDelayedKey(queue),
|
||||||
|
DateTime.now().add(delay).millisecondsSinceEpoch.toDouble(),
|
||||||
|
_serialize(id, job)
|
||||||
|
);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Job?> pop([String? queue]) async {
|
||||||
|
queue ??= 'default';
|
||||||
|
|
||||||
|
// Move delayed jobs
|
||||||
|
var now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
var jobs = await _redis.zrangebyscore(
|
||||||
|
_getDelayedKey(queue),
|
||||||
|
'-inf',
|
||||||
|
now.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var job in jobs) {
|
||||||
|
await _redis.rpush(_getKey(queue), job);
|
||||||
|
await _redis.zrem(_getDelayedKey(queue), job);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get next job
|
||||||
|
var payload = await _redis.lpop(_getKey(queue));
|
||||||
|
if (payload == null) return null;
|
||||||
|
|
||||||
|
return RedisJob(
|
||||||
|
connection: this,
|
||||||
|
queue: queue,
|
||||||
|
payload: payload
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets queue key
|
||||||
|
String _getKey(String queue) => '$_prefix:$queue';
|
||||||
|
|
||||||
|
/// Gets delayed queue key
|
||||||
|
String _getDelayedKey(String queue) => '$_prefix:$queue:delayed';
|
||||||
|
|
||||||
|
/// Serializes job payload
|
||||||
|
String _serialize(String id, dynamic job) {
|
||||||
|
return jsonEncode({
|
||||||
|
'id': id,
|
||||||
|
'type': job.runtimeType.toString(),
|
||||||
|
'data': job.toMap(),
|
||||||
|
'attempts': 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Queue Jobs
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core job interface
|
||||||
|
abstract class Job {
|
||||||
|
/// Job ID
|
||||||
|
String get id;
|
||||||
|
|
||||||
|
/// Job queue
|
||||||
|
String get queue;
|
||||||
|
|
||||||
|
/// Number of attempts
|
||||||
|
int get attempts;
|
||||||
|
|
||||||
|
/// Maximum tries
|
||||||
|
int get maxTries => 3;
|
||||||
|
|
||||||
|
/// Timeout in seconds
|
||||||
|
int get timeout => 60;
|
||||||
|
|
||||||
|
/// Executes the job
|
||||||
|
Future<void> handle();
|
||||||
|
|
||||||
|
/// Releases the job back onto queue
|
||||||
|
Future<void> release([Duration? delay]);
|
||||||
|
|
||||||
|
/// Deletes the job
|
||||||
|
Future<void> delete();
|
||||||
|
|
||||||
|
/// Fails the job
|
||||||
|
Future<void> fail([Exception? exception]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Database job implementation
|
||||||
|
class DatabaseJob implements Job {
|
||||||
|
/// Database connection
|
||||||
|
final DatabaseConnection _connection;
|
||||||
|
|
||||||
|
/// Job data
|
||||||
|
final Map<String, dynamic> _data;
|
||||||
|
|
||||||
|
/// Job queue
|
||||||
|
@override
|
||||||
|
final String queue;
|
||||||
|
|
||||||
|
DatabaseJob({
|
||||||
|
required DatabaseConnection connection,
|
||||||
|
required String queue,
|
||||||
|
required Map<String, dynamic> job
|
||||||
|
}) : _connection = connection,
|
||||||
|
_data = job,
|
||||||
|
queue = queue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id => _data['id'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get attempts => _data['attempts'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> handle() async {
|
||||||
|
var payload = jsonDecode(_data['payload']);
|
||||||
|
var job = _deserialize(payload);
|
||||||
|
await job.handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> release([Duration? delay]) async {
|
||||||
|
await _connection._db.table(_connection._table)
|
||||||
|
.where('id', id)
|
||||||
|
.update({
|
||||||
|
'reserved_at': null,
|
||||||
|
'available_at': delay != null
|
||||||
|
? DateTime.now().add(delay)
|
||||||
|
: DateTime.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete() async {
|
||||||
|
await _connection._db.table(_connection._table)
|
||||||
|
.where('id', id)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> fail([Exception? exception]) async {
|
||||||
|
await _connection._db.table(_connection._table)
|
||||||
|
.where('id', id)
|
||||||
|
.update({
|
||||||
|
'failed_at': DateTime.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exception != null) {
|
||||||
|
await _connection._db.table('failed_jobs').insert({
|
||||||
|
'id': Uuid().v4(),
|
||||||
|
'connection': _connection.name,
|
||||||
|
'queue': queue,
|
||||||
|
'payload': _data['payload'],
|
||||||
|
'exception': exception.toString(),
|
||||||
|
'failed_at': DateTime.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes job payload
|
||||||
|
dynamic _deserialize(Map<String, dynamic> payload) {
|
||||||
|
var type = payload['type'];
|
||||||
|
var data = payload['data'];
|
||||||
|
|
||||||
|
return _connection._container.make(type)
|
||||||
|
..fromMap(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Job Batching
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Job batch
|
||||||
|
class Batch {
|
||||||
|
/// Batch ID
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// Queue connection
|
||||||
|
final QueueConnection _connection;
|
||||||
|
|
||||||
|
/// Jobs in batch
|
||||||
|
final List<Job> _jobs;
|
||||||
|
|
||||||
|
/// Options
|
||||||
|
final BatchOptions _options;
|
||||||
|
|
||||||
|
Batch(this.id, this._connection, this._jobs, this._options);
|
||||||
|
|
||||||
|
/// Gets total jobs
|
||||||
|
int get totalJobs => _jobs.length;
|
||||||
|
|
||||||
|
/// Gets pending jobs
|
||||||
|
Future<int> get pendingJobs async {
|
||||||
|
return await _connection.table('job_batches')
|
||||||
|
.where('id', id)
|
||||||
|
.value('pending_jobs');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets failed jobs
|
||||||
|
Future<int> get failedJobs async {
|
||||||
|
return await _connection.table('job_batches')
|
||||||
|
.where('id', id)
|
||||||
|
.value('failed_jobs');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds jobs to batch
|
||||||
|
Future<void> add(List<Job> jobs) async {
|
||||||
|
_jobs.addAll(jobs);
|
||||||
|
|
||||||
|
await _connection.table('job_batches')
|
||||||
|
.where('id', id)
|
||||||
|
.increment('total_jobs', jobs.length)
|
||||||
|
.increment('pending_jobs', jobs.length);
|
||||||
|
|
||||||
|
for (var job in jobs) {
|
||||||
|
await _connection.push(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancels the batch
|
||||||
|
Future<void> cancel() async {
|
||||||
|
await _connection.table('job_batches')
|
||||||
|
.where('id', id)
|
||||||
|
.update({
|
||||||
|
'cancelled_at': DateTime.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes the batch
|
||||||
|
Future<void> delete() async {
|
||||||
|
await _connection.table('job_batches')
|
||||||
|
.where('id', id)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
### 1. Basic Queue Usage
|
||||||
|
```dart
|
||||||
|
// Define job
|
||||||
|
class ProcessPodcast implements Job {
|
||||||
|
final Podcast podcast;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> handle() async {
|
||||||
|
await podcast.process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push job to queue
|
||||||
|
await queue.push(ProcessPodcast(podcast));
|
||||||
|
|
||||||
|
// Push delayed job
|
||||||
|
await queue.later(
|
||||||
|
Duration(minutes: 10),
|
||||||
|
ProcessPodcast(podcast)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Job Batching
|
||||||
|
```dart
|
||||||
|
// Create batch
|
||||||
|
var batch = await queue.batch([
|
||||||
|
ProcessPodcast(podcast1),
|
||||||
|
ProcessPodcast(podcast2),
|
||||||
|
ProcessPodcast(podcast3)
|
||||||
|
])
|
||||||
|
.allowFailures()
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
// Add more jobs
|
||||||
|
await batch.add([
|
||||||
|
ProcessPodcast(podcast4),
|
||||||
|
ProcessPodcast(podcast5)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check progress
|
||||||
|
print('Pending: ${await batch.pendingJobs}');
|
||||||
|
print('Failed: ${await batch.failedJobs}');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Queue Worker
|
||||||
|
```dart
|
||||||
|
// Start worker
|
||||||
|
var worker = QueueWorker(connection)
|
||||||
|
..onJob((job) async {
|
||||||
|
print('Processing job ${job.id}');
|
||||||
|
})
|
||||||
|
..onException((job, exception) async {
|
||||||
|
print('Job ${job.id} failed: $exception');
|
||||||
|
});
|
||||||
|
|
||||||
|
await worker.daemon([
|
||||||
|
'default',
|
||||||
|
'emails',
|
||||||
|
'podcasts'
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Queue Manager', () {
|
||||||
|
test('pushes jobs to queue', () async {
|
||||||
|
var queue = QueueManager(config);
|
||||||
|
var job = ProcessPodcast(podcast);
|
||||||
|
|
||||||
|
var id = await queue.push(job);
|
||||||
|
|
||||||
|
expect(id, isNotEmpty);
|
||||||
|
verify(() => connection.push(job, null)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles delayed jobs', () async {
|
||||||
|
var queue = QueueManager(config);
|
||||||
|
var job = ProcessPodcast(podcast);
|
||||||
|
var delay = Duration(minutes: 5);
|
||||||
|
|
||||||
|
await queue.later(delay, job);
|
||||||
|
|
||||||
|
verify(() => connection.later(delay, job, null)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Job Batching', () {
|
||||||
|
test('processes job batches', () async {
|
||||||
|
var batch = await queue.batch([
|
||||||
|
ProcessPodcast(podcast1),
|
||||||
|
ProcessPodcast(podcast2)
|
||||||
|
]).dispatch();
|
||||||
|
|
||||||
|
expect(batch.totalJobs, equals(2));
|
||||||
|
expect(await batch.pendingJobs, equals(2));
|
||||||
|
expect(await batch.failedJobs, equals(0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement core queue features
|
||||||
|
2. Add queue connections
|
||||||
|
3. Add job batching
|
||||||
|
4. Add queue worker
|
||||||
|
5. Write tests
|
||||||
|
6. Add benchmarks
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing queue features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Events Package Specification](events_package_specification.md)
|
||||||
|
6. Review [Bus Package Specification](bus_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each queue feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Support event integration (see [Events Package Specification](events_package_specification.md))
|
||||||
|
5. Support bus integration (see [Bus Package Specification](bus_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing queue features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Queue system must:
|
||||||
|
1. Handle high job throughput
|
||||||
|
2. Process batches efficiently
|
||||||
|
3. Support concurrent workers
|
||||||
|
4. Scale horizontally
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Queue tests must:
|
||||||
|
1. Cover all queue operations
|
||||||
|
2. Test job processing
|
||||||
|
3. Verify batching
|
||||||
|
4. Check worker behavior
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Queue documentation must:
|
||||||
|
1. Explain queue patterns
|
||||||
|
2. Show job examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
295
docs/route_gap_analysis.md
Normal file
295
docs/route_gap_analysis.md
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
# Route Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our Route package's actual implementation and Laravel's routing functionality, identifying areas that need implementation or documentation updates.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Route Package Specification](route_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Pipeline Package Specification](pipeline_package_specification.md) for middleware pipeline
|
||||||
|
> - See [Container Package Specification](container_package_specification.md) for dependency injection
|
||||||
|
|
||||||
|
## Implementation Gaps
|
||||||
|
|
||||||
|
### 1. Missing Laravel Features
|
||||||
|
```dart
|
||||||
|
// Documented but not implemented:
|
||||||
|
|
||||||
|
// 1. Route Model Binding
|
||||||
|
class RouteModelBinding {
|
||||||
|
// Need to implement:
|
||||||
|
void bind(String key, Type type);
|
||||||
|
void bindWhere(String key, Type type, Function where);
|
||||||
|
void bindCallback(String key, Function callback);
|
||||||
|
void scopeBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Route Caching
|
||||||
|
class RouteCache {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> cache();
|
||||||
|
Future<void> clear();
|
||||||
|
bool isEnabled();
|
||||||
|
Future<void> reload();
|
||||||
|
Future<void> compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Route Fallbacks
|
||||||
|
class RouteFallback {
|
||||||
|
// Need to implement:
|
||||||
|
void fallback(dynamic action);
|
||||||
|
void missing(Function callback);
|
||||||
|
void methodNotAllowed(Function callback);
|
||||||
|
void notFound(Function callback);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Route Features
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// 1. Route Constraints
|
||||||
|
class RouteConstraints {
|
||||||
|
// Need to implement:
|
||||||
|
void pattern(String name, String pattern);
|
||||||
|
void patterns(Map<String, String> patterns);
|
||||||
|
bool matches(String name, String value);
|
||||||
|
void whereNumber(List<String> parameters);
|
||||||
|
void whereAlpha(List<String> parameters);
|
||||||
|
void whereAlphaNumeric(List<String> parameters);
|
||||||
|
void whereUuid(List<String> parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Route Substitutions
|
||||||
|
class RouteSubstitution {
|
||||||
|
// Need to implement:
|
||||||
|
void substitute(String key, dynamic value);
|
||||||
|
void substituteBindings(Map<String, dynamic> bindings);
|
||||||
|
void substituteImplicit(Map<String, Type> bindings);
|
||||||
|
String resolveBinding(String key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Route Rate Limiting
|
||||||
|
class RouteRateLimiting {
|
||||||
|
// Need to implement:
|
||||||
|
void throttle(String name, int maxAttempts, Duration decay);
|
||||||
|
void rateLimit(String name, int maxAttempts, Duration decay);
|
||||||
|
void forUser(String name, int maxAttempts, Duration decay);
|
||||||
|
void exempt(List<String> routes);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Group Features
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// 1. Group Attributes
|
||||||
|
class RouteGroupAttributes {
|
||||||
|
// Need to implement:
|
||||||
|
void controller(Type controller);
|
||||||
|
void namespace(String namespace);
|
||||||
|
void name(String name);
|
||||||
|
void domain(String domain);
|
||||||
|
void where(Map<String, String> patterns);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Group Middleware
|
||||||
|
class RouteGroupMiddleware {
|
||||||
|
// Need to implement:
|
||||||
|
void aliasMiddleware(String name, Type middleware);
|
||||||
|
void middlewarePriority(List<String> middleware);
|
||||||
|
void pushMiddlewareToGroup(String group, String middleware);
|
||||||
|
List<String> getMiddlewareGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Group Resources
|
||||||
|
class RouteGroupResources {
|
||||||
|
// Need to implement:
|
||||||
|
void resources(Map<String, Type> resources);
|
||||||
|
void apiResources(Map<String, Type> resources);
|
||||||
|
void singleton(String name, Type controller);
|
||||||
|
void apiSingleton(String name, Type controller);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Gaps
|
||||||
|
|
||||||
|
### 1. Missing API Documentation
|
||||||
|
```dart
|
||||||
|
// Need to document:
|
||||||
|
|
||||||
|
/// Binds route model.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// router.bind('user', User, (value) async {
|
||||||
|
/// return await User.find(value);
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
void bind(String key, Type type, [Function? callback]);
|
||||||
|
|
||||||
|
/// Defines route pattern.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// router.pattern('id', '[0-9]+');
|
||||||
|
/// router.get('users/{id}', UsersController)
|
||||||
|
/// .where('id', '[0-9]+');
|
||||||
|
/// ```
|
||||||
|
void pattern(String name, String pattern);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Integration Examples
|
||||||
|
```dart
|
||||||
|
// Need examples for:
|
||||||
|
|
||||||
|
// 1. Route Model Binding
|
||||||
|
router.bind('user', User);
|
||||||
|
|
||||||
|
router.get('users/{user}', (User user) {
|
||||||
|
return user; // Auto-resolved from ID
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Route Rate Limiting
|
||||||
|
router.middleware(['throttle:60,1'])
|
||||||
|
.group(() {
|
||||||
|
router.get('api/users', UsersController);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Route Resources
|
||||||
|
router.resources({
|
||||||
|
'photos': PhotoController,
|
||||||
|
'posts': PostController
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Test Coverage
|
||||||
|
```dart
|
||||||
|
// Need tests for:
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Route Model Binding', () {
|
||||||
|
test('resolves bound models', () async {
|
||||||
|
router.bind('user', User);
|
||||||
|
|
||||||
|
var response = await router.dispatch(
|
||||||
|
Request('GET', '/users/1')
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.data, isA<User>());
|
||||||
|
expect(response.data.id, equals('1'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Route Caching', () {
|
||||||
|
test('caches compiled routes', () async {
|
||||||
|
await router.cache();
|
||||||
|
|
||||||
|
var route = router.match(
|
||||||
|
Request('GET', '/users/1')
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(route, isNotNull);
|
||||||
|
expect(route!.compiled, isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **High Priority**
|
||||||
|
- Route model binding (Laravel compatibility)
|
||||||
|
- Route caching (Laravel compatibility)
|
||||||
|
- Route constraints
|
||||||
|
|
||||||
|
2. **Medium Priority**
|
||||||
|
- Route substitutions
|
||||||
|
- Route rate limiting
|
||||||
|
- Group resources
|
||||||
|
|
||||||
|
3. **Low Priority**
|
||||||
|
- Route fallbacks
|
||||||
|
- Additional constraints
|
||||||
|
- Performance optimizations
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Implementation Tasks**
|
||||||
|
- Add route model binding
|
||||||
|
- Add route caching
|
||||||
|
- Add route constraints
|
||||||
|
- Add group resources
|
||||||
|
|
||||||
|
2. **Documentation Tasks**
|
||||||
|
- Document model binding
|
||||||
|
- Document caching
|
||||||
|
- Document constraints
|
||||||
|
- Add integration examples
|
||||||
|
|
||||||
|
3. **Testing Tasks**
|
||||||
|
- Add binding tests
|
||||||
|
- Add caching tests
|
||||||
|
- Add constraint tests
|
||||||
|
- Add resource tests
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing routing features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Route Package Specification](route_package_specification.md)
|
||||||
|
6. Review [Pipeline Package Specification](pipeline_package_specification.md)
|
||||||
|
7. Review [Container Package Specification](container_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each routing feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in [Route Package Specification](route_package_specification.md)
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing routing features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Routing system must:
|
||||||
|
1. Match routes efficiently
|
||||||
|
2. Handle complex patterns
|
||||||
|
3. Support caching
|
||||||
|
4. Scale with route count
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Route tests must:
|
||||||
|
1. Cover all route types
|
||||||
|
2. Test pattern matching
|
||||||
|
3. Verify middleware
|
||||||
|
4. Check parameter binding
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Route documentation must:
|
||||||
|
1. Explain routing patterns
|
||||||
|
2. Show group examples
|
||||||
|
3. Cover parameter binding
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
550
docs/route_package_specification.md
Normal file
550
docs/route_package_specification.md
Normal file
|
@ -0,0 +1,550 @@
|
||||||
|
# Route Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Route package provides a robust routing system that matches Laravel's routing functionality. It supports route registration, middleware, parameter binding, and route groups while integrating with our Pipeline and Container packages.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Pipeline Package Specification](pipeline_package_specification.md) for middleware pipeline
|
||||||
|
> - See [Container Package Specification](container_package_specification.md) for dependency injection
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Router
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Core router implementation
|
||||||
|
class Router implements RouterContract {
|
||||||
|
/// Container instance
|
||||||
|
final Container _container;
|
||||||
|
|
||||||
|
/// Route collection
|
||||||
|
final RouteCollection _routes;
|
||||||
|
|
||||||
|
/// Current route
|
||||||
|
Route? _current;
|
||||||
|
|
||||||
|
/// Global middleware
|
||||||
|
final List<dynamic> _middleware = [];
|
||||||
|
|
||||||
|
Router(this._container)
|
||||||
|
: _routes = RouteCollection();
|
||||||
|
|
||||||
|
/// Gets current route
|
||||||
|
Route? get current => _current;
|
||||||
|
|
||||||
|
/// Gets global middleware
|
||||||
|
List<dynamic> get middleware => List.from(_middleware);
|
||||||
|
|
||||||
|
/// Adds global middleware
|
||||||
|
void pushMiddleware(dynamic middleware) {
|
||||||
|
_middleware.add(middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers GET route
|
||||||
|
Route get(String uri, dynamic action) {
|
||||||
|
return addRoute(['GET', 'HEAD'], uri, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers POST route
|
||||||
|
Route post(String uri, dynamic action) {
|
||||||
|
return addRoute(['POST'], uri, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers PUT route
|
||||||
|
Route put(String uri, dynamic action) {
|
||||||
|
return addRoute(['PUT'], uri, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers DELETE route
|
||||||
|
Route delete(String uri, dynamic action) {
|
||||||
|
return addRoute(['DELETE'], uri, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers PATCH route
|
||||||
|
Route patch(String uri, dynamic action) {
|
||||||
|
return addRoute(['PATCH'], uri, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers OPTIONS route
|
||||||
|
Route options(String uri, dynamic action) {
|
||||||
|
return addRoute(['OPTIONS'], uri, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds route to collection
|
||||||
|
Route addRoute(List<String> methods, String uri, dynamic action) {
|
||||||
|
var route = Route(methods, uri, action);
|
||||||
|
_routes.add(route);
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates route group
|
||||||
|
void group(Map<String, dynamic> attributes, Function callback) {
|
||||||
|
var group = RouteGroup(attributes);
|
||||||
|
|
||||||
|
_routes.pushGroup(group);
|
||||||
|
callback();
|
||||||
|
_routes.popGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches request to route
|
||||||
|
Route? match(Request request) {
|
||||||
|
_current = _routes.match(request);
|
||||||
|
return _current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches request to route
|
||||||
|
Future<Response> dispatch(Request request) async {
|
||||||
|
var route = match(request);
|
||||||
|
if (route == null) {
|
||||||
|
throw RouteNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _runRoute(route, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs route through middleware
|
||||||
|
Future<Response> _runRoute(Route route, Request request) async {
|
||||||
|
var pipeline = _container.make<MiddlewarePipeline>();
|
||||||
|
|
||||||
|
return await pipeline
|
||||||
|
.send(request)
|
||||||
|
.through([
|
||||||
|
..._middleware,
|
||||||
|
...route.gatherMiddleware()
|
||||||
|
])
|
||||||
|
.then((request) => route.run(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Route Collection
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Route collection
|
||||||
|
class RouteCollection {
|
||||||
|
/// Routes by method
|
||||||
|
final Map<String, List<Route>> _routes = {};
|
||||||
|
|
||||||
|
/// Route groups
|
||||||
|
final List<RouteGroup> _groups = [];
|
||||||
|
|
||||||
|
/// Adds route to collection
|
||||||
|
void add(Route route) {
|
||||||
|
for (var method in route.methods) {
|
||||||
|
_routes.putIfAbsent(method, () => []).add(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_groups.isNotEmpty) {
|
||||||
|
route.group = _groups.last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes route group
|
||||||
|
void pushGroup(RouteGroup group) {
|
||||||
|
if (_groups.isNotEmpty) {
|
||||||
|
group.parent = _groups.last;
|
||||||
|
}
|
||||||
|
_groups.add(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pops route group
|
||||||
|
void popGroup() {
|
||||||
|
_groups.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches request to route
|
||||||
|
Route? match(Request request) {
|
||||||
|
var routes = _routes[request.method] ?? [];
|
||||||
|
|
||||||
|
for (var route in routes) {
|
||||||
|
if (route.matches(request)) {
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Route
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Route definition
|
||||||
|
class Route {
|
||||||
|
/// HTTP methods
|
||||||
|
final List<String> methods;
|
||||||
|
|
||||||
|
/// URI pattern
|
||||||
|
final String uri;
|
||||||
|
|
||||||
|
/// Route action
|
||||||
|
final dynamic action;
|
||||||
|
|
||||||
|
/// Route group
|
||||||
|
RouteGroup? group;
|
||||||
|
|
||||||
|
/// Route middleware
|
||||||
|
final List<dynamic> _middleware = [];
|
||||||
|
|
||||||
|
/// Route parameters
|
||||||
|
final Map<String, dynamic> _parameters = {};
|
||||||
|
|
||||||
|
Route(this.methods, this.uri, this.action);
|
||||||
|
|
||||||
|
/// Adds middleware
|
||||||
|
Route middleware(List<dynamic> middleware) {
|
||||||
|
_middleware.addAll(middleware);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets route name
|
||||||
|
String? get name => _parameters['as'];
|
||||||
|
|
||||||
|
/// Sets route name
|
||||||
|
Route name(String name) {
|
||||||
|
_parameters['as'] = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets route domain
|
||||||
|
String? get domain => _parameters['domain'];
|
||||||
|
|
||||||
|
/// Sets route domain
|
||||||
|
Route domain(String domain) {
|
||||||
|
_parameters['domain'] = domain;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets route prefix
|
||||||
|
String get prefix {
|
||||||
|
var prefix = '';
|
||||||
|
var group = this.group;
|
||||||
|
|
||||||
|
while (group != null) {
|
||||||
|
if (group.prefix != null) {
|
||||||
|
prefix = '${group.prefix}/$prefix';
|
||||||
|
}
|
||||||
|
group = group.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix.isEmpty ? '' : prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets full URI
|
||||||
|
String get fullUri => '${prefix.isEmpty ? "" : "$prefix/"}$uri';
|
||||||
|
|
||||||
|
/// Gathers middleware
|
||||||
|
List<dynamic> gatherMiddleware() {
|
||||||
|
var middleware = [..._middleware];
|
||||||
|
var group = this.group;
|
||||||
|
|
||||||
|
while (group != null) {
|
||||||
|
middleware.addAll(group.middleware);
|
||||||
|
group = group.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return middleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches request
|
||||||
|
bool matches(Request request) {
|
||||||
|
return _matchesMethod(request.method) &&
|
||||||
|
_matchesUri(request.uri) &&
|
||||||
|
_matchesDomain(request.host);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches HTTP method
|
||||||
|
bool _matchesMethod(String method) {
|
||||||
|
return methods.contains(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches URI pattern
|
||||||
|
bool _matchesUri(Uri uri) {
|
||||||
|
var pattern = RegExp(_compilePattern());
|
||||||
|
return pattern.hasMatch(uri.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches domain pattern
|
||||||
|
bool _matchesDomain(String? host) {
|
||||||
|
if (domain == null) return true;
|
||||||
|
if (host == null) return false;
|
||||||
|
|
||||||
|
var pattern = RegExp(_compileDomainPattern());
|
||||||
|
return pattern.hasMatch(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compiles URI pattern
|
||||||
|
String _compilePattern() {
|
||||||
|
return fullUri
|
||||||
|
.replaceAll('/', '\\/')
|
||||||
|
.replaceAllMapped(
|
||||||
|
RegExp(r'{([^}]+)}'),
|
||||||
|
(match) => '(?<${match[1]}>[^/]+)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compiles domain pattern
|
||||||
|
String _compileDomainPattern() {
|
||||||
|
return domain!
|
||||||
|
.replaceAll('.', '\\.')
|
||||||
|
.replaceAllMapped(
|
||||||
|
RegExp(r'{([^}]+)}'),
|
||||||
|
(match) => '(?<${match[1]}>[^.]+)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs route action
|
||||||
|
Future<Response> run(Request request) async {
|
||||||
|
var action = _resolveAction();
|
||||||
|
var parameters = _resolveParameters(request);
|
||||||
|
|
||||||
|
if (action is Function) {
|
||||||
|
return await Function.apply(action, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action is Controller) {
|
||||||
|
return await action.callAction(
|
||||||
|
action.runtimeType.toString(),
|
||||||
|
parameters
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw RouteActionNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves route action
|
||||||
|
dynamic _resolveAction() {
|
||||||
|
if (action is String) {
|
||||||
|
var parts = action.split('@');
|
||||||
|
var controller = _container.make(parts[0]);
|
||||||
|
controller.method = parts[1];
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves route parameters
|
||||||
|
List<dynamic> _resolveParameters(Request request) {
|
||||||
|
var pattern = RegExp(_compilePattern());
|
||||||
|
var match = pattern.firstMatch(request.uri.path);
|
||||||
|
|
||||||
|
if (match == null) return [];
|
||||||
|
|
||||||
|
return match.groupNames.map((name) {
|
||||||
|
return _resolveParameter(name, match.namedGroup(name)!);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves route parameter
|
||||||
|
dynamic _resolveParameter(String name, String value) {
|
||||||
|
if (_parameters.containsKey(name)) {
|
||||||
|
return _parameters[name](value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Route Groups
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Route group
|
||||||
|
class RouteGroup {
|
||||||
|
/// Group attributes
|
||||||
|
final Map<String, dynamic> attributes;
|
||||||
|
|
||||||
|
/// Parent group
|
||||||
|
RouteGroup? parent;
|
||||||
|
|
||||||
|
RouteGroup(this.attributes);
|
||||||
|
|
||||||
|
/// Gets group prefix
|
||||||
|
String? get prefix => attributes['prefix'];
|
||||||
|
|
||||||
|
/// Gets group middleware
|
||||||
|
List<dynamic> get middleware => attributes['middleware'] ?? [];
|
||||||
|
|
||||||
|
/// Gets group domain
|
||||||
|
String? get domain => attributes['domain'];
|
||||||
|
|
||||||
|
/// Gets group name prefix
|
||||||
|
String? get namePrefix => attributes['as'];
|
||||||
|
|
||||||
|
/// Gets merged attributes
|
||||||
|
Map<String, dynamic> get mergedAttributes {
|
||||||
|
var merged = Map.from(attributes);
|
||||||
|
var parent = this.parent;
|
||||||
|
|
||||||
|
while (parent != null) {
|
||||||
|
for (var entry in parent.attributes.entries) {
|
||||||
|
if (!merged.containsKey(entry.key)) {
|
||||||
|
merged[entry.key] = entry.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
### 1. Basic Routing
|
||||||
|
```dart
|
||||||
|
// Register routes
|
||||||
|
router.get('/', HomeController);
|
||||||
|
router.post('/users', UsersController);
|
||||||
|
router.get('/users/{id}', (String id) {
|
||||||
|
return User.find(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Match and dispatch
|
||||||
|
var route = router.match(request);
|
||||||
|
var response = await router.dispatch(request);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Route Groups
|
||||||
|
```dart
|
||||||
|
router.group({
|
||||||
|
'prefix': 'api',
|
||||||
|
'middleware': ['auth'],
|
||||||
|
'namespace': 'Api'
|
||||||
|
}, () {
|
||||||
|
router.get('users', UsersController);
|
||||||
|
router.get('posts', PostsController);
|
||||||
|
|
||||||
|
router.group({
|
||||||
|
'prefix': 'admin',
|
||||||
|
'middleware': ['admin']
|
||||||
|
}, () {
|
||||||
|
router.get('stats', StatsController);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Route Parameters
|
||||||
|
```dart
|
||||||
|
// Required parameters
|
||||||
|
router.get('users/{id}', (String id) {
|
||||||
|
return User.find(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Optional parameters
|
||||||
|
router.get('posts/{id?}', (String? id) {
|
||||||
|
return id != null ? Post.find(id) : Post.all();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Regular expression constraints
|
||||||
|
router.get('users/{id}', UsersController)
|
||||||
|
.where('id', '[0-9]+');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Router', () {
|
||||||
|
test('matches routes', () {
|
||||||
|
var router = Router(container);
|
||||||
|
router.get('/users/{id}', UsersController);
|
||||||
|
|
||||||
|
var request = Request('GET', '/users/1');
|
||||||
|
var route = router.match(request);
|
||||||
|
|
||||||
|
expect(route, isNotNull);
|
||||||
|
expect(route!.action, equals(UsersController));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles route groups', () {
|
||||||
|
var router = Router(container);
|
||||||
|
|
||||||
|
router.group({
|
||||||
|
'prefix': 'api',
|
||||||
|
'middleware': ['auth']
|
||||||
|
}, () {
|
||||||
|
router.get('users', UsersController);
|
||||||
|
});
|
||||||
|
|
||||||
|
var route = router.match(Request('GET', '/api/users'));
|
||||||
|
expect(route, isNotNull);
|
||||||
|
expect(route!.gatherMiddleware(), contains('auth'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement core routing
|
||||||
|
2. Add route groups
|
||||||
|
3. Add route parameters
|
||||||
|
4. Add middleware support
|
||||||
|
5. Write tests
|
||||||
|
6. Add benchmarks
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing routing features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Pipeline Package Specification](pipeline_package_specification.md)
|
||||||
|
6. Review [Container Package Specification](container_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each routing feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Support middleware (see [Pipeline Package Specification](pipeline_package_specification.md))
|
||||||
|
5. Support dependency injection (see [Container Package Specification](container_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing routing features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Routing system must:
|
||||||
|
1. Match routes efficiently
|
||||||
|
2. Handle complex patterns
|
||||||
|
3. Support caching
|
||||||
|
4. Scale with route count
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Route tests must:
|
||||||
|
1. Cover all route types
|
||||||
|
2. Test pattern matching
|
||||||
|
3. Verify middleware
|
||||||
|
4. Check parameter binding
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Route documentation must:
|
||||||
|
1. Explain routing patterns
|
||||||
|
2. Show group examples
|
||||||
|
3. Cover parameter binding
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
436
docs/support_package_specification.md
Normal file
436
docs/support_package_specification.md
Normal file
|
@ -0,0 +1,436 @@
|
||||||
|
# Support Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Support package provides fundamental utilities, helper functions, and common abstractions used throughout the framework. It aims to match Laravel's Support package functionality while leveraging Dart's strengths.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Contracts Package Specification](contracts_package_specification.md) for support contracts
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Collections
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Provides Laravel-like collection operations
|
||||||
|
class Collection<T> {
|
||||||
|
final List<T> _items;
|
||||||
|
|
||||||
|
Collection(this._items);
|
||||||
|
|
||||||
|
/// Creates a collection from an iterable
|
||||||
|
factory Collection.from(Iterable<T> items) {
|
||||||
|
return Collection(items.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps items while maintaining collection type
|
||||||
|
Collection<R> map<R>(R Function(T) callback) {
|
||||||
|
return Collection(_items.map(callback).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filters items
|
||||||
|
Collection<T> where(bool Function(T) test) {
|
||||||
|
return Collection(_items.where(test).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reduces collection to single value
|
||||||
|
R reduce<R>(R Function(R, T) callback, R initial) {
|
||||||
|
return _items.fold(initial, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Groups items by key
|
||||||
|
Map<K, List<T>> groupBy<K>(K Function(T) keySelector) {
|
||||||
|
return _items.fold({}, (map, item) {
|
||||||
|
var key = keySelector(item);
|
||||||
|
map.putIfAbsent(key, () => []).add(item);
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. String Manipulation
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Provides Laravel-like string manipulation
|
||||||
|
extension StringHelpers on String {
|
||||||
|
/// Converts string to camelCase
|
||||||
|
String camelCase() {
|
||||||
|
var words = split(RegExp(r'[\s_-]+'));
|
||||||
|
return words.first +
|
||||||
|
words.skip(1)
|
||||||
|
.map((w) => w[0].toUpperCase() + w.substring(1))
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts string to snake_case
|
||||||
|
String snakeCase() {
|
||||||
|
return replaceAllMapped(
|
||||||
|
RegExp(r'[A-Z]'),
|
||||||
|
(m) => '_${m[0]!.toLowerCase()}'
|
||||||
|
).replaceAll(RegExp(r'^_'), '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts string to kebab-case
|
||||||
|
String kebabCase() {
|
||||||
|
return snakeCase().replaceAll('_', '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts string to StudlyCase
|
||||||
|
String studlyCase() {
|
||||||
|
return split(RegExp(r'[\s_-]+'))
|
||||||
|
.map((w) => w[0].toUpperCase() + w.substring(1))
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Array/List Helpers
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Provides Laravel-like array manipulation
|
||||||
|
extension ArrayHelpers<T> on List<T> {
|
||||||
|
/// Gets first item matching predicate
|
||||||
|
T? firstWhere(bool Function(T) test, {T? orElse()}) {
|
||||||
|
try {
|
||||||
|
return super.firstWhere(test);
|
||||||
|
} catch (e) {
|
||||||
|
return orElse?.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plucks single field from list of maps
|
||||||
|
List<V> pluck<V>(String key) {
|
||||||
|
return map((item) =>
|
||||||
|
(item as Map<String, dynamic>)[key] as V
|
||||||
|
).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Groups items by key
|
||||||
|
Map<K, List<T>> groupBy<K>(K Function(T) keySelector) {
|
||||||
|
return fold({}, (map, item) {
|
||||||
|
var key = keySelector(item);
|
||||||
|
map.putIfAbsent(key, () => []).add(item);
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Service Provider Support
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Base class for service providers
|
||||||
|
abstract class ServiceProvider {
|
||||||
|
/// The container instance
|
||||||
|
late final Container container;
|
||||||
|
|
||||||
|
/// Register bindings with the container
|
||||||
|
void register();
|
||||||
|
|
||||||
|
/// Bootstrap any application services
|
||||||
|
void boot() {}
|
||||||
|
|
||||||
|
/// Determines if provider is deferred
|
||||||
|
bool get isDeferred => false;
|
||||||
|
|
||||||
|
/// Gets services provided
|
||||||
|
List<Type> get provides => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks a provider as deferred
|
||||||
|
abstract class DeferredServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
bool get isDeferred => true;
|
||||||
|
|
||||||
|
/// Gets events that trigger loading
|
||||||
|
List<String> get when => [];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Fluent Interface
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Provides fluent interface building
|
||||||
|
class Fluent {
|
||||||
|
final Map<String, dynamic> _attributes;
|
||||||
|
|
||||||
|
Fluent([Map<String, dynamic>? attributes])
|
||||||
|
: _attributes = attributes ?? {};
|
||||||
|
|
||||||
|
/// Gets attribute value
|
||||||
|
T? get<T>(String key) => _attributes[key] as T?;
|
||||||
|
|
||||||
|
/// Sets attribute value
|
||||||
|
Fluent set(String key, dynamic value) {
|
||||||
|
_attributes[key] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets all attributes
|
||||||
|
Map<String, dynamic> toMap() => Map.from(_attributes);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Optional Type
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Provides Laravel-like Optional type
|
||||||
|
class Optional<T> {
|
||||||
|
final T? _value;
|
||||||
|
|
||||||
|
const Optional(this._value);
|
||||||
|
|
||||||
|
/// Creates Optional from nullable value
|
||||||
|
factory Optional.of(T? value) => Optional(value);
|
||||||
|
|
||||||
|
/// Gets value or default
|
||||||
|
T get(T defaultValue) => _value ?? defaultValue;
|
||||||
|
|
||||||
|
/// Maps value if present
|
||||||
|
Optional<R> map<R>(R Function(T) mapper) {
|
||||||
|
return Optional(_value == null ? null : mapper(_value!));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if value is present
|
||||||
|
bool get isPresent => _value != null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. High Order Message Proxies
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Provides Laravel-like high order messaging
|
||||||
|
class HigherOrderProxy<T> {
|
||||||
|
final T _target;
|
||||||
|
|
||||||
|
HigherOrderProxy(this._target);
|
||||||
|
|
||||||
|
/// Invokes method on target
|
||||||
|
R call<R>(String method, [List<dynamic>? args]) {
|
||||||
|
return Function.apply(
|
||||||
|
_target.runtimeType.getMethod(method),
|
||||||
|
args ?? []
|
||||||
|
) as R;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with Container
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Register support services
|
||||||
|
class SupportServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
// Register collection factory
|
||||||
|
container.bind<CollectionFactory>((c) => CollectionFactory());
|
||||||
|
|
||||||
|
// Register string helpers
|
||||||
|
container.bind<StringHelpers>((c) => StringHelpers());
|
||||||
|
|
||||||
|
// Register array helpers
|
||||||
|
container.bind<ArrayHelpers>((c) => ArrayHelpers());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Collections
|
||||||
|
```dart
|
||||||
|
// Create collection
|
||||||
|
var collection = Collection([1, 2, 3, 4, 5]);
|
||||||
|
|
||||||
|
// Chain operations
|
||||||
|
var result = collection
|
||||||
|
.where((n) => n.isEven)
|
||||||
|
.map((n) => n * 2)
|
||||||
|
.reduce((sum, n) => sum + n, 0);
|
||||||
|
|
||||||
|
// Group items
|
||||||
|
var users = Collection([
|
||||||
|
User('John', 'Admin'),
|
||||||
|
User('Jane', 'User'),
|
||||||
|
User('Bob', 'Admin')
|
||||||
|
]);
|
||||||
|
|
||||||
|
var byRole = users.groupBy((u) => u.role);
|
||||||
|
```
|
||||||
|
|
||||||
|
### String Helpers
|
||||||
|
```dart
|
||||||
|
// Convert cases
|
||||||
|
'user_name'.camelCase(); // userName
|
||||||
|
'userName'.snakeCase(); // user_name
|
||||||
|
'user name'.studlyCase(); // UserName
|
||||||
|
'UserName'.kebabCase(); // user-name
|
||||||
|
|
||||||
|
// Other operations
|
||||||
|
'hello'.padLeft(10); // ' hello'
|
||||||
|
'HELLO'.toLowerCase(); // 'hello'
|
||||||
|
' text '.trim(); // 'text'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Providers
|
||||||
|
```dart
|
||||||
|
class UserServiceProvider extends ServiceProvider {
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
container.bind<UserRepository>((c) => UserRepositoryImpl());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void boot() {
|
||||||
|
var repo = container.make<UserRepository>();
|
||||||
|
repo.initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CacheServiceProvider extends DeferredServiceProvider {
|
||||||
|
@override
|
||||||
|
List<String> get when => ['cache.needed'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Type> get provides => [CacheManager];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void register() {
|
||||||
|
container.bind<CacheManager>((c) => CacheManagerImpl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Collection Tests', () {
|
||||||
|
test('should map values', () {
|
||||||
|
var collection = Collection([1, 2, 3]);
|
||||||
|
var result = collection.map((n) => n * 2);
|
||||||
|
expect(result.toList(), equals([2, 4, 6]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should filter values', () {
|
||||||
|
var collection = Collection([1, 2, 3, 4]);
|
||||||
|
var result = collection.where((n) => n.isEven);
|
||||||
|
expect(result.toList(), equals([2, 4]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('String Helper Tests', () {
|
||||||
|
test('should convert to camelCase', () {
|
||||||
|
expect('user_name'.camelCase(), equals('userName'));
|
||||||
|
expect('first name'.camelCase(), equals('firstName'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should convert to snakeCase', () {
|
||||||
|
expect('userName'.snakeCase(), equals('user_name'));
|
||||||
|
expect('FirstName'.snakeCase(), equals('first_name'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
1. **Collection Operations**
|
||||||
|
```dart
|
||||||
|
// Use lazy evaluation when possible
|
||||||
|
collection
|
||||||
|
.where((n) => n.isEven) // Lazy
|
||||||
|
.map((n) => n * 2) // Lazy
|
||||||
|
.toList(); // Eager
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **String Manipulations**
|
||||||
|
```dart
|
||||||
|
// Cache regex patterns
|
||||||
|
final _camelCasePattern = RegExp(r'[\s_-]+');
|
||||||
|
final _snakeCasePattern = RegExp(r'[A-Z]');
|
||||||
|
|
||||||
|
// Use StringBuffer for concatenation
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
for (var word in words) {
|
||||||
|
buffer.write(word);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Service Provider Loading**
|
||||||
|
```dart
|
||||||
|
// Defer provider loading when possible
|
||||||
|
class HeavyServiceProvider extends DeferredServiceProvider {
|
||||||
|
@override
|
||||||
|
List<String> get when => ['heavy.needed'];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement core features
|
||||||
|
2. Add comprehensive tests
|
||||||
|
3. Create integration examples
|
||||||
|
4. Add performance benchmarks
|
||||||
|
|
||||||
|
Would you like me to continue with documentation for the Pipeline or Contracts package?
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing support features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Understand [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each support feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Implement required contracts (see [Contracts Package Specification](contracts_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing support features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
5. Implement all contracts from [Contracts Package Specification](contracts_package_specification.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Support utilities must:
|
||||||
|
1. Handle large collections efficiently
|
||||||
|
2. Optimize string operations
|
||||||
|
3. Minimize memory allocations
|
||||||
|
4. Support async operations where appropriate
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Support tests must:
|
||||||
|
1. Cover all utility functions
|
||||||
|
2. Test edge cases
|
||||||
|
3. Verify error handling
|
||||||
|
4. Check performance characteristics
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Support documentation must:
|
||||||
|
1. Explain utility patterns
|
||||||
|
2. Show usage examples
|
||||||
|
3. Cover error handling
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
|
@ -1,10 +0,0 @@
|
||||||
# Performance Testing
|
|
||||||
|
|
||||||
The performance test can be run with the following tools.
|
|
||||||
|
|
||||||
## WRT
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wrk -t12 -c400 -d30s http://localhost:8080/query?queries=20
|
|
||||||
```
|
|
||||||
This runs a benchmark for 30 seconds, using 12 threads, and keeping 400 HTTP connections open.
|
|
312
docs/testing_gap_analysis.md
Normal file
312
docs/testing_gap_analysis.md
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
# Testing Package Gap Analysis
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes the gaps between our Testing package's actual implementation and Laravel's testing functionality, identifying areas that need implementation or documentation updates.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Testing Package Specification](testing_package_specification.md) for current implementation
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for overall status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Container Package Specification](container_package_specification.md) for dependency injection
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for event testing
|
||||||
|
|
||||||
|
## Implementation Gaps
|
||||||
|
|
||||||
|
### 1. Missing Laravel Features
|
||||||
|
```dart
|
||||||
|
// Documented but not implemented:
|
||||||
|
|
||||||
|
// 1. Browser Testing
|
||||||
|
class BrowserTest {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> browse(Function(Browser) callback);
|
||||||
|
Future<void> visit(String page);
|
||||||
|
Future<void> click(String text);
|
||||||
|
Future<void> type(String field, String value);
|
||||||
|
Future<void> press(String button);
|
||||||
|
Future<void> assertSee(String text);
|
||||||
|
Future<void> assertPathIs(String path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Parallel Testing
|
||||||
|
class ParallelTesting {
|
||||||
|
// Need to implement:
|
||||||
|
void setToken(String token);
|
||||||
|
void setProcesses(int count);
|
||||||
|
Future<void> runInParallel();
|
||||||
|
Future<void> withoutOverlapping(String key);
|
||||||
|
Future<void> isolateDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Time Testing
|
||||||
|
class TimeTesting {
|
||||||
|
// Need to implement:
|
||||||
|
void travel(Duration duration);
|
||||||
|
void freeze(DateTime time);
|
||||||
|
void resume();
|
||||||
|
void setTestNow(DateTime time);
|
||||||
|
DateTime now();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Test Features
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// 1. Test Data Factories
|
||||||
|
class TestDataFactory<T> {
|
||||||
|
// Need to implement:
|
||||||
|
T define(Map<String, dynamic> attributes);
|
||||||
|
T make([Map<String, dynamic>? attributes]);
|
||||||
|
Future<T> create([Map<String, dynamic>? attributes]);
|
||||||
|
List<T> makeMany(int count, [Map<String, dynamic>? attributes]);
|
||||||
|
Future<List<T>> createMany(int count, [Map<String, dynamic>? attributes]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Test Doubles
|
||||||
|
class TestDoubles {
|
||||||
|
// Need to implement:
|
||||||
|
dynamic spy(dynamic target);
|
||||||
|
dynamic mock(Type type);
|
||||||
|
dynamic fake(Type type);
|
||||||
|
dynamic partial(Type type);
|
||||||
|
void verifyNever(Function invocation);
|
||||||
|
void verifyOnce(Function invocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Test Database
|
||||||
|
class TestDatabase {
|
||||||
|
// Need to implement:
|
||||||
|
Future<void> beginTransaction();
|
||||||
|
Future<void> rollback();
|
||||||
|
Future<void> refresh();
|
||||||
|
Future<void> seed(String class);
|
||||||
|
Future<void> truncate(List<String> tables);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Assertion Features
|
||||||
|
```dart
|
||||||
|
// Need to implement:
|
||||||
|
|
||||||
|
// 1. Collection Assertions
|
||||||
|
class CollectionAssertions {
|
||||||
|
// Need to implement:
|
||||||
|
void assertCount(int count);
|
||||||
|
void assertEmpty();
|
||||||
|
void assertContains(dynamic item);
|
||||||
|
void assertDoesntContain(dynamic item);
|
||||||
|
void assertHasKey(String key);
|
||||||
|
void assertHasValue(dynamic value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Response Assertions
|
||||||
|
class ResponseAssertions {
|
||||||
|
// Need to implement:
|
||||||
|
void assertViewIs(String name);
|
||||||
|
void assertViewHas(String key, [dynamic value]);
|
||||||
|
void assertViewMissing(String key);
|
||||||
|
void assertSessionHas(String key, [dynamic value]);
|
||||||
|
void assertSessionMissing(String key);
|
||||||
|
void assertCookie(String name, [String? value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Exception Assertions
|
||||||
|
class ExceptionAssertions {
|
||||||
|
// Need to implement:
|
||||||
|
void assertThrows<T>(Function callback);
|
||||||
|
void assertDoesntThrow(Function callback);
|
||||||
|
void assertThrowsMessage(Type type, String message);
|
||||||
|
void assertThrowsIf(bool condition, Function callback);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation Gaps
|
||||||
|
|
||||||
|
### 1. Missing API Documentation
|
||||||
|
```dart
|
||||||
|
// Need to document:
|
||||||
|
|
||||||
|
/// Runs browser test.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// await browse((browser) async {
|
||||||
|
/// await browser.visit('/login');
|
||||||
|
/// await browser.type('email', 'user@example.com');
|
||||||
|
/// await browser.press('Login');
|
||||||
|
/// await browser.assertPathIs('/dashboard');
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
Future<void> browse(Function(Browser) callback);
|
||||||
|
|
||||||
|
/// Creates test data factory.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// class UserFactory extends Factory<User> {
|
||||||
|
/// @override
|
||||||
|
/// User define() {
|
||||||
|
/// return User()
|
||||||
|
/// ..name = faker.person.name()
|
||||||
|
/// ..email = faker.internet.email();
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
abstract class Factory<T>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Missing Integration Examples
|
||||||
|
```dart
|
||||||
|
// Need examples for:
|
||||||
|
|
||||||
|
// 1. Parallel Testing
|
||||||
|
await test.parallel((runner) {
|
||||||
|
runner.setProcesses(4);
|
||||||
|
runner.isolateDatabase();
|
||||||
|
await runner.run();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Time Testing
|
||||||
|
await test.freeze(DateTime(2024, 1, 1), () async {
|
||||||
|
await processScheduledJobs();
|
||||||
|
await test.travel(Duration(days: 1));
|
||||||
|
await verifyJobsCompleted();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Test Doubles
|
||||||
|
var mock = test.mock(PaymentGateway);
|
||||||
|
when(mock.charge(any)).thenReturn(true);
|
||||||
|
|
||||||
|
await processPayment(mock);
|
||||||
|
verify(mock.charge(any)).called(1);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Missing Test Coverage
|
||||||
|
```dart
|
||||||
|
// Need tests for:
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Browser Testing', () {
|
||||||
|
test('interacts with browser', () async {
|
||||||
|
await browse((browser) async {
|
||||||
|
await browser.visit('/login');
|
||||||
|
await browser.type('email', 'test@example.com');
|
||||||
|
await browser.type('password', 'password');
|
||||||
|
await browser.press('Login');
|
||||||
|
|
||||||
|
await browser.assertPathIs('/dashboard');
|
||||||
|
await browser.assertSee('Welcome');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Test Factories', () {
|
||||||
|
test('creates test data', () async {
|
||||||
|
var users = await UserFactory()
|
||||||
|
.count(3)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
expect(users, hasLength(3));
|
||||||
|
expect(users.first.email, contains('@'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **High Priority**
|
||||||
|
- Browser testing (Laravel compatibility)
|
||||||
|
- Parallel testing (Laravel compatibility)
|
||||||
|
- Test data factories
|
||||||
|
|
||||||
|
2. **Medium Priority**
|
||||||
|
- Test doubles
|
||||||
|
- Time testing
|
||||||
|
- Test database features
|
||||||
|
|
||||||
|
3. **Low Priority**
|
||||||
|
- Additional assertions
|
||||||
|
- Additional test helpers
|
||||||
|
- Performance optimizations
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Implementation Tasks**
|
||||||
|
- Add browser testing
|
||||||
|
- Add parallel testing
|
||||||
|
- Add test factories
|
||||||
|
- Add test doubles
|
||||||
|
|
||||||
|
2. **Documentation Tasks**
|
||||||
|
- Document browser testing
|
||||||
|
- Document parallel testing
|
||||||
|
- Document factories
|
||||||
|
- Add integration examples
|
||||||
|
|
||||||
|
3. **Testing Tasks**
|
||||||
|
- Add browser tests
|
||||||
|
- Add parallel tests
|
||||||
|
- Add factory tests
|
||||||
|
- Add double tests
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing testing features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Testing Package Specification](testing_package_specification.md)
|
||||||
|
6. Review [Container Package Specification](container_package_specification.md)
|
||||||
|
7. Review [Events Package Specification](events_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each testing feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Match specifications in [Testing Package Specification](testing_package_specification.md)
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing testing features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Testing system must:
|
||||||
|
1. Execute tests efficiently
|
||||||
|
2. Support parallel testing
|
||||||
|
3. Handle large test suites
|
||||||
|
4. Manage test isolation
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Testing package tests must:
|
||||||
|
1. Cover all testing features
|
||||||
|
2. Test browser interactions
|
||||||
|
3. Verify parallel execution
|
||||||
|
4. Check test factories
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Testing documentation must:
|
||||||
|
1. Explain testing patterns
|
||||||
|
2. Show browser examples
|
||||||
|
3. Cover parallel testing
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
343
docs/testing_guide.md
Normal file
343
docs/testing_guide.md
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
# Testing Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide outlines our testing approach, which follows Laravel's testing patterns while leveraging Dart's testing capabilities. It covers unit testing, integration testing, performance testing, and Laravel-style testing approaches.
|
||||||
|
|
||||||
|
## Test Types
|
||||||
|
|
||||||
|
### 1. Unit Tests
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Service Tests', () {
|
||||||
|
late Container container;
|
||||||
|
late UserService service;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container(reflector);
|
||||||
|
container.bind<Database>((c) => MockDatabase());
|
||||||
|
service = container.make<UserService>();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creates user', () async {
|
||||||
|
var user = await service.create({
|
||||||
|
'name': 'John Doe',
|
||||||
|
'email': 'john@example.com'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(user.name, equals('John Doe'));
|
||||||
|
expect(user.email, equals('john@example.com'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('validates user data', () {
|
||||||
|
expect(
|
||||||
|
() => service.create({'name': 'John Doe'}),
|
||||||
|
throwsA(isA<ValidationException>())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Integration Tests
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('API Integration', () {
|
||||||
|
late Application app;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = await createApplication();
|
||||||
|
await app.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await app.shutdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creates user through API', () async {
|
||||||
|
var response = await app.post('/users', body: {
|
||||||
|
'name': 'John Doe',
|
||||||
|
'email': 'john@example.com'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode, equals(201));
|
||||||
|
expect(response.json['name'], equals('John Doe'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles validation errors', () async {
|
||||||
|
var response = await app.post('/users', body: {
|
||||||
|
'name': 'John Doe'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.statusCode, equals(422));
|
||||||
|
expect(response.json['errors'], contains('email'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Performance Tests
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Performance Tests', () {
|
||||||
|
late Application app;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = await createApplication();
|
||||||
|
await app.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles concurrent requests', () async {
|
||||||
|
var stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
|
// Create 100 concurrent requests
|
||||||
|
var futures = List.generate(100, (i) =>
|
||||||
|
app.get('/users')
|
||||||
|
);
|
||||||
|
|
||||||
|
var responses = await Future.wait(futures);
|
||||||
|
stopwatch.stop();
|
||||||
|
|
||||||
|
// Verify responses
|
||||||
|
expect(responses, everyElement(
|
||||||
|
predicate((r) => r.statusCode == 200)
|
||||||
|
));
|
||||||
|
|
||||||
|
// Check performance
|
||||||
|
expect(
|
||||||
|
stopwatch.elapsedMilliseconds / responses.length,
|
||||||
|
lessThan(100) // Less than 100ms per request
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles database operations efficiently', () async {
|
||||||
|
var stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
|
// Create 1000 records
|
||||||
|
for (var i = 0; i < 1000; i++) {
|
||||||
|
await app.post('/users', body: {
|
||||||
|
'name': 'User $i',
|
||||||
|
'email': 'user$i@example.com'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stopwatch.stop();
|
||||||
|
|
||||||
|
// Check performance
|
||||||
|
expect(
|
||||||
|
stopwatch.elapsedMilliseconds / 1000,
|
||||||
|
lessThan(50) // Less than 50ms per operation
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Laravel-Style Feature Tests
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('Feature Tests', () {
|
||||||
|
late TestCase test;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
test = await TestCase.make();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user can register', () async {
|
||||||
|
await test
|
||||||
|
.post('/register', {
|
||||||
|
'name': 'John Doe',
|
||||||
|
'email': 'john@example.com',
|
||||||
|
'password': 'password',
|
||||||
|
'password_confirmation': 'password'
|
||||||
|
})
|
||||||
|
.assertStatus(302)
|
||||||
|
.assertRedirect('/home');
|
||||||
|
|
||||||
|
test.assertDatabaseHas('users', {
|
||||||
|
'email': 'john@example.com'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user can login', () async {
|
||||||
|
// Create user
|
||||||
|
await test.createUser({
|
||||||
|
'email': 'john@example.com',
|
||||||
|
'password': 'password'
|
||||||
|
});
|
||||||
|
|
||||||
|
await test
|
||||||
|
.post('/login', {
|
||||||
|
'email': 'john@example.com',
|
||||||
|
'password': 'password'
|
||||||
|
})
|
||||||
|
.assertAuthenticated();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Testing Tools
|
||||||
|
|
||||||
|
### 1. WRK Benchmarking
|
||||||
|
```bash
|
||||||
|
# Basic load test
|
||||||
|
wrk -t12 -c400 -d30s http://localhost:8080/api/endpoint
|
||||||
|
|
||||||
|
# Test with custom script
|
||||||
|
wrk -t12 -c400 -d30s -s script.lua http://localhost:8080/api/endpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Custom Load Testing
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
test('load test', () async {
|
||||||
|
var client = HttpClient();
|
||||||
|
var stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
|
// Configure test
|
||||||
|
var duration = Duration(minutes: 1);
|
||||||
|
var concurrency = 100;
|
||||||
|
var results = <Duration>[];
|
||||||
|
|
||||||
|
// Run test
|
||||||
|
while (stopwatch.elapsed < duration) {
|
||||||
|
var requests = List.generate(concurrency, (i) async {
|
||||||
|
var requestWatch = Stopwatch()..start();
|
||||||
|
await client.get('localhost', 8080, '/api/endpoint');
|
||||||
|
requestWatch.stop();
|
||||||
|
results.add(requestWatch.elapsed);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future.wait(requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze results
|
||||||
|
var average = results.reduce((a, b) => a + b) ~/ results.length;
|
||||||
|
var sorted = List.of(results)..sort();
|
||||||
|
var p95 = sorted[(sorted.length * 0.95).floor()];
|
||||||
|
var p99 = sorted[(sorted.length * 0.99).floor()];
|
||||||
|
|
||||||
|
print('Results:');
|
||||||
|
print('Average: ${average.inMilliseconds}ms');
|
||||||
|
print('P95: ${p95.inMilliseconds}ms');
|
||||||
|
print('P99: ${p99.inMilliseconds}ms');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Test Organization
|
||||||
|
```dart
|
||||||
|
// Group related tests
|
||||||
|
group('UserService', () {
|
||||||
|
group('creation', () {
|
||||||
|
test('creates valid user', () {});
|
||||||
|
test('validates input', () {});
|
||||||
|
test('handles duplicates', () {});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('authentication', () {
|
||||||
|
test('authenticates valid credentials', () {});
|
||||||
|
test('rejects invalid credentials', () {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Test Data Management
|
||||||
|
```dart
|
||||||
|
class TestCase {
|
||||||
|
// Create test data
|
||||||
|
Future<User> createUser([Map<String, dynamic>? attributes]) async {
|
||||||
|
return factory.create(User, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up after tests
|
||||||
|
Future<void> cleanup() async {
|
||||||
|
await database.truncate(['users', 'posts', 'comments']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Assertions
|
||||||
|
```dart
|
||||||
|
// Use descriptive assertions
|
||||||
|
expect(user.name, equals('John Doe'),
|
||||||
|
reason: 'User name should match input');
|
||||||
|
|
||||||
|
expect(response.statusCode,
|
||||||
|
isIn([200, 201]),
|
||||||
|
reason: 'Response should indicate success'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => service.validateEmail('invalid'),
|
||||||
|
throwsA(isA<ValidationException>()),
|
||||||
|
reason: 'Should reject invalid email'
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Benchmarks
|
||||||
|
|
||||||
|
### 1. Response Time Targets
|
||||||
|
```yaml
|
||||||
|
API Endpoints:
|
||||||
|
- Average: < 100ms
|
||||||
|
- P95: < 200ms
|
||||||
|
- P99: < 500ms
|
||||||
|
|
||||||
|
Database Operations:
|
||||||
|
- Simple queries: < 10ms
|
||||||
|
- Complex queries: < 50ms
|
||||||
|
- Writes: < 20ms
|
||||||
|
|
||||||
|
Cache Operations:
|
||||||
|
- Reads: < 5ms
|
||||||
|
- Writes: < 10ms
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Throughput Targets
|
||||||
|
```yaml
|
||||||
|
API Layer:
|
||||||
|
- Minimum: 1000 requests/second
|
||||||
|
- Target: 5000 requests/second
|
||||||
|
|
||||||
|
Database Layer:
|
||||||
|
- Reads: 10000 operations/second
|
||||||
|
- Writes: 1000 operations/second
|
||||||
|
|
||||||
|
Cache Layer:
|
||||||
|
- Operations: 50000/second
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Resource Usage Targets
|
||||||
|
```yaml
|
||||||
|
Memory:
|
||||||
|
- Base: < 100MB
|
||||||
|
- Under load: < 500MB
|
||||||
|
- Leak rate: < 1MB/hour
|
||||||
|
|
||||||
|
CPU:
|
||||||
|
- Idle: < 5%
|
||||||
|
- Average load: < 40%
|
||||||
|
- Peak load: < 80%
|
||||||
|
|
||||||
|
Connections:
|
||||||
|
- Database: < 100 concurrent
|
||||||
|
- Cache: < 1000 concurrent
|
||||||
|
- HTTP: < 10000 concurrent
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement test helpers
|
||||||
|
2. Add more Laravel-style assertions
|
||||||
|
3. Create performance test suite
|
||||||
|
4. Add continuous benchmarking
|
||||||
|
5. Improve test coverage
|
||||||
|
|
||||||
|
Would you like me to:
|
||||||
|
1. Create more test examples?
|
||||||
|
2. Add specific performance tests?
|
||||||
|
3. Create Laravel-compatible test helpers?
|
466
docs/testing_package_specification.md
Normal file
466
docs/testing_package_specification.md
Normal file
|
@ -0,0 +1,466 @@
|
||||||
|
# Testing Package Specification
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Testing package provides a robust testing framework that matches Laravel's testing functionality. It supports test case base classes, assertions, database testing, HTTP testing, and mocking while integrating with our Container and Event packages.
|
||||||
|
|
||||||
|
> **Related Documentation**
|
||||||
|
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
|
||||||
|
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
|
||||||
|
> - See [Testing Guide](testing_guide.md) for testing approaches
|
||||||
|
> - See [Getting Started Guide](getting_started.md) for development setup
|
||||||
|
> - See [Container Package Specification](container_package_specification.md) for dependency injection
|
||||||
|
> - See [Events Package Specification](events_package_specification.md) for event testing
|
||||||
|
|
||||||
|
## Core Features
|
||||||
|
|
||||||
|
### 1. Test Case
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Base test case class
|
||||||
|
abstract class TestCase {
|
||||||
|
/// Container instance
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
/// Application instance
|
||||||
|
late Application app;
|
||||||
|
|
||||||
|
/// Event dispatcher
|
||||||
|
late EventDispatcherContract events;
|
||||||
|
|
||||||
|
/// Sets up test case
|
||||||
|
@override
|
||||||
|
void setUp() {
|
||||||
|
container = Container();
|
||||||
|
app = Application(container);
|
||||||
|
events = container.make<EventDispatcherContract>();
|
||||||
|
|
||||||
|
setUpApplication();
|
||||||
|
registerServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets up application
|
||||||
|
void setUpApplication() {
|
||||||
|
app.singleton<Application>((c) => app);
|
||||||
|
app.singleton<Container>((c) => container);
|
||||||
|
app.singleton<EventDispatcherContract>((c) => events);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers test services
|
||||||
|
void registerServices() {}
|
||||||
|
|
||||||
|
/// Creates test instance
|
||||||
|
T make<T>([dynamic parameters]) {
|
||||||
|
return container.make<T>(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs test in transaction
|
||||||
|
Future<T> transaction<T>(Future<T> Function() callback) async {
|
||||||
|
var db = container.make<DatabaseManager>();
|
||||||
|
return await db.transaction(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refreshes database
|
||||||
|
Future<void> refreshDatabase() async {
|
||||||
|
await artisan.call('migrate:fresh');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seeds database
|
||||||
|
Future<void> seed([String? class]) async {
|
||||||
|
await artisan.call('db:seed', [
|
||||||
|
if (class != null) '--class=$class'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. HTTP Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// HTTP test case
|
||||||
|
abstract class HttpTestCase extends TestCase {
|
||||||
|
/// HTTP client
|
||||||
|
late TestClient client;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setUp() {
|
||||||
|
super.setUp();
|
||||||
|
client = TestClient(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes GET request
|
||||||
|
Future<TestResponse> get(String uri, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
Map<String, dynamic>? query
|
||||||
|
}) {
|
||||||
|
return client.get(uri, headers: headers, query: query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes POST request
|
||||||
|
Future<TestResponse> post(String uri, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
dynamic body
|
||||||
|
}) {
|
||||||
|
return client.post(uri, headers: headers, body: body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes PUT request
|
||||||
|
Future<TestResponse> put(String uri, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
dynamic body
|
||||||
|
}) {
|
||||||
|
return client.put(uri, headers: headers, body: body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes DELETE request
|
||||||
|
Future<TestResponse> delete(String uri, {
|
||||||
|
Map<String, String>? headers
|
||||||
|
}) {
|
||||||
|
return client.delete(uri, headers: headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Acts as user
|
||||||
|
Future<void> actingAs(User user) async {
|
||||||
|
await auth.login(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test HTTP client
|
||||||
|
class TestClient {
|
||||||
|
/// Application instance
|
||||||
|
final Application app;
|
||||||
|
|
||||||
|
TestClient(this.app);
|
||||||
|
|
||||||
|
/// Makes HTTP request
|
||||||
|
Future<TestResponse> request(
|
||||||
|
String method,
|
||||||
|
String uri, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
dynamic body,
|
||||||
|
Map<String, dynamic>? query
|
||||||
|
}) async {
|
||||||
|
var request = Request(method, uri)
|
||||||
|
..headers.addAll(headers ?? {})
|
||||||
|
..body = body
|
||||||
|
..uri = uri.replace(queryParameters: query);
|
||||||
|
|
||||||
|
var response = await app.handle(request);
|
||||||
|
return TestResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test HTTP response
|
||||||
|
class TestResponse {
|
||||||
|
/// Response instance
|
||||||
|
final Response response;
|
||||||
|
|
||||||
|
TestResponse(this.response);
|
||||||
|
|
||||||
|
/// Asserts response status
|
||||||
|
void assertStatus(int status) {
|
||||||
|
expect(response.statusCode, equals(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts response is OK
|
||||||
|
void assertOk() {
|
||||||
|
assertStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts response is redirect
|
||||||
|
void assertRedirect([String? location]) {
|
||||||
|
expect(response.statusCode, inInclusiveRange(300, 399));
|
||||||
|
if (location != null) {
|
||||||
|
expect(response.headers['location'], equals(location));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts response contains JSON
|
||||||
|
void assertJson(Map<String, dynamic> json) {
|
||||||
|
expect(response.json(), equals(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts response contains text
|
||||||
|
void assertSee(String text) {
|
||||||
|
expect(response.body, contains(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Database Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Database test case
|
||||||
|
abstract class DatabaseTestCase extends TestCase {
|
||||||
|
/// Database manager
|
||||||
|
late DatabaseManager db;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setUp() {
|
||||||
|
super.setUp();
|
||||||
|
db = container.make<DatabaseManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seeds database
|
||||||
|
Future<void> seed(String seeder) async {
|
||||||
|
await artisan.call('db:seed', ['--class=$seeder']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts database has record
|
||||||
|
Future<void> assertDatabaseHas(
|
||||||
|
String table,
|
||||||
|
Map<String, dynamic> data
|
||||||
|
) async {
|
||||||
|
var count = await db.table(table)
|
||||||
|
.where(data)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
expect(count, greaterThan(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts database missing record
|
||||||
|
Future<void> assertDatabaseMissing(
|
||||||
|
String table,
|
||||||
|
Map<String, dynamic> data
|
||||||
|
) async {
|
||||||
|
var count = await db.table(table)
|
||||||
|
.where(data)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
expect(count, equals(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts database count
|
||||||
|
Future<void> assertDatabaseCount(
|
||||||
|
String table,
|
||||||
|
int count
|
||||||
|
) async {
|
||||||
|
var actual = await db.table(table).count();
|
||||||
|
expect(actual, equals(count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Event Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
/// Event test case
|
||||||
|
abstract class EventTestCase extends TestCase {
|
||||||
|
/// Fake event dispatcher
|
||||||
|
late FakeEventDispatcher events;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setUp() {
|
||||||
|
super.setUp();
|
||||||
|
events = FakeEventDispatcher();
|
||||||
|
container.instance<EventDispatcherContract>(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts event dispatched
|
||||||
|
void assertDispatched(Type event, [Function? callback]) {
|
||||||
|
expect(events.dispatched(event), isTrue);
|
||||||
|
|
||||||
|
if (callback != null) {
|
||||||
|
var dispatched = events.dispatched(event, callback);
|
||||||
|
expect(dispatched, isTrue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts event not dispatched
|
||||||
|
void assertNotDispatched(Type event) {
|
||||||
|
expect(events.dispatched(event), isFalse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts nothing dispatched
|
||||||
|
void assertNothingDispatched() {
|
||||||
|
expect(events.hasDispatched(), isFalse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fake event dispatcher
|
||||||
|
class FakeEventDispatcher implements EventDispatcherContract {
|
||||||
|
/// Dispatched events
|
||||||
|
final List<dynamic> _events = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> dispatch<T>(T event) async {
|
||||||
|
_events.add(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if event dispatched
|
||||||
|
bool dispatched(Type event, [Function? callback]) {
|
||||||
|
var dispatched = _events.whereType<Type>();
|
||||||
|
if (dispatched.isEmpty) return false;
|
||||||
|
|
||||||
|
if (callback == null) return true;
|
||||||
|
|
||||||
|
return dispatched.any((e) => callback(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if any events dispatched
|
||||||
|
bool hasDispatched() => _events.isNotEmpty;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Examples
|
||||||
|
|
||||||
|
### 1. HTTP Testing
|
||||||
|
```dart
|
||||||
|
class UserTest extends HttpTestCase {
|
||||||
|
test('creates user', () async {
|
||||||
|
var response = await post('/users', body: {
|
||||||
|
'name': 'John Doe',
|
||||||
|
'email': 'john@example.com'
|
||||||
|
});
|
||||||
|
|
||||||
|
response.assertStatus(201);
|
||||||
|
await assertDatabaseHas('users', {
|
||||||
|
'email': 'john@example.com'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('requires authentication', () async {
|
||||||
|
var user = await User.factory().create();
|
||||||
|
await actingAs(user);
|
||||||
|
|
||||||
|
var response = await get('/dashboard');
|
||||||
|
response.assertOk();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Database Testing
|
||||||
|
```dart
|
||||||
|
class OrderTest extends DatabaseTestCase {
|
||||||
|
test('creates order', () async {
|
||||||
|
await seed(ProductSeeder);
|
||||||
|
|
||||||
|
var order = await Order.create({
|
||||||
|
'product_id': 1,
|
||||||
|
'quantity': 5
|
||||||
|
});
|
||||||
|
|
||||||
|
await assertDatabaseHas('orders', {
|
||||||
|
'id': order.id,
|
||||||
|
'quantity': 5
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Event Testing
|
||||||
|
```dart
|
||||||
|
class PaymentTest extends EventTestCase {
|
||||||
|
test('dispatches payment events', () async {
|
||||||
|
var payment = await processPayment(order);
|
||||||
|
|
||||||
|
assertDispatched(PaymentProcessed, (event) {
|
||||||
|
return event.payment.id == payment.id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
group('HTTP Testing', () {
|
||||||
|
test('makes requests', () async {
|
||||||
|
var client = TestClient(app);
|
||||||
|
|
||||||
|
var response = await client.get('/users');
|
||||||
|
|
||||||
|
expect(response.statusCode, equals(200));
|
||||||
|
expect(response.json(), isA<List>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles authentication', () async {
|
||||||
|
var case = UserTest();
|
||||||
|
await case.setUp();
|
||||||
|
|
||||||
|
await case.actingAs(user);
|
||||||
|
var response = await case.get('/profile');
|
||||||
|
|
||||||
|
response.assertOk();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Database Testing', () {
|
||||||
|
test('seeds database', () async {
|
||||||
|
var case = OrderTest();
|
||||||
|
await case.setUp();
|
||||||
|
|
||||||
|
await case.seed(ProductSeeder);
|
||||||
|
|
||||||
|
await case.assertDatabaseCount('products', 10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Implement core testing features
|
||||||
|
2. Add HTTP testing
|
||||||
|
3. Add database testing
|
||||||
|
4. Add event testing
|
||||||
|
5. Write tests
|
||||||
|
6. Add examples
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### 1. Getting Started
|
||||||
|
Before implementing testing features:
|
||||||
|
1. Review [Getting Started Guide](getting_started.md)
|
||||||
|
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Follow [Testing Guide](testing_guide.md)
|
||||||
|
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
5. Review [Container Package Specification](container_package_specification.md)
|
||||||
|
6. Review [Events Package Specification](events_package_specification.md)
|
||||||
|
|
||||||
|
### 2. Implementation Process
|
||||||
|
For each testing feature:
|
||||||
|
1. Write tests following [Testing Guide](testing_guide.md)
|
||||||
|
2. Implement following Laravel patterns
|
||||||
|
3. Document following [Getting Started Guide](getting_started.md#documentation)
|
||||||
|
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
|
||||||
|
### 3. Quality Requirements
|
||||||
|
All implementations must:
|
||||||
|
1. Pass all tests (see [Testing Guide](testing_guide.md))
|
||||||
|
2. Meet Laravel compatibility requirements
|
||||||
|
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
|
||||||
|
4. Support dependency injection (see [Container Package Specification](container_package_specification.md))
|
||||||
|
5. Support event testing (see [Events Package Specification](events_package_specification.md))
|
||||||
|
|
||||||
|
### 4. Integration Considerations
|
||||||
|
When implementing testing features:
|
||||||
|
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
|
||||||
|
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
|
||||||
|
3. Use testing approaches from [Testing Guide](testing_guide.md)
|
||||||
|
4. Follow development setup in [Getting Started Guide](getting_started.md)
|
||||||
|
|
||||||
|
### 5. Performance Guidelines
|
||||||
|
Testing system must:
|
||||||
|
1. Execute tests efficiently
|
||||||
|
2. Support parallel testing
|
||||||
|
3. Handle large test suites
|
||||||
|
4. Manage test isolation
|
||||||
|
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
|
||||||
|
|
||||||
|
### 6. Testing Requirements
|
||||||
|
Testing package tests must:
|
||||||
|
1. Cover all testing features
|
||||||
|
2. Test HTTP assertions
|
||||||
|
3. Verify database testing
|
||||||
|
4. Check event assertions
|
||||||
|
5. Follow patterns in [Testing Guide](testing_guide.md)
|
||||||
|
|
||||||
|
### 7. Documentation Requirements
|
||||||
|
Testing documentation must:
|
||||||
|
1. Explain testing patterns
|
||||||
|
2. Show assertion examples
|
||||||
|
3. Cover test organization
|
||||||
|
4. Include performance tips
|
||||||
|
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)
|
|
@ -13,6 +13,7 @@ repository: https://github.com/protevus/platform
|
||||||
packages:
|
packages:
|
||||||
- apps/**
|
- apps/**
|
||||||
- packages/**
|
- packages/**
|
||||||
|
- sandbox/**
|
||||||
- helpers/tools/**
|
- helpers/tools/**
|
||||||
- examples/**
|
- examples/**
|
||||||
|
|
||||||
|
|
7
packages/bus/.gitignore
vendored
Normal file
7
packages/bus/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
# Avoid committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
3
packages/bus/CHANGELOG.md
Normal file
3
packages/bus/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version.
|
10
packages/bus/LICENSE.md
Normal file
10
packages/bus/LICENSE.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
The Laravel Framework is Copyright (c) Taylor Otwell
|
||||||
|
The Fabric Framework is Copyright (c) Vieo, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
packages/bus/README.md
Normal file
1
packages/bus/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<p align="center"><a href="https://protevus.com" target="_blank"><img src="https://git.protevus.com/protevus/branding/raw/branch/main/protevus-logo-bg.png"></a></p>
|
30
packages/bus/analysis_options.yaml
Normal file
30
packages/bus/analysis_options.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
9
packages/bus/lib/angel3_bus.dart
Normal file
9
packages/bus/lib/angel3_bus.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
library angel3_bus;
|
||||||
|
|
||||||
|
export 'src/dispatcher.dart';
|
||||||
|
export 'src/command.dart';
|
||||||
|
export 'src/handler.dart';
|
||||||
|
export 'src/queue.dart';
|
||||||
|
export 'src/batch.dart';
|
||||||
|
export 'src/chain.dart';
|
||||||
|
export 'src/bus_service_provider.dart';
|
19
packages/bus/lib/src/batch.dart
Normal file
19
packages/bus/lib/src/batch.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'command.dart';
|
||||||
|
import 'dispatcher.dart';
|
||||||
|
|
||||||
|
class Batch {
|
||||||
|
// Implement Batch
|
||||||
|
}
|
||||||
|
|
||||||
|
class PendingBatch {
|
||||||
|
final Dispatcher _dispatcher;
|
||||||
|
final List<Command> _commands;
|
||||||
|
|
||||||
|
PendingBatch(this._dispatcher, this._commands);
|
||||||
|
|
||||||
|
Future<void> dispatch() async {
|
||||||
|
for (var command in _commands) {
|
||||||
|
await _dispatcher.dispatch(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
packages/bus/lib/src/bus_service_provider.dart
Normal file
60
packages/bus/lib/src/bus_service_provider.dart
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// // lib/src/bus_service_provider.dart
|
||||||
|
|
||||||
|
// import 'package:angel3_framework/angel3_framework.dart';
|
||||||
|
// import 'package:angel3_event_bus/angel3_event_bus.dart';
|
||||||
|
// import 'package:angel3_mq/angel3_mq.dart';
|
||||||
|
// import 'dispatcher.dart';
|
||||||
|
|
||||||
|
// class BusServiceProvider extends Provider {
|
||||||
|
// @override
|
||||||
|
// Future<void> boot(Angel app) async {
|
||||||
|
// // Register EventBus
|
||||||
|
// app.container.registerSingleton<EventBus>(EventBus());
|
||||||
|
|
||||||
|
// // Register Queue
|
||||||
|
// app.container.registerSingleton<Queue>(MemoryQueue());
|
||||||
|
|
||||||
|
// // Create and register the Dispatcher
|
||||||
|
// final dispatcher = Dispatcher(app.container);
|
||||||
|
// app.container.registerSingleton<Dispatcher>(dispatcher);
|
||||||
|
|
||||||
|
// // Register any global middleware or mappings
|
||||||
|
// dispatcher.pipeThrough([
|
||||||
|
// // Add any global middleware here
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// // Register command-to-handler mappings
|
||||||
|
// dispatcher.map({
|
||||||
|
// // Add your command-to-handler mappings here
|
||||||
|
// // Example: ExampleCommand: ExampleCommandHandler,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// class MemoryQueue implements Queue {
|
||||||
|
// final List<Command> _queue = [];
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<void> push(Command command) async {
|
||||||
|
// _queue.add(command);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<void> later(Duration delay, Command command) async {
|
||||||
|
// await Future.delayed(delay);
|
||||||
|
// _queue.add(command);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<void> pushOn(String queue, Command command) async {
|
||||||
|
// // For simplicity, ignoring the queue parameter in this implementation
|
||||||
|
// _queue.add(command);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<void> laterOn(String queue, Duration delay, Command command) async {
|
||||||
|
// // For simplicity, ignoring the queue parameter in this implementation
|
||||||
|
// await Future.delayed(delay);
|
||||||
|
// _queue.add(command);
|
||||||
|
// }
|
||||||
|
// }
|
15
packages/bus/lib/src/chain.dart
Normal file
15
packages/bus/lib/src/chain.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'command.dart';
|
||||||
|
import 'dispatcher.dart';
|
||||||
|
|
||||||
|
class PendingChain {
|
||||||
|
final Dispatcher _dispatcher;
|
||||||
|
final List<Command> _commands;
|
||||||
|
|
||||||
|
PendingChain(this._dispatcher, this._commands);
|
||||||
|
|
||||||
|
Future<void> dispatch() async {
|
||||||
|
for (var command in _commands) {
|
||||||
|
await _dispatcher.dispatch(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
packages/bus/lib/src/command.dart
Normal file
5
packages/bus/lib/src/command.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// lib/src/command.dart
|
||||||
|
|
||||||
|
abstract class Command {}
|
||||||
|
|
||||||
|
abstract class ShouldQueue implements Command {}
|
251
packages/bus/lib/src/dispatcher.dart
Normal file
251
packages/bus/lib/src/dispatcher.dart
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
// lib/src/dispatcher.dart
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:platform_container/container.dart';
|
||||||
|
import 'package:angel3_reactivex/angel3_reactivex.dart';
|
||||||
|
import 'package:angel3_event_bus/event_bus.dart';
|
||||||
|
import 'package:angel3_mq/mq.dart';
|
||||||
|
|
||||||
|
import 'command.dart';
|
||||||
|
import 'handler.dart';
|
||||||
|
import 'batch.dart';
|
||||||
|
import 'chain.dart';
|
||||||
|
|
||||||
|
/// A class that handles dispatching and processing of commands.
|
||||||
|
///
|
||||||
|
/// This dispatcher supports both synchronous and asynchronous command execution,
|
||||||
|
/// as well as queueing commands for later processing.
|
||||||
|
class Dispatcher implements QueueingDispatcher {
|
||||||
|
final Container container;
|
||||||
|
final EventBus _eventBus;
|
||||||
|
final Subject<Command> _commandSubject;
|
||||||
|
final MQClient _queue;
|
||||||
|
final Map<Type, Type> _handlers = {};
|
||||||
|
|
||||||
|
/// Creates a new [Dispatcher] instance.
|
||||||
|
///
|
||||||
|
/// [container] is used for dependency injection and to retrieve necessary services.
|
||||||
|
Dispatcher(this.container)
|
||||||
|
: _eventBus = container.make<EventBus>(),
|
||||||
|
_commandSubject = BehaviorSubject<Command>(),
|
||||||
|
_queue = container.make<MQClient>() {
|
||||||
|
_setupCommandProcessing();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets up the command processing pipeline.
|
||||||
|
///
|
||||||
|
/// This method initializes the stream that processes commands and emits events.
|
||||||
|
void _setupCommandProcessing() {
|
||||||
|
_commandSubject
|
||||||
|
.flatMap((command) => Stream.fromFuture(_processCommand(command))
|
||||||
|
.map((result) => CommandEvent(command, result: result))
|
||||||
|
.onErrorReturnWith(
|
||||||
|
(error, stackTrace) => CommandEvent(command, error: error)))
|
||||||
|
.listen((event) {
|
||||||
|
_eventBus.fire(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches a command for execution.
|
||||||
|
///
|
||||||
|
/// If the command implements [ShouldQueue], it will be dispatched to a queue.
|
||||||
|
/// Otherwise, it will be executed immediately.
|
||||||
|
///
|
||||||
|
/// [command] is the command to be dispatched.
|
||||||
|
@override
|
||||||
|
Future<dynamic> dispatch(Command command) {
|
||||||
|
if (command is ShouldQueue) {
|
||||||
|
return dispatchToQueue(command);
|
||||||
|
} else {
|
||||||
|
return dispatchNow(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches a command for immediate execution.
|
||||||
|
///
|
||||||
|
/// [command] is the command to be executed.
|
||||||
|
/// [handler] is an optional specific handler for the command.
|
||||||
|
@override
|
||||||
|
Future<dynamic> dispatchNow(Command command, [Handler? handler]) {
|
||||||
|
final completer = Completer<dynamic>();
|
||||||
|
_commandSubject.add(command);
|
||||||
|
|
||||||
|
_eventBus
|
||||||
|
.on<CommandEvent>()
|
||||||
|
.where((event) => event.command == command)
|
||||||
|
.take(1)
|
||||||
|
.listen((event) {
|
||||||
|
if (event.error != null) {
|
||||||
|
completer.completeError(event.error);
|
||||||
|
} else {
|
||||||
|
completer.complete(event.result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes a command by finding and executing its appropriate handler.
|
||||||
|
///
|
||||||
|
/// [command] is the command to be processed.
|
||||||
|
Future<dynamic> _processCommand(Command command) async {
|
||||||
|
final handlerType = _handlers[command.runtimeType];
|
||||||
|
if (handlerType != null) {
|
||||||
|
final handler = container.make(handlerType) as Handler;
|
||||||
|
return await handler.handle(command);
|
||||||
|
} else {
|
||||||
|
throw Exception('No handler found for command: ${command.runtimeType}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches a command to a queue for later processing.
|
||||||
|
///
|
||||||
|
/// [command] is the command to be queued.
|
||||||
|
@override
|
||||||
|
Future<dynamic> dispatchToQueue(Command command) async {
|
||||||
|
final message = Message(
|
||||||
|
payload: command,
|
||||||
|
headers: {
|
||||||
|
'commandType': command.runtimeType.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_queue.sendMessage(
|
||||||
|
message: message,
|
||||||
|
// You might want to specify an exchange name and routing key if needed
|
||||||
|
// exchangeName: 'your_exchange_name',
|
||||||
|
// routingKey: 'your_routing_key',
|
||||||
|
);
|
||||||
|
return message.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches a command synchronously.
|
||||||
|
///
|
||||||
|
/// This is an alias for [dispatchNow].
|
||||||
|
///
|
||||||
|
/// [command] is the command to be executed.
|
||||||
|
/// [handler] is an optional specific handler for the command.
|
||||||
|
@override
|
||||||
|
Future<dynamic> dispatchSync(Command command, [Handler? handler]) {
|
||||||
|
return dispatchNow(command, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds a batch by its ID.
|
||||||
|
///
|
||||||
|
/// [batchId] is the ID of the batch to find.
|
||||||
|
@override
|
||||||
|
Future<Batch?> findBatch(String batchId) async {
|
||||||
|
// Implement batch finding logic
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new pending batch of commands.
|
||||||
|
///
|
||||||
|
/// [commands] is the list of commands to be included in the batch.
|
||||||
|
@override
|
||||||
|
PendingBatch batch(List<Command> commands) {
|
||||||
|
return PendingBatch(this, commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new pending chain of commands.
|
||||||
|
///
|
||||||
|
/// [commands] is the list of commands to be included in the chain.
|
||||||
|
@override
|
||||||
|
PendingChain chain(List<Command> commands) {
|
||||||
|
return PendingChain(this, commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a list of pipes to the command processing pipeline.
|
||||||
|
///
|
||||||
|
/// [pipes] is the list of pipes to be applied.
|
||||||
|
@override
|
||||||
|
Dispatcher pipeThrough(List<Pipe> pipes) {
|
||||||
|
_commandSubject.transform(
|
||||||
|
StreamTransformer.fromHandlers(
|
||||||
|
handleData: (data, sink) {
|
||||||
|
var result = data;
|
||||||
|
for (var pipe in pipes) {
|
||||||
|
result = pipe(result);
|
||||||
|
}
|
||||||
|
sink.add(result);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps command types to their respective handler types.
|
||||||
|
///
|
||||||
|
/// [handlers] is a map where keys are command types and values are handler types.
|
||||||
|
@override
|
||||||
|
Dispatcher map(Map<Type, Type> handlers) {
|
||||||
|
_handlers.addAll(handlers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispatches a command to be executed after the current request-response cycle.
|
||||||
|
///
|
||||||
|
/// [command] is the command to be dispatched after the response.
|
||||||
|
@override
|
||||||
|
void dispatchAfterResponse(Command command) {
|
||||||
|
final message = Message(
|
||||||
|
payload: command,
|
||||||
|
headers: {
|
||||||
|
'commandType': command.runtimeType.toString(),
|
||||||
|
'dispatchAfterResponse': 'true',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
_queue.sendMessage(
|
||||||
|
message: message,
|
||||||
|
// You might want to specify an exchange name if needed
|
||||||
|
// exchangeName: 'your_exchange_name',
|
||||||
|
// If you want to use a specific queue for after-response commands:
|
||||||
|
routingKey: 'after_response_queue',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class QueueingDispatcher {
|
||||||
|
Future<dynamic> dispatch(Command command);
|
||||||
|
Future<dynamic> dispatchSync(Command command, [Handler? handler]);
|
||||||
|
Future<dynamic> dispatchNow(Command command, [Handler? handler]);
|
||||||
|
Future<dynamic> dispatchToQueue(Command command);
|
||||||
|
Future<Batch?> findBatch(String batchId);
|
||||||
|
PendingBatch batch(List<Command> commands);
|
||||||
|
PendingChain chain(List<Command> commands);
|
||||||
|
Dispatcher pipeThrough(List<Pipe> pipes);
|
||||||
|
Dispatcher map(Map<Type, Type> handlers);
|
||||||
|
void dispatchAfterResponse(Command command);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef Pipe = Command Function(Command);
|
||||||
|
|
||||||
|
class CommandCompletedEvent extends AppEvent {
|
||||||
|
final dynamic result;
|
||||||
|
|
||||||
|
CommandCompletedEvent(this.result);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [result];
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommandErrorEvent extends AppEvent {
|
||||||
|
final dynamic error;
|
||||||
|
|
||||||
|
CommandErrorEvent(this.error);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [error];
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommandEvent extends AppEvent {
|
||||||
|
final Command command;
|
||||||
|
final dynamic result;
|
||||||
|
final dynamic error;
|
||||||
|
|
||||||
|
CommandEvent(this.command, {this.result, this.error});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [command, result, error];
|
||||||
|
}
|
5
packages/bus/lib/src/handler.dart
Normal file
5
packages/bus/lib/src/handler.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import 'command.dart';
|
||||||
|
|
||||||
|
abstract class Handler {
|
||||||
|
Future<dynamic> handle(Command command);
|
||||||
|
}
|
8
packages/bus/lib/src/queue.dart
Normal file
8
packages/bus/lib/src/queue.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import 'command.dart';
|
||||||
|
|
||||||
|
abstract class Queue {
|
||||||
|
Future<void> push(Command command);
|
||||||
|
Future<void> later(Duration delay, Command command);
|
||||||
|
Future<void> pushOn(String queue, Command command);
|
||||||
|
Future<void> laterOn(String queue, Duration delay, Command command);
|
||||||
|
}
|
24
packages/bus/pubspec.yaml
Normal file
24
packages/bus/pubspec.yaml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
name: platform_bus
|
||||||
|
description: The Bus Package for the Protevus Platform
|
||||||
|
version: 0.0.1
|
||||||
|
homepage: https://protevus.com
|
||||||
|
documentation: https://docs.protevus.com
|
||||||
|
repository: https://github.com/protevus/platformo
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.4.2
|
||||||
|
|
||||||
|
# Add regular dependencies here.
|
||||||
|
dependencies:
|
||||||
|
platform_container: ^9.0.0
|
||||||
|
platform_core: ^9.0.0
|
||||||
|
angel3_reactivex: ^9.0.0
|
||||||
|
angel3_event_bus: ^9.0.0
|
||||||
|
angel3_mq: ^9.0.0
|
||||||
|
# path: ^1.8.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner: ^2.1.0
|
||||||
|
lints: ^3.0.0
|
||||||
|
mockito: ^5.3.0
|
||||||
|
test: ^1.24.0
|
197
packages/bus/test/dispatcher_test.dart
Normal file
197
packages/bus/test/dispatcher_test.dart
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:platform_bus/angel3_bus.dart';
|
||||||
|
import 'package:platform_container/container.dart';
|
||||||
|
import 'package:angel3_event_bus/event_bus.dart';
|
||||||
|
import 'package:angel3_mq/mq.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
class IsMessage extends Matcher {
|
||||||
|
@override
|
||||||
|
bool matches(item, Map matchState) => item is Message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) =>
|
||||||
|
description.add('is a Message');
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockContainer extends Mock implements Container {
|
||||||
|
final Map<Type, dynamic> _instances = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
T make<T>([Type? type]) {
|
||||||
|
type ??= T;
|
||||||
|
return _instances[type] as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerInstance<T>(T instance) {
|
||||||
|
_instances[T] = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockEventBus extends Mock implements EventBus {
|
||||||
|
@override
|
||||||
|
Stream<T> on<T extends AppEvent>() {
|
||||||
|
return super.noSuchMethod(
|
||||||
|
Invocation.method(#on, [], {#T: T}),
|
||||||
|
returnValue: Stream<T>.empty(),
|
||||||
|
) as Stream<T>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockMQClient extends Mock implements MQClient {
|
||||||
|
Message? capturedMessage;
|
||||||
|
String? capturedExchangeName;
|
||||||
|
String? capturedRoutingKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation,
|
||||||
|
{Object? returnValue, Object? returnValueForMissingStub}) {
|
||||||
|
if (invocation.memberName == #sendMessage) {
|
||||||
|
final namedArgs = invocation.namedArguments;
|
||||||
|
capturedMessage = namedArgs[#message] as Message?;
|
||||||
|
capturedExchangeName = namedArgs[#exchangeName] as String?;
|
||||||
|
capturedRoutingKey = namedArgs[#routingKey] as String?;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return super.noSuchMethod(invocation,
|
||||||
|
returnValue: returnValue,
|
||||||
|
returnValueForMissingStub: returnValueForMissingStub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestCommand implements Command {
|
||||||
|
final String data;
|
||||||
|
TestCommand(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestHandler implements Handler {
|
||||||
|
@override
|
||||||
|
Future<dynamic> handle(Command command) async {
|
||||||
|
if (command is TestCommand) {
|
||||||
|
return 'Handled: ${command.data}';
|
||||||
|
}
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestQueuedCommand implements Command, ShouldQueue {
|
||||||
|
final String data;
|
||||||
|
TestQueuedCommand(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late MockContainer container;
|
||||||
|
late MockEventBus eventBus;
|
||||||
|
late MockMQClient mqClient;
|
||||||
|
late Dispatcher dispatcher;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = MockContainer();
|
||||||
|
eventBus = MockEventBus();
|
||||||
|
mqClient = MockMQClient();
|
||||||
|
|
||||||
|
container.registerInstance<EventBus>(eventBus);
|
||||||
|
container.registerInstance<MQClient>(mqClient);
|
||||||
|
|
||||||
|
dispatcher = Dispatcher(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Dispatcher', () {
|
||||||
|
test('dispatchNow should handle command and return result', () async {
|
||||||
|
final command = TestCommand('test data');
|
||||||
|
final handler = TestHandler();
|
||||||
|
|
||||||
|
container.registerInstance<TestHandler>(handler);
|
||||||
|
dispatcher.map({TestCommand: TestHandler});
|
||||||
|
|
||||||
|
final commandEventController = StreamController<CommandEvent>();
|
||||||
|
when(eventBus.on<CommandEvent>())
|
||||||
|
.thenAnswer((_) => commandEventController.stream);
|
||||||
|
|
||||||
|
final future = dispatcher.dispatchNow(command);
|
||||||
|
|
||||||
|
// Simulate the event firing
|
||||||
|
commandEventController
|
||||||
|
.add(CommandEvent(command, result: 'Handled: test data'));
|
||||||
|
|
||||||
|
final result = await future;
|
||||||
|
expect(result, equals('Handled: test data'));
|
||||||
|
|
||||||
|
await commandEventController.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dispatch should handle regular commands immediately', () async {
|
||||||
|
final command = TestCommand('regular');
|
||||||
|
final handler = TestHandler();
|
||||||
|
|
||||||
|
container.registerInstance<TestHandler>(handler);
|
||||||
|
dispatcher.map({TestCommand: TestHandler});
|
||||||
|
|
||||||
|
final commandEventController = StreamController<CommandEvent>();
|
||||||
|
when(eventBus.on<CommandEvent>())
|
||||||
|
.thenAnswer((_) => commandEventController.stream);
|
||||||
|
|
||||||
|
final future = dispatcher.dispatch(command);
|
||||||
|
|
||||||
|
// Simulate the event firing
|
||||||
|
commandEventController
|
||||||
|
.add(CommandEvent(command, result: 'Handled: regular'));
|
||||||
|
|
||||||
|
final result = await future;
|
||||||
|
expect(result, equals('Handled: regular'));
|
||||||
|
|
||||||
|
await commandEventController.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dispatch should queue ShouldQueue commands', () async {
|
||||||
|
final command = TestQueuedCommand('queued data');
|
||||||
|
|
||||||
|
// Dispatch the command
|
||||||
|
await dispatcher.dispatch(command);
|
||||||
|
|
||||||
|
// Verify that sendMessage was called and check the message properties
|
||||||
|
expect(mqClient.capturedMessage, isNotNull);
|
||||||
|
expect(mqClient.capturedMessage!.payload, equals(command));
|
||||||
|
expect(mqClient.capturedMessage!.headers?['commandType'],
|
||||||
|
equals('TestQueuedCommand'));
|
||||||
|
|
||||||
|
// Optionally, verify exchange name and routing key if needed
|
||||||
|
expect(mqClient.capturedExchangeName, isNull);
|
||||||
|
expect(mqClient.capturedRoutingKey, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'dispatchAfterResponse should send message to queue with specific header',
|
||||||
|
() {
|
||||||
|
final command = TestCommand('after response data');
|
||||||
|
|
||||||
|
// Call dispatchAfterResponse
|
||||||
|
dispatcher.dispatchAfterResponse(command);
|
||||||
|
|
||||||
|
// Verify that sendMessage was called and check the message properties
|
||||||
|
expect(mqClient.capturedMessage, isNotNull);
|
||||||
|
expect(mqClient.capturedMessage!.payload, equals(command));
|
||||||
|
expect(mqClient.capturedMessage!.headers?['commandType'],
|
||||||
|
equals('TestCommand'));
|
||||||
|
expect(mqClient.capturedMessage!.headers?['dispatchAfterResponse'],
|
||||||
|
equals('true'));
|
||||||
|
|
||||||
|
// Verify routing key
|
||||||
|
expect(mqClient.capturedRoutingKey, equals('after_response_queue'));
|
||||||
|
|
||||||
|
// Optionally, verify exchange name if needed
|
||||||
|
expect(mqClient.capturedExchangeName, isNull);
|
||||||
|
});
|
||||||
|
test('map should register command handlers', () {
|
||||||
|
dispatcher.map({TestCommand: TestHandler});
|
||||||
|
|
||||||
|
// Mock the event bus behavior for this test
|
||||||
|
when(eventBus.on<CommandEvent>()).thenAnswer((_) => Stream.empty());
|
||||||
|
|
||||||
|
// This test is a bit tricky to verify directly, but we can check if dispatch doesn't throw
|
||||||
|
expect(() => dispatcher.dispatch(TestCommand('test')), returnsNormally);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
7
packages/events/.gitignore
vendored
Normal file
7
packages/events/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
# Avoid committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
3
packages/events/CHANGELOG.md
Normal file
3
packages/events/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version.
|
10
packages/events/LICENSE.md
Normal file
10
packages/events/LICENSE.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
The Laravel Framework is Copyright (c) Taylor Otwell
|
||||||
|
The Fabric Framework is Copyright (c) Vieo, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
packages/events/README.md
Normal file
1
packages/events/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<p align="center"><a href="https://protevus.com" target="_blank"><img src="https://git.protevus.com/protevus/branding/raw/branch/main/protevus-logo-bg.png"></a></p>
|
30
packages/events/analysis_options.yaml
Normal file
30
packages/events/analysis_options.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
3
packages/events/lib/dispatcher.dart
Normal file
3
packages/events/lib/dispatcher.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
library;
|
||||||
|
|
||||||
|
export 'src/dispatcher.dart';
|
499
packages/events/lib/src/dispatcher.dart
Normal file
499
packages/events/lib/src/dispatcher.dart
Normal file
|
@ -0,0 +1,499 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:platform_container/container.dart';
|
||||||
|
import 'package:angel3_reactivex/angel3_reactivex.dart';
|
||||||
|
import 'package:angel3_event_bus/event_bus.dart';
|
||||||
|
import 'package:angel3_mq/mq.dart';
|
||||||
|
|
||||||
|
// Simulating some of the Laravel interfaces/classes
|
||||||
|
abstract class ShouldBroadcast {}
|
||||||
|
|
||||||
|
abstract class ShouldQueue {}
|
||||||
|
|
||||||
|
abstract class ShouldBeEncrypted {}
|
||||||
|
|
||||||
|
abstract class ShouldDispatchAfterCommit {}
|
||||||
|
|
||||||
|
class Dispatcher implements DispatcherContract {
|
||||||
|
// Properties as specified in YAML
|
||||||
|
final Container container;
|
||||||
|
final Map<String, List<Function>> _listeners = {};
|
||||||
|
final Map<String, List<Function>> _wildcards = {};
|
||||||
|
final Map<String, List<Function>> _wildcardsCache = {};
|
||||||
|
late final Function _queueResolver;
|
||||||
|
late final Function _transactionManagerResolver;
|
||||||
|
final Map<String, List<Function>> _eventBusListeners = {};
|
||||||
|
final Map<String, Completer<dynamic>> _untilCompleters = {};
|
||||||
|
final Map<String, StreamSubscription> _eventBusSubscriptions = {};
|
||||||
|
final Set<String> _processedMessageIds = {};
|
||||||
|
|
||||||
|
// Properties for Angel3 packages
|
||||||
|
final EventBus _eventBus;
|
||||||
|
late final MQClient? _mqClient;
|
||||||
|
final Map<String, BehaviorSubject<dynamic>> _subjects = {};
|
||||||
|
|
||||||
|
// Queue and exchange names
|
||||||
|
static const String _eventsQueue = 'events_queue';
|
||||||
|
static const String _delayedEventsQueue = 'delayed_events_queue';
|
||||||
|
static const String _eventsExchange = 'events_exchange';
|
||||||
|
|
||||||
|
Dispatcher(this.container) : _eventBus = EventBus();
|
||||||
|
|
||||||
|
// Setter for _mqClient
|
||||||
|
set mqClient(MQClient client) {
|
||||||
|
_mqClient = client;
|
||||||
|
_setupQueuesAndExchanges();
|
||||||
|
_startProcessingQueuedEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setupQueuesAndExchanges() {
|
||||||
|
_mqClient?.declareQueue(_eventsQueue);
|
||||||
|
_mqClient?.declareQueue(_delayedEventsQueue);
|
||||||
|
_mqClient?.declareExchange(
|
||||||
|
exchangeName: _eventsExchange,
|
||||||
|
exchangeType: ExchangeType.direct,
|
||||||
|
);
|
||||||
|
_mqClient?.bindQueue(
|
||||||
|
queueId: _eventsQueue,
|
||||||
|
exchangeName: _eventsExchange,
|
||||||
|
bindingKey: _eventsQueue,
|
||||||
|
);
|
||||||
|
_mqClient?.bindQueue(
|
||||||
|
queueId: _delayedEventsQueue,
|
||||||
|
exchangeName: _eventsExchange,
|
||||||
|
bindingKey: _delayedEventsQueue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startProcessingQueuedEvents() {
|
||||||
|
_mqClient?.fetchQueue(_eventsQueue).listen((Message message) async {
|
||||||
|
if (message.payload is Map) {
|
||||||
|
final eventData = message.payload as Map<String, dynamic>;
|
||||||
|
if (eventData.containsKey('event') &&
|
||||||
|
eventData.containsKey('payload')) {
|
||||||
|
await dispatch(eventData['event'], eventData['payload']);
|
||||||
|
} else {
|
||||||
|
print('Invalid message format: ${message.payload}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('Unexpected payload type: ${message.payload.runtimeType}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void listen(dynamic events, dynamic listener) {
|
||||||
|
if (events is String) {
|
||||||
|
_addListener(events, listener);
|
||||||
|
} else if (events is List) {
|
||||||
|
for (var event in events) {
|
||||||
|
_addListener(event, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (events is String && events.contains('*')) {
|
||||||
|
_setupWildcardListen(events, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addListener(String event, dynamic listener) {
|
||||||
|
_listeners.putIfAbsent(event, () => []).add(listener);
|
||||||
|
|
||||||
|
// Create a subject for this event if it doesn't exist
|
||||||
|
_subjects.putIfAbsent(event, () => BehaviorSubject<dynamic>());
|
||||||
|
|
||||||
|
// Add EventBus listener and store the subscription
|
||||||
|
final subscription = _eventBus.on().listen((AppEvent busEvent) {
|
||||||
|
if (busEvent is CustomAppEvent && busEvent.eventName == event) {
|
||||||
|
listener(event, busEvent.payload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_eventBusSubscriptions[event] = subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setupWildcardListen(String event, Function listener) {
|
||||||
|
_wildcards.putIfAbsent(event, () => []).add(listener);
|
||||||
|
_wildcardsCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool hasListeners(String eventName) {
|
||||||
|
return _listeners.containsKey(eventName) ||
|
||||||
|
_wildcards.containsKey(eventName) ||
|
||||||
|
hasWildcardListeners(eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasWildcardListeners(String eventName) {
|
||||||
|
return _wildcards.keys
|
||||||
|
.any((pattern) => _isWildcardMatch(pattern, eventName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void push(String event, [dynamic payload]) {
|
||||||
|
final effectivePayload = payload ?? [];
|
||||||
|
_mqClient?.sendMessage(
|
||||||
|
exchangeName: _eventsExchange,
|
||||||
|
routingKey: _delayedEventsQueue,
|
||||||
|
message: Message(
|
||||||
|
headers: {'expiration': '5000'}, // 5 seconds delay
|
||||||
|
payload: {
|
||||||
|
'event': event,
|
||||||
|
'payload':
|
||||||
|
effectivePayload is List ? effectivePayload : [effectivePayload],
|
||||||
|
},
|
||||||
|
timestamp: DateTime.now().toIso8601String(),
|
||||||
|
id: 'msg_${DateTime.now().millisecondsSinceEpoch}', // Ensure unique ID
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> flush(String event) async {
|
||||||
|
final messageStream = _mqClient?.fetchQueue(_delayedEventsQueue);
|
||||||
|
if (messageStream == null) {
|
||||||
|
print('Warning: MQClient is not initialized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final messagesToProcess = <Message>[];
|
||||||
|
|
||||||
|
// Collect messages to process
|
||||||
|
await for (final message in messageStream) {
|
||||||
|
print('Examining message: ${message.id}');
|
||||||
|
if (message.payload is Map<String, dynamic> &&
|
||||||
|
!_processedMessageIds.contains(message.id)) {
|
||||||
|
final eventData = message.payload as Map<String, dynamic>;
|
||||||
|
if (eventData['event'] == event) {
|
||||||
|
print('Adding message to process: ${message.id}');
|
||||||
|
messagesToProcess.add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print('Total messages to process: ${messagesToProcess.length}');
|
||||||
|
|
||||||
|
// Process collected messages
|
||||||
|
for (final message in messagesToProcess) {
|
||||||
|
final eventData = message.payload as Map<String, dynamic>;
|
||||||
|
print('Processing message: ${message.id}');
|
||||||
|
await dispatch(eventData['event'], eventData['payload']);
|
||||||
|
_mqClient?.deleteMessage(_delayedEventsQueue, message);
|
||||||
|
_processedMessageIds.add(message.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void subscribe(dynamic subscriber) {
|
||||||
|
if (subscriber is EventBusSubscriber) {
|
||||||
|
subscriber.subscribe(_eventBus);
|
||||||
|
} else {
|
||||||
|
// Handle other types of subscribers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<dynamic> until(dynamic event, [dynamic payload]) {
|
||||||
|
if (event is String) {
|
||||||
|
final completer = Completer<dynamic>();
|
||||||
|
_untilCompleters[event] = completer;
|
||||||
|
|
||||||
|
// Set up a one-time listener for this event
|
||||||
|
listen(event, (dynamic e, dynamic p) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete(p);
|
||||||
|
_untilCompleters.remove(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If payload is provided, dispatch the event immediately
|
||||||
|
if (payload != null) {
|
||||||
|
// Use dispatch instead of push to ensure immediate processing
|
||||||
|
dispatch(event, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
throw ArgumentError('Event must be a String');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<dynamic> dispatch(dynamic event, [dynamic payload, bool? halt]) async {
|
||||||
|
final eventName = event is String ? event : event.runtimeType.toString();
|
||||||
|
final eventPayload = payload ?? (event is AppEvent ? event : []);
|
||||||
|
|
||||||
|
if (event is ShouldBroadcast ||
|
||||||
|
(eventPayload is List &&
|
||||||
|
eventPayload.isNotEmpty &&
|
||||||
|
eventPayload[0] is ShouldBroadcast)) {
|
||||||
|
await _broadcastEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event is ShouldQueue ||
|
||||||
|
(eventPayload is List &&
|
||||||
|
eventPayload.isNotEmpty &&
|
||||||
|
eventPayload[0] is ShouldQueue)) {
|
||||||
|
return _queueEvent(eventName, eventPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
final listeners = getListeners(eventName);
|
||||||
|
for (var listener in listeners) {
|
||||||
|
final response =
|
||||||
|
await Function.apply(listener, [eventName, eventPayload]);
|
||||||
|
if (halt == true && response != null) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
if (response == false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return halt == true ? null : listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
// void _addToSubject(String eventName, dynamic payload) {
|
||||||
|
// if (_subjects.containsKey(eventName)) {
|
||||||
|
// _subjects[eventName]!.add(payload);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Function> getListeners(String eventName) {
|
||||||
|
var listeners = <Function>[
|
||||||
|
...(_listeners[eventName] ?? []),
|
||||||
|
...(_wildcardsCache[eventName] ?? _getWildcardListeners(eventName)),
|
||||||
|
...(_eventBusListeners[eventName] ?? []),
|
||||||
|
];
|
||||||
|
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Function> _getWildcardListeners(String eventName) {
|
||||||
|
final wildcardListeners = <Function>[];
|
||||||
|
for (var entry in _wildcards.entries) {
|
||||||
|
if (_isWildcardMatch(entry.key, eventName)) {
|
||||||
|
wildcardListeners.addAll(entry.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_wildcardsCache[eventName] = wildcardListeners;
|
||||||
|
return wildcardListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void forget(String event) {
|
||||||
|
// Remove from _listeners
|
||||||
|
_listeners.remove(event);
|
||||||
|
|
||||||
|
// Remove from _subjects
|
||||||
|
if (_subjects.containsKey(event)) {
|
||||||
|
_subjects[event]?.close();
|
||||||
|
_subjects.remove(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel and remove EventBus subscription
|
||||||
|
_eventBusSubscriptions[event]?.cancel();
|
||||||
|
_eventBusSubscriptions.remove(event);
|
||||||
|
|
||||||
|
// Remove from wildcards if applicable
|
||||||
|
if (event.contains('*')) {
|
||||||
|
_wildcards.remove(event);
|
||||||
|
_wildcardsCache.clear();
|
||||||
|
} else {
|
||||||
|
// If it's not a wildcard, we need to remove it from any matching wildcard listeners
|
||||||
|
_wildcards.forEach((pattern, listeners) {
|
||||||
|
if (_isWildcardMatch(pattern, event)) {
|
||||||
|
_wildcards[pattern] = listeners
|
||||||
|
.where((listener) => listener != _listeners[event])
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_wildcardsCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any 'until' completers for this event
|
||||||
|
_untilCompleters.remove(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void forgetPushed() {
|
||||||
|
_listeners.removeWhere((key, _) => key.endsWith('_pushed'));
|
||||||
|
_eventBusListeners.removeWhere((key, _) => key.endsWith('_pushed'));
|
||||||
|
// Note: We're not clearing all EventBus listeners here, as that might affect other parts of your application
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setQueueResolver(Function resolver) {
|
||||||
|
_queueResolver = resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setTransactionManagerResolver(Function resolver) {
|
||||||
|
_transactionManagerResolver = resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add these methods for testing purposes
|
||||||
|
void triggerQueueResolver() {
|
||||||
|
_queueResolver();
|
||||||
|
}
|
||||||
|
|
||||||
|
void triggerTransactionManagerResolver() {
|
||||||
|
_transactionManagerResolver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, List<Function>> getRawListeners() {
|
||||||
|
return Map.unmodifiable(_listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldBroadcast(List payload) {
|
||||||
|
return payload.isNotEmpty && payload[0] is ShouldBroadcast;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _broadcastEvent(dynamic event) async {
|
||||||
|
// Implement broadcasting logic here
|
||||||
|
// For now, we'll just print a message
|
||||||
|
print('Broadcasting event: ${event.runtimeType}');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isWildcardMatch(String pattern, String eventName) {
|
||||||
|
final regExp = RegExp('^${pattern.replaceAll('*', '.*')}\$');
|
||||||
|
return regExp.hasMatch(eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldQueue(List payload) {
|
||||||
|
return payload.isNotEmpty && payload[0] is ShouldQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _queueEvent(String eventName, dynamic payload) async {
|
||||||
|
_mqClient?.sendMessage(
|
||||||
|
exchangeName: _eventsExchange,
|
||||||
|
routingKey: _eventsQueue,
|
||||||
|
message: Message(
|
||||||
|
payload: {'event': eventName, 'payload': payload},
|
||||||
|
timestamp: DateTime.now().toIso8601String(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updated on<T> method
|
||||||
|
Stream<T> on<T>(String event) {
|
||||||
|
return (_subjects
|
||||||
|
.putIfAbsent(event, () => BehaviorSubject<dynamic>())
|
||||||
|
.stream as Stream<T>)
|
||||||
|
.where((event) => event is T)
|
||||||
|
.cast<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// In your Dispatcher class
|
||||||
|
void setMQClient(MQClient client) {
|
||||||
|
_mqClient = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to close the MQClient connection
|
||||||
|
Future<void> close() async {
|
||||||
|
_mqClient?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't forget to close the subjects when they're no longer needed
|
||||||
|
void dispose() {
|
||||||
|
for (var subject in _subjects.values) {
|
||||||
|
subject.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ... rest of the code (DispatcherContract, EventBusSubscriber, etc.) remains the same
|
||||||
|
|
||||||
|
abstract class DispatcherContract {
|
||||||
|
void listen(dynamic events, dynamic listener);
|
||||||
|
bool hasListeners(String eventName);
|
||||||
|
void push(String event, [dynamic payload]);
|
||||||
|
Future<void> flush(String event);
|
||||||
|
void subscribe(dynamic subscriber);
|
||||||
|
Future<dynamic> until(dynamic event, [dynamic payload]);
|
||||||
|
Future<dynamic> dispatch(dynamic event, [dynamic payload, bool halt]);
|
||||||
|
List<Function> getListeners(String eventName);
|
||||||
|
void forget(String event);
|
||||||
|
void forgetPushed();
|
||||||
|
void setQueueResolver(Function resolver);
|
||||||
|
void setTransactionManagerResolver(Function resolver);
|
||||||
|
Map<String, List<Function>> getRawListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper class for EventBus subscribers
|
||||||
|
abstract class EventBusSubscriber {
|
||||||
|
void subscribe(EventBus eventBus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin to simulate Macroable trait
|
||||||
|
mixin Macroable {
|
||||||
|
// Implementation of Macroable functionality
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin to simulate ReflectsClosures trait
|
||||||
|
mixin ReflectsClosures {
|
||||||
|
// Implementation of ReflectsClosures functionality
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not already defined, you might need to create an Event class
|
||||||
|
class Event {
|
||||||
|
final String name;
|
||||||
|
final dynamic data;
|
||||||
|
|
||||||
|
Event(this.name, this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom AppEvent subclasses for handling different event types
|
||||||
|
class StringBasedEvent extends AppEvent {
|
||||||
|
final String eventName;
|
||||||
|
final dynamic payload;
|
||||||
|
|
||||||
|
StringBasedEvent(this.eventName, this.payload);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [eventName, payload];
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomAppEvent extends AppEvent {
|
||||||
|
final String eventName;
|
||||||
|
final dynamic payload;
|
||||||
|
|
||||||
|
CustomAppEvent(this.eventName, this.payload);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [eventName, payload];
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a simple implementation of Reflector that does nothing
|
||||||
|
class EmptyReflector implements Reflector {
|
||||||
|
const EmptyReflector();
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedType reflectType(Type type) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedInstance reflectInstance(Object object) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedType reflectFutureOf(Type type) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? getName(Symbol symbol) {
|
||||||
|
// TODO: implement getName
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedClass? reflectClass(Type clazz) {
|
||||||
|
// TODO: implement reflectClass
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedFunction? reflectFunction(Function function) {
|
||||||
|
// TODO: implement reflectFunction
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
21
packages/events/pubspec.yaml
Normal file
21
packages/events/pubspec.yaml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
name: platform_events
|
||||||
|
description: The Events Package for the Protevus Platform
|
||||||
|
version: 0.0.1
|
||||||
|
homepage: https://protevus.com
|
||||||
|
documentation: https://docs.protevus.com
|
||||||
|
repository: https://github.com/protevus/platformo
|
||||||
|
environment:
|
||||||
|
sdk: ^3.4.2
|
||||||
|
|
||||||
|
# Add regular dependencies here.
|
||||||
|
dependencies:
|
||||||
|
platform_container: ^9.0.0
|
||||||
|
angel3_mq: ^9.0.0
|
||||||
|
angel3_event_bus: ^9.0.0
|
||||||
|
platform_core: ^9.0.0
|
||||||
|
angel3_reactivex: ^0.27.5
|
||||||
|
# path: ^1.8.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^3.0.0
|
||||||
|
test: ^1.24.0
|
430
packages/events/test/event_test.dart
Normal file
430
packages/events/test/event_test.dart
Normal file
|
@ -0,0 +1,430 @@
|
||||||
|
import 'package:angel3_event_bus/res/app_event.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:platform_container/container.dart';
|
||||||
|
import 'package:angel3_mq/mq.dart';
|
||||||
|
import 'package:platform_events/dispatcher.dart'; // Replace with the actual import path
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Dispatcher dispatcher;
|
||||||
|
late MockMQClient mockMQClient;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
var container = Container(EmptyReflector());
|
||||||
|
dispatcher = Dispatcher(container);
|
||||||
|
mockMQClient = MockMQClient();
|
||||||
|
dispatcher.mqClient = mockMQClient; // Use the setter
|
||||||
|
|
||||||
|
// Clear the queue before each test
|
||||||
|
mockMQClient.queuedMessages.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Dispatcher', () {
|
||||||
|
test('listen and dispatch', () async {
|
||||||
|
var callCount = 0;
|
||||||
|
dispatcher.listen('test_event', (dynamic event, dynamic payload) {
|
||||||
|
expect(event, equals('test_event'));
|
||||||
|
expect(payload, equals(['test_payload']));
|
||||||
|
callCount++;
|
||||||
|
});
|
||||||
|
await dispatcher.dispatch('test_event', ['test_payload']);
|
||||||
|
expect(callCount, equals(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('wildcard listener', () async {
|
||||||
|
var callCount = 0;
|
||||||
|
dispatcher.listen('test.*', (dynamic event, dynamic payload) {
|
||||||
|
expect(event, matches(RegExp(r'^test\.')));
|
||||||
|
callCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
await dispatcher.dispatch('test.one', ['payload1']);
|
||||||
|
await dispatcher.dispatch('test.two', ['payload2']);
|
||||||
|
expect(callCount, equals(2));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasListeners', () {
|
||||||
|
dispatcher.listen('test_event', (dynamic event, dynamic payload) {});
|
||||||
|
expect(dispatcher.hasListeners('test_event'), isTrue);
|
||||||
|
expect(dispatcher.hasListeners('non_existent_event'), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('until', () async {
|
||||||
|
// Test without pushing the event immediately
|
||||||
|
var futureResult = dispatcher.until('test_event');
|
||||||
|
|
||||||
|
// Use a small delay to ensure the until listener is set up
|
||||||
|
await Future.delayed(Duration(milliseconds: 10));
|
||||||
|
|
||||||
|
await dispatcher.dispatch('test_event', ['test_payload']);
|
||||||
|
var result = await futureResult;
|
||||||
|
expect(result, equals(['test_payload']));
|
||||||
|
|
||||||
|
// Test with pushing the event immediately
|
||||||
|
result =
|
||||||
|
await dispatcher.until('another_test_event', ['another_payload']);
|
||||||
|
expect(result, equals(['another_payload']));
|
||||||
|
}, timeout: Timeout(Duration(seconds: 5))); // Add a reasonable timeout
|
||||||
|
|
||||||
|
test('forget', () async {
|
||||||
|
var callCount = 0;
|
||||||
|
dispatcher.listen('test_event', (dynamic event, dynamic payload) {
|
||||||
|
callCount++;
|
||||||
|
});
|
||||||
|
await dispatcher.dispatch('test_event');
|
||||||
|
expect(callCount, equals(1));
|
||||||
|
|
||||||
|
dispatcher.forget('test_event');
|
||||||
|
await dispatcher.dispatch('test_event');
|
||||||
|
expect(callCount, equals(1)); // Should not increase
|
||||||
|
});
|
||||||
|
|
||||||
|
test('push and flush', () async {
|
||||||
|
print('Starting push and flush test');
|
||||||
|
|
||||||
|
// Push 4 messages
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
dispatcher.push('delayed_event', ['delayed_payload_$i']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that 4 messages were queued
|
||||||
|
expect(mockMQClient.queuedMessages['delayed_events_queue']?.length,
|
||||||
|
equals(4),
|
||||||
|
reason: 'Should have queued exactly 4 messages');
|
||||||
|
|
||||||
|
print(
|
||||||
|
'Queued messages: ${mockMQClient.queuedMessages['delayed_events_queue']?.length}');
|
||||||
|
|
||||||
|
var callCount = 0;
|
||||||
|
var processedPayloads = <String>[];
|
||||||
|
|
||||||
|
// Remove any existing listeners
|
||||||
|
dispatcher.forget('delayed_event');
|
||||||
|
|
||||||
|
dispatcher.listen('delayed_event', (dynamic event, dynamic payload) {
|
||||||
|
print('Listener called with payload: $payload');
|
||||||
|
expect(event, equals('delayed_event'));
|
||||||
|
expect(payload[0], startsWith('delayed_payload_'));
|
||||||
|
processedPayloads.add(payload[0]);
|
||||||
|
callCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
await dispatcher.flush('delayed_event');
|
||||||
|
|
||||||
|
print('After flush - Call count: $callCount');
|
||||||
|
print('Processed payloads: $processedPayloads');
|
||||||
|
|
||||||
|
expect(callCount, equals(4), reason: 'Should process exactly 4 messages');
|
||||||
|
expect(processedPayloads.toSet().length, equals(4),
|
||||||
|
reason: 'All payloads should be unique');
|
||||||
|
|
||||||
|
// Verify that all messages were removed from the queue
|
||||||
|
expect(mockMQClient.queuedMessages['delayed_events_queue']?.length,
|
||||||
|
equals(0),
|
||||||
|
reason: 'Queue should be empty after flush');
|
||||||
|
|
||||||
|
// Flush again to ensure no more messages are processed
|
||||||
|
await dispatcher.flush('delayed_event');
|
||||||
|
expect(callCount, equals(4),
|
||||||
|
reason: 'Should still be 4 after second flush');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shouldBroadcast', () async {
|
||||||
|
var broadcastEvent = BroadcastTestEvent();
|
||||||
|
var callCount = 0;
|
||||||
|
|
||||||
|
dispatcher.listen('BroadcastTestEvent', (dynamic event, dynamic payload) {
|
||||||
|
callCount++;
|
||||||
|
});
|
||||||
|
|
||||||
|
await dispatcher.dispatch(broadcastEvent);
|
||||||
|
expect(callCount, equals(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shouldQueue', () async {
|
||||||
|
var queueEvent = QueueTestEvent();
|
||||||
|
await dispatcher.dispatch(queueEvent);
|
||||||
|
expect(mockMQClient.queuedMessages['events_queue'], isNotEmpty);
|
||||||
|
expect(mockMQClient.queuedMessages['events_queue']!.first.payload,
|
||||||
|
containsPair('event', 'QueueTestEvent'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('forgetPushed removes only pushed events', () {
|
||||||
|
dispatcher.listen('event_pushed', (_, __) {});
|
||||||
|
dispatcher.listen('normal_event', (_, __) {});
|
||||||
|
|
||||||
|
dispatcher.forgetPushed();
|
||||||
|
|
||||||
|
expect(dispatcher.hasListeners('event_pushed'), isFalse);
|
||||||
|
expect(dispatcher.hasListeners('normal_event'), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setQueueResolver and setTransactionManagerResolver', () {
|
||||||
|
var queueResolverCalled = false;
|
||||||
|
var transactionManagerResolverCalled = false;
|
||||||
|
|
||||||
|
dispatcher.setQueueResolver(() {
|
||||||
|
queueResolverCalled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatcher.setTransactionManagerResolver(() {
|
||||||
|
transactionManagerResolverCalled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger the resolvers
|
||||||
|
dispatcher.triggerQueueResolver();
|
||||||
|
dispatcher.triggerTransactionManagerResolver();
|
||||||
|
|
||||||
|
expect(queueResolverCalled, isTrue);
|
||||||
|
expect(transactionManagerResolverCalled, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getRawListeners returns unmodifiable map', () {
|
||||||
|
dispatcher.listen('test_event', (_, __) {});
|
||||||
|
var rawListeners = dispatcher.getRawListeners();
|
||||||
|
|
||||||
|
expect(rawListeners, isA<Map<String, List<Function>>>());
|
||||||
|
expect(() => rawListeners['new_event'] = [], throwsUnsupportedError);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiple listeners for same event', () async {
|
||||||
|
var callCount1 = 0;
|
||||||
|
var callCount2 = 0;
|
||||||
|
|
||||||
|
dispatcher.listen('multi_event', (_, __) => callCount1++);
|
||||||
|
dispatcher.listen('multi_event', (_, __) => callCount2++);
|
||||||
|
|
||||||
|
await dispatcher.dispatch('multi_event');
|
||||||
|
|
||||||
|
expect(callCount1, equals(1));
|
||||||
|
expect(callCount2, equals(1));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class MQClientWrapper {
|
||||||
|
Stream<Message> fetchQueue(String queueId);
|
||||||
|
void sendMessage({
|
||||||
|
required Message message,
|
||||||
|
String? exchangeName,
|
||||||
|
String? routingKey,
|
||||||
|
});
|
||||||
|
String declareQueue(String queueId);
|
||||||
|
void declareExchange({
|
||||||
|
required String exchangeName,
|
||||||
|
required ExchangeType exchangeType,
|
||||||
|
});
|
||||||
|
void bindQueue({
|
||||||
|
required String queueId,
|
||||||
|
required String exchangeName,
|
||||||
|
String? bindingKey,
|
||||||
|
});
|
||||||
|
void close();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RealMQClientWrapper implements MQClientWrapper {
|
||||||
|
final MQClient _client;
|
||||||
|
|
||||||
|
RealMQClientWrapper(this._client);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Message> fetchQueue(String queueId) => _client.fetchQueue(queueId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void sendMessage({
|
||||||
|
required Message message,
|
||||||
|
String? exchangeName,
|
||||||
|
String? routingKey,
|
||||||
|
}) =>
|
||||||
|
_client.sendMessage(
|
||||||
|
message: message,
|
||||||
|
exchangeName: exchangeName,
|
||||||
|
routingKey: routingKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String declareQueue(String queueId) => _client.declareQueue(queueId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void declareExchange({
|
||||||
|
required String exchangeName,
|
||||||
|
required ExchangeType exchangeType,
|
||||||
|
}) =>
|
||||||
|
_client.declareExchange(
|
||||||
|
exchangeName: exchangeName,
|
||||||
|
exchangeType: exchangeType,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void bindQueue({
|
||||||
|
required String queueId,
|
||||||
|
required String exchangeName,
|
||||||
|
String? bindingKey,
|
||||||
|
}) =>
|
||||||
|
_client.bindQueue(
|
||||||
|
queueId: queueId,
|
||||||
|
exchangeName: exchangeName,
|
||||||
|
bindingKey: bindingKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void close() => _client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockMQClient implements MQClient {
|
||||||
|
Map<String, List<Message>> queuedMessages = {};
|
||||||
|
int _messageIdCounter = 0;
|
||||||
|
|
||||||
|
void queueMessage(String queueName, Message message) {
|
||||||
|
queuedMessages.putIfAbsent(queueName, () => []).add(message);
|
||||||
|
print(
|
||||||
|
'Queued message. Queue $queueName now has ${queuedMessages[queueName]?.length} messages');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String declareQueue(String queueId) {
|
||||||
|
queuedMessages[queueId] = [];
|
||||||
|
return queueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void deleteQueue(String queueId) {
|
||||||
|
queuedMessages.remove(queueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<Message> fetchQueue(String queueId) {
|
||||||
|
print('Fetching queue: $queueId');
|
||||||
|
return Stream.fromIterable(queuedMessages[queueId] ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void sendMessage({
|
||||||
|
required Message message,
|
||||||
|
String? exchangeName,
|
||||||
|
String? routingKey,
|
||||||
|
}) {
|
||||||
|
print('Sending message to queue: $routingKey');
|
||||||
|
final newMessage = Message(
|
||||||
|
payload: message.payload,
|
||||||
|
headers: message.headers,
|
||||||
|
timestamp: message.timestamp,
|
||||||
|
id: 'msg_${_messageIdCounter++}',
|
||||||
|
);
|
||||||
|
queueMessage(routingKey ?? '', newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Message? getLatestMessage(String queueId) {
|
||||||
|
final messages = queuedMessages[queueId];
|
||||||
|
return messages?.isNotEmpty == true ? messages!.last : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void bindQueue({
|
||||||
|
required String queueId,
|
||||||
|
required String exchangeName,
|
||||||
|
String? bindingKey,
|
||||||
|
}) {
|
||||||
|
// Implement if needed for your tests
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void unbindQueue({
|
||||||
|
required String queueId,
|
||||||
|
required String exchangeName,
|
||||||
|
String? bindingKey,
|
||||||
|
}) {
|
||||||
|
// Implement if needed for your tests
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void declareExchange({
|
||||||
|
required String exchangeName,
|
||||||
|
required ExchangeType exchangeType,
|
||||||
|
}) {
|
||||||
|
// Implement if needed for your tests
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void deleteExchange(String exchangeName) {
|
||||||
|
// Implement if needed for your tests
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> listQueues() {
|
||||||
|
return queuedMessages.keys.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void close() {
|
||||||
|
queuedMessages.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void deleteMessage(String queueId, Message message) {
|
||||||
|
print('Deleting message from queue: $queueId');
|
||||||
|
queuedMessages[queueId]?.removeWhere((m) => m.id == message.id);
|
||||||
|
print(
|
||||||
|
'After deletion, queue $queueId has ${queuedMessages[queueId]?.length} messages');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BroadcastTestEvent implements AppEvent, ShouldBroadcast {
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool? get stringify => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime get timestamp => DateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
class QueueTestEvent implements AppEvent, ShouldQueue {
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool? get stringify => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime get timestamp => DateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a simple implementation of Reflector that does nothing
|
||||||
|
class EmptyReflector implements Reflector {
|
||||||
|
const EmptyReflector();
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedType reflectType(Type type) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedInstance reflectInstance(Object object) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedType reflectFutureOf(Type type) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? getName(Symbol symbol) {
|
||||||
|
// TODO: implement getName
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedClass? reflectClass(Type clazz) {
|
||||||
|
// TODO: implement reflectClass
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedFunction? reflectFunction(Function function) {
|
||||||
|
// TODO: implement reflectFunction
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
7
packages/pipeline/.gitignore
vendored
Normal file
7
packages/pipeline/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
# Avoid committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
3
packages/pipeline/CHANGELOG.md
Normal file
3
packages/pipeline/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version.
|
10
packages/pipeline/LICENSE.md
Normal file
10
packages/pipeline/LICENSE.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
The Laravel Framework is Copyright (c) Taylor Otwell
|
||||||
|
The Fabric Framework is Copyright (c) Vieo, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
380
packages/pipeline/README.md
Normal file
380
packages/pipeline/README.md
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
<p align="center"><a href="https://protevus.com" target="_blank"><img src="https://git.protevus.com/protevus/branding/raw/branch/main/protevus-logo-bg.png"></a></p>
|
||||||
|
|
||||||
|
# Platform Pipeline
|
||||||
|
|
||||||
|
A Laravel-compatible pipeline implementation in Dart, providing a robust way to pass objects through a series of operations.
|
||||||
|
|
||||||
|
[![Pub Version](https://img.shields.io/pub/v/platform_pipeline)]()
|
||||||
|
[![Build Status](https://img.shields.io/github/workflow/status/platform/pipeline/tests)]()
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Features](#features)
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Basic Usage](#basic-usage)
|
||||||
|
- [Class-Based Pipes](#class-based-pipes)
|
||||||
|
- [Invokable Classes](#invokable-classes)
|
||||||
|
- [Using Different Method Names](#using-different-method-names)
|
||||||
|
- [Passing Parameters to Pipes](#passing-parameters-to-pipes)
|
||||||
|
- [Early Pipeline Termination](#early-pipeline-termination)
|
||||||
|
- [Conditional Pipeline Execution](#conditional-pipeline-execution)
|
||||||
|
- [Advanced Usage](#advanced-usage)
|
||||||
|
- [Working with Objects](#working-with-objects)
|
||||||
|
- [Async Operations](#async-operations)
|
||||||
|
- [Laravel API Compatibility](#laravel-api-compatibility)
|
||||||
|
- [Comparison with Laravel](#comparison-with-laravel)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
- [Testing](#testing)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Platform Pipeline is a 100% API-compatible port of Laravel's Pipeline to Dart. It allows you to pass an object through a series of operations (pipes) in a fluent, maintainable way. Each pipe can examine, modify, or replace the object before passing it to the next pipe in the sequence.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 💯 100% Laravel Pipeline API compatibility
|
||||||
|
- 🔄 Support for class-based and callable pipes
|
||||||
|
- 🎯 Dependency injection through container integration
|
||||||
|
- ⚡ Async operation support
|
||||||
|
- 🔀 Conditional pipeline execution
|
||||||
|
- 🎭 Method name customization via `via()`
|
||||||
|
- 🎁 Parameter passing to pipes
|
||||||
|
- 🛑 Early pipeline termination
|
||||||
|
- 🧪 Comprehensive test coverage
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Dart SDK: >=2.17.0 <4.0.0
|
||||||
|
- platform_container: ^1.0.0
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add this to your package's `pubspec.yaml` file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
platform_pipeline: ^1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:platform_pipeline/pipeline.dart';
|
||||||
|
import 'package:platform_container/container.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
// Create a container instance
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
// Create a pipeline
|
||||||
|
var result = await Pipeline(container)
|
||||||
|
.send('Hello')
|
||||||
|
.through([
|
||||||
|
(String value, next) => next(value + ' World'),
|
||||||
|
(String value, next) => next(value + '!'),
|
||||||
|
])
|
||||||
|
.then((value) => value);
|
||||||
|
|
||||||
|
print(result); // Outputs: Hello World!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Class-Based Pipes
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class UppercasePipe {
|
||||||
|
Future<String> handle(String value, Function next) async {
|
||||||
|
return next(value.toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddExclamationPipe {
|
||||||
|
Future<String> handle(String value, Function next) async {
|
||||||
|
return next(value + '!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
var result = await Pipeline(container)
|
||||||
|
.send('hello')
|
||||||
|
.through([
|
||||||
|
UppercasePipe(),
|
||||||
|
AddExclamationPipe(),
|
||||||
|
])
|
||||||
|
.then((value) => value);
|
||||||
|
|
||||||
|
print(result); // Outputs: HELLO!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Invokable Classes
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class TransformPipe {
|
||||||
|
Future<String> call(String value, Function next) async {
|
||||||
|
return next(value.toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
var result = await Pipeline(container)
|
||||||
|
.send('hello')
|
||||||
|
.through([TransformPipe()])
|
||||||
|
.then((value) => value);
|
||||||
|
|
||||||
|
print(result); // Outputs: HELLO
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Different Method Names
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class CustomPipe {
|
||||||
|
Future<String> transform(String value, Function next) async {
|
||||||
|
return next(value.toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
var result = await Pipeline(container)
|
||||||
|
.send('hello')
|
||||||
|
.through([CustomPipe()])
|
||||||
|
.via('transform')
|
||||||
|
.then((value) => value);
|
||||||
|
|
||||||
|
print(result); // Outputs: HELLO
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Passing Parameters to Pipes
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class PrefixPipe {
|
||||||
|
Future<String> handle(
|
||||||
|
String value,
|
||||||
|
Function next, [
|
||||||
|
String prefix = '',
|
||||||
|
]) async {
|
||||||
|
return next('$prefix$value');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var container = Container();
|
||||||
|
container.registerFactory<PrefixPipe>((c) => PrefixPipe());
|
||||||
|
|
||||||
|
var pipeline = Pipeline(container);
|
||||||
|
pipeline.registerPipeType('PrefixPipe', PrefixPipe);
|
||||||
|
|
||||||
|
var result = await pipeline
|
||||||
|
.send('World')
|
||||||
|
.through('PrefixPipe:Hello ')
|
||||||
|
.then((value) => value);
|
||||||
|
|
||||||
|
print(result); // Outputs: Hello World
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Early Pipeline Termination
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() async {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
var result = await Pipeline(container)
|
||||||
|
.send('hello')
|
||||||
|
.through([
|
||||||
|
(value, next) => 'TERMINATED', // Pipeline stops here
|
||||||
|
(value, next) => next('NEVER REACHED'),
|
||||||
|
])
|
||||||
|
.then((value) => value);
|
||||||
|
|
||||||
|
print(result); // Outputs: TERMINATED
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conditional Pipeline Execution
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() async {
|
||||||
|
var container = Container();
|
||||||
|
var shouldTransform = true;
|
||||||
|
|
||||||
|
var result = await Pipeline(container)
|
||||||
|
.send('hello')
|
||||||
|
.when(() => shouldTransform, (Pipeline pipeline) {
|
||||||
|
pipeline.pipe([
|
||||||
|
(value, next) => next(value.toUpperCase()),
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
.then((value) => value);
|
||||||
|
|
||||||
|
print(result); // Outputs: HELLO
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Working with Objects
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class User {
|
||||||
|
String name;
|
||||||
|
int age;
|
||||||
|
|
||||||
|
User(this.name, this.age);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AgeValidationPipe {
|
||||||
|
Future<User> handle(User user, Function next) async {
|
||||||
|
if (user.age < 18) {
|
||||||
|
throw Exception('User must be 18 or older');
|
||||||
|
}
|
||||||
|
return next(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NameFormattingPipe {
|
||||||
|
Future<User> handle(User user, Function next) async {
|
||||||
|
user.name = user.name.trim().toLowerCase();
|
||||||
|
return next(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
var user = User('John Doe ', 20);
|
||||||
|
|
||||||
|
try {
|
||||||
|
user = await Pipeline(container)
|
||||||
|
.send(user)
|
||||||
|
.through([
|
||||||
|
AgeValidationPipe(),
|
||||||
|
NameFormattingPipe(),
|
||||||
|
])
|
||||||
|
.then((value) => value);
|
||||||
|
|
||||||
|
print('${user.name} is ${user.age} years old');
|
||||||
|
// Outputs: john doe is 20 years old
|
||||||
|
} catch (e) {
|
||||||
|
print('Validation failed: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Async Operations
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class AsyncTransformPipe {
|
||||||
|
Future<String> handle(String value, Function next) async {
|
||||||
|
// Simulate async operation
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
|
return next(value.toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
var result = await Pipeline(container)
|
||||||
|
.send('hello')
|
||||||
|
.through([AsyncTransformPipe()])
|
||||||
|
.then((value) => value);
|
||||||
|
|
||||||
|
print(result); // Outputs after 1 second: HELLO
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Laravel API Compatibility
|
||||||
|
|
||||||
|
This package maintains 100% API compatibility with Laravel's Pipeline implementation. All Laravel Pipeline features are supported:
|
||||||
|
|
||||||
|
- `send()` - Set the object being passed through the pipeline
|
||||||
|
- `through()` - Set the array of pipes
|
||||||
|
- `pipe()` - Push additional pipes onto the pipeline
|
||||||
|
- `via()` - Set the method to call on the pipes
|
||||||
|
- `then()` - Run the pipeline with a final destination callback
|
||||||
|
- `thenReturn()` - Run the pipeline and return the result
|
||||||
|
|
||||||
|
## Comparison with Laravel
|
||||||
|
|
||||||
|
| Feature | Laravel | Platform Pipeline |
|
||||||
|
|---------|---------|------------------|
|
||||||
|
| API Methods | ✓ | ✓ |
|
||||||
|
| Container Integration | ✓ | ✓ |
|
||||||
|
| Pipe Types | Class, Callable | Class, Callable |
|
||||||
|
| Async Support | ✗ | ✓ |
|
||||||
|
| Type Safety | ✗ | ✓ |
|
||||||
|
| Parameter Passing | ✓ | ✓ |
|
||||||
|
| Early Termination | ✓ | ✓ |
|
||||||
|
| Method Customization | ✓ | ✓ |
|
||||||
|
| Conditional Execution | ✓ | ✓ |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. Container Not Provided
|
||||||
|
```dart
|
||||||
|
// ❌ Wrong
|
||||||
|
var pipeline = Pipeline(null);
|
||||||
|
|
||||||
|
// ✓ Correct
|
||||||
|
var container = Container();
|
||||||
|
var pipeline = Pipeline(container);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Missing Type Registration
|
||||||
|
```dart
|
||||||
|
// ❌ Wrong
|
||||||
|
pipeline.through('CustomPipe:param');
|
||||||
|
|
||||||
|
// ✓ Correct
|
||||||
|
pipeline.registerPipeType('CustomPipe', CustomPipe);
|
||||||
|
pipeline.through('CustomPipe:param');
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Incorrect Method Name
|
||||||
|
```dart
|
||||||
|
// ❌ Wrong
|
||||||
|
class CustomPipe {
|
||||||
|
void process(value, next) {} // Wrong method name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✓ Correct
|
||||||
|
class CustomPipe {
|
||||||
|
void handle(value, next) {} // Default method name
|
||||||
|
}
|
||||||
|
// Or specify the method name:
|
||||||
|
pipeline.via('process').through([CustomPipe()]);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run the tests with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dart test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This package is open-sourced software licensed under the MIT license.
|
30
packages/pipeline/analysis_options.yaml
Normal file
30
packages/pipeline/analysis_options.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
38
packages/pipeline/examples/async_pipeline.dart
Normal file
38
packages/pipeline/examples/async_pipeline.dart
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:platform_core/core.dart';
|
||||||
|
import 'package:platform_core/http.dart';
|
||||||
|
import 'package:platform_container/mirrors.dart';
|
||||||
|
import 'package:platform_pipeline/pipeline.dart';
|
||||||
|
|
||||||
|
class AsyncGreetingPipe {
|
||||||
|
Future<dynamic> handle(String input, Function next) async {
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
|
return next('Hello, $input');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AsyncExclamationPipe {
|
||||||
|
Future<dynamic> handle(String input, Function next) async {
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
|
return next('$input!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var app = Application(reflector: MirrorsReflector());
|
||||||
|
var http = PlatformHttp(app);
|
||||||
|
|
||||||
|
app.container.registerSingleton((c) => Pipeline(c));
|
||||||
|
|
||||||
|
app.get('/', (req, res) async {
|
||||||
|
var pipeline = app.container.make<Pipeline>();
|
||||||
|
var result = await pipeline
|
||||||
|
.send('World')
|
||||||
|
.through(['AsyncGreetingPipe', 'AsyncExclamationPipe']).then(
|
||||||
|
(result) => result.toUpperCase());
|
||||||
|
|
||||||
|
res.write(result); // Outputs: "HELLO, WORLD!" (after 2 seconds)
|
||||||
|
});
|
||||||
|
|
||||||
|
await http.startServer('localhost', 3000);
|
||||||
|
print('Server started on http://localhost:3000');
|
||||||
|
}
|
36
packages/pipeline/examples/basic_usage.dart
Normal file
36
packages/pipeline/examples/basic_usage.dart
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import 'package:platform_core/core.dart';
|
||||||
|
import 'package:platform_core/http.dart';
|
||||||
|
import 'package:platform_container/mirrors.dart';
|
||||||
|
import 'package:platform_pipeline/pipeline.dart';
|
||||||
|
|
||||||
|
class GreetingPipe {
|
||||||
|
dynamic handle(String input, Function next) {
|
||||||
|
return next('Hello, $input');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExclamationPipe {
|
||||||
|
dynamic handle(String input, Function next) {
|
||||||
|
return next('$input!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var app = Application(reflector: MirrorsReflector());
|
||||||
|
var http = PlatformHttp(app);
|
||||||
|
|
||||||
|
app.container.registerSingleton((c) => Pipeline(c));
|
||||||
|
|
||||||
|
app.get('/', (req, res) async {
|
||||||
|
var pipeline = app.container.make<Pipeline>();
|
||||||
|
var result = await pipeline
|
||||||
|
.send('World')
|
||||||
|
.through(['GreetingPipe', 'ExclamationPipe']).then(
|
||||||
|
(result) => result.toUpperCase());
|
||||||
|
|
||||||
|
res.write(result); // Outputs: "HELLO, WORLD!"
|
||||||
|
});
|
||||||
|
|
||||||
|
await http.startServer('localhost', 3000);
|
||||||
|
print('Server started on http://localhost:3000');
|
||||||
|
}
|
34
packages/pipeline/examples/error_handling.dart
Normal file
34
packages/pipeline/examples/error_handling.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:platform_core/core.dart';
|
||||||
|
import 'package:platform_core/http.dart';
|
||||||
|
import 'package:platform_container/mirrors.dart';
|
||||||
|
import 'package:platform_pipeline/pipeline.dart';
|
||||||
|
|
||||||
|
class ErrorPipe {
|
||||||
|
dynamic handle(String input, Function next) {
|
||||||
|
throw Exception('Simulated error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var app = Application(reflector: MirrorsReflector());
|
||||||
|
var http = PlatformHttp(app);
|
||||||
|
|
||||||
|
app.container.registerSingleton((c) => Pipeline(c));
|
||||||
|
|
||||||
|
app.get('/', (req, res) async {
|
||||||
|
var pipeline = app.container.make<Pipeline>();
|
||||||
|
try {
|
||||||
|
await pipeline
|
||||||
|
.send('World')
|
||||||
|
.through(['ErrorPipe']).then((result) => result.toUpperCase());
|
||||||
|
} catch (e) {
|
||||||
|
res.write('Error occurred: ${e.toString()}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.write('This should not be reached');
|
||||||
|
});
|
||||||
|
|
||||||
|
await http.startServer('localhost', 3000);
|
||||||
|
print('Server started on http://localhost:3000');
|
||||||
|
}
|
35
packages/pipeline/examples/mixed_pipes.dart
Normal file
35
packages/pipeline/examples/mixed_pipes.dart
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import 'package:platform_core/core.dart';
|
||||||
|
import 'package:platform_core/http.dart';
|
||||||
|
import 'package:platform_container/mirrors.dart';
|
||||||
|
import 'package:platform_pipeline/pipeline.dart';
|
||||||
|
|
||||||
|
class GreetingPipe {
|
||||||
|
dynamic handle(String input, Function next) {
|
||||||
|
return next('Hello, $input');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var app = Application(reflector: MirrorsReflector());
|
||||||
|
var http = PlatformHttp(app);
|
||||||
|
|
||||||
|
app.container.registerSingleton((c) => Pipeline(c));
|
||||||
|
|
||||||
|
app.get('/', (req, res) async {
|
||||||
|
var pipeline = app.container.make<Pipeline>();
|
||||||
|
var result = await pipeline.send('World').through([
|
||||||
|
'GreetingPipe',
|
||||||
|
(String input, Function next) => next('$input!'),
|
||||||
|
(String input, Function next) async {
|
||||||
|
await Future.delayed(Duration(seconds: 1));
|
||||||
|
return next(input.toUpperCase());
|
||||||
|
},
|
||||||
|
]).then((result) => 'Final result: $result');
|
||||||
|
|
||||||
|
res.write(
|
||||||
|
result); // Outputs: "Final result: HELLO, WORLD!" (after 1 second)
|
||||||
|
});
|
||||||
|
|
||||||
|
await http.startServer('localhost', 3000);
|
||||||
|
print('Server started on http://localhost:3000');
|
||||||
|
}
|
5
packages/pipeline/lib/pipeline.dart
Normal file
5
packages/pipeline/lib/pipeline.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
library;
|
||||||
|
|
||||||
|
export 'src/pipeline.dart';
|
||||||
|
export 'src/conditionable.dart';
|
||||||
|
export 'src/pipeline_contract.dart';
|
16
packages/pipeline/lib/src/conditionable.dart
Normal file
16
packages/pipeline/lib/src/conditionable.dart
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/// Provides conditional execution methods for the pipeline.
|
||||||
|
mixin Conditionable<T> {
|
||||||
|
T when(bool Function() callback, void Function(T) callback2) {
|
||||||
|
if (callback()) {
|
||||||
|
callback2(this as T);
|
||||||
|
}
|
||||||
|
return this as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
T unless(bool Function() callback, void Function(T) callback2) {
|
||||||
|
if (!callback()) {
|
||||||
|
callback2(this as T);
|
||||||
|
}
|
||||||
|
return this as T;
|
||||||
|
}
|
||||||
|
}
|
241
packages/pipeline/lib/src/pipeline.dart
Normal file
241
packages/pipeline/lib/src/pipeline.dart
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:mirrors';
|
||||||
|
import 'package:platform_container/container.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'pipeline_contract.dart';
|
||||||
|
import 'conditionable.dart';
|
||||||
|
|
||||||
|
/// Defines the signature for a pipe function.
|
||||||
|
typedef PipeFunction = FutureOr<dynamic> Function(
|
||||||
|
dynamic passable, FutureOr<dynamic> Function(dynamic) next);
|
||||||
|
|
||||||
|
/// The primary class for building and executing pipelines.
|
||||||
|
class Pipeline with Conditionable<Pipeline> implements PipelineContract {
|
||||||
|
/// The container implementation.
|
||||||
|
Container? _container;
|
||||||
|
|
||||||
|
final Map<String, Type> _typeMap = {};
|
||||||
|
|
||||||
|
/// The object being passed through the pipeline.
|
||||||
|
dynamic _passable;
|
||||||
|
|
||||||
|
/// The array of class pipes.
|
||||||
|
final List<dynamic> _pipes = [];
|
||||||
|
|
||||||
|
/// The method to call on each pipe.
|
||||||
|
String _method = 'handle';
|
||||||
|
|
||||||
|
/// Logger for the pipeline.
|
||||||
|
final Logger _logger = Logger('Pipeline');
|
||||||
|
|
||||||
|
/// Create a new class instance.
|
||||||
|
Pipeline(this._container);
|
||||||
|
|
||||||
|
void registerPipeType(String name, Type type) {
|
||||||
|
_typeMap[name] = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the object being sent through the pipeline.
|
||||||
|
@override
|
||||||
|
Pipeline send(dynamic passable) {
|
||||||
|
_passable = passable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the array of pipes.
|
||||||
|
@override
|
||||||
|
Pipeline through(dynamic pipes) {
|
||||||
|
if (_container == null) {
|
||||||
|
throw Exception(
|
||||||
|
'A container instance has not been passed to the Pipeline.');
|
||||||
|
}
|
||||||
|
_pipes.addAll(pipes is Iterable ? pipes.toList() : [pipes]);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push additional pipes onto the pipeline.
|
||||||
|
@override
|
||||||
|
Pipeline pipe(dynamic pipes) {
|
||||||
|
if (_container == null) {
|
||||||
|
throw Exception(
|
||||||
|
'A container instance has not been passed to the Pipeline.');
|
||||||
|
}
|
||||||
|
_pipes.addAll(pipes is Iterable ? pipes.toList() : [pipes]);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the method to call on the pipes.
|
||||||
|
@override
|
||||||
|
Pipeline via(String method) {
|
||||||
|
_method = method;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the pipeline with a final destination callback.
|
||||||
|
@override
|
||||||
|
Future<dynamic> then(FutureOr<dynamic> Function(dynamic) destination) async {
|
||||||
|
if (_container == null) {
|
||||||
|
throw Exception(
|
||||||
|
'A container instance has not been passed to the Pipeline.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var pipeline = (dynamic passable) async => await destination(passable);
|
||||||
|
|
||||||
|
for (var pipe in _pipes.reversed) {
|
||||||
|
var next = pipeline;
|
||||||
|
pipeline = (dynamic passable) async {
|
||||||
|
return await carry(pipe, passable, next);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return await pipeline(_passable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the pipeline and return the result.
|
||||||
|
@override
|
||||||
|
Future<dynamic> thenReturn() async {
|
||||||
|
return then((passable) => passable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a Closure that represents a slice of the application onion.
|
||||||
|
Future<dynamic> carry(dynamic pipe, dynamic passable, Function next) async {
|
||||||
|
try {
|
||||||
|
if (pipe is Function) {
|
||||||
|
return await pipe(passable, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipe is String) {
|
||||||
|
if (_container == null) {
|
||||||
|
throw Exception('Container is null, cannot resolve pipe: $pipe');
|
||||||
|
}
|
||||||
|
|
||||||
|
final parts = parsePipeString(pipe);
|
||||||
|
final pipeClass = parts[0];
|
||||||
|
final parameters = parts.length > 1 ? parts.sublist(1) : [];
|
||||||
|
|
||||||
|
Type? pipeType;
|
||||||
|
if (_typeMap.containsKey(pipeClass)) {
|
||||||
|
pipeType = _typeMap[pipeClass];
|
||||||
|
} else {
|
||||||
|
// Try to resolve from mirrors
|
||||||
|
try {
|
||||||
|
for (var lib in currentMirrorSystem().libraries.values) {
|
||||||
|
for (var decl in lib.declarations.values) {
|
||||||
|
if (decl is ClassMirror &&
|
||||||
|
decl.simpleName == Symbol(pipeClass)) {
|
||||||
|
pipeType = decl.reflectedType;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pipeType != null) break;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (pipeType == null) {
|
||||||
|
throw Exception('Type not registered for pipe: $pipe');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var instance = _container?.make(pipeType);
|
||||||
|
if (instance == null) {
|
||||||
|
throw Exception('Unable to resolve pipe: $pipe');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await invokeMethod(
|
||||||
|
instance, _method, [passable, next, ...parameters]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipe is Type) {
|
||||||
|
if (_container == null) {
|
||||||
|
throw Exception('Container is null, cannot resolve pipe type');
|
||||||
|
}
|
||||||
|
|
||||||
|
var instance = _container?.make(pipe);
|
||||||
|
if (instance == null) {
|
||||||
|
throw Exception('Unable to resolve pipe type: $pipe');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await invokeMethod(instance, _method, [passable, next]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle instance of a class
|
||||||
|
if (pipe is Object) {
|
||||||
|
return await invokeMethod(pipe, _method, [passable, next]);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception('Unsupported pipe type: ${pipe.runtimeType}');
|
||||||
|
} catch (e) {
|
||||||
|
return handleException(passable, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse full pipe string to get name and parameters.
|
||||||
|
List<String> parsePipeString(String pipe) {
|
||||||
|
var parts = pipe.split(':');
|
||||||
|
return [parts[0], if (parts.length > 1) ...parts[1].split(',')];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the array of configured pipes.
|
||||||
|
List<dynamic> pipes() {
|
||||||
|
return List.unmodifiable(_pipes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the container instance.
|
||||||
|
Container getContainer() {
|
||||||
|
if (_container == null) {
|
||||||
|
throw Exception(
|
||||||
|
'A container instance has not been passed to the Pipeline.');
|
||||||
|
}
|
||||||
|
return _container!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the container instance.
|
||||||
|
Pipeline setContainer(Container container) {
|
||||||
|
_container = container;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle the value returned from each pipe before passing it to the next.
|
||||||
|
dynamic handleCarry(dynamic carry) {
|
||||||
|
if (carry is Future) {
|
||||||
|
return carry.then((value) => value ?? _passable);
|
||||||
|
}
|
||||||
|
return carry ?? _passable;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> invokeMethod(
|
||||||
|
dynamic instance, String methodName, List<dynamic> arguments) async {
|
||||||
|
// First try call() for invokable objects
|
||||||
|
if (instance is Function) {
|
||||||
|
return await instance(arguments[0], arguments[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var instanceMirror = reflect(instance);
|
||||||
|
|
||||||
|
// Check for call method first (invokable objects)
|
||||||
|
var callSymbol = Symbol('call');
|
||||||
|
if (instanceMirror.type.declarations.containsKey(callSymbol)) {
|
||||||
|
var result = instanceMirror.invoke(callSymbol, arguments);
|
||||||
|
return await result.reflectee;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try the specified method
|
||||||
|
var methodSymbol = Symbol(methodName);
|
||||||
|
if (!instanceMirror.type.declarations.containsKey(methodSymbol)) {
|
||||||
|
throw Exception('Method $methodName not found on instance: $instance');
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = instanceMirror.invoke(methodSymbol, arguments);
|
||||||
|
return await result.reflectee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle the given exception.
|
||||||
|
dynamic handleException(dynamic passable, Object e) {
|
||||||
|
if (e is Exception && e.toString().contains('Container is null')) {
|
||||||
|
throw Exception(
|
||||||
|
'A container instance has not been passed to the Pipeline.');
|
||||||
|
}
|
||||||
|
_logger.severe('Exception occurred in pipeline', e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
9
packages/pipeline/lib/src/pipeline_contract.dart
Normal file
9
packages/pipeline/lib/src/pipeline_contract.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/// Represents a series of "pipes" through which an object can be passed.
|
||||||
|
abstract class PipelineContract {
|
||||||
|
PipelineContract send(dynamic passable);
|
||||||
|
PipelineContract through(dynamic pipes);
|
||||||
|
PipelineContract pipe(dynamic pipes);
|
||||||
|
PipelineContract via(String method);
|
||||||
|
Future<dynamic> then(dynamic Function(dynamic) destination);
|
||||||
|
Future<dynamic> thenReturn();
|
||||||
|
}
|
19
packages/pipeline/pubspec.yaml
Normal file
19
packages/pipeline/pubspec.yaml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
name: platform_pipeline
|
||||||
|
description: The Pipeline Package for the Protevus Platform
|
||||||
|
version: 0.0.1
|
||||||
|
homepage: https://protevus.com
|
||||||
|
documentation: https://docs.protevus.com
|
||||||
|
repository: https://github.com/protevus/platform
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.4.2
|
||||||
|
|
||||||
|
# Add regular dependencies here.
|
||||||
|
dependencies:
|
||||||
|
platform_container: ^9.0.0
|
||||||
|
platform_core: ^9.0.0
|
||||||
|
logging: ^1.1.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^3.0.0
|
||||||
|
test: ^1.24.0
|
258
packages/pipeline/test/laravel_pipeline_test.dart
Normal file
258
packages/pipeline/test/laravel_pipeline_test.dart
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
import 'package:platform_container/container.dart';
|
||||||
|
import 'package:platform_pipeline/pipeline.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// Test pipe classes to match Laravel's test classes
|
||||||
|
class PipelineTestPipeOne {
|
||||||
|
static String? testPipeOne;
|
||||||
|
|
||||||
|
Future<dynamic> handle(dynamic piped, Function next) async {
|
||||||
|
testPipeOne = piped.toString();
|
||||||
|
return next(piped);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> differentMethod(dynamic piped, Function next) async {
|
||||||
|
return next(piped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PipelineTestPipeTwo {
|
||||||
|
static String? testPipeOne;
|
||||||
|
|
||||||
|
Future<dynamic> call(dynamic piped, Function next) async {
|
||||||
|
testPipeOne = piped.toString();
|
||||||
|
return next(piped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PipelineTestParameterPipe {
|
||||||
|
static List<String>? testParameters;
|
||||||
|
|
||||||
|
Future<dynamic> handle(dynamic piped, Function next,
|
||||||
|
[String? parameter1, String? parameter2]) async {
|
||||||
|
testParameters = [
|
||||||
|
if (parameter1 != null) parameter1,
|
||||||
|
if (parameter2 != null) parameter2
|
||||||
|
];
|
||||||
|
return next(piped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Laravel Pipeline Tests', () {
|
||||||
|
late Container container;
|
||||||
|
late Pipeline pipeline;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container(const EmptyReflector());
|
||||||
|
pipeline = Pipeline(container);
|
||||||
|
|
||||||
|
// Register test classes with container
|
||||||
|
container
|
||||||
|
.registerFactory<PipelineTestPipeOne>((c) => PipelineTestPipeOne());
|
||||||
|
container
|
||||||
|
.registerFactory<PipelineTestPipeTwo>((c) => PipelineTestPipeTwo());
|
||||||
|
container.registerFactory<PipelineTestParameterPipe>(
|
||||||
|
(c) => PipelineTestParameterPipe());
|
||||||
|
|
||||||
|
// Register types with pipeline
|
||||||
|
pipeline.registerPipeType('PipelineTestPipeOne', PipelineTestPipeOne);
|
||||||
|
pipeline.registerPipeType('PipelineTestPipeTwo', PipelineTestPipeTwo);
|
||||||
|
pipeline.registerPipeType(
|
||||||
|
'PipelineTestParameterPipe', PipelineTestParameterPipe);
|
||||||
|
|
||||||
|
// Reset static test variables
|
||||||
|
PipelineTestPipeOne.testPipeOne = null;
|
||||||
|
PipelineTestPipeTwo.testPipeOne = null;
|
||||||
|
PipelineTestParameterPipe.testParameters = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline basic usage', () async {
|
||||||
|
String? testPipeTwo;
|
||||||
|
final pipeTwo = (dynamic piped, Function next) {
|
||||||
|
testPipeTwo = piped.toString();
|
||||||
|
return next(piped);
|
||||||
|
};
|
||||||
|
|
||||||
|
final result = await Pipeline(container)
|
||||||
|
.send('foo')
|
||||||
|
.through([PipelineTestPipeOne(), pipeTwo]).then((piped) => piped);
|
||||||
|
|
||||||
|
expect(result, equals('foo'));
|
||||||
|
expect(PipelineTestPipeOne.testPipeOne, equals('foo'));
|
||||||
|
expect(testPipeTwo, equals('foo'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline usage with objects', () async {
|
||||||
|
final result = await Pipeline(container)
|
||||||
|
.send('foo')
|
||||||
|
.through([PipelineTestPipeOne()]).then((piped) => piped);
|
||||||
|
|
||||||
|
expect(result, equals('foo'));
|
||||||
|
expect(PipelineTestPipeOne.testPipeOne, equals('foo'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline usage with invokable objects', () async {
|
||||||
|
final result = await Pipeline(container)
|
||||||
|
.send('foo')
|
||||||
|
.through([PipelineTestPipeTwo()]).then((piped) => piped);
|
||||||
|
|
||||||
|
expect(result, equals('foo'));
|
||||||
|
expect(PipelineTestPipeTwo.testPipeOne, equals('foo'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline usage with callable', () async {
|
||||||
|
String? testPipeOne;
|
||||||
|
final function = (dynamic piped, Function next) {
|
||||||
|
testPipeOne = 'foo';
|
||||||
|
return next(piped);
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await Pipeline(container)
|
||||||
|
.send('foo')
|
||||||
|
.through([function]).then((piped) => piped);
|
||||||
|
|
||||||
|
expect(result, equals('foo'));
|
||||||
|
expect(testPipeOne, equals('foo'));
|
||||||
|
|
||||||
|
testPipeOne = null;
|
||||||
|
|
||||||
|
result =
|
||||||
|
await Pipeline(container).send('bar').through(function).thenReturn();
|
||||||
|
|
||||||
|
expect(result, equals('bar'));
|
||||||
|
expect(testPipeOne, equals('foo'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline usage with pipe', () async {
|
||||||
|
final object = {'value': 0};
|
||||||
|
|
||||||
|
final function = (dynamic obj, Function next) {
|
||||||
|
obj['value']++;
|
||||||
|
return next(obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
final result = await Pipeline(container)
|
||||||
|
.send(object)
|
||||||
|
.through([function]).pipe([function]).then((piped) => piped);
|
||||||
|
|
||||||
|
expect(result, equals(object));
|
||||||
|
expect(object['value'], equals(2));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline usage with invokable class', () async {
|
||||||
|
final result = await Pipeline(container)
|
||||||
|
.send('foo')
|
||||||
|
.through([PipelineTestPipeTwo()]).then((piped) => piped);
|
||||||
|
|
||||||
|
expect(result, equals('foo'));
|
||||||
|
expect(PipelineTestPipeTwo.testPipeOne, equals('foo'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Then method is not called if the pipe returns', () async {
|
||||||
|
String thenValue = '(*_*)';
|
||||||
|
String secondValue = '(*_*)';
|
||||||
|
|
||||||
|
final result = await Pipeline(container).send('foo').through([
|
||||||
|
(value, next) => 'm(-_-)m',
|
||||||
|
(value, next) {
|
||||||
|
secondValue = 'm(-_-)m';
|
||||||
|
return next(value);
|
||||||
|
},
|
||||||
|
]).then((piped) {
|
||||||
|
thenValue = '(0_0)';
|
||||||
|
return piped;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result, equals('m(-_-)m'));
|
||||||
|
// The then callback is not called
|
||||||
|
expect(thenValue, equals('(*_*)'));
|
||||||
|
// The second pipe is not called
|
||||||
|
expect(secondValue, equals('(*_*)'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Then method input value', () async {
|
||||||
|
String? pipeReturn;
|
||||||
|
String? thenArg;
|
||||||
|
|
||||||
|
final result = await Pipeline(container).send('foo').through([
|
||||||
|
(value, next) async {
|
||||||
|
final nextValue = await next('::not_foo::');
|
||||||
|
pipeReturn = nextValue;
|
||||||
|
return 'pipe::$nextValue';
|
||||||
|
}
|
||||||
|
]).then((piped) {
|
||||||
|
thenArg = piped;
|
||||||
|
return 'then$piped';
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result, equals('pipe::then::not_foo::'));
|
||||||
|
expect(thenArg, equals('::not_foo::'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline usage with parameters', () async {
|
||||||
|
final parameters = ['one', 'two'];
|
||||||
|
|
||||||
|
final result = await Pipeline(container)
|
||||||
|
.send('foo')
|
||||||
|
.through('PipelineTestParameterPipe:${parameters.join(',')}')
|
||||||
|
.then((piped) => piped);
|
||||||
|
|
||||||
|
expect(result, equals('foo'));
|
||||||
|
expect(PipelineTestParameterPipe.testParameters, equals(parameters));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline via changes the method being called on the pipes', () async {
|
||||||
|
final result = await Pipeline(container)
|
||||||
|
.send('data')
|
||||||
|
.through(PipelineTestPipeOne())
|
||||||
|
.via('differentMethod')
|
||||||
|
.then((piped) => piped);
|
||||||
|
|
||||||
|
expect(result, equals('data'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline throws exception on resolve without container', () async {
|
||||||
|
expect(
|
||||||
|
() => Pipeline(null)
|
||||||
|
.send('data')
|
||||||
|
.through(PipelineTestPipeOne())
|
||||||
|
.then((piped) => piped),
|
||||||
|
throwsA(isA<Exception>().having(
|
||||||
|
(e) => e.toString(),
|
||||||
|
'message',
|
||||||
|
contains(
|
||||||
|
'A container instance has not been passed to the Pipeline'))));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline thenReturn method runs pipeline then returns passable',
|
||||||
|
() async {
|
||||||
|
final result = await Pipeline(container)
|
||||||
|
.send('foo')
|
||||||
|
.through([PipelineTestPipeOne()]).thenReturn();
|
||||||
|
|
||||||
|
expect(result, equals('foo'));
|
||||||
|
expect(PipelineTestPipeOne.testPipeOne, equals('foo'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline conditionable', () async {
|
||||||
|
var result = await Pipeline(container).send('foo').when(() => true,
|
||||||
|
(Pipeline pipeline) {
|
||||||
|
pipeline.pipe([PipelineTestPipeOne()]);
|
||||||
|
}).then((piped) => piped);
|
||||||
|
|
||||||
|
expect(result, equals('foo'));
|
||||||
|
expect(PipelineTestPipeOne.testPipeOne, equals('foo'));
|
||||||
|
|
||||||
|
PipelineTestPipeOne.testPipeOne = null;
|
||||||
|
|
||||||
|
result = await Pipeline(container).send('foo').when(() => false,
|
||||||
|
(Pipeline pipeline) {
|
||||||
|
pipeline.pipe([PipelineTestPipeOne()]);
|
||||||
|
}).then((piped) => piped);
|
||||||
|
|
||||||
|
expect(result, equals('foo'));
|
||||||
|
expect(PipelineTestPipeOne.testPipeOne, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
106
packages/pipeline/test/pipeline_test.dart
Normal file
106
packages/pipeline/test/pipeline_test.dart
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:platform_core/core.dart';
|
||||||
|
import 'package:platform_container/container.dart';
|
||||||
|
import 'package:platform_container/mirrors.dart';
|
||||||
|
import 'package:platform_pipeline/pipeline.dart';
|
||||||
|
|
||||||
|
class AddExclamationPipe {
|
||||||
|
Future<String> handle(String input, Function next) async {
|
||||||
|
return await next('$input!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UppercasePipe {
|
||||||
|
Future<String> handle(String input, Function next) async {
|
||||||
|
return await next(input.toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Application app;
|
||||||
|
late Container container;
|
||||||
|
late Pipeline pipeline;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
app = Application(reflector: MirrorsReflector());
|
||||||
|
container = app.container;
|
||||||
|
container.registerSingleton(AddExclamationPipe());
|
||||||
|
container.registerSingleton(UppercasePipe());
|
||||||
|
pipeline = Pipeline(container);
|
||||||
|
pipeline.registerPipeType('AddExclamationPipe', AddExclamationPipe);
|
||||||
|
pipeline.registerPipeType('UppercasePipe', UppercasePipe);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline should process simple string pipes', () async {
|
||||||
|
var result = await pipeline.send('hello').through(
|
||||||
|
['AddExclamationPipe', 'UppercasePipe']).then((res) async => res);
|
||||||
|
expect(result, equals('HELLO!'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline should process function pipes', () async {
|
||||||
|
var result = await pipeline.send('hello').through([
|
||||||
|
(String input, Function next) async {
|
||||||
|
var result = await next('$input, WORLD');
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
(String input, Function next) async {
|
||||||
|
var result = await next(input.toUpperCase());
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
]).then((res) async => res as String);
|
||||||
|
|
||||||
|
expect(result, equals('HELLO, WORLD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline should handle mixed pipe types', () async {
|
||||||
|
var result = await pipeline.send('hello').through([
|
||||||
|
'AddExclamationPipe',
|
||||||
|
(String input, Function next) async {
|
||||||
|
var result = await next(input.toUpperCase());
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
]).then((res) async => res as String);
|
||||||
|
expect(result, equals('HELLO!'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline should handle async pipes', () async {
|
||||||
|
var result = await pipeline.send('hello').through([
|
||||||
|
'UppercasePipe',
|
||||||
|
(String input, Function next) async {
|
||||||
|
await Future.delayed(Duration(milliseconds: 100));
|
||||||
|
return next('$input, WORLD');
|
||||||
|
},
|
||||||
|
]).then((res) async => res as String);
|
||||||
|
expect(result, equals('HELLO, WORLD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline should throw exception for unresolvable pipe', () {
|
||||||
|
expect(
|
||||||
|
() => pipeline
|
||||||
|
.send('hello')
|
||||||
|
.through(['NonExistentPipe']).then((res) => res),
|
||||||
|
throwsA(isA<Exception>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline should allow chaining of pipes', () async {
|
||||||
|
var result = await pipeline
|
||||||
|
.send('hello')
|
||||||
|
.pipe('AddExclamationPipe')
|
||||||
|
.pipe('UppercasePipe')
|
||||||
|
.then((res) async => res as String);
|
||||||
|
expect(result, equals('HELLO!'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pipeline should respect the order of pipes', () async {
|
||||||
|
var result1 = await pipeline
|
||||||
|
.send('hello')
|
||||||
|
.through(['AddExclamationPipe', 'UppercasePipe']).then((res) => res);
|
||||||
|
var result2 = await pipeline
|
||||||
|
.send('hello')
|
||||||
|
.through(['UppercasePipe', 'AddExclamationPipe']).then((res) => res);
|
||||||
|
expect(result1, equals('HELLO!'));
|
||||||
|
expect(result2, equals('HELLO!!'));
|
||||||
|
expect(result1, isNot(equals(result2)));
|
||||||
|
});
|
||||||
|
}
|
7
packages/process/.gitignore
vendored
Normal file
7
packages/process/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
# Avoid committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
3
packages/process/CHANGELOG.md
Normal file
3
packages/process/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version.
|
10
packages/process/LICENSE.md
Normal file
10
packages/process/LICENSE.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
The Laravel Framework is Copyright (c) Taylor Otwell
|
||||||
|
The Fabric Framework is Copyright (c) Vieo, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
packages/process/README.md
Normal file
1
packages/process/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<p align="center"><a href="https://protevus.com" target="_blank"><img src="https://git.protevus.com/protevus/branding/raw/branch/main/protevus-logo-bg.png"></a></p>
|
30
packages/process/analysis_options.yaml
Normal file
30
packages/process/analysis_options.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
36
packages/process/examples/basic_process/main.dart
Normal file
36
packages/process/examples/basic_process/main.dart
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// examples/basic_process/main.dart
|
||||||
|
import 'package:platform_core/core.dart';
|
||||||
|
import 'package:platform_core/http.dart';
|
||||||
|
import 'package:platform_process/angel3_process.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:platform_container/mirrors.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
Logger.root.level = Level.ALL;
|
||||||
|
Logger.root.onRecord.listen((record) {
|
||||||
|
print('${record.level.name}: ${record.time}: ${record.message}');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create an Angel application with MirrorsReflector
|
||||||
|
var app = Application(reflector: MirrorsReflector());
|
||||||
|
var http = PlatformHttp(app);
|
||||||
|
|
||||||
|
// Use dependency injection for ProcessManager
|
||||||
|
app.container.registerSingleton(ProcessManager());
|
||||||
|
|
||||||
|
app.get('/', (req, res) async {
|
||||||
|
// Use the ioc function to get the ProcessManager instance
|
||||||
|
var processManager = await req.container?.make<ProcessManager>();
|
||||||
|
|
||||||
|
var process = await processManager?.start(
|
||||||
|
'example_process',
|
||||||
|
'echo',
|
||||||
|
['Hello, Angel3 Process!'],
|
||||||
|
);
|
||||||
|
var result = await process?.run();
|
||||||
|
res.writeln('Process output: ${result?.output.trim()}');
|
||||||
|
});
|
||||||
|
|
||||||
|
await http.startServer('localhost', 3000);
|
||||||
|
print('Server listening at http://localhost:3000');
|
||||||
|
}
|
37
packages/process/examples/process_pipeline/main.dart
Normal file
37
packages/process/examples/process_pipeline/main.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// examples/process_pipeline/main.dart
|
||||||
|
import 'package:platform_core/core.dart';
|
||||||
|
import 'package:platform_core/http.dart';
|
||||||
|
import 'package:platform_process/angel3_process.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:platform_container/mirrors.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
Logger.root.level = Level.ALL;
|
||||||
|
Logger.root.onRecord.listen((record) {
|
||||||
|
print('${record.level.name}: ${record.time}: ${record.message}');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create an Angel application with MirrorsReflector
|
||||||
|
var app = Application(reflector: MirrorsReflector());
|
||||||
|
var http = PlatformHttp(app);
|
||||||
|
|
||||||
|
// Register ProcessManager as a singleton in the container
|
||||||
|
app.container.registerSingleton(ProcessManager());
|
||||||
|
|
||||||
|
app.get('/', (req, res) async {
|
||||||
|
// Use dependency injection to get the ProcessManager instance
|
||||||
|
var processManager = await req.container?.make<ProcessManager>();
|
||||||
|
|
||||||
|
var processes = [
|
||||||
|
angel3Process('echo', ['Hello']),
|
||||||
|
angel3Process('sed', ['s/Hello/Greetings/']),
|
||||||
|
angel3Process('tr', ['[:lower:]', '[:upper:]']),
|
||||||
|
];
|
||||||
|
|
||||||
|
var result = await processManager?.pipeline(processes);
|
||||||
|
res.writeln('Pipeline output: ${result?.output.trim()}');
|
||||||
|
});
|
||||||
|
|
||||||
|
await http.startServer('localhost', 3000);
|
||||||
|
print('Server listening at http://localhost:3000');
|
||||||
|
}
|
37
packages/process/examples/process_pool/main.dart
Normal file
37
packages/process/examples/process_pool/main.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// examples/process_pool/main.dart
|
||||||
|
import 'package:platform_core/core.dart';
|
||||||
|
import 'package:platform_core/http.dart';
|
||||||
|
import 'package:platform_process/angel3_process.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:platform_container/mirrors.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
Logger.root.level = Level.ALL;
|
||||||
|
Logger.root.onRecord.listen((record) {
|
||||||
|
print('${record.level.name}: ${record.time}: ${record.message}');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create an Angel application with MirrorsReflector
|
||||||
|
var app = Application(reflector: MirrorsReflector());
|
||||||
|
var http = PlatformHttp(app);
|
||||||
|
|
||||||
|
// Register ProcessManager as a singleton in the container
|
||||||
|
app.container.registerSingleton(ProcessManager());
|
||||||
|
|
||||||
|
app.get('/', (req, res) async {
|
||||||
|
// Use dependency injection to get the ProcessManager instance
|
||||||
|
var processManager = await req.container?.make<ProcessManager>();
|
||||||
|
|
||||||
|
var processes =
|
||||||
|
List.generate(5, (index) => angel3Process('echo', ['Process $index']));
|
||||||
|
var results = await processManager?.pool(processes, concurrency: 3);
|
||||||
|
var output = results
|
||||||
|
?.map((result) =>
|
||||||
|
'${result.process.command} output: ${result.output.trim()}')
|
||||||
|
.join('\n');
|
||||||
|
res.write(output);
|
||||||
|
});
|
||||||
|
|
||||||
|
await http.startServer('localhost', 3000);
|
||||||
|
print('Server listening at http://localhost:3000');
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
// examples/web_server_with_processes/main.dart
|
||||||
|
import 'package:platform_core/core.dart';
|
||||||
|
import 'package:platform_core/http.dart';
|
||||||
|
import 'package:platform_process/angel3_process.dart';
|
||||||
|
import 'package:file/local.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:angel3_mustache/angel3_mustache.dart';
|
||||||
|
import 'package:platform_container/mirrors.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
Logger.root.level = Level.ALL;
|
||||||
|
Logger.root.onRecord.listen((record) {
|
||||||
|
print('${record.level.name}: ${record.time}: ${record.message}');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create an Angel application with MirrorsReflector
|
||||||
|
var app = Application(reflector: MirrorsReflector());
|
||||||
|
var http = PlatformHttp(app);
|
||||||
|
|
||||||
|
// Register dependencies in the container
|
||||||
|
app.container.registerSingleton(const LocalFileSystem());
|
||||||
|
app.container.registerSingleton(ProcessManager());
|
||||||
|
|
||||||
|
// Set up the view renderer
|
||||||
|
var fs = await app.container.make<LocalFileSystem>();
|
||||||
|
var viewsDirectory = fs.directory('views');
|
||||||
|
//await app.configure(mustache(viewsDirectory));
|
||||||
|
|
||||||
|
app.get('/', (req, res) async {
|
||||||
|
await res.render('index');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/run-process', (req, res) async {
|
||||||
|
var body = await req.bodyAsMap;
|
||||||
|
var command = body['command'] as String?;
|
||||||
|
var args = (body['args'] as String?)?.split(' ') ?? [];
|
||||||
|
|
||||||
|
if (command == null || command.isEmpty) {
|
||||||
|
throw PlatformHttpException.badRequest(message: 'Command is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use dependency injection to get the ProcessManager instance
|
||||||
|
var processManager = await req.container?.make<ProcessManager>();
|
||||||
|
|
||||||
|
var process = await processManager?.start(
|
||||||
|
'user_process',
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
var result = await process?.run();
|
||||||
|
|
||||||
|
await res.json({
|
||||||
|
'output': result?.output.trim(),
|
||||||
|
'exitCode': result?.exitCode,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.fallback((req, res) => throw PlatformHttpException.notFound());
|
||||||
|
|
||||||
|
app.errorHandler = (e, req, res) {
|
||||||
|
res.writeln('Error: ${e.message}');
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
await http.startServer('localhost', 3000);
|
||||||
|
print('Server listening at http://localhost:3000');
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<!-- examples/web_server_with_processes/views/index.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Angel3 Process Example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Run a Process</h1>
|
||||||
|
<form id="processForm">
|
||||||
|
<label for="command">Command:</label>
|
||||||
|
<input type="text" id="command" name="command" required>
|
||||||
|
<br>
|
||||||
|
<label for="args">Arguments:</label>
|
||||||
|
<input type="text" id="args" name="args">
|
||||||
|
<br>
|
||||||
|
<button type="submit">Run Process</button>
|
||||||
|
</form>
|
||||||
|
<div id="output"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('processForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(e.target);
|
||||||
|
const response = await fetch('/run-process', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
document.getElementById('output').innerHTML = `
|
||||||
|
<h2>Output:</h2>
|
||||||
|
<pre>${result.output}</pre>
|
||||||
|
<p>Exit Code: ${result.exitCode}</p>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
7
packages/process/lib/angel3_process.dart
Normal file
7
packages/process/lib/angel3_process.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
library;
|
||||||
|
|
||||||
|
export 'src/process.dart';
|
||||||
|
export 'src/process_helper.dart';
|
||||||
|
export 'src/process_manager.dart';
|
||||||
|
export 'src/process_pipeline.dart';
|
||||||
|
export 'src/process_pool.dart';
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue