@lowdefy/build 4.5.1 → 4.6.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 (165) hide show
  1. package/dist/build/addDefaultPages/404.js +1 -1
  2. package/dist/build/addDefaultPages/addDefaultPages.js +10 -4
  3. package/dist/build/addKeys.js +85 -29
  4. package/dist/build/buildApi/buildApi.js +27 -8
  5. package/dist/build/buildApi/buildEndpoint.js +7 -1
  6. package/dist/build/buildApi/buildRoutine/buildControl.js +1 -1
  7. package/dist/build/buildApi/buildRoutine/buildRoutine.js +1 -1
  8. package/dist/build/buildApi/buildRoutine/buildStep.js +1 -1
  9. package/dist/build/buildApi/buildRoutine/controlTypes.js +34 -11
  10. package/dist/build/buildApi/buildRoutine/countControl.js +1 -1
  11. package/dist/build/buildApi/buildRoutine/countStepTypes.js +2 -2
  12. package/dist/build/buildApi/buildRoutine/setStepId.js +1 -1
  13. package/dist/build/buildApi/buildRoutine/validateStep.js +30 -9
  14. package/dist/build/buildApi/validateEndpoint.js +36 -7
  15. package/dist/build/buildApi/validateStepReferences.js +65 -0
  16. package/dist/build/buildApp.js +1 -1
  17. package/dist/build/buildAuth/buildApiAuth.js +7 -3
  18. package/dist/build/buildAuth/buildAuth.js +7 -4
  19. package/dist/build/buildAuth/buildAuthPlugins.js +42 -13
  20. package/dist/build/buildAuth/buildPageAuth.js +14 -3
  21. package/dist/build/buildAuth/getApiRoles.js +1 -1
  22. package/dist/build/buildAuth/getPageRoles.js +1 -1
  23. package/dist/build/buildAuth/getProtectedApi.js +1 -1
  24. package/dist/build/buildAuth/getProtectedPages.js +1 -1
  25. package/dist/build/buildAuth/validateAuthConfig.js +39 -5
  26. package/dist/build/buildAuth/validateMutualExclusivity.js +13 -5
  27. package/dist/build/buildConnections.js +23 -24
  28. package/dist/build/buildImports/buildIconImports.js +1 -1
  29. package/dist/build/buildImports/buildImports.js +1 -1
  30. package/dist/build/buildImports/buildImportsDev.js +1 -1
  31. package/dist/build/buildImports/buildImportsProd.js +1 -1
  32. package/dist/build/buildImports/buildStyleImports.js +1 -1
  33. package/dist/build/buildImports/defaultIconsDev.js +1 -1
  34. package/dist/build/buildImports/defaultIconsProd.js +1 -1
  35. package/dist/build/buildJs/generateJsFile.js +1 -1
  36. package/dist/build/buildJs/jsMapParser.js +1 -1
  37. package/dist/build/buildJs/writeJs.js +1 -1
  38. package/dist/build/buildLogger.js +41 -0
  39. package/dist/build/buildMenu.js +36 -12
  40. package/dist/build/buildPages/buildBlock/buildBlock.js +1 -1
  41. package/dist/build/buildPages/buildBlock/buildEvents.js +79 -9
  42. package/dist/build/buildPages/buildBlock/buildRequests.js +38 -8
  43. package/dist/build/buildPages/buildBlock/buildSubBlocks.js +6 -2
  44. package/dist/build/buildPages/buildBlock/countBlockOperators.js +1 -1
  45. package/dist/build/buildPages/buildBlock/countBlockTypes.js +2 -2
  46. package/dist/build/buildPages/buildBlock/moveSkeletonBlocksToArea.js +6 -2
  47. package/dist/build/buildPages/buildBlock/moveSubBlocksToArea.js +6 -2
  48. package/dist/build/buildPages/buildBlock/setBlockId.js +1 -1
  49. package/dist/build/buildPages/buildBlock/validateBlock.js +25 -7
  50. package/dist/build/buildPages/buildPage.js +35 -6
  51. package/dist/build/buildPages/validateLinkReferences.js +33 -0
  52. package/dist/build/buildPages/validatePayloadReferences.js +59 -0
  53. package/dist/build/buildPages/validateRequestReferences.js +33 -0
  54. package/dist/build/buildPages/validateServerStateReferences.js +41 -0
  55. package/dist/build/buildPages/validateStateReferences.js +79 -0
  56. package/dist/build/buildRefs/buildRefs.js +20 -3
  57. package/dist/build/buildRefs/createRefReviver.js +28 -0
  58. package/dist/build/buildRefs/evaluateBuildOperators.js +26 -7
  59. package/dist/build/buildRefs/evaluateStaticOperators.js +56 -0
  60. package/dist/build/buildRefs/getConfigFile.js +25 -6
  61. package/dist/build/buildRefs/getKey.js +1 -1
  62. package/dist/build/buildRefs/getRefContent.js +1 -1
  63. package/dist/build/buildRefs/getRefPath.js +1 -1
  64. package/dist/build/buildRefs/getRefsFromFile.js +9 -4
  65. package/dist/build/buildRefs/getUserJavascriptFunction.js +18 -4
  66. package/dist/build/buildRefs/makeRefDefinition.js +10 -5
  67. package/dist/build/buildRefs/parseNunjucks.js +1 -1
  68. package/dist/build/buildRefs/parseRefContent.js +100 -6
  69. package/dist/build/buildRefs/populateRefs.js +75 -13
  70. package/dist/build/buildRefs/recursiveBuild.js +66 -18
  71. package/dist/build/buildRefs/runRefResolver.js +11 -3
  72. package/dist/build/buildRefs/runTransformer.js +7 -3
  73. package/dist/build/buildTypes.js +22 -7
  74. package/dist/build/cleanBuildDirectory.js +1 -1
  75. package/dist/build/collectDynamicIdentifiers.js +35 -0
  76. package/dist/build/collectTypeNames.js +36 -0
  77. package/dist/build/copyPublicFolder.js +1 -1
  78. package/dist/build/{buildJs → full}/buildJs.js +3 -3
  79. package/dist/build/full/buildPages.js +87 -0
  80. package/dist/build/{buildPages → full}/buildTestPage.js +15 -3
  81. package/dist/build/{updateServerPackageJson.js → full/updateServerPackageJson.js} +1 -1
  82. package/dist/build/{writePages.js → full/writePages.js} +1 -1
  83. package/dist/build/{writeRequests.js → full/writeRequests.js} +11 -3
  84. package/dist/build/{writeTypes.js → full/writeTypes.js} +1 -1
  85. package/dist/build/jit/addInstalledTypes.js +51 -0
  86. package/dist/build/jit/buildJsShallow.js +41 -0
  87. package/dist/build/jit/buildPageJit.js +252 -0
  88. package/dist/build/jit/buildShallowPages.js +90 -0
  89. package/dist/build/jit/createPageRegistry.js +80 -0
  90. package/dist/build/jit/detectMissingPluginPackages.js +62 -0
  91. package/dist/build/jit/getRefPositions.js +38 -0
  92. package/dist/build/jit/isPageContentPath.js +24 -0
  93. package/dist/build/jit/pageContentKeys.js +26 -0
  94. package/dist/build/jit/shallowBuild.js +245 -0
  95. package/dist/build/jit/stripPageContent.js +23 -0
  96. package/dist/build/jit/updateServerPackageJsonJit.js +33 -0
  97. package/dist/build/jit/validatePageTypes.js +73 -0
  98. package/dist/build/jit/writePageJit.js +44 -0
  99. package/dist/build/jit/writePageRegistry.js +23 -0
  100. package/dist/build/jit/writeSourcelessPages.js +23 -0
  101. package/dist/build/testSchema.js +45 -7
  102. package/dist/build/validateConfig.js +2 -2
  103. package/dist/build/validateOperatorsDynamic.js +28 -0
  104. package/dist/build/writeApi.js +1 -1
  105. package/dist/build/writeApp.js +1 -1
  106. package/dist/build/writeAuth.js +1 -1
  107. package/dist/build/writeConfig.js +1 -1
  108. package/dist/build/writeConnections.js +1 -1
  109. package/dist/build/writeGlobal.js +1 -1
  110. package/dist/build/writeLogger.js +19 -0
  111. package/dist/build/writeMaps.js +1 -1
  112. package/dist/build/writeMenus.js +1 -1
  113. package/dist/build/writePluginImports/generateImportFile.js +1 -1
  114. package/dist/build/writePluginImports/writeActionImports.js +1 -1
  115. package/dist/build/writePluginImports/writeActionSchemaMap.js +42 -0
  116. package/dist/build/writePluginImports/writeAuthImports.js +1 -1
  117. package/dist/build/writePluginImports/writeBlockImports.js +1 -1
  118. package/dist/build/writePluginImports/writeBlockSchemaMap.js +42 -0
  119. package/dist/build/writePluginImports/writeConnectionImports.js +1 -1
  120. package/dist/build/writePluginImports/writeIconImports.js +1 -1
  121. package/dist/build/writePluginImports/writeOperatorImports.js +1 -1
  122. package/dist/build/writePluginImports/writeOperatorSchemaMap.js +49 -0
  123. package/dist/build/writePluginImports/writePluginImports.js +16 -1
  124. package/dist/build/writePluginImports/writeStyleImports.js +1 -1
  125. package/dist/createContext.js +16 -4
  126. package/dist/defaultTypesMap.js +477 -455
  127. package/dist/index.js +188 -127
  128. package/dist/indexDev.js +18 -0
  129. package/dist/lowdefySchema.js +589 -0
  130. package/dist/scripts/generateDefaultTypes.js +2 -1
  131. package/dist/scripts/run.js +1 -1
  132. package/dist/{test → test-utils}/buildRefs/testBuildRefsAsyncFunction.js +1 -1
  133. package/dist/{test → test-utils}/buildRefs/testBuildRefsErrorResolver.js +1 -1
  134. package/dist/{test → test-utils}/buildRefs/testBuildRefsNullResolver.js +1 -1
  135. package/dist/{test → test-utils}/buildRefs/testBuildRefsParsingResolver.js +1 -1
  136. package/dist/{test → test-utils}/buildRefs/testBuildRefsResolver.js +1 -1
  137. package/dist/{test → test-utils}/buildRefs/testBuildRefsTransform.js +1 -1
  138. package/dist/{test → test-utils}/buildRefs/testBuildRefsTransformIdentity.js +1 -1
  139. package/dist/test-utils/buildRefs/testJitPageResolver.js +24 -0
  140. package/dist/test-utils/createTestLogger.js +36 -0
  141. package/dist/test-utils/parseTestYaml.js +126 -0
  142. package/dist/test-utils/runBuild.js +228 -0
  143. package/dist/test-utils/runBuildForSnapshots.js +704 -0
  144. package/dist/{test → test-utils}/testContext.js +12 -2
  145. package/dist/utils/collectExceptions.js +34 -0
  146. package/dist/utils/countOperators.js +31 -12
  147. package/dist/utils/createBuildHandleError.js +38 -0
  148. package/dist/utils/createCheckDuplicateId.js +15 -9
  149. package/dist/utils/createCounter.js +16 -3
  150. package/dist/utils/createHandleWarning.js +41 -0
  151. package/dist/utils/createPluginTypesMap.js +1 -1
  152. package/dist/utils/extractOperatorKey.js +36 -0
  153. package/dist/utils/findConfigKey.js +37 -0
  154. package/dist/utils/findSimilarString.js +53 -0
  155. package/dist/utils/logCollectedErrors.js +30 -0
  156. package/dist/utils/makeId.js +13 -8
  157. package/dist/utils/preserveMetaProperties.js +27 -0
  158. package/dist/utils/readConfigFile.js +1 -1
  159. package/dist/utils/setNonEnumerableProperty.js +23 -0
  160. package/dist/utils/traverseConfig.js +43 -0
  161. package/dist/utils/tryBuildStep.js +46 -0
  162. package/dist/utils/writeBuildArtifact.js +1 -1
  163. package/package.json +46 -41
  164. package/dist/build/buildPages/buildPages.js +0 -31
  165. package/dist/utils/formatErrorMessage.js +0 -56
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -13,8 +13,9 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
16
17
  import page404 from './404.js';
