jsql-neo 3.5.2 → 4.0.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 (36) hide show
  1. package/LICENSE +202 -0
  2. package/bin/jsql +97 -0
  3. package/index.js +20 -0
  4. package/lib/database.js +484 -28
  5. package/lib/mod.js +316 -0
  6. package/lib/mysql_compat.js +232 -0
  7. package/lib/mysql_server.js +514 -0
  8. package/lib/native_client.js +470 -0
  9. package/lib/nedb_compat.js +506 -0
  10. package/lib/plugin.js +35 -0
  11. package/lib/sql.js +1160 -0
  12. package/lib/table.js +42 -21
  13. package/lib/wasm_client.js +176 -8
  14. package/native/jsql-neo-native.node +0 -0
  15. package/nativesrc/jsql-neo-core/Cargo.toml +24 -0
  16. package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +449 -0
  17. package/nativesrc/jsql-neo-core/src/engine/memory.rs +147 -0
  18. package/nativesrc/jsql-neo-core/src/engine/mod.rs +41 -0
  19. package/nativesrc/jsql-neo-core/src/engine/table.rs +664 -0
  20. package/nativesrc/jsql-neo-core/src/lib.rs +3 -0
  21. package/nativesrc/jsql-neo-core/src/storage/mod.rs +1 -0
  22. package/nativesrc/jsql-neo-core/src/storage/persistent.rs +2 -0
  23. package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -0
  24. package/nativesrc/jsql-neo-core/src/types.rs +94 -0
  25. package/nativesrc/jsql-neo-native/Cargo.lock +606 -0
  26. package/nativesrc/jsql-neo-native/Cargo.toml +16 -0
  27. package/nativesrc/jsql-neo-native/jsql-neo-native.node +0 -0
  28. package/nativesrc/jsql-neo-native/package.json +7 -0
  29. package/nativesrc/jsql-neo-native/src/lib.rs +281 -0
  30. package/package.json +9 -1
  31. package/wasm/jsql_neo_wasm.d.ts +8 -0
  32. package/wasm/jsql_neo_wasm.js +455 -6
  33. package/wasm/jsql_neo_wasm_bg.js +22 -0
  34. package/wasm/jsql_neo_wasm_bg.wasm +0 -0
  35. package/wasm/jsql_neo_wasm_bg.wasm.d.ts +4 -0
  36. package/wasm/package.json +1 -8
