web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

commit 20ad7162b5043e24c0f62d822cf7e491daf9157b
parent 6ddb8f06db93f142d7b3528bc6a130e1256a5f17
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Mon, 23 Sep 2024 20:55:33 +0000

client: update sqlite classes

Diffstat:
Mclient/src/capacitor/index.ts | 10++++++++--
Dclient/src/capacitor/sql.ts | 533-------------------------------------------------------------------------------
Aclient/src/capacitor/sqlite.ts | 341+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclient/src/capacitor/sqlite_lib.ts | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mclient/src/types.ts | 2+-
5 files changed, 472 insertions(+), 536 deletions(-)

diff --git a/client/src/capacitor/index.ts b/client/src/capacitor/index.ts @@ -17,7 +17,7 @@ import { CapacitorClientNetwork } from "./network"; import { CapacitorClientPreferences } from "./preferences"; import { CapacitorClientSettings } from "./settings"; import { CapacitorClientShare } from "./share"; -import { CapacitorClientSQLite } from "./sql"; +import { CapacitorClientSQLite, type ICapacitorClientSQLiteUpgrade } from "./sqlite"; import { CapacitorClientWifi } from "./wifi"; import { CapacitorClientWindow } from "./window"; @@ -39,8 +39,14 @@ export class ClientCapacitor implements IClient { private _window: IClientWindow = new CapacitorClientWindow(); private _ble: IClientBluetoothLe = new CapacitorClientBluetoothLe(); private _camera: IClientCamera = new CapacitorClientCamera(); - private _db: CapacitorClientSQLite = new CapacitorClientSQLite(); private _settings: CapacitorClientSettings = new CapacitorClientSettings(); + private _db: CapacitorClientSQLite; + + constructor(opts: { + sqlite_upgrade: ICapacitorClientSQLiteUpgrade[]; + }) { + this._db = new CapacitorClientSQLite(opts.sqlite_upgrade); + } public get nostr() { return this._nostr; diff --git a/client/src/capacitor/sql.ts b/client/src/capacitor/sql.ts @@ -1,532 +0,0 @@ -import { CapacitorSQLite, SQLiteConnection, SQLiteDBConnection, type DBSQLiteValues, type capSQLiteChanges, type capSQLiteUpgradeOptions, type capSQLiteVersionUpgrade } from '@radroots/capacitor-sqlite'; -import { Capacitor } from '@capacitor/core'; -import { time_created_on, uuidv4, err_msg } from '@radroots/utils'; -import { type IModelsQueryParam, type IModelsQueryBindValue, type IModelsQueryBindValueTuple, type IModelsQueryBindValueOpt, models_initial_upgrade, parse_location_gcs_form_field_types, location_gcs_sort, type ILocationGcsGetList, type ILocationGcsGet, type ILocationGcsUpdate, type ILocationGcsQueryBindValues, type ILocationGcsQueryBindValuesKey, type ILocationGcsQueryBindValuesTuple, parse_location_gcs, parse_location_gcss, type LocationGcs, type LocationGcsFields, type LocationGcsFormFields, LocationGcsSchema, parse_trade_product_form_field_types, trade_product_sort, type ITradeProductGetList, type ITradeProductGet, type ITradeProductUpdate, type ITradeProductQueryBindValues, type ITradeProductQueryBindValuesKey, type ITradeProductQueryBindValuesTuple, parse_trade_product, parse_trade_products, type TradeProduct, type TradeProductFields, type TradeProductFormFields, TradeProductSchema} from "@radroots/models"; - -const models_upgrades = [ - { - toVersion: 1, - statements: [ - ...models_initial_upgrade - ] - } -]; - -export type IISQLiteServiceDatabaseLog = { key: string, bind_values: IModelsQueryBindValueOpt[], query: string, e: any }; -export type IISQLiteServiceOpenDatabase = { - platform: string; - database: string; - upgrade: capSQLiteVersionUpgrade[]; - version: number; -}; - -export type IISQLiteServiceMessage = - | "*-location-gcs-geohash-unique" - | "*-validate" - | "*-result" - | "*-fields" - | "*-open" - | "*-connect" - | "*-db" - | "*-exe-result" - | "*-exe" - | "*-sel-result" - | "*-sel" - | "*"; - -class SQLiteVersionService { - version_map: Map<string, number> = new Map(); - - set_version(db_name: string, version: number) { - this.version_map.set(db_name, version); - }; - get_version(db_name: string): number | undefined { - const version = this.version_map.get(db_name); - return version; - }; -}; -const sqlite_version_svc = new SQLiteVersionService(); - -export class SQLiteService { - private _platform = Capacitor.getPlatform(); - private _plugin = CapacitorSQLite; - private _conn = new SQLiteConnection(CapacitorSQLite); - private _db_version_dict: Map<string, number> = new Map(); - private _logs: IISQLiteServiceDatabaseLog[] = []; - - public get logs() { - return this._logs; - } - - public get platform() { - return this._platform; - } - public async init_web_store(): Promise<void> { - try { - await this._conn.initWebStore(); - } catch (e) { - const { error } = err_msg(e); - throw new Error(`SQLiteService.init_web_store: ${error}`); - } - } - public async add_upgrade(options: capSQLiteUpgradeOptions): Promise<void> { - try { - await this._plugin.addUpgradeStatement(options); - } catch (e) { - const { error } = err_msg(e); - throw new Error(`SQLiteService.add_upgrade: ${error}`); - } - } - public async open_db(db_name: string, loadToVersion: number, read_only: boolean, encryption_passphrase?: string): Promise<SQLiteDBConnection> { - this._db_version_dict.set(db_name, loadToVersion); - const mode = encryption_passphrase ? "secret" : "no-encryption"; - try { - let db: SQLiteDBConnection; - const retCC = (await this._conn.checkConnectionsConsistency()).result; - const isConn = (await this._conn.isConnection(db_name, read_only)).result; - if (retCC && isConn) { - db = await this._conn.retrieveConnection(db_name, read_only); - } else { - db = await this._conn - .createConnection(db_name, !!encryption_passphrase, mode, loadToVersion, read_only); - }; - await db.open(); - const res = (await db.isDBOpen()).result!; - if (!res) { - throw new Error('SQLiteService.open_db: database not opened') - } - return db; - } catch (e) { - const { error } = err_msg(e); - throw new Error(`SQLiteService.open_db: ${error}`); - } - } - public async close_db(db_name: string, read_only: boolean): Promise<void> { - try { - const isConn = (await this._conn.isConnection(db_name, read_only)).result; - if (isConn) { - await this._conn.closeConnection(db_name, read_only); - } - } catch (e) { - const { error } = err_msg(e); - throw new Error(`SQLiteService.close_db: ${error}`); - } - } - public async save_to_store(db_name: string): Promise<void> { - try { - await this._conn.saveToStore(db_name); - } catch (e) { - const { error } = err_msg(e); - throw new Error(`SQLiteService.save_to_store: ${error}`); - } - } - public async save_to_disk(db_name: string): Promise<void> { - try { - await this._conn.saveToLocalDisk(db_name); - return; - } catch (e) { - const { error } = err_msg(e); - throw new Error(`SQLiteService.save_to_disk: ${error}`); - } - } - public async is_connected(db_name: string, read_only: boolean): Promise<boolean> { - try { - const is_connected = (await this._conn.isConnection(db_name, read_only)).result; - if (is_connected !== undefined) { - return is_connected; - } else { - throw new Error(`SQLiteService.is_connected undefined`); - } - - } catch (e) { - const { error } = err_msg(e); - throw new Error(`SQLiteService.is_connected: ${error}`); - } - } -} -const sqlite_svc = new SQLiteService(); - -export class CapacitorClientSQLite { - private _platform = sqlite_svc.platform; - private _db_name: string | null = null; - private _db: SQLiteDBConnection | null = null; - private _upgrade = models_upgrades; - private _version = models_upgrades[models_upgrades.length - 1].toVersion; - - public get logs() { - return sqlite_svc.logs; - } - - // - // private - private append_logs(log_key: IISQLiteServiceMessage, bind_values: any, query: string, e: any): IISQLiteServiceMessage { - sqlite_svc.logs.push({ - key: "database-" + log_key, - bind_values, - query, - e, - }) - return log_key; - } - - private filter_bind_value_fields(fields: IModelsQueryBindValueTuple[]): IModelsQueryBindValueTuple[] { - return fields.filter(([_, v]) => !!v); - } - - private async execute(query: string, bv_o?: IModelsQueryBindValueOpt): Promise<capSQLiteChanges | IISQLiteServiceMessage> { - try { - if (!this._db) return "*-db"; - const result = await this._db.run(query, bv_o ? bv_o : undefined); - if (sqlite_svc.platform === "web" && this._db_name) await sqlite_svc.save_to_store(this._db_name); - if (result) return result; - return this.append_logs("*-exe-result", bv_o, query, result); - } catch (e) { - const { error } = err_msg(e, "execute"); - if (String(e).includes("UNIQUE constraint failed: location_gcs.geohash")) return "*-location-gcs-geohash-unique"; - return this.append_logs("*-exe", bv_o, query, error); - }; - }; - - private async select(query: string, bv_o?: IModelsQueryBindValueOpt): Promise<DBSQLiteValues | IISQLiteServiceMessage> { - try { - if (!this._db) return "*-db"; - const result = await this._db.query(query, bv_o ? bv_o : undefined); - if (result) return result; - return this.append_logs("*-sel-result", bv_o, query, result); - } catch (e) { - const { error } = err_msg(e, "select"); - return this.append_logs("*-sel", bv_o, query, error); - }; - }; - - private async open(opts: IISQLiteServiceOpenDatabase): Promise<undefined | IISQLiteServiceMessage> { - try { - if (this._platform === "web") await sqlite_svc.init_web_store(); - await sqlite_svc.add_upgrade({ - database: opts.database, - upgrade: opts.upgrade - }); - const db = await sqlite_svc.open_db(opts.database, opts.version, false); - sqlite_version_svc.set_version(opts.database, opts.version); - if (!db) return "*-db"; - if (opts.platform === "web") await sqlite_svc.save_to_store(opts.database); - this._db = db; - this._db_name = opts.database; - return this.append_logs("*-open", [], "database opened", [new Date().toISOString()]); - } catch (e) { - const { error } = err_msg(e, "open"); - return this.append_logs("*-open", [], "catch", error); - }; - } - - public async connect(database: string): Promise<true | IISQLiteServiceMessage> { - try { - this._db_name = database; - await this.open({ - platform: this._platform, - database, - upgrade: this._upgrade, - version: this._version, - }).then(async () => { - if (this._platform === "web") await sqlite_svc.save_to_store(database); - }); - return true; - } catch (e) { - const { error } = err_msg(e, "connect"); - return this.append_logs("*-connect", [], "catch", error); - }; - } - - private location_gcs_add_validate(opts: LocationGcsFormFields): LocationGcsFields | string[] { - const opts_filtered = Object.entries(opts).reduce((acc: Record<string, (string | number)>, [key, value]) => { - if (!!value) { - switch (parse_location_gcs_form_field_types(key)) { - case "string": - acc[key] = value; - break; - case "number": - acc[key] = Number(value); - break; - } - }; - return acc; - }, {}); - const location_gcs_v = LocationGcsSchema.safeParse(opts_filtered); - if (!location_gcs_v.success) return location_gcs_v.error.issues.map(i => i.message); - else return { - ...location_gcs_v.data, - }; - }; - - public async location_gcs_add(opts: LocationGcsFormFields): Promise<{ id: string; } | string[] | IISQLiteServiceMessage> { - const optsv = this.location_gcs_add_validate(opts); - if (Array.isArray(optsv)) return optsv; - const fields = Object.entries(optsv); - if (!fields.length) return "*-fields"; - const id = uuidv4(); - const bind_values_tup: IModelsQueryBindValueTuple[] = [ - ["id", id], - ["created_at", time_created_on()] - ]; - for (const field of this.filter_bind_value_fields(fields)) bind_values_tup.push(field); - const bind_values = bind_values_tup.map(([_, v]) => v); - const query = `INSERT INTO location_gcs (${bind_values_tup.map(([k]) => k).join(", ")}) VALUES (${bind_values_tup.map((_, num) => `$${1 + num}`).join(", ")});`; - try { - const result = await this.execute(query, bind_values); - if (typeof result !== "string" && typeof result.changes?.changes === "number" && result.changes.changes > 0) return { id }; - else if (typeof result === "string") return result; - return "*-result"; - } catch (e) { - return this.append_logs("*", bind_values, query, ["location_gcs_add", e]); - }; - }; - - private location_gcs_query_bind_values = (opts: ILocationGcsQueryBindValues): ILocationGcsQueryBindValuesTuple => { - if ("id" in opts) return ["id", opts.id]; - else return ["geohash", opts.geohash]; - }; - - private location_gcs_get_query_list = (opts: ILocationGcsGetList): IModelsQueryParam => { - const sort = location_gcs_sort[opts.sort || "newest"]; - let query = ""; - let bind_values = null; - if (opts.list[0] === "all") { - query = `SELECT * FROM location_gcs ORDER BY ${sort};`; - } - if (!query) throw new Error("Error: Missing query (location_gcs_get_query_list)") - return { - query, - bind_values - }; - }; - - private location_gcs_get_parse_opts = (opts: ILocationGcsGet): IModelsQueryParam => { - if ("list" in opts) return this.location_gcs_get_query_list(opts); - else { - const bv_tup = this.location_gcs_query_bind_values(opts); - return { - query: `SELECT * FROM location_gcs WHERE ${bv_tup[0]} = $1;`, - bind_values: [bv_tup[1]] - }; - }; - }; - - public async location_gcs_get(opts: ILocationGcsGet): Promise<LocationGcs[] | IISQLiteServiceMessage> { - const { query, bind_values } = this.location_gcs_get_parse_opts(opts); - try { - const response = await this.select(query, bind_values); - if (typeof response === "string") return response; - else { - const result = parse_location_gcss(response); - if (result) return result; - } - return "*-result"; - } catch (e) { - return this.append_logs("*", opts, query, ["location_gcs_get", e]); - }; - }; - - public async location_gcs_delete(opts: ILocationGcsQueryBindValues): Promise<true | IISQLiteServiceMessage> { - const bv_tup = this.location_gcs_query_bind_values(opts); - const bind_values = [bv_tup[1]]; - const query = `DELETE FROM location_gcs WHERE ${bv_tup[0]} = $1;`; - try { - const response = await this.execute(query, bind_values); - if (typeof response === "string") return response; - else if (typeof response.changes?.changes === "number" && response.changes.changes > 0) return true; - return "*-result"; - } catch (e) { - return this.append_logs("*", [], query, ["location_gcs_delete", e]); - }; - }; - - public async location_gcs_update(opts: ILocationGcsUpdate): Promise<true | string[] | IISQLiteServiceMessage> { - const optsv = this.location_gcs_add_validate(opts.fields); - if (Array.isArray(optsv)) return optsv; - const fields = this.filter_bind_value_fields(Object.entries(optsv)); - if (!fields.length) return "*-fields"; - const bv_tup = this.location_gcs_query_bind_values(opts.on); - const bind_values = [bv_tup[1], ...fields.map(([_, v]) => v)]; - const query = `UPDATE location_gcs SET ${fields.map(([k], num) => `${k} = $${1 + num}`).join(", ")} WHERE ${bv_tup[0]} = $1;`; - try { - const response = await this.execute(query, bind_values); - if (typeof response === "string") return response; - else if (typeof response.changes?.changes === "number" && response.changes.changes > 0) return true; - return "*-result"; - } catch (e) { - return this.append_logs("*", [], query, ["location_gcs_update", e]); - }; - }; - - private trade_product_add_validate(opts: TradeProductFormFields): TradeProductFields | string[] { - const opts_filtered = Object.entries(opts).reduce((acc: Record<string, (string | number)>, [key, value]) => { - if (!!value) { - switch (parse_trade_product_form_field_types(key)) { - case "string": - acc[key] = value; - break; - case "number": - acc[key] = Number(value); - break; - } - }; - return acc; - }, {}); - const trade_product_v = TradeProductSchema.safeParse(opts_filtered); - if (!trade_product_v.success) return trade_product_v.error.issues.map(i => i.message); - else return { - ...trade_product_v.data, - }; - }; - - public async trade_product_add(opts: TradeProductFormFields): Promise<{ id: string; } | string[] | IISQLiteServiceMessage> { - const optsv = this.trade_product_add_validate(opts); - if (Array.isArray(optsv)) return optsv; - const fields = Object.entries(optsv); - if (!fields.length) return "*-fields"; - const id = uuidv4(); - const bind_values_tup: IModelsQueryBindValueTuple[] = [ - ["id", id], - ["created_at", time_created_on()] - ]; - for (const field of this.filter_bind_value_fields(fields)) bind_values_tup.push(field); - const bind_values = bind_values_tup.map(([_, v]) => v); - const query = `INSERT INTO trade_product (${bind_values_tup.map(([k]) => k).join(", ")}) VALUES (${bind_values_tup.map((_, num) => `$${1 + num}`).join(", ")});`; - try { - const result = await this.execute(query, bind_values); - if (typeof result !== "string" && typeof result.changes?.changes === "number" && result.changes.changes > 0) return { id }; - else if (typeof result === "string") return result; - return "*-result"; - } catch (e) { - return this.append_logs("*", bind_values, query, ["trade_product_add", e]); - }; - }; - - private trade_product_query_bind_values = (opts: ITradeProductQueryBindValues): ITradeProductQueryBindValuesTuple => { - if ("id" in opts) return ["id", opts.id]; - else return ["url", opts.url]; - }; - - private trade_product_get_query_list = (opts: ITradeProductGetList): IModelsQueryParam => { - const sort = trade_product_sort[opts.sort || "newest"]; - let query = ""; - let bind_values = null; - if (opts.list[0] === "all") { - query = `SELECT * FROM trade_product ORDER BY ${sort};`; - } - if (!query) throw new Error("Error: Missing query (trade_product_get_query_list)") - return { - query, - bind_values - }; - }; - - private trade_product_get_parse_opts = (opts: ITradeProductGet): IModelsQueryParam => { - if ("list" in opts) return this.trade_product_get_query_list(opts); - else { - const bv_tup = this.trade_product_query_bind_values(opts); - return { - query: `SELECT * FROM trade_product WHERE ${bv_tup[0]} = $1;`, - bind_values: [bv_tup[1]] - }; - }; - }; - - public async trade_product_get(opts: ITradeProductGet): Promise<TradeProduct[] | IISQLiteServiceMessage> { - const { query, bind_values } = this.trade_product_get_parse_opts(opts); - try { - const response = await this.select(query, bind_values); - if (typeof response === "string") return response; - else { - const result = parse_trade_products(response); - if (result) return result; - } - return "*-result"; - } catch (e) { - return this.append_logs("*", opts, query, ["trade_product_get", e]); - }; - }; - - public async trade_product_delete(opts: ITradeProductQueryBindValues): Promise<true | IISQLiteServiceMessage> { - const bv_tup = this.trade_product_query_bind_values(opts); - const bind_values = [bv_tup[1]]; - const query = `DELETE FROM trade_product WHERE ${bv_tup[0]} = $1;`; - try { - const response = await this.execute(query, bind_values); - if (typeof response === "string") return response; - else if (typeof response.changes?.changes === "number" && response.changes.changes > 0) return true; - return "*-result"; - } catch (e) { - return this.append_logs("*", [], query, ["trade_product_delete", e]); - }; - }; - - public async trade_product_update(opts: ITradeProductUpdate): Promise<true | string[] | IISQLiteServiceMessage> { - const optsv = this.trade_product_add_validate(opts.fields); - if (Array.isArray(optsv)) return optsv; - const fields = this.filter_bind_value_fields(Object.entries(optsv)); - if (!fields.length) return "*-fields"; - const bv_tup = this.trade_product_query_bind_values(opts.on); - const bind_values = [bv_tup[1], ...fields.map(([_, v]) => v)]; - const query = `UPDATE trade_product SET ${fields.map(([k], num) => `${k} = $${1 + num}`).join(", ")} WHERE ${bv_tup[0]} = $1;`; - try { - const response = await this.execute(query, bind_values); - if (typeof response === "string") return response; - else if (typeof response.changes?.changes === "number" && response.changes.changes > 0) return true; - return "*-result"; - } catch (e) { - return this.append_logs("*", [], query, ["trade_product_update", e]); - }; - }; - - public async get_trade_product_location(): Promise<any[] | IISQLiteServiceMessage> { - const bind_values = undefined; - const query = "SELECT * FROM trade_product_location;"; - try { - const response = await this.select(query, bind_values); - if (typeof response === "string") { - return response; - } else if (response && Array.isArray(response.values)) { - return response.values; - } - return "*-result"; - } catch (e) { - const { error } = err_msg(e, "connect"); - return this.append_logs("*", bind_values, query, ["get_trade_product_location", error]); - }; - }; - - public async set_trade_product_location(opts: { trade_product_id: string; location_gcs_id: string }): Promise<true | IISQLiteServiceMessage> { - const bind_values = [opts.trade_product_id, opts.location_gcs_id]; - const query = "INSERT INTO trade_product_location (tb_tploc_0, tb_tploc_1) VALUES ($1, $2);"; - try { - const response = await this.execute(query, bind_values); - if (typeof response === "string") { - return response; - } else if (typeof response.changes?.changes === "number" && response.changes.changes > 0) { - return true; - } - return "*-result"; - } catch (e) { - const { error } = err_msg(e, "connect"); - return this.append_logs("*", bind_values, query, ["set_trade_product_location", error]); - }; - }; - - public async unset_trade_product_location(opts: { trade_product_id: string; location_gcs_id: string; }): Promise<true | IISQLiteServiceMessage> { - const bind_values = [opts.trade_product_id, opts.location_gcs_id]; - const query = "DELETE FROM trade_product_location WHERE tb_tploc_0 = $1 AND tb_tploc_1 = $2;"; - try { - const response = await this.execute(query, bind_values); - if (typeof response === "string") { - return response; - } else if (typeof response.changes?.changes === "number" && response.changes.changes > 0) { - return true; - } - return "*-result"; - } catch (e) { - const { error } = err_msg(e, "connect"); - return this.append_logs("*", bind_values, query, ["unset_trade_product_location", error]); - }; - }; -}; -\ No newline at end of file diff --git a/client/src/capacitor/sqlite.ts b/client/src/capacitor/sqlite.ts @@ -0,0 +1,340 @@ +import { type capSQLiteChanges, type DBSQLiteValues, SQLiteDBConnection } from "@radroots/capacitor-sqlite"; +import { type IModelsQueryValue, type IModelsQueryParam, type IModelsQueryBindValue, type IModelsQueryBindValueTuple, type IModelsQueryBindValueOpt, parse_location_gcs_form_fields, location_gcs_sort, type ILocationGcsGetList, type ILocationGcsGet, type ILocationGcsUpdate, type ILocationGcsQueryBindValues, type ILocationGcsQueryBindValuesKey, type ILocationGcsQueryBindValuesTuple, parse_location_gcs, parse_location_gcs_list, type LocationGcs, type LocationGcsFields, type LocationGcsFormFields, LocationGcsSchema, parse_trade_product_form_fields, trade_product_sort, type ITradeProductGetList, type ITradeProductGet, type ITradeProductUpdate, type ITradeProductQueryBindValues, type ITradeProductQueryBindValuesKey, type ITradeProductQueryBindValuesTuple, parse_trade_product, parse_trade_product_list, type TradeProduct, type TradeProductFields, type TradeProductFormFields, TradeProductSchema} from "@radroots/models"; +import { err_msg, time_created_on, uuidv4 } from "@radroots/utils"; +import { sqlite_svc, sqlite_version_svc, type IISQLiteServiceOpenDatabase } from "./sqlite_lib"; + +export type ICapacitorClientSQLiteUpgrade = { toVersion: number; statements: string[]; }; +export type ICapacitorClientSQLiteMessage = + | "*-location-gcs-geohash-unique" + | "*-validate" + | "*-result" + | "*-fields" + | "*-open" + | "*-connect" + | "*-db" + | "*-exe-result" + | "*-exe" + | "*-sel-result" + | "*-sel" + | "*"; + +export class CapacitorClientSQLite { + private _platform = sqlite_svc.platform; + private _db_name: string | null = null; + private _db: SQLiteDBConnection | null = null; + private _upgrade: ICapacitorClientSQLiteUpgrade[]; + private _version: number; + + constructor(upgrade: ICapacitorClientSQLiteUpgrade[]) { + this._upgrade = upgrade; + this._version = upgrade[upgrade.length - 1].toVersion; + } + + private append_logs(log_key: ICapacitorClientSQLiteMessage, bind_values: any, query: string, e: any): ICapacitorClientSQLiteMessage { + sqlite_svc.logs.push({ + key: "database-" + log_key, + bind_values, + query, + e, + }) + return log_key; + } + + private filter_bind_value_fields(fields: IModelsQueryBindValueTuple[]): IModelsQueryBindValueTuple[] { + return fields.filter(([_, v]) => !!v); + } + + private async execute(query: string, bv_o?: IModelsQueryBindValueOpt): Promise<capSQLiteChanges | ICapacitorClientSQLiteMessage> { + try { + if (!this._db) return "*-db"; + const result = await this._db.run(query, bv_o ? bv_o : undefined); + if (sqlite_svc.platform === "web" && this._db_name) await sqlite_svc.save_to_store(this._db_name); + if (result) return result; + return this.append_logs("*-exe-result", bv_o, query, result); + } catch (e) { + const { error } = err_msg(e, "execute"); + if (String(e).includes("UNIQUE constraint failed: location_gcs.geohash")) return "*-location-gcs-geohash-unique"; + return this.append_logs("*-exe", bv_o, query, error); + }; + } + + private async select(query: string, bv_o?: IModelsQueryBindValueOpt): Promise<DBSQLiteValues | ICapacitorClientSQLiteMessage> { + try { + if (!this._db) return "*-db"; + const result = await this._db.query(query, bv_o ? bv_o : undefined); + if (result) return result; + return this.append_logs("*-sel-result", bv_o, query, result); + } catch (e) { + const { error } = err_msg(e, "select"); + return this.append_logs("*-sel", bv_o, query, error); + }; + } + + private async open(opts: IISQLiteServiceOpenDatabase): Promise<undefined | ICapacitorClientSQLiteMessage> { + try { + if (this._platform === "web") await sqlite_svc.init_web_store(); + await sqlite_svc.add_upgrade({ + database: opts.database, + upgrade: opts.upgrade + }); + const db = await sqlite_svc.open_db(opts.database, opts.version, false); + sqlite_version_svc.set_version(opts.database, opts.version); + if (!db) return "*-db"; + if (opts.platform === "web") await sqlite_svc.save_to_store(opts.database); + this._db = db; + this._db_name = opts.database; + return this.append_logs("*-open", [], "database opened", [new Date().toISOString()]); + } catch (e) { + const { error } = err_msg(e, "open"); + return this.append_logs("*-open", [], "catch", error); + }; + } + + public get logs() { + return sqlite_svc.logs; + } + + public async connect(database: string): Promise<true | ICapacitorClientSQLiteMessage> { + try { + this._db_name = database; + await this.open({ + platform: this._platform, + database, + upgrade: this._upgrade, + version: this._version, + }).then(async () => { + if (this._platform === "web") await sqlite_svc.save_to_store(database); + }); + return true; + } catch (e) { + const { error } = err_msg(e, "connect"); + return this.append_logs("*-connect", [], "catch", error); + }; + } + + private location_gcs_add_validate(fields: LocationGcsFormFields): LocationGcsFields | string[] { + const fields_record = Object.entries(fields).filter(([_, v]) => !!v).reduce((acc: Record<string, IModelsQueryValue>, i) => { + const [key, val] = parse_location_gcs_form_fields(i); + acc[key] = val; + return acc; + }, {}); + const fields_schema = LocationGcsSchema.safeParse(fields_record); + if (!fields_schema.success) return fields_schema.error.issues.map(i => i.message); + else return { + ...fields_schema.data, + }; + } + + public async location_gcs_add(opts: LocationGcsFormFields): Promise<{ id: string; } | string[] | ICapacitorClientSQLiteMessage> { + const opts_v = this.location_gcs_add_validate(opts); + if (Array.isArray(opts_v)) return opts_v; + const fields = Object.entries(opts_v); + if (!fields.length) return "*-fields"; + const id = uuidv4(); + const bind_values_tup: IModelsQueryBindValueTuple[] = [ + ["id", id], + ["created_at", time_created_on()] + ]; + for (const field of this.filter_bind_value_fields(fields)) bind_values_tup.push(field); + const bind_values = bind_values_tup.map(([_, v]) => v); + const query = `INSERT INTO location_gcs (${bind_values_tup.map(([k]) => k).join(", ")}) VALUES (${bind_values_tup.map((_, num) => `$${1 + num}`).join(", ")});`; + try { + const result = await this.execute(query, bind_values); + if (typeof result !== "string" && typeof result.changes?.changes === "number" && result.changes.changes > 0) return { id, }; + else if (typeof result === "string") return result; + return "*-result"; + } catch (e) { + return this.append_logs("*", bind_values, query, ["location_gcs_add", e]); + }; + } + + private location_gcs_query_bind_values = (opts: ILocationGcsQueryBindValues): ILocationGcsQueryBindValuesTuple => { + if ("id" in opts) return ["id", opts.id]; + else return ["geohash", opts.geohash]; + } + + private location_gcs_get_query_list = (opts: ILocationGcsGetList): IModelsQueryParam => { + const sort = location_gcs_sort[opts.sort || "newest"]; + let query = ""; + let bind_values = null; + if (opts.list[0] === "all") { + query = `SELECT * FROM location_gcs ORDER BY ${sort};`; + } + if (!query) throw new Error("Error: Missing query (location_gcs_get_query_list)") + return { + query, + bind_values + }; + } + + private location_gcs_get_parse_opts = (opts: ILocationGcsGet): IModelsQueryParam => { + if ("list" in opts) return this.location_gcs_get_query_list(opts); + else { + const bv_tup = this.location_gcs_query_bind_values(opts); + return { + query: `SELECT * FROM location_gcs WHERE ${bv_tup[0]} = $1;`, + bind_values: [bv_tup[1]] + }; + }; + } + + public async location_gcs_get(opts: ILocationGcsGet): Promise<LocationGcs[] | ICapacitorClientSQLiteMessage> { + const { query, bind_values } = this.location_gcs_get_parse_opts(opts); + try { + const response = await this.select(query, bind_values); + if (typeof response === "string") return response; + else { + const result = parse_location_gcs_list(response); + if (result) return result; + } + return "*-result"; + } catch (e) { + return this.append_logs("*", opts, query, ["location_gcs_get", e]); + }; + } + + public async location_gcs_delete(opts: ILocationGcsQueryBindValues): Promise<true | ICapacitorClientSQLiteMessage> { + const bv_tup = this.location_gcs_query_bind_values(opts); + const bind_values = [bv_tup[1]]; + const query = `DELETE FROM location_gcs WHERE ${bv_tup[0]} = $1;`; + try { + const response = await this.execute(query, bind_values); + if (typeof response === "string") return response; + else if (typeof response.changes?.changes === "number" && response.changes.changes > 0) return true; + return "*-result"; + } catch (e) { + return this.append_logs("*", [], query, ["location_gcs_delete", e]); + }; + } + + public async location_gcs_update(opts: ILocationGcsUpdate): Promise<true | string[] | ICapacitorClientSQLiteMessage> { + const opts_v = this.location_gcs_add_validate(opts.fields); + if (Array.isArray(opts_v)) return opts_v; + const fields = this.filter_bind_value_fields(Object.entries(opts_v)); + if (!fields.length) return "*-fields"; + const bv_tup = this.location_gcs_query_bind_values(opts.on); + const bind_values = [bv_tup[1], ...fields.map(([_, v]) => v)]; + const query = `UPDATE location_gcs SET ${fields.map(([k], num) => `${k} = $${1 + num}`).join(", ")} WHERE ${bv_tup[0]} = $1;`; + try { + const response = await this.execute(query, bind_values); + if (typeof response === "string") return response; + else if (typeof response.changes?.changes === "number" && response.changes.changes > 0) return true; + return "*-result"; + } catch (e) { + return this.append_logs("*", [], query, ["location_gcs_update", e]); + }; + } + + private trade_product_add_validate(fields: TradeProductFormFields): TradeProductFields | string[] { + const fields_record = Object.entries(fields).filter(([_, v]) => !!v).reduce((acc: Record<string, IModelsQueryValue>, i) => { + const [key, val] = parse_trade_product_form_fields(i); + acc[key] = val; + return acc; + }, {}); + const fields_schema = TradeProductSchema.safeParse(fields_record); + if (!fields_schema.success) return fields_schema.error.issues.map(i => i.message); + else return { + ...fields_schema.data, + }; + } + + public async trade_product_add(opts: TradeProductFormFields): Promise<{ id: string; } | string[] | ICapacitorClientSQLiteMessage> { + const opts_v = this.trade_product_add_validate(opts); + if (Array.isArray(opts_v)) return opts_v; + const fields = Object.entries(opts_v); + if (!fields.length) return "*-fields"; + const id = uuidv4(); + const bind_values_tup: IModelsQueryBindValueTuple[] = [ + ["id", id], + ["created_at", time_created_on()] + ]; + for (const field of this.filter_bind_value_fields(fields)) bind_values_tup.push(field); + const bind_values = bind_values_tup.map(([_, v]) => v); + const query = `INSERT INTO trade_product (${bind_values_tup.map(([k]) => k).join(", ")}) VALUES (${bind_values_tup.map((_, num) => `$${1 + num}`).join(", ")});`; + try { + const result = await this.execute(query, bind_values); + if (typeof result !== "string" && typeof result.changes?.changes === "number" && result.changes.changes > 0) return { id, }; + else if (typeof result === "string") return result; + return "*-result"; + } catch (e) { + return this.append_logs("*", bind_values, query, ["trade_product_add", e]); + }; + } + + private trade_product_query_bind_values = (opts: ITradeProductQueryBindValues): ITradeProductQueryBindValuesTuple => { + if ("id" in opts) return ["id", opts.id]; + else return ["url", opts.url]; + } + + private trade_product_get_query_list = (opts: ITradeProductGetList): IModelsQueryParam => { + const sort = trade_product_sort[opts.sort || "newest"]; + let query = ""; + let bind_values = null; + if (opts.list[0] === "all") { + query = `SELECT * FROM trade_product ORDER BY ${sort};`; + } + if (!query) throw new Error("Error: Missing query (trade_product_get_query_list)") + return { + query, + bind_values + }; + } + + private trade_product_get_parse_opts = (opts: ITradeProductGet): IModelsQueryParam => { + if ("list" in opts) return this.trade_product_get_query_list(opts); + else { + const bv_tup = this.trade_product_query_bind_values(opts); + return { + query: `SELECT * FROM trade_product WHERE ${bv_tup[0]} = $1;`, + bind_values: [bv_tup[1]] + }; + }; + } + + public async trade_product_get(opts: ITradeProductGet): Promise<TradeProduct[] | ICapacitorClientSQLiteMessage> { + const { query, bind_values } = this.trade_product_get_parse_opts(opts); + try { + const response = await this.select(query, bind_values); + if (typeof response === "string") return response; + else { + const result = parse_trade_product_list(response); + if (result) return result; + } + return "*-result"; + } catch (e) { + return this.append_logs("*", opts, query, ["trade_product_get", e]); + }; + } + + public async trade_product_delete(opts: ITradeProductQueryBindValues): Promise<true | ICapacitorClientSQLiteMessage> { + const bv_tup = this.trade_product_query_bind_values(opts); + const bind_values = [bv_tup[1]]; + const query = `DELETE FROM trade_product WHERE ${bv_tup[0]} = $1;`; + try { + const response = await this.execute(query, bind_values); + if (typeof response === "string") return response; + else if (typeof response.changes?.changes === "number" && response.changes.changes > 0) return true; + return "*-result"; + } catch (e) { + return this.append_logs("*", [], query, ["trade_product_delete", e]); + }; + } + + public async trade_product_update(opts: ITradeProductUpdate): Promise<true | string[] | ICapacitorClientSQLiteMessage> { + const opts_v = this.trade_product_add_validate(opts.fields); + if (Array.isArray(opts_v)) return opts_v; + const fields = this.filter_bind_value_fields(Object.entries(opts_v)); + if (!fields.length) return "*-fields"; + const bv_tup = this.trade_product_query_bind_values(opts.on); + const bind_values = [bv_tup[1], ...fields.map(([_, v]) => v)]; + const query = `UPDATE trade_product SET ${fields.map(([k], num) => `${k} = $${1 + num}`).join(", ")} WHERE ${bv_tup[0]} = $1;`; + try { + const response = await this.execute(query, bind_values); + if (typeof response === "string") return response; + else if (typeof response.changes?.changes === "number" && response.changes.changes > 0) return true; + return "*-result"; + } catch (e) { + return this.append_logs("*", [], query, ["trade_product_update", e]); + }; + } +}; +\ No newline at end of file diff --git a/client/src/capacitor/sqlite_lib.ts b/client/src/capacitor/sqlite_lib.ts @@ -0,0 +1,121 @@ +import { Capacitor } from "@capacitor/core"; +import { CapacitorSQLite, type capSQLiteUpgradeOptions, type capSQLiteVersionUpgrade, SQLiteConnection, SQLiteDBConnection } from "@radroots/capacitor-sqlite"; +import type { IModelsQueryBindValueOpt } from "@radroots/models"; +import { err_msg } from "@radroots/utils"; + +export type ISQLiteServiceDatabaseLog = { key: string, bind_values: IModelsQueryBindValueOpt[], query: string, e: any }; +export type IISQLiteServiceOpenDatabase = { + platform: string; + database: string; + upgrade: capSQLiteVersionUpgrade[]; + version: number; +}; + +export class CapacitorClientSQLiteVersionService { + version_map: Map<string, number> = new Map(); + + set_version(db_name: string, version: number) { + this.version_map.set(db_name, version); + } + + get_version(db_name: string): number | undefined { + const version = this.version_map.get(db_name); + return version; + } +}; + +export class CapacitorClientSQLiteService { + private _platform = Capacitor.getPlatform(); + private _plugin = CapacitorSQLite; + private _conn = new SQLiteConnection(CapacitorSQLite); + private _db_version_dict: Map<string, number> = new Map(); + private _logs: ISQLiteServiceDatabaseLog[] = []; + + public get logs() { + return this._logs; + } + + public get platform() { + return this._platform; + } + + public async init_web_store(): Promise<void> { + try { + await this._conn.initWebStore(); + } catch (e) { + const { error } = err_msg(e); + throw new Error(`Error: CapacitorClientSQLiteService init_web_store: ${error}`); + } + } + + public async add_upgrade(options: capSQLiteUpgradeOptions): Promise<void> { + try { + await this._plugin.addUpgradeStatement(options); + } catch (e) { + const { error } = err_msg(e); + throw new Error(`Error: CapacitorClientSQLiteService add_upgrade: ${error}`); + } + } + + public async open_db(db_name: string, loadToVersion: number, read_only: boolean, encryption_passphrase?: string): Promise<SQLiteDBConnection> { + this._db_version_dict.set(db_name, loadToVersion); + const mode = encryption_passphrase ? "secret" : "no-encryption"; + try { + let db: SQLiteDBConnection; + const ret_cc = (await this._conn.checkConnectionsConsistency()).result; + const is_conn = (await this._conn.isConnection(db_name, read_only)).result; + if (ret_cc && is_conn) db = await this._conn.retrieveConnection(db_name, read_only); + else db = await this._conn + .createConnection(db_name, !!encryption_passphrase, mode, loadToVersion, read_only); + await db.open(); + const res = (await db.isDBOpen()).result!; + if (!res) throw new Error('Error: CapacitorClientSQLiteService open_db: database not opened') + return db; + } catch (e) { + const { error } = err_msg(e); + throw new Error(`Error: CapacitorClientSQLiteService open_db: ${error}`); + } + } + + public async close_db(db_name: string, read_only: boolean): Promise<void> { + try { + const is_conn = (await this._conn.isConnection(db_name, read_only)).result; + if (is_conn) await this._conn.closeConnection(db_name, read_only); + } catch (e) { + const { error } = err_msg(e); + throw new Error(`Error: CapacitorClientSQLiteService close_db: ${error}`); + } + } + + public async save_to_store(db_name: string): Promise<void> { + try { + await this._conn.saveToStore(db_name); + } catch (e) { + const { error } = err_msg(e); + throw new Error(`Error: CapacitorClientSQLiteService save_to_store: ${error}`); + } + } + + public async save_to_disk(db_name: string): Promise<void> { + try { + await this._conn.saveToLocalDisk(db_name); + } catch (e) { + const { error } = err_msg(e); + throw new Error(`Error: CapacitorClientSQLiteService save_to_disk: ${error}`); + } + } + + public async is_conn(db_name: string, read_only: boolean): Promise<boolean> { + try { + const is_conn = (await this._conn.isConnection(db_name, read_only)).result; + if (is_conn !== undefined) return is_conn; + throw new Error(`Error: CapacitorClientSQLiteService is_conn undefined`); + } catch (e) { + const { error } = err_msg(e); + throw new Error(`Error: CapacitorClientSQLiteService is_conn: ${error}`); + } + } +}; + +export const sqlite_version_svc = new CapacitorClientSQLiteVersionService(); +export const sqlite_svc = new CapacitorClientSQLiteService(); +\ No newline at end of file diff --git a/client/src/types.ts b/client/src/types.ts @@ -4,7 +4,7 @@ import { type ScanResult } from '@radroots/capacitor-bluetooth-le'; import { IOSSettings, type AndroidSettings } from '@radroots/capacitor-native-settings'; import { type ConnectToWifiResult, type GetCurrentWifiResult, type PermissionStatus, type ScanWifiResult } from '@radroots/capacitor-wifi'; import { type ErrorResponse } from '@radroots/utils'; -import { CapacitorClientSQLite } from './capacitor/sql'; +import { CapacitorClientSQLite } from './capacitor/sqlite'; export type IClient = { nostr: IClientNostr;