feat(core): now retrieves classes properly
This commit is contained in:
@ -1,3 +1,3 @@
|
||||
export * from './smartdata.classes.db'
|
||||
export * from './smartdata.classes.dbcollection'
|
||||
export * from './smartdata.classes.dbdoc'
|
||||
export * from "./smartdata.classes.db";
|
||||
export * from "./smartdata.classes.dbtable";
|
||||
export * from "./smartdata.classes.dbdoc";
|
||||
|
@ -1,41 +1,47 @@
|
||||
import * as plugins from './smartdata.plugins'
|
||||
import { Objectmap } from 'lik'
|
||||
import * as plugins from "./smartdata.plugins";
|
||||
import { Objectmap } from "lik";
|
||||
|
||||
import { DbTable } from './smartdata.classes.dbcollection'
|
||||
import { DbTable } from "./smartdata.classes.dbtable";
|
||||
|
||||
import { Connection as dbConnection, ConnectionOptions } from 'rethinkdb'
|
||||
import { Connection as dbConnection, ConnectionOptions } from "rethinkdb";
|
||||
|
||||
/**
|
||||
* interface - indicates the connection status of the db
|
||||
*/
|
||||
export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed'
|
||||
export type TConnectionStatus =
|
||||
| "initial"
|
||||
| "disconnected"
|
||||
| "connected"
|
||||
| "failed";
|
||||
|
||||
export class Db {
|
||||
dbName: string
|
||||
connectionOptions: plugins.rethinkDb.ConnectionOptions
|
||||
dbConnection: plugins.rethinkDb.Connection
|
||||
status: TConnectionStatus
|
||||
dbTablesMap = new Objectmap<DbTable<any>>()
|
||||
dbName: string;
|
||||
connectionOptions: plugins.rethinkDb.ConnectionOptions;
|
||||
dbConnection: plugins.rethinkDb.Connection;
|
||||
status: TConnectionStatus;
|
||||
dbTablesMap = new Objectmap<DbTable<any>>();
|
||||
|
||||
constructor(connectionOptionsArg: ConnectionOptions) {
|
||||
this.dbName = connectionOptionsArg.db
|
||||
this.connectionOptions = connectionOptionsArg
|
||||
this.status = 'initial'
|
||||
this.dbName = connectionOptionsArg.db;
|
||||
this.connectionOptions = connectionOptionsArg;
|
||||
this.status = "initial";
|
||||
}
|
||||
|
||||
/**
|
||||
* supply additional SSl options
|
||||
* supply additional SSl options needed to connect to certain Rethink DB servers (e.g. compose.io)
|
||||
*/
|
||||
setSsl (certificateStringArg: string, formatArg: 'base64' | 'clearText') {
|
||||
let certificateString: string
|
||||
if(formatArg = 'base64') {
|
||||
certificateString = plugins.smartstring.base64.decode(certificateStringArg)
|
||||
setSsl(certificateStringArg: string, formatArg: "base64" | "clearText") {
|
||||
let certificateString: string;
|
||||
if ((formatArg = "base64")) {
|
||||
certificateString = plugins.smartstring.base64.decode(
|
||||
certificateStringArg
|
||||
);
|
||||
} else {
|
||||
certificateString = certificateStringArg
|
||||
certificateString = certificateStringArg;
|
||||
}
|
||||
this.connectionOptions['ssl'] = {
|
||||
this.connectionOptions["ssl"] = {
|
||||
ca: Buffer.from(certificateString)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// basic connection stuff ----------------------------------------------
|
||||
@ -43,37 +49,37 @@ export class Db {
|
||||
/**
|
||||
* connects to the database that was specified during instance creation
|
||||
*/
|
||||
async connect (): Promise<any> {
|
||||
this.dbConnection = await plugins.rethinkDb.connect(this.connectionOptions)
|
||||
this.dbConnection.use(this.dbName)
|
||||
this.status = 'connected'
|
||||
plugins.beautylog.ok(`Connected to database ${this.dbName}`)
|
||||
async connect(): Promise<any> {
|
||||
this.dbConnection = await plugins.rethinkDb.connect(this.connectionOptions);
|
||||
this.dbConnection.use(this.dbName);
|
||||
this.status = "connected";
|
||||
plugins.beautylog.ok(`Connected to database ${this.dbName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* closes the connection to the databse
|
||||
*/
|
||||
async close (): Promise<any> {
|
||||
await this.dbConnection.close()
|
||||
this.status = 'disconnected'
|
||||
plugins.beautylog.ok(`disconnected from database ${this.dbName}`)
|
||||
async close(): Promise<any> {
|
||||
await this.dbConnection.close();
|
||||
this.status = "disconnected";
|
||||
plugins.beautylog.ok(`disconnected from database ${this.dbName}`);
|
||||
}
|
||||
|
||||
// handle table to class distribution
|
||||
|
||||
addTable(dbTableArg: DbTable<any>) {
|
||||
this.dbTablesMap.add(dbTableArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a table's name and returns smartdata's DbTable class
|
||||
* @param nameArg
|
||||
* @returns DbTable
|
||||
*/
|
||||
async getDbTableByName<T>(nameArg: string): Promise<DbTable<T>> {
|
||||
let resultCollection = this.dbTablesMap.find((dbCollectionArg) => {
|
||||
return dbCollectionArg.tableName === nameArg
|
||||
})
|
||||
return resultCollection
|
||||
}
|
||||
|
||||
addTable (dbCollectionArg: DbTable<any>) {
|
||||
this.dbTablesMap.add(dbCollectionArg)
|
||||
let resultCollection = this.dbTablesMap.find(dbTableArg => {
|
||||
return dbTableArg.tableName === nameArg;
|
||||
});
|
||||
return resultCollection;
|
||||
}
|
||||
}
|
||||
|
@ -1,119 +0,0 @@
|
||||
import * as plugins from './smartdata.plugins'
|
||||
import { Db } from './smartdata.classes.db'
|
||||
import { DbDoc } from './smartdata.classes.dbdoc'
|
||||
|
||||
// RethinkDb Interfaces
|
||||
import { WriteResult, Cursor } from 'rethinkdb'
|
||||
|
||||
export interface IFindOptions {
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface IDocValidation<T> {
|
||||
(doc: T): boolean
|
||||
}
|
||||
|
||||
export function Collection (db: Db) {
|
||||
return function (constructor) {
|
||||
constructor[ 'dbCollection' ] = new DbTable(constructor, db)
|
||||
}
|
||||
}
|
||||
|
||||
export class DbTable<T> {
|
||||
/**
|
||||
* the collection that is used, defaults to mongodb collection,
|
||||
* can be nedb datastore (sub api of mongodb)
|
||||
*/
|
||||
table: plugins.rethinkDb.Table
|
||||
objectValidation: IDocValidation<T> = null
|
||||
tableName: string
|
||||
db: Db
|
||||
|
||||
constructor (collectedClassArg: T & DbDoc<T>, dbArg: Db) {
|
||||
// tell the collection where it belongs
|
||||
this.tableName = collectedClassArg.name
|
||||
this.db = dbArg
|
||||
|
||||
// tell the db class about it (important since Db uses different systems under the hood)
|
||||
this.db.addTable(this)
|
||||
}
|
||||
|
||||
async init() {
|
||||
if(!this.table) {
|
||||
// connect this instance to a RethinkDB table
|
||||
const availableTables = await plugins.rethinkDb
|
||||
.db(this.db.dbName)
|
||||
.tableList()
|
||||
.run(this.db.dbConnection)
|
||||
if(availableTables.indexOf(this.tableName)) {
|
||||
await plugins.rethinkDb
|
||||
.db(this.db.dbName)
|
||||
.tableCreate(this.tableName)
|
||||
.run(this.db.dbConnection)
|
||||
}
|
||||
}
|
||||
this.table = plugins.rethinkDb.table(this.tableName)
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a validation function that all newly inserted and updated objects have to pass
|
||||
*/
|
||||
addDocValidation (funcArg: IDocValidation<T>) {
|
||||
this.objectValidation = funcArg
|
||||
}
|
||||
|
||||
/**
|
||||
* finds an object in the DbCollection
|
||||
*/
|
||||
async find (): Promise<Cursor> {
|
||||
await this.init()
|
||||
return await plugins.rethinkDb.table(this.tableName).filter({
|
||||
/* TODO: */
|
||||
}).run(this.db.dbConnection)
|
||||
}
|
||||
|
||||
/**
|
||||
* create an object in the database
|
||||
*/
|
||||
async insert (dbDocArg: T & DbDoc<T>): Promise<WriteResult> {
|
||||
await this.init()
|
||||
await this.checkDoc(dbDocArg)
|
||||
return await plugins.rethinkDb.table(this.tableName).insert(
|
||||
dbDocArg.createSavableObject()
|
||||
).run(this.db.dbConnection)
|
||||
}
|
||||
|
||||
/**
|
||||
* inserts object into the DbCollection
|
||||
*/
|
||||
async update (dbDocArg: T & DbDoc<T>): Promise<WriteResult> {
|
||||
await this.init()
|
||||
await this.checkDoc(dbDocArg)
|
||||
console.log(this.tableName, dbDocArg.createSavableObject())
|
||||
return await plugins.rethinkDb.table(this.tableName).update(
|
||||
dbDocArg.createSavableObject()
|
||||
).run(this.db.dbConnection)
|
||||
}
|
||||
|
||||
/**
|
||||
* checks a Doc for constraints
|
||||
* if this.objectValidation is not set it passes.
|
||||
*/
|
||||
private checkDoc (docArg: T): Promise<void> {
|
||||
let 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
|
||||
}
|
||||
|
||||
extractKey (writeResult: WriteResult) {
|
||||
|
||||
}
|
||||
}
|
@ -1,74 +1,99 @@
|
||||
import * as plugins from './smartdata.plugins'
|
||||
import * as plugins from "./smartdata.plugins";
|
||||
|
||||
import { Objectmap } from 'lik'
|
||||
import { Objectmap } from "lik";
|
||||
|
||||
import { Db } from './smartdata.classes.db'
|
||||
import { DbTable } from './smartdata.classes.dbcollection'
|
||||
import { Db } from "./smartdata.classes.db";
|
||||
import { DbTable } from "./smartdata.classes.dbtable";
|
||||
|
||||
export type TDocCreation = 'db' | 'new' | 'mixed'
|
||||
export type TDocCreation = "db" | "new" | "mixed";
|
||||
|
||||
/**
|
||||
* saveable - saveable decorator to be used on class properties
|
||||
*/
|
||||
export function svDb() {
|
||||
return (target: DbDoc<any>, key: string) => {
|
||||
console.log('called sva')
|
||||
if (!target.saveableProperties) { target.saveableProperties = [] }
|
||||
target.saveableProperties.push(key)
|
||||
}
|
||||
console.log("called sva");
|
||||
if (!target.saveableProperties) {
|
||||
target.saveableProperties = [];
|
||||
}
|
||||
target.saveableProperties.push(key);
|
||||
};
|
||||
}
|
||||
|
||||
export class DbDoc<T> {
|
||||
|
||||
/**
|
||||
* the collection object an Doc belongs to
|
||||
*/
|
||||
collection: DbTable<T>
|
||||
collection: DbTable<T>;
|
||||
|
||||
/**
|
||||
* how the Doc in memory was created, may prove useful later.
|
||||
*/
|
||||
creationStatus: TDocCreation = 'new'
|
||||
creationStatus: TDocCreation = "new";
|
||||
|
||||
/**
|
||||
* an array of saveable properties of a doc
|
||||
*/
|
||||
saveableProperties: string[]
|
||||
saveableProperties: string[];
|
||||
|
||||
/**
|
||||
* name
|
||||
*/
|
||||
name: string
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* primary id in the database
|
||||
*/
|
||||
dbId: string
|
||||
dbId: string;
|
||||
|
||||
/**
|
||||
* class constructor
|
||||
*/
|
||||
constructor () {
|
||||
this.name = this.constructor['name']
|
||||
this.collection = this.constructor[ 'dbCollection' ]
|
||||
constructor() {
|
||||
this.name = this.constructor["name"];
|
||||
this.collection = this.constructor["dbTable"];
|
||||
}
|
||||
|
||||
static async getInstances<T>(filterArg): Promise<T[]> {
|
||||
let self: any = this; // fool typesystem
|
||||
let referenceTable: DbTable<T> = self.dbTable;
|
||||
const foundDocs = await referenceTable.find(filterArg);
|
||||
const returnArray = [];
|
||||
for (let item of foundDocs) {
|
||||
let newInstance = new this();
|
||||
for (let key in item) {
|
||||
if(key !== 'id') {
|
||||
newInstance[key] = item[key];
|
||||
}
|
||||
}
|
||||
returnArray.push(newInstance);
|
||||
}
|
||||
return returnArray;
|
||||
}
|
||||
|
||||
static async getInstance<T>(filterArg): Promise<T> {
|
||||
let result = await this.getInstances<T>(filterArg)
|
||||
if(result && result.length > 0) {
|
||||
return result[0]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* saves this instance but not any connected items
|
||||
* may lead to data inconsistencies, but is faster
|
||||
*/
|
||||
async save () {
|
||||
let self: any = this
|
||||
async save() {
|
||||
let self: any = this;
|
||||
switch (this.creationStatus) {
|
||||
case 'db':
|
||||
await this.collection.update(self)
|
||||
break
|
||||
case 'new':
|
||||
let writeResult = await this.collection.insert(self)
|
||||
this.creationStatus = 'db'
|
||||
case "db":
|
||||
await this.collection.update(self);
|
||||
break;
|
||||
case "new":
|
||||
let writeResult = await this.collection.insert(self);
|
||||
this.creationStatus = "db";
|
||||
break;
|
||||
default:
|
||||
console.error('neither new nor in db?')
|
||||
console.error("neither new nor in db?");
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,25 +101,25 @@ export class DbDoc<T> {
|
||||
* also store any referenced objects to DB
|
||||
* better for data consistency
|
||||
*/
|
||||
saveDeep (savedMapArg: Objectmap<DbDoc<any>> = null) {
|
||||
saveDeep(savedMapArg: Objectmap<DbDoc<any>> = null) {
|
||||
if (!savedMapArg) {
|
||||
savedMapArg = new Objectmap<DbDoc<any>>()
|
||||
savedMapArg = new Objectmap<DbDoc<any>>();
|
||||
}
|
||||
savedMapArg.add(this)
|
||||
this.save()
|
||||
savedMapArg.add(this);
|
||||
this.save();
|
||||
for (let propertyKey in this) {
|
||||
let property: any = this[ propertyKey ]
|
||||
let property: any = this[propertyKey];
|
||||
if (property instanceof DbDoc && !savedMapArg.checkForObject(property)) {
|
||||
property.saveDeep(savedMapArg)
|
||||
property.saveDeep(savedMapArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createSavableObject () {
|
||||
let saveableObject: any = {} // is not exposed to outside, so any is ok here
|
||||
createSavableObject() {
|
||||
let saveableObject: any = {}; // is not exposed to outside, so any is ok here
|
||||
for (let propertyNameString of this.saveableProperties) {
|
||||
saveableObject[ propertyNameString ] = this[ propertyNameString ]
|
||||
saveableObject[propertyNameString] = this[propertyNameString];
|
||||
}
|
||||
return saveableObject
|
||||
return saveableObject;
|
||||
}
|
||||
}
|
||||
|
127
ts/smartdata.classes.dbtable.ts
Normal file
127
ts/smartdata.classes.dbtable.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import * as plugins from "./smartdata.plugins";
|
||||
import { Db } from "./smartdata.classes.db";
|
||||
import { DbDoc } from "./smartdata.classes.dbdoc";
|
||||
|
||||
// RethinkDb Interfaces
|
||||
import { WriteResult, Cursor } from "rethinkdb";
|
||||
|
||||
export interface IFindOptions {
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface IDocValidationFunc<T> {
|
||||
(doc: T): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a decorator that will tell the decorated class what dbTable to use
|
||||
* @param db
|
||||
*/
|
||||
export function Table(db: Db) {
|
||||
return function(constructor) {
|
||||
constructor["dbTable"] = new DbTable(constructor, db);
|
||||
};
|
||||
}
|
||||
|
||||
export class DbTable<T> {
|
||||
/**
|
||||
* the collection that is used
|
||||
*/
|
||||
table: plugins.rethinkDb.Table;
|
||||
objectValidation: IDocValidationFunc<T> = null;
|
||||
tableName: string;
|
||||
db: Db;
|
||||
|
||||
constructor(collectedClassArg: T & DbDoc<T>, dbArg: Db) {
|
||||
// tell the collection where it belongs
|
||||
this.tableName = collectedClassArg.name;
|
||||
this.db = dbArg;
|
||||
|
||||
// tell the db class about it (important since Db uses different systems under the hood)
|
||||
this.db.addTable(this);
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.table) {
|
||||
// connect this instance to a RethinkDB table
|
||||
const availableTables = await plugins.rethinkDb
|
||||
.db(this.db.dbName)
|
||||
.tableList()
|
||||
.run(this.db.dbConnection);
|
||||
if (availableTables.indexOf(this.tableName)) {
|
||||
await plugins.rethinkDb
|
||||
.db(this.db.dbName)
|
||||
.tableCreate(this.tableName)
|
||||
.run(this.db.dbConnection);
|
||||
}
|
||||
}
|
||||
this.table = plugins.rethinkDb.table(this.tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a validation function that all newly inserted and updated objects have to pass
|
||||
*/
|
||||
addDocValidation(funcArg: IDocValidationFunc<T>) {
|
||||
this.objectValidation = funcArg;
|
||||
}
|
||||
|
||||
/**
|
||||
* finds an object in the DbCollection
|
||||
*/
|
||||
async find(filterObject: any): Promise<any> {
|
||||
await this.init();
|
||||
let cursor = await plugins.rethinkDb
|
||||
.table(this.tableName)
|
||||
.filter(filterObject)
|
||||
.run(this.db.dbConnection);
|
||||
return await cursor.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* create an object in the database
|
||||
*/
|
||||
async insert(dbDocArg: T & DbDoc<T>): Promise<WriteResult> {
|
||||
await this.init();
|
||||
await this.checkDoc(dbDocArg);
|
||||
return await plugins.rethinkDb
|
||||
.table(this.tableName)
|
||||
.insert(dbDocArg.createSavableObject())
|
||||
.run(this.db.dbConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* inserts object into the DbCollection
|
||||
*/
|
||||
async update(dbDocArg: T & DbDoc<T>): Promise<WriteResult> {
|
||||
await this.init();
|
||||
await this.checkDoc(dbDocArg);
|
||||
console.log(this.tableName, dbDocArg.createSavableObject());
|
||||
return await plugins.rethinkDb
|
||||
.table(this.tableName)
|
||||
.update(dbDocArg.createSavableObject())
|
||||
.run(this.db.dbConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* checks a Doc for constraints
|
||||
* if this.objectValidation is not set it passes.
|
||||
*/
|
||||
private checkDoc(docArg: T): Promise<void> {
|
||||
let 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;
|
||||
}
|
||||
|
||||
extractKey(writeResult: WriteResult) {}
|
||||
}
|
@ -1,15 +1,8 @@
|
||||
import * as assert from 'assert'
|
||||
import * as beautylog from 'beautylog'
|
||||
import * as lodash from 'lodash'
|
||||
import * as rethinkDb from 'rethinkdb'
|
||||
import * as smartq from 'smartq'
|
||||
import * as smartstring from 'smartstring'
|
||||
import * as assert from "assert";
|
||||
import * as beautylog from "beautylog";
|
||||
import * as lodash from "lodash";
|
||||
import * as rethinkDb from "rethinkdb";
|
||||
import * as smartq from "smartq";
|
||||
import * as smartstring from "smartstring";
|
||||
|
||||
export {
|
||||
assert,
|
||||
beautylog,
|
||||
lodash,
|
||||
smartq,
|
||||
rethinkDb,
|
||||
smartstring
|
||||
}
|
||||
export { assert, beautylog, lodash, smartq, rethinkDb, smartstring };
|
||||
|
Reference in New Issue
Block a user