@lowdefy/build 5.5.1 → 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 (107) hide show
  1. package/dist/build/buildAgents.js +70 -17
  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 +69 -0
  12. package/dist/build/buildAuth/buildAuth.js +14 -0
  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/getAgentRoles.js +39 -0
  17. package/dist/build/buildAuth/getProtectedAgents.js +29 -0
  18. package/dist/build/buildAuth/getProtectedWebsockets.js +29 -0
  19. package/dist/build/buildAuth/getWebsocketRoles.js +39 -0
  20. package/dist/build/buildAuth/rejectReservedEntityIds.js +52 -0
  21. package/dist/build/buildAuth/validateAuthConfig.js +15 -3
  22. package/dist/build/buildImports/buildImportsDev.js +15 -0
  23. package/dist/build/buildImports/buildImportsProd.js +4 -1
  24. package/dist/build/buildJs/generateClientJsModule.js +27 -0
  25. package/dist/build/buildJs/writeJs.js +3 -5
  26. package/dist/build/buildMcp.js +73 -0
  27. package/dist/build/buildModuleDefs.js +90 -48
  28. package/dist/build/buildModules.js +61 -14
  29. package/dist/build/buildNotifications.js +98 -0
  30. package/dist/build/buildPages/buildBlock/buildBlock.js +3 -1
  31. package/dist/build/buildPages/buildBlock/buildDynamicBlock.js +131 -0
  32. package/dist/build/buildPages/buildBlock/buildEvents.js +197 -22
  33. package/dist/build/buildPages/buildBlock/buildRequests.js +9 -0
  34. package/dist/build/buildPages/buildBlock/normalizeClassAndStyles.js +2 -11
  35. package/dist/build/buildPages/buildBlock/setBlockId.js +5 -2
  36. package/dist/build/buildPages/buildBlock/validateBlock.js +19 -1
  37. package/dist/build/buildPages/buildPage.js +27 -3
  38. package/dist/build/buildPages/buildSubscriptions.js +145 -0
  39. package/dist/build/buildPages/validateDynamicBlockRefs.js +30 -0
  40. package/dist/build/buildPages/validateStateReferences.js +4 -2
  41. package/dist/build/buildPages/validateWebsocketRefs.js +30 -0
  42. package/dist/build/buildRefs/buildRefs.js +21 -12
  43. package/dist/build/buildRefs/cloneWithMarkers.js +57 -0
  44. package/dist/build/buildRefs/deferredRegions.js +80 -0
  45. package/dist/build/buildRefs/deferredRegistry.js +292 -0
  46. package/dist/build/buildRefs/getKey.js +16 -5
  47. package/dist/build/buildRefs/getModuleRefContent.js +21 -6
  48. package/dist/build/buildRefs/getUserJavascriptFunction.js +1 -1
  49. package/dist/build/buildRefs/parseRefContent.js +24 -0
  50. package/dist/build/buildRefs/{evaluateStaticOperators.js → precomputeRuntimeOperators.js} +7 -6
  51. package/dist/build/buildRefs/walker.js +490 -209
  52. package/dist/build/buildTypes.js +22 -1
  53. package/dist/build/buildWebsockets.js +99 -0
  54. package/dist/build/collectPageContent.js +7 -1
  55. package/dist/build/copyAgentFileSystems.js +2 -2
  56. package/dist/build/fetchModules.js +18 -4
  57. package/dist/build/full/buildPages.js +16 -0
  58. package/dist/build/full/updateServerPackageJson.js +21 -0
  59. package/dist/build/jit/buildPageJit.js +46 -6
  60. package/dist/build/jit/collectSkeletonSourceFiles.js +14 -0
  61. package/dist/build/jit/pageContentKeys.js +1 -0
  62. package/dist/build/jit/shallowBuild.js +81 -1
  63. package/dist/build/jit/writePageJit.js +11 -8
  64. package/dist/build/registerModules.js +149 -42
  65. package/dist/build/validateCallAgentSteps.js +58 -0
  66. package/dist/build/validateConfig.js +4 -0
  67. package/dist/build/validateCronConfig.js +110 -0
  68. package/dist/build/validateRenderNotificationSteps.js +50 -0
  69. package/dist/build/writeApi.js +58 -1
  70. package/dist/build/writeI18n.js +8 -1
  71. package/dist/build/writeMcp.js +21 -0
  72. package/dist/build/writeNotifications.js +30 -0
  73. package/dist/build/writePluginImports/importPluginModule.js +39 -0
  74. package/dist/build/writePluginImports/writeActionSchemaMap.js +6 -7
  75. package/dist/build/writePluginImports/writeAuthImports.js +4 -0
  76. package/dist/build/writePluginImports/writeAvailableTypes.js +99 -0
  77. package/dist/build/writePluginImports/writeBlockSchemaMap.js +10 -9
  78. package/dist/build/writePluginImports/writeConnectionSchemaMap.js +101 -0
  79. package/dist/build/writePluginImports/writeGlobalsCss.js +20 -11
  80. package/dist/build/writePluginImports/writeNotificationImports.js +31 -0
  81. package/dist/build/writePluginImports/writeOperatorSchemaMap.js +6 -7
  82. package/dist/build/writePluginImports/writePluginImports.js +18 -4
  83. package/dist/build/writePluginImports/writeWebsocketImports.js +22 -0
  84. package/dist/build/writeWebsockets.js +30 -0
  85. package/dist/createContext.js +6 -1
  86. package/dist/defaultPackages.js +7 -1
  87. package/dist/defaultTypesMap.js +689 -386
  88. package/dist/dynamic/buildDynamicBlocks.js +120 -0
  89. package/dist/index.js +78 -8
  90. package/dist/indexDev.js +4 -0
  91. package/dist/lowdefySchema.js +861 -6
  92. package/dist/scripts/generateDefaultTypes.js +5 -2
  93. package/dist/test-utils/expectTerminates.js +33 -0
  94. package/dist/test-utils/runBuild.js +20 -1
  95. package/dist/test-utils/runBuildForSnapshots.js +34 -4
  96. package/dist/test-utils/testContext.js +8 -2
  97. package/dist/utils/countOperators.js +5 -17
  98. package/dist/utils/createPluginTypesMap.js +21 -0
  99. package/dist/utils/getCronEnvironmentNames.js +25 -0
  100. package/dist/utils/logCollectedErrors.js +44 -10
  101. package/dist/utils/serializeBuildException.js +29 -0
  102. package/dist/utils/validateCronExpression.js +131 -0
  103. package/dist/utils/validateId.js +11 -0
  104. package/dist/utils/writeBuildArtifact.js +5 -2
  105. package/package.json +66 -59
  106. package/dist/build/writePluginImports/writeServerExternalPackages.js +0 -121
  107. /package/dist/build/{resolveModuleOperators.js → buildRefs/scopeMenuItemIds.js} +0 -0
