@jaypie/express 1.2.24 → 1.2.26

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.
@@ -1,13 +1,458 @@
1
- export { createLambdaHandler, createLambdaStreamHandler, getCurrentInvoke, LambdaRequest, LambdaResponseBuffered, LambdaResponseStreaming, } from "./adapter/index.js";
2
- export type { ApiGatewayV1Event, CreateLambdaHandlerOptions, FunctionUrlEvent, LambdaContext, LambdaEvent, LambdaHandler, LambdaResponse, LambdaStreamHandler, ResponseStream, } from "./adapter/index.js";
3
- export { EXPRESS } from "./constants.js";
4
- export { default as cors } from "./cors.helper.js";
5
- export type { CorsConfig } from "./cors.helper.js";
6
- export { default as fabricApiResponse } from "./fabricApiResponse.js";
7
- export { default as expressHandler } from "./expressHandler.js";
8
- export type { ExpressHandlerLocals, ExpressHandlerOptions, JaypieHandlerSetup, JaypieHandlerTeardown, JaypieHandlerValidate, } from "./expressHandler.js";
9
- export { default as expressHttpCodeHandler } from "./http.handler.js";
10
- export { default as expressStreamHandler } from "./expressStreamHandler.js";
11
- export type { ExpressStreamHandler, ExpressStreamHandlerLocals, ExpressStreamHandlerOptions, JaypieStreamHandlerSetup, JaypieStreamHandlerTeardown, JaypieStreamHandlerValidate, } from "./expressStreamHandler.js";
12
- export { default as getCurrentInvokeUuid } from "./getCurrentInvokeUuid.adapter.js";
13
- export * from "./routes.js";
1
+ import * as express from 'express';
2
+ import { Application, Request, Response, NextFunction } from 'express';
3
+ import { StreamFormat } from '@jaypie/aws';
4
+ import { IncomingHttpHeaders, OutgoingHttpHeaders } from 'node:http';
5
+ import { Readable, Writable } from 'node:stream';
6
+
7
+ interface LambdaContext {
8
+ awsRequestId: string;
9
+ callbackWaitsForEmptyEventLoop?: boolean;
10
+ functionName?: string;
11
+ functionVersion?: string;
12
+ invokedFunctionArn?: string;
13
+ logGroupName?: string;
14
+ logStreamName?: string;
15
+ memoryLimitInMB?: string;
16
+ [key: string]: unknown;
17
+ }
18
+ interface ApiGatewayV1Event {
19
+ body?: string | null;
20
+ headers: Record<string, string>;
21
+ httpMethod: string;
22
+ isBase64Encoded: boolean;
23
+ multiValueHeaders?: Record<string, string[]>;
24
+ multiValueQueryStringParameters?: Record<string, string[]> | null;
25
+ path: string;
26
+ pathParameters?: Record<string, string> | null;
27
+ queryStringParameters?: Record<string, string> | null;
28
+ requestContext: {
29
+ accountId: string;
30
+ apiId: string;
31
+ domainName?: string;
32
+ httpMethod: string;
33
+ identity: {
34
+ sourceIp: string;
35
+ userAgent?: string;
36
+ };
37
+ path: string;
38
+ protocol: string;
39
+ requestId: string;
40
+ requestTime?: string;
41
+ requestTimeEpoch?: number;
42
+ resourceId?: string;
43
+ resourcePath?: string;
44
+ stage: string;
45
+ };
46
+ resource?: string;
47
+ stageVariables?: Record<string, string> | null;
48
+ }
49
+ interface FunctionUrlEvent {
50
+ body?: string;
51
+ cookies?: string[];
52
+ headers: Record<string, string>;
53
+ isBase64Encoded: boolean;
54
+ rawPath: string;
55
+ rawQueryString: string;
56
+ requestContext: {
57
+ accountId: string;
58
+ apiId: string;
59
+ domainName: string;
60
+ domainPrefix: string;
61
+ http: {
62
+ method: string;
63
+ path: string;
64
+ protocol: string;
65
+ sourceIp: string;
66
+ userAgent: string;
67
+ };
68
+ requestId: string;
69
+ routeKey: string;
70
+ stage: string;
71
+ time: string;
72
+ timeEpoch: number;
73
+ };
74
+ routeKey: string;
75
+ version: "2.0";
76
+ }
77
+ type LambdaEvent = ApiGatewayV1Event | FunctionUrlEvent;
78
+ interface LambdaResponse {
79
+ body: string;
80
+ cookies?: string[];
81
+ headers: Record<string, string>;
82
+ isBase64Encoded: boolean;
83
+ statusCode: number;
84
+ }
85
+ interface ResponseStream {
86
+ end(): void;
87
+ write(chunk: string | Uint8Array): void;
88
+ }
89
+ type LambdaHandler = (event: LambdaEvent, context: LambdaContext) => Promise<LambdaResponse>;
90
+ type LambdaStreamHandler = (event: LambdaEvent, context: LambdaContext) => Promise<void>;
91
+ type CreateLambdaHandlerOptions = {
92
+ /**
93
+ * Optional name for logging and debugging
94
+ */
95
+ name?: string;
96
+ };
97
+
98
+ interface LambdaRequestOptions {
99
+ body?: Buffer | null;
100
+ headers: Record<string, string>;
101
+ lambdaContext: LambdaContext;
102
+ lambdaEvent: LambdaEvent;
103
+ method: string;
104
+ protocol: string;
105
+ query?: Record<string, string | string[]>;
106
+ remoteAddress: string;
107
+ url: string;
108
+ }
109
+ interface MockSocket {
110
+ destroy: () => void;
111
+ encrypted: boolean;
112
+ remoteAddress: string;
113
+ }
114
+ /**
115
+ * Mock IncomingMessage that extends Readable stream.
116
+ * Provides Express-compatible request interface from Lambda Function URL events.
117
+ */
118
+ declare class LambdaRequest extends Readable {
119
+ readonly method: string;
120
+ readonly url: string;
121
+ readonly headers: IncomingHttpHeaders;
122
+ readonly httpVersion: string;
123
+ readonly httpVersionMajor: number;
124
+ readonly httpVersionMinor: number;
125
+ complete: boolean;
126
+ readonly socket: MockSocket;
127
+ readonly connection: MockSocket;
128
+ readonly originalUrl: string;
129
+ readonly path: string;
130
+ baseUrl: string;
131
+ body: unknown;
132
+ params: Record<string, string>;
133
+ query: Record<string, unknown>;
134
+ readonly rawBody: string | undefined;
135
+ readonly _lambdaContext: LambdaContext;
136
+ readonly _lambdaEvent: LambdaEvent;
137
+ private bodyBuffer;
138
+ private bodyPushed;
139
+ constructor(options: LambdaRequestOptions);
140
+ _read(): void;
141
+ get(headerName: string): string | undefined;
142
+ header(headerName: string): string | undefined;
143
+ private normalizeHeaders;
144
+ }
145
+
146
+ /**
147
+ * Mock ServerResponse that buffers the response.
148
+ * Collects status, headers, and body chunks, then returns a Lambda response.
149
+ */
150
+ declare class LambdaResponseBuffered extends Writable {
151
+ statusCode: number;
152
+ statusMessage: string;
153
+ readonly socket: {
154
+ remoteAddress: string;
155
+ };
156
+ _chunks: Buffer[];
157
+ _ended: boolean;
158
+ _headers: Map<string, string | string[]>;
159
+ _headersSent: boolean;
160
+ _resolve: ((result: LambdaResponse) => void) | null;
161
+ constructor();
162
+ _internalGetHeader(name: string): string | undefined;
163
+ _internalSetHeader(name: string, value: string): void;
164
+ _internalHasHeader(name: string): boolean;
165
+ _internalRemoveHeader(name: string): void;
166
+ getResult(): Promise<LambdaResponse>;
167
+ setHeader(name: string, value: number | string | string[]): this;
168
+ getHeader(name: string): number | string | string[] | undefined;
169
+ removeHeader(name: string): void;
170
+ getHeaders(): OutgoingHttpHeaders;
171
+ hasHeader(name: string): boolean;
172
+ getHeaderNames(): string[];
173
+ /**
174
+ * Proxy for direct header access (e.g., res.headers['content-type']).
175
+ * Required for compatibility with middleware like helmet that access headers directly.
176
+ * Uses direct _headers access to bypass dd-trace interception.
177
+ */
178
+ get headers(): Record<string, string | string[] | undefined>;
179
+ writeHead(statusCode: number, statusMessageOrHeaders?: OutgoingHttpHeaders | string, headers?: OutgoingHttpHeaders): this;
180
+ get headersSent(): boolean;
181
+ /**
182
+ * Express-style alias for getHeader().
183
+ * Used by middleware like decorateResponse that use res.get().
184
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
185
+ */
186
+ get(name: string): number | string | string[] | undefined;
187
+ /**
188
+ * Express-style alias for setHeader().
189
+ * Used by middleware like decorateResponse that use res.set().
190
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
191
+ */
192
+ set(name: string, value: number | string | string[]): this;
193
+ status(code: number): this;
194
+ json(data: unknown): this;
195
+ send(body?: Buffer | object | string): this;
196
+ /**
197
+ * Add a field to the Vary response header.
198
+ * Used by CORS middleware to indicate response varies by Origin.
199
+ * Uses direct _headers access to bypass dd-trace interception.
200
+ */
201
+ vary(field: string): this;
202
+ _write(chunk: Buffer | string, encoding: BufferEncoding, // eslint-disable-line no-undef
203
+ callback: (error?: Error | null) => void): void;
204
+ _final(callback: (error?: Error | null) => void): void;
205
+ buildResult(): LambdaResponse;
206
+ private isBinaryContentType;
207
+ }
208
+
209
+ /**
210
+ * Mock ServerResponse that streams directly to Lambda responseStream.
211
+ * Uses awslambda.HttpResponseStream.from() to set status and headers.
212
+ */
213
+ declare class LambdaResponseStreaming extends Writable {
214
+ statusCode: number;
215
+ statusMessage: string;
216
+ readonly socket: {
217
+ remoteAddress: string;
218
+ };
219
+ _headers: Map<string, string | string[]>;
220
+ _headersSent: boolean;
221
+ private _convertedFrom204;
222
+ private _pendingWrites;
223
+ private _responseStream;
224
+ private _wrappedStream;
225
+ constructor(responseStream: ResponseStream);
226
+ _internalGetHeader(name: string): string | undefined;
227
+ _internalSetHeader(name: string, value: string): void;
228
+ _internalHasHeader(name: string): boolean;
229
+ _internalRemoveHeader(name: string): void;
230
+ setHeader(name: string, value: number | string | string[]): this;
231
+ getHeader(name: string): number | string | string[] | undefined;
232
+ removeHeader(name: string): void;
233
+ getHeaders(): OutgoingHttpHeaders;
234
+ hasHeader(name: string): boolean;
235
+ getHeaderNames(): string[];
236
+ /**
237
+ * Proxy for direct header access (e.g., res.headers['content-type']).
238
+ * Required for compatibility with middleware like helmet that access headers directly.
239
+ * Uses direct _headers access to bypass dd-trace interception.
240
+ */
241
+ get headers(): Record<string, string | string[] | undefined>;
242
+ writeHead(statusCode: number, statusMessageOrHeaders?: OutgoingHttpHeaders | string, headers?: OutgoingHttpHeaders): this;
243
+ get headersSent(): boolean;
244
+ flushHeaders(): void;
245
+ /**
246
+ * Express-style alias for getHeader().
247
+ * Used by middleware like decorateResponse that use res.get().
248
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
249
+ */
250
+ get(name: string): number | string | string[] | undefined;
251
+ /**
252
+ * Express-style alias for setHeader().
253
+ * Used by middleware like decorateResponse that use res.set().
254
+ * Note: Directly accesses _headers to avoid prototype chain issues with bundled code.
255
+ */
256
+ set(name: string, value: number | string | string[]): this;
257
+ status(code: number): this;
258
+ json(data: unknown): this;
259
+ send(body?: Buffer | object | string): this;
260
+ /**
261
+ * Add a field to the Vary response header.
262
+ * Used by CORS middleware to indicate response varies by Origin.
263
+ * Uses direct _headers access to bypass dd-trace interception.
264
+ */
265
+ vary(field: string): this;
266
+ _write(chunk: Buffer | string, encoding: BufferEncoding, // eslint-disable-line no-undef
267
+ callback: (error?: Error | null) => void): void;
268
+ _final(callback: (error?: Error | null) => void): void;
269
+ }
270
+
271
+ /**
272
+ * Get the current Lambda invoke context.
273
+ * Used by getCurrentInvokeUuid adapter to get the request ID.
274
+ */
275
+ declare function getCurrentInvoke(): {
276
+ context: LambdaContext;
277
+ event: LambdaEvent;
278
+ } | null;
279
+ /**
280
+ * Create a Lambda handler that buffers the Express response.
281
+ * Returns the complete response as a Lambda response object.
282
+ *
283
+ * @example
284
+ * ```typescript
285
+ * import express from "express";
286
+ * import { createLambdaHandler } from "@jaypie/express";
287
+ *
288
+ * const app = express();
289
+ * app.get("/", (req, res) => res.json({ message: "Hello" }));
290
+ *
291
+ * export const handler = createLambdaHandler(app);
292
+ * ```
293
+ */
294
+ declare function createLambdaHandler(app: Application, _options?: CreateLambdaHandlerOptions): LambdaHandler;
295
+ /**
296
+ * Create a Lambda handler that streams the Express response.
297
+ * Uses awslambda.streamifyResponse() for Lambda response streaming.
298
+ *
299
+ * @example
300
+ * ```typescript
301
+ * import express from "express";
302
+ * import { createLambdaStreamHandler } from "@jaypie/express";
303
+ *
304
+ * const app = express();
305
+ * app.get("/stream", (req, res) => {
306
+ * res.setHeader("Content-Type", "text/event-stream");
307
+ * res.write("data: Hello\n\n");
308
+ * res.end();
309
+ * });
310
+ *
311
+ * export const handler = createLambdaStreamHandler(app);
312
+ * ```
313
+ */
314
+ declare function createLambdaStreamHandler(app: Application, _options?: CreateLambdaHandlerOptions): LambdaStreamHandler;
315
+
316
+ declare const EXPRESS: {
317
+ readonly PATH: {
318
+ readonly ANY: "*";
319
+ readonly ID: "/:id";
320
+ readonly ROOT: RegExp;
321
+ };
322
+ };
323
+
324
+ interface CorsConfig {
325
+ origin?: string | string[];
326
+ overrides?: Record<string, unknown>;
327
+ }
328
+ /**
329
+ * CORS middleware with Lambda streaming support.
330
+ *
331
+ * For OPTIONS preflight requests, this middleware handles them early and
332
+ * terminates the response immediately. This is critical for Lambda streaming
333
+ * handlers where the response stream would otherwise stay open waiting for
334
+ * streaming data that never comes.
335
+ *
336
+ * For regular requests, delegates to the standard cors package behavior.
337
+ */
338
+ declare const _default: (config?: CorsConfig) => ((req: Request, res: Response, next: NextFunction) => void);
339
+
340
+ /**
341
+ * Wrap a service return value in the canonical Jaypie API envelope.
342
+ *
343
+ * Rules:
344
+ * - `null` / `undefined` → `{ data: null }`
345
+ * - `{ data }` with exactly one key → passthrough
346
+ * - `{ errors }` with exactly one key → passthrough
347
+ * - everything else (including `{ data, other }`) → `{ data: value }`
348
+ *
349
+ * The "only one key, and that key is `data` or `errors`" check prevents
350
+ * false positives where a domain object happens to contain a `data` field.
351
+ */
352
+ declare function fabricApiResponse(result: unknown): unknown;
353
+
354
+ type JaypieHandlerSetup = (req: Request, res: Response) => Promise<void> | void;
355
+ type JaypieHandlerTeardown = (req: Request, res: Response) => Promise<void> | void;
356
+ type JaypieHandlerValidate = (req: Request, res: Response) => Promise<boolean | void> | boolean | void;
357
+ type ExpressHandlerLocals = (req: Request, res: Response) => Promise<unknown> | unknown;
358
+ interface ExpressHandlerOptions {
359
+ chaos?: string;
360
+ /**
361
+ * When true, the handler's return value is wrapped with
362
+ * `fabricApiResponse` before `res.json()`. Plain objects become
363
+ * `{ data: value }`; pre-wrapped `{ data }` or `{ errors }` payloads
364
+ * pass through unchanged. `null` / `undefined` become `{ data: null }`.
365
+ */
366
+ fabric?: boolean;
367
+ locals?: Record<string, unknown | ExpressHandlerLocals>;
368
+ name?: string;
369
+ secrets?: string[];
370
+ setup?: JaypieHandlerSetup[] | JaypieHandlerSetup;
371
+ teardown?: JaypieHandlerTeardown[] | JaypieHandlerTeardown;
372
+ unavailable?: boolean;
373
+ validate?: JaypieHandlerValidate[] | JaypieHandlerValidate;
374
+ }
375
+ type ExpressHandler<T> = (req: Request, res: Response, ...params: unknown[]) => Promise<T>;
376
+ declare function expressHandler<T>(handler: ExpressHandler<T>, options?: ExpressHandlerOptions): ExpressHandler<T>;
377
+ declare function expressHandler<T>(options: ExpressHandlerOptions, handler: ExpressHandler<T>): ExpressHandler<T>;
378
+
379
+ declare const httpHandler: (statusCode?: number, context?: ExpressHandlerOptions) => ((req: Request, res: Response) => Promise<Record<string, unknown> | null>);
380
+
381
+ type JaypieStreamHandlerSetup = (req: Request, res: Response) => Promise<void> | void;
382
+ type JaypieStreamHandlerTeardown = (req: Request, res: Response) => Promise<void> | void;
383
+ type JaypieStreamHandlerValidate = (req: Request, res: Response) => Promise<boolean | void> | boolean | void;
384
+ type ExpressStreamHandlerLocals = (req: Request, res: Response) => Promise<unknown> | unknown;
385
+ interface ExpressStreamHandlerOptions {
386
+ chaos?: string;
387
+ contentType?: string;
388
+ format?: StreamFormat;
389
+ locals?: Record<string, unknown | ExpressStreamHandlerLocals>;
390
+ name?: string;
391
+ secrets?: string[];
392
+ setup?: JaypieStreamHandlerSetup[] | JaypieStreamHandlerSetup;
393
+ teardown?: JaypieStreamHandlerTeardown[] | JaypieStreamHandlerTeardown;
394
+ unavailable?: boolean;
395
+ validate?: JaypieStreamHandlerValidate[] | JaypieStreamHandlerValidate;
396
+ }
397
+ /**
398
+ * Streaming Express handler function signature.
399
+ * Handler should write to the response stream and not return a value.
400
+ */
401
+ type ExpressStreamHandler = (req: Request, res: Response, ...params: unknown[]) => Promise<void>;
402
+ /**
403
+ * Creates a streaming Express handler that sets up SSE headers and allows
404
+ * streaming responses. The handler receives the standard req/res and should
405
+ * write to res.write() directly.
406
+ *
407
+ * Usage:
408
+ * ```ts
409
+ * app.get('/stream', expressStreamHandler(async (req, res) => {
410
+ * const llmStream = llm.stream("Hello");
411
+ * await createExpressStream(llmStream, res);
412
+ * }));
413
+ * ```
414
+ *
415
+ * The handler sets appropriate SSE headers automatically:
416
+ * - Content-Type: text/event-stream
417
+ * - Cache-Control: no-cache
418
+ * - Connection: keep-alive
419
+ * - X-Accel-Buffering: no (disables nginx buffering)
420
+ */
421
+ declare function expressStreamHandler(handler: ExpressStreamHandler, options?: ExpressStreamHandlerOptions): ExpressStreamHandler;
422
+ declare function expressStreamHandler(options: ExpressStreamHandlerOptions, handler: ExpressStreamHandler): ExpressStreamHandler;
423
+
424
+ /**
425
+ * Get the current invoke UUID from Lambda context.
426
+ * Works with Jaypie Lambda adapter (createLambdaHandler/createLambdaStreamHandler).
427
+ *
428
+ * @param req - Optional Express request object. Used to extract context
429
+ * from Jaypie adapter's _lambdaContext.
430
+ * @returns The AWS request ID or undefined if not in Lambda context
431
+ */
432
+ declare function getCurrentInvokeUuid(req?: Request): string | undefined;
433
+
434
+ interface RequestSummary {
435
+ baseUrl: string;
436
+ body: unknown;
437
+ headers: Record<string, string | string[] | undefined>;
438
+ method: string;
439
+ query: Request["query"];
440
+ url: string;
441
+ }
442
+ declare function summarizeRequest(req: Request): RequestSummary;
443
+
444
+ interface EchoResponse {
445
+ req: ReturnType<typeof summarizeRequest>;
446
+ }
447
+
448
+ declare const badRequestRoute: (req: express.Request, res: express.Response) => Promise<Record<string, unknown> | null>;
449
+ declare const echoRoute: (req: express.Request, res: express.Response) => Promise<EchoResponse>;
450
+ declare const forbiddenRoute: (req: express.Request, res: express.Response) => Promise<Record<string, unknown> | null>;
451
+ declare const goneRoute: (req: express.Request, res: express.Response) => Promise<Record<string, unknown> | null>;
452
+ declare const methodNotAllowedRoute: (req: express.Request, res: express.Response) => Promise<Record<string, unknown> | null>;
453
+ declare const noContentRoute: (req: express.Request, res: express.Response) => Promise<Record<string, unknown> | null>;
454
+ declare const notFoundRoute: (req: express.Request, res: express.Response) => Promise<Record<string, unknown> | null>;
455
+ declare const notImplementedRoute: (req: express.Request, res: express.Response, ...params: unknown[]) => Promise<unknown>;
456
+
457
+ export { EXPRESS, LambdaRequest, LambdaResponseBuffered, LambdaResponseStreaming, badRequestRoute, _default as cors, createLambdaHandler, createLambdaStreamHandler, echoRoute, expressHandler, httpHandler as expressHttpCodeHandler, expressStreamHandler, fabricApiResponse, forbiddenRoute, getCurrentInvoke, getCurrentInvokeUuid, goneRoute, methodNotAllowedRoute, noContentRoute, notFoundRoute, notImplementedRoute };
458
+ export type { ApiGatewayV1Event, CorsConfig, CreateLambdaHandlerOptions, ExpressHandlerLocals, ExpressHandlerOptions, ExpressStreamHandler, ExpressStreamHandlerLocals, ExpressStreamHandlerOptions, FunctionUrlEvent, JaypieHandlerSetup, JaypieHandlerTeardown, JaypieHandlerValidate, JaypieStreamHandlerSetup, JaypieStreamHandlerTeardown, JaypieStreamHandlerValidate, LambdaContext, LambdaEvent, LambdaHandler, LambdaResponse, LambdaStreamHandler, ResponseStream };
package/dist/esm/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { flushLlmObs, hasDatadogEnv, submitMetric, DATADOG, loadDatadogApiKey } from '@jaypie/datadog';
1
2
  import { Readable, Writable } from 'node:stream';
