Merge pull request 'fix-tests-sotiris-9-7' (#6) from fix-tests-sotiris-9-7 into main
Some checks failed
Publish Alpha Package to npm / publish (push) Failing after 53s

Reviewed-on: #6
This commit is contained in:
anonpenguin 2025-07-12 11:29:22 +00:00
commit 86d4c70da9
19 changed files with 3233 additions and 170 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,689 @@
# Behind the Scenes: How DebrosFramework Works with OrbitDB
This guide explains what happens under the hood when you use DebrosFramework's high-level abstractions. Understanding these internals will help you debug issues, optimize performance, and better understand the framework's architecture.
## Overview: From Models to OrbitDB
DebrosFramework provides ORM-like abstractions over OrbitDB's peer-to-peer databases. When you define models, create relationships, and run migrations, the framework translates these operations into OrbitDB database operations.
```
Your Code → DebrosFramework → OrbitDB → IPFS
```
## Model Creation and Database Mapping
### What Happens When You Define a Model
When you define a model like this:
```typescript
@Model({
scope: 'global',
type: 'docstore',
sharding: { strategy: 'hash', count: 4, key: 'id' }
})
export class User extends BaseModel {
@Field({ type: 'string', required: true })
username: string;
@Field({ type: 'string', required: true })
email: string;
}
```
**Behind the scenes:**
1. **Model Registration**: The `@Model` decorator registers the model in `ModelRegistry`
2. **Field Configuration**: `@Field` decorators are stored in a static `fields` Map on the model class
3. **Database Planning**: The framework determines what OrbitDB databases need to be created
### OrbitDB Database Creation
For the `User` model above, DebrosFramework creates:
```typescript
// Global scope, no sharding
const userDB = await orbitdb.open('global-user', 'docstore', {
accessController: {
type: 'orbitdb',
write: ['*'] // Or specific access rules
}
});
// With sharding (strategy: 'hash', count: 4)
const userShard0 = await orbitdb.open('global-user-shard-0', 'docstore');
const userShard1 = await orbitdb.open('global-user-shard-1', 'docstore');
const userShard2 = await orbitdb.open('global-user-shard-2', 'docstore');
const userShard3 = await orbitdb.open('global-user-shard-3', 'docstore');
```
### Database Naming Convention
```typescript
// Global models
`global-${modelName.toLowerCase()}`
// User-scoped models
`${userId}-${modelName.toLowerCase()}`
// Sharded models
`${scope}-${modelName.toLowerCase()}-shard-${shardIndex}`
// System databases
`${appName}-bootstrap` // Shard coordination
`${appName}-directory-shard-${i}` // User directory
`${userId}-mappings` // User's database mappings
```
## CRUD Operations Under the Hood
### Creating a Record
When you call:
```typescript
const user = await User.create({
username: 'alice',
email: 'alice@example.com'
});
```
**Behind the scenes:**
1. **Validation**: Field validators run on the data
2. **Transformation**: Field transformers process the data
3. **ID Generation**: A unique ID is generated (typically UUID)
4. **Lifecycle Hooks**: `@BeforeCreate` hooks execute
5. **Shard Selection**: If sharded, determine which shard to use
6. **OrbitDB Operation**: Data is stored in the appropriate database
```typescript
// What actually happens in OrbitDB
const database = await this.getShardForKey(user.id); // or getGlobalDatabase()
const docHash = await database.put({
_id: user.id,
username: 'alice',
email: 'alice@example.com',
_model: 'User',
_createdAt: Date.now(),
_updatedAt: Date.now()
});
```
### Reading Records
When you call:
```typescript
const user = await User.findById('user123');
```
**Behind the scenes:**
1. **Shard Resolution**: Determine which shard contains the record
2. **Database Query**: Query the appropriate OrbitDB database
3. **Data Hydration**: Convert raw OrbitDB data back to model instance
4. **Field Processing**: Apply any field transformations
```typescript
// OrbitDB operations
const shard = this.getShardForKey('user123');
const doc = await shard.get('user123');
if (doc) {
// Convert to model instance
const user = new User(doc);
user._isNew = false;
return user;
}
```
### Query Operations
When you call:
```typescript
const users = await User.query().where('isActive', true).find();
```
**Behind the scenes:**
1. **Query Planning**: Determine which databases to search
2. **Parallel Queries**: Query all relevant shards simultaneously
3. **Result Aggregation**: Combine results from multiple databases
4. **Filtering**: Apply where conditions to the aggregated results
```typescript
// Actual implementation
const shards = this.getAllShardsForModel('User');
const results = await Promise.all(
shards.map(async (shard) => {
const docs = shard.iterator().collect();
return docs.filter(doc => doc.isActive === true);
})
);
const allResults = results.flat();
return allResults.map(doc => new User(doc));
```
## Relationships and Cross-Database Operations
### How Relationships Work
When you define relationships:
```typescript
@Model({ scope: 'global', type: 'docstore' })
export class User extends BaseModel {
@HasMany(() => Post, 'authorId')
posts: Post[];
}
@Model({ scope: 'user', type: 'docstore' })
export class Post extends BaseModel {
@Field({ type: 'string', required: true })
authorId: string;
@BelongsTo(() => User, 'authorId')
author: User;
}
```
**Behind the scenes:**
Relationships are stored as foreign keys and resolved through cross-database queries:
```typescript
// User in global database
{
_id: 'user123',
username: 'alice',
_model: 'User'
}
// Posts in user-specific database
{
_id: 'post456',
title: 'My Post',
authorId: 'user123', // Foreign key reference
_model: 'Post'
}
```
### Relationship Loading
When you load relationships:
```typescript
const user = await User.findById('user123', { with: ['posts'] });
```
**Behind the scenes:**
1. **Primary Query**: Load the user from global database
2. **Relationship Resolution**: Identify related models and their databases
3. **Cross-Database Query**: Query user-specific databases for posts
4. **Data Assembly**: Attach loaded relationships to the main model
```typescript
// Implementation
const user = await globalUserDB.get('user123');
const userDB = await this.getUserDatabase('user123', 'Post');
const posts = await userDB.iterator().collect()
.filter(doc => doc.authorId === 'user123');
user.posts = posts.map(doc => new Post(doc));
```
## Sharding Implementation
### Hash Sharding
When you configure hash sharding:
```typescript
@Model({
sharding: { strategy: 'hash', count: 4, key: 'id' }
})
export class Post extends BaseModel {
// ...
}
```
**Behind the scenes:**
```typescript
// Shard selection algorithm
function getShardForKey(key: string, shardCount: number): number {
const hash = crypto.createHash('sha256').update(key).digest('hex');
const hashInt = parseInt(hash.substring(0, 8), 16);
return hashInt % shardCount;
}
// Creating a post
const post = new Post({ title: 'Hello' });
const shardIndex = getShardForKey(post.id, 4); // Returns 0-3
const database = await orbitdb.open(`user123-post-shard-${shardIndex}`, 'docstore');
await database.put(post.toJSON());
```
### User Sharding
For user-scoped models:
```typescript
@Model({
scope: 'user',
sharding: { strategy: 'user', count: 2, key: 'authorId' }
})
export class Post extends BaseModel {
// ...
}
```
**Behind the scenes:**
```typescript
// Each user gets their own set of sharded databases
const userId = 'user123';
const shardIndex = getShardForKey(post.id, 2);
const database = await orbitdb.open(`${userId}-post-shard-${shardIndex}`, 'docstore');
```
## Migration System Implementation
### What Migrations Actually Do
Since OrbitDB doesn't have traditional schema migrations, DebrosFramework implements them differently:
```typescript
const migration = createMigration('add_user_bio', '1.1.0')
.addField('User', 'bio', { type: 'string', required: false })
.transformData('User', (user) => ({
...user,
bio: user.bio || 'No bio provided'
}))
.build();
```
**Behind the scenes:**
1. **Migration Tracking**: Store migration state in a special database
2. **Data Transformation**: Read all records, transform them, and write back
3. **Schema Updates**: Update model field configurations
4. **Validation**: Ensure all data conforms to new schema
```typescript
// Migration implementation
async function runMigration(migration: Migration) {
// Track migration state
const migrationDB = await orbitdb.open('migrations', 'docstore');
// For each target model
for (const modelName of migration.targetModels) {
const databases = await this.getAllDatabasesForModel(modelName);
for (const database of databases) {
// Read all documents
const docs = await database.iterator().collect();
// Transform each document
for (const doc of docs) {
const transformed = migration.transform(doc);
await database.put(transformed);
}
}
}
// Record migration as completed
await migrationDB.put({
_id: migration.id,
version: migration.version,
appliedAt: Date.now(),
status: 'completed'
});
}
```
## User Directory and Database Discovery
### How User Databases Are Discovered
DebrosFramework maintains a distributed directory system:
```typescript
// Bootstrap database (shared across network)
const bootstrap = await orbitdb.open('myapp-bootstrap', 'keyvalue');
// Directory shards for user mappings
const dirShard0 = await orbitdb.open('myapp-directory-shard-0', 'keyvalue');
const dirShard1 = await orbitdb.open('myapp-directory-shard-1', 'keyvalue');
const dirShard2 = await orbitdb.open('myapp-directory-shard-2', 'keyvalue');
const dirShard3 = await orbitdb.open('myapp-directory-shard-3', 'keyvalue');
```
**Behind the scenes:**
When a user creates their first record:
1. **User Database Creation**: Create user-specific databases
2. **Mapping Storage**: Store database addresses in user's mappings database
3. **Directory Registration**: Register user's mappings database in global directory
4. **Shard Distribution**: Use consistent hashing to distribute users across directory shards
```typescript
// Creating user databases
async function createUserDatabases(userId: string) {
// Create mappings database
const mappingsDB = await orbitdb.open(`${userId}-mappings`, 'keyvalue');
// Create model databases
const postDB = await orbitdb.open(`${userId}-post`, 'docstore');
const commentDB = await orbitdb.open(`${userId}-comment`, 'docstore');
// Store mappings
await mappingsDB.set('mappings', {
postDB: postDB.address.toString(),
commentDB: commentDB.address.toString()
});
// Register in global directory
const dirShard = this.getDirectoryShardForUser(userId);
await dirShard.set(userId, mappingsDB.address.toString());
return { mappingsDB, postDB, commentDB };
}
```
## Caching and Performance Optimization
### Query Caching
When you enable query caching:
```typescript
const framework = new DebrosFramework({
features: { queryCache: true }
});
```
**Behind the scenes:**
```typescript
// Cache key generation
function generateCacheKey(query: QueryBuilder): string {
return crypto
.createHash('md5')
.update(JSON.stringify({
model: query.modelName,
where: query.whereConditions,
orderBy: query.orderByConditions,
limit: query.limitValue
}))
.digest('hex');
}
// Query execution with caching
async function executeQuery(query: QueryBuilder) {
const cacheKey = generateCacheKey(query);
// Check cache first
const cached = await this.queryCache.get(cacheKey);
if (cached) {
return cached.map(data => new query.ModelClass(data));
}
// Execute query
const results = await this.executeQueryOnDatabases(query);
// Cache results
await this.queryCache.set(cacheKey, results.map(r => r.toJSON()), 300000);
return results;
}
```
### Relationship Caching
```typescript
// Relationship cache
const relationshipCache = new Map();
async function loadRelationship(model: BaseModel, relationshipName: string) {
const cacheKey = `${model.constructor.name}:${model.id}:${relationshipName}`;
if (relationshipCache.has(cacheKey)) {
return relationshipCache.get(cacheKey);
}
// Load relationship from databases
const related = await this.queryRelatedModels(model, relationshipName);
// Cache for 5 minutes
relationshipCache.set(cacheKey, related);
setTimeout(() => relationshipCache.delete(cacheKey), 300000);
return related;
}
```
## Automatic Pinning Strategy
### How Pinning Works
When you enable automatic pinning:
```typescript
const framework = new DebrosFramework({
features: { automaticPinning: true }
});
```
**Behind the scenes:**
```typescript
// Pinning manager tracks access patterns
class PinningManager {
private accessCount = new Map<string, number>();
private pinned = new Set<string>();
async trackAccess(address: string) {
const count = this.accessCount.get(address) || 0;
this.accessCount.set(address, count + 1);
// Pin popular content
if (count > 10 && !this.pinned.has(address)) {
await this.ipfs.pin.add(address);
this.pinned.add(address);
console.log(`📌 Pinned popular content: ${address}`);
}
}
async evaluatePinning() {
// Run every hour
setInterval(() => {
this.unpinStaleContent();
this.pinPopularContent();
}, 3600000);
}
}
```
## PubSub and Real-time Updates
### Event Publishing
When models change:
```typescript
// After successful database operation
async function publishModelEvent(event: ModelEvent) {
const topic = `debros:${event.modelName}:${event.type}`;
await this.pubsub.publish(topic, JSON.stringify(event));
}
// Model creation
await User.create({ username: 'alice' });
// Publishes to: "debros:User:create"
// Model update
await user.save();
// Publishes to: "debros:User:update"
```
### Event Subscription
```typescript
// Subscribe to model events
pubsub.subscribe('debros:User:*', (message) => {
const event = JSON.parse(message);
console.log(`User ${event.type}: ${event.modelId}`);
// Invalidate related caches
this.invalidateUserCache(event.modelId);
});
```
## Database Synchronization
### How Peers Sync Data
OrbitDB handles the low-level synchronization, but DebrosFramework optimizes it:
```typescript
// Replication configuration
const database = await orbitdb.open('global-user', 'docstore', {
replicate: true,
sync: true,
accessController: {
type: 'orbitdb',
write: ['*']
}
});
// Custom sync logic
database.events.on('peer', (peer) => {
console.log(`👥 Peer connected: ${peer}`);
});
database.events.on('replicated', (address) => {
console.log(`🔄 Replicated data from: ${address}`);
// Invalidate caches, trigger UI updates
this.invalidateCaches();
});
```
## Error Handling and Recovery
### Database Connection Failures
```typescript
// Retry logic for database operations
async function withRetry<T>(operation: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries) throw error;
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Max retries exceeded');
}
// Usage
const user = await withRetry(() => User.findById('user123'));
```
### Data Corruption Recovery
```typescript
// Validate data integrity
async function validateDatabase(database: Database) {
const docs = await database.iterator().collect();
for (const doc of docs) {
try {
// Validate against model schema
const model = new modelClass(doc);
await model.validate();
} catch (error) {
console.warn(`⚠️ Invalid document found: ${doc._id}`, error);
// Mark for repair or removal
}
}
}
```
## Performance Monitoring
### Internal Metrics
```typescript
// Performance tracking
class MetricsCollector {
private queryTimes = new Map<string, number[]>();
private operationCounts = new Map<string, number>();
recordQueryTime(operation: string, duration: number) {
const times = this.queryTimes.get(operation) || [];
times.push(duration);
this.queryTimes.set(operation, times.slice(-100)); // Keep last 100
}
getAverageQueryTime(operation: string): number {
const times = this.queryTimes.get(operation) || [];
return times.reduce((a, b) => a + b, 0) / times.length;
}
getMetrics() {
return {
queryTimes: Object.fromEntries(this.queryTimes),
operationCounts: Object.fromEntries(this.operationCounts),
cacheHitRates: this.getCacheHitRates(),
databaseSizes: this.getDatabaseSizes()
};
}
}
```
## Debugging and Introspection
### Debug Mode
```typescript
const framework = new DebrosFramework({
monitoring: { logLevel: 'debug' }
});
// Enables detailed logging
// 🔍 Query: User.findById(user123) -> shard-2
// 📊 Cache miss: query-hash-abc123
// 🔄 Database operation: put user123 -> completed in 45ms
// 📡 PubSub: published User:create event
```
### Database Inspection
```typescript
// Access raw OrbitDB databases for debugging
const framework = getFramework();
const databaseManager = framework.getDatabaseManager();
// List all databases
const databases = await databaseManager.getAllDatabases();
console.log('Databases:', Array.from(databases.keys()));
// Inspect database contents
const userDB = await databaseManager.getGlobalDatabase('User');
const docs = await userDB.iterator().collect();
console.log('User documents:', docs);
// Check database addresses
console.log('Database address:', userDB.address.toString());
```
This behind-the-scenes documentation helps developers understand the complexity that DebrosFramework abstracts away while providing the transparency needed for debugging and optimization.

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

@ -24,6 +24,7 @@ const sidebars: SidebarsConfig = {
'core-concepts/architecture',
'core-concepts/models',
'core-concepts/decorators',
'core-concepts/database-management',
],
},
{
@ -31,6 +32,23 @@ const sidebars: SidebarsConfig = {
label: 'Query System',
items: [
'query-system/query-builder',
'query-system/relationships',
],
},
{
type: 'category',
label: 'Advanced Topics',
items: [
'advanced/migrations',
'advanced/performance',
'advanced/automatic-pinning',
],
},
{
type: 'category',
label: 'Guides',
items: [
'guides/migration-guide',
],
},
{
@ -38,6 +56,17 @@ const sidebars: SidebarsConfig = {
label: 'Examples',
items: [
'examples/basic-usage',
'examples/complex-queries',
'examples/migrations',
'examples/social-platform',
'examples/working-examples',
],
},
{
type: 'category',
label: 'Internals',
items: [
'internals/behind-the-scenes',
],
},
{
@ -54,6 +83,10 @@ const sidebars: SidebarsConfig = {
'contributing/overview',
'contributing/development-setup',
'contributing/code-guidelines',
'contributing/community',
'contributing/documentation-guide',
'contributing/testing-guide',
'contributing/release-process',
],
},
],
@ -68,6 +101,28 @@ const sidebars: SidebarsConfig = {
'api/debros-framework',
'api/base-model',
'api/query-builder',
'api/query-executor',
'api/database-manager',
'api/shard-manager',
'api/relationship-manager',
],
},
{
type: 'category',
label: 'Migration System',
items: [
'api/migration-builder',
'api/migration-manager',
],
},
{
type: 'category',
label: 'Decorators',
items: [
'api/decorators/model',
'api/decorators/field',
'api/decorators/relationships',
'api/decorators/hooks',
],
},
{

View File

@ -26,6 +26,9 @@ import { MigrationManager } from './migrations/MigrationManager';
import { FrameworkConfig } from './types/framework';
export interface DebrosFrameworkConfig extends FrameworkConfig {
// Application identity
appName?: string;
// Environment settings
environment?: 'development' | 'production' | 'test';
@ -265,7 +268,8 @@ export class DebrosFramework {
console.log('🔧 Initializing core components...');
// Database Manager
this.databaseManager = new DatabaseManager(this.orbitDBService!);
const appName = this.config.appName || 'debros-app';
this.databaseManager = new DatabaseManager(this.orbitDBService!, appName);
await this.databaseManager.initializeAllDatabases();
console.log('✅ DatabaseManager initialized');

View File

@ -17,9 +17,11 @@ export class DatabaseManager {
private globalDatabases: Map<string, any> = new Map();
private globalDirectoryShards: any[] = [];
private initialized: boolean = false;
private appName: string;
constructor(orbitDBService: FrameworkOrbitDBService) {
constructor(orbitDBService: FrameworkOrbitDBService, appName: string = 'debros-app') {
this.orbitDBService = orbitDBService;
this.appName = appName.toLowerCase().replace(/[^a-z0-9-]/g, '-'); // Sanitize app name
}
async initializeAllDatabases(): Promise<void> {
@ -61,23 +63,179 @@ export class DatabaseManager {
private async initializeSystemDatabases(): Promise<void> {
console.log('🔧 Creating system databases...');
// Create global user directory shards
// Create global user directory shards that are shared across all nodes
const DIRECTORY_SHARD_COUNT = 4; // Configurable
// Use deterministic approach for shared shards
await this.initializeSharedShards(DIRECTORY_SHARD_COUNT);
for (let i = 0; i < DIRECTORY_SHARD_COUNT; i++) {
const shardName = `global-user-directory-shard-${i}`;
console.log(`✅ Initialized ${this.globalDirectoryShards.length} directory shards`);
}
private async initializeSharedShards(shardCount: number): Promise<void> {
console.log(`🔧 Initializing ${shardCount} shared directory shards...`);
// First, create or connect to a bootstrap database for sharing shard addresses
const bootstrapDB = await this.getOrCreateBootstrapDB();
// Implement leader election to prevent race conditions
let shardAddresses: string[] = [];
let isLeader = false;
try {
// Try to become the leader by atomically setting a leader flag
const nodeId = this.getNodeId();
const leaderKey = 'shard-leader';
const shardAddressKey = 'shard-addresses';
// Check if someone is already the leader and has published shards
try {
const existingShards = await bootstrapDB.get(shardAddressKey);
if (existingShards && Array.isArray(existingShards) && existingShards.length === shardCount) {
shardAddresses = existingShards;
console.log(`📡 Found existing shard addresses in bootstrap database`);
}
} catch (error) {
console.log(`🔍 No existing shard addresses found`);
}
if (shardAddresses.length === 0) {
// Try to become the leader
try {
const existingLeader = await bootstrapDB.get(leaderKey);
if (!existingLeader) {
// No leader yet, try to become one
await bootstrapDB.set(leaderKey, { nodeId, timestamp: Date.now() });
console.log(`👑 Became shard leader: ${nodeId}`);
isLeader = true;
} else {
console.log(`🔍 Another node is already the leader: ${existingLeader.nodeId}`);
// Wait a bit for the leader to create shards
await new Promise(resolve => setTimeout(resolve, 2000));
// Try again to get shard addresses
try {
const shards = await bootstrapDB.get(shardAddressKey);
if (shards && Array.isArray(shards) && shards.length === shardCount) {
shardAddresses = shards;
console.log(`📡 Found shard addresses published by leader`);
}
} catch (error) {
console.warn(`⚠️ Leader did not publish shards, will create our own`);
}
}
} catch (error) {
console.log(`🔍 Failed to check/set leader, proceeding anyway`);
}
}
} catch (error) {
console.warn(`⚠️ Bootstrap coordination failed, creating local shards:`, error);
}
if (shardAddresses.length === shardCount) {
// Connect to existing shards
await this.connectToExistingShards(shardAddresses);
} else {
// Create new shards (either as leader or fallback)
await this.createAndPublishShards(shardCount, bootstrapDB, isLeader);
}
console.log(`✅ Initialized ${this.globalDirectoryShards.length} directory shards`);
}
private async getOrCreateBootstrapDB(): Promise<any> {
const bootstrapName = `${this.appName}-bootstrap`;
try {
// Create a well-known bootstrap database that all nodes of this app can access
const bootstrapDB = await this.createDatabase(bootstrapName, 'keyvalue', 'system');
// Wait a moment for potential replication
await new Promise(resolve => setTimeout(resolve, 500));
console.log(`🔧 Connected to bootstrap database: ${bootstrapName}`);
return bootstrapDB;
} catch (error) {
console.error(`❌ Failed to connect to bootstrap database:`, error);
throw error;
}
}
private async connectToExistingShards(shardAddresses: string[]): Promise<void> {
console.log(`📡 Connecting to ${shardAddresses.length} existing shards...`);
for (let i = 0; i < shardAddresses.length; i++) {
try {
const shard = await this.openDatabaseByAddress(shardAddresses[i]);
this.globalDirectoryShards.push(shard);
console.log(`✓ Connected to existing directory shard ${i}: ${shardAddresses[i]}`);
} catch (error) {
console.error(`❌ Failed to connect to shard ${i} at ${shardAddresses[i]}:`, error);
throw new Error(`Failed to connect to existing shard ${i}`);
}
}
}
private async createAndPublishShards(shardCount: number, bootstrapDB: any, isLeader: boolean = false): Promise<void> {
const roleText = isLeader ? 'as leader' : 'as fallback';
console.log(`🔧 Creating ${shardCount} new directory shards ${roleText}...`);
const shardAddresses: string[] = [];
for (let i = 0; i < shardCount; i++) {
const shardName = `${this.appName}-directory-shard-${i}`;
try {
const shard = await this.createDatabase(shardName, 'keyvalue', 'system');
await this.waitForDatabaseSync(shard);
this.globalDirectoryShards.push(shard);
console.log(`✓ Created directory shard: ${shardName}`);
shardAddresses.push(shard.address);
console.log(`✓ Created directory shard ${i}: ${shardName} at ${shard.address}`);
} catch (error) {
console.error(`❌ Failed to create directory shard ${shardName}:`, error);
console.error(`❌ Failed to create directory shard ${i}:`, error);
throw error;
}
}
// Publish shard addresses to bootstrap database (especially important if we're the leader)
if (isLeader || shardAddresses.length > 0) {
try {
await bootstrapDB.set('shard-addresses', shardAddresses);
const publishText = isLeader ? 'as leader' : 'as fallback';
console.log(`📡 Published ${shardAddresses.length} shard addresses to bootstrap database ${publishText}`);
} catch (error) {
console.warn(`⚠️ Failed to publish shard addresses to bootstrap database:`, error);
// Don't fail the whole process if we can't publish
}
}
}
private getNodeId(): string {
// Try to get a unique node identifier
return process.env.NODE_ID || process.env.HOSTNAME || `node-${Math.random().toString(36).substr(2, 9)}`;
}
private async openDatabaseByAddress(address: string): Promise<any> {
try {
// Check if we already have this database cached by address
if (this.databases.has(address)) {
return this.databases.get(address);
}
console.log(`✅ Created ${this.globalDirectoryShards.length} directory shards`);
// Open database by address
const orbitdb = this.orbitDBService.getOrbitDB();
const db = await orbitdb.open(address);
// Cache the database
this.databases.set(address, db);
return db;
} catch (error) {
console.error(`Failed to open database at address ${address}:`, error);
throw new Error(`Database opening failed: ${error}`);
}
}
async createUserDatabases(userId: string): Promise<UserMappingsData> {
@ -204,24 +362,7 @@ export class DatabaseManager {
}
private async openDatabase(address: string): Promise<any> {
try {
// Check if we already have this database cached by address
if (this.databases.has(address)) {
return this.databases.get(address);
}
// Open database by address (implementation may vary based on OrbitDB version)
const orbitdb = this.orbitDBService.getOrbitDB();
const db = await orbitdb.open(address);
// Cache the database
this.databases.set(address, db);
return db;
} catch (error) {
console.error(`Failed to open database at address ${address}:`, error);
throw new Error(`Database opening failed: ${error}`);
}
return await this.openDatabaseByAddress(address);
}
private async registerUserInDirectory(userId: string, mappingsAddress: string): Promise<void> {
@ -234,6 +375,10 @@ export class DatabaseManager {
try {
await shard.set(userId, mappingsAddress);
// Wait for the registration to be replicated across nodes
await this.waitForDatabaseSync(shard);
console.log(`✓ Registered user ${userId} in directory shard ${shardIndex}`);
} catch (error) {
console.error(`Failed to register user ${userId} in directory:`, error);
@ -241,6 +386,42 @@ export class DatabaseManager {
}
}
private async waitForDatabaseSync(database: any): Promise<void> {
// Wait for OrbitDB database to be synced across the network
// This ensures that data is replicated before proceeding
const maxWaitTime = 1000; // Reduced to 1 second max wait
const checkInterval = 50; // Check every 50ms
const startTime = Date.now();
// Wait for the database to be ready and have peers
while (Date.now() - startTime < maxWaitTime) {
try {
// Check if database is accessible and has been replicated
if (database && database.access) {
// For OrbitDB, we can check if the database is ready
await new Promise(resolve => setTimeout(resolve, checkInterval));
// Additional check for peer connectivity if available
if (database.replicationStatus) {
const status = database.replicationStatus();
if (status.buffered === 0 && status.queued === 0) {
break; // Database is synced
}
} else {
// Basic wait to ensure replication
if (Date.now() - startTime > 200) { // Reduced minimum wait to 200ms
break;
}
}
}
} catch (error) {
// Ignore sync check errors, continue with basic wait
}
await new Promise(resolve => setTimeout(resolve, checkInterval));
}
}
private getShardIndex(key: string, shardCount: number): number {
// Simple hash-based sharding
let hash = 0;
@ -351,6 +532,40 @@ export class DatabaseManager {
}
}
async getDocument(database: any, dbType: StoreType, id: string): Promise<any> {
try {
switch (dbType) {
case 'keyvalue':
return await database.get(id);
case 'docstore':
return await database.get(id);
case 'eventlog':
// For eventlog, we need to search through entries
const iterator = database.iterator();
const entries = iterator.collect();
return entries.find((entry: any) => entry.id === id || entry._id === id);
case 'feed':
// For feed, we need to search through entries
const feedIterator = database.iterator();
const feedEntries = feedIterator.collect();
return feedEntries.find((entry: any) => entry.id === id || entry._id === id);
case 'counter':
// Counter doesn't have individual documents
return database.id === id ? { value: database.value, id: database.id } : null;
default:
throw new Error(`Unsupported database type: ${dbType}`);
}
} catch (error) {
console.error(`Error fetching document ${id} from ${dbType} database:`, error);
return null;
}
}
// Cleanup methods
async stop(): Promise<void> {
console.log('🛑 Stopping DatabaseManager...');

View File

@ -6,7 +6,7 @@ export class ModelRegistry {
private static models: Map<string, typeof BaseModel> = new Map();
private static configs: Map<string, ModelConfig> = new Map();
static register(name: string, modelClass: typeof BaseModel, config: ModelConfig): void {
static register(name: string, modelClass: typeof BaseModel, config: ModelConfig = {}): void {
this.models.set(name, modelClass);
this.configs.set(name, config);

View File

@ -35,14 +35,19 @@ export abstract class BaseModel {
key !== '_isNew' &&
data[key] !== undefined
) {
// Always set directly - the Field decorator's setter will handle validation and transformation
try {
(this as any)[key] = data[key];
} catch (error) {
console.error(`Error setting field ${key}:`, error);
// If Field setter fails, set the private key directly
// Check if this is a field defined in the model
const modelClass = this.constructor as typeof BaseModel;
if (modelClass.fields && modelClass.fields.has(key)) {
// For model fields, store in private field
const privateKey = `_${key}`;
(this as any)[privateKey] = data[key];
} else {
// For non-field properties, set directly
try {
(this as any)[key] = data[key];
} catch (error) {
console.error(`Error setting property ${key}:`, error);
}
}
}
});
@ -60,21 +65,30 @@ export abstract class BaseModel {
private cleanupShadowingProperties(): void {
const modelClass = this.constructor as typeof BaseModel;
// For each field, ensure no instance properties are shadowing prototype getters
// For each field, ensure proper getters and setters
for (const [fieldName] of modelClass.fields) {
// If there's an instance property, remove it and create a working getter
const privateKey = `_${fieldName}`;
let existingValue = (this as any)[privateKey];
// If there's an instance property, remove it and preserve its value
if (this.hasOwnProperty(fieldName)) {
const _oldValue = (this as any)[fieldName];
delete (this as any)[fieldName];
// Use the instance property value if the private field doesn't exist
if (existingValue === undefined) {
existingValue = _oldValue;
(this as any)[privateKey] = _oldValue;
}
}
// Define a working getter directly on the instance
// Always ensure the field has proper getters/setters
if (!this.hasOwnProperty(fieldName)) {
Object.defineProperty(this, fieldName, {
get: () => {
const privateKey = `_${fieldName}`;
return (this as any)[privateKey];
},
set: (value: any) => {
const privateKey = `_${fieldName}`;
(this as any)[privateKey] = value;
this.markFieldAsModified(fieldName);
},
@ -440,10 +454,14 @@ export abstract class BaseModel {
const errors: string[] = [];
const modelClass = this.constructor as typeof BaseModel;
// Validate each field using private keys (more reliable)
// Validate each field using getter values (more reliable)
for (const [fieldName, fieldConfig] of modelClass.fields) {
const privateKey = `_${fieldName}`;
const value = (this as any)[privateKey];
const privateValue = (this as any)[privateKey];
const propertyValue = (this as any)[fieldName];
// Use the property value (getter) if available, otherwise use private value
const value = propertyValue !== undefined ? propertyValue : privateValue;
const fieldErrors = await this.validateField(fieldName, value, fieldConfig);
errors.push(...fieldErrors);
@ -900,7 +918,7 @@ export abstract class BaseModel {
}
static query<T extends BaseModel>(this: typeof BaseModel & (new (data?: any) => T)): any {
const { QueryBuilder } = require('../query/QueryBuilder');
// Use the imported QueryBuilder directly
return new QueryBuilder(this);
}

View File

@ -113,8 +113,6 @@ export class QueryExecutor<T extends BaseModel> {
private async executeUserSpecificQuery(userFilter: QueryCondition): Promise<T[]> {
const userIds = userFilter.operator === 'userIn' ? userFilter.value : [userFilter.value];
console.log(`👤 Querying user databases for ${userIds.length} users`);
const results: T[] = [];
// Query each user's database in parallel
@ -127,7 +125,7 @@ export class QueryExecutor<T extends BaseModel> {
return await this.queryDatabase(userDB, this.model.storeType);
} catch (error) {
console.warn(`Failed to query user ${userId} database:`, error);
// Silently handle user database query failures
return [];
}
});
@ -143,8 +141,6 @@ export class QueryExecutor<T extends BaseModel> {
}
private async executeGlobalIndexQuery(): Promise<T[]> {
console.log(`📇 Querying global index for ${this.model.name}`);
// Query global index for user-scoped models
const globalIndexName = `${this.model.modelName}GlobalIndex`;
const indexShards = this.framework.shardManager.getAllShards(globalIndexName);
@ -175,9 +171,111 @@ export class QueryExecutor<T extends BaseModel> {
// It's expensive but ensures completeness
console.warn(`⚠️ Executing expensive all-users query for ${this.model.name}`);
// This would require getting all user IDs from the directory
// For now, return empty array and log warning
console.warn('All-users query not implemented - please ensure global indexes are set up');
try {
// Get all entity IDs from the directory shards
const entityIds = await this.getAllEntityIdsFromDirectory();
if (entityIds.length === 0) {
console.warn('No entities found in directory shards');
// Try alternative discovery methods when directory shards are empty
return await this.executeAlternativeDiscovery();
}
const results: T[] = [];
// Query each entity's database in parallel (in batches to avoid overwhelming the system)
const batchSize = 10;
for (let i = 0; i < entityIds.length; i += batchSize) {
const batch = entityIds.slice(i, i + batchSize);
const batchPromises = batch.map(async (entityId: string) => {
try {
const entityDB = await this.framework.databaseManager.getUserDatabase(
entityId,
this.model.modelName,
);
return await this.queryDatabase(entityDB, this.model.storeType);
} catch (error) {
// Silently handle entity database query failures
return [];
}
});
const batchResults = await Promise.all(batchPromises);
for (const entityResult of batchResults) {
results.push(...entityResult);
}
}
return this.postProcessResults(results);
} catch (error) {
console.error('Error executing all-entities query:', error);
return [];
}
}
private async executeAlternativeDiscovery(): Promise<T[]> {
// Alternative discovery method when directory shards are not working
// This is a temporary workaround for the cross-node synchronization issue
console.warn(`🔄 Attempting alternative entity discovery for ${this.model.name}`);
try {
// Try to find entities in the local node's cached user mappings
const localResults = await this.queryLocalUserMappings();
if (localResults.length > 0) {
console.log(`📂 Found ${localResults.length} entities via local discovery`);
return localResults;
}
// If no local results, try to query known database patterns
return await this.queryKnownDatabasePatterns();
} catch (error) {
console.warn('Alternative discovery failed:', error);
return [];
}
}
private async queryLocalUserMappings(): Promise<T[]> {
// Query user mappings that are cached locally
try {
const databaseManager = this.framework.databaseManager;
const results: T[] = [];
// Get cached user mappings from the database manager
const userMappings = (databaseManager as any).userMappings;
if (userMappings && userMappings.size > 0) {
console.log(`📂 Found ${userMappings.size} cached user mappings`);
// Query each cached user's database
for (const [userId, mappings] of userMappings.entries()) {
try {
const userDB = await databaseManager.getUserDatabase(userId, this.model.modelName);
const userResults = await this.queryDatabase(userDB, this.model.storeType);
results.push(...userResults);
} catch (error) {
// Silently handle user database query failures
}
}
}
return results;
} catch (error) {
console.warn('Local user mappings query failed:', error);
return [];
}
}
private async queryKnownDatabasePatterns(): Promise<T[]> {
// Try to query databases using known patterns
// This is a fallback when directory discovery fails
console.warn(`🔍 Attempting known database pattern queries for ${this.model.name}`);
// For now, return empty array to prevent delays
// In a more sophisticated implementation, this could:
// 1. Try common user ID patterns
// 2. Use IPFS to discover databases
// 3. Query peer nodes directly
return [];
}
@ -192,8 +290,6 @@ export class QueryExecutor<T extends BaseModel> {
}
private async executeShardedQuery(): Promise<T[]> {
console.log(`🔀 Executing sharded query for ${this.model.name}`);
const conditions = this.query.getConditions();
const shardingConfig = this.model.sharding!;
@ -616,6 +712,90 @@ export class QueryExecutor<T extends BaseModel> {
};
}
private async getAllEntityIdsFromDirectory(): Promise<string[]> {
const maxRetries = 2; // Reduced retry count to prevent long delays
const baseDelay = 50; // Reduced base delay
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const directoryShards = await this.framework.databaseManager.getGlobalDirectoryShards();
const entityIds: string[] = [];
// Query all directory shards - simplified approach
const shardPromises = directoryShards.map(async (shard: any, index: number) => {
try {
// For keyvalue stores, we need to get the keys (entity IDs), not values
const shardData = shard.all();
const keys = Object.keys(shardData);
return keys;
} catch (error) {
console.warn(`Failed to read directory shard ${index}:`, error);
return [];
}
});
const shardResults = await Promise.all(shardPromises);
// Flatten all entity IDs from all shards
for (const shardEntityIds of shardResults) {
entityIds.push(...shardEntityIds);
}
// Remove duplicates and filter out empty strings
const uniqueEntityIds = [...new Set(entityIds.filter(id => id && id.trim()))];
// If we found entities, return them
if (uniqueEntityIds.length > 0) {
console.log(`📂 Found ${uniqueEntityIds.length} entities in directory shards`);
return uniqueEntityIds;
}
// If this is our last attempt, return empty array
if (attempt === maxRetries) {
console.warn('📂 No entities found in directory shards after all attempts');
return [];
}
// Wait before retry with linear backoff (shorter delays)
const delay = baseDelay * (attempt + 1);
console.log(`📂 No entities found, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries + 1})`);
await new Promise(resolve => setTimeout(resolve, delay));
} catch (error) {
console.error(`Error getting entity IDs from directory (attempt ${attempt + 1}):`, error);
if (attempt === maxRetries) {
return [];
}
// Wait before retry
const delay = baseDelay * (attempt + 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
return [];
}
private async waitForShardReady(shard: any): Promise<void> {
// Wait briefly for the shard to be ready for reading
const maxWait = 200; // ms
const startTime = Date.now();
while (Date.now() - startTime < maxWait) {
try {
if (shard && shard.all) {
// Try to access the shard data
shard.all();
break;
}
} catch (error) {
// Continue waiting
}
await new Promise(resolve => setTimeout(resolve, 20));
}
}
private getFrameworkInstance(): any {
const framework = (globalThis as any).__debrosFramework;
if (!framework) {

View File

@ -36,11 +36,6 @@ export class FrameworkOrbitDBService {
}
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))}`);
}

View File

@ -51,28 +51,11 @@ class BlogAPIServer {
// Logging
this.app.use((req, res, next) => {
console.log(`[${this.nodeId}] ${new Date().toISOString()} ${req.method} ${req.path}`);
if (req.method === 'POST' && req.body) {
console.log(`[${this.nodeId}] Request body:`, JSON.stringify(req.body, null, 2));
}
next();
});
// Error handling
this.app.use(
(error: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error(`[${this.nodeId}] Error:`, error);
if (error instanceof ValidationError) {
return res.status(400).json({
error: error.message,
field: error.field,
nodeId: this.nodeId,
});
}
res.status(500).json({
error: 'Internal server error',
nodeId: this.nodeId,
});
},
);
}
private setupRoutes() {
@ -101,20 +84,47 @@ class BlogAPIServer {
this.setupPostRoutes();
this.setupCommentRoutes();
this.setupMetricsRoutes();
// Error handling middleware must be defined after all routes
this.app.use(
(error: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error(`[${this.nodeId}] Error:`, error);
if (error instanceof ValidationError) {
return res.status(400).json({
error: error.message,
field: error.field,
nodeId: this.nodeId,
});
}
res.status(500).json({
error: 'Internal server error',
nodeId: this.nodeId,
});
},
);
}
private setupUserRoutes() {
// Create user
this.app.post('/api/users', async (req, res, next) => {
try {
console.log(`[${this.nodeId}] Received user creation request:`, JSON.stringify(req.body, null, 2));
const sanitizedData = BlogValidation.sanitizeUserInput(req.body);
console.log(`[${this.nodeId}] Sanitized user data:`, JSON.stringify(sanitizedData, null, 2));
BlogValidation.validateUser(sanitizedData);
console.log(`[${this.nodeId}] User validation passed`);
const user = await User.create(sanitizedData);
console.log(`[${this.nodeId}] User created successfully:`, JSON.stringify(user, null, 2));
console.log(`[${this.nodeId}] Created user: ${user.getFieldValue('username')} (${user.id})`);
console.log(`[${this.nodeId}] Created user: ${user.username} (${user.id})`);
res.status(201).json(user);
} catch (error) {
console.error(`[${this.nodeId}] Error creating user:`, error);
next(error);
}
});
@ -150,18 +160,20 @@ class BlogAPIServer {
.orWhere('displayName', 'like', `%${search}%`);
}
const users = await query
.orderBy('createdAt', 'desc')
.limit(limit)
.offset((page - 1) * limit)
.find();
const users = await query
.orderBy('createdAt', 'desc')
.limit(limit)
.offset((page - 1) * limit)
.find();
res.json({
users: users.map((u) => u.toJSON()),
page,
limit,
nodeId: this.nodeId,
});
const userList = users ? users.map((u) => u.toJSON()) : [];
res.json({
users: userList,
page,
limit,
nodeId: this.nodeId,
});
} catch (error) {
next(error);
}
@ -221,14 +233,32 @@ class BlogAPIServer {
// Create category
this.app.post('/api/categories', async (req, res, next) => {
try {
console.log(`[${this.nodeId}] Received category creation request:`, JSON.stringify(req.body, null, 2));
const sanitizedData = BlogValidation.sanitizeCategoryInput(req.body);
console.log(`[${this.nodeId}] Sanitized category data:`, JSON.stringify(sanitizedData, null, 2));
// Generate slug if not provided
if (!sanitizedData.slug && sanitizedData.name) {
sanitizedData.slug = sanitizedData.name
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, '')
.replace(/--+/g, '-')
.replace(/^-|-$/g, '');
console.log(`[${this.nodeId}] Generated slug: ${sanitizedData.slug}`);
}
BlogValidation.validateCategory(sanitizedData);
console.log(`[${this.nodeId}] Category validation passed`);
const category = await Category.create(sanitizedData);
console.log(`[${this.nodeId}] Category created successfully:`, JSON.stringify(category, null, 2));
console.log(`[${this.nodeId}] Created category: ${category.getFieldValue('name')} (${category.id})`);
console.log(`[${this.nodeId}] Created category: ${category.name} (${category.id})`);
res.status(201).json(category);
} catch (error) {
console.error(`[${this.nodeId}] Error creating category:`, error);
next(error);
}
});
@ -236,15 +266,17 @@ class BlogAPIServer {
// Get all categories
this.app.get('/api/categories', async (req, res, next) => {
try {
const categories = await Category.query()
.where('isActive', true)
.orderBy('name', 'asc')
.find();
const categories = await Category.query()
.where('isActive', true)
.orderBy('name', 'asc')
.find();
res.json({
categories,
nodeId: this.nodeId,
});
const categoryList = categories || [];
res.json({
categories: categoryList,
nodeId: this.nodeId,
});
} catch (error) {
next(error);
}
@ -272,11 +304,23 @@ class BlogAPIServer {
this.app.post('/api/posts', async (req, res, next) => {
try {
const sanitizedData = BlogValidation.sanitizePostInput(req.body);
// Generate slug if not provided
if (!sanitizedData.slug && sanitizedData.title) {
sanitizedData.slug = sanitizedData.title
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, '')
.replace(/--+/g, '-')
.replace(/^-|-$/g, '');
console.log(`[${this.nodeId}] Generated slug: ${sanitizedData.slug}`);
}
BlogValidation.validatePost(sanitizedData);
const post = await Post.create(sanitizedData);
console.log(`[${this.nodeId}] Created post: ${post.getFieldValue('title')} (${post.id})`);
console.log(`[${this.nodeId}] Created post: ${post.title} (${post.id})`);
res.status(201).json(post);
} catch (error) {
next(error);
@ -338,8 +382,10 @@ class BlogAPIServer {
.offset((page - 1) * limit)
.find();
const postList = posts || [];
res.json({
posts,
posts: postList,
page,
limit,
nodeId: this.nodeId,
@ -352,7 +398,9 @@ class BlogAPIServer {
// Update post
this.app.put('/api/posts/:id', async (req, res, next) => {
try {
const post = await Post.findById(req.params.id);
const post = await Post.query()
.where('id', req.params.id)
.first();
if (!post) {
return res.status(404).json({
error: 'Post not found',
@ -376,7 +424,9 @@ class BlogAPIServer {
// Publish post
this.app.post('/api/posts/:id/publish', async (req, res, next) => {
try {
const post = await Post.findById(req.params.id);
const post = await Post.query()
.where('id', req.params.id)
.first();
if (!post) {
return res.status(404).json({
error: 'Post not found',
@ -395,7 +445,9 @@ class BlogAPIServer {
// Unpublish post
this.app.post('/api/posts/:id/unpublish', async (req, res, next) => {
try {
const post = await Post.findById(req.params.id);
const post = await Post.query()
.where('id', req.params.id)
.first();
if (!post) {
return res.status(404).json({
error: 'Post not found',
@ -414,7 +466,9 @@ class BlogAPIServer {
// Like post
this.app.post('/api/posts/:id/like', async (req, res, next) => {
try {
const post = await Post.findById(req.params.id);
const post = await Post.query()
.where('id', req.params.id)
.first();
if (!post) {
return res.status(404).json({
error: 'Post not found',
@ -432,7 +486,9 @@ class BlogAPIServer {
// View post (increment view count)
this.app.post('/api/posts/:id/view', async (req, res, next) => {
try {
const post = await Post.findById(req.params.id);
const post = await Post.query()
.where('id', req.params.id)
.first();
if (!post) {
return res.status(404).json({
error: 'Post not found',
@ -476,8 +532,10 @@ class BlogAPIServer {
.orderBy('createdAt', 'asc')
.find();
const commentList = comments || [];
res.json({
comments,
comments: commentList,
nodeId: this.nodeId,
});
} catch (error) {
@ -488,7 +546,9 @@ class BlogAPIServer {
// Approve comment
this.app.post('/api/comments/:id/approve', async (req, res, next) => {
try {
const comment = await Comment.findById(req.params.id);
const comment = await Comment.query()
.where('id', req.params.id)
.first();
if (!comment) {
return res.status(404).json({
error: 'Comment not found',
@ -507,7 +567,9 @@ class BlogAPIServer {
// Like comment
this.app.post('/api/comments/:id/like', async (req, res, next) => {
try {
const comment = await Comment.findById(req.params.id);
const comment = await Comment.query()
.where('id', req.params.id)
.first();
if (!comment) {
return res.status(404).json({
error: 'Comment not found',
@ -566,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) {
@ -651,6 +719,7 @@ class BlogAPIServer {
// Initialize framework
this.framework = new DebrosFramework({
appName: 'blog-app', // Unique app name for this blog application
environment: 'test',
features: {
autoMigration: true,

View File

@ -200,12 +200,18 @@ export class Category extends BaseModel {
@BeforeCreate()
generateSlug() {
console.log(`[DEBUG] generateSlug called for category: ${this.name}`);
console.log(`[DEBUG] Current slug: ${this.slug}`);
if (!this.slug && this.name) {
this.slug = this.name
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, '');
.replace(/[^a-z0-9-]/g, '')
.replace(/--+/g, '-')
.replace(/^-|-$/g, '');
console.log(`[DEBUG] Generated slug: ${this.slug}`);
}
console.log(`[DEBUG] Final slug: ${this.slug}`);
}
}
@ -411,6 +417,7 @@ export interface CreateUserRequest {
export interface CreateCategoryRequest {
name: string;
slug?: string;
description?: string;
color?: string;
}
@ -445,3 +452,24 @@ export interface UpdatePostRequest {
// Initialize field configurations after all models are defined
setupFieldConfigurations();
// Ensure static properties are set properly (for Docker environment)
UserProfile.modelName = 'UserProfile';
UserProfile.storeType = 'docstore';
UserProfile.scope = 'global';
User.modelName = 'User';
User.storeType = 'docstore';
User.scope = 'global';
Category.modelName = 'Category';
Category.storeType = 'docstore';
Category.scope = 'global';
Post.modelName = 'Post';
Post.storeType = 'docstore';
Post.scope = 'user';
Comment.modelName = 'Comment';
Comment.storeType = 'docstore';
Comment.scope = 'user';

View File

@ -176,8 +176,8 @@ export class BlogValidation {
static sanitizeUserInput(data: CreateUserRequest): CreateUserRequest {
return {
username: this.sanitizeString(data.username),
email: this.sanitizeString(data.email.toLowerCase()),
username: data.username ? this.sanitizeString(data.username) : '',
email: data.email ? this.sanitizeString(data.email.toLowerCase()) : '',
displayName: data.displayName ? this.sanitizeString(data.displayName) : undefined,
avatar: data.avatar ? this.sanitizeString(data.avatar) : undefined,
roles: data.roles ? this.sanitizeArray(data.roles) : undefined
@ -186,7 +186,8 @@ export class BlogValidation {
static sanitizeCategoryInput(data: CreateCategoryRequest): CreateCategoryRequest {
return {
name: this.sanitizeString(data.name),
name: data.name ? this.sanitizeString(data.name) : '',
slug: data.slug ? this.sanitizeString(data.slug) : undefined,
description: data.description ? this.sanitizeString(data.description) : undefined,
color: data.color ? this.sanitizeString(data.color) : undefined
};