@openfn/ws-worker 0.2.7 → 0.2.10
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 +27 -0
- package/README.md +1 -1
- package/dist/index.d.ts +106 -16
- package/dist/index.js +113 -35
- package/dist/start.js +168 -107
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# ws-worker
|
|
2
2
|
|
|
3
|
+
## 0.2.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 30da946: Better conversion of edge conditions to only take the upstream job into account
|
|
8
|
+
- c1aa9b3: Leave attempt queue channel on disconnect
|
|
9
|
+
Allow outstanding work to finish before closing down on SIGTERM
|
|
10
|
+
- 60b6fba: Add a healthcheck at /livez and respond with 200 at root
|
|
11
|
+
- Updated dependencies [a6dd44b]
|
|
12
|
+
- @openfn/engine-multi@0.2.1
|
|
13
|
+
|
|
14
|
+
## 0.2.9
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 54d0017: Start ws-worker using node (not pnpm) by default
|
|
19
|
+
- 6f78b7a: Add env var for WORKER_REPO_DIR
|
|
20
|
+
- Updated dependencies [4a17048]
|
|
21
|
+
- @openfn/engine-multi@0.2.0
|
|
22
|
+
- @openfn/runtime@0.2.0
|
|
23
|
+
|
|
24
|
+
## 0.2.8
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Tweak typings
|
|
29
|
+
|
|
3
30
|
## 0.2.7
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ You can start a dev server (which rebuilds on save) by running:
|
|
|
48
48
|
pnpm start:watch
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
This will wrap a real runtime engine into the server
|
|
51
|
+
This will wrap a real runtime engine into the server. It will rebuild when the Worker Engine code changes (although you'll have to `pnpm build:watch` in `runtime-manager`). This will use the repo at `WORKER_REPO_DIR` (or a default path in /tmp)
|
|
52
52
|
|
|
53
53
|
### Disabling auto-fetch
|
|
54
54
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
1
2
|
import Koa from 'koa';
|
|
2
3
|
import { SanitizePolicies, Logger } from '@openfn/logger';
|
|
3
4
|
import { Channel as Channel$1 } from 'phoenix';
|
|
5
|
+
import { Server } from 'http';
|
|
4
6
|
import { RuntimeEngine } from '@openfn/engine-multi';
|
|
5
7
|
|
|
6
8
|
type ExitReasonStrings =
|
|
@@ -17,6 +19,40 @@ type ExitReason = {
|
|
|
17
19
|
error_type: string | null;
|
|
18
20
|
};
|
|
19
21
|
|
|
22
|
+
type Node = {
|
|
23
|
+
id: string;
|
|
24
|
+
body?: string;
|
|
25
|
+
adaptor?: string;
|
|
26
|
+
credential_id?: any; // TODO tighten this up, string or object
|
|
27
|
+
type?: 'webhook' | 'cron'; // trigger only
|
|
28
|
+
state?: any; // Initial state / defaults
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
interface Edge {
|
|
32
|
+
id: string;
|
|
33
|
+
source_job_id?: string;
|
|
34
|
+
source_trigger_id?: string;
|
|
35
|
+
target_job_id: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
condition?: string;
|
|
38
|
+
error_path?: boolean;
|
|
39
|
+
errors?: any;
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// An attempt object returned by Lightning
|
|
44
|
+
type Attempt = {
|
|
45
|
+
id: string;
|
|
46
|
+
dataclip_id: string;
|
|
47
|
+
starting_node_id: string;
|
|
48
|
+
|
|
49
|
+
triggers: Node[];
|
|
50
|
+
jobs: Node[];
|
|
51
|
+
edges: Edge[];
|
|
52
|
+
|
|
53
|
+
options?: AttemptOptions;
|
|
54
|
+
};
|
|
55
|
+
|
|
20
56
|
type AttemptOptions = {
|
|
21
57
|
timeout?: number;
|
|
22
58
|
sanitize?: SanitizePolicies;
|
|
@@ -45,13 +81,6 @@ type ReceiveHook = {
|
|
|
45
81
|
) => ReceiveHook;
|
|
46
82
|
};
|
|
47
83
|
|
|
48
|
-
// export declare class Socket extends PhxSocket {
|
|
49
|
-
// constructor(endpoint: string, options: { params: any });
|
|
50
|
-
// onOpen(callback: () => void): void;
|
|
51
|
-
// connect(): void;
|
|
52
|
-
// channel(channelName: string, params: any): Channel;
|
|
53
|
-
// }
|
|
54
|
-
|
|
55
84
|
interface Channel extends Channel$1 {
|
|
56
85
|
// on: (event: string, fn: (evt: any) => void) => void;
|
|
57
86
|
|
|
@@ -60,6 +89,68 @@ interface Channel extends Channel$1 {
|
|
|
60
89
|
// join: () => ReceiveHook;
|
|
61
90
|
}
|
|
62
91
|
|
|
92
|
+
declare const CLAIM = "claim";
|
|
93
|
+
declare type ClaimPayload = {
|
|
94
|
+
demand?: number;
|
|
95
|
+
};
|
|
96
|
+
declare type ClaimReply = {
|
|
97
|
+
attempts: Array<ClaimAttempt>;
|
|
98
|
+
};
|
|
99
|
+
declare type ClaimAttempt = {
|
|
100
|
+
id: string;
|
|
101
|
+
token: string;
|
|
102
|
+
};
|
|
103
|
+
declare const GET_ATTEMPT = "fetch:attempt";
|
|
104
|
+
declare type GetAttemptPayload = void;
|
|
105
|
+
declare type GetAttemptReply = Attempt;
|
|
106
|
+
declare const GET_CREDENTIAL = "fetch:credential";
|
|
107
|
+
declare type GetCredentialPayload = {
|
|
108
|
+
id: string;
|
|
109
|
+
};
|
|
110
|
+
declare type GetCredentialReply = {};
|
|
111
|
+
declare const GET_DATACLIP = "fetch:dataclip";
|
|
112
|
+
declare type GetDataclipPayload = {
|
|
113
|
+
id: string;
|
|
114
|
+
};
|
|
115
|
+
declare type GetDataClipReply = Uint8Array;
|
|
116
|
+
declare const ATTEMPT_START = "attempt:start";
|
|
117
|
+
declare type AttemptStartPayload = void;
|
|
118
|
+
declare type AttemptStartReply = {};
|
|
119
|
+
declare const ATTEMPT_COMPLETE = "attempt:complete";
|
|
120
|
+
declare type AttemptCompletePayload = ExitReason & {
|
|
121
|
+
final_dataclip_id?: string;
|
|
122
|
+
};
|
|
123
|
+
declare type AttemptCompleteReply = undefined;
|
|
124
|
+
declare const ATTEMPT_LOG = "attempt:log";
|
|
125
|
+
declare type AttemptLogPayload = {
|
|
126
|
+
message: Array<string | object>;
|
|
127
|
+
timestamp: string;
|
|
128
|
+
attempt_id: string;
|
|
129
|
+
level?: string;
|
|
130
|
+
source?: string;
|
|
131
|
+
job_id?: string;
|
|
132
|
+
run_id?: string;
|
|
133
|
+
};
|
|
134
|
+
declare type AttemptLogReply = void;
|
|
135
|
+
declare const RUN_START = "run:start";
|
|
136
|
+
declare type RunStartPayload = {
|
|
137
|
+
job_id: string;
|
|
138
|
+
run_id: string;
|
|
139
|
+
attempt_id?: string;
|
|
140
|
+
input_dataclip_id?: string;
|
|
141
|
+
};
|
|
142
|
+
declare type RunStartReply = void;
|
|
143
|
+
declare const RUN_COMPLETE = "run:complete";
|
|
144
|
+
declare type RunCompletePayload = ExitReason & {
|
|
145
|
+
attempt_id?: string;
|
|
146
|
+
job_id: string;
|
|
147
|
+
run_id: string;
|
|
148
|
+
output_dataclip?: string;
|
|
149
|
+
output_dataclip_id?: string;
|
|
150
|
+
};
|
|
151
|
+
declare type RunCompleteReply = void;
|
|
152
|
+
declare const INTERNAL_ATTEMPT_COMPLETE = "server:attempt-complete";
|
|
153
|
+
|
|
63
154
|
declare type Context = {
|
|
64
155
|
channel: Channel;
|
|
65
156
|
state: AttemptState;
|
|
@@ -67,11 +158,6 @@ declare type Context = {
|
|
|
67
158
|
onFinish: (result: any) => void;
|
|
68
159
|
};
|
|
69
160
|
|
|
70
|
-
declare type CLAIM_ATTEMPT = {
|
|
71
|
-
id: string;
|
|
72
|
-
token: string;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
161
|
declare type ServerOptions = {
|
|
76
162
|
maxWorkflows?: number;
|
|
77
163
|
port?: number;
|
|
@@ -86,13 +172,17 @@ declare type ServerOptions = {
|
|
|
86
172
|
};
|
|
87
173
|
interface ServerApp extends Koa {
|
|
88
174
|
id: string;
|
|
89
|
-
socket
|
|
90
|
-
|
|
175
|
+
socket?: any;
|
|
176
|
+
queueChannel?: Channel;
|
|
91
177
|
workflows: Record<string, true | Context>;
|
|
92
|
-
|
|
178
|
+
destroyed: boolean;
|
|
179
|
+
events: EventEmitter;
|
|
180
|
+
server: Server;
|
|
181
|
+
engine: RuntimeEngine;
|
|
182
|
+
execute: ({ id, token }: ClaimAttempt) => Promise<void>;
|
|
93
183
|
destroy: () => void;
|
|
94
184
|
killWorkloop?: () => void;
|
|
95
185
|
}
|
|
96
186
|
declare function createServer(engine: RuntimeEngine, options?: ServerOptions): ServerApp;
|
|
97
187
|
|
|
98
|
-
export { createServer as default };
|
|
188
|
+
export { ATTEMPT_COMPLETE, ATTEMPT_LOG, ATTEMPT_START, AttemptCompletePayload, AttemptCompleteReply, AttemptLogPayload, AttemptLogReply, AttemptStartPayload, AttemptStartReply, CLAIM, ClaimAttempt, ClaimPayload, ClaimReply, GET_ATTEMPT, GET_CREDENTIAL, GET_DATACLIP, GetAttemptPayload, GetAttemptReply, GetCredentialPayload, GetCredentialReply, GetDataClipReply, GetDataclipPayload, INTERNAL_ATTEMPT_COMPLETE, RUN_COMPLETE, RUN_START, RunCompletePayload, RunCompleteReply, RunStartPayload, RunStartReply, createServer as default };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/server.ts
|
|
2
|
+
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
2
3
|
import Koa from "koa";
|
|
3
4
|
import bodyParser from "koa-bodyparser";
|
|
4
5
|
import koaLogger from "koa-logger";
|
|
@@ -6,6 +7,63 @@ import Router from "@koa/router";
|
|
|
6
7
|
import { humanId } from "human-id";
|
|
7
8
|
import { createMockLogger as createMockLogger2 } from "@openfn/logger";
|
|
8
9
|
|
|
10
|
+
// src/events.ts
|
|
11
|
+
var CLAIM = "claim";
|
|
12
|
+
var GET_ATTEMPT = "fetch:attempt";
|
|
13
|
+
var GET_CREDENTIAL = "fetch:credential";
|
|
14
|
+
var GET_DATACLIP = "fetch:dataclip";
|
|
15
|
+
var ATTEMPT_START = "attempt:start";
|
|
16
|
+
var ATTEMPT_COMPLETE = "attempt:complete";
|
|
17
|
+
var ATTEMPT_LOG = "attempt:log";
|
|
18
|
+
var RUN_START = "run:start";
|
|
19
|
+
var RUN_COMPLETE = "run:complete";
|
|
20
|
+
var INTERNAL_ATTEMPT_COMPLETE = "server:attempt-complete";
|
|
21
|
+
|
|
22
|
+
// src/api/destroy.ts
|
|
23
|
+
var destroy = async (app, logger) => {
|
|
24
|
+
logger.info("Closing server...");
|
|
25
|
+
await Promise.all([
|
|
26
|
+
new Promise((resolve) => {
|
|
27
|
+
app.destroyed = true;
|
|
28
|
+
app.killWorkloop?.();
|
|
29
|
+
app.queueChannel?.leave();
|
|
30
|
+
app.server.close(async () => {
|
|
31
|
+
resolve();
|
|
32
|
+
});
|
|
33
|
+
}),
|
|
34
|
+
new Promise(async (resolve) => {
|
|
35
|
+
await waitForAttempts(app, logger);
|
|
36
|
+
await app.engine.destroy();
|
|
37
|
+
app.socket?.disconnect();
|
|
38
|
+
resolve();
|
|
39
|
+
})
|
|
40
|
+
]);
|
|
41
|
+
logger.success("Server closed");
|
|
42
|
+
};
|
|
43
|
+
var waitForAttempts = (app, logger) => new Promise((resolve) => {
|
|
44
|
+
const log = () => {
|
|
45
|
+
logger.debug(
|
|
46
|
+
`Waiting for ${Object.keys(app.workflows).length} attempts to complete...`
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
const onAttemptComplete = () => {
|
|
50
|
+
if (Object.keys(app.workflows).length === 0) {
|
|
51
|
+
logger.debug("All attempts completed!");
|
|
52
|
+
app.events.off(INTERNAL_ATTEMPT_COMPLETE, onAttemptComplete);
|
|
53
|
+
resolve();
|
|
54
|
+
} else {
|
|
55
|
+
log();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
if (Object.keys(app.workflows).length) {
|
|
59
|
+
log();
|
|
60
|
+
app.events.on(INTERNAL_ATTEMPT_COMPLETE, onAttemptComplete);
|
|
61
|
+
} else {
|
|
62
|
+
resolve();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
var destroy_default = destroy;
|
|
66
|
+
|
|
9
67
|
// src/util/try-with-backoff.ts
|
|
10
68
|
var BACKOFF_MULTIPLIER = 1.15;
|
|
11
69
|
var tryWithBackoff = (fn, opts = {}) => {
|
|
@@ -49,19 +107,6 @@ var try_with_backoff_default = tryWithBackoff;
|
|
|
49
107
|
|
|
50
108
|
// src/api/claim.ts
|
|
51
109
|
import { createMockLogger } from "@openfn/logger";
|
|
52
|
-
|
|
53
|
-
// src/events.ts
|
|
54
|
-
var CLAIM = "claim";
|
|
55
|
-
var GET_ATTEMPT = "fetch:attempt";
|
|
56
|
-
var GET_CREDENTIAL = "fetch:credential";
|
|
57
|
-
var GET_DATACLIP = "fetch:dataclip";
|
|
58
|
-
var ATTEMPT_START = "attempt:start";
|
|
59
|
-
var ATTEMPT_COMPLETE = "attempt:complete";
|
|
60
|
-
var ATTEMPT_LOG = "attempt:log";
|
|
61
|
-
var RUN_START = "run:start";
|
|
62
|
-
var RUN_COMPLETE = "run:complete";
|
|
63
|
-
|
|
64
|
-
// src/api/claim.ts
|
|
65
110
|
var mockLogger = createMockLogger();
|
|
66
111
|
var claim = (app, logger = mockLogger, maxWorkers = 5) => {
|
|
67
112
|
return new Promise((resolve, reject) => {
|
|
@@ -69,8 +114,11 @@ var claim = (app, logger = mockLogger, maxWorkers = 5) => {
|
|
|
69
114
|
if (activeWorkers >= maxWorkers) {
|
|
70
115
|
return reject(new Error("Server at capacity"));
|
|
71
116
|
}
|
|
117
|
+
if (!app.queueChannel) {
|
|
118
|
+
return reject(new Error("No websocket available"));
|
|
119
|
+
}
|
|
72
120
|
logger.debug("requesting attempt...");
|
|
73
|
-
app.
|
|
121
|
+
app.queueChannel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
|
|
74
122
|
logger.debug(`pulled ${attempts.length} attempts`);
|
|
75
123
|
if (!attempts?.length) {
|
|
76
124
|
return reject(new Error("No attempts returned"));
|
|
@@ -112,6 +160,7 @@ var startWorkloop = (app, logger, minBackoff, maxBackoff, maxWorkers) => {
|
|
|
112
160
|
logger.debug("cancelling workloop");
|
|
113
161
|
cancelled = true;
|
|
114
162
|
promise.cancel();
|
|
163
|
+
app.queueChannel?.leave();
|
|
115
164
|
};
|
|
116
165
|
};
|
|
117
166
|
var workloop_default = startWorkloop;
|
|
@@ -122,13 +171,15 @@ import crypto2 from "node:crypto";
|
|
|
122
171
|
// src/util/convert-attempt.ts
|
|
123
172
|
import crypto from "node:crypto";
|
|
124
173
|
var conditions = {
|
|
125
|
-
on_job_success:
|
|
126
|
-
on_job_failure:
|
|
127
|
-
always: null
|
|
174
|
+
on_job_success: (upstreamId) => `Boolean(!state.errors?.["${upstreamId}"] ?? true)`,
|
|
175
|
+
on_job_failure: (upstreamId) => `Boolean(state.errors && state.errors["${upstreamId}"])`,
|
|
176
|
+
always: (_upstreamId) => null
|
|
128
177
|
};
|
|
129
|
-
var mapEdgeCondition = (
|
|
178
|
+
var mapEdgeCondition = (edge) => {
|
|
179
|
+
const { condition } = edge;
|
|
130
180
|
if (condition && condition in conditions) {
|
|
131
|
-
|
|
181
|
+
const upstream = edge.source_job_id || edge.source_trigger_id;
|
|
182
|
+
return conditions[condition](upstream);
|
|
132
183
|
}
|
|
133
184
|
return condition;
|
|
134
185
|
};
|
|
@@ -177,7 +228,7 @@ var convert_attempt_default = (attempt) => {
|
|
|
177
228
|
}
|
|
178
229
|
const next = edges.filter((e) => e.source_job_id === id).reduce((obj, edge) => {
|
|
179
230
|
const newEdge = {};
|
|
180
|
-
const condition = mapEdgeCondition(edge
|
|
231
|
+
const condition = mapEdgeCondition(edge);
|
|
181
232
|
if (condition) {
|
|
182
233
|
newEdge.condition = condition;
|
|
183
234
|
}
|
|
@@ -446,6 +497,11 @@ async function loadCredential(channel, credentialId) {
|
|
|
446
497
|
return get_with_reply_default(channel, GET_CREDENTIAL, { id: credentialId });
|
|
447
498
|
}
|
|
448
499
|
|
|
500
|
+
// src/middleware/healthcheck.ts
|
|
501
|
+
var healthcheck_default = (ctx) => {
|
|
502
|
+
ctx.status = 200;
|
|
503
|
+
};
|
|
504
|
+
|
|
449
505
|
// src/channels/attempt.ts
|
|
450
506
|
var joinAttemptChannel = (socket, token, attemptId, logger) => {
|
|
451
507
|
return new Promise((resolve, reject) => {
|
|
@@ -481,14 +537,14 @@ import { WebSocket } from "ws";
|
|
|
481
537
|
// src/util/worker-token.ts
|
|
482
538
|
import * as jose from "jose";
|
|
483
539
|
var alg = "HS256";
|
|
484
|
-
var generateWorkerToken = async (secret, workerId) => {
|
|
540
|
+
var generateWorkerToken = async (secret, workerId, logger) => {
|
|
485
541
|
if (!secret) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
542
|
+
logger.warn();
|
|
543
|
+
logger.warn("WARNING: Worker Secret not provided!");
|
|
544
|
+
logger.warn(
|
|
489
545
|
"This worker will attempt to connect to Lightning with default secret"
|
|
490
546
|
);
|
|
491
|
-
|
|
547
|
+
logger.warn();
|
|
492
548
|
}
|
|
493
549
|
const encodedSecret = new TextEncoder().encode(secret || "<secret>");
|
|
494
550
|
const claims = {
|
|
@@ -502,7 +558,7 @@ var worker_token_default = generateWorkerToken;
|
|
|
502
558
|
// src/channels/worker-queue.ts
|
|
503
559
|
var connectToWorkerQueue = (endpoint, serverId, secret, logger, SocketConstructor = PhxSocket) => {
|
|
504
560
|
const events = new EventEmitter();
|
|
505
|
-
worker_token_default(secret, serverId).then((token) => {
|
|
561
|
+
worker_token_default(secret, serverId, logger).then((token) => {
|
|
506
562
|
const socket = new SocketConstructor(endpoint, {
|
|
507
563
|
params: { token },
|
|
508
564
|
transport: WebSocket
|
|
@@ -545,7 +601,7 @@ function connect(app, logger, options = {}) {
|
|
|
545
601
|
const onConnect = ({ socket, channel }) => {
|
|
546
602
|
logger.success("Connected to Lightning at", options.lightning);
|
|
547
603
|
app.socket = socket;
|
|
548
|
-
app.
|
|
604
|
+
app.queueChannel = channel;
|
|
549
605
|
if (!options.noLoop) {
|
|
550
606
|
logger.info("Starting workloop");
|
|
551
607
|
app.killWorkloop = workloop_default(
|
|
@@ -592,6 +648,8 @@ function createServer(engine, options = {}) {
|
|
|
592
648
|
const app = new Koa();
|
|
593
649
|
app.id = humanId({ separator: "-", capitalize: false });
|
|
594
650
|
const router = new Router();
|
|
651
|
+
app.events = new EventEmitter2();
|
|
652
|
+
app.engine = engine;
|
|
595
653
|
app.use(bodyParser());
|
|
596
654
|
app.use(
|
|
597
655
|
koaLogger((str, _args) => {
|
|
@@ -599,8 +657,12 @@ function createServer(engine, options = {}) {
|
|
|
599
657
|
})
|
|
600
658
|
);
|
|
601
659
|
app.workflows = {};
|
|
602
|
-
|
|
660
|
+
app.destroyed = false;
|
|
661
|
+
app.server = app.listen(port);
|
|
603
662
|
logger.success(`ws-worker ${app.id} listening on ${port}`);
|
|
663
|
+
process.send?.("READY");
|
|
664
|
+
router.get("/livez", healthcheck_default);
|
|
665
|
+
router.get("/", healthcheck_default);
|
|
604
666
|
app.execute = async ({ id, token }) => {
|
|
605
667
|
if (app.socket) {
|
|
606
668
|
app.workflows[id] = true;
|
|
@@ -612,6 +674,7 @@ function createServer(engine, options = {}) {
|
|
|
612
674
|
const onFinish = () => {
|
|
613
675
|
delete app.workflows[id];
|
|
614
676
|
attemptChannel.leave();
|
|
677
|
+
app.events.emit(INTERNAL_ATTEMPT_COMPLETE);
|
|
615
678
|
};
|
|
616
679
|
const context = execute(
|
|
617
680
|
attemptChannel,
|
|
@@ -638,19 +701,24 @@ function createServer(engine, options = {}) {
|
|
|
638
701
|
ctx.status = 204;
|
|
639
702
|
});
|
|
640
703
|
});
|
|
641
|
-
app.destroy =
|
|
642
|
-
logger.info("Closing server...");
|
|
643
|
-
server.close();
|
|
644
|
-
await engine.destroy();
|
|
645
|
-
app.killWorkloop?.();
|
|
646
|
-
logger.success("Server closed");
|
|
647
|
-
};
|
|
704
|
+
app.destroy = () => destroy_default(app, logger);
|
|
648
705
|
app.use(router.routes());
|
|
649
706
|
if (options.lightning) {
|
|
650
707
|
connect(app, logger, options);
|
|
651
708
|
} else {
|
|
652
709
|
logger.warn("No lightning URL provided");
|
|
653
710
|
}
|
|
711
|
+
let shutdown = false;
|
|
712
|
+
const exit = async (signal) => {
|
|
713
|
+
if (!shutdown) {
|
|
714
|
+
shutdown = true;
|
|
715
|
+
logger.always(`${signal} RECEIVED: CLOSING SERVER`);
|
|
716
|
+
await app.destroy();
|
|
717
|
+
process.exit();
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
process.on("SIGINT", () => exit("SIGINT"));
|
|
721
|
+
process.on("SIGTERM", () => exit("SIGTERM"));
|
|
654
722
|
app.on = (...args) => {
|
|
655
723
|
return engine.on(...args);
|
|
656
724
|
};
|
|
@@ -661,5 +729,15 @@ var server_default = createServer;
|
|
|
661
729
|
// src/index.ts
|
|
662
730
|
var src_default = server_default;
|
|
663
731
|
export {
|
|
732
|
+
ATTEMPT_COMPLETE,
|
|
733
|
+
ATTEMPT_LOG,
|
|
734
|
+
ATTEMPT_START,
|
|
735
|
+
CLAIM,
|
|
736
|
+
GET_ATTEMPT,
|
|
737
|
+
GET_CREDENTIAL,
|
|
738
|
+
GET_DATACLIP,
|
|
739
|
+
INTERNAL_ATTEMPT_COMPLETE,
|
|
740
|
+
RUN_COMPLETE,
|
|
741
|
+
RUN_START,
|
|
664
742
|
src_default as default
|
|
665
743
|
};
|
package/dist/start.js
CHANGED
|
@@ -4864,8 +4864,8 @@ import createLogger from "@openfn/logger";
|
|
|
4864
4864
|
import createRTE from "@openfn/engine-multi";
|
|
4865
4865
|
|
|
4866
4866
|
// src/mock/runtime-engine.ts
|
|
4867
|
-
import crypto from "node:crypto";
|
|
4868
4867
|
import { EventEmitter } from "node:events";
|
|
4868
|
+
import run from "@openfn/runtime";
|
|
4869
4869
|
|
|
4870
4870
|
// src/mock/resolvers.ts
|
|
4871
4871
|
var mockResolveCredential = (_credId) => new Promise(
|
|
@@ -4887,6 +4887,10 @@ var resolvers_default = {
|
|
|
4887
4887
|
};
|
|
4888
4888
|
|
|
4889
4889
|
// src/mock/runtime-engine.ts
|
|
4890
|
+
var helpers = {
|
|
4891
|
+
fn: (f) => (s) => f(s),
|
|
4892
|
+
wait: (duration) => (s) => new Promise((resolve5) => setTimeout(() => resolve5(s), duration))
|
|
4893
|
+
};
|
|
4890
4894
|
async function createMock() {
|
|
4891
4895
|
const activeWorkflows = {};
|
|
4892
4896
|
const bus = new EventEmitter();
|
|
@@ -4902,73 +4906,59 @@ async function createMock() {
|
|
|
4902
4906
|
const listen = (planId, events) => {
|
|
4903
4907
|
listeners[planId] = events;
|
|
4904
4908
|
};
|
|
4905
|
-
const
|
|
4906
|
-
const { id, expression, configuration, adaptor } = job;
|
|
4907
|
-
if (!expression && !adaptor) {
|
|
4908
|
-
return initialState;
|
|
4909
|
-
}
|
|
4910
|
-
const runId = crypto.randomUUID();
|
|
4911
|
-
const jobId = id;
|
|
4912
|
-
if (typeof configuration === "string") {
|
|
4913
|
-
await resolvers.credential?.(configuration);
|
|
4914
|
-
}
|
|
4915
|
-
const info = (...message) => {
|
|
4916
|
-
dispatch("workflow-log", {
|
|
4917
|
-
workflowId,
|
|
4918
|
-
message,
|
|
4919
|
-
level: "info",
|
|
4920
|
-
time: (BigInt(Date.now()) * BigInt(1e3)).toString(),
|
|
4921
|
-
name: "mck"
|
|
4922
|
-
});
|
|
4923
|
-
};
|
|
4924
|
-
dispatch("job-start", { workflowId, jobId, runId });
|
|
4925
|
-
info("Running job " + jobId);
|
|
4926
|
-
let nextState = initialState;
|
|
4927
|
-
if (expression?.startsWith?.("wait@")) {
|
|
4928
|
-
const [_, delay] = expression.split("@");
|
|
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
|
-
}
|
|
4941
|
-
}
|
|
4942
|
-
dispatch("job-complete", {
|
|
4943
|
-
workflowId,
|
|
4944
|
-
jobId,
|
|
4945
|
-
state: nextState,
|
|
4946
|
-
runId,
|
|
4947
|
-
next: []
|
|
4948
|
-
});
|
|
4949
|
-
return nextState;
|
|
4950
|
-
};
|
|
4951
|
-
const execute2 = (xplan, options = {
|
|
4909
|
+
const execute2 = async (xplan, options = {
|
|
4952
4910
|
resolvers: resolvers_default
|
|
4953
4911
|
}) => {
|
|
4954
|
-
|
|
4955
|
-
throw new Error("test error");
|
|
4956
|
-
}
|
|
4957
|
-
const { id, jobs, initialState } = xplan;
|
|
4958
|
-
const workflowId = id;
|
|
4912
|
+
const { id, jobs } = xplan;
|
|
4959
4913
|
activeWorkflows[id] = true;
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4914
|
+
for (const job of jobs) {
|
|
4915
|
+
if (typeof job.configuration === "string") {
|
|
4916
|
+
job.configuration = await options.resolvers?.credential?.(
|
|
4917
|
+
job.configuration
|
|
4918
|
+
);
|
|
4919
|
+
}
|
|
4920
|
+
if (typeof job.expression === "string" && !job.expression.match(/export default \[/)) {
|
|
4921
|
+
job.expression = `export default [${job.expression}];`;
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
const jobLogger = {
|
|
4925
|
+
log: (...args2) => {
|
|
4926
|
+
dispatch("workflow-log", {
|
|
4927
|
+
workflowId: id,
|
|
4928
|
+
level: "info",
|
|
4929
|
+
json: true,
|
|
4930
|
+
message: args2,
|
|
4931
|
+
time: Date.now()
|
|
4932
|
+
});
|
|
4933
|
+
}
|
|
4934
|
+
};
|
|
4935
|
+
const opts = {
|
|
4936
|
+
strict: false,
|
|
4937
|
+
jobLogger,
|
|
4938
|
+
...options,
|
|
4939
|
+
globals: helpers,
|
|
4940
|
+
callbacks: {
|
|
4941
|
+
notify: (name, payload) => {
|
|
4942
|
+
dispatch(name, {
|
|
4943
|
+
workflowId: id,
|
|
4944
|
+
...payload
|
|
4945
|
+
});
|
|
4966
4946
|
}
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4947
|
+
}
|
|
4948
|
+
};
|
|
4949
|
+
setTimeout(async () => {
|
|
4950
|
+
dispatch("workflow-start", { workflowId: id });
|
|
4951
|
+
try {
|
|
4952
|
+
await run(xplan, void 0, opts);
|
|
4953
|
+
} catch (e) {
|
|
4954
|
+
dispatch("workflow-error", {
|
|
4955
|
+
workflowId: id,
|
|
4956
|
+
type: e.name,
|
|
4957
|
+
message: e.message
|
|
4958
|
+
});
|
|
4959
|
+
}
|
|
4960
|
+
delete activeWorkflows[id];
|
|
4961
|
+
dispatch("workflow-complete", { workflowId: id });
|
|
4972
4962
|
}, 1);
|
|
4973
4963
|
};
|
|
4974
4964
|
const getStatus = () => {
|
|
@@ -4976,17 +4966,20 @@ async function createMock() {
|
|
|
4976
4966
|
active: Object.keys(activeWorkflows).length
|
|
4977
4967
|
};
|
|
4978
4968
|
};
|
|
4969
|
+
const destroy2 = async () => true;
|
|
4979
4970
|
return {
|
|
4980
4971
|
on,
|
|
4981
4972
|
once,
|
|
4982
4973
|
execute: execute2,
|
|
4983
4974
|
getStatus,
|
|
4984
|
-
listen
|
|
4975
|
+
listen,
|
|
4976
|
+
destroy: destroy2
|
|
4985
4977
|
};
|
|
4986
4978
|
}
|
|
4987
4979
|
var runtime_engine_default = createMock;
|
|
4988
4980
|
|
|
4989
4981
|
// src/server.ts
|
|
4982
|
+
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
4990
4983
|
import Koa from "koa";
|
|
4991
4984
|
import bodyParser from "koa-bodyparser";
|
|
4992
4985
|
import koaLogger from "koa-logger";
|
|
@@ -4994,6 +4987,63 @@ import Router from "@koa/router";
|
|
|
4994
4987
|
import { humanId } from "human-id";
|
|
4995
4988
|
import { createMockLogger as createMockLogger2 } from "@openfn/logger";
|
|
4996
4989
|
|
|
4990
|
+
// src/events.ts
|
|
4991
|
+
var CLAIM = "claim";
|
|
4992
|
+
var GET_ATTEMPT = "fetch:attempt";
|
|
4993
|
+
var GET_CREDENTIAL = "fetch:credential";
|
|
4994
|
+
var GET_DATACLIP = "fetch:dataclip";
|
|
4995
|
+
var ATTEMPT_START = "attempt:start";
|
|
4996
|
+
var ATTEMPT_COMPLETE = "attempt:complete";
|
|
4997
|
+
var ATTEMPT_LOG = "attempt:log";
|
|
4998
|
+
var RUN_START = "run:start";
|
|
4999
|
+
var RUN_COMPLETE = "run:complete";
|
|
5000
|
+
var INTERNAL_ATTEMPT_COMPLETE = "server:attempt-complete";
|
|
5001
|
+
|
|
5002
|
+
// src/api/destroy.ts
|
|
5003
|
+
var destroy = async (app, logger2) => {
|
|
5004
|
+
logger2.info("Closing server...");
|
|
5005
|
+
await Promise.all([
|
|
5006
|
+
new Promise((resolve5) => {
|
|
5007
|
+
app.destroyed = true;
|
|
5008
|
+
app.killWorkloop?.();
|
|
5009
|
+
app.queueChannel?.leave();
|
|
5010
|
+
app.server.close(async () => {
|
|
5011
|
+
resolve5();
|
|
5012
|
+
});
|
|
5013
|
+
}),
|
|
5014
|
+
new Promise(async (resolve5) => {
|
|
5015
|
+
await waitForAttempts(app, logger2);
|
|
5016
|
+
await app.engine.destroy();
|
|
5017
|
+
app.socket?.disconnect();
|
|
5018
|
+
resolve5();
|
|
5019
|
+
})
|
|
5020
|
+
]);
|
|
5021
|
+
logger2.success("Server closed");
|
|
5022
|
+
};
|
|
5023
|
+
var waitForAttempts = (app, logger2) => new Promise((resolve5) => {
|
|
5024
|
+
const log = () => {
|
|
5025
|
+
logger2.debug(
|
|
5026
|
+
`Waiting for ${Object.keys(app.workflows).length} attempts to complete...`
|
|
5027
|
+
);
|
|
5028
|
+
};
|
|
5029
|
+
const onAttemptComplete = () => {
|
|
5030
|
+
if (Object.keys(app.workflows).length === 0) {
|
|
5031
|
+
logger2.debug("All attempts completed!");
|
|
5032
|
+
app.events.off(INTERNAL_ATTEMPT_COMPLETE, onAttemptComplete);
|
|
5033
|
+
resolve5();
|
|
5034
|
+
} else {
|
|
5035
|
+
log();
|
|
5036
|
+
}
|
|
5037
|
+
};
|
|
5038
|
+
if (Object.keys(app.workflows).length) {
|
|
5039
|
+
log();
|
|
5040
|
+
app.events.on(INTERNAL_ATTEMPT_COMPLETE, onAttemptComplete);
|
|
5041
|
+
} else {
|
|
5042
|
+
resolve5();
|
|
5043
|
+
}
|
|
5044
|
+
});
|
|
5045
|
+
var destroy_default = destroy;
|
|
5046
|
+
|
|
4997
5047
|
// src/util/try-with-backoff.ts
|
|
4998
5048
|
var BACKOFF_MULTIPLIER = 1.15;
|
|
4999
5049
|
var tryWithBackoff = (fn, opts = {}) => {
|
|
@@ -5037,19 +5087,6 @@ var try_with_backoff_default = tryWithBackoff;
|
|
|
5037
5087
|
|
|
5038
5088
|
// src/api/claim.ts
|
|
5039
5089
|
import { createMockLogger } from "@openfn/logger";
|
|
5040
|
-
|
|
5041
|
-
// src/events.ts
|
|
5042
|
-
var CLAIM = "claim";
|
|
5043
|
-
var GET_ATTEMPT = "fetch:attempt";
|
|
5044
|
-
var GET_CREDENTIAL = "fetch:credential";
|
|
5045
|
-
var GET_DATACLIP = "fetch:dataclip";
|
|
5046
|
-
var ATTEMPT_START = "attempt:start";
|
|
5047
|
-
var ATTEMPT_COMPLETE = "attempt:complete";
|
|
5048
|
-
var ATTEMPT_LOG = "attempt:log";
|
|
5049
|
-
var RUN_START = "run:start";
|
|
5050
|
-
var RUN_COMPLETE = "run:complete";
|
|
5051
|
-
|
|
5052
|
-
// src/api/claim.ts
|
|
5053
5090
|
var mockLogger = createMockLogger();
|
|
5054
5091
|
var claim = (app, logger2 = mockLogger, maxWorkers = 5) => {
|
|
5055
5092
|
return new Promise((resolve5, reject) => {
|
|
@@ -5057,8 +5094,11 @@ var claim = (app, logger2 = mockLogger, maxWorkers = 5) => {
|
|
|
5057
5094
|
if (activeWorkers >= maxWorkers) {
|
|
5058
5095
|
return reject(new Error("Server at capacity"));
|
|
5059
5096
|
}
|
|
5097
|
+
if (!app.queueChannel) {
|
|
5098
|
+
return reject(new Error("No websocket available"));
|
|
5099
|
+
}
|
|
5060
5100
|
logger2.debug("requesting attempt...");
|
|
5061
|
-
app.
|
|
5101
|
+
app.queueChannel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
|
|
5062
5102
|
logger2.debug(`pulled ${attempts.length} attempts`);
|
|
5063
5103
|
if (!attempts?.length) {
|
|
5064
5104
|
return reject(new Error("No attempts returned"));
|
|
@@ -5100,23 +5140,26 @@ var startWorkloop = (app, logger2, minBackoff2, maxBackoff2, maxWorkers) => {
|
|
|
5100
5140
|
logger2.debug("cancelling workloop");
|
|
5101
5141
|
cancelled = true;
|
|
5102
5142
|
promise.cancel();
|
|
5143
|
+
app.queueChannel?.leave();
|
|
5103
5144
|
};
|
|
5104
5145
|
};
|
|
5105
5146
|
var workloop_default = startWorkloop;
|
|
5106
5147
|
|
|
5107
5148
|
// src/api/execute.ts
|
|
5108
|
-
import
|
|
5149
|
+
import crypto2 from "node:crypto";
|
|
5109
5150
|
|
|
5110
5151
|
// src/util/convert-attempt.ts
|
|
5111
|
-
import
|
|
5152
|
+
import crypto from "node:crypto";
|
|
5112
5153
|
var conditions = {
|
|
5113
|
-
on_job_success:
|
|
5114
|
-
on_job_failure:
|
|
5115
|
-
always: null
|
|
5154
|
+
on_job_success: (upstreamId) => `Boolean(!state.errors?.["${upstreamId}"] ?? true)`,
|
|
5155
|
+
on_job_failure: (upstreamId) => `Boolean(state.errors && state.errors["${upstreamId}"])`,
|
|
5156
|
+
always: (_upstreamId) => null
|
|
5116
5157
|
};
|
|
5117
|
-
var mapEdgeCondition = (
|
|
5158
|
+
var mapEdgeCondition = (edge) => {
|
|
5159
|
+
const { condition } = edge;
|
|
5118
5160
|
if (condition && condition in conditions) {
|
|
5119
|
-
|
|
5161
|
+
const upstream = edge.source_job_id || edge.source_trigger_id;
|
|
5162
|
+
return conditions[condition](upstream);
|
|
5120
5163
|
}
|
|
5121
5164
|
return condition;
|
|
5122
5165
|
};
|
|
@@ -5153,7 +5196,7 @@ var convert_attempt_default = (attempt) => {
|
|
|
5153
5196
|
}
|
|
5154
5197
|
if (attempt.jobs?.length) {
|
|
5155
5198
|
attempt.jobs.forEach((job) => {
|
|
5156
|
-
const id = job.id ||
|
|
5199
|
+
const id = job.id || crypto.randomUUID();
|
|
5157
5200
|
nodes[id] = {
|
|
5158
5201
|
id,
|
|
5159
5202
|
configuration: job.credential_id,
|
|
@@ -5165,7 +5208,7 @@ var convert_attempt_default = (attempt) => {
|
|
|
5165
5208
|
}
|
|
5166
5209
|
const next = edges.filter((e) => e.source_job_id === id).reduce((obj, edge) => {
|
|
5167
5210
|
const newEdge = {};
|
|
5168
|
-
const condition = mapEdgeCondition(edge
|
|
5211
|
+
const condition = mapEdgeCondition(edge);
|
|
5169
5212
|
if (condition) {
|
|
5170
5213
|
newEdge.condition = condition;
|
|
5171
5214
|
}
|
|
@@ -5336,7 +5379,7 @@ var sendEvent = (channel, event, payload) => new Promise((resolve5, reject) => {
|
|
|
5336
5379
|
channel.push(event, payload).receive("error", reject).receive("timeout", () => reject(new Error("timeout"))).receive("ok", resolve5);
|
|
5337
5380
|
});
|
|
5338
5381
|
function onJobStart({ channel, state }, event) {
|
|
5339
|
-
state.activeRun =
|
|
5382
|
+
state.activeRun = crypto2.randomUUID();
|
|
5340
5383
|
state.activeJob = event.jobId;
|
|
5341
5384
|
const input_dataclip_id = state.inputDataclips[event.jobId];
|
|
5342
5385
|
return sendEvent(channel, RUN_START, {
|
|
@@ -5354,7 +5397,7 @@ function onJobError(context, event) {
|
|
|
5354
5397
|
}
|
|
5355
5398
|
}
|
|
5356
5399
|
function onJobComplete({ channel, state }, event, error) {
|
|
5357
|
-
const dataclipId =
|
|
5400
|
+
const dataclipId = crypto2.randomUUID();
|
|
5358
5401
|
const run_id = state.activeRun;
|
|
5359
5402
|
const job_id = state.activeJob;
|
|
5360
5403
|
if (!state.dataclips) {
|
|
@@ -5434,6 +5477,11 @@ async function loadCredential(channel, credentialId) {
|
|
|
5434
5477
|
return get_with_reply_default(channel, GET_CREDENTIAL, { id: credentialId });
|
|
5435
5478
|
}
|
|
5436
5479
|
|
|
5480
|
+
// src/middleware/healthcheck.ts
|
|
5481
|
+
var healthcheck_default = (ctx) => {
|
|
5482
|
+
ctx.status = 200;
|
|
5483
|
+
};
|
|
5484
|
+
|
|
5437
5485
|
// src/channels/attempt.ts
|
|
5438
5486
|
var joinAttemptChannel = (socket, token, attemptId, logger2) => {
|
|
5439
5487
|
return new Promise((resolve5, reject) => {
|
|
@@ -5469,14 +5517,14 @@ import { WebSocket } from "ws";
|
|
|
5469
5517
|
// src/util/worker-token.ts
|
|
5470
5518
|
import * as jose from "jose";
|
|
5471
5519
|
var alg = "HS256";
|
|
5472
|
-
var generateWorkerToken = async (secret, workerId) => {
|
|
5520
|
+
var generateWorkerToken = async (secret, workerId, logger2) => {
|
|
5473
5521
|
if (!secret) {
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5522
|
+
logger2.warn();
|
|
5523
|
+
logger2.warn("WARNING: Worker Secret not provided!");
|
|
5524
|
+
logger2.warn(
|
|
5477
5525
|
"This worker will attempt to connect to Lightning with default secret"
|
|
5478
5526
|
);
|
|
5479
|
-
|
|
5527
|
+
logger2.warn();
|
|
5480
5528
|
}
|
|
5481
5529
|
const encodedSecret = new TextEncoder().encode(secret || "<secret>");
|
|
5482
5530
|
const claims = {
|
|
@@ -5490,7 +5538,7 @@ var worker_token_default = generateWorkerToken;
|
|
|
5490
5538
|
// src/channels/worker-queue.ts
|
|
5491
5539
|
var connectToWorkerQueue = (endpoint, serverId, secret, logger2, SocketConstructor = PhxSocket) => {
|
|
5492
5540
|
const events = new EventEmitter2();
|
|
5493
|
-
worker_token_default(secret, serverId).then((token) => {
|
|
5541
|
+
worker_token_default(secret, serverId, logger2).then((token) => {
|
|
5494
5542
|
const socket = new SocketConstructor(endpoint, {
|
|
5495
5543
|
params: { token },
|
|
5496
5544
|
transport: WebSocket
|
|
@@ -5533,7 +5581,7 @@ function connect(app, logger2, options = {}) {
|
|
|
5533
5581
|
const onConnect = ({ socket, channel }) => {
|
|
5534
5582
|
logger2.success("Connected to Lightning at", options.lightning);
|
|
5535
5583
|
app.socket = socket;
|
|
5536
|
-
app.
|
|
5584
|
+
app.queueChannel = channel;
|
|
5537
5585
|
if (!options.noLoop) {
|
|
5538
5586
|
logger2.info("Starting workloop");
|
|
5539
5587
|
app.killWorkloop = workloop_default(
|
|
@@ -5580,6 +5628,8 @@ function createServer(engine, options = {}) {
|
|
|
5580
5628
|
const app = new Koa();
|
|
5581
5629
|
app.id = humanId({ separator: "-", capitalize: false });
|
|
5582
5630
|
const router = new Router();
|
|
5631
|
+
app.events = new EventEmitter3();
|
|
5632
|
+
app.engine = engine;
|
|
5583
5633
|
app.use(bodyParser());
|
|
5584
5634
|
app.use(
|
|
5585
5635
|
koaLogger((str, _args) => {
|
|
@@ -5587,8 +5637,12 @@ function createServer(engine, options = {}) {
|
|
|
5587
5637
|
})
|
|
5588
5638
|
);
|
|
5589
5639
|
app.workflows = {};
|
|
5590
|
-
|
|
5640
|
+
app.destroyed = false;
|
|
5641
|
+
app.server = app.listen(port);
|
|
5591
5642
|
logger2.success(`ws-worker ${app.id} listening on ${port}`);
|
|
5643
|
+
process.send?.("READY");
|
|
5644
|
+
router.get("/livez", healthcheck_default);
|
|
5645
|
+
router.get("/", healthcheck_default);
|
|
5592
5646
|
app.execute = async ({ id, token }) => {
|
|
5593
5647
|
if (app.socket) {
|
|
5594
5648
|
app.workflows[id] = true;
|
|
@@ -5600,6 +5654,7 @@ function createServer(engine, options = {}) {
|
|
|
5600
5654
|
const onFinish = () => {
|
|
5601
5655
|
delete app.workflows[id];
|
|
5602
5656
|
attemptChannel.leave();
|
|
5657
|
+
app.events.emit(INTERNAL_ATTEMPT_COMPLETE);
|
|
5603
5658
|
};
|
|
5604
5659
|
const context = execute(
|
|
5605
5660
|
attemptChannel,
|
|
@@ -5626,19 +5681,24 @@ function createServer(engine, options = {}) {
|
|
|
5626
5681
|
ctx.status = 204;
|
|
5627
5682
|
});
|
|
5628
5683
|
});
|
|
5629
|
-
app.destroy =
|
|
5630
|
-
logger2.info("Closing server...");
|
|
5631
|
-
server.close();
|
|
5632
|
-
await engine.destroy();
|
|
5633
|
-
app.killWorkloop?.();
|
|
5634
|
-
logger2.success("Server closed");
|
|
5635
|
-
};
|
|
5684
|
+
app.destroy = () => destroy_default(app, logger2);
|
|
5636
5685
|
app.use(router.routes());
|
|
5637
5686
|
if (options.lightning) {
|
|
5638
5687
|
connect(app, logger2, options);
|
|
5639
5688
|
} else {
|
|
5640
5689
|
logger2.warn("No lightning URL provided");
|
|
5641
5690
|
}
|
|
5691
|
+
let shutdown = false;
|
|
5692
|
+
const exit = async (signal) => {
|
|
5693
|
+
if (!shutdown) {
|
|
5694
|
+
shutdown = true;
|
|
5695
|
+
logger2.always(`${signal} RECEIVED: CLOSING SERVER`);
|
|
5696
|
+
await app.destroy();
|
|
5697
|
+
process.exit();
|
|
5698
|
+
}
|
|
5699
|
+
};
|
|
5700
|
+
process.on("SIGINT", () => exit("SIGINT"));
|
|
5701
|
+
process.on("SIGTERM", () => exit("SIGTERM"));
|
|
5642
5702
|
app.on = (...args2) => {
|
|
5643
5703
|
return engine.on(...args2);
|
|
5644
5704
|
};
|
|
@@ -5647,6 +5707,7 @@ function createServer(engine, options = {}) {
|
|
|
5647
5707
|
var server_default = createServer;
|
|
5648
5708
|
|
|
5649
5709
|
// src/start.ts
|
|
5710
|
+
var { WORKER_REPO_DIR, WORKER_SECRET } = process.env;
|
|
5650
5711
|
var args = yargs_default(hideBin(process.argv)).command("server", "Start a ws-worker server").option("port", {
|
|
5651
5712
|
alias: "p",
|
|
5652
5713
|
description: "Port to run the server on",
|
|
@@ -5658,7 +5719,8 @@ var args = yargs_default(hideBin(process.argv)).command("server", "Start a ws-wo
|
|
|
5658
5719
|
default: "ws://localhost:4000/worker"
|
|
5659
5720
|
}).option("repo-dir", {
|
|
5660
5721
|
alias: "d",
|
|
5661
|
-
description: "Path to the runtime repo (where modules will be installed)"
|
|
5722
|
+
description: "Path to the runtime repo (where modules will be installed)",
|
|
5723
|
+
default: WORKER_REPO_DIR
|
|
5662
5724
|
}).option("secret", {
|
|
5663
5725
|
alias: "s",
|
|
5664
5726
|
description: "Worker secret (comes from WORKER_SECRET by default)"
|
|
@@ -5689,7 +5751,6 @@ if (args.lightning === "mock") {
|
|
|
5689
5751
|
args.secret = "abdefg";
|
|
5690
5752
|
}
|
|
5691
5753
|
} else if (!args.secret) {
|
|
5692
|
-
const { WORKER_SECRET } = process.env;
|
|
5693
5754
|
if (!WORKER_SECRET) {
|
|
5694
5755
|
logger.error("WORKER_SECRET is not set");
|
|
5695
5756
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/ws-worker",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
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/
|
|
26
|
-
"@openfn/
|
|
24
|
+
"@openfn/engine-multi": "0.2.1",
|
|
25
|
+
"@openfn/logger": "0.0.19",
|
|
26
|
+
"@openfn/runtime": "0.2.0"
|
|
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.
|
|
43
|
+
"@openfn/lightning-mock": "1.1.3"
|
|
44
44
|
},
|
|
45
45
|
"files": [
|
|
46
46
|
"dist",
|