@openfn/ws-worker 1.19.7 → 1.20.1
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 +23 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +272 -150
- package/dist/start.js +302 -151
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# ws-worker
|
|
2
2
|
|
|
3
|
+
## 1.20.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [a1bfdc1]
|
|
8
|
+
- Updated dependencies [c70369b]
|
|
9
|
+
- @openfn/runtime@1.7.7
|
|
10
|
+
- @openfn/logger@1.1.1
|
|
11
|
+
- @openfn/engine-multi@1.9.1
|
|
12
|
+
|
|
13
|
+
## 1.20.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- 3a7038b: Allow logs to be sent to Lightning in batches. This behavior is disabled by default for back-compatibility: set `WORKER_BATCH_LOGS=true` to enable.
|
|
18
|
+
- 5417e97: Reduce the payload size of log events sent out to Lightning. This defaults to 1mb but can be configured with `WORKER_MAX_LOG_PAYLOAD_MB`
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [5417e97]
|
|
23
|
+
- @openfn/lexicon@1.3.0
|
|
24
|
+
- @openfn/engine-multi@1.9.0
|
|
25
|
+
|
|
3
26
|
## 1.19.7
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -49,9 +49,13 @@ interface Channel extends Channel$1 {
|
|
|
49
49
|
type WorkerRunOptions = ExecuteOptions & {
|
|
50
50
|
outputDataclips?: boolean;
|
|
51
51
|
payloadLimitMb?: number;
|
|
52
|
+
logPayloadLimitMb?: number;
|
|
52
53
|
jobLogLevel?: LogLevel;
|
|
53
54
|
timeoutRetryCount?: number;
|
|
54
55
|
timeoutRetryDelay?: number;
|
|
56
|
+
batchLogs?: boolean;
|
|
57
|
+
batchInterval?: number;
|
|
58
|
+
batchLimit?: number;
|
|
55
59
|
};
|
|
56
60
|
|
|
57
61
|
type Context = {
|
|
@@ -65,6 +69,9 @@ type Context = {
|
|
|
65
69
|
};
|
|
66
70
|
|
|
67
71
|
type ServerOptions = {
|
|
72
|
+
batchLogs?: boolean;
|
|
73
|
+
batchInterval?: number;
|
|
74
|
+
batchLimit?: number;
|
|
68
75
|
maxWorkflows?: number;
|
|
69
76
|
port?: number;
|
|
70
77
|
lightning?: string;
|
|
@@ -82,6 +89,7 @@ type ServerOptions = {
|
|
|
82
89
|
messageTimeoutSeconds?: number;
|
|
83
90
|
claimTimeoutSeconds?: number;
|
|
84
91
|
payloadLimitMb?: number;
|
|
92
|
+
logPayloadLimitMb?: number;
|
|
85
93
|
collectionsVersion?: string;
|
|
86
94
|
collectionsUrl?: string;
|
|
87
95
|
monorepoDir?: string;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
import { exec as _exec } from "node:child_process";
|
|
5
|
-
import * as
|
|
5
|
+
import * as Sentry7 from "@sentry/node";
|
|
6
6
|
import Koa from "koa";
|
|
7
7
|
import bodyParser from "koa-bodyparser";
|
|
8
8
|
import koaLogger from "koa-logger";
|
|
@@ -271,7 +271,16 @@ var startWorkloop = (app, logger, minBackoff, maxBackoff, maxWorkers) => {
|
|
|
271
271
|
var workloop_default = startWorkloop;
|
|
272
272
|
|
|
273
273
|
// src/api/execute.ts
|
|
274
|
-
import * as
|
|
274
|
+
import * as Sentry4 from "@sentry/node";
|
|
275
|
+
import {
|
|
276
|
+
JOB_COMPLETE as JOB_COMPLETE2,
|
|
277
|
+
JOB_START as JOB_START2,
|
|
278
|
+
WORKFLOW_COMPLETE as WORKFLOW_COMPLETE2,
|
|
279
|
+
WORKFLOW_LOG as WORKFLOW_LOG2,
|
|
280
|
+
WORKFLOW_START as WORKFLOW_START2,
|
|
281
|
+
JOB_ERROR as JOB_ERROR2,
|
|
282
|
+
WORKFLOW_ERROR as WORKFLOW_ERROR2
|
|
283
|
+
} from "@openfn/engine-multi";
|
|
275
284
|
|
|
276
285
|
// src/util/convert-lightning-plan.ts
|
|
277
286
|
import crypto2 from "node:crypto";
|
|
@@ -342,6 +351,9 @@ var convert_lightning_plan_default = (run, options = {}) => {
|
|
|
342
351
|
if ("payload_limit_mb" in run.options) {
|
|
343
352
|
engineOpts.payloadLimitMb = run.options.payload_limit_mb;
|
|
344
353
|
}
|
|
354
|
+
if ("log_payload_limit_mb" in run.options) {
|
|
355
|
+
engineOpts.logPayloadLimitMb = run.options.log_payload_limit_mb;
|
|
356
|
+
}
|
|
345
357
|
if ("run_memory_limit_mb" in run.options) {
|
|
346
358
|
engineOpts.memoryLimitMb = run.options.run_memory_limit_mb;
|
|
347
359
|
}
|
|
@@ -576,43 +588,59 @@ var stringify_default = (obj) => stringify(obj, (_key, value) => {
|
|
|
576
588
|
return value;
|
|
577
589
|
});
|
|
578
590
|
|
|
579
|
-
// src/util/throttle.ts
|
|
580
|
-
var createThrottler = () => {
|
|
581
|
-
const q = [];
|
|
582
|
-
let activePromise;
|
|
583
|
-
const add = (fn) => {
|
|
584
|
-
return (...args) => (
|
|
585
|
-
// the wrapped function is a deferred promise
|
|
586
|
-
// that will reject or resolve as usual
|
|
587
|
-
new Promise((resolve, reject) => {
|
|
588
|
-
q.push({ fn, args, resolve, reject });
|
|
589
|
-
shift();
|
|
590
|
-
})
|
|
591
|
-
);
|
|
592
|
-
};
|
|
593
|
-
const shift = () => {
|
|
594
|
-
if (activePromise) {
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
const next = q.shift();
|
|
598
|
-
if (next) {
|
|
599
|
-
const { fn, args, resolve, reject } = next;
|
|
600
|
-
activePromise = fn(...args).then(resolve).catch(reject).finally(() => {
|
|
601
|
-
activePromise = void 0;
|
|
602
|
-
shift();
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
};
|
|
606
|
-
return add;
|
|
607
|
-
};
|
|
608
|
-
var throttle_default = createThrottler;
|
|
609
|
-
|
|
610
591
|
// src/util/timestamp.ts
|
|
611
592
|
var timeInMicroseconds = (time) => time && (BigInt(time) / BigInt(1e3)).toString();
|
|
612
593
|
|
|
613
594
|
// src/events/run-start.ts
|
|
614
595
|
import { timestamp } from "@openfn/logger";
|
|
615
596
|
|
|
597
|
+
// src/events/run-log.ts
|
|
598
|
+
async function onRunLog(context, events) {
|
|
599
|
+
if (!Array.isArray(events)) {
|
|
600
|
+
events = [events];
|
|
601
|
+
}
|
|
602
|
+
const { state, options } = context;
|
|
603
|
+
const { batchLogs } = options ?? {};
|
|
604
|
+
const logs = events.map((evt) => {
|
|
605
|
+
let message = evt.message;
|
|
606
|
+
if (evt.redacted) {
|
|
607
|
+
message = [
|
|
608
|
+
`(Log message redacted: exceeds ${options.logPayloadLimitMb ?? options.payloadLimitMb}mb memory limit)`
|
|
609
|
+
];
|
|
610
|
+
} else if (typeof evt.message === "string") {
|
|
611
|
+
message = JSON.parse(evt.message);
|
|
612
|
+
}
|
|
613
|
+
const logLine = {
|
|
614
|
+
message,
|
|
615
|
+
source: evt.name,
|
|
616
|
+
level: evt.level,
|
|
617
|
+
timestamp: timeInMicroseconds(evt.time)
|
|
618
|
+
};
|
|
619
|
+
if (state.activeStep) {
|
|
620
|
+
logLine.step_id = state.activeStep;
|
|
621
|
+
}
|
|
622
|
+
return logLine;
|
|
623
|
+
});
|
|
624
|
+
if (batchLogs) {
|
|
625
|
+
const payload = {
|
|
626
|
+
run_id: `${state.plan.id}`,
|
|
627
|
+
logs
|
|
628
|
+
};
|
|
629
|
+
return sendEvent(context, RUN_LOG, payload);
|
|
630
|
+
} else {
|
|
631
|
+
return new Promise(async (resolve) => {
|
|
632
|
+
for (const log of logs) {
|
|
633
|
+
const payload = {
|
|
634
|
+
run_id: `${state.plan.id}`,
|
|
635
|
+
...log
|
|
636
|
+
};
|
|
637
|
+
await sendEvent(context, RUN_LOG, payload);
|
|
638
|
+
}
|
|
639
|
+
resolve();
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
616
644
|
// src/util/versions.ts
|
|
617
645
|
import { mainSymbols } from "figures";
|
|
618
646
|
var { triangleRightSmall: t } = mainSymbols;
|
|
@@ -680,21 +708,25 @@ async function onRunStart(context, event) {
|
|
|
680
708
|
timestamp: timeInMicroseconds(event.time)
|
|
681
709
|
});
|
|
682
710
|
if ("payloadLimitMb" in options) {
|
|
683
|
-
await
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
711
|
+
await onRunLog(versionLogContext, [
|
|
712
|
+
{
|
|
713
|
+
// use the fake time in the log
|
|
714
|
+
time,
|
|
715
|
+
message: [`Payload limit: ${options.payloadLimitMb}mb`],
|
|
716
|
+
level: "info",
|
|
717
|
+
name: "RTE"
|
|
718
|
+
}
|
|
719
|
+
]);
|
|
690
720
|
}
|
|
691
721
|
const versionMessage = versions_default(versions);
|
|
692
|
-
await
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
722
|
+
await onRunLog(versionLogContext, [
|
|
723
|
+
{
|
|
724
|
+
time,
|
|
725
|
+
message: [versionMessage],
|
|
726
|
+
level: "info",
|
|
727
|
+
name: "VER"
|
|
728
|
+
}
|
|
729
|
+
]);
|
|
698
730
|
}
|
|
699
731
|
|
|
700
732
|
// src/events/step-complete.ts
|
|
@@ -763,14 +795,16 @@ async function onStepComplete(context, event, error) {
|
|
|
763
795
|
state.withheldDataclips[dataclipId] = true;
|
|
764
796
|
evt.output_dataclip_error = "DATACLIP_TOO_LARGE";
|
|
765
797
|
const time = (timestamp2() - BigInt(1e7)).toString();
|
|
766
|
-
await
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
798
|
+
await onRunLog(context, [
|
|
799
|
+
{
|
|
800
|
+
time,
|
|
801
|
+
message: [
|
|
802
|
+
"Dataclip exceeds payload limit: output will not be sent back to the app."
|
|
803
|
+
],
|
|
804
|
+
level: "info",
|
|
805
|
+
name: "R/T"
|
|
806
|
+
}
|
|
807
|
+
]);
|
|
774
808
|
} else {
|
|
775
809
|
evt.output_dataclip_id = dataclipId;
|
|
776
810
|
if (!options || options.outputDataclips !== false) {
|
|
@@ -807,20 +841,24 @@ import { timestamp as timestamp3 } from "@openfn/logger";
|
|
|
807
841
|
var log_final_reason_default = async (context, reason) => {
|
|
808
842
|
const time = (timestamp3() - BigInt(1e7)).toString();
|
|
809
843
|
const statusMessage = `Run complete with status: ${reason.reason}`;
|
|
810
|
-
await
|
|
811
|
-
|
|
812
|
-
message: [statusMessage],
|
|
813
|
-
level: "info",
|
|
814
|
-
name: "R/T"
|
|
815
|
-
});
|
|
816
|
-
if (reason.reason !== "success") {
|
|
817
|
-
const errorMessage = `${reason.error_type}: ${reason.error_message || "unknown"}`;
|
|
818
|
-
await onJobLog(context, {
|
|
844
|
+
await onRunLog(context, [
|
|
845
|
+
{
|
|
819
846
|
time,
|
|
820
|
-
message: [
|
|
847
|
+
message: [statusMessage],
|
|
821
848
|
level: "info",
|
|
822
849
|
name: "R/T"
|
|
823
|
-
}
|
|
850
|
+
}
|
|
851
|
+
]);
|
|
852
|
+
if (reason.reason !== "success") {
|
|
853
|
+
const errorMessage = `${reason.error_type}: ${reason.error_message || "unknown"}`;
|
|
854
|
+
await onRunLog(context, [
|
|
855
|
+
{
|
|
856
|
+
time,
|
|
857
|
+
message: [errorMessage],
|
|
858
|
+
level: "info",
|
|
859
|
+
name: "R/T"
|
|
860
|
+
}
|
|
861
|
+
]);
|
|
824
862
|
}
|
|
825
863
|
};
|
|
826
864
|
|
|
@@ -865,15 +903,137 @@ async function onRunError(context, event) {
|
|
|
865
903
|
}
|
|
866
904
|
}
|
|
867
905
|
|
|
868
|
-
// src/api/
|
|
869
|
-
|
|
906
|
+
// src/api/process-events.ts
|
|
907
|
+
import * as Sentry3 from "@sentry/node";
|
|
908
|
+
import {
|
|
909
|
+
JOB_COMPLETE,
|
|
910
|
+
JOB_ERROR,
|
|
911
|
+
JOB_START,
|
|
912
|
+
WORKFLOW_COMPLETE,
|
|
913
|
+
WORKFLOW_ERROR,
|
|
914
|
+
WORKFLOW_LOG,
|
|
915
|
+
WORKFLOW_START
|
|
916
|
+
} from "@openfn/engine-multi";
|
|
917
|
+
var DEFAULT_BATCH_LIMIT = 10;
|
|
918
|
+
var DEFAULT_BATCH_INTERVAL = 10;
|
|
870
919
|
var eventMap = {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
920
|
+
[WORKFLOW_START]: RUN_START,
|
|
921
|
+
[JOB_START]: STEP_START,
|
|
922
|
+
[JOB_COMPLETE]: STEP_COMPLETE,
|
|
923
|
+
[WORKFLOW_LOG]: RUN_LOG,
|
|
924
|
+
[WORKFLOW_COMPLETE]: RUN_COMPLETE
|
|
876
925
|
};
|
|
926
|
+
var allEngineEvents = [
|
|
927
|
+
WORKFLOW_START,
|
|
928
|
+
WORKFLOW_COMPLETE,
|
|
929
|
+
JOB_START,
|
|
930
|
+
JOB_COMPLETE,
|
|
931
|
+
WORKFLOW_LOG,
|
|
932
|
+
JOB_ERROR,
|
|
933
|
+
WORKFLOW_ERROR
|
|
934
|
+
];
|
|
935
|
+
function eventProcessor(engine, context, callbacks, options = {}) {
|
|
936
|
+
const { id: planId, logger } = context;
|
|
937
|
+
const {
|
|
938
|
+
batchLimit: limit = DEFAULT_BATCH_LIMIT,
|
|
939
|
+
batchInterval: interval = DEFAULT_BATCH_INTERVAL
|
|
940
|
+
} = options;
|
|
941
|
+
const queue = [];
|
|
942
|
+
const next = async () => {
|
|
943
|
+
const evt = queue[0];
|
|
944
|
+
if (evt) {
|
|
945
|
+
await process2(evt.name, evt.event);
|
|
946
|
+
queue.shift();
|
|
947
|
+
next();
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
let activeBatch = null;
|
|
951
|
+
let batch = [];
|
|
952
|
+
let start = -1;
|
|
953
|
+
let batchTimeout;
|
|
954
|
+
const sendBatch = async (name) => {
|
|
955
|
+
clearTimeout(batchTimeout);
|
|
956
|
+
activeBatch = null;
|
|
957
|
+
await send(name, batch, batch.length);
|
|
958
|
+
batch = [];
|
|
959
|
+
};
|
|
960
|
+
const send = async (name, payload, batchSize) => {
|
|
961
|
+
const lightningEvent = eventMap[name] ?? name;
|
|
962
|
+
await callbacks[name](context, payload);
|
|
963
|
+
if (batchSize) {
|
|
964
|
+
logger.info(
|
|
965
|
+
`${planId} :: sent ${lightningEvent} (${batchSize}):: OK :: ${Date.now() - start}ms`
|
|
966
|
+
);
|
|
967
|
+
} else {
|
|
968
|
+
logger.info(
|
|
969
|
+
`${planId} :: sent ${lightningEvent} :: OK :: ${Date.now() - start}ms`
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
const process2 = async (name, event) => {
|
|
974
|
+
if (name !== "workflow-log") {
|
|
975
|
+
Sentry3.addBreadcrumb({
|
|
976
|
+
category: "event",
|
|
977
|
+
message: name,
|
|
978
|
+
level: "info"
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
if (name === activeBatch) {
|
|
982
|
+
batch.push(event);
|
|
983
|
+
if (batch.length >= limit) {
|
|
984
|
+
await sendBatch(name);
|
|
985
|
+
}
|
|
986
|
+
return;
|
|
987
|
+
} else if (activeBatch) {
|
|
988
|
+
await sendBatch(activeBatch);
|
|
989
|
+
}
|
|
990
|
+
if (name in callbacks) {
|
|
991
|
+
try {
|
|
992
|
+
start = Date.now();
|
|
993
|
+
if (options?.batch?.[name]) {
|
|
994
|
+
activeBatch = name;
|
|
995
|
+
batch.push(event);
|
|
996
|
+
while (queue.length > 1 && queue[1].name === name) {
|
|
997
|
+
const [nextBatchItem] = queue.splice(1, 1);
|
|
998
|
+
batch.push(nextBatchItem.event);
|
|
999
|
+
if (batch.length >= limit) {
|
|
1000
|
+
return sendBatch(name);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (!batchTimeout) {
|
|
1004
|
+
const batchName = activeBatch;
|
|
1005
|
+
batchTimeout = setTimeout(async () => {
|
|
1006
|
+
sendBatch(batchName);
|
|
1007
|
+
}, interval);
|
|
1008
|
+
}
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
await send(name, event);
|
|
1012
|
+
} catch (e2) {
|
|
1013
|
+
if (!e2.reportedToSentry) {
|
|
1014
|
+
Sentry3.captureException(e2);
|
|
1015
|
+
logger.error(e2);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
} else {
|
|
1019
|
+
logger.warn("no event bound for", name);
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
const enqueue = (name, event) => {
|
|
1023
|
+
queue.push({ name, event });
|
|
1024
|
+
if (queue.length == 1) {
|
|
1025
|
+
next();
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
const e = allEngineEvents.reduce(
|
|
1029
|
+
(obj, e2) => Object.assign(obj, { [e2]: (p) => enqueue(e2, p) }),
|
|
1030
|
+
{}
|
|
1031
|
+
);
|
|
1032
|
+
engine.listen(planId, e);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/api/execute.ts
|
|
1036
|
+
var enc = new TextDecoder("utf-8");
|
|
877
1037
|
function execute(channel, engine, logger, plan, input, options = {}, onFinish = (_result) => {
|
|
878
1038
|
}) {
|
|
879
1039
|
logger.info("executing ", plan.id);
|
|
@@ -887,8 +1047,8 @@ function execute(channel, engine, logger, plan, input, options = {}, onFinish =
|
|
|
887
1047
|
options,
|
|
888
1048
|
onFinish
|
|
889
1049
|
};
|
|
890
|
-
|
|
891
|
-
|
|
1050
|
+
Sentry4.withIsolationScope(async () => {
|
|
1051
|
+
Sentry4.addBreadcrumb({
|
|
892
1052
|
category: "run",
|
|
893
1053
|
message: "Executing run: loading metadata",
|
|
894
1054
|
level: "info",
|
|
@@ -896,47 +1056,26 @@ function execute(channel, engine, logger, plan, input, options = {}, onFinish =
|
|
|
896
1056
|
runId: plan.id
|
|
897
1057
|
}
|
|
898
1058
|
});
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
Sentry3.captureException(e);
|
|
919
|
-
logger.error(e);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
return {
|
|
924
|
-
[eventName]: wrappedFn
|
|
925
|
-
};
|
|
926
|
-
};
|
|
927
|
-
const listeners = Object.assign(
|
|
928
|
-
{},
|
|
929
|
-
addEvent("workflow-start", throttle(onRunStart)),
|
|
930
|
-
addEvent("job-start", throttle(onStepStart)),
|
|
931
|
-
addEvent("job-complete", throttle(onStepComplete)),
|
|
932
|
-
addEvent("job-error", throttle(onJobError)),
|
|
933
|
-
addEvent("workflow-log", throttle(onJobLog)),
|
|
934
|
-
// This will also resolve the promise
|
|
935
|
-
addEvent("workflow-complete", throttle(onWorkflowComplete)),
|
|
936
|
-
addEvent("workflow-error", throttle(onRunError))
|
|
937
|
-
// TODO send autoinstall logs
|
|
1059
|
+
eventProcessor(
|
|
1060
|
+
engine,
|
|
1061
|
+
context,
|
|
1062
|
+
{
|
|
1063
|
+
[WORKFLOW_START2]: onRunStart,
|
|
1064
|
+
[JOB_START2]: onStepStart,
|
|
1065
|
+
[JOB_COMPLETE2]: onStepComplete,
|
|
1066
|
+
[JOB_ERROR2]: onJobError,
|
|
1067
|
+
[WORKFLOW_LOG2]: onRunLog,
|
|
1068
|
+
[WORKFLOW_COMPLETE2]: onWorkflowComplete,
|
|
1069
|
+
[WORKFLOW_ERROR2]: onRunError
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
batch: options.batchLogs ? {
|
|
1073
|
+
[WORKFLOW_LOG2]: true
|
|
1074
|
+
} : {},
|
|
1075
|
+
batchInterval: options.batchInterval,
|
|
1076
|
+
batchLimit: options.batchLimit
|
|
1077
|
+
}
|
|
938
1078
|
);
|
|
939
|
-
engine.listen(plan.id, listeners);
|
|
940
1079
|
const resolvers = {
|
|
941
1080
|
credential: (id) => loadCredential(context, id)
|
|
942
1081
|
// TODO not supported right now
|
|
@@ -959,7 +1098,7 @@ function execute(channel, engine, logger, plan, input, options = {}, onFinish =
|
|
|
959
1098
|
}
|
|
960
1099
|
}
|
|
961
1100
|
try {
|
|
962
|
-
|
|
1101
|
+
Sentry4.addBreadcrumb({
|
|
963
1102
|
category: "run",
|
|
964
1103
|
message: "run metadata loaded: starting run",
|
|
965
1104
|
level: "info",
|
|
@@ -969,7 +1108,7 @@ function execute(channel, engine, logger, plan, input, options = {}, onFinish =
|
|
|
969
1108
|
});
|
|
970
1109
|
engine.execute(plan, loadedInput, { resolvers, ...options });
|
|
971
1110
|
} catch (e) {
|
|
972
|
-
|
|
1111
|
+
Sentry4.addBreadcrumb({
|
|
973
1112
|
category: "run",
|
|
974
1113
|
message: "exception in run",
|
|
975
1114
|
level: "info",
|
|
@@ -996,29 +1135,6 @@ function onJobError(context, event) {
|
|
|
996
1135
|
return onStepComplete(context, event, event.error);
|
|
997
1136
|
}
|
|
998
1137
|
}
|
|
999
|
-
function onJobLog(context, event) {
|
|
1000
|
-
const { state, options } = context;
|
|
1001
|
-
let message = event.message;
|
|
1002
|
-
if (event.redacted) {
|
|
1003
|
-
message = [
|
|
1004
|
-
`(Log message redacted: exceeds ${options.payloadLimitMb}mb memory limit)`
|
|
1005
|
-
];
|
|
1006
|
-
} else if (typeof event.message === "string") {
|
|
1007
|
-
message = JSON.parse(event.message);
|
|
1008
|
-
}
|
|
1009
|
-
const log = {
|
|
1010
|
-
run_id: `${state.plan.id}`,
|
|
1011
|
-
message,
|
|
1012
|
-
source: event.name,
|
|
1013
|
-
level: event.level,
|
|
1014
|
-
// @ts-ignore
|
|
1015
|
-
timestamp: timeInMicroseconds(event.time)
|
|
1016
|
-
};
|
|
1017
|
-
if (state.activeStep) {
|
|
1018
|
-
log.step_id = state.activeStep;
|
|
1019
|
-
}
|
|
1020
|
-
return sendEvent(context, RUN_LOG, log);
|
|
1021
|
-
}
|
|
1022
1138
|
async function loadDataclip(context, stateId) {
|
|
1023
1139
|
const result = await sendEvent(context, GET_DATACLIP, {
|
|
1024
1140
|
id: stateId
|
|
@@ -1036,7 +1152,7 @@ var healthcheck_default = (ctx) => {
|
|
|
1036
1152
|
};
|
|
1037
1153
|
|
|
1038
1154
|
// src/channels/run.ts
|
|
1039
|
-
import * as
|
|
1155
|
+
import * as Sentry5 from "@sentry/node";
|
|
1040
1156
|
var joinRunChannel = (socket, token, runId, logger, timeout = 30) => {
|
|
1041
1157
|
return new Promise((resolve, reject) => {
|
|
1042
1158
|
let didReceiveOk = false;
|
|
@@ -1055,12 +1171,12 @@ var joinRunChannel = (socket, token, runId, logger, timeout = 30) => {
|
|
|
1055
1171
|
resolve({ channel, run });
|
|
1056
1172
|
}
|
|
1057
1173
|
}).receive("error", (err) => {
|
|
1058
|
-
|
|
1174
|
+
Sentry5.captureException(err);
|
|
1059
1175
|
logger.error(`error connecting to ${channelName}`, err);
|
|
1060
1176
|
channel?.leave();
|
|
1061
1177
|
reject(err);
|
|
1062
1178
|
}).receive("timeout", (err) => {
|
|
1063
|
-
|
|
1179
|
+
Sentry5.captureException(err);
|
|
1064
1180
|
logger.error(`Timeout for ${channelName}`, err);
|
|
1065
1181
|
channel?.leave();
|
|
1066
1182
|
reject(err);
|
|
@@ -1077,7 +1193,7 @@ var run_default = joinRunChannel;
|
|
|
1077
1193
|
|
|
1078
1194
|
// src/channels/worker-queue.ts
|
|
1079
1195
|
import EventEmitter from "node:events";
|
|
1080
|
-
import * as
|
|
1196
|
+
import * as Sentry6 from "@sentry/node";
|
|
1081
1197
|
import { Socket as PhxSocket } from "phoenix";
|
|
1082
1198
|
import { WebSocket } from "ws";
|
|
1083
1199
|
import { API_VERSION } from "@openfn/lexicon/lightning";
|
|
@@ -1116,13 +1232,13 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger, options) => {
|
|
|
1116
1232
|
SocketConstructor = PhxSocket
|
|
1117
1233
|
} = options;
|
|
1118
1234
|
const events = new EventEmitter();
|
|
1119
|
-
|
|
1235
|
+
Sentry6.addBreadcrumb({
|
|
1120
1236
|
category: "lifecycle",
|
|
1121
1237
|
message: "Connecting to worker queue",
|
|
1122
1238
|
level: "info"
|
|
1123
1239
|
});
|
|
1124
1240
|
worker_token_default(secret, serverId, logger).then(async (token) => {
|
|
1125
|
-
|
|
1241
|
+
Sentry6.addBreadcrumb({
|
|
1126
1242
|
category: "lifecycle",
|
|
1127
1243
|
message: "Worker token generated",
|
|
1128
1244
|
level: "info"
|
|
@@ -1141,7 +1257,7 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger, options) => {
|
|
|
1141
1257
|
let didOpen = false;
|
|
1142
1258
|
let shouldReportConnectionError = true;
|
|
1143
1259
|
socket.onOpen(() => {
|
|
1144
|
-
|
|
1260
|
+
Sentry6.addBreadcrumb({
|
|
1145
1261
|
category: "lifecycle",
|
|
1146
1262
|
message: "Web socket connected",
|
|
1147
1263
|
level: "info"
|
|
@@ -1168,7 +1284,7 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger, options) => {
|
|
|
1168
1284
|
events.emit("disconnect");
|
|
1169
1285
|
});
|
|
1170
1286
|
socket.onError((e) => {
|
|
1171
|
-
|
|
1287
|
+
Sentry6.addBreadcrumb({
|
|
1172
1288
|
category: "lifecycle",
|
|
1173
1289
|
message: "Error in web socket connection",
|
|
1174
1290
|
level: "info"
|
|
@@ -1176,7 +1292,7 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger, options) => {
|
|
|
1176
1292
|
if (shouldReportConnectionError) {
|
|
1177
1293
|
logger.debug("Reporting connection error to sentry");
|
|
1178
1294
|
shouldReportConnectionError = false;
|
|
1179
|
-
|
|
1295
|
+
Sentry6.captureException(e);
|
|
1180
1296
|
}
|
|
1181
1297
|
if (!didOpen) {
|
|
1182
1298
|
events.emit("error", e.message);
|
|
@@ -1286,11 +1402,11 @@ function createServer(engine, options = {}) {
|
|
|
1286
1402
|
app.events = new EventEmitter2();
|
|
1287
1403
|
app.engine = engine;
|
|
1288
1404
|
if (options.sentryDsn) {
|
|
1289
|
-
|
|
1405
|
+
Sentry7.init({
|
|
1290
1406
|
environment: options.sentryEnv,
|
|
1291
1407
|
dsn: options.sentryDsn
|
|
1292
1408
|
});
|
|
1293
|
-
|
|
1409
|
+
Sentry7.setupKoaErrorHandler(app);
|
|
1294
1410
|
}
|
|
1295
1411
|
app.use(bodyParser());
|
|
1296
1412
|
app.use(
|
|
@@ -1347,8 +1463,14 @@ function createServer(engine, options = {}) {
|
|
|
1347
1463
|
if (!("payloadLimitMb" in options2)) {
|
|
1348
1464
|
options2.payloadLimitMb = app.options.payloadLimitMb;
|
|
1349
1465
|
}
|
|
1466
|
+
if (!("logPayloadLimitMb" in options2)) {
|
|
1467
|
+
options2.logPayloadLimitMb = app.options.logPayloadLimitMb;
|
|
1468
|
+
}
|
|
1350
1469
|
options2.timeoutRetryCount = app.options.timeoutRetryCount;
|
|
1351
1470
|
options2.timeoutRetryDelay = app.options.timeoutRetryDelayMs ?? app.options.socketTimeoutSeconds;
|
|
1471
|
+
options2.batchLogs = app.options.batchLogs;
|
|
1472
|
+
options2.batchInterval = app.options.batchInterval;
|
|
1473
|
+
options2.batchLimit = app.options.batchLimit;
|
|
1352
1474
|
const onFinish = () => {
|
|
1353
1475
|
const duration = (Date.now() - start) / 1e3;
|
|
1354
1476
|
logger.debug(
|
package/dist/start.js
CHANGED
|
@@ -151,7 +151,7 @@ var runtime_engine_default = createMock;
|
|
|
151
151
|
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
152
152
|
import { promisify } from "node:util";
|
|
153
153
|
import { exec as _exec } from "node:child_process";
|
|
154
|
-
import * as
|
|
154
|
+
import * as Sentry7 from "@sentry/node";
|
|
155
155
|
import Koa from "koa";
|
|
156
156
|
import bodyParser from "koa-bodyparser";
|
|
157
157
|
import koaLogger from "koa-logger";
|
|
@@ -420,7 +420,16 @@ var startWorkloop = (app, logger2, minBackoff2, maxBackoff2, maxWorkers) => {
|
|
|
420
420
|
var workloop_default = startWorkloop;
|
|
421
421
|
|
|
422
422
|
// src/api/execute.ts
|
|
423
|
-
import * as
|
|
423
|
+
import * as Sentry4 from "@sentry/node";
|
|
424
|
+
import {
|
|
425
|
+
JOB_COMPLETE as JOB_COMPLETE2,
|
|
426
|
+
JOB_START as JOB_START2,
|
|
427
|
+
WORKFLOW_COMPLETE as WORKFLOW_COMPLETE2,
|
|
428
|
+
WORKFLOW_LOG as WORKFLOW_LOG2,
|
|
429
|
+
WORKFLOW_START as WORKFLOW_START2,
|
|
430
|
+
JOB_ERROR as JOB_ERROR2,
|
|
431
|
+
WORKFLOW_ERROR as WORKFLOW_ERROR2
|
|
432
|
+
} from "@openfn/engine-multi";
|
|
424
433
|
|
|
425
434
|
// src/util/convert-lightning-plan.ts
|
|
426
435
|
import crypto3 from "node:crypto";
|
|
@@ -491,6 +500,9 @@ var convert_lightning_plan_default = (run2, options = {}) => {
|
|
|
491
500
|
if ("payload_limit_mb" in run2.options) {
|
|
492
501
|
engineOpts.payloadLimitMb = run2.options.payload_limit_mb;
|
|
493
502
|
}
|
|
503
|
+
if ("log_payload_limit_mb" in run2.options) {
|
|
504
|
+
engineOpts.logPayloadLimitMb = run2.options.log_payload_limit_mb;
|
|
505
|
+
}
|
|
494
506
|
if ("run_memory_limit_mb" in run2.options) {
|
|
495
507
|
engineOpts.memoryLimitMb = run2.options.run_memory_limit_mb;
|
|
496
508
|
}
|
|
@@ -725,43 +737,59 @@ var stringify_default = (obj) => stringify(obj, (_key, value) => {
|
|
|
725
737
|
return value;
|
|
726
738
|
});
|
|
727
739
|
|
|
728
|
-
// src/util/throttle.ts
|
|
729
|
-
var createThrottler = () => {
|
|
730
|
-
const q = [];
|
|
731
|
-
let activePromise;
|
|
732
|
-
const add = (fn) => {
|
|
733
|
-
return (...args2) => (
|
|
734
|
-
// the wrapped function is a deferred promise
|
|
735
|
-
// that will reject or resolve as usual
|
|
736
|
-
new Promise((resolve5, reject) => {
|
|
737
|
-
q.push({ fn, args: args2, resolve: resolve5, reject });
|
|
738
|
-
shift();
|
|
739
|
-
})
|
|
740
|
-
);
|
|
741
|
-
};
|
|
742
|
-
const shift = () => {
|
|
743
|
-
if (activePromise) {
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
const next = q.shift();
|
|
747
|
-
if (next) {
|
|
748
|
-
const { fn, args: args2, resolve: resolve5, reject } = next;
|
|
749
|
-
activePromise = fn(...args2).then(resolve5).catch(reject).finally(() => {
|
|
750
|
-
activePromise = void 0;
|
|
751
|
-
shift();
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
return add;
|
|
756
|
-
};
|
|
757
|
-
var throttle_default = createThrottler;
|
|
758
|
-
|
|
759
740
|
// src/util/timestamp.ts
|
|
760
741
|
var timeInMicroseconds = (time) => time && (BigInt(time) / BigInt(1e3)).toString();
|
|
761
742
|
|
|
762
743
|
// src/events/run-start.ts
|
|
763
744
|
import { timestamp } from "@openfn/logger";
|
|
764
745
|
|
|
746
|
+
// src/events/run-log.ts
|
|
747
|
+
async function onRunLog(context, events) {
|
|
748
|
+
if (!Array.isArray(events)) {
|
|
749
|
+
events = [events];
|
|
750
|
+
}
|
|
751
|
+
const { state, options } = context;
|
|
752
|
+
const { batchLogs } = options ?? {};
|
|
753
|
+
const logs = events.map((evt) => {
|
|
754
|
+
let message = evt.message;
|
|
755
|
+
if (evt.redacted) {
|
|
756
|
+
message = [
|
|
757
|
+
`(Log message redacted: exceeds ${options.logPayloadLimitMb ?? options.payloadLimitMb}mb memory limit)`
|
|
758
|
+
];
|
|
759
|
+
} else if (typeof evt.message === "string") {
|
|
760
|
+
message = JSON.parse(evt.message);
|
|
761
|
+
}
|
|
762
|
+
const logLine = {
|
|
763
|
+
message,
|
|
764
|
+
source: evt.name,
|
|
765
|
+
level: evt.level,
|
|
766
|
+
timestamp: timeInMicroseconds(evt.time)
|
|
767
|
+
};
|
|
768
|
+
if (state.activeStep) {
|
|
769
|
+
logLine.step_id = state.activeStep;
|
|
770
|
+
}
|
|
771
|
+
return logLine;
|
|
772
|
+
});
|
|
773
|
+
if (batchLogs) {
|
|
774
|
+
const payload = {
|
|
775
|
+
run_id: `${state.plan.id}`,
|
|
776
|
+
logs
|
|
777
|
+
};
|
|
778
|
+
return sendEvent(context, RUN_LOG, payload);
|
|
779
|
+
} else {
|
|
780
|
+
return new Promise(async (resolve5) => {
|
|
781
|
+
for (const log of logs) {
|
|
782
|
+
const payload = {
|
|
783
|
+
run_id: `${state.plan.id}`,
|
|
784
|
+
...log
|
|
785
|
+
};
|
|
786
|
+
await sendEvent(context, RUN_LOG, payload);
|
|
787
|
+
}
|
|
788
|
+
resolve5();
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
765
793
|
// src/util/versions.ts
|
|
766
794
|
import { mainSymbols } from "figures";
|
|
767
795
|
var { triangleRightSmall: t } = mainSymbols;
|
|
@@ -829,21 +857,25 @@ async function onRunStart(context, event) {
|
|
|
829
857
|
timestamp: timeInMicroseconds(event.time)
|
|
830
858
|
});
|
|
831
859
|
if ("payloadLimitMb" in options) {
|
|
832
|
-
await
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
860
|
+
await onRunLog(versionLogContext, [
|
|
861
|
+
{
|
|
862
|
+
// use the fake time in the log
|
|
863
|
+
time,
|
|
864
|
+
message: [`Payload limit: ${options.payloadLimitMb}mb`],
|
|
865
|
+
level: "info",
|
|
866
|
+
name: "RTE"
|
|
867
|
+
}
|
|
868
|
+
]);
|
|
839
869
|
}
|
|
840
870
|
const versionMessage = versions_default(versions);
|
|
841
|
-
await
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
871
|
+
await onRunLog(versionLogContext, [
|
|
872
|
+
{
|
|
873
|
+
time,
|
|
874
|
+
message: [versionMessage],
|
|
875
|
+
level: "info",
|
|
876
|
+
name: "VER"
|
|
877
|
+
}
|
|
878
|
+
]);
|
|
847
879
|
}
|
|
848
880
|
|
|
849
881
|
// src/events/step-complete.ts
|
|
@@ -912,14 +944,16 @@ async function onStepComplete(context, event, error) {
|
|
|
912
944
|
state.withheldDataclips[dataclipId] = true;
|
|
913
945
|
evt.output_dataclip_error = "DATACLIP_TOO_LARGE";
|
|
914
946
|
const time = (timestamp2() - BigInt(1e7)).toString();
|
|
915
|
-
await
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
947
|
+
await onRunLog(context, [
|
|
948
|
+
{
|
|
949
|
+
time,
|
|
950
|
+
message: [
|
|
951
|
+
"Dataclip exceeds payload limit: output will not be sent back to the app."
|
|
952
|
+
],
|
|
953
|
+
level: "info",
|
|
954
|
+
name: "R/T"
|
|
955
|
+
}
|
|
956
|
+
]);
|
|
923
957
|
} else {
|
|
924
958
|
evt.output_dataclip_id = dataclipId;
|
|
925
959
|
if (!options || options.outputDataclips !== false) {
|
|
@@ -956,20 +990,24 @@ import { timestamp as timestamp3 } from "@openfn/logger";
|
|
|
956
990
|
var log_final_reason_default = async (context, reason) => {
|
|
957
991
|
const time = (timestamp3() - BigInt(1e7)).toString();
|
|
958
992
|
const statusMessage = `Run complete with status: ${reason.reason}`;
|
|
959
|
-
await
|
|
960
|
-
|
|
961
|
-
message: [statusMessage],
|
|
962
|
-
level: "info",
|
|
963
|
-
name: "R/T"
|
|
964
|
-
});
|
|
965
|
-
if (reason.reason !== "success") {
|
|
966
|
-
const errorMessage = `${reason.error_type}: ${reason.error_message || "unknown"}`;
|
|
967
|
-
await onJobLog(context, {
|
|
993
|
+
await onRunLog(context, [
|
|
994
|
+
{
|
|
968
995
|
time,
|
|
969
|
-
message: [
|
|
996
|
+
message: [statusMessage],
|
|
970
997
|
level: "info",
|
|
971
998
|
name: "R/T"
|
|
972
|
-
}
|
|
999
|
+
}
|
|
1000
|
+
]);
|
|
1001
|
+
if (reason.reason !== "success") {
|
|
1002
|
+
const errorMessage = `${reason.error_type}: ${reason.error_message || "unknown"}`;
|
|
1003
|
+
await onRunLog(context, [
|
|
1004
|
+
{
|
|
1005
|
+
time,
|
|
1006
|
+
message: [errorMessage],
|
|
1007
|
+
level: "info",
|
|
1008
|
+
name: "R/T"
|
|
1009
|
+
}
|
|
1010
|
+
]);
|
|
973
1011
|
}
|
|
974
1012
|
};
|
|
975
1013
|
|
|
@@ -1014,15 +1052,137 @@ async function onRunError(context, event) {
|
|
|
1014
1052
|
}
|
|
1015
1053
|
}
|
|
1016
1054
|
|
|
1017
|
-
// src/api/
|
|
1018
|
-
|
|
1055
|
+
// src/api/process-events.ts
|
|
1056
|
+
import * as Sentry3 from "@sentry/node";
|
|
1057
|
+
import {
|
|
1058
|
+
JOB_COMPLETE,
|
|
1059
|
+
JOB_ERROR,
|
|
1060
|
+
JOB_START,
|
|
1061
|
+
WORKFLOW_COMPLETE,
|
|
1062
|
+
WORKFLOW_ERROR,
|
|
1063
|
+
WORKFLOW_LOG,
|
|
1064
|
+
WORKFLOW_START
|
|
1065
|
+
} from "@openfn/engine-multi";
|
|
1066
|
+
var DEFAULT_BATCH_LIMIT = 10;
|
|
1067
|
+
var DEFAULT_BATCH_INTERVAL = 10;
|
|
1019
1068
|
var eventMap = {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1069
|
+
[WORKFLOW_START]: RUN_START,
|
|
1070
|
+
[JOB_START]: STEP_START,
|
|
1071
|
+
[JOB_COMPLETE]: STEP_COMPLETE,
|
|
1072
|
+
[WORKFLOW_LOG]: RUN_LOG,
|
|
1073
|
+
[WORKFLOW_COMPLETE]: RUN_COMPLETE
|
|
1025
1074
|
};
|
|
1075
|
+
var allEngineEvents = [
|
|
1076
|
+
WORKFLOW_START,
|
|
1077
|
+
WORKFLOW_COMPLETE,
|
|
1078
|
+
JOB_START,
|
|
1079
|
+
JOB_COMPLETE,
|
|
1080
|
+
WORKFLOW_LOG,
|
|
1081
|
+
JOB_ERROR,
|
|
1082
|
+
WORKFLOW_ERROR
|
|
1083
|
+
];
|
|
1084
|
+
function eventProcessor(engine, context, callbacks, options = {}) {
|
|
1085
|
+
const { id: planId, logger: logger2 } = context;
|
|
1086
|
+
const {
|
|
1087
|
+
batchLimit: limit = DEFAULT_BATCH_LIMIT,
|
|
1088
|
+
batchInterval: interval = DEFAULT_BATCH_INTERVAL
|
|
1089
|
+
} = options;
|
|
1090
|
+
const queue = [];
|
|
1091
|
+
const next = async () => {
|
|
1092
|
+
const evt = queue[0];
|
|
1093
|
+
if (evt) {
|
|
1094
|
+
await process2(evt.name, evt.event);
|
|
1095
|
+
queue.shift();
|
|
1096
|
+
next();
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
let activeBatch = null;
|
|
1100
|
+
let batch = [];
|
|
1101
|
+
let start = -1;
|
|
1102
|
+
let batchTimeout;
|
|
1103
|
+
const sendBatch = async (name) => {
|
|
1104
|
+
clearTimeout(batchTimeout);
|
|
1105
|
+
activeBatch = null;
|
|
1106
|
+
await send(name, batch, batch.length);
|
|
1107
|
+
batch = [];
|
|
1108
|
+
};
|
|
1109
|
+
const send = async (name, payload, batchSize) => {
|
|
1110
|
+
const lightningEvent = eventMap[name] ?? name;
|
|
1111
|
+
await callbacks[name](context, payload);
|
|
1112
|
+
if (batchSize) {
|
|
1113
|
+
logger2.info(
|
|
1114
|
+
`${planId} :: sent ${lightningEvent} (${batchSize}):: OK :: ${Date.now() - start}ms`
|
|
1115
|
+
);
|
|
1116
|
+
} else {
|
|
1117
|
+
logger2.info(
|
|
1118
|
+
`${planId} :: sent ${lightningEvent} :: OK :: ${Date.now() - start}ms`
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
const process2 = async (name, event) => {
|
|
1123
|
+
if (name !== "workflow-log") {
|
|
1124
|
+
Sentry3.addBreadcrumb({
|
|
1125
|
+
category: "event",
|
|
1126
|
+
message: name,
|
|
1127
|
+
level: "info"
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
if (name === activeBatch) {
|
|
1131
|
+
batch.push(event);
|
|
1132
|
+
if (batch.length >= limit) {
|
|
1133
|
+
await sendBatch(name);
|
|
1134
|
+
}
|
|
1135
|
+
return;
|
|
1136
|
+
} else if (activeBatch) {
|
|
1137
|
+
await sendBatch(activeBatch);
|
|
1138
|
+
}
|
|
1139
|
+
if (name in callbacks) {
|
|
1140
|
+
try {
|
|
1141
|
+
start = Date.now();
|
|
1142
|
+
if (options?.batch?.[name]) {
|
|
1143
|
+
activeBatch = name;
|
|
1144
|
+
batch.push(event);
|
|
1145
|
+
while (queue.length > 1 && queue[1].name === name) {
|
|
1146
|
+
const [nextBatchItem] = queue.splice(1, 1);
|
|
1147
|
+
batch.push(nextBatchItem.event);
|
|
1148
|
+
if (batch.length >= limit) {
|
|
1149
|
+
return sendBatch(name);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
if (!batchTimeout) {
|
|
1153
|
+
const batchName = activeBatch;
|
|
1154
|
+
batchTimeout = setTimeout(async () => {
|
|
1155
|
+
sendBatch(batchName);
|
|
1156
|
+
}, interval);
|
|
1157
|
+
}
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
await send(name, event);
|
|
1161
|
+
} catch (e2) {
|
|
1162
|
+
if (!e2.reportedToSentry) {
|
|
1163
|
+
Sentry3.captureException(e2);
|
|
1164
|
+
logger2.error(e2);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
} else {
|
|
1168
|
+
logger2.warn("no event bound for", name);
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
const enqueue = (name, event) => {
|
|
1172
|
+
queue.push({ name, event });
|
|
1173
|
+
if (queue.length == 1) {
|
|
1174
|
+
next();
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
const e = allEngineEvents.reduce(
|
|
1178
|
+
(obj, e2) => Object.assign(obj, { [e2]: (p) => enqueue(e2, p) }),
|
|
1179
|
+
{}
|
|
1180
|
+
);
|
|
1181
|
+
engine.listen(planId, e);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// src/api/execute.ts
|
|
1185
|
+
var enc = new TextDecoder("utf-8");
|
|
1026
1186
|
function execute(channel, engine, logger2, plan, input, options = {}, onFinish = (_result) => {
|
|
1027
1187
|
}) {
|
|
1028
1188
|
logger2.info("executing ", plan.id);
|
|
@@ -1036,8 +1196,8 @@ function execute(channel, engine, logger2, plan, input, options = {}, onFinish =
|
|
|
1036
1196
|
options,
|
|
1037
1197
|
onFinish
|
|
1038
1198
|
};
|
|
1039
|
-
|
|
1040
|
-
|
|
1199
|
+
Sentry4.withIsolationScope(async () => {
|
|
1200
|
+
Sentry4.addBreadcrumb({
|
|
1041
1201
|
category: "run",
|
|
1042
1202
|
message: "Executing run: loading metadata",
|
|
1043
1203
|
level: "info",
|
|
@@ -1045,47 +1205,26 @@ function execute(channel, engine, logger2, plan, input, options = {}, onFinish =
|
|
|
1045
1205
|
runId: plan.id
|
|
1046
1206
|
}
|
|
1047
1207
|
});
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
Sentry3.captureException(e);
|
|
1068
|
-
logger2.error(e);
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
};
|
|
1072
|
-
return {
|
|
1073
|
-
[eventName]: wrappedFn
|
|
1074
|
-
};
|
|
1075
|
-
};
|
|
1076
|
-
const listeners = Object.assign(
|
|
1077
|
-
{},
|
|
1078
|
-
addEvent("workflow-start", throttle(onRunStart)),
|
|
1079
|
-
addEvent("job-start", throttle(onStepStart)),
|
|
1080
|
-
addEvent("job-complete", throttle(onStepComplete)),
|
|
1081
|
-
addEvent("job-error", throttle(onJobError)),
|
|
1082
|
-
addEvent("workflow-log", throttle(onJobLog)),
|
|
1083
|
-
// This will also resolve the promise
|
|
1084
|
-
addEvent("workflow-complete", throttle(onWorkflowComplete)),
|
|
1085
|
-
addEvent("workflow-error", throttle(onRunError))
|
|
1086
|
-
// TODO send autoinstall logs
|
|
1208
|
+
eventProcessor(
|
|
1209
|
+
engine,
|
|
1210
|
+
context,
|
|
1211
|
+
{
|
|
1212
|
+
[WORKFLOW_START2]: onRunStart,
|
|
1213
|
+
[JOB_START2]: onStepStart,
|
|
1214
|
+
[JOB_COMPLETE2]: onStepComplete,
|
|
1215
|
+
[JOB_ERROR2]: onJobError,
|
|
1216
|
+
[WORKFLOW_LOG2]: onRunLog,
|
|
1217
|
+
[WORKFLOW_COMPLETE2]: onWorkflowComplete,
|
|
1218
|
+
[WORKFLOW_ERROR2]: onRunError
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
batch: options.batchLogs ? {
|
|
1222
|
+
[WORKFLOW_LOG2]: true
|
|
1223
|
+
} : {},
|
|
1224
|
+
batchInterval: options.batchInterval,
|
|
1225
|
+
batchLimit: options.batchLimit
|
|
1226
|
+
}
|
|
1087
1227
|
);
|
|
1088
|
-
engine.listen(plan.id, listeners);
|
|
1089
1228
|
const resolvers = {
|
|
1090
1229
|
credential: (id) => loadCredential(context, id)
|
|
1091
1230
|
// TODO not supported right now
|
|
@@ -1108,7 +1247,7 @@ function execute(channel, engine, logger2, plan, input, options = {}, onFinish =
|
|
|
1108
1247
|
}
|
|
1109
1248
|
}
|
|
1110
1249
|
try {
|
|
1111
|
-
|
|
1250
|
+
Sentry4.addBreadcrumb({
|
|
1112
1251
|
category: "run",
|
|
1113
1252
|
message: "run metadata loaded: starting run",
|
|
1114
1253
|
level: "info",
|
|
@@ -1118,7 +1257,7 @@ function execute(channel, engine, logger2, plan, input, options = {}, onFinish =
|
|
|
1118
1257
|
});
|
|
1119
1258
|
engine.execute(plan, loadedInput, { resolvers, ...options });
|
|
1120
1259
|
} catch (e) {
|
|
1121
|
-
|
|
1260
|
+
Sentry4.addBreadcrumb({
|
|
1122
1261
|
category: "run",
|
|
1123
1262
|
message: "exception in run",
|
|
1124
1263
|
level: "info",
|
|
@@ -1145,29 +1284,6 @@ function onJobError(context, event) {
|
|
|
1145
1284
|
return onStepComplete(context, event, event.error);
|
|
1146
1285
|
}
|
|
1147
1286
|
}
|
|
1148
|
-
function onJobLog(context, event) {
|
|
1149
|
-
const { state, options } = context;
|
|
1150
|
-
let message = event.message;
|
|
1151
|
-
if (event.redacted) {
|
|
1152
|
-
message = [
|
|
1153
|
-
`(Log message redacted: exceeds ${options.payloadLimitMb}mb memory limit)`
|
|
1154
|
-
];
|
|
1155
|
-
} else if (typeof event.message === "string") {
|
|
1156
|
-
message = JSON.parse(event.message);
|
|
1157
|
-
}
|
|
1158
|
-
const log = {
|
|
1159
|
-
run_id: `${state.plan.id}`,
|
|
1160
|
-
message,
|
|
1161
|
-
source: event.name,
|
|
1162
|
-
level: event.level,
|
|
1163
|
-
// @ts-ignore
|
|
1164
|
-
timestamp: timeInMicroseconds(event.time)
|
|
1165
|
-
};
|
|
1166
|
-
if (state.activeStep) {
|
|
1167
|
-
log.step_id = state.activeStep;
|
|
1168
|
-
}
|
|
1169
|
-
return sendEvent(context, RUN_LOG, log);
|
|
1170
|
-
}
|
|
1171
1287
|
async function loadDataclip(context, stateId) {
|
|
1172
1288
|
const result = await sendEvent(context, GET_DATACLIP, {
|
|
1173
1289
|
id: stateId
|
|
@@ -1185,7 +1301,7 @@ var healthcheck_default = (ctx) => {
|
|
|
1185
1301
|
};
|
|
1186
1302
|
|
|
1187
1303
|
// src/channels/run.ts
|
|
1188
|
-
import * as
|
|
1304
|
+
import * as Sentry5 from "@sentry/node";
|
|
1189
1305
|
var joinRunChannel = (socket, token, runId, logger2, timeout = 30) => {
|
|
1190
1306
|
return new Promise((resolve5, reject) => {
|
|
1191
1307
|
let didReceiveOk = false;
|
|
@@ -1204,12 +1320,12 @@ var joinRunChannel = (socket, token, runId, logger2, timeout = 30) => {
|
|
|
1204
1320
|
resolve5({ channel, run: run2 });
|
|
1205
1321
|
}
|
|
1206
1322
|
}).receive("error", (err) => {
|
|
1207
|
-
|
|
1323
|
+
Sentry5.captureException(err);
|
|
1208
1324
|
logger2.error(`error connecting to ${channelName}`, err);
|
|
1209
1325
|
channel?.leave();
|
|
1210
1326
|
reject(err);
|
|
1211
1327
|
}).receive("timeout", (err) => {
|
|
1212
|
-
|
|
1328
|
+
Sentry5.captureException(err);
|
|
1213
1329
|
logger2.error(`Timeout for ${channelName}`, err);
|
|
1214
1330
|
channel?.leave();
|
|
1215
1331
|
reject(err);
|
|
@@ -1226,7 +1342,7 @@ var run_default = joinRunChannel;
|
|
|
1226
1342
|
|
|
1227
1343
|
// src/channels/worker-queue.ts
|
|
1228
1344
|
import EventEmitter2 from "node:events";
|
|
1229
|
-
import * as
|
|
1345
|
+
import * as Sentry6 from "@sentry/node";
|
|
1230
1346
|
import { Socket as PhxSocket } from "phoenix";
|
|
1231
1347
|
import { WebSocket } from "ws";
|
|
1232
1348
|
import { API_VERSION } from "@openfn/lexicon/lightning";
|
|
@@ -1265,13 +1381,13 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger2, options) => {
|
|
|
1265
1381
|
SocketConstructor = PhxSocket
|
|
1266
1382
|
} = options;
|
|
1267
1383
|
const events = new EventEmitter2();
|
|
1268
|
-
|
|
1384
|
+
Sentry6.addBreadcrumb({
|
|
1269
1385
|
category: "lifecycle",
|
|
1270
1386
|
message: "Connecting to worker queue",
|
|
1271
1387
|
level: "info"
|
|
1272
1388
|
});
|
|
1273
1389
|
worker_token_default(secret, serverId, logger2).then(async (token) => {
|
|
1274
|
-
|
|
1390
|
+
Sentry6.addBreadcrumb({
|
|
1275
1391
|
category: "lifecycle",
|
|
1276
1392
|
message: "Worker token generated",
|
|
1277
1393
|
level: "info"
|
|
@@ -1290,7 +1406,7 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger2, options) => {
|
|
|
1290
1406
|
let didOpen = false;
|
|
1291
1407
|
let shouldReportConnectionError = true;
|
|
1292
1408
|
socket.onOpen(() => {
|
|
1293
|
-
|
|
1409
|
+
Sentry6.addBreadcrumb({
|
|
1294
1410
|
category: "lifecycle",
|
|
1295
1411
|
message: "Web socket connected",
|
|
1296
1412
|
level: "info"
|
|
@@ -1317,7 +1433,7 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger2, options) => {
|
|
|
1317
1433
|
events.emit("disconnect");
|
|
1318
1434
|
});
|
|
1319
1435
|
socket.onError((e) => {
|
|
1320
|
-
|
|
1436
|
+
Sentry6.addBreadcrumb({
|
|
1321
1437
|
category: "lifecycle",
|
|
1322
1438
|
message: "Error in web socket connection",
|
|
1323
1439
|
level: "info"
|
|
@@ -1325,7 +1441,7 @@ var connectToWorkerQueue = (endpoint, serverId, secret, logger2, options) => {
|
|
|
1325
1441
|
if (shouldReportConnectionError) {
|
|
1326
1442
|
logger2.debug("Reporting connection error to sentry");
|
|
1327
1443
|
shouldReportConnectionError = false;
|
|
1328
|
-
|
|
1444
|
+
Sentry6.captureException(e);
|
|
1329
1445
|
}
|
|
1330
1446
|
if (!didOpen) {
|
|
1331
1447
|
events.emit("error", e.message);
|
|
@@ -1435,11 +1551,11 @@ function createServer(engine, options = {}) {
|
|
|
1435
1551
|
app.events = new EventEmitter3();
|
|
1436
1552
|
app.engine = engine;
|
|
1437
1553
|
if (options.sentryDsn) {
|
|
1438
|
-
|
|
1554
|
+
Sentry7.init({
|
|
1439
1555
|
environment: options.sentryEnv,
|
|
1440
1556
|
dsn: options.sentryDsn
|
|
1441
1557
|
});
|
|
1442
|
-
|
|
1558
|
+
Sentry7.setupKoaErrorHandler(app);
|
|
1443
1559
|
}
|
|
1444
1560
|
app.use(bodyParser());
|
|
1445
1561
|
app.use(
|
|
@@ -1496,8 +1612,14 @@ function createServer(engine, options = {}) {
|
|
|
1496
1612
|
if (!("payloadLimitMb" in options2)) {
|
|
1497
1613
|
options2.payloadLimitMb = app.options.payloadLimitMb;
|
|
1498
1614
|
}
|
|
1615
|
+
if (!("logPayloadLimitMb" in options2)) {
|
|
1616
|
+
options2.logPayloadLimitMb = app.options.logPayloadLimitMb;
|
|
1617
|
+
}
|
|
1499
1618
|
options2.timeoutRetryCount = app.options.timeoutRetryCount;
|
|
1500
1619
|
options2.timeoutRetryDelay = app.options.timeoutRetryDelayMs ?? app.options.socketTimeoutSeconds;
|
|
1620
|
+
options2.batchLogs = app.options.batchLogs;
|
|
1621
|
+
options2.batchInterval = app.options.batchInterval;
|
|
1622
|
+
options2.batchLimit = app.options.batchLimit;
|
|
1501
1623
|
const onFinish = () => {
|
|
1502
1624
|
const duration = (Date.now() - start) / 1e3;
|
|
1503
1625
|
logger2.debug(
|
|
@@ -6450,6 +6572,9 @@ function parseArgs(argv) {
|
|
|
6450
6572
|
const {
|
|
6451
6573
|
OPENFN_ADAPTORS_REPO,
|
|
6452
6574
|
WORKER_BACKOFF,
|
|
6575
|
+
WORKER_BATCH_INTERVAL,
|
|
6576
|
+
WORKER_BATCH_LIMIT,
|
|
6577
|
+
WORKER_BATCH_LOGS,
|
|
6453
6578
|
WORKER_CAPACITY,
|
|
6454
6579
|
WORKER_CLAIM_TIMEOUT_SECONDS,
|
|
6455
6580
|
WORKER_COLLECTIONS_URL,
|
|
@@ -6458,6 +6583,7 @@ function parseArgs(argv) {
|
|
|
6458
6583
|
WORKER_LIGHTNING_SERVICE_URL,
|
|
6459
6584
|
WORKER_LOG_LEVEL,
|
|
6460
6585
|
WORKER_MAX_PAYLOAD_MB,
|
|
6586
|
+
WORKER_MAX_LOG_PAYLOAD_MB,
|
|
6461
6587
|
WORKER_MAX_RUN_DURATION_SECONDS,
|
|
6462
6588
|
WORKER_MAX_RUN_MEMORY_MB,
|
|
6463
6589
|
WORKER_MESSAGE_TIMEOUT_SECONDS,
|
|
@@ -6510,6 +6636,9 @@ function parseArgs(argv) {
|
|
|
6510
6636
|
description: "Base64-encoded public key. Used to verify run tokens. Env: WORKER_LIGHTNING_PUBLIC_KEY"
|
|
6511
6637
|
}).option("log", {
|
|
6512
6638
|
description: "Set the log level for stdout (default to info, set to debug for verbose output). Env: WORKER_LOG_LEVEL"
|
|
6639
|
+
}).option("log-payload-memory", {
|
|
6640
|
+
description: "Maximum memory allocated to log payloads, in mb. Env: WORKER_MAX_LOG_PAYLOAD_MB",
|
|
6641
|
+
type: "number"
|
|
6513
6642
|
}).option("loop", {
|
|
6514
6643
|
description: "Disable the claims loop",
|
|
6515
6644
|
type: "boolean",
|
|
@@ -6518,6 +6647,15 @@ function parseArgs(argv) {
|
|
|
6518
6647
|
description: "Use a mock runtime engine",
|
|
6519
6648
|
type: "boolean",
|
|
6520
6649
|
default: false
|
|
6650
|
+
}).option("batch-logs", {
|
|
6651
|
+
description: "Allow logs emitted from the server to be batched up. Env: WORKER_BATCH_LOGS",
|
|
6652
|
+
type: "boolean"
|
|
6653
|
+
}).option("batch-interval", {
|
|
6654
|
+
description: "Interval for batching logs, in milliseconds. Env: WORKER_BATCH_INTERVAL",
|
|
6655
|
+
type: "number"
|
|
6656
|
+
}).option("batch-limit", {
|
|
6657
|
+
description: "Maximum number of logs to batch before sending. Env: WORKER_BATCH_LIMIT",
|
|
6658
|
+
type: "number"
|
|
6521
6659
|
}).option("backoff", {
|
|
6522
6660
|
description: "Claim backoff rules: min/max (in seconds). Env: WORKER_BACKOFF"
|
|
6523
6661
|
}).option("capacity", {
|
|
@@ -6571,6 +6709,9 @@ function parseArgs(argv) {
|
|
|
6571
6709
|
"ws://localhost:4000/worker"
|
|
6572
6710
|
),
|
|
6573
6711
|
repoDir: setArg(args2.repoDir, WORKER_REPO_DIR),
|
|
6712
|
+
batchLogs: setArg(args2.batchLogs, WORKER_BATCH_LOGS, false),
|
|
6713
|
+
batchInterval: setArg(args2.batchInterval, WORKER_BATCH_INTERVAL, 10),
|
|
6714
|
+
batchLimit: setArg(args2.batchLimit, WORKER_BATCH_LIMIT, 50),
|
|
6574
6715
|
monorepoDir: setArg(args2.monorepoDir, OPENFN_ADAPTORS_REPO),
|
|
6575
6716
|
secret: setArg(args2.secret, WORKER_SECRET),
|
|
6576
6717
|
sentryDsn: setArg(args2.sentryDsn, WORKER_SENTRY_DSN),
|
|
@@ -6589,6 +6730,11 @@ function parseArgs(argv) {
|
|
|
6589
6730
|
),
|
|
6590
6731
|
runMemory: setArg(args2.runMemory, WORKER_MAX_RUN_MEMORY_MB, 500),
|
|
6591
6732
|
payloadMemory: setArg(args2.payloadMemory, WORKER_MAX_PAYLOAD_MB, 10),
|
|
6733
|
+
logPayloadMemory: setArg(
|
|
6734
|
+
args2.logPayloadMemory,
|
|
6735
|
+
WORKER_MAX_LOG_PAYLOAD_MB,
|
|
6736
|
+
1
|
|
6737
|
+
),
|
|
6592
6738
|
maxRunDurationSeconds: setArg(
|
|
6593
6739
|
args2.maxRunDurationSeconds,
|
|
6594
6740
|
WORKER_MAX_RUN_DURATION_SECONDS,
|
|
@@ -6672,6 +6818,8 @@ function engineReady(engine) {
|
|
|
6672
6818
|
},
|
|
6673
6819
|
maxWorkflows: args.capacity,
|
|
6674
6820
|
payloadLimitMb: args.payloadMemory,
|
|
6821
|
+
logPayloadLimitMb: args.logPayloadMemory ?? 1,
|
|
6822
|
+
// Default to 1MB
|
|
6675
6823
|
collectionsVersion: args.collectionsVersion,
|
|
6676
6824
|
collectionsUrl: args.collectionsUrl,
|
|
6677
6825
|
monorepoDir: args.monorepoDir,
|
|
@@ -6680,7 +6828,10 @@ function engineReady(engine) {
|
|
|
6680
6828
|
// deprecated!
|
|
6681
6829
|
socketTimeoutSeconds: args.socketTimeoutSeconds,
|
|
6682
6830
|
timeoutRetryCount: args.timeoutRetryCount,
|
|
6683
|
-
timeoutRetryDelayMs: args.timeoutRetryDelayMs
|
|
6831
|
+
timeoutRetryDelayMs: args.timeoutRetryDelayMs,
|
|
6832
|
+
batchLogs: args.batchLogs,
|
|
6833
|
+
batchInterval: args.batchInterval,
|
|
6834
|
+
batchLimit: args.batchLimit
|
|
6684
6835
|
};
|
|
6685
6836
|
if (args.socketTimeoutSeconds) {
|
|
6686
6837
|
logger.warn(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/ws-worker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.1",
|
|
4
4
|
"description": "A Websocket Worker to connect Lightning to a Runtime Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"koa-logger": "^3.2.1",
|
|
24
24
|
"phoenix": "1.7.10",
|
|
25
25
|
"ws": "^8.18.3",
|
|
26
|
-
"@openfn/engine-multi": "1.
|
|
27
|
-
"@openfn/
|
|
28
|
-
"@openfn/
|
|
29
|
-
"@openfn/
|
|
26
|
+
"@openfn/engine-multi": "1.9.1",
|
|
27
|
+
"@openfn/lexicon": "^1.3.0",
|
|
28
|
+
"@openfn/logger": "1.1.1",
|
|
29
|
+
"@openfn/runtime": "1.7.7"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/koa": "^2.15.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"tsup": "^6.7.0",
|
|
44
44
|
"typescript": "^4.9.5",
|
|
45
45
|
"yargs": "^17.7.2",
|
|
46
|
-
"@openfn/lightning-mock": "2.3.
|
|
46
|
+
"@openfn/lightning-mock": "2.3.10"
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
49
|
"dist",
|