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.
Files changed (48) hide show
  1. package/dist/{chunk-TRI4V5BF.cjs → chunk-2EHB2KXS.cjs} +4 -4
  2. package/dist/{chunk-TRI4V5BF.cjs.map → chunk-2EHB2KXS.cjs.map} +1 -1
  3. package/dist/{chunk-M4ANN7RL.js → chunk-AXCCOSA3.js} +3 -3
  4. package/dist/{chunk-M4ANN7RL.js.map → chunk-AXCCOSA3.js.map} +1 -1
  5. package/dist/{chunk-NHCNRQD3.cjs → chunk-IW37CN6L.cjs} +7 -2
  6. package/dist/chunk-IW37CN6L.cjs.map +1 -0
  7. package/dist/{chunk-CDZBY5WU.cjs → chunk-M4K4GMI7.cjs} +23 -14
  8. package/dist/chunk-M4K4GMI7.cjs.map +1 -0
  9. package/dist/{chunk-ZWLYTPLR.js → chunk-MAAPH5NI.js} +3 -3
  10. package/dist/{chunk-ZWLYTPLR.js.map → chunk-MAAPH5NI.js.map} +1 -1
  11. package/dist/{chunk-DMUSPYUX.js → chunk-PTMQAULX.js} +13 -4
  12. package/dist/chunk-PTMQAULX.js.map +1 -0
  13. package/dist/{chunk-LGE7W72O.cjs → chunk-UUL3EEY4.cjs} +7 -7
  14. package/dist/{chunk-LGE7W72O.cjs.map → chunk-UUL3EEY4.cjs.map} +1 -1
  15. package/dist/{chunk-HCCXC7XG.js → chunk-YHW3MAS7.js} +7 -2
  16. package/dist/chunk-YHW3MAS7.js.map +1 -0
  17. package/dist/decorators.cjs +5 -5
  18. package/dist/decorators.d.cts +1 -1
  19. package/dist/decorators.d.ts +1 -1
  20. package/dist/decorators.js +3 -3
  21. package/dist/functional.cjs +10 -10
  22. package/dist/functional.d.cts +5 -5
  23. package/dist/functional.d.ts +5 -5
  24. package/dist/functional.js +3 -3
  25. package/dist/http.cjs +3 -3
  26. package/dist/http.js +1 -1
  27. package/dist/index.cjs +27 -27
  28. package/dist/index.d.cts +1 -1
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.js +6 -6
  31. package/dist/semantic-helpers.cjs +8 -8
  32. package/dist/semantic-helpers.d.cts +1 -1
  33. package/dist/semantic-helpers.d.ts +1 -1
  34. package/dist/semantic-helpers.js +4 -4
  35. package/dist/{trace-context-DRZdUvVY.d.cts → trace-context-gTjI7ZDb.d.cts} +23 -4
  36. package/dist/{trace-context-DRZdUvVY.d.ts → trace-context-gTjI7ZDb.d.ts} +23 -4
  37. package/dist/trace-helpers.cjs +13 -13
  38. package/dist/trace-helpers.js +2 -2
  39. package/package.json +6 -6
  40. package/src/functional.strict-types.typecheck.ts +53 -0
  41. package/src/functional.test.ts +155 -0
  42. package/src/functional.ts +23 -18
  43. package/src/functional.types.test.ts +135 -0
  44. package/src/trace-context.ts +40 -3
  45. package/dist/chunk-CDZBY5WU.cjs.map +0 -1
  46. package/dist/chunk-DMUSPYUX.js.map +0 -1
  47. package/dist/chunk-HCCXC7XG.js.map +0 -1
  48. package/dist/chunk-NHCNRQD3.cjs.map +0 -1
@@ -1,54 +1,54 @@
1
1
  'use strict';
2
2
 
3
- var chunkTRI4V5BF_cjs = require('./chunk-TRI4V5BF.cjs');
4
- require('./chunk-NHCNRQD3.cjs');
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 chunkTRI4V5BF_cjs.createDeterministicTraceId; }
11
+ get: function () { return chunk2EHB2KXS_cjs.createDeterministicTraceId; }
12
12
  });
