jsql-neo 4.4.1 → 4.4.3

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/lib/migrate.js CHANGED
@@ -130,7 +130,7 @@ async function importFromJSON(engine, data) {
130
130
  await engine.createTable(name, normalizeSchema(t.schema));
131
131
  created.push(name);
132
132
  if (Array.isArray(t.rows) && t.rows.length > 0) {
133
- const ids = await engine.insert(name, t.rows.map(r => ({ ...r.fields, id: r.id })));
133
+ const ids = await engine.insert(name, t.rows.map(r => ({ ...r })));
134
134
  inserted += Array.isArray(ids) ? ids.length : t.rows.length;
135
135
  }
136
136
  }
@@ -250,8 +250,11 @@ class RedisServer {
250
250
  case 'LPUSH': case 'RPUSH': {
251
251
  const v = this._get(args[0]);
252
252
  const list = (v && v.type === 'list') ? [...v.val] : [];
253
- if (cmd === 'LPUSH') list.unshift(...args.slice(1));
254
- else list.push(...args.slice(1));
253
+ if (cmd === 'LPUSH') {
254
+ for (const val of args.slice(1)) list.unshift(val);
255
+ } else {
256
+ list.push(...args.slice(1));
257
+ }
255
258
  this.db.set(args[0], { type: 'list', val: list, ttl: v ? v.ttl : null });
256
259
  this._scheduleSnapshot();
257
260
  return list.length;
package/lib/sql.js CHANGED
@@ -1994,11 +1994,11 @@ function splitStatements(sql) {
1994
1994
  }
1995
1995
  if (c === "'" || c === '"') { inStr = c; current += c; i++; continue; }
1996
1996
  if (c === '-' && sql[i + 1] === '-') {
1997
- while (i < sql.length && sql[i] !== '\n') { current += sql[i]; i++; }
1997
+ while (i < sql.length && sql[i] !== '\n') i++;
1998
1998
  continue;
1999
1999
  }
2000
2000
  if (c === '#') {
2001
- while (i < sql.length && sql[i] !== '\n') { current += sql[i]; i++; }
2001
+ while (i < sql.length && sql[i] !== '\n') i++;
2002
2002
  continue;
2003
2003
  }
2004
2004
  if (c === '/' && sql[i + 1] === '*') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsql-neo",
3
- "version": "4.4.1",
3
+ "version": "4.4.3",
4
4
  "description": "JSQL-NEO — Rust-powered embedded database with WASM, REST API, B-Tree indexes, WAL, crash recovery",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -8,14 +8,20 @@
8
8
  ".": {
9
9
  "types": "./index.d.ts",
10
10
  "browser": "./wasm/browser.mjs",
11
+ "import": "./index.js",
12
+ "require": "./index.js",
11
13
  "default": "./index.js"
12
14
  },
15
+ "./cli": "./bin/jsql",
13
16
  "./wasm/browser.mjs": {
14
17
  "types": "./wasm/browser.d.ts",
15
18
  "default": "./wasm/browser.mjs"
16
19
  },
20
+ "./wasm/browser_bg.mjs": "./wasm/browser_bg.mjs",
21
+ "./wasm/browser.d.ts": "./wasm/browser.d.ts",
17
22
  "./wasm/*": "./wasm/*",
18
- "./lib/*": "./lib/*",
23
+ "./lib/*.js": "./lib/*.js",
24
+ "./lib/*": "./lib/*.js",
19
25
  "./package.json": "./package.json"
20
26
  },
21
27
  "bin": {
@@ -38,6 +44,7 @@
38
44
  "scripts": {
39
45
  "postinstall": "node postinstall.js",
40
46
  "test": "node test/smoke.js",
47
+ "test:coverage": "c8 --all --include='lib/**' --exclude='lib/mysql_server.js' --exclude='lib/native_client.js' --exclude='lib/wasm_client.js' --exclude='lib/mysql_compat.js' --exclude='lib/nedb_compat.js' --exclude='lib/plugin.js' --reporter=text --reporter=lcov node test/coverage.js",
41
48
  "test:orms": "node examples/orms/run-all.js"
42
49
  },
43
50
  "keywords": [
@@ -66,5 +73,8 @@
66
73
  },
67
74
  "dependencies": {
68
75
  "@vexify-org/yaggs": "^8.1.0"
76
+ },
77
+ "devDependencies": {
78
+ "c8": "^10.1.2"
69
79
  }
70
80
  }
@@ -0,0 +1,187 @@
1
+ /*
2
+ * Integration coverage test — exercises Redis server, Web UI, migration tools
3
+ * and CLI logic in-process (zero dependencies) so c8 can measure real usage.
4
+ *
5
+ * npm run coverage (c8 node test/coverage.js)
6
+ */
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const os = require('os');
10
+
11
+ const ROOT = path.join(__dirname, '..');
12
+ let passed = 0, failed = 0;
13
+
14
+ function ok(name, cond) {
15
+ if (cond) { passed++; console.log('[OK]', name); }
16
+ else { failed++; console.log('[FAIL]', name); }
17
+ }
18
+
19
+ function tmp(name) {
20
+ const p = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'jsql-cov-')), name);
21
+ fs.mkdirSync(path.dirname(p), { recursive: true });
22
+ return p;
23
+ }
24
+
25
+ (async () => {
26
+ /* ---------- Redis server ---------- */
27
+ const { RedisServer } = require(path.join(ROOT, 'lib/redis_server'));
28
+ const Database = require(path.join(ROOT, 'lib/database'));
29
+ const { executeSQL } = require(path.join(ROOT, 'lib/sql'));
30
+ const { WebUI } = require(path.join(ROOT, 'lib/web_ui'));
31
+ const migrate = require(path.join(ROOT, 'lib/migrate'));
32
+
33
+ {
34
+ const rs = new RedisServer({ dataDir: tmp('redis') });
35
+ ok('redis PING', rs.execute('PING', []) === 'PONG');
36
+ ok('redis ECHO', rs.execute('ECHO', ['hi']) === 'hi');
37
+ rs.execute('SET', ['k', 'v']);
38
+ ok('redis GET', rs.execute('GET', ['k']) === 'v');
39
+ ok('redis SETNX existing', rs.execute('SETNX', ['k', 'x']) === 0);
40
+ ok('redis SETNX new', rs.execute('SETNX', ['n', 'x']) === 1);
41
+ rs.execute('INCR', ['cnt']);
42
+ rs.execute('INCR', ['cnt']);
43
+ ok('redis INCR', rs.execute('GET', ['cnt']) === '2');
44
+ ok('redis DECR', rs.execute('DECR', ['cnt']) === 1);
45
+ ok('redis INCRBY', rs.execute('INCRBY', ['cnt', '5']) === 6);
46
+ rs.execute('APPEND', ['a', 'hello']);
47
+ ok('redis APPEND/STRLEN', rs.execute('STRLEN', ['a']) === 5);
48
+ rs.execute('HSET', ['h', 'f1', 'v1', 'f2', 'v2']);
49
+ ok('redis HGET', rs.execute('HGET', ['h', 'f1']) === 'v1');
50
+ const hg = rs.execute('HGETALL', ['h']);
51
+ ok('redis HGETALL', Array.isArray(hg) && hg.length === 4);
52
+ ok('redis HDEL', rs.execute('HDEL', ['h', 'f1']) === 1);
53
+ ok('redis HLEN', rs.execute('HLEN', ['h']) === 1);
54
+ ok('redis HKEYS', JSON.stringify(rs.execute('HKEYS', ['h'])) === '["f2"]');
55
+ rs.execute('LPUSH', ['l', 'a', 'b']);
56
+ ok('redis LLEN', rs.execute('LLEN', ['l']) === 2);
57
+ const lr = rs.execute('LRANGE', ['l', '0', '-1']);
58
+ ok('redis LRANGE', JSON.stringify(lr) === '["b","a"]');
59
+ ok('redis LINDEX', rs.execute('LINDEX', ['l', '0']) === 'b');
60
+ rs.execute('RPUSH', ['l', 'c']);
61
+ ok('redis LPOP', rs.execute('LPOP', ['l']) === 'b');
62
+ ok('redis LREM', rs.execute('LREM', ['l', '0', 'a']) === 1);
63
+ rs.execute('SADD', ['s', 'm1', 'm2', 'm3']);
64
+ ok('redis SCARD', rs.execute('SCARD', ['s']) === 3);
65
+ ok('redis SISMEMBER', rs.execute('SISMEMBER', ['s', 'm1']) === 1);
66
+ ok('redis SMEMBERS', rs.execute('SMEMBERS', ['s']).length === 3);
67
+ ok('redis SREM', rs.execute('SREM', ['s', 'm1']) === 1);
68
+ ok('redis KEYS', rs.execute('KEYS', ['*']).length >= 5);
69
+ ok('redis EXISTS', rs.execute('EXISTS', ['k', 'zzz']) === 1);
70
+ ok('redis TYPE', rs.execute('TYPE', ['h']) === 'hash');
71
+ ok('redis TTL missing', rs.execute('TTL', ['zzz']) === -2);
72
+ rs.execute('EXPIRE', ['k', '100']);
73
+ ok('redis TTL set', rs.execute('TTL', ['k']) > 0);
74
+ ok('redis PERSIST', rs.execute('PERSIST', ['k']) === 1);
75
+ ok('redis DBSIZE', rs.execute('DBSIZE', []) >= 5);
76
+ ok('redis FLUSHALL', rs.execute('FLUSHALL', []) === 'OK');
77
+ ok('redis DBSIZE after flush', rs.execute('DBSIZE', []) === 0);
78
+ ok('redis INFO', rs.execute('INFO', []).includes('redis_version'));
79
+ ok('redis SELECT', rs.execute('SELECT', ['3']) === 'OK');
80
+ rs.stop();
81
+ const persisted = JSON.parse(fs.readFileSync(rs.snapshotPath(), 'utf8'));
82
+ ok('redis snapshot file written', Object.keys(persisted).length >= 0);
83
+ }
84
+
85
+ /* ---------- Web UI ---------- */
86
+ {
87
+ const dbFile = tmp('webui/db.json');
88
+ fs.mkdirSync(path.dirname(dbFile), { recursive: true });
89
+ fs.writeFileSync(dbFile, JSON.stringify({
90
+ __schema__: { users: { id: { type: 'integer', primaryKey: true, autoIncrement: true }, name: { type: 'string' } } },
91
+ __meta__: { version: 1 },
92
+ users: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }],
93
+ }));
94
+ const ui = new WebUI({ port: 0, dataDir: path.dirname(dbFile) });
95
+ const port = await ui.start();
96
+ const dbs = await (await fetch(`http://127.0.0.1:${port}/api/databases`)).json();
97
+ ok('webui list databases', dbs.length === 1 && dbs[0].name === 'db');
98
+ const tbls = await (await fetch(`http://127.0.0.1:${port}/api/tables?db=db`)).json();
99
+ ok('webui list tables', tbls.tables.length === 1 && tbls.tables[0].name === 'users' && tbls.tables[0].count === 2);
100
+ const q = await (await fetch(`http://127.0.0.1:${port}/api/query`, {
101
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
102
+ body: JSON.stringify({ db: 'db', sql: 'SELECT name FROM users WHERE id = 1' }),
103
+ })).json();
104
+ ok('webui query', q.ok && JSON.stringify(q.rows) === '[[\"Alice\"]]');
105
+ const ins = await (await fetch(`http://127.0.0.1:${port}/api/query`, {
106
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
107
+ body: JSON.stringify({ db: 'db', sql: "INSERT INTO users (name) VALUES ('Carol')" }),
108
+ })).json();
109
+ ok('webui insert', ins.ok && ins.affected === 1);
110
+ const bad = await (await fetch(`http://127.0.0.1:${port}/api/query`, {
111
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
112
+ body: JSON.stringify({ db: 'db', sql: 'SELECT * FROM nope' }),
113
+ })).json();
114
+ ok('webui error path', !bad.ok && bad.error.includes('nope'));
115
+ const html = await (await fetch(`http://127.0.0.1:${port}/`)).text();
116
+ ok('webui page', html.includes('JSQL-NEO'));
117
+ ok('webui missing db', (await (await fetch(`http://127.0.0.1:${port}/api/tables?db=x`)).json()).error !== undefined);
118
+ await ui.stop();
119
+ }
120
+
121
+ /* ---------- Migration tools + SQL engine ---------- */
122
+ {
123
+ const db = new Database(':memory:', { autoSave: false });
124
+ const dump = `CREATE TABLE products (
125
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
126
+ name VARCHAR(100) COLLATE utf8mb4_bin DEFAULT NULL,
127
+ price DECIMAL(10,2) DEFAULT '0.00',
128
+ note TEXT
129
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
130
+ -- comment line
131
+ INSERT INTO products (name, price, note) VALUES
132
+ ('Laptop', 999.99, 'It\\'s a \\"pro\\" model\\nwith newline'),
133
+ ('Mouse', 19.50, NULL),
134
+ ('Cable', 9.90, 'USB\\tC');
135
+ `;
136
+ const r = await migrate.importDump(db, dump);
137
+ ok('migrate import dump', r.created.includes('products') && r.inserted === 3);
138
+ const csv = await migrate.exportTableToCSV(db, 'products');
139
+ ok('migrate export csv', csv.split('\n').length >= 4 && csv.includes('Laptop'));
140
+ const rows = await db.find('products', {}, { limit: 10 });
141
+ ok('migrate escaped round-trip', rows[0].name === 'Laptop' && rows[0].note.includes('with newline'));
142
+ const json = await migrate.exportAllToJSON(db);
143
+ ok('migrate export json', json.products && json.products.schema && json.products.rows.length === 3);
144
+ const db2 = new Database(':memory:', { autoSave: false });
145
+ await migrate.importFromJSON(db2, json);
146
+ ok('migrate import json', (await db2.count('products')) === 3);
147
+ const r2 = await migrate.importFromCSV(db2, 'products2', 'a,b\n1,hello\n2,"quo,ted"\n', { schema: { a: { type: 'string' }, b: { type: 'string' } } });
148
+ ok('migrate csv import', r2.inserted === 2);
149
+
150
+ await executeSQL(db, 'CREATE TABLE users (id INTEGER PRIMARY KEY AUTO_INCREMENT, name STRING, age INTEGER)');
151
+ await executeSQL(db, "INSERT INTO users (name, age) VALUES ('Alice', 30), ('Bob', 25)");
152
+ const sel = await executeSQL(db, 'SELECT name, age FROM users WHERE age > 26 ORDER BY age DESC');
153
+ ok('sql select where order', JSON.stringify(sel.rows) === '[["Alice",30]]');
154
+ const agg = await executeSQL(db, 'SELECT COUNT(*) AS n, AVG(age) AS a FROM users');
155
+ ok('sql aggregate', agg.rows[0][0] === 2);
156
+ const fn = await executeSQL(db, 'SELECT VERSION(), CONCAT("x","y")');
157
+ ok('sql func column names', fn.columns[0] === 'VERSION()');
158
+ await executeSQL(db, 'UPDATE users SET age = 31 WHERE name = \'Bob\'');
159
+ await executeSQL(db, 'DELETE FROM users WHERE id = 1');
160
+ ok('sql count after delete', (await db.count('users')) === 1);
161
+ await executeSQL(db, 'BEGIN');
162
+ await executeSQL(db, "INSERT INTO users (name, age) VALUES ('Temp', 99)");
163
+ await executeSQL(db, 'ROLLBACK');
164
+ ok('sql rollback', (await db.count('users')) === 1);
165
+ await executeSQL(db, 'START TRANSACTION');
166
+ await executeSQL(db, "INSERT INTO users (name, age) VALUES ('X', 1)");
167
+ await executeSQL(db, 'TRUNCATE TABLE users');
168
+ ok('sql truncate', (await db.count('users')) === 0);
169
+ const sqlApi = require(path.join(ROOT, 'lib/sql'));
170
+ const parsed = sqlApi.parseSQL('SELECT * FROM t WHERE id = 5');
171
+ ok('parseSQL', parsed.type === 'select');
172
+ ok('applyParams', sqlApi.applyParams('SELECT * FROM t WHERE id = ?', [7]) === 'SELECT * FROM t WHERE id = 7');
173
+ }
174
+
175
+ /* ---------- CLI module load ---------- */
176
+ {
177
+ try {
178
+ require(path.join(ROOT, 'bin/jsql'));
179
+ ok('cli loads', true);
180
+ } catch (e) {
181
+ ok('cli loads', !/Cannot find module/.test(e.message));
182
+ }
183
+ }
184
+
185
+ console.log(`\n${passed} passed, ${failed} failed`);
186
+ process.exit(failed ? 1 : 0);
187
+ })().catch(e => { console.error('[FATAL]', e.stack.split('\n').slice(0, 3).join('\n')); process.exit(1); });