service-bridge 1.8.5-dev.49 → 1.9.0-dev.52
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 +173 -126
- package/dist/index.js +928 -488
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
|
|
10
|
-
**The
|
|
10
|
+
**The Unified Bridge for Microservices Interaction**
|
|
11
11
|
|
|
12
|
-
Node.js SDK for [ServiceBridge](https://servicebridge.dev) —
|
|
12
|
+
Node.js SDK for [ServiceBridge](https://servicebridge.dev) — production-ready RPC, durable events, workflows, jobs, and distributed tracing in a single SDK. One Go runtime and PostgreSQL.
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
┌─────────────────────────────────────────────────────────────────┐
|
|
@@ -92,31 +92,31 @@ bun add service-bridge
|
|
|
92
92
|
### 2. Create a worker (service that handles calls)
|
|
93
93
|
|
|
94
94
|
```ts
|
|
95
|
-
import {
|
|
95
|
+
import { ServiceBridge } from "service-bridge";
|
|
96
96
|
|
|
97
|
-
const sb =
|
|
97
|
+
const sb = new ServiceBridge(
|
|
98
98
|
process.env.SERVICEBRIDGE_URL ?? "localhost:14445",
|
|
99
99
|
process.env.SERVICEBRIDGE_SERVICE_KEY!,
|
|
100
100
|
);
|
|
101
101
|
|
|
102
|
-
sb.
|
|
102
|
+
sb.rpc.handle("payment.charge", async (payload: { orderId: string; amount: number }) => {
|
|
103
103
|
return { ok: true, txId: `tx_${Date.now()}`, orderId: payload.orderId };
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
await sb.
|
|
106
|
+
await sb.start({ host: "localhost" });
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
### 3. Call it from another service
|
|
110
110
|
|
|
111
111
|
```ts
|
|
112
|
-
import {
|
|
112
|
+
import { ServiceBridge } from "service-bridge";
|
|
113
113
|
|
|
114
|
-
const sb =
|
|
114
|
+
const sb = new ServiceBridge(
|
|
115
115
|
process.env.SERVICEBRIDGE_URL ?? "localhost:14445",
|
|
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.invoke<{ ok: boolean; txId: string }>("payment.charge", {
|
|
120
120
|
orderId: "ord_42",
|
|
121
121
|
amount: 4990,
|
|
122
122
|
});
|
|
@@ -147,13 +147,13 @@ For manual Docker Compose setup, configuration reference, and all runtime enviro
|
|
|
147
147
|
A complete order flow: HTTP request → RPC → Event → Event handler with streaming.
|
|
148
148
|
|
|
149
149
|
```ts
|
|
150
|
-
import {
|
|
150
|
+
import { ServiceBridge } from "service-bridge";
|
|
151
151
|
|
|
152
152
|
// --- Payments service (worker) ---
|
|
153
153
|
|
|
154
|
-
const payments =
|
|
154
|
+
const payments = new ServiceBridge("localhost:14445", process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
155
155
|
|
|
156
|
-
payments.
|
|
156
|
+
payments.rpc.handle("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 ...
|
|
@@ -162,21 +162,21 @@ payments.handleRpc("payment.charge", async (payload: { orderId: string; amount:
|
|
|
162
162
|
return { ok: true, txId: `tx_${Date.now()}` };
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
await payments.
|
|
165
|
+
await payments.start({ host: "localhost" });
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
```ts
|
|
169
169
|
// --- Orders service (caller + event publisher) ---
|
|
170
170
|
|
|
171
|
-
const orders =
|
|
171
|
+
const orders = new 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.invoke<{ ok: boolean; txId: string }>("payment.charge", {
|
|
175
175
|
orderId: "ord_42",
|
|
176
176
|
amount: 4990,
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
-
await orders.
|
|
179
|
+
await orders.events.publish("orders.completed", {
|
|
180
180
|
orderId: "ord_42",
|
|
181
181
|
txId: charge.txId,
|
|
182
182
|
}, {
|
|
@@ -188,25 +188,25 @@ await orders.event("orders.completed", {
|
|
|
188
188
|
```ts
|
|
189
189
|
// --- Notifications service (event consumer) ---
|
|
190
190
|
|
|
191
|
-
const notifications =
|
|
191
|
+
const notifications = new ServiceBridge("localhost:14445", process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
192
192
|
|
|
193
|
-
notifications.
|
|
193
|
+
notifications.events.handle("orders.*", async (payload, ctx) => {
|
|
194
194
|
const body = payload as { orderId: string; txId: string };
|
|
195
195
|
await ctx.stream.write({ status: "sending_email", orderId: body.orderId }, "progress");
|
|
196
196
|
// ... send email ...
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
-
await notifications.
|
|
199
|
+
await notifications.start({ host: "localhost" });
|
|
200
200
|
```
|
|
201
201
|
|
|
202
202
|
```ts
|
|
203
203
|
// --- Orchestrate as a workflow ---
|
|
204
204
|
|
|
205
|
-
await orders.
|
|
206
|
-
{ id: "reserve",
|
|
207
|
-
{ id: "charge",
|
|
208
|
-
{ id: "wait_dlv", type: "event_wait", ref: "shipping.delivered",
|
|
209
|
-
{ id: "notify",
|
|
205
|
+
await orders.workflows.run("order.fulfillment", [
|
|
206
|
+
{ id: "reserve", type: "rpc", service: "inventory", ref: "inventory.reserve" },
|
|
207
|
+
{ id: "charge", type: "rpc", service: "payment", ref: "payment.charge", deps: ["reserve"] },
|
|
208
|
+
{ id: "wait_dlv", type: "event_wait", ref: "shipping.delivered", deps: ["charge"] },
|
|
209
|
+
{ id: "notify", type: "event", ref: "orders.fulfilled", deps: ["wait_dlv"] },
|
|
210
210
|
]);
|
|
211
211
|
```
|
|
212
212
|
|
|
@@ -259,43 +259,51 @@ Every step above — RPC, event publish, event delivery, workflow execution —
|
|
|
259
259
|
|
|
260
260
|
## API Reference
|
|
261
261
|
|
|
262
|
+
### `ServiceBridge` / `ServiceBridgeService` surface
|
|
263
|
+
|
|
264
|
+
Per-instance API for `new ServiceBridge(...)` (implements `ServiceBridgeService`):
|
|
265
|
+
|
|
266
|
+
- **Namespaces:** `rpc` (`handle`, `invoke`, `declare`), `events` (`handle`, `publish`, `publishWorker`, `declare`), `jobs` (`run`), `workflows` (`run`, `declare`).
|
|
267
|
+
- **Lifecycle:** `start(opts?)`, `stop()`.
|
|
268
|
+
- **Workflows:** `cancelWorkflow(traceId)`.
|
|
269
|
+
- **HTTP & traces:** `startHttpSpan(opts)`, `registerHttpEndpoint(opts)`, `watchTrace(traceId, opts?)`.
|
|
270
|
+
- **Module helpers (exported from `service-bridge`):** `getTraceContext`, `withTraceContext`, `ServiceBridgeError`, `mapGrpcStatus`, `SB`, `SB_MESSAGES`. (`captureConsole` exists internally for log capture but is not part of the public package exports.)
|
|
271
|
+
|
|
262
272
|
### Cross-SDK parity notes
|
|
263
273
|
|
|
264
274
|
ServiceBridge keeps the core API shape consistent across Node.js, Go, and Python:
|
|
265
|
-
constructor,
|
|
275
|
+
constructor, `rpc` / `events` / `jobs` / `workflows` namespaces, streams, `start`/`stop`, and `ServiceBridgeError`.
|
|
266
276
|
|
|
267
277
|
Constructor-level defaults for `timeout`, `retries`, and `retryDelay` are available
|
|
268
278
|
across all three SDKs. Parity differences are naming-only (language idioms):
|
|
269
279
|
|
|
270
280
|
- Constructor TLS overrides: `workerTLS`/`caCert` (Node), `WorkerTLS`/`CACert` (Go), `worker_tls`/`ca_cert` (Python)
|
|
271
281
|
- Handler hints: timeout/retryable/concurrency/prefetch are advisory in all SDKs
|
|
272
|
-
- Shared `
|
|
282
|
+
- Shared `start()` fields across SDKs: host, max in-flight, instance ID, weight, and per-start TLS override
|
|
273
283
|
|
|
274
|
-
### `
|
|
284
|
+
### `new ServiceBridge(url, serviceKey, opts?)`
|
|
275
285
|
|
|
276
286
|
```ts
|
|
277
|
-
|
|
278
|
-
url: string,
|
|
279
|
-
|
|
280
|
-
opts?: ServiceBridgeOpts,
|
|
281
|
-
): ServiceBridgeService
|
|
287
|
+
class ServiceBridge {
|
|
288
|
+
constructor(url: string, serviceKey: string, opts?: ServiceBridgeOpts);
|
|
289
|
+
}
|
|
282
290
|
```
|
|
283
291
|
|
|
284
|
-
Creates an SDK client instance.
|
|
285
|
-
Service identity is resolved by the runtime from `serviceKey`.
|
|
292
|
+
Creates an SDK client instance. Service identity is resolved by the runtime from the sbv2 `serviceKey` (key id). Use `new ServiceBridge(...)` as the **public** entry point from the `service-bridge` package (the constructor delegates to the same internal client setup used by the SDK; a lower-level factory exists in source but is **not** exported from the published entry).
|
|
286
293
|
|
|
287
294
|
`ServiceBridgeOpts`:
|
|
288
295
|
|
|
289
296
|
| Option | Type | Default | Description |
|
|
290
297
|
|---|---|---|---|
|
|
291
|
-
| `timeout` | `number` | `30000` | Default hard timeout per
|
|
292
|
-
| `retries` | `number` | `3` | Default retry count for `rpc()`. |
|
|
293
|
-
| `retryDelay` | `number` | `300` | Base backoff delay (ms) for `rpc()`. |
|
|
298
|
+
| `timeout` | `number` | `30000` | Default hard timeout per `rpc.invoke()` attempt (ms). |
|
|
299
|
+
| `retries` | `number` | `3` | Default retry count for `rpc.invoke()`. |
|
|
300
|
+
| `retryDelay` | `number` | `300` | Base backoff delay (ms) for `rpc.invoke()`. |
|
|
294
301
|
| `discoveryRefreshMs` | `number` | `10000` | Discovery refresh period for endpoint updates. |
|
|
295
302
|
| `queueMaxSize` | `number` | `1000` | Max offline queue size for control-plane writes. |
|
|
296
303
|
| `queueOverflow` | `"drop-oldest" \| "drop-newest" \| "error"` | `"drop-oldest"` | Overflow strategy for offline queue. |
|
|
297
304
|
| `heartbeatIntervalMs` | `number` | `10000` | Base heartbeat period for worker registrations. |
|
|
298
305
|
| `captureLogs` | `boolean` | `true` | Forward `console.*` logs to ServiceBridge. |
|
|
306
|
+
| `strictOutboundDeclarations` | `boolean` | `false` | When `true`, every outbound `rpc.invoke()` must be preceded by `rpc.declare(fn)` for the resolved target. |
|
|
299
307
|
|
|
300
308
|
### Advanced TLS overrides
|
|
301
309
|
|
|
@@ -317,15 +325,15 @@ type WorkerTLSOpts = {
|
|
|
317
325
|
|
|
318
326
|
---
|
|
319
327
|
|
|
320
|
-
### `rpc(
|
|
328
|
+
### `rpc.invoke(fn, payload?, opts?)`
|
|
321
329
|
|
|
322
330
|
```ts
|
|
323
|
-
|
|
331
|
+
invoke<T = unknown>(fn: string, payload?: unknown, opts?: RpcOpts): Promise<T>
|
|
324
332
|
```
|
|
325
333
|
|
|
326
|
-
Calls a registered RPC handler on another
|
|
334
|
+
Calls a registered RPC handler on another worker. Direct gRPC path, no proxy.
|
|
327
335
|
|
|
328
|
-
**
|
|
336
|
+
**Function name** — `fn` is a single **global function name** (the same string passed to `rpc.handle` on the callee), e.g. `payment.charge` or `user.get`. It must be unique in the catalog and **must not contain `/`**.
|
|
329
337
|
|
|
330
338
|
`RpcOpts`:
|
|
331
339
|
|
|
@@ -339,13 +347,15 @@ Calls a registered RPC handler on another service. Direct gRPC path, no proxy.
|
|
|
339
347
|
| `mode` | `"direct" \| "proxy"` | Transport mode. `"direct"` (default) connects directly to the worker. `"proxy"` routes through the control plane when direct connection is unavailable. |
|
|
340
348
|
|
|
341
349
|
```ts
|
|
342
|
-
const user = await sb.rpc<{ id: string; name: string }>("
|
|
350
|
+
const user = await sb.rpc.invoke<{ id: string; name: string }>("user.get", { id: "u_1" });
|
|
351
|
+
|
|
352
|
+
const user2 = await sb.rpc.invoke<{ id: string; name: string }>("user.get", { id: "u_1" }, {
|
|
343
353
|
timeout: 5000,
|
|
344
354
|
retries: 2,
|
|
345
355
|
});
|
|
346
356
|
```
|
|
347
357
|
|
|
348
|
-
`rpc()` is bounded even when a downstream worker is silent:
|
|
358
|
+
`rpc.invoke()` is bounded even when a downstream worker is silent:
|
|
349
359
|
each attempt has a hard local timeout, retries are finite (`retries + 1` total attempts),
|
|
350
360
|
and after the final failed attempt the root RPC span is closed with `error`.
|
|
351
361
|
|
|
@@ -353,10 +363,20 @@ Retry delay uses exponential backoff: `retryDelay * 2^(attempt-1)`.
|
|
|
353
363
|
|
|
354
364
|
---
|
|
355
365
|
|
|
356
|
-
### `
|
|
366
|
+
### `rpc.declare(fn)`
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
declare(fn: string): void
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Declares an outbound RPC dependency for registration metadata. When `strictOutboundDeclarations` is `true`, you must call `rpc.declare(fn)` before `rpc.invoke(fn, ...)` for that function. Does not invoke the remote handler.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
### `events.publish(topic, payload?, opts?)`
|
|
357
377
|
|
|
358
378
|
```ts
|
|
359
|
-
|
|
379
|
+
publish(topic: string, payload?: unknown, opts?: EventOpts): Promise<string>
|
|
360
380
|
```
|
|
361
381
|
|
|
362
382
|
Publishes a durable event. Returns `messageId` when online.
|
|
@@ -371,7 +391,7 @@ Publishes a durable event. Returns `messageId` when online.
|
|
|
371
391
|
| `headers` | `Record<string, string>` | Custom metadata headers. |
|
|
372
392
|
|
|
373
393
|
```ts
|
|
374
|
-
await sb.
|
|
394
|
+
await sb.events.publish("orders.created", { orderId: "ord_42" }, {
|
|
375
395
|
idempotencyKey: "order:ord_42",
|
|
376
396
|
headers: { source: "checkout" },
|
|
377
397
|
});
|
|
@@ -379,23 +399,38 @@ await sb.event("orders.created", { orderId: "ord_42" }, {
|
|
|
379
399
|
|
|
380
400
|
---
|
|
381
401
|
|
|
382
|
-
### `
|
|
402
|
+
### `events.publishWorker(topic, payload?, opts?)`
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
publishWorker(
|
|
406
|
+
topic: string,
|
|
407
|
+
payload?: unknown,
|
|
408
|
+
opts?: { traceId?: string; parentSpanId?: string; headers?: Record<string, string> },
|
|
409
|
+
): Promise<string>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Publishes over the worker session stream (after `start()`). If no worker session is active, the promise is rejected.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
### `events.declare(topic)`
|
|
383
417
|
|
|
384
418
|
```ts
|
|
385
|
-
|
|
419
|
+
declare(topic: string): void
|
|
386
420
|
```
|
|
387
421
|
|
|
388
|
-
|
|
422
|
+
Declares an outbound event dependency for registration metadata (does not publish a message).
|
|
389
423
|
|
|
390
424
|
---
|
|
391
425
|
|
|
392
|
-
### `
|
|
426
|
+
### `jobs.run(service, fn, opts)` / `jobs.run(target, opts)`
|
|
393
427
|
|
|
394
428
|
```ts
|
|
395
|
-
|
|
429
|
+
run(service: string, fn: string, opts: ScheduleOpts & { via: "rpc" }): Promise<string>
|
|
430
|
+
run(target: string, opts: ScheduleOpts & { via: "event" | "workflow" }): Promise<string>
|
|
396
431
|
```
|
|
397
432
|
|
|
398
|
-
Registers a scheduled or delayed job.
|
|
433
|
+
Registers a scheduled or delayed job. Resolves to the registration key: `"${service}/${fn}"` for the RPC overload, or the `target` string for the `event` / `workflow` overload.
|
|
399
434
|
|
|
400
435
|
`ScheduleOpts`:
|
|
401
436
|
|
|
@@ -409,32 +444,35 @@ Registers a scheduled or delayed job.
|
|
|
409
444
|
| `retryPolicyJson` | `string` | Retry policy JSON string. |
|
|
410
445
|
|
|
411
446
|
```ts
|
|
412
|
-
|
|
413
|
-
await sb.job("billing", "collect", {
|
|
447
|
+
await sb.jobs.run("payments", "billing.collect", {
|
|
414
448
|
cron: "0 * * * *",
|
|
415
449
|
timezone: "UTC",
|
|
416
450
|
via: "rpc",
|
|
417
451
|
});
|
|
452
|
+
```
|
|
418
453
|
|
|
419
|
-
|
|
420
|
-
await sb.job("user.signup", {
|
|
421
|
-
cron: "0 0 * * *",
|
|
422
|
-
via: "event",
|
|
423
|
-
});
|
|
454
|
+
---
|
|
424
455
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
456
|
+
### `workflows.run(name, steps, opts?)` — register DAG
|
|
457
|
+
|
|
458
|
+
TypeScript (single method; behavior depends on the second argument):
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
run(
|
|
462
|
+
nameOrService: string,
|
|
463
|
+
stepsOrName: WorkflowStep[] | string,
|
|
464
|
+
inputOrOpts?: unknown,
|
|
465
|
+
opts?: ExecuteWorkflowOpts,
|
|
466
|
+
): Promise<string | ExecuteWorkflowResult>
|
|
430
467
|
```
|
|
431
468
|
|
|
432
|
-
|
|
469
|
+
- **Register:** when `stepsOrName` is `WorkflowStep[]`, `nameOrService` is the workflow name, `inputOrOpts` is optional `WorkflowOpts`, and the promise resolves to that name (`string`).
|
|
470
|
+
- **Execute:** when `stepsOrName` is a `string`, `nameOrService` is the target **service** name, `stepsOrName` is the workflow name, `inputOrOpts` is the optional execution input, and `opts` is optional `ExecuteWorkflowOpts` (see execute section below).
|
|
433
471
|
|
|
434
|
-
|
|
472
|
+
Overload as used when registering:
|
|
435
473
|
|
|
436
474
|
```ts
|
|
437
|
-
|
|
475
|
+
run(name: string, steps: WorkflowStep[], opts?: WorkflowOpts): Promise<string>
|
|
438
476
|
```
|
|
439
477
|
|
|
440
478
|
Registers (or updates) a workflow definition as a DAG of typed steps. Returns the workflow name.
|
|
@@ -445,14 +483,14 @@ Registers (or updates) a workflow definition as a DAG of typed steps. Returns th
|
|
|
445
483
|
|---|---|---|
|
|
446
484
|
| `id` | `string` | Unique step identifier in the DAG. |
|
|
447
485
|
| `type` | `"rpc" \| "event" \| "event_wait" \| "sleep" \| "workflow"` | Step execution type. |
|
|
448
|
-
| `service` | `string` |
|
|
449
|
-
| `ref` | `string` |
|
|
486
|
+
| `service` | `string` | Required for `rpc` and `workflow`: target service that owns the function or child workflow. |
|
|
487
|
+
| `ref` | `string` | Target name: RPC function, event topic, waited topic, or child workflow name (per `type`). |
|
|
450
488
|
| `deps` | `string[]` | Dependencies. Empty/omitted means root step. |
|
|
451
489
|
| `if` | `string` | Optional filter expression (step is skipped if false). |
|
|
452
490
|
| `timeoutMs` | `number` | Optional timeout for `rpc` and `event_wait` steps. |
|
|
453
491
|
| `durationMs` | `number` | Required for `sleep` steps. |
|
|
454
492
|
|
|
455
|
-
`WorkflowOpts
|
|
493
|
+
`WorkflowOpts` (third argument when registering a DAG — shape below; the interface is defined in the SDK but **not** re-exported from the main `service-bridge` package entry, so use an inline object in app code):
|
|
456
494
|
|
|
457
495
|
```ts
|
|
458
496
|
interface WorkflowOpts {
|
|
@@ -467,9 +505,9 @@ interface WorkflowOpts {
|
|
|
467
505
|
| `stepTimeoutMs` | `number` | `30000` (30 s) | Default per-step timeout in milliseconds. |
|
|
468
506
|
|
|
469
507
|
```ts
|
|
470
|
-
await sb.
|
|
471
|
-
{ id: "reserve", type: "rpc", service: "inventory", ref: "
|
|
472
|
-
{ id: "charge", type: "rpc", service: "
|
|
508
|
+
await sb.workflows.run("order.fulfillment", [
|
|
509
|
+
{ id: "reserve", type: "rpc", service: "inventory", ref: "inventory.reserve" },
|
|
510
|
+
{ id: "charge", type: "rpc", service: "payment", ref: "payment.charge", deps: ["reserve"] },
|
|
473
511
|
{ id: "wait_5m", type: "sleep", durationMs: 300_000, deps: ["charge"] },
|
|
474
512
|
{ id: "notify", type: "event", ref: "orders.fulfilled", deps: ["wait_5m"] },
|
|
475
513
|
]);
|
|
@@ -478,36 +516,47 @@ await sb.workflow("order.fulfillment", [
|
|
|
478
516
|
With explicit limits:
|
|
479
517
|
|
|
480
518
|
```ts
|
|
481
|
-
await sb.
|
|
519
|
+
await sb.workflows.run("checkout.flow", steps, { stepTimeoutMs: 60_000 });
|
|
482
520
|
```
|
|
483
521
|
|
|
484
522
|
---
|
|
485
523
|
|
|
486
|
-
### `
|
|
524
|
+
### `workflows.declare(service, name)`
|
|
525
|
+
|
|
526
|
+
```ts
|
|
527
|
+
declare(service: string, name: string): void
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Declares an outbound workflow dependency for registration metadata (does not start an execution).
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
### `workflows.run(service, name, input?, opts?)` — execute
|
|
535
|
+
|
|
536
|
+
This is the same `run` method as above when the second argument is the workflow **name** (`string`), not a step array:
|
|
487
537
|
|
|
488
538
|
```ts
|
|
489
|
-
|
|
539
|
+
run(service: string, name: string, input?: unknown, opts?: ExecuteWorkflowOpts): Promise<ExecuteWorkflowResult>
|
|
490
540
|
```
|
|
491
541
|
|
|
492
|
-
Starts a workflow execution on demand. The workflow must be registered first via `
|
|
493
|
-
An alternative to scheduling via `
|
|
542
|
+
Starts a workflow execution on demand. The workflow must be registered first via `workflows.run(name, steps)`.
|
|
543
|
+
An alternative to scheduling via `jobs.run(target, { via: "workflow", ... })` — triggers the execution immediately.
|
|
494
544
|
|
|
495
545
|
| Parameter | Type | Default | Description |
|
|
496
546
|
|---|---|---|---|
|
|
497
|
-
| `service` | `string` | required |
|
|
498
|
-
| `
|
|
499
|
-
| `input` | `unknown` | `undefined` | Optional JSON-serializable input payload. |
|
|
547
|
+
| `service` / `name` | `string` | required | Target service and workflow name. |
|
|
548
|
+
| `input` | `unknown` | `undefined` | Optional JSON-serializable input payload (serialized as JSON for the runtime). |
|
|
500
549
|
|
|
501
|
-
Returns `{ traceId
|
|
550
|
+
Returns `{ traceId }`. Use `traceId` with `watchTrace()` to observe execution in real time.
|
|
502
551
|
|
|
503
|
-
`ExecuteWorkflowOpts
|
|
552
|
+
`ExecuteWorkflowOpts` (optional fourth argument):
|
|
504
553
|
|
|
505
554
|
| Option | Type | Description |
|
|
506
555
|
|---|---|---|
|
|
507
|
-
| `traceId` | `string` |
|
|
556
|
+
| `traceId` | `string` | Declared on the exported type for API parity; the current Node implementation does **not** forward this field to the control plane (the gRPC request is built without it). Prefer relying on the returned `traceId`. |
|
|
508
557
|
|
|
509
558
|
```ts
|
|
510
|
-
const { traceId
|
|
559
|
+
const { traceId } = await sb.workflows.run("users", "user.onboarding", { userId: "u_123" });
|
|
511
560
|
```
|
|
512
561
|
|
|
513
562
|
---
|
|
@@ -526,10 +575,10 @@ await sb.cancelWorkflow("trace_01HQ...XYZ");
|
|
|
526
575
|
|
|
527
576
|
---
|
|
528
577
|
|
|
529
|
-
### `
|
|
578
|
+
### `rpc.handle(fn, handler, opts?)`
|
|
530
579
|
|
|
531
580
|
```ts
|
|
532
|
-
|
|
581
|
+
handle(
|
|
533
582
|
fn: string,
|
|
534
583
|
handler: (payload: unknown, ctx?: RpcContext) => unknown | Promise<unknown>,
|
|
535
584
|
opts?: HandleRpcOpts,
|
|
@@ -557,7 +606,7 @@ Registers an RPC handler. Chainable.
|
|
|
557
606
|
| `allowedCallers` | `string[]` | Allow-list of caller service names. |
|
|
558
607
|
|
|
559
608
|
```ts
|
|
560
|
-
sb.
|
|
609
|
+
sb.rpc.handle("ai.generate", async (payload: { prompt: string }, ctx) => {
|
|
561
610
|
await ctx?.stream.write({ token: "Hello" }, "output");
|
|
562
611
|
await ctx?.stream.write({ token: " world" }, "output");
|
|
563
612
|
return { text: "Hello world" };
|
|
@@ -573,10 +622,10 @@ sb.handleRpc("ai.generate", async (payload: { prompt: string }, ctx) => {
|
|
|
573
622
|
|
|
574
623
|
---
|
|
575
624
|
|
|
576
|
-
### `
|
|
625
|
+
### `events.handle(pattern, handler, opts?)`
|
|
577
626
|
|
|
578
627
|
```ts
|
|
579
|
-
|
|
628
|
+
handle(
|
|
580
629
|
pattern: string,
|
|
581
630
|
handler: (payload: unknown, ctx: EventContext) => void | Promise<void>,
|
|
582
631
|
opts?: HandleEventOpts,
|
|
@@ -594,7 +643,7 @@ Registers an event consumer handler. Chainable.
|
|
|
594
643
|
| `retryPolicyJson` | `string` | Retry policy JSON string. |
|
|
595
644
|
| `filterExpr` | `string` | Server-side filter expression. |
|
|
596
645
|
|
|
597
|
-
|
|
646
|
+
The consumer group name is fixed as `<service-key-id>.<pattern>` (derived from your sbv2 key and the pattern string). Registering a second handler for the same pattern throws a duplicate consumer-group error.
|
|
598
647
|
|
|
599
648
|
**Delivery guarantee**: once a message is accepted by the runtime, delivery to each consumer group
|
|
600
649
|
is guaranteed. If the consumer is offline, the message waits in the server-side queue and is
|
|
@@ -612,7 +661,7 @@ a consumer, the delivery moves to DLQ with reason `delivery_ttl_exceeded`.
|
|
|
612
661
|
- `ctx.stream.write(...)` — append real-time chunks to trace stream
|
|
613
662
|
|
|
614
663
|
```ts
|
|
615
|
-
sb.
|
|
664
|
+
sb.events.handle("orders.*", async (payload, ctx) => {
|
|
616
665
|
const body = payload as { orderId?: string };
|
|
617
666
|
if (!body.orderId) {
|
|
618
667
|
ctx.reject("missing_order_id");
|
|
@@ -624,17 +673,17 @@ sb.handleEvent("orders.*", async (payload, ctx) => {
|
|
|
624
673
|
|
|
625
674
|
---
|
|
626
675
|
|
|
627
|
-
### `
|
|
676
|
+
### `start(opts?)`
|
|
628
677
|
|
|
629
678
|
```ts
|
|
630
|
-
|
|
679
|
+
start(opts?: StartOpts): Promise<void>
|
|
631
680
|
```
|
|
632
681
|
|
|
633
682
|
Starts the worker gRPC server and registers handlers with the control plane.
|
|
634
683
|
The promise resolves once startup/registration is complete (it does not block
|
|
635
|
-
the Node.js process). Throws immediately if no handlers are registered (neither `
|
|
684
|
+
the Node.js process). Throws immediately if no handlers are registered (neither `rpc.handle()` nor `events.handle()` have been called).
|
|
636
685
|
|
|
637
|
-
`
|
|
686
|
+
`StartOpts`:
|
|
638
687
|
|
|
639
688
|
| Option | Type | Description |
|
|
640
689
|
|---|---|---|
|
|
@@ -642,10 +691,10 @@ the Node.js process). Throws immediately if no handlers are registered (neither
|
|
|
642
691
|
| `maxInFlight` | `number` | Max in-flight runtime-originated commands over `OpenWorkerSession`. Default: `128`. |
|
|
643
692
|
| `instanceId` | `string` | Stable worker instance identifier. |
|
|
644
693
|
| `weight` | `number` | Scheduling/discovery weight hint. |
|
|
645
|
-
| `tls` | `WorkerTLSOpts` | Per-
|
|
694
|
+
| `tls` | `WorkerTLSOpts` | Per-start worker TLS override. |
|
|
646
695
|
|
|
647
696
|
```ts
|
|
648
|
-
await sb.
|
|
697
|
+
await sb.start({
|
|
649
698
|
host: "localhost",
|
|
650
699
|
instanceId: process.env.HOSTNAME,
|
|
651
700
|
});
|
|
@@ -702,19 +751,18 @@ registerHttpEndpoint(opts: {
|
|
|
702
751
|
}): Promise<void>
|
|
703
752
|
```
|
|
704
753
|
|
|
705
|
-
Registers HTTP route metadata in the ServiceBridge service catalog.
|
|
706
|
-
Also starts a periodic heartbeat to keep the HTTP endpoint alive in the registry.
|
|
754
|
+
Registers HTTP route metadata in the ServiceBridge service catalog (stored and sent on the next Reconcile). **Requires a completed worker `start()`**: until `start()` has finished successfully, the call resolves but does not record the route (HTTP middleware may invoke `registerHttpEndpoint` on first request; catalog entries appear only after `start()` has run).
|
|
707
755
|
|
|
708
756
|
| Option | Type | Description |
|
|
709
757
|
|---|---|---|
|
|
710
758
|
| `method` | `string` | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, etc. |
|
|
711
759
|
| `route` | `string` | Route pattern with parameter placeholders, e.g. `"/users/:id"`. |
|
|
712
|
-
| `instanceId` | `string` |
|
|
713
|
-
| `endpoint` | `string` |
|
|
760
|
+
| `instanceId` | `string` | Present on the public opts type; **not** applied by the current Node client when building `http_routes` for Reconcile (worker identity comes from `start()`). |
|
|
761
|
+
| `endpoint` | `string` | Same as above — use `start()` / deployment wiring for the reachable worker base URL. |
|
|
714
762
|
| `allowedCallers` | `string[]` | Service names allowed to call (RBAC). |
|
|
715
763
|
| `requestSchemaJson` | `string` | JSON schema for request validation metadata. |
|
|
716
764
|
| `responseSchemaJson` | `string` | JSON schema for response validation metadata. |
|
|
717
|
-
| `transport` | `string` |
|
|
765
|
+
| `transport` | `string` | Present on the public opts type; **not** sent per route in the current Node reconcile payload. |
|
|
718
766
|
|
|
719
767
|
```ts
|
|
720
768
|
await sb.registerHttpEndpoint({
|
|
@@ -777,7 +825,7 @@ for await (const evt of sb.watchTrace(traceId, { key: "output", fromSequence: 0
|
|
|
777
825
|
#### `getTraceContext()`
|
|
778
826
|
|
|
779
827
|
```ts
|
|
780
|
-
getTraceContext():
|
|
828
|
+
getTraceContext(): TraceCtx | undefined
|
|
781
829
|
```
|
|
782
830
|
|
|
783
831
|
Returns the current async-local trace context.
|
|
@@ -794,7 +842,7 @@ if (tc) {
|
|
|
794
842
|
#### `withTraceContext(ctx, fn)`
|
|
795
843
|
|
|
796
844
|
```ts
|
|
797
|
-
withTraceContext<T>(ctx:
|
|
845
|
+
withTraceContext<T>(ctx: TraceCtx, fn: () => T): T
|
|
798
846
|
```
|
|
799
847
|
|
|
800
848
|
Runs a function inside an explicit trace context.
|
|
@@ -803,7 +851,7 @@ Runs a function inside an explicit trace context.
|
|
|
803
851
|
import { withTraceContext } from "service-bridge";
|
|
804
852
|
|
|
805
853
|
withTraceContext({ traceId: "trace-1", spanId: "span-1" }, async () => {
|
|
806
|
-
await sb.
|
|
854
|
+
await sb.events.publish("audit.log", { action: "user.login" });
|
|
807
855
|
});
|
|
808
856
|
```
|
|
809
857
|
|
|
@@ -819,10 +867,10 @@ npm install express
|
|
|
819
867
|
|
|
820
868
|
```ts
|
|
821
869
|
import express from "express";
|
|
822
|
-
import {
|
|
870
|
+
import { ServiceBridge } from "service-bridge";
|
|
823
871
|
import { servicebridgeMiddleware, registerExpressRoutes } from "service-bridge/express";
|
|
824
872
|
|
|
825
|
-
const sb =
|
|
873
|
+
const sb = new ServiceBridge(process.env.SERVICEBRIDGE_URL!, process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
826
874
|
const app = express();
|
|
827
875
|
|
|
828
876
|
app.use(servicebridgeMiddleware({
|
|
@@ -832,7 +880,7 @@ app.use(servicebridgeMiddleware({
|
|
|
832
880
|
}));
|
|
833
881
|
|
|
834
882
|
app.get("/users/:id", async (req, res) => {
|
|
835
|
-
const user = await req.servicebridge.rpc("
|
|
883
|
+
const user = await req.servicebridge.rpc.invoke("user.get", { id: req.params.id });
|
|
836
884
|
res.json(user);
|
|
837
885
|
});
|
|
838
886
|
```
|
|
@@ -875,10 +923,10 @@ npm install fastify
|
|
|
875
923
|
|
|
876
924
|
```ts
|
|
877
925
|
import Fastify from "fastify";
|
|
878
|
-
import {
|
|
926
|
+
import { ServiceBridge } from "service-bridge";
|
|
879
927
|
import { servicebridgePlugin, wrapHandler } from "service-bridge/fastify";
|
|
880
928
|
|
|
881
|
-
const sb =
|
|
929
|
+
const sb = new ServiceBridge(process.env.SERVICEBRIDGE_URL!, process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
882
930
|
const app = Fastify();
|
|
883
931
|
|
|
884
932
|
await app.register(servicebridgePlugin, {
|
|
@@ -888,7 +936,7 @@ await app.register(servicebridgePlugin, {
|
|
|
888
936
|
});
|
|
889
937
|
|
|
890
938
|
app.get("/users/:id", wrapHandler(async (request, reply) => {
|
|
891
|
-
const user = await request.servicebridge.rpc("
|
|
939
|
+
const user = await request.servicebridge.rpc.invoke("user.get", {
|
|
892
940
|
id: (request.params as any).id,
|
|
893
941
|
});
|
|
894
942
|
return reply.send(user);
|
|
@@ -947,11 +995,11 @@ Extracts trace context from HTTP headers. Supports W3C `traceparent`, `x-trace-i
|
|
|
947
995
|
- Embedded/explicit CA PEM is validated with strict x509 parsing.
|
|
948
996
|
- If `workerTLS` is not provided, SDK auto-provisions worker certs via gRPC `ProvisionWorkerCertificate`.
|
|
949
997
|
- `workerTLS.cert` and `workerTLS.key` must be provided together.
|
|
950
|
-
- `
|
|
998
|
+
- `start({ tls })` overrides global `workerTLS` for a specific worker instance.
|
|
951
999
|
|
|
952
1000
|
### Offline queue behavior
|
|
953
1001
|
|
|
954
|
-
When the control plane is unavailable, SDK queues write operations (`
|
|
1002
|
+
When the control plane is unavailable, SDK queues write operations (`events.publish`, `jobs.run`, `workflows.run`, telemetry writes).
|
|
955
1003
|
|
|
956
1004
|
- Queue size: `queueMaxSize` (default: 1000)
|
|
957
1005
|
- Overflow policy: `queueOverflow` (default: `"drop-oldest"`)
|
|
@@ -961,7 +1009,7 @@ When the control plane is unavailable, SDK queues write operations (`event`, `jo
|
|
|
961
1009
|
|
|
962
1010
|
## Environment Variables
|
|
963
1011
|
|
|
964
|
-
The SDK requires values you pass into `
|
|
1012
|
+
The SDK requires values you pass into `new ServiceBridge(...)`. Common setup:
|
|
965
1013
|
|
|
966
1014
|
| Variable | Required | Example | Description |
|
|
967
1015
|
|---|---|---|---|
|
|
@@ -969,7 +1017,7 @@ The SDK requires values you pass into `servicebridge(...)`. Common setup:
|
|
|
969
1017
|
| `SERVICEBRIDGE_SERVICE_KEY` | yes | `sbv2.<id>.<secret>.<ca>` | Service authentication key (sbv2 only) |
|
|
970
1018
|
|
|
971
1019
|
```ts
|
|
972
|
-
const sb =
|
|
1020
|
+
const sb = new ServiceBridge(
|
|
973
1021
|
process.env.SERVICEBRIDGE_URL ?? "localhost:14445",
|
|
974
1022
|
process.env.SERVICEBRIDGE_SERVICE_KEY!,
|
|
975
1023
|
);
|
|
@@ -982,13 +1030,13 @@ const sb = servicebridge(
|
|
|
982
1030
|
`ServiceBridgeError` is exported for normalized SDK and runtime errors.
|
|
983
1031
|
|
|
984
1032
|
```ts
|
|
985
|
-
import {
|
|
1033
|
+
import { ServiceBridge, ServiceBridgeError } from "service-bridge";
|
|
986
1034
|
|
|
987
1035
|
try {
|
|
988
|
-
await sb.rpc("
|
|
1036
|
+
await sb.rpc.invoke("payment.charge", { orderId: "ord_1" });
|
|
989
1037
|
} catch (e) {
|
|
990
1038
|
if (e instanceof ServiceBridgeError) {
|
|
991
|
-
console.error(e.component, e.operation, e.severity, e.retryable, e.code);
|
|
1039
|
+
console.error(e.component, e.operation, e.severity, e.retryable, e.code, e.grpcStatus);
|
|
992
1040
|
}
|
|
993
1041
|
throw e;
|
|
994
1042
|
}
|
|
@@ -999,8 +1047,9 @@ try {
|
|
|
999
1047
|
| `component` | `string` | SDK subsystem (for example, `"rpc"` or `"event"`). |
|
|
1000
1048
|
| `operation` | `string` | Operation that failed. |
|
|
1001
1049
|
| `severity` | `"fatal" \| "retriable" \| "ignorable"` | Error classification. |
|
|
1002
|
-
| `retryable` | `boolean` | Whether retry is recommended. |
|
|
1003
|
-
| `code` | `
|
|
1050
|
+
| `retryable` | `boolean` | Whether retry is recommended (`true` when `severity === "retriable"`). |
|
|
1051
|
+
| `code` | `ServiceBridgeErrorCode` | Stable SDK error id (`SB_*`). |
|
|
1052
|
+
| `grpcStatus` | `number \| undefined` | gRPC status code when the error came from gRPC. |
|
|
1004
1053
|
| `cause` | `unknown` | Original underlying error. |
|
|
1005
1054
|
|
|
1006
1055
|
---
|
|
@@ -1172,12 +1221,12 @@ session.onConfigPush({
|
|
|
1172
1221
|
'payment-svc': { mode: 'proxy', fallbackPolicy: 'fallback_to_direct' },
|
|
1173
1222
|
},
|
|
1174
1223
|
functionOverrides: {
|
|
1175
|
-
'payment
|
|
1224
|
+
'payment.charge': { mode: 'proxy', timeoutMs: 5000 },
|
|
1176
1225
|
},
|
|
1177
1226
|
});
|
|
1178
1227
|
|
|
1179
1228
|
// Разрешить транспорт для функции
|
|
1180
|
-
const mode = session.resolveTransportMode('payment
|
|
1229
|
+
const mode = session.resolveTransportMode('payment.charge'); // 'proxy'
|
|
1181
1230
|
```
|
|
1182
1231
|
|
|
1183
1232
|
### Все события сессии
|
|
@@ -1230,12 +1279,10 @@ const mode = session.resolveTransportMode('payment-svc.charge'); // 'proxy'
|
|
|
1230
1279
|
| `FunctionTransportOverride` | interface | Per-function transport override |
|
|
1231
1280
|
| `ResumeState` | interface | Reconnect resume state |
|
|
1232
1281
|
|
|
1233
|
-
|
|
1282
|
+
From the main entry `service-bridge`, types such as `ServiceBridgeOpts`, `RpcOpts`, `EventOpts`, `HandleRpcOpts`, `HandleEventOpts`, `ScheduleOpts`, `StartOpts`, `ExecuteWorkflowOpts`, and `ExecuteWorkflowResult` are available. The DAG shapes **`WorkflowStep` and `WorkflowOpts` are documented above but are not named exports** from that entry — use inline object literals (inference from `workflows.run(...)`) unless your toolchain exposes deep paths. Example:
|
|
1234
1283
|
|
|
1235
1284
|
```ts
|
|
1236
1285
|
import type {
|
|
1237
|
-
WorkflowStep,
|
|
1238
|
-
WorkerTLSOpts,
|
|
1239
1286
|
RpcContext,
|
|
1240
1287
|
EventContext,
|
|
1241
1288
|
StreamWriter,
|