doc-changes-5-7 #4
391
.claude/development-guidelines.md
Normal file
391
.claude/development-guidelines.md
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
# DebrosFramework Development Guidelines
|
||||||
|
|
||||||
|
## Code Style and Standards
|
||||||
|
|
||||||
|
### TypeScript Configuration
|
||||||
|
- **Strict Mode**: Always use strict TypeScript configuration
|
||||||
|
- **Decorators**: Enable `experimentalDecorators` and `emitDecoratorMetadata`
|
||||||
|
- **Target**: ES2020 for modern JavaScript features
|
||||||
|
- **Module System**: ES modules with CommonJS interop
|
||||||
|
|
||||||
|
### Code Formatting
|
||||||
|
- **Prettier**: Use Prettier for consistent code formatting
|
||||||
|
- **ESLint**: Follow ESLint rules for code quality
|
||||||
|
- **Indentation**: 2 spaces for all files
|
||||||
|
- **Line Length**: Max 120 characters per line
|
||||||
|
- **Semicolons**: Always use semicolons
|
||||||
|
- **Quotes**: Single quotes for strings, double quotes for JSX
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
- **Classes**: PascalCase (e.g., `UserModel`, `QueryBuilder`)
|
||||||
|
- **Functions/Methods**: camelCase (e.g., `createUser`, `findById`)
|
||||||
|
- **Variables**: camelCase (e.g., `userId`, `queryResult`)
|
||||||
|
- **Constants**: UPPER_SNAKE_CASE (e.g., `DEFAULT_CACHE_SIZE`)
|
||||||
|
- **Files**: PascalCase for classes, camelCase for utilities
|
||||||
|
- **Interfaces**: PascalCase with descriptive names (e.g., `ModelConfig`)
|
||||||
|
|
||||||
|
## Framework Architecture Patterns
|
||||||
|
|
||||||
|
### Model System
|
||||||
|
```typescript
|
||||||
|
// Always extend BaseModel for data models
|
||||||
|
@Model({
|
||||||
|
scope: 'user' | 'global',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: { strategy: 'hash', count: 4, key: 'fieldName' }
|
||||||
|
})
|
||||||
|
export class ModelName extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
propertyName: string;
|
||||||
|
|
||||||
|
@BelongsTo(() => RelatedModel, 'foreignKey')
|
||||||
|
relation: RelatedModel;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query Patterns
|
||||||
|
```typescript
|
||||||
|
// Use chainable query builder pattern
|
||||||
|
const results = await Model.query()
|
||||||
|
.where('field', 'operator', value)
|
||||||
|
.with(['relationship1', 'relationship2'])
|
||||||
|
.orderBy('field', 'direction')
|
||||||
|
.limit(count)
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// Prefer specific methods over generic ones
|
||||||
|
const user = await User.findById(id); // Good
|
||||||
|
const user = await User.query().where('id', id).findOne(); // Less preferred
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
```typescript
|
||||||
|
// Always use try-catch for async operations
|
||||||
|
try {
|
||||||
|
const result = await someAsyncOperation();
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Operation failed:', error);
|
||||||
|
throw new DebrosFrameworkError('Descriptive error message', { originalError: error });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate inputs early
|
||||||
|
if (!userId || typeof userId !== 'string') {
|
||||||
|
throw new ValidationError('User ID must be a non-empty string');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Layer Pattern
|
||||||
|
```typescript
|
||||||
|
// Encapsulate business logic in service classes
|
||||||
|
export class UserService {
|
||||||
|
async createUser(userData: CreateUserData): Promise<User> {
|
||||||
|
// Validation
|
||||||
|
await this.validateUserData(userData);
|
||||||
|
|
||||||
|
// Business logic
|
||||||
|
const user = await User.create(userData);
|
||||||
|
|
||||||
|
// Post-processing
|
||||||
|
await this.sendWelcomeEmail(user);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async validateUserData(data: CreateUserData): Promise<void> {
|
||||||
|
// Validation logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Standards
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
- **Jest**: Use Jest for all unit testing
|
||||||
|
- **Mocking**: Mock external dependencies (OrbitDB, IPFS)
|
||||||
|
- **Coverage**: Aim for >90% code coverage
|
||||||
|
- **Structure**: One test file per source file
|
||||||
|
- **Naming**: `*.test.ts` for test files
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
describe('ModelName', () => {
|
||||||
|
let model: ModelName;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
model = new ModelName();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('methodName', () => {
|
||||||
|
it('should handle normal case', async () => {
|
||||||
|
// Arrange
|
||||||
|
const input = 'test value';
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = await model.methodName(input);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.property).toBe('expected value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for invalid input', async () => {
|
||||||
|
// Arrange & Act & Assert
|
||||||
|
await expect(model.methodName(null)).rejects.toThrow('Expected error message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
- **Docker**: Use Docker for real integration tests
|
||||||
|
- **Scenarios**: Test complete user workflows
|
||||||
|
- **Data**: Use realistic test data
|
||||||
|
- **Cleanup**: Always clean up test data
|
||||||
|
|
||||||
|
## Performance Guidelines
|
||||||
|
|
||||||
|
### Query Optimization
|
||||||
|
```typescript
|
||||||
|
// Use selective field loading when possible
|
||||||
|
const users = await User.query().select(['id', 'username']).find();
|
||||||
|
|
||||||
|
// Prefer eager loading for predictable relationships
|
||||||
|
const posts = await Post.query().with(['author', 'comments']).find();
|
||||||
|
|
||||||
|
// Use caching for expensive queries
|
||||||
|
const popularPosts = await Post.query()
|
||||||
|
.where('likeCount', '>', 100)
|
||||||
|
.cache(300) // Cache for 5 minutes
|
||||||
|
.find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Management
|
||||||
|
- **Lazy Loading**: Use lazy loading for large datasets
|
||||||
|
- **Pagination**: Always use pagination for large result sets
|
||||||
|
- **Cache Limits**: Set appropriate cache size limits
|
||||||
|
- **Cleanup**: Clean up resources in finally blocks
|
||||||
|
|
||||||
|
### Database Design
|
||||||
|
- **Sharding**: Design effective sharding strategies
|
||||||
|
- **Indexing**: Use indexes for frequently queried fields
|
||||||
|
- **Relationships**: Avoid deep relationship chains
|
||||||
|
- **Data Types**: Use appropriate data types for storage efficiency
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Input Validation
|
||||||
|
```typescript
|
||||||
|
// Always validate and sanitize inputs
|
||||||
|
@Field({
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
validate: (value: string) => value.length >= 3 && value.length <= 50,
|
||||||
|
transform: (value: string) => value.trim().toLowerCase()
|
||||||
|
})
|
||||||
|
username: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Access Control
|
||||||
|
- **User Scoping**: Use user-scoped models for private data
|
||||||
|
- **Validation**: Validate user permissions before operations
|
||||||
|
- **Sanitization**: Sanitize all user inputs
|
||||||
|
- **Encryption**: Use encryption for sensitive data
|
||||||
|
|
||||||
|
### Error Messages
|
||||||
|
- **Security**: Don't expose internal system details in error messages
|
||||||
|
- **Logging**: Log security-relevant events
|
||||||
|
- **Validation**: Provide clear validation error messages
|
||||||
|
|
||||||
|
## Documentation Standards
|
||||||
|
|
||||||
|
### Code Documentation
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* Creates a new user with the provided data.
|
||||||
|
*
|
||||||
|
* @param userData - The user data to create
|
||||||
|
* @param options - Additional creation options
|
||||||
|
* @returns Promise resolving to the created user
|
||||||
|
* @throws ValidationError if user data is invalid
|
||||||
|
* @throws DuplicateError if username or email already exists
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const user = await userService.createUser({
|
||||||
|
* username: 'alice',
|
||||||
|
* email: 'alice@example.com'
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
async createUser(userData: CreateUserData, options?: CreateOptions): Promise<User> {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### README Updates
|
||||||
|
- Keep README.md up to date with latest features
|
||||||
|
- Include practical examples
|
||||||
|
- Document breaking changes
|
||||||
|
- Provide migration guides
|
||||||
|
|
||||||
|
### API Documentation
|
||||||
|
- Document all public APIs
|
||||||
|
- Include parameter types and return types
|
||||||
|
- Provide usage examples
|
||||||
|
- Document error conditions
|
||||||
|
|
||||||
|
## Common Patterns and Anti-Patterns
|
||||||
|
|
||||||
|
### ✅ Good Patterns
|
||||||
|
|
||||||
|
#### Model Definition
|
||||||
|
```typescript
|
||||||
|
@Model({
|
||||||
|
scope: 'user',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: { strategy: 'hash', count: 4, key: 'userId' }
|
||||||
|
})
|
||||||
|
export class Post extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true, maxLength: 200 })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
@BeforeCreate()
|
||||||
|
setDefaults() {
|
||||||
|
this.createdAt = Date.now();
|
||||||
|
this.updatedAt = Date.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Service Methods
|
||||||
|
```typescript
|
||||||
|
async createPost(authorId: string, postData: CreatePostData): Promise<Post> {
|
||||||
|
// Validate input
|
||||||
|
if (!authorId) throw new ValidationError('Author ID is required');
|
||||||
|
|
||||||
|
// Check permissions
|
||||||
|
const author = await User.findById(authorId);
|
||||||
|
if (!author) throw new NotFoundError('Author not found');
|
||||||
|
|
||||||
|
// Create post
|
||||||
|
const post = await Post.create({
|
||||||
|
...postData,
|
||||||
|
authorId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Post-processing
|
||||||
|
await this.notifyFollowers(author, post);
|
||||||
|
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Anti-Patterns
|
||||||
|
|
||||||
|
#### Avoid Direct Database Access
|
||||||
|
```typescript
|
||||||
|
// Bad: Direct database manipulation
|
||||||
|
const db = await orbitdb.open('posts');
|
||||||
|
await db.put('key', data);
|
||||||
|
|
||||||
|
// Good: Use model methods
|
||||||
|
const post = await Post.create(data);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Avoid Synchronous Operations
|
||||||
|
```typescript
|
||||||
|
// Bad: Synchronous file operations
|
||||||
|
const data = fs.readFileSync('file.json');
|
||||||
|
|
||||||
|
// Good: Async operations
|
||||||
|
const data = await fs.promises.readFile('file.json');
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Avoid Deep Relationship Chains
|
||||||
|
```typescript
|
||||||
|
// Bad: Deep relationship loading
|
||||||
|
const posts = await Post.query()
|
||||||
|
.with(['author.profile.settings.preferences'])
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// Good: Load only what you need
|
||||||
|
const posts = await Post.query()
|
||||||
|
.with(['author'])
|
||||||
|
.find();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Guidelines
|
||||||
|
|
||||||
|
### Schema Changes
|
||||||
|
```typescript
|
||||||
|
// Create migration for schema changes
|
||||||
|
const migration = createMigration('add_user_avatar', '1.1.0')
|
||||||
|
.addField('User', 'avatarUrl', {
|
||||||
|
type: 'string',
|
||||||
|
required: false
|
||||||
|
})
|
||||||
|
.transformData('User', (user) => ({
|
||||||
|
...user,
|
||||||
|
avatarUrl: user.avatarUrl || null
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backwards Compatibility
|
||||||
|
- Always maintain backwards compatibility in minor versions
|
||||||
|
- Deprecate features before removing them
|
||||||
|
- Provide migration paths for breaking changes
|
||||||
|
- Document all changes in CHANGELOG.md
|
||||||
|
|
||||||
|
## Deployment Considerations
|
||||||
|
|
||||||
|
### Environment Configuration
|
||||||
|
- Use environment variables for configuration
|
||||||
|
- Provide default configurations for development
|
||||||
|
- Validate configuration on startup
|
||||||
|
- Document required environment variables
|
||||||
|
|
||||||
|
### Production Readiness
|
||||||
|
- Enable production optimizations
|
||||||
|
- Configure appropriate cache sizes
|
||||||
|
- Set up monitoring and logging
|
||||||
|
- Implement health checks
|
||||||
|
|
||||||
|
### Performance Monitoring
|
||||||
|
- Monitor query performance
|
||||||
|
- Track cache hit rates
|
||||||
|
- Monitor memory usage
|
||||||
|
- Set up alerts for errors
|
||||||
|
|
||||||
|
## Contributing Guidelines
|
||||||
|
|
||||||
|
### Pull Request Process
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create feature branch from main
|
||||||
|
3. Implement changes with tests
|
||||||
|
4. Update documentation
|
||||||
|
5. Submit pull request with description
|
||||||
|
|
||||||
|
### Code Review Checklist
|
||||||
|
- [ ] Code follows style guidelines
|
||||||
|
- [ ] Tests are included and passing
|
||||||
|
- [ ] Documentation is updated
|
||||||
|
- [ ] No breaking changes (or properly documented)
|
||||||
|
- [ ] Performance implications considered
|
||||||
|
- [ ] Security implications reviewed
|
||||||
|
|
||||||
|
### Commit Message Format
|
||||||
|
```
|
||||||
|
type(scope): description
|
||||||
|
|
||||||
|
body (optional)
|
||||||
|
|
||||||
|
footer (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
Types: feat, fix, docs, style, refactor, test, chore
|
||||||
|
|
||||||
|
These guidelines ensure consistent, maintainable, and high-quality code throughout the DebrosFramework project.
|
249
.claude/project-overview.md
Normal file
249
.claude/project-overview.md
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
# DebrosFramework Project Overview
|
||||||
|
|
||||||
|
## Project Identity
|
||||||
|
**DebrosFramework** is a powerful Node.js framework that provides an ORM-like abstraction over OrbitDB and IPFS, making it easy to build scalable decentralized applications.
|
||||||
|
|
||||||
|
- **Package Name**: `@debros/network`
|
||||||
|
- **Version**: 0.5.1-beta (Active Development)
|
||||||
|
- **License**: GNU GPL v3.0
|
||||||
|
- **Language**: TypeScript
|
||||||
|
- **Framework Type**: Decentralized Application Framework
|
||||||
|
|
||||||
|
## What This Project Does
|
||||||
|
|
||||||
|
DebrosFramework simplifies the development of decentralized applications by providing:
|
||||||
|
|
||||||
|
### Core Capabilities
|
||||||
|
- **Model-based Abstraction**: Define data models using decorators and TypeScript classes
|
||||||
|
- **Automatic Database Management**: Handle user-scoped and global databases automatically
|
||||||
|
- **Smart Sharding**: Distribute data across multiple databases for scalability
|
||||||
|
- **Advanced Query System**: Rich query capabilities with relationship loading and caching
|
||||||
|
- **Automatic Features**: Built-in pinning strategies and PubSub event publishing
|
||||||
|
- **Migration System**: Schema evolution and data transformation capabilities
|
||||||
|
- **Type Safety**: Full TypeScript support with strong typing throughout
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
1. **🏗️ Model-Driven Development**: Familiar decorator patterns for data models
|
||||||
|
2. **🔍 Powerful Query System**: Complex queries with relationship loading
|
||||||
|
3. **🚀 Automatic Scaling**: Handle millions of users with automatic sharding
|
||||||
|
4. **🔄 Schema Evolution**: Safe data structure migrations
|
||||||
|
5. **🔗 Rich Relationships**: Complex relationships between models
|
||||||
|
6. **⚡ Performance Features**: Query caching, eager loading, optimized pagination
|
||||||
|
7. **🎯 Model Hooks**: Lifecycle hooks for business logic
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
The framework is built around several core components:
|
||||||
|
|
||||||
|
1. **Models & Decorators**: Define data structure and behavior
|
||||||
|
2. **Database Manager**: Handles database creation and management
|
||||||
|
3. **Shard Manager**: Distributes data across multiple databases
|
||||||
|
4. **Query System**: Processes queries with optimization and caching
|
||||||
|
5. **Relationship Manager**: Handles complex relationships between models
|
||||||
|
6. **Migration System**: Manages schema evolution over time
|
||||||
|
7. **Automatic Features**: Pinning, PubSub, and performance optimization
|
||||||
|
|
||||||
|
## Technology Stack
|
||||||
|
|
||||||
|
### Core Dependencies
|
||||||
|
- **OrbitDB**: Distributed peer-to-peer database on IPFS
|
||||||
|
- **IPFS/Helia**: Distributed file system for data storage
|
||||||
|
- **libp2p**: Peer-to-peer networking stack
|
||||||
|
- **TypeScript**: Strong typing and modern JavaScript features
|
||||||
|
|
||||||
|
### Key Libraries
|
||||||
|
- `@orbitdb/core`: Core OrbitDB functionality
|
||||||
|
- `@helia/unixfs`: IPFS file system operations
|
||||||
|
- `@libp2p/*`: Various libp2p modules for networking
|
||||||
|
- `winston`: Logging
|
||||||
|
- `node-cache`: In-memory caching
|
||||||
|
- `express`: HTTP server for integration tests
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/framework/
|
||||||
|
├── core/ # Core framework components
|
||||||
|
│ ├── ConfigManager.ts
|
||||||
|
│ ├── DatabaseManager.ts
|
||||||
|
│ └── ModelRegistry.ts
|
||||||
|
├── models/ # Model system and decorators
|
||||||
|
│ ├── BaseModel.ts
|
||||||
|
│ └── decorators/
|
||||||
|
├── query/ # Query builder and execution
|
||||||
|
│ ├── QueryBuilder.ts
|
||||||
|
│ ├── QueryExecutor.ts
|
||||||
|
│ ├── QueryOptimizer.ts
|
||||||
|
│ └── QueryCache.ts
|
||||||
|
├── relationships/ # Relationship management
|
||||||
|
│ ├── RelationshipManager.ts
|
||||||
|
│ ├── LazyLoader.ts
|
||||||
|
│ └── RelationshipCache.ts
|
||||||
|
├── sharding/ # Data sharding logic
|
||||||
|
│ └── ShardManager.ts
|
||||||
|
├── migrations/ # Schema migration system
|
||||||
|
│ ├── MigrationManager.ts
|
||||||
|
│ └── MigrationBuilder.ts
|
||||||
|
├── pinning/ # Automatic pinning features
|
||||||
|
│ └── PinningManager.ts
|
||||||
|
├── pubsub/ # Event publishing system
|
||||||
|
│ └── PubSubManager.ts
|
||||||
|
├── services/ # External service integrations
|
||||||
|
│ ├── OrbitDBService.ts
|
||||||
|
│ ├── IPFSService.ts
|
||||||
|
│ └── RealOrbitDBService.ts
|
||||||
|
└── types/ # TypeScript type definitions
|
||||||
|
├── framework.ts
|
||||||
|
├── models.ts
|
||||||
|
└── queries.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Status
|
||||||
|
|
||||||
|
**Current State**: Beta (v0.5.1-beta) - Active Development
|
||||||
|
|
||||||
|
### What's Stable
|
||||||
|
- ✅ Core model system with decorators
|
||||||
|
- ✅ Basic CRUD operations
|
||||||
|
- ✅ Query builder and execution
|
||||||
|
- ✅ Relationship management
|
||||||
|
- ✅ Database management and sharding
|
||||||
|
- ✅ Migration system foundation
|
||||||
|
|
||||||
|
### What's In Development
|
||||||
|
- 🚧 Advanced query optimization
|
||||||
|
- 🚧 Performance optimization features
|
||||||
|
- 🚧 Production-ready configurations
|
||||||
|
- 🚧 Extended relationship types
|
||||||
|
- 🚧 Enhanced error handling
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- **Unit Tests**: Comprehensive test suite using Jest
|
||||||
|
- **Integration Tests**: Docker-based real-world scenarios
|
||||||
|
- **Blog Scenario**: Complete blogging platform test case
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
### Model Scoping
|
||||||
|
- **Global Models**: Shared across all users (e.g., User profiles)
|
||||||
|
- **User Models**: Each user has their own database instance (e.g., Posts, Comments)
|
||||||
|
|
||||||
|
### Sharding Strategies
|
||||||
|
- **Hash Sharding**: Distribute data based on key hashing
|
||||||
|
- **Range Sharding**: Distribute data based on value ranges
|
||||||
|
- **User Sharding**: Dedicated shards per user or user group
|
||||||
|
|
||||||
|
### Query System
|
||||||
|
- **Chainable API**: Fluent interface for building queries
|
||||||
|
- **Type Safety**: Full TypeScript support with auto-completion
|
||||||
|
- **Relationship Loading**: Eager and lazy loading strategies
|
||||||
|
- **Caching**: Intelligent query result caching
|
||||||
|
|
||||||
|
### Relationships
|
||||||
|
- **BelongsTo**: Many-to-one relationships
|
||||||
|
- **HasMany**: One-to-many relationships
|
||||||
|
- **HasOne**: One-to-one relationships
|
||||||
|
- **ManyToMany**: Many-to-many relationships (with through tables)
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### 1. Social Platforms
|
||||||
|
- User profiles and authentication
|
||||||
|
- Posts, comments, and reactions
|
||||||
|
- Friend networks and messaging
|
||||||
|
- Activity feeds and notifications
|
||||||
|
|
||||||
|
### 2. Content Management
|
||||||
|
- Blogs and publishing platforms
|
||||||
|
- Document management systems
|
||||||
|
- Media galleries and collections
|
||||||
|
- Version control for content
|
||||||
|
|
||||||
|
### 3. Collaborative Applications
|
||||||
|
- Real-time document editing
|
||||||
|
- Project management tools
|
||||||
|
- Team collaboration platforms
|
||||||
|
- Knowledge bases and wikis
|
||||||
|
|
||||||
|
### 4. Marketplace Applications
|
||||||
|
- Product catalogs and inventory
|
||||||
|
- User reviews and ratings
|
||||||
|
- Order management systems
|
||||||
|
- Payment and transaction records
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Node.js 18.0 or higher
|
||||||
|
- TypeScript knowledge
|
||||||
|
- Basic understanding of IPFS and OrbitDB concepts
|
||||||
|
- Docker (for integration tests)
|
||||||
|
|
||||||
|
### Build Process
|
||||||
|
```bash
|
||||||
|
npm run build # Compile TypeScript
|
||||||
|
npm run dev # Development with watch mode
|
||||||
|
npm run lint # ESLint code checking
|
||||||
|
npm run format # Prettier code formatting
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
npm run test:unit # Fast unit tests with mocks
|
||||||
|
npm run test:real # Full integration tests with Docker
|
||||||
|
npm run test:blog-integration # Blog scenario integration tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Development Principles
|
||||||
|
1. **Type Safety First**: Everything is strongly typed
|
||||||
|
2. **Decorator-Based**: Use decorators for configuration
|
||||||
|
3. **Async/Await**: All operations are promise-based
|
||||||
|
4. **Error Handling**: Comprehensive error management
|
||||||
|
5. **Performance**: Built-in optimization and caching
|
||||||
|
6. **Scalability**: Designed for distributed systems
|
||||||
|
|
||||||
|
## API Patterns
|
||||||
|
|
||||||
|
### Model Definition
|
||||||
|
```typescript
|
||||||
|
@Model({
|
||||||
|
scope: 'user',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: { strategy: 'hash', count: 4, key: 'userId' }
|
||||||
|
})
|
||||||
|
class Post extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@BelongsTo(() => User, 'userId')
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query Operations
|
||||||
|
```typescript
|
||||||
|
const posts = await Post.query()
|
||||||
|
.where('isPublished', true)
|
||||||
|
.where('createdAt', '>', Date.now() - 7 * 24 * 60 * 60 * 1000)
|
||||||
|
.with(['user', 'comments'])
|
||||||
|
.orderBy('likeCount', 'desc')
|
||||||
|
.limit(20)
|
||||||
|
.find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lifecycle Hooks
|
||||||
|
```typescript
|
||||||
|
class User extends BaseModel {
|
||||||
|
@BeforeCreate()
|
||||||
|
setupNewUser() {
|
||||||
|
this.registeredAt = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterCreate()
|
||||||
|
async sendWelcomeEmail() {
|
||||||
|
// Business logic after creation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This framework makes building decentralized applications feel like traditional web development while providing the benefits of distributed, peer-to-peer systems.
|
607
.claude/technical-reference.md
Normal file
607
.claude/technical-reference.md
Normal file
@ -0,0 +1,607 @@
|
|||||||
|
# DebrosFramework Technical Reference
|
||||||
|
|
||||||
|
## Core Architecture Components
|
||||||
|
|
||||||
|
### DebrosFramework Main Class
|
||||||
|
**Location**: `src/framework/DebrosFramework.ts`
|
||||||
|
|
||||||
|
The main framework class that orchestrates all components:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class DebrosFramework {
|
||||||
|
// Core services
|
||||||
|
private orbitDBService: FrameworkOrbitDBService;
|
||||||
|
private ipfsService: FrameworkIPFSService;
|
||||||
|
|
||||||
|
// Framework components
|
||||||
|
private databaseManager: DatabaseManager;
|
||||||
|
private shardManager: ShardManager;
|
||||||
|
private queryCache: QueryCache;
|
||||||
|
private relationshipManager: RelationshipManager;
|
||||||
|
private pinningManager: PinningManager;
|
||||||
|
private pubsubManager: PubSubManager;
|
||||||
|
private migrationManager: MigrationManager;
|
||||||
|
|
||||||
|
// Lifecycle methods
|
||||||
|
async initialize(orbitDBService?, ipfsService?, config?): Promise<void>
|
||||||
|
async start(): Promise<void>
|
||||||
|
async stop(): Promise<void>
|
||||||
|
getStatus(): FrameworkStatus
|
||||||
|
getMetrics(): FrameworkMetrics
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### BaseModel Class
|
||||||
|
**Location**: `src/framework/models/BaseModel.ts`
|
||||||
|
|
||||||
|
Abstract base class for all data models:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export abstract class BaseModel {
|
||||||
|
// Instance properties
|
||||||
|
public id: string;
|
||||||
|
public _loadedRelations: Map<string, any>;
|
||||||
|
protected _isDirty: boolean;
|
||||||
|
protected _isNew: boolean;
|
||||||
|
|
||||||
|
// Static configuration
|
||||||
|
static modelName: string;
|
||||||
|
static storeType: StoreType;
|
||||||
|
static scope: 'user' | 'global';
|
||||||
|
static sharding?: ShardingConfig;
|
||||||
|
static fields: Map<string, FieldConfig>;
|
||||||
|
static relationships: Map<string, RelationshipConfig>;
|
||||||
|
|
||||||
|
// CRUD operations
|
||||||
|
async save(): Promise<this>
|
||||||
|
static async create<T>(data: any): Promise<T>
|
||||||
|
static async findById<T>(id: string): Promise<T | null>
|
||||||
|
static query<T>(): QueryBuilder<T>
|
||||||
|
async delete(): Promise<void>
|
||||||
|
|
||||||
|
// Lifecycle hooks
|
||||||
|
async beforeCreate(): Promise<void>
|
||||||
|
async afterCreate(): Promise<void>
|
||||||
|
async beforeUpdate(): Promise<void>
|
||||||
|
async afterUpdate(): Promise<void>
|
||||||
|
async beforeDelete(): Promise<void>
|
||||||
|
async afterDelete(): Promise<void>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Decorator System
|
||||||
|
|
||||||
|
### @Model Decorator
|
||||||
|
**Location**: `src/framework/models/decorators/Model.ts`
|
||||||
|
|
||||||
|
Configures model behavior and database storage:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ModelConfig {
|
||||||
|
scope: 'user' | 'global'; // Database scope
|
||||||
|
type: StoreType; // OrbitDB store type
|
||||||
|
sharding?: ShardingConfig; // Data distribution strategy
|
||||||
|
pinning?: PinningConfig; // Automatic pinning configuration
|
||||||
|
pubsub?: PubSubConfig; // Event publishing configuration
|
||||||
|
validation?: ValidationConfig; // Model-level validation
|
||||||
|
}
|
||||||
|
|
||||||
|
@Model({
|
||||||
|
scope: 'user',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: { strategy: 'hash', count: 4, key: 'userId' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### @Field Decorator
|
||||||
|
**Location**: `src/framework/models/decorators/Field.ts`
|
||||||
|
|
||||||
|
Defines field properties and validation:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface FieldConfig {
|
||||||
|
type: FieldType; // Data type
|
||||||
|
required?: boolean; // Required field
|
||||||
|
unique?: boolean; // Unique constraint
|
||||||
|
default?: any | (() => any); // Default value
|
||||||
|
validate?: (value: any) => boolean; // Custom validation
|
||||||
|
transform?: (value: any) => any; // Data transformation
|
||||||
|
serialize?: boolean; // Include in serialization
|
||||||
|
index?: boolean; // Create index for field
|
||||||
|
virtual?: boolean; // Virtual field (not stored)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
validate: (value: string) => value.length >= 3,
|
||||||
|
transform: (value: string) => value.trim().toLowerCase()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Relationship Decorators
|
||||||
|
**Location**: `src/framework/models/decorators/relationships.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Many-to-one relationship
|
||||||
|
@BelongsTo(() => User, 'userId')
|
||||||
|
author: User;
|
||||||
|
|
||||||
|
// One-to-many relationship
|
||||||
|
@HasMany(() => Post, 'authorId')
|
||||||
|
posts: Post[];
|
||||||
|
|
||||||
|
// One-to-one relationship
|
||||||
|
@HasOne(() => Profile, 'userId')
|
||||||
|
profile: Profile;
|
||||||
|
|
||||||
|
// Many-to-many relationship
|
||||||
|
@ManyToMany(() => Tag, 'post_tags', 'tag_id', 'post_id')
|
||||||
|
tags: Tag[];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lifecycle Hook Decorators
|
||||||
|
**Location**: `src/framework/models/decorators/hooks.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@BeforeCreate()
|
||||||
|
setupDefaults() {
|
||||||
|
this.createdAt = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterCreate()
|
||||||
|
async sendNotification() {
|
||||||
|
await this.notifyUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeUpdate()
|
||||||
|
updateTimestamp() {
|
||||||
|
this.updatedAt = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterUpdate()
|
||||||
|
async invalidateCache() {
|
||||||
|
await this.clearRelatedCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeDelete()
|
||||||
|
async checkPermissions() {
|
||||||
|
if (!this.canDelete()) {
|
||||||
|
throw new Error('Cannot delete this record');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterDelete()
|
||||||
|
async cleanupRelations() {
|
||||||
|
await this.removeRelatedData();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query System
|
||||||
|
|
||||||
|
### QueryBuilder Class
|
||||||
|
**Location**: `src/framework/query/QueryBuilder.ts`
|
||||||
|
|
||||||
|
Fluent interface for building queries:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class QueryBuilder<T> {
|
||||||
|
// Filter methods
|
||||||
|
where(field: string, value: any): QueryBuilder<T>
|
||||||
|
where(field: string, operator: string, value: any): QueryBuilder<T>
|
||||||
|
where(callback: (query: QueryBuilder<T>) => void): QueryBuilder<T>
|
||||||
|
orWhere(field: string, value: any): QueryBuilder<T>
|
||||||
|
whereIn(field: string, values: any[]): QueryBuilder<T>
|
||||||
|
whereNotIn(field: string, values: any[]): QueryBuilder<T>
|
||||||
|
whereNull(field: string): QueryBuilder<T>
|
||||||
|
whereNotNull(field: string): QueryBuilder<T>
|
||||||
|
whereLike(field: string, pattern: string): QueryBuilder<T>
|
||||||
|
|
||||||
|
// Relationship methods
|
||||||
|
with(relations: string[]): QueryBuilder<T>
|
||||||
|
withCount(relations: string[]): QueryBuilder<T>
|
||||||
|
|
||||||
|
// Ordering and limiting
|
||||||
|
orderBy(field: string, direction?: 'asc' | 'desc'): QueryBuilder<T>
|
||||||
|
limit(count: number): QueryBuilder<T>
|
||||||
|
offset(count: number): QueryBuilder<T>
|
||||||
|
|
||||||
|
// Field selection
|
||||||
|
select(fields: string[]): QueryBuilder<T>
|
||||||
|
distinct(field?: string): QueryBuilder<T>
|
||||||
|
|
||||||
|
// Caching
|
||||||
|
cache(ttl?: number): QueryBuilder<T>
|
||||||
|
|
||||||
|
// Execution methods
|
||||||
|
find(): Promise<T[]>
|
||||||
|
findOne(): Promise<T | null>
|
||||||
|
first(): Promise<T | null>
|
||||||
|
count(): Promise<number>
|
||||||
|
exists(): Promise<boolean>
|
||||||
|
paginate(page: number, perPage: number): Promise<PaginationResult<T>>
|
||||||
|
|
||||||
|
// Aggregation
|
||||||
|
sum(field: string): Promise<number>
|
||||||
|
avg(field: string): Promise<number>
|
||||||
|
min(field: string): Promise<any>
|
||||||
|
max(field: string): Promise<any>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query Operators
|
||||||
|
```typescript
|
||||||
|
type QueryOperator =
|
||||||
|
| 'eq' // Equal to
|
||||||
|
| 'ne' // Not equal to
|
||||||
|
| 'gt' // Greater than
|
||||||
|
| 'gte' // Greater than or equal
|
||||||
|
| 'lt' // Less than
|
||||||
|
| 'lte' // Less than or equal
|
||||||
|
| 'in' // In array
|
||||||
|
| 'not in' // Not in array
|
||||||
|
| 'like' // Pattern matching
|
||||||
|
| 'regex' // Regular expression
|
||||||
|
| 'is null' // Is null
|
||||||
|
| 'is not null' // Is not null
|
||||||
|
| 'includes' // Array includes value
|
||||||
|
| 'includes any'// Array includes any of values
|
||||||
|
| 'includes all'// Array includes all values
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Management
|
||||||
|
|
||||||
|
### DatabaseManager Class
|
||||||
|
**Location**: `src/framework/core/DatabaseManager.ts`
|
||||||
|
|
||||||
|
Handles database creation and lifecycle:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class DatabaseManager {
|
||||||
|
// Database operations
|
||||||
|
async getGlobalDatabase(modelName: string): Promise<Database>
|
||||||
|
async getUserDatabase(userId: string, modelName: string): Promise<Database>
|
||||||
|
async createDatabase(name: string, type: StoreType, options?: any): Promise<Database>
|
||||||
|
async closeDatabase(name: string): Promise<void>
|
||||||
|
|
||||||
|
// Document operations
|
||||||
|
async getDocument(database: Database, storeType: StoreType, id: string): Promise<any>
|
||||||
|
async putDocument(database: Database, storeType: StoreType, id: string, data: any): Promise<void>
|
||||||
|
async deleteDocument(database: Database, storeType: StoreType, id: string): Promise<void>
|
||||||
|
async queryDocuments(database: Database, storeType: StoreType, query: any): Promise<any[]>
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
async initialize(orbitDBService: FrameworkOrbitDBService): Promise<void>
|
||||||
|
async stop(): Promise<void>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ShardManager Class
|
||||||
|
**Location**: `src/framework/sharding/ShardManager.ts`
|
||||||
|
|
||||||
|
Handles data distribution across shards:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class ShardManager {
|
||||||
|
// Shard operations
|
||||||
|
getShardForKey(modelName: string, key: string): Shard
|
||||||
|
getShardForRange(modelName: string, value: any): Shard
|
||||||
|
getAllShards(modelName: string): Shard[]
|
||||||
|
|
||||||
|
// Shard management
|
||||||
|
async createShards(modelName: string, config: ShardingConfig): Promise<void>
|
||||||
|
async redistributeData(modelName: string, newShardCount: number): Promise<void>
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
setShardingConfig(modelName: string, config: ShardingConfig): void
|
||||||
|
getShardingConfig(modelName: string): ShardingConfig | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ShardingConfig {
|
||||||
|
strategy: 'hash' | 'range' | 'user';
|
||||||
|
count: number;
|
||||||
|
key: string;
|
||||||
|
ranges?: ShardRange[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Shard {
|
||||||
|
id: string;
|
||||||
|
database: Database;
|
||||||
|
range?: { min: any; max: any };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relationship Management
|
||||||
|
|
||||||
|
### RelationshipManager Class
|
||||||
|
**Location**: `src/framework/relationships/RelationshipManager.ts`
|
||||||
|
|
||||||
|
Handles loading and caching of model relationships:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class RelationshipManager {
|
||||||
|
// Relationship loading
|
||||||
|
async loadRelationship(model: BaseModel, relationshipName: string): Promise<any>
|
||||||
|
async loadRelationships(model: BaseModel, relationshipNames: string[]): Promise<void>
|
||||||
|
async eagerLoadRelationships(models: BaseModel[], relationshipNames: string[]): Promise<void>
|
||||||
|
|
||||||
|
// Relationship operations
|
||||||
|
async attachRelationship(model: BaseModel, relationshipName: string, relatedModel: BaseModel): Promise<void>
|
||||||
|
async detachRelationship(model: BaseModel, relationshipName: string, relatedModel: BaseModel): Promise<void>
|
||||||
|
async syncRelationship(model: BaseModel, relationshipName: string, relatedModels: BaseModel[]): Promise<void>
|
||||||
|
|
||||||
|
// Caching
|
||||||
|
getCachedRelationship(model: BaseModel, relationshipName: string): any
|
||||||
|
setCachedRelationship(model: BaseModel, relationshipName: string, data: any): void
|
||||||
|
clearRelationshipCache(model: BaseModel, relationshipName?: string): void
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration System
|
||||||
|
|
||||||
|
### MigrationManager Class
|
||||||
|
**Location**: `src/framework/migrations/MigrationManager.ts`
|
||||||
|
|
||||||
|
Handles schema evolution and data transformation:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class MigrationManager {
|
||||||
|
// Migration operations
|
||||||
|
async runMigration(migrationId: string): Promise<MigrationResult>
|
||||||
|
async rollbackMigration(migrationId: string): Promise<MigrationResult>
|
||||||
|
async runPendingMigrations(): Promise<MigrationResult[]>
|
||||||
|
|
||||||
|
// Migration management
|
||||||
|
registerMigration(migration: Migration): void
|
||||||
|
getPendingMigrations(): Migration[]
|
||||||
|
getAppliedMigrations(): Promise<string[]>
|
||||||
|
|
||||||
|
// Status
|
||||||
|
getMigrationStatus(): Promise<MigrationStatus>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Migration {
|
||||||
|
id: string;
|
||||||
|
version: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
targetModels: string[];
|
||||||
|
up: MigrationOperation[];
|
||||||
|
down: MigrationOperation[];
|
||||||
|
dependencies?: string[];
|
||||||
|
validators?: MigrationValidator[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### MigrationBuilder Class
|
||||||
|
**Location**: `src/framework/migrations/MigrationBuilder.ts`
|
||||||
|
|
||||||
|
Fluent interface for creating migrations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export function createMigration(name: string, version: string): MigrationBuilder {
|
||||||
|
return new MigrationBuilder(name, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MigrationBuilder {
|
||||||
|
// Field operations
|
||||||
|
addField(modelName: string, fieldName: string, config: FieldConfig): MigrationBuilder
|
||||||
|
removeField(modelName: string, fieldName: string): MigrationBuilder
|
||||||
|
modifyField(modelName: string, fieldName: string, config: FieldConfig): MigrationBuilder
|
||||||
|
renameField(modelName: string, oldName: string, newName: string): MigrationBuilder
|
||||||
|
|
||||||
|
// Index operations
|
||||||
|
addIndex(modelName: string, fields: string[], options?: IndexOptions): MigrationBuilder
|
||||||
|
removeIndex(modelName: string, indexName: string): MigrationBuilder
|
||||||
|
|
||||||
|
// Data transformation
|
||||||
|
transformData(modelName: string, transformer: (data: any) => any): MigrationBuilder
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
addValidator(name: string, validator: MigrationValidator): MigrationBuilder
|
||||||
|
|
||||||
|
// Build migration
|
||||||
|
build(): Migration
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caching System
|
||||||
|
|
||||||
|
### QueryCache Class
|
||||||
|
**Location**: `src/framework/query/QueryCache.ts`
|
||||||
|
|
||||||
|
Intelligent caching of query results:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class QueryCache {
|
||||||
|
// Cache operations
|
||||||
|
get(key: string): Promise<any>
|
||||||
|
set(key: string, value: any, ttl?: number): Promise<void>
|
||||||
|
delete(key: string): Promise<void>
|
||||||
|
clear(): Promise<void>
|
||||||
|
|
||||||
|
// Cache management
|
||||||
|
invalidateModelCache(modelName: string): Promise<void>
|
||||||
|
invalidateUserCache(userId: string): Promise<void>
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
getStats(): CacheStats
|
||||||
|
getHitRate(): number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### RelationshipCache Class
|
||||||
|
**Location**: `src/framework/relationships/RelationshipCache.ts`
|
||||||
|
|
||||||
|
Specialized caching for relationship data:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class RelationshipCache {
|
||||||
|
// Relationship caching
|
||||||
|
getCachedRelationship(modelId: string, relationshipName: string): any
|
||||||
|
setCachedRelationship(modelId: string, relationshipName: string, data: any, ttl?: number): void
|
||||||
|
invalidateRelationship(modelId: string, relationshipName: string): void
|
||||||
|
|
||||||
|
// Batch operations
|
||||||
|
preloadRelationships(modelIds: string[], relationshipNames: string[]): Promise<void>
|
||||||
|
warmCache(modelName: string, relationshipName: string): Promise<void>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Type Definitions
|
||||||
|
|
||||||
|
### Framework Types
|
||||||
|
**Location**: `src/framework/types/framework.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface FrameworkConfig {
|
||||||
|
cache?: CacheConfig;
|
||||||
|
queryOptimization?: QueryOptimizationConfig;
|
||||||
|
automaticPinning?: PinningConfig;
|
||||||
|
pubsub?: PubSubConfig;
|
||||||
|
development?: DevelopmentConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CacheConfig {
|
||||||
|
enabled?: boolean;
|
||||||
|
maxSize?: number;
|
||||||
|
ttl?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StoreType = 'docstore' | 'eventlog' | 'keyvalue' | 'counter' | 'feed';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Model Types
|
||||||
|
**Location**: `src/framework/types/models.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface FieldConfig {
|
||||||
|
type: FieldType;
|
||||||
|
required?: boolean;
|
||||||
|
unique?: boolean;
|
||||||
|
default?: any | (() => any);
|
||||||
|
validate?: (value: any) => boolean;
|
||||||
|
transform?: (value: any) => any;
|
||||||
|
serialize?: boolean;
|
||||||
|
index?: boolean;
|
||||||
|
virtual?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FieldType = 'string' | 'number' | 'boolean' | 'array' | 'object' | 'date';
|
||||||
|
|
||||||
|
export interface RelationshipConfig {
|
||||||
|
type: 'belongsTo' | 'hasMany' | 'hasOne' | 'manyToMany';
|
||||||
|
target: () => typeof BaseModel;
|
||||||
|
foreignKey: string;
|
||||||
|
localKey?: string;
|
||||||
|
through?: string;
|
||||||
|
throughForeignKey?: string;
|
||||||
|
throughLocalKey?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query Types
|
||||||
|
**Location**: `src/framework/types/queries.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface QueryOptions {
|
||||||
|
where?: WhereClause[];
|
||||||
|
orderBy?: OrderByClause[];
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
with?: string[];
|
||||||
|
cache?: boolean | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WhereClause {
|
||||||
|
field: string;
|
||||||
|
operator: QueryOperator;
|
||||||
|
value: any;
|
||||||
|
boolean: 'and' | 'or';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderByClause {
|
||||||
|
field: string;
|
||||||
|
direction: 'asc' | 'desc';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginationResult<T> {
|
||||||
|
data: T[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
perPage: number;
|
||||||
|
totalPages: number;
|
||||||
|
hasNext: boolean;
|
||||||
|
hasPrev: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Framework Errors
|
||||||
|
```typescript
|
||||||
|
export class DebrosFrameworkError extends Error {
|
||||||
|
code: string;
|
||||||
|
details?: any;
|
||||||
|
|
||||||
|
constructor(message: string, code?: string, details?: any) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'DebrosFrameworkError';
|
||||||
|
this.code = code || 'UNKNOWN_ERROR';
|
||||||
|
this.details = details;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ValidationError extends DebrosFrameworkError {
|
||||||
|
field: string;
|
||||||
|
value: any;
|
||||||
|
constraint: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QueryError extends DebrosFrameworkError {
|
||||||
|
query: string;
|
||||||
|
parameters?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RelationshipError extends DebrosFrameworkError {
|
||||||
|
modelName: string;
|
||||||
|
relationshipName: string;
|
||||||
|
relatedModel: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Optimization
|
||||||
|
|
||||||
|
### Query Optimization
|
||||||
|
**Location**: `src/framework/query/QueryOptimizer.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class QueryOptimizer {
|
||||||
|
// Query optimization
|
||||||
|
optimizeQuery(query: QueryBuilder<any>): QueryBuilder<any>
|
||||||
|
analyzeQueryPerformance(query: QueryBuilder<any>): Promise<QueryAnalysis>
|
||||||
|
suggestIndexes(modelName: string): Promise<IndexSuggestion[]>
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
getSlowQueries(): Promise<SlowQuery[]>
|
||||||
|
getQueryStats(): Promise<QueryStats>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### LazyLoader Class
|
||||||
|
**Location**: `src/framework/relationships/LazyLoader.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class LazyLoader {
|
||||||
|
// Lazy loading
|
||||||
|
async loadOnDemand(model: BaseModel, relationshipName: string): Promise<any>
|
||||||
|
async batchLoad(models: BaseModel[], relationshipName: string): Promise<void>
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
setBatchSize(size: number): void
|
||||||
|
setLoadingStrategy(strategy: 'immediate' | 'batched' | 'deferred'): void
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This technical reference provides the implementation details needed to work effectively with the DebrosFramework codebase.
|
@ -2,9 +2,15 @@
|
|||||||
"src/**/*.{js,ts}": [
|
"src/**/*.{js,ts}": [
|
||||||
"prettier --write",
|
"prettier --write",
|
||||||
"eslint --fix",
|
"eslint --fix",
|
||||||
|
"pnpm run test:unit",
|
||||||
"npm run build"
|
"npm run build"
|
||||||
],
|
],
|
||||||
|
"tests/**/*.{js,ts}": [
|
||||||
|
"prettier --write",
|
||||||
|
"eslint --fix",
|
||||||
|
"pnpm run test:unit"
|
||||||
|
],
|
||||||
"*.{json,md}": [
|
"*.{json,md}": [
|
||||||
"prettier --write"
|
"prettier --write"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
347
CONTRIBUTING.md
Normal file
347
CONTRIBUTING.md
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
# Contributing to DebrosFramework
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to DebrosFramework! This document provides guidelines and information for contributors.
|
||||||
|
|
||||||
|
## Development Status
|
||||||
|
|
||||||
|
DebrosFramework is currently in **beta (v0.5.0-beta)** and under active development. We welcome contributions from the community to help improve the framework.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js 18.0 or higher
|
||||||
|
- npm or pnpm package manager
|
||||||
|
- Git
|
||||||
|
- TypeScript knowledge
|
||||||
|
- Familiarity with IPFS and OrbitDB concepts
|
||||||
|
|
||||||
|
### Development Setup
|
||||||
|
|
||||||
|
1. **Fork and Clone**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YOUR_USERNAME/network.git
|
||||||
|
cd network
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install Dependencies**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Build the Project**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Run Tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unit tests
|
||||||
|
pnpm run test:unit
|
||||||
|
|
||||||
|
# Integration tests
|
||||||
|
pnpm run test:real
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/framework/
|
||||||
|
├── core/ # Core framework components
|
||||||
|
├── models/ # Model system and decorators
|
||||||
|
├── query/ # Query builder and execution
|
||||||
|
├── relationships/ # Relationship management
|
||||||
|
├── sharding/ # Data sharding logic
|
||||||
|
├── migrations/ # Schema migration system
|
||||||
|
├── pinning/ # Automatic pinning features
|
||||||
|
├── pubsub/ # Event publishing system
|
||||||
|
└── types/ # TypeScript type definitions
|
||||||
|
|
||||||
|
docs/docs/ # Documentation source
|
||||||
|
tests/ # Test suites
|
||||||
|
└── real-integration/ # Integration test scenarios
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to Contribute
|
||||||
|
|
||||||
|
### Types of Contributions
|
||||||
|
|
||||||
|
We welcome the following types of contributions:
|
||||||
|
|
||||||
|
1. **🐛 Bug Reports** - Report issues and bugs
|
||||||
|
2. **✨ Feature Requests** - Suggest new features
|
||||||
|
3. **📖 Documentation** - Improve docs and examples
|
||||||
|
4. **🔧 Code Contributions** - Bug fixes and new features
|
||||||
|
5. **🧪 Testing** - Add tests and improve test coverage
|
||||||
|
6. **💡 Examples** - Create usage examples and tutorials
|
||||||
|
|
||||||
|
### Bug Reports
|
||||||
|
|
||||||
|
When reporting bugs, please include:
|
||||||
|
|
||||||
|
- **Clear description** of the issue
|
||||||
|
- **Steps to reproduce** the problem
|
||||||
|
- **Expected vs actual behavior**
|
||||||
|
- **Environment details** (Node.js version, OS, etc.)
|
||||||
|
- **Code examples** that demonstrate the issue
|
||||||
|
- **Error messages** and stack traces
|
||||||
|
|
||||||
|
Use our bug report template:
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
## Bug Description
|
||||||
|
|
||||||
|
[Clear description of the bug]
|
||||||
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
|
||||||
|
1. [First step]
|
||||||
|
2. [Second step]
|
||||||
|
3. [etc.]
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
[What you expected to happen]
|
||||||
|
|
||||||
|
## Actual Behavior
|
||||||
|
|
||||||
|
[What actually happened]
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
- DebrosFramework version: [version]
|
||||||
|
- Node.js version: [version]
|
||||||
|
- OS: [operating system]
|
||||||
|
|
||||||
|
## Code Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Minimal code example that reproduces the issue
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
### Feature Requests
|
||||||
|
|
||||||
|
For feature requests, please provide:
|
||||||
|
|
||||||
|
- **Clear use case** and motivation
|
||||||
|
- **Detailed description** of the proposed feature
|
||||||
|
- **API design suggestions** (if applicable)
|
||||||
|
- **Examples** of how it would be used
|
||||||
|
- **Alternatives considered**
|
||||||
|
|
||||||
|
### Code Contributions
|
||||||
|
|
||||||
|
#### Before You Start
|
||||||
|
|
||||||
|
1. **Check existing issues** to avoid duplicate work
|
||||||
|
2. **Discuss large changes** in an issue first
|
||||||
|
3. **Follow the coding standards** outlined below
|
||||||
|
4. **Write tests** for your changes
|
||||||
|
5. **Update documentation** as needed
|
||||||
|
|
||||||
|
#### Development Workflow
|
||||||
|
|
||||||
|
1. **Create a feature branch**
|
||||||
|
```bash
|
||||||
|
git checkout -b feature/your-feature-name
|
||||||
|
````
|
||||||
|
|
||||||
|
2. **Make your changes**
|
||||||
|
|
||||||
|
- Write clean, well-documented code
|
||||||
|
- Follow TypeScript best practices
|
||||||
|
- Add tests for new functionality
|
||||||
|
- Update relevant documentation
|
||||||
|
|
||||||
|
3. **Test your changes**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run test:unit
|
||||||
|
pnpm run test:real
|
||||||
|
pnpm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Commit your changes**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "feat: add new feature description"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Push and create PR**
|
||||||
|
```bash
|
||||||
|
git push origin feature/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Commit Message Format
|
||||||
|
|
||||||
|
We use conventional commits for consistent commit messages:
|
||||||
|
|
||||||
|
```
|
||||||
|
type(scope): description
|
||||||
|
|
||||||
|
body (optional)
|
||||||
|
|
||||||
|
footer (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Types:**
|
||||||
|
|
||||||
|
- `feat`: New features
|
||||||
|
- `fix`: Bug fixes
|
||||||
|
- `docs`: Documentation changes
|
||||||
|
- `style`: Code style changes (formatting, etc.)
|
||||||
|
- `refactor`: Code refactoring
|
||||||
|
- `test`: Adding or updating tests
|
||||||
|
- `chore`: Maintenance tasks
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(models): add support for computed fields
|
||||||
|
fix(query): resolve relationship loading issue
|
||||||
|
docs(readme): update installation instructions
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Code Style Guidelines
|
||||||
|
|
||||||
|
1. **TypeScript**
|
||||||
|
|
||||||
|
- Use strict TypeScript configuration
|
||||||
|
- Provide proper type annotations
|
||||||
|
- Use interfaces for object types
|
||||||
|
- Follow naming conventions
|
||||||
|
|
||||||
|
2. **Formatting**
|
||||||
|
|
||||||
|
- Use Prettier for code formatting
|
||||||
|
- Run `pnpm run format` before committing
|
||||||
|
- Use 2 spaces for indentation
|
||||||
|
|
||||||
|
3. **ESLint**
|
||||||
|
|
||||||
|
- Follow ESLint rules
|
||||||
|
- Run `pnpm run lint` and fix any issues
|
||||||
|
- Use `pnpm run lint:fix` for auto-fixes
|
||||||
|
|
||||||
|
4. **Documentation**
|
||||||
|
- Add JSDoc comments for public APIs
|
||||||
|
- Update relevant documentation files
|
||||||
|
- Include code examples where appropriate
|
||||||
|
|
||||||
|
#### Testing Guidelines
|
||||||
|
|
||||||
|
1. **Unit Tests**
|
||||||
|
|
||||||
|
- Write tests for all new functionality
|
||||||
|
- Use Jest for unit testing
|
||||||
|
- Aim for high code coverage
|
||||||
|
- Test edge cases and error conditions
|
||||||
|
|
||||||
|
2. **Integration Tests**
|
||||||
|
|
||||||
|
- Add integration tests for significant features
|
||||||
|
- Test real-world scenarios
|
||||||
|
- Use the blog scenario tests as reference
|
||||||
|
|
||||||
|
3. **Test Structure**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
describe('FeatureName', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Setup
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should behave correctly in normal case', () => {
|
||||||
|
// Test implementation
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle edge case', () => {
|
||||||
|
// Edge case test
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for invalid input', () => {
|
||||||
|
// Error case test
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
### Documentation Structure
|
||||||
|
|
||||||
|
- **README.md** - Overview and quick start
|
||||||
|
- **docs/docs/intro.md** - Framework introduction
|
||||||
|
- **docs/docs/getting-started.md** - Setup guide
|
||||||
|
- **docs/docs/core-concepts/** - Architecture and concepts
|
||||||
|
- **docs/docs/api/** - API reference
|
||||||
|
- **docs/docs/examples/** - Usage examples
|
||||||
|
|
||||||
|
### Writing Documentation
|
||||||
|
|
||||||
|
1. **Use clear, concise language**
|
||||||
|
2. **Provide code examples**
|
||||||
|
3. **Include both basic and advanced usage**
|
||||||
|
4. **Keep examples up-to-date**
|
||||||
|
5. **Add diagrams where helpful**
|
||||||
|
|
||||||
|
### Building Documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd docs
|
||||||
|
npm install
|
||||||
|
npm run start # Development server
|
||||||
|
npm run build # Production build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Release Process
|
||||||
|
|
||||||
|
Releases are managed by the core team and follow semantic versioning:
|
||||||
|
|
||||||
|
- **Patch** (0.5.1): Bug fixes and small improvements
|
||||||
|
- **Minor** (0.6.0): New features, backward compatible
|
||||||
|
- **Major** (1.0.0): Breaking changes
|
||||||
|
|
||||||
|
## Community Guidelines
|
||||||
|
|
||||||
|
### Code of Conduct
|
||||||
|
|
||||||
|
We are committed to providing a welcoming and inclusive environment. Please:
|
||||||
|
|
||||||
|
- **Be respectful** and considerate
|
||||||
|
- **Use inclusive language**
|
||||||
|
- **Accept constructive feedback**
|
||||||
|
- **Focus on what's best** for the community
|
||||||
|
- **Show empathy** towards other contributors
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
|
||||||
|
- **GitHub Issues** - For bug reports and feature requests
|
||||||
|
- **GitHub Discussions** - For questions and community discussion
|
||||||
|
- **Discord** - For real-time chat and support
|
||||||
|
|
||||||
|
## Recognition
|
||||||
|
|
||||||
|
Contributors will be recognized in:
|
||||||
|
|
||||||
|
- **CONTRIBUTORS.md** file
|
||||||
|
- **Release notes** for significant contributions
|
||||||
|
- **Documentation credits** for doc contributions
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
If you have questions about contributing, please:
|
||||||
|
|
||||||
|
1. Check existing documentation
|
||||||
|
2. Search GitHub issues
|
||||||
|
3. Ask in GitHub Discussions
|
||||||
|
4. Contact the maintainers
|
||||||
|
|
||||||
|
Thank you for contributing to DebrosFramework! 🚀
|
601
README.md
601
README.md
@ -1,21 +1,18 @@
|
|||||||
# @debros/network
|
# @debros/network
|
||||||
|
|
||||||
Core networking functionality for the Debros decentralized network. This package provides a powerful database interface with advanced features built on IPFS and OrbitDB for decentralized applications.
|
**DebrosFramework** - A powerful Node.js framework that provides an ORM-like abstraction over OrbitDB and IPFS, making it easy to build scalable decentralized applications.
|
||||||
|
|
||||||
## Features
|
## What is DebrosFramework?
|
||||||
|
|
||||||
- Rich database-like API with TypeScript support
|
DebrosFramework simplifies the development of decentralized applications by providing:
|
||||||
- Multiple database store types (KeyValue, Document, Feed, Counter)
|
|
||||||
- Document operations with schema validation
|
- **Model-based Abstraction**: Define your data models using decorators and TypeScript classes
|
||||||
- Advanced querying with pagination, sorting and filtering
|
- **Automatic Database Management**: Handle user-scoped and global databases automatically
|
||||||
- Transaction support for batch operations
|
- **Smart Sharding**: Distribute data across multiple databases for scalability
|
||||||
- Built-in file storage with metadata
|
- **Advanced Query System**: Rich query capabilities with relationship loading and caching
|
||||||
- Real-time subscriptions for data changes
|
- **Automatic Features**: Built-in pinning strategies and PubSub event publishing
|
||||||
- Memory caching for performance
|
- **Migration System**: Schema evolution and data transformation capabilities
|
||||||
- Connection pooling for managing multiple database instances
|
- **Type Safety**: Full TypeScript support with strong typing throughout
|
||||||
- Index creation for faster queries
|
|
||||||
- Comprehensive error handling with error codes
|
|
||||||
- Performance metrics and monitoring
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -23,280 +20,366 @@ Core networking functionality for the Debros decentralized network. This package
|
|||||||
npm install @debros/network
|
npm install @debros/network
|
||||||
```
|
```
|
||||||
|
|
||||||
## Basic Usage
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Define Your Models
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { initDB, create, get, query, uploadFile, logger } from '@debros/network';
|
import { BaseModel, Model, Field, HasMany } from '@debros/network';
|
||||||
|
|
||||||
|
@Model({
|
||||||
|
scope: 'global',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: { strategy: 'hash', count: 4, key: 'id' },
|
||||||
|
})
|
||||||
|
export class User extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true, unique: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Field({ type: 'string', required: true, unique: true })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@HasMany(() => Post, 'userId')
|
||||||
|
posts: Post[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Model({
|
||||||
|
scope: 'user',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: { strategy: 'user', count: 2, key: 'userId' },
|
||||||
|
})
|
||||||
|
export class Post extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Initialize the Framework
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { DebrosFramework } from '@debros/network';
|
||||||
|
import { setupOrbitDB, setupIPFS } from './services';
|
||||||
|
|
||||||
// Initialize the database service
|
|
||||||
async function startApp() {
|
async function startApp() {
|
||||||
try {
|
// Initialize services
|
||||||
// Initialize with default configuration
|
const orbitDBService = await setupOrbitDB();
|
||||||
await initDB();
|
const ipfsService = await setupIPFS();
|
||||||
logger.info('Database initialized successfully');
|
|
||||||
|
|
||||||
// Create a new user document
|
// Initialize framework
|
||||||
const userId = 'user123';
|
const framework = new DebrosFramework({
|
||||||
const user = {
|
features: {
|
||||||
username: 'johndoe',
|
queryCache: true,
|
||||||
walletAddress: '0x1234567890',
|
automaticPinning: true,
|
||||||
avatar: null,
|
pubsub: true,
|
||||||
};
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const result = await create('users', userId, user);
|
await framework.initialize(orbitDBService, ipfsService);
|
||||||
logger.info(`Created user with ID: ${result.id}`);
|
console.log('✅ DebrosFramework initialized successfully!');
|
||||||
|
|
||||||
// Get a user by ID
|
// Create a user
|
||||||
const retrievedUser = await get('users', userId);
|
const user = await User.create({
|
||||||
logger.info('User:', retrievedUser);
|
username: 'alice',
|
||||||
|
email: 'alice@example.com',
|
||||||
|
});
|
||||||
|
|
||||||
// Query users with filtering
|
// Create a post
|
||||||
const activeUsers = await query('users', (user) => user.isActive === true, {
|
const post = await Post.create({
|
||||||
limit: 10,
|
title: 'My First Post',
|
||||||
sort: { field: 'createdAt', order: 'desc' },
|
content: 'Hello DebrosFramework!',
|
||||||
});
|
userId: user.id,
|
||||||
logger.info(`Found ${activeUsers.total} active users`);
|
});
|
||||||
|
|
||||||
// Upload a file
|
// Query with relationships
|
||||||
const fileData = Buffer.from('File content');
|
const usersWithPosts = await User.query().with(['posts']).where('username', 'alice').find();
|
||||||
const fileUpload = await uploadFile(fileData, { filename: 'document.txt' });
|
|
||||||
logger.info(`Uploaded file with CID: ${fileUpload.cid}`);
|
|
||||||
|
|
||||||
return true;
|
console.log('User:', usersWithPosts[0]);
|
||||||
} catch (error) {
|
console.log('Posts:', usersWithPosts[0].posts);
|
||||||
logger.error('Failed to start app:', error);
|
}
|
||||||
throw error;
|
```
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### 🏗️ Model-Driven Development
|
||||||
|
|
||||||
|
Define your data models using familiar decorator patterns:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Model({
|
||||||
|
scope: 'user',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: { strategy: 'hash', count: 4, key: 'userId' },
|
||||||
|
})
|
||||||
|
class Post extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@BelongsTo(() => User, 'userId')
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔍 Powerful Query System
|
||||||
|
|
||||||
|
Build complex queries with relationship loading:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const users = await User.query()
|
||||||
|
.where('isActive', true)
|
||||||
|
.where('registeredAt', '>', Date.now() - 30 * 24 * 60 * 60 * 1000)
|
||||||
|
.with(['posts', 'followers'])
|
||||||
|
.orderBy('username')
|
||||||
|
.limit(20)
|
||||||
|
.find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🚀 Automatic Scaling
|
||||||
|
|
||||||
|
Handle millions of users with automatic sharding and pinning:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Framework automatically:
|
||||||
|
// - Creates user-scoped databases
|
||||||
|
// - Distributes data across shards
|
||||||
|
// - Manages pinning strategies
|
||||||
|
// - Optimizes query routing
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔄 Schema Evolution
|
||||||
|
|
||||||
|
Migrate your data structures safely:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const migration = createMigration('add_user_profiles', '1.1.0')
|
||||||
|
.addField('User', 'profilePicture', { type: 'string', required: false })
|
||||||
|
.addField('User', 'bio', { type: 'string', required: false })
|
||||||
|
.transformData('User', (user) => ({
|
||||||
|
...user,
|
||||||
|
displayName: user.username || 'Anonymous',
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔗 Rich Relationships
|
||||||
|
|
||||||
|
Handle complex relationships between models:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Model({ scope: 'global' })
|
||||||
|
class User extends BaseModel {
|
||||||
|
@HasMany(() => Post, 'userId')
|
||||||
|
posts: Post[];
|
||||||
|
|
||||||
|
@ManyToMany(() => User, 'followers', 'following')
|
||||||
|
followers: User[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load user with all relationships
|
||||||
|
const user = await User.findById(userId, {
|
||||||
|
with: ['posts.comments', 'followers.posts'],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⚡ Performance Features
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Query caching
|
||||||
|
const cachedUsers = await User.query()
|
||||||
|
.where('isActive', true)
|
||||||
|
.cache(300) // Cache for 5 minutes
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// Eager loading
|
||||||
|
const usersWithPosts = await User.query().with(['posts.comments']).find();
|
||||||
|
|
||||||
|
// Optimized pagination
|
||||||
|
const page = await User.query().orderBy('createdAt', 'desc').paginate(1, 20);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🎯 Model Hooks
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class User extends BaseModel {
|
||||||
|
@BeforeCreate()
|
||||||
|
async beforeCreate() {
|
||||||
|
this.createdAt = Date.now();
|
||||||
|
// Hash password, validate data, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterCreate()
|
||||||
|
async afterCreate() {
|
||||||
|
// Send welcome email, create defaults, etc.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startApp();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Database Store Types
|
|
||||||
|
|
||||||
The library supports multiple OrbitDB store types, each optimized for different use cases:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { create, get, update, StoreType } from '@debros/network';
|
|
||||||
|
|
||||||
// Default KeyValue store (for general use)
|
|
||||||
await create('users', 'user1', { name: 'Alice' });
|
|
||||||
|
|
||||||
// Document store (better for complex documents with indexing)
|
|
||||||
await create(
|
|
||||||
'posts',
|
|
||||||
'post1',
|
|
||||||
{ title: 'Hello', content: '...' },
|
|
||||||
{ storeType: StoreType.DOCSTORE },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Feed/EventLog store (append-only, good for immutable logs)
|
|
||||||
await create('events', 'evt1', { type: 'login', user: 'alice' }, { storeType: StoreType.FEED });
|
|
||||||
|
|
||||||
// Counter store (for numeric counters)
|
|
||||||
await create('stats', 'visits', { value: 0 }, { storeType: StoreType.COUNTER });
|
|
||||||
|
|
||||||
// Increment a counter
|
|
||||||
await update('stats', 'visits', { increment: 1 }, { storeType: StoreType.COUNTER });
|
|
||||||
|
|
||||||
// Get counter value
|
|
||||||
const stats = await get('stats', 'visits', { storeType: StoreType.COUNTER });
|
|
||||||
console.log(`Visit count: ${stats.value}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Schema Validation
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { defineSchema, create } from '@debros/network';
|
|
||||||
|
|
||||||
// Define a schema
|
|
||||||
defineSchema('users', {
|
|
||||||
properties: {
|
|
||||||
username: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
min: 3,
|
|
||||||
max: 20,
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: 'string',
|
|
||||||
pattern: '^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$',
|
|
||||||
},
|
|
||||||
age: {
|
|
||||||
type: 'number',
|
|
||||||
min: 18,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ['username'],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Document creation will be validated against the schema
|
|
||||||
await create('users', 'user1', {
|
|
||||||
username: 'alice',
|
|
||||||
email: 'alice@example.com',
|
|
||||||
age: 25,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Transactions
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { createTransaction, commitTransaction } from '@debros/network';
|
|
||||||
|
|
||||||
// Create a transaction
|
|
||||||
const transaction = createTransaction();
|
|
||||||
|
|
||||||
// Add multiple operations
|
|
||||||
transaction
|
|
||||||
.create('posts', 'post1', { title: 'Hello World', content: '...' })
|
|
||||||
.update('users', 'user1', { postCount: 1 })
|
|
||||||
.delete('drafts', 'draft1');
|
|
||||||
|
|
||||||
// Commit all operations
|
|
||||||
const result = await commitTransaction(transaction);
|
|
||||||
console.log(`Transaction completed with ${result.results.length} operations`);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Event Subscriptions
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { subscribe } from '@debros/network';
|
|
||||||
|
|
||||||
// Subscribe to document changes
|
|
||||||
const unsubscribe = subscribe('document:created', (data) => {
|
|
||||||
console.log(`New document created in ${data.collection}:`, data.id);
|
|
||||||
console.log('Document data:', data.document);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Other event types
|
|
||||||
// subscribe('document:updated', (data) => { ... });
|
|
||||||
// subscribe('document:deleted', (data) => { ... });
|
|
||||||
|
|
||||||
// Later, unsubscribe when done
|
|
||||||
unsubscribe();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pagination and Sorting
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { list, query } from '@debros/network';
|
|
||||||
|
|
||||||
// List with pagination and sorting
|
|
||||||
const page1 = await list('users', {
|
|
||||||
limit: 10,
|
|
||||||
offset: 0,
|
|
||||||
sort: { field: 'createdAt', order: 'desc' },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Query with pagination
|
|
||||||
const results = await query('users', (user) => user.age > 21, { limit: 10, offset: 20 });
|
|
||||||
|
|
||||||
console.log(`Found ${results.total} matches, showing ${results.documents.length}`);
|
|
||||||
console.log(`Has more pages: ${results.hasMore}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
### TypeScript Support
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { get, update, query } from '@debros/network';
|
|
||||||
|
|
||||||
interface User {
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
age: number;
|
|
||||||
createdAt: number;
|
|
||||||
updatedAt: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type-safe operations
|
|
||||||
const user = await get<User>('users', 'user1');
|
|
||||||
|
|
||||||
await update<User>('users', 'user1', { age: 26 });
|
|
||||||
|
|
||||||
const results = await query<User>('users', (user) => user.age > 21);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Connection Management
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { initDB, closeConnection } from '@debros/network';
|
|
||||||
|
|
||||||
// Create multiple connections
|
|
||||||
const conn1 = await initDB('connection1');
|
|
||||||
const conn2 = await initDB('connection2');
|
|
||||||
|
|
||||||
// Use specific connection
|
|
||||||
await create('users', 'user1', { name: 'Alice' }, { connectionId: conn1 });
|
|
||||||
|
|
||||||
// Close a specific connection
|
|
||||||
await closeConnection(conn1);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Reference
|
## API Reference
|
||||||
|
|
||||||
### Core Database Operations
|
### Framework Management
|
||||||
|
|
||||||
- `initDB(connectionId?: string): Promise<string>` - Initialize the database
|
- `new DebrosFramework(config?)` - Create framework instance
|
||||||
- `create<T>(collection, id, data, options?): Promise<CreateResult>` - Create a document
|
- `framework.initialize(orbitDBService, ipfsService, config?)` - Initialize framework
|
||||||
- `get<T>(collection, id, options?): Promise<T | null>` - Get a document by ID
|
- `framework.start()` - Start the framework
|
||||||
- `update<T>(collection, id, data, options?): Promise<UpdateResult>` - Update a document
|
- `framework.stop()` - Stop the framework
|
||||||
- `remove(collection, id, options?): Promise<boolean>` - Delete a document
|
- `framework.getStatus()` - Get framework status
|
||||||
- `list<T>(collection, options?): Promise<PaginatedResult<T>>` - List documents with pagination
|
|
||||||
- `query<T>(collection, filter, options?): Promise<PaginatedResult<T>>` - Query documents
|
|
||||||
- `stopDB(): Promise<void>` - Stop the database service
|
|
||||||
|
|
||||||
### Store Types
|
### Model Operations
|
||||||
|
|
||||||
- `StoreType.KEYVALUE` - Key-value pair storage (default)
|
- `Model.create(data)` - Create a new model instance
|
||||||
- `StoreType.DOCSTORE` - Document storage with indexing
|
- `Model.findById(id, options?)` - Find model by ID
|
||||||
- `StoreType.FEED` - Append-only log
|
- `Model.findOne(criteria, options?)` - Find single model
|
||||||
- `StoreType.EVENTLOG` - Alias for FEED
|
- `Model.query()` - Start a query builder
|
||||||
- `StoreType.COUNTER` - Numeric counter
|
- `model.save()` - Save model changes
|
||||||
|
- `model.delete()` - Delete model instance
|
||||||
|
|
||||||
### Schema Validation
|
### Query System
|
||||||
|
|
||||||
- `defineSchema(collection, schema): void` - Define a schema for a collection
|
- `Model.query().where(field, operator, value)` - Add where condition
|
||||||
|
- `Model.query().with(relationships)` - Eager load relationships
|
||||||
|
- `Model.query().orderBy(field, direction)` - Add ordering
|
||||||
|
- `Model.query().limit(count)` - Limit results
|
||||||
|
- `Model.query().offset(count)` - Add offset
|
||||||
|
- `Model.query().paginate(page, perPage)` - Paginate results
|
||||||
|
- `Model.query().cache(ttl)` - Cache query results
|
||||||
|
- `Model.query().find()` - Execute query
|
||||||
|
- `Model.query().count()` - Count results
|
||||||
|
|
||||||
### Transactions
|
### Decorators
|
||||||
|
|
||||||
- `createTransaction(connectionId?): Transaction` - Create a new transaction
|
- `@Model(config)` - Define model configuration
|
||||||
- `commitTransaction(transaction): Promise<{success, results}>` - Execute the transaction
|
- `@Field(config)` - Define field properties
|
||||||
- `Transaction.create<T>(collection, id, data): Transaction` - Add a create operation
|
- `@BelongsTo(target, foreignKey)` - Many-to-one relationship
|
||||||
- `Transaction.update<T>(collection, id, data): Transaction` - Add an update operation
|
- `@HasMany(target, foreignKey)` - One-to-many relationship
|
||||||
- `Transaction.delete(collection, id): Transaction` - Add a delete operation
|
- `@HasOne(target, foreignKey)` - One-to-one relationship
|
||||||
|
- `@ManyToMany(target, through)` - Many-to-many relationship
|
||||||
|
- `@BeforeCreate()`, `@AfterCreate()` - Lifecycle hooks
|
||||||
|
- `@BeforeUpdate()`, `@AfterUpdate()` - Update hooks
|
||||||
|
- `@BeforeDelete()`, `@AfterDelete()` - Delete hooks
|
||||||
|
|
||||||
### Event Subscriptions
|
### Migration System
|
||||||
|
|
||||||
- `subscribe(event, callback): () => void` - Subscribe to database events, returns unsubscribe function
|
- `createMigration(name, version)` - Create new migration
|
||||||
- Event types: 'document:created', 'document:updated', 'document:deleted'
|
- `migration.addField(model, field, config)` - Add field to model
|
||||||
|
- `migration.removeField(model, field)` - Remove field from model
|
||||||
### File Operations
|
- `migration.transformData(model, transformer)` - Transform existing data
|
||||||
|
- `migrationManager.runPendingMigrations()` - Run pending migrations
|
||||||
- `uploadFile(fileData, options?): Promise<FileUploadResult>` - Upload a file
|
|
||||||
- `getFile(cid, options?): Promise<FileResult>` - Get a file by CID
|
|
||||||
- `deleteFile(cid, options?): Promise<boolean>` - Delete a file
|
|
||||||
|
|
||||||
### Connection Management
|
|
||||||
|
|
||||||
- `closeConnection(connectionId): Promise<boolean>` - Close a specific connection
|
|
||||||
|
|
||||||
### Indexes and Performance
|
|
||||||
|
|
||||||
- `createIndex(collection, field, options?): Promise<boolean>` - Create an index
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
### Framework Configuration
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { config, initDB } from '@debros/network';
|
import { DebrosFramework, PRODUCTION_CONFIG, DEVELOPMENT_CONFIG } from '@debros/network';
|
||||||
|
|
||||||
// Configure (optional)
|
// Development configuration
|
||||||
config.env.fingerprint = 'my-unique-app-id';
|
const framework = new DebrosFramework({
|
||||||
config.env.port = 9000;
|
...DEVELOPMENT_CONFIG,
|
||||||
config.ipfs.blockstorePath = './custom-path/blockstore';
|
features: {
|
||||||
config.orbitdb.directory = './custom-path/orbitdb';
|
queryCache: true,
|
||||||
|
automaticPinning: false,
|
||||||
|
pubsub: true,
|
||||||
|
relationshipCache: true,
|
||||||
|
autoMigration: true,
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
queryTimeout: 30000,
|
||||||
|
batchSize: 50,
|
||||||
|
},
|
||||||
|
monitoring: {
|
||||||
|
enableMetrics: true,
|
||||||
|
logLevel: 'debug',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize with configuration
|
// Production configuration
|
||||||
await initDB();
|
const prodFramework = new DebrosFramework({
|
||||||
|
...PRODUCTION_CONFIG,
|
||||||
|
performance: {
|
||||||
|
queryTimeout: 10000,
|
||||||
|
batchSize: 200,
|
||||||
|
maxConcurrentOperations: 500,
|
||||||
|
},
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Model Configuration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Model({
|
||||||
|
scope: 'global', // 'user' or 'global'
|
||||||
|
type: 'docstore', // OrbitDB store type
|
||||||
|
sharding: {
|
||||||
|
strategy: 'hash', // 'hash', 'range', or 'user'
|
||||||
|
count: 4, // Number of shards
|
||||||
|
key: 'id', // Sharding key
|
||||||
|
},
|
||||||
|
pinning: {
|
||||||
|
strategy: 'popularity', // Pinning strategy
|
||||||
|
factor: 2,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class MyModel extends BaseModel {
|
||||||
|
// Model definition
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
DebrosFramework is built around several core components:
|
||||||
|
|
||||||
|
1. **Models & Decorators**: Define your data structure and behavior
|
||||||
|
2. **Database Manager**: Handles database creation and management
|
||||||
|
3. **Shard Manager**: Distributes data across multiple databases
|
||||||
|
4. **Query System**: Processes queries with optimization and caching
|
||||||
|
5. **Relationship Manager**: Handles complex relationships between models
|
||||||
|
6. **Migration System**: Manages schema evolution over time
|
||||||
|
7. **Automatic Features**: Pinning, PubSub, and performance optimization
|
||||||
|
|
||||||
|
### Key Benefits
|
||||||
|
|
||||||
|
- **Scalability**: Automatic sharding and distributed data management
|
||||||
|
- **Performance**: Built-in caching, query optimization, and lazy loading
|
||||||
|
- **Developer Experience**: Familiar ORM patterns with TypeScript support
|
||||||
|
- **Flexibility**: Support for various data patterns and relationships
|
||||||
|
- **Reliability**: Comprehensive error handling and recovery mechanisms
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Ready to build your first decentralized application? Check out our comprehensive documentation:
|
||||||
|
|
||||||
|
- **[📖 Complete Documentation](./docs)** - Comprehensive guides and examples
|
||||||
|
- **[🚀 Getting Started Guide](./docs/docs/getting-started.md)** - Set up your development environment
|
||||||
|
- **[🏗️ Architecture Overview](./docs/docs/core-concepts/architecture.md)** - Understand how the framework works
|
||||||
|
- **[📝 API Reference](./docs/docs/api/overview.md)** - Complete API documentation
|
||||||
|
- **[💡 Examples](./docs/docs/examples/basic-usage.md)** - Practical usage examples
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run unit tests
|
||||||
|
npm run test:unit
|
||||||
|
|
||||||
|
# Run integration tests
|
||||||
|
npm run test:real
|
||||||
|
|
||||||
|
# Run specific blog scenario integration test
|
||||||
|
npm run test:blog-integration
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We welcome contributions! Please see our [Contributing Guide](./CONTRIBUTING.md) for details.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the GNU GPL v3.0 License - see the [LICENSE](./LICENSE) file for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**DebrosFramework** - Making decentralized application development as simple as traditional web development, while providing the benefits of distributed systems.
|
||||||
|
810
docs/docs/api/base-model.md
Normal file
810
docs/docs/api/base-model.md
Normal file
@ -0,0 +1,810 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# BaseModel Class
|
||||||
|
|
||||||
|
The `BaseModel` class is the abstract base class for all data models in Debros Network. It provides ORM-like functionality with automatic database management, validation, relationships, and lifecycle hooks.
|
||||||
|
|
||||||
|
## Class Definition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
abstract class BaseModel {
|
||||||
|
// Instance properties
|
||||||
|
id: string;
|
||||||
|
createdAt?: number;
|
||||||
|
updatedAt?: number;
|
||||||
|
|
||||||
|
// Static methods
|
||||||
|
static async create<T extends BaseModel>(
|
||||||
|
this: ModelConstructor<T>,
|
||||||
|
data: Partial<T>,
|
||||||
|
options?: CreateOptions,
|
||||||
|
): Promise<T>;
|
||||||
|
|
||||||
|
static async findById<T extends BaseModel>(
|
||||||
|
this: ModelConstructor<T>,
|
||||||
|
id: string,
|
||||||
|
options?: FindOptions,
|
||||||
|
): Promise<T | null>;
|
||||||
|
|
||||||
|
static async findOne<T extends BaseModel>(
|
||||||
|
this: ModelConstructor<T>,
|
||||||
|
criteria: Partial<T>,
|
||||||
|
options?: FindOptions,
|
||||||
|
): Promise<T | null>;
|
||||||
|
|
||||||
|
static query<T extends BaseModel>(this: ModelConstructor<T>): QueryBuilder<T>;
|
||||||
|
|
||||||
|
// Instance methods
|
||||||
|
save(options?: SaveOptions): Promise<this>;
|
||||||
|
delete(options?: DeleteOptions): Promise<boolean>;
|
||||||
|
reload(options?: ReloadOptions): Promise<this>;
|
||||||
|
validate(): Promise<ValidationResult>;
|
||||||
|
toJSON(): Record<string, any>;
|
||||||
|
clone(): this;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Static Methods
|
||||||
|
|
||||||
|
### create(data, options?)
|
||||||
|
|
||||||
|
Creates a new model instance and saves it to the database.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `data`: Partial model data
|
||||||
|
- `options` (optional): Creation options
|
||||||
|
|
||||||
|
**Returns:** `Promise<T>` - The created model instance
|
||||||
|
|
||||||
|
**Throws:**
|
||||||
|
|
||||||
|
- `ValidationError` - If validation fails
|
||||||
|
- `DatabaseError` - If database operation fails
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { BaseModel, Model, Field } from '@debros/network';
|
||||||
|
|
||||||
|
@Model({
|
||||||
|
scope: 'global',
|
||||||
|
type: 'docstore',
|
||||||
|
})
|
||||||
|
class User extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true, unique: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Field({ type: 'string', required: true, unique: true })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Field({ type: 'number', required: false, default: 0 })
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new user
|
||||||
|
const user = await User.create({
|
||||||
|
username: 'alice',
|
||||||
|
email: 'alice@example.com',
|
||||||
|
score: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(user.id); // Generated ID
|
||||||
|
console.log(user.username); // 'alice'
|
||||||
|
console.log(user.createdAt); // Timestamp
|
||||||
|
```
|
||||||
|
|
||||||
|
**With validation:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const user = await User.create({
|
||||||
|
username: 'ab', // Too short
|
||||||
|
email: 'invalid-email', // Invalid format
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ValidationError) {
|
||||||
|
console.log('Validation failed:', error.field, error.constraint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**With options:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = await User.create(
|
||||||
|
{
|
||||||
|
username: 'bob',
|
||||||
|
email: 'bob@example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
validate: true,
|
||||||
|
skipHooks: false,
|
||||||
|
userId: 'user123', // For user-scoped models
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### findById(id, options?)
|
||||||
|
|
||||||
|
Finds a model instance by its ID.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `id`: The model ID to search for
|
||||||
|
- `options` (optional): Find options
|
||||||
|
|
||||||
|
**Returns:** `Promise<T | null>` - The found model or null
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Basic find
|
||||||
|
const user = await User.findById('user123');
|
||||||
|
if (user) {
|
||||||
|
console.log('Found user:', user.username);
|
||||||
|
} else {
|
||||||
|
console.log('User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// With relationships
|
||||||
|
const user = await User.findById('user123', {
|
||||||
|
with: ['posts', 'profile'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// With specific user context
|
||||||
|
const user = await User.findById('user123', {
|
||||||
|
userId: 'current-user-id',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### findOne(criteria, options?)
|
||||||
|
|
||||||
|
Finds the first model instance matching the criteria.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `criteria`: Partial model data to match
|
||||||
|
- `options` (optional): Find options
|
||||||
|
|
||||||
|
**Returns:** `Promise<T | null>` - The found model or null
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Find by field
|
||||||
|
const user = await User.findOne({
|
||||||
|
username: 'alice',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find with multiple criteria
|
||||||
|
const user = await User.findOne({
|
||||||
|
email: 'alice@example.com',
|
||||||
|
isActive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// With options
|
||||||
|
const user = await User.findOne(
|
||||||
|
{
|
||||||
|
username: 'alice',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
with: ['posts'],
|
||||||
|
cache: 300, // Cache for 5 minutes
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### query()
|
||||||
|
|
||||||
|
Returns a query builder for complex queries.
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>` - Query builder instance
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Basic query
|
||||||
|
const users = await User.query()
|
||||||
|
.where('isActive', true)
|
||||||
|
.orderBy('createdAt', 'desc')
|
||||||
|
.limit(10)
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// Complex query with relationships
|
||||||
|
const activeUsers = await User.query()
|
||||||
|
.where('isActive', true)
|
||||||
|
.where('score', '>', 100)
|
||||||
|
.where('registeredAt', '>', Date.now() - 30 * 24 * 60 * 60 * 1000)
|
||||||
|
.with(['posts.comments', 'profile'])
|
||||||
|
.orderBy('score', 'desc')
|
||||||
|
.paginate(1, 20);
|
||||||
|
|
||||||
|
// Query with caching
|
||||||
|
const cachedUsers = await User.query()
|
||||||
|
.where('isActive', true)
|
||||||
|
.cache(600) // Cache for 10 minutes
|
||||||
|
.find();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instance Methods
|
||||||
|
|
||||||
|
### save(options?)
|
||||||
|
|
||||||
|
Saves the current model instance to the database.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `options` (optional): Save options
|
||||||
|
|
||||||
|
**Returns:** `Promise<this>` - The saved model instance
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = await User.findById('user123');
|
||||||
|
user.email = 'newemail@example.com';
|
||||||
|
user.score += 10;
|
||||||
|
|
||||||
|
await user.save();
|
||||||
|
console.log('User updated');
|
||||||
|
|
||||||
|
// With options
|
||||||
|
await user.save({
|
||||||
|
validate: true,
|
||||||
|
skipHooks: false,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### delete(options?)
|
||||||
|
|
||||||
|
Deletes the model instance from the database.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `options` (optional): Delete options
|
||||||
|
|
||||||
|
**Returns:** `Promise<boolean>` - True if deletion was successful
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = await User.findById('user123');
|
||||||
|
if (user) {
|
||||||
|
const deleted = await user.delete();
|
||||||
|
console.log('User deleted:', deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// With options
|
||||||
|
await user.delete({
|
||||||
|
skipHooks: false,
|
||||||
|
cascade: true, // Delete related records
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### reload(options?)
|
||||||
|
|
||||||
|
Reloads the model instance from the database.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `options` (optional): Reload options
|
||||||
|
|
||||||
|
**Returns:** `Promise<this>` - The reloaded model instance
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = await User.findById('user123');
|
||||||
|
|
||||||
|
// Model might be updated elsewhere
|
||||||
|
await user.reload();
|
||||||
|
|
||||||
|
// With relationships
|
||||||
|
await user.reload({
|
||||||
|
with: ['posts', 'profile'],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### validate()
|
||||||
|
|
||||||
|
Validates the current model instance.
|
||||||
|
|
||||||
|
**Returns:** `Promise<ValidationResult>` - Validation result
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = new User();
|
||||||
|
user.username = 'alice';
|
||||||
|
user.email = 'invalid-email';
|
||||||
|
|
||||||
|
const result = await user.validate();
|
||||||
|
if (!result.valid) {
|
||||||
|
console.log('Validation errors:', result.errors);
|
||||||
|
// [{ field: 'email', constraint: 'must be valid email', value: 'invalid-email' }]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### toJSON()
|
||||||
|
|
||||||
|
Converts the model instance to a plain JavaScript object.
|
||||||
|
|
||||||
|
**Returns:** `Record<string, any>` - Plain object representation
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = await User.findById('user123');
|
||||||
|
const userObj = user.toJSON();
|
||||||
|
|
||||||
|
console.log(userObj);
|
||||||
|
// {
|
||||||
|
// id: 'user123',
|
||||||
|
// username: 'alice',
|
||||||
|
// email: 'alice@example.com',
|
||||||
|
// score: 100,
|
||||||
|
// createdAt: 1234567890,
|
||||||
|
// updatedAt: 1234567890
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Useful for JSON serialization
|
||||||
|
const jsonString = JSON.stringify(user); // Calls toJSON() automatically
|
||||||
|
```
|
||||||
|
|
||||||
|
### clone()
|
||||||
|
|
||||||
|
Creates a deep copy of the model instance (without ID).
|
||||||
|
|
||||||
|
**Returns:** `this` - Cloned model instance
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = await User.findById('user123');
|
||||||
|
const userCopy = user.clone();
|
||||||
|
|
||||||
|
userCopy.username = 'alice_copy';
|
||||||
|
await userCopy.save(); // Creates new record
|
||||||
|
|
||||||
|
console.log(user.id !== userCopy.id); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lifecycle Hooks
|
||||||
|
|
||||||
|
### Hook Decorators
|
||||||
|
|
||||||
|
Models can define lifecycle hooks using decorators:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Model({ scope: 'global', type: 'docstore' })
|
||||||
|
class User extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Field({ type: 'number', required: false })
|
||||||
|
loginCount: number = 0;
|
||||||
|
|
||||||
|
@BeforeCreate()
|
||||||
|
async beforeCreateHook() {
|
||||||
|
this.createdAt = Date.now();
|
||||||
|
this.updatedAt = Date.now();
|
||||||
|
|
||||||
|
// Validate unique username
|
||||||
|
const existing = await User.findOne({ username: this.username });
|
||||||
|
if (existing) {
|
||||||
|
throw new ValidationError('username', this.username, 'must be unique');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterCreate()
|
||||||
|
async afterCreateHook() {
|
||||||
|
console.log(`New user created: ${this.username}`);
|
||||||
|
|
||||||
|
// Send welcome email
|
||||||
|
await this.sendWelcomeEmail();
|
||||||
|
|
||||||
|
// Create default settings
|
||||||
|
await this.createDefaultSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeUpdate()
|
||||||
|
async beforeUpdateHook() {
|
||||||
|
this.updatedAt = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterUpdate()
|
||||||
|
async afterUpdateHook() {
|
||||||
|
console.log(`User updated: ${this.username}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeDelete()
|
||||||
|
async beforeDeleteHook() {
|
||||||
|
// Clean up related data
|
||||||
|
await this.deleteRelatedPosts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterDelete()
|
||||||
|
async afterDeleteHook() {
|
||||||
|
console.log(`User deleted: ${this.username}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom methods
|
||||||
|
private async sendWelcomeEmail() {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createDefaultSettings() {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
private async deleteRelatedPosts() {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Hook Types
|
||||||
|
|
||||||
|
| Hook | Trigger | Usage |
|
||||||
|
| ----------------- | ------------------------------- | --------------------------------------- |
|
||||||
|
| `@BeforeCreate()` | Before creating new record | Validation, default values, preparation |
|
||||||
|
| `@AfterCreate()` | After creating new record | Notifications, related record creation |
|
||||||
|
| `@BeforeUpdate()` | Before updating existing record | Validation, timestamp updates, logging |
|
||||||
|
| `@AfterUpdate()` | After updating existing record | Cache invalidation, notifications |
|
||||||
|
| `@BeforeDelete()` | Before deleting record | Cleanup, validation, logging |
|
||||||
|
| `@AfterDelete()` | After deleting record | Cleanup, notifications, cascade deletes |
|
||||||
|
| `@BeforeSave()` | Before save (create or update) | Common validation, timestamps |
|
||||||
|
| `@AfterSave()` | After save (create or update) | Common post-processing |
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
### Field Validation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Model({ scope: 'global', type: 'docstore' })
|
||||||
|
class User extends BaseModel {
|
||||||
|
@Field({
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
minLength: 3,
|
||||||
|
maxLength: 20,
|
||||||
|
validate: (username: string) => /^[a-zA-Z0-9_]+$/.test(username),
|
||||||
|
transform: (username: string) => username.toLowerCase(),
|
||||||
|
})
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
validate: (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
|
||||||
|
transform: (email: string) => email.toLowerCase(),
|
||||||
|
})
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
type: 'number',
|
||||||
|
required: false,
|
||||||
|
default: 0,
|
||||||
|
validate: (score: number) => score >= 0 && score <= 1000,
|
||||||
|
})
|
||||||
|
score: number;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
type: 'array',
|
||||||
|
required: false,
|
||||||
|
default: [],
|
||||||
|
validate: (tags: string[]) => tags.length <= 10,
|
||||||
|
})
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Validation Methods
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Model({ scope: 'global', type: 'docstore' })
|
||||||
|
class User extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
// Custom validation method
|
||||||
|
async customValidation(): Promise<ValidationResult> {
|
||||||
|
const errors: ValidationError[] = [];
|
||||||
|
|
||||||
|
// Check username availability
|
||||||
|
if (this.username) {
|
||||||
|
const existing = await User.findOne({ username: this.username });
|
||||||
|
if (existing && existing.id !== this.id) {
|
||||||
|
errors.push(new ValidationError('username', this.username, 'already taken'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check email format and availability
|
||||||
|
if (this.email) {
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(this.email)) {
|
||||||
|
errors.push(new ValidationError('email', this.email, 'invalid format'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await User.findOne({ email: this.email });
|
||||||
|
if (existing && existing.id !== this.id) {
|
||||||
|
errors.push(new ValidationError('email', this.email, 'already registered'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check password strength
|
||||||
|
if (this.password && this.password.length < 8) {
|
||||||
|
errors.push(new ValidationError('password', this.password, 'must be at least 8 characters'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: errors.length === 0,
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeCreate()
|
||||||
|
@BeforeUpdate()
|
||||||
|
async validateBeforeSave() {
|
||||||
|
const result = await this.customValidation();
|
||||||
|
if (!result.valid) {
|
||||||
|
throw new ValidationError('model', this, 'custom validation failed', result.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Interfaces
|
||||||
|
|
||||||
|
### ModelConstructor Type
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type ModelConstructor<T extends BaseModel> = new () => T;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options Interfaces
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface CreateOptions {
|
||||||
|
validate?: boolean;
|
||||||
|
skipHooks?: boolean;
|
||||||
|
userId?: string; // For user-scoped models
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FindOptions {
|
||||||
|
with?: string[]; // Relationship names to eager load
|
||||||
|
cache?: boolean | number; // Cache result
|
||||||
|
userId?: string; // For user-scoped models
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SaveOptions {
|
||||||
|
validate?: boolean;
|
||||||
|
skipHooks?: boolean;
|
||||||
|
upsert?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteOptions {
|
||||||
|
skipHooks?: boolean;
|
||||||
|
cascade?: boolean; // Delete related records
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReloadOptions {
|
||||||
|
with?: string[]; // Relationship names to eager load
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validation Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ValidationResult {
|
||||||
|
valid: boolean;
|
||||||
|
errors: ValidationError[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValidationError extends Error {
|
||||||
|
constructor(
|
||||||
|
public field: string,
|
||||||
|
public value: any,
|
||||||
|
public constraint: string,
|
||||||
|
public details?: any,
|
||||||
|
) {
|
||||||
|
super(`Validation failed for field '${field}': ${constraint}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Common Errors
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Validation errors
|
||||||
|
try {
|
||||||
|
const user = await User.create({
|
||||||
|
username: 'a', // Too short
|
||||||
|
email: 'invalid',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ValidationError) {
|
||||||
|
console.log(`Field ${error.field} failed: ${error.constraint}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found errors
|
||||||
|
const user = await User.findById('non-existent-id');
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundError('User', 'non-existent-id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database errors
|
||||||
|
try {
|
||||||
|
await user.save();
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DatabaseError) {
|
||||||
|
console.log('Database operation failed:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
### Blog Post Model
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
BaseModel,
|
||||||
|
Model,
|
||||||
|
Field,
|
||||||
|
BelongsTo,
|
||||||
|
HasMany,
|
||||||
|
BeforeCreate,
|
||||||
|
AfterCreate,
|
||||||
|
} from '@debros/network';
|
||||||
|
import { User } from './User';
|
||||||
|
import { Comment } from './Comment';
|
||||||
|
|
||||||
|
@Model({
|
||||||
|
scope: 'user',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: {
|
||||||
|
strategy: 'user',
|
||||||
|
count: 2,
|
||||||
|
key: 'authorId',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class Post extends BaseModel {
|
||||||
|
@Field({
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
minLength: 1,
|
||||||
|
maxLength: 200,
|
||||||
|
})
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
minLength: 1,
|
||||||
|
maxLength: 10000,
|
||||||
|
})
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
authorId: string;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
type: 'array',
|
||||||
|
required: false,
|
||||||
|
default: [],
|
||||||
|
transform: (tags: string[]) => tags.map((tag) => tag.toLowerCase()),
|
||||||
|
})
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@Field({ type: 'boolean', required: false, default: false })
|
||||||
|
isPublished: boolean;
|
||||||
|
|
||||||
|
@Field({ type: 'number', required: false, default: 0 })
|
||||||
|
viewCount: number;
|
||||||
|
|
||||||
|
@Field({ type: 'number', required: false })
|
||||||
|
publishedAt?: number;
|
||||||
|
|
||||||
|
// Relationships
|
||||||
|
@BelongsTo(() => User, 'authorId')
|
||||||
|
author: User;
|
||||||
|
|
||||||
|
@HasMany(() => Comment, 'postId')
|
||||||
|
comments: Comment[];
|
||||||
|
|
||||||
|
@BeforeCreate()
|
||||||
|
setupNewPost() {
|
||||||
|
this.createdAt = Date.now();
|
||||||
|
this.updatedAt = Date.now();
|
||||||
|
this.viewCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterCreate()
|
||||||
|
async afterPostCreated() {
|
||||||
|
console.log(`New post created: ${this.title}`);
|
||||||
|
|
||||||
|
// Update author's post count
|
||||||
|
const author = await User.findById(this.authorId);
|
||||||
|
if (author) {
|
||||||
|
author.postCount = (author.postCount || 0) + 1;
|
||||||
|
await author.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom methods
|
||||||
|
async publish(): Promise<void> {
|
||||||
|
this.isPublished = true;
|
||||||
|
this.publishedAt = Date.now();
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
async incrementViews(): Promise<void> {
|
||||||
|
this.viewCount += 1;
|
||||||
|
await this.save({ skipHooks: true }); // Skip hooks for performance
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCommentCount(): Promise<number> {
|
||||||
|
return await Comment.query().where('postId', this.id).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTopComments(limit: number = 5): Promise<Comment[]> {
|
||||||
|
return await Comment.query()
|
||||||
|
.where('postId', this.id)
|
||||||
|
.orderBy('likeCount', 'desc')
|
||||||
|
.limit(limit)
|
||||||
|
.with(['author'])
|
||||||
|
.find();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage examples
|
||||||
|
async function blogExamples() {
|
||||||
|
// Create a post
|
||||||
|
const post = await Post.create({
|
||||||
|
title: 'My First Post',
|
||||||
|
content: 'This is the content of my first post...',
|
||||||
|
authorId: 'user123',
|
||||||
|
tags: ['JavaScript', 'Web Development'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find posts by author
|
||||||
|
const userPosts = await Post.query()
|
||||||
|
.where('authorId', 'user123')
|
||||||
|
.where('isPublished', true)
|
||||||
|
.orderBy('publishedAt', 'desc')
|
||||||
|
.with(['author', 'comments.author'])
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// Publish a post
|
||||||
|
await post.publish();
|
||||||
|
|
||||||
|
// Get post with comments
|
||||||
|
const postWithComments = await Post.findById(post.id, {
|
||||||
|
with: ['author', 'comments.author'],
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Post:', postWithComments?.title);
|
||||||
|
console.log('Author:', postWithComments?.author.username);
|
||||||
|
console.log('Comments:', postWithComments?.comments.length);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This comprehensive BaseModel documentation covers all the essential functionality for working with models in Debros Network, including CRUD operations, validation, relationships, hooks, and real-world usage examples.
|
828
docs/docs/api/query-builder.md
Normal file
828
docs/docs/api/query-builder.md
Normal file
@ -0,0 +1,828 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 4
|
||||||
|
---
|
||||||
|
|
||||||
|
# QueryBuilder Class
|
||||||
|
|
||||||
|
The `QueryBuilder` class provides a fluent API for constructing complex database queries. It supports filtering, sorting, relationships, pagination, and caching with type safety throughout.
|
||||||
|
|
||||||
|
## Class Definition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class QueryBuilder<T extends BaseModel> {
|
||||||
|
// Filtering methods
|
||||||
|
where(field: string, value: any): QueryBuilder<T>;
|
||||||
|
where(field: string, operator: QueryOperator, value: any): QueryBuilder<T>;
|
||||||
|
whereIn(field: string, values: any[]): QueryBuilder<T>;
|
||||||
|
whereNotIn(field: string, values: any[]): QueryBuilder<T>;
|
||||||
|
whereNull(field: string): QueryBuilder<T>;
|
||||||
|
whereNotNull(field: string): QueryBuilder<T>;
|
||||||
|
whereBetween(field: string, min: any, max: any): QueryBuilder<T>;
|
||||||
|
whereRaw(condition: string, parameters?: any[]): QueryBuilder<T>;
|
||||||
|
|
||||||
|
// Logical operators
|
||||||
|
and(): QueryBuilder<T>;
|
||||||
|
or(): QueryBuilder<T>;
|
||||||
|
not(): QueryBuilder<T>;
|
||||||
|
|
||||||
|
// Sorting
|
||||||
|
orderBy(field: string, direction?: 'asc' | 'desc'): QueryBuilder<T>;
|
||||||
|
orderByRaw(orderClause: string): QueryBuilder<T>;
|
||||||
|
|
||||||
|
// Limiting and pagination
|
||||||
|
limit(count: number): QueryBuilder<T>;
|
||||||
|
offset(count: number): QueryBuilder<T>;
|
||||||
|
paginate(page: number, perPage: number): Promise<PaginatedResult<T>>;
|
||||||
|
|
||||||
|
// Relationships
|
||||||
|
with(relationships: string[]): QueryBuilder<T>;
|
||||||
|
withCount(relationships: string[]): QueryBuilder<T>;
|
||||||
|
whereHas(relationship: string, callback?: (query: QueryBuilder<any>) => void): QueryBuilder<T>;
|
||||||
|
whereDoesntHave(relationship: string): QueryBuilder<T>;
|
||||||
|
|
||||||
|
// Aggregation
|
||||||
|
count(): Promise<number>;
|
||||||
|
sum(field: string): Promise<number>;
|
||||||
|
avg(field: string): Promise<number>;
|
||||||
|
min(field: string): Promise<any>;
|
||||||
|
max(field: string): Promise<any>;
|
||||||
|
|
||||||
|
// Caching
|
||||||
|
cache(ttl?: number): QueryBuilder<T>;
|
||||||
|
fresh(): QueryBuilder<T>;
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
find(): Promise<T[]>;
|
||||||
|
findOne(): Promise<T | null>;
|
||||||
|
first(): Promise<T | null>;
|
||||||
|
get(): Promise<T[]>;
|
||||||
|
|
||||||
|
// Advanced features
|
||||||
|
distinct(field?: string): QueryBuilder<T>;
|
||||||
|
groupBy(field: string): QueryBuilder<T>;
|
||||||
|
having(field: string, operator: QueryOperator, value: any): QueryBuilder<T>;
|
||||||
|
|
||||||
|
// Query info
|
||||||
|
toSQL(): string;
|
||||||
|
getParameters(): any[];
|
||||||
|
explain(): Promise<QueryPlan>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Filtering Methods
|
||||||
|
|
||||||
|
### where(field, value) / where(field, operator, value)
|
||||||
|
|
||||||
|
Adds a WHERE condition to the query.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `field`: Field name to filter on
|
||||||
|
- `value`: Value to compare (when using equals operator)
|
||||||
|
- `operator`: Comparison operator
|
||||||
|
- `value`: Value to compare against
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>` - Builder instance for chaining
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Basic equality
|
||||||
|
const activeUsers = await User.query().where('isActive', true).find();
|
||||||
|
|
||||||
|
// With operators
|
||||||
|
const highScoreUsers = await User.query()
|
||||||
|
.where('score', '>', 1000)
|
||||||
|
.where('registeredAt', '>=', Date.now() - 30 * 24 * 60 * 60 * 1000)
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// String operations
|
||||||
|
const usersWithAlice = await User.query().where('username', 'like', '%alice%').find();
|
||||||
|
|
||||||
|
// Array operations
|
||||||
|
const adminUsers = await User.query().where('roles', 'includes', 'admin').find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### whereIn(field, values)
|
||||||
|
|
||||||
|
Filters records where field value is in the provided array.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `field`: Field name
|
||||||
|
- `values`: Array of values to match
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Find users with specific IDs
|
||||||
|
const users = await User.query().whereIn('id', ['user1', 'user2', 'user3']).find();
|
||||||
|
|
||||||
|
// Find posts with specific tags
|
||||||
|
const posts = await Post.query().whereIn('category', ['tech', 'programming', 'tutorial']).find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### whereNotIn(field, values)
|
||||||
|
|
||||||
|
Filters records where field value is NOT in the provided array.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `field`: Field name
|
||||||
|
- `values`: Array of values to exclude
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Exclude specific users
|
||||||
|
const users = await User.query().whereNotIn('status', ['banned', 'suspended']).find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### whereNull(field) / whereNotNull(field)
|
||||||
|
|
||||||
|
Filters records based on null values.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `field`: Field name to check
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Find users without profile pictures
|
||||||
|
const usersWithoutAvatars = await User.query().whereNull('avatarUrl').find();
|
||||||
|
|
||||||
|
// Find users with profile pictures
|
||||||
|
const usersWithAvatars = await User.query().whereNotNull('avatarUrl').find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### whereBetween(field, min, max)
|
||||||
|
|
||||||
|
Filters records where field value is between min and max.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `field`: Field name
|
||||||
|
- `min`: Minimum value (inclusive)
|
||||||
|
- `max`: Maximum value (inclusive)
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Find users with scores between 100 and 500
|
||||||
|
const moderateUsers = await User.query().whereBetween('score', 100, 500).find();
|
||||||
|
|
||||||
|
// Find posts from last week
|
||||||
|
const lastWeek = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
||||||
|
const recentPosts = await Post.query().whereBetween('createdAt', lastWeek, Date.now()).find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### whereRaw(condition, parameters?)
|
||||||
|
|
||||||
|
Adds a raw WHERE condition.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `condition`: Raw SQL-like condition string
|
||||||
|
- `parameters`: Optional parameters for the condition
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Complex condition
|
||||||
|
const users = await User.query()
|
||||||
|
.whereRaw('score > ? AND (registeredAt > ? OR isPremium = ?)', [100, lastWeek, true])
|
||||||
|
.find();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logical Operators
|
||||||
|
|
||||||
|
### and() / or() / not()
|
||||||
|
|
||||||
|
Combines conditions with logical operators.
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// AND (default behavior)
|
||||||
|
const premiumActiveUsers = await User.query()
|
||||||
|
.where('isActive', true)
|
||||||
|
.and()
|
||||||
|
.where('isPremium', true)
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// OR condition
|
||||||
|
const eligibleUsers = await User.query()
|
||||||
|
.where('isPremium', true)
|
||||||
|
.or()
|
||||||
|
.where('score', '>', 1000)
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// NOT condition
|
||||||
|
const nonAdminUsers = await User.query().not().where('role', 'admin').find();
|
||||||
|
|
||||||
|
// Complex combinations
|
||||||
|
const complexQuery = await User.query()
|
||||||
|
.where('isActive', true)
|
||||||
|
.and()
|
||||||
|
.group((query) => query.where('isPremium', true).or().where('score', '>', 500))
|
||||||
|
.find();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sorting Methods
|
||||||
|
|
||||||
|
### orderBy(field, direction?)
|
||||||
|
|
||||||
|
Orders results by specified field.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `field`: Field name to sort by
|
||||||
|
- `direction`: Sort direction ('asc' or 'desc', defaults to 'asc')
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Sort by score descending
|
||||||
|
const topUsers = await User.query().orderBy('score', 'desc').limit(10).find();
|
||||||
|
|
||||||
|
// Multiple sort criteria
|
||||||
|
const users = await User.query().orderBy('score', 'desc').orderBy('username', 'asc').find();
|
||||||
|
|
||||||
|
// Sort by date
|
||||||
|
const recentPosts = await Post.query().orderBy('createdAt', 'desc').find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### orderByRaw(orderClause)
|
||||||
|
|
||||||
|
Orders results using a raw ORDER BY clause.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `orderClause`: Raw order clause
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const users = await User.query().orderByRaw('score DESC, RANDOM()').find();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limiting and Pagination
|
||||||
|
|
||||||
|
### limit(count)
|
||||||
|
|
||||||
|
Limits the number of results.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `count`: Maximum number of records to return
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Get top 10 users
|
||||||
|
const topUsers = await User.query().orderBy('score', 'desc').limit(10).find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### offset(count)
|
||||||
|
|
||||||
|
Skips a number of records.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `count`: Number of records to skip
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Get users 11-20 (skip first 10)
|
||||||
|
const nextBatch = await User.query().orderBy('score', 'desc').offset(10).limit(10).find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### paginate(page, perPage)
|
||||||
|
|
||||||
|
Paginates results and returns pagination info.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `page`: Page number (1-based)
|
||||||
|
- `perPage`: Number of records per page
|
||||||
|
|
||||||
|
**Returns:** `Promise<PaginatedResult<T>>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Get page 2 with 20 users per page
|
||||||
|
const result = await User.query().where('isActive', true).orderBy('score', 'desc').paginate(2, 20);
|
||||||
|
|
||||||
|
console.log('Users:', result.data);
|
||||||
|
console.log('Total:', result.total);
|
||||||
|
console.log('Page:', result.page);
|
||||||
|
console.log('Per page:', result.perPage);
|
||||||
|
console.log('Total pages:', result.totalPages);
|
||||||
|
console.log('Has next:', result.hasNext);
|
||||||
|
console.log('Has previous:', result.hasPrev);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relationship Methods
|
||||||
|
|
||||||
|
### with(relationships)
|
||||||
|
|
||||||
|
Eager loads relationships.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `relationships`: Array of relationship names or dot-notation for nested relationships
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Load single relationship
|
||||||
|
const usersWithPosts = await User.query().with(['posts']).find();
|
||||||
|
|
||||||
|
// Load multiple relationships
|
||||||
|
const usersWithData = await User.query().with(['posts', 'profile', 'followers']).find();
|
||||||
|
|
||||||
|
// Load nested relationships
|
||||||
|
const usersWithCommentsAuthors = await User.query().with(['posts.comments.author']).find();
|
||||||
|
|
||||||
|
// Mixed relationships
|
||||||
|
const complexLoad = await User.query()
|
||||||
|
.with(['posts.comments.author', 'profile', 'followers.profile'])
|
||||||
|
.find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### withCount(relationships)
|
||||||
|
|
||||||
|
Loads relationship counts without loading the actual relationships.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `relationships`: Array of relationship names
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const usersWithCounts = await User.query().withCount(['posts', 'followers', 'following']).find();
|
||||||
|
|
||||||
|
// Access counts
|
||||||
|
usersWithCounts.forEach((user) => {
|
||||||
|
console.log(`${user.username}: ${user.postsCount} posts, ${user.followersCount} followers`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### whereHas(relationship, callback?)
|
||||||
|
|
||||||
|
Filters records that have related records matching criteria.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `relationship`: Relationship name
|
||||||
|
- `callback`: Optional callback to add conditions to the relationship query
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Users who have posts
|
||||||
|
const usersWithPosts = await User.query().whereHas('posts').find();
|
||||||
|
|
||||||
|
// Users who have published posts
|
||||||
|
const usersWithPublishedPosts = await User.query()
|
||||||
|
.whereHas('posts', (query) => {
|
||||||
|
query.where('isPublished', true);
|
||||||
|
})
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// Users who have recent posts
|
||||||
|
const usersWithRecentPosts = await User.query()
|
||||||
|
.whereHas('posts', (query) => {
|
||||||
|
query.where('createdAt', '>', Date.now() - 7 * 24 * 60 * 60 * 1000);
|
||||||
|
})
|
||||||
|
.find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### whereDoesntHave(relationship)
|
||||||
|
|
||||||
|
Filters records that don't have related records.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `relationship`: Relationship name
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Users without posts
|
||||||
|
const usersWithoutPosts = await User.query().whereDoesntHave('posts').find();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Aggregation Methods
|
||||||
|
|
||||||
|
### count()
|
||||||
|
|
||||||
|
Returns the count of matching records.
|
||||||
|
|
||||||
|
**Returns:** `Promise<number>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Count all active users
|
||||||
|
const activeUserCount = await User.query().where('isActive', true).count();
|
||||||
|
|
||||||
|
console.log(`Active users: ${activeUserCount}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
### sum(field) / avg(field) / min(field) / max(field)
|
||||||
|
|
||||||
|
Performs aggregation operations on a field.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `field`: Field name to aggregate
|
||||||
|
|
||||||
|
**Returns:** `Promise<number>` or `Promise<any>` for min/max
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Calculate statistics
|
||||||
|
const totalScore = await User.query().sum('score');
|
||||||
|
const averageScore = await User.query().avg('score');
|
||||||
|
const highestScore = await User.query().max('score');
|
||||||
|
const lowestScore = await User.query().min('score');
|
||||||
|
|
||||||
|
console.log(`Total: ${totalScore}, Average: ${averageScore}`);
|
||||||
|
console.log(`Range: ${lowestScore} - ${highestScore}`);
|
||||||
|
|
||||||
|
// With conditions
|
||||||
|
const premiumAverage = await User.query().where('isPremium', true).avg('score');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caching Methods
|
||||||
|
|
||||||
|
### cache(ttl?)
|
||||||
|
|
||||||
|
Caches query results.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `ttl`: Time to live in seconds (optional, uses default if not provided)
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Cache for default duration
|
||||||
|
const cachedUsers = await User.query().where('isActive', true).cache().find();
|
||||||
|
|
||||||
|
// Cache for 10 minutes
|
||||||
|
const cachedPosts = await Post.query().where('isPublished', true).cache(600).find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### fresh()
|
||||||
|
|
||||||
|
Forces a fresh query, bypassing cache.
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Always fetch fresh data
|
||||||
|
const freshUsers = await User.query().where('isActive', true).fresh().find();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution Methods
|
||||||
|
|
||||||
|
### find()
|
||||||
|
|
||||||
|
Executes the query and returns all matching records.
|
||||||
|
|
||||||
|
**Returns:** `Promise<T[]>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const users = await User.query().where('isActive', true).orderBy('score', 'desc').find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### findOne() / first()
|
||||||
|
|
||||||
|
Executes the query and returns the first matching record.
|
||||||
|
|
||||||
|
**Returns:** `Promise<T | null>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Find the highest scoring user
|
||||||
|
const topUser = await User.query().orderBy('score', 'desc').findOne();
|
||||||
|
|
||||||
|
// Find specific user
|
||||||
|
const user = await User.query().where('username', 'alice').first();
|
||||||
|
```
|
||||||
|
|
||||||
|
### get()
|
||||||
|
|
||||||
|
Alias for `find()`.
|
||||||
|
|
||||||
|
**Returns:** `Promise<T[]>`
|
||||||
|
|
||||||
|
## Advanced Methods
|
||||||
|
|
||||||
|
### distinct(field?)
|
||||||
|
|
||||||
|
Returns distinct values.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `field`: Optional field to get distinct values for
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Get users with distinct emails
|
||||||
|
const uniqueUsers = await User.query().distinct('email').find();
|
||||||
|
|
||||||
|
// Get all distinct tags from posts
|
||||||
|
const uniqueTags = await Post.query().distinct('tags').find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### groupBy(field)
|
||||||
|
|
||||||
|
Groups results by field.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `field`: Field to group by
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Group users by registration date
|
||||||
|
const usersByDate = await User.query().groupBy('registeredAt').withCount(['posts']).find();
|
||||||
|
```
|
||||||
|
|
||||||
|
### having(field, operator, value)
|
||||||
|
|
||||||
|
Adds HAVING condition (used with groupBy).
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
- `field`: Field name
|
||||||
|
- `operator`: Comparison operator
|
||||||
|
- `value`: Value to compare
|
||||||
|
|
||||||
|
**Returns:** `QueryBuilder<T>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Find dates with more than 10 user registrations
|
||||||
|
const busyDays = await User.query().groupBy('registeredAt').having('COUNT(*)', '>', 10).find();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query Inspection
|
||||||
|
|
||||||
|
### toSQL()
|
||||||
|
|
||||||
|
Returns the SQL representation of the query.
|
||||||
|
|
||||||
|
**Returns:** `string`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const query = User.query().where('isActive', true).orderBy('score', 'desc').limit(10);
|
||||||
|
|
||||||
|
console.log('SQL:', query.toSQL());
|
||||||
|
```
|
||||||
|
|
||||||
|
### getParameters()
|
||||||
|
|
||||||
|
Returns the parameters for the query.
|
||||||
|
|
||||||
|
**Returns:** `any[]`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const query = User.query().where('score', '>', 100);
|
||||||
|
|
||||||
|
console.log('Parameters:', query.getParameters());
|
||||||
|
```
|
||||||
|
|
||||||
|
### explain()
|
||||||
|
|
||||||
|
Returns the query execution plan.
|
||||||
|
|
||||||
|
**Returns:** `Promise<QueryPlan>`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const plan = await User.query().where('isActive', true).explain();
|
||||||
|
|
||||||
|
console.log('Query plan:', plan);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Type Definitions
|
||||||
|
|
||||||
|
### QueryOperator
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type QueryOperator =
|
||||||
|
| 'eq'
|
||||||
|
| '='
|
||||||
|
| 'ne'
|
||||||
|
| '!='
|
||||||
|
| '<>'
|
||||||
|
| 'gt'
|
||||||
|
| '>'
|
||||||
|
| 'gte'
|
||||||
|
| '>='
|
||||||
|
| 'lt'
|
||||||
|
| '<'
|
||||||
|
| 'lte'
|
||||||
|
| '<='
|
||||||
|
| 'in'
|
||||||
|
| 'not in'
|
||||||
|
| 'like'
|
||||||
|
| 'not like'
|
||||||
|
| 'regex'
|
||||||
|
| 'is null'
|
||||||
|
| 'is not null'
|
||||||
|
| 'includes'
|
||||||
|
| 'includes any'
|
||||||
|
| 'includes all';
|
||||||
|
```
|
||||||
|
|
||||||
|
### PaginatedResult
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PaginatedResult<T> {
|
||||||
|
data: T[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
perPage: number;
|
||||||
|
totalPages: number;
|
||||||
|
hasNext: boolean;
|
||||||
|
hasPrev: boolean;
|
||||||
|
firstPage: number;
|
||||||
|
lastPage: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### QueryPlan
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface QueryPlan {
|
||||||
|
estimatedCost: number;
|
||||||
|
estimatedRows: number;
|
||||||
|
indexesUsed: string[];
|
||||||
|
operations: QueryOperation[];
|
||||||
|
warnings: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryOperation {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
cost: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complex Query Examples
|
||||||
|
|
||||||
|
### Advanced Filtering
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Complex user search
|
||||||
|
const searchResults = await User.query()
|
||||||
|
.where('isActive', true)
|
||||||
|
.and()
|
||||||
|
.group((query) =>
|
||||||
|
query
|
||||||
|
.where('username', 'like', `%${searchTerm}%`)
|
||||||
|
.or()
|
||||||
|
.where('email', 'like', `%${searchTerm}%`)
|
||||||
|
.or()
|
||||||
|
.where('bio', 'like', `%${searchTerm}%`),
|
||||||
|
)
|
||||||
|
.and()
|
||||||
|
.group((query) => query.where('isPremium', true).or().where('score', '>', 500))
|
||||||
|
.orderBy('score', 'desc')
|
||||||
|
.with(['profile', 'posts.comments'])
|
||||||
|
.paginate(page, 20);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Analytics Query
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// User activity analytics
|
||||||
|
const analytics = await User.query()
|
||||||
|
.where('registeredAt', '>', startDate)
|
||||||
|
.where('registeredAt', '<', endDate)
|
||||||
|
.whereHas('posts', (query) => {
|
||||||
|
query.where('isPublished', true);
|
||||||
|
})
|
||||||
|
.withCount(['posts', 'comments', 'followers'])
|
||||||
|
.orderBy('score', 'desc')
|
||||||
|
.cache(300) // Cache for 5 minutes
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// Process analytics data
|
||||||
|
const stats = {
|
||||||
|
totalUsers: analytics.length,
|
||||||
|
averageScore: analytics.reduce((sum, user) => sum + user.score, 0) / analytics.length,
|
||||||
|
totalPosts: analytics.reduce((sum, user) => sum + user.postsCount, 0),
|
||||||
|
totalComments: analytics.reduce((sum, user) => sum + user.commentsCount, 0),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dashboard Query
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
async function getDashboardData(userId: string) {
|
||||||
|
// Multiple optimized queries
|
||||||
|
const [userStats, recentPosts, topComments, followingActivity] = await Promise.all([
|
||||||
|
// User statistics
|
||||||
|
User.query()
|
||||||
|
.where('id', userId)
|
||||||
|
.withCount(['posts', 'comments', 'followers', 'following'])
|
||||||
|
.cache(60) // Cache for 1 minute
|
||||||
|
.first(),
|
||||||
|
|
||||||
|
// Recent posts
|
||||||
|
Post.query()
|
||||||
|
.where('authorId', userId)
|
||||||
|
.orderBy('createdAt', 'desc')
|
||||||
|
.limit(5)
|
||||||
|
.with(['comments.author'])
|
||||||
|
.find(),
|
||||||
|
|
||||||
|
// Top comments by user
|
||||||
|
Comment.query()
|
||||||
|
.where('authorId', userId)
|
||||||
|
.orderBy('likeCount', 'desc')
|
||||||
|
.limit(10)
|
||||||
|
.with(['post.author'])
|
||||||
|
.find(),
|
||||||
|
|
||||||
|
// Activity from people user follows
|
||||||
|
Post.query()
|
||||||
|
.whereHas('author', (query) => {
|
||||||
|
query.whereHas('followers', (followQuery) => {
|
||||||
|
followQuery.where('followerId', userId);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.orderBy('createdAt', 'desc')
|
||||||
|
.limit(20)
|
||||||
|
.with(['author', 'comments'])
|
||||||
|
.find(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
userStats,
|
||||||
|
recentPosts,
|
||||||
|
topComments,
|
||||||
|
followingActivity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The QueryBuilder provides a powerful, type-safe way to construct complex database queries with support for relationships, caching, pagination, and advanced filtering options.
|
734
docs/docs/contributing/code-guidelines.md
Normal file
734
docs/docs/contributing/code-guidelines.md
Normal file
@ -0,0 +1,734 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Code Guidelines
|
||||||
|
|
||||||
|
This document outlines the coding standards, best practices, and architectural principles for contributing to DebrosFramework.
|
||||||
|
|
||||||
|
## 🎯 Core Principles
|
||||||
|
|
||||||
|
### 1. Developer Experience First
|
||||||
|
|
||||||
|
Every API decision should prioritize developer experience:
|
||||||
|
|
||||||
|
- **Intuitive naming** - Use clear, descriptive names
|
||||||
|
- **Consistent patterns** - Follow established conventions
|
||||||
|
- **Helpful errors** - Provide actionable error messages
|
||||||
|
- **Complete TypeScript** - Full type safety and IntelliSense
|
||||||
|
|
||||||
|
### 2. Performance by Default
|
||||||
|
|
||||||
|
Optimize for common use cases:
|
||||||
|
|
||||||
|
- **Lazy loading** - Load data only when needed
|
||||||
|
- **Automatic caching** - Cache frequently accessed data
|
||||||
|
- **Efficient queries** - Optimize database operations
|
||||||
|
- **Memory management** - Clean up resources properly
|
||||||
|
|
||||||
|
### 3. Scalability Built-in
|
||||||
|
|
||||||
|
Design for applications with millions of users:
|
||||||
|
|
||||||
|
- **Automatic sharding** - Distribute data effectively
|
||||||
|
- **Parallel processing** - Execute operations concurrently
|
||||||
|
- **Resource optimization** - Use resources efficiently
|
||||||
|
- **Graceful degradation** - Handle failures elegantly
|
||||||
|
|
||||||
|
## 📝 TypeScript Standards
|
||||||
|
|
||||||
|
### Type Safety Requirements
|
||||||
|
|
||||||
|
All code must be fully typed with strict TypeScript:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Explicit types
|
||||||
|
interface UserCreateData {
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
bio?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createUser(data: UserCreateData): Promise<User> {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Bad - Using any
|
||||||
|
async function createUser(data: any): Promise<any> {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interface Design
|
||||||
|
|
||||||
|
Use interfaces for all public APIs:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Clear interface definition
|
||||||
|
interface QueryOptions {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
orderBy?: OrderByClause[];
|
||||||
|
with?: string[];
|
||||||
|
cache?: boolean | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Good - Generic interfaces
|
||||||
|
interface PaginatedResult<T> {
|
||||||
|
data: T[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
perPage: number;
|
||||||
|
hasMore: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
Use typed error classes with helpful messages:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Specific error types
|
||||||
|
export class ValidationError extends DebrosFrameworkError {
|
||||||
|
constructor(
|
||||||
|
public field: string,
|
||||||
|
public value: any,
|
||||||
|
public constraint: string,
|
||||||
|
message?: string,
|
||||||
|
) {
|
||||||
|
super(message || `Validation failed for field '${field}': ${constraint}`, 'VALIDATION_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Good - Error usage
|
||||||
|
throw new ValidationError('email', 'invalid-email', 'must be valid email format');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generic Programming
|
||||||
|
|
||||||
|
Use generics for reusable components:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Generic model operations
|
||||||
|
abstract class BaseModel {
|
||||||
|
static async create<T extends BaseModel>(
|
||||||
|
this: ModelConstructor<T>,
|
||||||
|
data: Partial<T>,
|
||||||
|
): Promise<T> {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
static query<T extends BaseModel>(this: ModelConstructor<T>): QueryBuilder<T> {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏗️ Architecture Patterns
|
||||||
|
|
||||||
|
### Decorator Pattern
|
||||||
|
|
||||||
|
Use decorators consistently for metadata:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Consistent decorator usage
|
||||||
|
@Model({
|
||||||
|
scope: 'global',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: { strategy: 'hash', count: 4, key: 'id' },
|
||||||
|
})
|
||||||
|
export class User extends BaseModel {
|
||||||
|
@Field({
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
validate: (value: string) => value.length >= 3,
|
||||||
|
})
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@HasMany(() => Post, 'userId')
|
||||||
|
posts: Post[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Pattern
|
||||||
|
|
||||||
|
Use dependency injection for services:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Service injection
|
||||||
|
export class DatabaseManager {
|
||||||
|
constructor(
|
||||||
|
private orbitDBService: FrameworkOrbitDBService,
|
||||||
|
private shardManager: ShardManager,
|
||||||
|
private configManager: ConfigManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async getDatabaseForModel<T extends BaseModel>(
|
||||||
|
modelClass: ModelConstructor<T>,
|
||||||
|
userId?: string,
|
||||||
|
): Promise<Database> {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Builder Pattern
|
||||||
|
|
||||||
|
Use builders for complex configuration:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Fluent builder API
|
||||||
|
const migration = createMigration('add_user_profiles', '1.1.0')
|
||||||
|
.addField('User', 'profilePicture', { type: 'string', required: false })
|
||||||
|
.addField('User', 'bio', { type: 'string', required: false })
|
||||||
|
.transformData('User', (user) => ({
|
||||||
|
...user,
|
||||||
|
displayName: user.username || 'Anonymous',
|
||||||
|
}))
|
||||||
|
.addValidator('check_profile_data', async (context) => {
|
||||||
|
// Validation logic
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Async/Await Patterns
|
||||||
|
|
||||||
|
### Promise Handling
|
||||||
|
|
||||||
|
Always use async/await instead of Promise chains:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - async/await
|
||||||
|
async function getUserWithPosts(userId: string): Promise<User> {
|
||||||
|
const user = await User.findById(userId);
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundError('User', userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const posts = await Post.query().where('userId', userId).orderBy('createdAt', 'desc').find();
|
||||||
|
|
||||||
|
user.posts = posts;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Bad - Promise chains
|
||||||
|
function getUserWithPosts(userId: string): Promise<User> {
|
||||||
|
return User.findById(userId).then((user) => {
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundError('User', userId);
|
||||||
|
}
|
||||||
|
return Post.query()
|
||||||
|
.where('userId', userId)
|
||||||
|
.orderBy('createdAt', 'desc')
|
||||||
|
.find()
|
||||||
|
.then((posts) => {
|
||||||
|
user.posts = posts;
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Propagation
|
||||||
|
|
||||||
|
Let errors bubble up with proper context:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Error context
|
||||||
|
async function createUserWithProfile(userData: UserCreateData): Promise<User> {
|
||||||
|
try {
|
||||||
|
const user = await User.create(userData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await UserProfile.create({
|
||||||
|
userId: user.id,
|
||||||
|
displayName: userData.username,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Clean up user if profile creation fails
|
||||||
|
await user.delete();
|
||||||
|
throw new OperationError('Failed to create user profile', 'USER_PROFILE_CREATION_FAILED', {
|
||||||
|
userId: user.id,
|
||||||
|
originalError: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ValidationError) {
|
||||||
|
throw error; // Re-throw validation errors as-is
|
||||||
|
}
|
||||||
|
throw new OperationError('Failed to create user', 'USER_CREATION_FAILED', {
|
||||||
|
userData,
|
||||||
|
originalError: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 Testing Standards
|
||||||
|
|
||||||
|
### Unit Test Structure
|
||||||
|
|
||||||
|
Use consistent test structure:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Clear test structure
|
||||||
|
describe('User Model', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await setupTestDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await cleanupTestDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create user with valid data', async () => {
|
||||||
|
// Arrange
|
||||||
|
const userData = {
|
||||||
|
username: 'testuser',
|
||||||
|
email: 'test@example.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const user = await User.create(userData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(user).toBeDefined();
|
||||||
|
expect(user.username).toBe(userData.username);
|
||||||
|
expect(user.email).toBe(userData.email);
|
||||||
|
expect(user.id).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw ValidationError for invalid email', async () => {
|
||||||
|
// Arrange
|
||||||
|
const userData = {
|
||||||
|
username: 'testuser',
|
||||||
|
email: 'invalid-email',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await expect(User.create(userData)).rejects.toThrow(ValidationError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('relationships', () => {
|
||||||
|
it('should load posts relationship', async () => {
|
||||||
|
// Test relationship loading
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Test Patterns
|
||||||
|
|
||||||
|
Test real-world scenarios:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Integration test
|
||||||
|
describe('Blog Scenario Integration', () => {
|
||||||
|
let framework: DebrosFramework;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
framework = await setupFrameworkForTesting();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await framework.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle complete blog workflow', async () => {
|
||||||
|
// Create user
|
||||||
|
const user = await User.create({
|
||||||
|
username: 'blogger',
|
||||||
|
email: 'blogger@example.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create post
|
||||||
|
const post = await Post.create({
|
||||||
|
title: 'Test Post',
|
||||||
|
content: 'Test content',
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add comment
|
||||||
|
const comment = await Comment.create({
|
||||||
|
content: 'Great post!',
|
||||||
|
postId: post.id,
|
||||||
|
authorId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify relationships
|
||||||
|
const postWithComments = await Post.query()
|
||||||
|
.where('id', post.id)
|
||||||
|
.with(['comments.author'])
|
||||||
|
.findOne();
|
||||||
|
|
||||||
|
expect(postWithComments).toBeDefined();
|
||||||
|
expect(postWithComments!.comments).toHaveLength(1);
|
||||||
|
expect(postWithComments!.comments[0].author.username).toBe('blogger');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Performance Guidelines
|
||||||
|
|
||||||
|
### Query Optimization
|
||||||
|
|
||||||
|
Write efficient queries:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Optimized query
|
||||||
|
const recentPosts = await Post.query()
|
||||||
|
.where('publishedAt', '>', Date.now() - 7 * 24 * 60 * 60 * 1000)
|
||||||
|
.where('isPublished', true)
|
||||||
|
.with(['author']) // Eager load to avoid N+1
|
||||||
|
.orderBy('publishedAt', 'desc')
|
||||||
|
.limit(20)
|
||||||
|
.cache(300) // Cache for 5 minutes
|
||||||
|
.find();
|
||||||
|
|
||||||
|
// ❌ Bad - Inefficient query
|
||||||
|
const allPosts = await Post.query().find(); // Loads everything
|
||||||
|
const recentPosts = allPosts
|
||||||
|
.filter((p) => p.publishedAt > Date.now() - 7 * 24 * 60 * 60 * 1000)
|
||||||
|
.filter((p) => p.isPublished)
|
||||||
|
.slice(0, 20);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Management
|
||||||
|
|
||||||
|
Clean up resources properly:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Resource cleanup
|
||||||
|
export class QueryCache {
|
||||||
|
private cache = new Map<string, CacheEntry>();
|
||||||
|
private cleanupInterval: NodeJS.Timeout;
|
||||||
|
|
||||||
|
constructor(private ttl: number = 300000) {
|
||||||
|
this.cleanupInterval = setInterval(() => {
|
||||||
|
this.cleanup();
|
||||||
|
}, this.ttl / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop(): Promise<void> {
|
||||||
|
if (this.cleanupInterval) {
|
||||||
|
clearInterval(this.cleanupInterval);
|
||||||
|
}
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private cleanup(): void {
|
||||||
|
const now = Date.now();
|
||||||
|
for (const [key, entry] of this.cache.entries()) {
|
||||||
|
if (entry.expiresAt < now) {
|
||||||
|
this.cache.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Async Performance
|
||||||
|
|
||||||
|
Use Promise.all for parallel operations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Parallel execution
|
||||||
|
async function getUserDashboardData(userId: string): Promise<DashboardData> {
|
||||||
|
const [user, recentPosts, stats, notifications] = await Promise.all([
|
||||||
|
User.findById(userId),
|
||||||
|
Post.query().where('userId', userId).limit(5).find(),
|
||||||
|
getUserStats(userId),
|
||||||
|
getRecentNotifications(userId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
recentPosts,
|
||||||
|
stats,
|
||||||
|
notifications,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Bad - Sequential execution
|
||||||
|
async function getUserDashboardData(userId: string): Promise<DashboardData> {
|
||||||
|
const user = await User.findById(userId);
|
||||||
|
const recentPosts = await Post.query().where('userId', userId).limit(5).find();
|
||||||
|
const stats = await getUserStats(userId);
|
||||||
|
const notifications = await getRecentNotifications(userId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
recentPosts,
|
||||||
|
stats,
|
||||||
|
notifications,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Code Organization
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
Organize code logically:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/framework/
|
||||||
|
├── core/ # Core framework components
|
||||||
|
│ ├── ConfigManager.ts
|
||||||
|
│ ├── DatabaseManager.ts
|
||||||
|
│ └── ModelRegistry.ts
|
||||||
|
├── models/ # Model system
|
||||||
|
│ ├── BaseModel.ts
|
||||||
|
│ └── decorators/
|
||||||
|
│ ├── Field.ts
|
||||||
|
│ ├── Model.ts
|
||||||
|
│ ├── relationships.ts
|
||||||
|
│ └── hooks.ts
|
||||||
|
├── query/ # Query system
|
||||||
|
│ ├── QueryBuilder.ts
|
||||||
|
│ ├── QueryExecutor.ts
|
||||||
|
│ └── QueryOptimizer.ts
|
||||||
|
└── types/ # Type definitions
|
||||||
|
├── framework.ts
|
||||||
|
├── models.ts
|
||||||
|
└── queries.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import Organization
|
||||||
|
|
||||||
|
Use consistent import patterns:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Organized imports
|
||||||
|
// Node.js built-ins
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
|
||||||
|
// External packages
|
||||||
|
import { Database } from '@orbitdb/core';
|
||||||
|
import { CID } from 'multiformats';
|
||||||
|
|
||||||
|
// Framework internals
|
||||||
|
import { BaseModel } from '../models/BaseModel';
|
||||||
|
import { ConfigManager } from '../core/ConfigManager';
|
||||||
|
import { DatabaseManager } from '../core/DatabaseManager';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import type { ModelConfig, FieldConfig } from '../types/models';
|
||||||
|
import type { QueryOptions, QueryResult } from '../types/queries';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Export Patterns
|
||||||
|
|
||||||
|
Use consistent export patterns:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Clear exports
|
||||||
|
// Main class export
|
||||||
|
export class QueryBuilder<T extends BaseModel> {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type exports
|
||||||
|
export type { QueryOptions, QueryResult, WhereClause, OrderByClause };
|
||||||
|
|
||||||
|
// Utility exports
|
||||||
|
export { buildWhereClause, optimizeQuery, validateQueryOptions };
|
||||||
|
|
||||||
|
// Default export for main functionality
|
||||||
|
export default QueryBuilder;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Documentation Standards
|
||||||
|
|
||||||
|
### JSDoc Comments
|
||||||
|
|
||||||
|
Document all public APIs:
|
||||||
|
|
||||||
|
````typescript
|
||||||
|
/**
|
||||||
|
* Creates a new model instance with the provided data.
|
||||||
|
*
|
||||||
|
* @template T - The model type extending BaseModel
|
||||||
|
* @param data - The data to create the model with
|
||||||
|
* @param options - Optional creation options
|
||||||
|
* @returns Promise resolving to the created model instance
|
||||||
|
*
|
||||||
|
* @throws {ValidationError} When data validation fails
|
||||||
|
* @throws {DatabaseError} When database operation fails
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const user = await User.create({
|
||||||
|
* username: 'john',
|
||||||
|
* email: 'john@example.com'
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static async create<T extends BaseModel>(
|
||||||
|
this: ModelConstructor<T>,
|
||||||
|
data: Partial<T>,
|
||||||
|
options?: CreateOptions
|
||||||
|
): Promise<T> {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
````
|
||||||
|
|
||||||
|
### Code Comments
|
||||||
|
|
||||||
|
Add comments for complex logic:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Explaining complex logic
|
||||||
|
private calculateShardIndex(key: string, shardCount: number): number {
|
||||||
|
// Use consistent hashing to distribute data evenly across shards
|
||||||
|
// This ensures that the same key always maps to the same shard
|
||||||
|
const hash = this.hashFunction(key);
|
||||||
|
|
||||||
|
// Use modulo to map hash to shard index
|
||||||
|
// Add 1 to avoid negative numbers with certain hash functions
|
||||||
|
return Math.abs(hash) % shardCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Good - Explaining business logic
|
||||||
|
async ensureUserDatabaseExists(userId: string): Promise<Database> {
|
||||||
|
// Check if user database already exists in cache
|
||||||
|
const existingDb = this.userDatabases.get(userId);
|
||||||
|
if (existingDb) {
|
||||||
|
return existingDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new user database with user-specific configuration
|
||||||
|
// This provides data isolation and improved performance
|
||||||
|
const database = await this.createUserDatabase(userId);
|
||||||
|
|
||||||
|
// Cache the database for future use
|
||||||
|
this.userDatabases.set(userId, database);
|
||||||
|
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚡ Performance Monitoring
|
||||||
|
|
||||||
|
### Metrics Collection
|
||||||
|
|
||||||
|
Add metrics to important operations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Performance monitoring
|
||||||
|
export class QueryExecutor {
|
||||||
|
private metrics = new Map<string, PerformanceMetric>();
|
||||||
|
|
||||||
|
async executeQuery<T>(query: QueryBuilder<T>): Promise<QueryResult<T>> {
|
||||||
|
const startTime = Date.now();
|
||||||
|
const queryKey = query.toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.internalExecuteQuery(query);
|
||||||
|
|
||||||
|
// Record successful execution
|
||||||
|
this.recordMetric(queryKey, Date.now() - startTime, true);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
// Record failed execution
|
||||||
|
this.recordMetric(queryKey, Date.now() - startTime, false);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private recordMetric(queryKey: string, duration: number, success: boolean): void {
|
||||||
|
const existing = this.metrics.get(queryKey) || {
|
||||||
|
count: 0,
|
||||||
|
totalDuration: 0,
|
||||||
|
successCount: 0,
|
||||||
|
averageDuration: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
existing.count++;
|
||||||
|
existing.totalDuration += duration;
|
||||||
|
if (success) existing.successCount++;
|
||||||
|
existing.averageDuration = existing.totalDuration / existing.count;
|
||||||
|
|
||||||
|
this.metrics.set(queryKey, existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 Security Considerations
|
||||||
|
|
||||||
|
### Input Validation
|
||||||
|
|
||||||
|
Validate all inputs thoroughly:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Input validation
|
||||||
|
export class UserService {
|
||||||
|
async createUser(userData: UserCreateData): Promise<User> {
|
||||||
|
// Validate required fields
|
||||||
|
if (!userData.username || typeof userData.username !== 'string') {
|
||||||
|
throw new ValidationError('username', userData.username, 'required string');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize username
|
||||||
|
const sanitizedUsername = userData.username.trim().toLowerCase();
|
||||||
|
|
||||||
|
// Check length constraints
|
||||||
|
if (sanitizedUsername.length < 3 || sanitizedUsername.length > 20) {
|
||||||
|
throw new ValidationError('username', sanitizedUsername, 'length between 3-20');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for valid characters
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(sanitizedUsername)) {
|
||||||
|
throw new ValidationError('username', sanitizedUsername, 'alphanumeric and underscore only');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check uniqueness
|
||||||
|
const existingUser = await User.findOne({ username: sanitizedUsername });
|
||||||
|
if (existingUser) {
|
||||||
|
throw new ConflictError('Username already exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
return User.create({
|
||||||
|
...userData,
|
||||||
|
username: sanitizedUsername,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Information
|
||||||
|
|
||||||
|
Don't leak sensitive information in errors:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Safe error messages
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof DatabaseConnectionError) {
|
||||||
|
// Don't expose internal connection details
|
||||||
|
throw new OperationError(
|
||||||
|
'Database operation failed',
|
||||||
|
'DATABASE_ERROR',
|
||||||
|
{ operation: 'create_user' } // Safe context only
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Bad - Leaking sensitive info
|
||||||
|
catch (error) {
|
||||||
|
throw new Error(`Database connection failed: ${error.message} at ${error.stack}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
These guidelines help ensure that DebrosFramework maintains high code quality, performance, and developer experience. When in doubt, prioritize clarity, type safety, and developer experience.
|
||||||
|
|
||||||
|
**Next:** Check out our [Testing Guide](./testing-guide) to learn about writing comprehensive tests.
|
475
docs/docs/contributing/development-setup.md
Normal file
475
docs/docs/contributing/development-setup.md
Normal file
@ -0,0 +1,475 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Development Setup
|
||||||
|
|
||||||
|
This guide will help you set up your development environment for contributing to DebrosFramework.
|
||||||
|
|
||||||
|
## 🔧 Prerequisites
|
||||||
|
|
||||||
|
### Required Software
|
||||||
|
|
||||||
|
| Software | Version | Purpose |
|
||||||
|
| -------------- | ------- | --------------------------- |
|
||||||
|
| **Node.js** | 18.0+ | Runtime environment |
|
||||||
|
| **pnpm** | Latest | Package manager (preferred) |
|
||||||
|
| **Git** | Latest | Version control |
|
||||||
|
| **Docker** | Latest | Integration testing |
|
||||||
|
| **TypeScript** | 5.0+ | Development language |
|
||||||
|
|
||||||
|
### Optional Tools
|
||||||
|
|
||||||
|
- **VS Code** - Recommended editor with excellent TypeScript support
|
||||||
|
- **Docker Desktop** - GUI for Docker management
|
||||||
|
- **Gitea CLI** (if available) - Command-line interface for our Gitea instance
|
||||||
|
|
||||||
|
## 🚀 Environment Setup
|
||||||
|
|
||||||
|
### 1. Repository Access
|
||||||
|
|
||||||
|
#### Create Gitea Account
|
||||||
|
|
||||||
|
1. Visit https://git.debros.io
|
||||||
|
2. Click "Sign Up" to create an account
|
||||||
|
3. Verify your email address
|
||||||
|
4. Request access to the DeBros organization (contact maintainers)
|
||||||
|
|
||||||
|
#### Fork and Clone
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Fork the repository through Gitea web interface
|
||||||
|
# Then clone your fork
|
||||||
|
git clone https://git.debros.io/DeBros/network.git
|
||||||
|
cd network
|
||||||
|
|
||||||
|
# Add upstream remote
|
||||||
|
git remote add upstream https://git.debros.io/DeBros/network.git
|
||||||
|
|
||||||
|
# Verify remotes
|
||||||
|
git remote -v
|
||||||
|
# origin https://git.debros.io/DeBros/network.git (fetch)
|
||||||
|
# origin https://git.debros.io/DeBros/network.git (push)
|
||||||
|
# upstream https://git.debros.io/DeBros/network.git (fetch)
|
||||||
|
# upstream https://git.debros.io/DeBros/network.git (push)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Project Setup
|
||||||
|
|
||||||
|
#### Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install pnpm if not already installed
|
||||||
|
npm install -g pnpm
|
||||||
|
|
||||||
|
# Install project dependencies
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Install global development tools
|
||||||
|
pnpm install -g tsx ts-node
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Verify Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check versions
|
||||||
|
node --version # Should be 18.0+
|
||||||
|
pnpm --version # Should be latest
|
||||||
|
tsc --version # Should be 5.0+
|
||||||
|
|
||||||
|
# Check project setup
|
||||||
|
pnpm run build # Should complete without errors
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. IDE Configuration
|
||||||
|
|
||||||
|
#### VS Code Setup
|
||||||
|
|
||||||
|
Install recommended extensions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install VS Code extensions
|
||||||
|
code --install-extension ms-vscode.vscode-typescript-next
|
||||||
|
code --install-extension esbenp.prettier-vscode
|
||||||
|
code --install-extension ms-vscode.vscode-eslint
|
||||||
|
code --install-extension bradlc.vscode-tailwindcss
|
||||||
|
code --install-extension ms-vscode.vscode-json
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `.vscode/settings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"typescript.preferences.includePackageJsonAutoImports": "auto",
|
||||||
|
"typescript.suggest.autoImports": true,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": true
|
||||||
|
},
|
||||||
|
"typescript.preferences.importModuleSpecifier": "relative",
|
||||||
|
"files.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/dist": true,
|
||||||
|
"**/.git": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TypeScript Configuration
|
||||||
|
|
||||||
|
The project includes proper TypeScript configuration in `tsconfig.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 Development Workflow
|
||||||
|
|
||||||
|
### Running the Framework
|
||||||
|
|
||||||
|
#### Basic Build and Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clean build
|
||||||
|
pnpm run clean
|
||||||
|
pnpm run build
|
||||||
|
|
||||||
|
# Run unit tests
|
||||||
|
pnpm run test:unit
|
||||||
|
|
||||||
|
# Run integration tests (requires Docker)
|
||||||
|
pnpm run test:real
|
||||||
|
|
||||||
|
# Run specific blog integration test
|
||||||
|
pnpm run test:blog-integration
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Development Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Watch mode for development
|
||||||
|
pnpm run dev
|
||||||
|
|
||||||
|
# Linting and formatting
|
||||||
|
pnpm run lint # Check for lint errors
|
||||||
|
pnpm run lint:fix # Fix auto-fixable lint errors
|
||||||
|
pnpm run format # Format code with Prettier
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
pnpm run clean # Remove build artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Setup
|
||||||
|
|
||||||
|
#### Unit Tests
|
||||||
|
|
||||||
|
Unit tests use Jest and are located in `tests/unit/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all unit tests
|
||||||
|
pnpm run test:unit
|
||||||
|
|
||||||
|
# Run specific test file
|
||||||
|
npx jest tests/unit/framework/core/ConfigManager.test.ts
|
||||||
|
|
||||||
|
# Run tests in watch mode
|
||||||
|
npx jest --watch tests/unit/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Integration Tests
|
||||||
|
|
||||||
|
Integration tests use Docker to create real IPFS/OrbitDB environments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ensure Docker is running
|
||||||
|
docker --version
|
||||||
|
|
||||||
|
# Run full integration test suite
|
||||||
|
pnpm run test:real
|
||||||
|
|
||||||
|
# This will:
|
||||||
|
# 1. Build Docker containers
|
||||||
|
# 2. Start IPFS bootstrap node
|
||||||
|
# 3. Start multiple framework instances
|
||||||
|
# 4. Run real-world scenarios
|
||||||
|
# 5. Tear down containers
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── unit/ # Unit tests
|
||||||
|
│ ├── framework/ # Framework component tests
|
||||||
|
│ └── shared/ # Shared test utilities
|
||||||
|
└── real-integration/ # Integration tests
|
||||||
|
└── blog-scenario/ # Blog application test scenario
|
||||||
|
├── docker/ # Docker configuration
|
||||||
|
├── scenarios/ # Test scenarios
|
||||||
|
└── models/ # Test models
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Development
|
||||||
|
|
||||||
|
#### Docker Setup for Testing
|
||||||
|
|
||||||
|
The integration tests require Docker. Make sure you have:
|
||||||
|
|
||||||
|
1. **Docker Desktop** installed and running
|
||||||
|
2. **Docker Compose** available (included with Docker Desktop)
|
||||||
|
3. **Sufficient memory** allocated to Docker (4GB+ recommended)
|
||||||
|
|
||||||
|
#### Docker Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build test containers
|
||||||
|
docker-compose -f tests/real-integration/blog-scenario/docker/docker-compose.blog.yml build
|
||||||
|
|
||||||
|
# Run integration tests
|
||||||
|
pnpm run test:real
|
||||||
|
|
||||||
|
# Clean up Docker resources
|
||||||
|
docker system prune -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Git Workflow
|
||||||
|
|
||||||
|
### Branch Strategy
|
||||||
|
|
||||||
|
We use a feature branch workflow:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start from main branch
|
||||||
|
git checkout main
|
||||||
|
git pull upstream main
|
||||||
|
|
||||||
|
# Create feature branch
|
||||||
|
git checkout -b feature/your-feature-name
|
||||||
|
|
||||||
|
# Make changes, commit, and push
|
||||||
|
git add .
|
||||||
|
git commit -m "feat: add new feature"
|
||||||
|
git push origin feature/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pull Request Process
|
||||||
|
|
||||||
|
1. **Create Pull Request** in Gitea web interface
|
||||||
|
2. **Fill out template** with description and testing notes
|
||||||
|
3. **Request review** from maintainers
|
||||||
|
4. **Address feedback** if any
|
||||||
|
5. **Merge** once approved
|
||||||
|
|
||||||
|
## 🛠️ Development Tools
|
||||||
|
|
||||||
|
### Code Quality Tools
|
||||||
|
|
||||||
|
#### ESLint Configuration
|
||||||
|
|
||||||
|
The project uses ESLint for code quality:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for issues
|
||||||
|
pnpm run lint
|
||||||
|
|
||||||
|
# Fix auto-fixable issues
|
||||||
|
pnpm run lint:fix
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Prettier Configuration
|
||||||
|
|
||||||
|
Prettier handles code formatting:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Format all files
|
||||||
|
pnpm run format
|
||||||
|
|
||||||
|
# Format specific files
|
||||||
|
npx prettier --write "src/**/*.ts"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Husky Git Hooks
|
||||||
|
|
||||||
|
The project includes pre-commit hooks:
|
||||||
|
|
||||||
|
- **Pre-commit**: Runs lint-staged to format staged files
|
||||||
|
- **Pre-push**: Runs basic tests to prevent broken code
|
||||||
|
|
||||||
|
### Package Scripts Reference
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && tsc-esm-fix --outDir=./dist/esm",
|
||||||
|
"dev": "tsc -w",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"lint": "npx eslint src",
|
||||||
|
"format": "prettier --write \"**/*.{ts,js,json,md}\"",
|
||||||
|
"lint:fix": "npx eslint src --fix",
|
||||||
|
"test:unit": "jest tests/unit",
|
||||||
|
"test:blog-integration": "tsx tests/real-integration/blog-scenario/scenarios/BlogTestRunner.ts",
|
||||||
|
"test:real": "docker-compose -f tests/real-integration/blog-scenario/docker/docker-compose.blog.yml up --build --abort-on-container-exit",
|
||||||
|
"prepublishOnly": "npm run clean && npm run build"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Debugging
|
||||||
|
|
||||||
|
### Framework Debugging
|
||||||
|
|
||||||
|
#### Enable Debug Logging
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In your test or development code
|
||||||
|
const framework = new DebrosFramework({
|
||||||
|
monitoring: {
|
||||||
|
logLevel: 'debug',
|
||||||
|
enableMetrics: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### VS Code Debugging
|
||||||
|
|
||||||
|
Create `.vscode/launch.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug Unit Tests",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
|
"args": ["--runInBand", "${file}"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug Integration Test",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/node_modules/.bin/tsx",
|
||||||
|
"args": ["tests/real-integration/blog-scenario/scenarios/BlogTestRunner.ts"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### Node.js Version Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Node.js version
|
||||||
|
node --version
|
||||||
|
|
||||||
|
# If using nvm, switch to correct version
|
||||||
|
nvm use 18
|
||||||
|
nvm install 18.19.0
|
||||||
|
nvm alias default 18.19.0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Docker status
|
||||||
|
docker --version
|
||||||
|
docker-compose --version
|
||||||
|
|
||||||
|
# Clean Docker cache if tests fail
|
||||||
|
docker system prune -f
|
||||||
|
docker volume prune -f
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TypeScript Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clear TypeScript cache
|
||||||
|
npx tsc --build --clean
|
||||||
|
|
||||||
|
# Rebuild project
|
||||||
|
pnpm run clean
|
||||||
|
pnpm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Additional Resources
|
||||||
|
|
||||||
|
### Learning Resources
|
||||||
|
|
||||||
|
- **[IPFS Documentation](https://docs.ipfs.io/)** - Understanding IPFS concepts
|
||||||
|
- **[OrbitDB Guide](https://orbitdb.org/getting-started/)** - OrbitDB basics
|
||||||
|
- **[libp2p Concepts](https://docs.libp2p.io/concepts/)** - Peer-to-peer networking
|
||||||
|
- **[TypeScript Handbook](https://www.typescriptlang.org/docs/)** - TypeScript reference
|
||||||
|
|
||||||
|
### Community Resources
|
||||||
|
|
||||||
|
- **Gitea Repository**: https://git.debros.io/DeBros/network
|
||||||
|
- **Documentation**: This documentation site
|
||||||
|
- **Discord** (if available): Community chat
|
||||||
|
- **Email**: Contact maintainers for access and questions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Setup Verification
|
||||||
|
|
||||||
|
Run this checklist to verify your setup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Check Node.js version
|
||||||
|
node --version # Should be 18.0+
|
||||||
|
|
||||||
|
# 2. Check package manager
|
||||||
|
pnpm --version # Should be latest
|
||||||
|
|
||||||
|
# 3. Install dependencies
|
||||||
|
pnpm install # Should complete without errors
|
||||||
|
|
||||||
|
# 4. Build project
|
||||||
|
pnpm run build # Should complete without errors
|
||||||
|
|
||||||
|
# 5. Run linting
|
||||||
|
pnpm run lint # Should pass with no errors
|
||||||
|
|
||||||
|
# 6. Run unit tests
|
||||||
|
pnpm run test:unit # Should pass all tests
|
||||||
|
|
||||||
|
# 7. Check Docker (optional, for integration tests)
|
||||||
|
docker --version # Should show Docker version
|
||||||
|
pnpm run test:real # Should run integration tests
|
||||||
|
```
|
||||||
|
|
||||||
|
If all steps pass, you're ready to contribute! 🎉
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Next Steps:**
|
||||||
|
|
||||||
|
- Read our **[Code Guidelines](./code-guidelines)** to understand coding standards
|
||||||
|
- Check out **[Testing Guide](./testing-guide)** to learn about writing tests
|
||||||
|
- Browse existing issues in Gitea to find something to work on
|
214
docs/docs/contributing/overview.md
Normal file
214
docs/docs/contributing/overview.md
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Contributing to DebrosFramework
|
||||||
|
|
||||||
|
Welcome to the DebrosFramework contributor community! We're excited to have you help build the future of decentralized application development.
|
||||||
|
|
||||||
|
## 🌟 Why Contribute?
|
||||||
|
|
||||||
|
DebrosFramework is an ambitious project that aims to make decentralized application development as simple as traditional web development. Your contributions help:
|
||||||
|
|
||||||
|
- **Advance decentralized technology** - Make dApps more accessible to developers
|
||||||
|
- **Build better tools** - Create powerful abstractions over IPFS and OrbitDB
|
||||||
|
- **Shape the future** - Influence how decentralized applications are built
|
||||||
|
- **Learn cutting-edge tech** - Work with the latest in distributed systems
|
||||||
|
- **Join a community** - Connect with like-minded developers
|
||||||
|
|
||||||
|
## 🚀 Development Status
|
||||||
|
|
||||||
|
**Current Version**: 0.5.0-beta
|
||||||
|
**Status**: Active Development - Beta Release
|
||||||
|
|
||||||
|
DebrosFramework is in active beta development. The core architecture is not stable, but we're continuously improving APIs, adding features, and optimizing performance. This is an excellent time to contribute as your input can significantly shape the framework's direction.
|
||||||
|
|
||||||
|
### What's Ready for Contribution
|
||||||
|
|
||||||
|
✅ **Core Framework** - Stable architecture, ready for enhancements
|
||||||
|
✅ **Model System** - Decorator-based models with validation
|
||||||
|
✅ **Query Builder** - Rich querying with optimization
|
||||||
|
✅ **Relationship System** - Complex data relationships
|
||||||
|
✅ **Sharding** - Automatic data distribution
|
||||||
|
✅ **Migration System** - Schema evolution tools
|
||||||
|
✅ **Documentation** - Comprehensive guides and examples
|
||||||
|
|
||||||
|
### What's Coming Next
|
||||||
|
|
||||||
|
🔄 **Performance Optimization** - Query caching and execution improvements
|
||||||
|
🔄 **Advanced Features** - Real-time subscriptions and event systems
|
||||||
|
🔄 **Developer Experience** - Better tooling and debugging
|
||||||
|
🔄 **Production Ready** - Stability and performance for production use
|
||||||
|
|
||||||
|
## 🏗️ Repository Information
|
||||||
|
|
||||||
|
### Self-Hosted Git Repository
|
||||||
|
|
||||||
|
We use a **self-hosted Gitea instance** instead of GitHub:
|
||||||
|
|
||||||
|
**Repository URL**: https://git.debros.io/DeBros/network
|
||||||
|
|
||||||
|
**Why Gitea?**
|
||||||
|
|
||||||
|
- **Decentralization aligned** - Fits our philosophy of decentralized systems
|
||||||
|
- **Full control** - Complete control over our development infrastructure
|
||||||
|
- **Privacy focused** - No external dependencies for sensitive development data
|
||||||
|
- **Community owned** - Aligns with our open-source, community-driven approach
|
||||||
|
|
||||||
|
### Getting Repository Access
|
||||||
|
|
||||||
|
1. **Create an account** at https://git.debros.io
|
||||||
|
2. **Request access** by contacting the maintainers
|
||||||
|
3. **Fork the repository** to your account
|
||||||
|
4. **Clone your fork** locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.debros.io/DeBros/network.git
|
||||||
|
cd network
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤝 How to Contribute
|
||||||
|
|
||||||
|
### Types of Contributions Welcome
|
||||||
|
|
||||||
|
| Type | Description | Skill Level | Time Commitment |
|
||||||
|
| ----------------------- | ------------------------------ | --------------------- | --------------- |
|
||||||
|
| 🐛 **Bug Reports** | Find and report issues | Beginner | Low |
|
||||||
|
| 📖 **Documentation** | Improve guides and examples | Beginner-Intermediate | Low-Medium |
|
||||||
|
| ✨ **Feature Requests** | Suggest new capabilities | Any | Low |
|
||||||
|
| 🧪 **Testing** | Write tests and test scenarios | Intermediate | Medium |
|
||||||
|
| 🔧 **Bug Fixes** | Fix reported issues | Intermediate | Medium |
|
||||||
|
| ⚡ **Performance** | Optimize existing code | Advanced | Medium-High |
|
||||||
|
| 🚀 **New Features** | Implement new functionality | Advanced | High |
|
||||||
|
| 🏗️ **Architecture** | Design system improvements | Expert | High |
|
||||||
|
|
||||||
|
### Contribution Areas
|
||||||
|
|
||||||
|
#### 🎯 High Priority Areas
|
||||||
|
|
||||||
|
1. **Integration Tests** - Real-world scenario testing
|
||||||
|
2. **Performance Optimization** - Query execution and caching
|
||||||
|
3. **Developer Experience** - Better error messages and debugging
|
||||||
|
4. **Documentation** - More examples and use cases
|
||||||
|
5. **Type Safety** - Improved TypeScript definitions
|
||||||
|
|
||||||
|
#### 🔥 Hot Topics
|
||||||
|
|
||||||
|
- **Real-time Features** - PubSub improvements and real-time data sync
|
||||||
|
- **Migration Tools** - Better schema evolution and data transformation
|
||||||
|
- **Query Optimization** - Smarter query planning and execution
|
||||||
|
- **Monitoring** - Performance metrics and health checking
|
||||||
|
- **CLI Tools** - Development and deployment tooling
|
||||||
|
|
||||||
|
## 🛠️ Technical Overview
|
||||||
|
|
||||||
|
### Architecture Understanding
|
||||||
|
|
||||||
|
Before contributing code, familiarize yourself with the framework architecture:
|
||||||
|
|
||||||
|
```
|
||||||
|
DebrosFramework/
|
||||||
|
├── Core Layer (ConfigManager, DatabaseManager)
|
||||||
|
├── Model Layer (BaseModel, Decorators, Validation)
|
||||||
|
├── Query Layer (QueryBuilder, QueryExecutor, Optimization)
|
||||||
|
├── Relationship Layer (RelationshipManager, LazyLoader)
|
||||||
|
├── Sharding Layer (ShardManager, Distribution)
|
||||||
|
├── Migration Layer (MigrationManager, MigrationBuilder)
|
||||||
|
├── Feature Layer (PinningManager, PubSubManager)
|
||||||
|
└── Service Layer (OrbitDBService, IPFSService)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Technologies
|
||||||
|
|
||||||
|
- **TypeScript** - Primary development language
|
||||||
|
- **OrbitDB** - Distributed database layer
|
||||||
|
- **IPFS/Helia** - Distributed storage layer
|
||||||
|
- **libp2p** - Peer-to-peer networking
|
||||||
|
- **Docker** - Containerization for testing
|
||||||
|
- **Jest** - Unit testing framework
|
||||||
|
- **Prettier/ESLint** - Code formatting and linting
|
||||||
|
|
||||||
|
### Development Philosophy
|
||||||
|
|
||||||
|
1. **Developer Experience First** - APIs should be intuitive and well-documented
|
||||||
|
2. **Type Safety** - Comprehensive TypeScript support throughout
|
||||||
|
3. **Performance by Default** - Optimize common use cases automatically
|
||||||
|
4. **Flexibility** - Support diverse application patterns
|
||||||
|
5. **Reliability** - Robust error handling and recovery
|
||||||
|
6. **Scalability** - Design for applications with millions of users
|
||||||
|
|
||||||
|
## 📋 Getting Started Checklist
|
||||||
|
|
||||||
|
Before making your first contribution:
|
||||||
|
|
||||||
|
- [ ] Read this contributor guide completely
|
||||||
|
- [ ] Set up your development environment
|
||||||
|
- [ ] Run the test suite successfully
|
||||||
|
- [ ] Explore the codebase and documentation
|
||||||
|
- [ ] Join our community channels
|
||||||
|
- [ ] Choose your first contribution area
|
||||||
|
- [ ] Check existing issues and discussions
|
||||||
|
|
||||||
|
## 🔗 Quick Links
|
||||||
|
|
||||||
|
- **[Development Setup](./development-setup)** - Get your environment ready
|
||||||
|
- **[Code Guidelines](./code-guidelines)** - Coding standards and best practices
|
||||||
|
- **[Testing Guide](./testing-guide)** - How to write and run tests
|
||||||
|
- **[Documentation Guide](./documentation-guide)** - Contributing to docs
|
||||||
|
- **[Release Process](./release-process)** - How we ship new versions
|
||||||
|
- **[Community](./community)** - Connect with other contributors
|
||||||
|
|
||||||
|
## 💡 First Time Contributors
|
||||||
|
|
||||||
|
New to open source or DebrosFramework? Start here:
|
||||||
|
|
||||||
|
1. **Good First Issues** - Look for issues tagged `good-first-issue` in our Gitea repository
|
||||||
|
2. **Documentation** - Help improve our guides and examples
|
||||||
|
3. **Testing** - Add test cases for existing functionality
|
||||||
|
4. **Examples** - Create new usage examples and tutorials
|
||||||
|
|
||||||
|
## 🎯 Contributor Levels
|
||||||
|
|
||||||
|
### 🌱 **Beginner Contributors**
|
||||||
|
|
||||||
|
- Report bugs and suggest improvements
|
||||||
|
- Fix typos and improve documentation
|
||||||
|
- Add simple test cases
|
||||||
|
- Create usage examples
|
||||||
|
|
||||||
|
### 🌿 **Intermediate Contributors**
|
||||||
|
|
||||||
|
- Fix bugs and implement small features
|
||||||
|
- Improve existing functionality
|
||||||
|
- Write comprehensive tests
|
||||||
|
- Contribute to API design discussions
|
||||||
|
|
||||||
|
### 🌳 **Advanced Contributors**
|
||||||
|
|
||||||
|
- Implement major features
|
||||||
|
- Optimize performance-critical code
|
||||||
|
- Design architectural improvements
|
||||||
|
- Mentor other contributors
|
||||||
|
|
||||||
|
### 🏆 **Core Contributors**
|
||||||
|
|
||||||
|
- Drive technical direction
|
||||||
|
- Review and merge contributions
|
||||||
|
- Manage releases and roadmap
|
||||||
|
- Represent the project publicly
|
||||||
|
|
||||||
|
## 🏅 Recognition
|
||||||
|
|
||||||
|
Contributors are recognized through:
|
||||||
|
|
||||||
|
- **Contributor list** in repository and documentation
|
||||||
|
- **Release notes** crediting significant contributions
|
||||||
|
- **Community highlights** in announcements
|
||||||
|
- **Direct contributor access** for consistent contributors
|
||||||
|
- **Maintainer status** for exceptional long-term contributors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Ready to contribute? Head over to our **[Development Setup Guide](./development-setup)** to get started!
|
||||||
|
|
||||||
|
_Have questions? Join our community channels or reach out to the maintainers. We're here to help! 🚀_
|
@ -109,6 +109,18 @@ DebrosFramework is perfect for developers who want to:
|
|||||||
|
|
||||||
Ready to build your first decentralized application? Check out our [Getting Started Guide](./getting-started) to set up your development environment and create your first models.
|
Ready to build your first decentralized application? Check out our [Getting Started Guide](./getting-started) to set up your development environment and create your first models.
|
||||||
|
|
||||||
|
## Development Status
|
||||||
|
|
||||||
|
**Current Version**: 0.5.0-beta
|
||||||
|
**Status**: Active Development - Beta Release
|
||||||
|
|
||||||
|
DebrosFramework is currently in beta and under active development. The core architecture and APIs are stabilizing, but some features may still change. We recommend using it for:
|
||||||
|
|
||||||
|
- ✅ **Development and Testing**: Perfect for building and testing decentralized applications
|
||||||
|
- ✅ **Prototyping**: Rapid prototyping of dApp concepts
|
||||||
|
- ✅ **Learning**: Understanding decentralized application architecture
|
||||||
|
- ⚠️ **Production**: Use with caution - APIs may change in future versions
|
||||||
|
|
||||||
## Community and Support
|
## Community and Support
|
||||||
|
|
||||||
- 📖 [Documentation](./getting-started) - Comprehensive guides and examples
|
- 📖 [Documentation](./getting-started) - Comprehensive guides and examples
|
||||||
|
691
docs/docs/videos/index.md
Normal file
691
docs/docs/videos/index.md
Normal file
@ -0,0 +1,691 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 6
|
||||||
|
---
|
||||||
|
|
||||||
|
# Video Tutorials
|
||||||
|
|
||||||
|
Learn Debros Network through comprehensive video tutorials. These step-by-step guides will help you master the framework from basic concepts to advanced implementations.
|
||||||
|
|
||||||
|
## 🎬 **Getting Started Series**
|
||||||
|
|
||||||
|
### 1. Introduction to Debros Network
|
||||||
|
|
||||||
|
**Duration: 15 minutes**
|
||||||
|
|
||||||
|
A complete overview of Debros Network, its architecture, and core concepts. Perfect for developers new to decentralized applications.
|
||||||
|
|
||||||
|
**What You'll Learn:**
|
||||||
|
|
||||||
|
- Framework architecture and components
|
||||||
|
- Key differences from traditional frameworks
|
||||||
|
- When to use Debros Network
|
||||||
|
- Development environment overview
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Code examples from this video
|
||||||
|
import { DebrosFramework, BaseModel, Model, Field } from '@debros/network';
|
||||||
|
|
||||||
|
@Model({
|
||||||
|
scope: 'global',
|
||||||
|
type: 'docstore',
|
||||||
|
})
|
||||||
|
class User extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true })
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Introduction Video**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Setting Up Your Development Environment
|
||||||
|
|
||||||
|
**Duration: 20 minutes**
|
||||||
|
|
||||||
|
Step-by-step guide to setting up Debros Network in your development environment.
|
||||||
|
|
||||||
|
**What You'll Learn:**
|
||||||
|
|
||||||
|
- Installing dependencies
|
||||||
|
- Project structure setup
|
||||||
|
- IDE configuration
|
||||||
|
- Development tools
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- npm or pnpm
|
||||||
|
- TypeScript knowledge
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Commands from this video
|
||||||
|
npm create debros-app my-app
|
||||||
|
cd my-app
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Setup Video**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Your First Debros Application
|
||||||
|
|
||||||
|
**Duration: 25 minutes**
|
||||||
|
|
||||||
|
Build your first decentralized application with user management and basic CRUD operations.
|
||||||
|
|
||||||
|
**What You'll Learn:**
|
||||||
|
|
||||||
|
- Creating models with decorators
|
||||||
|
- Database initialization
|
||||||
|
- Basic CRUD operations
|
||||||
|
- Error handling
|
||||||
|
|
||||||
|
**Project Files:**
|
||||||
|
|
||||||
|
- `models/User.ts`
|
||||||
|
- `models/Post.ts`
|
||||||
|
- `app.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Final code from this tutorial
|
||||||
|
@Model({
|
||||||
|
scope: 'global',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: { strategy: 'hash', count: 4, key: 'id' },
|
||||||
|
})
|
||||||
|
export class User extends BaseModel {
|
||||||
|
@Field({ type: 'string', required: true, unique: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Field({ type: 'string', required: true, unique: true })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@HasMany(() => Post, 'authorId')
|
||||||
|
posts: Post[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch First App Video**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ **Core Concepts Series**
|
||||||
|
|
||||||
|
### 4. Understanding Models and Decorators
|
||||||
|
|
||||||
|
**Duration: 30 minutes**
|
||||||
|
|
||||||
|
Deep dive into the model system, decorators, and data validation.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Model configuration options
|
||||||
|
- Field types and validation
|
||||||
|
- Custom validators
|
||||||
|
- Lifecycle hooks
|
||||||
|
- Best practices
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Advanced model example from video
|
||||||
|
@Model({
|
||||||
|
scope: 'user',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: { strategy: 'user', count: 2, key: 'userId' },
|
||||||
|
})
|
||||||
|
export class BlogPost extends BaseModel {
|
||||||
|
@Field({
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
minLength: 5,
|
||||||
|
maxLength: 200,
|
||||||
|
validate: (title: string) => !title.includes('spam'),
|
||||||
|
})
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@BeforeCreate()
|
||||||
|
async validatePost() {
|
||||||
|
// Custom validation logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Models Deep Dive**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Mastering the Query System
|
||||||
|
|
||||||
|
**Duration: 35 minutes**
|
||||||
|
|
||||||
|
Complete guide to building complex queries with relationships and optimization.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Query builder API
|
||||||
|
- Filtering and sorting
|
||||||
|
- Relationship loading
|
||||||
|
- Pagination
|
||||||
|
- Query optimization
|
||||||
|
- Caching strategies
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Complex query example from video
|
||||||
|
const results = await User.query()
|
||||||
|
.where('isActive', true)
|
||||||
|
.where('score', '>', 100)
|
||||||
|
.whereHas('posts', (query) => {
|
||||||
|
query.where('isPublished', true).where('createdAt', '>', Date.now() - 30 * 24 * 60 * 60 * 1000);
|
||||||
|
})
|
||||||
|
.with(['posts.comments.author', 'profile'])
|
||||||
|
.orderBy('score', 'desc')
|
||||||
|
.cache(300)
|
||||||
|
.paginate(1, 20);
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Query Mastery**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Working with Relationships
|
||||||
|
|
||||||
|
**Duration: 25 minutes**
|
||||||
|
|
||||||
|
Understanding and implementing relationships between models.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Relationship types (HasMany, BelongsTo, etc.)
|
||||||
|
- Eager vs lazy loading
|
||||||
|
- Nested relationships
|
||||||
|
- Performance considerations
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Relationship examples from video
|
||||||
|
@Model({ scope: 'global', type: 'docstore' })
|
||||||
|
export class User extends BaseModel {
|
||||||
|
@HasMany(() => Post, 'authorId')
|
||||||
|
posts: Post[];
|
||||||
|
|
||||||
|
@ManyToMany(() => User, 'followers', 'following')
|
||||||
|
followers: User[];
|
||||||
|
|
||||||
|
@HasOne(() => UserProfile, 'userId')
|
||||||
|
profile: UserProfile;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Relationships Video**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 **Advanced Features Series**
|
||||||
|
|
||||||
|
### 7. Database Sharding and Scaling
|
||||||
|
|
||||||
|
**Duration: 40 minutes**
|
||||||
|
|
||||||
|
Learn how to scale your application with automatic sharding strategies.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Sharding strategies
|
||||||
|
- User-scoped vs global databases
|
||||||
|
- Performance optimization
|
||||||
|
- Monitoring and metrics
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Sharding configuration examples
|
||||||
|
@Model({
|
||||||
|
scope: 'user',
|
||||||
|
type: 'docstore',
|
||||||
|
sharding: {
|
||||||
|
strategy: 'hash',
|
||||||
|
count: 8,
|
||||||
|
key: 'userId',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class UserData extends BaseModel {
|
||||||
|
// Model implementation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Sharding Guide**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Migrations and Schema Evolution
|
||||||
|
|
||||||
|
**Duration: 30 minutes**
|
||||||
|
|
||||||
|
Managing database schema changes and data migrations.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Creating migrations
|
||||||
|
- Data transformations
|
||||||
|
- Rollback strategies
|
||||||
|
- Production deployment
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Migration example from video
|
||||||
|
const migration = createMigration('add_user_profiles', '1.1.0')
|
||||||
|
.addField('User', 'profilePicture', {
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
.addField('User', 'bio', {
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
.transformData('User', (user) => ({
|
||||||
|
...user,
|
||||||
|
displayName: user.username || 'Anonymous',
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Migrations Video**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Real-time Features and PubSub
|
||||||
|
|
||||||
|
**Duration: 25 minutes**
|
||||||
|
|
||||||
|
Implementing real-time functionality with the built-in PubSub system.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Event publishing
|
||||||
|
- Real-time subscriptions
|
||||||
|
- WebSocket integration
|
||||||
|
- Performance considerations
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Real-time examples from video
|
||||||
|
@Model({ scope: 'global', type: 'docstore' })
|
||||||
|
export class ChatMessage extends BaseModel {
|
||||||
|
@AfterCreate()
|
||||||
|
async publishMessage() {
|
||||||
|
await this.publish('message:created', {
|
||||||
|
roomId: this.roomId,
|
||||||
|
message: this,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Real-time Video**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ **Project Tutorials**
|
||||||
|
|
||||||
|
### 10. Building a Complete Blog Application
|
||||||
|
|
||||||
|
**Duration: 60 minutes**
|
||||||
|
|
||||||
|
Build a full-featured blog application with authentication, posts, and comments.
|
||||||
|
|
||||||
|
**Features Built:**
|
||||||
|
|
||||||
|
- User authentication
|
||||||
|
- Post creation and editing
|
||||||
|
- Comment system
|
||||||
|
- User profiles
|
||||||
|
- Admin dashboard
|
||||||
|
|
||||||
|
**Final Project Structure:**
|
||||||
|
|
||||||
|
```
|
||||||
|
blog-app/
|
||||||
|
├── models/
|
||||||
|
│ ├── User.ts
|
||||||
|
│ ├── Post.ts
|
||||||
|
│ ├── Comment.ts
|
||||||
|
│ └── Category.ts
|
||||||
|
├── services/
|
||||||
|
│ ├── AuthService.ts
|
||||||
|
│ └── BlogService.ts
|
||||||
|
└── app.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Blog Tutorial**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. Creating a Social Media Platform
|
||||||
|
|
||||||
|
**Duration: 90 minutes**
|
||||||
|
|
||||||
|
Build a decentralized social media platform with advanced features.
|
||||||
|
|
||||||
|
**Features Built:**
|
||||||
|
|
||||||
|
- User profiles and following
|
||||||
|
- Feed generation
|
||||||
|
- Real-time messaging
|
||||||
|
- Content moderation
|
||||||
|
- Analytics dashboard
|
||||||
|
|
||||||
|
**Part 1: User System and Profiles** (30 min)
|
||||||
|
[**▶️ Watch Part 1**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
**Part 2: Posts and Feed** (30 min)
|
||||||
|
[**▶️ Watch Part 2**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
**Part 3: Real-time Features** (30 min)
|
||||||
|
[**▶️ Watch Part 3**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 12. E-commerce Platform with Debros Network
|
||||||
|
|
||||||
|
**Duration: 75 minutes**
|
||||||
|
|
||||||
|
Build a decentralized e-commerce platform with product management and orders.
|
||||||
|
|
||||||
|
**Features Built:**
|
||||||
|
|
||||||
|
- Product catalog
|
||||||
|
- Shopping cart
|
||||||
|
- Order processing
|
||||||
|
- Inventory management
|
||||||
|
- Payment integration
|
||||||
|
|
||||||
|
**Part 1: Product Management** (25 min)
|
||||||
|
[**▶️ Watch Part 1**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
**Part 2: Shopping and Orders** (25 min)
|
||||||
|
[**▶️ Watch Part 2**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
**Part 3: Advanced Features** (25 min)
|
||||||
|
[**▶️ Watch Part 3**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 **Development Workflow Series**
|
||||||
|
|
||||||
|
### 13. Testing Strategies for Debros Applications
|
||||||
|
|
||||||
|
**Duration: 35 minutes**
|
||||||
|
|
||||||
|
Comprehensive testing approaches for decentralized applications.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Unit testing models
|
||||||
|
- Integration testing
|
||||||
|
- Mocking strategies
|
||||||
|
- Performance testing
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Testing example from video
|
||||||
|
describe('User Model', () => {
|
||||||
|
it('should create user with valid data', async () => {
|
||||||
|
const user = await User.create({
|
||||||
|
username: 'testuser',
|
||||||
|
email: 'test@example.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(user.id).toBeDefined();
|
||||||
|
expect(user.username).toBe('testuser');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Testing Video**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 14. Deployment and Production Best Practices
|
||||||
|
|
||||||
|
**Duration: 45 minutes**
|
||||||
|
|
||||||
|
Deploy your Debros Network applications to production environments.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Production configuration
|
||||||
|
- Docker containers
|
||||||
|
- Monitoring and logging
|
||||||
|
- Performance optimization
|
||||||
|
- Security considerations
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# Docker example from video
|
||||||
|
FROM node:18-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci --only=production
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["npm", "start"]
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Deployment Video**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 15. Performance Optimization Techniques
|
||||||
|
|
||||||
|
**Duration: 40 minutes**
|
||||||
|
|
||||||
|
Advanced techniques for optimizing Debros Network applications.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Query optimization
|
||||||
|
- Caching strategies
|
||||||
|
- Database indexing
|
||||||
|
- Memory management
|
||||||
|
- Profiling tools
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Optimization examples from video
|
||||||
|
// Efficient query with proper indexing
|
||||||
|
const optimizedQuery = await User.query()
|
||||||
|
.where('isActive', true) // Indexed field
|
||||||
|
.with(['posts']) // Eager load to avoid N+1
|
||||||
|
.cache(300) // Cache frequently accessed data
|
||||||
|
.limit(50) // Reasonable limits
|
||||||
|
.find();
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Optimization Video**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 **Integration Series**
|
||||||
|
|
||||||
|
### 16. Frontend Integration with React
|
||||||
|
|
||||||
|
**Duration: 50 minutes**
|
||||||
|
|
||||||
|
Integrate Debros Network with React applications for full-stack development.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- React hooks for Debros
|
||||||
|
- State management
|
||||||
|
- Real-time updates
|
||||||
|
- Error boundaries
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// React integration example from video
|
||||||
|
import { useDebrosQuery, useDebrosModel } from '@debros/react';
|
||||||
|
|
||||||
|
function UserProfile({ userId }: { userId: string }) {
|
||||||
|
const { user, loading, error } = useDebrosModel(User, userId, {
|
||||||
|
with: ['posts', 'profile']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading) return <div>Loading...</div>;
|
||||||
|
if (error) return <div>Error: {error.message}</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>{user.username}</h1>
|
||||||
|
<p>Posts: {user.posts.length}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch React Integration**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 17. Building APIs with Express and Debros
|
||||||
|
|
||||||
|
**Duration: 35 minutes**
|
||||||
|
|
||||||
|
Create REST APIs using Express.js with Debros Network as the backend.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Express middleware
|
||||||
|
- API route design
|
||||||
|
- Authentication
|
||||||
|
- Error handling
|
||||||
|
- API documentation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Express API example from video
|
||||||
|
app.get('/api/users/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const user = await User.findById(req.params.id, {
|
||||||
|
with: ['posts'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ error: 'User not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(user);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
[**▶️ Watch Express Integration**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 18. Mobile Development with React Native
|
||||||
|
|
||||||
|
**Duration: 45 minutes**
|
||||||
|
|
||||||
|
Build mobile applications using React Native and Debros Network.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- React Native setup
|
||||||
|
- Offline synchronization
|
||||||
|
- Mobile-specific optimizations
|
||||||
|
- Push notifications
|
||||||
|
|
||||||
|
[**▶️ Watch Mobile Development**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 **Masterclass Series**
|
||||||
|
|
||||||
|
### 19. Architecture Patterns and Best Practices
|
||||||
|
|
||||||
|
**Duration: 60 minutes**
|
||||||
|
|
||||||
|
Advanced architectural patterns for large-scale Debros Network applications.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Domain-driven design
|
||||||
|
- CQRS patterns
|
||||||
|
- Event sourcing
|
||||||
|
- Microservices architecture
|
||||||
|
|
||||||
|
[**▶️ Watch Architecture Masterclass**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 20. Contributing to Debros Network
|
||||||
|
|
||||||
|
**Duration: 30 minutes**
|
||||||
|
|
||||||
|
Learn how to contribute to the Debros Network open-source project.
|
||||||
|
|
||||||
|
**Topics Covered:**
|
||||||
|
|
||||||
|
- Development setup
|
||||||
|
- Code standards
|
||||||
|
- Testing requirements
|
||||||
|
- Pull request process
|
||||||
|
|
||||||
|
[**▶️ Watch Contributing Guide**](https://youtube.com/watch?v=VIDEO_ID_HERE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 **Additional Resources**
|
||||||
|
|
||||||
|
### Video Playlists
|
||||||
|
|
||||||
|
**🎬 [Complete Beginner Series](https://youtube.com/playlist?list=PLAYLIST_ID)**
|
||||||
|
Videos 1-6: Everything you need to get started
|
||||||
|
|
||||||
|
**🏗️ [Advanced Development](https://youtube.com/playlist?list=PLAYLIST_ID)**
|
||||||
|
Videos 7-12: Advanced features and patterns
|
||||||
|
|
||||||
|
**🛠️ [Project Tutorials](https://youtube.com/playlist?list=PLAYLIST_ID)**
|
||||||
|
Videos 10-12: Complete project walkthroughs
|
||||||
|
|
||||||
|
**🔧 [Production Ready](https://youtube.com/playlist?list=PLAYLIST_ID)**
|
||||||
|
Videos 13-15: Testing, deployment, and optimization
|
||||||
|
|
||||||
|
### Community Videos
|
||||||
|
|
||||||
|
**Community Showcase**
|
||||||
|
|
||||||
|
- [Building a Decentralized Chat App](https://youtube.com/watch?v=COMMUNITY_VIDEO_1)
|
||||||
|
- [E-learning Platform with Debros](https://youtube.com/watch?v=COMMUNITY_VIDEO_2)
|
||||||
|
- [IoT Data Management](https://youtube.com/watch?v=COMMUNITY_VIDEO_3)
|
||||||
|
|
||||||
|
### Interactive Learning
|
||||||
|
|
||||||
|
**🎮 [Interactive Tutorials](https://learn.debros.io)**
|
||||||
|
Hands-on coding exercises with instant feedback
|
||||||
|
|
||||||
|
**💬 [Discord Community](https://discord.gg/debros)**
|
||||||
|
Get help and discuss videos with other developers
|
||||||
|
|
||||||
|
**📖 [Workshop Materials](https://github.com/debros/workshops)**
|
||||||
|
Download code samples and workshop materials
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 **Release Schedule**
|
||||||
|
|
||||||
|
New video tutorials are released every **Tuesday and Friday**:
|
||||||
|
|
||||||
|
- **Tuesdays**: Core concepts and feature deep-dives
|
||||||
|
- **Fridays**: Project tutorials and real-world applications
|
||||||
|
|
||||||
|
**Subscribe to our [YouTube channel](https://youtube.com/@debrosnetwork)** and enable notifications to stay updated with the latest tutorials.
|
||||||
|
|
||||||
|
## 🤝 **Request a Tutorial**
|
||||||
|
|
||||||
|
Have a specific topic you'd like covered? Request a tutorial:
|
||||||
|
|
||||||
|
- **[GitHub Discussions](https://github.com/debros/network/discussions)** - Community requests
|
||||||
|
- **[Email Us](mailto:tutorials@debros.io)** - Direct requests
|
||||||
|
- **[Discord](https://discord.gg/debros)** - Community suggestions
|
||||||
|
|
||||||
|
We prioritize tutorials based on community demand and framework updates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_All videos include closed captions, downloadable code samples, and companion blog posts for reference._
|
@ -1,12 +1,12 @@
|
|||||||
import {themes as prismThemes} from 'prism-react-renderer';
|
import { themes as prismThemes } from 'prism-react-renderer';
|
||||||
import type {Config} from '@docusaurus/types';
|
import type { Config } from '@docusaurus/types';
|
||||||
import type * as Preset from '@docusaurus/preset-classic';
|
import type * as Preset from '@docusaurus/preset-classic';
|
||||||
|
|
||||||
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
title: 'DebrosFramework',
|
title: 'Debros Network',
|
||||||
tagline: 'Build scalable decentralized applications with ease',
|
tagline: 'Next-Generation Decentralized Web Framework',
|
||||||
favicon: 'img/favicon.ico',
|
favicon: 'img/favicon.ico',
|
||||||
|
|
||||||
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
|
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
|
||||||
@ -71,13 +71,20 @@ const config: Config = {
|
|||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
// Replace with your project's social card
|
// Replace with your project's social card
|
||||||
image: 'img/docusaurus-social-card.jpg',
|
image: 'img/debros-social-card.jpg',
|
||||||
|
colorMode: {
|
||||||
|
defaultMode: 'dark',
|
||||||
|
disableSwitch: false,
|
||||||
|
respectPrefersColorScheme: false,
|
||||||
|
},
|
||||||
navbar: {
|
navbar: {
|
||||||
title: 'DebrosFramework',
|
title: 'Debros Network',
|
||||||
logo: {
|
// logo: {
|
||||||
alt: 'DebrosFramework Logo',
|
// alt: 'Debros Network Logo',
|
||||||
src: 'img/logo.svg',
|
// src: 'img/logo.svg',
|
||||||
},
|
// srcDark: 'img/logo-dark.svg',
|
||||||
|
// },
|
||||||
|
hideOnScroll: true,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'docSidebar',
|
type: 'docSidebar',
|
||||||
@ -91,10 +98,14 @@ const config: Config = {
|
|||||||
position: 'left',
|
position: 'left',
|
||||||
label: 'API Reference',
|
label: 'API Reference',
|
||||||
},
|
},
|
||||||
{to: '/blog', label: 'Blog', position: 'left'},
|
{
|
||||||
|
href: 'https://git.debros.io/DeBros/network',
|
||||||
|
label: 'Gitea',
|
||||||
|
position: 'right',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
href: 'https://github.com/debros/network',
|
href: 'https://github.com/debros/network',
|
||||||
label: 'GitHub',
|
label: 'GitHub Mirror',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -103,11 +114,23 @@ const config: Config = {
|
|||||||
style: 'dark',
|
style: 'dark',
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
title: 'Docs',
|
title: 'Documentation',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Tutorial',
|
label: 'Getting Started',
|
||||||
to: '/docs/intro',
|
to: '/docs/getting-started',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Core Concepts',
|
||||||
|
to: '/docs/core-concepts/architecture',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'API Reference',
|
||||||
|
to: '/docs/api/overview',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Contributing',
|
||||||
|
to: '/docs/contributing/overview',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -115,34 +138,34 @@ const config: Config = {
|
|||||||
title: 'Community',
|
title: 'Community',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Stack Overflow',
|
label: 'Gitea Repository',
|
||||||
href: 'https://stackoverflow.com/questions/tagged/docusaurus',
|
href: 'https://git.debros.io/DeBros/network',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Discord',
|
label: 'GitHub Mirror',
|
||||||
href: 'https://discordapp.com/invite/docusaurus',
|
href: 'https://github.com/debros/network',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'X',
|
label: 'Issues & Support',
|
||||||
href: 'https://x.com/docusaurus',
|
href: 'https://git.debros.io/DeBros/network/issues',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'More',
|
title: 'Resources',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Blog',
|
label: 'Examples',
|
||||||
to: '/blog',
|
to: '/docs/examples/basic-usage',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'GitHub',
|
label: 'License',
|
||||||
href: 'https://github.com/facebook/docusaurus',
|
href: 'https://git.debros.io/DeBros/network/src/branch/main/LICENSE',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
copyright: `Copyright © ${new Date().getFullYear()} DebrosFramework. Built with Docusaurus.`,
|
copyright: `Copyright © ${new Date().getFullYear()} Debros Network. Empowering the decentralized web.`,
|
||||||
},
|
},
|
||||||
prism: {
|
prism: {
|
||||||
theme: prismThemes.github,
|
theme: prismThemes.github,
|
||||||
|
@ -40,6 +40,22 @@ const sidebars: SidebarsConfig = {
|
|||||||
'examples/basic-usage',
|
'examples/basic-usage',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Video Tutorials',
|
||||||
|
items: [
|
||||||
|
'videos/index',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Contributing',
|
||||||
|
items: [
|
||||||
|
'contributing/overview',
|
||||||
|
'contributing/development-setup',
|
||||||
|
'contributing/code-guidelines',
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
// API Reference sidebar
|
// API Reference sidebar
|
||||||
@ -47,9 +63,18 @@ const sidebars: SidebarsConfig = {
|
|||||||
'api/overview',
|
'api/overview',
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: 'category',
|
||||||
label: 'Framework Classes',
|
label: 'Core Classes',
|
||||||
items: [
|
items: [
|
||||||
'api/debros-framework',
|
'api/debros-framework',
|
||||||
|
'api/base-model',
|
||||||
|
'api/query-builder',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Network API',
|
||||||
|
items: [
|
||||||
|
'api/network-api',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -5,52 +5,86 @@ import styles from './styles.module.css';
|
|||||||
|
|
||||||
type FeatureItem = {
|
type FeatureItem = {
|
||||||
title: string;
|
title: string;
|
||||||
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
|
icon: string;
|
||||||
description: ReactNode;
|
description: ReactNode;
|
||||||
|
highlight?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FeatureList: FeatureItem[] = [
|
const FeatureList: FeatureItem[] = [
|
||||||
{
|
{
|
||||||
title: 'Easy to Use',
|
title: 'Decentralized by Design',
|
||||||
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
|
icon: '🌐',
|
||||||
description: (
|
description: (
|
||||||
<>
|
<>
|
||||||
Docusaurus was designed from the ground up to be easily installed and
|
Built on <strong>OrbitDB</strong> and <strong>IPFS</strong>, DebrosFramework
|
||||||
used to get your website up and running quickly.
|
creates truly decentralized applications that don't rely on centralized servers
|
||||||
|
or single points of failure.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
highlight: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Developer Experience First',
|
||||||
|
icon: '⚡',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Full <strong>TypeScript</strong> support with intelligent auto-completion,
|
||||||
|
decorator-based models, and intuitive APIs that make building complex
|
||||||
|
applications feel effortless.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Focus on What Matters',
|
title: 'Infinite Scalability',
|
||||||
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
|
icon: '🚀',
|
||||||
description: (
|
description: (
|
||||||
<>
|
<>
|
||||||
Docusaurus lets you focus on your docs, and we'll do the chores. Go
|
Automatic sharding, efficient queries, and built-in caching ensure your
|
||||||
ahead and move your docs into the <code>docs</code> directory.
|
applications can scale to millions of users without architectural changes.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Powered by React',
|
title: 'Zero Configuration',
|
||||||
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
|
icon: '🎯',
|
||||||
description: (
|
description: (
|
||||||
<>
|
<>
|
||||||
Extend or customize your website layout by reusing React. Docusaurus can
|
Start building immediately with sensible defaults. No complex setup,
|
||||||
be extended while reusing the same header and footer.
|
no configuration files, no DevOps headaches—just pure development focus.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Real-time Sync',
|
||||||
|
icon: '🔄',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Built-in real-time synchronization across all peers. Changes propagate
|
||||||
|
instantly across the network with conflict resolution and offline support.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Enterprise Ready',
|
||||||
|
icon: '🔒',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Production-grade security, comprehensive testing, detailed documentation,
|
||||||
|
and enterprise support make DebrosFramework ready for mission-critical applications.
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function Feature({title, Svg, description}: FeatureItem) {
|
function Feature({title, icon, description, highlight}: FeatureItem) {
|
||||||
return (
|
return (
|
||||||
<div className={clsx('col col--4')}>
|
<div className={clsx(styles.featureCard, highlight && styles.featureCardHighlight)}>
|
||||||
<div className="text--center">
|
<div className={styles.featureIcon}>
|
||||||
<Svg className={styles.featureSvg} role="img" />
|
<span className={styles.iconEmoji}>{icon}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text--center padding-horiz--md">
|
<div className={styles.featureContent}>
|
||||||
<Heading as="h3">{title}</Heading>
|
<Heading as="h3" className={styles.featureTitle}>{title}</Heading>
|
||||||
<p>{description}</p>
|
<p className={styles.featureDescription}>{description}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -60,7 +94,15 @@ export default function HomepageFeatures(): ReactNode {
|
|||||||
return (
|
return (
|
||||||
<section className={styles.features}>
|
<section className={styles.features}>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className={styles.featuresHeader}>
|
||||||
|
<Heading as="h2" className={styles.featuresTitle}>
|
||||||
|
Why Choose Debros Network?
|
||||||
|
</Heading>
|
||||||
|
<p className={styles.featuresSubtitle}>
|
||||||
|
Everything you need to build the next generation of decentralized applications
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.featuresGrid}>
|
||||||
{FeatureList.map((props, idx) => (
|
{FeatureList.map((props, idx) => (
|
||||||
<Feature key={idx} {...props} />
|
<Feature key={idx} {...props} />
|
||||||
))}
|
))}
|
||||||
|
@ -1,11 +1,191 @@
|
|||||||
|
/* Features Section */
|
||||||
.features {
|
.features {
|
||||||
display: flex;
|
padding: 6rem 0;
|
||||||
align-items: center;
|
background: var(--ifm-background-color);
|
||||||
padding: 2rem 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.featureSvg {
|
.featuresHeader {
|
||||||
height: 200px;
|
text-align: center;
|
||||||
width: 200px;
|
margin-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuresTitle {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
background: var(--debros-gradient-secondary);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuresSubtitle {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--ifm-color-content-secondary);
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuresGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureCard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
padding: 2.5rem;
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.1);
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureCard::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: var(--debros-gradient-secondary);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureCard:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
box-shadow: 0 20px 40px rgba(139, 92, 246, 0.15);
|
||||||
|
border-color: rgba(139, 92, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureCard:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureCardHighlight {
|
||||||
|
background: linear-gradient(135deg, rgba(139, 92, 246, 0.05) 0%, rgba(59, 130, 246, 0.05) 100%);
|
||||||
|
border-color: rgba(139, 92, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureCardHighlight::before {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureIcon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
background: rgba(139, 92, 246, 0.1);
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.2);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureIcon::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: var(--debros-gradient-secondary);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureCard:hover .featureIcon::before {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconEmoji {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureContent {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureTitle {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--ifm-color-content);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureDescription {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--ifm-color-content-secondary);
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureDescription strong {
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media screen and (max-width: 996px) {
|
||||||
|
.features {
|
||||||
|
padding: 4rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuresTitle {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuresSubtitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuresGrid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureCard {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureIcon {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconEmoji {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureTitle {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 576px) {
|
||||||
|
.featuresGrid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureCard {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuresTitle {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,260 @@
|
|||||||
/**
|
/**
|
||||||
* Any CSS included here will be global. The classic template
|
* DebrosFramework - Futuristic Dark Theme
|
||||||
* bundles Infima by default. Infima is a CSS framework designed to
|
* Custom styling for next-generation decentralized web framework
|
||||||
* work well for content-centric websites.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* You can override the default Infima variables here. */
|
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
|
||||||
|
|
||||||
|
/* Custom CSS Variables for DebrosFramework */
|
||||||
:root {
|
:root {
|
||||||
--ifm-color-primary: #2e8555;
|
/* Primary colors - Cyber Purple/Blue gradient */
|
||||||
--ifm-color-primary-dark: #29784c;
|
--ifm-color-primary: #7c3aed;
|
||||||
--ifm-color-primary-darker: #277148;
|
--ifm-color-primary-dark: #6d28d9;
|
||||||
--ifm-color-primary-darkest: #205d3b;
|
--ifm-color-primary-darker: #5b21b6;
|
||||||
--ifm-color-primary-light: #33925d;
|
--ifm-color-primary-darkest: #4c1d95;
|
||||||
--ifm-color-primary-lighter: #359962;
|
--ifm-color-primary-light: #8b5cf6;
|
||||||
--ifm-color-primary-lightest: #3cad6e;
|
--ifm-color-primary-lighter: #a78bfa;
|
||||||
|
--ifm-color-primary-lightest: #c4b5fd;
|
||||||
|
|
||||||
|
/* Accent colors */
|
||||||
|
--debros-accent-cyan: #06b6d4;
|
||||||
|
--debros-accent-emerald: #10b981;
|
||||||
|
--debros-accent-rose: #f43f5e;
|
||||||
|
--debros-accent-amber: #f59e0b;
|
||||||
|
|
||||||
|
/* Background gradients */
|
||||||
|
--debros-gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
--debros-gradient-secondary: linear-gradient(135deg, #7c3aed 0%, #3b82f6 50%, #06b6d4 100%);
|
||||||
|
--debros-gradient-hero: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%);
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--ifm-font-family-base: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
--ifm-font-family-monospace: 'JetBrains Mono', SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
--ifm-code-font-size: 95%;
|
--ifm-code-font-size: 95%;
|
||||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
|
||||||
|
/* Light theme overrides */
|
||||||
|
--ifm-background-color: #ffffff;
|
||||||
|
--ifm-background-surface-color: #f8fafc;
|
||||||
|
--docusaurus-highlighted-code-line-bg: rgba(124, 58, 237, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
/* Dark theme - Default for DebrosFramework */
|
||||||
[data-theme='dark'] {
|
[data-theme='dark'] {
|
||||||
--ifm-color-primary: #25c2a0;
|
/* Primary colors for dark theme */
|
||||||
--ifm-color-primary-dark: #21af90;
|
--ifm-color-primary: #8b5cf6;
|
||||||
--ifm-color-primary-darker: #1fa588;
|
--ifm-color-primary-dark: #7c3aed;
|
||||||
--ifm-color-primary-darkest: #1a8870;
|
--ifm-color-primary-darker: #6d28d9;
|
||||||
--ifm-color-primary-light: #29d5b0;
|
--ifm-color-primary-darkest: #5b21b6;
|
||||||
--ifm-color-primary-lighter: #32d8b4;
|
--ifm-color-primary-light: #a78bfa;
|
||||||
--ifm-color-primary-lightest: #4fddbf;
|
--ifm-color-primary-lighter: #c4b5fd;
|
||||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
--ifm-color-primary-lightest: #ddd6fe;
|
||||||
|
|
||||||
|
/* Dark background system */
|
||||||
|
--ifm-background-color: #0a0a0f;
|
||||||
|
--ifm-background-surface-color: #111827;
|
||||||
|
--ifm-color-content: #e5e7eb;
|
||||||
|
--ifm-color-content-secondary: #9ca3af;
|
||||||
|
|
||||||
|
/* Navbar dark styling */
|
||||||
|
--ifm-navbar-background-color: rgba(17, 24, 39, 0.95);
|
||||||
|
--ifm-navbar-shadow: 0 4px 28px rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
|
/* Sidebar dark styling */
|
||||||
|
--ifm-menu-color: #d1d5db;
|
||||||
|
--ifm-menu-color-background-active: rgba(139, 92, 246, 0.1);
|
||||||
|
--ifm-menu-color-background-hover: rgba(139, 92, 246, 0.05);
|
||||||
|
|
||||||
|
/* Code highlighting */
|
||||||
|
--docusaurus-highlighted-code-line-bg: rgba(139, 92, 246, 0.2);
|
||||||
|
|
||||||
|
/* Footer dark styling */
|
||||||
|
--ifm-footer-background-color: #030712;
|
||||||
|
--ifm-footer-color: #9ca3af;
|
||||||
|
--ifm-footer-link-color: #d1d5db;
|
||||||
|
--ifm-footer-title-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global Typography Enhancements */
|
||||||
|
body {
|
||||||
|
font-feature-settings: 'liga', 'kern';
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar Styling */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--ifm-color-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--ifm-color-primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navbar Enhancements */
|
||||||
|
.navbar {
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-bottom: 1px solid rgba(139, 92, 246, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar__title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
background: var(--debros-gradient-secondary);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar__item {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar__link:hover {
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Enhancements */
|
||||||
|
.button {
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-family: var(--ifm-font-family-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--primary {
|
||||||
|
background: var(--debros-gradient-secondary);
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 4px 14px rgba(139, 92, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(139, 92, 246, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--secondary {
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid var(--ifm-color-primary);
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--secondary:hover {
|
||||||
|
background: var(--ifm-color-primary);
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code Block Enhancements */
|
||||||
|
.prism-code {
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.2);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
background: rgba(139, 92, 246, 0.1);
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.2);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Enhancements */
|
||||||
|
.table {
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead tr {
|
||||||
|
background: rgba(139, 92, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Components */
|
||||||
|
.card {
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.1);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 12px 32px rgba(139, 92, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glow Effects */
|
||||||
|
.glow {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
right: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
background: var(--debros-gradient-secondary);
|
||||||
|
border-radius: inherit;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow:hover::before {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media screen and (max-width: 996px) {
|
||||||
|
.navbar__title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation Utilities */
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-up {
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-pulse {
|
||||||
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,449 @@
|
|||||||
/**
|
/**
|
||||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
* DebrosFramework Landing Page Styles
|
||||||
* and scoped locally.
|
* Futuristic design with cyber-aesthetic
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.heroBanner {
|
/* Hero Section */
|
||||||
padding: 4rem 0;
|
.hero {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--debros-gradient-hero);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroBackground {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridPattern {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(139, 92, 246, 0.1) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(139, 92, 246, 0.1) 1px, transparent 1px);
|
||||||
|
background-size: 50px 50px;
|
||||||
|
animation: gridMove 20s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gridMove {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(50px, 50px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.glowOrb1,
|
||||||
|
.glowOrb2,
|
||||||
|
.glowOrb3 {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(60px);
|
||||||
|
animation: float 8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glowOrb1 {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
background: radial-gradient(circle, rgba(124, 58, 237, 0.3) 0%, transparent 70%);
|
||||||
|
top: 10%;
|
||||||
|
left: 10%;
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glowOrb2 {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
background: radial-gradient(circle, rgba(59, 130, 246, 0.2) 0%, transparent 70%);
|
||||||
|
top: 60%;
|
||||||
|
right: 20%;
|
||||||
|
animation-delay: -3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glowOrb3 {
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
background: radial-gradient(circle, rgba(6, 182, 212, 0.2) 0%, transparent 70%);
|
||||||
|
bottom: 20%;
|
||||||
|
left: 50%;
|
||||||
|
animation-delay: -6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0px) rotate(0deg);
|
||||||
|
}
|
||||||
|
33% {
|
||||||
|
transform: translateY(-20px) rotate(120deg);
|
||||||
|
}
|
||||||
|
66% {
|
||||||
|
transform: translateY(10px) rotate(240deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroContent {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badgeText {
|
||||||
|
background: rgba(139, 92, 246, 0.1);
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.3);
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroTitle {
|
||||||
|
font-size: 4rem;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
animation: fadeInUp 0.6s ease-out 0.2s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleGradient {
|
||||||
|
background: var(--debros-gradient-secondary);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroSubtitle {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
animation: fadeInUp 0.6s ease-out 0.4s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroDescription {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--ifm-color-content-secondary);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
animation: fadeInUp 0.6s ease-out 0.6s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroButtons {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
animation: fadeInUp 0.6s ease-out 0.8s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primaryButton {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 996px) {
|
.primaryButton::before {
|
||||||
.heroBanner {
|
content: '';
|
||||||
padding: 2rem;
|
position: absolute;
|
||||||
}
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||||
|
transition: left 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
.primaryButton:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonIcon {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primaryButton:hover .buttonIcon {
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondaryButton {
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickStart {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
animation: fadeInUp 0.6s ease-out 1s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickStartLabel {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--ifm-color-content-secondary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeSnippet {
|
||||||
|
background: rgba(139, 92, 246, 0.1);
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.3);
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: var(--ifm-font-family-monospace);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats Section */
|
||||||
|
.stats {
|
||||||
|
padding: 4rem 0;
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
border-top: 1px solid rgba(139, 92, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Video Section */
|
||||||
|
.videoSection {
|
||||||
|
padding: 6rem 0;
|
||||||
|
background: var(--ifm-background-color);
|
||||||
|
border-top: 1px solid rgba(139, 92, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoHeader {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoTitle {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
background: var(--debros-gradient-secondary);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoSubtitle {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--ifm-color-content-secondary);
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoContainer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr;
|
||||||
|
gap: 3rem;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoWrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 56.25%; /* 16:9 aspect ratio */
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.2);
|
||||||
|
box-shadow: 0 8px 32px rgba(139, 92, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoEmbed {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoFeatures {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoFeature {
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.1);
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoFeature:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 24px rgba(139, 92, 246, 0.15);
|
||||||
|
border-color: rgba(139, 92, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoFeature strong {
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statsGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statItem {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
background: var(--ifm-background-color);
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.1);
|
||||||
|
border-radius: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statItem::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: var(--debros-gradient-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.statItem:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
box-shadow: 0 16px 40px rgba(139, 92, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.statNumber {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-family: var(--ifm-font-family-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.statLabel {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ifm-color-content-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media screen and (max-width: 996px) {
|
||||||
|
.hero {
|
||||||
|
min-height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroTitle {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroSubtitle {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroDescription {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heroButtons {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quickStart {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statsGrid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoSection {
|
||||||
|
padding: 4rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoTitle {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoSubtitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoContainer {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glowOrb1,
|
||||||
|
.glowOrb2,
|
||||||
|
.glowOrb3 {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 576px) {
|
||||||
|
.heroTitle {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statsGrid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statItem {
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statNumber {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoTitle {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoFeatures {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoFeature {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,24 +8,128 @@ import Heading from '@theme/Heading';
|
|||||||
|
|
||||||
import styles from './index.module.css';
|
import styles from './index.module.css';
|
||||||
|
|
||||||
function HomepageHeader() {
|
function HeroSection() {
|
||||||
const {siteConfig} = useDocusaurusContext();
|
const {siteConfig} = useDocusaurusContext();
|
||||||
return (
|
return (
|
||||||
<header className={clsx('hero hero--primary', styles.heroBanner)}>
|
<section className={styles.hero}>
|
||||||
|
<div className={styles.heroBackground}>
|
||||||
|
<div className={styles.gridPattern}></div>
|
||||||
|
<div className={styles.glowOrb1}></div>
|
||||||
|
<div className={styles.glowOrb2}></div>
|
||||||
|
<div className={styles.glowOrb3}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<Heading as="h1" className="hero__title">
|
<div className={styles.heroContent}>
|
||||||
{siteConfig.title}
|
<div className={styles.badge}>
|
||||||
</Heading>
|
<span className={styles.badgeText}>🚀 Beta Release</span>
|
||||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
</div>
|
||||||
<div className={styles.buttons}>
|
|
||||||
<Link
|
<Heading as="h1" className={styles.heroTitle}>
|
||||||
className="button button--secondary button--lg"
|
<span className={styles.titleGradient}>Debros Network</span>
|
||||||
to="/docs/intro">
|
</Heading>
|
||||||
Docusaurus Tutorial - 5min ⏱️
|
|
||||||
</Link>
|
<p className={styles.heroSubtitle}>
|
||||||
|
{siteConfig.tagline}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className={styles.heroDescription}>
|
||||||
|
Build scalable, decentralized applications with ease using our next-generation
|
||||||
|
framework powered by OrbitDB, IPFS, and cutting-edge web technologies.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className={styles.heroButtons}>
|
||||||
|
<Link
|
||||||
|
className={clsx('button button--primary button--lg', styles.primaryButton)}
|
||||||
|
to="/docs/getting-started">
|
||||||
|
Get Started
|
||||||
|
<span className={styles.buttonIcon}>→</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
className={clsx('button button--secondary button--lg', styles.secondaryButton)}
|
||||||
|
to="/docs/intro">
|
||||||
|
View Documentation
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.quickStart}>
|
||||||
|
<span className={styles.quickStartLabel}>Quick Start:</span>
|
||||||
|
<code className={styles.codeSnippet}>
|
||||||
|
npm create debros-app my-app
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatsSection() {
|
||||||
|
return (
|
||||||
|
<section className={styles.stats}>
|
||||||
|
<div className="container">
|
||||||
|
<div className={styles.statsGrid}>
|
||||||
|
<div className={styles.statItem}>
|
||||||
|
<div className={styles.statNumber}>100%</div>
|
||||||
|
<div className={styles.statLabel}>TypeScript</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.statItem}>
|
||||||
|
<div className={styles.statNumber}>Zero</div>
|
||||||
|
<div className={styles.statLabel}>Config</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.statItem}>
|
||||||
|
<div className={styles.statNumber}>∞</div>
|
||||||
|
<div className={styles.statLabel}>Scalability</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.statItem}>
|
||||||
|
<div className={styles.statNumber}>🌐</div>
|
||||||
|
<div className={styles.statLabel}>Decentralized</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function VideoSection() {
|
||||||
|
return (
|
||||||
|
<section className={styles.videoSection}>
|
||||||
|
<div className="container">
|
||||||
|
<div className={styles.videoHeader}>
|
||||||
|
<Heading as="h2" className={styles.videoTitle}>
|
||||||
|
See Debros Network in Action
|
||||||
|
</Heading>
|
||||||
|
<p className={styles.videoSubtitle}>
|
||||||
|
Watch our comprehensive tutorial to learn how to build your first decentralized application
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.videoContainer}>
|
||||||
|
<div className={styles.videoWrapper}>
|
||||||
|
{/* Replace VIDEO_ID_HERE with your actual YouTube video ID */}
|
||||||
|
<iframe
|
||||||
|
className={styles.videoEmbed}
|
||||||
|
src="https://www.youtube.com/embed/VIDEO_ID_HERE?rel=0&modestbranding=1&autohide=1&showinfo=0"
|
||||||
|
title="Debros Network Tutorial"
|
||||||
|
frameBorder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowFullScreen
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
<div className={styles.videoFeatures}>
|
||||||
|
<div className={styles.videoFeature}>
|
||||||
|
🚀 <strong>Quick Start:</strong> Get up and running in under 5 minutes
|
||||||
|
</div>
|
||||||
|
<div className={styles.videoFeature}>
|
||||||
|
📚 <strong>Step-by-Step:</strong> Follow along with detailed explanations
|
||||||
|
</div>
|
||||||
|
<div className={styles.videoFeature}>
|
||||||
|
🔧 <strong>Best Practices:</strong> Learn the recommended patterns and approaches
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,10 +137,12 @@ export default function Home(): ReactNode {
|
|||||||
const {siteConfig} = useDocusaurusContext();
|
const {siteConfig} = useDocusaurusContext();
|
||||||
return (
|
return (
|
||||||
<Layout
|
<Layout
|
||||||
title={`Hello from ${siteConfig.title}`}
|
title={siteConfig.title}
|
||||||
description="Description will go into a meta tag in <head />">
|
description="Next-Generation Decentralized Web Framework - Build scalable, decentralized applications with ease using Debros Network">
|
||||||
<HomepageHeader />
|
<HeroSection />
|
||||||
|
<StatsSection />
|
||||||
<main>
|
<main>
|
||||||
|
<VideoSection />
|
||||||
<HomepageFeatures />
|
<HomepageFeatures />
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
19
jest.integration.config.cjs
Normal file
19
jest.integration.config.cjs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
roots: ['<rootDir>/tests/real-integration'],
|
||||||
|
testMatch: ['**/tests/**/*.test.ts'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.ts$': [
|
||||||
|
'ts-jest',
|
||||||
|
{
|
||||||
|
isolatedModules: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
testTimeout: 120000, // 2 minutes for integration tests
|
||||||
|
verbose: true,
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/tests/real-integration/blog-scenario/tests/setup.ts'],
|
||||||
|
maxWorkers: 1, // Run tests sequentially for integration tests
|
||||||
|
collectCoverage: false, // Skip coverage for integration tests
|
||||||
|
};
|
22
package.json
22
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@debros/network",
|
"name": "@debros/network",
|
||||||
"version": "0.5.0-beta",
|
"version": "0.5.1-beta",
|
||||||
"description": "Debros network core functionality for IPFS, libp2p and OrbitDB",
|
"description": "DebrosFramework - A powerful Node.js framework providing ORM-like abstraction over OrbitDB and IPFS for building scalable decentralized applications",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@ -20,16 +20,23 @@
|
|||||||
"format": "prettier --write \"**/*.{ts,js,json,md}\"",
|
"format": "prettier --write \"**/*.{ts,js,json,md}\"",
|
||||||
"lint:fix": "npx eslint src --fix",
|
"lint:fix": "npx eslint src --fix",
|
||||||
"test:unit": "jest tests/unit",
|
"test:unit": "jest tests/unit",
|
||||||
"test:blog-integration": "tsx tests/real-integration/blog-scenario/scenarios/BlogTestRunner.ts",
|
"test:blog-integration": "jest --config=jest.integration.config.cjs tests/real-integration/blog-scenario/tests",
|
||||||
"test:real": "docker-compose -f tests/real-integration/blog-scenario/docker/docker-compose.blog.yml up --build --abort-on-container-exit"
|
"test:real": "docker-compose -f tests/real-integration/blog-scenario/docker/docker-compose.blog.yml up --build --abort-on-container-exit"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ipfs",
|
"debros",
|
||||||
"libp2p",
|
"framework",
|
||||||
"orbitdb",
|
"orm",
|
||||||
"decentralized",
|
"decentralized",
|
||||||
|
"ipfs",
|
||||||
|
"orbitdb",
|
||||||
"p2p",
|
"p2p",
|
||||||
"debros"
|
"typescript",
|
||||||
|
"models",
|
||||||
|
"query-builder",
|
||||||
|
"sharding",
|
||||||
|
"dapp",
|
||||||
|
"distributed"
|
||||||
],
|
],
|
||||||
"author": "Debros",
|
"author": "Debros",
|
||||||
"license": "gnu-gpl-v3.0",
|
"license": "gnu-gpl-v3.0",
|
||||||
@ -69,6 +76,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"axios": "^1.6.0",
|
||||||
"@eslint/js": "^9.24.0",
|
"@eslint/js": "^9.24.0",
|
||||||
"@jest/globals": "^30.0.1",
|
"@jest/globals": "^30.0.1",
|
||||||
"@orbitdb/core-types": "^1.0.14",
|
"@orbitdb/core-types": "^1.0.14",
|
||||||
|
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -111,6 +111,9 @@ importers:
|
|||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
specifier: ^8.29.0
|
specifier: ^8.29.0
|
||||||
version: 8.29.0(eslint@9.24.0)(typescript@5.8.2)
|
version: 8.29.0(eslint@9.24.0)(typescript@5.8.2)
|
||||||
|
axios:
|
||||||
|
specifier: ^1.6.0
|
||||||
|
version: 1.8.4(debug@4.4.0)
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.24.0
|
specifier: ^9.24.0
|
||||||
version: 9.24.0
|
version: 9.24.0
|
||||||
|
@ -132,7 +132,7 @@ export class DebrosFramework {
|
|||||||
this.status = {
|
this.status = {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
healthy: false,
|
healthy: false,
|
||||||
version: '1.0.0', // This would come from package.json
|
version: '0.5.0-beta', // This would come from package.json
|
||||||
environment: this.config.environment || 'development',
|
environment: this.config.environment || 'development',
|
||||||
services: {
|
services: {
|
||||||
orbitdb: 'disconnected',
|
orbitdb: 'disconnected',
|
||||||
@ -522,9 +522,8 @@ export class DebrosFramework {
|
|||||||
this.status.services.pubsub = this.pubsubManager ? 'active' : 'inactive';
|
this.status.services.pubsub = this.pubsubManager ? 'active' : 'inactive';
|
||||||
|
|
||||||
// Overall health check - only require core services to be healthy
|
// Overall health check - only require core services to be healthy
|
||||||
const coreServicesHealthy =
|
const coreServicesHealthy =
|
||||||
this.status.services.orbitdb === 'connected' &&
|
this.status.services.orbitdb === 'connected' && this.status.services.ipfs === 'connected';
|
||||||
this.status.services.ipfs === 'connected';
|
|
||||||
|
|
||||||
this.status.healthy = this.initialized && coreServicesHealthy;
|
this.status.healthy = this.initialized && coreServicesHealthy;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -616,7 +615,7 @@ export class DebrosFramework {
|
|||||||
return {
|
return {
|
||||||
healthy: this.status.healthy,
|
healthy: this.status.healthy,
|
||||||
services: { ...this.status.services },
|
services: { ...this.status.services },
|
||||||
lastCheck: this.status.lastHealthCheck
|
lastCheck: this.status.lastHealthCheck,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,8 +78,8 @@ export type { FieldConfig, RelationshipConfig, ModelConfig, ValidationError } fr
|
|||||||
// export { ValidationError } from './types/models'; // Already exported above
|
// export { ValidationError } from './types/models'; // Already exported above
|
||||||
|
|
||||||
// Version information
|
// Version information
|
||||||
export const FRAMEWORK_VERSION = '1.0.0';
|
export const FRAMEWORK_VERSION = '0.5.0-beta';
|
||||||
export const API_VERSION = '1.0';
|
export const API_VERSION = '0.5';
|
||||||
|
|
||||||
// Feature flags for conditional exports
|
// Feature flags for conditional exports
|
||||||
export const FEATURES = {
|
export const FEATURES = {
|
||||||
|
@ -22,14 +22,19 @@ export abstract class BaseModel {
|
|||||||
constructor(data: any = {}) {
|
constructor(data: any = {}) {
|
||||||
// Generate ID first
|
// Generate ID first
|
||||||
this.id = this.generateId();
|
this.id = this.generateId();
|
||||||
|
|
||||||
// Apply field defaults first
|
// Apply field defaults first
|
||||||
this.applyFieldDefaults();
|
this.applyFieldDefaults();
|
||||||
|
|
||||||
// Then apply provided data, but only for properties that are explicitly provided
|
// Then apply provided data, but only for properties that are explicitly provided
|
||||||
if (data && typeof data === 'object') {
|
if (data && typeof data === 'object') {
|
||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
if (key !== '_loadedRelations' && key !== '_isDirty' && key !== '_isNew' && data[key] !== undefined) {
|
if (
|
||||||
|
key !== '_loadedRelations' &&
|
||||||
|
key !== '_isDirty' &&
|
||||||
|
key !== '_isNew' &&
|
||||||
|
data[key] !== undefined
|
||||||
|
) {
|
||||||
// Always set directly - the Field decorator's setter will handle validation and transformation
|
// Always set directly - the Field decorator's setter will handle validation and transformation
|
||||||
try {
|
try {
|
||||||
(this as any)[key] = data[key];
|
(this as any)[key] = data[key];
|
||||||
@ -41,28 +46,27 @@ export abstract class BaseModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark as existing if it has an ID in the data
|
// Mark as existing if it has an ID in the data
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
this._isNew = false;
|
this._isNew = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any instance properties that might shadow prototype getters
|
// Remove any instance properties that might shadow prototype getters
|
||||||
this.cleanupShadowingProperties();
|
this.cleanupShadowingProperties();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanupShadowingProperties(): void {
|
private cleanupShadowingProperties(): void {
|
||||||
const modelClass = this.constructor as typeof BaseModel;
|
const modelClass = this.constructor as typeof BaseModel;
|
||||||
|
|
||||||
// For each field, ensure no instance properties are shadowing prototype getters
|
// For each field, ensure no instance properties are shadowing prototype getters
|
||||||
for (const [fieldName] of modelClass.fields) {
|
for (const [fieldName] of modelClass.fields) {
|
||||||
// If there's an instance property, remove it and create a working getter
|
// If there's an instance property, remove it and create a working getter
|
||||||
if (this.hasOwnProperty(fieldName)) {
|
if (this.hasOwnProperty(fieldName)) {
|
||||||
const _oldValue = (this as any)[fieldName];
|
const _oldValue = (this as any)[fieldName];
|
||||||
delete (this as any)[fieldName];
|
delete (this as any)[fieldName];
|
||||||
|
|
||||||
// Define a working getter directly on the instance
|
// Define a working getter directly on the instance
|
||||||
Object.defineProperty(this, fieldName, {
|
Object.defineProperty(this, fieldName, {
|
||||||
get: () => {
|
get: () => {
|
||||||
@ -75,21 +79,20 @@ export abstract class BaseModel {
|
|||||||
this.markFieldAsModified(fieldName);
|
this.markFieldAsModified(fieldName);
|
||||||
},
|
},
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true
|
configurable: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Core CRUD operations
|
// Core CRUD operations
|
||||||
async save(): Promise<this> {
|
async save(): Promise<this> {
|
||||||
if (this._isNew) {
|
if (this._isNew) {
|
||||||
// Clean up any instance properties before hooks run
|
// Clean up any instance properties before hooks run
|
||||||
this.cleanupShadowingProperties();
|
this.cleanupShadowingProperties();
|
||||||
|
|
||||||
await this.beforeCreate();
|
await this.beforeCreate();
|
||||||
|
|
||||||
// Clean up any instance properties created by hooks
|
// Clean up any instance properties created by hooks
|
||||||
this.cleanupShadowingProperties();
|
this.cleanupShadowingProperties();
|
||||||
|
|
||||||
@ -102,11 +105,10 @@ export abstract class BaseModel {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
this.setFieldValue('createdAt', now);
|
this.setFieldValue('createdAt', now);
|
||||||
this.setFieldValue('updatedAt', now);
|
this.setFieldValue('updatedAt', now);
|
||||||
|
|
||||||
// Clean up any additional shadowing properties after setting timestamps
|
// Clean up any additional shadowing properties after setting timestamps
|
||||||
this.cleanupShadowingProperties();
|
this.cleanupShadowingProperties();
|
||||||
|
|
||||||
|
|
||||||
// Validate after all field generation is complete
|
// Validate after all field generation is complete
|
||||||
await this.validate();
|
await this.validate();
|
||||||
|
|
||||||
@ -117,7 +119,7 @@ export abstract class BaseModel {
|
|||||||
this.clearModifications();
|
this.clearModifications();
|
||||||
|
|
||||||
await this.afterCreate();
|
await this.afterCreate();
|
||||||
|
|
||||||
// Clean up any shadowing properties created during save
|
// Clean up any shadowing properties created during save
|
||||||
this.cleanupShadowingProperties();
|
this.cleanupShadowingProperties();
|
||||||
} else if (this._isDirty) {
|
} else if (this._isDirty) {
|
||||||
@ -125,7 +127,7 @@ export abstract class BaseModel {
|
|||||||
|
|
||||||
// Set timestamp using Field setter
|
// Set timestamp using Field setter
|
||||||
this.setFieldValue('updatedAt', Date.now());
|
this.setFieldValue('updatedAt', Date.now());
|
||||||
|
|
||||||
// Validate after hooks have run
|
// Validate after hooks have run
|
||||||
await this.validate();
|
await this.validate();
|
||||||
|
|
||||||
@ -135,7 +137,7 @@ export abstract class BaseModel {
|
|||||||
this.clearModifications();
|
this.clearModifications();
|
||||||
|
|
||||||
await this.afterUpdate();
|
await this.afterUpdate();
|
||||||
|
|
||||||
// Clean up any shadowing properties created during save
|
// Clean up any shadowing properties created during save
|
||||||
this.cleanupShadowingProperties();
|
this.cleanupShadowingProperties();
|
||||||
}
|
}
|
||||||
@ -168,21 +170,32 @@ export abstract class BaseModel {
|
|||||||
try {
|
try {
|
||||||
const modelClass = this as any;
|
const modelClass = this as any;
|
||||||
let data = null;
|
let data = null;
|
||||||
|
|
||||||
if (modelClass.scope === 'user') {
|
if (modelClass.scope === 'user') {
|
||||||
// For user-scoped models, we would need userId - for now, try global
|
// For user-scoped models, we would need userId - for now, try global
|
||||||
const database = await framework.databaseManager?.getGlobalDatabase?.(modelClass.modelName || modelClass.name);
|
const database = await framework.databaseManager?.getGlobalDatabase?.(
|
||||||
|
modelClass.modelName || modelClass.name,
|
||||||
|
);
|
||||||
if (database && framework.databaseManager?.getDocument) {
|
if (database && framework.databaseManager?.getDocument) {
|
||||||
data = await framework.databaseManager.getDocument(database, modelClass.storeType, id);
|
data = await framework.databaseManager.getDocument(database, modelClass.storeType, id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (modelClass.sharding) {
|
if (modelClass.sharding) {
|
||||||
const shard = framework.shardManager?.getShardForKey?.(modelClass.modelName || modelClass.name, id);
|
const shard = framework.shardManager?.getShardForKey?.(
|
||||||
|
modelClass.modelName || modelClass.name,
|
||||||
|
id,
|
||||||
|
);
|
||||||
if (shard && framework.databaseManager?.getDocument) {
|
if (shard && framework.databaseManager?.getDocument) {
|
||||||
data = await framework.databaseManager.getDocument(shard.database, modelClass.storeType, id);
|
data = await framework.databaseManager.getDocument(
|
||||||
|
shard.database,
|
||||||
|
modelClass.storeType,
|
||||||
|
id,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const database = await framework.databaseManager?.getGlobalDatabase?.(modelClass.modelName || modelClass.name);
|
const database = await framework.databaseManager?.getGlobalDatabase?.(
|
||||||
|
modelClass.modelName || modelClass.name,
|
||||||
|
);
|
||||||
if (database && framework.databaseManager?.getDocument) {
|
if (database && framework.databaseManager?.getDocument) {
|
||||||
data = await framework.databaseManager.getDocument(database, modelClass.storeType, id);
|
data = await framework.databaseManager.getDocument(database, modelClass.storeType, id);
|
||||||
}
|
}
|
||||||
@ -195,7 +208,7 @@ export abstract class BaseModel {
|
|||||||
instance.clearModifications();
|
instance.clearModifications();
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to find by ID:', error);
|
console.error('Failed to find by ID:', error);
|
||||||
@ -283,16 +296,22 @@ export abstract class BaseModel {
|
|||||||
criteria: any,
|
criteria: any,
|
||||||
): Promise<T | null> {
|
): Promise<T | null> {
|
||||||
const query = new QueryBuilder<T>(this as any);
|
const query = new QueryBuilder<T>(this as any);
|
||||||
|
|
||||||
// Apply criteria as where clauses
|
// Apply criteria as where clauses
|
||||||
Object.keys(criteria).forEach(key => {
|
Object.keys(criteria).forEach((key) => {
|
||||||
query.where(key, '=', criteria[key]);
|
query.where(key, '=', criteria[key]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const results = await query.limit(1).exec();
|
const results = await query.limit(1).exec();
|
||||||
return results.length > 0 ? results[0] : null;
|
return results.length > 0 ? results[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async count<T extends BaseModel>(
|
||||||
|
this: typeof BaseModel & (new (data?: any) => T),
|
||||||
|
): Promise<number> {
|
||||||
|
return await new QueryBuilder<T>(this as any).count();
|
||||||
|
}
|
||||||
|
|
||||||
// Relationship operations
|
// Relationship operations
|
||||||
async load(relationships: string[]): Promise<this> {
|
async load(relationships: string[]): Promise<this> {
|
||||||
const framework = this.getFrameworkInstance();
|
const framework = this.getFrameworkInstance();
|
||||||
@ -385,6 +404,11 @@ export abstract class BaseModel {
|
|||||||
// Include basic properties
|
// Include basic properties
|
||||||
result.id = this.id;
|
result.id = this.id;
|
||||||
|
|
||||||
|
// For OrbitDB docstore compatibility, also include _id field
|
||||||
|
if (modelClass.storeType === 'docstore') {
|
||||||
|
result._id = this.id;
|
||||||
|
}
|
||||||
|
|
||||||
// Include loaded relations
|
// Include loaded relations
|
||||||
this._loadedRelations.forEach((value, key) => {
|
this._loadedRelations.forEach((value, key) => {
|
||||||
result[key] = value;
|
result[key] = value;
|
||||||
@ -396,7 +420,7 @@ export abstract class BaseModel {
|
|||||||
fromJSON(data: any): this {
|
fromJSON(data: any): this {
|
||||||
if (!data) return this;
|
if (!data) return this;
|
||||||
|
|
||||||
// Set basic properties
|
// Set basic properties
|
||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
if (key !== '_loadedRelations' && key !== '_isDirty' && key !== '_isNew') {
|
if (key !== '_loadedRelations' && key !== '_isDirty' && key !== '_isNew') {
|
||||||
(this as any)[key] = data[key];
|
(this as any)[key] = data[key];
|
||||||
@ -416,12 +440,11 @@ export abstract class BaseModel {
|
|||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
const modelClass = this.constructor as typeof BaseModel;
|
const modelClass = this.constructor as typeof BaseModel;
|
||||||
|
|
||||||
|
|
||||||
// Validate each field using private keys (more reliable)
|
// Validate each field using private keys (more reliable)
|
||||||
for (const [fieldName, fieldConfig] of modelClass.fields) {
|
for (const [fieldName, fieldConfig] of modelClass.fields) {
|
||||||
const privateKey = `_${fieldName}`;
|
const privateKey = `_${fieldName}`;
|
||||||
const value = (this as any)[privateKey];
|
const value = (this as any)[privateKey];
|
||||||
|
|
||||||
const fieldErrors = await this.validateField(fieldName, value, fieldConfig);
|
const fieldErrors = await this.validateField(fieldName, value, fieldConfig);
|
||||||
errors.push(...fieldErrors);
|
errors.push(...fieldErrors);
|
||||||
}
|
}
|
||||||
@ -435,7 +458,11 @@ export abstract class BaseModel {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async validateField(fieldName: string, value: any, config: FieldConfig): Promise<string[]> {
|
private async validateField(
|
||||||
|
fieldName: string,
|
||||||
|
value: any,
|
||||||
|
config: FieldConfig,
|
||||||
|
): Promise<string[]> {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
// Required validation
|
// Required validation
|
||||||
@ -544,18 +571,18 @@ export abstract class BaseModel {
|
|||||||
|
|
||||||
private applyFieldDefaults(): void {
|
private applyFieldDefaults(): void {
|
||||||
const modelClass = this.constructor as typeof BaseModel;
|
const modelClass = this.constructor as typeof BaseModel;
|
||||||
|
|
||||||
// Ensure we have fields map
|
// Ensure we have fields map
|
||||||
if (!modelClass.fields) {
|
if (!modelClass.fields) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [fieldName, fieldConfig] of modelClass.fields) {
|
for (const [fieldName, fieldConfig] of modelClass.fields) {
|
||||||
if (fieldConfig.default !== undefined) {
|
if (fieldConfig.default !== undefined) {
|
||||||
const privateKey = `_${fieldName}`;
|
const privateKey = `_${fieldName}`;
|
||||||
const hasProperty = (this as any).hasOwnProperty(privateKey);
|
const hasProperty = (this as any).hasOwnProperty(privateKey);
|
||||||
const currentValue = (this as any)[privateKey];
|
const currentValue = (this as any)[privateKey];
|
||||||
|
|
||||||
// Always apply default value to private field if it's not set
|
// Always apply default value to private field if it's not set
|
||||||
if (!hasProperty || currentValue === undefined) {
|
if (!hasProperty || currentValue === undefined) {
|
||||||
// Apply default value to private field
|
// Apply default value to private field
|
||||||
@ -594,16 +621,29 @@ export abstract class BaseModel {
|
|||||||
getFieldValue(fieldName: string): any {
|
getFieldValue(fieldName: string): any {
|
||||||
// Always ensure this field's getter works properly
|
// Always ensure this field's getter works properly
|
||||||
this.ensureFieldGetter(fieldName);
|
this.ensureFieldGetter(fieldName);
|
||||||
|
|
||||||
|
// Try private key first
|
||||||
const privateKey = `_${fieldName}`;
|
const privateKey = `_${fieldName}`;
|
||||||
return (this as any)[privateKey];
|
let value = (this as any)[privateKey];
|
||||||
|
|
||||||
|
// If private key is undefined, try the property getter as fallback
|
||||||
|
if (value === undefined) {
|
||||||
|
try {
|
||||||
|
value = (this as any)[fieldName];
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to access field ${fieldName} using getter:`, error);
|
||||||
|
// Ignore errors from getter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureFieldGetter(fieldName: string): void {
|
private ensureFieldGetter(fieldName: string): void {
|
||||||
// If there's a shadowing instance property, remove it and create a working getter
|
// If there's a shadowing instance property, remove it and create a working getter
|
||||||
if (this.hasOwnProperty(fieldName)) {
|
if (this.hasOwnProperty(fieldName)) {
|
||||||
delete (this as any)[fieldName];
|
delete (this as any)[fieldName];
|
||||||
|
|
||||||
// Define a working getter directly on the instance
|
// Define a working getter directly on the instance
|
||||||
Object.defineProperty(this, fieldName, {
|
Object.defineProperty(this, fieldName, {
|
||||||
get: () => {
|
get: () => {
|
||||||
@ -616,7 +656,7 @@ export abstract class BaseModel {
|
|||||||
this.markFieldAsModified(fieldName);
|
this.markFieldAsModified(fieldName);
|
||||||
},
|
},
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true
|
configurable: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -636,14 +676,14 @@ export abstract class BaseModel {
|
|||||||
getAllFieldValues(): Record<string, any> {
|
getAllFieldValues(): Record<string, any> {
|
||||||
const modelClass = this.constructor as typeof BaseModel;
|
const modelClass = this.constructor as typeof BaseModel;
|
||||||
const values: Record<string, any> = {};
|
const values: Record<string, any> = {};
|
||||||
|
|
||||||
for (const [fieldName] of modelClass.fields) {
|
for (const [fieldName] of modelClass.fields) {
|
||||||
const value = this.getFieldValue(fieldName);
|
const value = this.getFieldValue(fieldName);
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
values[fieldName] = value;
|
values[fieldName] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -676,17 +716,20 @@ export abstract class BaseModel {
|
|||||||
try {
|
try {
|
||||||
if (modelClass.scope === 'user') {
|
if (modelClass.scope === 'user') {
|
||||||
// For user-scoped models, we need a userId (check common field names)
|
// For user-scoped models, we need a userId (check common field names)
|
||||||
const userId = (this as any).userId || (this as any).authorId || (this as any).ownerId;
|
const userId =
|
||||||
|
this.getFieldValue('userId') ||
|
||||||
|
this.getFieldValue('authorId') ||
|
||||||
|
this.getFieldValue('ownerId');
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
|
throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure user databases exist before accessing them
|
// Ensure user databases exist before accessing them
|
||||||
await this.ensureUserDatabasesExist(framework, userId);
|
await this.ensureUserDatabasesExist(framework, userId);
|
||||||
|
|
||||||
// Ensure user databases exist before accessing them
|
// Ensure user databases exist before accessing them
|
||||||
await this.ensureUserDatabasesExist(framework, userId);
|
await this.ensureUserDatabasesExist(framework, userId);
|
||||||
|
|
||||||
const database = await framework.databaseManager.getUserDatabase(
|
const database = await framework.databaseManager.getUserDatabase(
|
||||||
userId,
|
userId,
|
||||||
modelClass.modelName,
|
modelClass.modelName,
|
||||||
@ -705,7 +748,11 @@ export abstract class BaseModel {
|
|||||||
} else {
|
} else {
|
||||||
// Use single global database
|
// Use single global database
|
||||||
const database = await framework.databaseManager.getGlobalDatabase(modelClass.modelName);
|
const database = await framework.databaseManager.getGlobalDatabase(modelClass.modelName);
|
||||||
await framework.databaseManager.addDocument(database, modelClass.storeType, this.toJSON());
|
await framework.databaseManager.addDocument(
|
||||||
|
database,
|
||||||
|
modelClass.storeType,
|
||||||
|
this.toJSON(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -725,14 +772,17 @@ export abstract class BaseModel {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (modelClass.scope === 'user') {
|
if (modelClass.scope === 'user') {
|
||||||
const userId = (this as any).userId || (this as any).authorId || (this as any).ownerId;
|
const userId =
|
||||||
|
this.getFieldValue('userId') ||
|
||||||
|
this.getFieldValue('authorId') ||
|
||||||
|
this.getFieldValue('ownerId');
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
|
throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure user databases exist before accessing them
|
// Ensure user databases exist before accessing them
|
||||||
await this.ensureUserDatabasesExist(framework, userId);
|
await this.ensureUserDatabasesExist(framework, userId);
|
||||||
|
|
||||||
const database = await framework.databaseManager.getUserDatabase(
|
const database = await framework.databaseManager.getUserDatabase(
|
||||||
userId,
|
userId,
|
||||||
modelClass.modelName,
|
modelClass.modelName,
|
||||||
@ -779,14 +829,17 @@ export abstract class BaseModel {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (modelClass.scope === 'user') {
|
if (modelClass.scope === 'user') {
|
||||||
const userId = (this as any).userId || (this as any).authorId || (this as any).ownerId;
|
const userId =
|
||||||
|
this.getFieldValue('userId') ||
|
||||||
|
this.getFieldValue('authorId') ||
|
||||||
|
this.getFieldValue('ownerId');
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
|
throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure user databases exist before accessing them
|
// Ensure user databases exist before accessing them
|
||||||
await this.ensureUserDatabasesExist(framework, userId);
|
await this.ensureUserDatabasesExist(framework, userId);
|
||||||
|
|
||||||
const database = await framework.databaseManager.getUserDatabase(
|
const database = await framework.databaseManager.getUserDatabase(
|
||||||
userId,
|
userId,
|
||||||
modelClass.modelName,
|
modelClass.modelName,
|
||||||
@ -858,7 +911,7 @@ export abstract class BaseModel {
|
|||||||
if (!(globalThis as any).__mockDatabase) {
|
if (!(globalThis as any).__mockDatabase) {
|
||||||
(globalThis as any).__mockDatabase = new Map();
|
(globalThis as any).__mockDatabase = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockDatabase = {
|
const mockDatabase = {
|
||||||
_data: (globalThis as any).__mockDatabase,
|
_data: (globalThis as any).__mockDatabase,
|
||||||
async get(id: string) {
|
async get(id: string) {
|
||||||
@ -874,7 +927,7 @@ export abstract class BaseModel {
|
|||||||
},
|
},
|
||||||
async all() {
|
async all() {
|
||||||
return Array.from(this._data.values());
|
return Array.from(this._data.values());
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -908,13 +961,13 @@ export abstract class BaseModel {
|
|||||||
},
|
},
|
||||||
async getAllDocuments(_database: any, _type: string) {
|
async getAllDocuments(_database: any, _type: string) {
|
||||||
return await mockDatabase.all();
|
return await mockDatabase.all();
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
shardManager: {
|
shardManager: {
|
||||||
getShardForKey(_modelName: string, _key: string) {
|
getShardForKey(_modelName: string, _key: string) {
|
||||||
return { database: mockDatabase };
|
return { database: mockDatabase };
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -7,18 +7,8 @@ export function Field(config: FieldConfig) {
|
|||||||
validateFieldConfig(config);
|
validateFieldConfig(config);
|
||||||
|
|
||||||
// Handle ESM case where target might be undefined
|
// Handle ESM case where target might be undefined
|
||||||
if (!target) {
|
if (!target || typeof target !== 'object') {
|
||||||
// In ESM environment, defer the decorator application
|
// Skip the decorator if target is not available - the field will be handled later
|
||||||
// Create a deferred setup that will be called when the class is actually used
|
|
||||||
console.warn(`Target is undefined for field:`, {
|
|
||||||
propertyKey,
|
|
||||||
propertyKeyType: typeof propertyKey,
|
|
||||||
propertyKeyValue: JSON.stringify(propertyKey),
|
|
||||||
configType: config.type,
|
|
||||||
target,
|
|
||||||
targetType: typeof target
|
|
||||||
});
|
|
||||||
deferredFieldSetup(config, propertyKey);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,15 @@ export class FrameworkOrbitDBService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async openDatabase(name: string, type: StoreType): Promise<any> {
|
async openDatabase(name: string, type: StoreType): Promise<any> {
|
||||||
|
console.log('FrameworkOrbitDBService.openDatabase called with:', { name, type });
|
||||||
|
console.log('this.orbitDBService:', this.orbitDBService);
|
||||||
|
console.log('typeof this.orbitDBService.openDB:', typeof this.orbitDBService.openDB);
|
||||||
|
console.log('this.orbitDBService methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(this.orbitDBService)));
|
||||||
|
|
||||||
|
if (typeof this.orbitDBService.openDB !== 'function') {
|
||||||
|
throw new Error(`openDB is not a function. Service type: ${typeof this.orbitDBService}, methods: ${Object.getOwnPropertyNames(Object.getPrototypeOf(this.orbitDBService))}`);
|
||||||
|
}
|
||||||
|
|
||||||
return await this.orbitDBService.openDB(name, type);
|
return await this.orbitDBService.openDB(name, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1646
system.txt
1646
system.txt
File diff suppressed because it is too large
Load Diff
@ -112,8 +112,8 @@ class BlogAPIServer {
|
|||||||
|
|
||||||
const user = await User.create(sanitizedData);
|
const user = await User.create(sanitizedData);
|
||||||
|
|
||||||
console.log(`[${this.nodeId}] Created user: ${user.username} (${user.id})`);
|
console.log(`[${this.nodeId}] Created user: ${user.getFieldValue('username')} (${user.id})`);
|
||||||
res.status(201).json(user.toJSON());
|
res.status(201).json(user);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@ -226,7 +226,7 @@ class BlogAPIServer {
|
|||||||
|
|
||||||
const category = await Category.create(sanitizedData);
|
const category = await Category.create(sanitizedData);
|
||||||
|
|
||||||
console.log(`[${this.nodeId}] Created category: ${category.name} (${category.id})`);
|
console.log(`[${this.nodeId}] Created category: ${category.getFieldValue('name')} (${category.id})`);
|
||||||
res.status(201).json(category);
|
res.status(201).json(category);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@ -276,7 +276,7 @@ class BlogAPIServer {
|
|||||||
|
|
||||||
const post = await Post.create(sanitizedData);
|
const post = await Post.create(sanitizedData);
|
||||||
|
|
||||||
console.log(`[${this.nodeId}] Created post: ${post.title} (${post.id})`);
|
console.log(`[${this.nodeId}] Created post: ${post.getFieldValue('title')} (${post.id})`);
|
||||||
res.status(201).json(post);
|
res.status(201).json(post);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@ -627,9 +627,6 @@ class BlogAPIServer {
|
|||||||
const { OrbitDBService } = await import(
|
const { OrbitDBService } = await import(
|
||||||
'../../../../src/framework/services/RealOrbitDBService'
|
'../../../../src/framework/services/RealOrbitDBService'
|
||||||
);
|
);
|
||||||
const { FrameworkIPFSService, FrameworkOrbitDBService } = await import(
|
|
||||||
'../../../../src/framework/services/OrbitDBService'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initialize IPFS service
|
// Initialize IPFS service
|
||||||
const ipfsService = new IPFSService({
|
const ipfsService = new IPFSService({
|
||||||
@ -648,10 +645,10 @@ class BlogAPIServer {
|
|||||||
await orbitDBService.init();
|
await orbitDBService.init();
|
||||||
console.log(`[${this.nodeId}] OrbitDB service initialized`);
|
console.log(`[${this.nodeId}] OrbitDB service initialized`);
|
||||||
|
|
||||||
// Wrap services for framework
|
// Debug: Check OrbitDB service methods
|
||||||
const frameworkIPFS = new FrameworkIPFSService(ipfsService);
|
console.log(`[${this.nodeId}] OrbitDB service methods:`, Object.getOwnPropertyNames(Object.getPrototypeOf(orbitDBService)));
|
||||||
const frameworkOrbitDB = new FrameworkOrbitDBService(orbitDBService);
|
console.log(`[${this.nodeId}] Has openDB method:`, typeof orbitDBService.openDB === 'function');
|
||||||
|
|
||||||
// Initialize framework
|
// Initialize framework
|
||||||
this.framework = new DebrosFramework({
|
this.framework = new DebrosFramework({
|
||||||
environment: 'test',
|
environment: 'test',
|
||||||
@ -669,8 +666,32 @@ class BlogAPIServer {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pass raw services to framework - it will wrap them itself
|
||||||
await this.framework.initialize(orbitDBService, ipfsService);
|
await this.framework.initialize(orbitDBService, ipfsService);
|
||||||
console.log(`[${this.nodeId}] DebrosFramework initialized successfully`);
|
console.log(`[${this.nodeId}] DebrosFramework initialized successfully`);
|
||||||
|
|
||||||
|
// Register models with framework
|
||||||
|
this.framework.registerModel(User, {
|
||||||
|
scope: 'global',
|
||||||
|
type: 'docstore'
|
||||||
|
});
|
||||||
|
this.framework.registerModel(UserProfile, {
|
||||||
|
scope: 'global',
|
||||||
|
type: 'docstore'
|
||||||
|
});
|
||||||
|
this.framework.registerModel(Category, {
|
||||||
|
scope: 'global',
|
||||||
|
type: 'docstore'
|
||||||
|
});
|
||||||
|
this.framework.registerModel(Post, {
|
||||||
|
scope: 'user',
|
||||||
|
type: 'docstore'
|
||||||
|
});
|
||||||
|
this.framework.registerModel(Comment, {
|
||||||
|
scope: 'user',
|
||||||
|
type: 'docstore'
|
||||||
|
});
|
||||||
|
console.log(`[${this.nodeId}] Models registered with framework`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,79 @@ import 'reflect-metadata';
|
|||||||
import { BaseModel } from '../../../../src/framework/models/BaseModel';
|
import { BaseModel } from '../../../../src/framework/models/BaseModel';
|
||||||
import { Model, Field, HasMany, BelongsTo, HasOne, BeforeCreate, AfterCreate } from '../../../../src/framework/models/decorators';
|
import { Model, Field, HasMany, BelongsTo, HasOne, BeforeCreate, AfterCreate } from '../../../../src/framework/models/decorators';
|
||||||
|
|
||||||
|
// Force field registration by manually setting up field configurations
|
||||||
|
function setupFieldConfigurations() {
|
||||||
|
// User Profile fields
|
||||||
|
if (!UserProfile.fields) {
|
||||||
|
(UserProfile as any).fields = new Map();
|
||||||
|
}
|
||||||
|
UserProfile.fields.set('userId', { type: 'string', required: true });
|
||||||
|
UserProfile.fields.set('bio', { type: 'string', required: false });
|
||||||
|
UserProfile.fields.set('location', { type: 'string', required: false });
|
||||||
|
UserProfile.fields.set('website', { type: 'string', required: false });
|
||||||
|
UserProfile.fields.set('socialLinks', { type: 'object', required: false });
|
||||||
|
UserProfile.fields.set('interests', { type: 'array', required: false, default: [] });
|
||||||
|
UserProfile.fields.set('createdAt', { type: 'number', required: false, default: () => Date.now() });
|
||||||
|
UserProfile.fields.set('updatedAt', { type: 'number', required: false, default: () => Date.now() });
|
||||||
|
|
||||||
|
// User fields
|
||||||
|
if (!User.fields) {
|
||||||
|
(User as any).fields = new Map();
|
||||||
|
}
|
||||||
|
User.fields.set('username', { type: 'string', required: true, unique: true });
|
||||||
|
User.fields.set('email', { type: 'string', required: true, unique: true });
|
||||||
|
User.fields.set('displayName', { type: 'string', required: false });
|
||||||
|
User.fields.set('avatar', { type: 'string', required: false });
|
||||||
|
User.fields.set('isActive', { type: 'boolean', required: false, default: true });
|
||||||
|
User.fields.set('roles', { type: 'array', required: false, default: [] });
|
||||||
|
User.fields.set('createdAt', { type: 'number', required: false });
|
||||||
|
User.fields.set('lastLoginAt', { type: 'number', required: false });
|
||||||
|
|
||||||
|
// Category fields
|
||||||
|
if (!Category.fields) {
|
||||||
|
(Category as any).fields = new Map();
|
||||||
|
}
|
||||||
|
Category.fields.set('name', { type: 'string', required: true, unique: true });
|
||||||
|
Category.fields.set('slug', { type: 'string', required: true, unique: true });
|
||||||
|
Category.fields.set('description', { type: 'string', required: false });
|
||||||
|
Category.fields.set('color', { type: 'string', required: false });
|
||||||
|
Category.fields.set('isActive', { type: 'boolean', required: false, default: true });
|
||||||
|
Category.fields.set('createdAt', { type: 'number', required: false, default: () => Date.now() });
|
||||||
|
|
||||||
|
// Post fields
|
||||||
|
if (!Post.fields) {
|
||||||
|
(Post as any).fields = new Map();
|
||||||
|
}
|
||||||
|
Post.fields.set('title', { type: 'string', required: true });
|
||||||
|
Post.fields.set('slug', { type: 'string', required: true, unique: true });
|
||||||
|
Post.fields.set('content', { type: 'string', required: true });
|
||||||
|
Post.fields.set('excerpt', { type: 'string', required: false });
|
||||||
|
Post.fields.set('authorId', { type: 'string', required: true });
|
||||||
|
Post.fields.set('categoryId', { type: 'string', required: false });
|
||||||
|
Post.fields.set('tags', { type: 'array', required: false, default: [] });
|
||||||
|
Post.fields.set('status', { type: 'string', required: false, default: 'draft' });
|
||||||
|
Post.fields.set('featuredImage', { type: 'string', required: false });
|
||||||
|
Post.fields.set('isFeatured', { type: 'boolean', required: false, default: false });
|
||||||
|
Post.fields.set('viewCount', { type: 'number', required: false, default: 0 });
|
||||||
|
Post.fields.set('likeCount', { type: 'number', required: false, default: 0 });
|
||||||
|
Post.fields.set('createdAt', { type: 'number', required: false });
|
||||||
|
Post.fields.set('updatedAt', { type: 'number', required: false });
|
||||||
|
Post.fields.set('publishedAt', { type: 'number', required: false });
|
||||||
|
|
||||||
|
// Comment fields
|
||||||
|
if (!Comment.fields) {
|
||||||
|
(Comment as any).fields = new Map();
|
||||||
|
}
|
||||||
|
Comment.fields.set('content', { type: 'string', required: true });
|
||||||
|
Comment.fields.set('postId', { type: 'string', required: true });
|
||||||
|
Comment.fields.set('authorId', { type: 'string', required: true });
|
||||||
|
Comment.fields.set('parentId', { type: 'string', required: false });
|
||||||
|
Comment.fields.set('isApproved', { type: 'boolean', required: false, default: true });
|
||||||
|
Comment.fields.set('likeCount', { type: 'number', required: false, default: 0 });
|
||||||
|
Comment.fields.set('createdAt', { type: 'number', required: false });
|
||||||
|
Comment.fields.set('updatedAt', { type: 'number', required: false });
|
||||||
|
}
|
||||||
|
|
||||||
// User Profile Model
|
// User Profile Model
|
||||||
@Model({
|
@Model({
|
||||||
scope: 'global',
|
scope: 'global',
|
||||||
@ -368,4 +441,7 @@ export interface UpdatePostRequest {
|
|||||||
tags?: string[];
|
tags?: string[];
|
||||||
featuredImage?: string;
|
featuredImage?: string;
|
||||||
isFeatured?: boolean;
|
isFeatured?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize field configurations after all models are defined
|
||||||
|
setupFieldConfigurations();
|
||||||
|
Reference in New Issue
Block a user