@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.
Files changed (44) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +78 -2
  3. package/dist/adapters/node-sqlite.d.ts +65 -0
  4. package/dist/adapters/node-sqlite.js +241 -0
  5. package/dist/adapters/node-sqlite.js.map +1 -0
  6. package/dist/better-sqlite3.d.ts +49 -0
  7. package/dist/better-sqlite3.js +60 -0
  8. package/dist/better-sqlite3.js.map +1 -0
  9. package/dist/bin/mongolite-debug.js +0 -0
  10. package/dist/cjs/adapters/browser.js +106 -0
  11. package/dist/cjs/adapters/cloudflare.js +97 -0
  12. package/dist/cjs/adapters/node-sqlite.js +248 -0
  13. package/dist/cjs/better-sqlite3.js +80 -0
  14. package/dist/cjs/bin/mongolite-debug.js +152 -0
  15. package/dist/cjs/changeStream.js +341 -0
  16. package/dist/cjs/cloudflare.js +59 -0
  17. package/dist/cjs/collection.js +1748 -0
  18. package/dist/cjs/cursors/findCursor.js +837 -0
  19. package/dist/cjs/db.js +307 -0
  20. package/dist/cjs/debugger/query-debugger.js +297 -0
  21. package/dist/cjs/index.js +65 -0
  22. package/dist/cjs/mongo-client.js +58 -0
  23. package/dist/cjs/package.json +3 -0
  24. package/dist/cjs/types.js +2 -0
  25. package/dist/cjs/utils/connection-registry.js +122 -0
  26. package/dist/cjs/utils/indexing.js +45 -0
  27. package/dist/cjs/utils/projection.js +101 -0
  28. package/dist/cjs/utils/queryOperators.js +142 -0
  29. package/dist/collection.d.ts +25 -0
  30. package/dist/collection.js +53 -14
  31. package/dist/collection.js.map +1 -1
  32. package/dist/cursors/findCursor.d.ts +58 -0
  33. package/dist/cursors/findCursor.js +88 -11
  34. package/dist/cursors/findCursor.js.map +1 -1
  35. package/dist/db.d.ts +23 -0
  36. package/dist/db.js +85 -39
  37. package/dist/db.js.map +1 -1
  38. package/dist/index.d.ts +5 -2
  39. package/dist/index.js +6 -3
  40. package/dist/index.js.map +1 -1
  41. package/dist/utils/connection-registry.d.ts +74 -0
  42. package/dist/utils/connection-registry.js +117 -0
  43. package/dist/utils/connection-registry.js.map +1 -0
  44. package/package.json +20 -10
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CloudflareDurableObjectAdapter = void 0;
4
+ /**
5
+ * Database adapter that wraps the Cloudflare Durable Objects `SqlStorage` API,
6
+ * allowing MongoLite to be used inside a Cloudflare Durable Object.
7
+ *
8
+ * ## Usage
9
+ *
10
+ * ```ts
11
+ * import { DurableObject } from 'cloudflare:workers';
12
+ * import { MongoLite, CloudflareDurableObjectAdapter } from '@semics-tech/mongolite/cloudflare';
13
+ *
14
+ * export class MyDurableObject extends DurableObject {
15
+ * private client: MongoLite;
16
+ *
17
+ * constructor(ctx: DurableObjectState, env: Env) {
18
+ * super(ctx, env);
19
+ * const adapter = new CloudflareDurableObjectAdapter(ctx.storage.sql);
20
+ * this.client = new MongoLite(adapter);
21
+ * }
22
+ *
23
+ * async fetch(request: Request) {
24
+ * await this.client.connect();
25
+ * const users = this.client.collection('users');
26
+ * await users.insertOne({ name: 'Alice', age: 30 });
27
+ * const user = await users.findOne({ name: 'Alice' });
28
+ * return Response.json(user);
29
+ * }
30
+ * }
31
+ * ```
32
+ *
33
+ * ## Limitations
34
+ *
35
+ * - **`$regex` queries are not supported.** Cloudflare's SQLite does not allow custom
36
+ * SQL functions, so the `regexp()` UDF registered by the default Node.js adapter is
37
+ * unavailable. Queries using `$regex` will throw a SQL error at runtime.
38
+ * - `WAL` mode and most `PRAGMA` statements are not available in Cloudflare Durable
39
+ * Objects (Cloudflare manages this internally).
40
+ * - Change Streams (`collection.watch()`) use SQL triggers and a change-log table.
41
+ * They work in Cloudflare Durable Objects, but the polling mechanism relies on
42
+ * `setInterval`, which is supported in the Workers runtime.
43
+ */
44
+ class CloudflareDurableObjectAdapter {
45
+ /**
46
+ * @param sql The `SqlStorage` instance from a Cloudflare Durable Object context
47
+ * (i.e. `ctx.storage.sql`).
48
+ */
49
+ constructor(sql) {
50
+ this.sql = sql;
51
+ }
52
+ /**
53
+ * No-op: Cloudflare Durable Objects manage the storage lifecycle automatically.
54
+ */
55
+ async connect() {
56
+ // Nothing to do — the SqlStorage instance is already live.
57
+ }
58
+ /**
59
+ * No-op: Cloudflare Durable Objects manage the storage lifecycle automatically.
60
+ */
61
+ async close() {
62
+ // Nothing to do — storage is managed by the Durable Object runtime.
63
+ }
64
+ /**
65
+ * Executes a SQL statement that does not return rows (INSERT, UPDATE, DELETE, DDL).
66
+ */
67
+ async run(sql, params = []) {
68
+ const cursor = this.sql.exec(sql, ...params);
69
+ return { lastID: 0, changes: cursor.rowsWritten };
70
+ }
71
+ /**
72
+ * Executes a SQL query and returns the first matching row, or `undefined`.
73
+ */
74
+ async get(sql, params = []) {
75
+ const rows = this.sql.exec(sql, ...params).toArray();
76
+ return rows[0];
77
+ }
78
+ /**
79
+ * Executes a SQL query and returns all matching rows.
80
+ */
81
+ async all(sql, params = []) {
82
+ return this.sql.exec(sql, ...params).toArray();
83
+ }
84
+ /**
85
+ * Executes a single SQL statement (DDL, transactions, etc.).
86
+ *
87
+ * Cloudflare's `SqlStorage.exec` only supports one statement at a time, and
88
+ * some valid statements (e.g. `CREATE TRIGGER ... BEGIN ...; ...; END;`)
89
+ * contain internal semicolons. Callers must therefore pass exactly one
90
+ * statement per call; this method will not attempt to split multi-statement
91
+ * SQL strings.
92
+ */
93
+ async exec(sql) {
94
+ this.sql.exec(sql);
95
+ }
96
+ }
97
+ exports.CloudflareDurableObjectAdapter = CloudflareDurableObjectAdapter;
@@ -0,0 +1,248 @@
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.NodeSqliteAdapter = void 0;
7
+ exports.sharedConnectionCount = sharedConnectionCount;
8
+ const node_sqlite_1 = require("node:sqlite");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const connection_registry_js_1 = require("../utils/connection-registry.js");
11
+ /** Shared `node:sqlite` handles for this process, keyed by connection identity. */
12
+ const sharedConnections = new connection_registry_js_1.ConnectionRegistry();
13
+ /** Exposed for tests and diagnostics. */
14
+ function sharedConnectionCount() {
15
+ return sharedConnections.size;
16
+ }
17
+ /**
18
+ * Database adapter backed by Node.js's built-in `node:sqlite` module — no native
19
+ * addon, no prebuilt binary, no `node-gyp`. Requires Node.js 22.5.0+ (the module
20
+ * is marked experimental by Node.js until it graduates to stable).
21
+ *
22
+ * ## Usage
23
+ *
24
+ * ```ts
25
+ * import { MongoLite, NodeSqliteAdapter } from '@semics-tech/mongolite';
26
+ *
27
+ * const client = new MongoLite(new NodeSqliteAdapter('./data.db'));
28
+ * await client.connect();
29
+ * const users = client.collection('users');
30
+ * await users.insertOne({ name: 'Alice', age: 30 });
31
+ * await client.close();
32
+ * ```
33
+ *
34
+ * ## Limitations
35
+ *
36
+ * - **Experimental.** `node:sqlite` is still marked experimental upstream and its
37
+ * API may change between Node.js minor versions.
38
+ * - **Node.js 22.5.0+ only.** Importing this adapter on an older runtime (or on
39
+ * Bun/Deno without a `node:sqlite` polyfill) throws at import time.
40
+ * - Rows are returned with a `null` prototype (per `node:sqlite`'s API). Plain
41
+ * property access (`row.foo`) and `JSON.stringify`/`Object.keys` all work as
42
+ * expected; `row.hasOwnProperty(...)` does not.
43
+ */
44
+ class NodeSqliteAdapter {
45
+ /**
46
+ * @param dbPathOrOptions Path to the SQLite database file (use `':memory:'`
47
+ * for an in-memory database), or an options object mirroring the built-in
48
+ * `better-sqlite3` adapter's `MongoLiteOptions`.
49
+ */
50
+ constructor(dbPathOrOptions) {
51
+ this.db = null;
52
+ this.openPromise = null;
53
+ let shared;
54
+ if (typeof dbPathOrOptions === 'string') {
55
+ this.filePath = dbPathOrOptions;
56
+ this.verbose = false;
57
+ this.readOnly = false;
58
+ this.WAL = true;
59
+ this.busyTimeout = connection_registry_js_1.DEFAULT_BUSY_TIMEOUT_MS;
60
+ shared = false;
61
+ }
62
+ else {
63
+ this.filePath = dbPathOrOptions.filePath;
64
+ this.verbose = dbPathOrOptions.verbose || false;
65
+ this.readOnly = dbPathOrOptions.readOnly || false;
66
+ this.WAL = dbPathOrOptions.WAL ?? true;
67
+ this.busyTimeout = dbPathOrOptions.busyTimeout ?? connection_registry_js_1.DEFAULT_BUSY_TIMEOUT_MS;
68
+ shared = dbPathOrOptions.shared ?? false;
69
+ }
70
+ this.shareKey =
71
+ shared && (0, connection_registry_js_1.isShareablePath)(this.filePath)
72
+ ? (0, connection_registry_js_1.buildConnectionKey)({
73
+ backend: 'node:sqlite',
74
+ filePath: node_path_1.default.resolve(this.filePath),
75
+ readOnly: this.readOnly,
76
+ WAL: this.WAL,
77
+ busyTimeout: this.busyTimeout,
78
+ verbose: this.verbose,
79
+ })
80
+ : null;
81
+ }
82
+ /**
83
+ * Opens the database connection if it's not already open.
84
+ */
85
+ async connect() {
86
+ if (this.db && this.openPromise) {
87
+ return this.openPromise;
88
+ }
89
+ if (this.db) {
90
+ return Promise.resolve();
91
+ }
92
+ this.openPromise = new Promise((resolve, reject) => {
93
+ try {
94
+ // Only runs on a genuinely new connection — when sharing, an existing
95
+ // handle already has its pragmas and UDFs applied.
96
+ const openConnection = () => {
97
+ const db = new node_sqlite_1.DatabaseSync(this.filePath, { readOnly: this.readOnly });
98
+ if (this.WAL && !this.readOnly) {
99
+ db.exec('PRAGMA journal_mode = WAL');
100
+ }
101
+ // Wait rather than immediately throwing SQLITE_BUSY when another
102
+ // connection (often another process) holds a lock.
103
+ if (this.busyTimeout > 0) {
104
+ db.exec(`PRAGMA busy_timeout = ${this.busyTimeout}`);
105
+ }
106
+ // Register regexp UDF for $regex operator support.
107
+ // WARNING: Patterns are compiled via JavaScript RegExp. User-supplied patterns that are
108
+ // not validated for catastrophic backtracking (ReDoS) could block the event loop.
109
+ // Avoid using untrusted/unvalidated patterns with $regex in security-sensitive contexts.
110
+ db.function('regexp', { deterministic: true }, (pattern, value) => {
111
+ if (typeof pattern !== 'string' || value === null || value === undefined)
112
+ return 0;
113
+ try {
114
+ return new RegExp(pattern).test(String(value)) ? 1 : 0;
115
+ }
116
+ catch {
117
+ return 0;
118
+ }
119
+ });
120
+ // Register regexp_flags UDF for $regex with $options support
121
+ db.function('regexp_flags', { deterministic: true }, (pattern, flags, value) => {
122
+ if (typeof pattern !== 'string' || value === null || value === undefined)
123
+ return 0;
124
+ try {
125
+ const f = typeof flags === 'string' ? flags : '';
126
+ return new RegExp(pattern, f).test(String(value)) ? 1 : 0;
127
+ }
128
+ catch {
129
+ return 0;
130
+ }
131
+ });
132
+ if (this.verbose) {
133
+ console.log(`SQLite database opened (node:sqlite): ${this.filePath}`);
134
+ }
135
+ return db;
136
+ };
137
+ this.db = this.shareKey
138
+ ? sharedConnections.acquire(this.shareKey, openConnection)
139
+ : openConnection();
140
+ resolve();
141
+ }
142
+ catch (err) {
143
+ console.error(`Error opening database ${this.filePath}:`, err.message);
144
+ this.db = null;
145
+ this.openPromise = null;
146
+ reject(err);
147
+ }
148
+ });
149
+ return this.openPromise;
150
+ }
151
+ async ensureConnected() {
152
+ if (!this.db || !this.openPromise) {
153
+ await this.connect();
154
+ }
155
+ else {
156
+ await this.openPromise;
157
+ }
158
+ if (!this.db) {
159
+ throw new Error('Database is not connected. Connection attempt failed.');
160
+ }
161
+ return this.db;
162
+ }
163
+ async run(sql, params = []) {
164
+ const dbInstance = await this.ensureConnected();
165
+ try {
166
+ const result = dbInstance.prepare(sql).run(...params);
167
+ return {
168
+ lastID: Number(result.lastInsertRowid),
169
+ changes: Number(result.changes),
170
+ };
171
+ }
172
+ catch (err) {
173
+ console.error(`Error running SQL: ${sql}`, err);
174
+ throw err;
175
+ }
176
+ }
177
+ async get(sql, params = []) {
178
+ const dbInstance = await this.ensureConnected();
179
+ try {
180
+ return dbInstance.prepare(sql).get(...params);
181
+ }
182
+ catch (err) {
183
+ console.error(`Error getting SQL: ${sql}`, err);
184
+ throw err;
185
+ }
186
+ }
187
+ async all(sql, params = []) {
188
+ const dbInstance = await this.ensureConnected();
189
+ try {
190
+ return dbInstance.prepare(sql).all(...params);
191
+ }
192
+ catch (err) {
193
+ console.error(`Error getting all SQL: ${sql}`, err);
194
+ throw err;
195
+ }
196
+ }
197
+ async exec(sql) {
198
+ const dbInstance = await this.ensureConnected();
199
+ try {
200
+ dbInstance.exec(sql);
201
+ }
202
+ catch (err) {
203
+ console.error(`Error executing SQL: ${sql}`, err);
204
+ throw err;
205
+ }
206
+ }
207
+ async close() {
208
+ if (this.openPromise) {
209
+ await this.openPromise;
210
+ }
211
+ if (this.db) {
212
+ try {
213
+ // When shared, only the last holder actually closes the handle.
214
+ const isLastHolder = this.shareKey
215
+ ? sharedConnections.release(this.shareKey, this.db)
216
+ : true;
217
+ if (isLastHolder) {
218
+ this.db.close();
219
+ }
220
+ if (this.verbose) {
221
+ console.log(isLastHolder
222
+ ? `SQLite database closed (node:sqlite): ${this.filePath}`
223
+ : `SQLite database released (still in use): ${this.filePath}`);
224
+ }
225
+ }
226
+ catch (err) {
227
+ console.error(`Error closing database ${this.filePath}:`, err.message);
228
+ throw err;
229
+ }
230
+ finally {
231
+ this.db = null;
232
+ this.openPromise = null;
233
+ }
234
+ }
235
+ else {
236
+ this.db = null;
237
+ this.openPromise = null;
238
+ }
239
+ }
240
+ /**
241
+ * Gets the underlying `node:sqlite` `DatabaseSync` instance.
242
+ * Useful for operations not covered by this wrapper.
243
+ */
244
+ async getDbInstance() {
245
+ return this.ensureConnected();
246
+ }
247
+ }
248
+ exports.NodeSqliteAdapter = NodeSqliteAdapter;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /**
3
+ * `better-sqlite3`-backed entry point for MongoLite.
4
+ *
5
+ * The main `@semics-tech/mongolite` entry point defaults to `NodeSqliteAdapter`, backed by
6
+ * Node.js's built-in `node:sqlite` module, which requires Node.js 22.5.0+. Import
7
+ * from `@semics-tech/mongolite/better-sqlite3` instead if you need to run on an older
8
+ * Node.js runtime, or you simply prefer the more battle-tested `better-sqlite3`
9
+ * native addon.
10
+ *
11
+ * `better-sqlite3` is an **optional dependency** of this package — install it
12
+ * yourself if it wasn't already pulled in:
13
+ *
14
+ * ```bash
15
+ * npm install better-sqlite3
16
+ * ```
17
+ *
18
+ * ## Usage
19
+ *
20
+ * ```ts
21
+ * import { MongoLite } from '@semics-tech/mongolite/better-sqlite3';
22
+ *
23
+ * const client = new MongoLite('./myapp.sqlite');
24
+ * await client.connect();
25
+ * const users = client.collection('users');
26
+ * await users.insertOne({ name: 'Alice', age: 30 });
27
+ * await client.close();
28
+ * ```
29
+ */
30
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ var desc = Object.getOwnPropertyDescriptor(m, k);
33
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
34
+ desc = { enumerable: true, get: function() { return m[k]; } };
35
+ }
36
+ Object.defineProperty(o, k2, desc);
37
+ }) : (function(o, m, k, k2) {
38
+ if (k2 === undefined) k2 = k;
39
+ o[k2] = m[k];
40
+ }));
41
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
42
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.MongoLite = exports.SQLiteDB = exports.ChangeStream = exports.MongoLiteCollection = void 0;
46
+ const db_js_1 = require("./db.js");
47
+ const mongo_client_js_1 = require("./mongo-client.js");
48
+ var collection_js_1 = require("./collection.js");
49
+ Object.defineProperty(exports, "MongoLiteCollection", { enumerable: true, get: function () { return collection_js_1.MongoLiteCollection; } });
50
+ __exportStar(require("./types.js"), exports);
51
+ var changeStream_js_1 = require("./changeStream.js");
52
+ Object.defineProperty(exports, "ChangeStream", { enumerable: true, get: function () { return changeStream_js_1.ChangeStream; } });
53
+ var db_js_2 = require("./db.js");
54
+ Object.defineProperty(exports, "SQLiteDB", { enumerable: true, get: function () { return db_js_2.SQLiteDB; } });
55
+ /**
56
+ * MongoLite client backed by the `better-sqlite3` native addon.
57
+ *
58
+ * You can construct it with:
59
+ * - A file path string — uses the built-in `SQLiteDB` (`better-sqlite3`) adapter.
60
+ * - A `MongoLiteClientOptions` object — uses the built-in adapter with options.
61
+ * - An `IDatabaseAdapter` instance — use a custom adapter for other backends.
62
+ */
63
+ class MongoLite extends mongo_client_js_1.MongoLite {
64
+ constructor(dbPathOrOptions, options = {}) {
65
+ if (dbPathOrOptions &&
66
+ typeof dbPathOrOptions === 'object' &&
67
+ typeof dbPathOrOptions.connect === 'function' &&
68
+ typeof dbPathOrOptions.run === 'function' &&
69
+ typeof dbPathOrOptions.get === 'function' &&
70
+ typeof dbPathOrOptions.all === 'function' &&
71
+ typeof dbPathOrOptions.exec === 'function' &&
72
+ typeof dbPathOrOptions.close === 'function') {
73
+ super(dbPathOrOptions, options);
74
+ }
75
+ else {
76
+ super(new db_js_1.SQLiteDB(dbPathOrOptions), options);
77
+ }
78
+ }
79
+ }
80
+ exports.MongoLite = MongoLite;
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const query_debugger_js_1 = require("../debugger/query-debugger.js");
41
+ const path_1 = __importDefault(require("path"));
42
+ // CLI argument parsing
43
+ function parseArgs() {
44
+ const args = process.argv.slice(2);
45
+ const options = {};
46
+ for (let i = 0; i < args.length; i++) {
47
+ const arg = args[i];
48
+ if (arg === '--verbose' || arg === '-v') {
49
+ options.verbose = true;
50
+ }
51
+ else if (arg === '--collection' || arg === '-c') {
52
+ options.collection = args[++i];
53
+ }
54
+ else if (arg === '--database' || arg === '-d') {
55
+ options.database = args[++i];
56
+ }
57
+ else if (arg === '--help' || arg === '-h') {
58
+ options.help = true;
59
+ }
60
+ }
61
+ return options;
62
+ }
63
+ function showHelp() {
64
+ console.log('MongoLite Query Debugger');
65
+ console.log('');
66
+ console.log('Interactive debugging tool for MongoLite queries.');
67
+ console.log('');
68
+ console.log('Usage: npx mongolite-debug [options]');
69
+ console.log('');
70
+ console.log('Options:');
71
+ console.log(' -d, --database <path> Database file path (default: ./mongolite.db)');
72
+ console.log(' -c, --collection <name> Initial collection to use');
73
+ console.log(' -v, --verbose Enable verbose output');
74
+ console.log(' -h, --help Show this help');
75
+ console.log('');
76
+ console.log('Interactive Commands:');
77
+ console.log(' .help Show available commands');
78
+ console.log(' .collections List all collections');
79
+ console.log(' .use <collection> Select a collection to work with');
80
+ console.log(' .find <filter> Convert find filter to SQL and execute');
81
+ console.log(' .sql <query> Execute raw SQL query');
82
+ console.log(' .last Show last generated SQL');
83
+ console.log(' .sample [count] Show sample documents from current collection');
84
+ console.log(' .exit Exit the debugger');
85
+ console.log('');
86
+ console.log('Examples:');
87
+ console.log(' npx mongolite-debug -d ./myapp.db -c users');
88
+ console.log(' npx mongolite-debug --verbose');
89
+ console.log('');
90
+ console.log(' # Inside the debugger:');
91
+ console.log(' .use users');
92
+ console.log(' .find {"age": {"$gt": 25}}');
93
+ console.log(' .sql SELECT * FROM users WHERE json_extract(data, "$.age") > 25');
94
+ console.log('');
95
+ }
96
+ async function main() {
97
+ const options = parseArgs();
98
+ if (options.help) {
99
+ showHelp();
100
+ process.exit(0);
101
+ }
102
+ // Set default database path if not provided
103
+ if (!options.database) {
104
+ // Look for common database files in the current directory
105
+ const cwd = process.cwd();
106
+ const commonNames = [
107
+ 'mongolite.db',
108
+ 'mongolite.sqlite',
109
+ 'database.db',
110
+ 'database.sqlite',
111
+ 'app.db',
112
+ 'app.sqlite',
113
+ ];
114
+ // Check if any common database files exist
115
+ const { existsSync } = await Promise.resolve().then(() => __importStar(require('fs')));
116
+ let foundDb = false;
117
+ for (const name of commonNames) {
118
+ const dbPath = path_1.default.join(cwd, name);
119
+ if (existsSync(dbPath)) {
120
+ options.database = dbPath;
121
+ foundDb = true;
122
+ break;
123
+ }
124
+ }
125
+ if (!foundDb) {
126
+ options.database = path_1.default.join(cwd, 'mongolite.db');
127
+ }
128
+ }
129
+ console.log('🔍 MongoLite Query Debugger (npx version)');
130
+ console.log(`📁 Database: ${options.database}`);
131
+ if (options.collection) {
132
+ console.log(`📋 Initial collection: ${options.collection}`);
133
+ }
134
+ console.log('');
135
+ try {
136
+ const queryDebugger = new query_debugger_js_1.QueryDebugger(options);
137
+ await queryDebugger.start();
138
+ }
139
+ catch (error) {
140
+ console.error('Fatal error:', error instanceof Error ? error.message : error);
141
+ if (error instanceof Error && error.message.includes('SQLITE_CANTOPEN')) {
142
+ console.error('');
143
+ console.error('💡 Tip: Make sure the database file exists or specify a different path with -d');
144
+ console.error(' Example: npx mongolite-debug -d ./path/to/your/database.db');
145
+ }
146
+ process.exit(1);
147
+ }
148
+ }
149
+ main().catch((error) => {
150
+ console.error('Unexpected error:', error);
151
+ process.exit(1);
152
+ });