@octaviaflow/flow-rules 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -23,12 +23,12 @@ export { actionsRegistry, getAllActions, getActionById, getActionsByCategory, }
23
23
  export { validateNode, validateEdge, validateWorkflow, getNodeValidationStatus, getEdgeValidationStatus, type ValidationError, type ValidationResult, } from "./rules/workflowValidator";
24
24
  export { getNodeConfigStatus, type NodeConfigStatus, type NodeConfigStatusResult, } from "./rules/nodeConfigStatus";
25
25
  export { generateWorkflowId, generateStepId, generateEdgeId, generateUUIDv4, } from "./ids";
26
- export { CONDITION_OPERATORS, type ConditionConfig, type ConditionFilter, type ConditionOperator, } from "./catalog/logic/condition/config";
27
- export { type ForEachConfig, type ForEachConfigType, type ForEachMode, } from "./catalog/logic/for-each/config";
26
+ export { CONDITION_OPERATORS, ConditionConfig, type ConditionFilter, type ConditionOperator, } from "./catalog/logic/condition/config";
27
+ export { ForEachConfig, type ForEachConfigType, type ForEachMode, } from "./catalog/logic/for-each/config";
28
28
  export { buildPathOptions, collectArrayPaths, collectArrayPathsFromSample, inferSchemaFromSample, type UpstreamNodeRef, } from "./catalog/logic/for-each/pathOptions";
29
- export { type QueryMode, type QueryType } from "./catalog/general/query/types";
30
- export { type VariableField, type VariableSource, type VariableSourceKind, type VariableType, type VariablesConfig, } from "./catalog/utility/variables/config";
31
- export { type DebugConfig, type DebugConfigType, type DebugMode, type DebugSamplingStrategy, type WatchExpression, } from "./catalog/utility/debug/config";
29
+ export { QueryMode, QueryType } from "./catalog/general/query/types";
30
+ export { type VariableField, type VariableSource, type VariableSourceKind, type VariableType, VariablesConfig, } from "./catalog/utility/variables/config";
31
+ export { DebugConfig, type DebugConfigType, type DebugMode, type DebugSamplingStrategy, type WatchExpression, } from "./catalog/utility/debug/config";
32
32
  export { migrateDelayConfig, } from "./catalog/actions/delay/migrate";
33
33
  export { type DelayConfig, } from "./catalog/actions/delay/types";
34
34
  export { emailDefinition, } from "./catalog/communication/email-send/index";
package/dist/index.js CHANGED
@@ -1026,6 +1026,19 @@ var groupDefinition = {
1026
1026
  };
1027
1027
 
1028
1028
  // src/catalog/general/query/types.ts
1029
+ var QueryMode;
1030
+ ((QueryMode) => {
1031
+ QueryMode["Visual"] = "visual";
1032
+ QueryMode["SQL"] = "sql";
1033
+ })(QueryMode ||= {});
1034
+ var QueryType;
1035
+ ((QueryType) => {
1036
+ QueryType["Select"] = "select";
1037
+ QueryType["Insert"] = "insert";
1038
+ QueryType["Update"] = "update";
1039
+ QueryType["Delete"] = "delete";
1040
+ QueryType["Custom"] = "custom";
1041
+ })(QueryType ||= {});
1029
1042
  var LogicalOperator;
