@openfn/ws-worker 0.2.11 → 0.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.
- package/CHANGELOG.md +23 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +221 -63
- package/dist/start.js +232 -71
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# ws-worker
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 419d310: Throttle attempt events to better preserve their sequencing
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 598c669: Make edge conditions more stable if state is not passed
|
|
12
|
+
- 6e906a7: Better handling of job-error
|
|
13
|
+
- Updated dependencies [02ab459]
|
|
14
|
+
- @openfn/runtime@0.2.2
|
|
15
|
+
- @openfn/engine-multi@0.2.4
|
|
16
|
+
|
|
17
|
+
## 0.2.12
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 6c3e9e42: Ensure capacity is also set on the engine
|
|
22
|
+
- Updated dependencies [05ccc10b]
|
|
23
|
+
- Updated dependencies [7235bf5e]
|
|
24
|
+
- @openfn/engine-multi@0.2.3
|
|
25
|
+
|
|
3
26
|
## 0.2.11
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
import Koa from 'koa';
|
|
3
3
|
import { SanitizePolicies, Logger } from '@openfn/logger';
|
|
4
4
|
import { Channel as Channel$1 } from 'phoenix';
|
|
5
|
-
import {
|
|
5
|
+
import { ExecutionPlan } from '@openfn/runtime';
|
|
6
6
|
import { RuntimeEngine } from '@openfn/engine-multi';
|
|
7
|
+
import { Server } from 'http';
|
|
7
8
|
|
|
8
9
|
type ExitReasonStrings =
|
|
9
10
|
| 'success'
|
|
@@ -23,7 +24,8 @@ type Node = {
|
|
|
23
24
|
id: string;
|
|
24
25
|
body?: string;
|
|
25
26
|
adaptor?: string;
|
|
26
|
-
|
|
27
|
+
credential?: object;
|
|
28
|
+
credential_id?: string;
|
|
27
29
|
type?: 'webhook' | 'cron'; // trigger only
|
|
28
30
|
state?: any; // Initial state / defaults
|
|
29
31
|
};
|
|
@@ -138,6 +140,7 @@ declare type RunStartPayload = {
|
|
|
138
140
|
run_id: string;
|
|
139
141
|
attempt_id?: string;
|
|
140
142
|
input_dataclip_id?: string;
|
|
143
|
+
versions: Record<string, string>;
|
|
141
144
|
};
|
|
142
145
|
declare type RunStartReply = void;
|
|
143
146
|
declare const RUN_COMPLETE = "run:complete";
|
|
@@ -155,6 +158,7 @@ declare type Context = {
|
|
|
155
158
|
channel: Channel;
|
|
156
159
|
state: AttemptState;
|
|
157
160
|
logger: Logger;
|
|
161
|
+
engine: RuntimeEngine;
|
|
158
162
|
onFinish: (result: any) => void;
|
|
159
163
|
};
|
|
160
164
|
|
package/dist/index.js
CHANGED
|
@@ -165,14 +165,11 @@ var startWorkloop = (app, logger, minBackoff, maxBackoff, maxWorkers) => {
|
|
|
165
165
|
};
|
|
166
166
|
var workloop_default = startWorkloop;
|
|
167
167
|
|
|
168
|
-
// src/api/execute.ts
|
|
169
|
-
import crypto2 from "node:crypto";
|
|
170
|
-
|
|
171
168
|
// src/util/convert-attempt.ts
|
|
172
169
|
import crypto from "node:crypto";
|
|
173
170
|
var conditions = {
|
|
174
|
-
on_job_success: (upstreamId) => `Boolean(!state
|
|
175
|
-
on_job_failure: (upstreamId) => `Boolean(state
|
|
171
|
+
on_job_success: (upstreamId) => `Boolean(!state?.errors?.["${upstreamId}"] ?? true)`,
|
|
172
|
+
on_job_failure: (upstreamId) => `Boolean(state?.errors && state.errors["${upstreamId}"])`,
|
|
176
173
|
always: (_upstreamId) => null
|
|
177
174
|
};
|
|
178
175
|
var mapEdgeCondition = (edge) => {
|
|
@@ -219,7 +216,7 @@ var convert_attempt_default = (attempt) => {
|
|
|
219
216
|
const id = job.id || crypto.randomUUID();
|
|
220
217
|
nodes[id] = {
|
|
221
218
|
id,
|
|
222
|
-
configuration: job.credential_id,
|
|
219
|
+
configuration: job.credential || job.credential_id,
|
|
223
220
|
expression: job.body,
|
|
224
221
|
adaptor: job.adaptor
|
|
225
222
|
};
|
|
@@ -328,6 +325,203 @@ var calculateAttemptExitReason = (state) => {
|
|
|
328
325
|
return { reason: "success", error_type: null, error_message: null };
|
|
329
326
|
};
|
|
330
327
|
|
|
328
|
+
// src/events/run-complete.ts
|
|
329
|
+
import crypto2 from "node:crypto";
|
|
330
|
+
function onRunComplete({ channel, state }, event, error) {
|
|
331
|
+
const dataclipId = crypto2.randomUUID();
|
|
332
|
+
const run_id = state.activeRun;
|
|
333
|
+
const job_id = state.activeJob;
|
|
334
|
+
if (!state.dataclips) {
|
|
335
|
+
state.dataclips = {};
|
|
336
|
+
}
|
|
337
|
+
const outputState = event.state || {};
|
|
338
|
+
state.dataclips[dataclipId] = event.state;
|
|
339
|
+
delete state.activeRun;
|
|
340
|
+
delete state.activeJob;
|
|
341
|
+
state.lastDataclipId = dataclipId;
|
|
342
|
+
event.next?.forEach((nextJobId) => {
|
|
343
|
+
state.inputDataclips[nextJobId] = dataclipId;
|
|
344
|
+
});
|
|
345
|
+
const { reason, error_message, error_type } = calculateJobExitReason(
|
|
346
|
+
job_id,
|
|
347
|
+
event.state,
|
|
348
|
+
error
|
|
349
|
+
);
|
|
350
|
+
state.reasons[job_id] = { reason, error_message, error_type };
|
|
351
|
+
const evt = {
|
|
352
|
+
run_id,
|
|
353
|
+
job_id,
|
|
354
|
+
output_dataclip_id: dataclipId,
|
|
355
|
+
output_dataclip: stringify_default(outputState),
|
|
356
|
+
reason,
|
|
357
|
+
error_message,
|
|
358
|
+
error_type,
|
|
359
|
+
mem: event.mem,
|
|
360
|
+
duration: event.duration,
|
|
361
|
+
thread_id: event.threadId
|
|
362
|
+
};
|
|
363
|
+
return sendEvent(channel, RUN_COMPLETE, evt);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/events/run-start.ts
|
|
367
|
+
import crypto3 from "node:crypto";
|
|
368
|
+
import { timestamp } from "@openfn/logger";
|
|
369
|
+
|
|
370
|
+
// package.json
|
|
371
|
+
var package_default = {
|
|
372
|
+
name: "@openfn/ws-worker",
|
|
373
|
+
version: "0.3.0",
|
|
374
|
+
description: "A Websocket Worker to connect Lightning to a Runtime Engine",
|
|
375
|
+
main: "dist/index.js",
|
|
376
|
+
type: "module",
|
|
377
|
+
scripts: {
|
|
378
|
+
test: "pnpm ava --serial",
|
|
379
|
+
"test:types": "pnpm tsc --noEmit --project tsconfig.json",
|
|
380
|
+
build: "tsup --config tsup.config.js",
|
|
381
|
+
"build:watch": "pnpm build --watch",
|
|
382
|
+
start: "ts-node-esm --transpile-only src/start.ts",
|
|
383
|
+
"start:prod": "node dist/start.js",
|
|
384
|
+
"start:watch": "nodemon -e ts,js --watch ../runtime-manager/dist --watch ./src --exec 'pnpm start'",
|
|
385
|
+
pack: "pnpm pack --pack-destination ../../dist"
|
|
386
|
+
},
|
|
387
|
+
bin: {
|
|
388
|
+
worker: "dist/start.js"
|
|
389
|
+
},
|
|
390
|
+
author: "Open Function Group <admin@openfn.org>",
|
|
391
|
+
license: "ISC",
|
|
392
|
+
dependencies: {
|
|
393
|
+
"@koa/router": "^12.0.0",
|
|
394
|
+
"@openfn/engine-multi": "workspace:*",
|
|
395
|
+
"@openfn/logger": "workspace:*",
|
|
396
|
+
"@openfn/runtime": "workspace:*",
|
|
397
|
+
"@types/koa-logger": "^3.1.2",
|
|
398
|
+
"@types/ws": "^8.5.6",
|
|
399
|
+
"fast-safe-stringify": "^2.1.1",
|
|
400
|
+
figures: "^5.0.0",
|
|
401
|
+
"human-id": "^4.1.0",
|
|
402
|
+
jose: "^4.14.6",
|
|
403
|
+
koa: "^2.13.4",
|
|
404
|
+
"koa-bodyparser": "^4.4.0",
|
|
405
|
+
"koa-logger": "^3.2.1",
|
|
406
|
+
phoenix: "^1.7.7",
|
|
407
|
+
ws: "^8.14.1"
|
|
408
|
+
},
|
|
409
|
+
devDependencies: {
|
|
410
|
+
"@openfn/lightning-mock": "workspace:*",
|
|
411
|
+
"@types/koa": "^2.13.5",
|
|
412
|
+
"@types/koa-bodyparser": "^4.3.10",
|
|
413
|
+
"@types/koa__router": "^12.0.1",
|
|
414
|
+
"@types/node": "^18.15.3",
|
|
415
|
+
"@types/nodemon": "1.19.3",
|
|
416
|
+
"@types/phoenix": "^1.6.2",
|
|
417
|
+
"@types/yargs": "^17.0.12",
|
|
418
|
+
ava: "5.1.0",
|
|
419
|
+
nodemon: "3.0.1",
|
|
420
|
+
"ts-node": "^10.9.1",
|
|
421
|
+
tslib: "^2.4.0",
|
|
422
|
+
tsup: "^6.2.3",
|
|
423
|
+
typescript: "^4.6.4",
|
|
424
|
+
yargs: "^17.6.2"
|
|
425
|
+
},
|
|
426
|
+
files: [
|
|
427
|
+
"dist",
|
|
428
|
+
"README.md",
|
|
429
|
+
"CHANGELOG.md"
|
|
430
|
+
]
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
// src/util/versions.ts
|
|
434
|
+
import { mainSymbols } from "figures";
|
|
435
|
+
var { triangleRightSmall: t } = mainSymbols;
|
|
436
|
+
var versions_default = (runId, versions, adaptor) => {
|
|
437
|
+
let longest = "compiler".length;
|
|
438
|
+
for (const v in versions) {
|
|
439
|
+
longest = Math.max(v.length, longest);
|
|
440
|
+
}
|
|
441
|
+
const { node, compiler, engine, worker, runtime, ...adaptors } = versions;
|
|
442
|
+
const prefix = (str2) => ` ${t} ${str2.padEnd(longest + 4, " ")}`;
|
|
443
|
+
let str = `Versions for run ${runId}:
|
|
444
|
+
${prefix("node.js")}${versions.node || "unknown"}
|
|
445
|
+
${prefix("worker")}${versions.worker || "unknown"}
|
|
446
|
+
${prefix("engine")}${versions.engine || "unknown"}
|
|
447
|
+
${prefix("runtime")}${versions.runtime || "unknown"}
|
|
448
|
+
${prefix("compiler")}${versions.compiler || "unknown"}`;
|
|
449
|
+
if (Object.keys(adaptors).length) {
|
|
450
|
+
let allAdaptors = Object.keys(adaptors);
|
|
451
|
+
if (adaptor) {
|
|
452
|
+
allAdaptors = allAdaptors.filter((name) => adaptor.startsWith(name));
|
|
453
|
+
}
|
|
454
|
+
str += "\n" + allAdaptors.sort().map((adaptorName) => `${prefix(adaptorName)}${adaptors[adaptorName]}`).join("\n");
|
|
455
|
+
}
|
|
456
|
+
return str;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// src/events/run-start.ts
|
|
460
|
+
async function onRunStart(context, event) {
|
|
461
|
+
const time = (timestamp() - BigInt(1e7)).toString();
|
|
462
|
+
const { channel, state } = context;
|
|
463
|
+
state.activeRun = crypto3.randomUUID();
|
|
464
|
+
state.activeJob = event.jobId;
|
|
465
|
+
const job = state.plan.jobs.find(({ id }) => id === event.jobId);
|
|
466
|
+
const input_dataclip_id = state.inputDataclips[event.jobId];
|
|
467
|
+
const versions = {
|
|
468
|
+
worker: package_default.version,
|
|
469
|
+
...event.versions
|
|
470
|
+
};
|
|
471
|
+
const versionLogContext = {
|
|
472
|
+
...context,
|
|
473
|
+
state: {
|
|
474
|
+
...state,
|
|
475
|
+
activeRun: state.activeRun
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
await sendEvent(channel, RUN_START, {
|
|
479
|
+
run_id: state.activeRun,
|
|
480
|
+
job_id: state.activeJob,
|
|
481
|
+
input_dataclip_id,
|
|
482
|
+
versions
|
|
483
|
+
});
|
|
484
|
+
const versionMessage = versions_default(
|
|
485
|
+
versionLogContext.state.activeRun,
|
|
486
|
+
versions,
|
|
487
|
+
job?.adaptor
|
|
488
|
+
);
|
|
489
|
+
await onJobLog(versionLogContext, {
|
|
490
|
+
time,
|
|
491
|
+
message: [versionMessage],
|
|
492
|
+
level: "info",
|
|
493
|
+
name: "VER"
|
|
494
|
+
});
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/util/throttle.ts
|
|
499
|
+
var createThrottler = () => {
|
|
500
|
+
const q = [];
|
|
501
|
+
let activePromise;
|
|
502
|
+
const add = (fn) => {
|
|
503
|
+
return (...args) => new Promise((resolve, reject) => {
|
|
504
|
+
q.push({ fn, args, resolve, reject });
|
|
505
|
+
shift();
|
|
506
|
+
});
|
|
507
|
+
};
|
|
508
|
+
const shift = () => {
|
|
509
|
+
if (activePromise) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const next = q.shift();
|
|
513
|
+
if (next) {
|
|
514
|
+
const { fn, args, resolve, reject } = next;
|
|
515
|
+
activePromise = fn(...args).then(resolve).catch(reject).finally(() => {
|
|
516
|
+
activePromise = void 0;
|
|
517
|
+
shift();
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
return add;
|
|
522
|
+
};
|
|
523
|
+
var throttle_default = createThrottler;
|
|
524
|
+
|
|
331
525
|
// src/api/execute.ts
|
|
332
526
|
var enc = new TextDecoder("utf-8");
|
|
333
527
|
var eventMap = {
|
|
@@ -341,7 +535,8 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
|
|
|
341
535
|
}) {
|
|
342
536
|
logger.info("executing ", plan.id);
|
|
343
537
|
const state = create_attempt_state_default(plan, options);
|
|
344
|
-
const context = { channel, state, logger, onFinish };
|
|
538
|
+
const context = { channel, state, logger, engine, onFinish };
|
|
539
|
+
const throttle = throttle_default();
|
|
345
540
|
const addEvent = (eventName, handler) => {
|
|
346
541
|
const wrappedFn = async (event) => {
|
|
347
542
|
const lightningEvent = eventMap[eventName] ?? eventName;
|
|
@@ -361,13 +556,13 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
|
|
|
361
556
|
};
|
|
362
557
|
const listeners = Object.assign(
|
|
363
558
|
{},
|
|
364
|
-
addEvent("workflow-start", onWorkflowStart),
|
|
365
|
-
addEvent("job-start",
|
|
366
|
-
addEvent("job-complete",
|
|
367
|
-
addEvent("job-error", onJobError),
|
|
368
|
-
addEvent("workflow-log", onJobLog),
|
|
369
|
-
addEvent("workflow-complete", onWorkflowComplete),
|
|
370
|
-
addEvent("workflow-error", onWorkflowError)
|
|
559
|
+
addEvent("workflow-start", throttle(onWorkflowStart)),
|
|
560
|
+
addEvent("job-start", throttle(onRunStart)),
|
|
561
|
+
addEvent("job-complete", throttle(onRunComplete)),
|
|
562
|
+
addEvent("job-error", throttle(onJobError)),
|
|
563
|
+
addEvent("workflow-log", throttle(onJobLog)),
|
|
564
|
+
addEvent("workflow-complete", throttle(onWorkflowComplete)),
|
|
565
|
+
addEvent("workflow-error", throttle(onWorkflowError))
|
|
371
566
|
);
|
|
372
567
|
engine.listen(plan.id, listeners);
|
|
373
568
|
const resolvers = {
|
|
@@ -396,59 +591,17 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
|
|
|
396
591
|
return context;
|
|
397
592
|
}
|
|
398
593
|
var sendEvent = (channel, event, payload) => new Promise((resolve, reject) => {
|
|
399
|
-
channel.push(event, payload).receive("error", reject).receive("timeout", () =>
|
|
594
|
+
channel.push(event, payload).receive("error", reject).receive("timeout", () => {
|
|
595
|
+
reject(new Error("timeout"));
|
|
596
|
+
}).receive("ok", resolve);
|
|
400
597
|
});
|
|
401
|
-
function onJobStart({ channel, state }, event) {
|
|
402
|
-
state.activeRun = crypto2.randomUUID();
|
|
403
|
-
state.activeJob = event.jobId;
|
|
404
|
-
const input_dataclip_id = state.inputDataclips[event.jobId];
|
|
405
|
-
return sendEvent(channel, RUN_START, {
|
|
406
|
-
run_id: state.activeRun,
|
|
407
|
-
job_id: state.activeJob,
|
|
408
|
-
input_dataclip_id
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
598
|
function onJobError(context, event) {
|
|
412
599
|
const { state, error, jobId } = event;
|
|
413
|
-
if (state
|
|
414
|
-
|
|
600
|
+
if (state?.errors?.[jobId]?.message === error.message) {
|
|
601
|
+
return onRunComplete(context, event);
|
|
415
602
|
} else {
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
function onJobComplete({ channel, state }, event, error) {
|
|
420
|
-
const dataclipId = crypto2.randomUUID();
|
|
421
|
-
const run_id = state.activeRun;
|
|
422
|
-
const job_id = state.activeJob;
|
|
423
|
-
if (!state.dataclips) {
|
|
424
|
-
state.dataclips = {};
|
|
603
|
+
return onRunComplete(context, event, event.error);
|
|
425
604
|
}
|
|
426
|
-
state.dataclips[dataclipId] = event.state;
|
|
427
|
-
delete state.activeRun;
|
|
428
|
-
delete state.activeJob;
|
|
429
|
-
state.lastDataclipId = dataclipId;
|
|
430
|
-
event.next?.forEach((nextJobId) => {
|
|
431
|
-
state.inputDataclips[nextJobId] = dataclipId;
|
|
432
|
-
});
|
|
433
|
-
const { reason, error_message, error_type } = calculateJobExitReason(
|
|
434
|
-
job_id,
|
|
435
|
-
event.state,
|
|
436
|
-
error
|
|
437
|
-
);
|
|
438
|
-
state.reasons[job_id] = { reason, error_message, error_type };
|
|
439
|
-
const evt = {
|
|
440
|
-
run_id,
|
|
441
|
-
job_id,
|
|
442
|
-
output_dataclip_id: dataclipId,
|
|
443
|
-
output_dataclip: stringify_default(event.state),
|
|
444
|
-
reason,
|
|
445
|
-
error_message,
|
|
446
|
-
error_type,
|
|
447
|
-
mem: event.mem,
|
|
448
|
-
duration: event.duration,
|
|
449
|
-
thread_id: event.threadId
|
|
450
|
-
};
|
|
451
|
-
return sendEvent(channel, RUN_COMPLETE, evt);
|
|
452
605
|
}
|
|
453
606
|
function onWorkflowStart({ channel }, _event) {
|
|
454
607
|
return sendEvent(channel, ATTEMPT_START);
|
|
@@ -462,9 +615,13 @@ async function onWorkflowComplete({ state, channel, onFinish }, _event) {
|
|
|
462
615
|
});
|
|
463
616
|
onFinish({ reason, state: result });
|
|
464
617
|
}
|
|
465
|
-
async function onWorkflowError(
|
|
618
|
+
async function onWorkflowError(context, event) {
|
|
619
|
+
const { state, channel, logger, onFinish } = context;
|
|
466
620
|
try {
|
|
467
621
|
const reason = calculateJobExitReason("", { data: {} }, event);
|
|
622
|
+
if (state.activeJob) {
|
|
623
|
+
await onJobError(context, { error: event });
|
|
624
|
+
}
|
|
468
625
|
await sendEvent(channel, ATTEMPT_COMPLETE, {
|
|
469
626
|
final_dataclip_id: state.lastDataclipId,
|
|
470
627
|
...reason
|
|
@@ -473,6 +630,7 @@ async function onWorkflowError({ state, channel, logger, onFinish }, event) {
|
|
|
473
630
|
} catch (e) {
|
|
474
631
|
logger.error("ERROR in workflow-error handler:", e.message);
|
|
475
632
|
logger.error(e);
|
|
633
|
+
onFinish({});
|
|
476
634
|
}
|
|
477
635
|
}
|
|
478
636
|
function onJobLog({ channel, state }, event) {
|
package/dist/start.js
CHANGED
|
@@ -4954,6 +4954,7 @@ async function createMock() {
|
|
|
4954
4954
|
dispatch("workflow-start", { workflowId: id, threadId });
|
|
4955
4955
|
try {
|
|
4956
4956
|
await run(xplan, void 0, opts);
|
|
4957
|
+
dispatch("workflow-complete", { workflowId: id, threadId });
|
|
4957
4958
|
} catch (e) {
|
|
4958
4959
|
dispatch("workflow-error", {
|
|
4959
4960
|
threadId,
|
|
@@ -4961,9 +4962,9 @@ async function createMock() {
|
|
|
4961
4962
|
type: e.name,
|
|
4962
4963
|
message: e.message
|
|
4963
4964
|
});
|
|
4965
|
+
} finally {
|
|
4966
|
+
delete activeWorkflows[id];
|
|
4964
4967
|
}
|
|
4965
|
-
delete activeWorkflows[id];
|
|
4966
|
-
dispatch("workflow-complete", { workflowId: id, threadId });
|
|
4967
4968
|
}, 1);
|
|
4968
4969
|
};
|
|
4969
4970
|
const getStatus = () => {
|
|
@@ -5150,14 +5151,11 @@ var startWorkloop = (app, logger2, minBackoff2, maxBackoff2, maxWorkers) => {
|
|
|
5150
5151
|
};
|
|
5151
5152
|
var workloop_default = startWorkloop;
|
|
5152
5153
|
|
|
5153
|
-
// src/api/execute.ts
|
|
5154
|
-
import crypto3 from "node:crypto";
|
|
5155
|
-
|
|
5156
5154
|
// src/util/convert-attempt.ts
|
|
5157
5155
|
import crypto2 from "node:crypto";
|
|
5158
5156
|
var conditions = {
|
|
5159
|
-
on_job_success: (upstreamId) => `Boolean(!state
|
|
5160
|
-
on_job_failure: (upstreamId) => `Boolean(state
|
|
5157
|
+
on_job_success: (upstreamId) => `Boolean(!state?.errors?.["${upstreamId}"] ?? true)`,
|
|
5158
|
+
on_job_failure: (upstreamId) => `Boolean(state?.errors && state.errors["${upstreamId}"])`,
|
|
5161
5159
|
always: (_upstreamId) => null
|
|
5162
5160
|
};
|
|
5163
5161
|
var mapEdgeCondition = (edge) => {
|
|
@@ -5204,7 +5202,7 @@ var convert_attempt_default = (attempt) => {
|
|
|
5204
5202
|
const id = job.id || crypto2.randomUUID();
|
|
5205
5203
|
nodes[id] = {
|
|
5206
5204
|
id,
|
|
5207
|
-
configuration: job.credential_id,
|
|
5205
|
+
configuration: job.credential || job.credential_id,
|
|
5208
5206
|
expression: job.body,
|
|
5209
5207
|
adaptor: job.adaptor
|
|
5210
5208
|
};
|
|
@@ -5313,6 +5311,203 @@ var calculateAttemptExitReason = (state) => {
|
|
|
5313
5311
|
return { reason: "success", error_type: null, error_message: null };
|
|
5314
5312
|
};
|
|
5315
5313
|
|
|
5314
|
+
// src/events/run-complete.ts
|
|
5315
|
+
import crypto3 from "node:crypto";
|
|
5316
|
+
function onRunComplete({ channel, state }, event, error) {
|
|
5317
|
+
const dataclipId = crypto3.randomUUID();
|
|
5318
|
+
const run_id = state.activeRun;
|
|
5319
|
+
const job_id = state.activeJob;
|
|
5320
|
+
if (!state.dataclips) {
|
|
5321
|
+
state.dataclips = {};
|
|
5322
|
+
}
|
|
5323
|
+
const outputState = event.state || {};
|
|
5324
|
+
state.dataclips[dataclipId] = event.state;
|
|
5325
|
+
delete state.activeRun;
|
|
5326
|
+
delete state.activeJob;
|
|
5327
|
+
state.lastDataclipId = dataclipId;
|
|
5328
|
+
event.next?.forEach((nextJobId) => {
|
|
5329
|
+
state.inputDataclips[nextJobId] = dataclipId;
|
|
5330
|
+
});
|
|
5331
|
+
const { reason, error_message, error_type } = calculateJobExitReason(
|
|
5332
|
+
job_id,
|
|
5333
|
+
event.state,
|
|
5334
|
+
error
|
|
5335
|
+
);
|
|
5336
|
+
state.reasons[job_id] = { reason, error_message, error_type };
|
|
5337
|
+
const evt = {
|
|
5338
|
+
run_id,
|
|
5339
|
+
job_id,
|
|
5340
|
+
output_dataclip_id: dataclipId,
|
|
5341
|
+
output_dataclip: stringify_default(outputState),
|
|
5342
|
+
reason,
|
|
5343
|
+
error_message,
|
|
5344
|
+
error_type,
|
|
5345
|
+
mem: event.mem,
|
|
5346
|
+
duration: event.duration,
|
|
5347
|
+
thread_id: event.threadId
|
|
5348
|
+
};
|
|
5349
|
+
return sendEvent(channel, RUN_COMPLETE, evt);
|
|
5350
|
+
}
|
|
5351
|
+
|
|
5352
|
+
// src/events/run-start.ts
|
|
5353
|
+
import crypto4 from "node:crypto";
|
|
5354
|
+
import { timestamp } from "@openfn/logger";
|
|
5355
|
+
|
|
5356
|
+
// package.json
|
|
5357
|
+
var package_default = {
|
|
5358
|
+
name: "@openfn/ws-worker",
|
|
5359
|
+
version: "0.3.0",
|
|
5360
|
+
description: "A Websocket Worker to connect Lightning to a Runtime Engine",
|
|
5361
|
+
main: "dist/index.js",
|
|
5362
|
+
type: "module",
|
|
5363
|
+
scripts: {
|
|
5364
|
+
test: "pnpm ava --serial",
|
|
5365
|
+
"test:types": "pnpm tsc --noEmit --project tsconfig.json",
|
|
5366
|
+
build: "tsup --config tsup.config.js",
|
|
5367
|
+
"build:watch": "pnpm build --watch",
|
|
5368
|
+
start: "ts-node-esm --transpile-only src/start.ts",
|
|
5369
|
+
"start:prod": "node dist/start.js",
|
|
5370
|
+
"start:watch": "nodemon -e ts,js --watch ../runtime-manager/dist --watch ./src --exec 'pnpm start'",
|
|
5371
|
+
pack: "pnpm pack --pack-destination ../../dist"
|
|
5372
|
+
},
|
|
5373
|
+
bin: {
|
|
5374
|
+
worker: "dist/start.js"
|
|
5375
|
+
},
|
|
5376
|
+
author: "Open Function Group <admin@openfn.org>",
|
|
5377
|
+
license: "ISC",
|
|
5378
|
+
dependencies: {
|
|
5379
|
+
"@koa/router": "^12.0.0",
|
|
5380
|
+
"@openfn/engine-multi": "workspace:*",
|
|
5381
|
+
"@openfn/logger": "workspace:*",
|
|
5382
|
+
"@openfn/runtime": "workspace:*",
|
|
5383
|
+
"@types/koa-logger": "^3.1.2",
|
|
5384
|
+
"@types/ws": "^8.5.6",
|
|
5385
|
+
"fast-safe-stringify": "^2.1.1",
|
|
5386
|
+
figures: "^5.0.0",
|
|
5387
|
+
"human-id": "^4.1.0",
|
|
5388
|
+
jose: "^4.14.6",
|
|
5389
|
+
koa: "^2.13.4",
|
|
5390
|
+
"koa-bodyparser": "^4.4.0",
|
|
5391
|
+
"koa-logger": "^3.2.1",
|
|
5392
|
+
phoenix: "^1.7.7",
|
|
5393
|
+
ws: "^8.14.1"
|
|
5394
|
+
},
|
|
5395
|
+
devDependencies: {
|
|
5396
|
+
"@openfn/lightning-mock": "workspace:*",
|
|
5397
|
+
"@types/koa": "^2.13.5",
|
|
5398
|
+
"@types/koa-bodyparser": "^4.3.10",
|
|
5399
|
+
"@types/koa__router": "^12.0.1",
|
|
5400
|
+
"@types/node": "^18.15.3",
|
|
5401
|
+
"@types/nodemon": "1.19.3",
|
|
5402
|
+
"@types/phoenix": "^1.6.2",
|
|
5403
|
+
"@types/yargs": "^17.0.12",
|
|
5404
|
+
ava: "5.1.0",
|
|
5405
|
+
nodemon: "3.0.1",
|
|
5406
|
+
"ts-node": "^10.9.1",
|
|
5407
|
+
tslib: "^2.4.0",
|
|
5408
|
+
tsup: "^6.2.3",
|
|
5409
|
+
typescript: "^4.6.4",
|
|
5410
|
+
yargs: "^17.6.2"
|
|
5411
|
+
},
|
|
5412
|
+
files: [
|
|
5413
|
+
"dist",
|
|
5414
|
+
"README.md",
|
|
5415
|
+
"CHANGELOG.md"
|
|
5416
|
+
]
|
|
5417
|
+
};
|
|
5418
|
+
|
|
5419
|
+
// src/util/versions.ts
|
|
5420
|
+
import { mainSymbols } from "figures";
|
|
5421
|
+
var { triangleRightSmall: t } = mainSymbols;
|
|
5422
|
+
var versions_default = (runId, versions, adaptor) => {
|
|
5423
|
+
let longest = "compiler".length;
|
|
5424
|
+
for (const v in versions) {
|
|
5425
|
+
longest = Math.max(v.length, longest);
|
|
5426
|
+
}
|
|
5427
|
+
const { node, compiler, engine, worker, runtime, ...adaptors } = versions;
|
|
5428
|
+
const prefix = (str2) => ` ${t} ${str2.padEnd(longest + 4, " ")}`;
|
|
5429
|
+
let str = `Versions for run ${runId}:
|
|
5430
|
+
${prefix("node.js")}${versions.node || "unknown"}
|
|
5431
|
+
${prefix("worker")}${versions.worker || "unknown"}
|
|
5432
|
+
${prefix("engine")}${versions.engine || "unknown"}
|
|
5433
|
+
${prefix("runtime")}${versions.runtime || "unknown"}
|
|
5434
|
+
${prefix("compiler")}${versions.compiler || "unknown"}`;
|
|
5435
|
+
if (Object.keys(adaptors).length) {
|
|
5436
|
+
let allAdaptors = Object.keys(adaptors);
|
|
5437
|
+
if (adaptor) {
|
|
5438
|
+
allAdaptors = allAdaptors.filter((name) => adaptor.startsWith(name));
|
|
5439
|
+
}
|
|
5440
|
+
str += "\n" + allAdaptors.sort().map((adaptorName) => `${prefix(adaptorName)}${adaptors[adaptorName]}`).join("\n");
|
|
5441
|
+
}
|
|
5442
|
+
return str;
|
|
5443
|
+
};
|
|
5444
|
+
|
|
5445
|
+
// src/events/run-start.ts
|
|
5446
|
+
async function onRunStart(context, event) {
|
|
5447
|
+
const time = (timestamp() - BigInt(1e7)).toString();
|
|
5448
|
+
const { channel, state } = context;
|
|
5449
|
+
state.activeRun = crypto4.randomUUID();
|
|
5450
|
+
state.activeJob = event.jobId;
|
|
5451
|
+
const job = state.plan.jobs.find(({ id }) => id === event.jobId);
|
|
5452
|
+
const input_dataclip_id = state.inputDataclips[event.jobId];
|
|
5453
|
+
const versions = {
|
|
5454
|
+
worker: package_default.version,
|
|
5455
|
+
...event.versions
|
|
5456
|
+
};
|
|
5457
|
+
const versionLogContext = {
|
|
5458
|
+
...context,
|
|
5459
|
+
state: {
|
|
5460
|
+
...state,
|
|
5461
|
+
activeRun: state.activeRun
|
|
5462
|
+
}
|
|
5463
|
+
};
|
|
5464
|
+
await sendEvent(channel, RUN_START, {
|
|
5465
|
+
run_id: state.activeRun,
|
|
5466
|
+
job_id: state.activeJob,
|
|
5467
|
+
input_dataclip_id,
|
|
5468
|
+
versions
|
|
5469
|
+
});
|
|
5470
|
+
const versionMessage = versions_default(
|
|
5471
|
+
versionLogContext.state.activeRun,
|
|
5472
|
+
versions,
|
|
5473
|
+
job?.adaptor
|
|
5474
|
+
);
|
|
5475
|
+
await onJobLog(versionLogContext, {
|
|
5476
|
+
time,
|
|
5477
|
+
message: [versionMessage],
|
|
5478
|
+
level: "info",
|
|
5479
|
+
name: "VER"
|
|
5480
|
+
});
|
|
5481
|
+
return;
|
|
5482
|
+
}
|
|
5483
|
+
|
|
5484
|
+
// src/util/throttle.ts
|
|
5485
|
+
var createThrottler = () => {
|
|
5486
|
+
const q = [];
|
|
5487
|
+
let activePromise;
|
|
5488
|
+
const add = (fn) => {
|
|
5489
|
+
return (...args2) => new Promise((resolve5, reject) => {
|
|
5490
|
+
q.push({ fn, args: args2, resolve: resolve5, reject });
|
|
5491
|
+
shift();
|
|
5492
|
+
});
|
|
5493
|
+
};
|
|
5494
|
+
const shift = () => {
|
|
5495
|
+
if (activePromise) {
|
|
5496
|
+
return;
|
|
5497
|
+
}
|
|
5498
|
+
const next = q.shift();
|
|
5499
|
+
if (next) {
|
|
5500
|
+
const { fn, args: args2, resolve: resolve5, reject } = next;
|
|
5501
|
+
activePromise = fn(...args2).then(resolve5).catch(reject).finally(() => {
|
|
5502
|
+
activePromise = void 0;
|
|
5503
|
+
shift();
|
|
5504
|
+
});
|
|
5505
|
+
}
|
|
5506
|
+
};
|
|
5507
|
+
return add;
|
|
5508
|
+
};
|
|
5509
|
+
var throttle_default = createThrottler;
|
|
5510
|
+
|
|
5316
5511
|
// src/api/execute.ts
|
|
5317
5512
|
var enc = new TextDecoder("utf-8");
|
|
5318
5513
|
var eventMap = {
|
|
@@ -5326,7 +5521,8 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
|
|
|
5326
5521
|
}) {
|
|
5327
5522
|
logger2.info("executing ", plan.id);
|
|
5328
5523
|
const state = create_attempt_state_default(plan, options);
|
|
5329
|
-
const context = { channel, state, logger: logger2, onFinish };
|
|
5524
|
+
const context = { channel, state, logger: logger2, engine, onFinish };
|
|
5525
|
+
const throttle = throttle_default();
|
|
5330
5526
|
const addEvent = (eventName, handler) => {
|
|
5331
5527
|
const wrappedFn = async (event) => {
|
|
5332
5528
|
const lightningEvent = eventMap[eventName] ?? eventName;
|
|
@@ -5346,13 +5542,13 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
|
|
|
5346
5542
|
};
|
|
5347
5543
|
const listeners = Object.assign(
|
|
5348
5544
|
{},
|
|
5349
|
-
addEvent("workflow-start", onWorkflowStart),
|
|
5350
|
-
addEvent("job-start",
|
|
5351
|
-
addEvent("job-complete",
|
|
5352
|
-
addEvent("job-error", onJobError),
|
|
5353
|
-
addEvent("workflow-log", onJobLog),
|
|
5354
|
-
addEvent("workflow-complete", onWorkflowComplete),
|
|
5355
|
-
addEvent("workflow-error", onWorkflowError)
|
|
5545
|
+
addEvent("workflow-start", throttle(onWorkflowStart)),
|
|
5546
|
+
addEvent("job-start", throttle(onRunStart)),
|
|
5547
|
+
addEvent("job-complete", throttle(onRunComplete)),
|
|
5548
|
+
addEvent("job-error", throttle(onJobError)),
|
|
5549
|
+
addEvent("workflow-log", throttle(onJobLog)),
|
|
5550
|
+
addEvent("workflow-complete", throttle(onWorkflowComplete)),
|
|
5551
|
+
addEvent("workflow-error", throttle(onWorkflowError))
|
|
5356
5552
|
);
|
|
5357
5553
|
engine.listen(plan.id, listeners);
|
|
5358
5554
|
const resolvers = {
|
|
@@ -5381,59 +5577,17 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
|
|
|
5381
5577
|
return context;
|
|
5382
5578
|
}
|
|
5383
5579
|
var sendEvent = (channel, event, payload) => new Promise((resolve5, reject) => {
|
|
5384
|
-
channel.push(event, payload).receive("error", reject).receive("timeout", () =>
|
|
5580
|
+
channel.push(event, payload).receive("error", reject).receive("timeout", () => {
|
|
5581
|
+
reject(new Error("timeout"));
|
|
5582
|
+
}).receive("ok", resolve5);
|
|
5385
5583
|
});
|
|
5386
|
-
function onJobStart({ channel, state }, event) {
|
|
5387
|
-
state.activeRun = crypto3.randomUUID();
|
|
5388
|
-
state.activeJob = event.jobId;
|
|
5389
|
-
const input_dataclip_id = state.inputDataclips[event.jobId];
|
|
5390
|
-
return sendEvent(channel, RUN_START, {
|
|
5391
|
-
run_id: state.activeRun,
|
|
5392
|
-
job_id: state.activeJob,
|
|
5393
|
-
input_dataclip_id
|
|
5394
|
-
});
|
|
5395
|
-
}
|
|
5396
5584
|
function onJobError(context, event) {
|
|
5397
5585
|
const { state, error, jobId } = event;
|
|
5398
|
-
if (state
|
|
5399
|
-
|
|
5586
|
+
if (state?.errors?.[jobId]?.message === error.message) {
|
|
5587
|
+
return onRunComplete(context, event);
|
|
5400
5588
|
} else {
|
|
5401
|
-
|
|
5402
|
-
}
|
|
5403
|
-
}
|
|
5404
|
-
function onJobComplete({ channel, state }, event, error) {
|
|
5405
|
-
const dataclipId = crypto3.randomUUID();
|
|
5406
|
-
const run_id = state.activeRun;
|
|
5407
|
-
const job_id = state.activeJob;
|
|
5408
|
-
if (!state.dataclips) {
|
|
5409
|
-
state.dataclips = {};
|
|
5589
|
+
return onRunComplete(context, event, event.error);
|
|
5410
5590
|
}
|
|
5411
|
-
state.dataclips[dataclipId] = event.state;
|
|
5412
|
-
delete state.activeRun;
|
|
5413
|
-
delete state.activeJob;
|
|
5414
|
-
state.lastDataclipId = dataclipId;
|
|
5415
|
-
event.next?.forEach((nextJobId) => {
|
|
5416
|
-
state.inputDataclips[nextJobId] = dataclipId;
|
|
5417
|
-
});
|
|
5418
|
-
const { reason, error_message, error_type } = calculateJobExitReason(
|
|
5419
|
-
job_id,
|
|
5420
|
-
event.state,
|
|
5421
|
-
error
|
|
5422
|
-
);
|
|
5423
|
-
state.reasons[job_id] = { reason, error_message, error_type };
|
|
5424
|
-
const evt = {
|
|
5425
|
-
run_id,
|
|
5426
|
-
job_id,
|
|
5427
|
-
output_dataclip_id: dataclipId,
|
|
5428
|
-
output_dataclip: stringify_default(event.state),
|
|
5429
|
-
reason,
|
|
5430
|
-
error_message,
|
|
5431
|
-
error_type,
|
|
5432
|
-
mem: event.mem,
|
|
5433
|
-
duration: event.duration,
|
|
5434
|
-
thread_id: event.threadId
|
|
5435
|
-
};
|
|
5436
|
-
return sendEvent(channel, RUN_COMPLETE, evt);
|
|
5437
5591
|
}
|
|
5438
5592
|
function onWorkflowStart({ channel }, _event) {
|
|
5439
5593
|
return sendEvent(channel, ATTEMPT_START);
|
|
@@ -5447,9 +5601,13 @@ async function onWorkflowComplete({ state, channel, onFinish }, _event) {
|
|
|
5447
5601
|
});
|
|
5448
5602
|
onFinish({ reason, state: result });
|
|
5449
5603
|
}
|
|
5450
|
-
async function onWorkflowError(
|
|
5604
|
+
async function onWorkflowError(context, event) {
|
|
5605
|
+
const { state, channel, logger: logger2, onFinish } = context;
|
|
5451
5606
|
try {
|
|
5452
5607
|
const reason = calculateJobExitReason("", { data: {} }, event);
|
|
5608
|
+
if (state.activeJob) {
|
|
5609
|
+
await onJobError(context, { error: event });
|
|
5610
|
+
}
|
|
5453
5611
|
await sendEvent(channel, ATTEMPT_COMPLETE, {
|
|
5454
5612
|
final_dataclip_id: state.lastDataclipId,
|
|
5455
5613
|
...reason
|
|
@@ -5458,6 +5616,7 @@ async function onWorkflowError({ state, channel, logger: logger2, onFinish }, ev
|
|
|
5458
5616
|
} catch (e) {
|
|
5459
5617
|
logger2.error("ERROR in workflow-error handler:", e.message);
|
|
5460
5618
|
logger2.error(e);
|
|
5619
|
+
onFinish({});
|
|
5461
5620
|
}
|
|
5462
5621
|
}
|
|
5463
5622
|
function onJobLog({ channel, state }, event) {
|
|
@@ -5790,12 +5949,14 @@ if (args.mock) {
|
|
|
5790
5949
|
engineReady(engine);
|
|
5791
5950
|
});
|
|
5792
5951
|
} else {
|
|
5793
|
-
createRTE({
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5952
|
+
createRTE({
|
|
5953
|
+
repoDir: args.repoDir,
|
|
5954
|
+
memoryLimitMb: args.runMemory,
|
|
5955
|
+
maxWorkers: args.capacity
|
|
5956
|
+
}).then((engine) => {
|
|
5957
|
+
logger.debug("engine created");
|
|
5958
|
+
engineReady(engine);
|
|
5959
|
+
});
|
|
5799
5960
|
}
|
|
5800
5961
|
/**
|
|
5801
5962
|
* @fileoverview Main entrypoint for libraries using yargs-parser in Node.js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/ws-worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A Websocket Worker to connect Lightning to a Runtime Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"@types/koa-logger": "^3.1.2",
|
|
15
15
|
"@types/ws": "^8.5.6",
|
|
16
16
|
"fast-safe-stringify": "^2.1.1",
|
|
17
|
+
"figures": "^5.0.0",
|
|
17
18
|
"human-id": "^4.1.0",
|
|
18
19
|
"jose": "^4.14.6",
|
|
19
20
|
"koa": "^2.13.4",
|
|
@@ -21,9 +22,9 @@
|
|
|
21
22
|
"koa-logger": "^3.2.1",
|
|
22
23
|
"phoenix": "^1.7.7",
|
|
23
24
|
"ws": "^8.14.1",
|
|
24
|
-
"@openfn/engine-multi": "0.2.
|
|
25
|
+
"@openfn/engine-multi": "0.2.4",
|
|
25
26
|
"@openfn/logger": "0.0.19",
|
|
26
|
-
"@openfn/runtime": "0.2.
|
|
27
|
+
"@openfn/runtime": "0.2.2"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/koa": "^2.13.5",
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
"tsup": "^6.2.3",
|
|
41
42
|
"typescript": "^4.6.4",
|
|
42
43
|
"yargs": "^17.6.2",
|
|
43
|
-
"@openfn/lightning-mock": "1.1.
|
|
44
|
+
"@openfn/lightning-mock": "1.1.6"
|
|
44
45
|
},
|
|
45
46
|
"files": [
|
|
46
47
|
"dist",
|