diff --git a/src/App.vue b/src/App.vue
index d1e6953..f539c2f 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -22,8 +22,13 @@
@@ -31,11 +36,16 @@
diff --git a/src/db.ts b/src/db.ts
index e4f485a..4f178bc 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -25,6 +25,7 @@ import { reactive } from 'vue';
import { Balance } from './amounts.ts';
import { ExtendedDatabase } from './dbutil.ts';
+import { CriticalError } from './error.ts';
export const DB_VERSION = 3; // Should match schema.sql
export const DT_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS000';
@@ -53,8 +54,23 @@ export const db = reactive({
}
if (filename !== null) {
- // Initialise cached data
const session = await this.load();
+
+ // Validate database version
+ let dbVersion: {value: string}[];
+ try {
+ dbVersion = await session.select("SELECT value FROM metadata WHERE key = 'version'");
+ } catch (err) {
+ throw new CriticalError('Unable to parse database (SQL error getting metadata.version)', err);
+ }
+ if (dbVersion.length === 0) {
+ throw new CriticalError('Unable to parse database (no metadata.version)');
+ }
+ if (dbVersion[0].value !== DB_VERSION.toString()) {
+ throw new CriticalError('Unsupported database version ' + dbVersion[0].value + ' (expected ' + DB_VERSION + ')');
+ }
+
+ // Initialise cached data
const metadataRaw: {key: string, value: string}[] = await session.select("SELECT * FROM metadata");
const metadataObject = Object.fromEntries(metadataRaw.map((x) => [x.key, x.value]));
this.metadata.version = parseInt(metadataObject.version);
@@ -62,8 +78,6 @@ export const db = reactive({
this.metadata.reporting_commodity = metadataObject.reporting_commodity;
this.metadata.dps = parseInt(metadataObject.amount_dps);
}
-
- // TODO: Validate database version
},
load: async function(): Promise {
diff --git a/src/error.ts b/src/error.ts
new file mode 100644
index 0000000..ffdbee6
--- /dev/null
+++ b/src/error.ts
@@ -0,0 +1,39 @@
+/*
+ DrCr: Web-based double-entry bookkeeping framework
+ Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+import { ref } from 'vue';
+
+// Global error state
+export const error = ref(null as CriticalError | null);
+
+export class CriticalError extends Error {
+ public error?: any;
+
+ constructor(message: string, error?: any) {
+ super(message);
+ this.error = error;
+ }
+}
+
+export function handleCriticalError(err: any) {
+ if (err instanceof CriticalError) {
+ error.value = err;
+ } else {
+ error.value = new CriticalError('An unexpected error occurred', err);
+ }
+}
diff --git a/src/main.ts b/src/main.ts
index 79c66ce..699f971 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -25,6 +25,7 @@ import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import { db } from './db.ts';
+import { handleCriticalError } from './error.ts';
async function initApp() {
// Init router
@@ -61,7 +62,11 @@ async function initApp() {
// Init state
const dbFilename: string = await invoke('get_open_filename');
if (dbFilename !== null) {
- await db.init(dbFilename); // Ensure all metadata cached before loading Vue
+ try {
+ await db.init(dbFilename); // Ensure all metadata cached before loading Vue
+ } catch (err) {
+ handleCriticalError(err);
+ }
}
// Create Vue app
diff --git a/src/pages/CriticalErrorView.vue b/src/pages/CriticalErrorView.vue
new file mode 100644
index 0000000..d3c3a44
--- /dev/null
+++ b/src/pages/CriticalErrorView.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
{{ error.message }}
+
{{ error.error }}
+
+
+
+
+
+