@lowdefy/build 4.5.2 → 4.7.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 (163) 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 +40 -7
  57. package/dist/build/buildRefs/evaluateStaticOperators.js +52 -0
  58. package/dist/build/buildRefs/getConfigFile.js +25 -6
  59. package/dist/build/buildRefs/getKey.js +1 -1
  60. package/dist/build/buildRefs/getRefContent.js +1 -1
  61. package/dist/build/buildRefs/getRefPath.js +1 -1
  62. package/dist/build/buildRefs/getUserJavascriptFunction.js +18 -4
  63. package/dist/build/buildRefs/makeRefDefinition.js +10 -5
  64. package/dist/build/buildRefs/parseNunjucks.js +1 -1
  65. package/dist/build/buildRefs/parseRefContent.js +108 -7
  66. package/dist/build/buildRefs/runRefResolver.js +11 -3
  67. package/dist/build/buildRefs/runTransformer.js +7 -3
  68. package/dist/build/buildRefs/walker.js +340 -0
  69. package/dist/build/buildTypes.js +22 -7
  70. package/dist/build/cleanBuildDirectory.js +1 -1
  71. package/dist/build/collectDynamicIdentifiers.js +35 -0
  72. package/dist/build/collectTypeNames.js +36 -0
  73. package/dist/build/copyPublicFolder.js +1 -1
  74. package/dist/build/{buildJs → full}/buildJs.js +3 -3
  75. package/dist/build/full/buildPages.js +87 -0
  76. package/dist/build/{buildPages → full}/buildTestPage.js +15 -3
  77. package/dist/build/{updateServerPackageJson.js → full/updateServerPackageJson.js} +1 -1
  78. package/dist/build/{writePages.js → full/writePages.js} +1 -1
  79. package/dist/build/{writeRequests.js → full/writeRequests.js} +11 -3
  80. package/dist/build/{writeTypes.js → full/writeTypes.js} +1 -1
  81. package/dist/build/jit/addInstalledTypes.js +51 -0
  82. package/dist/build/jit/buildJsShallow.js +41 -0
  83. package/dist/build/jit/buildPageJit.js +271 -0
  84. package/dist/build/jit/buildShallowPages.js +90 -0
  85. package/dist/build/jit/createPageRegistry.js +85 -0
  86. package/dist/build/jit/detectMissingPluginPackages.js +62 -0
  87. package/dist/build/jit/isPageContentPath.js +24 -0
  88. package/dist/build/jit/pageContentKeys.js +26 -0
  89. package/dist/build/jit/shallowBuild.js +242 -0
  90. package/dist/build/jit/updateServerPackageJsonJit.js +33 -0
  91. package/dist/build/jit/validatePageTypes.js +73 -0
  92. package/dist/build/jit/writePageJit.js +44 -0
  93. package/dist/build/jit/writePageRegistry.js +23 -0
  94. package/dist/build/jit/writeSourcelessPages.js +23 -0
  95. package/dist/build/testSchema.js +45 -7
  96. package/dist/build/validateConfig.js +2 -2
  97. package/dist/build/validateOperatorsDynamic.js +28 -0
  98. package/dist/build/writeApi.js +1 -1
  99. package/dist/build/writeApp.js +1 -1
  100. package/dist/build/writeAuth.js +1 -1
  101. package/dist/build/writeConfig.js +1 -1
  102. package/dist/build/writeConnections.js +1 -1
  103. package/dist/build/writeGlobal.js +1 -1
  104. package/dist/build/writeLogger.js +19 -0
  105. package/dist/build/writeMaps.js +1 -1
  106. package/dist/build/writeMenus.js +1 -1
  107. package/dist/build/writePluginImports/generateImportFile.js +1 -1
  108. package/dist/build/writePluginImports/writeActionImports.js +1 -1
  109. package/dist/build/writePluginImports/writeActionSchemaMap.js +42 -0
  110. package/dist/build/writePluginImports/writeAuthImports.js +1 -1
  111. package/dist/build/writePluginImports/writeBlockImports.js +1 -1
  112. package/dist/build/writePluginImports/writeBlockSchemaMap.js +42 -0
  113. package/dist/build/writePluginImports/writeConnectionImports.js +1 -1
  114. package/dist/build/writePluginImports/writeIconImports.js +1 -1
  115. package/dist/build/writePluginImports/writeOperatorImports.js +1 -1
  116. package/dist/build/writePluginImports/writeOperatorSchemaMap.js +49 -0
  117. package/dist/build/writePluginImports/writePluginImports.js +16 -1
  118. package/dist/build/writePluginImports/writeStyleImports.js +1 -1
  119. package/dist/createContext.js +16 -4
  120. package/dist/defaultTypesMap.js +479 -457
  121. package/dist/index.js +190 -127
  122. package/dist/indexDev.js +19 -0
  123. package/dist/lowdefySchema.js +588 -0
  124. package/dist/scripts/generateDefaultTypes.js +2 -1
  125. package/dist/scripts/run.js +1 -1
  126. package/dist/{test → test-utils}/buildRefs/testBuildRefsAsyncFunction.js +1 -1
  127. package/dist/{test → test-utils}/buildRefs/testBuildRefsErrorResolver.js +1 -1
  128. package/dist/{test → test-utils}/buildRefs/testBuildRefsNullResolver.js +1 -1
  129. package/dist/{test → test-utils}/buildRefs/testBuildRefsParsingResolver.js +1 -1
  130. package/dist/{test → test-utils}/buildRefs/testBuildRefsResolver.js +1 -1
  131. package/dist/{test → test-utils}/buildRefs/testBuildRefsTransform.js +1 -1
  132. package/dist/{test → test-utils}/buildRefs/testBuildRefsTransformIdentity.js +1 -1
  133. package/dist/test-utils/buildRefs/testJitPageResolver.js +24 -0
  134. package/dist/test-utils/createTestLogger.js +36 -0
  135. package/dist/test-utils/parseTestYaml.js +126 -0
  136. package/dist/test-utils/runBuild.js +228 -0
  137. package/dist/test-utils/runBuildForSnapshots.js +704 -0
  138. package/dist/{test → test-utils}/testContext.js +12 -2
  139. package/dist/utils/collectExceptions.js +34 -0
  140. package/dist/utils/countOperators.js +31 -12
  141. package/dist/utils/createBuildHandleError.js +38 -0
  142. package/dist/utils/createCheckDuplicateId.js +15 -9
  143. package/dist/utils/createCounter.js +16 -3
  144. package/dist/utils/createHandleWarning.js +41 -0
  145. package/dist/utils/createPluginTypesMap.js +1 -1
  146. package/dist/utils/extractOperatorKey.js +36 -0
  147. package/dist/utils/findConfigKey.js +37 -0
  148. package/dist/utils/findSimilarString.js +53 -0
  149. package/dist/utils/logCollectedErrors.js +30 -0
  150. package/dist/utils/makeId.js +16 -8
  151. package/dist/utils/preserveMetaProperties.js +27 -0
  152. package/dist/utils/readConfigFile.js +1 -1
  153. package/dist/utils/setNonEnumerableProperty.js +23 -0
  154. package/dist/utils/traverseConfig.js +43 -0
  155. package/dist/utils/tryBuildStep.js +46 -0
  156. package/dist/utils/writeBuildArtifact.js +1 -1
  157. package/package.json +46 -41
  158. package/dist/build/buildPages/buildPages.js +0 -31
  159. package/dist/build/buildRefs/evaluateBuildOperators.js +0 -34
  160. package/dist/build/buildRefs/getRefsFromFile.js +0 -37
  161. package/dist/build/buildRefs/populateRefs.js +0 -43
  162. package/dist/build/buildRefs/recursiveBuild.js +0 -85
  163. package/dist/utils/formatErrorMessage.js +0 -56
