@octaviaflow/flow-rules 0.11.0 → 0.12.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.
@@ -36,6 +36,7 @@ export declare const ConsoleLogConfig: {
36
36
  level: {
37
37
  type: string;
38
38
  required: boolean;
39
+ options: string[];
39
40
  description: string;
40
41
  };
41
42
  data: {
@@ -25,6 +25,7 @@ export declare const DelayConfig: {
25
25
  unit: {
26
26
  type: string;
27
27
  required: boolean;
28
+ options: string[];
28
29
  description: string;
29
30
  };
30
31
  };
@@ -72,6 +72,7 @@ export declare const HTTPRequestConfig: {
72
72
  apiType: {
73
73
  type: string;
74
74
  required: boolean;
75
+ options: string[];
75
76
  };
76
77
  baseUrl: {
77
78
  type: string;
@@ -84,6 +85,7 @@ export declare const HTTPRequestConfig: {
84
85
  bodyType: {
85
86
  type: string;
86
87
  required: boolean;
88
+ options: string[];
87
89
  };
88
90
  formData: {
89
91
  type: string;
@@ -124,6 +126,7 @@ export declare const HTTPRequestConfig: {
124
126
  responseType: {
125
127
  type: string;
126
128
  required: boolean;
129
+ options: string[];
127
130
  };
128
131
  expectedContentTypes: {
129
132
  type: string;
@@ -132,6 +135,7 @@ export declare const HTTPRequestConfig: {
132
135
  method: {
133
136
  type: string;
134
137
  required: boolean;
138
+ options: string[];
135
139
  description: string;
136
140
  };
137
141
  url: {
@@ -29,6 +29,8 @@ export declare const EmailConfig: {
29
29
  operation: {
30
30
  type: string;
31
31
  required: boolean;
32
+ options: string[];
33
+ description: string;
32
34
  };
33
35
  to: {
34
36
  type: string;
@@ -17,6 +17,7 @@ export declare const WebhookCallConfig: {
17
17
  method: {
18
18
  type: string;
19
19
  required: boolean;
20
+ options: string[];
20
21
  };
21
22
  payload: {
22
23
  type: string;
@@ -25,6 +25,7 @@ export declare const WhatsappSendConfig: {
25
25
  mode: {
26
26
  type: string;
27
27
  required: boolean;
28
+ options: string[];
28
29
  };
29
30
  templateName: {
30
31
  type: string;
@@ -48,10 +48,14 @@ export declare const BuildQueryContractSchema: {
48
48
  readonly mode: {
49
49
  readonly type: "select";
50
50
  readonly required: true;
51
+ readonly options: readonly ["visual", "sql"];
52
+ readonly description: "visual: the query is built from visualConfig; sql: the query is the text in sqlConfig.sql";
51
53
  };
52
54
  readonly queryType: {
53
55
  readonly type: "select";
54
56
  readonly required: true;
57
+ readonly options: readonly ["select", "insert", "update", "delete", "custom"];
58
+ readonly description: "Only select is a read; the write types exist for the SQL mode of database connectors";
55
59
  };
56
60
  readonly visualConfig: {
57
61
  readonly type: "object";
@@ -26,6 +26,14 @@ export interface FieldMapping {
26
26
  depth?: number;
27
27
  }
28
28
  export type TransformMode = 'visual' | 'expression';
29
+ /**
30
+ * The authoring modes. `visual` is the Field Mapper and the only mode the panel
31
+ * offers; `expression` is the retired JavaScript mode, kept so steps saved in it
32
+ * still run. Anything else is not a mode: the Engine fails the step with
33
+ * "Unknown transform mode" at run time, against the customer's live systems —
34
+ * which is how an agent's guess of `"append"` surfaced (DEV-421).
35
+ */
36
+ export declare const TRANSFORM_MODES: readonly TransformMode[];
29
37
  /** Data operation the user wants to perform */
30
38
  export type TransformOperation = 'upsert' | 'insert' | 'update' | 'delete';
31
39
  export declare const TRANSFORM_OPERATIONS: {
@@ -82,6 +90,8 @@ export declare const TransformConfig: {
82
90
  operation: {
83
91
  type: string;
84
92
  required: boolean;
93
+ options: readonly TransformOperation[];
94
+ description: string;
85
95
  };
86
96
  connectorId: {
87
97
  type: string;
@@ -90,14 +100,19 @@ export declare const TransformConfig: {
90
100
  database: {
91
101
  type: string;
92
102
  required: boolean;
103
+ description: string;
93
104
  };
94
105
  objectName: {
95
106
  type: string;
96
107
  required: boolean;
108
+ dynamic: boolean;
109
+ description: string;
97
110
  };
98
111
  mode: {
99
112
  type: string;
100
113
  required: boolean;
114
+ options: readonly TransformMode[];
115
+ description: string;
101
116
  };
102
117
  mappings: {
103
118
  type: string;
@@ -32,6 +32,7 @@ export declare const ConditionConfig: {
32
32
  mode: {
33
33
  type: string;
34
34
  required: boolean;
35
+ options: string[];
35
36
  };
36
37
  filters: {
37
38
  type: string;
@@ -68,10 +68,14 @@ export declare const ForEachConfig: {
68
68
  arrayPath: {
69
69
  type: string;
70
70
  required: boolean;
71
+ dynamic: boolean;
72
+ description: string;
71
73
  };
72
74
  iterationMode: {
73
75
  type: string;
74
76
  required: boolean;
77
+ options: string[];
78
+ description: string;
75
79
  };
76
80
  itemVariable: {
77
81
  type: string;
@@ -21,6 +21,7 @@ export declare const FileOperationConfig: {
21
21
  operation: {
22
22
  type: string;
23
23
  required: boolean;
24
+ options: string[];
24
25
  };
25
26
  sourcePath: {
26
27
  type: string;
@@ -12,6 +12,7 @@ export declare const FormatConfig: {
12
12
  format: {
13
13
  type: string;
14
14
  required: boolean;
15
+ options: string[];
15
16
  };
16
17
  };
17
18
  validate: (config: Partial<FormatConfigType>) => {
@@ -14,6 +14,7 @@ export declare const ScheduleTriggerConfig: {
14
14
  scheduleType: {
15
15
  type: string;
16
16
  required: boolean;
17
+ options: string[];
17
18
  };
18
19
  interval: {
19
20
  type: string;
@@ -23,6 +24,7 @@ export declare const ScheduleTriggerConfig: {
23
24
  intervalUnit: {
24
25
  type: string;
25
26
  required: boolean;
27
+ options: string[];
26
28
  };
27
29
  cronExpression: {
28
30
  type: string;
@@ -16,6 +16,7 @@ export declare const WebhookTriggerConfig: {
16
16
  method: {
17
17
  type: string;
18
18
  required: boolean;
19
+ options: string[];
19
20
  };
20
21
  requireAuth: {
21
22
  type: string;
@@ -55,6 +55,7 @@ export declare const DebugConfig: {
55
55
  mode: {
56
56
  type: string;
57
57
  required: boolean;
58
+ options: string[];
58
59
  };
59
60
  label: {
60
61
  type: string;
package/dist/index.cjs CHANGED
@@ -88,6 +88,7 @@ __export(exports_src, {
88
88
  NodeStatus: () => NodeStatus,
89
89
  ONEDRIVE_OPERATIONS: () => ONEDRIVE_OPERATIONS,
90
90
  OneDriveActionConfig: () => OneDriveActionConfig,
91
+ PLATFORM_CONFIG_KEYS: () => PLATFORM_CONFIG_KEYS,
91
92
  QueryMode: () => QueryMode,
92
93
  QueryType: () => QueryType,
93
94
  S3ActionConfig: () => S3ActionConfig,
@@ -95,6 +96,7 @@ __export(exports_src, {
95
96
  STORAGE_CONNECTOR_IDS: () => STORAGE_CONNECTOR_IDS,
96
97
  SortDirection: () => SortDirection,
97
98
  StorageActionConfig: () => StorageActionConfig,
99
+ TRANSFORM_MODES: () => TRANSFORM_MODES,
98
100
  TRANSFORM_OPERATIONS: () => TRANSFORM_OPERATIONS,
99
101
  TransformConfig: () => TransformConfig,
100
102
  USER_FUNCTION_PREFIX: () => USER_FUNCTION_PREFIX,
@@ -136,6 +138,7 @@ __export(exports_src, {
136
138
  mappingTransformFunctionName: () => mappingTransformFunctionName,
137
139
  migrateDelayConfig: () => migrateDelayConfig,
138
140
  queryDefinition: () => queryDefinition,
141
+ selectsWithoutValues: () => selectsWithoutValues,
139
142
  storageConfigKeys: () => storageConfigKeys,
140
143
  storageOperations: () => storageOperations,
141
144
  summarizeConnectorTriggerEvents: () => summarizeConnectorTriggerEvents,
@@ -1410,8 +1413,18 @@ function isQueryableConnector(connector) {
1410
1413
  }
1411
1414
  var BuildQueryContractSchema = {
1412
1415
  connectorId: { type: "connection", required: true },
1413
- mode: { type: "select", required: true },
1414
- queryType: { type: "select", required: true },
1416
+ mode: {
1417
+ type: "select",
1418
+ required: true,
1419
+ options: ["visual", "sql"],
1420
+ description: "visual: the query is built from visualConfig; sql: the query is the text in sqlConfig.sql"
1421
+ },
1422
+ queryType: {
1423
+ type: "select",
1424
+ required: true,
1425
+ options: ["select", "insert", "update", "delete", "custom"],
1426
+ description: "Only select is a read; the write types exist for the SQL mode of database connectors"
1427
+ },
1415
1428
  visualConfig: { type: "object", required: false },
1416
1429
  sqlConfig: { type: "object", required: false },
1417
1430
  timeout: { type: "number", required: false },
@@ -3311,7 +3324,7 @@ var ConditionConfig = {
3311
3324
  condition: ""
3312
3325
  },
3313
3326
  schema: {
3314
- mode: { type: "select", required: true },
3327
+ mode: { type: "select", required: true, options: ["visual", "expression"] },
3315
3328
  filters: { type: "array", required: false },
3316
3329
  condition: { type: "expression", required: false }
3317
3330
  },
@@ -3381,8 +3394,18 @@ var ForEachConfig = {
3381
3394
  collect: "array"
3382
3395
  },
3383
3396
  schema: {
3384
- arrayPath: { type: "select", required: false },
3385
- iterationMode: { type: "select", required: false },
3397
+ arrayPath: {
3398
+ type: "select",
3399
+ required: false,
3400
+ dynamic: true,
3401
+ 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"
3402
+ },
3403
+ iterationMode: {
3404
+ type: "select",
3405
+ required: false,
3406
+ options: ["records", "fields", "range", "nested", "batch", "cursor", "while", "array"],
3407
+ description: "What one iteration is: records (one per record, the default; array is its legacy alias), fields, range, nested, batch, cursor or while"
3408
+ },
3386
3409
  itemVariable: { type: "string", required: true },
3387
3410
  indexVariable: { type: "string", required: false },
3388
3411
  batchSize: { type: "number", required: false },
@@ -3586,7 +3609,7 @@ function fieldToJsonSchema(f) {
3586
3609
  // src/catalog/utility/debug/config.ts
3587
3610
  var DebugConfig = {
3588
3611
  schema: {
3589
- mode: { type: "select", required: true },
3612
+ mode: { type: "select", required: true, options: ["log", "snapshot", "watch", "assert", "profile"] },
3590
3613
  label: { type: "string", required: false },
3591
3614
  pinned: { type: "boolean", required: false },
3592
3615
  condition: { type: "expression", required: false },
@@ -3701,7 +3724,12 @@ var EmailConfig = {
3701
3724
  },
3702
3725
  schema: {
3703
3726
  connection: { type: "connection", required: true },
3704
- operation: { type: "select", required: true },
3727
+ operation: {
3728
+ type: "select",
3729
+ required: true,
3730
+ options: ["send", "read", "search", "delete", "move", "mark_read"],
3731
+ description: "Only send runs today (DEV-349); the other values are kept so older steps still parse, and validate() rejects them"
3732
+ },
3705
3733
  to: { type: "string", required: false },
3706
3734
  cc: { type: "string", required: false },
3707
3735
  bcc: { type: "string", required: false },
@@ -3754,12 +3782,15 @@ var EmailConfig = {
3754
3782
  }
3755
3783
  };
3756
3784
  // src/catalog/general/transform/config.ts
3785
+ var TRANSFORM_MODES = ["visual", "expression"];
3757
3786
  var TRANSFORM_OPERATIONS = [
3758
3787
  { id: "upsert", label: "Upsert" },
3759
3788
  { id: "insert", label: "Insert" },
3760
3789
  { id: "update", label: "Update" },
3761
3790
  { id: "delete", label: "Delete" }
3762
3791
  ];
3792
+ var TRANSFORM_OPERATION_IDS = TRANSFORM_OPERATIONS.map((o) => o.id);
3793
+ var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
3763
3794
  var DATABASE_CONNECTOR_IDS = new Set(["mongodb", "postgresql", "mysql", "sqlite", "google_sheets"]);
3764
3795
  var TransformConfig = {
3765
3796
  defaults: {
@@ -3783,11 +3814,30 @@ var TransformConfig = {
3783
3814
  },
3784
3815
  schema: {
3785
3816
  name: { type: "string", required: false },
3786
- operation: { type: "select", required: true },
3817
+ operation: {
3818
+ type: "select",
3819
+ required: true,
3820
+ options: TRANSFORM_OPERATION_IDS,
3821
+ description: "What to do with each mapped record at the target: upsert, insert, update or delete"
3822
+ },
3787
3823
  connectorId: { type: "connection", required: true },
3788
- database: { type: "text", required: false },
3789
- objectName: { type: "select", required: true },
3790
- mode: { type: "select", required: true },
3824
+ database: {
3825
+ type: "text",
3826
+ required: false,
3827
+ description: "Database name for MongoDB / PostgreSQL / MySQL / SQLite; the spreadsheet id for Google Sheets"
3828
+ },
3829
+ objectName: {
3830
+ type: "select",
3831
+ required: true,
3832
+ dynamic: true,
3833
+ description: "The table, entity, object or sheet tab to write to — the names come from the connected app (describe_connector_object)"
3834
+ },
3835
+ mode: {
3836
+ type: "select",
3837
+ required: true,
3838
+ options: TRANSFORM_MODES,
3839
+ 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`."
3840
+ },
3791
3841
  mappings: { type: "array", required: false },
3792
3842
  expression: { type: "code", required: false }
3793
3843
  },
@@ -3801,6 +3851,12 @@ var TransformConfig = {
3801
3851
  const errors = [];
3802
3852
  if (!config.operation) {
3803
3853
  errors.push("Operation is required");
3854
+ } else if (!isOneOf(TRANSFORM_OPERATION_IDS, config.operation)) {
3855
+ errors.push(`Operation must be one of: ${TRANSFORM_OPERATION_IDS.join(", ")} (got "${String(config.operation)}")`);
3856
+ }
3857
+ const mode = config.mode;
3858
+ if (mode !== undefined && mode !== null && mode !== "" && !isOneOf(TRANSFORM_MODES, mode)) {
3859
+ 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.`);
3804
3860
  }
3805
3861
  if (!config.connectorId) {
3806
3862
  errors.push("Target connector is required");
@@ -5004,6 +5060,7 @@ var ConsoleLogConfig = {
5004
5060
  level: {
5005
5061
  type: "select",
5006
5062
  required: false,
5063
+ options: ["info", "warning", "error", "debug"],
5007
5064
  description: "Log level for the message"
5008
5065
  },
5009
5066
  data: {
@@ -5054,6 +5111,7 @@ var DelayConfig = {
5054
5111
  unit: {
5055
5112
  type: "select",
5056
5113
  required: false,
5114
+ options: ["milliseconds", "seconds", "minutes", "hours"],
5057
5115
  description: "Time unit for the duration"
5058
5116
  }
5059
5117
  },
@@ -5118,10 +5176,10 @@ var HTTPRequestConfig = {
5118
5176
  expectedContentTypes: []
5119
5177
  },
5120
5178
  schema: {
5121
- apiType: { type: "select", required: false },
5179
+ apiType: { type: "select", required: false, options: ["rest", "graphql"] },
5122
5180
  baseUrl: { type: "string", required: false },
5123
5181
  queryParams: { type: "array", required: false },
5124
- bodyType: { type: "select", required: false },
5182
+ bodyType: { type: "select", required: false, options: ["none", "json", "raw", "form-data", "xml"] },
5125
5183
  formData: { type: "array", required: false },
5126
5184
  graphqlQuery: { type: "code", required: false },
5127
5185
  graphqlVariables: { type: "json", required: false },
@@ -5131,11 +5189,12 @@ var HTTPRequestConfig = {
5131
5189
  retry: { type: "object", required: false },
5132
5190
  pagination: { type: "object", required: false },
5133
5191
  extract: { type: "object", required: false },
5134
- responseType: { type: "select", required: false },
5192
+ responseType: { type: "select", required: false, options: ["json", "text", "binary"] },
5135
5193
  expectedContentTypes: { type: "array", required: false },
5136
5194
  method: {
5137
5195
  type: "select",
5138
5196
  required: true,
5197
+ options: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
5139
5198
  description: "HTTP method to use"
5140
5199
  },
5141
5200
  url: {
@@ -5325,7 +5384,7 @@ var WebhookCallConfig = {
5325
5384
  },
5326
5385
  schema: {
5327
5386
  url: { type: "url", required: true },
5328
- method: { type: "select", required: true },
5387
+ method: { type: "select", required: true, options: ["POST", "PUT", "PATCH", "GET"] },
5329
5388
  payload: { type: "json", required: false },
5330
5389
  headers: { type: "json", required: false }
5331
5390
  },
@@ -5350,7 +5409,7 @@ var WhatsappSendConfig = {
5350
5409
  schema: {
5351
5410
  connection: { type: "connection", required: true },
5352
5411
  to: { type: "string", required: true },
5353
- mode: { type: "select", required: true },
5412
+ mode: { type: "select", required: true, options: ["template", "text"] },
5354
5413
  templateName: { type: "string", required: false },
5355
5414
  templateLanguage: { type: "string", required: false },
5356
5415
  templateVariables: { type: "array", required: false },
@@ -5397,7 +5456,7 @@ var FileOperationConfig = {
5397
5456
  },
5398
5457
  schema: {
5399
5458
  connection: { type: "connection", required: true },
5400
- operation: { type: "select", required: true },
5459
+ operation: { type: "select", required: true, options: ["upload", "download", "delete", "list", "copy", "move", "exists"] },
5401
5460
  sourcePath: { type: "string", required: false },
5402
5461
  destinationPath: { type: "string", required: true },
5403
5462
  fileContent: { type: "string", required: false },
@@ -5431,7 +5490,7 @@ var FormatConfig = {
5431
5490
  defaults: { input: "", format: "json" },
5432
5491
  schema: {
5433
5492
  input: { type: "expression", required: true },
5434
- format: { type: "select", required: true }
5493
+ format: { type: "select", required: true, options: ["json", "csv", "xml", "yaml"] }
5435
5494
  },
5436
5495
  validate: (config) => {
5437
5496
  const errors = [];
@@ -5456,9 +5515,9 @@ var ScheduleTriggerConfig = {
5456
5515
  timezone: "UTC"
5457
5516
  },
5458
5517
  schema: {
5459
- scheduleType: { type: "select", required: true },
5518
+ scheduleType: { type: "select", required: true, options: ["interval", "cron", "specific"] },
5460
5519
  interval: { type: "number", required: false, min: 1 },
5461
- intervalUnit: { type: "select", required: false },
5520
+ intervalUnit: { type: "select", required: false, options: ["minutes", "hours", "days", "weeks"] },
5462
5521
  cronExpression: { type: "string", required: false },
5463
5522
  timezone: { type: "string", required: false }
5464
5523
  },
@@ -5496,7 +5555,7 @@ var WebhookTriggerConfig = {
5496
5555
  },
5497
5556
  schema: {
5498
5557
  webhookPath: { type: "string", required: false },
5499
- method: { type: "select", required: true },
5558
+ method: { type: "select", required: true, options: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
5500
5559
  requireAuth: { type: "boolean", required: false }
5501
5560
  },
5502
5561
  validate: (config) => {
@@ -5630,11 +5689,18 @@ function contractFieldsFor(definition) {
5630
5689
  label: humanise(key),
5631
5690
  type: CONTRACT_TYPE_TO_FIELD_TYPE[field.type] ?? "string" /* String */,
5632
5691
  validation: { required: field.required === true },
5692
+ ...typeof field.description === "string" && field.description ? { description: field.description } : {},
5633
5693
  ...field.options ? { options: field.options.map((v) => ({ label: String(v), value: v })) } : {}
5634
5694
  });
5635
5695
  }
5636
5696
  return [...declared, ...derived];
5637
5697
  }
5698
+ function selectsWithoutValues(actionId) {
5699
+ const contract = contractFor(actionId);
5700
+ if (!contract)
5701
+ return [];
5702
+ return Object.entries(contract.schema).filter(([, f]) => f.type === "select" && !(f.options && f.options.length > 0) && f.dynamic !== true).map(([key]) => key);
5703
+ }
5638
5704
  function contractDrift(definition) {
5639
5705
  const contract = contractFor(definition.id);
5640
5706
  if (!contract)
@@ -5664,6 +5730,10 @@ function contractDerivedKeys(definition) {
5664
5730
  var MAX_STEP_CONFIG_BYTES = 256 * 1024;
5665
5731
  var FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
5666
5732
  var MAX_DEPTH = 12;
5733
+ var PLATFORM_CONFIG_KEYS = {
5734
+ flowId: { key: "flowId", label: "Flow", type: "string" /* String */, validation: { required: false } },
5735
+ outputAs: { key: "outputAs", label: "Output as", type: "string" /* String */, validation: { required: false } }
5736
+ };
5667
5737
  function typeMatches(field, value) {
5668
5738
  if (value === null || value === undefined || value === "")
5669
5739
  return true;
@@ -5771,8 +5841,10 @@ function validateStepConfig(step) {
5771
5841
  const declared = new Map(contractFieldsFor(definition).map((f) => [f.key, f]));
5772
5842
  const entries = Object.entries(config);
5773
5843
  for (const [key, value] of entries) {
5774
- const field = declared.get(key);
5844
+ const field = declared.get(key) ?? PLATFORM_CONFIG_KEYS[key];
5775
5845
  if (!field) {
5846
+ if (isEditorPrivateKey(key))
5847
+ continue;
5776
5848
  issues.push({
5777
5849
  severity: "warning",
5778
5850
  code: "UNDECLARED_FIELD",
package/dist/index.d.ts CHANGED
@@ -36,7 +36,7 @@ export { EmailConfig, } from "./catalog/communication/email-send/config";
36
36
  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
- export { DATABASE_CONNECTOR_IDS, type FieldMapping, TRANSFORM_OPERATIONS, TransformConfig, type TransformConfigType, type TransformOperation, } from "./catalog/general/transform/config";
39
+ export { DATABASE_CONNECTOR_IDS, type FieldMapping, TRANSFORM_MODES, TRANSFORM_OPERATIONS, TransformConfig, type TransformConfigType, type TransformOperation, } from "./catalog/general/transform/config";
40
40
  export { STORAGE_CONNECTOR_IDS, type StorageExtraField, type StoragePathField, getStorageProvider, storageConfigKeys, } from "./catalog/storage/storageCapabilities";
41
41
  export { ManualTriggerConfig } from "./catalog/triggers/manual-trigger/index";
42
42
  export { AiAgentConfig, type AiAgentConfigType, type AiAgentOverrides } from "./catalog/ai/ai-agent/config";
@@ -60,6 +60,6 @@ export { CONNECTOR_EVENT_ENTITIES, EVENT_CAPABLE_CONNECTOR_IDS, getEntitiesForCo
60
60
  export * from "./catalog/workflow";
61
61
  export { type FlowConnection, type FlowData, type FlowStep, generateConnectionsFromSteps, transformConnectionToEdge, transformFlowToWorkflow, transformStepToNode, validateFlowData, } from "./rules/flowShape";
62
62
  export { flattenNestedWorkflow } from "./rules/flattenNestedWorkflow";
63
- export { configErrors, MAX_STEP_CONFIG_BYTES, validateStepConfig, validateStepConfigs, type ConfigIssue, type ConfigIssueCode, } from "./rules/configSafety";
63
+ export { configErrors, MAX_STEP_CONFIG_BYTES, validateStepConfig, PLATFORM_CONFIG_KEYS, validateStepConfigs, type ConfigIssue, type ConfigIssueCode, } from "./rules/configSafety";
64
64
  export { isMappingFunction, MAPPING_FUNCTION_SPECS, MAPPING_FUNCTIONS, mappingTransformFunctionName, USER_FUNCTION_PREFIX, type MappingFunction, type MappingFunctionSpec, } from "./rules/mappingFunctions";
65
- export { CONFIG_CONTRACTS, contractDerivedKeys, contractDrift, contractFieldsFor, contractVocabularyDrift, contractFor, declaredKeys, isEditorPrivateKey, type ConfigContract, type ContractField, type ContractFieldType, } from "./rules/configContracts";
65
+ export { CONFIG_CONTRACTS, contractDerivedKeys, contractDrift, contractFieldsFor, contractVocabularyDrift, contractFor, declaredKeys, isEditorPrivateKey, selectsWithoutValues, type ConfigContract, type ContractField, type ContractFieldType, } from "./rules/configContracts";
package/dist/index.js CHANGED
@@ -1256,8 +1256,18 @@ function isQueryableConnector(connector) {
1256
1256
  }
1257
1257
  var BuildQueryContractSchema = {
1258
1258
  connectorId: { type: "connection", required: true },
1259
- mode: { type: "select", required: true },
1260
- queryType: { type: "select", required: true },
1259
+ mode: {
1260
+ type: "select",
1261
+ required: true,
1262
+ options: ["visual", "sql"],
1263
+ description: "visual: the query is built from visualConfig; sql: the query is the text in sqlConfig.sql"
1264
+ },
1265
+ queryType: {
1266
+ type: "select",
1267
+ required: true,
1268
+ options: ["select", "insert", "update", "delete", "custom"],
1269
+ description: "Only select is a read; the write types exist for the SQL mode of database connectors"
1270
+ },
1261
1271
  visualConfig: { type: "object", required: false },
1262
1272
  sqlConfig: { type: "object", required: false },
1263
1273
  timeout: { type: "number", required: false },
@@ -3157,7 +3167,7 @@ var ConditionConfig = {
3157
3167
  condition: ""
3158
3168
  },
3159
3169
  schema: {
3160
- mode: { type: "select", required: true },
3170
+ mode: { type: "select", required: true, options: ["visual", "expression"] },
3161
3171
  filters: { type: "array", required: false },
3162
3172
  condition: { type: "expression", required: false }
3163
3173
  },
@@ -3227,8 +3237,18 @@ var ForEachConfig = {
3227
3237
  collect: "array"
3228
3238
  },
3229
3239
  schema: {
3230
- arrayPath: { type: "select", required: false },
3231
- iterationMode: { type: "select", required: false },
3240
+ arrayPath: {
3241
+ type: "select",
3242
+ required: false,
3243
+ dynamic: true,
3244
+ 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"
3245
+ },
3246
+ iterationMode: {
3247
+ type: "select",
3248
+ required: false,
3249
+ options: ["records", "fields", "range", "nested", "batch", "cursor", "while", "array"],
3250
+ description: "What one iteration is: records (one per record, the default; array is its legacy alias), fields, range, nested, batch, cursor or while"
3251
+ },
3232
3252
  itemVariable: { type: "string", required: true },
3233
3253
  indexVariable: { type: "string", required: false },
3234
3254
  batchSize: { type: "number", required: false },
@@ -3432,7 +3452,7 @@ function fieldToJsonSchema(f) {
3432
3452
  // src/catalog/utility/debug/config.ts
3433
3453
  var DebugConfig = {
3434
3454
  schema: {
3435
- mode: { type: "select", required: true },
3455
+ mode: { type: "select", required: true, options: ["log", "snapshot", "watch", "assert", "profile"] },
3436
3456
  label: { type: "string", required: false },
3437
3457
  pinned: { type: "boolean", required: false },
3438
3458
  condition: { type: "expression", required: false },
@@ -3547,7 +3567,12 @@ var EmailConfig = {
3547
3567
  },
3548
3568
  schema: {
3549
3569
  connection: { type: "connection", required: true },
3550
- operation: { type: "select", required: true },
3570
+ operation: {
3571
+ type: "select",
3572
+ required: true,
3573
+ options: ["send", "read", "search", "delete", "move", "mark_read"],
3574
+ description: "Only send runs today (DEV-349); the other values are kept so older steps still parse, and validate() rejects them"
3575
+ },
3551
3576
  to: { type: "string", required: false },
3552
3577
  cc: { type: "string", required: false },
3553
3578
  bcc: { type: "string", required: false },
@@ -3600,12 +3625,15 @@ var EmailConfig = {
3600
3625
  }
3601
3626
  };
3602
3627
  // src/catalog/general/transform/config.ts
3628
+ var TRANSFORM_MODES = ["visual", "expression"];
3603
3629
  var TRANSFORM_OPERATIONS = [
3604
3630
  { id: "upsert", label: "Upsert" },
3605
3631
  { id: "insert", label: "Insert" },
3606
3632
  { id: "update", label: "Update" },
3607
3633
  { id: "delete", label: "Delete" }
3608
3634
  ];
3635
+ var TRANSFORM_OPERATION_IDS = TRANSFORM_OPERATIONS.map((o) => o.id);
3636
+ var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
3609
3637
  var DATABASE_CONNECTOR_IDS = new Set(["mongodb", "postgresql", "mysql", "sqlite", "google_sheets"]);
3610
3638
  var TransformConfig = {
3611
3639
  defaults: {
@@ -3629,11 +3657,30 @@ var TransformConfig = {
3629
3657
  },
3630
3658
  schema: {
3631
3659
  name: { type: "string", required: false },
3632
- operation: { type: "select", required: true },
3660
+ operation: {
3661
+ type: "select",
3662
+ required: true,
3663
+ options: TRANSFORM_OPERATION_IDS,
3664
+ description: "What to do with each mapped record at the target: upsert, insert, update or delete"
3665
+ },
3633
3666
  connectorId: { type: "connection", required: true },
3634
- database: { type: "text", required: false },
3635
- objectName: { type: "select", required: true },
3636
- mode: { type: "select", required: true },
3667
+ database: {
3668
+ type: "text",
3669
+ required: false,
3670
+ description: "Database name for MongoDB / PostgreSQL / MySQL / SQLite; the spreadsheet id for Google Sheets"
3671
+ },
3672
+ objectName: {
3673
+ type: "select",
3674
+ required: true,
3675
+ dynamic: true,
3676
+ description: "The table, entity, object or sheet tab to write to — the names come from the connected app (describe_connector_object)"
3677
+ },
3678
+ mode: {
3679
+ type: "select",
3680
+ required: true,
3681
+ options: TRANSFORM_MODES,
3682
+ 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`."
3683
+ },
3637
3684
  mappings: { type: "array", required: false },
3638
3685
  expression: { type: "code", required: false }
3639
3686
  },
@@ -3647,6 +3694,12 @@ var TransformConfig = {
3647
3694
  const errors = [];
3648
3695
  if (!config.operation) {
3649
3696
  errors.push("Operation is required");
3697
+ } else if (!isOneOf(TRANSFORM_OPERATION_IDS, config.operation)) {
3698
+ errors.push(`Operation must be one of: ${TRANSFORM_OPERATION_IDS.join(", ")} (got "${String(config.operation)}")`);
3699
+ }
3700
+ const mode = config.mode;
3701
+ if (mode !== undefined && mode !== null && mode !== "" && !isOneOf(TRANSFORM_MODES, mode)) {
3702
+ 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
3703
  }
3651
3704
  if (!config.connectorId) {
3652
3705
  errors.push("Target connector is required");
@@ -4850,6 +4903,7 @@ var ConsoleLogConfig = {
4850
4903
  level: {
4851
4904
  type: "select",
4852
4905
  required: false,
4906
+ options: ["info", "warning", "error", "debug"],
4853
4907
  description: "Log level for the message"
4854
4908
  },
4855
4909
  data: {
@@ -4900,6 +4954,7 @@ var DelayConfig = {
4900
4954
  unit: {
4901
4955
  type: "select",
4902
4956
  required: false,
4957
+ options: ["milliseconds", "seconds", "minutes", "hours"],
4903
4958
  description: "Time unit for the duration"
4904
4959
  }
4905
4960
  },
@@ -4964,10 +5019,10 @@ var HTTPRequestConfig = {
4964
5019
  expectedContentTypes: []
4965
5020
  },
4966
5021
  schema: {
4967
- apiType: { type: "select", required: false },
5022
+ apiType: { type: "select", required: false, options: ["rest", "graphql"] },
4968
5023
  baseUrl: { type: "string", required: false },
4969
5024
  queryParams: { type: "array", required: false },
4970
- bodyType: { type: "select", required: false },
5025
+ bodyType: { type: "select", required: false, options: ["none", "json", "raw", "form-data", "xml"] },
4971
5026
  formData: { type: "array", required: false },
4972
5027
  graphqlQuery: { type: "code", required: false },
4973
5028
  graphqlVariables: { type: "json", required: false },
@@ -4977,11 +5032,12 @@ var HTTPRequestConfig = {
4977
5032
  retry: { type: "object", required: false },
4978
5033
  pagination: { type: "object", required: false },
4979
5034
  extract: { type: "object", required: false },
4980
- responseType: { type: "select", required: false },
5035
+ responseType: { type: "select", required: false, options: ["json", "text", "binary"] },
4981
5036
  expectedContentTypes: { type: "array", required: false },
4982
5037
  method: {
4983
5038
  type: "select",
4984
5039
  required: true,
5040
+ options: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
4985
5041
  description: "HTTP method to use"
4986
5042
  },
4987
5043
  url: {
@@ -5171,7 +5227,7 @@ var WebhookCallConfig = {
5171
5227
  },
5172
5228
  schema: {
5173
5229
  url: { type: "url", required: true },
5174
- method: { type: "select", required: true },
5230
+ method: { type: "select", required: true, options: ["POST", "PUT", "PATCH", "GET"] },
5175
5231
  payload: { type: "json", required: false },
5176
5232
  headers: { type: "json", required: false }
5177
5233
  },
@@ -5196,7 +5252,7 @@ var WhatsappSendConfig = {
5196
5252
  schema: {
5197
5253
  connection: { type: "connection", required: true },
5198
5254
  to: { type: "string", required: true },
5199
- mode: { type: "select", required: true },
5255
+ mode: { type: "select", required: true, options: ["template", "text"] },
5200
5256
  templateName: { type: "string", required: false },
5201
5257
  templateLanguage: { type: "string", required: false },
5202
5258
  templateVariables: { type: "array", required: false },
@@ -5243,7 +5299,7 @@ var FileOperationConfig = {
5243
5299
  },
5244
5300
  schema: {
5245
5301
  connection: { type: "connection", required: true },
5246
- operation: { type: "select", required: true },
5302
+ operation: { type: "select", required: true, options: ["upload", "download", "delete", "list", "copy", "move", "exists"] },
5247
5303
  sourcePath: { type: "string", required: false },
5248
5304
  destinationPath: { type: "string", required: true },
5249
5305
  fileContent: { type: "string", required: false },
@@ -5277,7 +5333,7 @@ var FormatConfig = {
5277
5333
  defaults: { input: "", format: "json" },
5278
5334
  schema: {
5279
5335
  input: { type: "expression", required: true },
5280
- format: { type: "select", required: true }
5336
+ format: { type: "select", required: true, options: ["json", "csv", "xml", "yaml"] }
5281
5337
  },
5282
5338
  validate: (config) => {
5283
5339
  const errors = [];
@@ -5302,9 +5358,9 @@ var ScheduleTriggerConfig = {
5302
5358
  timezone: "UTC"
5303
5359
  },
5304
5360
  schema: {
5305
- scheduleType: { type: "select", required: true },
5361
+ scheduleType: { type: "select", required: true, options: ["interval", "cron", "specific"] },
5306
5362
  interval: { type: "number", required: false, min: 1 },
5307
- intervalUnit: { type: "select", required: false },
5363
+ intervalUnit: { type: "select", required: false, options: ["minutes", "hours", "days", "weeks"] },
5308
5364
  cronExpression: { type: "string", required: false },
5309
5365
  timezone: { type: "string", required: false }
5310
5366
  },
@@ -5342,7 +5398,7 @@ var WebhookTriggerConfig = {
5342
5398
  },
5343
5399
  schema: {
5344
5400
  webhookPath: { type: "string", required: false },
5345
- method: { type: "select", required: true },
5401
+ method: { type: "select", required: true, options: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
5346
5402
  requireAuth: { type: "boolean", required: false }
5347
5403
  },
5348
5404
  validate: (config) => {
@@ -5476,11 +5532,18 @@ function contractFieldsFor(definition) {
5476
5532
  label: humanise(key),
5477
5533
  type: CONTRACT_TYPE_TO_FIELD_TYPE[field.type] ?? "string" /* String */,
5478
5534
  validation: { required: field.required === true },
5535
+ ...typeof field.description === "string" && field.description ? { description: field.description } : {},
5479
5536
  ...field.options ? { options: field.options.map((v) => ({ label: String(v), value: v })) } : {}
5480
5537
  });
5481
5538
  }
5482
5539
  return [...declared, ...derived];
5483
5540
  }
5541
+ function selectsWithoutValues(actionId) {
5542
+ const contract = contractFor(actionId);
5543
+ if (!contract)
5544
+ return [];
5545
+ return Object.entries(contract.schema).filter(([, f]) => f.type === "select" && !(f.options && f.options.length > 0) && f.dynamic !== true).map(([key]) => key);
5546
+ }
5484
5547
  function contractDrift(definition) {
5485
5548
  const contract = contractFor(definition.id);
5486
5549
  if (!contract)
@@ -5510,6 +5573,10 @@ function contractDerivedKeys(definition) {
5510
5573
  var MAX_STEP_CONFIG_BYTES = 256 * 1024;
5511
5574
  var FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
5512
5575
  var MAX_DEPTH = 12;
5576
+ var PLATFORM_CONFIG_KEYS = {
5577
+ flowId: { key: "flowId", label: "Flow", type: "string" /* String */, validation: { required: false } },
5578
+ outputAs: { key: "outputAs", label: "Output as", type: "string" /* String */, validation: { required: false } }
5579
+ };
5513
5580
  function typeMatches(field, value) {
5514
5581
  if (value === null || value === undefined || value === "")
5515
5582
  return true;
@@ -5617,8 +5684,10 @@ function validateStepConfig(step) {
5617
5684
  const declared = new Map(contractFieldsFor(definition).map((f) => [f.key, f]));
5618
5685
  const entries = Object.entries(config);
5619
5686
  for (const [key, value] of entries) {
5620
- const field = declared.get(key);
5687
+ const field = declared.get(key) ?? PLATFORM_CONFIG_KEYS[key];
5621
5688
  if (!field) {
5689
+ if (isEditorPrivateKey(key))
5690
+ continue;
5622
5691
  issues.push({
5623
5692
  severity: "warning",
5624
5693
  code: "UNDECLARED_FIELD",
@@ -5793,6 +5862,7 @@ export {
5793
5862
  NodeStatus,
5794
5863
  ONEDRIVE_OPERATIONS,
5795
5864
  OneDriveActionConfig,
5865
+ PLATFORM_CONFIG_KEYS,
5796
5866
  QueryMode,
5797
5867
  QueryType,
5798
5868
  S3ActionConfig,
@@ -5800,6 +5870,7 @@ export {
5800
5870
  STORAGE_CONNECTOR_IDS,
5801
5871
  SortDirection,
5802
5872
  StorageActionConfig,
5873
+ TRANSFORM_MODES,
5803
5874
  TRANSFORM_OPERATIONS,
5804
5875
  TransformConfig,
5805
5876
  USER_FUNCTION_PREFIX,
@@ -5841,6 +5912,7 @@ export {
5841
5912
  mappingTransformFunctionName,
5842
5913
  migrateDelayConfig,
5843
5914
  queryDefinition,
5915
+ selectsWithoutValues,
5844
5916
  storageConfigKeys,
5845
5917
  storageOperations,
5846
5918
  summarizeConnectorTriggerEvents,
@@ -42,7 +42,23 @@ export type ContractFieldType = "string" | "text" | "select" | "json" | "connect
42
42
  export interface ContractField {
43
43
  type: string;
44
44
  required?: boolean;
45
+ /**
46
+ * The values a `select` accepts, when the set is fixed. `describe_action_type`
47
+ * lists them and the fence warns on a value outside them (`FIELD_BAD_OPTION`).
48
+ * A select with no `options` must say `dynamic: true` instead — the catalog
49
+ * test fails on one that says neither, because a select an agent cannot see
50
+ * the values of is a guess waiting to fail at run time (`mode: "append"` on a
51
+ * Transform, DEV-421).
52
+ */
45
53
  options?: ReadonlyArray<string | number>;
54
+ /**
55
+ * The values come from the customer's own system or from the upstream data
56
+ * (an object name, a sheet tab, an array path) — not from the catalog. Say
57
+ * where in `description`; `describe_connector_object` is the usual answer.
58
+ */
59
+ dynamic?: boolean;
60
+ /** Shown by the panel and by `describe_action_type`. */
61
+ description?: string;
46
62
  [extra: string]: unknown;
47
63
  }
48
64
  export interface ConfigContract {
@@ -80,6 +96,13 @@ export declare function isEditorPrivateKey(key: string): boolean;
80
96
  * editor should read.
81
97
  */
82
98
  export declare function contractFieldsFor(definition: ActionDefinition): FieldConfig[];
99
+ /**
100
+ * Every `select` in a contract that names neither its `options` nor
101
+ * `dynamic: true`. A non-empty result is a bug: `describe_action_type` would
102
+ * show the field as a choice with nothing to choose from, and an agent would
103
+ * guess. The catalog test fails on any.
104
+ */
105
+ export declare function selectsWithoutValues(actionId: string): string[];
83
106
  /**
84
107
  * `inputSchema` keys the contract does NOT know. A non-empty result is a bug:
85
108
  * `describe_action_type` is advertising a key the platform does not store under
@@ -33,8 +33,26 @@
33
33
  * An enumerated field's options are checked but only WARN, for the same
34
34
  * reason — see the note at that check.
35
35
  */
36
+ import { type FieldConfig } from "../catalog/types";
36
37
  /** Largest config a single step may carry, serialised. */
37
38
  export declare const MAX_STEP_CONFIG_BYTES: number;
39
+ /**
40
+ * Keys that belong to the PLATFORM, not to any action — on nearly every step,
41
+ * declared by no contract, and rightly so:
42
+ *
43
+ * - `flowId`: NodeConfigPanel seeds every custom panel's config with the flow
44
+ * it belongs to, and it is saved with the step;
45
+ * - `outputAs`: the Engine reads it for EVERY step type — the step's key in
46
+ * the run's data bag.
47
+ *
48
+ * Type-checked like a declared field; never reported as undeclared; never
49
+ * advertised by `contractFieldsFor` (an agent is told the action's own keys).
50
+ * Before this, every panel-built step raised one to four UNDECLARED_FIELD
51
+ * warnings for these and the editor's own `_` snapshots, which drowned the
52
+ * warnings that mean something — `connection` on a storage step, `message` on
53
+ * a Slack step (DEV-412).
54
+ */
55
+ export declare const PLATFORM_CONFIG_KEYS: Readonly<Record<string, FieldConfig>>;
38
56
  export type ConfigIssueCode = "FORBIDDEN_KEY" | "CONFIG_TOO_LARGE" | "CONFIG_TOO_DEEP" | "FIELD_WRONG_TYPE" | "FIELD_BAD_OPTION" | "UNDECLARED_FIELD";
39
57
  export interface ConfigIssue {
40
58
  severity: "error" | "warning";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octaviaflow/flow-rules",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "The flow action catalog and the rules engine behind Flow Doctor — one definition of what a step accepts and what makes a flow valid, shared by the editor and the server",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",