tranfi 0.0.2 → 0.2.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 (151) hide show
  1. package/LICENSE +177 -21
  2. package/NOTICE +8 -0
  3. package/README.md +627 -0
  4. package/app/assets/index-6quYZ5Ap.css +5 -0
  5. package/app/assets/index-BIAIKnrp.js +160 -0
  6. package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  7. package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  8. package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  9. package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  10. package/app/index.html +13 -0
  11. package/binding.gyp +121 -0
  12. package/csrc/arena.c +93 -0
  13. package/csrc/batch.c +976 -0
  14. package/csrc/buffer.c +154 -0
  15. package/csrc/cJSON.c +3386 -0
  16. package/csrc/cJSON.h +316 -0
  17. package/csrc/codec_csv.c +1951 -0
  18. package/csrc/codec_jsonl.c +1086 -0
  19. package/csrc/codec_table.c +248 -0
  20. package/csrc/codec_text.c +447 -0
  21. package/csrc/compiler.c +130 -0
  22. package/csrc/config.h +21 -0
  23. package/csrc/date_utils.h +94 -0
  24. package/csrc/dsl.c +5417 -0
  25. package/csrc/dsl.h +22 -0
  26. package/csrc/expr.c +1553 -0
  27. package/csrc/expr.h +58 -0
  28. package/csrc/internal.h +539 -0
  29. package/csrc/ir.c +166 -0
  30. package/csrc/ir.h +208 -0
  31. package/csrc/ir_schema.c +75 -0
  32. package/csrc/ir_serialize.c +166 -0
  33. package/csrc/ir_sql.c +1822 -0
  34. package/csrc/ir_validate.c +576 -0
  35. package/csrc/json_path.c +210 -0
  36. package/csrc/main.c +1241 -0
  37. package/csrc/memory_estimate.c +477 -0
  38. package/csrc/op_acf.c +283 -0
  39. package/csrc/op_across.c +477 -0
  40. package/csrc/op_anomaly.c +255 -0
  41. package/csrc/op_assert.c +761 -0
  42. package/csrc/op_bin.c +248 -0
  43. package/csrc/op_cast.c +523 -0
  44. package/csrc/op_clip.c +99 -0
  45. package/csrc/op_date_trunc.c +355 -0
  46. package/csrc/op_datetime.c +394 -0
  47. package/csrc/op_derive.c +216 -0
  48. package/csrc/op_diff.c +250 -0
  49. package/csrc/op_ewma.c +222 -0
  50. package/csrc/op_explode.c +206 -0
  51. package/csrc/op_fill_down.c +235 -0
  52. package/csrc/op_fill_null.c +268 -0
  53. package/csrc/op_filter.c +181 -0
  54. package/csrc/op_frequency.c +721 -0
  55. package/csrc/op_grep.c +181 -0
  56. package/csrc/op_group_agg.c +1956 -0
  57. package/csrc/op_hash.c +159 -0
  58. package/csrc/op_head.c +84 -0
  59. package/csrc/op_interpolate.c +445 -0
  60. package/csrc/op_join.c +2902 -0
  61. package/csrc/op_json_extract.c +227 -0
  62. package/csrc/op_json_filter.c +384 -0
  63. package/csrc/op_json_flatten.c +293 -0
  64. package/csrc/op_json_schema.c +503 -0
  65. package/csrc/op_label_encode.c +419 -0
  66. package/csrc/op_lag.c +181 -0
  67. package/csrc/op_lead.c +242 -0
  68. package/csrc/op_normalize.c +510 -0
  69. package/csrc/op_onehot.c +457 -0
  70. package/csrc/op_pivot.c +1754 -0
  71. package/csrc/op_quarantine.c +189 -0
  72. package/csrc/op_registry.c +3044 -0
  73. package/csrc/op_rename.c +129 -0
  74. package/csrc/op_replace.c +354 -0
  75. package/csrc/op_rleid.c +297 -0
  76. package/csrc/op_rowid.c +559 -0
  77. package/csrc/op_sample.c +158 -0
  78. package/csrc/op_schema.c +1341 -0
  79. package/csrc/op_schema_infer.c +252 -0
  80. package/csrc/op_select.c +340 -0
  81. package/csrc/op_set.c +3449 -0
  82. package/csrc/op_skip.c +95 -0
  83. package/csrc/op_sort.c +819 -0
  84. package/csrc/op_source_name.c +120 -0
  85. package/csrc/op_split.c +151 -0
  86. package/csrc/op_split_data.c +119 -0
  87. package/csrc/op_stack.c +271 -0
  88. package/csrc/op_stats.c +875 -0
  89. package/csrc/op_step.c +333 -0
  90. package/csrc/op_tail.c +105 -0
  91. package/csrc/op_tee.c +338 -0
  92. package/csrc/op_top.c +357 -0
  93. package/csrc/op_trim.c +138 -0
  94. package/csrc/op_unique.c +1343 -0
  95. package/csrc/op_unpivot.c +193 -0
  96. package/csrc/op_validate.c +648 -0
  97. package/csrc/op_window.c +591 -0
  98. package/csrc/path_policy.c +85 -0
  99. package/csrc/pipeline.c +1088 -0
  100. package/csrc/recipes.c +104 -0
  101. package/csrc/recipes.h +27 -0
  102. package/csrc/report.c +506 -0
  103. package/csrc/report.h +22 -0
  104. package/csrc/selector.c +1097 -0
  105. package/csrc/size_utils.c +348 -0
  106. package/csrc/spill.c +317 -0
  107. package/csrc/spill.h +21 -0
  108. package/csrc/tranfi.h +291 -0
  109. package/csrc/transform.h +209 -0
  110. package/csrc/transform_api.c +2237 -0
  111. package/csrc/transform_categorical.c +923 -0
  112. package/csrc/transform_internal.h +472 -0
  113. package/csrc/transform_json.c +3812 -0
  114. package/csrc/transform_numeric.c +1966 -0
  115. package/csrc/transform_sha256.c +154 -0
  116. package/csrc/transform_wasm.h +162 -0
  117. package/csrc/transform_wasm_api.c +1373 -0
  118. package/csrc/wasm_api.c +218 -0
  119. package/napi_api.c +534 -0
  120. package/napi_transform.c +1648 -0
  121. package/napi_transform.h +8 -0
  122. package/package.json +64 -59
  123. package/scripts/install-native.js +76 -0
  124. package/scripts/prepack.js +64 -0
  125. package/scripts/sync-csrc.js +23 -0
  126. package/src/cli.js +190 -0
  127. package/src/engines/duckdb.js +142 -0
  128. package/src/index.js +925 -0
  129. package/src/memory_policy.js +411 -0
  130. package/src/native.js +18 -0
  131. package/src/pipeline.js +709 -0
  132. package/src/recipe_json.js +80 -0
  133. package/src/server.js +279 -0
  134. package/src/transform.js +403 -0
  135. package/src/transform_error.js +10 -0
  136. package/src/wasm.js +21 -0
  137. package/wasm/index.js +732 -0
  138. package/wasm/package.json +1 -0
  139. package/wasm/tranfi_core.js +0 -0
  140. package/wasm/transform.js +1156 -0
  141. package/wasm/worker.js +786 -0
  142. package/dist/bundle.js +0 -1
  143. package/index.html +0 -18
  144. package/src/app.css +0 -169
  145. package/src/app.js +0 -203
  146. package/src/app.vue +0 -250
  147. package/src/bulma-input.vue +0 -110
  148. package/src/common-inputs.js +0 -28
  149. package/src/main.js +0 -20
  150. package/src/transforms.js +0 -166
  151. package/webpack.config.js +0 -108