@@ -14,8 +14,11 @@
14
14
  limitations under the License.
15
15
  */ import { type } from '@lowdefy/helpers';
16
16
  import buildAuthPlugins from './buildAuthPlugins.js';
17
+ import buildAgentAuth from './buildAgentAuth.js';
18
+ import buildAuthStrategies from './buildAuthStrategies.js';
17
19
  import buildApiAuth from './buildApiAuth.js';
18
20
  import buildPageAuth from './buildPageAuth.js';
21
+ import buildWebsocketAuth from './buildWebsocketAuth.js';
19
22
  import validateAuthConfig from './validateAuthConfig.js';
20
23
  function buildAuth({ components, context }) {
21
24
  const configured = !type.isNone(components.auth);
@@ -24,10 +27,21 @@ function buildAuth({ components, context }) {
24
27
  context
25
28
  });
26
29
  components.auth.configured = configured;
30
+ buildAuthStrategies({
31
+ components
32
+ });
27
33
  buildApiAuth({
28
34
  components,
29
35
  context
30
36
  });
37
+ buildWebsocketAuth({
38
+ components,
39
+ context
40
+ });
41
+ buildAgentAuth({
42
+ components,
43
+ context
44
+ });
31
45
  buildPageAuth({
32
46
  components,
33
47
  context
@@ -96,5 +96,11 @@ function buildAuthPlugins({ components, context }) {
96
96
  typeClass: 'provider',
97
97
  context
98
98
  });
99
+ buildAuthPlugin({
100
+ counter: counters.strategies,
101
+ pluginConfig: authConfig.strategies,
102
+ typeClass: 'strategy',
103
+ context
104
+ });
99
105
  }
