@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/catalog/actions/console-log/config.d.ts +1 -0
- package/dist/catalog/actions/delay/config.d.ts +1 -0
- package/dist/catalog/actions/http-request/config.d.ts +4 -0
- package/dist/catalog/communication/email-send/config.d.ts +2 -0
- package/dist/catalog/communication/webhook-call/config.d.ts +1 -0
- package/dist/catalog/communication/whatsapp-send/config.d.ts +1 -0
- package/dist/catalog/general/aggregate/config.d.ts +126 -0
- package/dist/catalog/general/aggregate/index.d.ts +11 -0
- package/dist/catalog/general/query/config.d.ts +4 -0
- package/dist/catalog/general/transform/config.d.ts +15 -0
- package/dist/catalog/index.d.ts +2 -1
- package/dist/catalog/isOneOf.d.ts +7 -0
- package/dist/catalog/logic/condition/config.d.ts +1 -0
- package/dist/catalog/logic/for-each/config.d.ts +4 -0
- package/dist/catalog/logic/for-each/recordSchema.d.ts +26 -0
- package/dist/catalog/storage/file-operation/config.d.ts +1 -0
- package/dist/catalog/transforms/format/config.d.ts +1 -0
- package/dist/catalog/triggers/schedule-trigger/config.d.ts +2 -0
- package/dist/catalog/triggers/webhook-trigger/config.d.ts +1 -0
- package/dist/catalog/types.d.ts +1 -0
- package/dist/catalog/utility/debug/config.d.ts +1 -0
- package/dist/index.cjs +445 -22
- package/dist/index.d.ts +4 -2
- package/dist/index.js +445 -22
- package/dist/rules/configContracts.d.ts +25 -2
- package/package.json +1 -1
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: {
|
|
1260
|
-
|
|
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: {
|
|
3231
|
-
|
|
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: {
|
|
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: {
|
|
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: {
|
|
3635
|
-
|
|
3636
|
-
|
|
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,
|