From da3f0c78364318b5a8b6d612c560d3e39cc733ee Mon Sep 17 00:00:00 2001 From: LukeGus Date: Mon, 19 Oct 2026 22:03:25 -0500 Subject: [PATCH] fix: keep text primary keys in the plugin data export --- CHANGELOG.md | 1 + src/backend/plugins/user-data.ts | 6 ++---- src/backend/tests/plugins/user-data.test.ts | 24 +++++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95c4484c3..919a3e603 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ ### Fixed - Linking the desktop app to a server no longer stalls on the server dashboard after sign in +- Exporting your data no longer fails when a plugin table has a text key, like saved passkeys ### Removed diff --git a/src/backend/plugins/user-data.ts b/src/backend/plugins/user-data.ts index 4807deb07..59bb8c4b9 100644 --- a/src/backend/plugins/user-data.ts +++ b/src/backend/plugins/user-data.ts @@ -27,7 +27,7 @@ export interface UserOwnedTable { otherUserColumns: string[]; /** refHost columns. */ hostColumns: string[]; - /** Columns that travel, as SQL names. The primary key and secrets do not. */ + /** Columns that travel, as SQL names. Auto ids and secrets do not. */ columns: string[]; /** The owner column is the primary key: one row per user. */ userIsKey: boolean; @@ -57,9 +57,7 @@ export function listUserOwnedTables(): UserOwnedTable[] { .filter( ([, column]) => column === user[1] || - (column.type !== "id" && - !column.primaryKey && - column.type !== "encryptedText"), + (column.type !== "id" && column.type !== "encryptedText"), ) .map(([property, column]) => columnName(property, column)), userIsKey: !!user[1].primaryKey, diff --git a/src/backend/tests/plugins/user-data.test.ts b/src/backend/tests/plugins/user-data.test.ts index 66d0b135c..4dc5d3183 100644 --- a/src/backend/tests/plugins/user-data.test.ts +++ b/src/backend/tests/plugins/user-data.test.ts @@ -155,6 +155,30 @@ describe("plugin user data", () => { }); }); + it("exports a table with a text primary key", async () => { + const credentials = defineTable("credential", { + id: varchar(64).primaryKey(), + userId: refUser().notNull(), + label: text(), + }); + for (const statement of createTableSql("sqlite", "fixture", credentials)) { + sqlite.exec(statement); + } + registerTable("fixture", credentials); + sqlite.exec( + "INSERT INTO p_fixture_credential (id, user_id, label) VALUES ('abc', 'u1', 'key')", + ); + + const file = new Database(":memory:"); + file.exec("CREATE TABLE users (id TEXT PRIMARY KEY)"); + file.exec("CREATE TABLE ssh_data (id INTEGER PRIMARY KEY)"); + file.exec("INSERT INTO users (id) VALUES ('u1')"); + await writeUserPluginTables(file, "u1"); + expect(file.prepare("SELECT * FROM p_fixture_credential").all()).toEqual([ + { id: "abc", user_id: "u1", label: "key" }, + ]); + }); + it("writes large tables in chunks and lets the event loop run between them", async () => { const insert = sqlite.prepare( "INSERT INTO p_fixture_bookmark (user_id, path) VALUES ('u1', ?)",