@lowdefy/build 5.1.0 → 5.3.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 (64) hide show
  1. package/dist/build/buildAgents.js +249 -0
  2. package/dist/build/buildApi/buildRoutine/countStepTypes.js +3 -0
  3. package/dist/build/buildApi/buildRoutine/setStepId.js +3 -2
  4. package/dist/build/buildApi/buildRoutine/validateStep.js +19 -0
  5. package/dist/build/buildApi/validateEndpoint.js +10 -0
  6. package/dist/build/buildApi/validateStepReferences.js +4 -4
  7. package/dist/build/buildAuth/buildApiAuth.js +2 -1
  8. package/dist/build/buildAuth/buildPageAuth.js +2 -1
  9. package/dist/build/buildAuth/getApiRoles.js +12 -6
  10. package/dist/build/buildAuth/getPageRoles.js +12 -6
  11. package/dist/build/buildAuth/getProtectedApi.js +3 -2
  12. package/dist/build/buildAuth/getProtectedPages.js +3 -2
  13. package/dist/build/buildAuth/matchPattern.js +22 -0
  14. package/dist/build/buildConnections.js +42 -4
  15. package/dist/build/buildImports/buildImportsDev.js +5 -0
  16. package/dist/build/buildImports/buildImportsProd.js +1 -0
  17. package/dist/build/buildJs/jsMapParser.js +25 -12
  18. package/dist/build/buildJs/writeJs.js +2 -2
  19. package/dist/build/buildMenu.js +41 -0
  20. package/dist/build/buildModuleDefs.js +97 -0
  21. package/dist/build/buildModules.js +96 -0
  22. package/dist/build/buildPages/buildBlock/buildBlock.js +2 -2
  23. package/dist/build/buildPages/buildBlock/buildEvents.js +16 -1
  24. package/dist/build/buildPages/buildBlock/buildSubBlocks.js +2 -1
  25. package/dist/build/buildPages/buildBlock/validateBlock.js +3 -3
  26. package/dist/build/buildPages/buildPage.js +1 -0
  27. package/dist/build/buildPages/validateCallApiRefs.js +31 -0
  28. package/dist/build/buildRefs/getModuleRefContent.js +81 -0
  29. package/dist/build/buildRefs/makeRefDefinition.js +6 -0
  30. package/dist/build/buildRefs/walker.js +424 -44
  31. package/dist/build/buildTypes.js +7 -0
  32. package/dist/build/copyAgentFileSystems.js +45 -0
  33. package/dist/build/fetchGitHubModule.js +94 -0
  34. package/dist/build/fetchModules.js +60 -0
  35. package/dist/build/full/buildPages.js +10 -1
  36. package/dist/build/full/updateServerPackageJson.js +1 -0
  37. package/dist/build/full/writePages.js +1 -1
  38. package/dist/build/jit/buildPageJit.js +34 -4
  39. package/dist/build/jit/collectSkeletonSourceFiles.js +8 -0
  40. package/dist/build/jit/createPageRegistry.js +10 -1
  41. package/dist/build/jit/shallowBuild.js +37 -11
  42. package/dist/build/jit/writePageJit.js +2 -2
  43. package/dist/build/jit/writeSourcelessPages.js +1 -1
  44. package/dist/build/parseModuleSource.js +48 -0
  45. package/dist/build/registerModules.js +247 -0
  46. package/dist/build/resolveDepTarget.js +43 -0
  47. package/dist/build/resolveModuleDependencies.js +60 -0
  48. package/dist/build/resolveModuleOperators.js +27 -0
  49. package/dist/build/testSchema.js +22 -11
  50. package/dist/build/writeAgents.js +26 -0
  51. package/dist/build/writePluginImports/writeAgentImports.js +22 -0
  52. package/dist/build/writePluginImports/writeGlobalsCss.js +1 -1
  53. package/dist/build/writePluginImports/writePluginImports.js +5 -0
  54. package/dist/createContext.js +6 -0
  55. package/dist/defaultPackages.js +58 -0
  56. package/dist/defaultTypesMap.js +469 -357
  57. package/dist/index.js +31 -1
  58. package/dist/indexDev.js +3 -1
  59. package/dist/lowdefySchema.js +302 -0
  60. package/dist/scripts/generateDefaultTypes.js +2 -35
  61. package/dist/test-utils/testContext.js +2 -0
  62. package/dist/utils/createPluginTypesMap.js +7 -0
  63. package/package.json +53 -42
  64. package/dist/build/jit/stripPageContent.js +0 -29
