@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.
@@ -13,30 +13,90 @@
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 createCheckDuplicateId from '../../../utils/createCheckDuplicateId.js';
17
- function checkAction(action, { blockId, checkDuplicateActionId, eventId, pageId, typeCounters }) {
18
+ function checkAction(action, { blockId, checkDuplicateActionId, eventId, linkActionRefs, pageId, requestActionRefs, typeCounters }) {
19
+ const configKey = action['~k'];
18
20
  if (type.isUndefined(action.id)) {
19
- throw new Error(`Action id missing on event "${eventId}" on block "${blockId}" on page "${pageId}".`);
21
+ throw new ConfigError(`Action id missing on event "${eventId}" on block "${blockId}" on page "${pageId}".`, {
22
+ configKey
23
+ });
20
24
  }
21
25
  if (!type.isString(action.id)) {
22
- throw new Error(`Action id is not a string on event "${eventId}" on block "${blockId}" on page "${pageId}". Received ${JSON.stringify(action.id)}.`);
26
+ throw new ConfigError(`Action id is not a string on event "${eventId}" on block "${blockId}" on page "${pageId}".`, {
27
+ received: action.id,
28
+ configKey
29
+ });
23
30
  }
24
31
  checkDuplicateActionId({
25
32
  id: action.id,
33
+ configKey,
26
34
  eventId,
27
35
  blockId,
28
36
  pageId
29
37
  });
30
38
  if (!type.isString(action.type)) {
31
- throw new Error(`Action type is not a string on action "${action.id}" on event "${eventId}" on block "${blockId}" on page "${pageId}". Received ${JSON.stringify(action.type)}.`);
39
+ throw new ConfigError(`Action type is not a string on action "${action.id}" on event "${eventId}" on block "${blockId}" on page "${pageId}".`, {
40
+ received: action.type,
41
+ configKey
42
+ });
43
+ }
44
+ typeCounters.actions.increment(action.type, configKey);
45
+ // Collect static Request action references for validation
46
+ if (action.type === 'Request' && !type.isNone(action.params)) {
47
+ const params = action.params;
48
+ if (type.isString(params)) {
49
+ requestActionRefs.push({
50
+ requestId: params,
51
+ action,
52
+ blockId,
53
+ eventId
54
+ });
55
+ } else if (type.isArray(params)) {
56
+ params.forEach((param)=>{
57
+ if (type.isString(param)) {
58
+ requestActionRefs.push({
59
+ requestId: param,
60
+ action,
61
+ blockId,
62
+ eventId
63
+ });
64
+ }
65
+ });
66
+ }
67
+ }
68
+ // Collect static Link action references for validation
69
+ if (action.type === 'Link' && !type.isNone(action.params)) {
70
+ const params = action.params;
71
+ // Link params can be a string (pageId) or object with pageId property
72
+ if (type.isString(params)) {
73
+ linkActionRefs.push({
74
+ pageId: params,
75
+ action,
76
+ blockId,
77
+ eventId,
78
+ sourcePageId: pageId
79
+ });
80
+ } else if (type.isObject(params) && type.isString(params.pageId)) {
81
+ linkActionRefs.push({
82
+ pageId: params.pageId,
83
+ action,
84
+ blockId,
85
+ eventId,
86
+ sourcePageId: pageId
87
+ });
88
+ }
32
89
  }
33
- typeCounters.actions.increment(action.type);
34
90
  }
35
91
  function buildEvents(block, pageContext) {
36
92
  if (block.events) {
37
93
  Object.keys(block.events).map((key)=>{
94
+ const eventConfigKey = block.events[key]?.['~k'] || block['~k'];
38
95
  if (!type.isArray(block.events[key]) && !type.isObject(block.events[key]) || type.isObject(block.events[key]) && type.isNone(block.events[key].try)) {
39
- throw new Error(`Actions must be an array at "${block.blockId}" in event "${key}" on page "${pageContext.pageId}". Received ${JSON.stringify(block.events[key].try)}`);
96
+ throw new ConfigError(`Actions must be an array at "${block.blockId}" in event "${key}" on page "${pageContext.pageId}".`, {
97
+ received: block.events[key]?.try,
98
+ configKey: eventConfigKey
99
+ });
40
100
  }
41
101
  if (type.isArray(block.events[key])) {
42
102
  block.events[key] = {
@@ -45,13 +105,19 @@ function buildEvents(block, pageContext) {
45
105
  };
46
106
  }
47
107
  if (!type.isArray(block.events[key].try)) {
48
- throw new Error(`Try actions must be an array at "${block.blockId}" in event "${key}.try" on page "${pageContext.pageId}". Received ${JSON.stringify(block.events[key].try)}`);
108
+ throw new ConfigError(`Try actions must be an array at "${block.blockId}" in event "${key}.try" on page "${pageContext.pageId}".`, {
109
+ received: block.events[key].try,
110
+ configKey: eventConfigKey
111
+ });
49
112
  }
50
113
  if (type.isNone(block.events[key].catch)) {
51
114
  block.events[key].catch = [];
52
115
  }
53
116
  if (!type.isArray(block.events[key].catch)) {
54
- throw new Error(`Catch actions must be an array at "${block.blockId}" in event "${key}.catch" on page "${pageContext.pageId}". Received ${JSON.stringify(block.events[key].catch)}`);
117
+ throw new ConfigError(`Catch actions must be an array at "${block.blockId}" in event "${key}.catch" on page "${pageContext.pageId}".`, {
118
+ received: block.events[key].catch,
119
+ configKey: eventConfigKey
120
+ });
55
121
  }
56
122
  const checkDuplicateActionId = createCheckDuplicateId({
57
123
  message: 'Duplicate actionId "{{ id }}" on event "{{ eventId }}" on block "{{ blockId }}" on page "{{ pageId }}".'
@@ -61,6 +127,8 @@ function buildEvents(block, pageContext) {
61
127
  blockId: block.blockId,
62
128
  typeCounters: pageContext.typeCounters,
63
129
  pageId: pageContext.pageId,
130
+ linkActionRefs: pageContext.linkActionRefs,
131
+ requestActionRefs: pageContext.requestActionRefs,
64
132
  checkDuplicateActionId
65
133
  }));
66
134
  block.events[key].catch.map((action)=>checkAction(action, {
@@ -68,6 +136,8 @@ function buildEvents(block, pageContext) {
68
136
  blockId: block.blockId,
69
137
  typeCounters: pageContext.typeCounters,
70
138
  pageId: pageContext.pageId,
139
+ linkActionRefs: pageContext.linkActionRefs,
140
+ requestActionRefs: pageContext.requestActionRefs,
71
141
  checkDuplicateActionId
72
142
  }));
73
143
  });
@@ -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,28 +13,58 @@
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 buildRequest(request, pageContext) {
17
- const { auth, checkDuplicateRequestId, pageId, typeCounters } = pageContext;
18
+ const { auth, checkDuplicateRequestId, context, pageId, typeCounters } = pageContext;
19
+ const configKey = request['~k'];
18
20
  if (type.isUndefined(request.id)) {
19
- throw new Error(`Request id missing at page "${pageId}".`);
21
+ throw new ConfigError(`Request id missing at page "${pageId}".`, {
22
+ configKey
23
+ });
20
24
  }
21
25
  if (!type.isString(request.id)) {
22
- throw new Error(`Request id is not a string at page "${pageId}". Received ${JSON.stringify(request.id)}.`);
26
+ throw new ConfigError(`Request id is not a string at page "${pageId}".`, {
27
+ received: request.id,
28
+ configKey
29
+ });
23
30
  }
24
31
  checkDuplicateRequestId({
25
32
  id: request.id,
33
+ configKey,
26
34
  pageId
27
35
  });
28
36
  if (request.id.includes('.')) {
29
- throw new Error(`Request id "${request.id}" at page "${pageId}" should not include a period (".").`);
37
+ throw new ConfigError(`Request id "${request.id}" at page "${pageId}" should not include a period (".").`, {
38
+ configKey
39
+ });
30
40
  }
31
41
  if (!type.isString(request.type)) {
32
- throw new Error(`Request type is not a string at at request at "${request.id}" at page "${pageId}". Received ${JSON.stringify(request.type)}.`);
42
+ throw new ConfigError(`Request type is not a string at request "${request.id}" at page "${pageId}".`, {
43
+ received: request.type,
44
+ configKey
45
+ });
46
+ }
47
+ typeCounters.requests.increment(request.type, configKey);
48
+ // Validate connectionId references an existing connection
49
+ if (!type.isNone(request.connectionId)) {
50
+ if (!type.isString(request.connectionId)) {
51
+ throw new ConfigError(`Request "${request.id}" at page "${pageId}" connectionId is not a string.`, {
52
+ received: request.connectionId,
53
+ configKey
54
+ });
55
+ }
56
+ if (!context.connectionIds.has(request.connectionId)) {
57
+ throw new ConfigError(`Request "${request.id}" at page "${pageId}" references non-existent connection "${request.connectionId}".`, {
58
+ configKey,
59
+ checkSlug: 'connection-refs'
60
+ });
61
+ }
33
62
  }
34
- typeCounters.requests.increment(request.type);
35
63
  if (type.isUndefined(request.payload)) request.payload = {};
36
64
  if (!type.isObject(request.payload)) {
37
- throw new Error(`Request "${request.id}" at page "${pageId}" payload should be an object.`);
65
+ throw new ConfigError(`Request "${request.id}" at page "${pageId}" payload should be an object.`, {
66
+ configKey
67
+ });
38
68
  }
39
69
  request.auth = auth;
40
70
  request.requestId = request.id;
@@ -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 buildBlock from './buildBlock.js';
17
18
  function buildSubBlocks(block, pageContext) {
18
19
  if (type.isObject(block.areas)) {
@@ -21,7 +22,10 @@ function buildSubBlocks(block, pageContext) {
21
22
  block.areas[key].blocks = [];
22
23
  }
23
24
  if (!type.isArray(block.areas[key].blocks)) {
24
- throw new Error(`Expected blocks to be an array at ${block.blockId} in area ${key} on page ${pageContext.pageId}. Received ${JSON.stringify(block.areas[key].blocks)}`);
25
+ throw new ConfigError(`Expected blocks to be an array at ${block.blockId} in area ${key} on page ${pageContext.pageId}.`, {
26
+ received: block.areas[key].blocks,
27
+ configKey: block.areas[key]['~k'] ?? block['~k']
28
+ });
25
29
  }
26
30
  block.areas[key].blocks.map((blk)=>buildBlock(blk, pageContext));
27
31
  });
@@ -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 countBlockTypes(block, { typeCounters }) {
16
- typeCounters.blocks.increment(block.type);
16
+ typeCounters.blocks.increment(block.type, block['~k']);
17
17
  }
18
18
  export default countBlockTypes;
@@ -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,10 +13,14 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { set, type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
16
17
  function recMoveSkeletonBlocksToArea(block, blockId, pageId) {
17
18
  if (!type.isNone(block.blocks)) {
18
19
  if (!type.isArray(block.blocks)) {
19
- throw new Error(`Skeleton blocks at ${blockId} on page ${pageId} is not an array. Received ${JSON.stringify(block.blocks)}`);
20
+ throw new ConfigError(`Skeleton blocks at ${blockId} on page ${pageId} is not an array.`, {
21
+ received: block.blocks,
22
+ configKey: block['~k']
23
+ });
20
24
  }
21
25
  set(block, 'areas.content.blocks', block.blocks);
22
26
  delete block.blocks;
@@ -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,10 +13,14 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { set, type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
16
17
  function moveSubBlocksToArea(block, pageContext) {
17
18
  if (!type.isNone(block.blocks)) {
18
19
  if (!type.isArray(block.blocks)) {
19
- throw new Error(`Blocks at ${block.blockId} on page ${pageContext.pageId} is not an array. Received ${JSON.stringify(block.blocks)}`);
20
+ throw new ConfigError(`Blocks at ${block.blockId} on page ${pageContext.pageId} is not an array.`, {
21
+ received: block.blocks,
22
+ configKey: block['~k']
23
+ });
20
24
  }
21
25
  set(block, 'areas.content.blocks', block.blocks);
22
26
  delete block.blocks;
@@ -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,25 +13,43 @@
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 validateBlock(block, { pageId }) {
18
+ const configKey = block?.['~k'];
17
19
  if (!type.isObject(block)) {
18
- throw new Error(`Expected block to be an object on page "${pageId}". Received ${JSON.stringify(block)}.`);
20
+ throw new ConfigError(`Expected block to be an object on page "${pageId}".`, {
21
+ received: block,
22
+ configKey
23
+ });
19
24
  }
20
25
  if (type.isUndefined(block.id)) {
21
- throw new Error(`Block id missing at page "${pageId}".`);
26
+ throw new ConfigError(`Block id missing at page "${pageId}".`, {
27
+ configKey
28
+ });
22
29
  }
23
30
  if (!type.isString(block.id)) {
24
- throw new Error(`Block id is not a string at page "${pageId}". Received ${JSON.stringify(block.id)}.`);
31
+ throw new ConfigError(`Block id is not a string at page "${pageId}".`, {
32
+ received: block.id,
33
+ configKey
34
+ });
25
35
  }
26
36
  if (type.isNone(block.type)) {
27
- throw new Error(`Block type is not defined at "${block.id}" on page "${pageId}".`);
37
+ throw new ConfigError(`Block type is not defined at "${block.id}" on page "${pageId}".`, {
38
+ configKey
39
+ });
28
40
  }
29
41
  if (!type.isString(block.type)) {
30
- throw new Error(`Block type is not a string at "${block.id}" on page "${pageId}". Received ${JSON.stringify(block.type)}.`);
42
+ throw new ConfigError(`Block type is not a string at "${block.id}" on page "${pageId}".`, {
43
+ received: block.type,
44
+ configKey
45
+ });
31
46
  }
32
47
  if (!type.isNone(block.requests)) {
33
48
  if (!type.isArray(block.requests)) {
34
- throw new Error(`Requests is not an array at "${block.id}" on page "${pageId}". Received ${JSON.stringify(block.requests)}`);
49
+ throw new ConfigError(`Requests is not an array at "${block.id}" on page "${pageId}".`, {
50
+ received: block.requests,
51
+ configKey
52
+ });
35
53
  }
36
54
  }
37
55
  }
@@ -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.
@@ -13,33 +13,62 @@
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 buildBlock from './buildBlock/buildBlock.js';
18
+ import collectExceptions from '../../utils/collectExceptions.js';
17
19
  import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
18
20
  import createCounter from '../../utils/createCounter.js';
21
+ import validateRequestReferences from './validateRequestReferences.js';
19
22
  function buildPage({ page, index, context, checkDuplicatePageId }) {
23
+ const configKey = page['~k'];
20
24
  if (type.isUndefined(page.id)) {
21
- throw new Error(`Page id missing at page ${index}.`);
25
+ collectExceptions(context, new ConfigError(`Page id missing at page ${index}.`, {
26
+ configKey
27
+ }));
28
+ return {
29
+ failed: true
30
+ };
22
31
  }
23
32
  if (!type.isString(page.id)) {
24
- throw new Error(`Page id is not a string at page ${index}. Received ${JSON.stringify(page.id)}.`);
33
+ collectExceptions(context, new ConfigError(`Page id is not a string at page ${index}.`, {
34
+ received: page.id,
35
+ configKey
36
+ }));
37
+ return {
38
+ failed: true
39
+ };
40
+ }
41
+ if (checkDuplicatePageId) {
42
+ checkDuplicatePageId({
43
+ id: page.id,
44
+ configKey
45
+ });
25
46
  }
26
- checkDuplicatePageId({
27
- id: page.id
28
- });
29
47
  page.pageId = page.id;
30
48
  const requests = [];
49
+ const requestActionRefs = [];
31
50
  buildBlock(page, {
32
51
  auth: page.auth,
33
52
  blockIdCounter: createCounter(),
34
53
  checkDuplicateRequestId: createCheckDuplicateId({
35
54
  message: 'Duplicate requestId "{{ id }}" on page "{{ pageId }}".'
36
55
  }),
56
+ context,
37
57
  pageId: page.pageId,
38
58
  requests,
59
+ requestActionRefs,
60
+ linkActionRefs: context.linkActionRefs,
39
61
  typeCounters: context.typeCounters
40
62
  });
41
63
  // set page.id since buildBlock sets id as well.
42
64
  page.id = `page:${page.pageId}`;
65
+ // Validate that all Request actions reference defined requests
66
+ validateRequestReferences({
67
+ requestActionRefs,
68
+ requests,
69
+ pageId: page.pageId,
70
+ context
71
+ });
43
72
  page.requests = requests;
44
73
  }
45
74
  export default buildPage;
@@ -0,0 +1,33 @@
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
+ function validateLinkReferences({ linkActionRefs, pageIds, context }) {
17
+ const pageIdSet = new Set(pageIds);
18
+ linkActionRefs.forEach(({ pageId, action, sourcePageId })=>{
19
+ // Only skip validation if skip is explicitly true
20
+ // Pages must exist in app even if Link is conditional
21
+ if (action.skip === true) {
22
+ return;
23
+ }
24
+ if (!pageIdSet.has(pageId)) {
25
+ context.handleWarning(new ConfigWarning(`Page "${pageId}" not found. Link on page "${sourcePageId}" references non-existent page.`, {
26
+ configKey: action['~k'],
27
+ prodError: true,
28
+ checkSlug: 'link-refs'
29
+ }));
30
+ }
31
+ });
32
+ }
33
+ export default validateLinkReferences;
@@ -0,0 +1,59 @@
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 traverseConfig from '../../utils/traverseConfig.js';
18
+ function validatePayloadReferences({ page, context }) {
19
+ const requests = page.requests || [];
20
+ requests.forEach((request)=>{
21
+ // Collect payload keys defined in the request
22
+ const payloadKeys = Object.keys(request.payload || {});
23
+ // Skip if no payload defined (nothing to reference)
24
+ if (payloadKeys.length === 0) return;
25
+ // Find _payload references in request.properties
26
+ const payloadRefs = new Map(); // dedup key -> { configKey, fullValue }
27
+ traverseConfig({
28
+ config: request.properties,
29
+ visitor: (obj)=>{
30
+ if (obj._payload !== undefined) {
31
+ const fullValue = type.isString(obj._payload) ? obj._payload : type.isObject(obj._payload) ? obj._payload.key || obj._payload.path : null;
32
+ if (!fullValue) return;
33
+ const dedupKey = fullValue.split(/[.[]/)[0];
34
+ if (!dedupKey || payloadRefs.has(dedupKey)) return;
35
+ payloadRefs.set(dedupKey, {
36
+ configKey: obj['~k'],
37
+ fullValue
38
+ });
39
+ }
40
+ }
41
+ });
42
+ // Warn for undefined payload references
43
+ payloadRefs.forEach(({ configKey, fullValue }, dedupKey)=>{
44
+ // Check if the full reference value matches any payload key.
45
+ // A payload key matches if the reference equals it exactly,
46
+ // or accesses a sub-path of it (key followed by '.' or '[').
47
+ // This handles dotted payload keys like "qc_state.chemical_analysis".
48
+ const isValid = payloadKeys.some((key)=>fullValue === key || fullValue.startsWith(key + '.') || fullValue.startsWith(key + '['));
49
+ if (isValid) return;
50
+ const message = `_payload references "${dedupKey}" in request "${request.requestId}" on page "${page.pageId}", ` + `but no key "${dedupKey}" exists in the request payload definition. ` + `Payload keys are defined in the request's "payload" property. ` + `Check for typos or add the key to the payload definition.`;
51
+ context.handleWarning(new ConfigWarning(message, {
52
+ configKey,
53
+ prodError: true,
54
+ checkSlug: 'payload-refs'
55
+ }));
56
+ });
57
+ });
58
+ }
59
+ export default validatePayloadReferences;
@@ -0,0 +1,33 @@
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
+ function validateRequestReferences({ requestActionRefs, requests, pageId, context }) {
18
+ const requestIds = new Set(requests.map((req)=>req.requestId));
19
+ requestActionRefs.forEach(({ requestId, action })=>{
20
+ // Skip validation if action has skip condition (true or operator object)
21
+ if (action.skip === true || type.isObject(action.skip)) {
22
+ return;
23
+ }
24
+ if (!requestIds.has(requestId)) {
25
+ context.handleWarning(new ConfigWarning(`Request "${requestId}" not defined on page "${pageId}".`, {
26
+ configKey: action['~k'],
27
+ prodError: true,
28
+ checkSlug: 'request-refs'
29
+ }));
30
+ }
31
+ });
32
+ }
33
+ export default validateRequestReferences;
@@ -0,0 +1,41 @@
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 traverseConfig from '../../utils/traverseConfig.js';
17
+ function validateServerStateReferences({ page, context }) {
18
+ (page.requests ?? []).forEach((request)=>{
19
+ if (!request.properties) return;
20
+ let found = false;
21
+ let configKey = request['~k'];
22
+ traverseConfig({
23
+ config: request.properties,
24
+ visitor: (obj)=>{
25
+ if (found) return;
26
+ if (obj._state !== undefined) {
27
+ found = true;
28
+ configKey = obj['~k'] ?? configKey;
29
+ }
30
+ }
31
+ });
32
+ if (!found) return;
33
+ const message = `_state is not available in request properties. ` + `Found _state in request "${request.requestId}" on page "${page.pageId}". ` + `Request properties are evaluated on the server where state is not available. ` + `To use a state value in a request, add it to the request "payload" using _state, ` + `then reference it in request properties using _payload.`;
34
+ context.handleWarning(new ConfigWarning(message, {
35
+ configKey,
36
+ prodError: true,
37
+ checkSlug: 'state-refs'
38
+ }));
39
+ });
40
+ }
41
+ export default validateServerStateReferences;