tranfi 0.0.2 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +395 -0
  2. package/app/assets/index-6quYZ5Ap.css +5 -0
  3. package/app/assets/index-pDFMluyz.js +160 -0
  4. package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  5. package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  6. package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  7. package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  8. package/app/index.html +13 -0
  9. package/binding.gyp +69 -0
  10. package/csrc/arena.c +91 -0
  11. package/csrc/batch.c +229 -0
  12. package/csrc/buffer.c +78 -0
  13. package/csrc/cJSON.c +3143 -0
  14. package/csrc/cJSON.h +300 -0
  15. package/csrc/codec_csv.c +1058 -0
  16. package/csrc/codec_jsonl.c +374 -0
  17. package/csrc/codec_table.c +218 -0
  18. package/csrc/codec_text.c +229 -0
  19. package/csrc/compiler.c +102 -0
  20. package/csrc/date_utils.h +94 -0
  21. package/csrc/dsl.c +1180 -0
  22. package/csrc/dsl.h +22 -0
  23. package/csrc/expr.c +1245 -0
  24. package/csrc/expr.h +56 -0
  25. package/csrc/internal.h +250 -0
  26. package/csrc/ir.c +119 -0
  27. package/csrc/ir.h +167 -0
  28. package/csrc/ir_schema.c +60 -0
  29. package/csrc/ir_serialize.c +104 -0
  30. package/csrc/ir_sql.c +1211 -0
  31. package/csrc/ir_validate.c +120 -0
  32. package/csrc/main.c +392 -0
  33. package/csrc/op_acf.c +133 -0
  34. package/csrc/op_anomaly.c +120 -0
  35. package/csrc/op_bin.c +109 -0
  36. package/csrc/op_cast.c +195 -0
  37. package/csrc/op_clip.c +88 -0
  38. package/csrc/op_date_trunc.c +181 -0
  39. package/csrc/op_datetime.c +212 -0
  40. package/csrc/op_derive.c +248 -0
  41. package/csrc/op_diff.c +134 -0
  42. package/csrc/op_ewma.c +103 -0
  43. package/csrc/op_explode.c +108 -0
  44. package/csrc/op_fill_down.c +163 -0
  45. package/csrc/op_fill_null.c +123 -0
  46. package/csrc/op_filter.c +132 -0
  47. package/csrc/op_frequency.c +193 -0
  48. package/csrc/op_grep.c +163 -0
  49. package/csrc/op_group_agg.c +285 -0
  50. package/csrc/op_hash.c +126 -0
  51. package/csrc/op_head.c +149 -0
  52. package/csrc/op_interpolate.c +239 -0
  53. package/csrc/op_join.c +384 -0
  54. package/csrc/op_label_encode.c +144 -0
  55. package/csrc/op_lead.c +190 -0
  56. package/csrc/op_normalize.c +226 -0
  57. package/csrc/op_onehot.c +185 -0
  58. package/csrc/op_pivot.c +370 -0
  59. package/csrc/op_registry.c +1148 -0
  60. package/csrc/op_rename.c +138 -0
  61. package/csrc/op_replace.c +202 -0
  62. package/csrc/op_sample.c +101 -0
  63. package/csrc/op_select.c +140 -0
  64. package/csrc/op_skip.c +152 -0
  65. package/csrc/op_sort.c +273 -0
  66. package/csrc/op_split.c +114 -0
  67. package/csrc/op_split_data.c +87 -0
  68. package/csrc/op_stack.c +315 -0
  69. package/csrc/op_stats.c +779 -0
  70. package/csrc/op_step.c +171 -0
  71. package/csrc/op_tail.c +96 -0
  72. package/csrc/op_top.c +150 -0
  73. package/csrc/op_trim.c +109 -0
  74. package/csrc/op_unique.c +300 -0
  75. package/csrc/op_unpivot.c +159 -0
  76. package/csrc/op_validate.c +71 -0
  77. package/csrc/op_window.c +150 -0
  78. package/csrc/pipeline.c +315 -0
  79. package/csrc/plan.c +206 -0
  80. package/csrc/recipes.c +102 -0
  81. package/csrc/recipes.h +27 -0
  82. package/csrc/report.c +463 -0
  83. package/csrc/report.h +22 -0
  84. package/csrc/tranfi.h +123 -0
  85. package/csrc/wasm_api.c +157 -0
  86. package/napi_api.c +326 -0
  87. package/package.json +46 -57
  88. package/src/cli.js +193 -0
  89. package/src/engines/duckdb.js +109 -0
  90. package/src/index.js +306 -0
  91. package/src/native.js +22 -0
  92. package/src/pipeline.js +286 -0
  93. package/src/server.js +277 -0
  94. package/src/wasm.js +19 -0
  95. package/wasm/index.js +244 -0
  96. package/wasm/package.json +1 -0
  97. package/wasm/tranfi_core.js +0 -0
  98. package/LICENSE +0 -21
  99. package/dist/bundle.js +0 -1
  100. package/index.html +0 -18
  101. package/src/app.css +0 -169
  102. package/src/app.js +0 -203
  103. package/src/app.vue +0 -250
  104. package/src/bulma-input.vue +0 -110
  105. package/src/common-inputs.js +0 -28
  106. package/src/main.js +0 -20
  107. package/src/transforms.js +0 -166
  108. package/webpack.config.js +0 -108
