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
@@ -0,0 +1,411 @@
1
+ /** Host-side memory policy checks for native Tranfi execution. */
2
+
3
+ const BLOCKING_OPS = new Set([
4
+ 'sort',
5
+ 'pivot',
6
+ 'interpolate',
7
+ 'normalize',
8
+ 'acf',
9
+ 'codec.table.encode'
10
+ ])
11
+
12
+ const KEY_STATE_OPS = new Set([
13
+ 'rowid',
14
+ 'unique',
15
+ 'dedup',
16
+ 'group-agg',
17
+ 'frequency',
18
+ 'join',
19
+ 'semi-join',
20
+ 'anti-join',
21
+ 'onehot',
22
+ 'label-encode',
23
+ 'intersect',
24
+ 'setdiff',
25
+ 'intersect-all',
26
+ 'setdiff-all',
27
+ 'union'
28
+ ])
29
+
30
+ const SIZE_UNITS = {
31
+ '': 1,
32
+ b: 1,
33
+ k: 1024,
34
+ kb: 1024,
35
+ kib: 1024,
36
+ m: 1024 ** 2,
37
+ mb: 1024 ** 2,
38
+ mib: 1024 ** 2,
39
+ g: 1024 ** 3,
40
+ gb: 1024 ** 3,
41
+ gib: 1024 ** 3,
42
+ t: 1024 ** 4,
43
+ tb: 1024 ** 4,
44
+ tib: 1024 ** 4
45
+ }
46
+
47
+ const UNIQUE_DEFAULT_BLOOM_BYTES = 1024 ** 2
48
+
49
+ function parseMemorySize(value) {
50
+ if (value === undefined || value === null) return null
51
+ if (typeof value === 'number') {
52
+ if (!Number.isFinite(value) || value <= 0 || Math.floor(value) !== value) {
53
+ throw new Error('memory must be a positive integer byte count')
54
+ }
55
+ return value
56
+ }
57
+ if (typeof value !== 'string') {
58
+ throw new TypeError('memory must be a positive byte count or size string')
59
+ }
60
+ let text = value.trim()
61
+ const lower = text.toLowerCase()
62
+ if (lower.startsWith('max:') || lower.startsWith('max=')) text = text.slice(4).trim()
63
+ const match = text.match(/^(\d+)\s*([a-zA-Z]*)$/)
64
+ if (!match) throw new Error('memory must look like 64MB or max:64MB')
65
+ const amount = Number(match[1])
66
+ const suffix = match[2].toLowerCase()
67
+ if (amount <= 0) throw new Error('memory must be positive')
68
+ if (!(suffix in SIZE_UNITS)) throw new Error(`unsupported memory size suffix '${suffix}'`)
69
+ return amount * SIZE_UNITS[suffix]
70
+ }
71
+
72
+ function memoryForEngine(value) {
73
+ const n = parseMemorySize(value)
74
+ return n === null ? null : `${n}B`
75
+ }
76
+
77
+ function formatBytes(n) {
78
+ if (n < 1024) return `${n}B`
79
+ if (n < 1024 ** 2) return `${(n / 1024).toFixed(1)}KB`
80
+ if (n < 1024 ** 3) return `${(n / (1024 ** 2)).toFixed(1)}MB`
81
+ return `${(n / (1024 ** 3)).toFixed(1)}GB`
82
+ }
83
+
84
+ function stepsFromPlan(planJson) {
85
+ const plan = typeof planJson === 'string' ? JSON.parse(planJson) : planJson
86
+ if (!plan || !Array.isArray(plan.steps)) throw new Error('plan must contain a steps array')
87
+ return plan.steps
88
+ }
89
+
90
+ function isFilteringJoin(step) {
91
+ const args = argsOf(step)
92
+ return step.op === 'semi-join' || step.op === 'anti-join' ||
93
+ (step.op === 'join' && (args.how === 'semi' || args.how === 'anti'))
94
+ }
95
+
96
+ function memoryClass(step) {
97
+ if (typeof step.memory_class === 'string') return step.memory_class
98
+ const args = argsOf(step)
99
+ if (step.op === 'pivot') {
100
+ if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
101
+ if (args.sorted === true && Array.isArray(args.categories) && args.categories.length > 0) return 'bounded_state'
102
+ }
103
+ if (step.op === 'unique' || step.op === 'dedup') {
104
+ if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
105
+ if (args.sorted === true) return 'bounded_state'
106
+ }
107
+ if (step.op === 'group-agg') {
108
+ if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
109
+ if (args.sorted === true) return 'bounded_state'
110
+ }
111
+ if (step.op === 'join' || step.op === 'semi-join' || step.op === 'anti-join') {
112
+ if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
113
+ if (args.sorted === true) return 'bounded_state'
114
+ }
115
+ if (step.op === 'intersect' || step.op === 'setdiff' || step.op === 'intersect-all' || step.op === 'setdiff-all') {
116
+ if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
117
+ if (args.sorted === true) return 'bounded_state'
118
+ }
119
+ if (step.op === 'union') {
120
+ if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
121
+ if (args.sorted === true) return 'bounded_state'
122
+ }
123
+ if (BLOCKING_OPS.has(step.op)) return 'blocking'
124
+ if (KEY_STATE_OPS.has(step.op)) return 'key_state'
125
+ return null
126
+ }
127
+
128
+ function argsOf(step) {
129
+ return step && step.args && typeof step.args === 'object' ? step.args : {}
130
+ }
131
+
132
+ function positiveInt(value) {
133
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 && Math.floor(value) === value
134
+ ? value
135
+ : null
136
+ }
137
+
138
+ function arrayLen(value) {
139
+ return Array.isArray(value) ? value.length : 0
140
+ }
141
+
142
+ function stringArrayBytes(value) {
143
+ if (!Array.isArray(value)) return 0
144
+ let total = 0
145
+ for (const item of value) {
146
+ if (typeof item === 'string') total += Buffer.byteLength(item, 'utf-8') + 1
147
+ }
148
+ return total
149
+ }
150
+
151
+ function categoryCap(step) {
152
+ const args = argsOf(step)
153
+ const maxCategories = positiveInt(args.max_categories)
154
+ let nCategories = arrayLen(args.categories)
155
+ let literalBytes = stringArrayBytes(args.categories)
156
+ if (args.unknown === 'other') {
157
+ nCategories += 1
158
+ literalBytes += 'other'.length + 1
159
+ }
160
+ if (maxCategories !== null) return [maxCategories, literalBytes]
161
+ if (nCategories > 0) return [nCategories, literalBytes]
162
+ return [null, literalBytes]
163
+ }
164
+
165
+ function estimateKeyStateStepBytes(step) {
166
+ const args = argsOf(step)
167
+ const op = step.op
168
+
169
+ if (op === 'rowid') {
170
+ const nCols = arrayLen(args.columns)
171
+ if (nCols === 0 || args.sorted === true) return 2048 + nCols * 128
172
+ const maxStateBytes = positiveInt(args.max_state_bytes)
173
+ if (maxStateBytes !== null) return maxStateBytes
174
+ const maxKeys = positiveInt(args.max_keys)
175
+ if (maxKeys === null) throw new Error("step 'rowid' needs max_keys, max_state_bytes, or sorted=true")
176
+ return 1024 + maxKeys * 224 + nCols * 128
177
+ }
178
+
179
+ if (op === 'unique' || op === 'dedup') {
180
+ const nCols = arrayLen(args.columns)
181
+ if (args.sorted === true) return 2048 + nCols * 128
182
+ if (args.approx === true || args.mode === 'approx') {
183
+ const bloomBytes = positiveInt(args.bloom_bytes)
184
+ return bloomBytes !== null ? bloomBytes : UNIQUE_DEFAULT_BLOOM_BYTES
185
+ }
186
+ const maxStateBytes = positiveInt(args.max_state_bytes)
187
+ if (maxStateBytes !== null) return maxStateBytes
188
+ const maxKeys = positiveInt(args.max_keys)
189
+ if (maxKeys === null) throw new Error(`step '${op}' needs max_keys, max_state_bytes, or sorted=true for byte-bounded native execution`)
190
+ return 1024 + maxKeys * (256 + nCols * 32)
191
+ }
192
+
193
+ if (op === 'group-agg') {
194
+ const nGroup = arrayLen(args.group_by)
195
+ const nAggs = arrayLen(args.aggs)
196
+ if (typeof args.spill_dir === 'string' && args.spill_dir) {
197
+ const spillBytes = positiveInt(args.spill_memory_bytes)
198
+ if (spillBytes !== null) return spillBytes
199
+ throw new Error("step 'group-agg' spill mode needs spill_memory_bytes for a host byte estimate")
200
+ }
201
+ const maxStateBytes = positiveInt(args.max_state_bytes)
202
+ if (maxStateBytes !== null) return maxStateBytes
203
+ if (args.sorted === true) return 4096 + nGroup * 128 + nAggs * 128
204
+ const maxGroups = positiveInt(args.max_groups)
205
+ if (maxGroups === null) throw new Error("step 'group-agg' needs max_groups, max_state_bytes, or sorted=true for byte-bounded native execution")
206
+ return 2048 + maxGroups * (384 + nGroup * 64 + nAggs * 96)
207
+ }
208
+
209
+ if (op === 'frequency') {
210
+ const approx = args.mode === 'approx' || args.approx === true
211
+ const maxValues = positiveInt(args.max_values)
212
+ if (approx && maxValues === null) throw new Error("step 'frequency' mode=approx needs max_values for byte-bounded native execution")
213
+ const maxStateBytes = positiveInt(args.max_state_bytes)
214
+ if (maxStateBytes !== null) return maxStateBytes
215
+ if (maxValues === null) throw new Error("step 'frequency' needs max_values or max_state_bytes for byte-bounded native execution")
216
+ const cappedValues = args.overflow === 'other' ? maxValues + 1 : maxValues
217
+ return 1024 + cappedValues * (224 + arrayLen(args.columns) * 32)
218
+ }
219
+
220
+ if (op === 'onehot' || op === 'label-encode') {
221
+ const maxStateBytes = positiveInt(args.max_state_bytes)
222
+ if (maxStateBytes !== null) return maxStateBytes
223
+ const [cap, literalBytes] = categoryCap(step)
224
+ if (cap === null) throw new Error(`step '${op}' needs max_categories, max_state_bytes, or declared categories`)
225
+ return 1024 + literalBytes + cap * (op === 'onehot' ? 320 : 224)
226
+ }
227
+
228
+ if (op === 'union-all') return 4096
229
+
230
+ if (op === 'intersect' || op === 'setdiff' || op === 'intersect-all' || op === 'setdiff-all') {
231
+ const nCols = arrayLen(args.columns)
232
+ const bagSetOp = op === 'intersect-all' || op === 'setdiff-all'
233
+ if (typeof args.spill_dir === 'string' && args.spill_dir) {
234
+ const spillBytes = positiveInt(args.spill_memory_bytes)
235
+ if (spillBytes !== null) return spillBytes
236
+ throw new Error(`step '${op}' spill mode needs spill_memory_bytes for a host byte estimate`)
237
+ }
238
+ if (args.sorted === true) {
239
+ return 4096 + nCols * 256
240
+ }
241
+ const maxLookupBytes = positiveInt(args.max_lookup_bytes)
242
+ if (maxLookupBytes === null) throw new Error(`step '${op}' needs max_lookup_bytes for byte-bounded native execution`)
243
+ const maxStateBytes = positiveInt(args.max_state_bytes)
244
+ if (maxStateBytes !== null) return 4096 + maxLookupBytes * 3 + maxStateBytes
245
+ let est = 4096 + maxLookupBytes * 3
246
+ const maxLookupKeys = positiveInt(args.max_lookup_keys)
247
+ if (bagSetOp) {
248
+ if (maxLookupKeys === null) throw new Error(`step '${op}' needs max_lookup_keys or max_state_bytes for bag-count set semantics`)
249
+ return est + maxLookupKeys * (224 + nCols * 32)
250
+ }
251
+ const maxOutputKeys = positiveInt(args.max_output_keys)
252
+ if (maxOutputKeys === null) throw new Error(`step '${op}' needs max_output_keys or max_state_bytes for duplicate-eliminating set semantics`)
253
+ if (maxLookupKeys !== null) est += maxLookupKeys * (192 + nCols * 32)
254
+ est += maxOutputKeys * (192 + nCols * 32)
255
+ return est
256
+ }
257
+
258
+ if (op === 'union') {
259
+ if (args.sorted === true) {
260
+ const nCols = arrayLen(args.columns)
261
+ return 4096 + nCols * 256
262
+ }
263
+ if (typeof args.spill_dir === 'string' && args.spill_dir) {
264
+ const spillMemoryBytes = positiveInt(args.spill_memory_bytes)
265
+ if (spillMemoryBytes === null) throw new Error("step 'union' spill needs spill_memory_bytes for byte-bounded native execution")
266
+ return spillMemoryBytes
267
+ }
268
+ const maxStateBytes = positiveInt(args.max_state_bytes)
269
+ if (maxStateBytes !== null) return maxStateBytes
270
+ const nCols = arrayLen(args.columns)
271
+ const maxOutputKeys = positiveInt(args.max_output_keys)
272
+ if (maxOutputKeys === null) throw new Error("step 'union' needs max_output_keys or max_state_bytes for duplicate-eliminating set semantics")
273
+ return 4096 + maxOutputKeys * (192 + nCols * 32)
274
+ }
275
+
276
+ if (op === 'join' || op === 'semi-join' || op === 'anti-join') {
277
+ const filtering = isFilteringJoin(step)
278
+ if (typeof args.spill_dir === 'string' && args.spill_dir) {
279
+ if (!filtering) {
280
+ const maxMatches = positiveInt(args.max_matches_per_row)
281
+ if (maxMatches === null) throw new Error(`step '${op}' spill mode needs max_matches_per_row for byte-bounded mutating join`)
282
+ }
283
+ const spillBytes = positiveInt(args.spill_memory_bytes)
284
+ if (spillBytes !== null) return spillBytes
285
+ throw new Error(`step '${op}' spill mode needs spill_memory_bytes for a host byte estimate`)
286
+ }
287
+ if (args.sorted === true) {
288
+ if (filtering) return 4096
289
+ const maxMatches = positiveInt(args.max_matches_per_row)
290
+ if (maxMatches === null) throw new Error(`step '${op}' sorted=true needs max_matches_per_row for byte-bounded mutating join`)
291
+ return 4096 + maxMatches * 512
292
+ }
293
+ const maxLookupBytes = positiveInt(args.max_lookup_bytes)
294
+ if (maxLookupBytes === null) throw new Error(`step '${op}' needs max_lookup_bytes or sorted=true for byte-bounded native execution`)
295
+ let est = 4096 + maxLookupBytes * 4
296
+ const maxStateBytes = positiveInt(args.max_state_bytes)
297
+ if (maxStateBytes !== null) return est + maxStateBytes
298
+ const maxLookupRows = positiveInt(args.max_lookup_rows)
299
+ const maxLookupKeys = positiveInt(args.max_lookup_keys)
300
+ if (maxLookupRows !== null) est += maxLookupRows * 96
301
+ if (maxLookupKeys !== null) est += maxLookupKeys * 192
302
+ return est
303
+ }
304
+
305
+ throw new Error(`step '${op}' has no native byte estimator`)
306
+ }
307
+
308
+ function loadPlan(planJson) {
309
+ const plan = typeof planJson === 'string' ? JSON.parse(planJson) : planJson
310
+ if (!plan || !Array.isArray(plan.steps)) throw new Error('plan must contain a steps array')
311
+ return plan
312
+ }
313
+
314
+ function blockingSteps(steps) {
315
+ return steps.filter(step => memoryClass(step) === 'blocking')
316
+ }
317
+
318
+ function keyStateSteps(steps) {
319
+ return steps.filter(step => memoryClass(step) === 'key_state')
320
+ }
321
+
322
+ function pivotStepCanUseNativeSpill(step) {
323
+ const args = argsOf(step)
324
+ if (step.op !== 'pivot' || args.sorted === true) return false
325
+ return (Array.isArray(args.categories) && args.categories.length > 0) || positiveInt(args.max_categories) !== null
326
+ }
327
+
328
+ function blockingStepCanUseNativeSpill(step) {
329
+ return step.op === 'sort' || pivotStepCanUseNativeSpill(step)
330
+ }
331
+
332
+ function blockingPlanCanUseNativeSpill(steps) {
333
+ return steps.length > 0 && steps.every(blockingStepCanUseNativeSpill)
334
+ }
335
+
336
+ function keyStateStepCanUseNativeSpill(step) {
337
+ const args = argsOf(step)
338
+ return (step.op === 'unique' || step.op === 'dedup' || step.op === 'group-agg' || step.op === 'join' || step.op === 'semi-join' || step.op === 'anti-join' || step.op === 'intersect' || step.op === 'setdiff' || step.op === 'intersect-all' || step.op === 'setdiff-all' || step.op === 'union') && args.sorted !== true
339
+ }
340
+
341
+ function injectNativeSpill(plan, spillDir, memoryBytes) {
342
+ for (const step of plan.steps) {
343
+ if (!blockingStepCanUseNativeSpill(step) && !keyStateStepCanUseNativeSpill(step)) continue
344
+ if (!step.args || typeof step.args !== 'object') step.args = {}
345
+ step.args.spill_dir = spillDir
346
+ if (memoryBytes !== null) step.args.spill_memory_bytes = memoryBytes
347
+ }
348
+ return JSON.stringify(plan)
349
+ }
350
+
351
+ function prepareNativePlan(planJson, { allowBlocking = false, memory, spillDir } = {}) {
352
+ const plan = loadPlan(planJson)
353
+ const steps = plan.steps
354
+ const blocking = blockingSteps(steps)
355
+ const firstBlocking = blocking[0] || null
356
+ const keySteps = keyStateSteps(steps)
357
+ const memoryBytes = parseMemorySize(memory)
358
+ const canSpillBlocking = Boolean(spillDir) && blockingPlanCanUseNativeSpill(blocking)
359
+ const spillableKeySteps = spillDir ? keySteps.filter(keyStateStepCanUseNativeSpill) : []
360
+ const keyStepsForEstimate = keySteps.filter(step => !spillableKeySteps.includes(step))
361
+
362
+ if (spillDir) {
363
+ const unsupportedKey = keySteps.find(step => !keyStateStepCanUseNativeSpill(step))
364
+ if (unsupportedKey) {
365
+ throw new Error(`spillDir was requested, but native spill is not implemented yet for key-state step '${unsupportedKey.op}'`)
366
+ }
367
+ const unsupported = blocking.find(step => !blockingStepCanUseNativeSpill(step))
368
+ if (unsupported) {
369
+ throw new Error(`spillDir was requested, but native spill is not implemented yet for blocking step '${unsupported.op}'`)
370
+ }
371
+ }
372
+
373
+ if (memoryBytes !== null && firstBlocking && !canSpillBlocking) {
374
+ throw new Error(`memory ${formatBytes(memoryBytes)} was requested, but native byte caps are not implemented for blocking step '${firstBlocking.op}'`)
375
+ }
376
+
377
+ if (memoryBytes !== null && keyStepsForEstimate.length > 0) {
378
+ let total = 0
379
+ for (const step of keyStepsForEstimate) total += estimateKeyStateStepBytes(step)
380
+ if (total > memoryBytes) {
381
+ throw new Error(`estimated native key-state memory ${formatBytes(total)} exceeds memory ${formatBytes(memoryBytes)}`)
382
+ }
383
+ }
384
+
385
+ if (firstBlocking && !allowBlocking && !canSpillBlocking) {
386
+ 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`)
387
+ }
388
+
389
+ if (canSpillBlocking || spillableKeySteps.length > 0) return injectNativeSpill(plan, spillDir, memoryBytes)
390
+ return typeof planJson === 'string' ? planJson : JSON.stringify(plan)
391
+ }
392
+
393
+ function validateNativeMemoryPolicy(planJson, { allowBlocking = false, memory, spillDir } = {}) {
394
+ const memoryBytes = parseMemorySize(memory)
395
+ const prepared = prepareNativePlan(planJson, { allowBlocking, memory, spillDir })
396
+ const plan = loadPlan(prepared)
397
+ const keySteps = keyStateSteps(plan.steps)
398
+
399
+ if (memoryBytes === null || keySteps.length === 0) return null
400
+ let estimated = 0
401
+ for (const step of keySteps) estimated += estimateKeyStateStepBytes(step)
402
+ return estimated
403
+ }
404
+
405
+ module.exports = {
406
+ parseMemorySize,
407
+ memoryForEngine,
408
+ prepareNativePlan,
409
+ validateNativeMemoryPolicy,
410
+ estimateKeyStateStepBytes
411
+ }
package/src/native.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * native.js — Load the N-API native addon.
3
+ * Returns the binding object or null if not available.
4
+ */
5
+
6
+ let binding = null
7
+
8
+ try {
9
+ binding = require('../build/Release/tranfi_napi.node')
10
+ } catch {
11
+ try {
12
+ binding = require('../build/Debug/tranfi_napi.node')
13
+ } catch {
14
+ binding = null
15
+ }
16
+ }
17
+
18
+ module.exports = binding