jsql-neo 5.3.0 → 5.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 +248 -33
- package/lib/mongo_server.js +1 -1
- package/lib/native_client.js +182 -23
- package/lib/query.js +20 -2
- package/lib/sql.js +44 -6
- package/lib/sqlite_worker.js +5 -1
- package/lib/table.js +20 -2
- package/lib/wasm_client.js +200 -23
- package/lib/web_ui.js +1 -1
- package/nativesrc/jsql-neo-core/Cargo.toml +1 -1
- package/nativesrc/jsql-neo-native/Cargo.toml +1 -1
- package/nativesrc/jsql-neo-wasm/Cargo.lock +2 -2
- package/nativesrc/jsql-neo-wasm/Cargo.toml +1 -1
- package/nativesrc/jsql-neo-wasm/src/lib.rs +24 -0
- package/package.json +3 -3
- package/test/join.test.js +110 -0
- package/test/native.test.js +159 -0
- package/test/wasm.test.js +111 -0
- package/wasm/browser.d.ts +81 -80
- package/wasm/browser.mjs +407 -242
- package/wasm/browser_bg.mjs +166 -31
- package/wasm/jsql_neo_wasm.d.ts +6 -0
- package/wasm/jsql_neo_wasm.js +57 -0
- package/wasm/jsql_neo_wasm_bg.wasm +0 -0
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +3 -0
- package/wasm/package.json +1 -1
- package/lib/compress_pool.js +0 -122
- package/lib/compress_worker.js +0 -32
- package/wasm/jsql_neo_wasm_bg.js +0 -373
package/wasm/browser.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
init,
|
|
3
2
|
jsql_reset,
|
|
4
3
|
jsql_create_table,
|
|
5
4
|
jsql_drop_table,
|
|
@@ -12,30 +11,151 @@ import {
|
|
|
12
11
|
jsql_remove_by_id,
|
|
13
12
|
jsql_remove_by_ids,
|
|
14
13
|
jsql_count,
|
|
14
|
+
jsql_begin_tx,
|
|
15
|
+
jsql_commit_tx,
|
|
16
|
+
jsql_rollback_tx,
|
|
15
17
|
} from './browser_bg.mjs';
|
|
18
|
+
import init from './browser_bg.mjs';
|
|
16
19
|
|
|
17
20
|
function safeJsonParse(str) {
|
|
18
21
|
try { return JSON.parse(str); } catch { return str; }
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
const
|
|
24
|
+
const NATIVE_TYPE_MAP = {
|
|
25
|
+
text: 'string',
|
|
26
|
+
varchar: 'string',
|
|
27
|
+
double: 'float',
|
|
28
|
+
number: 'float',
|
|
29
|
+
numeric: 'float',
|
|
30
|
+
decimal: 'float',
|
|
31
|
+
date: 'string',
|
|
32
|
+
timestamp: 'string',
|
|
33
|
+
datetime: 'string',
|
|
34
|
+
json: 'string',
|
|
35
|
+
array: 'string',
|
|
36
|
+
object: 'string',
|
|
37
|
+
bool: 'boolean',
|
|
38
|
+
bigint: 'string',
|
|
39
|
+
int: 'integer',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function parseShorthandSchema(str) {
|
|
43
|
+
const def = { type: str };
|
|
44
|
+
const strip = (re) => {
|
|
45
|
+
const next = def.type.replace(re, '').trim();
|
|
46
|
+
def.type = next || def.type;
|
|
47
|
+
};
|
|
48
|
+
if (/\bprimary\s+key\b/i.test(def.type)) { def.primaryKey = true; strip(/\bprimary\s+key\b/gi); }
|
|
49
|
+
if (/\bauto_?increment\b/i.test(def.type)) { def.autoIncrement = true; strip(/\bauto_?increment\b/gi); }
|
|
50
|
+
if (/\bnot\s+null\b/i.test(def.type)) { def.required = true; strip(/\bnot\s+null\b/gi); }
|
|
51
|
+
if (/\bunique\b/i.test(def.type)) { def.unique = true; strip(/\bunique\b/gi); }
|
|
52
|
+
if (/\bdefault\s+(\S+)/i.test(def.type)) {
|
|
53
|
+
const m = def.type.match(/\bdefault\s+(\S+)/i);
|
|
54
|
+
def.default = m[1].replace(/^['"]|['"]$/g, '');
|
|
55
|
+
strip(/\bdefault\s+\S+/gi);
|
|
56
|
+
}
|
|
57
|
+
return def;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseSchemaString(str) {
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const seg of str.split(',')) {
|
|
63
|
+
const trimmed = seg.trim();
|
|
64
|
+
if (!trimmed) continue;
|
|
65
|
+
const parts = trimmed.split(/\s+/);
|
|
66
|
+
const name = parts.shift();
|
|
67
|
+
const def = parseShorthandSchema(parts.join(' ').toLowerCase().trim());
|
|
68
|
+
if (def.type.length === 0) def.type = 'string';
|
|
69
|
+
out[name] = def;
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function mapNativeSchema(schema) {
|
|
75
|
+
if (typeof schema === 'string') schema = parseSchemaString(schema);
|
|
76
|
+
const mapped = {};
|
|
77
|
+
for (const [field, def] of Object.entries(schema || {})) {
|
|
78
|
+
if (typeof def === 'string') {
|
|
79
|
+
const parsed = parseShorthandSchema(def.toLowerCase().trim());
|
|
80
|
+
if (parsed.type.length === 0) parsed.type = 'string';
|
|
81
|
+
mapped[field] = { ...parsed, type: NATIVE_TYPE_MAP[parsed.type] || parsed.type };
|
|
82
|
+
} else if (def && typeof def === 'object') {
|
|
83
|
+
const t = (def.type || 'string').toLowerCase();
|
|
84
|
+
const out = { ...def, type: NATIVE_TYPE_MAP[t] || t };
|
|
85
|
+
if (out.primary_key !== undefined && out.primaryKey === undefined) { out.primaryKey = out.primary_key; delete out.primary_key; }
|
|
86
|
+
if (out.auto_increment !== undefined && out.autoIncrement === undefined) { out.autoIncrement = out.auto_increment; delete out.auto_increment; }
|
|
87
|
+
mapped[field] = out;
|
|
88
|
+
} else {
|
|
89
|
+
mapped[field] = { type: 'string' };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return mapped;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function restoreRow(row, schema) {
|
|
96
|
+
if (typeof row !== 'object' || row === null) return row;
|
|
97
|
+
let src = row;
|
|
98
|
+
if (row.fields && typeof row.fields === 'object' && row.fields !== null) {
|
|
99
|
+
src = { ...row.fields };
|
|
100
|
+
if (row.id !== undefined) {
|
|
101
|
+
const pkCols = [];
|
|
102
|
+
if (schema) {
|
|
103
|
+
for (const [k, def] of Object.entries(schema)) {
|
|
104
|
+
if (def && typeof def === 'object' && def.autoIncrement) continue;
|
|
105
|
+
const isPk = typeof def === 'string'
|
|
106
|
+
? /\bprimary\s+key\b/i.test(def)
|
|
107
|
+
: !!(def && (def.primaryKey || def.primary_key));
|
|
108
|
+
if (isPk) pkCols.push(k);
|
|
109
|
+
}
|
|
110
|
+
} else if (src.id === undefined) {
|
|
111
|
+
pkCols.push('id');
|
|
112
|
+
}
|
|
113
|
+
for (const c of pkCols) {
|
|
114
|
+
const v = src[c];
|
|
115
|
+
if (v === undefined || v === null || v === 0 || v === '' || v === false) src[c] = row.id;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (row.created_at !== undefined) src.created_at = row.created_at;
|
|
119
|
+
if (row.updated_at !== undefined) src.updated_at = row.updated_at;
|
|
120
|
+
}
|
|
121
|
+
if (!schema) return src;
|
|
122
|
+
const out = {};
|
|
123
|
+
for (const [field, value] of Object.entries(src)) {
|
|
124
|
+
const colDef = schema[field];
|
|
125
|
+
let t = null;
|
|
126
|
+
if (typeof colDef === 'string') t = colDef.toLowerCase();
|
|
127
|
+
else if (colDef && typeof colDef === 'object' && colDef.type) t = String(colDef.type).toLowerCase();
|
|
128
|
+
if (['json', 'array', 'object'].includes(t) && typeof value === 'string') {
|
|
129
|
+
try { out[field] = JSON.parse(value); } catch (e) { out[field] = value; }
|
|
130
|
+
} else if ((t === 'integer' || t === 'int' || t === 'bigint') && typeof value === 'string' && /^-?\d+$/.test(value)) {
|
|
131
|
+
out[field] = Number(value);
|
|
132
|
+
} else if ((t === 'float' || t === 'double' || t === 'number') && typeof value === 'string' && !Number.isNaN(Number(value))) {
|
|
133
|
+
out[field] = Number(value);
|
|
134
|
+
} else {
|
|
135
|
+
out[field] = value;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const INT64_TAG = 1;
|
|
142
|
+
const FLOAT_TAG = 2;
|
|
143
|
+
const STR_TAG = 3;
|
|
144
|
+
const BOOL_TAG = 4;
|
|
145
|
+
const INT32_TAG = 5;
|
|
146
|
+
|
|
22
147
|
|
|
23
|
-
/**
|
|
24
|
-
* JSQL-NEO browser engine — full SQL database running in the browser.
|
|
25
|
-
*
|
|
26
|
-
* WASM (Rust) for data, IndexedDB for persistence, the shared SQL engine for
|
|
27
|
-
* queries. Node.js and browsers share the same JSQL API.
|
|
28
|
-
*/
|
|
29
148
|
export class JSQL {
|
|
30
149
|
constructor(opts = {}) {
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
150
|
+
this._flushThreshold = opts.flushThreshold || 5000;
|
|
151
|
+
this._buffer = {};
|
|
152
|
+
this._bufferSize = 0;
|
|
34
153
|
this._tableNames = new Set();
|
|
35
154
|
this._schemas = {};
|
|
36
|
-
this._dbList = new Set(['default']);
|
|
37
155
|
this._txId = undefined;
|
|
38
|
-
this.
|
|
156
|
+
this._autoPlugins = opts.modules !== false;
|
|
157
|
+
|
|
158
|
+
this._plugins = [];
|
|
39
159
|
this._hooks = {
|
|
40
160
|
beforeInsert: [], afterInsert: [],
|
|
41
161
|
beforeUpdate: [], afterUpdate: [],
|
|
@@ -44,19 +164,55 @@ export class JSQL {
|
|
|
44
164
|
beforeCreateTable: [], afterCreateTable: [],
|
|
45
165
|
beforeDropTable: [], afterDropTable: [],
|
|
46
166
|
beforeFlush: [], afterFlush: [],
|
|
47
|
-
|
|
167
|
+
beforeCount: [], afterCount: [],
|
|
168
|
+
onStart: [], onStop: []
|
|
48
169
|
};
|
|
49
170
|
this._eventListeners = [];
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this._snapPromise = null;
|
|
171
|
+
if (this._autoPlugins && typeof globalThis.__jsqlLoadModules === 'function') {
|
|
172
|
+
globalThis.__jsqlLoadModules(this);
|
|
173
|
+
}
|
|
54
174
|
}
|
|
55
175
|
|
|
56
|
-
|
|
176
|
+
use(plugin) {
|
|
177
|
+
if (typeof plugin === 'function') {
|
|
178
|
+
plugin = { install: plugin };
|
|
179
|
+
}
|
|
180
|
+
if (plugin.hooks) {
|
|
181
|
+
for (const [event, fn] of Object.entries(plugin.hooks)) {
|
|
182
|
+
if (this._hooks[event]) {
|
|
183
|
+
this._hooks[event].push(fn);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (typeof plugin.install === 'function') {
|
|
188
|
+
plugin.install(this, this._buildCtx(plugin));
|
|
189
|
+
}
|
|
190
|
+
if (typeof plugin.onEvent === 'function') {
|
|
191
|
+
this._eventListeners.push(plugin.onEvent);
|
|
192
|
+
}
|
|
193
|
+
this._plugins.push(plugin);
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_buildCtx(plugin) {
|
|
198
|
+
const self = this;
|
|
199
|
+
return {
|
|
200
|
+
name: plugin.name || 'anonymous',
|
|
201
|
+
engine: this,
|
|
202
|
+
plugin,
|
|
203
|
+
on(hook, fn) { return self.on(hook, fn); },
|
|
204
|
+
onEvent(fn) { return self.onEvent(fn); },
|
|
205
|
+
emit(eventName, data) { self._emit(eventName, data); },
|
|
206
|
+
tables() { return Array.from(self._tableNames); },
|
|
207
|
+
hasTable(name) { return self._tableNames.has(name); },
|
|
208
|
+
getTableSchema(name) { return self._schemas[name] || null; }
|
|
209
|
+
};
|
|
210
|
+
}
|
|
57
211
|
|
|
58
212
|
on(event, fn) {
|
|
59
|
-
if (this._hooks[event])
|
|
213
|
+
if (this._hooks[event]) {
|
|
214
|
+
this._hooks[event].push(fn);
|
|
215
|
+
}
|
|
60
216
|
return this;
|
|
61
217
|
}
|
|
62
218
|
|
|
@@ -67,7 +223,7 @@ export class JSQL {
|
|
|
67
223
|
|
|
68
224
|
_emit(eventName, data) {
|
|
69
225
|
for (const fn of this._eventListeners) {
|
|
70
|
-
try { fn(eventName, data); } catch { /* ignore */ }
|
|
226
|
+
try { fn(eventName, data); } catch (e) { /* ignore */ }
|
|
71
227
|
}
|
|
72
228
|
}
|
|
73
229
|
|
|
@@ -77,130 +233,170 @@ export class JSQL {
|
|
|
77
233
|
for (const fn of hooks) {
|
|
78
234
|
const r = fn(...args);
|
|
79
235
|
if (r === false) return false;
|
|
80
|
-
if (r !== undefined && args.length > 0)
|
|
236
|
+
if (r !== undefined && args.length > 0) {
|
|
237
|
+
args[0] = r;
|
|
238
|
+
}
|
|
81
239
|
}
|
|
82
240
|
return true;
|
|
83
241
|
}
|
|
84
242
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.
|
|
90
|
-
const req = indexedDB.open(this._dbName, 1);
|
|
91
|
-
req.onupgradeneeded = () => {
|
|
92
|
-
const db = req.result;
|
|
93
|
-
if (!db.objectStoreNames.contains('store')) db.createObjectStore('store', { keyPath: 'k' });
|
|
94
|
-
};
|
|
95
|
-
req.onsuccess = () => { this._idb = req.result; resolve(this._idb); };
|
|
96
|
-
req.onerror = () => reject(req.error);
|
|
97
|
-
});
|
|
98
|
-
return this._idbPromise;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async _readSnapshot() {
|
|
102
|
-
const db = await this._openIDB();
|
|
103
|
-
return new Promise((resolve, reject) => {
|
|
104
|
-
const tx = db.transaction('store', 'readonly');
|
|
105
|
-
const get = tx.objectStore('store').get('snap');
|
|
106
|
-
get.onsuccess = () => resolve(get.result ? get.result.v : null);
|
|
107
|
-
get.onerror = () => reject(get.error);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async _writeSnapshot() {
|
|
112
|
-
if (!this._persist) return;
|
|
113
|
-
if (this._snapPromise) return this._snapPromise;
|
|
114
|
-
this._snapPromise = (async () => {
|
|
115
|
-
const tables = {};
|
|
116
|
-
for (const name of this._tableNames) {
|
|
117
|
-
const rows = [];
|
|
118
|
-
let offset = 0;
|
|
119
|
-
for (;;) {
|
|
120
|
-
const page = safeJsonParse(jsql_find(name, '', PAGE, offset)) || [];
|
|
121
|
-
rows.push(...page);
|
|
122
|
-
if (page.length < PAGE) break;
|
|
123
|
-
offset += page.length;
|
|
124
|
-
}
|
|
125
|
-
tables[name] = { schema: this._schemas[name] || {}, rows: rows.map(r => r.fields) };
|
|
126
|
-
}
|
|
127
|
-
const db = await this._openIDB();
|
|
128
|
-
await new Promise((resolve, reject) => {
|
|
129
|
-
const tx = db.transaction('store', 'readwrite');
|
|
130
|
-
tx.objectStore('store').put({ k: 'snap', v: { tables } });
|
|
131
|
-
tx.oncomplete = () => resolve();
|
|
132
|
-
tx.onerror = () => reject(tx.error);
|
|
133
|
-
});
|
|
134
|
-
})().finally(() => { this._snapPromise = null; });
|
|
135
|
-
return this._snapPromise;
|
|
243
|
+
async start() {
|
|
244
|
+
await init();
|
|
245
|
+
jsql_reset();
|
|
246
|
+
this._runHooks('onStart', []);
|
|
247
|
+
this._emit('start', {});
|
|
136
248
|
}
|
|
137
249
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
250
|
+
_pkOf(table) {
|
|
251
|
+
const schema = this._schemas[table];
|
|
252
|
+
if (!schema) return null;
|
|
253
|
+
for (const [k, def] of Object.entries(schema)) {
|
|
254
|
+
const isPk = (typeof def === 'string' && /\bprimary\s+key\b/i.test(def))
|
|
255
|
+
|| !!(def && typeof def === 'object' && (def.primaryKey || def.primary_key));
|
|
256
|
+
if (isPk) return k;
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
_pkAutoIncrement(table) {
|
|
262
|
+
const schema = this._schemas[table];
|
|
263
|
+
const pk = this._pkOf(table);
|
|
264
|
+
if (!pk || !schema) return false;
|
|
265
|
+
const def = schema[pk];
|
|
266
|
+
if (typeof def === 'string') return /\bauto_?increment\b/i.test(def);
|
|
267
|
+
return !!(def && def.autoIncrement);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
_coercePkId(table, id) {
|
|
271
|
+
const schema = this._schemas[table];
|
|
272
|
+
const pk = this._pkOf(table);
|
|
273
|
+
if (!schema || !pk) return id;
|
|
274
|
+
const def = schema[pk];
|
|
275
|
+
let t = null;
|
|
276
|
+
if (typeof def === 'string') t = def.toLowerCase();
|
|
277
|
+
else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
|
|
278
|
+
if (['integer', 'int', 'bigint'].includes(t)) return Number(id);
|
|
279
|
+
return id;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
_findRawById(table, id) {
|
|
283
|
+
const pk = this._pkOf(table);
|
|
284
|
+
if (pk) {
|
|
285
|
+
const filter = {};
|
|
286
|
+
filter[pk] = this._coercePkId(table, id);
|
|
287
|
+
const r = safeJsonParse(jsql_find(table, JSON.stringify(filter), 1, 0));
|
|
288
|
+
if (r && r.error) return r;
|
|
289
|
+
return Array.isArray(r) && r.length > 0 ? r[0] : null;
|
|
290
|
+
}
|
|
291
|
+
return safeJsonParse(jsql_find_by_id(table, BigInt(id)));
|
|
142
292
|
}
|
|
143
293
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
294
|
+
_seqIdByPk(table, id) {
|
|
295
|
+
const pk = this._pkOf(table);
|
|
296
|
+
if (pk) {
|
|
297
|
+
const filter = {};
|
|
298
|
+
filter[pk] = this._coercePkId(table, id);
|
|
299
|
+
const r = safeJsonParse(jsql_find(table, JSON.stringify(filter), 1, 0));
|
|
300
|
+
if (Array.isArray(r) && r.length > 0) return r[0].id;
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
return Number(id);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async _insertBatch(table, rows) {
|
|
307
|
+
const schema = this._schemas[table];
|
|
308
|
+
if (schema) {
|
|
309
|
+
rows = rows.map(row => {
|
|
310
|
+
const out = {};
|
|
311
|
+
for (const [k, v] of Object.entries(row)) {
|
|
312
|
+
const def = schema[k];
|
|
313
|
+
let t = null;
|
|
314
|
+
if (typeof def === 'string') t = def.toLowerCase();
|
|
315
|
+
else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
|
|
316
|
+
if (v !== null && v !== undefined && ['json', 'array', 'object'].includes(t) && typeof v !== 'string') {
|
|
317
|
+
out[k] = JSON.stringify(v);
|
|
318
|
+
} else if (v instanceof Date) {
|
|
319
|
+
out[k] = v.toISOString();
|
|
320
|
+
} else {
|
|
321
|
+
out[k] = v;
|
|
163
322
|
}
|
|
164
323
|
}
|
|
324
|
+
return out;
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const r = safeJsonParse(jsql_insert_json(table, JSON.stringify(rows)));
|
|
328
|
+
if (r && r.error) throw new Error(r.error);
|
|
329
|
+
if (r && Array.isArray(r) && !this._pkAutoIncrement(table)) {
|
|
330
|
+
const pk = this._pkOf(table);
|
|
331
|
+
if (pk) {
|
|
332
|
+
return r.map((seq, i) => {
|
|
333
|
+
const row = rows[i];
|
|
334
|
+
return (row && row[pk] !== undefined && row[pk] !== null) ? row[pk] : seq;
|
|
335
|
+
});
|
|
165
336
|
}
|
|
166
337
|
}
|
|
167
|
-
|
|
168
|
-
this._emit('start', {});
|
|
338
|
+
return r;
|
|
169
339
|
}
|
|
170
340
|
|
|
171
|
-
async
|
|
172
|
-
if (!this._runHooks('beforeFlush', [])) return;
|
|
173
|
-
|
|
341
|
+
async _flush() {
|
|
342
|
+
if (!this._runHooks('beforeFlush', [])) return null;
|
|
343
|
+
const flushed = {};
|
|
344
|
+
for (const [table, rows] of Object.entries(this._buffer)) {
|
|
345
|
+
if (rows.length === 0) continue;
|
|
346
|
+
const r = await this._insertBatch(table, rows);
|
|
347
|
+
flushed[table] = r;
|
|
348
|
+
}
|
|
349
|
+
this._buffer = {};
|
|
350
|
+
this._bufferSize = 0;
|
|
174
351
|
this._runHooks('afterFlush', []);
|
|
352
|
+
return flushed;
|
|
175
353
|
}
|
|
176
354
|
|
|
177
|
-
async
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
355
|
+
async flush() {
|
|
356
|
+
await this._flush();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async insert(table, data) {
|
|
360
|
+
const arr = Array.isArray(data) ? data : [data];
|
|
361
|
+
if (!this._runHooks('beforeInsert', [table, arr])) return [];
|
|
362
|
+
if (arr.length > 1) {
|
|
363
|
+
await this._flush();
|
|
364
|
+
let result;
|
|
365
|
+
for (let i = 0; i < arr.length; i += this._flushThreshold) {
|
|
366
|
+
const chunk = arr.slice(i, i + this._flushThreshold);
|
|
367
|
+
const r = await this._insertBatch(table, chunk);
|
|
368
|
+
if (r && r.error) throw new Error(r.error);
|
|
369
|
+
if (!result) result = r;
|
|
370
|
+
}
|
|
371
|
+
this._emit('insert', { table, count: arr.length, ids: result });
|
|
372
|
+
this._runHooks('afterInsert', [table, arr, result]);
|
|
373
|
+
return result;
|
|
183
374
|
}
|
|
184
|
-
this.
|
|
185
|
-
this.
|
|
186
|
-
this.
|
|
375
|
+
if (!this._buffer[table]) this._buffer[table] = [];
|
|
376
|
+
this._buffer[table].push(arr[0]);
|
|
377
|
+
this._bufferSize++;
|
|
378
|
+
const flushed = await this._flush();
|
|
379
|
+
return flushed && flushed[table] ? flushed[table] : null;
|
|
187
380
|
}
|
|
188
381
|
|
|
189
|
-
|
|
382
|
+
async insertMany(table, data) {
|
|
383
|
+
return this.insert(table, data);
|
|
384
|
+
}
|
|
190
385
|
|
|
191
386
|
async createTable(name, schema) {
|
|
387
|
+
await this._flush();
|
|
192
388
|
if (!this._runHooks('beforeCreateTable', [name, schema])) return null;
|
|
193
|
-
const r = safeJsonParse(jsql_create_table(name, JSON.stringify(schema)));
|
|
389
|
+
const r = safeJsonParse(jsql_create_table(name, JSON.stringify(mapNativeSchema(schema))));
|
|
194
390
|
if (r && r.ok === false) throw new Error(r.error || 'create table failed');
|
|
195
391
|
this._tableNames.add(name);
|
|
196
|
-
this._schemas[name] = schema;
|
|
392
|
+
this._schemas[name] = typeof schema === 'string' ? mapNativeSchema(schema) : schema;
|
|
197
393
|
this._emit('createTable', { name, schema });
|
|
198
394
|
this._runHooks('afterCreateTable', [name, schema]);
|
|
199
|
-
this._schedulePersist();
|
|
200
395
|
return r;
|
|
201
396
|
}
|
|
202
397
|
|
|
203
398
|
async dropTable(name) {
|
|
399
|
+
await this._flush();
|
|
204
400
|
if (!this._runHooks('beforeDropTable', [name])) return null;
|
|
205
401
|
const r = safeJsonParse(jsql_drop_table(name));
|
|
206
402
|
if (r && r.ok === false) throw new Error(r.error || 'drop table failed');
|
|
@@ -208,197 +404,166 @@ export class JSQL {
|
|
|
208
404
|
delete this._schemas[name];
|
|
209
405
|
this._emit('dropTable', { name });
|
|
210
406
|
this._runHooks('afterDropTable', [name]);
|
|
211
|
-
this._schedulePersist();
|
|
212
407
|
return r;
|
|
213
408
|
}
|
|
214
409
|
|
|
215
|
-
async insert(table, data) {
|
|
216
|
-
const arr = Array.isArray(data) ? data : [data];
|
|
217
|
-
if (!this._runHooks('beforeInsert', [table, arr])) return [];
|
|
218
|
-
let result = [];
|
|
219
|
-
for (let i = 0; i < arr.length; i += PAGE) {
|
|
220
|
-
const r = safeJsonParse(jsql_insert_json(table, JSON.stringify(arr.slice(i, i + PAGE))));
|
|
221
|
-
if (r && r.error) throw new Error(r.error);
|
|
222
|
-
result = result.concat(r || []);
|
|
223
|
-
}
|
|
224
|
-
this._emit('insert', { table, count: arr.length, ids: result });
|
|
225
|
-
this._runHooks('afterInsert', [table, arr, result]);
|
|
226
|
-
this._schedulePersist();
|
|
227
|
-
return result;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async insertMany(table, data) {
|
|
231
|
-
return this.insert(table, data);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
410
|
async findById(table, id) {
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async findByIds(table, ids) {
|
|
241
|
-
const r = safeJsonParse(jsql_find_by_ids(table, JSON.stringify(ids)));
|
|
411
|
+
await this._flush();
|
|
412
|
+
if (!this._runHooks('beforeFind', [table, { id }])) return null;
|
|
413
|
+
const r = this._findRawById(table, id);
|
|
242
414
|
if (r && r.error) throw new Error(r.error);
|
|
415
|
+
this._runHooks('afterFind', [table, { id }, r]);
|
|
416
|
+
const schema = this._schemas[table];
|
|
417
|
+
if (schema && r && typeof r === 'object') return restoreRow(r, schema);
|
|
243
418
|
return r;
|
|
244
419
|
}
|
|
245
420
|
|
|
246
421
|
async find(table, filter, opts = {}) {
|
|
422
|
+
await this._flush();
|
|
247
423
|
if (!this._runHooks('beforeFind', [table, { filter, opts }])) return [];
|
|
248
|
-
const { limit = 100, offset = 0 } = opts;
|
|
249
424
|
const filterStr = filter ? JSON.stringify(filter) : '';
|
|
425
|
+
const { limit = 100, offset = 0 } = opts;
|
|
250
426
|
const r = safeJsonParse(jsql_find(table, filterStr, limit, offset));
|
|
251
427
|
if (r && r.error) throw new Error(r.error);
|
|
252
428
|
this._runHooks('afterFind', [table, { filter, opts }, r]);
|
|
429
|
+
const schema = this._schemas[table];
|
|
430
|
+
if (schema && Array.isArray(r)) return r.map(row => restoreRow(row, schema));
|
|
253
431
|
return r;
|
|
254
432
|
}
|
|
255
433
|
|
|
256
434
|
async count(table) {
|
|
435
|
+
await this._flush();
|
|
436
|
+
this._runHooks('beforeCount', [table]);
|
|
257
437
|
const r = parseInt(jsql_count(table), 10);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
async updateById(table, id, data) {
|
|
262
|
-
if (!this._runHooks('beforeUpdate', [table, id, data])) return;
|
|
263
|
-
const r = safeJsonParse(jsql_update_by_id(table, BigInt(id), JSON.stringify(data)));
|
|
264
|
-
if (r && r.ok === false) throw new Error(r.error || 'update failed');
|
|
265
|
-
this._emit('update', { table, id, data });
|
|
266
|
-
this._runHooks('afterUpdate', [table, id, data, r]);
|
|
267
|
-
this._schedulePersist();
|
|
268
|
-
return r;
|
|
438
|
+
const n = isNaN(r) ? 0 : r;
|
|
439
|
+
this._runHooks('afterCount', [table, n]);
|
|
440
|
+
return n;
|
|
269
441
|
}
|
|
270
442
|
|
|
271
443
|
async updateByIds(table, entries) {
|
|
272
|
-
const pairs =
|
|
444
|
+
const pairs = [];
|
|
445
|
+
for (const [id, data] of entries) {
|
|
446
|
+
const seq = this._seqIdByPk(table, id);
|
|
447
|
+
if (seq !== null) pairs.push([seq, data]);
|
|
448
|
+
}
|
|
273
449
|
if (!this._runHooks('beforeUpdate', [table, pairs])) return;
|
|
450
|
+
await this._flush();
|
|
274
451
|
const r = safeJsonParse(jsql_update_by_ids(table, JSON.stringify(pairs)));
|
|
275
452
|
if (r && r.error) throw new Error(r.error);
|
|
276
453
|
this._emit('update', { table, entries: pairs, result: r });
|
|
277
454
|
this._runHooks('afterUpdate', [table, pairs, r]);
|
|
278
|
-
this._schedulePersist();
|
|
279
|
-
return r;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
async removeById(table, id) {
|
|
283
|
-
if (!this._runHooks('beforeDelete', [table, id])) return;
|
|
284
|
-
const r = safeJsonParse(jsql_remove_by_id(table, BigInt(id)));
|
|
285
|
-
if (r && r.ok === false) throw new Error(r.error || 'remove failed');
|
|
286
|
-
this._emit('delete', { table, id });
|
|
287
|
-
this._runHooks('afterDelete', [table, id, r]);
|
|
288
|
-
this._schedulePersist();
|
|
289
455
|
return r;
|
|
290
456
|
}
|
|
291
457
|
|
|
292
458
|
async removeByIds(table, ids) {
|
|
293
459
|
if (!this._runHooks('beforeDelete', [table, ids])) return;
|
|
294
|
-
|
|
460
|
+
await this._flush();
|
|
461
|
+
const seqs = ids.map(id => this._seqIdByPk(table, id)).filter(s => s !== null);
|
|
462
|
+
const r = safeJsonParse(jsql_remove_by_ids(table, JSON.stringify(seqs)));
|
|
295
463
|
if (r && r.error) throw new Error(r.error);
|
|
296
|
-
this._emit('delete', { table, ids, result: r });
|
|
464
|
+
this._emit('delete', { table, ids: seqs, result: r });
|
|
297
465
|
this._runHooks('afterDelete', [table, ids, r]);
|
|
298
|
-
this._schedulePersist();
|
|
299
466
|
return r;
|
|
300
467
|
}
|
|
301
468
|
|
|
302
|
-
async
|
|
303
|
-
|
|
469
|
+
async findByIds(table, ids) {
|
|
470
|
+
await this._flush();
|
|
471
|
+
if (!this._runHooks('beforeFind', [table, { ids }])) return null;
|
|
472
|
+
let r;
|
|
473
|
+
if (this._pkOf(table)) {
|
|
474
|
+
r = ids.map(id => this._findRawById(table, id)).filter(x => x !== null && !(x && x.error));
|
|
475
|
+
} else {
|
|
476
|
+
r = safeJsonParse(jsql_find_by_ids(table, JSON.stringify(ids)));
|
|
477
|
+
}
|
|
478
|
+
if (r && r.error) throw new Error(r.error);
|
|
479
|
+
this._runHooks('afterFind', [table, { ids }, r]);
|
|
480
|
+
const schema = this._schemas[table];
|
|
481
|
+
if (schema && Array.isArray(r)) return r.map(row => restoreRow(row, schema));
|
|
482
|
+
return r;
|
|
304
483
|
}
|
|
305
484
|
|
|
306
|
-
|
|
307
|
-
return
|
|
485
|
+
findByIdsRaw(table, ids) {
|
|
486
|
+
return this.findByIds(table, ids);
|
|
308
487
|
}
|
|
309
488
|
|
|
310
|
-
|
|
311
|
-
|
|
489
|
+
async updateById(table, id, data) {
|
|
490
|
+
if (!this._runHooks('beforeUpdate', [table, id, data])) return;
|
|
491
|
+
await this._flush();
|
|
492
|
+
const seq = this._seqIdByPk(table, id);
|
|
493
|
+
if (seq === null) return null;
|
|
494
|
+
const r = safeJsonParse(jsql_update_by_id(table, BigInt(seq), JSON.stringify(data)));
|
|
495
|
+
if (r && r.ok === false) throw new Error(r.error || 'update failed');
|
|
496
|
+
this._emit('update', { table, id, data });
|
|
497
|
+
this._runHooks('afterUpdate', [table, id, data, r]);
|
|
498
|
+
return r;
|
|
312
499
|
}
|
|
313
500
|
|
|
314
|
-
async
|
|
315
|
-
|
|
501
|
+
async removeById(table, id) {
|
|
502
|
+
if (!this._runHooks('beforeDelete', [table, id])) return;
|
|
503
|
+
await this._flush();
|
|
504
|
+
const seq = this._seqIdByPk(table, id);
|
|
505
|
+
if (seq === null) return null;
|
|
506
|
+
const r = safeJsonParse(jsql_remove_by_id(table, BigInt(seq)));
|
|
507
|
+
if (r && r.ok === false) throw new Error(r.error || 'remove failed');
|
|
508
|
+
this._emit('delete', { table, id });
|
|
509
|
+
this._runHooks('afterDelete', [table, id, r]);
|
|
510
|
+
return r;
|
|
316
511
|
}
|
|
317
512
|
|
|
318
|
-
/* ---------- transactions (snapshot rollback) ---------- */
|
|
319
|
-
|
|
320
513
|
async beginTx() {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
514
|
+
await this._flush();
|
|
515
|
+
const r = safeJsonParse(jsql_begin_tx());
|
|
516
|
+
if (r && r.ok === false) throw new Error(r.error || 'begin transaction failed');
|
|
517
|
+
this._txId = r.txId;
|
|
518
|
+
return r.txId;
|
|
324
519
|
}
|
|
325
520
|
|
|
326
|
-
async commitTx() {
|
|
327
|
-
|
|
328
|
-
|
|
521
|
+
async commitTx(txId) {
|
|
522
|
+
const r = safeJsonParse(jsql_commit_tx(String(txId)));
|
|
523
|
+
if (r && r.ok === false) throw new Error(r.error || 'commit transaction failed');
|
|
524
|
+
return true;
|
|
329
525
|
}
|
|
330
526
|
|
|
331
|
-
async rollbackTx() {
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (snap) await this._restoreSnapshot(snap);
|
|
336
|
-
this._schedulePersist();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/* same-name aliases used by some SQL paths */
|
|
340
|
-
async begin() { return this.beginTx(); }
|
|
341
|
-
async commit() { return this.commitTx(); }
|
|
342
|
-
async rollback() { return this.rollbackTx(); }
|
|
343
|
-
|
|
344
|
-
async _snapshotAll() {
|
|
345
|
-
const tables = {};
|
|
346
|
-
for (const name of this._tableNames) {
|
|
347
|
-
const rows = [];
|
|
348
|
-
let offset = 0;
|
|
349
|
-
for (;;) {
|
|
350
|
-
const page = safeJsonParse(jsql_find(name, '', PAGE, offset)) || [];
|
|
351
|
-
rows.push(...page);
|
|
352
|
-
if (page.length < PAGE) break;
|
|
353
|
-
offset += page.length;
|
|
354
|
-
}
|
|
355
|
-
tables[name] = { schema: this._schemas[name], rows: rows.map(r => r.fields) };
|
|
356
|
-
}
|
|
357
|
-
return tables;
|
|
527
|
+
async rollbackTx(txId) {
|
|
528
|
+
const r = safeJsonParse(jsql_rollback_tx(String(txId)));
|
|
529
|
+
if (r && r.ok === false) throw new Error(r.error || 'rollback transaction failed');
|
|
530
|
+
return true;
|
|
358
531
|
}
|
|
359
532
|
|
|
360
|
-
async
|
|
361
|
-
|
|
362
|
-
this._tableNames.clear();
|
|
363
|
-
for (const [name, t] of Object.entries(tables)) {
|
|
364
|
-
jsql_create_table(name, JSON.stringify(t.schema));
|
|
365
|
-
this._tableNames.add(name);
|
|
366
|
-
this._schemas[name] = t.schema;
|
|
367
|
-
if (t.rows && t.rows.length) {
|
|
368
|
-
for (let i = 0; i < t.rows.length; i += PAGE) {
|
|
369
|
-
jsql_insert_json(name, JSON.stringify(t.rows.slice(i, i + PAGE)));
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
533
|
+
async beginTransaction() {
|
|
534
|
+
return this.beginTx();
|
|
373
535
|
}
|
|
374
536
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
537
|
+
async commit() {
|
|
538
|
+
if (this._txId === undefined) return true;
|
|
539
|
+
const r = await this.commitTx(this._txId);
|
|
540
|
+
this._txId = undefined;
|
|
541
|
+
return r;
|
|
379
542
|
}
|
|
380
543
|
|
|
381
|
-
async
|
|
382
|
-
this.
|
|
544
|
+
async rollback() {
|
|
545
|
+
if (this._txId === undefined) return true;
|
|
546
|
+
const r = await this.rollbackTx(this._txId);
|
|
547
|
+
this._txId = undefined;
|
|
548
|
+
return r;
|
|
383
549
|
}
|
|
384
550
|
|
|
385
|
-
async
|
|
386
|
-
this.
|
|
551
|
+
async hasTable(name) {
|
|
552
|
+
return this._tableNames.has(name);
|
|
387
553
|
}
|
|
388
554
|
|
|
389
|
-
async
|
|
390
|
-
|
|
391
|
-
throw new Error(`Unknown database '${name}'`);
|
|
392
|
-
}
|
|
555
|
+
async getTables() {
|
|
556
|
+
return Array.from(this._tableNames);
|
|
393
557
|
}
|
|
394
558
|
|
|
395
|
-
|
|
559
|
+
async getTableSchema(name) {
|
|
560
|
+
return this._schemas[name] || null;
|
|
561
|
+
}
|
|
396
562
|
|
|
397
|
-
async
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
563
|
+
async stop() {
|
|
564
|
+
await this._flush();
|
|
565
|
+
this._runHooks('onStop', []);
|
|
566
|
+
this._emit('stop', {});
|
|
401
567
|
}
|
|
402
568
|
}
|
|
403
569
|
|
|
404
|
-
export default { JSQL };
|