@openfn/ws-worker 0.2.9 → 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 CHANGED
@@ -1,5 +1,16 @@
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
+
3
14
  ## 0.2.9
4
15
 
5
16
  ### Patch Changes
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 =
@@ -87,13 +89,6 @@ interface Channel extends Channel$1 {
87
89
  // join: () => ReceiveHook;
88
90
  }
89
91
 
90
- declare type Context = {
91
- channel: Channel;
92
- state: AttemptState;
93
- logger: Logger;
94
- onFinish: (result: any) => void;
95
- };
96
-
97
92
  declare const CLAIM = "claim";
98
93
  declare type ClaimPayload = {
99
94
  demand?: number;
@@ -154,6 +149,14 @@ declare type RunCompletePayload = ExitReason & {
154
149
  output_dataclip_id?: string;
155
150
  };
156
151
  declare type RunCompleteReply = void;
152
+ declare const INTERNAL_ATTEMPT_COMPLETE = "server:attempt-complete";
153
+
154
+ declare type Context = {
155
+ channel: Channel;
156
+ state: AttemptState;
157
+ logger: Logger;
158
+ onFinish: (result: any) => void;
159
+ };
157
160
 
158
161
  declare type ServerOptions = {
159
162
  maxWorkflows?: number;
@@ -169,13 +172,17 @@ declare type ServerOptions = {
169
172
  };
170
173
  interface ServerApp extends Koa {
171
174
  id: string;
172
- socket: any;
173
- channel: Channel;
175
+ socket?: any;
176
+ queueChannel?: Channel;
174
177
  workflows: Record<string, true | Context>;
178
+ destroyed: boolean;
179
+ events: EventEmitter;
180
+ server: Server;
181
+ engine: RuntimeEngine;
175
182
  execute: ({ id, token }: ClaimAttempt) => Promise<void>;
176
183
  destroy: () => void;
177
184
  killWorkloop?: () => void;
178
185
  }
179
186
  declare function createServer(engine: RuntimeEngine, options?: ServerOptions): ServerApp;
180
187
 
181
- 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, RUN_COMPLETE, RUN_START, RunCompletePayload, RunCompleteReply, RunStartPayload, RunStartReply, 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.channel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
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: "!state.errors",
126
- on_job_failure: "state.errors",
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 = (condition) => {
178
+ var mapEdgeCondition = (edge) => {
179
+ const { condition } = edge;
130
180
  if (condition && condition in conditions) {
131
- return conditions[condition];
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.condition);
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
- console.warn();
487
- console.warn("WARNING: Worker Secret not provided!");
488
- console.warn(
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
- console.warn();
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.channel = channel;
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
- const server = app.listen(port);
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 = async () => {
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
  };
@@ -668,6 +736,7 @@ export {
668
736
  GET_ATTEMPT,
669
737
  GET_CREDENTIAL,
670
738
  GET_DATACLIP,
739
+ INTERNAL_ATTEMPT_COMPLETE,
671
740
  RUN_COMPLETE,
672
741
  RUN_START,
673
742
  src_default as default
package/dist/start.js CHANGED
@@ -4966,17 +4966,20 @@ async function createMock() {
4966
4966
  active: Object.keys(activeWorkflows).length
4967
4967
  };
4968
4968
  };
4969
+ const destroy2 = async () => true;
4969
4970
  return {
4970
4971
  on,
4971
4972
  once,
4972
4973
  execute: execute2,
4973
4974
  getStatus,
4974
- listen
4975
+ listen,
4976
+ destroy: destroy2
4975
4977
  };
4976
4978
  }
4977
4979
  var runtime_engine_default = createMock;
4978
4980
 
4979
4981
  // src/server.ts
4982
+ import { EventEmitter as EventEmitter3 } from "node:events";
4980
4983
  import Koa from "koa";
4981
4984
  import bodyParser from "koa-bodyparser";
4982
4985
  import koaLogger from "koa-logger";
@@ -4984,6 +4987,63 @@ import Router from "@koa/router";
4984
4987
  import { humanId } from "human-id";
4985
4988
  import { createMockLogger as createMockLogger2 } from "@openfn/logger";
4986
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
+
4987
5047
  // src/util/try-with-backoff.ts
4988
5048
  var BACKOFF_MULTIPLIER = 1.15;
4989
5049
  var tryWithBackoff = (fn, opts = {}) => {
@@ -5027,19 +5087,6 @@ var try_with_backoff_default = tryWithBackoff;
5027
5087
 
5028
5088
  // src/api/claim.ts
5029
5089
  import { createMockLogger } from "@openfn/logger";
5030
-
5031
- // src/events.ts
5032
- var CLAIM = "claim";
5033
- var GET_ATTEMPT = "fetch:attempt";
5034
- var GET_CREDENTIAL = "fetch:credential";
5035
- var GET_DATACLIP = "fetch:dataclip";
5036
- var ATTEMPT_START = "attempt:start";
5037
- var ATTEMPT_COMPLETE = "attempt:complete";
5038
- var ATTEMPT_LOG = "attempt:log";
5039
- var RUN_START = "run:start";
5040
- var RUN_COMPLETE = "run:complete";
5041
-
5042
- // src/api/claim.ts
5043
5090
  var mockLogger = createMockLogger();
5044
5091
  var claim = (app, logger2 = mockLogger, maxWorkers = 5) => {
5045
5092
  return new Promise((resolve5, reject) => {
@@ -5047,8 +5094,11 @@ var claim = (app, logger2 = mockLogger, maxWorkers = 5) => {
5047
5094
  if (activeWorkers >= maxWorkers) {
5048
5095
  return reject(new Error("Server at capacity"));
5049
5096
  }
5097
+ if (!app.queueChannel) {
5098
+ return reject(new Error("No websocket available"));
5099
+ }
5050
5100
  logger2.debug("requesting attempt...");
5051
- app.channel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
5101
+ app.queueChannel.push(CLAIM, { demand: 1 }).receive("ok", ({ attempts }) => {
5052
5102
  logger2.debug(`pulled ${attempts.length} attempts`);
5053
5103
  if (!attempts?.length) {
5054
5104
  return reject(new Error("No attempts returned"));
@@ -5090,6 +5140,7 @@ var startWorkloop = (app, logger2, minBackoff2, maxBackoff2, maxWorkers) => {
5090
5140
  logger2.debug("cancelling workloop");
5091
5141
  cancelled = true;
5092
5142
  promise.cancel();
5143
+ app.queueChannel?.leave();
5093
5144
  };
5094
5145
  };
5095
5146
  var workloop_default = startWorkloop;
@@ -5100,13 +5151,15 @@ import crypto2 from "node:crypto";
5100
5151
  // src/util/convert-attempt.ts
5101
5152
  import crypto from "node:crypto";
5102
5153
  var conditions = {
5103
- on_job_success: "!state.errors",
5104
- on_job_failure: "state.errors",
5105
- 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
5106
5157
  };
5107
- var mapEdgeCondition = (condition) => {
5158
+ var mapEdgeCondition = (edge) => {
5159
+ const { condition } = edge;
5108
5160
  if (condition && condition in conditions) {
5109
- return conditions[condition];
5161
+ const upstream = edge.source_job_id || edge.source_trigger_id;
5162
+ return conditions[condition](upstream);
5110
5163
  }
5111
5164
  return condition;
5112
5165
  };
@@ -5155,7 +5208,7 @@ var convert_attempt_default = (attempt) => {
5155
5208
  }
5156
5209
  const next = edges.filter((e) => e.source_job_id === id).reduce((obj, edge) => {
5157
5210
  const newEdge = {};
5158
- const condition = mapEdgeCondition(edge.condition);
5211
+ const condition = mapEdgeCondition(edge);
5159
5212
  if (condition) {
5160
5213
  newEdge.condition = condition;
5161
5214
  }
@@ -5424,6 +5477,11 @@ async function loadCredential(channel, credentialId) {
5424
5477
  return get_with_reply_default(channel, GET_CREDENTIAL, { id: credentialId });
5425
5478
  }
5426
5479
 
5480
+ // src/middleware/healthcheck.ts
5481
+ var healthcheck_default = (ctx) => {
5482
+ ctx.status = 200;
5483
+ };
5484
+
5427
5485
  // src/channels/attempt.ts
5428
5486
  var joinAttemptChannel = (socket, token, attemptId, logger2) => {
5429
5487
  return new Promise((resolve5, reject) => {
@@ -5459,14 +5517,14 @@ import { WebSocket } from "ws";
5459
5517
  // src/util/worker-token.ts
5460
5518
  import * as jose from "jose";
5461
5519
  var alg = "HS256";
5462
- var generateWorkerToken = async (secret, workerId) => {
5520
+ var generateWorkerToken = async (secret, workerId, logger2) => {
5463
5521
  if (!secret) {
5464
- console.warn();
5465
- console.warn("WARNING: Worker Secret not provided!");
5466
- console.warn(
5522
+ logger2.warn();
5523
+ logger2.warn("WARNING: Worker Secret not provided!");
5524
+ logger2.warn(
5467
5525
  "This worker will attempt to connect to Lightning with default secret"
5468
5526
  );
5469
- console.warn();
5527
+ logger2.warn();
5470
5528
  }
5471
5529
  const encodedSecret = new TextEncoder().encode(secret || "<secret>");
5472
5530
  const claims = {
@@ -5480,7 +5538,7 @@ var worker_token_default = generateWorkerToken;
5480
5538
  // src/channels/worker-queue.ts
5481
5539
  var connectToWorkerQueue = (endpoint, serverId, secret, logger2, SocketConstructor = PhxSocket) => {
5482
5540
  const events = new EventEmitter2();
5483
- worker_token_default(secret, serverId).then((token) => {
5541
+ worker_token_default(secret, serverId, logger2).then((token) => {
5484
5542
  const socket = new SocketConstructor(endpoint, {
5485
5543
  params: { token },
5486
5544
  transport: WebSocket
@@ -5523,7 +5581,7 @@ function connect(app, logger2, options = {}) {
5523
5581
  const onConnect = ({ socket, channel }) => {
5524
5582
  logger2.success("Connected to Lightning at", options.lightning);
5525
5583
  app.socket = socket;
5526
- app.channel = channel;
5584
+ app.queueChannel = channel;
5527
5585
  if (!options.noLoop) {
5528
5586
  logger2.info("Starting workloop");
5529
5587
  app.killWorkloop = workloop_default(
@@ -5570,6 +5628,8 @@ function createServer(engine, options = {}) {
5570
5628
  const app = new Koa();
5571
5629
  app.id = humanId({ separator: "-", capitalize: false });
5572
5630
  const router = new Router();
5631
+ app.events = new EventEmitter3();
5632
+ app.engine = engine;
5573
5633
  app.use(bodyParser());
5574
5634
  app.use(
5575
5635
  koaLogger((str, _args) => {
@@ -5577,8 +5637,12 @@ function createServer(engine, options = {}) {
5577
5637
  })
5578
5638
  );
5579
5639
  app.workflows = {};
5580
- const server = app.listen(port);
5640
+ app.destroyed = false;
5641
+ app.server = app.listen(port);
5581
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);
5582
5646
  app.execute = async ({ id, token }) => {
5583
5647
  if (app.socket) {
5584
5648
  app.workflows[id] = true;
@@ -5590,6 +5654,7 @@ function createServer(engine, options = {}) {
5590
5654
  const onFinish = () => {
5591
5655
  delete app.workflows[id];
5592
5656
  attemptChannel.leave();
5657
+ app.events.emit(INTERNAL_ATTEMPT_COMPLETE);
5593
5658
  };
5594
5659
  const context = execute(
5595
5660
  attemptChannel,
@@ -5616,19 +5681,24 @@ function createServer(engine, options = {}) {
5616
5681
  ctx.status = 204;
5617
5682
  });
5618
5683
  });
5619
- app.destroy = async () => {
5620
- logger2.info("Closing server...");
5621
- server.close();
5622
- await engine.destroy();
5623
- app.killWorkloop?.();
5624
- logger2.success("Server closed");
5625
- };
5684
+ app.destroy = () => destroy_default(app, logger2);
5626
5685
  app.use(router.routes());
5627
5686
  if (options.lightning) {
5628
5687
  connect(app, logger2, options);
5629
5688
  } else {
5630
5689
  logger2.warn("No lightning URL provided");
5631
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"));
5632
5702
  app.on = (...args2) => {
5633
5703
  return engine.on(...args2);
5634
5704
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/ws-worker",
3
- "version": "0.2.9",
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,7 +21,7 @@
21
21
  "koa-logger": "^3.2.1",
22
22
  "phoenix": "^1.7.7",
23
23
  "ws": "^8.14.1",
24
- "@openfn/engine-multi": "0.2.0",
24
+ "@openfn/engine-multi": "0.2.1",
25
25
  "@openfn/logger": "0.0.19",
26
26
  "@openfn/runtime": "0.2.0"
27
27
  },
@@ -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.1.2"
43
+ "@openfn/lightning-mock": "1.1.3"
44
44
  },
45
45
  "files": [
46
46
  "dist",