autotel 5.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/dist/auto.cjs +1 -1
- package/dist/auto.js +1 -1
- package/dist/correlation-id.cjs +9 -9
- package/dist/correlation-id.js +1 -1
- package/dist/decorators.cjs +2 -2
- package/dist/decorators.cjs.map +1 -1
- package/dist/decorators.js +1 -1
- package/dist/drain-pipeline.cjs +10 -1
- package/dist/drain-pipeline.cjs.map +1 -1
- package/dist/drain-pipeline.d.cts +5 -0
- package/dist/drain-pipeline.d.cts.map +1 -1
- package/dist/drain-pipeline.d.ts +5 -0
- package/dist/drain-pipeline.d.ts.map +1 -1
- package/dist/drain-pipeline.js +10 -1
- package/dist/drain-pipeline.js.map +1 -1
- package/dist/event.cjs +4 -4
- package/dist/event.js +1 -1
- package/dist/{functional-Bit6noRu.js → functional-BnDUxnSl.js} +2 -4
- package/dist/{functional-Bit6noRu.js.map → functional-BnDUxnSl.js.map} +1 -1
- package/dist/{functional-Cj7PSItS.cjs → functional-DcLBuCPz.cjs} +12 -14
- package/dist/{functional-Cj7PSItS.cjs.map → functional-DcLBuCPz.cjs.map} +1 -1
- package/dist/functional.cjs +1 -1
- package/dist/functional.js +1 -1
- package/dist/http.cjs +2 -2
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +1 -1
- package/dist/index.cjs +33 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -147
- package/dist/index.js.map +1 -1
- package/dist/init-C3RApHKJ.cjs +3371 -0
- package/dist/init-C3RApHKJ.cjs.map +1 -0
- package/dist/{init-CTyqG9qR.d.ts → init-D6n2diBy.d.ts} +21 -2
- package/dist/init-D6n2diBy.d.ts.map +1 -0
- package/dist/init-R-gSEXkJ.js +3190 -0
- package/dist/init-R-gSEXkJ.js.map +1 -0
- package/dist/{init-a4yPDYqC.d.cts → init-XzXzrarP.d.cts} +21 -2
- package/dist/init-XzXzrarP.d.cts.map +1 -0
- package/dist/instrumentation.cjs +1 -1
- package/dist/instrumentation.js +1 -1
- package/dist/messaging.cjs +1 -1
- package/dist/messaging.js +1 -1
- package/dist/semantic-helpers.cjs +1 -1
- package/dist/semantic-helpers.js +1 -1
- package/dist/webhook.cjs +1 -1
- package/dist/webhook.js +1 -1
- package/dist/workflow-distributed.cjs +1 -1
- package/dist/workflow-distributed.js +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.js +1 -1
- package/dist/yaml-config.d.cts +1 -1
- package/dist/yaml-config.d.ts +1 -1
- package/package.json +2 -3
- package/dist/event-CwIpFdoY.cjs +0 -778
- package/dist/event-CwIpFdoY.cjs.map +0 -1
- package/dist/event-pLMF5b3d.js +0 -761
- package/dist/event-pLMF5b3d.js.map +0 -1
- package/dist/init-B2uvZReD.cjs +0 -1120
- package/dist/init-B2uvZReD.cjs.map +0 -1
- package/dist/init-CTyqG9qR.d.ts.map +0 -1
- package/dist/init-CdEWjhvl.js +0 -1043
- package/dist/init-CdEWjhvl.js.map +0 -1
- package/dist/init-a4yPDYqC.d.cts.map +0 -1
- package/dist/operation-context-CKBoA4Qy.js +0 -59
- package/dist/operation-context-CKBoA4Qy.js.map +0 -1
- package/dist/operation-context-hzKbTKnt.cjs +0 -72
- package/dist/operation-context-hzKbTKnt.cjs.map +0 -1
- package/dist/track-BBcxEJEC.js +0 -1110
- package/dist/track-BBcxEJEC.js.map +0 -1
- package/dist/track-BI6dmZ2X.cjs +0 -1207
- package/dist/track-BI6dmZ2X.cjs.map +0 -1
- package/skills/analyze-traces/SKILL.md +0 -209
- package/skills/autotel-core/SKILL.md +0 -166
- package/skills/autotel-events/SKILL.md +0 -84
- package/skills/autotel-frameworks/SKILL.md +0 -125
- package/skills/autotel-instrumentation/SKILL.md +0 -150
- package/skills/autotel-request-logging/SKILL.md +0 -99
- package/skills/autotel-structured-errors/SKILL.md +0 -125
- package/skills/build-audit-trails/SKILL.md +0 -460
- package/skills/build-audit-trails/references/audit-queries.md +0 -73
- package/skills/build-audit-trails/references/framework-wiring.md +0 -196
- package/skills/debug-missing-spans/SKILL.md +0 -250
- package/skills/migrate-to-autotel/SKILL.md +0 -269
- package/skills/review-otel-patterns/SKILL.md +0 -540
- package/skills/review-otel-patterns/references/code-review.md +0 -76
- package/skills/review-otel-patterns/references/processor-pipeline.md +0 -205
- package/skills/review-otel-patterns/references/structured-errors.md +0 -102
- package/skills/review-otel-patterns/references/wide-spans.md +0 -85
- package/skills/tune-sampling/SKILL.md +0 -290
package/README.md
CHANGED
|
@@ -1827,6 +1827,8 @@ export const checkout = withTracing({})((ctx) => async (order: Order) => {
|
|
|
1827
1827
|
log.set({
|
|
1828
1828
|
cart: { total_cents: order.totalCents, item_count: order.items.length },
|
|
1829
1829
|
});
|
|
1830
|
+
// Set snapshot severity without fabricating a log event or exception.
|
|
1831
|
+
log.setLevel('info');
|
|
1830
1832
|
|
|
1831
1833
|
try {
|
|
1832
1834
|
await processPayment(order);
|
|
@@ -1849,6 +1851,9 @@ export const checkout = withTracing({})((ctx) => async (order: Order) => {
|
|
|
1849
1851
|
You also get:
|
|
1850
1852
|
|
|
1851
1853
|
- `log.getContext()` to inspect the accumulated request context.
|
|
1854
|
+
- `log.setLevel('debug' | 'info' | 'warn' | 'error')` to set canonical snapshot
|
|
1855
|
+
severity explicitly. An explicit level wins over later inferred warn/error
|
|
1856
|
+
levels.
|
|
1852
1857
|
- `log.emitNow(overrides?)` to capture an immediate snapshot (emits a correlated log-style snapshot
|
|
1853
1858
|
and returns `{ timestamp, traceId, spanId, correlationId, context }`).
|
|
1854
1859
|
|
|
@@ -1862,7 +1867,15 @@ import { createDrainPipeline, init } from 'autotel';
|
|
|
1862
1867
|
|
|
1863
1868
|
const pipeline = createDrainPipeline({
|
|
1864
1869
|
batch: { size: 50, intervalMs: 2000 },
|
|
1865
|
-
retry: {
|
|
1870
|
+
retry: {
|
|
1871
|
+
maxAttempts: 3,
|
|
1872
|
+
backoff: 'exponential',
|
|
1873
|
+
// Do not retry permanent client errors. Keep 429 retryable if desired.
|
|
1874
|
+
shouldRetry: (error) =>
|
|
1875
|
+
!(error instanceof HttpError) ||
|
|
1876
|
+
error.status === 429 ||
|
|
1877
|
+
error.status >= 500,
|
|
1878
|
+
},
|
|
1866
1879
|
maxBufferSize: 1000,
|
|
1867
1880
|
});
|
|
1868
1881
|
|
package/dist/auto.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('./rolldown-runtime-C_NdSu1c.cjs');
|
|
2
|
-
const require_init = require('./init-
|
|
2
|
+
const require_init = require('./init-C3RApHKJ.cjs');
|
|
3
3
|
const require_yaml_config = require('./yaml-config.cjs');
|
|
4
4
|
let node_module = require("node:module");
|
|
5
5
|
node_module = require_rolldown_runtime.__toESM(node_module, 1);
|
package/dist/auto.js
CHANGED
package/dist/correlation-id.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const
|
|
2
|
+
const require_init = require('./init-C3RApHKJ.cjs');
|
|
3
3
|
|
|
4
|
-
exports.CORRELATION_ID_BAGGAGE_KEY =
|
|
5
|
-
exports.generateCorrelationId =
|
|
6
|
-
exports.getCorrelationId =
|
|
7
|
-
exports.getCorrelationStorage =
|
|
8
|
-
exports.getOrCreateCorrelationId =
|
|
9
|
-
exports.runWithCorrelationId =
|
|
10
|
-
exports.setCorrelationId =
|
|
11
|
-
exports.setCorrelationIdInBaggage =
|
|
4
|
+
exports.CORRELATION_ID_BAGGAGE_KEY = require_init.CORRELATION_ID_BAGGAGE_KEY;
|
|
5
|
+
exports.generateCorrelationId = require_init.generateCorrelationId;
|
|
6
|
+
exports.getCorrelationId = require_init.getCorrelationId;
|
|
7
|
+
exports.getCorrelationStorage = require_init.getCorrelationStorage;
|
|
8
|
+
exports.getOrCreateCorrelationId = require_init.getOrCreateCorrelationId;
|
|
9
|
+
exports.runWithCorrelationId = require_init.runWithCorrelationId;
|
|
10
|
+
exports.setCorrelationId = require_init.setCorrelationId;
|
|
11
|
+
exports.setCorrelationIdInBaggage = require_init.setCorrelationIdInBaggage;
|
package/dist/correlation-id.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as setCorrelationId, S as runWithCorrelationId, _ as CORRELATION_ID_BAGGAGE_KEY, b as getCorrelationStorage, v as generateCorrelationId, w as setCorrelationIdInBaggage, x as getOrCreateCorrelationId, y as getCorrelationId } from "./init-R-gSEXkJ.js";
|
|
2
2
|
|
|
3
3
|
export { CORRELATION_ID_BAGGAGE_KEY, generateCorrelationId, getCorrelationId, getCorrelationStorage, getOrCreateCorrelationId, runWithCorrelationId, setCorrelationId, setCorrelationIdInBaggage };
|
package/dist/decorators.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_config = require('./config.cjs');
|
|
3
|
-
const
|
|
3
|
+
const require_init = require('./init-C3RApHKJ.cjs');
|
|
4
4
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
5
5
|
|
|
6
6
|
//#region src/decorators.ts
|
|
@@ -14,7 +14,7 @@ function Trace(nameOrOptions, maybeOptions) {
|
|
|
14
14
|
return async function(...args) {
|
|
15
15
|
return require_config.getConfig().tracer.startActiveSpan(spanName, async (span) => {
|
|
16
16
|
try {
|
|
17
|
-
const ctx =
|
|
17
|
+
const ctx = require_init.createTraceContext(span);
|
|
18
18
|
const originalCtx = this.ctx;
|
|
19
19
|
try {
|
|
20
20
|
this.ctx = ctx;
|
package/dist/decorators.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.cjs","names":["getConfig","createTraceContext","SpanStatusCode"],"sources":["../src/decorators.ts"],"sourcesContent":["/**\n * TypeScript 5+ Decorators for autotel\n *\n * Provides @Trace decorator for class-based code.\n *\n * **Requires TypeScript 5.0+**\n *\n * @example Method decorator\n * ```typescript\n * import { Trace } from 'autotel/decorators'\n *\n * class OrderService {\n * @Trace('order.create', { withMetrics: true })\n * async createOrder(data: OrderData) {\n * return await db.orders.create(data)\n * }\n *\n * @Trace() // Uses method name as span name\n * async processPayment(orderId: string) {\n * return await stripe.charge(orderId)\n * }\n * }\n * ```\n */\n\nimport type { TracingOptions, TraceContext } from './functional';\nimport { getConfig } from './config';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { createTraceContext } from './trace-context';\n\n/**\n * Options for @Trace method decorator\n */\nexport interface TraceDecoratorOptions extends Omit<TracingOptions, 'name'> {\n /**\n * Custom span name. If not provided, uses the method name.\n */\n name?: string;\n}\n\n/**\n * @Trace - Method decorator for fine-grained tracing\n *\n * Wraps a class method with automatic tracing. Supports both patterns:\n * - Simple: method doesn't use ctx\n * - Advanced: method accesses ctx via this.ctx\n *\n * @example Simple usage (no ctx)\n * ```typescript\n * class OrderService {\n * @Trace()\n * async createOrder(data: OrderData) {\n * return await db.orders.create(data)\n * }\n * }\n * ```\n *\n * @example With custom name and options\n * ```typescript\n * class PaymentService {\n * @Trace('payment.charge', { withMetrics: true })\n * async chargeCard(amount: number) {\n * return await stripe.charges.create({ amount })\n * }\n * }\n * ```\n *\n * @example Accessing ctx\n * ```typescript\n * interface WithTraceContext {\n * ctx?: TraceContext\n * }\n *\n * class UserService {\n * @Trace()\n * async createUser(data: UserData) {\n * // Access ctx via this.ctx (available during execution)\n * const ctx = (this as unknown as WithTraceContext).ctx\n * if (ctx) {\n * ctx.setAttribute('user.id', data.id)\n * }\n * return await db.users.create(data)\n * }\n * }\n * ```\n */\nexport function Trace(\n options?: TraceDecoratorOptions,\n): <T extends (...args: unknown[]) => Promise<unknown>>(\n originalMethod: T,\n context: ClassMethodDecoratorContext,\n) => T;\nexport function Trace(\n name?: string,\n options?: TraceDecoratorOptions,\n): <T extends (...args: unknown[]) => Promise<unknown>>(\n originalMethod: T,\n context: ClassMethodDecoratorContext,\n) => T;\nexport function Trace(\n nameOrOptions?: string | TraceDecoratorOptions,\n maybeOptions?: TraceDecoratorOptions,\n): <T extends (...args: unknown[]) => Promise<unknown>>(\n originalMethod: T,\n context: ClassMethodDecoratorContext,\n) => T {\n // Parse arguments\n const name =\n typeof nameOrOptions === 'string' ? nameOrOptions : nameOrOptions?.name;\n // Options are used in the returned decorator function, not here\n const _options: TraceDecoratorOptions =\n typeof nameOrOptions === 'string'\n ? maybeOptions || {}\n : nameOrOptions || {};\n\n // TypeScript 5+ decorator signature\n return function <T extends (...args: unknown[]) => Promise<unknown>>(\n originalMethod: T,\n context: ClassMethodDecoratorContext,\n ): T {\n const methodName = String(context.name);\n\n // Skip if not an async function\n // Check multiple ways to detect async functions (for different transpilation environments)\n // TypeScript decorators run at class definition time, so we need robust detection\n const methodStr = originalMethod?.toString() || '';\n const isAsync =\n originalMethod &&\n (originalMethod.constructor?.name === 'AsyncFunction' ||\n methodStr.trim().startsWith('async ') ||\n (methodStr.includes('[native code]') && methodStr.includes('async')) ||\n // Fallback: if function has async in its string representation\n /async\\s+/.test(methodStr));\n\n if (!isAsync) {\n // Not an async function, return as-is\n return originalMethod;\n }\n\n const spanName = name || methodName;\n\n return async function <This>(\n this: This,\n ...args: unknown[]\n ): Promise<unknown> {\n const config = getConfig();\n const tracer = config.tracer;\n\n return tracer.startActiveSpan(spanName, async (span) => {\n try {\n // Make ctx available via this.ctx for methods that need it\n const ctx: TraceContext = createTraceContext(span);\n\n const originalCtx = (this as { ctx?: TraceContext }).ctx;\n try {\n (this as { ctx?: TraceContext }).ctx = ctx;\n const result = await originalMethod.apply(this, args as []);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } finally {\n // Restore original ctx\n if (originalCtx === undefined) {\n delete (this as { ctx?: TraceContext }).ctx;\n } else {\n (this as { ctx?: TraceContext }).ctx = originalCtx;\n }\n }\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : 'Unknown error',\n });\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n } finally {\n span.end();\n }\n });\n } as T;\n };\n}\n\n// Re-export types for convenience\n\nexport { type TraceContext, type TracingOptions } from './functional';\n"],"mappings":";;;;;;AAmGA,SAAgB,MACd,eACA,cAIK;CAEL,MAAM,OACJ,OAAO,kBAAkB,WAAW,gBAAgB,eAAe;CAQrE,OAAO,SACL,gBACA,SACG;EACH,MAAM,aAAa,OAAO,QAAQ,IAAI;EAKtC,MAAM,YAAY,gBAAgB,SAAS,KAAK;EAShD,IAAI,EAPF,mBACC,eAAe,aAAa,SAAS,mBACpC,UAAU,KAAK,CAAC,CAAC,WAAW,QAAQ,KACnC,UAAU,SAAS,eAAe,KAAK,UAAU,SAAS,OAAO,KAElE,WAAW,KAAK,SAAS,KAI3B,OAAO;EAGT,MAAM,WAAW,QAAQ;EAEzB,OAAO,eAEL,GAAG,MACe;GAIlB,OAHeA,yBACK,CAAC,CAAC,OAER,gBAAgB,UAAU,OAAO,SAAS;IACtD,IAAI;KAEF,MAAM,MAAoBC,
|
|
1
|
+
{"version":3,"file":"decorators.cjs","names":["getConfig","createTraceContext","SpanStatusCode"],"sources":["../src/decorators.ts"],"sourcesContent":["/**\n * TypeScript 5+ Decorators for autotel\n *\n * Provides @Trace decorator for class-based code.\n *\n * **Requires TypeScript 5.0+**\n *\n * @example Method decorator\n * ```typescript\n * import { Trace } from 'autotel/decorators'\n *\n * class OrderService {\n * @Trace('order.create', { withMetrics: true })\n * async createOrder(data: OrderData) {\n * return await db.orders.create(data)\n * }\n *\n * @Trace() // Uses method name as span name\n * async processPayment(orderId: string) {\n * return await stripe.charge(orderId)\n * }\n * }\n * ```\n */\n\nimport type { TracingOptions, TraceContext } from './functional';\nimport { getConfig } from './config';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { createTraceContext } from './trace-context';\n\n/**\n * Options for @Trace method decorator\n */\nexport interface TraceDecoratorOptions extends Omit<TracingOptions, 'name'> {\n /**\n * Custom span name. If not provided, uses the method name.\n */\n name?: string;\n}\n\n/**\n * @Trace - Method decorator for fine-grained tracing\n *\n * Wraps a class method with automatic tracing. Supports both patterns:\n * - Simple: method doesn't use ctx\n * - Advanced: method accesses ctx via this.ctx\n *\n * @example Simple usage (no ctx)\n * ```typescript\n * class OrderService {\n * @Trace()\n * async createOrder(data: OrderData) {\n * return await db.orders.create(data)\n * }\n * }\n * ```\n *\n * @example With custom name and options\n * ```typescript\n * class PaymentService {\n * @Trace('payment.charge', { withMetrics: true })\n * async chargeCard(amount: number) {\n * return await stripe.charges.create({ amount })\n * }\n * }\n * ```\n *\n * @example Accessing ctx\n * ```typescript\n * interface WithTraceContext {\n * ctx?: TraceContext\n * }\n *\n * class UserService {\n * @Trace()\n * async createUser(data: UserData) {\n * // Access ctx via this.ctx (available during execution)\n * const ctx = (this as unknown as WithTraceContext).ctx\n * if (ctx) {\n * ctx.setAttribute('user.id', data.id)\n * }\n * return await db.users.create(data)\n * }\n * }\n * ```\n */\nexport function Trace(\n options?: TraceDecoratorOptions,\n): <T extends (...args: unknown[]) => Promise<unknown>>(\n originalMethod: T,\n context: ClassMethodDecoratorContext,\n) => T;\nexport function Trace(\n name?: string,\n options?: TraceDecoratorOptions,\n): <T extends (...args: unknown[]) => Promise<unknown>>(\n originalMethod: T,\n context: ClassMethodDecoratorContext,\n) => T;\nexport function Trace(\n nameOrOptions?: string | TraceDecoratorOptions,\n maybeOptions?: TraceDecoratorOptions,\n): <T extends (...args: unknown[]) => Promise<unknown>>(\n originalMethod: T,\n context: ClassMethodDecoratorContext,\n) => T {\n // Parse arguments\n const name =\n typeof nameOrOptions === 'string' ? nameOrOptions : nameOrOptions?.name;\n // Options are used in the returned decorator function, not here\n const _options: TraceDecoratorOptions =\n typeof nameOrOptions === 'string'\n ? maybeOptions || {}\n : nameOrOptions || {};\n\n // TypeScript 5+ decorator signature\n return function <T extends (...args: unknown[]) => Promise<unknown>>(\n originalMethod: T,\n context: ClassMethodDecoratorContext,\n ): T {\n const methodName = String(context.name);\n\n // Skip if not an async function\n // Check multiple ways to detect async functions (for different transpilation environments)\n // TypeScript decorators run at class definition time, so we need robust detection\n const methodStr = originalMethod?.toString() || '';\n const isAsync =\n originalMethod &&\n (originalMethod.constructor?.name === 'AsyncFunction' ||\n methodStr.trim().startsWith('async ') ||\n (methodStr.includes('[native code]') && methodStr.includes('async')) ||\n // Fallback: if function has async in its string representation\n /async\\s+/.test(methodStr));\n\n if (!isAsync) {\n // Not an async function, return as-is\n return originalMethod;\n }\n\n const spanName = name || methodName;\n\n return async function <This>(\n this: This,\n ...args: unknown[]\n ): Promise<unknown> {\n const config = getConfig();\n const tracer = config.tracer;\n\n return tracer.startActiveSpan(spanName, async (span) => {\n try {\n // Make ctx available via this.ctx for methods that need it\n const ctx: TraceContext = createTraceContext(span);\n\n const originalCtx = (this as { ctx?: TraceContext }).ctx;\n try {\n (this as { ctx?: TraceContext }).ctx = ctx;\n const result = await originalMethod.apply(this, args as []);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } finally {\n // Restore original ctx\n if (originalCtx === undefined) {\n delete (this as { ctx?: TraceContext }).ctx;\n } else {\n (this as { ctx?: TraceContext }).ctx = originalCtx;\n }\n }\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : 'Unknown error',\n });\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n } finally {\n span.end();\n }\n });\n } as T;\n };\n}\n\n// Re-export types for convenience\n\nexport { type TraceContext, type TracingOptions } from './functional';\n"],"mappings":";;;;;;AAmGA,SAAgB,MACd,eACA,cAIK;CAEL,MAAM,OACJ,OAAO,kBAAkB,WAAW,gBAAgB,eAAe;CAQrE,OAAO,SACL,gBACA,SACG;EACH,MAAM,aAAa,OAAO,QAAQ,IAAI;EAKtC,MAAM,YAAY,gBAAgB,SAAS,KAAK;EAShD,IAAI,EAPF,mBACC,eAAe,aAAa,SAAS,mBACpC,UAAU,KAAK,CAAC,CAAC,WAAW,QAAQ,KACnC,UAAU,SAAS,eAAe,KAAK,UAAU,SAAS,OAAO,KAElE,WAAW,KAAK,SAAS,KAI3B,OAAO;EAGT,MAAM,WAAW,QAAQ;EAEzB,OAAO,eAEL,GAAG,MACe;GAIlB,OAHeA,yBACK,CAAC,CAAC,OAER,gBAAgB,UAAU,OAAO,SAAS;IACtD,IAAI;KAEF,MAAM,MAAoBC,gCAAmB,IAAI;KAEjD,MAAM,cAAe,KAAgC;KACrD,IAAI;MACF,AAAC,KAAgC,MAAM;MACvC,MAAM,SAAS,MAAM,eAAe,MAAM,MAAM,IAAU;MAC1D,KAAK,UAAU,EAAE,MAAMC,kCAAe,GAAG,CAAC;MAC1C,OAAO;KACT,UAAU;MAER,IAAI,gBAAgB,QAClB,OAAQ,KAAgC;WAExC,AAAC,KAAgC,MAAM;KAE3C;IACF,SAAS,OAAO;KACd,KAAK,UAAU;MACb,MAAMA,kCAAe;MACrB,SAAS,iBAAiB,QAAQ,MAAM,UAAU;KACpD,CAAC;KACD,KAAK,gBACH,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAC1D;KACA,MAAM;IACR,UAAU;KACR,KAAK,IAAI;IACX;GACF,CAAC;EACH;CACF;AACF"}
|
package/dist/decorators.js
CHANGED
package/dist/drain-pipeline.cjs
CHANGED
|
@@ -15,6 +15,7 @@ function createDrainPipeline(options) {
|
|
|
15
15
|
const initialDelayMs = options?.retry?.initialDelayMs ?? 1e3;
|
|
16
16
|
const maxDelayMs = options?.retry?.maxDelayMs ?? 3e4;
|
|
17
17
|
const jitter = options?.retry?.jitter ?? true;
|
|
18
|
+
const shouldRetry = options?.retry?.shouldRetry;
|
|
18
19
|
const dropPolicy = options?.dropPolicy ?? "oldest";
|
|
19
20
|
const onDropped = options?.onDropped;
|
|
20
21
|
if (!Number.isFinite(batchSize) || batchSize <= 0) throw new Error(`[autotel/drain-pipeline] batch.size must be a positive finite number, got: ${batchSize}`);
|
|
@@ -46,7 +47,15 @@ function createDrainPipeline(options) {
|
|
|
46
47
|
return;
|
|
47
48
|
} catch (error) {
|
|
48
49
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
49
|
-
|
|
50
|
+
let retry = attempt < maxAttempts;
|
|
51
|
+
if (retry && shouldRetry) try {
|
|
52
|
+
retry = shouldRetry(lastError, attempt, batch);
|
|
53
|
+
} catch (classifierError) {
|
|
54
|
+
lastError = classifierError instanceof Error ? classifierError : new Error(String(classifierError));
|
|
55
|
+
retry = false;
|
|
56
|
+
}
|
|
57
|
+
if (retry) await wait(computeDelay(attempt));
|
|
58
|
+
else break;
|
|
50
59
|
}
|
|
51
60
|
onDropped?.(batch, lastError);
|
|
52
61
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drain-pipeline.cjs","names":[],"sources":["../src/drain-pipeline.ts"],"sourcesContent":["export interface DrainPipelineOptions<T = unknown> {\n batch?: {\n /** Maximum events per batch. @default 50 */\n size?: number;\n /** Max time an event can stay buffered before flush. @default 5000 */\n intervalMs?: number;\n };\n retry?: {\n /** Total attempts including first try. @default 3 */\n maxAttempts?: number;\n /** Delay strategy between attempts. @default 'exponential' */\n backoff?: 'exponential' | 'linear' | 'fixed';\n /** Base delay for first retry. @default 1000 */\n initialDelayMs?: number;\n /** Max delay cap. @default 30000 */\n maxDelayMs?: number;\n /** Add random jitter to delays. @default true */\n jitter?: boolean;\n };\n /** Max buffered events before dropping. @default 1000 */\n maxBufferSize?: number;\n /** Overflow policy. @default 'oldest' */\n dropPolicy?: 'oldest' | 'newest';\n /** Called when events are dropped from overflow or exhausted retries. */\n onDropped?: (events: T[], error?: Error) => void;\n}\n\nexport interface PipelineDrainFn<T> {\n (ctx: T): void;\n /** Flush all buffered events. */\n flush: () => Promise<void>;\n /** Flush and stop scheduling future timer work. */\n shutdown: () => Promise<void>;\n readonly pending: number;\n}\n\nfunction wait(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n}\n\nexport function createDrainPipeline<T = unknown>(\n options?: DrainPipelineOptions<T>,\n): (drain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T> {\n const batchSize = options?.batch?.size ?? 50;\n const intervalMs = options?.batch?.intervalMs ?? 5000;\n const maxBufferSize = options?.maxBufferSize ?? 1000;\n const maxAttempts = options?.retry?.maxAttempts ?? 3;\n const backoff = options?.retry?.backoff ?? 'exponential';\n const initialDelayMs = options?.retry?.initialDelayMs ?? 1000;\n const maxDelayMs = options?.retry?.maxDelayMs ?? 30_000;\n const jitter = options?.retry?.jitter ?? true;\n const dropPolicy = options?.dropPolicy ?? 'oldest';\n const onDropped = options?.onDropped;\n\n if (!Number.isFinite(batchSize) || batchSize <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] batch.size must be a positive finite number, got: ${batchSize}`,\n );\n }\n if (!Number.isFinite(intervalMs) || intervalMs <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] batch.intervalMs must be a positive finite number, got: ${intervalMs}`,\n );\n }\n if (!Number.isFinite(maxBufferSize) || maxBufferSize <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] maxBufferSize must be a positive finite number, got: ${maxBufferSize}`,\n );\n }\n if (!Number.isFinite(maxAttempts) || maxAttempts <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] retry.maxAttempts must be a positive finite number, got: ${maxAttempts}`,\n );\n }\n\n return (drain: (batch: T[]) => void | Promise<void>): PipelineDrainFn<T> => {\n const buffer: T[] = [];\n let timer: ReturnType<typeof setTimeout> | null = null;\n let activeFlush: Promise<void> | null = null;\n let isShutdown = false;\n\n const clearTimer = () => {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n\n const computeDelay = (attempt: number): number => {\n const base =\n backoff === 'fixed'\n ? initialDelayMs\n : backoff === 'linear'\n ? initialDelayMs * attempt\n : initialDelayMs * 2 ** (attempt - 1);\n\n const bounded = Math.min(base, maxDelayMs);\n if (!jitter || bounded <= 0) return bounded;\n const factor = 0.5 + Math.random(); // [0.5, 1.5)\n return Math.max(0, Math.round(bounded * factor));\n };\n\n const sendWithRetry = async (batch: T[]): Promise<void> => {\n let lastError: Error | undefined;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await drain(batch);\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n
|
|
1
|
+
{"version":3,"file":"drain-pipeline.cjs","names":[],"sources":["../src/drain-pipeline.ts"],"sourcesContent":["export interface DrainPipelineOptions<T = unknown> {\n batch?: {\n /** Maximum events per batch. @default 50 */\n size?: number;\n /** Max time an event can stay buffered before flush. @default 5000 */\n intervalMs?: number;\n };\n retry?: {\n /** Total attempts including first try. @default 3 */\n maxAttempts?: number;\n /** Delay strategy between attempts. @default 'exponential' */\n backoff?: 'exponential' | 'linear' | 'fixed';\n /** Base delay for first retry. @default 1000 */\n initialDelayMs?: number;\n /** Max delay cap. @default 30000 */\n maxDelayMs?: number;\n /** Add random jitter to delays. @default true */\n jitter?: boolean;\n /**\n * Decide whether a failed attempt is safe to repeat. Return false for\n * permanent failures such as an HTTP 4xx response. @default always retry\n */\n shouldRetry?: (\n error: Error,\n attempt: number,\n batch: readonly T[],\n ) => boolean;\n };\n /** Max buffered events before dropping. @default 1000 */\n maxBufferSize?: number;\n /** Overflow policy. @default 'oldest' */\n dropPolicy?: 'oldest' | 'newest';\n /** Called when events are dropped from overflow or exhausted retries. */\n onDropped?: (events: T[], error?: Error) => void;\n}\n\nexport interface PipelineDrainFn<T> {\n (ctx: T): void;\n /** Flush all buffered events. */\n flush: () => Promise<void>;\n /** Flush and stop scheduling future timer work. */\n shutdown: () => Promise<void>;\n readonly pending: number;\n}\n\nfunction wait(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n}\n\nexport function createDrainPipeline<T = unknown>(\n options?: DrainPipelineOptions<T>,\n): (drain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T> {\n const batchSize = options?.batch?.size ?? 50;\n const intervalMs = options?.batch?.intervalMs ?? 5000;\n const maxBufferSize = options?.maxBufferSize ?? 1000;\n const maxAttempts = options?.retry?.maxAttempts ?? 3;\n const backoff = options?.retry?.backoff ?? 'exponential';\n const initialDelayMs = options?.retry?.initialDelayMs ?? 1000;\n const maxDelayMs = options?.retry?.maxDelayMs ?? 30_000;\n const jitter = options?.retry?.jitter ?? true;\n const shouldRetry = options?.retry?.shouldRetry;\n const dropPolicy = options?.dropPolicy ?? 'oldest';\n const onDropped = options?.onDropped;\n\n if (!Number.isFinite(batchSize) || batchSize <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] batch.size must be a positive finite number, got: ${batchSize}`,\n );\n }\n if (!Number.isFinite(intervalMs) || intervalMs <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] batch.intervalMs must be a positive finite number, got: ${intervalMs}`,\n );\n }\n if (!Number.isFinite(maxBufferSize) || maxBufferSize <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] maxBufferSize must be a positive finite number, got: ${maxBufferSize}`,\n );\n }\n if (!Number.isFinite(maxAttempts) || maxAttempts <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] retry.maxAttempts must be a positive finite number, got: ${maxAttempts}`,\n );\n }\n\n return (drain: (batch: T[]) => void | Promise<void>): PipelineDrainFn<T> => {\n const buffer: T[] = [];\n let timer: ReturnType<typeof setTimeout> | null = null;\n let activeFlush: Promise<void> | null = null;\n let isShutdown = false;\n\n const clearTimer = () => {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n\n const computeDelay = (attempt: number): number => {\n const base =\n backoff === 'fixed'\n ? initialDelayMs\n : backoff === 'linear'\n ? initialDelayMs * attempt\n : initialDelayMs * 2 ** (attempt - 1);\n\n const bounded = Math.min(base, maxDelayMs);\n if (!jitter || bounded <= 0) return bounded;\n const factor = 0.5 + Math.random(); // [0.5, 1.5)\n return Math.max(0, Math.round(bounded * factor));\n };\n\n const sendWithRetry = async (batch: T[]): Promise<void> => {\n let lastError: Error | undefined;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await drain(batch);\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n let retry = attempt < maxAttempts;\n if (retry && shouldRetry) {\n try {\n retry = shouldRetry(lastError, attempt, batch);\n } catch (classifierError) {\n lastError =\n classifierError instanceof Error\n ? classifierError\n : new Error(String(classifierError));\n retry = false;\n }\n }\n if (retry) {\n await wait(computeDelay(attempt));\n } else {\n break;\n }\n }\n }\n onDropped?.(batch, lastError);\n };\n\n const drainBuffer = async (): Promise<void> => {\n while (buffer.length > 0) {\n const batch = buffer.splice(0, batchSize);\n await sendWithRetry(batch);\n }\n };\n\n const scheduleFlush = () => {\n if (isShutdown || timer || activeFlush) return;\n timer = setTimeout(() => {\n timer = null;\n startFlush();\n }, intervalMs);\n timer.unref?.();\n };\n\n const startFlush = () => {\n if (activeFlush || isShutdown) return;\n activeFlush = drainBuffer().finally(() => {\n activeFlush = null;\n if (isShutdown) return;\n if (buffer.length >= batchSize) {\n startFlush();\n } else if (buffer.length > 0) {\n scheduleFlush();\n }\n });\n };\n\n const push = (ctx: T) => {\n if (isShutdown) return;\n\n if (buffer.length >= maxBufferSize) {\n if (dropPolicy === 'newest') {\n onDropped?.([ctx]);\n return;\n }\n const dropped = buffer.splice(0, 1);\n onDropped?.(dropped);\n }\n\n buffer.push(ctx);\n if (buffer.length >= batchSize) {\n clearTimer();\n startFlush();\n } else {\n scheduleFlush();\n }\n };\n\n const flush = async (): Promise<void> => {\n clearTimer();\n if (activeFlush) await activeFlush;\n\n const snapshot = buffer.length;\n if (snapshot <= 0) return;\n const toFlush = buffer.splice(0, snapshot);\n while (toFlush.length > 0) {\n const batch = toFlush.splice(0, batchSize);\n await sendWithRetry(batch);\n }\n };\n\n const shutdown = async (): Promise<void> => {\n isShutdown = true;\n await flush();\n };\n\n const fn = push as PipelineDrainFn<T>;\n fn.flush = flush;\n fn.shutdown = shutdown;\n Object.defineProperty(fn, 'pending', {\n enumerable: true,\n get: () => buffer.length,\n });\n return fn;\n };\n}\n"],"mappings":"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"}
|
|
@@ -17,6 +17,11 @@ interface DrainPipelineOptions<T = unknown> {
|
|
|
17
17
|
maxDelayMs?: number;
|
|
18
18
|
/** Add random jitter to delays. @default true */
|
|
19
19
|
jitter?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Decide whether a failed attempt is safe to repeat. Return false for
|
|
22
|
+
* permanent failures such as an HTTP 4xx response. @default always retry
|
|
23
|
+
*/
|
|
24
|
+
shouldRetry?: (error: Error, attempt: number, batch: readonly T[]) => boolean;
|
|
20
25
|
};
|
|
21
26
|
/** Max buffered events before dropping. @default 1000 */
|
|
22
27
|
maxBufferSize?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drain-pipeline.d.cts","names":[],"sources":["../src/drain-pipeline.ts"],"mappings":";UAAiB,qBAAqB;EACpC;;IAEE;;IAEA;;EAEF;;IAEE;;IAEA;;IAEA;;IAEA;;IAEA;;;
|
|
1
|
+
{"version":3,"file":"drain-pipeline.d.cts","names":[],"sources":["../src/drain-pipeline.ts"],"mappings":";UAAiB,qBAAqB;EACpC;;IAEE;;IAEA;;EAEF;;IAEE;;IAEA;;IAEA;;IAEA;;IAEA;;;;;IAKA,eACE,OAAO,OACP,iBACA,gBAAgB;;;EAIpB;;EAEA;;EAEA,aAAa,QAAQ,KAAK,QAAQ;;UAGnB,gBAAgB;GAC9B,KAAK;;EAEN,aAAa;;EAEb,gBAAgB;WACP;;iBAUK,oBAAoB,aAClC,UAAU,qBAAqB,MAC7B,QAAQ,OAAO,eAAe,kBAAkB,gBAAgB"}
|
package/dist/drain-pipeline.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ interface DrainPipelineOptions<T = unknown> {
|
|
|
17
17
|
maxDelayMs?: number;
|
|
18
18
|
/** Add random jitter to delays. @default true */
|
|
19
19
|
jitter?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Decide whether a failed attempt is safe to repeat. Return false for
|
|
22
|
+
* permanent failures such as an HTTP 4xx response. @default always retry
|
|
23
|
+
*/
|
|
24
|
+
shouldRetry?: (error: Error, attempt: number, batch: readonly T[]) => boolean;
|
|
20
25
|
};
|
|
21
26
|
/** Max buffered events before dropping. @default 1000 */
|
|
22
27
|
maxBufferSize?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drain-pipeline.d.ts","names":[],"sources":["../src/drain-pipeline.ts"],"mappings":";UAAiB,qBAAqB;EACpC;;IAEE;;IAEA;;EAEF;;IAEE;;IAEA;;IAEA;;IAEA;;IAEA;;;
|
|
1
|
+
{"version":3,"file":"drain-pipeline.d.ts","names":[],"sources":["../src/drain-pipeline.ts"],"mappings":";UAAiB,qBAAqB;EACpC;;IAEE;;IAEA;;EAEF;;IAEE;;IAEA;;IAEA;;IAEA;;IAEA;;;;;IAKA,eACE,OAAO,OACP,iBACA,gBAAgB;;;EAIpB;;EAEA;;EAEA,aAAa,QAAQ,KAAK,QAAQ;;UAGnB,gBAAgB;GAC9B,KAAK;;EAEN,aAAa;;EAEb,gBAAgB;WACP;;iBAUK,oBAAoB,aAClC,UAAU,qBAAqB,MAC7B,QAAQ,OAAO,eAAe,kBAAkB,gBAAgB"}
|
package/dist/drain-pipeline.js
CHANGED
|
@@ -13,6 +13,7 @@ function createDrainPipeline(options) {
|
|
|
13
13
|
const initialDelayMs = options?.retry?.initialDelayMs ?? 1e3;
|
|
14
14
|
const maxDelayMs = options?.retry?.maxDelayMs ?? 3e4;
|
|
15
15
|
const jitter = options?.retry?.jitter ?? true;
|
|
16
|
+
const shouldRetry = options?.retry?.shouldRetry;
|
|
16
17
|
const dropPolicy = options?.dropPolicy ?? "oldest";
|
|
17
18
|
const onDropped = options?.onDropped;
|
|
18
19
|
if (!Number.isFinite(batchSize) || batchSize <= 0) throw new Error(`[autotel/drain-pipeline] batch.size must be a positive finite number, got: ${batchSize}`);
|
|
@@ -44,7 +45,15 @@ function createDrainPipeline(options) {
|
|
|
44
45
|
return;
|
|
45
46
|
} catch (error) {
|
|
46
47
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
47
|
-
|
|
48
|
+
let retry = attempt < maxAttempts;
|
|
49
|
+
if (retry && shouldRetry) try {
|
|
50
|
+
retry = shouldRetry(lastError, attempt, batch);
|
|
51
|
+
} catch (classifierError) {
|
|
52
|
+
lastError = classifierError instanceof Error ? classifierError : new Error(String(classifierError));
|
|
53
|
+
retry = false;
|
|
54
|
+
}
|
|
55
|
+
if (retry) await wait(computeDelay(attempt));
|
|
56
|
+
else break;
|
|
48
57
|
}
|
|
49
58
|
onDropped?.(batch, lastError);
|
|
50
59
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drain-pipeline.js","names":[],"sources":["../src/drain-pipeline.ts"],"sourcesContent":["export interface DrainPipelineOptions<T = unknown> {\n batch?: {\n /** Maximum events per batch. @default 50 */\n size?: number;\n /** Max time an event can stay buffered before flush. @default 5000 */\n intervalMs?: number;\n };\n retry?: {\n /** Total attempts including first try. @default 3 */\n maxAttempts?: number;\n /** Delay strategy between attempts. @default 'exponential' */\n backoff?: 'exponential' | 'linear' | 'fixed';\n /** Base delay for first retry. @default 1000 */\n initialDelayMs?: number;\n /** Max delay cap. @default 30000 */\n maxDelayMs?: number;\n /** Add random jitter to delays. @default true */\n jitter?: boolean;\n };\n /** Max buffered events before dropping. @default 1000 */\n maxBufferSize?: number;\n /** Overflow policy. @default 'oldest' */\n dropPolicy?: 'oldest' | 'newest';\n /** Called when events are dropped from overflow or exhausted retries. */\n onDropped?: (events: T[], error?: Error) => void;\n}\n\nexport interface PipelineDrainFn<T> {\n (ctx: T): void;\n /** Flush all buffered events. */\n flush: () => Promise<void>;\n /** Flush and stop scheduling future timer work. */\n shutdown: () => Promise<void>;\n readonly pending: number;\n}\n\nfunction wait(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n}\n\nexport function createDrainPipeline<T = unknown>(\n options?: DrainPipelineOptions<T>,\n): (drain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T> {\n const batchSize = options?.batch?.size ?? 50;\n const intervalMs = options?.batch?.intervalMs ?? 5000;\n const maxBufferSize = options?.maxBufferSize ?? 1000;\n const maxAttempts = options?.retry?.maxAttempts ?? 3;\n const backoff = options?.retry?.backoff ?? 'exponential';\n const initialDelayMs = options?.retry?.initialDelayMs ?? 1000;\n const maxDelayMs = options?.retry?.maxDelayMs ?? 30_000;\n const jitter = options?.retry?.jitter ?? true;\n const dropPolicy = options?.dropPolicy ?? 'oldest';\n const onDropped = options?.onDropped;\n\n if (!Number.isFinite(batchSize) || batchSize <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] batch.size must be a positive finite number, got: ${batchSize}`,\n );\n }\n if (!Number.isFinite(intervalMs) || intervalMs <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] batch.intervalMs must be a positive finite number, got: ${intervalMs}`,\n );\n }\n if (!Number.isFinite(maxBufferSize) || maxBufferSize <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] maxBufferSize must be a positive finite number, got: ${maxBufferSize}`,\n );\n }\n if (!Number.isFinite(maxAttempts) || maxAttempts <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] retry.maxAttempts must be a positive finite number, got: ${maxAttempts}`,\n );\n }\n\n return (drain: (batch: T[]) => void | Promise<void>): PipelineDrainFn<T> => {\n const buffer: T[] = [];\n let timer: ReturnType<typeof setTimeout> | null = null;\n let activeFlush: Promise<void> | null = null;\n let isShutdown = false;\n\n const clearTimer = () => {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n\n const computeDelay = (attempt: number): number => {\n const base =\n backoff === 'fixed'\n ? initialDelayMs\n : backoff === 'linear'\n ? initialDelayMs * attempt\n : initialDelayMs * 2 ** (attempt - 1);\n\n const bounded = Math.min(base, maxDelayMs);\n if (!jitter || bounded <= 0) return bounded;\n const factor = 0.5 + Math.random(); // [0.5, 1.5)\n return Math.max(0, Math.round(bounded * factor));\n };\n\n const sendWithRetry = async (batch: T[]): Promise<void> => {\n let lastError: Error | undefined;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await drain(batch);\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n
|
|
1
|
+
{"version":3,"file":"drain-pipeline.js","names":[],"sources":["../src/drain-pipeline.ts"],"sourcesContent":["export interface DrainPipelineOptions<T = unknown> {\n batch?: {\n /** Maximum events per batch. @default 50 */\n size?: number;\n /** Max time an event can stay buffered before flush. @default 5000 */\n intervalMs?: number;\n };\n retry?: {\n /** Total attempts including first try. @default 3 */\n maxAttempts?: number;\n /** Delay strategy between attempts. @default 'exponential' */\n backoff?: 'exponential' | 'linear' | 'fixed';\n /** Base delay for first retry. @default 1000 */\n initialDelayMs?: number;\n /** Max delay cap. @default 30000 */\n maxDelayMs?: number;\n /** Add random jitter to delays. @default true */\n jitter?: boolean;\n /**\n * Decide whether a failed attempt is safe to repeat. Return false for\n * permanent failures such as an HTTP 4xx response. @default always retry\n */\n shouldRetry?: (\n error: Error,\n attempt: number,\n batch: readonly T[],\n ) => boolean;\n };\n /** Max buffered events before dropping. @default 1000 */\n maxBufferSize?: number;\n /** Overflow policy. @default 'oldest' */\n dropPolicy?: 'oldest' | 'newest';\n /** Called when events are dropped from overflow or exhausted retries. */\n onDropped?: (events: T[], error?: Error) => void;\n}\n\nexport interface PipelineDrainFn<T> {\n (ctx: T): void;\n /** Flush all buffered events. */\n flush: () => Promise<void>;\n /** Flush and stop scheduling future timer work. */\n shutdown: () => Promise<void>;\n readonly pending: number;\n}\n\nfunction wait(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n });\n}\n\nexport function createDrainPipeline<T = unknown>(\n options?: DrainPipelineOptions<T>,\n): (drain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T> {\n const batchSize = options?.batch?.size ?? 50;\n const intervalMs = options?.batch?.intervalMs ?? 5000;\n const maxBufferSize = options?.maxBufferSize ?? 1000;\n const maxAttempts = options?.retry?.maxAttempts ?? 3;\n const backoff = options?.retry?.backoff ?? 'exponential';\n const initialDelayMs = options?.retry?.initialDelayMs ?? 1000;\n const maxDelayMs = options?.retry?.maxDelayMs ?? 30_000;\n const jitter = options?.retry?.jitter ?? true;\n const shouldRetry = options?.retry?.shouldRetry;\n const dropPolicy = options?.dropPolicy ?? 'oldest';\n const onDropped = options?.onDropped;\n\n if (!Number.isFinite(batchSize) || batchSize <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] batch.size must be a positive finite number, got: ${batchSize}`,\n );\n }\n if (!Number.isFinite(intervalMs) || intervalMs <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] batch.intervalMs must be a positive finite number, got: ${intervalMs}`,\n );\n }\n if (!Number.isFinite(maxBufferSize) || maxBufferSize <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] maxBufferSize must be a positive finite number, got: ${maxBufferSize}`,\n );\n }\n if (!Number.isFinite(maxAttempts) || maxAttempts <= 0) {\n throw new Error(\n `[autotel/drain-pipeline] retry.maxAttempts must be a positive finite number, got: ${maxAttempts}`,\n );\n }\n\n return (drain: (batch: T[]) => void | Promise<void>): PipelineDrainFn<T> => {\n const buffer: T[] = [];\n let timer: ReturnType<typeof setTimeout> | null = null;\n let activeFlush: Promise<void> | null = null;\n let isShutdown = false;\n\n const clearTimer = () => {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n\n const computeDelay = (attempt: number): number => {\n const base =\n backoff === 'fixed'\n ? initialDelayMs\n : backoff === 'linear'\n ? initialDelayMs * attempt\n : initialDelayMs * 2 ** (attempt - 1);\n\n const bounded = Math.min(base, maxDelayMs);\n if (!jitter || bounded <= 0) return bounded;\n const factor = 0.5 + Math.random(); // [0.5, 1.5)\n return Math.max(0, Math.round(bounded * factor));\n };\n\n const sendWithRetry = async (batch: T[]): Promise<void> => {\n let lastError: Error | undefined;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await drain(batch);\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n let retry = attempt < maxAttempts;\n if (retry && shouldRetry) {\n try {\n retry = shouldRetry(lastError, attempt, batch);\n } catch (classifierError) {\n lastError =\n classifierError instanceof Error\n ? classifierError\n : new Error(String(classifierError));\n retry = false;\n }\n }\n if (retry) {\n await wait(computeDelay(attempt));\n } else {\n break;\n }\n }\n }\n onDropped?.(batch, lastError);\n };\n\n const drainBuffer = async (): Promise<void> => {\n while (buffer.length > 0) {\n const batch = buffer.splice(0, batchSize);\n await sendWithRetry(batch);\n }\n };\n\n const scheduleFlush = () => {\n if (isShutdown || timer || activeFlush) return;\n timer = setTimeout(() => {\n timer = null;\n startFlush();\n }, intervalMs);\n timer.unref?.();\n };\n\n const startFlush = () => {\n if (activeFlush || isShutdown) return;\n activeFlush = drainBuffer().finally(() => {\n activeFlush = null;\n if (isShutdown) return;\n if (buffer.length >= batchSize) {\n startFlush();\n } else if (buffer.length > 0) {\n scheduleFlush();\n }\n });\n };\n\n const push = (ctx: T) => {\n if (isShutdown) return;\n\n if (buffer.length >= maxBufferSize) {\n if (dropPolicy === 'newest') {\n onDropped?.([ctx]);\n return;\n }\n const dropped = buffer.splice(0, 1);\n onDropped?.(dropped);\n }\n\n buffer.push(ctx);\n if (buffer.length >= batchSize) {\n clearTimer();\n startFlush();\n } else {\n scheduleFlush();\n }\n };\n\n const flush = async (): Promise<void> => {\n clearTimer();\n if (activeFlush) await activeFlush;\n\n const snapshot = buffer.length;\n if (snapshot <= 0) return;\n const toFlush = buffer.splice(0, snapshot);\n while (toFlush.length > 0) {\n const batch = toFlush.splice(0, batchSize);\n await sendWithRetry(batch);\n }\n };\n\n const shutdown = async (): Promise<void> => {\n isShutdown = true;\n await flush();\n };\n\n const fn = push as PipelineDrainFn<T>;\n fn.flush = flush;\n fn.shutdown = shutdown;\n Object.defineProperty(fn, 'pending', {\n enumerable: true,\n get: () => buffer.length,\n });\n return fn;\n };\n}\n"],"mappings":"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"}
|
package/dist/event.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const
|
|
2
|
+
const require_init = require('./init-C3RApHKJ.cjs');
|
|
3
3
|
|
|
4
|
-
exports.Event =
|
|
5
|
-
exports.getEvents =
|
|
6
|
-
exports.resetEvents =
|
|
4
|
+
exports.Event = require_init.Event;
|
|
5
|
+
exports.getEvents = require_init.getEvents;
|
|
6
|
+
exports.resetEvents = require_init.resetEvents;
|
package/dist/event.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { getForceFlushableProvider } from "./tracer-provider.js";
|
|
2
2
|
import { getConfig } from "./config.js";
|
|
3
3
|
import { AUTOTEL_SAMPLING_RATE, AUTOTEL_SAMPLING_TAIL_EVALUATED, AUTOTEL_SAMPLING_TAIL_KEEP, AlwaysSampler } from "./sampling.js";
|
|
4
|
-
import {
|
|
5
|
-
import { g as getContextStorage, h as getActiveContextWithBaggage, p as createTraceContext, t as getEventQueue } from "./track-BBcxEJEC.js";
|
|
4
|
+
import { D as getActiveContextWithBaggage, O as getContextStorage, T as createTraceContext, h as getEventQueue, m as runInOperationContext, r as getSdk, t as getConfig$1 } from "./init-R-gSEXkJ.js";
|
|
6
5
|
import { setSpanName } from "./trace-helpers.js";
|
|
7
|
-
import { n as runInOperationContext } from "./operation-context-CKBoA4Qy.js";
|
|
8
6
|
import { SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
9
7
|
import * as nodeFs from "node:fs";
|
|
10
8
|
import * as nodeUrl from "node:url";
|
|
@@ -837,4 +835,4 @@ function withBaggage(options) {
|
|
|
837
835
|
|
|
838
836
|
//#endregion
|
|
839
837
|
export { trace$1 as a, withTracing as c, span as i, getActiveTraceContext as n, withBaggage as o, instrument as r, withNewContext as s, ctx as t };
|
|
840
|
-
//# sourceMappingURL=functional-
|
|
838
|
+
//# sourceMappingURL=functional-BnDUxnSl.js.map
|