@openfn/ws-worker 1.4.1 → 1.5.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,11 @@
1
1
  # ws-worker
2
2
 
3
+ ## 1.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f363254: Allow a payload limit to be set for large dataclips and logs (payload_limit_mb)
8
+
3
9
  ## 1.4.1
4
10
 
5
11
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -41,6 +41,7 @@ interface Channel extends Channel$1 {
41
41
 
42
42
  declare type WorkerRunOptions = ExecuteOptions & {
43
43
  outputDataclips?: boolean;
44
+ payloadLimitMb?: number;
44
45
  };
45
46
 
46
47
  declare type Context = {
@@ -64,6 +65,7 @@ declare type ServerOptions = {
64
65
  min?: number;
65
66
  max?: number;
66
67
  };
68
+ payloadLimitMb?: number;
67
69
  };
68
70
  interface ServerApp extends Koa {
69
71
  id: string;
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ var name, version, description, main, type, scripts, bin, author, license, depen
29
29
  var init_package = __esm({
30
30
  "package.json"() {
31
31
  name = "@openfn/ws-worker";
32
- version = "1.4.1";
32
+ version = "1.5.0";
33
33
  description = "A Websocket Worker to connect Lightning to a Runtime Engine";
34
34
  main = "dist/index.js";
35
35
  type = "module";
@@ -332,13 +332,19 @@ var convert_lightning_plan_default = (run) => {
332
332
  const runtimeOpts = {};
333
333
  const engineOpts = {};
334
334
  if (run.options) {
335
- if (run.options.run_timeout_ms) {
335
+ if ("run_timeout_ms" in run.options) {
336
336
  engineOpts.runTimeoutMs = run.options.run_timeout_ms;
337
337
  }
338
- if (run.options.sanitize) {
338
+ if ("payload_limit_mb" in run.options) {
339
+ engineOpts.payloadLimitMb = run.options.payload_limit_mb;
340
+ }
341
+ if ("run_memory_limit_mb" in run.options) {
342
+ engineOpts.memoryLimitMb = run.options.run_memory_limit_mb;
343
+ }
344
+ if ("sanitize" in run.options) {
339
345
  engineOpts.sanitize = run.options.sanitize;
340
346
  }
341
- if (run.options.hasOwnProperty("output_dataclips")) {
347
+ if ("output_dataclips" in run.options) {
342
348
  engineOpts.outputDataclips = run.options.output_dataclips;
343
349
  }
344
350
  }
@@ -527,7 +533,7 @@ ${prefix("worker")}${versions.worker || "unknown"}`;
527
533
  // src/events/run-start.ts
528
534
  init_package();
529
535
  async function onRunStart(context, event) {
530
- const { channel, state } = context;
536
+ const { channel, state, options = {} } = context;
531
537
  const time = (timestamp() - BigInt(1e7)).toString();
532
538
  const versionLogContext = {
533
539
  ...context,
@@ -541,6 +547,14 @@ async function onRunStart(context, event) {
541
547
  ...event.versions
542
548
  };
543
549
  await sendEvent(channel, RUN_START, { versions });
550
+ if ("payloadLimitMb" in options) {
551
+ await onJobLog(versionLogContext, {
552
+ time,
553
+ message: [`Payload limit: ${options.payloadLimitMb}mb`],
554
+ level: "info",
555
+ name: "RTE"
556
+ });
557
+ }
544
558
  const versionMessage = versions_default(versions);
545
559
  await onJobLog(versionLogContext, {
546
560
  time,
@@ -552,6 +566,7 @@ async function onRunStart(context, event) {
552
566
 
553
567
  // src/events/step-complete.ts
554
568
  import crypto3 from "node:crypto";
569
+ import { timestamp as timestamp2 } from "@openfn/logger";
555
570
 
556
571
  // src/api/reasons.ts
557
572
  var calculateJobExitReason = (jobId, state = { data: {} }, error) => {
@@ -586,8 +601,25 @@ var calculateRunExitReason = (state) => {
586
601
  return { reason: "success", error_type: null, error_message: null };
587
602
  };
588
603
 
604
+ // src/util/ensure-payload-size.ts
605
+ var ensure_payload_size_default = (payload, limit_mb) => {
606
+ if (!isNaN(limit_mb)) {
607
+ const limit = limit_mb;
608
+ const size_bytes = Buffer.byteLength(payload, "utf8");
609
+ const size_mb = size_bytes / 1024 / 1024;
610
+ if (size_mb > limit) {
611
+ const e = new Error();
612
+ e.severity = "kill";
613
+ e.name = "PAYLOAD_TOO_LARGE";
614
+ e.message = `The payload exceeded the size limit of ${limit}mb`;
615
+ throw e;
616
+ }
617
+ }
618
+ };
619
+
589
620
  // src/events/step-complete.ts
590
- function onStepComplete({ channel, state, options }, event, error) {
621
+ async function onStepComplete(context, event, error) {
622
+ const { channel, state, options } = context;
591
623
  const dataclipId = crypto3.randomUUID();
592
624
  const step_id = state.activeStep;
593
625
  const job_id = state.activeJob;
@@ -602,26 +634,35 @@ function onStepComplete({ channel, state, options }, event, error) {
602
634
  event.next?.forEach((nextJobId) => {
603
635
  state.inputDataclips[nextJobId] = dataclipId;
604
636
  });
605
- const { reason, error_message, error_type } = calculateJobExitReason(
606
- job_id,
607
- event.state,
608
- error
609
- );
610
- state.reasons[job_id] = { reason, error_message, error_type };
611
637
  const evt = {
612
638
  step_id,
613
639
  job_id,
614
- output_dataclip_id: dataclipId,
615
- reason,
616
- error_message,
617
- error_type,
618
640
  mem: event.mem,
619
641
  duration: event.duration,
620
642
  thread_id: event.threadId
621
643
  };
622
- if (!options || options.outputDataclips !== false) {
623
- evt.output_dataclip = stringify_default(outputState);
644
+ try {
645
+ if (!options || options.outputDataclips !== false) {
646
+ const payload = stringify_default(outputState);
647
+ ensure_payload_size_default(payload, options?.payloadLimitMb);
648
+ evt.output_dataclip = payload;
649
+ }
650
+ evt.output_dataclip_id = dataclipId;
651
+ } catch (e) {
652
+ evt.output_dataclip_error = "DATACLIP_TOO_LARGE";
653
+ const time = (timestamp2() - BigInt(1e7)).toString();
654
+ await onJobLog(context, {
655
+ time,
656
+ message: [
657
+ "Dataclip too large. This dataclip will not be sent back to lighting."
658
+ ],
659
+ level: "info",
660
+ name: "R/T"
661
+ });
624
662
  }
663
+ const reason = calculateJobExitReason(job_id, event.state, error);
664
+ state.reasons[job_id] = reason;
665
+ Object.assign(evt, reason);
625
666
  return sendEvent(channel, STEP_COMPLETE, evt);
626
667
  }
627
668
 
@@ -640,9 +681,9 @@ async function onStepStart(context, event) {
640
681
  }
641
682
 
642
683
  // src/util/log-final-reason.ts
643
- import { timestamp as timestamp2 } from "@openfn/logger";
684
+ import { timestamp as timestamp3 } from "@openfn/logger";
644
685
  var log_final_reason_default = async (context, reason) => {
645
- const time = (timestamp2() - BigInt(1e7)).toString();
686
+ const time = (timestamp3() - BigInt(1e7)).toString();
646
687
  let message = `Run complete with status: ${reason.reason}`;
647
688
  if (reason.reason !== "success") {
648
689
  message += `
@@ -792,11 +833,25 @@ function onJobError(context, event) {
792
833
  return onStepComplete(context, event, event.error);
793
834
  }
794
835
  }
795
- function onJobLog({ channel, state }, event) {
836
+ function onJobLog({ channel, state, options }, event) {
796
837
  const timeInMicroseconds = BigInt(event.time) / BigInt(1e3);
838
+ let message = event.message;
839
+ try {
840
+ if (typeof event.message === "string") {
841
+ ensure_payload_size_default(event.message, options?.payloadLimitMb);
842
+ message = JSON.parse(message);
843
+ } else if (event.message) {
844
+ const payload = stringify_default(event.message);
845
+ ensure_payload_size_default(payload, options?.payloadLimitMb);
846
+ }
847
+ } catch (e) {
848
+ message = [
849
+ `(Log message redacted: exceeds ${options.payloadLimitMb}mb memory limit)`
850
+ ];
851
+ }
797
852
  const log = {
798
853
  run_id: state.plan.id,
799
- message: typeof event.message === "string" ? JSON.parse(event.message) : event.message,
854
+ message,
800
855
  source: event.name,
801
856
  level: event.level,
802
857
  timestamp: timeInMicroseconds.toString()
@@ -1005,16 +1060,19 @@ function createServer(engine, options = {}) {
1005
1060
  process.send?.("READY");
1006
1061
  router.get("/livez", healthcheck_default);
1007
1062
  router.get("/", healthcheck_default);
1008
- app.options = options || {};
1063
+ app.options = options;
1009
1064
  app.execute = async ({ id, token }) => {
1010
1065
  if (app.socket) {
1011
1066
  app.workflows[id] = true;
1012
1067
  const {
1013
1068
  channel: runChannel,
1014
1069
  plan,
1015
- options: options2,
1070
+ options: options2 = {},
1016
1071
  input
1017
1072
  } = await run_default(app.socket, token, id, logger);
1073
+ if (!("payloadLimitMb" in options2)) {
1074
+ options2.payloadLimitMb = app.options.payloadLimitMb;
1075
+ }
1018
1076
  const onFinish = () => {
1019
1077
  logger.debug(`workflow ${id} complete: releasing worker`);
1020
1078
  delete app.workflows[id];
package/dist/start.js CHANGED
@@ -37,7 +37,7 @@ var name, version, description, main, type, scripts, bin, author, license, depen
37
37
  var init_package = __esm({
38
38
  "package.json"() {
39
39
  name = "@openfn/ws-worker";
40
- version = "1.4.1";
40
+ version = "1.5.0";
41
41
  description = "A Websocket Worker to connect Lightning to a Runtime Engine";
42
42
  main = "dist/index.js";
43
43
  type = "module";
@@ -471,13 +471,19 @@ var convert_lightning_plan_default = (run2) => {
471
471
  const runtimeOpts = {};
472
472
  const engineOpts = {};
473
473
  if (run2.options) {
474
- if (run2.options.run_timeout_ms) {
474
+ if ("run_timeout_ms" in run2.options) {
475
475
  engineOpts.runTimeoutMs = run2.options.run_timeout_ms;
476
476
  }
477
- if (run2.options.sanitize) {
477
+ if ("payload_limit_mb" in run2.options) {
478
+ engineOpts.payloadLimitMb = run2.options.payload_limit_mb;
479
+ }
480
+ if ("run_memory_limit_mb" in run2.options) {
481
+ engineOpts.memoryLimitMb = run2.options.run_memory_limit_mb;
482
+ }
483
+ if ("sanitize" in run2.options) {
478
484
  engineOpts.sanitize = run2.options.sanitize;
479
485
  }
480
- if (run2.options.hasOwnProperty("output_dataclips")) {
486
+ if ("output_dataclips" in run2.options) {
481
487
  engineOpts.outputDataclips = run2.options.output_dataclips;
482
488
  }
483
489
  }
@@ -666,7 +672,7 @@ ${prefix("worker")}${versions.worker || "unknown"}`;
666
672
  // src/events/run-start.ts
667
673
  init_package();
668
674
  async function onRunStart(context, event) {
669
- const { channel, state } = context;
675
+ const { channel, state, options = {} } = context;
670
676
  const time = (timestamp() - BigInt(1e7)).toString();
671
677
  const versionLogContext = {
672
678
  ...context,
@@ -680,6 +686,14 @@ async function onRunStart(context, event) {
680
686
  ...event.versions
681
687
  };
682
688
  await sendEvent(channel, RUN_START, { versions });
689
+ if ("payloadLimitMb" in options) {
690
+ await onJobLog(versionLogContext, {
691
+ time,
692
+ message: [`Payload limit: ${options.payloadLimitMb}mb`],
693
+ level: "info",
694
+ name: "RTE"
695
+ });
696
+ }
683
697
  const versionMessage = versions_default(versions);
684
698
  await onJobLog(versionLogContext, {
685
699
  time,
@@ -691,6 +705,7 @@ async function onRunStart(context, event) {
691
705
 
692
706
  // src/events/step-complete.ts
693
707
  import crypto4 from "node:crypto";
708
+ import { timestamp as timestamp2 } from "@openfn/logger";
694
709
 
695
710
  // src/api/reasons.ts
696
711
  var calculateJobExitReason = (jobId, state = { data: {} }, error) => {
@@ -725,8 +740,25 @@ var calculateRunExitReason = (state) => {
725
740
  return { reason: "success", error_type: null, error_message: null };
726
741
  };
727
742
 
743
+ // src/util/ensure-payload-size.ts
744
+ var ensure_payload_size_default = (payload, limit_mb) => {
745
+ if (!isNaN(limit_mb)) {
746
+ const limit = limit_mb;
747
+ const size_bytes = Buffer.byteLength(payload, "utf8");
748
+ const size_mb = size_bytes / 1024 / 1024;
749
+ if (size_mb > limit) {
750
+ const e = new Error();
751
+ e.severity = "kill";
752
+ e.name = "PAYLOAD_TOO_LARGE";
753
+ e.message = `The payload exceeded the size limit of ${limit}mb`;
754
+ throw e;
755
+ }
756
+ }
757
+ };
758
+
728
759
  // src/events/step-complete.ts
729
- function onStepComplete({ channel, state, options }, event, error) {
760
+ async function onStepComplete(context, event, error) {
761
+ const { channel, state, options } = context;
730
762
  const dataclipId = crypto4.randomUUID();
731
763
  const step_id = state.activeStep;
732
764
  const job_id = state.activeJob;
@@ -741,26 +773,35 @@ function onStepComplete({ channel, state, options }, event, error) {
741
773
  event.next?.forEach((nextJobId) => {
742
774
  state.inputDataclips[nextJobId] = dataclipId;
743
775
  });
744
- const { reason, error_message, error_type } = calculateJobExitReason(
745
- job_id,
746
- event.state,
747
- error
748
- );
749
- state.reasons[job_id] = { reason, error_message, error_type };
750
776
  const evt = {
751
777
  step_id,
752
778
  job_id,
753
- output_dataclip_id: dataclipId,
754
- reason,
755
- error_message,
756
- error_type,
757
779
  mem: event.mem,
758
780
  duration: event.duration,
759
781
  thread_id: event.threadId
760
782
  };
761
- if (!options || options.outputDataclips !== false) {
762
- evt.output_dataclip = stringify_default(outputState);
783
+ try {
784
+ if (!options || options.outputDataclips !== false) {
785
+ const payload = stringify_default(outputState);
786
+ ensure_payload_size_default(payload, options?.payloadLimitMb);
787
+ evt.output_dataclip = payload;
788
+ }
789
+ evt.output_dataclip_id = dataclipId;
790
+ } catch (e) {
791
+ evt.output_dataclip_error = "DATACLIP_TOO_LARGE";
792
+ const time = (timestamp2() - BigInt(1e7)).toString();
793
+ await onJobLog(context, {
794
+ time,
795
+ message: [
796
+ "Dataclip too large. This dataclip will not be sent back to lighting."
797
+ ],
798
+ level: "info",
799
+ name: "R/T"
800
+ });
763
801
  }
802
+ const reason = calculateJobExitReason(job_id, event.state, error);
803
+ state.reasons[job_id] = reason;
804
+ Object.assign(evt, reason);
764
805
  return sendEvent(channel, STEP_COMPLETE, evt);
765
806
  }
766
807
 
@@ -779,9 +820,9 @@ async function onStepStart(context, event) {
779
820
  }
780
821
 
781
822
  // src/util/log-final-reason.ts
782
- import { timestamp as timestamp2 } from "@openfn/logger";
823
+ import { timestamp as timestamp3 } from "@openfn/logger";
783
824
  var log_final_reason_default = async (context, reason) => {
784
- const time = (timestamp2() - BigInt(1e7)).toString();
825
+ const time = (timestamp3() - BigInt(1e7)).toString();
785
826
  let message = `Run complete with status: ${reason.reason}`;
786
827
  if (reason.reason !== "success") {
787
828
  message += `
@@ -931,11 +972,25 @@ function onJobError(context, event) {
931
972
  return onStepComplete(context, event, event.error);
932
973
  }
933
974
  }
934
- function onJobLog({ channel, state }, event) {
975
+ function onJobLog({ channel, state, options }, event) {
935
976
  const timeInMicroseconds = BigInt(event.time) / BigInt(1e3);
977
+ let message = event.message;
978
+ try {
979
+ if (typeof event.message === "string") {
980
+ ensure_payload_size_default(event.message, options?.payloadLimitMb);
981
+ message = JSON.parse(message);
982
+ } else if (event.message) {
983
+ const payload = stringify_default(event.message);
984
+ ensure_payload_size_default(payload, options?.payloadLimitMb);
985
+ }
986
+ } catch (e) {
987
+ message = [
988
+ `(Log message redacted: exceeds ${options.payloadLimitMb}mb memory limit)`
989
+ ];
990
+ }
936
991
  const log = {
937
992
  run_id: state.plan.id,
938
- message: typeof event.message === "string" ? JSON.parse(event.message) : event.message,
993
+ message,
939
994
  source: event.name,
940
995
  level: event.level,
941
996
  timestamp: timeInMicroseconds.toString()
@@ -1144,16 +1199,19 @@ function createServer(engine, options = {}) {
1144
1199
  process.send?.("READY");
1145
1200
  router.get("/livez", healthcheck_default);
1146
1201
  router.get("/", healthcheck_default);
1147
- app.options = options || {};
1202
+ app.options = options;
1148
1203
  app.execute = async ({ id, token }) => {
1149
1204
  if (app.socket) {
1150
1205
  app.workflows[id] = true;
1151
1206
  const {
1152
1207
  channel: runChannel,
1153
1208
  plan,
1154
- options: options2,
1209
+ options: options2 = {},
1155
1210
  input
1156
1211
  } = await run_default(app.socket, token, id, logger2);
1212
+ if (!("payloadLimitMb" in options2)) {
1213
+ options2.payloadLimitMb = app.options.payloadLimitMb;
1214
+ }
1157
1215
  const onFinish = () => {
1158
1216
  logger2.debug(`workflow ${id} complete: releasing worker`);
1159
1217
  delete app.workflows[id];
@@ -6082,6 +6140,7 @@ function parseArgs(argv) {
6082
6140
  WORKER_LIGHTNING_PUBLIC_KEY,
6083
6141
  WORKER_LIGHTNING_SERVICE_URL,
6084
6142
  WORKER_LOG_LEVEL,
6143
+ WORKER_MAX_PAYLOAD_MB,
6085
6144
  WORKER_MAX_RUN_DURATION_SECONDS,
6086
6145
  WORKER_MAX_RUN_MEMORY_MB,
6087
6146
  WORKER_PORT,
@@ -6125,6 +6184,9 @@ function parseArgs(argv) {
6125
6184
  }).option("run-memory", {
6126
6185
  description: "Maximum memory allocated to a single run, in mb. Env: WORKER_MAX_RUN_MEMORY_MB",
6127
6186
  type: "number"
6187
+ }).option("payload-memory", {
6188
+ description: "Maximum memory allocated to a single run, in mb. Env: WORKER_MAX_PAYLOAD_MB",
6189
+ type: "number"
6128
6190
  }).option("max-run-duration-seconds", {
6129
6191
  alias: "t",
6130
6192
  description: "Default run timeout for the server, in seconds. Env: WORKER_MAX_RUN_DURATION_SECONDS",
@@ -6154,6 +6216,7 @@ function parseArgs(argv) {
6154
6216
  ["configuration", "response"]
6155
6217
  ),
6156
6218
  runMemory: setArg(args2.runMemory, WORKER_MAX_RUN_MEMORY_MB, 500),
6219
+ payloadMemory: setArg(args2.payloadMemory, WORKER_MAX_PAYLOAD_MB, 10),
6157
6220
  maxRunDurationSeconds: setArg(
6158
6221
  args2.maxRunDurationSeconds,
6159
6222
  WORKER_MAX_RUN_DURATION_SECONDS,
@@ -6187,7 +6250,8 @@ function engineReady(engine) {
6187
6250
  min: minBackoff,
6188
6251
  max: maxBackoff
6189
6252
  },
6190
- maxWorkflows: args.capacity
6253
+ maxWorkflows: args.capacity,
6254
+ payloadLimitMb: args.payloadMemory
6191
6255
  };
6192
6256
  if (args.lightningPublicKey) {
6193
6257
  logger.info(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/ws-worker",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "A Websocket Worker to connect Lightning to a Runtime Engine",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -23,9 +23,9 @@
23
23
  "phoenix": "1.7.10",
24
24
  "ws": "^8.14.1",
25
25
  "@openfn/engine-multi": "1.2.1",
26
+ "@openfn/logger": "1.0.1",
26
27
  "@openfn/lexicon": "^1.0.2",
27
- "@openfn/runtime": "1.4.1",
28
- "@openfn/logger": "1.0.1"
28
+ "@openfn/runtime": "1.4.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/koa": "^2.13.5",