Browse Source

fix linting

sql-absurd-sql
Trent Larson 2 weeks ago
parent
commit
5f24f4975d
  1. 2
      src/components/DataExportSection.vue
  2. 14
      src/db-sql/migration.ts
  3. 22
      src/db/index.ts
  4. 5
      src/interfaces/database.ts
  5. 10
      src/libs/util.ts
  6. 11
      src/main.web.ts
  7. 2
      src/registerSQLWorker.js
  8. 12
      src/services/database.d.ts
  9. 65
      src/services/database.ts
  10. 18
      src/services/migrationService.ts
  11. 10
      src/views/AccountViewView.vue
  12. 10
      src/views/ContactQRScanShowView.vue
  13. 10
      src/views/NewIdentifierView.vue
  14. 14
      src/views/TestView.vue

2
src/components/DataExportSection.vue

@ -136,7 +136,7 @@ export default class DataExportSection extends Vue {
transform: (table, value, key) => { transform: (table, value, key) => {
if (table === "contacts") { if (table === "contacts") {
// Dexie inserts a number 0 when some are undefined, so we need to totally remove them. // Dexie inserts a number 0 when some are undefined, so we need to totally remove them.
Object.keys(value).forEach(prop => { Object.keys(value).forEach((prop) => {
if (value[prop] === undefined) { if (value[prop] === undefined) {
delete value[prop]; delete value[prop];
} }

14
src/db-sql/migration.ts

@ -1,10 +1,10 @@
import migrationService from '../services/migrationService'; import migrationService from "../services/migrationService";
import type { QueryExecResult } from '../services/migrationService'; import type { QueryExecResult } from "../services/migrationService";
// Each migration can include multiple SQL statements (with semicolons) // Each migration can include multiple SQL statements (with semicolons)
const MIGRATIONS = [ const MIGRATIONS = [
{ {
name: '001_initial', name: "001_initial",
// see ../db/tables files for explanations of the fields // see ../db/tables files for explanations of the fields
sql: ` sql: `
CREATE TABLE IF NOT EXISTS accounts ( CREATE TABLE IF NOT EXISTS accounts (
@ -84,8 +84,8 @@ const MIGRATIONS = [
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
blobB64 TEXT blobB64 TEXT
); );
` `,
} },
]; ];
export async function registerMigrations(): Promise<void> { export async function registerMigrations(): Promise<void> {
@ -96,8 +96,8 @@ export async function registerMigrations(): Promise<void> {
} }
export async function runMigrations( export async function runMigrations(
sqlExec: (sql: string, params?: any[]) => Promise<Array<QueryExecResult>> sqlExec: (sql: string, params?: any[]) => Promise<Array<QueryExecResult>>,
): Promise<void> { ): Promise<void> {
await registerMigrations(); await registerMigrations();
await migrationService.runMigrations(sqlExec); await migrationService.runMigrations(sqlExec);
} }

22
src/db/index.ts

@ -90,7 +90,10 @@ db.on("populate", async () => {
try { try {
await db.settings.add(DEFAULT_SETTINGS); await db.settings.add(DEFAULT_SETTINGS);
} catch (error) { } catch (error) {
console.error("Error populating the database with default settings:", error); console.error(
"Error populating the database with default settings:",
error,
);
} }
}); });
@ -105,7 +108,7 @@ async function safeOpenDatabase(retries = 1, delay = 500): Promise<void> {
// Create a promise that rejects after 5 seconds // Create a promise that rejects after 5 seconds
const timeoutPromise = new Promise((_, reject) => { const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Database open timed out')), 500); setTimeout(() => reject(new Error("Database open timed out")), 500);
}); });
// Race between the open operation and the timeout // Race between the open operation and the timeout
@ -123,7 +126,7 @@ async function safeOpenDatabase(retries = 1, delay = 500): Promise<void> {
console.error(`Attempt ${i + 1}: Database open failed:`, error); console.error(`Attempt ${i + 1}: Database open failed:`, error);
if (i < retries - 1) { if (i < retries - 1) {
console.log(`Attempt ${i + 1}: Waiting ${delay}ms before retry...`); console.log(`Attempt ${i + 1}: Waiting ${delay}ms before retry...`);
await new Promise(resolve => setTimeout(resolve, delay)); await new Promise((resolve) => setTimeout(resolve, delay));
} else { } else {
throw error; throw error;
} }
@ -145,16 +148,23 @@ export async function updateDefaultSettings(
await safeOpenDatabase(); await safeOpenDatabase();
} catch (openError: unknown) { } catch (openError: unknown) {
console.error("Failed to open database:", openError, String(openError)); console.error("Failed to open database:", openError, String(openError));
throw new Error(`The database connection failed. We recommend you try again or restart the app.`); throw new Error(
`The database connection failed. We recommend you try again or restart the app.`,
);
} }
const result = await db.settings.update(MASTER_SETTINGS_KEY, settingsChanges); const result = await db.settings.update(
MASTER_SETTINGS_KEY,
settingsChanges,
);
return result; return result;
} catch (error) { } catch (error) {
console.error("Error updating default settings:", error); console.error("Error updating default settings:", error);
if (error instanceof Error) { if (error instanceof Error) {
throw error; // Re-throw if it's already an Error with a message throw error; // Re-throw if it's already an Error with a message
} else { } else {
throw new Error(`Failed to update settings. We recommend you try again or restart the app.`); throw new Error(
`Failed to update settings. We recommend you try again or restart the app.`,
);
} }
} }
} }

5
src/interfaces/database.ts

@ -8,7 +8,10 @@ export interface QueryExecResult {
export interface DatabaseService { export interface DatabaseService {
initialize(): Promise<void>; initialize(): Promise<void>;
query(sql: string, params?: any[]): Promise<QueryExecResult[]>; query(sql: string, params?: any[]): Promise<QueryExecResult[]>;
run(sql: string, params?: any[]): Promise<{ changes: number; lastId?: number }>; run(
sql: string,
params?: any[],
): Promise<{ changes: number; lastId?: number }>;
getOneRow(sql: string, params?: any[]): Promise<any[] | undefined>; getOneRow(sql: string, params?: any[]): Promise<any[] | undefined>;
getAll(sql: string, params?: any[]): Promise<any[][]>; getAll(sql: string, params?: any[]): Promise<any[][]>;
} }

10
src/libs/util.ts

@ -561,14 +561,16 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
newId.did, newId.did,
identity, identity,
mnemonic, mnemonic,
newId.keys[0].publicKeyHex newId.keys[0].publicKeyHex,
] ],
); );
await updateDefaultSettings({ activeDid: newId.did }); await updateDefaultSettings({ activeDid: newId.did });
} catch (error) { } catch (error) {
console.error("Failed to update default settings:", error); console.error("Failed to update default settings:", error);
throw new Error("Failed to set default settings. Please try again or restart the app."); throw new Error(
"Failed to set default settings. Please try again or restart the app.",
);
} }
await updateAccountSettings(newId.did, { isRegistered: false }); await updateAccountSettings(newId.did, { isRegistered: false });
return newId.did; return newId.did;

