@lowdefy/build 0.0.0-experimental-20251203205559 → 0.0.0-experimental-20260113081624

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 (54) hide show
  1. package/dist/build/addKeys.js +2 -0
  2. package/dist/build/buildApi/buildEndpoint.js +6 -0
  3. package/dist/build/buildApi/buildRoutine/countStepTypes.js +1 -1
  4. package/dist/build/buildApi/validateStepReferences.js +65 -0
  5. package/dist/build/buildAuth/buildAuthPlugins.js +42 -12
  6. package/dist/build/buildAuth/validateAuthConfig.js +10 -2
  7. package/dist/build/buildAuth/validateMutualExclusivity.js +18 -4
  8. package/dist/build/buildConnections.js +25 -6
  9. package/dist/build/buildImports/buildIconImports.js +19 -36
  10. package/dist/build/buildLogger.js +41 -0
  11. package/dist/build/buildMenu.js +40 -13
  12. package/dist/build/buildPages/buildBlock/buildEvents.js +90 -9
  13. package/dist/build/buildPages/buildBlock/buildRequests.js +47 -7
  14. package/dist/build/buildPages/buildBlock/countBlockTypes.js +1 -1
  15. package/dist/build/buildPages/buildBlock/validateBlock.js +33 -7
  16. package/dist/build/buildPages/buildPage.js +28 -4
  17. package/dist/build/buildPages/buildPages.js +26 -1
  18. package/dist/build/buildPages/buildTestPage.js +9 -1
  19. package/dist/build/buildPages/validateLinkReferences.js +33 -0
  20. package/dist/build/buildPages/validatePayloadReferences.js +52 -0
  21. package/dist/build/buildPages/validateRequestReferences.js +33 -0
  22. package/dist/build/buildPages/validateStateReferences.js +52 -0
  23. package/dist/build/buildRefs/buildRefs.js +11 -33
  24. package/dist/build/buildRefs/evaluateBuildOperators.js +8 -2
  25. package/dist/build/buildRefs/getConfigFile.js +2 -1
  26. package/dist/build/buildRefs/getRefContent.js +17 -31
  27. package/dist/build/buildRefs/getRefsFromFile.js +8 -3
  28. package/dist/build/buildRefs/makeRefDefinition.js +9 -8
  29. package/dist/build/buildRefs/parseRefContent.js +61 -2
  30. package/dist/build/buildRefs/populateRefs.js +14 -11
  31. package/dist/build/buildRefs/recursiveBuild.js +62 -71
  32. package/dist/build/buildTypes.js +19 -2
  33. package/dist/build/formatBuildError.js +34 -0
  34. package/dist/build/writeLogger.js +19 -0
  35. package/dist/createContext.js +3 -19
  36. package/dist/defaultTypesMap.js +541 -534
  37. package/dist/index.js +138 -125
  38. package/dist/lowdefySchema.js +75 -0
  39. package/dist/test/testContext.js +2 -1
  40. package/dist/utils/collectConfigError.js +40 -0
  41. package/dist/utils/countOperators.js +24 -11
  42. package/dist/utils/createCheckDuplicateId.js +30 -9
  43. package/dist/utils/createCounter.js +15 -2
  44. package/dist/utils/extractOperatorKey.js +36 -0
  45. package/dist/utils/findSimilarString.js +53 -0
  46. package/dist/utils/formatConfigError.js +24 -0
  47. package/dist/utils/formatConfigMessage.js +33 -0
  48. package/dist/utils/formatConfigWarning.js +24 -0
  49. package/dist/utils/traverseConfig.js +43 -0
  50. package/dist/utils/tryBuildStep.js +38 -0
  51. package/package.json +39 -39
  52. package/dist/utils/createBuildProfiler.js +0 -125
  53. package/dist/utils/invalidateChangedFiles.js +0 -89
  54. package/dist/utils/makeRefHash.js +0 -15
@@ -14,29 +14,95 @@
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
16
  import createCheckDuplicateId from '../../../utils/createCheckDuplicateId.js';
