@pikku/core 0.12.150 → 0.12.151
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/dist/function/function-runner.js +23 -0
- package/dist/function/trace-queue-service.d.ts +26 -0
- package/dist/function/trace-queue-service.js +48 -0
- package/dist/mesh.d.ts +2 -2
- package/dist/mesh.js +3 -3
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/mesh-auth.d.ts +3 -2
- package/dist/middleware/mesh-auth.js +7 -2
- package/dist/middleware/remote-jobs-secret.d.ts +1 -1
- package/dist/middleware/require-origin.d.ts +19 -0
- package/dist/middleware/require-origin.js +36 -2
- package/dist/services/deployment-service.d.ts +1 -1
- package/dist/services/incoming-webhook-service.d.ts +4 -2
- package/dist/services/incoming-webhook-service.js +4 -3
- package/dist/services/queue-webhook-service.js +7 -1
- package/dist/services/remote-jobs.d.ts +6 -1
- package/dist/services/scheduler-service.d.ts +3 -1
- package/dist/services/webhook-service.d.ts +2 -0
- package/dist/services/workflow-service.d.ts +4 -1
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/queue-identity-transport-tests.d.ts +18 -0
- package/dist/testing/queue-identity-transport-tests.js +162 -0
- package/dist/testing/queue-trace-conformance.d.ts +26 -0
- package/dist/testing/queue-trace-conformance.js +259 -0
- package/dist/testing/service-tests/channel-store-tests.js +11 -0
- package/dist/testing/service-tests/queued-workflow-harness.d.ts +18 -1
- package/dist/testing/service-tests/queued-workflow-harness.js +19 -3
- package/dist/testing/service-tests/workflow-service-tests.js +12 -0
- package/dist/testing/trace-matrix/assertions.d.ts +3 -0
- package/dist/testing/trace-matrix/assertions.js +190 -0
- package/dist/testing/trace-matrix/chain-harness.d.ts +119 -0
- package/dist/testing/trace-matrix/chain-harness.js +777 -0
- package/dist/testing/trace-matrix/dimensions.d.ts +55 -0
- package/dist/testing/trace-matrix/dimensions.js +305 -0
- package/dist/testing/trace-matrix/pairwise.d.ts +20 -0
- package/dist/testing/trace-matrix/pairwise.js +75 -0
- package/dist/testing/trace-matrix/report.d.ts +1 -0
- package/dist/testing/trace-matrix/report.js +16 -0
- package/dist/testing/trace-matrix/transports.d.ts +19 -0
- package/dist/testing/trace-matrix/transports.js +27 -0
- package/dist/testing/trace-matrix/workflow-cases.d.ts +24 -0
- package/dist/testing/trace-matrix/workflow-cases.js +147 -0
- package/dist/trace.d.ts +66 -0
- package/dist/trace.js +97 -0
- package/dist/types/core.types.d.ts +6 -2
- package/dist/wirings/addon/addon-reach.d.ts +3 -0
- package/dist/wirings/addon/addon-reach.js +9 -0
- package/dist/wirings/addon/define-plugin.d.ts +6 -0
- package/dist/wirings/addon/wire-addon.js +5 -0
- package/dist/wirings/addon/wire-remote-addon.d.ts +11 -0
- package/dist/wirings/agent/agent-finalize.d.ts +2 -0
- package/dist/wirings/agent/agent-finalize.js +1 -1
- package/dist/wirings/agent/agent-prepare.d.ts +3 -0
- package/dist/wirings/agent/agent-prepare.js +24 -8
- package/dist/wirings/agent/agent-rpc.js +1 -0
- package/dist/wirings/agent/agent-runner.js +3 -0
- package/dist/wirings/agent/agent-stream.js +4 -0
- package/dist/wirings/agent/voice-input.d.ts +1 -1
- package/dist/wirings/agent/voice-output.d.ts +1 -1
- package/dist/wirings/agent-scorer/agent-scorer-live.d.ts +4 -2
- package/dist/wirings/agent-scorer/agent-scorer-live.js +2 -2
- package/dist/wirings/channel/channel-common.d.ts +4 -1
- package/dist/wirings/channel/channel-common.js +3 -1
- package/dist/wirings/channel/channel-handler.js +2 -0
- package/dist/wirings/channel/channel-host-rpc.d.ts +1 -1
- package/dist/wirings/channel/channel-host-rpc.js +2 -2
- package/dist/wirings/channel/channel-rpc-responder.d.ts +7 -1
- package/dist/wirings/channel/channel-rpc-responder.js +2 -2
- package/dist/wirings/channel/channel-rpc-service.d.ts +1 -1
- package/dist/wirings/channel/channel-rpc-service.js +2 -2
- package/dist/wirings/channel/channel-rpc.types.d.ts +5 -2
- package/dist/wirings/channel/channel-rpc.types.js +1 -1
- package/dist/wirings/channel/channel-store.d.ts +4 -2
- package/dist/wirings/channel/channel-store.js +2 -2
- package/dist/wirings/channel/channel.types.d.ts +3 -0
- package/dist/wirings/channel/local/local-channel-runner.js +16 -5
- package/dist/wirings/channel/pikku-abstract-channel-handler.d.ts +2 -0
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +3 -1
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +27 -4
- package/dist/wirings/gateway/gateway-runner.js +3 -1
- package/dist/wirings/http/http-runner.d.ts +4 -4
- package/dist/wirings/http/http-runner.js +14 -18
- package/dist/wirings/http/http.types.d.ts +3 -3
- package/dist/wirings/mcp/mcp-runner.js +15 -5
- package/dist/wirings/queue/in-memory-queue-service.d.ts +1 -1
- package/dist/wirings/queue/in-memory-queue-service.js +8 -2
- package/dist/wirings/queue/index.d.ts +1 -0
- package/dist/wirings/queue/index.js +1 -0
- package/dist/wirings/queue/queue-runner.d.ts +3 -4
- package/dist/wirings/queue/queue-runner.js +8 -7
- package/dist/wirings/queue/queue-trace.d.ts +18 -0
- package/dist/wirings/queue/queue-trace.js +42 -0
- package/dist/wirings/queue/queue.types.d.ts +7 -0
- package/dist/wirings/queue/remote-queue-job.d.ts +1 -1
- package/dist/wirings/queue/remote-queue-job.js +5 -3
- package/dist/wirings/rpc/rpc-runner.d.ts +7 -1
- package/dist/wirings/rpc/rpc-runner.js +43 -21
- package/dist/wirings/rpc/rpc-types.d.ts +2 -0
- package/dist/wirings/scheduler/remote-scheduled-job.d.ts +1 -1
- package/dist/wirings/scheduler/remote-scheduled-job.js +2 -2
- package/dist/wirings/scheduler/scheduler-runner.d.ts +4 -4
- package/dist/wirings/scheduler/scheduler-runner.js +7 -6
- package/dist/wirings/trigger/pikku-trigger-service.js +7 -4
- package/dist/wirings/trigger/webhook-source-runner.d.ts +7 -1
- package/dist/wirings/trigger/webhook-source-runner.js +11 -3
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
- package/dist/wirings/workflow/graph/graph-runner.js +49 -30
- package/dist/wirings/workflow/index.d.ts +3 -1
- package/dist/wirings/workflow/index.js +1 -0
- package/dist/wirings/workflow/pikku-scenario-service.js +2 -0
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +37 -40
- package/dist/wirings/workflow/pikku-workflow-service.js +122 -133
- package/dist/wirings/workflow/workflow-child-step.d.ts +2 -2
- package/dist/wirings/workflow/workflow-child-step.js +11 -4
- package/dist/wirings/workflow/workflow-compensation.d.ts +6 -0
- package/dist/wirings/workflow/workflow-compensation.js +15 -1
- package/dist/wirings/workflow/workflow-constants.d.ts +13 -0
- package/dist/wirings/workflow/workflow-constants.js +13 -0
- package/dist/wirings/workflow/workflow-dsl-pass.d.ts +2 -0
- package/dist/wirings/workflow/workflow-dsl-pass.js +27 -11
- package/dist/wirings/workflow/workflow-recovery.d.ts +131 -3
- package/dist/wirings/workflow/workflow-recovery.js +233 -7
- package/dist/wirings/workflow/workflow-run-engine.types.d.ts +1 -0
- package/dist/wirings/workflow/workflow-run-mirror.d.ts +4 -0
- package/dist/wirings/workflow/workflow-run-mirror.js +16 -0
- package/dist/wirings/workflow/workflow-run-status.d.ts +2 -0
- package/dist/wirings/workflow/workflow-run-status.js +13 -0
- package/dist/wirings/workflow/workflow-run-trace.d.ts +22 -0
- package/dist/wirings/workflow/workflow-run-trace.js +24 -0
- package/dist/wirings/workflow/workflow-step-claim.d.ts +18 -0
- package/dist/wirings/workflow/workflow-step-claim.js +47 -0
- package/dist/wirings/workflow/workflow-step-retry.d.ts +6 -2
- package/dist/wirings/workflow/workflow-step-retry.js +8 -3
- package/dist/wirings/workflow/workflow-wire.d.ts +12 -0
- package/dist/wirings/workflow/workflow-wire.js +33 -0
- package/dist/wirings/workflow/workflow.types.d.ts +16 -0
- package/package.json +2 -1
|
@@ -5,6 +5,7 @@ import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
|
5
5
|
import { resolveNamespace } from '../rpc/rpc-runner.js';
|
|
6
6
|
import { BadRequestError, NotFoundError } from '../../errors/errors.js';
|
|
7
7
|
import { PikkuSessionService, createMiddlewareSessionWireProps, } from '../../services/user-session-service.js';
|
|
8
|
+
import { traceWire, tracedLogger } from '../../trace.js';
|
|
8
9
|
/**
|
|
9
10
|
* A JSON-RPC error raised by `runMCPTool`, `runMCPResource` and `runMCPPrompt`; `error` is the
|
|
10
11
|
* response body to send back to the MCP client.
|
|
@@ -154,6 +155,10 @@ async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncId, { mcp: mcp
|
|
|
154
155
|
metaKey = name) {
|
|
155
156
|
const singletonServices = getSingletonServices();
|
|
156
157
|
const createWireServices = getCreateWireServices();
|
|
158
|
+
// Here rather than left to the function runner: a call refused before it
|
|
159
|
+
// gets there still has to be found in the logs.
|
|
160
|
+
const trace = traceWire();
|
|
161
|
+
const logger = tracedLogger(singletonServices.logger, trace.traceId);
|
|
157
162
|
try {
|
|
158
163
|
if (request.jsonrpc !== '2.0') {
|
|
159
164
|
throw new BadRequestError('Invalid JSON-RPC version, only supoorted version is 2.0');
|
|
@@ -167,6 +172,7 @@ metaKey = name) {
|
|
|
167
172
|
singletonServices.logger.debug(`Running MCP ${type}: ${name}`);
|
|
168
173
|
const mcpSessionService = new PikkuSessionService(singletonServices.sessionStore);
|
|
169
174
|
const wire = {
|
|
175
|
+
...trace,
|
|
170
176
|
mcp: mcpWire,
|
|
171
177
|
// Only when the transport supplied one. Setting `http: undefined`
|
|
172
178
|
// explicitly would be the same to a reader and different to a spread.
|
|
@@ -212,27 +218,31 @@ metaKey = name) {
|
|
|
212
218
|
};
|
|
213
219
|
}
|
|
214
220
|
catch (e) {
|
|
215
|
-
|
|
221
|
+
logger.error(`Error running MCP ${type} '${name}':`, e.constructor);
|
|
216
222
|
const errorResponse = getErrorResponse(e);
|
|
217
223
|
if (errorResponse?.mcpCode) {
|
|
218
224
|
throw new MCPError({
|
|
219
225
|
id: request.id,
|
|
220
226
|
code: errorResponse.mcpCode,
|
|
221
227
|
message: errorResponse.message,
|
|
228
|
+
data: { errorId: trace.traceId },
|
|
222
229
|
});
|
|
223
230
|
}
|
|
224
231
|
else {
|
|
225
232
|
if (errorResponse) {
|
|
226
|
-
|
|
233
|
+
logger.warn(`Got error without a mapping: ${errorResponse.message}`);
|
|
227
234
|
}
|
|
228
235
|
throw new MCPError({
|
|
229
236
|
id: request.id,
|
|
230
237
|
code: -32603,
|
|
231
238
|
message: 'Internal error',
|
|
232
239
|
// knowledge: decisions/security/mcp-internal-error-details-are-double-gated-on-production.md
|
|
233
|
-
data:
|
|
234
|
-
|
|
235
|
-
|
|
240
|
+
data: {
|
|
241
|
+
errorId: trace.traceId,
|
|
242
|
+
...(exposeErrors && !isProduction() && e instanceof Error
|
|
243
|
+
? { message: e.message, stack: e.stack }
|
|
244
|
+
: {}),
|
|
245
|
+
},
|
|
236
246
|
});
|
|
237
247
|
}
|
|
238
248
|
}
|
|
@@ -4,7 +4,7 @@ import type { QueueService, JobOptions, QueueInspection } from './queue.types.js
|
|
|
4
4
|
*
|
|
5
5
|
* Jobs run after a short delay, payloads are JSON round-tripped as a real
|
|
6
6
|
* backend would, and failed jobs are retried up to `attempts` with the given
|
|
7
|
-
* backoff. Nothing is persisted, `getJob` always returns `null`, and jobs still
|
|
7
|
+
* backoff. A job its worker discarded is not retried. Nothing is persisted, `getJob` always returns `null`, and jobs still
|
|
8
8
|
* pending when the process exits are lost.
|
|
9
9
|
*/
|
|
10
10
|
export declare class InMemoryQueueService implements QueueService {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runQueueJob } from './queue-runner.js';
|
|
1
|
+
import { QueueJobDiscardedError, runQueueJob } from './queue-runner.js';
|
|
2
2
|
const FINISHED_JOBS_KEPT = 100;
|
|
3
3
|
// knowledge: decisions/internals/the-dev-queue-copies-prod-timing-and-serialization-semantics.md
|
|
4
4
|
/**
|
|
@@ -6,7 +6,7 @@ const FINISHED_JOBS_KEPT = 100;
|
|
|
6
6
|
*
|
|
7
7
|
* Jobs run after a short delay, payloads are JSON round-tripped as a real
|
|
8
8
|
* backend would, and failed jobs are retried up to `attempts` with the given
|
|
9
|
-
* backoff. Nothing is persisted, `getJob` always returns `null`, and jobs still
|
|
9
|
+
* backoff. A job its worker discarded is not retried. Nothing is persisted, `getJob` always returns `null`, and jobs still
|
|
10
10
|
* pending when the process exits are lost.
|
|
11
11
|
*/
|
|
12
12
|
export class InMemoryQueueService {
|
|
@@ -44,12 +44,18 @@ export class InMemoryQueueService {
|
|
|
44
44
|
status: () => 'active',
|
|
45
45
|
metadata: () => ({ attemptsMade, maxAttempts, createdAt }),
|
|
46
46
|
pikkuUserId: options?.pikkuUserId,
|
|
47
|
+
traceparent: options?.traceparent,
|
|
47
48
|
};
|
|
48
49
|
try {
|
|
49
50
|
await runQueueJob({ job });
|
|
50
51
|
this.finish(queueName, record, 'completed', null);
|
|
51
52
|
}
|
|
52
53
|
catch (e) {
|
|
54
|
+
// As on a real backend, a discard ends the job and is not a failure.
|
|
55
|
+
if (e instanceof QueueJobDiscardedError) {
|
|
56
|
+
this.finish(queueName, record, 'completed', null);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
53
59
|
record.error = e?.message ?? String(e);
|
|
54
60
|
if (attemptsMade < maxAttempts) {
|
|
55
61
|
record.status = 'delayed';
|
|
@@ -2,6 +2,7 @@ export type { ConfigValidationResult, CoreQueueWorker, GroupConcurrencyConfig, J
|
|
|
2
2
|
export { wireQueueWorker, runQueueJob, QueueJobDiscardedError, QueueJobFailedError, } from './queue-runner.js';
|
|
3
3
|
export type { QueueIdentityBinding } from './queue-identity.js';
|
|
4
4
|
export { SignedQueueService } from './signed-queue-service.js';
|
|
5
|
+
export { unwrapJobTrace, wrapJobTrace } from './queue-trace.js';
|
|
5
6
|
export type { QueueConfigMapping } from './validate-worker-config.js';
|
|
6
7
|
export { registerQueueWorkers } from './register-queue-helper.js';
|
|
7
8
|
export type { QueueRegistrationCallback } from './register-queue-helper.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { wireQueueWorker, runQueueJob, QueueJobDiscardedError, QueueJobFailedError, } from './queue-runner.js';
|
|
2
2
|
export { SignedQueueService } from './signed-queue-service.js';
|
|
3
|
+
export { unwrapJobTrace, wrapJobTrace } from './queue-trace.js';
|
|
3
4
|
export { registerQueueWorkers } from './register-queue-helper.js';
|
|
4
5
|
export { pikkuRemoteQueueJobFunc } from './remote-queue-job.js';
|
|
@@ -37,18 +37,17 @@ export declare function removeQueueWorker(name: string): Promise<void>;
|
|
|
37
37
|
* The job's `pikkuUserId` claim is verified before the function runs; an
|
|
38
38
|
* unsigned or invalid claim is logged and the job runs without one. The function
|
|
39
39
|
* runs with `auth: false`, the worker's middleware, and a `queue` wire carrying
|
|
40
|
-
* `updateProgress`, `fail` and `discard`.
|
|
40
|
+
* `updateProgress`, `fail` and `discard`. It continues the job's `traceparent`,
|
|
41
|
+
* or starts a new trace when the job has no valid one.
|
|
41
42
|
*
|
|
42
43
|
* @param params.job - The job as the adapter received it.
|
|
43
44
|
* @param params.updateProgress - Reports progress to the backend; defaults to logging it.
|
|
44
|
-
* @param params.traceId - Defaults to `q-<jobId>`.
|
|
45
45
|
* @returns What the worker's function returned.
|
|
46
46
|
* @throws {PikkuMissingMetaError} When no metadata was generated for the queue.
|
|
47
47
|
* @throws {QueueJobFailedError | QueueJobDiscardedError} When the worker called `fail` or `discard`.
|
|
48
48
|
* @throws Whatever the worker threw, after logging it, so the adapter can apply its retry policy.
|
|
49
49
|
*/
|
|
50
|
-
export declare function runQueueJob({ job, updateProgress,
|
|
50
|
+
export declare function runQueueJob({ job, updateProgress, }: {
|
|
51
51
|
job: QueueJob;
|
|
52
52
|
updateProgress?: (progress: number | string | object) => Promise<void>;
|
|
53
|
-
traceId?: string;
|
|
54
53
|
}): Promise<void>;
|
|
@@ -3,6 +3,7 @@ import { PikkuMissingMetaError } from '../../errors/errors.js';
|
|
|
3
3
|
import { getSingletonServices, getCreateWireServices, pikkuState, } from '../../pikku-state.js';
|
|
4
4
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
5
5
|
import { resolveQueueJobIdentity } from './queue-identity.js';
|
|
6
|
+
import { continueTrace } from '../../trace.js';
|
|
6
7
|
class QueueWorkerNotFoundError extends PikkuError {
|
|
7
8
|
constructor(name) {
|
|
8
9
|
super(`Queue processor not found: ${name}`);
|
|
@@ -75,22 +76,21 @@ export async function removeQueueWorker(name) {
|
|
|
75
76
|
* The job's `pikkuUserId` claim is verified before the function runs; an
|
|
76
77
|
* unsigned or invalid claim is logged and the job runs without one. The function
|
|
77
78
|
* runs with `auth: false`, the worker's middleware, and a `queue` wire carrying
|
|
78
|
-
* `updateProgress`, `fail` and `discard`.
|
|
79
|
+
* `updateProgress`, `fail` and `discard`. It continues the job's `traceparent`,
|
|
80
|
+
* or starts a new trace when the job has no valid one.
|
|
79
81
|
*
|
|
80
82
|
* @param params.job - The job as the adapter received it.
|
|
81
83
|
* @param params.updateProgress - Reports progress to the backend; defaults to logging it.
|
|
82
|
-
* @param params.traceId - Defaults to `q-<jobId>`.
|
|
83
84
|
* @returns What the worker's function returned.
|
|
84
85
|
* @throws {PikkuMissingMetaError} When no metadata was generated for the queue.
|
|
85
86
|
* @throws {QueueJobFailedError | QueueJobDiscardedError} When the worker called `fail` or `discard`.
|
|
86
87
|
* @throws Whatever the worker threw, after logging it, so the adapter can apply its retry policy.
|
|
87
88
|
*/
|
|
88
|
-
export async function runQueueJob({ job, updateProgress,
|
|
89
|
+
export async function runQueueJob({ job, updateProgress, }) {
|
|
89
90
|
const singletonServices = getSingletonServices();
|
|
90
91
|
const createWireServices = getCreateWireServices();
|
|
91
|
-
const
|
|
92
|
-
const logger = singletonServices.logger.scope?.(
|
|
93
|
-
singletonServices.logger;
|
|
92
|
+
const trace = continueTrace(job.traceparent);
|
|
93
|
+
const logger = singletonServices.logger.scope?.(trace.traceId) ?? singletonServices.logger;
|
|
94
94
|
const meta = pikkuState(null, 'queue', 'meta');
|
|
95
95
|
const processorMeta = meta[job.queueName];
|
|
96
96
|
if (!processorMeta) {
|
|
@@ -126,7 +126,8 @@ export async function runQueueJob({ job, updateProgress, traceId, }) {
|
|
|
126
126
|
try {
|
|
127
127
|
logger.info(`Processing job ${job.id} in queue ${job.queueName}`);
|
|
128
128
|
const wire = {
|
|
129
|
-
traceId:
|
|
129
|
+
traceId: trace.traceId,
|
|
130
|
+
trace,
|
|
130
131
|
queue,
|
|
131
132
|
};
|
|
132
133
|
const result = await runPikkuFunc('queue', job.queueName, processorMeta.pikkuFuncId, {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Puts a job's `traceparent` and signed identity claim next to its payload, for
|
|
3
|
+
* queues with no per-message metadata that survives a retry. Applied by the
|
|
4
|
+
* adapter, below `SignedQueueService`, so the signature still covers `data`.
|
|
5
|
+
* A job with neither is sent as it is, unless its data looks like an envelope,
|
|
6
|
+
* or a worker would unwrap it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const wrapJobTrace: (data: unknown, traceparent?: string, pikkuUserId?: string) => any;
|
|
9
|
+
/**
|
|
10
|
+
* Undoes {@link wrapJobTrace}. A payload without the envelope is the job's data
|
|
11
|
+
* as enqueued. `pikkuUserId` is the unverified claim: only `runQueueJob` may
|
|
12
|
+
* turn it into an identity.
|
|
13
|
+
*/
|
|
14
|
+
export declare const unwrapJobTrace: (payload: any) => {
|
|
15
|
+
data: any;
|
|
16
|
+
traceparent?: string;
|
|
17
|
+
pikkuUserId?: string;
|
|
18
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const TRACEPARENT_KEY = '__pikkuTraceparent';
|
|
2
|
+
const IDENTITY_KEY = '__pikkuUserId';
|
|
3
|
+
const ENVELOPE_KEYS = new Set([TRACEPARENT_KEY, IDENTITY_KEY, 'data']);
|
|
4
|
+
const isOptionalString = (value) => value === undefined || typeof value === 'string';
|
|
5
|
+
const isEnvelope = (payload) => typeof payload === 'object' &&
|
|
6
|
+
payload !== null &&
|
|
7
|
+
(payload[TRACEPARENT_KEY] !== undefined ||
|
|
8
|
+
payload[IDENTITY_KEY] !== undefined) &&
|
|
9
|
+
isOptionalString(payload[TRACEPARENT_KEY]) &&
|
|
10
|
+
isOptionalString(payload[IDENTITY_KEY]) &&
|
|
11
|
+
Object.keys(payload).every((key) => ENVELOPE_KEYS.has(key));
|
|
12
|
+
/**
|
|
13
|
+
* Puts a job's `traceparent` and signed identity claim next to its payload, for
|
|
14
|
+
* queues with no per-message metadata that survives a retry. Applied by the
|
|
15
|
+
* adapter, below `SignedQueueService`, so the signature still covers `data`.
|
|
16
|
+
* A job with neither is sent as it is, unless its data looks like an envelope,
|
|
17
|
+
* or a worker would unwrap it.
|
|
18
|
+
*/
|
|
19
|
+
// knowledge: decisions/internals/bullmq-and-pg-boss-carry-traceparent-in-a-payload-envelope.md
|
|
20
|
+
export const wrapJobTrace = (data, traceparent, pikkuUserId) => traceparent || pikkuUserId || isEnvelope(data)
|
|
21
|
+
? {
|
|
22
|
+
[TRACEPARENT_KEY]: traceparent ?? '',
|
|
23
|
+
...(pikkuUserId ? { [IDENTITY_KEY]: pikkuUserId } : {}),
|
|
24
|
+
data,
|
|
25
|
+
}
|
|
26
|
+
: data;
|
|
27
|
+
/**
|
|
28
|
+
* Undoes {@link wrapJobTrace}. A payload without the envelope is the job's data
|
|
29
|
+
* as enqueued. `pikkuUserId` is the unverified claim: only `runQueueJob` may
|
|
30
|
+
* turn it into an identity.
|
|
31
|
+
*/
|
|
32
|
+
export const unwrapJobTrace = (payload) => {
|
|
33
|
+
if (!isEnvelope(payload))
|
|
34
|
+
return { data: payload };
|
|
35
|
+
const traceparent = payload[TRACEPARENT_KEY];
|
|
36
|
+
const pikkuUserId = payload[IDENTITY_KEY];
|
|
37
|
+
return {
|
|
38
|
+
data: payload.data,
|
|
39
|
+
...(traceparent ? { traceparent } : {}),
|
|
40
|
+
...(pikkuUserId !== undefined ? { pikkuUserId } : {}),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -91,6 +91,8 @@ export interface QueueJob<T = any, R = any> {
|
|
|
91
91
|
waitForCompletion?: (ttl?: number) => Promise<R>;
|
|
92
92
|
metadata?: () => Promise<QueueJobMetadata> | QueueJobMetadata;
|
|
93
93
|
pikkuUserId?: string;
|
|
94
|
+
/** The W3C trace the producer was in, when the adapter carried one. */
|
|
95
|
+
traceparent?: string;
|
|
94
96
|
}
|
|
95
97
|
/**
|
|
96
98
|
* Options for {@link QueueService.add}.
|
|
@@ -114,6 +116,11 @@ export interface JobOptions {
|
|
|
114
116
|
pikkuUserId?: string;
|
|
115
117
|
/** Counts against the worker's {@link PikkuWorkerConfig.groupConcurrency}. */
|
|
116
118
|
group?: JobGroup;
|
|
119
|
+
/**
|
|
120
|
+
* The W3C trace the job continues. Stamped from the calling function's wire
|
|
121
|
+
* when left unset, so set it only to move the job onto another trace.
|
|
122
|
+
*/
|
|
123
|
+
traceparent?: string;
|
|
117
124
|
}
|
|
118
125
|
export type QueueJobCounts = Record<QueueJobStatus, number>;
|
|
119
126
|
export type QueueJobRecord = {
|
|
@@ -8,4 +8,4 @@ import type { RemoteQueueJobData } from '../../services/remote-jobs.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function pikkuRemoteQueueJobFunc({ logger }: {
|
|
10
10
|
logger: Logger;
|
|
11
|
-
}, { queueName, data, jobId,
|
|
11
|
+
}, { queueName, data, jobId, traceparent, pikkuUserId }: RemoteQueueJobData): Promise<void>;
|
|
@@ -6,11 +6,13 @@ import { QueueJobDiscardedError, runQueueJob } from './queue-runner.js';
|
|
|
6
6
|
* discarded job answers 4xx so the sender stops; anything else propagates and
|
|
7
7
|
* is delivered again.
|
|
8
8
|
*/
|
|
9
|
-
export async function pikkuRemoteQueueJobFunc({ logger }, { queueName, data, jobId,
|
|
9
|
+
export async function pikkuRemoteQueueJobFunc({ logger }, { queueName, data, jobId, traceparent, pikkuUserId }) {
|
|
10
10
|
const job = {
|
|
11
11
|
queueName,
|
|
12
12
|
data,
|
|
13
|
-
id: jobId ??
|
|
13
|
+
id: jobId ?? `remote-${globalThis.crypto.randomUUID()}`,
|
|
14
|
+
traceparent,
|
|
15
|
+
pikkuUserId,
|
|
14
16
|
status: async () => 'active',
|
|
15
17
|
metadata: () => ({
|
|
16
18
|
processedAt: new Date(),
|
|
@@ -28,7 +30,7 @@ export async function pikkuRemoteQueueJobFunc({ logger }, { queueName, data, job
|
|
|
28
30
|
},
|
|
29
31
|
};
|
|
30
32
|
try {
|
|
31
|
-
await runQueueJob({ job
|
|
33
|
+
await runQueueJob({ job });
|
|
32
34
|
}
|
|
33
35
|
catch (error) {
|
|
34
36
|
if (error instanceof PikkuMissingMetaError ||
|
|
@@ -41,8 +41,13 @@ export declare class ContextAwareRPCService {
|
|
|
41
41
|
requiresAuth?: boolean;
|
|
42
42
|
sessionService?: SessionService<CoreUserSession>;
|
|
43
43
|
}, packageName?: string | null);
|
|
44
|
+
get traceparent(): string | undefined;
|
|
44
45
|
rpcExposed(funcName: string, data: any): Promise<any>;
|
|
45
46
|
rpc<In = any, Out = any>(funcName: string, data: In): Promise<Out>;
|
|
47
|
+
/** A call runs as a span of its own under the caller's. */
|
|
48
|
+
private callSpan;
|
|
49
|
+
/** The services' logger, on the trace of the wire this service belongs to. */
|
|
50
|
+
private get logger();
|
|
46
51
|
private mapUsedAddon;
|
|
47
52
|
/**
|
|
48
53
|
* Resolution half of an addon RPC call: never executes anything, so an
|
|
@@ -63,6 +68,7 @@ export declare class ContextAwareRPCService {
|
|
|
63
68
|
type: string;
|
|
64
69
|
id?: string;
|
|
65
70
|
parentRunId?: string;
|
|
71
|
+
traceparent?: string;
|
|
66
72
|
};
|
|
67
73
|
}): Promise<{
|
|
68
74
|
runId: string;
|
|
@@ -107,7 +113,7 @@ export declare const rpcService: PikkuRPCService<import("../../types/core.types.
|
|
|
107
113
|
secrets?: {
|
|
108
114
|
requireAllowedHosts?: boolean;
|
|
109
115
|
};
|
|
110
|
-
workflow?: import("../workflow/workflow.types.js").WorkflowServiceConfig
|
|
116
|
+
workflow?: Partial<import("../workflow/workflow.types.js").WorkflowServiceConfig>;
|
|
111
117
|
webhook?: import("../../services/webhook-service.js").WebhookServiceConfig;
|
|
112
118
|
postgres?: import("../../types/core.types.js").PostgresConfig;
|
|
113
119
|
}>, PikkuRPC>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { childTraceWire, outgoingTraceparent, tracedLogger, } from '../../trace.js';
|
|
1
2
|
import { AddonNotUsedError, addonCallerOf, addonMayReach, mapAddonCall, } from '../addon/addon-reach.js';
|
|
2
3
|
import { forwardStepName, isCompensationStepName, } from '../../compensation-name.js';
|
|
3
4
|
import { addonInstanceForNamespace } from '../addon/addon-runner.js';
|
|
@@ -80,7 +81,7 @@ addError(RemoteAddonTimeoutError, {
|
|
|
80
81
|
/** Headers a wiring's `headers` hook cannot set: the call's auth, trace and contract. */
|
|
81
82
|
const RESERVED_REMOTE_HEADERS = new Set([
|
|
82
83
|
'authorization',
|
|
83
|
-
'
|
|
84
|
+
'traceparent',
|
|
84
85
|
REMOTE_CONTRACT_HEADER,
|
|
85
86
|
'content-type',
|
|
86
87
|
'accept',
|
|
@@ -264,6 +265,9 @@ export class ContextAwareRPCService {
|
|
|
264
265
|
this.options = options;
|
|
265
266
|
this.packageName = packageName;
|
|
266
267
|
}
|
|
268
|
+
get traceparent() {
|
|
269
|
+
return outgoingTraceparent(this.wire);
|
|
270
|
+
}
|
|
267
271
|
async rpcExposed(funcName, data) {
|
|
268
272
|
const addonCaller = addonCallerOf(this.wire);
|
|
269
273
|
if (addonCaller && !addonMayReach(addonCaller, funcName)) {
|
|
@@ -323,14 +327,15 @@ export class ContextAwareRPCService {
|
|
|
323
327
|
return await this.rpc(funcName, data);
|
|
324
328
|
}
|
|
325
329
|
async rpc(funcName, data) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
};
|
|
330
|
+
// A local function is a span of its own. Across a boundary the callee's
|
|
331
|
+
// parent must be a span that exists, so that is the caller's.
|
|
332
|
+
const updatedWire = { ...this.wire };
|
|
333
|
+
const localWire = { ...updatedWire, ...this.callSpan() };
|
|
329
334
|
if (funcName.includes(':')) {
|
|
330
335
|
funcName = this.mapUsedAddon(funcName);
|
|
331
336
|
const addonCall = this.resolveAddonFunction(funcName);
|
|
332
337
|
if (addonCall !== NOT_RESOLVED) {
|
|
333
|
-
return await this.executeAddonFunction(addonCall, data, updatedWire);
|
|
338
|
+
return await this.executeAddonFunction(addonCall, data, addonCall.kind === 'remote' ? updatedWire : localWire);
|
|
334
339
|
}
|
|
335
340
|
}
|
|
336
341
|
let resolved;
|
|
@@ -341,7 +346,7 @@ export class ContextAwareRPCService {
|
|
|
341
346
|
if (e instanceof RPCNotFoundError) {
|
|
342
347
|
if (!this.packageName && this.services.deploymentService) {
|
|
343
348
|
const session = await resolveWireSession(this.wire);
|
|
344
|
-
return this.services.deploymentService.invoke(funcName, data, session, this.wire
|
|
349
|
+
return this.services.deploymentService.invoke(funcName, data, session, outgoingTraceparent(this.wire));
|
|
345
350
|
}
|
|
346
351
|
}
|
|
347
352
|
throw e;
|
|
@@ -353,11 +358,19 @@ export class ContextAwareRPCService {
|
|
|
353
358
|
auth: this.options.requiresAuth,
|
|
354
359
|
singletonServices: this.services,
|
|
355
360
|
data: () => data,
|
|
356
|
-
wire:
|
|
361
|
+
wire: localWire,
|
|
357
362
|
packageName: resolved.packageName,
|
|
358
363
|
addonInstance,
|
|
359
364
|
});
|
|
360
365
|
}
|
|
366
|
+
/** A call runs as a span of its own under the caller's. */
|
|
367
|
+
callSpan() {
|
|
368
|
+
return this.wire.trace ? childTraceWire(this.wire.trace) : {};
|
|
369
|
+
}
|
|
370
|
+
/** The services' logger, on the trace of the wire this service belongs to. */
|
|
371
|
+
get logger() {
|
|
372
|
+
return tracedLogger(this.services.logger, this.wire.traceId);
|
|
373
|
+
}
|
|
361
374
|
mapUsedAddon(funcName) {
|
|
362
375
|
return mapAddonCall(this.wire.addonNamespace, funcName);
|
|
363
376
|
}
|
|
@@ -427,7 +440,7 @@ export class ContextAwareRPCService {
|
|
|
427
440
|
*/
|
|
428
441
|
async executeAddonFunction(addonCall, data, wire) {
|
|
429
442
|
if (addonCall.kind === 'remote') {
|
|
430
|
-
return this.invokeRemoteAddonFunction(addonCall.namespace, addonCall.fnName, data, addonCall.addonConfig);
|
|
443
|
+
return this.invokeRemoteAddonFunction(addonCall.namespace, addonCall.fnName, data, addonCall.addonConfig, outgoingTraceparent(wire));
|
|
431
444
|
}
|
|
432
445
|
return runPikkuFunc('rpc', addonCall.namespacedFunction, addonCall.pikkuFuncId, {
|
|
433
446
|
auth: addonCall.auth,
|
|
@@ -439,7 +452,7 @@ export class ContextAwareRPCService {
|
|
|
439
452
|
addonInstance: addonCall.addonInstance,
|
|
440
453
|
});
|
|
441
454
|
}
|
|
442
|
-
async invokeRemoteAddonFunction(namespace, fnName, data, cfg) {
|
|
455
|
+
async invokeRemoteAddonFunction(namespace, fnName, data, cfg, traceparent) {
|
|
443
456
|
const serverUrl = typeof cfg.serverUrl === 'function'
|
|
444
457
|
? await cfg.serverUrl(this.services)
|
|
445
458
|
: cfg.serverUrl;
|
|
@@ -476,8 +489,8 @@ export class ContextAwareRPCService {
|
|
|
476
489
|
if (token) {
|
|
477
490
|
headers.authorization = `Bearer ${token}`;
|
|
478
491
|
}
|
|
479
|
-
if (
|
|
480
|
-
headers
|
|
492
|
+
if (traceparent) {
|
|
493
|
+
headers.traceparent = traceparent;
|
|
481
494
|
}
|
|
482
495
|
const timeoutMs = cfg.remoteTimeoutMs ?? DEFAULT_REMOTE_ADDON_TIMEOUT_MS;
|
|
483
496
|
const base = serverUrl.replace(/\/+$/, '');
|
|
@@ -496,11 +509,11 @@ export class ContextAwareRPCService {
|
|
|
496
509
|
if (e?.name === 'TimeoutError') {
|
|
497
510
|
throw new RemoteAddonTimeoutError(namespace, remoteFn, timeoutMs);
|
|
498
511
|
}
|
|
499
|
-
this.
|
|
512
|
+
this.logger.error(`Remote addon '${namespace}:${remoteFn}' could not be reached: ${e instanceof Error ? e.message : e}`);
|
|
500
513
|
throw new RemoteAddonFailedError(namespace, remoteFn);
|
|
501
514
|
}
|
|
502
515
|
if (!res.ok) {
|
|
503
|
-
throw await remoteAddonResponseError(res, namespace, remoteFn, this.
|
|
516
|
+
throw await remoteAddonResponseError(res, namespace, remoteFn, this.logger);
|
|
504
517
|
}
|
|
505
518
|
if (res.status === 204) {
|
|
506
519
|
return undefined;
|
|
@@ -513,7 +526,7 @@ export class ContextAwareRPCService {
|
|
|
513
526
|
return JSON.parse(text);
|
|
514
527
|
}
|
|
515
528
|
catch {
|
|
516
|
-
this.
|
|
529
|
+
this.logger.error(`Remote addon '${namespace}:${remoteFn}' answered ${res.status} with a body that is not JSON`);
|
|
517
530
|
throw new RemoteAddonFailedError(namespace, remoteFn);
|
|
518
531
|
}
|
|
519
532
|
}
|
|
@@ -538,7 +551,7 @@ export class ContextAwareRPCService {
|
|
|
538
551
|
!this.packageName &&
|
|
539
552
|
this.services.deploymentService) {
|
|
540
553
|
const session = await resolveWireSession(mergedWire);
|
|
541
|
-
return this.services.deploymentService.invoke(rpcName, data, session, mergedWire
|
|
554
|
+
return this.services.deploymentService.invoke(rpcName, data, session, outgoingTraceparent(mergedWire));
|
|
542
555
|
}
|
|
543
556
|
throw e;
|
|
544
557
|
}
|
|
@@ -562,11 +575,17 @@ export class ContextAwareRPCService {
|
|
|
562
575
|
workflowName = this.mapUsedAddon(workflowName);
|
|
563
576
|
}
|
|
564
577
|
const parentRunId = this.wire.workflowStep?.runId ?? this.wire.graph?.runId;
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
578
|
+
const traceparent = options?.wire?.traceparent ?? this.traceparent;
|
|
579
|
+
const wire = {
|
|
580
|
+
...(options?.wire ?? {
|
|
581
|
+
type: this.wire.wireType ?? 'unknown',
|
|
582
|
+
id: this.wire.wireId,
|
|
583
|
+
...(parentRunId ? { parentRunId } : {}),
|
|
584
|
+
...(this.wire.pikkuUserId
|
|
585
|
+
? { pikkuUserId: this.wire.pikkuUserId }
|
|
586
|
+
: {}),
|
|
587
|
+
}),
|
|
588
|
+
...(traceparent ? { traceparent } : {}),
|
|
570
589
|
};
|
|
571
590
|
return this.services.workflowService.startWorkflow(workflowName, input, wire, this, options);
|
|
572
591
|
}
|
|
@@ -593,7 +612,7 @@ export class ContextAwareRPCService {
|
|
|
593
612
|
`Set up a DeploymentService to enable mesh function calls.`);
|
|
594
613
|
}
|
|
595
614
|
const session = await resolveWireSession(this.wire);
|
|
596
|
-
return this.services.deploymentService.invoke(funcName, data, session, this.wire
|
|
615
|
+
return this.services.deploymentService.invoke(funcName, data, session, outgoingTraceparent(this.wire));
|
|
597
616
|
}
|
|
598
617
|
}
|
|
599
618
|
/**
|
|
@@ -652,6 +671,9 @@ class ContextRPCView {
|
|
|
652
671
|
this.startWorkflow = serviceRPC.startWorkflow.bind(serviceRPC);
|
|
653
672
|
this.rpcWithWire = serviceRPC.rpcWithWire.bind(serviceRPC);
|
|
654
673
|
}
|
|
674
|
+
get traceparent() {
|
|
675
|
+
return this.serviceRPC.traceparent;
|
|
676
|
+
}
|
|
655
677
|
get agent() {
|
|
656
678
|
return this.serviceRPC.agent;
|
|
657
679
|
}
|
|
@@ -5,6 +5,8 @@ import type { AgentInterruptResult } from '../agent/agent-interrupt.js';
|
|
|
5
5
|
export type PikkuRPC<Invoke extends (...args: any[]) => any = (...args: any[]) => any, Mesh extends (...args: any[]) => any = (...args: any[]) => any, startWorkflow extends (...args: any[]) => any = (...args: any[]) => any, AgentRun extends (...args: any[]) => any = (...args: any[]) => any, AgentStream extends (...args: any[]) => any = (...args: any[]) => any> = {
|
|
6
6
|
depth: number;
|
|
7
7
|
global: boolean;
|
|
8
|
+
/** The W3C trace of the invocation holding this `rpc`, if it is in one. */
|
|
9
|
+
traceparent?: string;
|
|
8
10
|
invoke: Invoke;
|
|
9
11
|
mesh: Mesh;
|
|
10
12
|
exposed: (name: string, data: any) => Promise<any>;
|
|
@@ -8,4 +8,4 @@ import type { RemoteScheduledJobData } from '../../services/remote-jobs.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function pikkuRemoteScheduledJobFunc({ logger }: {
|
|
10
10
|
logger: Logger;
|
|
11
|
-
}, { taskName }: RemoteScheduledJobData): Promise<void>;
|
|
11
|
+
}, { taskName, traceparent }: RemoteScheduledJobData): Promise<void>;
|
|
@@ -6,9 +6,9 @@ import { ScheduledTaskNotFoundError, runScheduledTask, } from './scheduler-runne
|
|
|
6
6
|
*
|
|
7
7
|
* @throws {BadRequestError} When the named task is not wired in this unit.
|
|
8
8
|
*/
|
|
9
|
-
export async function pikkuRemoteScheduledJobFunc({ logger }, { taskName }) {
|
|
9
|
+
export async function pikkuRemoteScheduledJobFunc({ logger }, { taskName, traceparent }) {
|
|
10
10
|
try {
|
|
11
|
-
await runScheduledTask({ name: taskName,
|
|
11
|
+
await runScheduledTask({ name: taskName, traceparent });
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
14
|
if (error instanceof ScheduledTaskNotFoundError) {
|
|
@@ -2,11 +2,11 @@ import type { CoreUserSession } from '../../types/core.types.js';
|
|
|
2
2
|
import type { CoreScheduledTask } from './scheduler.types.js';
|
|
3
3
|
import { PikkuError } from '../../errors/error-handler.js';
|
|
4
4
|
import type { CorePikkuFunctionConfig, CorePikkuFunctionSessionless } from '../../function/functions.types.js';
|
|
5
|
-
/** Parameters for {@link runScheduledTask}: the task's name, an optional session to run it with, and an optional trace
|
|
5
|
+
/** Parameters for {@link runScheduledTask}: the task's name, an optional session to run it with, and an optional W3C trace to continue. */
|
|
6
6
|
export type RunScheduledTasksParams = {
|
|
7
7
|
name: string;
|
|
8
8
|
session?: CoreUserSession;
|
|
9
|
-
|
|
9
|
+
traceparent?: string;
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
12
|
* Registers a sessionless function to run on a cron schedule.
|
|
@@ -35,10 +35,10 @@ export declare class ScheduledTaskNotFoundError extends PikkuError {
|
|
|
35
35
|
* When `session` is given it is placed on the wire, so the function and its
|
|
36
36
|
* middleware see that user; otherwise the task runs without one.
|
|
37
37
|
*
|
|
38
|
-
* @param params - See {@link RunScheduledTasksParams}. `
|
|
38
|
+
* @param params - See {@link RunScheduledTasksParams}. A missing or invalid `traceparent` starts a new trace.
|
|
39
39
|
* @throws {ScheduledTaskNotFoundError} When the task or its metadata is missing.
|
|
40
40
|
* @throws The error the function threw, including the one `scheduledTask.skip()` raises, so the caller can tell a skip from a failure.
|
|
41
41
|
*/
|
|
42
|
-
export declare function runScheduledTask({ name, session,
|
|
42
|
+
export declare function runScheduledTask({ name, session, traceparent, }: RunScheduledTasksParams): Promise<void>;
|
|
43
43
|
/** Every wired scheduled task, keyed by name. Scheduler services read this to know what to schedule. */
|
|
44
44
|
export declare const getScheduledTasks: () => Map<string, CoreScheduledTask>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getErrorResponse, PikkuError } from '../../errors/error-handler.js';
|
|
2
2
|
import { getSingletonServices, getCreateWireServices, pikkuState, } from '../../pikku-state.js';
|
|
3
3
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
4
|
+
import { continueTrace } from '../../trace.js';
|
|
4
5
|
import { PikkuSessionService, createMiddlewareSessionWireProps, } from '../../services/user-session-service.js';
|
|
5
6
|
/**
|
|
6
7
|
* Registers a sessionless function to run on a cron schedule.
|
|
@@ -56,16 +57,15 @@ class ScheduledTaskSkippedError extends PikkuError {
|
|
|
56
57
|
* When `session` is given it is placed on the wire, so the function and its
|
|
57
58
|
* middleware see that user; otherwise the task runs without one.
|
|
58
59
|
*
|
|
59
|
-
* @param params - See {@link RunScheduledTasksParams}. `
|
|
60
|
+
* @param params - See {@link RunScheduledTasksParams}. A missing or invalid `traceparent` starts a new trace.
|
|
60
61
|
* @throws {ScheduledTaskNotFoundError} When the task or its metadata is missing.
|
|
61
62
|
* @throws The error the function threw, including the one `scheduledTask.skip()` raises, so the caller can tell a skip from a failure.
|
|
62
63
|
*/
|
|
63
|
-
export async function runScheduledTask({ name, session,
|
|
64
|
+
export async function runScheduledTask({ name, session, traceparent, }) {
|
|
64
65
|
const singletonServices = getSingletonServices();
|
|
65
66
|
const createWireServices = getCreateWireServices();
|
|
66
|
-
const
|
|
67
|
-
const scopedLogger = singletonServices.logger.scope?.(
|
|
68
|
-
singletonServices.logger;
|
|
67
|
+
const trace = continueTrace(traceparent);
|
|
68
|
+
const scopedLogger = singletonServices.logger.scope?.(trace.traceId) ?? singletonServices.logger;
|
|
69
69
|
const task = pikkuState(null, 'scheduler', 'tasks').get(name);
|
|
70
70
|
const meta = pikkuState(null, 'scheduler', 'meta')[name];
|
|
71
71
|
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
@@ -79,7 +79,8 @@ export async function runScheduledTask({ name, session, traceId, }) {
|
|
|
79
79
|
throw new ScheduledTaskNotFoundError(`Scheduled task meta not found: ${name}`);
|
|
80
80
|
}
|
|
81
81
|
const wire = {
|
|
82
|
-
traceId:
|
|
82
|
+
traceId: trace.traceId,
|
|
83
|
+
trace,
|
|
83
84
|
scheduledTask: {
|
|
84
85
|
name,
|
|
85
86
|
schedule: task.schedule,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { rpcService } from '../rpc/rpc-runner.js';
|
|
2
2
|
import { setupTrigger } from './trigger-runner.js';
|
|
3
3
|
import { getSingletonServices, getCreateWireServices, pikkuState, } from '../../pikku-state.js';
|
|
4
|
+
import { traceWire, tracedLogger } from '../../trace.js';
|
|
4
5
|
/**
|
|
5
6
|
* Base class for a {@link TriggerService}: pairs registered trigger sources with
|
|
6
7
|
* the triggers listening to them and owns teardown. Subclasses decide where and
|
|
@@ -54,9 +55,11 @@ export class PikkuTriggerService {
|
|
|
54
55
|
// The user rides on the wire, where the function runner resolves the
|
|
55
56
|
// target's session and credentials from it and a started workflow records
|
|
56
57
|
// it on the run.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
// One trace per event, shared by every target it fires.
|
|
59
|
+
const wire = {
|
|
60
|
+
...traceWire(),
|
|
61
|
+
...(options?.pikkuUserId ? { pikkuUserId: options.pikkuUserId } : {}),
|
|
62
|
+
};
|
|
60
63
|
const wireServices = await createWireServices?.(singletonServices, wire);
|
|
61
64
|
const services = { ...singletonServices, ...wireServices };
|
|
62
65
|
const rpc = rpcService.getContextRPCService(services, wire, false);
|
|
@@ -74,7 +77,7 @@ export class PikkuTriggerService {
|
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
catch (error) {
|
|
77
|
-
singletonServices.logger.error(`Error invoking ${target.targetType} '${target.targetName}' from trigger '${triggerName}': ${error}`);
|
|
80
|
+
tracedLogger(singletonServices.logger, wire.traceId).error(`Error invoking ${target.targetType} '${target.targetName}' from trigger '${triggerName}': ${error}`);
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
}
|