diff --git a/frappe/core/dbHandler.ts b/frappe/core/dbHandler.ts index 8faff14e..b92aeee2 100644 --- a/frappe/core/dbHandler.ts +++ b/frappe/core/dbHandler.ts @@ -1,20 +1,114 @@ +import { DatabaseDemux } from '@/demux/db'; import { Frappe } from 'frappe/core/frappe'; +import { DatabaseBase, GetAllOptions } from 'utils/db/types'; +import { DocValueMap, RawValueMap } from './types'; -type SingleValue = { fieldname: string; parent: string; value: unknown }; +export class DatabaseHandler extends DatabaseBase { + #frappe: Frappe; + #demux: DatabaseDemux; -export class DbHandler { - frappe: Frappe; constructor(frappe: Frappe) { - this.frappe = frappe; + super(); + this.#frappe = frappe; + this.#demux = new DatabaseDemux(frappe.isElectron); } - init() {} - close() {} - exists(doctype: string, name: string): boolean { - return false; + async createNewDatabase(dbPath: string, countryCode?: string) { + await this.#demux.createNewDatabase(dbPath, countryCode); } - getSingleValues(...fieldnames: Omit[]): SingleValue[] { - return []; + async connectToDatabase(dbPath: string, countryCode?: string) { + await this.#demux.connectToDatabase(dbPath, countryCode); } + + init() { + // do nothing + } + + async insert( + schemaName: string, + docValueMap: DocValueMap + ): Promise { + let rawValueMap = this.toRawValueMap( + schemaName, + docValueMap + ) as RawValueMap; + rawValueMap = (await this.#demux.call( + 'insert', + schemaName, + rawValueMap + )) as RawValueMap; + return this.toDocValueMap(schemaName, rawValueMap) as DocValueMap; + } + + // Read + async get( + schemaName: string, + name: string, + fields?: string | string[] + ): Promise { + const rawValueMap = (await this.#demux.call( + 'get', + schemaName, + name, + fields + )) as RawValueMap; + return this.toDocValueMap(schemaName, rawValueMap) as DocValueMap; + } + + async getAll( + schemaName: string, + options: GetAllOptions + ): Promise { + const rawValueMap = (await this.#demux.call( + 'getAll', + schemaName, + options + )) as RawValueMap[]; + + return this.toDocValueMap(schemaName, rawValueMap) as DocValueMap[]; + } + + async getSingleValues( + ...fieldnames: ({ fieldname: string; parent?: string } | string)[] + ): Promise<{ fieldname: string; parent: string; value: unknown }[]> { + await this.#demux.call('getSingleValues', ...fieldnames); + } + + // Update + async rename( + schemaName: string, + oldName: string, + newName: string + ): Promise { + await this.#demux.call('rename', schemaName, oldName, newName); + } + + async update(schemaName: string, docValueMap: DocValueMap): Promise { + const rawValueMap = this.toRawValueMap(schemaName, docValueMap); + await this.#demux.call('update', schemaName, rawValueMap); + } + + // Delete + async delete(schemaName: string, name: string): Promise { + await this.#demux.call('delete', schemaName, name); + } + + // Other + async close(): Promise { + await this.#demux.call('close'); + } + + async exists(schemaName: string, name?: string): Promise { + return (await this.#demux.call('exists', schemaName, name)) as boolean; + } + + toDocValueMap( + schemaName: string, + rawValueMap: RawValueMap | RawValueMap[] + ): DocValueMap | DocValueMap[] {} + toRawValueMap( + schemaName: string, + docValueMap: DocValueMap | DocValueMap[] + ): RawValueMap | RawValueMap[] {} } diff --git a/frappe/core/frappe.ts b/frappe/core/frappe.ts index 0a20febf..8eb51342 100644 --- a/frappe/core/frappe.ts +++ b/frappe/core/frappe.ts @@ -12,7 +12,7 @@ import * as errors from '../utils/errors'; import { format } from '../utils/format'; import { t, T } from '../utils/translation'; import { AuthHandler } from './authHandler'; -import { DbHandler } from './dbHandler'; +import { DatabaseHandler } from './dbHandler'; import { DocHandler } from './docHandler'; export class Frappe { @@ -28,7 +28,7 @@ export class Frappe { auth: AuthHandler; doc: DocHandler; - db: DbHandler; + db: DatabaseHandler; Meta?: typeof Meta; Document?: typeof Doc; @@ -42,7 +42,7 @@ export class Frappe { constructor() { this.auth = new AuthHandler(this); this.doc = new DocHandler(this); - this.db = new DbHandler(this); + this.db = new DatabaseHandler(this); this.pesa = getMoneyMaker({ currency: 'XXX', precision: DEFAULT_INTERNAL_PRECISION, diff --git a/frappe/core/types.ts b/frappe/core/types.ts new file mode 100644 index 00000000..042f916d --- /dev/null +++ b/frappe/core/types.ts @@ -0,0 +1,7 @@ +import Money from 'pesa/dist/types/src/money'; +import { RawValue } from 'schemas/types'; + +export type DocValue = string | number | boolean | Date | Money; + +export type DocValueMap = Record; +export type RawValueMap = Record; diff --git a/main/registerIpcMainActionListeners.ts b/main/registerIpcMainActionListeners.ts index 4b06857c..dd7387ba 100644 --- a/main/registerIpcMainActionListeners.ts +++ b/main/registerIpcMainActionListeners.ts @@ -8,6 +8,7 @@ import { getUrlAndTokenString, sendError } from '../src/contactMothership'; import { getLanguageMap } from '../src/getLanguageMap'; import saveHtmlAsPdf from '../src/saveHtmlAsPdf'; import { DatabaseMethod } from '../utils/db/types'; +import { DatabaseResponse } from '../utils/ipc/types'; import { IPC_ACTIONS } from '../utils/messages'; import { getMainWindowSize } from './helpers'; @@ -126,21 +127,48 @@ export default function registerIpcMainActionListeners(main: Main) { ipcMain.handle( IPC_ACTIONS.DB_CREATE, async (_, dbPath: string, countryCode?: string) => { - return databaseManager.createNewDatabase(dbPath, countryCode); + const response: DatabaseResponse = { error: '', data: undefined }; + try { + response.data = await databaseManager.createNewDatabase( + dbPath, + countryCode + ); + } catch (error) { + response.error = error.toString(); + } + + return response; } ); ipcMain.handle( IPC_ACTIONS.DB_CONNECT, async (_, dbPath: string, countryCode?: string) => { - return databaseManager.createNewDatabase(dbPath, countryCode); + const response: DatabaseResponse = { error: '', data: undefined }; + try { + response.data = await databaseManager.createNewDatabase( + dbPath, + countryCode + ); + } catch (error) { + response.error = error.toString(); + } + + return response; } ); ipcMain.handle( IPC_ACTIONS.DB_CALL, async (_, method: DatabaseMethod, ...args: unknown[]) => { - return databaseManager.call(method, ...args); + const response: DatabaseResponse = { error: '', data: undefined }; + try { + response.data = await databaseManager.call(method, ...args); + } catch (error) { + response.error = error.toString(); + } + + return response; } ); } diff --git a/schemas/types.ts b/schemas/types.ts index 2dfbf271..cbfb6f37 100644 --- a/schemas/types.ts +++ b/schemas/types.ts @@ -56,9 +56,9 @@ export enum FieldTypeEnum { } export type FieldType = keyof typeof FieldTypeEnum; -export type RawValue = string | number | boolean; +export type RawValue = string | number | boolean | null; -// @formatter:off +// prettier-ignore export interface BaseField { fieldname: string; // Column name in the db fieldtype: FieldType; // UI Descriptive field types that map to column types diff --git a/src/demux/db.ts b/src/demux/db.ts new file mode 100644 index 00000000..99119110 --- /dev/null +++ b/src/demux/db.ts @@ -0,0 +1,63 @@ +import { ipcRenderer } from 'electron'; +import { DatabaseMethod } from 'utils/db/types'; +import { IPC_ACTIONS } from 'utils/messages'; +import { DatabaseResponse } from '../../utils/ipc/types'; + +export class DatabaseDemux { + #isElectron: boolean = false; + constructor(isElectron: boolean) { + this.#isElectron = isElectron; + } + + async createNewDatabase(dbPath: string, countryCode?: string): Promise { + let response: DatabaseResponse; + if (this.#isElectron) { + response = await ipcRenderer.invoke( + IPC_ACTIONS.DB_CREATE, + dbPath, + countryCode + ); + } else { + // TODO: API Call + response = { error: '', data: undefined }; + } + + if (response.error) { + throw new Error(response.error); + } + } + + async connectToDatabase(dbPath: string, countryCode?: string): Promise { + let response: DatabaseResponse; + if (this.#isElectron) { + response = await ipcRenderer.invoke( + IPC_ACTIONS.DB_CONNECT, + dbPath, + countryCode + ); + } else { + // TODO: API Call + response = { error: '', data: undefined }; + } + + if (response.error) { + throw new Error(response.error); + } + } + + async call(method: DatabaseMethod, ...args: unknown[]): Promise { + let response: DatabaseResponse; + if (this.#isElectron) { + response = await ipcRenderer.invoke(IPC_ACTIONS.DB_CALL, method, ...args); + } else { + // TODO: API Call + response = { error: '', data: undefined }; + } + + if (response.error) { + throw new Error(response.error); + } + + return response.data; + } +} diff --git a/utils/ipc/types.ts b/utils/ipc/types.ts new file mode 100644 index 00000000..c86fc084 --- /dev/null +++ b/utils/ipc/types.ts @@ -0,0 +1,4 @@ +export interface DatabaseResponse { + error: string; + data?: unknown; +}