@cadenza.io/service 2.15.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.mjs CHANGED
@@ -38,7 +38,7 @@ var DeputyTask = class extends Task {
38
38
  return;
39
39
  }
40
40
  if (context.__metadata.__skipRemoteExecution) {
41
- resolve(true);
41
+ resolve(context);
42
42
  return;
43
43
  }
44
44
  const processId = uuid();
@@ -141,6 +141,8 @@ var DeputyTask = class extends Task {
141
141
  __executionTraceId: metadata.__executionTraceId ?? null,
142
142
  __metadata: {
143
143
  ...metadata,
144
+ __skipRemoteExecution: metadata.__skipRemoteExecution ?? ctx.__skipRemoteExecution ?? false,
145
+ __blockRemoteExecution: metadata.__blockRemoteExecution ?? ctx.__blockRemoteExecution ?? false,
144
146
  __deputyTaskName: this.name
145
147
  },
146
148
  ...ctx
@@ -219,6 +221,7 @@ var DatabaseTask = class extends DeputyTask {
219
221
  const dynamicQueryData = ctx.queryData ?? {};
220
222
  delete ctx.queryData;
221
223
  const deputyContext = {
224
+ ...ctx,
222
225
  __localTaskName: this.name,
223
226
  __localTaskVersion: this.version,
224
227
  __localServiceName: CadenzaService.serviceRegistry.serviceName,
@@ -229,6 +232,8 @@ var DatabaseTask = class extends DeputyTask {
229
232
  __localRoutineExecId: metadata.__routineExecId ?? metadata.__metadata?.__routineExecId,
230
233
  __metadata: {
231
234
  ...metadata,
235
+ __skipRemoteExecution: metadata.__skipRemoteExecution ?? ctx.__skipRemoteExecution ?? false,
236
+ __blockRemoteExecution: metadata.__blockRemoteExecution ?? ctx.__blockRemoteExecution ?? false,
232
237
  __deputyTaskName: this.name
233
238
  },
234
239
  queryData: {
@@ -315,6 +320,167 @@ function summarizeResponderStatuses(statuses) {
315
320
  return { responded, failed, timedOut, pending };
316
321
  }
317
322
 
323
+ // src/utils/transport.ts
324
+ var DEFAULT_PROTOCOLS = ["rest", "socket"];
325
+ function normalizeString(value) {
326
+ return typeof value === "string" ? value.trim() : "";
327
+ }
328
+ function normalizeTransportProtocols(value) {
329
+ const rawValues = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
330
+ const normalized = rawValues.map((entry) => normalizeString(entry)).filter(
331
+ (entry) => entry === "rest" || entry === "socket"
332
+ );
333
+ return Array.from(new Set(normalized));
334
+ }
335
+ function normalizeTransportOrigin(origin) {
336
+ const raw = normalizeString(origin);
337
+ if (!raw) {
338
+ return null;
339
+ }
340
+ let parsed;
341
+ try {
342
+ parsed = new URL(raw);
343
+ } catch {
344
+ return null;
345
+ }
346
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
347
+ return null;
348
+ }
349
+ if (parsed.pathname && parsed.pathname !== "/") {
350
+ return null;
351
+ }
352
+ if (parsed.search || parsed.hash) {
353
+ return null;
354
+ }
355
+ return parsed.origin;
356
+ }
357
+ function normalizeSecurityProfile(value) {
358
+ const normalized = normalizeString(value);
359
+ if (normalized === "low" || normalized === "medium" || normalized === "high") {
360
+ return normalized;
361
+ }
362
+ return null;
363
+ }
364
+ function normalizeServiceTransportConfig(value) {
365
+ const raw = value ?? {};
366
+ const role = normalizeString(raw.role);
367
+ const origin = normalizeTransportOrigin(raw.origin);
368
+ const protocols = normalizeTransportProtocols(raw.protocols);
369
+ if (!origin) {
370
+ return null;
371
+ }
372
+ if (role !== "internal" && role !== "public") {
373
+ return null;
374
+ }
375
+ return {
376
+ role,
377
+ origin,
378
+ protocols: protocols.length > 0 ? protocols : [...DEFAULT_PROTOCOLS],
379
+ securityProfile: normalizeSecurityProfile(raw.securityProfile),
380
+ authStrategy: normalizeString(raw.authStrategy) || null
381
+ };
382
+ }
383
+ function normalizeServiceTransportDescriptor(value) {
384
+ const raw = value ?? {};
385
+ const uuid5 = normalizeString(raw.uuid);
386
+ const serviceInstanceId = normalizeString(
387
+ raw.serviceInstanceId ?? raw.service_instance_id
388
+ );
389
+ const config = normalizeServiceTransportConfig(raw);
390
+ if (!uuid5 || !serviceInstanceId || !config) {
391
+ return null;
392
+ }
393
+ return {
394
+ uuid: uuid5,
395
+ serviceInstanceId,
396
+ role: config.role,
397
+ origin: config.origin,
398
+ protocols: config.protocols ?? [...DEFAULT_PROTOCOLS],
399
+ securityProfile: config.securityProfile ?? null,
400
+ authStrategy: config.authStrategy ?? null,
401
+ deleted: Boolean(raw.deleted),
402
+ clientCreated: Boolean(raw.clientCreated ?? raw.client_created ?? false)
403
+ };
404
+ }
405
+ function transportSupportsProtocol(transport, protocol) {
406
+ return !!transport && transport.protocols.includes(protocol);
407
+ }
408
+ function selectTransportForRole(transports, role, protocol) {
409
+ const filtered = transports.filter(
410
+ (transport) => !transport.deleted && transport.role === role && (!protocol || transportSupportsProtocol(transport, protocol))
411
+ );
412
+ return filtered[0];
413
+ }
414
+ function buildTransportClientKey(transport) {
415
+ return transport.uuid;
416
+ }
417
+ function parseTransportOrigin(origin) {
418
+ const normalized = normalizeTransportOrigin(origin);
419
+ if (!normalized) {
420
+ return null;
421
+ }
422
+ const parsed = new URL(normalized);
423
+ const protocol = parsed.protocol === "https:" ? "https" : "http";
424
+ const port = parsed.port ? Number(parsed.port) : protocol === "https" ? 443 : 80;
425
+ return {
426
+ protocol,
427
+ hostname: parsed.hostname,
428
+ port
429
+ };
430
+ }
431
+
432
+ // src/utils/serviceInstance.ts
433
+ function normalizeString2(value) {
434
+ return typeof value === "string" ? value.trim() : "";
435
+ }
436
+ function normalizeTransportArray(value, serviceInstanceId) {
437
+ if (!Array.isArray(value)) {
438
+ return [];
439
+ }
440
+ return value.map(
441
+ (entry) => normalizeServiceTransportDescriptor({
442
+ ...entry ?? {},
443
+ service_instance_id: entry?.service_instance_id ?? entry?.serviceInstanceId ?? serviceInstanceId
444
+ })
445
+ ).filter((transport) => !!transport).sort((left, right) => left.origin.localeCompare(right.origin));
446
+ }
447
+ function normalizeServiceInstanceDescriptor(value) {
448
+ const raw = value ?? {};
449
+ const uuid5 = normalizeString2(raw.uuid);
450
+ const serviceName = normalizeString2(raw.serviceName ?? raw.service_name);
451
+ if (!uuid5 || !serviceName) {
452
+ return null;
453
+ }
454
+ const transports = normalizeTransportArray(raw.transports, uuid5);
455
+ return {
456
+ uuid: uuid5,
457
+ serviceName,
458
+ numberOfRunningGraphs: Math.max(
459
+ 0,
460
+ Math.trunc(
461
+ Number(raw.numberOfRunningGraphs ?? raw.number_of_running_graphs ?? 0) || 0
462
+ )
463
+ ),
464
+ isPrimary: Boolean(raw.isPrimary ?? raw.is_primary ?? false),
465
+ isActive: Boolean(raw.isActive ?? raw.is_active ?? true),
466
+ isNonResponsive: Boolean(
467
+ raw.isNonResponsive ?? raw.is_non_responsive ?? false
468
+ ),
469
+ isBlocked: Boolean(raw.isBlocked ?? raw.is_blocked ?? false),
470
+ runtimeState: raw.runtimeState === "healthy" || raw.runtimeState === "degraded" || raw.runtimeState === "overloaded" || raw.runtimeState === "unavailable" ? raw.runtimeState : void 0,
471
+ acceptingWork: typeof raw.acceptingWork === "boolean" ? raw.acceptingWork : void 0,
472
+ reportedAt: typeof raw.reportedAt === "string" ? raw.reportedAt : typeof raw.reported_at === "string" ? raw.reported_at : void 0,
473
+ health: raw.health ?? {},
474
+ isFrontend: Boolean(raw.isFrontend ?? raw.is_frontend ?? false),
475
+ isDatabase: Boolean(raw.isDatabase ?? raw.is_database ?? false),
476
+ transports,
477
+ clientCreatedTransportIds: Array.isArray(raw.clientCreatedTransportIds) ? raw.clientCreatedTransportIds.map((entry) => normalizeString2(entry)).filter((entry) => entry.length > 0) : void 0
478
+ };
479
+ }
480
+ function getRouteableTransport(instance, role, protocol) {
481
+ return selectTransportForRole(instance.transports ?? [], role, protocol);
482
+ }
483
+
318
484
  // src/utils/readiness.ts
319
485
  function evaluateDependencyReadiness(input) {
320
486
  const missedHeartbeats = Math.max(
@@ -561,6 +727,7 @@ var ServiceRegistry = class _ServiceRegistry {
561
727
  this.numberOfRunningGraphs = 0;
562
728
  this.useSocket = false;
563
729
  this.retryCount = 3;
730
+ this.isFrontend = false;
564
731
  CadenzaService.defineIntent({
565
732
  name: META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT,
566
733
  description: "Gather transport diagnostics across all services and communication clients.",
@@ -708,57 +875,60 @@ var ServiceRegistry = class _ServiceRegistry {
708
875
  this.handleInstanceUpdateTask = CadenzaService.createMetaTask(
709
876
  "Handle Instance Update",
710
877
  (ctx, emit) => {
711
- const serviceInstance = ctx.serviceInstance ?? (ctx.__serviceInstanceId || ctx.serviceInstanceId ? {
712
- uuid: ctx.__serviceInstanceId ?? ctx.serviceInstanceId,
713
- serviceName: ctx.__serviceName ?? ctx.serviceName,
714
- address: ctx.serviceAddress ?? "",
715
- port: ctx.servicePort ?? 0,
716
- exposed: !!ctx.exposed,
717
- isFrontend: !!ctx.isFrontend,
718
- isActive: typeof ctx.isActive === "boolean" ? ctx.isActive : typeof ctx.__active === "boolean" ? ctx.__active : true,
719
- isNonResponsive: !!ctx.isNonResponsive,
720
- isBlocked: !!ctx.isBlocked,
721
- health: ctx.health ?? ctx.__health ?? {},
722
- numberOfRunningGraphs: ctx.numberOfRunningGraphs ?? ctx.__numberOfRunningGraphs ?? 0,
723
- isPrimary: false
724
- } : void 0);
725
- if (!serviceInstance?.uuid || !serviceInstance?.serviceName) {
878
+ const serviceInstance = normalizeServiceInstanceDescriptor(
879
+ ctx.serviceInstance ?? ctx.data ?? ctx.queryData?.data ?? (ctx.__serviceInstanceId || ctx.serviceInstanceId ? {
880
+ uuid: ctx.__serviceInstanceId ?? ctx.serviceInstanceId,
881
+ serviceName: ctx.__serviceName ?? ctx.serviceName,
882
+ isFrontend: !!ctx.isFrontend,
883
+ isActive: typeof ctx.isActive === "boolean" ? ctx.isActive : typeof ctx.__active === "boolean" ? ctx.__active : true,
884
+ isNonResponsive: !!ctx.isNonResponsive,
885
+ isBlocked: !!ctx.isBlocked,
886
+ health: ctx.health ?? ctx.__health ?? {},
887
+ numberOfRunningGraphs: ctx.numberOfRunningGraphs ?? ctx.__numberOfRunningGraphs ?? 0,
888
+ isPrimary: false,
889
+ transports: ctx.transports ?? []
890
+ } : void 0)
891
+ );
892
+ if (!serviceInstance) {
726
893
  return false;
727
894
  }
728
- const {
729
- uuid: uuid4,
730
- serviceName,
731
- address,
732
- port,
733
- exposed,
734
- isFrontend,
735
- deleted
736
- } = serviceInstance;
737
- if (uuid4 === this.serviceInstanceId) return;
895
+ const uuid5 = serviceInstance.uuid;
896
+ const serviceName = serviceInstance.serviceName;
897
+ const deleted = Boolean(
898
+ ctx.deleted ?? ctx.serviceInstance?.deleted ?? ctx.data?.deleted
899
+ );
900
+ if (uuid5 === this.serviceInstanceId) return;
738
901
  if (deleted) {
739
- const indexToDelete = this.instances.get(serviceName)?.findIndex((i) => i.uuid === uuid4) ?? -1;
740
- if (indexToDelete >= 0) {
902
+ const existingInstance = this.instances.get(serviceName)?.find((instance) => instance.uuid === uuid5);
903
+ const indexToDelete = this.instances.get(serviceName)?.findIndex((i) => i.uuid === uuid5) ?? -1;
904
+ if (indexToDelete >= 0 && existingInstance) {
741
905
  this.instances.get(serviceName)?.splice(indexToDelete, 1);
906
+ for (const transport of existingInstance.transports) {
907
+ const transportKey = buildTransportClientKey(transport);
908
+ emit(`meta.socket_shutdown_requested:${transportKey}`, {});
909
+ emit(`meta.fetch.destroy_requested:${transportKey}`, {});
910
+ }
742
911
  }
743
912
  if (this.instances.get(serviceName)?.length === 0) {
744
913
  this.instances.delete(serviceName);
745
- } else if (this.instances.get(serviceName)?.filter((i) => i.address === address && i.port === port).length === 0) {
746
- emit(`meta.socket_shutdown_requested:${address}_${port}`, {});
747
- emit(`meta.fetch.destroy_requested:${address}_${port}`, {});
748
914
  }
749
- this.unregisterDependee(uuid4, serviceName);
915
+ this.unregisterDependee(uuid5, serviceName);
750
916
  return;
751
917
  }
752
918
  if (!this.instances.has(serviceName))
753
919
  this.instances.set(serviceName, []);
754
920
  const instances = this.instances.get(serviceName);
755
- const existing = instances.find((i) => i.uuid === uuid4);
921
+ const existing = instances.find((i) => i.uuid === uuid5);
756
922
  if (existing) {
757
- Object.assign(existing, serviceInstance);
923
+ Object.assign(existing, {
924
+ ...serviceInstance,
925
+ transports: serviceInstance.transports.length > 0 ? serviceInstance.transports : existing.transports,
926
+ clientCreatedTransportIds: existing.clientCreatedTransportIds ?? []
927
+ });
758
928
  } else {
759
929
  instances.push(serviceInstance);
760
930
  }
761
- const trackedInstance = existing ?? instances.find((instance) => instance.uuid === uuid4);
931
+ const trackedInstance = existing ?? instances.find((instance) => instance.uuid === uuid5);
762
932
  if (trackedInstance) {
763
933
  const snapshot = this.resolveRuntimeStatusSnapshot(
764
934
  trackedInstance.numberOfRunningGraphs ?? 0,
@@ -773,31 +943,44 @@ var ServiceRegistry = class _ServiceRegistry {
773
943
  if (this.serviceName === serviceName) {
774
944
  return false;
775
945
  }
776
- if (!isFrontend && (this.deputies.has(serviceName) || this.remoteIntents.has(serviceName)) || this.remoteSignals.has(serviceName)) {
777
- const clientCreated = instances?.some(
778
- (i) => i.address === address && i.port === port && i.clientCreated && i.isActive
946
+ const trackedTransport = this.getRouteableTransport(
947
+ trackedInstance,
948
+ this.useSocket ? "socket" : "rest"
949
+ );
950
+ if (!serviceInstance.isFrontend && (this.deputies.has(serviceName) || this.remoteIntents.has(serviceName)) || this.remoteSignals.has(serviceName)) {
951
+ const communicationTypes = Array.from(
952
+ new Set(
953
+ this.deputies.get(serviceName)?.map((d) => d.communicationType) ?? []
954
+ )
779
955
  );
780
- if (!clientCreated) {
781
- const communicationTypes = Array.from(
782
- new Set(
783
- this.deputies.get(serviceName)?.map((d) => d.communicationType) ?? []
784
- )
956
+ if (!communicationTypes.includes("signal") && this.remoteSignals.has(serviceName)) {
957
+ communicationTypes.push("signal");
958
+ }
959
+ if (trackedTransport) {
960
+ const clientCreated = this.hasTransportClientCreated(
961
+ trackedInstance,
962
+ trackedTransport.uuid
785
963
  );
786
- if (!communicationTypes.includes("signal") && this.remoteSignals.has(serviceName)) {
787
- communicationTypes.push("signal");
964
+ if (!clientCreated) {
965
+ emit("meta.service_registry.dependee_registered", {
966
+ serviceName,
967
+ serviceInstanceId: uuid5,
968
+ serviceTransportId: trackedTransport.uuid,
969
+ serviceOrigin: trackedTransport.origin,
970
+ transportProtocols: trackedTransport.protocols,
971
+ communicationTypes
972
+ });
973
+ this.markTransportClientCreated(
974
+ trackedInstance,
975
+ trackedTransport.uuid
976
+ );
788
977
  }
789
- emit("meta.service_registry.dependee_registered", {
978
+ } else {
979
+ emit("meta.service_registry.routeable_transport_missing", {
790
980
  serviceName,
791
- serviceInstanceId: uuid4,
792
- serviceAddress: address,
793
- servicePort: port,
794
- protocol: exposed ? "https" : "http",
795
- communicationTypes
796
- });
797
- instances?.filter(
798
- (i) => i.address === address && i.port === port && i.isActive
799
- ).forEach((i) => {
800
- i.clientCreated = true;
981
+ serviceInstanceId: uuid5,
982
+ requiredRole: this.getRoutingTransportRole(),
983
+ isFrontend: this.isFrontend
801
984
  });
802
985
  }
803
986
  }
@@ -815,6 +998,81 @@ var ServiceRegistry = class _ServiceRegistry {
815
998
  "meta.socket_shutdown_requested",
816
999
  "meta.fetch.destroy_requested"
817
1000
  );
1001
+ this.handleTransportUpdateTask = CadenzaService.createMetaTask(
1002
+ "Handle Transport Update",
1003
+ (ctx, emit) => {
1004
+ const transport = normalizeServiceTransportDescriptor(
1005
+ ctx.serviceTransport ?? ctx.data ?? ctx.queryData?.data ?? ctx
1006
+ );
1007
+ if (!transport) {
1008
+ return false;
1009
+ }
1010
+ let ownerInstance;
1011
+ for (const instances of this.instances.values()) {
1012
+ ownerInstance = instances.find(
1013
+ (instance) => instance.uuid === transport.serviceInstanceId
1014
+ );
1015
+ if (ownerInstance) {
1016
+ break;
1017
+ }
1018
+ }
1019
+ if (!ownerInstance) {
1020
+ return false;
1021
+ }
1022
+ if (transport.deleted) {
1023
+ ownerInstance.transports = ownerInstance.transports.filter(
1024
+ (existingTransport2) => existingTransport2.uuid !== transport.uuid
1025
+ );
1026
+ const transportKey = buildTransportClientKey(transport);
1027
+ emit(`meta.socket_shutdown_requested:${transportKey}`, {});
1028
+ emit(`meta.fetch.destroy_requested:${transportKey}`, {});
1029
+ return true;
1030
+ }
1031
+ const existingTransport = this.getTransportById(ownerInstance, transport.uuid);
1032
+ if (existingTransport) {
1033
+ Object.assign(existingTransport, transport);
1034
+ } else {
1035
+ ownerInstance.transports.push(transport);
1036
+ }
1037
+ if (ownerInstance.uuid === this.serviceInstanceId) {
1038
+ return true;
1039
+ }
1040
+ const hasRemoteInterest = (!ownerInstance.isFrontend && (this.deputies.has(ownerInstance.serviceName) || this.remoteIntents.has(ownerInstance.serviceName)) || this.remoteSignals.has(ownerInstance.serviceName)) && transport.role === this.getRoutingTransportRole();
1041
+ if (!hasRemoteInterest) {
1042
+ return true;
1043
+ }
1044
+ if (!this.hasTransportClientCreated(ownerInstance, transport.uuid)) {
1045
+ const communicationTypes = Array.from(
1046
+ new Set(
1047
+ this.deputies.get(ownerInstance.serviceName)?.map((descriptor) => descriptor.communicationType) ?? []
1048
+ )
1049
+ );
1050
+ if (!communicationTypes.includes("signal") && this.remoteSignals.has(ownerInstance.serviceName)) {
1051
+ communicationTypes.push("signal");
1052
+ }
1053
+ emit("meta.service_registry.dependee_registered", {
1054
+ serviceName: ownerInstance.serviceName,
1055
+ serviceInstanceId: ownerInstance.uuid,
1056
+ serviceTransportId: transport.uuid,
1057
+ serviceOrigin: transport.origin,
1058
+ transportProtocols: transport.protocols,
1059
+ communicationTypes
1060
+ });
1061
+ this.markTransportClientCreated(ownerInstance, transport.uuid);
1062
+ }
1063
+ return true;
1064
+ },
1065
+ "Handles service transport updates independently from instance rows."
1066
+ ).doOn(
1067
+ "global.meta.service_instance_transport.inserted",
1068
+ "global.meta.service_instance_transport.updated",
1069
+ "meta.service_instance_transport.inserted",
1070
+ "meta.service_instance_transport.updated"
1071
+ ).attachSignal(
1072
+ "meta.service_registry.dependee_registered",
1073
+ "meta.socket_shutdown_requested",
1074
+ "meta.fetch.destroy_requested"
1075
+ );
818
1076
  CadenzaService.createMetaTask(
819
1077
  "Track dependee registration",
820
1078
  (ctx) => {
@@ -910,17 +1168,25 @@ var ServiceRegistry = class _ServiceRegistry {
910
1168
  this.handleServiceNotRespondingTask = CadenzaService.createMetaTask(
911
1169
  "Handle service not responding",
912
1170
  (ctx, emit) => {
913
- const { serviceName, serviceAddress, servicePort } = ctx;
1171
+ const { serviceName, serviceInstanceId, serviceTransportId } = ctx;
914
1172
  const serviceInstances = this.instances.get(serviceName);
915
- const instances = serviceInstances?.filter(
916
- (i) => i.address === serviceAddress && i.port === servicePort
917
- );
1173
+ const instances = serviceInstances?.filter((instance) => {
1174
+ if (serviceInstanceId && instance.uuid === serviceInstanceId) {
1175
+ return true;
1176
+ }
1177
+ if (serviceTransportId) {
1178
+ return instance.transports.some(
1179
+ (transport) => transport.uuid === serviceTransportId
1180
+ );
1181
+ }
1182
+ return false;
1183
+ });
918
1184
  CadenzaService.log(
919
1185
  "Service not responding.",
920
1186
  {
921
1187
  serviceName,
922
- serviceAddress,
923
- servicePort,
1188
+ serviceInstanceId,
1189
+ serviceTransportId,
924
1190
  instances
925
1191
  },
926
1192
  "warning",
@@ -961,7 +1227,7 @@ var ServiceRegistry = class _ServiceRegistry {
961
1227
  this.handleServiceHandshakeTask = CadenzaService.createMetaTask(
962
1228
  "Handle service handshake",
963
1229
  (ctx, emit) => {
964
- const { serviceName, serviceInstanceId, serviceAddress, servicePort } = ctx;
1230
+ const { serviceName, serviceInstanceId } = ctx;
965
1231
  const serviceInstances = this.instances.get(serviceName);
966
1232
  const instance = serviceInstances?.find(
967
1233
  (i) => i.uuid === serviceInstanceId
@@ -989,26 +1255,6 @@ var ServiceRegistry = class _ServiceRegistry {
989
1255
  uuid: instance.uuid
990
1256
  }
991
1257
  });
992
- const instancesToDelete = serviceInstances?.filter(
993
- (i) => i.uuid !== serviceInstanceId && i.address === serviceAddress && i.port === servicePort
994
- );
995
- for (const i of instancesToDelete ?? []) {
996
- const indexToDelete = this.instances.get(serviceName)?.indexOf(i) ?? -1;
997
- if (indexToDelete >= 0) {
998
- this.instances.get(serviceName)?.splice(indexToDelete, 1);
999
- }
1000
- this.unregisterDependee(i.uuid, serviceName);
1001
- emit("global.meta.service_registry.deleted", {
1002
- data: {
1003
- isActive: false,
1004
- isNonResponsive: false,
1005
- deleted: true
1006
- },
1007
- filter: {
1008
- uuid: i.uuid
1009
- }
1010
- });
1011
- }
1012
1258
  return true;
1013
1259
  },
1014
1260
  "Handles service handshake"
@@ -1027,16 +1273,13 @@ var ServiceRegistry = class _ServiceRegistry {
1027
1273
  return false;
1028
1274
  }
1029
1275
  let applied = this.applyRuntimeStatusReport(report);
1030
- if (!applied && report.serviceAddress && typeof report.servicePort === "number") {
1276
+ if (!applied && report.transportId && report.transportOrigin) {
1031
1277
  if (!this.instances.has(report.serviceName)) {
1032
1278
  this.instances.set(report.serviceName, []);
1033
1279
  }
1034
1280
  this.instances.get(report.serviceName).push({
1035
1281
  uuid: report.serviceInstanceId,
1036
1282
  serviceName: report.serviceName,
1037
- address: report.serviceAddress,
1038
- port: report.servicePort,
1039
- exposed: !!report.exposed,
1040
1283
  isFrontend: !!report.isFrontend,
1041
1284
  isActive: report.isActive,
1042
1285
  isNonResponsive: report.isNonResponsive,
@@ -1046,7 +1289,18 @@ var ServiceRegistry = class _ServiceRegistry {
1046
1289
  acceptingWork: report.acceptingWork,
1047
1290
  reportedAt: report.reportedAt,
1048
1291
  health: report.health ?? {},
1049
- isPrimary: false
1292
+ isPrimary: false,
1293
+ transports: [
1294
+ {
1295
+ uuid: report.transportId,
1296
+ serviceInstanceId: report.serviceInstanceId,
1297
+ role: this.getRoutingTransportRole(),
1298
+ origin: report.transportOrigin,
1299
+ protocols: report.transportProtocols && report.transportProtocols.length > 0 ? report.transportProtocols : ["rest", "socket"],
1300
+ securityProfile: null,
1301
+ authStrategy: null
1302
+ }
1303
+ ]
1050
1304
  });
1051
1305
  applied = true;
1052
1306
  }
@@ -1087,21 +1341,9 @@ var ServiceRegistry = class _ServiceRegistry {
1087
1341
  deleted: !!m.deleted
1088
1342
  })
1089
1343
  );
1090
- const serviceInstances = (inquiryResult.serviceInstances ?? []).filter(
1091
- (instance) => !instance.deleted && !!instance.isActive && !instance.isNonResponsive && !instance.isBlocked
1092
- ).map((instance) => ({
1093
- uuid: instance.uuid,
1094
- address: instance.address,
1095
- port: instance.port,
1096
- serviceName: instance.serviceName,
1097
- isActive: !!instance.isActive,
1098
- isNonResponsive: !!instance.isNonResponsive,
1099
- isBlocked: !!instance.isBlocked,
1100
- health: instance.health ?? {},
1101
- exposed: !!instance.exposed,
1102
- created: instance.created,
1103
- isFrontend: !!instance.isFrontend
1104
- }));
1344
+ const serviceInstances = (inquiryResult.serviceInstances ?? []).map((instance) => normalizeServiceInstanceDescriptor(instance)).filter(
1345
+ (instance) => !!instance && !!instance.isActive && !instance.isNonResponsive && !instance.isBlocked
1346
+ );
1105
1347
  return {
1106
1348
  ...ctx,
1107
1349
  signalToTaskMaps,
@@ -1188,7 +1430,19 @@ var ServiceRegistry = class _ServiceRegistry {
1188
1430
  const { __serviceName, __triedInstances, __retries, __broadcast } = context;
1189
1431
  let retries = __retries ?? 0;
1190
1432
  let triedInstances = __triedInstances ?? [];
1191
- const instances = this.instances.get(__serviceName)?.filter((i) => i.isActive && !i.isNonResponsive && !i.isBlocked).sort((a, b) => {
1433
+ const preferredRole = this.getRoutingTransportRole();
1434
+ const instances = this.instances.get(__serviceName)?.filter((instance) => {
1435
+ if (!instance.isActive || instance.isNonResponsive || instance.isBlocked) {
1436
+ return false;
1437
+ }
1438
+ return Boolean(
1439
+ this.getRouteableTransport(
1440
+ instance,
1441
+ this.useSocket ? "socket" : "rest",
1442
+ preferredRole
1443
+ ) ?? this.getRouteableTransport(instance, "rest", preferredRole)
1444
+ );
1445
+ }).sort((a, b) => {
1192
1446
  const leftStatus = this.resolveRuntimeStatusSnapshot(
1193
1447
  a.numberOfRunningGraphs ?? 0,
1194
1448
  a.isActive,
@@ -1209,9 +1463,7 @@ var ServiceRegistry = class _ServiceRegistry {
1209
1463
  });
1210
1464
  if (!instances || instances.length === 0 || retries > this.retryCount) {
1211
1465
  context.errored = true;
1212
- context.__error = `No active instances for ${__serviceName}. Retries: ${retries}. ${this.instances.get(
1213
- __serviceName
1214
- )}`;
1466
+ context.__error = this.isFrontend && preferredRole === "public" ? `No public transport available for ${__serviceName}.` : `No routeable ${preferredRole} transport available for ${__serviceName}. Retries: ${retries}.`;
1215
1467
  emit(
1216
1468
  `meta.service_registry.load_balance_failed:${context.__metadata.__deputyExecId}`,
1217
1469
  context
@@ -1220,10 +1472,21 @@ var ServiceRegistry = class _ServiceRegistry {
1220
1472
  }
1221
1473
  if (__broadcast || instances[0].isFrontend) {
1222
1474
  for (const instance of instances) {
1223
- const socketKey = instance.isFrontend ? instance.address : `${instance.address}_${instance.port}`;
1475
+ const selectedTransport2 = this.getRouteableTransport(instance, "socket", preferredRole) ?? this.getRouteableTransport(instance, "rest", preferredRole);
1476
+ if (!selectedTransport2) {
1477
+ continue;
1478
+ }
1479
+ const transportKey = buildTransportClientKey(selectedTransport2);
1224
1480
  emit(
1225
- `meta.service_registry.selected_instance_for_socket:${socketKey}`,
1226
- context
1481
+ `${this.useSocket && transportSupportsProtocol(selectedTransport2, "socket") ? "meta.service_registry.selected_instance_for_socket" : "meta.service_registry.selected_instance_for_fetch"}:${transportKey}`,
1482
+ {
1483
+ ...context,
1484
+ __instance: instance.uuid,
1485
+ __transportId: selectedTransport2.uuid,
1486
+ __transportOrigin: selectedTransport2.origin,
1487
+ __transportProtocols: selectedTransport2.protocols,
1488
+ __fetchId: transportKey
1489
+ }
1227
1490
  );
1228
1491
  }
1229
1492
  return context;
@@ -1246,12 +1509,25 @@ var ServiceRegistry = class _ServiceRegistry {
1246
1509
  if (retries > 0) {
1247
1510
  selected = instancesToTry[Math.floor(Math.random() * instancesToTry.length)];
1248
1511
  }
1512
+ const selectedTransport = this.getRouteableTransport(selected, "socket", preferredRole) ?? this.getRouteableTransport(selected, "rest", preferredRole);
1513
+ if (!selectedTransport) {
1514
+ context.errored = true;
1515
+ context.__error = `No routeable ${preferredRole} transport available for ${selected.serviceName}/${selected.uuid}.`;
1516
+ emit(
1517
+ `meta.service_registry.load_balance_failed:${context.__metadata.__deputyExecId}`,
1518
+ context
1519
+ );
1520
+ return context;
1521
+ }
1249
1522
  context.__instance = selected.uuid;
1250
- context.__fetchId = `${selected.address}_${selected.port}`;
1523
+ context.__transportId = selectedTransport.uuid;
1524
+ context.__transportOrigin = selectedTransport.origin;
1525
+ context.__transportProtocols = selectedTransport.protocols;
1526
+ context.__fetchId = buildTransportClientKey(selectedTransport);
1251
1527
  context.__triedInstances = triedInstances;
1252
1528
  context.__triedInstances.push(selected.uuid);
1253
1529
  context.__retries = retries;
1254
- if (this.useSocket) {
1530
+ if (this.useSocket && transportSupportsProtocol(selectedTransport, "socket")) {
1255
1531
  emit(
1256
1532
  `meta.service_registry.selected_instance_for_socket:${context.__fetchId}`,
1257
1533
  context
@@ -1443,9 +1719,9 @@ var ServiceRegistry = class _ServiceRegistry {
1443
1719
  __active: report.isActive,
1444
1720
  serviceName: report.serviceName,
1445
1721
  serviceInstanceId: report.serviceInstanceId,
1446
- serviceAddress: report.serviceAddress,
1447
- servicePort: report.servicePort,
1448
- exposed: report.exposed,
1722
+ transportId: report.transportId,
1723
+ transportOrigin: report.transportOrigin,
1724
+ transportProtocols: report.transportProtocols,
1449
1725
  isFrontend: report.isFrontend,
1450
1726
  reportedAt: report.reportedAt,
1451
1727
  numberOfRunningGraphs: report.numberOfRunningGraphs,
@@ -1488,12 +1764,17 @@ var ServiceRegistry = class _ServiceRegistry {
1488
1764
  continue;
1489
1765
  }
1490
1766
  this.runtimeStatusFallbackInFlightByInstance.add(serviceInstanceId);
1767
+ const transport = this.getRouteableTransport(
1768
+ instance,
1769
+ this.useSocket ? "socket" : "rest"
1770
+ );
1491
1771
  emit("meta.service_registry.runtime_status_fallback_requested", {
1492
1772
  ...ctx,
1493
1773
  serviceName,
1494
1774
  serviceInstanceId,
1495
- serviceAddress: instance.address,
1496
- servicePort: instance.port
1775
+ serviceTransportId: transport?.uuid,
1776
+ serviceOrigin: transport?.origin,
1777
+ transportProtocols: transport?.protocols
1497
1778
  });
1498
1779
  }
1499
1780
  }
@@ -1527,6 +1808,10 @@ var ServiceRegistry = class _ServiceRegistry {
1527
1808
  };
1528
1809
  } catch (error) {
1529
1810
  const instance = this.getInstance(serviceName, serviceInstanceId);
1811
+ const transport = instance ? this.getRouteableTransport(
1812
+ instance,
1813
+ this.useSocket ? "socket" : "rest"
1814
+ ) : void 0;
1530
1815
  const message = error instanceof Error ? error.message : String(error);
1531
1816
  CadenzaService.log(
1532
1817
  "Runtime status fallback inquiry failed.",
@@ -1541,8 +1826,9 @@ var ServiceRegistry = class _ServiceRegistry {
1541
1826
  ...ctx,
1542
1827
  serviceName,
1543
1828
  serviceInstanceId,
1544
- serviceAddress: instance?.address ?? ctx.serviceAddress,
1545
- servicePort: instance?.port ?? ctx.servicePort,
1829
+ serviceTransportId: transport?.uuid ?? ctx.serviceTransportId,
1830
+ serviceOrigin: transport?.origin ?? ctx.serviceOrigin,
1831
+ transportProtocols: transport?.protocols ?? ctx.transportProtocols,
1546
1832
  __error: message,
1547
1833
  errored: true
1548
1834
  });
@@ -1655,85 +1941,185 @@ var ServiceRegistry = class _ServiceRegistry {
1655
1941
  inputSchema: {
1656
1942
  type: "object",
1657
1943
  properties: {
1658
- uuid: {
1659
- type: "string"
1660
- },
1661
- address: {
1662
- type: "string"
1663
- },
1664
- port: {
1665
- type: "number"
1666
- },
1667
- process_pid: {
1668
- type: "number"
1669
- },
1670
- is_primary: {
1671
- type: "boolean"
1672
- },
1673
- service_name: {
1674
- type: "string"
1675
- },
1676
- is_active: {
1677
- type: "boolean"
1678
- },
1679
- is_non_responsive: {
1680
- type: "boolean"
1681
- },
1682
- is_blocked: {
1683
- type: "boolean"
1684
- },
1685
- exposed: {
1686
- type: "boolean"
1944
+ data: {
1945
+ type: "object",
1946
+ properties: {
1947
+ uuid: {
1948
+ type: "string"
1949
+ },
1950
+ process_pid: {
1951
+ type: "number"
1952
+ },
1953
+ is_primary: {
1954
+ type: "boolean"
1955
+ },
1956
+ service_name: {
1957
+ type: "string"
1958
+ },
1959
+ is_active: {
1960
+ type: "boolean"
1961
+ },
1962
+ is_frontend: {
1963
+ type: "boolean"
1964
+ },
1965
+ is_database: {
1966
+ type: "boolean"
1967
+ },
1968
+ is_non_responsive: {
1969
+ type: "boolean"
1970
+ },
1971
+ is_blocked: {
1972
+ type: "boolean"
1973
+ },
1974
+ health: {
1975
+ type: "object"
1976
+ }
1977
+ },
1978
+ required: ["uuid", "process_pid", "service_name"]
1687
1979
  }
1688
1980
  },
1689
- required: [
1690
- "id",
1691
- "address",
1692
- "port",
1693
- "process_pid",
1694
- "service_name",
1695
- "exposed"
1696
- ]
1981
+ required: ["data"]
1697
1982
  },
1698
- // validateInputContext: true,
1699
1983
  outputSchema: {
1700
1984
  type: "object",
1701
1985
  properties: {
1702
- id: {
1986
+ uuid: {
1703
1987
  type: "string"
1704
1988
  }
1705
1989
  },
1706
- required: ["id"]
1990
+ required: ["uuid"]
1707
1991
  },
1708
- // validateOutputContext: true,
1709
1992
  retryCount: 5,
1710
1993
  retryDelay: 1e3
1711
1994
  }
1712
- ).doOn("global.meta.rest.network_configured", "meta.rest.browser_detected").then(
1995
+ ).doOn("meta.service_registry.instance_registration_requested").then(
1713
1996
  CadenzaService.createMetaTask(
1714
1997
  "Setup service",
1715
1998
  (ctx) => {
1716
- const { serviceInstance, data, __useSocket, __retryCount } = ctx;
1717
- this.serviceInstanceId = serviceInstance?.uuid ?? data?.uuid;
1999
+ const {
2000
+ serviceInstance,
2001
+ data,
2002
+ queryData,
2003
+ __useSocket,
2004
+ __retryCount,
2005
+ __isFrontend
2006
+ } = ctx;
2007
+ const normalizedLocalInstance = normalizeServiceInstanceDescriptor({
2008
+ ...serviceInstance ?? data ?? queryData?.data ?? {},
2009
+ transports: ctx.transportData ?? []
2010
+ });
2011
+ if (!normalizedLocalInstance?.uuid || !normalizedLocalInstance.serviceName) {
2012
+ return false;
2013
+ }
2014
+ this.serviceInstanceId = normalizedLocalInstance.uuid;
1718
2015
  this.instances.set(
1719
- data?.service_name ?? serviceInstance?.service_name,
1720
- [{ ...serviceInstance ?? data }]
2016
+ normalizedLocalInstance.serviceName,
2017
+ [{ ...normalizedLocalInstance }]
1721
2018
  );
1722
2019
  this.useSocket = __useSocket;
1723
2020
  this.retryCount = __retryCount;
2021
+ this.isFrontend = typeof __isFrontend === "boolean" ? __isFrontend : !!normalizedLocalInstance.isFrontend;
1724
2022
  console.log("SETUP SERVICE", this.serviceInstanceId);
1725
2023
  return true;
1726
2024
  },
1727
2025
  "Sets service instance id after insertion"
1728
- ).emits("meta.service_registry.instance_inserted")
2026
+ ).emits("meta.service_registry.instance_inserted").then(
2027
+ CadenzaService.createMetaTask(
2028
+ "Prepare service transport inserts",
2029
+ function* (ctx, emit) {
2030
+ const transportData = Array.isArray(ctx.transportData) ? ctx.transportData : [];
2031
+ for (const transport of transportData) {
2032
+ const transportContext = {
2033
+ ...ctx,
2034
+ data: {
2035
+ ...transport,
2036
+ service_instance_id: transport.service_instance_id ?? ctx.__serviceInstanceId
2037
+ }
2038
+ };
2039
+ emit(
2040
+ "meta.service_registry.transport_registration_requested",
2041
+ transportContext
2042
+ );
2043
+ yield transportContext;
2044
+ }
2045
+ },
2046
+ "Splits declared service transports into individual insert payloads."
2047
+ ).attachSignal("meta.service_registry.transport_registration_requested")
2048
+ )
1729
2049
  );
2050
+ this.insertServiceTransportTask = CadenzaService.createCadenzaDBInsertTask(
2051
+ "service_instance_transport",
2052
+ {
2053
+ onConflict: {
2054
+ target: ["service_instance_id", "role", "origin"],
2055
+ action: {
2056
+ do: "update",
2057
+ set: {
2058
+ protocols: "excluded",
2059
+ security_profile: "excluded",
2060
+ auth_strategy: "excluded",
2061
+ deleted: "false"
2062
+ }
2063
+ }
2064
+ }
2065
+ },
2066
+ {
2067
+ inputSchema: {
2068
+ type: "object",
2069
+ properties: {
2070
+ data: {
2071
+ type: "object",
2072
+ properties: {
2073
+ uuid: {
2074
+ type: "string"
2075
+ },
2076
+ service_instance_id: {
2077
+ type: "string"
2078
+ },
2079
+ role: {
2080
+ type: "string"
2081
+ },
2082
+ origin: {
2083
+ type: "string"
2084
+ },
2085
+ protocols: {
2086
+ type: "array",
2087
+ items: {
2088
+ type: "string"
2089
+ }
2090
+ },
2091
+ security_profile: {
2092
+ type: "string"
2093
+ },
2094
+ auth_strategy: {
2095
+ type: "string"
2096
+ }
2097
+ },
2098
+ required: ["uuid", "service_instance_id", "role", "origin"]
2099
+ }
2100
+ },
2101
+ required: ["data"]
2102
+ },
2103
+ outputSchema: {
2104
+ type: "object",
2105
+ properties: {
2106
+ uuid: {
2107
+ type: "string"
2108
+ }
2109
+ },
2110
+ required: ["uuid"]
2111
+ },
2112
+ retryCount: 5,
2113
+ retryDelay: 1e3
2114
+ }
2115
+ ).doOn("meta.service_registry.transport_registration_requested").emits("meta.service_registry.transport_registered").emitsOnFail("meta.service_registry.transport_registration_failed");
1730
2116
  CadenzaService.createMetaTask(
1731
2117
  "Handle service creation",
1732
2118
  (ctx) => {
1733
2119
  if (!ctx.__cadenzaDBConnect) {
1734
2120
  ctx.__skipRemoteExecution = true;
1735
2121
  }
1736
- if (isBrowser) {
2122
+ if (isBrowser || ctx.__isFrontend) {
1737
2123
  CadenzaService.createMetaTask("Prepare for signal sync", () => {
1738
2124
  return {};
1739
2125
  }).then(
@@ -1766,28 +2152,24 @@ var ServiceRegistry = class _ServiceRegistry {
1766
2152
  for (const service of services) {
1767
2153
  const instances = this.instances.get(service).filter((i) => i.isActive);
1768
2154
  for (const instance of instances) {
1769
- if (instance.clientCreated) continue;
1770
- const address = instance.address;
1771
- const port = instance.port;
1772
- const clientCreated = instances?.some(
1773
- (i) => i.address === address && i.port === port && i.clientCreated && i.isActive
2155
+ const transport = this.getRouteableTransport(
2156
+ instance,
2157
+ this.useSocket ? "socket" : "rest"
1774
2158
  );
1775
- if (!clientCreated) {
2159
+ if (!transport) {
2160
+ continue;
2161
+ }
2162
+ if (!this.hasTransportClientCreated(instance, transport.uuid)) {
1776
2163
  emit("meta.service_registry.dependee_registered", {
1777
2164
  serviceName: service,
1778
2165
  serviceInstanceId: instance.uuid,
1779
- serviceAddress: address,
1780
- servicePort: port,
1781
- protocol: instance.exposed ? "https" : "http",
2166
+ serviceTransportId: transport.uuid,
2167
+ serviceOrigin: transport.origin,
2168
+ transportProtocols: transport.protocols,
1782
2169
  communicationTypes: ["signal"]
1783
2170
  });
2171
+ this.markTransportClientCreated(instance, transport.uuid);
1784
2172
  }
1785
- instance.clientCreated = true;
1786
- instances.forEach((i) => {
1787
- if (i.address === address && i.port === port) {
1788
- i.clientCreated = true;
1789
- }
1790
- });
1791
2173
  }
1792
2174
  }
1793
2175
  return {};
@@ -1926,6 +2308,33 @@ var ServiceRegistry = class _ServiceRegistry {
1926
2308
  }
1927
2309
  return this.getInstance(this.serviceName, this.serviceInstanceId);
1928
2310
  }
2311
+ getRoutingTransportRole() {
2312
+ return this.isFrontend ? "public" : "internal";
2313
+ }
2314
+ getTransportById(instance, transportId) {
2315
+ return instance.transports.find((transport) => transport.uuid === transportId);
2316
+ }
2317
+ getRouteableTransport(instance, protocol, role = this.getRoutingTransportRole()) {
2318
+ return getRouteableTransport(instance, role, protocol);
2319
+ }
2320
+ getTransportClientKey(instance, protocol, role = this.getRoutingTransportRole()) {
2321
+ const transport = this.getRouteableTransport(instance, protocol, role);
2322
+ if (!transport) {
2323
+ return null;
2324
+ }
2325
+ return buildTransportClientKey(transport);
2326
+ }
2327
+ hasTransportClientCreated(instance, transportId) {
2328
+ return (instance.clientCreatedTransportIds ?? []).includes(transportId);
2329
+ }
2330
+ markTransportClientCreated(instance, transportId) {
2331
+ if (!instance.clientCreatedTransportIds) {
2332
+ instance.clientCreatedTransportIds = [];
2333
+ }
2334
+ if (!instance.clientCreatedTransportIds.includes(transportId)) {
2335
+ instance.clientCreatedTransportIds.push(transportId);
2336
+ }
2337
+ }
1929
2338
  registerDependee(serviceName, serviceInstanceId, options = {}) {
1930
2339
  if (!serviceName || !serviceInstanceId) {
1931
2340
  return;
@@ -2003,7 +2412,13 @@ var ServiceRegistry = class _ServiceRegistry {
2003
2412
  if (!serviceName || !serviceInstanceId) {
2004
2413
  return null;
2005
2414
  }
2006
- const servicePort = ctx.servicePort ?? ctx.port ?? ctx.serviceInstance?.port;
2415
+ const transportId = ctx.transportId ?? ctx.serviceTransportId ?? ctx.serviceTransport?.uuid ?? void 0;
2416
+ const transportOrigin = ctx.transportOrigin ?? ctx.serviceOrigin ?? ctx.serviceTransport?.origin ?? void 0;
2417
+ const transportProtocols = Array.isArray(
2418
+ ctx.transportProtocols ?? ctx.serviceTransport?.protocols
2419
+ ) ? (ctx.transportProtocols ?? ctx.serviceTransport?.protocols).map((entry) => String(entry)).filter(
2420
+ (entry) => entry === "rest" || entry === "socket"
2421
+ ) : void 0;
2007
2422
  const numberOfRunningGraphs = Math.max(
2008
2423
  0,
2009
2424
  Math.trunc(
@@ -2022,9 +2437,9 @@ var ServiceRegistry = class _ServiceRegistry {
2022
2437
  return {
2023
2438
  serviceName,
2024
2439
  serviceInstanceId,
2025
- serviceAddress: ctx.serviceAddress ?? ctx.address ?? ctx.serviceInstance?.address,
2026
- servicePort: typeof servicePort === "number" ? servicePort : void 0,
2027
- exposed: typeof ctx.exposed === "boolean" ? ctx.exposed : typeof ctx.serviceInstance?.exposed === "boolean" ? ctx.serviceInstance.exposed : void 0,
2440
+ transportId: typeof transportId === "string" && transportId.trim().length > 0 ? transportId : void 0,
2441
+ transportOrigin: typeof transportOrigin === "string" && transportOrigin.trim().length > 0 ? transportOrigin : void 0,
2442
+ transportProtocols: transportProtocols && transportProtocols.length > 0 ? Array.from(new Set(transportProtocols)) : void 0,
2028
2443
  isFrontend: typeof ctx.isFrontend === "boolean" ? ctx.isFrontend : typeof ctx.serviceInstance?.isFrontend === "boolean" ? ctx.serviceInstance.isFrontend : void 0,
2029
2444
  reportedAt: ctx.reportedAt ?? (typeof ctx.__reportedAt === "string" ? ctx.__reportedAt : void 0) ?? (/* @__PURE__ */ new Date()).toISOString(),
2030
2445
  state: ctx.state === "healthy" || ctx.state === "degraded" || ctx.state === "overloaded" || ctx.state === "unavailable" ? ctx.state : resolved.state,
@@ -2041,14 +2456,23 @@ var ServiceRegistry = class _ServiceRegistry {
2041
2456
  if (!instance) {
2042
2457
  return false;
2043
2458
  }
2044
- if (report.serviceAddress) {
2045
- instance.address = report.serviceAddress;
2046
- }
2047
- if (typeof report.servicePort === "number") {
2048
- instance.port = report.servicePort;
2049
- }
2050
- if (typeof report.exposed === "boolean") {
2051
- instance.exposed = report.exposed;
2459
+ if (report.transportId && report.transportOrigin) {
2460
+ const protocols = report.transportProtocols && report.transportProtocols.length > 0 ? report.transportProtocols : ["rest", "socket"];
2461
+ const existingTransport = this.getTransportById(instance, report.transportId);
2462
+ if (existingTransport) {
2463
+ existingTransport.origin = report.transportOrigin;
2464
+ existingTransport.protocols = protocols;
2465
+ } else {
2466
+ instance.transports.push({
2467
+ uuid: report.transportId,
2468
+ serviceInstanceId: report.serviceInstanceId,
2469
+ role: this.getRoutingTransportRole(),
2470
+ origin: report.transportOrigin,
2471
+ protocols,
2472
+ securityProfile: null,
2473
+ authStrategy: null
2474
+ });
2475
+ }
2052
2476
  }
2053
2477
  if (typeof report.isFrontend === "boolean") {
2054
2478
  instance.isFrontend = report.isFrontend;
@@ -2091,9 +2515,21 @@ var ServiceRegistry = class _ServiceRegistry {
2091
2515
  const report = {
2092
2516
  serviceName: this.serviceName,
2093
2517
  serviceInstanceId: this.serviceInstanceId,
2094
- serviceAddress: localInstance.address,
2095
- servicePort: localInstance.port,
2096
- exposed: localInstance.exposed,
2518
+ transportId: this.getRouteableTransport(
2519
+ localInstance,
2520
+ this.useSocket ? "socket" : "rest",
2521
+ "internal"
2522
+ )?.uuid ?? void 0,
2523
+ transportOrigin: this.getRouteableTransport(
2524
+ localInstance,
2525
+ this.useSocket ? "socket" : "rest",
2526
+ "internal"
2527
+ )?.origin ?? void 0,
2528
+ transportProtocols: this.getRouteableTransport(
2529
+ localInstance,
2530
+ this.useSocket ? "socket" : "rest",
2531
+ "internal"
2532
+ )?.protocols ?? void 0,
2097
2533
  isFrontend: localInstance.isFrontend,
2098
2534
  reportedAt,
2099
2535
  state: snapshot.state,
@@ -2308,6 +2744,7 @@ var ServiceRegistry = class _ServiceRegistry {
2308
2744
  this.numberOfRunningGraphs = 0;
2309
2745
  this.runtimeStatusHeartbeatStarted = false;
2310
2746
  this.lastRuntimeStatusSnapshot = null;
2747
+ this.isFrontend = false;
2311
2748
  }
2312
2749
  };
2313
2750
 
@@ -2479,13 +2916,10 @@ var RestController = class _RestController {
2479
2916
  CadenzaService.createMetaTask(
2480
2917
  "Setup Express app security",
2481
2918
  (ctx, emit) => {
2482
- if (isBrowser) {
2483
- emit("meta.rest.browser_detected", {
2919
+ if (isBrowser || ctx.__isFrontend) {
2920
+ emit("meta.service_registry.instance_registration_requested", {
2484
2921
  data: {
2485
2922
  uuid: ctx.__serviceInstanceId,
2486
- address: `browser:${ctx.__serviceInstanceId}`,
2487
- port: 0,
2488
- exposed: false,
2489
2923
  process_pid: 1,
2490
2924
  service_name: ctx.__serviceName,
2491
2925
  is_frontend: true,
@@ -2494,6 +2928,7 @@ var RestController = class _RestController {
2494
2928
  is_blocked: false,
2495
2929
  health: {}
2496
2930
  },
2931
+ transportData: [],
2497
2932
  ...ctx
2498
2933
  });
2499
2934
  return;
@@ -2566,7 +3001,7 @@ var RestController = class _RestController {
2566
3001
  return { ...ctx, __app: app };
2567
3002
  },
2568
3003
  "Sets up the Express server according to the security profile"
2569
- ).attachSignal("meta.rest.browser_detected").then(
3004
+ ).attachSignal("meta.service_registry.instance_registration_requested").then(
2570
3005
  CadenzaService.createMetaTask(
2571
3006
  "Define RestServer",
2572
3007
  (ctx) => {
@@ -2686,28 +3121,32 @@ var RestController = class _RestController {
2686
3121
  CadenzaService.createMetaTask(
2687
3122
  "Configure network",
2688
3123
  async (ctx) => {
2689
- let address = "undefined";
2690
- let port = ctx.__port;
2691
- let exposed = false;
3124
+ let httpOrigin = null;
3125
+ let httpsOrigin = null;
3126
+ const resolveBoundAddress = (server) => {
3127
+ if (typeof server?.address() === "string") {
3128
+ return server.address();
3129
+ }
3130
+ if (server?.address()?.address === "::") {
3131
+ if (process.env.NODE_ENV === "development") {
3132
+ return "localhost";
3133
+ }
3134
+ if (process.env.IS_DOCKER === "true") {
3135
+ return process.env.CADENZA_SERVER_URL || "localhost";
3136
+ }
3137
+ }
3138
+ return server?.address()?.address || "localhost";
3139
+ };
2692
3140
  const createHttpServer = async (ctx2) => {
2693
3141
  await new Promise((resolve) => {
2694
3142
  const server = http.createServer(ctx2.__app);
2695
3143
  ctx2.httpServer = server;
2696
3144
  server.listen(ctx2.__port, () => {
2697
- if (typeof server?.address() === "string") {
2698
- address = server.address();
2699
- } else if (server?.address()?.address === "::") {
2700
- if (process.env.NODE_ENV === "development") {
2701
- address = "localhost";
2702
- } else if (process.env.IS_DOCKER === "true") {
2703
- address = process.env.CADENZA_SERVER_URL || "localhost";
2704
- }
2705
- } else {
2706
- address = server?.address()?.address || "undefined";
2707
- }
2708
- console.log(
2709
- `Server is running on ${address}:${port}`
2710
- );
3145
+ const addressInfo = server.address();
3146
+ const address = resolveBoundAddress(server);
3147
+ const port = typeof addressInfo === "object" && addressInfo ? addressInfo.port || ctx2.__port : ctx2.__port;
3148
+ httpOrigin = `http://${address}:${port}`;
3149
+ console.log(`Server is running on ${httpOrigin}`);
2711
3150
  resolve(address);
2712
3151
  });
2713
3152
  CadenzaService.createMetaTask(
@@ -2733,22 +3172,12 @@ var RestController = class _RestController {
2733
3172
  ctx2.__app
2734
3173
  );
2735
3174
  ctx2.httpsServer = httpsServer;
2736
- ctx2.__port = 443;
2737
- port = 443;
2738
3175
  httpsServer.listen(443, () => {
2739
- if (typeof httpsServer?.address() === "string") {
2740
- address = httpsServer.address();
2741
- } else if (httpsServer?.address()?.address === "::") {
2742
- if (process.env.IS_DOCKER === "true") {
2743
- address = process.env.CADENZA_SERVER_URL || "localhost";
2744
- }
2745
- } else {
2746
- address = httpsServer?.address()?.address || "";
2747
- }
2748
- exposed = true;
2749
- console.log(
2750
- `HTTPS Server is running on ${address}:443`
2751
- );
3176
+ const addressInfo = httpsServer.address();
3177
+ const address = resolveBoundAddress(httpsServer);
3178
+ const port = typeof addressInfo === "object" && addressInfo ? addressInfo.port || 443 : 443;
3179
+ httpsOrigin = `https://${address}:${port}`;
3180
+ console.log(`HTTPS Server is running on ${httpsOrigin}`);
2752
3181
  resolve(address);
2753
3182
  });
2754
3183
  CadenzaService.createMetaTask(
@@ -2768,11 +3197,34 @@ var RestController = class _RestController {
2768
3197
  } else if (ctx.__networkMode === "auto") {
2769
3198
  await createHttpServer(ctx);
2770
3199
  }
3200
+ const declaredTransports = Array.isArray(ctx.__declaredTransports) ? ctx.__declaredTransports : [];
3201
+ const hasExplicitInternalTransport = declaredTransports.some(
3202
+ (transport) => transport.role === "internal"
3203
+ );
3204
+ const transportData = declaredTransports.map((transport) => ({
3205
+ uuid: transport.uuid,
3206
+ service_instance_id: ctx.__serviceInstanceId,
3207
+ role: transport.role,
3208
+ origin: transport.origin,
3209
+ protocols: transport.protocols ?? ["rest", "socket"],
3210
+ ...transport.securityProfile ? { security_profile: transport.securityProfile } : {},
3211
+ ...transport.authStrategy ? { auth_strategy: transport.authStrategy } : {}
3212
+ }));
3213
+ if (!hasExplicitInternalTransport) {
3214
+ const internalOrigin = httpOrigin ?? httpsOrigin;
3215
+ if (internalOrigin) {
3216
+ transportData.unshift({
3217
+ uuid: `${ctx.__serviceInstanceId}-internal-auto`,
3218
+ service_instance_id: ctx.__serviceInstanceId,
3219
+ role: "internal",
3220
+ origin: internalOrigin,
3221
+ protocols: ["rest", "socket"],
3222
+ ...ctx.__securityProfile ? { security_profile: ctx.__securityProfile } : {}
3223
+ });
3224
+ }
3225
+ }
2771
3226
  ctx.data = {
2772
3227
  uuid: ctx.__serviceInstanceId,
2773
- address,
2774
- port,
2775
- exposed,
2776
3228
  process_pid: process.pid,
2777
3229
  service_name: ctx.__serviceName,
2778
3230
  is_active: true,
@@ -2781,7 +3233,12 @@ var RestController = class _RestController {
2781
3233
  is_blocked: false,
2782
3234
  health: {}
2783
3235
  };
3236
+ ctx.transportData = transportData;
2784
3237
  delete ctx.__app;
3238
+ CadenzaService.emit(
3239
+ "meta.service_registry.instance_registration_requested",
3240
+ ctx
3241
+ );
2785
3242
  return ctx;
2786
3243
  },
2787
3244
  "Configures network mode"
@@ -2831,10 +3288,12 @@ var RestController = class _RestController {
2831
3288
  CadenzaService.createMetaTask(
2832
3289
  "Setup fetch client",
2833
3290
  (ctx) => {
2834
- const { serviceName, serviceAddress, servicePort, protocol } = ctx;
2835
- const port = protocol === "https" ? 443 : servicePort;
2836
- const URL2 = `${protocol}://${serviceAddress}:${port}`;
2837
- const fetchId = `${serviceAddress}_${port}`;
3291
+ const serviceName = String(ctx.serviceName ?? "");
3292
+ const URL2 = String(ctx.serviceOrigin ?? "");
3293
+ const fetchId = String(ctx.serviceTransportId ?? "");
3294
+ if (!serviceName || !URL2 || !fetchId) {
3295
+ return false;
3296
+ }
2838
3297
  const fetchDiagnostics = this.ensureFetchClientDiagnostics(
2839
3298
  fetchId,
2840
3299
  serviceName,
@@ -3086,18 +3545,18 @@ var RestController = class _RestController {
3086
3545
  serviceName,
3087
3546
  serviceInstanceId,
3088
3547
  communicationTypes,
3089
- serviceAddress,
3090
- servicePort,
3091
- protocol
3548
+ serviceTransportId,
3549
+ serviceOrigin,
3550
+ transportProtocols
3092
3551
  } = ctx;
3093
- const fetchId = `${serviceAddress}_${servicePort}`;
3552
+ const fetchId = String(serviceTransportId ?? "");
3094
3553
  emit(`meta.fetch.handshake_requested:${fetchId}`, {
3095
3554
  serviceInstanceId,
3096
3555
  serviceName,
3097
3556
  communicationTypes,
3098
- serviceAddress,
3099
- servicePort,
3100
- protocol,
3557
+ serviceTransportId,
3558
+ serviceOrigin,
3559
+ transportProtocols,
3101
3560
  handshakeData: {
3102
3561
  instanceId: CadenzaService.serviceRegistry.serviceInstanceId,
3103
3562
  serviceName: CadenzaService.serviceRegistry.serviceName
@@ -3280,7 +3739,6 @@ var RestController = class _RestController {
3280
3739
  };
3281
3740
 
3282
3741
  // src/network/SocketController.ts
3283
- import { Server } from "socket.io";
3284
3742
  import { RateLimiterMemory as RateLimiterMemory2 } from "rate-limiter-flexible";
3285
3743
  import { io } from "socket.io-client";
3286
3744
 
@@ -3323,6 +3781,13 @@ var waitForSocketConnection = async (socket, timeoutMs, createError) => {
3323
3781
  };
3324
3782
 
3325
3783
  // src/network/SocketController.ts
3784
+ var dynamicImport = new Function(
3785
+ "specifier",
3786
+ "return import(specifier);"
3787
+ );
3788
+ async function importNodeModule(specifier) {
3789
+ return dynamicImport(specifier);
3790
+ }
3326
3791
  var SocketController = class _SocketController {
3327
3792
  constructor() {
3328
3793
  this.diagnosticsErrorHistoryLimit = 100;
@@ -3347,9 +3812,9 @@ var SocketController = class _SocketController {
3347
3812
  serviceInstanceId: "",
3348
3813
  communicationTypes: [],
3349
3814
  serviceName: "",
3350
- serviceAddress: "",
3351
- servicePort: 0,
3352
- protocol: "http",
3815
+ serviceTransportId: "",
3816
+ serviceOrigin: "",
3817
+ transportProtocols: [],
3353
3818
  url: "",
3354
3819
  socketId: null,
3355
3820
  connected: false,
@@ -3522,7 +3987,7 @@ var SocketController = class _SocketController {
3522
3987
  const setupSocketServerTask = CadenzaService.createMetaTask(
3523
3988
  "Setup SocketServer",
3524
3989
  this.socketServerActor.task(
3525
- ({ state, runtimeState, input, actor, setState, setRuntimeState, emit }) => {
3990
+ async ({ state, runtimeState, input, actor, setState, setRuntimeState, emit }) => {
3526
3991
  const serverKey = this.resolveSocketServerKey(input) ?? actor.key ?? this.socketServerDefaultKey;
3527
3992
  const shouldUseSocket = Boolean(input.__useSocket);
3528
3993
  if (!shouldUseSocket) {
@@ -3541,7 +4006,9 @@ var SocketController = class _SocketController {
3541
4006
  }
3542
4007
  let runtimeHandle = runtimeState;
3543
4008
  if (!runtimeHandle) {
3544
- runtimeHandle = this.createSocketServerRuntimeHandleFromContext(input);
4009
+ runtimeHandle = await this.createSocketServerRuntimeHandleFromContext(
4010
+ input
4011
+ );
3545
4012
  setRuntimeState(runtimeHandle);
3546
4013
  }
3547
4014
  const profile = String(input.__securityProfile ?? state.securityProfile ?? "medium");
@@ -3799,21 +4266,21 @@ var SocketController = class _SocketController {
3799
4266
  if (input.serviceName !== void 0) {
3800
4267
  next.serviceName = String(input.serviceName);
3801
4268
  }
3802
- if (input.serviceAddress !== void 0) {
3803
- next.serviceAddress = String(input.serviceAddress);
4269
+ if (input.serviceTransportId !== void 0) {
4270
+ next.serviceTransportId = String(input.serviceTransportId);
3804
4271
  }
3805
4272
  if (input.serviceInstanceId !== void 0) {
3806
4273
  next.serviceInstanceId = String(input.serviceInstanceId);
3807
4274
  }
3808
- if (input.protocol !== void 0) {
3809
- next.protocol = String(input.protocol);
4275
+ if (input.serviceOrigin !== void 0) {
4276
+ next.serviceOrigin = String(input.serviceOrigin);
4277
+ }
4278
+ if (input.transportProtocols !== void 0) {
4279
+ next.transportProtocols = Array.isArray(input.transportProtocols) ? input.transportProtocols.map((entry) => String(entry)) : [];
3810
4280
  }
3811
4281
  if (input.url !== void 0) {
3812
4282
  next.url = String(input.url);
3813
4283
  }
3814
- if (input.servicePort !== void 0) {
3815
- next.servicePort = Number(input.servicePort);
3816
- }
3817
4284
  if (input.fetchId !== void 0) {
3818
4285
  next.fetchId = String(input.fetchId);
3819
4286
  }
@@ -3857,25 +4324,24 @@ var SocketController = class _SocketController {
3857
4324
  input.communicationTypes
3858
4325
  );
3859
4326
  const serviceName = String(input.serviceName ?? "");
3860
- const serviceAddress = String(input.serviceAddress ?? "");
3861
- const protocol = String(input.protocol ?? "http");
3862
- const normalizedPort = this.resolveServicePort(protocol, input.servicePort);
3863
- if (!serviceAddress || !normalizedPort) {
4327
+ const serviceTransportId = String(input.serviceTransportId ?? "");
4328
+ const serviceOrigin = String(input.serviceOrigin ?? "");
4329
+ const parsedOrigin = parseTransportOrigin(serviceOrigin);
4330
+ if (!serviceTransportId || !serviceOrigin || !parsedOrigin) {
3864
4331
  CadenzaService.log(
3865
- "Socket client setup skipped due to missing address/port",
4332
+ "Socket client setup skipped due to missing transport origin",
3866
4333
  {
3867
4334
  serviceName,
3868
- serviceAddress,
3869
- servicePort: input.servicePort,
3870
- protocol
4335
+ serviceTransportId,
4336
+ serviceOrigin
3871
4337
  },
3872
4338
  "warning"
3873
4339
  );
3874
4340
  return false;
3875
4341
  }
3876
- const socketProtocol = protocol === "https" ? "wss" : "ws";
3877
- const url = `${socketProtocol}://${serviceAddress}:${normalizedPort}`;
3878
- const fetchId = `${serviceAddress}_${normalizedPort}`;
4342
+ const socketProtocol = parsedOrigin.protocol === "https" ? "wss" : "ws";
4343
+ const url = `${socketProtocol}://${parsedOrigin.hostname}:${parsedOrigin.port}`;
4344
+ const fetchId = serviceTransportId;
3879
4345
  const applySessionOperation = (operation, patch = {}) => {
3880
4346
  CadenzaService.emit("meta.socket_client.session_operation_requested", {
3881
4347
  fetchId,
@@ -3884,9 +4350,9 @@ var SocketController = class _SocketController {
3884
4350
  serviceInstanceId,
3885
4351
  communicationTypes,
3886
4352
  serviceName,
3887
- serviceAddress,
3888
- servicePort: normalizedPort,
3889
- protocol,
4353
+ serviceTransportId,
4354
+ serviceOrigin,
4355
+ transportProtocols: input.transportProtocols,
3890
4356
  url
3891
4357
  });
3892
4358
  };
@@ -3905,9 +4371,9 @@ var SocketController = class _SocketController {
3905
4371
  serviceInstanceId,
3906
4372
  communicationTypes,
3907
4373
  serviceName,
3908
- serviceAddress,
3909
- servicePort: normalizedPort,
3910
- protocol,
4374
+ serviceTransportId,
4375
+ serviceOrigin,
4376
+ transportProtocols: Array.isArray(input.transportProtocols) ? input.transportProtocols.map((entry) => String(entry)) : [],
3911
4377
  url,
3912
4378
  destroyed: false,
3913
4379
  updatedAt: Date.now()
@@ -4235,8 +4701,8 @@ var SocketController = class _SocketController {
4235
4701
  );
4236
4702
  CadenzaService.emit(`meta.socket_client.disconnected:${fetchId}`, {
4237
4703
  serviceName,
4238
- serviceAddress,
4239
- servicePort: normalizedPort
4704
+ serviceTransportId,
4705
+ serviceOrigin
4240
4706
  });
4241
4707
  runtimeHandle.handshake = false;
4242
4708
  });
@@ -4257,7 +4723,7 @@ var SocketController = class _SocketController {
4257
4723
  {
4258
4724
  serviceInstanceId: CadenzaService.serviceRegistry.serviceInstanceId,
4259
4725
  serviceName: CadenzaService.serviceRegistry.serviceName,
4260
- isFrontend: isBrowser,
4726
+ isFrontend: CadenzaService.serviceRegistry.isFrontend || isBrowser,
4261
4727
  __status: "success"
4262
4728
  },
4263
4729
  1e4,
@@ -4435,9 +4901,9 @@ var SocketController = class _SocketController {
4435
4901
  serviceInstanceId,
4436
4902
  serviceName,
4437
4903
  communicationTypes,
4438
- serviceAddress,
4439
- servicePort: normalizedPort,
4440
- protocol,
4904
+ serviceTransportId,
4905
+ serviceOrigin,
4906
+ transportProtocols: input.transportProtocols,
4441
4907
  handshakeData: {
4442
4908
  instanceId: CadenzaService.serviceRegistry.serviceInstanceId,
4443
4909
  serviceName: CadenzaService.serviceRegistry.serviceName
@@ -4476,31 +4942,20 @@ var SocketController = class _SocketController {
4476
4942
  if (explicitFetchId) {
4477
4943
  return explicitFetchId;
4478
4944
  }
4479
- const serviceAddress = String(input.serviceAddress ?? "").trim();
4480
- const protocol = String(input.protocol ?? "http").trim();
4481
- const port = this.resolveServicePort(protocol, input.servicePort);
4482
- if (!serviceAddress || !port) {
4483
- return void 0;
4484
- }
4485
- return `${serviceAddress}_${port}`;
4486
- }
4487
- resolveServicePort(protocol, rawPort) {
4488
- if (protocol === "https") {
4489
- return 443;
4490
- }
4491
- const parsed = Number(rawPort);
4492
- if (!Number.isFinite(parsed) || parsed <= 0) {
4493
- return void 0;
4494
- }
4495
- return Math.trunc(parsed);
4945
+ const transportId = String(
4946
+ input.serviceTransportId ?? input.transportId ?? ""
4947
+ ).trim();
4948
+ return transportId || void 0;
4496
4949
  }
4497
- createSocketServerRuntimeHandleFromContext(context) {
4950
+ async createSocketServerRuntimeHandleFromContext(context) {
4498
4951
  const baseServer = context.httpsServer ?? context.httpServer;
4499
4952
  if (!baseServer) {
4500
4953
  throw new Error(
4501
4954
  "Socket server runtime setup requires either httpsServer or httpServer"
4502
4955
  );
4503
4956
  }
4957
+ const socketServerModule = await importNodeModule("socket.io");
4958
+ const Server = socketServerModule.Server;
4504
4959
  const server = new Server(baseServer, {
4505
4960
  pingInterval: 3e4,
4506
4961
  pingTimeout: 2e4,
@@ -4791,16 +5246,140 @@ var SignalController = class _SignalController {
4791
5246
  service_name: CadenzaService.serviceRegistry.serviceName,
4792
5247
  service_instance_id: CadenzaService.serviceRegistry.serviceInstanceId
4793
5248
  }
4794
- };
4795
- },
4796
- "",
4797
- { isSubMeta: true, concurrency: 100 }
4798
- ).doOn("sub_meta.signal_broker.emitting_signal").emits("global.sub_meta.signal_controller.signal_emitted");
4799
- }
4800
- };
5249
+ };
5250
+ },
5251
+ "",
5252
+ { isSubMeta: true, concurrency: 100 }
5253
+ ).doOn("sub_meta.signal_broker.emitting_signal").emits("global.sub_meta.signal_controller.signal_emitted");
5254
+ }
5255
+ };
5256
+
5257
+ // src/graph/controllers/registerActorSessionPersistence.ts
5258
+ import { META_ACTOR_SESSION_STATE_PERSIST_INTENT } from "@cadenza.io/core";
5259
+ function registerActorSessionPersistenceTasks() {
5260
+ if (CadenzaService.get("Persist actor session state")) {
5261
+ return;
5262
+ }
5263
+ const actorSessionStateInsertTask = CadenzaService.get("dbInsertActorSessionState") ?? CadenzaService.get("Insert actor_session_state in CadenzaDB") ?? CadenzaService.createCadenzaDBInsertTask(
5264
+ "actor_session_state",
5265
+ {},
5266
+ { concurrency: 100, isSubMeta: true }
5267
+ );
5268
+ const validateActorSessionStatePersistenceTask = CadenzaService.createMetaTask(
5269
+ "Validate actor session state persistence",
5270
+ (ctx) => {
5271
+ if (ctx.errored || ctx.failed || ctx.__success !== true) {
5272
+ throw new Error(
5273
+ String(
5274
+ ctx.__error ?? ctx.error ?? "actor_session_state persistence query failed"
5275
+ )
5276
+ );
5277
+ }
5278
+ const rowCount = Number(ctx.rowCount ?? 0);
5279
+ if (!Number.isFinite(rowCount) || rowCount <= 0) {
5280
+ throw new Error(
5281
+ "actor_session_state persistence did not affect any rows (possible stale durable_version)"
5282
+ );
5283
+ }
5284
+ return {
5285
+ __success: true,
5286
+ persisted: true,
5287
+ actor_name: ctx.actor_name,
5288
+ actor_version: ctx.actor_version,
5289
+ actor_key: ctx.actor_key,
5290
+ service_name: ctx.service_name,
5291
+ durable_version: ctx.durable_version
5292
+ };
5293
+ },
5294
+ "Enforces strict actor session persistence success contract.",
5295
+ { isSubMeta: true, concurrency: 100 }
5296
+ );
5297
+ const insertAndValidateActorSessionStateTask = actorSessionStateInsertTask.then(
5298
+ validateActorSessionStatePersistenceTask
5299
+ );
5300
+ CadenzaService.createMetaTask(
5301
+ "Persist actor session state",
5302
+ (ctx) => {
5303
+ const actorName = typeof ctx.actor_name === "string" ? ctx.actor_name.trim() : "";
5304
+ const actorKey = typeof ctx.actor_key === "string" ? ctx.actor_key.trim() : "";
5305
+ const actorVersion = Number(ctx.actor_version ?? 1);
5306
+ const durableVersion = Number(ctx.durable_version);
5307
+ if (!actorName) {
5308
+ throw new Error("actor_name is required for actor session persistence");
5309
+ }
5310
+ if (!actorKey) {
5311
+ throw new Error("actor_key is required for actor session persistence");
5312
+ }
5313
+ if (!Number.isInteger(actorVersion) || actorVersion < 1) {
5314
+ throw new Error("actor_version must be a positive integer");
5315
+ }
5316
+ if (!Number.isInteger(durableVersion) || durableVersion < 0) {
5317
+ throw new Error("durable_version must be a non-negative integer");
5318
+ }
5319
+ if (typeof ctx.durable_state !== "object" || ctx.durable_state === null || Array.isArray(ctx.durable_state)) {
5320
+ throw new Error("durable_state must be a non-null object");
5321
+ }
5322
+ const serviceName = CadenzaService.serviceRegistry.serviceName;
5323
+ if (!serviceName) {
5324
+ throw new Error("service_name is not available for actor session persistence");
5325
+ }
5326
+ let expiresAt = null;
5327
+ if (ctx.expires_at !== void 0 && ctx.expires_at !== null) {
5328
+ if (ctx.expires_at instanceof Date) {
5329
+ expiresAt = ctx.expires_at.toISOString();
5330
+ } else if (typeof ctx.expires_at === "string" && ctx.expires_at.trim().length > 0) {
5331
+ expiresAt = ctx.expires_at;
5332
+ } else {
5333
+ throw new Error("expires_at must be null, Date, or non-empty string");
5334
+ }
5335
+ }
5336
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5337
+ return {
5338
+ ...ctx,
5339
+ actor_name: actorName,
5340
+ actor_key: actorKey,
5341
+ actor_version: actorVersion,
5342
+ durable_version: durableVersion,
5343
+ expires_at: expiresAt,
5344
+ service_name: serviceName,
5345
+ queryData: {
5346
+ data: {
5347
+ actor_name: actorName,
5348
+ actor_version: actorVersion,
5349
+ actor_key: actorKey,
5350
+ service_name: serviceName,
5351
+ durable_state: ctx.durable_state,
5352
+ durable_version: durableVersion,
5353
+ expires_at: expiresAt,
5354
+ updated: updatedAt
5355
+ },
5356
+ onConflict: {
5357
+ target: [
5358
+ "actor_name",
5359
+ "actor_version",
5360
+ "actor_key",
5361
+ "service_name"
5362
+ ],
5363
+ action: {
5364
+ do: "update",
5365
+ set: {
5366
+ durable_state: "excluded",
5367
+ durable_version: "excluded",
5368
+ expires_at: "excluded",
5369
+ updated: "excluded"
5370
+ },
5371
+ where: "actor_session_state.durable_version <= excluded.durable_version"
5372
+ }
5373
+ }
5374
+ }
5375
+ };
5376
+ },
5377
+ "Validates and prepares actor_session_state payload for strict write-through persistence.",
5378
+ { isSubMeta: true, concurrency: 100 }
5379
+ ).then(insertAndValidateActorSessionStateTask).respondsTo(META_ACTOR_SESSION_STATE_PERSIST_INTENT);
5380
+ }
4801
5381
 
4802
5382
  // src/graph/controllers/GraphMetadataController.ts
4803
- import { META_ACTOR_SESSION_STATE_PERSIST_INTENT } from "@cadenza.io/core";
4804
5383
  var GraphMetadataController = class _GraphMetadataController {
4805
5384
  static get instance() {
4806
5385
  if (!this._instance) this._instance = new _GraphMetadataController();
@@ -5034,123 +5613,7 @@ var GraphMetadataController = class _GraphMetadataController {
5034
5613
  }
5035
5614
  };
5036
5615
  }).doOn("meta.actor.task_associated").emits("global.meta.graph_metadata.actor_task_associated");
5037
- const actorSessionStateInsertTask = CadenzaService.get("dbInsertActorSessionState") ?? CadenzaService.get("Insert actor_session_state in CadenzaDB") ?? CadenzaService.createCadenzaDBInsertTask(
5038
- "actor_session_state",
5039
- {},
5040
- { concurrency: 100, isSubMeta: true }
5041
- );
5042
- const validateActorSessionStatePersistenceTask = CadenzaService.createMetaTask(
5043
- "Validate actor session state persistence",
5044
- (ctx) => {
5045
- if (ctx.errored || ctx.failed || ctx.__success !== true) {
5046
- throw new Error(
5047
- String(
5048
- ctx.__error ?? ctx.error ?? "actor_session_state persistence query failed"
5049
- )
5050
- );
5051
- }
5052
- const rowCount = Number(ctx.rowCount ?? 0);
5053
- if (!Number.isFinite(rowCount) || rowCount <= 0) {
5054
- throw new Error(
5055
- "actor_session_state persistence did not affect any rows (possible stale durable_version)"
5056
- );
5057
- }
5058
- return {
5059
- __success: true,
5060
- persisted: true,
5061
- actor_name: ctx.actor_name,
5062
- actor_version: ctx.actor_version,
5063
- actor_key: ctx.actor_key,
5064
- service_name: ctx.service_name,
5065
- durable_version: ctx.durable_version
5066
- };
5067
- },
5068
- "Enforces strict actor session persistence success contract.",
5069
- { isSubMeta: true, concurrency: 100 }
5070
- );
5071
- const insertAndValidateActorSessionStateTask = actorSessionStateInsertTask.then(
5072
- validateActorSessionStatePersistenceTask
5073
- );
5074
- CadenzaService.createMetaTask(
5075
- "Persist actor session state",
5076
- (ctx) => {
5077
- const actorName = typeof ctx.actor_name === "string" ? ctx.actor_name.trim() : "";
5078
- const actorKey = typeof ctx.actor_key === "string" ? ctx.actor_key.trim() : "";
5079
- const actorVersion = Number(ctx.actor_version ?? 1);
5080
- const durableVersion = Number(ctx.durable_version);
5081
- if (!actorName) {
5082
- throw new Error("actor_name is required for actor session persistence");
5083
- }
5084
- if (!actorKey) {
5085
- throw new Error("actor_key is required for actor session persistence");
5086
- }
5087
- if (!Number.isInteger(actorVersion) || actorVersion < 1) {
5088
- throw new Error("actor_version must be a positive integer");
5089
- }
5090
- if (!Number.isInteger(durableVersion) || durableVersion < 0) {
5091
- throw new Error("durable_version must be a non-negative integer");
5092
- }
5093
- if (typeof ctx.durable_state !== "object" || ctx.durable_state === null || Array.isArray(ctx.durable_state)) {
5094
- throw new Error("durable_state must be a non-null object");
5095
- }
5096
- const serviceName = CadenzaService.serviceRegistry.serviceName;
5097
- if (!serviceName) {
5098
- throw new Error("service_name is not available for actor session persistence");
5099
- }
5100
- let expiresAt = null;
5101
- if (ctx.expires_at !== void 0 && ctx.expires_at !== null) {
5102
- if (ctx.expires_at instanceof Date) {
5103
- expiresAt = ctx.expires_at.toISOString();
5104
- } else if (typeof ctx.expires_at === "string" && ctx.expires_at.trim().length > 0) {
5105
- expiresAt = ctx.expires_at;
5106
- } else {
5107
- throw new Error("expires_at must be null, Date, or non-empty string");
5108
- }
5109
- }
5110
- const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5111
- return {
5112
- ...ctx,
5113
- actor_name: actorName,
5114
- actor_key: actorKey,
5115
- actor_version: actorVersion,
5116
- durable_version: durableVersion,
5117
- expires_at: expiresAt,
5118
- service_name: serviceName,
5119
- queryData: {
5120
- data: {
5121
- actor_name: actorName,
5122
- actor_version: actorVersion,
5123
- actor_key: actorKey,
5124
- service_name: serviceName,
5125
- durable_state: ctx.durable_state,
5126
- durable_version: durableVersion,
5127
- expires_at: expiresAt,
5128
- updated: updatedAt
5129
- },
5130
- onConflict: {
5131
- target: [
5132
- "actor_name",
5133
- "actor_version",
5134
- "actor_key",
5135
- "service_name"
5136
- ],
5137
- action: {
5138
- do: "update",
5139
- set: {
5140
- durable_state: "excluded",
5141
- durable_version: "excluded",
5142
- expires_at: "excluded",
5143
- updated: "excluded"
5144
- },
5145
- where: "actor_session_state.durable_version <= excluded.durable_version"
5146
- }
5147
- }
5148
- }
5149
- };
5150
- },
5151
- "Validates and prepares actor_session_state payload for strict write-through persistence.",
5152
- { isSubMeta: true, concurrency: 100 }
5153
- ).then(insertAndValidateActorSessionStateTask).respondsTo(META_ACTOR_SESSION_STATE_PERSIST_INTENT);
5616
+ registerActorSessionPersistenceTasks();
5154
5617
  CadenzaService.createMetaTask("Handle Intent Creation", (ctx) => {
5155
5618
  const intentName = ctx.data?.name;
5156
5619
  return {
@@ -7786,6 +8249,165 @@ var GraphSyncController = class _GraphSyncController {
7786
8249
  }
7787
8250
  };
7788
8251
 
8252
+ // src/utils/bootstrap.ts
8253
+ var DEFAULT_BOOTSTRAP_GLOBAL_KEY = "__CADENZA_RUNTIME__";
8254
+ function normalizeString3(value) {
8255
+ if (typeof value !== "string") {
8256
+ return void 0;
8257
+ }
8258
+ const normalized = value.trim();
8259
+ return normalized.length > 0 ? normalized : void 0;
8260
+ }
8261
+ function readEnvString(name) {
8262
+ if (typeof process === "undefined") {
8263
+ return void 0;
8264
+ }
8265
+ return normalizeString3(process.env?.[name]);
8266
+ }
8267
+ function readConfiguredPort(value) {
8268
+ if (value === void 0 || value === null || value === "") {
8269
+ return void 0;
8270
+ }
8271
+ const parsed = Number(value);
8272
+ if (!Number.isFinite(parsed)) {
8273
+ throw new Error(`Invalid port value: ${String(value)}`);
8274
+ }
8275
+ const normalized = Math.trunc(parsed);
8276
+ if (normalized <= 0) {
8277
+ throw new Error(`Port must be a positive integer: ${String(value)}`);
8278
+ }
8279
+ return normalized;
8280
+ }
8281
+ function buildBootstrapUrl(protocol, address, port) {
8282
+ return `${protocol}://${address}:${port}`;
8283
+ }
8284
+ function resolveInjectedBootstrapUrl(injectedGlobalKey) {
8285
+ if (typeof globalThis === "undefined") {
8286
+ return void 0;
8287
+ }
8288
+ const runtimeConfig = globalThis[injectedGlobalKey];
8289
+ return normalizeString3(runtimeConfig?.bootstrapUrl);
8290
+ }
8291
+ function resolveExplicitBootstrapValue(options) {
8292
+ const injectedGlobalKey = normalizeString3(options.bootstrap?.injectedGlobalKey) ?? DEFAULT_BOOTSTRAP_GLOBAL_KEY;
8293
+ const explicitBootstrapUrl = normalizeString3(options.bootstrap?.url);
8294
+ if (explicitBootstrapUrl) {
8295
+ return {
8296
+ value: explicitBootstrapUrl,
8297
+ port: readConfiguredPort(options.cadenzaDB?.port),
8298
+ injectedGlobalKey
8299
+ };
8300
+ }
8301
+ if (options.runtime === "browser") {
8302
+ const injected = resolveInjectedBootstrapUrl(injectedGlobalKey);
8303
+ if (injected) {
8304
+ return {
8305
+ value: injected,
8306
+ port: readConfiguredPort(options.cadenzaDB?.port),
8307
+ injectedGlobalKey
8308
+ };
8309
+ }
8310
+ }
8311
+ const explicitAddress = normalizeString3(options.cadenzaDB?.address);
8312
+ if (explicitAddress) {
8313
+ return {
8314
+ value: explicitAddress,
8315
+ port: readConfiguredPort(options.cadenzaDB?.port),
8316
+ injectedGlobalKey
8317
+ };
8318
+ }
8319
+ const envAddress = readEnvString("CADENZA_DB_ADDRESS");
8320
+ return {
8321
+ value: envAddress,
8322
+ port: readConfiguredPort(options.cadenzaDB?.port ?? readEnvString("CADENZA_DB_PORT")),
8323
+ injectedGlobalKey
8324
+ };
8325
+ }
8326
+ function readIntegerEnv(name, fallback) {
8327
+ const raw = readEnvString(name);
8328
+ if (!raw) {
8329
+ return fallback;
8330
+ }
8331
+ const parsed = Number(raw);
8332
+ if (!Number.isFinite(parsed)) {
8333
+ return fallback;
8334
+ }
8335
+ const normalized = Math.trunc(parsed);
8336
+ if (normalized <= 0) {
8337
+ return fallback;
8338
+ }
8339
+ return normalized;
8340
+ }
8341
+ function readStringEnv(name) {
8342
+ return readEnvString(name);
8343
+ }
8344
+ function readListEnv(name) {
8345
+ const raw = readEnvString(name);
8346
+ if (!raw) {
8347
+ return [];
8348
+ }
8349
+ return raw.split("|").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => entry.split(",").map((part) => part.trim()));
8350
+ }
8351
+ function resolveBootstrapEndpoint(options) {
8352
+ const { value, port: fallbackPort, injectedGlobalKey } = resolveExplicitBootstrapValue(options);
8353
+ if (!value) {
8354
+ throw new Error(
8355
+ 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."
8356
+ );
8357
+ }
8358
+ const raw = value.trim();
8359
+ if (raw.length === 0) {
8360
+ throw new Error("Bootstrap URL cannot be empty");
8361
+ }
8362
+ if (raw.includes("://")) {
8363
+ const parsed2 = new URL(raw);
8364
+ const protocol2 = parsed2.protocol.replace(":", "");
8365
+ if (protocol2 !== "http" && protocol2 !== "https") {
8366
+ throw new Error(`Unsupported bootstrap protocol: ${parsed2.protocol}`);
8367
+ }
8368
+ if (parsed2.pathname && parsed2.pathname !== "/" && parsed2.pathname.trim().length > 0) {
8369
+ throw new Error(
8370
+ "Bootstrap URL must be an origin without a path component."
8371
+ );
8372
+ }
8373
+ const port2 = parsed2.port ? readConfiguredPort(parsed2.port) : fallbackPort;
8374
+ if (!port2) {
8375
+ throw new Error(
8376
+ "Bootstrap URL must include a port or CADENZA_DB_PORT must be provided."
8377
+ );
8378
+ }
8379
+ return {
8380
+ url: buildBootstrapUrl(protocol2, parsed2.hostname, port2),
8381
+ protocol: protocol2,
8382
+ address: parsed2.hostname,
8383
+ port: port2,
8384
+ exposed: protocol2 === "https",
8385
+ injectedGlobalKey
8386
+ };
8387
+ }
8388
+ if (raw.includes("/") || raw.includes("?") || raw.includes("#")) {
8389
+ throw new Error(
8390
+ "Bootstrap address without protocol must not include a path, query, or hash."
8391
+ );
8392
+ }
8393
+ const parsed = new URL(`http://${raw}`);
8394
+ const protocol = "http";
8395
+ const port = parsed.port ? readConfiguredPort(parsed.port) : fallbackPort;
8396
+ if (!port) {
8397
+ throw new Error(
8398
+ "Bootstrap address must include a port or CADENZA_DB_PORT must be provided."
8399
+ );
8400
+ }
8401
+ return {
8402
+ url: buildBootstrapUrl(protocol, parsed.hostname, port),
8403
+ protocol,
8404
+ address: parsed.hostname,
8405
+ port,
8406
+ exposed: false,
8407
+ injectedGlobalKey
8408
+ };
8409
+ }
8410
+
7789
8411
  // src/Cadenza.ts
7790
8412
  var CadenzaService = class {
7791
8413
  /**
@@ -7804,11 +8426,60 @@ var CadenzaService = class {
7804
8426
  this.metaRunner = Cadenza.metaRunner;
7805
8427
  this.registry = Cadenza.registry;
7806
8428
  this.serviceRegistry = ServiceRegistry.instance;
7807
- SignalController.instance;
7808
8429
  RestController.instance;
7809
8430
  SocketController.instance;
7810
8431
  console.log("BOOTSTRAPPED");
7811
8432
  }
8433
+ static ensureTransportControllers(isFrontend) {
8434
+ if (!isFrontend) {
8435
+ SignalController.instance;
8436
+ }
8437
+ }
8438
+ static setHydrationResults(hydration) {
8439
+ this.hydratedInquiryResults = /* @__PURE__ */ new Map();
8440
+ const initialInquiryResults = hydration?.initialInquiryResults ?? {};
8441
+ for (const [key, value] of Object.entries(initialInquiryResults)) {
8442
+ this.hydratedInquiryResults.set(key, value);
8443
+ }
8444
+ }
8445
+ static consumeHydratedInquiryResult(hydrationKey) {
8446
+ if (!hydrationKey) {
8447
+ return void 0;
8448
+ }
8449
+ const result = this.hydratedInquiryResults.get(hydrationKey);
8450
+ if (result === void 0) {
8451
+ return void 0;
8452
+ }
8453
+ this.hydratedInquiryResults.delete(hydrationKey);
8454
+ return result;
8455
+ }
8456
+ static ensureFrontendSyncLoop() {
8457
+ if (this.frontendSyncScheduled) {
8458
+ return;
8459
+ }
8460
+ this.frontendSyncScheduled = true;
8461
+ Cadenza.interval("meta.sync_requested", { __syncing: false }, 18e4);
8462
+ Cadenza.schedule("meta.sync_requested", { __syncing: false }, 250);
8463
+ }
8464
+ static normalizeDeclaredTransports(transports, serviceId) {
8465
+ return (transports ?? []).map((transport) => normalizeServiceTransportConfig(transport)).filter(
8466
+ (transport) => !!transport
8467
+ ).map((transport, index) => ({
8468
+ ...transport,
8469
+ uuid: `${serviceId}-transport-${index + 1}`
8470
+ }));
8471
+ }
8472
+ static createBootstrapTransport(serviceInstanceId, role, endpoint) {
8473
+ return {
8474
+ uuid: `${serviceInstanceId}-${role}-bootstrap`,
8475
+ service_instance_id: serviceInstanceId,
8476
+ role,
8477
+ origin: endpoint.url,
8478
+ protocols: ["rest", "socket"],
8479
+ security_profile: null,
8480
+ auth_strategy: null
8481
+ };
8482
+ }
7812
8483
  /**
7813
8484
  * Validates the provided service name based on specific rules.
7814
8485
  *
@@ -7921,6 +8592,12 @@ var CadenzaService = class {
7921
8592
  }
7922
8593
  static async inquire(inquiry, context, options = {}) {
7923
8594
  this.bootstrap();
8595
+ const hydratedResult = this.consumeHydratedInquiryResult(
8596
+ options.hydrationKey
8597
+ );
8598
+ if (hydratedResult !== void 0) {
8599
+ return hydratedResult;
8600
+ }
7924
8601
  const observer = this.inquiryBroker?.inquiryObservers.get(inquiry);
7925
8602
  const allResponders = observer ? Array.from(observer.tasks).map((task) => ({
7926
8603
  task,
@@ -8525,58 +9202,97 @@ var CadenzaService = class {
8525
9202
  const serviceId = options.customServiceId ?? uuid3();
8526
9203
  this.serviceRegistry.serviceName = serviceName;
8527
9204
  this.serviceRegistry.serviceInstanceId = serviceId;
9205
+ this.setHydrationResults(options.hydration);
9206
+ const explicitFrontendMode = options.isFrontend;
8528
9207
  options = {
8529
9208
  loadBalance: true,
8530
9209
  useSocket: true,
8531
9210
  displayName: void 0,
8532
9211
  isMeta: false,
8533
- port: parseInt(process.env.HTTP_PORT ?? "3000"),
8534
- securityProfile: process.env.SECURITY_PROFILE ?? "medium",
8535
- networkMode: process.env.NETWORK_MODE ?? "dev",
9212
+ port: readIntegerEnv("HTTP_PORT", 3e3),
9213
+ securityProfile: readStringEnv("SECURITY_PROFILE") ?? "medium",
9214
+ networkMode: readStringEnv("NETWORK_MODE") ?? "dev",
8536
9215
  retryCount: 3,
8537
9216
  cadenzaDB: {
8538
- connect: true,
8539
- address: process.env.CADENZA_DB_ADDRESS ?? "localhost",
8540
- port: parseInt(process.env.CADENZA_DB_PORT ?? "5000")
9217
+ connect: true
8541
9218
  },
8542
- relatedServices: process.env.RELATED_SERVICES ? process.env.RELATED_SERVICES.split("|").map(
8543
- (s) => s.trim().split(",")
8544
- ) : [],
8545
- isFrontend: isBrowser,
9219
+ relatedServices: readListEnv("RELATED_SERVICES"),
9220
+ isFrontend: typeof explicitFrontendMode === "boolean" ? explicitFrontendMode : isBrowser,
8546
9221
  ...options
8547
9222
  };
9223
+ const isFrontend = !!options.isFrontend;
9224
+ const declaredTransports = this.normalizeDeclaredTransports(
9225
+ options.transports,
9226
+ serviceId
9227
+ );
9228
+ this.serviceRegistry.isFrontend = isFrontend;
9229
+ this.serviceRegistry.useSocket = !!options.useSocket;
9230
+ this.serviceRegistry.retryCount = options.retryCount ?? 3;
9231
+ this.ensureTransportControllers(isFrontend);
9232
+ const resolvedBootstrapEndpoint = options.cadenzaDB?.connect ? resolveBootstrapEndpoint({
9233
+ runtime: isFrontend ? "browser" : "server",
9234
+ bootstrap: options.bootstrap,
9235
+ cadenzaDB: options.cadenzaDB
9236
+ }) : void 0;
9237
+ if (resolvedBootstrapEndpoint) {
9238
+ options.cadenzaDB = {
9239
+ ...options.cadenzaDB,
9240
+ connect: true,
9241
+ address: resolvedBootstrapEndpoint.address,
9242
+ port: resolvedBootstrapEndpoint.port
9243
+ };
9244
+ }
8548
9245
  if (options.cadenzaDB?.connect) {
8549
9246
  this.emit("meta.initializing_service", {
8550
9247
  // Seed the CadenzaDB
8551
9248
  serviceInstance: {
8552
9249
  uuid: "cadenza-db",
8553
9250
  serviceName: "CadenzaDB",
8554
- address: options.cadenzaDB?.address,
8555
- port: options.cadenzaDB?.port,
8556
- exposed: options.networkMode !== "dev",
8557
9251
  numberOfRunningGraphs: 0,
8558
9252
  isActive: true,
8559
9253
  // Assume it is deployed
8560
9254
  isNonResponsive: false,
8561
9255
  isBlocked: false,
8562
- health: {}
9256
+ health: {},
9257
+ isFrontend: false,
9258
+ transports: resolvedBootstrapEndpoint ? [
9259
+ this.createBootstrapTransport(
9260
+ "cadenza-db",
9261
+ isFrontend ? "public" : "internal",
9262
+ resolvedBootstrapEndpoint
9263
+ )
9264
+ ] : []
8563
9265
  }
8564
9266
  });
8565
9267
  }
8566
9268
  options.relatedServices?.forEach((service) => {
9269
+ const relatedTransport = normalizeServiceTransportConfig({
9270
+ role: isFrontend ? "public" : "internal",
9271
+ origin: service[2].includes("://") ? service[2] : `http://${service[2]}`,
9272
+ protocols: ["rest", "socket"]
9273
+ });
8567
9274
  this.emit("meta.initializing_service", {
8568
9275
  serviceInstance: {
8569
9276
  uuid: service[0],
8570
9277
  serviceName: service[1],
8571
- address: service[2].split(":")[0],
8572
- port: service[2].split(":")[1] ?? 3e3,
8573
- exposed: options.networkMode !== "dev",
8574
9278
  numberOfRunningGraphs: 0,
8575
9279
  isActive: true,
8576
9280
  // Assume it is deployed
8577
9281
  isNonResponsive: false,
8578
9282
  isBlocked: false,
8579
- health: {}
9283
+ health: {},
9284
+ isFrontend: false,
9285
+ transports: relatedTransport ? [
9286
+ {
9287
+ uuid: `${service[0]}-${relatedTransport.role}`,
9288
+ service_instance_id: service[0],
9289
+ role: relatedTransport.role,
9290
+ origin: relatedTransport.origin,
9291
+ protocols: relatedTransport.protocols ?? ["rest", "socket"],
9292
+ security_profile: relatedTransport.securityProfile ?? null,
9293
+ auth_strategy: relatedTransport.authStrategy ?? null
9294
+ }
9295
+ ] : []
8580
9296
  }
8581
9297
  });
8582
9298
  });
@@ -8597,7 +9313,9 @@ var CadenzaService = class {
8597
9313
  __networkMode: options.networkMode,
8598
9314
  __retryCount: options.retryCount,
8599
9315
  __cadenzaDBConnect: options.cadenzaDB?.connect,
8600
- __isDatabase: options.isDatabase
9316
+ __isDatabase: options.isDatabase,
9317
+ __isFrontend: isFrontend,
9318
+ __declaredTransports: declaredTransports
8601
9319
  };
8602
9320
  if (options.cadenzaDB?.connect) {
8603
9321
  this.createEphemeralMetaTask("Create service", async (context, emit) => {
@@ -8613,12 +9331,42 @@ var CadenzaService = class {
8613
9331
  }).doOn("meta.rest.handshake");
8614
9332
  }
8615
9333
  this.createMetaTask("Handle service setup completion", () => {
8616
- GraphMetadataController.instance;
8617
- GraphSyncController.instance.isCadenzaDBReady = !!options.cadenzaDB?.connect;
8618
- GraphSyncController.instance.init();
9334
+ if (isFrontend) {
9335
+ registerActorSessionPersistenceTasks();
9336
+ this.ensureFrontendSyncLoop();
9337
+ } else {
9338
+ GraphMetadataController.instance;
9339
+ GraphSyncController.instance.isCadenzaDBReady = !!options.cadenzaDB?.connect;
9340
+ GraphSyncController.instance.init();
9341
+ }
8619
9342
  this.log("Service created.");
8620
9343
  return true;
8621
9344
  }).doOn("meta.service_registry.instance_inserted");
9345
+ if (!options.cadenzaDB?.connect && isFrontend) {
9346
+ Cadenza.schedule(
9347
+ "meta.service_registry.instance_registration_requested",
9348
+ {
9349
+ data: {
9350
+ uuid: serviceId,
9351
+ process_pid: 1,
9352
+ service_name: serviceName,
9353
+ is_frontend: true,
9354
+ is_active: true,
9355
+ is_non_responsive: false,
9356
+ is_blocked: false,
9357
+ health: {}
9358
+ },
9359
+ transportData: [],
9360
+ __serviceName: serviceName,
9361
+ __serviceInstanceId: serviceId,
9362
+ __useSocket: options.useSocket,
9363
+ __retryCount: options.retryCount,
9364
+ __isFrontend: true,
9365
+ __skipRemoteExecution: true
9366
+ },
9367
+ 0
9368
+ );
9369
+ }
8622
9370
  this.serviceCreated = true;
8623
9371
  }
8624
9372
  /**
@@ -8644,9 +9392,9 @@ var CadenzaService = class {
8644
9392
  * @return {void}
8645
9393
  */
8646
9394
  static createPostgresActor(name, schema, description = "", options = {}) {
8647
- if (isBrowser) {
9395
+ if (isBrowser || options.isFrontend) {
8648
9396
  console.warn(
8649
- "PostgresActor creation is not supported in the browser."
9397
+ "PostgresActor creation is not supported in frontend mode."
8650
9398
  );
8651
9399
  return;
8652
9400
  }
@@ -8691,9 +9439,9 @@ var CadenzaService = class {
8691
9439
  * Creates a dedicated database service by composing a PostgresActor and a Cadenza service.
8692
9440
  */
8693
9441
  static createDatabaseService(name, schema, description = "", options = {}) {
8694
- if (isBrowser) {
9442
+ if (isBrowser || options.isFrontend) {
8695
9443
  console.warn(
8696
- "Database service creation is not supported in the browser. Use the CadenzaDB service instead."
9444
+ "Database service creation is not supported in frontend mode. Use the CadenzaDB service instead."
8697
9445
  );
8698
9446
  return;
8699
9447
  }
@@ -8788,7 +9536,7 @@ var CadenzaService = class {
8788
9536
  retryCount: 3,
8789
9537
  databaseType: "postgres",
8790
9538
  databaseName: snakeCase2(name),
8791
- poolSize: parseInt(process.env.DATABASE_POOL_SIZE ?? "10"),
9539
+ poolSize: readIntegerEnv("DATABASE_POOL_SIZE", 10),
8792
9540
  ownerServiceName: options.ownerServiceName ?? this.serviceRegistry?.serviceName ?? null,
8793
9541
  ...options
8794
9542
  };
@@ -8799,18 +9547,16 @@ var CadenzaService = class {
8799
9547
  useSocket: true,
8800
9548
  displayName: void 0,
8801
9549
  isMeta: false,
8802
- port: parseInt(process.env.HTTP_PORT ?? "3000"),
8803
- securityProfile: process.env.SECURITY_PROFILE ?? "medium",
8804
- networkMode: process.env.NETWORK_MODE ?? "dev",
9550
+ port: readIntegerEnv("HTTP_PORT", 3e3),
9551
+ securityProfile: readStringEnv("SECURITY_PROFILE") ?? "medium",
9552
+ networkMode: readStringEnv("NETWORK_MODE") ?? "dev",
8805
9553
  retryCount: 3,
8806
9554
  cadenzaDB: {
8807
- connect: true,
8808
- address: process.env.CADENZA_DB_ADDRESS ?? "localhost",
8809
- port: parseInt(process.env.CADENZA_DB_PORT ?? "5000")
9555
+ connect: true
8810
9556
  },
8811
9557
  databaseType: "postgres",
8812
9558
  databaseName: snakeCase2(name),
8813
- poolSize: parseInt(process.env.DATABASE_POOL_SIZE ?? "10"),
9559
+ poolSize: readIntegerEnv("DATABASE_POOL_SIZE", 10),
8814
9560
  isDatabase: true,
8815
9561
  ownerServiceName: options.ownerServiceName ?? name,
8816
9562
  ...options
@@ -9258,12 +10004,19 @@ var CadenzaService = class {
9258
10004
  }
9259
10005
  static reset() {
9260
10006
  Cadenza.reset();
9261
- this.serviceRegistry.reset();
10007
+ this.serviceRegistry?.reset();
10008
+ this.isBootstrapped = false;
10009
+ this.serviceCreated = false;
10010
+ this.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
10011
+ this.hydratedInquiryResults = /* @__PURE__ */ new Map();
10012
+ this.frontendSyncScheduled = false;
9262
10013
  }
9263
10014
  };
9264
10015
  CadenzaService.isBootstrapped = false;
9265
10016
  CadenzaService.serviceCreated = false;
9266
10017
  CadenzaService.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
10018
+ CadenzaService.hydratedInquiryResults = /* @__PURE__ */ new Map();
10019
+ CadenzaService.frontendSyncScheduled = false;
9267
10020
 
9268
10021
  // src/index.ts
9269
10022
  import {
@@ -9273,9 +10026,274 @@ import {
9273
10026
  GraphRoutine as GraphRoutine2,
9274
10027
  Task as Task4
9275
10028
  } from "@cadenza.io/core";
10029
+
10030
+ // src/ssr/createSSRInquiryBridge.ts
10031
+ import { v4 as uuid4 } from "uuid";
10032
+ function ensureFetch() {
10033
+ if (typeof globalThis.fetch !== "function") {
10034
+ throw new Error("SSR inquiry bridge requires global fetch support.");
10035
+ }
10036
+ return globalThis.fetch.bind(globalThis);
10037
+ }
10038
+ function normalizeArrayResponse(value, keys) {
10039
+ for (const key of keys) {
10040
+ if (Array.isArray(value?.[key])) {
10041
+ return value[key];
10042
+ }
10043
+ }
10044
+ if (Array.isArray(value?.rows)) {
10045
+ return value.rows;
10046
+ }
10047
+ if (Array.isArray(value?.data)) {
10048
+ return value.data;
10049
+ }
10050
+ return [];
10051
+ }
10052
+ function normalizeIntentMap(raw) {
10053
+ const intentName = String(raw.intentName ?? raw.intent_name ?? "").trim();
10054
+ const serviceName = String(raw.serviceName ?? raw.service_name ?? "").trim();
10055
+ const taskName = String(raw.taskName ?? raw.task_name ?? "").trim();
10056
+ const taskVersion = Math.max(
10057
+ 1,
10058
+ Math.trunc(Number(raw.taskVersion ?? raw.task_version ?? 1) || 1)
10059
+ );
10060
+ if (!intentName || !serviceName || !taskName) {
10061
+ return null;
10062
+ }
10063
+ return {
10064
+ intentName,
10065
+ serviceName,
10066
+ taskName,
10067
+ taskVersion,
10068
+ deleted: Boolean(raw.deleted)
10069
+ };
10070
+ }
10071
+ function buildInquiryMeta(inquiry, startedAt, statuses) {
10072
+ const counts = summarizeResponderStatuses(statuses);
10073
+ return {
10074
+ inquiry,
10075
+ isMetaInquiry: isMetaIntentName(inquiry),
10076
+ totalResponders: statuses.length,
10077
+ eligibleResponders: statuses.length,
10078
+ filteredOutResponders: 0,
10079
+ responded: counts.responded,
10080
+ failed: counts.failed,
10081
+ timedOut: counts.timedOut,
10082
+ pending: counts.pending,
10083
+ durationMs: Date.now() - startedAt,
10084
+ responders: statuses
10085
+ };
10086
+ }
10087
+ function createSSRInquiryBridge(options = {}) {
10088
+ const bootstrapEndpoint = resolveBootstrapEndpoint({
10089
+ runtime: "server",
10090
+ bootstrap: options.bootstrap,
10091
+ cadenzaDB: options.cadenzaDB
10092
+ });
10093
+ const fetchImplementation = ensureFetch();
10094
+ const initialInquiryResults = {};
10095
+ const postDelegation = async (targetUrl, remoteRoutineName, context, timeoutMs) => {
10096
+ const signal = AbortSignal.timeout(timeoutMs);
10097
+ const response = await fetchImplementation(`${targetUrl}/delegation`, {
10098
+ method: "POST",
10099
+ headers: {
10100
+ "Content-Type": "application/json"
10101
+ },
10102
+ body: JSON.stringify({
10103
+ ...context,
10104
+ __remoteRoutineName: remoteRoutineName,
10105
+ __metadata: {
10106
+ ...context.__metadata ?? {},
10107
+ __deputyExecId: uuid4()
10108
+ }
10109
+ }),
10110
+ signal
10111
+ });
10112
+ return await response.json();
10113
+ };
10114
+ const queryTable = async (tableName, queryData, timeoutMs) => {
10115
+ const response = await postDelegation(
10116
+ bootstrapEndpoint.url,
10117
+ `Query ${tableName}`,
10118
+ { queryData },
10119
+ timeoutMs
10120
+ );
10121
+ return normalizeArrayResponse(response, [
10122
+ `${tableName}Rows`,
10123
+ `${tableName}s`,
10124
+ tableName,
10125
+ tableName.replace(/_([a-z])/g, (_match, char) => char.toUpperCase())
10126
+ ]);
10127
+ };
10128
+ return {
10129
+ async inquire(inquiry, context = {}, inquiryOptions = {}) {
10130
+ const startedAt = Date.now();
10131
+ const overallTimeoutMs = inquiryOptions.overallTimeoutMs ?? inquiryOptions.timeout ?? 3e4;
10132
+ const perResponderTimeoutMs = inquiryOptions.perResponderTimeoutMs ?? overallTimeoutMs;
10133
+ const intentMaps = (await queryTable(
10134
+ "intent_to_task_map",
10135
+ {
10136
+ filter: {
10137
+ intent_name: inquiry
10138
+ }
10139
+ },
10140
+ overallTimeoutMs
10141
+ )).map(normalizeIntentMap).filter(
10142
+ (item) => !!item && item.intentName === inquiry && !item.deleted
10143
+ );
10144
+ if (intentMaps.length === 0) {
10145
+ return {
10146
+ __inquiryMeta: buildInquiryMeta(inquiry, startedAt, [])
10147
+ };
10148
+ }
10149
+ const relevantServiceNames = Array.from(
10150
+ new Set(intentMaps.map((item) => item.serviceName))
10151
+ );
10152
+ const rawServiceInstances = await queryTable(
10153
+ "service_instance",
10154
+ {
10155
+ filter: {
10156
+ service_name: relevantServiceNames
10157
+ }
10158
+ },
10159
+ overallTimeoutMs
10160
+ );
10161
+ const rawServiceTransports = await queryTable(
10162
+ "service_instance_transport",
10163
+ {
10164
+ filter: {
10165
+ deleted: false
10166
+ }
10167
+ },
10168
+ overallTimeoutMs
10169
+ );
10170
+ const transportsByInstance = /* @__PURE__ */ new Map();
10171
+ for (const transport of rawServiceTransports) {
10172
+ const serviceInstanceId = String(
10173
+ transport.serviceInstanceId ?? transport.service_instance_id ?? ""
10174
+ ).trim();
10175
+ if (!serviceInstanceId) {
10176
+ continue;
10177
+ }
10178
+ if (!transportsByInstance.has(serviceInstanceId)) {
10179
+ transportsByInstance.set(serviceInstanceId, []);
10180
+ }
10181
+ transportsByInstance.get(serviceInstanceId).push(transport);
10182
+ }
10183
+ const serviceInstances = rawServiceInstances.map(
10184
+ (instance) => normalizeServiceInstanceDescriptor({
10185
+ ...instance,
10186
+ transports: transportsByInstance.get(String(instance.uuid ?? "").trim()) ?? []
10187
+ })
10188
+ ).filter(
10189
+ (item) => !!item && relevantServiceNames.includes(item.serviceName) && item.isActive && !item.isNonResponsive && !item.isBlocked
10190
+ ).sort((left, right) => {
10191
+ if (left.serviceName !== right.serviceName) {
10192
+ return left.serviceName.localeCompare(right.serviceName);
10193
+ }
10194
+ if (left.isPrimary !== right.isPrimary) {
10195
+ return left.isPrimary ? -1 : 1;
10196
+ }
10197
+ return (left.numberOfRunningGraphs ?? 0) - (right.numberOfRunningGraphs ?? 0);
10198
+ });
10199
+ const statuses = intentMaps.map((map) => ({
10200
+ isRemote: true,
10201
+ serviceName: map.serviceName,
10202
+ taskName: map.taskName,
10203
+ taskVersion: map.taskVersion,
10204
+ localTaskName: `SSR inquiry via ${map.serviceName} (${map.taskName} v${map.taskVersion})`,
10205
+ status: "timed_out",
10206
+ durationMs: 0
10207
+ }));
10208
+ const fulfilledContexts = await Promise.all(
10209
+ intentMaps.map(async (map, index) => {
10210
+ const status = statuses[index];
10211
+ const startedAtForResponder = Date.now();
10212
+ const selectedInstance = serviceInstances.find(
10213
+ (instance) => instance.serviceName === map.serviceName
10214
+ );
10215
+ if (!selectedInstance) {
10216
+ status.status = "failed";
10217
+ status.error = `No active instances for ${map.serviceName}`;
10218
+ status.durationMs = Date.now() - startedAtForResponder;
10219
+ return null;
10220
+ }
10221
+ const targetTransport = selectTransportForRole(
10222
+ selectedInstance.transports,
10223
+ "internal",
10224
+ "rest"
10225
+ );
10226
+ if (!targetTransport) {
10227
+ status.status = "failed";
10228
+ status.error = `No internal transport for ${selectedInstance.serviceName}/${selectedInstance.uuid}`;
10229
+ status.durationMs = Date.now() - startedAtForResponder;
10230
+ return null;
10231
+ }
10232
+ const targetUrl = targetTransport.origin;
10233
+ try {
10234
+ const result = await postDelegation(
10235
+ targetUrl,
10236
+ map.taskName,
10237
+ context,
10238
+ perResponderTimeoutMs
10239
+ );
10240
+ status.durationMs = Date.now() - startedAtForResponder;
10241
+ if (result?.errored || result?.failed) {
10242
+ status.status = "failed";
10243
+ status.error = String(
10244
+ result?.__error ?? result?.error ?? "Remote inquiry failed"
10245
+ );
10246
+ return null;
10247
+ }
10248
+ status.status = "fulfilled";
10249
+ return result;
10250
+ } catch (error) {
10251
+ status.durationMs = Date.now() - startedAtForResponder;
10252
+ if (error instanceof Error && (error.name === "AbortError" || /timed out/i.test(error.message))) {
10253
+ status.status = "timed_out";
10254
+ status.error = error.message;
10255
+ return null;
10256
+ }
10257
+ status.status = "failed";
10258
+ status.error = error instanceof Error ? error.message : String(error);
10259
+ return null;
10260
+ }
10261
+ })
10262
+ );
10263
+ const mergedContext = mergeInquiryContexts(
10264
+ fulfilledContexts.filter(
10265
+ (item) => item !== null
10266
+ )
10267
+ );
10268
+ const responseContext = {
10269
+ ...mergedContext,
10270
+ __inquiryMeta: buildInquiryMeta(inquiry, startedAt, statuses)
10271
+ };
10272
+ if (inquiryOptions.hydrationKey) {
10273
+ initialInquiryResults[inquiryOptions.hydrationKey] = responseContext;
10274
+ }
10275
+ if (inquiryOptions.requireComplete && statuses.some((status) => status.status !== "fulfilled")) {
10276
+ throw {
10277
+ ...responseContext,
10278
+ __error: `Inquiry '${inquiry}' did not complete successfully`,
10279
+ errored: true
10280
+ };
10281
+ }
10282
+ return responseContext;
10283
+ },
10284
+ dehydrate() {
10285
+ return {
10286
+ initialInquiryResults: { ...initialInquiryResults }
10287
+ };
10288
+ }
10289
+ };
10290
+ }
10291
+
10292
+ // src/index.ts
9276
10293
  var index_default = CadenzaService;
9277
10294
  export {
9278
10295
  Actor2 as Actor,
10296
+ DatabaseController,
9279
10297
  DatabaseTask,
9280
10298
  DebounceTask2 as DebounceTask,
9281
10299
  DeputyTask,
@@ -9288,6 +10306,7 @@ export {
9288
10306
  SignalTransmissionTask,
9289
10307
  SocketController,
9290
10308
  Task4 as Task,
10309
+ createSSRInquiryBridge,
9291
10310
  index_default as default
9292
10311
  };
9293
10312
  //# sourceMappingURL=index.mjs.map