@octaviaflow/flow-rules 0.11.1 → 0.13.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.
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ var ActionType;
45
45
  ActionType["SpeechToText"] = "speech_to_text";
46
46
  ActionType["TextToSpeech"] = "text_to_speech";
47
47
  ActionType["Transform"] = "transform";
48
+ ActionType["Aggregate"] = "aggregate";
48
49
  ActionType["FileOperation"] = "file_operation";
49
50
  ActionType["FileAction"] = "file_action";
50
51
  ActionType["CsvAction"] = "csv_action";
@@ -1256,8 +1257,18 @@ function isQueryableConnector(connector) {
1256
1257
  }
1257
1258
  var BuildQueryContractSchema = {
1258
1259
  connectorId: { type: "connection", required: true },
1259
- mode: { type: "select", required: true },
1260
- queryType: { type: "select", required: true },
1260
+ mode: {
1261
+ type: "select",
1262
+ required: true,
1263
+ options: ["visual", "sql"],
1264
+ description: "visual: the query is built from visualConfig; sql: the query is the text in sqlConfig.sql"
1265
+ },
1266
+ queryType: {
1267
+ type: "select",
1268
+ required: true,
1269
+ options: ["select", "insert", "update", "delete", "custom"],
1270
+ description: "Only select is a read; the write types exist for the SQL mode of database connectors"
1271
+ },
1261
1272
  visualConfig: { type: "object", required: false },
1262
1273
  sqlConfig: { type: "object", required: false },
1263
1274
  timeout: { type: "number", required: false },
@@ -1577,6 +1588,46 @@ var transformDefinition = {
1577
1588
  testable: true
1578
1589
  };
1579
1590
 
1591
+ // src/catalog/general/aggregate/index.ts
1592
+ var aggregateDefinition = {
1593
+ id: "aggregate",
1594
+ name: "Aggregate",
1595
+ description: "Total, count or average the upstream records into one row — or one row per group — of computed values " + "(sum, count, avg, min, max, count_distinct, first, last). Put it after the step that produces the rows and " + "before the step that writes the result, for example a TOTAL row under the rows a Transform just wrote.",
1596
+ category: "transform" /* Transform */,
1597
+ type: "aggregate" /* Aggregate */,
1598
+ tags: ["aggregate", "sum", "total", "count", "average", "min", "max", "group by", "rollup", "built-in"],
1599
+ status: "stable" /* Stable */,
1600
+ icon: "Sigma",
1601
+ color: "#0043ce",
1602
+ inputSchema: { fields: [] },
1603
+ outputSchema: {
1604
+ type: "object",
1605
+ properties: [
1606
+ {
1607
+ key: "data",
1608
+ type: "array" /* Array */,
1609
+ description: "One row per group (a single row when there is no group-by): the group keys, then each metric under its `as` key"
1610
+ },
1611
+ {
1612
+ key: "_meta",
1613
+ type: "object" /* Object */,
1614
+ description: "rowsIn, groups, and ignored — values skipped for a numeric metric because they were not numbers"
1615
+ }
1616
+ ],
1617
+ sampleOutput: {
1618
+ data: [{ TotalAmount: 12850.4, Invoices: 35 }],
1619
+ _meta: { rowsIn: 35, groups: 1, ignored: 0 }
1620
+ },
1621
+ dynamic: true
1622
+ },
1623
+ version: "1.0.0",
1624
+ createdAt: "2026-10-08T00:00:00Z",
1625
+ updatedAt: "2026-10-08T00:00:00Z",
1626
+ createdBy: "system",
1627
+ isOfficial: true,
1628
+ testable: false
1629
+ };
1630
+
1580
1631
  // src/catalog/storage/csv-action/index.ts
1581
1632
  var csvActionDefinition = {
1582
1633
  id: "csv_action",
@@ -2636,7 +2687,8 @@ var actionsRegistry = {
2636
2687
  console_log: consoleLogDefinition,
2637
2688
  delay: delayDefinition,
2638
2689
  query: queryDefinition,
2639
- transform: transformDefinition
2690
+ transform: transformDefinition,
2691
+ aggregate: aggregateDefinition
2640
2692
  },
2641
2693
  ["logic" /* Logic */]: {
2642
2694
  condition: conditionDefinition,
@@ -3157,7 +3209,7 @@ var ConditionConfig = {
3157
3209
  condition: ""
3158
3210
  },
3159
3211
  schema: {
3160
- mode: { type: "select", required: true },
3212
+ mode: { type: "select", required: true, options: ["visual", "expression"] },
3161
3213
  filters: { type: "array", required: false },
3162
3214
  condition: { type: "expression", required: false }
3163
3215
  },
@@ -3227,8 +3279,18 @@ var ForEachConfig = {
3227
3279
  collect: "array"
3228
3280
  },
3229
3281
  schema: {
3230
- arrayPath: { type: "select", required: false },
3231
- iterationMode: { type: "select", required: false },
3282
+ arrayPath: {
3283
+ type: "select",
3284
+ required: false,
3285
+ dynamic: true,
3286
+ description: "A path to an array in the upstream step's output (the Query's object name, or a field holding a list) — the values come from that step's data, not from the catalog"
3287
+ },
3288
+ iterationMode: {
3289
+ type: "select",
3290
+ required: false,
3291
+ options: ["records", "fields", "range", "nested", "batch", "cursor", "while", "array"],
3292
+ description: "What one iteration is: records (one per record, the default; array is its legacy alias), fields, range, nested, batch, cursor or while"
3293
+ },
3232
3294
  itemVariable: { type: "string", required: true },
3233
3295
  indexVariable: { type: "string", required: false },
3234
3296
  batchSize: { type: "number", required: false },
@@ -3432,7 +3494,7 @@ function fieldToJsonSchema(f) {
3432
3494
  // src/catalog/utility/debug/config.ts
3433
3495
  var DebugConfig = {
3434
3496
  schema: {
3435
- mode: { type: "select", required: true },
3497
+ mode: { type: "select", required: true, options: ["log", "snapshot", "watch", "assert", "profile"] },
3436
3498
  label: { type: "string", required: false },
3437
3499
  pinned: { type: "boolean", required: false },
3438
3500
  condition: { type: "expression", required: false },
@@ -3547,7 +3609,12 @@ var EmailConfig = {
3547
3609
  },
3548
3610
  schema: {
3549
3611
  connection: { type: "connection", required: true },
3550
- operation: { type: "select", required: true },
3612
+ operation: {
3613
+ type: "select",
3614
+ required: true,
3615
+ options: ["send", "read", "search", "delete", "move", "mark_read"],
3616
+ description: "Only send runs today (DEV-349); the other values are kept so older steps still parse, and validate() rejects them"
3617
+ },
3551
3618
  to: { type: "string", required: false },
3552
3619
  cc: { type: "string", required: false },
3553
3620
  bcc: { type: "string", required: false },
@@ -3599,13 +3666,18 @@ var EmailConfig = {
3599
3666
  }
3600
3667
  }
3601
3668
  };
3669
+ // src/catalog/isOneOf.ts
3670
+ var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
3671
+
3602
3672
  // src/catalog/general/transform/config.ts
3673
+ var TRANSFORM_MODES = ["visual", "expression"];
3603
3674
  var TRANSFORM_OPERATIONS = [
3604
3675
  { id: "upsert", label: "Upsert" },
3605
3676
  { id: "insert", label: "Insert" },
3606
3677
  { id: "update", label: "Update" },
3607
3678
  { id: "delete", label: "Delete" }
3608
3679
  ];
3680
+ var TRANSFORM_OPERATION_IDS = TRANSFORM_OPERATIONS.map((o) => o.id);
3609
3681
  var DATABASE_CONNECTOR_IDS = new Set(["mongodb", "postgresql", "mysql", "sqlite", "google_sheets"]);
3610
3682
  var TransformConfig = {
3611
3683
  defaults: {
@@ -3629,11 +3701,30 @@ var TransformConfig = {
3629
3701
  },
3630
3702
  schema: {
3631
3703
  name: { type: "string", required: false },
3632
- operation: { type: "select", required: true },
3704
+ operation: {
3705
+ type: "select",
3706
+ required: true,
3707
+ options: TRANSFORM_OPERATION_IDS,
3708
+ description: "What to do with each mapped record at the target: upsert, insert, update or delete"
3709
+ },
3633
3710
  connectorId: { type: "connection", required: true },
3634
- database: { type: "text", required: false },
3635
- objectName: { type: "select", required: true },
3636
- mode: { type: "select", required: true },
3711
+ database: {
3712
+ type: "text",
3713
+ required: false,
3714
+ description: "Database name for MongoDB / PostgreSQL / MySQL / SQLite; the spreadsheet id for Google Sheets"
3715
+ },
3716
+ objectName: {
3717
+ type: "select",
3718
+ required: true,
3719
+ dynamic: true,
3720
+ description: "The table, entity, object or sheet tab to write to — the names come from the connected app (describe_connector_object)"
3721
+ },
3722
+ mode: {
3723
+ type: "select",
3724
+ required: true,
3725
+ options: TRANSFORM_MODES,
3726
+ description: "How the mappings are authored: visual (the Field Mapper — the only mode the editor offers) or expression (legacy JavaScript). This is not the write mode; that is `operation`."
3727
+ },
3637
3728
  mappings: { type: "array", required: false },
3638
3729
  expression: { type: "code", required: false }
3639
3730
  },
@@ -3647,6 +3738,12 @@ var TransformConfig = {
3647
3738
  const errors = [];
3648
3739
  if (!config.operation) {
3649
3740
  errors.push("Operation is required");
3741
+ } else if (!isOneOf(TRANSFORM_OPERATION_IDS, config.operation)) {
3742
+ errors.push(`Operation must be one of: ${TRANSFORM_OPERATION_IDS.join(", ")} (got "${String(config.operation)}")`);
3743
+ }
3744
+ const mode = config.mode;
3745
+ if (mode !== undefined && mode !== null && mode !== "" && !isOneOf(TRANSFORM_MODES, mode)) {
3746
+ errors.push(`Mode must be one of: ${TRANSFORM_MODES.join(", ")} (got "${String(mode)}"). ` + `Mode is how the mappings are authored, not how rows are written — for that, see operation.`);
3650
3747
  }
3651
3748
  if (!config.connectorId) {
3652
3749
  errors.push("Target connector is required");
@@ -3682,6 +3779,313 @@ var TransformConfig = {
3682
3779
  return `map_${Date.now()}_${randomSuffix(5)}`;
3683
3780
  }
3684
3781
  };
3782
+ // src/catalog/general/aggregate/config.ts
3783
+ var AGGREGATE_FUNCTIONS = [
3784
+ "sum",
3785
+ "count",
3786
+ "avg",
3787
+ "min",
3788
+ "max",
3789
+ "count_distinct",
3790
+ "first",
3791
+ "last"
3792
+ ];
3793
+ var AGGREGATE_FUNCTION_DESCRIPTIONS = {
3794
+ sum: "add up the numeric values of a field",
3795
+ count: "count the records (with a field: the records where it is not blank)",
3796
+ avg: "the mean of the numeric values of a field",
3797
+ min: "the smallest value of a field",
3798
+ max: "the largest value of a field",
3799
+ count_distinct: "how many different values a field has",
3800
+ first: "the field's value on the first record",
3801
+ last: "the field's value on the last record"
3802
+ };
3803
+ var RESERVED_KEYS2 = new Set(["_meta", "_lineage", "_redacted", "_error"]);
3804
+ var KEY_PATTERN2 = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
3805
+ var NUMBER_VALUED = new Set(["sum", "avg", "count", "count_distinct"]);
3806
+ function groupKeyName(path) {
3807
+ const trimmed = String(path ?? "").trim().replace(/\[\]$/, "");
3808
+ const segments = trimmed.split(".").filter(Boolean);
3809
+ return segments.length > 0 ? segments[segments.length - 1] : trimmed;
3810
+ }
3811
+ function schemaAt(schema, path) {
3812
+ let cur = schema;
3813
+ for (const part of path.replace(/\[\]$/, "").split(".").filter(Boolean)) {
3814
+ if (!cur)
3815
+ return;
3816
+ if (cur.type === "array" && cur.items)
3817
+ cur = cur.items;
3818
+ const next = cur.properties?.[part];
3819
+ if (!next)
3820
+ return;
3821
+ cur = next;
3822
+ }
3823
+ return cur;
3824
+ }
3825
+ function leafOf(node, fallback) {
3826
+ const t = node?.type;
3827
+ if (t === "number" || t === "integer" || t === "string" || t === "boolean" || t === "date")
3828
+ return { type: t };
3829
+ return { type: fallback };
3830
+ }
3831
+ function metricSchema(metric, source) {
3832
+ const what = metric.field ? `${metric.fn} of ${metric.field}` : metric.fn;
3833
+ if (NUMBER_VALUED.has(metric.fn))
3834
+ return { type: "number", description: what };
3835
+ return { ...leafOf(metric.field ? schemaAt(source, metric.field) : undefined, "string"), description: what };
3836
+ }
3837
+ var FUNCTION_LIST = AGGREGATE_FUNCTIONS.join(", ");
3838
+ var AggregateConfig = {
3839
+ schema: {
3840
+ arrayPath: {
3841
+ type: "select",
3842
+ required: false,
3843
+ dynamic: true,
3844
+ description: "Which upstream records to aggregate: a path to an array in the upstream step's output. " + "Leave empty for the records the previous step produced (its data). The values come from that step's data, not from the catalog."
3845
+ },
3846
+ groupBy: {
3847
+ type: "array",
3848
+ required: false,
3849
+ description: 'Record fields (dot-paths) to group by, e.g. ["CustomerRef.name"]. Empty: one row for all records. ' + 'Each key lands on the output row under its last segment ("name").'
3850
+ },
3851
+ metrics: {
3852
+ type: "array",
3853
+ required: true,
3854
+ description: 'What to compute, at least one: [{ "fn", "field", "as", "decimals"? }]. ' + `fn is one of ${FUNCTION_LIST} (` + AGGREGATE_FUNCTIONS.map((fn) => `${fn}: ${AGGREGATE_FUNCTION_DESCRIPTIONS[fn]}`).join("; ") + "). field is the record key it reads (a dot-path; count may omit it to count records). " + "as is the output key. decimals rounds a sum or avg. " + 'Example: [{ "fn": "sum", "field": "Balance", "as": "TotalAmount", "decimals": 2 }, { "fn": "count", "as": "Invoices" }]'
3855
+ },
3856
+ outputAs: {
3857
+ type: "string",
3858
+ required: false,
3859
+ description: "The name later steps use for this result, e.g. totals → {{totals[].TotalAmount}}. Defaults to the step name."
3860
+ }
3861
+ },
3862
+ defaults: {
3863
+ arrayPath: "",
3864
+ groupBy: [],
3865
+ metrics: [],
3866
+ outputAs: ""
3867
+ },
3868
+ validate(config) {
3869
+ const errors = [];
3870
+ const metrics = Array.isArray(config.metrics) ? config.metrics : [];
3871
+ if (metrics.length === 0) {
3872
+ errors.push('Add at least one metric — what to compute, e.g. { "fn": "sum", "field": "Balance", "as": "TotalAmount" }.');
3873
+ }
3874
+ const groupKeys = [];
3875
+ const groupBy = config.groupBy;
3876
+ if (groupBy !== undefined && groupBy !== null) {
3877
+ if (!Array.isArray(groupBy)) {
3878
+ errors.push('groupBy must be a list of record fields (dot-paths), e.g. ["CustomerRef.name"].');
3879
+ } else {
3880
+ for (const g of groupBy) {
3881
+ if (typeof g !== "string" || g.trim() === "") {
3882
+ errors.push("groupBy has an empty entry — each one is a record field (dot-path).");
3883
+ continue;
3884
+ }
3885
+ groupKeys.push(groupKeyName(g));
3886
+ }
3887
+ }
3888
+ }
3889
+ const seen = new Set;
3890
+ for (const key of groupKeys) {
3891
+ if (seen.has(key)) {
3892
+ errors.push(`Two group-by fields both land under "${key}" — group by fields whose last segment differs.`);
3893
+ }
3894
+ seen.add(key);
3895
+ }
3896
+ metrics.forEach((m, i) => {
3897
+ const where = m && typeof m === "object" && typeof m.as === "string" && m.as ? `"${m.as}"` : `metric ${i + 1}`;
3898
+ if (!m || typeof m !== "object") {
3899
+ errors.push(`${where}: a metric is an object { "fn", "field", "as" }.`);
3900
+ return;
3901
+ }
3902
+ const fn = m.fn;
3903
+ const fnValid = isOneOf(AGGREGATE_FUNCTIONS, fn);
3904
+ if (fn === undefined || fn === null || fn === "") {
3905
+ errors.push(`${where}: fn is required — one of ${FUNCTION_LIST}.`);
3906
+ } else if (!fnValid) {
3907
+ errors.push(`${where}: fn must be one of: ${FUNCTION_LIST} (got "${String(fn)}").`);
3908
+ }
3909
+ const field = m.field;
3910
+ if (field !== undefined && field !== null && typeof field !== "string") {
3911
+ errors.push(`${where}: field must be a string — a dot-path on the record, e.g. "Balance".`);
3912
+ } else if (fnValid && fn !== "count" && (typeof field !== "string" || field.trim() === "")) {
3913
+ errors.push(`${where}: ${fn} needs a field — the record key it reads, e.g. "Balance".`);
3914
+ }
3915
+ const as = m.as;
3916
+ if (typeof as !== "string" || as === "") {
3917
+ errors.push(`${where}: as is required — the output key the value lands under, e.g. "TotalAmount".`);
3918
+ } else if (RESERVED_KEYS2.has(as)) {
3919
+ errors.push(`"${as}" is reserved — pick another output key.`);
3920
+ } else if (!KEY_PATTERN2.test(as)) {
3921
+ errors.push(`"${as}" is not a valid output key (letters, digits, underscore; must not start with a digit).`);
3922
+ } else if (seen.has(as)) {
3923
+ errors.push(`Duplicate output key "${as}" — every metric and group-by key lands under its own name.`);
3924
+ } else {
3925
+ seen.add(as);
3926
+ }
3927
+ const decimals = m.decimals;
3928
+ if (decimals !== undefined && decimals !== null) {
3929
+ if (typeof decimals !== "number" || !Number.isInteger(decimals) || decimals < 0 || decimals > 10) {
3930
+ errors.push(`${where}: decimals must be a whole number from 0 to 10.`);
3931
+ }
3932
+ }
3933
+ });
3934
+ const outputAs = config.outputAs;
3935
+ if (outputAs !== undefined && outputAs !== null && outputAs !== "") {
3936
+ if (typeof outputAs !== "string" || !KEY_PATTERN2.test(outputAs)) {
3937
+ errors.push('outputAs must be a single name (letters, digits, underscore), e.g. "totals" — later steps read {{totals[].<key>}}.');
3938
+ }
3939
+ }
3940
+ const arrayPath = config.arrayPath;
3941
+ if (arrayPath !== undefined && arrayPath !== null && typeof arrayPath !== "string") {
3942
+ errors.push("arrayPath must be a string — a path to an array in the upstream output, or empty for its records.");
3943
+ }
3944
+ return { valid: errors.length === 0, errors };
3945
+ },
3946
+ computeOutputSchema(config, source) {
3947
+ const properties = {};
3948
+ for (const g of Array.isArray(config.groupBy) ? config.groupBy : []) {
3949
+ if (typeof g !== "string" || g.trim() === "")
3950
+ continue;
3951
+ properties[groupKeyName(g)] = { ...leafOf(schemaAt(source, g), "string"), description: `group: ${g}` };
3952
+ }
3953
+ for (const m of Array.isArray(config.metrics) ? config.metrics : []) {
3954
+ if (!m || typeof m !== "object" || typeof m.as !== "string" || !m.as || !isOneOf(AGGREGATE_FUNCTIONS, m.fn))
3955
+ continue;
3956
+ properties[m.as] = metricSchema(m, source);
3957
+ }
3958
+ return { type: "object", properties };
3959
+ },
3960
+ merge(userConfig) {
3961
+ return { ...AggregateConfig.defaults, ...userConfig };
3962
+ },
3963
+ getLabel(config) {
3964
+ const metrics = Array.isArray(config.metrics) ? config.metrics : [];
3965
+ const parts = metrics.filter((m) => m && typeof m === "object" && m.fn).map((m) => m.field ? `${m.fn}(${m.field})` : String(m.fn));
3966
+ const by = Array.isArray(config.groupBy) && config.groupBy.length > 0 ? ` by ${config.groupBy.map(groupKeyName).join(", ")}` : "";
3967
+ return parts.length > 0 ? `${parts.join(", ")}${by}` : "Aggregate";
3968
+ }
3969
+ };
3970
+ // src/catalog/logic/for-each/recordSchema.ts
3971
+ function resolveSchemaPath(root, path) {
3972
+ if (!root)
3973
+ return;
3974
+ const parts = path.split(".").filter(Boolean);
3975
+ let cur = root;
3976
+ for (const part of parts) {
3977
+ if (cur?.type === "object" && cur.properties && part in cur.properties) {
3978
+ cur = cur.properties[part];
3979
+ continue;
3980
+ }
3981
+ if (cur?.type === "array" && cur.items?.properties && part in cur.items.properties) {
3982
+ cur = cur.items.properties[part];
3983
+ continue;
3984
+ }
3985
+ return;
3986
+ }
3987
+ if (cur?.type === "array" && cur.items)
3988
+ return cur.items;
3989
+ if (cur?.type === "object" && cur.properties)
3990
+ return cur;
3991
+ return;
3992
+ }
3993
+ function resolveSamplePath(sample, path) {
3994
+ const parts = path.split(".").filter(Boolean);
3995
+ let cur = sample;
3996
+ for (const part of parts) {
3997
+ if (cur === null || cur === undefined)
3998
+ return;
3999
+ if (Array.isArray(cur)) {
4000
+ if (cur.length === 0 || typeof cur[0] !== "object" || cur[0] === null)
4001
+ return;
4002
+ cur = cur[0][part];
4003
+ continue;
4004
+ }
4005
+ if (typeof cur === "object") {
4006
+ cur = cur[part];
4007
+ continue;
4008
+ }
4009
+ return;
4010
+ }
4011
+ return cur;
4012
+ }
4013
+ function sampleRecords(node) {
4014
+ const output = node?.executionData?.output;
4015
+ if (!output)
4016
+ return null;
4017
+ if (Array.isArray(output))
4018
+ return output;
4019
+ const data = output.data;
4020
+ return Array.isArray(data) ? data : null;
4021
+ }
4022
+ var isRootPath = (path) => path === "" || path === "data";
4023
+ function resolveRecordSchema(upstreamNodes, arrayPath) {
4024
+ const node = upstreamNodes?.[0];
4025
+ if (!node)
4026
+ return;
4027
+ const path = (arrayPath || "").trim();
4028
+ const isRoot = isRootPath(path);
4029
+ const schema = node.outputSchema || node.config?._outputSchema;
4030
+ if (schema?.properties) {
4031
+ if (isRoot) {
4032
+ const hasDeepFields = Object.values(schema.properties).some((f) => f?.properties || f?.items);
4033
+ if (hasDeepFields)
4034
+ return schema;
4035
+ } else {
4036
+ const resolved = resolveSchemaPath(schema, path);
4037
+ if (resolved)
4038
+ return resolved;
4039
+ }
4040
+ }
4041
+ const records = sampleRecords(node);
4042
+ if (records && records.length > 0 && typeof records[0] === "object" && records[0] !== null) {
4043
+ const sample = records[0];
4044
+ if (isRoot)
4045
+ return inferSchemaFromSample(sample);
4046
+ const resolved = resolveSamplePath(sample, path);
4047
+ if (Array.isArray(resolved) && resolved.length > 0 && typeof resolved[0] === "object" && resolved[0] !== null) {
4048
+ return inferSchemaFromSample(resolved[0]);
4049
+ }
4050
+ if (resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
4051
+ return inferSchemaFromSample(resolved);
4052
+ }
4053
+ if (!path.includes("."))
4054
+ return inferSchemaFromSample(sample);
4055
+ }
4056
+ return schema?.properties ? schema : undefined;
4057
+ }
4058
+ function detectRecordFields(upstreamNodes, arrayPath) {
4059
+ const set = new Set;
4060
+ const props = resolveRecordSchema(upstreamNodes, arrayPath)?.properties;
4061
+ if (props && typeof props === "object")
4062
+ for (const k of Object.keys(props))
4063
+ set.add(k);
4064
+ const records = sampleRecords(upstreamNodes?.[0]);
4065
+ if (records) {
4066
+ const path = (arrayPath || "").trim();
4067
+ const addKeys = (v) => {
4068
+ if (v && typeof v === "object" && !Array.isArray(v))
4069
+ for (const k of Object.keys(v))
4070
+ set.add(k);
4071
+ };
4072
+ for (const r of records.slice(0, 25)) {
4073
+ if (isRootPath(path)) {
4074
+ addKeys(r);
4075
+ continue;
4076
+ }
4077
+ const at = resolveSamplePath(r, path);
4078
+ if (Array.isArray(at))
4079
+ for (const item of at.slice(0, 25))
4080
+ addKeys(item);
4081
+ else if (at && typeof at === "object")
4082
+ addKeys(at);
4083
+ else if (!path.includes("."))
4084
+ addKeys(r);
4085
+ }
4086
+ }
4087
+ return Array.from(set);
4088
+ }
3685
4089
  // src/catalog/storage/storageCapabilities.ts
3686
4090
  var contentTypeField = {
3687
4091
  key: "contentType",
@@ -4850,6 +5254,7 @@ var ConsoleLogConfig = {
4850
5254
  level: {
4851
5255
  type: "select",
4852
5256
  required: false,
5257
+ options: ["info", "warning", "error", "debug"],
4853
5258
  description: "Log level for the message"
4854
5259
  },
4855
5260
  data: {
@@ -4900,6 +5305,7 @@ var DelayConfig = {
4900
5305
  unit: {
4901
5306
  type: "select",
4902
5307
  required: false,
5308
+ options: ["milliseconds", "seconds", "minutes", "hours"],
4903
5309
  description: "Time unit for the duration"
4904
5310
  }
4905
5311
  },
@@ -4964,10 +5370,10 @@ var HTTPRequestConfig = {
4964
5370
  expectedContentTypes: []
4965
5371
  },
4966
5372
  schema: {
4967
- apiType: { type: "select", required: false },
5373
+ apiType: { type: "select", required: false, options: ["rest", "graphql"] },
4968
5374
  baseUrl: { type: "string", required: false },
4969
5375
  queryParams: { type: "array", required: false },
4970
- bodyType: { type: "select", required: false },
5376
+ bodyType: { type: "select", required: false, options: ["none", "json", "raw", "form-data", "xml"] },
4971
5377
  formData: { type: "array", required: false },
4972
5378
  graphqlQuery: { type: "code", required: false },
4973
5379
  graphqlVariables: { type: "json", required: false },
@@ -4977,11 +5383,12 @@ var HTTPRequestConfig = {
4977
5383
  retry: { type: "object", required: false },
4978
5384
  pagination: { type: "object", required: false },
4979
5385
  extract: { type: "object", required: false },
4980
- responseType: { type: "select", required: false },
5386
+ responseType: { type: "select", required: false, options: ["json", "text", "binary"] },
4981
5387
  expectedContentTypes: { type: "array", required: false },
4982
5388
  method: {
4983
5389
  type: "select",
4984
5390
  required: true,
5391
+ options: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
4985
5392
  description: "HTTP method to use"
4986
5393
  },
4987
5394
  url: {
@@ -5171,7 +5578,7 @@ var WebhookCallConfig = {
5171
5578
  },
5172
5579
  schema: {
5173
5580
  url: { type: "url", required: true },
5174
- method: { type: "select", required: true },
5581
+ method: { type: "select", required: true, options: ["POST", "PUT", "PATCH", "GET"] },
5175
5582
  payload: { type: "json", required: false },
5176
5583
  headers: { type: "json", required: false }
5177
5584
  },
@@ -5196,7 +5603,7 @@ var WhatsappSendConfig = {
5196
5603
  schema: {
5197
5604
  connection: { type: "connection", required: true },
5198
5605
  to: { type: "string", required: true },
5199
- mode: { type: "select", required: true },
5606
+ mode: { type: "select", required: true, options: ["template", "text"] },
5200
5607
  templateName: { type: "string", required: false },
5201
5608
  templateLanguage: { type: "string", required: false },
5202
5609
  templateVariables: { type: "array", required: false },
@@ -5243,7 +5650,7 @@ var FileOperationConfig = {
5243
5650
  },
5244
5651
  schema: {
5245
5652
  connection: { type: "connection", required: true },
5246
- operation: { type: "select", required: true },
5653
+ operation: { type: "select", required: true, options: ["upload", "download", "delete", "list", "copy", "move", "exists"] },
5247
5654
  sourcePath: { type: "string", required: false },
5248
5655
  destinationPath: { type: "string", required: true },
5249
5656
  fileContent: { type: "string", required: false },
@@ -5277,7 +5684,7 @@ var FormatConfig = {
5277
5684
  defaults: { input: "", format: "json" },
5278
5685
  schema: {
5279
5686
  input: { type: "expression", required: true },
5280
- format: { type: "select", required: true }
5687
+ format: { type: "select", required: true, options: ["json", "csv", "xml", "yaml"] }
5281
5688
  },
5282
5689
  validate: (config) => {
5283
5690
  const errors = [];
@@ -5302,9 +5709,9 @@ var ScheduleTriggerConfig = {
5302
5709
  timezone: "UTC"
5303
5710
  },
5304
5711
  schema: {
5305
- scheduleType: { type: "select", required: true },
5712
+ scheduleType: { type: "select", required: true, options: ["interval", "cron", "specific"] },
5306
5713
  interval: { type: "number", required: false, min: 1 },
5307
- intervalUnit: { type: "select", required: false },
5714
+ intervalUnit: { type: "select", required: false, options: ["minutes", "hours", "days", "weeks"] },
5308
5715
  cronExpression: { type: "string", required: false },
5309
5716
  timezone: { type: "string", required: false }
5310
5717
  },
@@ -5342,7 +5749,7 @@ var WebhookTriggerConfig = {
5342
5749
  },
5343
5750
  schema: {
5344
5751
  webhookPath: { type: "string", required: false },
5345
- method: { type: "select", required: true },
5752
+ method: { type: "select", required: true, options: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
5346
5753
  requireAuth: { type: "boolean", required: false }
5347
5754
  },
5348
5755
  validate: (config) => {
@@ -5406,6 +5813,7 @@ var CONFIG_CONTRACTS = {
5406
5813
  }
5407
5814
  },
5408
5815
  transform: { schema: TransformConfig.schema, validate: asValidator(TransformConfig.validate) },
5816
+ aggregate: { schema: AggregateConfig.schema, validate: asValidator(AggregateConfig.validate) },
5409
5817
  condition: { schema: ConditionConfig.schema, validate: asValidator(ConditionConfig.validate) },
5410
5818
  for_each: { schema: ForEachConfig.schema, validate: asValidator(ForEachConfig.validate) },
5411
5819
  file_operation: { schema: FileOperationConfig.schema, validate: asValidator(FileOperationConfig.validate) },
@@ -5476,11 +5884,18 @@ function contractFieldsFor(definition) {
5476
5884
  label: humanise(key),
5477
5885
  type: CONTRACT_TYPE_TO_FIELD_TYPE[field.type] ?? "string" /* String */,
5478
5886
  validation: { required: field.required === true },
5887
+ ...typeof field.description === "string" && field.description ? { description: field.description } : {},
5479
5888
  ...field.options ? { options: field.options.map((v) => ({ label: String(v), value: v })) } : {}
5480
5889
  });
5481
5890
  }
5482
5891
  return [...declared, ...derived];
5483
5892
  }
5893
+ function selectsWithoutValues(actionId) {
5894
+ const contract = contractFor(actionId);
5895
+ if (!contract)
5896
+ return [];
5897
+ return Object.entries(contract.schema).filter(([, f]) => f.type === "select" && !(f.options && f.options.length > 0) && f.dynamic !== true).map(([key]) => key);
5898
+ }
5484
5899
  function contractDrift(definition) {
5485
5900
  const contract = contractFor(definition.id);
5486
5901
  if (!contract)
@@ -5750,9 +6165,12 @@ function mappingTransformFunctionName(transform) {
5750
6165
  return match ? match[1] : null;
5751
6166
  }
5752
6167
  export {
6168
+ AGGREGATE_FUNCTIONS,
6169
+ AGGREGATE_FUNCTION_DESCRIPTIONS,
5753
6170
  ActionCategory,
5754
6171
  ActionStatus,
5755
6172
  ActionType,
6173
+ AggregateConfig,
5756
6174
  AggregateFunction,
5757
6175
  AiAgentConfig,
5758
6176
  CONDITION_OPERATORS,
@@ -5807,6 +6225,7 @@ export {
5807
6225
  STORAGE_CONNECTOR_IDS,
5808
6226
  SortDirection,
5809
6227
  StorageActionConfig,
6228
+ TRANSFORM_MODES,
5810
6229
  TRANSFORM_OPERATIONS,
5811
6230
  TransformConfig,
5812
6231
  USER_FUNCTION_PREFIX,
@@ -5823,6 +6242,7 @@ export {
5823
6242
  contractFor,
5824
6243
  contractVocabularyDrift,
5825
6244
  declaredKeys,
6245
+ detectRecordFields,
5826
6246
  emailDefinition,
5827
6247
  fileEditErrors,
5828
6248
  fileFormatErrors,
@@ -5840,6 +6260,7 @@ export {
5840
6260
  getNodeConfigStatus,
5841
6261
  getNodeValidationStatus,
5842
6262
  getStorageProvider,
6263
+ groupKeyName,
5843
6264
  inferSchemaFromSample,
5844
6265
  isEditorPrivateKey,
5845
6266
  isEventCapableConnector,
@@ -5848,6 +6269,8 @@ export {
5848
6269
  mappingTransformFunctionName,
5849
6270
  migrateDelayConfig,
5850
6271
  queryDefinition,
6272
+ resolveRecordSchema,
6273
+ selectsWithoutValues,
5851
6274
  storageConfigKeys,
5852
6275
  storageOperations,
5853
6276
  summarizeConnectorTriggerEvents,