11
src/main.web.ts

@ -1,4 +1,4 @@
import { initBackend } from 'absurd-sql/dist/indexeddb-main-thread'; import { initBackend } from "absurd-sql/dist/indexeddb-main-thread";
import { initializeApp } from "./main.common"; import { initializeApp } from "./main.common";
import "./registerServiceWorker"; // Web PWA support import "./registerServiceWorker"; // Web PWA support
@ -6,9 +6,12 @@ const app = initializeApp();
function sqlInit() { function sqlInit() {
// see https://github.com/jlongster/absurd-sql // see https://github.com/jlongster/absurd-sql
let worker = new Worker(new URL('./registerSQLWorker.js', import.meta.url), { const worker = new Worker(
type: 'module' new URL("./registerSQLWorker.js", import.meta.url),
}); {
type: "module",
},
);
// This is only required because Safari doesn't support nested // This is only required because Safari doesn't support nested
// workers. This installs a handler that will proxy creating web // workers. This installs a handler that will proxy creating web
// workers through the main thread // workers through the main thread

2
src/registerSQLWorker.js

@ -1,4 +1,4 @@
import databaseService from './services/database'; import databaseService from "./services/database";
async function run() { async function run() {
await databaseService.initialize(); await databaseService.initialize();

12
src/services/database.d.ts

@ -1,23 +1,25 @@
import { DatabaseService } from '../interfaces/database'; import { DatabaseService } from "../interfaces/database";
declare module '@jlongster/sql.js' { declare module "@jlongster/sql.js" {
interface SQL { interface SQL {
Database: any; Database: any;
FS: any; FS: any;
register_for_idb: (fs: any) => void; register_for_idb: (fs: any) => void;
} }
function initSqlJs(config: { locateFile: (file: string) => string }): Promise<SQL>; function initSqlJs(config: {
locateFile: (file: string) => string;
}): Promise<SQL>;
export default initSqlJs; export default initSqlJs;
} }
declare module 'absurd-sql' { declare module "absurd-sql" {
export class SQLiteFS { export class SQLiteFS {
constructor(fs: any, backend: any); constructor(fs: any, backend: any);
} }
} }
declare module 'absurd-sql/dist/indexeddb-backend' { declare module "absurd-sql/dist/indexeddb-backend" {
export default class IndexedDBBackend { export default class IndexedDBBackend {
constructor(); constructor();
} }

65
src/services/database.ts

@ -1,18 +1,21 @@
// Add type declarations for external modules // Add type declarations for external modules
declare module '@jlongster/sql.js'; declare module "@jlongster/sql.js";
declare module 'absurd-sql'; declare module "absurd-sql";
declare module 'absurd-sql/dist/indexeddb-backend'; declare module "absurd-sql/dist/indexeddb-backend";
import initSqlJs from '@jlongster/sql.js'; import initSqlJs from "@jlongster/sql.js";
import { SQLiteFS } from 'absurd-sql'; import { SQLiteFS } from "absurd-sql";
import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend'; import IndexedDBBackend from "absurd-sql/dist/indexeddb-backend";
import { runMigrations } from '../db-sql/migration'; import { runMigrations } from "../db-sql/migration";
import type { QueryExecResult } from '../interfaces/database'; import type { QueryExecResult } from "../interfaces/database";
interface SQLDatabase { interface SQLDatabase {
exec: (sql: string, params?: any[]) => Promise<QueryExecResult[]>; exec: (sql: string, params?: any[]) => Promise<QueryExecResult[]>;
run: (sql: string, params?: any[]) => Promise<{ changes: number; lastId?: number }>; run: (
sql: string,
params?: any[],
) => Promise<{ changes: number; lastId?: number }>;
} }
class DatabaseService { class DatabaseService {
@ -62,34 +65,39 @@ class DatabaseService {
const SQL = await initSqlJs({ const SQL = await initSqlJs({
locateFile: (file: string) => { locateFile: (file: string) => {
return new URL(`/node_modules/@jlongster/sql.js/dist/${file}`, import.meta.url).href; return new URL(
} `/node_modules/@jlongster/sql.js/dist/${file}`,
import.meta.url,
).href;
},
}); });
let sqlFS = new SQLiteFS(SQL.FS, new IndexedDBBackend()); const sqlFS = new SQLiteFS(SQL.FS, new IndexedDBBackend());
SQL.register_for_idb(sqlFS); SQL.register_for_idb(sqlFS);
SQL.FS.mkdir('/sql'); SQL.FS.mkdir("/sql");
SQL.FS.mount(sqlFS, {}, '/sql'); SQL.FS.mount(sqlFS, {}, "/sql");
const path = '/sql/db.sqlite'; const path = "/sql/db.sqlite";
if (typeof SharedArrayBuffer === 'undefined') { if (typeof SharedArrayBuffer === "undefined") {
let stream = SQL.FS.open(path, 'a+'); const stream = SQL.FS.open(path, "a+");
await stream.node.contents.readIfFallback(); await stream.node.contents.readIfFallback();
SQL.FS.close(stream); SQL.FS.close(stream);
} }
this.db = new SQL.Database(path, { filename: true }); this.db = new SQL.Database(path, { filename: true });
if (!this.db) { if (!this.db) {
throw new Error('The database initialization failed. We recommend you restart or reinstall.'); throw new Error(
"The database initialization failed. We recommend you restart or reinstall.",
);
} }
await this.db.exec(`PRAGMA journal_mode=MEMORY;`); await this.db.exec(`PRAGMA journal_mode=MEMORY;`);
const sqlExec = this.db.exec.bind(this.db); const sqlExec = this.db.exec.bind(this.db);
// Run migrations // Run migrations
await runMigrations(sqlExec); await runMigrations(sqlExec);
this.initialized = true; this.initialized = true;
} }
@ -108,13 +116,20 @@ class DatabaseService {
// If initialized but no db, something went wrong // If initialized but no db, something went wrong
if (!this.db) { if (!this.db) {
console.error(`Database not properly initialized after await waitForInitialization() - initialized flag is true but db is null`); console.error(
throw new Error(`The database could not be initialized. We recommend you restart or reinstall.`); `Database not properly initialized after await waitForInitialization() - initialized flag is true but db is null`,
);
throw new Error(
`The database could not be initialized. We recommend you restart or reinstall.`,
);
} }
} }
// Used for inserts, updates, and deletes // Used for inserts, updates, and deletes
async run(sql: string, params: any[] = []): Promise<{ changes: number; lastId?: number }> { async run(
sql: string,
params: any[] = [],
): Promise<{ changes: number; lastId?: number }> {
await this.waitForInitialization(); await this.waitForInitialization();
return this.db!.run(sql, params); return this.db!.run(sql, params);
} }
@ -141,4 +156,4 @@ class DatabaseService {
// Create a singleton instance // Create a singleton instance
const databaseService = DatabaseService.getInstance(); const databaseService = DatabaseService.getInstance();
export default databaseService; export default databaseService;

18
src/services/migrationService.ts

@ -1,4 +1,4 @@
import { QueryExecResult } from '../interfaces/database'; import { QueryExecResult } from "../interfaces/database";
interface Migration { interface Migration {
name: string; name: string;
@ -23,7 +23,7 @@ export class MigrationService {
} }
async runMigrations( async runMigrations(
sqlExec: (sql: string, params?: any[]) => Promise<Array<QueryExecResult>> sqlExec: (sql: string, params?: any[]) => Promise<Array<QueryExecResult>>,
): Promise<void> { ): Promise<void> {
// Create migrations table if it doesn't exist // Create migrations table if it doesn't exist
await sqlExec(` await sqlExec(`
@ -35,12 +35,16 @@ export class MigrationService {
`); `);
// Get list of executed migrations // Get list of executed migrations
const result: QueryExecResult[] = await sqlExec('SELECT name FROM migrations;'); const result: QueryExecResult[] = await sqlExec(
"SELECT name FROM migrations;",
);
let executedMigrations: Set<string> = new Set(); let executedMigrations: Set<string> = new Set();
// Even with that query, the QueryExecResult may be [] (which doesn't make sense to me). // Even with that query, the QueryExecResult may be [] (which doesn't make sense to me).
if (result.length > 0) { if (result.length > 0) {
const singleResult = result[0]; const singleResult = result[0];
executedMigrations = new Set(singleResult.values.map((row: any[]) => row[0])); executedMigrations = new Set(
singleResult.values.map((row: any[]) => row[0]),
);
} }
// Run pending migrations in order // Run pending migrations in order
@ -48,7 +52,9 @@ export class MigrationService {
if (!executedMigrations.has(migration.name)) { if (!executedMigrations.has(migration.name)) {
try { try {
await sqlExec(migration.sql); await sqlExec(migration.sql);
await sqlExec('INSERT INTO migrations (name) VALUES (?)', [migration.name]); await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
migration.name,
]);
console.log(`Migration ${migration.name} executed successfully`); console.log(`Migration ${migration.name} executed successfully`);
} catch (error) { } catch (error) {
console.error(`Error executing migration ${migration.name}:`, error); console.error(`Error executing migration ${migration.name}:`, error);
@ -59,4 +65,4 @@ export class MigrationService {
} }
} }
export default MigrationService.getInstance(); export default MigrationService.getInstance();

10
src/views/AccountViewView.vue

@ -76,7 +76,8 @@
Set Your Name Set Your Name
</button> </button>
<p class="text-xs text-slate-500 mt-1"> <p class="text-xs text-slate-500 mt-1">
(Don't worry: this is not visible to anyone until you share it with them. It's not sent to any servers.) (Don't worry: this is not visible to anyone until you share it with
them. It's not sent to any servers.)
</p> </p>
<UserNameDialog ref="userNameDialog" /> <UserNameDialog ref="userNameDialog" />
</span> </span>
@ -964,7 +965,7 @@ import { AxiosError } from "axios";
import { Buffer } from "buffer/"; import { Buffer } from "buffer/";
import Dexie from "dexie"; import Dexie from "dexie";
import "dexie-export-import"; import "dexie-export-import";
// @ts-ignore - they aren't exporting it but it's there // @ts-expect-error - they aren't exporting it but it's there
import { ImportProgress } from "dexie-export-import"; import { ImportProgress } from "dexie-export-import";
import { LeafletMouseEvent } from "leaflet"; import { LeafletMouseEvent } from "leaflet";
import * as R from "ramda"; import * as R from "ramda";
@ -1610,12 +1611,13 @@ export default class AccountViewView extends Vue {
*/ */
async submitImportFile() { async submitImportFile() {
if (inputImportFileNameRef.value != null) { if (inputImportFileNameRef.value != null) {
await db.delete() await db
.delete()
.then(async () => { .then(async () => {
// BulkError: settings.bulkAdd(): 1 of 21 operations failed. Errors: ConstraintError: Key already exists in the object store. // BulkError: settings.bulkAdd(): 1 of 21 operations failed. Errors: ConstraintError: Key already exists in the object store.
await Dexie.import(inputImportFileNameRef.value as Blob, { await Dexie.import(inputImportFileNameRef.value as Blob, {
progressCallback: this.progressCallback, progressCallback: this.progressCallback,
}) });
}) })
.catch((error) => { .catch((error) => {
logger.error("Error importing file:", error); logger.error("Error importing file:", error);

10
src/views/ContactQRScanShowView.vue

@ -726,9 +726,11 @@ export default class ContactQRScanShow extends Vue {
// Apply mirroring after a short delay to ensure video element is ready // Apply mirroring after a short delay to ensure video element is ready
setTimeout(() => { setTimeout(() => {
const videoElement = document.querySelector('.qr-scanner video') as HTMLVideoElement; const videoElement = document.querySelector(
".qr-scanner video",
) as HTMLVideoElement;
if (videoElement) { if (videoElement) {
videoElement.style.transform = 'scaleX(-1)'; videoElement.style.transform = "scaleX(-1)";
} }
}, 1000); }, 1000);
} }
@ -943,7 +945,9 @@ export default class ContactQRScanShow extends Vue {
// Add method to detect desktop browser // Add method to detect desktop browser
private detectDesktopBrowser(): boolean { private detectDesktopBrowser(): boolean {
const userAgent = navigator.userAgent.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase();
return !/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent); return !/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
userAgent,
);
} }
// Update the computed property for camera mirroring // Update the computed property for camera mirroring

10
src/views/NewIdentifierView.vue

@ -34,9 +34,13 @@
</div> </div>
<div v-else-if="hitError"> <div v-else-if="hitError">
<span class="text-xl">Error Creating Identity</span> <span class="text-xl">Error Creating Identity</span>
<font-awesome icon="exclamation-triangle" class="fa-fw text-red-500 ml-2"></font-awesome> <font-awesome
icon="exclamation-triangle"
class="fa-fw text-red-500 ml-2"
></font-awesome>
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
Try fully restarting the app. If that doesn't work, back up all data (identities and other data) and reinstall the app. Try fully restarting the app. If that doesn't work, back up all data
(identities and other data) and reinstall the app.
</p> </p>
</div> </div>
<div v-else> <div v-else>
@ -85,7 +89,7 @@ export default class NewIdentifierView extends Vue {
.catch((error) => { .catch((error) => {
this.loading = false; this.loading = false;
this.hitError = true; this.hitError = true;
console.error('Failed to generate identity:', error); console.error("Failed to generate identity:", error);
}); });
} }
} }

14
src/views/TestView.vue

@ -167,7 +167,9 @@
<div class="flex gap-2 mb-2"> <div class="flex gap-2 mb-2">
<button <button
class="text-sm text-blue-600 hover:text-blue-800 underline" class="text-sm text-blue-600 hover:text-blue-800 underline"
@click="sqlQuery = 'SELECT * FROM sqlite_master WHERE type=\'table\';'" @click="
sqlQuery = 'SELECT * FROM sqlite_master WHERE type=\'table\';'
"
> >
All Tables All Tables
</button> </button>
@ -178,7 +180,7 @@
placeholder="Enter your SQL query here..." placeholder="Enter your SQL query here..."
></textarea> ></textarea>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<button <button
class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2" class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
@ -189,7 +191,9 @@
</div> </div>
<div v-if="sqlResult" class="mt-4"> <div v-if="sqlResult" class="mt-4">
<h3 class="text-lg font-semibold mb-2">Result:</h3> <h3 class="text-lg font-semibold mb-2">Result:</h3>
<pre class="bg-gray-100 p-4 rounded-md overflow-x-auto">{{ JSON.stringify(sqlResult, null, 2) }}</pre> <pre class="bg-gray-100 p-4 rounded-md overflow-x-auto">{{
JSON.stringify(sqlResult, null, 2)
}}</pre>
</div> </div>
</div> </div>
@ -532,7 +536,7 @@ export default class Help extends Vue {
async executeSql() { async executeSql() {
try { try {
const isSelect = this.sqlQuery.trim().toLowerCase().startsWith('select'); const isSelect = this.sqlQuery.trim().toLowerCase().startsWith("select");
if (isSelect) { if (isSelect) {
this.sqlResult = await databaseService.query(this.sqlQuery); this.sqlResult = await databaseService.query(this.sqlQuery);
} else { } else {
@ -548,7 +552,7 @@ export default class Help extends Vue {
title: "SQL Error", title: "SQL Error",
text: error instanceof Error ? error.message : String(error), text: error instanceof Error ? error.message : String(error),
}, },
5000 5000,
); );
} }
} }

Loading…
Cancel
Save