2022-05-17 00:33:44 +02:00
|
|
|
import * as plugins from './smartdata.plugins.js';
|
2016-09-13 22:53:21 +02:00
|
|
|
|
2022-05-17 00:33:44 +02:00
|
|
|
import { SmartdataDb } from './smartdata.classes.db.js';
|
|
|
|
import { SmartdataDbCursor } from './smartdata.classes.cursor.js';
|
2023-06-24 23:57:34 +02:00
|
|
|
import { type IManager, SmartdataCollection } from './smartdata.classes.collection.js';
|
2022-05-17 00:33:44 +02:00
|
|
|
import { SmartdataDbWatcher } from './smartdata.classes.watcher.js';
|
2016-09-13 22:53:21 +02:00
|
|
|
|
2018-07-08 23:48:14 +02:00
|
|
|
export type TDocCreation = 'db' | 'new' | 'mixed';
|
2016-09-13 22:53:21 +02:00
|
|
|
|
2024-05-31 18:39:33 +02:00
|
|
|
export function globalSvDb() {
|
|
|
|
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
|
|
|
console.log(`called svDb() on >${target.constructor.name}.${key}<`);
|
|
|
|
if (!target.globalSaveableProperties) {
|
|
|
|
target.globalSaveableProperties = [];
|
|
|
|
}
|
|
|
|
target.globalSaveableProperties.push(key);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-11-18 00:42:25 +01:00
|
|
|
/**
|
2016-11-18 00:59:57 +01:00
|
|
|
* saveable - saveable decorator to be used on class properties
|
2016-11-18 00:42:25 +01:00
|
|
|
*/
|
2016-11-18 13:56:15 +01:00
|
|
|
export function svDb() {
|
2020-02-19 18:30:34 +00:00
|
|
|
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
2020-09-25 20:42:38 +00:00
|
|
|
console.log(`called svDb() on >${target.constructor.name}.${key}<`);
|
2024-05-31 18:39:33 +02:00
|
|
|
if (!target.saveableProperties) {
|
|
|
|
target.saveableProperties = [];
|
2018-01-14 17:32:04 +01:00
|
|
|
}
|
2024-05-31 18:39:33 +02:00
|
|
|
target.saveableProperties.push(key);
|
2018-01-14 17:32:04 +01:00
|
|
|
};
|
2016-11-18 00:42:25 +01:00
|
|
|
}
|
2016-09-14 01:02:11 +02:00
|
|
|
|
2018-07-10 21:27:16 +02:00
|
|
|
/**
|
|
|
|
* unique index - decorator to mark a unique index
|
|
|
|
*/
|
|
|
|
export function unI() {
|
2020-02-19 18:30:34 +00:00
|
|
|
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
2020-09-25 20:42:38 +00:00
|
|
|
console.log(`called unI on >>${target.constructor.name}.${key}<<`);
|
2018-07-10 21:27:16 +02:00
|
|
|
|
|
|
|
// mark the index as unique
|
2024-05-31 18:39:33 +02:00
|
|
|
if (!target.uniqueIndexes) {
|
|
|
|
target.uniqueIndexes = [];
|
2018-07-10 21:27:16 +02:00
|
|
|
}
|
2024-05-31 18:39:33 +02:00
|
|
|
target.uniqueIndexes.push(key);
|
2018-07-10 21:27:16 +02:00
|
|
|
|
|
|
|
// and also save it
|
2024-05-31 18:39:33 +02:00
|
|
|
if (!target.saveableProperties) {
|
|
|
|
target.saveableProperties = [];
|
2018-07-10 21:27:16 +02:00
|
|
|
}
|
2024-05-31 18:39:33 +02:00
|
|
|
target.saveableProperties.push(key);
|
2018-07-10 21:27:16 +02:00
|
|
|
};
|
2019-01-07 02:41:38 +01:00
|
|
|
}
|
2018-07-10 21:27:16 +02:00
|
|
|
|
2021-11-12 16:23:26 +01:00
|
|
|
export const convertFilterForMongoDb = (filterArg: { [key: string]: any }) => {
|
|
|
|
const convertedFilter: { [key: string]: any } = {};
|
2024-09-05 15:06:35 +02:00
|
|
|
|
2021-11-12 16:23:26 +01:00
|
|
|
const convertFilterArgument = (keyPathArg2: string, filterArg2: any) => {
|
2024-09-05 15:06:35 +02:00
|
|
|
if (Array.isArray(filterArg2)) {
|
|
|
|
// Directly assign arrays (they might be using operators like $in or $all)
|
2024-09-05 15:28:52 +02:00
|
|
|
convertFilterArgument(keyPathArg2, filterArg2[0]);
|
2024-09-05 15:06:35 +02:00
|
|
|
} else if (typeof filterArg2 === 'object' && filterArg2 !== null) {
|
2021-11-12 16:23:26 +01:00
|
|
|
for (const key of Object.keys(filterArg2)) {
|
|
|
|
if (key.startsWith('$')) {
|
|
|
|
convertedFilter[keyPathArg2] = filterArg2;
|
|
|
|
return;
|
|
|
|
} else if (key.includes('.')) {
|
2024-09-05 13:45:55 +02:00
|
|
|
throw new Error('keys cannot contain dots');
|
2021-11-12 16:23:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const key of Object.keys(filterArg2)) {
|
|
|
|
convertFilterArgument(`${keyPathArg2}.${key}`, filterArg2[key]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
convertedFilter[keyPathArg2] = filterArg2;
|
|
|
|
}
|
|
|
|
};
|
2024-09-05 15:06:35 +02:00
|
|
|
|
2021-11-12 16:23:26 +01:00
|
|
|
for (const key of Object.keys(filterArg)) {
|
|
|
|
convertFilterArgument(key, filterArg[key]);
|
|
|
|
}
|
|
|
|
return convertedFilter;
|
|
|
|
};
|
|
|
|
|
2024-06-18 20:12:14 +02:00
|
|
|
export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends IManager = any> {
|
2017-02-25 11:37:05 +01:00
|
|
|
/**
|
|
|
|
* the collection object an Doc belongs to
|
|
|
|
*/
|
2020-09-10 10:12:17 +00:00
|
|
|
public static collection: SmartdataCollection<any>;
|
|
|
|
public collection: SmartdataCollection<any>;
|
2021-09-17 22:34:15 +02:00
|
|
|
public static defaultManager;
|
2021-06-09 15:38:14 +02:00
|
|
|
public static manager;
|
2021-06-09 14:10:08 +02:00
|
|
|
public manager: TManager;
|
2016-11-18 00:42:25 +01:00
|
|
|
|
2022-05-17 00:33:44 +02:00
|
|
|
// STATIC
|
2021-11-12 16:23:26 +01:00
|
|
|
public static createInstanceFromMongoDbNativeDoc<T>(
|
|
|
|
this: plugins.tsclass.typeFest.Class<T>,
|
|
|
|
mongoDbNativeDocArg: any
|
|
|
|
): T {
|
|
|
|
const newInstance = new this();
|
|
|
|
(newInstance as any).creationStatus = 'db';
|
|
|
|
for (const key of Object.keys(mongoDbNativeDocArg)) {
|
|
|
|
newInstance[key] = mongoDbNativeDocArg[key];
|
|
|
|
}
|
|
|
|
return newInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gets all instances as array
|
|
|
|
* @param this
|
|
|
|
* @param filterArg
|
|
|
|
* @returns
|
|
|
|
*/
|
2020-08-18 15:10:44 +00:00
|
|
|
public static async getInstances<T>(
|
2021-06-06 17:48:37 +02:00
|
|
|
this: plugins.tsclass.typeFest.Class<T>,
|
2020-08-18 15:10:44 +00:00
|
|
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
|
|
|
|
): Promise<T[]> {
|
2021-11-12 16:23:26 +01:00
|
|
|
const foundDocs = await (this as any).collection.findAll(convertFilterForMongoDb(filterArg));
|
2018-01-14 17:32:04 +01:00
|
|
|
const returnArray = [];
|
2021-11-12 16:23:26 +01:00
|
|
|
for (const foundDoc of foundDocs) {
|
|
|
|
const newInstance: T = (this as any).createInstanceFromMongoDbNativeDoc(foundDoc);
|
2018-01-14 17:32:04 +01:00
|
|
|
returnArray.push(newInstance);
|
|
|
|
}
|
|
|
|
return returnArray;
|
|
|
|
}
|
|
|
|
|
2021-11-12 16:23:26 +01:00
|
|
|
/**
|
|
|
|
* gets the first matching instance
|
|
|
|
* @param this
|
|
|
|
* @param filterArg
|
|
|
|
* @returns
|
|
|
|
*/
|
2020-08-18 15:10:44 +00:00
|
|
|
public static async getInstance<T>(
|
2021-06-06 17:48:37 +02:00
|
|
|
this: plugins.tsclass.typeFest.Class<T>,
|
2020-08-18 15:10:44 +00:00
|
|
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
|
|
|
|
): Promise<T> {
|
2021-11-12 16:23:26 +01:00
|
|
|
const foundDoc = await (this as any).collection.findOne(convertFilterForMongoDb(filterArg));
|
|
|
|
if (foundDoc) {
|
|
|
|
const newInstance: T = (this as any).createInstanceFromMongoDbNativeDoc(foundDoc);
|
|
|
|
return newInstance;
|
|
|
|
} else {
|
|
|
|
return null;
|
2018-01-14 17:32:04 +01:00
|
|
|
}
|
2017-02-25 11:37:05 +01:00
|
|
|
}
|
2016-11-18 00:42:25 +01:00
|
|
|
|
2024-03-26 13:21:36 +01:00
|
|
|
/**
|
|
|
|
* get a unique id prefixed with the class name
|
|
|
|
*/
|
2024-03-26 13:22:33 +01:00
|
|
|
public static async getNewId<T = any>(this: plugins.tsclass.typeFest.Class<T>, lengthArg: number = 20) {
|
2024-03-27 17:30:14 +01:00
|
|
|
return `${(this as any).className}:${plugins.smartunique.shortId(lengthArg)}`;
|
2024-03-26 13:21:36 +01:00
|
|
|
}
|
|
|
|
|
2021-11-12 16:23:26 +01:00
|
|
|
/**
|
|
|
|
* get cursor
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
public static async getCursor<T>(
|
|
|
|
this: plugins.tsclass.typeFest.Class<T>,
|
|
|
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
|
|
|
|
) {
|
2022-05-17 23:54:26 +02:00
|
|
|
const collection: SmartdataCollection<T> = (this as any).collection;
|
|
|
|
const cursor: SmartdataDbCursor<T> = await collection.getCursor(
|
|
|
|
convertFilterForMongoDb(filterArg),
|
|
|
|
this as any as typeof SmartDataDbDoc
|
2021-11-12 19:02:29 +01:00
|
|
|
);
|
2021-11-12 16:23:26 +01:00
|
|
|
return cursor;
|
|
|
|
}
|
|
|
|
|
2022-05-17 00:33:44 +02:00
|
|
|
/**
|
|
|
|
* watch the collection
|
2022-11-01 18:23:57 +01:00
|
|
|
* @param this
|
|
|
|
* @param filterArg
|
|
|
|
* @param forEachFunction
|
2022-05-17 00:33:44 +02:00
|
|
|
*/
|
2022-11-01 18:23:57 +01:00
|
|
|
public static async watch<T>(
|
2022-05-17 00:33:44 +02:00
|
|
|
this: plugins.tsclass.typeFest.Class<T>,
|
|
|
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
|
|
|
|
) {
|
|
|
|
const collection: SmartdataCollection<T> = (this as any).collection;
|
|
|
|
const watcher: SmartdataDbWatcher<T> = await collection.watch(
|
2022-05-17 23:54:26 +02:00
|
|
|
convertFilterForMongoDb(filterArg),
|
2022-11-01 18:23:57 +01:00
|
|
|
this as any
|
2022-05-17 00:33:44 +02:00
|
|
|
);
|
|
|
|
return watcher;
|
|
|
|
}
|
|
|
|
|
2021-11-12 16:23:26 +01:00
|
|
|
/**
|
|
|
|
* run a function for all instances
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
public static async forEach<T>(
|
|
|
|
this: plugins.tsclass.typeFest.Class<T>,
|
|
|
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>,
|
|
|
|
forEachFunction: (itemArg: T) => Promise<any>
|
|
|
|
) {
|
|
|
|
const cursor: SmartdataDbCursor<T> = await (this as any).getCursor(filterArg);
|
2021-11-12 19:02:29 +01:00
|
|
|
await cursor.forEach(forEachFunction);
|
2021-11-12 16:23:26 +01:00
|
|
|
}
|
|
|
|
|
2024-04-15 18:34:13 +02:00
|
|
|
/**
|
|
|
|
* returns a count of the documents in the collection
|
|
|
|
*/
|
|
|
|
public static async getCount<T>(
|
|
|
|
this: plugins.tsclass.typeFest.Class<T>,
|
|
|
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T> = ({} as any)
|
|
|
|
) {
|
|
|
|
const collection: SmartdataCollection<T> = (this as any).collection;
|
|
|
|
return await collection.getCount(filterArg);
|
|
|
|
}
|
|
|
|
|
2022-05-17 00:33:44 +02:00
|
|
|
// INSTANCE
|
|
|
|
|
|
|
|
/**
|
|
|
|
* how the Doc in memory was created, may prove useful later.
|
|
|
|
*/
|
|
|
|
public creationStatus: TDocCreation = 'new';
|
|
|
|
|
2024-04-14 01:24:21 +02:00
|
|
|
/**
|
|
|
|
* updated from db in any case where doc comes from db
|
|
|
|
*/
|
2024-05-31 18:39:33 +02:00
|
|
|
@globalSvDb()
|
2024-04-15 14:26:21 +02:00
|
|
|
_createdAt: string = (new Date()).toISOString();
|
2024-04-14 01:24:21 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* will be updated everytime the doc is saved
|
|
|
|
*/
|
2024-05-31 18:39:33 +02:00
|
|
|
@globalSvDb()
|
2024-04-15 14:26:21 +02:00
|
|
|
_updatedAt: string = (new Date()).toISOString();
|
2024-04-14 01:24:21 +02:00
|
|
|
|
2024-05-31 18:39:33 +02:00
|
|
|
/**
|
|
|
|
* an array of saveable properties of ALL doc
|
|
|
|
*/
|
|
|
|
public globalSaveableProperties: string[];
|
|
|
|
|
2022-05-17 00:33:44 +02:00
|
|
|
/**
|
|
|
|
* unique indexes
|
|
|
|
*/
|
|
|
|
public uniqueIndexes: string[];
|
|
|
|
|
|
|
|
/**
|
2024-05-31 18:39:33 +02:00
|
|
|
* an array of saveable properties of a specific doc
|
2022-05-17 00:33:44 +02:00
|
|
|
*/
|
|
|
|
public saveableProperties: string[];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* name
|
|
|
|
*/
|
|
|
|
public name: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* primary id in the database
|
|
|
|
*/
|
|
|
|
public dbDocUniqueId: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* class constructor
|
|
|
|
*/
|
|
|
|
constructor() {}
|
|
|
|
|
2017-02-25 11:37:05 +01:00
|
|
|
/**
|
|
|
|
* saves this instance but not any connected items
|
|
|
|
* may lead to data inconsistencies, but is faster
|
|
|
|
*/
|
2019-09-02 16:42:29 +02:00
|
|
|
public async save() {
|
2019-01-08 14:37:17 +01:00
|
|
|
// tslint:disable-next-line: no-this-assignment
|
2019-09-02 16:42:29 +02:00
|
|
|
const self: any = this;
|
2020-09-25 21:05:21 +00:00
|
|
|
let dbResult: any;
|
2024-04-14 01:24:21 +02:00
|
|
|
|
2024-04-15 14:26:21 +02:00
|
|
|
this._updatedAt = (new Date()).toISOString();
|
2024-04-14 01:24:21 +02:00
|
|
|
|
2018-01-12 01:22:58 +01:00
|
|
|
switch (this.creationStatus) {
|
2018-07-08 23:48:14 +02:00
|
|
|
case 'db':
|
2020-09-25 21:05:21 +00:00
|
|
|
dbResult = await this.collection.update(self);
|
2018-01-14 17:32:04 +01:00
|
|
|
break;
|
2018-07-08 23:48:14 +02:00
|
|
|
case 'new':
|
2020-09-25 21:05:21 +00:00
|
|
|
dbResult = await this.collection.insert(self);
|
2018-07-08 23:48:14 +02:00
|
|
|
this.creationStatus = 'db';
|
2018-01-12 01:22:58 +01:00
|
|
|
break;
|
|
|
|
default:
|
2018-07-08 23:48:14 +02:00
|
|
|
console.error('neither new nor in db?');
|
2017-02-25 11:37:05 +01:00
|
|
|
}
|
2020-09-25 21:05:21 +00:00
|
|
|
return dbResult;
|
2017-02-25 11:37:05 +01:00
|
|
|
}
|
2016-09-14 01:02:11 +02:00
|
|
|
|
2019-01-08 18:45:30 +01:00
|
|
|
/**
|
|
|
|
* deletes a document from the database
|
|
|
|
*/
|
2019-09-02 16:58:19 +02:00
|
|
|
public async delete() {
|
2020-09-10 10:12:17 +00:00
|
|
|
await this.collection.delete(this);
|
2019-09-02 16:58:19 +02:00
|
|
|
}
|
2019-01-08 18:45:30 +01:00
|
|
|
|
2017-02-25 11:37:05 +01:00
|
|
|
/**
|
|
|
|
* also store any referenced objects to DB
|
|
|
|
* better for data consistency
|
|
|
|
*/
|
2023-07-21 20:08:18 +02:00
|
|
|
public saveDeep(savedMapArg: plugins.lik.ObjectMap<SmartDataDbDoc<any, any>> = null) {
|
2017-02-25 11:37:05 +01:00
|
|
|
if (!savedMapArg) {
|
2023-07-21 20:08:18 +02:00
|
|
|
savedMapArg = new plugins.lik.ObjectMap<SmartDataDbDoc<any, any>>();
|
2017-02-25 11:37:05 +01:00
|
|
|
}
|
2018-01-14 17:32:04 +01:00
|
|
|
savedMapArg.add(this);
|
|
|
|
this.save();
|
2019-09-02 16:42:29 +02:00
|
|
|
for (const propertyKey of Object.keys(this)) {
|
|
|
|
const property: any = this[propertyKey];
|
2018-07-10 00:02:04 +02:00
|
|
|
if (property instanceof SmartDataDbDoc && !savedMapArg.checkForObject(property)) {
|
2018-01-14 17:32:04 +01:00
|
|
|
property.saveDeep(savedMapArg);
|
2017-02-25 11:37:05 +01:00
|
|
|
}
|
2016-09-13 22:53:21 +02:00
|
|
|
}
|
2017-02-25 11:37:05 +01:00
|
|
|
}
|
2018-01-12 01:22:58 +01:00
|
|
|
|
2023-02-06 11:43:11 +01:00
|
|
|
/**
|
|
|
|
* updates an object from db
|
|
|
|
*/
|
|
|
|
public async updateFromDb() {
|
2023-08-15 01:01:16 +02:00
|
|
|
const mongoDbNativeDoc = await this.collection.findOne(await this.createIdentifiableObject());
|
2023-02-06 11:43:11 +01:00
|
|
|
for (const key of Object.keys(mongoDbNativeDoc)) {
|
|
|
|
this[key] = mongoDbNativeDoc[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 18:45:30 +01:00
|
|
|
/**
|
|
|
|
* creates a saveable object so the instance can be persisted as json in the database
|
|
|
|
*/
|
2020-02-19 18:30:34 +00:00
|
|
|
public async createSavableObject(): Promise<TImplements> {
|
|
|
|
const saveableObject: unknown = {}; // is not exposed to outside, so any is ok here
|
2024-05-31 18:47:48 +02:00
|
|
|
const saveableProperties = [
|
|
|
|
...this.globalSaveableProperties,
|
|
|
|
...this.saveableProperties
|
|
|
|
]
|
|
|
|
for (const propertyNameString of saveableProperties) {
|
2018-01-14 17:32:04 +01:00
|
|
|
saveableObject[propertyNameString] = this[propertyNameString];
|
2018-01-12 01:22:58 +01:00
|
|
|
}
|
2020-02-19 18:30:34 +00:00
|
|
|
return saveableObject as TImplements;
|
2018-01-12 01:22:58 +01:00
|
|
|
}
|
2019-01-08 18:45:30 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* creates an identifiable object for operations that require filtering
|
|
|
|
*/
|
|
|
|
public async createIdentifiableObject() {
|
|
|
|
const identifiableObject: any = {}; // is not exposed to outside, so any is ok here
|
|
|
|
for (const propertyNameString of this.uniqueIndexes) {
|
|
|
|
identifiableObject[propertyNameString] = this[propertyNameString];
|
|
|
|
}
|
|
|
|
return identifiableObject;
|
|
|
|
}
|
2016-09-13 22:53:21 +02:00
|
|
|
}
|