@cadenza.io/service 2.8.0 → 2.10.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/dist/index.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ Actor: () => import_core4.Actor,
33
34
  DatabaseTask: () => DatabaseTask,
34
35
  DebounceTask: () => import_core4.DebounceTask,
35
36
  DeputyTask: () => DeputyTask,
@@ -2474,6 +2475,8 @@ var RestController = class _RestController {
2474
2475
  constructor() {
2475
2476
  this.fetchClientDiagnostics = /* @__PURE__ */ new Map();
2476
2477
  this.diagnosticsErrorHistoryLimit = 100;
2478
+ this.diagnosticsMaxClientEntries = 500;
2479
+ this.destroyedDiagnosticsTtlMs = 15 * 6e4;
2477
2480
  /**
2478
2481
  * Fetches data from the given URL with a specified timeout. This function performs
2479
2482
  * a fetch request with the ability to cancel the request if it exceeds the provided timeout duration.
@@ -3127,6 +3130,28 @@ var RestController = class _RestController {
3127
3130
  if (!this._instance) this._instance = new _RestController();
3128
3131
  return this._instance;
3129
3132
  }
3133
+ pruneFetchClientDiagnostics(now = Date.now()) {
3134
+ for (const [fetchId, state] of this.fetchClientDiagnostics.entries()) {
3135
+ if (state.destroyed && now - state.updatedAt > this.destroyedDiagnosticsTtlMs) {
3136
+ this.fetchClientDiagnostics.delete(fetchId);
3137
+ }
3138
+ }
3139
+ if (this.fetchClientDiagnostics.size <= this.diagnosticsMaxClientEntries) {
3140
+ return;
3141
+ }
3142
+ const entriesByEvictionPriority = Array.from(
3143
+ this.fetchClientDiagnostics.entries()
3144
+ ).sort((left, right) => {
3145
+ if (left[1].destroyed !== right[1].destroyed) {
3146
+ return left[1].destroyed ? -1 : 1;
3147
+ }
3148
+ return left[1].updatedAt - right[1].updatedAt;
3149
+ });
3150
+ while (this.fetchClientDiagnostics.size > this.diagnosticsMaxClientEntries && entriesByEvictionPriority.length > 0) {
3151
+ const [fetchId] = entriesByEvictionPriority.shift();
3152
+ this.fetchClientDiagnostics.delete(fetchId);
3153
+ }
3154
+ }
3130
3155
  resolveTransportDiagnosticsOptions(ctx) {
3131
3156
  const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
3132
3157
  const includeErrorHistory = Boolean(ctx.includeErrorHistory);
@@ -3139,6 +3164,8 @@ var RestController = class _RestController {
3139
3164
  };
3140
3165
  }
3141
3166
  ensureFetchClientDiagnostics(fetchId, serviceName, url) {
3167
+ const now = Date.now();
3168
+ this.pruneFetchClientDiagnostics(now);
3142
3169
  let state = this.fetchClientDiagnostics.get(fetchId);
3143
3170
  if (!state) {
3144
3171
  state = {
@@ -3158,13 +3185,14 @@ var RestController = class _RestController {
3158
3185
  signalFailures: 0,
3159
3186
  statusChecks: 0,
3160
3187
  statusFailures: 0,
3161
- updatedAt: Date.now()
3188
+ updatedAt: now
3162
3189
  };
3163
3190
  this.fetchClientDiagnostics.set(fetchId, state);
3164
3191
  } else {
3165
3192
  state.serviceName = serviceName;
3166
3193
  state.url = url;
3167
3194
  }
3195
+ this.pruneFetchClientDiagnostics(now);
3168
3196
  return state;
3169
3197
  }
3170
3198
  getErrorMessage(error) {
@@ -3196,6 +3224,7 @@ var RestController = class _RestController {
3196
3224
  }
3197
3225
  }
3198
3226
  collectFetchTransportDiagnostics(ctx) {
3227
+ this.pruneFetchClientDiagnostics();
3199
3228
  const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
3200
3229
  const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
3201
3230
  const states = Array.from(this.fetchClientDiagnostics.values()).sort(
@@ -3312,55 +3341,261 @@ var waitForSocketConnection = async (socket, timeoutMs, createError) => {
3312
3341
 
3313
3342
  // src/network/SocketController.ts
3314
3343
  var SocketController = class _SocketController {
3315
- /**
3316
- * Constructs the `SocketServer`, setting up a WebSocket server with specific configurations,
3317
- * including connection state recovery, rate limiting, CORS handling, and custom event handling.
3318
- * This class sets up the communication infrastructure for scalable, resilient, and secure WebSocket-based interactions
3319
- * using metadata-driven task execution with `Cadenza`.
3320
- *
3321
- * It provides support for:
3322
- * - Origin-based access control for connections.
3323
- * - Optional payload sanitization.
3324
- * - Configurable rate limiting and behavior on limit breaches (soft/hard disconnects).
3325
- * - Event handlers for connection, handshake, delegation, signaling, status checks, and disconnection.
3326
- *
3327
- * The server can handle both internal and external interactions depending on the provided configurations,
3328
- * and integrates directly with Cadenza's task workflow engine.
3329
- *
3330
- * Initializes the `SocketServer` to be ready for WebSocket communication.
3331
- */
3332
3344
  constructor() {
3333
- this.socketClientDiagnostics = /* @__PURE__ */ new Map();
3334
3345
  this.diagnosticsErrorHistoryLimit = 100;
3346
+ this.diagnosticsMaxClientEntries = 500;
3347
+ this.destroyedDiagnosticsTtlMs = 15 * 6e4;
3348
+ this.socketServerDefaultKey = "socket-server-default";
3349
+ this.socketServerInitialSessionState = {
3350
+ serverKey: this.socketServerDefaultKey,
3351
+ useSocket: false,
3352
+ status: "inactive",
3353
+ securityProfile: "medium",
3354
+ networkType: "internal",
3355
+ connectionCount: 0,
3356
+ lastStartedAt: null,
3357
+ lastConnectedAt: null,
3358
+ lastDisconnectedAt: null,
3359
+ lastShutdownAt: null,
3360
+ updatedAt: 0
3361
+ };
3362
+ this.socketClientInitialSessionState = {
3363
+ fetchId: "",
3364
+ serviceInstanceId: "",
3365
+ communicationTypes: [],
3366
+ serviceName: "",
3367
+ serviceAddress: "",
3368
+ servicePort: 0,
3369
+ protocol: "http",
3370
+ url: "",
3371
+ socketId: null,
3372
+ connected: false,
3373
+ handshake: false,
3374
+ pendingDelegations: 0,
3375
+ pendingTimers: 0,
3376
+ reconnectAttempts: 0,
3377
+ connectErrors: 0,
3378
+ reconnectErrors: 0,
3379
+ socketErrors: 0,
3380
+ errorCount: 0,
3381
+ destroyed: false,
3382
+ lastHandshakeAt: null,
3383
+ lastHandshakeError: null,
3384
+ lastDisconnectAt: null,
3385
+ updatedAt: 0
3386
+ };
3387
+ this.socketServerActor = CadenzaService.createActor(
3388
+ {
3389
+ name: "SocketServerActor",
3390
+ description: "Holds durable socket server session state and runtime socket server handle",
3391
+ defaultKey: this.socketServerDefaultKey,
3392
+ keyResolver: (input) => this.resolveSocketServerKey(input),
3393
+ loadPolicy: "lazy",
3394
+ writeContract: "overwrite",
3395
+ initState: this.socketServerInitialSessionState
3396
+ },
3397
+ { isMeta: true }
3398
+ );
3399
+ this.socketClientActor = CadenzaService.createActor(
3400
+ {
3401
+ name: "SocketClientActor",
3402
+ description: "Holds durable socket client session state and runtime socket connection handles",
3403
+ defaultKey: "socket-client-default",
3404
+ keyResolver: (input) => this.resolveSocketClientFetchId(input),
3405
+ loadPolicy: "lazy",
3406
+ writeContract: "overwrite",
3407
+ initState: this.socketClientInitialSessionState
3408
+ },
3409
+ { isMeta: true }
3410
+ );
3411
+ this.socketClientDiagnosticsActor = CadenzaService.createActor(
3412
+ {
3413
+ name: "SocketClientDiagnosticsActor",
3414
+ description: "Tracks socket client diagnostics snapshots per fetchId for transport observability",
3415
+ defaultKey: "socket-client-diagnostics",
3416
+ loadPolicy: "eager",
3417
+ writeContract: "overwrite",
3418
+ initState: {
3419
+ entries: {}
3420
+ }
3421
+ },
3422
+ { isMeta: true }
3423
+ );
3424
+ this.registerDiagnosticsTasks();
3425
+ this.registerSocketServerTasks();
3426
+ this.registerSocketClientTasks();
3335
3427
  CadenzaService.createMetaTask(
3336
3428
  "Collect socket transport diagnostics",
3337
- (ctx) => this.collectSocketTransportDiagnostics(ctx),
3429
+ this.socketClientDiagnosticsActor.task(
3430
+ ({ state, input }) => this.collectSocketTransportDiagnostics(input, state.entries),
3431
+ { mode: "read" }
3432
+ ),
3338
3433
  "Responds to distributed transport diagnostics inquiries with socket client data."
3339
3434
  ).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
3340
- CadenzaService.createMetaRoutine(
3341
- "SocketServer",
3342
- [
3343
- CadenzaService.createMetaTask("Setup SocketServer", (ctx) => {
3344
- if (!ctx.__useSocket) {
3435
+ }
3436
+ static get instance() {
3437
+ if (!this._instance) this._instance = new _SocketController();
3438
+ return this._instance;
3439
+ }
3440
+ registerDiagnosticsTasks() {
3441
+ CadenzaService.createThrottledMetaTask(
3442
+ "SocketClientDiagnosticsActor.Upsert",
3443
+ this.socketClientDiagnosticsActor.task(
3444
+ ({ state, input, setState }) => {
3445
+ const fetchId = String(input.fetchId ?? "").trim();
3446
+ if (!fetchId) {
3345
3447
  return;
3346
3448
  }
3347
- const server = new import_socket.Server(ctx.httpsServer ?? ctx.httpServer, {
3348
- pingInterval: 3e4,
3349
- pingTimeout: 2e4,
3350
- maxHttpBufferSize: 1e7,
3351
- // 10MB large payloads
3352
- connectionStateRecovery: {
3353
- maxDisconnectionDuration: 2 * 60 * 1e3,
3354
- // 2min
3355
- skipMiddlewares: true
3356
- // Optional: bypass rate limiter on recover
3449
+ const now = Date.now();
3450
+ const entries = { ...state.entries };
3451
+ const existing = entries[fetchId];
3452
+ const base = existing ? {
3453
+ ...existing,
3454
+ errorHistory: [...existing.errorHistory]
3455
+ } : {
3456
+ fetchId,
3457
+ serviceName: String(input.serviceName ?? ""),
3458
+ url: String(input.url ?? ""),
3459
+ socketId: null,
3460
+ connected: false,
3461
+ handshake: false,
3462
+ reconnectAttempts: 0,
3463
+ connectErrors: 0,
3464
+ reconnectErrors: 0,
3465
+ socketErrors: 0,
3466
+ pendingDelegations: 0,
3467
+ pendingTimers: 0,
3468
+ destroyed: false,
3469
+ lastHandshakeAt: null,
3470
+ lastHandshakeError: null,
3471
+ lastDisconnectAt: null,
3472
+ lastError: null,
3473
+ lastErrorAt: 0,
3474
+ errorHistory: [],
3475
+ updatedAt: now
3476
+ };
3477
+ if (input.serviceName !== void 0) {
3478
+ base.serviceName = String(input.serviceName);
3479
+ }
3480
+ if (input.url !== void 0) {
3481
+ base.url = String(input.url);
3482
+ }
3483
+ const patch = input.patch && typeof input.patch === "object" ? input.patch : {};
3484
+ Object.assign(base, patch);
3485
+ base.fetchId = fetchId;
3486
+ base.updatedAt = now;
3487
+ const errorMessage = input.error !== void 0 ? this.getErrorMessage(input.error) : void 0;
3488
+ if (errorMessage) {
3489
+ base.lastError = errorMessage;
3490
+ base.lastErrorAt = now;
3491
+ base.errorHistory.push({
3492
+ at: new Date(now).toISOString(),
3493
+ message: errorMessage
3494
+ });
3495
+ if (base.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
3496
+ base.errorHistory.splice(
3497
+ 0,
3498
+ base.errorHistory.length - this.diagnosticsErrorHistoryLimit
3499
+ );
3357
3500
  }
3501
+ }
3502
+ entries[fetchId] = base;
3503
+ this.pruneDiagnosticsEntries(entries, now);
3504
+ setState({ entries });
3505
+ },
3506
+ { mode: "write" }
3507
+ ),
3508
+ (context) => String(context?.fetchId ?? "default"),
3509
+ "Upserts socket client diagnostics in actor state."
3510
+ ).doOn("meta.socket_client.diagnostics_upsert_requested");
3511
+ }
3512
+ registerSocketServerTasks() {
3513
+ CadenzaService.createThrottledMetaTask(
3514
+ "SocketServerActor.PatchSession",
3515
+ this.socketServerActor.task(
3516
+ ({ state, input, setState }) => {
3517
+ const patch = input.patch && typeof input.patch === "object" ? input.patch : {};
3518
+ setState({
3519
+ ...state,
3520
+ ...patch,
3521
+ updatedAt: Date.now()
3522
+ });
3523
+ },
3524
+ { mode: "write" }
3525
+ ),
3526
+ (context) => String(context?.serverKey ?? this.socketServerDefaultKey),
3527
+ "Applies partial durable session updates for socket server actor."
3528
+ ).doOn("meta.socket_server.session_patch_requested");
3529
+ CadenzaService.createMetaTask(
3530
+ "SocketServerActor.ClearRuntime",
3531
+ this.socketServerActor.task(
3532
+ ({ setRuntimeState }) => {
3533
+ setRuntimeState(null);
3534
+ },
3535
+ { mode: "write" }
3536
+ ),
3537
+ "Clears socket server runtime handle after shutdown."
3538
+ ).doOn("meta.socket_server.runtime_clear_requested");
3539
+ const setupSocketServerTask = CadenzaService.createMetaTask(
3540
+ "Setup SocketServer",
3541
+ this.socketServerActor.task(
3542
+ ({ state, runtimeState, input, actor, setState, setRuntimeState, emit }) => {
3543
+ const serverKey = this.resolveSocketServerKey(input) ?? actor.key ?? this.socketServerDefaultKey;
3544
+ const shouldUseSocket = Boolean(input.__useSocket);
3545
+ if (!shouldUseSocket) {
3546
+ this.destroySocketServerRuntimeHandle(runtimeState);
3547
+ setRuntimeState(null);
3548
+ setState({
3549
+ ...state,
3550
+ serverKey,
3551
+ useSocket: false,
3552
+ status: "inactive",
3553
+ connectionCount: 0,
3554
+ lastShutdownAt: (/* @__PURE__ */ new Date()).toISOString(),
3555
+ updatedAt: Date.now()
3556
+ });
3557
+ return;
3558
+ }
3559
+ let runtimeHandle = runtimeState;
3560
+ if (!runtimeHandle) {
3561
+ runtimeHandle = this.createSocketServerRuntimeHandleFromContext(input);
3562
+ setRuntimeState(runtimeHandle);
3563
+ }
3564
+ const profile = String(input.__securityProfile ?? state.securityProfile ?? "medium");
3565
+ const networkType = String(input.__networkType ?? state.networkType ?? "internal");
3566
+ const schedulePatch = (patch) => {
3567
+ CadenzaService.emit("meta.socket_server.session_patch_requested", {
3568
+ serverKey,
3569
+ patch
3570
+ });
3571
+ };
3572
+ if (runtimeHandle.initialized) {
3573
+ schedulePatch({
3574
+ status: "active",
3575
+ useSocket: true,
3576
+ securityProfile: profile,
3577
+ networkType,
3578
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3579
+ lastStartedAt: state.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString()
3580
+ });
3581
+ return;
3582
+ }
3583
+ const server = runtimeHandle.server;
3584
+ runtimeHandle.initialized = true;
3585
+ setState({
3586
+ ...state,
3587
+ serverKey,
3588
+ useSocket: true,
3589
+ status: "active",
3590
+ securityProfile: profile,
3591
+ networkType,
3592
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3593
+ lastStartedAt: state.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
3594
+ updatedAt: Date.now()
3358
3595
  });
3359
- const profile = ctx.__securityProfile ?? "medium";
3360
3596
  server.use((socket, next) => {
3361
3597
  const origin = socket?.handshake?.headers?.origin;
3362
3598
  const allowedOrigins = ["*"];
3363
- const networkType = ctx.__networkType ?? "internal";
3364
3599
  let effectiveOrigin = origin || "unknown";
3365
3600
  if (networkType === "internal") effectiveOrigin = "internal";
3366
3601
  if (profile !== "low" && !allowedOrigins.includes(effectiveOrigin) && !allowedOrigins.includes("*")) {
@@ -3371,7 +3606,9 @@ var SocketController = class _SocketController {
3371
3606
  medium: { points: 1e4, duration: 10 },
3372
3607
  high: { points: 1e3, duration: 60, blockDuration: 300 }
3373
3608
  };
3374
- const limiter = new import_rate_limiter_flexible2.RateLimiterMemory(limiterOptions[profile]);
3609
+ const limiter = new import_rate_limiter_flexible2.RateLimiterMemory(
3610
+ limiterOptions[profile] ?? limiterOptions.medium
3611
+ );
3375
3612
  const clientKey = socket?.handshake?.address || "unknown";
3376
3613
  socket.use((packet, packetNext) => {
3377
3614
  limiter.consume(clientKey).then(() => packetNext()).catch((rej) => {
@@ -3406,116 +3643,111 @@ var SocketController = class _SocketController {
3406
3643
  });
3407
3644
  next();
3408
3645
  });
3409
- if (!server) {
3410
- CadenzaService.log("Socket setup error: No server", {}, "error");
3411
- return { ...ctx, __error: "No server", errored: true };
3412
- }
3413
3646
  server.on("connection", (ws) => {
3647
+ runtimeHandle.connectedSocketIds.add(ws.id);
3648
+ schedulePatch({
3649
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3650
+ lastConnectedAt: (/* @__PURE__ */ new Date()).toISOString(),
3651
+ status: "active"
3652
+ });
3414
3653
  try {
3415
- ws.on(
3416
- "handshake",
3417
- (ctx2, callback) => {
3418
- CadenzaService.log("SocketServer: New connection", {
3419
- ...ctx2,
3420
- socketId: ws.id
3421
- });
3422
- callback({
3423
- status: "success",
3424
- serviceName: CadenzaService.serviceRegistry.serviceName
3425
- });
3426
- if (ctx2.isFrontend) {
3427
- const fetchId = `browser:${ctx2.serviceInstanceId}`;
3428
- CadenzaService.createMetaTask(
3429
- `Transmit signal to ${fetchId}`,
3430
- (ctx3, emit) => {
3431
- if (ctx3.__signalName === void 0) {
3432
- return;
3433
- }
3434
- ws.emit("signal", ctx3);
3435
- if (ctx3.__routineExecId) {
3436
- emit(
3437
- `meta.socket_client.transmitted:${ctx3.__routineExecId}`,
3438
- {}
3439
- );
3440
- }
3654
+ ws.on("handshake", (ctx, callback) => {
3655
+ CadenzaService.log("SocketServer: New connection", {
3656
+ ...ctx,
3657
+ socketId: ws.id
3658
+ });
3659
+ callback({
3660
+ status: "success",
3661
+ serviceName: CadenzaService.serviceRegistry.serviceName
3662
+ });
3663
+ if (ctx.isFrontend) {
3664
+ const fetchId = `browser:${ctx.serviceInstanceId}`;
3665
+ CadenzaService.createMetaTask(
3666
+ `Transmit signal to ${fetchId}`,
3667
+ (c, emitter) => {
3668
+ if (c.__signalName === void 0) {
3669
+ return;
3670
+ }
3671
+ ws.emit("signal", c);
3672
+ if (c.__routineExecId) {
3673
+ emitter(`meta.socket_client.transmitted:${c.__routineExecId}`, {});
3441
3674
  }
3442
- ).doOn(
3443
- `meta.service_registry.selected_instance_for_socket:${fetchId}`
3444
- ).attachSignal("meta.socket_client.transmitted");
3445
- }
3446
- CadenzaService.emit("meta.socket.handshake", ctx2);
3447
- }
3448
- );
3449
- ws.on(
3450
- "delegation",
3451
- (ctx2, callback) => {
3452
- const deputyExecId = ctx2.__metadata.__deputyExecId;
3453
- CadenzaService.createEphemeralMetaTask(
3454
- "Resolve delegation",
3455
- (ctx3) => {
3456
- callback(ctx3);
3457
- },
3458
- "Resolves a delegation request using the provided callback from the client (.emitWithAck())",
3459
- { register: false }
3460
- ).doOn(`meta.node.graph_completed:${deputyExecId}`).emits(`meta.socket.delegation_resolved:${deputyExecId}`);
3461
- CadenzaService.createEphemeralMetaTask(
3462
- "Delegation progress update",
3463
- (ctx3) => {
3464
- if (ctx3.__progress !== void 0)
3465
- ws.emit("delegation_progress", ctx3);
3466
3675
  },
3467
- "Updates delegation progress",
3468
- {
3469
- once: false,
3470
- destroyCondition: (ctx3) => ctx3.data.progress === 1 || ctx3.data?.progress === void 0,
3471
- register: false
3472
- }
3473
- ).doOn(
3474
- `meta.node.routine_execution_progress:${deputyExecId}`,
3475
- `meta.node.graph_completed:${deputyExecId}`
3476
- ).emitsOnFail(`meta.socket.progress_failed:${deputyExecId}`);
3477
- CadenzaService.emit("meta.socket.delegation_requested", {
3478
- ...ctx2,
3479
- __name: ctx2.__remoteRoutineName
3480
- });
3676
+ "Transmit frontend bound signal through active websocket."
3677
+ ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal("meta.socket_client.transmitted");
3481
3678
  }
3482
- );
3483
- ws.on(
3484
- "signal",
3485
- (ctx2, callback) => {
3486
- if (CadenzaService.signalBroker.listObservedSignals().includes(ctx2.__signalName)) {
3487
- callback({
3488
- __status: "success",
3489
- __signalName: ctx2.__signalName
3490
- });
3491
- CadenzaService.emit(ctx2.__signalName, ctx2);
3492
- } else {
3493
- CadenzaService.log(
3494
- `No such signal ${ctx2.__signalName} on ${ctx2.__serviceName}`,
3495
- "warning"
3496
- );
3497
- callback({
3498
- ...ctx2,
3499
- __status: "error",
3500
- __error: `No such signal: ${ctx2.__signalName}`,
3501
- errored: true
3502
- });
3679
+ CadenzaService.emit("meta.socket.handshake", ctx);
3680
+ });
3681
+ ws.on("delegation", (ctx, callback) => {
3682
+ const deputyExecId = ctx.__metadata.__deputyExecId;
3683
+ CadenzaService.createEphemeralMetaTask(
3684
+ "Resolve delegation",
3685
+ (delegationCtx) => {
3686
+ callback(delegationCtx);
3687
+ },
3688
+ "Resolves a delegation request using client callback.",
3689
+ { register: false }
3690
+ ).doOn(`meta.node.graph_completed:${deputyExecId}`).emits(`meta.socket.delegation_resolved:${deputyExecId}`);
3691
+ CadenzaService.createEphemeralMetaTask(
3692
+ "Delegation progress update",
3693
+ (progressCtx) => {
3694
+ if (progressCtx.__progress !== void 0) {
3695
+ ws.emit("delegation_progress", progressCtx);
3696
+ }
3697
+ },
3698
+ "Updates delegation progress to client.",
3699
+ {
3700
+ once: false,
3701
+ destroyCondition: (progressCtx) => progressCtx.data.progress === 1 || progressCtx.data?.progress === void 0,
3702
+ register: false
3503
3703
  }
3704
+ ).doOn(
3705
+ `meta.node.routine_execution_progress:${deputyExecId}`,
3706
+ `meta.node.graph_completed:${deputyExecId}`
3707
+ ).emitsOnFail(`meta.socket.progress_failed:${deputyExecId}`);
3708
+ CadenzaService.emit("meta.socket.delegation_requested", {
3709
+ ...ctx,
3710
+ __name: ctx.__remoteRoutineName
3711
+ });
3712
+ });
3713
+ ws.on("signal", (ctx, callback) => {
3714
+ if (CadenzaService.signalBroker.listObservedSignals().includes(ctx.__signalName)) {
3715
+ callback({
3716
+ __status: "success",
3717
+ __signalName: ctx.__signalName
3718
+ });
3719
+ CadenzaService.emit(ctx.__signalName, ctx);
3720
+ } else {
3721
+ CadenzaService.log(
3722
+ `No such signal ${ctx.__signalName} on ${ctx.__serviceName}`,
3723
+ "warning"
3724
+ );
3725
+ callback({
3726
+ ...ctx,
3727
+ __status: "error",
3728
+ __error: `No such signal: ${ctx.__signalName}`,
3729
+ errored: true
3730
+ });
3504
3731
  }
3505
- );
3732
+ });
3506
3733
  ws.on(
3507
3734
  "status_check",
3508
- (ctx2, callback) => {
3735
+ (ctx, callback) => {
3509
3736
  CadenzaService.createEphemeralMetaTask(
3510
3737
  "Resolve status check",
3511
3738
  callback,
3512
3739
  "Resolves a status check request",
3513
3740
  { register: false }
3514
3741
  ).doAfter(CadenzaService.serviceRegistry.getStatusTask);
3515
- CadenzaService.emit("meta.socket.status_check_requested", ctx2);
3742
+ CadenzaService.emit("meta.socket.status_check_requested", ctx);
3516
3743
  }
3517
3744
  );
3518
3745
  ws.on("disconnect", () => {
3746
+ runtimeHandle.connectedSocketIds.delete(ws.id);
3747
+ schedulePatch({
3748
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3749
+ lastDisconnectedAt: (/* @__PURE__ */ new Date()).toISOString()
3750
+ });
3519
3751
  CadenzaService.log(
3520
3752
  "Socket client disconnected",
3521
3753
  { socketId: ws.id },
@@ -3525,514 +3757,846 @@ var SocketController = class _SocketController {
3525
3757
  __wsId: ws.id
3526
3758
  });
3527
3759
  });
3528
- } catch (e) {
3760
+ } catch (error) {
3529
3761
  CadenzaService.log(
3530
3762
  "SocketServer: Error in socket event",
3531
- { error: e },
3763
+ { error },
3532
3764
  "error"
3533
3765
  );
3534
3766
  }
3535
3767
  CadenzaService.emit("meta.socket.connected", { __wsId: ws.id });
3536
3768
  });
3537
- CadenzaService.createMetaTask(
3538
- "Broadcast status",
3539
- (ctx2) => server.emit("status_update", ctx2),
3769
+ runtimeHandle.broadcastStatusTask = CadenzaService.createMetaTask(
3770
+ `Broadcast status ${serverKey}`,
3771
+ (ctx) => server.emit("status_update", ctx),
3540
3772
  "Broadcasts the status of the server to all clients"
3541
3773
  ).doOn("meta.service.updated");
3542
- CadenzaService.createMetaTask(
3543
- "Shutdown SocketServer",
3544
- () => server.close(),
3774
+ runtimeHandle.shutdownTask = CadenzaService.createMetaTask(
3775
+ `Shutdown SocketServer ${serverKey}`,
3776
+ async () => {
3777
+ this.destroySocketServerRuntimeHandle(runtimeHandle);
3778
+ CadenzaService.emit("meta.socket_server.runtime_clear_requested", {
3779
+ serverKey
3780
+ });
3781
+ CadenzaService.emit("meta.socket_server.session_patch_requested", {
3782
+ serverKey,
3783
+ patch: {
3784
+ useSocket: false,
3785
+ status: "shutdown",
3786
+ connectionCount: 0,
3787
+ lastShutdownAt: (/* @__PURE__ */ new Date()).toISOString()
3788
+ }
3789
+ });
3790
+ },
3545
3791
  "Shuts down the socket server"
3546
3792
  ).doOn("meta.socket_server_shutdown_requested").emits("meta.socket.shutdown");
3547
- return ctx;
3548
- })
3549
- ],
3550
- "Bootstraps the socket server"
3551
- ).doOn("global.meta.rest.network_configured");
3793
+ return true;
3794
+ },
3795
+ { mode: "write" }
3796
+ ),
3797
+ "Initializes socket server runtime through actor state."
3798
+ );
3799
+ setupSocketServerTask.doOn("global.meta.rest.network_configured");
3800
+ }
3801
+ registerSocketClientTasks() {
3802
+ CadenzaService.createThrottledMetaTask(
3803
+ "SocketClientActor.ApplySessionOperation",
3804
+ this.socketClientActor.task(
3805
+ ({ state, input, setState }) => {
3806
+ const operation = String(
3807
+ input.operation ?? "transmit"
3808
+ );
3809
+ const patch = input.patch && typeof input.patch === "object" ? input.patch : {};
3810
+ let next = {
3811
+ ...state,
3812
+ ...patch,
3813
+ communicationTypes: patch.communicationTypes !== void 0 ? this.normalizeCommunicationTypes(patch.communicationTypes) : state.communicationTypes,
3814
+ updatedAt: Date.now()
3815
+ };
3816
+ if (input.serviceName !== void 0) {
3817
+ next.serviceName = String(input.serviceName);
3818
+ }
3819
+ if (input.serviceAddress !== void 0) {
3820
+ next.serviceAddress = String(input.serviceAddress);
3821
+ }
3822
+ if (input.serviceInstanceId !== void 0) {
3823
+ next.serviceInstanceId = String(input.serviceInstanceId);
3824
+ }
3825
+ if (input.protocol !== void 0) {
3826
+ next.protocol = String(input.protocol);
3827
+ }
3828
+ if (input.url !== void 0) {
3829
+ next.url = String(input.url);
3830
+ }
3831
+ if (input.servicePort !== void 0) {
3832
+ next.servicePort = Number(input.servicePort);
3833
+ }
3834
+ if (input.fetchId !== void 0) {
3835
+ next.fetchId = String(input.fetchId);
3836
+ }
3837
+ if (operation === "connect") {
3838
+ next.destroyed = false;
3839
+ } else if (operation === "handshake") {
3840
+ next.destroyed = false;
3841
+ next.connected = patch.connected ?? true;
3842
+ next.handshake = patch.handshake ?? true;
3843
+ } else if (operation === "shutdown") {
3844
+ next.connected = false;
3845
+ next.handshake = false;
3846
+ next.destroyed = true;
3847
+ next.pendingDelegations = 0;
3848
+ next.pendingTimers = 0;
3849
+ }
3850
+ setState(next);
3851
+ return next;
3852
+ },
3853
+ { mode: "write" }
3854
+ ),
3855
+ (context) => String(this.resolveSocketClientFetchId(context ?? {}) ?? "default"),
3856
+ "Applies socket client session operation patch in actor durable state."
3857
+ ).doOn("meta.socket_client.session_operation_requested");
3858
+ CadenzaService.createMetaTask(
3859
+ "SocketClientActor.ClearRuntime",
3860
+ this.socketClientActor.task(
3861
+ ({ setRuntimeState }) => {
3862
+ setRuntimeState(null);
3863
+ },
3864
+ { mode: "write" }
3865
+ ),
3866
+ "Clears socket client runtime handle."
3867
+ ).doOn("meta.socket_client.runtime_clear_requested");
3552
3868
  CadenzaService.createMetaTask(
3553
3869
  "Connect to socket server",
3554
- (ctx) => {
3555
- const {
3556
- serviceInstanceId,
3557
- communicationTypes,
3558
- serviceName,
3559
- serviceAddress,
3560
- servicePort,
3561
- protocol
3562
- } = ctx;
3563
- const socketProtocol = protocol === "https" ? "wss" : "ws";
3564
- const port = protocol === "https" ? 443 : servicePort;
3565
- const URL = `${socketProtocol}://${serviceAddress}:${port}`;
3566
- const fetchId = `${serviceAddress}_${port}`;
3567
- const socketDiagnostics = this.ensureSocketClientDiagnostics(
3568
- fetchId,
3569
- serviceName,
3570
- URL
3571
- );
3572
- socketDiagnostics.destroyed = false;
3573
- socketDiagnostics.updatedAt = Date.now();
3574
- let handshake = false;
3575
- let errorCount = 0;
3576
- const ERROR_LIMIT = 5;
3577
- if (CadenzaService.get(`Socket handshake with ${URL}`)) {
3578
- console.error("Socket client already exists", URL);
3579
- return;
3580
- }
3581
- const pendingDelegationIds = /* @__PURE__ */ new Set();
3582
- const pendingTimers = /* @__PURE__ */ new Set();
3583
- const syncPendingCounts = () => {
3584
- socketDiagnostics.pendingDelegations = pendingDelegationIds.size;
3585
- socketDiagnostics.pendingTimers = pendingTimers.size;
3586
- socketDiagnostics.updatedAt = Date.now();
3587
- };
3588
- let handshakeTask = null;
3589
- let emitWhenReady = null;
3590
- let transmitTask = null;
3591
- let delegateTask = null;
3592
- let socket = null;
3593
- socket = (0, import_socket2.io)(URL, {
3594
- reconnection: true,
3595
- reconnectionAttempts: 5,
3596
- reconnectionDelay: 2e3,
3597
- reconnectionDelayMax: 1e4,
3598
- randomizationFactor: 0.5,
3599
- transports: ["websocket"],
3600
- autoConnect: false
3601
- });
3602
- emitWhenReady = (event, data, timeoutMs = 6e4, ack) => {
3603
- return new Promise((resolve) => {
3604
- const resolveWithError = (errorMessage, fallbackError) => {
3605
- resolve({
3606
- ...data,
3607
- errored: true,
3608
- __error: errorMessage,
3609
- error: fallbackError instanceof Error ? fallbackError.message : errorMessage,
3610
- socketId: socket?.id,
3870
+ this.socketClientActor.task(
3871
+ ({ state, runtimeState, input, setState, setRuntimeState, emit }) => {
3872
+ const serviceInstanceId = String(input.serviceInstanceId ?? "");
3873
+ const communicationTypes = this.normalizeCommunicationTypes(
3874
+ input.communicationTypes
3875
+ );
3876
+ const serviceName = String(input.serviceName ?? "");
3877
+ const serviceAddress = String(input.serviceAddress ?? "");
3878
+ const protocol = String(input.protocol ?? "http");
3879
+ const normalizedPort = this.resolveServicePort(protocol, input.servicePort);
3880
+ if (!serviceAddress || !normalizedPort) {
3881
+ CadenzaService.log(
3882
+ "Socket client setup skipped due to missing address/port",
3883
+ {
3611
3884
  serviceName,
3612
- URL
3613
- });
3614
- };
3615
- const tryEmit = async () => {
3616
- const waitTimeoutMs = timeoutMs > 0 ? timeoutMs + 10 : 1e4;
3617
- const waitResult = await waitForSocketConnection(
3618
- socket,
3619
- waitTimeoutMs,
3620
- (reason, error) => {
3621
- if (reason === "connect_timeout") {
3622
- return `Socket connect timed out before '${event}'`;
3623
- }
3624
- if (reason === "connect_error") {
3625
- const errMessage = error instanceof Error ? error.message : String(error);
3626
- return `Socket connect error before '${event}': ${errMessage}`;
3627
- }
3628
- return `Socket disconnected before '${event}'`;
3885
+ serviceAddress,
3886
+ servicePort: input.servicePort,
3887
+ protocol
3888
+ },
3889
+ "warning"
3890
+ );
3891
+ return false;
3892
+ }
3893
+ const socketProtocol = protocol === "https" ? "wss" : "ws";
3894
+ const url = `${socketProtocol}://${serviceAddress}:${normalizedPort}`;
3895
+ const fetchId = `${serviceAddress}_${normalizedPort}`;
3896
+ const applySessionOperation = (operation, patch = {}) => {
3897
+ CadenzaService.emit("meta.socket_client.session_operation_requested", {
3898
+ fetchId,
3899
+ operation,
3900
+ patch,
3901
+ serviceInstanceId,
3902
+ communicationTypes,
3903
+ serviceName,
3904
+ serviceAddress,
3905
+ servicePort: normalizedPort,
3906
+ protocol,
3907
+ url
3908
+ });
3909
+ };
3910
+ const upsertDiagnostics = (patch, error) => {
3911
+ CadenzaService.emit("meta.socket_client.diagnostics_upsert_requested", {
3912
+ fetchId,
3913
+ serviceName,
3914
+ url,
3915
+ patch,
3916
+ error
3917
+ });
3918
+ };
3919
+ setState({
3920
+ ...state,
3921
+ fetchId,
3922
+ serviceInstanceId,
3923
+ communicationTypes,
3924
+ serviceName,
3925
+ serviceAddress,
3926
+ servicePort: normalizedPort,
3927
+ protocol,
3928
+ url,
3929
+ destroyed: false,
3930
+ updatedAt: Date.now()
3931
+ });
3932
+ let runtimeHandle = runtimeState;
3933
+ if (!runtimeHandle || runtimeHandle.url !== url) {
3934
+ this.destroySocketClientRuntimeHandle(runtimeHandle);
3935
+ runtimeHandle = this.createSocketClientRuntimeHandle(url);
3936
+ setRuntimeState(runtimeHandle);
3937
+ }
3938
+ upsertDiagnostics({
3939
+ destroyed: false,
3940
+ connected: false,
3941
+ handshake: false,
3942
+ socketId: runtimeHandle.socket.id ?? null
3943
+ });
3944
+ applySessionOperation("connect", {
3945
+ destroyed: false,
3946
+ connected: false,
3947
+ handshake: false,
3948
+ socketId: runtimeHandle.socket.id ?? null,
3949
+ pendingDelegations: runtimeHandle.pendingDelegationIds.size,
3950
+ pendingTimers: runtimeHandle.pendingTimers.size,
3951
+ errorCount: runtimeHandle.errorCount
3952
+ });
3953
+ if (runtimeHandle.initialized) {
3954
+ return true;
3955
+ }
3956
+ runtimeHandle.initialized = true;
3957
+ runtimeHandle.handshake = false;
3958
+ runtimeHandle.errorCount = 0;
3959
+ const syncPendingCounts = () => {
3960
+ const pendingDelegations = runtimeHandle.pendingDelegationIds.size;
3961
+ const pendingTimers = runtimeHandle.pendingTimers.size;
3962
+ upsertDiagnostics({
3963
+ pendingDelegations,
3964
+ pendingTimers
3965
+ });
3966
+ applySessionOperation("delegate", {
3967
+ pendingDelegations,
3968
+ pendingTimers
3969
+ });
3970
+ };
3971
+ runtimeHandle.emitWhenReady = (event, data, timeoutMs = 6e4, ack) => {
3972
+ return new Promise((resolve) => {
3973
+ const parsedTimeout = Number(timeoutMs);
3974
+ const normalizedTimeoutMs = Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? Math.trunc(parsedTimeout) : 6e4;
3975
+ let timer = null;
3976
+ let settled = false;
3977
+ const clearPendingTimer = () => {
3978
+ if (!timer) {
3979
+ return;
3629
3980
  }
3630
- );
3631
- if (!waitResult.ok) {
3632
- CadenzaService.log(
3633
- waitResult.error,
3634
- { socketId: socket?.id, serviceName, URL, event },
3635
- "error"
3636
- );
3637
- this.recordSocketClientError(
3638
- fetchId,
3981
+ clearTimeout(timer);
3982
+ runtimeHandle.pendingTimers.delete(timer);
3983
+ syncPendingCounts();
3984
+ timer = null;
3985
+ };
3986
+ const settle = (response) => {
3987
+ if (settled) {
3988
+ return;
3989
+ }
3990
+ settled = true;
3991
+ clearPendingTimer();
3992
+ if (ack) ack(response);
3993
+ resolve(response);
3994
+ };
3995
+ const resolveWithError = (errorMessage, fallbackError) => {
3996
+ settle({
3997
+ ...data,
3998
+ errored: true,
3999
+ __error: errorMessage,
4000
+ error: fallbackError instanceof Error ? fallbackError.message : errorMessage,
4001
+ socketId: runtimeHandle.socket.id,
3639
4002
  serviceName,
3640
- URL,
3641
- waitResult.error
3642
- );
3643
- resolveWithError(waitResult.error);
3644
- return;
3645
- }
3646
- let timer = null;
3647
- if (timeoutMs !== 0) {
3648
- timer = setTimeout(() => {
3649
- if (timer) {
3650
- pendingTimers.delete(timer);
3651
- syncPendingCounts();
3652
- timer = null;
4003
+ url
4004
+ });
4005
+ };
4006
+ const tryEmit = async () => {
4007
+ const waitResult = await waitForSocketConnection(
4008
+ runtimeHandle.socket,
4009
+ normalizedTimeoutMs + 10,
4010
+ (reason, error) => {
4011
+ if (reason === "connect_timeout") {
4012
+ return `Socket connect timed out before '${event}'`;
4013
+ }
4014
+ if (reason === "connect_error") {
4015
+ const errMessage = error instanceof Error ? error.message : String(error);
4016
+ return `Socket connect error before '${event}': ${errMessage}`;
4017
+ }
4018
+ return `Socket disconnected before '${event}'`;
3653
4019
  }
4020
+ );
4021
+ if (!waitResult.ok) {
3654
4022
  CadenzaService.log(
3655
- `Socket event '${event}' timed out`,
3656
- { socketId: socket?.id, serviceName, URL },
4023
+ waitResult.error,
4024
+ {
4025
+ socketId: runtimeHandle.socket.id,
4026
+ serviceName,
4027
+ url,
4028
+ event
4029
+ },
3657
4030
  "error"
3658
4031
  );
3659
- this.recordSocketClientError(
3660
- fetchId,
3661
- serviceName,
3662
- URL,
3663
- `Socket event '${event}' timed out`
3664
- );
3665
- resolveWithError(`Socket event '${event}' timed out`);
3666
- }, timeoutMs + 10);
3667
- pendingTimers.add(timer);
3668
- syncPendingCounts();
3669
- }
3670
- const connectedSocket = socket;
3671
- if (!connectedSocket) {
3672
- resolveWithError(
3673
- `Socket unavailable before emitting '${event}'`
3674
- );
3675
- return;
3676
- }
3677
- connectedSocket.timeout(timeoutMs).emit(event, data, (err, response) => {
3678
- if (timer) {
3679
- clearTimeout(timer);
3680
- pendingTimers.delete(timer);
3681
- syncPendingCounts();
3682
- timer = null;
4032
+ upsertDiagnostics({}, waitResult.error);
4033
+ resolveWithError(waitResult.error);
4034
+ return;
3683
4035
  }
3684
- if (err) {
4036
+ timer = setTimeout(() => {
4037
+ if (settled) {
4038
+ return;
4039
+ }
4040
+ clearPendingTimer();
4041
+ const message = `Socket event '${event}' timed out`;
3685
4042
  CadenzaService.log(
3686
- "Socket timeout.",
4043
+ message,
4044
+ { socketId: runtimeHandle.socket.id, serviceName, url },
4045
+ "error"
4046
+ );
4047
+ upsertDiagnostics(
3687
4048
  {
3688
- event,
3689
- error: err.message,
3690
- socketId: socket?.id,
3691
- serviceName
4049
+ lastHandshakeError: message
3692
4050
  },
3693
- "warning"
4051
+ message
3694
4052
  );
3695
- this.recordSocketClientError(
3696
- fetchId,
4053
+ applySessionOperation("transmit", {
4054
+ lastHandshakeError: message
4055
+ });
4056
+ resolveWithError(message);
4057
+ }, normalizedTimeoutMs + 10);
4058
+ runtimeHandle.pendingTimers.add(timer);
4059
+ syncPendingCounts();
4060
+ runtimeHandle.socket.timeout(normalizedTimeoutMs).emit(event, data, (err, response) => {
4061
+ if (err) {
4062
+ CadenzaService.log(
4063
+ "Socket timeout.",
4064
+ {
4065
+ event,
4066
+ error: err.message,
4067
+ socketId: runtimeHandle.socket.id,
4068
+ serviceName
4069
+ },
4070
+ "warning"
4071
+ );
4072
+ upsertDiagnostics(
4073
+ {
4074
+ lastHandshakeError: err.message
4075
+ },
4076
+ err
4077
+ );
4078
+ applySessionOperation("transmit", {
4079
+ lastHandshakeError: err.message
4080
+ });
4081
+ response = {
4082
+ __error: `Timeout error: ${err}`,
4083
+ errored: true,
4084
+ ...data
4085
+ };
4086
+ }
4087
+ settle(response);
4088
+ });
4089
+ };
4090
+ void tryEmit().catch((error) => {
4091
+ CadenzaService.log(
4092
+ "Socket emit failed unexpectedly",
4093
+ {
4094
+ event,
4095
+ error: error instanceof Error ? error.message : String(error),
4096
+ socketId: runtimeHandle.socket.id,
3697
4097
  serviceName,
3698
- URL,
3699
- err
3700
- );
3701
- response = {
3702
- __error: `Timeout error: ${err}`,
3703
- errored: true,
3704
- ...ctx,
3705
- ...ctx.__metadata
3706
- };
3707
- }
3708
- if (ack) ack(response);
3709
- resolve(response);
4098
+ url
4099
+ },
4100
+ "error"
4101
+ );
4102
+ const message = `Socket event '${event}' failed`;
4103
+ upsertDiagnostics(
4104
+ {
4105
+ lastHandshakeError: error instanceof Error ? error.message : String(error)
4106
+ },
4107
+ error
4108
+ );
4109
+ applySessionOperation("transmit", {
4110
+ lastHandshakeError: error instanceof Error ? error.message : String(error)
4111
+ });
4112
+ resolveWithError(message, error);
3710
4113
  });
3711
- };
3712
- void tryEmit();
4114
+ });
4115
+ };
4116
+ const socket = runtimeHandle.socket;
4117
+ socket.on("connect", () => {
4118
+ if (runtimeHandle.handshake) return;
4119
+ upsertDiagnostics({
4120
+ connected: true,
4121
+ destroyed: false,
4122
+ socketId: socket.id ?? null
4123
+ });
4124
+ applySessionOperation("connect", {
4125
+ connected: true,
4126
+ destroyed: false,
4127
+ socketId: socket.id ?? null
4128
+ });
4129
+ CadenzaService.emit(`meta.socket_client.connected:${fetchId}`, input);
3713
4130
  });
3714
- };
3715
- socket.on("connect", () => {
3716
- if (handshake) return;
3717
- socketDiagnostics.connected = true;
3718
- socketDiagnostics.destroyed = false;
3719
- socketDiagnostics.socketId = socket?.id ?? null;
3720
- socketDiagnostics.updatedAt = Date.now();
3721
- CadenzaService.emit(`meta.socket_client.connected:${fetchId}`, ctx);
3722
- });
3723
- socket.on("delegation_progress", (ctx2) => {
3724
- CadenzaService.emit(
3725
- `meta.socket_client.delegation_progress:${ctx2.__metadata.__deputyExecId}`,
3726
- ctx2
3727
- );
3728
- });
3729
- socket.on("signal", (ctx2) => {
3730
- if (CadenzaService.signalBroker.listObservedSignals().includes(ctx2.__signalName)) {
3731
- CadenzaService.emit(ctx2.__signalName, ctx2);
3732
- }
3733
- });
3734
- socket.on("status_update", (status) => {
3735
- CadenzaService.emit("meta.socket_client.status_received", status);
3736
- });
3737
- socket.on("connect_error", (err) => {
3738
- handshake = false;
3739
- socketDiagnostics.connected = false;
3740
- socketDiagnostics.handshake = false;
3741
- socketDiagnostics.connectErrors++;
3742
- socketDiagnostics.lastHandshakeError = err.message;
3743
- socketDiagnostics.updatedAt = Date.now();
3744
- this.recordSocketClientError(fetchId, serviceName, URL, err);
3745
- CadenzaService.log(
3746
- "Socket connect error",
3747
- { error: err.message, serviceName, socketId: socket?.id, URL },
3748
- "error"
3749
- );
3750
- CadenzaService.emit(`meta.socket_client.connect_error:${fetchId}`, err);
3751
- });
3752
- socket.on("reconnect_attempt", (attempt) => {
3753
- socketDiagnostics.reconnectAttempts = Math.max(
3754
- socketDiagnostics.reconnectAttempts,
3755
- attempt
3756
- );
3757
- socketDiagnostics.updatedAt = Date.now();
3758
- CadenzaService.log(`Reconnect attempt: ${attempt}`);
3759
- });
3760
- socket.on("reconnect", (attempt) => {
3761
- socketDiagnostics.connected = true;
3762
- socketDiagnostics.updatedAt = Date.now();
3763
- CadenzaService.log(`Socket reconnected after ${attempt} tries`, {
3764
- socketId: socket?.id,
3765
- URL,
3766
- serviceName
4131
+ socket.on("delegation_progress", (delegationCtx) => {
4132
+ CadenzaService.emit(
4133
+ `meta.socket_client.delegation_progress:${delegationCtx.__metadata.__deputyExecId}`,
4134
+ delegationCtx
4135
+ );
3767
4136
  });
3768
- });
3769
- socket.on("reconnect_error", (err) => {
3770
- handshake = false;
3771
- socketDiagnostics.connected = false;
3772
- socketDiagnostics.handshake = false;
3773
- socketDiagnostics.reconnectErrors++;
3774
- socketDiagnostics.lastHandshakeError = err.message;
3775
- socketDiagnostics.updatedAt = Date.now();
3776
- this.recordSocketClientError(fetchId, serviceName, URL, err);
3777
- CadenzaService.log(
3778
- "Socket reconnect failed.",
3779
- { error: err.message, serviceName, URL, socketId: socket?.id },
3780
- "warning"
3781
- );
3782
- });
3783
- socket.on("error", (err) => {
3784
- errorCount++;
3785
- socketDiagnostics.socketErrors++;
3786
- socketDiagnostics.updatedAt = Date.now();
3787
- this.recordSocketClientError(fetchId, serviceName, URL, err);
3788
- CadenzaService.log(
3789
- "Socket error",
3790
- { error: err, socketId: socket?.id, URL, serviceName },
3791
- "error"
3792
- );
3793
- CadenzaService.emit("meta.socket_client.error", err);
3794
- });
3795
- socket.on("disconnect", () => {
3796
- const disconnectedAt = (/* @__PURE__ */ new Date()).toISOString();
3797
- socketDiagnostics.connected = false;
3798
- socketDiagnostics.handshake = false;
3799
- socketDiagnostics.lastDisconnectAt = disconnectedAt;
3800
- socketDiagnostics.updatedAt = Date.now();
3801
- CadenzaService.log(
3802
- "Socket disconnected.",
3803
- { URL, serviceName, socketId: socket?.id },
3804
- "warning"
3805
- );
3806
- CadenzaService.emit(`meta.socket_client.disconnected:${fetchId}`, {
3807
- serviceName,
3808
- serviceAddress,
3809
- servicePort
4137
+ socket.on("signal", (signalCtx) => {
4138
+ if (CadenzaService.signalBroker.listObservedSignals().includes(signalCtx.__signalName)) {
4139
+ CadenzaService.emit(signalCtx.__signalName, signalCtx);
4140
+ }
3810
4141
  });
3811
- handshake = false;
3812
- });
3813
- socket.connect();
3814
- handshakeTask = CadenzaService.createMetaTask(
3815
- `Socket handshake with ${URL}`,
3816
- async (ctx2, emit) => {
3817
- if (handshake) return;
3818
- handshake = true;
3819
- socketDiagnostics.handshake = true;
3820
- socketDiagnostics.updatedAt = Date.now();
3821
- await emitWhenReady?.(
3822
- "handshake",
4142
+ socket.on("status_update", (status) => {
4143
+ CadenzaService.emit("meta.socket_client.status_received", status);
4144
+ });
4145
+ socket.on("connect_error", (err) => {
4146
+ runtimeHandle.handshake = false;
4147
+ upsertDiagnostics(
3823
4148
  {
3824
- serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
3825
- serviceName: CadenzaService.serviceRegistry.serviceName,
3826
- isFrontend: isBrowser,
3827
- __status: "success"
4149
+ connected: false,
4150
+ handshake: false,
4151
+ connectErrors: state.connectErrors + 1,
4152
+ lastHandshakeError: err.message
3828
4153
  },
3829
- 1e4,
3830
- (result) => {
3831
- if (result.status === "success") {
3832
- socketDiagnostics.connected = true;
3833
- socketDiagnostics.handshake = true;
3834
- socketDiagnostics.lastHandshakeAt = (/* @__PURE__ */ new Date()).toISOString();
3835
- socketDiagnostics.lastHandshakeError = null;
3836
- socketDiagnostics.updatedAt = Date.now();
3837
- CadenzaService.log("Socket client connected", {
3838
- result,
3839
- serviceName,
3840
- socketId: socket?.id,
3841
- URL
3842
- });
3843
- } else {
3844
- socketDiagnostics.connected = false;
3845
- socketDiagnostics.handshake = false;
3846
- socketDiagnostics.lastHandshakeError = result?.__error ?? result?.error ?? "Socket handshake failed";
3847
- socketDiagnostics.updatedAt = Date.now();
3848
- this.recordSocketClientError(
3849
- fetchId,
3850
- serviceName,
3851
- URL,
3852
- socketDiagnostics.lastHandshakeError
3853
- );
3854
- CadenzaService.log(
3855
- "Socket handshake failed",
3856
- { result, serviceName, socketId: socket?.id, URL },
3857
- "warning"
3858
- );
3859
- }
3860
- }
4154
+ err
3861
4155
  );
3862
- },
3863
- "Handshakes with socket server"
3864
- ).doOn(`meta.socket_client.connected:${fetchId}`);
3865
- delegateTask = CadenzaService.createMetaTask(
3866
- `Delegate flow to Socket service ${URL}`,
3867
- async (ctx2, emit) => {
3868
- if (ctx2.__remoteRoutineName === void 0) {
3869
- return;
3870
- }
3871
- return new Promise((resolve) => {
3872
- delete ctx2.__isSubMeta;
3873
- delete ctx2.__broadcast;
3874
- const requestSentAt = Date.now();
3875
- pendingDelegationIds.add(ctx2.__metadata.__deputyExecId);
3876
- syncPendingCounts();
3877
- emitWhenReady?.(
3878
- "delegation",
3879
- ctx2,
3880
- ctx2.__timeout ?? 6e4,
3881
- (resultContext) => {
3882
- const requestDuration = Date.now() - requestSentAt;
3883
- const metadata = resultContext.__metadata;
3884
- delete resultContext.__metadata;
3885
- emit(
3886
- `meta.socket_client.delegated:${ctx2.__metadata.__deputyExecId}`,
3887
- {
3888
- ...resultContext,
3889
- ...metadata,
3890
- __requestDuration: requestDuration
3891
- }
3892
- );
3893
- pendingDelegationIds.delete(ctx2.__metadata.__deputyExecId);
3894
- syncPendingCounts();
3895
- if (resultContext?.errored || resultContext?.failed) {
3896
- this.recordSocketClientError(
3897
- fetchId,
4156
+ applySessionOperation("connect", {
4157
+ connected: false,
4158
+ handshake: false,
4159
+ connectErrors: state.connectErrors + 1,
4160
+ lastHandshakeError: err.message
4161
+ });
4162
+ CadenzaService.log(
4163
+ "Socket connect error",
4164
+ {
4165
+ error: err.message,
4166
+ serviceName,
4167
+ socketId: socket.id,
4168
+ url
4169
+ },
4170
+ "error"
4171
+ );
4172
+ CadenzaService.emit(`meta.socket_client.connect_error:${fetchId}`, err);
4173
+ });
4174
+ socket.on("reconnect_attempt", (attempt) => {
4175
+ upsertDiagnostics({ reconnectAttempts: attempt });
4176
+ applySessionOperation("connect", {
4177
+ reconnectAttempts: attempt
4178
+ });
4179
+ CadenzaService.log(`Reconnect attempt: ${attempt}`);
4180
+ });
4181
+ socket.on("reconnect", (attempt) => {
4182
+ upsertDiagnostics({ connected: true });
4183
+ applySessionOperation("connect", {
4184
+ connected: true
4185
+ });
4186
+ CadenzaService.log(`Socket reconnected after ${attempt} tries`, {
4187
+ socketId: socket.id,
4188
+ url,
4189
+ serviceName
4190
+ });
4191
+ });
4192
+ socket.on("reconnect_error", (err) => {
4193
+ runtimeHandle.handshake = false;
4194
+ upsertDiagnostics(
4195
+ {
4196
+ connected: false,
4197
+ handshake: false,
4198
+ reconnectErrors: state.reconnectErrors + 1,
4199
+ lastHandshakeError: err.message
4200
+ },
4201
+ err
4202
+ );
4203
+ applySessionOperation("connect", {
4204
+ connected: false,
4205
+ handshake: false,
4206
+ reconnectErrors: state.reconnectErrors + 1,
4207
+ lastHandshakeError: err.message
4208
+ });
4209
+ CadenzaService.log(
4210
+ "Socket reconnect failed.",
4211
+ { error: err.message, serviceName, url, socketId: socket.id },
4212
+ "warning"
4213
+ );
4214
+ });
4215
+ socket.on("error", (err) => {
4216
+ runtimeHandle.errorCount += 1;
4217
+ upsertDiagnostics(
4218
+ {
4219
+ socketErrors: state.socketErrors + 1,
4220
+ lastHandshakeError: this.getErrorMessage(err)
4221
+ },
4222
+ err
4223
+ );
4224
+ applySessionOperation("transmit", {
4225
+ socketErrors: state.socketErrors + 1,
4226
+ errorCount: runtimeHandle.errorCount,
4227
+ lastHandshakeError: this.getErrorMessage(err)
4228
+ });
4229
+ CadenzaService.log(
4230
+ "Socket error",
4231
+ { error: err, socketId: socket.id, url, serviceName },
4232
+ "error"
4233
+ );
4234
+ CadenzaService.emit("meta.socket_client.error", err);
4235
+ });
4236
+ socket.on("disconnect", () => {
4237
+ const disconnectedAt = (/* @__PURE__ */ new Date()).toISOString();
4238
+ upsertDiagnostics({
4239
+ connected: false,
4240
+ handshake: false,
4241
+ lastDisconnectAt: disconnectedAt
4242
+ });
4243
+ applySessionOperation("connect", {
4244
+ connected: false,
4245
+ handshake: false,
4246
+ lastDisconnectAt: disconnectedAt
4247
+ });
4248
+ CadenzaService.log(
4249
+ "Socket disconnected.",
4250
+ { url, serviceName, socketId: socket.id },
4251
+ "warning"
4252
+ );
4253
+ CadenzaService.emit(`meta.socket_client.disconnected:${fetchId}`, {
4254
+ serviceName,
4255
+ serviceAddress,
4256
+ servicePort: normalizedPort
4257
+ });
4258
+ runtimeHandle.handshake = false;
4259
+ });
4260
+ socket.connect();
4261
+ runtimeHandle.handshakeTask = CadenzaService.createMetaTask(
4262
+ `Socket handshake with ${url}`,
4263
+ async (_ctx, emitter) => {
4264
+ if (runtimeHandle.handshake) return;
4265
+ runtimeHandle.handshake = true;
4266
+ upsertDiagnostics({
4267
+ handshake: true
4268
+ });
4269
+ applySessionOperation("handshake", {
4270
+ handshake: true
4271
+ });
4272
+ await runtimeHandle.emitWhenReady?.(
4273
+ "handshake",
4274
+ {
4275
+ serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
4276
+ serviceName: CadenzaService.serviceRegistry.serviceName,
4277
+ isFrontend: isBrowser,
4278
+ __status: "success"
4279
+ },
4280
+ 1e4,
4281
+ (result) => {
4282
+ if (result.status === "success") {
4283
+ const handshakeAt = (/* @__PURE__ */ new Date()).toISOString();
4284
+ upsertDiagnostics({
4285
+ connected: true,
4286
+ handshake: true,
4287
+ lastHandshakeAt: handshakeAt,
4288
+ lastHandshakeError: null,
4289
+ socketId: socket.id ?? null
4290
+ });
4291
+ applySessionOperation("handshake", {
4292
+ connected: true,
4293
+ handshake: true,
4294
+ lastHandshakeAt: handshakeAt,
4295
+ lastHandshakeError: null,
4296
+ socketId: socket.id ?? null
4297
+ });
4298
+ CadenzaService.log("Socket client connected", {
4299
+ result,
3898
4300
  serviceName,
3899
- URL,
3900
- resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed"
4301
+ socketId: socket.id,
4302
+ url
4303
+ });
4304
+ } else {
4305
+ const errorMessage = result?.__error ?? result?.error ?? "Socket handshake failed";
4306
+ upsertDiagnostics(
4307
+ {
4308
+ connected: false,
4309
+ handshake: false,
4310
+ lastHandshakeError: errorMessage
4311
+ },
4312
+ errorMessage
4313
+ );
4314
+ applySessionOperation("handshake", {
4315
+ connected: false,
4316
+ handshake: false,
4317
+ lastHandshakeError: errorMessage
4318
+ });
4319
+ CadenzaService.log(
4320
+ "Socket handshake failed",
4321
+ { result, serviceName, socketId: socket.id, url },
4322
+ "warning"
3901
4323
  );
3902
4324
  }
3903
- resolve(resultContext);
4325
+ void emitter;
3904
4326
  }
3905
4327
  );
3906
- });
3907
- },
3908
- `Delegate flow to service ${serviceName} with address ${URL}`
3909
- ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal(
3910
- "meta.socket_client.delegated",
3911
- "meta.socket_shutdown_requested"
3912
- );
3913
- transmitTask = CadenzaService.createMetaTask(
3914
- `Transmit signal to socket server ${URL}`,
3915
- async (ctx2, emit) => {
3916
- if (ctx2.__signalName === void 0) {
3917
- return;
3918
- }
3919
- return new Promise((resolve) => {
3920
- delete ctx2.__broadcast;
3921
- emitWhenReady?.("signal", ctx2, 5e3, (response) => {
3922
- if (ctx2.__routineExecId) {
3923
- emit(
3924
- `meta.socket_client.transmitted:${ctx2.__routineExecId}`,
3925
- response
4328
+ },
4329
+ "Handshakes with socket server"
4330
+ ).doOn(`meta.socket_client.connected:${fetchId}`);
4331
+ runtimeHandle.delegateTask = CadenzaService.createMetaTask(
4332
+ `Delegate flow to Socket service ${url}`,
4333
+ async (delegateCtx, emitter) => {
4334
+ if (delegateCtx.__remoteRoutineName === void 0) {
4335
+ return;
4336
+ }
4337
+ delete delegateCtx.__isSubMeta;
4338
+ delete delegateCtx.__broadcast;
4339
+ const deputyExecId = delegateCtx.__metadata?.__deputyExecId;
4340
+ const requestSentAt = Date.now();
4341
+ if (deputyExecId) {
4342
+ runtimeHandle.pendingDelegationIds.add(deputyExecId);
4343
+ syncPendingCounts();
4344
+ }
4345
+ try {
4346
+ const resultContext = await runtimeHandle.emitWhenReady?.(
4347
+ "delegation",
4348
+ delegateCtx,
4349
+ delegateCtx.__timeout ?? 6e4
4350
+ ) ?? {
4351
+ errored: true,
4352
+ __error: "Socket delegation returned no response"
4353
+ };
4354
+ const requestDuration = Date.now() - requestSentAt;
4355
+ const metadata = resultContext.__metadata;
4356
+ delete resultContext.__metadata;
4357
+ if (deputyExecId) {
4358
+ emitter(`meta.socket_client.delegated:${deputyExecId}`, {
4359
+ ...resultContext,
4360
+ ...metadata,
4361
+ __requestDuration: requestDuration
4362
+ });
4363
+ }
4364
+ if (resultContext?.errored || resultContext?.failed) {
4365
+ const errorMessage = resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed";
4366
+ upsertDiagnostics(
4367
+ {
4368
+ lastHandshakeError: String(errorMessage)
4369
+ },
4370
+ errorMessage
3926
4371
  );
4372
+ applySessionOperation("delegate", {
4373
+ lastHandshakeError: String(errorMessage)
4374
+ });
4375
+ }
4376
+ return resultContext;
4377
+ } catch (error) {
4378
+ const message = error instanceof Error ? error.message : String(error);
4379
+ const failedContext = {
4380
+ errored: true,
4381
+ __error: message
4382
+ };
4383
+ if (deputyExecId) {
4384
+ emitter(`meta.socket_client.delegated:${deputyExecId}`, {
4385
+ ...failedContext,
4386
+ __requestDuration: Date.now() - requestSentAt
4387
+ });
4388
+ }
4389
+ upsertDiagnostics(
4390
+ {
4391
+ lastHandshakeError: message
4392
+ },
4393
+ error
4394
+ );
4395
+ applySessionOperation("delegate", {
4396
+ lastHandshakeError: message
4397
+ });
4398
+ return failedContext;
4399
+ } finally {
4400
+ if (deputyExecId) {
4401
+ runtimeHandle.pendingDelegationIds.delete(deputyExecId);
4402
+ syncPendingCounts();
3927
4403
  }
3928
- resolve(response);
3929
- });
3930
- });
3931
- },
3932
- `Transmits signal to service ${serviceName} with address ${URL}`
3933
- ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal("meta.socket_client.transmitted");
3934
- CadenzaService.createEphemeralMetaTask(
3935
- `Shutdown SocketClient ${URL}`,
3936
- (ctx2, emit) => {
3937
- handshake = false;
3938
- socketDiagnostics.connected = false;
3939
- socketDiagnostics.handshake = false;
3940
- socketDiagnostics.destroyed = true;
3941
- socketDiagnostics.updatedAt = Date.now();
3942
- CadenzaService.log("Shutting down socket client", { URL, serviceName });
3943
- socket?.close();
3944
- handshakeTask?.destroy();
3945
- delegateTask?.destroy();
3946
- transmitTask?.destroy();
3947
- handshakeTask = null;
3948
- delegateTask = null;
3949
- transmitTask = null;
3950
- emitWhenReady = null;
3951
- socket = null;
3952
- emit(`meta.fetch.handshake_requested:${fetchId}`, {
3953
- serviceInstanceId,
3954
- serviceName,
3955
- communicationTypes,
3956
- serviceAddress,
3957
- servicePort,
3958
- protocol,
3959
- handshakeData: {
3960
- instanceId: CadenzaService.serviceRegistry.serviceInstanceId,
3961
- serviceName: CadenzaService.serviceRegistry.serviceName
3962
4404
  }
3963
- });
3964
- for (const id of pendingDelegationIds) {
3965
- emit(`meta.socket_client.delegated:${id}`, {
4405
+ },
4406
+ `Delegate flow to service ${serviceName} with address ${url}`
4407
+ ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal(
4408
+ "meta.socket_client.delegated",
4409
+ "meta.socket_shutdown_requested"
4410
+ );
4411
+ runtimeHandle.transmitTask = CadenzaService.createMetaTask(
4412
+ `Transmit signal to socket server ${url}`,
4413
+ async (signalCtx, emitter) => {
4414
+ if (signalCtx.__signalName === void 0) {
4415
+ return;
4416
+ }
4417
+ delete signalCtx.__broadcast;
4418
+ const response = await runtimeHandle.emitWhenReady?.("signal", signalCtx, 5e3) ?? {
3966
4419
  errored: true,
3967
- __error: "Shutting down socket client"
4420
+ __error: "Socket signal transmission returned no response"
4421
+ };
4422
+ applySessionOperation("transmit", {});
4423
+ if (signalCtx.__routineExecId) {
4424
+ emitter(`meta.socket_client.transmitted:${signalCtx.__routineExecId}`, {
4425
+ ...response
4426
+ });
4427
+ }
4428
+ return response;
4429
+ },
4430
+ `Transmits signal to service ${serviceName} with address ${url}`
4431
+ ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal("meta.socket_client.transmitted");
4432
+ CadenzaService.createEphemeralMetaTask(
4433
+ `Shutdown SocketClient ${url}`,
4434
+ (_ctx, emitter) => {
4435
+ runtimeHandle.handshake = false;
4436
+ upsertDiagnostics({
4437
+ connected: false,
4438
+ handshake: false,
4439
+ destroyed: true,
4440
+ pendingDelegations: 0,
4441
+ pendingTimers: 0
3968
4442
  });
3969
- }
3970
- pendingDelegationIds.clear();
3971
- syncPendingCounts();
3972
- for (const timer of pendingTimers) {
3973
- clearTimeout(timer);
3974
- }
3975
- pendingTimers.clear();
3976
- syncPendingCounts();
3977
- },
3978
- "Shuts down the socket client"
3979
- ).doOn(
3980
- `meta.socket_shutdown_requested:${fetchId}`,
3981
- `meta.socket_client.disconnected:${fetchId}`,
3982
- `meta.fetch.handshake_failed:${fetchId}`,
3983
- `meta.socket_client.connect_error:${fetchId}`
3984
- ).attachSignal("meta.fetch.handshake_requested").emits("meta.socket_client_shutdown_complete");
3985
- return true;
3986
- },
3987
- "Connects to a specified socket server"
4443
+ applySessionOperation("shutdown", {
4444
+ connected: false,
4445
+ handshake: false,
4446
+ destroyed: true,
4447
+ pendingDelegations: 0,
4448
+ pendingTimers: 0
4449
+ });
4450
+ CadenzaService.log("Shutting down socket client", { url, serviceName });
4451
+ emitter(`meta.fetch.handshake_requested:${fetchId}`, {
4452
+ serviceInstanceId,
4453
+ serviceName,
4454
+ communicationTypes,
4455
+ serviceAddress,
4456
+ servicePort: normalizedPort,
4457
+ protocol,
4458
+ handshakeData: {
4459
+ instanceId: CadenzaService.serviceRegistry.serviceInstanceId,
4460
+ serviceName: CadenzaService.serviceRegistry.serviceName
4461
+ }
4462
+ });
4463
+ for (const id of runtimeHandle.pendingDelegationIds) {
4464
+ emitter(`meta.socket_client.delegated:${id}`, {
4465
+ errored: true,
4466
+ __error: "Shutting down socket client"
4467
+ });
4468
+ }
4469
+ this.destroySocketClientRuntimeHandle(runtimeHandle);
4470
+ emitter("meta.socket_client.runtime_clear_requested", {
4471
+ fetchId
4472
+ });
4473
+ },
4474
+ "Shuts down the socket client"
4475
+ ).doOn(
4476
+ `meta.socket_shutdown_requested:${fetchId}`,
4477
+ `meta.socket_client.disconnected:${fetchId}`,
4478
+ `meta.fetch.handshake_failed:${fetchId}`,
4479
+ `meta.socket_client.connect_error:${fetchId}`
4480
+ ).attachSignal("meta.fetch.handshake_requested").emits("meta.socket_client_shutdown_complete");
4481
+ return true;
4482
+ },
4483
+ { mode: "write" }
4484
+ ),
4485
+ "Connects to a specified socket server and wires runtime tasks."
3988
4486
  ).doOn("meta.fetch.handshake_complete").emitsOnFail("meta.socket_client.connect_failed");
3989
4487
  }
3990
- static get instance() {
3991
- if (!this._instance) this._instance = new _SocketController();
3992
- return this._instance;
4488
+ resolveSocketServerKey(input) {
4489
+ return String(input.serverKey ?? input.__socketServerKey ?? this.socketServerDefaultKey).trim() || this.socketServerDefaultKey;
3993
4490
  }
3994
- resolveTransportDiagnosticsOptions(ctx) {
3995
- const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
3996
- const includeErrorHistory = Boolean(ctx.includeErrorHistory);
3997
- const requestedLimit = Number(ctx.errorHistoryLimit);
3998
- const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
4491
+ resolveSocketClientFetchId(input) {
4492
+ const explicitFetchId = String(input.fetchId ?? "").trim();
4493
+ if (explicitFetchId) {
4494
+ return explicitFetchId;
4495
+ }
4496
+ const serviceAddress = String(input.serviceAddress ?? "").trim();
4497
+ const protocol = String(input.protocol ?? "http").trim();
4498
+ const port = this.resolveServicePort(protocol, input.servicePort);
4499
+ if (!serviceAddress || !port) {
4500
+ return void 0;
4501
+ }
4502
+ return `${serviceAddress}_${port}`;
4503
+ }
4504
+ resolveServicePort(protocol, rawPort) {
4505
+ if (protocol === "https") {
4506
+ return 443;
4507
+ }
4508
+ const parsed = Number(rawPort);
4509
+ if (!Number.isFinite(parsed) || parsed <= 0) {
4510
+ return void 0;
4511
+ }
4512
+ return Math.trunc(parsed);
4513
+ }
4514
+ createSocketServerRuntimeHandleFromContext(context) {
4515
+ const baseServer = context.httpsServer ?? context.httpServer;
4516
+ if (!baseServer) {
4517
+ throw new Error(
4518
+ "Socket server runtime setup requires either httpsServer or httpServer"
4519
+ );
4520
+ }
4521
+ const server = new import_socket.Server(baseServer, {
4522
+ pingInterval: 3e4,
4523
+ pingTimeout: 2e4,
4524
+ maxHttpBufferSize: 1e7,
4525
+ connectionStateRecovery: {
4526
+ maxDisconnectionDuration: 2 * 60 * 1e3,
4527
+ skipMiddlewares: true
4528
+ }
4529
+ });
3999
4530
  return {
4000
- detailLevel,
4001
- includeErrorHistory,
4002
- errorHistoryLimit
4531
+ server,
4532
+ initialized: false,
4533
+ connectedSocketIds: /* @__PURE__ */ new Set(),
4534
+ broadcastStatusTask: null,
4535
+ shutdownTask: null
4003
4536
  };
4004
4537
  }
4005
- ensureSocketClientDiagnostics(fetchId, serviceName, url) {
4006
- let state = this.socketClientDiagnostics.get(fetchId);
4007
- if (!state) {
4008
- state = {
4009
- fetchId,
4010
- serviceName,
4011
- url,
4012
- socketId: null,
4013
- connected: false,
4014
- handshake: false,
4015
- reconnectAttempts: 0,
4016
- connectErrors: 0,
4017
- reconnectErrors: 0,
4018
- socketErrors: 0,
4019
- pendingDelegations: 0,
4020
- pendingTimers: 0,
4021
- destroyed: false,
4022
- lastHandshakeAt: null,
4023
- lastHandshakeError: null,
4024
- lastDisconnectAt: null,
4025
- lastError: null,
4026
- lastErrorAt: 0,
4027
- errorHistory: [],
4028
- updatedAt: Date.now()
4029
- };
4030
- this.socketClientDiagnostics.set(fetchId, state);
4031
- } else {
4032
- state.serviceName = serviceName;
4033
- state.url = url;
4538
+ destroySocketServerRuntimeHandle(runtimeHandle) {
4539
+ if (!runtimeHandle) {
4540
+ return;
4034
4541
  }
4035
- return state;
4542
+ runtimeHandle.broadcastStatusTask?.destroy();
4543
+ runtimeHandle.shutdownTask?.destroy();
4544
+ runtimeHandle.broadcastStatusTask = null;
4545
+ runtimeHandle.shutdownTask = null;
4546
+ runtimeHandle.connectedSocketIds.clear();
4547
+ runtimeHandle.initialized = false;
4548
+ runtimeHandle.server.close();
4549
+ runtimeHandle.server.removeAllListeners();
4550
+ }
4551
+ createSocketClientRuntimeHandle(url) {
4552
+ return {
4553
+ url,
4554
+ socket: (0, import_socket2.io)(url, {
4555
+ reconnection: true,
4556
+ reconnectionAttempts: 5,
4557
+ reconnectionDelay: 2e3,
4558
+ reconnectionDelayMax: 1e4,
4559
+ randomizationFactor: 0.5,
4560
+ transports: ["websocket"],
4561
+ autoConnect: false
4562
+ }),
4563
+ initialized: false,
4564
+ handshake: false,
4565
+ errorCount: 0,
4566
+ pendingDelegationIds: /* @__PURE__ */ new Set(),
4567
+ pendingTimers: /* @__PURE__ */ new Set(),
4568
+ emitWhenReady: null,
4569
+ handshakeTask: null,
4570
+ delegateTask: null,
4571
+ transmitTask: null
4572
+ };
4573
+ }
4574
+ destroySocketClientRuntimeHandle(runtimeHandle) {
4575
+ if (!runtimeHandle) {
4576
+ return;
4577
+ }
4578
+ runtimeHandle.initialized = false;
4579
+ runtimeHandle.handshake = false;
4580
+ runtimeHandle.emitWhenReady = null;
4581
+ runtimeHandle.handshakeTask?.destroy();
4582
+ runtimeHandle.delegateTask?.destroy();
4583
+ runtimeHandle.transmitTask?.destroy();
4584
+ runtimeHandle.handshakeTask = null;
4585
+ runtimeHandle.delegateTask = null;
4586
+ runtimeHandle.transmitTask = null;
4587
+ for (const timer of runtimeHandle.pendingTimers) {
4588
+ clearTimeout(timer);
4589
+ }
4590
+ runtimeHandle.pendingTimers.clear();
4591
+ runtimeHandle.pendingDelegationIds.clear();
4592
+ runtimeHandle.socket.close();
4593
+ runtimeHandle.socket.removeAllListeners();
4594
+ }
4595
+ normalizeCommunicationTypes(value) {
4596
+ if (!Array.isArray(value)) {
4597
+ return [];
4598
+ }
4599
+ return value.map((item) => String(item)).filter((item) => item.trim().length > 0);
4036
4600
  }
4037
4601
  getErrorMessage(error) {
4038
4602
  if (error instanceof Error) {
@@ -4047,28 +4611,53 @@ var SocketController = class _SocketController {
4047
4611
  return String(error);
4048
4612
  }
4049
4613
  }
4050
- recordSocketClientError(fetchId, serviceName, url, error) {
4051
- const state = this.ensureSocketClientDiagnostics(fetchId, serviceName, url);
4052
- const message = this.getErrorMessage(error);
4053
- const now = Date.now();
4054
- state.lastError = message;
4055
- state.lastErrorAt = now;
4056
- state.updatedAt = now;
4057
- state.errorHistory.push({
4058
- at: new Date(now).toISOString(),
4059
- message
4614
+ pruneDiagnosticsEntries(entries, now = Date.now()) {
4615
+ for (const [fetchId, state] of Object.entries(entries)) {
4616
+ if (state.destroyed && now - state.updatedAt > this.destroyedDiagnosticsTtlMs) {
4617
+ delete entries[fetchId];
4618
+ }
4619
+ }
4620
+ if (Object.keys(entries).length <= this.diagnosticsMaxClientEntries) {
4621
+ return;
4622
+ }
4623
+ const entriesByEvictionPriority = Object.entries(entries).sort((left, right) => {
4624
+ if (left[1].destroyed !== right[1].destroyed) {
4625
+ return left[1].destroyed ? -1 : 1;
4626
+ }
4627
+ return left[1].updatedAt - right[1].updatedAt;
4060
4628
  });
4061
- if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
4062
- state.errorHistory.splice(
4063
- 0,
4064
- state.errorHistory.length - this.diagnosticsErrorHistoryLimit
4065
- );
4629
+ while (Object.keys(entries).length > this.diagnosticsMaxClientEntries && entriesByEvictionPriority.length > 0) {
4630
+ const [fetchId] = entriesByEvictionPriority.shift();
4631
+ delete entries[fetchId];
4632
+ }
4633
+ }
4634
+ async getSocketClientDiagnosticsEntry(fetchId) {
4635
+ const normalized = String(fetchId ?? "").trim();
4636
+ if (!normalized) {
4637
+ return void 0;
4066
4638
  }
4639
+ const snapshot = this.socketClientDiagnosticsActor.getState();
4640
+ const entries = { ...snapshot.entries };
4641
+ this.pruneDiagnosticsEntries(entries);
4642
+ return entries[normalized];
4643
+ }
4644
+ resolveTransportDiagnosticsOptions(ctx) {
4645
+ const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
4646
+ const includeErrorHistory = Boolean(ctx.includeErrorHistory);
4647
+ const requestedLimit = Number(ctx.errorHistoryLimit);
4648
+ const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
4649
+ return {
4650
+ detailLevel,
4651
+ includeErrorHistory,
4652
+ errorHistoryLimit
4653
+ };
4067
4654
  }
4068
- collectSocketTransportDiagnostics(ctx) {
4655
+ collectSocketTransportDiagnostics(ctx, diagnosticsEntries) {
4069
4656
  const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
4070
4657
  const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
4071
- const states = Array.from(this.socketClientDiagnostics.values()).sort(
4658
+ const entries = { ...diagnosticsEntries };
4659
+ this.pruneDiagnosticsEntries(entries);
4660
+ const states = Object.values(entries).sort(
4072
4661
  (a, b) => a.fetchId.localeCompare(b.fetchId)
4073
4662
  );
4074
4663
  const summary = {
@@ -4086,10 +4675,7 @@ var SocketController = class _SocketController {
4086
4675
  0
4087
4676
  ),
4088
4677
  connectErrors: states.reduce((acc, state) => acc + state.connectErrors, 0),
4089
- reconnectErrors: states.reduce(
4090
- (acc, state) => acc + state.reconnectErrors,
4091
- 0
4092
- ),
4678
+ reconnectErrors: states.reduce((acc, state) => acc + state.reconnectErrors, 0),
4093
4679
  socketErrors: states.reduce((acc, state) => acc + state.socketErrors, 0),
4094
4680
  latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
4095
4681
  };
@@ -4448,6 +5034,22 @@ var GraphMetadataController = class _GraphMetadataController {
4448
5034
  "Handles task execution relationship creation",
4449
5035
  { concurrency: 100, isSubMeta: true }
4450
5036
  ).doOn("meta.node.mapped", "meta.node.detected_previous_task_execution").emits("global.meta.graph_metadata.relationship_executed");
5037
+ CadenzaService.createMetaTask("Handle actor creation", (ctx) => {
5038
+ return {
5039
+ data: {
5040
+ ...ctx.data,
5041
+ service_name: CadenzaService.serviceRegistry.serviceName
5042
+ }
5043
+ };
5044
+ }).doOn("meta.actor.created").emits("global.meta.graph_metadata.actor_created");
5045
+ CadenzaService.createMetaTask("Handle actor task association", (ctx) => {
5046
+ return {
5047
+ data: {
5048
+ ...ctx.data,
5049
+ service_name: CadenzaService.serviceRegistry.serviceName
5050
+ }
5051
+ };
5052
+ }).doOn("meta.actor.task_associated").emits("global.meta.graph_metadata.actor_task_associated");
4451
5053
  CadenzaService.createMetaTask("Handle Intent Creation", (ctx) => {
4452
5054
  const intentName = ctx.data?.name;
4453
5055
  return {
@@ -6119,8 +6721,69 @@ function tableFieldTypeToSchemaType(type) {
6119
6721
  var import_uuid3 = require("uuid");
6120
6722
 
6121
6723
  // src/graph/controllers/GraphSyncController.ts
6724
+ var ACTOR_TASK_METADATA = /* @__PURE__ */ Symbol.for("@cadenza.io/core/actor-task-meta");
6725
+ function getActorTaskRuntimeMetadata(taskFunction) {
6726
+ if (typeof taskFunction !== "function") {
6727
+ return void 0;
6728
+ }
6729
+ return taskFunction[ACTOR_TASK_METADATA];
6730
+ }
6731
+ function sanitizeActorMetadataValue(value) {
6732
+ if (value === null) {
6733
+ return null;
6734
+ }
6735
+ if (value === void 0 || typeof value === "function") {
6736
+ return void 0;
6737
+ }
6738
+ if (Array.isArray(value)) {
6739
+ const items = [];
6740
+ for (const item of value) {
6741
+ const sanitizedItem = sanitizeActorMetadataValue(item);
6742
+ if (sanitizedItem !== void 0) {
6743
+ items.push(sanitizedItem);
6744
+ }
6745
+ }
6746
+ return items;
6747
+ }
6748
+ if (typeof value === "object") {
6749
+ const output = {};
6750
+ for (const [key, nestedValue] of Object.entries(value)) {
6751
+ const sanitizedNestedValue = sanitizeActorMetadataValue(nestedValue);
6752
+ if (sanitizedNestedValue !== void 0) {
6753
+ output[key] = sanitizedNestedValue;
6754
+ }
6755
+ }
6756
+ return output;
6757
+ }
6758
+ return value;
6759
+ }
6760
+ function buildActorRegistrationData(actor) {
6761
+ const definition = sanitizeActorMetadataValue(
6762
+ typeof actor?.toDefinition === "function" ? actor.toDefinition() : {}
6763
+ );
6764
+ const stateDefinition = definition?.state && typeof definition.state === "object" ? definition.state : {};
6765
+ const actorKind = typeof definition?.kind === "string" ? definition.kind : actor?.kind;
6766
+ return {
6767
+ name: definition?.name ?? actor?.spec?.name ?? "",
6768
+ description: definition?.description ?? actor?.spec?.description ?? "",
6769
+ default_key: definition?.defaultKey ?? actor?.spec?.defaultKey ?? "default",
6770
+ load_policy: definition?.loadPolicy ?? actor?.spec?.loadPolicy ?? "eager",
6771
+ write_contract: definition?.writeContract ?? actor?.spec?.writeContract ?? "overwrite",
6772
+ runtime_read_guard: definition?.runtimeReadGuard ?? actor?.spec?.runtimeReadGuard ?? "none",
6773
+ consistency_profile: definition?.consistencyProfile ?? actor?.spec?.consistencyProfile ?? null,
6774
+ key_definition: definition?.key ?? null,
6775
+ state_definition: stateDefinition,
6776
+ retry_policy: definition?.retry ?? {},
6777
+ idempotency_policy: definition?.idempotency ?? {},
6778
+ session_policy: definition?.session ?? {},
6779
+ is_meta: actorKind === "meta",
6780
+ version: 1
6781
+ };
6782
+ }
6122
6783
  var GraphSyncController = class _GraphSyncController {
6123
6784
  constructor() {
6785
+ this.registeredActors = /* @__PURE__ */ new Set();
6786
+ this.registeredActorTaskMaps = /* @__PURE__ */ new Set();
6124
6787
  this.isCadenzaDBReady = false;
6125
6788
  }
6126
6789
  static get instance() {
@@ -6378,6 +7041,120 @@ var GraphSyncController = class _GraphSyncController {
6378
7041
  )
6379
7042
  )
6380
7043
  );
7044
+ this.splitActorsForRegistration = CadenzaService.createMetaTask(
7045
+ "Split actors for registration",
7046
+ function* (ctx) {
7047
+ CadenzaService.debounce("meta.sync_controller.synced_resource", {
7048
+ delayMs: 3e3
7049
+ });
7050
+ const actors = ctx.actors ?? [];
7051
+ for (const actor of actors) {
7052
+ const data = {
7053
+ ...buildActorRegistrationData(actor),
7054
+ service_name: CadenzaService.serviceRegistry.serviceName
7055
+ };
7056
+ if (!data.name) {
7057
+ continue;
7058
+ }
7059
+ const registrationKey = `${data.name}|${data.version}|${data.service_name}`;
7060
+ if (this.registeredActors.has(registrationKey)) {
7061
+ continue;
7062
+ }
7063
+ yield {
7064
+ data,
7065
+ __actorRegistrationKey: registrationKey
7066
+ };
7067
+ }
7068
+ }.bind(this)
7069
+ ).then(
7070
+ (this.isCadenzaDBReady ? CadenzaService.createCadenzaDBInsertTask(
7071
+ "actor",
7072
+ {
7073
+ onConflict: {
7074
+ target: ["name", "service_name", "version"],
7075
+ action: {
7076
+ do: "nothing"
7077
+ }
7078
+ }
7079
+ },
7080
+ { concurrency: 30 }
7081
+ ) : CadenzaService.get("dbInsertActor"))?.then(
7082
+ CadenzaService.createMetaTask("Record actor registration", (ctx) => {
7083
+ if (!ctx.__syncing) {
7084
+ return;
7085
+ }
7086
+ CadenzaService.debounce("meta.sync_controller.synced_resource", {
7087
+ delayMs: 3e3
7088
+ });
7089
+ this.registeredActors.add(ctx.__actorRegistrationKey);
7090
+ return true;
7091
+ }).then(
7092
+ CadenzaService.createUniqueMetaTask(
7093
+ "Gather actor registration",
7094
+ () => true
7095
+ ).emits("meta.sync_controller.synced_actors")
7096
+ )
7097
+ )
7098
+ );
7099
+ this.registerActorTaskMapTask = CadenzaService.createMetaTask(
7100
+ "Split actor task maps",
7101
+ function* (ctx) {
7102
+ const task = ctx.task;
7103
+ if (task.hidden || !task.register) {
7104
+ return;
7105
+ }
7106
+ const metadata = getActorTaskRuntimeMetadata(task.taskFunction);
7107
+ if (!metadata?.actorName) {
7108
+ return;
7109
+ }
7110
+ const registrationKey = `${metadata.actorName}|${task.name}|${task.version}|${CadenzaService.serviceRegistry.serviceName}`;
7111
+ if (this.registeredActorTaskMaps.has(registrationKey)) {
7112
+ return;
7113
+ }
7114
+ yield {
7115
+ data: {
7116
+ actor_name: metadata.actorName,
7117
+ actor_version: 1,
7118
+ task_name: task.name,
7119
+ task_version: task.version,
7120
+ service_name: CadenzaService.serviceRegistry.serviceName,
7121
+ mode: metadata.mode,
7122
+ description: task.description ?? metadata.actorDescription ?? "",
7123
+ is_meta: metadata.actorKind === "meta" || task.isMeta === true
7124
+ },
7125
+ __actorTaskMapRegistrationKey: registrationKey
7126
+ };
7127
+ }.bind(this)
7128
+ ).then(
7129
+ (this.isCadenzaDBReady ? CadenzaService.createCadenzaDBInsertTask(
7130
+ "actor_task_map",
7131
+ {
7132
+ onConflict: {
7133
+ target: [
7134
+ "actor_name",
7135
+ "actor_version",
7136
+ "task_name",
7137
+ "task_version",
7138
+ "service_name"
7139
+ ],
7140
+ action: {
7141
+ do: "nothing"
7142
+ }
7143
+ }
7144
+ },
7145
+ { concurrency: 30 }
7146
+ ) : CadenzaService.get("dbInsertActorTaskMap"))?.then(
7147
+ CadenzaService.createMetaTask("Record actor task map registration", (ctx) => {
7148
+ if (!ctx.__syncing) {
7149
+ return;
7150
+ }
7151
+ CadenzaService.debounce("meta.sync_controller.synced_resource", {
7152
+ delayMs: 3e3
7153
+ });
7154
+ this.registeredActorTaskMaps.add(ctx.__actorTaskMapRegistrationKey);
7155
+ })
7156
+ )
7157
+ );
6381
7158
  const registerSignalTask = CadenzaService.createMetaTask(
6382
7159
  "Record signal registration",
6383
7160
  (ctx) => {
@@ -6619,12 +7396,19 @@ var GraphSyncController = class _GraphSyncController {
6619
7396
  ).then(this.splitSignalsTask);
6620
7397
  CadenzaService.registry.getAllTasks.clone().doOn("meta.sync_controller.synced_signals").then(this.splitTasksForRegistration);
6621
7398
  CadenzaService.registry.getAllRoutines.clone().doOn("meta.sync_controller.synced_tasks").then(this.splitRoutinesTask);
7399
+ CadenzaService.createMetaTask("Get all actors", (ctx) => {
7400
+ return {
7401
+ ...ctx,
7402
+ actors: CadenzaService.getAllActors()
7403
+ };
7404
+ }).doOn("meta.sync_controller.synced_tasks").then(this.splitActorsForRegistration);
6622
7405
  CadenzaService.registry.doForEachTask.clone().doOn("meta.sync_controller.synced_tasks").then(
6623
7406
  this.registerTaskMapTask,
6624
7407
  this.registerSignalToTaskMapTask,
6625
7408
  this.registerIntentToTaskMapTask,
6626
7409
  this.registerDeputyRelationshipTask
6627
7410
  );
7411
+ CadenzaService.registry.doForEachTask.clone().doOn("meta.sync_controller.synced_tasks", "meta.sync_controller.synced_actors").then(this.registerActorTaskMapTask);
6628
7412
  CadenzaService.registry.getAllRoutines.clone().doOn("meta.sync_controller.synced_routines").then(this.splitTasksInRoutines);
6629
7413
  CadenzaService.createMetaTask("Finish sync", (ctx, emit) => {
6630
7414
  emit("global.meta.sync_controller.synced", {
@@ -7011,6 +7795,14 @@ var CadenzaService = class {
7011
7795
  static get(taskName) {
7012
7796
  return import_core3.default.get(taskName);
7013
7797
  }
7798
+ static getActor(actorName) {
7799
+ const cadenzaWithActors = import_core3.default;
7800
+ return cadenzaWithActors.getActor?.(actorName);
7801
+ }
7802
+ static getAllActors() {
7803
+ const cadenzaWithActors = import_core3.default;
7804
+ return cadenzaWithActors.getAllActors?.() ?? [];
7805
+ }
7014
7806
  static getRoutine(routineName) {
7015
7807
  return import_core3.default.getRoutine(routineName);
7016
7808
  }
@@ -7576,6 +8368,14 @@ var CadenzaService = class {
7576
8368
  options.isMeta = true;
7577
8369
  this.createDatabaseService(name, schema, description, options);
7578
8370
  }
8371
+ static createActor(spec, options = {}) {
8372
+ this.bootstrap();
8373
+ return import_core3.default.createActor(spec, options);
8374
+ }
8375
+ static createActorFromDefinition(definition, options = {}) {
8376
+ this.bootstrap();
8377
+ return import_core3.default.createActorFromDefinition(definition, options);
8378
+ }
7579
8379
  /**
7580
8380
  * Creates and registers a new task with the provided name, function, and optional details.
7581
8381
  *
@@ -7993,6 +8793,7 @@ var import_core4 = require("@cadenza.io/core");
7993
8793
  var index_default = CadenzaService;
7994
8794
  // Annotate the CommonJS export names for ESM import in node:
7995
8795
  0 && (module.exports = {
8796
+ Actor,
7996
8797
  DatabaseTask,
7997
8798
  DebounceTask,
7998
8799
  DeputyTask,