Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @openfn/cli

## 1.30.4

### Patch Changes

- 0ed97be: Improve error reporting to resolve the dreaded "fn is not a function" error that occurs when writing `console.log()` at the top of job code
- Updated dependencies [0ed97be]
- @openfn/runtime@1.8.5

## 1.30.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openfn/cli",
"version": "1.30.3",
"version": "1.30.4",
"description": "CLI devtools for the OpenFn toolchain",
"engines": {
"node": ">=18",
Expand Down
7 changes: 7 additions & 0 deletions packages/engine-multi/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# engine-multi

## 1.10.6

### Patch Changes

- Updated dependencies [0ed97be]
- @openfn/runtime@1.8.5

## 1.10.5

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/engine-multi/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openfn/engine-multi",
"version": "1.10.5",
"version": "1.10.6",
"description": "Multi-process runtime engine",
"main": "dist/index.js",
"type": "module",
Expand Down
8 changes: 8 additions & 0 deletions packages/lightning-mock/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @openfn/lightning-mock

## 2.4.8

### Patch Changes

- Updated dependencies [0ed97be]
- @openfn/runtime@1.8.5
- @openfn/engine-multi@1.10.6

## 2.4.7

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/lightning-mock/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openfn/lightning-mock",
"version": "2.4.7",
"version": "2.4.8",
"private": true,
"description": "A mock Lightning server",
"main": "dist/index.js",
Expand Down
6 changes: 6 additions & 0 deletions packages/runtime/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @openfn/runtime

## 1.8.5

### Patch Changes

- 0ed97be: Improve error reporting to resolve the dreaded "fn is not a function" error that occurs when writing `console.log()` at the top of job code

## 1.8.4

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openfn/runtime",
"version": "1.8.4",
"version": "1.8.5",
"description": "Job processing runtime.",
"type": "module",
"exports": {
Expand Down
45 changes: 43 additions & 2 deletions packages/runtime/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ export function assertRuntimeError(e: any) {
// See https://nodejs.org/api/errors.html for errors
export function assertRuntimeCrash(e: any) {
// ignore instanceof SystemError, AssertionError
if (e.constructor.name.match(/ReferenceError|SyntaxError/)) {
if (
e.severity === 'crash' ||
e.constructor.name.match(/ReferenceError|SyntaxError/)
) {
throw new RuntimeCrash(e);
}
}
Expand Down Expand Up @@ -106,6 +109,10 @@ export class RTError extends Error {
name: string = 'Error';
pos?: ErrorPosition;
step?: string;
/** Hint to the user how to fix the problem */
fix?: string;
/** More details about the error */
details?: string;

constructor() {
super();
Expand All @@ -123,6 +130,34 @@ export class ValidationError extends RTError {
this.message = message;
}
}
export class InvalidExportsError extends RTError {
severity = 'crash';
name = 'ValidationError';

constructor() {
super();
this.message = `Invalid exports in job expression: exports must be an array`;

this.fix = 'Ensure job code has been compiled before calling the runtime';

delete this.stack;
}
}

export class InvalidOperationError extends RTError {
severity = 'crash';
name = 'ValidationError';

constructor(idx: number) {
super();
this.message = `Invalid operation at statement ${idx}`;

this.fix =
'Ensure all top-level function or method calls are wrapped in an operation, like fn()';

delete this.stack;
}
}

// Generic runtime execution error
// This is a wrapper around any node/js error thrown during execution
Expand Down Expand Up @@ -155,13 +190,19 @@ export class RuntimeCrash extends RTError {
subtype: string;
name = 'RuntimeCrash';

constructor(error: Error) {
constructor(error: any) {
super();
this.subtype = error.constructor.name;
this.message = `${this.subtype}: ${error.message}`;

this.pos = extractPosition(error);
this.stack = extractStackTrace(error);
if (error.fix) {
this.fix = error.fix;
}
if (error.details) {
this.details = error.details;
}
}
}

Expand Down
13 changes: 13 additions & 0 deletions packages/runtime/src/execute/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
assertRuntimeError,
assertSecurityKill,
AdaptorError,
InvalidOperationError,
InvalidExportsError,
} from '../errors';
import type { JobModule, ExecutionContext, GlobalsModule } from '../types';
import { ModuleInfoMap } from '../modules/linker';
Expand Down Expand Up @@ -234,6 +236,17 @@ const prepareJob = async (
log: opts.logger,
});
const operations = exports.default;

if (!Array.isArray(operations)) {
throw new InvalidExportsError();
}

operations.forEach((op: unknown, idx: number) => {
if (typeof op !== 'function') {
throw new InvalidOperationError(idx);
}
});

return {
operations,
...exports,
Expand Down
6 changes: 5 additions & 1 deletion packages/runtime/src/util/log-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,13 @@ const createErrorReporter = (logger: Logger): ErrorReporter => {
logger.error(error.message);
}

if (error.fix) {
logger.error(error.fix);
logger.break();
}
if (error.details) {
logger.error('Additional error details:');
logger.print(error.details);
logger.error(error.details);
logger.break();
}

Expand Down
17 changes: 17 additions & 0 deletions packages/runtime/test/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const createPlan = (expression: string, options: WorkflowOptions = {}) => ({
workflow: {
steps: [
{
id: 'a',
expression,
},
],
Expand Down Expand Up @@ -101,6 +102,22 @@ test('extractStackTrace: basic test', (t) => {
);
});

test('crash on invalid operation (the iconic "fn is not a function" error)', async (t) => {
// Note that this will output 'x' to stdout and look a bit weird!
const expression = 'export default [console.debug("x")]';
const plan = createPlan(expression, { timeout: 1 });

let error;
try {
await run(plan);
} catch (e) {
error = e;
}

t.is(error.severity, 'crash');
t.regex(error.message, /invalid operation at statement 0/i);
});

test('crash on timeout', async (t) => {
const expression = 'export default [(s) => new Promise((resolve) => {})]';

Expand Down
9 changes: 9 additions & 0 deletions packages/ws-worker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# ws-worker

## 1.22.2

### Patch Changes

- 0ed97be: Improve error reporting to resolve the dreaded "fn is not a function" error that occurs when writing `console.log()` at the top of job code
- Updated dependencies [0ed97be]
- @openfn/runtime@1.8.5
- @openfn/engine-multi@1.10.6

## 1.22.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/ws-worker/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openfn/ws-worker",
"version": "1.22.1",
"version": "1.22.2",
"description": "A Websocket Worker to connect Lightning to a Runtime Engine",
"main": "dist/index.js",
"type": "module",
Expand Down