service-bridge 1.7.0-dev.50 → 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.
Files changed (3) hide show
  1. package/README.md +175 -108
  2. package/dist/index.js +384 -361
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -102,12 +102,12 @@ var SB_MESSAGES = {
102
102
  [SB.WORKER_TLS_CA_REQUIRED]: "A TLS CA certificate is required when starting the worker with TLS.",
103
103
  [SB.DUPLICATE_EVENT_CONSUMER_GROUP]: "Duplicate event consumer group for this pattern; use a unique pattern per handler.",
104
104
  [SB.FROZEN_AFTER_START]: "This registration cannot be changed after start(); configure handlers and outbound declarations before start().",
105
- [SB.UNDECLARED_OUTBOUND_RPC]: "Outbound RPC was not declared; call callsRpc(fn) before invoking this RPC when strict outbound declarations are enabled.",
105
+ [SB.UNDECLARED_OUTBOUND_RPC]: "Outbound RPC was not declared; call rpc.declare(fn) before invoking this RPC when strict outbound declarations are enabled.",
106
106
  [SB.RECONCILE_FAILED_PRECONDITION]: "Registration reconcile failed: the runtime rejected the current declaration state (FailedPrecondition).",
107
- [SB.JOB_RPC_ARGS_REQUIRED]: "job(): service and function name are required for RPC jobs.",
108
- [SB.JOB_VIA_MISMATCH]: "job(): via must be 'rpc' when using the (service, fn, opts) form.",
109
- [SB.JOB_TARGET_FORM_REQUIRED]: "job(): use job(service, fn, opts) when via is 'rpc'.",
110
- [SB.EXECUTE_WORKFLOW_ARGS_REQUIRED]: "executeWorkflow(): service name and workflow name are required.",
107
+ [SB.JOB_RPC_ARGS_REQUIRED]: "jobs.run(): service and function name are required for RPC jobs.",
108
+ [SB.JOB_VIA_MISMATCH]: "jobs.run(): via must be 'rpc' when using the (service, fn, opts) form.",
109
+ [SB.JOB_TARGET_FORM_REQUIRED]: "jobs.run(): use jobs.run(service, fn, opts) when via is 'rpc'.",
110
+ [SB.EXECUTE_WORKFLOW_ARGS_REQUIRED]: "workflows.run(): service name and workflow name are required.",
111
111
  [SB.WORKFLOW_NOT_CANCELLED]: "The workflow was not cancelled.",
112
112
  [SB.INTERNAL_INVARIANT_VIOLATION]: "Internal SDK invariant was violated.",
113
113
  [SB.HANDLER_RETURNED_FAILURE]: "The RPC handler reported a failure.",
@@ -2030,7 +2030,7 @@ function servicebridge(url, serviceKey, opts = {}) {
2030
2030
  return { ack: true };
2031
2031
  });
2032
2032
  }
