@n8n/workflow-sdk 0.35.2 → 0.36.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.
Files changed (30) hide show
  1. package/dist/build.tsbuildinfo +1 -1
  2. package/dist/generate-types/generate-types.d.ts +4 -2
  3. package/dist/generate-types/generate-types.js +175 -111
  4. package/dist/generate-types/generate-types.js.map +1 -1
  5. package/dist/generate-types/generate-zod-schemas.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +4 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/validation/index.d.ts +1 -0
  10. package/dist/validation/index.js +4 -1
  11. package/dist/validation/index.js.map +1 -1
  12. package/dist/validation/required-subnode-connections.d.ts +36 -0
  13. package/dist/validation/required-subnode-connections.js +131 -0
  14. package/dist/validation/required-subnode-connections.js.map +1 -0
  15. package/dist/validation.d.ts +1 -1
  16. package/dist/validation.js +3 -1
  17. package/dist/validation.js.map +1 -1
  18. package/dist/workflow-builder/group-layout-utils.d.ts +32 -0
  19. package/dist/workflow-builder/group-layout-utils.js +211 -0
  20. package/dist/workflow-builder/group-layout-utils.js.map +1 -0
  21. package/dist/workflow-builder/layout-utils.d.ts +1 -1
  22. package/dist/workflow-builder/layout-utils.js +212 -203
  23. package/dist/workflow-builder/layout-utils.js.map +1 -1
  24. package/dist/workflow-builder/node-builders/node-builder.d.ts +1 -1
  25. package/dist/workflow-builder/node-builders/node-builder.js +12 -1
  26. package/dist/workflow-builder/node-builders/node-builder.js.map +1 -1
  27. package/dist/workflow-builder/subnode-utils.js.map +1 -1
  28. package/dist/workflow-builder.js +15 -3
  29. package/dist/workflow-builder.js.map +1 -1
  30. package/package.json +10 -7
@@ -149,10 +149,12 @@ export declare function tryMergeUxForkVariants(a: NodeProperty['displayOptions']
149
149
  export declare function mapPropertyType(prop: NodeProperty, discriminatorContext?: DiscriminatorCombination): string;
150
150
  export declare function extractDiscriminatorCombinations(node: NodeTypeDescription): DiscriminatorCombination[];
151
151
  export declare function getPropertiesForCombination(node: NodeTypeDescription, combination: DiscriminatorCombination): NodeProperty[];
152
- export declare function propertyAppliesToVersion(prop: NodeProperty, version: number): boolean;
152
+ export declare function propertyAppliesToVersion(prop: Pick<NodeProperty, 'displayOptions'>, version: number): boolean;
153
153
  export declare function filterPropertiesForVersion(properties: NodeProperty[], version: number): NodeProperty[];
154
154
  export declare function generateDiscriminatedUnion(node: NodeTypeDescription): string;
155
- export declare function generatePropertyJSDoc(prop: NodeProperty, discriminatorContext?: DiscriminatorCombination): string;
155
+ export declare function generatePropertyJSDoc(prop: NodeProperty, discriminatorContext?: DiscriminatorCombination, options?: {
156
+ conditionallyRequired?: boolean;
157
+ }): string;
156
158
  export declare function generateNodeJSDoc(node: NodeTypeDescription): string;
157
159
  export declare function generatePropertyLine(prop: NodeProperty, optional: boolean, discriminatorContext?: DiscriminatorCombination): string;
158
160
  export declare function groupVersionsByProperties(nodes: NodeTypeDescription[]): VersionGroup[];
@@ -256,6 +256,9 @@ const AI_TYPE_TO_SUBNODE_FIELD = {
256
256
  canBeMultiple: false,
257
257
  },
258
258
  };
259
+ function isNodeProperty(option) {
260
+ return typeof option.type === 'string' && typeof option.displayName === 'string';
261
+ }
259
262
  function variationApplies(variation, combo) {
260
263
  const opts = variation.displayOptions;
261
264
  if (!combo)
@@ -814,7 +817,7 @@ function quotePropertyName(name) {
814
817
  }
815
818
  return name;
816
819
  }
