Use SQLite for shared program state

This commit is contained in:
2026-06-23 17:59:46 +09:00
parent 5df369100a
commit 5b7444ad9a
2 changed files with 78 additions and 12 deletions

BIN
data/flow.db Normal file

Binary file not shown.

View File

@@ -1,13 +1,18 @@
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
const databasePath = path.resolve(process.cwd(), 'data', 'flow-db.json'); const dataDirectory = path.resolve(process.cwd(), 'data');
const sqlitePath = path.join(dataDirectory, 'flow.db');
const seedJsonPath = path.join(dataDirectory, 'flow-db.json');
async function readDatabase() { let database;
async function readSeedJson() {
try { try {
const raw = await fs.readFile(databasePath, 'utf8'); const raw = await fs.readFile(seedJsonPath, 'utf8');
return JSON.parse(raw); return JSON.parse(raw);
} catch (error) { } catch (error) {
if (error.code === 'ENOENT') return null; if (error.code === 'ENOENT') return null;
@@ -15,12 +20,74 @@ async function readDatabase() {
} }
} }
async function getDatabase() {
if (database) return database;
await fs.mkdir(dataDirectory, { recursive: true });
database = new DatabaseSync(sqlitePath);
database.exec(`
CREATE TABLE IF NOT EXISTS app_state (
id INTEGER PRIMARY KEY CHECK (id = 1),
content TEXT NOT NULL,
program_states TEXT NOT NULL,
updated_at TEXT NOT NULL
)
`);
const existing = database.prepare('SELECT id FROM app_state WHERE id = 1').get();
if (!existing) {
const seed = await readSeedJson();
if (seed) writeDatabase(seed);
} else {
const seed = await readSeedJson();
const row = database.prepare('SELECT content FROM app_state WHERE id = 1').get();
const existingContent = row ? JSON.parse(row.content) : null;
const existingExtraCount = existingContent?.extraPrograms?.length ?? 0;
const seedExtraCount = seed?.content?.extraPrograms?.length ?? 0;
if (existingExtraCount === 0 && seedExtraCount > 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) { async function writeDatabase(data) {
await fs.mkdir(path.dirname(databasePath), { recursive: true }); const db = await getDatabase();
await fs.writeFile(databasePath, JSON.stringify({ const updatedAt = new Date().toISOString();
...data, const content = data.content ?? {};
updatedAt: new Date().toISOString() const programStates = data.programStates ?? {};
}, null, 2));
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) { function readRequestBody(request) {
@@ -48,7 +115,7 @@ function sendJson(response, statusCode, payload) {
function serverDatabasePlugin() { function serverDatabasePlugin() {
return { return {
name: 'flow-server-database', name: 'flow-server-sqlite-database',
configureServer(server) { configureServer(server) {
server.middlewares.use('/api/state', async (request, response) => { server.middlewares.use('/api/state', async (request, response) => {
try { try {
@@ -66,11 +133,10 @@ function serverDatabasePlugin() {
const current = request.method === 'PATCH' ? (await readDatabase()) ?? {} : {}; const current = request.method === 'PATCH' ? (await readDatabase()) ?? {} : {};
const body = await readRequestBody(request); const body = await readRequestBody(request);
const payload = body ? JSON.parse(body) : {}; const payload = body ? JSON.parse(body) : {};
const nextData = { const nextData = await writeDatabase({
...current, ...current,
...payload ...payload
}; });
await writeDatabase(nextData);
sendJson(response, 200, nextData); sendJson(response, 200, nextData);
return; return;
} }