/// import DI, { DDNames } from '@gongt/ts-stl-library/DI'; import { ObjectId } from 'mongodb'; import { ModelUpdateOptions, Schema, SchemaOptions, } from 'mongoose'; import { inspect, InspectOptions } from 'util'; import { BaseDocument, UpdateResult } from './base.type'; import { ConnectionRegistry, databaseInitComplete, di, registerModel, testDatabaseMuted } from './connect'; import { DataDocument } from './method.type'; import { MongodbOperationImplements } from './mongodb.interface'; export function autoDetect(target: DataModel, propertyKey: 'collectionName'): void { const modelName = target.constructor.name.replace(/Model$/, ''); Object.assign(target, { [propertyKey]: modelName, }); } export interface DataModelArgs { connectionName: string; } /** * @singleton : every class name is a singleton, multiple new will return same instance. */ export abstract class DataModel extends MongodbOperationImplements { private readonly _realConnectionName: string; protected displayTitle: string; protected readonly schemaOptions: Partial; constructor({connectionName}: Partial = {}) { super(); this._realConnectionName = connectionName; this.displayTitle = `<${this.modelName}@${connectionName}>`; if (!testDatabaseMuted) { if (databaseInitComplete) { this.__init_database(); } else { registerModel(this.modelName, () => { if (!testDatabaseMuted) { this.__init_database(); } return false; }); } } if (this.log.data.enabled) { this.wrapDebug('insert'); this.wrapDebug('remove'); } this.initialization(); this.log.info('prepared: [db=%s] [table=%s]', this.connectionName, this.collectionName); } /** @internal */ public toString() { return `[BaseMongo::DataModel ${this.displayTitle}]`; } public async getInstance(idObj: ObjectId|string|DataDocument): Promise> { if (typeof idObj === 'string' || idObj instanceof ObjectId) { return await this.getById(idObj); } else { return idObj; } } public upsert(conditions: Object, doc: Object, options: ModelUpdateOptions = {}): Promise { return this.model.update(conditions, doc, { multi: false, setDefaultsOnInsert: true, ...options, upsert: true, }).exec(); } /** @internal */ public [inspect.custom](depth: number, options: InspectOptions) { const padding = ' '.repeat(2 + depth * 2); return `{ ${padding}BaseMongo::DataModel${this.displayTitle} table=${this.realConnectionName} } `; } /** @internal */ private __init_database() { const modelName = this.modelName; const connectionName = this.realConnectionName; const database: ConnectionRegistry = di.get(connectionName, null); if (!database) { throw new Error(`can not create ${modelName} instance: no connection is named ${connectionName}.`); } this._namespace = database.db.databaseName + '.' + this.collectionName; if (database.rr.has(modelName)) { this.model = database.rr.get(modelName); return; } this.log.silly('init'); const schema = new Schema(this.schema, { ...(this.schemaOptions || {}), collection: this.collectionName, minimize: false, typeKey: 'type', retainKeyOrder: true, timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt', }, }); const commonHook = DI.get(DDNames.mongodbSchemaHook, null); if (commonHook) { commonHook(schema); } this.schemaHook(schema); this.model = database.model>(modelName, schema); database.rr.set(modelName, this.model); } protected create(): DataDocument { const item = new this.model; item._id = new ObjectId; return item; } /** only for overwrite */ protected initialization() { } protected insert(content: IDocType) { const item = new this.model(content); item._id = new ObjectId(); return item.save(); } protected remove(doc: any): Promise { return this.model.remove(doc).exec(); } /** * only for overwrite, no content * @param {"mongoose".Schema} schemaObject */ protected schemaHook(schemaObject: Schema) { } private _namespace: string; get namespace() { if (this._namespace) { return this._namespace; } throw new Error('please wait database to connect, before using any method.'); } private get realConnectionName() { const connectionName = this.connectionName || this._realConnectionName; if (!connectionName) { throw new Error(`can not create ${this.modelName} instance: no connection name in class declaration, and no arguments.`); } return connectionName; } }