2033
- async function handleRpc(call, cb) {
2033
+ async function workerGrpcHandle(call, cb) {
2034
2034
  const entry = lookupFnHandler(call.request.function_name || "");
2035
2035
  const allowedCallers = entry?.opts.allowedCallers;
2036
2036
  if (!requirePeerCN(call, allowedCallers, false)) {
@@ -2365,293 +2365,406 @@ function servicebridge(url, serviceKey, opts = {}) {
2365
2365
  return;
2366
2366
  }
2367
2367
  }
2368
- const svc = {
2369
- async rpc(fn, payload, opts2) {
2370
- try {
2371
- await _controlReady;
2372
- } catch (err) {
2373
- throw normalizeServiceError(err, "control-plane");
2374
- }
2375
- const lookupKey = fn.trim();
2376
- if (!lookupKey) {
2377
- throw new ServiceBridgeError({
2378
- code: SB.RPC_FN_REQUIRED,
2379
- message: SB_MESSAGES[SB.RPC_FN_REQUIRED],
2380
- component: "sdk",
2381
- operation: "rpc",
2382
- severity: "fatal"
2383
- });
2384
- }
2385
- const tc = traceStorage.getStore();
2386
- const traceId = opts2?.traceId ?? tc?.traceId ?? crypto.randomUUID();
2387
- const maxRetries = normalizeNonNegativeInt(opts2?.retries ?? globalOpts.retries ?? 3, 3);
2388
- const baseDelay = normalizePositiveInt(opts2?.retryDelay ?? globalOpts.retryDelay ?? 300, 300);
2389
- const timeout = normalizePositiveInt(opts2?.timeout ?? globalOpts.timeout ?? 30000, 30000);
2390
- let functionKey = resolveFunctionKey(lookupKey);
2391
- if (!functionKey) {
2392
- await doLookupFunction(lookupKey);
2393
- functionKey = resolveFunctionKey(lookupKey);
2394
- }
2395
- if (!functionKey) {
2396
- throw new ServiceBridgeError({
2397
- code: SB.NO_RPC_ENDPOINTS,
2398
- message: `No endpoints available for RPC: ${lookupKey}`,
2399
- component: "worker",
2400
- operation: lookupKey,
2401
- severity: "fatal"
2402
- });
2403
- }
2404
- assertOutboundRpcDeclaredForTarget(functionKey);
2405
- const fmeta = functionMeta.get(functionKey);
2406
- if (fmeta && !containsOrAll(fmeta.allowedCallers, service)) {
2407
- throw new ServiceBridgeError({
2408
- code: SB.RPC_CALLER_NOT_ALLOWED,
2409
- message: `Service "${service}" is not allowed to call "${lookupKey}". Permitted callers: ${fmeta.allowedCallers.join(", ") || "(none)"}.`,
2410
- component: "sdk",
2411
- operation: "rpc",
2412
- severity: "fatal"
2368
+ async function rpcInvoke(fn, payload, opts2) {
2369
+ try {
2370
+ await _controlReady;
2371
+ } catch (err) {
2372
+ throw normalizeServiceError(err, "control-plane");
2373
+ }
2374
+ const lookupKey = fn.trim();
2375
+ if (!lookupKey) {
2376
+ throw new ServiceBridgeError({
2377
+ code: SB.RPC_FN_REQUIRED,
2378
+ message: SB_MESSAGES[SB.RPC_FN_REQUIRED],
2379
+ component: "sdk",
2380
+ operation: "rpc",
2381
+ severity: "fatal"
2382
+ });
2383
+ }
2384
+ const tc = traceStorage.getStore();
2385
+ const traceId = opts2?.traceId ?? tc?.traceId ?? crypto.randomUUID();
2386
+ const maxRetries = normalizeNonNegativeInt(opts2?.retries ?? globalOpts.retries ?? 3, 3);
2387
+ const baseDelay = normalizePositiveInt(opts2?.retryDelay ?? globalOpts.retryDelay ?? 300, 300);
2388
+ const timeout = normalizePositiveInt(opts2?.timeout ?? globalOpts.timeout ?? 30000, 30000);
2389
+ let functionKey = resolveFunctionKey(lookupKey);
2390
+ if (!functionKey) {
2391
+ await doLookupFunction(lookupKey);
2392
+ functionKey = resolveFunctionKey(lookupKey);
2393
+ }
2394
+ if (!functionKey) {
2395
+ throw new ServiceBridgeError({
2396
+ code: SB.NO_RPC_ENDPOINTS,
2397
+ message: `No endpoints available for RPC: ${lookupKey}`,
2398
+ component: "worker",
2399
+ operation: lookupKey,
2400
+ severity: "fatal"
2401
+ });
2402
+ }
2403
+ assertOutboundRpcDeclaredForTarget(functionKey);
2404
+ const fmeta = functionMeta.get(functionKey);
2405
+ if (fmeta && !containsOrAll(fmeta.allowedCallers, service)) {
2406
+ throw new ServiceBridgeError({
2407
+ code: SB.RPC_CALLER_NOT_ALLOWED,
2408
+ message: `Service "${service}" is not allowed to call "${lookupKey}". Permitted callers: ${fmeta.allowedCallers.join(", ") || "(none)"}.`,
2409
+ component: "sdk",
2410
+ operation: "rpc",
2411
+ severity: "fatal"
2412
+ });
2413
+ }
2414
+ const inputSchema = fmeta?.inputSchema;
2415
+ const outputSchema = fmeta?.outputSchema;
2416
+ const inputBuf = inputSchema ? encodeWithSchema(inputSchema, payload) : toJsonBuffer(payload);
2417
+ const telemetryInputBuf = toJsonBuffer(payload);
2418
+ const rpcFnName = fmeta?.fnName ?? lookupKey;
2419
+ const rpcServiceName = fmeta?.serviceName ?? "";
2420
+ const rootSpanId = crypto.randomUUID();
2421
+ const parentSpanId = opts2?.parentSpanId ?? tc?.spanId ?? "";
2422
+ const rootStartedAt = Date.now();
2423
+ const hasRetries = maxRetries > 0;
2424
+ reportCallStartAsync(traceId, rootSpanId, parentSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, 1, "rpc", rpcServiceName);
2425
+ return traceStorage.run({ traceId, spanId: rootSpanId }, async () => {
2426
+ if (opts2?.mode === "proxy") {
2427
+ return await new Promise((resolve, reject) => {
2428
+ stub.ProxyCall({
2429
+ function_name: rpcFnName,
2430
+ payload: inputBuf,
2431
+ trace_id: traceId,
2432
+ span_id: rootSpanId,
2433
+ timeout_ms: timeout
2434
+ }, meta, { deadline: new Date(Date.now() + timeout) }, (err, res) => {
2435
+ if (err) {
2436
+ reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, false, 1, undefined, String(err), "rpc", parentSpanId, rpcServiceName);
2437
+ return reject(normalizeServiceError(err, lookupKey, "control-plane"));
2438
+ }
2439
+ if (!res?.success) {
2440
+ const errMsg = res?.error ?? "proxy call failed";
2441
+ reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, false, 1, undefined, errMsg, "rpc", parentSpanId, rpcServiceName);
2442
+ return reject(normalizeServiceError(new Error(errMsg), lookupKey, "worker"));
2443
+ }
2444
+ reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, true, 1, res.output, "", "rpc", parentSpanId, rpcServiceName);
2445
+ try {
2446
+ const out = res.output?.length ? outputSchema ? decodeWithSchema(outputSchema, res.output) : JSON.parse(res.output.toString()) : undefined;
2447
+ resolve(out);
2448
+ } catch (parseErr) {
2449
+ reject(new Error(`Failed to parse proxy response: ${parseErr}`));
2450
+ }
2451
+ });
2413
2452
  });
2414
2453
  }
2415
- const inputSchema = fmeta?.inputSchema;
2416
- const outputSchema = fmeta?.outputSchema;
2417
- const inputBuf = inputSchema ? encodeWithSchema(inputSchema, payload) : toJsonBuffer(payload);
2418
- const telemetryInputBuf = toJsonBuffer(payload);
2419
- const rpcFnName = fmeta?.fnName ?? lookupKey;
2420
- const rpcServiceName = fmeta?.serviceName ?? "";
2421
- const rootSpanId = crypto.randomUUID();
2422
- const parentSpanId = opts2?.parentSpanId ?? tc?.spanId ?? "";
2423
- const rootStartedAt = Date.now();
2424
- const hasRetries = maxRetries > 0;
2425
- reportCallStartAsync(traceId, rootSpanId, parentSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, 1, "rpc", rpcServiceName);
2426
- return traceStorage.run({ traceId, spanId: rootSpanId }, async () => {
2427
- if (opts2?.mode === "proxy") {
2428
- return await new Promise((resolve, reject) => {
2429
- stub.ProxyCall({
2430
- function_name: rpcFnName,
2431
- payload: inputBuf,
2432
- trace_id: traceId,
2433
- span_id: rootSpanId,
2434
- timeout_ms: timeout
2435
- }, meta, { deadline: new Date(Date.now() + timeout) }, (err, res) => {
2436
- if (err) {
2437
- reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, false, 1, undefined, String(err), "rpc", parentSpanId, rpcServiceName);
2438
- return reject(normalizeServiceError(err, lookupKey, "control-plane"));
2439
- }
2440
- if (!res?.success) {
2441
- const errMsg = res?.error ?? "proxy call failed";
2442
- reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, false, 1, undefined, errMsg, "rpc", parentSpanId, rpcServiceName);
2443
- return reject(normalizeServiceError(new Error(errMsg), lookupKey, "worker"));
2444
- }
2445
- reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, true, 1, res.output, "", "rpc", parentSpanId, rpcServiceName);
2446
- try {
2447
- const out = res.output?.length ? outputSchema ? decodeWithSchema(outputSchema, res.output) : JSON.parse(res.output.toString()) : undefined;
2448
- resolve(out);
2449
- } catch (parseErr) {
2450
- reject(new Error(`Failed to parse proxy response: ${parseErr}`));
2451
- }
2452
- });
2453
- });
2454
+ await ensureCallerTLS();
2455
+ const channel = getOrCreateFunctionChannel(functionKey);
2456
+ const workerClient = new WorkerHandlerService("__channel__", grpc2.credentials.createInsecure(), { channelOverride: channel });
2457
+ let lastError;
2458
+ for (let attempt = 1;attempt <= maxRetries + 1; attempt++) {
2459
+ const attemptSpanId = hasRetries ? crypto.randomUUID() : rootSpanId;
2460
+ const attemptParentId = hasRetries ? rootSpanId : parentSpanId;
2461
+ const attemptStartedAt = hasRetries ? Date.now() : rootStartedAt;
2462
+ if (hasRetries) {
2463
+ reportCallStartAsync(traceId, attemptSpanId, attemptParentId, rpcFnName, attemptStartedAt, telemetryInputBuf, attempt, "rpc", rpcServiceName);
2454
2464
  }
2455
- await ensureCallerTLS();
2456
- const channel = getOrCreateFunctionChannel(functionKey);
2457
- const workerClient = new WorkerHandlerService("__channel__", grpc2.credentials.createInsecure(), { channelOverride: channel });
2458
- let lastError;
2459
- for (let attempt = 1;attempt <= maxRetries + 1; attempt++) {
2460
- const attemptSpanId = hasRetries ? crypto.randomUUID() : rootSpanId;
2461
- const attemptParentId = hasRetries ? rootSpanId : parentSpanId;
2462
- const attemptStartedAt = hasRetries ? Date.now() : rootStartedAt;
2463
- if (hasRetries) {
2464
- reportCallStartAsync(traceId, attemptSpanId, attemptParentId, rpcFnName, attemptStartedAt, telemetryInputBuf, attempt, "rpc", rpcServiceName);
2465
+ try {
2466
+ const attemptTimeoutMs = timeout;
2467
+ const deadline = new Date(Date.now() + attemptTimeoutMs);
2468
+ const workerFnName = fmeta?.fnName ?? lookupKey;
2469
+ if (!workerFnName) {
2470
+ throw new ServiceBridgeError({
2471
+ code: SB.INTERNAL_INVARIANT_VIOLATION,
2472
+ message: SB_MESSAGES[SB.INTERNAL_INVARIANT_VIOLATION],
2473
+ component: "sdk",
2474
+ operation: "rpc",
2475
+ severity: "fatal"
2476
+ });
2465
2477
  }
2466
- try {
2467
- const attemptTimeoutMs = timeout;
2468
- const deadline = new Date(Date.now() + attemptTimeoutMs);
2469
- const workerFnName = fmeta?.fnName ?? lookupKey;
2470
- if (!workerFnName) {
2471
- throw new ServiceBridgeError({
2472
- code: SB.INTERNAL_INVARIANT_VIOLATION,
2473
- message: SB_MESSAGES[SB.INTERNAL_INVARIANT_VIOLATION],
2478
+ const res = await new Promise((resolve, reject) => {
2479
+ let settled = false;
2480
+ let unaryCall;
2481
+ const timeoutTimer = setTimeout(() => {
2482
+ if (settled)
2483
+ return;
2484
+ settled = true;
2485
+ try {
2486
+ unaryCall?.cancel?.();
2487
+ } catch {}
2488
+ reject(new ServiceBridgeError({
2489
+ code: SB.NON_GRPC,
2490
+ message: `rpc attempt timeout after ${attemptTimeoutMs}ms`,
2474
2491
  component: "sdk",
2475
2492
  operation: "rpc",
2476
2493
  severity: "fatal"
2477
- });
2478
- }
2479
- const res = await new Promise((resolve, reject) => {
2480
- let settled = false;
2481
- let unaryCall;
2482
- const timeoutTimer = setTimeout(() => {
2483
- if (settled)
2484
- return;
2485
- settled = true;
2486
- try {
2487
- unaryCall?.cancel?.();
2488
- } catch {}
2489
- reject(new ServiceBridgeError({
2490
- code: SB.NON_GRPC,
2491
- message: `rpc attempt timeout after ${attemptTimeoutMs}ms`,
2492
- component: "sdk",
2493
- operation: "rpc",
2494
- severity: "fatal"
2495
- }));
2496
- }, attemptTimeoutMs);
2497
- const finish = (err, response) => {
2498
- if (settled)
2499
- return;
2500
- settled = true;
2501
- clearTimeout(timeoutTimer);
2502
- if (err) {
2503
- reject(err);
2504
- return;
2505
- }
2506
- resolve(response ?? { success: false, error: "empty response" });
2507
- };
2508
- try {
2509
- unaryCall = workerClient.Handle({
2510
- function_name: workerFnName,
2511
- payload: inputBuf,
2512
- trace_id: traceId,
2513
- span_id: attemptSpanId,
2514
- parent_span_id: hasRetries ? rootSpanId : ""
2515
- }, makeWorkerMeta(), { deadline, waitForReady: true }, finish);
2516
- } catch (err) {
2517
- finish(err);
2494
+ }));
2495
+ }, attemptTimeoutMs);
2496
+ const finish = (err, response) => {
2497
+ if (settled)
2498
+ return;
2499
+ settled = true;
2500
+ clearTimeout(timeoutTimer);
2501
+ if (err) {
2502
+ reject(err);
2503
+ return;
2518
2504
  }
2519
- });
2520
- if (!res?.success) {
2521
- throw new ServiceBridgeError({
2522
- code: SB.HANDLER_RETURNED_FAILURE,
2523
- message: res?.error?.trim() || SB_MESSAGES[SB.HANDLER_RETURNED_FAILURE],
2524
- component: "worker",
2525
- operation: "rpc",
2526
- severity: "fatal"
2527
- });
2528
- }
2529
- const outputBuf = res.output ?? Buffer.alloc(0);
2530
- const result = outputSchema ? decodeWithSchema(outputSchema, outputBuf) : outputBuf?.length ? JSON.parse(outputBuf.toString()) : {};
2531
- const encodedOutput = outputBuf?.length ? toJsonBuffer(result) : Buffer.alloc(0);
2532
- if (hasRetries) {
2533
- reportCallAsync(traceId, attemptSpanId, rpcFnName, attemptStartedAt, telemetryInputBuf, true, attempt, encodedOutput, "", "rpc", attemptParentId, rpcServiceName);
2534
- reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, true, attempt, encodedOutput, "", "rpc", parentSpanId, rpcServiceName);
2535
- } else {
2536
- reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, true, attempt, encodedOutput, "", "rpc", parentSpanId, rpcServiceName);
2505
+ resolve(response ?? { success: false, error: "empty response" });
2506
+ };
2507
+ try {
2508
+ unaryCall = workerClient.Handle({
2509
+ function_name: workerFnName,
2510
+ payload: inputBuf,
2511
+ trace_id: traceId,
2512
+ span_id: attemptSpanId,
2513
+ parent_span_id: hasRetries ? rootSpanId : ""
2514
+ }, makeWorkerMeta(), { deadline, waitForReady: true }, finish);
2515
+ } catch (err) {
2516
+ finish(err);
2537
2517
  }
2538
- return result;
2539
- } catch (e) {
2540
- lastError = e;
2541
- const errMsg = normalizeUnknownErrorMessage(e);
2542
- if (hasRetries) {
2543
- reportCallAsync(traceId, attemptSpanId, rpcFnName, attemptStartedAt, telemetryInputBuf, false, attempt, undefined, errMsg, "rpc", attemptParentId, rpcServiceName);
2544
- if (attempt > maxRetries) {
2545
- reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, false, attempt, undefined, errMsg, "rpc", parentSpanId, rpcServiceName);
2546
- }
2547
- } else {
2518
+ });
2519
+ if (!res?.success) {
2520
+ throw new ServiceBridgeError({
2521
+ code: SB.HANDLER_RETURNED_FAILURE,
2522
+ message: res?.error?.trim() || SB_MESSAGES[SB.HANDLER_RETURNED_FAILURE],
2523
+ component: "worker",
2524
+ operation: "rpc",
2525
+ severity: "fatal"
2526
+ });
2527
+ }
2528
+ const outputBuf = res.output ?? Buffer.alloc(0);
2529
+ const result = outputSchema ? decodeWithSchema(outputSchema, outputBuf) : outputBuf?.length ? JSON.parse(outputBuf.toString()) : {};
2530
+ const encodedOutput = outputBuf?.length ? toJsonBuffer(result) : Buffer.alloc(0);
2531
+ if (hasRetries) {
2532
+ reportCallAsync(traceId, attemptSpanId, rpcFnName, attemptStartedAt, telemetryInputBuf, true, attempt, encodedOutput, "", "rpc", attemptParentId, rpcServiceName);
2533
+ reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, true, attempt, encodedOutput, "", "rpc", parentSpanId, rpcServiceName);
2534
+ } else {
2535
+ reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, true, attempt, encodedOutput, "", "rpc", parentSpanId, rpcServiceName);
2536
+ }
2537
+ return result;
2538
+ } catch (e) {
2539
+ lastError = e;
2540
+ const errMsg = normalizeUnknownErrorMessage(e);
2541
+ if (hasRetries) {
2542
+ reportCallAsync(traceId, attemptSpanId, rpcFnName, attemptStartedAt, telemetryInputBuf, false, attempt, undefined, errMsg, "rpc", attemptParentId, rpcServiceName);
2543
+ if (attempt > maxRetries) {
2548
2544
  reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, false, attempt, undefined, errMsg, "rpc", parentSpanId, rpcServiceName);
2549
2545
  }
2550
- if (attempt <= maxRetries) {
2551
- await new Promise((r) => setTimeout(r, baseDelay * 2 ** (attempt - 1)));
2552
- }
2546
+ } else {
2547
+ reportCallAsync(traceId, rootSpanId, rpcFnName, rootStartedAt, telemetryInputBuf, false, attempt, undefined, errMsg, "rpc", parentSpanId, rpcServiceName);
2548
+ }
2549
+ if (attempt <= maxRetries) {
2550
+ await new Promise((r) => setTimeout(r, baseDelay * 2 ** (attempt - 1)));
2553
2551
  }
2554
2552
  }
2555
- throw normalizeServiceError(lastError, lookupKey, "worker");
2556
- });
2557
- },
2558
- event(topic, payload, opts2) {
2559
- if (!isOnline) {
2560
- enqueueOffline({ type: "event", topic, payload, opts: opts2 });
2561
- return Promise.resolve("");
2562
2553
  }
2563
- const tc = traceStorage.getStore();
2564
- return new Promise((resolve, reject) => {
2565
- stub.Publish({
2554
+ throw normalizeServiceError(lastError, lookupKey, "worker");
2555
+ });
2556
+ }
2557
+ const svc = {
2558
+ rpc: {
2559
+ handle(fn, handler, opts2) {
2560
+ assertRegistrationsNotFrozen("handle-rpc");
2561
+ fnHandlers.set(fn, { handler, opts: opts2 ?? {} });
2562
+ return svc;
2563
+ },
2564
+ declare(fn) {
2565
+ assertRegistrationsNotFrozen("calls-rpc");
2566
+ outgoingDeps.push({
2567
+ dep_type: "rpc",
2568
+ target_service: "",
2569
+ target_resource: fn
2570
+ });
2571
+ },
2572
+ invoke: rpcInvoke
2573
+ },
2574
+ events: {
2575
+ handle(pattern, handler, opts2) {
2576
+ assertRegistrationsNotFrozen("handle-event");
2577
+ const normalizedOpts = opts2 ?? {};
2578
+ const groupName = `${defaultConsumerPrefix}.${pattern}`;
2579
+ if (eventHandlers.has(groupName)) {
2580
+ throw new ServiceBridgeError({
2581
+ code: SB.DUPLICATE_EVENT_CONSUMER_GROUP,
2582
+ message: `${SB_MESSAGES[SB.DUPLICATE_EVENT_CONSUMER_GROUP]} Group: "${groupName}".`,
2583
+ component: "sdk",
2584
+ operation: "handle-event",
2585
+ severity: "fatal"
2586
+ });
2587
+ }
2588
+ eventHandlers.set(groupName, {
2589
+ groupName,
2590
+ pattern,
2591
+ handler,
2592
+ opts: normalizedOpts
2593
+ });
2594
+ return svc;
2595
+ },
2596
+ declare(topic) {
2597
+ assertRegistrationsNotFrozen("calls-event");
2598
+ outgoingDeps.push({
2599
+ dep_type: "event",
2600
+ target_service: "",
2601
+ target_resource: topic
2602
+ });
2603
+ },
2604
+ publish(topic, payload, opts2) {
2605
+ if (!isOnline) {
2606
+ enqueueOffline({ type: "event", topic, payload, opts: opts2 });
2607
+ return Promise.resolve("");
2608
+ }
2609
+ const tc = traceStorage.getStore();
2610
+ return new Promise((resolve, reject) => {
2611
+ stub.Publish({
2612
+ topic,
2613
+ payload: toJsonBuffer(payload),
2614
+ headers: toWireStringMap(opts2?.headers),
2615
+ trace_id: opts2?.traceId ?? tc?.traceId ?? "",
2616
+ parent_span_id: opts2?.parentSpanId ?? tc?.spanId ?? "",
2617
+ producer_service: defaultConsumerPrefix,
2618
+ idempotency_key: opts2?.idempotencyKey ?? ""
2619
+ }, meta, unaryDeadlineOptions(), (err, res) => err ? reject(normalizeServiceError(err, `publish:${topic}`)) : resolve(res?.message_id ?? ""));
2620
+ });
2621
+ },
2622
+ publishWorker(topic, payload, opts2) {
2623
+ const stream = workerSessionStream;
2624
+ if (!stream) {
2625
+ return Promise.reject(new Error("events.publishWorker requires an active worker session (call start() first)"));
2626
+ }
2627
+ const tc = traceStorage.getStore();
2628
+ const requestId = crypto.randomUUID();
2629
+ const msg = {
2630
+ request_id: requestId,
2566
2631
  topic,
2567
2632
  payload: toJsonBuffer(payload),
2568
2633
  headers: toWireStringMap(opts2?.headers),
2569
2634
  trace_id: opts2?.traceId ?? tc?.traceId ?? "",
2570
- parent_span_id: opts2?.parentSpanId ?? tc?.spanId ?? "",
2571
- producer_service: defaultConsumerPrefix,
2572
- idempotency_key: opts2?.idempotencyKey ?? ""
2573
- }, meta, unaryDeadlineOptions(), (err, res) => err ? reject(normalizeServiceError(err, `publish:${topic}`)) : resolve(res?.message_id ?? ""));
2574
- });
2575
- },
2576
- publishEvent(topic, payload, opts2) {
2577
- const stream = workerSessionStream;
2578
- if (!stream) {
2579
- return Promise.reject(new Error("publishEvent requires an active worker session (call start() first)"));
2635
+ parent_span_id: opts2?.parentSpanId ?? tc?.spanId ?? ""
2636
+ };
2637
+ return new Promise((resolve, reject) => {
2638
+ const timer = setTimeout(() => {
2639
+ pendingPublishAcks.delete(requestId);
2640
+ reject(new Error(`publishWorker timed out after 30 s (topic="${topic}", request_id="${requestId}")`));
2641
+ }, 30000);
2642
+ pendingPublishAcks.set(requestId, { resolve, reject, timer });
2643
+ try {
2644
+ stream.write({ publish_message: msg });
2645
+ } catch (err) {
2646
+ pendingPublishAcks.delete(requestId);
2647
+ clearTimeout(timer);
2648
+ reject(normalizeServiceError(err, `publish-event:${topic}`));
2649
+ }
2650
+ });
2580
2651
  }
2581
- const tc = traceStorage.getStore();
2582
- const requestId = crypto.randomUUID();
2583
- const msg = {
2584
- request_id: requestId,
2585
- topic,
2586
- payload: toJsonBuffer(payload),
2587
- headers: toWireStringMap(opts2?.headers),
2588
- trace_id: opts2?.traceId ?? tc?.traceId ?? "",
2589
- parent_span_id: opts2?.parentSpanId ?? tc?.spanId ?? ""
2590
- };
2591
- return new Promise((resolve, reject) => {
2592
- const timer = setTimeout(() => {
2593
- pendingPublishAcks.delete(requestId);
2594
- reject(new Error(`publishEvent timed out after 30 s (topic="${topic}", request_id="${requestId}")`));
2595
- }, 30000);
2596
- pendingPublishAcks.set(requestId, { resolve, reject, timer });
2597
- try {
2598
- stream.write({ publish_message: msg });
2599
- } catch (err) {
2600
- pendingPublishAcks.delete(requestId);
2601
- clearTimeout(timer);
2602
- reject(normalizeServiceError(err, `publish-event:${topic}`));
2603
- }
2604
- });
2605
2652
  },
2606
- handleRpc(fn, handler, opts2) {
2607
- assertRegistrationsNotFrozen("handle-rpc");
2608
- fnHandlers.set(fn, { handler, opts: opts2 ?? {} });
2609
- return svc;
2610
- },
2611
- callsRpc(fn) {
2612
- assertRegistrationsNotFrozen("calls-rpc");
2613
- outgoingDeps.push({
2614
- dep_type: "rpc",
2615
- target_service: "",
2616
- target_resource: fn
2617
- });
2618
- },
2619
- callsEvent(topic) {
2620
- assertRegistrationsNotFrozen("calls-event");
2621
- outgoingDeps.push({
2622
- dep_type: "event",
2623
- target_service: "",
2624
- target_resource: topic
2625
- });
2626
- },
2627
- callsWorkflow(service2, name) {
2628
- assertRegistrationsNotFrozen("calls-workflow");
2629
- outgoingDeps.push({
2630
- dep_type: "workflow",
2631
- target_service: service2,
2632
- target_resource: name
2633
- });
2653
+ jobs: {
2654
+ run: async (serviceOrTarget, fnOrOpts, optsOrUndefined) => {
2655
+ let target2;
2656
+ let opts2;
2657
+ if (typeof fnOrOpts === "string") {
2658
+ const service2 = serviceOrTarget.trim();
2659
+ const fn = fnOrOpts.trim();
2660
+ opts2 = optsOrUndefined ?? { via: "rpc" };
2661
+ if (!service2 || !fn) {
2662
+ throw new ServiceBridgeError({
2663
+ code: SB.JOB_RPC_ARGS_REQUIRED,
2664
+ message: SB_MESSAGES[SB.JOB_RPC_ARGS_REQUIRED],
2665
+ component: "sdk",
2666
+ operation: "job",
2667
+ severity: "fatal"
2668
+ });
2669
+ }
2670
+ if (opts2.via !== "rpc") {
2671
+ throw new ServiceBridgeError({
2672
+ code: SB.JOB_VIA_MISMATCH,
2673
+ message: SB_MESSAGES[SB.JOB_VIA_MISMATCH],
2674
+ component: "sdk",
2675
+ operation: "job",
2676
+ severity: "fatal"
2677
+ });
2678
+ }
2679
+ target2 = `${service2}/${fn}`;
2680
+ } else {
2681
+ target2 = serviceOrTarget;
2682
+ opts2 = fnOrOpts;
2683
+ if (opts2.via === "rpc") {
2684
+ throw new ServiceBridgeError({
2685
+ code: SB.JOB_TARGET_FORM_REQUIRED,
2686
+ message: SB_MESSAGES[SB.JOB_TARGET_FORM_REQUIRED],
2687
+ component: "sdk",
2688
+ operation: "job",
2689
+ severity: "fatal"
2690
+ });
2691
+ }
2692
+ }
2693
+ jobRegistrations.set(target2, { target: target2, opts: opts2 });
2694
+ if (isOnline) {
2695
+ await reconcileRegistrations(`job:${target2}`).catch((err) => {
2696
+ reportSDKError(`register-job:${target2}`, err);
2697
+ });
2698
+ } else {
2699
+ enqueueOffline({ type: "job", target: target2, opts: opts2 });
2700
+ }
2701
+ return target2;
2702
+ }
2634
2703
  },
2635
- handleEvent(pattern, handler, opts2) {
2636
- assertRegistrationsNotFrozen("handle-event");
2637
- const normalizedOpts = opts2 ?? {};
2638
- const groupName = `${defaultConsumerPrefix}.${pattern}`;
2639
- if (eventHandlers.has(groupName)) {
2640
- throw new ServiceBridgeError({
2641
- code: SB.DUPLICATE_EVENT_CONSUMER_GROUP,
2642
- message: `${SB_MESSAGES[SB.DUPLICATE_EVENT_CONSUMER_GROUP]} Group: "${groupName}".`,
2643
- component: "sdk",
2644
- operation: "handle-event",
2645
- severity: "fatal"
2704
+ workflows: {
2705
+ declare(service2, name) {
2706
+ assertRegistrationsNotFrozen("calls-workflow");
2707
+ outgoingDeps.push({
2708
+ dep_type: "workflow",
2709
+ target_service: service2,
2710
+ target_resource: name
2711
+ });
2712
+ },
2713
+ run: async (nameOrService, stepsOrName, inputOrOpts, opts2) => {
2714
+ if (Array.isArray(stepsOrName)) {
2715
+ const name2 = nameOrService;
2716
+ const steps = stepsOrName;
2717
+ const wfOpts = inputOrOpts;
2718
+ workflowRegistrations.set(name2, { name: name2, steps, opts: wfOpts });
2719
+ if (isOnline) {
2720
+ await reconcileRegistrations(`workflow:${name2}`).catch((err) => {
2721
+ reportSDKError(`register-workflow:${name2}`, err);
2722
+ });
2723
+ } else {
2724
+ enqueueOffline({ type: "workflow", name: name2, steps, opts: wfOpts });
2725
+ }
2726
+ return name2;
2727
+ }
2728
+ const service2 = nameOrService;
2729
+ const name = stepsOrName;
2730
+ await _controlReady;
2731
+ const svcName = service2.trim();
2732
+ const wfName = name.trim();
2733
+ if (!svcName || !wfName) {
2734
+ return Promise.reject(new ServiceBridgeError({
2735
+ code: SB.EXECUTE_WORKFLOW_ARGS_REQUIRED,
2736
+ message: SB_MESSAGES[SB.EXECUTE_WORKFLOW_ARGS_REQUIRED],
2737
+ component: "sdk",
2738
+ operation: "execute-workflow",
2739
+ severity: "fatal"
2740
+ }));
2741
+ }
2742
+ const payload = toJsonBuffer(inputOrOpts ?? {});
2743
+ const execReq = {
2744
+ workflow_name: wfName,
2745
+ service_name: svcName,
2746
+ input: payload
2747
+ };
2748
+ const tid = opts2?.traceId?.trim();
2749
+ if (tid) {
2750
+ execReq.trace_id = tid;
2751
+ }
2752
+ const gtid = opts2?.groupTraceId?.trim();
2753
+ if (gtid) {
2754
+ execReq.group_trace_id = gtid;
2755
+ }
2756
+ return new Promise((resolve, reject) => {
2757
+ stub.ExecuteWorkflow(execReq, meta, unaryDeadlineOptions(), (err, res) => {
2758
+ if (err) {
2759
+ reject(normalizeServiceError(err, "execute-workflow"));
2760
+ return;
2761
+ }
2762
+ resolve({
2763
+ traceId: res?.trace_id ?? ""
2764
+ });
2765
+ });
2646
2766
  });
2647
2767
  }
2648
- eventHandlers.set(groupName, {
2649
- groupName,
2650
- pattern,
2651
- handler,
2652
- opts: normalizedOpts
2653
- });
2654
- return svc;
2655
2768
  },
2656
2769
  async start(opts2 = {}) {
2657
2770
  if (workerServer) {
@@ -2675,7 +2788,7 @@ function servicebridge(url, serviceKey, opts = {}) {
2675
2788
  try {
2676
2789
  workerServer = new grpc2.Server(channelOpts);
2677
2790
  workerServer.addService(WorkerHandlerService.service, {
2678
- Handle: handleRpc,
2791
+ Handle: workerGrpcHandle,
2679
2792
  Deliver: handleDeliver
2680
2793
  });
2681
2794
  const host = opts2.host || "localhost";
@@ -2801,96 +2914,6 @@ function servicebridge(url, serviceKey, opts = {}) {
2801
2914
  }
2802
2915
  }, 200);
2803
2916
  },
2804
- async job(serviceOrTarget, fnOrOpts, optsOrUndefined) {
2805
- let target2;
2806
- let opts2;
2807
- if (typeof fnOrOpts === "string") {
2808
- const service2 = serviceOrTarget.trim();
2809
- const fn = fnOrOpts.trim();
2810
- opts2 = optsOrUndefined ?? { via: "rpc" };
2811
- if (!service2 || !fn) {
2812
- throw new ServiceBridgeError({
2813
- code: SB.JOB_RPC_ARGS_REQUIRED,
2814
- message: SB_MESSAGES[SB.JOB_RPC_ARGS_REQUIRED],
2815
- component: "sdk",
2816
- operation: "job",
2817
- severity: "fatal"
2818
- });
2819
- }
2820
- if (opts2.via !== "rpc") {
2821
- throw new ServiceBridgeError({
2822
- code: SB.JOB_VIA_MISMATCH,
2823
- message: SB_MESSAGES[SB.JOB_VIA_MISMATCH],
2824
- component: "sdk",
2825
- operation: "job",
2826
- severity: "fatal"
2827
- });
2828
- }
2829
- target2 = `${service2}/${fn}`;
2830
- } else {
2831
- target2 = serviceOrTarget;
2832
- opts2 = fnOrOpts;
2833
- if (opts2.via === "rpc") {
2834
- throw new ServiceBridgeError({
2835
- code: SB.JOB_TARGET_FORM_REQUIRED,
2836
- message: SB_MESSAGES[SB.JOB_TARGET_FORM_REQUIRED],
2837
- component: "sdk",
2838
- operation: "job",
2839
- severity: "fatal"
2840
- });
2841
- }
2842
- }
2843
- jobRegistrations.set(target2, { target: target2, opts: opts2 });
2844
- if (isOnline) {
2845
- await reconcileRegistrations(`job:${target2}`).catch((err) => {
2846
- reportSDKError(`register-job:${target2}`, err);
2847
- });
2848
- } else {
2849
- enqueueOffline({ type: "job", target: target2, opts: opts2 });
2850
- }
2851
- return target2;
2852
- },
2853
- async workflow(name, steps, opts2) {
2854
- workflowRegistrations.set(name, { name, steps, opts: opts2 });
2855
- if (isOnline) {
2856
- await reconcileRegistrations(`workflow:${name}`).catch((err) => {
2857
- reportSDKError(`register-workflow:${name}`, err);
2858
- });
2859
- } else {
2860
- enqueueOffline({ type: "workflow", name, steps, opts: opts2 });
2861
- }
2862
- return name;
2863
- },
2864
- async executeWorkflow(service2, name, input, opts2) {
2865
- await _controlReady;
2866
- const svc2 = service2.trim();
2867
- const wfName = name.trim();
2868
- if (!svc2 || !wfName) {
2869
- return Promise.reject(new ServiceBridgeError({
2870
- code: SB.EXECUTE_WORKFLOW_ARGS_REQUIRED,
2871
- message: SB_MESSAGES[SB.EXECUTE_WORKFLOW_ARGS_REQUIRED],
2872
- component: "sdk",
2873
- operation: "execute-workflow",
2874
- severity: "fatal"
2875
- }));
2876
- }
2877
- const payload = toJsonBuffer(input ?? {});
2878
- return new Promise((resolve, reject) => {
2879
- stub.ExecuteWorkflow({
2880
- workflow_name: wfName,
2881
- service_name: svc2,
2882
- input: payload
2883
- }, meta, unaryDeadlineOptions(), (err, res) => {
2884
- if (err) {
2885
- reject(normalizeServiceError(err, "execute-workflow"));
2886
- return;
2887
- }
2888
- resolve({
2889
- traceId: res?.trace_id ?? ""
2890
- });
2891
- });
2892
- });
2893
- },
2894
2917
  async cancelWorkflow(traceId) {
2895
2918
  await _controlReady;
2896
2919
  await new Promise((resolve, reject) => {