@fudrouter/fsrouter 0.6.104 → 0.6.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/lib/db/index.js +13 -0
  2. package/dist/lib/db/restoreRunner.js +136 -0
  3. package/dist/routes/db/raw/route.js +40 -0
  4. package/dist/routes/db/restore-path/route.js +28 -0
  5. package/dist/routes/db/route.js +3 -175
  6. package/package.json +4 -2
  7. package/public/.vite/manifest.json +86 -86
  8. package/public/assets/EndpointPageClient.Y4DlHkTu.js +6 -0
  9. package/public/assets/Loading.DFQ28pFE.js +1 -0
  10. package/public/assets/NoAuthProxyCard.CZTUlj4t.js +89 -0
  11. package/public/assets/index.AIBoioDI.js +89 -0
  12. package/public/assets/index.C--zNQ3O.css +1 -0
  13. package/public/assets/page.4cDBHbw-.js +6 -0
  14. package/public/assets/page.BKEq519M.js +4 -0
  15. package/public/assets/page.BP6GAYzJ.js +1 -0
  16. package/public/assets/page.BSn6cIN6.js +1 -0
  17. package/public/assets/page.BZ_cZfHH.js +1 -0
  18. package/public/assets/page.B_eGnZ87.js +1 -0
  19. package/public/assets/page.BeKSN8d5.js +1 -0
  20. package/public/assets/page.BrKiDQss.js +1 -0
  21. package/public/assets/page.C5AiGRKH.js +1 -0
  22. package/public/assets/page.C8zACKBM.js +1 -0
  23. package/public/assets/page.CJLP_zJ7.js +1 -0
  24. package/public/assets/page.CSrGxSfO.js +1 -0
  25. package/public/assets/page.CZDbQNsn.js +1 -0
  26. package/public/assets/page.CdAUv8An.js +2 -0
  27. package/public/assets/page.Ce-kykhG.js +1 -0
  28. package/public/assets/page.Cff3z2jC.js +1 -0
  29. package/public/assets/page.CinUcqhI.js +23 -0
  30. package/public/assets/page.Cl4BXH-q.js +6 -0
  31. package/public/assets/page.Cq_2LsWB.js +2 -0
  32. package/public/assets/page.CudzOdqp.js +1 -0
  33. package/public/assets/page.CyElgSss.js +5 -0
  34. package/public/assets/page.D9BzRCo6.js +1 -0
  35. package/public/assets/page.DCn1Ir9m.js +1 -0
  36. package/public/assets/page.DE5z4wT0.js +7 -0
  37. package/public/assets/page.DOB5QnMp.js +1 -0
  38. package/public/assets/page.DPthpQJn.js +1 -0
  39. package/public/assets/page.DReP7jhx.js +1 -0
  40. package/public/assets/page.Dd_cOSCu.js +1 -0
  41. package/public/assets/page.DsT3YjTA.js +31 -0
  42. package/public/assets/page.Dw5UwTfS.js +1 -0
  43. package/public/assets/page.F2ClRfJd.js +1 -0
  44. package/public/assets/page.NPv76Z9i.js +64 -0
  45. package/public/assets/page.SGPaUlyq.js +1 -0
  46. package/public/assets/page.hoGohFe7.js +1 -0
  47. package/public/assets/page.mbHhHec7.js +5 -0
  48. package/public/assets/page.q9OWaAVY.js +1 -0
  49. package/public/assets/page.zM1PvG2b.js +1 -0
  50. package/public/index.html +4 -4