100
106
  export default buildAuthPlugins;
@@ -0,0 +1,117 @@
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 } from '@lowdefy/errors';
17
+ const strategyTypes = [
18
+ 'apiKey',
19
+ 'jwt'
20
+ ];
21
+ // Prototype-pollution vectors rejected as claimMapping path segments.
22
+ const reservedKeys = new Set([
23
+ '__proto__',
24
+ 'constructor',
25
+ 'prototype',
26
+ '__defineGetter__',
27
+ '__defineSetter__',
28
+ '__lookupGetter__',
29
+ '__lookupSetter__'
30
+ ]);
31
+ function setDefault(object, key, value) {
32
+ if (type.isNone(object[key])) {
33
+ object[key] = value;
34
+ }
35
+ }
36
+ function validateApiKeyStrategy({ strategy, configKey }) {
37
+ const properties = strategy.properties ?? {};
38
+ if (!type.isArray(properties.keys) || properties.keys.length === 0) {
39
+ throw new ConfigError(`Auth strategy "${strategy.id}" requires "properties.keys" to be a non-empty array of keys.`, {
40
+ configKey
41
+ });
42
+ }
43
+ properties.keys.forEach((key, index)=>{
44
+ if (type.isNone(key.value)) {
45
+ throw new ConfigError(`Auth strategy "${strategy.id}" key at index ${index} is missing "value". Reference the key with the _secret operator.`, {
46
+ configKey
47
+ });
48
+ }
49
+ });
50
+ }
51
+ function validateJwtStrategy({ strategy, configKey }) {
52
+ const properties = strategy.properties ?? {};
53
+ const hasSecret = !type.isNone(properties.secret);
54
+ const hasJwksUri = !type.isNone(properties.jwksUri);
55
+ if (hasSecret === hasJwksUri) {
56
+ throw new ConfigError(`Auth strategy "${strategy.id}" requires exactly one of "properties.secret" or "properties.jwksUri" to verify token signatures.`, {
57
+ configKey
58
+ });
59
+ }
60
+ // The algorithms allowlist restricts which signing algorithms are accepted,
61
+ // preventing "alg: none" and algorithm-confusion downgrade attacks.
62
+ if (!type.isArray(properties.algorithms) || properties.algorithms.length === 0) {
63
+ throw new ConfigError(`Auth strategy "${strategy.id}" requires "properties.algorithms" to be a non-empty array restricting the accepted signing algorithms.`, {
64
+ configKey
65
+ });
66
+ }
67
+ // claimMapping field names become object keys on the user and attributes
68
+ // objects the jwt verifier builds per request - gated here where the config
69
+ // location is available, so a pollution-vector key never reaches runtime.
70
+ Object.keys(properties.claimMapping ?? {}).forEach((field)=>{
71
+ const reservedSegment = field.split('.').find((segment)=>reservedKeys.has(segment));
72
+ if (!type.isNone(reservedSegment)) {
73
+ throw new ConfigError(`Auth strategy "${strategy.id}" claimMapping field "${field}" uses reserved name "${reservedSegment}".`, {
74
+ configKey
75
+ });
76
+ }
77
+ });
78
+ }
79
+ // Validates auth.strategies and writes per-entry defaults so the runtime
80
+ // never falls back. Objects are mutated in place to preserve ~k markers.
81
+ function buildAuthStrategies({ components }) {
82
+ const seenIds = {};
83
+ (components.auth.strategies ?? []).forEach((strategy)=>{
84
+ const configKey = strategy['~k'];
85
+ if (seenIds[strategy.id] === true) {
86
+ throw new ConfigError(`Duplicate auth strategy id "${strategy.id}".`, {
87
+ configKey
88
+ });
89
+ }
90
+ seenIds[strategy.id] = true;
91
+ if (!strategyTypes.includes(strategy.type)) {
92
+ throw new ConfigError(`Auth strategy "${strategy.id}" has unknown type "${strategy.type}". Valid types are: ${strategyTypes.join(', ')}.`, {
93
+ received: strategy.type,
94
+ configKey
95
+ });
96
+ }
97
+ if (strategy.type === 'apiKey') {
98
+ validateApiKeyStrategy({
99
+ strategy,
100
+ configKey
101
+ });
102
+ }
103
+ if (strategy.type === 'jwt') {
104
+ validateJwtStrategy({
105
+ strategy,
106
+ configKey
107
+ });
108
+ }
109
+ setDefault(strategy, 'roles', []);
110
+ setDefault(strategy, 'attributes', {});
111
+ if (strategy.type === 'apiKey') {
112
+ setDefault(strategy.properties, 'headerName', 'X-API-Key');
113
+ }
114
+ });
115
+ return components;
116
+ }
117
+ export default buildAuthStrategies;
@@ -0,0 +1,55 @@
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 } from '@lowdefy/errors';
17
+ import getProtectedWebsockets from './getProtectedWebsockets.js';
18
+ import getWebsocketRoles from './getWebsocketRoles.js';
19
+ import { isInPatternList } from './matchPattern.js';
20
+ function buildWebsocketAuth({ components, context }) {
21
+ const protectedWebsockets = getProtectedWebsockets({
22
+ components
23
+ });
24
+ const websocketRoles = getWebsocketRoles({
25
+ components
26
+ });
27
+ let configPublicWebsockets = [];
28
+ if (type.isArray(components.auth.websockets.public)) {
29
+ configPublicWebsockets = components.auth.websockets.public;
30
+ }
31
+ (components.websockets || []).forEach((websocket)=>{
32
+ if (websocketRoles[websocket.id]) {
33
+ if (isInPatternList(websocket.id, configPublicWebsockets)) {
34
+ throw new ConfigError(`Websocket "${websocket.id}" is both protected by roles and public.`, {
35
+ received: websocketRoles[websocket.id],
36
+ configKey: websocket['~k']
37
+ });
38
+ }
39
+ websocket.auth = {
40
+ public: false,
41
+ roles: websocketRoles[websocket.id]
42
+ };
43
+ } else if (protectedWebsockets.includes(websocket.id)) {
44
+ websocket.auth = {
45
+ public: false
46
+ };
47
+ } else {
48
+ websocket.auth = {
49
+ public: true
50
+ };
51
+ }
52
+ });
53
+ return components;
54
+ }
55
+ export default buildWebsocketAuth;
@@ -0,0 +1,39 @@
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 { matchesPattern } from './matchPattern.js';
16
+ function getAgentRoles({ components }) {
17
+ const roles = components.auth.api.roles;
18
+ const agentIds = (components.agents ?? []).map((agent)=>agent.id);
19
+ const agentRoles = {};
20
+ Object.keys(roles).forEach((roleName)=>{
21
+ roles[roleName].forEach((pattern)=>{
22
+ agentIds.forEach((agentId)=>{
23
+ if (matchesPattern(agentId, pattern)) {
24
+ if (!agentRoles[agentId]) {
25
+ agentRoles[agentId] = new Set();
26
+ }
27
+ agentRoles[agentId].add(roleName);
28
+ }
29
+ });
30
+ });
31
+ });
32
+ Object.keys(agentRoles).forEach((agentId)=>{
33
+ agentRoles[agentId] = [
34
+ ...agentRoles[agentId]
35
+ ];
36
+ });
37
+ return agentRoles;
38
+ }
39
+ export default getAgentRoles;
@@ -0,0 +1,29 @@
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 { isInPatternList } from './matchPattern.js';
17
+ function getProtectedAgents({ components }) {
18
+ const agentIds = (components.agents ?? []).map((agent)=>agent.id);
19
+ let protectedAgents = [];
20
+ if (type.isArray(components.auth.api.public)) {
21
+ protectedAgents = agentIds.filter((agentId)=>!isInPatternList(agentId, components.auth.api.public));
22
+ } else if (components.auth.api.protected === true) {
23
+ protectedAgents = agentIds;
24
+ } else if (type.isArray(components.auth.api.protected)) {
25
+ protectedAgents = agentIds.filter((agentId)=>isInPatternList(agentId, components.auth.api.protected));
26
+ }
27
+ return protectedAgents;
28
+ }
29
+ export default getProtectedAgents;
@@ -0,0 +1,29 @@
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 { isInPatternList } from './matchPattern.js';
17
+ function getProtectedWebsockets({ components }) {
18
+ const websocketIds = (components.websockets ?? []).map((websocket)=>websocket.id);
19
+ let protectedWebsockets = [];
20
+ if (type.isArray(components.auth.websockets.public)) {
21
+ protectedWebsockets = websocketIds.filter((websocketId)=>!isInPatternList(websocketId, components.auth.websockets.public));
22
+ } else if (components.auth.websockets.protected === true) {
23
+ protectedWebsockets = websocketIds;
24
+ } else if (type.isArray(components.auth.websockets.protected)) {
25
+ protectedWebsockets = websocketIds.filter((websocketId)=>isInPatternList(websocketId, components.auth.websockets.protected));
26
+ }
27
+ return protectedWebsockets;
28
+ }
29
+ export default getProtectedWebsockets;
@@ -0,0 +1,39 @@
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 { matchesPattern } from './matchPattern.js';
16
+ function getWebsocketRoles({ components }) {
17
+ const roles = components.auth.websockets.roles;
18
+ const websocketIds = (components.websockets ?? []).map((websocket)=>websocket.id);
19
+ const websocketRoles = {};
20
+ Object.keys(roles).forEach((roleName)=>{
21
+ roles[roleName].forEach((pattern)=>{
22
+ websocketIds.forEach((websocketId)=>{
23
+ if (matchesPattern(websocketId, pattern)) {
24
+ if (!websocketRoles[websocketId]) {
25
+ websocketRoles[websocketId] = new Set();
26
+ }
27
+ websocketRoles[websocketId].add(roleName);
28
+ }
29
+ });
30
+ });
31
+ });
32
+ Object.keys(websocketRoles).forEach((websocketId)=>{
33
+ websocketRoles[websocketId] = [
34
+ ...websocketRoles[websocketId]
35
+ ];
36
+ });
37
+ return websocketRoles;
38
+ }
39
+ export default getWebsocketRoles;
@@ -0,0 +1,52 @@
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 { isReserved } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
17
+ // buildAuth runs before validateId sees page, endpoint and agent ids, and it keys
18
+ // plain-object role maps on them: getPageRoles, getApiRoles and getAgentRoles all
19
+ // build `roles[id]` and read it back with a truthiness check. A reserved id reads
20
+ // through Object.prototype there, so the entity is silently marked protected with
21
+ // Object.prototype as its roles - and the object then travels into addKeys, which
22
+ // stamps a '~k' onto Object.prototype and corrupts every plain object in the
23
+ // process. Gate the ids here, where buildAuth first touches them.
24
+ //
25
+ // validateId reports the same ids again in buildApi, buildAgents and buildPages;
26
+ // logCollectedErrors collapses the pair on source line plus message so the author
27
+ // reads it once.
28
+ function rejectReservedEntityIds({ components }) {
29
+ const entityTypes = [
30
+ {
31
+ items: components.pages,
32
+ field: 'Page id'
33
+ },
34
+ {
35
+ items: components.api,
36
+ field: 'Endpoint id'
37
+ },
38
+ {
39
+ items: components.agents,
40
+ field: 'Agent id'
41
+ }
42
+ ];
43
+ entityTypes.forEach(({ items, field })=>{
44
+ (items ?? []).forEach((item)=>{
45
+ if (!isReserved(item.id)) return;
46
+ throw new ConfigError(`${field} "${item.id}" is a reserved name and cannot be used as an id.`, {
47
+ configKey: item['~k']
48
+ });
49
+ });
50
+ });
51
+ }
52
+ export default rejectReservedEntityIds;
@@ -32,6 +32,12 @@ function validateAuthConfig({ components, context }) {
32
32
  if (type.isNone(components.auth.api.roles)) {
33
33
  components.auth.api.roles = {};
34
34
  }
35
+ if (type.isNone(components.auth.websockets)) {
36
+ components.auth.websockets = {};
37
+ }
38
+ if (type.isNone(components.auth.websockets.roles)) {
39
+ components.auth.websockets.roles = {};
40
+ }
35
41
  if (type.isNone(components.auth.authPages)) {
36
42
  components.auth.authPages = {};
37
43
  }
@@ -93,9 +99,15 @@ function validateAuthConfig({ components, context }) {
93
99
  context,
94
100
  entity: 'pages'
95
101
  });
