@openfn/ws-worker 0.1.5 → 0.1.8
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 +25 -0
- package/dist/index.d.ts +47 -2
- package/dist/index.js +74 -53
- package/dist/start.js +94 -61
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# ws-worker
|
|
2
2
|
|
|
3
|
+
## 0.1.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b8fd13d: Fix undefined log output
|
|
8
|
+
|
|
9
|
+
## 0.1.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 8f7f57b: Send timestamps as strings in microsecond precision
|
|
14
|
+
- Updated dependencies [ca701e8]
|
|
15
|
+
- @openfn/logger@0.0.19
|
|
16
|
+
- @openfn/engine-multi@0.1.5
|
|
17
|
+
- @openfn/runtime@0.0.33
|
|
18
|
+
|
|
19
|
+
## 0.1.6
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 5037c68: Support maxWorkers flag
|
|
24
|
+
Enforce minBackoff before claiming the next job
|
|
25
|
+
- Updated dependencies [ac7b0ca]
|
|
26
|
+
- @openfn/engine-multi@0.1.4
|
|
27
|
+
|
|
3
28
|
## 0.1.5
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
import Koa from 'koa';
|
|
2
|
-
import { Logger } from '@openfn/logger';
|
|
2
|
+
import { SanitizePolicies, Logger } from '@openfn/logger';
|
|
3
3
|
import { RuntimeEngine } from '@openfn/engine-multi';
|
|
4
|
+
import { Channel as Channel$1 } from 'phoenix';
|
|
5
|
+
import { ExecutionPlan } from '@openfn/runtime';
|
|
6
|
+
|
|
7
|
+
type AttemptOptions = {
|
|
8
|
+
timeout?: number;
|
|
9
|
+
sanitize?: SanitizePolicies;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type ReceiveHook = {
|
|
13
|
+
receive: (
|
|
14
|
+
status: 'ok' | 'timeout' | 'error',
|
|
15
|
+
callback: (payload?: any) => void
|
|
16
|
+
) => ReceiveHook;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// export declare class Socket extends PhxSocket {
|
|
20
|
+
// constructor(endpoint: string, options: { params: any });
|
|
21
|
+
// onOpen(callback: () => void): void;
|
|
22
|
+
// connect(): void;
|
|
23
|
+
// channel(channelName: string, params: any): Channel;
|
|
24
|
+
// }
|
|
25
|
+
|
|
26
|
+
interface Channel extends Channel$1 {
|
|
27
|
+
// on: (event: string, fn: (evt: any) => void) => void;
|
|
28
|
+
|
|
29
|
+
// TODO it would be super nice to infer the event from the payload
|
|
30
|
+
push: <P = any>(event: string, payload?: P) => ReceiveHook;
|
|
31
|
+
// join: () => ReceiveHook;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare type AttemptState = {
|
|
35
|
+
activeRun?: string;
|
|
36
|
+
activeJob?: string;
|
|
37
|
+
plan: ExecutionPlan;
|
|
38
|
+
options: AttemptOptions;
|
|
39
|
+
dataclips: Record<string, any>;
|
|
40
|
+
lastDataclipId?: string;
|
|
41
|
+
};
|
|
42
|
+
declare type Context = {
|
|
43
|
+
channel: Channel;
|
|
44
|
+
state: AttemptState;
|
|
45
|
+
logger: Logger;
|
|
46
|
+
onComplete: (result: any) => void;
|
|
47
|
+
};
|
|
4
48
|
|
|
5
49
|
declare type CLAIM_ATTEMPT = {
|
|
6
50
|
id: string;
|
|
@@ -22,7 +66,8 @@ declare type ServerOptions = {
|
|
|
22
66
|
interface ServerApp extends Koa {
|
|
23
67
|
id: string;
|
|
24
68
|
socket: any;
|
|
25
|
-
channel:
|
|
69
|
+
channel: Channel;
|
|
70
|
+
workflows: Record<string, true | Context>;
|
|
26
71
|
execute: ({ id, token }: CLAIM_ATTEMPT) => Promise<void>;
|
|
27
72
|
destroy: () => void;
|
|
28
73
|
killWorkloop: () => void;
|
package/dist/index.js
CHANGED
|
@@ -63,17 +63,21 @@ var RUN_COMPLETE = "run:complete";
|
|
|
63
63
|
|
|
64
64
|
// src/api/claim.ts
|
|
65
65
|
var mockLogger = createMockLogger();
|
|
66
|
-
var claim = (
|
|
66
|
+
var claim = (app, logger = mockLogger, maxWorkers = 5) => {
|
|
67
67
|
return new Promise((resolve, reject) => {
|
|
68
|
+
const activeWorkers = Object.keys(app.workflows).length;
|
|
69
|
+
if (activeWorkers >= maxWorkers) {
|
|
70
|
+
return reject(new Error("Server at capacity"));
|
|
71
|
+
}
|
|
68
72
|
logger.debug("requesting attempt...");
|
|
69
|
-
channel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
|
|
73
|
+
app.channel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
|
|
70
74
|
logger.debug(`pulled ${attempts.length} attempts`);
|
|
71
75
|
if (!attempts?.length) {
|
|
72
|
-
return reject(new Error("
|
|
76
|
+
return reject(new Error("No attempts returned"));
|
|
73
77
|
}
|
|
74
78
|
attempts.forEach((attempt) => {
|
|
75
79
|
logger.debug("starting attempt", attempt.id);
|
|
76
|
-
|
|
80
|
+
app.execute(attempt);
|
|
77
81
|
resolve();
|
|
78
82
|
});
|
|
79
83
|
});
|
|
@@ -82,18 +86,18 @@ var claim = (channel, execute2, logger = mockLogger) => {
|
|
|
82
86
|
var claim_default = claim;
|
|
83
87
|
|
|
84
88
|
// src/api/workloop.ts
|
|
85
|
-
var startWorkloop = (
|
|
89
|
+
var startWorkloop = (app, logger, minBackoff, maxBackoff, maxWorkers) => {
|
|
86
90
|
let promise;
|
|
87
91
|
let cancelled = false;
|
|
88
92
|
const workLoop = () => {
|
|
89
93
|
if (!cancelled) {
|
|
90
|
-
promise = try_with_backoff_default(() => claim_default(
|
|
94
|
+
promise = try_with_backoff_default(() => claim_default(app, logger, maxWorkers), {
|
|
91
95
|
min: minBackoff,
|
|
92
96
|
max: maxBackoff
|
|
93
97
|
});
|
|
94
98
|
promise.then(() => {
|
|
95
99
|
if (!cancelled) {
|
|
96
|
-
workLoop
|
|
100
|
+
setTimeout(workLoop, minBackoff);
|
|
97
101
|
}
|
|
98
102
|
});
|
|
99
103
|
}
|
|
@@ -212,62 +216,65 @@ var eventMap = {
|
|
|
212
216
|
"workflow-start": ATTEMPT_START,
|
|
213
217
|
"job-start": RUN_START,
|
|
214
218
|
"job-complete": RUN_COMPLETE,
|
|
215
|
-
log: ATTEMPT_LOG,
|
|
219
|
+
"workflow-log": ATTEMPT_LOG,
|
|
216
220
|
"workflow-complete": ATTEMPT_COMPLETE
|
|
217
221
|
};
|
|
218
|
-
function execute(channel, engine, logger, plan, options = {}) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
function execute(channel, engine, logger, plan, options = {}, onComplete = (_result) => {
|
|
223
|
+
}) {
|
|
224
|
+
logger.info("execute...");
|
|
225
|
+
const state = {
|
|
226
|
+
plan,
|
|
227
|
+
lastDataclipId: plan.initialState,
|
|
228
|
+
dataclips: {},
|
|
229
|
+
options
|
|
230
|
+
};
|
|
231
|
+
const context = { channel, state, logger, onComplete };
|
|
232
|
+
const addEvent = (eventName, handler) => {
|
|
233
|
+
const wrappedFn = async (event) => {
|
|
234
|
+
const lightningEvent = eventMap[eventName] ?? eventName;
|
|
235
|
+
try {
|
|
236
|
+
await handler(context, event);
|
|
237
|
+
logger.info(`${plan.id} :: ${lightningEvent} :: OK`);
|
|
238
|
+
} catch (e) {
|
|
239
|
+
logger.error(
|
|
240
|
+
`${plan.id} :: ${lightningEvent} :: ERR: ${e.message || e.toString()}`
|
|
241
|
+
);
|
|
242
|
+
logger.error(e);
|
|
243
|
+
}
|
|
226
244
|
};
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const wrappedFn = async (event) => {
|
|
230
|
-
const lightningEvent = eventMap[eventName];
|
|
231
|
-
try {
|
|
232
|
-
await handler(context, event);
|
|
233
|
-
logger.info(`${plan.id} :: ${lightningEvent} :: OK`);
|
|
234
|
-
} catch (e) {
|
|
235
|
-
logger.error(
|
|
236
|
-
`${plan.id} :: ${lightningEvent} :: ERR: ${e.message || e.toString()}`
|
|
237
|
-
);
|
|
238
|
-
logger.error(e);
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
return {
|
|
242
|
-
[eventName]: wrappedFn
|
|
243
|
-
};
|
|
244
|
-
};
|
|
245
|
-
const listeners = Object.assign(
|
|
246
|
-
{},
|
|
247
|
-
addEvent("workflow-start", onWorkflowStart),
|
|
248
|
-
addEvent("job-start", onJobStart),
|
|
249
|
-
addEvent("job-complete", onJobComplete),
|
|
250
|
-
addEvent("workflow-log", onJobLog),
|
|
251
|
-
addEvent("workflow-complete", onWorkflowComplete),
|
|
252
|
-
addEvent("workflow-error", onWorkflowError)
|
|
253
|
-
);
|
|
254
|
-
engine.listen(plan.id, listeners);
|
|
255
|
-
const resolvers = {
|
|
256
|
-
credential: (id) => loadCredential(channel, id)
|
|
245
|
+
return {
|
|
246
|
+
[eventName]: wrappedFn
|
|
257
247
|
};
|
|
248
|
+
};
|
|
249
|
+
const listeners = Object.assign(
|
|
250
|
+
{},
|
|
251
|
+
addEvent("workflow-start", onWorkflowStart),
|
|
252
|
+
addEvent("job-start", onJobStart),
|
|
253
|
+
addEvent("job-complete", onJobComplete),
|
|
254
|
+
addEvent("workflow-log", onJobLog),
|
|
255
|
+
addEvent("workflow-complete", onWorkflowComplete),
|
|
256
|
+
addEvent("workflow-error", onWorkflowError)
|
|
257
|
+
);
|
|
258
|
+
engine.listen(plan.id, listeners);
|
|
259
|
+
const resolvers = {
|
|
260
|
+
credential: (id) => loadCredential(channel, id)
|
|
261
|
+
};
|
|
262
|
+
Promise.resolve().then(async () => {
|
|
258
263
|
if (typeof plan.initialState === "string") {
|
|
259
264
|
logger.debug("loading dataclip", plan.initialState);
|
|
260
265
|
plan.initialState = await loadDataclip(channel, plan.initialState);
|
|
261
266
|
logger.success("dataclip loaded");
|
|
262
267
|
logger.debug(plan.initialState);
|
|
263
268
|
}
|
|
269
|
+
return plan;
|
|
270
|
+
}).then(() => {
|
|
264
271
|
try {
|
|
265
272
|
engine.execute(plan, { resolvers, ...options });
|
|
266
273
|
} catch (e) {
|
|
267
274
|
onWorkflowError(context, { workflowId: plan.id, message: e.message });
|
|
268
|
-
reject(e);
|
|
269
275
|
}
|
|
270
276
|
});
|
|
277
|
+
return context;
|
|
271
278
|
}
|
|
272
279
|
var sendEvent = (channel, event, payload) => new Promise((resolve, reject) => {
|
|
273
280
|
channel.push(event, payload).receive("error", reject).receive("timeout", () => reject(new Error("timeout"))).receive("ok", resolve);
|
|
@@ -321,12 +328,13 @@ async function onWorkflowError({ state, channel, onComplete }, event) {
|
|
|
321
328
|
onComplete({});
|
|
322
329
|
}
|
|
323
330
|
function onJobLog({ channel, state }, event) {
|
|
331
|
+
const timeInMicroseconds = BigInt(event.time) / BigInt(1e3);
|
|
324
332
|
const log = {
|
|
325
333
|
attempt_id: state.plan.id,
|
|
326
334
|
message: event.message,
|
|
327
335
|
source: event.name,
|
|
328
336
|
level: event.level,
|
|
329
|
-
timestamp:
|
|
337
|
+
timestamp: timeInMicroseconds.toString()
|
|
330
338
|
};
|
|
331
339
|
if (state.activeRun) {
|
|
332
340
|
log.run_id = state.activeRun;
|
|
@@ -443,11 +451,11 @@ function connect(app, engine, logger, options = {}) {
|
|
|
443
451
|
}
|
|
444
452
|
logger.info("Starting workloop");
|
|
445
453
|
app.killWorkloop = workloop_default(
|
|
446
|
-
|
|
447
|
-
app.execute,
|
|
454
|
+
app,
|
|
448
455
|
logger,
|
|
449
456
|
options.backoff?.min || MIN_BACKOFF,
|
|
450
|
-
options.backoff?.max || MAX_BACKOFF
|
|
457
|
+
options.backoff?.max || MAX_BACKOFF,
|
|
458
|
+
options.maxWorkflows
|
|
451
459
|
);
|
|
452
460
|
} else {
|
|
453
461
|
logger.break();
|
|
@@ -484,23 +492,36 @@ function createServer(engine, options = {}) {
|
|
|
484
492
|
logger.debug(str);
|
|
485
493
|
})
|
|
486
494
|
);
|
|
495
|
+
app.workflows = {};
|
|
487
496
|
const server = app.listen(port);
|
|
488
497
|
logger.success(`ws-worker ${app.id} listening on ${port}`);
|
|
489
498
|
app.execute = async ({ id, token }) => {
|
|
490
499
|
if (app.socket) {
|
|
500
|
+
app.workflows[id] = true;
|
|
491
501
|
const {
|
|
492
502
|
channel: attemptChannel,
|
|
493
503
|
plan,
|
|
494
504
|
options: options2
|
|
495
505
|
} = await attempt_default(app.socket, token, id, logger);
|
|
496
|
-
|
|
506
|
+
const onComplete = () => {
|
|
507
|
+
delete app.workflows[id];
|
|
508
|
+
};
|
|
509
|
+
const context = execute(
|
|
510
|
+
attemptChannel,
|
|
511
|
+
engine,
|
|
512
|
+
logger,
|
|
513
|
+
plan,
|
|
514
|
+
options2,
|
|
515
|
+
onComplete
|
|
516
|
+
);
|
|
517
|
+
app.workflows[id] = context;
|
|
497
518
|
} else {
|
|
498
519
|
logger.error("No lightning socket established");
|
|
499
520
|
}
|
|
500
521
|
};
|
|
501
522
|
router.post("/claim", async (ctx) => {
|
|
502
523
|
logger.info("triggering claim from POST request");
|
|
503
|
-
return claim_default(app
|
|
524
|
+
return claim_default(app, logger, options.maxWorkflows).then(() => {
|
|
504
525
|
logger.info("claim complete: 1 attempt claimed");
|
|
505
526
|
ctx.body = "complete";
|
|
506
527
|
ctx.status = 200;
|
package/dist/start.js
CHANGED
|
@@ -4917,19 +4917,27 @@ async function createMock() {
|
|
|
4917
4917
|
workflowId,
|
|
4918
4918
|
message,
|
|
4919
4919
|
level: "info",
|
|
4920
|
-
|
|
4920
|
+
time: (BigInt(Date.now()) * BigInt(1e3)).toString(),
|
|
4921
4921
|
name: "mck"
|
|
4922
4922
|
});
|
|
4923
4923
|
};
|
|
4924
4924
|
dispatch("job-start", { workflowId, jobId, runId });
|
|
4925
4925
|
info("Running job " + jobId);
|
|
4926
4926
|
let nextState = initialState;
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
info("Parsing expression as JSON state");
|
|
4930
|
-
info(nextState);
|
|
4931
|
-
} catch (e) {
|
|
4927
|
+
if (expression?.startsWith?.("wait@")) {
|
|
4928
|
+
const [_, delay] = expression.split("@");
|
|
4932
4929
|
nextState = initialState;
|
|
4930
|
+
await new Promise((resolve5) => {
|
|
4931
|
+
setTimeout(() => resolve5(), parseInt(delay));
|
|
4932
|
+
});
|
|
4933
|
+
} else {
|
|
4934
|
+
try {
|
|
4935
|
+
nextState = JSON.parse(expression);
|
|
4936
|
+
info("Parsing expression as JSON state");
|
|
4937
|
+
info(nextState);
|
|
4938
|
+
} catch (e) {
|
|
4939
|
+
nextState = initialState;
|
|
4940
|
+
}
|
|
4933
4941
|
}
|
|
4934
4942
|
dispatch("job-complete", { workflowId, jobId, state: nextState, runId });
|
|
4935
4943
|
return nextState;
|
|
@@ -5037,17 +5045,21 @@ var RUN_COMPLETE = "run:complete";
|
|
|
5037
5045
|
|
|
5038
5046
|
// src/api/claim.ts
|
|
5039
5047
|
var mockLogger = createMockLogger();
|
|
5040
|
-
var claim = (
|
|
5048
|
+
var claim = (app, logger2 = mockLogger, maxWorkers = 5) => {
|
|
5041
5049
|
return new Promise((resolve5, reject) => {
|
|
5050
|
+
const activeWorkers = Object.keys(app.workflows).length;
|
|
5051
|
+
if (activeWorkers >= maxWorkers) {
|
|
5052
|
+
return reject(new Error("Server at capacity"));
|
|
5053
|
+
}
|
|
5042
5054
|
logger2.debug("requesting attempt...");
|
|
5043
|
-
channel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
|
|
5055
|
+
app.channel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
|
|
5044
5056
|
logger2.debug(`pulled ${attempts.length} attempts`);
|
|
5045
5057
|
if (!attempts?.length) {
|
|
5046
|
-
return reject(new Error("
|
|
5058
|
+
return reject(new Error("No attempts returned"));
|
|
5047
5059
|
}
|
|
5048
5060
|
attempts.forEach((attempt) => {
|
|
5049
5061
|
logger2.debug("starting attempt", attempt.id);
|
|
5050
|
-
|
|
5062
|
+
app.execute(attempt);
|
|
5051
5063
|
resolve5();
|
|
5052
5064
|
});
|
|
5053
5065
|
});
|
|
@@ -5056,18 +5068,18 @@ var claim = (channel, execute2, logger2 = mockLogger) => {
|
|
|
5056
5068
|
var claim_default = claim;
|
|
5057
5069
|
|
|
5058
5070
|
// src/api/workloop.ts
|
|
5059
|
-
var startWorkloop = (
|
|
5071
|
+
var startWorkloop = (app, logger2, minBackoff2, maxBackoff2, maxWorkers) => {
|
|
5060
5072
|
let promise;
|
|
5061
5073
|
let cancelled = false;
|
|
5062
5074
|
const workLoop = () => {
|
|
5063
5075
|
if (!cancelled) {
|
|
5064
|
-
promise = try_with_backoff_default(() => claim_default(
|
|
5076
|
+
promise = try_with_backoff_default(() => claim_default(app, logger2, maxWorkers), {
|
|
5065
5077
|
min: minBackoff2,
|
|
5066
5078
|
max: maxBackoff2
|
|
5067
5079
|
});
|
|
5068
5080
|
promise.then(() => {
|
|
5069
5081
|
if (!cancelled) {
|
|
5070
|
-
workLoop
|
|
5082
|
+
setTimeout(workLoop, minBackoff2);
|
|
5071
5083
|
}
|
|
5072
5084
|
});
|
|
5073
5085
|
}
|
|
@@ -5186,62 +5198,65 @@ var eventMap = {
|
|
|
5186
5198
|
"workflow-start": ATTEMPT_START,
|
|
5187
5199
|
"job-start": RUN_START,
|
|
5188
5200
|
"job-complete": RUN_COMPLETE,
|
|
5189
|
-
log: ATTEMPT_LOG,
|
|
5201
|
+
"workflow-log": ATTEMPT_LOG,
|
|
5190
5202
|
"workflow-complete": ATTEMPT_COMPLETE
|
|
5191
5203
|
};
|
|
5192
|
-
function execute(channel, engine, logger2, plan, options = {}) {
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
};
|
|
5215
|
-
return {
|
|
5216
|
-
[eventName]: wrappedFn
|
|
5217
|
-
};
|
|
5204
|
+
function execute(channel, engine, logger2, plan, options = {}, onComplete = (_result) => {
|
|
5205
|
+
}) {
|
|
5206
|
+
logger2.info("execute...");
|
|
5207
|
+
const state = {
|
|
5208
|
+
plan,
|
|
5209
|
+
lastDataclipId: plan.initialState,
|
|
5210
|
+
dataclips: {},
|
|
5211
|
+
options
|
|
5212
|
+
};
|
|
5213
|
+
const context = { channel, state, logger: logger2, onComplete };
|
|
5214
|
+
const addEvent = (eventName, handler) => {
|
|
5215
|
+
const wrappedFn = async (event) => {
|
|
5216
|
+
const lightningEvent = eventMap[eventName] ?? eventName;
|
|
5217
|
+
try {
|
|
5218
|
+
await handler(context, event);
|
|
5219
|
+
logger2.info(`${plan.id} :: ${lightningEvent} :: OK`);
|
|
5220
|
+
} catch (e) {
|
|
5221
|
+
logger2.error(
|
|
5222
|
+
`${plan.id} :: ${lightningEvent} :: ERR: ${e.message || e.toString()}`
|
|
5223
|
+
);
|
|
5224
|
+
logger2.error(e);
|
|
5225
|
+
}
|
|
5218
5226
|
};
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
addEvent("workflow-start", onWorkflowStart),
|
|
5222
|
-
addEvent("job-start", onJobStart),
|
|
5223
|
-
addEvent("job-complete", onJobComplete),
|
|
5224
|
-
addEvent("workflow-log", onJobLog),
|
|
5225
|
-
addEvent("workflow-complete", onWorkflowComplete),
|
|
5226
|
-
addEvent("workflow-error", onWorkflowError)
|
|
5227
|
-
);
|
|
5228
|
-
engine.listen(plan.id, listeners);
|
|
5229
|
-
const resolvers = {
|
|
5230
|
-
credential: (id) => loadCredential(channel, id)
|
|
5227
|
+
return {
|
|
5228
|
+
[eventName]: wrappedFn
|
|
5231
5229
|
};
|
|
5230
|
+
};
|
|
5231
|
+
const listeners = Object.assign(
|
|
5232
|
+
{},
|
|
5233
|
+
addEvent("workflow-start", onWorkflowStart),
|
|
5234
|
+
addEvent("job-start", onJobStart),
|
|
5235
|
+
addEvent("job-complete", onJobComplete),
|
|
5236
|
+
addEvent("workflow-log", onJobLog),
|
|
5237
|
+
addEvent("workflow-complete", onWorkflowComplete),
|
|
5238
|
+
addEvent("workflow-error", onWorkflowError)
|
|
5239
|
+
);
|
|
5240
|
+
engine.listen(plan.id, listeners);
|
|
5241
|
+
const resolvers = {
|
|
5242
|
+
credential: (id) => loadCredential(channel, id)
|
|
5243
|
+
};
|
|
5244
|
+
Promise.resolve().then(async () => {
|
|
5232
5245
|
if (typeof plan.initialState === "string") {
|
|
5233
5246
|
logger2.debug("loading dataclip", plan.initialState);
|
|
5234
5247
|
plan.initialState = await loadDataclip(channel, plan.initialState);
|
|
5235
5248
|
logger2.success("dataclip loaded");
|
|
5236
5249
|
logger2.debug(plan.initialState);
|
|
5237
5250
|
}
|
|
5251
|
+
return plan;
|
|
5252
|
+
}).then(() => {
|
|
5238
5253
|
try {
|
|
5239
5254
|
engine.execute(plan, { resolvers, ...options });
|
|
5240
5255
|
} catch (e) {
|
|
5241
5256
|
onWorkflowError(context, { workflowId: plan.id, message: e.message });
|
|
5242
|
-
reject(e);
|
|
5243
5257
|
}
|
|
5244
5258
|
});
|
|
5259
|
+
return context;
|
|
5245
5260
|
}
|
|
5246
5261
|
var sendEvent = (channel, event, payload) => new Promise((resolve5, reject) => {
|
|
5247
5262
|
channel.push(event, payload).receive("error", reject).receive("timeout", () => reject(new Error("timeout"))).receive("ok", resolve5);
|
|
@@ -5295,12 +5310,13 @@ async function onWorkflowError({ state, channel, onComplete }, event) {
|
|
|
5295
5310
|
onComplete({});
|
|
5296
5311
|
}
|
|
5297
5312
|
function onJobLog({ channel, state }, event) {
|
|
5313
|
+
const timeInMicroseconds = BigInt(event.time) / BigInt(1e3);
|
|
5298
5314
|
const log = {
|
|
5299
5315
|
attempt_id: state.plan.id,
|
|
5300
5316
|
message: event.message,
|
|
5301
5317
|
source: event.name,
|
|
5302
5318
|
level: event.level,
|
|
5303
|
-
timestamp:
|
|
5319
|
+
timestamp: timeInMicroseconds.toString()
|
|
5304
5320
|
};
|
|
5305
5321
|
if (state.activeRun) {
|
|
5306
5322
|
log.run_id = state.activeRun;
|
|
@@ -5417,11 +5433,11 @@ function connect(app, engine, logger2, options = {}) {
|
|
|
5417
5433
|
}
|
|
5418
5434
|
logger2.info("Starting workloop");
|
|
5419
5435
|
app.killWorkloop = workloop_default(
|
|
5420
|
-
|
|
5421
|
-
app.execute,
|
|
5436
|
+
app,
|
|
5422
5437
|
logger2,
|
|
5423
5438
|
options.backoff?.min || MIN_BACKOFF,
|
|
5424
|
-
options.backoff?.max || MAX_BACKOFF
|
|
5439
|
+
options.backoff?.max || MAX_BACKOFF,
|
|
5440
|
+
options.maxWorkflows
|
|
5425
5441
|
);
|
|
5426
5442
|
} else {
|
|
5427
5443
|
logger2.break();
|
|
@@ -5458,23 +5474,36 @@ function createServer(engine, options = {}) {
|
|
|
5458
5474
|
logger2.debug(str);
|
|
5459
5475
|
})
|
|
5460
5476
|
);
|
|
5477
|
+
app.workflows = {};
|
|
5461
5478
|
const server = app.listen(port);
|
|
5462
5479
|
logger2.success(`ws-worker ${app.id} listening on ${port}`);
|
|
5463
5480
|
app.execute = async ({ id, token }) => {
|
|
5464
5481
|
if (app.socket) {
|
|
5482
|
+
app.workflows[id] = true;
|
|
5465
5483
|
const {
|
|
5466
5484
|
channel: attemptChannel,
|
|
5467
5485
|
plan,
|
|
5468
5486
|
options: options2
|
|
5469
5487
|
} = await attempt_default(app.socket, token, id, logger2);
|
|
5470
|
-
|
|
5488
|
+
const onComplete = () => {
|
|
5489
|
+
delete app.workflows[id];
|
|
5490
|
+
};
|
|
5491
|
+
const context = execute(
|
|
5492
|
+
attemptChannel,
|
|
5493
|
+
engine,
|
|
5494
|
+
logger2,
|
|
5495
|
+
plan,
|
|
5496
|
+
options2,
|
|
5497
|
+
onComplete
|
|
5498
|
+
);
|
|
5499
|
+
app.workflows[id] = context;
|
|
5471
5500
|
} else {
|
|
5472
5501
|
logger2.error("No lightning socket established");
|
|
5473
5502
|
}
|
|
5474
5503
|
};
|
|
5475
5504
|
router.post("/claim", async (ctx) => {
|
|
5476
5505
|
logger2.info("triggering claim from POST request");
|
|
5477
|
-
return claim_default(app
|
|
5506
|
+
return claim_default(app, logger2, options.maxWorkflows).then(() => {
|
|
5478
5507
|
logger2.info("claim complete: 1 attempt claimed");
|
|
5479
5508
|
ctx.body = "complete";
|
|
5480
5509
|
ctx.status = 200;
|
|
@@ -5535,6 +5564,10 @@ var args = yargs_default(hideBin(process.argv)).command("server", "Start a ws-wo
|
|
|
5535
5564
|
}).option("backoff", {
|
|
5536
5565
|
description: "Claim backoff rules: min/max (s)",
|
|
5537
5566
|
default: "1/10"
|
|
5567
|
+
}).option("capacity", {
|
|
5568
|
+
description: "max concurrent workers",
|
|
5569
|
+
default: 5,
|
|
5570
|
+
type: "number"
|
|
5538
5571
|
}).parse();
|
|
5539
5572
|
var logger = createLogger("SRV", { level: args.log });
|
|
5540
5573
|
if (args.lightning === "mock") {
|
|
@@ -5551,7 +5584,6 @@ if (args.lightning === "mock") {
|
|
|
5551
5584
|
args.secret = WORKER_SECRET;
|
|
5552
5585
|
}
|
|
5553
5586
|
var [minBackoff, maxBackoff] = args.backoff.split("/").map((n) => parseInt(n, 10) * 1e3);
|
|
5554
|
-
console.log(minBackoff, maxBackoff);
|
|
5555
5587
|
function engineReady(engine) {
|
|
5556
5588
|
server_default(engine, {
|
|
5557
5589
|
port: args.port,
|
|
@@ -5562,7 +5594,8 @@ function engineReady(engine) {
|
|
|
5562
5594
|
backoff: {
|
|
5563
5595
|
min: minBackoff,
|
|
5564
5596
|
max: maxBackoff
|
|
5565
|
-
}
|
|
5597
|
+
},
|
|
5598
|
+
maxWorkflows: args.capacity
|
|
5566
5599
|
});
|
|
5567
5600
|
}
|
|
5568
5601
|
if (args.mock) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/ws-worker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "A Websocket Worker to connect Lightning to a Runtime Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"koa-logger": "^3.2.1",
|
|
22
22
|
"phoenix": "^1.7.7",
|
|
23
23
|
"ws": "^8.14.1",
|
|
24
|
-
"@openfn/engine-multi": "0.1.
|
|
25
|
-
"@openfn/logger": "0.0.
|
|
26
|
-
"@openfn/runtime": "0.0.
|
|
24
|
+
"@openfn/engine-multi": "0.1.5",
|
|
25
|
+
"@openfn/logger": "0.0.19",
|
|
26
|
+
"@openfn/runtime": "0.0.33"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/koa": "^2.13.5",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"tsup": "^6.2.3",
|
|
41
41
|
"typescript": "^4.6.4",
|
|
42
42
|
"yargs": "^17.6.2",
|
|
43
|
-
"@openfn/lightning-mock": "1.0.
|
|
43
|
+
"@openfn/lightning-mock": "1.0.6"
|
|
44
44
|
},
|
|
45
45
|
"files": [
|
|
46
46
|
"dist",
|