autotel 4.0.0 → 4.2.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.
Files changed (232) hide show
  1. package/README.md +26 -1
  2. package/dist/auto.cjs +2 -2
  3. package/dist/auto.js +1 -1
  4. package/dist/correlation-id.cjs +1 -1
  5. package/dist/correlation-id.js +1 -1
  6. package/dist/decorators.cjs +1 -1
  7. package/dist/decorators.js +1 -1
  8. package/dist/{event-Dlqr4ZNL.cjs → event-BhHREDJk.cjs} +3 -3
  9. package/dist/{event-Dlqr4ZNL.cjs.map → event-BhHREDJk.cjs.map} +1 -1
  10. package/dist/{event-_58ryBjh.js → event-ByBTV9M2.js} +3 -3
  11. package/dist/{event-_58ryBjh.js.map → event-ByBTV9M2.js.map} +1 -1
  12. package/dist/event.cjs +1 -1
  13. package/dist/event.js +1 -1
  14. package/dist/{functional-BGkT8J-h.js → functional-DtI0u4vx.js} +19 -19
  15. package/dist/functional-DtI0u4vx.js.map +1 -0
  16. package/dist/{functional-C4CzoVrX.cjs → functional-zpzNLhky.cjs} +4 -4
  17. package/dist/{functional-C4CzoVrX.cjs.map → functional-zpzNLhky.cjs.map} +1 -1
  18. package/dist/functional.cjs +1 -1
  19. package/dist/functional.js +1 -1
  20. package/dist/http.cjs +1 -1
  21. package/dist/http.js +1 -1
  22. package/dist/index.cjs +5 -5
  23. package/dist/index.d.cts +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +5 -5
  26. package/dist/{init-DJQOdVlN.d.ts → init-B7u-DjxM.d.ts} +57 -2
  27. package/dist/init-B7u-DjxM.d.ts.map +1 -0
  28. package/dist/{init-DvapOXCc.cjs → init-BX7AmFRl.cjs} +40 -21
  29. package/dist/init-BX7AmFRl.cjs.map +1 -0
  30. package/dist/{init-Ch6t7MNI.js → init-D-jnNMix.js} +39 -20
  31. package/dist/init-D-jnNMix.js.map +1 -0
  32. package/dist/{init-CNp-ee80.d.cts → init-DSrRmVnz.d.cts} +57 -2
  33. package/dist/init-DSrRmVnz.d.cts.map +1 -0
  34. package/dist/instrumentation.cjs +1 -1
  35. package/dist/instrumentation.js +1 -1
  36. package/dist/logger-D3Ej3DII.js +446 -0
  37. package/dist/logger-D3Ej3DII.js.map +1 -0
  38. package/dist/logger-thMPLpOG.cjs +487 -0
  39. package/dist/logger-thMPLpOG.cjs.map +1 -0
  40. package/dist/logger.cjs +8 -236
  41. package/dist/logger.js +2 -204
  42. package/dist/messaging.cjs +1 -1
  43. package/dist/messaging.js +1 -1
  44. package/dist/semantic-helpers.cjs +1 -1
  45. package/dist/semantic-helpers.js +1 -1
  46. package/dist/{track-3HY4NGV-.cjs → track-D59FfpL0.cjs} +2 -2
  47. package/dist/{track-3HY4NGV-.cjs.map → track-D59FfpL0.cjs.map} +1 -1
  48. package/dist/{track-nsKVy-pj.js → track-wc0HafS_.js} +6 -6
  49. package/dist/track-wc0HafS_.js.map +1 -0
  50. package/dist/webhook.cjs +1 -1
  51. package/dist/webhook.js +1 -1
  52. package/dist/workflow-distributed.cjs +1 -1
  53. package/dist/workflow-distributed.js +1 -1
  54. package/dist/workflow.cjs +1 -1
  55. package/dist/workflow.js +1 -1
  56. package/dist/{yaml-config-B3dQ82GR.cjs → yaml-config-Ck2uB0Dp.cjs} +2 -1
  57. package/dist/yaml-config-Ck2uB0Dp.cjs.map +1 -0
  58. package/dist/yaml-config.cjs +1 -1
  59. package/dist/yaml-config.d.cts +7 -1
  60. package/dist/yaml-config.d.cts.map +1 -1
  61. package/dist/yaml-config.d.ts +7 -1
  62. package/dist/yaml-config.d.ts.map +1 -1
  63. package/dist/yaml-config.js +1 -0
  64. package/dist/yaml-config.js.map +1 -1
  65. package/package.json +1 -2
  66. package/skills/autotel-core/SKILL.md +2 -0
  67. package/skills/autotel-instrumentation/SKILL.md +25 -0
  68. package/skills/debug-missing-spans/SKILL.md +3 -1
  69. package/skills/migrate-to-autotel/SKILL.md +24 -23
  70. package/skills/review-otel-patterns/SKILL.md +5 -4
  71. package/dist/functional-BGkT8J-h.js.map +0 -1
  72. package/dist/init-CNp-ee80.d.cts.map +0 -1
  73. package/dist/init-Ch6t7MNI.js.map +0 -1
  74. package/dist/init-DJQOdVlN.d.ts.map +0 -1
  75. package/dist/init-DvapOXCc.cjs.map +0 -1
  76. package/dist/logger.cjs.map +0 -1
  77. package/dist/logger.js.map +0 -1
  78. package/dist/track-nsKVy-pj.js.map +0 -1
  79. package/dist/yaml-config-B3dQ82GR.cjs.map +0 -1
  80. package/src/attribute-redacting-processor.test.ts +0 -763
  81. package/src/attribute-redacting-processor.ts +0 -621
  82. package/src/attributes/attachers.ts +0 -161
  83. package/src/attributes/builders.ts +0 -529
  84. package/src/attributes/domains.ts +0 -42
  85. package/src/attributes/index.ts +0 -81
  86. package/src/attributes/registry.ts +0 -323
  87. package/src/attributes/types.ts +0 -211
  88. package/src/attributes/utils.ts +0 -64
  89. package/src/attributes/validators.ts +0 -266
  90. package/src/attributes.test.ts +0 -292
  91. package/src/auto.ts +0 -67
  92. package/src/autotel-logger.test.ts +0 -548
  93. package/src/autotel-logger.ts +0 -364
  94. package/src/baggage-span-processor.test.ts +0 -202
  95. package/src/baggage-span-processor.ts +0 -100
  96. package/src/business-baggage.test.ts +0 -500
  97. package/src/business-baggage.ts +0 -669
  98. package/src/circuit-breaker.test.ts +0 -341
  99. package/src/circuit-breaker.ts +0 -184
  100. package/src/config.test.ts +0 -94
  101. package/src/config.ts +0 -172
  102. package/src/correlated-events.test.ts +0 -151
  103. package/src/correlated-events.ts +0 -47
  104. package/src/correlation-id.test.ts +0 -163
  105. package/src/correlation-id.ts +0 -206
  106. package/src/db.test.ts +0 -252
  107. package/src/db.ts +0 -447
  108. package/src/decorators.test.ts +0 -153
  109. package/src/decorators.ts +0 -188
  110. package/src/define-event.test.ts +0 -41
  111. package/src/define-event.ts +0 -58
  112. package/src/devtools.ts +0 -60
  113. package/src/drain-pipeline.test.ts +0 -68
  114. package/src/drain-pipeline.ts +0 -199
  115. package/src/drain-toolkit.test.ts +0 -113
  116. package/src/drain-toolkit.ts +0 -129
  117. package/src/enricher-toolkit.test.ts +0 -67
  118. package/src/enricher-toolkit.ts +0 -79
  119. package/src/enrichers.test.ts +0 -150
  120. package/src/enrichers.ts +0 -145
  121. package/src/env-config.test.ts +0 -323
  122. package/src/env-config.ts +0 -309
  123. package/src/error-catalog.test.ts +0 -133
  124. package/src/error-catalog.ts +0 -262
  125. package/src/event-queue.test.ts +0 -864
  126. package/src/event-queue.ts +0 -699
  127. package/src/event-subscriber.ts +0 -262
  128. package/src/event-testing.ts +0 -197
  129. package/src/event.test.ts +0 -1104
  130. package/src/event.ts +0 -988
  131. package/src/events-config.ts +0 -235
  132. package/src/exporters.ts +0 -165
  133. package/src/filtering-span-processor.test.ts +0 -281
  134. package/src/filtering-span-processor.ts +0 -111
  135. package/src/flatten-attributes.test.ts +0 -76
  136. package/src/flatten-attributes.ts +0 -80
  137. package/src/functional.strict-types.typecheck.ts +0 -53
  138. package/src/functional.test.ts +0 -1464
  139. package/src/functional.ts +0 -2539
  140. package/src/functional.types.test.ts +0 -135
  141. package/src/hook.mjs +0 -15
  142. package/src/http.test.ts +0 -485
  143. package/src/http.ts +0 -424
  144. package/src/index.ts +0 -433
  145. package/src/init-auto-redactor.test.ts +0 -53
  146. package/src/init-redactor.test.ts +0 -8
  147. package/src/init.customization.test.ts +0 -594
  148. package/src/init.integrations.test.ts +0 -399
  149. package/src/init.openllmetry.test.ts +0 -194
  150. package/src/init.protocol.test.ts +0 -215
  151. package/src/init.ts +0 -2312
  152. package/src/instrumentation.test.ts +0 -108
  153. package/src/instrumentation.ts +0 -319
  154. package/src/logger.test.ts +0 -125
  155. package/src/logger.ts +0 -341
  156. package/src/messaging-adapters.test.ts +0 -595
  157. package/src/messaging-adapters.ts +0 -583
  158. package/src/messaging-testing.test.ts +0 -573
  159. package/src/messaging-testing.ts +0 -935
  160. package/src/messaging.test.ts +0 -1646
  161. package/src/messaging.ts +0 -2245
  162. package/src/metric-helpers.ts +0 -47
  163. package/src/metric-testing.ts +0 -197
  164. package/src/metric.ts +0 -446
  165. package/src/metrics.test.ts +0 -241
  166. package/src/node-require.ts +0 -123
  167. package/src/operation-context.ts +0 -93
  168. package/src/parse-error.test.ts +0 -73
  169. package/src/parse-error.ts +0 -112
  170. package/src/posthog-logs.test.ts +0 -115
  171. package/src/posthog-logs.ts +0 -77
  172. package/src/pretty-console-exporter.test.ts +0 -545
  173. package/src/pretty-console-exporter.ts +0 -413
  174. package/src/pretty-log-formatter.test.ts +0 -123
  175. package/src/pretty-log-formatter.ts +0 -210
  176. package/src/processors/canonical-log-line-processor.test.ts +0 -523
  177. package/src/processors/canonical-log-line-processor.ts +0 -396
  178. package/src/processors.ts +0 -152
  179. package/src/rate-limiter.test.ts +0 -199
  180. package/src/rate-limiter.ts +0 -98
  181. package/src/redact-values.test.ts +0 -90
  182. package/src/redact-values.ts +0 -34
  183. package/src/register.ts +0 -37
  184. package/src/request-logger.test.ts +0 -545
  185. package/src/request-logger.ts +0 -342
  186. package/src/sampling.test.ts +0 -1060
  187. package/src/sampling.ts +0 -737
  188. package/src/security-schema.test.ts +0 -45
  189. package/src/security-schema.ts +0 -107
  190. package/src/semantic-conventions.ts +0 -15
  191. package/src/semantic-helpers.test.ts +0 -226
  192. package/src/semantic-helpers.ts +0 -438
  193. package/src/shutdown.test.ts +0 -364
  194. package/src/shutdown.ts +0 -246
  195. package/src/span-name-normalizer.test.ts +0 -377
  196. package/src/span-name-normalizer.ts +0 -213
  197. package/src/stable-hash.ts +0 -27
  198. package/src/structured-error.test.ts +0 -191
  199. package/src/structured-error.ts +0 -157
  200. package/src/stub.integration.test.ts +0 -361
  201. package/src/tail-sampling-processor.test.ts +0 -230
  202. package/src/tail-sampling-processor.ts +0 -55
  203. package/src/test-span-collector.test.ts +0 -234
  204. package/src/test-span-collector.ts +0 -150
  205. package/src/testing.ts +0 -705
  206. package/src/trace-context.test.ts +0 -73
  207. package/src/trace-context.ts +0 -567
  208. package/src/trace-helpers.new.test.ts +0 -278
  209. package/src/trace-helpers.test.ts +0 -290
  210. package/src/trace-helpers.ts +0 -710
  211. package/src/trace-hybrid.test.ts +0 -42
  212. package/src/trace-hybrid.ts +0 -37
  213. package/src/tracer-provider.test.ts +0 -183
  214. package/src/tracer-provider.ts +0 -266
  215. package/src/track.test.ts +0 -154
  216. package/src/track.ts +0 -216
  217. package/src/validate.test.ts +0 -287
  218. package/src/validate.ts +0 -307
  219. package/src/validation-attributes.ts +0 -43
  220. package/src/validation.test.ts +0 -330
  221. package/src/validation.ts +0 -246
  222. package/src/variable-name-inference.test.ts +0 -178
  223. package/src/variable-name-inference.ts +0 -242
  224. package/src/webhook.test.ts +0 -649
  225. package/src/webhook.ts +0 -637
  226. package/src/workflow-distributed.test.ts +0 -786
  227. package/src/workflow-distributed.ts +0 -916
  228. package/src/workflow.async-safety.integration.test.ts +0 -345
  229. package/src/workflow.test.ts +0 -647
  230. package/src/workflow.ts +0 -810
  231. package/src/yaml-config.test.ts +0 -337
  232. package/src/yaml-config.ts +0 -342
