service-bridge 1.6.0-dev.33 → 1.7.0-dev.50
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 +50 -46
- package/dist/express.js +2 -2
- package/dist/fastify.js +2 -2
- package/dist/index.js +965 -428
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ Node.js SDK for [ServiceBridge](https://servicebridge.dev) — production-ready
|
|
|
73
73
|
|
|
74
74
|
**Saga / distributed transactions** — DAG workflows with typed steps (`rpc`, `event`, `event_wait`, `sleep`, child workflow). Compensations and rollbacks via workflow step dependencies.
|
|
75
75
|
|
|
76
|
-
**AI agent orchestration** — Stream LLM tokens via realtime
|
|
76
|
+
**AI agent orchestration** — Stream LLM tokens via realtime trace streams with replay. Orchestrate multi-step AI pipelines as workflows.
|
|
77
77
|
|
|
78
78
|
**Full-stack observability** — Every RPC call, event delivery, workflow step, and HTTP request traced automatically. One timeline, one dashboard. Prometheus metrics and Loki-compatible log API included.
|
|
79
79
|
|
|
@@ -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 }>("
|
|
119
|
+
const result = await sb.rpc<{ ok: boolean; txId: string }>("payment.charge", {
|
|
120
120
|
orderId: "ord_42",
|
|
121
121
|
amount: 4990,
|
|
122
122
|
});
|
|
@@ -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 }>("
|
|
174
|
+
const charge = await orders.rpc<{ ok: boolean; txId: string }>("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
|
|
207
|
-
{ id: "charge", type: "rpc", ref: "
|
|
206
|
+
{ id: "reserve", type: "rpc", ref: "inventory.reserve" },
|
|
207
|
+
{ id: "charge", type: "rpc", ref: "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
|
]);
|
|
@@ -236,7 +236,7 @@ Every step above — RPC, event publish, event delivery, workflow execution —
|
|
|
236
236
|
- **Metrics** — Prometheus-compatible `/metrics` endpoint (30+ metric families)
|
|
237
237
|
- **Logs** — structured log ingest with Loki-compatible query API
|
|
238
238
|
- **Alerts** — runtime alerts for delivery failures, errors, and service health
|
|
239
|
-
- **Dashboard** — realtime web UI for
|
|
239
|
+
- **Dashboard** — realtime web UI for traces, events, workflows, jobs, DLQ, service map, and service keys
|
|
240
240
|
|
|
241
241
|
---
|
|
242
242
|
|
|
@@ -262,7 +262,7 @@ Every step above — RPC, event publish, event delivery, workflow execution —
|
|
|
262
262
|
### Cross-SDK parity notes
|
|
263
263
|
|
|
264
264
|
ServiceBridge keeps the core API shape consistent across Node.js, Go, and Python:
|
|
265
|
-
constructor, RPC, events, jobs, workflows, `
|
|
265
|
+
constructor, RPC, events, jobs, workflows, `executeWorkflow`, streams, serve/stop, and `ServiceBridgeError`.
|
|
266
266
|
|
|
267
267
|
Constructor-level defaults for `timeout`, `retries`, and `retryDelay` are available
|
|
268
268
|
across all three SDKs. Parity differences are naming-only (language idioms):
|
|
@@ -324,7 +324,9 @@ type WorkerTLSOpts = {
|
|
|
324
324
|
rpc<T = unknown>(fn: string, payload?: unknown, opts?: RpcOpts): Promise<T>
|
|
325
325
|
```
|
|
326
326
|
|
|
327
|
-
Calls a registered RPC handler on another
|
|
327
|
+
Calls a registered RPC handler on another worker. Direct gRPC path, no proxy.
|
|
328
|
+
|
|
329
|
+
**Function name** — `fn` is a single **global function name** (the same string passed to `handleRpc` on the callee), e.g. `payment.charge` or `user.get`. It must be unique in the catalog and **must not contain `/`**.
|
|
328
330
|
|
|
329
331
|
`RpcOpts`:
|
|
330
332
|
|
|
@@ -338,7 +340,9 @@ Calls a registered RPC handler on another service. Direct gRPC path, no proxy.
|
|
|
338
340
|
| `mode` | `"direct" \| "proxy"` | Transport mode. `"direct"` (default) connects directly to the worker. `"proxy"` routes through the control plane when direct connection is unavailable. |
|
|
339
341
|
|
|
340
342
|
```ts
|
|
341
|
-
const user = await sb.rpc<{ id: string; name: string }>("
|
|
343
|
+
const user = await sb.rpc<{ id: string; name: string }>("user.get", { id: "u_1" });
|
|
344
|
+
|
|
345
|
+
const user2 = await sb.rpc<{ id: string; name: string }>("user.get", { id: "u_1" }, {
|
|
342
346
|
timeout: 5000,
|
|
343
347
|
retries: 2,
|
|
344
348
|
});
|
|
@@ -398,7 +402,7 @@ Registers a scheduled or delayed job.
|
|
|
398
402
|
| `retryPolicyJson` | `string` | Retry policy JSON string. |
|
|
399
403
|
|
|
400
404
|
```ts
|
|
401
|
-
await sb.job("billing
|
|
405
|
+
await sb.job("billing.collect", {
|
|
402
406
|
cron: "0 * * * *",
|
|
403
407
|
timezone: "UTC",
|
|
404
408
|
via: "rpc",
|
|
@@ -443,8 +447,8 @@ interface WorkflowOpts {
|
|
|
443
447
|
|
|
444
448
|
```ts
|
|
445
449
|
await sb.workflow("order.fulfillment", [
|
|
446
|
-
{ id: "reserve", type: "rpc", ref: "inventory
|
|
447
|
-
{ id: "charge", type: "rpc", ref: "
|
|
450
|
+
{ id: "reserve", type: "rpc", ref: "inventory.reserve" },
|
|
451
|
+
{ id: "charge", type: "rpc", ref: "payment.charge", deps: ["reserve"] },
|
|
448
452
|
{ id: "wait_5m", type: "sleep", durationMs: 300_000, deps: ["charge"] },
|
|
449
453
|
{ id: "notify", type: "event", ref: "orders.fulfilled", deps: ["wait_5m"] },
|
|
450
454
|
]);
|
|
@@ -458,44 +462,44 @@ await sb.workflow("checkout.flow", steps, { stepTimeoutMs: 60_000 });
|
|
|
458
462
|
|
|
459
463
|
---
|
|
460
464
|
|
|
461
|
-
### `
|
|
465
|
+
### `executeWorkflow(name, input?, opts?)`
|
|
462
466
|
|
|
463
467
|
```ts
|
|
464
|
-
|
|
468
|
+
executeWorkflow(name: string, input?: unknown, opts?: ExecuteWorkflowOpts): Promise<{ traceId: string; groupTraceId: string }>
|
|
465
469
|
```
|
|
466
470
|
|
|
467
|
-
Starts a workflow
|
|
468
|
-
An alternative to scheduling via `job(target, { via: "workflow" })` — triggers the
|
|
471
|
+
Starts a workflow execution on demand. The workflow must be registered first via `workflow()`.
|
|
472
|
+
An alternative to scheduling via `job(target, { via: "workflow" })` — triggers the execution immediately.
|
|
469
473
|
|
|
470
474
|
| Parameter | Type | Default | Description |
|
|
471
475
|
|---|---|---|---|
|
|
472
476
|
| `name` | `string` | required | Name of a previously registered workflow. |
|
|
473
477
|
| `input` | `unknown` | `undefined` | Optional JSON-serializable input payload. |
|
|
474
478
|
|
|
475
|
-
Returns `{
|
|
479
|
+
Returns `{ traceId, groupTraceId }`. Use `traceId` with `watchTrace()` to observe execution in real time.
|
|
476
480
|
|
|
477
|
-
`
|
|
481
|
+
`ExecuteWorkflowOpts`:
|
|
478
482
|
|
|
479
483
|
| Option | Type | Description |
|
|
480
484
|
|---|---|---|
|
|
481
|
-
| `traceId` | `string` | Override trace ID for this workflow
|
|
485
|
+
| `traceId` | `string` | Override trace ID for this workflow execution. |
|
|
482
486
|
|
|
483
487
|
```ts
|
|
484
|
-
const {
|
|
488
|
+
const { traceId, groupTraceId } = await sb.executeWorkflow("user.onboarding", { userId: "u_123" });
|
|
485
489
|
```
|
|
486
490
|
|
|
487
491
|
---
|
|
488
492
|
|
|
489
|
-
### `
|
|
493
|
+
### `cancelWorkflow(traceId)`
|
|
490
494
|
|
|
491
495
|
```ts
|
|
492
|
-
|
|
496
|
+
cancelWorkflow(traceId: string): Promise<void>
|
|
493
497
|
```
|
|
494
498
|
|
|
495
499
|
Cancels a running workflow instance.
|
|
496
500
|
|
|
497
501
|
```ts
|
|
498
|
-
await sb.
|
|
502
|
+
await sb.cancelWorkflow("trace_01HQ...XYZ");
|
|
499
503
|
```
|
|
500
504
|
|
|
501
505
|
---
|
|
@@ -531,7 +535,7 @@ Registers an RPC handler. Chainable.
|
|
|
531
535
|
| `allowedCallers` | `string[]` | Allow-list of caller service names. |
|
|
532
536
|
|
|
533
537
|
```ts
|
|
534
|
-
sb.handleRpc("ai
|
|
538
|
+
sb.handleRpc("ai.generate", async (payload: { prompt: string }, ctx) => {
|
|
535
539
|
await ctx?.stream.write({ token: "Hello" }, "output");
|
|
536
540
|
await ctx?.stream.write({ token: " world" }, "output");
|
|
537
541
|
return { text: "Hello world" };
|
|
@@ -542,7 +546,7 @@ sb.handleRpc("ai/generate", async (payload: { prompt: string }, ctx) => {
|
|
|
542
546
|
|
|
543
547
|
| Method | Signature | Description |
|
|
544
548
|
|---|---|---|
|
|
545
|
-
| `write` | `write(data: unknown, key?: string): Promise<void>` | Append a real-time chunk to the
|
|
549
|
+
| `write` | `write(data: unknown, key?: string): Promise<void>` | Append a real-time chunk to the trace stream. |
|
|
546
550
|
| `end` | `end(key?: string): Promise<void>` | No-op placeholder for API symmetry (lifecycle managed by runtime). |
|
|
547
551
|
|
|
548
552
|
---
|
|
@@ -584,7 +588,7 @@ a consumer, the delivery moves to DLQ with reason `delivery_ttl_exceeded`.
|
|
|
584
588
|
- `ctx.retry(delayMs?)` — ask for redelivery with optional delay
|
|
585
589
|
- `ctx.reject(reason)` — move to DLQ immediately, bypassing remaining retries
|
|
586
590
|
- `ctx.refs` — metadata (`topic`, `groupName`, `messageId`, `attempt`, `headers`)
|
|
587
|
-
- `ctx.stream.write(...)` — append real-time chunks to
|
|
591
|
+
- `ctx.stream.write(...)` — append real-time chunks to trace stream
|
|
588
592
|
|
|
589
593
|
```ts
|
|
590
594
|
sb.handleEvent("orders.*", async (payload, ctx) => {
|
|
@@ -702,32 +706,32 @@ await sb.registerHttpEndpoint({
|
|
|
702
706
|
|
|
703
707
|
---
|
|
704
708
|
|
|
705
|
-
### `
|
|
709
|
+
### `watchTrace(traceId, opts?)`
|
|
706
710
|
|
|
707
711
|
```ts
|
|
708
|
-
|
|
712
|
+
watchTrace(traceId: string, opts?: WatchTraceOpts): AsyncIterable<TraceStreamEvent>
|
|
709
713
|
```
|
|
710
714
|
|
|
711
|
-
Subscribes to a
|
|
712
|
-
identifier used by `ctx.stream.write(...)
|
|
715
|
+
Subscribes to a trace stream with replay and live updates. `traceId` is the stream
|
|
716
|
+
identifier used by `ctx.stream.write(...)`.
|
|
713
717
|
|
|
714
|
-
`
|
|
718
|
+
`WatchTraceOpts`:
|
|
715
719
|
|
|
716
720
|
| Option | Type | Default | Description |
|
|
717
721
|
|---|---|---|---|
|
|
718
722
|
| `key` | `string` | `""` | Stream key filter (`""` = all keys). |
|
|
719
723
|
| `fromSequence` | `number` | `0` | Replay from sequence cursor. |
|
|
720
724
|
|
|
721
|
-
`
|
|
725
|
+
`TraceStreamEvent`:
|
|
722
726
|
|
|
723
727
|
| Field | Type | Description |
|
|
724
728
|
|---|---|---|
|
|
725
|
-
| `type` | `"chunk" \| "
|
|
726
|
-
| `
|
|
729
|
+
| `type` | `"chunk" \| "trace_complete"` | Event kind. |
|
|
730
|
+
| `traceId` | `string` | Trace identifier being watched. |
|
|
727
731
|
| `key` | `string` | Stream lane key. |
|
|
728
732
|
| `sequence` | `number` | Monotonic sequence number. |
|
|
729
733
|
| `data` | `unknown` | JSON-decoded chunk payload. |
|
|
730
|
-
| `
|
|
734
|
+
| `traceStatus` | `string \| undefined` | Final status on `trace_complete`. |
|
|
731
735
|
|
|
732
736
|
Behavior:
|
|
733
737
|
|
|
@@ -737,11 +741,11 @@ Behavior:
|
|
|
737
741
|
- Enforces internal queue limit `256`; overflow is fatal (consumer must drain promptly).
|
|
738
742
|
|
|
739
743
|
```ts
|
|
740
|
-
for await (const evt of sb.
|
|
744
|
+
for await (const evt of sb.watchTrace(traceId, { key: "output", fromSequence: 0 })) {
|
|
741
745
|
if (evt.type === "chunk") {
|
|
742
746
|
process.stdout.write(String((evt.data as { token?: string }).token ?? ""));
|
|
743
747
|
}
|
|
744
|
-
if (evt.type === "
|
|
748
|
+
if (evt.type === "trace_complete") break;
|
|
745
749
|
}
|
|
746
750
|
```
|
|
747
751
|
|
|
@@ -766,18 +770,18 @@ if (tc) {
|
|
|
766
770
|
}
|
|
767
771
|
```
|
|
768
772
|
|
|
769
|
-
#### `
|
|
773
|
+
#### `withTraceContext(ctx, fn)`
|
|
770
774
|
|
|
771
775
|
```ts
|
|
772
|
-
|
|
776
|
+
withTraceContext<T>(ctx: { traceId: string; spanId: string }, fn: () => T): T
|
|
773
777
|
```
|
|
774
778
|
|
|
775
779
|
Runs a function inside an explicit trace context.
|
|
776
780
|
|
|
777
781
|
```ts
|
|
778
|
-
import {
|
|
782
|
+
import { withTraceContext } from "service-bridge";
|
|
779
783
|
|
|
780
|
-
|
|
784
|
+
withTraceContext({ traceId: "trace-1", spanId: "span-1" }, async () => {
|
|
781
785
|
await sb.event("audit.log", { action: "user.login" });
|
|
782
786
|
});
|
|
783
787
|
```
|
|
@@ -807,7 +811,7 @@ app.use(servicebridgeMiddleware({
|
|
|
807
811
|
}));
|
|
808
812
|
|
|
809
813
|
app.get("/users/:id", async (req, res) => {
|
|
810
|
-
const user = await req.servicebridge.rpc("
|
|
814
|
+
const user = await req.servicebridge.rpc("user.get", { id: req.params.id });
|
|
811
815
|
res.json(user);
|
|
812
816
|
});
|
|
813
817
|
```
|
|
@@ -863,7 +867,7 @@ await app.register(servicebridgePlugin, {
|
|
|
863
867
|
});
|
|
864
868
|
|
|
865
869
|
app.get("/users/:id", wrapHandler(async (request, reply) => {
|
|
866
|
-
const user = await request.servicebridge.rpc("
|
|
870
|
+
const user = await request.servicebridge.rpc("user.get", {
|
|
867
871
|
id: (request.params as any).id,
|
|
868
872
|
});
|
|
869
873
|
return reply.send(user);
|
|
@@ -960,7 +964,7 @@ const sb = servicebridge(
|
|
|
960
964
|
import { servicebridge, ServiceBridgeError } from "service-bridge";
|
|
961
965
|
|
|
962
966
|
try {
|
|
963
|
-
await sb.rpc("
|
|
967
|
+
await sb.rpc("payment.charge", { orderId: "ord_1" });
|
|
964
968
|
} catch (e) {
|
|
965
969
|
if (e instanceof ServiceBridgeError) {
|
|
966
970
|
console.error(e.component, e.operation, e.severity, e.retryable, e.code);
|
|
@@ -1148,12 +1152,12 @@ session.onConfigPush({
|
|
|
1148
1152
|
'payment-svc': { mode: 'proxy', fallbackPolicy: 'fallback_to_direct' },
|
|
1149
1153
|
},
|
|
1150
1154
|
functionOverrides: {
|
|
1151
|
-
'payment
|
|
1155
|
+
'payment.charge': { mode: 'proxy', timeoutMs: 5000 },
|
|
1152
1156
|
},
|
|
1153
1157
|
});
|
|
1154
1158
|
|
|
1155
1159
|
// Разрешить транспорт для функции
|
|
1156
|
-
const mode = session.resolveTransportMode('payment
|
|
1160
|
+
const mode = session.resolveTransportMode('payment.charge'); // 'proxy'
|
|
1157
1161
|
```
|
|
1158
1162
|
|
|
1159
1163
|
### Все события сессии
|
package/dist/express.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// http/src/express.ts
|
|
2
|
-
import {
|
|
2
|
+
import { withTraceContext } from "service-bridge";
|
|
3
3
|
|
|
4
4
|
// http/src/trace.ts
|
|
5
5
|
function parseTraceparent(traceparent) {
|
|
@@ -86,7 +86,7 @@ function servicebridgeMiddleware(options) {
|
|
|
86
86
|
};
|
|
87
87
|
res.on("finish", onFinish);
|
|
88
88
|
res.on("close", onFinish);
|
|
89
|
-
|
|
89
|
+
withTraceContext({ traceId: span.traceId, spanId: span.spanId }, () => {
|
|
90
90
|
next();
|
|
91
91
|
});
|
|
92
92
|
};
|
package/dist/fastify.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// http/src/fastify.ts
|
|
2
|
-
import {
|
|
2
|
+
import { withTraceContext } from "service-bridge";
|
|
3
3
|
|
|
4
4
|
// http/src/trace.ts
|
|
5
5
|
function parseTraceparent(traceparent) {
|
|
@@ -111,7 +111,7 @@ function wrapHandler(handler) {
|
|
|
111
111
|
const traceId = request.traceId;
|
|
112
112
|
const spanId = request.spanId;
|
|
113
113
|
if (traceId && spanId) {
|
|
114
|
-
return
|
|
114
|
+
return withTraceContext({ traceId, spanId }, () => handler(request, reply));
|
|
115
115
|
}
|
|
116
116
|
return handler(request, reply);
|
|
117
117
|
};
|