17
- function checkAction(action, { blockId, checkDuplicateActionId, eventId, pageId, typeCounters }) {
17
+ import formatConfigError from '../../../utils/formatConfigError.js';
18
+ function checkAction(action, { blockId, checkDuplicateActionId, context, eventId, linkActionRefs, pageId, requestActionRefs, typeCounters }) {
19
+ const configKey = action['~k'];
18
20
  if (type.isUndefined(action.id)) {
19
- throw new Error(`Action id missing on event "${eventId}" on block "${blockId}" on page "${pageId}".`);
21
+ throw new Error(formatConfigError({
22
+ message: `Action id missing on event "${eventId}" on block "${blockId}" on page "${pageId}".`,
23
+ configKey,
24
+ context
25
+ }));
20
26
  }
21
27
  if (!type.isString(action.id)) {
22
- throw new Error(`Action id is not a string on event "${eventId}" on block "${blockId}" on page "${pageId}". Received ${JSON.stringify(action.id)}.`);
28
+ throw new Error(formatConfigError({
29
+ message: `Action id is not a string on event "${eventId}" on block "${blockId}" on page "${pageId}". Received ${JSON.stringify(action.id)}.`,
30
+ configKey,
31
+ context
32
+ }));
23
33
  }
24
34
  checkDuplicateActionId({
25
35
  id: action.id,
36
+ configKey,
26
37
  eventId,
27
38
  blockId,
28
39
  pageId
29
40
  });
30
41
  if (!type.isString(action.type)) {
31
- throw new Error(`Action type is not a string on action "${action.id}" on event "${eventId}" on block "${blockId}" on page "${pageId}". Received ${JSON.stringify(action.type)}.`);
42
+ throw new Error(formatConfigError({
43
+ message: `Action type is not a string on action "${action.id}" on event "${eventId}" on block "${blockId}" on page "${pageId}". Received ${JSON.stringify(action.type)}.`,
44
+ configKey,
45
+ context
46
+ }));
47
+ }
48
+ typeCounters.actions.increment(action.type, configKey);
49
+ // Collect static Request action references for validation
50
+ if (action.type === 'Request' && !type.isNone(action.params)) {
51
+ const params = action.params;
52
+ if (type.isString(params)) {
53
+ requestActionRefs.push({
54
+ requestId: params,
55
+ action,
56
+ blockId,
57
+ eventId
58
+ });
59
+ } else if (type.isArray(params)) {
60
+ params.forEach((param)=>{
61
+ if (type.isString(param)) {
62
+ requestActionRefs.push({
63
+ requestId: param,
64
+ action,
65
+ blockId,
66
+ eventId
67
+ });
68
+ }
69
+ });
70
+ }
71
+ }
72
+ // Collect static Link action references for validation
73
+ if (action.type === 'Link' && !type.isNone(action.params)) {
74
+ const params = action.params;
75
+ // Link params can be a string (pageId) or object with pageId property
76
+ if (type.isString(params)) {
77
+ linkActionRefs.push({
78
+ pageId: params,
79
+ action,
80
+ blockId,
81
+ eventId,
82
+ sourcePageId: pageId
83
+ });
84
+ } else if (type.isObject(params) && type.isString(params.pageId)) {
85
+ linkActionRefs.push({
86
+ pageId: params.pageId,
87
+ action,
88
+ blockId,
89
+ eventId,
90
+ sourcePageId: pageId
91
+ });
92
+ }
32
93
  }
33
- typeCounters.actions.increment(action.type);
34
94
  }
35
95
  function buildEvents(block, pageContext) {
96
+ const { context } = pageContext;
36
97
  if (block.events) {
37
98
  Object.keys(block.events).map((key)=>{
99
+ const eventConfigKey = block.events[key]?.['~k'] || block['~k'];
38
100
  if (!type.isArray(block.events[key]) && !type.isObject(block.events[key]) || type.isObject(block.events[key]) && type.isNone(block.events[key].try)) {
39
- throw new Error(`Actions must be an array at "${block.blockId}" in event "${key}" on page "${pageContext.pageId}". Received ${JSON.stringify(block.events[key].try)}`);
101
+ throw new Error(formatConfigError({
102
+ message: `Actions must be an array at "${block.blockId}" in event "${key}" on page "${pageContext.pageId}". Received ${JSON.stringify(block.events[key]?.try)}`,
103
+ configKey: eventConfigKey,
104
+ context
105
+ }));
40
106
  }
41
107
  if (type.isArray(block.events[key])) {
42
108
  block.events[key] = {
@@ -45,29 +111,44 @@ function buildEvents(block, pageContext) {
45
111
  };
46
112
  }
47
113
  if (!type.isArray(block.events[key].try)) {
48
- throw new Error(`Try actions must be an array at "${block.blockId}" in event "${key}.try" on page "${pageContext.pageId}". Received ${JSON.stringify(block.events[key].try)}`);
114
+ throw new Error(formatConfigError({
115
+ message: `Try actions must be an array at "${block.blockId}" in event "${key}.try" on page "${pageContext.pageId}". Received ${JSON.stringify(block.events[key].try)}`,
116
+ configKey: eventConfigKey,
117
+ context
118
+ }));
49
119
  }
50
120
  if (type.isNone(block.events[key].catch)) {
51
121
  block.events[key].catch = [];
52
122
  }
53
123
  if (!type.isArray(block.events[key].catch)) {
54
- throw new Error(`Catch actions must be an array at "${block.blockId}" in event "${key}.catch" on page "${pageContext.pageId}". Received ${JSON.stringify(block.events[key].catch)}`);
124
+ throw new Error(formatConfigError({
125
+ message: `Catch actions must be an array at "${block.blockId}" in event "${key}.catch" on page "${pageContext.pageId}". Received ${JSON.stringify(block.events[key].catch)}`,
126
+ configKey: eventConfigKey,
127
+ context
128
+ }));
55
129
  }
56
130
  const checkDuplicateActionId = createCheckDuplicateId({
57
- message: 'Duplicate actionId "{{ id }}" on event "{{ eventId }}" on block "{{ blockId }}" on page "{{ pageId }}".'
131
+ message: 'Duplicate actionId "{{ id }}" on event "{{ eventId }}" on block "{{ blockId }}" on page "{{ pageId }}".',
132
+ context
58
133
  });
59
134
  block.events[key].try.map((action)=>checkAction(action, {
60
135
  eventId: key,
61
136
  blockId: block.blockId,
137
+ context,
62
138
  typeCounters: pageContext.typeCounters,
63
139
  pageId: pageContext.pageId,
140
+ linkActionRefs: pageContext.linkActionRefs,
141
+ requestActionRefs: pageContext.requestActionRefs,
64
142
  checkDuplicateActionId
65
143
  }));
66
144
  block.events[key].catch.map((action)=>checkAction(action, {
67
145
  eventId: key,
68
146
  blockId: block.blockId,
147
+ context,
69
148
  typeCounters: pageContext.typeCounters,
70
149
  pageId: pageContext.pageId,
150
+ linkActionRefs: pageContext.linkActionRefs,
151
+ requestActionRefs: pageContext.requestActionRefs,
71
152
  checkDuplicateActionId
72
153
  }));
73
154
  });
@@ -13,28 +13,68 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
+ import formatConfigError from '../../../utils/formatConfigError.js';
16
17
  function buildRequest(request, pageContext) {
17
- const { auth, checkDuplicateRequestId, pageId, typeCounters } = pageContext;
18
+ const { auth, checkDuplicateRequestId, context, pageId, typeCounters } = pageContext;
19
+ const configKey = request['~k'];
18
20
  if (type.isUndefined(request.id)) {
19
- throw new Error(`Request id missing at page "${pageId}".`);
21
+ throw new Error(formatConfigError({
22
+ message: `Request id missing at page "${pageId}".`,
23
+ configKey,
24
+ context
25
+ }));
20
26
  }
21
27
  if (!type.isString(request.id)) {
22
- throw new Error(`Request id is not a string at page "${pageId}". Received ${JSON.stringify(request.id)}.`);
28
+ throw new Error(formatConfigError({
29
+ message: `Request id is not a string at page "${pageId}". Received ${JSON.stringify(request.id)}.`,
30
+ configKey,
31
+ context
32
+ }));
23
33
  }
24
34
  checkDuplicateRequestId({
25
35
  id: request.id,
36
+ configKey,
26
37
  pageId
27
38
  });
28
39
  if (request.id.includes('.')) {
29
- throw new Error(`Request id "${request.id}" at page "${pageId}" should not include a period (".").`);
40
+ throw new Error(formatConfigError({
41
+ message: `Request id "${request.id}" at page "${pageId}" should not include a period (".").`,
42
+ configKey,
43
+ context
44
+ }));
30
45
  }
31
46
  if (!type.isString(request.type)) {
32
- throw new Error(`Request type is not a string at at request at "${request.id}" at page "${pageId}". Received ${JSON.stringify(request.type)}.`);
47
+ throw new Error(formatConfigError({
48
+ message: `Request type is not a string at request "${request.id}" at page "${pageId}". Received ${JSON.stringify(request.type)}.`,
49
+ configKey,
50
+ context
51
+ }));
52
+ }
53
+ typeCounters.requests.increment(request.type, configKey);
54
+ // Validate connectionId references an existing connection
55
+ if (!type.isNone(request.connectionId)) {
56
+ if (!type.isString(request.connectionId)) {
57
+ throw new Error(formatConfigError({
58
+ message: `Request "${request.id}" at page "${pageId}" connectionId is not a string. Received ${JSON.stringify(request.connectionId)}.`,
59
+ configKey,
60
+ context
61
+ }));
62
+ }
63
+ if (!context.connectionIds.has(request.connectionId)) {
64
+ throw new Error(formatConfigError({
65
+ message: `Request "${request.id}" at page "${pageId}" references non-existent connection "${request.connectionId}".`,
66
+ configKey,
67
+ context
68
+ }));
69
+ }
33
70
  }
34
- typeCounters.requests.increment(request.type);
35
71
  if (type.isUndefined(request.payload)) request.payload = {};
36
72
  if (!type.isObject(request.payload)) {
37
- throw new Error(`Request "${request.id}" at page "${pageId}" payload should be an object.`);
73
+ throw new Error(formatConfigError({
74
+ message: `Request "${request.id}" at page "${pageId}" payload should be an object.`,
75
+ configKey,
76
+ context
77
+ }));
38
78
  }
39
79
  request.auth = auth;
40
80
  request.requestId = request.id;
@@ -13,6 +13,6 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ function countBlockTypes(block, { typeCounters }) {
16
- typeCounters.blocks.increment(block.type);
16
+ typeCounters.blocks.increment(block.type, block['~k']);
17
17
  }
18
18
  export default countBlockTypes;
@@ -13,25 +13,51 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
- function validateBlock(block, { pageId }) {
16
+ import formatConfigError from '../../../utils/formatConfigError.js';
17
+ function validateBlock(block, { pageId, context }) {
18
+ const configKey = block?.['~k'];
17
19
  if (!type.isObject(block)) {
18
- throw new Error(`Expected block to be an object on page "${pageId}". Received ${JSON.stringify(block)}.`);
20
+ throw new Error(formatConfigError({
21
+ message: `Expected block to be an object on page "${pageId}". Received ${JSON.stringify(block)}.`,
22
+ configKey,
23
+ context
24
+ }));
19
25
  }
20
26
  if (type.isUndefined(block.id)) {
21
- throw new Error(`Block id missing at page "${pageId}".`);
27
+ throw new Error(formatConfigError({
28
+ message: `Block id missing at page "${pageId}".`,
29
+ configKey,
30
+ context
31
+ }));
22
32
  }
23
33
  if (!type.isString(block.id)) {
24
- throw new Error(`Block id is not a string at page "${pageId}". Received ${JSON.stringify(block.id)}.`);
34
+ throw new Error(formatConfigError({
35
+ message: `Block id is not a string at page "${pageId}". Received ${JSON.stringify(block.id)}.`,
36
+ configKey,
37
+ context
38
+ }));
25
39
  }
26
40
  if (type.isNone(block.type)) {
27
- throw new Error(`Block type is not defined at "${block.id}" on page "${pageId}".`);
41
+ throw new Error(formatConfigError({
42
+ message: `Block type is not defined at "${block.id}" on page "${pageId}".`,
43
+ configKey,
44
+ context
45
+ }));
28
46
  }
29
47
  if (!type.isString(block.type)) {
30
- throw new Error(`Block type is not a string at "${block.id}" on page "${pageId}". Received ${JSON.stringify(block.type)}.`);
48
+ throw new Error(formatConfigError({
49
+ message: `Block type is not a string at "${block.id}" on page "${pageId}". Received ${JSON.stringify(block.type)}.`,
50
+ configKey,
51
+ context
52
+ }));
31
53
  }
32
54
  if (!type.isNone(block.requests)) {
33
55
  if (!type.isArray(block.requests)) {
34
- throw new Error(`Requests is not an array at "${block.id}" on page "${pageId}". Received ${JSON.stringify(block.requests)}`);
56
+ throw new Error(formatConfigError({
57
+ message: `Requests is not an array at "${block.id}" on page "${pageId}". Received ${JSON.stringify(block.requests)}`,
58
+ configKey,
59
+ context
60
+ }));
35
61
  }
36
62
  }
37
63
  }
@@ -16,30 +16,54 @@
16
16
  import buildBlock from './buildBlock/buildBlock.js';
17
17
  import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
18
18
  import createCounter from '../../utils/createCounter.js';
19
+ import formatConfigError from '../../utils/formatConfigError.js';
20
+ import validateRequestReferences from './validateRequestReferences.js';
19
21
  function buildPage({ page, index, context, checkDuplicatePageId }) {
22
+ const configKey = page['~k'];
20
23
  if (type.isUndefined(page.id)) {
21
- throw new Error(`Page id missing at page ${index}.`);
24
+ throw new Error(formatConfigError({
25
+ message: `Page id missing at page ${index}.`,
26
+ configKey,
27
+ context
28
+ }));
22
29
  }
23
30
  if (!type.isString(page.id)) {
24
- throw new Error(`Page id is not a string at page ${index}. Received ${JSON.stringify(page.id)}.`);
31
+ throw new Error(formatConfigError({
32
+ message: `Page id is not a string at page ${index}. Received ${JSON.stringify(page.id)}.`,
33
+ configKey,
34
+ context
35
+ }));
25
36
  }
26
37
  checkDuplicatePageId({
27
- id: page.id
38
+ id: page.id,
39
+ configKey
28
40
  });
29
41
  page.pageId = page.id;
30
42
  const requests = [];
43
+ const requestActionRefs = [];
31
44
  buildBlock(page, {
32
45
  auth: page.auth,
33
46
  blockIdCounter: createCounter(),
34
47
  checkDuplicateRequestId: createCheckDuplicateId({
35
- message: 'Duplicate requestId "{{ id }}" on page "{{ pageId }}".'
48
+ message: 'Duplicate requestId "{{ id }}" on page "{{ pageId }}".',
49
+ context
36
50
  }),
51
+ context,
37
52
  pageId: page.pageId,
38
53
  requests,
54
+ requestActionRefs,
55
+ linkActionRefs: context.linkActionRefs,
39
56
  typeCounters: context.typeCounters
40
57
  });
41
58
  // set page.id since buildBlock sets id as well.
42
59
  page.id = `page:${page.pageId}`;
60
+ // Validate that all Request actions reference defined requests
61
+ validateRequestReferences({
62
+ requestActionRefs,
63
+ requests,
64
+ pageId: page.pageId,
65
+ context
66
+ });
43
67
  page.requests = requests;
44
68
  }
45
69
  export default buildPage;
@@ -15,17 +15,42 @@
15
15
  */ import { type } from '@lowdefy/helpers';
16
16
  import buildPage from './buildPage.js';
17
17
  import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
18
+ import validateLinkReferences from './validateLinkReferences.js';
19
+ import validatePayloadReferences from './validatePayloadReferences.js';
20
+ import validateStateReferences from './validateStateReferences.js';
18
21
  function buildPages({ components, context }) {
19
22
  const pages = type.isArray(components.pages) ? components.pages : [];
20
23
  const checkDuplicatePageId = createCheckDuplicateId({
21
- message: 'Duplicate pageId "{{ id }}".'
24
+ message: 'Duplicate pageId "{{ id }}".',
25
+ context
22
26
  });
27
+ // Initialize linkActionRefs to collect Link action references across all pages
28
+ context.linkActionRefs = [];
23
29
  pages.map((page, index)=>buildPage({
24
30
  page,
25
31
  index,
26
32
  context,
27
33
  checkDuplicatePageId
28
34
  }));
35
+ // Validate that all Link actions reference existing pages
36
+ const pageIds = pages.map((page)=>page.pageId);
37
+ validateLinkReferences({
38
+ linkActionRefs: context.linkActionRefs,
39
+ pageIds,
40
+ context
41
+ });
42
+ // Validate that _state references use defined block IDs
43
+ // and _payload references use defined payload keys
44
+ pages.forEach((page)=>{
45
+ validateStateReferences({
46
+ page,
47
+ context
48
+ });
49
+ validatePayloadReferences({
50
+ page,
51
+ context
52
+ });
53
+ });
29
54
  return components;
30
55
  }
31
56
  export default buildPages;
@@ -16,7 +16,7 @@
16
16
  import buildAuth from '../buildAuth/buildAuth.js';
17
17
  import buildPages from './buildPages.js';
18
18
  import createContext from '../../createContext.js';
19
- function buildTestPage({ pageConfig }) {
19
+ function buildTestPage({ pageConfig, connectionIds = [] }) {
20
20
  const context = createContext({
21
21
  customTypesMap: {},
22
22
  directories: {},
@@ -28,6 +28,14 @@ function buildTestPage({ pageConfig }) {
28
28
  },
29
29
  stage: 'test'
30
30
  });
31
+ // Add any connectionIds from test config to allow validation to pass
32
+ connectionIds.forEach((id)=>context.connectionIds.add(id));
33
+ // Also extract connectionIds from requests in the pageConfig
34
+ (pageConfig.requests || []).forEach((request)=>{
35
+ if (request.connectionId) {
36
+ context.connectionIds.add(request.connectionId);
37
+ }
38
+ });
31
39
  const components = {
32
40
  pages: [
33
41
  pageConfig
@@ -0,0 +1,33 @@
1
+ /*
2
+ Copyright 2020-2024 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 formatConfigError from '../../utils/formatConfigError.js';
16
+ function validateLinkReferences({ linkActionRefs, pageIds, context }) {
17
+ const pageIdSet = new Set(pageIds);
18
+ linkActionRefs.forEach(({ pageId, action, sourcePageId })=>{
19
+ if (!pageIdSet.has(pageId)) {
20
+ const errorMessage = formatConfigError({
21
+ message: `Page "${pageId}" not found. Link on page "${sourcePageId}" references non-existent page.`,
22
+ configKey: action['~k'],
23
+ context
24
+ });
25
+ if (context.stage === 'dev' || context.stage === 'test') {
26
+ context.logger.warn(errorMessage);
27
+ } else {
28
+ throw new Error(errorMessage);
29
+ }
30
+ }
31
+ });
32
+ }
33
+ export default validateLinkReferences;
@@ -0,0 +1,52 @@
1
+ /*
2
+ Copyright 2020-2024 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 extractOperatorKey from '../../utils/extractOperatorKey.js';
16
+ import formatConfigWarning from '../../utils/formatConfigWarning.js';
17
+ import traverseConfig from '../../utils/traverseConfig.js';
18
+ function validatePayloadReferences({ page, context }) {
19
+ const requests = page.requests || [];
20
+ requests.forEach((request)=>{
21
+ // Collect payload keys defined in the request
22
+ const payloadKeys = new Set(Object.keys(request.payload || {}));
23
+ // Skip if no payload defined (nothing to reference)
24
+ if (payloadKeys.size === 0) return;
25
+ // Find _payload references in request.properties
26
+ const payloadRefs = new Map(); // topLevelKey -> configKey (first occurrence)
27
+ traverseConfig({
28
+ config: request.properties,
29
+ visitor: (obj)=>{
30
+ if (obj._payload !== undefined) {
31
+ const topLevelKey = extractOperatorKey({
32
+ operatorValue: obj._payload
33
+ });
34
+ if (topLevelKey && !payloadRefs.has(topLevelKey)) {
35
+ payloadRefs.set(topLevelKey, obj['~k']);
36
+ }
37
+ }
38
+ }
39
+ });
40
+ // Warn for undefined payload references
41
+ payloadRefs.forEach((configKey, topLevelKey)=>{
42
+ if (payloadKeys.has(topLevelKey)) return;
43
+ const message = `_payload references "${topLevelKey}" in request "${request.requestId}" on page "${page.pageId}", ` + `but no key "${topLevelKey}" exists in the request payload definition. ` + `Payload keys are defined in the request's "payload" property. ` + `Check for typos or add the key to the payload definition.`;
44
+ context.logger.warn(formatConfigWarning({
45
+ message,
46
+ configKey,
47
+ context
48
+ }));
49
+ });
50
+ });
51
+ }
52
+ export default validatePayloadReferences;
@@ -0,0 +1,33 @@
1
+ /*
2
+ Copyright 2020-2024 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 formatConfigError from '../../utils/formatConfigError.js';
16
+ function validateRequestReferences({ requestActionRefs, requests, pageId, context }) {
17
+ const requestIds = new Set(requests.map((req)=>req.requestId));
18
+ requestActionRefs.forEach(({ requestId, action })=>{
19
+ if (!requestIds.has(requestId)) {
20
+ const errorMessage = formatConfigError({
21
+ message: `Request "${requestId}" not defined on page "${pageId}".`,
22
+ configKey: action['~k'],
23
+ context
24
+ });
25
+ if (context.stage === 'dev' || context.stage === 'test') {
26
+ context.logger.warn(errorMessage);
27
+ } else {
28
+ throw new Error(errorMessage);
29
+ }
30
+ }
31
+ });
32
+ }
33
+ export default validateRequestReferences;
@@ -0,0 +1,52 @@
1
+ /*
2
+ Copyright 2020-2024 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 extractOperatorKey from '../../utils/extractOperatorKey.js';
16
+ import formatConfigWarning from '../../utils/formatConfigWarning.js';
17
+ import traverseConfig from '../../utils/traverseConfig.js';
18
+ function validateStateReferences({ page, context }) {
19
+ // Single traversal collects both blockIds and _state references
20
+ // More memory-efficient than stringify+regex for massive pages
21
+ const blockIds = new Set();
22
+ const stateRefs = new Map(); // topLevelKey -> configKey (first occurrence)
23
+ traverseConfig({
24
+ config: page,
25
+ visitor: (obj)=>{
26
+ // Collect blockId if present
27
+ if (obj.blockId) {
28
+ blockIds.add(obj.blockId);
29
+ }
30
+ // Collect _state reference if present
31
+ if (obj._state !== undefined) {
32
+ const topLevelKey = extractOperatorKey({
33
+ operatorValue: obj._state
34
+ });
35
+ if (topLevelKey && !stateRefs.has(topLevelKey)) {
36
+ stateRefs.set(topLevelKey, obj['~k']);
37
+ }
38
+ }
39
+ }
40
+ });
41
+ // Filter to only undefined references and warn
42
+ stateRefs.forEach((configKey, topLevelKey)=>{
43
+ if (blockIds.has(topLevelKey)) return;
44
+ 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.`;
45
+ context.logger.warn(formatConfigWarning({
46
+ message,
47
+ configKey,
48
+ context
49
+ }));
50
+ });
51
+ }
52
+ export default validateStateReferences;
@@ -12,43 +12,21 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import createBuildProfiler from '../../utils/createBuildProfiler.js';
16
- import recursiveBuild from './recursiveBuild.js';
15
+ */ import recursiveBuild from './recursiveBuild.js';
17
16
  import makeRefDefinition from './makeRefDefinition.js';
18
17
  import evaluateBuildOperators from './evaluateBuildOperators.js';
19
- import invalidateChangedFiles from '../../utils/invalidateChangedFiles.js';
20
18
  async function buildRefs({ context }) {
21
- const profiler = createBuildProfiler({
22
- logger: context.logger,
23
- prefix: 'buildRefs'
24
- });
25
- // For incremental builds, invalidate caches for changed files and their dependents
26
- if (context.changedFiles && context.changedFiles.length > 0) {
27
- invalidateChangedFiles({
28
- changedFiles: context.changedFiles,
29
- dependencyGraph: context.dependencyGraph,
30
- parsedContentCache: context.parsedContentCache,
31
- refCache: context.refCache,
32
- pathToRefHashes: context.pathToRefHashes,
33
- logger: context.logger
34
- });
35
- }
36
19
  const refDef = makeRefDefinition('lowdefy.yaml', null, context.refMap);
37
- // Use persistent refCache from context for incremental builds
38
- const refCache = context.refCache;
39
- let components = await profiler.time('recursiveBuild', ()=>recursiveBuild({
40
- context,
41
- refDef,
42
- count: 0,
43
- refCache,
44
- profiler
45
- }));
46
- components = await profiler.time('evaluateBuildOperators:final', ()=>evaluateBuildOperators({
47
- context,
48
- input: components,
49
- refDef
50
- }));
51
- profiler.printSummary();
20
+ let components = await recursiveBuild({
21
+ context,
22
+ refDef,
23
+ count: 0
24
+ });
25
+ components = await evaluateBuildOperators({
26
+ context,
27
+ input: components,
28
+ refDef
29
+ });
52
30
  return components ?? {};
53
31
  }
54
32
  export default buildRefs;