@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.cjs CHANGED
@@ -39,9 +39,12 @@ var __export = (target, all) => {
39
39
  // src/index.ts
40
40
  var exports_src = {};
41
41
  __export(exports_src, {
42
+ AGGREGATE_FUNCTIONS: () => AGGREGATE_FUNCTIONS,
43
+ AGGREGATE_FUNCTION_DESCRIPTIONS: () => AGGREGATE_FUNCTION_DESCRIPTIONS,
42
44
  ActionCategory: () => ActionCategory,
43
45
  ActionStatus: () => ActionStatus,
44
46
  ActionType: () => ActionType,
47
+ AggregateConfig: () => AggregateConfig,
45
48
  AggregateFunction: () => AggregateFunction,
46
49
  AiAgentConfig: () => AiAgentConfig,
47
50
  CONDITION_OPERATORS: () => CONDITION_OPERATORS,
@@ -96,6 +99,7 @@ __export(exports_src, {
96
99
  STORAGE_CONNECTOR_IDS: () => STORAGE_CONNECTOR_IDS,
97
100
  SortDirection: () => SortDirection,
98
101
  StorageActionConfig: () => StorageActionConfig,
102
+ TRANSFORM_MODES: () => TRANSFORM_MODES,
99
103
  TRANSFORM_OPERATIONS: () => TRANSFORM_OPERATIONS,
100
104
  TransformConfig: () => TransformConfig,
101
105
  USER_FUNCTION_PREFIX: () => USER_FUNCTION_PREFIX,
@@ -112,6 +116,7 @@ __export(exports_src, {
112
116
  contractFor: () => contractFor,
113
117
  contractVocabularyDrift: () => contractVocabularyDrift,
114
118
  declaredKeys: () => declaredKeys,
119
+ detectRecordFields: () => detectRecordFields,
115
120
  emailDefinition: () => emailDefinition,
116
121
  fileEditErrors: () => fileEditErrors,
117
122
  fileFormatErrors: () => fileFormatErrors,
@@ -129,6 +134,7 @@ __export(exports_src, {
129
134
  getNodeConfigStatus: () => getNodeConfigStatus,
130
135
  getNodeValidationStatus: () => getNodeValidationStatus,
131
136
  getStorageProvider: () => getStorageProvider,
137
+ groupKeyName: () => groupKeyName,
132
138
  inferSchemaFromSample: () => inferSchemaFromSample,
133
139
  isEditorPrivateKey: () => isEditorPrivateKey,
134
140
  isEventCapableConnector: () => isEventCapableConnector,
@@ -137,6 +143,8 @@ __export(exports_src, {
137
143
  mappingTransformFunctionName: () => mappingTransformFunctionName,
138
144
  migrateDelayConfig: () => migrateDelayConfig,
139
145
  queryDefinition: () => queryDefinition,
146
+ resolveRecordSchema: () => resolveRecordSchema,
147
+ selectsWithoutValues: () => selectsWithoutValues,
140
148
  storageConfigKeys: () => storageConfigKeys,
141
149
  storageOperations: () => storageOperations,
142
150
  summarizeConnectorTriggerEvents: () => summarizeConnectorTriggerEvents,
@@ -200,6 +208,7 @@ var ActionType;
200
208
  ActionType["SpeechToText"] = "speech_to_text";
201
209
  ActionType["TextToSpeech"] = "text_to_speech";
202
210
  ActionType["Transform"] = "transform";
211
+ ActionType["Aggregate"] = "aggregate";
203
212
  ActionType["FileOperation"] = "file_operation";
204
213
  ActionType["FileAction"] = "file_action";
205
214
  ActionType["CsvAction"] = "csv_action";
@@ -1411,8 +1420,18 @@ function isQueryableConnector(connector) {
1411
1420
  }
1412
1421
  var BuildQueryContractSchema = {
1413
1422
  connectorId: { type: "connection", required: true },
1414
- mode: { type: "select", required: true },
1415
- queryType: { type: "select", required: true },
1423
+ mode: {
1424
+ type: "select",
1425
+ required: true,
1426
+ options: ["visual", "sql"],
1427
+ description: "visual: the query is built from visualConfig; sql: the query is the text in sqlConfig.sql"
1428
+ },
1429
+ queryType: {
1430
+ type: "select",
1431
+ required: true,
1432
+ options: ["select", "insert", "update", "delete", "custom"],
1433
+ description: "Only select is a read; the write types exist for the SQL mode of database connectors"
1434
+ },
1416
1435
  visualConfig: { type: "object", required: false },
1417
1436
  sqlConfig: { type: "object", required: false },
1418
1437
  timeout: { type: "number", required: false },
@@ -1732,6 +1751,46 @@ var transformDefinition = {
1732
1751
  testable: true
1733
1752
  };
1734
1753
 
1754
+ // src/catalog/general/aggregate/index.ts
1755
+ var aggregateDefinition = {
1756
+ id: "aggregate",
1757
+ name: "Aggregate",
1758
+ 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.",
1759
+ category: "transform" /* Transform */,
1760
+ type: "aggregate" /* Aggregate */,
1761
+ tags: ["aggregate", "sum", "total", "count", "average", "min", "max", "group by", "rollup", "built-in"],
1762
+ status: "stable" /* Stable */,
1763
+ icon: "Sigma",
1764
+ color: "#0043ce",
1765
+ inputSchema: { fields: [] },
1766
+ outputSchema: {
1767
+ type: "object",
1768
+ properties: [
1769
+ {
1770
+ key: "data",
1771
+ type: "array" /* Array */,
1772
+ description: "One row per group (a single row when there is no group-by): the group keys, then each metric under its `as` key"
1773
+ },
1774
+ {
1775
+ key: "_meta",
1776
+ type: "object" /* Object */,
1777
+ description: "rowsIn, groups, and ignored — values skipped for a numeric metric because they were not numbers"
1778
+ }
1779
+ ],
1780
+ sampleOutput: {
1781
+ data: [{ TotalAmount: 12850.4, Invoices: 35 }],
1782
+ _meta: { rowsIn: 35, groups: 1, ignored: 0 }
1783
+ },
1784
+ dynamic: true
1785
+ },
1786
+ version: "1.0.0",
1787
+ createdAt: "2026-10-08T00:00:00Z",
1788
+ updatedAt: "2026-10-08T00:00:00Z",
1789
+ createdBy: "system",
1790
+ isOfficial: true,
1791
+ testable: false
1792
+ };
1793
+
1735
1794
  // src/catalog/storage/csv-action/index.ts
1736
1795
  var csvActionDefinition = {
1737
1796
  id: "csv_action",
@@ -2791,7 +2850,8 @@ var actionsRegistry = {
2791
2850
  console_log: consoleLogDefinition,
2792
2851
  delay: delayDefinition,
2793
2852
  query: queryDefinition,
2794
- transform: transformDefinition
2853
+ transform: transformDefinition,
2854
+ aggregate: aggregateDefinition
2795
2855
  },
2796
2856
  ["logic" /* Logic */]: {
2797
2857
  condition: conditionDefinition,
@@ -3312,7 +3372,7 @@ var ConditionConfig = {
3312
3372
  condition: ""
3313
3373
  },
3314
3374
  schema: {
3315
- mode: { type: "select", required: true },
3375
+ mode: { type: "select", required: true, options: ["visual", "expression"] },
3316
3376
  filters: { type: "array", required: false },
3317
3377
  condition: { type: "expression", required: false }
3318
3378
  },
@@ -3382,8 +3442,18 @@ var ForEachConfig = {
3382
3442
  collect: "array"
3383
3443
  },
3384
3444
  schema: {
3385
- arrayPath: { type: "select", required: false },
3386
- iterationMode: { type: "select", required: false },
3445
+ arrayPath: {
3446
+ type: "select",
3447
+ required: false,
3448
+ dynamic: true,
3449
+ 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"
3450
+ },
3451
+ iterationMode: {
3452
+ type: "select",
3453
+ required: false,
3454
+ options: ["records", "fields", "range", "nested", "batch", "cursor", "while", "array"],
3455
+ description: "What one iteration is: records (one per record, the default; array is its legacy alias), fields, range, nested, batch, cursor or while"
3456
+ },
3387
3457
  itemVariable: { type: "string", required: true },
3388
3458
  indexVariable: { type: "string", required: false },
3389
3459
  batchSize: { type: "number", required: false },
@@ -3587,7 +3657,7 @@ function fieldToJsonSchema(f) {
3587
3657
  // src/catalog/utility/debug/config.ts
3588
3658
  var DebugConfig = {
3589
3659
  schema: {
3590
- mode: { type: "select", required: true },
3660
+ mode: { type: "select", required: true, options: ["log", "snapshot", "watch", "assert", "profile"] },
3591
3661
  label: { type: "string", required: false },
3592
3662
  pinned: { type: "boolean", required: false },
3593
3663
  condition: { type: "expression", required: false },
@@ -3702,7 +3772,12 @@ var EmailConfig = {
3702
3772
  },
3703
3773
  schema: {
3704
3774
  connection: { type: "connection", required: true },
3705
- operation: { type: "select", required: true },
3775
+ operation: {
3776
+ type: "select",
3777
+ required: true,
3778
+ options: ["send", "read", "search", "delete", "move", "mark_read"],
3779
+ description: "Only send runs today (DEV-349); the other values are kept so older steps still parse, and validate() rejects them"
3780
+ },
3706
3781
  to: { type: "string", required: false },
3707
3782
  cc: { type: "string", required: false },
3708
3783
  bcc: { type: "string", required: false },
@@ -3754,13 +3829,18 @@ var EmailConfig = {
3754
3829
  }
3755
3830
  }
3756
3831
  };
3832
+ // src/catalog/isOneOf.ts
3833
+ var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
3834
+
3757
3835
  // src/catalog/general/transform/config.ts
3836
+ var TRANSFORM_MODES = ["visual", "expression"];
3758
3837
  var TRANSFORM_OPERATIONS = [
3759
3838
  { id: "upsert", label: "Upsert" },
3760
3839
  { id: "insert", label: "Insert" },
3761
3840
  { id: "update", label: "Update" },
3762
3841
  { id: "delete", label: "Delete" }
3763
3842
  ];
3843
+ var TRANSFORM_OPERATION_IDS = TRANSFORM_OPERATIONS.map((o) => o.id);
3764
3844
  var DATABASE_CONNECTOR_IDS = new Set(["mongodb", "postgresql", "mysql", "sqlite", "google_sheets"]);
3765
3845
  var TransformConfig = {
3766
3846
  defaults: {
@@ -3784,11 +3864,30 @@ var TransformConfig = {
3784
3864
  },
3785
3865
  schema: {
3786
3866
  name: { type: "string", required: false },
3787
- operation: { type: "select", required: true },
3867
+ operation: {
3868
+ type: "select",
3869
+ required: true,
3870
+ options: TRANSFORM_OPERATION_IDS,
3871
+ description: "What to do with each mapped record at the target: upsert, insert, update or delete"
3872
+ },
3788
3873
  connectorId: { type: "connection", required: true },
3789
- database: { type: "text", required: false },
3790
- objectName: { type: "select", required: true },
3791
- mode: { type: "select", required: true },
3874
+ database: {
3875
+ type: "text",
3876
+ required: false,
3877
+ description: "Database name for MongoDB / PostgreSQL / MySQL / SQLite; the spreadsheet id for Google Sheets"
3878
+ },
3879
+ objectName: {
3880
+ type: "select",
3881
+ required: true,
3882
+ dynamic: true,
3883
+ description: "The table, entity, object or sheet tab to write to — the names come from the connected app (describe_connector_object)"
3884
+ },
3885
+ mode: {
3886
+ type: "select",
3887
+ required: true,
3888
+ options: TRANSFORM_MODES,
3889
+ 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`."
3890
+ },
3792
3891
  mappings: { type: "array", required: false },
3793
3892
  expression: { type: "code", required: false }
3794
3893
  },
@@ -3802,6 +3901,12 @@ var TransformConfig = {
3802
3901
  const errors = [];
3803
3902
  if (!config.operation) {
3804
3903
  errors.push("Operation is required");
3904
+ } else if (!isOneOf(TRANSFORM_OPERATION_IDS, config.operation)) {
3905
+ errors.push(`Operation must be one of: ${TRANSFORM_OPERATION_IDS.join(", ")} (got "${String(config.operation)}")`);
3906
+ }
3907
+ const mode = config.mode;
3908
+ if (mode !== undefined && mode !== null && mode !== "" && !isOneOf(TRANSFORM_MODES, mode)) {
3909
+ 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.`);
3805
3910
  }
3806
3911
  if (!config.connectorId) {
3807
3912
  errors.push("Target connector is required");
@@ -3837,6 +3942,313 @@ var TransformConfig = {
3837
3942
  return `map_${Date.now()}_${randomSuffix(5)}`;
3838
3943
  }
3839
3944
  };
3945
+ // src/catalog/general/aggregate/config.ts
3946
+ var AGGREGATE_FUNCTIONS = [
3947
+ "sum",
3948
+ "count",
3949
+ "avg",
3950
+ "min",
3951
+ "max",
3952
+ "count_distinct",
3953
+ "first",
3954
+ "last"
3955
+ ];
3956
+ var AGGREGATE_FUNCTION_DESCRIPTIONS = {
3957
+ sum: "add up the numeric values of a field",
3958
+ count: "count the records (with a field: the records where it is not blank)",
3959
+ avg: "the mean of the numeric values of a field",
3960
+ min: "the smallest value of a field",
3961
+ max: "the largest value of a field",
3962
+ count_distinct: "how many different values a field has",
3963
+ first: "the field's value on the first record",
3964
+ last: "the field's value on the last record"
3965
+ };
3966
+ var RESERVED_KEYS2 = new Set(["_meta", "_lineage", "_redacted", "_error"]);
3967
+ var KEY_PATTERN2 = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
3968
+ var NUMBER_VALUED = new Set(["sum", "avg", "count", "count_distinct"]);
3969
+ function groupKeyName(path) {
3970
+ const trimmed = String(path ?? "").trim().replace(/\[\]$/, "");
3971
+ const segments = trimmed.split(".").filter(Boolean);
3972
+ return segments.length > 0 ? segments[segments.length - 1] : trimmed;
3973
+ }
3974
+ function schemaAt(schema, path) {
3975
+ let cur = schema;
3976
+ for (const part of path.replace(/\[\]$/, "").split(".").filter(Boolean)) {
3977
+ if (!cur)
3978
+ return;
3979
+ if (cur.type === "array" && cur.items)
3980
+ cur = cur.items;
3981
+ const next = cur.properties?.[part];
3982
+ if (!next)
3983
+ return;
3984
+ cur = next;
3985
+ }
3986
+ return cur;
3987
+ }
3988
+ function leafOf(node, fallback) {
3989
+ const t = node?.type;
3990
+ if (t === "number" || t === "integer" || t === "string" || t === "boolean" || t === "date")
3991
+ return { type: t };
3992
+ return { type: fallback };
3993
+ }
3994
+ function metricSchema(metric, source) {
3995
+ const what = metric.field ? `${metric.fn} of ${metric.field}` : metric.fn;
3996
+ if (NUMBER_VALUED.has(metric.fn))
3997
+ return { type: "number", description: what };
3998
+ return { ...leafOf(metric.field ? schemaAt(source, metric.field) : undefined, "string"), description: what };
3999
+ }
4000
+ var FUNCTION_LIST = AGGREGATE_FUNCTIONS.join(", ");
4001
+ var AggregateConfig = {
4002
+ schema: {
4003
+ arrayPath: {
4004
+ type: "select",
4005
+ required: false,
4006
+ dynamic: true,
4007
+ 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."
4008
+ },
4009
+ groupBy: {
4010
+ type: "array",
4011
+ required: false,
4012
+ 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").'
4013
+ },
4014
+ metrics: {
4015
+ type: "array",
4016
+ required: true,
4017
+ 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" }]'
4018
+ },
4019
+ outputAs: {
4020
+ type: "string",
4021
+ required: false,
4022
+ description: "The name later steps use for this result, e.g. totals → {{totals[].TotalAmount}}. Defaults to the step name."
4023
+ }
4024
+ },
4025
+ defaults: {
4026
+ arrayPath: "",
4027
+ groupBy: [],
4028
+ metrics: [],
4029
+ outputAs: ""
4030
+ },
4031
+ validate(config) {
4032
+ const errors = [];
4033
+ const metrics = Array.isArray(config.metrics) ? config.metrics : [];
4034
+ if (metrics.length === 0) {
4035
+ errors.push('Add at least one metric — what to compute, e.g. { "fn": "sum", "field": "Balance", "as": "TotalAmount" }.');
4036
+ }
4037
+ const groupKeys = [];
4038
+ const groupBy = config.groupBy;
4039
+ if (groupBy !== undefined && groupBy !== null) {
4040
+ if (!Array.isArray(groupBy)) {
4041
+ errors.push('groupBy must be a list of record fields (dot-paths), e.g. ["CustomerRef.name"].');
4042
+ } else {
4043
+ for (const g of groupBy) {
4044
+ if (typeof g !== "string" || g.trim() === "") {
4045
+ errors.push("groupBy has an empty entry — each one is a record field (dot-path).");
4046
+ continue;
4047
+ }
4048
+ groupKeys.push(groupKeyName(g));
4049
+ }
4050
+ }
4051
+ }
4052
+ const seen = new Set;
4053
+ for (const key of groupKeys) {
4054
+ if (seen.has(key)) {
4055
+ errors.push(`Two group-by fields both land under "${key}" — group by fields whose last segment differs.`);
4056
+ }
4057
+ seen.add(key);
4058
+ }
4059
+ metrics.forEach((m, i) => {
4060
+ const where = m && typeof m === "object" && typeof m.as === "string" && m.as ? `"${m.as}"` : `metric ${i + 1}`;
4061
+ if (!m || typeof m !== "object") {
4062
+ errors.push(`${where}: a metric is an object { "fn", "field", "as" }.`);
4063
+ return;
4064
+ }
4065
+ const fn = m.fn;
4066
+ const fnValid = isOneOf(AGGREGATE_FUNCTIONS, fn);
4067
+ if (fn === undefined || fn === null || fn === "") {
4068
+ errors.push(`${where}: fn is required — one of ${FUNCTION_LIST}.`);
4069
+ } else if (!fnValid) {
4070
+ errors.push(`${where}: fn must be one of: ${FUNCTION_LIST} (got "${String(fn)}").`);
4071
+ }
4072
+ const field = m.field;
4073
+ if (field !== undefined && field !== null && typeof field !== "string") {
4074
+ errors.push(`${where}: field must be a string — a dot-path on the record, e.g. "Balance".`);
4075
+ } else if (fnValid && fn !== "count" && (typeof field !== "string" || field.trim() === "")) {
4076
+ errors.push(`${where}: ${fn} needs a field — the record key it reads, e.g. "Balance".`);
4077
+ }
4078
+ const as = m.as;
4079
+ if (typeof as !== "string" || as === "") {
4080
+ errors.push(`${where}: as is required — the output key the value lands under, e.g. "TotalAmount".`);
4081
+ } else if (RESERVED_KEYS2.has(as)) {
4082
+ errors.push(`"${as}" is reserved — pick another output key.`);
4083
+ } else if (!KEY_PATTERN2.test(as)) {
4084
+ errors.push(`"${as}" is not a valid output key (letters, digits, underscore; must not start with a digit).`);
4085
+ } else if (seen.has(as)) {
4086
+ errors.push(`Duplicate output key "${as}" — every metric and group-by key lands under its own name.`);
4087
+ } else {
4088
+ seen.add(as);
4089
+ }
4090
+ const decimals = m.decimals;
4091
+ if (decimals !== undefined && decimals !== null) {
4092
+ if (typeof decimals !== "number" || !Number.isInteger(decimals) || decimals < 0 || decimals > 10) {
4093
+ errors.push(`${where}: decimals must be a whole number from 0 to 10.`);
4094
+ }
4095
+ }
4096
+ });
4097
+ const outputAs = config.outputAs;
4098
+ if (outputAs !== undefined && outputAs !== null && outputAs !== "") {
4099
+ if (typeof outputAs !== "string" || !KEY_PATTERN2.test(outputAs)) {
4100
+ errors.push('outputAs must be a single name (letters, digits, underscore), e.g. "totals" — later steps read {{totals[].<key>}}.');
4101
+ }
4102
+ }
4103
+ const arrayPath = config.arrayPath;
4104
+ if (arrayPath !== undefined && arrayPath !== null && typeof arrayPath !== "string") {
4105
+ errors.push("arrayPath must be a string — a path to an array in the upstream output, or empty for its records.");
4106
+ }
4107
+ return { valid: errors.length === 0, errors };
4108
+ },
4109
+ computeOutputSchema(config, source) {
4110
+ const properties = {};
4111
+ for (const g of Array.isArray(config.groupBy) ? config.groupBy : []) {
4112
+ if (typeof g !== "string" || g.trim() === "")
4113
+ continue;
4114
+ properties[groupKeyName(g)] = { ...leafOf(schemaAt(source, g), "string"), description: `group: ${g}` };
4115
+ }
4116
+ for (const m of Array.isArray(config.metrics) ? config.metrics : []) {
4117
+ if (!m || typeof m !== "object" || typeof m.as !== "string" || !m.as || !isOneOf(AGGREGATE_FUNCTIONS, m.fn))
4118
+ continue;
4119
+ properties[m.as] = metricSchema(m, source);
4120
+ }
4121
+ return { type: "object", properties };
4122
+ },
4123
+ merge(userConfig) {
4124
+ return { ...AggregateConfig.defaults, ...userConfig };
4125
+ },
4126
+ getLabel(config) {
4127
+ const metrics = Array.isArray(config.metrics) ? config.metrics : [];
4128
+ const parts = metrics.filter((m) => m && typeof m === "object" && m.fn).map((m) => m.field ? `${m.fn}(${m.field})` : String(m.fn));
4129
+ const by = Array.isArray(config.groupBy) && config.groupBy.length > 0 ? ` by ${config.groupBy.map(groupKeyName).join(", ")}` : "";
4130
+ return parts.length > 0 ? `${parts.join(", ")}${by}` : "Aggregate";
4131
+ }
4132
+ };
4133
+ // src/catalog/logic/for-each/recordSchema.ts
4134
+ function resolveSchemaPath(root, path) {
4135
+ if (!root)
4136
+ return;
4137
+ const parts = path.split(".").filter(Boolean);
4138
+ let cur = root;
4139
+ for (const part of parts) {
4140
+ if (cur?.type === "object" && cur.properties && part in cur.properties) {
4141
+ cur = cur.properties[part];
4142
+ continue;
4143
+ }
4144
+ if (cur?.type === "array" && cur.items?.properties && part in cur.items.properties) {
4145
+ cur = cur.items.properties[part];
4146
+ continue;
4147
+ }
4148
+ return;
4149
+ }
4150
+ if (cur?.type === "array" && cur.items)
4151
+ return cur.items;
4152
+ if (cur?.type === "object" && cur.properties)
4153
+ return cur;
4154
+ return;
4155
+ }
4156
+ function resolveSamplePath(sample, path) {
4157
+ const parts = path.split(".").filter(Boolean);
4158
+ let cur = sample;
4159
+ for (const part of parts) {
4160
+ if (cur === null || cur === undefined)
4161
+ return;
4162
+ if (Array.isArray(cur)) {
4163
+ if (cur.length === 0 || typeof cur[0] !== "object" || cur[0] === null)
4164
+ return;
4165
+ cur = cur[0][part];
4166
+ continue;
4167
+ }
4168
+ if (typeof cur === "object") {
4169
+ cur = cur[part];
4170
+ continue;
4171
+ }
4172
+ return;
4173
+ }
4174
+ return cur;
4175
+ }
4176
+ function sampleRecords(node) {
4177
+ const output = node?.executionData?.output;
4178
+ if (!output)
4179
+ return null;
4180
+ if (Array.isArray(output))
4181
+ return output;
4182
+ const data = output.data;
4183
+ return Array.isArray(data) ? data : null;
4184
+ }
4185
+ var isRootPath = (path) => path === "" || path === "data";
4186
+ function resolveRecordSchema(upstreamNodes, arrayPath) {
4187
+ const node = upstreamNodes?.[0];
4188
+ if (!node)
4189
+ return;
4190
+ const path = (arrayPath || "").trim();
4191
+ const isRoot = isRootPath(path);
4192
+ const schema = node.outputSchema || node.config?._outputSchema;
4193
+ if (schema?.properties) {
4194
+ if (isRoot) {
4195
+ const hasDeepFields = Object.values(schema.properties).some((f) => f?.properties || f?.items);
4196
+ if (hasDeepFields)
4197
+ return schema;
4198
+ } else {
4199
+ const resolved = resolveSchemaPath(schema, path);
4200
+ if (resolved)
4201
+ return resolved;
4202
+ }
4203
+ }
4204
+ const records = sampleRecords(node);
4205
+ if (records && records.length > 0 && typeof records[0] === "object" && records[0] !== null) {
4206
+ const sample = records[0];
4207
+ if (isRoot)
4208
+ return inferSchemaFromSample(sample);
4209
+ const resolved = resolveSamplePath(sample, path);
4210
+ if (Array.isArray(resolved) && resolved.length > 0 && typeof resolved[0] === "object" && resolved[0] !== null) {
4211
+ return inferSchemaFromSample(resolved[0]);
4212
+ }
4213
+ if (resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
4214
+ return inferSchemaFromSample(resolved);
4215
+ }
4216
+ if (!path.includes("."))
4217
+ return inferSchemaFromSample(sample);
4218
+ }
4219
+ return schema?.properties ? schema : undefined;
4220
+ }
4221
+ function detectRecordFields(upstreamNodes, arrayPath) {
4222
+ const set = new Set;
4223
+ const props = resolveRecordSchema(upstreamNodes, arrayPath)?.properties;
4224
+ if (props && typeof props === "object")
4225
+ for (const k of Object.keys(props))
4226
+ set.add(k);
4227
+ const records = sampleRecords(upstreamNodes?.[0]);
4228
+ if (records) {
4229
+ const path = (arrayPath || "").trim();
4230
+ const addKeys = (v) => {
4231
+ if (v && typeof v === "object" && !Array.isArray(v))
4232
+ for (const k of Object.keys(v))
4233
+ set.add(k);
4234
+ };
4235
+ for (const r of records.slice(0, 25)) {
4236
+ if (isRootPath(path)) {
4237
+ addKeys(r);
4238
+ continue;
4239
+ }
4240
+ const at = resolveSamplePath(r, path);
4241
+ if (Array.isArray(at))
4242
+ for (const item of at.slice(0, 25))
4243
+ addKeys(item);
4244
+ else if (at && typeof at === "object")
4245
+ addKeys(at);
4246
+ else if (!path.includes("."))
4247
+ addKeys(r);
4248
+ }
4249
+ }
4250
+ return Array.from(set);
4251
+ }
3840
4252
  // src/catalog/storage/storageCapabilities.ts
3841
4253
  var contentTypeField = {
3842
4254
  key: "contentType",
@@ -5005,6 +5417,7 @@ var ConsoleLogConfig = {
5005
5417
  level: {
5006
5418
  type: "select",
5007
5419
  required: false,
5420
+ options: ["info", "warning", "error", "debug"],
5008
5421
  description: "Log level for the message"
5009
5422
  },
5010
5423
  data: {
@@ -5055,6 +5468,7 @@ var DelayConfig = {
5055
5468
  unit: {
5056
5469
  type: "select",
5057
5470
  required: false,
5471
+ options: ["milliseconds", "seconds", "minutes", "hours"],
5058
5472
  description: "Time unit for the duration"
5059
5473
  }
5060
5474
  },
@@ -5119,10 +5533,10 @@ var HTTPRequestConfig = {
5119
5533
  expectedContentTypes: []
5120
5534
  },
5121
5535
  schema: {
5122
- apiType: { type: "select", required: false },
5536
+ apiType: { type: "select", required: false, options: ["rest", "graphql"] },
5123
5537
  baseUrl: { type: "string", required: false },
5124
5538
  queryParams: { type: "array", required: false },
5125
- bodyType: { type: "select", required: false },
5539
+ bodyType: { type: "select", required: false, options: ["none", "json", "raw", "form-data", "xml"] },
5126
5540
  formData: { type: "array", required: false },
5127
5541
  graphqlQuery: { type: "code", required: false },
5128
5542
  graphqlVariables: { type: "json", required: false },
@@ -5132,11 +5546,12 @@ var HTTPRequestConfig = {
5132
5546
  retry: { type: "object", required: false },
5133
5547
  pagination: { type: "object", required: false },
5134
5548
  extract: { type: "object", required: false },
5135
- responseType: { type: "select", required: false },
5549
+ responseType: { type: "select", required: false, options: ["json", "text", "binary"] },
5136
5550
  expectedContentTypes: { type: "array", required: false },
5137
5551
  method: {
5138
5552
  type: "select",
5139
5553
  required: true,
5554
+ options: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
5140
5555
  description: "HTTP method to use"
5141
5556
  },
5142
5557
  url: {
@@ -5326,7 +5741,7 @@ var WebhookCallConfig = {
5326
5741
  },
5327
5742
  schema: {
5328
5743
  url: { type: "url", required: true },
5329
- method: { type: "select", required: true },
5744
+ method: { type: "select", required: true, options: ["POST", "PUT", "PATCH", "GET"] },
5330
5745
  payload: { type: "json", required: false },
5331
5746
  headers: { type: "json", required: false }
5332
5747
  },
@@ -5351,7 +5766,7 @@ var WhatsappSendConfig = {
5351
5766
  schema: {
5352
5767
  connection: { type: "connection", required: true },
5353
5768
  to: { type: "string", required: true },
5354
- mode: { type: "select", required: true },
5769
+ mode: { type: "select", required: true, options: ["template", "text"] },
5355
5770
  templateName: { type: "string", required: false },
5356
5771
  templateLanguage: { type: "string", required: false },
5357
5772
  templateVariables: { type: "array", required: false },
@@ -5398,7 +5813,7 @@ var FileOperationConfig = {
5398
5813
  },
5399
5814
  schema: {
5400
5815
  connection: { type: "connection", required: true },
5401
- operation: { type: "select", required: true },
5816
+ operation: { type: "select", required: true, options: ["upload", "download", "delete", "list", "copy", "move", "exists"] },
5402
5817
  sourcePath: { type: "string", required: false },
5403
5818
  destinationPath: { type: "string", required: true },
5404
5819
  fileContent: { type: "string", required: false },
@@ -5432,7 +5847,7 @@ var FormatConfig = {
5432
5847
  defaults: { input: "", format: "json" },
5433
5848
  schema: {
5434
5849
  input: { type: "expression", required: true },
5435
- format: { type: "select", required: true }
5850
+ format: { type: "select", required: true, options: ["json", "csv", "xml", "yaml"] }
5436
5851
  },
5437
5852
  validate: (config) => {
5438
5853
  const errors = [];
@@ -5457,9 +5872,9 @@ var ScheduleTriggerConfig = {
5457
5872
  timezone: "UTC"
5458
5873
  },
5459
5874
  schema: {
5460
- scheduleType: { type: "select", required: true },
5875
+ scheduleType: { type: "select", required: true, options: ["interval", "cron", "specific"] },
5461
5876
  interval: { type: "number", required: false, min: 1 },
5462
- intervalUnit: { type: "select", required: false },
5877
+ intervalUnit: { type: "select", required: false, options: ["minutes", "hours", "days", "weeks"] },
5463
5878
  cronExpression: { type: "string", required: false },
5464
5879
  timezone: { type: "string", required: false }
5465
5880
  },
@@ -5497,7 +5912,7 @@ var WebhookTriggerConfig = {
5497
5912
  },
5498
5913
  schema: {
5499
5914
  webhookPath: { type: "string", required: false },
5500
- method: { type: "select", required: true },
5915
+ method: { type: "select", required: true, options: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
5501
5916
  requireAuth: { type: "boolean", required: false }
5502
5917
  },
5503
5918
  validate: (config) => {
@@ -5561,6 +5976,7 @@ var CONFIG_CONTRACTS = {
5561
5976
  }
5562
5977
  },
5563
5978
  transform: { schema: TransformConfig.schema, validate: asValidator(TransformConfig.validate) },
5979
+ aggregate: { schema: AggregateConfig.schema, validate: asValidator(AggregateConfig.validate) },
5564
5980
  condition: { schema: ConditionConfig.schema, validate: asValidator(ConditionConfig.validate) },
5565
5981
  for_each: { schema: ForEachConfig.schema, validate: asValidator(ForEachConfig.validate) },
5566
5982
  file_operation: { schema: FileOperationConfig.schema, validate: asValidator(FileOperationConfig.validate) },
@@ -5631,11 +6047,18 @@ function contractFieldsFor(definition) {
5631
6047
  label: humanise(key),
5632
6048
  type: CONTRACT_TYPE_TO_FIELD_TYPE[field.type] ?? "string" /* String */,
5633
6049
  validation: { required: field.required === true },
6050
+ ...typeof field.description === "string" && field.description ? { description: field.description } : {},
5634
6051
  ...field.options ? { options: field.options.map((v) => ({ label: String(v), value: v })) } : {}
5635
6052
  });
5636
6053
  }
5637
6054
  return [...declared, ...derived];
5638
6055
  }
6056
+ function selectsWithoutValues(actionId) {
6057
+ const contract = contractFor(actionId);
6058
+ if (!contract)
6059
+ return [];
6060
+ return Object.entries(contract.schema).filter(([, f]) => f.type === "select" && !(f.options && f.options.length > 0) && f.dynamic !== true).map(([key]) => key);
6061
+ }
5639
6062
  function contractDrift(definition) {
5640
6063
  const contract = contractFor(definition.id);
5641
6064
  if (!contract)