@zuplo/cli 7.7.8 → 7.7.10

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 (154) hide show
  1. package/node_modules/@posthog/core/dist/error-tracking/parsers/base.d.ts.map +1 -1
  2. package/node_modules/@posthog/core/dist/error-tracking/parsers/base.js +19 -2
  3. package/node_modules/@posthog/core/dist/error-tracking/parsers/base.mjs +19 -2
  4. package/node_modules/@posthog/core/dist/index.d.ts +7 -0
  5. package/node_modules/@posthog/core/dist/index.d.ts.map +1 -1
  6. package/node_modules/@posthog/core/dist/index.js +82 -21
  7. package/node_modules/@posthog/core/dist/index.mjs +4 -0
  8. package/node_modules/@posthog/core/dist/logs/index.d.ts +3 -2
  9. package/node_modules/@posthog/core/dist/logs/index.d.ts.map +1 -1
  10. package/node_modules/@posthog/core/dist/logs/index.js +28 -22
  11. package/node_modules/@posthog/core/dist/logs/index.mjs +29 -23
  12. package/node_modules/@posthog/core/dist/logs/logs-utils.js +1 -1
  13. package/node_modules/@posthog/core/dist/logs/logs-utils.mjs +2 -2
  14. package/node_modules/@posthog/core/dist/logs/types.d.ts +2 -2
  15. package/node_modules/@posthog/core/dist/metrics/index.d.ts +5 -2
  16. package/node_modules/@posthog/core/dist/metrics/index.d.ts.map +1 -1
  17. package/node_modules/@posthog/core/dist/metrics/index.js +28 -14
  18. package/node_modules/@posthog/core/dist/metrics/index.mjs +29 -15
  19. package/node_modules/@posthog/core/dist/metrics/metrics-utils.d.ts.map +1 -1
  20. package/node_modules/@posthog/core/dist/metrics/metrics-utils.js +1 -2
  21. package/node_modules/@posthog/core/dist/metrics/metrics-utils.mjs +2 -3
  22. package/node_modules/@posthog/core/dist/metrics/types.d.ts +7 -0
  23. package/node_modules/@posthog/core/dist/metrics/types.d.ts.map +1 -1
  24. package/node_modules/@posthog/core/dist/posthog-core-stateless.d.ts +32 -4
  25. package/node_modules/@posthog/core/dist/posthog-core-stateless.d.ts.map +1 -1
  26. package/node_modules/@posthog/core/dist/posthog-core-stateless.js +68 -20
  27. package/node_modules/@posthog/core/dist/posthog-core-stateless.mjs +68 -20
  28. package/node_modules/@posthog/core/dist/traces/config.d.ts +13 -0
  29. package/node_modules/@posthog/core/dist/traces/config.d.ts.map +1 -0
  30. package/node_modules/@posthog/core/dist/traces/config.js +101 -0
  31. package/node_modules/@posthog/core/dist/traces/config.mjs +63 -0
  32. package/node_modules/@posthog/core/dist/traces/context.d.ts +18 -0
  33. package/node_modules/@posthog/core/dist/traces/context.d.ts.map +1 -0
  34. package/node_modules/@posthog/core/dist/traces/context.js +53 -0
  35. package/node_modules/@posthog/core/dist/traces/context.mjs +15 -0
  36. package/node_modules/@posthog/core/dist/traces/ids.d.ts +12 -0
  37. package/node_modules/@posthog/core/dist/traces/ids.d.ts.map +1 -0
  38. package/node_modules/@posthog/core/dist/traces/ids.js +92 -0
  39. package/node_modules/@posthog/core/dist/traces/ids.mjs +42 -0
  40. package/node_modules/@posthog/core/dist/traces/index.d.ts +150 -0
  41. package/node_modules/@posthog/core/dist/traces/index.d.ts.map +1 -0
  42. package/node_modules/@posthog/core/dist/traces/index.js +552 -0
  43. package/node_modules/@posthog/core/dist/traces/index.mjs +514 -0
  44. package/node_modules/@posthog/core/dist/traces/otlp.d.ts +24 -0
  45. package/node_modules/@posthog/core/dist/traces/otlp.d.ts.map +1 -0
  46. package/node_modules/@posthog/core/dist/traces/otlp.js +158 -0
  47. package/node_modules/@posthog/core/dist/traces/otlp.mjs +108 -0
  48. package/node_modules/@posthog/core/dist/traces/sanitize.d.ts +32 -0
  49. package/node_modules/@posthog/core/dist/traces/sanitize.d.ts.map +1 -0
  50. package/node_modules/@posthog/core/dist/traces/sanitize.js +93 -0
  51. package/node_modules/@posthog/core/dist/traces/sanitize.mjs +43 -0
  52. package/node_modules/@posthog/core/dist/traces/span.d.ts +198 -0
  53. package/node_modules/@posthog/core/dist/traces/span.d.ts.map +1 -0
  54. package/node_modules/@posthog/core/dist/traces/span.js +538 -0
  55. package/node_modules/@posthog/core/dist/traces/span.mjs +467 -0
  56. package/node_modules/@posthog/core/dist/traces/traceparent.d.ts +51 -0
  57. package/node_modules/@posthog/core/dist/traces/traceparent.d.ts.map +1 -0
  58. package/node_modules/@posthog/core/dist/traces/traceparent.js +116 -0
  59. package/node_modules/@posthog/core/dist/traces/traceparent.mjs +63 -0
  60. package/node_modules/@posthog/core/dist/traces/types.d.ts +114 -0
  61. package/node_modules/@posthog/core/dist/traces/types.d.ts.map +1 -0
  62. package/node_modules/@posthog/core/dist/traces/types.js +18 -0
  63. package/node_modules/@posthog/core/dist/traces/types.mjs +1 -0
  64. package/node_modules/@posthog/core/dist/utils/backoff.d.ts +23 -0
  65. package/node_modules/@posthog/core/dist/utils/backoff.d.ts.map +1 -0
  66. package/node_modules/@posthog/core/dist/utils/backoff.js +65 -0
  67. package/node_modules/@posthog/core/dist/utils/backoff.mjs +14 -0
  68. package/node_modules/@posthog/core/dist/utils/flush-timer.d.ts +26 -0
  69. package/node_modules/@posthog/core/dist/utils/flush-timer.d.ts.map +1 -0
  70. package/node_modules/@posthog/core/dist/utils/flush-timer.js +67 -0
  71. package/node_modules/@posthog/core/dist/utils/flush-timer.mjs +29 -0
  72. package/node_modules/@posthog/core/dist/utils/index.d.ts +1 -0
  73. package/node_modules/@posthog/core/dist/utils/index.d.ts.map +1 -1
  74. package/node_modules/@posthog/core/dist/utils/index.js +22 -2
  75. package/node_modules/@posthog/core/dist/utils/index.mjs +8 -1
  76. package/node_modules/@posthog/core/dist/utils/json-utils.d.ts +7 -0
  77. package/node_modules/@posthog/core/dist/utils/json-utils.d.ts.map +1 -1
  78. package/node_modules/@posthog/core/dist/utils/json-utils.js +27 -0
  79. package/node_modules/@posthog/core/dist/utils/json-utils.mjs +25 -1
  80. package/node_modules/@posthog/core/dist/utils/otlp-resource.d.ts +13 -2
  81. package/node_modules/@posthog/core/dist/utils/otlp-resource.d.ts.map +1 -1
  82. package/node_modules/@posthog/core/dist/utils/otlp-resource.js +34 -7
  83. package/node_modules/@posthog/core/dist/utils/otlp-resource.mjs +26 -2
  84. package/node_modules/@posthog/core/dist/utils/retry-after.d.ts +55 -0
  85. package/node_modules/@posthog/core/dist/utils/retry-after.d.ts.map +1 -0
  86. package/node_modules/@posthog/core/dist/utils/retry-after.js +95 -0
  87. package/node_modules/@posthog/core/dist/utils/retry-after.mjs +50 -0
  88. package/node_modules/@posthog/core/package.json +3 -3
  89. package/node_modules/@posthog/core/src/error-tracking/parsers/base.spec.ts +77 -7
  90. package/node_modules/@posthog/core/src/error-tracking/parsers/base.ts +42 -19
  91. package/node_modules/@posthog/core/src/index.ts +18 -0
  92. package/node_modules/@posthog/core/src/logs/index.spec.ts +490 -2
  93. package/node_modules/@posthog/core/src/logs/index.ts +63 -31
  94. package/node_modules/@posthog/core/src/logs/logs-utils.ts +2 -2
  95. package/node_modules/@posthog/core/src/logs/types.ts +2 -2
  96. package/node_modules/@posthog/core/src/metrics/index.spec.ts +304 -0
  97. package/node_modules/@posthog/core/src/metrics/index.ts +53 -16
  98. package/node_modules/@posthog/core/src/metrics/metrics-utils.ts +2 -3
  99. package/node_modules/@posthog/core/src/metrics/types.ts +10 -2
  100. package/node_modules/@posthog/core/src/posthog-core-stateless.ts +157 -38
  101. package/node_modules/@posthog/core/src/traces/config.spec.ts +271 -0
  102. package/node_modules/@posthog/core/src/traces/config.ts +142 -0
  103. package/node_modules/@posthog/core/src/traces/context.ts +30 -0
  104. package/node_modules/@posthog/core/src/traces/ids.spec.ts +115 -0
  105. package/node_modules/@posthog/core/src/traces/ids.ts +75 -0
  106. package/node_modules/@posthog/core/src/traces/index.spec.ts +3269 -0
  107. package/node_modules/@posthog/core/src/traces/index.ts +1063 -0
  108. package/node_modules/@posthog/core/src/traces/live-spans.spec.ts +69 -0
  109. package/node_modules/@posthog/core/src/traces/otlp.spec.ts +368 -0
  110. package/node_modules/@posthog/core/src/traces/otlp.ts +191 -0
  111. package/node_modules/@posthog/core/src/traces/sanitize.ts +116 -0
  112. package/node_modules/@posthog/core/src/traces/span.spec.ts +1123 -0
  113. package/node_modules/@posthog/core/src/traces/span.ts +844 -0
  114. package/node_modules/@posthog/core/src/traces/traceparent.spec.ts +184 -0
  115. package/node_modules/@posthog/core/src/traces/traceparent.ts +172 -0
  116. package/node_modules/@posthog/core/src/traces/types.ts +140 -0
  117. package/node_modules/@posthog/core/src/utils/backoff.ts +44 -0
  118. package/node_modules/@posthog/core/src/utils/flush-timer.ts +50 -0
  119. package/node_modules/@posthog/core/src/utils/index.ts +8 -0
  120. package/node_modules/@posthog/core/src/utils/json-utils.ts +33 -0
  121. package/node_modules/@posthog/core/src/utils/otlp-resource.spec.ts +55 -9
  122. package/node_modules/@posthog/core/src/utils/otlp-resource.ts +45 -4
  123. package/node_modules/@posthog/core/src/utils/retry-after.ts +135 -0
  124. package/node_modules/@posthog/types/dist/index.d.ts +1 -0
  125. package/node_modules/@posthog/types/dist/index.d.ts.map +1 -1
  126. package/node_modules/@posthog/types/dist/posthog-config.d.ts +29 -9
  127. package/node_modules/@posthog/types/dist/posthog-config.d.ts.map +1 -1
  128. package/node_modules/@posthog/types/dist/traces.d.ts +387 -0
  129. package/node_modules/@posthog/types/dist/traces.d.ts.map +1 -0
  130. package/node_modules/@posthog/types/dist/traces.js +18 -0
  131. package/node_modules/@posthog/types/dist/traces.mjs +1 -0
  132. package/node_modules/@posthog/types/package.json +1 -1
  133. package/node_modules/@posthog/types/src/index.ts +19 -0
  134. package/node_modules/@posthog/types/src/posthog-config.ts +29 -9
  135. package/node_modules/@posthog/types/src/traces.ts +429 -0
  136. package/node_modules/@zuplo/core/package.json +1 -1
  137. package/node_modules/@zuplo/graphql/package.json +1 -1
  138. package/node_modules/@zuplo/openapi-tools/package.json +1 -1
  139. package/node_modules/@zuplo/otel/package.json +1 -1
  140. package/node_modules/@zuplo/runtime/out/esm/{chunk-7ZX347IG.js → chunk-JTYJUJYO.js} +3 -3
  141. package/node_modules/@zuplo/runtime/out/esm/chunk-JTYJUJYO.js.map +1 -0
  142. package/node_modules/@zuplo/runtime/out/esm/chunk-M3VFT3HF.js +410 -0
  143. package/node_modules/@zuplo/runtime/out/esm/chunk-M3VFT3HF.js.map +1 -0
  144. package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
  145. package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +3 -3
  146. package/node_modules/@zuplo/runtime/out/esm/mocks/index.js +1 -1
  147. package/node_modules/@zuplo/runtime/out/esm/sdks/aws/index.js +1 -1
  148. package/node_modules/@zuplo/runtime/out/types/index.d.ts +20 -1
  149. package/node_modules/@zuplo/runtime/package.json +1 -1
  150. package/package.json +11 -11
  151. package/node_modules/@zuplo/runtime/out/esm/chunk-7ZX347IG.js.map +0 -1
  152. package/node_modules/@zuplo/runtime/out/esm/chunk-DRQ5CMVC.js +0 -410
  153. package/node_modules/@zuplo/runtime/out/esm/chunk-DRQ5CMVC.js.map +0 -1
  154. /package/node_modules/@zuplo/runtime/out/esm/{chunk-DRQ5CMVC.js.LEGAL.txt → chunk-M3VFT3HF.js.LEGAL.txt} +0 -0
