@cadenza.io/service 2.17.2 → 2.17.3

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
@@ -409,7 +409,14 @@ function selectTransportForRole(transports, role, protocol) {
409
409
  const filtered = transports.filter(
410
410
  (transport) => !transport.deleted && transport.role === role && (!protocol || transportSupportsProtocol(transport, protocol))
411
411
  );
412
- return filtered[0];
412
+ return filtered.sort((left, right) => {
413
+ const leftIsBootstrap = left.uuid.endsWith("-bootstrap") ? 1 : 0;
414
+ const rightIsBootstrap = right.uuid.endsWith("-bootstrap") ? 1 : 0;
415
+ if (leftIsBootstrap !== rightIsBootstrap) {
416
+ return leftIsBootstrap - rightIsBootstrap;
417
+ }
418
+ return left.origin.localeCompare(right.origin);
419
+ })[0];
413
420
  }
414
421
  function buildTransportClientKey(transport) {
415
422
  return transport.uuid;
@@ -1427,20 +1434,25 @@ var ServiceRegistry = class _ServiceRegistry {
1427
1434
  this.getBalancedInstance = CadenzaService.createMetaTask(
1428
1435
  "Get balanced instance",
1429
1436
  (context, emit) => {
1430
- const { __serviceName, __triedInstances, __retries, __broadcast } = context;
1437
+ const {
1438
+ __serviceName,
1439
+ __triedInstances,
1440
+ __retries,
1441
+ __broadcast,
1442
+ targetServiceInstanceId
1443
+ } = context;
1431
1444
  let retries = __retries ?? 0;
1432
1445
  let triedInstances = __triedInstances ?? [];
1433
1446
  const preferredRole = this.getRoutingTransportRole();
1434
1447
  const instances = this.instances.get(__serviceName)?.filter((instance) => {
1448
+ if (targetServiceInstanceId && instance.uuid !== targetServiceInstanceId) {
1449
+ return false;
1450
+ }
1435
1451
  if (!instance.isActive || instance.isNonResponsive || instance.isBlocked) {
1436
1452
  return false;
1437
1453
  }
1438
1454
  return Boolean(
1439
- this.getRouteableTransport(
1440
- instance,
1441
- this.useSocket ? "socket" : "rest",
1442
- preferredRole
1443
- ) ?? this.getRouteableTransport(instance, "rest", preferredRole)
1455
+ this.selectTransportForInstance(instance, context, preferredRole)
1444
1456
  );
1445
1457
  }).sort((a, b) => {
1446
1458
  const leftStatus = this.resolveRuntimeStatusSnapshot(
@@ -1472,13 +1484,17 @@ var ServiceRegistry = class _ServiceRegistry {
1472
1484
  }
1473
1485
  if (__broadcast || instances[0].isFrontend) {
1474
1486
  for (const instance of instances) {
1475
- const selectedTransport2 = this.getRouteableTransport(instance, "socket", preferredRole) ?? this.getRouteableTransport(instance, "rest", preferredRole);
1487
+ const selectedTransport2 = this.selectTransportForInstance(
1488
+ instance,
1489
+ context,
1490
+ preferredRole
1491
+ );
1476
1492
  if (!selectedTransport2) {
1477
1493
  continue;
1478
1494
  }
1479
1495
  const transportKey = buildTransportClientKey(selectedTransport2);
1480
1496
  emit(
1481
- `${this.useSocket && transportSupportsProtocol(selectedTransport2, "socket") ? "meta.service_registry.selected_instance_for_socket" : "meta.service_registry.selected_instance_for_fetch"}:${transportKey}`,
1497
+ `${this.resolveTransportProtocolOrder(context)[0] === "socket" && transportSupportsProtocol(selectedTransport2, "socket") ? "meta.service_registry.selected_instance_for_socket" : "meta.service_registry.selected_instance_for_fetch"}:${transportKey}`,
1482
1498
  {
1483
1499
  ...context,
1484
1500
  __instance: instance.uuid,
@@ -1509,7 +1525,11 @@ var ServiceRegistry = class _ServiceRegistry {
1509
1525
  if (retries > 0) {
1510
1526
  selected = instancesToTry[Math.floor(Math.random() * instancesToTry.length)];
1511
1527
  }
1512
- const selectedTransport = this.getRouteableTransport(selected, "socket", preferredRole) ?? this.getRouteableTransport(selected, "rest", preferredRole);
1528
+ const selectedTransport = this.selectTransportForInstance(
1529
+ selected,
1530
+ context,
1531
+ preferredRole
1532
+ );
1513
1533
  if (!selectedTransport) {
1514
1534
  context.errored = true;
1515
1535
  context.__error = `No routeable ${preferredRole} transport available for ${selected.serviceName}/${selected.uuid}.`;
@@ -1527,7 +1547,7 @@ var ServiceRegistry = class _ServiceRegistry {
1527
1547
  context.__triedInstances = triedInstances;
1528
1548
  context.__triedInstances.push(selected.uuid);
1529
1549
  context.__retries = retries;
1530
- if (this.useSocket && transportSupportsProtocol(selectedTransport, "socket")) {
1550
+ if (this.resolveTransportProtocolOrder(context)[0] === "socket" && transportSupportsProtocol(selectedTransport, "socket")) {
1531
1551
  emit(
1532
1552
  `meta.service_registry.selected_instance_for_socket:${context.__fetchId}`,
1533
1553
  context
@@ -2308,6 +2328,21 @@ var ServiceRegistry = class _ServiceRegistry {
2308
2328
  }
2309
2329
  return this.getInstance(this.serviceName, this.serviceInstanceId);
2310
2330
  }
2331
+ resolveTransportProtocolOrder(ctx) {
2332
+ const explicit = ctx.__preferredTransportProtocol === "rest" || ctx.__preferredTransportProtocol === "socket" ? ctx.__preferredTransportProtocol : void 0;
2333
+ const preferred = explicit ?? (this.useSocket ? "socket" : "rest");
2334
+ const fallback = preferred === "socket" ? "rest" : "socket";
2335
+ return [preferred, fallback];
2336
+ }
2337
+ selectTransportForInstance(instance, ctx, role = this.getRoutingTransportRole()) {
2338
+ for (const protocol of this.resolveTransportProtocolOrder(ctx)) {
2339
+ const transport = this.getRouteableTransport(instance, protocol, role);
2340
+ if (transport) {
2341
+ return transport;
2342
+ }
2343
+ }
2344
+ return void 0;
2345
+ }
2311
2346
  getRoutingTransportRole() {
2312
2347
  return this.isFrontend ? "public" : "internal";
2313
2348
  }
@@ -2567,12 +2602,41 @@ var ServiceRegistry = class _ServiceRegistry {
2567
2602
  return null;
2568
2603
  }
2569
2604
  async resolveRuntimeStatusFallbackInquiry(serviceName, serviceInstanceId, options = {}) {
2605
+ const instance = this.getInstance(serviceName, serviceInstanceId);
2606
+ if (instance) {
2607
+ const directReport = await this.requestRuntimeStatusViaRest(
2608
+ instance,
2609
+ serviceName,
2610
+ serviceInstanceId
2611
+ );
2612
+ if (directReport) {
2613
+ if (!this.applyRuntimeStatusReport(directReport)) {
2614
+ throw new Error(
2615
+ `No tracked instance for runtime fallback ${serviceName}/${serviceInstanceId}`
2616
+ );
2617
+ }
2618
+ this.lastHeartbeatAtByInstance.set(serviceInstanceId, Date.now());
2619
+ this.missedHeartbeatsByInstance.set(serviceInstanceId, 0);
2620
+ return {
2621
+ report: directReport,
2622
+ inquiryMeta: {
2623
+ inquiry: META_RUNTIME_STATUS_INTENT,
2624
+ responded: 1,
2625
+ failed: 0,
2626
+ timedOut: 0,
2627
+ pending: 0,
2628
+ directStatusCheck: true
2629
+ }
2630
+ };
2631
+ }
2632
+ }
2570
2633
  const inquiryResult = await CadenzaService.inquire(
2571
2634
  META_RUNTIME_STATUS_INTENT,
2572
2635
  {
2573
2636
  targetServiceName: serviceName,
2574
2637
  targetServiceInstanceId: serviceInstanceId,
2575
- detailLevel: options.detailLevel ?? "minimal"
2638
+ detailLevel: options.detailLevel ?? "minimal",
2639
+ __preferredTransportProtocol: "rest"
2576
2640
  },
2577
2641
  {
2578
2642
  overallTimeoutMs: options.overallTimeoutMs ?? this.runtimeStatusFallbackTimeoutMs,
@@ -2602,6 +2666,46 @@ var ServiceRegistry = class _ServiceRegistry {
2602
2666
  inquiryMeta: inquiryResult.__inquiryMeta ?? {}
2603
2667
  };
2604
2668
  }
2669
+ async requestRuntimeStatusViaRest(instance, serviceName, serviceInstanceId) {
2670
+ if (typeof globalThis.fetch !== "function") {
2671
+ return null;
2672
+ }
2673
+ const transport = this.getRouteableTransport(instance, "rest");
2674
+ if (!transport) {
2675
+ return null;
2676
+ }
2677
+ const controller = typeof AbortController === "function" ? new AbortController() : null;
2678
+ const timeoutId = controller ? setTimeout(() => controller.abort(), this.runtimeStatusFallbackTimeoutMs) : null;
2679
+ try {
2680
+ const response = await globalThis.fetch(`${transport.origin}/status`, {
2681
+ method: "GET",
2682
+ signal: controller?.signal
2683
+ });
2684
+ if ("ok" in response && response.ok === false) {
2685
+ return null;
2686
+ }
2687
+ const payload = typeof response.json === "function" ? await response.json() : response;
2688
+ const report = this.normalizeRuntimeStatusReport({
2689
+ ...payload,
2690
+ serviceTransportId: payload?.serviceTransportId ?? transport.uuid,
2691
+ serviceOrigin: payload?.serviceOrigin ?? transport.origin,
2692
+ transportProtocols: payload?.transportProtocols ?? transport.protocols
2693
+ });
2694
+ if (!report) {
2695
+ return null;
2696
+ }
2697
+ if (report.serviceName !== serviceName || report.serviceInstanceId !== serviceInstanceId) {
2698
+ return null;
2699
+ }
2700
+ return report;
2701
+ } catch {
2702
+ return null;
2703
+ } finally {
2704
+ if (timeoutId) {
2705
+ clearTimeout(timeoutId);
2706
+ }
2707
+ }
2708
+ }
2605
2709
  evaluateDependencyReadinessDetail(serviceName, serviceInstanceId, now = Date.now()) {
2606
2710
  const instance = this.getInstance(serviceName, serviceInstanceId);
2607
2711
  const missedHeartbeats = this.getHeartbeatMisses(serviceInstanceId, now);