From 5b7444ad9a4a0c2f324e5056810f71e9eb56ff5b Mon Sep 17 00:00:00 2001 From: Hyein Date: Tue, 23 Jun 2026 17:59:46 +0900 Subject: [PATCH] Use SQLite for shared program state --- data/flow.db | Bin 0 -> 16384 bytes vite.config.js | 90 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 78 insertions(+), 12 deletions(-) create mode 100644 data/flow.db diff --git a/data/flow.db b/data/flow.db new file mode 100644 index 0000000000000000000000000000000000000000..2c512b7a3bb1670d624402c441edc28062eb7cc2 GIT binary patch literal 16384 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 0) { + writeDatabase(seed); + } + } + + return database; +} + +async function readDatabase() { + const db = await getDatabase(); + const row = db + .prepare('SELECT content, program_states AS programStates, updated_at AS updatedAt FROM app_state WHERE id = 1') + .get(); + + if (!row) return null; + + return { + content: JSON.parse(row.content), + programStates: JSON.parse(row.programStates), + updatedAt: row.updatedAt + }; +} + async function writeDatabase(data) { - await fs.mkdir(path.dirname(databasePath), { recursive: true }); - await fs.writeFile(databasePath, JSON.stringify({ - ...data, - updatedAt: new Date().toISOString() - }, null, 2)); + const db = await getDatabase(); + const updatedAt = new Date().toISOString(); + const content = data.content ?? {}; + const programStates = data.programStates ?? {}; + + db.prepare(` + INSERT INTO app_state (id, content, program_states, updated_at) + VALUES (1, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + content = excluded.content, + program_states = excluded.program_states, + updated_at = excluded.updated_at + `).run(JSON.stringify(content), JSON.stringify(programStates), updatedAt); + + return { + content, + programStates, + updatedAt + }; } function readRequestBody(request) { @@ -48,7 +115,7 @@ function sendJson(response, statusCode, payload) { function serverDatabasePlugin() { return { - name: 'flow-server-database', + name: 'flow-server-sqlite-database', configureServer(server) { server.middlewares.use('/api/state', async (request, response) => { try { @@ -66,11 +133,10 @@ function serverDatabasePlugin() { const current = request.method === 'PATCH' ? (await readDatabase()) ?? {} : {}; const body = await readRequestBody(request); const payload = body ? JSON.parse(body) : {}; - const nextData = { + const nextData = await writeDatabase({ ...current, ...payload - }; - await writeDatabase(nextData); + }); sendJson(response, 200, nextData); return; }