jsql-neo 4.3.0 → 4.4.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 +64 -0
- package/bin/jsql +138 -0
- package/index.d.ts +351 -0
- package/index.js +18 -0
- package/lib/migrate.js +242 -0
- package/lib/mysql_server.js +51 -1
- package/lib/redis_server.js +448 -0
- package/lib/sql.js +125 -51
- package/lib/web_ui.js +226 -0
- package/package.json +66 -56
- package/test/smoke.js +58 -0
- package/wasm/browser.d.ts +88 -0
package/README.md
CHANGED
|
@@ -152,6 +152,67 @@ const ds = new DataSource({
|
|
|
152
152
|
|
|
153
153
|
Supported for ORM compatibility: prepared statements (`COM_STMT_PREPARE`/`EXECUTE`), binary protocol result sets, `SHOW COLUMNS` / `SHOW INDEX` / `SHOW CREATE TABLE` / `SHOW VARIABLES` / `SHOW GRANTS`, `information_schema` queries, `START TRANSACTION`, `TRUNCATE TABLE`, `SET` statements, scalar functions (`VERSION()`, `NOW()`, `CONCAT()`, `IFNULL()`, ...), and MySQL DDL forms (`int unsigned`, `auto_increment`, `ENGINE=InnoDB`, `DEFAULT CHARSET`).
|
|
154
154
|
|
|
155
|
+
## Redis-compatible server
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
jsql redis -p 6379 --data-dir ./redis-data [--auth secret]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
or from code:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
const { createRedisServer } = require('jsql-neo');
|
|
165
|
+
createRedisServer({ port: 6379, dataDir: './redis-data' }).listen();
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Speaks RESP2 (works with `redis-cli` / ioredis / node-redis). Strings, hashes, lists, sets, counters, TTL, `KEYS`/`EXISTS`/`DEL`, multi-select DBs, `AUTH`, and snapshot persistence to `data.rdb.json` (debounced 500ms + on shutdown).
|
|
169
|
+
|
|
170
|
+
## Web UI
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
jsql ui -p 8080 --data-dir ./data # then open http://localhost:8080
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
or from code: `new WebUI({ port: 8080, dataDir: './data' }).start()`. Zero-dependency management console: browse databases/tables, run SQL in the browser.
|
|
177
|
+
|
|
178
|
+
## CLI
|
|
179
|
+
|
|
180
|
+
`jsql serve` (foreground MySQL server), `jsql server start|stop|status` (background daemon), `jsql import <dump.sql|.json|.csv>`, `jsql export <table> <file>`, `jsql bench`, `jsql redis`, `jsql ui`, `jsql mod` (plugin registry), `jsql version`.
|
|
181
|
+
|
|
182
|
+
## Browser playground
|
|
183
|
+
|
|
184
|
+
`examples/playground/` is a self-contained SQL playground that runs the full engine in the browser (WASM + IndexedDB, no server):
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
cd examples/playground && npm install && npm run dev
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Testing
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
npm test # zero-dependency SQL engine smoke tests
|
|
194
|
+
npm run test:orms # ORM compatibility suites (start examples/orms/start-server.js first)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The ORM suites in `examples/orms/` run Sequelize (10/10), Knex (9/9) and TypeORM (8/8) against a live MySQL-compatible server.
|
|
198
|
+
|
|
199
|
+
## Benchmark
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
cd bench && npm install && npm run bench
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`bench/bench.js` compares the four engines on 100k rows (insert / point query / range query / count / update):
|
|
206
|
+
|
|
207
|
+
| Engine | Insert/s | Point query (500x) | Range query (500x) | Total |
|
|
208
|
+
|--------|----------|--------------------|--------------------|-------|
|
|
209
|
+
| **Native** (Rust N-API) | 0.66M | 930ms | 685ms | **1.77s** |
|
|
210
|
+
| better-sqlite3 (WAL) | 0.40M | 3258ms | 149ms | 3.66s |
|
|
211
|
+
| sql.js (WASM sqlite) | 0.30M | 5852ms | 366ms | 6.57s |
|
|
212
|
+
| Pure JS | 0.38M | 11278ms | 18138ms | 29.7s |
|
|
213
|
+
|
|
214
|
+
Native is ~2x faster than better-sqlite3 and ~17x faster than the pure-JS engine overall (Linux x64 / Node 24). Full breakdown: `bench/report.md`.
|
|
215
|
+
|
|
155
216
|
## Features
|
|
156
217
|
|
|
157
218
|
- Three engines: Native (N-API Rust), WASM (wasm-pack Rust), Pure JS (local JSON)
|
|
@@ -163,3 +224,6 @@ Supported for ORM compatibility: prepared statements (`COM_STMT_PREPARE`/`EXECUT
|
|
|
163
224
|
- Batch insert / update / delete
|
|
164
225
|
- Cursor-based pagination
|
|
165
226
|
- Transaction support (server engine)
|
|
227
|
+
- Built-in web management UI (`jsql ui` / `new WebUI(...)`): browse databases & tables, run SQL from the browser
|
|
228
|
+
- CLI: `jsql serve|server|import|export|bench|ui|mod|version`
|
|
229
|
+
- Migration tools: mysqldump import, JSON/CSV export/import
|
package/bin/jsql
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
2
4
|
const yaggs = require('@vexify-org/yaggs');
|
|
3
5
|
const { ModuleManager } = require('../lib/mod');
|
|
6
|
+
const Database = require('../lib/database');
|
|
7
|
+
const migrate = require('../lib/migrate');
|
|
4
8
|
|
|
5
9
|
function printModules(list) {
|
|
6
10
|
if (list.length === 0) {
|
|
@@ -93,6 +97,140 @@ const cli = yaggs()
|
|
|
93
97
|
fail(e);
|
|
94
98
|
}
|
|
95
99
|
})
|
|
100
|
+
.command('export', 'Export a table to JSON or CSV', (sub) => {
|
|
101
|
+
sub.option('db', { type: 'string', description: 'Database file path (default ./db.json)' });
|
|
102
|
+
sub.option('json', { type: 'boolean', description: 'Output metadata as JSON' });
|
|
103
|
+
}, async (argv) => {
|
|
104
|
+
const table = argv._[0];
|
|
105
|
+
const outFile = argv._[1];
|
|
106
|
+
const fail = (e) => { console.error(`Error: ${e.message}`); process.exitCode = 1; };
|
|
107
|
+
try {
|
|
108
|
+
if (!table || !outFile) throw new Error('Usage: jsql export <table> <file.json|csv> [--db <path>]');
|
|
109
|
+
const db = new Database(argv.db || './db.json', { autoSave: false });
|
|
110
|
+
await db.loadDatabase ? db.loadDatabase() : null;
|
|
111
|
+
const bytes = await migrate.exportToFile(db, table, outFile);
|
|
112
|
+
if (argv.json) console.log(JSON.stringify({ ok: true, table, file: outFile, bytes }));
|
|
113
|
+
else console.log(`Exported '${table}' (${bytes} bytes) to ${outFile}`);
|
|
114
|
+
} catch (e) { fail(e); }
|
|
115
|
+
})
|
|
116
|
+
.command('import', 'Import a mysqldump (.sql), JSON (.json) or CSV (.csv) file', (sub) => {
|
|
117
|
+
sub.option('db', { type: 'string', description: 'Database file path (default ./db.json)' });
|
|
118
|
+
sub.option('table', { type: 'string', description: 'Target table for CSV import' });
|
|
119
|
+
sub.option('strict', { type: 'boolean', description: 'Abort on first import error' });
|
|
120
|
+
sub.option('json', { type: 'boolean', description: 'Output metadata as JSON' });
|
|
121
|
+
}, async (argv) => {
|
|
122
|
+
const file = argv._[0];
|
|
123
|
+
const fail = (e) => { console.error(`Error: ${e.message}`); process.exitCode = 1; };
|
|
124
|
+
try {
|
|
125
|
+
if (!file) throw new Error('Usage: jsql import <file.sql|.json|.csv> [--db <path>] [--table <name>]');
|
|
126
|
+
const db = new Database(argv.db || './db.json', { autoSave: false });
|
|
127
|
+
await db.loadDatabase ? db.loadDatabase() : null;
|
|
128
|
+
const ext = path.extname(file).toLowerCase();
|
|
129
|
+
let result;
|
|
130
|
+
if (ext === '.sql') {
|
|
131
|
+
result = await migrate.importDumpFile(db, file, { strict: !!argv.strict });
|
|
132
|
+
} else if (ext === '.json') {
|
|
133
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
134
|
+
result = await migrate.importFromJSON(db, data);
|
|
135
|
+
} else if (ext === '.csv') {
|
|
136
|
+
if (!argv.table) throw new Error('CSV import requires --table <name>');
|
|
137
|
+
const schemaFile = file.replace(/\.csv$/i, '.schema.json');
|
|
138
|
+
const schema = fs.existsSync(schemaFile) ? JSON.parse(fs.readFileSync(schemaFile, 'utf8')) : null;
|
|
139
|
+
result = await migrate.importFromCSV(db, argv.table, fs.readFileSync(file, 'utf8'), { schema });
|
|
140
|
+
} else {
|
|
141
|
+
throw new Error('Unsupported file type: ' + ext);
|
|
142
|
+
}
|
|
143
|
+
if (argv.json) {
|
|
144
|
+
console.log(JSON.stringify({ ok: true, ...result }));
|
|
145
|
+
} else {
|
|
146
|
+
const errs = result.errors && result.errors.length;
|
|
147
|
+
console.log(`Imported: ${result.created ? result.created.length + ' table(s), ' : ''}${result.inserted || 0} row(s)` + (errs ? `, ${errs} error(s)` : ''));
|
|
148
|
+
}
|
|
149
|
+
} catch (e) { fail(e); }
|
|
150
|
+
})
|
|
151
|
+
.command('serve', 'Run the MySQL-compatible server in the foreground', (sub) => {
|
|
152
|
+
sub.option('port', { alias: 'p', type: 'number', description: 'Listen port (default 3306)' });
|
|
153
|
+
sub.option('host', { type: 'string', description: 'Listen host (default 127.0.0.1)' });
|
|
154
|
+
sub.option('data-dir', { type: 'string', description: 'Directory to store databases' });
|
|
155
|
+
sub.option('no-auth', { type: 'boolean', description: 'Allow connections without authentication' });
|
|
156
|
+
sub.option('log', { type: 'boolean', description: 'Log queries to stdout' });
|
|
157
|
+
}, async (argv) => {
|
|
158
|
+
const { createMysqlServer } = require('../lib/mysql_server');
|
|
159
|
+
const options = {
|
|
160
|
+
port: argv.port != null ? argv.port : 3306,
|
|
161
|
+
host: argv.host || '127.0.0.1',
|
|
162
|
+
dataDir: argv['data-dir'],
|
|
163
|
+
noAuth: argv['no-auth'] === true,
|
|
164
|
+
};
|
|
165
|
+
if (argv.log) options.onQuery = (q) => console.log('[sql]', q);
|
|
166
|
+
const srv = createMysqlServer(options);
|
|
167
|
+
srv.listen();
|
|
168
|
+
console.log(`jsql-neo MySQL server on ${options.host}:${options.port}${options.dataDir ? ' (data: ' + options.dataDir + ')' : ' (in-memory)'}`);
|
|
169
|
+
setInterval(() => {}, 1 << 30);
|
|
170
|
+
})
|
|
171
|
+
.command('bench', 'Insert + query benchmark against a data directory', (sub) => {
|
|
172
|
+
sub.option('n', { type: 'number', description: 'Number of rows (default 10000)' });
|
|
173
|
+
sub.option('data-dir', { type: 'string', description: 'Data directory (default :memory:)' });
|
|
174
|
+
sub.option('json', { type: 'boolean', description: 'Output as JSON' });
|
|
175
|
+
}, async (argv) => {
|
|
176
|
+
const N = argv.n || 10000;
|
|
177
|
+
const dataDir = argv['data-dir'] || ':memory:';
|
|
178
|
+
try {
|
|
179
|
+
const db = new Database(dataDir === ':memory:' ? ':memory:' : path.join(dataDir, 'bench.json'), { autoSave: false });
|
|
180
|
+
await db.createTable('bench', { id: { type: 'integer', primaryKey: true, autoIncrement: true }, name: { type: 'string' }, score: { type: 'float' } });
|
|
181
|
+
let t0 = Date.now();
|
|
182
|
+
const batch = [];
|
|
183
|
+
for (let i = 0; i < N; i++) batch.push({ name: 'user_' + i, score: Math.random() * 1000 });
|
|
184
|
+
for (let i = 0; i < batch.length; i += 1000) await db.insert('bench', batch.slice(i, i + 1000));
|
|
185
|
+
const insertMs = Date.now() - t0;
|
|
186
|
+
t0 = Date.now();
|
|
187
|
+
let hits = 0;
|
|
188
|
+
for (let i = 0; i < 100; i++) hits += (await db.find('bench', { score: { $gt: 500 } }, { limit: 10 })).length;
|
|
189
|
+
const queryMs = Date.now() - t0;
|
|
190
|
+
const total = await db.count('bench');
|
|
191
|
+
const out = { ok: true, rows: N, inserted: total, insertMs, insertPerSec: Math.round(N / (insertMs / 1000)), queryMs, queryCount: 100 };
|
|
192
|
+
if (argv.json) console.log(JSON.stringify(out));
|
|
193
|
+
else console.log(`Inserted ${total} rows in ${insertMs}ms (${out.insertPerSec}/s), 100 queries in ${queryMs}ms`);
|
|
194
|
+
} catch (e) { console.error(`Error: ${e.message}`); process.exitCode = 1; }
|
|
195
|
+
})
|
|
196
|
+
.command('ui', 'Serve the built-in web management console', (sub) => {
|
|
197
|
+
sub.option('port', { alias: 'p', type: 'number', description: 'HTTP port (default 8080)' });
|
|
198
|
+
sub.option('host', { type: 'string', description: 'Listen host (default 0.0.0.0)' });
|
|
199
|
+
sub.option('data-dir', { type: 'string', description: 'Directory containing *.json databases (default .)' });
|
|
200
|
+
sub.option('readonly', { type: 'boolean', description: 'Never write back to disk' });
|
|
201
|
+
}, async (argv) => {
|
|
202
|
+
const { WebUI } = require('../lib/web_ui');
|
|
203
|
+
const ui = new WebUI({
|
|
204
|
+
port: argv.port != null ? argv.port : 8080,
|
|
205
|
+
host: argv.host || '0.0.0.0',
|
|
206
|
+
dataDir: argv['data-dir'] || '.',
|
|
207
|
+
readonly: argv.readonly === true,
|
|
208
|
+
});
|
|
209
|
+
try {
|
|
210
|
+
const port = await ui.start();
|
|
211
|
+
console.log(`JSQL-NEO web UI on http://${argv.host || '0.0.0.0'}:${port} (data: ${ui.dataDir})`);
|
|
212
|
+
setInterval(() => {}, 1 << 30);
|
|
213
|
+
} catch (e) { console.error(`Error: ${e.message}`); process.exitCode = 1; }
|
|
214
|
+
})
|
|
215
|
+
.command('redis', 'Run the Redis-compatible server', (sub) => {
|
|
216
|
+
sub.option('port', { alias: 'p', type: 'number', description: 'Listen port (default 6379)' });
|
|
217
|
+
sub.option('host', { type: 'string', description: 'Listen host (default 127.0.0.1)' });
|
|
218
|
+
sub.option('data-dir', { type: 'string', description: 'Directory for data.rdb.json snapshot' });
|
|
219
|
+
sub.option('auth', { type: 'string', description: 'Require this password for AUTH' });
|
|
220
|
+
sub.option('log', { type: 'boolean', description: 'Log commands to stdout' });
|
|
221
|
+
}, (argv) => {
|
|
222
|
+
const { createRedisServer } = require('../lib/redis_server');
|
|
223
|
+
const srv = createRedisServer({
|
|
224
|
+
port: argv.port != null ? argv.port : 6379,
|
|
225
|
+
host: argv.host || '127.0.0.1',
|
|
226
|
+
dataDir: argv['data-dir'],
|
|
227
|
+
password: argv.auth || null,
|
|
228
|
+
onQuery: argv.log ? (c) => console.log('[redis]', c.join(' ')) : null,
|
|
229
|
+
});
|
|
230
|
+
srv.listen();
|
|
231
|
+
console.log(`jsql-neo Redis server on ${argv.host || '127.0.0.1'}:${argv.port != null ? argv.port : 6379}${argv['data-dir'] ? ' (data: ' + argv['data-dir'] + ')' : ''}`);
|
|
232
|
+
setInterval(() => {}, 1 << 30);
|
|
233
|
+
})
|
|
96
234
|
.command('version', 'Show version', null, () => {
|
|
97
235
|
console.log(require('../package.json').version);
|
|
98
236
|
})
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSQL-NEO — Rust-powered embedded database (Native / WASM / Pure JS)
|
|
3
|
+
* with a MySQL-compatible server mode.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare namespace JSQLNeo {
|
|
7
|
+
export type FieldType =
|
|
8
|
+
| 'string' | 'text' | 'varchar'
|
|
9
|
+
| 'integer' | 'int' | 'bigint'
|
|
10
|
+
| 'float' | 'double' | 'number'
|
|
11
|
+
| 'boolean'
|
|
12
|
+
| 'date' | 'datetime' | 'timestamp'
|
|
13
|
+
| 'object' | 'json' | 'array'
|
|
14
|
+
| 'any' | 'binary';
|
|
15
|
+
|
|
16
|
+
export interface FieldDef {
|
|
17
|
+
type: FieldType;
|
|
18
|
+
primaryKey?: boolean;
|
|
19
|
+
autoIncrement?: boolean;
|
|
20
|
+
unique?: boolean;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
nullable?: boolean;
|
|
23
|
+
length?: number;
|
|
24
|
+
maxLength?: number;
|
|
25
|
+
min?: number;
|
|
26
|
+
max?: number;
|
|
27
|
+
default?: unknown;
|
|
28
|
+
check?: string;
|
|
29
|
+
ref?: string;
|
|
30
|
+
computed?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type Schema = Record<string, FieldDef>;
|
|
34
|
+
|
|
35
|
+
export interface Row {
|
|
36
|
+
id: number | string;
|
|
37
|
+
fields?: Record<string, unknown>;
|
|
38
|
+
created_at?: string;
|
|
39
|
+
updated_at?: string;
|
|
40
|
+
[key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface FindOptions {
|
|
44
|
+
limit?: number;
|
|
45
|
+
offset?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface JSQLOptions {
|
|
49
|
+
dataDir?: string;
|
|
50
|
+
flushThreshold?: number;
|
|
51
|
+
modules?: boolean;
|
|
52
|
+
persistence?: boolean;
|
|
53
|
+
dbName?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type JSQLHook =
|
|
57
|
+
| 'beforeInsert' | 'afterInsert'
|
|
58
|
+
| 'beforeUpdate' | 'afterUpdate'
|
|
59
|
+
| 'beforeDelete' | 'afterDelete'
|
|
60
|
+
| 'beforeFind' | 'afterFind'
|
|
61
|
+
| 'beforeCreateTable' | 'afterCreateTable'
|
|
62
|
+
| 'beforeDropTable' | 'afterDropTable'
|
|
63
|
+
| 'beforeFlush' | 'afterFlush'
|
|
64
|
+
| 'beforeCount' | 'afterCount'
|
|
65
|
+
| 'onStart' | 'onStop';
|
|
66
|
+
|
|
67
|
+
export interface JSQLPlugin {
|
|
68
|
+
name?: string;
|
|
69
|
+
install?(db: JSQL, ctx: PluginContext): void;
|
|
70
|
+
onEvent?(event: string, data: unknown): void;
|
|
71
|
+
hooks?: Partial<Record<JSQLHook, (...args: any[]) => unknown>>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface PluginContext {
|
|
75
|
+
name: string;
|
|
76
|
+
engine: JSQL;
|
|
77
|
+
plugin: JSQLPlugin;
|
|
78
|
+
on(hook: JSQLHook, fn: (...args: any[]) => unknown): JSQL;
|
|
79
|
+
onEvent(fn: (event: string, data: unknown) => void): JSQL;
|
|
80
|
+
emit(eventName: string, data: unknown): void;
|
|
81
|
+
tables(): string[];
|
|
82
|
+
hasTable(name: string): boolean;
|
|
83
|
+
getTableSchema(name: string): Schema | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class JSQL {
|
|
87
|
+
constructor(opts?: JSQLOptions);
|
|
88
|
+
use(plugin: JSQLPlugin | ((db: JSQL) => void)): this;
|
|
89
|
+
on(event: JSQLHook, fn: (...args: any[]) => unknown): this;
|
|
90
|
+
onEvent(fn: (event: string, data: unknown) => void): this;
|
|
91
|
+
start(): Promise<void>;
|
|
92
|
+
stop(): Promise<void>;
|
|
93
|
+
flush(): Promise<void>;
|
|
94
|
+
createTable(name: string, schema: Schema): Promise<unknown>;
|
|
95
|
+
dropTable(name: string): Promise<unknown>;
|
|
96
|
+
insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<unknown>;
|
|
97
|
+
insertMany(table: string, data: Record<string, unknown>[]): Promise<unknown>;
|
|
98
|
+
findById(table: string, id: number | string | bigint): Promise<Row | null>;
|
|
99
|
+
findByIds(table: string, ids: Array<number | string>): Promise<Row[] | null>;
|
|
100
|
+
find(table: string, filter?: Record<string, unknown>, opts?: FindOptions): Promise<Row[]>;
|
|
101
|
+
count(table: string): Promise<number>;
|
|
102
|
+
updateById(table: string, id: number | string, data: Record<string, unknown>): Promise<unknown>;
|
|
103
|
+
updateByIds(table: string, entries: Array<[number | string, Record<string, unknown>]>): Promise<unknown>;
|
|
104
|
+
removeById(table: string, id: number | string): Promise<unknown>;
|
|
105
|
+
removeByIds(table: string, ids: Array<number | string>): Promise<unknown>;
|
|
106
|
+
hasTable(name: string): Promise<boolean>;
|
|
107
|
+
getTables(): Promise<string[]>;
|
|
108
|
+
getTableSchema(name: string): Promise<Schema | null>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export class NativeJSQL extends JSQL {}
|
|
112
|
+
|
|
113
|
+
export interface MysqlServerOptions {
|
|
114
|
+
port?: number;
|
|
115
|
+
host?: string;
|
|
116
|
+
dataDir?: string;
|
|
117
|
+
defaultDatabase?: string;
|
|
118
|
+
noAuth?: boolean;
|
|
119
|
+
auth?: Record<string, string>;
|
|
120
|
+
allowComments?: boolean;
|
|
121
|
+
safety?: boolean;
|
|
122
|
+
maxConnections?: number;
|
|
123
|
+
handshakeTimeout?: number;
|
|
124
|
+
maxAuthFails?: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export class MysqlServer {
|
|
128
|
+
constructor(options?: MysqlServerOptions);
|
|
129
|
+
listen(cb?: (err?: Error) => void): this;
|
|
130
|
+
close(cb?: () => void): void;
|
|
131
|
+
readonly address: { port: number; address: string; family: string } | null;
|
|
132
|
+
listDatabases(): Promise<string[]>;
|
|
133
|
+
createDatabase(name: string, opts?: { ifNotExists?: boolean }): Promise<void>;
|
|
134
|
+
dropDatabase(name: string, opts?: { ifExists?: boolean }): Promise<void>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function createMysqlServer(options?: MysqlServerOptions): MysqlServer;
|
|
138
|
+
|
|
139
|
+
export interface RedisServerOptions {
|
|
140
|
+
port?: number;
|
|
141
|
+
host?: string;
|
|
142
|
+
password?: string | null;
|
|
143
|
+
dataDir?: string | null;
|
|
144
|
+
onQuery?: (cmd: string, args: string[]) => void;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export class RedisServer {
|
|
148
|
+
constructor(options?: RedisServerOptions);
|
|
149
|
+
listen(): this;
|
|
150
|
+
stop(): void;
|
|
151
|
+
execute(cmd: string, args: string[]): string | number | string[] | null | 'OK' | 'PONG';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function createRedisServer(options?: RedisServerOptions): RedisServer;
|
|
155
|
+
|
|
156
|
+
export interface WebUIOptions {
|
|
157
|
+
port?: number;
|
|
158
|
+
host?: string;
|
|
159
|
+
dataDir?: string;
|
|
160
|
+
readonly?: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export class WebUI {
|
|
164
|
+
constructor(options?: WebUIOptions);
|
|
165
|
+
start(): Promise<number>;
|
|
166
|
+
stop(): Promise<void>;
|
|
167
|
+
listDatabases(): { name: string; tables: number }[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface MigrateResult {
|
|
171
|
+
created?: string[];
|
|
172
|
+
inserted?: number;
|
|
173
|
+
errors?: { line?: number; error: string }[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface MigrateTools {
|
|
177
|
+
normalizeSchema(schema: Record<string, unknown>): Record<string, unknown>;
|
|
178
|
+
parseCSV(text: string): string[][];
|
|
179
|
+
toCSV(rows: Record<string, unknown>[], columns?: string[]): string;
|
|
180
|
+
exportTableToJSON(db: unknown, table: string): { schema: unknown; rows: unknown[] };
|
|
181
|
+
exportAllToJSON(db: unknown): Record<string, unknown>;
|
|
182
|
+
importFromJSON(db: unknown, data: Record<string, unknown>): Promise<MigrateResult>;
|
|
183
|
+
exportTableToCSV(db: unknown, table: string): string;
|
|
184
|
+
importFromCSV(db: unknown, table: string, csv: string, opts?: { schema?: unknown }): Promise<MigrateResult>;
|
|
185
|
+
importDump(db: unknown, sql: string, opts?: { strict?: boolean }): Promise<MigrateResult>;
|
|
186
|
+
importDumpFile(db: unknown, file: string, opts?: { strict?: boolean }): Promise<MigrateResult>;
|
|
187
|
+
exportToFile(db: unknown, table: string, outFile: string): Promise<number>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const migrate: MigrateTools;
|
|
191
|
+
export function exportTableToJSON(db: unknown, table: string): { schema: unknown; rows: unknown[] };
|
|
192
|
+
export function exportAllToJSON(db: unknown): Record<string, unknown>;
|
|
193
|
+
export function importFromJSON(db: unknown, data: Record<string, unknown>): Promise<MigrateResult>;
|
|
194
|
+
export function exportTableToCSV(db: unknown, table: string): string;
|
|
195
|
+
export function importFromCSV(db: unknown, table: string, csv: string, opts?: { schema?: unknown }): Promise<MigrateResult>;
|
|
196
|
+
export function importDump(db: unknown, sql: string, opts?: { strict?: boolean }): Promise<MigrateResult>;
|
|
197
|
+
export function importDumpFile(db: unknown, file: string, opts?: { strict?: boolean }): Promise<MigrateResult>;
|
|
198
|
+
export function exportToFile(db: unknown, table: string, outFile: string): Promise<number>;
|
|
199
|
+
|
|
200
|
+
export interface ConnectionOptions {
|
|
201
|
+
host?: string;
|
|
202
|
+
port?: number;
|
|
203
|
+
user?: string;
|
|
204
|
+
password?: string;
|
|
205
|
+
database?: string;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface ConnectionResult {
|
|
209
|
+
insertId: number | null;
|
|
210
|
+
affectedRows: number;
|
|
211
|
+
rows: unknown[];
|
|
212
|
+
fields?: unknown[];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export class Connection {
|
|
216
|
+
constructor(options?: ConnectionOptions);
|
|
217
|
+
connect(cb?: (err?: Error) => void): void;
|
|
218
|
+
query(sql: string, params?: unknown[], cb?: (err: Error | null, result: ConnectionResult) => void): void;
|
|
219
|
+
execute(sql: string, params?: unknown[], cb?: (err: Error | null, result: ConnectionResult) => void): void;
|
|
220
|
+
beginTransaction(cb?: (err?: Error) => void): void;
|
|
221
|
+
commit(cb?: (err?: Error) => void): void;
|
|
222
|
+
rollback(cb?: (err?: Error) => void): void;
|
|
223
|
+
end(): void;
|
|
224
|
+
format(sql: string, params?: unknown[]): string;
|
|
225
|
+
escape(value: unknown): string;
|
|
226
|
+
escapeId(value: string): string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export class Pool {
|
|
230
|
+
constructor(options?: ConnectionOptions & { max?: number });
|
|
231
|
+
query(sql: string, params?: unknown[], cb?: (err: Error | null, result: ConnectionResult) => void): void;
|
|
232
|
+
getConnection(cb: (err: Error | null, conn: Connection) => void): void;
|
|
233
|
+
end(): void;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function createConnection(options?: ConnectionOptions): Connection;
|
|
237
|
+
export function createPool(options?: ConnectionOptions & { max?: number }): Pool;
|
|
238
|
+
|
|
239
|
+
export interface SQLResult {
|
|
240
|
+
ok: boolean;
|
|
241
|
+
type: string;
|
|
242
|
+
table?: string | null;
|
|
243
|
+
columns?: string[];
|
|
244
|
+
rows?: unknown[][];
|
|
245
|
+
raw?: unknown[];
|
|
246
|
+
affectedRows?: number;
|
|
247
|
+
insertId?: number | null;
|
|
248
|
+
ids?: unknown[];
|
|
249
|
+
error?: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface SQLOptions {
|
|
253
|
+
allowComments?: boolean;
|
|
254
|
+
safety?: boolean;
|
|
255
|
+
maxStatements?: number;
|
|
256
|
+
session?: Record<string, unknown>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function executeSQL(engine: unknown, sql: string, paramsOrOpts?: unknown[] | SQLOptions, opts?: SQLOptions): Promise<SQLResult>;
|
|
260
|
+
export function parseSQL(sql: string): unknown;
|
|
261
|
+
export function splitStatements(sql: string): string[];
|
|
262
|
+
export function applyParams(sql: string, values: unknown[]): string;
|
|
263
|
+
export function escapeValue(value: unknown): string;
|
|
264
|
+
export function escapeId(value: string): string;
|
|
265
|
+
|
|
266
|
+
export interface DatabaseOptions {
|
|
267
|
+
dataDir?: string;
|
|
268
|
+
persist?: boolean;
|
|
269
|
+
inMemoryOnly?: boolean;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export class Database {
|
|
273
|
+
constructor(options?: DatabaseOptions);
|
|
274
|
+
createTable(name: string, schema: Schema): Promise<unknown>;
|
|
275
|
+
insert(table: string, data: Record<string, unknown>): Promise<unknown>;
|
|
276
|
+
find(table: string, filter?: Record<string, unknown>): Promise<Row[]>;
|
|
277
|
+
update(table: string, filter: Record<string, unknown>, data: Record<string, unknown>): Promise<number>;
|
|
278
|
+
remove(table: string, filter: Record<string, unknown>): Promise<number>;
|
|
279
|
+
findOne(table: string, filter?: Record<string, unknown>): Promise<Row | null>;
|
|
280
|
+
count(table: string): Promise<number>;
|
|
281
|
+
getTables(): Promise<string[]>;
|
|
282
|
+
dropTable(name: string): Promise<void>;
|
|
283
|
+
getTableSchema(name: string): Promise<Schema | null>;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export class Table {
|
|
287
|
+
constructor(name: string, schema: Schema, db?: unknown);
|
|
288
|
+
insert(data: Record<string, unknown>): Promise<unknown>;
|
|
289
|
+
find(filter?: Record<string, unknown>): Promise<Row[]>;
|
|
290
|
+
updateById(id: number | string, data: Record<string, unknown>): Promise<unknown>;
|
|
291
|
+
removeById(id: number | string): Promise<unknown>;
|
|
292
|
+
count(): Promise<number>;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export class Query {
|
|
296
|
+
constructor(table: unknown);
|
|
297
|
+
exec(): Promise<Row[]>;
|
|
298
|
+
then(resolve: (rows: Row[]) => unknown, reject?: (err: Error) => unknown): Promise<unknown>;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export class BTree {
|
|
302
|
+
constructor(order?: number);
|
|
303
|
+
insert(key: unknown, value: unknown): void;
|
|
304
|
+
find(key: unknown): unknown;
|
|
305
|
+
range(min: unknown, max: unknown): unknown[];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export class Cache {
|
|
309
|
+
constructor(options?: Record<string, unknown>);
|
|
310
|
+
get(key: string): unknown;
|
|
311
|
+
set(key: string, value: unknown, ttlMs?: number): void;
|
|
312
|
+
del(key: string): void;
|
|
313
|
+
flush(): void;
|
|
314
|
+
close(): void;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export class Plugin {
|
|
318
|
+
static create(plugin: JSQLPlugin): JSQLPlugin;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export class ModuleManager {
|
|
322
|
+
constructor(opts?: { cwd?: string });
|
|
323
|
+
applyTo(engine: JSQL): void;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export class JSQL_Error extends Error {
|
|
327
|
+
code: number;
|
|
328
|
+
codeKey: string;
|
|
329
|
+
args: unknown[];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export const ErrorCodes: Record<string, { code: number; msg: string }>;
|
|
333
|
+
|
|
334
|
+
export class JSQLFormat {
|
|
335
|
+
constructor(db: unknown);
|
|
336
|
+
dump(): string;
|
|
337
|
+
load(dump: string): void;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export class Datastore {
|
|
341
|
+
constructor(options?: DatabaseOptions);
|
|
342
|
+
insert(docs: unknown): Promise<unknown>;
|
|
343
|
+
find(query?: unknown): Promise<unknown[]>;
|
|
344
|
+
update(query: unknown, update: unknown): Promise<number>;
|
|
345
|
+
remove(query: unknown): Promise<number>;
|
|
346
|
+
loadDatabase(): Promise<void>;
|
|
347
|
+
persistence: { persistCachedDatabase: (cb?: (err?: Error) => void) => void };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export = JSQLNeo;
|
package/index.js
CHANGED
|
@@ -19,6 +19,9 @@ const sql = require('./lib/sql');
|
|
|
19
19
|
const { Datastore } = require('./lib/nedb_compat');
|
|
20
20
|
const mysqlCompat = require('./lib/mysql_compat');
|
|
21
21
|
const { createMysqlServer, MysqlServer } = require('./lib/mysql_server');
|
|
22
|
+
const migrate = require('./lib/migrate');
|
|
23
|
+
const { WebUI } = require('./lib/web_ui');
|
|
24
|
+
const { RedisServer, createRedisServer } = require('./lib/redis_server');
|
|
22
25
|
|
|
23
26
|
module.exports = {
|
|
24
27
|
JSQL: WasmClient.JSQL,
|
|
@@ -44,4 +47,19 @@ module.exports = {
|
|
|
44
47
|
mysql: mysqlCompat,
|
|
45
48
|
createMysqlServer,
|
|
46
49
|
MysqlServer,
|
|
50
|
+
// 迁移工具: mysqldump 导入 / JSON / CSV
|
|
51
|
+
migrate,
|
|
52
|
+
exportTableToJSON: migrate.exportTableToJSON,
|
|
53
|
+
exportAllToJSON: migrate.exportAllToJSON,
|
|
54
|
+
importFromJSON: migrate.importFromJSON,
|
|
55
|
+
exportTableToCSV: migrate.exportTableToCSV,
|
|
56
|
+
importFromCSV: migrate.importFromCSV,
|
|
57
|
+
importDump: migrate.importDump,
|
|
58
|
+
importDumpFile: migrate.importDumpFile,
|
|
59
|
+
exportToFile: migrate.exportToFile,
|
|
60
|
+
// Web UI
|
|
61
|
+
WebUI,
|
|
62
|
+
// Redis 兼容服务器
|
|
63
|
+
RedisServer,
|
|
64
|
+
createRedisServer,
|
|
47
65
|
};
|