@lowdefy/api 5.1.0 → 5.2.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.
@@ -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];
@@ -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,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;
@@ -42,7 +42,9 @@ async function handleRequest(context, routineContext, { request }) {
42
42
  const { connectionProperties, requestProperties } = evaluateOperators(context, {
43
43
  connectionConfig,
44
44
  items,
45
- requestConfig
45
+ payload: routineContext.payload,
46
+ requestConfig,
47
+ steps: routineContext.steps
46
48
  });
47
49
  checkConnectionRead(context, {
48
50
  connectionConfig,
@@ -71,7 +73,7 @@ async function handleRequest(context, routineContext, { request }) {
71
73
  });
72
74
  addStepResult(context, routineContext, {
73
75
  result,
74
- stepId: request.requestId
76
+ stepId: request.stepId
75
77
  });
76
78
  context.logger.debug({
77
79
  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;
@@ -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',
@@ -56,7 +57,9 @@ async function callRequest(context, { blockId, pageId, payload, requestId }) {
56
57
  });
57
58
  const { connectionProperties, requestProperties } = evaluateOperators(context, {
58
59
  connectionConfig,
59
- requestConfig
60
+ payload: requestPayload,
61
+ requestConfig,
62
+ steps: {}
60
63
  });
61
64
  checkConnectionRead(context, {
62
65
  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.2.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.2.0",
38
+ "@lowdefy/errors": "5.2.0",
39
+ "@lowdefy/helpers": "5.2.0",
40
+ "@lowdefy/node-utils": "5.2.0",
41
+ "@lowdefy/nunjucks": "5.2.0",
42
+ "@lowdefy/operators": "5.2.0",
43
+ "@lowdefy/operators-js": "5.2.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@jest/globals": "28.1.3",