@@ -0,0 +1,340 @@
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 { get, type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
17
+ import { evaluateOperators } from '@lowdefy/operators';
18
+ import makeRefDefinition from './makeRefDefinition.js';
19
+ import getRefContent from './getRefContent.js';
20
+ import runTransformer from './runTransformer.js';
21
+ import getKey from './getKey.js';
22
+ import setNonEnumerableProperty from '../../utils/setNonEnumerableProperty.js';
23
+ import collectExceptions from '../../utils/collectExceptions.js';
24
+ let WalkContext = class WalkContext {
25
+ child(segment) {
26
+ return new WalkContext({
27
+ buildContext: this.buildContext,
28
+ refId: this.refId,
29
+ sourceRefId: this.sourceRefId,
30
+ vars: this.vars,
31
+ path: this.path ? `${this.path}.${segment}` : segment,
32
+ currentFile: this.currentFile,
33
+ refChain: this.refChain,
34
+ operators: this.operators,
35
+ env: this.env,
36
+ dynamicIdentifiers: this.dynamicIdentifiers,
37
+ shouldStop: this.shouldStop
38
+ });
39
+ }
40
+ forRef({ refId, vars, filePath }) {
41
+ const newChain = new Set(this.refChain);
42
+ if (filePath) {
43
+ newChain.add(filePath);
44
+ }
45
+ return new WalkContext({
46
+ buildContext: this.buildContext,
47
+ refId,
48
+ sourceRefId: this.refId,
49
+ vars: vars ?? {},
50
+ path: this.path,
51
+ currentFile: filePath ?? this.currentFile,
52
+ refChain: newChain,
53
+ operators: this.operators,
54
+ env: this.env,
55
+ dynamicIdentifiers: this.dynamicIdentifiers,
56
+ shouldStop: this.shouldStop
57
+ });
58
+ }
59
+ collectError(error) {
60
+ collectExceptions(this.buildContext, error);
61
+ }
62
+ get refMap() {
63
+ return this.buildContext.refMap;
64
+ }
65
+ get unresolvedRefVars() {
66
+ return this.buildContext.unresolvedRefVars;
67
+ }
68
+ constructor({ buildContext, refId, sourceRefId, vars, path, currentFile, refChain, operators, env, dynamicIdentifiers, shouldStop }){
69
+ this.buildContext = buildContext;
70
+ this.refId = refId;
71
+ this.sourceRefId = sourceRefId;
72
+ this.vars = vars;
73
+ this.path = path;
74
+ this.currentFile = currentFile;
75
+ this.refChain = refChain;
76
+ this.operators = operators;
77
+ this.env = env;
78
+ this.dynamicIdentifiers = dynamicIdentifiers;
79
+ this.shouldStop = shouldStop;
80
+ }
81
+ };
82
+ // Detect _build.* operator objects: single non-tilde key starting with '_build.'
83
+ function isBuildOperator(node) {
84
+ const keys = Object.keys(node);
85
+ const nonTildeKeys = keys.filter((k)=>!k.startsWith('~'));
86
+ return nonTildeKeys.length === 1 && nonTildeKeys[0].startsWith('_build.');
87
+ }
88
+ // Set ~r as non-enumerable if not already present
89
+ function tagRef(node, refId) {
90
+ if (type.isObject(node) || type.isArray(node)) {
91
+ if (node['~r'] === undefined) {
92
+ setNonEnumerableProperty(node, '~r', refId);
93
+ }
94
+ }
95
+ }
96
+ // Recursively set ~r on all objects/arrays that don't already have it
97
+ function tagRefDeep(node, refId) {
98
+ if (!type.isObject(node) && !type.isArray(node)) return;
99
+ if (node['~r'] !== undefined) return;
100
+ setNonEnumerableProperty(node, '~r', refId);
101
+ if (type.isArray(node)) {
102
+ for(let i = 0; i < node.length; i++){
103
+ tagRefDeep(node[i], refId);
104
+ }
105
+ } else {
106
+ for (const key of Object.keys(node)){
107
+ tagRefDeep(node[key], refId);
108
+ }
109
+ }
110
+ }
111
+ // Deep clone preserving ~r, ~l, ~k non-enumerable markers.
112
+ // Used before resolving ref def path/vars to prevent mutation of stored originals.
113
+ function cloneForResolve(value) {
114
+ if (!type.isObject(value) && !type.isArray(value)) return value;
115
+ if (type.isArray(value)) {
116
+ const clone = value.map((item)=>cloneForResolve(item));
117
+ if (value['~r'] !== undefined) setNonEnumerableProperty(clone, '~r', value['~r']);
118
+ if (value['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', value['~l']);
119
+ if (value['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', value['~k']);
120
+ if (value['~arr'] !== undefined) setNonEnumerableProperty(clone, '~arr', value['~arr']);
121
+ return clone;
122
+ }
123
+ const clone = {};
124
+ for (const key of Object.keys(value)){
125
+ clone[key] = cloneForResolve(value[key]);
126
+ }
127
+ if (value['~r'] !== undefined) setNonEnumerableProperty(clone, '~r', value['~r']);
128
+ if (value['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', value['~l']);
129
+ if (value['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', value['~k']);
130
+ return clone;
131
+ }
132
+ // Deep clone a var value, preserving markers and setting ~r provenance.
133
+ // When sourceRefId is null, preserves the template's existing ~r markers.
134
+ function cloneVarValue(value, sourceRefId) {
135
+ if (!type.isObject(value) && !type.isArray(value)) return value;
136
+ return cloneDeepWithProvenance(value, sourceRefId);
137
+ }
138
+ function cloneDeepWithProvenance(node, sourceRefId) {
139
+ if (!type.isObject(node) && !type.isArray(node)) return node;
140
+ if (type.isArray(node)) {
141
+ const clone = node.map((item)=>cloneDeepWithProvenance(item, sourceRefId));
142
+ if (node['~r'] !== undefined) {
143
+ setNonEnumerableProperty(clone, '~r', node['~r']);
144
+ } else if (sourceRefId) {
145
+ setNonEnumerableProperty(clone, '~r', sourceRefId);
146
+ }
147
+ if (node['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', node['~l']);
148
+ if (node['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', node['~k']);
149
+ if (node['~arr'] !== undefined) setNonEnumerableProperty(clone, '~arr', node['~arr']);
150
+ return clone;
151
+ }
152
+ const clone = {};
153
+ for (const key of Object.keys(node)){
154
+ clone[key] = cloneDeepWithProvenance(node[key], sourceRefId);
155
+ }
156
+ if (node['~r'] !== undefined) {
157
+ setNonEnumerableProperty(clone, '~r', node['~r']);
158
+ } else if (sourceRefId) {
159
+ setNonEnumerableProperty(clone, '~r', sourceRefId);
160
+ }
161
+ if (node['~l'] !== undefined) setNonEnumerableProperty(clone, '~l', node['~l']);
162
+ if (node['~k'] !== undefined) setNonEnumerableProperty(clone, '~k', node['~k']);
163
+ return clone;
164
+ }
165
+ // Evaluate a _build.* operator using evaluateOperators
166
+ function evaluateBuildOperator(node, ctx) {
167
+ const { output, errors } = evaluateOperators({
168
+ input: node,
169
+ operators: ctx.operators,
170
+ operatorPrefix: '_build.',
171
+ env: ctx.env,
172
+ dynamicIdentifiers: ctx.dynamicIdentifiers
173
+ });
174
+ if (errors.length > 0) {
175
+ errors.forEach((error)=>{
176
+ error.filePath = error.refId ? ctx.refMap[error.refId]?.path : ctx.currentFile;
177
+ ctx.collectError(error);
178
+ });
179
+ }
180
+ return output;
181
+ }
182
+ // Resolve a _var node
183
+ function resolveVar(node, ctx) {
184
+ const varDef = node._var;
185
+ // String form: { _var: "key" }
186
+ if (type.isString(varDef)) {
187
+ const value = get(ctx.vars, varDef, {
188
+ default: null
189
+ });
190
+ return cloneVarValue(value, ctx.sourceRefId);
191
+ }
192
+ // Object form: { _var: { key, default } }
193
+ if (type.isObject(varDef) && type.isString(varDef.key)) {
194
+ const varFromParent = get(ctx.vars, varDef.key);
195
+ // Var provided (even if null) → use parent's sourceRefId for location
196
+ if (!type.isUndefined(varFromParent)) {
197
+ return cloneVarValue(varFromParent, ctx.sourceRefId);
198
+ }
199
+ // Not provided → use default, preserve template's ~r
200
+ const defaultValue = type.isNone(varDef.default) ? null : varDef.default;
201
+ return cloneVarValue(defaultValue, null);
202
+ }
203
+ throw new ConfigError('_var operator takes a string or object with "key" field as arguments.', {
204
+ filePath: ctx.currentFile
205
+ });
206
+ }
207
+ // Resolve a _ref node (12-step ref handling)
208
+ async function resolveRef(node, ctx) {
209
+ // 1. Create ref definition
210
+ const lineNumber = node['~l'];
211
+ const refDef = makeRefDefinition(node._ref, ctx.refId, ctx.refMap, lineNumber);
212
+ // 2. Store unresolved vars before resolution mutates them, and clone so
213
+ // resolution operates on a copy (preserving original.vars for resolver refs).
214
+ const varKeys = Object.keys(refDef.vars);
215
+ if (varKeys.length > 0) {
216
+ ctx.unresolvedRefVars[refDef.id] = refDef.vars;
217
+ refDef.vars = cloneForResolve(refDef.vars);
218
+ }
219
+ // 3. Resolve dynamic path/vars/key
220
+ if (type.isObject(refDef.path)) {
221
+ refDef.path = await resolve(cloneForResolve(refDef.path), ctx);
222
+ }
223
+ for (const varKey of varKeys){
224
+ if (type.isObject(refDef.vars[varKey]) || type.isArray(refDef.vars[varKey])) {
225
+ refDef.vars[varKey] = await resolve(refDef.vars[varKey], ctx);
226
+ }
227
+ }
228
+ if (type.isObject(refDef.key)) {
229
+ refDef.key = await resolve(cloneForResolve(refDef.key), ctx);
230
+ }
231
+ // 4. Update refMap with resolved path; store original for resolver refs
232
+ ctx.refMap[refDef.id].path = refDef.path;
233
+ if (!refDef.path) {
234
+ ctx.refMap[refDef.id].original = refDef.original;
235
+ }
236
+ // 5. Circular detection
237
+ if (refDef.path && ctx.refChain.has(refDef.path)) {
238
+ const chainDisplay = [
239
+ ...ctx.refChain,
240
+ refDef.path
241
+ ].join('\n -> ');
242
+ throw new ConfigError(`Circular reference detected. File "${refDef.path}" references itself through:\n -> ${chainDisplay}`, {
243
+ filePath: ctx.currentFile,
244
+ lineNumber: ctx.currentFile ? lineNumber : null
245
+ });
246
+ }
247
+ // Steps 6-12: File operations that can fail independently per ref.
248
+ // Errors are collected so the walker can continue processing sibling refs,
249
+ // allowing multiple errors to be reported at once.
250
+ try {
251
+ // 6. Load content
252
+ let content = await getRefContent({
253
+ context: ctx.buildContext,
254
+ refDef,
255
+ referencedFrom: ctx.currentFile
256
+ });
257
+ // 7. Create child context for the ref file
258
+ const childCtx = ctx.forRef({
259
+ refId: refDef.id,
260
+ vars: refDef.vars,
261
+ filePath: refDef.path
262
+ });
263
+ // 8. Walk the content
264
+ content = await resolve(content, childCtx);
265
+ // 9. Run transformer
266
+ content = await runTransformer({
267
+ context: ctx.buildContext,
268
+ input: content,
269
+ refDef
270
+ });
271
+ // 10. Extract key
272
+ content = getKey({
273
+ input: content,
274
+ refDef
275
+ });
276
+ // 11. Tag all nodes with ~r for provenance
277
+ tagRefDeep(content, refDef.id);
278
+ // 12. Propagate ~ignoreBuildChecks
279
+ if (refDef.ignoreBuildChecks !== undefined) {
280
+ if (type.isObject(content)) {
281
+ content['~ignoreBuildChecks'] = refDef.ignoreBuildChecks;
282
+ } else if (type.isArray(content)) {
283
+ content.forEach((item)=>{
284
+ if (type.isObject(item)) {
285
+ item['~ignoreBuildChecks'] = refDef.ignoreBuildChecks;
286
+ }
287
+ });
288
+ }
289
+ }
290
+ return content;
291
+ } catch (error) {
292
+ if (error instanceof ConfigError) {
293
+ ctx.collectError(error);
294
+ return null;
295
+ }
296
+ throw error;
297
+ }
298
+ }
299
+ // Core walk function — single-pass async tree walker
300
+ async function resolve(node, ctx) {
301
+ // 1. Primitives pass through
302
+ if (!type.isObject(node) && !type.isArray(node)) return node;
303
+ // 2. Object with _ref
304
+ if (type.isObject(node) && !type.isUndefined(node._ref)) {
305
+ return resolveRef(node, ctx);
306
+ }
307
+ // 4. Object with _var — resolve, then re-walk the result so any
308
+ // _ref or _build.* operators inside the default value get processed.
309
+ if (type.isObject(node) && !type.isUndefined(node._var)) {
310
+ const varResult = resolveVar(node, ctx);
311
+ return resolve(varResult, ctx);
312
+ }
313
+ // 5. Array — walk children in-place
314
+ if (type.isArray(node)) {
315
+ for(let i = 0; i < node.length; i++){
316
+ node[i] = await resolve(node[i], ctx.child(String(i)));
317
+ }
318
+ return node;
319
+ }
320
+ // 6. Object — walk children in-place
321
+ const keys = Object.keys(node);
322
+ for (const key of keys){
323
+ if (ctx.shouldStop) {
324
+ const childPath = ctx.path ? `${ctx.path}.${key}` : key;
325
+ if (ctx.shouldStop(childPath, ctx.refId)) {
326
+ delete node[key];
327
+ continue;
328
+ }
329
+ }
330
+ node[key] = await resolve(node[key], ctx.child(key));
331
+ }
332
+ // Check if this is a _build.* operator
333
+ if (isBuildOperator(node)) {
334
+ const result = evaluateBuildOperator(node, ctx);
335
+ tagRefDeep(result, ctx.refId);
336
+ return result;
337
+ }
338
+ return node;
339
+ }
340
+ export { resolve, WalkContext, cloneForResolve, tagRefDeep };
@@ -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.
@@ -14,21 +14,36 @@
14
14
  limitations under the License.
15
15
  */ import basicTypes from '@lowdefy/blocks-basic/types';
16
16
  import loaderTypes from '@lowdefy/blocks-loaders/types';
17
+ import { ConfigError, ConfigWarning } from '@lowdefy/errors';
18
+ import findSimilarString from '../utils/findSimilarString.js';
17
19
  function buildTypeClass(context, { counter, definitions, store, typeClass, warnIfMissing = false }) {
18
20
  const counts = counter.getCounts();
21
+ const definedTypes = Object.keys(definitions);
19
22
  Object.keys(counts).forEach((typeName)=>{
20
23
  if (!definitions[typeName]) {
24
+ const configKey = counter.getLocation(typeName);
25
+ let message = `${typeClass} type "${typeName}" was used but is not defined.`;
26
+ const suggestion = findSimilarString({
27
+ input: typeName,
28
+ candidates: definedTypes
29
+ });
30
+ if (suggestion) {
31
+ message += ` Did you mean "${suggestion}"?`;
32
+ }
21
33
  if (warnIfMissing) {
22
- if (typeName === '_id') {
23
- return;
24
- }
25
- context.logger.warn(`${typeClass} type "${typeName}" was used but is not defined.`);
34
+ context.handleWarning(new ConfigWarning(message, {
35
+ configKey,
36
+ checkSlug: 'types'
37
+ }));
26
38
  return;
27
39
  }
28
- throw new Error(`${typeClass} type "${typeName}" was used but is not defined.`);
40
+ throw new ConfigError(message, {
41
+ configKey,
42
+ checkSlug: 'types'
43
+ });
29
44
  }
30
45
  store[typeName] = {
31
- originalTypeName: definitions[typeName].originalTypeName,
46
+ originalTypeName: definitions[typeName].originalTypeName ?? typeName,
32
47
  package: definitions[typeName].package,
33
48
  version: definitions[typeName].version,
34
49
  count: counts[typeName]
@@ -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.
@@ -0,0 +1,35 @@
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
+ function collectDynamicIdentifiers({ operators }) {
17
+ const dynamicIdentifiers = new Set();
18
+ Object.entries(operators).forEach(([operatorName, operatorFn])=>{
19
+ if (!type.isFunction(operatorFn)) return;
20
+ if (operatorFn.dynamic === true) {
21
+ dynamicIdentifiers.add(operatorName);
22
+ return;
23
+ }
24
+ // Check for method-level dynamic in meta
25
+ if (type.isObject(operatorFn.meta)) {
26
+ Object.entries(operatorFn.meta).forEach(([methodName, methodMeta])=>{
27
+ if (type.isObject(methodMeta) && methodMeta.dynamic === true) {
28
+ dynamicIdentifiers.add(`${operatorName}.${methodName}`);
29
+ }
30
+ });
31
+ }
32
+ });
33
+ return dynamicIdentifiers;
34
+ }
35
+ export default collectDynamicIdentifiers;
@@ -0,0 +1,36 @@
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
+ function collectTypeNames({ typesMap }) {
17
+ const typeNames = new Set();
18
+ if (!type.isObject(typesMap)) {
19
+ return typeNames;
20
+ }
21
+ [
22
+ 'blocks',
23
+ 'requests',
24
+ 'connections',
25
+ 'actions',
26
+ 'controls'
27
+ ].forEach((category)=>{
28
+ if (type.isObject(typesMap[category])) {
29
+ Object.keys(typesMap[category]).forEach((typeName)=>{
30
+ typeNames.add(typeName);
31
+ });
32
+ }
33
+ });
34
+ return typeNames;
35
+ }
36
+ export default collectTypeNames;
@@ -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.
@@ -12,11 +12,11 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import jsMapParser from './jsMapParser.js';
15
+ */ import jsMapParser from '../buildJs/jsMapParser.js';
16
16
  function buildJs({ components, context }) {
17
17
  components.pages = components.pages.map((page)=>{
18
18
  const pageRequests = [
19
- ...page.requests
19
+ ...page.requests ?? []
20
20
  ];
21
21
  delete page.requests;
22
22
  const cleanPage = jsMapParser({
@@ -0,0 +1,87 @@
1
+ /* eslint-disable no-param-reassign */ /*
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, shouldSuppressBuildCheck } from '@lowdefy/errors';
17
+ import buildPage from '../buildPages/buildPage.js';
18
+ import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
19
+ import validateLinkReferences from '../buildPages/validateLinkReferences.js';
20
+ import validatePayloadReferences from '../buildPages/validatePayloadReferences.js';
21
+ import validateServerStateReferences from '../buildPages/validateServerStateReferences.js';
22
+ import validateStateReferences from '../buildPages/validateStateReferences.js';
23
+ function buildPages({ components, context }) {
24
+ const pages = type.isArray(components.pages) ? components.pages : [];
25
+ const checkDuplicatePageId = createCheckDuplicateId({
26
+ message: 'Duplicate pageId "{{ id }}".'
27
+ });
28
+ // Initialize linkActionRefs to collect Link action references across all pages
29
+ context.linkActionRefs = [];
30
+ // Track which pages failed to build so we skip them in validation
31
+ const failedPageIndices = new Set();
32
+ // Wrap each page build to collect errors instead of stopping on first error
33
+ pages.forEach((page, index)=>{
34
+ try {
35
+ const result = buildPage({
36
+ page,
37
+ index,
38
+ context,
39
+ checkDuplicatePageId
40
+ });
41
+ // buildPage returns { failed: true } when validation fails
42
+ if (result?.failed) {
43
+ failedPageIndices.add(index);
44
+ }
45
+ } catch (error) {
46
+ // Skip suppressed ConfigErrors (via ~ignoreBuildChecks)
47
+ if (error instanceof ConfigError && shouldSuppressBuildCheck(error, context.keyMap)) {
48
+ return;
49
+ }
50
+ // Collect error object if context.errors exists, otherwise throw (for backward compat with tests)
51
+ if (context?.errors) {
52
+ context.errors.push(error);
53
+ failedPageIndices.add(index);
54
+ } else {
55
+ throw error;
56
+ }
57
+ }
58
+ });
59
+ // Validate that all Link actions reference existing pages
60
+ // Include all pages — a link to a broken page is valid; the page error is already reported
61
+ const pageIds = pages.map((page)=>page.pageId);
62
+ validateLinkReferences({
63
+ linkActionRefs: context.linkActionRefs,
64
+ pageIds,
65
+ context
66
+ });
67
+ // Validate that _state references use defined block IDs
68
+ // and _payload references use defined payload keys
69
+ // Skip pages that failed to build
70
+ pages.forEach((page, index)=>{
71
+ if (failedPageIndices.has(index)) return;
72
+ validateStateReferences({
73
+ page,
74
+ context
75
+ });
76
+ validatePayloadReferences({
77
+ page,
78
+ context
79
+ });
80
+ validateServerStateReferences({
81
+ page,
82
+ context
83
+ });
84
+ });
85
+ return components;
86
+ }
87
+ export default buildPages;
@@ -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.
@@ -16,7 +16,7 @@
16
16
  import buildAuth from '../buildAuth/buildAuth.js';
17
17
  import buildPages from './buildPages.js';
18
18
  import createContext from '../../createContext.js';
19
- function buildTestPage({ pageConfig }) {
19
+ function buildTestPage({ pageConfig, connectionIds = [] }) {
20
20
  const context = createContext({
21
21
  customTypesMap: {},
22
22
  directories: {},
@@ -24,10 +24,22 @@ function buildTestPage({ pageConfig }) {
24
24
  debug: ()=>{},
25
25
  log: ()=>{},
26
26
  warn: ()=>{},
27
- error: ()=>{}
27
+ error: ()=>{},
28
+ ui: {
29
+ warn: ()=>{},
30
+ error: ()=>{}
31
+ }
28
32
  },
29
33
  stage: 'test'
30
34
  });
35
+ // Add any connectionIds from test config to allow validation to pass
36
+ connectionIds.forEach((id)=>context.connectionIds.add(id));
37
+ // Also extract connectionIds from requests in the pageConfig
38
+ (pageConfig.requests || []).forEach((request)=>{
39
+ if (request.connectionId) {
40
+ context.connectionIds.add(request.connectionId);
41
+ }
42
+ });
31
43
  const components = {
32
44
  pages: [
33
45
  pageConfig
@@ -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
  /*
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.
@@ -12,9 +12,17 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import { serializer } from '@lowdefy/helpers';
15
+ */ import { serializer, type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
16
17
  async function writeRequestsOnPage({ page, context }) {
17
- return Promise.all(page.requests.map(async (request)=>{
18
+ const requests = page.requests ?? [];
19
+ if (!type.isArray(requests)) {
20
+ throw new ConfigError('Page requests must be an array.', {
21
+ received: requests,
22
+ configKey: page['~k']
23
+ });
24
+ }
25
+ return Promise.all(requests.map(async (request)=>{
18
26
  await context.writeBuildArtifact(`pages/${page.pageId}/requests/${request.requestId}.json`, serializer.serializeToString(request ?? {}));
19
27
  delete request.properties;
20
28
  delete request.type;
@@ -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.