package/wasm/index.js ADDED
@@ -0,0 +1,732 @@
1
+
2
+ var createTransformAPI = require('./transform.js')
3
+
4
+ var BLOCKING_OPS = {
5
+ 'sort': true,
6
+ 'pivot': true,
7
+ 'interpolate': true,
8
+ 'normalize': true,
9
+ 'acf': true,
10
+ 'codec.table.encode': true
11
+ }
12
+
13
+ var KEY_STATE_OPS = {
14
+ 'rowid': true,
15
+ 'unique': true,
16
+ 'dedup': true,
17
+ 'group-agg': true,
18
+ 'frequency': true,
19
+ 'join': true,
20
+ 'semi-join': true,
21
+ 'anti-join': true,
22
+ 'onehot': true,
23
+ 'label-encode': true,
24
+ 'intersect': true,
25
+ 'setdiff': true,
26
+ 'union': true
27
+ }
28
+
29
+ var SIZE_UNITS = {
30
+ '': 1,
31
+ b: 1,
32
+ k: 1024,
33
+ kb: 1024,
34
+ kib: 1024,
35
+ m: 1024 * 1024,
36
+ mb: 1024 * 1024,
37
+ mib: 1024 * 1024,
38
+ g: 1024 * 1024 * 1024,
39
+ gb: 1024 * 1024 * 1024,
40
+ gib: 1024 * 1024 * 1024
41
+ }
42
+
43
+ var UNIQUE_DEFAULT_BLOOM_BYTES = 1024 * 1024
44
+
45
+ function normalizeSqlDialect(options) {
46
+ var dialect = 'duckdb'
47
+ if (typeof options === 'string') {
48
+ dialect = options
49
+ } else if (options && typeof options === 'object') {
50
+ dialect = options.dialect || options.sqlDialect || dialect
51
+ } else if (options !== undefined && options !== null) {
52
+ throw new TypeError('compileToSql options must be an object or dialect string')
53
+ }
54
+ if (typeof dialect !== 'string') throw new TypeError('SQL dialect must be a string')
55
+ var name = dialect.toLowerCase()
56
+ if (name === 'duckdb') return name
57
+ if (name === 'sqlite' || name === 'postgres') {
58
+ throw new Error("SQL dialect '" + name + "' is recognized but not implemented yet; only duckdb lowering is available")
59
+ }
60
+ throw new Error("unknown SQL dialect '" + dialect + "' (expected duckdb, sqlite, or postgres)")
61
+ }
62
+
63
+ function parseMemorySize(value) {
64
+ if (value === undefined || value === null) return null
65
+ if (typeof value === 'number') {
66
+ if (!isFinite(value) || value <= 0 || Math.floor(value) !== value) throw new Error('memory must be a positive integer byte count')
67
+ return value
68
+ }
69
+ if (typeof value !== 'string') throw new TypeError('memory must be a positive byte count or size string')
70
+ var text = value.trim()
71
+ var lower = text.toLowerCase()
72
+ if (lower.indexOf('max:') === 0 || lower.indexOf('max=') === 0) text = text.slice(4).trim()
73
+ var match = /^(\d+)\s*([a-zA-Z]*)$/.exec(text)
74
+ if (!match) throw new Error('memory must look like 64MB or max:64MB')
75
+ var amount = Number(match[1])
76
+ var suffix = match[2].toLowerCase()
77
+ if (!SIZE_UNITS.hasOwnProperty(suffix)) throw new Error("unsupported memory size suffix '" + suffix + "'")
78
+ return amount * SIZE_UNITS[suffix]
79
+ }
80
+
81
+ function formatBytes(n) {
82
+ if (n < 1024) return String(n) + 'B'
83
+ if (n < 1024 * 1024) return (n / 1024).toFixed(1) + 'KB'
84
+ if (n < 1024 * 1024 * 1024) return (n / (1024 * 1024)).toFixed(1) + 'MB'
85
+ return (n / (1024 * 1024 * 1024)).toFixed(1) + 'GB'
86
+ }
87
+
88
+ function memoryClass(step) {
89
+ if (typeof step.memory_class === 'string') return step.memory_class
90
+ if (BLOCKING_OPS[step.op]) return 'blocking'
91
+ if (KEY_STATE_OPS[step.op]) return 'key_state'
92
+ return null
93
+ }
94
+
95
+ function argsOf(step) {
96
+ return step && step.args && typeof step.args === 'object' ? step.args : {}
97
+ }
98
+
99
+ function positiveInt(value) {
100
+ return typeof value === 'number' && isFinite(value) && value > 0 && Math.floor(value) === value ? value : null
101
+ }
102
+
103
+ function arrayLen(value) {
104
+ return Array.isArray(value) ? value.length : 0
105
+ }
106
+
107
+ function stringArrayBytes(value) {
108
+ if (!Array.isArray(value)) return 0
109
+ var enc = new TextEncoder()
110
+ var total = 0
111
+ for (var i = 0; i < value.length; i++) {
112
+ if (typeof value[i] === 'string') total += enc.encode(value[i]).length + 1
113
+ }
114
+ return total
115
+ }
116
+
117
+ function categoryCap(step) {
118
+ var args = argsOf(step)
119
+ var maxCategories = positiveInt(args.max_categories)
120
+ var nCategories = arrayLen(args.categories)
121
+ var literalBytes = stringArrayBytes(args.categories)
122
+ if (args.unknown === 'other') {
123
+ nCategories += 1
124
+ literalBytes += 'other'.length + 1
125
+ }
126
+ if (maxCategories !== null) return [maxCategories, literalBytes]
127
+ if (nCategories > 0) return [nCategories, literalBytes]
128
+ return [null, literalBytes]
129
+ }
130
+
131
+ function estimateKeyStateStepBytes(step) {
132
+ var args = argsOf(step)
133
+ var op = step.op
134
+ if (op === 'rowid') {
135
+ var nCols = arrayLen(args.columns)
136
+ if (nCols === 0 || args.sorted === true) return 2048 + nCols * 128
137
+ var maxStateBytes = positiveInt(args.max_state_bytes)
138
+ if (maxStateBytes !== null) return maxStateBytes
139
+ var maxKeys = positiveInt(args.max_keys)
140
+ if (maxKeys === null) throw new Error("step 'rowid' needs max_keys, max_state_bytes, or sorted=true")
141
+ return 1024 + maxKeys * 224 + nCols * 128
142
+ }
143
+ if (op === 'unique' || op === 'dedup') {
144
+ var ukNCols = arrayLen(args.columns)
145
+ if (args.sorted === true) return 2048 + ukNCols * 128
146
+ if (args.approx === true || args.mode === 'approx') {
147
+ var ukBloomBytes = positiveInt(args.bloom_bytes)
148
+ return ukBloomBytes !== null ? ukBloomBytes : UNIQUE_DEFAULT_BLOOM_BYTES
149
+ }
150
+ var ukMaxStateBytes = positiveInt(args.max_state_bytes)
151
+ if (ukMaxStateBytes !== null) return ukMaxStateBytes
152
+ var ukMaxKeys = positiveInt(args.max_keys)
153
+ if (ukMaxKeys === null) throw new Error("step '" + op + "' needs max_keys, max_state_bytes, or sorted=true for byte-bounded native execution")
154
+ return 1024 + ukMaxKeys * (256 + ukNCols * 32)
155
+ }
156
+ if (op === 'group-agg') {
157
+ var nGroup = arrayLen(args.group_by)
158
+ var nAggs = arrayLen(args.aggs)
159
+ var maxStateBytes = positiveInt(args.max_state_bytes)
160
+ if (maxStateBytes !== null) return maxStateBytes
161
+ if (args.sorted === true) return 4096 + nGroup * 128 + nAggs * 128
162
+ var maxGroups = positiveInt(args.max_groups)
163
+ if (maxGroups === null) throw new Error("step 'group-agg' needs max_groups, max_state_bytes, or sorted=true for byte-bounded native execution")
164
+ return 2048 + maxGroups * (384 + nGroup * 64 + nAggs * 96)
165
+ }
166
+ if (op === 'frequency') {
167
+ var maxStateBytes = positiveInt(args.max_state_bytes)
168
+ if (maxStateBytes !== null) return maxStateBytes
169
+ var maxValues = positiveInt(args.max_values)
170
+ if (maxValues === null) throw new Error("step 'frequency' needs max_values or max_state_bytes for byte-bounded native execution")
171
+ var cappedValues = args.overflow === 'other' ? maxValues + 1 : maxValues
172
+ return 1024 + cappedValues * (224 + arrayLen(args.columns) * 32)
173
+ }
174
+ if (op === 'onehot' || op === 'label-encode') {
175
+ var catMaxStateBytes = positiveInt(args.max_state_bytes)
176
+ if (catMaxStateBytes !== null) return catMaxStateBytes
177
+ var cap = categoryCap(step)
178
+ if (cap[0] === null) throw new Error("step '" + op + "' needs max_categories, max_state_bytes, or declared categories")
179
+ return 1024 + cap[1] + cap[0] * (op === 'onehot' ? 320 : 224)
180
+ }
181
+ if (op === 'intersect' || op === 'setdiff') {
182
+ var setNCols = arrayLen(args.columns)
183
+ if (args.sorted === true) return 4096 + setNCols * 256
184
+ var setMaxLookupBytes = positiveInt(args.max_lookup_bytes)
185
+ if (setMaxLookupBytes === null) throw new Error("step '" + op + "' needs max_lookup_bytes or sorted=true for byte-bounded native execution")
186
+ var setMaxStateBytes = positiveInt(args.max_state_bytes)
187
+ if (setMaxStateBytes !== null) return 4096 + setMaxLookupBytes * 3 + setMaxStateBytes
188
+ var setMaxOutputKeys = positiveInt(args.max_output_keys)
189
+ if (setMaxOutputKeys === null) throw new Error("step '" + op + "' needs max_output_keys or max_state_bytes for duplicate-eliminating set semantics")
190
+ var setEst = 4096 + setMaxLookupBytes * 3
191
+ var setMaxLookupKeys = positiveInt(args.max_lookup_keys)
192
+ if (setMaxLookupKeys !== null) setEst += setMaxLookupKeys * (192 + setNCols * 32)
193
+ setEst += setMaxOutputKeys * (192 + setNCols * 32)
194
+ return setEst
195
+ }
196
+ if (op === 'union') {
197
+ var unionMaxStateBytes = positiveInt(args.max_state_bytes)
198
+ if (unionMaxStateBytes !== null) return unionMaxStateBytes
199
+ var unionNCols = arrayLen(args.columns)
200
+ var unionMaxOutputKeys = positiveInt(args.max_output_keys)
201
+ if (unionMaxOutputKeys === null) throw new Error("step 'union' needs max_output_keys or max_state_bytes for duplicate-eliminating set semantics")
202
+ return 4096 + unionMaxOutputKeys * (192 + unionNCols * 32)
203
+ }
204
+ if (op === 'join' || op === 'semi-join' || op === 'anti-join') {
205
+ if (args.sorted === true) {
206
+ var filtering = op === 'semi-join' || op === 'anti-join' || args.how === 'semi' || args.how === 'anti'
207
+ if (filtering) return 4096
208
+ var maxMatches = positiveInt(args.max_matches_per_row)
209
+ if (maxMatches === null) throw new Error("step '" + op + "' sorted=true needs max_matches_per_row for byte-bounded mutating join")
210
+ return 4096 + maxMatches * 512
211
+ }
212
+ var maxLookupBytes = positiveInt(args.max_lookup_bytes)
213
+ if (maxLookupBytes === null) throw new Error("step '" + op + "' needs max_lookup_bytes or sorted=true for byte-bounded native execution")
214
+ var est = 4096 + maxLookupBytes * 4
215
+ var maxStateBytes = positiveInt(args.max_state_bytes)
216
+ if (maxStateBytes !== null) return est + maxStateBytes
217
+ var maxLookupRows = positiveInt(args.max_lookup_rows)
218
+ var maxLookupKeys = positiveInt(args.max_lookup_keys)
219
+ if (maxLookupRows !== null) est += maxLookupRows * 96
220
+ if (maxLookupKeys !== null) est += maxLookupKeys * 192
221
+ return est
222
+ }
223
+ throw new Error("step '" + op + "' has no native byte estimator")
224
+ }
225
+
226
+ function loadPlan(planJson) {
227
+ var plan = typeof planJson === 'string' ? JSON.parse(planJson) : planJson
228
+ if (!plan || !Array.isArray(plan.steps)) throw new Error('plan must contain a steps array')
229
+ return plan
230
+ }
231
+
232
+ function blockingSteps(steps) {
233
+ var out = []
234
+ for (var i = 0; i < steps.length; i++) if (memoryClass(steps[i]) === 'blocking') out.push(steps[i])
235
+ return out
236
+ }
237
+
238
+ function keyStateSteps(steps) {
239
+ var out = []
240
+ for (var i = 0; i < steps.length; i++) if (memoryClass(steps[i]) === 'key_state') out.push(steps[i])
241
+ return out
242
+ }
243
+
244
+ function blockingPlanCanUseNativeSpill(steps) {
245
+ if (steps.length === 0) return false
246
+ for (var i = 0; i < steps.length; i++) if (steps[i].op !== 'sort') return false
247
+ return true
248
+ }
249
+
250
+ function injectSortSpill(plan, spillDir, memoryBytes) {
251
+ for (var i = 0; i < plan.steps.length; i++) {
252
+ var step = plan.steps[i]
253
+ if (step.op !== 'sort') continue
254
+ if (!step.args || typeof step.args !== 'object') step.args = {}
255
+ step.args.spill_dir = spillDir
256
+ if (memoryBytes !== null) step.args.spill_memory_bytes = memoryBytes
257
+ }
258
+ return JSON.stringify(plan)
259
+ }
260
+
261
+ function prepareNativePlan(planJson, options) {
262
+ options = options || {}
263
+ var plan = loadPlan(planJson)
264
+ var blocking = blockingSteps(plan.steps)
265
+ var firstBlocking = blocking.length ? blocking[0] : null
266
+ var keySteps = keyStateSteps(plan.steps)
267
+ var memoryBytes = parseMemorySize(options.memory)
268
+ var canSpillBlocking = false
269
+
270
+ if (options.spillDir) {
271
+ throw new Error('spillDir is not supported by standalone WASM native execution because browser/WASM spill storage would occupy WASM memory; use the Node native addon, CLI/direct C, or DuckDB')
272
+ }
273
+
274
+ if (options.spillDir) {
275
+ if (keySteps.length > 0) {
276
+ throw new Error("spillDir was requested, but native spill is not implemented yet for key-state step '" + keySteps[0].op + "'")
277
+ }
278
+ for (var i = 0; i < blocking.length; i++) {
279
+ if (blocking[i].op !== 'sort') {
280
+ throw new Error("spillDir was requested, but native spill is not implemented yet for blocking step '" + blocking[i].op + "'")
281
+ }
282
+ }
283
+ }
284
+
285
+ if (memoryBytes !== null && firstBlocking && !canSpillBlocking) {
286
+ throw new Error("memory " + formatBytes(memoryBytes) + " was requested, but native byte caps are not implemented for blocking step '" + firstBlocking.op + "'")
287
+ }
288
+ if (memoryBytes !== null && keySteps.length > 0) {
289
+ var total = 0
290
+ for (var j = 0; j < keySteps.length; j++) total += estimateKeyStateStepBytes(keySteps[j])
291
+ if (total > memoryBytes) throw new Error('estimated native key-state memory ' + formatBytes(total) + ' exceeds memory ' + formatBytes(memoryBytes))
292
+ }
293
+ if (firstBlocking && !options.allowBlocking && !canSpillBlocking) {
294
+ throw new Error("blocking step '" + firstBlocking.op + "' requires full input in native mode; pass allowBlocking: true for known-small data or use an external engine")
295
+ }
296
+ if (canSpillBlocking) return injectSortSpill(plan, options.spillDir, memoryBytes)
297
+ return typeof planJson === 'string' ? planJson : JSON.stringify(plan)
298
+ }
299
+
300
+ function validateNativeMemoryPolicy(planJson, options) {
301
+ options = options || {}
302
+ var plan = loadPlan(planJson)
303
+ var keySteps = keyStateSteps(plan.steps)
304
+ var memoryBytes = parseMemorySize(options.memory)
305
+ prepareNativePlan(plan, options)
306
+ if (memoryBytes === null || keySteps.length === 0) return null
307
+ var total = 0
308
+ for (var i = 0; i < keySteps.length; i++) total += estimateKeyStateStepBytes(keySteps[i])
309
+ return total
310
+ }
311
+
312
+ /**
313
+ * tranfi/wasm — Browser-friendly WASM wrapper.
314
+ *
315
+ * Usage:
316
+ * import createTranfi from 'tranfi/wasm'
317
+ * const tf = await createTranfi()
318
+ *
319
+ * // Compile DSL to SQL
320
+ * const sql = tf.compileToSql('csv | filter "age > 25" | csv', { dialect: 'duckdb' })
321
+ *
322
+ * // Run with DuckDB-WASM
323
+ * import * as duckdb from '@duckdb/duckdb-wasm'
324
+ * const db = new duckdb.AsyncDuckDB(...)
325
+ * const result = await tf.runDuckDB(db, 'csv | filter "age > 25" | csv', csvBlob)
326
+ */
327
+
328
+ async function createTranfi() {
329
+ // Dynamic import to handle CJS/ESM interop
330
+ var mod = await import('./tranfi_core.js')
331
+ var createModule = mod.default || mod
332
+ var wasm = await createModule()
333
+ var transformAPI = createTransformAPI(wasm)
334
+
335
+ function allocString(str) {
336
+ var len = wasm.lengthBytesUTF8(str)
337
+ var ptr = wasm._malloc(len + 1)
338
+ wasm.stringToUTF8(str, ptr, len + 1)
339
+ return { ptr, len }
340
+ }
341
+
342
+ function toBytes(data) {
343
+ return data instanceof Uint8Array ? data : new TextEncoder().encode(data)
344
+ }
345
+
346
+ function concatUint8(chunks) {
347
+ var total = chunks.reduce(function(s, c) { return s + c.length }, 0)
348
+ var result = new Uint8Array(total)
349
+ var offset = 0
350
+ for (var i = 0; i < chunks.length; i++) {
351
+ result.set(chunks[i], offset)
352
+ offset += chunks[i].length
353
+ }
354
+ return result
355
+ }
356
+
357
+ function hostPolicyOptions(options) {
358
+ options = options || {}
359
+ return {
360
+ allowFs: options.allowFs === undefined ? Boolean(options.spillDir) : Boolean(options.allowFs),
361
+ allowSpill: Boolean(options.spillDir),
362
+ allowRulesFile: Boolean(options.allowRulesFile),
363
+ workspaceRoot: options.workspaceRoot
364
+ }
365
+ }
366
+
367
+ function planUsesHostPaths(planJson) {
368
+ try {
369
+ var plan = JSON.parse(planJson)
370
+ var steps = Array.isArray(plan.steps) ? plan.steps : []
371
+ return steps.some(function(step) {
372
+ var args = step && step.args
373
+ if (!args || typeof args !== 'object') return false
374
+ return ['file', 'rules_file', 'rulesFile', 'spill_dir', 'spillDir'].some(function(key) {
375
+ return typeof args[key] === 'string' && args[key].length > 0
376
+ })
377
+ })
378
+ } catch (_) {
379
+ return false
380
+ }
381
+ }
382
+
383
+ return {
384
+ /** Raw WASM module (for advanced use) */
385
+ _wasm: wasm,
386
+
387
+ TranfiTransformError: transformAPI.TranfiTransformError,
388
+ TransformRecipe: transformAPI.TransformRecipe,
389
+ TransformAnalyzer: transformAPI.TransformAnalyzer,
390
+ TransformPlan: transformAPI.TransformPlan,
391
+ TransformApply: transformAPI.TransformApply,
392
+ TransformCancelToken: transformAPI.TransformCancelToken,
393
+ createTransformCancelToken: transformAPI.createTransformCancelToken,
394
+ safeTransformLimits: transformAPI.safeTransformLimits,
395
+
396
+ /** Library version */
397
+ version() {
398
+ return wasm.ccall('wasm_version', 'string', [], [])
399
+ },
400
+
401
+ /** Compile DSL string to plan JSON */
402
+ compileDsl(dsl) {
403
+ var s = allocString(dsl)
404
+ var resultPtr = wasm.ccall('wasm_compile_dsl', 'number', ['number', 'number'], [s.ptr, s.len])
405
+ wasm._free(s.ptr)
406
+ if (!resultPtr) {
407
+ var err = wasm.ccall('wasm_pipeline_error', 'string', ['number'], [-1])
408
+ throw new Error('DSL parse failed: ' + (err || 'unknown error'))
409
+ }
410
+ var json = wasm.UTF8ToString(resultPtr)
411
+ wasm._free(resultPtr)
412
+ return json
413
+ },
414
+
415
+ /** Apply standalone WASM native memory policy to a compiled plan */
416
+ prepareNativePlan(planJson, options) {
417
+ return prepareNativePlan(planJson, options || {})
418
+ },
419
+
420
+ /** Compile DSL string to SQL query */
421
+ compileToSql(dsl, options) {
422
+ normalizeSqlDialect(options)
423
+ var s = allocString(dsl)
424
+ var resultPtr = wasm.ccall('wasm_compile_to_sql', 'number', ['number', 'number'], [s.ptr, s.len])
425
+ wasm._free(s.ptr)
426
+ if (!resultPtr) {
427
+ var err = wasm.ccall('wasm_pipeline_error', 'string', ['number'], [-1])
428
+ throw new Error('SQL compile failed: ' + (err || 'unknown error'))
429
+ }
430
+ var sql = wasm.UTF8ToString(resultPtr)
431
+ wasm._free(resultPtr)
432
+ return sql
433
+ },
434
+
435
+ /** Create a native pipeline (push/pull streaming) */
436
+ createPipeline(planJson, options) {
437
+ options = options || {}
438
+ var s = allocString(planJson)
439
+ var root = options.workspaceRoot ? allocString(String(options.workspaceRoot)) : { ptr: 0 }
440
+ var hasPolicyCreate = typeof wasm._wasm_pipeline_create_with_policy === 'function'
441
+ var needsPolicyCreate = options.allowFs || options.allowSpill || options.allowRulesFile || root.ptr || planUsesHostPaths(planJson)
442
+ var handle = -1
443
+ if (hasPolicyCreate) {
444
+ handle = wasm.ccall('wasm_pipeline_create_with_policy', 'number',
445
+ ['number', 'number', 'number', 'number', 'number', 'number'],
446
+ [s.ptr, s.len, options.allowFs ? 1 : 0, options.allowSpill ? 1 : 0,
447
+ options.allowRulesFile ? 1 : 0, root.ptr])
448
+ } else if (!needsPolicyCreate) {
449
+ handle = wasm.ccall('wasm_pipeline_create', 'number', ['number', 'number'], [s.ptr, s.len])
450
+ }
451
+ wasm._free(s.ptr)
452
+ if (root.ptr) wasm._free(root.ptr)
453
+ if (!hasPolicyCreate && needsPolicyCreate) {
454
+ throw new Error('Failed to create pipeline: WASM host policy export unavailable for this plan; rebuild tranfi_core.js')
455
+ }
456
+ if (handle < 0) {
457
+ var err = wasm.ccall('wasm_pipeline_error', 'string', ['number'], [handle])
458
+ throw new Error('Failed to create pipeline: ' + (err || 'unknown error'))
459
+ }
460
+ return handle
461
+ },
462
+
463
+ /** Push data into pipeline */
464
+ push(handle, data) {
465
+ var buf = data instanceof Uint8Array ? data : new TextEncoder().encode(data)
466
+ var ptr = wasm._malloc(buf.length)
467
+ wasm.HEAPU8.set(buf, ptr)
468
+ var rc = wasm.ccall('wasm_pipeline_push', 'number', ['number', 'number', 'number'], [handle, ptr, buf.length])
469
+ wasm._free(ptr)
470
+ if (rc !== 0) {
471
+ var err = wasm.ccall('wasm_pipeline_error', 'string', ['number'], [handle])
472
+ throw new Error('Push failed: ' + (err || 'unknown error'))
473
+ }
474
+ },
475
+
476
+ /** Flush the decoder input boundary without finishing transforms */
477
+ flushInput(handle) {
478
+ var rc = wasm.ccall('wasm_pipeline_flush_input', 'number', ['number'], [handle])
479
+ if (rc !== 0) {
480
+ var err = wasm.ccall('wasm_pipeline_error', 'string', ['number'], [handle])
481
+ throw new Error('Input flush failed: ' + (err || 'unknown error'))
482
+ }
483
+ },
484
+
485
+ /** Set or clear the current host source name */
486
+ setSourceName(handle, name) {
487
+ var source = name == null ? '' : String(name)
488
+ var s = allocString(source)
489
+ var rc = wasm.ccall('wasm_pipeline_set_source_name', 'number', ['number', 'number'], [handle, s.ptr])
490
+ wasm._free(s.ptr)
491
+ if (rc !== 0) {
492
+ var err = wasm.ccall('wasm_pipeline_error', 'string', ['number'], [handle])
493
+ throw new Error('Set source name failed: ' + (err || 'unknown error'))
494
+ }
495
+ },
496
+
497
+ /** Signal end of input */
498
+ finish(handle) {
499
+ var rc = wasm.ccall('wasm_pipeline_finish', 'number', ['number'], [handle])
500
+ if (rc !== 0) {
501
+ var err = wasm.ccall('wasm_pipeline_error', 'string', ['number'], [handle])
502
+ throw new Error('Finish failed: ' + (err || 'unknown error'))
503
+ }
504
+ },
505
+
506
+ /** Advance finish by one flush boundary. Returns true when complete. */
507
+ finishStep(handle) {
508
+ var rc = wasm.ccall('wasm_pipeline_finish_step', 'number', ['number'], [handle])
509
+ if (rc < 0) {
510
+ var err = wasm.ccall('wasm_pipeline_error', 'string', ['number'], [handle])
511
+ throw new Error('Finish failed: ' + (err || 'unknown error'))
512
+ }
513
+ return rc === 1
514
+ },
515
+
516
+ /** Pull output from pipeline */
517
+ pull(handle, channel) {
518
+ var bufSize = 65536
519
+ var ptr = wasm._malloc(bufSize)
520
+ var chunks = []
521
+ for (;;) {
522
+ var n = wasm.ccall('wasm_pipeline_pull', 'number',
523
+ ['number', 'number', 'number', 'number'], [handle, channel, ptr, bufSize])
524
+ if (n === 0) break
525
+ chunks.push(new Uint8Array(wasm.HEAPU8.buffer, ptr, n).slice())
526
+ }
527
+ wasm._free(ptr)
528
+ var total = chunks.reduce(function(s, c) { return s + c.length }, 0)
529
+ var result = new Uint8Array(total)
530
+ var offset = 0
531
+ for (var i = 0; i < chunks.length; i++) {
532
+ result.set(chunks[i], offset)
533
+ offset += chunks[i].length
534
+ }
535
+ return result
536
+ },
537
+
538
+ /** Pull at most bufSize bytes from pipeline */
539
+ pullChunk(handle, channel, bufSize) {
540
+ bufSize = bufSize || 65536
541
+ if (bufSize <= 0) throw new Error('bufSize must be positive')
542
+ var ptr = wasm._malloc(bufSize)
543
+ var n = wasm.ccall('wasm_pipeline_pull', 'number',
544
+ ['number', 'number', 'number', 'number'], [handle, channel, ptr, bufSize])
545
+ var result = new Uint8Array(wasm.HEAPU8.buffer, ptr, n).slice()
546
+ wasm._free(ptr)
547
+ return result
548
+ },
549
+
550
+ /** Free pipeline */
551
+ free(handle) {
552
+ wasm.ccall('wasm_pipeline_free', null, ['number'], [handle])
553
+ },
554
+
555
+ /** Run pipeline with native streaming engine */
556
+ run(dsl, data, options) {
557
+ options = options || {}
558
+ var chunkSize = options.chunkSize || 65536
559
+ if (chunkSize <= 0) throw new Error('chunkSize must be positive')
560
+ var onOutput = options.onOutput
561
+ if (onOutput && typeof onOutput !== 'function') throw new TypeError('onOutput must be a function')
562
+ var collectOutput = options.collectOutput !== false
563
+ var allowBlocking = options.allowBlocking === true
564
+
565
+ var planJson = this.compileDsl(dsl)
566
+ planJson = prepareNativePlan(planJson, { allowBlocking: allowBlocking, memory: options.memory, spillDir: options.spillDir })
567
+ var handle = this.createPipeline(planJson, hostPolicyOptions(options))
568
+ var outputChunks = []
569
+ var self = this
570
+ function drainMain() {
571
+ for (;;) {
572
+ var chunk = self.pullChunk(handle, 0, chunkSize)
573
+ if (chunk.length === 0) break
574
+ if (onOutput) onOutput(chunk)
575
+ if (collectOutput) outputChunks.push(chunk)
576
+ }
577
+ }
578
+
579
+ try {
580
+ var buf = toBytes(data || '')
581
+ for (var i = 0; i < buf.length; i += chunkSize) {
582
+ this.push(handle, buf.subarray(i, Math.min(i + chunkSize, buf.length)))
583
+ drainMain()
584
+ }
585
+ for (;;) {
586
+ var done = this.finishStep(handle)
587
+ drainMain()
588
+ if (done) break
589
+ }
590
+ var output = collectOutput ? concatUint8(outputChunks) : new Uint8Array(0)
591
+ return {
592
+ output: output,
593
+ errors: this.pull(handle, 1),
594
+ stats: this.pull(handle, 2),
595
+ samples: this.pull(handle, 3),
596
+ get outputText() { return new TextDecoder().decode(this.output) },
597
+ get statsText() { return new TextDecoder().decode(this.stats) }
598
+ }
599
+ } finally {
600
+ this.free(handle)
601
+ }
602
+ },
603
+
604
+ /** Yield native main-output chunks while a WASM pipeline runs */
605
+ *iterChunks(dsl, data, options) {
606
+ options = options || {}
607
+ var chunkSize = options.chunkSize || 65536
608
+ if (chunkSize <= 0) throw new Error('chunkSize must be positive')
609
+ var allowBlocking = options.allowBlocking === true
610
+ var planJson = this.compileDsl(dsl)
611
+ planJson = prepareNativePlan(planJson, { allowBlocking: allowBlocking, memory: options.memory, spillDir: options.spillDir })
612
+ var handle = this.createPipeline(planJson, hostPolicyOptions(options))
613
+ try {
614
+ var buf = toBytes(data || '')
615
+ for (var i = 0; i < buf.length; i += chunkSize) {
616
+ this.push(handle, buf.subarray(i, Math.min(i + chunkSize, buf.length)))
617
+ for (;;) {
618
+ var chunk = this.pullChunk(handle, 0, chunkSize)
619
+ if (chunk.length === 0) break
620
+ yield chunk
621
+ }
622
+ }
623
+ for (;;) {
624
+ var done = this.finishStep(handle)
625
+ for (;;) {
626
+ var out = this.pullChunk(handle, 0, chunkSize)
627
+ if (out.length === 0) break
628
+ yield out
629
+ }
630
+ if (done) break
631
+ }
632
+ } finally {
633
+ this.free(handle)
634
+ }
635
+ },
636
+
637
+ /**
638
+ * Run pipeline via DuckDB-WASM.
639
+ *
640
+ * @param {object} db — An initialized @duckdb/duckdb-wasm AsyncDuckDB or DuckDB instance
641
+ * @param {string} dsl — Tranfi DSL string
642
+ * @param {string|Uint8Array|File} data — CSV input data
643
+ * @returns {Promise<{output: Uint8Array, outputText: string, rows: Array}>}
644
+ */
645
+ async runDuckDB(db, dsl, data) {
646
+ var sql = this.compileToSql(dsl)
647
+ var conn = await db.connect()
648
+
649
+ try {
650
+ // Register input data as a table
651
+ if (typeof data === 'string' || data instanceof Uint8Array) {
652
+ var buf = data instanceof Uint8Array ? data : new TextEncoder().encode(data)
653
+ await db.registerFileBuffer('input.csv', buf)
654
+ sql = sql.replaceAll('input_data', "read_csv('input.csv')")
655
+ } else if (data && data.name) {
656
+ // File object
657
+ await db.registerFileHandle(data.name, data)
658
+ sql = sql.replaceAll('input_data', "read_csv('" + data.name + "')")
659
+ } else {
660
+ throw new Error('data must be a string, Uint8Array, or File')
661
+ }
662
+
663
+ var result = await conn.query(sql)
664
+ var rows = result.toArray().map(function(row) { return row.toJSON() })
665
+
666
+ // Convert to CSV
667
+ var output = ''
668
+ if (rows.length > 0) {
669
+ var cols = Object.keys(rows[0])
670
+ output = cols.join(',') + '\n'
671
+ for (var i = 0; i < rows.length; i++) {
672
+ var vals = cols.map(function(c) {
673
+ var v = rows[i][c]
674
+ if (v === null || v === undefined) return ''
675
+ var s = String(v)
676
+ if (s.indexOf(',') >= 0 || s.indexOf('"') >= 0 || s.indexOf('\n') >= 0) {
677
+ return '"' + s.replace(/"/g, '""') + '"'
678
+ }
679
+ return s
680
+ })
681
+ output += vals.join(',') + '\n'
682
+ }
683
+ }
684
+
685
+ var outputBytes = new TextEncoder().encode(output)
686
+ return {
687
+ output: outputBytes,
688
+ rows: rows,
689
+ get outputText() { return output }
690
+ }
691
+ } finally {
692
+ await conn.close()
693
+ }
694
+ },
695
+
696
+ /** Recipe helpers */
697
+ recipeCount() {
698
+ return wasm.ccall('wasm_recipe_count', 'number', [], [])
699
+ },
700
+ recipeName(index) {
701
+ return wasm.ccall('wasm_recipe_name', 'string', ['number'], [index])
702
+ },
703
+ recipeDsl(index) {
704
+ return wasm.ccall('wasm_recipe_dsl', 'string', ['number'], [index])
705
+ },
706
+ recipeDescription(index) {
707
+ return wasm.ccall('wasm_recipe_description', 'string', ['number'], [index])
708
+ },
709
+ recipeFindDsl(name) {
710
+ var s = allocString(name)
711
+ var result = wasm.ccall('wasm_recipe_find_dsl', 'string', ['number'], [s.ptr])
712
+ wasm._free(s.ptr)
713
+ return result || null
714
+ },
715
+ recipes() {
716
+ var n = this.recipeCount()
717
+ var result = []
718
+ for (var i = 0; i < n; i++) {
719
+ result.push({
720
+ name: this.recipeName(i),
721
+ dsl: this.recipeDsl(i),
722
+ description: this.recipeDescription(i)
723
+ })
724
+ }
725
+ return result
726
+ }
727
+ }
728
+ }
729
+
730
+ // Support both CJS and ESM
731
+ if (typeof module !== 'undefined') module.exports = createTranfi
732
+ if (typeof exports !== 'undefined') exports.default = createTranfi