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.
- package/README.md +175 -108
- package/dist/index.js +384 -361
- 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
|
|
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]: "
|
|
108
|
-
[SB.JOB_VIA_MISMATCH]: "
|
|
109
|
-
[SB.JOB_TARGET_FORM_REQUIRED]: "
|
|
110
|
-
[SB.EXECUTE_WORKFLOW_ARGS_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
|
|
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
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
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
|
-
|
|
2416
|
-
const
|
|
2417
|
-
const
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
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
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
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
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
const
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
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
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
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
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
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
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
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
|
-
|
|
2551
|
-
|
|
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
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
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
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
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
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
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
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
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:
|
|
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) => {
|