package/lib/mod.js ADDED
@@ -0,0 +1,316 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const Module = require('module');
5
+ const vm = require('vm');
6
+
7
+ const DEFAULT_CONFIG_PATH = path.join(os.homedir(), '.config', 'jsql', 'mod.config');
8
+ const CONFIG_PATH = process.env.JSQL_MOD_CONFIG || DEFAULT_CONFIG_PATH;
9
+
10
+ class ModApi {
11
+ constructor(collector) {
12
+ this._collector = collector;
13
+ }
14
+
15
+ nameset(name) {
16
+ if (typeof name !== 'string' || !name) {
17
+ throw new Error('nameset: module name must be a non-empty string');
18
+ }
19
+ if (this._collector.nameSet) {
20
+ throw new Error('nameset: module name already declared');
21
+ }
22
+ this._collector.name = name;
23
+ this._collector.nameSet = true;
24
+ }
25
+
26
+ priority(n) {
27
+ this._collector.priority = n;
28
+ return this;
29
+ }
30
+
31
+ depends(name) {
32
+ if (typeof name === 'string') name = [name];
33
+ for (const d of name) {
34
+ if (typeof d !== 'string' || !d) {
35
+ throw new Error('depends: dependency names must be non-empty strings');
36
+ }
37
+ }
38
+ this._collector.depends = [...(this._collector.depends || []), ...name];
39
+ return this;
40
+ }
41
+
42
+ api(obj) {
43
+ if (typeof obj !== 'object' || obj === null) {
44
+ throw new Error('api: must be an object');
45
+ }
46
+ this._collector.api = obj;
47
+ return this;
48
+ }
49
+
50
+ on(hook, fn) {
51
+ if (typeof fn !== 'function') throw new Error('on: hook must be a function');
52
+ if (!this._collector.hooks[hook]) this._collector.hooks[hook] = [];
53
+ this._collector.hooks[hook].push(fn);
54
+ return this;
55
+ }
56
+
57
+ onEvent(fn) {
58
+ if (typeof fn !== 'function') throw new Error('onEvent: must be a function');
59
+ this._collector.onEvent = fn;
60
+ return this;
61
+ }
62
+
63
+ install(fn) {
64
+ if (typeof fn !== 'function') throw new Error('install: must be a function');
65
+ this._collector.install = fn;
66
+ return this;
67
+ }
68
+ }
69
+
70
+ class ModuleManager {
71
+ constructor(configPath = CONFIG_PATH) {
72
+ this._configPath = configPath;
73
+ this._modules = [];
74
+ this._load();
75
+ }
76
+
77
+ _load() {
78
+ try {
79
+ if (!fs.existsSync(this._configPath)) return;
80
+ const data = JSON.parse(fs.readFileSync(this._configPath, 'utf8'));
81
+ if (Array.isArray(data)) {
82
+ this._modules = data;
83
+ } else if (Array.isArray(data.modules)) {
84
+ this._modules = data.modules;
85
+ }
86
+ } catch (e) {
87
+ this._modules = [];
88
+ }
89
+ }
90
+
91
+ save() {
92
+ const dir = path.dirname(this._configPath);
93
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
94
+ fs.writeFileSync(this._configPath, JSON.stringify({ modules: this._modules }, null, 2), 'utf8');
95
+ }
96
+
97
+ list() {
98
+ return this._modules.map(m => ({ ...m }));
99
+ }
100
+
101
+ find(name) {
102
+ return this._modules.find(m => m.name === name) || null;
103
+ }
104
+
105
+ add(address) {
106
+ const resolved = path.resolve(address);
107
+ if (!fs.existsSync(resolved)) {
108
+ throw new Error(`Module file not found: ${resolved}`);
109
+ }
110
+ if (fs.statSync(resolved).isDirectory()) {
111
+ throw new Error(`Expected a module file, got directory: ${resolved}`);
112
+ }
113
+
114
+ const plugin = this._loadModuleFile(resolved);
115
+ const name = plugin.name;
116
+
117
+ if (this.find(name)) {
118
+ throw new Error(`Module already registered: ${name}`);
119
+ }
120
+
121
+ const entry = {
122
+ name,
123
+ path: resolved,
124
+ enabled: false,
125
+ priority: plugin.priority || 0,
126
+ depends: plugin.depends || [],
127
+ addedAt: Date.now()
128
+ };
129
+ this._modules.push(entry);
130
+ this.save();
131
+ return { ...entry };
132
+ }
133
+
134
+ enable(name, opts = {}) {
135
+ const m = this.find(name);
136
+ if (!m) throw new Error(`Module not found: ${name}. Use 'jsql mod add --address <path>' first.`);
137
+ if (m.enabled) return { ...m, ok: true, message: 'already enabled' };
138
+
139
+ const plugin = this._loadModuleFile(m.path);
140
+
141
+ if (plugin.depends && plugin.depends.length > 0) {
142
+ for (const dep of plugin.depends) {
143
+ const depMod = this.find(dep);
144
+ if (!depMod) throw new Error(`Module '${name}' depends on '${dep}' which is not registered`);
145
+ if (!depMod.enabled && opts.withDeps !== false) {
146
+ this.enable(dep, opts);
147
+ }
148
+ }
149
+ }
150
+
151
+ m.enabled = true;
152
+ m.priority = plugin.priority || m.priority || 0;
153
+ this.save();
154
+ return { ...m, ok: true };
155
+ }
156
+
157
+ disable(name) {
158
+ const m = this.find(name);
159
+ if (!m) throw new Error(`Module not found: ${name}`);
160
+ if (!m.enabled) return { ...m, ok: true, message: 'already disabled' };
161
+ for (const other of this._modules) {
162
+ const plugin = this._loadModuleFile(other.path);
163
+ if (plugin.depends && plugin.depends.includes(name) && other.enabled) {
164
+ this.disable(other.name);
165
+ }
166
+ }
167
+ m.enabled = false;
168
+ this.save();
169
+ return { ...m, ok: true };
170
+ }
171
+
172
+ enableAll() {
173
+ const names = this._modules.map(m => m.name);
174
+ for (const name of names) {
175
+ try { this.enable(name); } catch (e) { /* skip failed */ }
176
+ }
177
+ this.save();
178
+ return this.list().filter(m => m.enabled).length;
179
+ }
180
+
181
+ disableAll() {
182
+ for (const m of this._modules) m.enabled = false;
183
+ this.save();
184
+ return this._modules.length;
185
+ }
186
+
187
+ applyTo(db) {
188
+ const enabled = this._sortEnabled();
189
+ for (const entry of enabled) {
190
+ const plugin = this._loadModuleFile(entry.path);
191
+ db.use(plugin);
192
+ }
193
+ return db;
194
+ }
195
+
196
+ api(name) {
197
+ const m = this.find(name);
198
+ if (!m) throw new Error(`Module not found: ${name}`);
199
+ const plugin = this._loadModuleFile(m.path);
200
+ return plugin.api || null;
201
+ }
202
+
203
+ _sortEnabled() {
204
+ const enabled = this._modules.filter(m => m.enabled).map(m => ({ ...m }));
205
+ const resolved = new Set();
206
+ const order = [];
207
+ const cache = new Map();
208
+
209
+ const resolve = (name, chain) => {
210
+ if (resolved.has(name)) return;
211
+ const entry = enabled.find(m => m.name === name);
212
+ if (!entry) return;
213
+ if (chain.has(name)) throw new Error(`Circular module dependency: ${[...chain, name].join(' -> ')}`);
214
+
215
+ let plugin;
216
+ if (cache.has(entry.path)) {
217
+ plugin = cache.get(entry.path);
218
+ } else {
219
+ plugin = this._loadModuleFile(entry.path);
220
+ cache.set(entry.path, plugin);
221
+ }
222
+
223
+ for (const dep of (plugin.depends || [])) {
224
+ resolve(dep, new Set([...chain, name]));
225
+ }
226
+ resolved.add(name);
227
+ order.push(entry);
228
+ };
229
+
230
+ for (const entry of enabled) resolve(entry.name, new Set());
231
+
232
+ order.sort((a, b) => (a.priority || 0) - (b.priority || 0));
233
+ return order;
234
+ }
235
+
236
+ remove(name) {
237
+ const idx = this._modules.findIndex(m => m.name === name);
238
+ if (idx === -1) throw new Error(`Module not found: ${name}`);
239
+ const [removed] = this._modules.splice(idx, 1);
240
+ this.save();
241
+ return removed;
242
+ }
243
+
244
+ get(name) {
245
+ const m = this.find(name);
246
+ if (!m) throw new Error(`Module not found: ${name}`);
247
+ return this._loadModuleFile(m.path);
248
+ }
249
+
250
+ _loadModuleFile(filePath) {
251
+ const ext = path.extname(filePath).toLowerCase();
252
+
253
+ if (ext === '.json') {
254
+ const obj = JSON.parse(fs.readFileSync(filePath, 'utf8'));
255
+ return {
256
+ name: obj.name || path.basename(filePath, ext),
257
+ ...obj
258
+ };
259
+ }
260
+
261
+ const code = fs.readFileSync(filePath, 'utf8');
262
+ const collector = { name: null, nameSet: false, hooks: {} };
263
+ const sandboxModule = new Module(filePath, module);
264
+ sandboxModule.filename = filePath;
265
+ sandboxModule.paths = Module._nodeModulePaths(path.dirname(filePath));
266
+ const sandboxRequire = Module.createRequire(filePath);
267
+
268
+ const jsqlMod = new ModApi(collector);
269
+ const context = vm.createContext({
270
+ module: sandboxModule,
271
+ exports: sandboxModule.exports,
272
+ require: sandboxRequire,
273
+ __filename: filePath,
274
+ __dirname: path.dirname(filePath),
275
+ console,
276
+ process,
277
+ Buffer,
278
+ setTimeout,
279
+ clearTimeout,
280
+ setInterval,
281
+ clearInterval,
282
+ URL,
283
+ URLSearchParams,
284
+ TextEncoder,
285
+ TextDecoder,
286
+ jsql: { mod: jsqlMod }
287
+ });
288
+
289
+ try {
290
+ vm.runInContext(code, context, { filename: filePath });
291
+ } catch (e) {
292
+ throw new Error(`Failed to load module ${filePath}: ${e.message}`);
293
+ }
294
+
295
+ const sandboxExports = sandboxModule.exports;
296
+ const hasExports = sandboxExports && Object.keys(sandboxExports).length > 0;
297
+ const plugin = hasExports ? sandboxExports : {
298
+ hooks: collector.hooks,
299
+ onEvent: collector.onEvent,
300
+ install: collector.install
301
+ };
302
+
303
+ const meta = {
304
+ name: collector.name || plugin.name || path.basename(filePath, ext),
305
+ priority: collector.priority !== undefined ? collector.priority : plugin.priority,
306
+ depends: collector.depends || plugin.depends,
307
+ api: collector.api || plugin.api
308
+ };
309
+ for (const [k, v] of Object.entries(meta)) {
310
+ if (v !== undefined && v !== null) plugin[k] = v;
311
+ }
312
+ return plugin;
313
+ }
314
+ }
315
+
316
+ module.exports = { ModuleManager, ModApi, CONFIG_PATH };
@@ -0,0 +1,232 @@
1
+ // © Vexify 2026 All Rights Reserved.
2
+ /**
3
+ * MySQL API 兼容层 — createConnection / createPool / query
4
+ * 底层用 JSQL SQL 执行器,接口形状对齐 mysql / mysql2 包。
5
+ */
6
+
7
+ const { executeSQL, applyParams, escapeValue, escapeId } = require('./sql');
8
+ const Database = require('./database');
9
+
10
+ function toResultPacket(r, table) {
11
+ if (isQueryResult(r)) {
12
+ const columns = r.columns || [];
13
+ const rows = (r.rows || []).map(vals => {
14
+ const obj = {};
15
+ columns.forEach((c, j) => { obj[c] = vals[j]; });
16
+ return obj;
17
+ });
18
+ const fields = columns.map(name => ({
19
+ name,
20
+ table: table || '',
21
+ type: 253,
22
+ length: 1024,
23
+ flags: 0,
24
+ charsetNr: 45,
25
+ }));
26
+ return { rows, fields };
27
+ }
28
+ return {
29
+ fieldCount: 0,
30
+ affectedRows: r && r.affectedRows !== undefined ? r.affectedRows : 0,
31
+ insertId: r && r.insertId !== undefined ? r.insertId : 0,
32
+ serverStatus: 2,
33
+ warningCount: 0,
34
+ message: r ? r.type + (r.table ? ' ' + r.table : '') : '',
35
+ protocol41: true,
36
+ changedRows: r && r.affectedRows !== undefined ? r.affectedRows : 0,
37
+ };
38
+ }
39
+
40
+ class Connection {
41
+ constructor(options = {}) {
42
+ this.config = {
43
+ host: options.host || 'localhost',
44
+ port: options.port || 3306,
45
+ user: options.user || 'root',
46
+ password: options.password || '',
47
+ database: options.database || null,
48
+ multipleStatements: options.multipleStatements === true,
49
+ };
50
+ this.safety = options.safety !== false;
51
+ this.allowComments = options.allowComments === true;
52
+ this.database = options.database && typeof options.database === 'object' && !options.filename && typeof options.database.start === 'function'
53
+ ? options.database
54
+ : (options.engine || null);
55
+ this.filename = options.filename || (typeof options.database === 'string' ? options.database : null);
56
+ this.engine = null;
57
+ this._ownEngine = false;
58
+ this.state = 'disconnected';
59
+ }
60
+
61
+ async _getEngine() {
62
+ if (this.engine) return this.engine;
63
+ if (this.database) {
64
+ this.engine = this.database;
65
+ if (typeof this.engine.start === 'function') await this.engine.start();
66
+ } else {
67
+ this.engine = new Database(this.filename || ':memory:');
68
+ if (typeof this.engine.start === 'function') await this.engine.start();
69
+ this._ownEngine = true;
70
+ }
71
+ this.state = 'connected';
72
+ return this.engine;
73
+ }
74
+
75
+ connect(cb) {
76
+ const p = this._getEngine().then(() => this);
77
+ if (cb) p.then(c => cb(null, c), err => cb(err));
78
+ return p;
79
+ }
80
+
81
+ async query(...args) {
82
+ let sql, values, cb;
83
+ if (typeof args[0] === 'object' && args[0] !== null && typeof args[0].sql === 'string') {
84
+ sql = args[0].sql;
85
+ values = args[0].values;
86
+ cb = typeof args[1] === 'function' ? args[1] : null;
87
+ } else {
88
+ sql = args[0];
89
+ values = Array.isArray(args[1]) ? args[1] : null;
90
+ cb = typeof args[1] === 'function' ? args[1] : (typeof args[2] === 'function' ? args[2] : null);
91
+ }
92
+ const p = (async () => {
93
+ const engine = await this._getEngine();
94
+ const finalSql = applyParams(sql, values);
95
+ const r = await executeSQL(engine, finalSql, {
96
+ safety: this.safety,
97
+ allowComments: this.allowComments,
98
+ maxStatements: this.config.multipleStatements ? null : 1,
99
+ });
100
+ const results = Array.isArray(r) ? r : [r];
101
+ const last = results[results.length - 1];
102
+ const packet = toResultPacket(last, last ? last.table : null);
103
+ if (isQueryResult(last)) {
104
+ const arr = [packet.rows, packet.fields];
105
+ arr.rows = packet.rows;
106
+ arr.fields = packet.fields;
107
+ return arr;
108
+ }
109
+ const arr = [packet];
110
+ arr.rows = null;
111
+ arr.fields = null;
112
+ return arr;
113
+ })();
114
+ if (cb) {
115
+ p.then(res => {
116
+ if (res && res.rows && res.fields) cb(null, res.rows, res.fields);
117
+ else if (res && res[0] && res[0].insertId !== undefined) cb(null, res[0]);
118
+ else cb(null, res[0]);
119
+ }, err => cb(err));
120
+ return undefined;
121
+ }
122
+ return p;
123
+ }
124
+
125
+ beginTransaction(cb) {
126
+ const p = this.query('BEGIN');
127
+ if (cb) p.then(() => cb(null), err => cb(err));
128
+ return p;
129
+ }
130
+
131
+ commit(cb) {
132
+ const p = this.query('COMMIT');
133
+ if (cb) p.then(() => cb(null), err => cb(err));
134
+ return p;
135
+ }
136
+
137
+ rollback(cb) {
138
+ const p = this.query('ROLLBACK');
139
+ if (cb) p.then(() => cb(null), err => cb(err));
140
+ return p;
141
+ }
142
+
143
+ ping(cb) {
144
+ const p = this._getEngine().then(() => true);
145
+ if (cb) p.then(ok => cb(null, ok), err => cb(err));
146
+ return p;
147
+ }
148
+
149
+ release() {
150
+ this.state = 'released';
151
+ }
152
+
153
+ destroy() {
154
+ this.state = 'destroyed';
155
+ }
156
+
157
+ end(cb) {
158
+ const p = (async () => {
159
+ if (this._ownEngine && this.engine && typeof this.engine.stop === 'function') {
160
+ await this.engine.stop();
161
+ }
162
+ this.state = 'closed';
163
+ return undefined;
164
+ })();
165
+ if (cb) p.then(() => cb(null), err => cb(err));
166
+ return p;
167
+ }
168
+
169
+ promise() {
170
+ return this;
171
+ }
172
+ }
173
+
174
+ class Pool {
175
+ constructor(options = {}) {
176
+ this.config = options;
177
+ this._connection = null;
178
+ }
179
+
180
+ async _getConnection() {
181
+ if (!this._connection) {
182
+ this._connection = new Connection(this.config);
183
+ await this._connection._getEngine();
184
+ }
185
+ return this._connection;
186
+ }
187
+
188
+ query(...args) {
189
+ return this._getConnection().then(conn => conn.query(...args));
190
+ }
191
+
192
+ getConnection(cb) {
193
+ const p = this._getConnection().then(conn => {
194
+ const release = conn.release.bind(conn);
195
+ return { connection: conn, release };
196
+ });
197
+ if (cb) p.then(r => cb(null, r.connection, r.release), err => cb(err));
198
+ return p;
199
+ }
200
+
201
+ end(cb) {
202
+ const p = (async () => {
203
+ if (this._connection) {
204
+ await this._connection.end();
205
+ this._connection = null;
206
+ }
207
+ return undefined;
208
+ })();
209
+ if (cb) p.then(() => cb(null), err => cb(err));
210
+ return p;
211
+ }
212
+
213
+ promise() {
214
+ return this;
215
+ }
216
+ }
217
+
218
+ function createConnection(options) {
219
+ return new Connection(options || {});
220
+ }
221
+
222
+ function createPool(options) {
223
+ return new Pool(options || {});
224
+ }
225
+
226
+ module.exports = {
227
+ createConnection,
228
+ createPool,
229
+ escape: escapeValue,
230
+ escapeId,
231
+ format: applyParams,
232
+ };