feat: rebuild desktop sync as linked server accounts, fix packaged plugin bundles and UI scaling

This commit is contained in:
LukeGus committed 2026-09-26 00:41:25 -05:00
1 parent 7c02daccf4
commit 6cd9a2846d
242 files changed
+39127 -8558

No files matched your search

@@ -1,109 +0,0 @@
import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js";
import { SyncTombstoneRepository } from "../../../database/repositories/sync-tombstone-repository.js";
describe("SyncTombstoneRepository", () => {
let adapter: TestSqliteDatabase | null = null;
afterEach(async () => {
if (adapter) {
await adapter.close();
adapter = null;
}
});
async function createRepository(
onWrite?: () => void | Promise<void>,
): Promise<SyncTombstoneRepository> {
adapter = new TestSqliteDatabase();
const context = await adapter.connect();
await adapter.exec(`
INSERT INTO users (id, username, password_hash)
VALUES ('user-1', 'alice', 'hash'), ('user-2', 'bob', 'hash');
`);
return new SyncTombstoneRepository(context, onWrite);
}
it("records a tombstone and lists it back for the owning user", async () => {
let writeCount = 0;
const repo = await createRepository(() => {
writeCount += 1;
});
await repo.record("user-1", "hosts", "sync-abc");
expect(writeCount).toBe(1);
const rows = await repo.listSince("user-1", "hosts", null);
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({
userId: "user-1",
entityType: "hosts",
syncId: "sync-abc",
});
});
it("does not record a tombstone for an empty syncId", async () => {
const repo = await createRepository();
await repo.record("user-1", "hosts", "");
const rows = await repo.listSince("user-1", "hosts", null);
expect(rows).toHaveLength(0);
});
it("recordMany writes multiple tombstones and filters out falsy ids", async () => {
let writeCount = 0;
const repo = await createRepository(() => {
writeCount += 1;
});
await repo.recordMany("user-1", "hosts", ["a", "", "b", "c"]);
expect(writeCount).toBe(1);
const rows = await repo.listSince("user-1", "hosts", null);
expect(rows.map((r) => r.syncId).sort()).toEqual(["a", "b", "c"]);
});
it("recordMany is a no-op when given no syncIds", async () => {
let writeCount = 0;
const repo = await createRepository(() => {
writeCount += 1;
});
await repo.recordMany("user-1", "hosts", []);
expect(writeCount).toBe(0);
});
it("scopes listSince by userId and entityType", async () => {
const repo = await createRepository();
await repo.record("user-1", "hosts", "sync-1");
await repo.record("user-1", "snippets", "sync-2");
await repo.record("user-2", "hosts", "sync-3");
const rows = await repo.listSince("user-1", "hosts", null);
expect(rows).toHaveLength(1);
expect(rows[0].syncId).toBe("sync-1");
});
it("filters listSince by the since timestamp", async () => {
const adapterLocal = new TestSqliteDatabase();
adapter = adapterLocal;
const context = await adapterLocal.connect();
await adapter.exec(`
INSERT INTO users (id, username, password_hash)
VALUES ('user-1', 'alice', 'hash');
INSERT INTO sync_tombstones (user_id, entity_type, sync_id, deleted_at)
VALUES
('user-1', 'hosts', 'old', '2026-01-01T00:00:00.000Z'),
('user-1', 'hosts', 'new', '2026-06-01T00:00:00.000Z');
`);
const repo = new SyncTombstoneRepository(context);
const rows = await repo.listSince(
"user-1",
"hosts",
"2026-03-01T00:00:00.000Z",
);
expect(rows).toHaveLength(1);
expect(rows[0].syncId).toBe("new");
});
});
@@ -1,111 +0,0 @@
import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js";
import { SyncTombstoneRepository } from "../../../database/repositories/sync-tombstone-repository.js";
import {
normalizeSyncTimestamp,
timestampAtOrAfter,
} from "../../../database/sync-timestamp.js";
import { sshCredentials } from "../../../database/db/schema.js";
import type { DatabaseContext } from "../../../database/repositories/database-context.js";
import { and, eq } from "drizzle-orm";
// The desktop sync engine always sends its cursor as new Date().toISOString().
const ISO_CURSOR = "2026-07-29T09:00:00.000Z";
describe("sync cursors across timestamp layouts", () => {
let adapter: TestSqliteDatabase | null = null;
afterEach(async () => {
if (adapter) {
await adapter.close();
adapter = null;
}
});
/**
* The harness migrates the schema itself, so the seeds below are INSERTs into
* the real tables. Both `sync_tombstones.user_id` and `ssh_credentials.user_id`
* are foreign keys into `users`, which the harness enforces, so the owning row
* has to exist before either seed runs.
*/
async function connect(): Promise<{
db: TestSqliteDatabase;
context: DatabaseContext;
}> {
const db = new TestSqliteDatabase();
adapter = db;
const context = await db.connect();
await db.exec(`
INSERT INTO users (id, username, password_hash) VALUES
('user-1', 'user', 'hash');
`);
return { db, context };
}
it("normalizes both layouts to one comparable form", () => {
expect(normalizeSyncTimestamp("2026-07-29T10:11:21.123Z")).toBe(
"2026-07-29 10:11:21",
);
expect(normalizeSyncTimestamp("2026-07-29 10:11:21")).toBe(
"2026-07-29 10:11:21",
);
});
it("returns tombstones recorded after an ISO cursor, whatever layout they were stored in", async () => {
const { db, context } = await connect();
await db.exec(`
INSERT INTO sync_tombstones (user_id, entity_type, sync_id, deleted_at) VALUES
('user-1', 'sshCredentials', 'sqlite-layout', '2026-07-29 10:07:32'),
('user-1', 'sshCredentials', 'iso-layout', '2026-07-29T10:07:32.500Z'),
('user-1', 'sshCredentials', 'too-old', '2026-07-29 08:00:00');
`);
const repo = new SyncTombstoneRepository(context);
const rows = await repo.listSince("user-1", "sshCredentials", ISO_CURSOR);
expect(rows.map((row) => row.syncId).sort()).toEqual([
"iso-layout",
"sqlite-layout",
]);
});
it("returns rows written by CURRENT_TIMESTAMP against an ISO cursor", async () => {
const { db, context } = await connect();
await db.exec(`
INSERT INTO ssh_credentials (user_id, name, auth_type, updated_at) VALUES
('user-1', 'newer-sqlite-layout', 'password', '2026-07-29 10:11:21'),
('user-1', 'newer-iso-layout', 'password', '2026-07-29T10:11:21.123Z'),
('user-1', 'older', 'password', '2026-07-29 08:59:59');
`);
const rows = await context.drizzle
.select({ name: sshCredentials.name })
.from(sshCredentials)
.where(
and(
eq(sshCredentials.userId, "user-1"),
timestampAtOrAfter(sshCredentials.updatedAt, ISO_CURSOR),
),
);
expect(rows.map((row) => row.name).sort()).toEqual([
"newer-iso-layout",
"newer-sqlite-layout",
]);
});
it("keeps rows written in the same second as the cursor", async () => {
const { db, context } = await connect();
await db.exec(`
INSERT INTO ssh_credentials (user_id, name, auth_type, updated_at)
VALUES ('user-1', 'same-second', 'password', '2026-07-29 09:00:00');
`);
const rows = await context.drizzle
.select({ name: sshCredentials.name })
.from(sshCredentials)
.where(timestampAtOrAfter(sshCredentials.updatedAt, ISO_CURSOR));
expect(rows.map((row) => row.name)).toEqual(["same-second"]);
});
});