@@ -0,0 +1,249 @@
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 path from 'path';
16
+ import fs from 'fs';
17
+ import { type } from '@lowdefy/helpers';
18
+ import { ConfigError, ConfigWarning } from '@lowdefy/errors';
19
+ import { RESERVED_PLATFORM_TOOL_NAMES } from '@lowdefy/ai-utils';
20
+ import countOperators from '../utils/countOperators.js';
21
+ import createCheckDuplicateId from '../utils/createCheckDuplicateId.js';
22
+ function detectCycles(agents) {
23
+ const graph = {};
24
+ for (const agent of agents){
25
+ graph[agent.agentId] = (agent.agents ?? []).map((ref)=>ref.agentId);
26
+ }
27
+ const visited = new Set();
28
+ const inStack = new Set();
29
+ function dfs(id) {
30
+ if (inStack.has(id)) return id;
31
+ if (visited.has(id)) return null;
32
+ visited.add(id);
33
+ inStack.add(id);
34
+ for (const neighbor of graph[id] ?? []){
35
+ const cycleNode = dfs(neighbor);
36
+ if (cycleNode !== null) return cycleNode;
37
+ }
38
+ inStack.delete(id);
39
+ return null;
40
+ }
41
+ for (const id of Object.keys(graph)){
42
+ const cycleNode = dfs(id);
43
+ if (cycleNode !== null) {
44
+ return cycleNode;
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ function buildAgents({ components, context }) {
50
+ if (!type.isArray(components.agents)) {
51
+ return components;
52
+ }
53
+ context.agentIds = new Set();
54
+ const checkDuplicateAgentId = createCheckDuplicateId({
55
+ message: 'Duplicate agentId "{{ id }}".'
56
+ });
57
+ components.agents.forEach((agent)=>{
58
+ const configKey = agent['~k'];
59
+ // Check duplicates
60
+ checkDuplicateAgentId({
61
+ id: agent.id,
62
+ configKey
63
+ });
64
+ // Track type usage for buildTypes validation
65
+ context.typeCounters.agents.increment(agent.type, configKey);
66
+ // Validate connectionId is provided
67
+ if (type.isNone(agent.connectionId)) {
68
+ throw new ConfigError(`Agent connectionId is not defined at "${agent.id}".`, {
69
+ configKey
70
+ });
71
+ }
72
+ // Validate connectionId references an existing connection
73
+ // Connections may have been renamed by buildConnections:
74
+ // connection.connectionId = original id, connection.id = 'connection:' + original id
75
+ const connectionExists = (components.connections ?? []).some((c)=>c.id === agent.connectionId || c.connectionId === agent.connectionId);
76
+ if (!connectionExists) {
77
+ throw new ConfigError(`Agent "${agent.id}" references connectionId "${agent.connectionId}" which does not exist.`, {
78
+ configKey
79
+ });
80
+ }
81
+ // Validate model is defined
82
+ if (type.isNone(agent.properties?.model)) {
83
+ throw new ConfigError(`Agent "model" is not defined at "${agent.id}".`, {
84
+ configKey
85
+ });
86
+ }
87
+ // Normalize tool strings to objects
88
+ agent.tools = (agent.tools ?? []).map((tool)=>{
89
+ if (type.isString(tool)) {
90
+ return {
91
+ endpointId: tool
92
+ };
93
+ }
94
+ return tool;
95
+ });
96
+ // Validate tools reference existing API endpoints with required tool metadata
97
+ agent.tools.forEach((toolConfig)=>{
98
+ if (RESERVED_PLATFORM_TOOL_NAMES.includes(toolConfig.endpointId)) {
99
+ throw new ConfigError(`Agent "${agent.id}" tool "${toolConfig.endpointId}" uses a reserved platform tool name. Reserved: ${RESERVED_PLATFORM_TOOL_NAMES.join(', ')}.`, {
100
+ configKey
101
+ });
102
+ }
103
+ const endpoint = (components.api ?? []).find((e)=>e.id === toolConfig.endpointId || e.endpointId === toolConfig.endpointId);
104
+ if (!endpoint) {
105
+ throw new ConfigError(`Agent "${agent.id}" references tool endpoint "${toolConfig.endpointId}" which does not exist.`, {
106
+ configKey
107
+ });
108
+ }
109
+ if (type.isNone(endpoint.description)) {
110
+ throw new ConfigError(`Endpoint "${toolConfig.endpointId}" is used as an agent tool but does not have a "description".`, {
111
+ configKey: endpoint['~k']
112
+ });
113
+ }
114
+ if (type.isNone(endpoint.payloadSchema)) {
115
+ throw new ConfigError(`Endpoint "${toolConfig.endpointId}" is used as an agent tool but does not have a "payloadSchema".`, {
116
+ configKey: endpoint['~k']
117
+ });
118
+ }
119
+ });
120
+ // Normalize MCP string shorthand to connectionId objects (same pattern as tools)
121
+ agent.mcp = (agent.mcp ?? []).map((mcp)=>{
122
+ if (type.isString(mcp)) {
123
+ return {
124
+ connectionId: mcp
125
+ };
126
+ }
127
+ return mcp;
128
+ });
129
+ // Validate MCP sources
130
+ agent.mcp.forEach((mcpSource, index)=>{
131
+ if (!type.isNone(mcpSource.connectionId)) {
132
+ // Validate connectionId references an existing connection
133
+ const mcpConnectionExists = (components.connections ?? []).some((c)=>c.id === mcpSource.connectionId || c.connectionId === mcpSource.connectionId);
134
+ if (!mcpConnectionExists) {
135
+ throw new ConfigError(`Agent "${agent.id}" "mcp" source at index ${index} references connection "${mcpSource.connectionId}" which does not exist.`, {
136
+ configKey
137
+ });
138
+ }
139
+ } else if (mcpSource.transport === 'stdio') {
140
+ if (type.isNone(mcpSource.command)) {
141
+ throw new ConfigError(`Agent "${agent.id}" "mcp" source at index ${index} uses stdio transport but is missing "command".`, {
142
+ configKey
143
+ });
144
+ }
145
+ } else {
146
+ if (type.isNone(mcpSource.url)) {
147
+ throw new ConfigError(`Agent "${agent.id}" "mcp" source at index ${index} is missing "url".`, {
148
+ configKey
149
+ });
150
+ }
151
+ }
152
+ });
153
+ // Validate hooks reference existing API endpoints
154
+ const hookNames = [
155
+ 'onStart',
156
+ 'onStepStart',
157
+ 'onToolCallStart',
158
+ 'onToolCallFinish',
159
+ 'onStepFinish',
160
+ 'onFinish'
161
+ ];
162
+ hookNames.forEach((hookName)=>{
163
+ (agent.hooks?.[hookName] ?? []).forEach((endpointId)=>{
164
+ const endpoint = (components.api ?? []).find((e)=>e.id === endpointId || e.endpointId === endpointId);
165
+ if (!endpoint) {
166
+ throw new ConfigError(`Agent "${agent.id}" hook "${hookName}" references endpoint "${endpointId}" which does not exist.`, {
167
+ configKey
168
+ });
169
+ }
170
+ });
171
+ });
172
+ // Normalize sub-agent strings to objects (same pattern as tools/mcp)
173
+ agent.agents = (agent.agents ?? []).map((ref)=>{
174
+ if (type.isString(ref)) {
175
+ return {
176
+ agentId: ref
177
+ };
178
+ }
179
+ return ref;
180
+ });
181
+ // Validate fileSystem basePath if present
182
+ if (agent.properties?.fileSystem) {
183
+ const basePath = agent.properties.fileSystem.basePath;
184
+ if (!type.isString(basePath)) {
185
+ throw new ConfigError(`Agent "${agent.id}" fileSystem.basePath is not a string.`, {
186
+ received: basePath,
187
+ configKey
188
+ });
189
+ }
190
+ const resolved = path.resolve(context.directories.config, basePath);
191
+ if (!fs.existsSync(resolved)) {
192
+ throw new ConfigError(`Agent "${agent.id}" fileSystem.basePath "${basePath}" does not exist.`, {
193
+ configKey
194
+ });
195
+ }
196
+ }
197
+ // Rename id to internal format
198
+ agent.agentId = agent.id;
199
+ context.agentIds.add(agent.agentId);
200
+ agent.id = `agent:${agent.agentId}`;
201
+ // Count server operators in properties
202
+ countOperators(agent.properties ?? {}, {
203
+ counter: context.typeCounters.operators.server
204
+ });
205
+ });
206
+ // Second pass: validate sub-agent references (needs all agentIds collected)
207
+ components.agents.forEach((agent)=>{
208
+ const configKey = agent['~k'];
209
+ agent.agents.forEach((subAgentRef)=>{
210
+ // Validate sub-agent reference exists
211
+ if (!context.agentIds.has(subAgentRef.agentId)) {
212
+ throw new ConfigError(`Agent "${agent.agentId}" references sub-agent "${subAgentRef.agentId}" which does not exist.`, {
213
+ configKey
214
+ });
215
+ }
216
+ // Reserved platform tool name guard for sub-agents
217
+ if (RESERVED_PLATFORM_TOOL_NAMES.includes(subAgentRef.agentId)) {
218
+ throw new ConfigError(`Agent "${agent.agentId}" sub-agent "${subAgentRef.agentId}" uses a reserved platform tool name. Reserved: ${RESERVED_PLATFORM_TOOL_NAMES.join(', ')}.`, {
219
+ configKey
220
+ });
221
+ }
222
+ // Check for name collision with endpoint tools
223
+ const hasToolCollision = agent.tools.some((toolConfig)=>toolConfig.endpointId === subAgentRef.agentId);
224
+ if (hasToolCollision) {
225
+ throw new ConfigError(`Agent "${agent.agentId}" sub-agent "${subAgentRef.agentId}" conflicts with an endpoint tool of the same name.`, {
226
+ configKey
227
+ });
228
+ }
229
+ // Warn if sub-agent has tools with confirm: true (unsupported in sub-agent context)
230
+ const subAgent = components.agents.find((a)=>a.agentId === subAgentRef.agentId);
231
+ const hasConfirmTools = (subAgent?.tools ?? []).some((t)=>t.confirm);
232
+ if (hasConfirmTools) {
233
+ context.handleWarning(new ConfigWarning(`Agent "${subAgentRef.agentId}" has tools with confirm: true, but tool approval is not supported in sub-agent context. Tools will auto-execute when called as a sub-agent.`, {
234
+ configKey
235
+ }));
236
+ }
237
+ });
238
+ });
239
+ // Detect circular sub-agent references
240
+ const cycleNode = detectCycles(components.agents);
241
+ if (cycleNode !== null) {
242
+ const agent = components.agents.find((a)=>a.agentId === cycleNode);
243
+ throw new ConfigError(`Circular sub-agent reference detected involving "${cycleNode}".`, {
244
+ configKey: agent?.['~k']
245
+ });
246
+ }
247
+ return components;
248
+ }
249
+ export default buildAgents;
@@ -13,6 +13,9 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ function countStepTypes(step, { typeCounters }) {
16
+ if (step.type === 'CallApi') {
17
+ return;
18
+ }
16
19
  typeCounters.requests.increment(step.type, step['~k']);
17
20
  }
18
21
  export default countStepTypes;
@@ -13,8 +13,9 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ function setStepId(step, { endpointId }) {
16
- step.requestId = step.id;
16
+ step.stepId = step.id;
17
17
  step.endpointId = endpointId;
18
- step.id = `request:${endpointId}:${step.requestId}`;
18
+ const prefix = step.type === 'CallApi' ? 'endpoint' : 'request';
19
+ step.id = `${prefix}:${endpointId}:${step.stepId}`;
19
20
  }
20
21
  export default setStepId;
@@ -50,6 +50,25 @@ function validateStep(step, { endpointId }) {
50
50
  configKey
51
51
  });
52
52
  }
53
+ if (step.type === 'CallApi') {
54
+ if (type.isNone(step.properties?.endpointId)) {
55
+ throw new ConfigError(`Endpoint step "${step.id}" at endpoint "${endpointId}" requires properties.endpointId.`, {
56
+ configKey
57
+ });
58
+ }
59
+ if (!type.isString(step.properties.endpointId) && !type.isObject(step.properties.endpointId)) {
60
+ throw new ConfigError(`Endpoint step "${step.id}" at endpoint "${endpointId}" properties.endpointId is not a string.`, {
61
+ received: step.properties.endpointId,
62
+ configKey
63
+ });
64
+ }
65
+ if (!type.isNone(step.connectionId)) {
66
+ throw new ConfigError(`Endpoint step "${step.id}" at endpoint "${endpointId}" should not have a connectionId.`, {
67
+ configKey
68
+ });
69
+ }
70
+ return;
71
+ }
53
72
  if (type.isUndefined(step.connectionId)) {
54
73
  throw new ConfigError(`Step connectionId missing at endpoint "${endpointId}".`, {
55
74
  configKey
@@ -44,6 +44,16 @@ function validateEndpoint({ endpoint, index, checkDuplicateEndpointId }) {
44
44
  configKey
45
45
  });
46
46
  }
47
+ const validEndpointTypes = [
48
+ 'Api',
49
+ 'InternalApi'
50
+ ];
51
+ if (!validEndpointTypes.includes(endpoint.type)) {
52
+ throw new ConfigError(`Endpoint type "${endpoint.type}" is not valid at "${endpoint.id}". Must be one of: ${validEndpointTypes.join(', ')}.`, {
53
+ received: endpoint.type,
54
+ configKey
55
+ });
56
+ }
47
57
  checkDuplicateEndpointId({
48
58
  id: endpoint.id,
49
59
  configKey
@@ -17,16 +17,16 @@ import { type } from '@lowdefy/helpers';
17
17
  import extractOperatorKey from '../../utils/extractOperatorKey.js';
18
18
  import traverseConfig from '../../utils/traverseConfig.js';
19
19
  // Collect all step IDs from a routine (including nested control structures)
20
- // Note: After buildRoutine, steps have requestId (original id) and id is modified
20
+ // Note: After buildRoutine, steps have stepId (original id) and id is modified
21
21
  function collectStepIds(routine, stepIds) {
22
22
  if (type.isArray(routine)) {
23
23
  routine.forEach((item)=>collectStepIds(item, stepIds));
24
24
  return;
25
25
  }
26
26
  if (type.isObject(routine)) {
27
- // Check if this is a step (has requestId after build, or id before build)
28
- if (routine.requestId) {
29
- stepIds.add(routine.requestId);
27
+ // Check if this is a step (has stepId after build, or id before build)
28
+ if (routine.stepId) {
29
+ stepIds.add(routine.stepId);
30
30
  }
31
31
  // Recurse into all values (handles control structures like :then, :else, :try, :catch)
32
32
  Object.values(routine).forEach((value)=>collectStepIds(value, stepIds));
@@ -16,6 +16,7 @@
16
16
  import { ConfigError } from '@lowdefy/errors';
17
17
  import getApiRoles from './getApiRoles.js';
18
18
  import getProtectedApi from './getProtectedApi.js';
19
+ import { isInPatternList } from './matchPattern.js';
19
20
  function buildApiAuth({ components, context }) {
20
21
  const protectedApiEndpoints = getProtectedApi({
21
22
  components
@@ -29,7 +30,7 @@ function buildApiAuth({ components, context }) {
29
30
  }
30
31
  (components.api || []).forEach((endpoint)=>{
31
32
  if (apiRoles[endpoint.id]) {
32
- if (configPublicApi.includes(endpoint.id)) {
33
+ if (isInPatternList(endpoint.id, configPublicApi)) {
33
34
  throw new ConfigError(`Endpoint "${endpoint.id}" is both protected by roles and public.`, {
34
35
  received: apiRoles[endpoint.id],
35
36
  configKey: endpoint['~k']
@@ -16,6 +16,7 @@
16
16
  import { ConfigError } from '@lowdefy/errors';
17
17
  import getPageRoles from './getPageRoles.js';
18
18
  import getProtectedPages from './getProtectedPages.js';
19
+ import { isInPatternList } from './matchPattern.js';
19
20
  function buildPageAuth({ components, context }) {
20
21
  const protectedPages = getProtectedPages({
21
22
  components
@@ -36,7 +37,7 @@ function buildPageAuth({ components, context }) {
36
37
  return;
37
38
  }
38
39
  if (pageRoles[page.id]) {
39
- if (configPublicPages.includes(page.id)) {
40
+ if (isInPatternList(page.id, configPublicPages)) {
40
41
  throw new ConfigError(`Page "${page.id}" is both protected by roles and public.`, {
41
42
  received: pageRoles[page.id],
42
43
  configKey: page['~k']
@@ -12,15 +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
- */ function getApiRoles({ components }) {
15
+ */ import { matchesPattern } from './matchPattern.js';
16
+ function getApiRoles({ components }) {
16
17
  const roles = components.auth.api.roles;
18
+ const endpointIds = (components.api ?? []).map((e)=>e.id);
17
19
  const apiRoles = {};
18
20
  Object.keys(roles).forEach((roleName)=>{
19
- roles[roleName].forEach((endpointId)=>{
20
- if (!apiRoles[endpointId]) {
21
- apiRoles[endpointId] = new Set();
22
- }
23
- apiRoles[endpointId].add(roleName);
21
+ roles[roleName].forEach((pattern)=>{
22
+ endpointIds.forEach((endpointId)=>{
23
+ if (matchesPattern(endpointId, pattern)) {
24
+ if (!apiRoles[endpointId]) {
25
+ apiRoles[endpointId] = new Set();
26
+ }
27
+ apiRoles[endpointId].add(roleName);
28
+ }
29
+ });
24
30
  });
25
31
  });
26
32
  Object.keys(apiRoles).forEach((endpointId)=>{
@@ -12,15 +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
- */ function getPageRoles({ components }) {
15
+ */ import { matchesPattern } from './matchPattern.js';
16
+ function getPageRoles({ components }) {
16
17
  const roles = components.auth.pages.roles;
18
+ const pageIds = (components.pages ?? []).map((p)=>p.id);
17
19
  const pageRoles = {};
18
20
  Object.keys(roles).forEach((roleName)=>{
19
- roles[roleName].forEach((pageId)=>{
20
- if (!pageRoles[pageId]) {
21
- pageRoles[pageId] = new Set();
22
- }
23
- pageRoles[pageId].add(roleName);
21
+ roles[roleName].forEach((pattern)=>{
22
+ pageIds.forEach((pageId)=>{
23
+ if (matchesPattern(pageId, pattern)) {
24
+ if (!pageRoles[pageId]) {
25
+ pageRoles[pageId] = new Set();
26
+ }
27
+ pageRoles[pageId].add(roleName);
28
+ }
29
+ });
24
30
  });
25
31
  });
26
32
  Object.keys(pageRoles).forEach((pageId)=>{
@@ -13,15 +13,16 @@
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 { isInPatternList } from './matchPattern.js';
16
17
  function getProtectedApi({ components }) {
17
18
  const endpointIds = (components.api || []).map((endpoint)=>endpoint.id);
18
19
  let protectedApi = [];
19
20
  if (type.isArray(components.auth.api.public)) {
20
- protectedApi = endpointIds.filter((endpointId)=>!components.auth.api.public.includes(endpointId));
21
+ protectedApi = endpointIds.filter((endpointId)=>!isInPatternList(endpointId, components.auth.api.public));
21
22
  } else if (components.auth.api.protected === true) {
22
23
  protectedApi = endpointIds;
23
24
  } else if (type.isArray(components.auth.api.protected)) {
24
- protectedApi = components.auth.api.protected;
25
+ protectedApi = endpointIds.filter((endpointId)=>isInPatternList(endpointId, components.auth.api.protected));
25
26
  }
26
27
  return protectedApi;
27
28
  }
@@ -13,15 +13,16 @@
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 { isInPatternList } from './matchPattern.js';
16
17
  function getProtectedPages({ components }) {
17
18
  const pageIds = (components.pages || []).map((page)=>page.id);
18
19
  let protectedPages = [];
19
20
  if (type.isArray(components.auth.pages.public)) {
20
- protectedPages = pageIds.filter((pageId)=>!components.auth.pages.public.includes(pageId));
21
+ protectedPages = pageIds.filter((pageId)=>!isInPatternList(pageId, components.auth.pages.public));
21
22
  } else if (components.auth.pages.protected === true) {
22
23
  protectedPages = pageIds;
23
24
  } else if (type.isArray(components.auth.pages.protected)) {
24
- protectedPages = components.auth.pages.protected;
25
+ protectedPages = pageIds.filter((pageId)=>isInPatternList(pageId, components.auth.pages.protected));
25
26
  }
26
27
  return protectedPages;
27
28
  }
@@ -0,0 +1,22 @@
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 picomatch from 'picomatch';
16
+ function matchesPattern(id, pattern) {
17
+ return picomatch.isMatch(id, pattern);
18
+ }
19
+ function isInPatternList(id, patternList) {
20
+ return patternList.some((pattern)=>matchesPattern(id, pattern));
21
+ }
22
+ export { matchesPattern, isInPatternList };
@@ -12,20 +12,58 @@
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 countOperators from '../utils/countOperators.js';
15
+ */ import { type } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
17
+ import collectExceptions from '../utils/collectExceptions.js';
18
+ import countOperators from '../utils/countOperators.js';
16
19
  import createCheckDuplicateId from '../utils/createCheckDuplicateId.js';
17
20
  import validateId from '../utils/validateId.js';
21
+ function validateConnection(connection, context) {
22
+ const configKey = connection?.['~k'];
23
+ if (!type.isObject(connection)) {
24
+ collectExceptions(context, new ConfigError('Connection should be an object.', {
25
+ received: connection,
26
+ configKey
27
+ }));
28
+ return false;
29
+ }
30
+ if (type.isUndefined(connection.id)) {
31
+ collectExceptions(context, new ConfigError('Connection id missing.', {
32
+ configKey
33
+ }));
34
+ return false;
35
+ }
36
+ if (!type.isString(connection.id)) {
37
+ collectExceptions(context, new ConfigError('Connection id is not a string.', {
38
+ received: connection.id,
39
+ configKey
40
+ }));
41
+ return false;
42
+ }
43
+ if (type.isNone(connection.type)) {
44
+ collectExceptions(context, new ConfigError(`Connection type is not defined at connection "${connection.id}".`, {
45
+ configKey
46
+ }));
47
+ return false;
48
+ }
49
+ if (!type.isString(connection.type)) {
50
+ collectExceptions(context, new ConfigError(`Connection type is not a string at connection "${connection.id}".`, {
51
+ received: connection.type,
52
+ configKey
53
+ }));
54
+ return false;
55
+ }
56
+ return true;
57
+ }
18
58
  function buildConnections({ components, context }) {
19
59
  // Store connection IDs for validation in buildRequests
20
60
  context.connectionIds = new Set();
21
- // Schema validates: id required, id is string, type is string
22
- // Only check for duplicates here (schema can't do that)
23
61
  const checkDuplicateConnectionId = createCheckDuplicateId({
24
62
  message: 'Duplicate connectionId "{{ id }}".'
25
63
  });
26
64
  (components.connections ?? []).forEach((connection)=>{
65
+ if (!validateConnection(connection, context)) return;
27
66
  const configKey = connection['~k'];
28
- // Check duplicates (schema can't validate this)
29
67
  checkDuplicateConnectionId({
30
68
  id: connection.id,
31
69
  configKey
@@ -22,6 +22,7 @@ function getPluginPackages({ components }) {
22
22
  });
23
23
  }
24
24
  getPackages(components.types.actions);
25
+ getPackages(components.types.agents);
25
26
  getPackages(components.types.auth.adapters);
26
27
  getPackages(components.types.auth.callbacks);
27
28
  getPackages(components.types.auth.events);
@@ -53,6 +54,10 @@ function buildImportsDev({ components, context }) {
53
54
  pluginPackages,
54
55
  map: context.typesMap.actions
55
56
  }),
57
+ agents: buildImportClassDev({
58
+ pluginPackages,
59
+ map: context.typesMap.agents
60
+ }),
56
61
  auth: {
57
62
  adapters: buildImportClassDev({
58
63
  pluginPackages,
@@ -25,6 +25,7 @@ function buildImportsProd({ components, context }) {
25
25
  const blocks = buildImportClassProd(components.types.blocks);
26
26
  return {
27
27
  actions: buildImportClassProd(components.types.actions),
28
+ agents: buildImportClassProd(components.types.agents),
28
29
  auth: {
29
30
  adapters: buildImportClassProd(components.types.auth.adapters),
30
31
  callbacks: buildImportClassProd(components.types.auth.callbacks),
@@ -15,12 +15,10 @@
15
15
  */ import { ConfigError } from '@lowdefy/errors';
16
16
  import { serializer, type } from '@lowdefy/helpers';
17
17
  import crypto from 'crypto';
18
- function makeHash({ jsMap, env, value }) {
18
+ function hashFn({ jsMap, env, value }) {
19
19
  const hash = crypto.createHash('sha1').update(value).digest('base64');
20
20
  jsMap[env][hash] = value;
21
- return {
22
- _js: hash
23
- };
21
+ return hash;
24
22
  }
25
23
  function JsMapParser({ input, jsMap, env }) {
26
24
  if (!jsMap[env]) {
@@ -31,15 +29,30 @@ function JsMapParser({ input, jsMap, env }) {
31
29
  if (Object.keys(value).length !== 1) return value;
32
30
  const key = Object.keys(value)[0];
33
31
  if (key !== '_js') return value;
34
- if (!type.isString(value[key])) {
35
- throw new ConfigError(`_js operator expects the JavaScript definition as a string. Received ${JSON.stringify(value[key])}.`, {
36
- configKey: value['~k']
37
- });
32
+ const inner = value[key];
33
+ if (type.isString(inner)) {
34
+ return {
35
+ _js: hashFn({
36
+ jsMap,
37
+ env,
38
+ value: inner
39
+ })
40
+ };
41
+ }
42
+ if (type.isObject(inner) && type.isString(inner.fn)) {
43
+ return {
44
+ _js: {
45
+ fn: hashFn({
46
+ jsMap,
47
+ env,
48
+ value: inner.fn
49
+ }),
50
+ args: inner.args
51
+ }
52
+ };
38
53
  }
39
- return makeHash({
40
- jsMap,
41
- env,
42
- value: value[key]
54
+ throw new ConfigError(`_js operator expects a JavaScript string or { fn: string, args?: object }. Received ${JSON.stringify(inner)}.`, {
55
+ configKey: value['~k']
43
56
  });
44
57
  };
45
58
  return serializer.copy(input, {
@@ -16,11 +16,11 @@
16
16
  async function writeJs({ context }) {
17
17
  await context.writeBuildArtifact('plugins/operators/clientJsMap.js', generateJsFile({
18
18
  map: context.jsMap.client,
19
- functionPrototype: `{ actions, event, input, location, lowdefyGlobal, request, state, urlQuery, user }`
19
+ functionPrototype: `{ actions, args, event, input, location, lowdefyGlobal, request, state, urlQuery, user }`
20
20
  }));
21
21
  await context.writeBuildArtifact('plugins/operators/serverJsMap.js', generateJsFile({
22
22
  map: context.jsMap.server,
23
- functionPrototype: `{ item, payload, secrets, state, step, user }`
23
+ functionPrototype: `{ args, item, payload, secrets, state, step, user }`
24
24
  }));
25
25
  }
26
26
  export default writeJs;