UNPKG

@wearesage/schema

Version:

A flexible schema definition and validation system for TypeScript with multi-database support

210 lines (180 loc) 6.98 kB
import { Type } from '../core/types'; import { DatabaseAdapter } from './interface'; import { MetadataRegistry } from '../core/MetadataRegistry'; import { SchemaReflector } from '../core/SchemaReflector'; /** * MongoDB-specific collection decorator */ export function Collection(name: string) { return function(target: any) { Reflect.defineMetadata('mongodb:collection', name, target); }; } /** * MongoDB-specific index decorator */ export function Index(keys: Record<string, 1 | -1 | '2dsphere' | 'text'>, options: any = {}) { return function(target: any, propertyKey?: string) { // If applied to a class, define index for the collection if (!propertyKey) { const existingIndexes = Reflect.getMetadata('mongodb:indexes', target) || []; Reflect.defineMetadata('mongodb:indexes', [...existingIndexes, { keys, options }], target); } // If applied to a property, define index for a specific field else { const existingIndexes = Reflect.getMetadata('mongodb:indexes', target.constructor) || []; const indexDef = { keys: { [propertyKey]: keys }, options }; Reflect.defineMetadata('mongodb:indexes', [...existingIndexes, indexDef], target.constructor); } }; } /** * Shorthand decorator for MongoDB entities */ export function MongoDB<T extends { new (...args: any[]): any }>(target: T): T { return DatabaseAdapter('MongoDB')(target); } /** * Implementation of DatabaseAdapter for MongoDB */ export class MongoDBAdapter implements DatabaseAdapter { readonly type = 'MongoDB'; private registry: MetadataRegistry; private reflector = new SchemaReflector(); /** * Constructor for MongoDB adapter * @param registry The metadata registry to use for schema information * @param connectionString The MongoDB connection string */ constructor(registry: MetadataRegistry, private connectionString: string) { this.registry = registry; } /** * Get the collection name for an entity type */ private getCollectionName<T>(entityType: Type<T>): string { // Get custom collection name if defined, otherwise use entity name in plural form const collectionName = Reflect.getMetadata('mongodb:collection', entityType); if (collectionName) { return collectionName; } // Simple pluralization (just add 's') return `${entityType.name.toLowerCase()}s`; } /** * Get all defined indexes for an entity type */ private getIndexes<T>(entityType: Type<T>): any[] { return Reflect.getMetadata('mongodb:indexes', entityType) || []; } /** * Convert entity to MongoDB document */ private entityToDocument<T>(entity: T): Record<string, any> { const entityType = (entity as any).constructor as Type<T>; const entitySchema = this.reflector.getEntitySchema(entityType); const document: Record<string, any> = {}; // Convert each property for (const [key, meta] of Object.entries(entitySchema.properties)) { if ((entity as any)[key] !== undefined) { document[key] = (entity as any)[key]; } } return document; } /** * Convert MongoDB document to entity instance */ private documentToEntity<T>(entityType: Type<T>, document: Record<string, any>): T { const entity = new entityType(); Object.assign(entity as any, document); return entity; } /** * Query for a single entity by criteria */ async query<T>(entityType: Type<T>, criteria: object): Promise<T | null> { const collectionName = this.getCollectionName(entityType); console.log(`[MongoDBAdapter] Querying ${collectionName} with criteria:`, criteria); // In a real implementation, this would use the MongoDB driver to execute a query // Mock implementation for demonstration purposes if ('id' in criteria) { // MongoDB uses _id as the primary key, but we'll use id for consistency const query = { id: criteria.id }; console.log(`[MongoDBAdapter] Finding document with query:`, query); // Simulate database call const result = await this.mockQueryExecution<T>(entityType, criteria); return result; } return null; } /** * Query for multiple entities by criteria */ async queryMany<T>(entityType: Type<T>, criteria: object): Promise<T[]> { const collectionName = this.getCollectionName(entityType); console.log(`[MongoDBAdapter] Querying ${collectionName} for multiple documents with criteria:`, criteria); // Mock implementation - in real code, would use MongoDB driver console.log(`[MongoDBAdapter] Finding documents with query:`, criteria); // Simulate database call return []; // Mock empty result } /** * Save an entity to MongoDB */ async save<T>(entity: T): Promise<void> { const entityType = (entity as any).constructor as Type<T>; const collectionName = this.getCollectionName(entityType); // Validate the entity before saving const validation = this.reflector.validateEntity(entity); if (!validation.valid) { throw new Error(`Invalid entity: ${validation.errors.join(', ')}`); } // Convert entity to MongoDB document const document = this.entityToDocument(entity); console.log(`[MongoDBAdapter] Saving document to collection ${collectionName}:`, document); // In a real implementation, would use MongoDB driver // const result = await collection.updateOne( // { id: document.id }, // { $set: document }, // { upsert: true } // ); } /** * Delete an entity from MongoDB */ async delete<T>(entityType: Type<T>, id: string | number): Promise<void> { const collectionName = this.getCollectionName(entityType); console.log(`[MongoDBAdapter] Deleting document with id ${id} from collection ${collectionName}`); // In a real implementation, would use MongoDB driver // const result = await collection.deleteOne({ id }); } /** * Execute a raw MongoDB command */ async runNativeQuery<T>(query: string, params?: any): Promise<T> { console.log(`[MongoDBAdapter] Running native query:`, query); console.log(`[MongoDBAdapter] Params:`, params); // In a real implementation, this would be something like: // const db = client.db(); // return db.command(JSON.parse(query)); return {} as T; // Mock result } /** * Mock method to simulate database query execution * In a real implementation, this would use the MongoDB driver */ private async mockQueryExecution<T>(entityType: Type<T>, criteria: object): Promise<T | null> { // Just a mock implementation for demonstration if ('id' in criteria && criteria.id === '123') { const entity = new entityType(); Object.assign(entity as any, { id: '123', name: 'Mock MongoDB Entity', createdAt: new Date() }); return entity; } return null; } }