@octaviaflow/flow-rules 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog/ai/ai-agent/config.d.ts +12 -0
- package/dist/catalog/general/aggregate/config.d.ts +126 -0
- package/dist/catalog/general/aggregate/index.d.ts +11 -0
- package/dist/catalog/index.d.ts +2 -1
- package/dist/catalog/isOneOf.d.ts +7 -0
- package/dist/catalog/logic/for-each/recordSchema.d.ts +26 -0
- package/dist/catalog/types.d.ts +1 -0
- package/dist/index.cjs +411 -10
- package/dist/index.d.ts +2 -0
- package/dist/index.js +411 -10
- package/dist/rules/configContracts.d.ts +2 -2
- package/package.json +1 -1
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,
|
|
@@ -113,6 +116,7 @@ __export(exports_src, {
|
|
|
113
116
|
contractFor: () => contractFor,
|
|
114
117
|
contractVocabularyDrift: () => contractVocabularyDrift,
|
|
115
118
|
declaredKeys: () => declaredKeys,
|
|
119
|
+
detectRecordFields: () => detectRecordFields,
|
|
116
120
|
emailDefinition: () => emailDefinition,
|
|
117
121
|
fileEditErrors: () => fileEditErrors,
|
|
118
122
|
fileFormatErrors: () => fileFormatErrors,
|
|
@@ -130,6 +134,7 @@ __export(exports_src, {
|
|
|
130
134
|
getNodeConfigStatus: () => getNodeConfigStatus,
|
|
131
135
|
getNodeValidationStatus: () => getNodeValidationStatus,
|
|
132
136
|
getStorageProvider: () => getStorageProvider,
|
|
137
|
+
groupKeyName: () => groupKeyName,
|
|
133
138
|
inferSchemaFromSample: () => inferSchemaFromSample,
|
|
134
139
|
isEditorPrivateKey: () => isEditorPrivateKey,
|
|
135
140
|
isEventCapableConnector: () => isEventCapableConnector,
|
|
@@ -138,6 +143,7 @@ __export(exports_src, {
|
|
|
138
143
|
mappingTransformFunctionName: () => mappingTransformFunctionName,
|
|
139
144
|
migrateDelayConfig: () => migrateDelayConfig,
|
|
140
145
|
queryDefinition: () => queryDefinition,
|
|
146
|
+
resolveRecordSchema: () => resolveRecordSchema,
|
|
141
147
|
selectsWithoutValues: () => selectsWithoutValues,
|
|
142
148
|
storageConfigKeys: () => storageConfigKeys,
|
|
143
149
|
storageOperations: () => storageOperations,
|
|
@@ -202,6 +208,7 @@ var ActionType;
|
|
|
202
208
|
ActionType["SpeechToText"] = "speech_to_text";
|
|
203
209
|
ActionType["TextToSpeech"] = "text_to_speech";
|
|
204
210
|
ActionType["Transform"] = "transform";
|
|
211
|
+
ActionType["Aggregate"] = "aggregate";
|
|
205
212
|
ActionType["FileOperation"] = "file_operation";
|
|
206
213
|
ActionType["FileAction"] = "file_action";
|
|
207
214
|
ActionType["CsvAction"] = "csv_action";
|
|
@@ -1530,7 +1537,7 @@ var queryDefinition = {
|
|
|
1530
1537
|
key: "visualConfig.limit",
|
|
1531
1538
|
label: "Limit",
|
|
1532
1539
|
type: "number" /* Number */,
|
|
1533
|
-
description: "
|
|
1540
|
+
description: "Rows the PREVIEW shows. A run never reads it — set visualConfig.maxRows to cap a run.",
|
|
1534
1541
|
defaultValue: 10,
|
|
1535
1542
|
validation: { required: false, min: 1, max: 1e4 },
|
|
1536
1543
|
conditional: [
|
|
@@ -1542,6 +1549,22 @@ var queryDefinition = {
|
|
|
1542
1549
|
}
|
|
1543
1550
|
]
|
|
1544
1551
|
},
|
|
1552
|
+
{
|
|
1553
|
+
key: "visualConfig.maxRows",
|
|
1554
|
+
label: "Max rows (run)",
|
|
1555
|
+
type: "number" /* Number */,
|
|
1556
|
+
description: "The most rows a run reads (up to 10,000). Unset: 10,000. The Limit above is preview-only.",
|
|
1557
|
+
validation: { required: false, min: 1, max: 1e4 },
|
|
1558
|
+
advanced: true,
|
|
1559
|
+
conditional: [
|
|
1560
|
+
{
|
|
1561
|
+
field: "mode",
|
|
1562
|
+
operator: "==",
|
|
1563
|
+
value: "visual" /* Visual */,
|
|
1564
|
+
action: "show"
|
|
1565
|
+
}
|
|
1566
|
+
]
|
|
1567
|
+
},
|
|
1545
1568
|
{
|
|
1546
1569
|
key: "sqlConfig.sql",
|
|
1547
1570
|
label: "SQL Query",
|
|
@@ -1588,7 +1611,7 @@ var queryDefinition = {
|
|
|
1588
1611
|
key: "maxRows",
|
|
1589
1612
|
label: "Max Rows",
|
|
1590
1613
|
type: "number" /* Number */,
|
|
1591
|
-
description: "
|
|
1614
|
+
description: "Legacy step-level field — NOT read at run. Cap a visual Query with visualConfig.maxRows; in SQL mode write a LIMIT.",
|
|
1592
1615
|
defaultValue: 1000,
|
|
1593
1616
|
validation: { required: false, min: 1, max: 1e4 },
|
|
1594
1617
|
advanced: true
|
|
@@ -1744,6 +1767,46 @@ var transformDefinition = {
|
|
|
1744
1767
|
testable: true
|
|
1745
1768
|
};
|
|
1746
1769
|
|
|
1770
|
+
// src/catalog/general/aggregate/index.ts
|
|
1771
|
+
var aggregateDefinition = {
|
|
1772
|
+
id: "aggregate",
|
|
1773
|
+
name: "Aggregate",
|
|
1774
|
+
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.",
|
|
1775
|
+
category: "transform" /* Transform */,
|
|
1776
|
+
type: "aggregate" /* Aggregate */,
|
|
1777
|
+
tags: ["aggregate", "sum", "total", "count", "average", "min", "max", "group by", "rollup", "built-in"],
|
|
1778
|
+
status: "stable" /* Stable */,
|
|
1779
|
+
icon: "Sigma",
|
|
1780
|
+
color: "#0043ce",
|
|
1781
|
+
inputSchema: { fields: [] },
|
|
1782
|
+
outputSchema: {
|
|
1783
|
+
type: "object",
|
|
1784
|
+
properties: [
|
|
1785
|
+
{
|
|
1786
|
+
key: "data",
|
|
1787
|
+
type: "array" /* Array */,
|
|
1788
|
+
description: "One row per group (a single row when there is no group-by): the group keys, then each metric under its `as` key"
|
|
1789
|
+
},
|
|
1790
|
+
{
|
|
1791
|
+
key: "_meta",
|
|
1792
|
+
type: "object" /* Object */,
|
|
1793
|
+
description: "rowsIn, groups, and ignored — values skipped for a numeric metric because they were not numbers"
|
|
1794
|
+
}
|
|
1795
|
+
],
|
|
1796
|
+
sampleOutput: {
|
|
1797
|
+
data: [{ TotalAmount: 12850.4, Invoices: 35 }],
|
|
1798
|
+
_meta: { rowsIn: 35, groups: 1, ignored: 0 }
|
|
1799
|
+
},
|
|
1800
|
+
dynamic: true
|
|
1801
|
+
},
|
|
1802
|
+
version: "1.0.0",
|
|
1803
|
+
createdAt: "2026-10-08T00:00:00Z",
|
|
1804
|
+
updatedAt: "2026-10-08T00:00:00Z",
|
|
1805
|
+
createdBy: "system",
|
|
1806
|
+
isOfficial: true,
|
|
1807
|
+
testable: false
|
|
1808
|
+
};
|
|
1809
|
+
|
|
1747
1810
|
// src/catalog/storage/csv-action/index.ts
|
|
1748
1811
|
var csvActionDefinition = {
|
|
1749
1812
|
id: "csv_action",
|
|
@@ -2665,7 +2728,7 @@ var graphqlQueryDefinition = {
|
|
|
2665
2728
|
var aiAgentDefinition = {
|
|
2666
2729
|
id: "ai_agent",
|
|
2667
2730
|
name: "AI Agent",
|
|
2668
|
-
description: "Run a reusable AI agent. Pick a saved agent from /ai/agents; optionally override model, temperature, or instructions per step.",
|
|
2731
|
+
description: "Run a reusable AI agent. Pick a saved agent from /ai/agents; optionally override model, temperature, or instructions per step. " + "When the step before it produces rows (a Query, a Transform), the agent runs ONCE PER ROW with the prompt as the per-row template — " + "reference single fields, never the whole table; put an Aggregate first to call it once. maxRows (default 50) caps the calls.",
|
|
2669
2732
|
category: "ai" /* AI */,
|
|
2670
2733
|
type: "ai_agent" /* AIAgent */,
|
|
2671
2734
|
tags: ["ai", "agent", "chat", "built-in"],
|
|
@@ -2685,21 +2748,28 @@ var aiAgentDefinition = {
|
|
|
2685
2748
|
key: "input",
|
|
2686
2749
|
label: "Input",
|
|
2687
2750
|
type: "string" /* String */,
|
|
2688
|
-
description: "
|
|
2751
|
+
description: "The prompt. Over a row-shaped upstream it is the per-row template: each data reference resolves to the current row. " + "A reference to the whole upstream table is refused. When blank, the upstream output is the input."
|
|
2689
2752
|
},
|
|
2690
2753
|
{
|
|
2691
2754
|
key: "overrides",
|
|
2692
2755
|
label: "Overrides",
|
|
2693
2756
|
type: "json" /* JSON */,
|
|
2694
2757
|
description: "Optional per-step overrides of the agent’s own settings: model, temperature, maxTokens, topP, instructions and instructionsMode."
|
|
2758
|
+
},
|
|
2759
|
+
{
|
|
2760
|
+
key: "maxRows",
|
|
2761
|
+
label: "Max rows",
|
|
2762
|
+
type: "number" /* Number */,
|
|
2763
|
+
description: "Per-row mode: the most model calls this step may make, one per upstream row (default 50, at most 1000)."
|
|
2695
2764
|
}
|
|
2696
2765
|
]
|
|
2697
2766
|
},
|
|
2698
2767
|
outputSchema: {
|
|
2699
2768
|
type: "object",
|
|
2700
2769
|
properties: [
|
|
2701
|
-
{ key: "text", type: "string" /* String */, description: "Agent response text
|
|
2702
|
-
{ key: "
|
|
2770
|
+
{ key: "text", type: "string" /* String */, description: "Agent response text; in per-row mode, one line per row" },
|
|
2771
|
+
{ key: "output", type: "json" /* JSON */, description: "The parsed answer (an object when the agent replies in JSON); in per-row mode, one entry per row" },
|
|
2772
|
+
{ key: "rows", type: "json" /* JSON */, description: "Per-row mode: one entry per upstream row" },
|
|
2703
2773
|
{ key: "usage", type: "json" /* JSON */, description: "Token usage + cost: tokensIn, tokensOut, latencyMs, costUsd" },
|
|
2704
2774
|
{ key: "model", type: "string" /* String */, description: "Model that produced the response" },
|
|
2705
2775
|
{ key: "agentVersion", type: "number" /* Number */, description: "Version of the agent definition that ran" }
|
|
@@ -2803,7 +2873,8 @@ var actionsRegistry = {
|
|
|
2803
2873
|
console_log: consoleLogDefinition,
|
|
2804
2874
|
delay: delayDefinition,
|
|
2805
2875
|
query: queryDefinition,
|
|
2806
|
-
transform: transformDefinition
|
|
2876
|
+
transform: transformDefinition,
|
|
2877
|
+
aggregate: aggregateDefinition
|
|
2807
2878
|
},
|
|
2808
2879
|
["logic" /* Logic */]: {
|
|
2809
2880
|
condition: conditionDefinition,
|
|
@@ -3781,6 +3852,9 @@ var EmailConfig = {
|
|
|
3781
3852
|
}
|
|
3782
3853
|
}
|
|
3783
3854
|
};
|
|
3855
|
+
// src/catalog/isOneOf.ts
|
|
3856
|
+
var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
|
|
3857
|
+
|
|
3784
3858
|
// src/catalog/general/transform/config.ts
|
|
3785
3859
|
var TRANSFORM_MODES = ["visual", "expression"];
|
|
3786
3860
|
var TRANSFORM_OPERATIONS = [
|
|
@@ -3790,7 +3864,6 @@ var TRANSFORM_OPERATIONS = [
|
|
|
3790
3864
|
{ id: "delete", label: "Delete" }
|
|
3791
3865
|
];
|
|
3792
3866
|
var TRANSFORM_OPERATION_IDS = TRANSFORM_OPERATIONS.map((o) => o.id);
|
|
3793
|
-
var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
|
|
3794
3867
|
var DATABASE_CONNECTOR_IDS = new Set(["mongodb", "postgresql", "mysql", "sqlite", "google_sheets"]);
|
|
3795
3868
|
var TransformConfig = {
|
|
3796
3869
|
defaults: {
|
|
@@ -3892,6 +3965,313 @@ var TransformConfig = {
|
|
|
3892
3965
|
return `map_${Date.now()}_${randomSuffix(5)}`;
|
|
3893
3966
|
}
|
|
3894
3967
|
};
|
|
3968
|
+
// src/catalog/general/aggregate/config.ts
|
|
3969
|
+
var AGGREGATE_FUNCTIONS = [
|
|
3970
|
+
"sum",
|
|
3971
|
+
"count",
|
|
3972
|
+
"avg",
|
|
3973
|
+
"min",
|
|
3974
|
+
"max",
|
|
3975
|
+
"count_distinct",
|
|
3976
|
+
"first",
|
|
3977
|
+
"last"
|
|
3978
|
+
];
|
|
3979
|
+
var AGGREGATE_FUNCTION_DESCRIPTIONS = {
|
|
3980
|
+
sum: "add up the numeric values of a field",
|
|
3981
|
+
count: "count the records (with a field: the records where it is not blank)",
|
|
3982
|
+
avg: "the mean of the numeric values of a field",
|
|
3983
|
+
min: "the smallest value of a field",
|
|
3984
|
+
max: "the largest value of a field",
|
|
3985
|
+
count_distinct: "how many different values a field has",
|
|
3986
|
+
first: "the field's value on the first record",
|
|
3987
|
+
last: "the field's value on the last record"
|
|
3988
|
+
};
|
|
3989
|
+
var RESERVED_KEYS2 = new Set(["_meta", "_lineage", "_redacted", "_error"]);
|
|
3990
|
+
var KEY_PATTERN2 = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
3991
|
+
var NUMBER_VALUED = new Set(["sum", "avg", "count", "count_distinct"]);
|
|
3992
|
+
function groupKeyName(path) {
|
|
3993
|
+
const trimmed = String(path ?? "").trim().replace(/\[\]$/, "");
|
|
3994
|
+
const segments = trimmed.split(".").filter(Boolean);
|
|
3995
|
+
return segments.length > 0 ? segments[segments.length - 1] : trimmed;
|
|
3996
|
+
}
|
|
3997
|
+
function schemaAt(schema, path) {
|
|
3998
|
+
let cur = schema;
|
|
3999
|
+
for (const part of path.replace(/\[\]$/, "").split(".").filter(Boolean)) {
|
|
4000
|
+
if (!cur)
|
|
4001
|
+
return;
|
|
4002
|
+
if (cur.type === "array" && cur.items)
|
|
4003
|
+
cur = cur.items;
|
|
4004
|
+
const next = cur.properties?.[part];
|
|
4005
|
+
if (!next)
|
|
4006
|
+
return;
|
|
4007
|
+
cur = next;
|
|
4008
|
+
}
|
|
4009
|
+
return cur;
|
|
4010
|
+
}
|
|
4011
|
+
function leafOf(node, fallback) {
|
|
4012
|
+
const t = node?.type;
|
|
4013
|
+
if (t === "number" || t === "integer" || t === "string" || t === "boolean" || t === "date")
|
|
4014
|
+
return { type: t };
|
|
4015
|
+
return { type: fallback };
|
|
4016
|
+
}
|
|
4017
|
+
function metricSchema(metric, source) {
|
|
4018
|
+
const what = metric.field ? `${metric.fn} of ${metric.field}` : metric.fn;
|
|
4019
|
+
if (NUMBER_VALUED.has(metric.fn))
|
|
4020
|
+
return { type: "number", description: what };
|
|
4021
|
+
return { ...leafOf(metric.field ? schemaAt(source, metric.field) : undefined, "string"), description: what };
|
|
4022
|
+
}
|
|
4023
|
+
var FUNCTION_LIST = AGGREGATE_FUNCTIONS.join(", ");
|
|
4024
|
+
var AggregateConfig = {
|
|
4025
|
+
schema: {
|
|
4026
|
+
arrayPath: {
|
|
4027
|
+
type: "select",
|
|
4028
|
+
required: false,
|
|
4029
|
+
dynamic: true,
|
|
4030
|
+
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."
|
|
4031
|
+
},
|
|
4032
|
+
groupBy: {
|
|
4033
|
+
type: "array",
|
|
4034
|
+
required: false,
|
|
4035
|
+
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").'
|
|
4036
|
+
},
|
|
4037
|
+
metrics: {
|
|
4038
|
+
type: "array",
|
|
4039
|
+
required: true,
|
|
4040
|
+
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" }]'
|
|
4041
|
+
},
|
|
4042
|
+
outputAs: {
|
|
4043
|
+
type: "string",
|
|
4044
|
+
required: false,
|
|
4045
|
+
description: "The name later steps use for this result, e.g. totals → {{totals[].TotalAmount}} in a Transform mapping. " + "A message or AI prompt reads it as {{ods://steps/<this step id>/output/data[0]/TotalAmount}} — the mapping form is not resolved there. " + "Defaults to the step name."
|
|
4046
|
+
}
|
|
4047
|
+
},
|
|
4048
|
+
defaults: {
|
|
4049
|
+
arrayPath: "",
|
|
4050
|
+
groupBy: [],
|
|
4051
|
+
metrics: [],
|
|
4052
|
+
outputAs: ""
|
|
4053
|
+
},
|
|
4054
|
+
validate(config) {
|
|
4055
|
+
const errors = [];
|
|
4056
|
+
const metrics = Array.isArray(config.metrics) ? config.metrics : [];
|
|
4057
|
+
if (metrics.length === 0) {
|
|
4058
|
+
errors.push('Add at least one metric — what to compute, e.g. { "fn": "sum", "field": "Balance", "as": "TotalAmount" }.');
|
|
4059
|
+
}
|
|
4060
|
+
const groupKeys = [];
|
|
4061
|
+
const groupBy = config.groupBy;
|
|
4062
|
+
if (groupBy !== undefined && groupBy !== null) {
|
|
4063
|
+
if (!Array.isArray(groupBy)) {
|
|
4064
|
+
errors.push('groupBy must be a list of record fields (dot-paths), e.g. ["CustomerRef.name"].');
|
|
4065
|
+
} else {
|
|
4066
|
+
for (const g of groupBy) {
|
|
4067
|
+
if (typeof g !== "string" || g.trim() === "") {
|
|
4068
|
+
errors.push("groupBy has an empty entry — each one is a record field (dot-path).");
|
|
4069
|
+
continue;
|
|
4070
|
+
}
|
|
4071
|
+
groupKeys.push(groupKeyName(g));
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
const seen = new Set;
|
|
4076
|
+
for (const key of groupKeys) {
|
|
4077
|
+
if (seen.has(key)) {
|
|
4078
|
+
errors.push(`Two group-by fields both land under "${key}" — group by fields whose last segment differs.`);
|
|
4079
|
+
}
|
|
4080
|
+
seen.add(key);
|
|
4081
|
+
}
|
|
4082
|
+
metrics.forEach((m, i) => {
|
|
4083
|
+
const where = m && typeof m === "object" && typeof m.as === "string" && m.as ? `"${m.as}"` : `metric ${i + 1}`;
|
|
4084
|
+
if (!m || typeof m !== "object") {
|
|
4085
|
+
errors.push(`${where}: a metric is an object { "fn", "field", "as" }.`);
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
const fn = m.fn;
|
|
4089
|
+
const fnValid = isOneOf(AGGREGATE_FUNCTIONS, fn);
|
|
4090
|
+
if (fn === undefined || fn === null || fn === "") {
|
|
4091
|
+
errors.push(`${where}: fn is required — one of ${FUNCTION_LIST}.`);
|
|
4092
|
+
} else if (!fnValid) {
|
|
4093
|
+
errors.push(`${where}: fn must be one of: ${FUNCTION_LIST} (got "${String(fn)}").`);
|
|
4094
|
+
}
|
|
4095
|
+
const field = m.field;
|
|
4096
|
+
if (field !== undefined && field !== null && typeof field !== "string") {
|
|
4097
|
+
errors.push(`${where}: field must be a string — a dot-path on the record, e.g. "Balance".`);
|
|
4098
|
+
} else if (fnValid && fn !== "count" && (typeof field !== "string" || field.trim() === "")) {
|
|
4099
|
+
errors.push(`${where}: ${fn} needs a field — the record key it reads, e.g. "Balance".`);
|
|
4100
|
+
}
|
|
4101
|
+
const as = m.as;
|
|
4102
|
+
if (typeof as !== "string" || as === "") {
|
|
4103
|
+
errors.push(`${where}: as is required — the output key the value lands under, e.g. "TotalAmount".`);
|
|
4104
|
+
} else if (RESERVED_KEYS2.has(as)) {
|
|
4105
|
+
errors.push(`"${as}" is reserved — pick another output key.`);
|
|
4106
|
+
} else if (!KEY_PATTERN2.test(as)) {
|
|
4107
|
+
errors.push(`"${as}" is not a valid output key (letters, digits, underscore; must not start with a digit).`);
|
|
4108
|
+
} else if (seen.has(as)) {
|
|
4109
|
+
errors.push(`Duplicate output key "${as}" — every metric and group-by key lands under its own name.`);
|
|
4110
|
+
} else {
|
|
4111
|
+
seen.add(as);
|
|
4112
|
+
}
|
|
4113
|
+
const decimals = m.decimals;
|
|
4114
|
+
if (decimals !== undefined && decimals !== null) {
|
|
4115
|
+
if (typeof decimals !== "number" || !Number.isInteger(decimals) || decimals < 0 || decimals > 10) {
|
|
4116
|
+
errors.push(`${where}: decimals must be a whole number from 0 to 10.`);
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
});
|
|
4120
|
+
const outputAs = config.outputAs;
|
|
4121
|
+
if (outputAs !== undefined && outputAs !== null && outputAs !== "") {
|
|
4122
|
+
if (typeof outputAs !== "string" || !KEY_PATTERN2.test(outputAs)) {
|
|
4123
|
+
errors.push('outputAs must be a single name (letters, digits, underscore), e.g. "totals" — later steps read {{totals[].<key>}}.');
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
const arrayPath = config.arrayPath;
|
|
4127
|
+
if (arrayPath !== undefined && arrayPath !== null && typeof arrayPath !== "string") {
|
|
4128
|
+
errors.push("arrayPath must be a string — a path to an array in the upstream output, or empty for its records.");
|
|
4129
|
+
}
|
|
4130
|
+
return { valid: errors.length === 0, errors };
|
|
4131
|
+
},
|
|
4132
|
+
computeOutputSchema(config, source) {
|
|
4133
|
+
const properties = {};
|
|
4134
|
+
for (const g of Array.isArray(config.groupBy) ? config.groupBy : []) {
|
|
4135
|
+
if (typeof g !== "string" || g.trim() === "")
|
|
4136
|
+
continue;
|
|
4137
|
+
properties[groupKeyName(g)] = { ...leafOf(schemaAt(source, g), "string"), description: `group: ${g}` };
|
|
4138
|
+
}
|
|
4139
|
+
for (const m of Array.isArray(config.metrics) ? config.metrics : []) {
|
|
4140
|
+
if (!m || typeof m !== "object" || typeof m.as !== "string" || !m.as || !isOneOf(AGGREGATE_FUNCTIONS, m.fn))
|
|
4141
|
+
continue;
|
|
4142
|
+
properties[m.as] = metricSchema(m, source);
|
|
4143
|
+
}
|
|
4144
|
+
return { type: "object", properties };
|
|
4145
|
+
},
|
|
4146
|
+
merge(userConfig) {
|
|
4147
|
+
return { ...AggregateConfig.defaults, ...userConfig };
|
|
4148
|
+
},
|
|
4149
|
+
getLabel(config) {
|
|
4150
|
+
const metrics = Array.isArray(config.metrics) ? config.metrics : [];
|
|
4151
|
+
const parts = metrics.filter((m) => m && typeof m === "object" && m.fn).map((m) => m.field ? `${m.fn}(${m.field})` : String(m.fn));
|
|
4152
|
+
const by = Array.isArray(config.groupBy) && config.groupBy.length > 0 ? ` by ${config.groupBy.map(groupKeyName).join(", ")}` : "";
|
|
4153
|
+
return parts.length > 0 ? `${parts.join(", ")}${by}` : "Aggregate";
|
|
4154
|
+
}
|
|
4155
|
+
};
|
|
4156
|
+
// src/catalog/logic/for-each/recordSchema.ts
|
|
4157
|
+
function resolveSchemaPath(root, path) {
|
|
4158
|
+
if (!root)
|
|
4159
|
+
return;
|
|
4160
|
+
const parts = path.split(".").filter(Boolean);
|
|
4161
|
+
let cur = root;
|
|
4162
|
+
for (const part of parts) {
|
|
4163
|
+
if (cur?.type === "object" && cur.properties && part in cur.properties) {
|
|
4164
|
+
cur = cur.properties[part];
|
|
4165
|
+
continue;
|
|
4166
|
+
}
|
|
4167
|
+
if (cur?.type === "array" && cur.items?.properties && part in cur.items.properties) {
|
|
4168
|
+
cur = cur.items.properties[part];
|
|
4169
|
+
continue;
|
|
4170
|
+
}
|
|
4171
|
+
return;
|
|
4172
|
+
}
|
|
4173
|
+
if (cur?.type === "array" && cur.items)
|
|
4174
|
+
return cur.items;
|
|
4175
|
+
if (cur?.type === "object" && cur.properties)
|
|
4176
|
+
return cur;
|
|
4177
|
+
return;
|
|
4178
|
+
}
|
|
4179
|
+
function resolveSamplePath(sample, path) {
|
|
4180
|
+
const parts = path.split(".").filter(Boolean);
|
|
4181
|
+
let cur = sample;
|
|
4182
|
+
for (const part of parts) {
|
|
4183
|
+
if (cur === null || cur === undefined)
|
|
4184
|
+
return;
|
|
4185
|
+
if (Array.isArray(cur)) {
|
|
4186
|
+
if (cur.length === 0 || typeof cur[0] !== "object" || cur[0] === null)
|
|
4187
|
+
return;
|
|
4188
|
+
cur = cur[0][part];
|
|
4189
|
+
continue;
|
|
4190
|
+
}
|
|
4191
|
+
if (typeof cur === "object") {
|
|
4192
|
+
cur = cur[part];
|
|
4193
|
+
continue;
|
|
4194
|
+
}
|
|
4195
|
+
return;
|
|
4196
|
+
}
|
|
4197
|
+
return cur;
|
|
4198
|
+
}
|
|
4199
|
+
function sampleRecords(node) {
|
|
4200
|
+
const output = node?.executionData?.output;
|
|
4201
|
+
if (!output)
|
|
4202
|
+
return null;
|
|
4203
|
+
if (Array.isArray(output))
|
|
4204
|
+
return output;
|
|
4205
|
+
const data = output.data;
|
|
4206
|
+
return Array.isArray(data) ? data : null;
|
|
4207
|
+
}
|
|
4208
|
+
var isRootPath = (path) => path === "" || path === "data";
|
|
4209
|
+
function resolveRecordSchema(upstreamNodes, arrayPath) {
|
|
4210
|
+
const node = upstreamNodes?.[0];
|
|
4211
|
+
if (!node)
|
|
4212
|
+
return;
|
|
4213
|
+
const path = (arrayPath || "").trim();
|
|
4214
|
+
const isRoot = isRootPath(path);
|
|
4215
|
+
const schema = node.outputSchema || node.config?._outputSchema;
|
|
4216
|
+
if (schema?.properties) {
|
|
4217
|
+
if (isRoot) {
|
|
4218
|
+
const hasDeepFields = Object.values(schema.properties).some((f) => f?.properties || f?.items);
|
|
4219
|
+
if (hasDeepFields)
|
|
4220
|
+
return schema;
|
|
4221
|
+
} else {
|
|
4222
|
+
const resolved = resolveSchemaPath(schema, path);
|
|
4223
|
+
if (resolved)
|
|
4224
|
+
return resolved;
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
const records = sampleRecords(node);
|
|
4228
|
+
if (records && records.length > 0 && typeof records[0] === "object" && records[0] !== null) {
|
|
4229
|
+
const sample = records[0];
|
|
4230
|
+
if (isRoot)
|
|
4231
|
+
return inferSchemaFromSample(sample);
|
|
4232
|
+
const resolved = resolveSamplePath(sample, path);
|
|
4233
|
+
if (Array.isArray(resolved) && resolved.length > 0 && typeof resolved[0] === "object" && resolved[0] !== null) {
|
|
4234
|
+
return inferSchemaFromSample(resolved[0]);
|
|
4235
|
+
}
|
|
4236
|
+
if (resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
|
|
4237
|
+
return inferSchemaFromSample(resolved);
|
|
4238
|
+
}
|
|
4239
|
+
if (!path.includes("."))
|
|
4240
|
+
return inferSchemaFromSample(sample);
|
|
4241
|
+
}
|
|
4242
|
+
return schema?.properties ? schema : undefined;
|
|
4243
|
+
}
|
|
4244
|
+
function detectRecordFields(upstreamNodes, arrayPath) {
|
|
4245
|
+
const set = new Set;
|
|
4246
|
+
const props = resolveRecordSchema(upstreamNodes, arrayPath)?.properties;
|
|
4247
|
+
if (props && typeof props === "object")
|
|
4248
|
+
for (const k of Object.keys(props))
|
|
4249
|
+
set.add(k);
|
|
4250
|
+
const records = sampleRecords(upstreamNodes?.[0]);
|
|
4251
|
+
if (records) {
|
|
4252
|
+
const path = (arrayPath || "").trim();
|
|
4253
|
+
const addKeys = (v) => {
|
|
4254
|
+
if (v && typeof v === "object" && !Array.isArray(v))
|
|
4255
|
+
for (const k of Object.keys(v))
|
|
4256
|
+
set.add(k);
|
|
4257
|
+
};
|
|
4258
|
+
for (const r of records.slice(0, 25)) {
|
|
4259
|
+
if (isRootPath(path)) {
|
|
4260
|
+
addKeys(r);
|
|
4261
|
+
continue;
|
|
4262
|
+
}
|
|
4263
|
+
const at = resolveSamplePath(r, path);
|
|
4264
|
+
if (Array.isArray(at))
|
|
4265
|
+
for (const item of at.slice(0, 25))
|
|
4266
|
+
addKeys(item);
|
|
4267
|
+
else if (at && typeof at === "object")
|
|
4268
|
+
addKeys(at);
|
|
4269
|
+
else if (!path.includes("."))
|
|
4270
|
+
addKeys(r);
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
return Array.from(set);
|
|
4274
|
+
}
|
|
3895
4275
|
// src/catalog/storage/storageCapabilities.ts
|
|
3896
4276
|
var contentTypeField = {
|
|
3897
4277
|
key: "contentType",
|
|
@@ -3961,7 +4341,18 @@ var STORAGE_PROVIDERS = {
|
|
|
3961
4341
|
operations: [
|
|
3962
4342
|
{ value: "list", label: "List files", path: { key: "folderId", label: "Folder", selectMode: "folder" }, fields: [{ key: "query", label: "Search query", type: "string", advanced: true }, { key: "pageSize", label: "Max results", type: "number", defaultValue: 50, advanced: true }] },
|
|
3963
4343
|
{ value: "download", label: "Download file", path: { key: "fileId", label: "File", selectMode: "both", required: true } },
|
|
3964
|
-
{
|
|
4344
|
+
{
|
|
4345
|
+
value: "upload",
|
|
4346
|
+
label: "Upload file",
|
|
4347
|
+
path: { key: "folderId", label: "Destination folder", selectMode: "both" },
|
|
4348
|
+
content: true,
|
|
4349
|
+
fields: [
|
|
4350
|
+
{ key: "fileName", label: "File name", type: "string", required: true },
|
|
4351
|
+
{ key: "folderName", label: "Folder path", type: "string", advanced: true, placeholder: "Reports/2026", hint: "Folders under My Drive, separated by /. Missing ones are created on the first run. Ignored when a destination folder is picked." },
|
|
4352
|
+
{ key: "ifExists", label: "If the file already exists", type: "select", options: [{ label: "Replace it (Drive keeps the versions)", value: "overwrite" }, { label: "Skip it", value: "skip" }, { label: "Keep both (rename)", value: "rename" }], defaultValue: "overwrite", advanced: true },
|
|
4353
|
+
{ key: "mimeType", label: "Content type", type: "string", advanced: true }
|
|
4354
|
+
]
|
|
4355
|
+
},
|
|
3965
4356
|
{ value: "delete", label: "Delete file", path: { key: "fileId", label: "File", selectMode: "both", required: true } },
|
|
3966
4357
|
{ value: "move", label: "Move", path: { key: "fileId", label: "File", selectMode: "both", required: true }, dest: { key: "destFolderId", label: "Destination folder", selectMode: "both", required: true } },
|
|
3967
4358
|
{ value: "copy", label: "Copy", path: { key: "fileId", label: "File", selectMode: "both", required: true }, dest: { key: "destFolderId", label: "Destination folder", selectMode: "both" }, fields: [{ key: "fileName", label: "New name", type: "string" }] },
|
|
@@ -4080,7 +4471,13 @@ var AiAgentConfig = {
|
|
|
4080
4471
|
schema: {
|
|
4081
4472
|
agentId: { type: "string", required: true, description: "Public id of a saved agent (agt_…)" },
|
|
4082
4473
|
input: { type: "text", required: false, description: "The prompt; may reference upstream data with {{ods://…}} tokens. Empty uses the first upstream output." },
|
|
4083
|
-
overrides: { type: "object", required: false, description: "Per-step overrides: model, temperature, topP, maxTokens, reasoningEffort, verbosity, batchSize, instructions, instructionsMode, expectedSchema" }
|
|
4474
|
+
overrides: { type: "object", required: false, description: "Per-step overrides: model, temperature, topP, maxTokens, reasoningEffort, verbosity, batchSize, instructions, instructionsMode, expectedSchema" },
|
|
4475
|
+
maxRows: {
|
|
4476
|
+
type: "number",
|
|
4477
|
+
required: false,
|
|
4478
|
+
min: 1,
|
|
4479
|
+
description: "Per-row mode: the most model calls this step may make, one per upstream row (default 50, at most 1000). More rows stop the step before any call."
|
|
4480
|
+
}
|
|
4084
4481
|
},
|
|
4085
4482
|
validate: (config) => {
|
|
4086
4483
|
const errors = [];
|
|
@@ -4089,6 +4486,9 @@ var AiAgentConfig = {
|
|
|
4089
4486
|
if (config.overrides !== undefined && (typeof config.overrides !== "object" || config.overrides === null || Array.isArray(config.overrides))) {
|
|
4090
4487
|
errors.push("overrides must be an object");
|
|
4091
4488
|
}
|
|
4489
|
+
if (config.maxRows !== undefined && !(Number.isInteger(config.maxRows) && config.maxRows >= 1 && config.maxRows <= 1000)) {
|
|
4490
|
+
errors.push("maxRows must be a whole number from 1 to 1000");
|
|
4491
|
+
}
|
|
4092
4492
|
return { valid: errors.length === 0, errors };
|
|
4093
4493
|
},
|
|
4094
4494
|
merge: (userConfig) => ({ ...AiAgentConfig.defaults, ...userConfig }),
|
|
@@ -5619,6 +6019,7 @@ var CONFIG_CONTRACTS = {
|
|
|
5619
6019
|
}
|
|
5620
6020
|
},
|
|
5621
6021
|
transform: { schema: TransformConfig.schema, validate: asValidator(TransformConfig.validate) },
|
|
6022
|
+
aggregate: { schema: AggregateConfig.schema, validate: asValidator(AggregateConfig.validate) },
|
|
5622
6023
|
condition: { schema: ConditionConfig.schema, validate: asValidator(ConditionConfig.validate) },
|
|
5623
6024
|
for_each: { schema: ForEachConfig.schema, validate: asValidator(ForEachConfig.validate) },
|
|
5624
6025
|
file_operation: { schema: FileOperationConfig.schema, validate: asValidator(FileOperationConfig.validate) },
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export { queryDefinition, } from "./catalog/general/query/index";
|
|
|
37
37
|
export { isQueryableConnector, } from "./catalog/general/query/config";
|
|
38
38
|
export { AggregateFunction, type BuildQueryConfig, type ColumnSchema, type ConnectorSchema, JoinType, LogicalOperator, type QueryExecutionResult, type QueryField, type QueryFilter, type QueryFilterGroup, type QueryJoin, type QuerySort, SortDirection, type VisualQueryConfig, } from "./catalog/general/query/types";
|
|
39
39
|
export { DATABASE_CONNECTOR_IDS, type FieldMapping, TRANSFORM_MODES, TRANSFORM_OPERATIONS, TransformConfig, type TransformConfigType, type TransformOperation, } from "./catalog/general/transform/config";
|
|
40
|
+
export { AGGREGATE_FUNCTIONS, AGGREGATE_FUNCTION_DESCRIPTIONS, AggregateConfig, type AggregateConfigType, type AggregateFn, type AggregateMetric, type AggregateSchemaNode, groupKeyName, } from "./catalog/general/aggregate/config";
|
|
41
|
+
export { detectRecordFields, resolveRecordSchema, type RecordSchemaLike, } from "./catalog/logic/for-each/recordSchema";
|
|
40
42
|
export { STORAGE_CONNECTOR_IDS, type StorageExtraField, type StoragePathField, getStorageProvider, storageConfigKeys, } from "./catalog/storage/storageCapabilities";
|
|
41
43
|
export { ManualTriggerConfig } from "./catalog/triggers/manual-trigger/index";
|
|
42
44
|
export { AiAgentConfig, type AiAgentConfigType, type AiAgentOverrides } from "./catalog/ai/ai-agent/config";
|