diff --git a/backend/database/core.ts b/backend/database/core.ts index 4468715f..26a95a84 100644 --- a/backend/database/core.ts +++ b/backend/database/core.ts @@ -1,5 +1,6 @@ import { knex, Knex } from 'knex'; import { getRandomString, getValueMapFromList } from 'utils'; +import { DatabaseBase, GetAllOptions, QueryFilter } from 'utils/db/types'; import { CannotCommitError, DatabaseError, @@ -15,14 +16,8 @@ import { SchemaMap, TargetField, } from '../../schemas/types'; -import { getDefaultMetaFieldValueMap, sqliteTypeMap, SYSTEM } from '../common'; -import { - ColumnDiff, - FieldValueMap, - GetAllOptions, - GetQueryBuilderOptions, - QueryFilter, -} from './types'; +import { getDefaultMetaFieldValueMap, sqliteTypeMap, SYSTEM } from '../helpers'; +import { ColumnDiff, FieldValueMap, GetQueryBuilderOptions } from './types'; /** * # DatabaseCore @@ -43,7 +38,7 @@ import { * the `fieldValueMap`. */ -export default class DatabaseCore { +export default class DatabaseCore extends DatabaseBase { knex?: Knex; typeMap = sqliteTypeMap; dbPath: string; @@ -51,6 +46,7 @@ export default class DatabaseCore { connectionParams: Knex.Config; constructor(dbPath?: string) { + super(); this.dbPath = dbPath ?? ':memory:'; this.connectionParams = { client: 'sqlite3', @@ -133,7 +129,10 @@ export default class DatabaseCore { return row.length > 0; } - async insert(schemaName: string, fieldValueMap: FieldValueMap) { + async insert( + schemaName: string, + fieldValueMap: FieldValueMap + ): Promise { // insert parent if (this.schemaMap[schemaName].isSingle) { await this.#updateSingleValues(schemaName, fieldValueMap); diff --git a/backend/database/manager.ts b/backend/database/manager.ts index 51d142b8..8a9590c6 100644 --- a/backend/database/manager.ts +++ b/backend/database/manager.ts @@ -1,4 +1,6 @@ +import { databaseMethodSet } from 'backend/helpers'; import fs from 'fs/promises'; +import { DatabaseMethod } from 'utils/db/types'; import { getSchemas } from '../../schemas'; import patches from '../patches'; import DatabaseCore from './core'; @@ -7,11 +9,14 @@ import { Patch } from './types'; export class DatabaseManager { db?: DatabaseCore; - constructor() {} - async createNewDatabase(dbPath: string, countryCode: string) { + get #isInitialized(): boolean { + return this.db !== undefined && this.db.knex !== undefined; + } + + async createNewDatabase(dbPath: string, countryCode?: string) { await this.#unlinkIfExists(dbPath); - this.connectToDatabase(dbPath, countryCode); + await this.connectToDatabase(dbPath, countryCode); } async connectToDatabase(dbPath: string, countryCode?: string) { @@ -22,14 +27,37 @@ export class DatabaseManager { const schemaMap = getSchemas(countryCode); this.db.setSchemaMap(schemaMap); - await this.migrate(); + await this.#migrate(); } - async migrate() { - if (this.db === undefined) { + async call(method: DatabaseMethod, ...args: unknown[]) { + if (!this.#isInitialized) { return; } + if (!databaseMethodSet.has(method)) { + return; + } + + // @ts-ignore + const response = await this.db[method](...args); + if (method === 'close') { + delete this.db; + } + + return response; + } + + async #migrate(): Promise { + if (!this.#isInitialized) { + return; + } + + const isFirstRun = await this.#getIsFirstRun(); + if (isFirstRun) { + await this.db!.migrate(); + } + const patchesToExecute = await this.#getPatchesToExecute(); const preMigrationPatches = patchesToExecute.filter( (p) => p.patch.beforeMigrate @@ -39,7 +67,7 @@ export class DatabaseManager { ); await runPatches(preMigrationPatches, this); - await this.db.migrate(); + await this.db!.migrate(); await runPatches(postMigrationPatches, this); } @@ -60,10 +88,15 @@ export class DatabaseManager { return undefined; } - const query = await this.db.knex!('SingleValue').where({ - fieldname: 'countryCode', - parent: 'SystemSettings', - }); + let query: { countryCode: string }[] = []; + try { + query = await this.db.knex!('SingleValue').where({ + fieldname: 'countryCode', + parent: 'SystemSettings', + }); + } catch { + // Database not inialized and no countryCode passed + } if (query.length > 0) { return query[0].countryCode as string; @@ -83,6 +116,17 @@ export class DatabaseManager { throw err; } } + + async #getIsFirstRun(): Promise { + if (!this.#isInitialized) { + return true; + } + + const tableList: unknown[] = await this.db!.knex!.raw( + "SELECT name FROM sqlite_master WHERE type='table'" + ); + return tableList.length === 0; + } } export default new DatabaseManager(); diff --git a/backend/database/runPatch.ts b/backend/database/runPatch.ts index 8c521700..f93704fd 100644 --- a/backend/database/runPatch.ts +++ b/backend/database/runPatch.ts @@ -1,4 +1,4 @@ -import { getDefaultMetaFieldValueMap } from '../common'; +import { getDefaultMetaFieldValueMap } from '../helpers'; import { DatabaseManager } from './manager'; import { FieldValueMap, Patch } from './types'; diff --git a/backend/database/tests/testCore.spec.ts b/backend/database/tests/testCore.spec.ts index 9f0d926b..2b1ddd80 100644 --- a/backend/database/tests/testCore.spec.ts +++ b/backend/database/tests/testCore.spec.ts @@ -3,7 +3,7 @@ import 'mocha'; import { getMapFromList } from 'schemas/helpers'; import { FieldTypeEnum, RawValue } from 'schemas/types'; import { getValueMapFromList, sleep } from 'utils'; -import { getDefaultMetaFieldValueMap, sqliteTypeMap } from '../../common'; +import { getDefaultMetaFieldValueMap, sqliteTypeMap } from '../../helpers'; import DatabaseCore from '../core'; import { FieldValueMap, SqliteTableInfo } from '../types'; import { diff --git a/backend/database/types.ts b/backend/database/types.ts index e91d7694..ff521c53 100644 --- a/backend/database/types.ts +++ b/backend/database/types.ts @@ -1,8 +1,6 @@ import { Field, RawValue } from '../../schemas/types'; import { DatabaseManager } from './manager'; -export type QueryFilter = Record; - export interface GetQueryBuilderOptions { offset?: number; limit?: number; @@ -11,16 +9,6 @@ export interface GetQueryBuilderOptions { order?: 'desc' | 'asc'; } -export interface GetAllOptions { - fields?: string[]; - filters?: QueryFilter; - offset?: number; - limit?: number; - groupBy?: string; - orderBy?: string; - order?: 'asc' | 'desc'; -} - export type ColumnDiff = { added: Field[]; removed: string[] }; export type FieldValueMap = Record< string, diff --git a/backend/common.ts b/backend/helpers.ts similarity index 77% rename from backend/common.ts rename to backend/helpers.ts index 9eb9cb60..4e480160 100644 --- a/backend/common.ts +++ b/backend/helpers.ts @@ -1,3 +1,4 @@ +import { DatabaseMethod } from 'utils/db/types'; import { KnexColumnType } from './database/types'; export const sqliteTypeMap: Record = { @@ -34,3 +35,15 @@ export function getDefaultMetaFieldValueMap() { modified: now, }; } + +export const databaseMethodSet: Set = new Set([ + 'insert', + 'get', + 'getAll', + 'getSingleValues', + 'rename', + 'update', + 'delete', + 'close', + 'exists', +]); diff --git a/common/model.ts b/common/model.ts deleted file mode 100644 index 278a6696..00000000 --- a/common/model.ts +++ /dev/null @@ -1,6 +0,0 @@ -import models from '../models'; -import coreModels from '../frappe/models'; - -export function getModel() { - console.log(models, coreModels); -} diff --git a/tsconfig.json b/tsconfig.json index 6330a185..462722a4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,6 @@ "@/*": ["src/*"], "schemas/*": ["schemas/*"], "backend/*": ["backend/*"], - "common/*": ["common/*"], "utils/*": ["utils/*"] }, "lib": ["esnext", "dom", "dom.iterable", "scripthost"] diff --git a/utils/db/types.ts b/utils/db/types.ts new file mode 100644 index 00000000..f25170bb --- /dev/null +++ b/utils/db/types.ts @@ -0,0 +1,63 @@ +/** + * The types in this file will be used by the main db class (core.ts) in the + * backend process and the the frontend db class (dbHandler.ts). + * + * DatabaseBase is an abstract class so that the function signatures + * match on both ends. + */ + +type UnknownMap = Record; +export abstract class DatabaseBase { + // Create + abstract insert( + schemaName: string, + fieldValueMap: UnknownMap + ): Promise; + + // Read + abstract get( + schemaName: string, + name: string, + fields?: string | string[] + ): Promise; + + abstract getAll( + schemaName: string, + options: GetAllOptions + ): Promise; + + abstract getSingleValues( + ...fieldnames: ({ fieldname: string; parent?: string } | string)[] + ): Promise<{ fieldname: string; parent: string; value: unknown }[]>; + + // Update + abstract rename( + schemaName: string, + oldName: string, + newName: string + ): Promise; + + abstract update(schemaName: string, fieldValueMap: UnknownMap): Promise; + + // Delete + abstract delete(schemaName: string, name: string): Promise; + + // Other + abstract close(): Promise; + + abstract exists(schemaName: string, name?: string): Promise; +} + +export type DatabaseMethod = keyof DatabaseBase; + +export interface GetAllOptions { + fields?: string[]; + filters?: QueryFilter; + offset?: number; + limit?: number; + groupBy?: string; + orderBy?: string; + order?: 'asc' | 'desc'; +} + +export type QueryFilter = Record; diff --git a/vue.config.js b/vue.config.js index bc2e0e95..80a8bfd2 100644 --- a/vue.config.js +++ b/vue.config.js @@ -42,7 +42,6 @@ module.exports = { '~': path.resolve('.'), schemas: path.resolve(__dirname, './schemas'), backend: path.resolve(__dirname, './backend'), - common: path.resolve(__dirname, './common'), utils: path.resolve(__dirname, './utils'), });