817
- function generateNestedPropertyJSDoc(prop, indent, discriminatorContext) {
820
+ function generateNestedPropertyJSDoc(prop, indent, discriminatorContext, displayOptionsVariants) {
818
821
  const lines = [];
819
822
  const description = prop.description ?? prop.displayName;
820
823
  if (description) {
@@ -848,45 +851,9 @@ function generateNestedPropertyJSDoc(prop, indent, discriminatorContext) {
848
851
  if (typeof prop.typeOptions?.loadOptionsMethod === 'string') {
849
852
  lines.push(`${indent} * @loadOptionsMethod ${prop.typeOptions.loadOptionsMethod}`);
850
853
  }
851
- if (prop.displayOptions) {
852
- if (prop.displayOptions.show && Object.keys(prop.displayOptions.show).length > 0) {
853
- const filteredShow = Object.entries(prop.displayOptions.show).filter(([key, values]) => {
854
- if (key === '@version')
855
- return false;
856
- const normalizedKey = key.startsWith('/') ? key.slice(1) : key;
857
- if (discriminatorContext?.[normalizedKey] !== undefined) {
858
- const showValues = values;
859
- if (showValues.includes(discriminatorContext[normalizedKey]))
860
- return false;
861
- }
862
- return true;
863
- });
864
- if (filteredShow.length > 0) {
865
- const showConditions = filteredShow
866
- .map(([key, values]) => `${key}: [${values.map((v) => JSON.stringify(v)).join(', ')}]`)
867
- .join(', ');
868
- lines.push(`${indent} * @displayOptions.show { ${showConditions} }`);
869
- }
870
- }
871
- if (prop.displayOptions.hide && Object.keys(prop.displayOptions.hide).length > 0) {
872
- const filteredHide = Object.entries(prop.displayOptions.hide).filter(([key, values]) => {
873
- if (key === '@version')
874
- return false;
875
- const normalizedKey = key.startsWith('/') ? key.slice(1) : key;
876
- if (discriminatorContext?.[normalizedKey] !== undefined) {
877
- const hideValues = values;
878
- if (hideValues.includes(discriminatorContext[normalizedKey]))
879
- return false;
880
- }
881
- return true;
882
- });
883
- if (filteredHide.length > 0) {
884
- const hideConditions = filteredHide
885
- .map(([key, values]) => `${key}: [${values.map((v) => JSON.stringify(v)).join(', ')}]`)
886
- .join(', ');
887
- lines.push(`${indent} * @displayOptions.hide { ${hideConditions} }`);
888
- }
889
- }
854
+ const variants = displayOptionsVariants ?? [prop.displayOptions];
855
+ for (const displayOptions of mergeDisplayOptionsVariants(variants, discriminatorContext)) {
856
+ lines.push(...formatDisplayOptionsJSDocLines(displayOptions, indent, discriminatorContext));
890
857
  }
891
858
  if (prop.default !== undefined && prop.default !== null && prop.default !== '') {
892
859
  let defaultStr;
@@ -903,6 +870,136 @@ function generateNestedPropertyJSDoc(prop, indent, discriminatorContext) {
903
870
  lines.push(`${indent} */`);
904
871
  return lines.join('\n');
905
872
  }
873
+ function getResidualDisplayOptions(displayOptions, discriminatorContext) {
874
+ const residual = {};
875
+ if (!displayOptions)
876
+ return residual;
877
+ const keep = ([key]) => {
878
+ if (key === '@version')
879
+ return false;
880
+ const normalizedKey = key.startsWith('/') ? key.slice(1) : key;
881
+ return discriminatorContext?.[normalizedKey] === undefined;
882
+ };
883
+ const show = Object.entries(displayOptions.show ?? {}).filter(keep);
884
+ if (show.length > 0)
885
+ residual.show = Object.fromEntries(show);
886
+ const hide = Object.entries(displayOptions.hide ?? {}).filter(keep);
887
+ if (hide.length > 0)
888
+ residual.hide = Object.fromEntries(hide);
889
+ return residual;
890
+ }
891
+ function hasResidualDisplayOptions(displayOptions, discriminatorContext) {
892
+ const residual = getResidualDisplayOptions(displayOptions, discriminatorContext);
893
+ return residual.show !== undefined || residual.hide !== undefined;
894
+ }
895
+ function formatDisplayOptionsJSDocLines(displayOptions, indent, discriminatorContext) {
896
+ const residual = getResidualDisplayOptions(displayOptions, discriminatorContext);
897
+ const format = (conditions) => Object.entries(conditions)
898
+ .map(([key, values]) => `${key}: [${values.map((v) => JSON.stringify(v)).join(', ')}]`)
899
+ .join(', ');
900
+ const lines = [];
901
+ if (residual.show)
902
+ lines.push(`${indent} * @displayOptions.show { ${format(residual.show)} }`);
903
+ if (residual.hide)
904
+ lines.push(`${indent} * @displayOptions.hide { ${format(residual.hide)} }`);
905
+ return lines;
906
+ }
907
+ function mergeDisplayOptionsVariants(variants, discriminatorContext) {
908
+ const residuals = new Map();
909
+ for (const variant of variants) {
910
+ const residual = getResidualDisplayOptions(variant, discriminatorContext);
911
+ if (residual.show === undefined && residual.hide === undefined)
912
+ return [];
913
+ residuals.set(JSON.stringify(residual), residual);
914
+ }
915
+ const merged = [];
916
+ for (const residual of residuals.values()) {
917
+ const last = merged[merged.length - 1];
918
+ const combined = last
919
+ ? (tryMergeUxForkVariants(last, residual) ?? tryMergeShowValueVariants(last, residual))
920
+ : null;
921
+ if (combined) {
922
+ merged[merged.length - 1] = combined;
923
+ }
924
+ else {
925
+ merged.push(residual);
926
+ }
927
+ }
928
+ return merged;
929
+ }
930
+ function tryMergeShowValueVariants(a, b) {
931
+ if (a.hide !== undefined || b.hide !== undefined)
932
+ return null;
933
+ if (a.show === undefined || b.show === undefined)
934
+ return null;
935
+ const aShow = a.show;
936
+ const bShow = b.show;
937
+ const keys = Object.keys(aShow);
938
+ if (keys.length !== Object.keys(bShow).length || keys.some((key) => !(key in bShow))) {
939
+ return null;
940
+ }
941
+ const differing = keys.filter((key) => JSON.stringify(aShow[key]) !== JSON.stringify(bShow[key]));
942
+ if (differing.length !== 1)
943
+ return null;
944
+ const [key] = differing;
945
+ const seen = new Set(aShow[key].map((value) => JSON.stringify(value)));
946
+ const values = [...aShow[key], ...bShow[key].filter((v) => !seen.has(JSON.stringify(v)))];
947
+ return { show: { ...aShow, [key]: values } };
948
+ }
949
+ function appliesToCombination(displayOptions, combination) {
950
+ if (!displayOptions || !combination)
951
+ return true;
952
+ const valueFor = (key) => combination[key.startsWith('/') ? key.slice(1) : key];
953
+ for (const [key, conditions] of Object.entries(displayOptions.show ?? {})) {
954
+ const value = valueFor(key);
955
+ if (value !== undefined && !(0, display_options_1.checkConditions)(conditions, [value]))
956
+ return false;
957
+ }
958
+ for (const [key, conditions] of Object.entries(displayOptions.hide ?? {})) {
959
+ const value = valueFor(key);
960
+ if (value !== undefined && (0, display_options_1.checkConditions)(conditions, [value]))
961
+ return false;
962
+ }
963
+ return true;
964
+ }
965
+ function emitNestedPropertyLines(properties, indent, discriminatorContext) {
966
+ const variantsByName = new Map();
967
+ for (const prop of properties) {
968
+ if (DISPLAY_ONLY_PROPERTY_TYPES.has(prop.type))
969
+ continue;
970
+ if (!appliesToCombination(prop.displayOptions, discriminatorContext))
971
+ continue;
972
+ const type = mapNestedPropertyType(prop, discriminatorContext);
973
+ if (!type)
974
+ continue;
975
+ const variants = variantsByName.get(prop.name) ?? [];
976
+ variants.push({ prop, type });
977
+ variantsByName.set(prop.name, variants);
978
+ }
979
+ const lines = [];
980
+ for (const [name, variants] of variantsByName) {
981
+ const quotedName = quotePropertyName(name);
982
+ const variantsByType = new Map();
983
+ for (const { prop, type } of variants) {
984
+ const props = variantsByType.get(type) ?? [];
985
+ props.push(prop);
986
+ variantsByType.set(type, props);
987
+ }
988
+ if (variantsByType.size === 1) {
989
+ const [type, props] = Array.from(variantsByType.entries())[0];
990
+ const jsDoc = generateNestedPropertyJSDoc(props[0], indent, discriminatorContext, props.map((p) => p.displayOptions));
991
+ lines.push(`${jsDoc}\n${indent}${quotedName}?: ${type}`);
992
+ continue;
993
+ }
994
+ const memberIndent = indent + INDENT;
995
+ const members = Array.from(variantsByType.entries()).map(([type, props]) => {
996
+ const jsDoc = generateNestedPropertyJSDoc(props[0], memberIndent, discriminatorContext, props.map((p) => p.displayOptions));
997
+ return `${jsDoc}\n${memberIndent}| ${type}`;
998
+ });
999
+ lines.push(`${indent}${quotedName}?:\n${members.join('\n')}`);
1000
+ }
1001
+ return lines;
1002
+ }
906
1003
  function generateFixedCollectionType(prop, discriminatorContext) {
907
1004
  if (!prop.options || prop.options.length === 0) {
908
1005
  return 'Record<string, unknown>';
@@ -914,18 +1011,7 @@ function generateFixedCollectionType(prop, discriminatorContext) {
914
1011
  continue;
915
1012
  }
916
1013
  const groupName = quotePropertyName(group.name);
917
- const nestedProps = [];
918
- for (const nestedProp of group.values) {
919
- if (DISPLAY_ONLY_PROPERTY_TYPES.has(nestedProp.type)) {
920
- continue;
921
- }
922
- const nestedType = mapNestedPropertyType(nestedProp, discriminatorContext);
923
- if (nestedType) {
924
- const quotedName = quotePropertyName(nestedProp.name);
925
- const jsDoc = generateNestedPropertyJSDoc(nestedProp, INDENT.repeat(3), discriminatorContext);
926
- nestedProps.push(`${jsDoc}\n${INDENT.repeat(3)}${quotedName}?: ${nestedType}`);
927
- }
928
- }
1014
+ const nestedProps = emitNestedPropertyLines(group.values, INDENT.repeat(3), discriminatorContext);
929
1015
  if (nestedProps.length > 0) {
930
1016
  const innerType = `{\n${nestedProps.join(';\n')};\n${INDENT.repeat(2)}}`;
931
1017
  const minRequired = prop.typeOptions?.minRequiredFields;
@@ -1139,22 +1225,10 @@ function generateCollectionType(prop, discriminatorContext) {
1139
1225
  if (!prop.options || prop.options.length === 0) {
1140
1226
  return 'Record<string, unknown>';
1141
1227
  }
1142
- const nestedProps = [];
1143
- for (const nestedProp of prop.options) {
1144
- if (nestedProp.values !== undefined) {
1145
- continue;
1146
- }
1147
- const nestedType = nestedProp.type;
1148
- if (DISPLAY_ONLY_PROPERTY_TYPES.has(nestedType)) {
1149
- continue;
1150
- }
1151
- const propType = mapNestedPropertyType(nestedProp, discriminatorContext);
1152
- if (propType) {
1153
- const quotedName = quotePropertyName(nestedProp.name);
1154
- const jsDoc = generateNestedPropertyJSDoc(nestedProp, INDENT.repeat(2), discriminatorContext);
1155
- nestedProps.push(`${jsDoc}\n${INDENT.repeat(2)}${quotedName}?: ${propType}`);
1156
- }
1157
- }
1228
+ const nestedProperties = prop.options
1229
+ .filter((option) => option.values === undefined)
1230
+ .filter(isNodeProperty);
1231
+ const nestedProps = emitNestedPropertyLines(nestedProperties, INDENT.repeat(2), discriminatorContext);
1158
1232
  if (nestedProps.length === 0) {
1159
1233
  return 'Record<string, unknown>';
1160
1234
  }
@@ -1413,8 +1487,33 @@ function propertyAppliesToVersion(prop, version) {
1413
1487
  }
1414
1488
  return true;
1415
1489
  }
1490
+ function filterNestedOptionsForVersion(prop, version) {
1491
+ if (!prop.options)
1492
+ return prop;
1493
+ let changed = false;
1494
+ const options = [];
1495
+ for (const option of prop.options) {
1496
+ if (!propertyAppliesToVersion(option, version)) {
1497
+ changed = true;
1498
+ continue;
1499
+ }
1500
+ let next = filterNestedOptionsForVersion(option, version);
1501
+ if (next.values) {
1502
+ const values = filterPropertiesForVersion(next.values, version);
1503
+ if (values.length !== next.values.length || values.some((v, i) => v !== next.values?.[i])) {
1504
+ next = { ...next, values };
1505
+ }
1506
+ }
1507
+ if (next !== option)
1508
+ changed = true;
1509
+ options.push(next);
1510
+ }
1511
+ return changed ? { ...prop, options } : prop;
1512
+ }
1416
1513
  function filterPropertiesForVersion(properties, version) {
1417
- return properties.filter((prop) => propertyAppliesToVersion(prop, version));
1514
+ return properties
1515
+ .filter((prop) => propertyAppliesToVersion(prop, version))
1516
+ .map((prop) => filterNestedOptionsForVersion(prop, version));
1418
1517
  }
1419
1518
  function generateDiscriminatedUnion(node) {
1420
1519
  const combinations = extractDiscriminatorCombinations(node);
@@ -1477,7 +1576,7 @@ function generateDiscriminatedUnion(node) {
1477
1576
  }
1478
1577
  return lines.join('\n');
1479
1578
  }
1480
- function generatePropertyJSDoc(prop, discriminatorContext) {
1579
+ function generatePropertyJSDoc(prop, discriminatorContext, options) {
1481
1580
  const lines = ['/**'];
1482
1581
  const description = prop.description ?? prop.displayName;
1483
1582
  const safeDescription = description
@@ -1506,45 +1605,9 @@ function generatePropertyJSDoc(prop, discriminatorContext) {
1506
1605
  if (typeof prop.typeOptions?.loadOptionsMethod === 'string') {
1507
1606
  lines.push(` * @loadOptionsMethod ${prop.typeOptions.loadOptionsMethod}`);
1508
1607
  }
1509
- if (prop.displayOptions) {
1510
- if (prop.displayOptions.show && Object.keys(prop.displayOptions.show).length > 0) {
1511
- const filteredShow = Object.entries(prop.displayOptions.show).filter(([key, values]) => {
1512
- if (key === '@version')
1513
- return false;
1514
- const normalizedKey = key.startsWith('/') ? key.slice(1) : key;
1515
- if (discriminatorContext?.[normalizedKey] !== undefined) {
1516
- const showValues = values;
1517
- if (showValues.includes(discriminatorContext[normalizedKey]))
1518
- return false;
1519
- }
1520
- return true;
1521
- });
1522
- if (filteredShow.length > 0) {
1523
- const showConditions = filteredShow
1524
- .map(([key, values]) => `${key}: [${values.map((v) => JSON.stringify(v)).join(', ')}]`)
1525
- .join(', ');
1526
- lines.push(` * @displayOptions.show { ${showConditions} }`);
1527
- }
1528
- }
1529
- if (prop.displayOptions.hide && Object.keys(prop.displayOptions.hide).length > 0) {
1530
- const filteredHide = Object.entries(prop.displayOptions.hide).filter(([key, values]) => {
1531
- if (key === '@version')
1532
- return false;
1533
- const normalizedKey = key.startsWith('/') ? key.slice(1) : key;
1534
- if (discriminatorContext?.[normalizedKey] !== undefined) {
1535
- const hideValues = values;
1536
- if (hideValues.includes(discriminatorContext[normalizedKey]))
1537
- return false;
1538
- }
1539
- return true;
1540
- });
1541
- if (filteredHide.length > 0) {
1542
- const hideConditions = filteredHide
1543
- .map(([key, values]) => `${key}: [${values.map((v) => JSON.stringify(v)).join(', ')}]`)
1544
- .join(', ');
1545
- lines.push(` * @displayOptions.hide { ${hideConditions} }`);
1546
- }
1547
- }
1608
+ lines.push(...formatDisplayOptionsJSDocLines(prop.displayOptions, '', discriminatorContext));
1609
+ if (options?.conditionallyRequired) {
1610
+ lines.push(' * @required when the displayOptions conditions above match');
1548
1611
  }
1549
1612
  if (prop.default !== undefined && prop.default !== null && prop.default !== '') {
1550
1613
  let defaultStr;
@@ -1598,18 +1661,19 @@ function generatePropertyLine(prop, optional, discriminatorContext) {
1598
1661
  return '';
1599
1662
  }
1600
1663
  const lines = [];
1664
+ const conditionallyRequired = !optional && hasResidualDisplayOptions(prop.displayOptions, discriminatorContext);
1601
1665
  const hasDisplayOptions = prop.displayOptions &&
1602
1666
  ((prop.displayOptions.show && Object.keys(prop.displayOptions.show).length > 0) ||
1603
1667
  (prop.displayOptions.hide && Object.keys(prop.displayOptions.hide).length > 0));
1604
1668
  if (prop.description || hasDisplayOptions || prop.hint || prop.builderHint) {
1605
- lines.push(generatePropertyJSDoc(prop, discriminatorContext));
1669
+ lines.push(generatePropertyJSDoc(prop, discriminatorContext, { conditionallyRequired }));
1606
1670
  }
1607
1671
  let propName = prop.name;
1608
1672
  const needsQuoting = RESERVED_WORDS.has(propName) || !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName);
1609
1673
  if (needsQuoting) {
1610
1674
  propName = `'${propName.replace(/'/g, "\\'")}'`;
1611
1675
  }
1612
- const optionalMarker = optional ? '?' : '';
1676
+ const optionalMarker = optional || conditionallyRequired ? '?' : '';
1613
1677
  lines.push(`${INDENT}${propName}${optionalMarker}: ${tsType};`);
1614
1678
  return lines.join(`\n${INDENT}`);
1615
1679
  }