feat(daemon): auto-persist signals to sense DB #150

Merged
xiaomo merged 1 commits from feat/auto-persist-signals into main 2026-04-27 05:33:29 +00:00
4 changed files with 34 additions and 5 deletions
+3 -1
View File
@@ -42,7 +42,9 @@ export function pickDefaultPreviewTable(db: DatabaseSync): string | null {
WHERE type = 'table' AND sql IS NOT NULL
AND name NOT LIKE 'sqlite\\_%' ESCAPE '\\'
ORDER BY
CASE WHEN name = '_migrations' THEN 1 ELSE 0 END,
CASE WHEN name = '_signals' THEN 0
WHEN name = '_migrations' THEN 2
ELSE 1 END,
name
LIMIT 1`,
)
@@ -176,7 +176,7 @@ describe("executeCompute", () => {
sqlite.exec(INIT_SQL);
const db = drizzle({ client: sqlite }) as DrizzleDB;
return {
runtime: { name: "test-sense", db, compute: computeFn },
runtime: { name: "test-sense", db, compute: computeFn, persistSignal: () => {} },
sqlite,
};
}
@@ -287,6 +287,7 @@ describe("executeCompute", () => {
await d.insert(samples).values({ ts: 1000, value: 1.23 });
return 1.23;
},
persistSignal: () => {},
};
const result = await executeCompute(runtime, {});
+19 -2
View File
@@ -40,6 +40,7 @@ export type SenseRuntime = {
name: string;
db: DrizzleDB;
compute: ComputeFn;
persistSignal: (payload: unknown) => void;
};
function ensureMigrationsTable(sqlite: DatabaseSync): Result<void> {
@@ -131,7 +132,7 @@ export function runMigrations(sqlite: DatabaseSync, migrationsDir: string): Resu
export function openSenseDb(
dbPath: string,
migrationsDir: string,
): Result<{ sqlite: DatabaseSync; db: DrizzleDB }> {
): Result<{ sqlite: DatabaseSync; db: DrizzleDB; persistSignal: (payload: unknown) => void }> {
let sqlite: DatabaseSync;
try {
@@ -146,9 +147,25 @@ export function openSenseDb(
const migResult = runMigrations(sqlite, migrationsDir);
if (!migResult.ok) return migResult;
// Auto-create _signals table for signal persistence (all senses get this)
sqlite.exec(
`CREATE TABLE IF NOT EXISTS _signals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
payload TEXT NOT NULL,
timestamp INTEGER NOT NULL
)`,
);
const insertStmt = sqlite.prepare("INSERT INTO _signals (payload, timestamp) VALUES (?, ?)");
function persistSignal(payload: unknown): void {
const json = JSON.stringify(payload);
insertStmt.run(json, Date.now());
}
// Drizzle infers a schema-specific DB type; senses are schema-agnostic at this layer.
const db = drizzle({ client: sqlite }) as DrizzleDB;
return ok({ sqlite, db });
return ok({ sqlite, db, persistSignal });
}
/**
+10 -1
View File
@@ -91,7 +91,15 @@ async function initSense(
}
const { db } = dbResult.value;
return { db, runtime: { name: senseName, db, compute: computeResult.value } };
return {
db,
runtime: {
name: senseName,
db,
compute: computeResult.value,
persistSignal: dbResult.value.persistSignal,
},
};
}
function buildPeers(
@@ -178,6 +186,7 @@ async function runCompute(
}
clearGracePeriodTimer(senseName);
if (result.value !== null) {
runtime.persistSignal(result.value);
sendSignal(senseName, result.value);
}
} catch (e: unknown) {