@cadenza.io/service 2.12.0 → 2.16.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;
2741
- const createHttpServer = async (ctx2) => {
2742
- await new Promise((resolve) => {
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
+ };
3191
+ const createHttpServer = async (ctx2) => {
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}`;
4996
+ const transportId = String(
4997
+ input.serviceTransportId ?? input.transportId ?? ""
4998
+ ).trim();
4999
+ return transportId || void 0;
4535
5000
  }
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);
4545
- }
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,
@@ -4848,8 +5305,132 @@ var SignalController = class _SignalController {
4848
5305
  }
4849
5306
  };
4850
5307
 
4851
- // src/graph/controllers/GraphMetadataController.ts
5308
+ // src/graph/controllers/registerActorSessionPersistence.ts
4852
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
+ }
5432
+
5433
+ // src/graph/controllers/GraphMetadataController.ts
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 {
@@ -5244,6 +5709,9 @@ function normalizeIntentToken(value) {
5244
5709
  }
5245
5710
  return normalized;
5246
5711
  }
5712
+ function buildPostgresActorName(name) {
5713
+ return `${String(name ?? "").trim()}PostgresActor`;
5714
+ }
5247
5715
  function validateIntentName(intentName) {
5248
5716
  if (!intentName || typeof intentName !== "string") {
5249
5717
  throw new Error("Intent name must be a non-empty string");
@@ -5381,7 +5849,8 @@ function resolveDataRows(data) {
5381
5849
  }
5382
5850
  var DatabaseController = class _DatabaseController {
5383
5851
  constructor() {
5384
- this.registrationsByService = /* @__PURE__ */ new Map();
5852
+ this.registrationsByActorName = /* @__PURE__ */ new Map();
5853
+ this.registrationsByActorToken = /* @__PURE__ */ new Map();
5385
5854
  this.adminDbClient = new import_pg.Pool({
5386
5855
  connectionString: process.env.DATABASE_ADDRESS ?? "",
5387
5856
  database: "postgres",
@@ -5392,15 +5861,11 @@ var DatabaseController = class _DatabaseController {
5392
5861
  CadenzaService.createMetaTask(
5393
5862
  "Route PostgresActor setup requests",
5394
5863
  (ctx) => {
5395
- const serviceName = String(ctx.options?.serviceName ?? ctx.serviceName ?? "");
5396
- if (!serviceName) {
5397
- return ctx;
5398
- }
5399
- const registration = this.registrationsByService.get(serviceName);
5864
+ const registration = this.resolveRegistration(ctx);
5400
5865
  if (!registration) {
5401
5866
  return ctx;
5402
5867
  }
5403
- CadenzaService.emit(`meta.postgres_actor.setup_requested.${registration.actorToken}`, ctx);
5868
+ this.requestPostgresActorSetup(registration, ctx);
5404
5869
  return ctx;
5405
5870
  },
5406
5871
  "Routes generic database init requests to actor-scoped setup signal.",
@@ -5412,23 +5877,25 @@ var DatabaseController = class _DatabaseController {
5412
5877
  return this._instance;
5413
5878
  }
5414
5879
  reset() {
5415
- for (const registration of this.registrationsByService.values()) {
5880
+ for (const registration of this.registrationsByActorName.values()) {
5416
5881
  const runtimeState = registration.actor.getRuntimeState(registration.actorKey);
5417
5882
  if (runtimeState?.pool) {
5418
5883
  runtimeState.pool.end().catch(() => void 0);
5419
5884
  }
5420
5885
  }
5421
- this.registrationsByService.clear();
5886
+ this.registrationsByActorName.clear();
5887
+ this.registrationsByActorToken.clear();
5422
5888
  this.adminDbClient.end().catch(() => void 0);
5423
5889
  }
5424
- createPostgresActor(serviceName, schema, options) {
5425
- const existing = this.registrationsByService.get(serviceName);
5890
+ createPostgresActor(name, schema, description, options) {
5891
+ const actorName = buildPostgresActorName(name);
5892
+ const existing = this.registrationsByActorName.get(actorName);
5426
5893
  if (existing) {
5427
5894
  return existing;
5428
5895
  }
5429
- const actorName = `${serviceName}PostgresActor`;
5430
5896
  const actorToken = normalizeIntentToken(actorName);
5431
- const actorKey = String(options.databaseName ?? (0, import_lodash_es.snakeCase)(serviceName));
5897
+ const actorKey = String(options.databaseName ?? (0, import_lodash_es.snakeCase)(name));
5898
+ const ownerServiceName = options.ownerServiceName ?? CadenzaService.serviceRegistry?.serviceName ?? null;
5432
5899
  const optionTimeout = typeof options.timeoutMs === "number" ? Number(options.timeoutMs) : Number(options.timeout);
5433
5900
  const safetyPolicy = {
5434
5901
  statementTimeoutMs: normalizePositiveInteger(
@@ -5446,7 +5913,7 @@ var DatabaseController = class _DatabaseController {
5446
5913
  const actor = CadenzaService.createActor(
5447
5914
  {
5448
5915
  name: actorName,
5449
- description: "Specialized PostgresActor owning pool runtime state and schema-driven DB task generation.",
5916
+ description: description || "Specialized PostgresActor owning pool runtime state and schema-driven DB task generation.",
5450
5917
  defaultKey: actorKey,
5451
5918
  keyResolver: (input) => typeof input.databaseName === "string" ? input.databaseName : void 0,
5452
5919
  loadPolicy: "eager",
@@ -5454,7 +5921,7 @@ var DatabaseController = class _DatabaseController {
5454
5921
  initState: {
5455
5922
  actorName,
5456
5923
  actorToken,
5457
- serviceName,
5924
+ ownerServiceName,
5458
5925
  databaseName: actorKey,
5459
5926
  status: "idle",
5460
5927
  schemaVersion: Number(schema.version ?? 1),
@@ -5469,23 +5936,99 @@ var DatabaseController = class _DatabaseController {
5469
5936
  { isMeta: Boolean(options.isMeta) }
5470
5937
  );
5471
5938
  const registration = {
5472
- serviceName,
5939
+ ownerServiceName,
5473
5940
  databaseName: actorKey,
5474
5941
  actorName,
5475
5942
  actorToken,
5476
5943
  actorKey,
5944
+ setupSignal: `meta.postgres_actor.setup_requested.${actorToken}`,
5945
+ setupDoneSignal: `meta.postgres_actor.setup_done.${actorToken}`,
5946
+ setupFailedSignal: `meta.postgres_actor.setup_failed.${actorToken}`,
5477
5947
  actor,
5478
5948
  schema,
5949
+ description,
5479
5950
  options,
5480
5951
  tasksGenerated: false,
5481
5952
  intentNames: /* @__PURE__ */ new Set()
5482
5953
  };
5483
- this.registrationsByService.set(serviceName, registration);
5954
+ this.registrationsByActorName.set(actorName, registration);
5955
+ this.registrationsByActorToken.set(actorToken, registration);
5484
5956
  this.registerSetupTask(registration);
5485
5957
  return registration;
5486
5958
  }
5959
+ requestPostgresActorSetup(registrationOrName, ctx = {}) {
5960
+ const registration = typeof registrationOrName === "string" ? this.resolveRegistration({
5961
+ actorName: registrationOrName
5962
+ }) : registrationOrName;
5963
+ if (!registration) {
5964
+ return void 0;
5965
+ }
5966
+ const payload = {
5967
+ ...ctx,
5968
+ actorName: registration.actorName,
5969
+ actorToken: registration.actorToken,
5970
+ databaseName: registration.databaseName,
5971
+ ownerServiceName: registration.ownerServiceName,
5972
+ options: {
5973
+ ...ctx.options ?? {},
5974
+ actorName: registration.actorName,
5975
+ actorToken: registration.actorToken,
5976
+ ownerServiceName: registration.ownerServiceName,
5977
+ databaseName: registration.databaseName
5978
+ }
5979
+ };
5980
+ const runtimeState = registration.actor.getRuntimeState(registration.actorKey);
5981
+ if (runtimeState?.ready) {
5982
+ this.emitSetupDone(registration, payload);
5983
+ return registration;
5984
+ }
5985
+ CadenzaService.emit(registration.setupSignal, payload);
5986
+ return registration;
5987
+ }
5988
+ resolveRegistration(ctx) {
5989
+ const rawActorToken = String(
5990
+ ctx.options?.actorToken ?? ctx.actorToken ?? ""
5991
+ ).trim();
5992
+ if (rawActorToken) {
5993
+ const actorToken = normalizeIntentToken(rawActorToken);
5994
+ const registration = this.registrationsByActorToken.get(actorToken);
5995
+ if (registration) {
5996
+ return registration;
5997
+ }
5998
+ }
5999
+ const rawActorName = String(
6000
+ ctx.options?.actorName ?? ctx.actorName ?? ctx.options?.postgresActorName ?? ctx.postgresActorName ?? ""
6001
+ ).trim();
6002
+ if (rawActorName) {
6003
+ const registration = this.registrationsByActorName.get(rawActorName) ?? this.registrationsByActorName.get(buildPostgresActorName(rawActorName));
6004
+ if (registration) {
6005
+ return registration;
6006
+ }
6007
+ }
6008
+ const legacyServiceName = String(
6009
+ ctx.options?.serviceName ?? ctx.serviceName ?? ""
6010
+ ).trim();
6011
+ if (legacyServiceName) {
6012
+ return this.registrationsByActorName.get(
6013
+ buildPostgresActorName(legacyServiceName)
6014
+ );
6015
+ }
6016
+ return void 0;
6017
+ }
6018
+ emitSetupDone(registration, payload) {
6019
+ const resolvedPayload = {
6020
+ ...payload,
6021
+ actorName: registration.actorName,
6022
+ actorToken: registration.actorToken,
6023
+ databaseName: registration.databaseName,
6024
+ ownerServiceName: registration.ownerServiceName,
6025
+ __success: true
6026
+ };
6027
+ CadenzaService.emit(registration.setupDoneSignal, resolvedPayload);
6028
+ CadenzaService.emit("meta.postgres_actor.setup_done", resolvedPayload);
6029
+ CadenzaService.emit("meta.database.setup_done", resolvedPayload);
6030
+ }
5487
6031
  registerSetupTask(registration) {
5488
- const setupSignal = `meta.postgres_actor.setup_requested.${registration.actorToken}`;
5489
6032
  CadenzaService.createMetaTask(
5490
6033
  `Setup ${registration.actorName}`,
5491
6034
  registration.actor.task(
@@ -5545,17 +6088,15 @@ var DatabaseController = class _DatabaseController {
5545
6088
  lastError: null,
5546
6089
  tables: Object.keys(registration.schema.tables ?? {})
5547
6090
  });
5548
- emit("meta.database.setup_done", {
5549
- serviceName: registration.serviceName,
5550
- databaseName: registration.databaseName,
5551
- actorName: registration.actorName,
5552
- __success: true
6091
+ this.emitSetupDone(registration, {
6092
+ ...input
5553
6093
  });
5554
6094
  return {
5555
6095
  ...input,
5556
6096
  __success: true,
5557
6097
  actorName: registration.actorName,
5558
- databaseName: registration.databaseName
6098
+ databaseName: registration.databaseName,
6099
+ ownerServiceName: registration.ownerServiceName
5559
6100
  };
5560
6101
  } catch (error) {
5561
6102
  const message = errorMessage(error);
@@ -5579,7 +6120,11 @@ var DatabaseController = class _DatabaseController {
5579
6120
  ),
5580
6121
  "Initializes PostgresActor runtime pool, applies schema, and generates CRUD tasks/intents.",
5581
6122
  { isMeta: true }
5582
- ).doOn(setupSignal);
6123
+ ).doOn(registration.setupSignal).emitsOnFail(
6124
+ registration.setupFailedSignal,
6125
+ "meta.postgres_actor.setup_failed",
6126
+ "meta.database.setup_failed"
6127
+ );
5583
6128
  }
5584
6129
  createTargetPool(databaseName, statementTimeoutMs) {
5585
6130
  const connectionString = this.buildDatabaseConnectionString(databaseName);
@@ -7755,6 +8300,165 @@ var GraphSyncController = class _GraphSyncController {
7755
8300
  }
7756
8301
  };
7757
8302
 
8303
+ // src/utils/bootstrap.ts
8304
+ var DEFAULT_BOOTSTRAP_GLOBAL_KEY = "__CADENZA_RUNTIME__";
8305
+ function normalizeString3(value) {
8306
+ if (typeof value !== "string") {
8307
+ return void 0;
8308
+ }
8309
+ const normalized = value.trim();
8310
+ return normalized.length > 0 ? normalized : void 0;
8311
+ }
8312
+ function readEnvString(name) {
8313
+ if (typeof process === "undefined") {
8314
+ return void 0;
8315
+ }
8316
+ return normalizeString3(process.env?.[name]);
8317
+ }
8318
+ function readConfiguredPort(value) {
8319
+ if (value === void 0 || value === null || value === "") {
8320
+ return void 0;
8321
+ }
8322
+ const parsed = Number(value);
8323
+ if (!Number.isFinite(parsed)) {
8324
+ throw new Error(`Invalid port value: ${String(value)}`);
8325
+ }
8326
+ const normalized = Math.trunc(parsed);
8327
+ if (normalized <= 0) {
8328
+ throw new Error(`Port must be a positive integer: ${String(value)}`);
8329
+ }
8330
+ return normalized;
8331
+ }
8332
+ function buildBootstrapUrl(protocol, address, port) {
8333
+ return `${protocol}://${address}:${port}`;
8334
+ }
8335
+ function resolveInjectedBootstrapUrl(injectedGlobalKey) {
8336
+ if (typeof globalThis === "undefined") {
8337
+ return void 0;
8338
+ }
8339
+ const runtimeConfig = globalThis[injectedGlobalKey];
8340
+ return normalizeString3(runtimeConfig?.bootstrapUrl);
8341
+ }
8342
+ function resolveExplicitBootstrapValue(options) {
8343
+ const injectedGlobalKey = normalizeString3(options.bootstrap?.injectedGlobalKey) ?? DEFAULT_BOOTSTRAP_GLOBAL_KEY;
8344
+ const explicitBootstrapUrl = normalizeString3(options.bootstrap?.url);
8345
+ if (explicitBootstrapUrl) {
8346
+ return {
8347
+ value: explicitBootstrapUrl,
8348
+ port: readConfiguredPort(options.cadenzaDB?.port),
8349
+ injectedGlobalKey
8350
+ };
8351
+ }
8352
+ if (options.runtime === "browser") {
8353
+ const injected = resolveInjectedBootstrapUrl(injectedGlobalKey);
8354
+ if (injected) {
8355
+ return {
8356
+ value: injected,
8357
+ port: readConfiguredPort(options.cadenzaDB?.port),
8358
+ injectedGlobalKey
8359
+ };
8360
+ }
8361
+ }
8362
+ const explicitAddress = normalizeString3(options.cadenzaDB?.address);
8363
+ if (explicitAddress) {
8364
+ return {
8365
+ value: explicitAddress,
8366
+ port: readConfiguredPort(options.cadenzaDB?.port),
8367
+ injectedGlobalKey
8368
+ };
8369
+ }
8370
+ const envAddress = readEnvString("CADENZA_DB_ADDRESS");
8371
+ return {
8372
+ value: envAddress,
8373
+ port: readConfiguredPort(options.cadenzaDB?.port ?? readEnvString("CADENZA_DB_PORT")),
8374
+ injectedGlobalKey
8375
+ };
8376
+ }
8377
+ function readIntegerEnv(name, fallback) {
8378
+ const raw = readEnvString(name);
8379
+ if (!raw) {
8380
+ return fallback;
8381
+ }
8382
+ const parsed = Number(raw);
8383
+ if (!Number.isFinite(parsed)) {
8384
+ return fallback;
8385
+ }
8386
+ const normalized = Math.trunc(parsed);
8387
+ if (normalized <= 0) {
8388
+ return fallback;
8389
+ }
8390
+ return normalized;
8391
+ }
8392
+ function readStringEnv(name) {
8393
+ return readEnvString(name);
8394
+ }
8395
+ function readListEnv(name) {
8396
+ const raw = readEnvString(name);
8397
+ if (!raw) {
8398
+ return [];
8399
+ }
8400
+ return raw.split("|").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => entry.split(",").map((part) => part.trim()));
8401
+ }
8402
+ function resolveBootstrapEndpoint(options) {
8403
+ const { value, port: fallbackPort, injectedGlobalKey } = resolveExplicitBootstrapValue(options);
8404
+ if (!value) {
8405
+ throw new Error(
8406
+ 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."
8407
+ );
8408
+ }
8409
+ const raw = value.trim();
8410
+ if (raw.length === 0) {
8411
+ throw new Error("Bootstrap URL cannot be empty");
8412
+ }
8413
+ if (raw.includes("://")) {
8414
+ const parsed2 = new URL(raw);
8415
+ const protocol2 = parsed2.protocol.replace(":", "");
8416
+ if (protocol2 !== "http" && protocol2 !== "https") {
8417
+ throw new Error(`Unsupported bootstrap protocol: ${parsed2.protocol}`);
8418
+ }
8419
+ if (parsed2.pathname && parsed2.pathname !== "/" && parsed2.pathname.trim().length > 0) {
8420
+ throw new Error(
8421
+ "Bootstrap URL must be an origin without a path component."
8422
+ );
8423
+ }
8424
+ const port2 = parsed2.port ? readConfiguredPort(parsed2.port) : fallbackPort;
8425
+ if (!port2) {
8426
+ throw new Error(
8427
+ "Bootstrap URL must include a port or CADENZA_DB_PORT must be provided."
8428
+ );
8429
+ }
8430
+ return {
8431
+ url: buildBootstrapUrl(protocol2, parsed2.hostname, port2),
8432
+ protocol: protocol2,
8433
+ address: parsed2.hostname,
8434
+ port: port2,
8435
+ exposed: protocol2 === "https",
8436
+ injectedGlobalKey
8437
+ };
8438
+ }
8439
+ if (raw.includes("/") || raw.includes("?") || raw.includes("#")) {
8440
+ throw new Error(
8441
+ "Bootstrap address without protocol must not include a path, query, or hash."
8442
+ );
8443
+ }
8444
+ const parsed = new URL(`http://${raw}`);
8445
+ const protocol = "http";
8446
+ const port = parsed.port ? readConfiguredPort(parsed.port) : fallbackPort;
8447
+ if (!port) {
8448
+ throw new Error(
8449
+ "Bootstrap address must include a port or CADENZA_DB_PORT must be provided."
8450
+ );
8451
+ }
8452
+ return {
8453
+ url: buildBootstrapUrl(protocol, parsed.hostname, port),
8454
+ protocol,
8455
+ address: parsed.hostname,
8456
+ port,
8457
+ exposed: false,
8458
+ injectedGlobalKey
8459
+ };
8460
+ }
8461
+
7758
8462
  // src/Cadenza.ts
