service-bridge 1.7.0-dev.34 → 1.7.0-dev.51
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 +171 -123
- package/dist/index.js +1011 -572
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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("charge", async (payload: { orderId: string; amount: number }
|
|
|
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,44 +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
|
-
serviceOrOpts?: string | ServiceBridgeOpts,
|
|
281
|
-
maybeGlobalOpts?: ServiceBridgeOpts,
|
|
282
|
-
): ServiceBridgeService
|
|
287
|
+
class ServiceBridge {
|
|
288
|
+
constructor(url: string, serviceKey: string, opts?: ServiceBridgeOpts);
|
|
289
|
+
}
|
|
283
290
|
```
|
|
284
291
|
|
|
285
|
-
Creates an SDK client instance.
|
|
286
|
-
Service identity is resolved by the runtime from `serviceKey`; passing a third `service` argument is legacy-only.
|
|
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).
|
|
287
293
|
|
|
288
294
|
`ServiceBridgeOpts`:
|
|
289
295
|
|
|
290
296
|
| Option | Type | Default | Description |
|
|
291
297
|
|---|---|---|---|
|
|
292
|
-
| `timeout` | `number` | `30000` | Default hard timeout per
|
|
293
|
-
| `retries` | `number` | `3` | Default retry count for `rpc()`. |
|
|
294
|
-
| `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()`. |
|
|
295
301
|
| `discoveryRefreshMs` | `number` | `10000` | Discovery refresh period for endpoint updates. |
|
|
296
302
|
| `queueMaxSize` | `number` | `1000` | Max offline queue size for control-plane writes. |
|
|
297
303
|
| `queueOverflow` | `"drop-oldest" \| "drop-newest" \| "error"` | `"drop-oldest"` | Overflow strategy for offline queue. |
|
|
298
304
|
| `heartbeatIntervalMs` | `number` | `10000` | Base heartbeat period for worker registrations. |
|
|
299
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. |
|
|
300
307
|
|
|
301
308
|
### Advanced TLS overrides
|
|
302
309
|
|
|
@@ -318,22 +325,15 @@ type WorkerTLSOpts = {
|
|
|
318
325
|
|
|
319
326
|
---
|
|
320
327
|
|
|
321
|
-
### `rpc(fn, payload?, opts?)`
|
|
328
|
+
### `rpc.invoke(fn, payload?, opts?)`
|
|
322
329
|
|
|
323
330
|
```ts
|
|
324
|
-
|
|
331
|
+
invoke<T = unknown>(fn: string, payload?: unknown, opts?: RpcOpts): Promise<T>
|
|
325
332
|
```
|
|
326
333
|
|
|
327
|
-
Calls a registered RPC handler on another
|
|
334
|
+
Calls a registered RPC handler on another worker. Direct gRPC path, no proxy.
|
|
328
335
|
|
|
329
|
-
**Function name
|
|
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.
|
|
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 `/`**.
|
|
337
337
|
|
|
338
338
|
`RpcOpts`:
|
|
339
339
|
|
|
@@ -347,17 +347,15 @@ Both formats are interchangeable when the name is unique globally. Canonical for
|
|
|
347
347
|
| `mode` | `"direct" \| "proxy"` | Transport mode. `"direct"` (default) connects directly to the worker. `"proxy"` routes through the control plane when direct connection is unavailable. |
|
|
348
348
|
|
|
349
349
|
```ts
|
|
350
|
-
|
|
351
|
-
const user = await sb.rpc<{ id: string; name: string }>("get", { id: "u_1" });
|
|
350
|
+
const user = await sb.rpc.invoke<{ id: string; name: string }>("user.get", { id: "u_1" });
|
|
352
351
|
|
|
353
|
-
|
|
354
|
-
const user = await sb.rpc<{ id: string; name: string }>("users/get", { id: "u_1" }, {
|
|
352
|
+
const user2 = await sb.rpc.invoke<{ id: string; name: string }>("user.get", { id: "u_1" }, {
|
|
355
353
|
timeout: 5000,
|
|
356
354
|
retries: 2,
|
|
357
355
|
});
|
|
358
356
|
```
|
|
359
357
|
|
|
360
|
-
`rpc()` is bounded even when a downstream worker is silent:
|
|
358
|
+
`rpc.invoke()` is bounded even when a downstream worker is silent:
|
|
361
359
|
each attempt has a hard local timeout, retries are finite (`retries + 1` total attempts),
|
|
362
360
|
and after the final failed attempt the root RPC span is closed with `error`.
|
|
363
361
|
|
|
@@ -365,10 +363,20 @@ Retry delay uses exponential backoff: `retryDelay * 2^(attempt-1)`.
|
|
|
365
363
|
|
|
366
364
|
---
|
|
367
365
|
|
|
368
|
-
### `
|
|
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?)`
|
|
369
377
|
|
|
370
378
|
```ts
|
|
371
|
-
|
|
379
|
+
publish(topic: string, payload?: unknown, opts?: EventOpts): Promise<string>
|
|
372
380
|
```
|
|
373
381
|
|
|
374
382
|
Publishes a durable event. Returns `messageId` when online.
|
|
@@ -383,7 +391,7 @@ Publishes a durable event. Returns `messageId` when online.
|
|
|
383
391
|
| `headers` | `Record<string, string>` | Custom metadata headers. |
|
|
384
392
|
|
|
385
393
|
```ts
|
|
386
|
-
await sb.
|
|
394
|
+
await sb.events.publish("orders.created", { orderId: "ord_42" }, {
|
|
387
395
|
idempotencyKey: "order:ord_42",
|
|
388
396
|
headers: { source: "checkout" },
|
|
389
397
|
});
|
|
@@ -391,23 +399,38 @@ await sb.event("orders.created", { orderId: "ord_42" }, {
|
|
|
391
399
|
|
|
392
400
|
---
|
|
393
401
|
|
|
394
|
-
### `
|
|
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)`
|
|
395
417
|
|
|
396
418
|
```ts
|
|
397
|
-
|
|
419
|
+
declare(topic: string): void
|
|
398
420
|
```
|
|
399
421
|
|
|
400
|
-
|
|
422
|
+
Declares an outbound event dependency for registration metadata (does not publish a message).
|
|
401
423
|
|
|
402
424
|
---
|
|
403
425
|
|
|
404
|
-
### `
|
|
426
|
+
### `jobs.run(service, fn, opts)` / `jobs.run(target, opts)`
|
|
405
427
|
|
|
406
428
|
```ts
|
|
407
|
-
|
|
429
|
+
run(service: string, fn: string, opts: ScheduleOpts & { via: "rpc" }): Promise<string>
|
|
430
|
+
run(target: string, opts: ScheduleOpts & { via: "event" | "workflow" }): Promise<string>
|
|
408
431
|
```
|
|
409
432
|
|
|
410
|
-
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.
|
|
411
434
|
|
|
412
435
|
`ScheduleOpts`:
|
|
413
436
|
|
|
@@ -421,7 +444,7 @@ Registers a scheduled or delayed job.
|
|
|
421
444
|
| `retryPolicyJson` | `string` | Retry policy JSON string. |
|
|
422
445
|
|
|
423
446
|
```ts
|
|
424
|
-
await sb.
|
|
447
|
+
await sb.jobs.run("payments", "billing.collect", {
|
|
425
448
|
cron: "0 * * * *",
|
|
426
449
|
timezone: "UTC",
|
|
427
450
|
via: "rpc",
|
|
@@ -430,10 +453,26 @@ await sb.job("billing/collect", {
|
|
|
430
453
|
|
|
431
454
|
---
|
|
432
455
|
|
|
433
|
-
### `
|
|
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>
|
|
467
|
+
```
|
|
468
|
+
|
|
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).
|
|
471
|
+
|
|
472
|
+
Overload as used when registering:
|
|
434
473
|
|
|
435
474
|
```ts
|
|
436
|
-
|
|
475
|
+
run(name: string, steps: WorkflowStep[], opts?: WorkflowOpts): Promise<string>
|
|
437
476
|
```
|
|
438
477
|
|
|
439
478
|
Registers (or updates) a workflow definition as a DAG of typed steps. Returns the workflow name.
|
|
@@ -444,13 +483,14 @@ Registers (or updates) a workflow definition as a DAG of typed steps. Returns th
|
|
|
444
483
|
|---|---|---|
|
|
445
484
|
| `id` | `string` | Unique step identifier in the DAG. |
|
|
446
485
|
| `type` | `"rpc" \| "event" \| "event_wait" \| "sleep" \| "workflow"` | Step execution type. |
|
|
447
|
-
| `
|
|
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`). |
|
|
448
488
|
| `deps` | `string[]` | Dependencies. Empty/omitted means root step. |
|
|
449
489
|
| `if` | `string` | Optional filter expression (step is skipped if false). |
|
|
450
490
|
| `timeoutMs` | `number` | Optional timeout for `rpc` and `event_wait` steps. |
|
|
451
491
|
| `durationMs` | `number` | Required for `sleep` steps. |
|
|
452
492
|
|
|
453
|
-
`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):
|
|
454
494
|
|
|
455
495
|
```ts
|
|
456
496
|
interface WorkflowOpts {
|
|
@@ -465,9 +505,9 @@ interface WorkflowOpts {
|
|
|
465
505
|
| `stepTimeoutMs` | `number` | `30000` (30 s) | Default per-step timeout in milliseconds. |
|
|
466
506
|
|
|
467
507
|
```ts
|
|
468
|
-
await sb.
|
|
469
|
-
{ id: "reserve", type: "rpc", ref: "inventory
|
|
470
|
-
{ id: "charge", type: "rpc", ref: "
|
|
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"] },
|
|
471
511
|
{ id: "wait_5m", type: "sleep", durationMs: 300_000, deps: ["charge"] },
|
|
472
512
|
{ id: "notify", type: "event", ref: "orders.fulfilled", deps: ["wait_5m"] },
|
|
473
513
|
]);
|
|
@@ -476,35 +516,47 @@ await sb.workflow("order.fulfillment", [
|
|
|
476
516
|
With explicit limits:
|
|
477
517
|
|
|
478
518
|
```ts
|
|
479
|
-
await sb.
|
|
519
|
+
await sb.workflows.run("checkout.flow", steps, { stepTimeoutMs: 60_000 });
|
|
480
520
|
```
|
|
481
521
|
|
|
482
522
|
---
|
|
483
523
|
|
|
484
|
-
### `
|
|
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:
|
|
485
537
|
|
|
486
538
|
```ts
|
|
487
|
-
|
|
539
|
+
run(service: string, name: string, input?: unknown, opts?: ExecuteWorkflowOpts): Promise<ExecuteWorkflowResult>
|
|
488
540
|
```
|
|
489
541
|
|
|
490
|
-
Starts a workflow execution on demand. The workflow must be registered first via `
|
|
491
|
-
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.
|
|
492
544
|
|
|
493
545
|
| Parameter | Type | Default | Description |
|
|
494
546
|
|---|---|---|---|
|
|
495
|
-
| `name` | `string` | required |
|
|
496
|
-
| `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). |
|
|
497
549
|
|
|
498
|
-
Returns `{ traceId
|
|
550
|
+
Returns `{ traceId }`. Use `traceId` with `watchTrace()` to observe execution in real time.
|
|
499
551
|
|
|
500
|
-
`ExecuteWorkflowOpts
|
|
552
|
+
`ExecuteWorkflowOpts` (optional fourth argument):
|
|
501
553
|
|
|
502
554
|
| Option | Type | Description |
|
|
503
555
|
|---|---|---|
|
|
504
|
-
| `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`. |
|
|
505
557
|
|
|
506
558
|
```ts
|
|
507
|
-
const { traceId
|
|
559
|
+
const { traceId } = await sb.workflows.run("users", "user.onboarding", { userId: "u_123" });
|
|
508
560
|
```
|
|
509
561
|
|
|
510
562
|
---
|
|
@@ -523,10 +575,10 @@ await sb.cancelWorkflow("trace_01HQ...XYZ");
|
|
|
523
575
|
|
|
524
576
|
---
|
|
525
577
|
|
|
526
|
-
### `
|
|
578
|
+
### `rpc.handle(fn, handler, opts?)`
|
|
527
579
|
|
|
528
580
|
```ts
|
|
529
|
-
|
|
581
|
+
handle(
|
|
530
582
|
fn: string,
|
|
531
583
|
handler: (payload: unknown, ctx?: RpcContext) => unknown | Promise<unknown>,
|
|
532
584
|
opts?: HandleRpcOpts,
|
|
@@ -554,7 +606,7 @@ Registers an RPC handler. Chainable.
|
|
|
554
606
|
| `allowedCallers` | `string[]` | Allow-list of caller service names. |
|
|
555
607
|
|
|
556
608
|
```ts
|
|
557
|
-
sb.
|
|
609
|
+
sb.rpc.handle("ai.generate", async (payload: { prompt: string }, ctx) => {
|
|
558
610
|
await ctx?.stream.write({ token: "Hello" }, "output");
|
|
559
611
|
await ctx?.stream.write({ token: " world" }, "output");
|
|
560
612
|
return { text: "Hello world" };
|
|
@@ -570,10 +622,10 @@ sb.handleRpc("ai/generate", async (payload: { prompt: string }, ctx) => {
|
|
|
570
622
|
|
|
571
623
|
---
|
|
572
624
|
|
|
573
|
-
### `
|
|
625
|
+
### `events.handle(pattern, handler, opts?)`
|
|
574
626
|
|
|
575
627
|
```ts
|
|
576
|
-
|
|
628
|
+
handle(
|
|
577
629
|
pattern: string,
|
|
578
630
|
handler: (payload: unknown, ctx: EventContext) => void | Promise<void>,
|
|
579
631
|
opts?: HandleEventOpts,
|
|
@@ -586,13 +638,12 @@ Registers an event consumer handler. Chainable.
|
|
|
586
638
|
|
|
587
639
|
| Option | Type | Description |
|
|
588
640
|
|---|---|---|
|
|
589
|
-
| `groupName` | `string` | Consumer group name. Default: `<service-key-id>.<pattern>`. |
|
|
590
641
|
| `concurrency` | `number` | Advisory concurrency hint (currently not hard-enforced). |
|
|
591
642
|
| `prefetch` | `number` | Advisory prefetch hint (currently not hard-enforced). |
|
|
592
643
|
| `retryPolicyJson` | `string` | Retry policy JSON string. |
|
|
593
644
|
| `filterExpr` | `string` | Server-side filter expression. |
|
|
594
645
|
|
|
595
|
-
|
|
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.
|
|
596
647
|
|
|
597
648
|
**Delivery guarantee**: once a message is accepted by the runtime, delivery to each consumer group
|
|
598
649
|
is guaranteed. If the consumer is offline, the message waits in the server-side queue and is
|
|
@@ -610,7 +661,7 @@ a consumer, the delivery moves to DLQ with reason `delivery_ttl_exceeded`.
|
|
|
610
661
|
- `ctx.stream.write(...)` — append real-time chunks to trace stream
|
|
611
662
|
|
|
612
663
|
```ts
|
|
613
|
-
sb.
|
|
664
|
+
sb.events.handle("orders.*", async (payload, ctx) => {
|
|
614
665
|
const body = payload as { orderId?: string };
|
|
615
666
|
if (!body.orderId) {
|
|
616
667
|
ctx.reject("missing_order_id");
|
|
@@ -622,17 +673,17 @@ sb.handleEvent("orders.*", async (payload, ctx) => {
|
|
|
622
673
|
|
|
623
674
|
---
|
|
624
675
|
|
|
625
|
-
### `
|
|
676
|
+
### `start(opts?)`
|
|
626
677
|
|
|
627
678
|
```ts
|
|
628
|
-
|
|
679
|
+
start(opts?: StartOpts): Promise<void>
|
|
629
680
|
```
|
|
630
681
|
|
|
631
682
|
Starts the worker gRPC server and registers handlers with the control plane.
|
|
632
683
|
The promise resolves once startup/registration is complete (it does not block
|
|
633
|
-
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).
|
|
634
685
|
|
|
635
|
-
`
|
|
686
|
+
`StartOpts`:
|
|
636
687
|
|
|
637
688
|
| Option | Type | Description |
|
|
638
689
|
|---|---|---|
|
|
@@ -640,10 +691,10 @@ the Node.js process). Throws immediately if no handlers are registered (neither
|
|
|
640
691
|
| `maxInFlight` | `number` | Max in-flight runtime-originated commands over `OpenWorkerSession`. Default: `128`. |
|
|
641
692
|
| `instanceId` | `string` | Stable worker instance identifier. |
|
|
642
693
|
| `weight` | `number` | Scheduling/discovery weight hint. |
|
|
643
|
-
| `tls` | `WorkerTLSOpts` | Per-
|
|
694
|
+
| `tls` | `WorkerTLSOpts` | Per-start worker TLS override. |
|
|
644
695
|
|
|
645
696
|
```ts
|
|
646
|
-
await sb.
|
|
697
|
+
await sb.start({
|
|
647
698
|
host: "localhost",
|
|
648
699
|
instanceId: process.env.HOSTNAME,
|
|
649
700
|
});
|
|
@@ -700,19 +751,18 @@ registerHttpEndpoint(opts: {
|
|
|
700
751
|
}): Promise<void>
|
|
701
752
|
```
|
|
702
753
|
|
|
703
|
-
Registers HTTP route metadata in the ServiceBridge service catalog.
|
|
704
|
-
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).
|
|
705
755
|
|
|
706
756
|
| Option | Type | Description |
|
|
707
757
|
|---|---|---|
|
|
708
758
|
| `method` | `string` | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, etc. |
|
|
709
759
|
| `route` | `string` | Route pattern with parameter placeholders, e.g. `"/users/:id"`. |
|
|
710
|
-
| `instanceId` | `string` |
|
|
711
|
-
| `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. |
|
|
712
762
|
| `allowedCallers` | `string[]` | Service names allowed to call (RBAC). |
|
|
713
763
|
| `requestSchemaJson` | `string` | JSON schema for request validation metadata. |
|
|
714
764
|
| `responseSchemaJson` | `string` | JSON schema for response validation metadata. |
|
|
715
|
-
| `transport` | `string` |
|
|
765
|
+
| `transport` | `string` | Present on the public opts type; **not** sent per route in the current Node reconcile payload. |
|
|
716
766
|
|
|
717
767
|
```ts
|
|
718
768
|
await sb.registerHttpEndpoint({
|
|
@@ -775,7 +825,7 @@ for await (const evt of sb.watchTrace(traceId, { key: "output", fromSequence: 0
|
|
|
775
825
|
#### `getTraceContext()`
|
|
776
826
|
|
|
777
827
|
```ts
|
|
778
|
-
getTraceContext():
|
|
828
|
+
getTraceContext(): TraceCtx | undefined
|
|
779
829
|
```
|
|
780
830
|
|
|
781
831
|
Returns the current async-local trace context.
|
|
@@ -792,7 +842,7 @@ if (tc) {
|
|
|
792
842
|
#### `withTraceContext(ctx, fn)`
|
|
793
843
|
|
|
794
844
|
```ts
|
|
795
|
-
withTraceContext<T>(ctx:
|
|
845
|
+
withTraceContext<T>(ctx: TraceCtx, fn: () => T): T
|
|
796
846
|
```
|
|
797
847
|
|
|
798
848
|
Runs a function inside an explicit trace context.
|
|
@@ -801,7 +851,7 @@ Runs a function inside an explicit trace context.
|
|
|
801
851
|
import { withTraceContext } from "service-bridge";
|
|
802
852
|
|
|
803
853
|
withTraceContext({ traceId: "trace-1", spanId: "span-1" }, async () => {
|
|
804
|
-
await sb.
|
|
854
|
+
await sb.events.publish("audit.log", { action: "user.login" });
|
|
805
855
|
});
|
|
806
856
|
```
|
|
807
857
|
|
|
@@ -817,10 +867,10 @@ npm install express
|
|
|
817
867
|
|
|
818
868
|
```ts
|
|
819
869
|
import express from "express";
|
|
820
|
-
import {
|
|
870
|
+
import { ServiceBridge } from "service-bridge";
|
|
821
871
|
import { servicebridgeMiddleware, registerExpressRoutes } from "service-bridge/express";
|
|
822
872
|
|
|
823
|
-
const sb =
|
|
873
|
+
const sb = new ServiceBridge(process.env.SERVICEBRIDGE_URL!, process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
824
874
|
const app = express();
|
|
825
875
|
|
|
826
876
|
app.use(servicebridgeMiddleware({
|
|
@@ -830,7 +880,7 @@ app.use(servicebridgeMiddleware({
|
|
|
830
880
|
}));
|
|
831
881
|
|
|
832
882
|
app.get("/users/:id", async (req, res) => {
|
|
833
|
-
const user = await req.servicebridge.rpc("
|
|
883
|
+
const user = await req.servicebridge.rpc.invoke("user.get", { id: req.params.id });
|
|
834
884
|
res.json(user);
|
|
835
885
|
});
|
|
836
886
|
```
|
|
@@ -873,10 +923,10 @@ npm install fastify
|
|
|
873
923
|
|
|
874
924
|
```ts
|
|
875
925
|
import Fastify from "fastify";
|
|
876
|
-
import {
|
|
926
|
+
import { ServiceBridge } from "service-bridge";
|
|
877
927
|
import { servicebridgePlugin, wrapHandler } from "service-bridge/fastify";
|
|
878
928
|
|
|
879
|
-
const sb =
|
|
929
|
+
const sb = new ServiceBridge(process.env.SERVICEBRIDGE_URL!, process.env.SERVICEBRIDGE_SERVICE_KEY!);
|
|
880
930
|
const app = Fastify();
|
|
881
931
|
|
|
882
932
|
await app.register(servicebridgePlugin, {
|
|
@@ -886,7 +936,7 @@ await app.register(servicebridgePlugin, {
|
|
|
886
936
|
});
|
|
887
937
|
|
|
888
938
|
app.get("/users/:id", wrapHandler(async (request, reply) => {
|
|
889
|
-
const user = await request.servicebridge.rpc("
|
|
939
|
+
const user = await request.servicebridge.rpc.invoke("user.get", {
|
|
890
940
|
id: (request.params as any).id,
|
|
891
941
|
});
|
|
892
942
|
return reply.send(user);
|
|
@@ -945,11 +995,11 @@ Extracts trace context from HTTP headers. Supports W3C `traceparent`, `x-trace-i
|
|
|
945
995
|
- Embedded/explicit CA PEM is validated with strict x509 parsing.
|
|
946
996
|
- If `workerTLS` is not provided, SDK auto-provisions worker certs via gRPC `ProvisionWorkerCertificate`.
|
|
947
997
|
- `workerTLS.cert` and `workerTLS.key` must be provided together.
|
|
948
|
-
- `
|
|
998
|
+
- `start({ tls })` overrides global `workerTLS` for a specific worker instance.
|
|
949
999
|
|
|
950
1000
|
### Offline queue behavior
|
|
951
1001
|
|
|
952
|
-
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).
|
|
953
1003
|
|
|
954
1004
|
- Queue size: `queueMaxSize` (default: 1000)
|
|
955
1005
|
- Overflow policy: `queueOverflow` (default: `"drop-oldest"`)
|
|
@@ -959,7 +1009,7 @@ When the control plane is unavailable, SDK queues write operations (`event`, `jo
|
|
|
959
1009
|
|
|
960
1010
|
## Environment Variables
|
|
961
1011
|
|
|
962
|
-
The SDK requires values you pass into `
|
|
1012
|
+
The SDK requires values you pass into `new ServiceBridge(...)`. Common setup:
|
|
963
1013
|
|
|
964
1014
|
| Variable | Required | Example | Description |
|
|
965
1015
|
|---|---|---|---|
|
|
@@ -967,7 +1017,7 @@ The SDK requires values you pass into `servicebridge(...)`. Common setup:
|
|
|
967
1017
|
| `SERVICEBRIDGE_SERVICE_KEY` | yes | `sbv2.<id>.<secret>.<ca>` | Service authentication key (sbv2 only) |
|
|
968
1018
|
|
|
969
1019
|
```ts
|
|
970
|
-
const sb =
|
|
1020
|
+
const sb = new ServiceBridge(
|
|
971
1021
|
process.env.SERVICEBRIDGE_URL ?? "localhost:14445",
|
|
972
1022
|
process.env.SERVICEBRIDGE_SERVICE_KEY!,
|
|
973
1023
|
);
|
|
@@ -980,13 +1030,13 @@ const sb = servicebridge(
|
|
|
980
1030
|
`ServiceBridgeError` is exported for normalized SDK and runtime errors.
|
|
981
1031
|
|
|
982
1032
|
```ts
|
|
983
|
-
import {
|
|
1033
|
+
import { ServiceBridge, ServiceBridgeError } from "service-bridge";
|
|
984
1034
|
|
|
985
1035
|
try {
|
|
986
|
-
await sb.rpc("
|
|
1036
|
+
await sb.rpc.invoke("payment.charge", { orderId: "ord_1" });
|
|
987
1037
|
} catch (e) {
|
|
988
1038
|
if (e instanceof ServiceBridgeError) {
|
|
989
|
-
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);
|
|
990
1040
|
}
|
|
991
1041
|
throw e;
|
|
992
1042
|
}
|
|
@@ -997,8 +1047,9 @@ try {
|
|
|
997
1047
|
| `component` | `string` | SDK subsystem (for example, `"rpc"` or `"event"`). |
|
|
998
1048
|
| `operation` | `string` | Operation that failed. |
|
|
999
1049
|
| `severity` | `"fatal" \| "retriable" \| "ignorable"` | Error classification. |
|
|
1000
|
-
| `retryable` | `boolean` | Whether retry is recommended. |
|
|
1001
|
-
| `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. |
|
|
1002
1053
|
| `cause` | `unknown` | Original underlying error. |
|
|
1003
1054
|
|
|
1004
1055
|
---
|
|
@@ -1055,7 +1106,6 @@ import { V2SessionClient, validateV2Config } from 'service-bridge';
|
|
|
1055
1106
|
|
|
1056
1107
|
const cfg = {
|
|
1057
1108
|
serverAddress: 'localhost:9090',
|
|
1058
|
-
serviceName: 'my-worker',
|
|
1059
1109
|
instanceId: 'worker-1',
|
|
1060
1110
|
zone: 'us-east-1a',
|
|
1061
1111
|
transportMode: 'direct' as const,
|
|
@@ -1171,12 +1221,12 @@ session.onConfigPush({
|
|
|
1171
1221
|
'payment-svc': { mode: 'proxy', fallbackPolicy: 'fallback_to_direct' },
|
|
1172
1222
|
},
|
|
1173
1223
|
functionOverrides: {
|
|
1174
|
-
'payment
|
|
1224
|
+
'payment.charge': { mode: 'proxy', timeoutMs: 5000 },
|
|
1175
1225
|
},
|
|
1176
1226
|
});
|
|
1177
1227
|
|
|
1178
1228
|
// Разрешить транспорт для функции
|
|
1179
|
-
const mode = session.resolveTransportMode('payment
|
|
1229
|
+
const mode = session.resolveTransportMode('payment.charge'); // 'proxy'
|
|
1180
1230
|
```
|
|
1181
1231
|
|
|
1182
1232
|
### Все события сессии
|
|
@@ -1229,12 +1279,10 @@ const mode = session.resolveTransportMode('payment-svc/charge'); // 'proxy'
|
|
|
1229
1279
|
| `FunctionTransportOverride` | interface | Per-function transport override |
|
|
1230
1280
|
| `ResumeState` | interface | Reconnect resume state |
|
|
1231
1281
|
|
|
1232
|
-
|
|
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:
|
|
1233
1283
|
|
|
1234
1284
|
```ts
|
|
1235
1285
|
import type {
|
|
1236
|
-
WorkflowStep,
|
|
1237
|
-
WorkerTLSOpts,
|
|
1238
1286
|
RpcContext,
|
|
1239
1287
|
EventContext,
|
|
1240
1288
|
StreamWriter,
|