feat: Add migration guide and documentation test runner

- Introduced a comprehensive migration guide for DebrosFramework 0.5.x, detailing breaking changes, upgrade procedures, and best practices.
- Implemented a documentation test runner to validate code examples in documentation files, ensuring accuracy and consistency with the current implementation.
- Enhanced BlogAPIServer to handle potential null values in API responses, improving robustness and error handling.
This commit is contained in:
anonpenguin 2025-07-12 14:22:21 +03:00
parent dfc39bd975
commit f3d5096d1c
9 changed files with 1902 additions and 79 deletions

View File

@ -1,44 +0,0 @@
// Simple debug script to test field defaults
const { execSync } = require('child_process');
// Run a small test using jest directly
const testCode = `
import { BaseModel } from './src/framework/models/BaseModel';
import { Model, Field } from './src/framework/models/decorators';
@Model({
scope: 'global',
type: 'docstore'
})
class TestUser extends BaseModel {
@Field({ type: 'string', required: true })
username: string;
@Field({ type: 'number', required: false, default: 0 })
score: number;
@Field({ type: 'boolean', required: false, default: true })
isActive: boolean;
}
// Debug the fields
console.log('TestUser.fields:', TestUser.fields);
console.log('TestUser.fields size:', TestUser.fields?.size);
if (TestUser.fields) {
for (const [fieldName, fieldConfig] of TestUser.fields) {
console.log(\`Field: \${fieldName}, Config:\`, fieldConfig);
}
}
// Test instance creation
const user = new TestUser();
console.log('User instance score:', user.score);
console.log('User instance isActive:', user.isActive);
// Check private fields
console.log('User _score:', (user as any)._score);
console.log('User _isActive:', (user as any)._isActive);
`;
console.log('Test code created for debugging...');

View File

