@cadenza.io/service 2.17.2 → 2.17.4

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.
@@ -460,7 +460,14 @@ function selectTransportForRole(transports, role, protocol) {
460
460
  const filtered = transports.filter(
461
461
  (transport) => !transport.deleted && transport.role === role && (!protocol || transportSupportsProtocol(transport, protocol))
462
462
  );
463
- return filtered[0];
463
+ return filtered.sort((left, right) => {
464
+ const leftIsBootstrap = left.uuid.endsWith("-bootstrap") ? 1 : 0;
465
+ const rightIsBootstrap = right.uuid.endsWith("-bootstrap") ? 1 : 0;
466
+ if (leftIsBootstrap !== rightIsBootstrap) {
467
+ return leftIsBootstrap - rightIsBootstrap;
468
+ }
469
+ return left.origin.localeCompare(right.origin);
470
+ })[0];
464
471
  }
465
472
  function buildTransportClientKey(transport) {
466
473
  return transport.uuid;
@@ -1478,20 +1485,25 @@ var ServiceRegistry = class _ServiceRegistry {
1478
1485
  this.getBalancedInstance = CadenzaService.createMetaTask(
1479
1486
  "Get balanced instance",
1480
1487
  (context, emit) => {
1481
- const { __serviceName, __triedInstances, __retries, __broadcast } = context;
1488
+ const {
1489
+ __serviceName,
1490
+ __triedInstances,
1491
+ __retries,
1492
+ __broadcast,
1493
+ targetServiceInstanceId
1494
+ } = context;
1482
1495
  let retries = __retries ?? 0;
1483
1496
  let triedInstances = __triedInstances ?? [];
1484
1497
  const preferredRole = this.getRoutingTransportRole();
1485
1498
  const instances = this.instances.get(__serviceName)?.filter((instance) => {
1499
+ if (targetServiceInstanceId && instance.uuid !== targetServiceInstanceId) {
1500
+ return false;
1501
+ }
1486
1502
  if (!instance.isActive || instance.isNonResponsive || instance.isBlocked) {
1487
1503
  return false;
1488
1504
  }
1489
1505
  return Boolean(
1490
- this.getRouteableTransport(
1491
- instance,
1492
- this.useSocket ? "socket" : "rest",
1493
- preferredRole
1494
- ) ?? this.getRouteableTransport(instance, "rest", preferredRole)
1506
+ this.selectTransportForInstance(instance, context, preferredRole)
1495
1507
  );
1496
1508
  }).sort((a, b) => {
1497
1509
  const leftStatus = this.resolveRuntimeStatusSnapshot(
@@ -1523,13 +1535,17 @@ var ServiceRegistry = class _ServiceRegistry {
1523
1535
  }
1524
1536
  if (__broadcast || instances[0].isFrontend) {
1525
1537
  for (const instance of instances) {
1526
- const selectedTransport2 = this.getRouteableTransport(instance, "socket", preferredRole) ?? this.getRouteableTransport(instance, "rest", preferredRole);
1538
+ const selectedTransport2 = this.selectTransportForInstance(
1539
+ instance,
1540
+ context,
1541
+ preferredRole
1542
+ );
1527
1543
  if (!selectedTransport2) {
1528
1544
  continue;
1529
1545
  }
1530
1546
  const transportKey = buildTransportClientKey(selectedTransport2);
1531
1547
  emit(
1532
- `${this.useSocket && transportSupportsProtocol(selectedTransport2, "socket") ? "meta.service_registry.selected_instance_for_socket" : "meta.service_registry.selected_instance_for_fetch"}:${transportKey}`,
1548
+ `${this.resolveTransportProtocolOrder(context)[0] === "socket" && transportSupportsProtocol(selectedTransport2, "socket") ? "meta.service_registry.selected_instance_for_socket" : "meta.service_registry.selected_instance_for_fetch"}:${transportKey}`,
1533
1549
  {
1534
1550
  ...context,
1535
1551
  __instance: instance.uuid,
@@ -1560,7 +1576,11 @@ var ServiceRegistry = class _ServiceRegistry {
1560
1576
  if (retries > 0) {
1561
1577
  selected = instancesToTry[Math.floor(Math.random() * instancesToTry.length)];
1562
1578
  }
1563
- const selectedTransport = this.getRouteableTransport(selected, "socket", preferredRole) ?? this.getRouteableTransport(selected, "rest", preferredRole);
1579
+ const selectedTransport = this.selectTransportForInstance(
1580
+ selected,
1581
+ context,
1582
+ preferredRole
1583
+ );
1564
1584
  if (!selectedTransport) {
1565
1585
  context.errored = true;
1566
1586
  context.__error = `No routeable ${preferredRole} transport available for ${selected.serviceName}/${selected.uuid}.`;
@@ -1578,7 +1598,7 @@ var ServiceRegistry = class _ServiceRegistry {
1578
1598
  context.__triedInstances = triedInstances;
1579
1599
  context.__triedInstances.push(selected.uuid);
1580
1600
  context.__retries = retries;
1581
- if (this.useSocket && transportSupportsProtocol(selectedTransport, "socket")) {
1601
+ if (this.resolveTransportProtocolOrder(context)[0] === "socket" && transportSupportsProtocol(selectedTransport, "socket")) {
1582
1602
  emit(
1583
1603
  `meta.service_registry.selected_instance_for_socket:${context.__fetchId}`,
1584
1604
  context
@@ -1604,50 +1624,10 @@ var ServiceRegistry = class _ServiceRegistry {
1604
1624
  "meta.service_registry.selected_instance_for_fetch",
1605
1625
  "meta.service_registry.socket_failed"
1606
1626
  );
1607
- this.getStatusTask = CadenzaService.createMetaTask("Get status", (ctx) => {
1608
- if (!this.serviceName) {
1609
- return {
1610
- __status: "error",
1611
- __error: "No service name defined",
1612
- errored: true
1613
- };
1614
- }
1615
- if (!this.serviceInstanceId) {
1616
- return {
1617
- __status: "error",
1618
- __error: "No service instance id defined",
1619
- errored: true
1620
- };
1621
- }
1622
- const report = this.buildLocalRuntimeStatusReport("full");
1623
- if (!report) {
1624
- return {
1625
- ...ctx,
1626
- __status: "error",
1627
- __error: "No local service instance available for status check",
1628
- errored: true
1629
- };
1630
- }
1631
- return {
1632
- ...ctx,
1633
- __status: "ok",
1634
- __serviceName: report.serviceName,
1635
- __serviceInstanceId: report.serviceInstanceId,
1636
- __numberOfRunningGraphs: report.numberOfRunningGraphs,
1637
- __health: report.health ?? {},
1638
- __active: report.isActive,
1639
- reportedAt: report.reportedAt,
1640
- serviceName: report.serviceName,
1641
- serviceInstanceId: report.serviceInstanceId,
1642
- numberOfRunningGraphs: report.numberOfRunningGraphs,
1643
- health: report.health ?? {},
1644
- isActive: report.isActive,
1645
- isNonResponsive: report.isNonResponsive,
1646
- isBlocked: report.isBlocked,
1647
- state: report.state,
1648
- acceptingWork: report.acceptingWork
1649
- };
1650
- }).doOn(
1627
+ this.getStatusTask = CadenzaService.createMetaTask(
1628
+ "Get status",
1629
+ (ctx) => this.resolveLocalStatusCheck(ctx)
1630
+ ).doOn(
1651
1631
  "meta.socket.status_check_requested",
1652
1632
  "meta.rest.status_check_requested"
1653
1633
  );
@@ -2359,6 +2339,65 @@ var ServiceRegistry = class _ServiceRegistry {
2359
2339
  }
2360
2340
  return this.getInstance(this.serviceName, this.serviceInstanceId);
2361
2341
  }
2342
+ resolveLocalStatusCheck(ctx = {}) {
2343
+ if (!this.serviceName) {
2344
+ return {
2345
+ __status: "error",
2346
+ __error: "No service name defined",
2347
+ errored: true
2348
+ };
2349
+ }
2350
+ if (!this.serviceInstanceId) {
2351
+ return {
2352
+ __status: "error",
2353
+ __error: "No service instance id defined",
2354
+ errored: true
2355
+ };
2356
+ }
2357
+ const report = this.buildLocalRuntimeStatusReport("full");
2358
+ if (!report) {
2359
+ return {
2360
+ ...ctx,
2361
+ __status: "error",
2362
+ __error: "No local service instance available for status check",
2363
+ errored: true
2364
+ };
2365
+ }
2366
+ return {
2367
+ ...ctx,
2368
+ __status: "ok",
2369
+ __serviceName: report.serviceName,
2370
+ __serviceInstanceId: report.serviceInstanceId,
2371
+ __numberOfRunningGraphs: report.numberOfRunningGraphs,
2372
+ __health: report.health ?? {},
2373
+ __active: report.isActive,
2374
+ reportedAt: report.reportedAt,
2375
+ serviceName: report.serviceName,
2376
+ serviceInstanceId: report.serviceInstanceId,
2377
+ numberOfRunningGraphs: report.numberOfRunningGraphs,
2378
+ health: report.health ?? {},
2379
+ isActive: report.isActive,
2380
+ isNonResponsive: report.isNonResponsive,
2381
+ isBlocked: report.isBlocked,
2382
+ state: report.state,
2383
+ acceptingWork: report.acceptingWork
2384
+ };
2385
+ }
2386
+ resolveTransportProtocolOrder(ctx) {
2387
+ const explicit = ctx.__preferredTransportProtocol === "rest" || ctx.__preferredTransportProtocol === "socket" ? ctx.__preferredTransportProtocol : void 0;
2388
+ const preferred = explicit ?? (this.useSocket ? "socket" : "rest");
2389
+ const fallback = preferred === "socket" ? "rest" : "socket";
2390
+ return [preferred, fallback];
2391
+ }
2392
+ selectTransportForInstance(instance, ctx, role = this.getRoutingTransportRole()) {
2393
+ for (const protocol of this.resolveTransportProtocolOrder(ctx)) {
2394
+ const transport = this.getRouteableTransport(instance, protocol, role);
2395
+ if (transport) {
2396
+ return transport;
2397
+ }
2398
+ }
2399
+ return void 0;
2400
+ }
2362
2401
  getRoutingTransportRole() {
2363
2402
  return this.isFrontend ? "public" : "internal";
2364
2403
  }
@@ -2618,12 +2657,41 @@ var ServiceRegistry = class _ServiceRegistry {
2618
2657
  return null;
2619
2658
  }
2620
2659
  async resolveRuntimeStatusFallbackInquiry(serviceName, serviceInstanceId, options = {}) {
2660
+ const instance = this.getInstance(serviceName, serviceInstanceId);
2661
+ if (instance) {
2662
+ const directReport = await this.requestRuntimeStatusViaRest(
2663
+ instance,
2664
+ serviceName,
2665
+ serviceInstanceId
2666
+ );
2667
+ if (directReport) {
2668
+ if (!this.applyRuntimeStatusReport(directReport)) {
2669
+ throw new Error(
2670
+ `No tracked instance for runtime fallback ${serviceName}/${serviceInstanceId}`
2671
+ );
2672
+ }
2673
+ this.lastHeartbeatAtByInstance.set(serviceInstanceId, Date.now());
2674
+ this.missedHeartbeatsByInstance.set(serviceInstanceId, 0);
2675
+ return {
2676
+ report: directReport,
2677
+ inquiryMeta: {
2678
+ inquiry: META_RUNTIME_STATUS_INTENT,
2679
+ responded: 1,
2680
+ failed: 0,
2681
+ timedOut: 0,
2682
+ pending: 0,
2683
+ directStatusCheck: true
2684
+ }
2685
+ };
2686
+ }
2687
+ }
2621
2688
  const inquiryResult = await CadenzaService.inquire(
2622
2689
  META_RUNTIME_STATUS_INTENT,
2623
2690
  {
2624
2691
  targetServiceName: serviceName,
2625
2692
  targetServiceInstanceId: serviceInstanceId,
2626
- detailLevel: options.detailLevel ?? "minimal"
2693
+ detailLevel: options.detailLevel ?? "minimal",
2694
+ __preferredTransportProtocol: "rest"
2627
2695
  },
2628
2696
  {
2629
2697
  overallTimeoutMs: options.overallTimeoutMs ?? this.runtimeStatusFallbackTimeoutMs,
@@ -2653,6 +2721,46 @@ var ServiceRegistry = class _ServiceRegistry {
2653
2721
  inquiryMeta: inquiryResult.__inquiryMeta ?? {}
2654
2722
  };
2655
2723
  }
2724
+ async requestRuntimeStatusViaRest(instance, serviceName, serviceInstanceId) {
2725
+ if (typeof globalThis.fetch !== "function") {
2726
+ return null;
2727
+ }
2728
+ const transport = this.getRouteableTransport(instance, "rest");
2729
+ if (!transport) {
2730
+ return null;
2731
+ }
2732
+ const controller = typeof AbortController === "function" ? new AbortController() : null;
2733
+ const timeoutId = controller ? setTimeout(() => controller.abort(), this.runtimeStatusFallbackTimeoutMs) : null;
2734
+ try {
2735
+ const response = await globalThis.fetch(`${transport.origin}/status`, {
2736
+ method: "GET",
2737
+ signal: controller?.signal
2738
+ });
2739
+ if ("ok" in response && response.ok === false) {
2740
+ return null;
2741
+ }
2742
+ const payload = typeof response.json === "function" ? await response.json() : response;
2743
+ const report = this.normalizeRuntimeStatusReport({
2744
+ ...payload,
2745
+ serviceTransportId: payload?.serviceTransportId ?? transport.uuid,
2746
+ serviceOrigin: payload?.serviceOrigin ?? transport.origin,
2747
+ transportProtocols: payload?.transportProtocols ?? transport.protocols
2748
+ });
2749
+ if (!report) {
2750
+ return null;
2751
+ }
2752
+ if (report.serviceName !== serviceName || report.serviceInstanceId !== serviceInstanceId) {
2753
+ return null;
2754
+ }
2755
+ return report;
2756
+ } catch {
2757
+ return null;
2758
+ } finally {
2759
+ if (timeoutId) {
2760
+ clearTimeout(timeoutId);
2761
+ }
2762
+ }
2763
+ }
2656
2764
  evaluateDependencyReadinessDetail(serviceName, serviceInstanceId, now = Date.now()) {
2657
2765
  const instance = this.getInstance(serviceName, serviceInstanceId);
2658
2766
  const missedHeartbeats = this.getHeartbeatMisses(serviceInstanceId, now);
@@ -3772,13 +3880,7 @@ var SocketController = class _SocketController {
3772
3880
  ws.on(
3773
3881
  "status_check",
3774
3882
  (ctx, callback) => {
3775
- CadenzaService.createEphemeralMetaTask(
3776
- "Resolve status check",
3777
- callback,
3778
- "Resolves a status check request",
3779
- { register: false }
3780
- ).doAfter(CadenzaService.serviceRegistry.getStatusTask);
3781
- CadenzaService.emit("meta.socket.status_check_requested", ctx);
3883
+ callback(CadenzaService.serviceRegistry.resolveLocalStatusCheck(ctx));
3782
3884
  }
3783
3885
  );
3784
3886
  ws.on("disconnect", () => {