@openfn/ws-worker 0.5.0 → 0.7.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 CHANGED
@@ -1,5 +1,36 @@
1
1
  # ws-worker
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 39af8e1: Ensure that we refer to the child of a 'run' (aka attempt) as a 'step'
8
+
9
+ ## 0.6.0
10
+
11
+ ### Patch Changes
12
+
13
+ - eb10b1f: Updated start env vars and arguments
14
+ - 281391b: Support attemptTimeoutMs in attempt options
15
+ Better server logging at startup
16
+ Support start arguments from the environment (but prefer CLI)
17
+ - 2857fe6: Send the exit reason to the attempt logs
18
+ - Updated dependencies [281391b]
19
+ - @openfn/engine-multi@0.3.0
20
+
21
+ ## 0.6.0
22
+
23
+ ### Minor Changes
24
+
25
+ - 9b9ca0c: New worker pool engine
26
+
27
+ ### Patch Changes
28
+
29
+ - Updated dependencies [0f22694]
30
+ - Updated dependencies [9b9ca0c]
31
+ - @openfn/runtime@0.2.5
32
+ - @openfn/engine-multi@0.3.0
33
+
3
34
  ## 0.5.0
4
35
 
5
36
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -56,13 +56,24 @@ type Attempt = {
56
56
  };
57
57
 
58
58
  type AttemptOptions = {
59
+ // This is what Lightning will ssend us
60
+ // Note that this is the NEW terminology, so it's the timeout for the whole "attempt"
61
+ runTimeout?: number;
62
+
63
+ // this is the internal old terminology, which will be deprecated soon
64
+ attemptTimeoutMs?: number;
65
+
66
+ attemptTimeout?: number; // deprecated
67
+
68
+ // deprecated alias for timeout. Maps to "attemptTimeout" internally
59
69
  timeout?: number;
70
+
60
71
  sanitize?: SanitizePolicies;
61
72
  };
62
73
 
63
74
  // Internal server state for each attempt
