tranfi 0.0.2 → 0.1.2

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 (108) hide show
  1. package/README.md +395 -0
  2. package/app/assets/index-6quYZ5Ap.css +5 -0
  3. package/app/assets/index-pDFMluyz.js +160 -0
  4. package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  5. package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  6. package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  7. package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  8. package/app/index.html +13 -0
  9. package/binding.gyp +69 -0
  10. package/csrc/arena.c +91 -0
  11. package/csrc/batch.c +229 -0
  12. package/csrc/buffer.c +78 -0
  13. package/csrc/cJSON.c +3143 -0
  14. package/csrc/cJSON.h +300 -0
  15. package/csrc/codec_csv.c +1058 -0
  16. package/csrc/codec_jsonl.c +374 -0
  17. package/csrc/codec_table.c +218 -0
  18. package/csrc/codec_text.c +229 -0
  19. package/csrc/compiler.c +102 -0
  20. package/csrc/date_utils.h +94 -0
  21. package/csrc/dsl.c +1180 -0
  22. package/csrc/dsl.h +22 -0
  23. package/csrc/expr.c +1245 -0
  24. package/csrc/expr.h +56 -0
  25. package/csrc/internal.h +250 -0
  26. package/csrc/ir.c +119 -0
  27. package/csrc/ir.h +167 -0
  28. package/csrc/ir_schema.c +60 -0
  29. package/csrc/ir_serialize.c +104 -0
  30. package/csrc/ir_sql.c +1211 -0
  31. package/csrc/ir_validate.c +120 -0
  32. package/csrc/main.c +392 -0
  33. package/csrc/op_acf.c +133 -0
  34. package/csrc/op_anomaly.c +120 -0
  35. package/csrc/op_bin.c +109 -0
  36. package/csrc/op_cast.c +195 -0
  37. package/csrc/op_clip.c +88 -0
  38. package/csrc/op_date_trunc.c +181 -0
  39. package/csrc/op_datetime.c +212 -0
  40. package/csrc/op_derive.c +248 -0
  41. package/csrc/op_diff.c +134 -0
  42. package/csrc/op_ewma.c +103 -0
  43. package/csrc/op_explode.c +108 -0
  44. package/csrc/op_fill_down.c +163 -0
  45. package/csrc/op_fill_null.c +123 -0
  46. package/csrc/op_filter.c +132 -0
  47. package/csrc/op_frequency.c +193 -0
  48. package/csrc/op_grep.c +163 -0
  49. package/csrc/op_group_agg.c +285 -0
  50. package/csrc/op_hash.c +126 -0
  51. package/csrc/op_head.c +149 -0
  52. package/csrc/op_interpolate.c +239 -0
  53. package/csrc/op_join.c +384 -0
  54. package/csrc/op_label_encode.c +144 -0
  55. package/csrc/op_lead.c +190 -0
  56. package/csrc/op_normalize.c +226 -0
  57. package/csrc/op_onehot.c +185 -0
  58. package/csrc/op_pivot.c +370 -0
  59. package/csrc/op_registry.c +1148 -0
  60. package/csrc/op_rename.c +138 -0
  61. package/csrc/op_replace.c +202 -0
  62. package/csrc/op_sample.c +101 -0
  63. package/csrc/op_select.c +140 -0
  64. package/csrc/op_skip.c +152 -0
  65. package/csrc/op_sort.c +273 -0
  66. package/csrc/op_split.c +114 -0
  67. package/csrc/op_split_data.c +87 -0
  68. package/csrc/op_stack.c +315 -0
  69. package/csrc/op_stats.c +779 -0
  70. package/csrc/op_step.c +171 -0
  71. package/csrc/op_tail.c +96 -0
  72. package/csrc/op_top.c +150 -0
  73. package/csrc/op_trim.c +109 -0
  74. package/csrc/op_unique.c +300 -0
  75. package/csrc/op_unpivot.c +159 -0
  76. package/csrc/op_validate.c +71 -0
  77. package/csrc/op_window.c +150 -0
  78. package/csrc/pipeline.c +315 -0
  79. package/csrc/plan.c +206 -0
  80. package/csrc/recipes.c +102 -0
  81. package/csrc/recipes.h +27 -0
  82. package/csrc/report.c +463 -0
  83. package/csrc/report.h +22 -0
  84. package/csrc/tranfi.h +123 -0
  85. package/csrc/wasm_api.c +157 -0
  86. package/napi_api.c +326 -0
  87. package/package.json +46 -57
  88. package/src/cli.js +193 -0
  89. package/src/engines/duckdb.js +109 -0
  90. package/src/index.js +306 -0
  91. package/src/native.js +22 -0
  92. package/src/pipeline.js +286 -0
  93. package/src/server.js +277 -0
  94. package/src/wasm.js +19 -0
  95. package/wasm/index.js +244 -0
  96. package/wasm/package.json +1 -0
  97. package/wasm/tranfi_core.js +0 -0
  98. package/LICENSE +0 -21
  99. package/dist/bundle.js +0 -1
  100. package/index.html +0 -18
  101. package/src/app.css +0 -169
  102. package/src/app.js +0 -203
  103. package/src/app.vue +0 -250
  104. package/src/bulma-input.vue +0 -110
  105. package/src/common-inputs.js +0 -28
  106. package/src/main.js +0 -20
  107. package/src/transforms.js +0 -166
  108. package/webpack.config.js +0 -108
