autotel 2.2.0 → 2.4.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/dist/{chunk-TRI4V5BF.cjs → chunk-2EHB2KXS.cjs} +4 -4
- package/dist/{chunk-TRI4V5BF.cjs.map → chunk-2EHB2KXS.cjs.map} +1 -1
- package/dist/{chunk-M4ANN7RL.js → chunk-AXCCOSA3.js} +3 -3
- package/dist/{chunk-M4ANN7RL.js.map → chunk-AXCCOSA3.js.map} +1 -1
- package/dist/{chunk-NHCNRQD3.cjs → chunk-IW37CN6L.cjs} +7 -2
- package/dist/chunk-IW37CN6L.cjs.map +1 -0
- package/dist/{chunk-CDZBY5WU.cjs → chunk-M4K4GMI7.cjs} +23 -14
- package/dist/chunk-M4K4GMI7.cjs.map +1 -0
- package/dist/{chunk-ZWLYTPLR.js → chunk-MAAPH5NI.js} +3 -3
- package/dist/{chunk-ZWLYTPLR.js.map → chunk-MAAPH5NI.js.map} +1 -1
- package/dist/{chunk-DMUSPYUX.js → chunk-PTMQAULX.js} +13 -4
- package/dist/chunk-PTMQAULX.js.map +1 -0
- package/dist/{chunk-LGE7W72O.cjs → chunk-UUL3EEY4.cjs} +7 -7
- package/dist/{chunk-LGE7W72O.cjs.map → chunk-UUL3EEY4.cjs.map} +1 -1
- package/dist/{chunk-HCCXC7XG.js → chunk-YHW3MAS7.js} +7 -2
- package/dist/chunk-YHW3MAS7.js.map +1 -0
- package/dist/decorators.cjs +5 -5
- package/dist/decorators.d.cts +1 -1
- package/dist/decorators.d.ts +1 -1
- package/dist/decorators.js +3 -3
- package/dist/functional.cjs +10 -10
- package/dist/functional.d.cts +5 -5
- package/dist/functional.d.ts +5 -5
- package/dist/functional.js +3 -3
- package/dist/http.cjs +3 -3
- package/dist/http.js +1 -1
- package/dist/index.cjs +27 -27
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/semantic-helpers.cjs +8 -8
- package/dist/semantic-helpers.d.cts +1 -1
- package/dist/semantic-helpers.d.ts +1 -1
- package/dist/semantic-helpers.js +4 -4
- package/dist/{trace-context-DRZdUvVY.d.cts → trace-context-gTjI7ZDb.d.cts} +23 -4
- package/dist/{trace-context-DRZdUvVY.d.ts → trace-context-gTjI7ZDb.d.ts} +23 -4
- package/dist/trace-helpers.cjs +13 -13
- package/dist/trace-helpers.js +2 -2
- package/package.json +6 -6
- package/src/functional.strict-types.typecheck.ts +53 -0
- package/src/functional.test.ts +155 -0
- package/src/functional.ts +23 -18
- package/src/functional.types.test.ts +135 -0
- package/src/trace-context.ts +40 -3
- package/dist/chunk-CDZBY5WU.cjs.map +0 -1
- package/dist/chunk-DMUSPYUX.js.map +0 -1
- package/dist/chunk-HCCXC7XG.js.map +0 -1
- package/dist/chunk-NHCNRQD3.cjs.map +0 -1
package/dist/trace-helpers.cjs
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
require('./chunk-
|
|
3
|
+
var chunk2EHB2KXS_cjs = require('./chunk-2EHB2KXS.cjs');
|
|
4
|
+
require('./chunk-IW37CN6L.cjs');
|
|
5
5
|
require('./chunk-G7VZBCD6.cjs');
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
Object.defineProperty(exports, "createDeterministicTraceId", {
|
|
10
10
|
enumerable: true,
|
|
11
|
-
get: function () { return
|
|
11
|
+
get: function () { return chunk2EHB2KXS_cjs.createDeterministicTraceId; }
|
|
12
12
|
});
|
|
13
13
|
Object.defineProperty(exports, "enrichWithTraceContext", {
|
|
14
14
|
enumerable: true,
|
|
15
|
-
get: function () { return
|
|
15
|
+
get: function () { return chunk2EHB2KXS_cjs.enrichWithTraceContext; }
|
|
16
16
|
});
|
|
17
17
|
Object.defineProperty(exports, "finalizeSpan", {
|
|
18
18
|
enumerable: true,
|
|
19
|
-
get: function () { return
|
|
19
|
+
get: function () { return chunk2EHB2KXS_cjs.finalizeSpan; }
|
|
20
20
|
});
|
|
21
21
|
Object.defineProperty(exports, "flattenMetadata", {
|
|
22
22
|
enumerable: true,
|
|
23
|
-
get: function () { return
|
|
23
|
+
get: function () { return chunk2EHB2KXS_cjs.flattenMetadata; }
|
|
24
24
|
});
|
|
25
25
|
Object.defineProperty(exports, "getActiveContext", {
|
|
26
26
|
enumerable: true,
|
|
27
|
-
get: function () { return
|
|
27
|
+
get: function () { return chunk2EHB2KXS_cjs.getActiveContext; }
|
|
28
28
|
});
|
|
29
29
|
Object.defineProperty(exports, "getActiveSpan", {
|
|
30
30
|
enumerable: true,
|
|
31
|
-
get: function () { return
|
|
31
|
+
get: function () { return chunk2EHB2KXS_cjs.getActiveSpan; }
|
|
32
32
|
});
|
|
33
33
|
Object.defineProperty(exports, "getTraceContext", {
|
|
34
34
|
enumerable: true,
|
|
35
|
-
get: function () { return
|
|
35
|
+
get: function () { return chunk2EHB2KXS_cjs.getTraceContext; }
|
|
36
36
|
});
|
|
37
37
|
Object.defineProperty(exports, "getTracer", {
|
|
38
38
|
enumerable: true,
|
|
39
|
-
get: function () { return
|
|
39
|
+
get: function () { return chunk2EHB2KXS_cjs.getTracer; }
|
|
40
40
|
});
|
|
41
41
|
Object.defineProperty(exports, "isTracing", {
|
|
42
42
|
enumerable: true,
|
|
43
|
-
get: function () { return
|
|
43
|
+
get: function () { return chunk2EHB2KXS_cjs.isTracing; }
|
|
44
44
|
});
|
|
45
45
|
Object.defineProperty(exports, "runWithSpan", {
|
|
46
46
|
enumerable: true,
|
|
47
|
-
get: function () { return
|
|
47
|
+
get: function () { return chunk2EHB2KXS_cjs.runWithSpan; }
|
|
48
48
|
});
|
|
49
49
|
Object.defineProperty(exports, "setSpanName", {
|
|
50
50
|
enumerable: true,
|
|
51
|
-
get: function () { return
|
|
51
|
+
get: function () { return chunk2EHB2KXS_cjs.setSpanName; }
|
|
52
52
|
});
|
|
53
53
|
//# sourceMappingURL=trace-helpers.cjs.map
|
|
54
54
|
//# sourceMappingURL=trace-helpers.cjs.map
|
package/dist/trace-helpers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { createDeterministicTraceId, enrichWithTraceContext, finalizeSpan, flattenMetadata, getActiveContext, getActiveSpan, getTraceContext, getTracer, isTracing, runWithSpan, setSpanName } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { createDeterministicTraceId, enrichWithTraceContext, finalizeSpan, flattenMetadata, getActiveContext, getActiveSpan, getTraceContext, getTracer, isTracing, runWithSpan, setSpanName } from './chunk-AXCCOSA3.js';
|
|
2
|
+
import './chunk-YHW3MAS7.js';
|
|
3
3
|
import './chunk-Z6ZWNWWR.js';
|
|
4
4
|
//# sourceMappingURL=trace-helpers.js.map
|
|
5
5
|
//# sourceMappingURL=trace-helpers.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Write Once, Observe Anywhere",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -160,10 +160,10 @@
|
|
|
160
160
|
"license": "MIT",
|
|
161
161
|
"dependencies": {
|
|
162
162
|
"@opentelemetry/api": "^1.9.0",
|
|
163
|
-
"import-in-the-middle": "^
|
|
164
|
-
"yaml": "^2.
|
|
163
|
+
"import-in-the-middle": "^2.0.0",
|
|
164
|
+
"yaml": "^2.8.1",
|
|
165
165
|
"@opentelemetry/api-logs": "^0.208.0",
|
|
166
|
-
"@opentelemetry/auto-instrumentations-node": "^0.67.
|
|
166
|
+
"@opentelemetry/auto-instrumentations-node": "^0.67.2",
|
|
167
167
|
"@opentelemetry/exporter-metrics-otlp-http": "^0.208.0",
|
|
168
168
|
"@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
|
|
169
169
|
"@opentelemetry/instrumentation-pino": "^0.55.0",
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
"@opentelemetry/resource-detector-gcp": "^0.43.0",
|
|
185
185
|
"@opentelemetry/sdk-logs": "^0.208.0",
|
|
186
186
|
"@opentelemetry/sdk-trace-node": "^2.2.0",
|
|
187
|
-
"@traceloop/node-server-sdk": "^0.
|
|
187
|
+
"@traceloop/node-server-sdk": "^0.22.0",
|
|
188
188
|
"pino": "^10.1.0",
|
|
189
189
|
"pino-pretty": "^13.1.2"
|
|
190
190
|
},
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
"@edge-runtime/vm": "^5.0.0",
|
|
232
232
|
"@opentelemetry/api": "^1.9.0",
|
|
233
233
|
"@opentelemetry/context-async-hooks": "^2.2.0",
|
|
234
|
-
"@opentelemetry/auto-instrumentations-node": "^0.67.
|
|
234
|
+
"@opentelemetry/auto-instrumentations-node": "^0.67.2",
|
|
235
235
|
"@opentelemetry/exporter-logs-otlp-grpc": "^0.208.0",
|
|
236
236
|
"@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
|
|
237
237
|
"@opentelemetry/exporter-metrics-otlp-grpc": "^0.208.0",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict type inference test for the bug:
|
|
3
|
+
* trace() with name parameter returns unknown type
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { trace } from './functional';
|
|
7
|
+
|
|
8
|
+
// This test file should be checked with: npx tsc --noEmit
|
|
9
|
+
|
|
10
|
+
// Helper type to detect unknown - this will be 'true' if T is unknown, 'false' otherwise
|
|
11
|
+
type IsUnknown<T> = unknown extends T
|
|
12
|
+
? T extends unknown
|
|
13
|
+
? true
|
|
14
|
+
: false
|
|
15
|
+
: false;
|
|
16
|
+
|
|
17
|
+
// Test 1: With explicit TraceContext type - this should always work
|
|
18
|
+
const withExplicitType = trace('test-span', () => async () => {
|
|
19
|
+
return { foo: 'bar' };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Type assertion - this line should compile without error if type is inferred correctly
|
|
23
|
+
const _test1: () => Promise<{ foo: string }> = withExplicitType;
|
|
24
|
+
|
|
25
|
+
// Test 2: Without explicit TraceContext type - this is the bug scenario
|
|
26
|
+
const withoutExplicitType = trace('test-span', () => async () => {
|
|
27
|
+
return { foo: 'bar' };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Get the inner type of the Promise returned by the function
|
|
31
|
+
type InnerReturnType = Awaited<ReturnType<typeof withoutExplicitType>>;
|
|
32
|
+
|
|
33
|
+
// This will be 'true' if the bug exists (type is unknown)
|
|
34
|
+
type IsBugPresent = IsUnknown<InnerReturnType>;
|
|
35
|
+
|
|
36
|
+
// Type assertion - if the bug exists, this will fail because IsBugPresent is 'true'
|
|
37
|
+
// If the bug is fixed, IsBugPresent is 'false' and this compiles
|
|
38
|
+
const _bugCheck: IsBugPresent extends false ? 'fixed' : never = 'fixed';
|
|
39
|
+
|
|
40
|
+
// Type assertion - if the bug exists, this line will fail compilation
|
|
41
|
+
// because the return type would be () => Promise<unknown>
|
|
42
|
+
const _test2: () => Promise<{ foo: string }> = withoutExplicitType;
|
|
43
|
+
|
|
44
|
+
// Test 3: Alternative - check if we can access .foo on the result
|
|
45
|
+
async function testAccess() {
|
|
46
|
+
const result = await withoutExplicitType();
|
|
47
|
+
// If the type is unknown, TypeScript will error on .foo access
|
|
48
|
+
// If the type is { foo: string }, this compiles fine
|
|
49
|
+
return result.foo;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Prevent unused variable warnings
|
|
53
|
+
export { _test1, _test2, testAccess, _bugCheck };
|
package/src/functional.test.ts
CHANGED
|
@@ -1126,4 +1126,159 @@ describe('Functional API', () => {
|
|
|
1126
1126
|
expect(collector.getSpans()).toHaveLength(1);
|
|
1127
1127
|
});
|
|
1128
1128
|
});
|
|
1129
|
+
|
|
1130
|
+
describe('Array attribute support', () => {
|
|
1131
|
+
it('should support string array attributes', async () => {
|
|
1132
|
+
const collector = createTraceCollector();
|
|
1133
|
+
|
|
1134
|
+
await trace(async (ctx: TraceContext) => {
|
|
1135
|
+
ctx.setAttribute('tags', ['qa', 'test', 'automated']);
|
|
1136
|
+
return 'done';
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
const spans = collector.getSpans();
|
|
1140
|
+
expect(spans).toHaveLength(1);
|
|
1141
|
+
expect(spans[0]!.attributes['tags']).toEqual(['qa', 'test', 'automated']);
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
it('should support number array attributes', async () => {
|
|
1145
|
+
const collector = createTraceCollector();
|
|
1146
|
+
|
|
1147
|
+
await trace(async (ctx: TraceContext) => {
|
|
1148
|
+
ctx.setAttribute('scores', [95, 87, 92]);
|
|
1149
|
+
return 'done';
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
const spans = collector.getSpans();
|
|
1153
|
+
expect(spans).toHaveLength(1);
|
|
1154
|
+
expect(spans[0]!.attributes['scores']).toEqual([95, 87, 92]);
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
it('should support boolean array attributes', async () => {
|
|
1158
|
+
const collector = createTraceCollector();
|
|
1159
|
+
|
|
1160
|
+
await trace(async (ctx: TraceContext) => {
|
|
1161
|
+
ctx.setAttribute('flags', [true, false, true]);
|
|
1162
|
+
return 'done';
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
const spans = collector.getSpans();
|
|
1166
|
+
expect(spans).toHaveLength(1);
|
|
1167
|
+
expect(spans[0]!.attributes['flags']).toEqual([true, false, true]);
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
it('should support mixed attributes including arrays via setAttributes', async () => {
|
|
1171
|
+
const collector = createTraceCollector();
|
|
1172
|
+
|
|
1173
|
+
await trace(async (ctx: TraceContext) => {
|
|
1174
|
+
ctx.setAttributes({
|
|
1175
|
+
'user.id': 'user_123',
|
|
1176
|
+
environment: 'development',
|
|
1177
|
+
version: '1.0.0',
|
|
1178
|
+
tags: ['qa', 'test'],
|
|
1179
|
+
scores: [1, 2, 3],
|
|
1180
|
+
});
|
|
1181
|
+
return 'done';
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
const spans = collector.getSpans();
|
|
1185
|
+
expect(spans).toHaveLength(1);
|
|
1186
|
+
expect(spans[0]!.attributes['user.id']).toBe('user_123');
|
|
1187
|
+
expect(spans[0]!.attributes['environment']).toBe('development');
|
|
1188
|
+
expect(spans[0]!.attributes['tags']).toEqual(['qa', 'test']);
|
|
1189
|
+
expect(spans[0]!.attributes['scores']).toEqual([1, 2, 3]);
|
|
1190
|
+
});
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
describe('Full OTel Span API', () => {
|
|
1194
|
+
it('should support addEvent for span events', async () => {
|
|
1195
|
+
const collector = createTraceCollector();
|
|
1196
|
+
|
|
1197
|
+
// Verify the method can be called without error
|
|
1198
|
+
const result = await trace(async (ctx: TraceContext) => {
|
|
1199
|
+
ctx.addEvent('order.started', { 'order.id': '123' });
|
|
1200
|
+
ctx.addEvent('items.fetched', { 'item.count': 5 });
|
|
1201
|
+
return 'done';
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
expect(result).toBe('done');
|
|
1205
|
+
expect(collector.getSpans()).toHaveLength(1);
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
it('should support updateName for dynamic span naming', async () => {
|
|
1209
|
+
const collector = createTraceCollector();
|
|
1210
|
+
|
|
1211
|
+
await trace('initial.name', async (ctx: TraceContext) => {
|
|
1212
|
+
ctx.updateName('updated.name');
|
|
1213
|
+
return 'done';
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
const spans = collector.getSpans();
|
|
1217
|
+
expect(spans).toHaveLength(1);
|
|
1218
|
+
expect(spans[0]!.name).toBe('updated.name');
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
it('should support isRecording', async () => {
|
|
1222
|
+
const collector = createTraceCollector();
|
|
1223
|
+
let wasRecording = false;
|
|
1224
|
+
|
|
1225
|
+
await trace(async (ctx: TraceContext) => {
|
|
1226
|
+
wasRecording = ctx.isRecording();
|
|
1227
|
+
return 'done';
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
expect(wasRecording).toBe(true);
|
|
1231
|
+
expect(collector.getSpans()).toHaveLength(1);
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
it('should support addLink for span links', async () => {
|
|
1235
|
+
const collector = createTraceCollector();
|
|
1236
|
+
|
|
1237
|
+
// Create a mock span context to link to
|
|
1238
|
+
const linkContext = {
|
|
1239
|
+
traceId: '0af7651916cd43dd8448eb211c80319c',
|
|
1240
|
+
spanId: 'b7ad6b7169203331',
|
|
1241
|
+
traceFlags: 1,
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
// Verify the method can be called without error
|
|
1245
|
+
const result = await trace(async (ctx: TraceContext) => {
|
|
1246
|
+
ctx.addLink({ context: linkContext });
|
|
1247
|
+
return 'done';
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
expect(result).toBe('done');
|
|
1251
|
+
expect(collector.getSpans()).toHaveLength(1);
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
it('should support addLinks for multiple span links', async () => {
|
|
1255
|
+
const collector = createTraceCollector();
|
|
1256
|
+
|
|
1257
|
+
const links = [
|
|
1258
|
+
{
|
|
1259
|
+
context: {
|
|
1260
|
+
traceId: '0af7651916cd43dd8448eb211c80319c',
|
|
1261
|
+
spanId: 'b7ad6b7169203331',
|
|
1262
|
+
traceFlags: 1,
|
|
1263
|
+
},
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
context: {
|
|
1267
|
+
traceId: '0af7651916cd43dd8448eb211c80319d',
|
|
1268
|
+
spanId: 'b7ad6b7169203332',
|
|
1269
|
+
traceFlags: 1,
|
|
1270
|
+
},
|
|
1271
|
+
},
|
|
1272
|
+
];
|
|
1273
|
+
|
|
1274
|
+
// Verify the method can be called without error
|
|
1275
|
+
const result = await trace(async (ctx: TraceContext) => {
|
|
1276
|
+
ctx.addLinks(links);
|
|
1277
|
+
return 'done';
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
expect(result).toBe('done');
|
|
1281
|
+
expect(collector.getSpans()).toHaveLength(1);
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1129
1284
|
});
|
package/src/functional.ts
CHANGED
|
@@ -406,6 +406,11 @@ function createDummyCtx<
|
|
|
406
406
|
setAttributes: () => {},
|
|
407
407
|
setStatus: () => {},
|
|
408
408
|
recordException: () => {},
|
|
409
|
+
addEvent: () => {},
|
|
410
|
+
addLink: () => {},
|
|
411
|
+
addLinks: () => {},
|
|
412
|
+
updateName: () => {},
|
|
413
|
+
isRecording: () => false,
|
|
409
414
|
getBaggage: () => {},
|
|
410
415
|
setBaggage: () => '',
|
|
411
416
|
deleteBaggage: () => {},
|
|
@@ -1636,11 +1641,11 @@ export function trace<TReturn = unknown>(
|
|
|
1636
1641
|
name: string,
|
|
1637
1642
|
fn: ExcludeFactoryReturn<(ctx: TraceContext) => TReturn>,
|
|
1638
1643
|
): TReturn;
|
|
1639
|
-
// Overload 2b: Name + factory sync function with no args
|
|
1640
|
-
export function trace(
|
|
1644
|
+
// Overload 2b: Name + factory sync function with no args
|
|
1645
|
+
export function trace<TReturn = unknown>(
|
|
1641
1646
|
name: string,
|
|
1642
|
-
fnFactory: (ctx: TraceContext) => () =>
|
|
1643
|
-
): () =>
|
|
1647
|
+
fnFactory: (ctx: TraceContext) => () => TReturn,
|
|
1648
|
+
): () => TReturn;
|
|
1644
1649
|
// Overload 2c: Name + factory sync function - non-generic for type inference
|
|
1645
1650
|
export function trace<TArgs extends unknown[], TReturn>(
|
|
1646
1651
|
name: string,
|
|
@@ -1670,11 +1675,11 @@ export function trace<TReturn = unknown>(
|
|
|
1670
1675
|
options: TracingOptions<[], TReturn>,
|
|
1671
1676
|
fn: (ctx: TraceContext) => TReturn,
|
|
1672
1677
|
): TReturn;
|
|
1673
|
-
// Overload 3b: Options + factory sync function with no args
|
|
1674
|
-
export function trace(
|
|
1675
|
-
options: TracingOptions,
|
|
1676
|
-
fnFactory: (ctx: TraceContext) => () =>
|
|
1677
|
-
): () =>
|
|
1678
|
+
// Overload 3b: Options + factory sync function with no args
|
|
1679
|
+
export function trace<TReturn = unknown>(
|
|
1680
|
+
options: TracingOptions<[], TReturn>,
|
|
1681
|
+
fnFactory: (ctx: TraceContext) => () => TReturn,
|
|
1682
|
+
): () => TReturn;
|
|
1678
1683
|
// Overload 3c: Options + factory sync function - non-generic for type inference
|
|
1679
1684
|
export function trace<TArgs extends unknown[], TReturn>(
|
|
1680
1685
|
options: TracingOptions<TArgs, TReturn>,
|
|
@@ -1740,11 +1745,11 @@ export function trace<TReturn = unknown>(
|
|
|
1740
1745
|
name: string,
|
|
1741
1746
|
fn: ExcludeFactoryReturn<(ctx: TraceContext) => Promise<TReturn>>,
|
|
1742
1747
|
): Promise<TReturn>;
|
|
1743
|
-
// Overload 5b: Name + factory async function with no args
|
|
1744
|
-
export function trace(
|
|
1748
|
+
// Overload 5b: Name + factory async function with no args
|
|
1749
|
+
export function trace<TReturn = unknown>(
|
|
1745
1750
|
name: string,
|
|
1746
|
-
fnFactory: (ctx: TraceContext) => () => Promise<
|
|
1747
|
-
): () => Promise<
|
|
1751
|
+
fnFactory: (ctx: TraceContext) => () => Promise<TReturn>,
|
|
1752
|
+
): () => Promise<TReturn>;
|
|
1748
1753
|
// Overload 5c: Name + factory async function - non-generic for type inference
|
|
1749
1754
|
export function trace<TArgs extends unknown[], TReturn>(
|
|
1750
1755
|
name: string,
|
|
@@ -1776,11 +1781,11 @@ export function trace<TReturn = unknown>(
|
|
|
1776
1781
|
options: TracingOptions<[], TReturn>,
|
|
1777
1782
|
fn: (ctx: TraceContext) => Promise<TReturn>,
|
|
1778
1783
|
): Promise<TReturn>;
|
|
1779
|
-
// Overload 6b: Options + factory async function with no args
|
|
1780
|
-
export function trace(
|
|
1781
|
-
options: TracingOptions,
|
|
1782
|
-
fnFactory: (ctx: TraceContext) => () => Promise<
|
|
1783
|
-
): () => Promise<
|
|
1784
|
+
// Overload 6b: Options + factory async function with no args
|
|
1785
|
+
export function trace<TReturn = unknown>(
|
|
1786
|
+
options: TracingOptions<[], TReturn>,
|
|
1787
|
+
fnFactory: (ctx: TraceContext) => () => Promise<TReturn>,
|
|
1788
|
+
): () => Promise<TReturn>;
|
|
1784
1789
|
// Overload 6c: Options + factory async function - non-generic for type inference
|
|
1785
1790
|
export function trace<TArgs extends unknown[], TReturn>(
|
|
1786
1791
|
options: TracingOptions<TArgs, TReturn>,
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type inference tests for trace() function
|
|
3
|
+
*
|
|
4
|
+
* These tests verify that TypeScript correctly infers return types
|
|
5
|
+
* for various trace() call signatures.
|
|
6
|
+
*
|
|
7
|
+
* Run with: pnpm run type-check
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from 'vitest';
|
|
11
|
+
import { trace } from './functional';
|
|
12
|
+
import type { TraceContext } from './trace-helpers';
|
|
13
|
+
|
|
14
|
+
describe('trace() type inference', () => {
|
|
15
|
+
// Helper to ensure we're getting the expected type
|
|
16
|
+
// If the type is `unknown`, accessing .foo will cause a type error
|
|
17
|
+
// This is a compile-time check
|
|
18
|
+
|
|
19
|
+
it('trace(fn) - single argument factory should infer return type', async () => {
|
|
20
|
+
// This SHOULD work - returns Promise<{ foo: string }>
|
|
21
|
+
const fn1 = trace((_ctx: TraceContext) => async () => {
|
|
22
|
+
return { foo: 'bar' };
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const result1 = await fn1();
|
|
26
|
+
// If type is correct, this compiles. If unknown, this errors.
|
|
27
|
+
expect(result1.foo).toBe('bar');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('trace(fn) - without explicit ctx type should infer return type', async () => {
|
|
31
|
+
// Test from bug report: ctx without explicit type annotation
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
33
|
+
const fn1 = trace((ctx) => async () => {
|
|
34
|
+
return { foo: 'bar' };
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const result1 = await fn1();
|
|
38
|
+
// If type is correct, this compiles. If unknown, this errors.
|
|
39
|
+
expect(result1.foo).toBe('bar');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('trace(name, fn) - two argument factory should infer return type', async () => {
|
|
43
|
+
// BUG: This SHOULD return Promise<{ foo: string }> but might return unknown
|
|
44
|
+
const fn2 = trace('my-span-name', (_ctx: TraceContext) => async () => {
|
|
45
|
+
return { foo: 'bar' };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const result2 = await fn2();
|
|
49
|
+
// If the bug exists, TypeScript will error here because result2 is `unknown`
|
|
50
|
+
// and we can't access .foo on unknown
|
|
51
|
+
expect(result2.foo).toBe('bar');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('trace(name, fn) - without explicit ctx type should infer return type', async () => {
|
|
55
|
+
// Exact reproduction from bug report
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
57
|
+
const fn2 = trace('my-span-name', (ctx) => async () => {
|
|
58
|
+
return { foo: 'bar' };
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result2 = await fn2();
|
|
62
|
+
// If the type is properly inferred as { foo: string }, accessing .foo should work.
|
|
63
|
+
// If the bug exists and type is `unknown`, TypeScript will error here.
|
|
64
|
+
// Adding @ts-expect-error would make the type check pass ONLY if there's an error.
|
|
65
|
+
expect(result2.foo).toBe('bar');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('BUG VERIFICATION: trace(name, fn) returns unknown type', async () => {
|
|
69
|
+
// This test uses @ts-expect-error to VERIFY the bug exists
|
|
70
|
+
// If @ts-expect-error is "unused", that means the bug is FIXED
|
|
71
|
+
// If @ts-expect-error is needed, the bug EXISTS
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
73
|
+
const fn2 = trace('my-span-name', (ctx) => async () => {
|
|
74
|
+
return { foo: 'bar' };
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const result2 = await fn2();
|
|
78
|
+
|
|
79
|
+
// If result2 is inferred as `unknown`, we'd need @ts-expect-error
|
|
80
|
+
// If result2 is inferred as `{ foo: string }`, this line works without error
|
|
81
|
+
// BUG FIXED: No @ts-expect-error needed anymore!
|
|
82
|
+
const _fooValue: string = result2.foo;
|
|
83
|
+
expect(_fooValue).toBe('bar');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('trace(fn) with args should infer return type', async () => {
|
|
87
|
+
const fn3 = trace(
|
|
88
|
+
(_ctx: TraceContext) => async (name: string, age: number) => {
|
|
89
|
+
return { name, age };
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const result3 = await fn3('Alice', 30);
|
|
94
|
+
expect(result3.name).toBe('Alice');
|
|
95
|
+
expect(result3.age).toBe(30);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('trace(name, fn) with args should infer return type', async () => {
|
|
99
|
+
// BUG: This should also infer correctly
|
|
100
|
+
const fn4 = trace(
|
|
101
|
+
'user.create',
|
|
102
|
+
(_ctx: TraceContext) => async (name: string, age: number) => {
|
|
103
|
+
return { name, age };
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const result4 = await fn4('Bob', 25);
|
|
108
|
+
// If type is correct, this compiles. If unknown, this errors.
|
|
109
|
+
expect(result4.name).toBe('Bob');
|
|
110
|
+
expect(result4.age).toBe(25);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('trace(name, fn) sync factory should infer return type', () => {
|
|
114
|
+
const fn5 = trace('sync.operation', (_ctx: TraceContext) => () => {
|
|
115
|
+
return 42;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result5 = fn5();
|
|
119
|
+
// Type should be number, not unknown
|
|
120
|
+
const numResult: number = result5;
|
|
121
|
+
expect(numResult).toBe(42);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('trace(name, fn) plain function should infer return type', async () => {
|
|
125
|
+
// Plain function (no ctx) with name
|
|
126
|
+
const fn6 = trace('plain.function', async (a: number, b: number) => {
|
|
127
|
+
return a + b;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const result6 = await fn6(2, 3);
|
|
131
|
+
// Type should be number, not unknown
|
|
132
|
+
const numResult: number = result6;
|
|
133
|
+
expect(numResult).toBe(5);
|
|
134
|
+
});
|
|
135
|
+
});
|
package/src/trace-context.ts
CHANGED
|
@@ -7,6 +7,8 @@ import type {
|
|
|
7
7
|
SpanStatusCode,
|
|
8
8
|
BaggageEntry,
|
|
9
9
|
Context,
|
|
10
|
+
Link,
|
|
11
|
+
TimeInput,
|
|
10
12
|
} from '@opentelemetry/api';
|
|
11
13
|
import { context, propagation } from '@opentelemetry/api';
|
|
12
14
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
@@ -76,14 +78,44 @@ export interface TraceContextBase {
|
|
|
76
78
|
correlationId: string;
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Attribute value types following OpenTelemetry specification.
|
|
83
|
+
* Supports primitive values and arrays of homogeneous primitives.
|
|
84
|
+
*/
|
|
85
|
+
export type AttributeValue =
|
|
86
|
+
| string
|
|
87
|
+
| number
|
|
88
|
+
| boolean
|
|
89
|
+
| string[]
|
|
90
|
+
| number[]
|
|
91
|
+
| boolean[];
|
|
92
|
+
|
|
79
93
|
/**
|
|
80
94
|
* Span methods available on trace context
|
|
81
95
|
*/
|
|
82
96
|
export interface SpanMethods {
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
/** Set a single attribute on the span */
|
|
98
|
+
setAttribute(key: string, value: AttributeValue): void;
|
|
99
|
+
/** Set multiple attributes on the span */
|
|
100
|
+
setAttributes(attrs: Record<string, AttributeValue>): void;
|
|
101
|
+
/** Set the status of the span */
|
|
85
102
|
setStatus(status: { code: SpanStatusCode; message?: string }): void;
|
|
86
|
-
|
|
103
|
+
/** Record an exception on the span */
|
|
104
|
+
recordException(exception: Error, time?: TimeInput): void;
|
|
105
|
+
/** Add an event to the span (for logging milestones/checkpoints) */
|
|
106
|
+
addEvent(
|
|
107
|
+
name: string,
|
|
108
|
+
attributesOrStartTime?: Record<string, AttributeValue> | TimeInput,
|
|
109
|
+
startTime?: TimeInput,
|
|
110
|
+
): void;
|
|
111
|
+
/** Add a link to another span */
|
|
112
|
+
addLink(link: Link): void;
|
|
113
|
+
/** Add multiple links to other spans */
|
|
114
|
+
addLinks(links: Link[]): void;
|
|
115
|
+
/** Update the span name dynamically */
|
|
116
|
+
updateName(name: string): void;
|
|
117
|
+
/** Check if the span is recording */
|
|
118
|
+
isRecording(): boolean;
|
|
87
119
|
}
|
|
88
120
|
|
|
89
121
|
/**
|
|
@@ -355,6 +387,11 @@ export function createTraceContext<
|
|
|
355
387
|
setAttributes: span.setAttributes.bind(span),
|
|
356
388
|
setStatus: span.setStatus.bind(span),
|
|
357
389
|
recordException: span.recordException.bind(span),
|
|
390
|
+
addEvent: span.addEvent.bind(span),
|
|
391
|
+
addLink: span.addLink.bind(span),
|
|
392
|
+
addLinks: span.addLinks.bind(span),
|
|
393
|
+
updateName: span.updateName.bind(span),
|
|
394
|
+
isRecording: span.isRecording.bind(span),
|
|
358
395
|
...baggageHelpers,
|
|
359
396
|
};
|
|
360
397
|
}
|