1030
1043
  ((LogicalOperator) => {
1031
1044
  LogicalOperator["And"] = "and";
@@ -2983,6 +2996,135 @@ var CONDITION_OPERATORS = [
2983
2996
  { id: "is_not_null", label: "Is Not Null", needsValue: false },
2984
2997
  { id: "is_empty", label: "Is Empty", needsValue: false }
2985
2998
  ];
2999
+ var ConditionConfig = {
3000
+ defaults: {
3001
+ mode: "visual",
3002
+ filters: [],
3003
+ condition: ""
3004
+ },
3005
+ schema: {
3006
+ mode: { type: "select", required: true },
3007
+ filters: { type: "array", required: false },
3008
+ condition: { type: "expression", required: false }
3009
+ },
3010
+ validate: (config) => {
3011
+ const errors = [];
3012
+ if (config.mode === "expression") {
3013
+ if (!config.condition) {
3014
+ errors.push("Condition expression is required");
3015
+ } else {
3016
+ try {
3017
+ new Function(`return ${config.condition}`);
3018
+ } catch (e) {
3019
+ errors.push(`Invalid expression: ${e instanceof Error ? e.message : "Unknown error"}`);
3020
+ }
3021
+ }
3022
+ } else {
3023
+ if (!config.filters || config.filters.length === 0) {
3024
+ errors.push("At least one condition filter is required");
3025
+ } else {
3026
+ config.filters.forEach((f, i) => {
3027
+ if (!f.field)
3028
+ errors.push(`Filter ${i + 1}: Field is required`);
3029
+ if (!f.operator)
3030
+ errors.push(`Filter ${i + 1}: Operator is required`);
3031
+ const opDef = CONDITION_OPERATORS.find((o) => o.id === f.operator);
3032
+ if (opDef?.needsValue && !f.value) {
3033
+ errors.push(`Filter ${i + 1}: Value is required`);
3034
+ }
3035
+ });
3036
+ }
3037
+ }
3038
+ return { valid: errors.length === 0, errors };
3039
+ },
3040
+ merge: (userConfig) => {
3041
+ return { ...ConditionConfig.defaults, ...userConfig };
3042
+ },
3043
+ generateName: (filters) => {
3044
+ if (filters.length === 0)
3045
+ return "If …";
3046
+ const parts = filters.map((f, i) => {
3047
+ const opLabel = CONDITION_OPERATORS.find((o) => o.id === f.operator)?.label || f.operator;
3048
+ const base = `${f.field || "?"} ${opLabel}${f.value ? ` ${f.value}` : ""}`;
3049
+ if (i === 0)
3050
+ return base;
3051
+ return `${(f.logicalOperator || "and").toUpperCase()} ${base}`;
3052
+ });
3053
+ return `If ${parts.join(" ")}`;
3054
+ },
3055
+ getLabel: (config) => {
3056
+ if (config.mode === "expression")
3057
+ return `If ${config.condition || "…"}`;
3058
+ return ConditionConfig.generateName(config.filters);
3059
+ },
3060
+ createFilterId: () => `cond_${Date.now()}_${randomSuffix(5)}`
3061
+ };
3062
+ // src/catalog/logic/for-each/config.ts
3063
+ var SOURCELESS_MODES = new Set(["range", "cursor", "while"]);
3064
+ var ForEachConfig = {
3065
+ defaults: {
3066
+ arrayPath: "",
3067
+ iterationMode: "records",
3068
+ itemVariable: "item",
3069
+ indexVariable: "index",
3070
+ batchSize: 0,
3071
+ continueOnError: false,
3072
+ concurrency: "sequential",
3073
+ collect: "array"
3074
+ },
3075
+ schema: {
3076
+ arrayPath: { type: "select", required: false },
3077
+ iterationMode: { type: "select", required: false },
3078
+ itemVariable: { type: "string", required: true },
3079
+ indexVariable: { type: "string", required: false },
3080
+ batchSize: { type: "number", required: false },
3081
+ continueOnError: { type: "boolean", required: false }
3082
+ },
3083
+ validate: (config) => {
3084
+ const errors = [];
3085
+ const mode = config.iterationMode || "records";
3086
+ if (mode === "range") {
3087
+ if (!config.count || config.count <= 0) {
3088
+ errors.push("Range mode needs a positive iteration count");
3089
+ }
3090
+ } else if (mode === "nested") {
3091
+ if (!config.arrayPath)
3092
+ errors.push("Select the records to drill into");
3093
+ if (!config.nestedPath)
3094
+ errors.push('Nested mode needs the sub-array path to iterate (e.g. "lineItems")');
3095
+ } else if (!SOURCELESS_MODES.has(mode)) {
3096
+ if (!config.arrayPath) {
3097
+ errors.push("Array path is required — select which records to iterate over");
3098
+ }
3099
+ }
3100
+ if (config.concurrency === "parallel" && config.maxConcurrency !== undefined && config.maxConcurrency < 1) {
3101
+ errors.push("Max concurrency must be at least 1");
3102
+ }
3103
+ if (config.batchSize !== undefined && config.batchSize < 0) {
3104
+ errors.push("Batch size must be 0 (all) or a positive number");
3105
+ }
3106
+ if (config.collect === "groupBy" && !config.groupByPath) {
3107
+ errors.push("Group-by collection needs a field path to group on");
3108
+ }
3109
+ return { valid: errors.length === 0, errors };
3110
+ },
3111
+ merge: (userConfig) => {
3112
+ return { ...ForEachConfig.defaults, ...userConfig };
3113
+ },
3114
+ getLabel: (config) => {
3115
+ const mode = config.iterationMode || "records";
3116
+ if (mode === "range")
3117
+ return `ForEach ×${config.count ?? "…"}`;
3118
+ if (mode === "cursor")
3119
+ return "ForEach (paginate)";
3120
+ if (mode === "while")
3121
+ return "ForEach (while)";
3122
+ return `ForEach in ${config.arrayPath || "…"}`;
3123
+ },
3124
+ generateName: (arrayPath) => {
3125
+ return `ForEach in ${arrayPath || "…"}`;
3126
+ }
3127
+ };
2986
3128
  // src/catalog/logic/for-each/pathOptions.ts
2987
3129
  function inferSchemaFromSample(sample) {
2988
3130
  const properties = {};
@@ -3078,6 +3220,127 @@ function buildPathOptions(upstreamNodes) {
3078
3220
  }
3079
3221
  return paths;
3080
3222
  }
3223
+ // src/catalog/utility/variables/config.ts
3224
+ var RESERVED_KEYS = new Set(["_lineage", "_meta", "_redacted", "_error"]);
3225
+ var KEY_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
3226
+ var VariablesConfig = {
3227
+ defaults: {
3228
+ fields: []
3229
+ },
3230
+ validate(config) {
3231
+ const errors = [];
3232
+ const fields = config.fields ?? [];
3233
+ const seen = new Set;
3234
+ for (const f of fields) {
3235
+ if (!f.key) {
3236
+ errors.push("Each field needs a key.");
3237
+ continue;
3238
+ }
3239
+ if (RESERVED_KEYS.has(f.key)) {
3240
+ errors.push(`"${f.key}" is reserved — pick another key.`);
3241
+ continue;
3242
+ }
3243
+ if (!KEY_PATTERN.test(f.key)) {
3244
+ errors.push(`"${f.key}" is not a valid identifier (letters, digits, underscore; must not start with a digit).`);
3245
+ continue;
3246
+ }
3247
+ if (seen.has(f.key)) {
3248
+ errors.push(`Duplicate key "${f.key}".`);
3249
+ continue;
3250
+ }
3251
+ seen.add(f.key);
3252
+ if (!f.type)
3253
+ errors.push(`"${f.key}" is missing a type.`);
3254
+ if (!f.source)
3255
+ errors.push(`"${f.key}" is missing a source.`);
3256
+ }
3257
+ return { valid: errors.length === 0, errors };
3258
+ },
3259
+ computeOutputSchema(fields) {
3260
+ const properties = {};
3261
+ for (const f of fields) {
3262
+ properties[f.key] = fieldToJsonSchema(f);
3263
+ }
3264
+ return { type: "object", properties };
3265
+ }
3266
+ };
3267
+ function fieldToJsonSchema(f) {
3268
+ const node = { type: f.type };
3269
+ if (f.description)
3270
+ node.description = f.description;
3271
+ if (f.validation?.enum)
3272
+ node.enum = f.validation.enum;
3273
+ return node;
3274
+ }
3275
+ // src/catalog/utility/debug/config.ts
3276
+ var DebugConfig = {
3277
+ defaults: {
3278
+ mode: "log",
3279
+ label: "",
3280
+ log: { level: "info", message: "" }
3281
+ },
3282
+ validate(config) {
3283
+ const errors = [];
3284
+ if (!config.mode)
3285
+ errors.push("Mode is required.");
3286
+ if (!config.label?.trim())
3287
+ errors.push("Label is required.");
3288
+ switch (config.mode) {
3289
+ case "log":
3290
+ if (!config.log?.message?.trim())
3291
+ errors.push("Log mode: message is required.");
3292
+ if (!config.log?.level)
3293
+ errors.push("Log mode: level is required.");
3294
+ break;
3295
+ case "snapshot":
3296
+ break;
3297
+ case "watch":
3298
+ if (!config.watch?.expressions?.length) {
3299
+ errors.push("Watch mode: add at least one expression.");
3300
+ } else {
3301
+ for (const e of config.watch.expressions) {
3302
+ if (!e.label?.trim())
3303
+ errors.push("Watch mode: each expression needs a label.");
3304
+ if (!e.expression?.trim())
3305
+ errors.push("Watch mode: each expression needs an expression string.");
3306
+ }
3307
+ }
3308
+ break;
3309
+ case "assert":
3310
+ if (!config.assert?.predicate?.trim())
3311
+ errors.push("Assert mode: predicate is required.");
3312
+ if (!config.assert?.failureMessage?.trim())
3313
+ errors.push("Assert mode: failure message is required.");
3314
+ break;
3315
+ case "profile":
3316
+ if (!config.profile?.scope)
3317
+ errors.push("Profile mode: scope is required.");
3318
+ break;
3319
+ }
3320
+ return { valid: errors.length === 0, errors };
3321
+ },
3322
+ migrateLegacyStub(config) {
3323
+ if (config.mode) {
3324
+ const { value: _stub, ...rest } = config;
3325
+ return { ...DebugConfig.defaults, ...rest };
3326
+ }
3327
+ return {
3328
+ mode: "log",
3329
+ label: config.label ?? "Debug",
3330
+ log: {
3331
+ level: "info",
3332
+ message: config.label ?? "Debug",
3333
+ includeInputData: true
3334
+ }
3335
+ };
3336
+ },
3337
+ merge(userConfig) {
3338
+ return { ...DebugConfig.defaults, ...userConfig };
3339
+ },
3340
+ getLabel(config) {
3341
+ return `Debug [${config.mode}]: ${config.label}`;
3342
+ }
3343
+ };
3081
3344
  // src/catalog/actions/delay/migrate.ts
3082
3345
  var LEGACY_UNITS = new Set([
3083
3346
  "milliseconds",
@@ -3435,16 +3698,22 @@ export {
3435
3698
  ActionType,
3436
3699
  AggregateFunction,
3437
3700
  CONDITION_OPERATORS,
3701
+ ConditionConfig,
3438
3702
  DATABASE_CONNECTOR_IDS,
3703
+ DebugConfig,
3439
3704
  EmailConfig,
3440
3705
  FieldType,
3706
+ ForEachConfig,
3441
3707
  JoinType,
3442
3708
  LogicalOperator,
3443
3709
  ManualTriggerConfig,
3710
+ QueryMode,
3711
+ QueryType,
3444
3712
  STORAGE_CONNECTOR_IDS,
3445
3713
  SortDirection,
3446
3714
  TRANSFORM_OPERATIONS,
3447
3715
  TransformConfig,
3716
+ VariablesConfig,
3448
3717
  actionsRegistry,
3449
3718
  buildPathOptions,
3450
3719
  collectArrayPaths,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octaviaflow/flow-rules",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
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.js",