package/src/decorators.ts DELETED
@@ -1,188 +0,0 @@
1
- /**
2
- * TypeScript 5+ Decorators for autotel
3
- *
4
- * Provides @Trace decorator for class-based code.
5
- *
6
- * **Requires TypeScript 5.0+**
7
- *
8
- * @example Method decorator
9
- * ```typescript
10
- * import { Trace } from 'autotel/decorators'
11
- *
12
- * class OrderService {
13
- * @Trace('order.create', { withMetrics: true })
14
- * async createOrder(data: OrderData) {
15
- * return await db.orders.create(data)
16
- * }
17
- *
18
- * @Trace() // Uses method name as span name
19
- * async processPayment(orderId: string) {
20
- * return await stripe.charge(orderId)
21
- * }
22
- * }
23
- * ```
24
- */
25
-
26
- import type { TracingOptions, TraceContext } from './functional';
27
- import { getConfig } from './config';
28
- import { SpanStatusCode } from '@opentelemetry/api';
29
- import { createTraceContext } from './trace-context';
30
-
31
- /**
32
- * Options for @Trace method decorator
33
- */
34
- export interface TraceDecoratorOptions extends Omit<TracingOptions, 'name'> {
35
- /**
36
- * Custom span name. If not provided, uses the method name.
37
- */
38
- name?: string;
39
- }
40
-
41
- /**
42
- * @Trace - Method decorator for fine-grained tracing
43
- *
44
- * Wraps a class method with automatic tracing. Supports both patterns:
45
- * - Simple: method doesn't use ctx
46
- * - Advanced: method accesses ctx via this.ctx
47
- *
48
- * @example Simple usage (no ctx)
49
- * ```typescript
50
- * class OrderService {
51
- * @Trace()
52
- * async createOrder(data: OrderData) {
53
- * return await db.orders.create(data)
54
- * }
55
- * }
56
- * ```
57
- *
58
- * @example With custom name and options
59
- * ```typescript
60
- * class PaymentService {
61
- * @Trace('payment.charge', { withMetrics: true })
62
- * async chargeCard(amount: number) {
63
- * return await stripe.charges.create({ amount })
64
- * }
65
- * }
66
- * ```
67
- *
68
- * @example Accessing ctx
69
- * ```typescript
70
- * interface WithTraceContext {
71
- * ctx?: TraceContext
72
- * }
73
- *
74
- * class UserService {
75
- * @Trace()
76
- * async createUser(data: UserData) {
77
- * // Access ctx via this.ctx (available during execution)
78
- * const ctx = (this as unknown as WithTraceContext).ctx
79
- * if (ctx) {
80
- * ctx.setAttribute('user.id', data.id)
81
- * }
82
- * return await db.users.create(data)
83
- * }
84
- * }
85
- * ```
86
- */
87
- export function Trace(
88
- options?: TraceDecoratorOptions,
89
- ): <T extends (...args: unknown[]) => Promise<unknown>>(
90
- originalMethod: T,
91
- context: ClassMethodDecoratorContext,
92
- ) => T;
93
- export function Trace(
94
- name?: string,
95
- options?: TraceDecoratorOptions,
96
- ): <T extends (...args: unknown[]) => Promise<unknown>>(
97
- originalMethod: T,
98
- context: ClassMethodDecoratorContext,
99
- ) => T;
100
- export function Trace(
101
- nameOrOptions?: string | TraceDecoratorOptions,
102
- maybeOptions?: TraceDecoratorOptions,
103
- ): <T extends (...args: unknown[]) => Promise<unknown>>(
104
- originalMethod: T,
105
- context: ClassMethodDecoratorContext,
106
- ) => T {
107
- // Parse arguments
108
- const name =
109
- typeof nameOrOptions === 'string' ? nameOrOptions : nameOrOptions?.name;
110
- // Options are used in the returned decorator function, not here
111
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
112
- const _options: TraceDecoratorOptions =
113
- typeof nameOrOptions === 'string'
114
- ? maybeOptions || {}
115
- : nameOrOptions || {};
116
-
117
- // TypeScript 5+ decorator signature
118
- return function <T extends (...args: unknown[]) => Promise<unknown>>(
119
- originalMethod: T,
120
- context: ClassMethodDecoratorContext,
121
- ): T {
122
- const methodName = String(context.name);
123
-
124
- // Skip if not an async function
125
- // Check multiple ways to detect async functions (for different transpilation environments)
126
- // TypeScript decorators run at class definition time, so we need robust detection
127
- const methodStr = originalMethod?.toString() || '';
128
- const isAsync =
129
- originalMethod &&
130
- (originalMethod.constructor?.name === 'AsyncFunction' ||
131
- methodStr.trim().startsWith('async ') ||
132
- (methodStr.includes('[native code]') && methodStr.includes('async')) ||
133
- // Fallback: if function has async in its string representation
134
- /async\s+/.test(methodStr));
135
-
136
- if (!isAsync) {
137
- // Not an async function, return as-is
138
- return originalMethod;
139
- }
140
-
141
- const spanName = name || methodName;
142
-
143
- return async function <This>(
144
- this: This,
145
- ...args: unknown[]
146
- ): Promise<unknown> {
147
- const config = getConfig();
148
- const tracer = config.tracer;
149
-
150
- return tracer.startActiveSpan(spanName, async (span) => {
151
- try {
152
- // Make ctx available via this.ctx for methods that need it
153
- const ctx: TraceContext = createTraceContext(span);
154
-
155
- const originalCtx = (this as { ctx?: TraceContext }).ctx;
156
- try {
157
- (this as { ctx?: TraceContext }).ctx = ctx;
158
- const result = await originalMethod.apply(this, args as []);
159
- span.setStatus({ code: SpanStatusCode.OK });
160
- return result;
161
- } finally {
162
- // Restore original ctx
163
- if (originalCtx === undefined) {
164
- delete (this as { ctx?: TraceContext }).ctx;
165
- } else {
166
- (this as { ctx?: TraceContext }).ctx = originalCtx;
167
- }
168
- }
169
- } catch (error) {
170
- span.setStatus({
171
- code: SpanStatusCode.ERROR,
172
- message: error instanceof Error ? error.message : 'Unknown error',
173
- });
174
- span.recordException(
175
- error instanceof Error ? error : new Error(String(error)),
176
- );
177
- throw error;
178
- } finally {
179
- span.end();
180
- }
181
- });
182
- } as T;
183
- };
184
- }
185
-
186
- // Re-export types for convenience
187
-
188
- export { type TraceContext, type TracingOptions } from './functional';
@@ -1,41 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { defineEvent } from './define-event';
3
-
4
- describe('defineEvent', () => {
5
- it('validates payload and exposes schema metadata when provided', () => {
6
- const event = defineEvent(
7
- 'order.placed',
8
- {
9
- safeParse(input: unknown) {
10
- if (
11
- typeof input === 'object' &&
12
- input !== null &&
13
- 'orderId' in input &&
14
- typeof (input as Record<string, unknown>).orderId === 'string'
15
- ) {
16
- return {
17
- success: true as const,
18
- data: input as { orderId: string },
19
- };
20
- }
21
- return { success: false as const, error: new Error('invalid') };
22
- },
23
- },
24
- {
25
- toJsonSchema: () => ({
26
- type: 'object',
27
- properties: { orderId: { type: 'string' } },
28
- required: ['orderId'],
29
- }),
30
- },
31
- );
32
-
33
- expect(event.name).toBe('order.placed');
34
- expect(event.schemaMetadata?.source).toBe('zod');
35
- expect(event.schemaMetadata?.hash).toMatch(/^[a-f0-9]{64}$/);
36
- expect(() => event.track({ orderId: 'o-1' })).not.toThrow();
37
- expect(() => event.track({} as { orderId: string })).toThrow(
38
- /Schema validation failed/,
39
- );
40
- });
41
- });
@@ -1,58 +0,0 @@
1
- import { track } from './track';
2
- import { hashJson } from './stable-hash';
3
- import type { EventSchemaMetadata } from './event-subscriber';
4
-
5
- type SafeParseResult<T> =
6
- | { success: true; data: T }
7
- | { success: false; error: unknown };
8
-
9
- export interface SchemaLike<T> {
10
- safeParse(input: unknown): SafeParseResult<T>;
11
- }
12
-
13
- export interface DefineEventOptions<S> {
14
- toJsonSchema?: (schema: S) => unknown;
15
- }
16
-
17
- export interface DefinedEvent<Name extends string, Payload> {
18
- readonly name: Name;
19
- readonly schemaMetadata?: EventSchemaMetadata;
20
- track(payload: Payload): void;
21
- }
22
-
23
- export function defineEvent<
24
- Name extends string,
25
- Payload,
26
- S extends SchemaLike<Payload>,
27
- >(
28
- name: Name,
29
- schema: S,
30
- options: DefineEventOptions<S> = {},
31
- ): DefinedEvent<Name, Payload> {
32
- const jsonSchema = options.toJsonSchema?.(schema);
33
- const schemaMetadata = jsonSchema
34
- ? {
35
- source: 'zod' as const,
36
- jsonSchema,
37
- hash: hashJson(jsonSchema),
38
- }
39
- : undefined;
40
-
41
- return {
42
- name,
43
- schemaMetadata,
44
- track(payload: Payload) {
45
- const parsed = schema.safeParse(payload);
46
- if (!parsed.success) {
47
- throw new Error(
48
- `Invalid payload for event "${name}". Schema validation failed.`,
49
- );
50
- }
51
- track(
52
- name,
53
- parsed.data,
54
- schemaMetadata ? { schema: schemaMetadata } : undefined,
55
- );
56
- },
57
- };
58
- }
package/src/devtools.ts DELETED
@@ -1,60 +0,0 @@
1
- export interface AutotelDevtoolsConfig {
2
- enabled?: boolean;
3
- endpoint?: string;
4
- embedded?: boolean;
5
- host?: string;
6
- port?: number;
7
- verbose?: boolean;
8
- }
9
-
10
- export interface ResolvedAutotelDevtoolsConfig {
11
- enabled: boolean;
12
- endpoint?: string;
13
- embedded: boolean;
14
- host: string;
15
- port: number;
16
- verbose: boolean;
17
- }
18
-
19
- const defaultHost = '127.0.0.1';
20
- const defaultPort = 4318;
21
-
22
- export function resolveDevtoolsConfig(
23
- config: boolean | AutotelDevtoolsConfig | undefined,
24
- ): ResolvedAutotelDevtoolsConfig {
25
- if (!config) {
26
- return {
27
- enabled: false,
28
- endpoint: undefined,
29
- embedded: false,
30
- host: defaultHost,
31
- port: defaultPort,
32
- verbose: false,
33
- };
34
- }
35
-
36
- if (config === true) {
37
- return {
38
- enabled: true,
39
- endpoint: `http://${defaultHost}:${defaultPort}`,
40
- embedded: false,
41
- host: defaultHost,
42
- port: defaultPort,
43
- verbose: false,
44
- };
45
- }
46
-
47
- const enabled = config.enabled ?? true;
48
- const host = config.host ?? defaultHost;
49
- const port = config.port ?? defaultPort;
50
- const endpoint = config.endpoint ?? `http://${host}:${port}`;
51
-
52
- return {
53
- enabled,
54
- endpoint: enabled ? endpoint : undefined,
55
- embedded: enabled && (config.embedded ?? false),
56
- host,
57
- port,
58
- verbose: config.verbose ?? false,
59
- };
60
- }
@@ -1,68 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { createDrainPipeline } from './drain-pipeline';
3
-
4
- describe('createDrainPipeline', () => {
5
- it('batches by size and sends to drain', async () => {
6
- const batchDrain = vi.fn(async () => {});
7
- const pipeline = createDrainPipeline<number>({
8
- batch: { size: 2, intervalMs: 1000 },
9
- });
10
- const drain = pipeline(batchDrain);
11
-
12
- drain(1);
13
- drain(2);
14
- await new Promise((resolve) => setImmediate(resolve));
15
-
16
- expect(batchDrain).toHaveBeenCalledTimes(1);
17
- expect(batchDrain).toHaveBeenCalledWith([1, 2]);
18
- expect(drain.pending).toBe(0);
19
- });
20
-
21
- it('retries failed batches and eventually succeeds', async () => {
22
- let attempts = 0;
23
- const batchDrain = vi.fn(async () => {
24
- attempts++;
25
- if (attempts < 2) throw new Error('temporary');
26
- });
27
-
28
- const pipeline = createDrainPipeline<number>({
29
- batch: { size: 1, intervalMs: 1000 },
30
- retry: {
31
- maxAttempts: 3,
32
- initialDelayMs: 1,
33
- maxDelayMs: 2,
34
- backoff: 'fixed',
35
- jitter: false,
36
- },
37
- });
38
- const drain = pipeline(batchDrain);
39
-
40
- drain(42);
41
- await drain.flush();
42
-
43
- expect(batchDrain).toHaveBeenCalledTimes(2);
44
- expect(drain.pending).toBe(0);
45
- });
46
-
47
- it('drops overflowed events based on policy', async () => {
48
- const dropped: number[] = [];
49
- const batchDrain = vi.fn(async () => {});
50
- const pipeline = createDrainPipeline<number>({
51
- batch: { size: 10, intervalMs: 1000 },
52
- maxBufferSize: 2,
53
- dropPolicy: 'oldest',
54
- onDropped: (events) => dropped.push(...events),
55
- });
56
- const drain = pipeline(batchDrain);
57
-
58
- drain(1);
59
- drain(2);
60
- drain(3); // drops 1
61
-
62
- expect(dropped).toEqual([1]);
63
- expect(drain.pending).toBe(2);
64
-
65
- await drain.flush();
66
- expect(batchDrain).toHaveBeenCalledWith([2, 3]);
67
- });
68
- });
@@ -1,199 +0,0 @@
1
- export interface DrainPipelineOptions<T = unknown> {
2
- batch?: {
3
- /** Maximum events per batch. @default 50 */
4
- size?: number;
5
- /** Max time an event can stay buffered before flush. @default 5000 */
6
- intervalMs?: number;
7
- };
8
- retry?: {
9
- /** Total attempts including first try. @default 3 */
10
- maxAttempts?: number;
11
- /** Delay strategy between attempts. @default 'exponential' */
12
- backoff?: 'exponential' | 'linear' | 'fixed';
13
- /** Base delay for first retry. @default 1000 */
14
- initialDelayMs?: number;
15
- /** Max delay cap. @default 30000 */
16
- maxDelayMs?: number;
17
- /** Add random jitter to delays. @default true */
18
- jitter?: boolean;
19
- };
20
- /** Max buffered events before dropping. @default 1000 */
21
- maxBufferSize?: number;
22
- /** Overflow policy. @default 'oldest' */
23
- dropPolicy?: 'oldest' | 'newest';
24
- /** Called when events are dropped from overflow or exhausted retries. */
25
- onDropped?: (events: T[], error?: Error) => void;
26
- }
27
-
28
- export interface PipelineDrainFn<T> {
29
- (ctx: T): void;
30
- /** Flush all buffered events. */
31
- flush: () => Promise<void>;
32
- /** Flush and stop scheduling future timer work. */
33
- shutdown: () => Promise<void>;
34
- readonly pending: number;
35
- }
36
-
37
- function wait(ms: number): Promise<void> {
38
- return new Promise((resolve) => {
39
- const timer = setTimeout(resolve, ms);
40
- timer.unref?.();
41
- });
42
- }
43
-
44
- export function createDrainPipeline<T = unknown>(
45
- options?: DrainPipelineOptions<T>,
46
- ): (drain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T> {
47
- const batchSize = options?.batch?.size ?? 50;
48
- const intervalMs = options?.batch?.intervalMs ?? 5000;
49
- const maxBufferSize = options?.maxBufferSize ?? 1000;
50
- const maxAttempts = options?.retry?.maxAttempts ?? 3;
51
- const backoff = options?.retry?.backoff ?? 'exponential';
52
- const initialDelayMs = options?.retry?.initialDelayMs ?? 1000;
53
- const maxDelayMs = options?.retry?.maxDelayMs ?? 30_000;
54
- const jitter = options?.retry?.jitter ?? true;
55
- const dropPolicy = options?.dropPolicy ?? 'oldest';
56
- const onDropped = options?.onDropped;
57
-
58
- if (!Number.isFinite(batchSize) || batchSize <= 0) {
59
- throw new Error(
60
- `[autotel/drain-pipeline] batch.size must be a positive finite number, got: ${batchSize}`,
61
- );
62
- }
63
- if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
64
- throw new Error(
65
- `[autotel/drain-pipeline] batch.intervalMs must be a positive finite number, got: ${intervalMs}`,
66
- );
67
- }
68
- if (!Number.isFinite(maxBufferSize) || maxBufferSize <= 0) {
69
- throw new Error(
70
- `[autotel/drain-pipeline] maxBufferSize must be a positive finite number, got: ${maxBufferSize}`,
71
- );
72
- }
73
- if (!Number.isFinite(maxAttempts) || maxAttempts <= 0) {
74
- throw new Error(
75
- `[autotel/drain-pipeline] retry.maxAttempts must be a positive finite number, got: ${maxAttempts}`,
76
- );
77
- }
78
-
79
- return (drain: (batch: T[]) => void | Promise<void>): PipelineDrainFn<T> => {
80
- const buffer: T[] = [];
81
- let timer: ReturnType<typeof setTimeout> | null = null;
82
- let activeFlush: Promise<void> | null = null;
83
- let isShutdown = false;
84
-
85
- const clearTimer = () => {
86
- if (timer) {
87
- clearTimeout(timer);
88
- timer = null;
89
- }
90
- };
91
-
92
- const computeDelay = (attempt: number): number => {
93
- const base =
94
- backoff === 'fixed'
95
- ? initialDelayMs
96
- : backoff === 'linear'
97
- ? initialDelayMs * attempt
98
- : initialDelayMs * 2 ** (attempt - 1);
99
-
100
- const bounded = Math.min(base, maxDelayMs);
101
- if (!jitter || bounded <= 0) return bounded;
102
- const factor = 0.5 + Math.random(); // [0.5, 1.5)
103
- return Math.max(0, Math.round(bounded * factor));
104
- };
105
-
106
- const sendWithRetry = async (batch: T[]): Promise<void> => {
107
- let lastError: Error | undefined;
108
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
109
- try {
110
- await drain(batch);
111
- return;
112
- } catch (error) {
113
- lastError = error instanceof Error ? error : new Error(String(error));
114
- if (attempt < maxAttempts) {
115
- await wait(computeDelay(attempt));
116
- }
117
- }
118
- }
119
- onDropped?.(batch, lastError);
120
- };
121
-
122
- const drainBuffer = async (): Promise<void> => {
123
- while (buffer.length > 0) {
124
- const batch = buffer.splice(0, batchSize);
125
- await sendWithRetry(batch);
126
- }
127
- };
128
-
129
- const scheduleFlush = () => {
130
- if (isShutdown || timer || activeFlush) return;
131
- timer = setTimeout(() => {
132
- timer = null;
133
- startFlush();
134
- }, intervalMs);
135
- timer.unref?.();
136
- };
137
-
138
- const startFlush = () => {
139
- if (activeFlush || isShutdown) return;
140
- activeFlush = drainBuffer().finally(() => {
141
- activeFlush = null;
142
- if (isShutdown) return;
143
- if (buffer.length >= batchSize) {
144
- startFlush();
145
- } else if (buffer.length > 0) {
146
- scheduleFlush();
147
- }
148
- });
149
- };
150
-
151
- const push = (ctx: T) => {
152
- if (isShutdown) return;
153
-
154
- if (buffer.length >= maxBufferSize) {
155
- if (dropPolicy === 'newest') {
156
- onDropped?.([ctx]);
157
- return;
158
- }
159
- const dropped = buffer.splice(0, 1);
160
- onDropped?.(dropped);
161
- }
162
-
163
- buffer.push(ctx);
164
- if (buffer.length >= batchSize) {
165
- clearTimer();
166
- startFlush();
167
- } else {
168
- scheduleFlush();
169
- }
170
- };
171
-
172
- const flush = async (): Promise<void> => {
173
- clearTimer();
174
- if (activeFlush) await activeFlush;
175
-
176
- const snapshot = buffer.length;
177
- if (snapshot <= 0) return;
178
- const toFlush = buffer.splice(0, snapshot);
179
- while (toFlush.length > 0) {
180
- const batch = toFlush.splice(0, batchSize);
181
- await sendWithRetry(batch);
182
- }
183
- };
184
-
185
- const shutdown = async (): Promise<void> => {
186
- isShutdown = true;
187
- await flush();
188
- };
189
-
190
- const fn = push as PipelineDrainFn<T>;
191
- fn.flush = flush;
192
- fn.shutdown = shutdown;
193
- Object.defineProperty(fn, 'pending', {
194
- enumerable: true,
195
- get: () => buffer.length,
196
- });
197
- return fn;
198
- };
199
- }