@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
|
@@ -2,7 +2,8 @@ import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
|
2
2
|
import { runPikkuFunc } from '../../function/function-runner.js';
|
|
3
3
|
import { createMiddlewareSessionWireProps, } from '../../services/user-session-service.js';
|
|
4
4
|
import { combineChannelMiddleware, wrapChannelWithMiddleware, } from '../../channel-middleware-runner.js';
|
|
5
|
-
|
|
5
|
+
import { childTraceWire } from '../../trace.js';
|
|
6
|
+
export const runChannelLifecycleWithMiddleware = async ({ channelConfig, meta, lifecycleConfig, lifecycleType, services, channel, data, channelMiddlewareMeta, userSession, wireServices, trace, }) => {
|
|
6
7
|
const lifecycleMiddleware = typeof lifecycleConfig === 'object' && 'middleware' in lifecycleConfig
|
|
7
8
|
? lifecycleConfig.middleware || []
|
|
8
9
|
: [];
|
|
@@ -17,6 +18,7 @@ export const runChannelLifecycleWithMiddleware = async ({ channelConfig, meta, l
|
|
|
17
18
|
});
|
|
18
19
|
let wire = {
|
|
19
20
|
channel,
|
|
21
|
+
...childTraceWire(trace),
|
|
20
22
|
...(userSession ? createMiddlewareSessionWireProps(userSession) : {}),
|
|
21
23
|
};
|
|
22
24
|
if (allChannelMiddleware.length > 0) {
|
|
@@ -3,6 +3,7 @@ import { runPikkuFunc } from '../../function/function-runner.js';
|
|
|
3
3
|
import { isChannelRPCPending, isChannelRPCResponse } from './channel-rpc.js';
|
|
4
4
|
import { handleChannelRPCResponse } from './channel-host-rpc.js';
|
|
5
5
|
import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js';
|
|
6
|
+
import { childTraceWire } from '../../trace.js';
|
|
6
7
|
const getChannelMeta = (channelName) => {
|
|
7
8
|
return pikkuState(null, 'channel', 'meta')[channelName];
|
|
8
9
|
};
|
|
@@ -88,6 +89,7 @@ export const processMessageHandlers = (services, channelConfig, channelHandler,
|
|
|
88
89
|
sessionService: userSession,
|
|
89
90
|
wire: {
|
|
90
91
|
channel: channelHandler.getChannel(),
|
|
92
|
+
...childTraceWire(channelHandler.trace),
|
|
91
93
|
...(userSession && createMiddlewareSessionWireProps(userSession)),
|
|
92
94
|
},
|
|
93
95
|
packageName,
|
|
@@ -15,7 +15,7 @@ export declare const getChannelHostRPC: (channel: PikkuChannel<unknown, any>, op
|
|
|
15
15
|
* Kept out of the channel handler so a channel nobody calls back on pays for no
|
|
16
16
|
* registry, and because a handler has no services to build a validator with.
|
|
17
17
|
*/
|
|
18
|
-
export declare const channelRemote: (channel: PikkuChannel<unknown, any, any>, funcName: string, data?: unknown) => Promise<unknown>;
|
|
18
|
+
export declare const channelRemote: (channel: PikkuChannel<unknown, any, any>, funcName: string, data?: unknown, traceparent?: string) => Promise<unknown>;
|
|
19
19
|
/** False for an unknown connection or id, so a stray frame is dropped. */
|
|
20
20
|
export declare const handleChannelRPCResponse: (channelId: string, message: unknown) => boolean;
|
|
21
21
|
/**
|
|
@@ -19,10 +19,10 @@ export const getChannelHostRPC = (channel, options = {}) => {
|
|
|
19
19
|
* Kept out of the channel handler so a channel nobody calls back on pays for no
|
|
20
20
|
* registry, and because a handler has no services to build a validator with.
|
|
21
21
|
*/
|
|
22
|
-
export const channelRemote = async (channel, funcName, data) => getChannelHostRPC(channel, {
|
|
22
|
+
export const channelRemote = async (channel, funcName, data, traceparent) => getChannelHostRPC(channel, {
|
|
23
23
|
validateInput: createChannelRPCInputValidator(getSingletonServices()),
|
|
24
24
|
validateResult: createChannelRPCResultValidator(getSingletonServices()),
|
|
25
|
-
}).invoke(funcName, data);
|
|
25
|
+
}).invoke(funcName, data, undefined, traceparent);
|
|
26
26
|
/** False for an unknown connection or id, so a stray frame is dropped. */
|
|
27
27
|
export const handleChannelRPCResponse = (channelId, message) => hostRPCByChannel.get(channelId)?.handleResponse(message) ?? false;
|
|
28
28
|
/**
|
|
@@ -7,9 +7,15 @@ import { type ApprovalRequester, type Capabilities } from './channel-rpc.types.j
|
|
|
7
7
|
* name is refused exactly like an unknown one, so the caller cannot probe for
|
|
8
8
|
* what this end can do.
|
|
9
9
|
*/
|
|
10
|
-
export declare const createChannelRPCResponder: ({ capabilities, send, approve, }: {
|
|
10
|
+
export declare const createChannelRPCResponder: ({ capabilities, send, approve, trustInboundTraceparent, }: {
|
|
11
11
|
capabilities: Capabilities;
|
|
12
12
|
send: (data: unknown) => Promise<void> | void;
|
|
13
13
|
/** Omitted means there is nobody to ask, which refuses rather than runs. */
|
|
14
14
|
approve?: ApprovalRequester;
|
|
15
|
+
/**
|
|
16
|
+
* Hands a capability the peer's `traceparent`. Off by default, like public
|
|
17
|
+
* HTTP: the caller may turn it on only for a channel whose peer it has
|
|
18
|
+
* authenticated.
|
|
19
|
+
*/
|
|
20
|
+
trustInboundTraceparent?: boolean;
|
|
15
21
|
}) => (message: unknown) => Promise<boolean>;
|
|
@@ -7,7 +7,7 @@ import { CHANNEL_RPC_PENDING, CHANNEL_RPC_RESPONSE, isCapabilityDef, isChannelRP
|
|
|
7
7
|
* name is refused exactly like an unknown one, so the caller cannot probe for
|
|
8
8
|
* what this end can do.
|
|
9
9
|
*/
|
|
10
|
-
export const createChannelRPCResponder = ({ capabilities, send, approve, }) => {
|
|
10
|
+
export const createChannelRPCResponder = ({ capabilities, send, approve, trustInboundTraceparent = false, }) => {
|
|
11
11
|
return async (message) => {
|
|
12
12
|
if (!isChannelRPCRequest(message)) {
|
|
13
13
|
return false;
|
|
@@ -58,7 +58,7 @@ export const createChannelRPCResponder = ({ capabilities, send, approve, }) => {
|
|
|
58
58
|
action: CHANNEL_RPC_RESPONSE,
|
|
59
59
|
id: message.id,
|
|
60
60
|
ok: true,
|
|
61
|
-
result: await execute(message.data),
|
|
61
|
+
result: await execute(message.data, trustInboundTraceparent ? { traceparent: message.traceparent } : {}),
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
catch (e) {
|
|
@@ -30,5 +30,5 @@ export declare class ChannelDeploymentService implements DeploymentService {
|
|
|
30
30
|
* session on the wire would carry the caller's credentials while authorising
|
|
31
31
|
* nothing.
|
|
32
32
|
*/
|
|
33
|
-
invoke(funcName: string, data: unknown, _session?: unknown,
|
|
33
|
+
invoke(funcName: string, data: unknown, _session?: unknown, traceparent?: string): Promise<unknown>;
|
|
34
34
|
}
|
|
@@ -37,7 +37,7 @@ export class ChannelDeploymentService {
|
|
|
37
37
|
* session on the wire would carry the caller's credentials while authorising
|
|
38
38
|
* nothing.
|
|
39
39
|
*/
|
|
40
|
-
async invoke(funcName, data, _session,
|
|
40
|
+
async invoke(funcName, data, _session, traceparent) {
|
|
41
41
|
// Not a security control — the peer must check what it is handed, and a
|
|
42
42
|
// hostile caller would send arguments that pass. It catches version drift
|
|
43
43
|
// and fails it here rather than inside the peer's process.
|
|
@@ -60,7 +60,7 @@ export class ChannelDeploymentService {
|
|
|
60
60
|
id,
|
|
61
61
|
funcName,
|
|
62
62
|
data,
|
|
63
|
-
|
|
63
|
+
traceparent,
|
|
64
64
|
};
|
|
65
65
|
try {
|
|
66
66
|
await this.send(request);
|
|
@@ -12,7 +12,8 @@ export interface ChannelRPCRequest {
|
|
|
12
12
|
id: string;
|
|
13
13
|
funcName: string;
|
|
14
14
|
data: unknown;
|
|
15
|
-
|
|
15
|
+
/** A claim by the peer. See `trustInboundTraceparent` on the responder. */
|
|
16
|
+
traceparent?: string;
|
|
16
17
|
}
|
|
17
18
|
/** The answer to a {@link ChannelRPCRequest} with the same `id`: a result when `ok`, an error name and message otherwise. */
|
|
18
19
|
export interface ChannelRPCResponse {
|
|
@@ -63,7 +64,9 @@ export interface ApprovalPolicy {
|
|
|
63
64
|
approvalDescriptionFn?: (input: unknown) => Promise<string> | string;
|
|
64
65
|
}
|
|
65
66
|
/** A function one end of a channel offers for the peer to call through `channel.remote`. */
|
|
66
|
-
export type CapabilityHandler = (data: any
|
|
67
|
+
export type CapabilityHandler = (data: any, context?: {
|
|
68
|
+
traceparent?: string;
|
|
69
|
+
}) => Promise<unknown> | unknown;
|
|
67
70
|
/**
|
|
68
71
|
* `needsApproval` is required here on purpose. On `AgentToolDef` the same
|
|
69
72
|
* field is optional and absence means "do not ask"; here absence means the
|
|
@@ -18,7 +18,7 @@ export const isChannelRPCRequest = (message) => isRecord(message) &&
|
|
|
18
18
|
message.id.length > 0 &&
|
|
19
19
|
typeof message.funcName === 'string' &&
|
|
20
20
|
message.funcName.length > 0 &&
|
|
21
|
-
(message.
|
|
21
|
+
(message.traceparent === undefined || typeof message.traceparent === 'string');
|
|
22
22
|
export const isChannelRPCResponse = (message) => isRecord(message) &&
|
|
23
23
|
message.action === CHANNEL_RPC_RESPONSE &&
|
|
24
24
|
typeof message.id === 'string' &&
|
|
@@ -4,12 +4,14 @@ export type Channel<ChannelType = unknown, OpeningData = unknown> = {
|
|
|
4
4
|
channelName: string;
|
|
5
5
|
channelObject?: ChannelType;
|
|
6
6
|
openingData?: OpeningData;
|
|
7
|
+
/** The connection's W3C trace, so every frame continues it. */
|
|
8
|
+
traceparent?: string;
|
|
7
9
|
};
|
|
8
10
|
/**
|
|
9
11
|
* Persists open channels for serverless runtimes, where each connect, message and disconnect runs in a separate invocation.
|
|
10
12
|
*
|
|
11
|
-
* It keeps the channel's opening data, its state, and the
|
|
12
|
-
* session established on connect. The session itself is not stored: each
|
|
13
|
+
* It keeps the channel's opening data, its trace, its state, and the
|
|
14
|
+
* `pikkuUserId` of the session established on connect. The session itself is not stored: each
|
|
13
15
|
* message reloads it from the `sessionStore` by that id.
|
|
14
16
|
*/
|
|
15
17
|
export declare abstract class ChannelStore<ChannelType = unknown, OpeningData = unknown, TypedChannel = Channel<ChannelType, OpeningData>> {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Persists open channels for serverless runtimes, where each connect, message and disconnect runs in a separate invocation.
|
|
3
3
|
*
|
|
4
|
-
* It keeps the channel's opening data, its state, and the
|
|
5
|
-
* session established on connect. The session itself is not stored: each
|
|
4
|
+
* It keeps the channel's opening data, its trace, its state, and the
|
|
5
|
+
* `pikkuUserId` of the session established on connect. The session itself is not stored: each
|
|
6
6
|
* message reloads it from the `sessionStore` by that id.
|
|
7
7
|
*/
|
|
8
8
|
export class ChannelStore {
|
|
@@ -7,6 +7,7 @@ export type CorePikkuChannelMiddleware<Services = any, Event = unknown> = (servi
|
|
|
7
7
|
/** Builds a {@link CorePikkuChannelMiddleware} from configuration, so the same middleware can be reused with different settings. */
|
|
8
8
|
export type CorePikkuChannelMiddlewareFactory<In = any, Services = any, Event = unknown> = (input: In) => CorePikkuChannelMiddleware<Services, Event>;
|
|
9
9
|
import type { CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuFunctionSessionless, CorePikkuPermission } from '../../function/functions.types.js';
|
|
10
|
+
import type { TraceContext } from '../../trace.js';
|
|
10
11
|
/** Options for running a channel connection. They mean the same as the matching {@link RunHTTPWiringOptions}. */
|
|
11
12
|
export type RunChannelOptions = Partial<{
|
|
12
13
|
skipUserSession: boolean;
|
|
@@ -134,6 +135,8 @@ export interface PikkuChannel<OpeningData, out Out, Remote extends (...args: any
|
|
|
134
135
|
}
|
|
135
136
|
/** The runtime side of one connection: how a channel's sends reach the actual socket. */
|
|
136
137
|
export interface PikkuChannelHandler<OpeningData = unknown, Out = unknown> {
|
|
138
|
+
/** The connection's trace. Each message and reverse RPC is a span in it. */
|
|
139
|
+
trace?: TraceContext;
|
|
137
140
|
send(message: Out, isBinary?: boolean): Promise<void> | void;
|
|
138
141
|
sendBinary(data: BinaryData): Promise<void> | void;
|
|
139
142
|
getChannel(): PikkuChannel<OpeningData, Out>;
|
|
@@ -10,6 +10,7 @@ import { PikkuSessionService, createMiddlewareSessionWireProps, } from '../../..
|
|
|
10
10
|
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js';
|
|
11
11
|
import { runChannelLifecycleWithMiddleware } from '../channel-common.js';
|
|
12
12
|
import { getSingletonServices, getCreateWireServices, pikkuState, } from '../../../pikku-state.js';
|
|
13
|
+
import { childTraceWire, createTrace, tracedLogger } from '../../../trace.js';
|
|
13
14
|
const runUpgradeMiddleware = async (singletonServices, request, response, userSession) => {
|
|
14
15
|
const http = createHTTPWire(request, response);
|
|
15
16
|
const httpGroups = pikkuState(null, 'middleware', 'httpGroup');
|
|
@@ -73,9 +74,14 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
73
74
|
const main = async () => {
|
|
74
75
|
try {
|
|
75
76
|
channelHandler = new PikkuLocalChannelHandler(channelId, channelConfig.name, openingData, singletonServices.logger);
|
|
77
|
+
// knowledge: decisions/internals/a-channel-connection-is-one-trace.md
|
|
78
|
+
channelHandler.trace = createTrace();
|
|
79
|
+
const { traceId } = channelHandler.trace;
|
|
80
|
+
const logger = tracedLogger(singletonServices.logger, traceId);
|
|
76
81
|
const channel = channelHandler.getChannel();
|
|
77
82
|
const wire = {
|
|
78
83
|
channel,
|
|
84
|
+
...childTraceWire(channelHandler.trace),
|
|
79
85
|
...createMiddlewareSessionWireProps(userSession),
|
|
80
86
|
};
|
|
81
87
|
if (createWireServices) {
|
|
@@ -103,17 +109,19 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
103
109
|
channelMiddlewareMeta: meta.channelMiddleware,
|
|
104
110
|
userSession,
|
|
105
111
|
wireServices,
|
|
112
|
+
trace: channelHandler?.trace,
|
|
106
113
|
});
|
|
107
114
|
if (result !== undefined) {
|
|
108
115
|
await channel.send(result);
|
|
109
116
|
}
|
|
110
117
|
}
|
|
111
118
|
catch (e) {
|
|
112
|
-
|
|
119
|
+
logger.error(`Error handling onConnect: ${e}`);
|
|
113
120
|
const errorResponse = getErrorResponse(e);
|
|
114
121
|
await channel.send({
|
|
115
122
|
error: errorResponse?.message ??
|
|
116
123
|
(isProduction() ? 'Internal server error' : e.message),
|
|
124
|
+
errorId: traceId,
|
|
117
125
|
...(!isProduction() && { errorName: e.constructor?.name }),
|
|
118
126
|
});
|
|
119
127
|
}
|
|
@@ -132,14 +140,16 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
132
140
|
channelMiddlewareMeta: meta.channelMiddleware,
|
|
133
141
|
userSession,
|
|
134
142
|
wireServices,
|
|
143
|
+
trace: channelHandler?.trace,
|
|
135
144
|
});
|
|
136
145
|
}
|
|
137
146
|
catch (e) {
|
|
138
|
-
|
|
147
|
+
logger.error(`Error handling onDisconnect: ${e}`);
|
|
139
148
|
const errorResponse = getErrorResponse(e);
|
|
140
149
|
await channel.send({
|
|
141
150
|
error: errorResponse?.message ??
|
|
142
151
|
(isProduction() ? 'Internal server error' : e.message),
|
|
152
|
+
errorId: traceId,
|
|
143
153
|
...(!isProduction() && { errorName: e.constructor?.name }),
|
|
144
154
|
});
|
|
145
155
|
}
|
|
@@ -160,15 +170,16 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
160
170
|
}
|
|
161
171
|
}
|
|
162
172
|
catch (e) {
|
|
163
|
-
|
|
173
|
+
logger.error(e);
|
|
164
174
|
const errorResponse = getErrorResponse(e);
|
|
165
175
|
await channel.send({
|
|
166
176
|
error: errorResponse?.message ??
|
|
167
177
|
(isProduction() ? 'Internal server error' : e.message),
|
|
178
|
+
errorId: traceId,
|
|
168
179
|
...(!isProduction() && { errorName: e.constructor?.name }),
|
|
169
180
|
});
|
|
170
181
|
setTimeout(() => {
|
|
171
|
-
Promise.resolve(channel.close()).catch((closeError) =>
|
|
182
|
+
Promise.resolve(channel.close()).catch((closeError) => logger.error(closeError));
|
|
172
183
|
}, 200);
|
|
173
184
|
}
|
|
174
185
|
});
|
|
@@ -181,7 +192,7 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
181
192
|
}
|
|
182
193
|
}
|
|
183
194
|
catch (e) {
|
|
184
|
-
|
|
195
|
+
logger.error(`Error handling binary message: ${e.message}`);
|
|
185
196
|
}
|
|
186
197
|
});
|
|
187
198
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BinaryData, PikkuChannel, PikkuChannelHandler } from './channel.types.js';
|
|
2
|
+
import { type TraceContext } from '../../trace.js';
|
|
2
3
|
/**
|
|
3
4
|
* Base class for a runtime's {@link PikkuChannelHandler}. A runtime implements `send` and `sendBinary`; this builds the {@link PikkuChannel} on top, with in-memory state and `remote`.
|
|
4
5
|
*
|
|
@@ -10,6 +11,7 @@ export declare abstract class PikkuAbstractChannelHandler<OpeningData = unknown,
|
|
|
10
11
|
protected openingData: OpeningData;
|
|
11
12
|
protected channel?: PikkuChannel<OpeningData, Out>;
|
|
12
13
|
constructor(channelId: string, channelName: string, openingData: OpeningData);
|
|
14
|
+
trace?: TraceContext;
|
|
13
15
|
abstract send(message: Out, isBinary?: boolean): Promise<void> | void;
|
|
14
16
|
abstract sendBinary(data: BinaryData): Promise<void> | void;
|
|
15
17
|
getChannel(): PikkuChannel<OpeningData, Out>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { channelRemote, releaseChannelHostRPC } from './channel-host-rpc.js';
|
|
2
|
+
import { outgoingTraceparent } from '../../trace.js';
|
|
2
3
|
/**
|
|
3
4
|
* Base class for a runtime's {@link PikkuChannelHandler}. A runtime implements `send` and `sendBinary`; this builds the {@link PikkuChannel} on top, with in-memory state and `remote`.
|
|
4
5
|
*
|
|
@@ -14,6 +15,7 @@ export class PikkuAbstractChannelHandler {
|
|
|
14
15
|
this.channelName = channelName;
|
|
15
16
|
this.openingData = openingData;
|
|
16
17
|
}
|
|
18
|
+
trace;
|
|
17
19
|
getChannel() {
|
|
18
20
|
if (!this.channel) {
|
|
19
21
|
let channelState;
|
|
@@ -32,7 +34,7 @@ export class PikkuAbstractChannelHandler {
|
|
|
32
34
|
clearState: () => {
|
|
33
35
|
channelState = undefined;
|
|
34
36
|
},
|
|
35
|
-
remote: (funcName, data) => channelRemote(this.getChannel(), funcName, data),
|
|
37
|
+
remote: (funcName, data) => channelRemote(this.getChannel(), funcName, data, outgoingTraceparent(this)),
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
40
|
return this.channel;
|
|
@@ -7,6 +7,7 @@ import { PikkuSessionService, createMiddlewareSessionWireProps, } from '../../..
|
|
|
7
7
|
import { getSingletonServices, getCreateWireServices, pikkuState, } from '../../../pikku-state.js';
|
|
8
8
|
import { PikkuFetchHTTPRequest } from '../../http/pikku-fetch-http-request.js';
|
|
9
9
|
import { runChannelLifecycleWithMiddleware } from '../channel-common.js';
|
|
10
|
+
import { createTrace, formatTraceparent, parseTraceparent, tracedLogger, } from '../../../trace.js';
|
|
10
11
|
const getVariablesForChannel = ({ channelId, channelName, channelHandlerFactory, openingData, }) => {
|
|
11
12
|
const channels = pikkuState(null, 'channel', 'channels');
|
|
12
13
|
const channelConfig = channels.get(channelName);
|
|
@@ -26,6 +27,21 @@ const getVariablesForChannel = ({ channelId, channelName, channelHandlerFactory,
|
|
|
26
27
|
meta,
|
|
27
28
|
};
|
|
28
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* The connection's trace as connect stored it. A frame is a span in it, so this
|
|
32
|
+
* is the connection's own span and not a new one.
|
|
33
|
+
*/
|
|
34
|
+
// knowledge: decisions/internals/a-channel-connection-is-one-trace.md
|
|
35
|
+
const storedTrace = (traceparent) => {
|
|
36
|
+
const stored = parseTraceparent(traceparent);
|
|
37
|
+
return stored
|
|
38
|
+
? {
|
|
39
|
+
traceId: stored.traceId,
|
|
40
|
+
spanId: stored.parentSpanId,
|
|
41
|
+
sampled: stored.sampled,
|
|
42
|
+
}
|
|
43
|
+
: createTrace();
|
|
44
|
+
};
|
|
29
45
|
/**
|
|
30
46
|
* Handles a serverless connect event: opens the channel, stores it, and runs `onConnect`.
|
|
31
47
|
*
|
|
@@ -56,18 +72,21 @@ export const runChannelConnect = async ({ channelId, channelObject, request, res
|
|
|
56
72
|
coerceDataFromSchema,
|
|
57
73
|
userSession,
|
|
58
74
|
});
|
|
75
|
+
const trace = createTrace();
|
|
59
76
|
try {
|
|
60
77
|
await channelStore.addChannel({
|
|
61
78
|
channelId,
|
|
62
79
|
channelName: channelConfig.name,
|
|
63
80
|
openingData,
|
|
64
81
|
channelObject,
|
|
82
|
+
traceparent: formatTraceparent(trace),
|
|
65
83
|
});
|
|
66
|
-
const { channel } = getVariablesForChannel({
|
|
84
|
+
const { channel, channelHandler } = getVariablesForChannel({
|
|
67
85
|
channelId,
|
|
68
86
|
channelHandlerFactory,
|
|
69
87
|
channelName: channelConfig.name,
|
|
70
88
|
});
|
|
89
|
+
channelHandler.trace = trace;
|
|
71
90
|
channel.setState = (state) => channelStore.setState(channelId, state);
|
|
72
91
|
// knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
|
|
73
92
|
channel.getState = () => channelStore.getState(channelId);
|
|
@@ -95,6 +114,7 @@ export const runChannelConnect = async ({ channelId, channelObject, request, res
|
|
|
95
114
|
channelMiddlewareMeta: meta.channelMiddleware,
|
|
96
115
|
userSession,
|
|
97
116
|
wireServices,
|
|
117
|
+
trace: channelHandler.trace,
|
|
98
118
|
});
|
|
99
119
|
}
|
|
100
120
|
const pikkuUserId = userSession.getPikkuUserId();
|
|
@@ -131,7 +151,7 @@ export const runChannelDisconnect = async ({ channelId, channelStore, channelHan
|
|
|
131
151
|
singletonServices.logger.info(`Channel ${channelId} not found during disconnect - already cleaned up`);
|
|
132
152
|
return;
|
|
133
153
|
}
|
|
134
|
-
const { openingData, channelName, pikkuUserId } = channelData;
|
|
154
|
+
const { openingData, channelName, pikkuUserId, traceparent } = channelData;
|
|
135
155
|
const { channel, channelConfig, meta } = getVariablesForChannel({
|
|
136
156
|
channelId,
|
|
137
157
|
channelHandlerFactory,
|
|
@@ -173,6 +193,7 @@ export const runChannelDisconnect = async ({ channelId, channelStore, channelHan
|
|
|
173
193
|
channelMiddlewareMeta: meta.channelMiddleware,
|
|
174
194
|
userSession,
|
|
175
195
|
wireServices,
|
|
196
|
+
trace: storedTrace(traceparent),
|
|
176
197
|
});
|
|
177
198
|
}
|
|
178
199
|
catch (e) {
|
|
@@ -198,13 +219,14 @@ export const runChannelMessage = async ({ channelId, channelStore, channelHandle
|
|
|
198
219
|
const singletonServices = getSingletonServices();
|
|
199
220
|
const createWireServices = getCreateWireServices();
|
|
200
221
|
let wireServices;
|
|
201
|
-
const { openingData, channelName, pikkuUserId } = await channelStore.getChannel(channelId);
|
|
222
|
+
const { openingData, channelName, pikkuUserId, traceparent } = await channelStore.getChannel(channelId);
|
|
202
223
|
const { channel, channelHandler, channelConfig } = getVariablesForChannel({
|
|
203
224
|
channelId,
|
|
204
225
|
channelHandlerFactory,
|
|
205
226
|
openingData,
|
|
206
227
|
channelName,
|
|
207
228
|
});
|
|
229
|
+
channelHandler.trace = storedTrace(traceparent);
|
|
208
230
|
channel.setState = (state) => channelStore.setState(channelId, state);
|
|
209
231
|
// knowledge: decisions/internals/channel-state-accessors-are-unsound-generics-that-every-implementation-asserts.md
|
|
210
232
|
channel.getState = () => channelStore.getState(channelId);
|
|
@@ -234,10 +256,11 @@ export const runChannelMessage = async ({ channelId, channelStore, channelHandle
|
|
|
234
256
|
response = await onMessage(data);
|
|
235
257
|
}
|
|
236
258
|
catch (e) {
|
|
237
|
-
singletonServices.logger.error(`Error processing message: ${e.message || e}`);
|
|
259
|
+
tracedLogger(singletonServices.logger, channelHandler.trace.traceId).error(`Error processing message: ${e.message || e}`);
|
|
238
260
|
return {
|
|
239
261
|
error: e.message || 'Unknown error',
|
|
240
262
|
errorName: e.constructor?.name,
|
|
263
|
+
errorId: channelHandler.trace.traceId,
|
|
241
264
|
};
|
|
242
265
|
}
|
|
243
266
|
finally {
|
|
@@ -66,6 +66,7 @@ export const resolveGatewayAdapter = (config, services) => {
|
|
|
66
66
|
}
|
|
67
67
|
return resolved;
|
|
68
68
|
};
|
|
69
|
+
import { traceWire } from '../../trace.js';
|
|
69
70
|
/**
|
|
70
71
|
* Wires a messaging gateway (Slack, WhatsApp, a web chat, ...) so each inbound message runs `func`
|
|
71
72
|
* with the adapter-normalised message.
|
|
@@ -318,7 +319,8 @@ export const createListenerMessageHandler = (name, config, singletonServices) =>
|
|
|
318
319
|
const parsed = adapter.parse(rawData);
|
|
319
320
|
if (!parsed)
|
|
320
321
|
return;
|
|
321
|
-
|
|
322
|
+
// Here rather than left to the function runner: middleware runs first.
|
|
323
|
+
const wire = traceWire();
|
|
322
324
|
const gateway = {
|
|
323
325
|
gatewayName: name,
|
|
324
326
|
senderId: parsed.senderId,
|
|
@@ -62,9 +62,9 @@ export declare const pikkuFetch: <In, Out>(request: Request | PikkuHTTPRequest,
|
|
|
62
62
|
*
|
|
63
63
|
* The lower-level form of {@link fetch} for adapters that bring their own
|
|
64
64
|
* request and response objects (Express, Fastify, uWS). A web `Request` is
|
|
65
|
-
* wrapped with the `maxBodySize` limit. The trace
|
|
66
|
-
* then
|
|
67
|
-
* the client as `errorId` on failures.
|
|
65
|
+
* wrapped with the `maxBodySize` limit. The trace comes from a valid `traceId`,
|
|
66
|
+
* then `traceparent` when `trustInboundTraceparent` is set, and is otherwise new.
|
|
67
|
+
* Its id is returned to the client as `errorId` on failures.
|
|
68
68
|
*
|
|
69
69
|
* Errors are written to `response`: a 4xx keeps its message and payload, while a
|
|
70
70
|
* 5xx or an unknown error hides its message unless `exposeErrors` is set outside
|
|
@@ -78,4 +78,4 @@ export declare const pikkuFetch: <In, Out>(request: Request | PikkuHTTPRequest,
|
|
|
78
78
|
* @throws The original error when `bubbleErrors` is set, after the error response has been written.
|
|
79
79
|
* @throws {Error} When `skipUserSession` is set on a route that requires auth.
|
|
80
80
|
*/
|
|
81
|
-
export declare const fetchData: <In, Out>(request: Request | PikkuHTTPRequest, response: PikkuHTTPResponse, { skipUserSession, respondWith404, logWarningsForStatusCodes, coerceDataFromSchema, bubbleErrors, exposeErrors,
|
|
81
|
+
export declare const fetchData: <In, Out>(request: Request | PikkuHTTPRequest, response: PikkuHTTPResponse, { skipUserSession, respondWith404, logWarningsForStatusCodes, coerceDataFromSchema, bubbleErrors, exposeErrors, traceId, trustInboundTraceparent, maxBodySize, }?: RunHTTPWiringOptions) => Promise<Out | void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NotFoundError } from '../../errors/errors.js';
|
|
2
2
|
import { createWeakUID, isSerializable } from '../../utils.js';
|
|
3
|
+
import { resolveHTTPTrace } from '../../trace.js';
|
|
3
4
|
import { getSingletonServices, getCreateWireServices, } from '../../pikku-state.js';
|
|
4
5
|
import { PikkuSessionService, createMiddlewareSessionWireProps, } from '../../services/user-session-service.js';
|
|
5
6
|
import { getErrorResponse } from '../../errors/error-handler.js';
|
|
@@ -120,7 +121,7 @@ export const createHTTPWire = (request, response) => {
|
|
|
120
121
|
};
|
|
121
122
|
const executeRoute = async (services, matchedRoute, http, options) => {
|
|
122
123
|
const { params, route, meta } = matchedRoute;
|
|
123
|
-
const { singletonServices, createWireServices, skipUserSession,
|
|
124
|
+
const { singletonServices, createWireServices, skipUserSession, trace } = services;
|
|
124
125
|
const userSession = new PikkuSessionService(singletonServices.sessionStore);
|
|
125
126
|
http?.request?.setParams(params);
|
|
126
127
|
if (meta.headersSchemaName && http.request && singletonServices.schema) {
|
|
@@ -206,7 +207,8 @@ const executeRoute = async (services, matchedRoute, http, options) => {
|
|
|
206
207
|
});
|
|
207
208
|
}
|
|
208
209
|
const wire = {
|
|
209
|
-
traceId:
|
|
210
|
+
traceId: trace.traceId,
|
|
211
|
+
trace,
|
|
210
212
|
http,
|
|
211
213
|
channel,
|
|
212
214
|
...createMiddlewareSessionWireProps(userSession),
|
|
@@ -295,9 +297,9 @@ export const pikkuFetch = async (request, params = {}) => {
|
|
|
295
297
|
*
|
|
296
298
|
* The lower-level form of {@link fetch} for adapters that bring their own
|
|
297
299
|
* request and response objects (Express, Fastify, uWS). A web `Request` is
|
|
298
|
-
* wrapped with the `maxBodySize` limit. The trace
|
|
299
|
-
* then
|
|
300
|
-
* the client as `errorId` on failures.
|
|
300
|
+
* wrapped with the `maxBodySize` limit. The trace comes from a valid `traceId`,
|
|
301
|
+
* then `traceparent` when `trustInboundTraceparent` is set, and is otherwise new.
|
|
302
|
+
* Its id is returned to the client as `errorId` on failures.
|
|
301
303
|
*
|
|
302
304
|
* Errors are written to `response`: a 4xx keeps its message and payload, while a
|
|
303
305
|
* 5xx or an unknown error hides its message unless `exposeErrors` is set outside
|
|
@@ -311,22 +313,16 @@ export const pikkuFetch = async (request, params = {}) => {
|
|
|
311
313
|
* @throws The original error when `bubbleErrors` is set, after the error response has been written.
|
|
312
314
|
* @throws {Error} When `skipUserSession` is set on a route that requires auth.
|
|
313
315
|
*/
|
|
314
|
-
export const fetchData = async (request, response, { skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, exposeErrors = !isProduction(),
|
|
316
|
+
export const fetchData = async (request, response, { skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, exposeErrors = !isProduction(), traceId, trustInboundTraceparent, maxBodySize, } = {}) => {
|
|
315
317
|
const singletonServices = getSingletonServices();
|
|
316
318
|
const createWireServices = getCreateWireServices();
|
|
317
319
|
let result;
|
|
318
320
|
const pikkuRequest = request instanceof Request
|
|
319
321
|
? new PikkuFetchHTTPRequest(request, { maxBodySize })
|
|
320
322
|
: request;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
requestId = pikkuRequest.header('x-request-id');
|
|
325
|
-
}
|
|
326
|
-
catch { }
|
|
327
|
-
}
|
|
328
|
-
requestId = requestId || generateRequestId?.() || createWeakUID();
|
|
329
|
-
const scopedLogger = singletonServices.logger.scope?.(requestId) ?? singletonServices.logger;
|
|
323
|
+
const trace = resolveHTTPTrace(() => pikkuRequest.header('traceparent'), singletonServices.logger, { traceId, trustInboundTraceparent });
|
|
324
|
+
// Read at the point of logging: mesh auth can continue the caller's trace.
|
|
325
|
+
const scopedLogger = () => singletonServices.logger.scope?.(trace.traceId) ?? singletonServices.logger;
|
|
330
326
|
const http = createHTTPWire(pikkuRequest, response);
|
|
331
327
|
const apiType = http.request.method();
|
|
332
328
|
const apiRoute = http.request.path();
|
|
@@ -344,7 +340,7 @@ export const fetchData = async (request, response, { skipUserSession = false, re
|
|
|
344
340
|
response.status(204).json(undefined);
|
|
345
341
|
return;
|
|
346
342
|
}
|
|
347
|
-
scopedLogger.info({
|
|
343
|
+
scopedLogger().info({
|
|
348
344
|
message: 'Route not found',
|
|
349
345
|
apiRoute,
|
|
350
346
|
apiType,
|
|
@@ -356,11 +352,11 @@ export const fetchData = async (request, response, { skipUserSession = false, re
|
|
|
356
352
|
singletonServices,
|
|
357
353
|
createWireServices,
|
|
358
354
|
skipUserSession,
|
|
359
|
-
|
|
355
|
+
trace,
|
|
360
356
|
}, matchedRoute, http, { coerceDataFromSchema }));
|
|
361
357
|
return result;
|
|
362
358
|
}
|
|
363
359
|
catch (e) {
|
|
364
|
-
handleHTTPError(e, http,
|
|
360
|
+
handleHTTPError(e, http, trace.traceId, scopedLogger(), logWarningsForStatusCodes, respondWith404, bubbleErrors, exposeErrors);
|
|
365
361
|
}
|
|
366
362
|
};
|
|
@@ -20,7 +20,7 @@ export type AssertHTTPWiringParams<In, HTTPWiring extends string> = ExtractHTTPW
|
|
|
20
20
|
* - `coerceDataFromSchema` – coerce query and path strings to the schema's types (default `true`).
|
|
21
21
|
* - `bubbleErrors` – rethrow after writing the error response.
|
|
22
22
|
* - `exposeErrors` – include messages and stacks of 5xx errors; ignored in production.
|
|
23
|
-
* - `
|
|
23
|
+
* - `trustInboundTraceparent` – continue the trace named by `traceparent` on every request. Off by default, because the header is caller-supplied.
|
|
24
24
|
*/
|
|
25
25
|
export type RunHTTPWiringOptions = Partial<{
|
|
26
26
|
skipUserSession: boolean;
|
|
@@ -29,8 +29,8 @@ export type RunHTTPWiringOptions = Partial<{
|
|
|
29
29
|
coerceDataFromSchema: boolean;
|
|
30
30
|
bubbleErrors: boolean;
|
|
31
31
|
exposeErrors: boolean;
|
|
32
|
-
|
|
33
|
-
/**
|
|
32
|
+
trustInboundTraceparent: boolean;
|
|
33
|
+
/** A trace ID the runtime already holds: 32 lowercase hex, anything else is ignored. */
|
|
34
34
|
traceId: string;
|
|
35
35
|
/** Maximum request body size in bytes, applied when pikku wraps a fetch `Request`. */
|
|
36
36
|
maxBodySize: number;
|