17
- function addDefaultPages({ components }) {
18
+ function addDefaultPages({ components, context }) {
18
19
  // If not copied, the same object is mutated by build every time
19
20
  // build runs for dev server. See #647
20
21
  const defaultPages = [
@@ -24,11 +25,16 @@ function addDefaultPages({ components }) {
24
25
  components.pages = [];
25
26
  }
26
27
  if (!type.isArray(components.pages)) {
27
- throw new Error('lowdefy.pages is not an array.');
28
+ throw new ConfigError('lowdefy.pages is not an array.', {
29
+ received: components.pages
30
+ });
28
31
  }
29
32
  const pageIds = components.pages.map((page, index)=>{
30
33
  if (!type.isObject(page)) {
31
- throw new Error(`pages[${index}] is not an object. Received ${JSON.stringify(page)}`);
34
+ throw new ConfigError(`pages[${index}] is not an object.`, {
35
+ received: page,
36
+ configKey: page?.['~k']
37
+ });
32
38
  }
33
39
  return page.id;
34
40
  });
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -13,13 +13,37 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError, VALID_CHECK_SLUGS } from '@lowdefy/errors';
17
+ import collectExceptions from '../utils/collectExceptions.js';
16
18
  import makeId from '../utils/makeId.js';
17
- function recArray({ array, nextKey, key, keyMap, keyMapId }) {
19
+ import setNonEnumerableProperty from '../utils/setNonEnumerableProperty.js';
20
+ function recArray({ array, arrayKey, keyMap, parentKeyMapId, context }) {
21
+ let arrayKeyMapId;
22
+ if (array['~k']) {
23
+ arrayKeyMapId = array['~k'];
24
+ } else {
25
+ arrayKeyMapId = makeId.next();
26
+ const entry = {
27
+ key: arrayKey,
28
+ '~k_parent': parentKeyMapId
29
+ };
30
+ if (array['~r'] !== undefined) entry['~r'] = array['~r'];
31
+ if (array['~l'] !== undefined) entry['~l'] = array['~l'];
32
+ keyMap[arrayKeyMapId] = entry;
33
+ Object.defineProperty(array, '~k', {
34
+ value: arrayKeyMapId,
35
+ enumerable: false,
36
+ writable: true,
37
+ configurable: true
38
+ });
39
+ delete array['~r'];
40
+ delete array['~l'];
41
+ }
18
42
  array.forEach((item, index)=>{
19
43
  if (type.isObject(item)) {
20
- let path = `${key}.${nextKey}[${index}]`;
44
+ let path = `${arrayKey}[${index}]`;
21
45
  // TODO: Convert all artifacts to not modify id.
22
- const id = item.blockId ?? item.menuId ?? item.menuItemId ?? item.requestId ?? item.connectionId ?? item.connectionId ?? item.id;
46
+ const id = item.blockId ?? item.menuId ?? item.menuItemId ?? item.requestId ?? item.connectionId ?? item.id;
23
47
  if (id) {
24
48
  path = `${path.slice(0, -1)}:${id}]`;
25
49
  }
@@ -30,61 +54,93 @@ function recArray({ array, nextKey, key, keyMap, keyMapId }) {
30
54
  object: item,
31
55
  key: path,
32
56
  keyMap: keyMap,
33
- parentKeyMapId: keyMapId
57
+ parentKeyMapId: arrayKeyMapId,
58
+ context
34
59
  });
35
60
  }
36
61
  if (type.isArray(item)) {
37
62
  recArray({
38
63
  array: item,
39
- nextKey,
40
- key,
64
+ arrayKey: `${arrayKey}[${index}]`,
41
65
  keyMap,
42
- keyMapId
66
+ parentKeyMapId: arrayKeyMapId,
67
+ context
43
68
  });
44
69
  }
45
70
  });
46
71
  }
47
- function recAddKeys({ object, key, keyMap, parentKeyMapId }) {
48
- const keyMapId = makeId();
49
- keyMap[keyMapId] = {
50
- key,
51
- '~r': object['~r'],
52
- '~k_parent': parentKeyMapId
53
- };
54
- Object.defineProperty(object, '~k', {
55
- value: keyMapId,
56
- enumerable: false,
57
- writable: true,
58
- configurable: true
59
- });
60
- delete object['~r'];
72
+ function recAddKeys({ object, key, keyMap, parentKeyMapId, context }) {
73
+ let keyMapId;
74
+ let storedKey = key;
75
+ // Skip objects that already have a ~k (already processed)
76
+ if (object['~k']) {
77
+ keyMapId = object['~k'];
78
+ // Use the stored key from keyMap for correct child paths
79
+ storedKey = keyMap[keyMapId]?.key ?? key;
80
+ } else {
81
+ keyMapId = makeId.next();
82
+ const entry = {
83
+ key,
84
+ '~k_parent': parentKeyMapId
85
+ };
86
+ if (object['~r'] !== undefined) entry['~r'] = object['~r'];
87
+ if (object['~l'] !== undefined) entry['~l'] = object['~l'];
88
+ // Add entry to keyMap BEFORE validation so errors can resolve location
89
+ keyMap[keyMapId] = entry;
90
+ // Handle ~ignoreBuildChecks property
91
+ if (object['~ignoreBuildChecks'] !== undefined) {
92
+ const checks = object['~ignoreBuildChecks'];
93
+ if (Array.isArray(checks)) {
94
+ const validSlugs = Object.keys(VALID_CHECK_SLUGS);
95
+ const invalid = checks.filter((slug)=>!validSlugs.includes(slug));
96
+ if (invalid.length > 0) {
97
+ collectExceptions(context, new ConfigError(`Invalid check slug(s): "${invalid.join('", "')}". Valid slugs: ${validSlugs.join(', ')}`, {
98
+ configKey: keyMapId
99
+ }));
100
+ }
101
+ } else if (checks !== true) {
102
+ collectExceptions(context, new ConfigError('~ignoreBuildChecks must be true or an array of check slugs.', {
103
+ received: checks,
104
+ configKey: keyMapId
105
+ }));
106
+ }
107
+ entry['~ignoreBuildChecks'] = checks;
108
+ }
109
+ setNonEnumerableProperty(object, '~k', keyMapId);
110
+ delete object['~r'];
111
+ delete object['~l'];
112
+ delete object['~ignoreBuildChecks'];
113
+ }
114
+ // Always recurse into children (they may be new objects without keys)
61
115
  Object.keys(object).forEach((nextKey)=>{
62
116
  if (type.isObject(object[nextKey])) {
63
117
  recAddKeys({
64
118
  object: object[nextKey],
65
- key: `${key}.${nextKey}`,
119
+ key: `${storedKey}.${nextKey}`,
66
120
  keyMap: keyMap,
67
- parentKeyMapId: keyMapId
121
+ parentKeyMapId: keyMapId,
122
+ context
68
123
  });
69
124
  }
70
125
  if (type.isArray(object[nextKey])) {
71
126
  recArray({
72
127
  array: object[nextKey],
73
- nextKey,
74
- key,
128
+ arrayKey: `${storedKey}.${nextKey}`,
75
129
  keyMap,
76
- keyMapId
130
+ parentKeyMapId: keyMapId,
131
+ context
77
132
  });
78
133
  }
79
134
  });
80
135
  }
81
136
  function addKeys({ components, context }) {
82
- const keyMapId = makeId(true);
137
+ const keyMapId = makeId.next();
83
138
  recAddKeys({
84
139
  object: components,
85
140
  key: 'root',
86
141
  keyMap: context.keyMap,
87
- parentKeyMapId: keyMapId
142
+ parentKeyMapId: keyMapId,
143
+ context
88
144
  });
89
145
  }
90
146
  export default addKeys;
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -13,22 +13,41 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError, shouldSuppressBuildCheck } from '@lowdefy/errors';
16
17
  import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
17
18
  import buildEndpoint from './buildEndpoint.js';
18
19
  function buildApi({ components, context }) {
19
20
  if (components.api && !type.isArray(components.api)) {
20
- throw new Error(`Api is not an array. Received ${JSON.stringify(components.api)}.`);
21
+ throw new ConfigError('Api is not an array.', {
22
+ received: components.api
23
+ });
21
24
  }
22
25
  const api = type.isArray(components.api) ? components.api : [];
23
26
  const checkDuplicateEndpointId = createCheckDuplicateId({
24
27
  message: 'Duplicate endpointId "{{ id }}".'
25
28
  });
26
- api.map((endpoint, index)=>buildEndpoint({
27
- endpoint,
28
- index,
29
- context,
30
- checkDuplicateEndpointId
31
- }));
29
+ // Wrap each endpoint build to collect errors instead of stopping on first error
30
+ api.forEach((endpoint, index)=>{
31
+ try {
32
+ buildEndpoint({
33
+ endpoint,
34
+ index,
35
+ context,
36
+ checkDuplicateEndpointId
37
+ });
38
+ } catch (error) {
39
+ // Skip suppressed ConfigErrors (via ~ignoreBuildChecks)
40
+ if (error instanceof ConfigError && shouldSuppressBuildCheck(error, context.keyMap)) {
41
+ return;
42
+ }
43
+ // Collect error object if context.errors exists, otherwise throw (for backward compat with tests)
44
+ if (context?.errors) {
45
+ context.errors.push(error);
46
+ } else {
47
+ throw error;
48
+ }
49
+ }
50
+ });
32
51
  return components;
33
52
  }
34
53
  export default buildApi;
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable no-param-reassign */ /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import buildRoutine from './buildRoutine/buildRoutine.js';
16
16
  import validateEndpoint from './validateEndpoint.js';
17
+ import validateStepReferences from './validateStepReferences.js';
17
18
  function buildEndpoint({ endpoint, index, context, checkDuplicateEndpointId }) {
18
19
  validateEndpoint({
19
20
  endpoint,
@@ -25,6 +26,11 @@ function buildEndpoint({ endpoint, index, context, checkDuplicateEndpointId }) {
25
26
  endpointId: endpoint.endpointId,
26
27
  typeCounters: context.typeCounters
27
28
  });
29
+ // Validate that _step references point to defined step IDs
30
+ validateStepReferences({
31
+ endpoint,
32
+ context
33
+ });
28
34
  endpoint.id = `endpoint:${endpoint.endpointId}`;
29
35
  }
30
36
  export default buildEndpoint;
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable no-param-reassign */ /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
16
17
  import buildRoutine from './buildRoutine.js';
17
18
  import countControl from './countControl.js';
18
19
  import countOperators from '../../../utils/countOperators.js';
@@ -144,23 +145,35 @@ const controlTypes = {
144
145
  function getAdditionalKeys(controlType, keys) {
145
146
  return keys.filter((item)=>!controlTypes[controlType].required.includes(item));
146
147
  }
147
- function checkMissingRequiredControls({ controlType, keys }, { endpointId }) {
148
+ function checkMissingRequiredControls({ controlType, keys, control }, endpointContext) {
149
+ const { endpointId } = endpointContext;
148
150
  const missingControls = controlTypes[controlType].required.filter((item)=>!keys.includes(item));
149
151
  if (missingControls.length > 0) {
150
- throw new Error(`Missing required control type(s) for endpoint ${endpointId}. Missing ${JSON.stringify(missingControls)}`);
152
+ throw new ConfigError(`Missing required control type(s) for endpoint ${endpointId}.`, {
153
+ received: missingControls,
154
+ configKey: control?.['~k']
155
+ });
151
156
  }
152
157
  }
153
- function checkInvalidControls({ controlType, keys }, { endpointId }) {
158
+ function checkInvalidControls({ controlType, keys, control }, endpointContext) {
159
+ const { endpointId } = endpointContext;
154
160
  const additionalControls = getAdditionalKeys(controlType, keys);
155
161
  const invalidControls = additionalControls.filter((item)=>!controlTypes[controlType].optional.includes(item));
156
162
  if (invalidControls.length > 0) {
157
- throw new Error(`Invalid control type(s) for endpoint ${endpointId}. Received ${JSON.stringify(invalidControls)}`);
163
+ throw new ConfigError(`Invalid control type(s) for endpoint ${endpointId}.`, {
164
+ received: invalidControls,
165
+ configKey: control?.['~k']
166
+ });
158
167
  }
159
168
  }
160
169
  function handleSwitch(control, endpointContext) {
170
+ const { endpointId } = endpointContext;
161
171
  const switchArray = control[':switch'];
162
172
  if (!type.isArray(control[':switch'])) {
163
- throw new Error(`Type given for :switch control is invalid at endpoint ${endpointContext.endpointId}. Received ${JSON.stringify(control[':switch'])}`);
173
+ throw new ConfigError(`Type given for :switch control is invalid at endpoint ${endpointId}.`, {
174
+ received: control[':switch'],
175
+ configKey: control['~k']
176
+ });
164
177
  }
165
178
  switchArray.forEach((caseObj)=>{
166
179
  const input = {
@@ -168,7 +181,8 @@ function handleSwitch(control, endpointContext) {
168
181
  keys: [
169
182
  ':switch',
170
183
  ...Object.keys(caseObj)
171
- ]
184
+ ],
185
+ control: caseObj
172
186
  };
173
187
  checkMissingRequiredControls(input, endpointContext);
174
188
  checkInvalidControls(input, endpointContext);
@@ -185,13 +199,20 @@ function handleSwitch(control, endpointContext) {
185
199
  });
186
200
  }
187
201
  function validateControl(control, endpointContext) {
202
+ const { endpointId } = endpointContext;
188
203
  const keys = Object.keys(control);
189
204
  const intersection = keys.filter((item)=>Object.keys(controlTypes).includes(item));
190
205
  if (intersection.length === 0) {
191
- throw new Error(`Invalid control type(s) for endpoint ${endpointContext.endpointId}. Received "${JSON.stringify(keys)}"`);
206
+ throw new ConfigError(`Invalid control type(s) for endpoint ${endpointId}.`, {
207
+ received: keys,
208
+ configKey: control['~k']
209
+ });
192
210
  }
193
211
  if (intersection.length > 1) {
194
- throw new Error(`More than one control type found for endpoint ${endpointContext.endpointId}. Received ${JSON.stringify(intersection)}`);
212
+ throw new ConfigError(`More than one control type found for endpoint ${endpointId}.`, {
213
+ received: intersection,
214
+ configKey: control['~k']
215
+ });
195
216
  }
196
217
  const controlType = intersection[0];
197
218
  if (controlType === ':switch') {
@@ -200,11 +221,13 @@ function validateControl(control, endpointContext) {
200
221
  }
201
222
  checkMissingRequiredControls({
202
223
  controlType,
203
- keys
224
+ keys,
225
+ control
204
226
  }, endpointContext);
205
227
  checkInvalidControls({
206
228
  controlType,
207
- keys
229
+ keys,
230
+ control
208
231
  }, endpointContext);
209
232
  return controlType;
210
233
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -13,6 +13,6 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ function countStepTypes(step, { typeCounters }) {
16
- typeCounters.requests.increment(step.type);
16
+ typeCounters.requests.increment(step.type, step['~k']);
17
17
  }
18
18
  export default countStepTypes;
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -13,30 +13,51 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
16
17
  function validateStep(step, { endpointId }) {
18
+ const configKey = step['~k'];
17
19
  if (Object.keys(step).length === 0) {
18
- throw new Error(`Step is not defined at endpoint "${endpointId}"`);
20
+ throw new ConfigError(`Step is not defined at endpoint "${endpointId}".`, {
21
+ configKey
22
+ });
19
23
  }
20
24
  if (type.isUndefined(step.id)) {
21
- throw new Error(`Step id missing at endpoint "${endpointId}".`);
25
+ throw new ConfigError(`Step id missing at endpoint "${endpointId}".`, {
26
+ configKey
27
+ });
22
28
  }
23
29
  if (!type.isString(step.id)) {
24
- throw new Error(`Step id is not a string at endpoint "${endpointId}". Received ${JSON.stringify(step.id)}.`);
30
+ throw new ConfigError(`Step id is not a string at endpoint "${endpointId}".`, {
31
+ received: step.id,
32
+ configKey
33
+ });
25
34
  }
26
35
  if (step.id.includes('.')) {
27
- throw new Error(`Step id "${step.id}" at api "${endpointId}" should not include a period (".").`);
36
+ throw new ConfigError(`Step id "${step.id}" at endpoint "${endpointId}" should not include a period (".").`, {
37
+ configKey
38
+ });
28
39
  }
29
40
  if (type.isNone(step.type)) {
30
- throw new Error(`Step type is not defined at "${step.id}" on endpoint "${endpointId}".`);
41
+ throw new ConfigError(`Step type is not defined at "${step.id}" on endpoint "${endpointId}".`, {
42
+ configKey
43
+ });
31
44
  }
32
45
  if (!type.isString(step.type)) {
33
- throw new Error(`Step type is not a string at "${step.id}" on endpoint "${endpointId}". Received ${JSON.stringify(step.type)}.`);
46
+ throw new ConfigError(`Step type is not a string at "${step.id}" on endpoint "${endpointId}".`, {
47
+ received: step.type,
48
+ configKey
49
+ });
34
50
  }
35
51
  if (type.isUndefined(step.connectionId)) {
36
- throw new Error(`Step connectionId missing at endpoint "${endpointId}".`);
52
+ throw new ConfigError(`Step connectionId missing at endpoint "${endpointId}".`, {
53
+ configKey
54
+ });
37
55
  }
38
56
  if (!type.isString(step.connectionId)) {
39
- throw new Error(`Step connectionId is not a string at endpoint "${endpointId}". Received ${JSON.stringify(step.connectionId)}.`);
57
+ throw new ConfigError(`Step connectionId is not a string at endpoint "${endpointId}".`, {
58
+ received: step.connectionId,
59
+ configKey
60
+ });
40
61
  }
41
62
  }
42
63
  export default validateStep;
@@ -1,22 +1,51 @@
1
- import { type } from '@lowdefy/helpers';
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
2
17
  function validateEndpoint({ endpoint, index, checkDuplicateEndpointId }) {
18
+ const configKey = endpoint['~k'];
3
19
  if (type.isUndefined(endpoint.id)) {
4
- throw new Error(`Endpoint id missing at endpoint ${index}.`);
20
+ throw new ConfigError(`Endpoint id missing at endpoint ${index}.`, {
21
+ configKey
22
+ });
5
23
  }
6
24
  if (!type.isString(endpoint.id)) {
7
- throw new Error(`Endpoint id is not a string at endpoint ${index}. Received ${JSON.stringify(endpoint.id)}.`);
25
+ throw new ConfigError(`Endpoint id is not a string at endpoint ${index}.`, {
26
+ received: endpoint.id,
27
+ configKey
28
+ });
8
29
  }
9
30
  if (endpoint.id.includes('.')) {
10
- throw new Error(`Endpoint id "${endpoint.id}" at endpoint "${endpoint.id}" should not include a period (".").`);
31
+ throw new ConfigError(`Endpoint id "${endpoint.id}" should not include a period (".").`, {
32
+ configKey
33
+ });
11
34
  }
12
35
  if (type.isUndefined(endpoint.type)) {
13
- throw new Error(`Endpoint type is not defined at "${endpoint.id}" on endpoint "${endpoint.id}".`);
36
+ throw new ConfigError(`Endpoint type is not defined at "${endpoint.id}".`, {
37
+ configKey
38
+ });
14
39
  }
15
40
  if (!type.isString(endpoint.type)) {
16
- throw new Error(`Endpoint type is not a string at "${endpoint.id}" on endpoint "${endpoint.id}". Received ${JSON.stringify(endpoint.type)}.`);
41
+ throw new ConfigError(`Endpoint type is not a string at "${endpoint.id}".`, {
42
+ received: endpoint.type,
43
+ configKey
44
+ });
17
45
  }
18
46
  checkDuplicateEndpointId({
19
- id: endpoint.id
47
+ id: endpoint.id,
48
+ configKey
20
49
  });
21
50
  }
22
51
  export default validateEndpoint;
@@ -0,0 +1,65 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { ConfigWarning } from '@lowdefy/errors';
16
+ import { type } from '@lowdefy/helpers';
17
+ import extractOperatorKey from '../../utils/extractOperatorKey.js';
18
+ import traverseConfig from '../../utils/traverseConfig.js';
19
+ // Collect all step IDs from a routine (including nested control structures)
20
+ // Note: After buildRoutine, steps have requestId (original id) and id is modified
21
+ function collectStepIds(routine, stepIds) {
22
+ if (type.isArray(routine)) {
23
+ routine.forEach((item)=>collectStepIds(item, stepIds));
24
+ return;
25
+ }
26
+ if (type.isObject(routine)) {
27
+ // Check if this is a step (has requestId after build, or id before build)
28
+ if (routine.requestId) {
29
+ stepIds.add(routine.requestId);
30
+ }
31
+ // Recurse into all values (handles control structures like :then, :else, :try, :catch)
32
+ Object.values(routine).forEach((value)=>collectStepIds(value, stepIds));
33
+ }
34
+ }
35
+ function validateStepReferences({ endpoint, context }) {
36
+ // Collect all step IDs defined in the routine
37
+ const stepIds = new Set();
38
+ collectStepIds(endpoint.routine, stepIds);
39
+ // Find _step references in the routine
40
+ const stepRefs = new Map(); // topLevelKey -> configKey (first occurrence)
41
+ traverseConfig({
42
+ config: endpoint.routine,
43
+ visitor: (obj)=>{
44
+ if (obj._step !== undefined) {
45
+ const stepId = extractOperatorKey({
46
+ operatorValue: obj._step
47
+ });
48
+ if (stepId && !stepRefs.has(stepId)) {
49
+ stepRefs.set(stepId, obj['~k']);
50
+ }
51
+ }
52
+ }
53
+ });
54
+ // Warn for undefined step references
55
+ stepRefs.forEach((configKey, stepId)=>{
56
+ if (stepIds.has(stepId)) return;
57
+ const message = `_step references "${stepId}" in endpoint "${endpoint.endpointId}", ` + `but no step with id "${stepId}" exists in the routine. ` + `Step IDs are defined by the "id" property of each step. ` + `Check for typos or add a step with this id.`;
58
+ context.handleWarning(new ConfigWarning(message, {
59
+ configKey,
60
+ prodError: true,
61
+ checkSlug: 'step-refs'
62
+ }));
63
+ });
64
+ }
65
+ export default validateStepReferences;
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable no-param-reassign */ /*
2
- Copyright 2020-2024 Lowdefy, Inc
2
+ Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.