autotel 2.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.
Files changed (272) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1946 -0
  3. package/dist/chunk-2LNRY4QK.js +273 -0
  4. package/dist/chunk-2LNRY4QK.js.map +1 -0
  5. package/dist/chunk-3HENGDW2.js +587 -0
  6. package/dist/chunk-3HENGDW2.js.map +1 -0
  7. package/dist/chunk-4OAT42CA.cjs +73 -0
  8. package/dist/chunk-4OAT42CA.cjs.map +1 -0
  9. package/dist/chunk-5GWX5LFW.js +70 -0
  10. package/dist/chunk-5GWX5LFW.js.map +1 -0
  11. package/dist/chunk-5R2M36QB.js +195 -0
  12. package/dist/chunk-5R2M36QB.js.map +1 -0
  13. package/dist/chunk-5ZN622AO.js +73 -0
  14. package/dist/chunk-5ZN622AO.js.map +1 -0
  15. package/dist/chunk-77MSMAUQ.cjs +498 -0
  16. package/dist/chunk-77MSMAUQ.cjs.map +1 -0
  17. package/dist/chunk-ABPEQ6RK.cjs +596 -0
  18. package/dist/chunk-ABPEQ6RK.cjs.map +1 -0
  19. package/dist/chunk-BWYGJKRB.js +95 -0
  20. package/dist/chunk-BWYGJKRB.js.map +1 -0
  21. package/dist/chunk-BZHG5IZ4.js +73 -0
  22. package/dist/chunk-BZHG5IZ4.js.map +1 -0
  23. package/dist/chunk-G7VZBCD6.cjs +35 -0
  24. package/dist/chunk-G7VZBCD6.cjs.map +1 -0
  25. package/dist/chunk-GVLK7YUU.cjs +30 -0
  26. package/dist/chunk-GVLK7YUU.cjs.map +1 -0
  27. package/dist/chunk-HCCXC7XG.js +205 -0
  28. package/dist/chunk-HCCXC7XG.js.map +1 -0
  29. package/dist/chunk-HE6T6FIX.cjs +203 -0
  30. package/dist/chunk-HE6T6FIX.cjs.map +1 -0
  31. package/dist/chunk-KIXWPOCO.cjs +100 -0
  32. package/dist/chunk-KIXWPOCO.cjs.map +1 -0
  33. package/dist/chunk-KVGNW3FC.js +87 -0
  34. package/dist/chunk-KVGNW3FC.js.map +1 -0
  35. package/dist/chunk-LITNXTTT.js +3 -0
  36. package/dist/chunk-LITNXTTT.js.map +1 -0
  37. package/dist/chunk-M4ANN7RL.js +114 -0
  38. package/dist/chunk-M4ANN7RL.js.map +1 -0
  39. package/dist/chunk-NC52UBR2.cjs +32 -0
  40. package/dist/chunk-NC52UBR2.cjs.map +1 -0
  41. package/dist/chunk-NHCNRQD3.cjs +212 -0
  42. package/dist/chunk-NHCNRQD3.cjs.map +1 -0
  43. package/dist/chunk-NZ72VDNY.cjs +4 -0
  44. package/dist/chunk-NZ72VDNY.cjs.map +1 -0
  45. package/dist/chunk-P6JUDYNO.js +57 -0
  46. package/dist/chunk-P6JUDYNO.js.map +1 -0
  47. package/dist/chunk-RJYY7BWX.js +1349 -0
  48. package/dist/chunk-RJYY7BWX.js.map +1 -0
  49. package/dist/chunk-TRI4V5BF.cjs +126 -0
  50. package/dist/chunk-TRI4V5BF.cjs.map +1 -0
  51. package/dist/chunk-UL33I6IS.js +139 -0
  52. package/dist/chunk-UL33I6IS.js.map +1 -0
  53. package/dist/chunk-URRW6M2C.cjs +61 -0
  54. package/dist/chunk-URRW6M2C.cjs.map +1 -0
  55. package/dist/chunk-UY3UYPBZ.cjs +77 -0
  56. package/dist/chunk-UY3UYPBZ.cjs.map +1 -0
  57. package/dist/chunk-W3253FGB.cjs +277 -0
  58. package/dist/chunk-W3253FGB.cjs.map +1 -0
  59. package/dist/chunk-W7LHZVQF.js +26 -0
  60. package/dist/chunk-W7LHZVQF.js.map +1 -0
  61. package/dist/chunk-WBWNM6LB.cjs +1360 -0
  62. package/dist/chunk-WBWNM6LB.cjs.map +1 -0
  63. package/dist/chunk-WFJ7L2RV.js +494 -0
  64. package/dist/chunk-WFJ7L2RV.js.map +1 -0
  65. package/dist/chunk-X4RMFFMR.js +28 -0
  66. package/dist/chunk-X4RMFFMR.js.map +1 -0
  67. package/dist/chunk-Y4Y2S7BM.cjs +92 -0
  68. package/dist/chunk-Y4Y2S7BM.cjs.map +1 -0
  69. package/dist/chunk-YLPNXZFI.cjs +143 -0
  70. package/dist/chunk-YLPNXZFI.cjs.map +1 -0
  71. package/dist/chunk-YTXEZ4SD.cjs +77 -0
  72. package/dist/chunk-YTXEZ4SD.cjs.map +1 -0
  73. package/dist/chunk-Z6ZWNWWR.js +30 -0
  74. package/dist/chunk-Z6ZWNWWR.js.map +1 -0
  75. package/dist/config.cjs +26 -0
  76. package/dist/config.cjs.map +1 -0
  77. package/dist/config.d.cts +75 -0
  78. package/dist/config.d.ts +75 -0
  79. package/dist/config.js +5 -0
  80. package/dist/config.js.map +1 -0
  81. package/dist/db.cjs +233 -0
  82. package/dist/db.cjs.map +1 -0
  83. package/dist/db.d.cts +123 -0
  84. package/dist/db.d.ts +123 -0
  85. package/dist/db.js +228 -0
  86. package/dist/db.js.map +1 -0
  87. package/dist/decorators.cjs +67 -0
  88. package/dist/decorators.cjs.map +1 -0
  89. package/dist/decorators.d.cts +91 -0
  90. package/dist/decorators.d.ts +91 -0
  91. package/dist/decorators.js +65 -0
  92. package/dist/decorators.js.map +1 -0
  93. package/dist/event-subscriber.cjs +6 -0
  94. package/dist/event-subscriber.cjs.map +1 -0
  95. package/dist/event-subscriber.d.cts +116 -0
  96. package/dist/event-subscriber.d.ts +116 -0
  97. package/dist/event-subscriber.js +3 -0
  98. package/dist/event-subscriber.js.map +1 -0
  99. package/dist/event-testing.cjs +21 -0
  100. package/dist/event-testing.cjs.map +1 -0
  101. package/dist/event-testing.d.cts +110 -0
  102. package/dist/event-testing.d.ts +110 -0
  103. package/dist/event-testing.js +4 -0
  104. package/dist/event-testing.js.map +1 -0
  105. package/dist/event.cjs +30 -0
  106. package/dist/event.cjs.map +1 -0
  107. package/dist/event.d.cts +282 -0
  108. package/dist/event.d.ts +282 -0
  109. package/dist/event.js +13 -0
  110. package/dist/event.js.map +1 -0
  111. package/dist/exporters.cjs +17 -0
  112. package/dist/exporters.cjs.map +1 -0
  113. package/dist/exporters.d.cts +1 -0
  114. package/dist/exporters.d.ts +1 -0
  115. package/dist/exporters.js +4 -0
  116. package/dist/exporters.js.map +1 -0
  117. package/dist/functional.cjs +46 -0
  118. package/dist/functional.cjs.map +1 -0
  119. package/dist/functional.d.cts +478 -0
  120. package/dist/functional.d.ts +478 -0
  121. package/dist/functional.js +13 -0
  122. package/dist/functional.js.map +1 -0
  123. package/dist/http.cjs +189 -0
  124. package/dist/http.cjs.map +1 -0
  125. package/dist/http.d.cts +169 -0
  126. package/dist/http.d.ts +169 -0
  127. package/dist/http.js +184 -0
  128. package/dist/http.js.map +1 -0
  129. package/dist/index.cjs +333 -0
  130. package/dist/index.cjs.map +1 -0
  131. package/dist/index.d.cts +758 -0
  132. package/dist/index.d.ts +758 -0
  133. package/dist/index.js +143 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/instrumentation.cjs +182 -0
  136. package/dist/instrumentation.cjs.map +1 -0
  137. package/dist/instrumentation.d.cts +49 -0
  138. package/dist/instrumentation.d.ts +49 -0
  139. package/dist/instrumentation.js +179 -0
  140. package/dist/instrumentation.js.map +1 -0
  141. package/dist/logger.cjs +19 -0
  142. package/dist/logger.cjs.map +1 -0
  143. package/dist/logger.d.cts +146 -0
  144. package/dist/logger.d.ts +146 -0
  145. package/dist/logger.js +6 -0
  146. package/dist/logger.js.map +1 -0
  147. package/dist/metric-helpers.cjs +31 -0
  148. package/dist/metric-helpers.cjs.map +1 -0
  149. package/dist/metric-helpers.d.cts +13 -0
  150. package/dist/metric-helpers.d.ts +13 -0
  151. package/dist/metric-helpers.js +6 -0
  152. package/dist/metric-helpers.js.map +1 -0
  153. package/dist/metric-testing.cjs +21 -0
  154. package/dist/metric-testing.cjs.map +1 -0
  155. package/dist/metric-testing.d.cts +110 -0
  156. package/dist/metric-testing.d.ts +110 -0
  157. package/dist/metric-testing.js +4 -0
  158. package/dist/metric-testing.js.map +1 -0
  159. package/dist/metric.cjs +26 -0
  160. package/dist/metric.cjs.map +1 -0
  161. package/dist/metric.d.cts +240 -0
  162. package/dist/metric.d.ts +240 -0
  163. package/dist/metric.js +9 -0
  164. package/dist/metric.js.map +1 -0
  165. package/dist/processors.cjs +17 -0
  166. package/dist/processors.cjs.map +1 -0
  167. package/dist/processors.d.cts +1 -0
  168. package/dist/processors.d.ts +1 -0
  169. package/dist/processors.js +4 -0
  170. package/dist/processors.js.map +1 -0
  171. package/dist/sampling.cjs +40 -0
  172. package/dist/sampling.cjs.map +1 -0
  173. package/dist/sampling.d.cts +260 -0
  174. package/dist/sampling.d.ts +260 -0
  175. package/dist/sampling.js +7 -0
  176. package/dist/sampling.js.map +1 -0
  177. package/dist/semantic-helpers.cjs +35 -0
  178. package/dist/semantic-helpers.cjs.map +1 -0
  179. package/dist/semantic-helpers.d.cts +442 -0
  180. package/dist/semantic-helpers.d.ts +442 -0
  181. package/dist/semantic-helpers.js +14 -0
  182. package/dist/semantic-helpers.js.map +1 -0
  183. package/dist/tail-sampling-processor.cjs +13 -0
  184. package/dist/tail-sampling-processor.cjs.map +1 -0
  185. package/dist/tail-sampling-processor.d.cts +27 -0
  186. package/dist/tail-sampling-processor.d.ts +27 -0
  187. package/dist/tail-sampling-processor.js +4 -0
  188. package/dist/tail-sampling-processor.js.map +1 -0
  189. package/dist/testing.cjs +286 -0
  190. package/dist/testing.cjs.map +1 -0
  191. package/dist/testing.d.cts +291 -0
  192. package/dist/testing.d.ts +291 -0
  193. package/dist/testing.js +263 -0
  194. package/dist/testing.js.map +1 -0
  195. package/dist/trace-context-DRZdUvVY.d.cts +181 -0
  196. package/dist/trace-context-DRZdUvVY.d.ts +181 -0
  197. package/dist/trace-helpers.cjs +54 -0
  198. package/dist/trace-helpers.cjs.map +1 -0
  199. package/dist/trace-helpers.d.cts +524 -0
  200. package/dist/trace-helpers.d.ts +524 -0
  201. package/dist/trace-helpers.js +5 -0
  202. package/dist/trace-helpers.js.map +1 -0
  203. package/dist/tracer-provider.cjs +21 -0
  204. package/dist/tracer-provider.cjs.map +1 -0
  205. package/dist/tracer-provider.d.cts +169 -0
  206. package/dist/tracer-provider.d.ts +169 -0
  207. package/dist/tracer-provider.js +4 -0
  208. package/dist/tracer-provider.js.map +1 -0
  209. package/package.json +280 -0
  210. package/src/baggage-span-processor.test.ts +202 -0
  211. package/src/baggage-span-processor.ts +98 -0
  212. package/src/circuit-breaker.test.ts +341 -0
  213. package/src/circuit-breaker.ts +184 -0
  214. package/src/config.test.ts +94 -0
  215. package/src/config.ts +169 -0
  216. package/src/db.test.ts +252 -0
  217. package/src/db.ts +447 -0
  218. package/src/decorators.test.ts +203 -0
  219. package/src/decorators.ts +188 -0
  220. package/src/env-config.test.ts +246 -0
  221. package/src/env-config.ts +158 -0
  222. package/src/event-queue.test.ts +222 -0
  223. package/src/event-queue.ts +203 -0
  224. package/src/event-subscriber.ts +136 -0
  225. package/src/event-testing.ts +197 -0
  226. package/src/event.test.ts +718 -0
  227. package/src/event.ts +556 -0
  228. package/src/exporters.ts +96 -0
  229. package/src/functional.test.ts +1059 -0
  230. package/src/functional.ts +2295 -0
  231. package/src/http.test.ts +487 -0
  232. package/src/http.ts +424 -0
  233. package/src/index.ts +158 -0
  234. package/src/init.customization.test.ts +210 -0
  235. package/src/init.integrations.test.ts +366 -0
  236. package/src/init.openllmetry.test.ts +282 -0
  237. package/src/init.protocol.test.ts +215 -0
  238. package/src/init.ts +1426 -0
  239. package/src/instrumentation.test.ts +108 -0
  240. package/src/instrumentation.ts +308 -0
  241. package/src/logger.test.ts +117 -0
  242. package/src/logger.ts +246 -0
  243. package/src/metric-helpers.ts +47 -0
  244. package/src/metric-testing.ts +197 -0
  245. package/src/metric.ts +434 -0
  246. package/src/metrics.test.ts +205 -0
  247. package/src/operation-context.ts +93 -0
  248. package/src/processors.ts +106 -0
  249. package/src/rate-limiter.test.ts +199 -0
  250. package/src/rate-limiter.ts +98 -0
  251. package/src/sampling.test.ts +513 -0
  252. package/src/sampling.ts +428 -0
  253. package/src/semantic-helpers.test.ts +311 -0
  254. package/src/semantic-helpers.ts +584 -0
  255. package/src/shutdown.test.ts +311 -0
  256. package/src/shutdown.ts +222 -0
  257. package/src/stub.integration.test.ts +361 -0
  258. package/src/tail-sampling-processor.test.ts +226 -0
  259. package/src/tail-sampling-processor.ts +51 -0
  260. package/src/testing.ts +670 -0
  261. package/src/trace-context.ts +470 -0
  262. package/src/trace-helpers.new.test.ts +278 -0
  263. package/src/trace-helpers.test.ts +242 -0
  264. package/src/trace-helpers.ts +690 -0
  265. package/src/tracer-provider.test.ts +183 -0
  266. package/src/tracer-provider.ts +266 -0
  267. package/src/track.test.ts +153 -0
  268. package/src/track.ts +120 -0
  269. package/src/validation.test.ts +306 -0
  270. package/src/validation.ts +239 -0
  271. package/src/variable-name-inference.test.ts +178 -0
  272. package/src/variable-name-inference.ts +242 -0
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Sampling Strategies
3
+ *
4
+ * Provides intelligent sampling beyond simple random rates.
5
+ * Helps reduce telemetry costs while capturing critical data.
6
+ *
7
+ * Key strategies:
8
+ * - Always trace errors and slow requests (critical for debugging)
9
+ * - Sample by user ID for consistent request tracing
10
+ * - Adaptive sampling based on load
11
+ * - Sample by feature flags for A/B testing correlation
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { AlwaysOnErrorSampler, UserIdSampler } from './sampling'
16
+ *
17
+ * @Instrumented({
18
+ * serviceName: 'user',
19
+ * sampler: new AlwaysOnErrorSampler(0.1) // 10% baseline, 100% on errors
20
+ * })
21
+ * class UserService { }
22
+ * ```
23
+ */
24
+
25
+ import { type Logger } from './logger';
26
+
27
+ /**
28
+ * Sampler interface - return true to trace, false to skip
29
+ */
30
+ export interface Sampler {
31
+ /**
32
+ * Decide whether to trace this operation
33
+ *
34
+ * @param context - Sampling context
35
+ * @returns true to trace, false to skip
36
+ */
37
+ shouldSample(context: SamplingContext): boolean;
38
+
39
+ /**
40
+ * Whether this sampler needs tail sampling (post-execution decision)
41
+ * If true, spans are always created and shouldKeepTrace() is called after execution
42
+ *
43
+ * @returns true if this sampler needs to evaluate after operation completes
44
+ */
45
+ needsTailSampling?(): boolean;
46
+
47
+ /**
48
+ * Re-evaluate sampling decision after operation completes (tail sampling)
49
+ * Only called if needsTailSampling() returns true
50
+ *
51
+ * @param context - Sampling context
52
+ * @param result - Operation result
53
+ * @returns true if this trace should be kept, false to drop it
54
+ */
55
+ shouldKeepTrace?(context: SamplingContext, result: OperationResult): boolean;
56
+ }
57
+
58
+ /**
59
+ * Context information for sampling decisions
60
+ */
61
+ export interface SamplingContext {
62
+ /** Operation name */
63
+ operationName: string;
64
+ /** Method arguments (for extracting user IDs, etc.) */
65
+ args: unknown[];
66
+ /** Optional metadata (e.g., feature flags, request headers) */
67
+ metadata?: Record<string, unknown>;
68
+ }
69
+
70
+ /**
71
+ * Result of a trace operation (for post-execution sampling)
72
+ */
73
+ export interface OperationResult {
74
+ /** Whether the operation succeeded */
75
+ success: boolean;
76
+ /** Duration in milliseconds */
77
+ duration: number;
78
+ /** Error if operation failed */
79
+ error?: Error;
80
+ }
81
+
82
+ /**
83
+ * Simple random sampler
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * new RandomSampler(0.1) // Sample 10% of requests
88
+ * ```
89
+ */
90
+ export class RandomSampler implements Sampler {
91
+ constructor(private readonly sampleRate: number) {
92
+ if (sampleRate < 0 || sampleRate > 1) {
93
+ throw new Error('Sample rate must be between 0 and 1');
94
+ }
95
+ }
96
+
97
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
98
+ shouldSample(_context: SamplingContext): boolean {
99
+ return Math.random() < this.sampleRate;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Always sample (100% tracing)
105
+ */
106
+ export class AlwaysSampler implements Sampler {
107
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
108
+ shouldSample(_context: SamplingContext): boolean {
109
+ return true;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Never sample (0% tracing)
115
+ */
116
+ export class NeverSampler implements Sampler {
117
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
118
+ shouldSample(_context: SamplingContext): boolean {
119
+ return false;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Adaptive sampler that always traces errors and slow requests
125
+ *
126
+ * This is the recommended sampler for production use.
127
+ * It ensures you never miss critical issues while keeping costs down.
128
+ *
129
+ * Strategy:
130
+ * - Always trace errors (critical for debugging)
131
+ * - Always trace slow requests (performance issues)
132
+ * - Use baseline sample rate for successful fast requests
133
+ *
134
+ * **IMPORTANT - Tail Sampling Requirement:**
135
+ * This sampler uses tail sampling (makes decisions AFTER execution).
136
+ * You MUST use TailSamplingSpanProcessor for it to work correctly:
137
+ *
138
+ * - If using initInstrumentation(): TailSamplingSpanProcessor is auto-configured
139
+ * - If using custom TracerProvider: You MUST manually register TailSamplingSpanProcessor
140
+ *
141
+ * Without TailSamplingSpanProcessor, ALL spans are exported (defeating the cost savings).
142
+ *
143
+ * @see TailSamplingSpanProcessor
144
+ * @see README.md "Tail Sampling with Custom Providers" section
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * new AdaptiveSampler({
149
+ * baselineSampleRate: 0.1, // 10% of normal requests
150
+ * slowThresholdMs: 1000, // Requests > 1s are "slow"
151
+ * alwaysSampleErrors: true, // Always trace errors
152
+ * alwaysSampleSlow: true // Always trace slow requests
153
+ * })
154
+ * ```
155
+ */
156
+ export class AdaptiveSampler implements Sampler {
157
+ private baselineSampleRate: number;
158
+ private slowThresholdMs: number;
159
+ private alwaysSampleErrors: boolean;
160
+ private alwaysSampleSlow: boolean;
161
+ private logger?: Logger;
162
+
163
+ // Track whether we should sample this request
164
+ private readonly samplingDecisions = new WeakMap<unknown[], boolean>();
165
+ // Track operation results to enable post-execution decision
166
+ private readonly operationResults = new WeakMap<unknown[], OperationResult>();
167
+
168
+ constructor(
169
+ options: {
170
+ baselineSampleRate?: number;
171
+ slowThresholdMs?: number;
172
+ alwaysSampleErrors?: boolean;
173
+ alwaysSampleSlow?: boolean;
174
+ logger?: Logger;
175
+ } = {},
176
+ ) {
177
+ this.baselineSampleRate = options.baselineSampleRate ?? 0.1;
178
+ this.slowThresholdMs = options.slowThresholdMs ?? 1000;
179
+ this.alwaysSampleErrors = options.alwaysSampleErrors ?? true;
180
+ this.alwaysSampleSlow = options.alwaysSampleSlow ?? true;
181
+ this.logger = options.logger;
182
+
183
+ if (this.baselineSampleRate < 0 || this.baselineSampleRate > 1) {
184
+ throw new Error('Baseline sample rate must be between 0 and 1');
185
+ }
186
+ }
187
+
188
+ needsTailSampling(): boolean {
189
+ // AdaptiveSampler ALWAYS needs tail sampling to implement error/slow capture
190
+ return true;
191
+ }
192
+
193
+ shouldSample(context: SamplingContext): boolean {
194
+ // For tail sampling, we optimistically create spans for all requests
195
+ // The real decision happens in shouldKeepTrace() after execution
196
+ // We still store the baseline decision for shouldKeepTrace() to use
197
+ const baselineDecision = Math.random() < this.baselineSampleRate;
198
+ this.samplingDecisions.set(context.args, baselineDecision);
199
+
200
+ // Always return true to create the span (tail sampling will decide if we keep it)
201
+ return true;
202
+ }
203
+
204
+ /**
205
+ * Re-evaluate sampling decision after operation completes
206
+ *
207
+ * This allows us to always capture errors and slow requests,
208
+ * even if they weren't initially sampled.
209
+ *
210
+ * @param context - Sampling context
211
+ * @param result - Operation result
212
+ * @returns true if this operation should be kept (not discarded)
213
+ */
214
+ shouldKeepTrace(context: SamplingContext, result: OperationResult): boolean {
215
+ const baselineDecision = this.samplingDecisions.get(context.args) ?? false;
216
+
217
+ // Always keep errors
218
+ if (this.alwaysSampleErrors && !result.success) {
219
+ if (!baselineDecision) {
220
+ this.logger?.debug('Adaptive sampling: Keeping error trace', {
221
+ operation: context.operationName,
222
+ error: result.error?.message,
223
+ });
224
+ }
225
+ return true;
226
+ }
227
+
228
+ // Always keep slow requests
229
+ if (this.alwaysSampleSlow && result.duration >= this.slowThresholdMs) {
230
+ if (!baselineDecision) {
231
+ this.logger?.debug('Adaptive sampling: Keeping slow trace', {
232
+ operation: context.operationName,
233
+ duration: result.duration,
234
+ });
235
+ }
236
+ return true;
237
+ }
238
+
239
+ // Otherwise, use baseline decision
240
+ return baselineDecision;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * User-based sampler for consistent tracing
246
+ *
247
+ * Always samples requests from specific user IDs.
248
+ * Useful for debugging specific user issues or monitoring VIP users.
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * new UserIdSampler({
253
+ * baselineSampleRate: 0.01, // 1% of normal users
254
+ * alwaysSampleUsers: ['vip_123'], // Always trace VIP users
255
+ * extractUserId: (args) => args[0]?.userId // Extract user ID from first arg
256
+ * })
257
+ * ```
258
+ */
259
+ export class UserIdSampler implements Sampler {
260
+ private baselineSampleRate: number;
261
+ private alwaysSampleUsers: Set<string>;
262
+ private extractUserId: (args: unknown[]) => string | undefined;
263
+ private logger?: Logger;
264
+
265
+ constructor(options: {
266
+ baselineSampleRate?: number;
267
+ alwaysSampleUsers?: string[];
268
+ extractUserId: (args: unknown[]) => string | undefined;
269
+ logger?: Logger;
270
+ }) {
271
+ this.baselineSampleRate = options.baselineSampleRate ?? 0.1;
272
+ this.alwaysSampleUsers = new Set(options.alwaysSampleUsers || []);
273
+ this.extractUserId = options.extractUserId;
274
+ this.logger = options.logger;
275
+ }
276
+
277
+ shouldSample(context: SamplingContext): boolean {
278
+ const userId = this.extractUserId(context.args);
279
+
280
+ // Always sample specific users
281
+ if (userId && this.alwaysSampleUsers.has(userId)) {
282
+ this.logger?.debug('Sampling user request', {
283
+ operation: context.operationName,
284
+ userId,
285
+ });
286
+ return true;
287
+ }
288
+
289
+ // For consistent per-user sampling, hash the user ID
290
+ if (userId) {
291
+ const hash = this.hashString(userId);
292
+ return hash < this.baselineSampleRate;
293
+ }
294
+
295
+ // Fallback to random sampling if no user ID
296
+ return Math.random() < this.baselineSampleRate;
297
+ }
298
+
299
+ /**
300
+ * Add user IDs to always-sample list
301
+ */
302
+ addAlwaysSampleUsers(...userIds: string[]): void {
303
+ for (const userId of userIds) {
304
+ this.alwaysSampleUsers.add(userId);
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Remove user IDs from always-sample list
310
+ */
311
+ removeAlwaysSampleUsers(...userIds: string[]): void {
312
+ for (const userId of userIds) {
313
+ this.alwaysSampleUsers.delete(userId);
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Simple hash function for consistent user sampling
319
+ */
320
+ private hashString(str: string): number {
321
+ let hash = 0;
322
+ for (let i = 0; i < str.length; i++) {
323
+ const char = str.codePointAt(i) ?? 0;
324
+ hash = (hash << 5) - hash + char;
325
+ hash = hash & hash; // Convert to 32-bit integer
326
+ }
327
+ return Math.abs(hash) / 2_147_483_647; // Normalize to 0-1
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Composite sampler that combines multiple samplers
333
+ *
334
+ * Samples if ANY of the child samplers returns true.
335
+ *
336
+ * @example
337
+ * ```typescript
338
+ * new CompositeSampler([
339
+ * new UserIdSampler({ extractUserId: (args) => args[0]?.userId }),
340
+ * new AdaptiveSampler({ baselineSampleRate: 0.1 })
341
+ * ])
342
+ * ```
343
+ */
344
+ export class CompositeSampler implements Sampler {
345
+ constructor(private readonly samplers: Sampler[]) {
346
+ if (samplers.length === 0) {
347
+ throw new Error('CompositeSampler requires at least one child sampler');
348
+ }
349
+ }
350
+
351
+ shouldSample(context: SamplingContext): boolean {
352
+ return this.samplers.some((sampler) => sampler.shouldSample(context));
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Feature flag sampler
358
+ *
359
+ * Always samples requests with specific feature flags enabled.
360
+ * Perfect for correlating A/B test experiments with metrics.
361
+ *
362
+ * @example
363
+ * ```typescript
364
+ * new FeatureFlagSampler({
365
+ * baselineSampleRate: 0.01,
366
+ * alwaysSampleFlags: ['new_checkout', 'experimental_ui'],
367
+ * extractFlags: (args, metadata) => metadata?.featureFlags
368
+ * })
369
+ * ```
370
+ */
371
+ export class FeatureFlagSampler implements Sampler {
372
+ private baselineSampleRate: number;
373
+ private alwaysSampleFlags: Set<string>;
374
+ private extractFlags: (
375
+ args: unknown[],
376
+ metadata?: Record<string, unknown>,
377
+ ) => string[] | undefined;
378
+ private logger?: Logger;
379
+
380
+ constructor(options: {
381
+ baselineSampleRate?: number;
382
+ alwaysSampleFlags?: string[];
383
+ extractFlags: (
384
+ args: unknown[],
385
+ metadata?: Record<string, unknown>,
386
+ ) => string[] | undefined;
387
+ logger?: Logger;
388
+ }) {
389
+ this.baselineSampleRate = options.baselineSampleRate ?? 0.1;
390
+ this.alwaysSampleFlags = new Set(options.alwaysSampleFlags || []);
391
+ this.extractFlags = options.extractFlags;
392
+ this.logger = options.logger;
393
+ }
394
+
395
+ shouldSample(context: SamplingContext): boolean {
396
+ const flags = this.extractFlags(context.args, context.metadata);
397
+
398
+ // Always sample if any monitored flag is enabled
399
+ if (flags && flags.some((flag) => this.alwaysSampleFlags.has(flag))) {
400
+ this.logger?.debug('Sampling feature flag request', {
401
+ operation: context.operationName,
402
+ flags,
403
+ });
404
+ return true;
405
+ }
406
+
407
+ // Fallback to random sampling
408
+ return Math.random() < this.baselineSampleRate;
409
+ }
410
+
411
+ /**
412
+ * Add feature flags to always-sample list
413
+ */
414
+ addAlwaysSampleFlags(...flags: string[]): void {
415
+ for (const flag of flags) {
416
+ this.alwaysSampleFlags.add(flag);
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Remove feature flags from always-sample list
422
+ */
423
+ removeAlwaysSampleFlags(...flags: string[]): void {
424
+ for (const flag of flags) {
425
+ this.alwaysSampleFlags.delete(flag);
426
+ }
427
+ }
428
+ }