csvql-query 1.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 (3) hide show
  1. package/index.d.ts +180 -0
  2. package/index.js +382 -0
  3. package/package.json +44 -0
package/index.d.ts ADDED
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Options accepted by {@link query} and {@link queryCsv}.
3
+ */
4
+ export interface QueryOptions {
5
+ /**
6
+ * Source field delimiter when the file is not comma-separated.
7
+ * Use `'\t'` for TSV, `'|'` for pipe-delimited, `';'` for semicolon, etc.
8
+ * The conversion is quote-aware — quoted fields containing the delimiter
9
+ * are handled correctly.
10
+ *
11
+ * @example
12
+ * query("SELECT * FROM 'data.tsv'", { delimiter: '\t' })
13
+ */
14
+ delimiter?: string;
15
+
16
+ /**
17
+ * Skip lines whose content starts with this string before running the query.
18
+ * Useful for files that include shell-style comments or metadata headers.
19
+ *
20
+ * @example
21
+ * query("SELECT * FROM 'data.csv'", { comment: '#' })
22
+ */
23
+ comment?: string;
24
+
25
+ /**
26
+ * When `true`, blank and whitespace-only lines are stripped before querying.
27
+ * The engine already ignores truly empty lines; this option also removes
28
+ * lines that contain only spaces or tabs.
29
+ *
30
+ * @default false
31
+ */
32
+ skipEmptyLines?: boolean;
33
+ }
34
+
35
+ /**
36
+ * Run a SQL query against one or more CSV/TSV files and return results
37
+ * as an array of objects.
38
+ *
39
+ * The Zig/SIMD engine streams through the file without loading all rows into
40
+ * memory — RAM stays ~5 MB regardless of file size. Numeric values come back
41
+ * as JS numbers (via JSON.parse); everything else is a string.
42
+ *
43
+ * @param sql SQL query. File paths go inside FROM/JOIN clauses, e.g.
44
+ * `"SELECT * FROM 'data.csv' WHERE age > 30"`
45
+ * @param opts Optional pre-processing options (delimiter, comment, skipEmptyLines).
46
+ * @throws {Error} on invalid SQL, file not found, or engine error.
47
+ *
48
+ * @example
49
+ * // Basic query
50
+ * const rows = query("SELECT name, city FROM 'employees.csv' LIMIT 10")
51
+ *
52
+ * @example
53
+ * // Filter + aggregate
54
+ * const rows = query(
55
+ * "SELECT city, COUNT(*) as n, AVG(salary) as avg FROM 'employees.csv' GROUP BY city ORDER BY avg DESC"
56
+ * )
57
+ *
58
+ * @example
59
+ * // JOIN two files
60
+ * const rows = query(
61
+ * "SELECT e.name, d.name as dept FROM 'employees.csv' e JOIN 'departments.csv' d ON e.dept_id = d.id"
62
+ * )
63
+ *
64
+ * @example
65
+ * // TSV input
66
+ * const rows = query("SELECT * FROM 'scores.tsv' WHERE score > 90", { delimiter: '\t' })
67
+ *
68
+ * @example
69
+ * // Strip comment and blank lines
70
+ * const rows = query("SELECT * FROM 'data.csv'", { comment: '#', skipEmptyLines: true })
71
+ */
72
+ export function query(sql: string, opts?: QueryOptions): Record<string, unknown>[];
73
+
74
+ /**
75
+ * Run a SQL query and return results as a CSV string (header row + data rows).
76
+ *
77
+ * Useful for piping results into another CSV consumer or writing to a file
78
+ * without the overhead of JSON serialisation.
79
+ *
80
+ * @param sql SQL query.
81
+ * @param opts Optional pre-processing options — same as {@link query}.
82
+ * @returns CSV text, e.g. `"name,salary\nAlice,120000\nBob,80000\n"`
83
+ * @throws {Error} on invalid SQL, file not found, or engine error.
84
+ *
85
+ * @example
86
+ * const csv = queryCsv("SELECT city, COUNT(*) FROM 'data.csv' GROUP BY city")
87
+ * fs.writeFileSync('summary.csv', csv)
88
+ *
89
+ * @example
90
+ * // TSV → CSV
91
+ * const csv = queryCsv("SELECT name, score FROM 'results.tsv' ORDER BY score DESC", { delimiter: '\t' })
92
+ */
93
+ export function queryCsv(sql: string, opts?: QueryOptions): string;
94
+
95
+ /**
96
+ * Options for {@link find} — no SQL required.
97
+ */
98
+ export interface FindOptions {
99
+ /**
100
+ * Columns to return. Accepts a comma-separated string (`'name, city'`) or an array
101
+ * (`['name', 'city']`). Defaults to all columns when omitted.
102
+ */
103
+ columns?: string | string[];
104
+
105
+ /**
106
+ * Filter rows without writing SQL. Supported operators: `=` `!=` `>` `>=` `<` `<=`
107
+ * Combine conditions with `AND` / `OR`.
108
+ * String values are quoted automatically; numbers stay unquoted.
109
+ *
110
+ * @example 'salary>100000'
111
+ * @example 'department=Engineering AND salary>80000'
112
+ * @example 'city=Austin OR city=Boston'
113
+ */
114
+ where?: string;
115
+
116
+ /**
117
+ * Maximum number of rows to return. Omit or set to 0 for all rows.
118
+ */
119
+ limit?: number;
120
+
121
+ /**
122
+ * Sort column and direction. Format: `'column'` (asc by default) or `'column:desc'`.
123
+ *
124
+ * @example 'salary:desc'
125
+ * @example 'name:asc'
126
+ */
127
+ orderBy?: string;
128
+
129
+ /** Source field delimiter when the file is not comma-separated. E.g. `'\t'` for TSV. */
130
+ delimiter?: string;
131
+
132
+ /** Skip lines starting with this string. E.g. `'#'` for comment rows. */
133
+ comment?: string;
134
+
135
+ /** Strip blank / whitespace-only lines before querying. Default `false`. */
136
+ skipEmptyLines?: boolean;
137
+ }
138
+
139
+ /**
140
+ * Query a CSV file without writing SQL.
141
+ *
142
+ * Accepts simple filter, column, sort, and limit options. Internally translated
143
+ * to SQL and executed by the same SIMD engine as {@link query}.
144
+ *
145
+ * For aggregates (COUNT, SUM, AVG, GROUP BY, JOIN) use {@link query} instead.
146
+ *
147
+ * @param file Path to the CSV/TSV file.
148
+ * @param opts Filter, column, sort, limit, and pre-processing options.
149
+ * @returns Array of row objects.
150
+ * @throws {Error} on invalid condition, file not found, or engine error.
151
+ *
152
+ * @example
153
+ * // All rows
154
+ * find('employees.csv')
155
+ *
156
+ * @example
157
+ * // Pick columns + filter
158
+ * find('employees.csv', {
159
+ * columns: ['name', 'city', 'salary'],
160
+ * where: 'salary>100000',
161
+ * })
162
+ *
163
+ * @example
164
+ * // Filter + sort + limit
165
+ * find('employees.csv', {
166
+ * where: 'department=Engineering AND salary>80000',
167
+ * orderBy: 'salary:desc',
168
+ * limit: 10,
169
+ * })
170
+ *
171
+ * @example
172
+ * // TSV file
173
+ * find('scores.tsv', {
174
+ * columns: ['name', 'score'],
175
+ * where: 'score>=90',
176
+ * orderBy: 'score:desc',
177
+ * delimiter: '\t',
178
+ * })
179
+ */
180
+ export function find(file: string, opts?: FindOptions): Record<string, unknown>[];
package/index.js ADDED
@@ -0,0 +1,382 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * csvql — fast SQL queries on CSV files from Node.js
5
+ *
6
+ * Uses the csvql Zig/SIMD engine via N-API. The engine streams through the
7
+ * file without loading it into memory, so RAM usage stays ~5 MB regardless
8
+ * of file size.
9
+ *
10
+ * @example
11
+ * const csvql = require('csvql-query');
12
+ *
13
+ * // Basic query
14
+ * const rows = csvql.query("SELECT * FROM 'data.csv' WHERE salary > 100000");
15
+ *
16
+ * // TSV file
17
+ * const rows = csvql.query("SELECT * FROM 'data.tsv'", { delimiter: '\t' });
18
+ *
19
+ * // CSV with comment/blank rows
20
+ * const rows = csvql.query("SELECT * FROM 'data.csv'", { comment: '#', skipEmptyLines: true });
21
+ *
22
+ * // Return as CSV string
23
+ * const csv = csvql.queryCsv("SELECT city, COUNT(*) FROM 'data.csv' GROUP BY city");
24
+ */
25
+
26
+ const path = require('path');
27
+ const fs = require('fs');
28
+ const os = require('os');
29
+ const crypto = require('crypto');
30
+
31
+ function findNative() {
32
+ // 1. Bundled in same dir (npm run build copies it here, or local publish)
33
+ const bundled = path.join(__dirname, 'csvql.node');
34
+ if (fs.existsSync(bundled)) return bundled;
35
+
36
+ // 2. Platform-specific npm package (installed via optionalDependencies)
37
+ const pkg = `csvql-query-${process.platform}-${process.arch}`;
38
+ try {
39
+ const pkgDir = path.dirname(require.resolve(`${pkg}/package.json`));
40
+ const bin = path.join(pkgDir, 'csvql.node');
41
+ if (fs.existsSync(bin)) return bin;
42
+ } catch (_) {}
43
+
44
+ // 3. Dev build output (zig build node from repo root)
45
+ const dev = path.join(__dirname, '..', 'zig-out', 'lib', 'csvql.node');
46
+ if (fs.existsSync(dev)) return dev;
47
+
48
+ throw new Error(
49
+ `csvql native module not found for ${process.platform}-${process.arch}.\n` +
50
+ 'Install via npm: npm install csvql-query\n' +
51
+ 'Build from source: zig build node -Doptimize=ReleaseFast'
52
+ );
53
+ }
54
+
55
+ const native = require(findNative());
56
+
57
+ // ── Preprocessing (delimiter conversion + comment/blank stripping) ────────────
58
+
59
+ /**
60
+ * Quote-aware delimiter converter for a single line.
61
+ * Splits on `from` respecting double-quoted fields, re-joins with `to`,
62
+ * and re-quotes any output field that contains `to` or a double-quote.
63
+ *
64
+ * @param {string} line
65
+ * @param {string} from source delimiter (e.g. '\t')
66
+ * @param {string} to target delimiter (always ',')
67
+ * @returns {string}
68
+ */
69
+ function convertDelimiter(line, from, to) {
70
+ const fields = [];
71
+ let cur = '', inQuotes = false;
72
+ for (let i = 0; i < line.length; i++) {
73
+ const ch = line[i];
74
+ if (ch === '"') { inQuotes = !inQuotes; cur += ch; }
75
+ else if (!inQuotes && line.startsWith(from, i)) { fields.push(cur); cur = ''; i += from.length - 1; }
76
+ else { cur += ch; }
77
+ }
78
+ fields.push(cur);
79
+ return fields.map(f => {
80
+ if (f.includes(to) || f.includes('"') || f.includes('\n'))
81
+ return '"' + f.replace(/"/g, '""') + '"';
82
+ return f;
83
+ }).join(to);
84
+ }
85
+
86
+ /**
87
+ * Write a preprocessed copy of `filePath` to a temp file applying:
88
+ * opts.delimiter — convert this separator to comma
89
+ * opts.comment — skip lines starting with this string
90
+ * opts.skipEmptyLines — skip blank lines
91
+ *
92
+ * Returns the temp file path, or null when no preprocessing is needed.
93
+ * Caller is responsible for deleting the temp file.
94
+ *
95
+ * @param {string} filePath
96
+ * @param {QueryOptions} opts
97
+ * @returns {string|null}
98
+ */
99
+ function preprocess(filePath, opts) {
100
+ const needsDelim = opts.delimiter && opts.delimiter !== ',';
101
+ const needsComment = typeof opts.comment === 'string';
102
+ const needsSkip = opts.skipEmptyLines === true;
103
+
104
+ if (!needsDelim && !needsComment && !needsSkip) return null;
105
+
106
+ const tmp = path.join(os.tmpdir(), `_csvql_${crypto.randomBytes(6).toString('hex')}.csv`);
107
+ const out = [];
108
+
109
+ for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
110
+ if (needsComment && line.startsWith(opts.comment)) continue;
111
+ if (needsSkip && line.trim() === '') continue;
112
+ out.push(needsDelim ? convertDelimiter(line, opts.delimiter, ',') : line);
113
+ }
114
+
115
+ fs.writeFileSync(tmp, out.join('\n'));
116
+ return tmp;
117
+ }
118
+
119
+ /**
120
+ * Find all file paths referenced in a SQL string (FROM / JOIN clauses)
121
+ * that exist on disk.
122
+ *
123
+ * @param {string} sql
124
+ * @returns {{ match: string, filePath: string }[]}
125
+ */
126
+ function extractPaths(sql) {
127
+ const found = [];
128
+ const re = /['"]([^'"]+)['"]/g;
129
+ let m;
130
+ while ((m = re.exec(sql)) !== null)
131
+ if (fs.existsSync(m[1])) found.push({ match: m[0], filePath: m[1] });
132
+ return found;
133
+ }
134
+
135
+ /**
136
+ * Rewrite every file path in `sql` to a preprocessed temp file when needed.
137
+ * Handles JOIN queries with multiple file paths.
138
+ *
139
+ * @param {string} sql
140
+ * @param {QueryOptions} [opts]
141
+ * @returns {{ sql: string, temps: string[] }}
142
+ */
143
+ function maybePreprocess(sql, opts) {
144
+ if (!opts || !Object.keys(opts).length) return { sql, temps: [] };
145
+
146
+ const temps = [];
147
+ let out = sql;
148
+
149
+ for (const { match, filePath } of extractPaths(sql)) {
150
+ const tmp = preprocess(filePath, opts);
151
+ if (tmp) { out = out.replace(match, `'${tmp}'`); temps.push(tmp); }
152
+ }
153
+
154
+ return { sql: out, temps };
155
+ }
156
+
157
+ function cleanUp(temps) {
158
+ for (const f of temps) try { fs.unlinkSync(f); } catch (_) {}
159
+ }
160
+
161
+ // ── Public API ────────────────────────────────────────────────────────────────
162
+
163
+ /**
164
+ * @typedef {Object} QueryOptions
165
+ * @property {string} [delimiter] - Source field delimiter when not a comma.
166
+ * Use '\t' for TSV, '|' for pipe-delimited, etc.
167
+ * @property {string} [comment] - Skip lines whose first character(s) match this string.
168
+ * E.g. '#' to strip shell-style comments.
169
+ * @property {boolean} [skipEmptyLines] - Skip blank lines before querying. Default: false.
170
+ * The engine already ignores truly empty rows, but
171
+ * this also removes whitespace-only lines.
172
+ */
173
+
174
+ /**
175
+ * Run a SQL query against one or more CSV/TSV files and return results
176
+ * as an array of objects.
177
+ *
178
+ * The engine streams through the file without loading all rows into memory,
179
+ * so RAM stays ~5 MB for any file size. Values are typed where possible
180
+ * (numbers come back as JS numbers via JSON.parse).
181
+ *
182
+ * @param {string} sql SQL query. File paths go in FROM/JOIN clauses.
183
+ * E.g. "SELECT * FROM 'data.csv' WHERE age > 30"
184
+ * @param {QueryOptions} [opts]
185
+ * @returns {Record<string, unknown>[]}
186
+ * @throws {Error} on invalid SQL, file not found, or engine error
187
+ *
188
+ * @example
189
+ * // Basic select
190
+ * csvql.query("SELECT name, city FROM 'employees.csv' LIMIT 10")
191
+ *
192
+ * @example
193
+ * // WHERE filter
194
+ * csvql.query("SELECT name, salary FROM 'employees.csv' WHERE salary > 100000")
195
+ *
196
+ * @example
197
+ * // GROUP BY aggregate
198
+ * csvql.query("SELECT city, COUNT(*) as n, AVG(salary) as avg FROM 'employees.csv' GROUP BY city ORDER BY avg DESC")
199
+ *
200
+ * @example
201
+ * // JOIN two files
202
+ * csvql.query("SELECT e.name, d.name as dept FROM 'employees.csv' e JOIN 'departments.csv' d ON e.dept_id = d.id")
203
+ *
204
+ * @example
205
+ * // TSV file
206
+ * csvql.query("SELECT * FROM 'data.tsv' WHERE score > 90", { delimiter: '\t' })
207
+ *
208
+ * @example
209
+ * // Skip comment and blank lines
210
+ * csvql.query("SELECT * FROM 'data.csv'", { comment: '#', skipEmptyLines: true })
211
+ */
212
+ function query(sql, opts) {
213
+ const { sql: rewritten, temps } = maybePreprocess(sql, opts);
214
+ try {
215
+ return JSON.parse(native.queryJson(rewritten));
216
+ } finally {
217
+ cleanUp(temps);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Run a SQL query and return results as a CSV string (header row + data rows).
223
+ *
224
+ * Useful for piping results into another CSV consumer or writing to a file
225
+ * without the overhead of JSON serialisation.
226
+ *
227
+ * @param {string} sql
228
+ * @param {QueryOptions} [opts] Same options as {@link query}.
229
+ * @returns {string} CSV text, e.g. "name,salary\nAlice,120000\nBob,80000\n"
230
+ * @throws {Error} on invalid SQL, file not found, or engine error
231
+ *
232
+ * @example
233
+ * const csv = csvql.queryCsv("SELECT city, COUNT(*) FROM 'data.csv' GROUP BY city")
234
+ * fs.writeFileSync('output.csv', csv)
235
+ *
236
+ * @example
237
+ * // TSV input → CSV output
238
+ * const csv = csvql.queryCsv("SELECT name, score FROM 'results.tsv' ORDER BY score DESC", { delimiter: '\t' })
239
+ */
240
+ function queryCsv(sql, opts) {
241
+ const { sql: rewritten, temps } = maybePreprocess(sql, opts);
242
+ try {
243
+ return native.queryCsv(rewritten);
244
+ } finally {
245
+ cleanUp(temps);
246
+ }
247
+ }
248
+
249
+ // ── Simple (no-SQL) API ───────────────────────────────────────────────────────
250
+
251
+ /**
252
+ * Translate a simple condition like "salary>100000" or "name=Alice" into SQL.
253
+ * Numbers stay unquoted; anything that isn't a valid number gets single-quoted.
254
+ *
255
+ * @param {string} cond e.g. "salary>100000" or "city=Austin"
256
+ * @returns {string} e.g. "salary > 100000" or "city = 'Austin'"
257
+ */
258
+ function translateCondition(cond) {
259
+ // Match: column operator value (operators longest-first to avoid = matching >=)
260
+ const m = cond.trim().match(/^(\w+)(>=|<=|!=|>|<|=)(.+)$/);
261
+ if (!m) throw new Error(`Invalid where condition: "${cond}"`);
262
+ const [, col, op, raw] = m;
263
+ const val = raw.trim();
264
+ const quoted = isNaN(Number(val)) ? `'${val}'` : val;
265
+ return `${col} ${op} ${quoted}`;
266
+ }
267
+
268
+ /**
269
+ * Translate a simple where string (may contain AND / OR) into a SQL WHERE clause.
270
+ * e.g. "salary>100000 AND department=Engineering" → "salary > 100000 AND department = 'Engineering'"
271
+ *
272
+ * @param {string} where
273
+ * @returns {string}
274
+ */
275
+ function translateWhere(where) {
276
+ // Split on AND / OR keeping the keyword, translate each leaf condition.
277
+ return where
278
+ .split(/\b(AND|OR)\b/)
279
+ .map(part => {
280
+ const t = part.trim();
281
+ if (t === 'AND' || t === 'OR') return t;
282
+ return translateCondition(t);
283
+ })
284
+ .join(' ');
285
+ }
286
+
287
+ /**
288
+ * Translate a simple orderBy string into a SQL ORDER BY clause.
289
+ * e.g. "salary:desc" → "salary DESC" | "name" → "name ASC"
290
+ *
291
+ * @param {string} orderBy
292
+ * @returns {string}
293
+ */
294
+ function translateOrderBy(orderBy) {
295
+ const [col, dir = 'asc'] = orderBy.split(':');
296
+ return `${col.trim()} ${dir.trim().toUpperCase()}`;
297
+ }
298
+
299
+ /**
300
+ * Build a SQL string from simple find() options.
301
+ *
302
+ * @param {string} file
303
+ * @param {FindOptions} opts
304
+ * @returns {string}
305
+ */
306
+ function buildSql(file, opts) {
307
+ const cols = opts.columns
308
+ ? (Array.isArray(opts.columns) ? opts.columns.join(', ') : opts.columns)
309
+ : '*';
310
+
311
+ let sql = `SELECT ${cols} FROM '${file}'`;
312
+ if (opts.where) sql += ` WHERE ${translateWhere(opts.where)}`;
313
+ if (opts.orderBy) sql += ` ORDER BY ${translateOrderBy(opts.orderBy)}`;
314
+ if (opts.limit) sql += ` LIMIT ${opts.limit}`;
315
+ return sql;
316
+ }
317
+
318
+ /**
319
+ * @typedef {Object} FindOptions
320
+ * @property {string|string[]} [columns] - Columns to return. Comma-separated string or
321
+ * array. Default: all columns.
322
+ * @property {string} [where] - Filter condition in simple syntax — no SQL needed.
323
+ * Operators: = != > >= < <=
324
+ * Combine with AND / OR.
325
+ * Values are auto-quoted (numbers stay unquoted).
326
+ * E.g. 'salary>100000 AND department=Engineering'
327
+ * @property {number} [limit] - Maximum rows to return. Default: all rows.
328
+ * @property {string} [orderBy] - Sort column and direction: 'column:asc' or
329
+ * 'column:desc'. Direction defaults to asc.
330
+ * E.g. 'salary:desc'
331
+ * @property {string} [delimiter] - Source delimiter when not comma. E.g. '\t' for TSV.
332
+ * @property {string} [comment] - Skip lines starting with this string. E.g. '#'.
333
+ * @property {boolean} [skipEmptyLines] - Skip blank lines. Default false.
334
+ */
335
+
336
+ /**
337
+ * Query a CSV file without writing SQL.
338
+ *
339
+ * Uses the same SIMD engine as `query()` — the simple options are translated
340
+ * into a SQL string internally. No SQL knowledge required.
341
+ *
342
+ * For aggregates (COUNT, SUM, AVG, GROUP BY) use `query()` instead.
343
+ *
344
+ * @param {string} file Path to the CSV/TSV file.
345
+ * @param {FindOptions} [opts]
346
+ * @returns {Record<string, unknown>[]}
347
+ * @throws {Error} on invalid condition, file not found, or engine error
348
+ *
349
+ * @example
350
+ * // All rows
351
+ * csvql.find('employees.csv')
352
+ *
353
+ * @example
354
+ * // Pick columns + filter
355
+ * csvql.find('employees.csv', {
356
+ * columns: ['name', 'city', 'salary'],
357
+ * where: 'salary>100000',
358
+ * })
359
+ *
360
+ * @example
361
+ * // Filter + sort + limit
362
+ * csvql.find('employees.csv', {
363
+ * where: 'department=Engineering AND salary>80000',
364
+ * orderBy: 'salary:desc',
365
+ * limit: 10,
366
+ * })
367
+ *
368
+ * @example
369
+ * // TSV file, no SQL needed
370
+ * csvql.find('scores.tsv', {
371
+ * columns: ['name', 'score'],
372
+ * where: 'score>=90',
373
+ * orderBy: 'score:desc',
374
+ * delimiter: '\t',
375
+ * })
376
+ */
377
+ function find(file, opts = {}) {
378
+ const sql = buildSql(file, opts);
379
+ return query(sql, opts);
380
+ }
381
+
382
+ module.exports = { query, queryCsv, find };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "csvql-query",
3
+ "version": "1.0.0",
4
+ "description": "Fast SQL queries on CSV files — Zig/SIMD engine via N-API",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "index.js",
9
+ "index.d.ts"
10
+ ],
11
+ "scripts": {
12
+ "build": "zig build node -Doptimize=ReleaseFast && cp ../zig-out/lib/csvql.node .",
13
+ "test": "node test.js",
14
+ "bench": "node bench.js"
15
+ },
16
+ "keywords": [
17
+ "csv",
18
+ "sql",
19
+ "query",
20
+ "simd",
21
+ "fast",
22
+ "etl",
23
+ "parser"
24
+ ],
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/melihbirim/csvql"
29
+ },
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "optionalDependencies": {
34
+ "csvql-query-darwin-arm64": "1.0.0",
35
+ "csvql-query-darwin-x64": "1.0.0",
36
+ "csvql-query-linux-x64": "1.0.0",
37
+ "csvql-query-linux-arm64": "1.0.0",
38
+ "csvql-query-win32-x64": "1.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "csv-parse": "^7.0.0",
42
+ "papaparse": "^5.5.4"
43
+ }
44
+ }