feat(daemon): auto-persist signals to sense DB #150
@@ -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, {});
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user