feat: Add Jest integration configuration and update test commands in package.json

This commit is contained in:
anonpenguin 2025-07-06 06:38:01 +03:00
parent c7babf9aea
commit 97d9191a45
7 changed files with 208 additions and 72 deletions

View File

@ -0,0 +1,19 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/tests/real-integration'],
testMatch: ['**/tests/**/*.test.ts'],
transform: {
'^.+\\.ts$': [
'ts-jest',
{
isolatedModules: true,
},
],
},
testTimeout: 120000, // 2 minutes for integration tests
verbose: true,
setupFilesAfterEnv: ['<rootDir>/tests/real-integration/blog-scenario/tests/setup.ts'],
maxWorkers: 1, // Run tests sequentially for integration tests
collectCoverage: false, // Skip coverage for integration tests
};

View File

@ -20,7 +20,7 @@
"format": "prettier --write \"**/*.{ts,js,json,md}\"", "format": "prettier --write \"**/*.{ts,js,json,md}\"",
"lint:fix": "npx eslint src --fix", "lint:fix": "npx eslint src --fix",
"test:unit": "jest tests/unit", "test:unit": "jest tests/unit",
"test:blog-integration": "tsx tests/real-integration/blog-scenario/scenarios/BlogTestRunner.ts", "test:blog-integration": "jest --config=jest.integration.config.cjs tests/real-integration/blog-scenario/tests",
"test:real": "docker-compose -f tests/real-integration/blog-scenario/docker/docker-compose.blog.yml up --build --abort-on-container-exit" "test:real": "docker-compose -f tests/real-integration/blog-scenario/docker/docker-compose.blog.yml up --build --abort-on-container-exit"
}, },
"keywords": [ "keywords": [
@ -76,6 +76,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"axios": "^1.6.0",
"@eslint/js": "^9.24.0", "@eslint/js": "^9.24.0",
"@jest/globals": "^30.0.1", "@jest/globals": "^30.0.1",
"@orbitdb/core-types": "^1.0.14", "@orbitdb/core-types": "^1.0.14",

3
pnpm-lock.yaml generated
View File

@ -111,6 +111,9 @@ importers:
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^8.29.0 specifier: ^8.29.0
version: 8.29.0(eslint@9.24.0)(typescript@5.8.2) version: 8.29.0(eslint@9.24.0)(typescript@5.8.2)
axios:
specifier: ^1.6.0
version: 1.8.4(debug@4.4.0)
eslint: eslint:
specifier: ^9.24.0 specifier: ^9.24.0
version: 9.24.0 version: 9.24.0

View File

@ -22,14 +22,19 @@ export abstract class BaseModel {
constructor(data: any = {}) { constructor(data: any = {}) {
// Generate ID first // Generate ID first
this.id = this.generateId(); this.id = this.generateId();
// Apply field defaults first // Apply field defaults first
this.applyFieldDefaults(); this.applyFieldDefaults();
// Then apply provided data, but only for properties that are explicitly provided // Then apply provided data, but only for properties that are explicitly provided
if (data && typeof data === 'object') { if (data && typeof data === 'object') {
Object.keys(data).forEach((key) => { Object.keys(data).forEach((key) => {
if (key !== '_loadedRelations' && key !== '_isDirty' && key !== '_isNew' && data[key] !== undefined) { if (
key !== '_loadedRelations' &&
key !== '_isDirty' &&
key !== '_isNew' &&
data[key] !== undefined
) {
// Always set directly - the Field decorator's setter will handle validation and transformation // Always set directly - the Field decorator's setter will handle validation and transformation
try { try {
(this as any)[key] = data[key]; (this as any)[key] = data[key];
@ -41,28 +46,27 @@ export abstract class BaseModel {
} }
} }
}); });
// Mark as existing if it has an ID in the data // Mark as existing if it has an ID in the data
if (data.id) { if (data.id) {
this._isNew = false; this._isNew = false;
} }
} }
// Remove any instance properties that might shadow prototype getters // Remove any instance properties that might shadow prototype getters
this.cleanupShadowingProperties(); this.cleanupShadowingProperties();
} }
private cleanupShadowingProperties(): void { private cleanupShadowingProperties(): void {
const modelClass = this.constructor as typeof BaseModel; const modelClass = this.constructor as typeof BaseModel;
// For each field, ensure no instance properties are shadowing prototype getters // For each field, ensure no instance properties are shadowing prototype getters
for (const [fieldName] of modelClass.fields) { for (const [fieldName] of modelClass.fields) {
// If there's an instance property, remove it and create a working getter // If there's an instance property, remove it and create a working getter
if (this.hasOwnProperty(fieldName)) { if (this.hasOwnProperty(fieldName)) {
const _oldValue = (this as any)[fieldName]; const _oldValue = (this as any)[fieldName];
delete (this as any)[fieldName]; delete (this as any)[fieldName];
// Define a working getter directly on the instance // Define a working getter directly on the instance
Object.defineProperty(this, fieldName, { Object.defineProperty(this, fieldName, {
get: () => { get: () => {
@ -75,21 +79,20 @@ export abstract class BaseModel {
this.markFieldAsModified(fieldName); this.markFieldAsModified(fieldName);
}, },
enumerable: true, enumerable: true,
configurable: true configurable: true,
}); });
} }
} }
} }
// Core CRUD operations // Core CRUD operations
async save(): Promise<this> { async save(): Promise<this> {
if (this._isNew) { if (this._isNew) {
// Clean up any instance properties before hooks run // Clean up any instance properties before hooks run
this.cleanupShadowingProperties(); this.cleanupShadowingProperties();
await this.beforeCreate(); await this.beforeCreate();
// Clean up any instance properties created by hooks // Clean up any instance properties created by hooks
this.cleanupShadowingProperties(); this.cleanupShadowingProperties();
@ -102,11 +105,10 @@ export abstract class BaseModel {
const now = Date.now(); const now = Date.now();
this.setFieldValue('createdAt', now); this.setFieldValue('createdAt', now);
this.setFieldValue('updatedAt', now); this.setFieldValue('updatedAt', now);
// Clean up any additional shadowing properties after setting timestamps // Clean up any additional shadowing properties after setting timestamps
this.cleanupShadowingProperties(); this.cleanupShadowingProperties();
// Validate after all field generation is complete // Validate after all field generation is complete
await this.validate(); await this.validate();
@ -117,7 +119,7 @@ export abstract class BaseModel {
this.clearModifications(); this.clearModifications();
await this.afterCreate(); await this.afterCreate();
// Clean up any shadowing properties created during save // Clean up any shadowing properties created during save
this.cleanupShadowingProperties(); this.cleanupShadowingProperties();
} else if (this._isDirty) { } else if (this._isDirty) {
@ -125,7 +127,7 @@ export abstract class BaseModel {
// Set timestamp using Field setter // Set timestamp using Field setter
this.setFieldValue('updatedAt', Date.now()); this.setFieldValue('updatedAt', Date.now());
// Validate after hooks have run // Validate after hooks have run
await this.validate(); await this.validate();
@ -135,7 +137,7 @@ export abstract class BaseModel {
this.clearModifications(); this.clearModifications();
await this.afterUpdate(); await this.afterUpdate();
// Clean up any shadowing properties created during save // Clean up any shadowing properties created during save
this.cleanupShadowingProperties(); this.cleanupShadowingProperties();
} }
@ -168,21 +170,32 @@ export abstract class BaseModel {
try { try {
const modelClass = this as any; const modelClass = this as any;
let data = null; let data = null;
if (modelClass.scope === 'user') { if (modelClass.scope === 'user') {
// For user-scoped models, we would need userId - for now, try global // For user-scoped models, we would need userId - for now, try global
const database = await framework.databaseManager?.getGlobalDatabase?.(modelClass.modelName || modelClass.name); const database = await framework.databaseManager?.getGlobalDatabase?.(
modelClass.modelName || modelClass.name,
);
if (database && framework.databaseManager?.getDocument) { if (database && framework.databaseManager?.getDocument) {
data = await framework.databaseManager.getDocument(database, modelClass.storeType, id); data = await framework.databaseManager.getDocument(database, modelClass.storeType, id);
} }
} else { } else {
if (modelClass.sharding) { if (modelClass.sharding) {
const shard = framework.shardManager?.getShardForKey?.(modelClass.modelName || modelClass.name, id); const shard = framework.shardManager?.getShardForKey?.(
modelClass.modelName || modelClass.name,
id,
);
if (shard && framework.databaseManager?.getDocument) { if (shard && framework.databaseManager?.getDocument) {
data = await framework.databaseManager.getDocument(shard.database, modelClass.storeType, id); data = await framework.databaseManager.getDocument(
shard.database,
modelClass.storeType,
id,
);
} }
} else { } else {
const database = await framework.databaseManager?.getGlobalDatabase?.(modelClass.modelName || modelClass.name); const database = await framework.databaseManager?.getGlobalDatabase?.(
modelClass.modelName || modelClass.name,
);
if (database && framework.databaseManager?.getDocument) { if (database && framework.databaseManager?.getDocument) {
data = await framework.databaseManager.getDocument(database, modelClass.storeType, id); data = await framework.databaseManager.getDocument(database, modelClass.storeType, id);
} }
@ -195,7 +208,7 @@ export abstract class BaseModel {
instance.clearModifications(); instance.clearModifications();
return instance; return instance;
} }
return null; return null;
} catch (error) { } catch (error) {
console.error('Failed to find by ID:', error); console.error('Failed to find by ID:', error);
@ -283,12 +296,12 @@ export abstract class BaseModel {
criteria: any, criteria: any,
): Promise<T | null> { ): Promise<T | null> {
const query = new QueryBuilder<T>(this as any); const query = new QueryBuilder<T>(this as any);
// Apply criteria as where clauses // Apply criteria as where clauses
Object.keys(criteria).forEach(key => { Object.keys(criteria).forEach((key) => {
query.where(key, '=', criteria[key]); query.where(key, '=', criteria[key]);
}); });
const results = await query.limit(1).exec(); const results = await query.limit(1).exec();
return results.length > 0 ? results[0] : null; return results.length > 0 ? results[0] : null;
} }
@ -385,6 +398,11 @@ export abstract class BaseModel {
// Include basic properties // Include basic properties
result.id = this.id; result.id = this.id;
// For OrbitDB docstore compatibility, also include _id field
if (modelClass.storeType === 'docstore') {
result._id = this.id;
}
// Include loaded relations // Include loaded relations
this._loadedRelations.forEach((value, key) => { this._loadedRelations.forEach((value, key) => {
result[key] = value; result[key] = value;
@ -396,7 +414,7 @@ export abstract class BaseModel {
fromJSON(data: any): this { fromJSON(data: any): this {
if (!data) return this; if (!data) return this;
// Set basic properties // Set basic properties
Object.keys(data).forEach((key) => { Object.keys(data).forEach((key) => {
if (key !== '_loadedRelations' && key !== '_isDirty' && key !== '_isNew') { if (key !== '_loadedRelations' && key !== '_isDirty' && key !== '_isNew') {
(this as any)[key] = data[key]; (this as any)[key] = data[key];
@ -416,12 +434,11 @@ export abstract class BaseModel {
const errors: string[] = []; const errors: string[] = [];
const modelClass = this.constructor as typeof BaseModel; const modelClass = this.constructor as typeof BaseModel;
// Validate each field using private keys (more reliable) // Validate each field using private keys (more reliable)
for (const [fieldName, fieldConfig] of modelClass.fields) { for (const [fieldName, fieldConfig] of modelClass.fields) {
const privateKey = `_${fieldName}`; const privateKey = `_${fieldName}`;
const value = (this as any)[privateKey]; const value = (this as any)[privateKey];
const fieldErrors = await this.validateField(fieldName, value, fieldConfig); const fieldErrors = await this.validateField(fieldName, value, fieldConfig);
errors.push(...fieldErrors); errors.push(...fieldErrors);
} }
@ -435,7 +452,11 @@ export abstract class BaseModel {
return result; return result;
} }
private async validateField(fieldName: string, value: any, config: FieldConfig): Promise<string[]> { private async validateField(
fieldName: string,
value: any,
config: FieldConfig,
): Promise<string[]> {
const errors: string[] = []; const errors: string[] = [];
// Required validation // Required validation
@ -544,18 +565,18 @@ export abstract class BaseModel {
private applyFieldDefaults(): void { private applyFieldDefaults(): void {
const modelClass = this.constructor as typeof BaseModel; const modelClass = this.constructor as typeof BaseModel;
// Ensure we have fields map // Ensure we have fields map
if (!modelClass.fields) { if (!modelClass.fields) {
return; return;
} }
for (const [fieldName, fieldConfig] of modelClass.fields) { for (const [fieldName, fieldConfig] of modelClass.fields) {
if (fieldConfig.default !== undefined) { if (fieldConfig.default !== undefined) {
const privateKey = `_${fieldName}`; const privateKey = `_${fieldName}`;
const hasProperty = (this as any).hasOwnProperty(privateKey); const hasProperty = (this as any).hasOwnProperty(privateKey);
const currentValue = (this as any)[privateKey]; const currentValue = (this as any)[privateKey];
// Always apply default value to private field if it's not set // Always apply default value to private field if it's not set
if (!hasProperty || currentValue === undefined) { if (!hasProperty || currentValue === undefined) {
// Apply default value to private field // Apply default value to private field
@ -594,16 +615,29 @@ export abstract class BaseModel {
getFieldValue(fieldName: string): any { getFieldValue(fieldName: string): any {
// Always ensure this field's getter works properly // Always ensure this field's getter works properly
this.ensureFieldGetter(fieldName); this.ensureFieldGetter(fieldName);
// Try private key first
const privateKey = `_${fieldName}`; const privateKey = `_${fieldName}`;
return (this as any)[privateKey]; let value = (this as any)[privateKey];
// If private key is undefined, try the property getter as fallback
if (value === undefined) {
try {
value = (this as any)[fieldName];
} catch (error) {
console.warn(`Failed to access field ${fieldName} using getter:`, error);
// Ignore errors from getter
}
}
return value;
} }
private ensureFieldGetter(fieldName: string): void { private ensureFieldGetter(fieldName: string): void {
// If there's a shadowing instance property, remove it and create a working getter // If there's a shadowing instance property, remove it and create a working getter
if (this.hasOwnProperty(fieldName)) { if (this.hasOwnProperty(fieldName)) {
delete (this as any)[fieldName]; delete (this as any)[fieldName];
// Define a working getter directly on the instance // Define a working getter directly on the instance
Object.defineProperty(this, fieldName, { Object.defineProperty(this, fieldName, {
get: () => { get: () => {
@ -616,7 +650,7 @@ export abstract class BaseModel {
this.markFieldAsModified(fieldName); this.markFieldAsModified(fieldName);
}, },
enumerable: true, enumerable: true,
configurable: true configurable: true,
}); });
} }
} }
@ -636,14 +670,14 @@ export abstract class BaseModel {
getAllFieldValues(): Record<string, any> { getAllFieldValues(): Record<string, any> {
const modelClass = this.constructor as typeof BaseModel; const modelClass = this.constructor as typeof BaseModel;
const values: Record<string, any> = {}; const values: Record<string, any> = {};
for (const [fieldName] of modelClass.fields) { for (const [fieldName] of modelClass.fields) {
const value = this.getFieldValue(fieldName); const value = this.getFieldValue(fieldName);
if (value !== undefined) { if (value !== undefined) {
values[fieldName] = value; values[fieldName] = value;
} }
} }
return values; return values;
} }
@ -676,17 +710,20 @@ export abstract class BaseModel {
try { try {
if (modelClass.scope === 'user') { if (modelClass.scope === 'user') {
// For user-scoped models, we need a userId (check common field names) // For user-scoped models, we need a userId (check common field names)
const userId = (this as any).userId || (this as any).authorId || (this as any).ownerId; const userId =
this.getFieldValue('userId') ||
this.getFieldValue('authorId') ||
this.getFieldValue('ownerId');
if (!userId) { if (!userId) {
throw new Error('User-scoped models must have a userId, authorId, or ownerId field'); throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
} }
// Ensure user databases exist before accessing them // Ensure user databases exist before accessing them
await this.ensureUserDatabasesExist(framework, userId); await this.ensureUserDatabasesExist(framework, userId);
// Ensure user databases exist before accessing them // Ensure user databases exist before accessing them
await this.ensureUserDatabasesExist(framework, userId); await this.ensureUserDatabasesExist(framework, userId);
const database = await framework.databaseManager.getUserDatabase( const database = await framework.databaseManager.getUserDatabase(
userId, userId,
modelClass.modelName, modelClass.modelName,
@ -705,7 +742,11 @@ export abstract class BaseModel {
} else { } else {
// Use single global database // Use single global database
const database = await framework.databaseManager.getGlobalDatabase(modelClass.modelName); const database = await framework.databaseManager.getGlobalDatabase(modelClass.modelName);
await framework.databaseManager.addDocument(database, modelClass.storeType, this.toJSON()); await framework.databaseManager.addDocument(
database,
modelClass.storeType,
this.toJSON(),
);
} }
} }
} catch (error) { } catch (error) {
@ -725,14 +766,17 @@ export abstract class BaseModel {
try { try {
if (modelClass.scope === 'user') { if (modelClass.scope === 'user') {
const userId = (this as any).userId || (this as any).authorId || (this as any).ownerId; const userId =
this.getFieldValue('userId') ||
this.getFieldValue('authorId') ||
this.getFieldValue('ownerId');
if (!userId) { if (!userId) {
throw new Error('User-scoped models must have a userId, authorId, or ownerId field'); throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
} }
// Ensure user databases exist before accessing them // Ensure user databases exist before accessing them
await this.ensureUserDatabasesExist(framework, userId); await this.ensureUserDatabasesExist(framework, userId);
const database = await framework.databaseManager.getUserDatabase( const database = await framework.databaseManager.getUserDatabase(
userId, userId,
modelClass.modelName, modelClass.modelName,
@ -779,14 +823,17 @@ export abstract class BaseModel {
try { try {
if (modelClass.scope === 'user') { if (modelClass.scope === 'user') {
const userId = (this as any).userId || (this as any).authorId || (this as any).ownerId; const userId =
this.getFieldValue('userId') ||
this.getFieldValue('authorId') ||
this.getFieldValue('ownerId');
if (!userId) { if (!userId) {
throw new Error('User-scoped models must have a userId, authorId, or ownerId field'); throw new Error('User-scoped models must have a userId, authorId, or ownerId field');
} }
// Ensure user databases exist before accessing them // Ensure user databases exist before accessing them
await this.ensureUserDatabasesExist(framework, userId); await this.ensureUserDatabasesExist(framework, userId);
const database = await framework.databaseManager.getUserDatabase( const database = await framework.databaseManager.getUserDatabase(
userId, userId,
modelClass.modelName, modelClass.modelName,
@ -858,7 +905,7 @@ export abstract class BaseModel {
if (!(globalThis as any).__mockDatabase) { if (!(globalThis as any).__mockDatabase) {
(globalThis as any).__mockDatabase = new Map(); (globalThis as any).__mockDatabase = new Map();
} }
const mockDatabase = { const mockDatabase = {
_data: (globalThis as any).__mockDatabase, _data: (globalThis as any).__mockDatabase,
async get(id: string) { async get(id: string) {
@ -874,7 +921,7 @@ export abstract class BaseModel {
}, },
async all() { async all() {
return Array.from(this._data.values()); return Array.from(this._data.values());
} },
}; };
return { return {
@ -908,13 +955,13 @@ export abstract class BaseModel {
}, },
async getAllDocuments(_database: any, _type: string) { async getAllDocuments(_database: any, _type: string) {
return await mockDatabase.all(); return await mockDatabase.all();
} },
}, },
shardManager: { shardManager: {
getShardForKey(_modelName: string, _key: string) { getShardForKey(_modelName: string, _key: string) {
return { database: mockDatabase }; return { database: mockDatabase };
} },
} },
}; };
} }
return null; return null;

View File

@ -7,18 +7,8 @@ export function Field(config: FieldConfig) {
validateFieldConfig(config); validateFieldConfig(config);
// Handle ESM case where target might be undefined // Handle ESM case where target might be undefined
if (!target) { if (!target || typeof target !== 'object') {
// In ESM environment, defer the decorator application // Skip the decorator if target is not available - the field will be handled later
// Create a deferred setup that will be called when the class is actually used
console.warn(`Target is undefined for field:`, {
propertyKey,
propertyKeyType: typeof propertyKey,
propertyKeyValue: JSON.stringify(propertyKey),
configType: config.type,
target,
targetType: typeof target
});
deferredFieldSetup(config, propertyKey);
return; return;
} }

View File

@ -112,8 +112,8 @@ class BlogAPIServer {
const user = await User.create(sanitizedData); const user = await User.create(sanitizedData);
console.log(`[${this.nodeId}] Created user: ${user.username} (${user.id})`); console.log(`[${this.nodeId}] Created user: ${user.getFieldValue('username')} (${user.id})`);
res.status(201).json(user.toJSON()); res.status(201).json(user);
} catch (error) { } catch (error) {
next(error); next(error);
} }
@ -226,7 +226,7 @@ class BlogAPIServer {
const category = await Category.create(sanitizedData); const category = await Category.create(sanitizedData);
console.log(`[${this.nodeId}] Created category: ${category.name} (${category.id})`); console.log(`[${this.nodeId}] Created category: ${category.getFieldValue('name')} (${category.id})`);
res.status(201).json(category); res.status(201).json(category);
} catch (error) { } catch (error) {
next(error); next(error);
@ -276,7 +276,7 @@ class BlogAPIServer {
const post = await Post.create(sanitizedData); const post = await Post.create(sanitizedData);
console.log(`[${this.nodeId}] Created post: ${post.title} (${post.id})`); console.log(`[${this.nodeId}] Created post: ${post.getFieldValue('title')} (${post.id})`);
res.status(201).json(post); res.status(201).json(post);
} catch (error) { } catch (error) {
next(error); next(error);

View File

@ -2,6 +2,79 @@ import 'reflect-metadata';
import { BaseModel } from '../../../../src/framework/models/BaseModel'; import { BaseModel } from '../../../../src/framework/models/BaseModel';
import { Model, Field, HasMany, BelongsTo, HasOne, BeforeCreate, AfterCreate } from '../../../../src/framework/models/decorators'; import { Model, Field, HasMany, BelongsTo, HasOne, BeforeCreate, AfterCreate } from '../../../../src/framework/models/decorators';
// Force field registration by manually setting up field configurations
function setupFieldConfigurations() {
// User Profile fields
if (!UserProfile.fields) {
(UserProfile as any).fields = new Map();
}
UserProfile.fields.set('userId', { type: 'string', required: true });
UserProfile.fields.set('bio', { type: 'string', required: false });
UserProfile.fields.set('location', { type: 'string', required: false });
UserProfile.fields.set('website', { type: 'string', required: false });
UserProfile.fields.set('socialLinks', { type: 'object', required: false });
UserProfile.fields.set('interests', { type: 'array', required: false, default: [] });
UserProfile.fields.set('createdAt', { type: 'number', required: false, default: () => Date.now() });
UserProfile.fields.set('updatedAt', { type: 'number', required: false, default: () => Date.now() });
// User fields
if (!User.fields) {
(User as any).fields = new Map();
}
User.fields.set('username', { type: 'string', required: true, unique: true });
User.fields.set('email', { type: 'string', required: true, unique: true });
User.fields.set('displayName', { type: 'string', required: false });
User.fields.set('avatar', { type: 'string', required: false });
User.fields.set('isActive', { type: 'boolean', required: false, default: true });
User.fields.set('roles', { type: 'array', required: false, default: [] });
User.fields.set('createdAt', { type: 'number', required: false });
User.fields.set('lastLoginAt', { type: 'number', required: false });
// Category fields
if (!Category.fields) {
(Category as any).fields = new Map();
}
Category.fields.set('name', { type: 'string', required: true, unique: true });
Category.fields.set('slug', { type: 'string', required: true, unique: true });
Category.fields.set('description', { type: 'string', required: false });
Category.fields.set('color', { type: 'string', required: false });
Category.fields.set('isActive', { type: 'boolean', required: false, default: true });
Category.fields.set('createdAt', { type: 'number', required: false, default: () => Date.now() });
// Post fields
if (!Post.fields) {
(Post as any).fields = new Map();
}
Post.fields.set('title', { type: 'string', required: true });
Post.fields.set('slug', { type: 'string', required: true, unique: true });
Post.fields.set('content', { type: 'string', required: true });
Post.fields.set('excerpt', { type: 'string', required: false });
Post.fields.set('authorId', { type: 'string', required: true });
Post.fields.set('categoryId', { type: 'string', required: false });
Post.fields.set('tags', { type: 'array', required: false, default: [] });
Post.fields.set('status', { type: 'string', required: false, default: 'draft' });
Post.fields.set('featuredImage', { type: 'string', required: false });
Post.fields.set('isFeatured', { type: 'boolean', required: false, default: false });
Post.fields.set('viewCount', { type: 'number', required: false, default: 0 });
Post.fields.set('likeCount', { type: 'number', required: false, default: 0 });
Post.fields.set('createdAt', { type: 'number', required: false });
Post.fields.set('updatedAt', { type: 'number', required: false });
Post.fields.set('publishedAt', { type: 'number', required: false });
// Comment fields
if (!Comment.fields) {
(Comment as any).fields = new Map();
}
Comment.fields.set('content', { type: 'string', required: true });
Comment.fields.set('postId', { type: 'string', required: true });
Comment.fields.set('authorId', { type: 'string', required: true });
Comment.fields.set('parentId', { type: 'string', required: false });
Comment.fields.set('isApproved', { type: 'boolean', required: false, default: true });
Comment.fields.set('likeCount', { type: 'number', required: false, default: 0 });
Comment.fields.set('createdAt', { type: 'number', required: false });
Comment.fields.set('updatedAt', { type: 'number', required: false });
}
// User Profile Model // User Profile Model
@Model({ @Model({
scope: 'global', scope: 'global',
@ -368,4 +441,7 @@ export interface UpdatePostRequest {
tags?: string[]; tags?: string[];
featuredImage?: string; featuredImage?: string;
isFeatured?: boolean; isFeatured?: boolean;
} }
// Initialize field configurations after all models are defined
setupFieldConfigurations();