@coradb/coradb-sqlsyntaxchecker-wasm 0.3.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/index.js ADDED
@@ -0,0 +1,529 @@
1
+ // CoraDB-SQLSyntaxChecker-Wasm — is this valid CoraDB SQL?
2
+ //
3
+ // The engine's own grammar and lexer (src/parser/corasql_grammar.y and
4
+ // corasql_lexer.l), with nothing built from what they read, compiled to
5
+ // WebAssembly. Runs in the browser and in Node 18+, needs no server and no
6
+ // connection, and answers exactly as the engine's parser would: accepted, or
7
+ // where it stopped and why.
8
+ //
9
+ // Given the database's schema (a CoraDB schema document, see schema.json),
10
+ // validate() also checks the names a statement uses: that its tables, views,
11
+ // node and edge classes exist, that they have the columns it reads, that the
12
+ // functions and procedures it calls exist and get the right number of
13
+ // arguments, that the serials it reads exist, and that an INSERT gives each
14
+ // row as many values as it has columns. Data types and the engine's other
15
+ // rules are not checked: those need the database itself.
16
+
17
+ import { checkNames, createdNames, indexSchema } from './schema-check.js';
18
+
19
+ export { checkNames, createdNames } from './schema-check.js';
20
+
21
+ /** The server settings that change how SQL is read, at the engine's defaults. */
22
+ export const DEFAULT_SETTINGS = Object.freeze({
23
+ ansiQuotes: true,
24
+ noBackslashEscapes: true,
25
+ pipesAsConcat: true,
26
+ });
27
+
28
+ /** The settings to use: each one given (not undefined or null) as a boolean, the rest at the default. */
29
+ function settingsOf(given) {
30
+ const s = { ...DEFAULT_SETTINGS };
31
+ if (given) {
32
+ for (const key of Object.keys(DEFAULT_SETTINGS)) {
33
+ if (given[key] !== undefined && given[key] !== null) s[key] = Boolean(given[key]);
34
+ }
35
+ }
36
+ return s;
37
+ }
38
+
39
+ /** Thrown by the proc_exit stand-in: the module ended itself, and its instance is not to be used again. */
40
+ class ModuleExit extends Error {}
41
+
42
+ /**
43
+ * Compile a checker from the module's bytes.
44
+ *
45
+ * The module was built against WASI for the C library's sake, but it never
46
+ * reads or writes anything: the six system calls it imports get do-nothing
47
+ * stand-ins, so it runs anywhere WebAssembly does.
48
+ *
49
+ * @param {BufferSource | WebAssembly.Module} bytes the contents of
50
+ * coradb-sqlsyntaxchecker.wasm, or the module already compiled from them
51
+ * (then a new checker costs only its instance)
52
+ * @returns {Promise<import('./index').SqlChecker>}
53
+ */
54
+ export async function createSqlChecker(bytes) {
55
+ let memory = null;
56
+ const wasi = {
57
+ // Output is dropped, but reported as written in full: the C library
58
+ // retries a write until every byte is out, so "0 written" would loop.
59
+ fd_write: (_fd, iovs, n, written) => {
60
+ if (memory) {
61
+ const view = new DataView(memory.buffer);
62
+ let total = 0;
63
+ for (let i = 0; i < n; i += 1) total += view.getUint32(iovs + i * 8 + 4, true);
64
+ view.setUint32(written, total, true);
65
+ }
66
+ return 0;
67
+ },
68
+ fd_close: () => 0,
69
+ fd_fdstat_get: () => 0,
70
+ fd_read: () => 0,
71
+ fd_seek: () => 0,
72
+ proc_exit: (code) => {
73
+ throw new ModuleExit(`The SQL checker stopped (${code}).`);
74
+ },
75
+ };
76
+ const imports = { wasi_snapshot_preview1: wasi };
77
+ let module;
78
+ let instance;
79
+ if (bytes instanceof WebAssembly.Module) {
80
+ module = bytes;
81
+ instance = await WebAssembly.instantiate(module, imports);
82
+ } else {
83
+ ({ module, instance } = await WebAssembly.instantiate(bytes, imports));
84
+ }
85
+ let x;
86
+ let applied = '';
87
+ const builtins = new Map();
88
+ const start = (inst) => {
89
+ x = inst.exports;
90
+ memory = x.memory;
91
+ x._initialize();
92
+ applied = '';
93
+ builtins.clear();
94
+ };
95
+ start(instance);
96
+ /*
97
+ * A statement nested too deeply runs out of stack: the module's own (a
98
+ * WebAssembly trap) or the JavaScript engine's (a RangeError, since the
99
+ * module's calls use that too). Either way the instance is left as it was
100
+ * at that moment, its stack pointer included, so it is replaced with a new
101
+ * one from the same compiled module before the error is passed on: the
102
+ * next check starts clean.
103
+ */
104
+ const broken = (err) => err instanceof WebAssembly.RuntimeError || err instanceof RangeError || err instanceof ModuleExit;
105
+ const guarded = (fn) => {
106
+ try {
107
+ return fn();
108
+ } catch (err) {
109
+ if (broken(err)) {
110
+ start(new WebAssembly.Instance(module, imports));
111
+ const why = err instanceof ModuleExit ? '' : '; it may nest too deeply';
112
+ throw new Error(`The SQL checker could not check this statement (${err.message})${why}.`, { cause: err });
113
+ }
114
+ throw err;
115
+ }
116
+ };
117
+
118
+ const encoder = new TextEncoder();
119
+ const decoder = new TextDecoder();
120
+ const cString = (ptr) => {
121
+ const all = new Uint8Array(x.memory.buffer);
122
+ let end = ptr;
123
+ while (end < all.length && all[end] !== 0) end += 1;
124
+ return decoder.decode(all.subarray(ptr, end));
125
+ };
126
+ /** Parse once; for a statement accepted, the grammar's events too (character offsets). */
127
+ const parse = (sql, settings, withEvents) => guarded(() => parseOnce(sql, settings, withEvents));
128
+ const parseOnce = (sql, settings, withEvents) => {
129
+ if (typeof sql !== 'string') throw new TypeError('The SQL must be a string.');
130
+ const s = settingsOf(settings);
131
+ // The lexer would stop at a NUL and accept whatever came before it.
132
+ const nul = sql.indexOf('\0');
133
+ if (nul >= 0) {
134
+ return {
135
+ error: { ...lineColumn(sql, nul), offset: nul, length: 1, message: 'the text contains a NUL character (U+0000)', token: '' },
136
+ events: null,
137
+ };
138
+ }
139
+ const key = `${+s.ansiQuotes}${+s.noBackslashEscapes}${+s.pipesAsConcat}`;
140
+ if (key !== applied) {
141
+ x.check_set(+s.ansiQuotes, +s.noBackslashEscapes, +s.pipesAsConcat);
142
+ applied = key;
143
+ }
144
+ const bytesIn = encoder.encode(sql);
145
+ const ptr = x.check_alloc(bytesIn.length);
146
+ if (ptr === 0) throw new Error('The SQL checker is out of memory.');
147
+ let trapped = false;
148
+ try {
149
+ new Uint8Array(x.memory.buffer).set(bytesIn, ptr);
150
+ if (x.check_sql(ptr, bytesIn.length) !== 0) {
151
+ return {
152
+ error: locate(sql, bytesIn, !s.noBackslashEscapes, {
153
+ byteOffset: x.check_offset(),
154
+ message: cString(x.check_message()),
155
+ token: cString(x.check_token()),
156
+ }),
157
+ events: null,
158
+ };
159
+ }
160
+ if (!withEvents) return { error: null, events: null };
161
+ const n = x.check_event_count();
162
+ // Out of memory for them: the names cannot be checked, which is not "all is well".
163
+ if (n < 0) throw new Error('The SQL checker ran out of memory for this text: its names were not checked.');
164
+ const raw = new Int32Array(x.memory.buffer, x.check_events(), n * 3);
165
+ const toChar = byteToChar(sql, bytesIn);
166
+ const events = [];
167
+ for (let i = 0; i < n; i += 1) {
168
+ events.push({ kind: raw[i * 3], start: toChar(raw[i * 3 + 1]), end: toChar(raw[i * 3 + 2]) });
169
+ }
170
+ return { error: null, events };
171
+ } catch (err) {
172
+ trapped = broken(err);
173
+ throw err;
174
+ } finally {
175
+ // not after a trap: that instance is about to be replaced
176
+ if (!trapped) x.check_free(ptr);
177
+ }
178
+ };
179
+ const builtin = (name) => guarded(() => builtinOnce(name));
180
+ const builtinOnce = (name) => {
181
+ const key = String(name).toLowerCase();
182
+ if (builtins.has(key)) return builtins.get(key);
183
+ const b = encoder.encode(`${key}\0`);
184
+ const ptr = x.check_alloc(b.length);
185
+ if (ptr === 0) throw new Error('The SQL checker is out of memory.');
186
+ let r = -2;
187
+ let trapped = false;
188
+ try {
189
+ new Uint8Array(x.memory.buffer).set(b, ptr);
190
+ r = x.check_function(ptr);
191
+ } catch (err) {
192
+ trapped = broken(err);
193
+ throw err;
194
+ } finally {
195
+ // not after a trap: that instance is about to be replaced
196
+ if (!trapped) x.check_free(ptr);
197
+ }
198
+ builtins.set(key, r);
199
+ return r;
200
+ };
201
+
202
+ return {
203
+ check(sql, settings = DEFAULT_SETTINGS) {
204
+ return parse(sql, settings, false).error;
205
+ },
206
+
207
+ validate(sql, options) {
208
+ const { schema, ...settings } = options ?? {};
209
+ const { error, events } = parse(sql, settings, Boolean(schema));
210
+ if (error) {
211
+ return [{ kind: 'syntax', line: error.line, column: error.column, offset: error.offset, length: error.length, message: describeError(error) }];
212
+ }
213
+ if (!schema || !events) return [];
214
+ if (schema.format !== 'coradb-schema' || !Array.isArray(schema.objects)) {
215
+ throw new TypeError('validate() takes a CoraDB schema document as `schema` (format "coradb-schema").');
216
+ }
217
+ // One statement at a time, so a trigger's or a script's own rules apply
218
+ // to it alone. The outermost statements, in order; a statement inside
219
+ // another (a trigger's action) belongs to it.
220
+ const outer = [];
221
+ let reach = -1;
222
+ const stmts = events.filter((e) => e.kind === 1).sort((a, b) => a.start - b.start || b.end - a.end);
223
+ for (const st of stmts) {
224
+ if (st.start >= reach) {
225
+ outer.push({ start: st.start, end: st.end, events: [] });
226
+ reach = st.end;
227
+ }
228
+ }
229
+ // Each event to its statement, in the order the grammar reported them
230
+ // (checkNames reads some of them in pairs).
231
+ for (const e of events) {
232
+ let lo = 0;
233
+ let hi = outer.length - 1;
234
+ let at = -1;
235
+ while (lo <= hi) {
236
+ const mid = (lo + hi) >> 1;
237
+ if (outer[mid].start <= e.start) {
238
+ at = mid;
239
+ lo = mid + 1;
240
+ } else hi = mid - 1;
241
+ }
242
+ const st = at >= 0 ? outer[at] : null;
243
+ if (st && e.end <= st.end) st.events.push({ kind: e.kind, start: e.start - st.start, end: e.end - st.start });
244
+ }
245
+ const created = createdNames(outer.map((s) => sql.slice(s.start, s.end)));
246
+ const s = settingsOf(settings);
247
+ const ctx = { builtin, created, backslashEscapes: !s.noBackslashEscapes, index: indexSchema(schema) };
248
+ const problems = [];
249
+ for (const st of outer) {
250
+ for (const p of checkNames(sql.slice(st.start, st.end), st.events, schema, ctx)) {
251
+ const offset = st.start + p.start;
252
+ problems.push({ kind: 'name', ...lineColumn(sql, offset), offset, length: Math.max(1, p.end - p.start), message: p.message });
253
+ }
254
+ }
255
+ return problems;
256
+ },
257
+
258
+ builtin,
259
+ };
260
+ }
261
+
262
+ /** A byte offset into the UTF-8 form of `sql`, as a character offset. */
263
+ function byteToChar(sql, bytes) {
264
+ if (bytes.length === sql.length) return (b) => b;
265
+ const map = new Int32Array(bytes.length + 1);
266
+ let b = 0;
267
+ for (let i = 0; i < sql.length; ) {
268
+ const cp = sql.codePointAt(i) ?? 0;
269
+ const len = cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4;
270
+ for (let k = 0; k < len; k += 1) map[b + k] = i;
271
+ b += len;
272
+ i += cp > 0xffff ? 2 : 1;
273
+ }
274
+ map[bytes.length] = sql.length;
275
+ return (off) => map[Math.max(0, Math.min(off, bytes.length))];
276
+ }
277
+
278
+ let compiled = null;
279
+
280
+ /** The module next to this file, compiled once (retried after a failure). */
281
+ function defaultModule() {
282
+ if (!compiled) {
283
+ compiled = readBytes(new URL('./coradb-sqlsyntaxchecker.wasm', import.meta.url))
284
+ .then((bytes) => WebAssembly.compile(bytes))
285
+ .catch((err) => {
286
+ compiled = null; // the next call tries again
287
+ throw err;
288
+ });
289
+ }
290
+ return compiled;
291
+ }
292
+
293
+ let loaded = null;
294
+
295
+ /**
296
+ * The checker, loaded once.
297
+ *
298
+ * With no argument the module next to this file is used: read from disk in
299
+ * Node, fetched in the browser (bundlers such as Vite and webpack copy it
300
+ * along, since it is named with `new URL(…, import.meta.url)`). Pass a URL,
301
+ * a path, or the bytes to load it from somewhere else.
302
+ *
303
+ * @param {URL | string | BufferSource} [source]
304
+ */
305
+ export function loadSqlChecker(source) {
306
+ if (source !== undefined) return readBytes(source).then(createSqlChecker);
307
+ if (!loaded) {
308
+ loaded = defaultModule()
309
+ .then(createSqlChecker)
310
+ .catch((err) => {
311
+ loaded = null; // the next call tries again
312
+ throw err;
313
+ });
314
+ }
315
+ return loaded;
316
+ }
317
+
318
+ /**
319
+ * Check SQL in one call: one statement, or a script of them separated by
320
+ * semicolons (the first error stops it, as it does the engine). Each call has
321
+ * a fresh checker, made from the module compiled once, as README recommends.
322
+ *
323
+ * @param {string} sql
324
+ * @param {Partial<typeof DEFAULT_SETTINGS>} [settings]
325
+ */
326
+ export async function checkSql(sql, settings) {
327
+ return (await createSqlChecker(await defaultModule())).check(sql, settings);
328
+ }
329
+
330
+ /**
331
+ * Validate in one call: the syntax, and with `options.schema` the names too.
332
+ *
333
+ * @param {string} sql
334
+ * @param {Partial<typeof DEFAULT_SETTINGS> & { schema?: object }} [options]
335
+ */
336
+ export async function validateSql(sql, options) {
337
+ return (await createSqlChecker(await defaultModule())).validate(sql, options);
338
+ }
339
+
340
+ const inNode = typeof process !== 'undefined' && !!process.versions?.node;
341
+
342
+ /**
343
+ * Node's fs, reached so that browser bundlers do not try to pack it:
344
+ * process.getBuiltinModule where Node has it (22.3+), otherwise an import by
345
+ * a name only known at run time.
346
+ */
347
+ async function nodeFs() {
348
+ const builtin = globalThis.process?.getBuiltinModule?.('node:fs/promises');
349
+ if (builtin) return builtin;
350
+ const name = 'node:fs/promises';
351
+ return import(/* @vite-ignore */ /* webpackIgnore: true */ name);
352
+ }
353
+
354
+ /** A path or URL, as what to read it with: a file path in Node, a URL anywhere else. */
355
+ function where(source) {
356
+ if (source instanceof URL) return source;
357
+ const windowsPath = /^[a-z]:[\\/]/i.test(source);
358
+ if (!windowsPath && /^[a-z][a-z0-9+.-]*:/i.test(source)) return new URL(source);
359
+ if (inNode) return null; // a file path
360
+ return new URL(source, globalThis.location?.href);
361
+ }
362
+
363
+ async function readBytes(source) {
364
+ if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) return source;
365
+ const url = where(source);
366
+ if (url === null || url.protocol === 'file:') {
367
+ return (await nodeFs()).readFile(url ?? source);
368
+ }
369
+ const response = await fetch(url);
370
+ if (!response.ok) throw new Error(`The SQL checker could not be loaded from ${url} (HTTP ${response.status}).`);
371
+ return response.arrayBuffer();
372
+ }
373
+
374
+ // --- where the error is, and what to say about it -----------------------------
375
+
376
+ /** The character offset of a byte offset into the UTF-8 form of `text`. */
377
+ export function charOffset(text, byteOffset) {
378
+ let bytes = 0;
379
+ let i = 0;
380
+ while (i < text.length && bytes < byteOffset) {
381
+ const cp = text.codePointAt(i) ?? 0;
382
+ bytes += cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4;
383
+ i += cp > 0xffff ? 2 : 1;
384
+ }
385
+ return i;
386
+ }
387
+
388
+ /** 1-based line and column (in characters) of an offset. */
389
+ function lineColumn(text, offset) {
390
+ let line = 1;
391
+ let lineStart = 0;
392
+ for (let i = text.indexOf('\n'); i >= 0 && i < offset; i = text.indexOf('\n', i + 1)) {
393
+ line += 1;
394
+ lineStart = i + 1;
395
+ }
396
+ return { line, column: offset - lineStart + 1 };
397
+ }
398
+
399
+ /**
400
+ * Where a string or quoted name that is never closed opens: the last quote
401
+ * the text leaves open, outside comments. With backslash escapes on
402
+ * (no_backslash_escapes off), a backslash in a string escapes what follows.
403
+ */
404
+ function openQuote(text, backslashEscapes) {
405
+ const n = text.length;
406
+ for (let i = 0; i < n; ) {
407
+ const ch = text[i];
408
+ const next = text[i + 1];
409
+ if ((ch === '-' && next === '-') || (ch === '/' && next === '/')) {
410
+ const nl = text.indexOf('\n', i);
411
+ i = nl < 0 ? n : nl;
412
+ continue;
413
+ }
414
+ if (ch === '/' && next === '*') {
415
+ const close = text.indexOf('*/', i + 2);
416
+ i = close < 0 ? n : close + 2;
417
+ continue;
418
+ }
419
+ if (ch === "'" || ch === '"' || ch === '`' || ch === '[') {
420
+ const close = ch === '[' ? ']' : ch;
421
+ let j = i + 1;
422
+ let closed = false;
423
+ while (j < n) {
424
+ if (backslashEscapes && (ch === "'" || ch === '"') && text[j] === '\\') {
425
+ j += 2;
426
+ continue;
427
+ }
428
+ if (text[j] === close) {
429
+ if (close !== ']' && text[j + 1] === close) {
430
+ j += 2;
431
+ continue;
432
+ }
433
+ closed = true;
434
+ j += 1;
435
+ break;
436
+ }
437
+ j += 1;
438
+ }
439
+ if (!closed) return i;
440
+ i = j;
441
+ continue;
442
+ }
443
+ i += 1;
444
+ }
445
+ return -1;
446
+ }
447
+
448
+ /** Add where in the text the error is, in characters, and how much of it to mark. */
449
+ function locate(sql, bytes, backslashEscapes, raw) {
450
+ let offset = charOffset(sql, Math.min(raw.byteOffset, bytes.length));
451
+ let length = Math.max(1, raw.token.length);
452
+ if (/unterminated/i.test(raw.message)) {
453
+ // The lexer only finds out at the end of the text: mark from the quote
454
+ // that was left open to the end of its line.
455
+ const quote = openQuote(sql, backslashEscapes);
456
+ if (quote >= 0) offset = quote;
457
+ const nl = sql.indexOf('\n', offset);
458
+ length = (nl < 0 ? sql.length : nl) - offset;
459
+ } else if (raw.token === '' || offset >= sql.length) {
460
+ // At the end of the text: the last thing there is.
461
+ const end = sql.replace(/\s+$/, '').length;
462
+ offset = Math.max(0, end - 1);
463
+ length = end > 0 ? 1 : 0;
464
+ }
465
+ length = Math.max(0, Math.min(length, sql.length - offset));
466
+ return { ...lineColumn(sql, offset), offset, length, message: raw.message, token: raw.token };
467
+ }
468
+
469
+ const TOKEN_WORDS = {
470
+ IdName: 'a name',
471
+ 'end of file': 'the end of the statement',
472
+ 'end of input': 'the end of the statement',
473
+ UNSIGNED_INTEGER: 'a number',
474
+ UNSIGNED_REAL: 'a number',
475
+ CHAR_STRING: 'a string',
476
+ NCHAR_STRING: 'a string',
477
+ BIT_STRING: 'a bit string',
478
+ HEX_STRING: 'a hex string',
479
+ BINARY_STRING: 'a string',
480
+ EUCKR_STRING: 'a string',
481
+ ISO_STRING: 'a string',
482
+ UTF8_STRING: 'a string',
483
+ PARAM_HEADER: 'a parameter',
484
+ };
485
+
486
+ /** One of the grammar's token names as a person would say it (DELETE_ → DELETE, IdName → a name). */
487
+ export function tokenWords(name) {
488
+ if (TOKEN_WORDS[name]) return TOKEN_WORDS[name];
489
+ const quoted = /^'(.+)'$/.exec(name);
490
+ if (quoted) return quoted[1];
491
+ return name.replace(/_+$/, '').replace(/_/g, ' ');
492
+ }
493
+
494
+ /**
495
+ * The error in words: `syntax error, unexpected FROM, expecting ')'` becomes
496
+ * `Syntax error: unexpected "from". Expected ).`
497
+ *
498
+ * @param {import('./index').SqlSyntaxError} err
499
+ */
500
+ export function describeError(err) {
501
+ const m = err.message;
502
+ if (/NUL character/.test(m)) return 'The text contains a NUL character (U+0000), which SQL cannot hold.';
503
+ if (/unterminated string/i.test(m)) return 'This string is never closed.';
504
+ if (/unterminated identifier/i.test(m)) return 'This quoted name is never closed.';
505
+ const typed = err.token.trim();
506
+ // A rule the grammar matches only to refuse (see ruleError in
507
+ // build/generate.mjs): the engine's own words, at the word.
508
+ if (/^invalid /i.test(m) && typed !== '') return `Syntax error: unexpected "${typed}" (${m}).`;
509
+ const parsed = /unexpected ([^,]+?)(?:, expecting (.+))?$/.exec(m);
510
+ if (!parsed) return m.charAt(0).toUpperCase() + m.slice(1);
511
+ const unexpected =
512
+ typed !== ''
513
+ ? `"${typed.length > 40 ? `${typed.slice(0, 40)}…` : typed}"`
514
+ : /^end of (?:file|input)$/.test(parsed[1])
515
+ ? 'end of the statement'
516
+ : tokenWords(parsed[1]);
517
+ let text = `Syntax error: unexpected ${unexpected}.`;
518
+ // "{" (the ODBC escape the grammar also allows) is never what anybody
519
+ // meant, and the engine's internal tokens are nobody's to type.
520
+ const wanted = (parsed[2] ?? '')
521
+ .split(' or ')
522
+ .filter((t) => t !== '' && t !== "'{'" && t !== "'}'" && !/__/.test(t))
523
+ .map(tokenWords);
524
+ if (wanted.length > 0) {
525
+ const list = wanted.length === 1 ? wanted[0] : `${wanted.slice(0, -1).join(', ')} or ${wanted[wanted.length - 1]}`;
526
+ text += ` Expected ${list}.`;
527
+ }
528
+ return text;
529
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@coradb/coradb-sqlsyntaxchecker-wasm",
3
+ "version": "0.3.3",
4
+ "description": "Offline checks for CoraDB SQL: the engine's own grammar and lexer, compiled to WebAssembly, plus the names a statement uses checked against a schema document. Browser and Node, no server needed.",
5
+ "keywords": [
6
+ "coradb",
7
+ "sql",
8
+ "syntax",
9
+ "parser",
10
+ "validator",
11
+ "schema",
12
+ "wasm"
13
+ ],
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/apsisware/CoraDB-SQLSyntaxChecker-Wasm.git"
18
+ },
19
+ "homepage": "https://github.com/apsisware/CoraDB-SQLSyntaxChecker-Wasm#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/apsisware/CoraDB-SQLSyntaxChecker-Wasm/issues"
22
+ },
23
+ "type": "module",
24
+ "main": "./index.js",
25
+ "types": "./index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./index.d.ts",
29
+ "default": "./index.js"
30
+ },
31
+ "./coradb-sqlsyntaxchecker.wasm": "./coradb-sqlsyntaxchecker.wasm",
32
+ "./schema.json": "./schema.json",
33
+ "./build-info.json": "./build-info.json"
34
+ },
35
+ "files": [
36
+ "index.js",
37
+ "index.d.ts",
38
+ "schema-check.js",
39
+ "schema.json",
40
+ "tools/coradbSchema.ts",
41
+ "coradb-sqlsyntaxchecker.wasm",
42
+ "build-info.json",
43
+ "LICENSE",
44
+ "NOTICE",
45
+ "README.md",
46
+ "README-schema.md",
47
+ "CHANGELOG.md"
48
+ ],
49
+ "sideEffects": false,
50
+ "engines": {
51
+ "node": ">=18"
52
+ },
53
+ "scripts": {
54
+ "build": "node build/build.mjs",
55
+ "test": "node --test test/coradb-sqlsyntaxchecker.test.mjs"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ }
60
+ }