tranfi 0.1.2 → 0.2.1

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 +443 -51
  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 +352 -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 +81 -41
  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/csrc/op_tee.c ADDED
@@ -0,0 +1,338 @@
1
+ /*
2
+ * op_tee.c -- Streaming side-channel row snapshots.
3
+ *
4
+ * Config: {
5
+ * "expr": "optional row predicate",
6
+ * "channel": "samples|errors|stats",
7
+ * "columns": ["optional", "selectors"],
8
+ * "limit": 1000,
9
+ * "every": 1,
10
+ * "name": "tee",
11
+ * "include_row": true
12
+ * }
13
+ */
14
+
15
+ #include "internal.h"
16
+ #include "cJSON.h"
17
+ #include <stdbool.h>
18
+ #include <stdio.h>
19
+ #include <stdlib.h>
20
+ #include <string.h>
21
+
22
+ typedef struct {
23
+ tf_expr *expr;
24
+ char *expr_text;
25
+ char *name;
26
+ char *channel_name;
27
+ int channel;
28
+ char **selectors;
29
+ size_t n_selectors;
30
+ int *indices;
31
+ size_t n_indices;
32
+ int resolved;
33
+ size_t limit;
34
+ size_t every;
35
+ size_t row_index;
36
+ size_t emitted;
37
+ int include_row;
38
+ tf_audit_options audit_opts;
39
+ } tee_state;
40
+
41
+ static int parse_channel(const char *s, int *out, const char **name_out) {
42
+ if (!s || !*s || strcmp(s, "samples") == 0 || strcmp(s, "sample") == 0) {
43
+ *out = TF_CHAN_SAMPLES;
44
+ *name_out = "samples";
45
+ return 1;
46
+ }
47
+ if (strcmp(s, "errors") == 0 || strcmp(s, "error") == 0) {
48
+ *out = TF_CHAN_ERRORS;
49
+ *name_out = "errors";
50
+ return 1;
51
+ }
52
+ if (strcmp(s, "stats") == 0 || strcmp(s, "audit") == 0) {
53
+ *out = TF_CHAN_STATS;
54
+ *name_out = strcmp(s, "audit") == 0 ? "audit" : "stats";
55
+ return 1;
56
+ }
57
+ return 0;
58
+ }
59
+
60
+ static tf_buffer *tee_side_buffer(tf_side_channels *side, int channel) {
61
+ if (!side) return NULL;
62
+ switch (channel) {
63
+ case TF_CHAN_ERRORS: return side->errors;
64
+ case TF_CHAN_STATS: return side->stats;
65
+ case TF_CHAN_SAMPLES: return side->samples;
66
+ default: return NULL;
67
+ }
68
+ }
69
+
70
+ static int tee_audit_column_allowed(const tee_state *st, const char *name) {
71
+ if (!st || st->audit_opts.n_columns == 0) return 1;
72
+ for (size_t i = 0; i < st->audit_opts.n_columns; i++) {
73
+ if (st->audit_opts.columns[i] && strcmp(st->audit_opts.columns[i], name ? name : "") == 0)
74
+ return 1;
75
+ }
76
+ return 0;
77
+ }
78
+
79
+ static cJSON *tee_selected_row_to_json(const tee_state *st, const tf_batch *in, size_t row) {
80
+ cJSON *data = cJSON_CreateObject();
81
+ if (!data) return NULL;
82
+ for (size_t i = 0; i < st->n_indices; i++) {
83
+ int ci = st->indices[i];
84
+ if (ci < 0 || (size_t)ci >= in->n_cols) continue;
85
+ const char *name = in->col_names[ci] ? in->col_names[ci] : "";
86
+ if (!tee_audit_column_allowed(st, name)) continue;
87
+ cJSON *value = tf_audit_cell_to_json(in, row, (size_t)ci, &st->audit_opts);
88
+ if (!value) { cJSON_Delete(data); return NULL; }
89
+ if (tf_json_add_item(data, name, value) != TF_OK) {
90
+ cJSON_Delete(data);
91
+ return NULL;
92
+ }
93
+ }
94
+ if (st->audit_opts.max_bytes > 0) {
95
+ char *printed = cJSON_PrintUnformatted(data);
96
+ if (!printed) { cJSON_Delete(data); return NULL; }
97
+ size_t n = strlen(printed);
98
+ free(printed);
99
+ if (n > st->audit_opts.max_bytes) {
100
+ cJSON_Delete(data);
101
+ data = cJSON_CreateObject();
102
+ if (!data) return NULL;
103
+ if (tf_json_add_bool(data, "_audit_truncated", 1) != TF_OK ||
104
+ tf_json_add_number(data, "max_bytes", (double)st->audit_opts.max_bytes) != TF_OK) {
105
+ cJSON_Delete(data);
106
+ return NULL;
107
+ }
108
+ }
109
+ }
110
+ return data;
111
+ }
112
+
113
+ static int tee_resolve_columns(tee_state *st, const tf_batch *in) {
114
+ if (st->resolved) return TF_OK;
115
+ free(st->indices);
116
+ st->indices = NULL;
117
+ st->n_indices = 0;
118
+
119
+ if (st->n_selectors == 0) {
120
+ st->indices = malloc((in->n_cols ? in->n_cols : 1) * sizeof(int));
121
+ if (!st->indices) return TF_ERROR;
122
+ for (size_t i = 0; i < in->n_cols; i++) st->indices[i] = (int)i;
123
+ st->n_indices = in->n_cols;
124
+ st->resolved = 1;
125
+ return TF_OK;
126
+ }
127
+
128
+ char *error = NULL;
129
+ if (tf_column_selectors_resolve(st->selectors, st->n_selectors,
130
+ in->col_names, in->col_types, in->n_cols,
131
+ &st->indices, &st->n_indices, &error) != TF_OK) {
132
+ char msg[512];
133
+ snprintf(msg, sizeof(msg), "tee: %s", error ? error : "invalid columns");
134
+ tf_set_last_error(msg);
135
+ free(error);
136
+ return TF_ERROR;
137
+ }
138
+ free(error);
139
+ st->resolved = 1;
140
+ return TF_OK;
141
+ }
142
+
143
+ static int tee_emit_row(tee_state *st, const tf_batch *in, size_t row,
144
+ tf_side_channels *side) {
145
+ tf_buffer *buf = tee_side_buffer(side, st->channel);
146
+ if (!buf) return TF_OK;
147
+
148
+ cJSON *obj = cJSON_CreateObject();
149
+ if (!obj) return TF_ERROR;
150
+ int rc = TF_ERROR;
151
+ if (tf_json_add_string(obj, "type", "tee") != TF_OK ||
152
+ tf_json_add_string(obj, "op", "tee") != TF_OK ||
153
+ tf_json_add_string(obj, "name", st->name ? st->name : "tee") != TF_OK ||
154
+ tf_json_add_string(obj, "channel", st->channel_name ? st->channel_name : "samples") != TF_OK) {
155
+ goto done;
156
+ }
157
+ if (st->expr_text && st->expr_text[0] &&
158
+ tf_json_add_string(obj, "expr", st->expr_text) != TF_OK) {
159
+ goto done;
160
+ }
161
+ if (st->include_row && tf_json_add_number(obj, "row", (double)st->row_index) != TF_OK) goto done;
162
+
163
+ if (st->audit_opts.include_row) {
164
+ cJSON *data = tee_selected_row_to_json(st, in, row);
165
+ if (!data || tf_json_add_item(obj, "data", data) != TF_OK) goto done;
166
+ }
167
+
168
+ rc = tf_buffer_write_json_line(buf, obj);
169
+ done:
170
+ cJSON_Delete(obj);
171
+ return rc;
172
+ }
173
+
174
+ static int tee_process(tf_step *self, tf_batch *in, tf_batch **out,
175
+ tf_side_channels *side) {
176
+ tee_state *st = self->state;
177
+ *out = NULL;
178
+
179
+ if (tee_resolve_columns(st, in) != TF_OK) return TF_ERROR;
180
+
181
+ tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows > 0 ? in->n_rows : 1);
182
+ if (!ob) return TF_ERROR;
183
+ if (tf_batch_clone_schema(ob, in) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
184
+
185
+ for (size_t r = 0; r < in->n_rows; r++) {
186
+ st->row_index++;
187
+ bool should_emit = true;
188
+ if (st->expr) {
189
+ if (tf_expr_eval(st->expr, in, r, &should_emit) != TF_OK) should_emit = false;
190
+ }
191
+ if (should_emit && st->every > 1 && ((st->row_index - 1) % st->every) != 0)
192
+ should_emit = false;
193
+ if (should_emit && st->emitted < st->limit) {
194
+ if (tee_emit_row(st, in, r, side) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
195
+ st->emitted++;
196
+ }
197
+ if (tf_batch_copy_row(ob, r, in, r) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
198
+ if (tf_batch_expose_row(ob, r) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
199
+ }
200
+
201
+ *out = ob;
202
+ return TF_OK;
203
+ }
204
+
205
+ static int tee_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
206
+ (void)self; (void)side;
207
+ *out = NULL;
208
+ return TF_OK;
209
+ }
210
+
211
+ static void tee_state_free(tee_state *st) {
212
+ if (!st) return;
213
+ tf_expr_free(st->expr);
214
+ free(st->expr_text);
215
+ free(st->name);
216
+ free(st->channel_name);
217
+ tf_audit_options_free(&st->audit_opts);
218
+ for (size_t i = 0; i < st->n_selectors; i++) free(st->selectors[i]);
219
+ free(st->selectors);
220
+ free(st->indices);
221
+ free(st);
222
+ }
223
+
224
+ static void tee_destroy(tf_step *self) {
225
+ if (!self) return;
226
+ tee_state_free(self->state);
227
+ free(self);
228
+ }
229
+
230
+ static int tee_copy_columns_arg(tee_state *st, const cJSON *cols) {
231
+ if (!cols) return TF_OK;
232
+ if (!cJSON_IsArray(cols)) {
233
+ tf_set_last_error("tee: columns must be an array");
234
+ return TF_ERROR;
235
+ }
236
+ int n = cJSON_GetArraySize((cJSON *)cols);
237
+ if (n <= 0) return TF_OK;
238
+ st->selectors = calloc((size_t)n, sizeof(char *));
239
+ if (!st->selectors) return TF_ERROR;
240
+ st->n_selectors = (size_t)n;
241
+ for (int i = 0; i < n; i++) {
242
+ cJSON *item = cJSON_GetArrayItem((cJSON *)cols, i);
243
+ if (!cJSON_IsString(item) || !item->valuestring[0]) {
244
+ tf_set_last_error("tee: columns must contain non-empty strings");
245
+ return TF_ERROR;
246
+ }
247
+ st->selectors[i] = strdup(item->valuestring);
248
+ if (!st->selectors[i]) return TF_ERROR;
249
+ }
250
+ return TF_OK;
251
+ }
252
+
253
+ tf_step *tf_tee_create(const cJSON *args) {
254
+ if (!args) return NULL;
255
+
256
+ tee_state *st = calloc(1, sizeof(tee_state));
257
+ if (!st) return NULL;
258
+ st->limit = 1000;
259
+ st->every = 1;
260
+ st->include_row = 1;
261
+ tf_audit_options_init(&st->audit_opts, 1);
262
+
263
+ cJSON *expr_json = cJSON_GetObjectItemCaseSensitive(args, "expr");
264
+ if (cJSON_IsString(expr_json) && expr_json->valuestring[0]) {
265
+ st->expr = tf_expr_parse(expr_json->valuestring);
266
+ if (!st->expr) { tee_state_free(st); return NULL; }
267
+ st->expr_text = strdup(expr_json->valuestring);
268
+ if (!st->expr_text) { tee_state_free(st); return NULL; }
269
+ }
270
+
271
+ cJSON *channel_json = cJSON_GetObjectItemCaseSensitive(args, "channel");
272
+ const char *channel_name = NULL;
273
+ if (!parse_channel(cJSON_IsString(channel_json) ? channel_json->valuestring : NULL,
274
+ &st->channel, &channel_name)) {
275
+ tf_set_last_error("tee: channel must be samples, errors, stats, or audit");
276
+ tee_state_free(st);
277
+ return NULL;
278
+ }
279
+ st->channel_name = strdup(channel_name);
280
+
281
+ cJSON *name_json = cJSON_GetObjectItemCaseSensitive(args, "name");
282
+ st->name = strdup(cJSON_IsString(name_json) && name_json->valuestring[0] ? name_json->valuestring : "tee");
283
+
284
+ cJSON *limit_json = cJSON_GetObjectItemCaseSensitive(args, "limit");
285
+ if (!limit_json) limit_json = cJSON_GetObjectItemCaseSensitive(args, "max_rows");
286
+ if (limit_json) {
287
+ size_t parsed_limit = 0;
288
+ if (tf_json_get_size_arg_any(args, "limit", "max_rows",
289
+ 1, TF_MAX_AUDIT_RECORDS,
290
+ &parsed_limit, "tee") < 0) {
291
+ tee_state_free(st);
292
+ return NULL;
293
+ }
294
+ st->limit = parsed_limit;
295
+ }
296
+
297
+ cJSON *every_json = cJSON_GetObjectItemCaseSensitive(args, "every");
298
+ if (every_json) {
299
+ size_t parsed_every = 0;
300
+ if (tf_json_get_size_arg(args, "every", 1, TF_MAX_COUNT_ARG,
301
+ &parsed_every, "tee") < 0) {
302
+ tee_state_free(st);
303
+ return NULL;
304
+ }
305
+ st->every = parsed_every;
306
+ }
307
+
308
+ cJSON *include_json = cJSON_GetObjectItemCaseSensitive(args, "include_row");
309
+ if (!include_json) include_json = cJSON_GetObjectItemCaseSensitive(args, "includeRow");
310
+ if (include_json) st->include_row = cJSON_IsTrue(include_json);
311
+
312
+ cJSON *columns_json = cJSON_GetObjectItemCaseSensitive(args, "columns");
313
+ if (tee_copy_columns_arg(st, columns_json) != TF_OK) {
314
+ tee_state_free(st);
315
+ return NULL;
316
+ }
317
+
318
+ if (tf_audit_options_parse(&st->audit_opts, args, "tee") != TF_OK) {
319
+ tee_state_free(st);
320
+ return NULL;
321
+ }
322
+
323
+ if (!st->name || !st->channel_name) {
324
+ tee_state_free(st);
325
+ return NULL;
326
+ }
327
+
328
+ tf_step *step = calloc(1, sizeof(tf_step));
329
+ if (!step) {
330
+ tee_state_free(st);
331
+ return NULL;
332
+ }
333
+ step->process = tee_process;
334
+ step->flush = tee_flush;
335
+ step->destroy = tee_destroy;
336
+ step->state = st;
337
+ return step;
338
+ }