7759
8463
  var CadenzaService = class {
7760
8464
  /**
@@ -7773,11 +8477,60 @@ var CadenzaService = class {
7773
8477
  this.metaRunner = import_core4.default.metaRunner;
7774
8478
  this.registry = import_core4.default.registry;
7775
8479
  this.serviceRegistry = ServiceRegistry.instance;
7776
- SignalController.instance;
7777
8480
  RestController.instance;
7778
8481
  SocketController.instance;
7779
8482
  console.log("BOOTSTRAPPED");
7780
8483
  }
8484
+ static ensureTransportControllers(isFrontend) {
8485
+ if (!isFrontend) {
8486
+ SignalController.instance;
8487
+ }
8488
+ }
8489
+ static setHydrationResults(hydration) {
8490
+ this.hydratedInquiryResults = /* @__PURE__ */ new Map();
8491
+ const initialInquiryResults = hydration?.initialInquiryResults ?? {};
8492
+ for (const [key, value] of Object.entries(initialInquiryResults)) {
8493
+ this.hydratedInquiryResults.set(key, value);
8494
+ }
8495
+ }
8496
+ static consumeHydratedInquiryResult(hydrationKey) {
8497
+ if (!hydrationKey) {
8498
+ return void 0;
8499
+ }
8500
+ const result = this.hydratedInquiryResults.get(hydrationKey);
8501
+ if (result === void 0) {
8502
+ return void 0;
8503
+ }
8504
+ this.hydratedInquiryResults.delete(hydrationKey);
8505
+ return result;
8506
+ }
8507
+ static ensureFrontendSyncLoop() {
8508
+ if (this.frontendSyncScheduled) {
8509
+ return;
8510
+ }
8511
+ this.frontendSyncScheduled = true;
8512
+ import_core4.default.interval("meta.sync_requested", { __syncing: false }, 18e4);
8513
+ import_core4.default.schedule("meta.sync_requested", { __syncing: false }, 250);
8514
+ }
8515
+ static normalizeDeclaredTransports(transports, serviceId) {
8516
+ return (transports ?? []).map((transport) => normalizeServiceTransportConfig(transport)).filter(
8517
+ (transport) => !!transport
8518
+ ).map((transport, index) => ({
8519
+ ...transport,
8520
+ uuid: `${serviceId}-transport-${index + 1}`
8521
+ }));
8522
+ }
8523
+ static createBootstrapTransport(serviceInstanceId, role, endpoint) {
8524
+ return {
8525
+ uuid: `${serviceInstanceId}-${role}-bootstrap`,
8526
+ service_instance_id: serviceInstanceId,
8527
+ role,
8528
+ origin: endpoint.url,
8529
+ protocols: ["rest", "socket"],
8530
+ security_profile: null,
8531
+ auth_strategy: null
8532
+ };
8533
+ }
7781
8534
  /**
7782
8535
  * Validates the provided service name based on specific rules.
7783
8536
  *
@@ -7890,6 +8643,12 @@ var CadenzaService = class {
7890
8643
  }
7891
8644
  static async inquire(inquiry, context, options = {}) {
7892
8645
  this.bootstrap();
8646
+ const hydratedResult = this.consumeHydratedInquiryResult(
8647
+ options.hydrationKey
8648
+ );
8649
+ if (hydratedResult !== void 0) {
8650
+ return hydratedResult;
8651
+ }
7893
8652
  const observer = this.inquiryBroker?.inquiryObservers.get(inquiry);
7894
8653
  const allResponders = observer ? Array.from(observer.tasks).map((task) => ({
7895
8654
  task,
@@ -8494,58 +9253,97 @@ var CadenzaService = class {
8494
9253
  const serviceId = options.customServiceId ?? (0, import_uuid3.v4)();
8495
9254
  this.serviceRegistry.serviceName = serviceName;
8496
9255
  this.serviceRegistry.serviceInstanceId = serviceId;
9256
+ this.setHydrationResults(options.hydration);
9257
+ const explicitFrontendMode = options.isFrontend;
8497
9258
  options = {
8498
9259
  loadBalance: true,
8499
9260
  useSocket: true,
8500
9261
  displayName: void 0,
8501
9262
  isMeta: false,
8502
- port: parseInt(process.env.HTTP_PORT ?? "3000"),
8503
- securityProfile: process.env.SECURITY_PROFILE ?? "medium",
8504
- networkMode: process.env.NETWORK_MODE ?? "dev",
9263
+ port: readIntegerEnv("HTTP_PORT", 3e3),
9264
+ securityProfile: readStringEnv("SECURITY_PROFILE") ?? "medium",
9265
+ networkMode: readStringEnv("NETWORK_MODE") ?? "dev",
8505
9266
  retryCount: 3,
8506
9267
  cadenzaDB: {
8507
- connect: true,
8508
- address: process.env.CADENZA_DB_ADDRESS ?? "localhost",
8509
- port: parseInt(process.env.CADENZA_DB_PORT ?? "5000")
9268
+ connect: true
8510
9269
  },
8511
- relatedServices: process.env.RELATED_SERVICES ? process.env.RELATED_SERVICES.split("|").map(
8512
- (s) => s.trim().split(",")
8513
- ) : [],
8514
- isFrontend: isBrowser,
9270
+ relatedServices: readListEnv("RELATED_SERVICES"),
9271
+ isFrontend: typeof explicitFrontendMode === "boolean" ? explicitFrontendMode : isBrowser,
8515
9272
  ...options
8516
9273
  };
9274
+ const isFrontend = !!options.isFrontend;
9275
+ const declaredTransports = this.normalizeDeclaredTransports(
9276
+ options.transports,
9277
+ serviceId
9278
+ );
9279
+ this.serviceRegistry.isFrontend = isFrontend;
9280
+ this.serviceRegistry.useSocket = !!options.useSocket;
9281
+ this.serviceRegistry.retryCount = options.retryCount ?? 3;
9282
+ this.ensureTransportControllers(isFrontend);
9283
+ const resolvedBootstrapEndpoint = options.cadenzaDB?.connect ? resolveBootstrapEndpoint({
9284
+ runtime: isFrontend ? "browser" : "server",
9285
+ bootstrap: options.bootstrap,
9286
+ cadenzaDB: options.cadenzaDB
9287
+ }) : void 0;
9288
+ if (resolvedBootstrapEndpoint) {
9289
+ options.cadenzaDB = {
9290
+ ...options.cadenzaDB,
9291
+ connect: true,
9292
+ address: resolvedBootstrapEndpoint.address,
9293
+ port: resolvedBootstrapEndpoint.port
9294
+ };
9295
+ }
8517
9296
  if (options.cadenzaDB?.connect) {
8518
9297
  this.emit("meta.initializing_service", {
8519
9298
  // Seed the CadenzaDB
8520
9299
  serviceInstance: {
8521
9300
  uuid: "cadenza-db",
8522
9301
  serviceName: "CadenzaDB",
8523
- address: options.cadenzaDB?.address,
8524
- port: options.cadenzaDB?.port,
8525
- exposed: options.networkMode !== "dev",
8526
9302
  numberOfRunningGraphs: 0,
8527
9303
  isActive: true,
8528
9304
  // Assume it is deployed
8529
9305
  isNonResponsive: false,
8530
9306
  isBlocked: false,
8531
- health: {}
9307
+ health: {},
9308
+ isFrontend: false,
9309
+ transports: resolvedBootstrapEndpoint ? [
9310
+ this.createBootstrapTransport(
9311
+ "cadenza-db",
9312
+ isFrontend ? "public" : "internal",
9313
+ resolvedBootstrapEndpoint
9314
+ )
9315
+ ] : []
8532
9316
  }
8533
9317
  });
8534
9318
  }
8535
9319
  options.relatedServices?.forEach((service) => {
9320
+ const relatedTransport = normalizeServiceTransportConfig({
9321
+ role: isFrontend ? "public" : "internal",
9322
+ origin: service[2].includes("://") ? service[2] : `http://${service[2]}`,
9323
+ protocols: ["rest", "socket"]
9324
+ });
8536
9325
  this.emit("meta.initializing_service", {
8537
9326
  serviceInstance: {
8538
9327
  uuid: service[0],
8539
9328
  serviceName: service[1],
8540
- address: service[2].split(":")[0],
8541
- port: service[2].split(":")[1] ?? 3e3,
8542
- exposed: options.networkMode !== "dev",
8543
9329
  numberOfRunningGraphs: 0,
8544
9330
  isActive: true,
8545
9331
  // Assume it is deployed
8546
9332
  isNonResponsive: false,
8547
9333
  isBlocked: false,
8548
- health: {}
9334
+ health: {},
9335
+ isFrontend: false,
9336
+ transports: relatedTransport ? [
9337
+ {
9338
+ uuid: `${service[0]}-${relatedTransport.role}`,
9339
+ service_instance_id: service[0],
9340
+ role: relatedTransport.role,
9341
+ origin: relatedTransport.origin,
9342
+ protocols: relatedTransport.protocols ?? ["rest", "socket"],
9343
+ security_profile: relatedTransport.securityProfile ?? null,
9344
+ auth_strategy: relatedTransport.authStrategy ?? null
9345
+ }
9346
+ ] : []
8549
9347
  }
8550
9348
  });
8551
9349
  });
@@ -8566,7 +9364,9 @@ var CadenzaService = class {
8566
9364
  __networkMode: options.networkMode,
8567
9365
  __retryCount: options.retryCount,
8568
9366
  __cadenzaDBConnect: options.cadenzaDB?.connect,
8569
- __isDatabase: options.isDatabase
9367
+ __isDatabase: options.isDatabase,
9368
+ __isFrontend: isFrontend,
9369
+ __declaredTransports: declaredTransports
8570
9370
  };
8571
9371
  if (options.cadenzaDB?.connect) {
8572
9372
  this.createEphemeralMetaTask("Create service", async (context, emit) => {
@@ -8582,12 +9382,42 @@ var CadenzaService = class {
8582
9382
  }).doOn("meta.rest.handshake");
8583
9383
  }
8584
9384
  this.createMetaTask("Handle service setup completion", () => {
8585
- GraphMetadataController.instance;
8586
- GraphSyncController.instance.isCadenzaDBReady = !!options.cadenzaDB?.connect;
8587
- GraphSyncController.instance.init();
9385
+ if (isFrontend) {
9386
+ registerActorSessionPersistenceTasks();
9387
+ this.ensureFrontendSyncLoop();
9388
+ } else {
9389
+ GraphMetadataController.instance;
9390
+ GraphSyncController.instance.isCadenzaDBReady = !!options.cadenzaDB?.connect;
9391
+ GraphSyncController.instance.init();
9392
+ }
8588
9393
  this.log("Service created.");
8589
9394
  return true;
8590
9395
  }).doOn("meta.service_registry.instance_inserted");
9396
+ if (!options.cadenzaDB?.connect && isFrontend) {
9397
+ import_core4.default.schedule(
9398
+ "meta.service_registry.instance_registration_requested",
9399
+ {
9400
+ data: {
9401
+ uuid: serviceId,
9402
+ process_pid: 1,
9403
+ service_name: serviceName,
9404
+ is_frontend: true,
9405
+ is_active: true,
9406
+ is_non_responsive: false,
9407
+ is_blocked: false,
9408
+ health: {}
9409
+ },
9410
+ transportData: [],
9411
+ __serviceName: serviceName,
9412
+ __serviceInstanceId: serviceId,
9413
+ __useSocket: options.useSocket,
9414
+ __retryCount: options.retryCount,
9415
+ __isFrontend: true,
9416
+ __skipRemoteExecution: true
9417
+ },
9418
+ 0
9419
+ );
9420
+ }
8591
9421
  this.serviceCreated = true;
8592
9422
  }
8593
9423
  /**
@@ -8603,87 +9433,52 @@ var CadenzaService = class {
8603
9433
  this.createCadenzaService(serviceName, description, options);
8604
9434
  }
8605
9435
  /**
8606
- * Creates and initializes a PostgresActor-backed database service.
8607
- * This is the canonical API for schema-driven postgres setup in cadenza-service.
9436
+ * Creates and initializes a specialized PostgresActor.
9437
+ * This is actor-only and does not create or register a network service.
8608
9438
  *
8609
- * @param {string} name - Logical actor/service name.
9439
+ * @param {string} name - Logical PostgresActor name.
8610
9440
  * @param {DatabaseSchemaDefinition} schema - Database schema definition.
8611
- * @param {string} [description=""] - Optional human-readable description.
8612
- * @param {ServerOptions & DatabaseOptions} [options={}] - Server/database runtime options.
9441
+ * @param {string} [description=""] - Optional human-readable actor description.
9442
+ * @param {ServerOptions & DatabaseOptions} [options={}] - Actor/database runtime options.
8613
9443
  * @return {void}
8614
9444
  */
