@openfn/ws-worker 0.2.12 → 0.3.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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # ws-worker
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Don't log compiler and runtime version logs
8
+
9
+ ## 0.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 419d310: Throttle attempt events to better preserve their sequencing
14
+
15
+ ### Patch Changes
16
+
17
+ - 598c669: Make edge conditions more stable if state is not passed
18
+ - 6e906a7: Better handling of job-error
19
+ - Updated dependencies [02ab459]
20
+ - @openfn/runtime@0.2.2
21
+ - @openfn/engine-multi@0.2.4
22
+
3
23
  ## 0.2.12
4
24
 
5
25
  ### 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,201 @@ 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.1",
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
+ if (Object.keys(adaptors).length) {
448
+ let allAdaptors = Object.keys(adaptors);
449
+ if (adaptor) {
450
+ allAdaptors = allAdaptors.filter((name) => adaptor.startsWith(name));
451
+ }
452
+ str += "\n" + allAdaptors.sort().map((adaptorName) => `${prefix(adaptorName)}${adaptors[adaptorName]}`).join("\n");
453
+ }
454
+ return str;
455
+ };
456
+
457
+ // src/events/run-start.ts
458
+ async function onRunStart(context, event) {
459
+ const time = (timestamp() - BigInt(1e7)).toString();
460
+ const { channel, state } = context;
461
+ state.activeRun = crypto3.randomUUID();
462
+ state.activeJob = event.jobId;
463
+ const job = state.plan.jobs.find(({ id }) => id === event.jobId);
464
+ const input_dataclip_id = state.inputDataclips[event.jobId];
465
+ const versions = {
466
+ worker: package_default.version,
467
+ ...event.versions
468
+ };
469
+ const versionLogContext = {
470
+ ...context,
471
+ state: {
472
+ ...state,
473
+ activeRun: state.activeRun
474
+ }
475
+ };
476
+ await sendEvent(channel, RUN_START, {
477
+ run_id: state.activeRun,
478
+ job_id: state.activeJob,
479
+ input_dataclip_id,
480
+ versions
481
+ });
482
+ const versionMessage = versions_default(
483
+ versionLogContext.state.activeRun,
484
+ versions,
485
+ job?.adaptor
486
+ );
487
+ await onJobLog(versionLogContext, {
488
+ time,
489
+ message: [versionMessage],
490
+ level: "info",
491
+ name: "VER"
492
+ });
493
+ return;
494
+ }
495
+
496
+ // src/util/throttle.ts
497
+ var createThrottler = () => {
498
+ const q = [];
499
+ let activePromise;
500
+ const add = (fn) => {
501
+ return (...args) => new Promise((resolve, reject) => {
502
+ q.push({ fn, args, resolve, reject });
503
+ shift();
504
+ });
505
+ };
506
+ const shift = () => {
507
+ if (activePromise) {
508
+ return;
509
+ }
510
+ const next = q.shift();
511
+ if (next) {
512
+ const { fn, args, resolve, reject } = next;
513
+ activePromise = fn(...args).then(resolve).catch(reject).finally(() => {
514
+ activePromise = void 0;
515
+ shift();
516
+ });
517
+ }
518
+ };
519
+ return add;
520
+ };
521
+ var throttle_default = createThrottler;
522
+
331
523
  // src/api/execute.ts
332
524
  var enc = new TextDecoder("utf-8");
333
525
  var eventMap = {
@@ -341,7 +533,8 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
341
533
  }) {
342
534
  logger.info("executing ", plan.id);
343
535
  const state = create_attempt_state_default(plan, options);
344
- const context = { channel, state, logger, onFinish };
536
+ const context = { channel, state, logger, engine, onFinish };
537
+ const throttle = throttle_default();
345
538
  const addEvent = (eventName, handler) => {
346
539
  const wrappedFn = async (event) => {
347
540
  const lightningEvent = eventMap[eventName] ?? eventName;
@@ -361,13 +554,13 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
361
554
  };
362
555
  const listeners = Object.assign(
363
556
  {},
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)
557
+ addEvent("workflow-start", throttle(onWorkflowStart)),
558
+ addEvent("job-start", throttle(onRunStart)),
559
+ addEvent("job-complete", throttle(onRunComplete)),
560
+ addEvent("job-error", throttle(onJobError)),
561
+ addEvent("workflow-log", throttle(onJobLog)),
562
+ addEvent("workflow-complete", throttle(onWorkflowComplete)),
563
+ addEvent("workflow-error", throttle(onWorkflowError))
371
564
  );
372
565
  engine.listen(plan.id, listeners);
373
566
  const resolvers = {
@@ -396,59 +589,17 @@ function execute(channel, engine, logger, plan, options = {}, onFinish = (_resul
396
589
  return context;
397
590
  }
398
591
  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);
592
+ channel.push(event, payload).receive("error", reject).receive("timeout", () => {
593
+ reject(new Error("timeout"));
594
+ }).receive("ok", resolve);
400
595
  });
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
596
  function onJobError(context, event) {
412
- const { state = {}, error, jobId } = event;
413
- if (state.errors?.[jobId]?.message === error.message) {
414
- onJobComplete(context, event);
597
+ const { state, error, jobId } = event;
598
+ if (state?.errors?.[jobId]?.message === error.message) {
599
+ return onRunComplete(context, event);
415
600
  } 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 = {};
601
+ return onRunComplete(context, event, event.error);
425
602
  }
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
603
  }
