service-bridge 1.8.1-dev.36 → 1.8.2-dev.38
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 +17 -29
- package/dist/index.js +23 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ const sb = servicebridge(
|
|
|
99
99
|
process.env.SERVICEBRIDGE_SERVICE_KEY!,
|
|
100
100
|
);
|
|
101
101
|
|
|
102
|
-
sb.handleRpc("charge", async (payload: { orderId: string; amount: number }) => {
|
|
102
|
+
sb.handleRpc("payment.charge", async (payload: { orderId: string; amount: number }) => {
|
|
103
103
|
return { ok: true, txId: `tx_${Date.now()}`, orderId: payload.orderId };
|
|
104
104
|
});
|
|
105
105
|
|
|
@@ -116,7 +116,7 @@ const sb = servicebridge(
|
|
|
116
116
|
process.env.SERVICEBRIDGE_SERVICE_KEY!,
|
|
117
117
|
);
|
|
118
118
|
|
|
119
|
-
const result = await sb.rpc<{ ok: boolean; txId: string }>("payments
|
|
119
|
+
const result = await sb.rpc<{ ok: boolean; txId: string }>("payments", "payment.charge", {
|
|
120
120
|
orderId: "ord_42",
|
|
121
121
|
amount: 4990,
|
|
122
122
|
});
|
|
@@ -153,7 +153,7 @@ import { servicebridge } from "service-bridge";
|
|
|
153
153
|
|
|
154
154
|
const payments = servicebridge("localhost:14445", process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
155
155
|
|
|
156
|
-
payments.handleRpc("charge", async (payload: { orderId: string; amount: number }, ctx) => {
|
|
156
|
+
payments.handleRpc("payment.charge", async (payload: { orderId: string; amount: number }, ctx) => {
|
|
157
157
|
await ctx?.stream.write({ status: "charging", orderId: payload.orderId }, "progress");
|
|
158
158
|
|
|
159
159
|
// ... charge logic ...
|
|
@@ -171,7 +171,7 @@ await payments.serve({ host: "localhost" });
|
|
|
171
171
|
const orders = servicebridge("localhost:14445", process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
172
172
|
|
|
173
173
|
// Call payments, then publish event
|
|
174
|
-
const charge = await orders.rpc<{ ok: boolean; txId: string }>("payments
|
|
174
|
+
const charge = await orders.rpc<{ ok: boolean; txId: string }>("payments", "payment.charge", {
|
|
175
175
|
orderId: "ord_42",
|
|
176
176
|
amount: 4990,
|
|
177
177
|
});
|
|
@@ -203,8 +203,8 @@ await notifications.serve({ host: "localhost" });
|
|
|
203
203
|
// --- Orchestrate as a workflow ---
|
|
204
204
|
|
|
205
205
|
await orders.workflow("order.fulfillment", [
|
|
206
|
-
{ id: "reserve", type: "rpc", ref: "inventory/reserve" },
|
|
207
|
-
{ id: "charge", type: "rpc", ref: "payments/charge", deps: ["reserve"] },
|
|
206
|
+
{ id: "reserve", type: "rpc", ref: "inventory/inventory.reserve" },
|
|
207
|
+
{ id: "charge", type: "rpc", ref: "payments/payment.charge", deps: ["reserve"] },
|
|
208
208
|
{ id: "wait_dlv", type: "event_wait", ref: "shipping.delivered", deps: ["charge"] },
|
|
209
209
|
{ id: "notify", type: "event", ref: "orders.fulfilled", deps: ["wait_dlv"] },
|
|
210
210
|
]);
|
|
@@ -318,22 +318,15 @@ type WorkerTLSOpts = {
|
|
|
318
318
|
|
|
319
319
|
---
|
|
320
320
|
|
|
321
|
-
### `rpc(fn, payload?, opts?)`
|
|
321
|
+
### `rpc(service, fn, payload?, opts?)`
|
|
322
322
|
|
|
323
323
|
```ts
|
|
324
|
-
rpc<T = unknown>(fn: string, payload?: unknown, opts?: RpcOpts): Promise<T>
|
|
324
|
+
rpc<T = unknown>(service: string, fn: string, payload?: unknown, opts?: RpcOpts): Promise<T>
|
|
325
325
|
```
|
|
326
326
|
|
|
327
327
|
Calls a registered RPC handler on another service. Direct gRPC path, no proxy.
|
|
328
328
|
|
|
329
|
-
**
|
|
330
|
-
|
|
331
|
-
| Format | Example | When to use |
|
|
332
|
-
|---|---|---|
|
|
333
|
-
| Plain name | `"charge"` | Function name is globally unique across all services. Resolved automatically via service discovery. |
|
|
334
|
-
| Canonical name | `"payments/charge"` | Multiple services expose a function with the same plain name, or you want to be explicit about the target service. |
|
|
335
|
-
|
|
336
|
-
Both formats are interchangeable when the name is unique globally. Canonical format is recommended in production for clarity and to avoid ambiguity as your service count grows.
|
|
329
|
+
**Arguments** — `service` is the callee’s logical name; `fn` is the name used in `handleRpc` (e.g. `payment.charge`). Use **dot notation** in `fn` to group methods. Do not put `/` in `fn`.
|
|
337
330
|
|
|
338
331
|
`RpcOpts`:
|
|
339
332
|
|
|
@@ -347,11 +340,7 @@ Both formats are interchangeable when the name is unique globally. Canonical for
|
|
|
347
340
|
| `mode` | `"direct" \| "proxy"` | Transport mode. `"direct"` (default) connects directly to the worker. `"proxy"` routes through the control plane when direct connection is unavailable. |
|
|
348
341
|
|
|
349
342
|
```ts
|
|
350
|
-
|
|
351
|
-
const user = await sb.rpc<{ id: string; name: string }>("get", { id: "u_1" });
|
|
352
|
-
|
|
353
|
-
// canonical name — explicit service target, always unambiguous
|
|
354
|
-
const user = await sb.rpc<{ id: string; name: string }>("users/get", { id: "u_1" }, {
|
|
343
|
+
const user = await sb.rpc<{ id: string; name: string }>("users", "user.get", { id: "u_1" }, {
|
|
355
344
|
timeout: 5000,
|
|
356
345
|
retries: 2,
|
|
357
346
|
});
|
|
@@ -421,7 +410,7 @@ Registers a scheduled or delayed job.
|
|
|
421
410
|
| `retryPolicyJson` | `string` | Retry policy JSON string. |
|
|
422
411
|
|
|
423
412
|
```ts
|
|
424
|
-
await sb.job("billing/collect", {
|
|
413
|
+
await sb.job("billing/billing.collect", {
|
|
425
414
|
cron: "0 * * * *",
|
|
426
415
|
timezone: "UTC",
|
|
427
416
|
via: "rpc",
|
|
@@ -466,8 +455,8 @@ interface WorkflowOpts {
|
|
|
466
455
|
|
|
467
456
|
```ts
|
|
468
457
|
await sb.workflow("order.fulfillment", [
|
|
469
|
-
{ id: "reserve", type: "rpc", ref: "inventory/reserve" },
|
|
470
|
-
{ id: "charge", type: "rpc", ref: "payments/charge", deps: ["reserve"] },
|
|
458
|
+
{ id: "reserve", type: "rpc", ref: "inventory/inventory.reserve" },
|
|
459
|
+
{ id: "charge", type: "rpc", ref: "payments/payment.charge", deps: ["reserve"] },
|
|
471
460
|
{ id: "wait_5m", type: "sleep", durationMs: 300_000, deps: ["charge"] },
|
|
472
461
|
{ id: "notify", type: "event", ref: "orders.fulfilled", deps: ["wait_5m"] },
|
|
473
462
|
]);
|
|
@@ -554,7 +543,7 @@ Registers an RPC handler. Chainable.
|
|
|
554
543
|
| `allowedCallers` | `string[]` | Allow-list of caller service names. |
|
|
555
544
|
|
|
556
545
|
```ts
|
|
557
|
-
sb.handleRpc("ai
|
|
546
|
+
sb.handleRpc("ai.generate", async (payload: { prompt: string }, ctx) => {
|
|
558
547
|
await ctx?.stream.write({ token: "Hello" }, "output");
|
|
559
548
|
await ctx?.stream.write({ token: " world" }, "output");
|
|
560
549
|
return { text: "Hello world" };
|
|
@@ -830,7 +819,7 @@ app.use(servicebridgeMiddleware({
|
|
|
830
819
|
}));
|
|
831
820
|
|
|
832
821
|
app.get("/users/:id", async (req, res) => {
|
|
833
|
-
const user = await req.servicebridge.rpc("users
|
|
822
|
+
const user = await req.servicebridge.rpc("users", "user.get", { id: req.params.id });
|
|
834
823
|
res.json(user);
|
|
835
824
|
});
|
|
836
825
|
```
|
|
@@ -886,7 +875,7 @@ await app.register(servicebridgePlugin, {
|
|
|
886
875
|
});
|
|
887
876
|
|
|
888
877
|
app.get("/users/:id", wrapHandler(async (request, reply) => {
|
|
889
|
-
const user = await request.servicebridge.rpc("users
|
|
878
|
+
const user = await request.servicebridge.rpc("users", "user.get", {
|
|
890
879
|
id: (request.params as any).id,
|
|
891
880
|
});
|
|
892
881
|
return reply.send(user);
|
|
@@ -983,7 +972,7 @@ const sb = servicebridge(
|
|
|
983
972
|
import { servicebridge, ServiceBridgeError } from "service-bridge";
|
|
984
973
|
|
|
985
974
|
try {
|
|
986
|
-
await sb.rpc("payments
|
|
975
|
+
await sb.rpc("payments", "payment.charge", { orderId: "ord_1" });
|
|
987
976
|
} catch (e) {
|
|
988
977
|
if (e instanceof ServiceBridgeError) {
|
|
989
978
|
console.error(e.component, e.operation, e.severity, e.retryable, e.code);
|
|
@@ -1055,7 +1044,6 @@ import { V2SessionClient, validateV2Config } from 'service-bridge';
|
|
|
1055
1044
|
|
|
1056
1045
|
const cfg = {
|
|
1057
1046
|
serverAddress: 'localhost:9090',
|
|
1058
|
-
serviceName: 'my-worker',
|
|
1059
1047
|
instanceId: 'worker-1',
|
|
1060
1048
|
zone: 'us-east-1a',
|
|
1061
1049
|
transportMode: 'direct' as const,
|
package/dist/index.js
CHANGED
|
@@ -177,7 +177,6 @@ class V2SessionClient {
|
|
|
177
177
|
constructor(config) {
|
|
178
178
|
this.config = {
|
|
179
179
|
serverAddress: config.serverAddress,
|
|
180
|
-
serviceName: config.serviceName,
|
|
181
180
|
instanceId: config.instanceId,
|
|
182
181
|
zone: config.zone ?? "",
|
|
183
182
|
transportMode: config.transportMode ?? "direct",
|
|
@@ -209,7 +208,6 @@ class V2SessionClient {
|
|
|
209
208
|
const rs = this.position.getResumeState(this.resumeToken, this.epoch);
|
|
210
209
|
return {
|
|
211
210
|
identity: {
|
|
212
|
-
serviceName: this.config.serviceName,
|
|
213
211
|
instanceId: this.config.instanceId,
|
|
214
212
|
transport: this.config.transportMode
|
|
215
213
|
},
|
|
@@ -309,8 +307,6 @@ class V2SessionClient {
|
|
|
309
307
|
function validateV2Config(cfg) {
|
|
310
308
|
if (!cfg.serverAddress)
|
|
311
309
|
throw new Error("servicebridge: serverAddress is required");
|
|
312
|
-
if (!cfg.serviceName)
|
|
313
|
-
throw new Error("servicebridge: serviceName is required");
|
|
314
310
|
if (!cfg.instanceId)
|
|
315
311
|
throw new Error("servicebridge: instanceId is required");
|
|
316
312
|
if (cfg.transportMode && cfg.transportMode !== "direct" && cfg.transportMode !== "proxy") {
|
|
@@ -632,7 +628,7 @@ function reportSDKError(operation, err, component = "sdk") {
|
|
|
632
628
|
}
|
|
633
629
|
return normalized;
|
|
634
630
|
}
|
|
635
|
-
function
|
|
631
|
+
function parseRegistryCanonicalName(target) {
|
|
636
632
|
const canonicalName = target.trim();
|
|
637
633
|
if (!canonicalName)
|
|
638
634
|
return null;
|
|
@@ -1065,7 +1061,6 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
1065
1061
|
function pushLog(level, msg, attrs) {
|
|
1066
1062
|
const tc = traceStorage.getStore();
|
|
1067
1063
|
const entry = {
|
|
1068
|
-
service_name: service,
|
|
1069
1064
|
level,
|
|
1070
1065
|
message: msg,
|
|
1071
1066
|
timestamp_ns: String(Date.now() * 1e6),
|
|
@@ -1147,7 +1142,7 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
1147
1142
|
const endpoints = parseEndpointsFromWire(res.endpoints);
|
|
1148
1143
|
const isNewFunction = !functionMeta.has(canonicalName);
|
|
1149
1144
|
if (isNewFunction) {
|
|
1150
|
-
const parsed =
|
|
1145
|
+
const parsed = parseRegistryCanonicalName(canonicalName);
|
|
1151
1146
|
if (parsed) {
|
|
1152
1147
|
functionMeta.set(canonicalName, {
|
|
1153
1148
|
canonicalName,
|
|
@@ -1533,7 +1528,6 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
1533
1528
|
await new Promise((resolve, reject) => {
|
|
1534
1529
|
stub.Reconcile({
|
|
1535
1530
|
identity: {
|
|
1536
|
-
service_name: service,
|
|
1537
1531
|
instance_id: state.instanceId,
|
|
1538
1532
|
endpoint: state.endpoint,
|
|
1539
1533
|
transport: state.transport
|
|
@@ -1991,7 +1985,6 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
1991
1985
|
if (!v2Session) {
|
|
1992
1986
|
v2Session = new V2SessionClient({
|
|
1993
1987
|
serverAddress: target,
|
|
1994
|
-
serviceName: service,
|
|
1995
1988
|
instanceId: serveState.instanceId,
|
|
1996
1989
|
transportMode: "direct",
|
|
1997
1990
|
maxInflight: serveState.maxInFlight,
|
|
@@ -2004,7 +1997,6 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
2004
1997
|
stream.write({
|
|
2005
1998
|
hello: {
|
|
2006
1999
|
identity: {
|
|
2007
|
-
service_name: hf.identity.serviceName,
|
|
2008
2000
|
instance_id: hf.identity.instanceId,
|
|
2009
2001
|
endpoint: serveState.endpoint,
|
|
2010
2002
|
transport: serveState.transport
|
|
@@ -2120,35 +2112,40 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
2120
2112
|
}
|
|
2121
2113
|
}
|
|
2122
2114
|
const svc = {
|
|
2123
|
-
async rpc(fn, payload, opts) {
|
|
2115
|
+
async rpc(targetService, fn, payload, opts) {
|
|
2124
2116
|
try {
|
|
2125
2117
|
await _controlReady;
|
|
2126
2118
|
} catch (err) {
|
|
2127
2119
|
throw normalizeServiceError(err, "control-plane");
|
|
2128
2120
|
}
|
|
2121
|
+
const svcName = targetService.trim();
|
|
2122
|
+
const fname = fn.trim();
|
|
2123
|
+
if (!svcName || !fname) {
|
|
2124
|
+
throw new Error("rpc: service and fn are required");
|
|
2125
|
+
}
|
|
2126
|
+
const lookupKey = `${svcName}/${fname}`;
|
|
2129
2127
|
const tc = traceStorage.getStore();
|
|
2130
2128
|
const traceId = opts?.traceId ?? tc?.traceId ?? crypto.randomUUID();
|
|
2131
2129
|
const maxRetries = normalizeNonNegativeInt(opts?.retries ?? globalOpts.retries ?? 3, 3);
|
|
2132
2130
|
const baseDelay = normalizePositiveInt(opts?.retryDelay ?? globalOpts.retryDelay ?? 300, 300);
|
|
2133
2131
|
const timeout = normalizePositiveInt(opts?.timeout ?? globalOpts.timeout ?? 30000, 30000);
|
|
2134
|
-
let canonical = resolveCanonical(
|
|
2132
|
+
let canonical = resolveCanonical(lookupKey);
|
|
2135
2133
|
if (!canonical) {
|
|
2136
|
-
await doLookupFunction(
|
|
2137
|
-
canonical = resolveCanonical(
|
|
2134
|
+
await doLookupFunction(lookupKey);
|
|
2135
|
+
canonical = resolveCanonical(lookupKey);
|
|
2138
2136
|
}
|
|
2139
2137
|
if (!canonical)
|
|
2140
|
-
throw normalizeServiceError(new Error(`No endpoints available for RPC: ${
|
|
2138
|
+
throw normalizeServiceError(new Error(`No endpoints available for RPC: ${lookupKey}`), lookupKey, "worker");
|
|
2141
2139
|
const fmeta = functionMeta.get(canonical);
|
|
2142
2140
|
if (fmeta && !containsOrAll(fmeta.allowedCallers, service)) {
|
|
2143
|
-
throw new Error(`Service "${service}" is not allowed to call "${
|
|
2141
|
+
throw new Error(`Service "${service}" is not allowed to call "${lookupKey}". ` + `Permitted callers: ${fmeta.allowedCallers.join(", ")}`);
|
|
2144
2142
|
}
|
|
2145
2143
|
const inputSchema = fmeta?.inputSchema;
|
|
2146
2144
|
const outputSchema = fmeta?.outputSchema;
|
|
2147
2145
|
const inputBuf = inputSchema ? encodeWithSchema(inputSchema, payload) : toJsonBuffer(payload);
|
|
2148
2146
|
const telemetryInputBuf = toJsonBuffer(payload);
|
|
2149
|
-
const
|
|
2150
|
-
const
|
|
2151
|
-
const rpcServiceName = _parsedCanonical?.serviceName ?? "";
|
|
2147
|
+
const rpcFnName = fname;
|
|
2148
|
+
const rpcServiceName = svcName;
|
|
2152
2149
|
const rootSpanId = crypto.randomUUID();
|
|
2153
2150
|
const parentSpanId = opts?.parentSpanId ?? tc?.spanId ?? "";
|
|
2154
2151
|
const rootStartedAt = Date.now();
|
|
@@ -2166,12 +2163,12 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
2166
2163
|
}, meta, { deadline: new Date(Date.now() + timeout) }, (err, res) => {
|
|
2167
2164
|
if (err) {
|
|
2168
2165
|
reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, false, 1, undefined, String(err), "rpc", parentSpanId, rpcServiceName);
|
|
2169
|
-
return reject(normalizeServiceError(err,
|
|
2166
|
+
return reject(normalizeServiceError(err, lookupKey, "control-plane"));
|
|
2170
2167
|
}
|
|
2171
2168
|
if (!res?.success) {
|
|
2172
2169
|
const errMsg = res?.error ?? "proxy call failed";
|
|
2173
2170
|
reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, false, 1, undefined, errMsg, "rpc", parentSpanId, rpcServiceName);
|
|
2174
|
-
return reject(normalizeServiceError(new Error(errMsg),
|
|
2171
|
+
return reject(normalizeServiceError(new Error(errMsg), lookupKey, "worker"));
|
|
2175
2172
|
}
|
|
2176
2173
|
reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, true, 1, res.output, "", "rpc", parentSpanId, rpcServiceName);
|
|
2177
2174
|
try {
|
|
@@ -2197,8 +2194,8 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
2197
2194
|
try {
|
|
2198
2195
|
const attemptTimeoutMs = timeout;
|
|
2199
2196
|
const deadline = new Date(Date.now() + attemptTimeoutMs);
|
|
2200
|
-
const
|
|
2201
|
-
if (!
|
|
2197
|
+
const workerFnName = fmeta?.fnName ?? fname;
|
|
2198
|
+
if (!workerFnName)
|
|
2202
2199
|
throw new Error("unreachable");
|
|
2203
2200
|
const res = await new Promise((resolve, reject) => {
|
|
2204
2201
|
let settled = false;
|
|
@@ -2225,7 +2222,7 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
2225
2222
|
};
|
|
2226
2223
|
try {
|
|
2227
2224
|
unaryCall = workerClient.Handle({
|
|
2228
|
-
function_name:
|
|
2225
|
+
function_name: workerFnName,
|
|
2229
2226
|
payload: inputBuf,
|
|
2230
2227
|
trace_id: traceId,
|
|
2231
2228
|
span_id: attemptSpanId,
|
|
@@ -2263,7 +2260,7 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
2263
2260
|
}
|
|
2264
2261
|
}
|
|
2265
2262
|
}
|
|
2266
|
-
throw normalizeServiceError(lastError,
|
|
2263
|
+
throw normalizeServiceError(lastError, lookupKey, "worker");
|
|
2267
2264
|
});
|
|
2268
2265
|
},
|
|
2269
2266
|
event(topic, payload, opts) {
|
|
@@ -2474,8 +2471,7 @@ function servicebridge(url, serviceKey, serviceOrOpts = {}, maybeGlobalOpts = {}
|
|
|
2474
2471
|
return new Promise((resolve, reject) => {
|
|
2475
2472
|
stub.ExecuteWorkflow({
|
|
2476
2473
|
workflow_name: name,
|
|
2477
|
-
input: payload
|
|
2478
|
-
service_name: service
|
|
2474
|
+
input: payload
|
|
2479
2475
|
}, meta, unaryDeadlineOptions(), (err, res) => {
|
|
2480
2476
|
if (err) {
|
|
2481
2477
|
reject(normalizeServiceError(err, "execute-workflow"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "service-bridge",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2-dev.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ServiceBridge SDK for Node.js — one self-hosted runtime for RPC, events, workflows, and jobs without a service mesh or sidecars. Direct gRPC between workers; durable events, jobs, tracing, auto mTLS. One Go runtime + PostgreSQL.",
|
|
6
6
|
"keywords": [
|