2
3
  import { ServerResponse } from 'node:http';
3
4
  import { CorsError, BadRequestError, UnhandledError, GatewayTimeoutError, UnavailableError, BadGatewayError, InternalError, TeapotError, GoneError, MethodNotAllowedError, NotFoundError, ForbiddenError, UnauthorizedError, NotImplementedError } from '@jaypie/errors';
@@ -5,7 +6,6 @@ import { force, envBoolean, JAYPIE, HTTP, getHeaderFrom, jaypieHandler } from '@
5
6
  import expressCors from 'cors';
6
7
  import { loadEnvSecrets, getContentTypeForFormat, formatStreamError } from '@jaypie/aws';
7
8
  import { log, redactAuth } from '@jaypie/logger';
8
- import { hasDatadogEnv, submitMetric, DATADOG, loadDatadogApiKey } from '@jaypie/datadog';
9
9
 
10
10
  //
11
11
  //
@@ -1099,6 +1099,9 @@ function createLambdaHandler(app, _options) {
1099
1099
  finally {
1100
1100
  // Clear current invoke context
1101
1101
  clearCurrentInvoke();
1102
+ // Flush buffered LLM Observability spans before the Lambda freezes.
1103
+ // No-op unless DD_LLMOBS_ENABLED; never throws.
1104
+ flushLlmObs();
1102
1105
  }
1103
1106
  };
1104
1107
  }
@@ -1142,6 +1145,9 @@ function createLambdaStreamHandler(app, _options) {
1142
1145
  finally {
1143
1146
  // Clear current invoke context
1144
1147
  clearCurrentInvoke();
1148
+ // Flush buffered LLM Observability spans before the Lambda freezes.
1149
+ // No-op unless DD_LLMOBS_ENABLED; never throws.
1150
+ flushLlmObs();
1145
1151
  }
1146
1152
  });
1147
1153
  }