@lowdefy/build 5.6.0 → 6.0.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 (100) hide show
  1. package/dist/build/buildAgents.js +61 -16
  2. package/dist/build/buildApi/buildApi.js +4 -1
  3. package/dist/build/buildApi/buildEndpoint.js +8 -2
  4. package/dist/build/buildApi/buildRoutine/countStepTypes.js +1 -1
  5. package/dist/build/buildApi/buildRoutine/setStepId.js +2 -0
  6. package/dist/build/buildApi/buildRoutine/validateStep.js +61 -0
  7. package/dist/build/buildApi/resolveEndpointSchedules.js +37 -0
  8. package/dist/build/buildApi/validateEndpoint.js +96 -1
  9. package/dist/build/buildApp.js +13 -20
  10. package/dist/build/buildAppMeta.js +130 -0
  11. package/dist/build/buildAuth/buildAgentAuth.js +14 -1
  12. package/dist/build/buildAuth/buildAuth.js +8 -3
  13. package/dist/build/buildAuth/buildAuthPlugins.js +6 -0
  14. package/dist/build/buildAuth/buildAuthStrategies.js +117 -0
  15. package/dist/build/buildAuth/buildWebsocketAuth.js +55 -0
  16. package/dist/build/buildAuth/getProtectedWebsockets.js +29 -0
  17. package/dist/build/buildAuth/getWebsocketRoles.js +39 -0
  18. package/dist/build/buildAuth/validateAuthConfig.js +15 -3
  19. package/dist/build/buildImports/buildImportsDev.js +15 -0
  20. package/dist/build/buildImports/buildImportsProd.js +4 -1
  21. package/dist/build/buildJs/generateClientJsModule.js +27 -0
  22. package/dist/build/buildJs/writeJs.js +3 -5
  23. package/dist/build/buildMcp.js +73 -0
  24. package/dist/build/buildModuleDefs.js +90 -48
  25. package/dist/build/buildModules.js +61 -14
  26. package/dist/build/buildNotifications.js +98 -0
  27. package/dist/build/buildPages/buildBlock/buildBlock.js +3 -1
  28. package/dist/build/buildPages/buildBlock/buildDynamicBlock.js +131 -0
  29. package/dist/build/buildPages/buildBlock/buildEvents.js +186 -21
  30. package/dist/build/buildPages/buildBlock/buildRequests.js +9 -0
  31. package/dist/build/buildPages/buildBlock/normalizeClassAndStyles.js +2 -11
  32. package/dist/build/buildPages/buildBlock/setBlockId.js +5 -2
  33. package/dist/build/buildPages/buildBlock/validateBlock.js +7 -0
  34. package/dist/build/buildPages/buildPage.js +27 -3
  35. package/dist/build/buildPages/buildSubscriptions.js +145 -0
  36. package/dist/build/buildPages/validateDynamicBlockRefs.js +30 -0
  37. package/dist/build/buildPages/validateStateReferences.js +4 -2
  38. package/dist/build/buildPages/validateWebsocketRefs.js +30 -0
  39. package/dist/build/buildRefs/buildRefs.js +21 -12
  40. package/dist/build/buildRefs/cloneWithMarkers.js +57 -0
  41. package/dist/build/buildRefs/deferredRegions.js +80 -0
  42. package/dist/build/buildRefs/deferredRegistry.js +292 -0
  43. package/dist/build/buildRefs/getModuleRefContent.js +21 -6
  44. package/dist/build/buildRefs/getUserJavascriptFunction.js +1 -1
  45. package/dist/build/buildRefs/parseRefContent.js +24 -0
  46. package/dist/build/buildRefs/{evaluateStaticOperators.js → precomputeRuntimeOperators.js} +7 -6
  47. package/dist/build/buildRefs/walker.js +471 -207
  48. package/dist/build/buildTypes.js +22 -1
  49. package/dist/build/buildWebsockets.js +99 -0
  50. package/dist/build/collectPageContent.js +7 -1
  51. package/dist/build/copyAgentFileSystems.js +2 -2
  52. package/dist/build/full/buildPages.js +16 -0
  53. package/dist/build/full/updateServerPackageJson.js +21 -0
  54. package/dist/build/jit/buildPageJit.js +46 -6
  55. package/dist/build/jit/collectSkeletonSourceFiles.js +14 -0
  56. package/dist/build/jit/pageContentKeys.js +1 -0
  57. package/dist/build/jit/shallowBuild.js +81 -1
  58. package/dist/build/jit/writePageJit.js +11 -8
  59. package/dist/build/registerModules.js +125 -24
  60. package/dist/build/validateCallAgentSteps.js +58 -0
  61. package/dist/build/validateConfig.js +4 -0
  62. package/dist/build/validateCronConfig.js +110 -0
  63. package/dist/build/validateRenderNotificationSteps.js +50 -0
  64. package/dist/build/writeApi.js +58 -1
  65. package/dist/build/writeMcp.js +21 -0
  66. package/dist/build/writeNotifications.js +30 -0
  67. package/dist/build/writePluginImports/importPluginModule.js +39 -0
  68. package/dist/build/writePluginImports/writeActionSchemaMap.js +6 -7
  69. package/dist/build/writePluginImports/writeAuthImports.js +4 -0
  70. package/dist/build/writePluginImports/writeAvailableTypes.js +99 -0
  71. package/dist/build/writePluginImports/writeBlockSchemaMap.js +10 -9
  72. package/dist/build/writePluginImports/writeConnectionSchemaMap.js +101 -0
  73. package/dist/build/writePluginImports/writeGlobalsCss.js +20 -10
  74. package/dist/build/writePluginImports/writeNotificationImports.js +31 -0
  75. package/dist/build/writePluginImports/writeOperatorSchemaMap.js +6 -7
  76. package/dist/build/writePluginImports/writePluginImports.js +18 -4
  77. package/dist/build/writePluginImports/writeWebsocketImports.js +22 -0
  78. package/dist/build/writeWebsockets.js +30 -0
  79. package/dist/createContext.js +6 -1
  80. package/dist/defaultPackages.js +7 -1
  81. package/dist/defaultTypesMap.js +666 -388
  82. package/dist/dynamic/buildDynamicBlocks.js +120 -0
  83. package/dist/index.js +78 -8
  84. package/dist/indexDev.js +4 -0
  85. package/dist/lowdefySchema.js +861 -6
  86. package/dist/scripts/generateDefaultTypes.js +5 -2
  87. package/dist/test-utils/expectTerminates.js +33 -0
  88. package/dist/test-utils/runBuild.js +12 -1
  89. package/dist/test-utils/runBuildForSnapshots.js +34 -4
  90. package/dist/test-utils/testContext.js +8 -2
  91. package/dist/utils/countOperators.js +5 -17
  92. package/dist/utils/createPluginTypesMap.js +21 -0
  93. package/dist/utils/getCronEnvironmentNames.js +25 -0
  94. package/dist/utils/logCollectedErrors.js +15 -9
  95. package/dist/utils/serializeBuildException.js +29 -0
  96. package/dist/utils/validateCronExpression.js +131 -0
  97. package/dist/utils/writeBuildArtifact.js +5 -2
  98. package/package.json +66 -59
  99. package/dist/build/writePluginImports/writeServerExternalPackages.js +0 -121
  100. /package/dist/build/{resolveModuleOperators.js → buildRefs/scopeMenuItemIds.js} +0 -0
