diff --git a/.lintstagedrc b/.lintstagedrc index 970fbfb..520a2a7 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -2,9 +2,15 @@ "src/**/*.{js,ts}": [ "prettier --write", "eslint --fix", + "pnpm run test:unit", "npm run build" ], + "tests/**/*.{js,ts}": [ + "prettier --write", + "eslint --fix", + "pnpm run test:unit" + ], "*.{json,md}": [ "prettier --write" ] -} \ No newline at end of file +} diff --git a/docs/docs/api/base-model.md b/docs/docs/api/base-model.md new file mode 100644 index 0000000..c86735e --- /dev/null +++ b/docs/docs/api/base-model.md @@ -0,0 +1,810 @@ +--- +sidebar_position: 3 +--- + +# BaseModel Class + +The `BaseModel` class is the abstract base class for all data models in Debros Network. It provides ORM-like functionality with automatic database management, validation, relationships, and lifecycle hooks. + +## Class Definition + +```typescript +abstract class BaseModel { + // Instance properties + id: string; + createdAt?: number; + updatedAt?: number; + + // Static methods + static async create( + this: ModelConstructor, + data: Partial, + options?: CreateOptions, + ): Promise; + + static async findById( + this: ModelConstructor, + id: string, + options?: FindOptions, + ): Promise; + + static async findOne( + this: ModelConstructor, + criteria: Partial, + options?: FindOptions, + ): Promise; + + static query(this: ModelConstructor): QueryBuilder; + + // Instance methods + save(options?: SaveOptions): Promise; + delete(options?: DeleteOptions): Promise; + reload(options?: ReloadOptions): Promise; + validate(): Promise; + toJSON(): Record; + clone(): this; +} +``` + +## Static Methods + +### create(data, options?) + +Creates a new model instance and saves it to the database. + +**Parameters:** + +- `data`: Partial model data +- `options` (optional): Creation options + +**Returns:** `Promise` - The created model instance + +**Throws:** + +- `ValidationError` - If validation fails +- `DatabaseError` - If database operation fails + +**Example:** + +```typescript +import { BaseModel, Model, Field } from '@debros/network'; + +@Model({ + scope: 'global', + type: 'docstore', +}) +class User extends BaseModel { + @Field({ type: 'string', required: true, unique: true }) + username: string; + + @Field({ type: 'string', required: true, unique: true }) + email: string; + + @Field({ type: 'number', required: false, default: 0 }) + score: number; +} + +// Create a new user +const user = await User.create({ + username: 'alice', + email: 'alice@example.com', + score: 100, +}); + +console.log(user.id); // Generated ID +console.log(user.username); // 'alice' +console.log(user.createdAt); // Timestamp +``` + +**With validation:** + +```typescript +try { + const user = await User.create({ + username: 'ab', // Too short + email: 'invalid-email', // Invalid format + }); +} catch (error) { + if (error instanceof ValidationError) { + console.log('Validation failed:', error.field, error.constraint); + } +} +``` + +**With options:** + +```typescript +const user = await User.create( + { + username: 'bob', + email: 'bob@example.com', + }, + { + validate: true, + skipHooks: false, + userId: 'user123', // For user-scoped models + }, +); +``` + +### findById(id, options?) + +Finds a model instance by its ID. + +**Parameters:** + +- `id`: The model ID to search for +- `options` (optional): Find options + +**Returns:** `Promise` - The found model or null + +**Example:** + +```typescript +// Basic find +const user = await User.findById('user123'); +if (user) { + console.log('Found user:', user.username); +} else { + console.log('User not found'); +} + +// With relationships +const user = await User.findById('user123', { + with: ['posts', 'profile'], +}); + +// With specific user context +const user = await User.findById('user123', { + userId: 'current-user-id', +}); +``` + +### findOne(criteria, options?) + +Finds the first model instance matching the criteria. + +**Parameters:** + +- `criteria`: Partial model data to match +- `options` (optional): Find options + +**Returns:** `Promise` - The found model or null + +**Example:** + +```typescript +// Find by field +const user = await User.findOne({ + username: 'alice', +}); + +// Find with multiple criteria +const user = await User.findOne({ + email: 'alice@example.com', + isActive: true, +}); + +// With options +const user = await User.findOne( + { + username: 'alice', + }, + { + with: ['posts'], + cache: 300, // Cache for 5 minutes + }, +); +``` + +### query() + +Returns a query builder for complex queries. + +**Returns:** `QueryBuilder` - Query builder instance + +**Example:** + +```typescript +// Basic query +const users = await User.query() + .where('isActive', true) + .orderBy('createdAt', 'desc') + .limit(10) + .find(); + +// Complex query with relationships +const activeUsers = await User.query() + .where('isActive', true) + .where('score', '>', 100) + .where('registeredAt', '>', Date.now() - 30 * 24 * 60 * 60 * 1000) + .with(['posts.comments', 'profile']) + .orderBy('score', 'desc') + .paginate(1, 20); + +// Query with caching +const cachedUsers = await User.query() + .where('isActive', true) + .cache(600) // Cache for 10 minutes + .find(); +``` + +## Instance Methods + +### save(options?) + +Saves the current model instance to the database. + +**Parameters:** + +- `options` (optional): Save options + +**Returns:** `Promise` - The saved model instance + +**Example:** + +```typescript +const user = await User.findById('user123'); +user.email = 'newemail@example.com'; +user.score += 10; + +await user.save(); +console.log('User updated'); + +// With options +await user.save({ + validate: true, + skipHooks: false, +}); +``` + +### delete(options?) + +Deletes the model instance from the database. + +**Parameters:** + +- `options` (optional): Delete options + +**Returns:** `Promise` - True if deletion was successful + +**Example:** + +```typescript +const user = await User.findById('user123'); +if (user) { + const deleted = await user.delete(); + console.log('User deleted:', deleted); +} + +// With options +await user.delete({ + skipHooks: false, + cascade: true, // Delete related records +}); +``` + +### reload(options?) + +Reloads the model instance from the database. + +**Parameters:** + +- `options` (optional): Reload options + +**Returns:** `Promise` - The reloaded model instance + +**Example:** + +```typescript +const user = await User.findById('user123'); + +// Model might be updated elsewhere +await user.reload(); + +// With relationships +await user.reload({ + with: ['posts', 'profile'], +}); +``` + +### validate() + +Validates the current model instance. + +**Returns:** `Promise` - Validation result + +**Example:** + +```typescript +const user = new User(); +user.username = 'alice'; +user.email = 'invalid-email'; + +const result = await user.validate(); +if (!result.valid) { + console.log('Validation errors:', result.errors); + // [{ field: 'email', constraint: 'must be valid email', value: 'invalid-email' }] +} +``` + +### toJSON() + +Converts the model instance to a plain JavaScript object. + +**Returns:** `Record` - Plain object representation + +**Example:** + +```typescript +const user = await User.findById('user123'); +const userObj = user.toJSON(); + +console.log(userObj); +// { +// id: 'user123', +// username: 'alice', +// email: 'alice@example.com', +// score: 100, +// createdAt: 1234567890, +// updatedAt: 1234567890 +// } + +// Useful for JSON serialization +const jsonString = JSON.stringify(user); // Calls toJSON() automatically +``` + +### clone() + +Creates a deep copy of the model instance (without ID). + +**Returns:** `this` - Cloned model instance + +**Example:** + +```typescript +const user = await User.findById('user123'); +const userCopy = user.clone(); + +userCopy.username = 'alice_copy'; +await userCopy.save(); // Creates new record + +console.log(user.id !== userCopy.id); // true +``` + +## Lifecycle Hooks + +### Hook Decorators + +Models can define lifecycle hooks using decorators: + +```typescript +@Model({ scope: 'global', type: 'docstore' }) +class User extends BaseModel { + @Field({ type: 'string', required: true }) + username: string; + + @Field({ type: 'string', required: true }) + email: string; + + @Field({ type: 'number', required: false }) + loginCount: number = 0; + + @BeforeCreate() + async beforeCreateHook() { + this.createdAt = Date.now(); + this.updatedAt = Date.now(); + + // Validate unique username + const existing = await User.findOne({ username: this.username }); + if (existing) { + throw new ValidationError('username', this.username, 'must be unique'); + } + } + + @AfterCreate() + async afterCreateHook() { + console.log(`New user created: ${this.username}`); + + // Send welcome email + await this.sendWelcomeEmail(); + + // Create default settings + await this.createDefaultSettings(); + } + + @BeforeUpdate() + async beforeUpdateHook() { + this.updatedAt = Date.now(); + } + + @AfterUpdate() + async afterUpdateHook() { + console.log(`User updated: ${this.username}`); + } + + @BeforeDelete() + async beforeDeleteHook() { + // Clean up related data + await this.deleteRelatedPosts(); + } + + @AfterDelete() + async afterDeleteHook() { + console.log(`User deleted: ${this.username}`); + } + + // Custom methods + private async sendWelcomeEmail() { + // Implementation + } + + private async createDefaultSettings() { + // Implementation + } + + private async deleteRelatedPosts() { + // Implementation + } +} +``` + +### Available Hook Types + +| Hook | Trigger | Usage | +| ----------------- | ------------------------------- | --------------------------------------- | +| `@BeforeCreate()` | Before creating new record | Validation, default values, preparation | +| `@AfterCreate()` | After creating new record | Notifications, related record creation | +| `@BeforeUpdate()` | Before updating existing record | Validation, timestamp updates, logging | +| `@AfterUpdate()` | After updating existing record | Cache invalidation, notifications | +| `@BeforeDelete()` | Before deleting record | Cleanup, validation, logging | +| `@AfterDelete()` | After deleting record | Cleanup, notifications, cascade deletes | +| `@BeforeSave()` | Before save (create or update) | Common validation, timestamps | +| `@AfterSave()` | After save (create or update) | Common post-processing | + +## Validation + +### Field Validation + +```typescript +@Model({ scope: 'global', type: 'docstore' }) +class User extends BaseModel { + @Field({ + type: 'string', + required: true, + unique: true, + minLength: 3, + maxLength: 20, + validate: (username: string) => /^[a-zA-Z0-9_]+$/.test(username), + transform: (username: string) => username.toLowerCase(), + }) + username: string; + + @Field({ + type: 'string', + required: true, + unique: true, + validate: (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email), + transform: (email: string) => email.toLowerCase(), + }) + email: string; + + @Field({ + type: 'number', + required: false, + default: 0, + validate: (score: number) => score >= 0 && score <= 1000, + }) + score: number; + + @Field({ + type: 'array', + required: false, + default: [], + validate: (tags: string[]) => tags.length <= 10, + }) + tags: string[]; +} +``` + +### Custom Validation Methods + +```typescript +@Model({ scope: 'global', type: 'docstore' }) +class User extends BaseModel { + @Field({ type: 'string', required: true }) + username: string; + + @Field({ type: 'string', required: true }) + email: string; + + @Field({ type: 'string', required: true }) + password: string; + + // Custom validation method + async customValidation(): Promise { + const errors: ValidationError[] = []; + + // Check username availability + if (this.username) { + const existing = await User.findOne({ username: this.username }); + if (existing && existing.id !== this.id) { + errors.push(new ValidationError('username', this.username, 'already taken')); + } + } + + // Check email format and availability + if (this.email) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(this.email)) { + errors.push(new ValidationError('email', this.email, 'invalid format')); + } + + const existing = await User.findOne({ email: this.email }); + if (existing && existing.id !== this.id) { + errors.push(new ValidationError('email', this.email, 'already registered')); + } + } + + // Check password strength + if (this.password && this.password.length < 8) { + errors.push(new ValidationError('password', this.password, 'must be at least 8 characters')); + } + + return { + valid: errors.length === 0, + errors, + }; + } + + @BeforeCreate() + @BeforeUpdate() + async validateBeforeSave() { + const result = await this.customValidation(); + if (!result.valid) { + throw new ValidationError('model', this, 'custom validation failed', result.errors); + } + } +} +``` + +## Configuration Interfaces + +### ModelConstructor Type + +```typescript +type ModelConstructor = new () => T; +``` + +### Options Interfaces + +```typescript +interface CreateOptions { + validate?: boolean; + skipHooks?: boolean; + userId?: string; // For user-scoped models +} + +interface FindOptions { + with?: string[]; // Relationship names to eager load + cache?: boolean | number; // Cache result + userId?: string; // For user-scoped models +} + +interface SaveOptions { + validate?: boolean; + skipHooks?: boolean; + upsert?: boolean; +} + +interface DeleteOptions { + skipHooks?: boolean; + cascade?: boolean; // Delete related records +} + +interface ReloadOptions { + with?: string[]; // Relationship names to eager load +} +``` + +### Validation Types + +```typescript +interface ValidationResult { + valid: boolean; + errors: ValidationError[]; +} + +class ValidationError extends Error { + constructor( + public field: string, + public value: any, + public constraint: string, + public details?: any, + ) { + super(`Validation failed for field '${field}': ${constraint}`); + } +} +``` + +## Error Handling + +### Common Errors + +```typescript +// Validation errors +try { + const user = await User.create({ + username: 'a', // Too short + email: 'invalid', + }); +} catch (error) { + if (error instanceof ValidationError) { + console.log(`Field ${error.field} failed: ${error.constraint}`); + } +} + +// Not found errors +const user = await User.findById('non-existent-id'); +if (!user) { + throw new NotFoundError('User', 'non-existent-id'); +} + +// Database errors +try { + await user.save(); +} catch (error) { + if (error instanceof DatabaseError) { + console.log('Database operation failed:', error.message); + } +} +``` + +## Complete Example + +### Blog Post Model + +```typescript +import { + BaseModel, + Model, + Field, + BelongsTo, + HasMany, + BeforeCreate, + AfterCreate, +} from '@debros/network'; +import { User } from './User'; +import { Comment } from './Comment'; + +@Model({ + scope: 'user', + type: 'docstore', + sharding: { + strategy: 'user', + count: 2, + key: 'authorId', + }, +}) +export class Post extends BaseModel { + @Field({ + type: 'string', + required: true, + minLength: 1, + maxLength: 200, + }) + title: string; + + @Field({ + type: 'string', + required: true, + minLength: 1, + maxLength: 10000, + }) + content: string; + + @Field({ type: 'string', required: true }) + authorId: string; + + @Field({ + type: 'array', + required: false, + default: [], + transform: (tags: string[]) => tags.map((tag) => tag.toLowerCase()), + }) + tags: string[]; + + @Field({ type: 'boolean', required: false, default: false }) + isPublished: boolean; + + @Field({ type: 'number', required: false, default: 0 }) + viewCount: number; + + @Field({ type: 'number', required: false }) + publishedAt?: number; + + // Relationships + @BelongsTo(() => User, 'authorId') + author: User; + + @HasMany(() => Comment, 'postId') + comments: Comment[]; + + @BeforeCreate() + setupNewPost() { + this.createdAt = Date.now(); + this.updatedAt = Date.now(); + this.viewCount = 0; + } + + @AfterCreate() + async afterPostCreated() { + console.log(`New post created: ${this.title}`); + + // Update author's post count + const author = await User.findById(this.authorId); + if (author) { + author.postCount = (author.postCount || 0) + 1; + await author.save(); + } + } + + // Custom methods + async publish(): Promise { + this.isPublished = true; + this.publishedAt = Date.now(); + await this.save(); + } + + async incrementViews(): Promise { + this.viewCount += 1; + await this.save({ skipHooks: true }); // Skip hooks for performance + } + + async getCommentCount(): Promise { + return await Comment.query().where('postId', this.id).count(); + } + + async getTopComments(limit: number = 5): Promise { + return await Comment.query() + .where('postId', this.id) + .orderBy('likeCount', 'desc') + .limit(limit) + .with(['author']) + .find(); + } +} + +// Usage examples +async function blogExamples() { + // Create a post + const post = await Post.create({ + title: 'My First Post', + content: 'This is the content of my first post...', + authorId: 'user123', + tags: ['JavaScript', 'Web Development'], + }); + + // Find posts by author + const userPosts = await Post.query() + .where('authorId', 'user123') + .where('isPublished', true) + .orderBy('publishedAt', 'desc') + .with(['author', 'comments.author']) + .find(); + + // Publish a post + await post.publish(); + + // Get post with comments + const postWithComments = await Post.findById(post.id, { + with: ['author', 'comments.author'], + }); + + console.log('Post:', postWithComments?.title); + console.log('Author:', postWithComments?.author.username); + console.log('Comments:', postWithComments?.comments.length); +} +``` + +This comprehensive BaseModel documentation covers all the essential functionality for working with models in Debros Network, including CRUD operations, validation, relationships, hooks, and real-world usage examples. diff --git a/docs/docs/api/query-builder.md b/docs/docs/api/query-builder.md new file mode 100644 index 0000000..4ed69d4 --- /dev/null +++ b/docs/docs/api/query-builder.md @@ -0,0 +1,828 @@ +--- +sidebar_position: 4 +--- + +# QueryBuilder Class + +The `QueryBuilder` class provides a fluent API for constructing complex database queries. It supports filtering, sorting, relationships, pagination, and caching with type safety throughout. + +## Class Definition + +```typescript +class QueryBuilder { + // Filtering methods + where(field: string, value: any): QueryBuilder; + where(field: string, operator: QueryOperator, value: any): QueryBuilder; + whereIn(field: string, values: any[]): QueryBuilder; + whereNotIn(field: string, values: any[]): QueryBuilder; + whereNull(field: string): QueryBuilder; + whereNotNull(field: string): QueryBuilder; + whereBetween(field: string, min: any, max: any): QueryBuilder; + whereRaw(condition: string, parameters?: any[]): QueryBuilder; + + // Logical operators + and(): QueryBuilder; + or(): QueryBuilder; + not(): QueryBuilder; + + // Sorting + orderBy(field: string, direction?: 'asc' | 'desc'): QueryBuilder; + orderByRaw(orderClause: string): QueryBuilder; + + // Limiting and pagination + limit(count: number): QueryBuilder; + offset(count: number): QueryBuilder; + paginate(page: number, perPage: number): Promise>; + + // Relationships + with(relationships: string[]): QueryBuilder; + withCount(relationships: string[]): QueryBuilder; + whereHas(relationship: string, callback?: (query: QueryBuilder) => void): QueryBuilder; + whereDoesntHave(relationship: string): QueryBuilder; + + // Aggregation + count(): Promise; + sum(field: string): Promise; + avg(field: string): Promise; + min(field: string): Promise; + max(field: string): Promise; + + // Caching + cache(ttl?: number): QueryBuilder; + fresh(): QueryBuilder; + + // Execution + find(): Promise; + findOne(): Promise; + first(): Promise; + get(): Promise; + + // Advanced features + distinct(field?: string): QueryBuilder; + groupBy(field: string): QueryBuilder; + having(field: string, operator: QueryOperator, value: any): QueryBuilder; + + // Query info + toSQL(): string; + getParameters(): any[]; + explain(): Promise; +} +``` + +## Filtering Methods + +### where(field, value) / where(field, operator, value) + +Adds a WHERE condition to the query. + +**Parameters:** + +- `field`: Field name to filter on +- `value`: Value to compare (when using equals operator) +- `operator`: Comparison operator +- `value`: Value to compare against + +**Returns:** `QueryBuilder` - Builder instance for chaining + +**Example:** + +```typescript +// Basic equality +const activeUsers = await User.query().where('isActive', true).find(); + +// With operators +const highScoreUsers = await User.query() + .where('score', '>', 1000) + .where('registeredAt', '>=', Date.now() - 30 * 24 * 60 * 60 * 1000) + .find(); + +// String operations +const usersWithAlice = await User.query().where('username', 'like', '%alice%').find(); + +// Array operations +const adminUsers = await User.query().where('roles', 'includes', 'admin').find(); +``` + +### whereIn(field, values) + +Filters records where field value is in the provided array. + +**Parameters:** + +- `field`: Field name +- `values`: Array of values to match + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Find users with specific IDs +const users = await User.query().whereIn('id', ['user1', 'user2', 'user3']).find(); + +// Find posts with specific tags +const posts = await Post.query().whereIn('category', ['tech', 'programming', 'tutorial']).find(); +``` + +### whereNotIn(field, values) + +Filters records where field value is NOT in the provided array. + +**Parameters:** + +- `field`: Field name +- `values`: Array of values to exclude + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Exclude specific users +const users = await User.query().whereNotIn('status', ['banned', 'suspended']).find(); +``` + +### whereNull(field) / whereNotNull(field) + +Filters records based on null values. + +**Parameters:** + +- `field`: Field name to check + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Find users without profile pictures +const usersWithoutAvatars = await User.query().whereNull('avatarUrl').find(); + +// Find users with profile pictures +const usersWithAvatars = await User.query().whereNotNull('avatarUrl').find(); +``` + +### whereBetween(field, min, max) + +Filters records where field value is between min and max. + +**Parameters:** + +- `field`: Field name +- `min`: Minimum value (inclusive) +- `max`: Maximum value (inclusive) + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Find users with scores between 100 and 500 +const moderateUsers = await User.query().whereBetween('score', 100, 500).find(); + +// Find posts from last week +const lastWeek = Date.now() - 7 * 24 * 60 * 60 * 1000; +const recentPosts = await Post.query().whereBetween('createdAt', lastWeek, Date.now()).find(); +``` + +### whereRaw(condition, parameters?) + +Adds a raw WHERE condition. + +**Parameters:** + +- `condition`: Raw SQL-like condition string +- `parameters`: Optional parameters for the condition + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Complex condition +const users = await User.query() + .whereRaw('score > ? AND (registeredAt > ? OR isPremium = ?)', [100, lastWeek, true]) + .find(); +``` + +## Logical Operators + +### and() / or() / not() + +Combines conditions with logical operators. + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// AND (default behavior) +const premiumActiveUsers = await User.query() + .where('isActive', true) + .and() + .where('isPremium', true) + .find(); + +// OR condition +const eligibleUsers = await User.query() + .where('isPremium', true) + .or() + .where('score', '>', 1000) + .find(); + +// NOT condition +const nonAdminUsers = await User.query().not().where('role', 'admin').find(); + +// Complex combinations +const complexQuery = await User.query() + .where('isActive', true) + .and() + .group((query) => query.where('isPremium', true).or().where('score', '>', 500)) + .find(); +``` + +## Sorting Methods + +### orderBy(field, direction?) + +Orders results by specified field. + +**Parameters:** + +- `field`: Field name to sort by +- `direction`: Sort direction ('asc' or 'desc', defaults to 'asc') + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Sort by score descending +const topUsers = await User.query().orderBy('score', 'desc').limit(10).find(); + +// Multiple sort criteria +const users = await User.query().orderBy('score', 'desc').orderBy('username', 'asc').find(); + +// Sort by date +const recentPosts = await Post.query().orderBy('createdAt', 'desc').find(); +``` + +### orderByRaw(orderClause) + +Orders results using a raw ORDER BY clause. + +**Parameters:** + +- `orderClause`: Raw order clause + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +const users = await User.query().orderByRaw('score DESC, RANDOM()').find(); +``` + +## Limiting and Pagination + +### limit(count) + +Limits the number of results. + +**Parameters:** + +- `count`: Maximum number of records to return + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Get top 10 users +const topUsers = await User.query().orderBy('score', 'desc').limit(10).find(); +``` + +### offset(count) + +Skips a number of records. + +**Parameters:** + +- `count`: Number of records to skip + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Get users 11-20 (skip first 10) +const nextBatch = await User.query().orderBy('score', 'desc').offset(10).limit(10).find(); +``` + +### paginate(page, perPage) + +Paginates results and returns pagination info. + +**Parameters:** + +- `page`: Page number (1-based) +- `perPage`: Number of records per page + +**Returns:** `Promise>` + +**Example:** + +```typescript +// Get page 2 with 20 users per page +const result = await User.query().where('isActive', true).orderBy('score', 'desc').paginate(2, 20); + +console.log('Users:', result.data); +console.log('Total:', result.total); +console.log('Page:', result.page); +console.log('Per page:', result.perPage); +console.log('Total pages:', result.totalPages); +console.log('Has next:', result.hasNext); +console.log('Has previous:', result.hasPrev); +``` + +## Relationship Methods + +### with(relationships) + +Eager loads relationships. + +**Parameters:** + +- `relationships`: Array of relationship names or dot-notation for nested relationships + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Load single relationship +const usersWithPosts = await User.query().with(['posts']).find(); + +// Load multiple relationships +const usersWithData = await User.query().with(['posts', 'profile', 'followers']).find(); + +// Load nested relationships +const usersWithCommentsAuthors = await User.query().with(['posts.comments.author']).find(); + +// Mixed relationships +const complexLoad = await User.query() + .with(['posts.comments.author', 'profile', 'followers.profile']) + .find(); +``` + +### withCount(relationships) + +Loads relationship counts without loading the actual relationships. + +**Parameters:** + +- `relationships`: Array of relationship names + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +const usersWithCounts = await User.query().withCount(['posts', 'followers', 'following']).find(); + +// Access counts +usersWithCounts.forEach((user) => { + console.log(`${user.username}: ${user.postsCount} posts, ${user.followersCount} followers`); +}); +``` + +### whereHas(relationship, callback?) + +Filters records that have related records matching criteria. + +**Parameters:** + +- `relationship`: Relationship name +- `callback`: Optional callback to add conditions to the relationship query + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Users who have posts +const usersWithPosts = await User.query().whereHas('posts').find(); + +// Users who have published posts +const usersWithPublishedPosts = await User.query() + .whereHas('posts', (query) => { + query.where('isPublished', true); + }) + .find(); + +// Users who have recent posts +const usersWithRecentPosts = await User.query() + .whereHas('posts', (query) => { + query.where('createdAt', '>', Date.now() - 7 * 24 * 60 * 60 * 1000); + }) + .find(); +``` + +### whereDoesntHave(relationship) + +Filters records that don't have related records. + +**Parameters:** + +- `relationship`: Relationship name + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Users without posts +const usersWithoutPosts = await User.query().whereDoesntHave('posts').find(); +``` + +## Aggregation Methods + +### count() + +Returns the count of matching records. + +**Returns:** `Promise` + +**Example:** + +```typescript +// Count all active users +const activeUserCount = await User.query().where('isActive', true).count(); + +console.log(`Active users: ${activeUserCount}`); +``` + +### sum(field) / avg(field) / min(field) / max(field) + +Performs aggregation operations on a field. + +**Parameters:** + +- `field`: Field name to aggregate + +**Returns:** `Promise` or `Promise` for min/max + +**Example:** + +```typescript +// Calculate statistics +const totalScore = await User.query().sum('score'); +const averageScore = await User.query().avg('score'); +const highestScore = await User.query().max('score'); +const lowestScore = await User.query().min('score'); + +console.log(`Total: ${totalScore}, Average: ${averageScore}`); +console.log(`Range: ${lowestScore} - ${highestScore}`); + +// With conditions +const premiumAverage = await User.query().where('isPremium', true).avg('score'); +``` + +## Caching Methods + +### cache(ttl?) + +Caches query results. + +**Parameters:** + +- `ttl`: Time to live in seconds (optional, uses default if not provided) + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Cache for default duration +const cachedUsers = await User.query().where('isActive', true).cache().find(); + +// Cache for 10 minutes +const cachedPosts = await Post.query().where('isPublished', true).cache(600).find(); +``` + +### fresh() + +Forces a fresh query, bypassing cache. + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Always fetch fresh data +const freshUsers = await User.query().where('isActive', true).fresh().find(); +``` + +## Execution Methods + +### find() + +Executes the query and returns all matching records. + +**Returns:** `Promise` + +**Example:** + +```typescript +const users = await User.query().where('isActive', true).orderBy('score', 'desc').find(); +``` + +### findOne() / first() + +Executes the query and returns the first matching record. + +**Returns:** `Promise` + +**Example:** + +```typescript +// Find the highest scoring user +const topUser = await User.query().orderBy('score', 'desc').findOne(); + +// Find specific user +const user = await User.query().where('username', 'alice').first(); +``` + +### get() + +Alias for `find()`. + +**Returns:** `Promise` + +## Advanced Methods + +### distinct(field?) + +Returns distinct values. + +**Parameters:** + +- `field`: Optional field to get distinct values for + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Get users with distinct emails +const uniqueUsers = await User.query().distinct('email').find(); + +// Get all distinct tags from posts +const uniqueTags = await Post.query().distinct('tags').find(); +``` + +### groupBy(field) + +Groups results by field. + +**Parameters:** + +- `field`: Field to group by + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Group users by registration date +const usersByDate = await User.query().groupBy('registeredAt').withCount(['posts']).find(); +``` + +### having(field, operator, value) + +Adds HAVING condition (used with groupBy). + +**Parameters:** + +- `field`: Field name +- `operator`: Comparison operator +- `value`: Value to compare + +**Returns:** `QueryBuilder` + +**Example:** + +```typescript +// Find dates with more than 10 user registrations +const busyDays = await User.query().groupBy('registeredAt').having('COUNT(*)', '>', 10).find(); +``` + +## Query Inspection + +### toSQL() + +Returns the SQL representation of the query. + +**Returns:** `string` + +**Example:** + +```typescript +const query = User.query().where('isActive', true).orderBy('score', 'desc').limit(10); + +console.log('SQL:', query.toSQL()); +``` + +### getParameters() + +Returns the parameters for the query. + +**Returns:** `any[]` + +**Example:** + +```typescript +const query = User.query().where('score', '>', 100); + +console.log('Parameters:', query.getParameters()); +``` + +### explain() + +Returns the query execution plan. + +**Returns:** `Promise` + +**Example:** + +```typescript +const plan = await User.query().where('isActive', true).explain(); + +console.log('Query plan:', plan); +``` + +## Type Definitions + +### QueryOperator + +```typescript +type QueryOperator = + | 'eq' + | '=' + | 'ne' + | '!=' + | '<>' + | 'gt' + | '>' + | 'gte' + | '>=' + | 'lt' + | '<' + | 'lte' + | '<=' + | 'in' + | 'not in' + | 'like' + | 'not like' + | 'regex' + | 'is null' + | 'is not null' + | 'includes' + | 'includes any' + | 'includes all'; +``` + +### PaginatedResult + +```typescript +interface PaginatedResult { + data: T[]; + total: number; + page: number; + perPage: number; + totalPages: number; + hasNext: boolean; + hasPrev: boolean; + firstPage: number; + lastPage: number; +} +``` + +### QueryPlan + +```typescript +interface QueryPlan { + estimatedCost: number; + estimatedRows: number; + indexesUsed: string[]; + operations: QueryOperation[]; + warnings: string[]; +} + +interface QueryOperation { + type: string; + description: string; + cost: number; +} +``` + +## Complex Query Examples + +### Advanced Filtering + +```typescript +// Complex user search +const searchResults = await User.query() + .where('isActive', true) + .and() + .group((query) => + query + .where('username', 'like', `%${searchTerm}%`) + .or() + .where('email', 'like', `%${searchTerm}%`) + .or() + .where('bio', 'like', `%${searchTerm}%`), + ) + .and() + .group((query) => query.where('isPremium', true).or().where('score', '>', 500)) + .orderBy('score', 'desc') + .with(['profile', 'posts.comments']) + .paginate(page, 20); +``` + +### Analytics Query + +```typescript +// User activity analytics +const analytics = await User.query() + .where('registeredAt', '>', startDate) + .where('registeredAt', '<', endDate) + .whereHas('posts', (query) => { + query.where('isPublished', true); + }) + .withCount(['posts', 'comments', 'followers']) + .orderBy('score', 'desc') + .cache(300) // Cache for 5 minutes + .find(); + +// Process analytics data +const stats = { + totalUsers: analytics.length, + averageScore: analytics.reduce((sum, user) => sum + user.score, 0) / analytics.length, + totalPosts: analytics.reduce((sum, user) => sum + user.postsCount, 0), + totalComments: analytics.reduce((sum, user) => sum + user.commentsCount, 0), +}; +``` + +### Dashboard Query + +```typescript +async function getDashboardData(userId: string) { + // Multiple optimized queries + const [userStats, recentPosts, topComments, followingActivity] = await Promise.all([ + // User statistics + User.query() + .where('id', userId) + .withCount(['posts', 'comments', 'followers', 'following']) + .cache(60) // Cache for 1 minute + .first(), + + // Recent posts + Post.query() + .where('authorId', userId) + .orderBy('createdAt', 'desc') + .limit(5) + .with(['comments.author']) + .find(), + + // Top comments by user + Comment.query() + .where('authorId', userId) + .orderBy('likeCount', 'desc') + .limit(10) + .with(['post.author']) + .find(), + + // Activity from people user follows + Post.query() + .whereHas('author', (query) => { + query.whereHas('followers', (followQuery) => { + followQuery.where('followerId', userId); + }); + }) + .orderBy('createdAt', 'desc') + .limit(20) + .with(['author', 'comments']) + .find(), + ]); + + return { + userStats, + recentPosts, + topComments, + followingActivity, + }; +} +``` + +The QueryBuilder provides a powerful, type-safe way to construct complex database queries with support for relationships, caching, pagination, and advanced filtering options. diff --git a/docs/docs/videos/index.md b/docs/docs/videos/index.md new file mode 100644 index 0000000..e1aa902 --- /dev/null +++ b/docs/docs/videos/index.md @@ -0,0 +1,691 @@ +--- +sidebar_position: 6 +--- + +# Video Tutorials + +Learn Debros Network through comprehensive video tutorials. These step-by-step guides will help you master the framework from basic concepts to advanced implementations. + +## 🎬 **Getting Started Series** + +### 1. Introduction to Debros Network + +**Duration: 15 minutes** + +A complete overview of Debros Network, its architecture, and core concepts. Perfect for developers new to decentralized applications. + +**What You'll Learn:** + +- Framework architecture and components +- Key differences from traditional frameworks +- When to use Debros Network +- Development environment overview + +```typescript +// Code examples from this video +import { DebrosFramework, BaseModel, Model, Field } from '@debros/network'; + +@Model({ + scope: 'global', + type: 'docstore', +}) +class User extends BaseModel { + @Field({ type: 'string', required: true }) + username: string; +} +``` + +[**▶️ Watch Introduction Video**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 2. Setting Up Your Development Environment + +**Duration: 20 minutes** + +Step-by-step guide to setting up Debros Network in your development environment. + +**What You'll Learn:** + +- Installing dependencies +- Project structure setup +- IDE configuration +- Development tools + +**Prerequisites:** + +- Node.js 18+ +- npm or pnpm +- TypeScript knowledge + +```bash +# Commands from this video +npm create debros-app my-app +cd my-app +npm install +npm run dev +``` + +[**▶️ Watch Setup Video**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 3. Your First Debros Application + +**Duration: 25 minutes** + +Build your first decentralized application with user management and basic CRUD operations. + +**What You'll Learn:** + +- Creating models with decorators +- Database initialization +- Basic CRUD operations +- Error handling + +**Project Files:** + +- `models/User.ts` +- `models/Post.ts` +- `app.ts` + +```typescript +// Final code from this tutorial +@Model({ + scope: 'global', + type: 'docstore', + sharding: { strategy: 'hash', count: 4, key: 'id' }, +}) +export class User extends BaseModel { + @Field({ type: 'string', required: true, unique: true }) + username: string; + + @Field({ type: 'string', required: true, unique: true }) + email: string; + + @HasMany(() => Post, 'authorId') + posts: Post[]; +} +``` + +[**▶️ Watch First App Video**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +## 🏗️ **Core Concepts Series** + +### 4. Understanding Models and Decorators + +**Duration: 30 minutes** + +Deep dive into the model system, decorators, and data validation. + +**Topics Covered:** + +- Model configuration options +- Field types and validation +- Custom validators +- Lifecycle hooks +- Best practices + +```typescript +// Advanced model example from video +@Model({ + scope: 'user', + type: 'docstore', + sharding: { strategy: 'user', count: 2, key: 'userId' }, +}) +export class BlogPost extends BaseModel { + @Field({ + type: 'string', + required: true, + minLength: 5, + maxLength: 200, + validate: (title: string) => !title.includes('spam'), + }) + title: string; + + @BeforeCreate() + async validatePost() { + // Custom validation logic + } +} +``` + +[**▶️ Watch Models Deep Dive**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 5. Mastering the Query System + +**Duration: 35 minutes** + +Complete guide to building complex queries with relationships and optimization. + +**Topics Covered:** + +- Query builder API +- Filtering and sorting +- Relationship loading +- Pagination +- Query optimization +- Caching strategies + +```typescript +// Complex query example from video +const results = await User.query() + .where('isActive', true) + .where('score', '>', 100) + .whereHas('posts', (query) => { + query.where('isPublished', true).where('createdAt', '>', Date.now() - 30 * 24 * 60 * 60 * 1000); + }) + .with(['posts.comments.author', 'profile']) + .orderBy('score', 'desc') + .cache(300) + .paginate(1, 20); +``` + +[**▶️ Watch Query Mastery**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 6. Working with Relationships + +**Duration: 25 minutes** + +Understanding and implementing relationships between models. + +**Topics Covered:** + +- Relationship types (HasMany, BelongsTo, etc.) +- Eager vs lazy loading +- Nested relationships +- Performance considerations + +```typescript +// Relationship examples from video +@Model({ scope: 'global', type: 'docstore' }) +export class User extends BaseModel { + @HasMany(() => Post, 'authorId') + posts: Post[]; + + @ManyToMany(() => User, 'followers', 'following') + followers: User[]; + + @HasOne(() => UserProfile, 'userId') + profile: UserProfile; +} +``` + +[**▶️ Watch Relationships Video**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +## 🚀 **Advanced Features Series** + +### 7. Database Sharding and Scaling + +**Duration: 40 minutes** + +Learn how to scale your application with automatic sharding strategies. + +**Topics Covered:** + +- Sharding strategies +- User-scoped vs global databases +- Performance optimization +- Monitoring and metrics + +```typescript +// Sharding configuration examples +@Model({ + scope: 'user', + type: 'docstore', + sharding: { + strategy: 'hash', + count: 8, + key: 'userId', + }, +}) +export class UserData extends BaseModel { + // Model implementation +} +``` + +[**▶️ Watch Sharding Guide**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 8. Migrations and Schema Evolution + +**Duration: 30 minutes** + +Managing database schema changes and data migrations. + +**Topics Covered:** + +- Creating migrations +- Data transformations +- Rollback strategies +- Production deployment + +```typescript +// Migration example from video +const migration = createMigration('add_user_profiles', '1.1.0') + .addField('User', 'profilePicture', { + type: 'string', + required: false, + }) + .addField('User', 'bio', { + type: 'string', + required: false, + }) + .transformData('User', (user) => ({ + ...user, + displayName: user.username || 'Anonymous', + })) + .build(); +``` + +[**▶️ Watch Migrations Video**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 9. Real-time Features and PubSub + +**Duration: 25 minutes** + +Implementing real-time functionality with the built-in PubSub system. + +**Topics Covered:** + +- Event publishing +- Real-time subscriptions +- WebSocket integration +- Performance considerations + +```typescript +// Real-time examples from video +@Model({ scope: 'global', type: 'docstore' }) +export class ChatMessage extends BaseModel { + @AfterCreate() + async publishMessage() { + await this.publish('message:created', { + roomId: this.roomId, + message: this, + }); + } +} +``` + +[**▶️ Watch Real-time Video**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +## 🛠️ **Project Tutorials** + +### 10. Building a Complete Blog Application + +**Duration: 60 minutes** + +Build a full-featured blog application with authentication, posts, and comments. + +**Features Built:** + +- User authentication +- Post creation and editing +- Comment system +- User profiles +- Admin dashboard + +**Final Project Structure:** + +``` +blog-app/ +├── models/ +│ ├── User.ts +│ ├── Post.ts +│ ├── Comment.ts +│ └── Category.ts +├── services/ +│ ├── AuthService.ts +│ └── BlogService.ts +└── app.ts +``` + +[**▶️ Watch Blog Tutorial**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 11. Creating a Social Media Platform + +**Duration: 90 minutes** + +Build a decentralized social media platform with advanced features. + +**Features Built:** + +- User profiles and following +- Feed generation +- Real-time messaging +- Content moderation +- Analytics dashboard + +**Part 1: User System and Profiles** (30 min) +[**▶️ Watch Part 1**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +**Part 2: Posts and Feed** (30 min) +[**▶️ Watch Part 2**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +**Part 3: Real-time Features** (30 min) +[**▶️ Watch Part 3**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 12. E-commerce Platform with Debros Network + +**Duration: 75 minutes** + +Build a decentralized e-commerce platform with product management and orders. + +**Features Built:** + +- Product catalog +- Shopping cart +- Order processing +- Inventory management +- Payment integration + +**Part 1: Product Management** (25 min) +[**▶️ Watch Part 1**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +**Part 2: Shopping and Orders** (25 min) +[**▶️ Watch Part 2**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +**Part 3: Advanced Features** (25 min) +[**▶️ Watch Part 3**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +## 🔧 **Development Workflow Series** + +### 13. Testing Strategies for Debros Applications + +**Duration: 35 minutes** + +Comprehensive testing approaches for decentralized applications. + +**Topics Covered:** + +- Unit testing models +- Integration testing +- Mocking strategies +- Performance testing + +```typescript +// Testing example from video +describe('User Model', () => { + it('should create user with valid data', async () => { + const user = await User.create({ + username: 'testuser', + email: 'test@example.com', + }); + + expect(user.id).toBeDefined(); + expect(user.username).toBe('testuser'); + }); +}); +``` + +[**▶️ Watch Testing Video**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 14. Deployment and Production Best Practices + +**Duration: 45 minutes** + +Deploy your Debros Network applications to production environments. + +**Topics Covered:** + +- Production configuration +- Docker containers +- Monitoring and logging +- Performance optimization +- Security considerations + +```dockerfile +# Docker example from video +FROM node:18-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production +COPY . . +RUN npm run build +EXPOSE 3000 +CMD ["npm", "start"] +``` + +[**▶️ Watch Deployment Video**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 15. Performance Optimization Techniques + +**Duration: 40 minutes** + +Advanced techniques for optimizing Debros Network applications. + +**Topics Covered:** + +- Query optimization +- Caching strategies +- Database indexing +- Memory management +- Profiling tools + +```typescript +// Optimization examples from video +// Efficient query with proper indexing +const optimizedQuery = await User.query() + .where('isActive', true) // Indexed field + .with(['posts']) // Eager load to avoid N+1 + .cache(300) // Cache frequently accessed data + .limit(50) // Reasonable limits + .find(); +``` + +[**▶️ Watch Optimization Video**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +## 📱 **Integration Series** + +### 16. Frontend Integration with React + +**Duration: 50 minutes** + +Integrate Debros Network with React applications for full-stack development. + +**Topics Covered:** + +- React hooks for Debros +- State management +- Real-time updates +- Error boundaries + +```typescript +// React integration example from video +import { useDebrosQuery, useDebrosModel } from '@debros/react'; + +function UserProfile({ userId }: { userId: string }) { + const { user, loading, error } = useDebrosModel(User, userId, { + with: ['posts', 'profile'] + }); + + if (loading) return
Loading...
; + if (error) return
Error: {error.message}
; + + return ( +
+

{user.username}

+

Posts: {user.posts.length}

+
+ ); +} +``` + +[**▶️ Watch React Integration**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 17. Building APIs with Express and Debros + +**Duration: 35 minutes** + +Create REST APIs using Express.js with Debros Network as the backend. + +**Topics Covered:** + +- Express middleware +- API route design +- Authentication +- Error handling +- API documentation + +```typescript +// Express API example from video +app.get('/api/users/:id', async (req, res) => { + try { + const user = await User.findById(req.params.id, { + with: ['posts'], + }); + + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + res.json(user); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); +``` + +[**▶️ Watch Express Integration**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 18. Mobile Development with React Native + +**Duration: 45 minutes** + +Build mobile applications using React Native and Debros Network. + +**Topics Covered:** + +- React Native setup +- Offline synchronization +- Mobile-specific optimizations +- Push notifications + +[**▶️ Watch Mobile Development**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +## 🎓 **Masterclass Series** + +### 19. Architecture Patterns and Best Practices + +**Duration: 60 minutes** + +Advanced architectural patterns for large-scale Debros Network applications. + +**Topics Covered:** + +- Domain-driven design +- CQRS patterns +- Event sourcing +- Microservices architecture + +[**▶️ Watch Architecture Masterclass**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +### 20. Contributing to Debros Network + +**Duration: 30 minutes** + +Learn how to contribute to the Debros Network open-source project. + +**Topics Covered:** + +- Development setup +- Code standards +- Testing requirements +- Pull request process + +[**▶️ Watch Contributing Guide**](https://youtube.com/watch?v=VIDEO_ID_HERE) + +--- + +## 📚 **Additional Resources** + +### Video Playlists + +**🎬 [Complete Beginner Series](https://youtube.com/playlist?list=PLAYLIST_ID)** +Videos 1-6: Everything you need to get started + +**🏗️ [Advanced Development](https://youtube.com/playlist?list=PLAYLIST_ID)** +Videos 7-12: Advanced features and patterns + +**🛠️ [Project Tutorials](https://youtube.com/playlist?list=PLAYLIST_ID)** +Videos 10-12: Complete project walkthroughs + +**🔧 [Production Ready](https://youtube.com/playlist?list=PLAYLIST_ID)** +Videos 13-15: Testing, deployment, and optimization + +### Community Videos + +**Community Showcase** + +- [Building a Decentralized Chat App](https://youtube.com/watch?v=COMMUNITY_VIDEO_1) +- [E-learning Platform with Debros](https://youtube.com/watch?v=COMMUNITY_VIDEO_2) +- [IoT Data Management](https://youtube.com/watch?v=COMMUNITY_VIDEO_3) + +### Interactive Learning + +**🎮 [Interactive Tutorials](https://learn.debros.io)** +Hands-on coding exercises with instant feedback + +**💬 [Discord Community](https://discord.gg/debros)** +Get help and discuss videos with other developers + +**📖 [Workshop Materials](https://github.com/debros/workshops)** +Download code samples and workshop materials + +--- + +## 📅 **Release Schedule** + +New video tutorials are released every **Tuesday and Friday**: + +- **Tuesdays**: Core concepts and feature deep-dives +- **Fridays**: Project tutorials and real-world applications + +**Subscribe to our [YouTube channel](https://youtube.com/@debrosnetwork)** and enable notifications to stay updated with the latest tutorials. + +## 🤝 **Request a Tutorial** + +Have a specific topic you'd like covered? Request a tutorial: + +- **[GitHub Discussions](https://github.com/debros/network/discussions)** - Community requests +- **[Email Us](mailto:tutorials@debros.io)** - Direct requests +- **[Discord](https://discord.gg/debros)** - Community suggestions + +We prioritize tutorials based on community demand and framework updates. + +--- + +_All videos include closed captions, downloadable code samples, and companion blog posts for reference._ diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 922f1e1..aa484d8 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -40,6 +40,13 @@ const sidebars: SidebarsConfig = { 'examples/basic-usage', ], }, + { + type: 'category', + label: 'Video Tutorials', + items: [ + 'videos/index', + ], + }, { type: 'category', label: 'Contributing', @@ -56,9 +63,18 @@ const sidebars: SidebarsConfig = { 'api/overview', { type: 'category', - label: 'Framework Classes', + label: 'Core Classes', items: [ 'api/debros-framework', + 'api/base-model', + 'api/query-builder', + ], + }, + { + type: 'category', + label: 'Network API', + items: [ + 'api/network-api', ], }, ],