@lowdefy/api 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 (29) hide show
  1. package/dist/context/createApiContext.js +0 -1
  2. package/dist/context/createEvaluateOperators.js +5 -5
  3. package/dist/index.js +3 -2
  4. package/dist/routes/agent/callAgent.js +217 -0
  5. package/dist/routes/agent/getAgentConfig.js +30 -0
  6. package/dist/routes/agent/getAgentResolver.js +33 -0
  7. package/dist/routes/auth/getNextAuthConfig.js +2 -2
  8. package/dist/routes/{request → connections}/getConnectionConfig.js +6 -7
  9. package/dist/routes/endpoints/addStepResult.js +3 -3
  10. package/dist/routes/endpoints/callEndpoint.js +16 -2
  11. package/dist/routes/endpoints/control/controlFor.js +3 -1
  12. package/dist/routes/endpoints/control/controlIf.js +3 -1
  13. package/dist/routes/endpoints/control/controlLog.js +6 -2
  14. package/dist/routes/endpoints/control/controlParallelFor.js +3 -1
  15. package/dist/routes/endpoints/control/controlReject.js +6 -2
  16. package/dist/routes/endpoints/control/controlReturn.js +3 -1
  17. package/dist/routes/endpoints/control/controlSetState.js +18 -2
  18. package/dist/routes/endpoints/control/controlSwitch.js +3 -1
  19. package/dist/routes/endpoints/control/controlThrow.js +6 -2
  20. package/dist/routes/endpoints/handleEndpointCall.js +79 -0
  21. package/dist/routes/endpoints/handleRequest.js +8 -5
  22. package/dist/routes/endpoints/runRoutine.js +7 -1
  23. package/dist/routes/endpoints/test/runTest.js +7 -10
  24. package/dist/routes/page/getPageConfig.js +1 -1
  25. package/dist/routes/request/callRequest.js +9 -5
  26. package/dist/routes/request/callRequestResolver.js +7 -5
  27. package/dist/routes/request/evaluateOperators.js +7 -3
  28. package/package.json +8 -8
  29. /package/dist/routes/{request → connections}/getConnection.js +0 -0
@@ -16,7 +16,6 @@
16
16
  import createReadConfigFile from './createReadConfigFile.js';
