smartdata/ts/smartdata.classes.collection.ts

299 lines
8.9 KiB
TypeScript
Raw Normal View History

2022-05-16 22:33:44 +00:00
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;
}
2021-06-09 10:40:55 +00:00
export type TDelayed<TDelayedArg> = () => TDelayedArg;
2019-01-08 13:37:17 +00:00
2020-09-10 10:12:17 +00:00
const collectionFactory = new CollectionFactory();
/**
* This is a decorator that will tell the decorated class what dbTable to use
2019-01-08 13:37:17 +00:00
* @param dbArg
*/
2021-06-09 11:40:23 +00:00
export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
2020-09-10 10:12:17 +00:00
return function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
2024-03-27 16:30:14 +00:00
const decoratedClass = class extends constructor {
public static className = constructor.name;
2020-09-10 10:12:17 +00:00
public static get collection() {
2021-06-09 11:40:23 +00:00
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);
}
};
2024-03-27 16:30:14 +00:00
return decoratedClass;
2021-06-09 11:40:23 +00:00
};
}
2021-06-09 12:10:08 +00:00
export interface IManager {
2021-10-16 19:17:02 +00:00
db: SmartdataDb;
2021-06-09 11:40:23 +00:00
}
2021-10-16 19:17:02 +00:00
export const setDefaultManagerForDoc = <T>(managerArg: IManager, dbDocArg: T): T => {
2021-09-19 15:05:08 +00:00
(dbDocArg as any).prototype.defaultManager = managerArg;
2021-09-17 22:38:20 +00:00
return dbDocArg;
2021-10-16 19:17:02 +00:00
};
2021-09-17 20:34:15 +00:00
2021-06-09 11:40:23 +00:00
/**
* This is a decorator that will tell the decorated class what dbTable to use
* @param dbArg
*/
2024-03-22 17:36:34 +00:00
export function managed<TManager extends IManager>(managerArg?: TManager | TDelayed<TManager>) {
2021-09-17 20:34:15 +00:00
return function classDecorator<T extends { new (...args: any[]): any }>(constructor: T) {
2024-03-27 16:30:14 +00:00
const decoratedClass = class extends constructor {
public static className = constructor.name;
2021-06-09 11:40:23 +00:00
public static get collection() {
let dbArg: SmartdataDb;
2021-09-17 20:34:15 +00:00
if (!managerArg) {
dbArg = this.prototype.defaultManager.db;
} else if (managerArg['db']) {
2021-10-16 19:17:02 +00:00
dbArg = (managerArg as TManager).db;
2021-06-09 11:40:23 +00:00
} else {
dbArg = (managerArg as TDelayed<TManager>)().db;
}
2020-09-10 10:12:17 +00:00
return collectionFactory.getCollection(constructor.name, dbArg);
}
public get collection() {
2021-06-09 11:40:23 +00:00
let dbArg: SmartdataDb;
2021-09-17 20:34:15 +00:00
if (!managerArg) {
//console.log(this.defaultManager.db);
//process.exit(0)
dbArg = this.defaultManager.db;
} else if (managerArg['db']) {
2021-10-16 19:17:02 +00:00
dbArg = (managerArg as TManager).db;
2021-06-09 11:40:23 +00:00
} else {
dbArg = (managerArg as TDelayed<TManager>)().db;
}
2020-09-10 10:12:17 +00:00
return collectionFactory.getCollection(constructor.name, dbArg);
}
2021-06-09 12:55:55 +00:00
public static get manager() {
let manager: TManager;
if (!managerArg) {
2021-11-12 18:02:29 +00:00
manager = this.prototype.defaultManager;
} else if (managerArg['db']) {
2021-10-16 19:17:02 +00:00
manager = managerArg as TManager;
2021-06-09 12:55:55 +00:00
} else {
manager = (managerArg as TDelayed<TManager>)();
}
return manager;
}
2021-06-09 10:40:55 +00:00
public get manager() {
2021-06-09 11:40:23 +00:00
let manager: TManager;
if (!managerArg) {
2021-11-12 18:02:29 +00:00
manager = this.defaultManager;
} else if (managerArg['db']) {
2021-10-16 19:17:02 +00:00
manager = managerArg as TManager;
2021-06-09 11:40:23 +00:00
} else {
manager = (managerArg as TDelayed<TManager>)();
2021-06-09 10:40:55 +00:00
}
2021-06-09 11:40:23 +00:00
return manager;
2021-06-09 10:40:55 +00:00
}
2020-09-10 10:12:17 +00:00
};
2024-03-27 16:30:14 +00:00
return decoratedClass;
};
}
2024-03-22 17:36:34 +00:00
/**
* @dpecrecated use @managed instead
*/
export const Manager = managed;
export class SmartdataCollection<T> {
/**
* the collection that is used
*/
2019-09-02 14:42:29 +00:00
public mongoDbCollection: plugins.mongodb.Collection;
public objectValidation: IDocValidationFunc<T> = null;
public collectionName: string;
public smartdataDb: SmartdataDb;
public uniqueIndexes: string[] = [];
2020-09-10 10:12:17 +00:00
constructor(classNameArg: string, smartDataDbArg: SmartdataDb) {
// tell the collection where it belongs
2020-09-10 10:12:17 +00:00
this.collectionName = classNameArg;
this.smartdataDb = smartDataDbArg;
// tell the db class about it (important since Db uses different systems under the hood)
2020-09-10 10:12:17 +00:00
this.smartdataDb.addCollection(this);
}
/**
* makes sure a collection exists within MongoDb that maps to the SmartdataCollection
*/
2019-09-02 14:42:29 +00:00
public async init() {
if (!this.mongoDbCollection) {
// connect this instance to a MongoDB collection
const availableMongoDbCollections = await this.smartdataDb.mongoDb.collections();
2020-08-18 12:01:46 +00:00
const wantedCollection = availableMongoDbCollections.find((collection) => {
return collection.collectionName === this.collectionName;
});
if (!wantedCollection) {
await this.smartdataDb.mongoDb.createCollection(this.collectionName);
2020-09-09 03:51:21 +00:00
console.log(`Successfully initiated Collection ${this.collectionName}`);
}
2020-09-09 03:51:21 +00:00
this.mongoDbCollection = this.smartdataDb.mongoDb.collection(this.collectionName);
}
}
/**
* mark unique index
*/
2019-09-02 14:42:29 +00:00
public markUniqueIndexes(keyArrayArg: string[] = []) {
for (const key of keyArrayArg) {
2019-01-07 01:41:38 +00:00
if (!this.uniqueIndexes.includes(key)) {
this.mongoDbCollection.createIndex(key, {
2020-08-18 12:01:46 +00:00
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
*/
2019-09-02 14:42:29 +00:00
public addDocValidation(funcArg: IDocValidationFunc<T>) {
this.objectValidation = funcArg;
}
/**
* finds an object in the DbCollection
*/
2021-11-12 18:02:29 +00:00
public async findOne(filterObject: any): Promise<any> {
await this.init();
2021-11-12 15:23:26 +00:00
const cursor = this.mongoDbCollection.find(filterObject);
const result = await cursor.next();
cursor.close();
return result;
}
2022-11-01 17:23:57 +00:00
public async getCursor(
filterObjectArg: any,
dbDocArg: typeof SmartDataDbDoc
): Promise<SmartdataDbCursor<any>> {
2021-11-12 15:23:26 +00:00
await this.init();
2022-05-17 21:54:26 +00:00
const cursor = this.mongoDbCollection.find(filterObjectArg);
return new SmartdataDbCursor(cursor, dbDocArg);
2021-11-12 15:23:26 +00:00
}
/**
* 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();
2018-07-09 22:02:04 +00:00
return result;
}
2022-05-16 22:33:44 +00:00
/**
* watches the collection while applying a filter
*/
2022-11-01 17:23:57 +00:00
public async watch(
filterObject: any,
smartdataDbDocArg: typeof SmartDataDbDoc
): Promise<SmartdataDbWatcher> {
2022-05-16 22:33:44 +00:00
await this.init();
2022-11-01 17:23:57 +00:00
const changeStream = this.mongoDbCollection.watch(
[
{
$match: filterObject,
},
],
2022-05-16 22:33:44 +00:00
{
2022-11-01 17:23:57 +00:00
fullDocument: 'updateLookup',
2022-05-16 22:33:44 +00:00
}
2022-11-01 17:23:57 +00:00
);
const smartdataWatcher = new SmartdataDbWatcher(changeStream, smartdataDbDocArg);
2022-05-17 19:26:17 +00:00
await smartdataWatcher.readyDeferred.promise;
return smartdataWatcher;
2022-05-16 22:33:44 +00:00
}
/**
* create an object in the database
*/
2020-02-19 18:30:34 +00:00
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
*/
2020-02-19 18:30:34 +00:00
public async update(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
await this.init();
await this.checkDoc(dbDocArg);
2019-01-08 17:45:30 +00:00
const identifiableObject = await dbDocArg.createIdentifiableObject();
const saveableObject = await dbDocArg.createSavableObject();
2019-09-02 14:42:29 +00:00
const updateableObject: any = {};
for (const key of Object.keys(saveableObject)) {
if (identifiableObject[key]) {
continue;
}
updateableObject[key] = saveableObject[key];
}
2020-09-25 21:05:21 +00:00
const result = await this.mongoDbCollection.updateOne(
2019-09-02 14:51:22 +00:00
identifiableObject,
{ $set: updateableObject },
{ upsert: true }
);
2020-09-25 21:05:21 +00:00
return result;
2019-01-08 17:45:30 +00:00
}
2020-02-19 18:30:34 +00:00
public async delete(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
2019-01-08 17:45:30 +00:00
await this.init();
await this.checkDoc(dbDocArg);
const identifiableObject = await dbDocArg.createIdentifiableObject();
2021-11-12 16:22:31 +00:00
await this.mongoDbCollection.deleteOne(identifiableObject);
}
public async getCount(filterObject: any) {
await this.init();
return this.mongoDbCollection.countDocuments(filterObject);
}
/**
* checks a Doc for constraints
* if this.objectValidation is not set it passes.
*/
private checkDoc(docArg: T): Promise<void> {
2023-08-14 23:24:29 +00:00
const done = plugins.smartpromise.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;
}
}