@octaviaflow/flow-rules 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1086,6 +1086,149 @@ var BuildQueryConfigDefaults = {
1086
1086
  parameters: {}
1087
1087
  }
1088
1088
  };
1089
+ var validateBuildQueryConfig = (config) => {
1090
+ const errors = [];
1091
+ const warnings = [];
1092
+ if (!config.connectorId) {
1093
+ errors.push({
1094
+ field: "connectorId",
1095
+ message: "Please select a connector",
1096
+ severity: "error"
1097
+ });
1098
+ }
1099
+ if (!config.mode) {
1100
+ errors.push({
1101
+ field: "mode",
1102
+ message: "Query mode is required",
1103
+ severity: "error"
1104
+ });
1105
+ }
1106
+ if (!config.queryType) {
1107
+ errors.push({
1108
+ field: "queryType",
1109
+ message: "Query type is required",
1110
+ severity: "error"
1111
+ });
1112
+ }
1113
+ if (config.mode === "visual" /* Visual */) {
1114
+ const visualErrors = validateVisualQueryConfig(config.visualConfig);
1115
+ errors.push(...visualErrors);
1116
+ } else if (config.mode === "sql" /* SQL */) {
1117
+ const sqlErrors = validateSQLQueryConfig(config.sqlConfig);
1118
+ errors.push(...sqlErrors);
1119
+ }
1120
+ if (config.timeout !== undefined) {
1121
+ if (config.timeout < 1000) {
1122
+ errors.push({
1123
+ field: "timeout",
1124
+ message: "Timeout must be at least 1000ms",
1125
+ severity: "error"
1126
+ });
1127
+ } else if (config.timeout > 300000) {
1128
+ warnings.push({
1129
+ field: "timeout",
1130
+ message: "Timeout is very high (>5 minutes)",
1131
+ severity: "warning"
1132
+ });
1133
+ }
1134
+ }
1135
+ if (config.maxRows !== undefined) {
1136
+ if (config.maxRows < 1) {
1137
+ errors.push({
1138
+ field: "maxRows",
1139
+ message: "Max rows must be at least 1",
1140
+ severity: "error"
1141
+ });
1142
+ } else if (config.maxRows > 1e4) {
1143
+ warnings.push({
1144
+ field: "maxRows",
1145
+ message: "Max rows is very high (>10,000). This may impact performance.",
1146
+ severity: "warning"
1147
+ });
1148
+ }
1149
+ }
1150
+ return {
1151
+ valid: errors.length === 0,
1152
+ errors,
1153
+ warnings
1154
+ };
1155
+ };
1156
+ var validateVisualQueryConfig = (config) => {
1157
+ const errors = [];
1158
+ if (!config) {
1159
+ errors.push({
1160
+ field: "visualConfig",
1161
+ message: "Visual query configuration is required",
1162
+ severity: "error"
1163
+ });
1164
+ return errors;
1165
+ }
1166
+ if (!config.tables || config.tables.length === 0) {
1167
+ errors.push({
1168
+ field: "visualConfig.tables",
1169
+ message: "At least one table must be selected",
1170
+ severity: "error"
1171
+ });
1172
+ }
1173
+ if (!config.fields || config.fields.length === 0) {
1174
+ errors.push({
1175
+ field: "visualConfig.fields",
1176
+ message: "At least one field must be selected",
1177
+ severity: "error"
1178
+ });
1179
+ }
1180
+ if (config.tables && config.tables.length > 1) {
1181
+ if (!config.joins || config.joins.length === 0) {
1182
+ errors.push({
1183
+ field: "visualConfig.joins",
1184
+ message: "Joins are required when selecting from multiple tables",
1185
+ severity: "error"
1186
+ });
1187
+ }
1188
+ }
1189
+ const hasAggregates = config.fields?.some((field) => field.aggregate);
1190
+ const hasNonAggregates = config.fields?.some((field) => !field.aggregate);
1191
+ if (hasAggregates && hasNonAggregates) {
1192
+ if (!config.groupBy || config.groupBy.length === 0) {
1193
+ errors.push({
1194
+ field: "visualConfig.groupBy",
1195
+ message: "Group by is required when mixing aggregate and non-aggregate fields",
1196
+ severity: "error"
1197
+ });
1198
+ }
1199
+ }
1200
+ return errors;
1201
+ };
1202
+ var validateSQLQueryConfig = (config) => {
1203
+ const errors = [];
1204
+ if (!config) {
1205
+ errors.push({
1206
+ field: "sqlConfig",
1207
+ message: "SQL query configuration is required",
1208
+ severity: "error"
1209
+ });
1210
+ return errors;
1211
+ }
1212
+ if (!config.sql || config.sql.trim().length === 0) {
1213
+ errors.push({
1214
+ field: "sqlConfig.sql",
1215
+ message: "SQL query is required",
1216
+ severity: "error"
1217
+ });
1218
+ } else {
1219
+ const sql = config.sql.trim().toLowerCase();
1220
+ const dangerousKeywords = ["drop", "truncate", "alter", "create", "grant", "revoke"];
1221
+ const hasDangerousKeyword = dangerousKeywords.some((keyword) => sql.includes(keyword + " "));
1222
+ if (hasDangerousKeyword) {
1223
+ errors.push({
1224
+ field: "sqlConfig.sql",
1225
+ message: "SQL query contains potentially dangerous operations (DROP, TRUNCATE, ALTER, etc.)",
1226
+ severity: "error"
1227
+ });
1228
+ }
1229
+ }
1230
+ return errors;
1231
+ };
1089
1232
  function isQueryableConnector(connector) {
1090
1233
  if (connector.isQueryable !== undefined) {
1091
1234
  return connector.isQueryable;
@@ -1111,6 +1254,15 @@ function isQueryableConnector(connector) {
1111
1254
  ];
1112
1255
  return !storageTypes.includes(connector.type.toLowerCase());
1113
1256
  }
1257
+ var BuildQueryContractSchema = {
1258
+ connectorId: { type: "connection", required: true },
1259
+ mode: { type: "select", required: true },
1260
+ queryType: { type: "select", required: true },
1261
+ visualConfig: { type: "object", required: false },
1262
+ sqlConfig: { type: "object", required: false },
1263
+ timeout: { type: "number", required: false },
1264
+ maxRows: { type: "number", required: false }
1265
+ };
1114
1266
 
1115
1267
  // src/catalog/general/query/index.ts
1116
1268
  var queryDefinition = {
@@ -2000,7 +2152,7 @@ var slackMessageDefinition = {
2000
2152
  fields: [
2001
2153
  { key: "connection", label: "Slack Workspace", type: "connection" /* Connection */, validation: { required: true } },
2002
2154
  { key: "channel", label: "Channel/User", type: "string" /* String */, validation: { required: true } },
2003
- { key: "message", label: "Message", type: "string" /* String */, validation: { required: true } },
2155
+ { key: "text", label: "Message", type: "string" /* String */, validation: { required: true } },
2004
2156
  { key: "blocks", label: "Blocks (JSON)", type: "json" /* JSON */, validation: { required: false } }
2005
2157
  ]
2006
2158
  },
@@ -3224,6 +3376,9 @@ function buildPathOptions(upstreamNodes) {
3224
3376
  var RESERVED_KEYS = new Set(["_lineage", "_meta", "_redacted", "_error"]);
3225
3377
  var KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
3226
3378
  var VariablesConfig = {
3379
+ schema: {
3380
+ fields: { type: "array", required: true }
3381
+ },
3227
3382
  defaults: {
3228
3383
  fields: []
3229
3384
  },
@@ -3274,6 +3429,18 @@ function fieldToJsonSchema(f) {
3274
3429
  }
3275
3430
  // src/catalog/utility/debug/config.ts
3276
3431
  var DebugConfig = {
3432
+ schema: {
3433
+ mode: { type: "select", required: true },
3434
+ label: { type: "string", required: false },
3435
+ pinned: { type: "boolean", required: false },
3436
+ condition: { type: "expression", required: false },
3437
+ sampling: { type: "object", required: false },
3438
+ log: { type: "object", required: false },
3439
+ snapshot: { type: "object", required: false },
3440
+ watch: { type: "object", required: false },
3441
+ assert: { type: "object", required: false },
3442
+ profile: { type: "object", required: false }
3443
+ },
3277
3444
  defaults: {
3278
3445
  mode: "log",
3279
3446
  label: "",
@@ -3459,6 +3626,7 @@ var TransformConfig = {
3459
3626
  sheetColumns: ""
3460
3627
  },
3461
3628
  schema: {
3629
+ name: { type: "string", required: false },
3462
3630
  operation: { type: "select", required: true },
3463
3631
  connectorId: { type: "connection", required: true },
3464
3632
  database: { type: "text", required: false },
@@ -3996,6 +4164,661 @@ function validateFlowData(flow) {
3996
4164
  const f = flow;
3997
4165
  return typeof f.id === "string" && typeof f.name === "string" && f.workflow !== undefined && typeof f.workflow === "object" && f.workflow !== null && Array.isArray(f.workflow.steps);
3998
4166
  }
4167
+ // src/catalog/actions/console-log/config.ts
4168
+ var ConsoleLogConfig = {
4169
+ defaults: {
4170
+ message: "",
4171
+ level: "info",
4172
+ data: undefined,
4173
+ includeInputData: true
4174
+ },
4175
+ schema: {
4176
+ includeInputData: { type: "boolean", required: false },
4177
+ message: {
4178
+ type: "string",
4179
+ required: true,
4180
+ description: "Message to log"
4181
+ },
4182
+ level: {
4183
+ type: "select",
4184
+ required: false,
4185
+ description: "Log level for the message"
4186
+ },
4187
+ data: {
4188
+ type: "json",
4189
+ required: false,
4190
+ description: "Additional data to log"
4191
+ }
4192
+ },
4193
+ validate: (config) => {
4194
+ const errors = [];
4195
+ if (!config.message) {
4196
+ errors.push("Message is required");
4197
+ } else if (typeof config.message !== "string") {
4198
+ errors.push("Message must be a string");
4199
+ }
4200
+ if (config.level && !["info", "warning", "error", "debug"].includes(config.level)) {
4201
+ errors.push("Invalid log level");
4202
+ }
4203
+ return {
4204
+ valid: errors.length === 0,
4205
+ errors
4206
+ };
4207
+ },
4208
+ merge: (userConfig) => {
4209
+ return {
4210
+ ...ConsoleLogConfig.defaults,
4211
+ ...userConfig
4212
+ };
4213
+ },
4214
+ getLabel: (config) => {
4215
+ return `Log [${config.level}]: ${config.message}`;
4216
+ }
4217
+ };
4218
+
4219
+ // src/catalog/actions/delay/config.ts
4220
+ var DelayConfig = {
4221
+ defaults: {
4222
+ duration: 1000,
4223
+ unit: "milliseconds"
4224
+ },
4225
+ schema: {
4226
+ duration: {
4227
+ type: "number",
4228
+ required: true,
4229
+ description: "Duration to delay",
4230
+ min: 0
4231
+ },
4232
+ unit: {
4233
+ type: "select",
4234
+ required: false,
4235
+ description: "Time unit for the duration"
4236
+ }
4237
+ },
4238
+ validate: (config) => {
4239
+ const errors = [];
4240
+ if (config.duration === undefined || config.duration === null) {
4241
+ errors.push("Duration is required");
4242
+ } else if (typeof config.duration !== "number") {
4243
+ errors.push("Duration must be a number");
4244
+ } else if (config.duration < 0) {
4245
+ errors.push("Duration must be non-negative");
4246
+ }
4247
+ if (config.unit && !["milliseconds", "seconds", "minutes", "hours"].includes(config.unit)) {
4248
+ errors.push("Invalid time unit");
4249
+ }
4250
+ return {
4251
+ valid: errors.length === 0,
4252
+ errors
4253
+ };
4254
+ },
4255
+ toMilliseconds: (duration, unit = "milliseconds") => {
4256
+ const multipliers = {
4257
+ milliseconds: 1,
4258
+ seconds: 1000,
4259
+ minutes: 60000,
4260
+ hours: 3600000
4261
+ };
4262
+ return duration * (multipliers[unit] || 1);
4263
+ },
4264
+ merge: (userConfig) => {
4265
+ return {
4266
+ ...DelayConfig.defaults,
4267
+ ...userConfig
4268
+ };
4269
+ },
4270
+ getLabel: (config) => {
4271
+ return `Delay ${config.duration}${config.unit ? " " + config.unit : "ms"}`;
4272
+ }
4273
+ };
4274
+
4275
+ // src/catalog/actions/http-request/config.ts
4276
+ var HTTPRequestConfig = {
4277
+ defaults: {
4278
+ apiType: "rest",
4279
+ method: "GET",
4280
+ url: "",
4281
+ queryParams: [],
4282
+ headers: [],
4283
+ bodyType: "json",
4284
+ body: undefined,
4285
+ formData: [],
4286
+ graphqlQuery: "",
4287
+ graphqlVariables: {},
4288
+ auth: { type: "none" },
4289
+ timeout: 30000,
4290
+ failOnError: true,
4291
+ followRedirects: true,
4292
+ retry: { maxAttempts: 1, retryOn: [429, 500, 502, 503, 504], initialDelayMs: 1000, backoffFactor: 2, maxDelayMs: 30000 },
4293
+ pagination: { type: "none" },
4294
+ extract: {},
4295
+ responseType: "json",
4296
+ expectedContentTypes: []
4297
+ },
4298
+ schema: {
4299
+ apiType: { type: "select", required: false },
4300
+ baseUrl: { type: "string", required: false },
4301
+ queryParams: { type: "array", required: false },
4302
+ bodyType: { type: "select", required: false },
4303
+ formData: { type: "array", required: false },
4304
+ graphqlQuery: { type: "code", required: false },
4305
+ graphqlVariables: { type: "json", required: false },
4306
+ auth: { type: "object", required: false },
4307
+ failOnError: { type: "boolean", required: false },
4308
+ followRedirects: { type: "boolean", required: false },
4309
+ retry: { type: "object", required: false },
4310
+ pagination: { type: "object", required: false },
4311
+ extract: { type: "object", required: false },
4312
+ responseType: { type: "select", required: false },
4313
+ expectedContentTypes: { type: "array", required: false },
4314
+ method: {
4315
+ type: "select",
4316
+ required: true,
4317
+ description: "HTTP method to use"
4318
+ },
4319
+ url: {
4320
+ type: "url",
4321
+ required: true,
4322
+ description: "The URL to send the request to"
4323
+ },
4324
+ headers: {
4325
+ type: "json",
4326
+ required: false,
4327
+ description: "HTTP headers to send with the request"
4328
+ },
4329
+ body: {
4330
+ type: "json",
4331
+ required: false,
4332
+ description: "Request body (for POST, PUT, PATCH)"
4333
+ },
4334
+ timeout: {
4335
+ type: "number",
4336
+ required: false,
4337
+ description: "Request timeout in milliseconds",
4338
+ min: 1000
4339
+ }
4340
+ },
4341
+ validate: (config) => {
4342
+ const errors = [];
4343
+ const apiType = config.apiType ?? "rest";
4344
+ if (apiType === "rest" && !config.method) {
4345
+ errors.push("Method is required");
4346
+ }
4347
+ if (apiType === "graphql" && !config.graphqlQuery?.trim()) {
4348
+ errors.push("GraphQL query is required");
4349
+ }
4350
+ if (!config.url) {
4351
+ errors.push("URL is required");
4352
+ } else if (typeof config.url === "string") {
4353
+ try {
4354
+ new URL(config.url);
4355
+ } catch {
4356
+ errors.push("Invalid URL format");
4357
+ }
4358
+ }
4359
+ if (config.timeout !== undefined && typeof config.timeout === "number") {
4360
+ if (config.timeout < 1000) {
4361
+ errors.push("Timeout must be at least 1000ms");
4362
+ }
4363
+ }
4364
+ return {
4365
+ valid: errors.length === 0,
4366
+ errors
4367
+ };
4368
+ },
4369
+ merge: (userConfig) => {
4370
+ return {
4371
+ ...HTTPRequestConfig.defaults,
4372
+ ...userConfig
4373
+ };
4374
+ },
4375
+ getLabel: (config) => {
4376
+ const type = config.apiType === "graphql" ? "GraphQL" : config.method ?? "GET";
4377
+ return `${type} ${config.url}`;
4378
+ }
4379
+ };
4380
+
4381
+ // src/catalog/api/graphql-query/config.ts
4382
+ var GraphQLQueryConfig = {
4383
+ defaults: {
4384
+ endpoint: "",
4385
+ query: "",
4386
+ variables: {},
4387
+ headers: {}
4388
+ },
4389
+ schema: {
4390
+ endpoint: { type: "url", required: true },
4391
+ query: { type: "code", required: true },
4392
+ variables: { type: "json", required: false },
4393
+ headers: { type: "json", required: false }
4394
+ },
4395
+ validate: (config) => {
4396
+ const errors = [];
4397
+ if (!config.endpoint)
4398
+ errors.push("Endpoint is required");
4399
+ if (!config.query)
4400
+ errors.push("Query is required");
4401
+ return { valid: errors.length === 0, errors };
4402
+ },
4403
+ merge: (userConfig) => {
4404
+ return { ...GraphQLQueryConfig.defaults, ...userConfig };
4405
+ },
4406
+ getLabel: (config) => {
4407
+ return `GraphQL: ${config.endpoint}`;
4408
+ }
4409
+ };
4410
+
4411
+ // src/catalog/communication/google-chat/config.ts
4412
+ var GoogleChatConfig = {
4413
+ defaults: { connection: "", space: "", spaceName: "", text: "", threadKey: "" },
4414
+ schema: {
4415
+ connection: { type: "connection", required: true },
4416
+ space: { type: "string", required: true },
4417
+ text: { type: "string", required: false },
4418
+ threadKey: { type: "string", required: false }
4419
+ },
4420
+ validate: (config) => {
4421
+ const errors = [];
4422
+ if (!config.connection)
4423
+ errors.push("Connect a Google Chat account");
4424
+ if (!config.space)
4425
+ errors.push("Pick a space");
4426
+ if (!config.text && !config.message)
4427
+ errors.push("Add a message");
4428
+ return { valid: errors.length === 0, errors };
4429
+ },
4430
+ merge: (userConfig) => ({
4431
+ ...GoogleChatConfig.defaults,
4432
+ ...userConfig
4433
+ }),
4434
+ getLabel: () => "Google Chat"
4435
+ };
4436
+
4437
+ // src/catalog/communication/slack-message/config.ts
4438
+ var SlackMessageConfig = {
4439
+ defaults: { connection: "", messageType: "channel", channel: "", channelName: "", text: "", blocks: undefined },
4440
+ schema: {
4441
+ connection: { type: "connection", required: true },
4442
+ messageType: { type: "string", required: false },
4443
+ channel: { type: "string", required: true },
4444
+ text: { type: "string", required: false },
4445
+ blocks: { type: "json", required: false }
4446
+ },
4447
+ validate: (config) => {
4448
+ const errors = [];
4449
+ if (!config.connection)
4450
+ errors.push("Slack workspace connection is required");
4451
+ if (!config.channel)
4452
+ errors.push(config.messageType === "user" ? "A user is required" : "A channel is required");
4453
+ const hasText = Boolean(config.text || config.message);
4454
+ const hasBlocks = Array.isArray(config.blocks) && config.blocks.length > 0;
4455
+ if (!hasText && !hasBlocks)
4456
+ errors.push("Add a message or at least one block");
4457
+ return { valid: errors.length === 0, errors };
4458
+ },
4459
+ merge: (userConfig) => {
4460
+ return { ...SlackMessageConfig.defaults, ...userConfig };
4461
+ },
4462
+ getLabel: (config) => {
4463
+ const prefix = config.messageType === "user" ? "@" : "#";
4464
+ return config.channelName ? `Slack: ${prefix}${config.channelName}` : config.channel ? `Slack: ${config.channel}` : "Slack";
4465
+ }
4466
+ };
4467
+
4468
+ // src/catalog/communication/teams-message/config.ts
4469
+ var TeamsMessageConfig = {
4470
+ defaults: { connection: "", teamId: "", teamName: "", channelId: "", channelName: "", title: "", message: "" },
4471
+ schema: {
4472
+ connection: { type: "connection", required: true },
4473
+ teamId: { type: "string", required: true },
4474
+ channelId: { type: "string", required: true },
4475
+ title: { type: "string", required: false },
4476
+ message: { type: "string", required: true }
4477
+ },
4478
+ validate: (config) => {
4479
+ const errors = [];
4480
+ if (!config.connection)
4481
+ errors.push("Connect a Microsoft Teams account");
4482
+ if (!config.teamId)
4483
+ errors.push("Pick a team");
4484
+ if (!config.channelId)
4485
+ errors.push("Pick a channel");
4486
+ if (!config.message)
4487
+ errors.push("Add a message");
4488
+ return { valid: errors.length === 0, errors };
4489
+ },
4490
+ merge: (userConfig) => {
4491
+ return { ...TeamsMessageConfig.defaults, ...userConfig };
4492
+ },
4493
+ getLabel: (config) => config.channelName ? `Teams: #${config.channelName}` : "Teams"
4494
+ };
4495
+
4496
+ // src/catalog/communication/webhook-call/config.ts
4497
+ var WebhookCallConfig = {
4498
+ defaults: {
4499
+ url: "",
4500
+ method: "POST",
4501
+ payload: {},
4502
+ headers: {}
4503
+ },
4504
+ schema: {
4505
+ url: { type: "url", required: true },
4506
+ method: { type: "select", required: true },
4507
+ payload: { type: "json", required: false },
4508
+ headers: { type: "json", required: false }
4509
+ },
4510
+ validate: (config) => {
4511
+ const errors = [];
4512
+ if (!config.url)
4513
+ errors.push("Webhook URL is required");
4514
+ if (!config.method)
4515
+ errors.push("HTTP method is required");
4516
+ return { valid: errors.length === 0, errors };
4517
+ },
4518
+ merge: (userConfig) => {
4519
+ return { ...WebhookCallConfig.defaults, ...userConfig };
4520
+ },
4521
+ getLabel: (config) => {
4522
+ return `${config.method}: ${config.url.substring(0, 40)}...`;
4523
+ }
4524
+ };
4525
+
4526
+ // src/catalog/communication/whatsapp-send/config.ts
4527
+ var WhatsappSendConfig = {
4528
+ schema: {
4529
+ connection: { type: "connection", required: true },
4530
+ to: { type: "string", required: true },
4531
+ mode: { type: "select", required: true },
4532
+ templateName: { type: "string", required: false },
4533
+ templateLanguage: { type: "string", required: false },
4534
+ templateVariables: { type: "array", required: false },
4535
+ text: { type: "string", required: false },
4536
+ previewUrl: { type: "boolean", required: false }
4537
+ },
4538
+ defaults: {
4539
+ connection: "",
4540
+ to: "",
4541
+ mode: "template",
4542
+ templateLanguage: "en_US",
4543
+ templateVariables: []
4544
+ },
4545
+ validate: (config) => {
4546
+ const errors = [];
4547
+ if (!config.connection)
4548
+ errors.push("Select a WhatsApp connection");
4549
+ if (!config.to?.trim())
4550
+ errors.push("Recipient phone number is required");
4551
+ if (config.mode === "text") {
4552
+ if (!config.text?.trim())
4553
+ errors.push("Message text is required");
4554
+ } else {
4555
+ if (!config.templateName?.trim())
4556
+ errors.push("Choose an approved template");
4557
+ }
4558
+ return { valid: errors.length === 0, errors };
4559
+ },
4560
+ merge: (userConfig) => {
4561
+ return { ...WhatsappSendConfig.defaults, ...userConfig };
4562
+ },
4563
+ getLabel: (config) => config.mode === "text" ? `WhatsApp to ${config.to || "—"}` : `WhatsApp template "${config.templateName || "—"}" to ${config.to || "—"}`
4564
+ };
4565
+
4566
+ // src/catalog/storage/file-operation/config.ts
4567
+ var FileOperationConfig = {
4568
+ defaults: {
4569
+ connection: "",
4570
+ operation: "upload",
4571
+ sourcePath: "",
4572
+ destinationPath: "",
4573
+ fileContent: "",
4574
+ options: {}
4575
+ },
4576
+ schema: {
4577
+ connection: { type: "connection", required: true },
4578
+ operation: { type: "select", required: true },
4579
+ sourcePath: { type: "string", required: false },
4580
+ destinationPath: { type: "string", required: true },
4581
+ fileContent: { type: "string", required: false },
4582
+ options: { type: "json", required: false }
4583
+ },
4584
+ validate: (config) => {
4585
+ const errors = [];
4586
+ if (!config.connection)
4587
+ errors.push("Storage connection is required");
4588
+ if (!config.operation)
4589
+ errors.push("Operation is required");
4590
+ if (!config.destinationPath)
4591
+ errors.push("Destination path is required");
4592
+ if (config.operation === "upload" || config.operation === "copy" || config.operation === "move") {
4593
+ if (!config.sourcePath) {
4594
+ errors.push("Source path is required for upload/copy/move operations");
4595
+ }
4596
+ }
4597
+ return { valid: errors.length === 0, errors };
4598
+ },
4599
+ merge: (userConfig) => {
4600
+ return { ...FileOperationConfig.defaults, ...userConfig };
4601
+ },
4602
+ getLabel: (config) => {
4603
+ return `${config.operation.toUpperCase()}: ${config.destinationPath}`;
4604
+ }
4605
+ };
4606
+
4607
+ // src/catalog/transforms/format/config.ts
4608
+ var FormatConfig = {
4609
+ defaults: { input: "", format: "json" },
4610
+ schema: {
4611
+ input: { type: "expression", required: true },
4612
+ format: { type: "select", required: true }
4613
+ },
4614
+ validate: (config) => {
4615
+ const errors = [];
4616
+ if (!config.input)
4617
+ errors.push("Input required");
4618
+ if (!config.format)
4619
+ errors.push("Format required");
4620
+ return { valid: errors.length === 0, errors };
4621
+ },
4622
+ merge: (userConfig) => {
4623
+ return { ...FormatConfig.defaults, ...userConfig };
4624
+ },
4625
+ getLabel: (config) => `Format as ${config.format.toUpperCase()}`
4626
+ };
4627
+
4628
+ // src/catalog/triggers/schedule-trigger/config.ts
4629
+ var ScheduleTriggerConfig = {
4630
+ defaults: {
4631
+ scheduleType: "interval",
4632
+ interval: 60,
4633
+ intervalUnit: "minutes",
4634
+ timezone: "UTC"
4635
+ },
4636
+ schema: {
4637
+ scheduleType: { type: "select", required: true },
4638
+ interval: { type: "number", required: false, min: 1 },
4639
+ intervalUnit: { type: "select", required: false },
4640
+ cronExpression: { type: "string", required: false },
4641
+ timezone: { type: "string", required: false }
4642
+ },
4643
+ validate: (config) => {
4644
+ const errors = [];
4645
+ if (!config.scheduleType) {
4646
+ errors.push("Schedule type is required");
4647
+ }
4648
+ if (config.scheduleType === "interval") {
4649
+ if (!config.interval || config.interval < 1) {
4650
+ errors.push("Interval must be at least 1");
4651
+ }
4652
+ }
4653
+ if (config.scheduleType === "cron" && !config.cronExpression) {
4654
+ errors.push("Cron expression is required");
4655
+ }
4656
+ return { valid: errors.length === 0, errors };
4657
+ },
4658
+ merge: (userConfig) => {
4659
+ return { ...ScheduleTriggerConfig.defaults, ...userConfig };
4660
+ },
4661
+ getLabel: (config) => {
4662
+ if (config.scheduleType === "interval") {
4663
+ return `Every ${config.interval} ${config.intervalUnit}`;
4664
+ }
4665
+ return `Schedule: ${config.scheduleType}`;
4666
+ }
4667
+ };
4668
+
4669
+ // src/catalog/triggers/webhook-trigger/config.ts
4670
+ var WebhookTriggerConfig = {
4671
+ defaults: {
4672
+ method: "POST",
4673
+ requireAuth: true
4674
+ },
4675
+ schema: {
4676
+ webhookPath: { type: "string", required: false },
4677
+ method: { type: "select", required: true },
4678
+ requireAuth: { type: "boolean", required: false }
4679
+ },
4680
+ validate: (config) => {
4681
+ const errors = [];
4682
+ if (!config.method) {
4683
+ errors.push("HTTP method is required");
4684
+ }
4685
+ return { valid: errors.length === 0, errors };
4686
+ },
4687
+ merge: (userConfig) => {
4688
+ return { ...WebhookTriggerConfig.defaults, ...userConfig };
4689
+ },
4690
+ getLabel: (config) => {
4691
+ return `Webhook ${config.method}${config.webhookPath ? ` ${config.webhookPath}` : ""}`;
4692
+ }
4693
+ };
4694
+
4695
+ // src/catalog/utility/error/config.ts
4696
+ var ErrorConfig = {
4697
+ defaults: {
4698
+ message: "",
4699
+ code: undefined
4700
+ },
4701
+ schema: {
4702
+ message: { type: "string", required: true },
4703
+ code: { type: "string", required: false }
4704
+ },
4705
+ validate: (config) => {
4706
+ const errors = [];
4707
+ if (!config.message)
4708
+ errors.push("Error message is required");
4709
+ return { valid: errors.length === 0, errors };
4710
+ },
4711
+ merge: (userConfig) => {
4712
+ return { ...ErrorConfig.defaults, ...userConfig };
4713
+ },
4714
+ getLabel: (config) => {
4715
+ return `Error: ${config.message}`;
4716
+ }
4717
+ };
4718
+
4719
+ // src/rules/configContracts.ts
4720
+ var asValidator = (fn) => (config) => fn(config);
4721
+ var CONFIG_CONTRACTS = {
4722
+ console_log: { schema: ConsoleLogConfig.schema, validate: asValidator(ConsoleLogConfig.validate) },
4723
+ delay: { schema: DelayConfig.schema, validate: asValidator(DelayConfig.validate) },
4724
+ http_request: { schema: HTTPRequestConfig.schema, validate: asValidator(HTTPRequestConfig.validate) },
4725
+ graphql_query: { schema: GraphQLQueryConfig.schema, validate: asValidator(GraphQLQueryConfig.validate) },
4726
+ email: { schema: EmailConfig.schema, validate: asValidator(EmailConfig.validate) },
4727
+ google_chat: { schema: GoogleChatConfig.schema, validate: asValidator(GoogleChatConfig.validate) },
4728
+ slack_message: { schema: SlackMessageConfig.schema, validate: asValidator(SlackMessageConfig.validate) },
4729
+ teams_message: { schema: TeamsMessageConfig.schema, validate: asValidator(TeamsMessageConfig.validate) },
4730
+ webhook_call: { schema: WebhookCallConfig.schema, validate: asValidator(WebhookCallConfig.validate) },
4731
+ whatsapp_send: { schema: WhatsappSendConfig.schema, validate: asValidator(WhatsappSendConfig.validate) },
4732
+ query: {
4733
+ schema: BuildQueryContractSchema,
4734
+ validate: (config) => {
4735
+ const r = validateBuildQueryConfig(config);
4736
+ const errors = (r.errors ?? []).filter((e) => e.severity !== "warning").map((e) => e.message);
4737
+ return { valid: errors.length === 0, errors };
4738
+ }
4739
+ },
4740
+ transform: { schema: TransformConfig.schema, validate: asValidator(TransformConfig.validate) },
4741
+ condition: { schema: ConditionConfig.schema, validate: asValidator(ConditionConfig.validate) },
4742
+ for_each: { schema: ForEachConfig.schema, validate: asValidator(ForEachConfig.validate) },
4743
+ file_operation: { schema: FileOperationConfig.schema, validate: asValidator(FileOperationConfig.validate) },
4744
+ format: { schema: FormatConfig.schema, validate: asValidator(FormatConfig.validate) },
4745
+ manual_trigger: { schema: ManualTriggerConfig.schema, validate: asValidator(ManualTriggerConfig.validate) },
4746
+ schedule_trigger: { schema: ScheduleTriggerConfig.schema, validate: asValidator(ScheduleTriggerConfig.validate) },
4747
+ webhook_trigger: { schema: WebhookTriggerConfig.schema, validate: asValidator(WebhookTriggerConfig.validate) },
4748
+ debug: { schema: DebugConfig.schema, validate: asValidator(DebugConfig.validate) },
4749
+ error: { schema: ErrorConfig.schema, validate: asValidator(ErrorConfig.validate) },
4750
+ variables: { schema: VariablesConfig.schema, validate: asValidator(VariablesConfig.validate) }
4751
+ };
4752
+ function contractFor(actionId) {
4753
+ return actionId ? CONFIG_CONTRACTS[actionId] : undefined;
4754
+ }
4755
+ function declaredKeys(actionId) {
4756
+ const c = contractFor(actionId);
4757
+ if (!c)
4758
+ return [];
4759
+ return Object.entries(c.schema).filter(([, f]) => f.type !== "none").map(([k]) => k);
4760
+ }
4761
+ function isEditorPrivateKey(key) {
4762
+ return key.startsWith("_");
4763
+ }
4764
+ var CONTRACT_TYPE_TO_FIELD_TYPE = {
4765
+ string: "string" /* String */,
4766
+ text: "string" /* String */,
4767
+ select: "select" /* Select */,
4768
+ json: "json" /* JSON */,
4769
+ connection: "connection" /* Connection */,
4770
+ number: "number" /* Number */,
4771
+ boolean: "boolean" /* Boolean */,
4772
+ array: "array" /* Array */,
4773
+ object: "object" /* Object */,
4774
+ url: "url" /* URL */,
4775
+ expression: "expression" /* Expression */,
4776
+ code: "code" /* Code */
4777
+ };
4778
+ function humanise(key) {
4779
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/^\w/, (c) => c.toUpperCase());
4780
+ }
4781
+ function contractFieldsFor(definition) {
4782
+ const declared = definition.inputSchema?.fields ?? [];
4783
+ const contract = contractFor(definition.id);
4784
+ if (!contract)
4785
+ return declared;
4786
+ const seen = new Set(declared.map((f) => f.key));
4787
+ const derived = [];
4788
+ for (const [key, field] of Object.entries(contract.schema)) {
4789
+ if (seen.has(key) || field.type === "none")
4790
+ continue;
4791
+ derived.push({
4792
+ key,
4793
+ label: humanise(key),
4794
+ type: CONTRACT_TYPE_TO_FIELD_TYPE[field.type] ?? "string" /* String */,
4795
+ validation: { required: field.required === true },
4796
+ ...field.options ? { options: field.options.map((v) => ({ label: String(v), value: v })) } : {}
4797
+ });
4798
+ }
4799
+ return [...declared, ...derived];
4800
+ }
4801
+ function contractDrift(definition) {
4802
+ const contract = contractFor(definition.id);
4803
+ if (!contract)
4804
+ return [];
4805
+ const covers = (key) => {
4806
+ if (key in contract.schema)
4807
+ return true;
4808
+ const root = key.split(".")[0];
4809
+ const rootField = contract.schema[root];
4810
+ return !!rootField && (rootField.type === "object" || rootField.type === "json");
4811
+ };
4812
+ return (definition.inputSchema?.fields ?? []).map((f) => f.key).filter((k) => !covers(k));
4813
+ }
4814
+ function contractVocabularyDrift(actionId) {
4815
+ const c = contractFor(actionId);
4816
+ if (!c)
4817
+ return [];
4818
+ const known = new Set([...Object.keys(CONTRACT_TYPE_TO_FIELD_TYPE), "none"]);
4819
+ return Object.entries(c.schema).filter(([, f]) => !known.has(f.type)).map(([k, f]) => `${k}:${f.type}`);
4820
+ }
4821
+
3999
4822
  // src/rules/configSafety.ts
4000
4823
  var MAX_STEP_CONFIG_BYTES = 256 * 1024;
4001
4824
  var FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
@@ -4104,7 +4927,7 @@ function validateStepConfig(step) {
4104
4927
  const definition = typeof step.actionDefinitionId === "string" ? getActionById(step.actionDefinitionId) : undefined;
4105
4928
  if (!definition)
4106
4929
  return issues;
4107
- const declared = new Map(definition.inputSchema.fields.map((f) => [f.key, f]));
4930
+ const declared = new Map(contractFieldsFor(definition).map((f) => [f.key, f]));
4108
4931
  const entries = Object.entries(config);
4109
4932
  for (const [key, value] of entries) {
4110
4933
  const field = declared.get(key);
@@ -4151,12 +4974,95 @@ function validateStepConfigs(steps) {
4151
4974
  function configErrors(steps) {
4152
4975
  return validateStepConfigs(steps).filter((i) => i.severity === "error");
4153
4976
  }
4977
+ // src/rules/mappingFunctions.ts
4978
+ var str = (v) => String(v ?? "");
4979
+ var HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
4980
+ var MAPPING_FUNCTIONS = {
4981
+ concat: (...args) => args.map((a) => a == null ? "" : String(a)).join(""),
4982
+ uppercase: (val) => str(val).toUpperCase(),
4983
+ lowercase: (val) => str(val).toLowerCase(),
4984
+ trim: (val) => str(val).trim(),
4985
+ parseDate: (val) => {
4986
+ const d = new Date(String(val));
4987
+ return isNaN(d.getTime()) ? null : d.toISOString();
4988
+ },
4989
+ parseNumber: (val) => {
4990
+ const n = Number(val);
4991
+ return isNaN(n) ? null : n;
4992
+ },
4993
+ toString: (val) => str(val),
4994
+ substring: (val, start, end) => {
4995
+ const s = str(val);
4996
+ return s.substring(Number(start) || 0, end !== undefined ? Number(end) : undefined);
4997
+ },
4998
+ replace: (val, search, replacement) => str(val).replace(String(search), String(replacement)),
4999
+ default: (val, fallback) => val == null || val === "" ? fallback : val,
5000
+ normalizeUrl: (val, scheme = "https") => {
5001
+ const s = str(val).trim();
5002
+ if (!s)
5003
+ return "";
5004
+ if (HAS_SCHEME.test(s))
5005
+ return s;
5006
+ return `${String(scheme || "https")}://${s}`;
5007
+ },
5008
+ normalizeEmail: (val) => str(val).trim().toLowerCase(),
5009
+ normalizePhone: (val, countryCode) => {
5010
+ const raw = str(val).trim();
5011
+ if (!raw)
5012
+ return "";
5013
+ const plus = raw.startsWith("+");
5014
+ const digits = raw.replace(/\D/g, "");
5015
+ if (!digits)
5016
+ return "";
5017
+ if (plus)
5018
+ return `+${digits}`;
5019
+ const cc = str(countryCode).replace(/\D/g, "");
5020
+ return cc ? `+${cc}${digits}` : digits;
5021
+ },
5022
+ toDateOnly: (val) => {
5023
+ const d = new Date(String(val));
5024
+ return isNaN(d.getTime()) ? null : d.toISOString().slice(0, 10);
5025
+ }
5026
+ };
5027
+ var MAPPING_FUNCTION_SPECS = [
5028
+ { name: "concat", signature: 'concat(value, "text", …)', description: "Joins the value and any literals into one string.", example: 'concat(value, " Ltd")' },
5029
+ { name: "uppercase", signature: "uppercase(value)", description: "Upper-cases the value.", example: "uppercase(value)" },
5030
+ { name: "lowercase", signature: "lowercase(value)", description: "Lower-cases the value.", example: "lowercase(value)" },
5031
+ { name: "trim", signature: "trim(value)", description: "Removes leading and trailing whitespace.", example: "trim(value)" },
5032
+ { name: "parseDate", signature: "parseDate(value)", description: "Parses to a full ISO timestamp, or null if unparseable.", example: "parseDate(value)" },
5033
+ { name: "parseNumber", signature: "parseNumber(value)", description: "Parses to a number, or null if not numeric.", example: "parseNumber(value)" },
5034
+ { name: "toString", signature: "toString(value)", description: 'Renders the value as a string ("" when empty).', example: "toString(value)" },
5035
+ { name: "substring", signature: "substring(value, start, end?)", description: "Takes part of the value by index.", example: "substring(value, 0, 10)" },
5036
+ { name: "replace", signature: 'replace(value, "find", "with")', description: "Replaces the first match.", example: 'replace(value, "Inc", "Inc.")' },
5037
+ { name: "default", signature: 'default(value, "fallback")', description: "Uses the fallback when the value is empty or missing.", example: 'default(value, "Unknown")' },
5038
+ { name: "normalizeUrl", signature: "normalizeUrl(value, scheme?)", description: "Adds https:// when the value names no scheme. Leaves http://, mailto: and others alone.", example: "normalizeUrl(value)" },
5039
+ { name: "normalizeEmail", signature: "normalizeEmail(value)", description: "Trims and lower-cases an address.", example: "normalizeEmail(value)" },
5040
+ { name: "normalizePhone", signature: "normalizePhone(value, countryCode?)", description: "Strips punctuation to digits, keeping a leading +. With a country code, prefixes numbers that lack one.", example: 'normalizePhone(value, "+1")' },
5041
+ { name: "toDateOnly", signature: "toDateOnly(value)", description: "Reduces a timestamp to YYYY-MM-DD in UTC.", example: "toDateOnly(value)" }
5042
+ ];
5043
+ var USER_FUNCTION_PREFIX = "user.";
5044
+ function isMappingFunction(name) {
5045
+ if (!name)
5046
+ return false;
5047
+ if (name.startsWith(USER_FUNCTION_PREFIX))
5048
+ return name.length > USER_FUNCTION_PREFIX.length;
5049
+ return Object.hasOwn(MAPPING_FUNCTIONS, name);
5050
+ }
5051
+ function mappingTransformFunctionName(transform) {
5052
+ const trimmed = (transform ?? "").trim();
5053
+ if (!trimmed)
5054
+ return null;
5055
+ const normalised = /^[A-Za-z_][\w.]*$/.test(trimmed) ? `${trimmed}(value)` : trimmed;
5056
+ const match = normalised.match(/^([A-Za-z_][\w.]*)\((.*)\)$/s);
5057
+ return match ? match[1] : null;
5058
+ }
4154
5059
  export {
4155
5060
  ActionCategory,
4156
5061
  ActionStatus,
4157
5062
  ActionType,
4158
5063
  AggregateFunction,
4159
5064
  CONDITION_OPERATORS,
5065
+ CONFIG_CONTRACTS,
4160
5066
  ConditionConfig,
4161
5067
  DATABASE_CONNECTOR_IDS,
4162
5068
  DebugConfig,
@@ -4166,6 +5072,8 @@ export {
4166
5072
  ForEachConfig,
4167
5073
  JoinType,
4168
5074
  LogicalOperator,
5075
+ MAPPING_FUNCTIONS,
5076
+ MAPPING_FUNCTION_SPECS,
4169
5077
  MAX_STEP_CONFIG_BYTES,
4170
5078
  ManualTriggerConfig,
4171
5079
  NodeStatus,
@@ -4175,12 +5083,18 @@ export {
4175
5083
  SortDirection,
4176
5084
  TRANSFORM_OPERATIONS,
4177
5085
  TransformConfig,
5086
+ USER_FUNCTION_PREFIX,
4178
5087
  VariablesConfig,
4179
5088
  actionsRegistry,
4180
5089
  buildPathOptions,
4181
5090
  collectArrayPaths,
4182
5091
  collectArrayPathsFromSample,
4183
5092
  configErrors,
5093
+ contractDrift,
5094
+ contractFieldsFor,
5095
+ contractFor,
5096
+ contractVocabularyDrift,
5097
+ declaredKeys,
4184
5098
  emailDefinition,
4185
5099
  flattenNestedWorkflow,
4186
5100
  generateConnectionsFromSteps,
@@ -4196,7 +5110,10 @@ export {
4196
5110
  getNodeValidationStatus,
4197
5111
  getStorageProvider,
4198
5112
  inferSchemaFromSample,
5113
+ isEditorPrivateKey,
5114
+ isMappingFunction,
4199
5115
  isQueryableConnector,
5116
+ mappingTransformFunctionName,
4200
5117
  migrateDelayConfig,
4201
5118
  queryDefinition,
4202
5119
  storageConfigKeys,