@cadenza.io/service 2.8.0 → 2.9.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,225 @@ 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.socketServerActor = CadenzaService.createActor(
3350
+ {
3351
+ name: "SocketServerActor",
3352
+ description: "Holds durable socket server session state and runtime socket server handle",
3353
+ defaultKey: this.socketServerDefaultKey,
3354
+ keyResolver: (input) => this.resolveSocketServerKey(input),
3355
+ loadPolicy: "lazy",
3356
+ writeContract: "overwrite",
3357
+ initState: this.createInitialSocketServerSessionState(
3358
+ this.socketServerDefaultKey
3359
+ )
3360
+ },
3361
+ { isMeta: true }
3362
+ );
3363
+ this.socketClientActor = CadenzaService.createActor(
3364
+ {
3365
+ name: "SocketClientActor",
3366
+ description: "Holds durable socket client session state and runtime socket connection handles",
3367
+ defaultKey: "socket-client-default",
3368
+ keyResolver: (input) => this.resolveSocketClientFetchId(input),
3369
+ loadPolicy: "lazy",
3370
+ writeContract: "overwrite",
3371
+ initState: this.createInitialSocketClientSessionState()
3372
+ },
3373
+ { isMeta: true }
3374
+ );
3375
+ this.socketClientDiagnosticsActor = CadenzaService.createActor(
3376
+ {
3377
+ name: "SocketClientDiagnosticsActor",
3378
+ description: "Tracks socket client diagnostics snapshots per fetchId for transport observability",
3379
+ defaultKey: "socket-client-diagnostics",
3380
+ loadPolicy: "eager",
3381
+ writeContract: "overwrite",
3382
+ initState: {
3383
+ entries: {}
3384
+ }
3385
+ },
3386
+ { isMeta: true }
3387
+ );
3388
+ this.registerDiagnosticsTasks();
3389
+ this.registerSocketServerTasks();
3390
+ this.registerSocketClientTasks();
3335
3391
  CadenzaService.createMetaTask(
3336
3392
  "Collect socket transport diagnostics",
3337
- (ctx) => this.collectSocketTransportDiagnostics(ctx),
3393
+ this.socketClientDiagnosticsActor.task(
3394
+ ({ state, input }) => this.collectSocketTransportDiagnostics(input, state.entries),
3395
+ { mode: "read" }
3396
+ ),
3338
3397
  "Responds to distributed transport diagnostics inquiries with socket client data."
3339
3398
  ).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
