@openfn/ws-worker 0.2.11 → 0.3.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,28 @@
1
1
  # ws-worker
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 419d310: Throttle attempt events to better preserve their sequencing
8
+
9
+ ### Patch Changes
10
+
11
+ - 598c669: Make edge conditions more stable if state is not passed
12
+ - 6e906a7: Better handling of job-error
13
+ - Updated dependencies [02ab459]
14
+ - @openfn/runtime@0.2.2
15
+ - @openfn/engine-multi@0.2.4
16
+
17
+ ## 0.2.12
18
+
19
+ ### Patch Changes
20
+
21
+ - 6c3e9e42: Ensure capacity is also set on the engine
22
+ - Updated dependencies [05ccc10b]
23
+ - Updated dependencies [7235bf5e]
24
+ - @openfn/engine-multi@0.2.3
25
+
3
26
  ## 0.2.11
4
27
 
5
28
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -2,8 +2,9 @@ import { EventEmitter } from 'node:events';
2
2
  import Koa from 'koa';
3
3
  import { SanitizePolicies, Logger } from '@openfn/logger';
4
4
  import { Channel as Channel$1 } from 'phoenix';
5
- import { Server } from 'http';
5
+ import { ExecutionPlan } from '@openfn/runtime';
6
6
  import { RuntimeEngine } from '@openfn/engine-multi';
7
+ import { Server } from 'http';
7
8
 
8
9
  type ExitReasonStrings =
9
10
  | 'success'
@@ -23,7 +24,8 @@ type Node = {
23
24
  id: string;
24
25
  body?: string;
25
26
  adaptor?: string;
26
- credential_id?: any; // TODO tighten this up, string or object
27
+ credential?: object;
28
+ credential_id?: string;
27
29
  type?: 'webhook' | 'cron'; // trigger only
28
30
  state?: any; // Initial state / defaults
29
31
  };
@@ -138,6 +140,7 @@ declare type RunStartPayload = {
138
140
  run_id: string;
139
141
  attempt_id?: string;
140
142
  input_dataclip_id?: string;
143
+ versions: Record<string, string>;
141
144
  };
142
145
  declare type RunStartReply = void;
143
146
  declare const RUN_COMPLETE = "run:complete";
@@ -155,6 +158,7 @@ declare type Context = {
155
158
  channel: Channel;
156
159
  state: AttemptState;
157
160
  logger: Logger;
161
+ engine: RuntimeEngine;
158
162
  onFinish: (result: any) => void;
159
163
  };
160
164
 
package/dist/index.js CHANGED
@@ -165,14 +165,11 @@ var startWorkloop = (app, logger, minBackoff, maxBackoff, maxWorkers) => {
165
165
  };
166
166
  var workloop_default = startWorkloop;
167
167
 
168
- // src/api/execute.ts
169
- import crypto2 from "node:crypto";
170
-
171
168
  // src/util/convert-attempt.ts
172
169
  import crypto from "node:crypto";
173
170
  var conditions = {
174
- on_job_success: (upstreamId) => `Boolean(!state.errors?.["${upstreamId}"] ?? true)`,
175
- on_job_failure: (upstreamId) => `Boolean(state.errors && state.errors["${upstreamId}"])`,
171
+ on_job_success: (upstreamId) => `Boolean(!state?.errors?.["${upstreamId}"] ?? true)`,
172
+ on_job_failure: (upstreamId) => `Boolean(state?.errors && state.errors["${upstreamId}"])`,
176
173
  always: (_upstreamId) => null
177
174
  };