13
13
  Object.defineProperty(exports, "enrichWithTraceContext", {
14
14
  enumerable: true,
15
- get: function () { return chunkTRI4V5BF_cjs.enrichWithTraceContext; }
15
+ get: function () { return chunk2EHB2KXS_cjs.enrichWithTraceContext; }
16
16
  });
17
17
  Object.defineProperty(exports, "finalizeSpan", {
18
18
  enumerable: true,
19
- get: function () { return chunkTRI4V5BF_cjs.finalizeSpan; }
19
+ get: function () { return chunk2EHB2KXS_cjs.finalizeSpan; }
20
20
  });
21
21
  Object.defineProperty(exports, "flattenMetadata", {
22
22
  enumerable: true,
23
- get: function () { return chunkTRI4V5BF_cjs.flattenMetadata; }
23
+ get: function () { return chunk2EHB2KXS_cjs.flattenMetadata; }
24
24
  });
25
25
  Object.defineProperty(exports, "getActiveContext", {
26
26
  enumerable: true,
27
- get: function () { return chunkTRI4V5BF_cjs.getActiveContext; }
27
+ get: function () { return chunk2EHB2KXS_cjs.getActiveContext; }
28
28
  });
29
29
  Object.defineProperty(exports, "getActiveSpan", {
30
30
  enumerable: true,
31
- get: function () { return chunkTRI4V5BF_cjs.getActiveSpan; }
31
+ get: function () { return chunk2EHB2KXS_cjs.getActiveSpan; }
32
32
  });
33
33
  Object.defineProperty(exports, "getTraceContext", {
34
34
  enumerable: true,
35
- get: function () { return chunkTRI4V5BF_cjs.getTraceContext; }
35
+ get: function () { return chunk2EHB2KXS_cjs.getTraceContext; }
36
36
  });
37
37
  Object.defineProperty(exports, "getTracer", {
38
38
  enumerable: true,
39
- get: function () { return chunkTRI4V5BF_cjs.getTracer; }
39
+ get: function () { return chunk2EHB2KXS_cjs.getTracer; }
40
40
  });
41
41
  Object.defineProperty(exports, "isTracing", {
42
42
  enumerable: true,
43
- get: function () { return chunkTRI4V5BF_cjs.isTracing; }
43
+ get: function () { return chunk2EHB2KXS_cjs.isTracing; }
44
44
  });
45
45
  Object.defineProperty(exports, "runWithSpan", {
46
46
  enumerable: true,
47
- get: function () { return chunkTRI4V5BF_cjs.runWithSpan; }
47
+ get: function () { return chunk2EHB2KXS_cjs.runWithSpan; }
48
48
  });
49
49
  Object.defineProperty(exports, "setSpanName", {
50
50
  enumerable: true,
51
- get: function () { return chunkTRI4V5BF_cjs.setSpanName; }
51
+ get: function () { return chunk2EHB2KXS_cjs.setSpanName; }
52
52
  });
53
53
  //# sourceMappingURL=trace-helpers.cjs.map
54
54
  //# sourceMappingURL=trace-helpers.cjs.map
@@ -1,5 +1,5 @@
1
- export { createDeterministicTraceId, enrichWithTraceContext, finalizeSpan, flattenMetadata, getActiveContext, getActiveSpan, getTraceContext, getTracer, isTracing, runWithSpan, setSpanName } from './chunk-M4ANN7RL.js';
2
- import './chunk-HCCXC7XG.js';
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.2.0",
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": "^1.11.0",
164
- "yaml": "^2.6.0",
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.1",
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.21.1",
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.1",
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 };
@@ -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 - non-generic for type inference
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) => () => unknown,
1643
- ): () => unknown;
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 - non-generic for type inference
1674
- export function trace(
1675
- options: TracingOptions,
1676
- fnFactory: (ctx: TraceContext) => () => unknown,
1677
- ): () => unknown;
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 - non-generic for type inference
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<unknown>,
1747
- ): () => Promise<unknown>;
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 - non-generic for type inference
1780
- export function trace(
1781
- options: TracingOptions,
1782
- fnFactory: (ctx: TraceContext) => () => Promise<unknown>,
1783
- ): () => Promise<unknown>;
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
+ });
@@ -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
- setAttribute(key: string, value: string | number | boolean): void;
84
- setAttributes(attrs: Record<string, string | number | boolean>): void;
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
- recordException(exception: Error): void;
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
  }