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.
- package/README.md +395 -0
- package/app/assets/index-6quYZ5Ap.css +5 -0
- package/app/assets/index-pDFMluyz.js +160 -0
- package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/app/index.html +13 -0
- package/binding.gyp +69 -0
- package/csrc/arena.c +91 -0
- package/csrc/batch.c +229 -0
- package/csrc/buffer.c +78 -0
- package/csrc/cJSON.c +3143 -0
- package/csrc/cJSON.h +300 -0
- package/csrc/codec_csv.c +1058 -0
- package/csrc/codec_jsonl.c +374 -0
- package/csrc/codec_table.c +218 -0
- package/csrc/codec_text.c +229 -0
- package/csrc/compiler.c +102 -0
- package/csrc/date_utils.h +94 -0
- package/csrc/dsl.c +1180 -0
- package/csrc/dsl.h +22 -0
- package/csrc/expr.c +1245 -0
- package/csrc/expr.h +56 -0
- package/csrc/internal.h +250 -0
- package/csrc/ir.c +119 -0
- package/csrc/ir.h +167 -0
- package/csrc/ir_schema.c +60 -0
- package/csrc/ir_serialize.c +104 -0
- package/csrc/ir_sql.c +1211 -0
- package/csrc/ir_validate.c +120 -0
- package/csrc/main.c +392 -0
- package/csrc/op_acf.c +133 -0
- package/csrc/op_anomaly.c +120 -0
- package/csrc/op_bin.c +109 -0
- package/csrc/op_cast.c +195 -0
- package/csrc/op_clip.c +88 -0
- package/csrc/op_date_trunc.c +181 -0
- package/csrc/op_datetime.c +212 -0
- package/csrc/op_derive.c +248 -0
- package/csrc/op_diff.c +134 -0
- package/csrc/op_ewma.c +103 -0
- package/csrc/op_explode.c +108 -0
- package/csrc/op_fill_down.c +163 -0
- package/csrc/op_fill_null.c +123 -0
- package/csrc/op_filter.c +132 -0
- package/csrc/op_frequency.c +193 -0
- package/csrc/op_grep.c +163 -0
- package/csrc/op_group_agg.c +285 -0
- package/csrc/op_hash.c +126 -0
- package/csrc/op_head.c +149 -0
- package/csrc/op_interpolate.c +239 -0
- package/csrc/op_join.c +384 -0
- package/csrc/op_label_encode.c +144 -0
- package/csrc/op_lead.c +190 -0
- package/csrc/op_normalize.c +226 -0
- package/csrc/op_onehot.c +185 -0
- package/csrc/op_pivot.c +370 -0
- package/csrc/op_registry.c +1148 -0
- package/csrc/op_rename.c +138 -0
- package/csrc/op_replace.c +202 -0
- package/csrc/op_sample.c +101 -0
- package/csrc/op_select.c +140 -0
- package/csrc/op_skip.c +152 -0
- package/csrc/op_sort.c +273 -0
- package/csrc/op_split.c +114 -0
- package/csrc/op_split_data.c +87 -0
- package/csrc/op_stack.c +315 -0
- package/csrc/op_stats.c +779 -0
- package/csrc/op_step.c +171 -0
- package/csrc/op_tail.c +96 -0
- package/csrc/op_top.c +150 -0
- package/csrc/op_trim.c +109 -0
- package/csrc/op_unique.c +300 -0
- package/csrc/op_unpivot.c +159 -0
- package/csrc/op_validate.c +71 -0
- package/csrc/op_window.c +150 -0
- package/csrc/pipeline.c +315 -0
- package/csrc/plan.c +206 -0
- package/csrc/recipes.c +102 -0
- package/csrc/recipes.h +27 -0
- package/csrc/report.c +463 -0
- package/csrc/report.h +22 -0
- package/csrc/tranfi.h +123 -0
- package/csrc/wasm_api.c +157 -0
- package/napi_api.c +326 -0
- package/package.json +46 -57
- package/src/cli.js +193 -0
- package/src/engines/duckdb.js +109 -0
- package/src/index.js +306 -0
- package/src/native.js +22 -0
- package/src/pipeline.js +286 -0
- package/src/server.js +277 -0
- package/src/wasm.js +19 -0
- package/wasm/index.js +244 -0
- package/wasm/package.json +1 -0
- package/wasm/tranfi_core.js +0 -0
- package/LICENSE +0 -21
- package/dist/bundle.js +0 -1
- package/index.html +0 -18
- package/src/app.css +0 -169
- package/src/app.js +0 -203
- package/src/app.vue +0 -250
- package/src/bulma-input.vue +0 -110
- package/src/common-inputs.js +0 -28
- package/src/main.js +0 -20
- package/src/transforms.js +0 -166
- 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
|