@lowdefy/engine 6.0.0 → 6.1.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.
package/dist/Actions.js CHANGED
@@ -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 { ActionError, ConfigError, UserError } from '@lowdefy/errors';
16
- import { type } from '@lowdefy/helpers';
16
+ import { projectCaughtError, type } from '@lowdefy/helpers';
17
17
  import getActionMethods from './actions/getActionMethods.js';
18
+ import { isDecodedServerError } from './decodeServerError.js';
18
19
  import { isStopChain } from './stopChain.js';
19
20
  const CONTROL_KEYS = [
20
21
  ':if',
@@ -27,24 +28,25 @@ function isControl(item) {
27
28
  let Actions = class Actions {
28
29
  logActionError({ error, action }) {
29
30
  const handleError = this.context._internal.lowdefy._internal.handleError;
30
- const actionId = action?.id || '';
31
- // Deduplicate by error message + action id
32
- const errorKey = `${error?.message || ''}:${actionId}`;
33
- if (this.loggedActionErrors.has(errorKey)) {
34
- return;
35
- }
36
- this.loggedActionErrors.add(errorKey);
37
- // User-facing errors log to browser console only, never to terminal
31
+ // User-facing errors log to browser console only, never to terminal. This line
32
+ // bypasses handleError, so it dedups here.
38
33
  if (error instanceof UserError) {
34
+ const errorKey = `${error.message}:${action?.id || ''}`;
35
+ if (this.loggedActionErrors.has(errorKey)) {
36
+ return;
37
+ }
38
+ this.loggedActionErrors.add(errorKey);
39
39
  this.context._internal.lowdefy._internal.logger.error(error);
40
40
  return;
41
41
  }
42
- // Lowdefy errors - use handleError (-> terminal)
42
+ // Not deduped here: every server failure carries the same generic message, so a
43
+ // message + action key would hide each later failure of the action. handleError
44
+ // dedups on the message it displays plus the server's requestId.
43
45
  if (handleError) {
44
46
  handleError(error);
45
47
  }
46
48
  }
47
- async callAsyncAction({ action, arrayIndices, block, event, index, responses }) {
49
+ async callAsyncAction({ action, arrayIndices, block, event, index, parseScope, responses }) {
48
50
  try {
49
51
  const response = await this.callAction({
50
52
  action,
@@ -52,6 +54,7 @@ let Actions = class Actions {
52
54
  block,
53
55
  event,
54
56
  index,
57
+ parseScope,
55
58
  responses
56
59
  });
57
60
  responses[action.id] = response;
@@ -63,7 +66,7 @@ let Actions = class Actions {
63
66
  }
64
67
  // Returns true when a ':return' control or an action ended the list, so callers can
65
68
  // end the event.
66
- async callActionLoop({ actions, arrayIndices, block, controls, counters, event, progress, responses }) {
69
+ async callActionLoop({ actions, arrayIndices, block, controls, counters, event, parseScope, progress, responses }) {
67
70
  for (const [position, action] of actions.entries()){
68
71
  if (isControl(action)) {
69
72
  const returned = await this.callControl({
@@ -73,6 +76,7 @@ let Actions = class Actions {
73
76
  controls,
74
77
  counters,
75
78
  event,
79
+ parseScope,
76
80
  progress,
77
81
  responses
78
82
  });
@@ -99,6 +103,7 @@ let Actions = class Actions {
99
103
  block,
100
104
  event,
101
105
  index,
106
+ parseScope,
102
107
  progress,
103
108
  responses
104
109
  });
@@ -109,6 +114,7 @@ let Actions = class Actions {
109
114
  block,
110
115
  event,
111
116
  index,
117
+ parseScope,
112
118
  progress,
113
119
  responses
114
120
  });
@@ -134,7 +140,7 @@ let Actions = class Actions {
134
140
  }
135
141
  return false;
136
142
  }
137
- async callControl({ arrayIndices, block, control, controls, counters, event, progress, responses }) {
143
+ async callControl({ arrayIndices, block, control, controls, counters, event, parseScope, progress, responses }) {
138
144
  const index = counters.control;
139
145
  counters.control += 1;
140
146
  if (':return' in control) {
@@ -144,6 +150,7 @@ let Actions = class Actions {
144
150
  event,
145
151
  input: control[':return'],
146
152
  node: control,
153
+ parseScope,
147
154
  responses
148
155
  });
149
156
  controls.push({
@@ -160,6 +167,7 @@ let Actions = class Actions {
160
167
  event,
161
168
  input: control[':if'],
162
169
  node: control,
170
+ parseScope,
163
171
  responses
164
172
  });
165
173
  // JS truthiness, matching the routine ':if' - not skip's strict === true.
@@ -176,6 +184,7 @@ let Actions = class Actions {
176
184
  controls,
177
185
  counters,
178
186
  event,
187
+ parseScope,
179
188
  progress,
180
189
  responses
181
190
  });
@@ -203,6 +212,7 @@ let Actions = class Actions {
203
212
  controls,
204
213
  counters,
205
214
  event,
215
+ parseScope,
206
216
  progress,
207
217
  responses
208
218
  });
@@ -219,6 +229,7 @@ let Actions = class Actions {
219
229
  event,
220
230
  input: caseObject[':case'],
221
231
  node: caseObject,
232
+ parseScope,
222
233
  responses
223
234
  });
224
235
  if (condition) {
@@ -235,6 +246,7 @@ let Actions = class Actions {
235
246
  controls,
236
247
  counters,
237
248
  event,
249
+ parseScope,
238
250
  progress,
239
251
  responses
240
252
  });
@@ -267,12 +279,14 @@ let Actions = class Actions {
267
279
  controls,
268
280
  counters,
269
281
  event,
282
+ parseScope,
270
283
  progress,
271
284
  responses
272
285
  });
273
286
  }
274
- evaluateControlValue({ arrayIndices, block, event, input, node, responses }) {
287
+ evaluateControlValue({ arrayIndices, block, event, input, node, parseScope, responses }) {
275
288
  const { output, errors: parserErrors } = this.context._internal.parser.parse({
289
+ ...parseScope,
276
290
  actions: responses,
277
291
  event,
278
292
  arrayIndices,
@@ -353,11 +367,21 @@ let Actions = class Actions {
353
367
  controls,
354
368
  counters,
355
369
  event,
370
+ parseScope: {
371
+ error: null
372
+ },
356
373
  responses,
357
374
  progress
358
375
  });
359
376
  } catch (error) {
360
377
  this.logActionError(error);
378
+ // A server error is already the wire shape, generic unless the author wrote
379
+ // it. A client-side error was built in the browser from data it already
380
+ // holds, so it keeps its real message - only its fields are narrowed.
381
+ const thrown = error.error;
382
+ const caught = isDecodedServerError(thrown) ? thrown : Object.assign(projectCaughtError(thrown), {
383
+ actionId: error.action?.id
384
+ });
361
385
  // Catch actions restart action numbering, matching flat-chain history; the control
362
386
  // counter continues so every control entry keeps a unique index within the event.
363
387
  counters.action = 0;
@@ -369,6 +393,9 @@ let Actions = class Actions {
369
393
  controls,
370
394
  counters,
371
395
  event,
396
+ parseScope: {
397
+ error: caught
398
+ },
372
399
  responses,
373
400
  progress
374
401
  });
@@ -419,7 +446,7 @@ let Actions = class Actions {
419
446
  success: true
420
447
  };
421
448
  }
422
- async callAction({ action, arrayIndices, block, event, index, progress, responses }) {
449
+ async callAction({ action, arrayIndices, block, event, index, parseScope, progress, responses }) {
423
450
  if (!this.actions[action.type]) {
424
451
  const error = new ConfigError(`Invalid action type "${action.type}" at "${block.blockId}".`, {
425
452
  configKey: action['~k']
@@ -431,6 +458,7 @@ let Actions = class Actions {
431
458
  };
432
459
  }
433
460
  const { output: parsedAction, errors: parserErrors } = this.context._internal.parser.parse({
461
+ ...parseScope,
434
462
  actions: responses,
435
463
  event,
436
464
  arrayIndices,
@@ -491,6 +519,7 @@ let Actions = class Actions {
491
519
  type: action.type
492
520
  };
493
521
  const { output: parsedMessages, errors: parserErrors } = this.context._internal.parser.parse({
522
+ ...parseScope,
494
523
  actions: responses,
495
524
  event,
496
525
  arrayIndices,
@@ -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 decodeServerError from './decodeServerError.js';
16
17
  async function callAPIHandler(context, { blockId, params }) {
17
18
  if (!context._internal.lowdefy.apiResponses[params.endpointId]) {
18
19
  context._internal.lowdefy.apiResponses[params.endpointId] = [];
@@ -55,7 +56,10 @@ async function callAPIHandler(context, { blockId, params }) {
55
56
  throw error;
56
57
  }
57
58
  const { error, response, status, success } = apiResponse;
58
- api.error = serializer.deserialize(error);
59
+ // Decoded once so the stored and the thrown error are the same object: the
60
+ // dev error is keyed by that object.
61
+ const decodedError = decodeServerError(error);
62
+ api.error = decodedError;
59
63
  api.loading = false;
60
64
  api.response = serializer.deserialize(response);
61
65
  api.status = status;
@@ -64,7 +68,7 @@ async function callAPIHandler(context, { blockId, params }) {
64
68
  api.responseTime = api.endTimestamp - api.startTimestamp;
65
69
  context._internal.update();
66
70
  if (!success) {
67
- throw serializer.deserialize(error);
71
+ throw decodedError;
68
72
  }
69
73
  return api;
70
74
  }
@@ -0,0 +1,43 @@
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
+ // The decoded error is stored as `request.error` / `api.error` and rethrown,
17
+ // and config reads it through `_actions`, `_request_details` and `_error`. Any
18
+ // property on it - enumerable or not - is one operator change away from config,
19
+ // so the dev server's full error is kept beside it here, keyed by the error
20
+ // itself, where only the dev tools look.
21
+ const devErrors = new WeakMap();
22
+ // A catch list's `_error` passes a server error through as decoded, but
23
+ // projects a client-side error. The two can share a name and class, so the
24
+ // engine tells them apart by identity.
25
+ const decoded = new WeakSet();
26
+ function decodeServerError(payload) {
27
+ if (type.isNone(payload)) return payload;
28
+ const { devError, ...rest } = payload;
29
+ const error = serializer.deserialize(rest);
30
+ if (!type.isNone(devError)) {
31
+ devErrors.set(error, serializer.deserialize(devError));
32
+ }
33
+ decoded.add(error);
34
+ return error;
35
+ }
36
+ function getDevError(error) {
37
+ return devErrors.get(error);
38
+ }
39
+ function isDecodedServerError(error) {
40
+ return decoded.has(error);
41
+ }
42
+ export { getDevError, isDecodedServerError };
43
+ export default decodeServerError;
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@
15
15
  */ import Actions from './Actions.js';
16
16
  import Slots from './Slots.js';
17
17
  import createLink from './createLink.js';
18
+ import decodeServerError, { getDevError } from './decodeServerError.js';
18
19
  import Events from './Events.js';
19
20
  import getContext from './getContext.js';
20
21
  import getHomePathname from './getHomePathname.js';
@@ -22,5 +23,5 @@ import Requests from './Requests.js';
22
23
  import resolveTarget from './resolveTarget.js';
23
24
  import State from './State.js';
24
25
  import stopChain from './stopChain.js';
25
- export { Actions, Slots, createLink, Events, getHomePathname, Requests, resolveTarget, State, stopChain };
26
+ export { Actions, Slots, createLink, decodeServerError, Events, getDevError, getHomePathname, Requests, resolveTarget, State, stopChain };
26
27
  export default getContext;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/engine",
3
- "version": "6.0.0",
3
+ "version": "6.1.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "",
6
6
  "homepage": "https://lowdefy.com",
@@ -30,16 +30,16 @@
30
30
  "dist/*"
31
31
  ],
32
32
  "dependencies": {
33
- "@lowdefy/errors": "6.0.0",
34
- "@lowdefy/helpers": "6.0.0",
35
- "@lowdefy/operators": "6.0.0"
33
+ "@lowdefy/errors": "6.1.0",
34
+ "@lowdefy/helpers": "6.1.0",
35
+ "@lowdefy/operators": "6.1.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@jest/globals": "28.1.3",
39
- "@lowdefy/actions-core": "6.0.0",
40
- "@lowdefy/build": "6.0.0",
41
- "@lowdefy/operators-js": "6.0.0",
42
- "@lowdefy/operators-mql": "6.0.0",
39
+ "@lowdefy/actions-core": "6.1.0",
40
+ "@lowdefy/build": "6.1.0",
41
+ "@lowdefy/operators-js": "6.1.0",
42
+ "@lowdefy/operators-mql": "6.1.0",
43
43
  "@swc/cli": "0.8.1",
44
44
  "@swc/core": "1.15.32",
45
45
  "@swc/jest": "0.2.39",