@cadenza.io/service 2.15.0 → 2.17.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
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  Actor: () => import_core5.Actor,
34
+ DatabaseController: () => DatabaseController,
34
35
  DatabaseTask: () => DatabaseTask,
35
36
  DebounceTask: () => import_core5.DebounceTask,
36
37
  DeputyTask: () => DeputyTask,
@@ -43,6 +44,7 @@ __export(index_exports, {
43
44
  SignalTransmissionTask: () => SignalTransmissionTask,
44
45
  SocketController: () => SocketController,
45
46
  Task: () => import_core5.Task,
47
+ createSSRInquiryBridge: () => createSSRInquiryBridge,
46
48
  default: () => index_default
47
49
  });
48
50
  module.exports = __toCommonJS(index_exports);
@@ -87,7 +89,7 @@ var DeputyTask = class extends import_core.Task {
87
89
  return;
88
90
  }
89
91
  if (context.__metadata.__skipRemoteExecution) {
90
- resolve(true);
92
+ resolve(context);
91
93
  return;
92
94
  }
93
95
  const processId = (0, import_uuid.v4)();
@@ -190,6 +192,8 @@ var DeputyTask = class extends import_core.Task {
190
192
  __executionTraceId: metadata.__executionTraceId ?? null,
191
193
  __metadata: {
192
194
  ...metadata,
195
+ __skipRemoteExecution: metadata.__skipRemoteExecution ?? ctx.__skipRemoteExecution ?? false,
196
+ __blockRemoteExecution: metadata.__blockRemoteExecution ?? ctx.__blockRemoteExecution ?? false,
193
197
  __deputyTaskName: this.name
194
198
  },
195
199
  ...ctx
@@ -268,6 +272,7 @@ var DatabaseTask = class extends DeputyTask {
268
272
  const dynamicQueryData = ctx.queryData ?? {};
269
273
  delete ctx.queryData;
270
274
  const deputyContext = {
275
+ ...ctx,
271
276
  __localTaskName: this.name,
272
277
  __localTaskVersion: this.version,
273
278
  __localServiceName: CadenzaService.serviceRegistry.serviceName,
@@ -278,6 +283,8 @@ var DatabaseTask = class extends DeputyTask {
278
283
  __localRoutineExecId: metadata.__routineExecId ?? metadata.__metadata?.__routineExecId,
279
284
  __metadata: {
280
285
  ...metadata,
286
+ __skipRemoteExecution: metadata.__skipRemoteExecution ?? ctx.__skipRemoteExecution ?? false,
287
+ __blockRemoteExecution: metadata.__blockRemoteExecution ?? ctx.__blockRemoteExecution ?? false,
281
288
  __deputyTaskName: this.name
282
289
  },
283
290
  queryData: {
@@ -364,6 +371,167 @@ function summarizeResponderStatuses(statuses) {
364
371
  return { responded, failed, timedOut, pending };
365
372
  }
366
373
 
374
+ // src/utils/transport.ts
375
+ var DEFAULT_PROTOCOLS = ["rest", "socket"];
376
+ function normalizeString(value) {
377
+ return typeof value === "string" ? value.trim() : "";
378
+ }
379
+ function normalizeTransportProtocols(value) {
380
+ const rawValues = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
381
+ const normalized = rawValues.map((entry) => normalizeString(entry)).filter(
382
+ (entry) => entry === "rest" || entry === "socket"
383
+ );
384
+ return Array.from(new Set(normalized));
385
+ }
386
+ function normalizeTransportOrigin(origin) {
387
+ const raw = normalizeString(origin);
388
+ if (!raw) {
389
+ return null;
390
+ }
391
+ let parsed;
392
+ try {
393
+ parsed = new URL(raw);
394
+ } catch {
395
+ return null;
396
+ }
397
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
398
+ return null;
399
+ }
400
+ if (parsed.pathname && parsed.pathname !== "/") {
401
+ return null;
402
+ }
403
+ if (parsed.search || parsed.hash) {
404
+ return null;
405
+ }
406
+ return parsed.origin;
407
+ }
408
+ function normalizeSecurityProfile(value) {
409
+ const normalized = normalizeString(value);
410
+ if (normalized === "low" || normalized === "medium" || normalized === "high") {
411
+ return normalized;
412
+ }
413
+ return null;
414
+ }
415
+ function normalizeServiceTransportConfig(value) {
416
+ const raw = value ?? {};
417
+ const role = normalizeString(raw.role);
418
+ const origin = normalizeTransportOrigin(raw.origin);
419
+ const protocols = normalizeTransportProtocols(raw.protocols);
420
+ if (!origin) {
421
+ return null;
422
+ }
423
+ if (role !== "internal" && role !== "public") {
424
+ return null;
425
+ }
426
+ return {
427
+ role,
428
+ origin,
429
+ protocols: protocols.length > 0 ? protocols : [...DEFAULT_PROTOCOLS],
430
+ securityProfile: normalizeSecurityProfile(raw.securityProfile),
431
+ authStrategy: normalizeString(raw.authStrategy) || null
432
+ };
433
+ }
434
+ function normalizeServiceTransportDescriptor(value) {
435
+ const raw = value ?? {};
436
+ const uuid5 = normalizeString(raw.uuid);
437
+ const serviceInstanceId = normalizeString(
438
+ raw.serviceInstanceId ?? raw.service_instance_id
439
+ );
440
+ const config = normalizeServiceTransportConfig(raw);
441
+ if (!uuid5 || !serviceInstanceId || !config) {
442
+ return null;
443
+ }
444
+ return {
445
+ uuid: uuid5,
446
+ serviceInstanceId,
447
+ role: config.role,
448
+ origin: config.origin,
449
+ protocols: config.protocols ?? [...DEFAULT_PROTOCOLS],
450
+ securityProfile: config.securityProfile ?? null,
451
+ authStrategy: config.authStrategy ?? null,
452
+ deleted: Boolean(raw.deleted),
453
+ clientCreated: Boolean(raw.clientCreated ?? raw.client_created ?? false)
454
+ };
455
+ }
456
+ function transportSupportsProtocol(transport, protocol) {
457
+ return !!transport && transport.protocols.includes(protocol);
458
+ }
459
+ function selectTransportForRole(transports, role, protocol) {
460
+ const filtered = transports.filter(
461
+ (transport) => !transport.deleted && transport.role === role && (!protocol || transportSupportsProtocol(transport, protocol))
462
+ );
463
+ return filtered[0];
464
+ }
465
+ function buildTransportClientKey(transport) {
466
+ return transport.uuid;
467
+ }
468
+ function parseTransportOrigin(origin) {
469
+ const normalized = normalizeTransportOrigin(origin);
470
+ if (!normalized) {
471
+ return null;
472
+ }
473
+ const parsed = new URL(normalized);
474
+ const protocol = parsed.protocol === "https:" ? "https" : "http";
475
+ const port = parsed.port ? Number(parsed.port) : protocol === "https" ? 443 : 80;
476
+ return {
477
+ protocol,
478
+ hostname: parsed.hostname,
479
+ port
480
+ };
481
+ }
482
+
483
+ // src/utils/serviceInstance.ts
484
+ function normalizeString2(value) {
485
+ return typeof value === "string" ? value.trim() : "";
486
+ }
487
+ function normalizeTransportArray(value, serviceInstanceId) {
488
+ if (!Array.isArray(value)) {
489
+ return [];
490
+ }
491
+ return value.map(
492
+ (entry) => normalizeServiceTransportDescriptor({
493
+ ...entry ?? {},
494
+ service_instance_id: entry?.service_instance_id ?? entry?.serviceInstanceId ?? serviceInstanceId
495
+ })
496
+ ).filter((transport) => !!transport).sort((left, right) => left.origin.localeCompare(right.origin));
497
+ }
498
+ function normalizeServiceInstanceDescriptor(value) {
499
+ const raw = value ?? {};
500
+ const uuid5 = normalizeString2(raw.uuid);
501
+ const serviceName = normalizeString2(raw.serviceName ?? raw.service_name);
502
+ if (!uuid5 || !serviceName) {
503
+ return null;
504
+ }
505
+ const transports = normalizeTransportArray(raw.transports, uuid5);
506
+ return {
507
+ uuid: uuid5,
508
+ serviceName,
509
+ numberOfRunningGraphs: Math.max(
510
+ 0,
511
+ Math.trunc(
512
+ Number(raw.numberOfRunningGraphs ?? raw.number_of_running_graphs ?? 0) || 0
513
+ )
514
+ ),
515
+ isPrimary: Boolean(raw.isPrimary ?? raw.is_primary ?? false),
516
+ isActive: Boolean(raw.isActive ?? raw.is_active ?? true),
517
+ isNonResponsive: Boolean(
518
+ raw.isNonResponsive ?? raw.is_non_responsive ?? false
519
+ ),
520
+ isBlocked: Boolean(raw.isBlocked ?? raw.is_blocked ?? false),
521
+ runtimeState: raw.runtimeState === "healthy" || raw.runtimeState === "degraded" || raw.runtimeState === "overloaded" || raw.runtimeState === "unavailable" ? raw.runtimeState : void 0,
522
+ acceptingWork: typeof raw.acceptingWork === "boolean" ? raw.acceptingWork : void 0,
523
+ reportedAt: typeof raw.reportedAt === "string" ? raw.reportedAt : typeof raw.reported_at === "string" ? raw.reported_at : void 0,
524
+ health: raw.health ?? {},
525
+ isFrontend: Boolean(raw.isFrontend ?? raw.is_frontend ?? false),
526
+ isDatabase: Boolean(raw.isDatabase ?? raw.is_database ?? false),
527
+ transports,
528
+ clientCreatedTransportIds: Array.isArray(raw.clientCreatedTransportIds) ? raw.clientCreatedTransportIds.map((entry) => normalizeString2(entry)).filter((entry) => entry.length > 0) : void 0
529
+ };
530
+ }
531
+ function getRouteableTransport(instance, role, protocol) {
532
+ return selectTransportForRole(instance.transports ?? [], role, protocol);
533
+ }
534
+
367
535
  // src/utils/readiness.ts
368
536
  function evaluateDependencyReadiness(input) {
369
537
  const missedHeartbeats = Math.max(
@@ -610,6 +778,7 @@ var ServiceRegistry = class _ServiceRegistry {
610
778
  this.numberOfRunningGraphs = 0;
611
779
  this.useSocket = false;
612
780
  this.retryCount = 3;
781
+ this.isFrontend = false;
613
782
  CadenzaService.defineIntent({
614
783
  name: META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT,
615
784
  description: "Gather transport diagnostics across all services and communication clients.",
@@ -757,57 +926,60 @@ var ServiceRegistry = class _ServiceRegistry {
757
926
  this.handleInstanceUpdateTask = CadenzaService.createMetaTask(
758
927
  "Handle Instance Update",
759
928
  (ctx, emit) => {
760
- const serviceInstance = ctx.serviceInstance ?? (ctx.__serviceInstanceId || ctx.serviceInstanceId ? {
761
- uuid: ctx.__serviceInstanceId ?? ctx.serviceInstanceId,
762
- serviceName: ctx.__serviceName ?? ctx.serviceName,
763
- address: ctx.serviceAddress ?? "",
764
- port: ctx.servicePort ?? 0,
765
- exposed: !!ctx.exposed,
766
- isFrontend: !!ctx.isFrontend,
767
- isActive: typeof ctx.isActive === "boolean" ? ctx.isActive : typeof ctx.__active === "boolean" ? ctx.__active : true,
768
- isNonResponsive: !!ctx.isNonResponsive,
769
- isBlocked: !!ctx.isBlocked,
770
- health: ctx.health ?? ctx.__health ?? {},
771
- numberOfRunningGraphs: ctx.numberOfRunningGraphs ?? ctx.__numberOfRunningGraphs ?? 0,
772
- isPrimary: false
773
- } : void 0);
774
- if (!serviceInstance?.uuid || !serviceInstance?.serviceName) {
929
+ const serviceInstance = normalizeServiceInstanceDescriptor(
930
+ ctx.serviceInstance ?? ctx.data ?? ctx.queryData?.data ?? (ctx.__serviceInstanceId || ctx.serviceInstanceId ? {
931
+ uuid: ctx.__serviceInstanceId ?? ctx.serviceInstanceId,
932
+ serviceName: ctx.__serviceName ?? ctx.serviceName,
933
+ isFrontend: !!ctx.isFrontend,
934
+ isActive: typeof ctx.isActive === "boolean" ? ctx.isActive : typeof ctx.__active === "boolean" ? ctx.__active : true,
935
+ isNonResponsive: !!ctx.isNonResponsive,
936
+ isBlocked: !!ctx.isBlocked,
937
+ health: ctx.health ?? ctx.__health ?? {},
938
+ numberOfRunningGraphs: ctx.numberOfRunningGraphs ?? ctx.__numberOfRunningGraphs ?? 0,
939
+ isPrimary: false,
940
+ transports: ctx.transports ?? []
941
+ } : void 0)
942
+ );
943
+ if (!serviceInstance) {
775
944
  return false;
776
945
  }
777
- const {
778
- uuid: uuid4,
779
- serviceName,
780
- address,
781
- port,
782
- exposed,
783
- isFrontend,
784
- deleted
785
- } = serviceInstance;
786
- if (uuid4 === this.serviceInstanceId) return;
946
+ const uuid5 = serviceInstance.uuid;
947
+ const serviceName = serviceInstance.serviceName;
948
+ const deleted = Boolean(
949
+ ctx.deleted ?? ctx.serviceInstance?.deleted ?? ctx.data?.deleted
950
+ );
951
+ if (uuid5 === this.serviceInstanceId) return;
787
952
  if (deleted) {
788
- const indexToDelete = this.instances.get(serviceName)?.findIndex((i) => i.uuid === uuid4) ?? -1;
789
- if (indexToDelete >= 0) {
953
+ const existingInstance = this.instances.get(serviceName)?.find((instance) => instance.uuid === uuid5);
954
+ const indexToDelete = this.instances.get(serviceName)?.findIndex((i) => i.uuid === uuid5) ?? -1;
955
+ if (indexToDelete >= 0 && existingInstance) {
790
956
  this.instances.get(serviceName)?.splice(indexToDelete, 1);
957
+ for (const transport of existingInstance.transports) {
958
+ const transportKey = buildTransportClientKey(transport);
959
+ emit(`meta.socket_shutdown_requested:${transportKey}`, {});
960
+ emit(`meta.fetch.destroy_requested:${transportKey}`, {});
961
+ }
791
962
  }
792
963
  if (this.instances.get(serviceName)?.length === 0) {
793
964
  this.instances.delete(serviceName);
794
- } else if (this.instances.get(serviceName)?.filter((i) => i.address === address && i.port === port).length === 0) {
795
- emit(`meta.socket_shutdown_requested:${address}_${port}`, {});
796
- emit(`meta.fetch.destroy_requested:${address}_${port}`, {});
797
965
  }
798
- this.unregisterDependee(uuid4, serviceName);
966
+ this.unregisterDependee(uuid5, serviceName);
799
967
  return;
800
968
  }
801
969
  if (!this.instances.has(serviceName))
802
970
  this.instances.set(serviceName, []);
803
971
  const instances = this.instances.get(serviceName);
804
- const existing = instances.find((i) => i.uuid === uuid4);
972
+ const existing = instances.find((i) => i.uuid === uuid5);
805
973
  if (existing) {
806
- Object.assign(existing, serviceInstance);
974
+ Object.assign(existing, {
975
+ ...serviceInstance,
976
+ transports: serviceInstance.transports.length > 0 ? serviceInstance.transports : existing.transports,
977
+ clientCreatedTransportIds: existing.clientCreatedTransportIds ?? []
978
+ });
807
979
  } else {
808
980
  instances.push(serviceInstance);
809
981
  }
810
- const trackedInstance = existing ?? instances.find((instance) => instance.uuid === uuid4);
982
+ const trackedInstance = existing ?? instances.find((instance) => instance.uuid === uuid5);
811
983
  if (trackedInstance) {
812
984
  const snapshot = this.resolveRuntimeStatusSnapshot(
813
985
  trackedInstance.numberOfRunningGraphs ?? 0,
@@ -822,31 +994,44 @@ var ServiceRegistry = class _ServiceRegistry {
822
994
  if (this.serviceName === serviceName) {
823
995
  return false;
824
996
  }
825
- if (!isFrontend && (this.deputies.has(serviceName) || this.remoteIntents.has(serviceName)) || this.remoteSignals.has(serviceName)) {
826
- const clientCreated = instances?.some(
827
- (i) => i.address === address && i.port === port && i.clientCreated && i.isActive
997
+ const trackedTransport = this.getRouteableTransport(
998
+ trackedInstance,
999
+ this.useSocket ? "socket" : "rest"
1000
+ );
1001
+ if (!serviceInstance.isFrontend && (this.deputies.has(serviceName) || this.remoteIntents.has(serviceName)) || this.remoteSignals.has(serviceName)) {
1002
+ const communicationTypes = Array.from(
1003
+ new Set(
1004
+ this.deputies.get(serviceName)?.map((d) => d.communicationType) ?? []
1005
+ )
828
1006
  );
829
- if (!clientCreated) {
830
- const communicationTypes = Array.from(
831
- new Set(
832
- this.deputies.get(serviceName)?.map((d) => d.communicationType) ?? []
833
- )
1007
+ if (!communicationTypes.includes("signal") && this.remoteSignals.has(serviceName)) {
1008
+ communicationTypes.push("signal");
1009
+ }
1010
+ if (trackedTransport) {
1011
+ const clientCreated = this.hasTransportClientCreated(
1012
+ trackedInstance,
1013
+ trackedTransport.uuid
834
1014
  );
835
- if (!communicationTypes.includes("signal") && this.remoteSignals.has(serviceName)) {
836
- communicationTypes.push("signal");
1015
+ if (!clientCreated) {
1016
+ emit("meta.service_registry.dependee_registered", {
1017
+ serviceName,
1018
+ serviceInstanceId: uuid5,
1019
+ serviceTransportId: trackedTransport.uuid,
1020
+ serviceOrigin: trackedTransport.origin,
1021
+ transportProtocols: trackedTransport.protocols,
1022
+ communicationTypes
1023
+ });
1024
+ this.markTransportClientCreated(
1025
+ trackedInstance,
1026
+ trackedTransport.uuid
1027
+ );
837
1028
  }
838
- emit("meta.service_registry.dependee_registered", {
1029
+ } else {
1030
+ emit("meta.service_registry.routeable_transport_missing", {
839
1031
  serviceName,
840
- serviceInstanceId: uuid4,
841
- serviceAddress: address,
842
- servicePort: port,
843
- protocol: exposed ? "https" : "http",
844
- communicationTypes
845
- });
846
- instances?.filter(
847
- (i) => i.address === address && i.port === port && i.isActive
848
- ).forEach((i) => {
849
- i.clientCreated = true;
1032
+ serviceInstanceId: uuid5,
1033
+ requiredRole: this.getRoutingTransportRole(),
1034
+ isFrontend: this.isFrontend
850
1035
  });
851
1036
  }
852
1037
  }
@@ -864,6 +1049,81 @@ var ServiceRegistry = class _ServiceRegistry {
864
1049
  "meta.socket_shutdown_requested",
865
1050
  "meta.fetch.destroy_requested"
866
1051
  );
1052
+ this.handleTransportUpdateTask = CadenzaService.createMetaTask(
1053
+ "Handle Transport Update",
1054
+ (ctx, emit) => {
1055
+ const transport = normalizeServiceTransportDescriptor(
1056
+ ctx.serviceTransport ?? ctx.data ?? ctx.queryData?.data ?? ctx
1057
+ );
1058
+ if (!transport) {
1059
+ return false;
1060
+ }
1061
+ let ownerInstance;
1062
+ for (const instances of this.instances.values()) {
1063
+ ownerInstance = instances.find(
1064
+ (instance) => instance.uuid === transport.serviceInstanceId
1065
+ );
1066
+ if (ownerInstance) {
1067
+ break;
1068
+ }
1069
+ }
1070
+ if (!ownerInstance) {
1071
+ return false;
1072
+ }
1073
+ if (transport.deleted) {
1074
+ ownerInstance.transports = ownerInstance.transports.filter(
1075
+ (existingTransport2) => existingTransport2.uuid !== transport.uuid
1076
+ );
1077
+ const transportKey = buildTransportClientKey(transport);
1078
+ emit(`meta.socket_shutdown_requested:${transportKey}`, {});
1079
+ emit(`meta.fetch.destroy_requested:${transportKey}`, {});
1080
+ return true;
1081
+ }
1082
+ const existingTransport = this.getTransportById(ownerInstance, transport.uuid);
1083
+ if (existingTransport) {
1084
+ Object.assign(existingTransport, transport);
1085
+ } else {
1086
+ ownerInstance.transports.push(transport);
1087
+ }
1088
+ if (ownerInstance.uuid === this.serviceInstanceId) {
1089
+ return true;
1090
+ }
1091
+ const hasRemoteInterest = (!ownerInstance.isFrontend && (this.deputies.has(ownerInstance.serviceName) || this.remoteIntents.has(ownerInstance.serviceName)) || this.remoteSignals.has(ownerInstance.serviceName)) && transport.role === this.getRoutingTransportRole();
1092
+ if (!hasRemoteInterest) {
1093
+ return true;
1094
+ }
1095
+ if (!this.hasTransportClientCreated(ownerInstance, transport.uuid)) {
1096
+ const communicationTypes = Array.from(
1097
+ new Set(
1098
+ this.deputies.get(ownerInstance.serviceName)?.map((descriptor) => descriptor.communicationType) ?? []
1099
+ )
1100
+ );
1101
+ if (!communicationTypes.includes("signal") && this.remoteSignals.has(ownerInstance.serviceName)) {
1102
+ communicationTypes.push("signal");
1103
+ }
1104
+ emit("meta.service_registry.dependee_registered", {
1105
+ serviceName: ownerInstance.serviceName,
1106
+ serviceInstanceId: ownerInstance.uuid,
1107
+ serviceTransportId: transport.uuid,
1108
+ serviceOrigin: transport.origin,
1109
+ transportProtocols: transport.protocols,
1110
+ communicationTypes
1111
+ });
1112
+ this.markTransportClientCreated(ownerInstance, transport.uuid);
1113
+ }
1114
+ return true;
1115
+ },
1116
+ "Handles service transport updates independently from instance rows."
1117
+ ).doOn(
1118
+ "global.meta.service_instance_transport.inserted",
1119
+ "global.meta.service_instance_transport.updated",
1120
+ "meta.service_instance_transport.inserted",
1121
+ "meta.service_instance_transport.updated"
1122
+ ).attachSignal(
1123
+ "meta.service_registry.dependee_registered",
1124
+ "meta.socket_shutdown_requested",
1125
+ "meta.fetch.destroy_requested"
1126
+ );
867
1127
  CadenzaService.createMetaTask(
868
1128
  "Track dependee registration",
869
1129
  (ctx) => {
@@ -959,17 +1219,25 @@ var ServiceRegistry = class _ServiceRegistry {
959
1219
  this.handleServiceNotRespondingTask = CadenzaService.createMetaTask(
960
1220
  "Handle service not responding",
961
1221
  (ctx, emit) => {
962
- const { serviceName, serviceAddress, servicePort } = ctx;
1222
+ const { serviceName, serviceInstanceId, serviceTransportId } = ctx;
963
1223
  const serviceInstances = this.instances.get(serviceName);
964
- const instances = serviceInstances?.filter(
965
- (i) => i.address === serviceAddress && i.port === servicePort
966
- );
1224
+ const instances = serviceInstances?.filter((instance) => {
1225
+ if (serviceInstanceId && instance.uuid === serviceInstanceId) {
1226
+ return true;
1227
+ }
1228
+ if (serviceTransportId) {
1229
+ return instance.transports.some(
1230
+ (transport) => transport.uuid === serviceTransportId
1231
+ );
1232
+ }
1233
+ return false;
1234
+ });
967
1235
  CadenzaService.log(
968
1236
  "Service not responding.",
969
1237
  {
970
1238
  serviceName,
971
- serviceAddress,
972
- servicePort,
1239
+ serviceInstanceId,
1240
+ serviceTransportId,
973
1241
  instances
974
1242
  },
975
1243
  "warning",
@@ -1010,7 +1278,7 @@ var ServiceRegistry = class _ServiceRegistry {
1010
1278
  this.handleServiceHandshakeTask = CadenzaService.createMetaTask(
1011
1279
  "Handle service handshake",
1012
1280
  (ctx, emit) => {
1013
- const { serviceName, serviceInstanceId, serviceAddress, servicePort } = ctx;
1281
+ const { serviceName, serviceInstanceId } = ctx;
1014
1282
  const serviceInstances = this.instances.get(serviceName);
1015
1283
  const instance = serviceInstances?.find(
1016
1284
  (i) => i.uuid === serviceInstanceId
@@ -1038,26 +1306,6 @@ var ServiceRegistry = class _ServiceRegistry {
1038
1306
  uuid: instance.uuid
1039
1307
  }
1040
1308
  });
1041
- const instancesToDelete = serviceInstances?.filter(
1042
- (i) => i.uuid !== serviceInstanceId && i.address === serviceAddress && i.port === servicePort
1043
- );
1044
- for (const i of instancesToDelete ?? []) {
1045
- const indexToDelete = this.instances.get(serviceName)?.indexOf(i) ?? -1;
1046
- if (indexToDelete >= 0) {
1047
- this.instances.get(serviceName)?.splice(indexToDelete, 1);
1048
- }
1049
- this.unregisterDependee(i.uuid, serviceName);
1050
- emit("global.meta.service_registry.deleted", {
1051
- data: {
1052
- isActive: false,
1053
- isNonResponsive: false,
1054
- deleted: true
1055
- },
1056
- filter: {
1057
- uuid: i.uuid
1058
- }
1059
- });
1060
- }
1061
1309
  return true;
1062
1310
  },
1063
1311
  "Handles service handshake"
@@ -1076,16 +1324,13 @@ var ServiceRegistry = class _ServiceRegistry {
1076
1324
  return false;
1077
1325
  }
1078
1326
  let applied = this.applyRuntimeStatusReport(report);
1079
- if (!applied && report.serviceAddress && typeof report.servicePort === "number") {
1327
+ if (!applied && report.transportId && report.transportOrigin) {
1080
1328
  if (!this.instances.has(report.serviceName)) {
1081
1329
  this.instances.set(report.serviceName, []);
1082
1330
  }
1083
1331
  this.instances.get(report.serviceName).push({
1084
1332
  uuid: report.serviceInstanceId,
1085
1333
  serviceName: report.serviceName,
1086
- address: report.serviceAddress,
1087
- port: report.servicePort,
1088
- exposed: !!report.exposed,
1089
1334
  isFrontend: !!report.isFrontend,
1090
1335
  isActive: report.isActive,
1091
1336
  isNonResponsive: report.isNonResponsive,
@@ -1095,7 +1340,18 @@ var ServiceRegistry = class _ServiceRegistry {
1095
1340
  acceptingWork: report.acceptingWork,
1096
1341
  reportedAt: report.reportedAt,
1097
1342
  health: report.health ?? {},
1098
- isPrimary: false
1343
+ isPrimary: false,
1344
+ transports: [
1345
+ {
1346
+ uuid: report.transportId,
1347
+ serviceInstanceId: report.serviceInstanceId,
1348
+ role: this.getRoutingTransportRole(),
1349
+ origin: report.transportOrigin,
1350
+ protocols: report.transportProtocols && report.transportProtocols.length > 0 ? report.transportProtocols : ["rest", "socket"],
1351
+ securityProfile: null,
1352
+ authStrategy: null
1353
+ }
1354
+ ]
1099
1355
  });
1100
1356
  applied = true;
1101
1357
  }
@@ -1136,21 +1392,9 @@ var ServiceRegistry = class _ServiceRegistry {
1136
1392
  deleted: !!m.deleted
1137
1393
  })
1138
1394
  );
1139
- const serviceInstances = (inquiryResult.serviceInstances ?? []).filter(
1140
- (instance) => !instance.deleted && !!instance.isActive && !instance.isNonResponsive && !instance.isBlocked
1141
- ).map((instance) => ({
1142
- uuid: instance.uuid,
1143
- address: instance.address,
1144
- port: instance.port,
1145
- serviceName: instance.serviceName,
1146
- isActive: !!instance.isActive,
1147
- isNonResponsive: !!instance.isNonResponsive,
1148
- isBlocked: !!instance.isBlocked,
1149
- health: instance.health ?? {},
1150
- exposed: !!instance.exposed,
1151
- created: instance.created,
1152
- isFrontend: !!instance.isFrontend
1153
- }));
1395
+ const serviceInstances = (inquiryResult.serviceInstances ?? []).map((instance) => normalizeServiceInstanceDescriptor(instance)).filter(
1396
+ (instance) => !!instance && !!instance.isActive && !instance.isNonResponsive && !instance.isBlocked
1397
+ );
1154
1398
  return {
1155
1399
  ...ctx,
1156
1400
  signalToTaskMaps,
@@ -1237,7 +1481,19 @@ var ServiceRegistry = class _ServiceRegistry {
1237
1481
  const { __serviceName, __triedInstances, __retries, __broadcast } = context;
1238
1482
  let retries = __retries ?? 0;
1239
1483
  let triedInstances = __triedInstances ?? [];
1240
- const instances = this.instances.get(__serviceName)?.filter((i) => i.isActive && !i.isNonResponsive && !i.isBlocked).sort((a, b) => {
1484
+ const preferredRole = this.getRoutingTransportRole();
1485
+ const instances = this.instances.get(__serviceName)?.filter((instance) => {
1486
+ if (!instance.isActive || instance.isNonResponsive || instance.isBlocked) {
1487
+ return false;
1488
+ }
1489
+ return Boolean(
1490
+ this.getRouteableTransport(
1491
+ instance,
1492
+ this.useSocket ? "socket" : "rest",
1493
+ preferredRole
1494
+ ) ?? this.getRouteableTransport(instance, "rest", preferredRole)
1495
+ );
1496
+ }).sort((a, b) => {
1241
1497
  const leftStatus = this.resolveRuntimeStatusSnapshot(
1242
1498
  a.numberOfRunningGraphs ?? 0,
1243
1499
  a.isActive,
@@ -1258,9 +1514,7 @@ var ServiceRegistry = class _ServiceRegistry {
1258
1514
  });
1259
1515
  if (!instances || instances.length === 0 || retries > this.retryCount) {
1260
1516
  context.errored = true;
1261
- context.__error = `No active instances for ${__serviceName}. Retries: ${retries}. ${this.instances.get(
1262
- __serviceName
1263
- )}`;
1517
+ context.__error = this.isFrontend && preferredRole === "public" ? `No public transport available for ${__serviceName}.` : `No routeable ${preferredRole} transport available for ${__serviceName}. Retries: ${retries}.`;
1264
1518
  emit(
1265
1519
  `meta.service_registry.load_balance_failed:${context.__metadata.__deputyExecId}`,
1266
1520
  context
@@ -1269,10 +1523,21 @@ var ServiceRegistry = class _ServiceRegistry {
1269
1523
  }
1270
1524
  if (__broadcast || instances[0].isFrontend) {
1271
1525
  for (const instance of instances) {
1272
- const socketKey = instance.isFrontend ? instance.address : `${instance.address}_${instance.port}`;
1526
+ const selectedTransport2 = this.getRouteableTransport(instance, "socket", preferredRole) ?? this.getRouteableTransport(instance, "rest", preferredRole);
1527
+ if (!selectedTransport2) {
1528
+ continue;
1529
+ }
1530
+ const transportKey = buildTransportClientKey(selectedTransport2);
1273
1531
  emit(
1274
- `meta.service_registry.selected_instance_for_socket:${socketKey}`,
1275
- context
1532
+ `${this.useSocket && transportSupportsProtocol(selectedTransport2, "socket") ? "meta.service_registry.selected_instance_for_socket" : "meta.service_registry.selected_instance_for_fetch"}:${transportKey}`,
1533
+ {
1534
+ ...context,
1535
+ __instance: instance.uuid,
1536
+ __transportId: selectedTransport2.uuid,
1537
+ __transportOrigin: selectedTransport2.origin,
1538
+ __transportProtocols: selectedTransport2.protocols,
1539
+ __fetchId: transportKey
1540
+ }
1276
1541
  );
1277
1542
  }
1278
1543
  return context;
@@ -1295,12 +1560,25 @@ var ServiceRegistry = class _ServiceRegistry {
1295
1560
  if (retries > 0) {
1296
1561
  selected = instancesToTry[Math.floor(Math.random() * instancesToTry.length)];
1297
1562
  }
1563
+ const selectedTransport = this.getRouteableTransport(selected, "socket", preferredRole) ?? this.getRouteableTransport(selected, "rest", preferredRole);
1564
+ if (!selectedTransport) {
1565
+ context.errored = true;
1566
+ context.__error = `No routeable ${preferredRole} transport available for ${selected.serviceName}/${selected.uuid}.`;
1567
+ emit(
1568
+ `meta.service_registry.load_balance_failed:${context.__metadata.__deputyExecId}`,
1569
+ context
1570
+ );
1571
+ return context;
1572
+ }
1298
1573
  context.__instance = selected.uuid;
1299
- context.__fetchId = `${selected.address}_${selected.port}`;
1574
+ context.__transportId = selectedTransport.uuid;
1575
+ context.__transportOrigin = selectedTransport.origin;
1576
+ context.__transportProtocols = selectedTransport.protocols;
1577
+ context.__fetchId = buildTransportClientKey(selectedTransport);
1300
1578
  context.__triedInstances = triedInstances;
1301
1579
  context.__triedInstances.push(selected.uuid);
1302
1580
  context.__retries = retries;
1303
- if (this.useSocket) {
1581
+ if (this.useSocket && transportSupportsProtocol(selectedTransport, "socket")) {
1304
1582
  emit(
1305
1583
  `meta.service_registry.selected_instance_for_socket:${context.__fetchId}`,
1306
1584
  context
@@ -1492,9 +1770,9 @@ var ServiceRegistry = class _ServiceRegistry {
1492
1770
  __active: report.isActive,
1493
1771
  serviceName: report.serviceName,
1494
1772
  serviceInstanceId: report.serviceInstanceId,
1495
- serviceAddress: report.serviceAddress,
1496
- servicePort: report.servicePort,
1497
- exposed: report.exposed,
1773
+ transportId: report.transportId,
1774
+ transportOrigin: report.transportOrigin,
1775
+ transportProtocols: report.transportProtocols,
1498
1776
  isFrontend: report.isFrontend,
1499
1777
  reportedAt: report.reportedAt,
1500
1778
  numberOfRunningGraphs: report.numberOfRunningGraphs,
@@ -1537,12 +1815,17 @@ var ServiceRegistry = class _ServiceRegistry {
1537
1815
  continue;
1538
1816
  }
1539
1817
  this.runtimeStatusFallbackInFlightByInstance.add(serviceInstanceId);
1818
+ const transport = this.getRouteableTransport(
1819
+ instance,
1820
+ this.useSocket ? "socket" : "rest"
1821
+ );
1540
1822
  emit("meta.service_registry.runtime_status_fallback_requested", {
1541
1823
  ...ctx,
1542
1824
  serviceName,
1543
1825
  serviceInstanceId,
1544
- serviceAddress: instance.address,
1545
- servicePort: instance.port
1826
+ serviceTransportId: transport?.uuid,
1827
+ serviceOrigin: transport?.origin,
1828
+ transportProtocols: transport?.protocols
1546
1829
  });
1547
1830
  }
1548
1831
  }
@@ -1576,6 +1859,10 @@ var ServiceRegistry = class _ServiceRegistry {
1576
1859
  };
1577
1860
  } catch (error) {
1578
1861
  const instance = this.getInstance(serviceName, serviceInstanceId);
1862
+ const transport = instance ? this.getRouteableTransport(
1863
+ instance,
1864
+ this.useSocket ? "socket" : "rest"
1865
+ ) : void 0;
1579
1866
  const message = error instanceof Error ? error.message : String(error);
1580
1867
  CadenzaService.log(
1581
1868
  "Runtime status fallback inquiry failed.",
@@ -1590,8 +1877,9 @@ var ServiceRegistry = class _ServiceRegistry {
1590
1877
  ...ctx,
1591
1878
  serviceName,
1592
1879
  serviceInstanceId,
1593
- serviceAddress: instance?.address ?? ctx.serviceAddress,
1594
- servicePort: instance?.port ?? ctx.servicePort,
1880
+ serviceTransportId: transport?.uuid ?? ctx.serviceTransportId,
1881
+ serviceOrigin: transport?.origin ?? ctx.serviceOrigin,
1882
+ transportProtocols: transport?.protocols ?? ctx.transportProtocols,
1595
1883
  __error: message,
1596
1884
  errored: true
1597
1885
  });
@@ -1704,85 +1992,185 @@ var ServiceRegistry = class _ServiceRegistry {
1704
1992
  inputSchema: {
1705
1993
  type: "object",
1706
1994
  properties: {
1707
- uuid: {
1708
- type: "string"
1709
- },
1710
- address: {
1711
- type: "string"
1712
- },
1713
- port: {
1714
- type: "number"
1715
- },
1716
- process_pid: {
1717
- type: "number"
1718
- },
1719
- is_primary: {
1720
- type: "boolean"
1721
- },
1722
- service_name: {
1723
- type: "string"
1724
- },
1725
- is_active: {
1726
- type: "boolean"
1727
- },
1728
- is_non_responsive: {
1729
- type: "boolean"
1730
- },
1731
- is_blocked: {
1732
- type: "boolean"
1733
- },
1734
- exposed: {
1735
- type: "boolean"
1995
+ data: {
1996
+ type: "object",
1997
+ properties: {
1998
+ uuid: {
1999
+ type: "string"
2000
+ },
2001
+ process_pid: {
2002
+ type: "number"
2003
+ },
2004
+ is_primary: {
2005
+ type: "boolean"
2006
+ },
2007
+ service_name: {
2008
+ type: "string"
2009
+ },
2010
+ is_active: {
2011
+ type: "boolean"
2012
+ },
2013
+ is_frontend: {
2014
+ type: "boolean"
2015
+ },
2016
+ is_database: {
2017
+ type: "boolean"
2018
+ },
2019
+ is_non_responsive: {
2020
+ type: "boolean"
2021
+ },
2022
+ is_blocked: {
2023
+ type: "boolean"
2024
+ },
2025
+ health: {
2026
+ type: "object"
2027
+ }
2028
+ },
2029
+ required: ["uuid", "process_pid", "service_name"]
1736
2030
  }
1737
2031
  },
1738
- required: [
1739
- "id",
1740
- "address",
1741
- "port",
1742
- "process_pid",
1743
- "service_name",
1744
- "exposed"
1745
- ]
2032
+ required: ["data"]
1746
2033
  },
1747
- // validateInputContext: true,
1748
2034
  outputSchema: {
1749
2035
  type: "object",
1750
2036
  properties: {
1751
- id: {
2037
+ uuid: {
1752
2038
  type: "string"
1753
2039
  }
1754
2040
  },
1755
- required: ["id"]
2041
+ required: ["uuid"]
1756
2042
  },
1757
- // validateOutputContext: true,
1758
2043
  retryCount: 5,
1759
2044
  retryDelay: 1e3
1760
2045
  }
1761
- ).doOn("global.meta.rest.network_configured", "meta.rest.browser_detected").then(
2046
+ ).doOn("meta.service_registry.instance_registration_requested").then(
1762
2047
  CadenzaService.createMetaTask(
1763
2048
  "Setup service",
1764
2049
  (ctx) => {
1765
- const { serviceInstance, data, __useSocket, __retryCount } = ctx;
1766
- this.serviceInstanceId = serviceInstance?.uuid ?? data?.uuid;
2050
+ const {
2051
+ serviceInstance,
2052
+ data,
2053
+ queryData,
2054
+ __useSocket,
2055
+ __retryCount,
2056
+ __isFrontend
2057
+ } = ctx;
2058
+ const normalizedLocalInstance = normalizeServiceInstanceDescriptor({
2059
+ ...serviceInstance ?? data ?? queryData?.data ?? {},
2060
+ transports: ctx.transportData ?? []
2061
+ });
2062
+ if (!normalizedLocalInstance?.uuid || !normalizedLocalInstance.serviceName) {
2063
+ return false;
2064
+ }
2065
+ this.serviceInstanceId = normalizedLocalInstance.uuid;
1767
2066
  this.instances.set(
1768
- data?.service_name ?? serviceInstance?.service_name,
1769
- [{ ...serviceInstance ?? data }]
2067
+ normalizedLocalInstance.serviceName,
2068
+ [{ ...normalizedLocalInstance }]
1770
2069
  );
1771
2070
  this.useSocket = __useSocket;
1772
2071
  this.retryCount = __retryCount;
2072
+ this.isFrontend = typeof __isFrontend === "boolean" ? __isFrontend : !!normalizedLocalInstance.isFrontend;
1773
2073
  console.log("SETUP SERVICE", this.serviceInstanceId);
1774
2074
  return true;
1775
2075
  },
1776
2076
  "Sets service instance id after insertion"
1777
- ).emits("meta.service_registry.instance_inserted")
2077
+ ).emits("meta.service_registry.instance_inserted").then(
2078
+ CadenzaService.createMetaTask(
2079
+ "Prepare service transport inserts",
2080
+ function* (ctx, emit) {
2081
+ const transportData = Array.isArray(ctx.transportData) ? ctx.transportData : [];
2082
+ for (const transport of transportData) {
2083
+ const transportContext = {
2084
+ ...ctx,
2085
+ data: {
2086
+ ...transport,
2087
+ service_instance_id: transport.service_instance_id ?? ctx.__serviceInstanceId
2088
+ }
2089
+ };
2090
+ emit(
2091
+ "meta.service_registry.transport_registration_requested",
2092
+ transportContext
2093
+ );
2094
+ yield transportContext;
2095
+ }
2096
+ },
2097
+ "Splits declared service transports into individual insert payloads."
2098
+ ).attachSignal("meta.service_registry.transport_registration_requested")
2099
+ )
1778
2100
  );
2101
+ this.insertServiceTransportTask = CadenzaService.createCadenzaDBInsertTask(
2102
+ "service_instance_transport",
2103
+ {
2104
+ onConflict: {
2105
+ target: ["service_instance_id", "role", "origin"],
2106
+ action: {
2107
+ do: "update",
2108
+ set: {
2109
+ protocols: "excluded",
2110
+ security_profile: "excluded",
2111
+ auth_strategy: "excluded",
2112
+ deleted: "false"
2113
+ }
2114
+ }
2115
+ }
2116
+ },
2117
+ {
2118
+ inputSchema: {
2119
+ type: "object",
2120
+ properties: {
2121
+ data: {
2122
+ type: "object",
2123
+ properties: {
2124
+ uuid: {
2125
+ type: "string"
2126
+ },
2127
+ service_instance_id: {
2128
+ type: "string"
2129
+ },
2130
+ role: {
2131
+ type: "string"
2132
+ },
2133
+ origin: {
2134
+ type: "string"
2135
+ },
2136
+ protocols: {
2137
+ type: "array",
2138
+ items: {
2139
+ type: "string"
2140
+ }
2141
+ },
2142
+ security_profile: {
2143
+ type: "string"
2144
+ },
2145
+ auth_strategy: {
2146
+ type: "string"
2147
+ }
2148
+ },
2149
+ required: ["uuid", "service_instance_id", "role", "origin"]
2150
+ }
2151
+ },
2152
+ required: ["data"]
2153
+ },
2154
+ outputSchema: {
2155
+ type: "object",
2156
+ properties: {
2157
+ uuid: {
2158
+ type: "string"
2159
+ }
2160
+ },
2161
+ required: ["uuid"]
2162
+ },
2163
+ retryCount: 5,
2164
+ retryDelay: 1e3
2165
+ }
2166
+ ).doOn("meta.service_registry.transport_registration_requested").emits("meta.service_registry.transport_registered").emitsOnFail("meta.service_registry.transport_registration_failed");
1779
2167
  CadenzaService.createMetaTask(
1780
2168
  "Handle service creation",
1781
2169
  (ctx) => {
1782
2170
  if (!ctx.__cadenzaDBConnect) {
1783
2171
  ctx.__skipRemoteExecution = true;
1784
2172
  }
1785
- if (isBrowser) {
2173
+ if (isBrowser || ctx.__isFrontend) {
1786
2174
  CadenzaService.createMetaTask("Prepare for signal sync", () => {
1787
2175
  return {};
1788
2176
  }).then(
@@ -1815,28 +2203,24 @@ var ServiceRegistry = class _ServiceRegistry {
1815
2203
  for (const service of services) {
1816
2204
  const instances = this.instances.get(service).filter((i) => i.isActive);
1817
2205
  for (const instance of instances) {
1818
- if (instance.clientCreated) continue;
1819
- const address = instance.address;
1820
- const port = instance.port;
1821
- const clientCreated = instances?.some(
1822
- (i) => i.address === address && i.port === port && i.clientCreated && i.isActive
2206
+ const transport = this.getRouteableTransport(
2207
+ instance,
2208
+ this.useSocket ? "socket" : "rest"
1823
2209
  );
1824
- if (!clientCreated) {
2210
+ if (!transport) {
2211
+ continue;
2212
+ }
2213
+ if (!this.hasTransportClientCreated(instance, transport.uuid)) {
1825
2214
  emit("meta.service_registry.dependee_registered", {
1826
2215
  serviceName: service,
1827
2216
  serviceInstanceId: instance.uuid,
1828
- serviceAddress: address,
1829
- servicePort: port,
1830
- protocol: instance.exposed ? "https" : "http",
2217
+ serviceTransportId: transport.uuid,
2218
+ serviceOrigin: transport.origin,
2219
+ transportProtocols: transport.protocols,
1831
2220
  communicationTypes: ["signal"]
1832
2221
  });
2222
+ this.markTransportClientCreated(instance, transport.uuid);
1833
2223
  }
1834
- instance.clientCreated = true;
1835
- instances.forEach((i) => {
1836
- if (i.address === address && i.port === port) {
1837
- i.clientCreated = true;
1838
- }
1839
- });
1840
2224
  }
1841
2225
  }
1842
2226
  return {};
@@ -1975,6 +2359,33 @@ var ServiceRegistry = class _ServiceRegistry {
1975
2359
  }
1976
2360
  return this.getInstance(this.serviceName, this.serviceInstanceId);
1977
2361
  }
2362
+ getRoutingTransportRole() {
2363
+ return this.isFrontend ? "public" : "internal";
2364
+ }
2365
+ getTransportById(instance, transportId) {
2366
+ return instance.transports.find((transport) => transport.uuid === transportId);
2367
+ }
2368
+ getRouteableTransport(instance, protocol, role = this.getRoutingTransportRole()) {
2369
+ return getRouteableTransport(instance, role, protocol);
2370
+ }
2371
+ getTransportClientKey(instance, protocol, role = this.getRoutingTransportRole()) {
2372
+ const transport = this.getRouteableTransport(instance, protocol, role);
2373
+ if (!transport) {
2374
+ return null;
2375
+ }
2376
+ return buildTransportClientKey(transport);
2377
+ }
2378
+ hasTransportClientCreated(instance, transportId) {
2379
+ return (instance.clientCreatedTransportIds ?? []).includes(transportId);
2380
+ }
2381
+ markTransportClientCreated(instance, transportId) {
2382
+ if (!instance.clientCreatedTransportIds) {
2383
+ instance.clientCreatedTransportIds = [];
2384
+ }
2385
+ if (!instance.clientCreatedTransportIds.includes(transportId)) {
2386
+ instance.clientCreatedTransportIds.push(transportId);
2387
+ }
2388
+ }
1978
2389
  registerDependee(serviceName, serviceInstanceId, options = {}) {
1979
2390
  if (!serviceName || !serviceInstanceId) {
1980
2391
  return;
@@ -2052,7 +2463,13 @@ var ServiceRegistry = class _ServiceRegistry {
2052
2463
  if (!serviceName || !serviceInstanceId) {
2053
2464
  return null;
2054
2465
  }
2055
- const servicePort = ctx.servicePort ?? ctx.port ?? ctx.serviceInstance?.port;
2466
+ const transportId = ctx.transportId ?? ctx.serviceTransportId ?? ctx.serviceTransport?.uuid ?? void 0;
2467
+ const transportOrigin = ctx.transportOrigin ?? ctx.serviceOrigin ?? ctx.serviceTransport?.origin ?? void 0;
2468
+ const transportProtocols = Array.isArray(
2469
+ ctx.transportProtocols ?? ctx.serviceTransport?.protocols
2470
+ ) ? (ctx.transportProtocols ?? ctx.serviceTransport?.protocols).map((entry) => String(entry)).filter(
2471
+ (entry) => entry === "rest" || entry === "socket"
2472
+ ) : void 0;
2056
2473
  const numberOfRunningGraphs = Math.max(
2057
2474
  0,
2058
2475
  Math.trunc(
@@ -2071,9 +2488,9 @@ var ServiceRegistry = class _ServiceRegistry {
2071
2488
  return {
2072
2489
  serviceName,
2073
2490
  serviceInstanceId,
2074
- serviceAddress: ctx.serviceAddress ?? ctx.address ?? ctx.serviceInstance?.address,
2075
- servicePort: typeof servicePort === "number" ? servicePort : void 0,
2076
- exposed: typeof ctx.exposed === "boolean" ? ctx.exposed : typeof ctx.serviceInstance?.exposed === "boolean" ? ctx.serviceInstance.exposed : void 0,
2491
+ transportId: typeof transportId === "string" && transportId.trim().length > 0 ? transportId : void 0,
2492
+ transportOrigin: typeof transportOrigin === "string" && transportOrigin.trim().length > 0 ? transportOrigin : void 0,
2493
+ transportProtocols: transportProtocols && transportProtocols.length > 0 ? Array.from(new Set(transportProtocols)) : void 0,
2077
2494
  isFrontend: typeof ctx.isFrontend === "boolean" ? ctx.isFrontend : typeof ctx.serviceInstance?.isFrontend === "boolean" ? ctx.serviceInstance.isFrontend : void 0,
2078
2495
  reportedAt: ctx.reportedAt ?? (typeof ctx.__reportedAt === "string" ? ctx.__reportedAt : void 0) ?? (/* @__PURE__ */ new Date()).toISOString(),
2079
2496
  state: ctx.state === "healthy" || ctx.state === "degraded" || ctx.state === "overloaded" || ctx.state === "unavailable" ? ctx.state : resolved.state,
@@ -2090,14 +2507,23 @@ var ServiceRegistry = class _ServiceRegistry {
2090
2507
  if (!instance) {
2091
2508
  return false;
2092
2509
  }
2093
- if (report.serviceAddress) {
2094
- instance.address = report.serviceAddress;
2095
- }
2096
- if (typeof report.servicePort === "number") {
2097
- instance.port = report.servicePort;
2098
- }
2099
- if (typeof report.exposed === "boolean") {
2100
- instance.exposed = report.exposed;
2510
+ if (report.transportId && report.transportOrigin) {
2511
+ const protocols = report.transportProtocols && report.transportProtocols.length > 0 ? report.transportProtocols : ["rest", "socket"];
2512
+ const existingTransport = this.getTransportById(instance, report.transportId);
2513
+ if (existingTransport) {
2514
+ existingTransport.origin = report.transportOrigin;
2515
+ existingTransport.protocols = protocols;
2516
+ } else {
2517
+ instance.transports.push({
2518
+ uuid: report.transportId,
2519
+ serviceInstanceId: report.serviceInstanceId,
2520
+ role: this.getRoutingTransportRole(),
2521
+ origin: report.transportOrigin,
2522
+ protocols,
2523
+ securityProfile: null,
2524
+ authStrategy: null
2525
+ });
2526
+ }
2101
2527
  }
2102
2528
  if (typeof report.isFrontend === "boolean") {
2103
2529
  instance.isFrontend = report.isFrontend;
@@ -2140,9 +2566,21 @@ var ServiceRegistry = class _ServiceRegistry {
2140
2566
  const report = {
2141
2567
  serviceName: this.serviceName,
2142
2568
  serviceInstanceId: this.serviceInstanceId,
2143
- serviceAddress: localInstance.address,
2144
- servicePort: localInstance.port,
2145
- exposed: localInstance.exposed,
2569
+ transportId: this.getRouteableTransport(
2570
+ localInstance,
2571
+ this.useSocket ? "socket" : "rest",
2572
+ "internal"
2573
+ )?.uuid ?? void 0,
2574
+ transportOrigin: this.getRouteableTransport(
2575
+ localInstance,
2576
+ this.useSocket ? "socket" : "rest",
2577
+ "internal"
2578
+ )?.origin ?? void 0,
2579
+ transportProtocols: this.getRouteableTransport(
2580
+ localInstance,
2581
+ this.useSocket ? "socket" : "rest",
2582
+ "internal"
2583
+ )?.protocols ?? void 0,
2146
2584
  isFrontend: localInstance.isFrontend,
2147
2585
  reportedAt,
2148
2586
  state: snapshot.state,
@@ -2357,6 +2795,7 @@ var ServiceRegistry = class _ServiceRegistry {
2357
2795
  this.numberOfRunningGraphs = 0;
2358
2796
  this.runtimeStatusHeartbeatStarted = false;
2359
2797
  this.lastRuntimeStatusSnapshot = null;
2798
+ this.isFrontend = false;
2360
2799
  }
2361
2800
  };
2362
2801
 
@@ -2528,13 +2967,10 @@ var RestController = class _RestController {
2528
2967
  CadenzaService.createMetaTask(
2529
2968
  "Setup Express app security",
2530
2969
  (ctx, emit) => {
2531
- if (isBrowser) {
2532
- emit("meta.rest.browser_detected", {
2970
+ if (isBrowser || ctx.__isFrontend) {
2971
+ emit("meta.service_registry.instance_registration_requested", {
2533
2972
  data: {
2534
2973
  uuid: ctx.__serviceInstanceId,
2535
- address: `browser:${ctx.__serviceInstanceId}`,
2536
- port: 0,
2537
- exposed: false,
2538
2974
  process_pid: 1,
2539
2975
  service_name: ctx.__serviceName,
2540
2976
  is_frontend: true,
@@ -2543,6 +2979,7 @@ var RestController = class _RestController {
2543
2979
  is_blocked: false,
2544
2980
  health: {}
2545
2981
  },
2982
+ transportData: [],
2546
2983
  ...ctx
2547
2984
  });
2548
2985
  return;
@@ -2615,7 +3052,7 @@ var RestController = class _RestController {
2615
3052
  return { ...ctx, __app: app };
2616
3053
  },
2617
3054
  "Sets up the Express server according to the security profile"
2618
- ).attachSignal("meta.rest.browser_detected").then(
3055
+ ).attachSignal("meta.service_registry.instance_registration_requested").then(
2619
3056
  CadenzaService.createMetaTask(
2620
3057
  "Define RestServer",
2621
3058
  (ctx) => {
@@ -2735,28 +3172,32 @@ var RestController = class _RestController {
2735
3172
  CadenzaService.createMetaTask(
2736
3173
  "Configure network",
2737
3174
  async (ctx) => {
2738
- let address = "undefined";
2739
- let port = ctx.__port;
2740
- let exposed = false;
3175
+ let httpOrigin = null;
3176
+ let httpsOrigin = null;
3177
+ const resolveBoundAddress = (server) => {
3178
+ if (typeof server?.address() === "string") {
3179
+ return server.address();
3180
+ }
3181
+ if (server?.address()?.address === "::") {
3182
+ if (process.env.NODE_ENV === "development") {
3183
+ return "localhost";
3184
+ }
3185
+ if (process.env.IS_DOCKER === "true") {
3186
+ return process.env.CADENZA_SERVER_URL || "localhost";
3187
+ }
3188
+ }
3189
+ return server?.address()?.address || "localhost";
3190
+ };
2741
3191
  const createHttpServer = async (ctx2) => {
2742
3192
  await new Promise((resolve) => {
2743
3193
  const server = import_node_http.default.createServer(ctx2.__app);
2744
3194
  ctx2.httpServer = server;
2745
3195
  server.listen(ctx2.__port, () => {
2746
- if (typeof server?.address() === "string") {
2747
- address = server.address();
2748
- } else if (server?.address()?.address === "::") {
2749
- if (process.env.NODE_ENV === "development") {
2750
- address = "localhost";
2751
- } else if (process.env.IS_DOCKER === "true") {
2752
- address = process.env.CADENZA_SERVER_URL || "localhost";
2753
- }
2754
- } else {
2755
- address = server?.address()?.address || "undefined";
2756
- }
2757
- console.log(
2758
- `Server is running on ${address}:${port}`
2759
- );
3196
+ const addressInfo = server.address();
3197
+ const address = resolveBoundAddress(server);
3198
+ const port = typeof addressInfo === "object" && addressInfo ? addressInfo.port || ctx2.__port : ctx2.__port;
3199
+ httpOrigin = `http://${address}:${port}`;
3200
+ console.log(`Server is running on ${httpOrigin}`);
2760
3201
  resolve(address);
2761
3202
  });
2762
3203
  CadenzaService.createMetaTask(
@@ -2782,22 +3223,12 @@ var RestController = class _RestController {
2782
3223
  ctx2.__app
2783
3224
  );
2784
3225
  ctx2.httpsServer = httpsServer;
2785
- ctx2.__port = 443;
2786
- port = 443;
2787
3226
  httpsServer.listen(443, () => {
2788
- if (typeof httpsServer?.address() === "string") {
2789
- address = httpsServer.address();
2790
- } else if (httpsServer?.address()?.address === "::") {
2791
- if (process.env.IS_DOCKER === "true") {
2792
- address = process.env.CADENZA_SERVER_URL || "localhost";
2793
- }
2794
- } else {
2795
- address = httpsServer?.address()?.address || "";
2796
- }
2797
- exposed = true;
2798
- console.log(
2799
- `HTTPS Server is running on ${address}:443`
2800
- );
3227
+ const addressInfo = httpsServer.address();
3228
+ const address = resolveBoundAddress(httpsServer);
3229
+ const port = typeof addressInfo === "object" && addressInfo ? addressInfo.port || 443 : 443;
3230
+ httpsOrigin = `https://${address}:${port}`;
3231
+ console.log(`HTTPS Server is running on ${httpsOrigin}`);
2801
3232
  resolve(address);
2802
3233
  });
2803
3234
  CadenzaService.createMetaTask(
@@ -2817,11 +3248,34 @@ var RestController = class _RestController {
2817
3248
  } else if (ctx.__networkMode === "auto") {
2818
3249
  await createHttpServer(ctx);
2819
3250
  }
3251
+ const declaredTransports = Array.isArray(ctx.__declaredTransports) ? ctx.__declaredTransports : [];
3252
+ const hasExplicitInternalTransport = declaredTransports.some(
3253
+ (transport) => transport.role === "internal"
3254
+ );
3255
+ const transportData = declaredTransports.map((transport) => ({
3256
+ uuid: transport.uuid,
3257
+ service_instance_id: ctx.__serviceInstanceId,
3258
+ role: transport.role,
3259
+ origin: transport.origin,
3260
+ protocols: transport.protocols ?? ["rest", "socket"],
3261
+ ...transport.securityProfile ? { security_profile: transport.securityProfile } : {},
3262
+ ...transport.authStrategy ? { auth_strategy: transport.authStrategy } : {}
3263
+ }));
3264
+ if (!hasExplicitInternalTransport) {
3265
+ const internalOrigin = httpOrigin ?? httpsOrigin;
3266
+ if (internalOrigin) {
3267
+ transportData.unshift({
3268
+ uuid: `${ctx.__serviceInstanceId}-internal-auto`,
3269
+ service_instance_id: ctx.__serviceInstanceId,
3270
+ role: "internal",
3271
+ origin: internalOrigin,
3272
+ protocols: ["rest", "socket"],
3273
+ ...ctx.__securityProfile ? { security_profile: ctx.__securityProfile } : {}
3274
+ });
3275
+ }
3276
+ }
2820
3277
  ctx.data = {
2821
3278
  uuid: ctx.__serviceInstanceId,
2822
- address,
2823
- port,
2824
- exposed,
2825
3279
  process_pid: process.pid,
2826
3280
  service_name: ctx.__serviceName,
2827
3281
  is_active: true,
@@ -2830,7 +3284,12 @@ var RestController = class _RestController {
2830
3284
  is_blocked: false,
2831
3285
  health: {}
2832
3286
  };
3287
+ ctx.transportData = transportData;
2833
3288
  delete ctx.__app;
3289
+ CadenzaService.emit(
3290
+ "meta.service_registry.instance_registration_requested",
3291
+ ctx
3292
+ );
2834
3293
  return ctx;
2835
3294
  },
2836
3295
  "Configures network mode"
@@ -2880,10 +3339,12 @@ var RestController = class _RestController {
2880
3339
  CadenzaService.createMetaTask(
2881
3340
  "Setup fetch client",
2882
3341
  (ctx) => {
2883
- const { serviceName, serviceAddress, servicePort, protocol } = ctx;
2884
- const port = protocol === "https" ? 443 : servicePort;
2885
- const URL2 = `${protocol}://${serviceAddress}:${port}`;
2886
- const fetchId = `${serviceAddress}_${port}`;
3342
+ const serviceName = String(ctx.serviceName ?? "");
3343
+ const URL2 = String(ctx.serviceOrigin ?? "");
3344
+ const fetchId = String(ctx.serviceTransportId ?? "");
3345
+ if (!serviceName || !URL2 || !fetchId) {
3346
+ return false;
3347
+ }
2887
3348
  const fetchDiagnostics = this.ensureFetchClientDiagnostics(
2888
3349
  fetchId,
2889
3350
  serviceName,
@@ -3135,18 +3596,18 @@ var RestController = class _RestController {
3135
3596
  serviceName,
3136
3597
  serviceInstanceId,
3137
3598
  communicationTypes,
3138
- serviceAddress,
3139
- servicePort,
3140
- protocol
3599
+ serviceTransportId,
3600
+ serviceOrigin,
3601
+ transportProtocols
3141
3602
  } = ctx;
3142
- const fetchId = `${serviceAddress}_${servicePort}`;
3603
+ const fetchId = String(serviceTransportId ?? "");
3143
3604
  emit(`meta.fetch.handshake_requested:${fetchId}`, {
3144
3605
  serviceInstanceId,
3145
3606
  serviceName,
3146
3607
  communicationTypes,
3147
- serviceAddress,
3148
- servicePort,
3149
- protocol,
3608
+ serviceTransportId,
3609
+ serviceOrigin,
3610
+ transportProtocols,
3150
3611
  handshakeData: {
3151
3612
  instanceId: CadenzaService.serviceRegistry.serviceInstanceId,
3152
3613
  serviceName: CadenzaService.serviceRegistry.serviceName
@@ -3329,9 +3790,8 @@ var RestController = class _RestController {
3329
3790
  };
3330
3791
 
3331
3792
  // src/network/SocketController.ts
3332
- var import_socket = require("socket.io");
3333
3793
  var import_rate_limiter_flexible2 = require("rate-limiter-flexible");
3334
- var import_socket2 = require("socket.io-client");
3794
+ var import_socket = require("socket.io-client");
3335
3795
 
3336
3796
  // src/network/socketClientUtils.ts
3337
3797
  var waitForSocketConnection = async (socket, timeoutMs, createError) => {
@@ -3372,6 +3832,13 @@ var waitForSocketConnection = async (socket, timeoutMs, createError) => {
3372
3832
  };
3373
3833
 
3374
3834
  // src/network/SocketController.ts
3835
+ var dynamicImport = new Function(
3836
+ "specifier",
3837
+ "return import(specifier);"
3838
+ );
3839
+ async function importNodeModule(specifier) {
3840
+ return dynamicImport(specifier);
3841
+ }
3375
3842
  var SocketController = class _SocketController {
3376
3843
  constructor() {
3377
3844
  this.diagnosticsErrorHistoryLimit = 100;
@@ -3396,9 +3863,9 @@ var SocketController = class _SocketController {
3396
3863
  serviceInstanceId: "",
3397
3864
  communicationTypes: [],
3398
3865
  serviceName: "",
3399
- serviceAddress: "",
3400
- servicePort: 0,
3401
- protocol: "http",
3866
+ serviceTransportId: "",
3867
+ serviceOrigin: "",
3868
+ transportProtocols: [],
3402
3869
  url: "",
3403
3870
  socketId: null,
3404
3871
  connected: false,
@@ -3571,7 +4038,7 @@ var SocketController = class _SocketController {
3571
4038
  const setupSocketServerTask = CadenzaService.createMetaTask(
3572
4039
  "Setup SocketServer",
3573
4040
  this.socketServerActor.task(
3574
- ({ state, runtimeState, input, actor, setState, setRuntimeState, emit }) => {
4041
+ async ({ state, runtimeState, input, actor, setState, setRuntimeState, emit }) => {
3575
4042
  const serverKey = this.resolveSocketServerKey(input) ?? actor.key ?? this.socketServerDefaultKey;
3576
4043
  const shouldUseSocket = Boolean(input.__useSocket);
3577
4044
  if (!shouldUseSocket) {
@@ -3590,7 +4057,9 @@ var SocketController = class _SocketController {
3590
4057
  }
3591
4058
  let runtimeHandle = runtimeState;
3592
4059
  if (!runtimeHandle) {
3593
- runtimeHandle = this.createSocketServerRuntimeHandleFromContext(input);
4060
+ runtimeHandle = await this.createSocketServerRuntimeHandleFromContext(
4061
+ input
4062
+ );
3594
4063
  setRuntimeState(runtimeHandle);
3595
4064
  }
3596
4065
  const profile = String(input.__securityProfile ?? state.securityProfile ?? "medium");
@@ -3848,21 +4317,21 @@ var SocketController = class _SocketController {
3848
4317
  if (input.serviceName !== void 0) {
3849
4318
  next.serviceName = String(input.serviceName);
3850
4319
  }
3851
- if (input.serviceAddress !== void 0) {
3852
- next.serviceAddress = String(input.serviceAddress);
4320
+ if (input.serviceTransportId !== void 0) {
4321
+ next.serviceTransportId = String(input.serviceTransportId);
3853
4322
  }
3854
4323
  if (input.serviceInstanceId !== void 0) {
3855
4324
  next.serviceInstanceId = String(input.serviceInstanceId);
3856
4325
  }
3857
- if (input.protocol !== void 0) {
3858
- next.protocol = String(input.protocol);
4326
+ if (input.serviceOrigin !== void 0) {
4327
+ next.serviceOrigin = String(input.serviceOrigin);
4328
+ }
4329
+ if (input.transportProtocols !== void 0) {
4330
+ next.transportProtocols = Array.isArray(input.transportProtocols) ? input.transportProtocols.map((entry) => String(entry)) : [];
3859
4331
  }
3860
4332
  if (input.url !== void 0) {
3861
4333
  next.url = String(input.url);
3862
4334
  }
3863
- if (input.servicePort !== void 0) {
3864
- next.servicePort = Number(input.servicePort);
3865
- }
3866
4335
  if (input.fetchId !== void 0) {
3867
4336
  next.fetchId = String(input.fetchId);
3868
4337
  }
@@ -3906,25 +4375,24 @@ var SocketController = class _SocketController {
3906
4375
  input.communicationTypes
3907
4376
  );
3908
4377
  const serviceName = String(input.serviceName ?? "");
3909
- const serviceAddress = String(input.serviceAddress ?? "");
3910
- const protocol = String(input.protocol ?? "http");
3911
- const normalizedPort = this.resolveServicePort(protocol, input.servicePort);
3912
- if (!serviceAddress || !normalizedPort) {
4378
+ const serviceTransportId = String(input.serviceTransportId ?? "");
4379
+ const serviceOrigin = String(input.serviceOrigin ?? "");
4380
+ const parsedOrigin = parseTransportOrigin(serviceOrigin);
4381
+ if (!serviceTransportId || !serviceOrigin || !parsedOrigin) {
3913
4382
  CadenzaService.log(
3914
- "Socket client setup skipped due to missing address/port",
4383
+ "Socket client setup skipped due to missing transport origin",
3915
4384
  {
3916
4385
  serviceName,
3917
- serviceAddress,
3918
- servicePort: input.servicePort,
3919
- protocol
4386
+ serviceTransportId,
4387
+ serviceOrigin
3920
4388
  },
3921
4389
  "warning"
3922
4390
  );
3923
4391
  return false;
3924
4392
  }
3925
- const socketProtocol = protocol === "https" ? "wss" : "ws";
3926
- const url = `${socketProtocol}://${serviceAddress}:${normalizedPort}`;
3927
- const fetchId = `${serviceAddress}_${normalizedPort}`;
4393
+ const socketProtocol = parsedOrigin.protocol === "https" ? "wss" : "ws";
4394
+ const url = `${socketProtocol}://${parsedOrigin.hostname}:${parsedOrigin.port}`;
4395
+ const fetchId = serviceTransportId;
3928
4396
  const applySessionOperation = (operation, patch = {}) => {
3929
4397
  CadenzaService.emit("meta.socket_client.session_operation_requested", {
3930
4398
  fetchId,
@@ -3933,9 +4401,9 @@ var SocketController = class _SocketController {
3933
4401
  serviceInstanceId,
3934
4402
  communicationTypes,
3935
4403
  serviceName,
3936
- serviceAddress,
3937
- servicePort: normalizedPort,
3938
- protocol,
4404
+ serviceTransportId,
4405
+ serviceOrigin,
4406
+ transportProtocols: input.transportProtocols,
3939
4407
  url
3940
4408
  });
3941
4409
  };
@@ -3954,9 +4422,9 @@ var SocketController = class _SocketController {
3954
4422
  serviceInstanceId,
3955
4423
  communicationTypes,
3956
4424
  serviceName,
3957
- serviceAddress,
3958
- servicePort: normalizedPort,
3959
- protocol,
4425
+ serviceTransportId,
4426
+ serviceOrigin,
4427
+ transportProtocols: Array.isArray(input.transportProtocols) ? input.transportProtocols.map((entry) => String(entry)) : [],
3960
4428
  url,
3961
4429
  destroyed: false,
3962
4430
  updatedAt: Date.now()
@@ -4284,8 +4752,8 @@ var SocketController = class _SocketController {
4284
4752
  );
4285
4753
  CadenzaService.emit(`meta.socket_client.disconnected:${fetchId}`, {
4286
4754
  serviceName,
4287
- serviceAddress,
4288
- servicePort: normalizedPort
4755
+ serviceTransportId,
4756
+ serviceOrigin
4289
4757
  });
4290
4758
  runtimeHandle.handshake = false;
4291
4759
  });
@@ -4306,7 +4774,7 @@ var SocketController = class _SocketController {
4306
4774
  {
4307
4775
  serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
4308
4776
  serviceName: CadenzaService.serviceRegistry.serviceName,
4309
- isFrontend: isBrowser,
4777
+ isFrontend: CadenzaService.serviceRegistry.isFrontend || isBrowser,
4310
4778
  __status: "success"
4311
4779
  },
4312
4780
  1e4,
@@ -4484,9 +4952,9 @@ var SocketController = class _SocketController {
4484
4952
  serviceInstanceId,
4485
4953
  serviceName,
4486
4954
  communicationTypes,
4487
- serviceAddress,
4488
- servicePort: normalizedPort,
4489
- protocol,
4955
+ serviceTransportId,
4956
+ serviceOrigin,
4957
+ transportProtocols: input.transportProtocols,
4490
4958
  handshakeData: {
4491
4959
  instanceId: CadenzaService.serviceRegistry.serviceInstanceId,
4492
4960
  serviceName: CadenzaService.serviceRegistry.serviceName
@@ -4525,32 +4993,21 @@ var SocketController = class _SocketController {
4525
4993
  if (explicitFetchId) {
4526
4994
  return explicitFetchId;
4527
4995
  }
4528
- const serviceAddress = String(input.serviceAddress ?? "").trim();
4529
- const protocol = String(input.protocol ?? "http").trim();
4530
- const port = this.resolveServicePort(protocol, input.servicePort);
4531
- if (!serviceAddress || !port) {
4532
- return void 0;
4533
- }
4534
- return `${serviceAddress}_${port}`;
4535
- }
4536
- resolveServicePort(protocol, rawPort) {
4537
- if (protocol === "https") {
4538
- return 443;
4539
- }
4540
- const parsed = Number(rawPort);
4541
- if (!Number.isFinite(parsed) || parsed <= 0) {
4542
- return void 0;
4543
- }
4544
- return Math.trunc(parsed);
4996
+ const transportId = String(
4997
+ input.serviceTransportId ?? input.transportId ?? ""
4998
+ ).trim();
4999
+ return transportId || void 0;
4545
5000
  }
4546
- createSocketServerRuntimeHandleFromContext(context) {
5001
+ async createSocketServerRuntimeHandleFromContext(context) {
4547
5002
  const baseServer = context.httpsServer ?? context.httpServer;
4548
5003
  if (!baseServer) {
4549
5004
  throw new Error(
4550
5005
  "Socket server runtime setup requires either httpsServer or httpServer"
4551
5006
  );
4552
5007
  }
4553
- const server = new import_socket.Server(baseServer, {
5008
+ const socketServerModule = await importNodeModule("socket.io");
5009
+ const Server = socketServerModule.Server;
5010
+ const server = new Server(baseServer, {
4554
5011
  pingInterval: 3e4,
4555
5012
  pingTimeout: 2e4,
4556
5013
  maxHttpBufferSize: 1e7,
@@ -4583,7 +5040,7 @@ var SocketController = class _SocketController {
4583
5040
  createSocketClientRuntimeHandle(url) {
4584
5041
  return {
4585
5042
  url,
4586
- socket: (0, import_socket2.io)(url, {
5043
+ socket: (0, import_socket.io)(url, {
4587
5044
  reconnection: true,
4588
5045
  reconnectionAttempts: 5,
4589
5046
  reconnectionDelay: 2e3,
@@ -4840,16 +5297,140 @@ var SignalController = class _SignalController {
4840
5297
  service_name: CadenzaService.serviceRegistry.serviceName,
4841
5298
  service_instance_id: CadenzaService.serviceRegistry.serviceInstanceId
4842
5299
  }
4843
- };
4844
- },
4845
- "",
4846
- { isSubMeta: true, concurrency: 100 }
4847
- ).doOn("sub_meta.signal_broker.emitting_signal").emits("global.sub_meta.signal_controller.signal_emitted");
4848
- }
4849
- };
5300
+ };
5301
+ },
5302
+ "",
5303
+ { isSubMeta: true, concurrency: 100 }
5304
+ ).doOn("sub_meta.signal_broker.emitting_signal").emits("global.sub_meta.signal_controller.signal_emitted");
5305
+ }
5306
+ };
5307
+
5308
+ // src/graph/controllers/registerActorSessionPersistence.ts
5309
+ var import_core3 = require("@cadenza.io/core");
5310
+ function registerActorSessionPersistenceTasks() {
5311
+ if (CadenzaService.get("Persist actor session state")) {
5312
+ return;
5313
+ }
5314
+ const actorSessionStateInsertTask = CadenzaService.get("dbInsertActorSessionState") ?? CadenzaService.get("Insert actor_session_state in CadenzaDB") ?? CadenzaService.createCadenzaDBInsertTask(
5315
+ "actor_session_state",
5316
+ {},
5317
+ { concurrency: 100, isSubMeta: true }
5318
+ );
5319
+ const validateActorSessionStatePersistenceTask = CadenzaService.createMetaTask(
5320
+ "Validate actor session state persistence",
5321
+ (ctx) => {
5322
+ if (ctx.errored || ctx.failed || ctx.__success !== true) {
5323
+ throw new Error(
5324
+ String(
5325
+ ctx.__error ?? ctx.error ?? "actor_session_state persistence query failed"
5326
+ )
5327
+ );
5328
+ }
5329
+ const rowCount = Number(ctx.rowCount ?? 0);
5330
+ if (!Number.isFinite(rowCount) || rowCount <= 0) {
5331
+ throw new Error(
5332
+ "actor_session_state persistence did not affect any rows (possible stale durable_version)"
5333
+ );
5334
+ }
5335
+ return {
5336
+ __success: true,
5337
+ persisted: true,
5338
+ actor_name: ctx.actor_name,
5339
+ actor_version: ctx.actor_version,
5340
+ actor_key: ctx.actor_key,
5341
+ service_name: ctx.service_name,
5342
+ durable_version: ctx.durable_version
5343
+ };
5344
+ },
5345
+ "Enforces strict actor session persistence success contract.",
5346
+ { isSubMeta: true, concurrency: 100 }
5347
+ );
5348
+ const insertAndValidateActorSessionStateTask = actorSessionStateInsertTask.then(
5349
+ validateActorSessionStatePersistenceTask
5350
+ );
5351
+ CadenzaService.createMetaTask(
5352
+ "Persist actor session state",
5353
+ (ctx) => {
5354
+ const actorName = typeof ctx.actor_name === "string" ? ctx.actor_name.trim() : "";
5355
+ const actorKey = typeof ctx.actor_key === "string" ? ctx.actor_key.trim() : "";
5356
+ const actorVersion = Number(ctx.actor_version ?? 1);
5357
+ const durableVersion = Number(ctx.durable_version);
5358
+ if (!actorName) {
5359
+ throw new Error("actor_name is required for actor session persistence");
5360
+ }
5361
+ if (!actorKey) {
5362
+ throw new Error("actor_key is required for actor session persistence");
5363
+ }
5364
+ if (!Number.isInteger(actorVersion) || actorVersion < 1) {
5365
+ throw new Error("actor_version must be a positive integer");
5366
+ }
5367
+ if (!Number.isInteger(durableVersion) || durableVersion < 0) {
5368
+ throw new Error("durable_version must be a non-negative integer");
5369
+ }
5370
+ if (typeof ctx.durable_state !== "object" || ctx.durable_state === null || Array.isArray(ctx.durable_state)) {
5371
+ throw new Error("durable_state must be a non-null object");
5372
+ }
5373
+ const serviceName = CadenzaService.serviceRegistry.serviceName;
5374
+ if (!serviceName) {
5375
+ throw new Error("service_name is not available for actor session persistence");
5376
+ }
5377
+ let expiresAt = null;
5378
+ if (ctx.expires_at !== void 0 && ctx.expires_at !== null) {
5379
+ if (ctx.expires_at instanceof Date) {
5380
+ expiresAt = ctx.expires_at.toISOString();
5381
+ } else if (typeof ctx.expires_at === "string" && ctx.expires_at.trim().length > 0) {
5382
+ expiresAt = ctx.expires_at;
5383
+ } else {
5384
+ throw new Error("expires_at must be null, Date, or non-empty string");
5385
+ }
5386
+ }
5387
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5388
+ return {
5389
+ ...ctx,
5390
+ actor_name: actorName,
5391
+ actor_key: actorKey,
5392
+ actor_version: actorVersion,
5393
+ durable_version: durableVersion,
5394
+ expires_at: expiresAt,
5395
+ service_name: serviceName,
5396
+ queryData: {
5397
+ data: {
5398
+ actor_name: actorName,
5399
+ actor_version: actorVersion,
5400
+ actor_key: actorKey,
5401
+ service_name: serviceName,
5402
+ durable_state: ctx.durable_state,
5403
+ durable_version: durableVersion,
5404
+ expires_at: expiresAt,
5405
+ updated: updatedAt
5406
+ },
5407
+ onConflict: {
5408
+ target: [
5409
+ "actor_name",
5410
+ "actor_version",
5411
+ "actor_key",
5412
+ "service_name"
5413
+ ],
5414
+ action: {
5415
+ do: "update",
5416
+ set: {
5417
+ durable_state: "excluded",
5418
+ durable_version: "excluded",
5419
+ expires_at: "excluded",
5420
+ updated: "excluded"
5421
+ },
5422
+ where: "actor_session_state.durable_version <= excluded.durable_version"
5423
+ }
5424
+ }
5425
+ }
5426
+ };
5427
+ },
5428
+ "Validates and prepares actor_session_state payload for strict write-through persistence.",
5429
+ { isSubMeta: true, concurrency: 100 }
5430
+ ).then(insertAndValidateActorSessionStateTask).respondsTo(import_core3.META_ACTOR_SESSION_STATE_PERSIST_INTENT);
5431
+ }
4850
5432
 
4851
5433
  // src/graph/controllers/GraphMetadataController.ts
4852
- var import_core3 = require("@cadenza.io/core");
4853
5434
  var GraphMetadataController = class _GraphMetadataController {
4854
5435
  static get instance() {
4855
5436
  if (!this._instance) this._instance = new _GraphMetadataController();
@@ -5083,123 +5664,7 @@ var GraphMetadataController = class _GraphMetadataController {
5083
5664
  }
5084
5665
  };
5085
5666
  }).doOn("meta.actor.task_associated").emits("global.meta.graph_metadata.actor_task_associated");
5086
- const actorSessionStateInsertTask = CadenzaService.get("dbInsertActorSessionState") ?? CadenzaService.get("Insert actor_session_state in CadenzaDB") ?? CadenzaService.createCadenzaDBInsertTask(
5087
- "actor_session_state",
5088
- {},
5089
- { concurrency: 100, isSubMeta: true }
5090
- );
5091
- const validateActorSessionStatePersistenceTask = CadenzaService.createMetaTask(
5092
- "Validate actor session state persistence",
5093
- (ctx) => {
5094
- if (ctx.errored || ctx.failed || ctx.__success !== true) {
5095
- throw new Error(
5096
- String(
5097
- ctx.__error ?? ctx.error ?? "actor_session_state persistence query failed"
5098
- )
5099
- );
5100
- }
5101
- const rowCount = Number(ctx.rowCount ?? 0);
5102
- if (!Number.isFinite(rowCount) || rowCount <= 0) {
5103
- throw new Error(
5104
- "actor_session_state persistence did not affect any rows (possible stale durable_version)"
5105
- );
5106
- }
5107
- return {
5108
- __success: true,
5109
- persisted: true,
5110
- actor_name: ctx.actor_name,
5111
- actor_version: ctx.actor_version,
5112
- actor_key: ctx.actor_key,
5113
- service_name: ctx.service_name,
5114
- durable_version: ctx.durable_version
5115
- };
5116
- },
5117
- "Enforces strict actor session persistence success contract.",
5118
- { isSubMeta: true, concurrency: 100 }
5119
- );
5120
- const insertAndValidateActorSessionStateTask = actorSessionStateInsertTask.then(
5121
- validateActorSessionStatePersistenceTask
5122
- );
5123
- CadenzaService.createMetaTask(
5124
- "Persist actor session state",
5125
- (ctx) => {
5126
- const actorName = typeof ctx.actor_name === "string" ? ctx.actor_name.trim() : "";
5127
- const actorKey = typeof ctx.actor_key === "string" ? ctx.actor_key.trim() : "";
5128
- const actorVersion = Number(ctx.actor_version ?? 1);
5129
- const durableVersion = Number(ctx.durable_version);
5130
- if (!actorName) {
5131
- throw new Error("actor_name is required for actor session persistence");
5132
- }
5133
- if (!actorKey) {
5134
- throw new Error("actor_key is required for actor session persistence");
5135
- }
5136
- if (!Number.isInteger(actorVersion) || actorVersion < 1) {
5137
- throw new Error("actor_version must be a positive integer");
5138
- }
5139
- if (!Number.isInteger(durableVersion) || durableVersion < 0) {
5140
- throw new Error("durable_version must be a non-negative integer");
5141
- }
5142
- if (typeof ctx.durable_state !== "object" || ctx.durable_state === null || Array.isArray(ctx.durable_state)) {
5143
- throw new Error("durable_state must be a non-null object");
5144
- }
5145
- const serviceName = CadenzaService.serviceRegistry.serviceName;
5146
- if (!serviceName) {
5147
- throw new Error("service_name is not available for actor session persistence");
5148
- }
5149
- let expiresAt = null;
5150
- if (ctx.expires_at !== void 0 && ctx.expires_at !== null) {
5151
- if (ctx.expires_at instanceof Date) {
5152
- expiresAt = ctx.expires_at.toISOString();
5153
- } else if (typeof ctx.expires_at === "string" && ctx.expires_at.trim().length > 0) {
5154
- expiresAt = ctx.expires_at;
5155
- } else {
5156
- throw new Error("expires_at must be null, Date, or non-empty string");
5157
- }
5158
- }
5159
- const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5160
- return {
5161
- ...ctx,
5162
- actor_name: actorName,
5163
- actor_key: actorKey,
5164
- actor_version: actorVersion,
5165
- durable_version: durableVersion,
5166
- expires_at: expiresAt,
5167
- service_name: serviceName,
5168
- queryData: {
5169
- data: {
5170
- actor_name: actorName,
5171
- actor_version: actorVersion,
5172
- actor_key: actorKey,
5173
- service_name: serviceName,
5174
- durable_state: ctx.durable_state,
5175
- durable_version: durableVersion,
5176
- expires_at: expiresAt,
5177
- updated: updatedAt
5178
- },
5179
- onConflict: {
5180
- target: [
5181
- "actor_name",
5182
- "actor_version",
5183
- "actor_key",
5184
- "service_name"
5185
- ],
5186
- action: {
5187
- do: "update",
5188
- set: {
5189
- durable_state: "excluded",
5190
- durable_version: "excluded",
5191
- expires_at: "excluded",
5192
- updated: "excluded"
5193
- },
5194
- where: "actor_session_state.durable_version <= excluded.durable_version"
5195
- }
5196
- }
5197
- }
5198
- };
5199
- },
5200
- "Validates and prepares actor_session_state payload for strict write-through persistence.",
5201
- { isSubMeta: true, concurrency: 100 }
5202
- ).then(insertAndValidateActorSessionStateTask).respondsTo(import_core3.META_ACTOR_SESSION_STATE_PERSIST_INTENT);
5667
+ registerActorSessionPersistenceTasks();
5203
5668
  CadenzaService.createMetaTask("Handle Intent Creation", (ctx) => {
5204
5669
  const intentName = ctx.data?.name;
5205
5670
  return {
@@ -5263,6 +5728,32 @@ function validateIntentName(intentName) {
5263
5728
  function defaultOperationIntentDescription(operation, tableName) {
5264
5729
  return `Perform a ${operation} operation on the ${tableName} table`;
5265
5730
  }
5731
+ function isExplicitSqlLiteral(value) {
5732
+ return /^'.*'::[a-z_][a-z0-9_]*(\[\])?$/i.test(value.trim());
5733
+ }
5734
+ function serializeInitialDataValueForSql(value, field) {
5735
+ if (value === void 0 || value === null) {
5736
+ return "NULL";
5737
+ }
5738
+ if (typeof value === "number") {
5739
+ return String(value);
5740
+ }
5741
+ if (typeof value === "boolean") {
5742
+ return value ? "TRUE" : "FALSE";
5743
+ }
5744
+ if (field?.type === "jsonb") {
5745
+ if (typeof value === "string" && isExplicitSqlLiteral(value)) {
5746
+ return value;
5747
+ }
5748
+ const jsonString = JSON.stringify(value);
5749
+ return `'${jsonString.replace(/'/g, "''")}'::jsonb`;
5750
+ }
5751
+ const stringValue = String(value);
5752
+ if (isExplicitSqlLiteral(stringValue)) {
5753
+ return stringValue;
5754
+ }
5755
+ return `'${stringValue.replace(/'/g, "''")}'`;
5756
+ }
5266
5757
  function readCustomIntentConfig(customIntent) {
5267
5758
  if (typeof customIntent === "string") {
5268
5759
  return {
@@ -6276,14 +6767,12 @@ var DatabaseController = class _DatabaseController {
6276
6767
  if (table.initialData) {
6277
6768
  ddl.push(
6278
6769
  `INSERT INTO ${tableName} (${table.initialData.fields.map(import_lodash_es.snakeCase).join(", ")}) VALUES ${table.initialData.data.map(
6279
- (row) => `(${row.map((value) => {
6280
- if (value === void 0) return "NULL";
6281
- if (value === null) return "NULL";
6282
- if (typeof value === "number") return String(value);
6283
- if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
6284
- const stringValue = String(value);
6285
- return `'${stringValue.replace(/'/g, "''")}'`;
6286
- }).join(", ")})`
6770
+ (row) => `(${row.map(
6771
+ (value, index) => serializeInitialDataValueForSql(
6772
+ value,
6773
+ table.fields[table.initialData.fields[index]]
6774
+ )
6775
+ ).join(", ")})`
6287
6776
  ).join(", ")} ON CONFLICT DO NOTHING;`
6288
6777
  );
6289
6778
  }
@@ -7835,6 +8324,165 @@ var GraphSyncController = class _GraphSyncController {
7835
8324
  }
7836
8325
  };
7837
8326
 
8327
+ // src/utils/bootstrap.ts
8328
+ var DEFAULT_BOOTSTRAP_GLOBAL_KEY = "__CADENZA_RUNTIME__";
8329
+ function normalizeString3(value) {
8330
+ if (typeof value !== "string") {
8331
+ return void 0;
8332
+ }
8333
+ const normalized = value.trim();
8334
+ return normalized.length > 0 ? normalized : void 0;
8335
+ }
8336
+ function readEnvString(name) {
8337
+ if (typeof process === "undefined") {
8338
+ return void 0;
8339
+ }
8340
+ return normalizeString3(process.env?.[name]);
8341
+ }
8342
+ function readConfiguredPort(value) {
8343
+ if (value === void 0 || value === null || value === "") {
8344
+ return void 0;
8345
+ }
8346
+ const parsed = Number(value);
8347
+ if (!Number.isFinite(parsed)) {
8348
+ throw new Error(`Invalid port value: ${String(value)}`);
8349
+ }
8350
+ const normalized = Math.trunc(parsed);
8351
+ if (normalized <= 0) {
8352
+ throw new Error(`Port must be a positive integer: ${String(value)}`);
8353
+ }
8354
+ return normalized;
8355
+ }
8356
+ function buildBootstrapUrl(protocol, address, port) {
8357
+ return `${protocol}://${address}:${port}`;
8358
+ }
8359
+ function resolveInjectedBootstrapUrl(injectedGlobalKey) {
8360
+ if (typeof globalThis === "undefined") {
8361
+ return void 0;
8362
+ }
8363
+ const runtimeConfig = globalThis[injectedGlobalKey];
8364
+ return normalizeString3(runtimeConfig?.bootstrapUrl);
8365
+ }
8366
+ function resolveExplicitBootstrapValue(options) {
8367
+ const injectedGlobalKey = normalizeString3(options.bootstrap?.injectedGlobalKey) ?? DEFAULT_BOOTSTRAP_GLOBAL_KEY;
8368
+ const explicitBootstrapUrl = normalizeString3(options.bootstrap?.url);
8369
+ if (explicitBootstrapUrl) {
8370
+ return {
8371
+ value: explicitBootstrapUrl,
8372
+ port: readConfiguredPort(options.cadenzaDB?.port),
8373
+ injectedGlobalKey
8374
+ };
8375
+ }
8376
+ if (options.runtime === "browser") {
8377
+ const injected = resolveInjectedBootstrapUrl(injectedGlobalKey);
8378
+ if (injected) {
8379
+ return {
8380
+ value: injected,
8381
+ port: readConfiguredPort(options.cadenzaDB?.port),
8382
+ injectedGlobalKey
8383
+ };
8384
+ }
8385
+ }
8386
+ const explicitAddress = normalizeString3(options.cadenzaDB?.address);
8387
+ if (explicitAddress) {
8388
+ return {
8389
+ value: explicitAddress,
8390
+ port: readConfiguredPort(options.cadenzaDB?.port),
8391
+ injectedGlobalKey
8392
+ };
8393
+ }
8394
+ const envAddress = readEnvString("CADENZA_DB_ADDRESS");
8395
+ return {
8396
+ value: envAddress,
8397
+ port: readConfiguredPort(options.cadenzaDB?.port ?? readEnvString("CADENZA_DB_PORT")),
8398
+ injectedGlobalKey
8399
+ };
8400
+ }
8401
+ function readIntegerEnv(name, fallback) {
8402
+ const raw = readEnvString(name);
8403
+ if (!raw) {
8404
+ return fallback;
8405
+ }
8406
+ const parsed = Number(raw);
8407
+ if (!Number.isFinite(parsed)) {
8408
+ return fallback;
8409
+ }
8410
+ const normalized = Math.trunc(parsed);
8411
+ if (normalized <= 0) {
8412
+ return fallback;
8413
+ }
8414
+ return normalized;
8415
+ }
8416
+ function readStringEnv(name) {
8417
+ return readEnvString(name);
8418
+ }
8419
+ function readListEnv(name) {
8420
+ const raw = readEnvString(name);
8421
+ if (!raw) {
8422
+ return [];
8423
+ }
8424
+ return raw.split("|").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => entry.split(",").map((part) => part.trim()));
8425
+ }
8426
+ function resolveBootstrapEndpoint(options) {
8427
+ const { value, port: fallbackPort, injectedGlobalKey } = resolveExplicitBootstrapValue(options);
8428
+ if (!value) {
8429
+ throw new Error(
8430
+ options.runtime === "browser" ? `No bootstrap URL available. Pass bootstrap.url or inject globalThis.${injectedGlobalKey}.bootstrapUrl.` : "No bootstrap URL available. Set CADENZA_DB_ADDRESS or pass bootstrap.url."
8431
+ );
8432
+ }
8433
+ const raw = value.trim();
8434
+ if (raw.length === 0) {
8435
+ throw new Error("Bootstrap URL cannot be empty");
8436
+ }
8437
+ if (raw.includes("://")) {
8438
+ const parsed2 = new URL(raw);
8439
+ const protocol2 = parsed2.protocol.replace(":", "");
8440
+ if (protocol2 !== "http" && protocol2 !== "https") {
8441
+ throw new Error(`Unsupported bootstrap protocol: ${parsed2.protocol}`);
8442
+ }
8443
+ if (parsed2.pathname && parsed2.pathname !== "/" && parsed2.pathname.trim().length > 0) {
8444
+ throw new Error(
8445
+ "Bootstrap URL must be an origin without a path component."
8446
+ );
8447
+ }
8448
+ const port2 = parsed2.port ? readConfiguredPort(parsed2.port) : fallbackPort;
8449
+ if (!port2) {
8450
+ throw new Error(
8451
+ "Bootstrap URL must include a port or CADENZA_DB_PORT must be provided."
8452
+ );
8453
+ }
8454
+ return {
8455
+ url: buildBootstrapUrl(protocol2, parsed2.hostname, port2),
8456
+ protocol: protocol2,
8457
+ address: parsed2.hostname,
8458
+ port: port2,
8459
+ exposed: protocol2 === "https",
8460
+ injectedGlobalKey
8461
+ };
8462
+ }
8463
+ if (raw.includes("/") || raw.includes("?") || raw.includes("#")) {
8464
+ throw new Error(
8465
+ "Bootstrap address without protocol must not include a path, query, or hash."
8466
+ );
8467
+ }
8468
+ const parsed = new URL(`http://${raw}`);
8469
+ const protocol = "http";
8470
+ const port = parsed.port ? readConfiguredPort(parsed.port) : fallbackPort;
8471
+ if (!port) {
8472
+ throw new Error(
8473
+ "Bootstrap address must include a port or CADENZA_DB_PORT must be provided."
8474
+ );
8475
+ }
8476
+ return {
8477
+ url: buildBootstrapUrl(protocol, parsed.hostname, port),
8478
+ protocol,
8479
+ address: parsed.hostname,
8480
+ port,
8481
+ exposed: false,
8482
+ injectedGlobalKey
8483
+ };
8484
+ }
8485
+
7838
8486
  // src/Cadenza.ts
7839
8487
  var CadenzaService = class {
7840
8488
  /**
@@ -7853,11 +8501,60 @@ var CadenzaService = class {
7853
8501
  this.metaRunner = import_core4.default.metaRunner;
7854
8502
  this.registry = import_core4.default.registry;
7855
8503
  this.serviceRegistry = ServiceRegistry.instance;
7856
- SignalController.instance;
7857
8504
  RestController.instance;
7858
8505
  SocketController.instance;
7859
8506
  console.log("BOOTSTRAPPED");
7860
8507
  }
8508
+ static ensureTransportControllers(isFrontend) {
8509
+ if (!isFrontend) {
8510
+ SignalController.instance;
8511
+ }
8512
+ }
8513
+ static setHydrationResults(hydration) {
8514
+ this.hydratedInquiryResults = /* @__PURE__ */ new Map();
8515
+ const initialInquiryResults = hydration?.initialInquiryResults ?? {};
8516
+ for (const [key, value] of Object.entries(initialInquiryResults)) {
8517
+ this.hydratedInquiryResults.set(key, value);
8518
+ }
8519
+ }
8520
+ static consumeHydratedInquiryResult(hydrationKey) {
8521
+ if (!hydrationKey) {
8522
+ return void 0;
8523
+ }
8524
+ const result = this.hydratedInquiryResults.get(hydrationKey);
8525
+ if (result === void 0) {
8526
+ return void 0;
8527
+ }
8528
+ this.hydratedInquiryResults.delete(hydrationKey);
8529
+ return result;
8530
+ }
8531
+ static ensureFrontendSyncLoop() {
8532
+ if (this.frontendSyncScheduled) {
8533
+ return;
8534
+ }
8535
+ this.frontendSyncScheduled = true;
8536
+ import_core4.default.interval("meta.sync_requested", { __syncing: false }, 18e4);
8537
+ import_core4.default.schedule("meta.sync_requested", { __syncing: false }, 250);
8538
+ }
8539
+ static normalizeDeclaredTransports(transports, serviceId) {
8540
+ return (transports ?? []).map((transport) => normalizeServiceTransportConfig(transport)).filter(
8541
+ (transport) => !!transport
8542
+ ).map((transport, index) => ({
8543
+ ...transport,
8544
+ uuid: `${serviceId}-transport-${index + 1}`
8545
+ }));
8546
+ }
8547
+ static createBootstrapTransport(serviceInstanceId, role, endpoint) {
8548
+ return {
8549
+ uuid: `${serviceInstanceId}-${role}-bootstrap`,
8550
+ service_instance_id: serviceInstanceId,
8551
+ role,
8552
+ origin: endpoint.url,
8553
+ protocols: ["rest", "socket"],
8554
+ security_profile: null,
8555
+ auth_strategy: null
8556
+ };
8557
+ }
7861
8558
  /**
7862
8559
  * Validates the provided service name based on specific rules.
7863
8560
  *
@@ -7970,6 +8667,12 @@ var CadenzaService = class {
7970
8667
  }
7971
8668
  static async inquire(inquiry, context, options = {}) {
7972
8669
  this.bootstrap();
8670
+ const hydratedResult = this.consumeHydratedInquiryResult(
8671
+ options.hydrationKey
8672
+ );
8673
+ if (hydratedResult !== void 0) {
8674
+ return hydratedResult;
8675
+ }
7973
8676
  const observer = this.inquiryBroker?.inquiryObservers.get(inquiry);
7974
8677
  const allResponders = observer ? Array.from(observer.tasks).map((task) => ({
7975
8678
  task,
@@ -8574,58 +9277,97 @@ var CadenzaService = class {
8574
9277
  const serviceId = options.customServiceId ?? (0, import_uuid3.v4)();
8575
9278
  this.serviceRegistry.serviceName = serviceName;
8576
9279
  this.serviceRegistry.serviceInstanceId = serviceId;
9280
+ this.setHydrationResults(options.hydration);
9281
+ const explicitFrontendMode = options.isFrontend;
8577
9282
  options = {
8578
9283
  loadBalance: true,
8579
9284
  useSocket: true,
8580
9285
  displayName: void 0,
8581
9286
  isMeta: false,
8582
- port: parseInt(process.env.HTTP_PORT ?? "3000"),
8583
- securityProfile: process.env.SECURITY_PROFILE ?? "medium",
8584
- networkMode: process.env.NETWORK_MODE ?? "dev",
9287
+ port: readIntegerEnv("HTTP_PORT", 3e3),
9288
+ securityProfile: readStringEnv("SECURITY_PROFILE") ?? "medium",
9289
+ networkMode: readStringEnv("NETWORK_MODE") ?? "dev",
8585
9290
  retryCount: 3,
8586
9291
  cadenzaDB: {
8587
- connect: true,
8588
- address: process.env.CADENZA_DB_ADDRESS ?? "localhost",
8589
- port: parseInt(process.env.CADENZA_DB_PORT ?? "5000")
9292
+ connect: true
8590
9293
  },
8591
- relatedServices: process.env.RELATED_SERVICES ? process.env.RELATED_SERVICES.split("|").map(
8592
- (s) => s.trim().split(",")
8593
- ) : [],
8594
- isFrontend: isBrowser,
9294
+ relatedServices: readListEnv("RELATED_SERVICES"),
9295
+ isFrontend: typeof explicitFrontendMode === "boolean" ? explicitFrontendMode : isBrowser,
8595
9296
  ...options
8596
9297
  };
9298
+ const isFrontend = !!options.isFrontend;
9299
+ const declaredTransports = this.normalizeDeclaredTransports(
9300
+ options.transports,
9301
+ serviceId
9302
+ );
9303
+ this.serviceRegistry.isFrontend = isFrontend;
9304
+ this.serviceRegistry.useSocket = !!options.useSocket;
9305
+ this.serviceRegistry.retryCount = options.retryCount ?? 3;
9306
+ this.ensureTransportControllers(isFrontend);
9307
+ const resolvedBootstrapEndpoint = options.cadenzaDB?.connect ? resolveBootstrapEndpoint({
9308
+ runtime: isFrontend ? "browser" : "server",
9309
+ bootstrap: options.bootstrap,
9310
+ cadenzaDB: options.cadenzaDB
9311
+ }) : void 0;
9312
+ if (resolvedBootstrapEndpoint) {
9313
+ options.cadenzaDB = {
9314
+ ...options.cadenzaDB,
9315
+ connect: true,
9316
+ address: resolvedBootstrapEndpoint.address,
9317
+ port: resolvedBootstrapEndpoint.port
9318
+ };
9319
+ }
8597
9320
  if (options.cadenzaDB?.connect) {
8598
9321
  this.emit("meta.initializing_service", {
8599
9322
  // Seed the CadenzaDB
8600
9323
  serviceInstance: {
8601
9324
  uuid: "cadenza-db",
8602
9325
  serviceName: "CadenzaDB",
8603
- address: options.cadenzaDB?.address,
8604
- port: options.cadenzaDB?.port,
8605
- exposed: options.networkMode !== "dev",
8606
9326
  numberOfRunningGraphs: 0,
8607
9327
  isActive: true,
8608
9328
  // Assume it is deployed
8609
9329
  isNonResponsive: false,
8610
9330
  isBlocked: false,
8611
- health: {}
9331
+ health: {},
9332
+ isFrontend: false,
9333
+ transports: resolvedBootstrapEndpoint ? [
9334
+ this.createBootstrapTransport(
9335
+ "cadenza-db",
9336
+ isFrontend ? "public" : "internal",
9337
+ resolvedBootstrapEndpoint
9338
+ )
9339
+ ] : []
8612
9340
  }
8613
9341
  });
8614
9342
  }
8615
9343
  options.relatedServices?.forEach((service) => {
9344
+ const relatedTransport = normalizeServiceTransportConfig({
9345
+ role: isFrontend ? "public" : "internal",
9346
+ origin: service[2].includes("://") ? service[2] : `http://${service[2]}`,
9347
+ protocols: ["rest", "socket"]
9348
+ });
8616
9349
  this.emit("meta.initializing_service", {
8617
9350
  serviceInstance: {
8618
9351
  uuid: service[0],
8619
9352
  serviceName: service[1],
8620
- address: service[2].split(":")[0],
8621
- port: service[2].split(":")[1] ?? 3e3,
8622
- exposed: options.networkMode !== "dev",
8623
9353
  numberOfRunningGraphs: 0,
8624
9354
  isActive: true,
8625
9355
  // Assume it is deployed
8626
9356
  isNonResponsive: false,
8627
9357
  isBlocked: false,
8628
- health: {}
9358
+ health: {},
9359
+ isFrontend: false,
9360
+ transports: relatedTransport ? [
9361
+ {
9362
+ uuid: `${service[0]}-${relatedTransport.role}`,
9363
+ service_instance_id: service[0],
9364
+ role: relatedTransport.role,
9365
+ origin: relatedTransport.origin,
9366
+ protocols: relatedTransport.protocols ?? ["rest", "socket"],
9367
+ security_profile: relatedTransport.securityProfile ?? null,
9368
+ auth_strategy: relatedTransport.authStrategy ?? null
9369
+ }
9370
+ ] : []
8629
9371
  }
8630
9372
  });
8631
9373
  });
@@ -8646,7 +9388,9 @@ var CadenzaService = class {
8646
9388
  __networkMode: options.networkMode,
8647
9389
  __retryCount: options.retryCount,
8648
9390
  __cadenzaDBConnect: options.cadenzaDB?.connect,
8649
- __isDatabase: options.isDatabase
9391
+ __isDatabase: options.isDatabase,
9392
+ __isFrontend: isFrontend,
9393
+ __declaredTransports: declaredTransports
8650
9394
  };
8651
9395
  if (options.cadenzaDB?.connect) {
8652
9396
  this.createEphemeralMetaTask("Create service", async (context, emit) => {
@@ -8662,12 +9406,42 @@ var CadenzaService = class {
8662
9406
  }).doOn("meta.rest.handshake");
8663
9407
  }
8664
9408
  this.createMetaTask("Handle service setup completion", () => {
8665
- GraphMetadataController.instance;
8666
- GraphSyncController.instance.isCadenzaDBReady = !!options.cadenzaDB?.connect;
8667
- GraphSyncController.instance.init();
9409
+ if (isFrontend) {
9410
+ registerActorSessionPersistenceTasks();
9411
+ this.ensureFrontendSyncLoop();
9412
+ } else {
9413
+ GraphMetadataController.instance;
9414
+ GraphSyncController.instance.isCadenzaDBReady = !!options.cadenzaDB?.connect;
9415
+ GraphSyncController.instance.init();
9416
+ }
8668
9417
  this.log("Service created.");
8669
9418
  return true;
8670
9419
  }).doOn("meta.service_registry.instance_inserted");
9420
+ if (!options.cadenzaDB?.connect && isFrontend) {
9421
+ import_core4.default.schedule(
9422
+ "meta.service_registry.instance_registration_requested",
9423
+ {
9424
+ data: {
9425
+ uuid: serviceId,
9426
+ process_pid: 1,
9427
+ service_name: serviceName,
9428
+ is_frontend: true,
9429
+ is_active: true,
9430
+ is_non_responsive: false,
9431
+ is_blocked: false,
9432
+ health: {}
9433
+ },
9434
+ transportData: [],
9435
+ __serviceName: serviceName,
9436
+ __serviceInstanceId: serviceId,
9437
+ __useSocket: options.useSocket,
9438
+ __retryCount: options.retryCount,
9439
+ __isFrontend: true,
9440
+ __skipRemoteExecution: true
9441
+ },
9442
+ 0
9443
+ );
9444
+ }
8671
9445
  this.serviceCreated = true;
8672
9446
  }
8673
9447
  /**
@@ -8693,9 +9467,9 @@ var CadenzaService = class {
8693
9467
  * @return {void}
8694
9468
  */
8695
9469
  static createPostgresActor(name, schema, description = "", options = {}) {
8696
- if (isBrowser) {
9470
+ if (isBrowser || options.isFrontend) {
8697
9471
  console.warn(
8698
- "PostgresActor creation is not supported in the browser."
9472
+ "PostgresActor creation is not supported in frontend mode."
8699
9473
  );
8700
9474
  return;
8701
9475
  }
@@ -8740,9 +9514,9 @@ var CadenzaService = class {
8740
9514
  * Creates a dedicated database service by composing a PostgresActor and a Cadenza service.
8741
9515
  */
8742
9516
  static createDatabaseService(name, schema, description = "", options = {}) {
8743
- if (isBrowser) {
9517
+ if (isBrowser || options.isFrontend) {
8744
9518
  console.warn(
8745
- "Database service creation is not supported in the browser. Use the CadenzaDB service instead."
9519
+ "Database service creation is not supported in frontend mode. Use the CadenzaDB service instead."
8746
9520
  );
8747
9521
  return;
8748
9522
  }
@@ -8837,7 +9611,7 @@ var CadenzaService = class {
8837
9611
  retryCount: 3,
8838
9612
  databaseType: "postgres",
8839
9613
  databaseName: (0, import_lodash_es2.snakeCase)(name),
8840
- poolSize: parseInt(process.env.DATABASE_POOL_SIZE ?? "10"),
9614
+ poolSize: readIntegerEnv("DATABASE_POOL_SIZE", 10),
8841
9615
  ownerServiceName: options.ownerServiceName ?? this.serviceRegistry?.serviceName ?? null,
8842
9616
  ...options
8843
9617
  };
@@ -8848,18 +9622,16 @@ var CadenzaService = class {
8848
9622
  useSocket: true,
8849
9623
  displayName: void 0,
8850
9624
  isMeta: false,
8851
- port: parseInt(process.env.HTTP_PORT ?? "3000"),
8852
- securityProfile: process.env.SECURITY_PROFILE ?? "medium",
8853
- networkMode: process.env.NETWORK_MODE ?? "dev",
9625
+ port: readIntegerEnv("HTTP_PORT", 3e3),
9626
+ securityProfile: readStringEnv("SECURITY_PROFILE") ?? "medium",
9627
+ networkMode: readStringEnv("NETWORK_MODE") ?? "dev",
8854
9628
  retryCount: 3,
8855
9629
  cadenzaDB: {
8856
- connect: true,
8857
- address: process.env.CADENZA_DB_ADDRESS ?? "localhost",
8858
- port: parseInt(process.env.CADENZA_DB_PORT ?? "5000")
9630
+ connect: true
8859
9631
  },
8860
9632
  databaseType: "postgres",
8861
9633
  databaseName: (0, import_lodash_es2.snakeCase)(name),
8862
- poolSize: parseInt(process.env.DATABASE_POOL_SIZE ?? "10"),
9634
+ poolSize: readIntegerEnv("DATABASE_POOL_SIZE", 10),
8863
9635
  isDatabase: true,
8864
9636
  ownerServiceName: options.ownerServiceName ?? name,
8865
9637
  ...options
@@ -9307,19 +10079,291 @@ var CadenzaService = class {
9307
10079
  }
9308
10080
  static reset() {
9309
10081
  import_core4.default.reset();
9310
- this.serviceRegistry.reset();
10082
+ this.serviceRegistry?.reset();
10083
+ this.isBootstrapped = false;
10084
+ this.serviceCreated = false;
10085
+ this.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
10086
+ this.hydratedInquiryResults = /* @__PURE__ */ new Map();
10087
+ this.frontendSyncScheduled = false;
9311
10088
  }
9312
10089
  };
9313
10090
  CadenzaService.isBootstrapped = false;
9314
10091
  CadenzaService.serviceCreated = false;
9315
10092
  CadenzaService.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
10093
+ CadenzaService.hydratedInquiryResults = /* @__PURE__ */ new Map();
10094
+ CadenzaService.frontendSyncScheduled = false;
9316
10095
 
9317
10096
  // src/index.ts
9318
10097
  var import_core5 = require("@cadenza.io/core");
10098
+
10099
+ // src/ssr/createSSRInquiryBridge.ts
10100
+ var import_uuid4 = require("uuid");
10101
+ function ensureFetch() {
10102
+ if (typeof globalThis.fetch !== "function") {
10103
+ throw new Error("SSR inquiry bridge requires global fetch support.");
10104
+ }
10105
+ return globalThis.fetch.bind(globalThis);
10106
+ }
10107
+ function normalizeArrayResponse(value, keys) {
10108
+ for (const key of keys) {
10109
+ if (Array.isArray(value?.[key])) {
10110
+ return value[key];
10111
+ }
10112
+ }
10113
+ if (Array.isArray(value?.rows)) {
10114
+ return value.rows;
10115
+ }
10116
+ if (Array.isArray(value?.data)) {
10117
+ return value.data;
10118
+ }
10119
+ return [];
10120
+ }
10121
+ function normalizeIntentMap(raw) {
10122
+ const intentName = String(raw.intentName ?? raw.intent_name ?? "").trim();
10123
+ const serviceName = String(raw.serviceName ?? raw.service_name ?? "").trim();
10124
+ const taskName = String(raw.taskName ?? raw.task_name ?? "").trim();
10125
+ const taskVersion = Math.max(
10126
+ 1,
10127
+ Math.trunc(Number(raw.taskVersion ?? raw.task_version ?? 1) || 1)
10128
+ );
10129
+ if (!intentName || !serviceName || !taskName) {
10130
+ return null;
10131
+ }
10132
+ return {
10133
+ intentName,
10134
+ serviceName,
10135
+ taskName,
10136
+ taskVersion,
10137
+ deleted: Boolean(raw.deleted)
10138
+ };
10139
+ }
10140
+ function buildInquiryMeta(inquiry, startedAt, statuses) {
10141
+ const counts = summarizeResponderStatuses(statuses);
10142
+ return {
10143
+ inquiry,
10144
+ isMetaInquiry: isMetaIntentName(inquiry),
10145
+ totalResponders: statuses.length,
10146
+ eligibleResponders: statuses.length,
10147
+ filteredOutResponders: 0,
10148
+ responded: counts.responded,
10149
+ failed: counts.failed,
10150
+ timedOut: counts.timedOut,
10151
+ pending: counts.pending,
10152
+ durationMs: Date.now() - startedAt,
10153
+ responders: statuses
10154
+ };
10155
+ }
10156
+ function createSSRInquiryBridge(options = {}) {
10157
+ const bootstrapEndpoint = resolveBootstrapEndpoint({
10158
+ runtime: "server",
10159
+ bootstrap: options.bootstrap,
10160
+ cadenzaDB: options.cadenzaDB
10161
+ });
10162
+ const fetchImplementation = ensureFetch();
10163
+ const initialInquiryResults = {};
10164
+ const postDelegation = async (targetUrl, remoteRoutineName, context, timeoutMs) => {
10165
+ const signal = AbortSignal.timeout(timeoutMs);
10166
+ const response = await fetchImplementation(`${targetUrl}/delegation`, {
10167
+ method: "POST",
10168
+ headers: {
10169
+ "Content-Type": "application/json"
10170
+ },
10171
+ body: JSON.stringify({
10172
+ ...context,
10173
+ __remoteRoutineName: remoteRoutineName,
10174
+ __metadata: {
10175
+ ...context.__metadata ?? {},
10176
+ __deputyExecId: (0, import_uuid4.v4)()
10177
+ }
10178
+ }),
10179
+ signal
10180
+ });
10181
+ return await response.json();
10182
+ };
10183
+ const queryTable = async (tableName, queryData, timeoutMs) => {
10184
+ const response = await postDelegation(
10185
+ bootstrapEndpoint.url,
10186
+ `Query ${tableName}`,
10187
+ { queryData },
10188
+ timeoutMs
10189
+ );
10190
+ return normalizeArrayResponse(response, [
10191
+ `${tableName}Rows`,
10192
+ `${tableName}s`,
10193
+ tableName,
10194
+ tableName.replace(/_([a-z])/g, (_match, char) => char.toUpperCase())
10195
+ ]);
10196
+ };
10197
+ return {
10198
+ async inquire(inquiry, context = {}, inquiryOptions = {}) {
10199
+ const startedAt = Date.now();
10200
+ const overallTimeoutMs = inquiryOptions.overallTimeoutMs ?? inquiryOptions.timeout ?? 3e4;
10201
+ const perResponderTimeoutMs = inquiryOptions.perResponderTimeoutMs ?? overallTimeoutMs;
10202
+ const intentMaps = (await queryTable(
10203
+ "intent_to_task_map",
10204
+ {
10205
+ filter: {
10206
+ intent_name: inquiry
10207
+ }
10208
+ },
10209
+ overallTimeoutMs
10210
+ )).map(normalizeIntentMap).filter(
10211
+ (item) => !!item && item.intentName === inquiry && !item.deleted
10212
+ );
10213
+ if (intentMaps.length === 0) {
10214
+ return {
10215
+ __inquiryMeta: buildInquiryMeta(inquiry, startedAt, [])
10216
+ };
10217
+ }
10218
+ const relevantServiceNames = Array.from(
10219
+ new Set(intentMaps.map((item) => item.serviceName))
10220
+ );
10221
+ const rawServiceInstances = await queryTable(
10222
+ "service_instance",
10223
+ {
10224
+ filter: {
10225
+ service_name: relevantServiceNames
10226
+ }
10227
+ },
10228
+ overallTimeoutMs
10229
+ );
10230
+ const rawServiceTransports = await queryTable(
10231
+ "service_instance_transport",
10232
+ {
10233
+ filter: {
10234
+ deleted: false
10235
+ }
10236
+ },
10237
+ overallTimeoutMs
10238
+ );
10239
+ const transportsByInstance = /* @__PURE__ */ new Map();
10240
+ for (const transport of rawServiceTransports) {
10241
+ const serviceInstanceId = String(
10242
+ transport.serviceInstanceId ?? transport.service_instance_id ?? ""
10243
+ ).trim();
10244
+ if (!serviceInstanceId) {
10245
+ continue;
10246
+ }
10247
+ if (!transportsByInstance.has(serviceInstanceId)) {
10248
+ transportsByInstance.set(serviceInstanceId, []);
10249
+ }
10250
+ transportsByInstance.get(serviceInstanceId).push(transport);
10251
+ }
10252
+ const serviceInstances = rawServiceInstances.map(
10253
+ (instance) => normalizeServiceInstanceDescriptor({
10254
+ ...instance,
10255
+ transports: transportsByInstance.get(String(instance.uuid ?? "").trim()) ?? []
10256
+ })
10257
+ ).filter(
10258
+ (item) => !!item && relevantServiceNames.includes(item.serviceName) && item.isActive && !item.isNonResponsive && !item.isBlocked
10259
+ ).sort((left, right) => {
10260
+ if (left.serviceName !== right.serviceName) {
10261
+ return left.serviceName.localeCompare(right.serviceName);
10262
+ }
10263
+ if (left.isPrimary !== right.isPrimary) {
10264
+ return left.isPrimary ? -1 : 1;
10265
+ }
10266
+ return (left.numberOfRunningGraphs ?? 0) - (right.numberOfRunningGraphs ?? 0);
10267
+ });
10268
+ const statuses = intentMaps.map((map) => ({
10269
+ isRemote: true,
10270
+ serviceName: map.serviceName,
10271
+ taskName: map.taskName,
10272
+ taskVersion: map.taskVersion,
10273
+ localTaskName: `SSR inquiry via ${map.serviceName} (${map.taskName} v${map.taskVersion})`,
10274
+ status: "timed_out",
10275
+ durationMs: 0
10276
+ }));
10277
+ const fulfilledContexts = await Promise.all(
10278
+ intentMaps.map(async (map, index) => {
10279
+ const status = statuses[index];
10280
+ const startedAtForResponder = Date.now();
10281
+ const selectedInstance = serviceInstances.find(
10282
+ (instance) => instance.serviceName === map.serviceName
10283
+ );
10284
+ if (!selectedInstance) {
10285
+ status.status = "failed";
10286
+ status.error = `No active instances for ${map.serviceName}`;
10287
+ status.durationMs = Date.now() - startedAtForResponder;
10288
+ return null;
10289
+ }
10290
+ const targetTransport = selectTransportForRole(
10291
+ selectedInstance.transports,
10292
+ "internal",
10293
+ "rest"
10294
+ );
10295
+ if (!targetTransport) {
10296
+ status.status = "failed";
10297
+ status.error = `No internal transport for ${selectedInstance.serviceName}/${selectedInstance.uuid}`;
10298
+ status.durationMs = Date.now() - startedAtForResponder;
10299
+ return null;
10300
+ }
10301
+ const targetUrl = targetTransport.origin;
10302
+ try {
10303
+ const result = await postDelegation(
10304
+ targetUrl,
10305
+ map.taskName,
10306
+ context,
10307
+ perResponderTimeoutMs
10308
+ );
10309
+ status.durationMs = Date.now() - startedAtForResponder;
10310
+ if (result?.errored || result?.failed) {
10311
+ status.status = "failed";
10312
+ status.error = String(
10313
+ result?.__error ?? result?.error ?? "Remote inquiry failed"
10314
+ );
10315
+ return null;
10316
+ }
10317
+ status.status = "fulfilled";
10318
+ return result;
10319
+ } catch (error) {
10320
+ status.durationMs = Date.now() - startedAtForResponder;
10321
+ if (error instanceof Error && (error.name === "AbortError" || /timed out/i.test(error.message))) {
10322
+ status.status = "timed_out";
10323
+ status.error = error.message;
10324
+ return null;
10325
+ }
10326
+ status.status = "failed";
10327
+ status.error = error instanceof Error ? error.message : String(error);
10328
+ return null;
10329
+ }
10330
+ })
10331
+ );
10332
+ const mergedContext = mergeInquiryContexts(
10333
+ fulfilledContexts.filter(
10334
+ (item) => item !== null
10335
+ )
10336
+ );
10337
+ const responseContext = {
10338
+ ...mergedContext,
10339
+ __inquiryMeta: buildInquiryMeta(inquiry, startedAt, statuses)
10340
+ };
10341
+ if (inquiryOptions.hydrationKey) {
10342
+ initialInquiryResults[inquiryOptions.hydrationKey] = responseContext;
10343
+ }
10344
+ if (inquiryOptions.requireComplete && statuses.some((status) => status.status !== "fulfilled")) {
10345
+ throw {
10346
+ ...responseContext,
10347
+ __error: `Inquiry '${inquiry}' did not complete successfully`,
10348
+ errored: true
10349
+ };
10350
+ }
10351
+ return responseContext;
10352
+ },
10353
+ dehydrate() {
10354
+ return {
10355
+ initialInquiryResults: { ...initialInquiryResults }
10356
+ };
10357
+ }
10358
+ };
10359
+ }
10360
+
10361
+ // src/index.ts
9319
10362
  var index_default = CadenzaService;
9320
10363
  // Annotate the CommonJS export names for ESM import in node:
9321
10364
  0 && (module.exports = {
9322
10365
  Actor,
10366
+ DatabaseController,
9323
10367
  DatabaseTask,
9324
10368
  DebounceTask,
9325
10369
  DeputyTask,
@@ -9331,6 +10375,7 @@ var index_default = CadenzaService;
9331
10375
  SignalController,
9332
10376
  SignalTransmissionTask,
9333
10377
  SocketController,
9334
- Task
10378
+ Task,
10379
+ createSSRInquiryBridge
9335
10380
  });
9336
10381
  //# sourceMappingURL=index.js.map