96
- // Validate NEXTAUTH_SECRET is set when auth providers are configured
97
- if (components.auth.providers.length > 0 && type.isNone(process.env.NEXTAUTH_SECRET)) {
98
- throw new ConfigError('Auth providers are configured but NEXTAUTH_SECRET environment variable is not set. ' + 'Set NEXTAUTH_SECRET to a secure random string (e.g., generate with `openssl rand -base64 32`).', {
102
+ validateMutualExclusivity({
103
+ components,
104
+ context,
105
+ entity: 'websockets'
106
+ });
107
+ // Validate AUTH_SECRET is set when auth providers are configured. Auth.js v5
108
+ // reads AUTH_SECRET; the v4 NEXTAUTH_SECRET variable is not supported.
109
+ if (components.auth.providers.length > 0 && type.isNone(process.env.AUTH_SECRET)) {
110
+ throw new ConfigError('Auth providers are configured but AUTH_SECRET environment variable is not set. ' + 'Set AUTH_SECRET to a secure random string (e.g., generate with `openssl rand -base64 32`).', {
99
111
  configKey: components.auth.providers['~k'] ?? components.auth['~k']
100
112
  });
101
113
  }
@@ -27,9 +27,12 @@ function getPluginPackages({ components }) {
27
27
  getPackages(components.types.auth.callbacks);
28
28
  getPackages(components.types.auth.events);
29
29
  getPackages(components.types.auth.providers);
30
+ getPackages(components.types.auth.strategies);
30
31
  getPackages(components.types.blocks);
31
32
  getPackages(components.types.connections);
33
+ getPackages(components.types.notifications);
32
34
  getPackages(components.types.requests);
35
+ getPackages(components.types.websockets);
33
36
  getPackages(components.types.operators.client);
34
37
  getPackages(components.types.operators.server);
35
38
  return pluginPackages;
@@ -74,6 +77,10 @@ function buildImportsDev({ components, context }) {
74
77
  providers: buildImportClassDev({
75
78
  pluginPackages,
76
79
  map: context.typesMap.auth.providers
80
+ }),
81
+ strategies: buildImportClassDev({
82
+ pluginPackages,
83
+ map: context.typesMap.auth.strategies
77
84
  })
78
85
  },
79
86
  blocks,
@@ -87,10 +94,18 @@ function buildImportsDev({ components, context }) {
87
94
  context,
88
95
  defaults: defaultIconsDev
89
96
  }),
97
+ notifications: buildImportClassDev({
98
+ pluginPackages,
99
+ map: context.typesMap.notifications
100
+ }),
90
101
  requests: buildImportClassDev({
91
102
  pluginPackages,
92
103
  map: context.typesMap.requests
93
104
  }),
105
+ websockets: buildImportClassDev({
106
+ pluginPackages,
107
+ map: context.typesMap.websockets
108
+ }),
94
109
  operators: {
95
110
  client: buildImportClassDev({
96
111
  pluginPackages,
@@ -30,7 +30,8 @@ function buildImportsProd({ components, context }) {
30
30
  adapters: buildImportClassProd(components.types.auth.adapters),
31
31
  callbacks: buildImportClassProd(components.types.auth.callbacks),
32
32
  events: buildImportClassProd(components.types.auth.events),
33
- providers: buildImportClassProd(components.types.auth.providers)
33
+ providers: buildImportClassProd(components.types.auth.providers),
34
+ strategies: buildImportClassProd(components.types.auth.strategies)
34
35
  },
35
36
  blocks,
36
37
  connections: buildImportClassProd(components.types.connections),
@@ -40,7 +41,9 @@ function buildImportsProd({ components, context }) {
40
41
  context,
41
42
  defaults: defaultIconsProd
42
43
  }),
44
+ notifications: buildImportClassProd(components.types.notifications),
43
45
  requests: buildImportClassProd(components.types.requests),
46
+ websockets: buildImportClassProd(components.types.websockets),
44
47
  operators: {
45
48
  client: buildImportClassProd(components.types.operators.client),
46
49
  server: buildImportClassProd(components.types.operators.server)
@@ -0,0 +1,27 @@
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 generateJsFile from './generateJsFile.js';
16
+ // Single definition of the client _js function prototype. Both the full build
17
+ // (writeJs) and the dev server's per-page fold (getPageJitEnrichment) generate
18
+ // client jsMap module text through here, so the destructured argument list has
19
+ // exactly one source of truth.
20
+ const CLIENT_JS_FUNCTION_PROTOTYPE = `{ actions, args, event, input, location, lowdefyApp, lowdefyGlobal, request, state, urlQuery, user }`;
21
+ function generateClientJsModule(map) {
22
+ return generateJsFile({
23
+ map,
24
+ functionPrototype: CLIENT_JS_FUNCTION_PROTOTYPE
25
+ });
26
+ }
27
+ export default generateClientJsModule;
@@ -12,12 +12,10 @@
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 generateJsFile from './generateJsFile.js';
15
+ */ import generateClientJsModule from './generateClientJsModule.js';
16
+ import generateJsFile from './generateJsFile.js';
16
17
  async function writeJs({ context }) {
17
- await context.writeBuildArtifact('plugins/operators/clientJsMap.js', generateJsFile({
18
- map: context.jsMap.client,
19
- functionPrototype: `{ actions, args, event, input, location, lowdefyApp, lowdefyGlobal, request, state, urlQuery, user }`
20
- }));
18
+ await context.writeBuildArtifact('plugins/operators/clientJsMap.js', generateClientJsModule(context.jsMap.client));
21
19
  await context.writeBuildArtifact('plugins/operators/serverJsMap.js', generateJsFile({
22
20
  map: context.jsMap.server,
23
21
  functionPrototype: `{ args, item, lowdefyApp, payload, secret, state, step, user }`
@@ -0,0 +1,73 @@
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
+ */ /* eslint-disable no-param-reassign */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
17
+ // Validates the mcp block and writes its defaults. Endpoints are matched via
18
+ // the same dual id/endpointId match buildApi uses. Referenced endpoints must
19
+ // carry the metadata an MCP client needs: a description and a payloadSchema
20
+ // (the tool inputSchema).
21
+ function buildMcp({ components }) {
22
+ if (type.isNone(components.mcp)) {
23
+ components.mcp = {};
24
+ }
25
+ const mcp = components.mcp;
26
+ const configKey = mcp['~k'] ?? components['~k'];
27
+ // Agent tools were removed from mcp; schema validation only warns on
28
+ // unknown properties, so a leftover "agents" key would otherwise build
29
+ // silently instead of surfacing the removal.
30
+ if (!type.isNone(mcp.agents)) {
31
+ throw new ConfigError('MCP agent tools are not supported. Remove "mcp.agents" from your config.', {
32
+ configKey
33
+ });
34
+ }
35
+ mcp.name = mcp.name ?? 'lowdefy';
36
+ mcp.version = mcp.version ?? '1.0.0';
37
+ mcp.endpoints = mcp.endpoints ?? [];
38
+ mcp.configured = mcp.endpoints.length > 0;
39
+ const seen = new Set();
40
+ mcp.endpoints.forEach((endpointId)=>{
41
+ if (seen.has(endpointId)) {
42
+ throw new ConfigError(`Duplicate MCP tool "${endpointId}".`, {
43
+ configKey
44
+ });
45
+ }
46
+ seen.add(endpointId);
47
+ const endpoint = (components.api ?? []).find((e)=>e.id === endpointId || e.endpointId === endpointId);
48
+ if (type.isNone(endpoint)) {
49
+ throw new ConfigError(`MCP endpoint "${endpointId}" does not reference a defined api endpoint.`, {
50
+ configKey
51
+ });
52
+ }
53
+ // MCP is an external transport - InternalApi endpoints are not addressable
54
+ // from outside the server, matching the HTTP endpoint route.
55
+ if (endpoint.type === 'InternalApi') {
56
+ throw new ConfigError(`MCP endpoint "${endpointId}" is an InternalApi endpoint. Only "Api" endpoints can be exposed as MCP tools.`, {
57
+ configKey
58
+ });
59
+ }
60
+ if (type.isNone(endpoint.description)) {
61
+ throw new ConfigError(`Endpoint "${endpointId}" is exposed as an MCP tool but does not have a "description".`, {
62
+ configKey: endpoint['~k'] ?? configKey
63
+ });
64
+ }
65
+ if (type.isNone(endpoint.payloadSchema)) {
66
+ throw new ConfigError(`Endpoint "${endpointId}" is exposed as an MCP tool but does not have a "payloadSchema".`, {
67
+ configKey: endpoint['~k'] ?? configKey
68
+ });
69
+ }
70
+ });
71
+ return components;
72
+ }
73
+ export default buildMcp;