autotel-edge 3.0.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/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/chunk-F32WSLNX.js +309 -0
- package/dist/chunk-F32WSLNX.js.map +1 -0
- package/dist/events.d.ts +86 -0
- package/dist/events.js +157 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +326 -0
- package/dist/index.js +921 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +89 -0
- package/dist/logger.js +81 -0
- package/dist/logger.js.map +1 -0
- package/dist/sampling.d.ts +166 -0
- package/dist/sampling.js +108 -0
- package/dist/sampling.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-Dj85cPUj.d.ts +182 -0
- package/package.json +88 -0
- package/src/api/logger.test.ts +367 -0
- package/src/api/logger.ts +197 -0
- package/src/compose.ts +243 -0
- package/src/core/buffer.ts +16 -0
- package/src/core/config.test.ts +388 -0
- package/src/core/config.ts +167 -0
- package/src/core/context.ts +224 -0
- package/src/core/exporter.ts +99 -0
- package/src/core/provider.ts +45 -0
- package/src/core/span.ts +222 -0
- package/src/core/spanprocessor.test.ts +521 -0
- package/src/core/spanprocessor.ts +232 -0
- package/src/core/trace-context.ts +66 -0
- package/src/core/tracer.test.ts +123 -0
- package/src/core/tracer.ts +216 -0
- package/src/events/index.test.ts +242 -0
- package/src/events/index.ts +338 -0
- package/src/events.ts +6 -0
- package/src/functional.test.ts +702 -0
- package/src/functional.ts +846 -0
- package/src/index.ts +81 -0
- package/src/logger.ts +13 -0
- package/src/sampling/index.test.ts +297 -0
- package/src/sampling/index.ts +276 -0
- package/src/sampling.ts +6 -0
- package/src/testing/index.ts +9 -0
- package/src/testing.ts +6 -0
- package/src/types.ts +267 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functional API for autotel-edge
|
|
3
|
+
*
|
|
4
|
+
* Provides zero-boilerplate tracing helpers that mirror the Node.js runtime
|
|
5
|
+
* implementation while staying optimized for edge environments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
trace as otelTrace,
|
|
10
|
+
SpanStatusCode,
|
|
11
|
+
type Span,
|
|
12
|
+
type AttributeValue,
|
|
13
|
+
} from '@opentelemetry/api';
|
|
14
|
+
import type { Sampler } from '@opentelemetry/sdk-trace-base';
|
|
15
|
+
import type { TraceContext } from './core/trace-context';
|
|
16
|
+
import { createTraceContext, setSpanName } from './core/trace-context';
|
|
17
|
+
|
|
18
|
+
// Re-export for convenience
|
|
19
|
+
export type { TraceContext } from './core/trace-context';
|
|
20
|
+
|
|
21
|
+
type AnyFn = (...args: any[]) => any;
|
|
22
|
+
|
|
23
|
+
const TRACE_FACTORY_SYMBOL = Symbol.for('autotel.edge.functional.factory');
|
|
24
|
+
const FACTORY_NAME_HINTS = new Set(['ctx', '_ctx', 'context', 'tracecontext', 'tracectx']);
|
|
25
|
+
|
|
26
|
+
const SINGLE_LINE_COMMENT_REGEX = /\/\/.*$/gm;
|
|
27
|
+
const MULTI_LINE_COMMENT_REGEX = /\/\*[\s\S]*?\*\//gm;
|
|
28
|
+
const PARAM_TOKEN_SANITIZE_REGEX = new RegExp(String.raw`[{}\[\]\s]`, 'g');
|
|
29
|
+
|
|
30
|
+
interface TraceFactoryMarked {
|
|
31
|
+
[TRACE_FACTORY_SYMBOL]?: true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function markAsTraceFactory(fn: AnyFn): void {
|
|
35
|
+
try {
|
|
36
|
+
Object.defineProperty(fn, TRACE_FACTORY_SYMBOL, {
|
|
37
|
+
value: true,
|
|
38
|
+
configurable: true,
|
|
39
|
+
});
|
|
40
|
+
} catch {
|
|
41
|
+
(fn as TraceFactoryMarked)[TRACE_FACTORY_SYMBOL] = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hasFactoryMark(fn: AnyFn): boolean {
|
|
46
|
+
return Boolean((fn as TraceFactoryMarked)[TRACE_FACTORY_SYMBOL]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sanitizeParameterToken(token: string): string {
|
|
50
|
+
const [firstToken] = token.split('=');
|
|
51
|
+
return (firstToken ?? '').replaceAll(PARAM_TOKEN_SANITIZE_REGEX, '').trim();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getFirstParameterToken(fn: AnyFn): string | null {
|
|
55
|
+
let source = Function.prototype.toString.call(fn);
|
|
56
|
+
source = source
|
|
57
|
+
.replaceAll(MULTI_LINE_COMMENT_REGEX, '')
|
|
58
|
+
.replaceAll(SINGLE_LINE_COMMENT_REGEX, '')
|
|
59
|
+
.trim();
|
|
60
|
+
|
|
61
|
+
const arrowMatch = source.match(/^(?:async\s*)?(?:\(([^)]*)\)|([^=()]+))\s*=>/);
|
|
62
|
+
if (arrowMatch) {
|
|
63
|
+
const params = (arrowMatch[1] ?? arrowMatch[2] ?? '').split(',');
|
|
64
|
+
const first = params[0]?.trim();
|
|
65
|
+
if (first) {
|
|
66
|
+
return sanitizeParameterToken(first);
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const functionMatch = source.match(/^[^(]*\(([^)]*)\)/);
|
|
72
|
+
if (functionMatch) {
|
|
73
|
+
const params = functionMatch[1]?.split(',');
|
|
74
|
+
const first = params?.[0]?.trim();
|
|
75
|
+
if (first) {
|
|
76
|
+
return sanitizeParameterToken(first);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function looksLikeTraceFactory(fn: AnyFn): boolean {
|
|
84
|
+
if (hasFactoryMark(fn)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (fn.length === 0) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const firstParam = getFirstParameterToken(fn);
|
|
93
|
+
if (!firstParam) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const normalized = firstParam.toLowerCase();
|
|
98
|
+
if (
|
|
99
|
+
FACTORY_NAME_HINTS.has(normalized) ||
|
|
100
|
+
normalized.startsWith('ctx') ||
|
|
101
|
+
normalized.startsWith('_ctx') ||
|
|
102
|
+
normalized.startsWith('trace')
|
|
103
|
+
) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if a function that takes ctx returns another function (factory pattern)
|
|
112
|
+
* vs returning a value directly (immediate execution pattern)
|
|
113
|
+
*/
|
|
114
|
+
function isFactoryReturningFunction(
|
|
115
|
+
fnWithCtx: (ctx: TraceContext) => unknown,
|
|
116
|
+
): boolean {
|
|
117
|
+
try {
|
|
118
|
+
const result = fnWithCtx(createDummyCtx());
|
|
119
|
+
return typeof result === 'function';
|
|
120
|
+
} catch {
|
|
121
|
+
// If the function throws when called with dummy ctx, assume it's immediate execution
|
|
122
|
+
// since factory functions typically just return a function and don't execute logic
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isTraceFactoryFunction<TArgs extends any[], TReturn>(
|
|
128
|
+
fn:
|
|
129
|
+
| ((...args: TArgs) => TReturn | Promise<TReturn>)
|
|
130
|
+
| ((ctx: TraceContext) => (...args: TArgs) => TReturn | Promise<TReturn>),
|
|
131
|
+
): fn is (ctx: TraceContext) => (...args: TArgs) => TReturn | Promise<TReturn> {
|
|
132
|
+
if (typeof fn !== 'function') {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (hasFactoryMark(fn as AnyFn)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (looksLikeTraceFactory(fn as AnyFn)) {
|
|
141
|
+
markAsTraceFactory(fn as AnyFn);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function ensureTraceFactory<TArgs extends any[], TReturn>(
|
|
149
|
+
fnOrFactory:
|
|
150
|
+
| ((...args: TArgs) => TReturn | Promise<TReturn>)
|
|
151
|
+
| ((ctx: TraceContext) => (...args: TArgs) => TReturn | Promise<TReturn>),
|
|
152
|
+
): (ctx: TraceContext) => (...args: TArgs) => TReturn | Promise<TReturn> {
|
|
153
|
+
if (isTraceFactoryFunction(fnOrFactory)) {
|
|
154
|
+
return fnOrFactory;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const plainFn = fnOrFactory as (...args: TArgs) => TReturn | Promise<TReturn>;
|
|
158
|
+
const factory = (ctx: TraceContext) => {
|
|
159
|
+
void ctx;
|
|
160
|
+
return plainFn;
|
|
161
|
+
};
|
|
162
|
+
markAsTraceFactory(factory);
|
|
163
|
+
return factory;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
type ExtractFunctionSignature<T> = T extends (ctx: TraceContext) => infer F
|
|
167
|
+
? F extends (...args: infer Args) => infer Return
|
|
168
|
+
? (...args: Args) => Return
|
|
169
|
+
: never
|
|
170
|
+
: never;
|
|
171
|
+
|
|
172
|
+
type WrappedFunction<TArgs extends any[], TReturn> = (...args: TArgs) => TReturn | Promise<TReturn>;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* trace function options
|
|
176
|
+
*/
|
|
177
|
+
export interface traceOptions<TArgs extends any[] = any[], TReturn = any> {
|
|
178
|
+
name?: string;
|
|
179
|
+
serviceName?: string;
|
|
180
|
+
sampler?: Sampler;
|
|
181
|
+
attributesFromArgs?: (args: TArgs) => Record<string, unknown>;
|
|
182
|
+
attributesFromResult?: (result: TReturn) => Record<string, unknown>;
|
|
183
|
+
attributes?: Record<string, unknown>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
187
|
+
|
|
188
|
+
function createDummyCtx(): TraceContext {
|
|
189
|
+
return {
|
|
190
|
+
traceId: '',
|
|
191
|
+
spanId: '',
|
|
192
|
+
correlationId: '',
|
|
193
|
+
setAttribute: () => {},
|
|
194
|
+
setAttributes: () => {},
|
|
195
|
+
setStatus: () => {},
|
|
196
|
+
recordException: () => {},
|
|
197
|
+
} as unknown as TraceContext;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function truncateErrorMessage(message: string): string {
|
|
201
|
+
if (message.length <= MAX_ERROR_MESSAGE_LENGTH) {
|
|
202
|
+
return message;
|
|
203
|
+
}
|
|
204
|
+
return `${message.slice(0, MAX_ERROR_MESSAGE_LENGTH)}... (truncated)`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
type InstrumentableFunction<TArgs extends any[] = any[], TReturn = any> = ((
|
|
208
|
+
...args: TArgs
|
|
209
|
+
) => TReturn | Promise<TReturn>) & {
|
|
210
|
+
displayName?: string;
|
|
211
|
+
name?: string;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
function inferFunctionName<TArgs extends any[] = any[], TReturn = any>(
|
|
215
|
+
fn: InstrumentableFunction<TArgs, TReturn>,
|
|
216
|
+
): string | undefined {
|
|
217
|
+
const displayName = (fn as { displayName?: string }).displayName;
|
|
218
|
+
if (displayName) {
|
|
219
|
+
return displayName;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (fn.name && fn.name !== 'anonymous') {
|
|
223
|
+
return fn.name;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const source = Function.prototype.toString.call(fn);
|
|
227
|
+
const match = source.match(/function\s+([^(\s]+)/);
|
|
228
|
+
if (match && match[1] && match[1] !== 'anonymous') {
|
|
229
|
+
return match[1];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getSpanName<TArgs extends any[], TReturn>(
|
|
236
|
+
options: traceOptions<TArgs, TReturn>,
|
|
237
|
+
fn: InstrumentableFunction<TArgs, TReturn>,
|
|
238
|
+
variableName?: string,
|
|
239
|
+
): string {
|
|
240
|
+
if (options.name) {
|
|
241
|
+
return options.name;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let fnName = variableName ?? inferFunctionName(fn);
|
|
245
|
+
fnName = fnName || 'anonymous';
|
|
246
|
+
|
|
247
|
+
if (options.serviceName) {
|
|
248
|
+
return `${options.serviceName}.${fnName}`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (fnName && fnName !== 'anonymous') {
|
|
252
|
+
return fnName;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return 'unknown';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function isAsyncFunction(fn: unknown): boolean {
|
|
259
|
+
return typeof fn === 'function' && fn.constructor?.name === 'AsyncFunction';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const INSTRUMENTED_SYMBOL = Symbol.for('autotel.edge.functional.instrumented');
|
|
263
|
+
|
|
264
|
+
function wrapWithTracingAsync<TArgs extends any[], TReturn>(
|
|
265
|
+
fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>,
|
|
266
|
+
options: traceOptions<TArgs, TReturn>,
|
|
267
|
+
variableName?: string,
|
|
268
|
+
): (...args: TArgs) => Promise<TReturn> {
|
|
269
|
+
const tempFn = fnFactory(createDummyCtx());
|
|
270
|
+
const spanName = getSpanName(options, tempFn, variableName);
|
|
271
|
+
|
|
272
|
+
const wrappedFunction = async function wrappedFunction(this: unknown, ...args: TArgs): Promise<TReturn> {
|
|
273
|
+
const tracer = otelTrace.getTracer('autotel-edge');
|
|
274
|
+
const spanOptions: Record<string, unknown> = options.sampler ? { sampler: options.sampler } : {};
|
|
275
|
+
|
|
276
|
+
return tracer.startActiveSpan(spanName, spanOptions, async (span) => {
|
|
277
|
+
setSpanName(span, spanName);
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const actualFn = fnFactory(createTraceContext(span));
|
|
281
|
+
|
|
282
|
+
if (options.attributes) {
|
|
283
|
+
span.setAttributes(options.attributes as Record<string, AttributeValue>);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (options.attributesFromArgs) {
|
|
287
|
+
const argsAttrs = options.attributesFromArgs(args);
|
|
288
|
+
span.setAttributes(argsAttrs as Record<string, AttributeValue>);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const result = await actualFn(...args);
|
|
292
|
+
|
|
293
|
+
if (options.attributesFromResult) {
|
|
294
|
+
const resultAttrs = options.attributesFromResult(result);
|
|
295
|
+
span.setAttributes(resultAttrs as Record<string, AttributeValue>);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
span.setAttribute('code.function', spanName);
|
|
299
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
300
|
+
span.end();
|
|
301
|
+
return result;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
const message = truncateErrorMessage(
|
|
304
|
+
error instanceof Error ? error.message : String(error ?? 'Unknown error'),
|
|
305
|
+
);
|
|
306
|
+
span.setAttribute('code.function', spanName);
|
|
307
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
308
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
309
|
+
span.end();
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
Object.defineProperty(wrappedFunction, 'name', {
|
|
316
|
+
value: tempFn.name || 'trace',
|
|
317
|
+
configurable: true,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
(wrappedFunction as any)[INSTRUMENTED_SYMBOL] = true;
|
|
321
|
+
|
|
322
|
+
return wrappedFunction;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function wrapWithTracingSync<TArgs extends any[], TReturn>(
|
|
326
|
+
fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn,
|
|
327
|
+
options: traceOptions<TArgs, TReturn>,
|
|
328
|
+
variableName?: string,
|
|
329
|
+
): (...args: TArgs) => TReturn {
|
|
330
|
+
const tempFn = fnFactory(createDummyCtx());
|
|
331
|
+
const spanName = getSpanName(options, tempFn, variableName);
|
|
332
|
+
|
|
333
|
+
const wrappedFunction = function wrappedFunction(this: unknown, ...args: TArgs): TReturn {
|
|
334
|
+
const tracer = otelTrace.getTracer('autotel-edge');
|
|
335
|
+
const spanOptions: Record<string, unknown> = options.sampler ? { sampler: options.sampler } : {};
|
|
336
|
+
|
|
337
|
+
return tracer.startActiveSpan(spanName, spanOptions, (span) => {
|
|
338
|
+
setSpanName(span, spanName);
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const actualFn = fnFactory(createTraceContext(span));
|
|
342
|
+
|
|
343
|
+
if (options.attributes) {
|
|
344
|
+
span.setAttributes(options.attributes as Record<string, AttributeValue>);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (options.attributesFromArgs) {
|
|
348
|
+
const argsAttrs = options.attributesFromArgs(args);
|
|
349
|
+
span.setAttributes(argsAttrs as Record<string, AttributeValue>);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const result = actualFn(...args);
|
|
353
|
+
|
|
354
|
+
if (options.attributesFromResult) {
|
|
355
|
+
const resultAttrs = options.attributesFromResult(result as TReturn);
|
|
356
|
+
span.setAttributes(resultAttrs as Record<string, AttributeValue>);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
span.setAttribute('code.function', spanName);
|
|
360
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
361
|
+
span.end();
|
|
362
|
+
return result;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
const message = truncateErrorMessage(
|
|
365
|
+
error instanceof Error ? error.message : String(error ?? 'Unknown error'),
|
|
366
|
+
);
|
|
367
|
+
span.setAttribute('code.function', spanName);
|
|
368
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
369
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
370
|
+
span.end();
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
Object.defineProperty(wrappedFunction, 'name', {
|
|
377
|
+
value: tempFn.name || 'trace',
|
|
378
|
+
configurable: true,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
(wrappedFunction as any)[INSTRUMENTED_SYMBOL] = true;
|
|
382
|
+
|
|
383
|
+
return wrappedFunction;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function wrapFactoryWithTracing<TArgs extends any[], TReturn>(
|
|
387
|
+
fnOrFactory:
|
|
388
|
+
| ((...args: TArgs) => TReturn | Promise<TReturn>)
|
|
389
|
+
| ((ctx: TraceContext) => (...args: TArgs) => TReturn | Promise<TReturn>),
|
|
390
|
+
options: traceOptions<TArgs, TReturn>,
|
|
391
|
+
variableName?: string,
|
|
392
|
+
): WrappedFunction<TArgs, TReturn> {
|
|
393
|
+
const factory = ensureTraceFactory(fnOrFactory);
|
|
394
|
+
const sampleFn = factory(createDummyCtx());
|
|
395
|
+
const useAsyncWrapper = isAsyncFunction(sampleFn);
|
|
396
|
+
|
|
397
|
+
if (useAsyncWrapper) {
|
|
398
|
+
return wrapWithTracingAsync(
|
|
399
|
+
factory as (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>,
|
|
400
|
+
options,
|
|
401
|
+
variableName,
|
|
402
|
+
) as WrappedFunction<TArgs, TReturn>;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return wrapWithTracingSync(
|
|
406
|
+
factory as (ctx: TraceContext) => (...args: TArgs) => TReturn,
|
|
407
|
+
options,
|
|
408
|
+
variableName,
|
|
409
|
+
) as WrappedFunction<TArgs, TReturn>;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Execute a function immediately within a trace span
|
|
414
|
+
* Used for the immediate execution pattern: trace((ctx) => result)
|
|
415
|
+
*/
|
|
416
|
+
function executeImmediately<TReturn = any>(
|
|
417
|
+
fn: (ctx: TraceContext) => TReturn | Promise<TReturn>,
|
|
418
|
+
options: traceOptions<any[], any>,
|
|
419
|
+
): TReturn | Promise<TReturn> {
|
|
420
|
+
const tracer = otelTrace.getTracer('@autotel/edge');
|
|
421
|
+
const spanName = options.name || 'anonymous';
|
|
422
|
+
|
|
423
|
+
return tracer.startActiveSpan(spanName, (span) => {
|
|
424
|
+
try {
|
|
425
|
+
setSpanName(span, spanName);
|
|
426
|
+
const ctxValue = createTraceContext(span);
|
|
427
|
+
|
|
428
|
+
const onSuccess = (result: TReturn) => {
|
|
429
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
430
|
+
if (options.attributes) {
|
|
431
|
+
for (const [key, value] of Object.entries(options.attributes)) {
|
|
432
|
+
span.setAttribute(key, value as AttributeValue);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
span.end();
|
|
436
|
+
return result;
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const onError = (error: unknown): never => {
|
|
440
|
+
const errorMessage =
|
|
441
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
442
|
+
const truncatedMessage = truncateErrorMessage(errorMessage);
|
|
443
|
+
|
|
444
|
+
span.setStatus({
|
|
445
|
+
code: SpanStatusCode.ERROR,
|
|
446
|
+
message: truncatedMessage,
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
span.setAttribute('error', true);
|
|
450
|
+
span.setAttribute('exception.type',
|
|
451
|
+
error instanceof Error ? error.constructor.name : 'Error');
|
|
452
|
+
span.setAttribute('exception.message', truncatedMessage);
|
|
453
|
+
|
|
454
|
+
span.recordException(
|
|
455
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
span.end();
|
|
459
|
+
throw error;
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const result = fn(ctxValue);
|
|
463
|
+
|
|
464
|
+
// Check if result is a Promise
|
|
465
|
+
if (result instanceof Promise) {
|
|
466
|
+
return result.then(onSuccess, onError);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return onSuccess(result);
|
|
470
|
+
} catch (error) {
|
|
471
|
+
const errorMessage =
|
|
472
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
473
|
+
const truncatedMessage = truncateErrorMessage(errorMessage);
|
|
474
|
+
|
|
475
|
+
span.setStatus({
|
|
476
|
+
code: SpanStatusCode.ERROR,
|
|
477
|
+
message: truncatedMessage,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
span.setAttribute('error', true);
|
|
481
|
+
span.setAttribute('exception.message', truncatedMessage);
|
|
482
|
+
|
|
483
|
+
span.recordException(
|
|
484
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
span.end();
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Sync overloads - Factory pattern with explicit return type helps TypeScript infer
|
|
494
|
+
// Overload 1a: Plain sync function with no args (auto-inferred name)
|
|
495
|
+
export function trace<TReturn = any>(
|
|
496
|
+
fn: () => TReturn,
|
|
497
|
+
): () => TReturn;
|
|
498
|
+
// Overload 1b: Plain sync function (auto-inferred name)
|
|
499
|
+
export function trace<TArgs extends any[], TReturn = any>(
|
|
500
|
+
fn: (...args: TArgs) => TReturn,
|
|
501
|
+
): (...args: TArgs) => TReturn;
|
|
502
|
+
|
|
503
|
+
// Overload 1c: Factory sync function with no args returning explicit type (auto-inferred name)
|
|
504
|
+
export function trace<TReturn = any>(
|
|
505
|
+
fnFactory: (ctx: TraceContext) => () => TReturn,
|
|
506
|
+
): () => TReturn;
|
|
507
|
+
// Overload 1d: Immediate execution - sync function with context (NEW PATTERN)
|
|
508
|
+
export function trace<TReturn = any>(
|
|
509
|
+
fn: (ctx: TraceContext) => TReturn,
|
|
510
|
+
): TReturn;
|
|
511
|
+
// Overload 1e: Factory sync function - use conditional type to extract signature (MUST come before generic)
|
|
512
|
+
// This overload is more specific and helps TypeScript infer types from factory functions
|
|
513
|
+
export function trace<TFactory extends (ctx: TraceContext) => (...args: any[]) => any>(
|
|
514
|
+
fnFactory: TFactory,
|
|
515
|
+
): ExtractFunctionSignature<TFactory>;
|
|
516
|
+
// Overload 1f: Generic factory sync function (fallback)
|
|
517
|
+
export function trace<TArgs extends any[], TReturn = any>(
|
|
518
|
+
fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn,
|
|
519
|
+
): (...args: TArgs) => TReturn;
|
|
520
|
+
|
|
521
|
+
// Overload 2a: Name + plain sync function
|
|
522
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
523
|
+
name: string,
|
|
524
|
+
fn: (...args: TArgs) => TReturn,
|
|
525
|
+
): (...args: TArgs) => TReturn;
|
|
526
|
+
|
|
527
|
+
// Overload 2b: Name + immediate execution sync with context
|
|
528
|
+
export function trace<TReturn = any>(
|
|
529
|
+
name: string,
|
|
530
|
+
fn: (ctx: TraceContext) => TReturn,
|
|
531
|
+
): TReturn;
|
|
532
|
+
|
|
533
|
+
// Overload 2c: Name + factory sync function
|
|
534
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
535
|
+
name: string,
|
|
536
|
+
fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn,
|
|
537
|
+
): (...args: TArgs) => TReturn;
|
|
538
|
+
|
|
539
|
+
// Overload 3a: Options + plain sync function
|
|
540
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
541
|
+
options: traceOptions<TArgs, TReturn>,
|
|
542
|
+
fn: (...args: TArgs) => TReturn,
|
|
543
|
+
): (...args: TArgs) => TReturn;
|
|
544
|
+
|
|
545
|
+
// Overload 3b: Options + immediate execution sync with context
|
|
546
|
+
export function trace<TReturn = any>(
|
|
547
|
+
options: traceOptions<[], TReturn>,
|
|
548
|
+
fn: (ctx: TraceContext) => TReturn,
|
|
549
|
+
): TReturn;
|
|
550
|
+
|
|
551
|
+
// Overload 3c: Options + factory sync function
|
|
552
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
553
|
+
options: traceOptions<TArgs, TReturn>,
|
|
554
|
+
fnFactory: (ctx: TraceContext) => (...args: TArgs) => TReturn,
|
|
555
|
+
): (...args: TArgs) => TReturn;
|
|
556
|
+
|
|
557
|
+
// Async overloads
|
|
558
|
+
// Overload 4a: Plain async function with no args (auto-inferred name)
|
|
559
|
+
export function trace<TReturn = any>(
|
|
560
|
+
fn: () => Promise<TReturn>,
|
|
561
|
+
): () => Promise<TReturn>;
|
|
562
|
+
// Overload 4b: Plain async function (auto-inferred name)
|
|
563
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
564
|
+
fn: (...args: TArgs) => Promise<TReturn>,
|
|
565
|
+
): (...args: TArgs) => Promise<TReturn>;
|
|
566
|
+
|
|
567
|
+
// Overload 4c: Immediate execution - async function with context (NEW PATTERN)
|
|
568
|
+
export function trace<TReturn = any>(
|
|
569
|
+
fn: (ctx: TraceContext) => Promise<TReturn>,
|
|
570
|
+
): Promise<TReturn>;
|
|
571
|
+
// Overload 4d: Factory async function with no args (auto-inferred name)
|
|
572
|
+
export function trace<TReturn = any>(
|
|
573
|
+
fnFactory: (ctx: TraceContext) => () => Promise<TReturn>,
|
|
574
|
+
): () => Promise<TReturn>;
|
|
575
|
+
// Overload 4e: Factory async function (auto-inferred name)
|
|
576
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
577
|
+
fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>,
|
|
578
|
+
): (...args: TArgs) => Promise<TReturn>;
|
|
579
|
+
|
|
580
|
+
// Overload 5a: Name + plain async function
|
|
581
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
582
|
+
name: string,
|
|
583
|
+
fn: (...args: TArgs) => Promise<TReturn>,
|
|
584
|
+
): (...args: TArgs) => Promise<TReturn>;
|
|
585
|
+
|
|
586
|
+
// Overload 5b: Name + immediate execution async with context
|
|
587
|
+
export function trace<TReturn = any>(
|
|
588
|
+
name: string,
|
|
589
|
+
fn: (ctx: TraceContext) => Promise<TReturn>,
|
|
590
|
+
): Promise<TReturn>;
|
|
591
|
+
|
|
592
|
+
// Overload 5c: Name + factory async function
|
|
593
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
594
|
+
name: string,
|
|
595
|
+
fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>,
|
|
596
|
+
): (...args: TArgs) => Promise<TReturn>;
|
|
597
|
+
|
|
598
|
+
// Overload 6a: Options + plain async function
|
|
599
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
600
|
+
options: traceOptions<TArgs, TReturn>,
|
|
601
|
+
fn: (...args: TArgs) => Promise<TReturn>,
|
|
602
|
+
): (...args: TArgs) => Promise<TReturn>;
|
|
603
|
+
|
|
604
|
+
// Overload 6b: Options + immediate execution async with context
|
|
605
|
+
export function trace<TReturn = any>(
|
|
606
|
+
options: traceOptions<[], TReturn>,
|
|
607
|
+
fn: (ctx: TraceContext) => Promise<TReturn>,
|
|
608
|
+
): Promise<TReturn>;
|
|
609
|
+
|
|
610
|
+
// Overload 6c: Options + factory async function
|
|
611
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
612
|
+
options: traceOptions<TArgs, TReturn>,
|
|
613
|
+
fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>,
|
|
614
|
+
): (...args: TArgs) => Promise<TReturn>;
|
|
615
|
+
|
|
616
|
+
// Implementation
|
|
617
|
+
export function trace<TArgs extends any[] = any[], TReturn = any>(
|
|
618
|
+
fnOrNameOrOptions:
|
|
619
|
+
| ((...args: TArgs) => TReturn)
|
|
620
|
+
| ((...args: TArgs) => Promise<TReturn>)
|
|
621
|
+
| ((ctx: TraceContext) => (...args: TArgs) => TReturn)
|
|
622
|
+
| ((ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>)
|
|
623
|
+
| ((ctx: TraceContext) => TReturn)
|
|
624
|
+
| ((ctx: TraceContext) => Promise<TReturn>)
|
|
625
|
+
| string
|
|
626
|
+
| traceOptions<TArgs, TReturn>,
|
|
627
|
+
maybeFn?:
|
|
628
|
+
| ((...args: TArgs) => TReturn)
|
|
629
|
+
| ((...args: TArgs) => Promise<TReturn>)
|
|
630
|
+
| ((ctx: TraceContext) => (...args: TArgs) => TReturn)
|
|
631
|
+
| ((ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>)
|
|
632
|
+
| ((ctx: TraceContext) => TReturn)
|
|
633
|
+
| ((ctx: TraceContext) => Promise<TReturn>),
|
|
634
|
+
): WrappedFunction<TArgs, TReturn> | TReturn | Promise<TReturn> {
|
|
635
|
+
// Handle: trace(fn) - single argument
|
|
636
|
+
if (typeof fnOrNameOrOptions === 'function') {
|
|
637
|
+
// Check if it's immediate execution pattern: (ctx) => result
|
|
638
|
+
if (
|
|
639
|
+
looksLikeTraceFactory(fnOrNameOrOptions as AnyFn) &&
|
|
640
|
+
!isFactoryReturningFunction(fnOrNameOrOptions as (ctx: TraceContext) => unknown)
|
|
641
|
+
) {
|
|
642
|
+
// Immediate execution pattern
|
|
643
|
+
return executeImmediately(
|
|
644
|
+
fnOrNameOrOptions as (ctx: TraceContext) => TReturn | Promise<TReturn>,
|
|
645
|
+
{},
|
|
646
|
+
) as WrappedFunction<TArgs, TReturn> | TReturn | Promise<TReturn>;
|
|
647
|
+
}
|
|
648
|
+
// Factory pattern or plain function
|
|
649
|
+
return wrapFactoryWithTracing(
|
|
650
|
+
fnOrNameOrOptions as (...args: TArgs) => TReturn,
|
|
651
|
+
{} as traceOptions<TArgs, TReturn>,
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Handle: trace(name, fn) or trace(options, fn) - two arguments
|
|
656
|
+
if (typeof fnOrNameOrOptions === 'string') {
|
|
657
|
+
if (!maybeFn) {
|
|
658
|
+
throw new Error('trace(name, fn): fn is required');
|
|
659
|
+
}
|
|
660
|
+
// Check if it's immediate execution pattern
|
|
661
|
+
if (
|
|
662
|
+
looksLikeTraceFactory(maybeFn as AnyFn) &&
|
|
663
|
+
!isFactoryReturningFunction(maybeFn as (ctx: TraceContext) => unknown)
|
|
664
|
+
) {
|
|
665
|
+
// Immediate execution pattern with name
|
|
666
|
+
return executeImmediately(
|
|
667
|
+
maybeFn as (ctx: TraceContext) => TReturn | Promise<TReturn>,
|
|
668
|
+
{ name: fnOrNameOrOptions },
|
|
669
|
+
) as WrappedFunction<TArgs, TReturn> | TReturn | Promise<TReturn>;
|
|
670
|
+
}
|
|
671
|
+
return wrapFactoryWithTracing(
|
|
672
|
+
maybeFn as (...args: TArgs) => TReturn,
|
|
673
|
+
{ name: fnOrNameOrOptions } as traceOptions<TArgs, TReturn>,
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Handle: trace(options, fn)
|
|
678
|
+
if (!maybeFn) {
|
|
679
|
+
throw new Error('trace(options, fn): fn is required');
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Check if it's immediate execution pattern
|
|
683
|
+
if (
|
|
684
|
+
looksLikeTraceFactory(maybeFn as AnyFn) &&
|
|
685
|
+
!isFactoryReturningFunction(maybeFn as (ctx: TraceContext) => unknown)
|
|
686
|
+
) {
|
|
687
|
+
// Immediate execution pattern with options
|
|
688
|
+
return executeImmediately(
|
|
689
|
+
maybeFn as (ctx: TraceContext) => TReturn | Promise<TReturn>,
|
|
690
|
+
fnOrNameOrOptions as traceOptions<any[], any>,
|
|
691
|
+
) as WrappedFunction<TArgs, TReturn> | TReturn | Promise<TReturn>;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return wrapFactoryWithTracing(
|
|
695
|
+
maybeFn as (...args: TArgs) => TReturn,
|
|
696
|
+
fnOrNameOrOptions as traceOptions<TArgs, TReturn>,
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export function withTracing<TArgs extends any[] = any[], TReturn = any>(
|
|
701
|
+
options: Omit<traceOptions<TArgs, TReturn>, 'name'>,
|
|
702
|
+
) {
|
|
703
|
+
return (
|
|
704
|
+
fnOrFactory:
|
|
705
|
+
| ((...args: TArgs) => TReturn | Promise<TReturn>)
|
|
706
|
+
| ((ctx: TraceContext) => (...args: TArgs) => TReturn | Promise<TReturn>)
|
|
707
|
+
): WrappedFunction<TArgs, TReturn> => wrapFactoryWithTracing(fnOrFactory, options as traceOptions<TArgs, TReturn>);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function shouldSkip(key: string, fn: Function, skip?: (string | RegExp | ((key: string, fn: Function) => boolean))[]): boolean {
|
|
711
|
+
if (key.startsWith('_')) {
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (!skip || skip.length === 0) {
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
for (const pattern of skip) {
|
|
720
|
+
if (typeof pattern === 'string' && key === pattern) {
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
if (pattern instanceof RegExp && pattern.test(key)) {
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
if (typeof pattern === 'function' && pattern(key, fn)) {
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export interface InstrumentOptions extends traceOptions {
|
|
735
|
+
functions: Record<string, any>;
|
|
736
|
+
overrides?: Record<string, Partial<traceOptions>>;
|
|
737
|
+
skip?: (string | RegExp | ((key: string, fn: Function) => boolean))[];
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
export function instrument<T extends Record<string, any>>(
|
|
741
|
+
options: InstrumentOptions,
|
|
742
|
+
): T {
|
|
743
|
+
const { functions, ...tracingOptions } = options;
|
|
744
|
+
const instrumented: Record<string, any> = {};
|
|
745
|
+
|
|
746
|
+
for (const key of Object.keys(functions)) {
|
|
747
|
+
const fn = functions[key];
|
|
748
|
+
|
|
749
|
+
if (typeof fn !== 'function') {
|
|
750
|
+
instrumented[key] = fn;
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (shouldSkip(key, fn, tracingOptions.skip)) {
|
|
755
|
+
instrumented[key] = fn;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const fnOptions: traceOptions = {
|
|
760
|
+
...tracingOptions,
|
|
761
|
+
...tracingOptions.overrides?.[key],
|
|
762
|
+
name: tracingOptions.overrides?.[key]?.name ?? tracingOptions.name,
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
const boundFn = fn.bind(functions);
|
|
766
|
+
const fnFactory = (_ctx: TraceContext) => boundFn as AnyFn;
|
|
767
|
+
|
|
768
|
+
instrumented[key] = wrapFactoryWithTracing(fnFactory, fnOptions, key);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return instrumented as T;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export interface SpanOptions {
|
|
775
|
+
name: string;
|
|
776
|
+
attributes?: Record<string, string | number | boolean>;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export function span<T = unknown>(options: SpanOptions, fn: (span: Span) => T): T;
|
|
780
|
+
export function span<T = unknown>(
|
|
781
|
+
options: SpanOptions,
|
|
782
|
+
fn: (span: Span) => Promise<T>,
|
|
783
|
+
): Promise<T>;
|
|
784
|
+
export function span<T = unknown>(
|
|
785
|
+
options: SpanOptions,
|
|
786
|
+
fn: (span: Span) => T | Promise<T>,
|
|
787
|
+
): T | Promise<T> {
|
|
788
|
+
const tracer = otelTrace.getTracer('autotel-edge');
|
|
789
|
+
|
|
790
|
+
const execute = (span: Span) => {
|
|
791
|
+
setSpanName(span, options.name);
|
|
792
|
+
|
|
793
|
+
try {
|
|
794
|
+
if (options.attributes) {
|
|
795
|
+
for (const [key, value] of Object.entries(options.attributes)) {
|
|
796
|
+
span.setAttribute(key, value);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const result = fn(span);
|
|
801
|
+
|
|
802
|
+
if (result instanceof Promise) {
|
|
803
|
+
return result
|
|
804
|
+
.then((value) => {
|
|
805
|
+
span.setAttribute('code.function', options.name);
|
|
806
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
807
|
+
span.end();
|
|
808
|
+
return value;
|
|
809
|
+
})
|
|
810
|
+
.catch((error) => {
|
|
811
|
+
const message = truncateErrorMessage(
|
|
812
|
+
error instanceof Error ? error.message : String(error ?? 'Unknown error'),
|
|
813
|
+
);
|
|
814
|
+
span.setAttribute('code.function', options.name);
|
|
815
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
816
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
817
|
+
span.end();
|
|
818
|
+
throw error;
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
span.setAttribute('code.function', options.name);
|
|
823
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
824
|
+
span.end();
|
|
825
|
+
return result;
|
|
826
|
+
} catch (error) {
|
|
827
|
+
const message = truncateErrorMessage(
|
|
828
|
+
error instanceof Error ? error.message : String(error ?? 'Unknown error'),
|
|
829
|
+
);
|
|
830
|
+
span.setAttribute('code.function', options.name);
|
|
831
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
832
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
833
|
+
span.end();
|
|
834
|
+
throw error;
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
const result = tracer.startActiveSpan(options.name, execute);
|
|
839
|
+
|
|
840
|
+
if (result instanceof Promise) {
|
|
841
|
+
return result;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return result as T;
|
|
845
|
+
}
|
|
846
|
+
|