@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
@@ -0,0 +1,51 @@
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 fs from 'fs';
16
+ import path from 'path';
17
+ function getInstalledPackages(directories) {
18
+ if (!directories.server) return null;
19
+ const pkgPath = path.join(directories.server, 'package.json');
20
+ if (!fs.existsSync(pkgPath)) return null;
21
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
22
+ return new Set(Object.keys(pkg.dependencies ?? {}));
23
+ }
24
+ // In dev mode, page content is built JIT so page-level types (actions, blocks,
25
+ // operators) aren't counted during skeleton build. Include all types from
26
+ // installed packages so they're available for client-side use. Dev server
27
+ // pre-installs default packages so bundle size is not a concern — only
28
+ // production builds tree-shake by counting exact type usage.
29
+ function addInstalledTypes({ components, context }) {
30
+ const installedPackages = getInstalledPackages(context.directories);
31
+ if (!installedPackages) return;
32
+ const addTypes = (store, definitions)=>{
33
+ for (const [typeName, def] of Object.entries(definitions)){
34
+ if (!store[typeName] && installedPackages.has(def.package)) {
35
+ store[typeName] = {
36
+ originalTypeName: def.originalTypeName,
37
+ package: def.package,
38
+ version: def.version,
39
+ count: 0
40
+ };
41
+ }
42
+ }
43
+ };
44
+ addTypes(components.types.actions, context.typesMap.actions);
45
+ addTypes(components.types.blocks, context.typesMap.blocks);
46
+ addTypes(components.types.operators.client, context.typesMap.operators.client);
47
+ addTypes(components.types.operators.server, context.typesMap.operators.server);
48
+ // Expose installedPackages on context for later use (e.g., writing build artifact)
49
+ context.installedPackages = installedPackages;
50
+ }
51
+ export default addInstalledTypes;
@@ -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 jsMapParser from '../buildJs/jsMapParser.js';
16
+ function buildJsShallow({ components, context }) {
17
+ // Extract JS from api/connections (page JS is built JIT)
18
+ if (components.api) {
19
+ components.api = jsMapParser({
20
+ input: components.api,
21
+ jsMap: context.jsMap,
22
+ env: 'server'
23
+ });
24
+ }
25
+ if (components.connections) {
26
+ components.connections = jsMapParser({
27
+ input: components.connections,
28
+ jsMap: context.jsMap,
29
+ env: 'server'
30
+ });
31
+ }
32
+ // Ensure both client and server jsMap keys exist.
33
+ // Page JS extraction is deferred to JIT build.
34
+ if (!context.jsMap.client) {
35
+ context.jsMap.client = {};
36
+ }
37
+ if (!context.jsMap.server) {
38
+ context.jsMap.server = {};
39
+ }
40
+ }
41
+ export default buildJsShallow;
@@ -0,0 +1,252 @@
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 fs from 'fs';
16
+ import path from 'path';
17
+ import { serializer, type } from '@lowdefy/helpers';
18
+ import { ConfigError, LowdefyInternalError } from '@lowdefy/errors';
19
+ import addKeys from '../addKeys.js';
20
+ import buildPage from '../buildPages/buildPage.js';
21
+ import validateLinkReferences from '../buildPages/validateLinkReferences.js';
22
+ import validatePayloadReferences from '../buildPages/validatePayloadReferences.js';
23
+ import validateServerStateReferences from '../buildPages/validateServerStateReferences.js';
24
+ import validateStateReferences from '../buildPages/validateStateReferences.js';
25
+ import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
26
+ import createContext from '../../createContext.js';
27
+ import createRefReviver from '../buildRefs/createRefReviver.js';
28
+ import evaluateBuildOperators from '../buildRefs/evaluateBuildOperators.js';
29
+ import evaluateStaticOperators from '../buildRefs/evaluateStaticOperators.js';
30
+ import jsMapParser from '../buildJs/jsMapParser.js';
31
+ import makeRefDefinition from '../buildRefs/makeRefDefinition.js';
32
+ import recursiveBuild from '../buildRefs/recursiveBuild.js';
33
+ import detectMissingPluginPackages from './detectMissingPluginPackages.js';
34
+ import updateServerPackageJsonJit from './updateServerPackageJsonJit.js';
35
+ import validatePageTypes from './validatePageTypes.js';
36
+ import writePageJit from './writePageJit.js';
37
+ async function buildPageJit({ pageId, pageRegistry, context, directories, logger }) {
38
+ // Use provided context or create a minimal one for JIT builds
39
+ const buildContext = context ?? createContext({
40
+ directories,
41
+ logger: logger ?? console,
42
+ stage: 'dev'
43
+ });
44
+ const pageEntry = type.isFunction(pageRegistry.get) ? pageRegistry.get(pageId) : pageRegistry[pageId];
45
+ if (!pageEntry) {
46
+ return null;
47
+ }
48
+ // Reset errors for this build. Keep a local reference so that concurrent
49
+ // JIT builds (different pages sharing buildContext) cannot corrupt our
50
+ // error list by reassigning buildContext.errors during an await.
51
+ const buildErrors = [];
52
+ buildContext.errors = buildErrors;
53
+ try {
54
+ // Pages without a source file (e.g., default 404) can only be served from
55
+ // their pre-built artifact — they have no YAML to re-resolve from.
56
+ // All user pages (with refId) always JIT-resolve from source YAML so that
57
+ // page-only edits are picked up without a skeleton rebuild.
58
+ if (!pageEntry.refId) {
59
+ const pagePath = path.join(buildContext.directories.build, 'pages', pageId, `${pageId}.json`);
60
+ try {
61
+ const content = await fs.promises.readFile(pagePath, 'utf8');
62
+ return serializer.deserialize(JSON.parse(content));
63
+ } catch (err) {
64
+ if (err.code !== 'ENOENT') throw err;
65
+ }
66
+ }
67
+ // Resolve the page file from scratch using the source file path determined
68
+ // by createPageRegistry's parent chain walk.
69
+ if (!pageEntry.refPath && !pageEntry.resolverOriginal) {
70
+ throw new ConfigError(`Page "${pageId}" has no source file reference. Cannot resolve page content.`);
71
+ }
72
+ // Resolve unresolved vars (which may contain inner _ref objects) fresh from disk.
73
+ // For resolver pages, unresolved vars live in resolverOriginal.vars (single source).
74
+ // For file-backed pages, they're stored separately in unresolvedVars.
75
+ const unresolvedVars = pageEntry.unresolvedVars ?? pageEntry.resolverOriginal?.vars;
76
+ let resolvedVars = null;
77
+ if (unresolvedVars) {
78
+ const varRefDef = makeRefDefinition({}, null, buildContext.refMap);
79
+ resolvedVars = await recursiveBuild({
80
+ context: buildContext,
81
+ refDef: varRefDef,
82
+ count: 0,
83
+ content: unresolvedVars,
84
+ referencedFrom: pageEntry.refPath ?? pageEntry.resolverOriginal?.resolver
85
+ });
86
+ resolvedVars = await evaluateBuildOperators({
87
+ context: buildContext,
88
+ input: resolvedVars,
89
+ refDef: varRefDef
90
+ });
91
+ }
92
+ let refDef;
93
+ if (pageEntry.resolverOriginal) {
94
+ const resolverDefinition = resolvedVars ? {
95
+ ...pageEntry.resolverOriginal,
96
+ vars: resolvedVars
97
+ } : pageEntry.resolverOriginal;
98
+ refDef = makeRefDefinition(resolverDefinition, null, buildContext.refMap);
99
+ buildContext.refMap[refDef.id].path = null;
100
+ } else {
101
+ const refDefinition = resolvedVars ? {
102
+ path: pageEntry.refPath,
103
+ vars: resolvedVars
104
+ } : pageEntry.refPath;
105
+ refDef = makeRefDefinition(refDefinition, null, buildContext.refMap);
106
+ buildContext.refMap[refDef.id].path = refDef.path;
107
+ }
108
+ let processed = await recursiveBuild({
109
+ context: buildContext,
110
+ refDef,
111
+ count: 0
112
+ });
113
+ // Top-level operator evaluation (same as buildRefs does after recursiveBuild)
114
+ processed = await evaluateBuildOperators({
115
+ context: buildContext,
116
+ input: processed,
117
+ refDef
118
+ });
119
+ processed = evaluateStaticOperators({
120
+ context: buildContext,
121
+ input: processed,
122
+ refDef
123
+ });
124
+ // When resolving from a collection file (with vars), the result is an array of pages.
125
+ // Find the specific page by ID.
126
+ if (type.isArray(processed)) {
127
+ processed = processed.find((p)=>type.isObject(p) && p.id === pageId);
128
+ if (!processed) {
129
+ throw new ConfigError(`Page "${pageId}" not found in resolved page source file.`);
130
+ }
131
+ }
132
+ // Stamp root-level content with ~r for correct error file tracing.
133
+ // recursiveBuild stamps child _ref content via createRefReviver, but the
134
+ // root file's own objects have no parent to do this. Without ~r, addKeys
135
+ // can't link objects to their source file and errors fall back to lowdefy.yaml.
136
+ const reviver = createRefReviver(refDef.id);
137
+ processed = serializer.copy(processed, {
138
+ reviver
139
+ });
140
+ // Apply skeleton-computed auth (buildAuth ran during skeleton build)
141
+ processed.auth = pageEntry.auth;
142
+ // Add keys to the resolved page
143
+ addKeys({
144
+ components: processed,
145
+ context: buildContext
146
+ });
147
+ // Initialize linkActionRefs for buildPage (normally done by buildPages)
148
+ if (!buildContext.linkActionRefs) {
149
+ buildContext.linkActionRefs = [];
150
+ }
151
+ // Build the page (validation, block processing)
152
+ const checkDuplicatePageId = createCheckDuplicateId({
153
+ message: 'Duplicate pageId "{{ id }}".'
154
+ });
155
+ buildPage({
156
+ page: processed,
157
+ index: 0,
158
+ context: buildContext,
159
+ checkDuplicatePageId
160
+ });
161
+ // Validate that all page-level types (blocks, actions, operators) exist
162
+ validatePageTypes({
163
+ context: buildContext
164
+ });
165
+ // Detect plugin packages that are in typesMap but not installed in server
166
+ const missingPackages = detectMissingPluginPackages({
167
+ context: buildContext,
168
+ installedPluginPackages: buildContext.installedPluginPackages
169
+ });
170
+ if (missingPackages.size > 0) {
171
+ if (buildContext.directories.server) {
172
+ await updateServerPackageJsonJit({
173
+ directories: buildContext.directories,
174
+ missingPackages
175
+ });
176
+ }
177
+ return {
178
+ installing: true,
179
+ packages: [
180
+ ...missingPackages.keys()
181
+ ]
182
+ };
183
+ }
184
+ // Validate link, state, payload, and server-state references
185
+ const pageIds = Object.keys(pageRegistry);
186
+ validateLinkReferences({
187
+ linkActionRefs: buildContext.linkActionRefs,
188
+ pageIds,
189
+ context: buildContext
190
+ });
191
+ validateStateReferences({
192
+ page: processed,
193
+ context: buildContext
194
+ });
195
+ validatePayloadReferences({
196
+ page: processed,
197
+ context: buildContext
198
+ });
199
+ validateServerStateReferences({
200
+ page: processed,
201
+ context: buildContext
202
+ });
203
+ // Extract JS functions from the page
204
+ const pageRequests = [
205
+ ...processed.requests ?? []
206
+ ];
207
+ delete processed.requests;
208
+ const cleanPage = jsMapParser({
209
+ input: processed,
210
+ jsMap: buildContext.jsMap,
211
+ env: 'client'
212
+ });
213
+ const cleanRequests = jsMapParser({
214
+ input: pageRequests,
215
+ jsMap: buildContext.jsMap,
216
+ env: 'server'
217
+ });
218
+ const finalPage = {
219
+ ...cleanPage,
220
+ requests: cleanRequests
221
+ };
222
+ // Check for collected errors from validation steps
223
+ if (buildErrors.length > 0) {
224
+ const error = new ConfigError(`Page "${pageId}" build failed with ${buildErrors.length} error(s).`);
225
+ error.buildErrors = buildErrors;
226
+ throw error;
227
+ }
228
+ // Write page artifacts
229
+ await writePageJit({
230
+ page: finalPage,
231
+ context: buildContext
232
+ });
233
+ return finalPage;
234
+ } catch (err) {
235
+ // Attach any collected errors to the thrown error
236
+ if (buildErrors.length > 0 && !err.buildErrors) {
237
+ err.buildErrors = [
238
+ err,
239
+ ...buildErrors
240
+ ];
241
+ }
242
+ if (err.isLowdefyError) {
243
+ throw err;
244
+ }
245
+ const lowdefyErr = new LowdefyInternalError(err.message, {
246
+ cause: err
247
+ });
248
+ lowdefyErr.buildErrors = err.buildErrors;
249
+ throw lowdefyErr;
250
+ }
251
+ }
252
+ export default buildPageJit;
@@ -0,0 +1,90 @@
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 { serializer } from '@lowdefy/helpers';
16
+ import buildPage from '../buildPages/buildPage.js';
17
+ import jsMapParser from '../buildJs/jsMapParser.js';
18
+ import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
19
+ import createPageRegistry from './createPageRegistry.js';
20
+ import PAGE_CONTENT_KEYS from './pageContentKeys.js';
21
+ function buildShallowPages({ components, context }) {
22
+ // Set pageId on all pages (normally done by buildPage in buildPages).
23
+ // Must run before createPageRegistry which uses page.id as map key.
24
+ for (const page of components.pages ?? []){
25
+ if (page.id && !page.pageId) {
26
+ page.pageId = page.id;
27
+ }
28
+ }
29
+ const pageRegistry = createPageRegistry({
30
+ components,
31
+ context
32
+ });
33
+ const checkDuplicatePageId = createCheckDuplicateId({
34
+ message: 'Duplicate pageId "{{ id }}".'
35
+ });
36
+ for (const page of components.pages ?? []){
37
+ checkDuplicatePageId({
38
+ id: page.id,
39
+ configKey: page['~k']
40
+ });
41
+ }
42
+ // Build sourceless pages (e.g., default 404) — no YAML to JIT-resolve from.
43
+ context.linkActionRefs = [];
44
+ const sourcelessPageArtifacts = [];
45
+ (components.pages ?? []).forEach((page, index)=>{
46
+ const entry = pageRegistry.get(page.id);
47
+ // Skip pages with a source file (JIT-resolved) and resolver pages (JIT re-run)
48
+ if (!entry || entry.refPath !== null || entry.resolverOriginal) return;
49
+ buildPage({
50
+ page,
51
+ index,
52
+ context
53
+ });
54
+ const pageRequests = [
55
+ ...page.requests ?? []
56
+ ];
57
+ delete page.requests;
58
+ const cleanPage = jsMapParser({
59
+ input: page,
60
+ jsMap: context.jsMap,
61
+ env: 'client'
62
+ });
63
+ const cleanRequests = jsMapParser({
64
+ input: pageRequests,
65
+ jsMap: context.jsMap,
66
+ env: 'server'
67
+ });
68
+ const builtPage = {
69
+ ...cleanPage,
70
+ requests: cleanRequests
71
+ };
72
+ sourcelessPageArtifacts.push({
73
+ pageId: builtPage.pageId,
74
+ pageJson: serializer.serializeToString(builtPage),
75
+ requests: (builtPage.requests ?? []).map((req)=>({
76
+ requestId: req.requestId,
77
+ requestJson: serializer.serializeToString(req)
78
+ }))
79
+ });
80
+ // Strip content for subsequent skeleton steps
81
+ for (const key of PAGE_CONTENT_KEYS){
82
+ delete page[key];
83
+ }
84
+ });
85
+ return {
86
+ pageRegistry,
87
+ sourcelessPageArtifacts
88
+ };
89
+ }
90
+ export default buildShallowPages;
@@ -0,0 +1,80 @@
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
+ // Walk up from ~r to find the page's source file for JIT re-resolution.
17
+ // Templates receive vars (they need id, title, etc. from the page file),
18
+ // while page files are self-contained or receive vars from a collection file.
19
+ // Stop at the first ref called WITHOUT vars — that's the page file.
20
+ // When ALL refs have vars (module pages), fall back to the first child of root.
21
+ // When the child of root has no path (resolver ref), go shallower — capture
22
+ // the resolver's info so JIT can re-run it with freshly resolved vars.
23
+ function findPageSourceRef(refId, refMap, unresolvedRefVars) {
24
+ let current = refId;
25
+ let firstChildOfRoot = null;
26
+ while(!type.isNone(current)){
27
+ const entry = refMap[current];
28
+ if (!entry) {
29
+ return null;
30
+ }
31
+ const hasVars = !type.isNone(unresolvedRefVars[current]);
32
+ // Track the first child of root as fallback
33
+ const parentEntry = !type.isNone(entry.parent) ? refMap[entry.parent] : null;
34
+ if (parentEntry && type.isNone(parentEntry.parent) && !firstChildOfRoot) {
35
+ // Resolver ref: capture the resolver's original definition and vars
36
+ // so JIT can re-run the resolver (instead of falling through to the
37
+ // template file below it, which would discard the resolver's vars).
38
+ firstChildOfRoot = entry.path ? {
39
+ path: entry.path,
40
+ unresolvedVars: unresolvedRefVars[current] ?? null
41
+ } : {
42
+ path: null,
43
+ original: refMap[current].original
44
+ };
45
+ }
46
+ // First ref without vars = self-contained page file
47
+ if (!hasVars) {
48
+ if (!type.isNone(entry.parent)) {
49
+ return {
50
+ path: entry.path,
51
+ unresolvedVars: null
52
+ };
53
+ }
54
+ // Reached root — use first child of root
55
+ return firstChildOfRoot;
56
+ }
57
+ current = entry.parent;
58
+ }
59
+ return firstChildOfRoot;
60
+ }
61
+ function createPageRegistry({ components, context }) {
62
+ const registry = new Map();
63
+ const unresolvedRefVars = context.unresolvedRefVars ?? {};
64
+ (components.pages ?? []).forEach((page)=>{
65
+ // Read ~r from keyMap — addKeys moves ~r there and deletes it from objects.
66
+ const keyMapEntry = context.keyMap[page['~k']];
67
+ const refId = keyMapEntry?.['~r'] ?? null;
68
+ const sourceRef = !type.isNone(refId) ? findPageSourceRef(refId, context.refMap, unresolvedRefVars) : null;
69
+ registry.set(page.id, {
70
+ pageId: page.id,
71
+ auth: page.auth,
72
+ refId,
73
+ refPath: sourceRef?.path ?? null,
74
+ unresolvedVars: sourceRef?.unresolvedVars ?? null,
75
+ resolverOriginal: sourceRef?.original ?? null
76
+ });
77
+ });
78
+ return registry;
79
+ }
80
+ export default createPageRegistry;
@@ -0,0 +1,62 @@
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
+ */ function collectMissing({ counter, definitions, installedPluginPackages, missingPackages }) {
16
+ const counts = counter.getCounts();
17
+ for (const typeName of Object.keys(counts)){
18
+ const def = definitions[typeName];
19
+ if (!def) continue;
20
+ if (installedPluginPackages.has(def.package)) continue;
21
+ if (!missingPackages.has(def.package)) {
22
+ missingPackages.set(def.package, {
23
+ version: def.version,
24
+ types: []
25
+ });
26
+ }
27
+ missingPackages.get(def.package).types.push(typeName);
28
+ }
29
+ }
30
+ function detectMissingPluginPackages({ context, installedPluginPackages }) {
31
+ const missingPackages = new Map();
32
+ if (!installedPluginPackages) {
33
+ return missingPackages;
34
+ }
35
+ const { typeCounters, typesMap } = context;
36
+ collectMissing({
37
+ counter: typeCounters.blocks,
38
+ definitions: typesMap.blocks,
39
+ installedPluginPackages,
40
+ missingPackages
41
+ });
42
+ collectMissing({
43
+ counter: typeCounters.actions,
44
+ definitions: typesMap.actions,
45
+ installedPluginPackages,
46
+ missingPackages
47
+ });
48
+ collectMissing({
49
+ counter: typeCounters.operators.client,
50
+ definitions: typesMap.operators.client,
51
+ installedPluginPackages,
52
+ missingPackages
53
+ });
54
+ collectMissing({
55
+ counter: typeCounters.operators.server,
56
+ definitions: typesMap.operators.server,
57
+ installedPluginPackages,
58
+ missingPackages
59
+ });
60
+ return missingPackages;
61
+ }
62
+ export default detectMissingPluginPackages;
@@ -0,0 +1,38 @@
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 getRefPositions(content, basePath) {
17
+ const positions = new Map();
18
+ walk(content, basePath, positions);
19
+ return positions;
20
+ }
21
+ function walk(node, path, positions) {
22
+ if (type.isObject(node)) {
23
+ if (node._ref && node._ref.id !== undefined) {
24
+ positions.set(node._ref.id, path);
25
+ return;
26
+ }
27
+ for (const key of Object.keys(node)){
28
+ const childPath = path ? `${path}.${key}` : key;
29
+ walk(node[key], childPath, positions);
30
+ }
31
+ } else if (type.isArray(node)) {
32
+ for(let i = 0; i < node.length; i++){
33
+ const childPath = path ? `${path}.${i}` : `${i}`;
34
+ walk(node[i], childPath, positions);
35
+ }
36
+ }
37
+ }
38
+ export default getRefPositions;
@@ -0,0 +1,24 @@
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 PAGE_CONTENT_KEYS from './pageContentKeys.js';
16
+ function isPageContentPath(jsonPath) {
17
+ if (!jsonPath.startsWith('pages.')) return false;
18
+ const segments = jsonPath.split('.');
19
+ for(let i = 1; i < segments.length; i++){
20
+ if (PAGE_CONTENT_KEYS.includes(segments[i])) return true;
21
+ }
22
+ return false;
23
+ }
24
+ export default isPageContentPath;
@@ -0,0 +1,26 @@
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
+ */ // Keys stripped from pages after createPageRegistry captures the page file ref.
16
+ // Only `id`, `type` (and skeleton-computed `pageId`, `auth`, `~k`, `~r`) survive.
17
+ // `type` is always a resolved string (never a ref target) and must stay on stubs
18
+ // for schema validation (block schema requires both `id` and `type`).
19
+ const PAGE_CONTENT_KEYS = [
20
+ 'blocks',
21
+ 'areas',
22
+ 'events',
23
+ 'requests',
24
+ 'layout'
25
+ ];
26
+ export default PAGE_CONTENT_KEYS;