@@ -80,6 +80,12 @@ export async function exportDb() {
80
80
  proxyPools: db.all(`SELECT * FROM proxyPools`).map((r) => ({ ...parseJson(r.data, {}), id: r.id, isActive: r.isActive === 1, testStatus: r.testStatus, createdAt: r.createdAt, updatedAt: r.updatedAt })),
81
81
  apiKeys: db.all(`SELECT * FROM apiKeys`).map((r) => ({ id: r.id, key: r.key, name: r.name, machineId: r.machineId, isActive: r.isActive === 1, createdAt: r.createdAt })),
82
82
  combos: db.all(`SELECT * FROM combos`).map((r) => ({ id: r.id, name: r.name, kind: r.kind, models: parseJson(r.models, []), createdAt: r.createdAt, updatedAt: r.updatedAt })),
83
+ codebuddyAccounts: db.all(`SELECT * FROM codebuddyAccounts`).map((r) => ({
84
+ id: r.id, email: r.email, password: r.password, profileDir: r.profileDir,
85
+ fsmailAlias: r.fsmailAlias, signupMethod: r.signupMethod, apiKey: r.apiKey,
86
+ apiKeyStatus: r.apiKeyStatus, lastError: r.lastError, lastRunAt: r.lastRunAt,
87
+ createdAt: r.createdAt, provider: r.provider, canvaEnrolled: r.canvaEnrolled === 1,
88
+ })),
83
89
  modelAliases: {},
84
90
  customModels: [],
85
91
  mitmAlias: {},
@@ -108,6 +114,7 @@ export async function importDb(payload) {
108
114
  db.run(`DELETE FROM proxyPools`);
109
115
  db.run(`DELETE FROM apiKeys`);
110
116
  db.run(`DELETE FROM combos`);
117
+ db.run(`DELETE FROM codebuddyAccounts`);
111
118
  db.run(`DELETE FROM kv WHERE scope IN ('modelAliases', 'customModels', 'mitmAlias', 'pricing')`);
112
119
 
113
120
  // Settings
@@ -148,6 +155,12 @@ export async function importDb(payload) {
148
155
  [c.id, c.name, c.kind || null, stringifyJson(c.models || []), c.createdAt || new Date().toISOString(), c.updatedAt || new Date().toISOString()]
149
156
  );
150
157
  }
158
+ for (const a of payload.codebuddyAccounts || []) {
159
+ db.run(
160
+ `INSERT OR REPLACE INTO codebuddyAccounts(id, email, password, profileDir, fsmailAlias, signupMethod, apiKey, apiKeyStatus, lastError, lastRunAt, createdAt, provider, canvaEnrolled) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
161
+ [a.id, a.email || null, a.password || null, a.profileDir || null, a.fsmailAlias || null, a.signupMethod || 'google', a.apiKey || null, a.apiKeyStatus || 'pending', a.lastError || null, a.lastRunAt || null, a.createdAt || new Date().toISOString(), a.provider || 'codebuddy', a.canvaEnrolled === false ? 0 : 1]
162
+ );
163
+ }
151
164
  for (const [a, m] of Object.entries(payload.modelAliases || {})) {
152
165
  db.run(`INSERT OR REPLACE INTO kv(scope, key, value) VALUES('modelAliases', ?, ?)`, [a, stringifyJson(m)]);
153
166
  }
@@ -0,0 +1,136 @@
1
+ import { getAdapter, resetAdapter } from "./driver.js";
2
+ import { importDb } from "./index.js";
3
+ import { DATA_DIR } from "../dataDir.js";
4
+ import path from "path";
5
+ import fs from "fs";
6
+
7
+ // Streaming restore runner shared by POST /api/db and POST /api/db/restore-path.
8
+ // Sends SSE events: progress {percent,label}, log {message,level}, error, done.
9
+ export function runRestoreStream(res, body) {
10
+ res.writeHead(200, {
11
+ "Content-Type": "text/event-stream",
12
+ "Cache-Control": "no-cache, no-transform",
13
+ "Connection": "keep-alive",
14
+ "X-Accel-Buffering": "no"
15
+ });
16
+ const sseSend = (event, data) => {
17
+ res.write(`event: ${event}\n`);
18
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
19
+ };
20
+ const log = (msg, level = "info") => sseSend("log", { message: msg, level, ts: new Date().toISOString() });
21
+ const progress = (percent, label) => {
22
+ sseSend("progress", { percent, label });
23
+ log(label);
24
+ };
25
+
26
+ (async () => {
27
+ try {
28
+ progress(5, "Memvalidasi file backup...");
29
+ if (!body || body.signature !== "FUDROUTER_BACKUP") {
30
+ sseSend("error", { message: "Invalid backup file signature." });
31
+ return res.end();
32
+ }
33
+
34
+ const hasLogical = !!body.database;
35
+ if (hasLogical) {
36
+ progress(15, "Mengimpor data logical (provider, connections, proxy, api keys, combos, akun automation)...");
37
+ await importDb(body.database);
38
+
39
+ progress(40, "Flush WAL ke file database utama (checkpoint)...");
40
+ const db = await getAdapter();
41
+ if (db && typeof db.checkpoint === "function") {
42
+ try { db.checkpoint(); } catch (e) { log(`checkpoint gagal: ${e.message}`, "warn"); }
43
+ }
44
+ if (db && typeof db.exec === "function") {
45
+ try { db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch { /* ignore */ }
46
+ }
47
+
48
+ progress(50, "Menutup koneksi DB agar file WAL/SHM tidak terkunci...");
49
+ if (db && typeof db.exec === "function") {
50
+ try { db.exec("PRAGMA journal_mode=DELETE"); } catch { /* ignore */ }
51
+ }
52
+ if (db && typeof db.close === "function") {
53
+ try { db.close(); } catch { /* ignore */ }
54
+ }
55
+ resetAdapter();
56
+ } else {
57
+ progress(15, "Menutup koneksi database aktif (raw restore)...");
58
+ const db = await getAdapter();
59
+ if (db && typeof db.close === "function") {
60
+ try { db.close(); } catch { /* ignore */ }
61
+ }
62
+ resetAdapter();
63
+ }
64
+
65
+ progress(50, "Membersihkan file WAL/SHM lock...");
66
+ const walFile = path.join(DATA_DIR, "db", "data.sqlite-wal");
67
+ const shmFile = path.join(DATA_DIR, "db", "data.sqlite-shm");
68
+
69
+ const safeUnlink = (file) => {
70
+ if (!fs.existsSync(file)) return;
71
+ const attempts = process.platform === "win32" ? 12 : 3;
72
+ for (let i = 0; i < attempts; i++) {
73
+ try {
74
+ fs.unlinkSync(file);
75
+ return;
76
+ } catch (e) {
77
+ if (e.code === "EBUSY" || e.code === "EPERM" || e.code === "ETXTBSY") {
78
+ try {
79
+ const tmp = `${file}.old-${Date.now()}`;
80
+ fs.renameSync(file, tmp);
81
+ try { fs.unlinkSync(tmp); } catch { /* ignore */ }
82
+ return;
83
+ } catch {
84
+ try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250); } catch { /* noop */ }
85
+ continue;
86
+ }
87
+ }
88
+ throw e;
89
+ }
90
+ }
91
+ log(`Tidak bisa menghapus ${file} (terkunci) — melanjutkan.`, "warn");
92
+ };
93
+ safeUnlink(walFile);
94
+ safeUnlink(shmFile);
95
+
96
+ const filesToRestore = [
97
+ { key: "db/data.sqlite", path: path.join(DATA_DIR, "db", "data.sqlite") },
98
+ { key: "machine-id", path: path.join(DATA_DIR, "machine-id") },
99
+ { key: "jwt-secret", path: path.join(DATA_DIR, "jwt-secret") },
100
+ { key: "auth/cli-secret", path: path.join(DATA_DIR, "auth", "cli-secret") }
101
+ ];
102
+
103
+ let idx = 0;
104
+ const total = filesToRestore.length;
105
+ for (const item of filesToRestore) {
106
+ if (hasLogical && item.key === "db/data.sqlite") {
107
+ idx++;
108
+ continue;
109
+ }
110
+ const b64Data = body.files?.[item.key];
111
+ const pct = 60 + Math.round((idx / total) * 35);
112
+ if (b64Data) {
113
+ progress(pct, `Menimpa file: ${item.key} ...`);
114
+ const dir = path.dirname(item.path);
115
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
116
+ fs.writeFileSync(item.path, Buffer.from(b64Data, "base64"));
117
+ log(`✓ Di-timpa: ${item.key}`, "info");
118
+ } else {
119
+ log(`Lewati (tidak ada di backup): ${item.key}`, "warn");
120
+ }
121
+ idx++;
122
+ }
123
+
124
+ progress(100, "Restore selesai. Server akan merestart...");
125
+ sseSend("done", { success: true, message: "Restore berhasil. Server merestart..." });
126
+
127
+ setTimeout(() => {
128
+ try { process.exit(0); } catch { /* ignore */ }
129
+ }, 1200);
130
+ } catch (error) {
131
+ console.error("Restore error:", error);
132
+ sseSend("error", { message: "Restore gagal: " + (error?.message || error) });
133
+ res.end();
134
+ }
135
+ })();
136
+ }
@@ -0,0 +1,40 @@
1
+ import { getAdapter } from "../../../lib/db/driver.js";
2
+ import { DATA_DIR } from "../../../lib/dataDir.js";
3
+ import path from "path";
4
+ import fs from "fs";
5
+ export const dynamic = "force-dynamic";
6
+ // GET /api/db/raw - Backup the raw SQLite database file (copy as-is)
7
+ export async function GET(req, res) {
8
+ try {
9
+ const db = await getAdapter();
10
+ // Flush WAL into the main file so the copied .sqlite is self-contained.
11
+ if (db && typeof db.checkpoint === "function") {
12
+ try {
13
+ db.checkpoint();
14
+ }
15
+ catch { /* ignore */ }
16
+ }
17
+ if (db && typeof db.exec === "function") {
18
+ try {
19
+ db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
20
+ }
21
+ catch { /* ignore */ }
22
+ }
23
+ const sqlitePath = path.join(DATA_DIR, "db", "data.sqlite");
24
+ if (!fs.existsSync(sqlitePath)) {
25
+ return res.status(404).json({ error: "Database file not found." });
26
+ }
27
+ res.setHeader("Content-Disposition", 'attachment; filename="fsrouter-db-raw.sqlite"');
28
+ res.setHeader("Content-Type", "application/octet-stream");
29
+ const stat = fs.statSync(sqlitePath);
30
+ res.setHeader("Content-Length", stat.size);
31
+ const stream = fs.createReadStream(sqlitePath);
32
+ stream.on("error", (e) => { console.error("Raw backup stream error:", e); });
33
+ stream.pipe(res);
34
+ }
35
+ catch (error) {
36
+ console.error("Raw backup error:", error);
37
+ if (!res.headersSent)
38
+ res.status(500).json({ error: "Failed to generate raw backup: " + error.message });
39
+ }
40
+ }
@@ -0,0 +1,28 @@
1
+ import fs from "fs";
2
+ import { runRestoreStream } from "../../../lib/db/restoreRunner.js";
3
+ export const dynamic = "force-dynamic";
4
+ // POST /api/db/restore-path - Restore from a file path on the server itself
5
+ // (e.g. user pasted C:\Users\fud\backups\fsrouter-backup.fud, or a raw .sqlite)
6
+ export async function POST(req, res) {
7
+ const { path: filePath } = req.body || {};
8
+ if (!filePath || typeof filePath !== "string") {
9
+ return res.status(400).json({ error: "Field 'path' wajib diisi." });
10
+ }
11
+ if (!fs.existsSync(filePath)) {
12
+ return res.status(404).json({ error: `File tidak ditemukan: ${filePath}` });
13
+ }
14
+ let body;
15
+ try {
16
+ if (filePath.endsWith(".sqlite")) {
17
+ const b64 = fs.readFileSync(filePath).toString("base64");
18
+ body = { signature: "FUDROUTER_BACKUP", version: 1, files: { "db/data.sqlite": b64 } };
19
+ }
20
+ else {
21
+ body = JSON.parse(fs.readFileSync(filePath, "utf-8"));
22
+ }
23
+ }
24
+ catch (e) {
25
+ return res.status(400).json({ error: "Gagal membaca/mem-parse file: " + e.message });
26
+ }
27
+ runRestoreStream(res, body);
28
+ }
@@ -1,8 +1,5 @@
1
- import { getAdapter, resetAdapter } from "../../lib/db/driver.js";
2
- import { exportDb, importDb } from "../../lib/db/index.js";
3
- import { DATA_DIR } from "../../lib/dataDir.js";
4
- import path from "path";
5
- import fs from "fs";
1
+ import { exportDb } from "../../lib/db/index.js";
2
+ import { runRestoreStream } from "../../lib/db/restoreRunner.js";
6
3
  export const dynamic = "force-dynamic";
7
4
  // GET /api/db - Backup database & secrets
8
5
  export async function GET(req, res) {
@@ -55,176 +52,7 @@ export async function GET(req, res) {
55
52
  return res.status(500).json({ error: "Failed to generate backup: " + error.message });
56
53
  }
57
54
  }
58
- // ─── SSE helpers for streaming restore progress ───────────────────────────────
59
- function sseInit(res) {
60
- res.writeHead(200, {
61
- "Content-Type": "text/event-stream",
62
- "Cache-Control": "no-cache, no-transform",
63
- "Connection": "keep-alive",
64
- "X-Accel-Buffering": "no"
65
- });
66
- }
67
- function sseSend(res, event, data) {
68
- res.write(`event: ${event}\n`);
69
- res.write(`data: ${JSON.stringify(data)}\n\n`);
70
- }
71
55
  // POST /api/db - Restore database & secrets (streaming progress)
72
56
  export async function POST_handler(req, res) {
73
- // Switch to SSE immediately so the client sees progress from the start.
74
- sseInit(res);
75
- const log = (msg, level = "info") => sseSend(res, "log", { message: msg, level, ts: new Date().toISOString() });
76
- const progress = (percent, label) => {
77
- sseSend(res, "progress", { percent, label });
78
- log(label);
79
- };
80
- // Run restore asynchronously; don't await the whole thing on the request.
81
- (async () => {
82
- try {
83
- const body = req.body;
84
- progress(5, "Memvalidasi file backup...");
85
- if (!body || body.signature !== "FUDROUTER_BACKUP") {
86
- sseSend(res, "error", { message: "Invalid backup file signature." });
87
- return res.end();
88
- }
89
- // Prefer logical import: raw SQLite replacement can lose rows when WAL/driver
90
- // state differs between machines. Keep raw-file restore below for old backups.
91
- const hasLogical = !!body.database;
92
- if (hasLogical) {
93
- progress(15, "Mengimpor data logical (provider, connections, proxy, api keys, combos)...");
94
- // Import on the live connection.
95
- await importDb(body.database);
96
- progress(40, "Flush WAL ke file database utama (checkpoint)...");
97
- const db = await getAdapter();
98
- if (db && typeof db.checkpoint === "function") {
99
- try {
100
- db.checkpoint();
101
- }
102
- catch (e) {
103
- log(`checkpoint gagal: ${e.message}`, "warn");
104
- }
105
- }
106
- if (db && typeof db.exec === "function") {
107
- try {
108
- db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
109
- }
110
- catch { /* ignore */ }
111
- }
112
- progress(50, "Menutup koneksi DB agar file WAL/SHM tidak terkunci...");
113
- // Switch journal mode to DELETE so SQLite folds the WAL back into the
114
- // main db file and deletes -wal/-shm itself — avoids leaving a locked
115
- // WAL that Windows can't unlink (which previously dropped restored rows).
116
- if (db && typeof db.exec === "function") {
117
- try {
118
- db.exec("PRAGMA journal_mode=DELETE");
119
- }
120
- catch { /* ignore */ }
121
- }
122
- // Close the connection so Windows releases the -wal/-shm file locks.
123
- // Logical data is already flushed to the main DB via the checkpoint above.
124
- if (db && typeof db.close === "function") {
125
- try {
126
- db.close();
127
- }
128
- catch { /* ignore */ }
129
- }
130
- // Clear the cached instance so getAdapter() re-opens fresh on next boot.
131
- resetAdapter();
132
- }
133
- else {
134
- progress(15, "Menutup koneksi database aktif (raw restore)...");
135
- const db = await getAdapter();
136
- if (db && typeof db.close === "function") {
137
- try {
138
- db.close();
139
- }
140
- catch { /* ignore */ }
141
- }
142
- // Reset cached instance so the next getAdapter() re-opens a fresh connection.
143
- resetAdapter();
144
- }
145
- // 2. Remove active -wal and -shm files to prevent corruption/mismatch.
146
- progress(50, "Membersihkan file WAL/SHM lock...");
147
- const walFile = path.join(DATA_DIR, "db", "data.sqlite-wal");
148
- const shmFile = path.join(DATA_DIR, "db", "data.sqlite-shm");
149
- const safeUnlink = (file) => {
150
- if (!fs.existsSync(file))
151
- return;
152
- const attempts = process.platform === "win32" ? 12 : 3;
153
- for (let i = 0; i < attempts; i++) {
154
- try {
155
- fs.unlinkSync(file);
156
- return;
157
- }
158
- catch (e) {
159
- if (e.code === "EBUSY" || e.code === "EPERM" || e.code === "ETXTBSY") {
160
- try {
161
- const tmp = `${file}.old-${Date.now()}`;
162
- fs.renameSync(file, tmp);
163
- try {
164
- fs.unlinkSync(tmp);
165
- }
166
- catch { /* ignore */ }
167
- return;
168
- }
169
- catch {
170
- try {
171
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250);
172
- }
173
- catch { /* noop */ }
174
- continue;
175
- }
176
- }
177
- throw e;
178
- }
179
- }
180
- log(`Tidak bisa menghapus ${file} (terkunci) — melanjutkan.`, "warn");
181
- };
182
- safeUnlink(walFile);
183
- safeUnlink(shmFile);
184
- // 3. Restore secrets/raw DB only for legacy backups. Do not overwrite the
185
- // logical import with a stale/incompatible SQLite file.
186
- const filesToRestore = [
187
- { key: "db/data.sqlite", path: path.join(DATA_DIR, "db", "data.sqlite") },
188
- { key: "machine-id", path: path.join(DATA_DIR, "machine-id") },
189
- { key: "jwt-secret", path: path.join(DATA_DIR, "jwt-secret") },
190
- { key: "auth/cli-secret", path: path.join(DATA_DIR, "auth", "cli-secret") }
191
- ];
192
- let idx = 0;
193
- const total = filesToRestore.length;
194
- for (const item of filesToRestore) {
195
- if (hasLogical && item.key === "db/data.sqlite") {
196
- idx++;
197
- continue; // logical import already applied the DB
198
- }
199
- const b64Data = body.files?.[item.key];
200
- const pct = 60 + Math.round((idx / total) * 35);
201
- if (b64Data) {
202
- progress(pct, `Menimpa file: ${item.key} ...`);
203
- const dir = path.dirname(item.path);
204
- if (!fs.existsSync(dir))
205
- fs.mkdirSync(dir, { recursive: true });
206
- fs.writeFileSync(item.path, Buffer.from(b64Data, "base64"));
207
- log(`✓ Di-timpa: ${item.key}`, "info");
208
- }
209
- else {
210
- log(`Lewati (tidak ada di backup): ${item.key}`, "warn");
211
- }
212
- idx++;
213
- }
214
- progress(100, "Restore selesai. Server akan merestart...");
215
- sseSend(res, "done", { success: true, message: "Restore berhasil. Server merestart..." });
216
- // 4. Shut down process so PM2 restarts it (applies restored data cleanly)
217
- setTimeout(() => {
218
- try {
219
- process.exit(0);
220
- }
221
- catch { /* ignore */ }
222
- }, 1200);
223
- }
224
- catch (error) {
225
- console.error("Restore error:", error);
226
- sseSend(res, "error", { message: "Restore gagal: " + (error?.message || error) });
227
- res.end();
228
- }
229
- })();
57
+ runRestoreStream(res, req.body);
230
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fudrouter/fsrouter",
3
- "version": "0.6.104",
3
+ "version": "0.6.106",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "9Router v2 — Express Backend (API, SSE, DB, Auth, MITM)",
@@ -21,7 +21,6 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "bcryptjs": "^3.0.3",
24
- "better-sqlite3": "^12.11.1",
25
24
  "confbox": "^0.2.4",
26
25
  "cookie-parser": "^1.4.7",
27
26
  "cors": "^2.8.5",
@@ -44,5 +43,8 @@
44
43
  "@/*": "./dist/*",
45
44
  "open-sse": "./open-sse/index.js",
46
45
  "open-sse/*": "./open-sse/*"
46
+ },
47
+ "optionalDependencies": {
48
+ "better-sqlite3": "^12.11.1"
47
49
  }
48
50
  }