diff --git a/package.json b/package.json index e2623eb..41334d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@grandlinex/bundle-postgresql", - "version": "0.14.0", + "version": "0.15.0", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -25,35 +25,35 @@ }, "license": "BSD-3-Clause", "dependencies": { - "@grandlinex/core": "^0.14.0", + "@grandlinex/core": "^0.15.0", "pg": "^8.7.1" }, "devDependencies": { - "@types/jest": "^27.0.2", + "@types/jest": "^27.0.3", "@types/jsonwebtoken": "^8.5.6", - "@types/node": "^16.11.7", + "@types/node": "^16.11.11", "@types/pg": "^8.6.1", - "@typescript-eslint/eslint-plugin": "^4.31.2", - "@typescript-eslint/parser": "^4.31.2", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", "cross-env": "^7.0.3", - "eslint": "^7.32.0", - "eslint-config-airbnb": "^19.0.0", - "eslint-config-airbnb-typescript": "^14.0.1", + "eslint": "^8.3.0", + "eslint-config-airbnb": "^19.0.2", + "eslint-config-airbnb-typescript": "^16.1.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jest": "^25.2.4", + "eslint-plugin-jest": "^25.3.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-react": "^7.27.0", + "eslint-plugin-react": "^7.27.1", "eslint-plugin-react-hooks": "^4.3.0", - "jest": "^27.3.1", + "jest": "^27.4.3", "jest-junit": "^13.0.0", - "prettier": "^2.4.1", + "prettier": "^2.5.0", "ts-jest": "^27.0.7", "ts-loader": "^9.2.6", "ts-node": "^10.4.0", - "typedoc": "^0.22.9", - "typescript": "^4.4.4" + "typedoc": "^0.22.10", + "typescript": "^4.5.2" }, "repository": { "type": "git", diff --git a/src/class/PGCon.ts b/src/class/PGCon.ts index 7505d24..60bac04 100644 --- a/src/class/PGCon.ts +++ b/src/class/PGCon.ts @@ -2,37 +2,24 @@ import { ConfigType, CoreDBCon, CoreEntity, + EntityConfig, + getColumnMeta, ICoreKernelModule, IDataBase, } from '@grandlinex/core'; import { RawQuery } from '@grandlinex/core/dist/lib'; import { Client, QueryResult } from 'pg'; +import { + buildSearchQ, + mappingWithDataType, + objToTable, + rowToObj, + tableToObj, +} from '../util'; type PGDBType = Client; -function buildSearchQ( - search: { [P in keyof E]?: E[P] }, - param: any[], - searchQ: string -) { - let temp = searchQ; - const keys: (keyof E)[] = Object.keys(search) as (keyof E)[]; - if (keys.length > 0) { - const filter: string[] = []; - let count = 1; - for (const key of keys) { - if (search[key] !== undefined) { - filter.push(`${key} = $${count++}`); - param.push(search[key]); - } - } - if (filter.length > 0) { - temp = ` WHERE ${filter.join(' AND ')}`; - } - } - return temp; -} export default abstract class PGCon extends CoreDBCon implements IDataBase @@ -49,30 +36,20 @@ export default abstract class PGCon } async createEntity( - className: string, + config: EntityConfig, entity: E ): Promise { const clone: any = entity; - const keys = Object.keys(entity); - const param: any[] = []; - const vals: string[] = []; - const newKeys: string[] = []; - let index = 1; - keys.forEach((key) => { - if (key === 'e_id' && clone[key] === null) { - return; - } - newKeys.push(key); - param.push(clone[key]); - vals.push(`$${index}`); - index++; - }); + const [keys, values, params] = objToTable(entity); + const result = await this.execScripts([ { - exec: `INSERT INTO ${this.schemaName}.${className}(${newKeys.join( + exec: `INSERT INTO ${this.schemaName}.${config.className}(${keys.join( ', ' - )}) VALUES (${vals.join(', ')}) returning e_id`, - param, + )}) + VALUES (${values.join(', ')}) + returning e_id`, + param: params, }, ]); clone.e_id = result[0]?.rows[0]?.e_id; @@ -80,54 +57,51 @@ export default abstract class PGCon } async updateEntity( - className: string, + config: EntityConfig, entity: E ): Promise { if (entity.e_id) { - const clone: any = entity; - const keys = Object.keys(entity); - const param: any[] = []; - const vals: string[] = []; - let index = 1; - keys.forEach((key) => { - if (key === 'e_id') { - return; - } - param.push(clone[key]); - vals.push(`${key}=$${index}`); - index++; - }); + const [, values, params] = objToTable(entity, true); const result = await this.execScripts([ { - exec: `UPDATE ${this.schemaName}.${className} SET ${vals.join( - ', ' - )} WHERE e_id=${entity.e_id};`, - param, + exec: `UPDATE ${this.schemaName}.${config.className} + SET ${values.join(', ')} + WHERE e_id = ${entity.e_id};`, + param: params, }, ]); - return result[0] ? clone : null; + return result[0] ? entity : null; } return null; } async getEntityById( - className: string, + config: EntityConfig, id: number ): Promise { const query = await this.execScripts([ { - exec: `SELECT * FROM ${this.schemaName}.${className} WHERE e_id=${id};`, + exec: `SELECT * + FROM ${this.schemaName}.${config.className} + WHERE e_id = ${id};`, param: [], }, ]); - return query[0]?.rows[0]; + + const res = query[0]?.rows[0]; + if (res) { + return rowToObj(config, res); + } + return null; } async deleteEntityById(className: string, id: number): Promise { const query = await this.execScripts([ { - exec: `DELETE FROM ${this.schemaName}.${className} WHERE e_id=${id};`, + exec: `DELETE + FROM ${this.schemaName}.${className} + WHERE e_id = ${id};`, param: [], }, ]); @@ -135,7 +109,7 @@ export default abstract class PGCon } async findEntity( - className: string, + config: EntityConfig, search: { [P in keyof E]?: E[P] | undefined } ): Promise { let searchQ = ''; @@ -145,31 +119,48 @@ export default abstract class PGCon const query = await this.execScripts([ { - exec: `SELECT * FROM ${this.schemaName}.${className}${searchQ};`, + exec: `SELECT * + FROM ${this.schemaName}.${config.className} ${searchQ};`, param, }, ]); - return query[0]?.rows[0] || null; + + const res = query[0]?.rows[0]; + if (res) { + return rowToObj(config, res); + } + return null; } async getEntityList( - className: string, - search: { + config: EntityConfig, + limit?: number, + search?: { [P in keyof E]: E[P]; } ): Promise { + if (limit === 0) { + return []; + } let searchQ = ''; + const range = limit ? ` LIMIT ${limit}` : ''; const param: any[] = []; if (search) { searchQ = buildSearchQ(search, param, searchQ); } const query = await this.execScripts([ { - exec: `SELECT * FROM ${this.schemaName}.${className}${searchQ};`, + exec: `SELECT * + FROM ${this.schemaName}.${config.className} ${searchQ}${range};`, param, }, ]); - return query[0]?.rows || []; + + const res = query[0]?.rows; + if (res) { + return tableToObj(config, res); + } + return []; } async initEntity( @@ -178,9 +169,10 @@ export default abstract class PGCon ): Promise { await this.execScripts([ { - exec: `CREATE TABLE ${ - this.schemaName - }.${className}(${this.transformEntityKeys(entity)});`, + exec: `CREATE TABLE ${this.schemaName}.${className} + ( + ${this.transformEntityKeys(entity)} + );`, param: [], }, ]); @@ -192,11 +184,13 @@ export default abstract class PGCon const out: string[] = []; keys.forEach((key) => { - if (key === 'e_id') { + const meta = getColumnMeta(entity, key); + if (meta?.dataType) { + mappingWithDataType(meta, out, key, this.schemaName); + } else if (key === 'e_id') { out.push(`e_id SERIAL PRIMARY KEY`); } else { const type = typeof entity[key]; - const dat = entity[key] as any; switch (type) { case 'bigint': case 'number': @@ -205,11 +199,6 @@ export default abstract class PGCon case 'string': out.push(`${key} TEXT`); break; - case 'object': - if (dat instanceof Date) { - out.push(`${key} TEXT`); - } - break; default: break; } @@ -223,9 +212,9 @@ export default abstract class PGCon try { const query = await this.execScripts([ { - exec: `DELETE - FROM ${this.schemaName}.config - WHERE c_key = $1;`, + exec: `DELETE + FROM ${this.schemaName}.config + WHERE c_key = $1;`, param: [key], }, ]); @@ -308,8 +297,8 @@ export default abstract class PGCon const query = await this.execScripts([ { exec: `SELECT * - FROM ${this.schemaName}.config - WHERE c_key = '${key}'`, + FROM ${this.schemaName}.config + WHERE c_key = '${key}'`, param: [], }, ]); @@ -320,7 +309,7 @@ export default abstract class PGCon const query = await this.execScripts([ { exec: `INSERT INTO ${this.schemaName}.config (c_key, c_value) - VALUES ('${key}', '${value}');`, + VALUES ('${key}', '${value}');`, param: [], }, ]); @@ -334,8 +323,8 @@ export default abstract class PGCon const query = await this.execScripts([ { exec: `SELECT * - FROM ${this.schemaName}.config - WHERE c_key = '${key}'`, + FROM ${this.schemaName}.config + WHERE c_key = '${key}'`, param: [], }, ]); diff --git a/src/index.ts b/src/index.ts index b81009f..fe52258 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import PGCon from './class/PGCon'; -// eslint-disable-next-line import/prefer-default-export +export * from './util'; export { PGCon }; +export default PGCon; diff --git a/src/util/buildSearchQ.ts b/src/util/buildSearchQ.ts new file mode 100644 index 0000000..564d91e --- /dev/null +++ b/src/util/buildSearchQ.ts @@ -0,0 +1,22 @@ +export default function buildSearchQ( + search: { [P in keyof E]?: E[P] }, + param: any[], + searchQ: string +) { + let temp = searchQ; + const keys: (keyof E)[] = Object.keys(search) as (keyof E)[]; + if (keys.length > 0) { + const filter: string[] = []; + let count = 1; + for (const key of keys) { + if (search[key] !== undefined) { + filter.push(`${key} = $${count++}`); + param.push(search[key]); + } + } + if (filter.length > 0) { + temp = ` WHERE ${filter.join(' AND ')}`; + } + } + return temp; +} diff --git a/src/util/converter.ts b/src/util/converter.ts new file mode 100644 index 0000000..160d814 --- /dev/null +++ b/src/util/converter.ts @@ -0,0 +1,69 @@ +import { + ColumnProps, + CoreEntity, + EntityConfig, + getColumnMeta, +} from '@grandlinex/core'; + +function convertSpecialFields( + meta: ColumnProps, + clone: any, + key: keyof E, + params: any[] +) { + if (meta.dataType === 'json') { + params.push(JSON.stringify(clone[key])); + } else { + params.push(clone[key]); + } +} + +export function objToTable( + entity: E, + update?: boolean +): [(keyof E)[], string[], unknown[]] { + const clone: any = entity; + const keysOrigal = Object.keys(entity) as (keyof E)[]; + const keys: (keyof E)[] = []; + const params: any[] = []; + const values: string[] = []; + + keysOrigal.forEach((key, index) => { + const meta = getColumnMeta(entity, key); + if (!meta) { + throw new Error('No col meta'); + } + if (meta.primaryKey) { + return; + } + convertSpecialFields(meta, clone, key, params); + if (update) { + values.push(`${key}=$${index}`); + } else { + values.push(`$${index}`); + } + + keys.push(key); + }); + + if (values.length === params.length && params.length === keys.length) { + return [keys, values, params]; + } + throw new Error('Invalid output length'); +} + +export function rowToObj( + config: EntityConfig, + row: any +): E { + return row; +} + +export function tableToObj( + config: EntityConfig, + table: any[] +): E[] { + return table.map((row) => { + return rowToObj(config, row); + }); +} diff --git a/src/util/index.ts b/src/util/index.ts new file mode 100644 index 0000000..4c5ff0d --- /dev/null +++ b/src/util/index.ts @@ -0,0 +1,6 @@ +import buildSearchQ from './buildSearchQ'; +import resolveDBType from './resolveDBType'; +import mappingWithDataType from './mappingWithDataType'; + +export * from './converter'; +export { buildSearchQ, resolveDBType, mappingWithDataType }; diff --git a/src/util/mappingWithDataType.ts b/src/util/mappingWithDataType.ts new file mode 100644 index 0000000..60f5779 --- /dev/null +++ b/src/util/mappingWithDataType.ts @@ -0,0 +1,27 @@ +import { ColumnProps, CoreEntity } from '@grandlinex/core'; +import resolveDBType from './resolveDBType'; + +export default function mappingWithDataType( + meta: ColumnProps, + out: string[], + key: keyof E, + schemaName: string +): void { + if (!meta.dataType) { + throw new Error('DataType not set'); + } + if (meta.dataType === 'serial') { + out.push(`${key} SERIAL NOT NUL`); + } else { + const canBeNull = `${meta.canBeNull ? '' : ' NOT NULL'}`; + const unique = `${meta.unique ? ' UNIQUE' : ''}`; + let foreignKey: string; + if (meta.foreignKey) { + foreignKey = ` REFERENCES ${schemaName}.${meta.foreignKey.relation}(${meta.foreignKey.key})`; + } else { + foreignKey = ''; + } + const dbType = resolveDBType(meta.dataType); + out.push(`${key} ${dbType}${foreignKey}${canBeNull}${unique}`); + } +} diff --git a/src/util/resolveDBType.ts b/src/util/resolveDBType.ts new file mode 100644 index 0000000..11e77c2 --- /dev/null +++ b/src/util/resolveDBType.ts @@ -0,0 +1,24 @@ +import { DataType } from '@grandlinex/core'; + +export default function resolveDBType(dType: DataType) { + switch (dType) { + case 'int': + return 'INT'; + case 'double': + case 'float': + return 'REAL'; + case 'blob': + return 'BYTEA'; + case 'string': + case 'text': + return 'TEXT'; + case 'boolean': + return 'BOOLEAN'; + case 'date': + return 'TIMESTAMP'; + case 'json': + return 'JSON'; + default: + throw Error('TypeNotSupported'); + } +} diff --git a/tests/DebugClasses.ts b/tests/DebugClasses.ts index e571b8e..4a25360 100644 --- a/tests/DebugClasses.ts +++ b/tests/DebugClasses.ts @@ -1,18 +1,17 @@ import CoreKernel, { + camelToSnakeCase, + Column, CoreClient, CoreCryptoClient, CoreDBCon, CoreEntity, - CoreKernelModule, CoreLoopService, + CoreKernelModule, Entity, ICoreCClient, ICoreKernelModule, OfflineService, - sleep + CoreDBUpdate, EProperties } from "@grandlinex/core"; -import CoreDBUpdate from "@grandlinex/core/dist/classes/CoreDBUpdate"; -import {PGCon} from "../src"; +import PGCon from "../src/"; import * as Path from "path"; - - type TCoreKernel=CoreKernel; class TestBaseMod extends CoreKernelModule { @@ -40,7 +39,7 @@ class TestKernel extends CoreKernel { this.setBaseModule(new TestBaseMod("testbase2",this)); this.setCryptoClient(new CoreCryptoClient(CoreCryptoClient.fromPW("testpw"))) this.addModule(new TestModuel(this)); - } + } } @@ -73,18 +72,94 @@ class TestDBUpdate extends CoreDBUpdate{ } } +@Entity("ExampleEntity") +class ExampleEntity extends CoreEntity { + @Column() + title: string; + @Column({ + canBeNull:true, + dataType:"int" + }) + age: number|null; + @Column({ + canBeNull:true, + dataType:"string" + }) + description: string|null; + + + constructor(props?:EProperties) { + super(); + this.title = props?.title||""; + this.description = props?.description||null; + this.age = props?.age||-1; + } +} +@Entity("TestEntity") class TestEntity extends CoreEntity{ + @Column() name:string - address?:string + @Column({ + canBeNull:true, + dataType:"string" + }) + address:string|null + @Column() age:number - time?:Date - - constructor( name: string,age: number ,address?: string , time?: Date) { - super( 0 ); - this.name = name; - this.address = address; - this.age = age; - this.time = time; + @Column({ + canBeNull:true, + dataType:"date" + }) + time:Date|null + + @Column({ + canBeNull:true, + dataType:"blob" + }) + raw: Buffer|null; + + @Column({ + canBeNull:true, + dataType:"json" + }) + json: any|null; + + + constructor( param?:EProperties ) { + super(); + this.name = param?.name||""; + this.address = param?.address||null; + this.age = param?.age||-1; + this.time = param?.time||null; + this.raw=param?.raw||null; + this.json=param?.json||null + } +} +@Entity("TestEntityLinked") +class TestEntityLinked extends TestEntity{ + + @Column({ + dataType:"int", + unique:true, + foreignKey:{ + key:"e_id", + relation:camelToSnakeCase('TestEntity') + } + }) + link:number|null + @Column({ + dataType:"boolean" + }) + flag:boolean + @Column({ + dataType:"float" + }) + floating:number + constructor( param?:EProperties ) { + super(param); + this.link = param?.link||null; + this.flag = !!param?.flag; + this.floating = param?.floating||0.0; } } class TestModuel extends CoreKernelModule{ @@ -96,7 +171,9 @@ class TestModuel extends CoreKernelModule)) } @@ -119,8 +196,10 @@ export { TCoreKernel, TestBaseMod, TestKernel, + TestEntityLinked, TestClient, TestDBUpdate, TestEntity, TestModuel, - } + ExampleEntity +} diff --git a/tests/core.test.ts b/tests/core.test.ts index 64d0f2f..f58fd85 100644 --- a/tests/core.test.ts +++ b/tests/core.test.ts @@ -1,10 +1,9 @@ - import * as Path from 'path'; +import * as Path from 'path'; -import { TestEntity, TestKernel } from './DebugClasses'; - import { +import {TestEntity, TestEntityLinked, TestKernel} from './DebugClasses'; +import { CoreDBCon, - CoreEntity, CoreEntityWrapper, createFolderIfNotExist, ICoreKernelModule, removeFolderIfExist, sleep @@ -18,16 +17,16 @@ const msiPath = Path.join(__dirname, '..', 'data'); const testPath = Path.join(__dirname, '..', 'data', 'config'); - createFolderIfNotExist(msiPath); - createFolderIfNotExist(testPath); +createFolderIfNotExist(msiPath); +createFolderIfNotExist(testPath); - let kernel = new TestKernel(appName,appCode,testPath); +let kernel = new TestKernel(appName,appCode,testPath); const testText = 'hello_world'; describe('Clean start', () => { - test('preload', async () => { + test('preload', async () => { expect(kernel.getState()).toBe('init'); }); test('start kernel', async () => { @@ -74,145 +73,231 @@ describe('TestDatabase', () => { }); }) - describe('Entity', () => { - let wrapper:undefined|CoreEntityWrapper=undefined; - - let entity:TestEntity = new TestEntity("Bob",30,"home",new Date()); - let entity2:TestEntity = new TestEntity("Alice",29 ); - - test('get wrapper class', async () => { - const mod=kernel.getChildModule("testModule") as ICoreKernelModule; - const db = mod.getDb() as CoreDBCon; - wrapper=db.getEntityWrapper("TestEntity") - expect(wrapper).not.toBeUndefined() - if (wrapper){ - expect((await wrapper.getObjList()).length).toBe(0) - } - }); - test('create new', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - entity= await wrapper.createObject(entity) || entity; - expect(entity?.e_id).not.toBeNull() - expect((await wrapper.getObjList()).length).toBe(1) - } - }); - test('create new 2', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - entity2=await wrapper.createObject(entity2)||entity2; - expect(entity2?.e_id).not.toBeNull() - expect((await wrapper.getObjList()).length).toBe(2) - } - }); - test('get by id', async () => { - expect(wrapper).not.toBeUndefined() - expect(entity.e_id).not.toBeNull() - if (wrapper ){ - expect((await wrapper.getObjById(entity.e_id as number))).not.toBeNull() - } - }); - - test('listing search id', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - expect((await wrapper.getObjList({ - e_id: entity.e_id, - }))).toHaveLength(1); - } - }); - test('listing search version', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - expect((await wrapper.getObjList({ - e_version:0 - }))).toHaveLength(2); - } - }); - test('listing search bob', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - expect((await wrapper.getObjList({ - name:"Bob" - }))).toHaveLength(1); - } - }); - test('listing search bob', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - expect((await wrapper.getObjList({ - name:"Bob" - }))).toHaveLength(1); - } - }); - test('listing search bob check date', async () => { - expect(wrapper).not.toBeUndefined() - - if (wrapper){ - const bonb=await wrapper.getObjList({ - name:"Bob" - }) - expect(bonb).toHaveLength(1); - expect(bonb[0].time).not.toBeNull(); - } - }); - test('listing search version no result', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - expect((await wrapper.getObjList({ - e_version:2 - }))).toHaveLength(0); - } - }); - - test('find entity', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - expect((await wrapper.findObj({ - e_version:0 - }))).not.toBeNull(); - } - }); - test('find entity no result', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - expect((await wrapper.findObj({ - e_version:2 - }))).toBeNull(); - } - }); - test('find entity by id', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - expect((await wrapper.findObj({ - e_id: entity.e_id, - }))).not.toBeNull(); - } - }); - - test('update', async () => { - expect(wrapper).not.toBeUndefined() - expect(entity.e_id).not.toBeNull() - if (wrapper){ - expect(entity.name).toBe("Bob") - entity.name="Bobi"; - const update=await wrapper.updateObject(entity); - expect(update).not.toBeNull() - expect(update?.name).toBe("Bobi") - expect((await wrapper.getObjList()).length).toBe(2) - } - }); - test('delete', async () => { - expect(wrapper).not.toBeUndefined() - if (wrapper){ - expect((await wrapper.delete(entity.e_id as number))).toBeTruthy(); - expect((await wrapper.getObjList()).length).toBe(1) - expect((await wrapper.delete(entity2.e_id as number))).toBeTruthy(); - expect((await wrapper.getObjList()).length).toBe(0) - } - }); - }) +describe('Entity', () => { + let wrapper:undefined|CoreEntityWrapper=undefined; + let wrapper2:undefined|CoreEntityWrapper=undefined; + + let entity:TestEntity = new TestEntity({ + e_id:null, + name:"Bob", + age:30, + address:"home", + time:new Date(), + raw:null, + json:{some:"value"} + }); + let entity2:TestEntity = new TestEntity({ + e_id:null, + name:"Alice", + age:29, + address:null, + time:null, + raw:null, + json:[{some:"value"},{some:"array"}] + }); + let entity3:TestEntityLinked = new TestEntityLinked({ + e_id:null, + name:"Alice", + age:29, + address:null, + time:null, + link:null, + raw:Buffer.from("message"), + json:null, + flag:true, + floating:0.9 + }); + + test('get wrapper class', async () => { + const mod=kernel.getChildModule("testModule") as ICoreKernelModule; + const db = mod.getDb() as CoreDBCon; + wrapper=db.getEntityWrapper("TestEntity") + wrapper2=db.getEntityWrapper("TestEntityLinked") + expect(wrapper).not.toBeUndefined() + if (wrapper){ + expect((await wrapper.getObjList()).length).toBe(0) + } + }); + test('create new', async () => { + expect(wrapper).not.toBeUndefined() + if (wrapper){ + entity= await wrapper.createObject(entity) || entity; + expect(entity?.e_id).not.toBeNull() + expect((await wrapper.getObjList()).length).toBe(1) + } + }); + test('create new 2', async () => { + expect(wrapper).not.toBeUndefined() + if (wrapper){ + entity2=await wrapper.createObject(entity2)||entity2; + expect(entity2?.e_id).not.toBeNull() + expect((await wrapper.getObjList()).length).toBe(2) + } + }); + test('create new 3', async () => { + expect(wrapper2).not.toBeUndefined() + if (wrapper2){ + entity3.link=entity.e_id + entity3=await wrapper2.createObject(entity3)||entity3; + expect(entity2?.e_id).not.toBeNull() + expect((await wrapper2.getObjList()).length).toBe(1) + } + }); + test('get by id', async () => { + expect(wrapper).not.toBeUndefined() + expect(entity.e_id).not.toBeNull() + if (wrapper ){ + expect((await wrapper.getObjById(entity.e_id as number))).not.toBeNull() + } + }); + + test('listing search id', async () => { + expect(wrapper).not.toBeUndefined() + if (wrapper){ + expect((await wrapper.getObjList({ + e_id: entity.e_id, + }))).toHaveLength(1); + } + }); + test('listing search fk id', async () => { + expect(wrapper2).not.toBeUndefined() + if (wrapper2){ + expect((await wrapper2.getObjList({ + link: entity.e_id, + }))).toHaveLength(1); + } + }); + test('listing search bob', async () => { + expect(wrapper).not.toBeUndefined() + if (wrapper){ + expect((await wrapper.getObjList({ + name:"Bob" + }))).toHaveLength(1); + } + }); + test('listing search bob', async () => { + expect(wrapper).not.toBeUndefined() + if (wrapper){ + expect((await wrapper.getObjList({ + name:"Bob" + }))).toHaveLength(1); + } + }); + test('listing search bob check date', async () => { + expect(wrapper).not.toBeUndefined() + + if (wrapper){ + const bonb=await wrapper.getObjList({ + name:"Bob" + }) + expect(bonb).toHaveLength(1); + expect(bonb[0].time).not.toBeNull(); + } + }); + test('find entity by id', async () => { + expect(wrapper).not.toBeUndefined() + if (wrapper){ + expect((await wrapper.findObj({ + e_id: entity.e_id, + }))).not.toBeNull(); + } + }); + + test('update', async () => { + expect(wrapper).not.toBeUndefined() + expect(entity.e_id).not.toBeNull() + if (wrapper){ + expect(entity.name).toBe("Bob") + entity.name="Bobi"; + const update=await wrapper.updateObject(entity); + expect(update).not.toBeNull() + expect(update?.name).toBe("Bobi") + expect((await wrapper.getObjList()).length).toBe(2) + } + }); + test('delete', async () => { + expect(wrapper).not.toBeUndefined() + // expect(wrapper2).not.toBeUndefined() + + if (wrapper2){ + expect((await wrapper2.getObjList()).length).toBe(1) + expect((await wrapper2.delete(entity3.e_id as number))).toBeTruthy(); + expect((await wrapper2.getObjList()).length).toBe(0) + } + if (wrapper){ + expect((await wrapper.delete(entity.e_id as number))).toBeTruthy(); + expect((await wrapper.getObjList()).length).toBe(1) + expect((await wrapper.delete(entity2.e_id as number))).toBeTruthy(); + expect((await wrapper.getObjList()).length).toBe(0) + } + }); +}) +describe('Bulk Entity', () => { + let wrapper:undefined|CoreEntityWrapper=undefined; + let idList:number[]=[] + let max=100; + test('get wrapper class', async () => { + const mod=kernel.getChildModule("testModule") as ICoreKernelModule; + const db = mod.getDb() as CoreDBCon; + wrapper=db.getEntityWrapper("TestEntity") + expect(wrapper).not.toBeUndefined() + if (wrapper){ + expect((await wrapper.getObjList()).length).toBe(0) + } + }); + test('create new', async () => { + expect(wrapper).not.toBeUndefined() + expect(wrapper).not.toBeNull() + if (wrapper){ + for (let i = 0; i < max; i++) { + let entity:TestEntity = new TestEntity({ + e_id:null, + name:"Bob", + age:i, + address:"home", + time:new Date(), + raw:null, + json:{some:"value",index:i} + }); + entity= await wrapper.createObject(entity) || entity; + expect(entity.e_id).not.toBeNull() + idList.push(entity.e_id as number) + expect((await wrapper.getObjList()).length).toBe(i+1) + } + }else { + expect(false).toBeTruthy() + } + + }); + test('listing search id limit 0', async () => { + expect(wrapper).not.toBeUndefined() + if (wrapper){ + expect((await wrapper.getObjList(undefined,0))).toHaveLength(0); + } + }); + test('listing search id limit 1', async () => { + expect(wrapper).not.toBeUndefined() + if (wrapper){ + expect((await wrapper.getObjList(undefined,1))).toHaveLength(1); + } + }); + test('listing search id limit 2', async () => { + expect(wrapper).not.toBeUndefined() + if (wrapper){ + expect((await wrapper.getObjList(undefined,2))).toHaveLength(2); + } + }); + test('delete', async () => { + expect(wrapper).not.toBeUndefined() + if (wrapper){ + for (const el of idList) { + expect((await wrapper.delete(el))).toBeTruthy(); + } + expect((await wrapper.getObjList()).length).toBe(0) + } + }); +}) describe("ShutDown",()=>{ diff --git a/tsconfig.json b/tsconfig.json index e8e13bb..c4d97b8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -67,8 +67,8 @@ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ "skipLibCheck": true,