apitally 1.0.0-beta.2 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -25
- package/dist/activation.cjs +2 -1
- package/dist/activation.cjs.map +1 -1
- package/dist/activation.d.cts +1 -0
- package/dist/activation.d.ts +1 -0
- package/dist/activation.js +2 -1
- package/dist/activation.js.map +1 -1
- package/dist/consumers.cjs +96 -0
- package/dist/consumers.cjs.map +1 -0
- package/dist/consumers.d.cts +13 -0
- package/dist/consumers.d.ts +13 -0
- package/dist/consumers.js +93 -0
- package/dist/consumers.js.map +1 -0
- package/dist/context.cjs +2 -8
- package/dist/context.cjs.map +1 -1
- package/dist/context.d.cts +5 -5
- package/dist/context.d.ts +5 -5
- package/dist/context.js +3 -7
- package/dist/context.js.map +1 -1
- package/dist/exportWorker.cjs +6 -5
- package/dist/exportWorker.cjs.map +1 -1
- package/dist/exportWorker.js +6 -5
- package/dist/exportWorker.js.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics.cjs +164 -32
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +4 -6
- package/dist/metrics.d.ts +4 -6
- package/dist/metrics.js +165 -33
- package/dist/metrics.js.map +1 -1
- package/dist/nestjs/index.cjs +6 -0
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.js +6 -0
- package/dist/nestjs/index.js.map +1 -1
- package/dist/providers.cjs +0 -8
- package/dist/providers.cjs.map +1 -1
- package/dist/providers.d.cts +0 -2
- package/dist/providers.d.ts +0 -2
- package/dist/providers.js +1 -8
- package/dist/providers.js.map +1 -1
- package/dist/requestObservation.cjs +6 -4
- package/dist/requestObservation.cjs.map +1 -1
- package/dist/requestObservation.js +7 -5
- package/dist/requestObservation.js.map +1 -1
- package/dist/serverErrors.cjs +30 -28
- package/dist/serverErrors.cjs.map +1 -1
- package/dist/serverErrors.js +30 -28
- package/dist/serverErrors.js.map +1 -1
- package/dist/spanProcessor.cjs +0 -2
- package/dist/spanProcessor.cjs.map +1 -1
- package/dist/spanProcessor.js +0 -2
- package/dist/spanProcessor.js.map +1 -1
- package/dist/spool.cjs +5 -0
- package/dist/spool.cjs.map +1 -1
- package/dist/spool.d.cts +2 -1
- package/dist/spool.d.ts +2 -1
- package/dist/spool.js +5 -0
- package/dist/spool.js.map +1 -1
- package/dist/validationErrors.cjs +29 -26
- package/dist/validationErrors.cjs.map +1 -1
- package/dist/validationErrors.js +29 -26
- package/dist/validationErrors.js.map +1 -1
- package/package.json +2 -2
- package/dist/consumer.cjs +0 -61
- package/dist/consumer.cjs.map +0 -1
- package/dist/consumer.d.cts +0 -12
- package/dist/consumer.d.ts +0 -12
- package/dist/consumer.js +0 -59
- package/dist/consumer.js.map +0 -1
package/dist/context.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.cjs","names":["createContextKey","context","getRPCMetadata","RPCType"],"sources":["../src/context.ts"],"sourcesContent":["import {\n type Attributes,\n type Context,\n context,\n createContextKey,\n type Span,\n} from \"@opentelemetry/api\";\nimport { getRPCMetadata, type RPCMetadata, RPCType } from \"@opentelemetry/core\";\nimport type { ValidationErrorDetail } from \"./validationErrors.js\";\n\n// Write sites resolve the request's SERVER span through this handle: under a child\n// span, the active span is not the SERVER span, and OTel has no public upward walk.\nexport interface SpanHandle {\n span?: Span;\n // This set-once reference preserves lifecycle ownership if span later changes.\n ownSpan?: Span;\n}\n\nexport interface
|
|
1
|
+
{"version":3,"file":"context.cjs","names":["createContextKey","context","getRPCMetadata","RPCType"],"sources":["../src/context.ts"],"sourcesContent":["import {\n type Attributes,\n type Context,\n context,\n createContextKey,\n type Span,\n} from \"@opentelemetry/api\";\nimport { getRPCMetadata, type RPCMetadata, RPCType } from \"@opentelemetry/core\";\nimport type { ValidationErrorDetail } from \"./validationErrors.js\";\n\n// Write sites resolve the request's SERVER span through this handle: under a child\n// span, the active span is not the SERVER span, and OTel has no public upward walk.\nexport interface SpanHandle {\n span?: Span;\n // This set-once reference preserves lifecycle ownership if span later changes.\n ownSpan?: Span;\n}\n\nexport interface RequestConsumer {\n identifier: string;\n name?: string;\n group?: string;\n attributes: Map<string, string | null>;\n}\n\n// Requests dropped for these reasons are still counted in request metrics,\n// except preflight and websocket requests, which are never recorded.\nexport type RequestDropReason = \"excluded\" | \"method\" | \"scheme\" | \"sampled-out\";\n\n// Transport attributes are applied to exported spans last and used for metrics,\n// so transport values take precedence.\nexport interface RequestRecord {\n attributes: Attributes;\n serverSpanId?: string;\n // Transport-measured request duration, set at completion; the duration\n // histogram records it independent of span timing.\n durationSeconds?: number;\n dropReason?: RequestDropReason;\n // Error state for the validation and server error events; it exists\n // independently of the SERVER span and of the drop decision.\n exception?: unknown;\n validationErrors?: ValidationErrorDetail[];\n // Consumer metadata for the consumer update event emitted at completion.\n consumer?: RequestConsumer;\n}\n\nexport const SPAN_HANDLE_KEY = createContextKey(\"apitally-span-handle\");\nexport const REQUEST_RECORD_KEY = createContextKey(\"apitally-request-record\");\n\nconst RPC_REQUEST_RECORD_KEY = Symbol.for(\"apitally.rpcRequestRecord\");\n\ntype RPCMetadataWithRequestRecord = RPCMetadata & {\n [RPC_REQUEST_RECORD_KEY]?: RequestRecord;\n};\n\n// Mutable holders carry changing request state because OTel contexts are immutable.\nexport function withRequestHolders(\n baseContext: Context,\n spanHandle: SpanHandle,\n record: RequestRecord,\n): Context {\n return baseContext.setValue(SPAN_HANDLE_KEY, spanHandle).setValue(REQUEST_RECORD_KEY, record);\n}\n\nexport function attachRequestRecordToRpcMetadata(\n rpcMetadata: RPCMetadata | undefined,\n record: RequestRecord,\n): void {\n if (rpcMetadata) {\n (rpcMetadata as RPCMetadataWithRequestRecord)[RPC_REQUEST_RECORD_KEY] = record;\n }\n}\n\nexport function getServerSpan(activeContext: Context = context.active()): Span | undefined {\n const spanHandle = activeContext.getValue(SPAN_HANDLE_KEY) as SpanHandle | undefined;\n const rpcMetadata = getRPCMetadata(activeContext) as RPCMetadataWithRequestRecord | undefined;\n return (\n spanHandle?.span ??\n (rpcMetadata?.type === RPCType.HTTP && rpcMetadata[RPC_REQUEST_RECORD_KEY]\n ? rpcMetadata.span\n : undefined)\n );\n}\n\nexport function getRequestRecord(\n activeContext: Context = context.active(),\n): RequestRecord | undefined {\n const rpcMetadata = getRPCMetadata(activeContext) as RPCMetadataWithRequestRecord | undefined;\n return (\n (activeContext.getValue(REQUEST_RECORD_KEY) as RequestRecord | undefined) ??\n rpcMetadata?.[RPC_REQUEST_RECORD_KEY]\n );\n}\n"],"mappings":";;;;AA8CA,MAAa,mBAAA,GAAkBA,mBAAAA,iBAAAA,CAAiB,sBAAsB;AACtE,MAAa,sBAAA,GAAqBA,mBAAAA,iBAAAA,CAAiB,yBAAyB;AAE5E,MAAM,yBAAyB,OAAO,IAAI,2BAA2B;AAOrE,SAAgB,mBACd,aACA,YACA,QACS;CACT,OAAO,YAAY,SAAS,iBAAiB,UAAU,CAAC,CAAC,SAAS,oBAAoB,MAAM;AAC9F;AAEA,SAAgB,iCACd,aACA,QACM;CACN,IAAI,aACF,YAA8C,0BAA0B;AAE5E;AAEA,SAAgB,cAAc,gBAAyBC,mBAAAA,QAAQ,OAAO,GAAqB;CACzF,MAAM,aAAa,cAAc,SAAS,eAAe;CACzD,MAAM,eAAA,GAAcC,oBAAAA,eAAAA,CAAe,aAAa;CAChD,OACE,YAAY,SACX,aAAa,SAASC,oBAAAA,QAAQ,QAAQ,YAAY,0BAC/C,YAAY,OACZ,KAAA;AAER;AAEA,SAAgB,iBACd,gBAAyBF,mBAAAA,QAAQ,OAAO,GACb;CAC3B,MAAM,eAAA,GAAcC,oBAAAA,eAAAA,CAAe,aAAa;CAChD,OACG,cAAc,SAAS,kBAAkB,KAC1C,cAAc;AAElB"}
|
package/dist/context.d.cts
CHANGED
|
@@ -6,10 +6,11 @@ export interface SpanHandle {
|
|
|
6
6
|
span?: Span;
|
|
7
7
|
ownSpan?: Span;
|
|
8
8
|
}
|
|
9
|
-
export interface
|
|
10
|
-
identifier
|
|
9
|
+
export interface RequestConsumer {
|
|
10
|
+
identifier: string;
|
|
11
11
|
name?: string;
|
|
12
12
|
group?: string;
|
|
13
|
+
attributes: Map<string, string | null>;
|
|
13
14
|
}
|
|
14
15
|
export type RequestDropReason = "excluded" | "method" | "scheme" | "sampled-out";
|
|
15
16
|
export interface RequestRecord {
|
|
@@ -19,14 +20,13 @@ export interface RequestRecord {
|
|
|
19
20
|
dropReason?: RequestDropReason;
|
|
20
21
|
exception?: unknown;
|
|
21
22
|
validationErrors?: ValidationErrorDetail[];
|
|
23
|
+
consumer?: RequestConsumer;
|
|
22
24
|
}
|
|
23
25
|
export declare const SPAN_HANDLE_KEY: symbol;
|
|
24
26
|
export declare const REQUEST_RECORD_KEY: symbol;
|
|
25
|
-
export declare
|
|
26
|
-
export declare function withRequestHolders(baseContext: Context, spanHandle: SpanHandle, record: RequestRecord, consumerHolder: ConsumerHolder): Context;
|
|
27
|
+
export declare function withRequestHolders(baseContext: Context, spanHandle: SpanHandle, record: RequestRecord): Context;
|
|
27
28
|
export declare function attachRequestRecordToRpcMetadata(rpcMetadata: RPCMetadata | undefined, record: RequestRecord): void;
|
|
28
29
|
export declare function getServerSpan(activeContext?: Context): Span | undefined;
|
|
29
30
|
export declare function getRequestRecord(activeContext?: Context): RequestRecord | undefined;
|
|
30
|
-
export declare function getConsumerHolder(activeContext?: Context): ConsumerHolder | undefined;
|
|
31
31
|
//#endregion
|
|
32
32
|
//# sourceMappingURL=context.d.cts.map
|
package/dist/context.d.ts
CHANGED
|
@@ -6,10 +6,11 @@ export interface SpanHandle {
|
|
|
6
6
|
span?: Span;
|
|
7
7
|
ownSpan?: Span;
|
|
8
8
|
}
|
|
9
|
-
export interface
|
|
10
|
-
identifier
|
|
9
|
+
export interface RequestConsumer {
|
|
10
|
+
identifier: string;
|
|
11
11
|
name?: string;
|
|
12
12
|
group?: string;
|
|
13
|
+
attributes: Map<string, string | null>;
|
|
13
14
|
}
|
|
14
15
|
export type RequestDropReason = "excluded" | "method" | "scheme" | "sampled-out";
|
|
15
16
|
export interface RequestRecord {
|
|
@@ -19,14 +20,13 @@ export interface RequestRecord {
|
|
|
19
20
|
dropReason?: RequestDropReason;
|
|
20
21
|
exception?: unknown;
|
|
21
22
|
validationErrors?: ValidationErrorDetail[];
|
|
23
|
+
consumer?: RequestConsumer;
|
|
22
24
|
}
|
|
23
25
|
export declare const SPAN_HANDLE_KEY: symbol;
|
|
24
26
|
export declare const REQUEST_RECORD_KEY: symbol;
|
|
25
|
-
export declare
|
|
26
|
-
export declare function withRequestHolders(baseContext: Context, spanHandle: SpanHandle, record: RequestRecord, consumerHolder: ConsumerHolder): Context;
|
|
27
|
+
export declare function withRequestHolders(baseContext: Context, spanHandle: SpanHandle, record: RequestRecord): Context;
|
|
27
28
|
export declare function attachRequestRecordToRpcMetadata(rpcMetadata: RPCMetadata | undefined, record: RequestRecord): void;
|
|
28
29
|
export declare function getServerSpan(activeContext?: Context): Span | undefined;
|
|
29
30
|
export declare function getRequestRecord(activeContext?: Context): RequestRecord | undefined;
|
|
30
|
-
export declare function getConsumerHolder(activeContext?: Context): ConsumerHolder | undefined;
|
|
31
31
|
//#endregion
|
|
32
32
|
//# sourceMappingURL=context.d.ts.map
|
package/dist/context.js
CHANGED
|
@@ -3,10 +3,9 @@ import { RPCType, getRPCMetadata } from "@opentelemetry/core";
|
|
|
3
3
|
//#region src/context.ts
|
|
4
4
|
const SPAN_HANDLE_KEY = createContextKey("apitally-span-handle");
|
|
5
5
|
const REQUEST_RECORD_KEY = createContextKey("apitally-request-record");
|
|
6
|
-
const CONSUMER_HOLDER_KEY = createContextKey("apitally-consumer-holder");
|
|
7
6
|
const RPC_REQUEST_RECORD_KEY = Symbol.for("apitally.rpcRequestRecord");
|
|
8
|
-
function withRequestHolders(baseContext, spanHandle, record
|
|
9
|
-
return baseContext.setValue(SPAN_HANDLE_KEY, spanHandle).setValue(REQUEST_RECORD_KEY, record)
|
|
7
|
+
function withRequestHolders(baseContext, spanHandle, record) {
|
|
8
|
+
return baseContext.setValue(SPAN_HANDLE_KEY, spanHandle).setValue(REQUEST_RECORD_KEY, record);
|
|
10
9
|
}
|
|
11
10
|
function attachRequestRecordToRpcMetadata(rpcMetadata, record) {
|
|
12
11
|
if (rpcMetadata) rpcMetadata[RPC_REQUEST_RECORD_KEY] = record;
|
|
@@ -20,10 +19,7 @@ function getRequestRecord(activeContext = context.active()) {
|
|
|
20
19
|
const rpcMetadata = getRPCMetadata(activeContext);
|
|
21
20
|
return activeContext.getValue(REQUEST_RECORD_KEY) ?? rpcMetadata?.[RPC_REQUEST_RECORD_KEY];
|
|
22
21
|
}
|
|
23
|
-
function getConsumerHolder(activeContext = context.active()) {
|
|
24
|
-
return activeContext.getValue(CONSUMER_HOLDER_KEY);
|
|
25
|
-
}
|
|
26
22
|
//#endregion
|
|
27
|
-
export {
|
|
23
|
+
export { REQUEST_RECORD_KEY, SPAN_HANDLE_KEY, attachRequestRecordToRpcMetadata, getRequestRecord, getServerSpan, withRequestHolders };
|
|
28
24
|
|
|
29
25
|
//# sourceMappingURL=context.js.map
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import {\n type Attributes,\n type Context,\n context,\n createContextKey,\n type Span,\n} from \"@opentelemetry/api\";\nimport { getRPCMetadata, type RPCMetadata, RPCType } from \"@opentelemetry/core\";\nimport type { ValidationErrorDetail } from \"./validationErrors.js\";\n\n// Write sites resolve the request's SERVER span through this handle: under a child\n// span, the active span is not the SERVER span, and OTel has no public upward walk.\nexport interface SpanHandle {\n span?: Span;\n // This set-once reference preserves lifecycle ownership if span later changes.\n ownSpan?: Span;\n}\n\nexport interface
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import {\n type Attributes,\n type Context,\n context,\n createContextKey,\n type Span,\n} from \"@opentelemetry/api\";\nimport { getRPCMetadata, type RPCMetadata, RPCType } from \"@opentelemetry/core\";\nimport type { ValidationErrorDetail } from \"./validationErrors.js\";\n\n// Write sites resolve the request's SERVER span through this handle: under a child\n// span, the active span is not the SERVER span, and OTel has no public upward walk.\nexport interface SpanHandle {\n span?: Span;\n // This set-once reference preserves lifecycle ownership if span later changes.\n ownSpan?: Span;\n}\n\nexport interface RequestConsumer {\n identifier: string;\n name?: string;\n group?: string;\n attributes: Map<string, string | null>;\n}\n\n// Requests dropped for these reasons are still counted in request metrics,\n// except preflight and websocket requests, which are never recorded.\nexport type RequestDropReason = \"excluded\" | \"method\" | \"scheme\" | \"sampled-out\";\n\n// Transport attributes are applied to exported spans last and used for metrics,\n// so transport values take precedence.\nexport interface RequestRecord {\n attributes: Attributes;\n serverSpanId?: string;\n // Transport-measured request duration, set at completion; the duration\n // histogram records it independent of span timing.\n durationSeconds?: number;\n dropReason?: RequestDropReason;\n // Error state for the validation and server error events; it exists\n // independently of the SERVER span and of the drop decision.\n exception?: unknown;\n validationErrors?: ValidationErrorDetail[];\n // Consumer metadata for the consumer update event emitted at completion.\n consumer?: RequestConsumer;\n}\n\nexport const SPAN_HANDLE_KEY = createContextKey(\"apitally-span-handle\");\nexport const REQUEST_RECORD_KEY = createContextKey(\"apitally-request-record\");\n\nconst RPC_REQUEST_RECORD_KEY = Symbol.for(\"apitally.rpcRequestRecord\");\n\ntype RPCMetadataWithRequestRecord = RPCMetadata & {\n [RPC_REQUEST_RECORD_KEY]?: RequestRecord;\n};\n\n// Mutable holders carry changing request state because OTel contexts are immutable.\nexport function withRequestHolders(\n baseContext: Context,\n spanHandle: SpanHandle,\n record: RequestRecord,\n): Context {\n return baseContext.setValue(SPAN_HANDLE_KEY, spanHandle).setValue(REQUEST_RECORD_KEY, record);\n}\n\nexport function attachRequestRecordToRpcMetadata(\n rpcMetadata: RPCMetadata | undefined,\n record: RequestRecord,\n): void {\n if (rpcMetadata) {\n (rpcMetadata as RPCMetadataWithRequestRecord)[RPC_REQUEST_RECORD_KEY] = record;\n }\n}\n\nexport function getServerSpan(activeContext: Context = context.active()): Span | undefined {\n const spanHandle = activeContext.getValue(SPAN_HANDLE_KEY) as SpanHandle | undefined;\n const rpcMetadata = getRPCMetadata(activeContext) as RPCMetadataWithRequestRecord | undefined;\n return (\n spanHandle?.span ??\n (rpcMetadata?.type === RPCType.HTTP && rpcMetadata[RPC_REQUEST_RECORD_KEY]\n ? rpcMetadata.span\n : undefined)\n );\n}\n\nexport function getRequestRecord(\n activeContext: Context = context.active(),\n): RequestRecord | undefined {\n const rpcMetadata = getRPCMetadata(activeContext) as RPCMetadataWithRequestRecord | undefined;\n return (\n (activeContext.getValue(REQUEST_RECORD_KEY) as RequestRecord | undefined) ??\n rpcMetadata?.[RPC_REQUEST_RECORD_KEY]\n );\n}\n"],"mappings":";;;AA8CA,MAAa,kBAAkB,iBAAiB,sBAAsB;AACtE,MAAa,qBAAqB,iBAAiB,yBAAyB;AAE5E,MAAM,yBAAyB,OAAO,IAAI,2BAA2B;AAOrE,SAAgB,mBACd,aACA,YACA,QACS;CACT,OAAO,YAAY,SAAS,iBAAiB,UAAU,CAAC,CAAC,SAAS,oBAAoB,MAAM;AAC9F;AAEA,SAAgB,iCACd,aACA,QACM;CACN,IAAI,aACF,YAA8C,0BAA0B;AAE5E;AAEA,SAAgB,cAAc,gBAAyB,QAAQ,OAAO,GAAqB;CACzF,MAAM,aAAa,cAAc,SAAS,eAAe;CACzD,MAAM,cAAc,eAAe,aAAa;CAChD,OACE,YAAY,SACX,aAAa,SAAS,QAAQ,QAAQ,YAAY,0BAC/C,YAAY,OACZ,KAAA;AAER;AAEA,SAAgB,iBACd,gBAAyB,QAAQ,OAAO,GACb;CAC3B,MAAM,cAAc,eAAe,aAAa;CAChD,OACG,cAAc,SAAS,kBAAkB,KAC1C,cAAc;AAElB"}
|
package/dist/exportWorker.cjs
CHANGED
|
@@ -5,7 +5,7 @@ let _opentelemetry_api = require("@opentelemetry/api");
|
|
|
5
5
|
let node_module = require("node:module");
|
|
6
6
|
let _opentelemetry_core = require("@opentelemetry/core");
|
|
7
7
|
//#region src/exportWorker.ts
|
|
8
|
-
const
|
|
8
|
+
const MAX_BACKLOG_SENDS_PER_CYCLE = 10;
|
|
9
9
|
const DEFAULT_EXPORT_INTERVAL_MILLIS = 15e3;
|
|
10
10
|
const EXPORT_INTERVAL_HEADER = "Apitally-Export-Interval";
|
|
11
11
|
const INITIAL_EXPORT_DELAY_MILLIS = 2e3;
|
|
@@ -108,22 +108,23 @@ var ExportWorker = class {
|
|
|
108
108
|
await callback();
|
|
109
109
|
}
|
|
110
110
|
if (signal.aborted) return;
|
|
111
|
+
let maxSends = Number.POSITIVE_INFINITY;
|
|
111
112
|
if (final) await this.spool.closeCurrentFiles();
|
|
112
113
|
else {
|
|
113
|
-
await this.spool.rotateForExport();
|
|
114
|
+
maxSends = MAX_BACKLOG_SENDS_PER_CYCLE + await this.spool.rotateForExport();
|
|
114
115
|
this.spool.touchFiles();
|
|
115
116
|
}
|
|
116
117
|
if (signal.aborted) return;
|
|
117
|
-
await this.sendPendingFiles(final, signal);
|
|
118
|
+
await this.sendPendingFiles(final, maxSends, signal);
|
|
118
119
|
});
|
|
119
120
|
} catch (error) {
|
|
120
121
|
require_logger.logDebug(`Error in Apitally export cycle: ${String(error)}`);
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
|
-
async sendPendingFiles(final, signal) {
|
|
124
|
+
async sendPendingFiles(final, maxSends, signal) {
|
|
124
125
|
let sent = 0;
|
|
125
126
|
for (const file of this.spool.pendingFiles()) {
|
|
126
|
-
if (signal.aborted ||
|
|
127
|
+
if (signal.aborted || sent >= maxSends) return;
|
|
127
128
|
if (!final && sent > 0) {
|
|
128
129
|
const pauseMillis = this.interSendPauseMillis();
|
|
129
130
|
if (pauseMillis > 0) await new Promise((resolve) => setTimeout(resolve, pauseMillis));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exportWorker.cjs","names":["getDistroVersion","context","suppressTracing","createRequire","logDebug"],"sources":["../src/exportWorker.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { context } from \"@opentelemetry/api\";\nimport { suppressTracing } from \"@opentelemetry/core\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport type { Spool, SpoolFile } from \"./spool.js\";\n\nconst MAX_SENDS_PER_CYCLE = 10;\nconst DEFAULT_EXPORT_INTERVAL_MILLIS = 15_000;\nconst EXPORT_INTERVAL_HEADER = \"Apitally-Export-Interval\";\nconst INITIAL_EXPORT_DELAY_MILLIS = 2_000;\nconst MIN_EXPORT_INTERVAL_SECONDS = 5;\nconst MAX_EXPORT_INTERVAL_SECONDS = 60;\nconst REQUEST_TIMEOUT_MILLIS = 10_000;\nconst RETRYABLE_STATUS_CODES = new Set([408, 429]);\n\nexport type FlushCallback = () => Promise<void> | void;\n\nexport interface ExportWorkerOptions {\n spool: Spool;\n otlpEndpoint: string;\n writeToken: string;\n env: string;\n initialExportDelayMillis?: number;\n requestTimeoutMillis?: number;\n interSendPauseMillis?: () => number;\n}\n\ninterface ProxyTransport {\n fetch: (url: string, init: RequestInit) => Promise<Response>;\n dispatcher: { close(): Promise<void> };\n}\n\n// Send cycles are serialized; a flush request joins the active cycle instead of\n// posting files twice.\nexport class ExportWorker {\n // Invoked at the start of every cycle, before files are rotated and sent.\n readonly flushCallbacks: FlushCallback[] = [];\n intervalMillis = DEFAULT_EXPORT_INTERVAL_MILLIS;\n private readonly spool: Spool;\n private readonly otlpEndpoint: string;\n private readonly headers: Record<string, string>;\n private readonly initialExportDelayMillis: number;\n private readonly requestTimeoutMillis: number;\n private readonly interSendPauseMillis: () => number;\n private readonly useProxy: boolean;\n private proxyTransport?: ProxyTransport;\n private readonly warnedStatuses = new Set<number>();\n private currentCycle?: Promise<void>;\n private currentCycleController?: AbortController;\n private timer?: NodeJS.Timeout;\n private started = false;\n\n constructor(options: ExportWorkerOptions) {\n this.spool = options.spool;\n this.otlpEndpoint = options.otlpEndpoint;\n this.headers = {\n Authorization: `Bearer ${options.writeToken}`,\n \"Apitally-Env\": options.env,\n \"Content-Type\": \"application/x-protobuf\",\n \"Content-Encoding\": \"gzip\",\n \"User-Agent\": `apitally-js/${getDistroVersion()}`,\n };\n this.initialExportDelayMillis = options.initialExportDelayMillis ?? INITIAL_EXPORT_DELAY_MILLIS;\n this.requestTimeoutMillis = options.requestTimeoutMillis ?? REQUEST_TIMEOUT_MILLIS;\n this.interSendPauseMillis = options.interSendPauseMillis ?? (() => 100 + Math.random() * 400);\n const env = process.env;\n this.useProxy = Boolean(env.HTTP_PROXY || env.http_proxy || env.HTTPS_PROXY || env.https_proxy);\n }\n\n start(): void {\n if (this.started) {\n return;\n }\n this.started = true;\n this.scheduleNextExportCycle(this.initialExportDelayMillis);\n }\n\n async stop(): Promise<void> {\n this.started = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n await this.waitForIdle();\n if (this.proxyTransport) {\n await this.proxyTransport.dispatcher.close();\n }\n }\n\n // Runs one send cycle, or joins the cycle already running (no file posts twice).\n runCycle(): Promise<void> {\n return this.currentCycle ?? this.chainCycle(false);\n }\n\n // Closes current files and sends everything pending, optionally within one deadline.\n async finalDrain(timeoutMillis?: number): Promise<void> {\n if (timeoutMillis === undefined) {\n await this.chainCycle(true);\n return;\n }\n\n const deadlineController = new AbortController();\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(() => {\n deadlineController.abort();\n resolve();\n }, timeoutMillis);\n });\n\n this.currentCycleController?.abort();\n const cycle = this.chainCycle(true, deadlineController.signal);\n try {\n await Promise.race([cycle, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n }\n\n waitForIdle(): Promise<void> {\n return this.currentCycle ?? Promise.resolve();\n }\n\n private chainCycle(final: boolean, deadlineSignal?: AbortSignal): Promise<void> {\n const previous = this.currentCycle ?? Promise.resolve();\n const controller = new AbortController();\n const signal = deadlineSignal\n ? AbortSignal.any([controller.signal, deadlineSignal])\n : controller.signal;\n const cycle = previous\n .then(() => this.executeCycle(final, signal))\n .finally(() => {\n if (this.currentCycle === cycle) {\n this.currentCycle = undefined;\n this.currentCycleController = undefined;\n }\n });\n this.currentCycle = cycle;\n this.currentCycleController = controller;\n return cycle;\n }\n\n private async executeCycle(final: boolean, signal: AbortSignal): Promise<void> {\n try {\n // Suppress instrumentation so worker flushes and POSTs generate no telemetry.\n await context.with(suppressTracing(context.active()), async () => {\n for (const callback of this.flushCallbacks) {\n if (signal.aborted) {\n return;\n }\n await callback();\n }\n if (signal.aborted) {\n return;\n }\n if (final) {\n await this.spool.closeCurrentFiles();\n } else {\n await this.spool.rotateForExport();\n this.spool.touchFiles();\n }\n if (signal.aborted) {\n return;\n }\n await this.sendPendingFiles(final, signal);\n });\n } catch (error) {\n logDebug(`Error in Apitally export cycle: ${String(error)}`);\n }\n }\n\n // During an outage, stopping on failure limits each cycle to one probe POST.\n private async sendPendingFiles(final: boolean, signal: AbortSignal): Promise<void> {\n let sent = 0;\n for (const file of this.spool.pendingFiles()) {\n if (signal.aborted || (!final && sent >= MAX_SENDS_PER_CYCLE)) {\n return;\n }\n if (!final && sent > 0) {\n const pauseMillis = this.interSendPauseMillis();\n if (pauseMillis > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, pauseMillis));\n }\n if (signal.aborted) {\n return;\n }\n }\n const expirationDeletion = this.spool.deleteIfExpired(file);\n if (expirationDeletion) {\n await expirationDeletion;\n continue;\n }\n sent += 1;\n if (!(await this.sendFile(file, signal))) {\n return;\n }\n }\n }\n\n // Sends the file's stored bytes verbatim. Returns false on a retryable\n // failure, which ends the cycle and keeps the file queued.\n private async sendFile(file: SpoolFile, signal: AbortSignal): Promise<boolean> {\n file.markAttempt();\n const url = `${this.otlpEndpoint}/v1/${file.signal}`;\n let body: Buffer;\n try {\n body = await file.readStoredBytes();\n } catch {\n logWarning(`Error reading buffered ${file.signal}, dropping the file`);\n await this.spool.deleteFile(file);\n return true;\n }\n if (signal.aborted) {\n return false;\n }\n let response: Response;\n try {\n try {\n response = await this.postFile(url, body, signal);\n } catch (error) {\n if (signal.aborted || isAbortError(error) || isTimeoutError(error)) {\n throw error;\n }\n // The server may close an idle keep-alive connection mid-request; retry once.\n response = await this.postFile(url, body, signal);\n }\n } catch (error) {\n if (!signal.aborted) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed (${String(error)}), will retry`,\n );\n }\n return false;\n }\n await response.arrayBuffer().catch(() => undefined);\n if (signal.aborted) {\n return false;\n }\n this.applyIntervalHeader(response);\n if (response.status >= 200 && response.status < 300) {\n await this.spool.deleteFile(file);\n return true;\n }\n if (RETRYABLE_STATUS_CODES.has(response.status) || response.status >= 500) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed with HTTP ${response.status}, will retry`,\n );\n return false;\n }\n if (!this.warnedStatuses.has(response.status)) {\n this.warnedStatuses.add(response.status);\n logWarning(\n `Apitally rejected buffered ${file.signal} with HTTP ${response.status}; they were dropped`,\n );\n }\n await this.spool.deleteFile(file);\n return true;\n }\n\n private postFile(url: string, body: Buffer, signal: AbortSignal): Promise<Response> {\n const init: RequestInit = {\n method: \"POST\",\n headers: this.headers,\n body: body as unknown as BodyInit,\n signal: AbortSignal.any([signal, AbortSignal.timeout(this.requestTimeoutMillis)]),\n };\n if (!this.useProxy) {\n return fetch(url, init);\n }\n const transport = this.getProxyTransport();\n return transport.fetch(url, {\n ...init,\n dispatcher: transport.dispatcher,\n } as RequestInit);\n }\n\n private getProxyTransport(): ProxyTransport {\n if (!this.proxyTransport) {\n // undici's fetch is required because Node's bundled fetch does not accept\n // an undici dispatcher on every supported Node version.\n const undici = createRequire(import.meta.url)(\"undici\") as {\n fetch: ProxyTransport[\"fetch\"];\n EnvHttpProxyAgent: new () => ProxyTransport[\"dispatcher\"];\n };\n this.proxyTransport = {\n fetch: undici.fetch,\n dispatcher: new undici.EnvHttpProxyAgent(),\n };\n }\n return this.proxyTransport;\n }\n\n private applyIntervalHeader(response: Response): void {\n const value = response.headers.get(EXPORT_INTERVAL_HEADER);\n if (!value) {\n return;\n }\n const seconds = Number.parseInt(value, 10);\n if (Number.isNaN(seconds)) {\n return;\n }\n this.intervalMillis =\n Math.min(Math.max(seconds, MIN_EXPORT_INTERVAL_SECONDS), MAX_EXPORT_INTERVAL_SECONDS) * 1000;\n }\n\n private scheduleNextExportCycle(delayMillis: number): void {\n this.timer = setTimeout(() => {\n void this.runCycle()\n .catch((error) => logDebug(`Error in Apitally export cycle: ${String(error)}`))\n .finally(() => {\n if (this.started) {\n // Jitter desynchronizes deployments whose processes started together.\n this.scheduleNextExportCycle(this.intervalMillis * (0.9 + Math.random() * 0.2));\n }\n });\n }, delayMillis);\n this.timer.unref();\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n return error instanceof Error && error.name === \"TimeoutError\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"exportWorker.cjs","names":["getDistroVersion","context","suppressTracing","createRequire","logDebug"],"sources":["../src/exportWorker.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { context } from \"@opentelemetry/api\";\nimport { suppressTracing } from \"@opentelemetry/core\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport type { Spool, SpoolFile } from \"./spool.js\";\n\nconst MAX_BACKLOG_SENDS_PER_CYCLE = 10;\nconst DEFAULT_EXPORT_INTERVAL_MILLIS = 15_000;\nconst EXPORT_INTERVAL_HEADER = \"Apitally-Export-Interval\";\nconst INITIAL_EXPORT_DELAY_MILLIS = 2_000;\nconst MIN_EXPORT_INTERVAL_SECONDS = 5;\nconst MAX_EXPORT_INTERVAL_SECONDS = 60;\nconst REQUEST_TIMEOUT_MILLIS = 10_000;\nconst RETRYABLE_STATUS_CODES = new Set([408, 429]);\n\nexport type FlushCallback = () => Promise<void> | void;\n\nexport interface ExportWorkerOptions {\n spool: Spool;\n otlpEndpoint: string;\n writeToken: string;\n env: string;\n initialExportDelayMillis?: number;\n requestTimeoutMillis?: number;\n interSendPauseMillis?: () => number;\n}\n\ninterface ProxyTransport {\n fetch: (url: string, init: RequestInit) => Promise<Response>;\n dispatcher: { close(): Promise<void> };\n}\n\n// Send cycles are serialized; a flush request joins the active cycle instead of\n// posting files twice.\nexport class ExportWorker {\n // Invoked at the start of every cycle, before files are rotated and sent.\n readonly flushCallbacks: FlushCallback[] = [];\n intervalMillis = DEFAULT_EXPORT_INTERVAL_MILLIS;\n private readonly spool: Spool;\n private readonly otlpEndpoint: string;\n private readonly headers: Record<string, string>;\n private readonly initialExportDelayMillis: number;\n private readonly requestTimeoutMillis: number;\n private readonly interSendPauseMillis: () => number;\n private readonly useProxy: boolean;\n private proxyTransport?: ProxyTransport;\n private readonly warnedStatuses = new Set<number>();\n private currentCycle?: Promise<void>;\n private currentCycleController?: AbortController;\n private timer?: NodeJS.Timeout;\n private started = false;\n\n constructor(options: ExportWorkerOptions) {\n this.spool = options.spool;\n this.otlpEndpoint = options.otlpEndpoint;\n this.headers = {\n Authorization: `Bearer ${options.writeToken}`,\n \"Apitally-Env\": options.env,\n \"Content-Type\": \"application/x-protobuf\",\n \"Content-Encoding\": \"gzip\",\n \"User-Agent\": `apitally-js/${getDistroVersion()}`,\n };\n this.initialExportDelayMillis = options.initialExportDelayMillis ?? INITIAL_EXPORT_DELAY_MILLIS;\n this.requestTimeoutMillis = options.requestTimeoutMillis ?? REQUEST_TIMEOUT_MILLIS;\n this.interSendPauseMillis = options.interSendPauseMillis ?? (() => 100 + Math.random() * 400);\n const env = process.env;\n this.useProxy = Boolean(env.HTTP_PROXY || env.http_proxy || env.HTTPS_PROXY || env.https_proxy);\n }\n\n start(): void {\n if (this.started) {\n return;\n }\n this.started = true;\n this.scheduleNextExportCycle(this.initialExportDelayMillis);\n }\n\n async stop(): Promise<void> {\n this.started = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n await this.waitForIdle();\n if (this.proxyTransport) {\n await this.proxyTransport.dispatcher.close();\n }\n }\n\n // Runs one send cycle, or joins the cycle already running (no file posts twice).\n runCycle(): Promise<void> {\n return this.currentCycle ?? this.chainCycle(false);\n }\n\n // Closes current files and sends everything pending, optionally within one deadline.\n async finalDrain(timeoutMillis?: number): Promise<void> {\n if (timeoutMillis === undefined) {\n await this.chainCycle(true);\n return;\n }\n\n const deadlineController = new AbortController();\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(() => {\n deadlineController.abort();\n resolve();\n }, timeoutMillis);\n });\n\n this.currentCycleController?.abort();\n const cycle = this.chainCycle(true, deadlineController.signal);\n try {\n await Promise.race([cycle, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n }\n\n waitForIdle(): Promise<void> {\n return this.currentCycle ?? Promise.resolve();\n }\n\n private chainCycle(final: boolean, deadlineSignal?: AbortSignal): Promise<void> {\n const previous = this.currentCycle ?? Promise.resolve();\n const controller = new AbortController();\n const signal = deadlineSignal\n ? AbortSignal.any([controller.signal, deadlineSignal])\n : controller.signal;\n const cycle = previous\n .then(() => this.executeCycle(final, signal))\n .finally(() => {\n if (this.currentCycle === cycle) {\n this.currentCycle = undefined;\n this.currentCycleController = undefined;\n }\n });\n this.currentCycle = cycle;\n this.currentCycleController = controller;\n return cycle;\n }\n\n private async executeCycle(final: boolean, signal: AbortSignal): Promise<void> {\n try {\n // Suppress instrumentation so worker flushes and POSTs generate no telemetry.\n await context.with(suppressTracing(context.active()), async () => {\n for (const callback of this.flushCallbacks) {\n if (signal.aborted) {\n return;\n }\n await callback();\n }\n if (signal.aborted) {\n return;\n }\n let maxSends = Number.POSITIVE_INFINITY;\n if (final) {\n await this.spool.closeCurrentFiles();\n } else {\n maxSends = MAX_BACKLOG_SENDS_PER_CYCLE + (await this.spool.rotateForExport());\n this.spool.touchFiles();\n }\n if (signal.aborted) {\n return;\n }\n await this.sendPendingFiles(final, maxSends, signal);\n });\n } catch (error) {\n logDebug(`Error in Apitally export cycle: ${String(error)}`);\n }\n }\n\n // During an outage, stopping on failure limits each cycle to one probe POST.\n private async sendPendingFiles(\n final: boolean,\n maxSends: number,\n signal: AbortSignal,\n ): Promise<void> {\n let sent = 0;\n for (const file of this.spool.pendingFiles()) {\n if (signal.aborted || sent >= maxSends) {\n return;\n }\n if (!final && sent > 0) {\n const pauseMillis = this.interSendPauseMillis();\n if (pauseMillis > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, pauseMillis));\n }\n if (signal.aborted) {\n return;\n }\n }\n const expirationDeletion = this.spool.deleteIfExpired(file);\n if (expirationDeletion) {\n await expirationDeletion;\n continue;\n }\n sent += 1;\n if (!(await this.sendFile(file, signal))) {\n return;\n }\n }\n }\n\n // Sends the file's stored bytes verbatim. Returns false on a retryable\n // failure, which ends the cycle and keeps the file queued.\n private async sendFile(file: SpoolFile, signal: AbortSignal): Promise<boolean> {\n file.markAttempt();\n const url = `${this.otlpEndpoint}/v1/${file.signal}`;\n let body: Buffer;\n try {\n body = await file.readStoredBytes();\n } catch {\n logWarning(`Error reading buffered ${file.signal}, dropping the file`);\n await this.spool.deleteFile(file);\n return true;\n }\n if (signal.aborted) {\n return false;\n }\n let response: Response;\n try {\n try {\n response = await this.postFile(url, body, signal);\n } catch (error) {\n if (signal.aborted || isAbortError(error) || isTimeoutError(error)) {\n throw error;\n }\n // The server may close an idle keep-alive connection mid-request; retry once.\n response = await this.postFile(url, body, signal);\n }\n } catch (error) {\n if (!signal.aborted) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed (${String(error)}), will retry`,\n );\n }\n return false;\n }\n await response.arrayBuffer().catch(() => undefined);\n if (signal.aborted) {\n return false;\n }\n this.applyIntervalHeader(response);\n if (response.status >= 200 && response.status < 300) {\n await this.spool.deleteFile(file);\n return true;\n }\n if (RETRYABLE_STATUS_CODES.has(response.status) || response.status >= 500) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed with HTTP ${response.status}, will retry`,\n );\n return false;\n }\n if (!this.warnedStatuses.has(response.status)) {\n this.warnedStatuses.add(response.status);\n logWarning(\n `Apitally rejected buffered ${file.signal} with HTTP ${response.status}; they were dropped`,\n );\n }\n await this.spool.deleteFile(file);\n return true;\n }\n\n private postFile(url: string, body: Buffer, signal: AbortSignal): Promise<Response> {\n const init: RequestInit = {\n method: \"POST\",\n headers: this.headers,\n body: body as unknown as BodyInit,\n signal: AbortSignal.any([signal, AbortSignal.timeout(this.requestTimeoutMillis)]),\n };\n if (!this.useProxy) {\n return fetch(url, init);\n }\n const transport = this.getProxyTransport();\n return transport.fetch(url, {\n ...init,\n dispatcher: transport.dispatcher,\n } as RequestInit);\n }\n\n private getProxyTransport(): ProxyTransport {\n if (!this.proxyTransport) {\n // undici's fetch is required because Node's bundled fetch does not accept\n // an undici dispatcher on every supported Node version.\n const undici = createRequire(import.meta.url)(\"undici\") as {\n fetch: ProxyTransport[\"fetch\"];\n EnvHttpProxyAgent: new () => ProxyTransport[\"dispatcher\"];\n };\n this.proxyTransport = {\n fetch: undici.fetch,\n dispatcher: new undici.EnvHttpProxyAgent(),\n };\n }\n return this.proxyTransport;\n }\n\n private applyIntervalHeader(response: Response): void {\n const value = response.headers.get(EXPORT_INTERVAL_HEADER);\n if (!value) {\n return;\n }\n const seconds = Number.parseInt(value, 10);\n if (Number.isNaN(seconds)) {\n return;\n }\n this.intervalMillis =\n Math.min(Math.max(seconds, MIN_EXPORT_INTERVAL_SECONDS), MAX_EXPORT_INTERVAL_SECONDS) * 1000;\n }\n\n private scheduleNextExportCycle(delayMillis: number): void {\n this.timer = setTimeout(() => {\n void this.runCycle()\n .catch((error) => logDebug(`Error in Apitally export cycle: ${String(error)}`))\n .finally(() => {\n if (this.started) {\n // Jitter desynchronizes deployments whose processes started together.\n this.scheduleNextExportCycle(this.intervalMillis * (0.9 + Math.random() * 0.2));\n }\n });\n }, delayMillis);\n this.timer.unref();\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n return error instanceof Error && error.name === \"TimeoutError\";\n}\n"],"mappings":"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"}
|
package/dist/exportWorker.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createRequire } from "node:module";
|
|
|
4
4
|
import { context } from "@opentelemetry/api";
|
|
5
5
|
import { suppressTracing } from "@opentelemetry/core";
|
|
6
6
|
//#region src/exportWorker.ts
|
|
7
|
-
const
|
|
7
|
+
const MAX_BACKLOG_SENDS_PER_CYCLE = 10;
|
|
8
8
|
const DEFAULT_EXPORT_INTERVAL_MILLIS = 15e3;
|
|
9
9
|
const EXPORT_INTERVAL_HEADER = "Apitally-Export-Interval";
|
|
10
10
|
const INITIAL_EXPORT_DELAY_MILLIS = 2e3;
|
|
@@ -107,22 +107,23 @@ var ExportWorker = class {
|
|
|
107
107
|
await callback();
|
|
108
108
|
}
|
|
109
109
|
if (signal.aborted) return;
|
|
110
|
+
let maxSends = Number.POSITIVE_INFINITY;
|
|
110
111
|
if (final) await this.spool.closeCurrentFiles();
|
|
111
112
|
else {
|
|
112
|
-
await this.spool.rotateForExport();
|
|
113
|
+
maxSends = MAX_BACKLOG_SENDS_PER_CYCLE + await this.spool.rotateForExport();
|
|
113
114
|
this.spool.touchFiles();
|
|
114
115
|
}
|
|
115
116
|
if (signal.aborted) return;
|
|
116
|
-
await this.sendPendingFiles(final, signal);
|
|
117
|
+
await this.sendPendingFiles(final, maxSends, signal);
|
|
117
118
|
});
|
|
118
119
|
} catch (error) {
|
|
119
120
|
logDebug(`Error in Apitally export cycle: ${String(error)}`);
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
|
-
async sendPendingFiles(final, signal) {
|
|
123
|
+
async sendPendingFiles(final, maxSends, signal) {
|
|
123
124
|
let sent = 0;
|
|
124
125
|
for (const file of this.spool.pendingFiles()) {
|
|
125
|
-
if (signal.aborted ||
|
|
126
|
+
if (signal.aborted || sent >= maxSends) return;
|
|
126
127
|
if (!final && sent > 0) {
|
|
127
128
|
const pauseMillis = this.interSendPauseMillis();
|
|
128
129
|
if (pauseMillis > 0) await new Promise((resolve) => setTimeout(resolve, pauseMillis));
|
package/dist/exportWorker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exportWorker.js","names":[],"sources":["../src/exportWorker.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { context } from \"@opentelemetry/api\";\nimport { suppressTracing } from \"@opentelemetry/core\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport type { Spool, SpoolFile } from \"./spool.js\";\n\nconst MAX_SENDS_PER_CYCLE = 10;\nconst DEFAULT_EXPORT_INTERVAL_MILLIS = 15_000;\nconst EXPORT_INTERVAL_HEADER = \"Apitally-Export-Interval\";\nconst INITIAL_EXPORT_DELAY_MILLIS = 2_000;\nconst MIN_EXPORT_INTERVAL_SECONDS = 5;\nconst MAX_EXPORT_INTERVAL_SECONDS = 60;\nconst REQUEST_TIMEOUT_MILLIS = 10_000;\nconst RETRYABLE_STATUS_CODES = new Set([408, 429]);\n\nexport type FlushCallback = () => Promise<void> | void;\n\nexport interface ExportWorkerOptions {\n spool: Spool;\n otlpEndpoint: string;\n writeToken: string;\n env: string;\n initialExportDelayMillis?: number;\n requestTimeoutMillis?: number;\n interSendPauseMillis?: () => number;\n}\n\ninterface ProxyTransport {\n fetch: (url: string, init: RequestInit) => Promise<Response>;\n dispatcher: { close(): Promise<void> };\n}\n\n// Send cycles are serialized; a flush request joins the active cycle instead of\n// posting files twice.\nexport class ExportWorker {\n // Invoked at the start of every cycle, before files are rotated and sent.\n readonly flushCallbacks: FlushCallback[] = [];\n intervalMillis = DEFAULT_EXPORT_INTERVAL_MILLIS;\n private readonly spool: Spool;\n private readonly otlpEndpoint: string;\n private readonly headers: Record<string, string>;\n private readonly initialExportDelayMillis: number;\n private readonly requestTimeoutMillis: number;\n private readonly interSendPauseMillis: () => number;\n private readonly useProxy: boolean;\n private proxyTransport?: ProxyTransport;\n private readonly warnedStatuses = new Set<number>();\n private currentCycle?: Promise<void>;\n private currentCycleController?: AbortController;\n private timer?: NodeJS.Timeout;\n private started = false;\n\n constructor(options: ExportWorkerOptions) {\n this.spool = options.spool;\n this.otlpEndpoint = options.otlpEndpoint;\n this.headers = {\n Authorization: `Bearer ${options.writeToken}`,\n \"Apitally-Env\": options.env,\n \"Content-Type\": \"application/x-protobuf\",\n \"Content-Encoding\": \"gzip\",\n \"User-Agent\": `apitally-js/${getDistroVersion()}`,\n };\n this.initialExportDelayMillis = options.initialExportDelayMillis ?? INITIAL_EXPORT_DELAY_MILLIS;\n this.requestTimeoutMillis = options.requestTimeoutMillis ?? REQUEST_TIMEOUT_MILLIS;\n this.interSendPauseMillis = options.interSendPauseMillis ?? (() => 100 + Math.random() * 400);\n const env = process.env;\n this.useProxy = Boolean(env.HTTP_PROXY || env.http_proxy || env.HTTPS_PROXY || env.https_proxy);\n }\n\n start(): void {\n if (this.started) {\n return;\n }\n this.started = true;\n this.scheduleNextExportCycle(this.initialExportDelayMillis);\n }\n\n async stop(): Promise<void> {\n this.started = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n await this.waitForIdle();\n if (this.proxyTransport) {\n await this.proxyTransport.dispatcher.close();\n }\n }\n\n // Runs one send cycle, or joins the cycle already running (no file posts twice).\n runCycle(): Promise<void> {\n return this.currentCycle ?? this.chainCycle(false);\n }\n\n // Closes current files and sends everything pending, optionally within one deadline.\n async finalDrain(timeoutMillis?: number): Promise<void> {\n if (timeoutMillis === undefined) {\n await this.chainCycle(true);\n return;\n }\n\n const deadlineController = new AbortController();\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(() => {\n deadlineController.abort();\n resolve();\n }, timeoutMillis);\n });\n\n this.currentCycleController?.abort();\n const cycle = this.chainCycle(true, deadlineController.signal);\n try {\n await Promise.race([cycle, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n }\n\n waitForIdle(): Promise<void> {\n return this.currentCycle ?? Promise.resolve();\n }\n\n private chainCycle(final: boolean, deadlineSignal?: AbortSignal): Promise<void> {\n const previous = this.currentCycle ?? Promise.resolve();\n const controller = new AbortController();\n const signal = deadlineSignal\n ? AbortSignal.any([controller.signal, deadlineSignal])\n : controller.signal;\n const cycle = previous\n .then(() => this.executeCycle(final, signal))\n .finally(() => {\n if (this.currentCycle === cycle) {\n this.currentCycle = undefined;\n this.currentCycleController = undefined;\n }\n });\n this.currentCycle = cycle;\n this.currentCycleController = controller;\n return cycle;\n }\n\n private async executeCycle(final: boolean, signal: AbortSignal): Promise<void> {\n try {\n // Suppress instrumentation so worker flushes and POSTs generate no telemetry.\n await context.with(suppressTracing(context.active()), async () => {\n for (const callback of this.flushCallbacks) {\n if (signal.aborted) {\n return;\n }\n await callback();\n }\n if (signal.aborted) {\n return;\n }\n if (final) {\n await this.spool.closeCurrentFiles();\n } else {\n await this.spool.rotateForExport();\n this.spool.touchFiles();\n }\n if (signal.aborted) {\n return;\n }\n await this.sendPendingFiles(final, signal);\n });\n } catch (error) {\n logDebug(`Error in Apitally export cycle: ${String(error)}`);\n }\n }\n\n // During an outage, stopping on failure limits each cycle to one probe POST.\n private async sendPendingFiles(final: boolean, signal: AbortSignal): Promise<void> {\n let sent = 0;\n for (const file of this.spool.pendingFiles()) {\n if (signal.aborted || (!final && sent >= MAX_SENDS_PER_CYCLE)) {\n return;\n }\n if (!final && sent > 0) {\n const pauseMillis = this.interSendPauseMillis();\n if (pauseMillis > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, pauseMillis));\n }\n if (signal.aborted) {\n return;\n }\n }\n const expirationDeletion = this.spool.deleteIfExpired(file);\n if (expirationDeletion) {\n await expirationDeletion;\n continue;\n }\n sent += 1;\n if (!(await this.sendFile(file, signal))) {\n return;\n }\n }\n }\n\n // Sends the file's stored bytes verbatim. Returns false on a retryable\n // failure, which ends the cycle and keeps the file queued.\n private async sendFile(file: SpoolFile, signal: AbortSignal): Promise<boolean> {\n file.markAttempt();\n const url = `${this.otlpEndpoint}/v1/${file.signal}`;\n let body: Buffer;\n try {\n body = await file.readStoredBytes();\n } catch {\n logWarning(`Error reading buffered ${file.signal}, dropping the file`);\n await this.spool.deleteFile(file);\n return true;\n }\n if (signal.aborted) {\n return false;\n }\n let response: Response;\n try {\n try {\n response = await this.postFile(url, body, signal);\n } catch (error) {\n if (signal.aborted || isAbortError(error) || isTimeoutError(error)) {\n throw error;\n }\n // The server may close an idle keep-alive connection mid-request; retry once.\n response = await this.postFile(url, body, signal);\n }\n } catch (error) {\n if (!signal.aborted) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed (${String(error)}), will retry`,\n );\n }\n return false;\n }\n await response.arrayBuffer().catch(() => undefined);\n if (signal.aborted) {\n return false;\n }\n this.applyIntervalHeader(response);\n if (response.status >= 200 && response.status < 300) {\n await this.spool.deleteFile(file);\n return true;\n }\n if (RETRYABLE_STATUS_CODES.has(response.status) || response.status >= 500) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed with HTTP ${response.status}, will retry`,\n );\n return false;\n }\n if (!this.warnedStatuses.has(response.status)) {\n this.warnedStatuses.add(response.status);\n logWarning(\n `Apitally rejected buffered ${file.signal} with HTTP ${response.status}; they were dropped`,\n );\n }\n await this.spool.deleteFile(file);\n return true;\n }\n\n private postFile(url: string, body: Buffer, signal: AbortSignal): Promise<Response> {\n const init: RequestInit = {\n method: \"POST\",\n headers: this.headers,\n body: body as unknown as BodyInit,\n signal: AbortSignal.any([signal, AbortSignal.timeout(this.requestTimeoutMillis)]),\n };\n if (!this.useProxy) {\n return fetch(url, init);\n }\n const transport = this.getProxyTransport();\n return transport.fetch(url, {\n ...init,\n dispatcher: transport.dispatcher,\n } as RequestInit);\n }\n\n private getProxyTransport(): ProxyTransport {\n if (!this.proxyTransport) {\n // undici's fetch is required because Node's bundled fetch does not accept\n // an undici dispatcher on every supported Node version.\n const undici = createRequire(import.meta.url)(\"undici\") as {\n fetch: ProxyTransport[\"fetch\"];\n EnvHttpProxyAgent: new () => ProxyTransport[\"dispatcher\"];\n };\n this.proxyTransport = {\n fetch: undici.fetch,\n dispatcher: new undici.EnvHttpProxyAgent(),\n };\n }\n return this.proxyTransport;\n }\n\n private applyIntervalHeader(response: Response): void {\n const value = response.headers.get(EXPORT_INTERVAL_HEADER);\n if (!value) {\n return;\n }\n const seconds = Number.parseInt(value, 10);\n if (Number.isNaN(seconds)) {\n return;\n }\n this.intervalMillis =\n Math.min(Math.max(seconds, MIN_EXPORT_INTERVAL_SECONDS), MAX_EXPORT_INTERVAL_SECONDS) * 1000;\n }\n\n private scheduleNextExportCycle(delayMillis: number): void {\n this.timer = setTimeout(() => {\n void this.runCycle()\n .catch((error) => logDebug(`Error in Apitally export cycle: ${String(error)}`))\n .finally(() => {\n if (this.started) {\n // Jitter desynchronizes deployments whose processes started together.\n this.scheduleNextExportCycle(this.intervalMillis * (0.9 + Math.random() * 0.2));\n }\n });\n }, delayMillis);\n this.timer.unref();\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n return error instanceof Error && error.name === \"TimeoutError\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"exportWorker.js","names":[],"sources":["../src/exportWorker.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { context } from \"@opentelemetry/api\";\nimport { suppressTracing } from \"@opentelemetry/core\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\nimport type { Spool, SpoolFile } from \"./spool.js\";\n\nconst MAX_BACKLOG_SENDS_PER_CYCLE = 10;\nconst DEFAULT_EXPORT_INTERVAL_MILLIS = 15_000;\nconst EXPORT_INTERVAL_HEADER = \"Apitally-Export-Interval\";\nconst INITIAL_EXPORT_DELAY_MILLIS = 2_000;\nconst MIN_EXPORT_INTERVAL_SECONDS = 5;\nconst MAX_EXPORT_INTERVAL_SECONDS = 60;\nconst REQUEST_TIMEOUT_MILLIS = 10_000;\nconst RETRYABLE_STATUS_CODES = new Set([408, 429]);\n\nexport type FlushCallback = () => Promise<void> | void;\n\nexport interface ExportWorkerOptions {\n spool: Spool;\n otlpEndpoint: string;\n writeToken: string;\n env: string;\n initialExportDelayMillis?: number;\n requestTimeoutMillis?: number;\n interSendPauseMillis?: () => number;\n}\n\ninterface ProxyTransport {\n fetch: (url: string, init: RequestInit) => Promise<Response>;\n dispatcher: { close(): Promise<void> };\n}\n\n// Send cycles are serialized; a flush request joins the active cycle instead of\n// posting files twice.\nexport class ExportWorker {\n // Invoked at the start of every cycle, before files are rotated and sent.\n readonly flushCallbacks: FlushCallback[] = [];\n intervalMillis = DEFAULT_EXPORT_INTERVAL_MILLIS;\n private readonly spool: Spool;\n private readonly otlpEndpoint: string;\n private readonly headers: Record<string, string>;\n private readonly initialExportDelayMillis: number;\n private readonly requestTimeoutMillis: number;\n private readonly interSendPauseMillis: () => number;\n private readonly useProxy: boolean;\n private proxyTransport?: ProxyTransport;\n private readonly warnedStatuses = new Set<number>();\n private currentCycle?: Promise<void>;\n private currentCycleController?: AbortController;\n private timer?: NodeJS.Timeout;\n private started = false;\n\n constructor(options: ExportWorkerOptions) {\n this.spool = options.spool;\n this.otlpEndpoint = options.otlpEndpoint;\n this.headers = {\n Authorization: `Bearer ${options.writeToken}`,\n \"Apitally-Env\": options.env,\n \"Content-Type\": \"application/x-protobuf\",\n \"Content-Encoding\": \"gzip\",\n \"User-Agent\": `apitally-js/${getDistroVersion()}`,\n };\n this.initialExportDelayMillis = options.initialExportDelayMillis ?? INITIAL_EXPORT_DELAY_MILLIS;\n this.requestTimeoutMillis = options.requestTimeoutMillis ?? REQUEST_TIMEOUT_MILLIS;\n this.interSendPauseMillis = options.interSendPauseMillis ?? (() => 100 + Math.random() * 400);\n const env = process.env;\n this.useProxy = Boolean(env.HTTP_PROXY || env.http_proxy || env.HTTPS_PROXY || env.https_proxy);\n }\n\n start(): void {\n if (this.started) {\n return;\n }\n this.started = true;\n this.scheduleNextExportCycle(this.initialExportDelayMillis);\n }\n\n async stop(): Promise<void> {\n this.started = false;\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n await this.waitForIdle();\n if (this.proxyTransport) {\n await this.proxyTransport.dispatcher.close();\n }\n }\n\n // Runs one send cycle, or joins the cycle already running (no file posts twice).\n runCycle(): Promise<void> {\n return this.currentCycle ?? this.chainCycle(false);\n }\n\n // Closes current files and sends everything pending, optionally within one deadline.\n async finalDrain(timeoutMillis?: number): Promise<void> {\n if (timeoutMillis === undefined) {\n await this.chainCycle(true);\n return;\n }\n\n const deadlineController = new AbortController();\n let deadlineTimer: NodeJS.Timeout | undefined;\n const deadlineReached = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(() => {\n deadlineController.abort();\n resolve();\n }, timeoutMillis);\n });\n\n this.currentCycleController?.abort();\n const cycle = this.chainCycle(true, deadlineController.signal);\n try {\n await Promise.race([cycle, deadlineReached]);\n } finally {\n if (deadlineTimer) {\n clearTimeout(deadlineTimer);\n }\n }\n }\n\n waitForIdle(): Promise<void> {\n return this.currentCycle ?? Promise.resolve();\n }\n\n private chainCycle(final: boolean, deadlineSignal?: AbortSignal): Promise<void> {\n const previous = this.currentCycle ?? Promise.resolve();\n const controller = new AbortController();\n const signal = deadlineSignal\n ? AbortSignal.any([controller.signal, deadlineSignal])\n : controller.signal;\n const cycle = previous\n .then(() => this.executeCycle(final, signal))\n .finally(() => {\n if (this.currentCycle === cycle) {\n this.currentCycle = undefined;\n this.currentCycleController = undefined;\n }\n });\n this.currentCycle = cycle;\n this.currentCycleController = controller;\n return cycle;\n }\n\n private async executeCycle(final: boolean, signal: AbortSignal): Promise<void> {\n try {\n // Suppress instrumentation so worker flushes and POSTs generate no telemetry.\n await context.with(suppressTracing(context.active()), async () => {\n for (const callback of this.flushCallbacks) {\n if (signal.aborted) {\n return;\n }\n await callback();\n }\n if (signal.aborted) {\n return;\n }\n let maxSends = Number.POSITIVE_INFINITY;\n if (final) {\n await this.spool.closeCurrentFiles();\n } else {\n maxSends = MAX_BACKLOG_SENDS_PER_CYCLE + (await this.spool.rotateForExport());\n this.spool.touchFiles();\n }\n if (signal.aborted) {\n return;\n }\n await this.sendPendingFiles(final, maxSends, signal);\n });\n } catch (error) {\n logDebug(`Error in Apitally export cycle: ${String(error)}`);\n }\n }\n\n // During an outage, stopping on failure limits each cycle to one probe POST.\n private async sendPendingFiles(\n final: boolean,\n maxSends: number,\n signal: AbortSignal,\n ): Promise<void> {\n let sent = 0;\n for (const file of this.spool.pendingFiles()) {\n if (signal.aborted || sent >= maxSends) {\n return;\n }\n if (!final && sent > 0) {\n const pauseMillis = this.interSendPauseMillis();\n if (pauseMillis > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, pauseMillis));\n }\n if (signal.aborted) {\n return;\n }\n }\n const expirationDeletion = this.spool.deleteIfExpired(file);\n if (expirationDeletion) {\n await expirationDeletion;\n continue;\n }\n sent += 1;\n if (!(await this.sendFile(file, signal))) {\n return;\n }\n }\n }\n\n // Sends the file's stored bytes verbatim. Returns false on a retryable\n // failure, which ends the cycle and keeps the file queued.\n private async sendFile(file: SpoolFile, signal: AbortSignal): Promise<boolean> {\n file.markAttempt();\n const url = `${this.otlpEndpoint}/v1/${file.signal}`;\n let body: Buffer;\n try {\n body = await file.readStoredBytes();\n } catch {\n logWarning(`Error reading buffered ${file.signal}, dropping the file`);\n await this.spool.deleteFile(file);\n return true;\n }\n if (signal.aborted) {\n return false;\n }\n let response: Response;\n try {\n try {\n response = await this.postFile(url, body, signal);\n } catch (error) {\n if (signal.aborted || isAbortError(error) || isTimeoutError(error)) {\n throw error;\n }\n // The server may close an idle keep-alive connection mid-request; retry once.\n response = await this.postFile(url, body, signal);\n }\n } catch (error) {\n if (!signal.aborted) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed (${String(error)}), will retry`,\n );\n }\n return false;\n }\n await response.arrayBuffer().catch(() => undefined);\n if (signal.aborted) {\n return false;\n }\n this.applyIntervalHeader(response);\n if (response.status >= 200 && response.status < 300) {\n await this.spool.deleteFile(file);\n return true;\n }\n if (RETRYABLE_STATUS_CODES.has(response.status) || response.status >= 500) {\n logDebug(\n `Sending buffered ${file.signal} to Apitally failed with HTTP ${response.status}, will retry`,\n );\n return false;\n }\n if (!this.warnedStatuses.has(response.status)) {\n this.warnedStatuses.add(response.status);\n logWarning(\n `Apitally rejected buffered ${file.signal} with HTTP ${response.status}; they were dropped`,\n );\n }\n await this.spool.deleteFile(file);\n return true;\n }\n\n private postFile(url: string, body: Buffer, signal: AbortSignal): Promise<Response> {\n const init: RequestInit = {\n method: \"POST\",\n headers: this.headers,\n body: body as unknown as BodyInit,\n signal: AbortSignal.any([signal, AbortSignal.timeout(this.requestTimeoutMillis)]),\n };\n if (!this.useProxy) {\n return fetch(url, init);\n }\n const transport = this.getProxyTransport();\n return transport.fetch(url, {\n ...init,\n dispatcher: transport.dispatcher,\n } as RequestInit);\n }\n\n private getProxyTransport(): ProxyTransport {\n if (!this.proxyTransport) {\n // undici's fetch is required because Node's bundled fetch does not accept\n // an undici dispatcher on every supported Node version.\n const undici = createRequire(import.meta.url)(\"undici\") as {\n fetch: ProxyTransport[\"fetch\"];\n EnvHttpProxyAgent: new () => ProxyTransport[\"dispatcher\"];\n };\n this.proxyTransport = {\n fetch: undici.fetch,\n dispatcher: new undici.EnvHttpProxyAgent(),\n };\n }\n return this.proxyTransport;\n }\n\n private applyIntervalHeader(response: Response): void {\n const value = response.headers.get(EXPORT_INTERVAL_HEADER);\n if (!value) {\n return;\n }\n const seconds = Number.parseInt(value, 10);\n if (Number.isNaN(seconds)) {\n return;\n }\n this.intervalMillis =\n Math.min(Math.max(seconds, MIN_EXPORT_INTERVAL_SECONDS), MAX_EXPORT_INTERVAL_SECONDS) * 1000;\n }\n\n private scheduleNextExportCycle(delayMillis: number): void {\n this.timer = setTimeout(() => {\n void this.runCycle()\n .catch((error) => logDebug(`Error in Apitally export cycle: ${String(error)}`))\n .finally(() => {\n if (this.started) {\n // Jitter desynchronizes deployments whose processes started together.\n this.scheduleNextExportCycle(this.intervalMillis * (0.9 + Math.random() * 0.2));\n }\n });\n }, delayMillis);\n this.timer.unref();\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n return error instanceof Error && error.name === \"TimeoutError\";\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_requestAttributes = require("./requestAttributes.cjs");
|
|
3
3
|
const require_exceptions = require("./exceptions.cjs");
|
|
4
|
-
const require_consumer = require("./consumer.cjs");
|
|
5
4
|
const require_spanProcessor = require("./spanProcessor.cjs");
|
|
6
5
|
const require_activation = require("./activation.cjs");
|
|
6
|
+
const require_consumers = require("./consumers.cjs");
|
|
7
7
|
const require_adonisjs_configure = require("./adonisjs/configure.cjs");
|
|
8
8
|
const require_elysia_middleware = require("./elysia/middleware.cjs");
|
|
9
9
|
const require_express_index = require("./express/index.cjs");
|
|
@@ -61,7 +61,7 @@ exports.ApitallySpanProcessor = require_spanProcessor.ApitallySpanProcessor;
|
|
|
61
61
|
exports.captureException = require_exceptions.captureException;
|
|
62
62
|
exports.configure = configure;
|
|
63
63
|
exports.instrument = require_tracing.instrument;
|
|
64
|
-
exports.setConsumer =
|
|
64
|
+
exports.setConsumer = require_consumers.setConsumer;
|
|
65
65
|
exports.setRequestAttribute = require_requestAttributes.setRequestAttribute;
|
|
66
66
|
exports.shutdown = require_activation.shutdown;
|
|
67
67
|
exports.span = require_tracing.span;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["configureAdonis"],"sources":["../src/index.ts"],"sourcesContent":["import { configure as configureAdonis } from \"./adonisjs/configure.js\";\nimport type { ApitallyOptions } from \"./config.js\";\nimport { installElysiaIntegration } from \"./elysia/middleware.js\";\nimport { useApitally as useApitallyExpress } from \"./express/index.js\";\nimport { useApitally as useApitallyFastify } from \"./fastify/index.js\";\nimport { useApitally as useApitallyH3 } from \"./h3/index.js\";\nimport { useApitally as useApitallyHapi } from \"./hapi/index.js\";\nimport { useApitally as useApitallyHono } from \"./hono/index.js\";\nimport { useApitally as useApitallyKoa } from \"./koa/index.js\";\n\nexport { shutdown } from \"./activation.js\";\nexport type {\n ApitallyOptions,\n BodyMaskingCallback,\n LogRecordMaskingCallback,\n SamplingCallback,\n} from \"./config.js\";\nexport type { ApitallyConsumer } from \"./
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["configureAdonis"],"sources":["../src/index.ts"],"sourcesContent":["import { configure as configureAdonis } from \"./adonisjs/configure.js\";\nimport type { ApitallyOptions } from \"./config.js\";\nimport { installElysiaIntegration } from \"./elysia/middleware.js\";\nimport { useApitally as useApitallyExpress } from \"./express/index.js\";\nimport { useApitally as useApitallyFastify } from \"./fastify/index.js\";\nimport { useApitally as useApitallyH3 } from \"./h3/index.js\";\nimport { useApitally as useApitallyHapi } from \"./hapi/index.js\";\nimport { useApitally as useApitallyHono } from \"./hono/index.js\";\nimport { useApitally as useApitallyKoa } from \"./koa/index.js\";\n\nexport { shutdown } from \"./activation.js\";\nexport type {\n ApitallyOptions,\n BodyMaskingCallback,\n LogRecordMaskingCallback,\n SamplingCallback,\n} from \"./config.js\";\nexport type { ApitallyConsumer } from \"./consumers.js\";\nexport { setConsumer } from \"./consumers.js\";\nexport { captureException } from \"./exceptions.js\";\nexport { setRequestAttribute } from \"./requestAttributes.js\";\nexport { ApitallySpanProcessor } from \"./spanProcessor.js\";\nexport { instrument, span } from \"./tracing.js\";\n\n/** AdonisJS Ace configuration hook. */\nexport function configure(command: unknown): Promise<void> {\n return configureAdonis(command as Parameters<typeof configureAdonis>[0]);\n}\n\nexport function useApitally(app: unknown, options?: ApitallyOptions): void {\n if (isExpressApp(app)) {\n useApitallyExpress(app, options);\n } else if (isFastifyApp(app)) {\n useApitallyFastify(app, options);\n } else if (isHapiServer(app)) {\n useApitallyHapi(app, options);\n } else if (isH3App(app)) {\n useApitallyH3(app, options);\n } else if (isElysiaApp(app)) {\n installElysiaIntegration(app, options);\n } else if (isHonoApp(app)) {\n useApitallyHono(app, options);\n } else if (isKoaApp(app)) {\n useApitallyKoa(app, options);\n } else {\n throw new TypeError(\n \"useApitally() could not detect a supported framework from the app argument\",\n );\n }\n}\n\nfunction isExpressApp(app: unknown): app is Parameters<typeof useApitallyExpress>[0] {\n const candidate = app as {\n use?: unknown;\n handle?: unknown;\n };\n return (\n typeof app === \"function\" &&\n typeof candidate.use === \"function\" &&\n typeof candidate.handle === \"function\"\n );\n}\n\nfunction isFastifyApp(app: unknown): app is Parameters<typeof useApitallyFastify>[0] {\n const candidate = app as {\n version?: unknown;\n addHook?: unknown;\n register?: unknown;\n route?: unknown;\n ready?: unknown;\n close?: unknown;\n };\n return (\n typeof app === \"object\" &&\n app !== null &&\n typeof candidate.version === \"string\" &&\n typeof candidate.addHook === \"function\" &&\n typeof candidate.register === \"function\" &&\n typeof candidate.route === \"function\" &&\n typeof candidate.ready === \"function\" &&\n typeof candidate.close === \"function\"\n );\n}\n\nfunction isHapiServer(app: unknown): app is Parameters<typeof useApitallyHapi>[0] {\n const candidate = app as {\n ext?: unknown;\n register?: unknown;\n route?: unknown;\n table?: unknown;\n initialize?: unknown;\n start?: unknown;\n stop?: unknown;\n };\n return (\n typeof app === \"object\" &&\n app !== null &&\n typeof candidate.ext === \"function\" &&\n typeof candidate.register === \"function\" &&\n typeof candidate.route === \"function\" &&\n typeof candidate.table === \"function\" &&\n typeof candidate.initialize === \"function\" &&\n typeof candidate.start === \"function\" &&\n typeof candidate.stop === \"function\"\n );\n}\n\nfunction isH3App(app: unknown): app is Parameters<typeof useApitallyH3>[0] {\n const candidate = app as {\n constructor?: Record<string, unknown>;\n fetch?: unknown;\n request?: unknown;\n use?: unknown;\n };\n return (\n typeof app === \"object\" &&\n app !== null &&\n candidate.constructor?.[\"~h3\"] === true &&\n typeof candidate.fetch === \"function\" &&\n typeof candidate.request === \"function\" &&\n typeof candidate.use === \"function\"\n );\n}\n\nfunction isElysiaApp(app: unknown): app is Parameters<typeof installElysiaIntegration>[0] {\n const candidate = app as {\n routes?: unknown;\n handle?: unknown;\n route?: unknown;\n wrap?: unknown;\n onTransform?: unknown;\n onError?: unknown;\n };\n // Avoid reading app.fetch, as it compiles Elysia before the integration can be installed\n return (\n typeof app === \"object\" &&\n app !== null &&\n Array.isArray(candidate.routes) &&\n typeof candidate.handle === \"function\" &&\n typeof candidate.route === \"function\" &&\n typeof candidate.wrap === \"function\" &&\n typeof candidate.onTransform === \"function\" &&\n typeof candidate.onError === \"function\"\n );\n}\n\nfunction isKoaApp(app: unknown): app is Parameters<typeof useApitallyKoa>[0] {\n const candidate = app as {\n middleware?: unknown;\n use?: unknown;\n callback?: unknown;\n };\n return (\n typeof app === \"object\" &&\n app !== null &&\n Array.isArray(candidate.middleware) &&\n typeof candidate.use === \"function\" &&\n typeof candidate.callback === \"function\"\n );\n}\n\nfunction isHonoApp(app: unknown): app is Parameters<typeof useApitallyHono>[0] {\n const candidate = app as {\n routes?: unknown;\n fetch?: unknown;\n route?: unknown;\n };\n return (\n typeof app === \"object\" &&\n app !== null &&\n Array.isArray(candidate.routes) &&\n typeof candidate.fetch === \"function\" &&\n typeof candidate.route === \"function\"\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAyBA,SAAgB,UAAU,SAAiC;CACzD,OAAOA,2BAAAA,UAAgB,OAAgD;AACzE;AAEA,SAAgB,YAAY,KAAc,SAAiC;CACzE,IAAI,aAAa,GAAG,GAClB,sBAAA,YAAmB,KAAK,OAAO;MAC1B,IAAI,aAAa,GAAG,GACzB,sBAAA,YAAmB,KAAK,OAAO;MAC1B,IAAI,aAAa,GAAG,GACzB,mBAAA,YAAgB,KAAK,OAAO;MACvB,IAAI,QAAQ,GAAG,GACpB,iBAAA,YAAc,KAAK,OAAO;MACrB,IAAI,YAAY,GAAG,GACxB,0BAAA,yBAAyB,KAAK,OAAO;MAChC,IAAI,UAAU,GAAG,GACtB,mBAAA,YAAgB,KAAK,OAAO;MACvB,IAAI,SAAS,GAAG,GACrB,kBAAA,YAAe,KAAK,OAAO;MAE3B,MAAM,IAAI,UACR,4EACF;AAEJ;AAEA,SAAS,aAAa,KAA+D;CACnF,MAAM,YAAY;CAIlB,OACE,OAAO,QAAQ,cACf,OAAO,UAAU,QAAQ,cACzB,OAAO,UAAU,WAAW;AAEhC;AAEA,SAAS,aAAa,KAA+D;CACnF,MAAM,YAAY;CAQlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,OAAO,UAAU,YAAY,YAC7B,OAAO,UAAU,YAAY,cAC7B,OAAO,UAAU,aAAa,cAC9B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,UAAU;AAE/B;AAEA,SAAS,aAAa,KAA4D;CAChF,MAAM,YAAY;CASlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,OAAO,UAAU,QAAQ,cACzB,OAAO,UAAU,aAAa,cAC9B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,eAAe,cAChC,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,SAAS;AAE9B;AAEA,SAAS,QAAQ,KAA0D;CACzE,MAAM,YAAY;CAMlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,UAAU,cAAc,WAAW,QACnC,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,YAAY,cAC7B,OAAO,UAAU,QAAQ;AAE7B;AAEA,SAAS,YAAY,KAAqE;CACxF,MAAM,YAAY;CASlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,MAAM,QAAQ,UAAU,MAAM,KAC9B,OAAO,UAAU,WAAW,cAC5B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,SAAS,cAC1B,OAAO,UAAU,gBAAgB,cACjC,OAAO,UAAU,YAAY;AAEjC;AAEA,SAAS,SAAS,KAA2D;CAC3E,MAAM,YAAY;CAKlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,MAAM,QAAQ,UAAU,UAAU,KAClC,OAAO,UAAU,QAAQ,cACzB,OAAO,UAAU,aAAa;AAElC;AAEA,SAAS,UAAU,KAA4D;CAC7E,MAAM,YAAY;CAKlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,MAAM,QAAQ,UAAU,MAAM,KAC9B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,UAAU;AAE/B"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApitallyOptions, BodyMaskingCallback, LogRecordMaskingCallback, SamplingCallback } from "./config.cjs";
|
|
2
2
|
import { ApitallySpanProcessor } from "./spanProcessor.cjs";
|
|
3
3
|
import { shutdown } from "./activation.cjs";
|
|
4
|
-
import { ApitallyConsumer, setConsumer } from "./
|
|
4
|
+
import { ApitallyConsumer, setConsumer } from "./consumers.cjs";
|
|
5
5
|
import { captureException } from "./exceptions.cjs";
|
|
6
6
|
import { setRequestAttribute } from "./requestAttributes.cjs";
|
|
7
7
|
import { instrument, span } from "./tracing.cjs";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApitallyOptions, BodyMaskingCallback, LogRecordMaskingCallback, SamplingCallback } from "./config.js";
|
|
2
2
|
import { ApitallySpanProcessor } from "./spanProcessor.js";
|
|
3
3
|
import { shutdown } from "./activation.js";
|
|
4
|
-
import { ApitallyConsumer, setConsumer } from "./
|
|
4
|
+
import { ApitallyConsumer, setConsumer } from "./consumers.js";
|
|
5
5
|
import { captureException } from "./exceptions.js";
|
|
6
6
|
import { setRequestAttribute } from "./requestAttributes.js";
|
|
7
7
|
import { instrument, span } from "./tracing.js";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { setRequestAttribute } from "./requestAttributes.js";
|
|
2
2
|
import { captureException } from "./exceptions.js";
|
|
3
|
-
import { setConsumer } from "./consumer.js";
|
|
4
3
|
import { ApitallySpanProcessor } from "./spanProcessor.js";
|
|
5
4
|
import { shutdown } from "./activation.js";
|
|
5
|
+
import { setConsumer } from "./consumers.js";
|
|
6
6
|
import { configure as configure$1 } from "./adonisjs/configure.js";
|
|
7
7
|
import { installElysiaIntegration } from "./elysia/middleware.js";
|
|
8
8
|
import { useApitally as useApitally$1 } from "./express/index.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["configureAdonis"],"sources":["../src/index.ts"],"sourcesContent":["import { configure as configureAdonis } from \"./adonisjs/configure.js\";\nimport type { ApitallyOptions } from \"./config.js\";\nimport { installElysiaIntegration } from \"./elysia/middleware.js\";\nimport { useApitally as useApitallyExpress } from \"./express/index.js\";\nimport { useApitally as useApitallyFastify } from \"./fastify/index.js\";\nimport { useApitally as useApitallyH3 } from \"./h3/index.js\";\nimport { useApitally as useApitallyHapi } from \"./hapi/index.js\";\nimport { useApitally as useApitallyHono } from \"./hono/index.js\";\nimport { useApitally as useApitallyKoa } from \"./koa/index.js\";\n\nexport { shutdown } from \"./activation.js\";\nexport type {\n ApitallyOptions,\n BodyMaskingCallback,\n LogRecordMaskingCallback,\n SamplingCallback,\n} from \"./config.js\";\nexport type { ApitallyConsumer } from \"./
|
|
1
|
+
{"version":3,"file":"index.js","names":["configureAdonis"],"sources":["../src/index.ts"],"sourcesContent":["import { configure as configureAdonis } from \"./adonisjs/configure.js\";\nimport type { ApitallyOptions } from \"./config.js\";\nimport { installElysiaIntegration } from \"./elysia/middleware.js\";\nimport { useApitally as useApitallyExpress } from \"./express/index.js\";\nimport { useApitally as useApitallyFastify } from \"./fastify/index.js\";\nimport { useApitally as useApitallyH3 } from \"./h3/index.js\";\nimport { useApitally as useApitallyHapi } from \"./hapi/index.js\";\nimport { useApitally as useApitallyHono } from \"./hono/index.js\";\nimport { useApitally as useApitallyKoa } from \"./koa/index.js\";\n\nexport { shutdown } from \"./activation.js\";\nexport type {\n ApitallyOptions,\n BodyMaskingCallback,\n LogRecordMaskingCallback,\n SamplingCallback,\n} from \"./config.js\";\nexport type { ApitallyConsumer } from \"./consumers.js\";\nexport { setConsumer } from \"./consumers.js\";\nexport { captureException } from \"./exceptions.js\";\nexport { setRequestAttribute } from \"./requestAttributes.js\";\nexport { ApitallySpanProcessor } from \"./spanProcessor.js\";\nexport { instrument, span } from \"./tracing.js\";\n\n/** AdonisJS Ace configuration hook. */\nexport function configure(command: unknown): Promise<void> {\n return configureAdonis(command as Parameters<typeof configureAdonis>[0]);\n}\n\nexport function useApitally(app: unknown, options?: ApitallyOptions): void {\n if (isExpressApp(app)) {\n useApitallyExpress(app, options);\n } else if (isFastifyApp(app)) {\n useApitallyFastify(app, options);\n } else if (isHapiServer(app)) {\n useApitallyHapi(app, options);\n } else if (isH3App(app)) {\n useApitallyH3(app, options);\n } else if (isElysiaApp(app)) {\n installElysiaIntegration(app, options);\n } else if (isHonoApp(app)) {\n useApitallyHono(app, options);\n } else if (isKoaApp(app)) {\n useApitallyKoa(app, options);\n } else {\n throw new TypeError(\n \"useApitally() could not detect a supported framework from the app argument\",\n );\n }\n}\n\nfunction isExpressApp(app: unknown): app is Parameters<typeof useApitallyExpress>[0] {\n const candidate = app as {\n use?: unknown;\n handle?: unknown;\n };\n return (\n typeof app === \"function\" &&\n typeof candidate.use === \"function\" &&\n typeof candidate.handle === \"function\"\n );\n}\n\nfunction isFastifyApp(app: unknown): app is Parameters<typeof useApitallyFastify>[0] {\n const candidate = app as {\n version?: unknown;\n addHook?: unknown;\n register?: unknown;\n route?: unknown;\n ready?: unknown;\n close?: unknown;\n };\n return (\n typeof app === \"object\" &&\n app !== null &&\n typeof candidate.version === \"string\" &&\n typeof candidate.addHook === \"function\" &&\n typeof candidate.register === \"function\" &&\n typeof candidate.route === \"function\" &&\n typeof candidate.ready === \"function\" &&\n typeof candidate.close === \"function\"\n );\n}\n\nfunction isHapiServer(app: unknown): app is Parameters<typeof useApitallyHapi>[0] {\n const candidate = app as {\n ext?: unknown;\n register?: unknown;\n route?: unknown;\n table?: unknown;\n initialize?: unknown;\n start?: unknown;\n stop?: unknown;\n };\n return (\n typeof app === \"object\" &&\n app !== null &&\n typeof candidate.ext === \"function\" &&\n typeof candidate.register === \"function\" &&\n typeof candidate.route === \"function\" &&\n typeof candidate.table === \"function\" &&\n typeof candidate.initialize === \"function\" &&\n typeof candidate.start === \"function\" &&\n typeof candidate.stop === \"function\"\n );\n}\n\nfunction isH3App(app: unknown): app is Parameters<typeof useApitallyH3>[0] {\n const candidate = app as {\n constructor?: Record<string, unknown>;\n fetch?: unknown;\n request?: unknown;\n use?: unknown;\n };\n return (\n typeof app === \"object\" &&\n app !== null &&\n candidate.constructor?.[\"~h3\"] === true &&\n typeof candidate.fetch === \"function\" &&\n typeof candidate.request === \"function\" &&\n typeof candidate.use === \"function\"\n );\n}\n\nfunction isElysiaApp(app: unknown): app is Parameters<typeof installElysiaIntegration>[0] {\n const candidate = app as {\n routes?: unknown;\n handle?: unknown;\n route?: unknown;\n wrap?: unknown;\n onTransform?: unknown;\n onError?: unknown;\n };\n // Avoid reading app.fetch, as it compiles Elysia before the integration can be installed\n return (\n typeof app === \"object\" &&\n app !== null &&\n Array.isArray(candidate.routes) &&\n typeof candidate.handle === \"function\" &&\n typeof candidate.route === \"function\" &&\n typeof candidate.wrap === \"function\" &&\n typeof candidate.onTransform === \"function\" &&\n typeof candidate.onError === \"function\"\n );\n}\n\nfunction isKoaApp(app: unknown): app is Parameters<typeof useApitallyKoa>[0] {\n const candidate = app as {\n middleware?: unknown;\n use?: unknown;\n callback?: unknown;\n };\n return (\n typeof app === \"object\" &&\n app !== null &&\n Array.isArray(candidate.middleware) &&\n typeof candidate.use === \"function\" &&\n typeof candidate.callback === \"function\"\n );\n}\n\nfunction isHonoApp(app: unknown): app is Parameters<typeof useApitallyHono>[0] {\n const candidate = app as {\n routes?: unknown;\n fetch?: unknown;\n route?: unknown;\n };\n return (\n typeof app === \"object\" &&\n app !== null &&\n Array.isArray(candidate.routes) &&\n typeof candidate.fetch === \"function\" &&\n typeof candidate.route === \"function\"\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,SAAgB,UAAU,SAAiC;CACzD,OAAOA,YAAgB,OAAgD;AACzE;AAEA,SAAgB,YAAY,KAAc,SAAiC;CACzE,IAAI,aAAa,GAAG,GAClB,cAAmB,KAAK,OAAO;MAC1B,IAAI,aAAa,GAAG,GACzB,cAAmB,KAAK,OAAO;MAC1B,IAAI,aAAa,GAAG,GACzB,cAAgB,KAAK,OAAO;MACvB,IAAI,QAAQ,GAAG,GACpB,cAAc,KAAK,OAAO;MACrB,IAAI,YAAY,GAAG,GACxB,yBAAyB,KAAK,OAAO;MAChC,IAAI,UAAU,GAAG,GACtB,cAAgB,KAAK,OAAO;MACvB,IAAI,SAAS,GAAG,GACrB,cAAe,KAAK,OAAO;MAE3B,MAAM,IAAI,UACR,4EACF;AAEJ;AAEA,SAAS,aAAa,KAA+D;CACnF,MAAM,YAAY;CAIlB,OACE,OAAO,QAAQ,cACf,OAAO,UAAU,QAAQ,cACzB,OAAO,UAAU,WAAW;AAEhC;AAEA,SAAS,aAAa,KAA+D;CACnF,MAAM,YAAY;CAQlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,OAAO,UAAU,YAAY,YAC7B,OAAO,UAAU,YAAY,cAC7B,OAAO,UAAU,aAAa,cAC9B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,UAAU;AAE/B;AAEA,SAAS,aAAa,KAA4D;CAChF,MAAM,YAAY;CASlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,OAAO,UAAU,QAAQ,cACzB,OAAO,UAAU,aAAa,cAC9B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,eAAe,cAChC,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,SAAS;AAE9B;AAEA,SAAS,QAAQ,KAA0D;CACzE,MAAM,YAAY;CAMlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,UAAU,cAAc,WAAW,QACnC,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,YAAY,cAC7B,OAAO,UAAU,QAAQ;AAE7B;AAEA,SAAS,YAAY,KAAqE;CACxF,MAAM,YAAY;CASlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,MAAM,QAAQ,UAAU,MAAM,KAC9B,OAAO,UAAU,WAAW,cAC5B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,SAAS,cAC1B,OAAO,UAAU,gBAAgB,cACjC,OAAO,UAAU,YAAY;AAEjC;AAEA,SAAS,SAAS,KAA2D;CAC3E,MAAM,YAAY;CAKlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,MAAM,QAAQ,UAAU,UAAU,KAClC,OAAO,UAAU,QAAQ,cACzB,OAAO,UAAU,aAAa;AAElC;AAEA,SAAS,UAAU,KAA4D;CAC7E,MAAM,YAAY;CAKlB,OACE,OAAO,QAAQ,YACf,QAAQ,QACR,MAAM,QAAQ,UAAU,MAAM,KAC9B,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,UAAU;AAE/B"}
|