178
175
  var mapEdgeCondition = (edge) => {
@@ -219,7 +216,7 @@ var convert_attempt_default = (attempt) => {
219
216
  const id = job.id || crypto.randomUUID();
220
217
  nodes[id] = {
221
218
  id,
222
- configuration: job.credential_id,
219
+ configuration: job.credential || job.credential_id,
223
220
  expression: job.body,
224
221
  adaptor: job.adaptor
225
222
  };
@@ -328,6 +325,203 @@ var calculateAttemptExitReason = (state) => {
328
325
  return { reason: "success", error_type: null, error_message: null };
329
326
  };
330
327
 
328
+ // src/events/run-complete.ts
329
+ import crypto2 from "node:crypto";
330
+ function onRunComplete({ channel, state }, event, error) {
331
+ const dataclipId = crypto2.randomUUID();
332
+ const run_id = state.activeRun;
333
+ const job_id = state.activeJob;
334
+ if (!state.dataclips) {
335
+ state.dataclips = {};
336
+ }
337
+ const outputState = event.state || {};
338
+ state.dataclips[dataclipId] = event.state;
339
+ delete state.activeRun;
340
+ delete state.activeJob;
341
+ state.lastDataclipId = dataclipId;
342
+ event.next?.forEach((nextJobId) => {
343
+ state.inputDataclips[nextJobId] = dataclipId;
344
+ });
345
+ const { reason, error_message, error_type } = calculateJobExitReason(
346
+ job_id,
347
+ event.state,
348
+ error
349
+ );
350
+ state.reasons[job_id] = { reason, error_message, error_type };
351
+ const evt = {
352
+ run_id,
353
+ job_id,
354
+ output_dataclip_id: dataclipId,
355
+ output_dataclip: stringify_default(outputState),
356
+ reason,
357
+ error_message,
358
+ error_type,
359
+ mem: event.mem,
360
+ duration: event.duration,
361
+ thread_id: event.threadId
362
+ };
363
+ return sendEvent(channel, RUN_COMPLETE, evt);
364
+ }
365
+
366
+ // src/events/run-start.ts
367
+ import crypto3 from "node:crypto";
368
+ import { timestamp } from "@openfn/logger";
369
+
370
+ // package.json
371
+ var package_default = {
372
+ name: "@openfn/ws-worker",
373
+ version: "0.3.0",
374
+ description: "A Websocket Worker to connect Lightning to a Runtime Engine",
375
+ main: "dist/index.js",
376
+ type: "module",
377
+ scripts: {
378
+ test: "pnpm ava --serial",
379
+ "test:types": "pnpm tsc --noEmit --project tsconfig.json",
380
+ build: "tsup --config tsup.config.js",
381
+ "build:watch": "pnpm build --watch",
382
+ start: "ts-node-esm --transpile-only src/start.ts",
383
+ "start:prod": "node dist/start.js",
384
+ "start:watch": "nodemon -e ts,js --watch ../runtime-manager/dist --watch ./src --exec 'pnpm start'",
385
+ pack: "pnpm pack --pack-destination ../../dist"
386
+ },
387
+ bin: {
388
+ worker: "dist/start.js"
389
+ },
390
+ author: "Open Function Group <admin@openfn.org>",
391
+ license: "ISC",
392
+ dependencies: {
393
+ "@koa/router": "^12.0.0",
394
+ "@openfn/engine-multi": "workspace:*",
395
+ "@openfn/logger": "workspace:*",
396
+ "@openfn/runtime": "workspace:*",
397
+ "@types/koa-logger": "^3.1.2",
398
+ "@types/ws": "^8.5.6",
399
+ "fast-safe-stringify": "^2.1.1",
400
+ figures: "^5.0.0",
401
+ "human-id": "^4.1.0",
402
+ jose: "^4.14.6",
403
+ koa: "^2.13.4",
404
+ "koa-bodyparser": "^4.4.0",
405
+ "koa-logger": "^3.2.1",
406
+ phoenix: "^1.7.7",
407
+ ws: "^8.14.1"
408
+ },
409
+ devDependencies: {
410
+ "@openfn/lightning-mock": "workspace:*",
411
+ "@types/koa": "^2.13.5",
412
+ "@types/koa-bodyparser": "^4.3.10",
413
+ "@types/koa__router": "^12.0.1",
414
+ "@types/node": "^18.15.3",
415
+ "@types/nodemon": "1.19.3",
416
+ "@types/phoenix": "^1.6.2",
417
+ "@types/yargs": "^17.0.12",
418
+ ava: "5.1.0",
419
+ nodemon: "3.0.1",
420
+ "ts-node": "^10.9.1",
421
+ tslib: "^2.4.0",
422
+ tsup: "^6.2.3",
423
+ typescript: "^4.6.4",
424
+ yargs: "^17.6.2"
425
+ },
426
+ files: [
427
+ "dist",
428
+ "README.md",
429
+ "CHANGELOG.md"
430
+ ]
431
+ };
432
+
433
+ // src/util/versions.ts
434
+ import { mainSymbols } from "figures";
435
+ var { triangleRightSmall: t } = mainSymbols;
436
+ var versions_default = (runId, versions, adaptor) => {
437
+ let longest = "compiler".length;
438
+ for (const v in versions) {
439
+ longest = Math.max(v.length, longest);
440
+ }
441
+ const { node, compiler, engine, worker, runtime, ...adaptors } = versions;
442
+ const prefix = (str2) => ` ${t} ${str2.padEnd(longest + 4, " ")}`;
443
+ let str = `Versions for run ${runId}:
444
+ ${prefix("node.js")}${versions.node || "unknown"}
445
+ ${prefix("worker")}${versions.worker || "unknown"}
446
+ ${prefix("engine")}${versions.engine || "unknown"}
447
+ ${prefix("runtime")}${versions.runtime || "unknown"}
448
+ ${prefix("compiler")}${versions.compiler || "unknown"}`;
449
+ if (Object.keys(adaptors).length) {
450
+ let allAdaptors = Object.keys(adaptors);
451
+ if (adaptor) {
452
+ allAdaptors = allAdaptors.filter((name) => adaptor.startsWith(name));
453
+ }
454
+ str += "\n" + allAdaptors.sort().map((adaptorName) => `${prefix(adaptorName)}${adaptors[adaptorName]}`).join("\n");
455
+ }
456
+ return str;
457
+ };
458
+
459
+ // src/events/run-start.ts
460
+ async function onRunStart(context, event) {
461
+ const time = (timestamp() - BigInt(1e7)).toString();
462
+ const { channel, state } = context;
463
+ state.activeRun = crypto3.randomUUID();
464
+ state.activeJob = event.jobId;
465
+ const job = state.plan.jobs.find(({ id }) => id === event.jobId);
466
+ const input_dataclip_id = state.inputDataclips[event.jobId];
467
+ const versions = {
468
+ worker: package_default.version,
469
+ ...event.versions
470
+ };
471
+ const versionLogContext = {
472
+ ...context,
473
+ state: {
474
+ ...state,
475
+ activeRun: state.activeRun
476
+ }
477
+ };
478
+ await sendEvent(channel, RUN_START, {
479
+ run_id: state.activeRun,
480
+ job_id: state.activeJob,
481
+ input_dataclip_id,
482
+ versions
483
+ });
484
+ const versionMessage = versions_default(
485
+ versionLogContext.state.activeRun,
486
+ versions,
487
+ job?.adaptor
488
+ );
489
+ await onJobLog(versionLogContext, {
490
+ time,
491
+ message: [versionMessage],
492
+ level: "info",
493
+ name: "VER"
494
+ });
495
+ return;
496
+ }
497
+
498
+ // src/util/throttle.ts
499
+ var createThrottler = () => {
500
+ const q = [];
501
+ let activePromise;
502
+ const add = (fn) => {
503
+ return (...args) => new Promise((resolve, reject) => {
504
+ q.push({ fn, args, resolve, reject });
505
+ shift();
506
+ });
507
+ };
508
+ const shift = () => {
509
+ if (activePromise) {
510
+ return;
511
+ }
512
+ const next = q.shift();
513
+ if (next) {
514
+ const { fn, args, resolve, reject } = next;
515
+ activePromise = fn(...args).then(resolve).catch(reject).finally(() => {
516
+ activePromise = void 0;
517
+ shift();
518
+ });
519
+ }
520
+ };
521
+ return add;
522
+ };
523
+ var throttle_default = createThrottler;
524
+
331
525
  // src/api/execute.ts
332
526
  var enc = new TextDecoder("utf-8");
333
527
  var eventMap = {
@@ -341,7 +535,8 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
341
535
  }) {
342
536
  logger.info("executing ", plan.id);
343
537
  const state = create_attempt_state_default(plan, options);
344
- const context = { channel, state, logger, onFinish };
538
+ const context = { channel, state, logger, engine, onFinish };
539
+ const throttle = throttle_default();
345
540
  const addEvent = (eventName, handler) => {
346
541
  const wrappedFn = async (event) => {
347
542
  const lightningEvent = eventMap[eventName] ?? eventName;
@@ -361,13 +556,13 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
361
556
  };
362
557
  const listeners = Object.assign(
363
558
  {},
364
- addEvent("workflow-start", onWorkflowStart),
365
- addEvent("job-start", onJobStart),
366
- addEvent("job-complete", onJobComplete),
367
- addEvent("job-error", onJobError),
368
- addEvent("workflow-log", onJobLog),
369
- addEvent("workflow-complete", onWorkflowComplete),
370
- addEvent("workflow-error", onWorkflowError)
559
+ addEvent("workflow-start", throttle(onWorkflowStart)),
560
+ addEvent("job-start", throttle(onRunStart)),
561
+ addEvent("job-complete", throttle(onRunComplete)),
562
+ addEvent("job-error", throttle(onJobError)),
563
+ addEvent("workflow-log", throttle(onJobLog)),
564
+ addEvent("workflow-complete", throttle(onWorkflowComplete)),
565
+ addEvent("workflow-error", throttle(onWorkflowError))
371
566
  );
372
567
  engine.listen(plan.id, listeners);
373
568
  const resolvers = {
@@ -396,59 +591,17 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
396
591
  return context;
397
592
  }
398
593
  var sendEvent = (channel, event, payload) => new Promise((resolve, reject) => {
399
- channel.push(event, payload).receive("error", reject).receive("timeout", () => reject(new Error("timeout"))).receive("ok", resolve);
594
+ channel.push(event, payload).receive("error", reject).receive("timeout", () => {
595
+ reject(new Error("timeout"));
596
+ }).receive("ok", resolve);
400
597
  });
401
- function onJobStart({ channel, state }, event) {
402
- state.activeRun = crypto2.randomUUID();
403
- state.activeJob = event.jobId;
404
- const input_dataclip_id = state.inputDataclips[event.jobId];
405
- return sendEvent(channel, RUN_START, {
406
- run_id: state.activeRun,
407
- job_id: state.activeJob,
408
- input_dataclip_id
409
- });
410
- }
411
598
  function onJobError(context, event) {
412
599
  const { state, error, jobId } = event;
413
- if (state.errors?.[jobId]?.message === error.message) {
414
- onJobComplete(context, event);
600
+ if (state?.errors?.[jobId]?.message === error.message) {
601
+ return onRunComplete(context, event);
415
602
  } else {
416
- onJobComplete(context, event, event.error);
417
- }
418
- }
419
- function onJobComplete({ channel, state }, event, error) {
420
- const dataclipId = crypto2.randomUUID();
421
- const run_id = state.activeRun;
422
- const job_id = state.activeJob;
423
- if (!state.dataclips) {
424
- state.dataclips = {};
603
+ return onRunComplete(context, event, event.error);
425
604
  }
426
- state.dataclips[dataclipId] = event.state;
427
- delete state.activeRun;
428
- delete state.activeJob;
429
- state.lastDataclipId = dataclipId;
430
- event.next?.forEach((nextJobId) => {
431
- state.inputDataclips[nextJobId] = dataclipId;
432
- });
433
- const { reason, error_message, error_type } = calculateJobExitReason(
434
- job_id,
435
- event.state,
436
- error
437
- );
438
- state.reasons[job_id] = { reason, error_message, error_type };
439
- const evt = {
440
- run_id,
441
- job_id,
442
- output_dataclip_id: dataclipId,
443
- output_dataclip: stringify_default(event.state),
444
- reason,
445
- error_message,
446
- error_type,
447
- mem: event.mem,
448
- duration: event.duration,
449
- thread_id: event.threadId
450
- };
451
- return sendEvent(channel, RUN_COMPLETE, evt);
452
605
  }
453
606
  function onWorkflowStart({ channel }, _event) {
454
607
  return sendEvent(channel, ATTEMPT_START);
@@ -462,9 +615,13 @@ async function onWorkflowComplete({ state, channel, onFinish }, _event) {
462
615
  });
463
616
  onFinish({ reason, state: result });
464
617
  }
465
- async function onWorkflowError({ state, channel, logger, onFinish }, event) {
618
+ async function onWorkflowError(context, event) {
619
+ const { state, channel, logger, onFinish } = context;
466
620
  try {
467
621
  const reason = calculateJobExitReason("", { data: {} }, event);
622
+ if (state.activeJob) {
623
+ await onJobError(context, { error: event });
624
+ }
468
625
  await sendEvent(channel, ATTEMPT_COMPLETE, {
469
626
  final_dataclip_id: state.lastDataclipId,
470
627
  ...reason
@@ -473,6 +630,7 @@ async function onWorkflowError({ state, channel, logger, onFinish }, event) {
473
630
  } catch (e) {
474
631
  logger.error("ERROR in workflow-error handler:", e.message);
475
632
  logger.error(e);
633
+ onFinish({});
476
634
  }
477
635
  }
478
636
  function onJobLog({ channel, state }, event) {
package/dist/start.js CHANGED
@@ -4954,6 +4954,7 @@ async function createMock() {
4954
4954
  dispatch("workflow-start", { workflowId: id, threadId });
4955
4955
  try {
4956
4956
  await run(xplan, void 0, opts);
4957
+ dispatch("workflow-complete", { workflowId: id, threadId });
4957
4958
  } catch (e) {
4958
4959
  dispatch("workflow-error", {
4959
4960
  threadId,
@@ -4961,9 +4962,9 @@ async function createMock() {
4961
4962
  type: e.name,
4962
4963
  message: e.message
4963
4964
  });
4965
+ } finally {
4966
+ delete activeWorkflows[id];
4964
4967
  }
4965
- delete activeWorkflows[id];
4966
- dispatch("workflow-complete", { workflowId: id, threadId });
4967
4968
  }, 1);
4968
4969
  };
4969
4970
  const getStatus = () => {
@@ -5150,14 +5151,11 @@ var startWorkloop = (app, logger2, minBackoff2, maxBackoff2, maxWorkers) => {
5150
5151
  };
5151
5152
  var workloop_default = startWorkloop;
5152
5153
 
5153
- // src/api/execute.ts
5154
- import crypto3 from "node:crypto";
5155
-
5156
5154
  // src/util/convert-attempt.ts
5157
5155
  import crypto2 from "node:crypto";
5158
5156
  var conditions = {
5159
- on_job_success: (upstreamId) => `Boolean(!state.errors?.["${upstreamId}"] ?? true)`,
5160
- on_job_failure: (upstreamId) => `Boolean(state.errors && state.errors["${upstreamId}"])`,
5157
+ on_job_success: (upstreamId) => `Boolean(!state?.errors?.["${upstreamId}"] ?? true)`,
5158
+ on_job_failure: (upstreamId) => `Boolean(state?.errors && state.errors["${upstreamId}"])`,
5161
5159
  always: (_upstreamId) => null
5162
5160
  };
5163
5161
  var mapEdgeCondition = (edge) => {
@@ -5204,7 +5202,7 @@ var convert_attempt_default = (attempt) => {
5204
5202
  const id = job.id || crypto2.randomUUID();
5205
5203
  nodes[id] = {
5206
5204
  id,
5207
- configuration: job.credential_id,
5205
+ configuration: job.credential || job.credential_id,
5208
5206
  expression: job.body,
5209
5207
  adaptor: job.adaptor
5210
5208
  };
@@ -5313,6 +5311,203 @@ var calculateAttemptExitReason = (state) => {
5313
5311
  return { reason: "success", error_type: null, error_message: null };
5314
5312
  };
5315
5313
 
5314
+ // src/events/run-complete.ts
5315
+ import crypto3 from "node:crypto";
5316
+ function onRunComplete({ channel, state }, event, error) {
5317
+ const dataclipId = crypto3.randomUUID();
5318
+ const run_id = state.activeRun;
5319
+ const job_id = state.activeJob;
5320
+ if (!state.dataclips) {
5321
+ state.dataclips = {};
5322
+ }
5323
+ const outputState = event.state || {};
5324
+ state.dataclips[dataclipId] = event.state;
5325
+ delete state.activeRun;
5326
+ delete state.activeJob;
5327
+ state.lastDataclipId = dataclipId;
5328
+ event.next?.forEach((nextJobId) => {
5329
+ state.inputDataclips[nextJobId] = dataclipId;
5330
+ });
5331
+ const { reason, error_message, error_type } = calculateJobExitReason(
5332
+ job_id,
5333
+ event.state,
5334
+ error
5335
+ );
5336
+ state.reasons[job_id] = { reason, error_message, error_type };
5337
+ const evt = {
5338
+ run_id,
5339
+ job_id,
5340
+ output_dataclip_id: dataclipId,
5341
+ output_dataclip: stringify_default(outputState),
5342
+ reason,
5343
+ error_message,
5344
+ error_type,
5345
+ mem: event.mem,
5346
+ duration: event.duration,
5347
+ thread_id: event.threadId
5348
+ };
5349
+ return sendEvent(channel, RUN_COMPLETE, evt);
5350
+ }
5351
+
5352
+ // src/events/run-start.ts
5353
+ import crypto4 from "node:crypto";
5354
+ import { timestamp } from "@openfn/logger";
5355
+
5356
+ // package.json
5357
+ var package_default = {
5358
+ name: "@openfn/ws-worker",
5359
+ version: "0.3.0",
5360
+ description: "A Websocket Worker to connect Lightning to a Runtime Engine",
5361
+ main: "dist/index.js",
5362
+ type: "module",
5363
+ scripts: {
5364
+ test: "pnpm ava --serial",
5365
+ "test:types": "pnpm tsc --noEmit --project tsconfig.json",
5366
+ build: "tsup --config tsup.config.js",
5367
+ "build:watch": "pnpm build --watch",
5368
+ start: "ts-node-esm --transpile-only src/start.ts",
5369
+ "start:prod": "node dist/start.js",
5370
+ "start:watch": "nodemon -e ts,js --watch ../runtime-manager/dist --watch ./src --exec 'pnpm start'",
5371
+ pack: "pnpm pack --pack-destination ../../dist"
5372
+ },
5373
+ bin: {
5374
+ worker: "dist/start.js"
5375
+ },
5376
+ author: "Open Function Group <admin@openfn.org>",
5377
+ license: "ISC",
5378
+ dependencies: {
5379
+ "@koa/router": "^12.0.0",
5380
+ "@openfn/engine-multi": "workspace:*",
5381
+ "@openfn/logger": "workspace:*",
5382
+ "@openfn/runtime": "workspace:*",
5383
+ "@types/koa-logger": "^3.1.2",
5384
+ "@types/ws": "^8.5.6",
5385
+ "fast-safe-stringify": "^2.1.1",
5386
+ figures: "^5.0.0",
5387
+ "human-id": "^4.1.0",
5388
+ jose: "^4.14.6",
5389
+ koa: "^2.13.4",
5390
+ "koa-bodyparser": "^4.4.0",
5391
+ "koa-logger": "^3.2.1",
5392
+ phoenix: "^1.7.7",
5393
+ ws: "^8.14.1"
5394
+ },
5395
+ devDependencies: {
5396
+ "@openfn/lightning-mock": "workspace:*",
5397
+ "@types/koa": "^2.13.5",
5398
+ "@types/koa-bodyparser": "^4.3.10",
5399
+ "@types/koa__router": "^12.0.1",
5400
+ "@types/node": "^18.15.3",
5401
+ "@types/nodemon": "1.19.3",
5402
+ "@types/phoenix": "^1.6.2",
5403
+ "@types/yargs": "^17.0.12",
5404
+ ava: "5.1.0",
5405
+ nodemon: "3.0.1",
5406
+ "ts-node": "^10.9.1",
5407
+ tslib: "^2.4.0",
5408
+ tsup: "^6.2.3",
5409
+ typescript: "^4.6.4",
5410
+ yargs: "^17.6.2"
5411
+ },
5412
+ files: [
5413
+ "dist",
5414
+ "README.md",
5415
+ "CHANGELOG.md"
5416
+ ]
5417
+ };
5418
+
5419
+ // src/util/versions.ts
5420
+ import { mainSymbols } from "figures";
5421
+ var { triangleRightSmall: t } = mainSymbols;
5422
+ var versions_default = (runId, versions, adaptor) => {
5423
+ let longest = "compiler".length;
5424
+ for (const v in versions) {
5425
+ longest = Math.max(v.length, longest);
5426
+ }
5427
+ const { node, compiler, engine, worker, runtime, ...adaptors } = versions;
5428
+ const prefix = (str2) => ` ${t} ${str2.padEnd(longest + 4, " ")}`;
5429
+ let str = `Versions for run ${runId}:
5430
+ ${prefix("node.js")}${versions.node || "unknown"}
5431
+ ${prefix("worker")}${versions.worker || "unknown"}
5432
+ ${prefix("engine")}${versions.engine || "unknown"}
5433
+ ${prefix("runtime")}${versions.runtime || "unknown"}
5434
+ ${prefix("compiler")}${versions.compiler || "unknown"}`;
5435
+ if (Object.keys(adaptors).length) {
5436
+ let allAdaptors = Object.keys(adaptors);
5437
+ if (adaptor) {
5438
+ allAdaptors = allAdaptors.filter((name) => adaptor.startsWith(name));
5439
+ }
5440
+ str += "\n" + allAdaptors.sort().map((adaptorName) => `${prefix(adaptorName)}${adaptors[adaptorName]}`).join("\n");
5441
+ }
5442
+ return str;
5443
+ };
5444
+
5445
+ // src/events/run-start.ts
5446
+ async function onRunStart(context, event) {
5447
+ const time = (timestamp() - BigInt(1e7)).toString();
5448
+ const { channel, state } = context;
5449
+ state.activeRun = crypto4.randomUUID();
5450
+ state.activeJob = event.jobId;
5451
+ const job = state.plan.jobs.find(({ id }) => id === event.jobId);
5452
+ const input_dataclip_id = state.inputDataclips[event.jobId];
5453
+ const versions = {
5454
+ worker: package_default.version,
5455
+ ...event.versions
5456
+ };
5457
+ const versionLogContext = {
5458
+ ...context,
5459
+ state: {
5460
+ ...state,
5461
+ activeRun: state.activeRun
5462
+ }
5463
+ };
5464
+ await sendEvent(channel, RUN_START, {
5465
+ run_id: state.activeRun,
5466
+ job_id: state.activeJob,
5467
+ input_dataclip_id,
5468
+ versions
5469
+ });
5470
+ const versionMessage = versions_default(
5471
+ versionLogContext.state.activeRun,
5472
+ versions,
5473
+ job?.adaptor
5474
+ );
5475
+ await onJobLog(versionLogContext, {
5476
+ time,
5477
+ message: [versionMessage],
5478
+ level: "info",
5479
+ name: "VER"
5480
+ });
5481
+ return;
5482
+ }
5483
+
5484
+ // src/util/throttle.ts
5485
+ var createThrottler = () => {
5486
+ const q = [];
5487
+ let activePromise;
5488
+ const add = (fn) => {
5489
+ return (...args2) => new Promise((resolve5, reject) => {
5490
+ q.push({ fn, args: args2, resolve: resolve5, reject });
5491
+ shift();
5492
+ });
5493
+ };
5494
+ const shift = () => {
5495
+ if (activePromise) {
5496
+ return;
5497
+ }
5498
+ const next = q.shift();
5499
+ if (next) {
5500
+ const { fn, args: args2, resolve: resolve5, reject } = next;
5501
+ activePromise = fn(...args2).then(resolve5).catch(reject).finally(() => {
5502
+ activePromise = void 0;
5503
+ shift();
5504
+ });
5505
+ }
5506
+ };
5507
+ return add;
5508
+ };
5509
+ var throttle_default = createThrottler;
5510
+
5316
5511
  // src/api/execute.ts
5317
5512
  var enc = new TextDecoder("utf-8");
5318
5513
  var eventMap = {
@@ -5326,7 +5521,8 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
5326
5521
  }) {
5327
5522
  logger2.info("executing ", plan.id);
5328
5523
  const state = create_attempt_state_default(plan, options);
5329
- const context = { channel, state, logger: logger2, onFinish };
5524
+ const context = { channel, state, logger: logger2, engine, onFinish };
5525
+ const throttle = throttle_default();
5330
5526
  const addEvent = (eventName, handler) => {
5331
5527
  const wrappedFn = async (event) => {
5332
5528
  const lightningEvent = eventMap[eventName] ?? eventName;
@@ -5346,13 +5542,13 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
5346
5542
  };
5347
5543
  const listeners = Object.assign(
5348
5544
  {},
5349
- addEvent("workflow-start", onWorkflowStart),
5350
- addEvent("job-start", onJobStart),
5351
- addEvent("job-complete", onJobComplete),
5352
- addEvent("job-error", onJobError),
5353
- addEvent("workflow-log", onJobLog),
5354
- addEvent("workflow-complete", onWorkflowComplete),
5355
- addEvent("workflow-error", onWorkflowError)
5545
+ addEvent("workflow-start", throttle(onWorkflowStart)),
5546
+ addEvent("job-start", throttle(onRunStart)),
5547
+ addEvent("job-complete", throttle(onRunComplete)),
5548
+ addEvent("job-error", throttle(onJobError)),
5549
+ addEvent("workflow-log", throttle(onJobLog)),
5550
+ addEvent("workflow-complete", throttle(onWorkflowComplete)),
5551
+ addEvent("workflow-error", throttle(onWorkflowError))
5356
5552
  );
5357
5553
  engine.listen(plan.id, listeners);
5358
5554
  const resolvers = {
@@ -5381,59 +5577,17 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
5381
5577
  return context;
5382
5578
  }
5383
5579
  var sendEvent = (channel, event, payload) => new Promise((resolve5, reject) => {
5384
- channel.push(event, payload).receive("error", reject).receive("timeout", () => reject(new Error("timeout"))).receive("ok", resolve5);
5580
+ channel.push(event, payload).receive("error", reject).receive("timeout", () => {
5581
+ reject(new Error("timeout"));
5582
+ }).receive("ok", resolve5);
5385
5583
  });
5386
- function onJobStart({ channel, state }, event) {
5387
- state.activeRun = crypto3.randomUUID();
5388
- state.activeJob = event.jobId;
5389
- const input_dataclip_id = state.inputDataclips[event.jobId];
5390
- return sendEvent(channel, RUN_START, {
5391
- run_id: state.activeRun,
5392
- job_id: state.activeJob,
5393
- input_dataclip_id
5394
- });
5395
- }
5396
5584
  function onJobError(context, event) {
5397
5585
  const { state, error, jobId } = event;
5398
- if (state.errors?.[jobId]?.message === error.message) {
5399
- onJobComplete(context, event);
5586
+ if (state?.errors?.[jobId]?.message === error.message) {
5587
+ return onRunComplete(context, event);
5400
5588
  } else {
5401
- onJobComplete(context, event, event.error);
5402
- }
5403
- }
5404
- function onJobComplete({ channel, state }, event, error) {
5405
- const dataclipId = crypto3.randomUUID();
5406
- const run_id = state.activeRun;
5407
- const job_id = state.activeJob;
5408
- if (!state.dataclips) {
5409
- state.dataclips = {};
5589
+ return onRunComplete(context, event, event.error);
5410
5590
  }
5411
- state.dataclips[dataclipId] = event.state;
5412
- delete state.activeRun;
5413
- delete state.activeJob;
5414
- state.lastDataclipId = dataclipId;
5415
- event.next?.forEach((nextJobId) => {
5416
- state.inputDataclips[nextJobId] = dataclipId;
5417
- });
5418
- const { reason, error_message, error_type } = calculateJobExitReason(
5419
- job_id,
5420
- event.state,
5421
- error
5422
- );
5423
- state.reasons[job_id] = { reason, error_message, error_type };
5424
- const evt = {
5425
- run_id,
5426
- job_id,
5427
- output_dataclip_id: dataclipId,
5428
- output_dataclip: stringify_default(event.state),
5429
- reason,
5430
- error_message,
5431
- error_type,
5432
- mem: event.mem,
5433
- duration: event.duration,
5434
- thread_id: event.threadId
5435
- };
5436
- return sendEvent(channel, RUN_COMPLETE, evt);
5437
5591
  }
5438
5592
  function onWorkflowStart({ channel }, _event) {
5439
5593
  return sendEvent(channel, ATTEMPT_START);
@@ -5447,9 +5601,13 @@ async function onWorkflowComplete({ state, channel, onFinish }, _event) {
5447
5601
  });
5448
5602
  onFinish({ reason, state: result });
5449
5603
  }
5450
- async function onWorkflowError({ state, channel, logger: logger2, onFinish }, event) {
5604
+ async function onWorkflowError(context, event) {
5605
+ const { state, channel, logger: logger2, onFinish } = context;
5451
5606
  try {
5452
5607
  const reason = calculateJobExitReason("", { data: {} }, event);
5608
+ if (state.activeJob) {
5609
+ await onJobError(context, { error: event });
5610
+ }
5453
5611
  await sendEvent(channel, ATTEMPT_COMPLETE, {
5454
5612
  final_dataclip_id: state.lastDataclipId,
5455
5613
  ...reason
@@ -5458,6 +5616,7 @@ async function onWorkflowError({ state, channel, logger: logger2, onFinish }, ev
5458
5616
  } catch (e) {
5459
5617
  logger2.error("ERROR in workflow-error handler:", e.message);
5460
5618
  logger2.error(e);
5619
+ onFinish({});
5461
5620
  }
5462
5621
  }
5463
5622
  function onJobLog({ channel, state }, event) {
@@ -5790,12 +5949,14 @@ if (args.mock) {
5790
5949
  engineReady(engine);
5791
5950
  });
5792
5951
  } else {
5793
- createRTE({ repoDir: args.repoDir, memoryLimitMb: args.runMemory }).then(
5794
- (engine) => {
5795
- logger.debug("engine created");
5796
- engineReady(engine);
5797
- }
5798
- );
5952
+ createRTE({
5953
+ repoDir: args.repoDir,
5954
+ memoryLimitMb: args.runMemory,
5955
+ maxWorkers: args.capacity
5956
+ }).then((engine) => {
5957
+ logger.debug("engine created");
5958
+ engineReady(engine);
5959
+ });
5799
5960
  }
5800
5961
  /**
5801
5962
  * @fileoverview Main entrypoint for libraries using yargs-parser in Node.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/ws-worker",
3
- "version": "0.2.11",
3
+ "version": "0.3.0",
4
4
  "description": "A Websocket Worker to connect Lightning to a Runtime Engine",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -14,6 +14,7 @@
14
14
  "@types/koa-logger": "^3.1.2",
15
15
  "@types/ws": "^8.5.6",
16
16
  "fast-safe-stringify": "^2.1.1",
17
+ "figures": "^5.0.0",
17
18
  "human-id": "^4.1.0",
18
19
  "jose": "^4.14.6",
19
20
  "koa": "^2.13.4",
@@ -21,9 +22,9 @@
21
22
  "koa-logger": "^3.2.1",
22
23
  "phoenix": "^1.7.7",
23
24
  "ws": "^8.14.1",
24
- "@openfn/engine-multi": "0.2.2",
25
+ "@openfn/engine-multi": "0.2.4",
25
26
  "@openfn/logger": "0.0.19",
26
- "@openfn/runtime": "0.2.1"
27
+ "@openfn/runtime": "0.2.2"
27
28
  },
28
29
  "devDependencies": {
29
30
  "@types/koa": "^2.13.5",
@@ -40,7 +41,7 @@
40
41
  "tsup": "^6.2.3",
41
42
  "typescript": "^4.6.4",
42
43
  "yargs": "^17.6.2",
43
- "@openfn/lightning-mock": "1.1.4"
44
+ "@openfn/lightning-mock": "1.1.6"
44
45
  },
45
46
  "files": [
46
47
  "dist",