@donkeylabs/server 2.0.24 → 2.0.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/server",
3
- "version": "2.0.24",
3
+ "version": "2.0.25",
4
4
  "type": "module",
5
5
  "description": "Type-safe plugin system for building RPC-style APIs with Bun",
6
6
  "main": "./src/index.ts",
@@ -91,6 +91,27 @@ async function main(): Promise<void> {
91
91
  plugins: bootstrap.manager.getServices(),
92
92
  events: createIpcEventBridge(socket, instanceId),
93
93
  pollInterval: 1000,
94
+ emitCustomEvent: async (payload) => {
95
+ sendEvent(socket, {
96
+ type: "event",
97
+ instanceId: payload.instanceId,
98
+ workflowName: payload.workflowName,
99
+ timestamp: Date.now(),
100
+ event: payload.event,
101
+ data: payload.data,
102
+ });
103
+ },
104
+ emitLog: async (payload) => {
105
+ sendEvent(socket, {
106
+ type: "log",
107
+ instanceId: payload.instanceId,
108
+ workflowName: payload.workflowName,
109
+ timestamp: Date.now(),
110
+ level: payload.level,
111
+ message: payload.message,
112
+ data: payload.data,
113
+ });
114
+ },
94
115
  });
95
116
 
96
117
  sendEvent(socket, {
@@ -11,6 +11,8 @@ import { createServer as createNetServer } from "node:net";
11
11
  // Message Protocol Types
12
12
  // ============================================
13
13
 
14
+ import type { LogLevel } from "./logger";
15
+
14
16
  export type WorkflowEventType =
15
17
  | "ready"
16
18
  | "started"
@@ -20,11 +22,14 @@ export type WorkflowEventType =
20
22
  | "step.failed"
21
23
  | "progress"
22
24
  | "completed"
23
- | "failed";
25
+ | "failed"
26
+ | "event"
27
+ | "log";
24
28
 
25
29
  export interface WorkflowEvent {
26
30
  type: WorkflowEventType;
27
31
  instanceId: string;
32
+ workflowName?: string;
28
33
  timestamp: number;
29
34
  stepName?: string;
30
35
  /** Step type (for step.started events) */
@@ -36,6 +41,14 @@ export interface WorkflowEvent {
36
41
  totalSteps?: number;
37
42
  /** Next step to execute (for step.completed events) */
38
43
  nextStep?: string;
44
+ /** Custom event name (for event type) */
45
+ event?: string;
46
+ /** Custom event payload or log data */
47
+ data?: Record<string, any>;
48
+ /** Log level (for log type) */
49
+ level?: LogLevel;
50
+ /** Log message (for log type) */
51
+ message?: string;
39
52
  }
40
53
 
41
54
  export interface ProxyRequest {
@@ -45,6 +45,19 @@ export interface StateMachineConfig {
45
45
  jobs?: Jobs;
46
46
  /** Poll interval for checking job completion (ms) */
47
47
  pollInterval?: number;
48
+ emitCustomEvent?: (payload: {
49
+ instanceId: string;
50
+ workflowName: string;
51
+ event: string;
52
+ data?: Record<string, any>;
53
+ }) => Promise<void>;
54
+ emitLog?: (payload: {
55
+ instanceId: string;
56
+ workflowName: string;
57
+ level: LogLevel;
58
+ message: string;
59
+ data?: Record<string, any>;
60
+ }) => Promise<void>;
48
61
  }
49
62
 
50
63
  // ============================================
@@ -58,6 +71,8 @@ export class WorkflowStateMachine {
58
71
  private events: StateMachineEvents;
59
72
  private jobs?: Jobs;
60
73
  private pollInterval: number;
74
+ private emitCustomEvent?: StateMachineConfig["emitCustomEvent"];
75
+ private emitLog?: StateMachineConfig["emitLog"];
61
76
  private cancelledInstances = new Set<string>();
62
77
 
63
78
  constructor(config: StateMachineConfig) {
@@ -67,6 +82,8 @@ export class WorkflowStateMachine {
67
82
  this.events = config.events;
68
83
  this.jobs = config.jobs;
69
84
  this.pollInterval = config.pollInterval ?? 1000;
85
+ this.emitCustomEvent = config.emitCustomEvent;
86
+ this.emitLog = config.emitLog;
70
87
  }
71
88
 
72
89
  /**
@@ -484,26 +501,39 @@ export class WorkflowStateMachine {
484
501
  const instanceId = instance.id;
485
502
 
486
503
  const scopedLogger = this.core?.logger?.scoped("workflow", instance.id);
487
- const emit = this.core?.events
488
- ? async (event: string, data?: Record<string, any>) => {
489
- const payload = {
490
- instanceId: instance.id,
491
- workflowName: instance.workflowName,
492
- event,
493
- data,
494
- };
504
+ const emit = async (event: string, data?: Record<string, any>) => {
505
+ const payload = {
506
+ instanceId: instance.id,
507
+ workflowName: instance.workflowName,
508
+ event,
509
+ data,
510
+ };
495
511
 
496
- await this.core!.events.emit("workflow.event", payload);
497
- await this.core!.events.emit(`workflow.${instance.workflowName}.event`, payload);
498
- await this.core!.events.emit(`workflow.${instance.id}.event`, payload);
499
- }
500
- : undefined;
512
+ if (this.core?.events) {
513
+ await this.core.events.emit("workflow.event", payload);
514
+ await this.core.events.emit(`workflow.${instance.workflowName}.event`, payload);
515
+ await this.core.events.emit(`workflow.${instance.id}.event`, payload);
516
+ }
501
517
 
502
- const log = scopedLogger
503
- ? (level: LogLevel, message: string, data?: Record<string, any>) => {
504
- scopedLogger[level](message, data);
505
- }
506
- : undefined;
518
+ if (this.emitCustomEvent) {
519
+ await this.emitCustomEvent(payload);
520
+ }
521
+ };
522
+
523
+ const log = (level: LogLevel, message: string, data?: Record<string, any>) => {
524
+ if (scopedLogger) {
525
+ scopedLogger[level](message, data);
526
+ }
527
+ if (this.emitLog) {
528
+ return this.emitLog({
529
+ instanceId: instance.id,
530
+ workflowName: instance.workflowName,
531
+ level,
532
+ message,
533
+ data,
534
+ });
535
+ }
536
+ };
507
537
 
508
538
  const core = this.core
509
539
  ? {
@@ -1475,6 +1475,53 @@ class WorkflowsImpl implements Workflows {
1475
1475
  break;
1476
1476
  }
1477
1477
 
1478
+ case "event": {
1479
+ const workflowName = event.workflowName ?? (await this.adapter.getInstance(instanceId))?.workflowName;
1480
+ const payload = {
1481
+ instanceId,
1482
+ workflowName,
1483
+ event: event.event,
1484
+ data: event.data,
1485
+ };
1486
+
1487
+ await this.emitEvent("workflow.event", payload);
1488
+ if (workflowName) {
1489
+ await this.emitEvent(`workflow.${workflowName}.event`, payload);
1490
+ }
1491
+ await this.emitEvent(`workflow.${instanceId}.event`, payload);
1492
+
1493
+ if (this.sse) {
1494
+ this.sse.broadcast(`workflow:${instanceId}`, "event", payload);
1495
+ this.sse.broadcast("workflows:all", "workflow.event", payload);
1496
+ }
1497
+ break;
1498
+ }
1499
+
1500
+ case "log": {
1501
+ const workflowName = event.workflowName ?? (await this.adapter.getInstance(instanceId))?.workflowName;
1502
+
1503
+ if (this.core?.logs && event.level && event.message) {
1504
+ this.core.logs.write({
1505
+ level: event.level,
1506
+ message: event.message,
1507
+ source: "workflow",
1508
+ sourceId: instanceId,
1509
+ data: event.data,
1510
+ context: workflowName ? { workflowName } : undefined,
1511
+ });
1512
+ }
1513
+
1514
+ if (this.sse) {
1515
+ this.sse.broadcast(`workflow:${instanceId}`, "log", {
1516
+ level: event.level,
1517
+ message: event.message,
1518
+ data: event.data,
1519
+ workflowName,
1520
+ });
1521
+ }
1522
+ break;
1523
+ }
1524
+
1478
1525
  case "completed": {
1479
1526
  // Clean up isolated process tracking
1480
1527
  this.cleanupIsolatedProcess(instanceId);