@@ -15,6 +15,7 @@
15
15
  */ import { type } from '@lowdefy/helpers';
16
16
  import { ConfigError, ConfigWarning } from '@lowdefy/errors';
17
17
  import buildBlock from './buildBlock/buildBlock.js';
18
+ import buildSubscriptions from './buildSubscriptions.js';
18
19
  import collectExceptions from '../../utils/collectExceptions.js';
19
20
  import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
20
21
  import validateId from '../../utils/validateId.js';
@@ -54,10 +55,16 @@ function buildPage({ page, index, context, checkDuplicatePageId }) {
54
55
  const requests = [];
55
56
  const requestActionRefs = [];
56
57
  const shortcutRefs = [];
57
- buildBlock(page, {
58
+ // Extract subscriptions before block building — validateBlock rejects the
59
+ // subscriptions key on nested blocks, so the page root must not carry it.
60
+ const subscriptions = page.subscriptions;
61
+ delete page.subscriptions;
62
+ const pageContext = {
58
63
  auth: page.auth,
59
64
  blockIdCounter: createCounter(),
60
65
  callApiActionRefs: context.callApiActionRefs ?? [],
66
+ websocketActionRefs: context.websocketActionRefs ?? [],
67
+ dynamicBlockRefs: context.dynamicBlockRefs ?? [],
61
68
  checkDuplicateRequestId: createCheckDuplicateId({
62
69
  message: 'Duplicate requestId "{{ id }}" on page "{{ pageId }}".'
63
70
  }),
@@ -68,9 +75,26 @@ function buildPage({ page, index, context, checkDuplicatePageId }) {
68
75
  shortcutRefs,
69
76
  linkActionRefs: context.linkActionRefs,
70
77
  typeCounters: context.typeCounters
71
- });
78
+ };
79
+ buildBlock(page, pageContext);
72
80
  // set page.id since buildBlock sets id as well.
73
81
  page.id = `page:${page.pageId}`;
82
+ // Flag pages with Dynamic blocks so the server can skip resolution
83
+ // (and the deep copy it requires) for static pages with one property read.
84
+ if (pageContext.hasDynamicBlocks === true) {
85
+ page.dynamic = true;
86
+ }
87
+ page.subscriptions = subscriptions;
88
+ buildSubscriptions(page, {
89
+ callApiActionRefs: context.callApiActionRefs ?? [],
90
+ context,
91
+ linkActionRefs: context.linkActionRefs,
92
+ pageId: page.pageId,
93
+ requestActionRefs,
94
+ shortcutRefs,
95
+ typeCounters: context.typeCounters,
96
+ websocketActionRefs: context.websocketActionRefs ?? []
97
+ });
74
98
  // Validate that all Request actions reference defined requests
75
99
  validateRequestReferences({
76
100
  requestActionRefs,
@@ -79,7 +103,7 @@ function buildPage({ page, index, context, checkDuplicatePageId }) {
79
103
  context
80
104
  });
81
105
  // Warn on duplicate shortcuts within the page
82
- const seenShortcuts = {};
106
+ const seenShortcuts = Object.create(null);
83
107
  shortcutRefs.forEach(({ shortcut, blockId, eventId, configKey })=>{
84
108
  if (seenShortcuts[shortcut]) {
85
109
  context.handleWarning(new ConfigWarning(`Duplicate shortcut "${shortcut}" on event "${eventId}" on block "${blockId}" on page "${page.pageId}" — already defined on block "${seenShortcuts[shortcut].blockId}".`, {
@@ -0,0 +1,145 @@
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, ConfigWarning } from '@lowdefy/errors';
17
+ import buildEvents from './buildBlock/buildEvents.js';
18
+ import countOperators from '../../utils/countOperators.js';
19
+ import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
20
+ const SUBSCRIPTION_EVENTS = [
21
+ 'onMessage',
22
+ 'onSubscribe',
23
+ 'onError'
24
+ ];
25
+ const DEFAULT_MAX_MESSAGES = 100;
26
+ const DEFAULT_THROTTLE_RENDER = 250;
27
+ const MIN_THROTTLE_RENDER = 100;
28
+ function buildSubscription({ subscription, pageContext }) {
29
+ const { context, pageId } = pageContext;
30
+ const configKey = subscription['~k'];
31
+ if (type.isUndefined(subscription.websocketId)) {
32
+ throw new ConfigError(`Subscription websocketId missing at page "${pageId}".`, {
33
+ configKey
34
+ });
35
+ }
36
+ if (!type.isString(subscription.websocketId)) {
37
+ throw new ConfigError(`Subscription websocketId is not a string at page "${pageId}".`, {
38
+ received: subscription.websocketId,
39
+ configKey
40
+ });
41
+ }
42
+ pageContext.checkDuplicateSubscriptionId({
43
+ id: subscription.websocketId,
44
+ configKey,
45
+ pageId
46
+ });
47
+ if (!context.websocketIds.has(subscription.websocketId)) {
48
+ throw new ConfigError(`Subscription "${subscription.websocketId}" at page "${pageId}" references a websocket which does not exist.`, {
49
+ configKey
50
+ });
51
+ }
52
+ if (type.isNone(subscription.payload)) {
53
+ subscription.payload = {};
54
+ }
55
+ if (!type.isObject(subscription.payload)) {
56
+ throw new ConfigError(`Subscription "${subscription.websocketId}" at page "${pageId}" payload should be an object.`, {
57
+ received: subscription.payload,
58
+ configKey
59
+ });
60
+ }
61
+ if (type.isNone(subscription.client)) {
62
+ subscription.client = {};
63
+ }
64
+ if (!type.isObject(subscription.client)) {
65
+ throw new ConfigError(`Subscription "${subscription.websocketId}" at page "${pageId}" client should be an object.`, {
66
+ received: subscription.client,
67
+ configKey
68
+ });
69
+ }
70
+ if (type.isNone(subscription.client.maxMessages)) {
71
+ subscription.client.maxMessages = DEFAULT_MAX_MESSAGES;
72
+ }
73
+ if (!type.isInt(subscription.client.maxMessages) || subscription.client.maxMessages < 1) {
74
+ throw new ConfigError(`Subscription "${subscription.websocketId}" at page "${pageId}" client.maxMessages should be a positive integer.`, {
75
+ received: subscription.client.maxMessages,
76
+ configKey
77
+ });
78
+ }
79
+ if (type.isNone(subscription.client.throttleRender)) {
80
+ subscription.client.throttleRender = DEFAULT_THROTTLE_RENDER;
81
+ }
82
+ if (!type.isNumber(subscription.client.throttleRender)) {
83
+ throw new ConfigError(`Subscription "${subscription.websocketId}" at page "${pageId}" client.throttleRender should be a number.`, {
84
+ received: subscription.client.throttleRender,
85
+ configKey
86
+ });
87
+ }
88
+ if (subscription.client.throttleRender < MIN_THROTTLE_RENDER) {
89
+ context.handleWarning(new ConfigWarning(`Subscription "${subscription.websocketId}" at page "${pageId}" client.throttleRender is below the minimum of ${MIN_THROTTLE_RENDER}ms and will be clamped.`, {
90
+ configKey
91
+ }));
92
+ subscription.client.throttleRender = MIN_THROTTLE_RENDER;
93
+ }
94
+ if (type.isNone(subscription.events)) {
95
+ subscription.events = {};
96
+ }
97
+ Object.keys(subscription.events).forEach((eventName)=>{
98
+ if (eventName.startsWith('~')) return;
99
+ if (!SUBSCRIPTION_EVENTS.includes(eventName)) {
100
+ context.handleWarning(new ConfigWarning(`Subscription "${subscription.websocketId}" at page "${pageId}" has event "${eventName}" which will never fire. Supported events: ${SUBSCRIPTION_EVENTS.join(', ')}.`, {
101
+ configKey: subscription.events[eventName]?.['~k'] ?? configKey
102
+ }));
103
+ }
104
+ });
105
+ // Reuse the block event builder — normalizes actions to { try, catch },
106
+ // validates action ids, counts action types, and collects Request action
107
+ // refs so subscription events can trigger page requests.
108
+ buildEvents({
109
+ blockId: `subscription:${subscription.websocketId}`,
110
+ events: subscription.events,
111
+ '~k': configKey
112
+ }, pageContext);
113
+ // Subscriptions are extracted from the page before buildBlock runs, so
114
+ // countBlockOperators never sees them — count client operators here.
115
+ // Payload and event action params are both evaluated client-side.
116
+ countOperators({
117
+ events: subscription.events,
118
+ payload: subscription.payload
119
+ }, {
120
+ counter: pageContext.typeCounters.operators.client
121
+ });
122
+ subscription.pageId = pageId;
123
+ subscription.id = `subscription:${pageId}:${subscription.websocketId}`;
124
+ }
125
+ function buildSubscriptions(page, pageContext) {
126
+ if (type.isNone(page.subscriptions)) {
127
+ page.subscriptions = [];
128
+ }
129
+ if (!type.isArray(page.subscriptions)) {
130
+ throw new ConfigError(`Subscriptions is not an array at page "${pageContext.pageId}".`, {
131
+ received: page.subscriptions,
132
+ configKey: page['~k']
133
+ });
134
+ }
135
+ pageContext.checkDuplicateSubscriptionId = createCheckDuplicateId({
136
+ message: 'Duplicate subscription "{{ id }}" on page "{{ pageId }}".'
137
+ });
138
+ page.subscriptions.forEach((subscription)=>{
139
+ buildSubscription({
140
+ subscription,
141
+ pageContext
142
+ });
143
+ });
144
+ }
145
+ export default buildSubscriptions;
@@ -0,0 +1,30 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { ConfigWarning } from '@lowdefy/errors';
16
+ function validateDynamicBlockRefs({ dynamicBlockRefs, endpointConfigs, context }) {
17
+ // Both Api and InternalApi endpoints are valid — resolution is an in-process
18
+ // call, so InternalApi is the recommended resolver type.
19
+ const existingEndpointIds = new Set(endpointConfigs.map((config)=>config.endpointId));
20
+ dynamicBlockRefs.forEach(({ endpointId, block, sourcePageId })=>{
21
+ if (!existingEndpointIds.has(endpointId)) {
22
+ context.handleWarning(new ConfigWarning(`Dynamic block "${block.blockId}" on page "${sourcePageId}" references non-existent endpoint "${endpointId}".`, {
23
+ configKey: block['~k'],
24
+ prodError: true,
25
+ checkSlug: 'dynamic-endpoint-refs'
26
+ }));
27
+ }
28
+ });
29
+ }
30
+ export default validateDynamicBlockRefs;
@@ -68,10 +68,12 @@ function validateStateReferences({ page, context }) {
68
68
  stateRefs.forEach((configKey, topLevelKey)=>{
69
69
  // Skip if state key is from an input block or SetState action
70
70
  if (blockIds.has(topLevelKey) || setStateKeys.has(topLevelKey)) return;
71
- const message = `_state references "${topLevelKey}" on page "${page.pageId}", ` + `but no input block with id "${topLevelKey}" exists on this page. ` + `State keys are created from input block ids. ` + `Check for typos, add an input block with this id, or initialize the state with SetState.`;
71
+ const message = `_state references "${topLevelKey}" on page "${page.pageId}", ` + `but no input block with id "${topLevelKey}" exists on this page. ` + `State keys are created from input block ids. ` + `Check for typos, add an input block with this id, or initialize the state with SetState. ` + `If the state is set at runtime (for example by a custom action calling setState), ` + `suppress this check with "~ignoreBuildChecks: [state-refs]" on the reference.`;
72
+ // This check is a heuristic: state written at runtime — custom actions
73
+ // calling setState, dynamic SetState wrappers — is statically invisible,
74
+ // so a miss here must never fail a production build (no prodError).
72
75
  context.handleWarning(new ConfigWarning(message, {
73
76
  configKey,
74
- prodError: true,
75
77
  checkSlug: 'state-refs'
76
78
  }));
77
79
  });
@@ -0,0 +1,30 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { ConfigWarning } from '@lowdefy/errors';
16
+ function validateWebsocketRefs({ websocketActionRefs, websocketIds, context }) {
17
+ websocketActionRefs.forEach(({ websocketId, action, actionType, sourcePageId })=>{
18
+ if (action.skip === true) {
19
+ return;
20
+ }
21
+ if (!websocketIds.has(websocketId)) {
22
+ context.handleWarning(new ConfigWarning(`${actionType} action on page "${sourcePageId}" references non-existent websocket "${websocketId}".`, {
23
+ configKey: action['~k'],
24
+ prodError: true,
25
+ checkSlug: 'websocket-refs'
26
+ }));
27
+ }
28
+ });
29
+ }
30
+ export default validateWebsocketRefs;
@@ -16,7 +16,6 @@
16
16
  import { resolve, WalkContext } from './walker.js';
17
17
  import getRefContent from './getRefContent.js';
18
18
  import makeRefDefinition from './makeRefDefinition.js';
19
- import evaluateStaticOperators from './evaluateStaticOperators.js';
20
19
  import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
21
20
  import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
22
21
  import isPageContentPath from '../jit/isPageContentPath.js';
@@ -30,6 +29,9 @@ const dynamicIdentifiers = collectDynamicIdentifiers({
30
29
  async function buildRefs({ context, shallowOptions }) {
31
30
  context.unresolvedRefVars = context.unresolvedRefVars ?? {};
32
31
  const refDef = makeRefDefinition('lowdefy.yaml', null, context.refMap);
32
+ // Stash for Phase 3.5 (precomputeRuntimeOperators) so it can resolve error
33
+ // source file paths without creating a new makeId entry.
34
+ context.rootRefDef = refDef;
33
35
  const ctx = new WalkContext({
34
36
  buildContext: context,
35
37
  refId: refDef.id,
@@ -42,24 +44,31 @@ async function buildRefs({ context, shallowOptions }) {
42
44
  ] : []),
43
45
  operators,
44
46
  env: process.env,
47
+ lowdefyApp: context.appMeta,
45
48
  dynamicIdentifiers,
46
- shouldStop: shallowOptions ? // JIT can re-resolve them from source files. Inline pages (defined
47
- // directly in lowdefy.yaml) live in the root ref and have no separate
48
- // source file — their content must be preserved for buildShallowPages.
49
- (path, refId)=>isPageContentPath(path) && refId !== refDef.id : null
49
+ shouldStop: (path, refId)=>{
50
+ // Module entry vars/connections were already resolved by parseLowdefyYaml
51
+ // (same predicate) and live on context.modules — preserve the raw blobs so
52
+ // the app pass doesn't pull and walk their refs a second time. buildModules
53
+ // reads entry ids only and deletes components.modules before testSchema.
54
+ if (/^modules\.\d+\.vars$/.test(path)) return 'preserve';
55
+ if (/^modules\.\d+\.connections$/.test(path)) return 'preserve';
56
+ if (shallowOptions) {
57
+ // Strip page content (blocks, events, etc.) from ref-backed pages so
58
+ // JIT can re-resolve them from source files. Inline pages (defined
59
+ // directly in lowdefy.yaml) live in the root ref and have no separate
60
+ // source file — their content must be preserved for buildShallowPages.
61
+ return isPageContentPath(path) && refId !== refDef.id;
62
+ }
63
+ return null;
64
+ }
50
65
  });
51
66
  const content = await getRefContent({
52
67
  context,
53
68
  refDef,
54
69
  referencedFrom: null
55
70
  });
56
- let components = await resolve(content, ctx);
57
- // Evaluate static operators (_sum, _if, etc.) that don't depend on runtime data
58
- components = evaluateStaticOperators({
59
- context,
60
- input: components,
61
- refDef
62
- });
71
+ const components = await resolve(content, ctx);
63
72
  return components ?? {};
64
73
  }
65
74
  export default buildRefs;
@@ -0,0 +1,57 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ import setNonEnumerableProperty from '../../utils/setNonEnumerableProperty.js';
17
+ // THE deep clone for config subtrees inside buildRefs — preserves the
18
+ // non-enumerable provenance markers (~r ~l ~k ~arr), and with assignRefId set
19
+ // stamps ~r on nodes that have none (pass nothing to preserve the template's
20
+ // existing markers only). Non-plain values (Date) pass by reference; nothing
21
+ // in the build mutates them.
22
+ //
23
+ // Do not use serializer.copy on config subtrees in buildRefs: it round-trips
24
+ // the same markers but pays a JSON round-trip and re-instantiates Dates — the
25
+ // serializer is the artifact boundary, this is the in-memory clone.
26
+ function cloneWithMarkers(value, { assignRefId = null } = {}) {
27
+ if (!type.isObject(value) && !type.isArray(value)) return value;
28
+ let clone;
29
+ if (type.isArray(value)) {
30
+ clone = value.map((item)=>cloneWithMarkers(item, {
31
+ assignRefId
32
+ }));
33
+ } else {
34
+ clone = {};
35
+ for (const key of Object.keys(value)){
36
+ clone[key] = cloneWithMarkers(value[key], {
37
+ assignRefId
38
+ });
39
+ }
40
+ }
41
+ if (value['~r'] !== undefined) {
42
+ setNonEnumerableProperty(clone, '~r', value['~r']);
43
+ } else if (assignRefId) {
44
+ setNonEnumerableProperty(clone, '~r', assignRefId);
45
+ }
46
+ for (const marker of [
47
+ '~l',
48
+ '~k',
49
+ '~arr'
50
+ ]){
51
+ if (value[marker] !== undefined) {
52
+ setNonEnumerableProperty(clone, marker, value[marker]);
53
+ }
54
+ }
55
+ return clone;
56
+ }
57
+ export default cloneWithMarkers;
@@ -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
+ */ // The single source of truth for which module-manifest regions each build
16
+ // phase defers, records, or skips — one table instead of hand-maintained
17
+ // shouldStop regex sets that must agree.
18
+ //
19
+ // Phases:
20
+ // - 'header' Phase A: parse static header keys (dependencies, plugins,
21
+ // secrets, vars definitions — defaults record-ified); content
22
+ // sections stay raw parsed YAML ('skip' = leave raw, do not
23
+ // descend).
24
+ // - 'exportables' Phase C.5: record-ify per-consumer bodies (components,
25
+ // menu links) in module-static scope; everything else stays
26
+ // raw for Phase D.
27
+ // - 'manifest' Phase D: one full walk; only varDefault placeholders need a
28
+ // rule (single-value kinds would otherwise be forced by the
29
+ // placeholder dispatch — they are demand-only). Per-consumer
30
+ // placeholders pass through the dispatch untouched.
31
+ //
32
+ // entryRef/connRemap creation is value-shape-driven (deferModuleRefs in the
33
+ // walker), not path-driven, so it has no row here. The JIT page-content
34
+ // predicate lives in buildRefs.js (it composes with the app-pass modules
35
+ // preserve and is not a manifest phase).
36
+ const VAR_DEFAULT = /^vars(\.[^.]+\.properties)*\.[^.]+\.default(\..*)?$/;
37
+ const CONTENT_SECTIONS = /^(pages|api|connections|agents|notifications|menus|components)(\..*)?$/;
38
+ const NON_EXPORTABLE_SECTIONS = /^(pages|api|connections|agents|notifications|vars|dependencies|plugins|secrets)(\..*)?$/;
39
+ const DEFERRED_REGIONS = [
40
+ {
41
+ phase: 'header',
42
+ match: VAR_DEFAULT,
43
+ action: 'record:varDefault'
44
+ },
45
+ {
46
+ phase: 'header',
47
+ match: CONTENT_SECTIONS,
48
+ action: 'skip'
49
+ },
50
+ {
51
+ phase: 'exportables',
52
+ match: /^components\.\d+\.component$/,
53
+ action: 'record:component'
54
+ },
55
+ {
56
+ phase: 'exportables',
57
+ match: /^menus\.\d+\.links$/,
58
+ action: 'record:menuLinks'
59
+ },
60
+ {
61
+ phase: 'exportables',
62
+ match: NON_EXPORTABLE_SECTIONS,
63
+ action: 'skip'
64
+ },
65
+ {
66
+ phase: 'manifest',
67
+ match: VAR_DEFAULT,
68
+ action: 'record:varDefault'
69
+ }
70
+ ];
71
+ function makeShouldStop(phase) {
72
+ const rows = DEFERRED_REGIONS.filter((row)=>row.phase === phase);
73
+ return (childPath)=>{
74
+ for (const row of rows){
75
+ if (row.match.test(childPath)) return row.action;
76
+ }
77
+ return false;
78
+ };
79
+ }
80
+ export { DEFERRED_REGIONS, makeShouldStop };