package/src/cli.js ADDED
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * cli.js — Tranfi CLI for Node.js.
5
+ *
6
+ * Usage:
7
+ * tranfi 'csv | filter "age > 25" | csv' < in.csv
8
+ * tranfi -f pipeline.tf < in.csv > out.csv
9
+ * tranfi profile < data.csv
10
+ */
11
+
12
+ import { pipeline, compileDsl, recipes } from './index.js'
13
+ import { readFileSync, writeFileSync, existsSync } from 'fs'
14
+ import { resolve, dirname } from 'path'
15
+ import { fileURLToPath } from 'url'
16
+ import nativeBinding from './native.js'
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url))
19
+
20
+ function usage() {
21
+ process.stderr.write(`Usage: tranfi [OPTIONS] PIPELINE
22
+ tranfi [OPTIONS] -f FILE
23
+
24
+ Streaming ETL with a pipe-style DSL.
25
+
26
+ Examples:
27
+ tranfi 'csv | csv' # passthrough
28
+ tranfi 'csv | filter "age > 25" | csv' # filter rows
29
+ tranfi 'csv | select name,age | csv' # select columns
30
+ tranfi 'csv | head 10 | csv' # first N rows
31
+ tranfi 'csv | sort age | csv' # sort by column
32
+ tranfi 'csv | stats | jsonl' # aggregate stats
33
+ tranfi profile # built-in recipe
34
+
35
+ Options:
36
+ -f FILE Read pipeline from file
37
+ -i FILE Read input from file instead of stdin
38
+ -o FILE Write output to file instead of stdout
39
+ -j Compile only, output JSON plan
40
+ -q Quiet mode (suppress stats)
41
+ -v Show version
42
+ -R List built-in recipes
43
+ -h Show this help
44
+ `)
45
+ }
46
+
47
+ function findAppDir () {
48
+ // Try known locations relative to this file
49
+ const candidates = [
50
+ resolve(__dirname, '../../app/dist'), // dev: js/src/ → ../../app/dist
51
+ resolve(__dirname, '../app'), // npm package: src/ → ../app/
52
+ ]
53
+ for (const dir of candidates) {
54
+ if (existsSync(resolve(dir, 'index.html'))) return dir
55
+ }
56
+ return null
57
+ }
58
+
59
+ async function serveCommand (argv) {
60
+ const { startServer } = await import('./server.js')
61
+ let dataDir = '.'
62
+ let appDir = null
63
+ let port = 3000
64
+
65
+ for (let i = 0; i < argv.length; i++) {
66
+ const arg = argv[i]
67
+ if (arg === '-d' || arg === '--data') dataDir = argv[++i]
68
+ else if (arg === '-a' || arg === '--app') appDir = argv[++i]
69
+ else if (arg === '-p' || arg === '--port') port = parseInt(argv[++i], 10)
70
+ else if (arg === '-h' || arg === '--help') {
71
+ process.stderr.write(`Usage: tranfi serve [OPTIONS]
72
+
73
+ Serve the tranfi app with a native backend.
74
+
75
+ Options:
76
+ -d, --data DIR Data directory (default: .)
77
+ -a, --app DIR App dist directory (auto-detect)
78
+ -p, --port PORT Port (default: 3000)
79
+ -h, --help Show this help
80
+ `)
81
+ process.exit(0)
82
+ }
83
+ }
84
+
85
+ if (!appDir) appDir = findAppDir()
86
+ if (!appDir) {
87
+ process.stderr.write('error: app directory not found. Use --app to specify it.\n')
88
+ process.exit(1)
89
+ }
90
+
91
+ startServer({ port, dataDir: resolve(dataDir), appDir: resolve(appDir) })
92
+ }
93
+
94
+ async function main() {
95
+ const argv = process.argv.slice(2)
96
+
97
+ // Check for serve subcommand
98
+ if (argv[0] === 'serve') {
99
+ return serveCommand(argv.slice(1))
100
+ }
101
+
102
+ let pipelineFile = null
103
+ let pipelineText = null
104
+ let inputFile = null
105
+ let outputFile = null
106
+ let jsonMode = false
107
+ let quiet = false
108
+
109
+ // Parse args
110
+ for (let i = 0; i < argv.length; i++) {
111
+ const arg = argv[i]
112
+ if (arg === '-h' || arg === '--help') { usage(); process.exit(0) }
113
+ else if (arg === '-v' || arg === '--version') {
114
+ const v = nativeBinding ? nativeBinding.version() : 'unknown'
115
+ process.stdout.write(`tranfi ${v}\n`)
116
+ process.exit(0)
117
+ }
118
+ else if (arg === '-R' || arg === '--recipes') {
119
+ const list = await recipes()
120
+ process.stdout.write(`Built-in recipes (${list.length}):\n\n`)
121
+ for (const r of list) {
122
+ process.stdout.write(` ${r.name.padEnd(12)} ${r.description}\n`)
123
+ process.stdout.write(` ${''.padEnd(12)} ${r.dsl}\n\n`)
124
+ }
125
+ process.exit(0)
126
+ }
127
+ else if (arg === '-j') jsonMode = true
128
+ else if (arg === '-q') quiet = true
129
+ else if (arg === '-f') { pipelineFile = argv[++i] }
130
+ else if (arg === '-i') { inputFile = argv[++i] }
131
+ else if (arg === '-o') { outputFile = argv[++i] }
132
+ else if (!arg.startsWith('-')) { pipelineText = arg }
133
+ else {
134
+ process.stderr.write(`error: unknown option '${arg}'\n`)
135
+ process.exit(1)
136
+ }
137
+ }
138
+
139
+ // Get pipeline text
140
+ if (pipelineFile) {
141
+ pipelineText = readFileSync(pipelineFile, 'utf-8')
142
+ }
143
+ if (!pipelineText) {
144
+ process.stderr.write('error: no pipeline specified\n\n')
145
+ usage()
146
+ process.exit(1)
147
+ }
148
+
149
+ // JSON mode
150
+ if (jsonMode) {
151
+ const json = await compileDsl(pipelineText)
152
+ process.stdout.write(json + '\n')
153
+ process.exit(0)
154
+ }
155
+
156
+ // Create and run pipeline
157
+ const p = pipeline(pipelineText)
158
+ const input = inputFile ? undefined : await readStdin()
159
+ const result = await p.run({
160
+ inputFile: inputFile || undefined,
161
+ input
162
+ })
163
+
164
+ // Write output
165
+ if (outputFile) {
166
+ writeFileSync(outputFile, result.output)
167
+ } else {
168
+ process.stdout.write(result.output)
169
+ }
170
+
171
+ // Errors to stderr
172
+ if (result.errors.length > 0) {
173
+ process.stderr.write(result.errors)
174
+ }
175
+
176
+ // Stats to stderr (unless quiet)
177
+ if (!quiet && result.stats.length > 0) {
178
+ process.stderr.write(result.stats)
179
+ }
180
+ }
181
+
182
+ function readStdin() {
183
+ return new Promise((resolve) => {
184
+ const chunks = []
185
+ process.stdin.on('data', chunk => chunks.push(chunk))
186
+ process.stdin.on('end', () => resolve(Buffer.concat(chunks)))
187
+ })
188
+ }
189
+
190
+ main().catch(err => {
191
+ process.stderr.write(`error: ${err.message}\n`)
192
+ process.exit(1)
193
+ })
@@ -0,0 +1,109 @@
1
+ /**
2
+ * duckdb.js — DuckDB engine for Node.js.
3
+ *
4
+ * Executes tranfi pipelines via SQL transpilation + duckdb.
5
+ * Install: npm install duckdb
6
+ */
7
+
8
+ import { writeFile, unlink } from 'fs/promises'
9
+ import { tmpdir } from 'os'
10
+ import { join } from 'path'
11
+ import { randomBytes } from 'crypto'
12
+ import { PipelineResult } from '../pipeline.js'
13
+
14
+ let _compileToSql = null
15
+
16
+ async function getCompileToSql() {
17
+ if (_compileToSql) return _compileToSql
18
+ const { compileToSql } = await import('../pipeline.js')
19
+ _compileToSql = compileToSql
20
+ return _compileToSql
21
+ }
22
+
23
+ class DuckDBEngine {
24
+ async run(dsl, { input, inputFile } = {}) {
25
+ let duckdb
26
+ try {
27
+ duckdb = (await import('duckdb')).default || await import('duckdb')
28
+ } catch {
29
+ throw new Error(
30
+ "DuckDB engine requires the 'duckdb' package. " +
31
+ 'Install it with: npm install duckdb'
32
+ )
33
+ }
34
+
35
+ const compileToSql = await getCompileToSql()
36
+ let sql = await compileToSql(dsl)
37
+
38
+ const db = new duckdb.Database(':memory:')
39
+ const conn = db.connect()
40
+
41
+ let tmpPath = null
42
+ try {
43
+ if (inputFile) {
44
+ sql = sql.replaceAll('input_data', `read_csv('${inputFile}')`)
45
+ } else if (input) {
46
+ const buf = typeof input === 'string' ? Buffer.from(input, 'utf-8') : input
47
+ tmpPath = join(tmpdir(), `tranfi_${randomBytes(8).toString('hex')}.csv`)
48
+ await writeFile(tmpPath, buf)
49
+ sql = sql.replaceAll('input_data', `read_csv('${tmpPath}')`)
50
+ } else {
51
+ throw new Error('Either input or inputFile must be provided')
52
+ }
53
+
54
+ const rows = await queryAll(conn, sql)
55
+ const output = rowsToCsv(rows)
56
+
57
+ return new PipelineResult(
58
+ Buffer.from(output, 'utf-8'),
59
+ Buffer.alloc(0),
60
+ Buffer.alloc(0),
61
+ Buffer.alloc(0)
62
+ )
63
+ } finally {
64
+ conn.close()
65
+ db.close()
66
+ if (tmpPath) {
67
+ await unlink(tmpPath).catch(() => {})
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ function queryAll(conn, sql) {
74
+ return new Promise((resolve, reject) => {
75
+ conn.all(sql, (err, rows) => {
76
+ if (err) reject(err)
77
+ else resolve(rows)
78
+ })
79
+ })
80
+ }
81
+
82
+ function rowsToCsv(rows) {
83
+ if (!rows || rows.length === 0) return ''
84
+ const cols = Object.keys(rows[0])
85
+ const lines = [cols.join(',')]
86
+ for (const row of rows) {
87
+ const vals = cols.map(c => {
88
+ const v = row[c]
89
+ if (v === null || v === undefined) return ''
90
+ const s = String(v)
91
+ if (s.includes(',') || s.includes('"') || s.includes('\n')) {
92
+ return '"' + s.replace(/"/g, '""') + '"'
93
+ }
94
+ return s
95
+ })
96
+ lines.push(vals.join(','))
97
+ }
98
+ return lines.join('\n') + '\n'
99
+ }
100
+
101
+ let _engine = null
102
+
103
+ export function getEngine(name) {
104
+ if (name === 'duckdb') {
105
+ if (!_engine) _engine = new DuckDBEngine()
106
+ return _engine
107
+ }
108
+ throw new Error(`Unknown engine: '${name}'. Available: 'native', 'duckdb'`)
109
+ }
package/src/index.js ADDED
@@ -0,0 +1,306 @@
1
+ /**
2
+ * tranfi — Streaming ETL language + runtime.
3
+ *
4
+ * Usage:
5
+ * import { pipeline, codec, ops, expr } from 'tranfi'
6
+ *
7
+ * const p = pipeline([
8
+ * codec.csv({ delimiter: ',' }),
9
+ * ops.filter(expr("col('age') > 25")),
10
+ * ops.select(['name', 'age']),
11
+ * codec.csvEncode(),
12
+ * ])
13
+ *
14
+ * const result = await p.run({ inputFile: './data.csv' })
15
+ * console.log(result.outputText)
16
+ *
17
+ * // Or use DSL string:
18
+ * const p2 = pipeline('csv | filter "col(\'age\') > 25" | csv')
19
+ * const result2 = await p2.run({ inputFile: './data.csv' })
20
+ */
21
+
22
+ import { Pipeline, PipelineResult, compileDsl, compileToSql, loadRecipe, saveRecipe, recipes } from './pipeline.js'
23
+
24
+ export { Pipeline, PipelineResult, compileDsl, compileToSql, loadRecipe, saveRecipe, recipes }
25
+
26
+ export function pipeline(steps, { engine } = {}) {
27
+ return new Pipeline(steps, { engine })
28
+ }
29
+
30
+ export function param(name, defaultValue) {
31
+ const result = { param: name }
32
+ if (defaultValue !== undefined) result.default = defaultValue
33
+ return result
34
+ }
35
+
36
+ export function expr(text) {
37
+ return text
38
+ }
39
+
40
+ export const codec = {
41
+ csv({ delimiter = ',', header = true, batchSize = 1024, repair = false } = {}) {
42
+ const args = {}
43
+ if (delimiter !== ',') args.delimiter = delimiter
44
+ if (!header) args.header = false
45
+ if (batchSize !== 1024) args.batch_size = batchSize
46
+ if (repair) args.repair = true
47
+ return { op: 'codec.csv.decode', args }
48
+ },
49
+
50
+ csvDecode(opts = {}) {
51
+ return codec.csv(opts)
52
+ },
53
+
54
+ csvEncode({ delimiter = ',' } = {}) {
55
+ const args = {}
56
+ if (delimiter !== ',') args.delimiter = delimiter
57
+ return { op: 'codec.csv.encode', args }
58
+ },
59
+
60
+ jsonl({ batchSize = 1024 } = {}) {
61
+ const args = {}
62
+ if (batchSize !== 1024) args.batch_size = batchSize
63
+ return { op: 'codec.jsonl.decode', args }
64
+ },
65
+
66
+ jsonlDecode(opts = {}) {
67
+ return codec.jsonl(opts)
68
+ },
69
+
70
+ jsonlEncode() {
71
+ return { op: 'codec.jsonl.encode', args: {} }
72
+ },
73
+
74
+ text({ batchSize = 1024 } = {}) {
75
+ const args = {}
76
+ if (batchSize !== 1024) args.batch_size = batchSize
77
+ return { op: 'codec.text.decode', args }
78
+ },
79
+
80
+ textDecode(opts = {}) {
81
+ return codec.text(opts)
82
+ },
83
+
84
+ textEncode() {
85
+ return { op: 'codec.text.encode', args: {} }
86
+ },
87
+
88
+ tableEncode({ maxWidth = 40, maxRows = 0 } = {}) {
89
+ const args = {}
90
+ if (maxWidth !== 40) args.max_width = maxWidth
91
+ if (maxRows !== 0) args.max_rows = maxRows
92
+ return { op: 'codec.table.encode', args }
93
+ }
94
+ }
95
+
96
+ export const ops = {
97
+ filter(expression) {
98
+ return { op: 'filter', args: { expr: expression } }
99
+ },
100
+
101
+ select(columns) {
102
+ return { op: 'select', args: { columns } }
103
+ },
104
+
105
+ rename(mapping) {
106
+ return { op: 'rename', args: { mapping } }
107
+ },
108
+
109
+ head(n) {
110
+ return { op: 'head', args: { n } }
111
+ },
112
+
113
+ skip(n) {
114
+ return { op: 'skip', args: { n } }
115
+ },
116
+
117
+ derive(columns) {
118
+ // columns: { name: expr, ... } or [{ name, expr }, ...]
119
+ const cols = Array.isArray(columns)
120
+ ? columns
121
+ : Object.entries(columns).map(([name, e]) => ({ name, expr: e }))
122
+ return { op: 'derive', args: { columns: cols } }
123
+ },
124
+
125
+ stats(statsList) {
126
+ const args = {}
127
+ if (statsList) args.stats = statsList
128
+ return { op: 'stats', args }
129
+ },
130
+
131
+ unique(columns) {
132
+ const args = {}
133
+ if (columns) args.columns = columns
134
+ return { op: 'unique', args }
135
+ },
136
+
137
+ sort(columns) {
138
+ // columns: ['age', '-name'] or [{ name, desc }, ...]
139
+ const cols = columns.map(c => {
140
+ if (typeof c === 'string') {
141
+ const desc = c.startsWith('-')
142
+ return { name: desc ? c.slice(1) : c, desc }
143
+ }
144
+ return c
145
+ })
146
+ return { op: 'sort', args: { columns: cols } }
147
+ },
148
+
149
+ tail(n) {
150
+ return { op: 'tail', args: { n } }
151
+ },
152
+
153
+ validate(expression) {
154
+ return { op: 'validate', args: { expr: expression } }
155
+ },
156
+
157
+ trim(columns) {
158
+ const args = {}
159
+ if (columns) args.columns = columns
160
+ return { op: 'trim', args }
161
+ },
162
+
163
+ fillNull(mapping) {
164
+ return { op: 'fill-null', args: { mapping } }
165
+ },
166
+
167
+ cast(mapping) {
168
+ return { op: 'cast', args: { mapping } }
169
+ },
170
+
171
+ clip(column, { min, max } = {}) {
172
+ const args = { column }
173
+ if (min !== undefined) args.min = min
174
+ if (max !== undefined) args.max = max
175
+ return { op: 'clip', args }
176
+ },
177
+
178
+ replace(column, pattern, replacement, { regex = false } = {}) {
179
+ const args = { column, pattern, replacement }
180
+ if (regex) args.regex = true
181
+ return { op: 'replace', args }
182
+ },
183
+
184
+ hash(columns) {
185
+ const args = {}
186
+ if (columns) args.columns = columns
187
+ return { op: 'hash', args }
188
+ },
189
+
190
+ bin(column, boundaries) {
191
+ return { op: 'bin', args: { column, boundaries } }
192
+ },
193
+
194
+ fillDown(columns) {
195
+ const args = {}
196
+ if (columns) args.columns = columns
197
+ return { op: 'fill-down', args }
198
+ },
199
+
200
+ step(column, func, result) {
201
+ const args = { column, func }
202
+ if (result) args.result = result
203
+ return { op: 'step', args }
204
+ },
205
+
206
+ window(column, size, func, result) {
207
+ const args = { column, size, func }
208
+ if (result) args.result = result
209
+ return { op: 'window', args }
210
+ },
211
+
212
+ explode(column, delimiter) {
213
+ const args = { column }
214
+ if (delimiter && delimiter !== ',') args.delimiter = delimiter
215
+ return { op: 'explode', args }
216
+ },
217
+
218
+ split(column, names, delimiter) {
219
+ const args = { column, names }
220
+ if (delimiter && delimiter !== ' ') args.delimiter = delimiter
221
+ return { op: 'split', args }
222
+ },
223
+
224
+ unpivot(columns) {
225
+ return { op: 'unpivot', args: { columns } }
226
+ },
227
+
228
+ top(n, column, desc = true) {
229
+ return { op: 'top', args: { n, column, desc } }
230
+ },
231
+
232
+ sample(n) {
233
+ return { op: 'sample', args: { n } }
234
+ },
235
+
236
+ groupAgg(groupBy, aggs) {
237
+ return { op: 'group-agg', args: { group_by: groupBy, aggs } }
238
+ },
239
+
240
+ frequency(columns) {
241
+ const args = {}
242
+ if (columns) args.columns = columns
243
+ return { op: 'frequency', args }
244
+ },
245
+
246
+ datetime(column, extract) {
247
+ const args = { column }
248
+ if (extract) args.extract = extract
249
+ return { op: 'datetime', args }
250
+ },
251
+
252
+ reorder(columns) {
253
+ return { op: 'reorder', args: { columns } }
254
+ },
255
+
256
+ dedup(columns) {
257
+ const args = {}
258
+ if (columns) args.columns = columns
259
+ return { op: 'dedup', args }
260
+ },
261
+
262
+ grep(pattern, { invert = false, column = '_line', regex = false } = {}) {
263
+ const args = { pattern }
264
+ if (invert) args.invert = true
265
+ if (column !== '_line') args.column = column
266
+ if (regex) args.regex = true
267
+ return { op: 'grep', args }
268
+ },
269
+
270
+ flatten() {
271
+ return { op: 'flatten', args: {} }
272
+ },
273
+
274
+ stack(file, { tag, tagValue } = {}) {
275
+ const args = { file }
276
+ if (tag) args.tag = tag
277
+ if (tagValue) args.tag_value = tagValue
278
+ return { op: 'stack', args }
279
+ },
280
+
281
+ lead(column, { offset = 1, result } = {}) {
282
+ const args = { column }
283
+ if (offset !== 1) args.offset = offset
284
+ if (result) args.result = result
285
+ return { op: 'lead', args }
286
+ },
287
+
288
+ dateTrunc(column, trunc, { result } = {}) {
289
+ const args = { column, trunc }
290
+ if (result) args.result = result
291
+ return { op: 'date-trunc', args }
292
+ }
293
+ }
294
+
295
+ export const io = {
296
+ read: {
297
+ file(path) {
298
+ return { op: 'io.read.file', args: { path } }
299
+ }
300
+ },
301
+ write: {
302
+ stdout() {
303
+ return { op: 'io.write.stdout', args: {} }
304
+ }
305
+ }
306
+ }
package/src/native.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * native.js — Load the N-API native addon.
3
+ * Returns the binding object or null if not available.
4
+ */
5
+
6
+ import { createRequire } from 'module'
7
+
8
+ let binding = null
9
+
10
+ try {
11
+ const require = createRequire(import.meta.url)
12
+ binding = require('../build/Release/tranfi_napi.node')
13
+ } catch {
14
+ try {
15
+ const require = createRequire(import.meta.url)
16
+ binding = require('../build/Debug/tranfi_napi.node')
17
+ } catch {
18
+ binding = null
19
+ }
20
+ }
21
+
22
+ export default binding