64
75
  type AttemptState = {
65
- activeRun?: string;
76
+ activeStep?: string;
66
77
  activeJob?: string;
67
78
  plan: ExecutionPlan;
68
79
  options: AttemptOptions;
@@ -131,27 +142,27 @@ declare type AttemptLogPayload = {
131
142
  level?: string;
132
143
  source?: string;
133
144
  job_id?: string;
134
- run_id?: string;
145
+ step_id?: string;
135
146
  };
136
147
  declare type AttemptLogReply = void;
137
- declare const RUN_START = "run:start";
138
- declare type RunStartPayload = {
148
+ declare const STEP_START = "step:start";
149
+ declare type StepStartPayload = {
139
150
  job_id: string;
140
- run_id: string;
151
+ step_id: string;
141
152
  attempt_id?: string;
142
153
  input_dataclip_id?: string;
143
154
  versions: Record<string, string>;
144
155
  };
145
- declare type RunStartReply = void;
146
- declare const RUN_COMPLETE = "run:complete";
147
- declare type RunCompletePayload = ExitReason & {
156
+ declare type StepStartReply = void;
157
+ declare const STEP_COMPLETE = "step:complete";
158
+ declare type StepCompletePayload = ExitReason & {
148
159
  attempt_id?: string;
149
160
  job_id: string;
150
- run_id: string;
161
+ step_id: string;
151
162
  output_dataclip?: string;
152
163
  output_dataclip_id?: string;
153
164
  };
154
- declare type RunCompleteReply = void;
165
+ declare type StepCompleteReply = void;
155
166
  declare const INTERNAL_ATTEMPT_COMPLETE = "server:attempt-complete";
156
167
 
157
168
  declare type Context = {
@@ -189,4 +200,4 @@ interface ServerApp extends Koa {
189
200
  }
190
201
  declare function createServer(engine: RuntimeEngine, options?: ServerOptions): ServerApp;
191
202
 
192
- 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 };
203
+ 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, STEP_COMPLETE, STEP_START, StepCompletePayload, StepCompleteReply, StepStartPayload, StepStartReply, createServer as default };
package/dist/index.js CHANGED
@@ -15,8 +15,8 @@ var GET_DATACLIP = "fetch:dataclip";
15
15
  var ATTEMPT_START = "attempt:start";
16
16
  var ATTEMPT_COMPLETE = "attempt:complete";
17
17
  var ATTEMPT_LOG = "attempt:log";
18
- var RUN_START = "run:start";
19
- var RUN_COMPLETE = "run:complete";
18
+ var STEP_START = "step:start";
19
+ var STEP_COMPLETE = "step:complete";
20
20
  var INTERNAL_ATTEMPT_COMPLETE = "server:attempt-complete";
21
21
 
22
22
  // src/api/destroy.ts
@@ -186,6 +186,14 @@ var mapTriggerEdgeCondition = (edge) => {
186
186
  return true;
187
187
  return condition;
188
188
  };
189
+ var mapOptions = (options) => {
190
+ const { attemptTimeout, timeout, runTimeout, ...opts } = options;
191
+ const to = runTimeout || attemptTimeout || timeout;
192
+ if (to) {
193
+ opts.attemptTimeoutMs = to;
194
+ }
195
+ return opts;
196
+ };
189
197
  var convert_attempt_default = (attempt) => {
190
198
  const options = attempt.options || {};
191
199
  const plan = {
@@ -249,7 +257,7 @@ var convert_attempt_default = (attempt) => {
249
257
  plan.jobs = Object.values(nodes);
250
258
  return {
251
259
  plan,
252
- options
260
+ options: mapOptions(options)
253
261
  };
254
262
  };
255
263
 
@@ -298,6 +306,9 @@ var create_attempt_state_default = (plan, options = {}) => {
298
306
  return state;
299
307
  };
300
308
 
309
+ // src/events/step-complete.ts
310
+ import crypto2 from "node:crypto";
311
+
301
312
  // src/api/reasons.ts
302
313
  var calculateJobExitReason = (jobId, state = { data: {} }, error) => {
303
314
  let reason = "success";
@@ -331,18 +342,17 @@ var calculateAttemptExitReason = (state) => {
331
342
  return { reason: "success", error_type: null, error_message: null };
332
343
  };
333
344
 
334
- // src/events/run-complete.ts
335
- import crypto2 from "node:crypto";
336
- function onRunComplete({ channel, state }, event, error) {
345
+ // src/events/step-complete.ts
346
+ function onStepComplete({ channel, state }, event, error) {
337
347
  const dataclipId = crypto2.randomUUID();
338
- const run_id = state.activeRun;
348
+ const step_id = state.activeStep;
339
349
  const job_id = state.activeJob;
340
350
  if (!state.dataclips) {
341
351
  state.dataclips = {};
342
352
  }
343
353
  const outputState = event.state || {};
344
354
  state.dataclips[dataclipId] = event.state;
345
- delete state.activeRun;
355
+ delete state.activeStep;
346
356
  delete state.activeJob;
347
357
  state.lastDataclipId = dataclipId;
348
358
  event.next?.forEach((nextJobId) => {
@@ -355,7 +365,7 @@ function onRunComplete({ channel, state }, event, error) {
355
365
  );
356
366
  state.reasons[job_id] = { reason, error_message, error_type };
357
367
  const evt = {
358
- run_id,
368
+ step_id,
359
369
  job_id,
360
370
  output_dataclip_id: dataclipId,
361
371
  output_dataclip: stringify_default(outputState),
@@ -366,17 +376,17 @@ function onRunComplete({ channel, state }, event, error) {
366
376
  duration: event.duration,
367
377
  thread_id: event.threadId
368
378
  };
369
- return sendEvent(channel, RUN_COMPLETE, evt);
379
+ return sendEvent(channel, STEP_COMPLETE, evt);
370
380
  }
371
381
 
372
- // src/events/run-start.ts
382
+ // src/events/step-start.ts
373
383
  import crypto3 from "node:crypto";
374
384
  import { timestamp } from "@openfn/logger";
375
385
 
376
386
  // package.json
377
387
  var package_default = {
378
388
  name: "@openfn/ws-worker",
379
- version: "0.5.0",
389
+ version: "0.7.0",
380
390
  description: "A Websocket Worker to connect Lightning to a Runtime Engine",
381
391
  main: "dist/index.js",
382
392
  type: "module",
@@ -439,14 +449,14 @@ var package_default = {
439
449
  // src/util/versions.ts
440
450
  import { mainSymbols } from "figures";
441
451
  var { triangleRightSmall: t } = mainSymbols;
442
- var versions_default = (runId, versions, adaptor) => {
452
+ var versions_default = (stepId, versions, adaptor) => {
443
453
  let longest = "compiler".length;
444
454
  for (const v in versions) {
445
455
  longest = Math.max(v.length, longest);
446
456
  }
447
457
  const { node, compiler, engine, worker, runtime, ...adaptors } = versions;
448
458
  const prefix = (str2) => ` ${t} ${str2.padEnd(longest + 4, " ")}`;
449
- let str = `Versions for run ${runId}:
459
+ let str = `Versions for step ${stepId}:
450
460
  ${prefix("node.js")}${versions.node || "unknown"}
451
461
  ${prefix("worker")}${versions.worker || "unknown"}
452
462
  ${prefix("engine")}${versions.engine || "unknown"}`;
@@ -460,11 +470,11 @@ ${prefix("engine")}${versions.engine || "unknown"}`;
460
470
  return str;
461
471
  };
462
472
 
463
- // src/events/run-start.ts
464
- async function onRunStart(context, event) {
473
+ // src/events/step-start.ts
474
+ async function onStepStart(context, event) {
465
475
  const time = (timestamp() - BigInt(1e7)).toString();
466
476
  const { channel, state } = context;
467
- state.activeRun = crypto3.randomUUID();
477
+ state.activeStep = crypto3.randomUUID();
468
478
  state.activeJob = event.jobId;
469
479
  const job = state.plan.jobs.find(({ id }) => id === event.jobId);
470
480
  const input_dataclip_id = state.inputDataclips[event.jobId];
@@ -476,17 +486,17 @@ async function onRunStart(context, event) {
476
486
  ...context,
477
487
  state: {
478
488
  ...state,
479
- activeRun: state.activeRun
489
+ activeStep: state.activeStep
480
490
  }
481
491
  };
482
- await sendEvent(channel, RUN_START, {
483
- run_id: state.activeRun,
492
+ await sendEvent(channel, STEP_START, {
493
+ step_id: state.activeStep,
484
494
  job_id: state.activeJob,
485
495
  input_dataclip_id,
486
496
  versions
487
497
  });
488
498
  const versionMessage = versions_default(
489
- versionLogContext.state.activeRun,
499
+ versionLogContext.state.activeStep,
490
500
  versions,
491
501
  job?.adaptor
492
502
  );
@@ -499,6 +509,57 @@ async function onRunStart(context, event) {
499
509
  return;
500
510
  }
501
511
 
512
+ // src/util/log-final-reason.ts
513
+ import { timestamp as timestamp2 } from "@openfn/logger";
514
+ var log_final_reason_default = async (context, reason) => {
515
+ const time = (timestamp2() - BigInt(1e7)).toString();
516
+ let message = `Run complete with status: ${reason.reason}`;
517
+ if (reason.reason !== "success") {
518
+ message += `
519
+ ${reason.error_type}: ${reason.error_message || "unknown"}`;
520
+ }
521
+ await onJobLog(context, {
522
+ time,
523
+ message: [message],
524
+ level: "info",
525
+ name: "R/T"
526
+ });
527
+ };
528
+
529
+ // src/events/attempt-complete.ts
530
+ async function onWorkflowComplete(context, _event) {
531
+ const { state, channel, onFinish } = context;
532
+ const result = state.dataclips[state.lastDataclipId];
533
+ const reason = calculateAttemptExitReason(state);
534
+ await log_final_reason_default(context, reason);
535
+ await sendEvent(channel, ATTEMPT_COMPLETE, {
536
+ final_dataclip_id: state.lastDataclipId,
537
+ ...reason
538
+ });
539
+ onFinish({ reason, state: result });
540
+ }
541
+
542
+ // src/events/attempt-error.ts
543
+ async function onAttemptError(context, event) {
544
+ const { state, channel, logger, onFinish } = context;
545
+ try {
546
+ const reason = calculateJobExitReason("", { data: {} }, event);
547
+ if (state.activeJob) {
548
+ await onJobError(context, { error: event });
549
+ }
550
+ await log_final_reason_default(context, reason);
551
+ await sendEvent(channel, ATTEMPT_COMPLETE, {
552
+ final_dataclip_id: state.lastDataclipId,
553
+ ...reason
554
+ });
555
+ onFinish({ reason });
556
+ } catch (e) {
557
+ logger.error("ERROR in workflow-error handler:", e.message);
558
+ logger.error(e);
559
+ onFinish({});
560
+ }
561
+ }
562
+
502
563
  // src/util/throttle.ts
503
564
  var createThrottler = () => {
504
565
  const q = [];
@@ -530,8 +591,8 @@ var throttle_default = createThrottler;
530
591
  var enc = new TextDecoder("utf-8");
531
592
  var eventMap = {
532
593
  "workflow-start": ATTEMPT_START,
533
- "job-start": RUN_START,
534
- "job-complete": RUN_COMPLETE,
594
+ "job-start": STEP_START,
595
+ "job-complete": STEP_COMPLETE,
535
596
  "workflow-log": ATTEMPT_LOG,
536
597
  "workflow-complete": ATTEMPT_COMPLETE
537
598
  };
@@ -561,12 +622,12 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
561
622
  const listeners = Object.assign(
562
623
  {},
563
624
  addEvent("workflow-start", throttle(onWorkflowStart)),
564
- addEvent("job-start", throttle(onRunStart)),
565
- addEvent("job-complete", throttle(onRunComplete)),
625
+ addEvent("job-start", throttle(onStepStart)),
626
+ addEvent("job-complete", throttle(onStepComplete)),
566
627
  addEvent("job-error", throttle(onJobError)),
567
628
  addEvent("workflow-log", throttle(onJobLog)),
568
629
  addEvent("workflow-complete", throttle(onWorkflowComplete)),
569
- addEvent("workflow-error", throttle(onWorkflowError))
630
+ addEvent("workflow-error", throttle(onAttemptError))
570
631
  );
571
632
  engine.listen(plan.id, listeners);
572
633
  const resolvers = {
@@ -584,7 +645,7 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
584
645
  try {
585
646
  engine.execute(plan, { resolvers, ...options });
586
647
  } catch (e) {
587
- onWorkflowError(context, {
648
+ onAttemptError(context, {
588
649
  workflowId: plan.id,
589
650
  message: e.message,
590
651
  type: e.type,
@@ -602,41 +663,14 @@ var sendEvent = (channel, event, payload) => new Promise((resolve, reject) => {
602
663
  function onJobError(context, event) {
603
664
  const { state, error, jobId } = event;
604
665
  if (state?.errors?.[jobId]?.message === error.message) {
605
- return onRunComplete(context, event);
666
+ return onStepComplete(context, event);
606
667
  } else {
607
- return onRunComplete(context, event, event.error);
668
+ return onStepComplete(context, event, event.error);
608
669
  }
609
670
  }
610
671
  function onWorkflowStart({ channel }, _event) {
611
672
  return sendEvent(channel, ATTEMPT_START);
612
673
  }
613
- async function onWorkflowComplete({ state, channel, onFinish }, _event) {
614
- const result = state.dataclips[state.lastDataclipId];
615
- const reason = calculateAttemptExitReason(state);
616
- await sendEvent(channel, ATTEMPT_COMPLETE, {
617
- final_dataclip_id: state.lastDataclipId,
618
- ...reason
619
- });
620
- onFinish({ reason, state: result });
621
- }
622
- async function onWorkflowError(context, event) {
623
- const { state, channel, logger, onFinish } = context;
624
- try {
625
- const reason = calculateJobExitReason("", { data: {} }, event);
626
- if (state.activeJob) {
627
- await onJobError(context, { error: event });
628
- }
629
- await sendEvent(channel, ATTEMPT_COMPLETE, {
630
- final_dataclip_id: state.lastDataclipId,
631
- ...reason
632
- });
633
- onFinish({ reason });
634
- } catch (e) {
635
- logger.error("ERROR in workflow-error handler:", e.message);
636
- logger.error(e);
637
- onFinish({});
638
- }
639
- }
640
674
  function onJobLog({ channel, state }, event) {
641
675
  const timeInMicroseconds = BigInt(event.time) / BigInt(1e3);
642
676
  const log = {
@@ -646,8 +680,8 @@ function onJobLog({ channel, state }, event) {
646
680
  level: event.level,
647
681
  timestamp: timeInMicroseconds.toString()
648
682
  };
649
- if (state.activeRun) {
650
- log.run_id = state.activeRun;
683
+ if (state.activeStep) {
684
+ log.step_id = state.activeStep;
651
685
  }
652
686
  return sendEvent(channel, ATTEMPT_LOG, log);
653
687
  }
@@ -809,7 +843,6 @@ function connect(app, logger, options = {}) {
809
843
  function createServer(engine, options = {}) {
810
844
  const logger = options.logger || createMockLogger2();
811
845
  const port = options.port || DEFAULT_PORT;
812
- logger.debug("Starting server");
813
846
  const app = new Koa();
814
847
  app.id = humanId({ separator: "-", capitalize: false });
815
848
  const router = new Router();
@@ -824,7 +857,7 @@ function createServer(engine, options = {}) {
824
857
  app.workflows = {};
825
858
  app.destroyed = false;
826
859
  app.server = app.listen(port);
827
- logger.success(`ws-worker ${app.id} listening on ${port}`);
860
+ logger.success(`Worker ${app.id} listening on ${port}`);
828
861
  process.send?.("READY");
829
862
  router.get("/livez", healthcheck_default);
830
863
  router.get("/", healthcheck_default);
@@ -902,7 +935,7 @@ export {
902
935
  GET_CREDENTIAL,
903
936
  GET_DATACLIP,
904
937
  INTERNAL_ATTEMPT_COMPLETE,
905
- RUN_COMPLETE,
906
- RUN_START,
938
+ STEP_COMPLETE,
939
+ STEP_START,
907
940
  src_default as default
908
941
  };
package/dist/start.js CHANGED
@@ -4890,7 +4890,10 @@ var resolvers_default = {
4890
4890
  // src/mock/runtime-engine.ts
4891
4891
  var helpers = {
4892
4892
  fn: (f) => (s) => f(s),
4893
- wait: (duration) => (s) => new Promise((resolve5) => setTimeout(() => resolve5(s), duration))
4893
+ wait: (duration) => (s) => new Promise((resolve5) => setTimeout(() => resolve5(s), duration)),
4894
+ err: () => {
4895
+ throw new Error("test_err");
4896
+ }
4894
4897
  };
4895
4898
  async function createMock() {
4896
4899
  const activeWorkflows = {};
@@ -5001,8 +5004,8 @@ var GET_DATACLIP = "fetch:dataclip";
5001
5004
  var ATTEMPT_START = "attempt:start";
5002
5005
  var ATTEMPT_COMPLETE = "attempt:complete";
5003
5006
  var ATTEMPT_LOG = "attempt:log";
5004
- var RUN_START = "run:start";
5005
- var RUN_COMPLETE = "run:complete";
5007
+ var STEP_START = "step:start";
5008
+ var STEP_COMPLETE = "step:complete";
5006
5009
  var INTERNAL_ATTEMPT_COMPLETE = "server:attempt-complete";
5007
5010
 
5008
5011
  // src/api/destroy.ts
@@ -5172,6 +5175,14 @@ var mapTriggerEdgeCondition = (edge) => {
5172
5175
  return true;
5173
5176
  return condition;
5174
5177
  };
5178
+ var mapOptions = (options) => {
5179
+ const { attemptTimeout, timeout, runTimeout, ...opts } = options;
5180
+ const to = runTimeout || attemptTimeout || timeout;
5181
+ if (to) {
5182
+ opts.attemptTimeoutMs = to;
5183
+ }
5184
+ return opts;
5185
+ };
5175
5186
  var convert_attempt_default = (attempt) => {
5176
5187
  const options = attempt.options || {};
5177
5188
  const plan = {
@@ -5235,7 +5246,7 @@ var convert_attempt_default = (attempt) => {
5235
5246
  plan.jobs = Object.values(nodes);
5236
5247
  return {
5237
5248
  plan,
5238
- options
5249
+ options: mapOptions(options)
5239
5250
  };
5240
5251
  };
5241
5252
 
@@ -5284,6 +5295,9 @@ var create_attempt_state_default = (plan, options = {}) => {
5284
5295
  return state;
5285
5296
  };
5286
5297
 
5298
+ // src/events/step-complete.ts
5299
+ import crypto3 from "node:crypto";
5300
+
5287
5301
  // src/api/reasons.ts
5288
5302
  var calculateJobExitReason = (jobId, state = { data: {} }, error) => {
5289
5303
  let reason = "success";
@@ -5317,18 +5331,17 @@ var calculateAttemptExitReason = (state) => {
5317
5331
  return { reason: "success", error_type: null, error_message: null };
5318
5332
  };
5319
5333
 
5320
- // src/events/run-complete.ts
5321
- import crypto3 from "node:crypto";
5322
- function onRunComplete({ channel, state }, event, error) {
5334
+ // src/events/step-complete.ts
5335
+ function onStepComplete({ channel, state }, event, error) {
5323
5336
  const dataclipId = crypto3.randomUUID();
5324
- const run_id = state.activeRun;
5337
+ const step_id = state.activeStep;
5325
5338
  const job_id = state.activeJob;
5326
5339
  if (!state.dataclips) {
5327
5340
  state.dataclips = {};
5328
5341
  }
5329
5342
  const outputState = event.state || {};
5330
5343
  state.dataclips[dataclipId] = event.state;
5331
- delete state.activeRun;
5344
+ delete state.activeStep;
5332
5345
  delete state.activeJob;
5333
5346
  state.lastDataclipId = dataclipId;
5334
5347
  event.next?.forEach((nextJobId) => {
@@ -5341,7 +5354,7 @@ function onRunComplete({ channel, state }, event, error) {
5341
5354
  );
5342
5355
  state.reasons[job_id] = { reason, error_message, error_type };
5343
5356
  const evt = {
5344
- run_id,
5357
+ step_id,
5345
5358
  job_id,
5346
5359
  output_dataclip_id: dataclipId,
5347
5360
  output_dataclip: stringify_default(outputState),
@@ -5352,17 +5365,17 @@ function onRunComplete({ channel, state }, event, error) {
5352
5365
  duration: event.duration,
5353
5366
  thread_id: event.threadId
5354
5367
  };
5355
- return sendEvent(channel, RUN_COMPLETE, evt);
5368
+ return sendEvent(channel, STEP_COMPLETE, evt);
5356
5369
  }
5357
5370
 
5358
- // src/events/run-start.ts
5371
+ // src/events/step-start.ts
5359
5372
  import crypto4 from "node:crypto";
5360
5373
  import { timestamp } from "@openfn/logger";
5361
5374
 
5362
5375
  // package.json
5363
5376
  var package_default = {
5364
5377
  name: "@openfn/ws-worker",
5365
- version: "0.5.0",
5378
+ version: "0.7.0",
5366
5379
  description: "A Websocket Worker to connect Lightning to a Runtime Engine",
5367
5380
  main: "dist/index.js",
5368
5381
  type: "module",
@@ -5425,14 +5438,14 @@ var package_default = {
5425
5438
  // src/util/versions.ts
5426
5439
  import { mainSymbols } from "figures";
5427
5440
  var { triangleRightSmall: t } = mainSymbols;
5428
- var versions_default = (runId, versions, adaptor) => {
5441
+ var versions_default = (stepId, versions, adaptor) => {
5429
5442
  let longest = "compiler".length;
5430
5443
  for (const v in versions) {
5431
5444
  longest = Math.max(v.length, longest);
5432
5445
  }
5433
5446
  const { node, compiler, engine, worker, runtime, ...adaptors } = versions;
5434
5447
  const prefix = (str2) => ` ${t} ${str2.padEnd(longest + 4, " ")}`;
5435
- let str = `Versions for run ${runId}:
5448
+ let str = `Versions for step ${stepId}:
5436
5449
  ${prefix("node.js")}${versions.node || "unknown"}
5437
5450
  ${prefix("worker")}${versions.worker || "unknown"}
5438
5451
  ${prefix("engine")}${versions.engine || "unknown"}`;
@@ -5446,11 +5459,11 @@ ${prefix("engine")}${versions.engine || "unknown"}`;
5446
5459
  return str;
5447
5460
  };
5448
5461
 
5449
- // src/events/run-start.ts
5450
- async function onRunStart(context, event) {
5462
+ // src/events/step-start.ts
5463
+ async function onStepStart(context, event) {
5451
5464
  const time = (timestamp() - BigInt(1e7)).toString();
5452
5465
  const { channel, state } = context;
5453
- state.activeRun = crypto4.randomUUID();
5466
+ state.activeStep = crypto4.randomUUID();
5454
5467
  state.activeJob = event.jobId;
5455
5468
  const job = state.plan.jobs.find(({ id }) => id === event.jobId);
5456
5469
  const input_dataclip_id = state.inputDataclips[event.jobId];
@@ -5462,17 +5475,17 @@ async function onRunStart(context, event) {
5462
5475
  ...context,
5463
5476
  state: {
5464
5477
  ...state,
5465
- activeRun: state.activeRun
5478
+ activeStep: state.activeStep
5466
5479
  }
5467
5480
  };
5468
- await sendEvent(channel, RUN_START, {
5469
- run_id: state.activeRun,
5481
+ await sendEvent(channel, STEP_START, {
5482
+ step_id: state.activeStep,
5470
5483
  job_id: state.activeJob,
5471
5484
  input_dataclip_id,
5472
5485
  versions
5473
5486
  });
5474
5487
  const versionMessage = versions_default(
5475
- versionLogContext.state.activeRun,
5488
+ versionLogContext.state.activeStep,
5476
5489
  versions,
5477
5490
  job?.adaptor
5478
5491
  );
@@ -5485,6 +5498,57 @@ async function onRunStart(context, event) {
5485
5498
  return;
5486
5499
  }
5487
5500
 
5501
+ // src/util/log-final-reason.ts
5502
+ import { timestamp as timestamp2 } from "@openfn/logger";
5503
+ var log_final_reason_default = async (context, reason) => {
5504
+ const time = (timestamp2() - BigInt(1e7)).toString();
5505
+ let message = `Run complete with status: ${reason.reason}`;
5506
+ if (reason.reason !== "success") {
5507
+ message += `
5508
+ ${reason.error_type}: ${reason.error_message || "unknown"}`;
5509
+ }
5510
+ await onJobLog(context, {
5511
+ time,
5512
+ message: [message],
5513
+ level: "info",
5514
+ name: "R/T"
5515
+ });
5516
+ };
5517
+
5518
+ // src/events/attempt-complete.ts
5519
+ async function onWorkflowComplete(context, _event) {
5520
+ const { state, channel, onFinish } = context;
5521
+ const result = state.dataclips[state.lastDataclipId];
5522
+ const reason = calculateAttemptExitReason(state);
5523
+ await log_final_reason_default(context, reason);
5524
+ await sendEvent(channel, ATTEMPT_COMPLETE, {
5525
+ final_dataclip_id: state.lastDataclipId,
5526
+ ...reason
5527
+ });
5528
+ onFinish({ reason, state: result });
5529
+ }
5530
+
5531
+ // src/events/attempt-error.ts
5532
+ async function onAttemptError(context, event) {
5533
+ const { state, channel, logger: logger2, onFinish } = context;
5534
+ try {
5535
+ const reason = calculateJobExitReason("", { data: {} }, event);
5536
+ if (state.activeJob) {
5537
+ await onJobError(context, { error: event });
5538
+ }
5539
+ await log_final_reason_default(context, reason);
5540
+ await sendEvent(channel, ATTEMPT_COMPLETE, {
5541
+ final_dataclip_id: state.lastDataclipId,
5542
+ ...reason
5543
+ });
5544
+ onFinish({ reason });
5545
+ } catch (e) {
5546
+ logger2.error("ERROR in workflow-error handler:", e.message);
5547
+ logger2.error(e);
5548
+ onFinish({});
5549
+ }
5550
+ }
5551
+
5488
5552
  // src/util/throttle.ts
5489
5553
  var createThrottler = () => {
5490
5554
  const q = [];
@@ -5516,8 +5580,8 @@ var throttle_default = createThrottler;
5516
5580
  var enc = new TextDecoder("utf-8");
5517
5581
  var eventMap = {
5518
5582
  "workflow-start": ATTEMPT_START,
5519
- "job-start": RUN_START,
5520
- "job-complete": RUN_COMPLETE,
5583
+ "job-start": STEP_START,
5584
+ "job-complete": STEP_COMPLETE,
5521
5585
  "workflow-log": ATTEMPT_LOG,
5522
5586
  "workflow-complete": ATTEMPT_COMPLETE
5523
5587
  };
@@ -5547,12 +5611,12 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
5547
5611
  const listeners = Object.assign(
5548
5612
  {},
5549
5613
  addEvent("workflow-start", throttle(onWorkflowStart)),
5550
- addEvent("job-start", throttle(onRunStart)),
5551
- addEvent("job-complete", throttle(onRunComplete)),
5614
+ addEvent("job-start", throttle(onStepStart)),
5615
+ addEvent("job-complete", throttle(onStepComplete)),
5552
5616
  addEvent("job-error", throttle(onJobError)),
5553
5617
  addEvent("workflow-log", throttle(onJobLog)),
5554
5618
  addEvent("workflow-complete", throttle(onWorkflowComplete)),
5555
- addEvent("workflow-error", throttle(onWorkflowError))
5619
+ addEvent("workflow-error", throttle(onAttemptError))
5556
5620
  );
5557
5621
  engine.listen(plan.id, listeners);
5558
5622
  const resolvers = {
@@ -5570,7 +5634,7 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
5570
5634
  try {
5571
5635
  engine.execute(plan, { resolvers, ...options });
5572
5636
  } catch (e) {
5573
- onWorkflowError(context, {
5637
+ onAttemptError(context, {
5574
5638
  workflowId: plan.id,
5575
5639
  message: e.message,
5576
5640
  type: e.type,
@@ -5588,41 +5652,14 @@ var sendEvent = (channel, event, payload) => new Promise((resolve5, reject) => {
5588
5652
  function onJobError(context, event) {
5589
5653
  const { state, error, jobId } = event;
5590
5654
  if (state?.errors?.[jobId]?.message === error.message) {
5591
- return onRunComplete(context, event);
5655
+ return onStepComplete(context, event);
5592
5656
  } else {
5593
- return onRunComplete(context, event, event.error);
5657
+ return onStepComplete(context, event, event.error);
5594
5658
  }
5595
5659
  }
5596
5660
  function onWorkflowStart({ channel }, _event) {
5597
5661
  return sendEvent(channel, ATTEMPT_START);
5598
5662
  }
5599
- async function onWorkflowComplete({ state, channel, onFinish }, _event) {
5600
- const result = state.dataclips[state.lastDataclipId];
5601
- const reason = calculateAttemptExitReason(state);
5602
- await sendEvent(channel, ATTEMPT_COMPLETE, {
5603
- final_dataclip_id: state.lastDataclipId,
5604
- ...reason
5605
- });
5606
- onFinish({ reason, state: result });
5607
- }
5608
- async function onWorkflowError(context, event) {
5609
- const { state, channel, logger: logger2, onFinish } = context;
5610
- try {
5611
- const reason = calculateJobExitReason("", { data: {} }, event);
5612
- if (state.activeJob) {
5613
- await onJobError(context, { error: event });
5614
- }
5615
- await sendEvent(channel, ATTEMPT_COMPLETE, {
5616
- final_dataclip_id: state.lastDataclipId,
5617
- ...reason
5618
- });
5619
- onFinish({ reason });
5620
- } catch (e) {
5621
- logger2.error("ERROR in workflow-error handler:", e.message);
5622
- logger2.error(e);
5623
- onFinish({});
5624
- }
5625
- }
5626
5663
  function onJobLog({ channel, state }, event) {
5627
5664
  const timeInMicroseconds = BigInt(event.time) / BigInt(1e3);
5628
5665
  const log = {
@@ -5632,8 +5669,8 @@ function onJobLog({ channel, state }, event) {
5632
5669
  level: event.level,
5633
5670
  timestamp: timeInMicroseconds.toString()
5634
5671
  };
5635
- if (state.activeRun) {
5636
- log.run_id = state.activeRun;
5672
+ if (state.activeStep) {
5673
+ log.step_id = state.activeStep;
5637
5674
  }
5638
5675
  return sendEvent(channel, ATTEMPT_LOG, log);
5639
5676
  }
@@ -5795,7 +5832,6 @@ function connect(app, logger2, options = {}) {
5795
5832
  function createServer(engine, options = {}) {
5796
5833
  const logger2 = options.logger || createMockLogger2();
5797
5834
  const port = options.port || DEFAULT_PORT;
5798
- logger2.debug("Starting server");
5799
5835
  const app = new Koa();
5800
5836
  app.id = humanId({ separator: "-", capitalize: false });
5801
5837
  const router = new Router();
@@ -5810,7 +5846,7 @@ function createServer(engine, options = {}) {
5810
5846
  app.workflows = {};
5811
5847
  app.destroyed = false;
5812
5848
  app.server = app.listen(port);
5813
- logger2.success(`ws-worker ${app.id} listening on ${port}`);
5849
+ logger2.success(`Worker ${app.id} listening on ${port}`);
5814
5850
  process.send?.("READY");
5815
5851
  router.get("/livez", healthcheck_default);
5816
5852
  router.get("/", healthcheck_default);
@@ -5879,30 +5915,37 @@ var server_default = createServer;
5879
5915
 
5880
5916
  // src/start.ts
5881
5917
  var {
5918
+ WORKER_BACKOFF,
5919
+ WORKER_CAPACITY,
5920
+ WORKER_LIGHTNING_SERVICE_URL,
5921
+ WORKER_LOG_LEVEL,
5922
+ WORKER_MAX_RUN_DURATION_SECONDS,
5923
+ WORKER_MAX_RUN_MEMORY_MB,
5924
+ WORKER_PORT,
5882
5925
  WORKER_REPO_DIR,
5883
5926
  WORKER_SECRET,
5884
- MAX_RUN_MEMORY,
5885
- STATE_PROPS_TO_REMOVE
5927
+ WORKER_STATE_PROPS_TO_REMOVE
5886
5928
  } = process.env;
5887
5929
  var args = yargs_default(hideBin(process.argv)).command("server", "Start a ws-worker server").option("port", {
5888
5930
  alias: "p",
5889
- description: "Port to run the server on",
5931
+ description: "Port to run the server on. Env: WORKER_PORT",
5890
5932
  type: "number",
5891
- default: 2222
5933
+ default: WORKER_PORT || 2222
5892
5934
  }).option("lightning", {
5893
- alias: "l",
5894
- description: 'Base url to Lightning websocket endpoint, eg, ws://localhost:4000/worker. Set to "mock" to use the default mock server',
5895
- default: "ws://localhost:4000/worker"
5935
+ alias: ["l", "lightning-service-url"],
5936
+ description: 'Base url to Lightning websocket endpoint, eg, ws://localhost:4000/worker. Set to "mock" to use the default mock server. Env: WORKER_LIGHTNING_SERVICE_URL',
5937
+ default: WORKER_LIGHTNING_SERVICE_URL || "ws://localhost:4000/worker"
5896
5938
  }).option("repo-dir", {
5897
5939
  alias: "d",
5898
- description: "Path to the runtime repo (where modules will be installed)",
5940
+ description: "Path to the runtime repo (where modules will be installed). Env: WORKER_REPO_DIR",
5899
5941
  default: WORKER_REPO_DIR
5900
5942
  }).option("secret", {
5901
5943
  alias: "s",
5902
- description: "Worker secret (comes from WORKER_SECRET by default)"
5944
+ description: "Worker secret. (comes from WORKER_SECRET by default). Env: WORKER_SECRET",
5945
+ default: WORKER_SECRET
5903
5946
  }).option("log", {
5904
- description: "Worker secret (comes from WORKER_SECRET by default)",
5905
- default: "info",
5947
+ description: "Set the log level for stdout (default to info, set to debug for verbose output). Env: WORKER_LOG_LEVEL",
5948
+ default: WORKER_LOG_LEVEL || "debug",
5906
5949
  type: "string"
5907
5950
  }).option("loop", {
5908
5951
  description: "Disable the claims loop",
@@ -5913,20 +5956,25 @@ var args = yargs_default(hideBin(process.argv)).command("server", "Start a ws-wo
5913
5956
  default: false,
5914
5957
  type: "boolean"
5915
5958
  }).option("backoff", {
5916
- description: "Claim backoff rules: min/max (s)",
5917
- default: "1/10"
5959
+ description: "Claim backoff rules: min/max (in seconds). Env: WORKER_BACKOFF",
5960
+ default: WORKER_BACKOFF || "1/10"
5918
5961
  }).option("capacity", {
5919
- description: "max concurrent workers",
5920
- default: 5,
5962
+ description: "max concurrent workers. Env: WORKER_CAPACITY",
5963
+ default: WORKER_CAPACITY ? parseInt(WORKER_CAPACITY) : 5,
5921
5964
  type: "number"
5922
5965
  }).option("state-props-to-remove", {
5923
- description: "A list of properties to remove from the final state returned by a job",
5924
- default: STATE_PROPS_TO_REMOVE ?? ["configuration", "response"],
5966
+ description: "A list of properties to remove from the final state returned by a job. Env: WORKER_STATE_PROPS_TO_REMOVE",
5967
+ default: WORKER_STATE_PROPS_TO_REMOVE ?? ["configuration", "response"],
5925
5968
  type: "array"
5926
5969
  }).option("run-memory", {
5927
- description: "Maximum memory allocated to a single run, in mb",
5970
+ description: "Maximum memory allocated to a single run, in mb. Env: WORKER_MAX_RUN_MEMORY_MB",
5971
+ type: "number",
5972
+ default: WORKER_MAX_RUN_MEMORY_MB ? parseInt(WORKER_MAX_RUN_MEMORY_MB) : 500
5973
+ }).option("max-run-duration-seconds", {
5974
+ alias: "t",
5975
+ description: "Default attempt timeout for the server, in seconds. Env: WORKER_MAX_RUN_DURATION_SECONDS",
5928
5976
  type: "number",
5929
- default: MAX_RUN_MEMORY ? parseInt(MAX_RUN_MEMORY) : 500
5977
+ default: WORKER_MAX_RUN_DURATION_SECONDS || 60 * 5
5930
5978
  }).parse();
5931
5979
  var logger = createLogger("SRV", { level: args.log });
5932
5980
  if (args.lightning === "mock") {
@@ -5935,15 +5983,13 @@ if (args.lightning === "mock") {
5935
5983
  args.secret = "abdefg";
5936
5984
  }
5937
5985
  } else if (!args.secret) {
5938
- if (!WORKER_SECRET) {
5939
- logger.error("WORKER_SECRET is not set");
5940
- process.exit(1);
5941
- }
5942
- args.secret = WORKER_SECRET;
5986
+ logger.error("WORKER_SECRET is not set");
5987
+ process.exit(1);
5943
5988
  }
5944
5989
  var [minBackoff, maxBackoff] = args.backoff.split("/").map((n) => parseInt(n, 10) * 1e3);
5945
5990
  function engineReady(engine) {
5946
- server_default(engine, {
5991
+ logger.debug("Creating worker server...");
5992
+ const workerOptions = {
5947
5993
  port: args.port,
5948
5994
  lightning: args.lightning,
5949
5995
  logger,
@@ -5954,7 +6000,10 @@ function engineReady(engine) {
5954
6000
  max: maxBackoff
5955
6001
  },
5956
6002
  maxWorkflows: args.capacity
5957
- });
6003
+ };
6004
+ const { logger: _l, secret: _s, ...humanOptions } = workerOptions;
6005
+ logger.debug("Worker options:", humanOptions);
6006
+ server_default(engine, workerOptions);
5958
6007
  }
5959
6008
  if (args.mock) {
5960
6009
  runtime_engine_default().then((engine) => {
@@ -5962,13 +6011,17 @@ if (args.mock) {
5962
6011
  engineReady(engine);
5963
6012
  });
5964
6013
  } else {
5965
- createRTE({
6014
+ const engineOptions = {
5966
6015
  repoDir: args.repoDir,
5967
6016
  memoryLimitMb: args.runMemory,
5968
6017
  maxWorkers: args.capacity,
5969
- statePropsToRemove: args.statePropsToRemove
5970
- }).then((engine) => {
5971
- logger.debug("engine created");
6018
+ statePropsToRemove: args.statePropsToRemove,
6019
+ attemptTimeoutMs: args.maxRunDurationSeconds * 1e3
6020
+ };
6021
+ logger.debug("Creating runtime engine...");
6022
+ logger.debug("Engine options:", engineOptions);
6023
+ createRTE(engineOptions).then((engine) => {
6024
+ logger.debug("Engine created!");
5972
6025
  engineReady(engine);
5973
6026
  });
5974
6027
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/ws-worker",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "A Websocket Worker to connect Lightning to a Runtime Engine",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -22,9 +22,9 @@
22
22
  "koa-logger": "^3.2.1",
23
23
  "phoenix": "^1.7.7",
24
24
  "ws": "^8.14.1",
25
- "@openfn/engine-multi": "0.2.6",
26
- "@openfn/runtime": "0.2.4",
27
- "@openfn/logger": "0.0.19"
25
+ "@openfn/engine-multi": "0.3.0",
26
+ "@openfn/logger": "0.0.19",
27
+ "@openfn/runtime": "0.2.5"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/koa": "^2.13.5",
@@ -41,7 +41,7 @@
41
41
  "tsup": "^6.2.3",
42
42
  "typescript": "^4.6.4",
43
43
  "yargs": "^17.6.2",
44
- "@openfn/lightning-mock": "1.1.8"
44
+ "@openfn/lightning-mock": "1.1.11"
45
45
  },
46
46
  "files": [
47
47
  "dist",