lambda-deadline-middleware 0.0.0 → 1.1.0
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/LICENSE +1 -1
- package/LICENSES/MIT.txt +18 -0
- package/README.md +99 -159
- package/REUSE.toml +9 -0
- package/SECURITY.md +34 -38
- package/dist/context-store.d.ts +2 -2
- package/dist/context-store.d.ts.map +1 -1
- package/dist/context-store.js +9 -17
- package/dist/context-store.js.map +1 -1
- package/dist/error.d.ts +4 -4
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +2 -2
- package/dist/error.js.map +1 -1
- package/dist/handler-wrapper.d.ts +5 -4
- package/dist/handler-wrapper.d.ts.map +1 -1
- package/dist/handler-wrapper.js +2 -4
- package/dist/handler-wrapper.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +7 -10
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +53 -67
- package/dist/middleware.js.map +1 -1
- package/dist/types.d.ts +1 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -12
- package/dist/types.js.map +1 -1
- package/package.json +39 -33
- package/src/context-store.ts +12 -24
- package/src/error.ts +6 -6
- package/src/handler-wrapper.ts +9 -10
- package/src/index.ts +3 -10
- package/src/middleware.ts +76 -89
- package/src/types.ts +5 -33
- package/dist/config.d.ts +0 -4
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -15
- package/dist/config.js.map +0 -1
- package/dist/registration.d.ts +0 -10
- package/dist/registration.d.ts.map +0 -1
- package/dist/registration.js +0 -23
- package/dist/registration.js.map +0 -1
- package/dist/telemetry.d.ts +0 -5
- package/dist/telemetry.d.ts.map +0 -1
- package/dist/telemetry.js +0 -82
- package/dist/telemetry.js.map +0 -1
- package/src/config.ts +0 -16
- package/src/registration.ts +0 -36
- package/src/telemetry.ts +0 -129
package/src/index.ts
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText:
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 lambda-deadline-middleware contributors
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
export { withLambdaDeadline } from "./handler-wrapper.js";
|
|
5
|
-
export { deadlineMiddleware
|
|
5
|
+
export { deadlineMiddleware } from "./middleware.js";
|
|
6
6
|
export { DeadlineExceededError, isDeadlineExceeded } from "./error.js";
|
|
7
7
|
export { getRemainingTimeInMillis } from "./context-store.js";
|
|
8
8
|
|
|
9
|
-
export type {
|
|
10
|
-
Milliseconds,
|
|
11
|
-
FlushBufferMs,
|
|
12
|
-
RequestDeadlineMs,
|
|
13
|
-
DeadlineComputation,
|
|
14
|
-
DeadlineMiddlewareConfig,
|
|
15
|
-
DeadlineOptions,
|
|
16
|
-
} from "./types.js";
|
|
9
|
+
export type { Milliseconds, DeadlineOptions } from "./types.js";
|
|
17
10
|
|
|
18
11
|
export type { LambdaContextLike } from "./context-store.js";
|
package/src/middleware.ts
CHANGED
|
@@ -1,109 +1,96 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText:
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 lambda-deadline-middleware contributors
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
import { getRemainingTimeInMillis } from "./context-store.js";
|
|
5
|
-
import { DeadlineExceededError } from "./error.js";
|
|
6
|
-
import type { DeadlineComputation, DeadlineMiddlewareConfig, RequestDeadlineMs } from "./types.js";
|
|
7
|
-
import { milliseconds } from "./types.js";
|
|
8
|
-
|
|
9
4
|
import type {
|
|
10
5
|
FinalizeHandler,
|
|
11
6
|
FinalizeHandlerArguments,
|
|
12
7
|
FinalizeHandlerOutput,
|
|
13
|
-
FinalizeRequestMiddleware,
|
|
14
8
|
HandlerExecutionContext,
|
|
9
|
+
Pluggable,
|
|
15
10
|
} from "@smithy/types";
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
import { getRemainingTimeInMillis } from "./context-store.js";
|
|
13
|
+
import { DeadlineExceededError } from "./error.js";
|
|
14
|
+
import { milliseconds } from "./types.js";
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
return { kind: "no-context" };
|
|
22
|
-
}
|
|
16
|
+
import type { DeadlineOptions } from "./types.js";
|
|
23
17
|
|
|
24
|
-
|
|
18
|
+
export const composeSignals = (
|
|
19
|
+
existing: AbortSignal | undefined,
|
|
20
|
+
deadline: AbortSignal,
|
|
21
|
+
): AbortSignal => {
|
|
22
|
+
if (existing === undefined) return deadline;
|
|
23
|
+
return AbortSignal.any([existing, deadline]);
|
|
24
|
+
};
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
26
|
+
export const deadlineMiddleware = <Input extends object, Output extends object>(
|
|
27
|
+
options?: DeadlineOptions,
|
|
28
|
+
): Pluggable<Input, Output> => {
|
|
29
|
+
const raw = options?.flushBufferMs ?? 1000;
|
|
30
|
+
if (raw < 0) {
|
|
31
|
+
throw new TypeError(`flushBufferMs option must be non-negative, received: ${raw}`);
|
|
32
32
|
}
|
|
33
|
+
const flushBufferMs = milliseconds(raw);
|
|
33
34
|
|
|
34
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- branded narrowing: deadline is validated > 0 above
|
|
35
|
-
return { kind: "deadline", value: deadline as RequestDeadlineMs };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface DeadlineTimer {
|
|
39
|
-
readonly controller: AbortController;
|
|
40
|
-
[Symbol.dispose]: () => void;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function createDeadlineTimer(
|
|
44
|
-
deadlineMs: RequestDeadlineMs,
|
|
45
|
-
config: DeadlineMiddlewareConfig,
|
|
46
|
-
): DeadlineTimer {
|
|
47
|
-
const controller = new AbortController();
|
|
48
|
-
const remaining = milliseconds(deadlineMs + config.flushBufferMs);
|
|
49
|
-
const error = new DeadlineExceededError({
|
|
50
|
-
deadlineMs: milliseconds(deadlineMs),
|
|
51
|
-
flushBufferMs: config.flushBufferMs,
|
|
52
|
-
remainingMs: remaining,
|
|
53
|
-
});
|
|
54
|
-
const timeoutId = setTimeout(() => {
|
|
55
|
-
controller.abort(error);
|
|
56
|
-
}, deadlineMs);
|
|
57
35
|
return {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
36
|
+
applyToStack(stack) {
|
|
37
|
+
// Registered at "finalizeRequest" (attempt level) rather than API-call level so each retry gets a deadline
|
|
38
|
+
// computed from the actual remaining time at that moment. API-call level would cache a stale deadline
|
|
39
|
+
// across retries, which grow more dangerous after backoff delays eat into remaining time.
|
|
40
|
+
stack.add(
|
|
41
|
+
(
|
|
42
|
+
next: FinalizeHandler<Input, Output>,
|
|
43
|
+
_context: HandlerExecutionContext,
|
|
44
|
+
): FinalizeHandler<Input, Output> =>
|
|
45
|
+
async (args: FinalizeHandlerArguments<Input>): Promise<FinalizeHandlerOutput<Output>> => {
|
|
46
|
+
const remaining = getRemainingTimeInMillis();
|
|
47
|
+
if (remaining === undefined) return next(args);
|
|
64
48
|
|
|
65
|
-
|
|
66
|
-
existing: AbortSignal | undefined,
|
|
67
|
-
deadline: AbortSignal,
|
|
68
|
-
): AbortSignal {
|
|
69
|
-
if (existing === undefined) return deadline;
|
|
70
|
-
return AbortSignal.any([existing, deadline]);
|
|
71
|
-
}
|
|
49
|
+
const deadline = remaining - flushBufferMs;
|
|
72
50
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// oxlint-disable-next-line typescript/consistent-return -- switch is exhaustive over DeadlineComputation discriminated union
|
|
81
|
-
async (args: FinalizeHandlerArguments<object>): Promise<FinalizeHandlerOutput<object>> => {
|
|
82
|
-
const computation = computeDeadline(config);
|
|
51
|
+
if (deadline <= 0) {
|
|
52
|
+
throw new DeadlineExceededError({
|
|
53
|
+
deadlineMs: milliseconds(0),
|
|
54
|
+
flushBufferMs,
|
|
55
|
+
remainingMs: milliseconds(remaining),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
83
58
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const timeoutId = setTimeout(() => {
|
|
61
|
+
controller.abort(
|
|
62
|
+
new DeadlineExceededError({
|
|
63
|
+
deadlineMs: milliseconds(deadline),
|
|
64
|
+
flushBufferMs,
|
|
65
|
+
remainingMs: milliseconds(remaining),
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
}, deadline);
|
|
87
69
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
70
|
+
// `using` guarantees cleanup (clearTimeout) even if next() throws, the promise rejects,
|
|
71
|
+
// or an external abort signal fires — strictly more reliable than try/finally.
|
|
72
|
+
using _timer = {
|
|
73
|
+
[Symbol.dispose]() {
|
|
74
|
+
clearTimeout(timeoutId);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
94
77
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
78
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Smithy request is an opaque object; we access optional signal property
|
|
79
|
+
const request = args.request as { signal?: AbortSignal } | undefined;
|
|
80
|
+
const signal = composeSignals(request?.signal, controller.signal);
|
|
81
|
+
const result = await next({
|
|
82
|
+
...args,
|
|
83
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- spreading opaque Smithy request to add signal
|
|
84
|
+
request: { ...(args.request as object), signal },
|
|
85
|
+
});
|
|
86
|
+
return result;
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
step: "finalizeRequest",
|
|
90
|
+
name: "deadlineMiddleware",
|
|
91
|
+
override: true,
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -1,50 +1,22 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText:
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 lambda-deadline-middleware contributors
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
+
// Branded type prevents interchange errors at compile time (e.g. passing seconds where milliseconds are expected).
|
|
5
|
+
// Zero runtime cost. Smart constructor below validates at the boundary and brands the value.
|
|
4
6
|
declare const BrandSymbol: unique symbol;
|
|
5
7
|
|
|
6
8
|
type Brand<T, B extends string> = T & { readonly [BrandSymbol]: B };
|
|
7
9
|
|
|
8
10
|
export type Milliseconds = Brand<number, "Milliseconds">;
|
|
9
11
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
export type RequestDeadlineMs = Brand<number, "RequestDeadlineMs">;
|
|
13
|
-
|
|
14
|
-
export function milliseconds(value: number): Milliseconds {
|
|
12
|
+
export const milliseconds = (value: number): Milliseconds => {
|
|
15
13
|
if (!Number.isFinite(value)) {
|
|
16
14
|
throw new TypeError(`milliseconds value must be finite, received: ${value}`);
|
|
17
15
|
}
|
|
18
16
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- branded type constructor: value is validated above
|
|
19
17
|
return value as Milliseconds;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function flushBufferMs(value: number): FlushBufferMs {
|
|
23
|
-
if (!Number.isFinite(value)) {
|
|
24
|
-
throw new TypeError(`flushBufferMs value must be finite, received: ${value}`);
|
|
25
|
-
}
|
|
26
|
-
if (value < 0) {
|
|
27
|
-
throw new TypeError(`flushBufferMs must be non-negative, received: ${value}`);
|
|
28
|
-
}
|
|
29
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- branded type constructor: value is validated above
|
|
30
|
-
return value as FlushBufferMs;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type DeadlineComputation =
|
|
34
|
-
| { readonly kind: "deadline"; readonly value: RequestDeadlineMs }
|
|
35
|
-
| {
|
|
36
|
-
readonly kind: "insufficient-time";
|
|
37
|
-
readonly remaining: Milliseconds;
|
|
38
|
-
readonly buffer: FlushBufferMs;
|
|
39
|
-
}
|
|
40
|
-
| { readonly kind: "no-context" };
|
|
41
|
-
|
|
42
|
-
export interface DeadlineMiddlewareConfig {
|
|
43
|
-
readonly flushBufferMs: FlushBufferMs;
|
|
44
|
-
readonly telemetryEnabled: boolean;
|
|
45
|
-
}
|
|
18
|
+
};
|
|
46
19
|
|
|
47
20
|
export interface DeadlineOptions {
|
|
48
21
|
readonly flushBufferMs?: number;
|
|
49
|
-
readonly telemetryEnabled?: boolean;
|
|
50
22
|
}
|
package/dist/config.d.ts
DELETED
package/dist/config.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":"AAIA,cAAc,0BAA0B,uBAAuB;AAE/D,OAAO,iBAAS,YAAY,KAAK,8BAA8B","names":[],"sources":["src/config.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { flushBufferMs } from \"./types.js\";\nimport type { DeadlineMiddlewareConfig, DeadlineOptions } from \"./types.js\";\n\nexport function parseConfig(raw: DeadlineOptions | undefined): DeadlineMiddlewareConfig {\n const buffer = raw?.flushBufferMs ?? 1000;\n if (buffer < 0) {\n throw new TypeError(`flushBufferMs option must be non-negative, received: ${buffer}`);\n }\n return {\n flushBufferMs: flushBufferMs(buffer),\n telemetryEnabled: raw?.telemetryEnabled ?? true,\n };\n}\n"]}
|
package/dist/config.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors
|
|
2
|
-
// SPDX-License-Identifier: MIT
|
|
3
|
-
import { flushBufferMs } from "./types.js";
|
|
4
|
-
export function parseConfig(raw) {
|
|
5
|
-
const buffer = raw?.flushBufferMs ?? 1e3;
|
|
6
|
-
if (buffer < 0) {
|
|
7
|
-
throw new TypeError(`flushBufferMs option must be non-negative, received: ${buffer}`);
|
|
8
|
-
}
|
|
9
|
-
return {
|
|
10
|
-
flushBufferMs: flushBufferMs(buffer),
|
|
11
|
-
telemetryEnabled: raw?.telemetryEnabled ?? true
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":";;AAGA,SAAS,qBAAqB;AAG9B,OAAO,SAAS,YAAY,KAA4D;CACtF,MAAM,SAAS,KAAK,iBAAiB;CACrC,IAAI,SAAS,GAAG;EACd,MAAM,IAAI,UAAU,wDAAwD,QAAQ;CACtF;CACA,OAAO;EACL,eAAe,cAAc,MAAM;EACnC,kBAAkB,KAAK,oBAAoB;CAC7C;AACF","names":[],"sources":["src/config.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport { flushBufferMs } from \"./types.js\";\nimport type { DeadlineMiddlewareConfig, DeadlineOptions } from \"./types.js\";\n\nexport function parseConfig(raw: DeadlineOptions | undefined): DeadlineMiddlewareConfig {\n const buffer = raw?.flushBufferMs ?? 1000;\n if (buffer < 0) {\n throw new TypeError(`flushBufferMs option must be non-negative, received: ${buffer}`);\n }\n return {\n flushBufferMs: flushBufferMs(buffer),\n telemetryEnabled: raw?.telemetryEnabled ?? true,\n };\n}\n"]}
|
package/dist/registration.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Pluggable } from "@smithy/types";
|
|
2
|
-
import type { DeadlineOptions } from "./types.js";
|
|
3
|
-
export declare function deadlineMiddleware(options?: DeadlineOptions): Pluggable<object, object>;
|
|
4
|
-
export declare function withDeadline<T extends {
|
|
5
|
-
middlewareStack: {
|
|
6
|
-
use: (pluggable: Pluggable<any, any>) => void;
|
|
7
|
-
};
|
|
8
|
-
}>(client: T, options?: DeadlineOptions): T;
|
|
9
|
-
|
|
10
|
-
//# sourceMappingURL=registration.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":"AAGA,cAAc,iBAAiB;AAI/B,cAAc,uBAAuB;AAErC,OAAO,iBAAS,mBAAmB,UAAU,kBAAkB;AAkB/D,OAAO,iBAAS,aAEd,UAAU;CAAE,iBAAiB;EAAE,MAAM,WAAW;CAA6B;AAAE,GAC/E,QAAQ,GAAG,UAAU,kBAAkB","names":[],"sources":["src/registration.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport type { Pluggable } from \"@smithy/types\";\n\nimport { parseConfig } from \"./config.js\";\nimport { deadlineMiddlewareHandler } from \"./middleware.js\";\nimport type { DeadlineOptions } from \"./types.js\";\n\nexport function deadlineMiddleware(options?: DeadlineOptions): Pluggable<object, object> {\n const config = parseConfig(options);\n\n return {\n applyToStack(stack) {\n stack.add(deadlineMiddlewareHandler(config), {\n step: \"finalizeRequest\",\n name: \"deadlineMiddleware\",\n override: true,\n });\n },\n };\n}\n\n// WeakSet allows GC of discarded clients in long-running processes\n// while still preventing duplicate middleware registration.\nconst registeredClients = new WeakSet<object>();\n\nexport function withDeadline<\n // oxlint-disable-next-line typescript/no-explicit-any -- AWS SDK clients use varying ServiceInput/OutputTypes generics, making `any` the only correct constraint here\n T extends { middlewareStack: { use: (pluggable: Pluggable<any, any>) => void } },\n>(client: T, options?: DeadlineOptions): T {\n if (registeredClients.has(client)) return client;\n registeredClients.add(client);\n client.middlewareStack.use(deadlineMiddleware(options));\n return client;\n}\n"]}
|
package/dist/registration.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { parseConfig } from "./config.js";
|
|
2
|
-
import { deadlineMiddlewareHandler } from "./middleware.js";
|
|
3
|
-
export function deadlineMiddleware(options) {
|
|
4
|
-
const config = parseConfig(options);
|
|
5
|
-
return { applyToStack(stack) {
|
|
6
|
-
stack.add(deadlineMiddlewareHandler(config), {
|
|
7
|
-
step: "finalizeRequest",
|
|
8
|
-
name: "deadlineMiddleware",
|
|
9
|
-
override: true
|
|
10
|
-
});
|
|
11
|
-
} };
|
|
12
|
-
}
|
|
13
|
-
// WeakSet allows GC of discarded clients in long-running processes
|
|
14
|
-
// while still preventing duplicate middleware registration.
|
|
15
|
-
const registeredClients = new WeakSet();
|
|
16
|
-
export function withDeadline(client, options) {
|
|
17
|
-
if (registeredClients.has(client)) return client;
|
|
18
|
-
registeredClients.add(client);
|
|
19
|
-
client.middlewareStack.use(deadlineMiddleware(options));
|
|
20
|
-
return client;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
//# sourceMappingURL=registration.js.map
|
package/dist/registration.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":"AAKA,SAAS,mBAAmB;AAC5B,SAAS,iCAAiC;AAG1C,OAAO,SAAS,mBAAmB,SAAsD;CACvF,MAAM,SAAS,YAAY,OAAO;CAElC,OAAO,EACL,aAAa,OAAO;EAClB,MAAM,IAAI,0BAA0B,MAAM,GAAG;GAC3C,MAAM;GACN,MAAM;GACN,UAAU;EACZ,CAAC;CACH,EACF;AACF;;;AAIA,MAAM,oBAAoB,IAAI,QAAgB;AAE9C,OAAO,SAAS,aAGd,QAAW,SAA8B;CACzC,IAAI,kBAAkB,IAAI,MAAM,GAAG,OAAO;CAC1C,kBAAkB,IAAI,MAAM;CAC5B,OAAO,gBAAgB,IAAI,mBAAmB,OAAO,CAAC;CACtD,OAAO;AACT","names":[],"sources":["src/registration.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport type { Pluggable } from \"@smithy/types\";\n\nimport { parseConfig } from \"./config.js\";\nimport { deadlineMiddlewareHandler } from \"./middleware.js\";\nimport type { DeadlineOptions } from \"./types.js\";\n\nexport function deadlineMiddleware(options?: DeadlineOptions): Pluggable<object, object> {\n const config = parseConfig(options);\n\n return {\n applyToStack(stack) {\n stack.add(deadlineMiddlewareHandler(config), {\n step: \"finalizeRequest\",\n name: \"deadlineMiddleware\",\n override: true,\n });\n },\n };\n}\n\n// WeakSet allows GC of discarded clients in long-running processes\n// while still preventing duplicate middleware registration.\nconst registeredClients = new WeakSet<object>();\n\nexport function withDeadline<\n // oxlint-disable-next-line typescript/no-explicit-any -- AWS SDK clients use varying ServiceInput/OutputTypes generics, making `any` the only correct constraint here\n T extends { middlewareStack: { use: (pluggable: Pluggable<any, any>) => void } },\n>(client: T, options?: DeadlineOptions): T {\n if (registeredClients.has(client)) return client;\n registeredClients.add(client);\n client.middlewareStack.use(deadlineMiddleware(options));\n return client;\n}\n"]}
|
package/dist/telemetry.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { DeadlineExceededError } from "./error.js";
|
|
2
|
-
import type { DeadlineMiddlewareConfig } from "./types.js";
|
|
3
|
-
export declare function emitDeadlineAbort(error: DeadlineExceededError, config: DeadlineMiddlewareConfig): Promise<void>;
|
|
4
|
-
|
|
5
|
-
//# sourceMappingURL=telemetry.d.ts.map
|
package/dist/telemetry.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":"AAGA,cAAc,6BAA6B;AAC3C,cAAc,gCAAgC;AAwG9C,OAAO,iBAAe,kBACpB,OAAO,uBACP,QAAQ,2BACP","names":[],"sources":["src/telemetry.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport type { DeadlineExceededError } from \"./error.js\";\nimport type { DeadlineMiddlewareConfig } from \"./types.js\";\n\ninterface AbortDetails {\n readonly deadlineMs: number;\n readonly flushBufferMs: number;\n readonly remainingMs: number;\n}\n\ninterface TelemetryEmitter {\n recordDeadlineAbort: (details: AbortDetails) => void;\n setDeadlineErrorStatus: (error: DeadlineExceededError) => void;\n}\n\nlet emitter: TelemetryEmitter | undefined;\nlet detected = false;\n\nasync function detectEmitter(): Promise<TelemetryEmitter | undefined> {\n if (detected) return emitter;\n detected = true;\n\n try {\n // Variable indirection prevents TypeScript from resolving the module\n // at compile time — keeps @opentelemetry/api as a purely optional runtime dep.\n const moduleName = \"@opentelemetry/api\";\n // oxlint-disable-next-line typescript/no-unsafe-assignment -- dynamic import of optional runtime dependency\n const otelApi: Record<string, unknown> = await import(/* webpackIgnore: true */ moduleName);\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing optional OTel API surface\n const trace = otelApi[\"trace\"] as { getActiveSpan: () => unknown } | undefined;\n\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing optional OTel API surface\n const SpanStatusCode = otelApi[\"SpanStatusCode\"] as\n | {\n ERROR: number;\n }\n | undefined;\n\n if (!trace || !SpanStatusCode) {\n emitter = undefined;\n return emitter;\n }\n\n emitter = {\n recordDeadlineAbort(details: AbortDetails): void {\n try {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span from untyped getActiveSpan()\n const span = trace.getActiveSpan() as Record<string, unknown> | undefined;\n if (!span) return;\n\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.addEvent method\n const addEvent = span[\"addEvent\"] as\n | ((name: string, attributes: Record<string, unknown>) => void)\n | undefined;\n if (typeof addEvent !== \"function\") return;\n\n addEvent.call(span, \"lambda-deadline-middleware.abort\", {\n \"deadline.duration_ms\": details.deadlineMs,\n \"deadline.flush_buffer_ms\": details.flushBufferMs,\n \"deadline.remaining_ms\": details.remainingMs,\n });\n } catch {\n // Telemetry must never disrupt request processing\n }\n },\n\n setDeadlineErrorStatus(error: DeadlineExceededError): void {\n try {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span from untyped getActiveSpan()\n const span = trace.getActiveSpan() as Record<string, unknown> | undefined;\n if (!span) return;\n\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.setStatus method\n const setStatus = span[\"setStatus\"] as\n | ((status: { code: number; message: string }) => void)\n | undefined;\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.setAttribute method\n const setAttribute = span[\"setAttribute\"] as\n | ((key: string, value: unknown) => void)\n | undefined;\n\n if (typeof setStatus === \"function\") {\n setStatus.call(span, {\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n }\n\n if (typeof setAttribute === \"function\") {\n setAttribute.call(span, \"error.type\", \"DeadlineExceededError\");\n setAttribute.call(span, \"deadline.duration_ms\", error.deadlineMs);\n setAttribute.call(span, \"deadline.flush_buffer_ms\", error.flushBufferMs);\n setAttribute.call(span, \"deadline.remaining_ms\", error.remainingMs);\n }\n } catch {\n // Telemetry must never disrupt request processing\n }\n },\n };\n } catch {\n emitter = undefined;\n }\n\n return emitter;\n}\n\nexport async function emitDeadlineAbort(\n error: DeadlineExceededError,\n config: DeadlineMiddlewareConfig,\n): Promise<void> {\n try {\n if (!config.telemetryEnabled) return;\n\n const em = await detectEmitter();\n if (!em) return;\n\n em.recordDeadlineAbort({\n deadlineMs: error.deadlineMs,\n flushBufferMs: error.flushBufferMs,\n remainingMs: error.remainingMs,\n });\n\n em.setDeadlineErrorStatus(error);\n } catch {\n // Telemetry must never disrupt request processing\n }\n}\n"]}
|
package/dist/telemetry.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
let emitter;
|
|
2
|
-
let detected = false;
|
|
3
|
-
async function detectEmitter() {
|
|
4
|
-
if (detected) return emitter;
|
|
5
|
-
detected = true;
|
|
6
|
-
try {
|
|
7
|
-
// Variable indirection prevents TypeScript from resolving the module
|
|
8
|
-
// at compile time — keeps @opentelemetry/api as a purely optional runtime dep.
|
|
9
|
-
const moduleName = "@opentelemetry/api";
|
|
10
|
-
// oxlint-disable-next-line typescript/no-unsafe-assignment -- dynamic import of optional runtime dependency
|
|
11
|
-
const otelApi = await import(
|
|
12
|
-
/* webpackIgnore: true */
|
|
13
|
-
moduleName
|
|
14
|
-
);
|
|
15
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing optional OTel API surface
|
|
16
|
-
const trace = otelApi["trace"];
|
|
17
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing optional OTel API surface
|
|
18
|
-
const SpanStatusCode = otelApi["SpanStatusCode"];
|
|
19
|
-
if (!trace || !SpanStatusCode) {
|
|
20
|
-
emitter = undefined;
|
|
21
|
-
return emitter;
|
|
22
|
-
}
|
|
23
|
-
emitter = {
|
|
24
|
-
recordDeadlineAbort(details) {
|
|
25
|
-
try {
|
|
26
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span from untyped getActiveSpan()
|
|
27
|
-
const span = trace.getActiveSpan();
|
|
28
|
-
if (!span) return;
|
|
29
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.addEvent method
|
|
30
|
-
const addEvent = span["addEvent"];
|
|
31
|
-
if (typeof addEvent !== "function") return;
|
|
32
|
-
addEvent.call(span, "lambda-deadline-middleware.abort", {
|
|
33
|
-
"deadline.duration_ms": details.deadlineMs,
|
|
34
|
-
"deadline.flush_buffer_ms": details.flushBufferMs,
|
|
35
|
-
"deadline.remaining_ms": details.remainingMs
|
|
36
|
-
});
|
|
37
|
-
} catch {}
|
|
38
|
-
},
|
|
39
|
-
setDeadlineErrorStatus(error) {
|
|
40
|
-
try {
|
|
41
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span from untyped getActiveSpan()
|
|
42
|
-
const span = trace.getActiveSpan();
|
|
43
|
-
if (!span) return;
|
|
44
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.setStatus method
|
|
45
|
-
const setStatus = span["setStatus"];
|
|
46
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.setAttribute method
|
|
47
|
-
const setAttribute = span["setAttribute"];
|
|
48
|
-
if (typeof setStatus === "function") {
|
|
49
|
-
setStatus.call(span, {
|
|
50
|
-
code: SpanStatusCode.ERROR,
|
|
51
|
-
message: error.message
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
if (typeof setAttribute === "function") {
|
|
55
|
-
setAttribute.call(span, "error.type", "DeadlineExceededError");
|
|
56
|
-
setAttribute.call(span, "deadline.duration_ms", error.deadlineMs);
|
|
57
|
-
setAttribute.call(span, "deadline.flush_buffer_ms", error.flushBufferMs);
|
|
58
|
-
setAttribute.call(span, "deadline.remaining_ms", error.remainingMs);
|
|
59
|
-
}
|
|
60
|
-
} catch {}
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
} catch {
|
|
64
|
-
emitter = undefined;
|
|
65
|
-
}
|
|
66
|
-
return emitter;
|
|
67
|
-
}
|
|
68
|
-
export async function emitDeadlineAbort(error, config) {
|
|
69
|
-
try {
|
|
70
|
-
if (!config.telemetryEnabled) return;
|
|
71
|
-
const em = await detectEmitter();
|
|
72
|
-
if (!em) return;
|
|
73
|
-
em.recordDeadlineAbort({
|
|
74
|
-
deadlineMs: error.deadlineMs,
|
|
75
|
-
flushBufferMs: error.flushBufferMs,
|
|
76
|
-
remainingMs: error.remainingMs
|
|
77
|
-
});
|
|
78
|
-
em.setDeadlineErrorStatus(error);
|
|
79
|
-
} catch {}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
//# sourceMappingURL=telemetry.js.map
|
package/dist/telemetry.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":"AAiBA,IAAI;AACJ,IAAI,WAAW;AAEf,eAAe,gBAAuD;CACpE,IAAI,UAAU,OAAO;CACrB,WAAW;CAEX,IAAI;;;EAGF,MAAM,aAAa;;EAEnB,MAAM,UAAmC,MAAM;;GAAiC;;;EAEhF,MAAM,QAAQ,QAAQ;;EAGtB,MAAM,iBAAiB,QAAQ;EAM/B,IAAI,CAAC,SAAS,CAAC,gBAAgB;GAC7B,UAAU;GACV,OAAO;EACT;EAEA,UAAU;GACR,oBAAoB,SAA6B;IAC/C,IAAI;;KAEF,MAAM,OAAO,MAAM,cAAc;KACjC,IAAI,CAAC,MAAM;;KAGX,MAAM,WAAW,KAAK;KAGtB,IAAI,OAAO,aAAa,YAAY;KAEpC,SAAS,KAAK,MAAM,oCAAoC;MACtD,wBAAwB,QAAQ;MAChC,4BAA4B,QAAQ;MACpC,yBAAyB,QAAQ;KACnC,CAAC;IACH,QAAQ,CAER;GACF;GAEA,uBAAuB,OAAoC;IACzD,IAAI;;KAEF,MAAM,OAAO,MAAM,cAAc;KACjC,IAAI,CAAC,MAAM;;KAGX,MAAM,YAAY,KAAK;;KAIvB,MAAM,eAAe,KAAK;KAI1B,IAAI,OAAO,cAAc,YAAY;MACnC,UAAU,KAAK,MAAM;OACnB,MAAM,eAAe;OACrB,SAAS,MAAM;MACjB,CAAC;KACH;KAEA,IAAI,OAAO,iBAAiB,YAAY;MACtC,aAAa,KAAK,MAAM,cAAc,uBAAuB;MAC7D,aAAa,KAAK,MAAM,wBAAwB,MAAM,UAAU;MAChE,aAAa,KAAK,MAAM,4BAA4B,MAAM,aAAa;MACvE,aAAa,KAAK,MAAM,yBAAyB,MAAM,WAAW;KACpE;IACF,QAAQ,CAER;GACF;EACF;CACF,QAAQ;EACN,UAAU;CACZ;CAEA,OAAO;AACT;AAEA,OAAO,eAAe,kBACpB,OACA,QACe;CACf,IAAI;EACF,IAAI,CAAC,OAAO,kBAAkB;EAE9B,MAAM,KAAK,MAAM,cAAc;EAC/B,IAAI,CAAC,IAAI;EAET,GAAG,oBAAoB;GACrB,YAAY,MAAM;GAClB,eAAe,MAAM;GACrB,aAAa,MAAM;EACrB,CAAC;EAED,GAAG,uBAAuB,KAAK;CACjC,QAAQ,CAER;AACF","names":[],"sources":["src/telemetry.ts"],"version":3,"sourcesContent":["// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors\n// SPDX-License-Identifier: MIT\n\nimport type { DeadlineExceededError } from \"./error.js\";\nimport type { DeadlineMiddlewareConfig } from \"./types.js\";\n\ninterface AbortDetails {\n readonly deadlineMs: number;\n readonly flushBufferMs: number;\n readonly remainingMs: number;\n}\n\ninterface TelemetryEmitter {\n recordDeadlineAbort: (details: AbortDetails) => void;\n setDeadlineErrorStatus: (error: DeadlineExceededError) => void;\n}\n\nlet emitter: TelemetryEmitter | undefined;\nlet detected = false;\n\nasync function detectEmitter(): Promise<TelemetryEmitter | undefined> {\n if (detected) return emitter;\n detected = true;\n\n try {\n // Variable indirection prevents TypeScript from resolving the module\n // at compile time — keeps @opentelemetry/api as a purely optional runtime dep.\n const moduleName = \"@opentelemetry/api\";\n // oxlint-disable-next-line typescript/no-unsafe-assignment -- dynamic import of optional runtime dependency\n const otelApi: Record<string, unknown> = await import(/* webpackIgnore: true */ moduleName);\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing optional OTel API surface\n const trace = otelApi[\"trace\"] as { getActiveSpan: () => unknown } | undefined;\n\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing optional OTel API surface\n const SpanStatusCode = otelApi[\"SpanStatusCode\"] as\n | {\n ERROR: number;\n }\n | undefined;\n\n if (!trace || !SpanStatusCode) {\n emitter = undefined;\n return emitter;\n }\n\n emitter = {\n recordDeadlineAbort(details: AbortDetails): void {\n try {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span from untyped getActiveSpan()\n const span = trace.getActiveSpan() as Record<string, unknown> | undefined;\n if (!span) return;\n\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.addEvent method\n const addEvent = span[\"addEvent\"] as\n | ((name: string, attributes: Record<string, unknown>) => void)\n | undefined;\n if (typeof addEvent !== \"function\") return;\n\n addEvent.call(span, \"lambda-deadline-middleware.abort\", {\n \"deadline.duration_ms\": details.deadlineMs,\n \"deadline.flush_buffer_ms\": details.flushBufferMs,\n \"deadline.remaining_ms\": details.remainingMs,\n });\n } catch {\n // Telemetry must never disrupt request processing\n }\n },\n\n setDeadlineErrorStatus(error: DeadlineExceededError): void {\n try {\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span from untyped getActiveSpan()\n const span = trace.getActiveSpan() as Record<string, unknown> | undefined;\n if (!span) return;\n\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.setStatus method\n const setStatus = span[\"setStatus\"] as\n | ((status: { code: number; message: string }) => void)\n | undefined;\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.setAttribute method\n const setAttribute = span[\"setAttribute\"] as\n | ((key: string, value: unknown) => void)\n | undefined;\n\n if (typeof setStatus === \"function\") {\n setStatus.call(span, {\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n }\n\n if (typeof setAttribute === \"function\") {\n setAttribute.call(span, \"error.type\", \"DeadlineExceededError\");\n setAttribute.call(span, \"deadline.duration_ms\", error.deadlineMs);\n setAttribute.call(span, \"deadline.flush_buffer_ms\", error.flushBufferMs);\n setAttribute.call(span, \"deadline.remaining_ms\", error.remainingMs);\n }\n } catch {\n // Telemetry must never disrupt request processing\n }\n },\n };\n } catch {\n emitter = undefined;\n }\n\n return emitter;\n}\n\nexport async function emitDeadlineAbort(\n error: DeadlineExceededError,\n config: DeadlineMiddlewareConfig,\n): Promise<void> {\n try {\n if (!config.telemetryEnabled) return;\n\n const em = await detectEmitter();\n if (!em) return;\n\n em.recordDeadlineAbort({\n deadlineMs: error.deadlineMs,\n flushBufferMs: error.flushBufferMs,\n remainingMs: error.remainingMs,\n });\n\n em.setDeadlineErrorStatus(error);\n } catch {\n // Telemetry must never disrupt request processing\n }\n}\n"]}
|
package/src/config.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors
|
|
2
|
-
// SPDX-License-Identifier: MIT
|
|
3
|
-
|
|
4
|
-
import { flushBufferMs } from "./types.js";
|
|
5
|
-
import type { DeadlineMiddlewareConfig, DeadlineOptions } from "./types.js";
|
|
6
|
-
|
|
7
|
-
export function parseConfig(raw: DeadlineOptions | undefined): DeadlineMiddlewareConfig {
|
|
8
|
-
const buffer = raw?.flushBufferMs ?? 1000;
|
|
9
|
-
if (buffer < 0) {
|
|
10
|
-
throw new TypeError(`flushBufferMs option must be non-negative, received: ${buffer}`);
|
|
11
|
-
}
|
|
12
|
-
return {
|
|
13
|
-
flushBufferMs: flushBufferMs(buffer),
|
|
14
|
-
telemetryEnabled: raw?.telemetryEnabled ?? true,
|
|
15
|
-
};
|
|
16
|
-
}
|
package/src/registration.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors
|
|
2
|
-
// SPDX-License-Identifier: MIT
|
|
3
|
-
|
|
4
|
-
import type { Pluggable } from "@smithy/types";
|
|
5
|
-
|
|
6
|
-
import { parseConfig } from "./config.js";
|
|
7
|
-
import { deadlineMiddlewareHandler } from "./middleware.js";
|
|
8
|
-
import type { DeadlineOptions } from "./types.js";
|
|
9
|
-
|
|
10
|
-
export function deadlineMiddleware(options?: DeadlineOptions): Pluggable<object, object> {
|
|
11
|
-
const config = parseConfig(options);
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
applyToStack(stack) {
|
|
15
|
-
stack.add(deadlineMiddlewareHandler(config), {
|
|
16
|
-
step: "finalizeRequest",
|
|
17
|
-
name: "deadlineMiddleware",
|
|
18
|
-
override: true,
|
|
19
|
-
});
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// WeakSet allows GC of discarded clients in long-running processes
|
|
25
|
-
// while still preventing duplicate middleware registration.
|
|
26
|
-
const registeredClients = new WeakSet<object>();
|
|
27
|
-
|
|
28
|
-
export function withDeadline<
|
|
29
|
-
// oxlint-disable-next-line typescript/no-explicit-any -- AWS SDK clients use varying ServiceInput/OutputTypes generics, making `any` the only correct constraint here
|
|
30
|
-
T extends { middlewareStack: { use: (pluggable: Pluggable<any, any>) => void } },
|
|
31
|
-
>(client: T, options?: DeadlineOptions): T {
|
|
32
|
-
if (registeredClients.has(client)) return client;
|
|
33
|
-
registeredClients.add(client);
|
|
34
|
-
client.middlewareStack.use(deadlineMiddleware(options));
|
|
35
|
-
return client;
|
|
36
|
-
}
|
package/src/telemetry.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText: 2024 lambda-deadline-middleware contributors
|
|
2
|
-
// SPDX-License-Identifier: MIT
|
|
3
|
-
|
|
4
|
-
import type { DeadlineExceededError } from "./error.js";
|
|
5
|
-
import type { DeadlineMiddlewareConfig } from "./types.js";
|
|
6
|
-
|
|
7
|
-
interface AbortDetails {
|
|
8
|
-
readonly deadlineMs: number;
|
|
9
|
-
readonly flushBufferMs: number;
|
|
10
|
-
readonly remainingMs: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface TelemetryEmitter {
|
|
14
|
-
recordDeadlineAbort: (details: AbortDetails) => void;
|
|
15
|
-
setDeadlineErrorStatus: (error: DeadlineExceededError) => void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let emitter: TelemetryEmitter | undefined;
|
|
19
|
-
let detected = false;
|
|
20
|
-
|
|
21
|
-
async function detectEmitter(): Promise<TelemetryEmitter | undefined> {
|
|
22
|
-
if (detected) return emitter;
|
|
23
|
-
detected = true;
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
// Variable indirection prevents TypeScript from resolving the module
|
|
27
|
-
// at compile time — keeps @opentelemetry/api as a purely optional runtime dep.
|
|
28
|
-
const moduleName = "@opentelemetry/api";
|
|
29
|
-
// oxlint-disable-next-line typescript/no-unsafe-assignment -- dynamic import of optional runtime dependency
|
|
30
|
-
const otelApi: Record<string, unknown> = await import(/* webpackIgnore: true */ moduleName);
|
|
31
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing optional OTel API surface
|
|
32
|
-
const trace = otelApi["trace"] as { getActiveSpan: () => unknown } | undefined;
|
|
33
|
-
|
|
34
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing optional OTel API surface
|
|
35
|
-
const SpanStatusCode = otelApi["SpanStatusCode"] as
|
|
36
|
-
| {
|
|
37
|
-
ERROR: number;
|
|
38
|
-
}
|
|
39
|
-
| undefined;
|
|
40
|
-
|
|
41
|
-
if (!trace || !SpanStatusCode) {
|
|
42
|
-
emitter = undefined;
|
|
43
|
-
return emitter;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
emitter = {
|
|
47
|
-
recordDeadlineAbort(details: AbortDetails): void {
|
|
48
|
-
try {
|
|
49
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span from untyped getActiveSpan()
|
|
50
|
-
const span = trace.getActiveSpan() as Record<string, unknown> | undefined;
|
|
51
|
-
if (!span) return;
|
|
52
|
-
|
|
53
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.addEvent method
|
|
54
|
-
const addEvent = span["addEvent"] as
|
|
55
|
-
| ((name: string, attributes: Record<string, unknown>) => void)
|
|
56
|
-
| undefined;
|
|
57
|
-
if (typeof addEvent !== "function") return;
|
|
58
|
-
|
|
59
|
-
addEvent.call(span, "lambda-deadline-middleware.abort", {
|
|
60
|
-
"deadline.duration_ms": details.deadlineMs,
|
|
61
|
-
"deadline.flush_buffer_ms": details.flushBufferMs,
|
|
62
|
-
"deadline.remaining_ms": details.remainingMs,
|
|
63
|
-
});
|
|
64
|
-
} catch {
|
|
65
|
-
// Telemetry must never disrupt request processing
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
setDeadlineErrorStatus(error: DeadlineExceededError): void {
|
|
70
|
-
try {
|
|
71
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span from untyped getActiveSpan()
|
|
72
|
-
const span = trace.getActiveSpan() as Record<string, unknown> | undefined;
|
|
73
|
-
if (!span) return;
|
|
74
|
-
|
|
75
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.setStatus method
|
|
76
|
-
const setStatus = span["setStatus"] as
|
|
77
|
-
| ((status: { code: number; message: string }) => void)
|
|
78
|
-
| undefined;
|
|
79
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- duck-typing OTel span.setAttribute method
|
|
80
|
-
const setAttribute = span["setAttribute"] as
|
|
81
|
-
| ((key: string, value: unknown) => void)
|
|
82
|
-
| undefined;
|
|
83
|
-
|
|
84
|
-
if (typeof setStatus === "function") {
|
|
85
|
-
setStatus.call(span, {
|
|
86
|
-
code: SpanStatusCode.ERROR,
|
|
87
|
-
message: error.message,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (typeof setAttribute === "function") {
|
|
92
|
-
setAttribute.call(span, "error.type", "DeadlineExceededError");
|
|
93
|
-
setAttribute.call(span, "deadline.duration_ms", error.deadlineMs);
|
|
94
|
-
setAttribute.call(span, "deadline.flush_buffer_ms", error.flushBufferMs);
|
|
95
|
-
setAttribute.call(span, "deadline.remaining_ms", error.remainingMs);
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
// Telemetry must never disrupt request processing
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
} catch {
|
|
103
|
-
emitter = undefined;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return emitter;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export async function emitDeadlineAbort(
|
|
110
|
-
error: DeadlineExceededError,
|
|
111
|
-
config: DeadlineMiddlewareConfig,
|
|
112
|
-
): Promise<void> {
|
|
113
|
-
try {
|
|
114
|
-
if (!config.telemetryEnabled) return;
|
|
115
|
-
|
|
116
|
-
const em = await detectEmitter();
|
|
117
|
-
if (!em) return;
|
|
118
|
-
|
|
119
|
-
em.recordDeadlineAbort({
|
|
120
|
-
deadlineMs: error.deadlineMs,
|
|
121
|
-
flushBufferMs: error.flushBufferMs,
|
|
122
|
-
remainingMs: error.remainingMs,
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
em.setDeadlineErrorStatus(error);
|
|
126
|
-
} catch {
|
|
127
|
-
// Telemetry must never disrupt request processing
|
|
128
|
-
}
|
|
129
|
-
}
|