@openfn/ws-worker 0.8.1 → 1.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/CHANGELOG.md +32 -0
- package/dist/index.d.ts +49 -121
- package/dist/index.js +328 -193
- package/dist/start.js +427 -272
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -1,3 +1,110 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name2 in all)
|
|
8
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// package.json
|
|
12
|
+
var package_exports = {};
|
|
13
|
+
__export(package_exports, {
|
|
14
|
+
author: () => author,
|
|
15
|
+
bin: () => bin,
|
|
16
|
+
default: () => package_default,
|
|
17
|
+
dependencies: () => dependencies,
|
|
18
|
+
description: () => description,
|
|
19
|
+
devDependencies: () => devDependencies,
|
|
20
|
+
files: () => files,
|
|
21
|
+
license: () => license,
|
|
22
|
+
main: () => main,
|
|
23
|
+
name: () => name,
|
|
24
|
+
scripts: () => scripts,
|
|
25
|
+
type: () => type,
|
|
26
|
+
version: () => version
|
|
27
|
+
});
|
|
28
|
+
var name, version, description, main, type, scripts, bin, author, license, dependencies, devDependencies, files, package_default;
|
|
29
|
+
var init_package = __esm({
|
|
30
|
+
"package.json"() {
|
|
31
|
+
name = "@openfn/ws-worker";
|
|
32
|
+
version = "1.1.0";
|
|
33
|
+
description = "A Websocket Worker to connect Lightning to a Runtime Engine";
|
|
34
|
+
main = "dist/index.js";
|
|
35
|
+
type = "module";
|
|
36
|
+
scripts = {
|
|
37
|
+
test: "pnpm ava --serial",
|
|
38
|
+
"test:types": "pnpm tsc --noEmit --project tsconfig.json",
|
|
39
|
+
build: "tsup --config tsup.config.js",
|
|
40
|
+
"build:watch": "pnpm build --watch",
|
|
41
|
+
start: "ts-node-esm --transpile-only src/start.ts",
|
|
42
|
+
"start:prod": "node dist/start.js",
|
|
43
|
+
"start:watch": "nodemon -e ts,js --watch ../runtime-manager/dist --watch ./src --exec 'pnpm start'",
|
|
44
|
+
pack: "pnpm pack --pack-destination ../../dist"
|
|
45
|
+
};
|
|
46
|
+
bin = {
|
|
47
|
+
worker: "dist/start.js"
|
|
48
|
+
};
|
|
49
|
+
author = "Open Function Group <admin@openfn.org>";
|
|
50
|
+
license = "ISC";
|
|
51
|
+
dependencies = {
|
|
52
|
+
"@koa/router": "^12.0.0",
|
|
53
|
+
"@openfn/engine-multi": "workspace:*",
|
|
54
|
+
"@openfn/lexicon": "workspace:^",
|
|
55
|
+
"@openfn/logger": "workspace:*",
|
|
56
|
+
"@openfn/runtime": "workspace:*",
|
|
57
|
+
"@types/koa-logger": "^3.1.2",
|
|
58
|
+
"@types/ws": "^8.5.6",
|
|
59
|
+
"fast-safe-stringify": "^2.1.1",
|
|
60
|
+
figures: "^5.0.0",
|
|
61
|
+
"human-id": "^4.1.0",
|
|
62
|
+
jose: "^4.14.6",
|
|
63
|
+
koa: "^2.13.4",
|
|
64
|
+
"koa-bodyparser": "^4.4.0",
|
|
65
|
+
"koa-logger": "^3.2.1",
|
|
66
|
+
phoenix: "1.7.10",
|
|
67
|
+
ws: "^8.14.1"
|
|
68
|
+
};
|
|
69
|
+
devDependencies = {
|
|
70
|
+
"@openfn/lightning-mock": "workspace:*",
|
|
71
|
+
"@types/koa": "^2.13.5",
|
|
72
|
+
"@types/koa-bodyparser": "^4.3.10",
|
|
73
|
+
"@types/koa__router": "^12.0.1",
|
|
74
|
+
"@types/node": "^18.15.3",
|
|
75
|
+
"@types/nodemon": "1.19.3",
|
|
76
|
+
"@types/phoenix": "^1.6.2",
|
|
77
|
+
"@types/yargs": "^17.0.12",
|
|
78
|
+
ava: "5.1.0",
|
|
79
|
+
nodemon: "3.0.1",
|
|
80
|
+
"ts-node": "^10.9.1",
|
|
81
|
+
tslib: "^2.4.0",
|
|
82
|
+
tsup: "^6.2.3",
|
|
83
|
+
typescript: "^4.6.4",
|
|
84
|
+
yargs: "^17.6.2"
|
|
85
|
+
};
|
|
86
|
+
files = [
|
|
87
|
+
"dist",
|
|
88
|
+
"README.md",
|
|
89
|
+
"CHANGELOG.md"
|
|
90
|
+
];
|
|
91
|
+
package_default = {
|
|
92
|
+
name,
|
|
93
|
+
version,
|
|
94
|
+
description,
|
|
95
|
+
main,
|
|
96
|
+
type,
|
|
97
|
+
scripts,
|
|
98
|
+
bin,
|
|
99
|
+
author,
|
|
100
|
+
license,
|
|
101
|
+
dependencies,
|
|
102
|
+
devDependencies,
|
|
103
|
+
files
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
1
108
|
// src/server.ts
|
|
2
109
|
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
3
110
|
import Koa from "koa";
|
|
@@ -10,8 +117,8 @@ import { createMockLogger as createMockLogger2 } from "@openfn/logger";
|
|
|
10
117
|
// src/events.ts
|
|
11
118
|
var CLAIM = "claim";
|
|
12
119
|
var GET_PLAN = "fetch:plan";
|
|
13
|
-
var GET_CREDENTIAL = "fetch:credential";
|
|
14
120
|
var GET_DATACLIP = "fetch:dataclip";
|
|
121
|
+
var GET_CREDENTIAL = "fetch:credential";
|
|
15
122
|
var RUN_START = "run:start";
|
|
16
123
|
var RUN_COMPLETE = "run:complete";
|
|
17
124
|
var RUN_LOG = "run:log";
|
|
@@ -35,6 +142,7 @@ var destroy = async (app, logger) => {
|
|
|
35
142
|
await waitForRuns(app, logger);
|
|
36
143
|
await app.engine.destroy();
|
|
37
144
|
app.socket?.disconnect();
|
|
145
|
+
logger.info("Server closed....");
|
|
38
146
|
resolve();
|
|
39
147
|
})
|
|
40
148
|
]);
|
|
@@ -59,6 +167,7 @@ var waitForRuns = (app, logger) => new Promise((resolve) => {
|
|
|
59
167
|
log();
|
|
60
168
|
app.events.on(INTERNAL_RUN_COMPLETE, onRunComplete);
|
|
61
169
|
} else {
|
|
170
|
+
logger.debug("No active rns detected");
|
|
62
171
|
resolve();
|
|
63
172
|
}
|
|
64
173
|
});
|
|
@@ -106,10 +215,22 @@ var tryWithBackoff = (fn, opts = {}) => {
|
|
|
106
215
|
var try_with_backoff_default = tryWithBackoff;
|
|
107
216
|
|
|
108
217
|
// src/api/claim.ts
|
|
218
|
+
import crypto from "node:crypto";
|
|
219
|
+
import * as jose from "jose";
|
|
109
220
|
import { createMockLogger } from "@openfn/logger";
|
|
110
221
|
var mockLogger = createMockLogger();
|
|
111
|
-
var
|
|
222
|
+
var verifyToken = async (token, publicKey) => {
|
|
223
|
+
const key = crypto.createPublicKey(publicKey);
|
|
224
|
+
const { payload } = await jose.jwtVerify(token, key, {
|
|
225
|
+
issuer: "Lightning"
|
|
226
|
+
});
|
|
227
|
+
if (payload) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
var claim = (app, logger = mockLogger, options = {}) => {
|
|
112
232
|
return new Promise((resolve, reject) => {
|
|
233
|
+
const { maxWorkers = 5 } = options;
|
|
113
234
|
const activeWorkers = Object.keys(app.workflows).length;
|
|
114
235
|
if (activeWorkers >= maxWorkers) {
|
|
115
236
|
return reject(new Error("Server at capacity"));
|
|
@@ -123,7 +244,21 @@ var claim = (app, logger = mockLogger, maxWorkers = 5) => {
|
|
|
123
244
|
if (!runs?.length) {
|
|
124
245
|
return reject(new Error("No runs returned"));
|
|
125
246
|
}
|
|
126
|
-
runs.forEach((run) => {
|
|
247
|
+
runs.forEach(async (run) => {
|
|
248
|
+
if (app.options?.runPublicKey) {
|
|
249
|
+
try {
|
|
250
|
+
await verifyToken(run.token, app.options.runPublicKey);
|
|
251
|
+
logger.debug("verified run token for", run.id);
|
|
252
|
+
} catch (e) {
|
|
253
|
+
logger.error("Error validating run token");
|
|
254
|
+
logger.error(e);
|
|
255
|
+
reject();
|
|
256
|
+
app.destroy();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
logger.debug("skipping run token validation for", run.id);
|
|
261
|
+
}
|
|
127
262
|
logger.debug("starting run", run.id);
|
|
128
263
|
app.execute(run);
|
|
129
264
|
resolve();
|
|
@@ -144,10 +279,15 @@ var startWorkloop = (app, logger, minBackoff, maxBackoff, maxWorkers) => {
|
|
|
144
279
|
let cancelled = false;
|
|
145
280
|
const workLoop = () => {
|
|
146
281
|
if (!cancelled) {
|
|
147
|
-
promise = try_with_backoff_default(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
282
|
+
promise = try_with_backoff_default(
|
|
283
|
+
() => claim_default(app, logger, {
|
|
284
|
+
maxWorkers
|
|
285
|
+
}),
|
|
286
|
+
{
|
|
287
|
+
min: minBackoff,
|
|
288
|
+
max: maxBackoff
|
|
289
|
+
}
|
|
290
|
+
);
|
|
151
291
|
promise.then(() => {
|
|
152
292
|
if (!cancelled) {
|
|
153
293
|
setTimeout(workLoop, minBackoff);
|
|
@@ -165,8 +305,8 @@ var startWorkloop = (app, logger, minBackoff, maxBackoff, maxWorkers) => {
|
|
|
165
305
|
};
|
|
166
306
|
var workloop_default = startWorkloop;
|
|
167
307
|
|
|
168
|
-
// src/util/convert-
|
|
169
|
-
import
|
|
308
|
+
// src/util/convert-lightning-plan.ts
|
|
309
|
+
import crypto2 from "node:crypto";
|
|
170
310
|
var conditions = {
|
|
171
311
|
on_job_success: (upstreamId) => `Boolean(!state?.errors?.["${upstreamId}"] ?? true)`,
|
|
172
312
|
on_job_failure: (upstreamId) => `Boolean(state?.errors && state.errors["${upstreamId}"])`,
|
|
@@ -186,19 +326,30 @@ var mapTriggerEdgeCondition = (edge) => {
|
|
|
186
326
|
return true;
|
|
187
327
|
return condition;
|
|
188
328
|
};
|
|
189
|
-
var
|
|
190
|
-
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
|
|
329
|
+
var convert_lightning_plan_default = (run) => {
|
|
330
|
+
const runtimeOpts = {};
|
|
331
|
+
const engineOpts = {};
|
|
332
|
+
if (run.options) {
|
|
333
|
+
if (run.options.runTimeoutMs) {
|
|
334
|
+
engineOpts.runTimeoutMs = run.options.runTimeoutMs;
|
|
335
|
+
}
|
|
336
|
+
if (run.options.sanitize) {
|
|
337
|
+
engineOpts.sanitize = run.options.sanitize;
|
|
338
|
+
}
|
|
339
|
+
if (run.options.hasOwnProperty("output_dataclips")) {
|
|
340
|
+
engineOpts.outputDataclips = run.options.output_dataclips;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
194
343
|
const plan = {
|
|
195
|
-
id: run.id
|
|
344
|
+
id: run.id,
|
|
345
|
+
options: runtimeOpts
|
|
196
346
|
};
|
|
347
|
+
let initialState;
|
|
197
348
|
if (run.dataclip_id) {
|
|
198
|
-
|
|
349
|
+
initialState = run.dataclip_id;
|
|
199
350
|
}
|
|
200
351
|
if (run.starting_node_id) {
|
|
201
|
-
|
|
352
|
+
runtimeOpts.start = run.starting_node_id;
|
|
202
353
|
}
|
|
203
354
|
const nodes = {};
|
|
204
355
|
const edges = run.edges ?? [];
|
|
@@ -210,27 +361,33 @@ var convert_run_default = (run) => {
|
|
|
210
361
|
};
|
|
211
362
|
const connectedEdges = edges.filter((e) => e.source_trigger_id === id);
|
|
212
363
|
if (connectedEdges.length) {
|
|
213
|
-
nodes[id].next = connectedEdges.reduce(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
364
|
+
nodes[id].next = connectedEdges.reduce(
|
|
365
|
+
(obj, edge) => {
|
|
366
|
+
if (edge.enabled !== false) {
|
|
367
|
+
obj[edge.target_job_id] = mapTriggerEdgeCondition(edge);
|
|
368
|
+
}
|
|
369
|
+
return obj;
|
|
370
|
+
},
|
|
371
|
+
{}
|
|
372
|
+
);
|
|
219
373
|
} else {
|
|
220
374
|
}
|
|
221
375
|
});
|
|
222
376
|
}
|
|
223
377
|
if (run.jobs?.length) {
|
|
224
|
-
run.jobs.forEach((
|
|
225
|
-
const id =
|
|
226
|
-
|
|
378
|
+
run.jobs.forEach((step) => {
|
|
379
|
+
const id = step.id || crypto2.randomUUID();
|
|
380
|
+
const job = {
|
|
227
381
|
id,
|
|
228
|
-
configuration:
|
|
229
|
-
expression:
|
|
230
|
-
adaptor:
|
|
382
|
+
configuration: step.credential || step.credential_id,
|
|
383
|
+
expression: step.body,
|
|
384
|
+
adaptor: step.adaptor
|
|
231
385
|
};
|
|
232
|
-
if (
|
|
233
|
-
|
|
386
|
+
if (step.name) {
|
|
387
|
+
job.name = step.name;
|
|
388
|
+
}
|
|
389
|
+
if (step.state) {
|
|
390
|
+
job.state = step.state;
|
|
234
391
|
}
|
|
235
392
|
const next = edges.filter((e) => e.source_job_id === id).reduce((obj, edge) => {
|
|
236
393
|
const newEdge = {};
|
|
@@ -245,21 +402,32 @@ var convert_run_default = (run) => {
|
|
|
245
402
|
return obj;
|
|
246
403
|
}, {});
|
|
247
404
|
if (Object.keys(next).length) {
|
|
248
|
-
|
|
405
|
+
job.next = next;
|
|
249
406
|
}
|
|
407
|
+
nodes[id] = job;
|
|
250
408
|
});
|
|
251
409
|
}
|
|
252
|
-
plan.
|
|
410
|
+
plan.workflow = {
|
|
411
|
+
steps: Object.values(nodes)
|
|
412
|
+
};
|
|
413
|
+
if (run.name) {
|
|
414
|
+
plan.workflow.name = run.name;
|
|
415
|
+
}
|
|
253
416
|
return {
|
|
254
417
|
plan,
|
|
255
|
-
options:
|
|
418
|
+
options: engineOpts,
|
|
419
|
+
input: initialState || {}
|
|
256
420
|
};
|
|
257
421
|
};
|
|
258
422
|
|
|
259
423
|
// src/util/get-with-reply.ts
|
|
260
|
-
var get_with_reply_default = (channel, event, payload) => new Promise((resolve) => {
|
|
424
|
+
var get_with_reply_default = (channel, event, payload) => new Promise((resolve, reject) => {
|
|
261
425
|
channel.push(event, payload).receive("ok", (evt) => {
|
|
262
426
|
resolve(evt);
|
|
427
|
+
}).receive("error", (e) => {
|
|
428
|
+
reject(e);
|
|
429
|
+
}).receive("timeout", (e) => {
|
|
430
|
+
reject(e);
|
|
263
431
|
});
|
|
264
432
|
});
|
|
265
433
|
|
|
@@ -273,19 +441,20 @@ var stringify_default = (obj) => stringify(obj, (_key, value) => {
|
|
|
273
441
|
});
|
|
274
442
|
|
|
275
443
|
// src/util/create-run-state.ts
|
|
276
|
-
var create_run_state_default = (plan,
|
|
444
|
+
var create_run_state_default = (plan, input) => {
|
|
277
445
|
const state = {
|
|
278
|
-
plan,
|
|
279
446
|
lastDataclipId: "",
|
|
280
447
|
dataclips: {},
|
|
281
448
|
inputDataclips: {},
|
|
282
449
|
reasons: {},
|
|
283
|
-
|
|
450
|
+
plan,
|
|
451
|
+
input
|
|
284
452
|
};
|
|
285
|
-
if (typeof
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
453
|
+
if (typeof input === "string") {
|
|
454
|
+
const jobs = plan.workflow.steps;
|
|
455
|
+
let startNode = jobs[0];
|
|
456
|
+
if (plan.options.start) {
|
|
457
|
+
startNode = jobs.find(({ id }) => id === plan.options.start);
|
|
289
458
|
}
|
|
290
459
|
const initialRuns = [];
|
|
291
460
|
if (!startNode.expression) {
|
|
@@ -294,7 +463,7 @@ var create_run_state_default = (plan, options = {}) => {
|
|
|
294
463
|
initialRuns.push(startNode.id);
|
|
295
464
|
}
|
|
296
465
|
initialRuns.forEach((id) => {
|
|
297
|
-
state.inputDataclips[id] =
|
|
466
|
+
state.inputDataclips[id] = input;
|
|
298
467
|
});
|
|
299
468
|
} else {
|
|
300
469
|
}
|
|
@@ -328,8 +497,59 @@ var createThrottler = () => {
|
|
|
328
497
|
};
|
|
329
498
|
var throttle_default = createThrottler;
|
|
330
499
|
|
|
500
|
+
// src/events/run-start.ts
|
|
501
|
+
import { timestamp } from "@openfn/logger";
|
|
502
|
+
|
|
503
|
+
// src/util/versions.ts
|
|
504
|
+
import { mainSymbols } from "figures";
|
|
505
|
+
var { triangleRightSmall: t } = mainSymbols;
|
|
506
|
+
var versions_default = (versions) => {
|
|
507
|
+
let longest = "worker".length;
|
|
508
|
+
for (const v in versions) {
|
|
509
|
+
longest = Math.max(v.length, longest);
|
|
510
|
+
}
|
|
511
|
+
const { node, worker, compiler, runtime, engine, ...adaptors } = versions;
|
|
512
|
+
const prefix = (str2) => ` ${t} ${str2.padEnd(longest + 4, " ")}`;
|
|
513
|
+
let str = `Versions:
|
|
514
|
+
${prefix("node.js")}${versions.node || "unknown"}
|
|
515
|
+
${prefix("worker")}${versions.worker || "unknown"}`;
|
|
516
|
+
if (Object.keys(adaptors).length) {
|
|
517
|
+
let allAdaptors = Object.keys(adaptors);
|
|
518
|
+
str += "\n" + allAdaptors.sort().map(
|
|
519
|
+
(adaptorName) => `${prefix(adaptorName)}${adaptors[adaptorName].sort().join(", ")}`
|
|
520
|
+
).join("\n");
|
|
521
|
+
}
|
|
522
|
+
return str;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// src/events/run-start.ts
|
|
526
|
+
init_package();
|
|
527
|
+
async function onRunStart(context, event) {
|
|
528
|
+
const { channel, state } = context;
|
|
529
|
+
const time = (timestamp() - BigInt(1e7)).toString();
|
|
530
|
+
const versionLogContext = {
|
|
531
|
+
...context,
|
|
532
|
+
state: {
|
|
533
|
+
...state,
|
|
534
|
+
activeStep: state.activeStep
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
const versions = {
|
|
538
|
+
worker: package_default.version,
|
|
539
|
+
...event.versions
|
|
540
|
+
};
|
|
541
|
+
await sendEvent(channel, RUN_START, { versions });
|
|
542
|
+
const versionMessage = versions_default(versions);
|
|
543
|
+
await onJobLog(versionLogContext, {
|
|
544
|
+
time,
|
|
545
|
+
message: [versionMessage],
|
|
546
|
+
level: "info",
|
|
547
|
+
name: "VER"
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
331
551
|
// src/events/step-complete.ts
|
|
332
|
-
import
|
|
552
|
+
import crypto3 from "node:crypto";
|
|
333
553
|
|
|
334
554
|
// src/api/reasons.ts
|
|
335
555
|
var calculateJobExitReason = (jobId, state = { data: {} }, error) => {
|
|
@@ -355,7 +575,7 @@ var isLeafNode = (state, job) => {
|
|
|
355
575
|
};
|
|
356
576
|
var calculateRunExitReason = (state) => {
|
|
357
577
|
if (state.plan && state.reasons) {
|
|
358
|
-
const leafJobReasons = state.plan.
|
|
578
|
+
const leafJobReasons = state.plan.workflow.steps.filter((job) => isLeafNode(state, job)).map(({ id }) => state.reasons[id]);
|
|
359
579
|
const fail = leafJobReasons.find((r) => r && r.reason === "fail");
|
|
360
580
|
if (fail) {
|
|
361
581
|
return fail;
|
|
@@ -365,8 +585,8 @@ var calculateRunExitReason = (state) => {
|
|
|
365
585
|
};
|
|
366
586
|
|
|
367
587
|
// src/events/step-complete.ts
|
|
368
|
-
function onStepComplete({ channel, state }, event, error) {
|
|
369
|
-
const dataclipId =
|
|
588
|
+
function onStepComplete({ channel, state, options }, event, error) {
|
|
589
|
+
const dataclipId = crypto3.randomUUID();
|
|
370
590
|
const step_id = state.activeStep;
|
|
371
591
|
const job_id = state.activeJob;
|
|
372
592
|
if (!state.dataclips) {
|
|
@@ -390,7 +610,6 @@ function onStepComplete({ channel, state }, event, error) {
|
|
|
390
610
|
step_id,
|
|
391
611
|
job_id,
|
|
392
612
|
output_dataclip_id: dataclipId,
|
|
393
|
-
output_dataclip: stringify_default(outputState),
|
|
394
613
|
reason,
|
|
395
614
|
error_message,
|
|
396
615
|
error_type,
|
|
@@ -398,137 +617,24 @@ function onStepComplete({ channel, state }, event, error) {
|
|
|
398
617
|
duration: event.duration,
|
|
399
618
|
thread_id: event.threadId
|
|
400
619
|
};
|
|
620
|
+
if (!options || options.outputDataclips !== false) {
|
|
621
|
+
evt.output_dataclip = stringify_default(outputState);
|
|
622
|
+
}
|
|
401
623
|
return sendEvent(channel, STEP_COMPLETE, evt);
|
|
402
624
|
}
|
|
403
625
|
|
|
404
626
|
// src/events/step-start.ts
|
|
405
|
-
import
|
|
406
|
-
import { timestamp } from "@openfn/logger";
|
|
407
|
-
|
|
408
|
-
// package.json
|
|
409
|
-
var package_default = {
|
|
410
|
-
name: "@openfn/ws-worker",
|
|
411
|
-
version: "0.8.1",
|
|
412
|
-
description: "A Websocket Worker to connect Lightning to a Runtime Engine",
|
|
413
|
-
main: "dist/index.js",
|
|
414
|
-
type: "module",
|
|
415
|
-
scripts: {
|
|
416
|
-
test: "pnpm ava --serial",
|
|
417
|
-
"test:types": "pnpm tsc --noEmit --project tsconfig.json",
|
|
418
|
-
build: "tsup --config tsup.config.js",
|
|
419
|
-
"build:watch": "pnpm build --watch",
|
|
420
|
-
start: "ts-node-esm --transpile-only src/start.ts",
|
|
421
|
-
"start:prod": "node dist/start.js",
|
|
422
|
-
"start:watch": "nodemon -e ts,js --watch ../runtime-manager/dist --watch ./src --exec 'pnpm start'",
|
|
423
|
-
pack: "pnpm pack --pack-destination ../../dist"
|
|
424
|
-
},
|
|
425
|
-
bin: {
|
|
426
|
-
worker: "dist/start.js"
|
|
427
|
-
},
|
|
428
|
-
author: "Open Function Group <admin@openfn.org>",
|
|
429
|
-
license: "ISC",
|
|
430
|
-
dependencies: {
|
|
431
|
-
"@koa/router": "^12.0.0",
|
|
432
|
-
"@openfn/engine-multi": "workspace:*",
|
|
433
|
-
"@openfn/logger": "workspace:*",
|
|
434
|
-
"@openfn/runtime": "workspace:*",
|
|
435
|
-
"@types/koa-logger": "^3.1.2",
|
|
436
|
-
"@types/ws": "^8.5.6",
|
|
437
|
-
"fast-safe-stringify": "^2.1.1",
|
|
438
|
-
figures: "^5.0.0",
|
|
439
|
-
"human-id": "^4.1.0",
|
|
440
|
-
jose: "^4.14.6",
|
|
441
|
-
koa: "^2.13.4",
|
|
442
|
-
"koa-bodyparser": "^4.4.0",
|
|
443
|
-
"koa-logger": "^3.2.1",
|
|
444
|
-
phoenix: "^1.7.7",
|
|
445
|
-
ws: "^8.14.1"
|
|
446
|
-
},
|
|
447
|
-
devDependencies: {
|
|
448
|
-
"@openfn/lightning-mock": "workspace:*",
|
|
449
|
-
"@types/koa": "^2.13.5",
|
|
450
|
-
"@types/koa-bodyparser": "^4.3.10",
|
|
451
|
-
"@types/koa__router": "^12.0.1",
|
|
452
|
-
"@types/node": "^18.15.3",
|
|
453
|
-
"@types/nodemon": "1.19.3",
|
|
454
|
-
"@types/phoenix": "^1.6.2",
|
|
455
|
-
"@types/yargs": "^17.0.12",
|
|
456
|
-
ava: "5.1.0",
|
|
457
|
-
nodemon: "3.0.1",
|
|
458
|
-
"ts-node": "^10.9.1",
|
|
459
|
-
tslib: "^2.4.0",
|
|
460
|
-
tsup: "^6.2.3",
|
|
461
|
-
typescript: "^4.6.4",
|
|
462
|
-
yargs: "^17.6.2"
|
|
463
|
-
},
|
|
464
|
-
files: [
|
|
465
|
-
"dist",
|
|
466
|
-
"README.md",
|
|
467
|
-
"CHANGELOG.md"
|
|
468
|
-
]
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
// src/util/versions.ts
|
|
472
|
-
import { mainSymbols } from "figures";
|
|
473
|
-
var { triangleRightSmall: t } = mainSymbols;
|
|
474
|
-
var versions_default = (stepId, versions, adaptor) => {
|
|
475
|
-
let longest = "compiler".length;
|
|
476
|
-
for (const v in versions) {
|
|
477
|
-
longest = Math.max(v.length, longest);
|
|
478
|
-
}
|
|
479
|
-
const { node, compiler, engine, worker, runtime, ...adaptors } = versions;
|
|
480
|
-
const prefix = (str2) => ` ${t} ${str2.padEnd(longest + 4, " ")}`;
|
|
481
|
-
let str = `Versions for step ${stepId}:
|
|
482
|
-
${prefix("node.js")}${versions.node || "unknown"}
|
|
483
|
-
${prefix("worker")}${versions.worker || "unknown"}
|
|
484
|
-
${prefix("engine")}${versions.engine || "unknown"}`;
|
|
485
|
-
if (Object.keys(adaptors).length) {
|
|
486
|
-
let allAdaptors = Object.keys(adaptors);
|
|
487
|
-
if (adaptor) {
|
|
488
|
-
allAdaptors = allAdaptors.filter((name) => adaptor.startsWith(name));
|
|
489
|
-
}
|
|
490
|
-
str += "\n" + allAdaptors.sort().map((adaptorName) => `${prefix(adaptorName)}${adaptors[adaptorName]}`).join("\n");
|
|
491
|
-
}
|
|
492
|
-
return str;
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
// src/events/step-start.ts
|
|
627
|
+
import crypto4 from "node:crypto";
|
|
496
628
|
async function onStepStart(context, event) {
|
|
497
|
-
const time = (timestamp() - BigInt(1e7)).toString();
|
|
498
629
|
const { channel, state } = context;
|
|
499
|
-
state.activeStep =
|
|
630
|
+
state.activeStep = crypto4.randomUUID();
|
|
500
631
|
state.activeJob = event.jobId;
|
|
501
|
-
const job = state.plan.jobs.find(({ id }) => id === event.jobId);
|
|
502
632
|
const input_dataclip_id = state.inputDataclips[event.jobId];
|
|
503
|
-
const versions = {
|
|
504
|
-
worker: package_default.version,
|
|
505
|
-
...event.versions
|
|
506
|
-
};
|
|
507
|
-
const versionLogContext = {
|
|
508
|
-
...context,
|
|
509
|
-
state: {
|
|
510
|
-
...state,
|
|
511
|
-
activeStep: state.activeStep
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
633
|
await sendEvent(channel, STEP_START, {
|
|
515
634
|
step_id: state.activeStep,
|
|
516
635
|
job_id: state.activeJob,
|
|
517
|
-
input_dataclip_id
|
|
518
|
-
versions
|
|
636
|
+
input_dataclip_id
|
|
519
637
|
});
|
|
520
|
-
const versionMessage = versions_default(
|
|
521
|
-
versionLogContext.state.activeStep,
|
|
522
|
-
versions,
|
|
523
|
-
job?.adaptor
|
|
524
|
-
);
|
|
525
|
-
await onJobLog(versionLogContext, {
|
|
526
|
-
time,
|
|
527
|
-
message: [versionMessage],
|
|
528
|
-
level: "info",
|
|
529
|
-
name: "VER"
|
|
530
|
-
});
|
|
531
|
-
return;
|
|
532
638
|
}
|
|
533
639
|
|
|
534
640
|
// src/util/log-final-reason.ts
|
|
@@ -576,7 +682,7 @@ async function onRunError(context, event) {
|
|
|
576
682
|
});
|
|
577
683
|
onFinish({ reason });
|
|
578
684
|
} catch (e) {
|
|
579
|
-
logger.error("ERROR in
|
|
685
|
+
logger.error("ERROR in run-error handler:", e.message);
|
|
580
686
|
logger.error(e);
|
|
581
687
|
onFinish({});
|
|
582
688
|
}
|
|
@@ -591,11 +697,18 @@ var eventMap = {
|
|
|
591
697
|
"workflow-log": RUN_LOG,
|
|
592
698
|
"workflow-complete": RUN_COMPLETE
|
|
593
699
|
};
|
|
594
|
-
function execute(channel, engine, logger, plan, options = {}, onFinish = (_result) => {
|
|
700
|
+
function execute(channel, engine, logger, plan, input, options = {}, onFinish = (_result) => {
|
|
595
701
|
}) {
|
|
596
702
|
logger.info("executing ", plan.id);
|
|
597
|
-
const state = create_run_state_default(plan,
|
|
598
|
-
const context = {
|
|
703
|
+
const state = create_run_state_default(plan, input);
|
|
704
|
+
const context = {
|
|
705
|
+
channel,
|
|
706
|
+
state,
|
|
707
|
+
logger,
|
|
708
|
+
engine,
|
|
709
|
+
options,
|
|
710
|
+
onFinish
|
|
711
|
+
};
|
|
599
712
|
const throttle = throttle_default();
|
|
600
713
|
const addEvent = (eventName, handler) => {
|
|
601
714
|
const wrappedFn = async (event) => {
|
|
@@ -616,7 +729,7 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
|
|
|
616
729
|
};
|
|
617
730
|
const listeners = Object.assign(
|
|
618
731
|
{},
|
|
619
|
-
addEvent("workflow-start", throttle(
|
|
732
|
+
addEvent("workflow-start", throttle(onRunStart)),
|
|
620
733
|
addEvent("job-start", throttle(onStepStart)),
|
|
621
734
|
addEvent("job-complete", throttle(onStepComplete)),
|
|
622
735
|
addEvent("job-error", throttle(onJobError)),
|
|
@@ -628,17 +741,24 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
|
|
|
628
741
|
const resolvers = {
|
|
629
742
|
credential: (id) => loadCredential(channel, id)
|
|
630
743
|
};
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
744
|
+
setTimeout(async () => {
|
|
745
|
+
let loadedInput = input;
|
|
746
|
+
if (typeof input === "string") {
|
|
747
|
+
logger.debug("loading dataclip", input);
|
|
748
|
+
try {
|
|
749
|
+
loadedInput = await loadDataclip(channel, input);
|
|
750
|
+
logger.success("dataclip loaded");
|
|
751
|
+
} catch (e) {
|
|
752
|
+
return onRunError(context, {
|
|
753
|
+
workflowId: plan.id,
|
|
754
|
+
message: `Failed to load dataclip ${input}${e.message ? `: ${e.message}` : ""}`,
|
|
755
|
+
type: "DataClipError",
|
|
756
|
+
severity: "exception"
|
|
757
|
+
});
|
|
758
|
+
}
|
|
637
759
|
}
|
|
638
|
-
return plan;
|
|
639
|
-
}).then(() => {
|
|
640
760
|
try {
|
|
641
|
-
engine.execute(plan, { resolvers, ...options });
|
|
761
|
+
engine.execute(plan, loadedInput, { resolvers, ...options });
|
|
642
762
|
} catch (e) {
|
|
643
763
|
onRunError(context, {
|
|
644
764
|
workflowId: plan.id,
|
|
@@ -663,9 +783,6 @@ function onJobError(context, event) {
|
|
|
663
783
|
return onStepComplete(context, event, event.error);
|
|
664
784
|
}
|
|
665
785
|
}
|
|
666
|
-
function onWorkflowStart({ channel }, _event) {
|
|
667
|
-
return sendEvent(channel, RUN_START);
|
|
668
|
-
}
|
|
669
786
|
function onJobLog({ channel, state }, event) {
|
|
670
787
|
const timeInMicroseconds = BigInt(event.time) / BigInt(1e3);
|
|
671
788
|
const log = {
|
|
@@ -707,9 +824,9 @@ var joinRunChannel = (socket, token, runId, logger) => {
|
|
|
707
824
|
if (!didReceiveOk) {
|
|
708
825
|
didReceiveOk = true;
|
|
709
826
|
logger.success(`connected to ${channelName}`, e);
|
|
710
|
-
const { plan, options } = await loadRun(channel);
|
|
827
|
+
const { plan, options, input } = await loadRun(channel);
|
|
711
828
|
logger.debug("converted run as execution plan:", plan);
|
|
712
|
-
resolve({ channel, plan, options });
|
|
829
|
+
resolve({ channel, plan, options, input });
|
|
713
830
|
}
|
|
714
831
|
}).receive("error", (err) => {
|
|
715
832
|
logger.error(`error connecting to ${channelName}`, err);
|
|
@@ -720,16 +837,17 @@ var joinRunChannel = (socket, token, runId, logger) => {
|
|
|
720
837
|
var run_default = joinRunChannel;
|
|
721
838
|
async function loadRun(channel) {
|
|
722
839
|
const runBody = await get_with_reply_default(channel, GET_PLAN);
|
|
723
|
-
return
|
|
840
|
+
return convert_lightning_plan_default(runBody);
|
|
724
841
|
}
|
|
725
842
|
|
|
726
843
|
// src/channels/worker-queue.ts
|
|
727
844
|
import EventEmitter from "node:events";
|
|
728
845
|
import { Socket as PhxSocket } from "phoenix";
|
|
729
846
|
import { WebSocket } from "ws";
|
|
847
|
+
import { API_VERSION } from "@openfn/lexicon/lightning";
|
|
730
848
|
|
|
731
849
|
// src/util/worker-token.ts
|
|
732
|
-
import * as
|
|
850
|
+
import * as jose2 from "jose";
|
|
733
851
|
var alg = "HS256";
|
|
734
852
|
var generateWorkerToken = async (secret, workerId, logger) => {
|
|
735
853
|
if (!secret) {
|
|
@@ -744,7 +862,7 @@ var generateWorkerToken = async (secret, workerId, logger) => {
|
|
|
744
862
|
const claims = {
|
|
745
863
|
worker_id: workerId
|
|
746
864
|
};
|
|
747
|
-
const jwt = await new
|
|
865
|
+
const jwt = await new jose2.SignJWT(claims).setProtectedHeader({ alg }).setIssuedAt().setIssuer("urn:openfn:worker").sign(encodedSecret);
|
|
748
866
|
return jwt;
|
|
749
867
|
};
|
|
750
868
|
var worker_token_default = generateWorkerToken;
|
|
@@ -752,9 +870,15 @@ var worker_token_default = generateWorkerToken;
|
|
|
752
870
|
// src/channels/worker-queue.ts
|
|
753
871
|
var connectToWorkerQueue = (endpoint, serverId, secret, logger, SocketConstructor = PhxSocket) => {
|
|
754
872
|
const events = new EventEmitter();
|
|
755
|
-
worker_token_default(secret, serverId, logger).then((token) => {
|
|
873
|
+
worker_token_default(secret, serverId, logger).then(async (token) => {
|
|
874
|
+
const pkg = await Promise.resolve().then(() => (init_package(), package_exports));
|
|
875
|
+
const params = {
|
|
876
|
+
token,
|
|
877
|
+
api_version: API_VERSION,
|
|
878
|
+
worker_version: pkg.default.version
|
|
879
|
+
};
|
|
756
880
|
const socket = new SocketConstructor(endpoint, {
|
|
757
|
-
params
|
|
881
|
+
params,
|
|
758
882
|
transport: WebSocket
|
|
759
883
|
});
|
|
760
884
|
let didOpen = false;
|
|
@@ -793,6 +917,9 @@ var MAX_BACKOFF = 1e3 * 30;
|
|
|
793
917
|
function connect(app, logger, options = {}) {
|
|
794
918
|
logger.debug("Connecting to Lightning at", options.lightning);
|
|
795
919
|
const onConnect = ({ socket, channel }) => {
|
|
920
|
+
if (app.destroyed) {
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
796
923
|
logger.success("Connected to Lightning at", options.lightning);
|
|
797
924
|
app.socket = socket;
|
|
798
925
|
app.queueChannel = channel;
|
|
@@ -827,6 +954,9 @@ function connect(app, logger, options = {}) {
|
|
|
827
954
|
}
|
|
828
955
|
};
|
|
829
956
|
const onError = (e) => {
|
|
957
|
+
if (app.destroyed) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
830
960
|
logger.error(
|
|
831
961
|
"CRITICAL ERROR: could not connect to lightning at",
|
|
832
962
|
options.lightning
|
|
@@ -856,13 +986,15 @@ function createServer(engine, options = {}) {
|
|
|
856
986
|
process.send?.("READY");
|
|
857
987
|
router.get("/livez", healthcheck_default);
|
|
858
988
|
router.get("/", healthcheck_default);
|
|
989
|
+
app.options = options || {};
|
|
859
990
|
app.execute = async ({ id, token }) => {
|
|
860
991
|
if (app.socket) {
|
|
861
992
|
app.workflows[id] = true;
|
|
862
993
|
const {
|
|
863
994
|
channel: runChannel,
|
|
864
995
|
plan,
|
|
865
|
-
options: options2
|
|
996
|
+
options: options2,
|
|
997
|
+
input
|
|
866
998
|
} = await run_default(app.socket, token, id, logger);
|
|
867
999
|
const onFinish = () => {
|
|
868
1000
|
delete app.workflows[id];
|
|
@@ -874,6 +1006,7 @@ function createServer(engine, options = {}) {
|
|
|
874
1006
|
engine,
|
|
875
1007
|
logger,
|
|
876
1008
|
plan,
|
|
1009
|
+
input,
|
|
877
1010
|
options2,
|
|
878
1011
|
onFinish
|
|
879
1012
|
);
|
|
@@ -884,7 +1017,9 @@ function createServer(engine, options = {}) {
|
|
|
884
1017
|
};
|
|
885
1018
|
router.post("/claim", async (ctx) => {
|
|
886
1019
|
logger.info("triggering claim from POST request");
|
|
887
|
-
return claim_default(app, logger,
|
|
1020
|
+
return claim_default(app, logger, {
|
|
1021
|
+
maxWorkers: options.maxWorkflows
|
|
1022
|
+
}).then(() => {
|
|
888
1023
|
logger.info("claim complete: 1 run claimed");
|
|
889
1024
|
ctx.body = "complete";
|
|
890
1025
|
ctx.status = 200;
|