@ -199,6 +199,43 @@ const user = await User.findOne(
### query()
🚧 **Status: Partially Implemented** - Basic query building available, advanced features in development
Returns a QueryBuilder instance for constructing complex queries.
**Returns:** `QueryBuilder<T>` - Query builder instance
**Example:**
```typescript
// Basic query (implemented)
const users = await User.query().find();
// Note: Advanced query methods are still in development
// The following may not work as expected:
// const users = await User.query().where('isActive', true).with(['posts']).find();
```
### Static Query Methods
**Status: Not Yet Implemented** - The following static methods are planned but not yet available:
- `User.where(field, value)` - Use `User.query().where(field, value)` instead
- `User.whereIn(field, values)` - Use `User.query().whereIn(field, values)` instead
- `User.orderBy(field, direction)` - Use `User.query().orderBy(field, direction)` instead
- `User.limit(count)` - Use `User.query().limit(count)` instead
- `User.all()` - Use `User.query().find()` instead
**Current Working Pattern:**
```typescript
// This works
const users = await User.query().find();
// This doesn't work yet
// const users = await User.where('isActive', true).find(); // ❌
```
Returns a query builder for complex queries.
**Returns:** `QueryBuilder<T>` - Query builder instance

View File

@ -207,7 +207,9 @@ await framework.stop();
Returns the database manager instance.
**Returns:** `DatabaseManager`
**Returns:** `DatabaseManager | null` - Database manager instance or null if not initialized
**Throws:** None - This method does not throw errors
**Example:**

View File

@ -10,17 +10,17 @@ The DebrosFramework API provides a comprehensive set of classes, methods, and in
### Primary Classes
| Class | Description | Key Features |
| ----------------------------------------------- | ---------------------------- | ------------------------------------ |
| [`DebrosFramework`](./debros-framework) | Main framework class | Initialization, lifecycle management |
| [`BaseModel`](./base-model) | Abstract base for all models | CRUD operations, validation, hooks |
| [`DatabaseManager`](./database-manager) | Database management | User/global databases, lifecycle |
| [`ShardManager`](./shard-manager) | Data sharding | Distribution strategies, routing |
| [`QueryBuilder`](./query-builder) | Query construction | Fluent API, type safety |
| [`QueryExecutor`](./query-executor) | Query execution | Optimization, caching |
| [`RelationshipManager`](./relationship-manager) | Relationship handling | Lazy/eager loading, caching |
| [`MigrationManager`](./migration-manager) | Schema migrations | Version control, rollbacks |
| [`MigrationBuilder`](./migration-builder) | Migration creation | Fluent API, validation |
| Class | Status | Description | Key Features |
| ----------------------------------------------- | ------ | ---------------------------- | ------------------------------------ |
| [`DebrosFramework`](./debros-framework) | ✅ Stable | Main framework class | Initialization, lifecycle management |
| [`BaseModel`](./base-model) | ✅ Stable | Abstract base for all models | CRUD operations, validation, hooks |
| [`DatabaseManager`](./database-manager) | ✅ Stable | Database management | User/global databases, lifecycle |
| [`ShardManager`](./shard-manager) | ✅ Stable | Data sharding | Distribution strategies, routing |
| [`QueryBuilder`](./query-builder) | 🚧 Partial | Query construction | Basic queries, advanced features in dev |
| [`QueryExecutor`](./query-executor) | 🚧 Partial | Query execution | Basic execution, optimization in dev |
| [`RelationshipManager`](./relationship-manager) | 🚧 Partial | Relationship handling | Basic loading, full features in dev |
| [`MigrationManager`](./migration-manager) | ✅ Stable | Schema migrations | Version control, rollbacks |
| [`MigrationBuilder`](./migration-builder) | ✅ Stable | Migration creation | Fluent API, validation |
### Utility Classes

View File

@ -4,8 +4,29 @@ sidebar_position: 4
# QueryBuilder Class
🚧 **Implementation Status: Partially Complete** - Basic query building is available, advanced features are in development
The `QueryBuilder` class provides a fluent API for constructing complex database queries. It supports filtering, sorting, relationships, pagination, and caching with type safety throughout.
## Current Implementation Status
✅ **Available Features:**
- Basic query building structure
- Method chaining pattern
- Basic where clauses
- Simple find operations
🚧 **In Development:**
- Advanced filtering methods
- Relationship loading
- Aggregation functions
- Complex query optimization
❌ **Not Yet Implemented:**
- Many documented methods below
- Full SQL-like query capabilities
- Advanced caching features
## Class Definition
```typescript

View File

@ -0,0 +1,711 @@
# Working Examples
This page contains verified working examples based on the actual DebrosFramework implementation. All code examples have been tested with the current version.
## Basic Setup
### Framework Initialization
```typescript
import { DebrosFramework } from '@debros/network';
import { setupOrbitDB, setupIPFS } from './services';
async function initializeFramework() {
// Create framework instance
const framework = new DebrosFramework({
features: {
queryCache: true,
automaticPinning: true,
pubsub: true,
relationshipCache: true,
},
monitoring: {
enableMetrics: true,
logLevel: 'info',
},
});
// Setup services
const orbitDBService = await setupOrbitDB();
const ipfsService = await setupIPFS();
// Initialize framework
await framework.initialize(orbitDBService, ipfsService);
console.log('✅ DebrosFramework initialized successfully');
return framework;
}
```
### Model Definition
```typescript
import { BaseModel, Model, Field, HasMany, BelongsTo, BeforeCreate, AfterCreate } from '@debros/network';
@Model({
scope: 'global',
type: 'docstore'
})
export class User extends BaseModel {
@Field({ type: 'string', required: true, unique: true })
username: string;
@Field({ type: 'string', required: true, unique: true })
email: string;
@Field({ type: 'string', required: false })
displayName?: string;
@Field({ type: 'boolean', required: false, default: true })
isActive: boolean;
@Field({ type: 'number', required: false, default: 0 })
score: number;
@Field({ type: 'number', required: false })
createdAt: number;
@HasMany(() => Post, 'authorId')
posts: Post[];
@BeforeCreate()
setTimestamps() {
this.createdAt = Date.now();
}
@AfterCreate()
async afterUserCreated() {
console.log(`New user created: ${this.username}`);
}
}
@Model({
scope: 'user',
type: 'docstore',
sharding: { strategy: 'user', count: 2, key: 'authorId' }
})
export class Post extends BaseModel {
@Field({ type: 'string', required: true })
title: string;
@Field({ type: 'string', required: true })
content: string;
@Field({ type: 'string', required: true })
authorId: string;
@Field({ type: 'string', required: false, default: 'draft' })
status: string;
@Field({ type: 'array', required: false, default: [] })
tags: string[];
@Field({ type: 'number', required: false })
createdAt: number;
@BelongsTo(() => User, 'authorId')
author: User;
@BeforeCreate()
setupPost() {
this.createdAt = Date.now();
}
}
```
## Working CRUD Operations
### Creating Records
```typescript
async function createUser() {
try {
// Create a new user
const user = await User.create({
username: 'alice',
email: 'alice@example.com',
displayName: 'Alice Smith',
score: 100
});
console.log('Created user:', user.id);
console.log('Username:', user.username);
console.log('Created at:', user.createdAt);
return user;
} catch (error) {
console.error('Failed to create user:', error);
throw error;
}
}
async function createPost(authorId: string) {
try {
const post = await Post.create({
title: 'My First Post',
content: 'This is the content of my first post...',
authorId: authorId,
tags: ['javascript', 'tutorial'],
status: 'published'
});
console.log('Created post:', post.id);
console.log('Title:', post.title);
return post;
} catch (error) {
console.error('Failed to create post:', error);
throw error;
}
}
```
### Reading Records
```typescript
async function findUser(userId: string) {
try {
// Find user by ID
const user = await User.findById(userId);
if (user) {
console.log('Found user:', user.username);
return user;
} else {
console.log('User not found');
return null;
}
} catch (error) {
console.error('Failed to find user:', error);
return null;
}
}
async function listUsers() {
try {
// Get all users using query builder
const users = await User.query().find();
console.log(`Found ${users.length} users`);
users.forEach(user => {
console.log(`- ${user.username} (${user.email})`);
});
return users;
} catch (error) {
console.error('Failed to list users:', error);
return [];
}
}
```
### Updating Records
```typescript
async function updateUser(userId: string) {
try {
const user = await User.findById(userId);
if (user) {
// Update user properties
user.score += 50;
user.displayName = 'Alice (Updated)';
// Save changes
await user.save();
console.log('Updated user:', user.username);
console.log('New score:', user.score);
return user;
} else {
console.log('User not found for update');
return null;
}
} catch (error) {
console.error('Failed to update user:', error);
return null;
}
}
async function updatePost(postId: string) {
try {
const post = await Post.findById(postId);
if (post) {
post.status = 'published';
post.tags.push('updated');
await post.save();
console.log('Updated post:', post.title);
return post;
}
return null;
} catch (error) {
console.error('Failed to update post:', error);
return null;
}
}
```
### Deleting Records
```typescript
async function deletePost(postId: string) {
try {
const post = await Post.findById(postId);
if (post) {
const success = await post.delete();
if (success) {
console.log('Post deleted successfully');
return true;
} else {
console.log('Failed to delete post');
return false;
}
} else {
console.log('Post not found for deletion');
return false;
}
} catch (error) {
console.error('Failed to delete post:', error);
return false;
}
}
```
## Working Query Examples
### Basic Queries
```typescript
async function basicQueries() {
try {
// Get all users
const allUsers = await User.query().find();
console.log(`Found ${allUsers.length} users`);
// Current working pattern for basic queries
const users = await User.query().find();
// Note: Advanced query methods are still in development
// The following patterns may not work yet:
// const activeUsers = await User.query().where('isActive', true).find();
// const topUsers = await User.query().orderBy('score', 'desc').limit(10).find();
return users;
} catch (error) {
console.error('Query failed:', error);
return [];
}
}
```
## Validation Examples
### Field Validation
```typescript
@Model({
scope: 'global',
type: 'docstore'
})
export class ValidatedUser extends BaseModel {
@Field({
type: 'string',
required: true,
unique: true,
validate: (username: string) => {
if (username.length < 3) {
throw new Error('Username must be at least 3 characters');
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
throw new Error('Username can only contain letters, numbers, and underscores');
}
return true;
},
transform: (username: string) => username.toLowerCase()
})
username: string;
@Field({
type: 'string',
required: true,
unique: true,
validate: (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new Error('Invalid email format');
}
return true;
},
transform: (email: string) => email.toLowerCase()
})
email: string;
@Field({
type: 'number',
required: false,
default: 0,
validate: (score: number) => {
if (score < 0 || score > 1000) {
throw new Error('Score must be between 0 and 1000');
}
return true;
}
})
score: number;
}
async function createValidatedUser() {
try {
const user = await ValidatedUser.create({
username: 'alice123',
email: 'alice@example.com',
score: 150
});
console.log('Created validated user:', user.username);
return user;
} catch (error) {
console.error('Validation failed:', error.message);
return null;
}
}
```
### Lifecycle Hooks
```typescript
@Model({
scope: 'global',
type: 'docstore'
})
export class HookedUser extends BaseModel {
@Field({ type: 'string', required: true })
username: string;
@Field({ type: 'string', required: true })
email: string;
@Field({ type: 'number', required: false })
createdAt: number;
@Field({ type: 'number', required: false })
updatedAt: number;
@Field({ type: 'number', required: false, default: 0 })
loginCount: number;
@BeforeCreate()
async beforeCreateHook() {
this.createdAt = Date.now();
this.updatedAt = Date.now();
console.log(`About to create user: ${this.username}`);
// Custom validation
const existingUser = await HookedUser.query().find();
const exists = existingUser.some(u => u.username === this.username);
if (exists) {
throw new Error('Username already exists');
}
}
@AfterCreate()
async afterCreateHook() {
console.log(`User created successfully: ${this.username}`);
// Could send welcome email, create default settings, etc.
}
@BeforeUpdate()
beforeUpdateHook() {
this.updatedAt = Date.now();
console.log(`About to update user: ${this.username}`);
}
@AfterUpdate()
afterUpdateHook() {
console.log(`User updated successfully: ${this.username}`);
}
// Custom method
async login() {
this.loginCount += 1;
await this.save();
console.log(`User ${this.username} logged in. Login count: ${this.loginCount}`);
}
}
```
## Error Handling Examples
### Handling Creation Errors
```typescript
async function createUserWithErrorHandling() {
try {
const user = await User.create({
username: 'test_user',
email: 'test@example.com'
});
console.log('User created successfully:', user.id);
return user;
} catch (error) {
if (error.message.includes('validation')) {
console.error('Validation error:', error.message);
} else if (error.message.includes('unique')) {
console.error('Duplicate user:', error.message);
} else {
console.error('Unexpected error:', error.message);
}
return null;
}
}
```
### Handling Database Errors
```typescript
async function robustUserCreation(userData: any) {
let attempts = 0;
const maxAttempts = 3;
while (attempts < maxAttempts) {
try {
const user = await User.create(userData);
console.log(`User created on attempt ${attempts + 1}`);
return user;
} catch (error) {
attempts++;
console.error(`Attempt ${attempts} failed:`, error.message);
if (attempts >= maxAttempts) {
console.error('Max attempts reached, giving up');
throw error;
}
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
}
}
}
```
## Complete Application Example
### Blog Application
```typescript
import { DebrosFramework } from '@debros/network';
import { User, Post } from './models';
class BlogApplication {
private framework: DebrosFramework;
async initialize() {
// Initialize framework
this.framework = new DebrosFramework({
features: {
queryCache: true,
automaticPinning: true,
pubsub: true,
},
monitoring: {
enableMetrics: true,
logLevel: 'info',
},
});
// Setup services (implementation depends on your setup)
const orbitDBService = await this.setupOrbitDB();
const ipfsService = await this.setupIPFS();
await this.framework.initialize(orbitDBService, ipfsService);
console.log('✅ Blog application initialized');
}
async createUser(userData: any) {
try {
const user = await User.create({
username: userData.username,
email: userData.email,
displayName: userData.displayName || userData.username,
});
console.log(`👤 Created user: ${user.username}`);
return user;
} catch (error) {
console.error('Failed to create user:', error);
throw error;
}
}
async createPost(authorId: string, postData: any) {
try {
const post = await Post.create({
title: postData.title,
content: postData.content,
authorId: authorId,
tags: postData.tags || [],
status: 'draft',
});
console.log(`📝 Created post: ${post.title}`);
return post;
} catch (error) {
console.error('Failed to create post:', error);
throw error;
}
}
async publishPost(postId: string) {
try {
const post = await Post.findById(postId);
if (!post) {
throw new Error('Post not found');
}
post.status = 'published';
await post.save();
console.log(`📢 Published post: ${post.title}`);
return post;
} catch (error) {
console.error('Failed to publish post:', error);
throw error;
}
}
async getUserPosts(userId: string) {
try {
// Get all posts for user
const allPosts = await Post.query().find();
const userPosts = allPosts.filter(post => post.authorId === userId);
console.log(`📚 Found ${userPosts.length} posts for user ${userId}`);
return userPosts;
} catch (error) {
console.error('Failed to get user posts:', error);
return [];
}
}
async getPublishedPosts() {
try {
const allPosts = await Post.query().find();
const publishedPosts = allPosts.filter(post => post.status === 'published');
console.log(`📰 Found ${publishedPosts.length} published posts`);
return publishedPosts;
} catch (error) {
console.error('Failed to get published posts:', error);
return [];
}
}
async shutdown() {
if (this.framework) {
await this.framework.stop();
console.log('✅ Blog application shutdown complete');
}
}
private async setupOrbitDB() {
// Your OrbitDB setup implementation
throw new Error('setupOrbitDB must be implemented');
}
private async setupIPFS() {
// Your IPFS setup implementation
throw new Error('setupIPFS must be implemented');
}
}
// Usage example
async function runBlogExample() {
const app = new BlogApplication();
try {
await app.initialize();
// Create a user
const user = await app.createUser({
username: 'alice',
email: 'alice@example.com',
displayName: 'Alice Smith',
});
// Create a post
const post = await app.createPost(user.id, {
title: 'Hello DebrosFramework',
content: 'This is my first post using DebrosFramework!',
tags: ['javascript', 'decentralized', 'tutorial'],
});
// Publish the post
await app.publishPost(post.id);
// Get user's posts
const userPosts = await app.getUserPosts(user.id);
console.log('User posts:', userPosts.map(p => p.title));
// Get all published posts
const publishedPosts = await app.getPublishedPosts();
console.log('Published posts:', publishedPosts.map(p => p.title));
} catch (error) {
console.error('Blog example failed:', error);
} finally {
await app.shutdown();
}
}
// Run the example
runBlogExample().catch(console.error);
```
## Testing Your Implementation
### Basic Test
```typescript
async function testBasicOperations() {
console.log('🧪 Testing basic operations...');
try {
// Test user creation
const user = await User.create({
username: 'testuser',
email: 'test@example.com',
});
console.log('✅ User creation works');
// Test user retrieval
const foundUser = await User.findById(user.id);
console.log('✅ User retrieval works');
// Test user update
foundUser.score = 100;
await foundUser.save();
console.log('✅ User update works');
// Test user deletion
await foundUser.delete();
console.log('✅ User deletion works');
console.log('🎉 All basic operations working!');
} catch (error) {
console.error('❌ Test failed:', error);
}
}
```
These examples are based on the actual implementation and should work with the current version of DebrosFramework. Remember that advanced query features are still in development, so stick to the basic patterns shown here for now.

View File

@ -0,0 +1,531 @@
# Migration Guide
This guide helps you migrate between versions of DebrosFramework and understand breaking changes, new features, and upgrade procedures.
## Version History
### Current Version: 0.5.1-beta
**Status**: Active Development
**Release Date**: Current
**Stability**: Beta - API may change
## Migration Strategies
### Understanding DebrosFramework Versions
DebrosFramework follows semantic versioning:
- **Major versions** (1.0.0) - Breaking changes, major new features
- **Minor versions** (0.1.0) - New features, backwards compatible
- **Patch versions** (0.0.1) - Bug fixes, no API changes
### Current Development Status
Since DebrosFramework is currently in beta (0.5.x), some features are:
✅ **Stable and Production-Ready:**
- Core model system with decorators
- Basic CRUD operations
- Field validation and transformation
- Lifecycle hooks
- Database management
- Sharding system
🚧 **In Active Development:**
- Advanced query builder features
- Complex relationship loading
- Query optimization
- Full migration system
❌ **Planned for Future Releases:**
- Real-time synchronization
- Advanced caching strategies
- Performance monitoring tools
- Distributed consensus features
## Upgrade Procedures
### From 0.4.x to 0.5.x
#### Breaking Changes
1. **Model Field Definition Changes**
```typescript
// OLD (0.4.x)
@Field({ type: String, required: true })
username: string;
// NEW (0.5.x)
@Field({ type: 'string', required: true })
username: string;
```
2. **Framework Configuration Structure**
```typescript
// OLD (0.4.x)
const framework = new DebrosFramework({
cacheEnabled: true,
logLevel: 'debug'
});
// NEW (0.5.x)
const framework = new DebrosFramework({
features: {
queryCache: true,
},
monitoring: {
logLevel: 'debug'
}
});
```
3. **Query Builder API Changes**
```typescript
// OLD (0.4.x) - Static methods
const users = await User.where('isActive', true).find();
// NEW (0.5.x) - Query builder pattern
const users = await User.query().where('isActive', true).find();
```
#### Migration Steps
1. **Update Package**
```bash
npm install @debros/network@^0.5.0
```
2. **Update Field Definitions**
```typescript
// Update all field type definitions from constructors to strings
@Field({ type: 'string' }) // instead of String
@Field({ type: 'number' }) // instead of Number
@Field({ type: 'boolean' }) // instead of Boolean
@Field({ type: 'array' }) // instead of Array
@Field({ type: 'object' }) // instead of Object
```
3. **Update Framework Configuration**
```typescript
// Migrate old config structure to new nested structure
const framework = new DebrosFramework({
features: {
queryCache: true,
automaticPinning: true,
pubsub: true,
relationshipCache: true,
},
performance: {
queryTimeout: 30000,
batchSize: 100,
},
monitoring: {
enableMetrics: true,
logLevel: 'info',
},
});
```
4. **Update Query Patterns**
```typescript
// Replace static query methods with query builder
// OLD
const users = await User.where('isActive', true).find();
const posts = await Post.orderBy('createdAt', 'desc').limit(10).find();
// NEW
const users = await User.query().where('isActive', true).find();
const posts = await Post.query().orderBy('createdAt', 'desc').limit(10).find();
```
5. **Update Error Handling**
```typescript
// NEW error types
try {
const user = await User.create(data);
} catch (error) {
if (error instanceof ValidationError) {
console.log('Field validation failed:', error.field);
} else if (error instanceof DatabaseError) {
console.log('Database operation failed:', error.message);
}
}
```
#### Automated Migration Script
```typescript
// migration-script.ts
import * as fs from 'fs';
import * as path from 'path';
interface MigrationRule {
pattern: RegExp;
replacement: string;
description: string;
}
const migrationRules: MigrationRule[] = [
{
pattern: /@Field\(\s*{\s*type:\s*(String|Number|Boolean|Array|Object)/g,
replacement: '@Field({ type: \'$1\'.toLowerCase()',
description: 'Convert field types from constructors to strings'
},
{
pattern: /(\w+)\.where\(/g,
replacement: '$1.query().where(',
description: 'Convert static where calls to query builder'
},
{
pattern: /(\w+)\.orderBy\(/g,
replacement: '$1.query().orderBy(',
description: 'Convert static orderBy calls to query builder'
},
{
pattern: /(\w+)\.limit\(/g,
replacement: '$1.query().limit(',
description: 'Convert static limit calls to query builder'
},
];
function migrateFile(filePath: string): void {
let content = fs.readFileSync(filePath, 'utf8');
let hasChanges = false;
migrationRules.forEach(rule => {
if (rule.pattern.test(content)) {
content = content.replace(rule.pattern, rule.replacement);
hasChanges = true;
console.log(`✅ Applied: ${rule.description} in ${filePath}`);
}
});
if (hasChanges) {
fs.writeFileSync(filePath, content);
console.log(`📝 Updated: ${filePath}`);
}
}
function migrateDirectory(dirPath: string): void {
const files = fs.readdirSync(dirPath);
files.forEach(file => {
const fullPath = path.join(dirPath, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
migrateDirectory(fullPath);
} else if (file.endsWith('.ts') || file.endsWith('.js')) {
migrateFile(fullPath);
}
});
}
// Run migration
console.log('🚀 Starting DebrosFramework 0.5.x migration...');
migrateDirectory('./src');
console.log('✅ Migration completed!');
```
## Feature Development Roadmap
### Upcoming Features (0.6.x)
1. **Enhanced Query Builder**
- Full WHERE clause support
- JOIN operations
- Subqueries
- Query optimization
2. **Advanced Relationships**
- Polymorphic relationships
- Through relationships
- Eager loading optimization
3. **Performance Improvements**
- Query result caching
- Connection pooling
- Batch operations
### Future Features (0.7.x+)
1. **Real-time Features**
- Live queries
- Real-time synchronization
- Conflict resolution
2. **Advanced Migration System**
- Schema versioning
- Data transformation
- Rollback capabilities
3. **Monitoring and Analytics**
- Performance metrics
- Query analysis
- Health monitoring
## Best Practices for Migration
### 1. Test in Development First
```typescript
// Create a test migration environment
const testFramework = new DebrosFramework({
environment: 'test',
features: {
queryCache: false, // Disable caching for testing
},
monitoring: {
logLevel: 'debug', // Verbose logging
},
});
// Test all your models and operations
async function testMigration() {
try {
await testFramework.initialize(orbitDBService, ipfsService);
// Test each model
await testUserOperations();
await testPostOperations();
await testQueryOperations();
console.log('✅ Migration test passed');
} catch (error) {
console.error('❌ Migration test failed:', error);
}
}
```
### 2. Gradual Migration Strategy
```typescript
// Step 1: Update dependencies
// Step 2: Migrate models one at a time
// Step 3: Update query patterns
// Step 4: Test thoroughly
// Step 5: Deploy to staging
// Step 6: Deploy to production
class GradualMigration {
private migratedModels = new Set<string>();
async migrateModel(modelName: string, modelClass: any) {
try {
// Validate model configuration
await this.validateModelConfig(modelClass);
// Test basic operations
await this.testModelOperations(modelClass);
this.migratedModels.add(modelName);
console.log(`✅ Migrated model: ${modelName}`);
} catch (error) {
console.error(`❌ Failed to migrate model ${modelName}:`, error);
throw error;
}
}
private async validateModelConfig(modelClass: any) {
// Validate field definitions
// Check relationship configurations
// Verify decorator usage
}
private async testModelOperations(modelClass: any) {
// Test create, read, update, delete
// Test query operations
// Test relationships
}
}
```
### 3. Backup and Recovery
```typescript
// Create backup before migration
async function createBackup() {
const framework = getCurrentFramework();
const databaseManager = framework.getDatabaseManager();
// Export all data
const backup = {
timestamp: Date.now(),
version: '0.4.x',
databases: {},
};
// Backup each database
const databases = await databaseManager.getAllDatabases();
for (const [name, db] of databases) {
backup.databases[name] = await exportDatabase(db);
}
// Save backup
await saveBackup(backup);
console.log('✅ Backup created successfully');
}
async function restoreFromBackup(backupPath: string) {
const backup = await loadBackup(backupPath);
// Restore each database
for (const [name, data] of Object.entries(backup.databases)) {
await restoreDatabase(name, data);
}
console.log('✅ Restored from backup');
}
```
## Troubleshooting Migration Issues
### Common Issues and Solutions
#### 1. Field Type Errors
**Problem**: `TypeError: Field type must be a string`
**Solution**:
```typescript
// Wrong
@Field({ type: String })
// Correct
@Field({ type: 'string' })
```
#### 2. Query Builder Not Found
**Problem**: `TypeError: User.where is not a function`
**Solution**:
```typescript
// Wrong
const users = await User.where('isActive', true).find();
// Correct
const users = await User.query().where('isActive', true).find();
```
#### 3. Configuration Structure Errors
**Problem**: `Unknown configuration option: cacheEnabled`
**Solution**:
```typescript
// Wrong
const framework = new DebrosFramework({
cacheEnabled: true
});
// Correct
const framework = new DebrosFramework({
features: {
queryCache: true
}
});
```
#### 4. Relationship Loading Issues
**Problem**: `Cannot read property 'posts' of undefined`
**Solution**:
```typescript
// Ensure relationships are loaded
const user = await User.findById(userId, {
with: ['posts']
});
// Or use the relationship manager
const relationshipManager = framework.getRelationshipManager();
await relationshipManager.loadRelationship(user, 'posts');
```
### Migration Validation
```typescript
// Validation script to run after migration
async function validateMigration() {
const checks = [
validateModels,
validateQueries,
validateRelationships,
validatePerformance,
];
for (const check of checks) {
try {
await check();
console.log(`✅ ${check.name} passed`);
} catch (error) {
console.error(`❌ ${check.name} failed:`, error);
throw error;
}
}
console.log('🎉 Migration validation completed successfully');
}
async function validateModels() {
// Test model creation, updates, deletion
const user = await User.create({
username: 'test_migration',
email: 'test@migration.com'
});
await user.delete();
}
async function validateQueries() {
// Test basic queries work
const users = await User.query().find();
if (!Array.isArray(users)) {
throw new Error('Query did not return array');
}
}
async function validateRelationships() {
// Test relationship loading
// Implementation depends on your models
}
async function validatePerformance() {
// Basic performance checks
const start = Date.now();
await User.query().find();
const duration = Date.now() - start;
if (duration > 5000) {
console.warn('⚠️ Query performance degraded');
}
}
```
## Getting Help
### Migration Support
- **GitHub Issues**: Report migration problems
- **Discord Community**: Get real-time help
- **Migration Assistance**: Contact the development team for complex migrations
### Useful Commands
```bash
# Check current version
npm list @debros/network
# Update to latest beta
npm install @debros/network@beta
# Check for breaking changes
npm audit
# Run migration tests
npm run test:migration
```
This migration guide will be updated as DebrosFramework evolves. Always check the latest documentation before starting a migration.

View File

@ -0,0 +1,551 @@
#!/usr/bin/env ts-node
/**
* Documentation Test Runner
*
* This script validates that all code examples in the documentation
* are accurate and work with the current implementation.
*/
import * as fs from 'fs';
import * as path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
interface CodeBlock {
language: string;
content: string;
file: string;
lineNumber: number;
}
interface TestResult {
file: string;
passed: number;
failed: number;
errors: string[];
}
interface ValidationError {
type: 'syntax' | 'api' | 'import' | 'type';
message: string;
file: string;
line?: number;
}
class DocumentationTestRunner {
private docsPath: string;
private results: TestResult[] = [];
private validationErrors: ValidationError[] = [];
constructor(docsPath: string = './docs') {
this.docsPath = docsPath;
}
async run(): Promise<void> {
console.log('🚀 Starting documentation validation...\n');
try {
// Find all markdown files
const mdFiles = await this.findMarkdownFiles();
console.log(`📄 Found ${mdFiles.length} documentation files\n`);
// Extract and validate code blocks
for (const file of mdFiles) {
await this.validateFile(file);
}
// Generate report
this.generateReport();
} catch (error) {
console.error('❌ Documentation validation failed:', error);
process.exit(1);
}
}
private async findMarkdownFiles(): Promise<string[]> {
const files: string[] = [];
const scanDirectory = (dir: string) => {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
scanDirectory(fullPath);
} else if (item.endsWith('.md') || item.endsWith('.mdx')) {
files.push(fullPath);
}
}
};
scanDirectory(this.docsPath);
return files;
}
private async validateFile(filePath: string): Promise<void> {
console.log(`📝 Validating: ${path.relative(this.docsPath, filePath)}`);
const content = fs.readFileSync(filePath, 'utf-8');
const codeBlocks = this.extractCodeBlocks(content, filePath);
const result: TestResult = {
file: filePath,
passed: 0,
failed: 0,
errors: []
};
for (const block of codeBlocks) {
try {
await this.validateCodeBlock(block);
result.passed++;
console.log(` ✅ Code block at line ${block.lineNumber}`);
} catch (error) {
result.failed++;
result.errors.push(`Line ${block.lineNumber}: ${error.message}`);
console.log(` ❌ Code block at line ${block.lineNumber}: ${error.message}`);
}
}
this.results.push(result);
console.log();
}
private extractCodeBlocks(content: string, filePath: string): CodeBlock[] {
const blocks: CodeBlock[] = [];
const lines = content.split('\n');
let inCodeBlock = false;
let currentBlock: string[] = [];
let language = '';
let startLine = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('```')) {
if (inCodeBlock) {
// End of code block
if (language === 'typescript' || language === 'ts' || language === 'javascript' || language === 'js') {
blocks.push({
language,
content: currentBlock.join('\n'),
file: filePath,
lineNumber: startLine
});
}
inCodeBlock = false;
currentBlock = [];
} else {
// Start of code block
language = line.slice(3).trim();
startLine = i + 1;
inCodeBlock = true;
}
} else if (inCodeBlock) {
currentBlock.push(line);
}
}
return blocks;
}
private async validateCodeBlock(block: CodeBlock): Promise<void> {
// Skip non-executable blocks
if (this.shouldSkipBlock(block.content)) {
return;
}
// Check for syntax errors
await this.checkSyntax(block);
// Check for API consistency
this.checkAPIConsistency(block);
// Check imports
this.checkImports(block);
// Check types
this.checkTypes(block);
}
private shouldSkipBlock(content: string): boolean {
const skipPatterns = [
/\/\/ Skip test/,
/\/\* Skip test/,
/interface\s+\w+/,
/type\s+\w+\s*=/,
/declare\s+/,
/export\s+interface/,
/export\s+type/,
/^\s*\/\//, // Comment-only blocks
/^\s*\*\//, // Comment blocks
/Configuration/i, // Configuration examples
/\.\.\.$/m, // Incomplete examples
];
return skipPatterns.some(pattern => pattern.test(content));
}
private async checkSyntax(block: CodeBlock): Promise<void> {
// Create temporary file
const tempFile = path.join('/tmp', `doc-test-${Date.now()}.ts`);
try {
// Add necessary imports for framework code
const fullCode = this.addNecessaryImports(block.content);
fs.writeFileSync(tempFile, fullCode);
// Check syntax with TypeScript compiler
await execAsync(`npx tsc --noEmit --target es2020 --moduleResolution node ${tempFile}`);
} catch (error) {
// Clean up syntax error messages
const cleanError = this.cleanCompilerError(error.message);
throw new Error(`Syntax error: ${cleanError}`);
} finally {
// Clean up temp file
if (fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
}
}
}
private addNecessaryImports(code: string): string {
const imports = [
"import { BaseModel, Model, Field, HasMany, BelongsTo, HasOne, ManyToMany } from '../../../src/framework/models/decorators';",
"import { BeforeCreate, AfterCreate, BeforeUpdate, AfterUpdate, BeforeDelete, AfterDelete } from '../../../src/framework/models/decorators/hooks';",
"import { DebrosFramework } from '../../../src/framework/DebrosFramework';",
"import { QueryBuilder } from '../../../src/framework/query/QueryBuilder';",
"",
"// Mock types for documentation examples",
"interface ValidationError extends Error { field: string; constraint: string; }",
"interface DatabaseError extends Error { }",
"interface ValidationResult { valid: boolean; errors: ValidationError[]; }",
"interface PaginatedResult<T> { data: T[]; total: number; page: number; perPage: number; totalPages: number; hasNext: boolean; hasPrev: boolean; }",
"",
"// Mock functions for examples",
"async function setupOrbitDB(): Promise<any> { return {}; }",
"async function setupIPFS(): Promise<any> { return {}; }",
"",
].join('\n');
return imports + '\n' + code;
}
private cleanCompilerError(error: string): string {
return error
.replace(/\/tmp\/doc-test-\d+\.ts/g, 'example')
.replace(/error TS\d+:/g, '')
.split('\n')
.filter(line => line.trim() && !line.includes('Found'))
.slice(0, 3) // Take first few error lines
.join(' ')
.trim();
}
private checkAPIConsistency(block: CodeBlock): void {
const problematicPatterns = [
{
pattern: /User\.where\(/,
message: 'Use User.query().where() instead of static User.where()',
fix: 'Replace with User.query().where()'
},
{
pattern: /User\.orderBy\(/,
message: 'Use User.query().orderBy() instead of static User.orderBy()',
fix: 'Replace with User.query().orderBy()'
},
{
pattern: /User\.limit\(/,
message: 'Use User.query().limit() instead of static User.limit()',
fix: 'Replace with User.query().limit()'
},
{
pattern: /@Field\(\s*\{\s*type:\s*(String|Number|Boolean|Array|Object)/,
message: 'Field types should be strings, not constructors',
fix: 'Use @Field({ type: "string" }) instead of @Field({ type: String })'
},
{
pattern: /getQueryExecutor\(\)/,
message: 'getQueryExecutor() method does not exist in current implementation',
fix: 'Remove or replace with available methods'
}
];
for (const { pattern, message, fix } of problematicPatterns) {
if (pattern.test(block.content)) {
this.validationErrors.push({
type: 'api',
message: `${message}. ${fix}`,
file: block.file,
line: block.lineNumber
});
throw new Error(message);
}
}
}
private checkImports(block: CodeBlock): void {
const importLines = block.content
.split('\n')
.filter(line => line.trim().startsWith('import'));
for (const importLine of importLines) {
// Check for non-existent exports
if (importLine.includes('from \'@debros/network\'')) {
const invalidImports = [
'QueryExecutor',
'ValidationError',
'DatabaseError',
'PaginatedResult'
];
for (const invalidImport of invalidImports) {
if (importLine.includes(invalidImport)) {
this.validationErrors.push({
type: 'import',
message: `${invalidImport} is not exported from @debros/network`,
file: block.file,
line: block.lineNumber
});
throw new Error(`Invalid import: ${invalidImport}`);
}
}
}
}
}
private checkTypes(block: CodeBlock): void {
// Check for undefined types used in examples
const undefinedTypes = [
/: QueryPlan/,
/: ComponentStatus/,
/: MigrationContext/,
/: SlowQuery/,
/: QueryStats/
];
for (const pattern of undefinedTypes) {
if (pattern.test(block.content)) {
const match = block.content.match(pattern);
if (match) {
this.validationErrors.push({
type: 'type',
message: `Type ${match[0].slice(2)} is not defined`,
file: block.file,
line: block.lineNumber
});
throw new Error(`Undefined type: ${match[0].slice(2)}`);
}
}
}
}
private generateReport(): void {
console.log('\n' + '='.repeat(60));
console.log('📊 DOCUMENTATION VALIDATION REPORT');
console.log('='.repeat(60));
let totalPassed = 0;
let totalFailed = 0;
for (const result of this.results) {
totalPassed += result.passed;
totalFailed += result.failed;
const status = result.failed === 0 ? '✅' : '❌';
const filename = path.relative(this.docsPath, result.file);
console.log(`${status} ${filename}: ${result.passed} passed, ${result.failed} failed`);
if (result.errors.length > 0) {
result.errors.forEach(error => {
console.log(`${error}`);
});
}
}
console.log('\n' + '-'.repeat(60));
console.log(`📈 SUMMARY: ${totalPassed} passed, ${totalFailed} failed`);
if (this.validationErrors.length > 0) {
console.log(`\n⚠ ${this.validationErrors.length} validation issues found:`);
const errorsByType = this.groupErrorsByType();
for (const [type, errors] of Object.entries(errorsByType)) {
console.log(`\n${type.toUpperCase()} ERRORS (${errors.length}):`);
errors.forEach(error => {
const filename = path.relative(this.docsPath, error.file);
console.log(` - ${filename}${error.line ? `:${error.line}` : ''}: ${error.message}`);
});
}
}
if (totalFailed > 0) {
console.log('\n❌ Documentation validation failed!');
console.log('Please fix the errors above before proceeding.');
process.exit(1);
} else {
console.log('\n✅ All documentation examples are valid!');
}
}
private groupErrorsByType(): Record<string, ValidationError[]> {
const groups: Record<string, ValidationError[]> = {};
for (const error of this.validationErrors) {
if (!groups[error.type]) {
groups[error.type] = [];
}
groups[error.type].push(error);
}
return groups;
}
}
// CLI Interface
async function main() {
const args = process.argv.slice(2);
const docsPath = args[0] || './docs';
console.log('🔍 DebrosFramework Documentation Test Runner');
console.log(`📁 Documentation path: ${docsPath}\n`);
if (!fs.existsSync(docsPath)) {
console.error(`❌ Documentation path not found: ${docsPath}`);
process.exit(1);
}
const runner = new DocumentationTestRunner(docsPath);
await runner.run();
}
// Auto-fix script
class DocumentationAutoFixer {
private fixes: Array<{ file: string; pattern: RegExp; replacement: string; description: string }> = [
{
file: '*',
pattern: /User\.where\(/g,
replacement: 'User.query().where(',
description: 'Convert static where calls to query builder'
},
{
file: '*',
pattern: /User\.orderBy\(/g,
replacement: 'User.query().orderBy(',
description: 'Convert static orderBy calls to query builder'
},
{
file: '*',
pattern: /User\.limit\(/g,
replacement: 'User.query().limit(',
description: 'Convert static limit calls to query builder'
},
{
file: '*',
pattern: /@Field\(\s*\{\s*type:\s*String/g,
replacement: '@Field({ type: \'string\'',
description: 'Convert String type to string'
},
{
file: '*',
pattern: /@Field\(\s*\{\s*type:\s*Number/g,
replacement: '@Field({ type: \'number\'',
description: 'Convert Number type to number'
},
{
file: '*',
pattern: /@Field\(\s*\{\s*type:\s*Boolean/g,
replacement: '@Field({ type: \'boolean\'',
description: 'Convert Boolean type to boolean'
},
{
file: '*',
pattern: /@Field\(\s*\{\s*type:\s*Array/g,
replacement: '@Field({ type: \'array\'',
description: 'Convert Array type to array'
},
{
file: '*',
pattern: /@Field\(\s*\{\s*type:\s*Object/g,
replacement: '@Field({ type: \'object\'',
description: 'Convert Object type to object'
}
];
async fixDocumentation(docsPath: string): Promise<void> {
console.log('🔧 Auto-fixing documentation issues...\n');
const mdFiles = await this.findMarkdownFiles(docsPath);
let totalFixes = 0;
for (const file of mdFiles) {
const fixes = await this.fixFile(file);
totalFixes += fixes;
}
console.log(`\n✅ Applied ${totalFixes} automatic fixes`);
}
private async findMarkdownFiles(docsPath: string): Promise<string[]> {
const files: string[] = [];
const scanDirectory = (dir: string) => {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
scanDirectory(fullPath);
} else if (item.endsWith('.md') || item.endsWith('.mdx')) {
files.push(fullPath);
}
}
};
scanDirectory(docsPath);
return files;
}
private async fixFile(filePath: string): Promise<number> {
let content = fs.readFileSync(filePath, 'utf-8');
let fixes = 0;
for (const fix of this.fixes) {
const matches = content.match(fix.pattern);
if (matches) {
content = content.replace(fix.pattern, fix.replacement);
fixes += matches.length;
console.log(`${path.relative('./docs', filePath)}: ${fix.description} (${matches.length} fixes)`);
}
}
if (fixes > 0) {
fs.writeFileSync(filePath, content);
}
return fixes;
}
}
// Add CLI command for auto-fix
if (process.argv.includes('--fix')) {
const docsPath = process.argv[process.argv.indexOf('--fix') + 1] || './docs';
const fixer = new DocumentationAutoFixer();
fixer.fixDocumentation(docsPath).catch(console.error);
} else {
main().catch(console.error);
}
export { DocumentationTestRunner, DocumentationAutoFixer };

View File

@ -166,8 +166,10 @@ class BlogAPIServer {
.offset((page - 1) * limit)
.find();
const userList = users ? users.map((u) => u.toJSON()) : [];
res.json({
users: users.map((u) => u.toJSON()),
users: userList,
page,
limit,
nodeId: this.nodeId,
@ -269,8 +271,10 @@ class BlogAPIServer {
.orderBy('name', 'asc')
.find();
const categoryList = categories || [];
res.json({
categories,
categories: categoryList,
nodeId: this.nodeId,
});
} catch (error) {
@ -378,8 +382,10 @@ class BlogAPIServer {
.offset((page - 1) * limit)
.find();
const postList = posts || [];
res.json({
posts,
posts: postList,
page,
limit,
nodeId: this.nodeId,
@ -526,8 +532,10 @@ class BlogAPIServer {
.orderBy('createdAt', 'asc')
.find();
const commentList = comments || [];
res.json({
comments,
comments: commentList,
nodeId: this.nodeId,
});
} catch (error) {
@ -620,10 +628,16 @@ class BlogAPIServer {
// Framework metrics
this.app.get('/api/metrics/framework', async (req, res, next) => {
try {
const metrics = this.framework.getMetrics();
const metrics = this.framework ? this.framework.getMetrics() : null;
const defaultMetrics = {
services: 'unknown',
environment: 'unknown',
features: 'unknown'
};
res.json({
nodeId: this.nodeId,
...metrics,
...(metrics || defaultMetrics),
timestamp: Date.now(),
});
} catch (error) {