17
17
  function createApiContext(context) {
18
18
  context.state = {};
19
- context.steps = {};
20
19
  context.user = context?.session?.user;
21
20
  context.authorize = createAuthorize(context);
22
21
  context.readConfigFile = createReadConfigFile(context);
@@ -14,21 +14,21 @@
14
14
  limitations under the License.
15
15
  */ import { ServerParser } from '@lowdefy/operators';
16
16
  function createEvaluateOperators(context) {
17
- const { jsMap, operators, payload, secrets, state, steps, user } = context;
17
+ const { jsMap, operators, secrets, state, user } = context;
18
18
  const operatorsParser = new ServerParser({
19
19
  jsMap,
20
20
  operators,
21
- payload,
22
21
  secrets,
23
22
  state,
24
- steps,
25
23
  user
26
24
  });
27
- function evaluateOperators({ input, items, location }) {
25
+ function evaluateOperators({ input, items, location, payload, steps }) {
28
26
  const { output, errors } = operatorsParser.parse({
29
27
  input,
30
28
  items,
31
- location
29
+ location,
30
+ payload,
31
+ steps
32
32
  });
33
33
  if (errors.length > 0) {
34
34
  throw errors[0];
package/dist/index.js CHANGED
@@ -12,7 +12,8 @@
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 callEndpoint from './routes/endpoints/callEndpoint.js';
15
+ */ import callAgent from './routes/agent/callAgent.js';
16
+ import callEndpoint from './routes/endpoints/callEndpoint.js';
16
17
  import callRequest from './routes/request/callRequest.js';
17
18
  import createApiContext from './context/createApiContext.js';
18
19
  import createSessionCallback from './routes/auth/callbacks/createSessionCallback.js';
@@ -21,4 +22,4 @@ import getNextAuthConfig from './routes/auth/getNextAuthConfig.js';
21
22
  import getPageConfig from './routes/page/getPageConfig.js';
22
23
  import getRootConfig from './routes/rootConfig/getRootConfig.js';
23
24
  import logClientError from './routes/log/logClientError.js';
24
- export { callEndpoint, callRequest, createApiContext, createSessionCallback, getHomeAndMenus, getNextAuthConfig, getPageConfig, getRootConfig, logClientError };
25
+ export { callAgent, callEndpoint, callRequest, createApiContext, createSessionCallback, getHomeAndMenus, getNextAuthConfig, getPageConfig, getRootConfig, logClientError };
@@ -0,0 +1,217 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { serializer, type } from '@lowdefy/helpers';
16
+ import createEvaluateOperators from '../../context/createEvaluateOperators.js';
17
+ import authorizeApiEndpoint from '../endpoints/authorizeApiEndpoint.js';
18
+ import getEndpointConfig from '../endpoints/getEndpointConfig.js';
19
+ import runRoutine from '../endpoints/runRoutine.js';
20
+ import getAgentConfig from './getAgentConfig.js';
21
+ import getAgentResolver from './getAgentResolver.js';
22
+ import getConnectionConfig from '../connections/getConnectionConfig.js';
23
+ import getConnection from '../connections/getConnection.js';
24
+ async function callAgent(context, { agentId, pageId, messages, conversationId, urlQuery, sharedState }) {
25
+ const { logger } = context;
26
+ context.pageId = pageId;
27
+ context.evaluateOperators = createEvaluateOperators(context);
28
+ logger.debug({
29
+ event: 'debug_agent',
30
+ agentId,
31
+ pageId
32
+ });
33
+ const agentConfig = await getAgentConfig(context, {
34
+ agentId
35
+ });
36
+ const agentContext = {
37
+ conversationId: conversationId ?? undefined,
38
+ pageId,
39
+ sharedState: sharedState ?? {},
40
+ urlQuery: urlQuery ?? {},
41
+ userId: context.user?.sub ?? context.user?.id ?? null
42
+ };
43
+ // Evaluate operators in agent properties (e.g. _user, _secret, _payload)
44
+ agentConfig.properties = context.evaluateOperators({
45
+ input: agentConfig.properties ?? {},
46
+ location: agentConfig.agentId,
47
+ payload: agentContext,
48
+ steps: {}
49
+ });
50
+ // Load connection config from build artifacts using agent's connectionId
51
+ const connectionConfig = await getConnectionConfig(context, {
52
+ connectionId: agentConfig.connectionId,
53
+ configKey: agentConfig['~k']
54
+ });
55
+ // Get connection plugin from registry
56
+ const connection = getConnection(context, {
57
+ connectionConfig
58
+ });
59
+ // Evaluate operators in connection properties
60
+ const connectionProperties = context.evaluateOperators({
61
+ input: connectionConfig.properties || {},
62
+ location: connectionConfig.connectionId,
63
+ payload: {},
64
+ steps: {}
65
+ });
66
+ // Create connection instance (e.g., Anthropic provider)
67
+ const connectionInstance = connection.create({
68
+ connection: connectionProperties
69
+ });
70
+ // Get agent type from plugin registry
71
+ const agentType = getAgentResolver(context, {
72
+ agentConfig
73
+ });
74
+ // Build resolver context with callEndpoint that allows InternalApi endpoints
75
+ const resolverContext = {
76
+ agentContext,
77
+ evaluateOperators: (input)=>context.evaluateOperators({
78
+ input,
79
+ location: agentConfig.agentId,
80
+ payload: agentContext,
81
+ steps: {}
82
+ }),
83
+ callEndpoint: async (endpointId, { payload, abortSignal })=>{
84
+ const endpointConfig = await getEndpointConfig(context, {
85
+ endpointId
86
+ });
87
+ authorizeApiEndpoint(context, {
88
+ endpointConfig
89
+ });
90
+ const routineContext = {
91
+ steps: {},
92
+ payload: payload ?? {},
93
+ arrayIndices: [],
94
+ items: {},
95
+ endpointDepth: 0
96
+ };
97
+ const { error, response, status } = await runRoutine(context, routineContext, {
98
+ routine: endpointConfig.routine
99
+ });
100
+ const success = ![
101
+ 'error',
102
+ 'reject'
103
+ ].includes(status);
104
+ return {
105
+ error: serializer.serialize(error),
106
+ response: serializer.serialize(response),
107
+ status: success ? 'success' : status,
108
+ success
109
+ };
110
+ },
111
+ getEndpointConfig: async ({ endpointId })=>{
112
+ return getEndpointConfig(context, {
113
+ endpointId
114
+ });
115
+ },
116
+ getAgentConfig: async ({ agentId })=>{
117
+ return getAgentConfig(context, {
118
+ agentId
119
+ });
120
+ },
121
+ getConnectionForAgent: async ({ agentConfig: subAgentConfig })=>{
122
+ const subConnectionConfig = await getConnectionConfig(context, {
123
+ connectionId: subAgentConfig.connectionId,
124
+ configKey: subAgentConfig['~k']
125
+ });
126
+ const subConnection = getConnection(context, {
127
+ connectionConfig: subConnectionConfig
128
+ });
129
+ const subConnectionProperties = context.evaluateOperators({
130
+ input: subConnectionConfig.properties || {},
131
+ location: subConnectionConfig.connectionId,
132
+ payload: {},
133
+ steps: {}
134
+ });
135
+ return subConnection.create({
136
+ connection: subConnectionProperties
137
+ });
138
+ },
139
+ resolveMcpSources: async ({ agentConfig: subAgentConfig })=>{
140
+ const resolvedMcp = [];
141
+ for (const mcpSource of subAgentConfig.mcp ?? []){
142
+ if (!type.isNone(mcpSource.connectionId)) {
143
+ const mcpConnConfig = await getConnectionConfig(context, {
144
+ connectionId: mcpSource.connectionId,
145
+ configKey: subAgentConfig['~k']
146
+ });
147
+ const mcpConnection = getConnection(context, {
148
+ connectionConfig: mcpConnConfig
149
+ });
150
+ const mcpConnProps = context.evaluateOperators({
151
+ input: mcpConnConfig.properties || {},
152
+ location: mcpConnConfig.connectionId,
153
+ payload: {},
154
+ steps: {}
155
+ });
156
+ const mcpConfig = mcpConnection.create({
157
+ connection: mcpConnProps
158
+ });
159
+ const { connectionId: _, ...overrides } = mcpSource;
160
+ resolvedMcp.push({
161
+ ...mcpConfig,
162
+ ...overrides
163
+ });
164
+ } else {
165
+ resolvedMcp.push(mcpSource);
166
+ }
167
+ }
168
+ return resolvedMcp;
169
+ }
170
+ };
171
+ // Resolve MCP connection references to inline config.
172
+ // Agent-level overrides (like confirm) may still contain operators —
173
+ // handleAgentChat evaluates those via its existing evaluateOperators call.
174
+ const resolvedMcp = [];
175
+ for (const mcpSource of agentConfig.mcp ?? []){
176
+ if (!type.isNone(mcpSource.connectionId)) {
177
+ const mcpConnConfig = await getConnectionConfig(context, {
178
+ connectionId: mcpSource.connectionId,
179
+ configKey: agentConfig['~k']
180
+ });
181
+ const mcpConnection = getConnection(context, {
182
+ connectionConfig: mcpConnConfig
183
+ });
184
+ const mcpConnProps = context.evaluateOperators({
185
+ input: mcpConnConfig.properties || {},
186
+ location: mcpConnConfig.connectionId,
187
+ payload: {},
188
+ steps: {}
189
+ });
190
+ const mcpConfig = mcpConnection.create({
191
+ connection: mcpConnProps
192
+ });
193
+ // Merge: connection properties as base, agent-level overrides on top
194
+ const { connectionId: _, ...overrides } = mcpSource;
195
+ resolvedMcp.push({
196
+ ...mcpConfig,
197
+ ...overrides
198
+ });
199
+ } else {
200
+ resolvedMcp.push(mcpSource);
201
+ }
202
+ }
203
+ agentConfig.mcp = resolvedMcp;
204
+ // Call the agent resolver
205
+ const { response } = await agentType.resolver({
206
+ connection: connectionInstance,
207
+ properties: {
208
+ agent: agentConfig,
209
+ messages
210
+ },
211
+ context: resolverContext
212
+ });
213
+ return {
214
+ response
215
+ };
216
+ }
217
+ export default callAgent;
@@ -0,0 +1,30 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { ConfigError } from '@lowdefy/errors';
16
+ async function getAgentConfig({ logger, readConfigFile }, { agentId }) {
17
+ const agent = await readConfigFile(`agents/${agentId}.json`);
18
+ if (!agent) {
19
+ const err = new ConfigError(`Agent "${agentId}" does not exist.`);
20
+ logger.debug({
21
+ params: {
22
+ agentId
23
+ },
24
+ err
25
+ }, err.message);
26
+ throw err;
27
+ }
28
+ return agent;
29
+ }
30
+ export default getAgentConfig;
@@ -0,0 +1,33 @@
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 { ConfigError } from '@lowdefy/errors';
16
+ function getAgentResolver({ agents, logger }, { agentConfig }) {
17
+ const agentType = agents[agentConfig.type];
18
+ if (!agentType) {
19
+ const err = new ConfigError(`Agent type "${agentConfig.type}" can not be found.`, {
20
+ configKey: agentConfig['~k']
21
+ });
22
+ logger.debug({
23
+ params: {
24
+ id: agentConfig.agentId,
25
+ type: agentConfig.type
26
+ },
27
+ err
28
+ }, err.message);
29
+ throw err;
30
+ }
31
+ return agentType;
32
+ }
33
+ export default getAgentResolver;
@@ -27,13 +27,13 @@ function getNextAuthConfig({ authJson, logger, plugins, secrets }) {
27
27
  operators: {
28
28
  _secret
29
29
  },
30
- payload: {},
31
30
  secrets,
32
31
  user: {}
33
32
  });
34
33
  const { output: authConfig, errors: operatorErrors } = operatorsParser.parse({
35
34
  input: authJson,
36
- location: 'auth'
35
+ location: 'auth',
36
+ payload: {}
37
37
  });
38
38
  if (operatorErrors.length > 0) {
39
39
  throw operatorErrors[0];
@@ -13,16 +13,15 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { ConfigError } from '@lowdefy/errors';
16
- async function getConnectionConfig({ logger, readConfigFile }, { requestConfig }) {
17
- const { connectionId, requestId } = requestConfig;
16
+ async function getConnectionConfig({ logger, readConfigFile }, { connectionId, configKey }) {
18
17
  let err;
19
18
  if (!connectionId) {
20
- err = new ConfigError(`Request "${requestId}" does not specify a connection.`, {
21
- configKey: requestConfig['~k']
19
+ err = new ConfigError('Connection id is missing.', {
20
+ configKey
22
21
  });
23
22
  logger.debug({
24
23
  params: {
25
- requestId
24
+ connectionId
26
25
  },
27
26
  err
28
27
  }, err.message);
@@ -31,11 +30,11 @@ async function getConnectionConfig({ logger, readConfigFile }, { requestConfig }
31
30
  const connection = await readConfigFile(`connections/${connectionId}.json`);
32
31
  if (!connection) {
33
32
  err = new ConfigError(`Connection "${connectionId}" does not exist.`, {
34
- configKey: requestConfig['~k']
33
+ configKey
35
34
  });
36
35
  logger.debug({
37
36
  params: {
38
- requestId
37
+ connectionId
39
38
  },
40
39
  err
41
40
  }, err.message);
@@ -13,11 +13,11 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { set } from '@lowdefy/helpers';
16
- function addStepResult(context, { arrayIndices }, { result, stepId }) {
16
+ function addStepResult(context, routineContext, { result, stepId }) {
17
17
  const key = [
18
18
  stepId,
19
- ...arrayIndices
19
+ ...routineContext.arrayIndices
20
20
  ].join('.');
21
- set(context.steps, key, result);
21
+ set(routineContext.steps, key, result);
22
22
  }
23
23
  export default addStepResult;
@@ -13,6 +13,7 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { serializer } from '@lowdefy/helpers';
16
+ import { ConfigError } from '@lowdefy/errors';
16
17
  import authorizeApiEndpoint from './authorizeApiEndpoint.js';
17
18
  import createEvaluateOperators from '../../context/createEvaluateOperators.js';
18
19
  import getEndpointConfig from './getEndpointConfig.js';
@@ -22,7 +23,6 @@ async function callEndpoint(context, { blockId, endpointId, pageId, payload }) {
22
23
  context.blockId = blockId;
23
24
  context.endpointId = endpointId;
24
25
  context.pageId = pageId;
25
- context.payload = serializer.deserialize(payload);
26
26
  context.evaluateOperators = createEvaluateOperators(context);
27
27
  logger.debug({
28
28
  event: 'debug_endpoint',
@@ -34,12 +34,26 @@ async function callEndpoint(context, { blockId, endpointId, pageId, payload }) {
34
34
  const endpointConfig = await getEndpointConfig(context, {
35
35
  endpointId
36
36
  });
37
+ // Block HTTP access to InternalApi endpoints — same error as missing endpoint
38
+ if (endpointConfig.type === 'InternalApi') {
39
+ const err = new ConfigError(`API Endpoint "${endpointId}" does not exist.`);
40
+ logger.debug({
41
+ params: {
42
+ endpointId
43
+ },
44
+ err
45
+ }, err.message);
46
+ throw err;
47
+ }
37
48
  authorizeApiEndpoint(context, {
38
49
  endpointConfig
39
50
  });
40
51
  const routineContext = {
52
+ steps: {},
53
+ payload: serializer.deserialize(payload),
41
54
  arrayIndices: [],
42
- items: {}
55
+ items: {},
56
+ endpointDepth: 0
43
57
  };
44
58
  const { error, response, status } = await runRoutine(context, routineContext, {
45
59
  routine: endpointConfig.routine
@@ -24,7 +24,9 @@ async function controlFor(context, routineContext, { control }) {
24
24
  const array = evaluateOperators({
25
25
  input: control[':in'],
26
26
  items,
27
- location: control['~k'] ?? ':for'
27
+ location: control['~k'] ?? ':for',
28
+ steps: routineContext.steps,
29
+ payload: routineContext.payload
28
30
  });
29
31
  logger.debug({
30
32
  event: 'debug_control_for',
@@ -19,7 +19,9 @@ async function controlIf(context, routineContext, { control }) {
19
19
  const evaluatedIf = evaluateOperators({
20
20
  input: control[':if'],
21
21
  items,
22
- location: control['~k'] ?? ':if'
22
+ location: control['~k'] ?? ':if',
23
+ steps: routineContext.steps,
24
+ payload: routineContext.payload
23
25
  });
24
26
  logger.debug({
25
27
  event: 'debug_control_if',
@@ -24,12 +24,16 @@ async function controlLog(context, routineContext, { control }) {
24
24
  const log = evaluateOperators({
25
25
  input: control[':log'],
26
26
  items,
27
- location
27
+ location,
28
+ steps: routineContext.steps,
29
+ payload: routineContext.payload
28
30
  });
29
31
  const logLevel = evaluateOperators({
30
32
  input: control[':level'],
31
33
  items,
32
- location
34
+ location,
35
+ steps: routineContext.steps,
36
+ payload: routineContext.payload
33
37
  }) ?? 'info';
34
38
  if (!type.isString(logLevel)) {
35
39
  throw new ConfigError(`Invalid :log in endpoint "${endpointId}" - :level must be a string.`, {
@@ -24,7 +24,9 @@ async function controlParallelFor(context, routineContext, { control }) {
24
24
  const array = evaluateOperators({
25
25
  input: control[':in'],
26
26
  items,
27
- location: control['~k'] ?? ':parallel_for'
27
+ location: control['~k'] ?? ':parallel_for',
28
+ steps: routineContext.steps,
29
+ payload: routineContext.payload
28
30
  });
29
31
  logger.debug({
30
32
  event: 'debug_control_parallel',
@@ -19,12 +19,16 @@
19
19
  const message = evaluateOperators({
20
20
  input: control[':reject'],
21
21
  items,
22
- location
22
+ location,
23
+ steps: routineContext.steps,
24
+ payload: routineContext.payload
23
25
  });
24
26
  const cause = evaluateOperators({
25
27
  input: control[':cause'],
26
28
  items,
27
- location
29
+ location,
30
+ steps: routineContext.steps,
31
+ payload: routineContext.payload
28
32
  });
29
33
  const error = new Error(message, {
30
34
  cause
@@ -18,7 +18,9 @@
18
18
  const response = evaluateOperators({
19
19
  input: control[':return'],
20
20
  items,
21
- location: control['~k'] ?? ':return'
21
+ location: control['~k'] ?? ':return',
22
+ steps: routineContext.steps,
23
+ payload: routineContext.payload
22
24
  });
23
25
  context.logger.debug({
24
26
  event: 'debug_control_return',
@@ -1,11 +1,27 @@
1
- import { set } from '@lowdefy/helpers';
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 { set } from '@lowdefy/helpers';
2
16
  function controlSetState(context, routineContext, { control }) {
3
17
  const { logger, evaluateOperators } = context;
4
18
  const { items } = routineContext;
5
19
  const evaluatedSetState = evaluateOperators({
6
20
  input: control[':set_state'],
7
21
  items,
8
- location: control['~k'] ?? ':set_state'
22
+ location: control['~k'] ?? ':set_state',
23
+ steps: routineContext.steps,
24
+ payload: routineContext.payload
9
25
  });
10
26
  logger.debug({
11
27
  event: 'debug_control_set_state',
@@ -24,7 +24,9 @@ async function controlSwitch(context, routineContext, { control }) {
24
24
  const evaluatedCase = evaluateOperators({
25
25
  input: caseObj[':case'],
26
26
  items,
27
- location: caseObj['~k'] ?? control['~k'] ?? ':switch'
27
+ location: caseObj['~k'] ?? control['~k'] ?? ':switch',
28
+ steps: routineContext.steps,
29
+ payload: routineContext.payload
28
30
  });
29
31
  logger.debug({
30
32
  event: 'debug_control_switch_case',
@@ -19,12 +19,16 @@
19
19
  const message = evaluateOperators({
20
20
  input: control[':throw'],
21
21
  items,
22
- location
22
+ location,
23
+ steps: routineContext.steps,
24
+ payload: routineContext.payload
23
25
  });
24
26
  const cause = evaluateOperators({
25
27
  input: control[':cause'],
26
28
  items,
27
- location
29
+ location,
30
+ steps: routineContext.steps,
31
+ payload: routineContext.payload
28
32
  });
29
33
  const error = new Error(message, {
30
34
  cause
@@ -0,0 +1,79 @@
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 { ConfigError } from '@lowdefy/errors';
16
+ import addStepResult from './addStepResult.js';
17
+ import authorizeApiEndpoint from './authorizeApiEndpoint.js';
18
+ import getEndpointConfig from './getEndpointConfig.js';
19
+ import runRoutine from './runRoutine.js';
20
+ async function handleEndpointCall(context, routineContext, { step }) {
21
+ const { logger, evaluateOperators } = context;
22
+ logger.debug({
23
+ event: 'debug_start_endpoint_call',
24
+ step
25
+ });
26
+ // Evaluate operators in step.properties (resolves endpointId, payload)
27
+ const evaluatedProperties = evaluateOperators({
28
+ input: step.properties,
29
+ items: routineContext.items,
30
+ location: step.stepId,
31
+ payload: routineContext.payload,
32
+ steps: routineContext.steps
33
+ });
34
+ // Check recursion depth
35
+ const currentDepth = routineContext.endpointDepth ?? 0;
36
+ if (currentDepth >= 10) {
37
+ throw new ConfigError('Endpoint call depth exceeded maximum of 10. Check for recursive endpoint calls.');
38
+ }
39
+ // Load target endpoint config
40
+ const endpointConfig = await getEndpointConfig(context, {
41
+ endpointId: evaluatedProperties.endpointId
42
+ });
43
+ // Authorize current user against target endpoint
44
+ authorizeApiEndpoint(context, {
45
+ endpointConfig
46
+ });
47
+ // Create isolated routineContext for the called endpoint
48
+ const childRoutineContext = {
49
+ steps: {},
50
+ payload: evaluatedProperties.payload ?? {},
51
+ arrayIndices: [],
52
+ items: {},
53
+ endpointDepth: currentDepth + 1
54
+ };
55
+ // Run the target endpoint's routine
56
+ const result = await runRoutine(context, childRoutineContext, {
57
+ routine: endpointConfig.routine
58
+ });
59
+ // Store the return value in the caller's steps
60
+ const response = result.status === 'return' ? result.response : null;
61
+ addStepResult(context, routineContext, {
62
+ result: response,
63
+ stepId: step.stepId
64
+ });
65
+ // Propagate errors and rejects to the caller
66
+ if (result.status === 'error' || result.status === 'reject') {
67
+ return result;
68
+ }
69
+ logger.debug({
70
+ event: 'debug_end_endpoint_call',
71
+ stepId: step.stepId,
72
+ targetEndpointId: evaluatedProperties.endpointId,
73
+ response
74
+ });
75
+ return {
76
+ status: 'continue'
77
+ };
78
+ }
79
+ export default handleEndpointCall;
@@ -17,8 +17,8 @@ import callRequestResolver from '../request/callRequestResolver.js';
17
17
  import checkConnectionRead from '../request/checkConnectionRead.js';
18
18
  import checkConnectionWrite from '../request/checkConnectionWrite.js';
19
19
  import evaluateOperators from '../request/evaluateOperators.js';
20
- import getConnection from '../request/getConnection.js';
21
- import getConnectionConfig from '../request/getConnectionConfig.js';
20
+ import getConnection from '../connections/getConnection.js';
21
+ import getConnectionConfig from '../connections/getConnectionConfig.js';
22
22
  import getRequestResolver from '../request/getRequestResolver.js';
23
23
  import validateSchemas from '../request/validateSchemas.js';
24
24
  async function handleRequest(context, routineContext, { request }) {
@@ -30,7 +30,8 @@ async function handleRequest(context, routineContext, { request }) {
30
30
  });
31
31
  const requestConfig = request;
32
32
  const connectionConfig = await getConnectionConfig(context, {
33
- requestConfig
33
+ connectionId: requestConfig.connectionId,
34
+ configKey: requestConfig['~k']
34
35
  });
35
36
  const connection = getConnection(context, {
36
37
  connectionConfig
@@ -42,7 +43,9 @@ async function handleRequest(context, routineContext, { request }) {
42
43
  const { connectionProperties, requestProperties } = evaluateOperators(context, {
43
44
  connectionConfig,
44
45
  items,
45
- requestConfig
46
+ payload: routineContext.payload,
47
+ requestConfig,
48
+ steps: routineContext.steps
46
49
  });
47
50
  checkConnectionRead(context, {
48
51
  connectionConfig,
@@ -71,7 +74,7 @@ async function handleRequest(context, routineContext, { request }) {
71
74
  });
72
75
  addStepResult(context, routineContext, {
73
76
  result,
74
- stepId: request.requestId
77
+ stepId: request.stepId
75
78
  });
76
79
  context.logger.debug({
77
80
  event: 'debug_end_request',
@@ -13,8 +13,9 @@
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 handleRequest from './handleRequest.js';
17
16
  import handleControl from './control/handleControl.js';
17
+ import handleEndpointCall from './handleEndpointCall.js';
18
+ import handleRequest from './handleRequest.js';
18
19
  async function runRoutine(context, routineContext, { routine }) {
19
20
  try {
20
21
  if (type.isObject(routine)) {
@@ -23,6 +24,11 @@ async function runRoutine(context, routineContext, { routine }) {
23
24
  request: routine
24
25
  });
25
26
  }
27
+ if (routine.id?.startsWith?.('endpoint:')) {
28
+ return await handleEndpointCall(context, routineContext, {
29
+ step: routine
30
+ });
31
+ }
26
32
  return await handleControl(context, routineContext, {
27
33
  control: routine
28
34
  });
@@ -65,7 +65,7 @@ const logger = {
65
65
  // error: console.error,
66
66
  error: jest.fn()
67
67
  };
68
- function createTextContext({ payload }) {
68
+ function createTextContext() {
69
69
  const context = testContext({
70
70
  connections,
71
71
  operators,
@@ -78,21 +78,17 @@ function createTextContext({ payload }) {
78
78
  }
79
79
  }
80
80
  });
81
- context.payload = payload;
82
- context.steps = {};
83
81
  context.blockId = 'blockId';
84
82
  context.pageId = 'pageId';
85
83
  context.endpointId = 'endpointId';
86
- context.evaluateOperators = createEvaluateOperators(context, {
87
- payload
88
- });
84
+ context.evaluateOperators = createEvaluateOperators(context);
89
85
  return context;
90
86
  }
91
87
  async function runTest({ routine, payload = {} }) {
92
- const context = createTextContext({
93
- payload
94
- });
88
+ const context = createTextContext();
95
89
  const routineContext = {
90
+ steps: {},
91
+ payload,
96
92
  items: {},
97
93
  arrayIndices: []
98
94
  };
@@ -101,7 +97,8 @@ async function runTest({ routine, payload = {} }) {
101
97
  });
102
98
  return {
103
99
  res,
104
- context
100
+ context,
101
+ routineContext
105
102
  };
106
103
  }
107
104
  export default runTest;
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import { serializer } from '@lowdefy/helpers';
16
16
  async function getPageConfig({ authorize, readConfigFile }, { pageId }) {
17
- const pageConfig = await readConfigFile(`pages/${pageId}/${pageId}.json`);
17
+ const pageConfig = await readConfigFile(`pages/${pageId}.json`);
18
18
  if (pageConfig && authorize(pageConfig)) {
19
19
  // eslint-disable-next-line no-unused-vars
20
20
  const { auth, ...rest } = pageConfig;
@@ -18,8 +18,8 @@ import callRequestResolver from './callRequestResolver.js';
18
18
  import checkConnectionRead from './checkConnectionRead.js';
19
19
  import checkConnectionWrite from './checkConnectionWrite.js';
20
20
  import evaluateOperators from './evaluateOperators.js';
21
- import getConnection from './getConnection.js';
22
- import getConnectionConfig from './getConnectionConfig.js';
21
+ import getConnection from '../connections/getConnection.js';
22
+ import getConnectionConfig from '../connections/getConnectionConfig.js';
23
23
  import getRequestConfig from './getRequestConfig.js';
24
24
  import getRequestResolver from './getRequestResolver.js';
25
25
  import validateSchemas from './validateSchemas.js';
@@ -28,7 +28,8 @@ async function callRequest(context, { blockId, pageId, payload, requestId }) {
28
28
  const { logger } = context;
29
29
  context.blockId = blockId;
30
30
  context.pageId = pageId;
31
- context.payload = serializer.deserialize(payload);
31
+ const requestPayload = serializer.deserialize(payload);
32
+ context.payload = requestPayload;
32
33
  context.evaluateOperators = createEvaluateOperators(context);
33
34
  logger.debug({
34
35
  event: 'debug_request',
@@ -42,7 +43,8 @@ async function callRequest(context, { blockId, pageId, payload, requestId }) {
42
43
  requestId
43
44
  });
44
45
  const connectionConfig = await getConnectionConfig(context, {
45
- requestConfig
46
+ connectionId: requestConfig.connectionId,
47
+ configKey: requestConfig['~k']
46
48
  });
47
49
  authorizeRequest(context, {
48
50
  requestConfig
@@ -56,7 +58,9 @@ async function callRequest(context, { blockId, pageId, payload, requestId }) {
56
58
  });
57
59
  const { connectionProperties, requestProperties } = evaluateOperators(context, {
58
60
  connectionConfig,
59
- requestConfig
61
+ payload: requestPayload,
62
+ requestConfig,
63
+ steps: {}
60
64
  });
61
65
  checkConnectionRead(context, {
62
66
  connectionConfig,
@@ -14,6 +14,8 @@
14
14
  limitations under the License.
15
15
  */ import { ConfigError, RequestError, ServiceError } from '@lowdefy/errors';
16
16
  async function callRequestResolver({ blockId, endpointId, logger, pageId, payload }, { connectionProperties, requestConfig, requestProperties, requestResolver }) {
17
+ // stepId for endpoint steps (after build), requestId for page requests
18
+ const stepOrRequestId = requestConfig.stepId ?? requestConfig.requestId;
17
19
  try {
18
20
  const response = await requestResolver({
19
21
  blockId,
@@ -23,7 +25,7 @@ async function callRequestResolver({ blockId, endpointId, logger, pageId, payloa
23
25
  pageId,
24
26
  payload,
25
27
  request: requestProperties,
26
- requestId: requestConfig.requestId
28
+ requestId: stepOrRequestId
27
29
  });
28
30
  return response;
29
31
  } catch (error) {
@@ -34,7 +36,7 @@ async function callRequestResolver({ blockId, endpointId, logger, pageId, payloa
34
36
  if (error instanceof ConfigError) {
35
37
  logger.debug({
36
38
  params: {
37
- id: requestConfig.requestId,
39
+ id: stepOrRequestId,
38
40
  type: requestConfig.type
39
41
  },
40
42
  err: error
@@ -50,7 +52,7 @@ async function callRequestResolver({ blockId, endpointId, logger, pageId, payloa
50
52
  });
51
53
  logger.debug({
52
54
  params: {
53
- id: requestConfig.requestId,
55
+ id: stepOrRequestId,
54
56
  type: requestConfig.type
55
57
  },
56
58
  err: serviceError
@@ -62,12 +64,12 @@ async function callRequestResolver({ blockId, endpointId, logger, pageId, payloa
62
64
  cause: error,
63
65
  typeName: requestConfig.type,
64
66
  received: requestProperties,
65
- location: `${requestConfig.connectionId}/${requestConfig.requestId}`,
67
+ location: `${requestConfig.connectionId}/${stepOrRequestId}`,
66
68
  configKey: requestConfig['~k']
67
69
  });
68
70
  logger.debug({
69
71
  params: {
70
- id: requestConfig.requestId,
72
+ id: stepOrRequestId,
71
73
  type: requestConfig.type
72
74
  },
73
75
  err: requestError
@@ -12,15 +12,19 @@
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 evaluateOperators({ evaluateOperators }, { connectionConfig, items, requestConfig }) {
15
+ */ function evaluateOperators({ evaluateOperators }, { connectionConfig, items, payload, requestConfig, steps }) {
16
16
  const connectionProperties = evaluateOperators({
17
17
  input: connectionConfig.properties || {},
18
- location: connectionConfig.connectionId
18
+ location: connectionConfig.connectionId,
19
+ payload,
20
+ steps
19
21
  });
20
22
  const requestProperties = evaluateOperators({
21
23
  input: requestConfig.properties || {},
22
24
  items,
23
- location: requestConfig.requestId
25
+ location: requestConfig.stepId ?? requestConfig.requestId,
26
+ payload,
27
+ steps
24
28
  });
25
29
  return {
26
30
  connectionProperties,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/api",
3
- "version": "5.1.0",
3
+ "version": "5.3.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "",
6
6
  "homepage": "https://lowdefy.com",
@@ -34,13 +34,13 @@
34
34
  "dist/*"
35
35
  ],
36
36
  "dependencies": {
37
- "@lowdefy/ajv": "5.1.0",
38
- "@lowdefy/errors": "5.1.0",
39
- "@lowdefy/helpers": "5.1.0",
40
- "@lowdefy/node-utils": "5.1.0",
41
- "@lowdefy/nunjucks": "5.1.0",
42
- "@lowdefy/operators": "5.1.0",
43
- "@lowdefy/operators-js": "5.1.0"
37
+ "@lowdefy/ajv": "5.3.0",
38
+ "@lowdefy/errors": "5.3.0",
39
+ "@lowdefy/helpers": "5.3.0",
40
+ "@lowdefy/node-utils": "5.3.0",
41
+ "@lowdefy/nunjucks": "5.3.0",
42
+ "@lowdefy/operators": "5.3.0",
43
+ "@lowdefy/operators-js": "5.3.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@jest/globals": "28.1.3",