@seip/blue-bird 1.0.2 → 1.1.1

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.
package/core/cli/init.js CHANGED
@@ -28,8 +28,9 @@ class ProjectInit {
28
28
  let title = "Blue-Bird";
29
29
  let port = 3000;
30
30
  let appUrl = "http://localhost:3000";
31
- let dbType = "none";
31
+ let dbType = "sqlite";
32
32
  let dbName = "blue_bird";
33
+ let dbFile = "database/blue_bird.db";
33
34
  let dbUser = "root";
34
35
  let dbPassword = "root";
35
36
  let dbPort = 3306;
@@ -55,11 +56,17 @@ class ProjectInit {
55
56
  appUrl = await ask("Application URL", defaultAppUrl);
56
57
 
57
58
  const dbTypeAns = await ask(
58
- "Which database do you want to configure? (none / mysql / postgres)",
59
- "none",
59
+ "Which database do you want to configure? (sqlite / mysql / postgres / none)",
60
+ "sqlite",
60
61
  );
61
62
  const cleanDbTypeAns = dbTypeAns.toLowerCase().trim();
62
63
  if (
64
+ cleanDbTypeAns === "sqlite" ||
65
+ cleanDbTypeAns === "sql" ||
66
+ cleanDbTypeAns === "sqlite3"
67
+ ) {
68
+ dbType = "sqlite";
69
+ } else if (
63
70
  cleanDbTypeAns === "postgres" ||
64
71
  cleanDbTypeAns === "postgresql" ||
65
72
  cleanDbTypeAns === "pg" ||
@@ -72,7 +79,15 @@ class ProjectInit {
72
79
  dbType = "none";
73
80
  }
74
81
 
75
- if (dbType !== "none") {
82
+ if (dbType === "sqlite") {
83
+ dbName = await ask("Database Name", dbName);
84
+ const defaultDbFile = `database/${dbName}.db`;
85
+ dbFile = await ask("Database File Path", defaultDbFile);
86
+ const dbDir = path.dirname(path.join(this.appDir, dbFile));
87
+ if (!fs.existsSync(dbDir)) {
88
+ fs.mkdirSync(dbDir, { recursive: true });
89
+ }
90
+ } else if (dbType !== "none") {
76
91
  dbName = await ask("Database Name", dbName);
77
92
  dbUser = await ask(
78
93
  "Database User",
@@ -103,8 +118,6 @@ class ProjectInit {
103
118
  "docker",
104
119
  ".env_example",
105
120
  "AGENTS.md",
106
- "index.js",
107
- ".gitignore"
108
121
  ];
109
122
 
110
123
  try {
@@ -128,12 +141,32 @@ class ProjectInit {
128
141
  }
129
142
  });
130
143
 
144
+ const gitignoreContent = `node_modules\nlogs\n.env\npackage-lock.json\n*.db\n*.sqlite\n*.sqlite-wal\n*.sqlite-shm\n*.db-wal\n*.db-shm\ntest/\n\nbackups/*.sql\nbackups/*.db\n`;
145
+ const gitignoreDest = path.join(this.appDir, ".gitignore");
146
+ const gitignoreSrc = path.join(this.sourceDir, ".gitignore");
147
+
148
+ if (!fs.existsSync(gitignoreDest)) {
149
+ if (fs.existsSync(gitignoreSrc)) {
150
+ fs.copyFileSync(gitignoreSrc, gitignoreDest);
151
+ console.log(chalk.green("[OK] Copied .gitignore to root."));
152
+ } else {
153
+ fs.writeFileSync(gitignoreDest, gitignoreContent, "utf-8");
154
+ console.log(chalk.green("[OK] Created .gitignore file."));
155
+ }
156
+ } else {
157
+ console.log(
158
+ chalk.yellow("[SKIP] .gitignore already exists, skipping."),
159
+ );
160
+ }
161
+
131
162
  const composeTemplateName =
132
163
  dbType === "postgres"
133
164
  ? "docker-compose.postgres.yml"
134
165
  : dbType === "mysql"
135
166
  ? "docker-compose.mysql.yml"
136
- : "docker-compose.none.yml";
167
+ : dbType === "sqlite"
168
+ ? "docker-compose.sqlite.yml"
169
+ : "docker-compose.none.yml";
137
170
  const composeSrc = path.join(
138
171
  this.sourceDir,
139
172
  "docker",
@@ -166,8 +199,15 @@ class ProjectInit {
166
199
  let envContent = fs.readFileSync(envExamplePath, "utf-8");
167
200
 
168
201
  const jwtSecret = crypto.randomBytes(32).toString("hex");
202
+ const composeProjectName =
203
+ title
204
+ .toLowerCase()
205
+ .trim()
206
+ .replace(/\s+/g, "-")
207
+ .replace(/[^a-z0-9_-]/g, "") || "blue-bird";
169
208
 
170
209
  const updates = {
210
+ COMPOSE_PROJECT_NAME: composeProjectName,
171
211
  TITLE: title,
172
212
  PORT: port,
173
213
  APP_URL: appUrl,
@@ -175,13 +215,19 @@ class ProjectInit {
175
215
  DB_TYPE: dbType,
176
216
  };
177
217
 
178
- if (dbType === "mysql") {
218
+ if (dbType === "sqlite") {
219
+ updates.DB_TYPE = "sqlite";
220
+ updates.DB_FILE = dbFile;
221
+ updates.DATABASE_URL = `sqlite:${dbFile}`;
222
+ } else if (dbType === "mysql") {
223
+ updates.DB_TYPE = "mysql";
179
224
  updates.DB_NAME = dbName;
180
225
  updates.DB_USER = dbUser;
181
226
  updates.DB_PASSWORD = dbPassword;
182
227
  updates.DB_PORT = dbPort;
183
228
  updates.DATABASE_URL = `mysql://${dbUser}:${dbPassword}@localhost:${dbPort}/${dbName}`;
184
229
  } else if (dbType === "postgres") {
230
+ updates.DB_TYPE = "postgres";
185
231
  updates.DB_NAME = dbName;
186
232
  updates.DB_USER = dbUser;
187
233
  updates.DB_PASSWORD = dbPassword;
@@ -190,10 +236,14 @@ class ProjectInit {
190
236
  }
191
237
 
192
238
  const lines = envContent.split(/\r?\n/);
239
+ let foundComposeProjectName = false;
193
240
  const updatedLines = lines.map((line) => {
194
241
  const match = line.match(/^([A-Z_]+)=(.+)/);
195
242
  if (match) {
196
243
  const key = match[1];
244
+ if (key === "COMPOSE_PROJECT_NAME") {
245
+ foundComposeProjectName = true;
246
+ }
197
247
  if (updates[key] !== undefined) {
198
248
  const value = updates[key];
199
249
  if (typeof value === "string" && !value.startsWith('"')) {
@@ -204,6 +254,21 @@ class ProjectInit {
204
254
  }
205
255
  return line;
206
256
  });
257
+
258
+ if (!foundComposeProjectName && updates.COMPOSE_PROJECT_NAME) {
259
+ const titleIdx = updatedLines.findIndex((l) => l.startsWith("TITLE="));
260
+ if (titleIdx !== -1) {
261
+ updatedLines.splice(
262
+ titleIdx,
263
+ 0,
264
+ `COMPOSE_PROJECT_NAME="${updates.COMPOSE_PROJECT_NAME}"`,
265
+ );
266
+ } else {
267
+ updatedLines.push(
268
+ `COMPOSE_PROJECT_NAME="${updates.COMPOSE_PROJECT_NAME}"`,
269
+ );
270
+ }
271
+ }
207
272
  envContent = updatedLines.join("\n");
208
273
 
209
274
  fs.writeFileSync(envPath, envContent, "utf-8");
@@ -218,7 +283,9 @@ class ProjectInit {
218
283
  );
219
284
  try {
220
285
  let packagesToInstall = ["redis"];
221
- if (dbType === "postgres") {
286
+ if (dbType === "sqlite") {
287
+ packagesToInstall.push("better-sqlite3");
288
+ } else if (dbType === "postgres") {
222
289
  packagesToInstall.push("pg");
223
290
  } else {
224
291
  packagesToInstall.push("mysql2");
@@ -323,6 +390,63 @@ class ProjectInit {
323
390
  }
324
391
  }
325
392
 
393
+ /**
394
+ * Handles adding on-demand modules and dependencies.
395
+ * @param {string} [feature]
396
+ */
397
+ function addCommand(feature) {
398
+ if (!feature) {
399
+ console.log(chalk.yellow("Usage: npx blue-bird add <feature>"));
400
+ console.log("Available features to add:");
401
+ console.log(" upload | multer - Installs multer for file uploads");
402
+ console.log(" ws | websocket - Installs ws for WebSockets");
403
+ console.log(" redis - Installs redis for distributed caching and sessions");
404
+ console.log(" sqlite - Installs better-sqlite3 for local SQLite database");
405
+ console.log(" mysql - Installs mysql2 for MySQL database");
406
+ console.log(" postgres | pg - Installs pg for PostgreSQL database");
407
+ console.log(" bcrypt - Installs bcrypt for password hashing");
408
+ console.log(" swagger - Installs swagger-ui-express");
409
+ return;
410
+ }
411
+
412
+ const cleanFeature = feature.toLowerCase().trim();
413
+ const packageMap = {
414
+ upload: "multer",
415
+ multer: "multer",
416
+ ws: "ws",
417
+ websocket: "ws",
418
+ websockets: "ws",
419
+ redis: "redis",
420
+ sqlite: "better-sqlite3",
421
+ "better-sqlite3": "better-sqlite3",
422
+ mysql: "mysql2",
423
+ mysql2: "mysql2",
424
+ postgres: "pg",
425
+ postgresql: "pg",
426
+ pg: "pg",
427
+ bcrypt: "bcrypt",
428
+ swagger: "swagger-ui-express",
429
+ };
430
+
431
+ const pkgName = packageMap[cleanFeature];
432
+ if (!pkgName) {
433
+ console.error(chalk.red(`[ERROR] Unknown feature '${feature}'.`));
434
+ console.log("Available features: upload, ws, redis, sqlite, mysql, postgres, bcrypt, swagger");
435
+ return;
436
+ }
437
+
438
+ console.log(chalk.cyan(`[INFO] Installing ${pkgName}...`));
439
+ try {
440
+ execSync(`npm install ${pkgName}`, {
441
+ stdio: "inherit",
442
+ cwd: process.cwd(),
443
+ });
444
+ console.log(chalk.green(`[OK] Successfully installed ${pkgName}.`));
445
+ } catch (err) {
446
+ console.error(chalk.red(`[ERROR] Failed to install ${pkgName}:`), err.message);
447
+ }
448
+ }
449
+
326
450
  const initializer = new ProjectInit();
327
451
 
328
452
  const args = process.argv.slice(2);
@@ -331,4 +455,6 @@ const command = args[0];
331
455
  if (command === "route") import("./route.js");
332
456
  else if (command === "swagger-install") import("./swagger.js");
333
457
  else if (command === "docker") import("./docker.js");
458
+ else if (command === "add") addCommand(args[1]);
334
459
  else initializer.run();
460
+
package/core/database.js CHANGED
@@ -1,3 +1,5 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
1
3
  import crypto from "node:crypto";
2
4
  import { getRedisClient } from "./cache.js";
3
5
 
@@ -15,14 +17,20 @@ if (
15
17
  DB_TYPE = "postgres";
16
18
  } else if (process.env.DATABASE_URL.startsWith("mysql://")) {
17
19
  DB_TYPE = "mysql";
20
+ } else if (
21
+ process.env.DATABASE_URL.startsWith("sqlite://") ||
22
+ process.env.DATABASE_URL.startsWith("sqlite:")
23
+ ) {
24
+ DB_TYPE = "sqlite";
18
25
  }
19
26
  }
20
27
  if (!DB_TYPE) {
21
- DB_TYPE = "mysql";
28
+ DB_TYPE = "sqlite";
22
29
  }
23
30
 
24
31
  let mysqlPromise = null;
25
32
  let pgPromise = null;
33
+ let sqlitePromise = null;
26
34
 
27
35
  if (DB_TYPE === "postgres") {
28
36
  try {
@@ -40,23 +48,79 @@ if (DB_TYPE === "postgres") {
40
48
  "[DATABASE ERROR] mysql2 package is not installed. Database wrapper is disabled.",
41
49
  );
42
50
  }
51
+ } else if (DB_TYPE === "sqlite") {
52
+ try {
53
+ sqlitePromise = await import("better-sqlite3");
54
+ } catch (err) {
55
+ console.error(
56
+ "[DATABASE ERROR] better-sqlite3 package is not installed. Database wrapper is disabled.",
57
+ );
58
+ }
43
59
  }
44
60
 
45
61
  /**
46
- * Database class wrapping mysql2 and pg with reconnection retries, connection pooling, and query caching.
62
+ * Database class wrapping better-sqlite3, mysql2, and pg with reconnection retries, connection pooling, and query caching.
47
63
  */
48
64
  class Database {
49
65
  /**
50
66
  * Initializes config from DATABASE_URL or DB_* environment variables.
51
- * For default Database use .env DB_HOST, DB_USER, DB_PASSWORD...
52
- * @param {number} [connectionLimit=10] - Maximum number of connections in the pool.
53
- * @param {number} [queueLimit=0] - Maximum number of queued connections.
54
- * @param {Object} [config={}] - Additional configuration options: DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT, DB_TYPE.
67
+ * For default Database use .env DB_HOST, DB_USER, DB_PASSWORD... or DB_FILE for SQLite.
68
+ * @param {number} [connectionLimit=10] - Maximum number of connections in the pool (MySQL/Postgres).
69
+ * @param {number} [queueLimit=0] - Maximum number of queued connections (MySQL/Postgres).
70
+ * @param {Object} [config={}] - Additional configuration options: DB_FILE, DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT, DB_TYPE.
71
+ * @example const connection = new Database(10, 0, { DB_TYPE: "sqlite", DB_FILE: "database/blue_bird.db" });
55
72
  * @example const connection = new Database(10, 0, { DB_HOST: "localhost", DB_USER: "root", DB_PASSWORD: "password", DB_NAME: "blue_bird", DB_PORT: 3306, DB_TYPE: "mysql" });
56
73
  */
57
74
  constructor(connectionLimit = 10, queueLimit = 0, config = {}) {
58
75
  this.pool = null;
59
- this.type = DB_TYPE;
76
+ this.db = null;
77
+ this.type = config.DB_TYPE || DB_TYPE;
78
+
79
+ let defaultSqliteFile = process.env.DB_FILE || "database/blue_bird.db";
80
+ let busyTimeout = 5000;
81
+ let journalMode = "WAL";
82
+ let synchronous = "NORMAL";
83
+
84
+ if (process.env.DATABASE_URL && !process.env.DATABASE_URL.startsWith("#")) {
85
+ try {
86
+ if (
87
+ process.env.DATABASE_URL.startsWith("sqlite://") ||
88
+ process.env.DATABASE_URL.startsWith("sqlite:")
89
+ ) {
90
+ const rawUrl = process.env.DATABASE_URL;
91
+ const cleanUrl = rawUrl.replace(/^sqlite:\/\/|^sqlite:/, "");
92
+ const [filePath, queryStr] = cleanUrl.split("?");
93
+ if (filePath) {
94
+ defaultSqliteFile = filePath;
95
+ }
96
+ if (queryStr) {
97
+ const params = new URLSearchParams(queryStr);
98
+ if (params.has("busy_timeout")) {
99
+ busyTimeout =
100
+ parseInt(params.get("busy_timeout"), 10) || busyTimeout;
101
+ }
102
+ if (params.has("journal_mode")) {
103
+ journalMode = params.get("journal_mode").toUpperCase();
104
+ }
105
+ if (params.has("synchronous")) {
106
+ synchronous = params.get("synchronous").toUpperCase();
107
+ }
108
+ }
109
+ }
110
+ } catch (err) {
111
+ console.error(
112
+ "[DATABASE ERROR] Failed to parse SQLite DATABASE_URL:",
113
+ err.message,
114
+ );
115
+ }
116
+ }
117
+
118
+ this.sqliteConfig = {
119
+ filename: config.DB_FILE || defaultSqliteFile,
120
+ busyTimeout: config.busyTimeout || busyTimeout,
121
+ journalMode: config.journalMode || journalMode,
122
+ synchronous: config.synchronous || synchronous,
123
+ };
60
124
 
61
125
  this.config = {
62
126
  ...config,
@@ -101,13 +165,46 @@ class Database {
101
165
  }
102
166
 
103
167
  /**
104
- * Creates the database connection pool with 3 retry attempts on failure.
168
+ * Creates the database connection pool or SQLite instance with retries on failure.
105
169
  * @param {number} [retries=3] - Number of connection attempts.
106
- * @returns {Promise<boolean>} True if connection pool was created.
170
+ * @returns {Promise<boolean>} True if connection was created.
107
171
  */
108
172
  async init(retries = 3) {
109
- if (!mysqlPromise && !pgPromise) return false;
110
- if (this.pool) return true;
173
+ if (!mysqlPromise && !pgPromise && !sqlitePromise) return false;
174
+ if (this.pool || this.db) return true;
175
+
176
+ if (this.type === "sqlite" && sqlitePromise) {
177
+ try {
178
+ const BetterSqlite = sqlitePromise.default || sqlitePromise;
179
+ const dbFilePath = path.isAbsolute(this.sqliteConfig.filename)
180
+ ? this.sqliteConfig.filename
181
+ : path.resolve(process.cwd(), this.sqliteConfig.filename);
182
+
183
+ const dir = path.dirname(dbFilePath);
184
+ if (!fs.existsSync(dir)) {
185
+ fs.mkdirSync(dir, { recursive: true });
186
+ }
187
+
188
+ this.db = new BetterSqlite(dbFilePath, {
189
+ timeout: this.sqliteConfig.busyTimeout,
190
+ });
191
+
192
+ this.db.pragma(`journal_mode = ${this.sqliteConfig.journalMode}`);
193
+ this.db.pragma(`synchronous = ${this.sqliteConfig.synchronous}`);
194
+ this.db.pragma("foreign_keys = ON");
195
+ this.db.pragma(`busy_timeout = ${this.sqliteConfig.busyTimeout}`);
196
+ this.db.pragma("temp_store = MEMORY");
197
+
198
+ return true;
199
+ } catch (err) {
200
+ console.error(
201
+ "[DATABASE ERROR] Failed to initialize SQLite database:",
202
+ err.message,
203
+ );
204
+ this.db = null;
205
+ return false;
206
+ }
207
+ }
111
208
 
112
209
  for (let attempt = 1; attempt <= retries; attempt++) {
113
210
  try {
@@ -138,7 +235,7 @@ class Database {
138
235
 
139
236
  /**
140
237
  * Runs a SQL query with parameters and formatting options.
141
- * Supports both MySQL and PostgreSQL (converting ? to $1, $2 for Postgres automatically).
238
+ * Supports SQLite, MySQL, and PostgreSQL (converting ? to $1, $2 for Postgres automatically).
142
239
  *
143
240
  * @param {string} sql - SQL query string.
144
241
  * @param {Array} [params=[]] - Query parameter array.
@@ -150,8 +247,8 @@ class Database {
150
247
  * @example const insert_id = await connection.query("INSERT INTO users (name, email, password) VALUES (?, ?, ?)", ["John Doe", "john@example.com", "password"]);
151
248
  */
152
249
  async query(sql, params = [], options = {}) {
153
- if (!mysqlPromise && !pgPromise) return false;
154
- if (!this.pool) {
250
+ if (!mysqlPromise && !pgPromise && !sqlitePromise) return false;
251
+ if (!this.pool && !this.db) {
155
252
  const initialized = await this.init();
156
253
  if (!initialized) return false;
157
254
  }
@@ -159,8 +256,8 @@ class Database {
159
256
  const queryOptions =
160
257
  typeof options === "string" ? { [options]: true } : options;
161
258
  const cleanSql = sql.trim();
162
- const isSelect = cleanSql.toLowerCase().startsWith("select");
163
- const isInsert = cleanSql.toLowerCase().startsWith("insert");
259
+ const isSelect = /^(select|pragma|explain)/i.test(cleanSql);
260
+ const isInsert = /^insert/i.test(cleanSql);
164
261
 
165
262
  const redisClient = getRedisClient();
166
263
  let cacheKey = null;
@@ -201,7 +298,42 @@ class Database {
201
298
  }
202
299
 
203
300
  try {
204
- if (this.type === "postgres" && pgPromise) {
301
+ if (this.type === "sqlite" && this.db) {
302
+ const stmt = this.db.prepare(cleanSql);
303
+
304
+ if (isSelect) {
305
+ if (queryOptions.return_row) {
306
+ const row = stmt.get(...params);
307
+ const result = row || null;
308
+ if (cacheKey && queryOptions.cache && redisClient) {
309
+ await redisClient
310
+ .set(cacheKey, JSON.stringify(result), {
311
+ EX: parseInt(queryOptions.cache),
312
+ })
313
+ .catch(() => {});
314
+ }
315
+ return result;
316
+ }
317
+
318
+ const rows = stmt.all(...params);
319
+ if (cacheKey && queryOptions.cache && redisClient) {
320
+ await redisClient
321
+ .set(cacheKey, JSON.stringify(rows), {
322
+ EX: parseInt(queryOptions.cache),
323
+ })
324
+ .catch(() => {});
325
+ }
326
+ return rows;
327
+ }
328
+
329
+ if (isInsert) {
330
+ const info = stmt.run(...params);
331
+ return Number(info.lastInsertRowid);
332
+ }
333
+
334
+ const info = stmt.run(...params);
335
+ return info.changes;
336
+ } else if (this.type === "postgres" && pgPromise) {
205
337
  let paramIndex = 1;
206
338
  const pgSql = cleanSql.replace(/\?/g, () => `$${paramIndex++}`);
207
339
  const res = await this.pool.query(pgSql, params);
@@ -305,13 +437,48 @@ class Database {
305
437
  * });
306
438
  */
307
439
  async transaction(callback) {
308
- if (!mysqlPromise && !pgPromise) throw new Error("[DATABASE ERROR] No database driver available.");
309
- if (!this.pool) {
440
+ if (!mysqlPromise && !pgPromise && !sqlitePromise) throw new Error("[DATABASE ERROR] No database driver available.");
441
+ if (!this.pool && !this.db) {
310
442
  const initialized = await this.init();
311
443
  if (!initialized) throw new Error("[DATABASE ERROR] Failed to initialize database pool.");
312
444
  }
313
445
 
314
- if (this.type === "postgres" && pgPromise) {
446
+ if (this.type === "sqlite" && this.db) {
447
+ this.db.exec("BEGIN IMMEDIATE");
448
+ try {
449
+ const tx = {
450
+ query: async (sql, params = [], options = {}) => {
451
+ const queryOptions = typeof options === "string" ? { [options]: true } : options;
452
+ const cleanSql = sql.trim();
453
+ const isSelect = /^(select|pragma|explain)/i.test(cleanSql);
454
+ const isInsert = /^insert/i.test(cleanSql);
455
+
456
+ const stmt = this.db.prepare(cleanSql);
457
+ if (isSelect) {
458
+ if (queryOptions.return_row) {
459
+ const row = stmt.get(...params);
460
+ return row || null;
461
+ }
462
+ return stmt.all(...params);
463
+ }
464
+ if (isInsert) {
465
+ const info = stmt.run(...params);
466
+ return Number(info.lastInsertRowid);
467
+ }
468
+ const info = stmt.run(...params);
469
+ return info.changes;
470
+ }
471
+ };
472
+
473
+ const result = await callback(tx);
474
+ this.db.exec("COMMIT");
475
+ return result;
476
+ } catch (err) {
477
+ this.db.exec("ROLLBACK");
478
+ console.error("[DATABASE ERROR] Transaction rolled back:", err.message);
479
+ throw err;
480
+ }
481
+ } else if (this.type === "postgres" && pgPromise) {
315
482
  const client = await this.pool.connect();
316
483
  try {
317
484
  await client.query("BEGIN");
@@ -394,6 +561,24 @@ class Database {
394
561
  async executeTransaction(callback) {
395
562
  return this.transaction(callback);
396
563
  }
564
+
565
+ /**
566
+ * Closes the database connection pool or SQLite instance.
567
+ */
568
+ async close() {
569
+ if (this.db) {
570
+ this.db.close();
571
+ this.db = null;
572
+ }
573
+ if (this.pool) {
574
+ if (typeof this.pool.end === "function") {
575
+ await this.pool.end();
576
+ }
577
+ this.pool = null;
578
+ }
579
+ }
397
580
  }
398
581
 
399
582
  export { Database, DB_TYPE };
583
+
584
+