@@ -0,0 +1,844 @@
1
+ import type { Span, SpanAttributes, SpanAttributeValue, SpanKind, SpanStatusCode, SpanTimeInput } from '@posthog/types'
2
+ import type { Logger } from '../types'
3
+ import type { SpanContextManager, SpanEventRecord, SpanRecord } from './types'
4
+ import {
5
+ formatTraceparent,
6
+ normalizeTraceparent,
7
+ sanitizeTracestate,
8
+ traceparentHeader,
9
+ TRACE_FLAGS_SAMPLED,
10
+ } from './traceparent'
11
+ import { clampEndTime, resolveSuppliedTime, sanitizeName } from './sanitize'
12
+ import { isArray, isError, isNullish } from '../utils'
13
+ import {
14
+ CIRCULAR_VALUE,
15
+ MAX_JSON_SAFE_VALUE_DEPTH,
16
+ MAX_JSON_SAFE_VALUE_ITEMS,
17
+ MAX_JSON_SAFE_VALUE_NODES,
18
+ UNSERIALIZABLE_VALUE,
19
+ assignUserAttributes,
20
+ } from '../utils/json-utils'
21
+
22
+ /**
23
+ * A monotonic millisecond reading where the platform has one, so an NTP
24
+ * correction mid-span can't produce a negative duration.
25
+ */
26
+ export function monotonicNow(): number | undefined {
27
+ const perf = (globalThis as { performance?: { now?: () => number } }).performance
28
+ return typeof perf?.now === 'function' ? perf.now() : undefined
29
+ }
30
+
31
+ /** A root span's start read on both clocks: the basis its local descendants are placed on. */
32
+ export interface ClockAnchor {
33
+ wall: number
34
+ mono: number
35
+ }
36
+
37
+ export interface SpanInit {
38
+ traceId: string
39
+ spanId: string
40
+ parentSpanId?: string
41
+ traceState?: string
42
+ /** The trace-flags byte to propagate; the inbound one when continuing a remote trace. */
43
+ traceFlags?: string
44
+ /** True when the parent came from a `traceparent` header rather than a local handle. */
45
+ parentIsRemote?: boolean
46
+ name: string
47
+ kind: SpanKind
48
+ attributes: SpanAttributes
49
+ /** ms epoch. */
50
+ startTime: number
51
+ /** True when the caller supplied an explicit `startTime`. */
52
+ backdated: boolean
53
+ /** Inherited from a local parent; replaces `startTime` unless the span is backdated. */
54
+ clockAnchor?: ClockAnchor
55
+ /** Keys the SDK attached itself. Exempt from the attribute cap and never evicted. */
56
+ autoAttributeKeys: string[]
57
+ maxAttributes: number
58
+ maxEvents: number
59
+ maxAttributesPerEvent: number
60
+ maxAttributeValueLength: number
61
+ }
62
+
63
+ export class PostHogSpan implements Span {
64
+ private readonly _traceId: string
65
+ private readonly _spanId: string
66
+ private readonly _parentSpanId?: string
67
+ private readonly _traceState?: string
68
+ private readonly _traceFlags: string
69
+ private readonly _parentIsRemote: boolean
70
+ private readonly _startTime: number
71
+ // Absent on backdated spans and on platforms with no monotonic source.
72
+ private readonly _startMono?: number
73
+ private readonly _clockAnchor?: ClockAnchor
74
+
75
+ private _name: string
76
+ private _kind: SpanKind
77
+ private _attributes: SpanAttributes
78
+ private _events: SpanEventRecord[] = []
79
+ private _status?: { code: SpanStatusCode; message?: string }
80
+ private _ended = false
81
+ private readonly _autoKeys: Set<string>
82
+ private readonly _maxAttributes: number
83
+ private readonly _maxEvents: number
84
+ private readonly _maxAttributesPerEvent: number
85
+ private readonly _maxAttributeValueLength: number
86
+ private _userAttributeCount = 0
87
+ private _userEventCount = 0
88
+ private _droppedAttributes = 0
89
+ private _droppedEvents = 0
90
+
91
+ constructor(
92
+ init: SpanInit,
93
+ private readonly _onEnd: (record: SpanRecord, autoKeys: ReadonlySet<string>) => void,
94
+ private readonly _logger?: Logger
95
+ ) {
96
+ this._traceId = init.traceId
97
+ this._spanId = init.spanId
98
+ this._parentSpanId = init.parentSpanId
99
+ this._traceState = init.traceState
100
+ this._traceFlags = init.traceFlags ?? TRACE_FLAGS_SAMPLED
101
+ this._parentIsRemote = init.parentIsRemote ?? false
102
+ this._name = init.name
103
+ this._kind = init.kind
104
+ this._autoKeys = new Set(init.autoAttributeKeys)
105
+ this._maxAttributes = init.maxAttributes
106
+ this._maxEvents = init.maxEvents
107
+ this._maxAttributesPerEvent = init.maxAttributesPerEvent
108
+ this._maxAttributeValueLength = init.maxAttributeValueLength
109
+ // Null-prototype: a `__proto__` key would otherwise swap this object's prototype
110
+ // instead of becoming an entry, and `toString` and friends would read as
111
+ // already-present.
112
+ this._attributes = Object.create(null) as SpanAttributes
113
+ // Object.keys, not for...in: the latter walks the prototype chain, so a
114
+ // polluted `Object.prototype` key would become an attribute of every span.
115
+ for (const key of Object.keys(init.attributes)) {
116
+ this._writeAttribute(key, init.attributes[key])
117
+ }
118
+ this._startMono = init.backdated ? undefined : monotonicNow()
119
+ this._startTime =
120
+ init.clockAnchor && this._startMono !== undefined
121
+ ? init.clockAnchor.wall + (this._startMono - init.clockAnchor.mono)
122
+ : init.startTime
123
+ if (this._startMono !== undefined) {
124
+ this._clockAnchor = init.clockAnchor ?? { wall: this._startTime, mono: this._startMono }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * "Now" on this span's clock basis: start plus monotonic elapsed where we
130
+ * have it, wall clock otherwise.
131
+ */
132
+ private _now(): number {
133
+ if (this._startMono !== undefined) {
134
+ const mono = monotonicNow()
135
+ if (mono !== undefined) {
136
+ return this._startTime + Math.max(0, mono - this._startMono)
137
+ }
138
+ }
139
+ return Date.now()
140
+ }
141
+
142
+ /** Guards every mutator: operations after `end()` no-op with a debug warning. */
143
+ private _mutable(operation: string): boolean {
144
+ if (this._ended) {
145
+ this._logger?.debug(`Ignoring ${operation} on a span that has already ended`)
146
+ return false
147
+ }
148
+ return true
149
+ }
150
+
151
+ /**
152
+ * Writes an attribute unless the span is already at its user-attribute cap.
153
+ *
154
+ * Overwriting a key already on the span always succeeds — the cap counts
155
+ * distinct user keys, not writes — and SDK-attached keys never count toward
156
+ * it, so a span at the cap still carries its person and session ids.
157
+ */
158
+ private _writeAttribute(key: string, value: SpanAttributeValue): void {
159
+ // Nullish removes the key rather than occupying it: storing one would spend no
160
+ // budget and make every later write to that key free, exceeding the cap.
161
+ if (isNullish(value)) {
162
+ if (key in this._attributes && !this._autoKeys.has(key)) {
163
+ this._userAttributeCount--
164
+ }
165
+ delete this._attributes[key]
166
+ return
167
+ }
168
+ // The cap is checked before the value is bounded: walking a value the span is
169
+ // about to drop is the dominant cost of a span that overflows its cap.
170
+ if (!this._autoKeys.has(key) && !(key in this._attributes)) {
171
+ if (this._userAttributeCount >= this._maxAttributes) {
172
+ this._droppedAttributes++
173
+ return
174
+ }
175
+ this._userAttributeCount++
176
+ }
177
+ this._attributes[key] = truncateAttributeValue(value, this._maxAttributeValueLength)
178
+ }
179
+
180
+ setAttribute(key: string, value: SpanAttributeValue): this {
181
+ if (this._mutable('setAttribute')) {
182
+ this._writeAttribute(key, value)
183
+ }
184
+ return this
185
+ }
186
+
187
+ setAttributes(attributes: SpanAttributes): this {
188
+ if (this._mutable('setAttributes')) {
189
+ // Read through the shared guard first — own enumerable keys only, and a
190
+ // throwing getter costs its own key — then write each through the cap.
191
+ const safe: SpanAttributes = assignUserAttributes({}, attributes)
192
+ for (const key of Object.keys(safe)) {
193
+ this._writeAttribute(key, safe[key])
194
+ }
195
+ }
196
+ return this
197
+ }
198
+
199
+ addEvent(name: string, attributes?: SpanAttributes, timestamp?: SpanTimeInput): this {
200
+ if (this._mutable('addEvent')) {
201
+ // An exception the SDK records spends an ordinary slot like any other
202
+ // event. A span that fills its events and then throws therefore keeps its
203
+ // `error` status but loses the exception detail, which `droppedEventsCount`
204
+ // reports — enough to find the case in production if it turns out to occur.
205
+ if (this._userEventCount >= this._maxEvents) {
206
+ this._droppedEvents++
207
+ return this
208
+ }
209
+ this._userEventCount++
210
+ // Copied so a caller reusing one object across events can't mutate a recorded one.
211
+ const bounded =
212
+ attributes && boundAttributes(attributes, this._maxAttributesPerEvent, this._maxAttributeValueLength)
213
+ this._events.push({
214
+ name: sanitizeName(name, 'Span event name', this._maxAttributeValueLength, this._logger),
215
+ timestamp: resolveSuppliedTime(timestamp, this._now(), 'event timestamp', this._logger),
216
+ ...(bounded && {
217
+ attributes: bounded.attributes,
218
+ ...(bounded.dropped && { droppedAttributesCount: bounded.dropped }),
219
+ }),
220
+ })
221
+ }
222
+ return this
223
+ }
224
+
225
+ setStatus(status: SpanStatusCode, message?: string): this {
226
+ if (this._mutable('setStatus')) {
227
+ if (status !== 'ok' && status !== 'error') {
228
+ this._logger?.debug(`Ignoring unknown span status "${String(status)}"; expected "ok" or "error"`)
229
+ return this
230
+ }
231
+ // Bounded like an attribute value: a status message is one more string the
232
+ // caller controls, and one large enough takes the span past the body limit.
233
+ this._status = {
234
+ code: status,
235
+ ...(message && { message: truncateString(message, this._maxAttributeValueLength) }),
236
+ }
237
+ }
238
+ return this
239
+ }
240
+
241
+ /** True when the caller explicitly marked the span `ok`; `withSpan` treats that as final. */
242
+ get statusIsExplicitlyOk(): boolean {
243
+ return this._status?.code === 'ok'
244
+ }
245
+
246
+ recordException(error: unknown): this {
247
+ if (!this._mutable('recordException')) {
248
+ return this
249
+ }
250
+ const { type, message, stack } = describeError(error)
251
+ this.addEvent(EXCEPTION_EVENT_NAME, {
252
+ 'exception.type': type,
253
+ 'exception.message': message,
254
+ ...(stack && { 'exception.stacktrace': stack }),
255
+ })
256
+ // recordException is itself an explicit call, so it follows last-write-wins
257
+ // rather than deferring to an earlier `ok`.
258
+ return this.setStatus('error', message)
259
+ }
260
+
261
+ updateName(name: string): this {
262
+ if (this._mutable('updateName')) {
263
+ this._name = sanitizeName(name, 'Span name', this._maxAttributeValueLength, this._logger)
264
+ }
265
+ return this
266
+ }
267
+
268
+ traceparent(): string | null {
269
+ return formatTraceparent(this._traceId, this._spanId, this._traceFlags)
270
+ }
271
+
272
+ tracestate(): string | null {
273
+ return this._traceState ?? null
274
+ }
275
+
276
+ /** Context a child span inherits when this handle is its parent. */
277
+ childContext(): {
278
+ traceId: string
279
+ parentSpanId: string
280
+ traceState?: string
281
+ traceFlags: string
282
+ clockAnchor?: ClockAnchor
283
+ } {
284
+ return {
285
+ traceId: this._traceId,
286
+ parentSpanId: this._spanId,
287
+ traceState: this._traceState,
288
+ // A child of a continued trace keeps propagating the caller's decision.
289
+ traceFlags: this._traceFlags,
290
+ clockAnchor: this._clockAnchor,
291
+ }
292
+ }
293
+
294
+ end(endTime?: SpanTimeInput): void {
295
+ if (this._ended) {
296
+ this._logger?.debug('Ignoring end() on a span that has already ended')
297
+ return
298
+ }
299
+ this._ended = true
300
+
301
+ const derived = this._now()
302
+ const resolved = resolveSuppliedTime(endTime, derived, 'end time', this._logger)
303
+
304
+ this._onEnd(
305
+ {
306
+ traceId: this._traceId,
307
+ spanId: this._spanId,
308
+ ...(this._parentSpanId && { parentSpanId: this._parentSpanId }),
309
+ ...(this._traceState && { traceState: this._traceState }),
310
+ traceFlags: this._traceFlags,
311
+ parentIsRemote: this._parentIsRemote,
312
+ name: this._name,
313
+ kind: this._kind,
314
+ ...(this._status && { status: this._status }),
315
+ // Copied out with an ordinary prototype: the store is null-prototype, but a
316
+ // record handed to user code should behave like a normal object.
317
+ attributes: { ...this._attributes },
318
+ events: this._events,
319
+ startTime: this._startTime,
320
+ endTime: clampEndTime(resolved, this._startTime),
321
+ ...(this._droppedAttributes && { droppedAttributesCount: this._droppedAttributes }),
322
+ ...(this._droppedEvents && { droppedEventsCount: this._droppedEvents }),
323
+ },
324
+ this._autoKeys
325
+ )
326
+ }
327
+ }
328
+
329
+ const EXCEPTION_EVENT_NAME = 'exception'
330
+
331
+ /** The widest value the OTLP `dropped_*_count` fields, declared `uint32`, can carry. */
332
+ const MAX_UINT32 = 0xffff_ffff
333
+
334
+ /** A value as its string form, or the encoder's marker when it refuses to produce one. */
335
+ function safeString(value: unknown): string {
336
+ try {
337
+ return typeof value === 'string' ? value : String(value)
338
+ } catch {
339
+ return UNSERIALIZABLE_VALUE
340
+ }
341
+ }
342
+
343
+ /**
344
+ * A caller-visible counter read back as a number, or 0 for anything else.
345
+ * Clamped to the `uint32` the OTLP field is declared as: a `beforeSpanSend` hook
346
+ * can write a larger number onto an event, and one that overflows the field is
347
+ * refused for the whole request.
348
+ */
349
+ export function nonNegativeCount(value: unknown): number {
350
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
351
+ return 0
352
+ }
353
+ return Math.min(Math.floor(value), MAX_UINT32)
354
+ }
355
+
356
+ /**
357
+ * The record's keys with the ones the span itself set first, in that order.
358
+ *
359
+ * `Object.keys` hoists integer-like keys to the front whatever the write order,
360
+ * so a hook adding `attributes['0']` would otherwise outrank an attribute the
361
+ * caller set before the hook ran — and the cap is documented as earliest-set-wins.
362
+ */
363
+ function orderedKeys(attributes: SpanAttributes, keysBeforeHook: readonly string[]): string[] {
364
+ if (!keysBeforeHook.length) {
365
+ return Object.keys(attributes)
366
+ }
367
+ // The encoder's own predicate: `in` would walk the prototype chain, so a key
368
+ // the caller set that collides with Object.prototype survives the hook deleting
369
+ // it and reads back as the inherited member, and `hasOwnProperty` would keep a
370
+ // key the hook hid by making it non-enumerable, which the encoder never emits.
371
+ const beforeHook = keysBeforeHook.filter((key) => Object.prototype.propertyIsEnumerable.call(attributes, key))
372
+ const seen = new Set(beforeHook)
373
+ return [...beforeHook, ...Object.keys(attributes).filter((key) => !seen.has(key))]
374
+ }
375
+
376
+ /**
377
+ * Re-applies the per-span caps to a record a `beforeSpanSend` hook has already
378
+ * seen. The hook writes to the plain record, not through the span's own guarded
379
+ * writer, so an enriching hook would otherwise push a span past the cap it was
380
+ * trimmed to and back into the 413 path the cap exists to avoid.
381
+ *
382
+ * Earliest-set entries win, matching the span-side rule; SDK-attached keys are
383
+ * exempt. Counts add to whatever the span already dropped.
384
+ */
385
+ export function applySpanLimits(
386
+ record: SpanRecord,
387
+ autoKeys: ReadonlySet<string>,
388
+ maxAttributes: number,
389
+ maxEvents: number,
390
+ maxAttributesPerEvent: number,
391
+ maxAttributeValueLength: number,
392
+ keysBeforeHook: readonly string[] = []
393
+ ): void {
394
+ let kept = 0
395
+ let droppedAttributes = 0
396
+ // Built fresh rather than edited in place: a hook is free to return a record
397
+ // whose attributes it froze, and a `delete` on one throws.
398
+ const attributes: SpanAttributes = {}
399
+ for (const key of orderedKeys(record.attributes, keysBeforeHook)) {
400
+ const value = record.attributes[key]
401
+ // Matches `_writeAttribute`: the encoder drops these, so a hook that blanks a
402
+ // value rather than deleting the key must not evict a real attribute.
403
+ if (isNullish(value)) {
404
+ continue
405
+ }
406
+ if (!autoKeys.has(key)) {
407
+ if (kept >= maxAttributes) {
408
+ droppedAttributes++
409
+ continue
410
+ }
411
+ kept++
412
+ }
413
+ Object.defineProperty(attributes, key, {
414
+ value: truncateAttributeValue(value, maxAttributeValueLength),
415
+ enumerable: true,
416
+ writable: true,
417
+ configurable: true,
418
+ })
419
+ }
420
+ record.attributes = attributes
421
+ if (droppedAttributes) {
422
+ // Coerced, not trusted: a hook can put anything in the counter, and a
423
+ // non-number there would erase the count the span itself accumulated.
424
+ record.droppedAttributesCount = nonNegativeCount(record.droppedAttributesCount) + droppedAttributes
425
+ }
426
+
427
+ // Walked rather than sliced: a hook can append events or rewrite their
428
+ // attributes, neither of which goes through `addEvent`, so each one still
429
+ // needs its attributes bounded on the way past.
430
+ let keptEvents = 0
431
+ let droppedEvents = 0
432
+ const events: SpanEventRecord[] = []
433
+ for (const event of record.events) {
434
+ if (keptEvents >= maxEvents) {
435
+ droppedEvents++
436
+ continue
437
+ }
438
+ keptEvents++
439
+ if (event.attributes) {
440
+ // A hook can widen an event as freely as it can add one, and neither goes
441
+ // through `addEvent`.
442
+ const bounded = boundAttributes(event.attributes, maxAttributesPerEvent, maxAttributeValueLength)
443
+ event.attributes = bounded.attributes
444
+ if (bounded.dropped) {
445
+ event.droppedAttributesCount = nonNegativeCount(event.droppedAttributesCount) + bounded.dropped
446
+ }
447
+ }
448
+ events.push(event)
449
+ }
450
+ record.events = events
451
+ if (droppedEvents) {
452
+ record.droppedEventsCount = nonNegativeCount(record.droppedEventsCount) + droppedEvents
453
+ }
454
+ if (record.status?.message) {
455
+ // Coerced first: a non-string would reach the encoder to be stringified at
456
+ // full length. Guarded, because a throwing `toString` here would cost the
457
+ // span, where the encoder downstream only marks the field.
458
+ record.status = {
459
+ ...record.status,
460
+ message: truncateString(safeString(record.status.message), maxAttributeValueLength),
461
+ }
462
+ }
463
+ }
464
+
465
+ /**
466
+ * An inert handle returned whenever tracing cannot run — traces unconfigured,
467
+ * SDK disabled, user opted out. Supports the full surface so caller code never
468
+ * branches, and returns `null` from `traceparent()` so an id this SDK never
469
+ * recorded cannot propagate.
470
+ */
471
+ export class NoopSpan implements Span {
472
+ setAttribute(): this {
473
+ return this
474
+ }
475
+ setAttributes(): this {
476
+ return this
477
+ }
478
+ addEvent(): this {
479
+ return this
480
+ }
481
+ setStatus(): this {
482
+ return this
483
+ }
484
+ recordException(): this {
485
+ return this
486
+ }
487
+ updateName(): this {
488
+ return this
489
+ }
490
+ traceparent(): string | null {
491
+ return null
492
+ }
493
+ tracestate(): string | null {
494
+ return null
495
+ }
496
+ end(): void {}
497
+ }
498
+
499
+ // Typed as `Span`, not `NoopSpan`: the class's methods take no parameters, so the
500
+ // concrete type would reject calls the interface allows.
501
+ export const NOOP_SPAN: Span = /* @__PURE__ */ new NoopSpan()
502
+
503
+ /**
504
+ * An inert handle that carries an inbound trace context. Records nothing, and
505
+ * echoes the `traceparent` it was handed — including the caller's version and
506
+ * sampled flag — so a service with tracing off still forwards the trace it
507
+ * received rather than severing it. The ids it propagates are the upstream
508
+ * caller's own; this SDK invents none.
509
+ */
510
+ export class PassThroughSpan extends NoopSpan {
511
+ constructor(
512
+ private readonly _traceparent: string,
513
+ private readonly _tracestate?: string
514
+ ) {
515
+ super()
516
+ }
517
+
518
+ override traceparent(): string {
519
+ return this._traceparent
520
+ }
521
+
522
+ override tracestate(): string | null {
523
+ return this._tracestate ?? null
524
+ }
525
+ }
526
+
527
+ /**
528
+ * The `stack` of whatever was thrown, as OTel's `exception.stacktrace`. Reads
529
+ * the property behind its own guard: a getter on a hostile object throws, and a
530
+ * thrown string has no stack at all. The value is bounded like any other
531
+ * attribute, by `maxAttributeValueLength`.
532
+ */
533
+ function readStack(error: unknown): { stack?: string } {
534
+ try {
535
+ const stack = (error as { stack?: unknown }).stack
536
+ return typeof stack === 'string' && stack ? { stack } : {}
537
+ } catch {
538
+ return {}
539
+ }
540
+ }
541
+
542
+ /**
543
+ * The handle to return when a span cannot be recorded: a pass-through when a
544
+ * context is available, the shared no-op otherwise. With no explicit `parent`
545
+ * the active handle supplies it, so an inbound trace survives nesting.
546
+ *
547
+ * @internal Exposed for cross-package use within this SDK; not part of the stable public API.
548
+ */
549
+ export function inertSpan(options?: { parent?: unknown; tracestate?: unknown }, active?: Span): Span {
550
+ const parent = traceparentHeader(options?.parent) ?? active
551
+ // A handle parent reports its own context, read behind a guard because a
552
+ // foreign handle's accessor may throw. A no-op reports none and stays a no-op.
553
+ const inbound = typeof parent === 'string' || parent == null ? parent : readHandle(parent, 'traceparent')
554
+ const traceparent = normalizeTraceparent(inbound)
555
+ if (!traceparent) {
556
+ return NOOP_SPAN
557
+ }
558
+ const tracestate =
559
+ typeof parent === 'string' || parent == null ? options?.tracestate : readHandle(parent, 'tracestate')
560
+ return new PassThroughSpan(traceparent, sanitizeTracestate(tracestate))
561
+ }
562
+
563
+ function readHandle(parent: unknown, method: 'traceparent' | 'tracestate'): unknown {
564
+ try {
565
+ const fn = (parent as Span)[method]
566
+ return typeof fn === 'function' ? fn.call(parent) : undefined
567
+ } catch {
568
+ return undefined
569
+ }
570
+ }
571
+
572
+ /** One walk's budget, allocated per attribute value. */
573
+ interface TruncateState {
574
+ /** Containers on the current path, so a back-reference stops the walk. */
575
+ ancestors: WeakSet<object>
576
+ /** Nodes this walk may still visit. */
577
+ remainingNodes: number
578
+ }
579
+
580
+ function truncateString(value: string, maxLength: number): string {
581
+ return value.length > maxLength ? value.slice(0, maxLength) : value
582
+ }
583
+
584
+ /**
585
+ * Bounds every string reachable from an attribute value to `maxLength`
586
+ * characters, including the strings nested inside arrays and objects. Numbers
587
+ * and booleans are bounded already.
588
+ *
589
+ * An unbounded value is the one thing the per-span caps do not stop: a single
590
+ * multi-MB attribute makes the whole span too large for the ingestion endpoint,
591
+ * and the 413 path then drops that span whole. `setAttribute('payload', { body })`
592
+ * is the usual way one arrives, so the bound has to reach inside the value.
593
+ *
594
+ * Returns the value it was given when nothing needed shortening, so the common
595
+ * case allocates nothing.
596
+ */
597
+ export function truncateAttributeValue(value: SpanAttributeValue, maxLength: number): SpanAttributeValue {
598
+ return truncateValue(value, maxLength, { ancestors: new WeakSet(), remainingNodes: MAX_JSON_SAFE_VALUE_NODES }, 0)
599
+ }
600
+
601
+ /**
602
+ * Walks under the same depth cap, node budget and ancestor set as
603
+ * `encodeAnyValue`, charging the same values. The encoder spends one budget
604
+ * across the whole attribute bag where this spends one per value, so it runs out
605
+ * no later and marks whatever this walk returned whole.
606
+ *
607
+ * Depth alone does not bound this: a value whose children point back at their
608
+ * siblings costs `fanout ** depth` visits, which is minutes of synchronous work
609
+ * inside the caller's own `setAttribute` call.
610
+ */
611
+ function truncateValue(
612
+ value: SpanAttributeValue,
613
+ maxLength: number,
614
+ state: TruncateState,
615
+ depth: number
616
+ ): SpanAttributeValue {
617
+ if (value === null || typeof value !== 'object') {
618
+ // Free, as it is in the encoder, which drops a nullish leaf without charging.
619
+ if (isNullish(value)) {
620
+ return value
621
+ }
622
+ // A shared subtree is re-walked once per path reaching it, so a leaf that
623
+ // skips the charge lets one value cost `budget * items` string copies.
624
+ if (state.remainingNodes <= 0) {
625
+ return value
626
+ }
627
+ state.remainingNodes--
628
+ return typeof value === 'string' ? truncateString(value, maxLength) : value
629
+ }
630
+ if (state.ancestors.has(value)) {
631
+ // The marker the encoder would produce, not the value itself. Handing the
632
+ // raw ancestor back puts it inside a *copied* parent, where the encoder's
633
+ // own cycle detection no longer recognises it and walks one more level of
634
+ // its strings at full length.
635
+ return CIRCULAR_VALUE
636
+ }
637
+ if (state.remainingNodes <= 0 || depth >= MAX_JSON_SAFE_VALUE_DEPTH) {
638
+ return value
639
+ }
640
+ state.remainingNodes--
641
+ state.ancestors.add(value)
642
+ try {
643
+ // A Date is emitted by the encoder from its own branch, ahead of any
644
+ // `toJSON` probe, so bounding it here would ship a truncated timestamp
645
+ // rather than a shorter one.
646
+ if (value instanceof Date) {
647
+ return value
648
+ }
649
+ // The representation the value defines for itself is what the encoder puts
650
+ // on the wire, so it is what has to be bounded — a `toJSON` returning a
651
+ // megabyte of text is invisible to a walk over the object's own keys.
652
+ const resolved = resolveToJson(value)
653
+ if (resolved.selfDescribed) {
654
+ // Resolving to nothing is the value's answer. Walking its keys anyway
655
+ // would build a plain object the encoder no longer treats as
656
+ // self-describing, putting the internals of a redacted value on the wire.
657
+ // Stored as the string the encoder builds from that same nullish result
658
+ // rather than as the value itself: the encoder probes `toJSON` a second
659
+ // time, so one that answers `null` here is free to answer with a megabyte
660
+ // there, past the bound this walk exists to apply. Left unbounded like the
661
+ // other markers — nine characters at most, and trimming it to `unde` would
662
+ // only make it unreadable.
663
+ return isNullish(resolved.value)
664
+ ? String(resolved.value)
665
+ : truncateValue(resolved.value, maxLength, state, depth + 1)
666
+ }
667
+ if (isArray(value)) {
668
+ // Only the items the encoder will emit are walked; it stops at the same
669
+ // cap, so bounding the rest is work spent on values that never ship.
670
+ const walked = Math.min(value.length, MAX_JSON_SAFE_VALUE_ITEMS)
671
+ // Accumulated rather than copied from the value: `slice()` reads every
672
+ // element, accessors past the cap included, and one of those throwing
673
+ // would reach the outer catch and cost the whole array its bound.
674
+ const boundedItems: SpanAttributeValue[] = []
675
+ for (let index = 0; index < walked; index++) {
676
+ try {
677
+ boundedItems.push(truncateValue(value[index], maxLength, state, depth + 1))
678
+ } catch {
679
+ // A throwing accessor costs its own item, as it does in the encoder.
680
+ boundedItems.push(UNSERIALIZABLE_VALUE)
681
+ }
682
+ }
683
+ // Carried so the encoder still marks what it cut.
684
+ if (value.length > walked) {
685
+ boundedItems.length = value.length
686
+ }
687
+ return boundedItems
688
+ }
689
+ const bounded: SpanAttributes = {}
690
+ // Counted the way the encoder counts, so the walk stops where its output
691
+ // does: a key it skips costs no slot, and reading past the last one it can
692
+ // emit is getter work on values that never ship.
693
+ let emittable = 0
694
+ for (const key of Object.keys(value)) {
695
+ if (emittable >= MAX_JSON_SAFE_VALUE_ITEMS) {
696
+ break
697
+ }
698
+ let boundedItem: SpanAttributeValue
699
+ try {
700
+ // Read once: re-reading to compare would run a getter a second time.
701
+ boundedItem = truncateValue((value as SpanAttributes)[key], maxLength, state, depth + 1)
702
+ } catch {
703
+ // A throwing accessor costs its own key. Reaching the walk's own catch
704
+ // would abandon the whole value unbounded, which is how a lazy ORM
705
+ // relation next to a large field puts that field on the wire whole.
706
+ boundedItem = UNSERIALIZABLE_VALUE
707
+ }
708
+ if (key && !isNullish(boundedItem)) {
709
+ emittable++
710
+ }
711
+ // defineProperty, not assignment: a nested `__proto__` key would otherwise
712
+ // swap the copy's prototype and vanish.
713
+ Object.defineProperty(bounded, key, {
714
+ value: boundedItem,
715
+ enumerable: true,
716
+ writable: true,
717
+ configurable: true,
718
+ })
719
+ }
720
+ return bounded
721
+ } catch {
722
+ // Whatever is left — a hostile `Object.keys`, a `slice` that throws — costs
723
+ // this value its bound rather than the span. Per-key reads are guarded
724
+ // above, so a single bad property does not reach here.
725
+ return value
726
+ } finally {
727
+ // Siblings pointing at the same object are duplication, not a cycle.
728
+ state.ancestors.delete(value)
729
+ }
730
+ }
731
+
732
+ /**
733
+ * The value's own serialized form. `selfDescribed` is false when it defines no
734
+ * `toJSON`, or when reading one throws — both fall through to the plain walk,
735
+ * as they do in the encoder.
736
+ */
737
+ function resolveToJson(value: object): { selfDescribed: boolean; value?: SpanAttributeValue } {
738
+ try {
739
+ const toJSON = (value as { toJSON?: unknown }).toJSON
740
+ if (typeof toJSON === 'function') {
741
+ return { selfDescribed: true, value: toJSON.call(value) as SpanAttributeValue }
742
+ }
743
+ } catch {
744
+ // Falls through to the plain walk.
745
+ }
746
+ return { selfDescribed: false }
747
+ }
748
+
749
+ /**
750
+ * A copy of a caller-supplied attribute bag holding at most `max` entries, each
751
+ * value bounded to `maxLength`, plus how many entries the cap refused.
752
+ *
753
+ * Once the cap is spent the remaining keys are counted without being read, so a
754
+ * wide object does not pay for the getters on values it is about to drop.
755
+ */
756
+ function boundAttributes(
757
+ source: SpanAttributes,
758
+ max: number,
759
+ maxLength: number
760
+ ): { attributes: SpanAttributes; dropped: number } {
761
+ let keys: string[]
762
+ try {
763
+ keys = Object.keys(source)
764
+ } catch {
765
+ // A hostile own-keys trap costs the bag, not the event carrying it.
766
+ return { attributes: {}, dropped: 0 }
767
+ }
768
+ const attributes: SpanAttributes = {}
769
+ let kept = 0
770
+ let dropped = 0
771
+ for (const key of keys) {
772
+ if (kept >= max) {
773
+ dropped++
774
+ continue
775
+ }
776
+ let value: SpanAttributeValue
777
+ try {
778
+ value = truncateAttributeValue(source[key], maxLength)
779
+ } catch {
780
+ // A throwing getter costs its own key, as it does in `assignUserAttributes`.
781
+ value = UNSERIALIZABLE_VALUE
782
+ }
783
+ // Nullish spends no slot, matching `_writeAttribute` and the span half of
784
+ // `applySpanLimits`: the encoder drops these, so a caller who blanked a value
785
+ // rather than omitting the key must not lose a real attribute to it.
786
+ if (isNullish(value)) {
787
+ continue
788
+ }
789
+ kept++
790
+ // defineProperty, not assignment: `attributes['__proto__'] = v` hits the
791
+ // prototype setter and the attribute vanishes.
792
+ Object.defineProperty(attributes, key, { value, enumerable: true, writable: true, configurable: true })
793
+ }
794
+ return { attributes, dropped }
795
+ }
796
+
797
+ /** `truncateAttributeValue` across an attribute bag, in place. */
798
+ export function truncateAttributes(attributes: SpanAttributes, maxLength: number): SpanAttributes {
799
+ for (const key of Object.keys(attributes)) {
800
+ attributes[key] = truncateAttributeValue(attributes[key], maxLength)
801
+ }
802
+ return attributes
803
+ }
804
+
805
+ /**
806
+ * Runs `fn` with `span` active, which every scoped helper does the same way.
807
+ *
808
+ * The shared no-op is never activated, so `getActiveSpan()` inside the callback
809
+ * reads null — callbacks should use the handle they're given. A pass-through
810
+ * handle is activated, so `getActiveSpan()?.traceparent()` still propagates an
811
+ * inbound trace through a service with tracing off.
812
+ *
813
+ * @internal Exposed for cross-package use within this SDK; not part of the stable public API.
814
+ */
815
+ export function runWithActiveSpan<T>(contextManager: SpanContextManager, span: Span, fn: (span: Span) => T): T {
816
+ return span === NOOP_SPAN ? fn(span) : contextManager.with(span, () => fn(span))
817
+ }
818
+
819
+ /**
820
+ * Extracts the OTel `exception.type` / `exception.message` pair from whatever was
821
+ * thrown. Anything can be thrown in JS, so non-Errors are described by type.
822
+ */
823
+ export function describeError(error: unknown): { type: string; message: string; stack?: string } {
824
+ try {
825
+ const stack = readStack(error)
826
+ if (isError(error)) {
827
+ return { type: error.name || 'Error', message: error.message || '', ...stack }
828
+ }
829
+ if (typeof error === 'string') {
830
+ return { type: 'string', message: error }
831
+ }
832
+ if (error && typeof error === 'object') {
833
+ const maybe = error as { name?: unknown; message?: unknown }
834
+ if (typeof maybe.message === 'string') {
835
+ return { type: typeof maybe.name === 'string' ? maybe.name : 'Object', message: maybe.message, ...stack }
836
+ }
837
+ }
838
+ return { type: typeof error, message: String(error) }
839
+ } catch {
840
+ // A hostile `toString` or accessor must not throw a second error: in `withSpan`
841
+ // that would replace the application's error and skip the span's `end()`.
842
+ return { type: typeof error, message: '' }
843
+ }
844
+ }