453
604
  function onWorkflowStart({ channel }, _event) {
454
605
  return sendEvent(channel, ATTEMPT_START);
package/dist/start.js CHANGED
@@ -5151,14 +5151,11 @@ var startWorkloop = (app, logger2, minBackoff2, maxBackoff2, maxWorkers) => {
5151
5151
  };
5152
5152
  var workloop_default = startWorkloop;
5153
5153
 
5154
- // src/api/execute.ts
5155
- import crypto3 from "node:crypto";
5156
-
5157
5154
  // src/util/convert-attempt.ts
5158
5155
  import crypto2 from "node:crypto";
5159
5156
  var conditions = {
5160
- on_job_success: (upstreamId) => `Boolean(!state.errors?.["${upstreamId}"] ?? true)`,
5161
- 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}"])`,
5162
5159
  always: (_upstreamId) => null
5163
5160
  };
5164
5161
  var mapEdgeCondition = (edge) => {
@@ -5205,7 +5202,7 @@ var convert_attempt_default = (attempt) => {
5205
5202
  const id = job.id || crypto2.randomUUID();
5206
5203
  nodes[id] = {
5207
5204
  id,
5208
- configuration: job.credential_id,
5205
+ configuration: job.credential || job.credential_id,
5209
5206
  expression: job.body,
5210
5207
  adaptor: job.adaptor
5211
5208
  };
@@ -5314,6 +5311,201 @@ var calculateAttemptExitReason = (state) => {
5314
5311
  return { reason: "success", error_type: null, error_message: null };
5315
5312
  };
5316
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.1",
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
+ if (Object.keys(adaptors).length) {
5434
+ let allAdaptors = Object.keys(adaptors);
5435
+ if (adaptor) {
5436
+ allAdaptors = allAdaptors.filter((name) => adaptor.startsWith(name));
5437
+ }
5438
+ str += "\n" + allAdaptors.sort().map((adaptorName) => `${prefix(adaptorName)}${adaptors[adaptorName]}`).join("\n");
5439
+ }
5440
+ return str;
5441
+ };
5442
+
5443
+ // src/events/run-start.ts
5444
+ async function onRunStart(context, event) {
5445
+ const time = (timestamp() - BigInt(1e7)).toString();
5446
+ const { channel, state } = context;
5447
+ state.activeRun = crypto4.randomUUID();
5448
+ state.activeJob = event.jobId;
5449
+ const job = state.plan.jobs.find(({ id }) => id === event.jobId);
5450
+ const input_dataclip_id = state.inputDataclips[event.jobId];
5451
+ const versions = {
5452
+ worker: package_default.version,
5453
+ ...event.versions
5454
+ };
5455
+ const versionLogContext = {
5456
+ ...context,
5457
+ state: {
5458
+ ...state,
5459
+ activeRun: state.activeRun
5460
+ }
5461
+ };
5462
+ await sendEvent(channel, RUN_START, {
5463
+ run_id: state.activeRun,
5464
+ job_id: state.activeJob,
5465
+ input_dataclip_id,
5466
+ versions
5467
+ });
5468
+ const versionMessage = versions_default(
5469
+ versionLogContext.state.activeRun,
5470
+ versions,
5471
+ job?.adaptor
5472
+ );
5473
+ await onJobLog(versionLogContext, {
5474
+ time,
5475
+ message: [versionMessage],
5476
+ level: "info",
5477
+ name: "VER"
5478
+ });
5479
+ return;
5480
+ }
5481
+
5482
+ // src/util/throttle.ts
5483
+ var createThrottler = () => {
5484
+ const q = [];
5485
+ let activePromise;
5486
+ const add = (fn) => {
5487
+ return (...args2) => new Promise((resolve5, reject) => {
5488
+ q.push({ fn, args: args2, resolve: resolve5, reject });
5489
+ shift();
5490
+ });
5491
+ };
5492
+ const shift = () => {
5493
+ if (activePromise) {
5494
+ return;
5495
+ }
5496
+ const next = q.shift();
5497
+ if (next) {
5498
+ const { fn, args: args2, resolve: resolve5, reject } = next;
5499
+ activePromise = fn(...args2).then(resolve5).catch(reject).finally(() => {
5500
+ activePromise = void 0;
5501
+ shift();
5502
+ });
5503
+ }
5504
+ };
5505
+ return add;
5506
+ };
5507
+ var throttle_default = createThrottler;
5508
+
5317
5509
  // src/api/execute.ts
5318
5510
  var enc = new TextDecoder("utf-8");
5319
5511
  var eventMap = {
@@ -5327,7 +5519,8 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
5327
5519
  }) {
5328
5520
  logger2.info("executing ", plan.id);
5329
5521
  const state = create_attempt_state_default(plan, options);
5330
- const context = { channel, state, logger: logger2, onFinish };
5522
+ const context = { channel, state, logger: logger2, engine, onFinish };
5523
+ const throttle = throttle_default();
5331
5524
  const addEvent = (eventName, handler) => {
5332
5525
  const wrappedFn = async (event) => {
5333
5526
  const lightningEvent = eventMap[eventName] ?? eventName;
@@ -5347,13 +5540,13 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
5347
5540
  };
5348
5541
  const listeners = Object.assign(
5349
5542
  {},
5350
- addEvent("workflow-start", onWorkflowStart),
5351
- addEvent("job-start", onJobStart),
5352
- addEvent("job-complete", onJobComplete),
5353
- addEvent("job-error", onJobError),
5354
- addEvent("workflow-log", onJobLog),
5355
- addEvent("workflow-complete", onWorkflowComplete),
5356
- addEvent("workflow-error", onWorkflowError)
5543
+ addEvent("workflow-start", throttle(onWorkflowStart)),
5544
+ addEvent("job-start", throttle(onRunStart)),
5545
+ addEvent("job-complete", throttle(onRunComplete)),
5546
+ addEvent("job-error", throttle(onJobError)),
5547
+ addEvent("workflow-log", throttle(onJobLog)),
5548
+ addEvent("workflow-complete", throttle(onWorkflowComplete)),
5549
+ addEvent("workflow-error", throttle(onWorkflowError))
5357
5550
  );
5358
5551
  engine.listen(plan.id, listeners);
5359
5552
  const resolvers = {
@@ -5382,60 +5575,18 @@ function execute(channel, engine, logger2, plan, options = {}, onFinish = (_resu
5382
5575
  return context;
5383
5576
  }
5384
5577
  var sendEvent = (channel, event, payload) => new Promise((resolve5, reject) => {
5385
- channel.push(event, payload).receive("error", reject).receive("timeout", () => reject(new Error("timeout"))).receive("ok", resolve5);
5578
+ channel.push(event, payload).receive("error", reject).receive("timeout", () => {
5579
+ reject(new Error("timeout"));
5580
+ }).receive("ok", resolve5);
5386
5581
  });
5387
- function onJobStart({ channel, state }, event) {
5388
- state.activeRun = crypto3.randomUUID();
5389
- state.activeJob = event.jobId;
5390
- const input_dataclip_id = state.inputDataclips[event.jobId];
5391
- return sendEvent(channel, RUN_START, {
5392
- run_id: state.activeRun,
5393
- job_id: state.activeJob,
5394
- input_dataclip_id
5395
- });
5396
- }
5397
5582
  function onJobError(context, event) {
5398
- const { state = {}, error, jobId } = event;
5399
- if (state.errors?.[jobId]?.message === error.message) {
5400
- onJobComplete(context, event);
5583
+ const { state, error, jobId } = event;
5584
+ if (state?.errors?.[jobId]?.message === error.message) {
5585
+ return onRunComplete(context, event);
5401
5586
  } else {
5402
- onJobComplete(context, event, event.error);
5587
+ return onRunComplete(context, event, event.error);
5403
5588
  }
5404
5589
  }
5405
- function onJobComplete({ channel, state }, event, error) {
5406
- const dataclipId = crypto3.randomUUID();
5407
- const run_id = state.activeRun;
5408
- const job_id = state.activeJob;
5409
- if (!state.dataclips) {
5410
- state.dataclips = {};
5411
- }
5412
- state.dataclips[dataclipId] = event.state;
5413
- delete state.activeRun;
5414
- delete state.activeJob;
5415
- state.lastDataclipId = dataclipId;
5416
- event.next?.forEach((nextJobId) => {
5417
- state.inputDataclips[nextJobId] = dataclipId;
5418
- });
5419
- const { reason, error_message, error_type } = calculateJobExitReason(
5420
- job_id,
5421
- event.state,
5422
- error
5423
- );
5424
- state.reasons[job_id] = { reason, error_message, error_type };
5425
- const evt = {
5426
- run_id,
5427
- job_id,
5428
- output_dataclip_id: dataclipId,
5429
- output_dataclip: stringify_default(event.state),
5430
- reason,
5431
- error_message,
5432
- error_type,
5433
- mem: event.mem,
5434
- duration: event.duration,
5435
- thread_id: event.threadId
5436
- };
5437
- return sendEvent(channel, RUN_COMPLETE, evt);
5438
- }
5439
5590
  function onWorkflowStart({ channel }, _event) {
5440
5591
  return sendEvent(channel, ATTEMPT_START);
5441
5592
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfn/ws-worker",
3
- "version": "0.2.12",
3
+ "version": "0.3.1",
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.3",
25
- "@openfn/logger": "0.0.19",
26
- "@openfn/runtime": "0.2.1"
25
+ "@openfn/engine-multi": "0.2.4",
26
+ "@openfn/runtime": "0.2.2",
27
+ "@openfn/logger": "0.0.19"
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.5"
44
+ "@openfn/lightning-mock": "1.1.6"
44
45
  },
45
46
  "files": [
46
47
  "dist",