3340
- CadenzaService.createMetaRoutine(
3341
- "SocketServer",
3342
- [
3343
- CadenzaService.createMetaTask("Setup SocketServer", (ctx) => {
3344
- if (!ctx.__useSocket) {
3399
+ }
3400
+ static get instance() {
3401
+ if (!this._instance) this._instance = new _SocketController();
3402
+ return this._instance;
3403
+ }
3404
+ registerDiagnosticsTasks() {
3405
+ CadenzaService.createThrottledMetaTask(
3406
+ "SocketClientDiagnosticsActor.Upsert",
3407
+ this.socketClientDiagnosticsActor.task(
3408
+ ({ state, input, setState }) => {
3409
+ const fetchId = String(input.fetchId ?? "").trim();
3410
+ if (!fetchId) {
3345
3411
  return;
3346
3412
  }
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
3413
+ const now = Date.now();
3414
+ const entries = { ...state.entries };
3415
+ const existing = entries[fetchId];
3416
+ const base = existing ? {
3417
+ ...existing,
3418
+ errorHistory: [...existing.errorHistory]
3419
+ } : {
3420
+ fetchId,
3421
+ serviceName: String(input.serviceName ?? ""),
3422
+ url: String(input.url ?? ""),
3423
+ socketId: null,
3424
+ connected: false,
3425
+ handshake: false,
3426
+ reconnectAttempts: 0,
3427
+ connectErrors: 0,
3428
+ reconnectErrors: 0,
3429
+ socketErrors: 0,
3430
+ pendingDelegations: 0,
3431
+ pendingTimers: 0,
3432
+ destroyed: false,
3433
+ lastHandshakeAt: null,
3434
+ lastHandshakeError: null,
3435
+ lastDisconnectAt: null,
3436
+ lastError: null,
3437
+ lastErrorAt: 0,
3438
+ errorHistory: [],
3439
+ updatedAt: now
3440
+ };
3441
+ if (input.serviceName !== void 0) {
3442
+ base.serviceName = String(input.serviceName);
3443
+ }
3444
+ if (input.url !== void 0) {
3445
+ base.url = String(input.url);
3446
+ }
3447
+ const patch = input.patch && typeof input.patch === "object" ? input.patch : {};
3448
+ Object.assign(base, patch);
3449
+ base.fetchId = fetchId;
3450
+ base.updatedAt = now;
3451
+ const errorMessage = input.error !== void 0 ? this.getErrorMessage(input.error) : void 0;
3452
+ if (errorMessage) {
3453
+ base.lastError = errorMessage;
3454
+ base.lastErrorAt = now;
3455
+ base.errorHistory.push({
3456
+ at: new Date(now).toISOString(),
3457
+ message: errorMessage
3458
+ });
3459
+ if (base.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
3460
+ base.errorHistory.splice(
3461
+ 0,
3462
+ base.errorHistory.length - this.diagnosticsErrorHistoryLimit
3463
+ );
3357
3464
  }
3465
+ }
3466
+ entries[fetchId] = base;
3467
+ this.pruneDiagnosticsEntries(entries, now);
3468
+ setState({ entries });
3469
+ },
3470
+ { mode: "write" }
3471
+ ),
3472
+ (context) => String(context?.fetchId ?? "default"),
3473
+ "Upserts socket client diagnostics in actor state."
3474
+ ).doOn("meta.socket_client.diagnostics_upsert_requested");
3475
+ }
3476
+ registerSocketServerTasks() {
3477
+ CadenzaService.createThrottledMetaTask(
3478
+ "SocketServerActor.PatchSession",
3479
+ this.socketServerActor.task(
3480
+ ({ state, input, setState }) => {
3481
+ const patch = input.patch && typeof input.patch === "object" ? input.patch : {};
3482
+ setState({
3483
+ ...state,
3484
+ ...patch,
3485
+ updatedAt: Date.now()
3486
+ });
3487
+ },
3488
+ { mode: "write" }
3489
+ ),
3490
+ (context) => String(context?.serverKey ?? this.socketServerDefaultKey),
3491
+ "Applies partial durable session updates for socket server actor."
3492
+ ).doOn("meta.socket_server.session_patch_requested");
3493
+ CadenzaService.createMetaTask(
3494
+ "SocketServerActor.ClearRuntime",
3495
+ this.socketServerActor.task(
3496
+ ({ setRuntimeState }) => {
3497
+ setRuntimeState(null);
3498
+ },
3499
+ { mode: "write" }
3500
+ ),
3501
+ "Clears socket server runtime handle after shutdown."
3502
+ ).doOn("meta.socket_server.runtime_clear_requested");
3503
+ const setupSocketServerTask = CadenzaService.createMetaTask(
3504
+ "Setup SocketServer",
3505
+ this.socketServerActor.task(
3506
+ ({ state, runtimeState, input, actor, setState, setRuntimeState, emit }) => {
3507
+ const serverKey = this.resolveSocketServerKey(input) ?? actor.key ?? this.socketServerDefaultKey;
3508
+ const shouldUseSocket = Boolean(input.__useSocket);
3509
+ if (!shouldUseSocket) {
3510
+ this.destroySocketServerRuntimeHandle(runtimeState);
3511
+ setRuntimeState(null);
3512
+ setState({
3513
+ ...state,
3514
+ serverKey,
3515
+ useSocket: false,
3516
+ status: "inactive",
3517
+ connectionCount: 0,
3518
+ lastShutdownAt: (/* @__PURE__ */ new Date()).toISOString(),
3519
+ updatedAt: Date.now()
3520
+ });
3521
+ return;
3522
+ }
3523
+ let runtimeHandle = runtimeState;
3524
+ if (!runtimeHandle) {
3525
+ runtimeHandle = this.createSocketServerRuntimeHandleFromContext(input);
3526
+ setRuntimeState(runtimeHandle);
3527
+ }
3528
+ const profile = String(input.__securityProfile ?? state.securityProfile ?? "medium");
3529
+ const networkType = String(input.__networkType ?? state.networkType ?? "internal");
3530
+ const schedulePatch = (patch) => {
3531
+ CadenzaService.emit("meta.socket_server.session_patch_requested", {
3532
+ serverKey,
3533
+ patch
3534
+ });
3535
+ };
3536
+ if (runtimeHandle.initialized) {
3537
+ schedulePatch({
3538
+ status: "active",
3539
+ useSocket: true,
3540
+ securityProfile: profile,
3541
+ networkType,
3542
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3543
+ lastStartedAt: state.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString()
3544
+ });
3545
+ return;
3546
+ }
3547
+ const server = runtimeHandle.server;
3548
+ runtimeHandle.initialized = true;
3549
+ setState({
3550
+ ...state,
3551
+ serverKey,
3552
+ useSocket: true,
3553
+ status: "active",
3554
+ securityProfile: profile,
3555
+ networkType,
3556
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3557
+ lastStartedAt: state.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
3558
+ updatedAt: Date.now()
3358
3559
  });
3359
- const profile = ctx.__securityProfile ?? "medium";
3360
3560
  server.use((socket, next) => {
3361
3561
  const origin = socket?.handshake?.headers?.origin;
3362
3562
  const allowedOrigins = ["*"];
3363
- const networkType = ctx.__networkType ?? "internal";
3364
3563
  let effectiveOrigin = origin || "unknown";
3365
3564
  if (networkType === "internal") effectiveOrigin = "internal";
3366
3565
  if (profile !== "low" && !allowedOrigins.includes(effectiveOrigin) && !allowedOrigins.includes("*")) {
@@ -3371,7 +3570,9 @@ var SocketController = class _SocketController {
3371
3570
  medium: { points: 1e4, duration: 10 },
3372
3571
  high: { points: 1e3, duration: 60, blockDuration: 300 }
3373
3572
  };
3374
- const limiter = new import_rate_limiter_flexible2.RateLimiterMemory(limiterOptions[profile]);
3573
+ const limiter = new import_rate_limiter_flexible2.RateLimiterMemory(
3574
+ limiterOptions[profile] ?? limiterOptions.medium
3575
+ );
3375
3576
  const clientKey = socket?.handshake?.address || "unknown";
3376
3577
  socket.use((packet, packetNext) => {
3377
3578
  limiter.consume(clientKey).then(() => packetNext()).catch((rej) => {
@@ -3406,116 +3607,111 @@ var SocketController = class _SocketController {
3406
3607
  });
3407
3608
  next();
3408
3609
  });
3409
- if (!server) {
3410
- CadenzaService.log("Socket setup error: No server", {}, "error");
3411
- return { ...ctx, __error: "No server", errored: true };
3412
- }
3413
3610
  server.on("connection", (ws) => {
3611
+ runtimeHandle.connectedSocketIds.add(ws.id);
3612
+ schedulePatch({
3613
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3614
+ lastConnectedAt: (/* @__PURE__ */ new Date()).toISOString(),
3615
+ status: "active"
3616
+ });
3414
3617
  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
- }
3618
+ ws.on("handshake", (ctx, callback) => {
3619
+ CadenzaService.log("SocketServer: New connection", {
3620
+ ...ctx,
3621
+ socketId: ws.id
3622
+ });
3623
+ callback({
3624
+ status: "success",
3625
+ serviceName: CadenzaService.serviceRegistry.serviceName
3626
+ });
3627
+ if (ctx.isFrontend) {
3628
+ const fetchId = `browser:${ctx.serviceInstanceId}`;
3629
+ CadenzaService.createMetaTask(
3630
+ `Transmit signal to ${fetchId}`,
3631
+ (c, emitter) => {
3632
+ if (c.__signalName === void 0) {
3633
+ return;
3634
+ }
3635
+ ws.emit("signal", c);
3636
+ if (c.__routineExecId) {
3637
+ emitter(`meta.socket_client.transmitted:${c.__routineExecId}`, {});
3441
3638
  }
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
3639
  },
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
- });
3640
+ "Transmit frontend bound signal through active websocket."
3641
+ ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal("meta.socket_client.transmitted");
3481
3642
  }
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
- });
3643
+ CadenzaService.emit("meta.socket.handshake", ctx);
3644
+ });
3645
+ ws.on("delegation", (ctx, callback) => {
3646
+ const deputyExecId = ctx.__metadata.__deputyExecId;
3647
+ CadenzaService.createEphemeralMetaTask(
3648
+ "Resolve delegation",
3649
+ (delegationCtx) => {
3650
+ callback(delegationCtx);
3651
+ },
3652
+ "Resolves a delegation request using client callback.",
3653
+ { register: false }
3654
+ ).doOn(`meta.node.graph_completed:${deputyExecId}`).emits(`meta.socket.delegation_resolved:${deputyExecId}`);
3655
+ CadenzaService.createEphemeralMetaTask(
3656
+ "Delegation progress update",
3657
+ (progressCtx) => {
3658
+ if (progressCtx.__progress !== void 0) {
3659
+ ws.emit("delegation_progress", progressCtx);
3660
+ }
3661
+ },
3662
+ "Updates delegation progress to client.",
3663
+ {
3664
+ once: false,
3665
+ destroyCondition: (progressCtx) => progressCtx.data.progress === 1 || progressCtx.data?.progress === void 0,
3666
+ register: false
3503
3667
  }
3668
+ ).doOn(
3669
+ `meta.node.routine_execution_progress:${deputyExecId}`,
3670
+ `meta.node.graph_completed:${deputyExecId}`
3671
+ ).emitsOnFail(`meta.socket.progress_failed:${deputyExecId}`);
3672
+ CadenzaService.emit("meta.socket.delegation_requested", {
3673
+ ...ctx,
3674
+ __name: ctx.__remoteRoutineName
3675
+ });
3676
+ });
3677
+ ws.on("signal", (ctx, callback) => {
3678
+ if (CadenzaService.signalBroker.listObservedSignals().includes(ctx.__signalName)) {
3679
+ callback({
3680
+ __status: "success",
3681
+ __signalName: ctx.__signalName
3682
+ });
3683
+ CadenzaService.emit(ctx.__signalName, ctx);
3684
+ } else {
3685
+ CadenzaService.log(
3686
+ `No such signal ${ctx.__signalName} on ${ctx.__serviceName}`,
3687
+ "warning"
3688
+ );
3689
+ callback({
3690
+ ...ctx,
3691
+ __status: "error",
3692
+ __error: `No such signal: ${ctx.__signalName}`,
3693
+ errored: true
3694
+ });
3504
3695
  }
3505
- );
3696
+ });
3506
3697
  ws.on(
3507
3698
  "status_check",
3508
- (ctx2, callback) => {
3699
+ (ctx, callback) => {
3509
3700
  CadenzaService.createEphemeralMetaTask(
3510
3701
  "Resolve status check",
3511
3702
  callback,
3512
3703
  "Resolves a status check request",
3513
3704
  { register: false }
3514
3705
  ).doAfter(CadenzaService.serviceRegistry.getStatusTask);
3515
- CadenzaService.emit("meta.socket.status_check_requested", ctx2);
3706
+ CadenzaService.emit("meta.socket.status_check_requested", ctx);
3516
3707
  }
3517
3708
  );
3518
3709
  ws.on("disconnect", () => {
3710
+ runtimeHandle.connectedSocketIds.delete(ws.id);
3711
+ schedulePatch({
3712
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3713
+ lastDisconnectedAt: (/* @__PURE__ */ new Date()).toISOString()
3714
+ });
3519
3715
  CadenzaService.log(
3520
3716
  "Socket client disconnected",
3521
3717
  { socketId: ws.id },
@@ -3525,514 +3721,888 @@ var SocketController = class _SocketController {
3525
3721
  __wsId: ws.id
3526
3722
  });
3527
3723
  });
3528
- } catch (e) {
3724
+ } catch (error) {
3529
3725
  CadenzaService.log(
3530
3726
  "SocketServer: Error in socket event",
3531
- { error: e },
3727
+ { error },
3532
3728
  "error"
3533
3729
  );
3534
3730
  }
3535
3731
  CadenzaService.emit("meta.socket.connected", { __wsId: ws.id });
3536
3732
  });
3537
- CadenzaService.createMetaTask(
3538
- "Broadcast status",
3539
- (ctx2) => server.emit("status_update", ctx2),
3733
+ runtimeHandle.broadcastStatusTask = CadenzaService.createMetaTask(
3734
+ `Broadcast status ${serverKey}`,
3735
+ (ctx) => server.emit("status_update", ctx),
3540
3736
  "Broadcasts the status of the server to all clients"
3541
3737
  ).doOn("meta.service.updated");
3542
- CadenzaService.createMetaTask(
3543
- "Shutdown SocketServer",
3544
- () => server.close(),
3738
+ runtimeHandle.shutdownTask = CadenzaService.createMetaTask(
3739
+ `Shutdown SocketServer ${serverKey}`,
3740
+ async () => {
3741
+ this.destroySocketServerRuntimeHandle(runtimeHandle);
3742
+ CadenzaService.emit("meta.socket_server.runtime_clear_requested", {
3743
+ serverKey
3744
+ });
3745
+ CadenzaService.emit("meta.socket_server.session_patch_requested", {
3746
+ serverKey,
3747
+ patch: {
3748
+ useSocket: false,
3749
+ status: "shutdown",
3750
+ connectionCount: 0,
3751
+ lastShutdownAt: (/* @__PURE__ */ new Date()).toISOString()
3752
+ }
3753
+ });
3754
+ },
3545
3755
  "Shuts down the socket server"
3546
3756
  ).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");
3757
+ return true;
3758
+ },
3759
+ { mode: "write" }
3760
+ ),
3761
+ "Initializes socket server runtime through actor state."
3762
+ );
3763
+ setupSocketServerTask.doOn("global.meta.rest.network_configured");
3764
+ }
3765
+ registerSocketClientTasks() {
3766
+ CadenzaService.createThrottledMetaTask(
3767
+ "SocketClientActor.ApplySessionOperation",
3768
+ this.socketClientActor.task(
3769
+ ({ state, input, setState }) => {
3770
+ const operation = String(
3771
+ input.operation ?? "transmit"
3772
+ );
3773
+ const patch = input.patch && typeof input.patch === "object" ? input.patch : {};
3774
+ let next = {
3775
+ ...state,
3776
+ ...patch,
3777
+ communicationTypes: patch.communicationTypes !== void 0 ? this.normalizeCommunicationTypes(patch.communicationTypes) : state.communicationTypes,
3778
+ updatedAt: Date.now()
3779
+ };
3780
+ if (input.serviceName !== void 0) {
3781
+ next.serviceName = String(input.serviceName);
3782
+ }
3783
+ if (input.serviceAddress !== void 0) {
3784
+ next.serviceAddress = String(input.serviceAddress);
3785
+ }
3786
+ if (input.serviceInstanceId !== void 0) {
3787
+ next.serviceInstanceId = String(input.serviceInstanceId);
3788
+ }
3789
+ if (input.protocol !== void 0) {
3790
+ next.protocol = String(input.protocol);
3791
+ }
3792
+ if (input.url !== void 0) {
3793
+ next.url = String(input.url);
3794
+ }
3795
+ if (input.servicePort !== void 0) {
3796
+ next.servicePort = Number(input.servicePort);
3797
+ }
3798
+ if (input.fetchId !== void 0) {
3799
+ next.fetchId = String(input.fetchId);
3800
+ }
3801
+ if (operation === "connect") {
3802
+ next.destroyed = false;
3803
+ } else if (operation === "handshake") {
3804
+ next.destroyed = false;
3805
+ next.connected = patch.connected ?? true;
3806
+ next.handshake = patch.handshake ?? true;
3807
+ } else if (operation === "shutdown") {
3808
+ next.connected = false;
3809
+ next.handshake = false;
3810
+ next.destroyed = true;
3811
+ next.pendingDelegations = 0;
3812
+ next.pendingTimers = 0;
3813
+ }
3814
+ setState(next);
3815
+ return next;
3816
+ },
3817
+ { mode: "write" }
3818
+ ),
3819
+ (context) => String(this.resolveSocketClientFetchId(context ?? {}) ?? "default"),
3820
+ "Applies socket client session operation patch in actor durable state."
3821
+ ).doOn("meta.socket_client.session_operation_requested");
3822
+ CadenzaService.createMetaTask(
3823
+ "SocketClientActor.ClearRuntime",
3824
+ this.socketClientActor.task(
3825
+ ({ setRuntimeState }) => {
3826
+ setRuntimeState(null);
3827
+ },
3828
+ { mode: "write" }
3829
+ ),
3830
+ "Clears socket client runtime handle."
3831
+ ).doOn("meta.socket_client.runtime_clear_requested");
3552
3832
  CadenzaService.createMetaTask(
3553
3833
  "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,
3834
+ this.socketClientActor.task(
3835
+ ({ state, runtimeState, input, setState, setRuntimeState, emit }) => {
3836
+ const serviceInstanceId = String(input.serviceInstanceId ?? "");
3837
+ const communicationTypes = this.normalizeCommunicationTypes(
3838
+ input.communicationTypes
3839
+ );
3840
+ const serviceName = String(input.serviceName ?? "");
3841
+ const serviceAddress = String(input.serviceAddress ?? "");
3842
+ const protocol = String(input.protocol ?? "http");
3843
+ const normalizedPort = this.resolveServicePort(protocol, input.servicePort);
3844
+ if (!serviceAddress || !normalizedPort) {
3845
+ CadenzaService.log(
3846
+ "Socket client setup skipped due to missing address/port",
3847
+ {
3611
3848
  serviceName,
3612
- URL
3849
+ serviceAddress,
3850
+ servicePort: input.servicePort,
3851
+ protocol
3852
+ },
3853
+ "warning"
3854
+ );
3855
+ return false;
3856
+ }
3857
+ const socketProtocol = protocol === "https" ? "wss" : "ws";
3858
+ const url = `${socketProtocol}://${serviceAddress}:${normalizedPort}`;
3859
+ const fetchId = `${serviceAddress}_${normalizedPort}`;
3860
+ const applySessionOperation = (operation, patch = {}) => {
3861
+ CadenzaService.emit("meta.socket_client.session_operation_requested", {
3862
+ fetchId,
3863
+ operation,
3864
+ patch,
3865
+ serviceInstanceId,
3866
+ communicationTypes,
3867
+ serviceName,
3868
+ serviceAddress,
3869
+ servicePort: normalizedPort,
3870
+ protocol,
3871
+ url
3872
+ });
3873
+ };
3874
+ const upsertDiagnostics = (patch, error) => {
3875
+ CadenzaService.emit("meta.socket_client.diagnostics_upsert_requested", {
3876
+ fetchId,
3877
+ serviceName,
3878
+ url,
3879
+ patch,
3880
+ error
3881
+ });
3882
+ };
3883
+ setState({
3884
+ ...state,
3885
+ fetchId,
3886
+ serviceInstanceId,
3887
+ communicationTypes,
3888
+ serviceName,
3889
+ serviceAddress,
3890
+ servicePort: normalizedPort,
3891
+ protocol,
3892
+ url,
3893
+ destroyed: false,
3894
+ updatedAt: Date.now()
3895
+ });
3896
+ let runtimeHandle = runtimeState;
3897
+ if (!runtimeHandle || runtimeHandle.url !== url) {
3898
+ this.destroySocketClientRuntimeHandle(runtimeHandle);
3899
+ runtimeHandle = this.createSocketClientRuntimeHandle(url);
3900
+ setRuntimeState(runtimeHandle);
3901
+ }
3902
+ upsertDiagnostics({
3903
+ destroyed: false,
3904
+ connected: false,
3905
+ handshake: false,
3906
+ socketId: runtimeHandle.socket.id ?? null
3907
+ });
3908
+ applySessionOperation("connect", {
3909
+ destroyed: false,
3910
+ connected: false,
3911
+ handshake: false,
3912
+ socketId: runtimeHandle.socket.id ?? null,
3913
+ pendingDelegations: runtimeHandle.pendingDelegationIds.size,
3914
+ pendingTimers: runtimeHandle.pendingTimers.size,
3915
+ errorCount: runtimeHandle.errorCount
3916
+ });
3917
+ if (runtimeHandle.initialized) {
3918
+ return true;
3919
+ }
3920
+ runtimeHandle.initialized = true;
3921
+ runtimeHandle.handshake = false;
3922
+ runtimeHandle.errorCount = 0;
3923
+ const syncPendingCounts = () => {
3924
+ const pendingDelegations = runtimeHandle.pendingDelegationIds.size;
3925
+ const pendingTimers = runtimeHandle.pendingTimers.size;
3926
+ upsertDiagnostics({
3927
+ pendingDelegations,
3928
+ pendingTimers
3929
+ });
3930
+ applySessionOperation("delegate", {
3931
+ pendingDelegations,
3932
+ pendingTimers
3933
+ });
3934
+ };
3935
+ runtimeHandle.emitWhenReady = (event, data, timeoutMs = 6e4, ack) => {
3936
+ return new Promise((resolve) => {
3937
+ const parsedTimeout = Number(timeoutMs);
3938
+ const normalizedTimeoutMs = Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? Math.trunc(parsedTimeout) : 6e4;
3939
+ let timer = null;
3940
+ let settled = false;
3941
+ const clearPendingTimer = () => {
3942
+ if (!timer) {
3943
+ return;
3944
+ }
3945
+ clearTimeout(timer);
3946
+ runtimeHandle.pendingTimers.delete(timer);
3947
+ syncPendingCounts();
3948
+ timer = null;
3949
+ };
3950
+ const settle = (response) => {
3951
+ if (settled) {
3952
+ return;
3953
+ }
3954
+ settled = true;
3955
+ clearPendingTimer();
3956
+ if (ack) ack(response);
3957
+ resolve(response);
3958
+ };
3959
+ const resolveWithError = (errorMessage, fallbackError) => {
3960
+ settle({
3961
+ ...data,
3962
+ errored: true,
3963
+ __error: errorMessage,
3964
+ error: fallbackError instanceof Error ? fallbackError.message : errorMessage,
3965
+ socketId: runtimeHandle.socket.id,
3966
+ serviceName,
3967
+ url
3968
+ });
3969
+ };
3970
+ const tryEmit = async () => {
3971
+ const waitResult = await waitForSocketConnection(
3972
+ runtimeHandle.socket,
3973
+ normalizedTimeoutMs + 10,
3974
+ (reason, error) => {
3975
+ if (reason === "connect_timeout") {
3976
+ return `Socket connect timed out before '${event}'`;
3977
+ }
3978
+ if (reason === "connect_error") {
3979
+ const errMessage = error instanceof Error ? error.message : String(error);
3980
+ return `Socket connect error before '${event}': ${errMessage}`;
3981
+ }
3982
+ return `Socket disconnected before '${event}'`;
3983
+ }
3984
+ );
3985
+ if (!waitResult.ok) {
3986
+ CadenzaService.log(
3987
+ waitResult.error,
3988
+ {
3989
+ socketId: runtimeHandle.socket.id,
3990
+ serviceName,
3991
+ url,
3992
+ event
3993
+ },
3994
+ "error"
3995
+ );
3996
+ upsertDiagnostics({}, waitResult.error);
3997
+ resolveWithError(waitResult.error);
3998
+ return;
3999
+ }
4000
+ timer = setTimeout(() => {
4001
+ if (settled) {
4002
+ return;
4003
+ }
4004
+ clearPendingTimer();
4005
+ const message = `Socket event '${event}' timed out`;
4006
+ CadenzaService.log(
4007
+ message,
4008
+ { socketId: runtimeHandle.socket.id, serviceName, url },
4009
+ "error"
4010
+ );
4011
+ upsertDiagnostics(
4012
+ {
4013
+ lastHandshakeError: message
4014
+ },
4015
+ message
4016
+ );
4017
+ applySessionOperation("transmit", {
4018
+ lastHandshakeError: message
4019
+ });
4020
+ resolveWithError(message);
4021
+ }, normalizedTimeoutMs + 10);
4022
+ runtimeHandle.pendingTimers.add(timer);
4023
+ syncPendingCounts();
4024
+ runtimeHandle.socket.timeout(normalizedTimeoutMs).emit(event, data, (err, response) => {
4025
+ if (err) {
4026
+ CadenzaService.log(
4027
+ "Socket timeout.",
4028
+ {
4029
+ event,
4030
+ error: err.message,
4031
+ socketId: runtimeHandle.socket.id,
4032
+ serviceName
4033
+ },
4034
+ "warning"
4035
+ );
4036
+ upsertDiagnostics(
4037
+ {
4038
+ lastHandshakeError: err.message
4039
+ },
4040
+ err
4041
+ );
4042
+ applySessionOperation("transmit", {
4043
+ lastHandshakeError: err.message
4044
+ });
4045
+ response = {
4046
+ __error: `Timeout error: ${err}`,
4047
+ errored: true,
4048
+ ...data
4049
+ };
4050
+ }
4051
+ settle(response);
4052
+ });
4053
+ };
4054
+ void tryEmit().catch((error) => {
4055
+ CadenzaService.log(
4056
+ "Socket emit failed unexpectedly",
4057
+ {
4058
+ event,
4059
+ error: error instanceof Error ? error.message : String(error),
4060
+ socketId: runtimeHandle.socket.id,
4061
+ serviceName,
4062
+ url
4063
+ },
4064
+ "error"
4065
+ );
4066
+ const message = `Socket event '${event}' failed`;
4067
+ upsertDiagnostics(
4068
+ {
4069
+ lastHandshakeError: error instanceof Error ? error.message : String(error)
4070
+ },
4071
+ error
4072
+ );
4073
+ applySessionOperation("transmit", {
4074
+ lastHandshakeError: error instanceof Error ? error.message : String(error)
4075
+ });
4076
+ resolveWithError(message, error);
4077
+ });
4078
+ });
4079
+ };
4080
+ const socket = runtimeHandle.socket;
4081
+ socket.on("connect", () => {
4082
+ if (runtimeHandle.handshake) return;
4083
+ upsertDiagnostics({
4084
+ connected: true,
4085
+ destroyed: false,
4086
+ socketId: socket.id ?? null
4087
+ });
4088
+ applySessionOperation("connect", {
4089
+ connected: true,
4090
+ destroyed: false,
4091
+ socketId: socket.id ?? null
4092
+ });
4093
+ CadenzaService.emit(`meta.socket_client.connected:${fetchId}`, input);
4094
+ });
4095
+ socket.on("delegation_progress", (delegationCtx) => {
4096
+ CadenzaService.emit(
4097
+ `meta.socket_client.delegation_progress:${delegationCtx.__metadata.__deputyExecId}`,
4098
+ delegationCtx
4099
+ );
4100
+ });
4101
+ socket.on("signal", (signalCtx) => {
4102
+ if (CadenzaService.signalBroker.listObservedSignals().includes(signalCtx.__signalName)) {
4103
+ CadenzaService.emit(signalCtx.__signalName, signalCtx);
4104
+ }
4105
+ });
4106
+ socket.on("status_update", (status) => {
4107
+ CadenzaService.emit("meta.socket_client.status_received", status);
4108
+ });
4109
+ socket.on("connect_error", (err) => {
4110
+ runtimeHandle.handshake = false;
4111
+ upsertDiagnostics(
4112
+ {
4113
+ connected: false,
4114
+ handshake: false,
4115
+ connectErrors: state.connectErrors + 1,
4116
+ lastHandshakeError: err.message
4117
+ },
4118
+ err
4119
+ );
4120
+ applySessionOperation("connect", {
4121
+ connected: false,
4122
+ handshake: false,
4123
+ connectErrors: state.connectErrors + 1,
4124
+ lastHandshakeError: err.message
4125
+ });
4126
+ CadenzaService.log(
4127
+ "Socket connect error",
4128
+ {
4129
+ error: err.message,
4130
+ serviceName,
4131
+ socketId: socket.id,
4132
+ url
4133
+ },
4134
+ "error"
4135
+ );
4136
+ CadenzaService.emit(`meta.socket_client.connect_error:${fetchId}`, err);
4137
+ });
4138
+ socket.on("reconnect_attempt", (attempt) => {
4139
+ upsertDiagnostics({ reconnectAttempts: attempt });
4140
+ applySessionOperation("connect", {
4141
+ reconnectAttempts: attempt
4142
+ });
4143
+ CadenzaService.log(`Reconnect attempt: ${attempt}`);
4144
+ });
4145
+ socket.on("reconnect", (attempt) => {
4146
+ upsertDiagnostics({ connected: true });
4147
+ applySessionOperation("connect", {
4148
+ connected: true
4149
+ });
4150
+ CadenzaService.log(`Socket reconnected after ${attempt} tries`, {
4151
+ socketId: socket.id,
4152
+ url,
4153
+ serviceName
4154
+ });
4155
+ });
4156
+ socket.on("reconnect_error", (err) => {
4157
+ runtimeHandle.handshake = false;
4158
+ upsertDiagnostics(
4159
+ {
4160
+ connected: false,
4161
+ handshake: false,
4162
+ reconnectErrors: state.reconnectErrors + 1,
4163
+ lastHandshakeError: err.message
4164
+ },
4165
+ err
4166
+ );
4167
+ applySessionOperation("connect", {
4168
+ connected: false,
4169
+ handshake: false,
4170
+ reconnectErrors: state.reconnectErrors + 1,
4171
+ lastHandshakeError: err.message
4172
+ });
4173
+ CadenzaService.log(
4174
+ "Socket reconnect failed.",
4175
+ { error: err.message, serviceName, url, socketId: socket.id },
4176
+ "warning"
4177
+ );
4178
+ });
4179
+ socket.on("error", (err) => {
4180
+ runtimeHandle.errorCount += 1;
4181
+ upsertDiagnostics(
4182
+ {
4183
+ socketErrors: state.socketErrors + 1,
4184
+ lastHandshakeError: this.getErrorMessage(err)
4185
+ },
4186
+ err
4187
+ );
4188
+ applySessionOperation("transmit", {
4189
+ socketErrors: state.socketErrors + 1,
4190
+ errorCount: runtimeHandle.errorCount,
4191
+ lastHandshakeError: this.getErrorMessage(err)
4192
+ });
4193
+ CadenzaService.log(
4194
+ "Socket error",
4195
+ { error: err, socketId: socket.id, url, serviceName },
4196
+ "error"
4197
+ );
4198
+ CadenzaService.emit("meta.socket_client.error", err);
4199
+ });
4200
+ socket.on("disconnect", () => {
4201
+ const disconnectedAt = (/* @__PURE__ */ new Date()).toISOString();
4202
+ upsertDiagnostics({
4203
+ connected: false,
4204
+ handshake: false,
4205
+ lastDisconnectAt: disconnectedAt
4206
+ });
4207
+ applySessionOperation("connect", {
4208
+ connected: false,
4209
+ handshake: false,
4210
+ lastDisconnectAt: disconnectedAt
4211
+ });
4212
+ CadenzaService.log(
4213
+ "Socket disconnected.",
4214
+ { url, serviceName, socketId: socket.id },
4215
+ "warning"
4216
+ );
4217
+ CadenzaService.emit(`meta.socket_client.disconnected:${fetchId}`, {
4218
+ serviceName,
4219
+ serviceAddress,
4220
+ servicePort: normalizedPort
4221
+ });
4222
+ runtimeHandle.handshake = false;
4223
+ });
4224
+ socket.connect();
4225
+ runtimeHandle.handshakeTask = CadenzaService.createMetaTask(
4226
+ `Socket handshake with ${url}`,
4227
+ async (_ctx, emitter) => {
4228
+ if (runtimeHandle.handshake) return;
4229
+ runtimeHandle.handshake = true;
4230
+ upsertDiagnostics({
4231
+ handshake: true
3613
4232
  });
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}`;
4233
+ applySessionOperation("handshake", {
4234
+ handshake: true
4235
+ });
4236
+ await runtimeHandle.emitWhenReady?.(
4237
+ "handshake",
4238
+ {
4239
+ serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
4240
+ serviceName: CadenzaService.serviceRegistry.serviceName,
4241
+ isFrontend: isBrowser,
4242
+ __status: "success"
4243
+ },
4244
+ 1e4,
4245
+ (result) => {
4246
+ if (result.status === "success") {
4247
+ const handshakeAt = (/* @__PURE__ */ new Date()).toISOString();
4248
+ upsertDiagnostics({
4249
+ connected: true,
4250
+ handshake: true,
4251
+ lastHandshakeAt: handshakeAt,
4252
+ lastHandshakeError: null,
4253
+ socketId: socket.id ?? null
4254
+ });
4255
+ applySessionOperation("handshake", {
4256
+ connected: true,
4257
+ handshake: true,
4258
+ lastHandshakeAt: handshakeAt,
4259
+ lastHandshakeError: null,
4260
+ socketId: socket.id ?? null
4261
+ });
4262
+ CadenzaService.log("Socket client connected", {
4263
+ result,
4264
+ serviceName,
4265
+ socketId: socket.id,
4266
+ url
4267
+ });
4268
+ } else {
4269
+ const errorMessage = result?.__error ?? result?.error ?? "Socket handshake failed";
4270
+ upsertDiagnostics(
4271
+ {
4272
+ connected: false,
4273
+ handshake: false,
4274
+ lastHandshakeError: errorMessage
4275
+ },
4276
+ errorMessage
4277
+ );
4278
+ applySessionOperation("handshake", {
4279
+ connected: false,
4280
+ handshake: false,
4281
+ lastHandshakeError: errorMessage
4282
+ });
4283
+ CadenzaService.log(
4284
+ "Socket handshake failed",
4285
+ { result, serviceName, socketId: socket.id, url },
4286
+ "warning"
4287
+ );
3627
4288
  }
3628
- return `Socket disconnected before '${event}'`;
4289
+ void emitter;
3629
4290
  }
3630
4291
  );
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,
3639
- serviceName,
3640
- URL,
3641
- waitResult.error
3642
- );
3643
- resolveWithError(waitResult.error);
4292
+ },
4293
+ "Handshakes with socket server"
4294
+ ).doOn(`meta.socket_client.connected:${fetchId}`);
4295
+ runtimeHandle.delegateTask = CadenzaService.createMetaTask(
4296
+ `Delegate flow to Socket service ${url}`,
4297
+ async (delegateCtx, emitter) => {
4298
+ if (delegateCtx.__remoteRoutineName === void 0) {
3644
4299
  return;
3645
4300
  }
3646
- let timer = null;
3647
- if (timeoutMs !== 0) {
3648
- timer = setTimeout(() => {
3649
- if (timer) {
3650
- pendingTimers.delete(timer);
3651
- syncPendingCounts();
3652
- timer = null;
3653
- }
3654
- CadenzaService.log(
3655
- `Socket event '${event}' timed out`,
3656
- { socketId: socket?.id, serviceName, URL },
3657
- "error"
3658
- );
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);
4301
+ delete delegateCtx.__isSubMeta;
4302
+ delete delegateCtx.__broadcast;
4303
+ const deputyExecId = delegateCtx.__metadata?.__deputyExecId;
4304
+ const requestSentAt = Date.now();
4305
+ if (deputyExecId) {
4306
+ runtimeHandle.pendingDelegationIds.add(deputyExecId);
3668
4307
  syncPendingCounts();
3669
4308
  }
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;
4309
+ try {
4310
+ const resultContext = await runtimeHandle.emitWhenReady?.(
4311
+ "delegation",
4312
+ delegateCtx,
4313
+ delegateCtx.__timeout ?? 6e4
4314
+ ) ?? {
4315
+ errored: true,
4316
+ __error: "Socket delegation returned no response"
4317
+ };
4318
+ const requestDuration = Date.now() - requestSentAt;
4319
+ const metadata = resultContext.__metadata;
4320
+ delete resultContext.__metadata;
4321
+ if (deputyExecId) {
4322
+ emitter(`meta.socket_client.delegated:${deputyExecId}`, {
4323
+ ...resultContext,
4324
+ ...metadata,
4325
+ __requestDuration: requestDuration
4326
+ });
3683
4327
  }
3684
- if (err) {
3685
- CadenzaService.log(
3686
- "Socket timeout.",
4328
+ if (resultContext?.errored || resultContext?.failed) {
4329
+ const errorMessage = resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed";
4330
+ upsertDiagnostics(
3687
4331
  {
3688
- event,
3689
- error: err.message,
3690
- socketId: socket?.id,
3691
- serviceName
4332
+ lastHandshakeError: String(errorMessage)
3692
4333
  },
3693
- "warning"
3694
- );
3695
- this.recordSocketClientError(
3696
- fetchId,
3697
- serviceName,
3698
- URL,
3699
- err
4334
+ errorMessage
3700
4335
  );
3701
- response = {
3702
- __error: `Timeout error: ${err}`,
3703
- errored: true,
3704
- ...ctx,
3705
- ...ctx.__metadata
3706
- };
4336
+ applySessionOperation("delegate", {
4337
+ lastHandshakeError: String(errorMessage)
4338
+ });
3707
4339
  }
3708
- if (ack) ack(response);
3709
- resolve(response);
3710
- });
3711
- };
3712
- void tryEmit();
3713
- });
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
3767
- });
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
3810
- });
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",
3823
- {
3824
- serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
3825
- serviceName: CadenzaService.serviceRegistry.serviceName,
3826
- isFrontend: isBrowser,
3827
- __status: "success"
3828
- },
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
4340
+ return resultContext;
4341
+ } catch (error) {
4342
+ const message = error instanceof Error ? error.message : String(error);
4343
+ const failedContext = {
4344
+ errored: true,
4345
+ __error: message
4346
+ };
4347
+ if (deputyExecId) {
4348
+ emitter(`meta.socket_client.delegated:${deputyExecId}`, {
4349
+ ...failedContext,
4350
+ __requestDuration: Date.now() - requestSentAt
3842
4351
  });
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
4352
  }
3860
- }
3861
- );
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);
4353
+ upsertDiagnostics(
4354
+ {
4355
+ lastHandshakeError: message
4356
+ },
4357
+ error
4358
+ );
4359
+ applySessionOperation("delegate", {
4360
+ lastHandshakeError: message
4361
+ });
4362
+ return failedContext;
4363
+ } finally {
4364
+ if (deputyExecId) {
4365
+ runtimeHandle.pendingDelegationIds.delete(deputyExecId);
3894
4366
  syncPendingCounts();
3895
- if (resultContext?.errored || resultContext?.failed) {
3896
- this.recordSocketClientError(
3897
- fetchId,
3898
- serviceName,
3899
- URL,
3900
- resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed"
3901
- );
3902
- }
3903
- resolve(resultContext);
3904
4367
  }
3905
- );
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
3926
- );
4368
+ }
4369
+ },
4370
+ `Delegate flow to service ${serviceName} with address ${url}`
4371
+ ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal(
4372
+ "meta.socket_client.delegated",
4373
+ "meta.socket_shutdown_requested"
4374
+ );
4375
+ runtimeHandle.transmitTask = CadenzaService.createMetaTask(
4376
+ `Transmit signal to socket server ${url}`,
4377
+ async (signalCtx, emitter) => {
4378
+ if (signalCtx.__signalName === void 0) {
4379
+ return;
4380
+ }
4381
+ delete signalCtx.__broadcast;
4382
+ const response = await runtimeHandle.emitWhenReady?.("signal", signalCtx, 5e3) ?? {
4383
+ errored: true,
4384
+ __error: "Socket signal transmission returned no response"
4385
+ };
4386
+ applySessionOperation("transmit", {});
4387
+ if (signalCtx.__routineExecId) {
4388
+ emitter(`meta.socket_client.transmitted:${signalCtx.__routineExecId}`, {
4389
+ ...response
4390
+ });
4391
+ }
4392
+ return response;
4393
+ },
4394
+ `Transmits signal to service ${serviceName} with address ${url}`
4395
+ ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal("meta.socket_client.transmitted");
4396
+ CadenzaService.createEphemeralMetaTask(
4397
+ `Shutdown SocketClient ${url}`,
4398
+ (_ctx, emitter) => {
4399
+ runtimeHandle.handshake = false;
4400
+ upsertDiagnostics({
4401
+ connected: false,
4402
+ handshake: false,
4403
+ destroyed: true,
4404
+ pendingDelegations: 0,
4405
+ pendingTimers: 0
4406
+ });
4407
+ applySessionOperation("shutdown", {
4408
+ connected: false,
4409
+ handshake: false,
4410
+ destroyed: true,
4411
+ pendingDelegations: 0,
4412
+ pendingTimers: 0
4413
+ });
4414
+ CadenzaService.log("Shutting down socket client", { url, serviceName });
4415
+ emitter(`meta.fetch.handshake_requested:${fetchId}`, {
4416
+ serviceInstanceId,
4417
+ serviceName,
4418
+ communicationTypes,
4419
+ serviceAddress,
4420
+ servicePort: normalizedPort,
4421
+ protocol,
4422
+ handshakeData: {
4423
+ instanceId: CadenzaService.serviceRegistry.serviceInstanceId,
4424
+ serviceName: CadenzaService.serviceRegistry.serviceName
3927
4425
  }
3928
- resolve(response);
3929
4426
  });
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
4427
+ for (const id of runtimeHandle.pendingDelegationIds) {
4428
+ emitter(`meta.socket_client.delegated:${id}`, {
4429
+ errored: true,
4430
+ __error: "Shutting down socket client"
4431
+ });
3962
4432
  }
3963
- });
3964
- for (const id of pendingDelegationIds) {
3965
- emit(`meta.socket_client.delegated:${id}`, {
3966
- errored: true,
3967
- __error: "Shutting down socket client"
4433
+ this.destroySocketClientRuntimeHandle(runtimeHandle);
4434
+ emitter("meta.socket_client.runtime_clear_requested", {
4435
+ fetchId
3968
4436
  });
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"
4437
+ },
4438
+ "Shuts down the socket client"
4439
+ ).doOn(
4440
+ `meta.socket_shutdown_requested:${fetchId}`,
4441
+ `meta.socket_client.disconnected:${fetchId}`,
4442
+ `meta.fetch.handshake_failed:${fetchId}`,
4443
+ `meta.socket_client.connect_error:${fetchId}`
4444
+ ).attachSignal("meta.fetch.handshake_requested").emits("meta.socket_client_shutdown_complete");
4445
+ return true;
4446
+ },
4447
+ { mode: "write" }
4448
+ ),
4449
+ "Connects to a specified socket server and wires runtime tasks."
3988
4450
  ).doOn("meta.fetch.handshake_complete").emitsOnFail("meta.socket_client.connect_failed");
3989
4451
  }
3990
- static get instance() {
3991
- if (!this._instance) this._instance = new _SocketController();
3992
- return this._instance;
4452
+ createInitialSocketServerSessionState(serverKey) {
4453
+ return {
4454
+ serverKey,
4455
+ useSocket: false,
4456
+ status: "inactive",
4457
+ securityProfile: "medium",
4458
+ networkType: "internal",
4459
+ connectionCount: 0,
4460
+ lastStartedAt: null,
4461
+ lastConnectedAt: null,
4462
+ lastDisconnectedAt: null,
4463
+ lastShutdownAt: null,
4464
+ updatedAt: 0
4465
+ };
3993
4466
  }
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;
4467
+ createInitialSocketClientSessionState() {
3999
4468
  return {
4000
- detailLevel,
4001
- includeErrorHistory,
4002
- errorHistoryLimit
4469
+ fetchId: "",
4470
+ serviceInstanceId: "",
4471
+ communicationTypes: [],
4472
+ serviceName: "",
4473
+ serviceAddress: "",
4474
+ servicePort: 0,
4475
+ protocol: "http",
4476
+ url: "",
4477
+ socketId: null,
4478
+ connected: false,
4479
+ handshake: false,
4480
+ pendingDelegations: 0,
4481
+ pendingTimers: 0,
4482
+ reconnectAttempts: 0,
4483
+ connectErrors: 0,
4484
+ reconnectErrors: 0,
4485
+ socketErrors: 0,
4486
+ errorCount: 0,
4487
+ destroyed: false,
4488
+ lastHandshakeAt: null,
4489
+ lastHandshakeError: null,
4490
+ lastDisconnectAt: null,
4491
+ updatedAt: 0
4003
4492
  };
4004
4493
  }
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;
4494
+ resolveSocketServerKey(input) {
4495
+ return String(input.serverKey ?? input.__socketServerKey ?? this.socketServerDefaultKey).trim() || this.socketServerDefaultKey;
4496
+ }
4497
+ resolveSocketClientFetchId(input) {
4498
+ const explicitFetchId = String(input.fetchId ?? "").trim();
4499
+ if (explicitFetchId) {
4500
+ return explicitFetchId;
4034
4501
  }
4035
- return state;
4502
+ const serviceAddress = String(input.serviceAddress ?? "").trim();
4503
+ const protocol = String(input.protocol ?? "http").trim();
4504
+ const port = this.resolveServicePort(protocol, input.servicePort);
4505
+ if (!serviceAddress || !port) {
4506
+ return void 0;
4507
+ }
4508
+ return `${serviceAddress}_${port}`;
4509
+ }
4510
+ resolveServicePort(protocol, rawPort) {
4511
+ if (protocol === "https") {
4512
+ return 443;
4513
+ }
4514
+ const parsed = Number(rawPort);
4515
+ if (!Number.isFinite(parsed) || parsed <= 0) {
4516
+ return void 0;
4517
+ }
4518
+ return Math.trunc(parsed);
4519
+ }
4520
+ createSocketServerRuntimeHandleFromContext(context) {
4521
+ const baseServer = context.httpsServer ?? context.httpServer;
4522
+ if (!baseServer) {
4523
+ throw new Error(
4524
+ "Socket server runtime setup requires either httpsServer or httpServer"
4525
+ );
4526
+ }
4527
+ const server = new import_socket.Server(baseServer, {
4528
+ pingInterval: 3e4,
4529
+ pingTimeout: 2e4,
4530
+ maxHttpBufferSize: 1e7,
4531
+ connectionStateRecovery: {
4532
+ maxDisconnectionDuration: 2 * 60 * 1e3,
4533
+ skipMiddlewares: true
4534
+ }
4535
+ });
4536
+ return {
4537
+ server,
4538
+ initialized: false,
4539
+ connectedSocketIds: /* @__PURE__ */ new Set(),
4540
+ broadcastStatusTask: null,
4541
+ shutdownTask: null
4542
+ };
4543
+ }
4544
+ destroySocketServerRuntimeHandle(runtimeHandle) {
4545
+ if (!runtimeHandle) {
4546
+ return;
4547
+ }
4548
+ runtimeHandle.broadcastStatusTask?.destroy();
4549
+ runtimeHandle.shutdownTask?.destroy();
4550
+ runtimeHandle.broadcastStatusTask = null;
4551
+ runtimeHandle.shutdownTask = null;
4552
+ runtimeHandle.connectedSocketIds.clear();
4553
+ runtimeHandle.initialized = false;
4554
+ runtimeHandle.server.close();
4555
+ runtimeHandle.server.removeAllListeners();
4556
+ }
4557
+ createSocketClientRuntimeHandle(url) {
4558
+ return {
4559
+ url,
4560
+ socket: (0, import_socket2.io)(url, {
4561
+ reconnection: true,
4562
+ reconnectionAttempts: 5,
4563
+ reconnectionDelay: 2e3,
4564
+ reconnectionDelayMax: 1e4,
4565
+ randomizationFactor: 0.5,
4566
+ transports: ["websocket"],
4567
+ autoConnect: false
4568
+ }),
4569
+ initialized: false,
4570
+ handshake: false,
4571
+ errorCount: 0,
4572
+ pendingDelegationIds: /* @__PURE__ */ new Set(),
4573
+ pendingTimers: /* @__PURE__ */ new Set(),
4574
+ emitWhenReady: null,
4575
+ handshakeTask: null,
4576
+ delegateTask: null,
4577
+ transmitTask: null
4578
+ };
4579
+ }
4580
+ destroySocketClientRuntimeHandle(runtimeHandle) {
4581
+ if (!runtimeHandle) {
4582
+ return;
4583
+ }
4584
+ runtimeHandle.initialized = false;
4585
+ runtimeHandle.handshake = false;
4586
+ runtimeHandle.emitWhenReady = null;
4587
+ runtimeHandle.handshakeTask?.destroy();
4588
+ runtimeHandle.delegateTask?.destroy();
4589
+ runtimeHandle.transmitTask?.destroy();
4590
+ runtimeHandle.handshakeTask = null;
4591
+ runtimeHandle.delegateTask = null;
4592
+ runtimeHandle.transmitTask = null;
4593
+ for (const timer of runtimeHandle.pendingTimers) {
4594
+ clearTimeout(timer);
4595
+ }
4596
+ runtimeHandle.pendingTimers.clear();
4597
+ runtimeHandle.pendingDelegationIds.clear();
4598
+ runtimeHandle.socket.close();
4599
+ runtimeHandle.socket.removeAllListeners();
4600
+ }
4601
+ normalizeCommunicationTypes(value) {
4602
+ if (!Array.isArray(value)) {
4603
+ return [];
4604
+ }
4605
+ return value.map((item) => String(item)).filter((item) => item.trim().length > 0);
4036
4606
  }
4037
4607
  getErrorMessage(error) {
4038
4608
  if (error instanceof Error) {
@@ -4047,28 +4617,53 @@ var SocketController = class _SocketController {
4047
4617
  return String(error);
4048
4618
  }
4049
4619
  }
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
4620
+ pruneDiagnosticsEntries(entries, now = Date.now()) {
4621
+ for (const [fetchId, state] of Object.entries(entries)) {
4622
+ if (state.destroyed && now - state.updatedAt > this.destroyedDiagnosticsTtlMs) {
4623
+ delete entries[fetchId];
4624
+ }
4625
+ }
4626
+ if (Object.keys(entries).length <= this.diagnosticsMaxClientEntries) {
4627
+ return;
4628
+ }
4629
+ const entriesByEvictionPriority = Object.entries(entries).sort((left, right) => {
4630
+ if (left[1].destroyed !== right[1].destroyed) {
4631
+ return left[1].destroyed ? -1 : 1;
4632
+ }
4633
+ return left[1].updatedAt - right[1].updatedAt;
4060
4634
  });
4061
- if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
4062
- state.errorHistory.splice(
4063
- 0,
4064
- state.errorHistory.length - this.diagnosticsErrorHistoryLimit
4065
- );
4635
+ while (Object.keys(entries).length > this.diagnosticsMaxClientEntries && entriesByEvictionPriority.length > 0) {
4636
+ const [fetchId] = entriesByEvictionPriority.shift();
4637
+ delete entries[fetchId];
4066
4638
  }
4067
4639
  }
4068
- collectSocketTransportDiagnostics(ctx) {
4640
+ async getSocketClientDiagnosticsEntry(fetchId) {
4641
+ const normalized = String(fetchId ?? "").trim();
4642
+ if (!normalized) {
4643
+ return void 0;
4644
+ }
4645
+ const snapshot = this.socketClientDiagnosticsActor.getState();
4646
+ const entries = { ...snapshot.entries };
4647
+ this.pruneDiagnosticsEntries(entries);
4648
+ return entries[normalized];
4649
+ }
4650
+ resolveTransportDiagnosticsOptions(ctx) {
4651
+ const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
4652
+ const includeErrorHistory = Boolean(ctx.includeErrorHistory);
4653
+ const requestedLimit = Number(ctx.errorHistoryLimit);
4654
+ const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
4655
+ return {
4656
+ detailLevel,
4657
+ includeErrorHistory,
4658
+ errorHistoryLimit
4659
+ };
4660
+ }
4661
+ collectSocketTransportDiagnostics(ctx, diagnosticsEntries) {
4069
4662
  const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
4070
4663
  const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
4071
- const states = Array.from(this.socketClientDiagnostics.values()).sort(
4664
+ const entries = { ...diagnosticsEntries };
4665
+ this.pruneDiagnosticsEntries(entries);
4666
+ const states = Object.values(entries).sort(
4072
4667
  (a, b) => a.fetchId.localeCompare(b.fetchId)
4073
4668
  );
4074
4669
  const summary = {
@@ -4086,10 +4681,7 @@ var SocketController = class _SocketController {
4086
4681
  0
4087
4682
  ),
4088
4683
  connectErrors: states.reduce((acc, state) => acc + state.connectErrors, 0),
4089
- reconnectErrors: states.reduce(
4090
- (acc, state) => acc + state.reconnectErrors,
4091
- 0
4092
- ),
4684
+ reconnectErrors: states.reduce((acc, state) => acc + state.reconnectErrors, 0),
4093
4685
  socketErrors: states.reduce((acc, state) => acc + state.socketErrors, 0),
4094
4686
  latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
4095
4687
  };
@@ -7576,6 +8168,14 @@ var CadenzaService = class {
7576
8168
  options.isMeta = true;
7577
8169
  this.createDatabaseService(name, schema, description, options);
7578
8170
  }
8171
+ static createActor(spec, options = {}) {
8172
+ this.bootstrap();
8173
+ return new import_core3.Actor(spec, options);
8174
+ }
8175
+ static createActorFromDefinition(definition, options = {}) {
8176
+ this.bootstrap();
8177
+ return import_core3.default.createActorFromDefinition(definition, options);
8178
+ }
7579
8179
  /**
7580
8180
  * Creates and registers a new task with the provided name, function, and optional details.
7581
8181
  *
@@ -7993,6 +8593,7 @@ var import_core4 = require("@cadenza.io/core");
7993
8593
  var index_default = CadenzaService;
7994
8594
  // Annotate the CommonJS export names for ESM import in node:
7995
8595
  0 && (module.exports = {
8596
+ Actor,
7996
8597
  DatabaseTask,
7997
8598
  DebounceTask,
7998
8599
  DeputyTask,