react-native-sqlite-mcp 1.0.1 → 1.0.2

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/dist/db.js CHANGED
@@ -1,89 +1,52 @@
1
- import sqlite3 from "sqlite3";
1
+ import initSqlJs from "sql.js";
2
+ import fs from "fs";
2
3
  import { logger } from "./logger.js";
3
- /**
4
- * Lightweight sqlite3 wrapper with Promise-based API.
5
- */
6
- export class Database {
7
- db;
8
- constructor(filename) {
9
- this.db = new sqlite3.Database(filename);
10
- }
11
- all(sql, params = []) {
12
- return new Promise((resolve, reject) => {
13
- this.db.all(sql, params, (err, rows) => {
14
- if (err)
15
- reject(err);
16
- else
17
- resolve(rows);
18
- });
19
- });
20
- }
21
- get(sql, params = []) {
22
- return new Promise((resolve, reject) => {
23
- this.db.get(sql, params, (err, row) => {
24
- if (err)
25
- reject(err);
26
- else
27
- resolve(row);
28
- });
29
- });
30
- }
31
- close() {
32
- return new Promise((resolve, reject) => {
33
- this.db.close((err) => {
34
- if (err)
35
- reject(err);
36
- else
37
- resolve();
38
- });
39
- });
4
+ let SQL = null;
5
+ async function getSqlJs() {
6
+ if (!SQL) {
7
+ SQL = await initSqlJs();
8
+ logger.debug("sql.js WASM engine initialized");
40
9
  }
10
+ return SQL;
41
11
  }
42
- const CACHE_TTL_MS = 60_000; // Close idle connections after 60s
12
+ const CACHE_TTL_MS = 60_000;
43
13
  const connectionCache = new Map();
44
- function getCachedDb(dbPath) {
14
+ async function getCachedDb(dbPath) {
45
15
  const existing = connectionCache.get(dbPath);
46
16
  if (existing) {
47
- // Refresh the idle timer
48
17
  clearTimeout(existing.timer);
49
18
  existing.lastUsed = Date.now();
50
19
  existing.timer = setTimeout(() => evictConnection(dbPath), CACHE_TTL_MS);
51
20
  return existing.db;
52
21
  }
53
- // Open a new connection
54
- const db = new Database(dbPath);
22
+ const sqlJs = await getSqlJs();
23
+ const buffer = fs.readFileSync(dbPath);
24
+ const db = new sqlJs.Database(buffer);
55
25
  const timer = setTimeout(() => evictConnection(dbPath), CACHE_TTL_MS);
56
- connectionCache.set(dbPath, { db, lastUsed: Date.now(), timer });
26
+ connectionCache.set(dbPath, { db, dbPath, lastUsed: Date.now(), timer });
57
27
  logger.debug(`Opened DB connection: ${dbPath}`);
58
28
  return db;
59
29
  }
60
- async function evictConnection(dbPath) {
30
+ function evictConnection(dbPath) {
61
31
  const entry = connectionCache.get(dbPath);
62
32
  if (!entry)
63
33
  return;
64
34
  connectionCache.delete(dbPath);
65
35
  try {
66
- await entry.db.close();
36
+ entry.db.close();
67
37
  logger.debug(`Closed idle DB connection: ${dbPath}`);
68
38
  }
69
39
  catch (e) {
70
40
  logger.warn(`Error closing DB: ${dbPath}`, { error: String(e) });
71
41
  }
72
42
  }
73
- /**
74
- * Close all cached connections. Called during graceful shutdown.
75
- */
76
43
  export async function closeAllConnections() {
77
44
  const paths = [...connectionCache.keys()];
78
45
  for (const p of paths) {
79
- await evictConnection(p);
46
+ evictConnection(p);
80
47
  }
81
48
  logger.info(`Closed ${paths.length} cached DB connection(s)`);
82
49
  }
83
- // ---------------------------------------------------------------------------
84
- // Public query API
85
- // ---------------------------------------------------------------------------
86
- /** Query timeout — prevents stuck queries from blocking everything */
87
50
  const QUERY_TIMEOUT_MS = 30_000;
88
51
  function withTimeout(promise, ms, label) {
89
52
  return new Promise((resolve, reject) => {
@@ -95,26 +58,31 @@ function withTimeout(promise, ms, label) {
95
58
  .catch((err) => { clearTimeout(timer); reject(err); });
96
59
  });
97
60
  }
98
- /**
99
- * Executes a simple query on the database.
100
- */
61
+ function runQuery(db, sql, params = []) {
62
+ const stmt = db.prepare(sql);
63
+ if (params.length > 0)
64
+ stmt.bind(params);
65
+ const results = [];
66
+ while (stmt.step()) {
67
+ results.push(stmt.getAsObject());
68
+ }
69
+ stmt.free();
70
+ return results;
71
+ }
101
72
  export async function queryDb(dbPath, sql, params = []) {
102
- const db = getCachedDb(dbPath);
103
- return withTimeout(db.all(sql, params), QUERY_TIMEOUT_MS, sql.slice(0, 80));
73
+ const db = await getCachedDb(dbPath);
74
+ return withTimeout(Promise.resolve(runQuery(db, sql, params)), QUERY_TIMEOUT_MS, sql.slice(0, 80));
104
75
  }
105
- /**
106
- * Returns a detailed schema of all tables in the database.
107
- */
108
76
  export async function inspectSchema(dbPath) {
109
- const db = getCachedDb(dbPath);
110
- const tables = await withTimeout(db.all("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"), QUERY_TIMEOUT_MS, "inspect_schema:tables");
77
+ const db = await getCachedDb(dbPath);
78
+ const tables = runQuery(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;");
111
79
  const schemaInfo = {};
112
80
  for (const table of tables) {
113
- const columns = await db.all(`PRAGMA table_info("${table.name}");`);
114
- const createSql = await db.get(`SELECT sql FROM sqlite_master WHERE type='table' AND name=?`, [table.name]);
81
+ const columns = runQuery(db, `PRAGMA table_info("${table.name}");`);
82
+ const createSqlRows = runQuery(db, `SELECT sql FROM sqlite_master WHERE type='table' AND name=?`, [table.name]);
115
83
  schemaInfo[table.name] = {
116
84
  columns,
117
- createSql: createSql?.sql
85
+ createSql: createSqlRows[0]?.sql
118
86
  };
119
87
  }
120
88
  return schemaInfo;
package/dist/locator.js CHANGED
@@ -49,7 +49,7 @@ export async function listDatabases(bundleId, targetPlatform) {
49
49
  }
50
50
  return results;
51
51
  }
52
- // Discover packages to scan
52
+ // if we have a specific bundleId-use it otherwise hunt
53
53
  let packagesToScan = [];
54
54
  if (bundleId) {
55
55
  packagesToScan = [bundleId];
@@ -76,12 +76,12 @@ export async function listDatabases(bundleId, targetPlatform) {
76
76
  try {
77
77
  await shell(`adb shell run-as ${pkg} ls -d ${baseDir}`, { timeout: 3_000, label: `adb-ls-${pkg}` });
78
78
  let foundFiles = [];
79
- // Find .db / .sqlite / .sqlite3 files recursively
79
+ // find .db / .sqlite / .sqlite3 files recursively
80
80
  const findOut = await shell(`adb shell "run-as ${pkg} find ${baseDir} -type f \\( -name \\"*.db\\" -o -name \\"*.sqlite\\" -o -name \\"*.sqlite3\\" \\)"`, { timeout: 8_000, ignoreErrors: true, label: `adb-find-${pkg}` });
81
81
  if (findOut) {
82
82
  foundFiles.push(...findOut.split('\n').map(l => l.trim().replace(/\r/g, '')).filter(Boolean));
83
83
  }
84
- // Also check for extensionless files in /databases
84
+ // also check for extensionless files in /databases
85
85
  const lsOut = await shell(`adb shell run-as ${pkg} ls -1p ${baseDir}/databases`, { timeout: 3_000, ignoreErrors: true, label: `adb-ls-dbs-${pkg}` });
86
86
  if (lsOut) {
87
87
  const lsFiles = lsOut.split('\n')
@@ -120,12 +120,6 @@ export async function listDatabases(bundleId, targetPlatform) {
120
120
  }
121
121
  return results;
122
122
  }
123
- /**
124
- * Finds the DB file based on platform and a provided/detected filename.
125
- * If dbNameGlob is empty/undefined, it will auto-select the first discovered database.
126
- * Prioritizes iOS (simctl), falls back to Android (adb).
127
- * Returns the local file path (either the iOS original or a pulled Android copy).
128
- */
129
123
  export async function syncDatabase(dbNameGlob, bundleId, targetPlatform) {
130
124
  const locations = await listDatabases(bundleId, targetPlatform);
131
125
  if (locations.length === 0) {
@@ -176,7 +170,6 @@ export async function syncDatabase(dbNameGlob, bundleId, targetPlatform) {
176
170
  continue;
177
171
  }
178
172
  const [targetDbDir, targetPkg] = appDir.split("::");
179
- // Force-stop is best-effort (ensures WAL is flushed)
180
173
  await shell(`adb shell am force-stop ${targetPkg}`, { timeout: 5_000, ignoreErrors: true, label: `adb-force-stop-${targetPkg}` });
181
174
  const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "rn-sqlite-mcp-"));
182
175
  const safeLocalName = targetDbName.replace(/\//g, '_');
package/dist/logger.js CHANGED
@@ -1,7 +1,3 @@
1
- /**
2
- * Structured logging utility for MCP servers.
3
- * ALL output goes to stderr to avoid polluting the stdio JSON-RPC transport.
4
- */
5
1
  const LOG_LEVELS = {
6
2
  debug: 0,
7
3
  info: 1,
package/dist/shell.js CHANGED
@@ -1,8 +1,3 @@
1
- /**
2
- * Async shell execution wrapper.
3
- * Replaces all execSync usage to keep the Node.js event loop alive
4
- * so MCP stdio transport can continue processing heartbeats/messages.
5
- */
6
1
  import { exec as execCb } from "child_process";
7
2
  import { promisify } from "util";
8
3
  import { logger } from "./logger.js";
@@ -10,10 +5,6 @@ const execAsync = promisify(execCb);
10
5
  function sleep(ms) {
11
6
  return new Promise((resolve) => setTimeout(resolve, ms));
12
7
  }
13
- /**
14
- * Execute a shell command asynchronously with timeout and retry support.
15
- * This is the core replacement for `execSync` — it does NOT block the event loop.
16
- */
17
8
  export async function shell(command, options = {}) {
18
9
  const { timeout = 10_000, retries = 0, retryDelay = 1_000, ignoreErrors = false, label, } = options;
19
10
  const tag = label || command.slice(0, 60);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-sqlite-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Universal React Native SQLite MCP Server",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -23,11 +23,11 @@
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
25
  "@modelcontextprotocol/sdk": "^1.6.0",
26
- "sqlite3": "^5.1.7"
26
+ "sql.js": "^1.14.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.13.4",
30
- "@types/sqlite3": "^3.1.11",
30
+ "@types/sql.js": "^1.4.9",
31
31
  "typescript": "^5.7.3"
32
32
  }
33
33
  }
package/src/db.ts CHANGED
@@ -1,51 +1,28 @@
1
- import sqlite3 from "sqlite3";
1
+ import initSqlJs, { type Database as SqlJsDatabase } from "sql.js";
2
+ import fs from "fs";
2
3
  import { logger } from "./logger.js";
3
4
 
4
- export class Database {
5
- private db: sqlite3.Database;
5
+ let SQL: Awaited<ReturnType<typeof initSqlJs>> | null = null;
6
6
 
7
- constructor(filename: string) {
8
- this.db = new sqlite3.Database(filename);
9
- }
10
-
11
- public all<T>(sql: string, params: any[] = []): Promise<T[]> {
12
- return new Promise((resolve, reject) => {
13
- this.db.all(sql, params, (err, rows) => {
14
- if (err) reject(err);
15
- else resolve(rows as Array<T>);
16
- });
17
- });
18
- }
19
-
20
- public get<T>(sql: string, params: any[] = []): Promise<T | undefined> {
21
- return new Promise((resolve, reject) => {
22
- this.db.get(sql, params, (err, row) => {
23
- if (err) reject(err);
24
- else resolve(row as T | undefined);
25
- });
26
- });
27
- }
28
-
29
- public close(): Promise<void> {
30
- return new Promise((resolve, reject) => {
31
- this.db.close((err) => {
32
- if (err) reject(err);
33
- else resolve();
34
- });
35
- });
7
+ async function getSqlJs() {
8
+ if (!SQL) {
9
+ SQL = await initSqlJs();
10
+ logger.debug("sql.js WASM engine initialized");
36
11
  }
12
+ return SQL;
37
13
  }
38
14
 
39
15
  interface CachedConnection {
40
- db: Database;
16
+ db: SqlJsDatabase;
17
+ dbPath: string;
41
18
  lastUsed: number;
42
19
  timer: ReturnType<typeof setTimeout>;
43
20
  }
44
21
 
45
- const CACHE_TTL_MS = 60_000; // close idle connections after 60s
22
+ const CACHE_TTL_MS = 60_000;
46
23
  const connectionCache = new Map<string, CachedConnection>();
47
24
 
48
- function getCachedDb(dbPath: string): Database {
25
+ async function getCachedDb(dbPath: string): Promise<SqlJsDatabase> {
49
26
  const existing = connectionCache.get(dbPath);
50
27
 
51
28
  if (existing) {
@@ -55,21 +32,23 @@ function getCachedDb(dbPath: string): Database {
55
32
  return existing.db;
56
33
  }
57
34
 
58
- const db = new Database(dbPath);
59
- const timer = setTimeout(() => evictConnection(dbPath), CACHE_TTL_MS);
35
+ const sqlJs = await getSqlJs();
36
+ const buffer = fs.readFileSync(dbPath);
37
+ const db = new sqlJs.Database(buffer);
60
38
 
61
- connectionCache.set(dbPath, { db, lastUsed: Date.now(), timer });
39
+ const timer = setTimeout(() => evictConnection(dbPath), CACHE_TTL_MS);
40
+ connectionCache.set(dbPath, { db, dbPath, lastUsed: Date.now(), timer });
62
41
  logger.debug(`Opened DB connection: ${dbPath}`);
63
42
  return db;
64
43
  }
65
44
 
66
- async function evictConnection(dbPath: string): Promise<void> {
45
+ function evictConnection(dbPath: string): void {
67
46
  const entry = connectionCache.get(dbPath);
68
47
  if (!entry) return;
69
48
 
70
49
  connectionCache.delete(dbPath);
71
50
  try {
72
- await entry.db.close();
51
+ entry.db.close();
73
52
  logger.debug(`Closed idle DB connection: ${dbPath}`);
74
53
  } catch (e) {
75
54
  logger.warn(`Error closing DB: ${dbPath}`, { error: String(e) });
@@ -79,7 +58,7 @@ async function evictConnection(dbPath: string): Promise<void> {
79
58
  export async function closeAllConnections(): Promise<void> {
80
59
  const paths = [...connectionCache.keys()];
81
60
  for (const p of paths) {
82
- await evictConnection(p);
61
+ evictConnection(p);
83
62
  }
84
63
  logger.info(`Closed ${paths.length} cached DB connection(s)`);
85
64
  }
@@ -98,32 +77,45 @@ function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise
98
77
  });
99
78
  }
100
79
 
80
+ function runQuery(db: SqlJsDatabase, sql: string, params: any[] = []): any[] {
81
+ const stmt = db.prepare(sql);
82
+ if (params.length > 0) stmt.bind(params);
83
+
84
+ const results: any[] = [];
85
+ while (stmt.step()) {
86
+ results.push(stmt.getAsObject());
87
+ }
88
+ stmt.free();
89
+ return results;
90
+ }
91
+
101
92
  export async function queryDb(dbPath: string, sql: string, params: any[] = []): Promise<any[]> {
102
- const db = getCachedDb(dbPath);
103
- return withTimeout(db.all(sql, params), QUERY_TIMEOUT_MS, sql.slice(0, 80));
93
+ const db = await getCachedDb(dbPath);
94
+ return withTimeout(
95
+ Promise.resolve(runQuery(db, sql, params)),
96
+ QUERY_TIMEOUT_MS,
97
+ sql.slice(0, 80)
98
+ );
104
99
  }
105
100
 
106
101
  export async function inspectSchema(dbPath: string): Promise<any> {
107
- const db = getCachedDb(dbPath);
102
+ const db = await getCachedDb(dbPath);
108
103
 
109
- const tables = await withTimeout(
110
- db.all<{ name: string }>("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"),
111
- QUERY_TIMEOUT_MS,
112
- "inspect_schema:tables"
113
- );
104
+ const tables = runQuery(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;");
114
105
 
115
106
  const schemaInfo: Record<string, any> = {};
116
107
 
117
108
  for (const table of tables) {
118
- const columns = await db.all(`PRAGMA table_info("${table.name}");`);
119
- const createSql = await db.get<{ sql: string }>(
109
+ const columns = runQuery(db, `PRAGMA table_info("${table.name}");`);
110
+ const createSqlRows = runQuery(
111
+ db,
120
112
  `SELECT sql FROM sqlite_master WHERE type='table' AND name=?`,
121
113
  [table.name]
122
114
  );
123
115
 
124
116
  schemaInfo[table.name] = {
125
117
  columns,
126
- createSql: createSql?.sql
118
+ createSql: createSqlRows[0]?.sql
127
119
  };
128
120
  }
129
121