Compare commits

..

1 commit

Author SHA1 Message Date
vince
d377756988 feat: add dashboard persistence store 2026-06-18 18:11:09 +02:00
2 changed files with 44 additions and 2 deletions

View file

@ -2,7 +2,7 @@ import { existsSync, rmSync } from "node:fs";
import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { afterEach, describe, expect, test, vi } from "vitest";
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
import type { DashboardDocument } from "$lib/model";
import {
@ -15,6 +15,7 @@ const stores: DashboardStore[] = [];
const tempRoots: string[] = [];
afterEach(() => {
vi.useRealTimers();
stores.splice(0).forEach((store) => store.close());
tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true }));
});
@ -130,6 +131,40 @@ describe("dashboard persistence store", () => {
"seed",
]);
});
test("assigns monotonic revision timestamps for stable history ordering", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-06-18T12:00:00.000Z"));
const { store } = await createTestStore();
const seed = store.seedDashboardIfEmpty(genericDashboardFixture, {
actor: "test-seed",
});
const updated = cloneDashboard({
...genericDashboardFixture,
metadata: {
...genericDashboardFixture.metadata,
title: "Changed Dashboard",
},
});
const commit = store.commitDashboard(updated, { actor: "agent" });
const rollback = store.rollbackToRevision(seed.id, { actor: "operator" });
expect([
seed.createdAt.toISOString(),
commit.createdAt.toISOString(),
rollback.createdAt.toISOString(),
]).toEqual([
"2026-06-18T12:00:00.000Z",
"2026-06-18T12:00:00.001Z",
"2026-06-18T12:00:00.002Z",
]);
expect(store.listRevisions().map((item) => item.id)).toEqual([
rollback.id,
commit.id,
seed.id,
]);
});
});
async function createTestStore() {

View file

@ -185,7 +185,7 @@ class SqliteDashboardStore implements DashboardStore {
throw new DashboardPersistenceValidationError(migration.failure);
}
const now = new Date();
const now = this.nextRevisionTimestamp();
const revision: DashboardRevision = {
id: randomUUID(),
dashboardId: this.dashboardId,
@ -230,6 +230,13 @@ class SqliteDashboardStore implements DashboardStore {
return revision;
}
private nextRevisionTimestamp(): Date {
const active = this.getActiveDashboard();
const activeUpdatedAtMs = active?.updatedAt.getTime() || 0;
return new Date(Math.max(Date.now(), activeUpdatedAtMs + 1));
}
}
type DashboardRevisionRow = typeof dashboardRevisions.$inferSelect;