@octaviaflow/flow-rules 0.11.1 → 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
@@ -96,6 +96,7 @@ __export(exports_src, {
96
96
  STORAGE_CONNECTOR_IDS: () => STORAGE_CONNECTOR_IDS,
97
97
  SortDirection: () => SortDirection,
98
98
  StorageActionConfig: () => StorageActionConfig,
99
+ TRANSFORM_MODES: () => TRANSFORM_MODES,
99
100
  TRANSFORM_OPERATIONS: () => TRANSFORM_OPERATIONS,
100
101
  TransformConfig: () => TransformConfig,
101
102
  USER_FUNCTION_PREFIX: () => USER_FUNCTION_PREFIX,
@@ -137,6 +138,7 @@ __export(exports_src, {
137
138
  mappingTransformFunctionName: () => mappingTransformFunctionName,
138
139
  migrateDelayConfig: () => migrateDelayConfig,
139
140
  queryDefinition: () => queryDefinition,
141
+ selectsWithoutValues: () => selectsWithoutValues,
140
142
  storageConfigKeys: () => storageConfigKeys,
141
143
  storageOperations: () => storageOperations,
142
144
  summarizeConnectorTriggerEvents: () => summarizeConnectorTriggerEvents,
@@ -1411,8 +1413,18 @@ function isQueryableConnector(connector) {
1411
1413
  }
1412
1414
  var BuildQueryContractSchema = {
1413
1415
  connectorId: { type: "connection", required: true },
1414
- mode: { type: "select", required: true },
1415
- 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
+ },
1416
1428
  visualConfig: { type: "object", required: false },
1417
1429
  sqlConfig: { type: "object", required: false },
1418
1430
  timeout: { type: "number", required: false },
@@ -3312,7 +3324,7 @@ var ConditionConfig = {
3312
3324
  condition: ""
3313
3325
  },
3314
3326
  schema: {
3315
- mode: { type: "select", required: true },
3327
+ mode: { type: "select", required: true, options: ["visual", "expression"] },
3316
3328
  filters: { type: "array", required: false },
3317
3329
  condition: { type: "expression", required: false }
3318
3330
  },
@@ -3382,8 +3394,18 @@ var ForEachConfig = {
3382
3394
  collect: "array"
3383
3395
  },
3384
3396
  schema: {
3385
- arrayPath: { type: "select", required: false },
3386
- 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
+ },
3387
3409
  itemVariable: { type: "string", required: true },
3388
3410
  indexVariable: { type: "string", required: false },
3389
3411
  batchSize: { type: "number", required: false },
@@ -3587,7 +3609,7 @@ function fieldToJsonSchema(f) {
3587
3609
  // src/catalog/utility/debug/config.ts
3588
3610
  var DebugConfig = {
3589
3611
  schema: {
3590
- mode: { type: "select", required: true },
3612
+ mode: { type: "select", required: true, options: ["log", "snapshot", "watch", "assert", "profile"] },
3591
3613
  label: { type: "string", required: false },
3592
3614
  pinned: { type: "boolean", required: false },
3593
3615
  condition: { type: "expression", required: false },
@@ -3702,7 +3724,12 @@ var EmailConfig = {
3702
3724
  },
3703
3725
  schema: {
3704
3726
  connection: { type: "connection", required: true },
3705
- 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
+ },
3706
3733
  to: { type: "string", required: false },
3707
3734
  cc: { type: "string", required: false },
3708
3735
  bcc: { type: "string", required: false },
@@ -3755,12 +3782,15 @@ var EmailConfig = {
3755
3782
  }
3756
3783
  };
3757
3784
  // src/catalog/general/transform/config.ts
3785
+ var TRANSFORM_MODES = ["visual", "expression"];
3758
3786
  var TRANSFORM_OPERATIONS = [
3759
3787
  { id: "upsert", label: "Upsert" },
3760
3788
  { id: "insert", label: "Insert" },
3761
3789
  { id: "update", label: "Update" },
3762
3790
  { id: "delete", label: "Delete" }
3763
3791
  ];
3792
+ var TRANSFORM_OPERATION_IDS = TRANSFORM_OPERATIONS.map((o) => o.id);
3793
+ var isOneOf = (set, value) => typeof value === "string" && set.includes(value);
3764
3794
  var DATABASE_CONNECTOR_IDS = new Set(["mongodb", "postgresql", "mysql", "sqlite", "google_sheets"]);
3765
3795
  var TransformConfig = {
3766
3796
  defaults: {
@@ -3784,11 +3814,30 @@ var TransformConfig = {
3784
3814
  },
3785
3815
  schema: {
3786
3816
  name: { type: "string", required: false },
3787
- 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
+ },
3788
3823
  connectorId: { type: "connection", required: true },
3789
- database: { type: "text", required: false },
3790
- objectName: { type: "select", required: true },
3791
- 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
+ },
3792
3841
  mappings: { type: "array", required: false },
3793
3842
  expression: { type: "code", required: false }
3794
3843
  },
@@ -3802,6 +3851,12 @@ var TransformConfig = {
3802
3851
  const errors = [];
3803
3852
  if (!config.operation) {
3804
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.`);
3805
3860
  }
3806
3861
  if (!config.connectorId) {
3807
3862
  errors.push("Target connector is required");
@@ -5005,6 +5060,7 @@ var ConsoleLogConfig = {
5005
5060
  level: {
5006
5061
  type: "select",
5007
5062
  required: false,
5063
+ options: ["info", "warning", "error", "debug"],
5008
5064
  description: "Log level for the message"
5009
5065
  },
5010
5066
  data: {
@@ -5055,6 +5111,7 @@ var DelayConfig = {
5055
5111
  unit: {
5056
5112
  type: "select",
5057
5113
  required: false,
5114
+ options: ["milliseconds", "seconds", "minutes", "hours"],
5058
5115
  description: "Time unit for the duration"
5059
5116
  }
5060
5117
  },
@@ -5119,10 +5176,10 @@ var HTTPRequestConfig = {
5119
5176
  expectedContentTypes: []
5120
5177
  },
5121
5178
  schema: {
5122
- apiType: { type: "select", required: false },
5179
+ apiType: { type: "select", required: false, options: ["rest", "graphql"] },
5123
5180
  baseUrl: { type: "string", required: false },
5124
5181
  queryParams: { type: "array", required: false },
5125
- bodyType: { type: "select", required: false },
5182
+ bodyType: { type: "select", required: false, options: ["none", "json", "raw", "form-data", "xml"] },
5126
5183
  formData: { type: "array", required: false },
5127
5184
  graphqlQuery: { type: "code", required: false },
5128
5185
  graphqlVariables: { type: "json", required: false },
@@ -5132,11 +5189,12 @@ var HTTPRequestConfig = {
5132
5189
  retry: { type: "object", required: false },
5133
5190
  pagination: { type: "object", required: false },
5134
5191
  extract: { type: "object", required: false },
5135
- responseType: { type: "select", required: false },
5192
+ responseType: { type: "select", required: false, options: ["json", "text", "binary"] },
5136
5193
  expectedContentTypes: { type: "array", required: false },
5137
5194
  method: {
5138
5195
  type: "select",
5139
5196
  required: true,
5197
+ options: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
5140
5198
  description: "HTTP method to use"
5141
5199
  },
5142
5200
  url: {
@@ -5326,7 +5384,7 @@ var WebhookCallConfig = {
5326
5384
  },
5327
5385
  schema: {
5328
5386
  url: { type: "url", required: true },
5329
- method: { type: "select", required: true },
5387
+ method: { type: "select", required: true, options: ["POST", "PUT", "PATCH", "GET"] },
5330
5388
  payload: { type: "json", required: false },
5331
5389
  headers: { type: "json", required: false }
5332
5390
  },
@@ -5351,7 +5409,7 @@ var WhatsappSendConfig = {
5351
5409
  schema: {
5352
5410
  connection: { type: "connection", required: true },
5353
5411
  to: { type: "string", required: true },
5354
- mode: { type: "select", required: true },
5412
+ mode: { type: "select", required: true, options: ["template", "text"] },
5355
5413
  templateName: { type: "string", required: false },
5356
5414
  templateLanguage: { type: "string", required: false },
5357
5415
  templateVariables: { type: "array", required: false },
@@ -5398,7 +5456,7 @@ var FileOperationConfig = {
5398
5456
  },
5399
5457
  schema: {
5400
5458
  connection: { type: "connection", required: true },
5401
- operation: { type: "select", required: true },
5459
+ operation: { type: "select", required: true, options: ["upload", "download", "delete", "list", "copy", "move", "exists"] },
5402
5460
  sourcePath: { type: "string", required: false },
5403
5461
  destinationPath: { type: "string", required: true },
5404
5462
  fileContent: { type: "string", required: false },
@@ -5432,7 +5490,7 @@ var FormatConfig = {
5432
5490
  defaults: { input: "", format: "json" },
5433
5491
  schema: {
5434
5492
  input: { type: "expression", required: true },
5435
- format: { type: "select", required: true }
5493
+ format: { type: "select", required: true, options: ["json", "csv", "xml", "yaml"] }
5436
5494
  },
5437
5495
  validate: (config) => {
5438
5496
  const errors = [];
@@ -5457,9 +5515,9 @@ var ScheduleTriggerConfig = {
5457
5515
  timezone: "UTC"
5458
5516
  },
5459
5517
  schema: {
5460
- scheduleType: { type: "select", required: true },
5518
+ scheduleType: { type: "select", required: true, options: ["interval", "cron", "specific"] },
5461
5519
  interval: { type: "number", required: false, min: 1 },
5462
- intervalUnit: { type: "select", required: false },
5520
+ intervalUnit: { type: "select", required: false, options: ["minutes", "hours", "days", "weeks"] },
5463
5521
  cronExpression: { type: "string", required: false },
5464
5522
  timezone: { type: "string", required: false }
5465
5523
  },
@@ -5497,7 +5555,7 @@ var WebhookTriggerConfig = {
5497
5555
  },
5498
5556
  schema: {
5499
5557
  webhookPath: { type: "string", required: false },
5500
- method: { type: "select", required: true },
5558
+ method: { type: "select", required: true, options: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
5501
5559
  requireAuth: { type: "boolean", required: false }
5502
5560
  },
5503
5561
  validate: (config) => {
@@ -5631,11 +5689,18 @@ function contractFieldsFor(definition) {
5631
5689
  label: humanise(key),
5632
5690
  type: CONTRACT_TYPE_TO_FIELD_TYPE[field.type] ?? "string" /* String */,
5633
5691
  validation: { required: field.required === true },
5692
+ ...typeof field.description === "string" && field.description ? { description: field.description } : {},
5634
5693
  ...field.options ? { options: field.options.map((v) => ({ label: String(v), value: v })) } : {}
5635
5694
  });
5636
5695
  }
5637
5696
  return [...declared, ...derived];
5638
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
+ }
5639
5704
  function contractDrift(definition) {
5640
5705
  const contract = contractFor(definition.id);
5641
5706
  if (!contract)
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";
@@ -62,4 +62,4 @@ export { type FlowConnection, type FlowData, type FlowStep, generateConnectionsF
62
62
  export { flattenNestedWorkflow } from "./rules/flattenNestedWorkflow";
63
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)
@@ -5807,6 +5870,7 @@ export {
5807
5870
  STORAGE_CONNECTOR_IDS,
5808
5871
  SortDirection,
5809
5872
  StorageActionConfig,
5873
+ TRANSFORM_MODES,
5810
5874
  TRANSFORM_OPERATIONS,
5811
5875
  TransformConfig,
5812
5876
  USER_FUNCTION_PREFIX,
@@ -5848,6 +5912,7 @@ export {
5848
5912
  mappingTransformFunctionName,
5849
5913
  migrateDelayConfig,
5850
5914
  queryDefinition,
5915
+ selectsWithoutValues,
5851
5916
  storageConfigKeys,
5852
5917
  storageOperations,
5853
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octaviaflow/flow-rules",
3
- "version": "0.11.1",
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",