Skip to content

Commit

Permalink
allow hard resetting the database if an error occurs
Browse files Browse the repository at this point in the history
  • Loading branch information
iansinnott committed Oct 22, 2023
1 parent 51b340a commit 9d5e81a
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 48 deletions.
9 changes: 6 additions & 3 deletions src/lib/components/SettingsModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
getOpenAi,
chatModels,
} from "$lib/stores/stores";
import { DB_NAME } from "$lib/constants";
import AutosizeTextarea from "./AutosizeTextarea.svelte";
import { getSystem } from "$lib/gui";
import { onMount } from "svelte";
import { ChatMessage, Thread } from "$lib/db";
import { ChatMessage, Thread, getLatestDbName } from "$lib/db";
import { mapKeys, toCamelCase } from "$lib/utils";
import CloseButton from "./CloseButton.svelte";
import type OpenAI from "openai";
let dbName = "";
let schema;
let migrationVersion;
onMount(async () => {
Expand All @@ -32,7 +32,10 @@
.filter((x) => x.id.startsWith("gpt"))
.sort((a, b) => a.id.localeCompare(b.id));
}
dbName = getLatestDbName() || "";
});
let showAdvanced = false;
</script>

Expand Down Expand Up @@ -285,7 +288,7 @@
<div class="overflow-auto max-w-full">
<pre
class="py-1 px-2 rounded text-slate-300 text-sm border border-zinc-700 table whitespace-pre-wrap overflow-auto w-full">
{DB_NAME}<span class="text-blue-300 opacity-50">/{$openAiConfig.siteId}</span>
{dbName}<span class="text-blue-300 opacity-50">/{$openAiConfig.siteId}</span>
</pre>
<p class="opacity-60">
<small> Database identifier used locally for persistent storage. </small>
Expand Down
26 changes: 0 additions & 26 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,26 +0,0 @@
/**
* A record of the databases we expect to be accessible to the app. Although
* clearly not enforced, this list should be treated as append-only. This does
* not mean the db version can't be moved backwards, just do so by appending.
*
* Appending a new db name effectively resets the db, without data loss since
* you can change it back. This is good for testing as if on a new system.
*/
export const DB_NAMES = [
"chat_db-v1",
"chat_db-v2",
"chat_db-v3",
"chat_db-v4",
"chat_db-v5",
"chat_db-v8.1",
"chat_db-v8.2", // Testing out migration fixes
"chat_db-v8.1", // Moving back to 8.1 now that migrations are fixed. No data changed, just migration code
"chat_db-v9.1", // Starting fresh in dev
"chat_db-v9.2", // QA importing
"chat_db-v10", // Corrupted db
"chat_db-v10.1", // Testing syncing
"chat_db-v11", // fts
"chat_db-v11.11", // fts
];

export const DB_NAME = DB_NAMES.at(-1);
70 changes: 67 additions & 3 deletions src/lib/db.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import initWasm, { SQLite3, DB } from "@vlcn.io/crsqlite-wasm";
import type { TXAsync } from "@vlcn.io/xplat-api";
import wasmUrl from "@vlcn.io/crsqlite-wasm/crsqlite.wasm?url";
import { DB_NAME } from "../lib/constants";
import {
db,
sqlite,
Expand All @@ -20,6 +19,67 @@ import { basename, debounce, groupBy, sha1sum, toCamelCase, toSnakeCase } from "
import { extractFragments } from "./markdown";
import tblrx, { TblRx } from "@vlcn.io/rx-tbl";

const legacyDbNames = [
"chat_db-v1",
"chat_db-v2",
"chat_db-v3",
"chat_db-v4",
"chat_db-v5",
"chat_db-v8.1",
"chat_db-v8.2", // Testing out migration fixes
"chat_db-v8.1", // Moving back to 8.1 now that migrations are fixed. No data changed, just migration code
"chat_db-v9.1", // Starting fresh in dev
"chat_db-v9.2", // QA importing
"chat_db-v10", // Corrupted db
"chat_db-v10.1", // Testing syncing
"chat_db-v11", // fts
"chat_db-v11.11", // fts
];

const lsKey = "prompta--dbNames";

/**
* Get a record of the databases we expect to be accessible to the app. Although
* clearly not enforced, this list should be treated as append-only. This does
* not mean the db version can't be moved backwards, just do so by appending.
*
* Appending a new db name effectively resets the db, without data loss since
* you can change it back. This is good for testing as if on a new system.
*/
const getDbNames = () => {
if (typeof localStorage === "undefined") {
throw new Error("localStorage is not available");
}

let _dbNames = localStorage.getItem(lsKey);

if (!_dbNames) {
_dbNames = JSON.stringify(legacyDbNames);
localStorage.setItem(lsKey, _dbNames);
}

let dbNames: string[] = [];

if (_dbNames) {
dbNames = JSON.parse(_dbNames);
}

return dbNames;
};

export const getLatestDbName = () => {
return getDbNames().at(-1);
};

export const incrementDbName = () => {
const v = (getDbNames().length + 1).toString().padStart(3, "0");
const next = `chat_db-v${v}`;

localStorage.setItem(lsKey, JSON.stringify([...getDbNames(), next]));

return next;
};

// ========================================================================================
// Rudimentary Migration System
// ========================================================================================
Expand Down Expand Up @@ -112,17 +172,21 @@ const migrateDb = async (db: DB) => {
}
};

