service-bridge 1.0.17 → 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.
- package/README.md +10 -13
- package/dist/index.js +399 -101
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,7 +97,6 @@ import { servicebridge } from "service-bridge";
|
|
|
97
97
|
const sb = servicebridge(
|
|
98
98
|
process.env.SERVICEBRIDGE_URL ?? "localhost:14445",
|
|
99
99
|
process.env.SERVICEBRIDGE_SERVICE_KEY!,
|
|
100
|
-
"payments",
|
|
101
100
|
);
|
|
102
101
|
|
|
103
102
|
sb.handleRpc("charge", async (payload: { orderId: string; amount: number }) => {
|
|
@@ -115,7 +114,6 @@ import { servicebridge } from "service-bridge";
|
|
|
115
114
|
const sb = servicebridge(
|
|
116
115
|
process.env.SERVICEBRIDGE_URL ?? "localhost:14445",
|
|
117
116
|
process.env.SERVICEBRIDGE_SERVICE_KEY!,
|
|
118
|
-
"orders",
|
|
119
117
|
);
|
|
120
118
|
|
|
121
119
|
const result = await sb.rpc<{ ok: boolean; txId: string }>("payments/charge", {
|
|
@@ -153,7 +151,7 @@ import { servicebridge } from "service-bridge";
|
|
|
153
151
|
|
|
154
152
|
// --- Payments service (worker) ---
|
|
155
153
|
|
|
156
|
-
const payments = servicebridge("localhost:14445", process.env.SERVICEBRIDGE_SERVICE_KEY
|
|
154
|
+
const payments = servicebridge("localhost:14445", process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
157
155
|
|
|
158
156
|
payments.handleRpc("charge", async (payload: { orderId: string; amount: number }, ctx) => {
|
|
159
157
|
await ctx?.stream.write({ status: "charging", orderId: payload.orderId }, "progress");
|
|
@@ -170,7 +168,7 @@ await payments.serve({ host: "localhost" });
|
|
|
170
168
|
```ts
|
|
171
169
|
// --- Orders service (caller + event publisher) ---
|
|
172
170
|
|
|
173
|
-
const orders = servicebridge("localhost:14445", process.env.SERVICEBRIDGE_SERVICE_KEY
|
|
171
|
+
const orders = servicebridge("localhost:14445", process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
174
172
|
|
|
175
173
|
// Call payments, then publish event
|
|
176
174
|
const charge = await orders.rpc<{ ok: boolean; txId: string }>("payments/charge", {
|
|
@@ -190,7 +188,7 @@ await orders.event("orders.completed", {
|
|
|
190
188
|
```ts
|
|
191
189
|
// --- Notifications service (event consumer) ---
|
|
192
190
|
|
|
193
|
-
const notifications = servicebridge("localhost:14445", process.env.SERVICEBRIDGE_SERVICE_KEY
|
|
191
|
+
const notifications = servicebridge("localhost:14445", process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
194
192
|
|
|
195
193
|
notifications.handleEvent("orders.*", async (payload, ctx) => {
|
|
196
194
|
const body = payload as { orderId: string; txId: string };
|
|
@@ -273,18 +271,19 @@ across all three SDKs. Parity differences are naming-only (language idioms):
|
|
|
273
271
|
- Handler hints: timeout/retryable/concurrency/prefetch are advisory in all SDKs
|
|
274
272
|
- Shared `serve()` fields across SDKs: host, max in-flight, instance ID, weight, and per-serve TLS override
|
|
275
273
|
|
|
276
|
-
### `servicebridge(url, serviceKey,
|
|
274
|
+
### `servicebridge(url, serviceKey, opts?)`
|
|
277
275
|
|
|
278
276
|
```ts
|
|
279
277
|
function servicebridge(
|
|
280
278
|
url: string,
|
|
281
279
|
serviceKey: string,
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
serviceOrOpts?: string | ServiceBridgeOpts,
|
|
281
|
+
maybeGlobalOpts?: ServiceBridgeOpts,
|
|
284
282
|
): ServiceBridgeService
|
|
285
283
|
```
|
|
286
284
|
|
|
287
285
|
Creates an SDK client instance.
|
|
286
|
+
Service identity is resolved by the runtime from `serviceKey`; passing a third `service` argument is legacy-only.
|
|
288
287
|
|
|
289
288
|
`ServiceBridgeOpts`:
|
|
290
289
|
|
|
@@ -540,7 +539,7 @@ Registers an event consumer handler. Chainable.
|
|
|
540
539
|
|
|
541
540
|
| Option | Type | Description |
|
|
542
541
|
|---|---|---|
|
|
543
|
-
| `groupName` | `string` | Consumer group name. Default: `<service>.<pattern>`. |
|
|
542
|
+
| `groupName` | `string` | Consumer group name. Default: `<service-key-id>.<pattern>`. |
|
|
544
543
|
| `concurrency` | `number` | Advisory concurrency hint (currently not hard-enforced). |
|
|
545
544
|
| `prefetch` | `number` | Advisory prefetch hint (currently not hard-enforced). |
|
|
546
545
|
| `retryPolicyJson` | `string` | Retry policy JSON string. |
|
|
@@ -766,7 +765,7 @@ import express from "express";
|
|
|
766
765
|
import { servicebridge } from "service-bridge";
|
|
767
766
|
import { servicebridgeMiddleware, registerExpressRoutes } from "service-bridge/express";
|
|
768
767
|
|
|
769
|
-
const sb = servicebridge(process.env.SERVICEBRIDGE_URL!, process.env.SERVICEBRIDGE_SERVICE_KEY
|
|
768
|
+
const sb = servicebridge(process.env.SERVICEBRIDGE_URL!, process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
770
769
|
const app = express();
|
|
771
770
|
|
|
772
771
|
app.use(servicebridgeMiddleware({
|
|
@@ -822,7 +821,7 @@ import Fastify from "fastify";
|
|
|
822
821
|
import { servicebridge } from "service-bridge";
|
|
823
822
|
import { servicebridgePlugin, wrapHandler } from "service-bridge/fastify";
|
|
824
823
|
|
|
825
|
-
const sb = servicebridge(process.env.SERVICEBRIDGE_URL!, process.env.SERVICEBRIDGE_SERVICE_KEY
|
|
824
|
+
const sb = servicebridge(process.env.SERVICEBRIDGE_URL!, process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
826
825
|
const app = Fastify();
|
|
827
826
|
|
|
828
827
|
await app.register(servicebridgePlugin, {
|
|
@@ -895,13 +894,11 @@ The SDK requires values you pass into `servicebridge(...)`. Common setup:
|
|
|
895
894
|
|---|---|---|---|
|
|
896
895
|
| `SERVICEBRIDGE_URL` | yes | `localhost:14445` | gRPC control plane URL |
|
|
897
896
|
| `SERVICEBRIDGE_SERVICE_KEY` | yes | `sbv2.<id>.<secret>.<ca>` | Service authentication key (sbv2 only) |
|
|
898
|
-
| `SERVICEBRIDGE_SERVICE` | yes (worker mode) | `orders` | Service name in registry |
|
|
899
897
|
|
|
900
898
|
```ts
|
|
901
899
|
const sb = servicebridge(
|
|
902
900
|
process.env.SERVICEBRIDGE_URL ?? "localhost:14445",
|
|
903
901
|
process.env.SERVICEBRIDGE_SERVICE_KEY!,
|
|
904
|
-
process.env.SERVICEBRIDGE_SERVICE ?? "orders",
|
|
905
902
|
);
|
|
906
903
|
```
|
|
907
904
|
|
package/dist/index.js
CHANGED
|
@@ -90,7 +90,27 @@ class ServiceBridgeError extends Error {
|
|
|
90
90
|
this.operation = opts.operation;
|
|
91
91
|
this.severity = opts.severity;
|
|
92
92
|
this.retryable = opts.severity === "retriable";
|
|
93
|
-
this
|
|
93
|
+
Object.defineProperty(this, "cause", {
|
|
94
|
+
value: opts.cause,
|
|
95
|
+
writable: true,
|
|
96
|
+
enumerable: false,
|
|
97
|
+
configurable: true
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
toString() {
|
|
101
|
+
return `ServiceBridgeError [${this.severity}]: ${this.message} (${this.operation})`;
|
|
102
|
+
}
|
|
103
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
104
|
+
const lines = [
|
|
105
|
+
`ServiceBridgeError [${this.severity}]`,
|
|
106
|
+
` operation: ${this.operation}`,
|
|
107
|
+
` message: ${this.message}`
|
|
108
|
+
];
|
|
109
|
+
if (this.code !== undefined) {
|
|
110
|
+
lines.push(` code: ${this.code}`);
|
|
111
|
+
}
|
|
112
|
+
return lines.join(`
|
|
113
|
+
`);
|
|
94
114
|
}
|
|
95
115
|
}
|
|
96
116
|
function containsValue(values, value) {
|
|
@@ -214,7 +234,14 @@ function normalizeServiceError(err, operation, component = "control-plane") {
|
|
|
214
234
|
}
|
|
215
235
|
const grpcErr = err;
|
|
216
236
|
const code = typeof grpcErr?.code === "number" ? grpcErr.code : undefined;
|
|
217
|
-
const
|
|
237
|
+
const details = typeof grpcErr?.details === "string" && grpcErr.details.trim() ? grpcErr.details.trim() : undefined;
|
|
238
|
+
const rawMsg = (() => {
|
|
239
|
+
const m = typeof grpcErr?.message === "string" && grpcErr.message ? grpcErr.message : err instanceof Error ? err.message : String(err);
|
|
240
|
+
return m.replace(/^\d+\s+[A-Z_]+:\s*/, "").trim() || m;
|
|
241
|
+
})();
|
|
242
|
+
const base = friendlyGrpcMessage(code, details ?? rawMsg);
|
|
243
|
+
const appendDetails = details && (code === grpc.status.INTERNAL || code === grpc.status.UNKNOWN || code === undefined);
|
|
244
|
+
const message = appendDetails ? `${base}: ${details}` : base;
|
|
218
245
|
return new ServiceBridgeError({
|
|
219
246
|
message,
|
|
220
247
|
code,
|
|
@@ -224,13 +251,74 @@ function normalizeServiceError(err, operation, component = "control-plane") {
|
|
|
224
251
|
cause: err
|
|
225
252
|
});
|
|
226
253
|
}
|
|
254
|
+
function friendlyGrpcMessage(code, rawMessage) {
|
|
255
|
+
if (code === undefined)
|
|
256
|
+
return rawMessage || "unknown error";
|
|
257
|
+
switch (code) {
|
|
258
|
+
case grpc.status.UNAVAILABLE:
|
|
259
|
+
return "control plane unavailable";
|
|
260
|
+
case grpc.status.DEADLINE_EXCEEDED:
|
|
261
|
+
return "request timed out";
|
|
262
|
+
case grpc.status.UNAUTHENTICATED:
|
|
263
|
+
return "authentication failed — check service key";
|
|
264
|
+
case grpc.status.PERMISSION_DENIED:
|
|
265
|
+
return "permission denied — check service key";
|
|
266
|
+
case grpc.status.RESOURCE_EXHAUSTED:
|
|
267
|
+
return "rate limit exceeded";
|
|
268
|
+
case grpc.status.NOT_FOUND:
|
|
269
|
+
return "resource not found";
|
|
270
|
+
case grpc.status.INTERNAL:
|
|
271
|
+
return "internal server error";
|
|
272
|
+
case grpc.status.FAILED_PRECONDITION:
|
|
273
|
+
return "precondition failed";
|
|
274
|
+
case grpc.status.CANCELLED:
|
|
275
|
+
return "request cancelled";
|
|
276
|
+
case grpc.status.UNIMPLEMENTED:
|
|
277
|
+
return "operation not supported by control plane";
|
|
278
|
+
case grpc.status.UNKNOWN:
|
|
279
|
+
return "unknown error from control plane";
|
|
280
|
+
default:
|
|
281
|
+
return rawMessage || "unknown error";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function friendlySDKMessage(operation, err) {
|
|
285
|
+
const base = err.message;
|
|
286
|
+
switch (operation) {
|
|
287
|
+
case "open-worker-session":
|
|
288
|
+
case "worker-session":
|
|
289
|
+
return err.severity === "retriable" ? `${base} — waiting for reconnect` : `worker session error: ${base}`;
|
|
290
|
+
case "worker-session-end":
|
|
291
|
+
return `worker session cleanup: ${base}`;
|
|
292
|
+
case "worker-session-command":
|
|
293
|
+
return `worker command error: ${base}`;
|
|
294
|
+
case "heartbeat":
|
|
295
|
+
return `heartbeat failed: ${base}`;
|
|
296
|
+
case "http-heartbeat":
|
|
297
|
+
return `HTTP heartbeat failed: ${base}`;
|
|
298
|
+
case "flush-offline-queue":
|
|
299
|
+
return `offline queue flush failed: ${base}`;
|
|
300
|
+
case "flush-on-ready":
|
|
301
|
+
case "flush-on-restore":
|
|
302
|
+
return `offline queue flush failed: ${base}`;
|
|
303
|
+
case "report-call-start":
|
|
304
|
+
case "report-call":
|
|
305
|
+
return `call report failed: ${base}`;
|
|
306
|
+
case "worker-force-shutdown":
|
|
307
|
+
case "worker-try-shutdown":
|
|
308
|
+
return `worker shutdown error: ${base}`;
|
|
309
|
+
case "close-control-plane-client":
|
|
310
|
+
return `control plane disconnect: ${base}`;
|
|
311
|
+
default:
|
|
312
|
+
return `${operation}: ${base}`;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
227
315
|
function reportSDKError(operation, err, component = "sdk") {
|
|
228
316
|
const normalized = normalizeServiceError(err, operation, component);
|
|
229
|
-
const
|
|
317
|
+
const friendly = friendlySDKMessage(operation, normalized);
|
|
230
318
|
if (normalized.severity === "fatal") {
|
|
231
|
-
console.error(
|
|
319
|
+
console.error(`[servicebridge] ${friendly}`);
|
|
232
320
|
} else {
|
|
233
|
-
console.warn(
|
|
321
|
+
console.warn(`[servicebridge] ${friendly}`);
|
|
234
322
|
}
|
|
235
323
|
return normalized;
|
|
236
324
|
}
|
|
@@ -260,6 +348,101 @@ function workerChannelOptions(tlsOpts) {
|
|
|
260
348
|
function workerClientCredentials(tlsOpts) {
|
|
261
349
|
return grpc.credentials.createSsl(toPemBuffer(tlsOpts?.caCert), toPemBuffer(tlsOpts?.key), toPemBuffer(tlsOpts?.cert));
|
|
262
350
|
}
|
|
351
|
+
var _wireStringMapMode = null;
|
|
352
|
+
function detectWireStringMapMode() {
|
|
353
|
+
const publishMethod = servicebridgeProto.ServiceBridge.service?.Publish;
|
|
354
|
+
const serialize = publishMethod?.requestSerialize;
|
|
355
|
+
if (typeof serialize !== "function") {
|
|
356
|
+
return "entries";
|
|
357
|
+
}
|
|
358
|
+
const baseReq = {
|
|
359
|
+
topic: "__probe__",
|
|
360
|
+
payload: Buffer.alloc(0),
|
|
361
|
+
trace_id: "",
|
|
362
|
+
parent_span_id: "",
|
|
363
|
+
producer_service: "",
|
|
364
|
+
idempotency_key: ""
|
|
365
|
+
};
|
|
366
|
+
try {
|
|
367
|
+
serialize({
|
|
368
|
+
...baseReq,
|
|
369
|
+
headers: { probe: "1" }
|
|
370
|
+
});
|
|
371
|
+
return "object";
|
|
372
|
+
} catch {
|
|
373
|
+
try {
|
|
374
|
+
serialize({
|
|
375
|
+
...baseReq,
|
|
376
|
+
headers: [{ key: "probe", value: "1" }]
|
|
377
|
+
});
|
|
378
|
+
return "entries";
|
|
379
|
+
} catch {
|
|
380
|
+
return "entries";
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function wireStringMapMode() {
|
|
385
|
+
if (_wireStringMapMode === null) {
|
|
386
|
+
_wireStringMapMode = detectWireStringMapMode();
|
|
387
|
+
}
|
|
388
|
+
return _wireStringMapMode;
|
|
389
|
+
}
|
|
390
|
+
function normalizeStringMap(raw) {
|
|
391
|
+
const normalized = {};
|
|
392
|
+
if (!raw)
|
|
393
|
+
return normalized;
|
|
394
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
395
|
+
const k = key.trim();
|
|
396
|
+
if (!k)
|
|
397
|
+
continue;
|
|
398
|
+
if (typeof value === "string") {
|
|
399
|
+
normalized[k] = value;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (value == null) {
|
|
403
|
+
normalized[k] = "";
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
407
|
+
normalized[k] = String(value);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
normalized[k] = JSON.stringify(value);
|
|
412
|
+
} catch {
|
|
413
|
+
normalized[k] = String(value);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return normalized;
|
|
417
|
+
}
|
|
418
|
+
function toWireStringMap(raw) {
|
|
419
|
+
const normalized = normalizeStringMap(raw);
|
|
420
|
+
if (wireStringMapMode() === "entries") {
|
|
421
|
+
return Object.entries(normalized).map(([key, value]) => ({
|
|
422
|
+
key,
|
|
423
|
+
value
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
426
|
+
return normalized;
|
|
427
|
+
}
|
|
428
|
+
function fromWireStringMap(raw) {
|
|
429
|
+
if (Array.isArray(raw)) {
|
|
430
|
+
const out = {};
|
|
431
|
+
for (const entry of raw) {
|
|
432
|
+
if (typeof entry?.key !== "string")
|
|
433
|
+
continue;
|
|
434
|
+
const key = entry.key.trim();
|
|
435
|
+
if (!key)
|
|
436
|
+
continue;
|
|
437
|
+
out[key] = typeof entry.value === "string" ? entry.value : entry.value == null ? "" : String(entry.value);
|
|
438
|
+
}
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
441
|
+
if (raw && typeof raw === "object") {
|
|
442
|
+
return normalizeStringMap(raw);
|
|
443
|
+
}
|
|
444
|
+
return {};
|
|
445
|
+
}
|
|
263
446
|
async function provisionWorkerTLS(controlClient, md, serviceName, extraIps) {
|
|
264
447
|
const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", {
|
|
265
448
|
namedCurve: "P-256",
|
|
@@ -296,8 +479,16 @@ function ensureSbResolverRegistered() {
|
|
|
296
479
|
listener;
|
|
297
480
|
refreshTimer = null;
|
|
298
481
|
constructor(target, listener) {
|
|
299
|
-
|
|
300
|
-
|
|
482
|
+
const authority = (target.authority ?? "").trim();
|
|
483
|
+
const normalizedPath = (target.path ?? "").replace(/^\/+/, "");
|
|
484
|
+
if (authority && authority !== "localhost" && _sbContexts.has(authority)) {
|
|
485
|
+
this.clientId = authority;
|
|
486
|
+
this.canonicalName = normalizedPath;
|
|
487
|
+
} else {
|
|
488
|
+
const [clientIdFromPath = "", ...canonicalParts] = normalizedPath.split("/");
|
|
489
|
+
this.clientId = clientIdFromPath.trim();
|
|
490
|
+
this.canonicalName = canonicalParts.join("/").trim();
|
|
491
|
+
}
|
|
301
492
|
this.listener = listener;
|
|
302
493
|
this.init().catch(() => {});
|
|
303
494
|
}
|
|
@@ -309,7 +500,7 @@ function ensureSbResolverRegistered() {
|
|
|
309
500
|
error: {
|
|
310
501
|
code: 14,
|
|
311
502
|
details: `No SB context for clientId=${this.clientId}`,
|
|
312
|
-
metadata:
|
|
503
|
+
metadata: new grpc.Metadata
|
|
313
504
|
}
|
|
314
505
|
}, {}, null, "sb-resolver");
|
|
315
506
|
return;
|
|
@@ -336,7 +527,7 @@ function ensureSbResolverRegistered() {
|
|
|
336
527
|
error: {
|
|
337
528
|
code: 14,
|
|
338
529
|
details: `No live endpoints for ${this.canonicalName}`,
|
|
339
|
-
metadata:
|
|
530
|
+
metadata: new grpc.Metadata
|
|
340
531
|
}
|
|
341
532
|
}, {}, null, "sb-resolver");
|
|
342
533
|
return;
|
|
@@ -355,7 +546,11 @@ function ensureSbResolverRegistered() {
|
|
|
355
546
|
}
|
|
356
547
|
registerResolver("sb", SbResolver);
|
|
357
548
|
}
|
|
358
|
-
function servicebridge(url, serviceKey,
|
|
549
|
+
function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}) {
|
|
550
|
+
const service = typeof serviceOrOpts === "string" ? serviceOrOpts.trim() : "";
|
|
551
|
+
const globalOpts = typeof serviceOrOpts === "string" ? maybeGlobalOpts : serviceOrOpts ?? {};
|
|
552
|
+
const parsedServiceKey = parseServiceKeyV2(serviceKey);
|
|
553
|
+
const defaultConsumerPrefix = service || parsedServiceKey.keyId || "consumer";
|
|
359
554
|
const meta = new grpc.Metadata;
|
|
360
555
|
meta.add("x-service-key", serviceKey);
|
|
361
556
|
const rawUrl = url.trim();
|
|
@@ -396,8 +591,9 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
396
591
|
const fnAliasMap = new Map;
|
|
397
592
|
const functionChannels = new Map;
|
|
398
593
|
let isOnline = false;
|
|
594
|
+
let isFlushing = false;
|
|
399
595
|
let stopped = false;
|
|
400
|
-
let
|
|
596
|
+
let isWatchingChannel = false;
|
|
401
597
|
const offlineQueue = [];
|
|
402
598
|
let workerServer = null;
|
|
403
599
|
let workerSessionStream = null;
|
|
@@ -444,7 +640,30 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
444
640
|
md.add("x-caller-service", service);
|
|
445
641
|
return md;
|
|
446
642
|
}
|
|
643
|
+
function sessionSend(msg) {
|
|
644
|
+
if (!workerSessionStream)
|
|
645
|
+
return false;
|
|
646
|
+
try {
|
|
647
|
+
workerSessionStream.write(msg);
|
|
648
|
+
return true;
|
|
649
|
+
} catch {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
447
653
|
async function sendReportCallStart(op) {
|
|
654
|
+
if (sessionSend({
|
|
655
|
+
telemetry_start: {
|
|
656
|
+
trace_id: op.traceId,
|
|
657
|
+
span_id: op.spanId,
|
|
658
|
+
parent_span_id: op.parentSpanId,
|
|
659
|
+
fn: op.fn,
|
|
660
|
+
started_at: String(op.startedAt),
|
|
661
|
+
input: op.inputBuf,
|
|
662
|
+
attempt: op.attempt
|
|
663
|
+
}
|
|
664
|
+
})) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
448
667
|
await new Promise((resolve, reject) => {
|
|
449
668
|
stub.ReportCallStart({
|
|
450
669
|
trace_id: op.traceId,
|
|
@@ -460,6 +679,22 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
460
679
|
});
|
|
461
680
|
}
|
|
462
681
|
async function sendReportCall(op) {
|
|
682
|
+
if (sessionSend({
|
|
683
|
+
telemetry_finish: {
|
|
684
|
+
trace_id: op.traceId,
|
|
685
|
+
span_id: op.spanId,
|
|
686
|
+
fn: op.fn,
|
|
687
|
+
started_at: String(op.startedAt),
|
|
688
|
+
duration_ms: String(op.durationMs),
|
|
689
|
+
success: op.success,
|
|
690
|
+
error: op.error ?? "",
|
|
691
|
+
input: op.inputBuf,
|
|
692
|
+
output: op.outputBuf ?? Buffer.alloc(0),
|
|
693
|
+
attempt: op.attempt
|
|
694
|
+
}
|
|
695
|
+
})) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
463
698
|
await new Promise((resolve, reject) => {
|
|
464
699
|
stub.ReportCall({
|
|
465
700
|
trace_id: op.traceId,
|
|
@@ -467,7 +702,7 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
467
702
|
fn: op.fn,
|
|
468
703
|
service_name: service,
|
|
469
704
|
started_at: String(op.startedAt),
|
|
470
|
-
duration_ms: String(
|
|
705
|
+
duration_ms: String(op.durationMs),
|
|
471
706
|
success: op.success,
|
|
472
707
|
error: op.error ?? "",
|
|
473
708
|
input: op.inputBuf,
|
|
@@ -482,12 +717,17 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
482
717
|
function flushLogs() {
|
|
483
718
|
if (logBatch.length === 0)
|
|
484
719
|
return;
|
|
485
|
-
const entries = logBatch
|
|
720
|
+
const entries = logBatch.map((entry) => ({
|
|
721
|
+
...entry,
|
|
722
|
+
attributes: toWireStringMap(entry.attributes)
|
|
723
|
+
}));
|
|
486
724
|
logBatch = [];
|
|
487
725
|
if (logFlushTimer !== null) {
|
|
488
726
|
clearTimeout(logFlushTimer);
|
|
489
727
|
logFlushTimer = null;
|
|
490
728
|
}
|
|
729
|
+
if (sessionSend({ telemetry_log: { entries } }))
|
|
730
|
+
return;
|
|
491
731
|
stub.ReportLog({ entries }, meta, unaryDeadlineOptions(), () => {});
|
|
492
732
|
}
|
|
493
733
|
function pushLog(level, msg, attrs) {
|
|
@@ -614,7 +854,7 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
614
854
|
throw new Error("worker TLS is not configured");
|
|
615
855
|
}
|
|
616
856
|
const creds = workerClientCredentials(tlsOpts);
|
|
617
|
-
ch = new grpc.Channel(`sb
|
|
857
|
+
ch = new grpc.Channel(`sb://localhost/${clientId}/${canonicalName}`, creds, {
|
|
618
858
|
...workerChannelOptions(tlsOpts ?? undefined),
|
|
619
859
|
"grpc.lb_policy_name": "round_robin"
|
|
620
860
|
});
|
|
@@ -661,29 +901,39 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
661
901
|
}
|
|
662
902
|
}
|
|
663
903
|
_controlReady.then(() => {
|
|
664
|
-
|
|
665
|
-
flushQueue().catch((err) => {
|
|
666
|
-
if (isConnectionError(err))
|
|
667
|
-
scheduleOnlineRestore();
|
|
668
|
-
else
|
|
669
|
-
reportSDKError("flush-on-ready", err);
|
|
670
|
-
});
|
|
904
|
+
watchChannelConnectivity();
|
|
671
905
|
}).catch(() => {});
|
|
672
|
-
function
|
|
673
|
-
|
|
906
|
+
function watchChannelConnectivity() {
|
|
907
|
+
isWatchingChannel = false;
|
|
908
|
+
if (stopped)
|
|
674
909
|
return;
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
910
|
+
const channel = stub.getChannel();
|
|
911
|
+
const state = channel.getConnectivityState(true);
|
|
912
|
+
if (state === grpc.connectivityState.READY) {
|
|
913
|
+
if (!isOnline) {
|
|
678
914
|
isOnline = true;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
915
|
+
const queueLen = offlineQueue.length;
|
|
916
|
+
console.info(`[servicebridge] reconnected to runtime${queueLen > 0 ? ` — flushing ${queueLen} queued operation(s)` : ""}`);
|
|
917
|
+
flushQueue().then(() => {
|
|
918
|
+
if (isOnline)
|
|
919
|
+
scheduleNextHeartbeat(100);
|
|
920
|
+
}).catch((err) => {
|
|
921
|
+
reportSDKError("flush-on-restore", err);
|
|
684
922
|
});
|
|
685
923
|
}
|
|
686
|
-
}
|
|
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
|
+
}
|
|
687
937
|
}
|
|
688
938
|
function isConnectionError(e) {
|
|
689
939
|
const code = e?.code;
|
|
@@ -716,8 +966,11 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
716
966
|
attempt
|
|
717
967
|
}).catch((err) => {
|
|
718
968
|
if (isConnectionError(err)) {
|
|
969
|
+
if (isOnline) {
|
|
970
|
+
console.warn("[servicebridge] lost connection to runtime — entering offline mode");
|
|
971
|
+
}
|
|
719
972
|
isOnline = false;
|
|
720
|
-
|
|
973
|
+
ensureChannelWatch();
|
|
721
974
|
enqueueOffline({
|
|
722
975
|
type: "reportCallStart",
|
|
723
976
|
traceId,
|
|
@@ -734,6 +987,7 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
734
987
|
});
|
|
735
988
|
}
|
|
736
989
|
function reportCallAsync(traceId, spanId, fn, startedAt, inputBuf, success, attempt, outputBuf, error = "") {
|
|
990
|
+
const durationMs = computeDurationMs(startedAt);
|
|
737
991
|
if (!isOnline) {
|
|
738
992
|
enqueueOffline({
|
|
739
993
|
type: "reportCall",
|
|
@@ -741,6 +995,7 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
741
995
|
spanId,
|
|
742
996
|
fn,
|
|
743
997
|
startedAt,
|
|
998
|
+
durationMs,
|
|
744
999
|
inputBuf,
|
|
745
1000
|
success,
|
|
746
1001
|
attempt,
|
|
@@ -754,6 +1009,7 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
754
1009
|
spanId,
|
|
755
1010
|
fn,
|
|
756
1011
|
startedAt,
|
|
1012
|
+
durationMs,
|
|
757
1013
|
inputBuf,
|
|
758
1014
|
success,
|
|
759
1015
|
attempt,
|
|
@@ -761,14 +1017,18 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
761
1017
|
error
|
|
762
1018
|
}).catch((err) => {
|
|
763
1019
|
if (isConnectionError(err)) {
|
|
1020
|
+
if (isOnline) {
|
|
1021
|
+
console.warn("[servicebridge] lost connection to runtime — entering offline mode");
|
|
1022
|
+
}
|
|
764
1023
|
isOnline = false;
|
|
765
|
-
|
|
1024
|
+
ensureChannelWatch();
|
|
766
1025
|
enqueueOffline({
|
|
767
1026
|
type: "reportCall",
|
|
768
1027
|
traceId,
|
|
769
1028
|
spanId,
|
|
770
1029
|
fn,
|
|
771
1030
|
startedAt,
|
|
1031
|
+
durationMs,
|
|
772
1032
|
inputBuf,
|
|
773
1033
|
success,
|
|
774
1034
|
attempt,
|
|
@@ -781,59 +1041,84 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
781
1041
|
});
|
|
782
1042
|
}
|
|
783
1043
|
async function flushQueue() {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1044
|
+
if (isFlushing || offlineQueue.length === 0)
|
|
1045
|
+
return;
|
|
1046
|
+
isFlushing = true;
|
|
1047
|
+
const snapshot = offlineQueue.slice();
|
|
1048
|
+
let flushed = 0;
|
|
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;
|
|
1102
|
+
}
|
|
1103
|
+
reportSDKError("flush-offline-queue", err);
|
|
1104
|
+
offlineQueue.shift();
|
|
832
1105
|
}
|
|
833
|
-
reportSDKError("flush-offline-queue", err);
|
|
834
|
-
break;
|
|
835
1106
|
}
|
|
1107
|
+
} finally {
|
|
1108
|
+
isFlushing = false;
|
|
836
1109
|
}
|
|
1110
|
+
if (flushed > 0) {
|
|
1111
|
+
const remaining = offlineQueue.length;
|
|
1112
|
+
const sentOps = snapshot.slice(0, flushed);
|
|
1113
|
+
console.info(`[servicebridge] flushed ${flushed}/${snapshot.length} queued operation(s) to runtime` + ` (${formatQueueSummary(sentOps)})` + (remaining > 0 ? ` — ${remaining} still queued` : ""));
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
function formatQueueSummary(ops) {
|
|
1117
|
+
const counts = {};
|
|
1118
|
+
for (const op of ops) {
|
|
1119
|
+
counts[op.type] = (counts[op.type] ?? 0) + 1;
|
|
1120
|
+
}
|
|
1121
|
+
return Object.entries(counts).map(([type, n]) => `${n} ${type}`).join(", ");
|
|
837
1122
|
}
|
|
838
1123
|
function requirePeerCN(call, allowedCallers, isDeliver) {
|
|
839
1124
|
const peerCN = extractPeerCN(call);
|
|
@@ -858,6 +1143,17 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
858
1143
|
return;
|
|
859
1144
|
const state = serveState;
|
|
860
1145
|
const metrics = getProcessMetrics();
|
|
1146
|
+
const hbMsg = {
|
|
1147
|
+
endpoint: state.endpoint,
|
|
1148
|
+
group_names: [...registeredGroups],
|
|
1149
|
+
function_names: [...fnHandlers.keys()]
|
|
1150
|
+
};
|
|
1151
|
+
if (metrics.cpuPercent != null)
|
|
1152
|
+
hbMsg.cpu_percent = metrics.cpuPercent;
|
|
1153
|
+
if (metrics.ramMb != null)
|
|
1154
|
+
hbMsg.ram_mb = metrics.ramMb;
|
|
1155
|
+
if (sessionSend({ heartbeat: hbMsg }))
|
|
1156
|
+
return;
|
|
861
1157
|
const req = {
|
|
862
1158
|
service_name: service,
|
|
863
1159
|
instance_id: state.instanceId,
|
|
@@ -875,6 +1171,8 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
875
1171
|
});
|
|
876
1172
|
}
|
|
877
1173
|
async function sendHttpHeartbeat(instanceId) {
|
|
1174
|
+
if (sessionSend({ http_heartbeat: {} }))
|
|
1175
|
+
return;
|
|
878
1176
|
await new Promise((resolve, reject) => {
|
|
879
1177
|
stub.HeartbeatHttpEndpoint({ service_name: service, instance_id: instanceId }, meta, unaryDeadlineOptions(), (err, res) => {
|
|
880
1178
|
if (err) {
|
|
@@ -967,8 +1265,11 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
967
1265
|
}
|
|
968
1266
|
} catch (err) {
|
|
969
1267
|
if (isConnectionError(err)) {
|
|
1268
|
+
if (isOnline) {
|
|
1269
|
+
console.warn("[servicebridge] lost connection to runtime — entering offline mode");
|
|
1270
|
+
}
|
|
970
1271
|
isOnline = false;
|
|
971
|
-
|
|
1272
|
+
ensureChannelWatch();
|
|
972
1273
|
} else if (isRegistryResyncRequiredError(err)) {
|
|
973
1274
|
await syncRegistrations("heartbeat-resync");
|
|
974
1275
|
} else {
|
|
@@ -996,12 +1297,12 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
996
1297
|
const append = (data, key = "default") => {
|
|
997
1298
|
if (!isOnline || !runId)
|
|
998
1299
|
return Promise.resolve();
|
|
1300
|
+
const dataBuf = Buffer.from(JSON.stringify(data));
|
|
1301
|
+
if (sessionSend({ append_stream: { run_id: runId, key, data: dataBuf } })) {
|
|
1302
|
+
return Promise.resolve();
|
|
1303
|
+
}
|
|
999
1304
|
return new Promise((resolve, reject) => {
|
|
1000
|
-
stub.AppendStream({
|
|
1001
|
-
run_id: runId,
|
|
1002
|
-
key,
|
|
1003
|
-
data: Buffer.from(JSON.stringify(data))
|
|
1004
|
-
}, meta, unaryDeadlineOptions(), (err) => err ? reject(normalizeServiceError(err, `append-stream:${runId}`)) : resolve());
|
|
1305
|
+
stub.AppendStream({ run_id: runId, key, data: dataBuf }, meta, unaryDeadlineOptions(), (err) => err ? reject(normalizeServiceError(err, `append-stream:${runId}`)) : resolve());
|
|
1005
1306
|
});
|
|
1006
1307
|
};
|
|
1007
1308
|
return {
|
|
@@ -1083,7 +1384,7 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
1083
1384
|
groupName: entry.groupName,
|
|
1084
1385
|
messageId: String(req.message_id ?? ""),
|
|
1085
1386
|
attempt: Number(req.attempt ?? 0),
|
|
1086
|
-
headers: req.headers
|
|
1387
|
+
headers: fromWireStringMap(req.headers)
|
|
1087
1388
|
},
|
|
1088
1389
|
retry(delayMs = 1000) {
|
|
1089
1390
|
shouldRetry = true;
|
|
@@ -1187,7 +1488,7 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
1187
1488
|
group_name: String(event.group_name ?? ""),
|
|
1188
1489
|
topic: String(event.topic ?? ""),
|
|
1189
1490
|
payload: event.payload ? Buffer.from(event.payload) : Buffer.alloc(0),
|
|
1190
|
-
headers: event.headers
|
|
1491
|
+
headers: fromWireStringMap(event.headers),
|
|
1191
1492
|
trace_id: String(event.trace_id ?? ""),
|
|
1192
1493
|
parent_span_id: String(event.parent_span_id ?? ""),
|
|
1193
1494
|
attempt: Number(event.attempt ?? 0)
|
|
@@ -1391,7 +1692,7 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
1391
1692
|
stub.Publish({
|
|
1392
1693
|
topic,
|
|
1393
1694
|
payload: toJsonBuffer(payload),
|
|
1394
|
-
headers: opts?.headers
|
|
1695
|
+
headers: toWireStringMap(opts?.headers),
|
|
1395
1696
|
trace_id: opts?.traceId ?? tc?.traceId ?? "",
|
|
1396
1697
|
parent_span_id: opts?.parentSpanId ?? tc?.spanId ?? "",
|
|
1397
1698
|
producer_service: service,
|
|
@@ -1405,7 +1706,7 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
1405
1706
|
},
|
|
1406
1707
|
handleEvent(pattern, handler, opts) {
|
|
1407
1708
|
const normalizedOpts = opts ?? {};
|
|
1408
|
-
const groupName = normalizedOpts.groupName || `${
|
|
1709
|
+
const groupName = normalizedOpts.groupName || `${defaultConsumerPrefix}.${pattern}`;
|
|
1409
1710
|
if (eventHandlers.has(groupName)) {
|
|
1410
1711
|
throw new Error(`Duplicate event consumer group "${groupName}". ` + "Use a distinct groupName for each handleEvent() registration.");
|
|
1411
1712
|
}
|
|
@@ -1423,9 +1724,6 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
1423
1724
|
if (fnHandlers.size === 0 && eventHandlers.size === 0) {
|
|
1424
1725
|
throw new Error("No handlers registered. Call handleRpc() or handleEvent() before serve().");
|
|
1425
1726
|
}
|
|
1426
|
-
if (!service.trim()) {
|
|
1427
|
-
throw new Error("serve() requires a non-empty service name");
|
|
1428
|
-
}
|
|
1429
1727
|
if (opts.maxInFlight != null && (!Number.isInteger(opts.maxInFlight) || opts.maxInFlight < 1)) {
|
|
1430
1728
|
throw new Error("serve() maxInFlight must be a positive integer");
|
|
1431
1729
|
}
|
|
@@ -1494,17 +1792,17 @@ function servicebridge(url, serviceKey, service = "", globalOpts = {}) {
|
|
|
1494
1792
|
closeWorkerSession();
|
|
1495
1793
|
serveState = null;
|
|
1496
1794
|
shutdownWorkerServerGracefully();
|
|
1497
|
-
|
|
1795
|
+
const fatal = normalizeServiceError(err, "serve");
|
|
1796
|
+
console.error(`[servicebridge] startup failed: ${fatal.message}`);
|
|
1797
|
+
throw fatal;
|
|
1498
1798
|
}
|
|
1499
1799
|
},
|
|
1500
1800
|
stop() {
|
|
1501
1801
|
stopped = true;
|
|
1502
1802
|
isOnline = false;
|
|
1503
|
-
|
|
1504
|
-
clearTimeout(onlineRestoreTimer);
|
|
1803
|
+
isWatchingChannel = false;
|
|
1505
1804
|
if (heartbeatTimer)
|
|
1506
1805
|
clearTimeout(heartbeatTimer);
|
|
1507
|
-
onlineRestoreTimer = null;
|
|
1508
1806
|
heartbeatTimer = null;
|
|
1509
1807
|
registeredGroups.clear();
|
|
1510
1808
|
closeWorkerSession();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "service-bridge",
|
|
3
|
-
"version": "1.
|
|
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": [
|