jsql-neo 6.5.7 → 6.5.9

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  > **One engine to rule them all** — a Rust-powered embedded database that speaks your language:
4
4
  > MySQL, PostgreSQL, MongoDB, Redis, SQL, TypeScript, **and the browser**.
5
5
 
6
- > **v6.5.7** — stable release · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo) · [jsql.vexify.top](https://jsql.vexify.top)
6
+ > **v6.5.9** — stable release · [github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo) · [jsql.vexify.top](https://jsql.vexify.top)
7
7
 
8
8
  ![Engines](https://img.shields.io/badge/engines-Native%20%7C%20WASM%20%7C%20Pure%20JS-7ee787)
9
9
  ![MySQL](https://img.shields.io/badge/protocol-MySQL%20compatible-1f6feb)
@@ -188,3 +188,6 @@ Apache-2.0 License — contributions welcome!
188
188
  - **GitHub:** [vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
189
189
  - **Issues:** [Open an issue](https://github.com/vexify-org/JSQL-neo/issues)
190
190
  - **PRs:** Fork → branch → PR — thank you!
191
+ - **Contributing:** [CONTRIBUTING.md](CONTRIBUTING.md)
192
+ - **Code of Conduct:** [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
193
+ - **Security:** [SECURITY.md](SECURITY.md) — please report vulnerabilities privately
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * JSQL-NEO v6.5.7 — Rust-Powered Embedded Database (WASM + HTTP)
2
+ * JSQL-NEO v6.5.9 — Rust-Powered Embedded Database (WASM + HTTP)
3
3
  *
4
4
  * @example
5
5
  * const jsql = require('jsql-neo');
package/lib/btree.js CHANGED
@@ -173,7 +173,7 @@ class BTree {
173
173
  const s = k === undefined || k === null ? '' : String(k);
174
174
  // 不能假设键按字典序排序(数字键按数值序),必须检查所有键
175
175
  if (s.startsWith(p)) {
176
- result.push(...node.values[i]);
176
+ for (const __e of node.values[i]) result.push(__e);
177
177
  }
178
178
  }
179
179
  node = node.next;
@@ -193,7 +193,7 @@ class BTree {
193
193
  const k = node.keys[i];
194
194
  if (k > max) return result;
195
195
  if (k >= min) {
196
- result.push(...node.values[i]);
196
+ for (const __e of node.values[i]) result.push(__e);
197
197
  }
198
198
  }
199
199
  node = node.next;
@@ -209,7 +209,7 @@ class BTree {
209
209
  let node = this._findLeaf(min);
210
210
  while (node) {
211
211
  for (let i = 0; i < node.keys.length; i++) {
212
- if (node.keys[i] > min) result.push(...node.values[i]);
212
+ if (node.keys[i] > min) for (const __e of node.values[i]) result.push(__e);
213
213
  }
214
214
  node = node.next;
215
215
  }
@@ -221,7 +221,7 @@ class BTree {
221
221
  let node = this._findLeaf(min);
222
222
  while (node) {
223
223
  for (let i = 0; i < node.keys.length; i++) {
224
- if (node.keys[i] >= min) result.push(...node.values[i]);
224
+ if (node.keys[i] >= min) for (const __e of node.values[i]) result.push(__e);
225
225
  }
226
226
  node = node.next;
227
227
  }
@@ -237,7 +237,7 @@ class BTree {
237
237
  while (!node.leaf) node = node.children[0];
238
238
  while (node) {
239
239
  for (let i = 0; i < node.keys.length; i++) {
240
- if (node.keys[i] < max) result.push(...node.values[i]);
240
+ if (node.keys[i] < max) for (const __e of node.values[i]) result.push(__e);
241
241
  else return result;
242
242
  }
243
243
  node = node.next;
@@ -251,7 +251,7 @@ class BTree {
251
251
  while (!node.leaf) node = node.children[0];
252
252
  while (node) {
253
253
  for (let i = 0; i < node.keys.length; i++) {
254
- if (node.keys[i] <= max) result.push(...node.values[i]);
254
+ if (node.keys[i] <= max) for (const __e of node.values[i]) result.push(__e);
255
255
  else return result;
256
256
  }
257
257
  node = node.next;
@@ -408,9 +408,9 @@ class BTree {
408
408
  left.keys.push(midKey);
409
409
  left.values.push(midVal);
410
410
  }
411
- left.keys.push(...right.keys);
412
- left.values.push(...right.values);
413
- if (!left.leaf) left.children.push(...right.children);
411
+ for (const __e of right.keys) left.keys.push(__e);
412
+ for (const __e of right.values) left.values.push(__e);
413
+ if (!left.leaf) for (const __e of right.children) left.children.push(__e);
414
414
  if (left.leaf) left.next = right.next;
415
415
 
416
416
  parent.keys.splice(index, 1);
package/lib/cache.js CHANGED
@@ -152,7 +152,7 @@ class Cache {
152
152
  throw new Error('WRONGTYPE Operation against a key holding the wrong kind of value');
153
153
  }
154
154
  if (!list) { list = []; this._getData().set(k, list); }
155
- list.push(...values.map(String));
155
+ for (const __e of values.map(String)) list.push(__e);
156
156
  this._dirty = true;
157
157
  return list.length;
158
158
  }
package/lib/database.js CHANGED
@@ -566,7 +566,7 @@ class Database {
566
566
  case 'insert': {
567
567
  const t = op.table && this._tables[op.table];
568
568
  if (!t || !Array.isArray(op.rows) || op.rows.length === 0) { failed++; break; }
569
- t._rows.push(...op.rows);
569
+ for (const __e of op.rows) t._rows.push(__e);
570
570
  touched.add(t);
571
571
  replayed++;
572
572
  break;
@@ -510,7 +510,7 @@ class JSQLFormat {
510
510
  t.schema = schema;
511
511
 
512
512
  const { rows: oldRows } = this.readTable(name);
513
- oldRows.push(...rows);
513
+ for (const __e of rows) oldRows.push(__e);
514
514
  this.writeTable(name, schema, oldRows, pkField);
515
515
  }
516
516
  }
@@ -151,6 +151,7 @@ class MongoServer {
151
151
  this.port = options.port ?? 27017;
152
152
  this.host = options.host || '127.0.0.1';
153
153
  this.dataDir = options.dataDir || null;
154
+ this.maxMessageSize = options.maxMessageSize || 48 * 1024 * 1024;
154
155
  this._engine = null;
155
156
  this._server = null;
156
157
  this._sockets = new Set();
@@ -201,6 +202,10 @@ class MongoServer {
201
202
  this._sockets.add(socket);
202
203
  socket.on('close', () => this._sockets.delete(socket));
203
204
  let buf = Buffer.isBuffer(existing) ? Buffer.from(existing) : Buffer.alloc(0);
205
+ if (buf.length > this.maxMessageSize) {
206
+ socket.destroy();
207
+ return;
208
+ }
204
209
  socket.on('error', () => {});
205
210
  let processing = false;
206
211
  let again = false;
@@ -211,7 +216,10 @@ class MongoServer {
211
216
  for (;;) {
212
217
  if (buf.length < 4) break;
213
218
  const len = buf.readInt32LE(0);
214
- if (len < 16 || len > 48 * 1024 * 1024) break;
219
+ if (len < 16 || len > this.maxMessageSize) {
220
+ socket.destroy();
221
+ return;
222
+ }
215
223
  if (buf.length < len) break;
216
224
  const msg = buf.slice(0, len);
217
225
  buf = buf.slice(len);
@@ -227,6 +235,10 @@ class MongoServer {
227
235
  }
228
236
  };
229
237
  socket.on('data', (chunk) => {
238
+ if (buf.length + chunk.length > this.maxMessageSize * 2) {
239
+ socket.destroy();
240
+ return;
241
+ }
230
242
  buf = Buffer.concat([buf, chunk]);
231
243
  processLoop();
232
244
  });
@@ -635,10 +647,14 @@ class MongoServer {
635
647
  doc[k] = vals.reduce((a, b) => a + b, 0) / vals.length;
636
648
  } else if (v.$min !== undefined) {
637
649
  const vals = grow.map(r => r[field]).filter(v => v != null);
638
- doc[k] = vals.length ? Math.min(...vals) : null;
650
+ let best = null;
651
+ for (const x of vals) if (best === null || x < best) best = x;
652
+ doc[k] = best;
639
653
  } else if (v.$max !== undefined) {
640
654
  const vals = grow.map(r => r[field]).filter(v => v != null);
641
- doc[k] = vals.length ? Math.max(...vals) : null;
655
+ let best = null;
656
+ for (const x of vals) if (best === null || x > best) best = x;
657
+ doc[k] = best;
642
658
  } else if (v.$first !== undefined) {
643
659
  doc[k] = grow[0] ? grow[0][field] : null;
644
660
  } else if (v.$last !== undefined) {
@@ -1297,11 +1297,14 @@ class MysqlServer {
1297
1297
  );
1298
1298
  friendly.code = 'EADDRINUSE';
1299
1299
  friendly.cause = err;
1300
- if (cb) cb(friendly);
1301
- else throw friendly;
1302
- } else if (cb) cb(err);
1300
+ if (typeof this.options.onError === 'function') this.options.onError(friendly);
1301
+ else if (cb) cb(friendly);
1302
+ else console.error('[jsql-neo] mysql server error:', friendly.message);
1303
+ } else if (typeof this.options.onError === 'function') this.options.onError(err);
1304
+ else if (cb) cb(err);
1305
+ else console.error('[jsql-neo] mysql server error:', err.message);
1303
1306
  });
1304
- this._server.listen(this.port, this.host, cb || (() => {}));
1307
+ this._server.listen(this.port, this.host, cb ? () => cb() : undefined);
1305
1308
  }).catch(err => {
1306
1309
  if (cb) cb(err);
1307
1310
  else throw err;
@@ -851,7 +851,7 @@ class JSQL {
851
851
  }
852
852
  if (!(await this._runHooks('beforeUpdate', [table, pairs]))) return;
853
853
  if (!this._opBuffer.update[table]) this._opBuffer.update[table] = [];
854
- this._opBuffer.update[table].push(...pairs);
854
+ for (const __e of pairs) this._opBuffer.update[table].push(__e);
855
855
  this._scheduleOpFlush();
856
856
  }
857
857
 
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const { execFileSync } = require('child_process');
5
+
6
+ const MIN_NAPI_VERSION = 6;
7
+
8
+ function detectLibc(platform = process.platform) {
9
+ if (platform !== 'linux') return null;
10
+ try {
11
+ const report = process.report && typeof process.report.getReport === 'function'
12
+ ? process.report.getReport()
13
+ : null;
14
+ if (report && report.header && report.header.glibcVersionRuntime) return 'gnu';
15
+ } catch (_) { /* ignore */ }
16
+ try {
17
+ if (fs.existsSync('/etc/alpine-release')) return 'musl';
18
+ } catch (_) { /* ignore */ }
19
+ try {
20
+ const out = execFileSync('ldd', ['--version'], {
21
+ encoding: 'utf8',
22
+ stdio: ['ignore', 'pipe', 'pipe'],
23
+ });
24
+ if (/musl/i.test(out)) return 'musl';
25
+ if (/glibc|GNU libc/i.test(out)) return 'gnu';
26
+ } catch (_) { /* ignore */ }
27
+ return 'gnu';
28
+ }
29
+
30
+ function nativeTargetSuffix(platform = process.platform, arch = process.arch, libc = detectLibc(platform)) {
31
+ const p = platform === 'win32' ? 'win32' : platform === 'darwin' ? 'darwin' : 'linux';
32
+ const a = arch === 'x64' ? 'x64' : arch === 'arm64' ? 'arm64' : arch;
33
+ if (p === 'linux') return `${p}-${a}-${libc === 'musl' ? 'musl' : 'gnu'}`;
34
+ if (p === 'darwin') return `${p}-${a}`;
35
+ return `${p}-${a}-msvc`;
36
+ }
37
+
38
+ function nativeNapiVersion() {
39
+ const v = Number(process.versions && process.versions.napi);
40
+ return Number.isFinite(v) ? v : 0;
41
+ }
42
+
43
+ function supportsNativeNapi() {
44
+ return nativeNapiVersion() >= MIN_NAPI_VERSION;
45
+ }
46
+
47
+ module.exports = {
48
+ MIN_NAPI_VERSION,
49
+ detectLibc,
50
+ nativeTargetSuffix,
51
+ nativeNapiVersion,
52
+ supportsNativeNapi,
53
+ };
@@ -170,7 +170,7 @@ function applyModifier(doc, update) {
170
170
  for (const [k, v] of Object.entries(payload)) {
171
171
  const cur = getPath(doc, k);
172
172
  const arr = Array.isArray(cur) ? cur : [];
173
- if (v && typeof v === 'object' && v.$each) arr.push(...v.$each);
173
+ if (v && typeof v === 'object' && v.$each) for (const __e of v.$each) arr.push(__e);
174
174
  else arr.push(v);
175
175
  setPath(doc, k, arr);
176
176
  }
package/lib/pg_server.js CHANGED
@@ -845,11 +845,12 @@ class PgServer {
845
845
  this._sockets.add(socket);
846
846
  new PgConnection(socket, this);
847
847
  });
848
- this._server.listen(this.port, this.host, cb || (() => {}));
849
848
  this._server.on('error', (err) => {
850
849
  if (this.options.onError) this.options.onError(err);
851
- else throw err;
850
+ else if (cb) cb(err);
851
+ else console.error('[jsql-neo] pg server error:', err.message);
852
852
  });
853
+ this._server.listen(this.port, this.host, cb ? () => cb() : undefined);
853
854
  return this;
854
855
  }
855
856
 
package/lib/query.js CHANGED
@@ -706,7 +706,7 @@ class Query {
706
706
  const result = [];
707
707
  for (const group of Object.values(groups)) {
708
708
  this._applyWindowToGroup(group, fn, field, dir);
709
- result.push(...group);
709
+ for (const __e of group) result.push(__e);
710
710
  }
711
711
  return result;
712
712
  }
@@ -335,7 +335,7 @@ class RedisServer {
335
335
  if (cmd === 'LPUSH') {
336
336
  for (const val of args.slice(1)) list.unshift(val);
337
337
  } else {
338
- list.push(...args.slice(1));
338
+ for (const __e of args.slice(1)) list.push(__e);
339
339
  }
340
340
  this.db.set(args[0], { type: 'list', val: list, ttl: v ? v.ttl : null });
341
341
  this._scheduleSnapshot();
package/lib/sql.js CHANGED
@@ -4114,13 +4114,25 @@ function applyScalarFunction(fnNode, row, ctx) {
4114
4114
  }
4115
4115
  function depth(obj) {
4116
4116
  if (typeof obj !== 'object' || obj === null) return 0;
4117
- if (Array.isArray(obj)) {
4118
- if (obj.length === 0) return 1;
4119
- return 1 + Math.max(...obj.map(depth));
4117
+ let max = 0;
4118
+ const stack = [{ o: obj, d: 1 }];
4119
+ while (stack.length) {
4120
+ const { o, d } = stack.pop();
4121
+ if (typeof o !== 'object' || o === null) continue;
4122
+ if (d > max) max = d;
4123
+ if (Array.isArray(o)) {
4124
+ for (const x of o) {
4125
+ if (x && typeof x === 'object') stack.push({ o: x, d: d + 1 });
4126
+ }
4127
+ } else {
4128
+ const ks = Object.keys(o);
4129
+ for (const k of ks) {
4130
+ const x = o[k];
4131
+ if (x && typeof x === 'object') stack.push({ o: x, d: d + 1 });
4132
+ }
4133
+ }
4120
4134
  }
4121
- const keys = Object.keys(obj);
4122
- if (keys.length === 0) return 1;
4123
- return 1 + Math.max(...keys.map(k => depth(obj[k])));
4135
+ return max;
4124
4136
  }
4125
4137
  return depth(data);
4126
4138
  }
@@ -5915,7 +5927,7 @@ class SQLExecutor {
5915
5927
  _rollupRow(groupReps) {
5916
5928
  const all = [];
5917
5929
  for (const rep of groupReps) {
5918
- if (rep._group) all.push(...rep._group);
5930
+ if (rep._group) for (const __e of rep._group) all.push(__e);
5919
5931
  else all.push(rep);
5920
5932
  }
5921
5933
  const out = {};
@@ -5979,8 +5991,12 @@ class SQLExecutor {
5979
5991
  const numeric = (v) => typeof v === 'number' ||
5980
5992
  (typeof v === 'string' && String(v).trim() !== '' && Number.isFinite(Number(v)));
5981
5993
  if (vs.every(numeric)) {
5982
- const nums = vs.map(v => Number(v));
5983
- return fn === 'MIN' ? Math.min(...nums) : Math.max(...nums);
5994
+ let best = Number(vs[0]);
5995
+ for (let i = 1; i < vs.length; i++) {
5996
+ const n = Number(vs[i]);
5997
+ if (fn === 'MIN' ? n < best : n > best) best = n;
5998
+ }
5999
+ return best;
5984
6000
  }
5985
6001
  let best = vs[0];
5986
6002
  for (const v of vs.slice(1)) {
package/lib/tui.js CHANGED
@@ -72,7 +72,9 @@ function renderTable(headers, rows, maxWidth) {
72
72
  for (const w of widths) total += w + 3;
73
73
  if (total > maxCol && widths.length > 1) {
74
74
  let overshoot = total - maxCol;
75
- let i = widths.indexOf(Math.max(...widths));
75
+ let maxW = widths[0];
76
+ for (const w of widths) if (w > maxW) maxW = w;
77
+ let i = widths.indexOf(maxW);
76
78
  while (overshoot > 0) {
77
79
  if (i === -1 || widths[i] <= 3) break;
78
80
  const cut = Math.min(widths[i] - 3, overshoot);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsql-neo",
3
- "version": "6.5.7",
3
+ "version": "6.5.9",
4
4
  "description": "JSQL-NEO \u2014 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",
package/postinstall.js CHANGED
@@ -4,6 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const http = require('http');
6
6
  const https = require('https');
7
+ const { detectLibc, nativeTargetSuffix, nativeNapiVersion, supportsNativeNapi, MIN_NAPI_VERSION } = require('./lib/native_target');
7
8
 
8
9
  const ROOT = __dirname;
9
10
  const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
@@ -69,13 +70,12 @@ const REPO = (pkg.repository && pkg.repository.url)
69
70
  ? pkg.repository.url.replace(/\.git$/, '').replace(/^.*github\.com\//i, '')
70
71
  : 'vexify-org/JSQL-neo';
71
72
 
72
- // 与 .github/workflows/build-native.yml 中发布的资产命名保持一致
73
- function nativeTargetSuffix(platform = process.platform, arch = process.arch) {
74
- const p = platform === 'win32' ? 'win32' : platform === 'darwin' ? 'darwin' : 'linux';
75
- const a = arch === 'x64' ? 'x64' : arch === 'arm64' ? 'arm64' : arch;
76
- if (p === 'linux') return `${p}-${a}-gnu`;
77
- if (p === 'darwin') return `${p}-${a}`;
78
- return `${p}-${a}-msvc`;
73
+ function sameOrigin(a, b) {
74
+ try { return new URL(a).origin === new URL(b).origin; } catch (_) { return false; }
75
+ }
76
+
77
+ function safeRedirect(next) {
78
+ return /^https?:/i.test(String(next || ''));
79
79
  }
80
80
 
81
81
  function download(url, dest, headers, redirects = 0) {
@@ -89,7 +89,11 @@ function download(url, dest, headers, redirects = 0) {
89
89
  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
90
90
  res.resume();
91
91
  const next = new URL(res.headers.location, url).toString();
92
- download(next, dest, headers, redirects + 1).then(resolve, reject);
92
+ if (!safeRedirect(next)) {
93
+ reject(new Error(`invalid redirect target for ${url}`));
94
+ return;
95
+ }
96
+ download(next, dest, sameOrigin(next, url) ? headers : undefined, redirects + 1).then(resolve, reject);
93
97
  return;
94
98
  }
95
99
  if (res.statusCode !== 200) {
@@ -145,7 +149,11 @@ function fetchText(url, headers, redirects = 0) {
145
149
  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
146
150
  res.resume();
147
151
  const next = new URL(res.headers.location, url).toString();
148
- fetchText(next, headers, redirects + 1).then(resolve, reject);
152
+ if (!safeRedirect(next)) {
153
+ reject(new Error(`invalid redirect target for ${url}`));
154
+ return;
155
+ }
156
+ fetchText(next, sameOrigin(next, url) ? headers : undefined, redirects + 1).then(resolve, reject);
149
157
  return;
150
158
  }
151
159
  if (res.statusCode !== 200) {
@@ -262,7 +270,15 @@ function tryLoadNative() {
262
270
  }
263
271
 
264
272
  async function downloadAndVerify() {
265
- const asset = `jsql-neo-native.${nativeTargetSuffix()}.node`;
273
+ if (!supportsNativeNapi()) {
274
+ console.warn(
275
+ `[jsql-neo] Node ${process.versions.node} provides N-API ${nativeNapiVersion() || 'unknown'}, ` +
276
+ `but the native module requires N-API >= ${MIN_NAPI_VERSION}; skipping native download.`
277
+ );
278
+ return false;
279
+ }
280
+ const libc = detectLibc();
281
+ const asset = process.env.JSQL_NATIVE_ASSET || `jsql-neo-native.${nativeTargetSuffix(process.platform, process.arch, libc)}.node`;
266
282
  const raw = `https://github.com/${REPO}/releases/download/${TAG}/${asset}`;
267
283
 
268
284
  let expected = null;
@@ -274,11 +290,18 @@ async function downloadAndVerify() {
274
290
 
275
291
  const allowUnverified = process.env.JSQL_ALLOW_UNVERIFIED_NATIVE === '1';
276
292
  if (!expected && !allowUnverified) {
277
- // fail closed:拿不到 SHA256SUMS 就不下载,绝不 require 未经校验的二进制
278
- console.warn(
279
- '[jsql-neo] no SHA256SUMS available for this release; refusing to download an unverified native module.\n' +
280
- '[jsql-neo] set JSQL_NATIVE_SHA256=<64-hex> to pin a checksum, or JSQL_ALLOW_UNVERIFIED_NATIVE=1 to override (unsafe).'
281
- );
293
+ if (libc === 'musl') {
294
+ console.warn(
295
+ `[jsql-neo] musl libc detected but this release has no native asset for ${asset}; ` +
296
+ 'falling back to the WASM/JS engine. Set JSQL_NATIVE_ASSET to a compatible musl build if available.'
297
+ );
298
+ } else {
299
+ // fail closed:拿不到 SHA256SUMS 就不下载,绝不 require 未经校验的二进制
300
+ console.warn(
301
+ '[jsql-neo] no SHA256SUMS available for this release; refusing to download an unverified native module.\n' +
302
+ '[jsql-neo] set JSQL_NATIVE_SHA256=<64-hex> to pin a checksum, or JSQL_ALLOW_UNVERIFIED_NATIVE=1 to override (unsafe).'
303
+ );
304
+ }
282
305
  return false;
283
306
  }
284
307
  if (!expected) {
@@ -323,6 +346,13 @@ async function ensureNativeNode() {
323
346
  console.log('[jsql-neo] native module ready: native/jsql-neo-native.node');
324
347
  return;
325
348
  }
349
+ if (!supportsNativeNapi()) {
350
+ console.warn(
351
+ `[jsql-neo] Node ${process.versions.node} provides N-API ${nativeNapiVersion() || 'unknown'}, ` +
352
+ `but the native module requires N-API >= ${MIN_NAPI_VERSION}; using the WASM/JS engine.`
353
+ );
354
+ return;
355
+ }
326
356
  if (await downloadAndVerify()) return;
327
357
  buildNativeFromSource();
328
358
  }