export const initDb = async () => {
export const initDb = async (dbName: string) => {
if (_db) {
console.debug("DB already initialized");
return;
}

if (!dbName) {
throw new Error("No database name provided");
}

// @note This only works in the browser. Don't use SSR anywhere where you need this
_sqlite = await initWasm(() => wasmUrl);
sqlite.set(_sqlite);

_db = await _sqlite.open(DB_NAME);
_db = await _sqlite.open(dbName);
db.set(_db);

await migrateDb(_db);
Expand Down
6 changes: 6 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,9 @@ export const sha1sum = async (s: string) => {
export const isPWAInstalled = () => {
return globalThis.matchMedia("(display-mode: standalone)").matches;
};

export function wrapError(innerError: Error, newMessage: string): Error {
const wrappedError = new Error(newMessage + "\n" + innerError.message);
wrappedError.stack = `${newMessage}\nCaused by: ${innerError.stack}`;
return wrappedError;
}
32 changes: 16 additions & 16 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
import "../app.postcss";
import { openAiConfig, syncStore, showInitScreen } from "../lib/stores/stores";
import { onMount } from "svelte";
import { DatabaseMeta, initDb } from "$lib/db";
import { DatabaseMeta, getLatestDbName, incrementDbName, initDb } from "$lib/db";
import SettingsModal from "$lib/components/SettingsModal.svelte";
import { getSystem } from "$lib/gui";
import classNames from "classnames";
import { dev } from "$app/environment";
import Toaster from "$lib/toast/Toaster.svelte";
import { assets } from "$app/paths";
import FullScreenError from "$lib/components/FullScreenError.svelte";
import { wrapError } from "$lib/utils";
const sys = getSystem();
let startupError: Error | null = null;
startupError = new Error("Test error");
let appReady = false;
const handleHardReset = async () => {
Expand All @@ -23,9 +23,14 @@
if (!confirmed) return;
await sys.alert("TODO: Reset the database");
appReady = false;
// await DatabaseMeta.hardReset();
incrementDbName();
// Reloading the window should cause handleStartup to be called again on app
// mount, using the new db name. Using a new db name is equivalent to using
// a new db. The old db will not be removed, but the app will have no
// existing data.
location.reload();
};
Expand All @@ -39,18 +44,14 @@
try {
const start = performance.now();
console.debug("Initializing database");
await initDb();
await initDb(getLatestDbName() || "");
console.debug(`Database initialized in ${performance.now() - start}ms`);
} catch (err: any) {
await sys.alert(
`There was an error initializing the database. Please try again. If the problem persists, please report it on GitHub.` +
err.message
);
throw err;
throw wrapError(err, `There was an error initializing the database.`);
} finally {
clearTimeout(_timeout);
}
clearTimeout(_timeout);
if (!$openAiConfig.apiKey) {
$showInitScreen = true;
console.warn(`No API key found. Please enter one in the settings.`);
Expand Down Expand Up @@ -171,12 +172,11 @@
})}
>
{#if startupError}
<FullScreenError error={startupError}>
<FullScreenError title="The app could not be initialized" error={startupError}>
<div class="prose prose-invert">
<h3>The app could not be initialized</h3>
<p>
<h3>
<em>What can you do?</em>
</p>
</h3>
<ul>
<li>
<strong> Reset your database </strong>. This will delete all your data, but it might
Expand Down

0 comments on commit 9d5e81a

Please sign in to comment.