@semics-tech/mongolite 1.0.0 → 1.2.0
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/LICENSE +1 -1
- package/README.md +78 -2
- package/dist/adapters/node-sqlite.d.ts +65 -0
- package/dist/adapters/node-sqlite.js +241 -0
- package/dist/adapters/node-sqlite.js.map +1 -0
- package/dist/better-sqlite3.d.ts +49 -0
- package/dist/better-sqlite3.js +60 -0
- package/dist/better-sqlite3.js.map +1 -0
- package/dist/bin/mongolite-debug.js +0 -0
- package/dist/cjs/adapters/browser.js +106 -0
- package/dist/cjs/adapters/cloudflare.js +97 -0
- package/dist/cjs/adapters/node-sqlite.js +248 -0
- package/dist/cjs/better-sqlite3.js +80 -0
- package/dist/cjs/bin/mongolite-debug.js +152 -0
- package/dist/cjs/changeStream.js +341 -0
- package/dist/cjs/cloudflare.js +59 -0
- package/dist/cjs/collection.js +1748 -0
- package/dist/cjs/cursors/findCursor.js +837 -0
- package/dist/cjs/db.js +307 -0
- package/dist/cjs/debugger/query-debugger.js +297 -0
- package/dist/cjs/index.js +65 -0
- package/dist/cjs/mongo-client.js +58 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/utils/connection-registry.js +122 -0
- package/dist/cjs/utils/indexing.js +45 -0
- package/dist/cjs/utils/projection.js +101 -0
- package/dist/cjs/utils/queryOperators.js +142 -0
- package/dist/collection.d.ts +25 -0
- package/dist/collection.js +53 -14
- package/dist/collection.js.map +1 -1
- package/dist/cursors/findCursor.d.ts +58 -0
- package/dist/cursors/findCursor.js +88 -11
- package/dist/cursors/findCursor.js.map +1 -1
- package/dist/db.d.ts +23 -0
- package/dist/db.js +85 -39
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/utils/connection-registry.d.ts +74 -0
- package/dist/utils/connection-registry.js +117 -0
- package/dist/utils/connection-registry.js.map +1 -0
- package/package.json +20 -10
package/dist/cjs/db.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SQLiteDB = exports.DEFAULT_BUSY_TIMEOUT_MS = void 0;
|
|
7
|
+
exports.sharedConnectionCount = sharedConnectionCount;
|
|
8
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const connection_registry_js_1 = require("./utils/connection-registry.js");
|
|
11
|
+
Object.defineProperty(exports, "DEFAULT_BUSY_TIMEOUT_MS", { enumerable: true, get: function () { return connection_registry_js_1.DEFAULT_BUSY_TIMEOUT_MS; } });
|
|
12
|
+
/** Shared `better-sqlite3` handles for this process, keyed by connection identity. */
|
|
13
|
+
const sharedConnections = new connection_registry_js_1.ConnectionRegistry();
|
|
14
|
+
/** Exposed for tests and diagnostics. */
|
|
15
|
+
function sharedConnectionCount() {
|
|
16
|
+
return sharedConnections.size;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* SQLiteDB class provides a wrapper around the better-sqlite3 library
|
|
20
|
+
* to simplify database operations.
|
|
21
|
+
*/
|
|
22
|
+
class SQLiteDB {
|
|
23
|
+
/**
|
|
24
|
+
* Creates an instance of SQLiteDB.
|
|
25
|
+
* @param {string | MongoLiteOptions} dbPathOrOptions - The path to the SQLite database file or an options object.
|
|
26
|
+
* Use ':memory:' for an in-memory database.
|
|
27
|
+
*/
|
|
28
|
+
constructor(dbPathOrOptions) {
|
|
29
|
+
this.db = null;
|
|
30
|
+
this.openPromise = null;
|
|
31
|
+
let shared;
|
|
32
|
+
if (typeof dbPathOrOptions === 'string') {
|
|
33
|
+
this.filePath = dbPathOrOptions;
|
|
34
|
+
this.verbose = false;
|
|
35
|
+
this.readOnly = false;
|
|
36
|
+
this.WAL = true;
|
|
37
|
+
this.busyTimeout = connection_registry_js_1.DEFAULT_BUSY_TIMEOUT_MS;
|
|
38
|
+
shared = false;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
this.filePath = dbPathOrOptions.filePath;
|
|
42
|
+
this.verbose = dbPathOrOptions.verbose || false;
|
|
43
|
+
this.readOnly = dbPathOrOptions.readOnly || false;
|
|
44
|
+
this.WAL = dbPathOrOptions.WAL ?? true;
|
|
45
|
+
this.busyTimeout = dbPathOrOptions.busyTimeout ?? connection_registry_js_1.DEFAULT_BUSY_TIMEOUT_MS;
|
|
46
|
+
shared = dbPathOrOptions.shared ?? false;
|
|
47
|
+
}
|
|
48
|
+
this.shareKey =
|
|
49
|
+
shared && (0, connection_registry_js_1.isShareablePath)(this.filePath)
|
|
50
|
+
? (0, connection_registry_js_1.buildConnectionKey)({
|
|
51
|
+
backend: 'better-sqlite3',
|
|
52
|
+
filePath: node_path_1.default.resolve(this.filePath),
|
|
53
|
+
readOnly: this.readOnly,
|
|
54
|
+
WAL: this.WAL,
|
|
55
|
+
busyTimeout: this.busyTimeout,
|
|
56
|
+
verbose: this.verbose,
|
|
57
|
+
})
|
|
58
|
+
: null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Opens the database connection if it's not already open.
|
|
62
|
+
* @returns {Promise<void>} A promise that resolves when the connection is open.
|
|
63
|
+
*/
|
|
64
|
+
async connect() {
|
|
65
|
+
if (this.db && this.openPromise) {
|
|
66
|
+
return this.openPromise;
|
|
67
|
+
}
|
|
68
|
+
if (this.db) {
|
|
69
|
+
// Already connected and openPromise is null (should not happen with proper logic)
|
|
70
|
+
return Promise.resolve();
|
|
71
|
+
}
|
|
72
|
+
this.openPromise = new Promise((resolve, reject) => {
|
|
73
|
+
try {
|
|
74
|
+
// Only runs on a genuinely new connection — when sharing, an existing
|
|
75
|
+
// handle already has its pragmas and UDFs applied.
|
|
76
|
+
const openConnection = () => {
|
|
77
|
+
const options = {
|
|
78
|
+
readonly: this.readOnly,
|
|
79
|
+
verbose: this.verbose ? console.log : undefined,
|
|
80
|
+
};
|
|
81
|
+
const db = new better_sqlite3_1.default(this.filePath, options);
|
|
82
|
+
// Enable Write-Ahead Logging if requested
|
|
83
|
+
if (this.WAL && !this.readOnly) {
|
|
84
|
+
db.pragma('journal_mode = WAL');
|
|
85
|
+
}
|
|
86
|
+
// Wait rather than immediately throwing SQLITE_BUSY when another
|
|
87
|
+
// connection (often another process) holds a lock.
|
|
88
|
+
if (this.busyTimeout > 0) {
|
|
89
|
+
db.pragma(`busy_timeout = ${this.busyTimeout}`);
|
|
90
|
+
}
|
|
91
|
+
// Register regexp UDF for $regex operator support.
|
|
92
|
+
// WARNING: Patterns are compiled via JavaScript RegExp. User-supplied patterns that are
|
|
93
|
+
// not validated for catastrophic backtracking (ReDoS) could block the event loop.
|
|
94
|
+
// Avoid using untrusted/unvalidated patterns with $regex in security-sensitive contexts.
|
|
95
|
+
db.function('regexp', { deterministic: true }, (pattern, value) => {
|
|
96
|
+
if (typeof pattern !== 'string' || value === null || value === undefined)
|
|
97
|
+
return 0;
|
|
98
|
+
try {
|
|
99
|
+
return new RegExp(pattern).test(String(value)) ? 1 : 0;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
// Register regexp_flags UDF for $regex with $options support
|
|
106
|
+
db.function('regexp_flags', { deterministic: true }, (pattern, flags, value) => {
|
|
107
|
+
if (typeof pattern !== 'string' || value === null || value === undefined)
|
|
108
|
+
return 0;
|
|
109
|
+
try {
|
|
110
|
+
const f = typeof flags === 'string' ? flags : '';
|
|
111
|
+
return new RegExp(pattern, f).test(String(value)) ? 1 : 0;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
if (this.verbose) {
|
|
118
|
+
console.log(`SQLite database opened: ${this.filePath}`);
|
|
119
|
+
}
|
|
120
|
+
return db;
|
|
121
|
+
};
|
|
122
|
+
this.db = this.shareKey
|
|
123
|
+
? sharedConnections.acquire(this.shareKey, openConnection)
|
|
124
|
+
: openConnection();
|
|
125
|
+
resolve();
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.error(`Error opening database ${this.filePath}:`, err.message);
|
|
129
|
+
this.db = null;
|
|
130
|
+
this.openPromise = null;
|
|
131
|
+
reject(err);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return this.openPromise;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Ensures the database connection is open before performing an operation.
|
|
138
|
+
* @private
|
|
139
|
+
* @returns {Promise<Database.Database>} The active database instance.
|
|
140
|
+
* @throws {Error} If the database is not connected.
|
|
141
|
+
*/
|
|
142
|
+
async ensureConnected() {
|
|
143
|
+
if (!this.db || !this.openPromise) {
|
|
144
|
+
await this.connect();
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
await this.openPromise; // Wait for any ongoing connection attempt
|
|
148
|
+
}
|
|
149
|
+
if (!this.db) {
|
|
150
|
+
// This should ideally not be reached if connect() works correctly
|
|
151
|
+
throw new Error('Database is not connected. Connection attempt failed.');
|
|
152
|
+
}
|
|
153
|
+
return this.db;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Executes a SQL query that does not return rows (e.g., INSERT, UPDATE, DELETE, CREATE).
|
|
157
|
+
* @param {string} sql - The SQL query string.
|
|
158
|
+
* @param {any[]} [params=[]] - Parameters for the SQL query.
|
|
159
|
+
* @returns {Promise<{ lastID: number, changes: number }>} Result of the execution.
|
|
160
|
+
*/
|
|
161
|
+
async run(sql, params = []) {
|
|
162
|
+
const dbInstance = await this.ensureConnected();
|
|
163
|
+
try {
|
|
164
|
+
const result = dbInstance.prepare(sql).run(...params);
|
|
165
|
+
return {
|
|
166
|
+
lastID: result.lastInsertRowid,
|
|
167
|
+
changes: result.changes,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
console.error(`Error running SQL: ${sql}`, err);
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Executes a SQL query that returns a single row.
|
|
177
|
+
* @param {string} sql - The SQL query string.
|
|
178
|
+
* @param {any[]} [params=[]] - Parameters for the SQL query.
|
|
179
|
+
* @returns {Promise<T | undefined>} The first row found, or undefined.
|
|
180
|
+
*/
|
|
181
|
+
async get(sql, params = []) {
|
|
182
|
+
const dbInstance = await this.ensureConnected();
|
|
183
|
+
try {
|
|
184
|
+
return dbInstance.prepare(sql).get(...params);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
console.error(`Error getting SQL: ${sql}`, err);
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Executes a SQL query that returns multiple rows.
|
|
193
|
+
* @param {string} sql - The SQL query string.
|
|
194
|
+
* @param {any[]} [params=[]] - Parameters for the SQL query.
|
|
195
|
+
* @returns {Promise<T[]>} An array of rows.
|
|
196
|
+
*/
|
|
197
|
+
async all(sql, params = []) {
|
|
198
|
+
const dbInstance = await this.ensureConnected();
|
|
199
|
+
try {
|
|
200
|
+
return dbInstance.prepare(sql).all(...params);
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
console.error(`Error getting all SQL: ${sql}`, err);
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Executes multiple SQL statements.
|
|
209
|
+
* @param {string} sql - SQL string with multiple statements.
|
|
210
|
+
* @returns {Promise<void>}
|
|
211
|
+
*/
|
|
212
|
+
async exec(sql) {
|
|
213
|
+
const dbInstance = await this.ensureConnected();
|
|
214
|
+
try {
|
|
215
|
+
dbInstance.exec(sql);
|
|
216
|
+
return Promise.resolve();
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
console.error(`Error executing SQL: ${sql}`, err);
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Closes the database connection.
|
|
225
|
+
* @returns {Promise<void>} A promise that resolves when the connection is closed.
|
|
226
|
+
*/
|
|
227
|
+
async close() {
|
|
228
|
+
if (this.openPromise) {
|
|
229
|
+
await this.openPromise; // Ensure any pending connection attempt is finished
|
|
230
|
+
}
|
|
231
|
+
if (this.db) {
|
|
232
|
+
try {
|
|
233
|
+
// When shared, only the last holder actually closes the handle.
|
|
234
|
+
const isLastHolder = this.shareKey
|
|
235
|
+
? sharedConnections.release(this.shareKey, this.db)
|
|
236
|
+
: true;
|
|
237
|
+
if (isLastHolder) {
|
|
238
|
+
this.db.close();
|
|
239
|
+
}
|
|
240
|
+
if (this.verbose) {
|
|
241
|
+
console.log(isLastHolder
|
|
242
|
+
? `SQLite database closed: ${this.filePath}`
|
|
243
|
+
: `SQLite database released (still in use): ${this.filePath}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
console.error(`Error closing database ${this.filePath}:`, err.message);
|
|
248
|
+
throw err; // Re-throw the error to indicate failure
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
this.db = null;
|
|
252
|
+
this.openPromise = null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// If db is null, it's already considered closed or was never opened.
|
|
257
|
+
this.db = null;
|
|
258
|
+
this.openPromise = null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Gets the underlying better-sqlite3 Database instance.
|
|
263
|
+
* Useful for operations not covered by this wrapper, like transactions.
|
|
264
|
+
* @returns {Promise<Database.Database>} The raw database object.
|
|
265
|
+
* @throws {Error} If the database is not connected.
|
|
266
|
+
*/
|
|
267
|
+
async getDbInstance() {
|
|
268
|
+
return this.ensureConnected();
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Prepares a SQL statement for later execution.
|
|
272
|
+
* @param {string} sql - The SQL query string.
|
|
273
|
+
* @returns {Promise<Statement>} A prepared statement that can be executed multiple times.
|
|
274
|
+
*/
|
|
275
|
+
async prepare(sql) {
|
|
276
|
+
const dbInstance = await this.ensureConnected();
|
|
277
|
+
return dbInstance.prepare(sql);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Begins a transaction.
|
|
281
|
+
* @returns {Promise<void>}
|
|
282
|
+
*/
|
|
283
|
+
async beginTransaction() {
|
|
284
|
+
const dbInstance = await this.ensureConnected();
|
|
285
|
+
dbInstance.prepare('BEGIN').run();
|
|
286
|
+
return Promise.resolve();
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Commits a transaction.
|
|
290
|
+
* @returns {Promise<void>}
|
|
291
|
+
*/
|
|
292
|
+
async commitTransaction() {
|
|
293
|
+
const dbInstance = await this.ensureConnected();
|
|
294
|
+
dbInstance.prepare('COMMIT').run();
|
|
295
|
+
return Promise.resolve();
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Rolls back a transaction.
|
|
299
|
+
* @returns {Promise<void>}
|
|
300
|
+
*/
|
|
301
|
+
async rollbackTransaction() {
|
|
302
|
+
const dbInstance = await this.ensureConnected();
|
|
303
|
+
dbInstance.prepare('ROLLBACK').run();
|
|
304
|
+
return Promise.resolve();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
exports.SQLiteDB = SQLiteDB;
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.QueryDebugger = void 0;
|
|
40
|
+
const index_js_1 = require("../index.js");
|
|
41
|
+
const findCursor_js_1 = require("../cursors/findCursor.js");
|
|
42
|
+
const readline = __importStar(require("readline"));
|
|
43
|
+
const path_1 = __importDefault(require("path"));
|
|
44
|
+
class QueryDebugger {
|
|
45
|
+
constructor(options = {}) {
|
|
46
|
+
this.currentCollection = null;
|
|
47
|
+
this.lastFilter = null;
|
|
48
|
+
this.lastSqlQuery = null;
|
|
49
|
+
const dbPath = options.database || path_1.default.join(process.cwd(), 'mongolite.db');
|
|
50
|
+
this.client = new index_js_1.MongoLite(dbPath, { verbose: options.verbose });
|
|
51
|
+
this.rl = readline.createInterface({
|
|
52
|
+
input: process.stdin,
|
|
53
|
+
output: process.stdout,
|
|
54
|
+
prompt: 'mongolite-debug> ',
|
|
55
|
+
});
|
|
56
|
+
if (options.collection) {
|
|
57
|
+
this.currentCollection = options.collection;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async start() {
|
|
61
|
+
console.log('Commands:');
|
|
62
|
+
console.log(' .help - Show this help');
|
|
63
|
+
console.log(' .collections - List all collections');
|
|
64
|
+
console.log(' .use <collection> - Select a collection to work with');
|
|
65
|
+
console.log(' .find <filter> - Convert find filter to SQL and execute');
|
|
66
|
+
console.log(' .sql <query> - Execute raw SQL query');
|
|
67
|
+
console.log(' .last - Show last generated SQL');
|
|
68
|
+
console.log(' .sample [count] - Show sample documents from current collection');
|
|
69
|
+
console.log(' .exit - Exit the debugger');
|
|
70
|
+
console.log('');
|
|
71
|
+
try {
|
|
72
|
+
await this.client.connect();
|
|
73
|
+
console.log('✅ Connected to database.');
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('❌ Failed to connect to database:', error instanceof Error ? error.message : error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
if (this.currentCollection) {
|
|
80
|
+
console.log(`Current collection: ${this.currentCollection}`);
|
|
81
|
+
}
|
|
82
|
+
this.rl.prompt();
|
|
83
|
+
this.rl.on('line', async (input) => {
|
|
84
|
+
const trimmed = input.trim();
|
|
85
|
+
if (trimmed === '.exit') {
|
|
86
|
+
await this.cleanup();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await this.handleCommand(trimmed);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
94
|
+
}
|
|
95
|
+
this.rl.prompt();
|
|
96
|
+
});
|
|
97
|
+
this.rl.on('close', async () => {
|
|
98
|
+
await this.cleanup();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async handleCommand(input) {
|
|
102
|
+
if (input === '.help') {
|
|
103
|
+
this.showHelp();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (input === '.collections') {
|
|
107
|
+
await this.listCollections();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (input.startsWith('.use ')) {
|
|
111
|
+
const collection = input.slice(5).trim();
|
|
112
|
+
this.currentCollection = collection;
|
|
113
|
+
console.log(`Switched to collection: ${collection}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (input.startsWith('.find ')) {
|
|
117
|
+
await this.handleFindCommand(input.slice(6).trim());
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (input.startsWith('.sql ')) {
|
|
121
|
+
await this.handleSqlCommand(input.slice(5).trim());
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (input === '.last') {
|
|
125
|
+
this.showLastQuery();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (input.startsWith('.sample')) {
|
|
129
|
+
const parts = input.split(' ');
|
|
130
|
+
const count = parts.length > 1 ? parseInt(parts[1]) || 5 : 5;
|
|
131
|
+
await this.showSample(count);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
console.log('Unknown command. Type .help for available commands.');
|
|
135
|
+
}
|
|
136
|
+
showHelp() {
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log('Available commands:');
|
|
139
|
+
console.log(' .help - Show this help');
|
|
140
|
+
console.log(' .collections - List all collections');
|
|
141
|
+
console.log(' .use <collection> - Select a collection to work with');
|
|
142
|
+
console.log(' .find <filter> - Convert find filter to SQL and execute');
|
|
143
|
+
console.log(' .sql <query> - Execute raw SQL query');
|
|
144
|
+
console.log(' .last - Show last generated SQL');
|
|
145
|
+
console.log(' .sample [count] - Show sample documents from current collection');
|
|
146
|
+
console.log(' .exit - Exit the debugger');
|
|
147
|
+
console.log('');
|
|
148
|
+
console.log('Examples:');
|
|
149
|
+
console.log(' .use users');
|
|
150
|
+
console.log(' .find {"age": {"$gt": 25}}');
|
|
151
|
+
console.log(' .find {"name": "John", "age": {"$in": [25, 30, 35]}}');
|
|
152
|
+
console.log(' .sql SELECT * FROM users WHERE json_extract(data, "$.age") > 25');
|
|
153
|
+
console.log('');
|
|
154
|
+
}
|
|
155
|
+
async listCollections() {
|
|
156
|
+
try {
|
|
157
|
+
const collections = await this.client.listCollections().toArray();
|
|
158
|
+
if (collections.length === 0) {
|
|
159
|
+
console.log('No collections found.');
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
console.log('Collections:');
|
|
163
|
+
collections.forEach((collection) => {
|
|
164
|
+
const marker = collection === this.currentCollection ? ' (current)' : '';
|
|
165
|
+
console.log(` - ${collection}${marker}`);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error('Error listing collections:', error instanceof Error ? error.message : error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async handleFindCommand(filterStr) {
|
|
174
|
+
if (!this.currentCollection) {
|
|
175
|
+
console.log('No collection selected. Use .use <collection> first.');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
let filter;
|
|
179
|
+
try {
|
|
180
|
+
filter = JSON.parse(filterStr);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.log('Invalid JSON filter:', error instanceof Error ? error.message : error);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.lastFilter = filter;
|
|
187
|
+
// Create a collection and a find cursor to extract the SQL
|
|
188
|
+
const collection = this.client.collection(this.currentCollection);
|
|
189
|
+
// We need to access the internal SQL generation. Let's create a custom cursor
|
|
190
|
+
// that exposes the SQL generation logic
|
|
191
|
+
const debugCursor = new DebugFindCursor(collection.db, this.currentCollection, filter, { verbose: true });
|
|
192
|
+
this.lastSqlQuery = debugCursor.getSQL();
|
|
193
|
+
console.log('');
|
|
194
|
+
console.log('📝 Generated SQL:');
|
|
195
|
+
console.log('Query:', this.lastSqlQuery.sql);
|
|
196
|
+
console.log('Parameters:', JSON.stringify(this.lastSqlQuery.params));
|
|
197
|
+
console.log('');
|
|
198
|
+
// Execute the query and show results
|
|
199
|
+
try {
|
|
200
|
+
const results = await debugCursor.toArray();
|
|
201
|
+
console.log(`📊 Results (${results.length} documents):`);
|
|
202
|
+
if (results.length > 0) {
|
|
203
|
+
results.forEach((doc, index) => {
|
|
204
|
+
console.log(`[${index}]`, JSON.stringify(doc, null, 2));
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
console.log('No documents found.');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
console.error('Execution error:', error instanceof Error ? error.message : error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async handleSqlCommand(sqlQuery) {
|
|
216
|
+
if (!this.currentCollection) {
|
|
217
|
+
console.log('No collection selected. Use .use <collection> first.');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
// Get the database instance to execute raw SQL
|
|
222
|
+
const db = this.client.db;
|
|
223
|
+
console.log('');
|
|
224
|
+
console.log('🔧 Executing SQL:');
|
|
225
|
+
console.log(sqlQuery);
|
|
226
|
+
console.log('');
|
|
227
|
+
const results = await db.all(sqlQuery);
|
|
228
|
+
console.log(`📊 Results (${results.length} rows):`);
|
|
229
|
+
if (results.length > 0) {
|
|
230
|
+
results.forEach((row, index) => {
|
|
231
|
+
console.log(`[${index}]`, JSON.stringify(row, null, 2));
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
console.log('No rows returned.');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
console.error('SQL execution error:', error instanceof Error ? error.message : error);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
showLastQuery() {
|
|
243
|
+
if (!this.lastSqlQuery) {
|
|
244
|
+
console.log('No previous query to show.');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
console.log('');
|
|
248
|
+
console.log('📝 Last Generated SQL:');
|
|
249
|
+
console.log('Query:', this.lastSqlQuery.sql);
|
|
250
|
+
console.log('Parameters:', JSON.stringify(this.lastSqlQuery.params));
|
|
251
|
+
if (this.lastFilter) {
|
|
252
|
+
console.log('Original Filter:', JSON.stringify(this.lastFilter, null, 2));
|
|
253
|
+
}
|
|
254
|
+
console.log('');
|
|
255
|
+
}
|
|
256
|
+
async showSample(count) {
|
|
257
|
+
if (!this.currentCollection) {
|
|
258
|
+
console.log('No collection selected. Use .use <collection> first.');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const collection = this.client.collection(this.currentCollection);
|
|
263
|
+
const results = await collection.find({}).limit(count).toArray();
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log(`📋 Sample documents from "${this.currentCollection}" (${results.length} of ${count} requested):`);
|
|
266
|
+
if (results.length > 0) {
|
|
267
|
+
results.forEach((doc, index) => {
|
|
268
|
+
console.log(`[${index}]`, JSON.stringify(doc, null, 2));
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
console.log('Collection is empty.');
|
|
273
|
+
}
|
|
274
|
+
console.log('');
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
console.error('Error fetching sample:', error instanceof Error ? error.message : error);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async cleanup() {
|
|
281
|
+
console.log('\nClosing database connection...');
|
|
282
|
+
await this.client.close();
|
|
283
|
+
this.rl.close();
|
|
284
|
+
process.exit(0);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
exports.QueryDebugger = QueryDebugger;
|
|
288
|
+
// Extended FindCursor that exposes SQL generation
|
|
289
|
+
class DebugFindCursor extends findCursor_js_1.FindCursor {
|
|
290
|
+
constructor(db, collectionName, filter, options = {}) {
|
|
291
|
+
super(db, collectionName, filter, options);
|
|
292
|
+
}
|
|
293
|
+
getSQL() {
|
|
294
|
+
// Access the private queryParts property
|
|
295
|
+
return this.queryParts;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.MongoLite = exports.NodeSqliteAdapter = exports.BrowserSqliteAdapter = exports.CloudflareDurableObjectAdapter = exports.ChangeStream = exports.MongoLiteCollection = void 0;
|
|
18
|
+
const node_sqlite_js_1 = require("./adapters/node-sqlite.js");
|
|
19
|
+
const mongo_client_js_1 = require("./mongo-client.js");
|
|
20
|
+
var collection_js_1 = require("./collection.js");
|
|
21
|
+
Object.defineProperty(exports, "MongoLiteCollection", { enumerable: true, get: function () { return collection_js_1.MongoLiteCollection; } });
|
|
22
|
+
__exportStar(require("./types.js"), exports);
|
|
23
|
+
var changeStream_js_1 = require("./changeStream.js");
|
|
24
|
+
Object.defineProperty(exports, "ChangeStream", { enumerable: true, get: function () { return changeStream_js_1.ChangeStream; } });
|
|
25
|
+
var cloudflare_js_1 = require("./adapters/cloudflare.js");
|
|
26
|
+
Object.defineProperty(exports, "CloudflareDurableObjectAdapter", { enumerable: true, get: function () { return cloudflare_js_1.CloudflareDurableObjectAdapter; } });
|
|
27
|
+
var browser_js_1 = require("./adapters/browser.js");
|
|
28
|
+
Object.defineProperty(exports, "BrowserSqliteAdapter", { enumerable: true, get: function () { return browser_js_1.BrowserSqliteAdapter; } });
|
|
29
|
+
var node_sqlite_js_2 = require("./adapters/node-sqlite.js");
|
|
30
|
+
Object.defineProperty(exports, "NodeSqliteAdapter", { enumerable: true, get: function () { return node_sqlite_js_2.NodeSqliteAdapter; } });
|
|
31
|
+
/**
|
|
32
|
+
* MongoLite class is the main entry point for interacting with the SQLite-backed database.
|
|
33
|
+
*
|
|
34
|
+
* You can construct it with:
|
|
35
|
+
* - A file path string — uses the built-in `node:sqlite` adapter (`NodeSqliteAdapter`).
|
|
36
|
+
* Requires Node.js 22.5.0+; for older runtimes, import `MongoLite` from
|
|
37
|
+
* `@semics-tech/mongolite/better-sqlite3` instead.
|
|
38
|
+
* - A `MongoLiteClientOptions` object — uses the built-in adapter with options.
|
|
39
|
+
* - An `IDatabaseAdapter` instance — use a custom adapter such as
|
|
40
|
+
* `CloudflareDurableObjectAdapter` for Cloudflare Durable Objects.
|
|
41
|
+
*/
|
|
42
|
+
class MongoLite extends mongo_client_js_1.MongoLite {
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new MongoLite client instance.
|
|
45
|
+
* @param dbPathOrOptions Path to the SQLite database file, an options object,
|
|
46
|
+
* or an `IDatabaseAdapter` for custom backends.
|
|
47
|
+
*/
|
|
48
|
+
constructor(dbPathOrOptions, options = {}) {
|
|
49
|
+
if (dbPathOrOptions &&
|
|
50
|
+
typeof dbPathOrOptions === 'object' &&
|
|
51
|
+
typeof dbPathOrOptions.connect === 'function' &&
|
|
52
|
+
typeof dbPathOrOptions.run === 'function' &&
|
|
53
|
+
typeof dbPathOrOptions.get === 'function' &&
|
|
54
|
+
typeof dbPathOrOptions.all === 'function' &&
|
|
55
|
+
typeof dbPathOrOptions.exec === 'function' &&
|
|
56
|
+
typeof dbPathOrOptions.close === 'function') {
|
|
57
|
+
// Custom adapter (e.g. CloudflareDurableObjectAdapter)
|
|
58
|
+
super(dbPathOrOptions, options);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
super(new node_sqlite_js_1.NodeSqliteAdapter(dbPathOrOptions), options);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.MongoLite = MongoLite;
|