service-bridge 1.1.0-dev.27 → 1.1.1-dev.29

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.
Files changed (2) hide show
  1. package/dist/index.js +92 -81
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -591,8 +591,9 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
591
591
  const fnAliasMap = new Map;
592
592
  const functionChannels = new Map;
593
593
  let isOnline = false;
594
+ let isFlushing = false;
594
595
  let stopped = false;
595
- let onlineRestoreTimer = null;
596
+ let isWatchingChannel = false;
596
597
  const offlineQueue = [];
597
598
  let workerServer = null;
598
599
  let workerSessionStream = null;
@@ -900,36 +901,43 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
900
901
  }
901
902
  }
902
903
  _controlReady.then(() => {
903
- isOnline = true;
904
- flushQueue().catch((err) => {
905
- if (isConnectionError(err))
906
- scheduleOnlineRestore();
907
- else
908
- reportSDKError("flush-on-ready", err);
909
- });
904
+ watchChannelConnectivity();
910
905
  }).catch(() => {});
911
- function scheduleOnlineRestore() {
912
- if (stopped || isOnline || onlineRestoreTimer)
906
+ function watchChannelConnectivity() {
907
+ isWatchingChannel = false;
908
+ if (stopped)
913
909
  return;
914
- onlineRestoreTimer = setTimeout(() => {
915
- onlineRestoreTimer = null;
916
- if (!stopped) {
910
+ const channel = stub.getChannel();
911
+ const state = channel.getConnectivityState(true);
912
+ if (state === grpc.connectivityState.READY) {
913
+ if (!isOnline) {
917
914
  isOnline = true;
918
915
  const queueLen = offlineQueue.length;
919
916
  console.info(`[servicebridge] reconnected to runtime${queueLen > 0 ? ` — flushing ${queueLen} queued operation(s)` : ""}`);
920
- flushQueue().catch((err) => {
921
- if (isConnectionError(err))
922
- scheduleOnlineRestore();
923
- else
924
- reportSDKError("flush-on-restore", err);
917
+ flushQueue().then(() => {
918
+ if (isOnline)
919
+ scheduleNextHeartbeat(100);
920
+ }).catch((err) => {
921
+ reportSDKError("flush-on-restore", err);
925
922
  });
926
- scheduleNextHeartbeat(100);
927
923
  }
928
- }, 2000);
924
+ } else if (state === grpc.connectivityState.TRANSIENT_FAILURE) {
925
+ if (isOnline) {
926
+ isOnline = false;
927
+ console.warn("[servicebridge] lost connection to runtime — entering offline mode");
928
+ }
929
+ }
930
+ isWatchingChannel = true;
931
+ channel.watchConnectivityState(state, Infinity, watchChannelConnectivity);
932
+ }
933
+ function ensureChannelWatch() {
934
+ if (!isWatchingChannel && !stopped) {
935
+ watchChannelConnectivity();
936
+ }
929
937
  }
930
938
  function isConnectionError(e) {
931
939
  const code = e?.code;
932
- return code === grpc.status.UNAVAILABLE || code === grpc.status.UNKNOWN || code === grpc.status.DEADLINE_EXCEEDED || code === grpc.status.RESOURCE_EXHAUSTED || code === grpc.status.INTERNAL;
940
+ return code === grpc.status.UNAVAILABLE || code === grpc.status.UNKNOWN;
933
941
  }
934
942
  function normalizeUnknownErrorMessage(error) {
935
943
  return error instanceof Error ? error.message : String(error);
@@ -962,7 +970,7 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
962
970
  console.warn("[servicebridge] lost connection to runtime — entering offline mode");
963
971
  }
964
972
  isOnline = false;
965
- scheduleOnlineRestore();
973
+ ensureChannelWatch();
966
974
  enqueueOffline({
967
975
  type: "reportCallStart",
968
976
  traceId,
@@ -1013,7 +1021,7 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
1013
1021
  console.warn("[servicebridge] lost connection to runtime — entering offline mode");
1014
1022
  }
1015
1023
  isOnline = false;
1016
- scheduleOnlineRestore();
1024
+ ensureChannelWatch();
1017
1025
  enqueueOffline({
1018
1026
  type: "reportCall",
1019
1027
  traceId,
@@ -1033,66 +1041,71 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
1033
1041
  });
1034
1042
  }
1035
1043
  async function flushQueue() {
1036
- if (offlineQueue.length === 0)
1044
+ if (isFlushing || offlineQueue.length === 0)
1037
1045
  return;
1046
+ isFlushing = true;
1038
1047
  const snapshot = offlineQueue.slice();
1039
1048
  let flushed = 0;
1040
- while (offlineQueue.length > 0 && isOnline) {
1041
- const op = offlineQueue[0];
1042
- try {
1043
- if (op.type === "event") {
1044
- await new Promise((res, rej) => {
1045
- stub.Publish({
1046
- topic: op.topic,
1047
- payload: toJsonBuffer(op.payload),
1048
- headers: toWireStringMap(op.opts?.headers),
1049
- trace_id: op.opts?.traceId ?? "",
1050
- parent_span_id: op.opts?.parentSpanId ?? "",
1051
- producer_service: service,
1052
- idempotency_key: op.opts?.idempotencyKey ?? ""
1053
- }, meta, unaryDeadlineOptions(), (err) => err ? rej(err) : res());
1054
- });
1055
- } else if (op.type === "job") {
1056
- await new Promise((res, rej) => {
1057
- stub.RegisterJob({
1058
- cron_expr: op.opts.cron ?? "",
1059
- timezone: op.opts.timezone ?? "UTC",
1060
- misfire_policy: op.opts.misfire ?? "fire_now",
1061
- target_type: op.opts.via ?? "rpc",
1062
- target_ref: op.target,
1063
- delay_ms: op.opts.delay ?? 0,
1064
- service_name: service,
1065
- retry_policy_json: op.opts.retryPolicyJson ?? "{}"
1066
- }, meta, unaryDeadlineOptions(), (err) => err ? rej(err) : res());
1067
- });
1068
- } else if (op.type === "workflow") {
1069
- await new Promise((res, rej) => {
1070
- stub.RegisterWorkflow({
1071
- name: op.name,
1072
- definition: JSON.stringify(op.steps),
1073
- opts: op.opts ? JSON.stringify(op.opts) : "{}",
1074
- service_name: service
1075
- }, meta, unaryDeadlineOptions(), (err) => err ? rej(err) : res());
1076
- });
1077
- } else if (op.type === "reportCallStart") {
1078
- await sendReportCallStart({ ...op });
1079
- } else if (op.type === "reportCall") {
1080
- await sendReportCall(op);
1081
- }
1082
- offlineQueue.shift();
1083
- flushed++;
1084
- } catch (err) {
1085
- if (isConnectionError(err)) {
1086
- if (isOnline) {
1087
- console.warn("[servicebridge] lost connection to runtime — entering offline mode");
1049
+ try {
1050
+ while (offlineQueue.length > 0 && isOnline) {
1051
+ const op = offlineQueue[0];
1052
+ try {
1053
+ if (op.type === "event") {
1054
+ await new Promise((res, rej) => {
1055
+ stub.Publish({
1056
+ topic: op.topic,
1057
+ payload: toJsonBuffer(op.payload),
1058
+ headers: toWireStringMap(op.opts?.headers),
1059
+ trace_id: op.opts?.traceId ?? "",
1060
+ parent_span_id: op.opts?.parentSpanId ?? "",
1061
+ producer_service: service,
1062
+ idempotency_key: op.opts?.idempotencyKey ?? ""
1063
+ }, meta, unaryDeadlineOptions(), (err) => err ? rej(err) : res());
1064
+ });
1065
+ } else if (op.type === "job") {
1066
+ await new Promise((res, rej) => {
1067
+ stub.RegisterJob({
1068
+ cron_expr: op.opts.cron ?? "",
1069
+ timezone: op.opts.timezone ?? "UTC",
1070
+ misfire_policy: op.opts.misfire ?? "fire_now",
1071
+ target_type: op.opts.via ?? "rpc",
1072
+ target_ref: op.target,
1073
+ delay_ms: op.opts.delay ?? 0,
1074
+ service_name: service,
1075
+ retry_policy_json: op.opts.retryPolicyJson ?? "{}"
1076
+ }, meta, unaryDeadlineOptions(), (err) => err ? rej(err) : res());
1077
+ });
1078
+ } else if (op.type === "workflow") {
1079
+ await new Promise((res, rej) => {
1080
+ stub.RegisterWorkflow({
1081
+ name: op.name,
1082
+ definition: JSON.stringify(op.steps),
1083
+ opts: op.opts ? JSON.stringify(op.opts) : "{}",
1084
+ service_name: service
1085
+ }, meta, unaryDeadlineOptions(), (err) => err ? rej(err) : res());
1086
+ });
1087
+ } else if (op.type === "reportCallStart") {
1088
+ await sendReportCallStart({ ...op });
1089
+ } else if (op.type === "reportCall") {
1090
+ await sendReportCall(op);
1091
+ }
1092
+ offlineQueue.shift();
1093
+ flushed++;
1094
+ } catch (err) {
1095
+ if (isConnectionError(err)) {
1096
+ if (isOnline) {
1097
+ console.warn("[servicebridge] lost connection to runtime — entering offline mode");
1098
+ }
1099
+ isOnline = false;
1100
+ ensureChannelWatch();
1101
+ break;
1088
1102
  }
1089
- isOnline = false;
1090
- scheduleOnlineRestore();
1091
- break;
1103
+ reportSDKError("flush-offline-queue", err);
1104
+ offlineQueue.shift();
1092
1105
  }
1093
- reportSDKError("flush-offline-queue", err);
1094
- break;
1095
1106
  }
1107
+ } finally {
1108
+ isFlushing = false;
1096
1109
  }
1097
1110
  if (flushed > 0) {
1098
1111
  const remaining = offlineQueue.length;
@@ -1256,7 +1269,7 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
1256
1269
  console.warn("[servicebridge] lost connection to runtime — entering offline mode");
1257
1270
  }
1258
1271
  isOnline = false;
1259
- scheduleOnlineRestore();
1272
+ ensureChannelWatch();
1260
1273
  } else if (isRegistryResyncRequiredError(err)) {
1261
1274
  await syncRegistrations("heartbeat-resync");
1262
1275
  } else {
@@ -1787,11 +1800,9 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
1787
1800
  stop() {
1788
1801
  stopped = true;
1789
1802
  isOnline = false;
1790
- if (onlineRestoreTimer)
1791
- clearTimeout(onlineRestoreTimer);
1803
+ isWatchingChannel = false;
1792
1804
  if (heartbeatTimer)
1793
1805
  clearTimeout(heartbeatTimer);
1794
- onlineRestoreTimer = null;
1795
1806
  heartbeatTimer = null;
1796
1807
  registeredGroups.clear();
1797
1808
  closeWorkerSession();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "service-bridge",
3
- "version": "1.1.0-dev.27",
3
+ "version": "1.1.1-dev.29",
4
4
  "type": "module",
5
5
  "description": "ServiceBridge SDK for Node.js — production-ready RPC, durable events, workflows, jobs, and distributed tracing. One Go runtime + PostgreSQL replaces Istio, RabbitMQ, Temporal, and Jaeger.",
6
6
  "keywords": [