@semics-tech/mongolite 1.2.0 → 1.3.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/README.md +14 -1
- package/dist/bin/mongolite-debug.d.ts +6 -1
- package/dist/bin/mongolite-debug.js +6 -113
- package/dist/bin/mongolite-debug.js.map +1 -1
- package/dist/bin/mongolite.d.ts +13 -0
- package/dist/bin/mongolite.js +172 -0
- package/dist/bin/mongolite.js.map +1 -0
- package/dist/cjs/bin/mongolite-debug.js +6 -149
- package/dist/cjs/bin/mongolite.js +207 -0
- package/dist/cjs/cli/discover.js +156 -0
- package/dist/cjs/cli/explorer.js +929 -0
- package/dist/cjs/cli/filter-builder.js +276 -0
- package/dist/cjs/cli/format.js +161 -0
- package/dist/cjs/cli/schema.js +211 -0
- package/dist/cjs/cli/ui.js +431 -0
- package/dist/cjs/cursors/findCursor.js +20 -8
- package/dist/cli/discover.d.ts +40 -0
- package/dist/cli/discover.js +146 -0
- package/dist/cli/discover.js.map +1 -0
- package/dist/cli/explorer.d.ts +59 -0
- package/dist/cli/explorer.js +923 -0
- package/dist/cli/explorer.js.map +1 -0
- package/dist/cli/filter-builder.d.ts +52 -0
- package/dist/cli/filter-builder.js +268 -0
- package/dist/cli/filter-builder.js.map +1 -0
- package/dist/cli/format.d.ts +29 -0
- package/dist/cli/format.js +153 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/schema.d.ts +58 -0
- package/dist/cli/schema.js +205 -0
- package/dist/cli/schema.js.map +1 -0
- package/dist/cli/ui.d.ts +71 -0
- package/dist/cli/ui.js +379 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cursors/findCursor.d.ts +12 -0
- package/dist/cursors/findCursor.js +20 -8
- package/dist/cursors/findCursor.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,207 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.parseArgs = parseArgs;
|
|
38
|
+
/**
|
|
39
|
+
* `mongolite` — the guided explorer for MongoLite databases.
|
|
40
|
+
*
|
|
41
|
+
* Running it with no arguments is the intended path: it finds your database
|
|
42
|
+
* files, lists the collections it finds inside, and walks you through a query.
|
|
43
|
+
* Flags are shortcuts for people who already know where they are going, and
|
|
44
|
+
* `--repl` drops into the older command-driven debugger.
|
|
45
|
+
*/
|
|
46
|
+
const explorer_js_1 = require("../cli/explorer.js");
|
|
47
|
+
const query_debugger_js_1 = require("../debugger/query-debugger.js");
|
|
48
|
+
const ui_js_1 = require("../cli/ui.js");
|
|
49
|
+
function parseArgs(argv) {
|
|
50
|
+
const options = { verbose: false, help: false, version: false, repl: false };
|
|
51
|
+
const positional = [];
|
|
52
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
53
|
+
const arg = argv[index];
|
|
54
|
+
switch (arg) {
|
|
55
|
+
case '--database':
|
|
56
|
+
case '-d':
|
|
57
|
+
options.database = argv[++index];
|
|
58
|
+
break;
|
|
59
|
+
case '--collection':
|
|
60
|
+
case '-c':
|
|
61
|
+
options.collection = argv[++index];
|
|
62
|
+
break;
|
|
63
|
+
case '--verbose':
|
|
64
|
+
case '-v':
|
|
65
|
+
options.verbose = true;
|
|
66
|
+
break;
|
|
67
|
+
case '--repl':
|
|
68
|
+
case '--expert':
|
|
69
|
+
options.repl = true;
|
|
70
|
+
break;
|
|
71
|
+
case '--sample':
|
|
72
|
+
options.sampleSize = Number(argv[++index]);
|
|
73
|
+
break;
|
|
74
|
+
case '--page-size':
|
|
75
|
+
options.pageSize = Number(argv[++index]);
|
|
76
|
+
break;
|
|
77
|
+
case '--help':
|
|
78
|
+
case '-h':
|
|
79
|
+
options.help = true;
|
|
80
|
+
break;
|
|
81
|
+
case '--version':
|
|
82
|
+
case '-V':
|
|
83
|
+
options.version = true;
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
if (arg.startsWith('-')) {
|
|
87
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
88
|
+
}
|
|
89
|
+
positional.push(arg);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// `mongolite ./app.db users` is the shape people try first, so support it.
|
|
94
|
+
if (!options.database && positional[0])
|
|
95
|
+
options.database = positional[0];
|
|
96
|
+
if (!options.collection && positional[1])
|
|
97
|
+
options.collection = positional[1];
|
|
98
|
+
return options;
|
|
99
|
+
}
|
|
100
|
+
function showHelp() {
|
|
101
|
+
const lines = [
|
|
102
|
+
`${ui_js_1.style.bold('mongolite')} — explore a MongoLite (SQLite-backed) database`,
|
|
103
|
+
'',
|
|
104
|
+
ui_js_1.style.bold('Usage'),
|
|
105
|
+
' npx mongolite Start the guided explorer',
|
|
106
|
+
' npx mongolite ./app.db Open a specific database',
|
|
107
|
+
' npx mongolite ./app.db users Open a specific collection',
|
|
108
|
+
'',
|
|
109
|
+
ui_js_1.style.bold('Options'),
|
|
110
|
+
' -d, --database <path> Database file to open (otherwise you get a list to pick from)',
|
|
111
|
+
' -c, --collection <name> Collection to start in',
|
|
112
|
+
' --sample <n> Documents to sample when inferring fields (default 200)',
|
|
113
|
+
' --page-size <n> Results shown per page (default 20)',
|
|
114
|
+
' --repl Use the older command-driven debugger instead',
|
|
115
|
+
' -v, --verbose Log the SQL being built',
|
|
116
|
+
' -h, --help Show this help',
|
|
117
|
+
' -V, --version Show the version',
|
|
118
|
+
'',
|
|
119
|
+
ui_js_1.style.bold('In the explorer'),
|
|
120
|
+
' ↑/↓ move · type to filter the list · enter to choose · esc to go back · Ctrl+C to quit',
|
|
121
|
+
'',
|
|
122
|
+
'No MongoDB knowledge is needed: pick a field, pick a condition in plain English,',
|
|
123
|
+
'and the filter it builds is printed so you can learn the syntax as you go.',
|
|
124
|
+
'If you already know it, "Write a MongoDB filter yourself" and "Run raw SQL" are',
|
|
125
|
+
'under Advanced.',
|
|
126
|
+
'',
|
|
127
|
+
];
|
|
128
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Find the package version by walking up from the running script.
|
|
132
|
+
* Deliberately avoids `import.meta` and `__dirname` so the same source compiles
|
|
133
|
+
* for both the ESM and CommonJS builds.
|
|
134
|
+
*/
|
|
135
|
+
async function showVersion() {
|
|
136
|
+
const { readFileSync, existsSync } = await Promise.resolve().then(() => __importStar(require('node:fs')));
|
|
137
|
+
const { dirname, join, parse } = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
138
|
+
let dir = dirname(process.argv[1] ?? process.cwd());
|
|
139
|
+
const { root } = parse(dir);
|
|
140
|
+
while (dir && dir !== root) {
|
|
141
|
+
const candidate = join(dir, 'package.json');
|
|
142
|
+
if (existsSync(candidate)) {
|
|
143
|
+
try {
|
|
144
|
+
const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
145
|
+
if (pkg.version) {
|
|
146
|
+
process.stdout.write(`${pkg.version}\n`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Unreadable package.json - keep walking up.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
dir = dirname(dir);
|
|
155
|
+
}
|
|
156
|
+
process.stdout.write('unknown\n');
|
|
157
|
+
}
|
|
158
|
+
async function main() {
|
|
159
|
+
let options;
|
|
160
|
+
try {
|
|
161
|
+
options = parseArgs(process.argv.slice(2));
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
165
|
+
process.stderr.write('Run with --help to see the available options.\n');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (options.help) {
|
|
170
|
+
showHelp();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (options.version) {
|
|
174
|
+
await showVersion();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (options.repl) {
|
|
178
|
+
const queryDebugger = new query_debugger_js_1.QueryDebugger({
|
|
179
|
+
database: options.database,
|
|
180
|
+
collection: options.collection,
|
|
181
|
+
verbose: options.verbose,
|
|
182
|
+
});
|
|
183
|
+
await queryDebugger.start();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const explorer = new explorer_js_1.Explorer({
|
|
187
|
+
database: options.database,
|
|
188
|
+
collection: options.collection,
|
|
189
|
+
verbose: options.verbose,
|
|
190
|
+
sampleSize: Number.isFinite(options.sampleSize) ? options.sampleSize : undefined,
|
|
191
|
+
pageSize: Number.isFinite(options.pageSize) ? options.pageSize : undefined,
|
|
192
|
+
});
|
|
193
|
+
await explorer.run();
|
|
194
|
+
}
|
|
195
|
+
main()
|
|
196
|
+
.then(() => {
|
|
197
|
+
(0, ui_js_1.closePrompts)();
|
|
198
|
+
process.exit(0);
|
|
199
|
+
})
|
|
200
|
+
.catch((error) => {
|
|
201
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
202
|
+
process.stderr.write(`\n✖ ${text}\n`);
|
|
203
|
+
if (text.includes('SQLITE_CANTOPEN') || text.includes('unable to open')) {
|
|
204
|
+
process.stderr.write(' The file may not exist, or may not be readable.\n');
|
|
205
|
+
}
|
|
206
|
+
process.exit(1);
|
|
207
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
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.findDatabaseFiles = findDatabaseFiles;
|
|
7
|
+
exports.formatBytes = formatBytes;
|
|
8
|
+
exports.formatRelativeTime = formatRelativeTime;
|
|
9
|
+
exports.listCollectionInfo = listCollectionInfo;
|
|
10
|
+
exports.resolveDatabasePath = resolveDatabasePath;
|
|
11
|
+
exports.databaseExists = databaseExists;
|
|
12
|
+
/**
|
|
13
|
+
* Finding things for the user so they do not have to type paths or remember
|
|
14
|
+
* collection names: SQLite files on disk, and MongoLite-shaped tables inside
|
|
15
|
+
* a database.
|
|
16
|
+
*/
|
|
17
|
+
const node_fs_1 = require("node:fs");
|
|
18
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
19
|
+
const DB_EXTENSIONS = ['.db', '.sqlite', '.sqlite3', '.db3'];
|
|
20
|
+
const SKIP_DIRS = new Set([
|
|
21
|
+
'node_modules',
|
|
22
|
+
'.git',
|
|
23
|
+
'dist',
|
|
24
|
+
'build',
|
|
25
|
+
'coverage',
|
|
26
|
+
'.next',
|
|
27
|
+
'.cache',
|
|
28
|
+
'vendor',
|
|
29
|
+
'.venv',
|
|
30
|
+
'venv',
|
|
31
|
+
'__pycache__',
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Walk `root` looking for SQLite-looking files, skipping the usual noisy
|
|
35
|
+
* directories. Results are newest-first, since the file you just wrote is
|
|
36
|
+
* almost always the one you want.
|
|
37
|
+
*/
|
|
38
|
+
function findDatabaseFiles(root = process.cwd(), options = {}) {
|
|
39
|
+
const maxDepth = options.depth ?? 2;
|
|
40
|
+
const limit = options.limit ?? 50;
|
|
41
|
+
const found = [];
|
|
42
|
+
const walk = (dir, depth) => {
|
|
43
|
+
if (found.length >= limit || depth > maxDepth)
|
|
44
|
+
return;
|
|
45
|
+
let entries;
|
|
46
|
+
try {
|
|
47
|
+
entries = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return; // Unreadable directory - nothing useful to say about it.
|
|
51
|
+
}
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
if (found.length >= limit)
|
|
54
|
+
return;
|
|
55
|
+
const full = node_path_1.default.join(dir, entry.name);
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.'))
|
|
58
|
+
continue;
|
|
59
|
+
walk(full, depth + 1);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!entry.isFile())
|
|
63
|
+
continue;
|
|
64
|
+
if (!DB_EXTENSIONS.includes(node_path_1.default.extname(entry.name).toLowerCase()))
|
|
65
|
+
continue;
|
|
66
|
+
// WAL/SHM siblings are not databases you can open directly.
|
|
67
|
+
if (/-(wal|shm)$/.test(entry.name))
|
|
68
|
+
continue;
|
|
69
|
+
try {
|
|
70
|
+
const stats = (0, node_fs_1.statSync)(full);
|
|
71
|
+
found.push({
|
|
72
|
+
path: full,
|
|
73
|
+
relativePath: node_path_1.default.relative(root, full) || entry.name,
|
|
74
|
+
sizeBytes: stats.size,
|
|
75
|
+
modified: stats.mtime,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// File vanished between readdir and stat - ignore.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
walk(root, 0);
|
|
84
|
+
found.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
85
|
+
return found;
|
|
86
|
+
}
|
|
87
|
+
function formatBytes(bytes) {
|
|
88
|
+
if (bytes < 1024)
|
|
89
|
+
return `${bytes} B`;
|
|
90
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
91
|
+
let value = bytes / 1024;
|
|
92
|
+
let unit = 0;
|
|
93
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
94
|
+
value /= 1024;
|
|
95
|
+
unit += 1;
|
|
96
|
+
}
|
|
97
|
+
return `${value >= 10 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
98
|
+
}
|
|
99
|
+
function formatRelativeTime(date, now = new Date()) {
|
|
100
|
+
const seconds = Math.max(0, Math.round((now.getTime() - date.getTime()) / 1000));
|
|
101
|
+
if (seconds < 60)
|
|
102
|
+
return 'just now';
|
|
103
|
+
const minutes = Math.round(seconds / 60);
|
|
104
|
+
if (minutes < 60)
|
|
105
|
+
return `${minutes}m ago`;
|
|
106
|
+
const hours = Math.round(minutes / 60);
|
|
107
|
+
if (hours < 24)
|
|
108
|
+
return `${hours}h ago`;
|
|
109
|
+
const days = Math.round(hours / 24);
|
|
110
|
+
if (days < 30)
|
|
111
|
+
return `${days}d ago`;
|
|
112
|
+
return date.toISOString().slice(0, 10);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* List the tables in the database, flagging which ones look like MongoLite
|
|
116
|
+
* collections (an `_id` column plus a `data` column). Foreign tables are still
|
|
117
|
+
* reported so a user pointed at the wrong file gets a useful message rather
|
|
118
|
+
* than an empty list.
|
|
119
|
+
*/
|
|
120
|
+
async function listCollectionInfo(db) {
|
|
121
|
+
const tables = await db.all(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
|
|
122
|
+
const infos = [];
|
|
123
|
+
for (const { name } of tables) {
|
|
124
|
+
let isMongoLite = false;
|
|
125
|
+
try {
|
|
126
|
+
const columns = await db.all(`PRAGMA table_info("${name}")`);
|
|
127
|
+
const columnNames = new Set(columns.map((column) => column.name));
|
|
128
|
+
isMongoLite = columnNames.has('_id') && columnNames.has('data');
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
isMongoLite = false;
|
|
132
|
+
}
|
|
133
|
+
let count = 0;
|
|
134
|
+
try {
|
|
135
|
+
const row = await db.get(`SELECT COUNT(*) as count FROM "${name}"`);
|
|
136
|
+
count = row?.count ?? 0;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
count = 0;
|
|
140
|
+
}
|
|
141
|
+
infos.push({ name, count, isMongoLite });
|
|
142
|
+
}
|
|
143
|
+
return infos;
|
|
144
|
+
}
|
|
145
|
+
/** Resolve a user-supplied database path, allowing `:memory:` and `~` shorthand. */
|
|
146
|
+
function resolveDatabasePath(input) {
|
|
147
|
+
if (input === ':memory:')
|
|
148
|
+
return input;
|
|
149
|
+
const expanded = input.startsWith('~')
|
|
150
|
+
? node_path_1.default.join(process.env.HOME ?? '', input.slice(1))
|
|
151
|
+
: input;
|
|
152
|
+
return node_path_1.default.resolve(expanded);
|
|
153
|
+
}
|
|
154
|
+
function databaseExists(dbPath) {
|
|
155
|
+
return dbPath === ':memory:' || (0, node_fs_1.existsSync)(dbPath);
|
|
156
|
+
}
|