285 lines
8.5 KiB
TypeScript
285 lines
8.5 KiB
TypeScript
import * as plugins from './smartdata.plugins.js';
|
|
import { SmartdataDb } from './smartdata.classes.db.js';
|
|
import { SmartdataDbCursor } from './smartdata.classes.cursor.js';
|
|
import { SmartDataDbDoc } from './smartdata.classes.doc.js';
|
|
import { SmartdataDbWatcher } from './smartdata.classes.watcher.js';
|
|
import { CollectionFactory } from './smartdata.classes.collectionfactory.js';
|
|
|
|
export interface IFindOptions {
|
|
limit?: number;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
export interface IDocValidationFunc<T> {
|
|
(doc: T): boolean;
|
|
}
|
|
|
|
export type TDelayed<TDelayedArg> = () => TDelayedArg;
|
|
|
|
const collectionFactory = new CollectionFactory();
|
|
|
|
/**
|
|
* This is a decorator that will tell the decorated class what dbTable to use
|
|
* @param dbArg
|
|
*/
|
|
export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
|
|
return function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
|
|
return class extends constructor {
|
|
public static get collection() {
|
|
if (!(dbArg instanceof SmartdataDb)) {
|
|
dbArg = dbArg();
|
|
}
|
|
return collectionFactory.getCollection(constructor.name, dbArg);
|
|
}
|
|
public get collection() {
|
|
if (!(dbArg instanceof SmartdataDb)) {
|
|
dbArg = dbArg();
|
|
}
|
|
return collectionFactory.getCollection(constructor.name, dbArg);
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
export interface IManager {
|
|
db: SmartdataDb;
|
|
}
|
|
|
|
export const setDefaultManagerForDoc = <T>(managerArg: IManager, dbDocArg: T): T => {
|
|
(dbDocArg as any).prototype.defaultManager = managerArg;
|
|
return dbDocArg;
|
|
};
|
|
|
|
/**
|
|
* This is a decorator that will tell the decorated class what dbTable to use
|
|
* @param dbArg
|
|
*/
|
|
export function Manager<TManager extends IManager>(managerArg?: TManager | TDelayed<TManager>) {
|
|
return function classDecorator<T extends { new (...args: any[]): any }>(constructor: T) {
|
|
return class extends constructor {
|
|
public static get collection() {
|
|
let dbArg: SmartdataDb;
|
|
if (!managerArg) {
|
|
dbArg = this.prototype.defaultManager.db;
|
|
} else if (managerArg['db']) {
|
|
dbArg = (managerArg as TManager).db;
|
|
} else {
|
|
dbArg = (managerArg as TDelayed<TManager>)().db;
|
|
}
|
|
return collectionFactory.getCollection(constructor.name, dbArg);
|
|
}
|
|
public get collection() {
|
|
let dbArg: SmartdataDb;
|
|
if (!managerArg) {
|
|
//console.log(this.defaultManager.db);
|
|
//process.exit(0)
|
|
dbArg = this.defaultManager.db;
|
|
} else if (managerArg['db']) {
|
|
dbArg = (managerArg as TManager).db;
|
|
} else {
|
|
dbArg = (managerArg as TDelayed<TManager>)().db;
|
|
}
|
|
return collectionFactory.getCollection(constructor.name, dbArg);
|
|
}
|
|
public static get manager() {
|
|
let manager: TManager;
|
|
if (!managerArg) {
|
|
manager = this.prototype.defaultManager;
|
|
} else if (managerArg['db']) {
|
|
manager = managerArg as TManager;
|
|
} else {
|
|
manager = (managerArg as TDelayed<TManager>)();
|
|
}
|
|
return manager;
|
|
}
|
|
public get manager() {
|
|
let manager: TManager;
|
|
if (!managerArg) {
|
|
manager = this.defaultManager;
|
|
} else if (managerArg['db']) {
|
|
manager = managerArg as TManager;
|
|
} else {
|
|
manager = (managerArg as TDelayed<TManager>)();
|
|
}
|
|
return manager;
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
export class SmartdataCollection<T> {
|
|
/**
|
|
* the collection that is used
|
|
*/
|
|
public mongoDbCollection: plugins.mongodb.Collection;
|
|
public objectValidation: IDocValidationFunc<T> = null;
|
|
public collectionName: string;
|
|
public smartdataDb: SmartdataDb;
|
|
public uniqueIndexes: string[] = [];
|
|
|
|
constructor(classNameArg: string, smartDataDbArg: SmartdataDb) {
|
|
// tell the collection where it belongs
|
|
this.collectionName = classNameArg;
|
|
this.smartdataDb = smartDataDbArg;
|
|
|
|
// tell the db class about it (important since Db uses different systems under the hood)
|
|
this.smartdataDb.addCollection(this);
|
|
}
|
|
|
|
/**
|
|
* makes sure a collection exists within MongoDb that maps to the SmartdataCollection
|
|
*/
|
|
public async init() {
|
|
if (!this.mongoDbCollection) {
|
|
// connect this instance to a MongoDB collection
|
|
const availableMongoDbCollections = await this.smartdataDb.mongoDb.collections();
|
|
const wantedCollection = availableMongoDbCollections.find((collection) => {
|
|
return collection.collectionName === this.collectionName;
|
|
});
|
|
if (!wantedCollection) {
|
|
await this.smartdataDb.mongoDb.createCollection(this.collectionName);
|
|
console.log(`Successfully initiated Collection ${this.collectionName}`);
|
|
}
|
|
this.mongoDbCollection = this.smartdataDb.mongoDb.collection(this.collectionName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* mark unique index
|
|
*/
|
|
public markUniqueIndexes(keyArrayArg: string[] = []) {
|
|
for (const key of keyArrayArg) {
|
|
if (!this.uniqueIndexes.includes(key)) {
|
|
this.mongoDbCollection.createIndex(key, {
|
|
unique: true,
|
|
});
|
|
// make sure we only call this once and not for every doc we create
|
|
this.uniqueIndexes.push(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* adds a validation function that all newly inserted and updated objects have to pass
|
|
*/
|
|
public addDocValidation(funcArg: IDocValidationFunc<T>) {
|
|
this.objectValidation = funcArg;
|
|
}
|
|
|
|
/**
|
|
* finds an object in the DbCollection
|
|
*/
|
|
public async findOne(filterObject: any): Promise<any> {
|
|
await this.init();
|
|
const cursor = this.mongoDbCollection.find(filterObject);
|
|
const result = await cursor.next();
|
|
cursor.close();
|
|
return result;
|
|
}
|
|
|
|
public async getCursor(
|
|
filterObjectArg: any,
|
|
dbDocArg: typeof SmartDataDbDoc
|
|
): Promise<SmartdataDbCursor<any>> {
|
|
await this.init();
|
|
const cursor = this.mongoDbCollection.find(filterObjectArg);
|
|
return new SmartdataDbCursor(cursor, dbDocArg);
|
|
}
|
|
|
|
/**
|
|
* finds an object in the DbCollection
|
|
*/
|
|
public async findAll(filterObject: any): Promise<any[]> {
|
|
await this.init();
|
|
const cursor = this.mongoDbCollection.find(filterObject);
|
|
const result = await cursor.toArray();
|
|
cursor.close();
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* watches the collection while applying a filter
|
|
*/
|
|
public async watch(
|
|
filterObject: any,
|
|
smartdataDbDocArg: typeof SmartDataDbDoc
|
|
): Promise<SmartdataDbWatcher> {
|
|
await this.init();
|
|
const changeStream = this.mongoDbCollection.watch(
|
|
[
|
|
{
|
|
$match: filterObject,
|
|
},
|
|
],
|
|
{
|
|
fullDocument: 'updateLookup',
|
|
}
|
|
);
|
|
const smartdataWatcher = new SmartdataDbWatcher(changeStream, smartdataDbDocArg);
|
|
await smartdataWatcher.readyDeferred.promise;
|
|
return smartdataWatcher;
|
|
}
|
|
|
|
/**
|
|
* create an object in the database
|
|
*/
|
|
public async insert(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
|
|
await this.init();
|
|
await this.checkDoc(dbDocArg);
|
|
this.markUniqueIndexes(dbDocArg.uniqueIndexes);
|
|
const saveableObject = await dbDocArg.createSavableObject();
|
|
const result = await this.mongoDbCollection.insertOne(saveableObject);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* inserts object into the DbCollection
|
|
*/
|
|
public async update(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
|
|
await this.init();
|
|
await this.checkDoc(dbDocArg);
|
|
const identifiableObject = await dbDocArg.createIdentifiableObject();
|
|
const saveableObject = await dbDocArg.createSavableObject();
|
|
const updateableObject: any = {};
|
|
for (const key of Object.keys(saveableObject)) {
|
|
if (identifiableObject[key]) {
|
|
continue;
|
|
}
|
|
updateableObject[key] = saveableObject[key];
|
|
}
|
|
const result = await this.mongoDbCollection.updateOne(
|
|
identifiableObject,
|
|
{ $set: updateableObject },
|
|
{ upsert: true }
|
|
);
|
|
return result;
|
|
}
|
|
|
|
public async delete(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
|
|
await this.init();
|
|
await this.checkDoc(dbDocArg);
|
|
const identifiableObject = await dbDocArg.createIdentifiableObject();
|
|
await this.mongoDbCollection.deleteOne(identifiableObject);
|
|
}
|
|
|
|
/**
|
|
* checks a Doc for constraints
|
|
* if this.objectValidation is not set it passes.
|
|
*/
|
|
private checkDoc(docArg: T): Promise<void> {
|
|
const done = plugins.smartq.defer<void>();
|
|
let validationResult = true;
|
|
if (this.objectValidation) {
|
|
validationResult = this.objectValidation(docArg);
|
|
}
|
|
if (validationResult) {
|
|
done.resolve();
|
|
} else {
|
|
done.reject('validation of object did not pass');
|
|
}
|
|
return done.promise;
|
|
}
|
|
}
|