@wearesage/schema
Version:
A flexible schema definition and validation system for TypeScript with multi-database support
210 lines (180 loc) • 6.98 kB
text/typescript
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;
}
}