8615
9445
  static createPostgresActor(name, schema, description = "", options = {}) {
8616
- if (isBrowser) {
9446
+ if (isBrowser || options.isFrontend) {
8617
9447
  console.warn(
8618
- "Database service creation is not supported in the browser. Use the CadenzaDB service instead."
9448
+ "PostgresActor creation is not supported in frontend mode."
8619
9449
  );
8620
9450
  return;
8621
9451
  }
8622
- if (this.serviceCreated) return;
8623
9452
  this.bootstrap();
8624
- this.serviceRegistry.serviceName = name;
9453
+ this.validateName(name);
8625
9454
  const databaseController = DatabaseController.instance;
8626
- options = {
8627
- loadBalance: true,
8628
- useSocket: true,
8629
- displayName: void 0,
8630
- isMeta: false,
8631
- port: parseInt(process.env.HTTP_PORT ?? "3000"),
8632
- securityProfile: process.env.SECURITY_PROFILE ?? "medium",
8633
- networkMode: process.env.NETWORK_MODE ?? "dev",
8634
- retryCount: 3,
8635
- cadenzaDB: {
8636
- connect: true,
8637
- address: process.env.CADENZA_DB_ADDRESS ?? "localhost",
8638
- port: parseInt(process.env.CADENZA_DB_PORT ?? "5000")
8639
- },
8640
- databaseType: "postgres",
8641
- databaseName: (0, import_lodash_es2.snakeCase)(name),
8642
- poolSize: parseInt(process.env.DATABASE_POOL_SIZE ?? "10"),
8643
- isDatabase: true,
8644
- ...options
8645
- };
9455
+ const normalizedOptions = this.normalizePostgresActorOptions(name, options);
8646
9456
  const registration = databaseController.createPostgresActor(
8647
9457
  name,
8648
9458
  schema,
8649
- options
9459
+ description,
9460
+ normalizedOptions
8650
9461
  );
8651
9462
  console.log("Creating PostgresActor", {
8652
- serviceName: name,
9463
+ name,
8653
9464
  actorName: registration.actorName,
8654
- options
9465
+ ownerServiceName: normalizedOptions.ownerServiceName ?? null,
9466
+ options: normalizedOptions
8655
9467
  });
8656
- this.emit("meta.database_init_requested", {
8657
- schema,
8658
- databaseName: options.databaseName,
8659
- options
9468
+ databaseController.requestPostgresActorSetup(registration, {
9469
+ actorName: registration.actorName,
9470
+ actorToken: registration.actorToken,
9471
+ databaseName: normalizedOptions.databaseName,
9472
+ ownerServiceName: normalizedOptions.ownerServiceName ?? null
8660
9473
  });
8661
- this.createMetaTask("Set database connection", () => {
8662
- this.createMetaTask("Insert database service bridge", (_, emit) => {
8663
- emit("global.meta.created_database_service", {
8664
- data: {
8665
- service_name: name,
8666
- description,
8667
- schema,
8668
- is_meta: options.isMeta
8669
- }
8670
- });
8671
- this.log("Database service created", {
8672
- name,
8673
- isMeta: options.isMeta,
8674
- actorName: registration.actorName
8675
- });
8676
- }).doOn("meta.service_registry.service_inserted");
8677
- this.createCadenzaService(name, description, options);
8678
- }).doOn("meta.database.setup_done");
8679
9474
  }
8680
9475
  /**
8681
- * Creates a meta PostgresActor service.
9476
+ * Creates a meta PostgresActor.
8682
9477
  *
8683
- * @param {string} name - Logical actor/service name.
9478
+ * @param {string} name - Logical PostgresActor name.
8684
9479
  * @param {DatabaseSchemaDefinition} schema - Database schema definition.
8685
9480
  * @param {string} [description=""] - Optional description.
8686
- * @param {ServerOptions & DatabaseOptions} [options={}] - Optional server/database options.
9481
+ * @param {ServerOptions & DatabaseOptions} [options={}] - Optional actor/database options.
8687
9482
  * @return {void}
8688
9483
  */
8689
9484
  static createMetaPostgresActor(name, schema, description = "", options = {}) {
@@ -8692,10 +9487,84 @@ var CadenzaService = class {
8692
9487
  this.createPostgresActor(name, schema, description, options);
8693
9488
  }
8694
9489
  /**
8695
- * Legacy compatibility wrapper. Prefer {@link createPostgresActor}.
9490
+ * Creates a dedicated database service by composing a PostgresActor and a Cadenza service.
8696
9491
  */
8697
9492
  static createDatabaseService(name, schema, description = "", options = {}) {
8698
- this.createPostgresActor(name, schema, description, options);
9493
+ if (isBrowser || options.isFrontend) {
9494
+ console.warn(
9495
+ "Database service creation is not supported in frontend mode. Use the CadenzaDB service instead."
9496
+ );
9497
+ return;
9498
+ }
9499
+ if (this.serviceCreated) return;
9500
+ this.bootstrap();
9501
+ this.validateName(name);
9502
+ this.validateServiceName(name);
9503
+ const databaseController = DatabaseController.instance;
9504
+ const actorOptions = this.normalizePostgresActorOptions(name, {
9505
+ ...options,
9506
+ ownerServiceName: options.ownerServiceName ?? name
9507
+ });
9508
+ const serviceOptions = this.normalizeDatabaseServiceOptions(name, {
9509
+ ...options,
9510
+ ownerServiceName: actorOptions.ownerServiceName
9511
+ });
9512
+ const registration = databaseController.createPostgresActor(
9513
+ name,
9514
+ schema,
9515
+ description,
9516
+ actorOptions
9517
+ );
9518
+ this.registerDatabaseServiceBridgeTask(
9519
+ name,
9520
+ description,
9521
+ schema,
9522
+ Boolean(serviceOptions.isMeta),
9523
+ registration.actorName
9524
+ );
9525
+ const createServiceTaskName = `Create database service ${name} after ${registration.actorName} setup`;
9526
+ if (!this.get(createServiceTaskName)) {
9527
+ this.createMetaTask(
9528
+ createServiceTaskName,
9529
+ () => {
9530
+ this.createCadenzaService(name, description, serviceOptions);
9531
+ return true;
9532
+ },
9533
+ "Creates the networked database service after PostgresActor setup completes."
9534
+ ).doOn(registration.setupDoneSignal);
9535
+ }
9536
+ const setupFailureTaskName = `Handle database service ${name} bootstrap failure`;
9537
+ if (!this.get(setupFailureTaskName)) {
9538
+ this.createMetaTask(
9539
+ setupFailureTaskName,
9540
+ (ctx) => {
9541
+ this.log(
9542
+ "Database service bootstrap failed before service creation.",
9543
+ {
9544
+ serviceName: name,
9545
+ actorName: registration.actorName,
9546
+ databaseName: registration.databaseName,
9547
+ error: ctx.__error
9548
+ },
9549
+ "error"
9550
+ );
9551
+ return true;
9552
+ },
9553
+ "Logs PostgresActor setup failures for database service bootstrap."
9554
+ ).doOn(registration.setupFailedSignal);
9555
+ }
9556
+ console.log("Creating database service wrapper", {
9557
+ serviceName: name,
9558
+ actorName: registration.actorName,
9559
+ actorOptions,
9560
+ serviceOptions
9561
+ });
9562
+ databaseController.requestPostgresActorSetup(registration, {
9563
+ actorName: registration.actorName,
9564
+ actorToken: registration.actorToken,
9565
+ databaseName: actorOptions.databaseName,
9566
+ ownerServiceName: actorOptions.ownerServiceName ?? name
9567
+ });
8699
9568
  }
8700
9569
  /**
8701
9570
  * Creates a meta database service with the specified configuration.
@@ -8707,7 +9576,71 @@ var CadenzaService = class {
8707
9576
  * @return {void} - This method does not return a value.
8708
9577
  */
8709
9578
  static createMetaDatabaseService(name, schema, description = "", options = {}) {
8710
- this.createMetaPostgresActor(name, schema, description, options);
9579
+ this.createDatabaseService(name, schema, description, {
9580
+ ...options,
9581
+ isMeta: true
9582
+ });
9583
+ }
9584
+ static normalizePostgresActorOptions(name, options = {}) {
9585
+ return {
9586
+ isMeta: false,
9587
+ retryCount: 3,
9588
+ databaseType: "postgres",
9589
+ databaseName: (0, import_lodash_es2.snakeCase)(name),
9590
+ poolSize: readIntegerEnv("DATABASE_POOL_SIZE", 10),
9591
+ ownerServiceName: options.ownerServiceName ?? this.serviceRegistry?.serviceName ?? null,
9592
+ ...options
9593
+ };
9594
+ }
9595
+ static normalizeDatabaseServiceOptions(name, options = {}) {
9596
+ return {
9597
+ loadBalance: true,
9598
+ useSocket: true,
9599
+ displayName: void 0,
9600
+ isMeta: false,
9601
+ port: readIntegerEnv("HTTP_PORT", 3e3),
9602
+ securityProfile: readStringEnv("SECURITY_PROFILE") ?? "medium",
9603
+ networkMode: readStringEnv("NETWORK_MODE") ?? "dev",
9604
+ retryCount: 3,
9605
+ cadenzaDB: {
9606
+ connect: true
9607
+ },
9608
+ databaseType: "postgres",
9609
+ databaseName: (0, import_lodash_es2.snakeCase)(name),
9610
+ poolSize: readIntegerEnv("DATABASE_POOL_SIZE", 10),
9611
+ isDatabase: true,
9612
+ ownerServiceName: options.ownerServiceName ?? name,
9613
+ ...options
9614
+ };
9615
+ }
9616
+ static registerDatabaseServiceBridgeTask(serviceName, description, schema, isMeta, actorName) {
9617
+ const taskName = `Insert database service bridge ${serviceName}`;
9618
+ if (this.get(taskName)) {
9619
+ return;
9620
+ }
9621
+ this.createMetaTask(
9622
+ taskName,
9623
+ (ctx, emit) => {
9624
+ if (ctx.__serviceName && ctx.__serviceName !== serviceName) {
9625
+ return false;
9626
+ }
9627
+ emit("global.meta.created_database_service", {
9628
+ data: {
9629
+ service_name: serviceName,
9630
+ description,
9631
+ schema,
9632
+ is_meta: isMeta
9633
+ }
9634
+ });
9635
+ this.log("Database service created", {
9636
+ name: serviceName,
9637
+ isMeta,
9638
+ actorName
9639
+ });
9640
+ return true;
9641
+ },
9642
+ "Bridges database service creation into the global metadata signal."
9643
+ ).doOn("meta.service_registry.service_inserted");
8711
9644
  }
8712
9645
  static createActor(spec, options = {}) {
8713
9646
  this.bootstrap();
@@ -9060,14 +9993,14 @@ var CadenzaService = class {
9060
9993
  return import_core4.default.createEphemeralTask(name, func, description, options);
9061
9994
  }
9062
9995
  /**
9063
- * Creates an ephemeral meta task with the specified name, function, description, and options.
9996
+ * Creates an ephemeral meta-task with the specified name, function, description, and options.
9064
9997
  * See {@link createEphemeralTask} and {@link createMetaTask} for more details.
9065
9998
  *
9066
9999
  * @param {string} name - The name of the task to be created.
9067
10000
  * @param {TaskFunction} func - The function to be executed as part of the task.
9068
10001
  * @param {string} [description] - An optional description of the task.
9069
10002
  * @param {TaskOptions & EphemeralTaskOptions} [options={}] - Additional options for configuring the task.
9070
- * @return {EphemeralTask} The created ephemeral meta task.
10003
+ * @return {EphemeralTask} The created ephemeral meta-task.
9071
10004
  */
9072
10005
  static createEphemeralMetaTask(name, func, description, options = {}) {
9073
10006
  this.bootstrap();
@@ -9122,19 +10055,291 @@ var CadenzaService = class {
9122
10055
  }
9123
10056
  static reset() {
9124
10057
  import_core4.default.reset();
9125
- this.serviceRegistry.reset();
10058
+ this.serviceRegistry?.reset();
10059
+ this.isBootstrapped = false;
10060
+ this.serviceCreated = false;
10061
+ this.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
10062
+ this.hydratedInquiryResults = /* @__PURE__ */ new Map();
10063
+ this.frontendSyncScheduled = false;
9126
10064
  }
9127
10065
  };
9128
10066
  CadenzaService.isBootstrapped = false;
9129
10067
  CadenzaService.serviceCreated = false;
9130
10068
  CadenzaService.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
10069
+ CadenzaService.hydratedInquiryResults = /* @__PURE__ */ new Map();
10070
+ CadenzaService.frontendSyncScheduled = false;
9131
10071
 
9132
10072
  // src/index.ts
9133
10073
  var import_core5 = require("@cadenza.io/core");
10074
+
10075
+ // src/ssr/createSSRInquiryBridge.ts
10076
+ var import_uuid4 = require("uuid");
10077
+ function ensureFetch() {
10078
+ if (typeof globalThis.fetch !== "function") {
10079
+ throw new Error("SSR inquiry bridge requires global fetch support.");
10080
+ }
10081
+ return globalThis.fetch.bind(globalThis);
10082
+ }
10083
+ function normalizeArrayResponse(value, keys) {
10084
+ for (const key of keys) {
10085
+ if (Array.isArray(value?.[key])) {
10086
+ return value[key];
10087
+ }
10088
+ }
10089
+ if (Array.isArray(value?.rows)) {
10090
+ return value.rows;
10091
+ }
10092
+ if (Array.isArray(value?.data)) {
10093
+ return value.data;
10094
+ }
10095
+ return [];
10096
+ }
10097
+ function normalizeIntentMap(raw) {
10098
+ const intentName = String(raw.intentName ?? raw.intent_name ?? "").trim();
10099
+ const serviceName = String(raw.serviceName ?? raw.service_name ?? "").trim();
10100
+ const taskName = String(raw.taskName ?? raw.task_name ?? "").trim();
10101
+ const taskVersion = Math.max(
10102
+ 1,
10103
+ Math.trunc(Number(raw.taskVersion ?? raw.task_version ?? 1) || 1)
10104
+ );
10105
+ if (!intentName || !serviceName || !taskName) {
10106
+ return null;
10107
+ }
10108
+ return {
10109
+ intentName,
10110
+ serviceName,
10111
+ taskName,
10112
+ taskVersion,
10113
+ deleted: Boolean(raw.deleted)
10114
+ };
10115
+ }
10116
+ function buildInquiryMeta(inquiry, startedAt, statuses) {
10117
+ const counts = summarizeResponderStatuses(statuses);
10118
+ return {
10119
+ inquiry,
10120
+ isMetaInquiry: isMetaIntentName(inquiry),
10121
+ totalResponders: statuses.length,
10122
+ eligibleResponders: statuses.length,
10123
+ filteredOutResponders: 0,
10124
+ responded: counts.responded,
10125
+ failed: counts.failed,
10126
+ timedOut: counts.timedOut,
10127
+ pending: counts.pending,
10128
+ durationMs: Date.now() - startedAt,
10129
+ responders: statuses
10130
+ };
10131
+ }
10132
+ function createSSRInquiryBridge(options = {}) {
10133
+ const bootstrapEndpoint = resolveBootstrapEndpoint({
10134
+ runtime: "server",
10135
+ bootstrap: options.bootstrap,
10136
+ cadenzaDB: options.cadenzaDB
10137
+ });
10138
+ const fetchImplementation = ensureFetch();
10139
+ const initialInquiryResults = {};
10140
+ const postDelegation = async (targetUrl, remoteRoutineName, context, timeoutMs) => {
10141
+ const signal = AbortSignal.timeout(timeoutMs);
10142
+ const response = await fetchImplementation(`${targetUrl}/delegation`, {
10143
+ method: "POST",
10144
+ headers: {
10145
+ "Content-Type": "application/json"
10146
+ },
10147
+ body: JSON.stringify({
10148
+ ...context,
10149
+ __remoteRoutineName: remoteRoutineName,
10150
+ __metadata: {
10151
+ ...context.__metadata ?? {},
10152
+ __deputyExecId: (0, import_uuid4.v4)()
10153
+ }
10154
+ }),
10155
+ signal
10156
+ });
10157
+ return await response.json();
10158
+ };
10159
+ const queryTable = async (tableName, queryData, timeoutMs) => {
10160
+ const response = await postDelegation(
10161
+ bootstrapEndpoint.url,
10162
+ `Query ${tableName}`,
10163
+ { queryData },
10164
+ timeoutMs
10165
+ );
10166
+ return normalizeArrayResponse(response, [
10167
+ `${tableName}Rows`,
10168
+ `${tableName}s`,
10169
+ tableName,
10170
+ tableName.replace(/_([a-z])/g, (_match, char) => char.toUpperCase())
10171
+ ]);
10172
+ };
10173
+ return {
10174
+ async inquire(inquiry, context = {}, inquiryOptions = {}) {
10175
+ const startedAt = Date.now();
10176
+ const overallTimeoutMs = inquiryOptions.overallTimeoutMs ?? inquiryOptions.timeout ?? 3e4;
10177
+ const perResponderTimeoutMs = inquiryOptions.perResponderTimeoutMs ?? overallTimeoutMs;
10178
+ const intentMaps = (await queryTable(
10179
+ "intent_to_task_map",
10180
+ {
10181
+ filter: {
10182
+ intent_name: inquiry
10183
+ }
10184
+ },
10185
+ overallTimeoutMs
10186
+ )).map(normalizeIntentMap).filter(
10187
+ (item) => !!item && item.intentName === inquiry && !item.deleted
10188
+ );
10189
+ if (intentMaps.length === 0) {
10190
+ return {
10191
+ __inquiryMeta: buildInquiryMeta(inquiry, startedAt, [])
10192
+ };
10193
+ }
10194
+ const relevantServiceNames = Array.from(
10195
+ new Set(intentMaps.map((item) => item.serviceName))
10196
+ );
10197
+ const rawServiceInstances = await queryTable(
10198
+ "service_instance",
10199
+ {
10200
+ filter: {
10201
+ service_name: relevantServiceNames
10202
+ }
10203
+ },
10204
+ overallTimeoutMs
10205
+ );
10206
+ const rawServiceTransports = await queryTable(
10207
+ "service_instance_transport",
10208
+ {
10209
+ filter: {
10210
+ deleted: false
10211
+ }
10212
+ },
10213
+ overallTimeoutMs
10214
+ );
10215
+ const transportsByInstance = /* @__PURE__ */ new Map();
10216
+ for (const transport of rawServiceTransports) {
10217
+ const serviceInstanceId = String(
10218
+ transport.serviceInstanceId ?? transport.service_instance_id ?? ""
10219
+ ).trim();
10220
+ if (!serviceInstanceId) {
10221
+ continue;
10222
+ }
10223
+ if (!transportsByInstance.has(serviceInstanceId)) {
10224
+ transportsByInstance.set(serviceInstanceId, []);
10225
+ }
10226
+ transportsByInstance.get(serviceInstanceId).push(transport);
10227
+ }
10228
+ const serviceInstances = rawServiceInstances.map(
10229
+ (instance) => normalizeServiceInstanceDescriptor({
10230
+ ...instance,
10231
+ transports: transportsByInstance.get(String(instance.uuid ?? "").trim()) ?? []
10232
+ })
10233
+ ).filter(
10234
+ (item) => !!item && relevantServiceNames.includes(item.serviceName) && item.isActive && !item.isNonResponsive && !item.isBlocked
10235
+ ).sort((left, right) => {
10236
+ if (left.serviceName !== right.serviceName) {
10237
+ return left.serviceName.localeCompare(right.serviceName);
10238
+ }
10239
+ if (left.isPrimary !== right.isPrimary) {
10240
+ return left.isPrimary ? -1 : 1;
10241
+ }
10242
+ return (left.numberOfRunningGraphs ?? 0) - (right.numberOfRunningGraphs ?? 0);
10243
+ });
10244
+ const statuses = intentMaps.map((map) => ({
10245
+ isRemote: true,
10246
+ serviceName: map.serviceName,
10247
+ taskName: map.taskName,
10248
+ taskVersion: map.taskVersion,
10249
+ localTaskName: `SSR inquiry via ${map.serviceName} (${map.taskName} v${map.taskVersion})`,
10250
+ status: "timed_out",
10251
+ durationMs: 0
10252
+ }));
10253
+ const fulfilledContexts = await Promise.all(
10254
+ intentMaps.map(async (map, index) => {
10255
+ const status = statuses[index];
10256
+ const startedAtForResponder = Date.now();
10257
+ const selectedInstance = serviceInstances.find(
10258
+ (instance) => instance.serviceName === map.serviceName
10259
+ );
10260
+ if (!selectedInstance) {
10261
+ status.status = "failed";
10262
+ status.error = `No active instances for ${map.serviceName}`;
10263
+ status.durationMs = Date.now() - startedAtForResponder;
10264
+ return null;
10265
+ }
10266
+ const targetTransport = selectTransportForRole(
10267
+ selectedInstance.transports,
10268
+ "internal",
10269
+ "rest"
10270
+ );
10271
+ if (!targetTransport) {
10272
+ status.status = "failed";
10273
+ status.error = `No internal transport for ${selectedInstance.serviceName}/${selectedInstance.uuid}`;
10274
+ status.durationMs = Date.now() - startedAtForResponder;
10275
+ return null;
10276
+ }
10277
+ const targetUrl = targetTransport.origin;
10278
+ try {
10279
+ const result = await postDelegation(
10280
+ targetUrl,
10281
+ map.taskName,
10282
+ context,
10283
+ perResponderTimeoutMs
10284
+ );
10285
+ status.durationMs = Date.now() - startedAtForResponder;
10286
+ if (result?.errored || result?.failed) {
10287
+ status.status = "failed";
10288
+ status.error = String(
10289
+ result?.__error ?? result?.error ?? "Remote inquiry failed"
10290
+ );
10291
+ return null;
10292
+ }
10293
+ status.status = "fulfilled";
10294
+ return result;
10295
+ } catch (error) {
10296
+ status.durationMs = Date.now() - startedAtForResponder;
10297
+ if (error instanceof Error && (error.name === "AbortError" || /timed out/i.test(error.message))) {
10298
+ status.status = "timed_out";
10299
+ status.error = error.message;
10300
+ return null;
10301
+ }
10302
+ status.status = "failed";
10303
+ status.error = error instanceof Error ? error.message : String(error);
10304
+ return null;
10305
+ }
10306
+ })
10307
+ );
10308
+ const mergedContext = mergeInquiryContexts(
10309
+ fulfilledContexts.filter(
10310
+ (item) => item !== null
10311
+ )
10312
+ );
10313
+ const responseContext = {
10314
+ ...mergedContext,
10315
+ __inquiryMeta: buildInquiryMeta(inquiry, startedAt, statuses)
10316
+ };
10317
+ if (inquiryOptions.hydrationKey) {
10318
+ initialInquiryResults[inquiryOptions.hydrationKey] = responseContext;
10319
+ }
10320
+ if (inquiryOptions.requireComplete && statuses.some((status) => status.status !== "fulfilled")) {
10321
+ throw {
10322
+ ...responseContext,
10323
+ __error: `Inquiry '${inquiry}' did not complete successfully`,
10324
+ errored: true
10325
+ };
10326
+ }
10327
+ return responseContext;
10328
+ },
10329
+ dehydrate() {
10330
+ return {
10331
+ initialInquiryResults: { ...initialInquiryResults }
10332
+ };
10333
+ }
10334
+ };
10335
+ }
10336
+
10337
+ // src/index.ts
9134
10338
  var index_default = CadenzaService;
9135
10339
  // Annotate the CommonJS export names for ESM import in node:
9136
10340
  0 && (module.exports = {
9137
10341
  Actor,
10342
+ DatabaseController,
9138
10343
  DatabaseTask,
9139
10344
  DebounceTask,
9140
10345
  DeputyTask,
@@ -9146,6 +10351,7 @@ var index_default = CadenzaService;
9146
10351
  SignalController,
9147
10352
  SignalTransmissionTask,
9148
10353
  SocketController,
9149
- Task
10354
+ Task,
10355
+ createSSRInquiryBridge
9150
10356
  });
9151
10357
  //# sourceMappingURL=index.js.map