@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.mjs CHANGED
@@ -1,5 +1,7 @@
1
1
  // src/Cadenza.ts
2
- import Cadenza from "@cadenza.io/core";
2
+ import Cadenza, {
3
+ Actor
4
+ } from "@cadenza.io/core";
3
5
 
4
6
  // src/graph/definition/DeputyTask.ts
5
7
  import { v4 as uuid } from "uuid";
@@ -2426,6 +2428,8 @@ var RestController = class _RestController {
2426
2428
  constructor() {
2427
2429
  this.fetchClientDiagnostics = /* @__PURE__ */ new Map();
2428
2430
  this.diagnosticsErrorHistoryLimit = 100;
2431
+ this.diagnosticsMaxClientEntries = 500;
2432
+ this.destroyedDiagnosticsTtlMs = 15 * 6e4;
2429
2433
  /**
2430
2434
  * Fetches data from the given URL with a specified timeout. This function performs
2431
2435
  * a fetch request with the ability to cancel the request if it exceeds the provided timeout duration.
@@ -3079,6 +3083,28 @@ var RestController = class _RestController {
3079
3083
  if (!this._instance) this._instance = new _RestController();
3080
3084
  return this._instance;
3081
3085
  }
3086
+ pruneFetchClientDiagnostics(now = Date.now()) {
3087
+ for (const [fetchId, state] of this.fetchClientDiagnostics.entries()) {
3088
+ if (state.destroyed && now - state.updatedAt > this.destroyedDiagnosticsTtlMs) {
3089
+ this.fetchClientDiagnostics.delete(fetchId);
3090
+ }
3091
+ }
3092
+ if (this.fetchClientDiagnostics.size <= this.diagnosticsMaxClientEntries) {
3093
+ return;
3094
+ }
3095
+ const entriesByEvictionPriority = Array.from(
3096
+ this.fetchClientDiagnostics.entries()
3097
+ ).sort((left, right) => {
3098
+ if (left[1].destroyed !== right[1].destroyed) {
3099
+ return left[1].destroyed ? -1 : 1;
3100
+ }
3101
+ return left[1].updatedAt - right[1].updatedAt;
3102
+ });
3103
+ while (this.fetchClientDiagnostics.size > this.diagnosticsMaxClientEntries && entriesByEvictionPriority.length > 0) {
3104
+ const [fetchId] = entriesByEvictionPriority.shift();
3105
+ this.fetchClientDiagnostics.delete(fetchId);
3106
+ }
3107
+ }
3082
3108
  resolveTransportDiagnosticsOptions(ctx) {
3083
3109
  const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
3084
3110
  const includeErrorHistory = Boolean(ctx.includeErrorHistory);
@@ -3091,6 +3117,8 @@ var RestController = class _RestController {
3091
3117
  };
3092
3118
  }
3093
3119
  ensureFetchClientDiagnostics(fetchId, serviceName, url) {
3120
+ const now = Date.now();
3121
+ this.pruneFetchClientDiagnostics(now);
3094
3122
  let state = this.fetchClientDiagnostics.get(fetchId);
3095
3123
  if (!state) {
3096
3124
  state = {
@@ -3110,13 +3138,14 @@ var RestController = class _RestController {
3110
3138
  signalFailures: 0,
3111
3139
  statusChecks: 0,
3112
3140
  statusFailures: 0,
3113
- updatedAt: Date.now()
3141
+ updatedAt: now
3114
3142
  };
3115
3143
  this.fetchClientDiagnostics.set(fetchId, state);
3116
3144
  } else {
3117
3145
  state.serviceName = serviceName;
3118
3146
  state.url = url;
3119
3147
  }
3148
+ this.pruneFetchClientDiagnostics(now);
3120
3149
  return state;
3121
3150
  }
3122
3151
  getErrorMessage(error) {
@@ -3148,6 +3177,7 @@ var RestController = class _RestController {
3148
3177
  }
3149
3178
  }
3150
3179
  collectFetchTransportDiagnostics(ctx) {
3180
+ this.pruneFetchClientDiagnostics();
3151
3181
  const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
3152
3182
  const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
3153
3183
  const states = Array.from(this.fetchClientDiagnostics.values()).sort(
@@ -3264,55 +3294,225 @@ var waitForSocketConnection = async (socket, timeoutMs, createError) => {
3264
3294
 
3265
3295
  // src/network/SocketController.ts
3266
3296
  var SocketController = class _SocketController {
3267
- /**
3268
- * Constructs the `SocketServer`, setting up a WebSocket server with specific configurations,
3269
- * including connection state recovery, rate limiting, CORS handling, and custom event handling.
3270
- * This class sets up the communication infrastructure for scalable, resilient, and secure WebSocket-based interactions
3271
- * using metadata-driven task execution with `Cadenza`.
3272
- *
3273
- * It provides support for:
3274
- * - Origin-based access control for connections.
3275
- * - Optional payload sanitization.
3276
- * - Configurable rate limiting and behavior on limit breaches (soft/hard disconnects).
3277
- * - Event handlers for connection, handshake, delegation, signaling, status checks, and disconnection.
3278
- *
3279
- * The server can handle both internal and external interactions depending on the provided configurations,
3280
- * and integrates directly with Cadenza's task workflow engine.
3281
- *
3282
- * Initializes the `SocketServer` to be ready for WebSocket communication.
3283
- */
3284
3297
  constructor() {
3285
- this.socketClientDiagnostics = /* @__PURE__ */ new Map();
3286
3298
  this.diagnosticsErrorHistoryLimit = 100;
3299
+ this.diagnosticsMaxClientEntries = 500;
3300
+ this.destroyedDiagnosticsTtlMs = 15 * 6e4;
3301
+ this.socketServerDefaultKey = "socket-server-default";
3302
+ this.socketServerActor = CadenzaService.createActor(
3303
+ {
3304
+ name: "SocketServerActor",
3305
+ description: "Holds durable socket server session state and runtime socket server handle",
3306
+ defaultKey: this.socketServerDefaultKey,
3307
+ keyResolver: (input) => this.resolveSocketServerKey(input),
3308
+ loadPolicy: "lazy",
3309
+ writeContract: "overwrite",
3310
+ initState: this.createInitialSocketServerSessionState(
3311
+ this.socketServerDefaultKey
3312
+ )
3313
+ },
3314
+ { isMeta: true }
3315
+ );
3316
+ this.socketClientActor = CadenzaService.createActor(
3317
+ {
3318
+ name: "SocketClientActor",
3319
+ description: "Holds durable socket client session state and runtime socket connection handles",
3320
+ defaultKey: "socket-client-default",
3321
+ keyResolver: (input) => this.resolveSocketClientFetchId(input),
3322
+ loadPolicy: "lazy",
3323
+ writeContract: "overwrite",
3324
+ initState: this.createInitialSocketClientSessionState()
3325
+ },
3326
+ { isMeta: true }
3327
+ );
3328
+ this.socketClientDiagnosticsActor = CadenzaService.createActor(
3329
+ {
3330
+ name: "SocketClientDiagnosticsActor",
3331
+ description: "Tracks socket client diagnostics snapshots per fetchId for transport observability",
3332
+ defaultKey: "socket-client-diagnostics",
3333
+ loadPolicy: "eager",
3334
+ writeContract: "overwrite",
3335
+ initState: {
3336
+ entries: {}
3337
+ }
3338
+ },
3339
+ { isMeta: true }
3340
+ );
3341
+ this.registerDiagnosticsTasks();
3342
+ this.registerSocketServerTasks();
3343
+ this.registerSocketClientTasks();
3287
3344
  CadenzaService.createMetaTask(
3288
3345
  "Collect socket transport diagnostics",
3289
- (ctx) => this.collectSocketTransportDiagnostics(ctx),
3346
+ this.socketClientDiagnosticsActor.task(
3347
+ ({ state, input }) => this.collectSocketTransportDiagnostics(input, state.entries),
3348
+ { mode: "read" }
3349
+ ),
3290
3350
  "Responds to distributed transport diagnostics inquiries with socket client data."
3291
3351
  ).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
3292
- CadenzaService.createMetaRoutine(
3293
- "SocketServer",
3294
- [
3295
- CadenzaService.createMetaTask("Setup SocketServer", (ctx) => {
3296
- if (!ctx.__useSocket) {
3352
+ }
3353
+ static get instance() {
3354
+ if (!this._instance) this._instance = new _SocketController();
3355
+ return this._instance;
3356
+ }
3357
+ registerDiagnosticsTasks() {
3358
+ CadenzaService.createThrottledMetaTask(
3359
+ "SocketClientDiagnosticsActor.Upsert",
3360
+ this.socketClientDiagnosticsActor.task(
3361
+ ({ state, input, setState }) => {
3362
+ const fetchId = String(input.fetchId ?? "").trim();
3363
+ if (!fetchId) {
3297
3364
  return;
3298
3365
  }
3299
- const server = new Server(ctx.httpsServer ?? ctx.httpServer, {
3300
- pingInterval: 3e4,
3301
- pingTimeout: 2e4,
3302
- maxHttpBufferSize: 1e7,
3303
- // 10MB large payloads
3304
- connectionStateRecovery: {
3305
- maxDisconnectionDuration: 2 * 60 * 1e3,
3306
- // 2min
3307
- skipMiddlewares: true
3308
- // Optional: bypass rate limiter on recover
3366
+ const now = Date.now();
3367
+ const entries = { ...state.entries };
3368
+ const existing = entries[fetchId];
3369
+ const base = existing ? {
3370
+ ...existing,
3371
+ errorHistory: [...existing.errorHistory]
3372
+ } : {
3373
+ fetchId,
3374
+ serviceName: String(input.serviceName ?? ""),
3375
+ url: String(input.url ?? ""),
3376
+ socketId: null,
3377
+ connected: false,
3378
+ handshake: false,
3379
+ reconnectAttempts: 0,
3380
+ connectErrors: 0,
3381
+ reconnectErrors: 0,
3382
+ socketErrors: 0,
3383
+ pendingDelegations: 0,
3384
+ pendingTimers: 0,
3385
+ destroyed: false,
3386
+ lastHandshakeAt: null,
3387
+ lastHandshakeError: null,
3388
+ lastDisconnectAt: null,
3389
+ lastError: null,
3390
+ lastErrorAt: 0,
3391
+ errorHistory: [],
3392
+ updatedAt: now
3393
+ };
3394
+ if (input.serviceName !== void 0) {
3395
+ base.serviceName = String(input.serviceName);
3396
+ }
3397
+ if (input.url !== void 0) {
3398
+ base.url = String(input.url);
3399
+ }
3400
+ const patch = input.patch && typeof input.patch === "object" ? input.patch : {};
3401
+ Object.assign(base, patch);
3402
+ base.fetchId = fetchId;
3403
+ base.updatedAt = now;
3404
+ const errorMessage = input.error !== void 0 ? this.getErrorMessage(input.error) : void 0;
3405
+ if (errorMessage) {
3406
+ base.lastError = errorMessage;
3407
+ base.lastErrorAt = now;
3408
+ base.errorHistory.push({
3409
+ at: new Date(now).toISOString(),
3410
+ message: errorMessage
3411
+ });
3412
+ if (base.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
3413
+ base.errorHistory.splice(
3414
+ 0,
3415
+ base.errorHistory.length - this.diagnosticsErrorHistoryLimit
3416
+ );
3309
3417
  }
3418
+ }
3419
+ entries[fetchId] = base;
3420
+ this.pruneDiagnosticsEntries(entries, now);
3421
+ setState({ entries });
3422
+ },
3423
+ { mode: "write" }
3424
+ ),
3425
+ (context) => String(context?.fetchId ?? "default"),
3426
+ "Upserts socket client diagnostics in actor state."
3427
+ ).doOn("meta.socket_client.diagnostics_upsert_requested");
3428
+ }
3429
+ registerSocketServerTasks() {
3430
+ CadenzaService.createThrottledMetaTask(
3431
+ "SocketServerActor.PatchSession",
3432
+ this.socketServerActor.task(
3433
+ ({ state, input, setState }) => {
3434
+ const patch = input.patch && typeof input.patch === "object" ? input.patch : {};
3435
+ setState({
3436
+ ...state,
3437
+ ...patch,
3438
+ updatedAt: Date.now()
3439
+ });
3440
+ },
3441
+ { mode: "write" }
3442
+ ),
3443
+ (context) => String(context?.serverKey ?? this.socketServerDefaultKey),
3444
+ "Applies partial durable session updates for socket server actor."
3445
+ ).doOn("meta.socket_server.session_patch_requested");
3446
+ CadenzaService.createMetaTask(
3447
+ "SocketServerActor.ClearRuntime",
3448
+ this.socketServerActor.task(
3449
+ ({ setRuntimeState }) => {
3450
+ setRuntimeState(null);
3451
+ },
3452
+ { mode: "write" }
3453
+ ),
3454
+ "Clears socket server runtime handle after shutdown."
3455
+ ).doOn("meta.socket_server.runtime_clear_requested");
3456
+ const setupSocketServerTask = CadenzaService.createMetaTask(
3457
+ "Setup SocketServer",
3458
+ this.socketServerActor.task(
3459
+ ({ state, runtimeState, input, actor, setState, setRuntimeState, emit }) => {
3460
+ const serverKey = this.resolveSocketServerKey(input) ?? actor.key ?? this.socketServerDefaultKey;
3461
+ const shouldUseSocket = Boolean(input.__useSocket);
3462
+ if (!shouldUseSocket) {
3463
+ this.destroySocketServerRuntimeHandle(runtimeState);
3464
+ setRuntimeState(null);
3465
+ setState({
3466
+ ...state,
3467
+ serverKey,
3468
+ useSocket: false,
3469
+ status: "inactive",
3470
+ connectionCount: 0,
3471
+ lastShutdownAt: (/* @__PURE__ */ new Date()).toISOString(),
3472
+ updatedAt: Date.now()
3473
+ });
3474
+ return;
3475
+ }
3476
+ let runtimeHandle = runtimeState;
3477
+ if (!runtimeHandle) {
3478
+ runtimeHandle = this.createSocketServerRuntimeHandleFromContext(input);
3479
+ setRuntimeState(runtimeHandle);
3480
+ }
3481
+ const profile = String(input.__securityProfile ?? state.securityProfile ?? "medium");
3482
+ const networkType = String(input.__networkType ?? state.networkType ?? "internal");
3483
+ const schedulePatch = (patch) => {
3484
+ CadenzaService.emit("meta.socket_server.session_patch_requested", {
3485
+ serverKey,
3486
+ patch
3487
+ });
3488
+ };
3489
+ if (runtimeHandle.initialized) {
3490
+ schedulePatch({
3491
+ status: "active",
3492
+ useSocket: true,
3493
+ securityProfile: profile,
3494
+ networkType,
3495
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3496
+ lastStartedAt: state.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString()
3497
+ });
3498
+ return;
3499
+ }
3500
+ const server = runtimeHandle.server;
3501
+ runtimeHandle.initialized = true;
3502
+ setState({
3503
+ ...state,
3504
+ serverKey,
3505
+ useSocket: true,
3506
+ status: "active",
3507
+ securityProfile: profile,
3508
+ networkType,
3509
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3510
+ lastStartedAt: state.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
3511
+ updatedAt: Date.now()
3310
3512
  });
3311
- const profile = ctx.__securityProfile ?? "medium";
3312
3513
  server.use((socket, next) => {
3313
3514
  const origin = socket?.handshake?.headers?.origin;
3314
3515
  const allowedOrigins = ["*"];
3315
- const networkType = ctx.__networkType ?? "internal";
3316
3516
  let effectiveOrigin = origin || "unknown";
3317
3517
  if (networkType === "internal") effectiveOrigin = "internal";
3318
3518
  if (profile !== "low" && !allowedOrigins.includes(effectiveOrigin) && !allowedOrigins.includes("*")) {
@@ -3323,7 +3523,9 @@ var SocketController = class _SocketController {
3323
3523
  medium: { points: 1e4, duration: 10 },
3324
3524
  high: { points: 1e3, duration: 60, blockDuration: 300 }
3325
3525
  };
3326
- const limiter = new RateLimiterMemory2(limiterOptions[profile]);
3526
+ const limiter = new RateLimiterMemory2(
3527
+ limiterOptions[profile] ?? limiterOptions.medium
3528
+ );
3327
3529
  const clientKey = socket?.handshake?.address || "unknown";
3328
3530
  socket.use((packet, packetNext) => {
3329
3531
  limiter.consume(clientKey).then(() => packetNext()).catch((rej) => {
@@ -3358,116 +3560,111 @@ var SocketController = class _SocketController {
3358
3560
  });
3359
3561
  next();
3360
3562
  });
3361
- if (!server) {
3362
- CadenzaService.log("Socket setup error: No server", {}, "error");
3363
- return { ...ctx, __error: "No server", errored: true };
3364
- }
3365
3563
  server.on("connection", (ws) => {
3564
+ runtimeHandle.connectedSocketIds.add(ws.id);
3565
+ schedulePatch({
3566
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3567
+ lastConnectedAt: (/* @__PURE__ */ new Date()).toISOString(),
3568
+ status: "active"
3569
+ });
3366
3570
  try {
3367
- ws.on(
3368
- "handshake",
3369
- (ctx2, callback) => {
3370
- CadenzaService.log("SocketServer: New connection", {
3371
- ...ctx2,
3372
- socketId: ws.id
3373
- });
3374
- callback({
3375
- status: "success",
3376
- serviceName: CadenzaService.serviceRegistry.serviceName
3377
- });
3378
- if (ctx2.isFrontend) {
3379
- const fetchId = `browser:${ctx2.serviceInstanceId}`;
3380
- CadenzaService.createMetaTask(
3381
- `Transmit signal to ${fetchId}`,
3382
- (ctx3, emit) => {
3383
- if (ctx3.__signalName === void 0) {
3384
- return;
3385
- }
3386
- ws.emit("signal", ctx3);
3387
- if (ctx3.__routineExecId) {
3388
- emit(
3389
- `meta.socket_client.transmitted:${ctx3.__routineExecId}`,
3390
- {}
3391
- );
3392
- }
3571
+ ws.on("handshake", (ctx, callback) => {
3572
+ CadenzaService.log("SocketServer: New connection", {
3573
+ ...ctx,
3574
+ socketId: ws.id
3575
+ });
3576
+ callback({
3577
+ status: "success",
3578
+ serviceName: CadenzaService.serviceRegistry.serviceName
3579
+ });
3580
+ if (ctx.isFrontend) {
3581
+ const fetchId = `browser:${ctx.serviceInstanceId}`;
3582
+ CadenzaService.createMetaTask(
3583
+ `Transmit signal to ${fetchId}`,
3584
+ (c, emitter) => {
3585
+ if (c.__signalName === void 0) {
3586
+ return;
3587
+ }
3588
+ ws.emit("signal", c);
3589
+ if (c.__routineExecId) {
3590
+ emitter(`meta.socket_client.transmitted:${c.__routineExecId}`, {});
3393
3591
  }
3394
- ).doOn(
3395
- `meta.service_registry.selected_instance_for_socket:${fetchId}`
3396
- ).attachSignal("meta.socket_client.transmitted");
3397
- }
3398
- CadenzaService.emit("meta.socket.handshake", ctx2);
3399
- }
3400
- );
3401
- ws.on(
3402
- "delegation",
3403
- (ctx2, callback) => {
3404
- const deputyExecId = ctx2.__metadata.__deputyExecId;
3405
- CadenzaService.createEphemeralMetaTask(
3406
- "Resolve delegation",
3407
- (ctx3) => {
3408
- callback(ctx3);
3409
- },
3410
- "Resolves a delegation request using the provided callback from the client (.emitWithAck())",
3411
- { register: false }
3412
- ).doOn(`meta.node.graph_completed:${deputyExecId}`).emits(`meta.socket.delegation_resolved:${deputyExecId}`);
3413
- CadenzaService.createEphemeralMetaTask(
3414
- "Delegation progress update",
3415
- (ctx3) => {
3416
- if (ctx3.__progress !== void 0)
3417
- ws.emit("delegation_progress", ctx3);
3418
3592
  },
3419
- "Updates delegation progress",
3420
- {
3421
- once: false,
3422
- destroyCondition: (ctx3) => ctx3.data.progress === 1 || ctx3.data?.progress === void 0,
3423
- register: false
3424
- }
3425
- ).doOn(
3426
- `meta.node.routine_execution_progress:${deputyExecId}`,
3427
- `meta.node.graph_completed:${deputyExecId}`
3428
- ).emitsOnFail(`meta.socket.progress_failed:${deputyExecId}`);
3429
- CadenzaService.emit("meta.socket.delegation_requested", {
3430
- ...ctx2,
3431
- __name: ctx2.__remoteRoutineName
3432
- });
3593
+ "Transmit frontend bound signal through active websocket."
3594
+ ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal("meta.socket_client.transmitted");
3433
3595
  }
3434
- );
3435
- ws.on(
3436
- "signal",
3437
- (ctx2, callback) => {
3438
- if (CadenzaService.signalBroker.listObservedSignals().includes(ctx2.__signalName)) {
3439
- callback({
3440
- __status: "success",
3441
- __signalName: ctx2.__signalName
3442
- });
3443
- CadenzaService.emit(ctx2.__signalName, ctx2);
3444
- } else {
3445
- CadenzaService.log(
3446
- `No such signal ${ctx2.__signalName} on ${ctx2.__serviceName}`,
3447
- "warning"
3448
- );
3449
- callback({
3450
- ...ctx2,
3451
- __status: "error",
3452
- __error: `No such signal: ${ctx2.__signalName}`,
3453
- errored: true
3454
- });
3596
+ CadenzaService.emit("meta.socket.handshake", ctx);
3597
+ });
3598
+ ws.on("delegation", (ctx, callback) => {
3599
+ const deputyExecId = ctx.__metadata.__deputyExecId;
3600
+ CadenzaService.createEphemeralMetaTask(
3601
+ "Resolve delegation",
3602
+ (delegationCtx) => {
3603
+ callback(delegationCtx);
3604
+ },
3605
+ "Resolves a delegation request using client callback.",
3606
+ { register: false }
3607
+ ).doOn(`meta.node.graph_completed:${deputyExecId}`).emits(`meta.socket.delegation_resolved:${deputyExecId}`);
3608
+ CadenzaService.createEphemeralMetaTask(
3609
+ "Delegation progress update",
3610
+ (progressCtx) => {
3611
+ if (progressCtx.__progress !== void 0) {
3612
+ ws.emit("delegation_progress", progressCtx);
3613
+ }
3614
+ },
3615
+ "Updates delegation progress to client.",
3616
+ {
3617
+ once: false,
3618
+ destroyCondition: (progressCtx) => progressCtx.data.progress === 1 || progressCtx.data?.progress === void 0,
3619
+ register: false
3455
3620
  }
3621
+ ).doOn(
3622
+ `meta.node.routine_execution_progress:${deputyExecId}`,
3623
+ `meta.node.graph_completed:${deputyExecId}`
3624
+ ).emitsOnFail(`meta.socket.progress_failed:${deputyExecId}`);
3625
+ CadenzaService.emit("meta.socket.delegation_requested", {
3626
+ ...ctx,
3627
+ __name: ctx.__remoteRoutineName
3628
+ });
3629
+ });
3630
+ ws.on("signal", (ctx, callback) => {
3631
+ if (CadenzaService.signalBroker.listObservedSignals().includes(ctx.__signalName)) {
3632
+ callback({
3633
+ __status: "success",
3634
+ __signalName: ctx.__signalName
3635
+ });
3636
+ CadenzaService.emit(ctx.__signalName, ctx);
3637
+ } else {
3638
+ CadenzaService.log(
3639
+ `No such signal ${ctx.__signalName} on ${ctx.__serviceName}`,
3640
+ "warning"
3641
+ );
3642
+ callback({
3643
+ ...ctx,
3644
+ __status: "error",
3645
+ __error: `No such signal: ${ctx.__signalName}`,
3646
+ errored: true
3647
+ });
3456
3648
  }
3457
- );
3649
+ });
3458
3650
  ws.on(
3459
3651
  "status_check",
3460
- (ctx2, callback) => {
3652
+ (ctx, callback) => {
3461
3653
  CadenzaService.createEphemeralMetaTask(
3462
3654
  "Resolve status check",
3463
3655
  callback,
3464
3656
  "Resolves a status check request",
3465
3657
  { register: false }
3466
3658
  ).doAfter(CadenzaService.serviceRegistry.getStatusTask);
3467
- CadenzaService.emit("meta.socket.status_check_requested", ctx2);
3659
+ CadenzaService.emit("meta.socket.status_check_requested", ctx);
3468
3660
  }
3469
3661
  );
3470
3662
  ws.on("disconnect", () => {
3663
+ runtimeHandle.connectedSocketIds.delete(ws.id);
3664
+ schedulePatch({
3665
+ connectionCount: runtimeHandle.connectedSocketIds.size,
3666
+ lastDisconnectedAt: (/* @__PURE__ */ new Date()).toISOString()
3667
+ });
3471
3668
  CadenzaService.log(
3472
3669
  "Socket client disconnected",
3473
3670
  { socketId: ws.id },
@@ -3477,514 +3674,888 @@ var SocketController = class _SocketController {
3477
3674
  __wsId: ws.id
3478
3675
  });
3479
3676
  });
3480
- } catch (e) {
3677
+ } catch (error) {
3481
3678
  CadenzaService.log(
3482
3679
  "SocketServer: Error in socket event",
3483
- { error: e },
3680
+ { error },
3484
3681
  "error"
3485
3682
  );
3486
3683
  }
3487
3684
  CadenzaService.emit("meta.socket.connected", { __wsId: ws.id });
3488
3685
  });
3489
- CadenzaService.createMetaTask(
3490
- "Broadcast status",
3491
- (ctx2) => server.emit("status_update", ctx2),
3686
+ runtimeHandle.broadcastStatusTask = CadenzaService.createMetaTask(
3687
+ `Broadcast status ${serverKey}`,
3688
+ (ctx) => server.emit("status_update", ctx),
3492
3689
  "Broadcasts the status of the server to all clients"
3493
3690
  ).doOn("meta.service.updated");
3494
- CadenzaService.createMetaTask(
3495
- "Shutdown SocketServer",
3496
- () => server.close(),
3691
+ runtimeHandle.shutdownTask = CadenzaService.createMetaTask(
3692
+ `Shutdown SocketServer ${serverKey}`,
3693
+ async () => {
3694
+ this.destroySocketServerRuntimeHandle(runtimeHandle);
3695
+ CadenzaService.emit("meta.socket_server.runtime_clear_requested", {
3696
+ serverKey
3697
+ });
3698
+ CadenzaService.emit("meta.socket_server.session_patch_requested", {
3699
+ serverKey,
3700
+ patch: {
3701
+ useSocket: false,
3702
+ status: "shutdown",
3703
+ connectionCount: 0,
3704
+ lastShutdownAt: (/* @__PURE__ */ new Date()).toISOString()
3705
+ }
3706
+ });
3707
+ },
3497
3708
  "Shuts down the socket server"
3498
3709
  ).doOn("meta.socket_server_shutdown_requested").emits("meta.socket.shutdown");
3499
- return ctx;
3500
- })
3501
- ],
3502
- "Bootstraps the socket server"
3503
- ).doOn("global.meta.rest.network_configured");
3710
+ return true;
3711
+ },
3712
+ { mode: "write" }
3713
+ ),
3714
+ "Initializes socket server runtime through actor state."
3715
+ );
3716
+ setupSocketServerTask.doOn("global.meta.rest.network_configured");
3717
+ }
3718
+ registerSocketClientTasks() {
3719
+ CadenzaService.createThrottledMetaTask(
3720
+ "SocketClientActor.ApplySessionOperation",
3721
+ this.socketClientActor.task(
3722
+ ({ state, input, setState }) => {
3723
+ const operation = String(
3724
+ input.operation ?? "transmit"
3725
+ );
3726
+ const patch = input.patch && typeof input.patch === "object" ? input.patch : {};
3727
+ let next = {
3728
+ ...state,
3729
+ ...patch,
3730
+ communicationTypes: patch.communicationTypes !== void 0 ? this.normalizeCommunicationTypes(patch.communicationTypes) : state.communicationTypes,
3731
+ updatedAt: Date.now()
3732
+ };
3733
+ if (input.serviceName !== void 0) {
3734
+ next.serviceName = String(input.serviceName);
3735
+ }
3736
+ if (input.serviceAddress !== void 0) {
3737
+ next.serviceAddress = String(input.serviceAddress);
3738
+ }
3739
+ if (input.serviceInstanceId !== void 0) {
3740
+ next.serviceInstanceId = String(input.serviceInstanceId);
3741
+ }
3742
+ if (input.protocol !== void 0) {
3743
+ next.protocol = String(input.protocol);
3744
+ }
3745
+ if (input.url !== void 0) {
3746
+ next.url = String(input.url);
3747
+ }
3748
+ if (input.servicePort !== void 0) {
3749
+ next.servicePort = Number(input.servicePort);
3750
+ }
3751
+ if (input.fetchId !== void 0) {
3752
+ next.fetchId = String(input.fetchId);
3753
+ }
3754
+ if (operation === "connect") {
3755
+ next.destroyed = false;
3756
+ } else if (operation === "handshake") {
3757
+ next.destroyed = false;
3758
+ next.connected = patch.connected ?? true;
3759
+ next.handshake = patch.handshake ?? true;
3760
+ } else if (operation === "shutdown") {
3761
+ next.connected = false;
3762
+ next.handshake = false;
3763
+ next.destroyed = true;
3764
+ next.pendingDelegations = 0;
3765
+ next.pendingTimers = 0;
3766
+ }
3767
+ setState(next);
3768
+ return next;
3769
+ },
3770
+ { mode: "write" }
3771
+ ),
3772
+ (context) => String(this.resolveSocketClientFetchId(context ?? {}) ?? "default"),
3773
+ "Applies socket client session operation patch in actor durable state."
3774
+ ).doOn("meta.socket_client.session_operation_requested");
3775
+ CadenzaService.createMetaTask(
3776
+ "SocketClientActor.ClearRuntime",
3777
+ this.socketClientActor.task(
3778
+ ({ setRuntimeState }) => {
3779
+ setRuntimeState(null);
3780
+ },
3781
+ { mode: "write" }
3782
+ ),
3783
+ "Clears socket client runtime handle."
3784
+ ).doOn("meta.socket_client.runtime_clear_requested");
3504
3785
  CadenzaService.createMetaTask(
3505
3786
  "Connect to socket server",
3506
- (ctx) => {
3507
- const {
3508
- serviceInstanceId,
3509
- communicationTypes,
3510
- serviceName,
3511
- serviceAddress,
3512
- servicePort,
3513
- protocol
3514
- } = ctx;
3515
- const socketProtocol = protocol === "https" ? "wss" : "ws";
3516
- const port = protocol === "https" ? 443 : servicePort;
3517
- const URL = `${socketProtocol}://${serviceAddress}:${port}`;
3518
- const fetchId = `${serviceAddress}_${port}`;
3519
- const socketDiagnostics = this.ensureSocketClientDiagnostics(
3520
- fetchId,
3521
- serviceName,
3522
- URL
3523
- );
3524
- socketDiagnostics.destroyed = false;
3525
- socketDiagnostics.updatedAt = Date.now();
3526
- let handshake = false;
3527
- let errorCount = 0;
3528
- const ERROR_LIMIT = 5;
3529
- if (CadenzaService.get(`Socket handshake with ${URL}`)) {
3530
- console.error("Socket client already exists", URL);
3531
- return;
3532
- }
3533
- const pendingDelegationIds = /* @__PURE__ */ new Set();
3534
- const pendingTimers = /* @__PURE__ */ new Set();
3535
- const syncPendingCounts = () => {
3536
- socketDiagnostics.pendingDelegations = pendingDelegationIds.size;
3537
- socketDiagnostics.pendingTimers = pendingTimers.size;
3538
- socketDiagnostics.updatedAt = Date.now();
3539
- };
3540
- let handshakeTask = null;
3541
- let emitWhenReady = null;
3542
- let transmitTask = null;
3543
- let delegateTask = null;
3544
- let socket = null;
3545
- socket = io(URL, {
3546
- reconnection: true,
3547
- reconnectionAttempts: 5,
3548
- reconnectionDelay: 2e3,
3549
- reconnectionDelayMax: 1e4,
3550
- randomizationFactor: 0.5,
3551
- transports: ["websocket"],
3552
- autoConnect: false
3553
- });
3554
- emitWhenReady = (event, data, timeoutMs = 6e4, ack) => {
3555
- return new Promise((resolve) => {
3556
- const resolveWithError = (errorMessage, fallbackError) => {
3557
- resolve({
3558
- ...data,
3559
- errored: true,
3560
- __error: errorMessage,
3561
- error: fallbackError instanceof Error ? fallbackError.message : errorMessage,
3562
- socketId: socket?.id,
3787
+ this.socketClientActor.task(
3788
+ ({ state, runtimeState, input, setState, setRuntimeState, emit }) => {
3789
+ const serviceInstanceId = String(input.serviceInstanceId ?? "");
3790
+ const communicationTypes = this.normalizeCommunicationTypes(
3791
+ input.communicationTypes
3792
+ );
3793
+ const serviceName = String(input.serviceName ?? "");
3794
+ const serviceAddress = String(input.serviceAddress ?? "");
3795
+ const protocol = String(input.protocol ?? "http");
3796
+ const normalizedPort = this.resolveServicePort(protocol, input.servicePort);
3797
+ if (!serviceAddress || !normalizedPort) {
3798
+ CadenzaService.log(
3799
+ "Socket client setup skipped due to missing address/port",
3800
+ {
3563
3801
  serviceName,
3564
- URL
3802
+ serviceAddress,
3803
+ servicePort: input.servicePort,
3804
+ protocol
3805
+ },
3806
+ "warning"
3807
+ );
3808
+ return false;
3809
+ }
3810
+ const socketProtocol = protocol === "https" ? "wss" : "ws";
3811
+ const url = `${socketProtocol}://${serviceAddress}:${normalizedPort}`;
3812
+ const fetchId = `${serviceAddress}_${normalizedPort}`;
3813
+ const applySessionOperation = (operation, patch = {}) => {
3814
+ CadenzaService.emit("meta.socket_client.session_operation_requested", {
3815
+ fetchId,
3816
+ operation,
3817
+ patch,
3818
+ serviceInstanceId,
3819
+ communicationTypes,
3820
+ serviceName,
3821
+ serviceAddress,
3822
+ servicePort: normalizedPort,
3823
+ protocol,
3824
+ url
3825
+ });
3826
+ };
3827
+ const upsertDiagnostics = (patch, error) => {
3828
+ CadenzaService.emit("meta.socket_client.diagnostics_upsert_requested", {
3829
+ fetchId,
3830
+ serviceName,
3831
+ url,
3832
+ patch,
3833
+ error
3834
+ });
3835
+ };
3836
+ setState({
3837
+ ...state,
3838
+ fetchId,
3839
+ serviceInstanceId,
3840
+ communicationTypes,
3841
+ serviceName,
3842
+ serviceAddress,
3843
+ servicePort: normalizedPort,
3844
+ protocol,
3845
+ url,
3846
+ destroyed: false,
3847
+ updatedAt: Date.now()
3848
+ });
3849
+ let runtimeHandle = runtimeState;
3850
+ if (!runtimeHandle || runtimeHandle.url !== url) {
3851
+ this.destroySocketClientRuntimeHandle(runtimeHandle);
3852
+ runtimeHandle = this.createSocketClientRuntimeHandle(url);
3853
+ setRuntimeState(runtimeHandle);
3854
+ }
3855
+ upsertDiagnostics({
3856
+ destroyed: false,
3857
+ connected: false,
3858
+ handshake: false,
3859
+ socketId: runtimeHandle.socket.id ?? null
3860
+ });
3861
+ applySessionOperation("connect", {
3862
+ destroyed: false,
3863
+ connected: false,
3864
+ handshake: false,
3865
+ socketId: runtimeHandle.socket.id ?? null,
3866
+ pendingDelegations: runtimeHandle.pendingDelegationIds.size,
3867
+ pendingTimers: runtimeHandle.pendingTimers.size,
3868
+ errorCount: runtimeHandle.errorCount
3869
+ });
3870
+ if (runtimeHandle.initialized) {
3871
+ return true;
3872
+ }
3873
+ runtimeHandle.initialized = true;
3874
+ runtimeHandle.handshake = false;
3875
+ runtimeHandle.errorCount = 0;
3876
+ const syncPendingCounts = () => {
3877
+ const pendingDelegations = runtimeHandle.pendingDelegationIds.size;
3878
+ const pendingTimers = runtimeHandle.pendingTimers.size;
3879
+ upsertDiagnostics({
3880
+ pendingDelegations,
3881
+ pendingTimers
3882
+ });
3883
+ applySessionOperation("delegate", {
3884
+ pendingDelegations,
3885
+ pendingTimers
3886
+ });
3887
+ };
3888
+ runtimeHandle.emitWhenReady = (event, data, timeoutMs = 6e4, ack) => {
3889
+ return new Promise((resolve) => {
3890
+ const parsedTimeout = Number(timeoutMs);
3891
+ const normalizedTimeoutMs = Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? Math.trunc(parsedTimeout) : 6e4;
3892
+ let timer = null;
3893
+ let settled = false;
3894
+ const clearPendingTimer = () => {
3895
+ if (!timer) {
3896
+ return;
3897
+ }
3898
+ clearTimeout(timer);
3899
+ runtimeHandle.pendingTimers.delete(timer);
3900
+ syncPendingCounts();
3901
+ timer = null;
3902
+ };
3903
+ const settle = (response) => {
3904
+ if (settled) {
3905
+ return;
3906
+ }
3907
+ settled = true;
3908
+ clearPendingTimer();
3909
+ if (ack) ack(response);
3910
+ resolve(response);
3911
+ };
3912
+ const resolveWithError = (errorMessage, fallbackError) => {
3913
+ settle({
3914
+ ...data,
3915
+ errored: true,
3916
+ __error: errorMessage,
3917
+ error: fallbackError instanceof Error ? fallbackError.message : errorMessage,
3918
+ socketId: runtimeHandle.socket.id,
3919
+ serviceName,
3920
+ url
3921
+ });
3922
+ };
3923
+ const tryEmit = async () => {
3924
+ const waitResult = await waitForSocketConnection(
3925
+ runtimeHandle.socket,
3926
+ normalizedTimeoutMs + 10,
3927
+ (reason, error) => {
3928
+ if (reason === "connect_timeout") {
3929
+ return `Socket connect timed out before '${event}'`;
3930
+ }
3931
+ if (reason === "connect_error") {
3932
+ const errMessage = error instanceof Error ? error.message : String(error);
3933
+ return `Socket connect error before '${event}': ${errMessage}`;
3934
+ }
3935
+ return `Socket disconnected before '${event}'`;
3936
+ }
3937
+ );
3938
+ if (!waitResult.ok) {
3939
+ CadenzaService.log(
3940
+ waitResult.error,
3941
+ {
3942
+ socketId: runtimeHandle.socket.id,
3943
+ serviceName,
3944
+ url,
3945
+ event
3946
+ },
3947
+ "error"
3948
+ );
3949
+ upsertDiagnostics({}, waitResult.error);
3950
+ resolveWithError(waitResult.error);
3951
+ return;
3952
+ }
3953
+ timer = setTimeout(() => {
3954
+ if (settled) {
3955
+ return;
3956
+ }
3957
+ clearPendingTimer();
3958
+ const message = `Socket event '${event}' timed out`;
3959
+ CadenzaService.log(
3960
+ message,
3961
+ { socketId: runtimeHandle.socket.id, serviceName, url },
3962
+ "error"
3963
+ );
3964
+ upsertDiagnostics(
3965
+ {
3966
+ lastHandshakeError: message
3967
+ },
3968
+ message
3969
+ );
3970
+ applySessionOperation("transmit", {
3971
+ lastHandshakeError: message
3972
+ });
3973
+ resolveWithError(message);
3974
+ }, normalizedTimeoutMs + 10);
3975
+ runtimeHandle.pendingTimers.add(timer);
3976
+ syncPendingCounts();
3977
+ runtimeHandle.socket.timeout(normalizedTimeoutMs).emit(event, data, (err, response) => {
3978
+ if (err) {
3979
+ CadenzaService.log(
3980
+ "Socket timeout.",
3981
+ {
3982
+ event,
3983
+ error: err.message,
3984
+ socketId: runtimeHandle.socket.id,
3985
+ serviceName
3986
+ },
3987
+ "warning"
3988
+ );
3989
+ upsertDiagnostics(
3990
+ {
3991
+ lastHandshakeError: err.message
3992
+ },
3993
+ err
3994
+ );
3995
+ applySessionOperation("transmit", {
3996
+ lastHandshakeError: err.message
3997
+ });
3998
+ response = {
3999
+ __error: `Timeout error: ${err}`,
4000
+ errored: true,
4001
+ ...data
4002
+ };
4003
+ }
4004
+ settle(response);
4005
+ });
4006
+ };
4007
+ void tryEmit().catch((error) => {
4008
+ CadenzaService.log(
4009
+ "Socket emit failed unexpectedly",
4010
+ {
4011
+ event,
4012
+ error: error instanceof Error ? error.message : String(error),
4013
+ socketId: runtimeHandle.socket.id,
4014
+ serviceName,
4015
+ url
4016
+ },
4017
+ "error"
4018
+ );
4019
+ const message = `Socket event '${event}' failed`;
4020
+ upsertDiagnostics(
4021
+ {
4022
+ lastHandshakeError: error instanceof Error ? error.message : String(error)
4023
+ },
4024
+ error
4025
+ );
4026
+ applySessionOperation("transmit", {
4027
+ lastHandshakeError: error instanceof Error ? error.message : String(error)
4028
+ });
4029
+ resolveWithError(message, error);
4030
+ });
4031
+ });
4032
+ };
4033
+ const socket = runtimeHandle.socket;
4034
+ socket.on("connect", () => {
4035
+ if (runtimeHandle.handshake) return;
4036
+ upsertDiagnostics({
4037
+ connected: true,
4038
+ destroyed: false,
4039
+ socketId: socket.id ?? null
4040
+ });
4041
+ applySessionOperation("connect", {
4042
+ connected: true,
4043
+ destroyed: false,
4044
+ socketId: socket.id ?? null
4045
+ });
4046
+ CadenzaService.emit(`meta.socket_client.connected:${fetchId}`, input);
4047
+ });
4048
+ socket.on("delegation_progress", (delegationCtx) => {
4049
+ CadenzaService.emit(
4050
+ `meta.socket_client.delegation_progress:${delegationCtx.__metadata.__deputyExecId}`,
4051
+ delegationCtx
4052
+ );
4053
+ });
4054
+ socket.on("signal", (signalCtx) => {
4055
+ if (CadenzaService.signalBroker.listObservedSignals().includes(signalCtx.__signalName)) {
4056
+ CadenzaService.emit(signalCtx.__signalName, signalCtx);
4057
+ }
4058
+ });
4059
+ socket.on("status_update", (status) => {
4060
+ CadenzaService.emit("meta.socket_client.status_received", status);
4061
+ });
4062
+ socket.on("connect_error", (err) => {
4063
+ runtimeHandle.handshake = false;
4064
+ upsertDiagnostics(
4065
+ {
4066
+ connected: false,
4067
+ handshake: false,
4068
+ connectErrors: state.connectErrors + 1,
4069
+ lastHandshakeError: err.message
4070
+ },
4071
+ err
4072
+ );
4073
+ applySessionOperation("connect", {
4074
+ connected: false,
4075
+ handshake: false,
4076
+ connectErrors: state.connectErrors + 1,
4077
+ lastHandshakeError: err.message
4078
+ });
4079
+ CadenzaService.log(
4080
+ "Socket connect error",
4081
+ {
4082
+ error: err.message,
4083
+ serviceName,
4084
+ socketId: socket.id,
4085
+ url
4086
+ },
4087
+ "error"
4088
+ );
4089
+ CadenzaService.emit(`meta.socket_client.connect_error:${fetchId}`, err);
4090
+ });
4091
+ socket.on("reconnect_attempt", (attempt) => {
4092
+ upsertDiagnostics({ reconnectAttempts: attempt });
4093
+ applySessionOperation("connect", {
4094
+ reconnectAttempts: attempt
4095
+ });
4096
+ CadenzaService.log(`Reconnect attempt: ${attempt}`);
4097
+ });
4098
+ socket.on("reconnect", (attempt) => {
4099
+ upsertDiagnostics({ connected: true });
4100
+ applySessionOperation("connect", {
4101
+ connected: true
4102
+ });
4103
+ CadenzaService.log(`Socket reconnected after ${attempt} tries`, {
4104
+ socketId: socket.id,
4105
+ url,
4106
+ serviceName
4107
+ });
4108
+ });
4109
+ socket.on("reconnect_error", (err) => {
4110
+ runtimeHandle.handshake = false;
4111
+ upsertDiagnostics(
4112
+ {
4113
+ connected: false,
4114
+ handshake: false,
4115
+ reconnectErrors: state.reconnectErrors + 1,
4116
+ lastHandshakeError: err.message
4117
+ },
4118
+ err
4119
+ );
4120
+ applySessionOperation("connect", {
4121
+ connected: false,
4122
+ handshake: false,
4123
+ reconnectErrors: state.reconnectErrors + 1,
4124
+ lastHandshakeError: err.message
4125
+ });
4126
+ CadenzaService.log(
4127
+ "Socket reconnect failed.",
4128
+ { error: err.message, serviceName, url, socketId: socket.id },
4129
+ "warning"
4130
+ );
4131
+ });
4132
+ socket.on("error", (err) => {
4133
+ runtimeHandle.errorCount += 1;
4134
+ upsertDiagnostics(
4135
+ {
4136
+ socketErrors: state.socketErrors + 1,
4137
+ lastHandshakeError: this.getErrorMessage(err)
4138
+ },
4139
+ err
4140
+ );
4141
+ applySessionOperation("transmit", {
4142
+ socketErrors: state.socketErrors + 1,
4143
+ errorCount: runtimeHandle.errorCount,
4144
+ lastHandshakeError: this.getErrorMessage(err)
4145
+ });
4146
+ CadenzaService.log(
4147
+ "Socket error",
4148
+ { error: err, socketId: socket.id, url, serviceName },
4149
+ "error"
4150
+ );
4151
+ CadenzaService.emit("meta.socket_client.error", err);
4152
+ });
4153
+ socket.on("disconnect", () => {
4154
+ const disconnectedAt = (/* @__PURE__ */ new Date()).toISOString();
4155
+ upsertDiagnostics({
4156
+ connected: false,
4157
+ handshake: false,
4158
+ lastDisconnectAt: disconnectedAt
4159
+ });
4160
+ applySessionOperation("connect", {
4161
+ connected: false,
4162
+ handshake: false,
4163
+ lastDisconnectAt: disconnectedAt
4164
+ });
4165
+ CadenzaService.log(
4166
+ "Socket disconnected.",
4167
+ { url, serviceName, socketId: socket.id },
4168
+ "warning"
4169
+ );
4170
+ CadenzaService.emit(`meta.socket_client.disconnected:${fetchId}`, {
4171
+ serviceName,
4172
+ serviceAddress,
4173
+ servicePort: normalizedPort
4174
+ });
4175
+ runtimeHandle.handshake = false;
4176
+ });
4177
+ socket.connect();
4178
+ runtimeHandle.handshakeTask = CadenzaService.createMetaTask(
4179
+ `Socket handshake with ${url}`,
4180
+ async (_ctx, emitter) => {
4181
+ if (runtimeHandle.handshake) return;
4182
+ runtimeHandle.handshake = true;
4183
+ upsertDiagnostics({
4184
+ handshake: true
3565
4185
  });
3566
- };
3567
- const tryEmit = async () => {
3568
- const waitTimeoutMs = timeoutMs > 0 ? timeoutMs + 10 : 1e4;
3569
- const waitResult = await waitForSocketConnection(
3570
- socket,
3571
- waitTimeoutMs,
3572
- (reason, error) => {
3573
- if (reason === "connect_timeout") {
3574
- return `Socket connect timed out before '${event}'`;
3575
- }
3576
- if (reason === "connect_error") {
3577
- const errMessage = error instanceof Error ? error.message : String(error);
3578
- return `Socket connect error before '${event}': ${errMessage}`;
4186
+ applySessionOperation("handshake", {
4187
+ handshake: true
4188
+ });
4189
+ await runtimeHandle.emitWhenReady?.(
4190
+ "handshake",
4191
+ {
4192
+ serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
4193
+ serviceName: CadenzaService.serviceRegistry.serviceName,
4194
+ isFrontend: isBrowser,
4195
+ __status: "success"
4196
+ },
4197
+ 1e4,
4198
+ (result) => {
4199
+ if (result.status === "success") {
4200
+ const handshakeAt = (/* @__PURE__ */ new Date()).toISOString();
4201
+ upsertDiagnostics({
4202
+ connected: true,
4203
+ handshake: true,
4204
+ lastHandshakeAt: handshakeAt,
4205
+ lastHandshakeError: null,
4206
+ socketId: socket.id ?? null
4207
+ });
4208
+ applySessionOperation("handshake", {
4209
+ connected: true,
4210
+ handshake: true,
4211
+ lastHandshakeAt: handshakeAt,
4212
+ lastHandshakeError: null,
4213
+ socketId: socket.id ?? null
4214
+ });
4215
+ CadenzaService.log("Socket client connected", {
4216
+ result,
4217
+ serviceName,
4218
+ socketId: socket.id,
4219
+ url
4220
+ });
4221
+ } else {
4222
+ const errorMessage = result?.__error ?? result?.error ?? "Socket handshake failed";
4223
+ upsertDiagnostics(
4224
+ {
4225
+ connected: false,
4226
+ handshake: false,
4227
+ lastHandshakeError: errorMessage
4228
+ },
4229
+ errorMessage
4230
+ );
4231
+ applySessionOperation("handshake", {
4232
+ connected: false,
4233
+ handshake: false,
4234
+ lastHandshakeError: errorMessage
4235
+ });
4236
+ CadenzaService.log(
4237
+ "Socket handshake failed",
4238
+ { result, serviceName, socketId: socket.id, url },
4239
+ "warning"
4240
+ );
3579
4241
  }
3580
- return `Socket disconnected before '${event}'`;
4242
+ void emitter;
3581
4243
  }
3582
4244
  );
3583
- if (!waitResult.ok) {
3584
- CadenzaService.log(
3585
- waitResult.error,
3586
- { socketId: socket?.id, serviceName, URL, event },
3587
- "error"
3588
- );
3589
- this.recordSocketClientError(
3590
- fetchId,
3591
- serviceName,
3592
- URL,
3593
- waitResult.error
3594
- );
3595
- resolveWithError(waitResult.error);
4245
+ },
4246
+ "Handshakes with socket server"
4247
+ ).doOn(`meta.socket_client.connected:${fetchId}`);
4248
+ runtimeHandle.delegateTask = CadenzaService.createMetaTask(
4249
+ `Delegate flow to Socket service ${url}`,
4250
+ async (delegateCtx, emitter) => {
4251
+ if (delegateCtx.__remoteRoutineName === void 0) {
3596
4252
  return;
3597
4253
  }
3598
- let timer = null;
3599
- if (timeoutMs !== 0) {
3600
- timer = setTimeout(() => {
3601
- if (timer) {
3602
- pendingTimers.delete(timer);
3603
- syncPendingCounts();
3604
- timer = null;
3605
- }
3606
- CadenzaService.log(
3607
- `Socket event '${event}' timed out`,
3608
- { socketId: socket?.id, serviceName, URL },
3609
- "error"
3610
- );
3611
- this.recordSocketClientError(
3612
- fetchId,
3613
- serviceName,
3614
- URL,
3615
- `Socket event '${event}' timed out`
3616
- );
3617
- resolveWithError(`Socket event '${event}' timed out`);
3618
- }, timeoutMs + 10);
3619
- pendingTimers.add(timer);
4254
+ delete delegateCtx.__isSubMeta;
4255
+ delete delegateCtx.__broadcast;
4256
+ const deputyExecId = delegateCtx.__metadata?.__deputyExecId;
4257
+ const requestSentAt = Date.now();
4258
+ if (deputyExecId) {
4259
+ runtimeHandle.pendingDelegationIds.add(deputyExecId);
3620
4260
  syncPendingCounts();
3621
4261
  }
3622
- const connectedSocket = socket;
3623
- if (!connectedSocket) {
3624
- resolveWithError(
3625
- `Socket unavailable before emitting '${event}'`
3626
- );
3627
- return;
3628
- }
3629
- connectedSocket.timeout(timeoutMs).emit(event, data, (err, response) => {
3630
- if (timer) {
3631
- clearTimeout(timer);
3632
- pendingTimers.delete(timer);
3633
- syncPendingCounts();
3634
- timer = null;
4262
+ try {
4263
+ const resultContext = await runtimeHandle.emitWhenReady?.(
4264
+ "delegation",
4265
+ delegateCtx,
4266
+ delegateCtx.__timeout ?? 6e4
4267
+ ) ?? {
4268
+ errored: true,
4269
+ __error: "Socket delegation returned no response"
4270
+ };
4271
+ const requestDuration = Date.now() - requestSentAt;
4272
+ const metadata = resultContext.__metadata;
4273
+ delete resultContext.__metadata;
4274
+ if (deputyExecId) {
4275
+ emitter(`meta.socket_client.delegated:${deputyExecId}`, {
4276
+ ...resultContext,
4277
+ ...metadata,
4278
+ __requestDuration: requestDuration
4279
+ });
3635
4280
  }
3636
- if (err) {
3637
- CadenzaService.log(
3638
- "Socket timeout.",
4281
+ if (resultContext?.errored || resultContext?.failed) {
4282
+ const errorMessage = resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed";
4283
+ upsertDiagnostics(
3639
4284
  {
3640
- event,
3641
- error: err.message,
3642
- socketId: socket?.id,
3643
- serviceName
4285
+ lastHandshakeError: String(errorMessage)
3644
4286
  },
3645
- "warning"
3646
- );
3647
- this.recordSocketClientError(
3648
- fetchId,
3649
- serviceName,
3650
- URL,
3651
- err
4287
+ errorMessage
3652
4288
  );
3653
- response = {
3654
- __error: `Timeout error: ${err}`,
3655
- errored: true,
3656
- ...ctx,
3657
- ...ctx.__metadata
3658
- };
4289
+ applySessionOperation("delegate", {
4290
+ lastHandshakeError: String(errorMessage)
4291
+ });
3659
4292
  }
3660
- if (ack) ack(response);
3661
- resolve(response);
3662
- });
3663
- };
3664
- void tryEmit();
3665
- });
3666
- };
3667
- socket.on("connect", () => {
3668
- if (handshake) return;
3669
- socketDiagnostics.connected = true;
3670
- socketDiagnostics.destroyed = false;
3671
- socketDiagnostics.socketId = socket?.id ?? null;
3672
- socketDiagnostics.updatedAt = Date.now();
3673
- CadenzaService.emit(`meta.socket_client.connected:${fetchId}`, ctx);
3674
- });
3675
- socket.on("delegation_progress", (ctx2) => {
3676
- CadenzaService.emit(
3677
- `meta.socket_client.delegation_progress:${ctx2.__metadata.__deputyExecId}`,
3678
- ctx2
3679
- );
3680
- });
3681
- socket.on("signal", (ctx2) => {
3682
- if (CadenzaService.signalBroker.listObservedSignals().includes(ctx2.__signalName)) {
3683
- CadenzaService.emit(ctx2.__signalName, ctx2);
3684
- }
3685
- });
3686
- socket.on("status_update", (status) => {
3687
- CadenzaService.emit("meta.socket_client.status_received", status);
3688
- });
3689
- socket.on("connect_error", (err) => {
3690
- handshake = false;
3691
- socketDiagnostics.connected = false;
3692
- socketDiagnostics.handshake = false;
3693
- socketDiagnostics.connectErrors++;
3694
- socketDiagnostics.lastHandshakeError = err.message;
3695
- socketDiagnostics.updatedAt = Date.now();
3696
- this.recordSocketClientError(fetchId, serviceName, URL, err);
3697
- CadenzaService.log(
3698
- "Socket connect error",
3699
- { error: err.message, serviceName, socketId: socket?.id, URL },
3700
- "error"
3701
- );
3702
- CadenzaService.emit(`meta.socket_client.connect_error:${fetchId}`, err);
3703
- });
3704
- socket.on("reconnect_attempt", (attempt) => {
3705
- socketDiagnostics.reconnectAttempts = Math.max(
3706
- socketDiagnostics.reconnectAttempts,
3707
- attempt
3708
- );
3709
- socketDiagnostics.updatedAt = Date.now();
3710
- CadenzaService.log(`Reconnect attempt: ${attempt}`);
3711
- });
3712
- socket.on("reconnect", (attempt) => {
3713
- socketDiagnostics.connected = true;
3714
- socketDiagnostics.updatedAt = Date.now();
3715
- CadenzaService.log(`Socket reconnected after ${attempt} tries`, {
3716
- socketId: socket?.id,
3717
- URL,
3718
- serviceName
3719
- });
3720
- });
3721
- socket.on("reconnect_error", (err) => {
3722
- handshake = false;
3723
- socketDiagnostics.connected = false;
3724
- socketDiagnostics.handshake = false;
3725
- socketDiagnostics.reconnectErrors++;
3726
- socketDiagnostics.lastHandshakeError = err.message;
3727
- socketDiagnostics.updatedAt = Date.now();
3728
- this.recordSocketClientError(fetchId, serviceName, URL, err);
3729
- CadenzaService.log(
3730
- "Socket reconnect failed.",
3731
- { error: err.message, serviceName, URL, socketId: socket?.id },
3732
- "warning"
3733
- );
3734
- });
3735
- socket.on("error", (err) => {
3736
- errorCount++;
3737
- socketDiagnostics.socketErrors++;
3738
- socketDiagnostics.updatedAt = Date.now();
3739
- this.recordSocketClientError(fetchId, serviceName, URL, err);
3740
- CadenzaService.log(
3741
- "Socket error",
3742
- { error: err, socketId: socket?.id, URL, serviceName },
3743
- "error"
3744
- );
3745
- CadenzaService.emit("meta.socket_client.error", err);
3746
- });
3747
- socket.on("disconnect", () => {
3748
- const disconnectedAt = (/* @__PURE__ */ new Date()).toISOString();
3749
- socketDiagnostics.connected = false;
3750
- socketDiagnostics.handshake = false;
3751
- socketDiagnostics.lastDisconnectAt = disconnectedAt;
3752
- socketDiagnostics.updatedAt = Date.now();
3753
- CadenzaService.log(
3754
- "Socket disconnected.",
3755
- { URL, serviceName, socketId: socket?.id },
3756
- "warning"
3757
- );
3758
- CadenzaService.emit(`meta.socket_client.disconnected:${fetchId}`, {
3759
- serviceName,
3760
- serviceAddress,
3761
- servicePort
3762
- });
3763
- handshake = false;
3764
- });
3765
- socket.connect();
3766
- handshakeTask = CadenzaService.createMetaTask(
3767
- `Socket handshake with ${URL}`,
3768
- async (ctx2, emit) => {
3769
- if (handshake) return;
3770
- handshake = true;
3771
- socketDiagnostics.handshake = true;
3772
- socketDiagnostics.updatedAt = Date.now();
3773
- await emitWhenReady?.(
3774
- "handshake",
3775
- {
3776
- serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
3777
- serviceName: CadenzaService.serviceRegistry.serviceName,
3778
- isFrontend: isBrowser,
3779
- __status: "success"
3780
- },
3781
- 1e4,
3782
- (result) => {
3783
- if (result.status === "success") {
3784
- socketDiagnostics.connected = true;
3785
- socketDiagnostics.handshake = true;
3786
- socketDiagnostics.lastHandshakeAt = (/* @__PURE__ */ new Date()).toISOString();
3787
- socketDiagnostics.lastHandshakeError = null;
3788
- socketDiagnostics.updatedAt = Date.now();
3789
- CadenzaService.log("Socket client connected", {
3790
- result,
3791
- serviceName,
3792
- socketId: socket?.id,
3793
- URL
4293
+ return resultContext;
4294
+ } catch (error) {
4295
+ const message = error instanceof Error ? error.message : String(error);
4296
+ const failedContext = {
4297
+ errored: true,
4298
+ __error: message
4299
+ };
4300
+ if (deputyExecId) {
4301
+ emitter(`meta.socket_client.delegated:${deputyExecId}`, {
4302
+ ...failedContext,
4303
+ __requestDuration: Date.now() - requestSentAt
3794
4304
  });
3795
- } else {
3796
- socketDiagnostics.connected = false;
3797
- socketDiagnostics.handshake = false;
3798
- socketDiagnostics.lastHandshakeError = result?.__error ?? result?.error ?? "Socket handshake failed";
3799
- socketDiagnostics.updatedAt = Date.now();
3800
- this.recordSocketClientError(
3801
- fetchId,
3802
- serviceName,
3803
- URL,
3804
- socketDiagnostics.lastHandshakeError
3805
- );
3806
- CadenzaService.log(
3807
- "Socket handshake failed",
3808
- { result, serviceName, socketId: socket?.id, URL },
3809
- "warning"
3810
- );
3811
4305
  }
3812
- }
3813
- );
3814
- },
3815
- "Handshakes with socket server"
3816
- ).doOn(`meta.socket_client.connected:${fetchId}`);
3817
- delegateTask = CadenzaService.createMetaTask(
3818
- `Delegate flow to Socket service ${URL}`,
3819
- async (ctx2, emit) => {
3820
- if (ctx2.__remoteRoutineName === void 0) {
3821
- return;
3822
- }
3823
- return new Promise((resolve) => {
3824
- delete ctx2.__isSubMeta;
3825
- delete ctx2.__broadcast;
3826
- const requestSentAt = Date.now();
3827
- pendingDelegationIds.add(ctx2.__metadata.__deputyExecId);
3828
- syncPendingCounts();
3829
- emitWhenReady?.(
3830
- "delegation",
3831
- ctx2,
3832
- ctx2.__timeout ?? 6e4,
3833
- (resultContext) => {
3834
- const requestDuration = Date.now() - requestSentAt;
3835
- const metadata = resultContext.__metadata;
3836
- delete resultContext.__metadata;
3837
- emit(
3838
- `meta.socket_client.delegated:${ctx2.__metadata.__deputyExecId}`,
3839
- {
3840
- ...resultContext,
3841
- ...metadata,
3842
- __requestDuration: requestDuration
3843
- }
3844
- );
3845
- pendingDelegationIds.delete(ctx2.__metadata.__deputyExecId);
4306
+ upsertDiagnostics(
4307
+ {
4308
+ lastHandshakeError: message
4309
+ },
4310
+ error
4311
+ );
4312
+ applySessionOperation("delegate", {
4313
+ lastHandshakeError: message
4314
+ });
4315
+ return failedContext;
4316
+ } finally {
4317
+ if (deputyExecId) {
4318
+ runtimeHandle.pendingDelegationIds.delete(deputyExecId);
3846
4319
  syncPendingCounts();
3847
- if (resultContext?.errored || resultContext?.failed) {
3848
- this.recordSocketClientError(
3849
- fetchId,
3850
- serviceName,
3851
- URL,
3852
- resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed"
3853
- );
3854
- }
3855
- resolve(resultContext);
3856
4320
  }
3857
- );
3858
- });
3859
- },
3860
- `Delegate flow to service ${serviceName} with address ${URL}`
3861
- ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal(
3862
- "meta.socket_client.delegated",
3863
- "meta.socket_shutdown_requested"
3864
- );
3865
- transmitTask = CadenzaService.createMetaTask(
3866
- `Transmit signal to socket server ${URL}`,
3867
- async (ctx2, emit) => {
3868
- if (ctx2.__signalName === void 0) {
3869
- return;
3870
- }
3871
- return new Promise((resolve) => {
3872
- delete ctx2.__broadcast;
3873
- emitWhenReady?.("signal", ctx2, 5e3, (response) => {
3874
- if (ctx2.__routineExecId) {
3875
- emit(
3876
- `meta.socket_client.transmitted:${ctx2.__routineExecId}`,
3877
- response
3878
- );
4321
+ }
4322
+ },
4323
+ `Delegate flow to service ${serviceName} with address ${url}`
4324
+ ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal(
4325
+ "meta.socket_client.delegated",
4326
+ "meta.socket_shutdown_requested"
4327
+ );
4328
+ runtimeHandle.transmitTask = CadenzaService.createMetaTask(
4329
+ `Transmit signal to socket server ${url}`,
4330
+ async (signalCtx, emitter) => {
4331
+ if (signalCtx.__signalName === void 0) {
4332
+ return;
4333
+ }
4334
+ delete signalCtx.__broadcast;
4335
+ const response = await runtimeHandle.emitWhenReady?.("signal", signalCtx, 5e3) ?? {
4336
+ errored: true,
4337
+ __error: "Socket signal transmission returned no response"
4338
+ };
4339
+ applySessionOperation("transmit", {});
4340
+ if (signalCtx.__routineExecId) {
4341
+ emitter(`meta.socket_client.transmitted:${signalCtx.__routineExecId}`, {
4342
+ ...response
4343
+ });
4344
+ }
4345
+ return response;
4346
+ },
4347
+ `Transmits signal to service ${serviceName} with address ${url}`
4348
+ ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal("meta.socket_client.transmitted");
4349
+ CadenzaService.createEphemeralMetaTask(
4350
+ `Shutdown SocketClient ${url}`,
4351
+ (_ctx, emitter) => {
4352
+ runtimeHandle.handshake = false;
4353
+ upsertDiagnostics({
4354
+ connected: false,
4355
+ handshake: false,
4356
+ destroyed: true,
4357
+ pendingDelegations: 0,
4358
+ pendingTimers: 0
4359
+ });
4360
+ applySessionOperation("shutdown", {
4361
+ connected: false,
4362
+ handshake: false,
4363
+ destroyed: true,
4364
+ pendingDelegations: 0,
4365
+ pendingTimers: 0
4366
+ });
4367
+ CadenzaService.log("Shutting down socket client", { url, serviceName });
4368
+ emitter(`meta.fetch.handshake_requested:${fetchId}`, {
4369
+ serviceInstanceId,
4370
+ serviceName,
4371
+ communicationTypes,
4372
+ serviceAddress,
4373
+ servicePort: normalizedPort,
4374
+ protocol,
4375
+ handshakeData: {
4376
+ instanceId: CadenzaService.serviceRegistry.serviceInstanceId,
4377
+ serviceName: CadenzaService.serviceRegistry.serviceName
3879
4378
  }
3880
- resolve(response);
3881
4379
  });
3882
- });
3883
- },
3884
- `Transmits signal to service ${serviceName} with address ${URL}`
3885
- ).doOn(`meta.service_registry.selected_instance_for_socket:${fetchId}`).attachSignal("meta.socket_client.transmitted");
3886
- CadenzaService.createEphemeralMetaTask(
3887
- `Shutdown SocketClient ${URL}`,
3888
- (ctx2, emit) => {
3889
- handshake = false;
3890
- socketDiagnostics.connected = false;
3891
- socketDiagnostics.handshake = false;
3892
- socketDiagnostics.destroyed = true;
3893
- socketDiagnostics.updatedAt = Date.now();
3894
- CadenzaService.log("Shutting down socket client", { URL, serviceName });
3895
- socket?.close();
3896
- handshakeTask?.destroy();
3897
- delegateTask?.destroy();
3898
- transmitTask?.destroy();
3899
- handshakeTask = null;
3900
- delegateTask = null;
3901
- transmitTask = null;
3902
- emitWhenReady = null;
3903
- socket = null;
3904
- emit(`meta.fetch.handshake_requested:${fetchId}`, {
3905
- serviceInstanceId,
3906
- serviceName,
3907
- communicationTypes,
3908
- serviceAddress,
3909
- servicePort,
3910
- protocol,
3911
- handshakeData: {
3912
- instanceId: CadenzaService.serviceRegistry.serviceInstanceId,
3913
- serviceName: CadenzaService.serviceRegistry.serviceName
4380
+ for (const id of runtimeHandle.pendingDelegationIds) {
4381
+ emitter(`meta.socket_client.delegated:${id}`, {
4382
+ errored: true,
4383
+ __error: "Shutting down socket client"
4384
+ });
3914
4385
  }
3915
- });
3916
- for (const id of pendingDelegationIds) {
3917
- emit(`meta.socket_client.delegated:${id}`, {
3918
- errored: true,
3919
- __error: "Shutting down socket client"
4386
+ this.destroySocketClientRuntimeHandle(runtimeHandle);
4387
+ emitter("meta.socket_client.runtime_clear_requested", {
4388
+ fetchId
3920
4389
  });
3921
- }
3922
- pendingDelegationIds.clear();
3923
- syncPendingCounts();
3924
- for (const timer of pendingTimers) {
3925
- clearTimeout(timer);
3926
- }
3927
- pendingTimers.clear();
3928
- syncPendingCounts();
3929
- },
3930
- "Shuts down the socket client"
3931
- ).doOn(
3932
- `meta.socket_shutdown_requested:${fetchId}`,
3933
- `meta.socket_client.disconnected:${fetchId}`,
3934
- `meta.fetch.handshake_failed:${fetchId}`,
3935
- `meta.socket_client.connect_error:${fetchId}`
3936
- ).attachSignal("meta.fetch.handshake_requested").emits("meta.socket_client_shutdown_complete");
3937
- return true;
3938
- },
3939
- "Connects to a specified socket server"
4390
+ },
4391
+ "Shuts down the socket client"
4392
+ ).doOn(
4393
+ `meta.socket_shutdown_requested:${fetchId}`,
4394
+ `meta.socket_client.disconnected:${fetchId}`,
4395
+ `meta.fetch.handshake_failed:${fetchId}`,
4396
+ `meta.socket_client.connect_error:${fetchId}`
4397
+ ).attachSignal("meta.fetch.handshake_requested").emits("meta.socket_client_shutdown_complete");
4398
+ return true;
4399
+ },
4400
+ { mode: "write" }
4401
+ ),
4402
+ "Connects to a specified socket server and wires runtime tasks."
3940
4403
  ).doOn("meta.fetch.handshake_complete").emitsOnFail("meta.socket_client.connect_failed");
3941
4404
  }
3942
- static get instance() {
3943
- if (!this._instance) this._instance = new _SocketController();
3944
- return this._instance;
4405
+ createInitialSocketServerSessionState(serverKey) {
4406
+ return {
4407
+ serverKey,
4408
+ useSocket: false,
4409
+ status: "inactive",
4410
+ securityProfile: "medium",
4411
+ networkType: "internal",
4412
+ connectionCount: 0,
4413
+ lastStartedAt: null,
4414
+ lastConnectedAt: null,
4415
+ lastDisconnectedAt: null,
4416
+ lastShutdownAt: null,
4417
+ updatedAt: 0
4418
+ };
3945
4419
  }
3946
- resolveTransportDiagnosticsOptions(ctx) {
3947
- const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
3948
- const includeErrorHistory = Boolean(ctx.includeErrorHistory);
3949
- const requestedLimit = Number(ctx.errorHistoryLimit);
3950
- const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
4420
+ createInitialSocketClientSessionState() {
3951
4421
  return {
3952
- detailLevel,
3953
- includeErrorHistory,
3954
- errorHistoryLimit
4422
+ fetchId: "",
4423
+ serviceInstanceId: "",
4424
+ communicationTypes: [],
4425
+ serviceName: "",
4426
+ serviceAddress: "",
4427
+ servicePort: 0,
4428
+ protocol: "http",
4429
+ url: "",
4430
+ socketId: null,
4431
+ connected: false,
4432
+ handshake: false,
4433
+ pendingDelegations: 0,
4434
+ pendingTimers: 0,
4435
+ reconnectAttempts: 0,
4436
+ connectErrors: 0,
4437
+ reconnectErrors: 0,
4438
+ socketErrors: 0,
4439
+ errorCount: 0,
4440
+ destroyed: false,
4441
+ lastHandshakeAt: null,
4442
+ lastHandshakeError: null,
4443
+ lastDisconnectAt: null,
4444
+ updatedAt: 0
3955
4445
  };
3956
4446
  }
3957
- ensureSocketClientDiagnostics(fetchId, serviceName, url) {
3958
- let state = this.socketClientDiagnostics.get(fetchId);
3959
- if (!state) {
3960
- state = {
3961
- fetchId,
3962
- serviceName,
3963
- url,
3964
- socketId: null,
3965
- connected: false,
3966
- handshake: false,
3967
- reconnectAttempts: 0,
3968
- connectErrors: 0,
3969
- reconnectErrors: 0,
3970
- socketErrors: 0,
3971
- pendingDelegations: 0,
3972
- pendingTimers: 0,
3973
- destroyed: false,
3974
- lastHandshakeAt: null,
3975
- lastHandshakeError: null,
3976
- lastDisconnectAt: null,
3977
- lastError: null,
3978
- lastErrorAt: 0,
3979
- errorHistory: [],
3980
- updatedAt: Date.now()
3981
- };
3982
- this.socketClientDiagnostics.set(fetchId, state);
3983
- } else {
3984
- state.serviceName = serviceName;
3985
- state.url = url;
4447
+ resolveSocketServerKey(input) {
4448
+ return String(input.serverKey ?? input.__socketServerKey ?? this.socketServerDefaultKey).trim() || this.socketServerDefaultKey;
4449
+ }
4450
+ resolveSocketClientFetchId(input) {
4451
+ const explicitFetchId = String(input.fetchId ?? "").trim();
4452
+ if (explicitFetchId) {
4453
+ return explicitFetchId;
3986
4454
  }
3987
- return state;
4455
+ const serviceAddress = String(input.serviceAddress ?? "").trim();
4456
+ const protocol = String(input.protocol ?? "http").trim();
4457
+ const port = this.resolveServicePort(protocol, input.servicePort);
4458
+ if (!serviceAddress || !port) {
4459
+ return void 0;
4460
+ }
4461
+ return `${serviceAddress}_${port}`;
4462
+ }
4463
+ resolveServicePort(protocol, rawPort) {
4464
+ if (protocol === "https") {
4465
+ return 443;
4466
+ }
4467
+ const parsed = Number(rawPort);
4468
+ if (!Number.isFinite(parsed) || parsed <= 0) {
4469
+ return void 0;
4470
+ }
4471
+ return Math.trunc(parsed);
4472
+ }
4473
+ createSocketServerRuntimeHandleFromContext(context) {
4474
+ const baseServer = context.httpsServer ?? context.httpServer;
4475
+ if (!baseServer) {
4476
+ throw new Error(
4477
+ "Socket server runtime setup requires either httpsServer or httpServer"
4478
+ );
4479
+ }
4480
+ const server = new Server(baseServer, {
4481
+ pingInterval: 3e4,
4482
+ pingTimeout: 2e4,
4483
+ maxHttpBufferSize: 1e7,
4484
+ connectionStateRecovery: {
4485
+ maxDisconnectionDuration: 2 * 60 * 1e3,
4486
+ skipMiddlewares: true
4487
+ }
4488
+ });
4489
+ return {
4490
+ server,
4491
+ initialized: false,
4492
+ connectedSocketIds: /* @__PURE__ */ new Set(),
4493
+ broadcastStatusTask: null,
4494
+ shutdownTask: null
4495
+ };
4496
+ }
4497
+ destroySocketServerRuntimeHandle(runtimeHandle) {
4498
+ if (!runtimeHandle) {
4499
+ return;
4500
+ }
4501
+ runtimeHandle.broadcastStatusTask?.destroy();
4502
+ runtimeHandle.shutdownTask?.destroy();
4503
+ runtimeHandle.broadcastStatusTask = null;
4504
+ runtimeHandle.shutdownTask = null;
4505
+ runtimeHandle.connectedSocketIds.clear();
4506
+ runtimeHandle.initialized = false;
4507
+ runtimeHandle.server.close();
4508
+ runtimeHandle.server.removeAllListeners();
4509
+ }
4510
+ createSocketClientRuntimeHandle(url) {
4511
+ return {
4512
+ url,
4513
+ socket: io(url, {
4514
+ reconnection: true,
4515
+ reconnectionAttempts: 5,
4516
+ reconnectionDelay: 2e3,
4517
+ reconnectionDelayMax: 1e4,
4518
+ randomizationFactor: 0.5,
4519
+ transports: ["websocket"],
4520
+ autoConnect: false
4521
+ }),
4522
+ initialized: false,
4523
+ handshake: false,
4524
+ errorCount: 0,
4525
+ pendingDelegationIds: /* @__PURE__ */ new Set(),
4526
+ pendingTimers: /* @__PURE__ */ new Set(),
4527
+ emitWhenReady: null,
4528
+ handshakeTask: null,
4529
+ delegateTask: null,
4530
+ transmitTask: null
4531
+ };
4532
+ }
4533
+ destroySocketClientRuntimeHandle(runtimeHandle) {
4534
+ if (!runtimeHandle) {
4535
+ return;
4536
+ }
4537
+ runtimeHandle.initialized = false;
4538
+ runtimeHandle.handshake = false;
4539
+ runtimeHandle.emitWhenReady = null;
4540
+ runtimeHandle.handshakeTask?.destroy();
4541
+ runtimeHandle.delegateTask?.destroy();
4542
+ runtimeHandle.transmitTask?.destroy();
4543
+ runtimeHandle.handshakeTask = null;
4544
+ runtimeHandle.delegateTask = null;
4545
+ runtimeHandle.transmitTask = null;
4546
+ for (const timer of runtimeHandle.pendingTimers) {
4547
+ clearTimeout(timer);
4548
+ }
4549
+ runtimeHandle.pendingTimers.clear();
4550
+ runtimeHandle.pendingDelegationIds.clear();
4551
+ runtimeHandle.socket.close();
4552
+ runtimeHandle.socket.removeAllListeners();
4553
+ }
4554
+ normalizeCommunicationTypes(value) {
4555
+ if (!Array.isArray(value)) {
4556
+ return [];
4557
+ }
4558
+ return value.map((item) => String(item)).filter((item) => item.trim().length > 0);
3988
4559
  }
3989
4560
  getErrorMessage(error) {
3990
4561
  if (error instanceof Error) {
@@ -3999,28 +4570,53 @@ var SocketController = class _SocketController {
3999
4570
  return String(error);
4000
4571
  }
4001
4572
  }
4002
- recordSocketClientError(fetchId, serviceName, url, error) {
4003
- const state = this.ensureSocketClientDiagnostics(fetchId, serviceName, url);
4004
- const message = this.getErrorMessage(error);
4005
- const now = Date.now();
4006
- state.lastError = message;
4007
- state.lastErrorAt = now;
4008
- state.updatedAt = now;
4009
- state.errorHistory.push({
4010
- at: new Date(now).toISOString(),
4011
- message
4573
+ pruneDiagnosticsEntries(entries, now = Date.now()) {
4574
+ for (const [fetchId, state] of Object.entries(entries)) {
4575
+ if (state.destroyed && now - state.updatedAt > this.destroyedDiagnosticsTtlMs) {
4576
+ delete entries[fetchId];
4577
+ }
4578
+ }
4579
+ if (Object.keys(entries).length <= this.diagnosticsMaxClientEntries) {
4580
+ return;
4581
+ }
4582
+ const entriesByEvictionPriority = Object.entries(entries).sort((left, right) => {
4583
+ if (left[1].destroyed !== right[1].destroyed) {
4584
+ return left[1].destroyed ? -1 : 1;
4585
+ }
4586
+ return left[1].updatedAt - right[1].updatedAt;
4012
4587
  });
4013
- if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
4014
- state.errorHistory.splice(
4015
- 0,
4016
- state.errorHistory.length - this.diagnosticsErrorHistoryLimit
4017
- );
4588
+ while (Object.keys(entries).length > this.diagnosticsMaxClientEntries && entriesByEvictionPriority.length > 0) {
4589
+ const [fetchId] = entriesByEvictionPriority.shift();
4590
+ delete entries[fetchId];
4018
4591
  }
4019
4592
  }
4020
- collectSocketTransportDiagnostics(ctx) {
4593
+ async getSocketClientDiagnosticsEntry(fetchId) {
4594
+ const normalized = String(fetchId ?? "").trim();
4595
+ if (!normalized) {
4596
+ return void 0;
4597
+ }
4598
+ const snapshot = this.socketClientDiagnosticsActor.getState();
4599
+ const entries = { ...snapshot.entries };
4600
+ this.pruneDiagnosticsEntries(entries);
4601
+ return entries[normalized];
4602
+ }
4603
+ resolveTransportDiagnosticsOptions(ctx) {
4604
+ const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
4605
+ const includeErrorHistory = Boolean(ctx.includeErrorHistory);
4606
+ const requestedLimit = Number(ctx.errorHistoryLimit);
4607
+ const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
4608
+ return {
4609
+ detailLevel,
4610
+ includeErrorHistory,
4611
+ errorHistoryLimit
4612
+ };
4613
+ }
4614
+ collectSocketTransportDiagnostics(ctx, diagnosticsEntries) {
4021
4615
  const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
4022
4616
  const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
4023
- const states = Array.from(this.socketClientDiagnostics.values()).sort(
4617
+ const entries = { ...diagnosticsEntries };
4618
+ this.pruneDiagnosticsEntries(entries);
4619
+ const states = Object.values(entries).sort(
4024
4620
  (a, b) => a.fetchId.localeCompare(b.fetchId)
4025
4621
  );
4026
4622
  const summary = {
@@ -4038,10 +4634,7 @@ var SocketController = class _SocketController {
4038
4634
  0
4039
4635
  ),
4040
4636
  connectErrors: states.reduce((acc, state) => acc + state.connectErrors, 0),
4041
- reconnectErrors: states.reduce(
4042
- (acc, state) => acc + state.reconnectErrors,
4043
- 0
4044
- ),
4637
+ reconnectErrors: states.reduce((acc, state) => acc + state.reconnectErrors, 0),
4045
4638
  socketErrors: states.reduce((acc, state) => acc + state.socketErrors, 0),
4046
4639
  latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
4047
4640
  };
@@ -7528,6 +8121,14 @@ var CadenzaService = class {
7528
8121
  options.isMeta = true;
7529
8122
  this.createDatabaseService(name, schema, description, options);
7530
8123
  }
8124
+ static createActor(spec, options = {}) {
8125
+ this.bootstrap();
8126
+ return new Actor(spec, options);
8127
+ }
8128
+ static createActorFromDefinition(definition, options = {}) {
8129
+ this.bootstrap();
8130
+ return Cadenza.createActorFromDefinition(definition, options);
8131
+ }
7531
8132
  /**
7532
8133
  * Creates and registers a new task with the provided name, function, and optional details.
7533
8134
  *
@@ -7942,6 +8543,7 @@ CadenzaService.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
7942
8543
 
7943
8544
  // src/index.ts
7944
8545
  import {
8546
+ Actor as Actor2,
7945
8547
  DebounceTask as DebounceTask2,
7946
8548
  EphemeralTask as EphemeralTask2,
7947
8549
  GraphRoutine as GraphRoutine2,
@@ -7949,6 +8551,7 @@ import {
7949
8551
  } from "@cadenza.io/core";
7950
8552
  var index_default = CadenzaService;
7951
8553
  export {
8554
+ Actor2 as Actor,
7952
8555
  DatabaseTask,
7953
8556
  DebounceTask2 as DebounceTask,
7954
8557
  DeputyTask,