tranfi 0.1.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 (132) hide show
  1. package/LICENSE +177 -0
  2. package/NOTICE +8 -0
  3. package/README.md +272 -40
  4. package/app/assets/{index-pDFMluyz.js → index-BIAIKnrp.js} +1 -1
  5. package/app/index.html +1 -1
  6. package/binding.gyp +55 -3
  7. package/csrc/arena.c +7 -5
  8. package/csrc/batch.c +818 -71
  9. package/csrc/buffer.c +84 -8
  10. package/csrc/cJSON.c +262 -19
  11. package/csrc/cJSON.h +17 -1
  12. package/csrc/codec_csv.c +1074 -181
  13. package/csrc/codec_jsonl.c +830 -118
  14. package/csrc/codec_table.c +108 -78
  15. package/csrc/codec_text.c +286 -68
  16. package/csrc/compiler.c +31 -3
  17. package/csrc/config.h +21 -0
  18. package/csrc/dsl.c +4722 -485
  19. package/csrc/expr.c +363 -55
  20. package/csrc/expr.h +2 -0
  21. package/csrc/internal.h +316 -27
  22. package/csrc/ir.c +65 -18
  23. package/csrc/ir.h +41 -0
  24. package/csrc/ir_schema.c +20 -5
  25. package/csrc/ir_serialize.c +68 -6
  26. package/csrc/ir_sql.c +796 -185
  27. package/csrc/ir_validate.c +462 -6
  28. package/csrc/json_path.c +210 -0
  29. package/csrc/main.c +879 -30
  30. package/csrc/memory_estimate.c +477 -0
  31. package/csrc/op_acf.c +171 -21
  32. package/csrc/op_across.c +477 -0
  33. package/csrc/op_anomaly.c +167 -32
  34. package/csrc/op_assert.c +761 -0
  35. package/csrc/op_bin.c +168 -29
  36. package/csrc/op_cast.c +383 -55
  37. package/csrc/op_clip.c +30 -19
  38. package/csrc/op_date_trunc.c +208 -34
  39. package/csrc/op_datetime.c +259 -77
  40. package/csrc/op_derive.c +65 -97
  41. package/csrc/op_diff.c +146 -30
  42. package/csrc/op_ewma.c +149 -30
  43. package/csrc/op_explode.c +124 -26
  44. package/csrc/op_fill_down.c +125 -53
  45. package/csrc/op_fill_null.c +176 -31
  46. package/csrc/op_filter.c +89 -40
  47. package/csrc/op_frequency.c +571 -43
  48. package/csrc/op_grep.c +36 -18
  49. package/csrc/op_group_agg.c +1790 -119
  50. package/csrc/op_hash.c +48 -15
  51. package/csrc/op_head.c +21 -86
  52. package/csrc/op_interpolate.c +268 -62
  53. package/csrc/op_join.c +2700 -182
  54. package/csrc/op_json_extract.c +227 -0
  55. package/csrc/op_json_filter.c +384 -0
  56. package/csrc/op_json_flatten.c +293 -0
  57. package/csrc/op_json_schema.c +503 -0
  58. package/csrc/op_label_encode.c +328 -53
  59. package/csrc/op_lag.c +181 -0
  60. package/csrc/op_lead.c +141 -89
  61. package/csrc/op_normalize.c +363 -79
  62. package/csrc/op_onehot.c +345 -73
  63. package/csrc/op_pivot.c +1546 -162
  64. package/csrc/op_quarantine.c +189 -0
  65. package/csrc/op_registry.c +2062 -166
  66. package/csrc/op_rename.c +41 -50
  67. package/csrc/op_replace.c +270 -118
  68. package/csrc/op_rleid.c +297 -0
  69. package/csrc/op_rowid.c +559 -0
  70. package/csrc/op_sample.c +80 -23
  71. package/csrc/op_schema.c +1341 -0
  72. package/csrc/op_schema_infer.c +252 -0
  73. package/csrc/op_select.c +265 -65
  74. package/csrc/op_set.c +3449 -0
  75. package/csrc/op_skip.c +30 -87
  76. package/csrc/op_sort.c +670 -124
  77. package/csrc/op_source_name.c +120 -0
  78. package/csrc/op_split.c +65 -28
  79. package/csrc/op_split_data.c +41 -9
  80. package/csrc/op_stack.c +178 -222
  81. package/csrc/op_stats.c +206 -110
  82. package/csrc/op_step.c +217 -55
  83. package/csrc/op_tail.c +21 -12
  84. package/csrc/op_tee.c +338 -0
  85. package/csrc/op_top.c +260 -53
  86. package/csrc/op_trim.c +48 -19
  87. package/csrc/op_unique.c +1193 -150
  88. package/csrc/op_unpivot.c +100 -66
  89. package/csrc/op_validate.c +601 -24
  90. package/csrc/op_window.c +492 -51
  91. package/csrc/path_policy.c +85 -0
  92. package/csrc/pipeline.c +872 -99
  93. package/csrc/recipes.c +3 -1
  94. package/csrc/report.c +73 -30
  95. package/csrc/selector.c +1097 -0
  96. package/csrc/size_utils.c +348 -0
  97. package/csrc/spill.c +317 -0
  98. package/csrc/spill.h +21 -0
  99. package/csrc/tranfi.h +169 -1
  100. package/csrc/transform.h +209 -0
  101. package/csrc/transform_api.c +2237 -0
  102. package/csrc/transform_categorical.c +923 -0
  103. package/csrc/transform_internal.h +472 -0
  104. package/csrc/transform_json.c +3812 -0
  105. package/csrc/transform_numeric.c +1966 -0
  106. package/csrc/transform_sha256.c +154 -0
  107. package/csrc/transform_wasm.h +162 -0
  108. package/csrc/transform_wasm_api.c +1373 -0
  109. package/csrc/wasm_api.c +70 -9
  110. package/napi_api.c +219 -11
  111. package/napi_transform.c +1648 -0
  112. package/napi_transform.h +8 -0
  113. package/package.json +27 -11
  114. package/scripts/install-native.js +76 -0
  115. package/scripts/prepack.js +64 -0
  116. package/scripts/sync-csrc.js +23 -0
  117. package/src/cli.js +8 -11
  118. package/src/engines/duckdb.js +45 -12
  119. package/src/index.js +661 -42
  120. package/src/memory_policy.js +411 -0
  121. package/src/native.js +1 -5
  122. package/src/pipeline.js +454 -31
  123. package/src/recipe_json.js +80 -0
  124. package/src/server.js +10 -8
  125. package/src/transform.js +403 -0
  126. package/src/transform_error.js +10 -0
  127. package/src/wasm.js +6 -4
  128. package/wasm/index.js +498 -10
  129. package/wasm/tranfi_core.js +0 -0
  130. package/wasm/transform.js +1156 -0
  131. package/wasm/worker.js +786 -0
  132. package/csrc/plan.c +0 -206
