@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,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,271 @@
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 operators from '@lowdefy/operators-js/operators/build';
20
+ import addKeys from '../addKeys.js';
21
+ import buildPage from '../buildPages/buildPage.js';
22
+ import validateLinkReferences from '../buildPages/validateLinkReferences.js';
23
+ import validatePayloadReferences from '../buildPages/validatePayloadReferences.js';
24
+ import validateServerStateReferences from '../buildPages/validateServerStateReferences.js';
25
+ import validateStateReferences from '../buildPages/validateStateReferences.js';
26
+ import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
27
+ import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
28
+ import createContext from '../../createContext.js';
29
+ import evaluateStaticOperators from '../buildRefs/evaluateStaticOperators.js';
30
+ import getRefContent from '../buildRefs/getRefContent.js';
31
+ import jsMapParser from '../buildJs/jsMapParser.js';
32
+ import makeRefDefinition from '../buildRefs/makeRefDefinition.js';
33
+ import { resolve, WalkContext, cloneForResolve, tagRefDeep } from '../buildRefs/walker.js';
34
+ import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
35
+ import writeMaps from '../writeMaps.js';
36
+ import detectMissingPluginPackages from './detectMissingPluginPackages.js';
37
+ import updateServerPackageJsonJit from './updateServerPackageJsonJit.js';
38
+ import validatePageTypes from './validatePageTypes.js';
39
+ import writePageJit from './writePageJit.js';
40
+ validateOperatorsDynamic({
41
+ operators
42
+ });
43
+ const dynamicIdentifiers = collectDynamicIdentifiers({
44
+ operators
45
+ });
46
+ async function buildPageJit({ pageId, pageRegistry, context, directories, logger }) {
47
+ // Use provided context or create a minimal one for JIT builds
48
+ const buildContext = context ?? createContext({
49
+ directories,
50
+ logger: logger ?? console,
51
+ stage: 'dev'
52
+ });
53
+ const pageEntry = type.isFunction(pageRegistry.get) ? pageRegistry.get(pageId) : pageRegistry[pageId];
54
+ if (!pageEntry) {
55
+ return null;
56
+ }
57
+ // Reset errors for this build. Keep a local reference so that concurrent
58
+ // JIT builds (different pages sharing buildContext) cannot corrupt our
59
+ // error list by reassigning buildContext.errors during an await.
60
+ const buildErrors = [];
61
+ buildContext.errors = buildErrors;
62
+ try {
63
+ // Pages without a source file (e.g., default 404) can only be served from
64
+ // their pre-built artifact — they have no YAML to re-resolve from.
65
+ // All user pages (with refId) always JIT-resolve from source YAML so that
66
+ // page-only edits are picked up without a skeleton rebuild.
67
+ if (!pageEntry.refId) {
68
+ const pagePath = path.join(buildContext.directories.build, 'pages', pageId, `${pageId}.json`);
69
+ try {
70
+ const content = await fs.promises.readFile(pagePath, 'utf8');
71
+ return serializer.deserialize(JSON.parse(content));
72
+ } catch (err) {
73
+ if (err.code !== 'ENOENT') throw err;
74
+ }
75
+ }
76
+ // Resolve the page file from scratch using the source file path determined
77
+ // by createPageRegistry's parent chain walk.
78
+ if (!pageEntry.refPath && !pageEntry.resolverOriginal) {
79
+ throw new ConfigError(`Page "${pageId}" has no source file reference. Cannot resolve page content.`);
80
+ }
81
+ // Resolve unresolved vars (which may contain inner _ref objects) fresh from disk.
82
+ // For resolver pages, unresolved vars live in resolverOriginal.vars (single source).
83
+ // For file-backed pages, they're stored separately in unresolvedVars.
84
+ const unresolvedVars = pageEntry.unresolvedVars ?? pageEntry.resolverOriginal?.vars;
85
+ let resolvedVars = null;
86
+ if (unresolvedVars) {
87
+ const varRefDef = makeRefDefinition({}, null, buildContext.refMap);
88
+ const varCtx = new WalkContext({
89
+ buildContext,
90
+ refId: varRefDef.id,
91
+ sourceRefId: null,
92
+ vars: {},
93
+ path: '',
94
+ currentFile: pageEntry.refPath ?? pageEntry.resolverOriginal?.resolver ?? '',
95
+ refChain: new Set(),
96
+ operators,
97
+ env: process.env,
98
+ dynamicIdentifiers,
99
+ shouldStop: null
100
+ });
101
+ resolvedVars = await resolve(cloneForResolve(unresolvedVars), varCtx);
102
+ }
103
+ let refDef;
104
+ if (pageEntry.resolverOriginal) {
105
+ const resolverDefinition = resolvedVars ? {
106
+ ...pageEntry.resolverOriginal,
107
+ vars: resolvedVars
108
+ } : pageEntry.resolverOriginal;
109
+ refDef = makeRefDefinition(resolverDefinition, null, buildContext.refMap);
110
+ buildContext.refMap[refDef.id].path = null;
111
+ } else {
112
+ const refDefinition = resolvedVars ? {
113
+ path: pageEntry.refPath,
114
+ vars: resolvedVars
115
+ } : pageEntry.refPath;
116
+ refDef = makeRefDefinition(refDefinition, null, buildContext.refMap);
117
+ buildContext.refMap[refDef.id].path = refDef.path;
118
+ }
119
+ const pageContent = await getRefContent({
120
+ context: buildContext,
121
+ refDef,
122
+ referencedFrom: null
123
+ });
124
+ const pageCtx = new WalkContext({
125
+ buildContext,
126
+ refId: refDef.id,
127
+ sourceRefId: null,
128
+ vars: refDef.vars ?? {},
129
+ path: '',
130
+ currentFile: refDef.path ?? '',
131
+ refChain: new Set(),
132
+ operators,
133
+ env: process.env,
134
+ dynamicIdentifiers,
135
+ shouldStop: null
136
+ });
137
+ let processed = await resolve(pageContent, pageCtx);
138
+ processed = evaluateStaticOperators({
139
+ context: buildContext,
140
+ input: processed,
141
+ refDef
142
+ });
143
+ // When resolving from a collection file (with vars), the result is an array of pages.
144
+ // Find the specific page by ID.
145
+ if (type.isArray(processed)) {
146
+ processed = processed.find((p)=>type.isObject(p) && p.id === pageId);
147
+ if (!processed) {
148
+ throw new ConfigError(`Page "${pageId}" not found in resolved page source file.`);
149
+ }
150
+ }
151
+ // Tag all objects with ~r for ref provenance (normally done inside _ref
152
+ // resolution by the walker; JIT resolves the page file directly).
153
+ tagRefDeep(processed, refDef.id);
154
+ // Apply skeleton-computed auth (buildAuth ran during skeleton build)
155
+ processed.auth = pageEntry.auth;
156
+ // Add keys to the resolved page
157
+ addKeys({
158
+ components: processed,
159
+ context: buildContext
160
+ });
161
+ // Write keyMap/refMap so the error handler reads JIT entries from disk.
162
+ // JIT addKeys assigns fresh ~k values that aren't in the skeleton keyMap.
163
+ await writeMaps({
164
+ context: buildContext
165
+ });
166
+ // Initialize linkActionRefs for buildPage (normally done by buildPages)
167
+ if (!buildContext.linkActionRefs) {
168
+ buildContext.linkActionRefs = [];
169
+ }
170
+ // Build the page (validation, block processing)
171
+ const checkDuplicatePageId = createCheckDuplicateId({
172
+ message: 'Duplicate pageId "{{ id }}".'
173
+ });
174
+ buildPage({
175
+ page: processed,
176
+ index: 0,
177
+ context: buildContext,
178
+ checkDuplicatePageId
179
+ });
180
+ // Validate that all page-level types (blocks, actions, operators) exist
181
+ validatePageTypes({
182
+ context: buildContext
183
+ });
184
+ // Detect plugin packages that are in typesMap but not installed in server
185
+ const missingPackages = detectMissingPluginPackages({
186
+ context: buildContext,
187
+ installedPluginPackages: buildContext.installedPluginPackages
188
+ });
189
+ if (missingPackages.size > 0) {
190
+ if (buildContext.directories.server) {
191
+ await updateServerPackageJsonJit({
192
+ directories: buildContext.directories,
193
+ missingPackages
194
+ });
195
+ }
196
+ return {
197
+ installing: true,
198
+ packages: [
199
+ ...missingPackages.keys()
200
+ ]
201
+ };
202
+ }
203
+ // Validate link, state, payload, and server-state references
204
+ const pageIds = Object.keys(pageRegistry);
205
+ validateLinkReferences({
206
+ linkActionRefs: buildContext.linkActionRefs,
207
+ pageIds,
208
+ context: buildContext
209
+ });
210
+ validateStateReferences({
211
+ page: processed,
212
+ context: buildContext
213
+ });
214
+ validatePayloadReferences({
215
+ page: processed,
216
+ context: buildContext
217
+ });
218
+ validateServerStateReferences({
219
+ page: processed,
220
+ context: buildContext
221
+ });
222
+ // Extract JS functions from the page
223
+ const pageRequests = [
224
+ ...processed.requests ?? []
225
+ ];
226
+ delete processed.requests;
227
+ const cleanPage = jsMapParser({
228
+ input: processed,
229
+ jsMap: buildContext.jsMap,
230
+ env: 'client'
231
+ });
232
+ const cleanRequests = jsMapParser({
233
+ input: pageRequests,
234
+ jsMap: buildContext.jsMap,
235
+ env: 'server'
236
+ });
237
+ const finalPage = {
238
+ ...cleanPage,
239
+ requests: cleanRequests
240
+ };
241
+ // Check for collected errors from validation steps
242
+ if (buildErrors.length > 0) {
243
+ const error = new ConfigError(`Page "${pageId}" build failed with ${buildErrors.length} error(s).`);
244
+ error.buildErrors = buildErrors;
245
+ throw error;
246
+ }
247
+ // Write page artifacts
248
+ await writePageJit({
249
+ page: finalPage,
250
+ context: buildContext
251
+ });
252
+ return finalPage;
253
+ } catch (err) {
254
+ // Attach any collected errors to the thrown error
255
+ if (buildErrors.length > 0 && !err.buildErrors) {
256
+ err.buildErrors = [
257
+ err,
258
+ ...buildErrors
259
+ ];
260
+ }
261
+ if (err.isLowdefyError) {
262
+ throw err;
263
+ }
264
+ const lowdefyErr = new LowdefyInternalError(err.message, {
265
+ cause: err
266
+ });
267
+ lowdefyErr.buildErrors = err.buildErrors;
268
+ throw lowdefyErr;
269
+ }
270
+ }
271
+ 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,85 @@
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
+ // Inline pages (defined directly in lowdefy.yaml) have a refId pointing to
70
+ // the root ref but findPageSourceRef returns null because there is no
71
+ // separate source file. Set refId to null so buildPageJit serves them from
72
+ // the pre-built artifact written by buildShallowPages.
73
+ const isInline = !type.isNone(refId) && sourceRef === null && !type.isNone(context.refMap[refId]) && type.isNone(context.refMap[refId].parent);
74
+ registry.set(page.id, {
75
+ pageId: page.id,
76
+ auth: page.auth,
77
+ refId: isInline ? null : refId,
78
+ refPath: sourceRef?.path ?? null,
79
+ unresolvedVars: sourceRef?.unresolvedVars ?? null,
80
+ resolverOriginal: sourceRef?.original ?? null
81
+ });
82
+ });
83
+ return registry;
84
+ }
85
+ 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,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;