@@ -0,0 +1,1148 @@
1
+ /*
2
+ * op_registry.c — Declarative registry of all built-in ops.
3
+ *
4
+ * Replaces the hard-coded strcmp chain in plan.c.
5
+ * Each entry describes an op's kind, capabilities, arguments,
6
+ * schema inference callback, and native constructor.
7
+ */
8
+
9
+ #include "ir.h"
10
+ #include "internal.h"
11
+ #include "cJSON.h"
12
+ #include <string.h>
13
+ #include <stdlib.h>
14
+
15
+ /* ---- Schema inference callbacks ---- */
16
+
17
+ /* Decoders: output schema unknown until runtime */
18
+ static int infer_schema_unknown(const tf_ir_node *node,
19
+ const tf_schema *in, tf_schema *out) {
20
+ (void)node; (void)in;
21
+ out->col_names = NULL;
22
+ out->col_types = NULL;
23
+ out->n_cols = 0;
24
+ out->known = false;
25
+ return TF_OK;
26
+ }
27
+
28
+ /* Encoders: consume schema, no output */
29
+ static int infer_schema_sink(const tf_ir_node *node,
30
+ const tf_schema *in, tf_schema *out) {
31
+ (void)node; (void)in;
32
+ out->col_names = NULL;
33
+ out->col_types = NULL;
34
+ out->n_cols = 0;
35
+ out->known = false;
36
+ return TF_OK;
37
+ }
38
+
39
+ /* filter, head, skip, unique: output schema = input schema */
40
+ static int infer_schema_passthrough(const tf_ir_node *node,
41
+ const tf_schema *in, tf_schema *out) {
42
+ (void)node;
43
+ if (!in->known) {
44
+ out->known = false;
45
+ out->col_names = NULL;
46
+ out->col_types = NULL;
47
+ out->n_cols = 0;
48
+ return TF_OK;
49
+ }
50
+ tf_schema_copy(out, in);
51
+ return TF_OK;
52
+ }
53
+
54
+ /* select: output schema = subset of input columns */
55
+ static int infer_schema_select(const tf_ir_node *node,
56
+ const tf_schema *in, tf_schema *out) {
57
+ if (!in->known) {
58
+ out->known = false;
59
+ out->col_names = NULL;
60
+ out->col_types = NULL;
61
+ out->n_cols = 0;
62
+ return TF_OK;
63
+ }
64
+
65
+ cJSON *cols = cJSON_GetObjectItemCaseSensitive(node->args, "columns");
66
+ if (!cols || !cJSON_IsArray(cols)) return TF_ERROR;
67
+
68
+ int n = cJSON_GetArraySize(cols);
69
+ out->col_names = calloc(n, sizeof(char *));
70
+ out->col_types = calloc(n, sizeof(tf_type));
71
+ out->n_cols = n;
72
+ out->known = true;
73
+
74
+ for (int i = 0; i < n; i++) {
75
+ cJSON *item = cJSON_GetArrayItem(cols, i);
76
+ if (!cJSON_IsString(item)) {
77
+ tf_schema_free(out);
78
+ return TF_ERROR;
79
+ }
80
+ const char *name = item->valuestring;
81
+ /* Find column in input schema */
82
+ bool found = false;
83
+ for (size_t j = 0; j < in->n_cols; j++) {
84
+ if (strcmp(in->col_names[j], name) == 0) {
85
+ out->col_names[i] = strdup(name);
86
+ out->col_types[i] = in->col_types[j];
87
+ found = true;
88
+ break;
89
+ }
90
+ }
91
+ if (!found) {
92
+ /* Column not in input — still record it, validation can catch it */
93
+ out->col_names[i] = strdup(name);
94
+ out->col_types[i] = TF_TYPE_NULL;
95
+ }
96
+ }
97
+ return TF_OK;
98
+ }
99
+
100
+ /* rename: output schema = input schema with renamed columns */
101
+ static int infer_schema_rename(const tf_ir_node *node,
102
+ const tf_schema *in, tf_schema *out) {
103
+ if (!in->known) {
104
+ out->known = false;
105
+ out->col_names = NULL;
106
+ out->col_types = NULL;
107
+ out->n_cols = 0;
108
+ return TF_OK;
109
+ }
110
+
111
+ tf_schema_copy(out, in);
112
+
113
+ cJSON *mapping = cJSON_GetObjectItemCaseSensitive(node->args, "mapping");
114
+ if (!mapping || !cJSON_IsObject(mapping)) return TF_OK; /* no renames */
115
+
116
+ cJSON *entry = NULL;
117
+ cJSON_ArrayForEach(entry, mapping) {
118
+ if (!cJSON_IsString(entry)) continue;
119
+ const char *old_name = entry->string;
120
+ const char *new_name = entry->valuestring;
121
+ for (size_t i = 0; i < out->n_cols; i++) {
122
+ if (strcmp(out->col_names[i], old_name) == 0) {
123
+ free(out->col_names[i]);
124
+ out->col_names[i] = strdup(new_name);
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ return TF_OK;
130
+ }
131
+
132
+ /* derive: output schema = input + derived columns */
133
+ static int infer_schema_derive(const tf_ir_node *node,
134
+ const tf_schema *in, tf_schema *out) {
135
+ if (!in->known) {
136
+ out->known = false;
137
+ out->col_names = NULL;
138
+ out->col_types = NULL;
139
+ out->n_cols = 0;
140
+ return TF_OK;
141
+ }
142
+
143
+ cJSON *columns = cJSON_GetObjectItemCaseSensitive(node->args, "columns");
144
+ int n_derived = columns ? cJSON_GetArraySize(columns) : 0;
145
+ size_t total = in->n_cols + n_derived;
146
+
147
+ out->col_names = calloc(total, sizeof(char *));
148
+ out->col_types = calloc(total, sizeof(tf_type));
149
+ out->n_cols = total;
150
+ out->known = true;
151
+
152
+ /* Copy input columns */
153
+ for (size_t i = 0; i < in->n_cols; i++) {
154
+ out->col_names[i] = strdup(in->col_names[i]);
155
+ out->col_types[i] = in->col_types[i];
156
+ }
157
+
158
+ /* Add derived columns (type unknown at compile time) */
159
+ for (int i = 0; i < n_derived; i++) {
160
+ cJSON *item = cJSON_GetArrayItem(columns, i);
161
+ cJSON *name_j = cJSON_GetObjectItemCaseSensitive(item, "name");
162
+ out->col_names[in->n_cols + i] = strdup(name_j ? name_j->valuestring : "?");
163
+ out->col_types[in->n_cols + i] = TF_TYPE_NULL;
164
+ }
165
+ return TF_OK;
166
+ }
167
+
168
+ /* validate: input + _valid bool column */
169
+ static int infer_schema_validate(const tf_ir_node *node,
170
+ const tf_schema *in, tf_schema *out) {
171
+ (void)node;
172
+ if (!in->known) { out->known = false; out->col_names = NULL; out->col_types = NULL; out->n_cols = 0; return TF_OK; }
173
+ out->n_cols = in->n_cols + 1;
174
+ out->col_names = calloc(out->n_cols, sizeof(char *));
175
+ out->col_types = calloc(out->n_cols, sizeof(tf_type));
176
+ out->known = true;
177
+ for (size_t i = 0; i < in->n_cols; i++) {
178
+ out->col_names[i] = strdup(in->col_names[i]);
179
+ out->col_types[i] = in->col_types[i];
180
+ }
181
+ out->col_names[in->n_cols] = strdup("_valid");
182
+ out->col_types[in->n_cols] = TF_TYPE_BOOL;
183
+ return TF_OK;
184
+ }
185
+
186
+ /* hash: input + _hash int column */
187
+ static int infer_schema_add_hash(const tf_ir_node *node,
188
+ const tf_schema *in, tf_schema *out) {
189
+ (void)node;
190
+ if (!in->known) { out->known = false; out->col_names = NULL; out->col_types = NULL; out->n_cols = 0; return TF_OK; }
191
+ out->n_cols = in->n_cols + 1;
192
+ out->col_names = calloc(out->n_cols, sizeof(char *));
193
+ out->col_types = calloc(out->n_cols, sizeof(tf_type));
194
+ out->known = true;
195
+ for (size_t i = 0; i < in->n_cols; i++) {
196
+ out->col_names[i] = strdup(in->col_names[i]);
197
+ out->col_types[i] = in->col_types[i];
198
+ }
199
+ out->col_names[in->n_cols] = strdup("_hash");
200
+ out->col_types[in->n_cols] = TF_TYPE_INT64;
201
+ return TF_OK;
202
+ }
203
+
204
+ /* frequency: output is value + count */
205
+ static int infer_schema_frequency(const tf_ir_node *node,
206
+ const tf_schema *in, tf_schema *out) {
207
+ (void)node; (void)in;
208
+ out->n_cols = 2;
209
+ out->col_names = calloc(2, sizeof(char *));
210
+ out->col_types = calloc(2, sizeof(tf_type));
211
+ out->known = true;
212
+ out->col_names[0] = strdup("value");
213
+ out->col_types[0] = TF_TYPE_STRING;
214
+ out->col_names[1] = strdup("count");
215
+ out->col_types[1] = TF_TYPE_INT64;
216
+ return TF_OK;
217
+ }
218
+
219
+ /* group-agg: group columns (string) + agg columns (float64) */
220
+ static int infer_schema_group_agg(const tf_ir_node *node,
221
+ const tf_schema *in, tf_schema *out) {
222
+ (void)in;
223
+ cJSON *group_by = cJSON_GetObjectItemCaseSensitive(node->args, "group_by");
224
+ cJSON *aggs = cJSON_GetObjectItemCaseSensitive(node->args, "aggs");
225
+ int ng = group_by ? cJSON_GetArraySize(group_by) : 0;
226
+ int na = aggs ? cJSON_GetArraySize(aggs) : 0;
227
+ out->n_cols = ng + na;
228
+ out->col_names = calloc(out->n_cols, sizeof(char *));
229
+ out->col_types = calloc(out->n_cols, sizeof(tf_type));
230
+ out->known = true;
231
+ for (int i = 0; i < ng; i++) {
232
+ cJSON *item = cJSON_GetArrayItem(group_by, i);
233
+ out->col_names[i] = strdup(cJSON_IsString(item) ? item->valuestring : "?");
234
+ out->col_types[i] = TF_TYPE_STRING;
235
+ }
236
+ for (int i = 0; i < na; i++) {
237
+ cJSON *item = cJSON_GetArrayItem(aggs, i);
238
+ cJSON *name_j = cJSON_GetObjectItemCaseSensitive(item, "name");
239
+ out->col_names[ng + i] = strdup(name_j && cJSON_IsString(name_j) ? name_j->valuestring : "?");
240
+ out->col_types[ng + i] = TF_TYPE_FLOAT64;
241
+ }
242
+ return TF_OK;
243
+ }
244
+
245
+ /* stats: output schema is known: column, count, sum, avg, min, max */
246
+ static int infer_schema_stats(const tf_ir_node *node,
247
+ const tf_schema *in, tf_schema *out) {
248
+ (void)in;
249
+ /* Determine which stats are requested */
250
+ int w_count=0, w_sum=0, w_avg=0, w_min=0, w_max=0;
251
+ int w_var=0, w_stddev=0, w_median=0, w_p25=0, w_p75=0;
252
+ int w_skewness=0, w_kurtosis=0, w_distinct=0, w_hist=0, w_sample=0;
253
+ cJSON *stats_arr = cJSON_GetObjectItemCaseSensitive(node->args, "stats");
254
+ if (stats_arr && cJSON_IsArray(stats_arr)) {
255
+ int n = cJSON_GetArraySize(stats_arr);
256
+ for (int i = 0; i < n; i++) {
257
+ cJSON *item = cJSON_GetArrayItem(stats_arr, i);
258
+ if (!cJSON_IsString(item)) continue;
259
+ const char *s = item->valuestring;
260
+ if (strcmp(s, "count") == 0) w_count = 1;
261
+ else if (strcmp(s, "sum") == 0) w_sum = 1;
262
+ else if (strcmp(s, "avg") == 0) w_avg = 1;
263
+ else if (strcmp(s, "min") == 0) w_min = 1;
264
+ else if (strcmp(s, "max") == 0) w_max = 1;
265
+ else if (strcmp(s, "var") == 0) w_var = 1;
266
+ else if (strcmp(s, "stddev") == 0) w_stddev = 1;
267
+ else if (strcmp(s, "median") == 0) w_median = 1;
268
+ else if (strcmp(s, "p25") == 0) w_p25 = 1;
269
+ else if (strcmp(s, "p75") == 0) w_p75 = 1;
270
+ else if (strcmp(s, "skewness") == 0) w_skewness = 1;
271
+ else if (strcmp(s, "kurtosis") == 0) w_kurtosis = 1;
272
+ else if (strcmp(s, "distinct") == 0) w_distinct = 1;
273
+ else if (strcmp(s, "hist") == 0) w_hist = 1;
274
+ else if (strcmp(s, "sample") == 0) w_sample = 1;
275
+ }
276
+ } else {
277
+ /* Default: basic + variance + median */
278
+ w_count = w_sum = w_avg = w_min = w_max = 1;
279
+ w_var = w_stddev = w_median = 1;
280
+ }
281
+
282
+ size_t n_cols = 1; /* column */
283
+ if (w_count) n_cols++;
284
+ if (w_sum) n_cols++;
285
+ if (w_avg) n_cols++;
286
+ if (w_min) n_cols++;
287
+ if (w_max) n_cols++;
288
+ if (w_var) n_cols++;
289
+ if (w_stddev) n_cols++;
290
+ if (w_median) n_cols++;
291
+ if (w_p25) n_cols++;
292
+ if (w_p75) n_cols++;
293
+ if (w_skewness) n_cols++;
294
+ if (w_kurtosis) n_cols++;
295
+ if (w_distinct) n_cols++;
296
+ if (w_hist) n_cols++;
297
+ if (w_sample) n_cols++;
298
+
299
+ out->col_names = calloc(n_cols, sizeof(char *));
300
+ out->col_types = calloc(n_cols, sizeof(tf_type));
301
+ out->n_cols = n_cols;
302
+ out->known = true;
303
+
304
+ size_t ci = 0;
305
+ out->col_names[ci] = strdup("column"); out->col_types[ci++] = TF_TYPE_STRING;
306
+ if (w_count) { out->col_names[ci] = strdup("count"); out->col_types[ci++] = TF_TYPE_INT64; }
307
+ if (w_sum) { out->col_names[ci] = strdup("sum"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
308
+ if (w_avg) { out->col_names[ci] = strdup("avg"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
309
+ if (w_min) { out->col_names[ci] = strdup("min"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
310
+ if (w_max) { out->col_names[ci] = strdup("max"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
311
+ if (w_var) { out->col_names[ci] = strdup("var"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
312
+ if (w_stddev) { out->col_names[ci] = strdup("stddev"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
313
+ if (w_median) { out->col_names[ci] = strdup("median"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
314
+ if (w_p25) { out->col_names[ci] = strdup("p25"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
315
+ if (w_p75) { out->col_names[ci] = strdup("p75"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
316
+ if (w_skewness) { out->col_names[ci] = strdup("skewness"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
317
+ if (w_kurtosis) { out->col_names[ci] = strdup("kurtosis"); out->col_types[ci++] = TF_TYPE_FLOAT64; }
318
+ if (w_distinct) { out->col_names[ci] = strdup("distinct"); out->col_types[ci++] = TF_TYPE_INT64; }
319
+ if (w_hist) { out->col_names[ci] = strdup("hist"); out->col_types[ci++] = TF_TYPE_STRING; }
320
+ if (w_sample) { out->col_names[ci] = strdup("sample"); out->col_types[ci++] = TF_TYPE_STRING; }
321
+ return TF_OK;
322
+ }
323
+
324
+ /* ---- Built-in ops table ---- */
325
+
326
+ static tf_arg_desc csv_decode_args[] = {
327
+ {"delimiter", "string", false, "\",\""},
328
+ {"header", "bool", false, "true"},
329
+ {"batch_size","int", false, "1024"},
330
+ {"repair", "bool", false, "false"},
331
+ };
332
+
333
+ static tf_arg_desc csv_encode_args[] = {
334
+ {"delimiter", "string", false, "\",\""},
335
+ {"header", "bool", false, "true"},
336
+ };
337
+
338
+ static tf_arg_desc jsonl_decode_args[] = {
339
+ {"batch_size", "int", false, "1024"},
340
+ };
341
+
342
+ static tf_arg_desc jsonl_encode_args[] = {
343
+ {0} /* no args */
344
+ };
345
+
346
+ static tf_arg_desc text_decode_args[] = {
347
+ {"batch_size", "int", false, "1024"},
348
+ };
349
+
350
+ static tf_arg_desc text_encode_args[] = {
351
+ {0} /* no args */
352
+ };
353
+
354
+ static tf_arg_desc grep_args[] = {
355
+ {"pattern", "string", true, NULL},
356
+ {"invert", "bool", false, "false"},
357
+ {"column", "string", false, "\"_line\""},
358
+ {"regex", "bool", false, "false"},
359
+ };
360
+
361
+ static tf_arg_desc filter_args[] = {
362
+ {"expr", "string", true, NULL},
363
+ };
364
+
365
+ static tf_arg_desc select_args[] = {
366
+ {"columns", "string[]", true, NULL},
367
+ };
368
+
369
+ static tf_arg_desc rename_args[] = {
370
+ {"mapping", "map", true, NULL},
371
+ };
372
+
373
+ static tf_arg_desc head_args[] = {
374
+ {"n", "int", true, NULL},
375
+ };
376
+
377
+ static tf_arg_desc skip_args[] = {
378
+ {"n", "int", true, NULL},
379
+ };
380
+
381
+ static tf_arg_desc derive_args[] = {
382
+ {"columns", "map[]", true, NULL},
383
+ };
384
+
385
+ static tf_arg_desc stats_args[] = {
386
+ {"stats", "string[]", false, NULL},
387
+ };
388
+
389
+ static tf_arg_desc unique_args[] = {
390
+ {"columns", "string[]", false, NULL},
391
+ };
392
+
393
+ static tf_arg_desc sort_args[] = {
394
+ {"columns", "map[]", true, NULL},
395
+ };
396
+
397
+ static tf_arg_desc validate_args[] = {
398
+ {"expr", "string", true, NULL},
399
+ };
400
+
401
+ static tf_arg_desc trim_args[] = {
402
+ {"columns", "string[]", false, NULL},
403
+ };
404
+
405
+ static tf_arg_desc fill_null_args[] = {
406
+ {"mapping", "map", true, NULL},
407
+ };
408
+
409
+ static tf_arg_desc cast_args[] = {
410
+ {"mapping", "map", true, NULL},
411
+ };
412
+
413
+ static tf_arg_desc clip_args[] = {
414
+ {"column", "string", true, NULL},
415
+ {"min", "float", false, NULL},
416
+ {"max", "float", false, NULL},
417
+ };
418
+
419
+ static tf_arg_desc replace_args[] = {
420
+ {"column", "string", true, NULL},
421
+ {"pattern", "string", true, NULL},
422
+ {"replacement", "string", true, NULL},
423
+ {"regex", "bool", false, "false"},
424
+ };
425
+
426
+ static tf_arg_desc hash_args[] = {
427
+ {"columns", "string[]", false, NULL},
428
+ };
429
+
430
+ static tf_arg_desc bin_args[] = {
431
+ {"column", "string", true, NULL},
432
+ {"boundaries", "float[]", true, NULL},
433
+ };
434
+
435
+ static tf_arg_desc fill_down_args[] = {
436
+ {"columns", "string[]", false, NULL},
437
+ };
438
+
439
+ static tf_arg_desc step_args[] = {
440
+ {"column", "string", true, NULL},
441
+ {"func", "string", true, NULL},
442
+ {"result", "string", false, NULL},
443
+ };
444
+
445
+ static tf_arg_desc window_args[] = {
446
+ {"column", "string", true, NULL},
447
+ {"size", "int", true, NULL},
448
+ {"func", "string", true, NULL},
449
+ {"result", "string", false, NULL},
450
+ };
451
+
452
+ static tf_arg_desc explode_args[] = {
453
+ {"column", "string", true, NULL},
454
+ {"delimiter", "string", false, "\",\""},
455
+ };
456
+
457
+ static tf_arg_desc split_args[] = {
458
+ {"column", "string", true, NULL},
459
+ {"delimiter", "string", false, "\" \""},
460
+ {"names", "string[]", true, NULL},
461
+ };
462
+
463
+ static tf_arg_desc unpivot_args[] = {
464
+ {"columns", "string[]", true, NULL},
465
+ };
466
+
467
+ static tf_arg_desc tail_args[] = {
468
+ {"n", "int", true, NULL},
469
+ };
470
+
471
+ static tf_arg_desc top_args[] = {
472
+ {"n", "int", true, NULL},
473
+ {"column", "string", true, NULL},
474
+ {"desc", "bool", false, "true"},
475
+ };
476
+
477
+ static tf_arg_desc sample_args[] = {
478
+ {"n", "int", true, NULL},
479
+ };
480
+
481
+ static tf_arg_desc group_agg_args[] = {
482
+ {"group_by", "string[]", true, NULL},
483
+ {"aggs", "map[]", true, NULL},
484
+ };
485
+
486
+ static tf_arg_desc frequency_args[] = {
487
+ {"columns", "string[]", false, NULL},
488
+ };
489
+
490
+ static tf_arg_desc datetime_args[] = {
491
+ {"column", "string", true, NULL},
492
+ {"extract", "string[]", false, NULL},
493
+ };
494
+
495
+ static tf_arg_desc pivot_args[] = {
496
+ {"name_column", "string", true, NULL},
497
+ {"value_column", "string", true, NULL},
498
+ {"agg", "string", false, "\"first\""},
499
+ };
500
+
501
+ static tf_arg_desc join_args[] = {
502
+ {"file", "string", true, NULL},
503
+ {"on", "string", true, NULL},
504
+ {"how", "string", false, "\"inner\""},
505
+ };
506
+
507
+ static tf_arg_desc stack_args[] = {
508
+ {"file", "string", true, NULL},
509
+ {"tag", "string", false, NULL},
510
+ {"tag_value", "string", false, NULL},
511
+ };
512
+
513
+ static tf_arg_desc lead_args[] = {
514
+ {"column", "string", true, NULL},
515
+ {"offset", "int", false, "1"},
516
+ {"result", "string", false, NULL},
517
+ };
518
+
519
+ static tf_arg_desc date_trunc_args[] = {
520
+ {"column", "string", true, NULL},
521
+ {"trunc", "string", true, NULL},
522
+ {"result", "string", false, NULL},
523
+ };
524
+
525
+ static tf_arg_desc onehot_args[] = {
526
+ {"column", "string", true, NULL},
527
+ {"drop", "bool", false, "false"},
528
+ };
529
+
530
+ static tf_arg_desc label_encode_args[] = {
531
+ {"column", "string", true, NULL},
532
+ {"result", "string", false, NULL},
533
+ };
534
+
535
+ static tf_arg_desc ewma_args[] = {
536
+ {"column", "string", true, NULL},
537
+ {"alpha", "float", true, NULL},
538
+ {"result", "string", false, NULL},
539
+ };
540
+
541
+ static tf_arg_desc diff_args[] = {
542
+ {"column", "string", true, NULL},
543
+ {"order", "int", false, "1"},
544
+ {"result", "string", false, NULL},
545
+ };
546
+
547
+ static tf_arg_desc anomaly_args[] = {
548
+ {"column", "string", true, NULL},
549
+ {"threshold", "float", false, "3.0"},
550
+ {"result", "string", false, NULL},
551
+ };
552
+
553
+ static tf_arg_desc split_data_args[] = {
554
+ {"ratio", "float", false, "0.8"},
555
+ {"result", "string", false, "\"_split\""},
556
+ {"seed", "int", false, "42"},
557
+ };
558
+
559
+ static tf_arg_desc interpolate_args[] = {
560
+ {"column", "string", true, NULL},
561
+ {"method", "string", false, "\"linear\""},
562
+ };
563
+
564
+ static tf_arg_desc normalize_args[] = {
565
+ {"columns", "string[]", true, NULL},
566
+ {"method", "string", false, "\"minmax\""},
567
+ };
568
+
569
+ static tf_arg_desc acf_args[] = {
570
+ {"column", "string", true, NULL},
571
+ {"lags", "int", false, "20"},
572
+ };
573
+
574
+ static tf_arg_desc table_encode_args[] = {
575
+ {"max_width", "int", false, "40"},
576
+ {"max_rows", "int", false, "0"},
577
+ };
578
+
579
+ static tf_op_entry builtin_ops[] = {
580
+ {
581
+ .name = "codec.csv.decode",
582
+ .kind = TF_OP_DECODER,
583
+ .tier = TF_TIER_CORE,
584
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
585
+ .args = csv_decode_args,
586
+ .n_args = 4,
587
+ .infer_schema = infer_schema_unknown,
588
+ .create_native = (void *(*)(const cJSON *))tf_csv_decoder_create,
589
+ },
590
+ {
591
+ .name = "codec.csv.encode",
592
+ .kind = TF_OP_ENCODER,
593
+ .tier = TF_TIER_CORE,
594
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
595
+ .args = csv_encode_args,
596
+ .n_args = 2,
597
+ .infer_schema = infer_schema_sink,
598
+ .create_native = (void *(*)(const cJSON *))tf_csv_encoder_create,
599
+ },
600
+ {
601
+ .name = "codec.jsonl.decode",
602
+ .kind = TF_OP_DECODER,
603
+ .tier = TF_TIER_CORE,
604
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
605
+ .args = jsonl_decode_args,
606
+ .n_args = 1,
607
+ .infer_schema = infer_schema_unknown,
608
+ .create_native = (void *(*)(const cJSON *))tf_jsonl_decoder_create,
609
+ },
610
+ {
611
+ .name = "codec.jsonl.encode",
612
+ .kind = TF_OP_ENCODER,
613
+ .tier = TF_TIER_CORE,
614
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
615
+ .args = jsonl_encode_args,
616
+ .n_args = 0,
617
+ .infer_schema = infer_schema_sink,
618
+ .create_native = (void *(*)(const cJSON *))tf_jsonl_encoder_create,
619
+ },
620
+ {
621
+ .name = "codec.text.decode",
622
+ .kind = TF_OP_DECODER,
623
+ .tier = TF_TIER_CORE,
624
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
625
+ .args = text_decode_args,
626
+ .n_args = 1,
627
+ .infer_schema = infer_schema_unknown,
628
+ .create_native = (void *(*)(const cJSON *))tf_text_decoder_create,
629
+ },
630
+ {
631
+ .name = "codec.text.encode",
632
+ .kind = TF_OP_ENCODER,
633
+ .tier = TF_TIER_CORE,
634
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
635
+ .args = text_encode_args,
636
+ .n_args = 0,
637
+ .infer_schema = infer_schema_sink,
638
+ .create_native = (void *(*)(const cJSON *))tf_text_encoder_create,
639
+ },
640
+ {
641
+ .name = "grep",
642
+ .kind = TF_OP_TRANSFORM,
643
+ .tier = TF_TIER_CORE,
644
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
645
+ .args = grep_args,
646
+ .n_args = 4,
647
+ .infer_schema = infer_schema_passthrough,
648
+ .create_native = (void *(*)(const cJSON *))tf_grep_create,
649
+ },
650
+ {
651
+ .name = "filter",
652
+ .kind = TF_OP_TRANSFORM,
653
+ .tier = TF_TIER_CORE,
654
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
655
+ .args = filter_args,
656
+ .n_args = 1,
657
+ .infer_schema = infer_schema_passthrough,
658
+ .create_native = (void *(*)(const cJSON *))tf_filter_create,
659
+ },
660
+ {
661
+ .name = "select",
662
+ .kind = TF_OP_TRANSFORM,
663
+ .tier = TF_TIER_CORE,
664
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
665
+ .args = select_args,
666
+ .n_args = 1,
667
+ .infer_schema = infer_schema_select,
668
+ .create_native = (void *(*)(const cJSON *))tf_select_create,
669
+ },
670
+ {
671
+ .name = "rename",
672
+ .kind = TF_OP_TRANSFORM,
673
+ .tier = TF_TIER_CORE,
674
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
675
+ .args = rename_args,
676
+ .n_args = 1,
677
+ .infer_schema = infer_schema_rename,
678
+ .create_native = (void *(*)(const cJSON *))tf_rename_create,
679
+ },
680
+ {
681
+ .name = "head",
682
+ .kind = TF_OP_TRANSFORM,
683
+ .tier = TF_TIER_CORE,
684
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
685
+ .args = head_args,
686
+ .n_args = 1,
687
+ .infer_schema = infer_schema_passthrough,
688
+ .create_native = (void *(*)(const cJSON *))tf_head_create,
689
+ },
690
+ {
691
+ .name = "skip",
692
+ .kind = TF_OP_TRANSFORM,
693
+ .tier = TF_TIER_CORE,
694
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
695
+ .args = skip_args,
696
+ .n_args = 1,
697
+ .infer_schema = infer_schema_passthrough,
698
+ .create_native = (void *(*)(const cJSON *))tf_skip_create,
699
+ },
700
+ {
701
+ .name = "derive",
702
+ .kind = TF_OP_TRANSFORM,
703
+ .tier = TF_TIER_CORE,
704
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
705
+ .args = derive_args,
706
+ .n_args = 1,
707
+ .infer_schema = infer_schema_derive,
708
+ .create_native = (void *(*)(const cJSON *))tf_derive_create,
709
+ },
710
+ {
711
+ .name = "stats",
712
+ .kind = TF_OP_TRANSFORM,
713
+ .tier = TF_TIER_CORE,
714
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
715
+ .args = stats_args,
716
+ .n_args = 1,
717
+ .infer_schema = infer_schema_stats,
718
+ .create_native = (void *(*)(const cJSON *))tf_stats_create,
719
+ },
720
+ {
721
+ .name = "unique",
722
+ .kind = TF_OP_TRANSFORM,
723
+ .tier = TF_TIER_CORE,
724
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
725
+ .args = unique_args,
726
+ .n_args = 1,
727
+ .infer_schema = infer_schema_passthrough,
728
+ .create_native = (void *(*)(const cJSON *))tf_unique_create,
729
+ },
730
+ {
731
+ .name = "sort",
732
+ .kind = TF_OP_TRANSFORM,
733
+ .tier = TF_TIER_CORE,
734
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
735
+ .args = sort_args,
736
+ .n_args = 1,
737
+ .infer_schema = infer_schema_passthrough,
738
+ .create_native = (void *(*)(const cJSON *))tf_sort_create,
739
+ },
740
+ /* ---- Aliases ---- */
741
+ {
742
+ .name = "reorder",
743
+ .kind = TF_OP_TRANSFORM,
744
+ .tier = TF_TIER_CORE,
745
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
746
+ .args = select_args,
747
+ .n_args = 1,
748
+ .infer_schema = infer_schema_select,
749
+ .create_native = (void *(*)(const cJSON *))tf_select_create,
750
+ },
751
+ {
752
+ .name = "dedup",
753
+ .kind = TF_OP_TRANSFORM,
754
+ .tier = TF_TIER_CORE,
755
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
756
+ .args = unique_args,
757
+ .n_args = 1,
758
+ .infer_schema = infer_schema_passthrough,
759
+ .create_native = (void *(*)(const cJSON *))tf_unique_create,
760
+ },
761
+ /* ---- Simple streaming ---- */
762
+ {
763
+ .name = "validate",
764
+ .kind = TF_OP_TRANSFORM,
765
+ .tier = TF_TIER_CORE,
766
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
767
+ .args = validate_args,
768
+ .n_args = 1,
769
+ .infer_schema = infer_schema_validate,
770
+ .create_native = (void *(*)(const cJSON *))tf_validate_create,
771
+ },
772
+ {
773
+ .name = "trim",
774
+ .kind = TF_OP_TRANSFORM,
775
+ .tier = TF_TIER_CORE,
776
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
777
+ .args = trim_args,
778
+ .n_args = 1,
779
+ .infer_schema = infer_schema_passthrough,
780
+ .create_native = (void *(*)(const cJSON *))tf_trim_create,
781
+ },
782
+ {
783
+ .name = "fill-null",
784
+ .kind = TF_OP_TRANSFORM,
785
+ .tier = TF_TIER_CORE,
786
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
787
+ .args = fill_null_args,
788
+ .n_args = 1,
789
+ .infer_schema = infer_schema_passthrough,
790
+ .create_native = (void *(*)(const cJSON *))tf_fill_null_create,
791
+ },
792
+ {
793
+ .name = "cast",
794
+ .kind = TF_OP_TRANSFORM,
795
+ .tier = TF_TIER_CORE,
796
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
797
+ .args = cast_args,
798
+ .n_args = 1,
799
+ .infer_schema = infer_schema_passthrough, /* type changes at runtime */
800
+ .create_native = (void *(*)(const cJSON *))tf_cast_create,
801
+ },
802
+ {
803
+ .name = "clip",
804
+ .kind = TF_OP_TRANSFORM,
805
+ .tier = TF_TIER_CORE,
806
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
807
+ .args = clip_args,
808
+ .n_args = 3,
809
+ .infer_schema = infer_schema_passthrough,
810
+ .create_native = (void *(*)(const cJSON *))tf_clip_create,
811
+ },
812
+ {
813
+ .name = "replace",
814
+ .kind = TF_OP_TRANSFORM,
815
+ .tier = TF_TIER_CORE,
816
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
817
+ .args = replace_args,
818
+ .n_args = 4,
819
+ .infer_schema = infer_schema_passthrough,
820
+ .create_native = (void *(*)(const cJSON *))tf_replace_create,
821
+ },
822
+ {
823
+ .name = "hash",
824
+ .kind = TF_OP_TRANSFORM,
825
+ .tier = TF_TIER_CORE,
826
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
827
+ .args = hash_args,
828
+ .n_args = 1,
829
+ .infer_schema = infer_schema_add_hash,
830
+ .create_native = (void *(*)(const cJSON *))tf_hash_create,
831
+ },
832
+ {
833
+ .name = "bin",
834
+ .kind = TF_OP_TRANSFORM,
835
+ .tier = TF_TIER_CORE,
836
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
837
+ .args = bin_args,
838
+ .n_args = 2,
839
+ .infer_schema = infer_schema_passthrough, /* adds column at runtime */
840
+ .create_native = (void *(*)(const cJSON *))tf_bin_create,
841
+ },
842
+ /* ---- Stateful streaming ---- */
843
+ {
844
+ .name = "fill-down",
845
+ .kind = TF_OP_TRANSFORM,
846
+ .tier = TF_TIER_CORE,
847
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
848
+ .args = fill_down_args,
849
+ .n_args = 1,
850
+ .infer_schema = infer_schema_passthrough,
851
+ .create_native = (void *(*)(const cJSON *))tf_fill_down_create,
852
+ },
853
+ {
854
+ .name = "step",
855
+ .kind = TF_OP_TRANSFORM,
856
+ .tier = TF_TIER_CORE,
857
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
858
+ .args = step_args,
859
+ .n_args = 3,
860
+ .infer_schema = infer_schema_passthrough, /* adds column at runtime */
861
+ .create_native = (void *(*)(const cJSON *))tf_step_create,
862
+ },
863
+ {
864
+ .name = "window",
865
+ .kind = TF_OP_TRANSFORM,
866
+ .tier = TF_TIER_CORE,
867
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
868
+ .args = window_args,
869
+ .n_args = 4,
870
+ .infer_schema = infer_schema_passthrough, /* adds column at runtime */
871
+ .create_native = (void *(*)(const cJSON *))tf_window_create,
872
+ },
873
+ /* ---- Row-multiplying streaming ---- */
874
+ {
875
+ .name = "explode",
876
+ .kind = TF_OP_TRANSFORM,
877
+ .tier = TF_TIER_CORE,
878
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
879
+ .args = explode_args,
880
+ .n_args = 2,
881
+ .infer_schema = infer_schema_passthrough,
882
+ .create_native = (void *(*)(const cJSON *))tf_explode_create,
883
+ },
884
+ {
885
+ .name = "split",
886
+ .kind = TF_OP_TRANSFORM,
887
+ .tier = TF_TIER_CORE,
888
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
889
+ .args = split_args,
890
+ .n_args = 3,
891
+ .infer_schema = infer_schema_passthrough, /* adds columns at runtime */
892
+ .create_native = (void *(*)(const cJSON *))tf_split_create,
893
+ },
894
+ {
895
+ .name = "unpivot",
896
+ .kind = TF_OP_TRANSFORM,
897
+ .tier = TF_TIER_CORE,
898
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
899
+ .args = unpivot_args,
900
+ .n_args = 1,
901
+ .infer_schema = infer_schema_passthrough, /* schema changes at runtime */
902
+ .create_native = (void *(*)(const cJSON *))tf_unpivot_create,
903
+ },
904
+ /* ---- Buffering ---- */
905
+ {
906
+ .name = "tail",
907
+ .kind = TF_OP_TRANSFORM,
908
+ .tier = TF_TIER_CORE,
909
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
910
+ .args = tail_args,
911
+ .n_args = 1,
912
+ .infer_schema = infer_schema_passthrough,
913
+ .create_native = (void *(*)(const cJSON *))tf_tail_create,
914
+ },
915
+ {
916
+ .name = "top",
917
+ .kind = TF_OP_TRANSFORM,
918
+ .tier = TF_TIER_CORE,
919
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
920
+ .args = top_args,
921
+ .n_args = 3,
922
+ .infer_schema = infer_schema_passthrough,
923
+ .create_native = (void *(*)(const cJSON *))tf_top_create,
924
+ },
925
+ {
926
+ .name = "sample",
927
+ .kind = TF_OP_TRANSFORM,
928
+ .tier = TF_TIER_CORE,
929
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE,
930
+ .args = sample_args,
931
+ .n_args = 1,
932
+ .infer_schema = infer_schema_passthrough,
933
+ .create_native = (void *(*)(const cJSON *))tf_sample_create,
934
+ },
935
+ {
936
+ .name = "group-agg",
937
+ .kind = TF_OP_TRANSFORM,
938
+ .tier = TF_TIER_CORE,
939
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
940
+ .args = group_agg_args,
941
+ .n_args = 2,
942
+ .infer_schema = infer_schema_group_agg,
943
+ .create_native = (void *(*)(const cJSON *))tf_group_agg_create,
944
+ },
945
+ {
946
+ .name = "frequency",
947
+ .kind = TF_OP_TRANSFORM,
948
+ .tier = TF_TIER_CORE,
949
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
950
+ .args = frequency_args,
951
+ .n_args = 1,
952
+ .infer_schema = infer_schema_frequency,
953
+ .create_native = (void *(*)(const cJSON *))tf_frequency_create,
954
+ },
955
+ /* ---- Complex ---- */
956
+ {
957
+ .name = "datetime",
958
+ .kind = TF_OP_TRANSFORM,
959
+ .tier = TF_TIER_CORE,
960
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
961
+ .args = datetime_args,
962
+ .n_args = 2,
963
+ .infer_schema = infer_schema_passthrough, /* adds columns at runtime */
964
+ .create_native = (void *(*)(const cJSON *))tf_datetime_create,
965
+ },
966
+ {
967
+ .name = "flatten",
968
+ .kind = TF_OP_TRANSFORM,
969
+ .tier = TF_TIER_CORE,
970
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
971
+ .args = NULL,
972
+ .n_args = 0,
973
+ .infer_schema = infer_schema_passthrough,
974
+ .create_native = NULL, /* passthrough — no-op at runtime */
975
+ },
976
+ /* ---- Pivot / Join ---- */
977
+ {
978
+ .name = "pivot",
979
+ .kind = TF_OP_TRANSFORM,
980
+ .tier = TF_TIER_CORE,
981
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
982
+ .args = pivot_args,
983
+ .n_args = 3,
984
+ .infer_schema = infer_schema_passthrough, /* schema changes at runtime */
985
+ .create_native = (void *(*)(const cJSON *))tf_pivot_create,
986
+ },
987
+ {
988
+ .name = "join",
989
+ .kind = TF_OP_TRANSFORM,
990
+ .tier = TF_TIER_CORE,
991
+ .caps = TF_CAP_STREAMING | TF_CAP_FS | TF_CAP_DETERMINISTIC,
992
+ .args = join_args,
993
+ .n_args = 3,
994
+ .infer_schema = infer_schema_passthrough, /* schema depends on lookup file */
995
+ .create_native = (void *(*)(const cJSON *))tf_join_create,
996
+ },
997
+ {
998
+ .name = "stack",
999
+ .kind = TF_OP_TRANSFORM,
1000
+ .tier = TF_TIER_CORE,
1001
+ .caps = TF_CAP_FS | TF_CAP_DETERMINISTIC,
1002
+ .args = stack_args,
1003
+ .n_args = 3,
1004
+ .infer_schema = infer_schema_passthrough,
1005
+ .create_native = (void *(*)(const cJSON *))tf_stack_create,
1006
+ },
1007
+ {
1008
+ .name = "lead",
1009
+ .kind = TF_OP_TRANSFORM,
1010
+ .tier = TF_TIER_CORE,
1011
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1012
+ .args = lead_args,
1013
+ .n_args = 3,
1014
+ .infer_schema = infer_schema_passthrough,
1015
+ .create_native = (void *(*)(const cJSON *))tf_lead_create,
1016
+ },
1017
+ {
1018
+ .name = "date-trunc",
1019
+ .kind = TF_OP_TRANSFORM,
1020
+ .tier = TF_TIER_CORE,
1021
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1022
+ .args = date_trunc_args,
1023
+ .n_args = 3,
1024
+ .infer_schema = infer_schema_passthrough,
1025
+ .create_native = (void *(*)(const cJSON *))tf_date_trunc_create,
1026
+ },
1027
+ {
1028
+ .name = "onehot",
1029
+ .kind = TF_OP_TRANSFORM,
1030
+ .tier = TF_TIER_CORE,
1031
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1032
+ .args = onehot_args,
1033
+ .n_args = 2,
1034
+ .infer_schema = infer_schema_passthrough,
1035
+ .create_native = (void *(*)(const cJSON *))tf_onehot_create,
1036
+ },
1037
+ {
1038
+ .name = "label-encode",
1039
+ .kind = TF_OP_TRANSFORM,
1040
+ .tier = TF_TIER_CORE,
1041
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1042
+ .args = label_encode_args,
1043
+ .n_args = 2,
1044
+ .infer_schema = infer_schema_passthrough,
1045
+ .create_native = (void *(*)(const cJSON *))tf_label_encode_create,
1046
+ },
1047
+ {
1048
+ .name = "ewma",
1049
+ .kind = TF_OP_TRANSFORM,
1050
+ .tier = TF_TIER_CORE,
1051
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1052
+ .args = ewma_args,
1053
+ .n_args = 3,
1054
+ .infer_schema = infer_schema_passthrough,
1055
+ .create_native = (void *(*)(const cJSON *))tf_ewma_create,
1056
+ },
1057
+ {
1058
+ .name = "diff",
1059
+ .kind = TF_OP_TRANSFORM,
1060
+ .tier = TF_TIER_CORE,
1061
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1062
+ .args = diff_args,
1063
+ .n_args = 3,
1064
+ .infer_schema = infer_schema_passthrough,
1065
+ .create_native = (void *(*)(const cJSON *))tf_diff_create,
1066
+ },
1067
+ {
1068
+ .name = "anomaly",
1069
+ .kind = TF_OP_TRANSFORM,
1070
+ .tier = TF_TIER_CORE,
1071
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1072
+ .args = anomaly_args,
1073
+ .n_args = 3,
1074
+ .infer_schema = infer_schema_passthrough,
1075
+ .create_native = (void *(*)(const cJSON *))tf_anomaly_create,
1076
+ },
1077
+ {
1078
+ .name = "split-data",
1079
+ .kind = TF_OP_TRANSFORM,
1080
+ .tier = TF_TIER_CORE,
1081
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE,
1082
+ .args = split_data_args,
1083
+ .n_args = 3,
1084
+ .infer_schema = infer_schema_passthrough,
1085
+ .create_native = (void *(*)(const cJSON *))tf_split_data_create,
1086
+ },
1087
+ {
1088
+ .name = "interpolate",
1089
+ .kind = TF_OP_TRANSFORM,
1090
+ .tier = TF_TIER_CORE,
1091
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1092
+ .args = interpolate_args,
1093
+ .n_args = 2,
1094
+ .infer_schema = infer_schema_passthrough,
1095
+ .create_native = (void *(*)(const cJSON *))tf_interpolate_create,
1096
+ },
1097
+ {
1098
+ .name = "normalize",
1099
+ .kind = TF_OP_TRANSFORM,
1100
+ .tier = TF_TIER_CORE,
1101
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1102
+ .args = normalize_args,
1103
+ .n_args = 2,
1104
+ .infer_schema = infer_schema_passthrough,
1105
+ .create_native = (void *(*)(const cJSON *))tf_normalize_create,
1106
+ },
1107
+ {
1108
+ .name = "acf",
1109
+ .kind = TF_OP_TRANSFORM,
1110
+ .tier = TF_TIER_CORE,
1111
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1112
+ .args = acf_args,
1113
+ .n_args = 2,
1114
+ .infer_schema = infer_schema_passthrough,
1115
+ .create_native = (void *(*)(const cJSON *))tf_acf_create,
1116
+ },
1117
+ {
1118
+ .name = "codec.table.encode",
1119
+ .kind = TF_OP_ENCODER,
1120
+ .tier = TF_TIER_CORE,
1121
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1122
+ .args = table_encode_args,
1123
+ .n_args = 2,
1124
+ .infer_schema = infer_schema_sink,
1125
+ .create_native = (void *(*)(const cJSON *))tf_table_encoder_create,
1126
+ },
1127
+ };
1128
+
1129
+ static const size_t n_builtin_ops = sizeof(builtin_ops) / sizeof(builtin_ops[0]);
1130
+
1131
+ /* ---- Public API ---- */
1132
+
1133
+ const tf_op_entry *tf_op_registry_find(const char *name) {
1134
+ for (size_t i = 0; i < n_builtin_ops; i++) {
1135
+ if (strcmp(builtin_ops[i].name, name) == 0)
1136
+ return &builtin_ops[i];
1137
+ }
1138
+ return NULL;
1139
+ }
1140
+
1141
+ size_t tf_op_registry_count(void) {
1142
+ return n_builtin_ops;
1143
+ }
1144
+
1145
+ const tf_op_entry *tf_op_registry_get(size_t index) {
1146
+ if (index >= n_builtin_ops) return NULL;
1147
+ return &builtin_ops[index];
1148
+ }