package/wasm/index.js CHANGED
@@ -1,3 +1,314 @@
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
+
1
312
  /**
2
313
  * tranfi/wasm — Browser-friendly WASM wrapper.
3
314
  *
@@ -6,7 +317,7 @@
6
317
  * const tf = await createTranfi()
7
318
  *
8
319
  * // Compile DSL to SQL
9
- * const sql = tf.compileToSql('csv | filter "age > 25" | csv')
320
+ * const sql = tf.compileToSql('csv | filter "age > 25" | csv', { dialect: 'duckdb' })
10
321
  *
11
322
  * // Run with DuckDB-WASM
12
323
  * import * as duckdb from '@duckdb/duckdb-wasm'
@@ -19,6 +330,7 @@ async function createTranfi() {
19
330
  var mod = await import('./tranfi_core.js')
20
331
  var createModule = mod.default || mod
21
332
  var wasm = await createModule()
333
+ var transformAPI = createTransformAPI(wasm)
22
334
 
23
335
  function allocString(str) {
24
336
  var len = wasm.lengthBytesUTF8(str)
@@ -27,10 +339,60 @@ async function createTranfi() {
27
339
  return { ptr, len }
28
340
  }
29
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
+
30
383
  return {
31
384
  /** Raw WASM module (for advanced use) */
32
385
  _wasm: wasm,
33
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
+
34
396
  /** Library version */
35
397
  version() {
36
398
  return wasm.ccall('wasm_version', 'string', [], [])
@@ -50,8 +412,14 @@ async function createTranfi() {
50
412
  return json
51
413
  },
52
414
 
415
+ /** Apply standalone WASM native memory policy to a compiled plan */
416
+ prepareNativePlan(planJson, options) {
417
+ return prepareNativePlan(planJson, options || {})
418
+ },
419
+
53
420
  /** Compile DSL string to SQL query */
54
- compileToSql(dsl) {
421
+ compileToSql(dsl, options) {
422
+ normalizeSqlDialect(options)
55
423
  var s = allocString(dsl)
56
424
  var resultPtr = wasm.ccall('wasm_compile_to_sql', 'number', ['number', 'number'], [s.ptr, s.len])
57
425
  wasm._free(s.ptr)
@@ -65,10 +433,26 @@ async function createTranfi() {
65
433
  },
66
434
 
67
435
  /** Create a native pipeline (push/pull streaming) */
68
- createPipeline(planJson) {
436
+ createPipeline(planJson, options) {
437
+ options = options || {}
69
438
  var s = allocString(planJson)
70
- var handle = wasm.ccall('wasm_pipeline_create', 'number', ['number', 'number'], [s.ptr, s.len])
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
+ }
71
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
+ }
72
456
  if (handle < 0) {
73
457
  var err = wasm.ccall('wasm_pipeline_error', 'string', ['number'], [handle])
74
458
  throw new Error('Failed to create pipeline: ' + (err || 'unknown error'))
@@ -89,6 +473,27 @@ async function createTranfi() {
89
473
  }
90
474
  },
91
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
+
92
497
  /** Signal end of input */
93
498
  finish(handle) {
94
499
  var rc = wasm.ccall('wasm_pipeline_finish', 'number', ['number'], [handle])
@@ -98,6 +503,16 @@ async function createTranfi() {
98
503
  }
99
504
  },
100
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
+
101
516
  /** Pull output from pipeline */
102
517
  pull(handle, channel) {
103
518
  var bufSize = 65536
@@ -120,21 +535,61 @@ async function createTranfi() {
120
535
  return result
121
536
  },
122
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
+
123
550
  /** Free pipeline */
124
551
  free(handle) {
125
552
  wasm.ccall('wasm_pipeline_free', null, ['number'], [handle])
126
553
  },
127
554
 
128
555
  /** Run pipeline with native streaming engine */
129
- run(dsl, data) {
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
+
130
565
  var planJson = this.compileDsl(dsl)
131
- var handle = this.createPipeline(planJson)
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
+
132
579
  try {
133
- var buf = data instanceof Uint8Array ? data : new TextEncoder().encode(data)
134
- this.push(handle, buf)
135
- this.finish(handle)
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)
136
591
  return {
137
- output: this.pull(handle, 0),
592
+ output: output,
138
593
  errors: this.pull(handle, 1),
139
594
  stats: this.pull(handle, 2),
140
595
  samples: this.pull(handle, 3),
@@ -146,6 +601,39 @@ async function createTranfi() {
146
601
  }
147
602
  },
148
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
+
149
637
  /**
150
638
  * Run pipeline via DuckDB-WASM.
151
639
  *
Binary file