dd-trace 6.0.0 → 6.2.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 (98) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/index.d.ts +28 -1
  3. package/initialize.mjs +4 -2
  4. package/package.json +34 -30
  5. package/packages/datadog-instrumentations/src/ai.js +45 -0
  6. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  7. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  8. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  9. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  10. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  11. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  13. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +5 -1
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +31 -0
  19. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +19 -5
  20. package/packages/datadog-instrumentations/src/jest.js +713 -63
  21. package/packages/datadog-instrumentations/src/mercurius.js +11 -0
  22. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  23. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  24. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  25. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  26. package/packages/datadog-instrumentations/src/mongoose.js +4 -5
  27. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +15 -0
  28. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  29. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
  30. package/packages/datadog-instrumentations/src/vitest-main.js +26 -1
  31. package/packages/datadog-instrumentations/src/vitest-util.js +2 -0
  32. package/packages/datadog-instrumentations/src/vitest-worker.js +103 -34
  33. package/packages/datadog-plugin-ai/src/index.js +1 -1
  34. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  35. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  36. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
  37. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  38. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  39. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  40. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  41. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  42. package/packages/datadog-plugin-cucumber/src/index.js +14 -23
  43. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
  44. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  45. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  46. package/packages/datadog-plugin-graphql/src/execute.js +25 -25
  47. package/packages/datadog-plugin-graphql/src/index.js +23 -1
  48. package/packages/datadog-plugin-graphql/src/request.js +104 -0
  49. package/packages/datadog-plugin-graphql/src/utils.js +186 -2
  50. package/packages/datadog-plugin-graphql/src/validate.js +25 -2
  51. package/packages/datadog-plugin-jest/src/index.js +30 -14
  52. package/packages/datadog-plugin-mocha/src/index.js +39 -14
  53. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  54. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  55. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  56. package/packages/datadog-plugin-playwright/src/index.js +3 -2
  57. package/packages/datadog-plugin-vitest/src/index.js +43 -9
  58. package/packages/dd-trace/src/aiguard/index.js +9 -14
  59. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  60. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  61. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  62. package/packages/dd-trace/src/appsec/graphql.js +9 -6
  63. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
  64. package/packages/dd-trace/src/appsec/lambda.js +8 -8
  65. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  66. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  67. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
  68. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
  69. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
  70. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  71. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  72. package/packages/dd-trace/src/config/supported-configurations.json +37 -0
  73. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  74. package/packages/dd-trace/src/datastreams/writer.js +14 -2
  75. package/packages/dd-trace/src/encode/0.4.js +23 -2
  76. package/packages/dd-trace/src/encode/0.5.js +12 -1
  77. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +11 -1
  78. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +20 -2
  79. package/packages/dd-trace/src/exporters/common/writer.js +17 -1
  80. package/packages/dd-trace/src/guardrails/index.js +3 -1
  81. package/packages/dd-trace/src/guardrails/telemetry.js +40 -3
  82. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  83. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  84. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  85. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  86. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  87. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  88. package/packages/dd-trace/src/msgpack/chunk.js +33 -1
  89. package/packages/dd-trace/src/msgpack/index.js +6 -1
  90. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  91. package/packages/dd-trace/src/plugin_manager.js +16 -0
  92. package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
  93. package/packages/dd-trace/src/plugins/index.js +5 -0
  94. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  95. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  96. package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +6 -0
  97. package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +8 -0
  98. package/vendor/dist/@apm-js-collab/code-transformer/index.js +5 -6
@@ -1,8 +1,5 @@
1
1
  'use strict'
2
2
 
3
- // Capture real Date.now at module load time, before any test can install fake timers.
4
- const realDateNow = Date.now.bind(Date)
5
-
6
3
  const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
7
4
  const { storage } = require('../../datadog-core')
8
5
 
@@ -52,8 +49,6 @@ const {
52
49
  TELEMETRY_TEST_SESSION,
53
50
  } = require('../../dd-trace/src/ci-visibility/telemetry')
54
51
 
55
- const BREAKPOINT_SET_GRACE_PERIOD_MS = 200
56
-
57
52
  class MochaPlugin extends CiPlugin {
58
53
  static id = 'mocha'
59
54
 
@@ -263,6 +258,7 @@ class MochaPlugin extends CiPlugin {
263
258
  span.finish()
264
259
  finishAllTraceSpans(span)
265
260
  this.activeTestSpan = null
261
+ this.cancelDiBreakpointHitWait()
266
262
  if (this.di && this.libraryConfig?.isDiEnabled && this.runningTestProbe && isLastRetry) {
267
263
  this.removeDiProbe(this.runningTestProbe)
268
264
  this.runningTestProbe = null
@@ -307,8 +303,24 @@ class MochaPlugin extends CiPlugin {
307
303
  return ctx.currentStore
308
304
  })
309
305
 
310
- this.addSub('ci:mocha:test:retry', ({ span, isFirstAttempt, willBeRetried, err, test, isAtrRetry }) => {
306
+ this.addSub('ci:mocha:test:retry', ({
307
+ span,
308
+ isFirstAttempt,
309
+ willBeRetried,
310
+ err,
311
+ test,
312
+ isAtrRetry,
313
+ promises,
314
+ }) => {
311
315
  if (span) {
316
+ const finishSpan = () => {
317
+ span.finish()
318
+ finishAllTraceSpans(span)
319
+ if (this.activeTestSpan === span) {
320
+ this.activeTestSpan = null
321
+ }
322
+ }
323
+
312
324
  span.setTag(TEST_STATUS, 'fail')
313
325
  if (!isFirstAttempt) {
314
326
  span.setTag(TEST_IS_RETRY, 'true')
@@ -330,21 +342,34 @@ class MochaPlugin extends CiPlugin {
330
342
  if (isFirstAttempt && willBeRetried && this.di && this.libraryConfig?.isDiEnabled) {
331
343
  const probeInformation = this.addDiProbe(err)
332
344
  if (probeInformation) {
333
- const { file, line, stackIndex } = probeInformation
345
+ const { file, line, stackIndex, setProbePromise } = probeInformation
334
346
  this.runningTestProbe = { file, line }
335
347
  this.testErrorStackIndex = stackIndex
336
348
  test._ddShouldWaitForHitProbe = true
337
- const waitUntil = realDateNow() + BREAKPOINT_SET_GRACE_PERIOD_MS
338
- while (realDateNow() < waitUntil) {
339
- // TODO: To avoid a race condition, we should wait until `probeInformation.setProbePromise` has resolved.
340
- // However, Mocha doesn't have a mechanism for waiting asyncrounously here, so for now, we'll have to
341
- // fall back to a fixed syncronous delay.
349
+ this.prepareDiBreakpointHitWait()
350
+ if (promises) {
351
+ promises.setProbePromise = this.waitForDiOperation(setProbePromise)
342
352
  }
343
353
  }
344
354
  }
345
355
 
346
- span.finish()
347
- finishAllTraceSpans(span)
356
+ if (!isFirstAttempt &&
357
+ willBeRetried &&
358
+ this.di &&
359
+ this.libraryConfig?.isDiEnabled &&
360
+ this.runningTestProbe &&
361
+ promises) {
362
+ promises.finishTestPromise = this.waitForInFlightDiBreakpointHits().then(finishSpan, finishSpan)
363
+ return
364
+ }
365
+
366
+ finishSpan()
367
+ }
368
+ })
369
+
370
+ this.addSub('ci:mocha:test:di:wait', ({ promises }) => {
371
+ if (this.di) {
372
+ promises.hitBreakpointPromise = this.waitForDiBreakpointHits()
348
373
  }
349
374
  })
350
375
 
@@ -0,0 +1,43 @@
1
+ 'use strict'
2
+
3
+ const MongodbCoreQueryPlugin = require('./query')
4
+
5
+ const MAX_RESOURCE_LENGTH = 10_000
6
+
7
+ class MongodbCoreBulkWritePlugin extends MongodbCoreQueryPlugin {
8
+ // bulkWrite is higher-level than the wire commands `query` traces, so it has its own operation
9
+ // and thus its own `apm:mongodb:bulkwrite:*` channels. It reuses the query plugin's `id`,
10
+ // `component`, and collection-stripping `getPeerService`, and inherits `finish`, `error`, and the
11
+ // finish-time parent-store restore from the tracing/outbound base classes.
12
+ static operation = 'bulkwrite'
13
+
14
+ /**
15
+ * Open the parent span for a `Collection#bulkWrite`. The per-type wire commands nest as
16
+ * children and carry the statements, host, and DBM comment, so this span only records
17
+ * the namespace and resource.
18
+ *
19
+ * @param {{ ns: string }} ctx
20
+ */
21
+ bindStart (ctx) {
22
+ const { ns } = ctx
23
+ const serviceResult = this.serviceName({ pluginConfig: this.config })
24
+
25
+ this.startSpan(this.operationName(), {
26
+ service: serviceResult,
27
+ resource: truncate(`bulkWrite ${ns}`),
28
+ type: 'mongodb',
29
+ kind: 'client',
30
+ meta: {
31
+ 'db.name': ns,
32
+ },
33
+ }, ctx)
34
+
35
+ return ctx.currentStore
36
+ }
37
+ }
38
+
39
+ function truncate (input) {
40
+ return input.length > MAX_RESOURCE_LENGTH ? input.slice(0, MAX_RESOURCE_LENGTH) : input
41
+ }
42
+
43
+ module.exports = MongodbCoreBulkWritePlugin
@@ -1,452 +1,17 @@
1
1
  'use strict'
2
2
 
3
- const { isMap, isRegExp } = require('node:util').types
3
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
4
+ const MongodbCoreBulkWritePlugin = require('./bulk-write')
5
+ const MongodbCoreQueryPlugin = require('./query')
4
6
 
5
- const DatabasePlugin = require('../../dd-trace/src/plugins/database')
6
-
7
- class MongodbCorePlugin extends DatabasePlugin {
7
+ class MongodbCorePlugin extends CompositePlugin {
8
8
  static id = 'mongodb-core'
9
- static component = 'mongodb'
10
- // avoid using db.name for peer.service since it includes the collection name
11
- // should be removed if one day this will be fixed
12
- /**
13
- * @override
14
- */
15
- static peerServicePrecursors = []
16
-
17
- /**
18
- * @override
19
- */
20
- configure (config) {
21
- super.configure(config)
22
-
23
- this.config.heartbeatEnabled = config.heartbeatEnabled ??
24
- this._tracerConfig.DD_TRACE_MONGODB_HEARTBEAT_ENABLED
25
- this.config.obfuscateQuery = normaliseObfuscateQuery(
26
- config.obfuscateQuery ?? this._tracerConfig.DD_TRACE_MONGODB_OBFUSCATE_QUERY
27
- )
28
- }
29
-
30
- bindStart (ctx) {
31
- const { ns, ops, options = {}, name } = ctx
32
- // heartbeat commands can be disabled if this.config.heartbeatEnabled is false
33
- if (!this.config.heartbeatEnabled && isHeartbeat(ops, this.config)) {
34
- return
35
- }
36
- const query = getQuery(ops, this.config.obfuscateQuery)
37
- const resource = truncate(getResource(this, ns, query, name))
38
- const serviceResult = this.serviceName({ pluginConfig: this.config })
39
- const span = this.startSpan(this.operationName(), {
40
- service: serviceResult,
41
- resource,
42
- type: 'mongodb',
43
- kind: 'client',
44
- meta: {
45
- // this is not technically correct since it includes the collection but we changing will break customer stuff
46
- 'db.name': ns,
47
- 'mongodb.query': query,
48
- 'out.host': options.host,
49
- 'out.port': options.port,
50
- },
51
- }, ctx)
52
- const comment = this.injectDbmComment(span, ops.comment, serviceResult.name)
53
- if (comment) {
54
- ops.comment = comment
55
- }
56
-
57
- return ctx.currentStore
58
- }
59
-
60
- /**
61
- * @override
62
- */
63
- getPeerService (tags) {
64
- let ns = tags['db.name']
65
- if (ns && tags['peer.service'] === undefined) {
66
- const dotIndex = ns.indexOf('.')
67
- if (dotIndex !== -1) {
68
- ns = ns.slice(0, dotIndex)
69
- }
70
- // the mongo ns is either dbName either dbName.collection. So we keep the first part
71
- tags['peer.service'] = ns
72
- }
73
- return super.getPeerService(tags)
74
- }
75
-
76
- injectDbmComment (span, comment, serviceName) {
77
- const dbmTraceComment = this.createDbmComment(span, serviceName)
78
-
79
- if (!dbmTraceComment) {
80
- return comment
81
- }
82
-
83
- if (comment) {
84
- // if the command already has a comment, append the dbm trace comment
85
- if (typeof comment === 'string') {
86
- comment += `,${dbmTraceComment}`
87
- } else if (Array.isArray(comment)) {
88
- comment.push(dbmTraceComment)
89
- } // do nothing if the comment is not a string or an array
90
- } else {
91
- comment = dbmTraceComment
92
- }
93
-
94
- return comment
95
- }
96
- }
97
-
98
- const MAX_DEPTH = 10
99
- const MAX_QUERY_LENGTH = 10_000
100
-
101
- function extractQuery (statements) {
102
- if (statements.length === 1 && statements[0].q) return statements[0].q
103
-
104
- const extractedQueries = []
105
- for (let i = 0; i < statements.length; i++) {
106
- if (statements[i].q) {
107
- extractedQueries.push(statements[i].q)
108
- }
109
- }
110
-
111
- return extractedQueries
112
- }
113
-
114
- /**
115
- * @param {Record<string, unknown> | unknown[] | undefined} cmd
116
- * @param {'none' | 'types' | 'redact'} mode
117
- */
118
- function getQuery (cmd, mode) {
119
- if (!cmd || (typeof cmd !== 'object' && !Array.isArray(cmd))) return
120
-
121
- if (Array.isArray(cmd)) return sanitiseAndStringify(extractQuery(cmd), mode)
122
- if (cmd.query) return sanitiseAndStringify(cmd.query, mode)
123
- if (cmd.filter) return sanitiseAndStringify(cmd.filter, mode)
124
- if (cmd.pipeline) return sanitiseAndStringify(cmd.pipeline, mode)
125
- if (cmd.deletes) return sanitiseAndStringify(extractQuery(cmd.deletes), mode)
126
- if (cmd.updates) return sanitiseAndStringify(extractQuery(cmd.updates), mode)
127
- }
128
-
129
- function getResource (plugin, ns, query, operationName) {
130
- let resource = `${operationName} ${ns}`
131
-
132
- if (plugin.config.queryInResourceName && query) {
133
- resource += ` ${query}`
134
- }
135
-
136
- return resource
137
- }
138
-
139
- function truncate (input) {
140
- return input.length > MAX_QUERY_LENGTH ? input.slice(0, MAX_QUERY_LENGTH) : input
141
- }
142
-
143
- // Depth doubles as the cycle bound: a cycle pushes past MAX_DEPTH and bails,
144
- // after which the slow path catches it via its ancestor stack.
145
- /** @param {unknown} input */
146
- function canStringifyDirect (input) {
147
- if (input === null ||
148
- typeof input !== 'object' ||
149
- ArrayBuffer.isView(input) ||
150
- input._bsontype !== undefined ||
151
- isRegExp(input) ||
152
- isMap(input) ||
153
- typeof input.toJSON === 'function') {
154
- return false
155
- }
156
- return canStringifyDirectWalk(input, 1)
157
- }
158
-
159
- /**
160
- * @param {Record<string, unknown> | unknown[]} value
161
- * @param {number} depth
162
- */
163
- function canStringifyDirectWalk (value, depth) {
164
- if (depth > MAX_DEPTH) return false
165
- const children = Array.isArray(value) ? value : Object.values(value)
166
- for (const child of children) {
167
- if (child === null ||
168
- typeof child === 'string' ||
169
- typeof child === 'number' ||
170
- typeof child === 'boolean') {
171
- continue
172
- }
173
- if (typeof child !== 'object' ||
174
- ArrayBuffer.isView(child) ||
175
- child._bsontype !== undefined ||
176
- isRegExp(child) ||
177
- isMap(child) ||
178
- typeof child.toJSON === 'function') {
179
- return false
180
- }
181
- if (!canStringifyDirectWalk(child, depth + 1)) return false
182
- }
183
- return true
184
- }
185
-
186
- /**
187
- * @param {Record<string, unknown> | unknown[]} input
188
- * @param {'none' | 'types' | 'redact'} mode
189
- */
190
- function sanitiseAndStringify (input, mode) {
191
- if (mode === 'none') {
192
- if (canStringifyDirect(input)) return JSON.stringify(input)
193
- return buildNone(input, [])
194
- }
195
- if (mode === 'redact') return buildRedact(input, [])
196
- return buildTypes(input, [])
197
- }
198
-
199
- const REDACT_LEAF = '"?"'
200
-
201
- /**
202
- * @param {RegExp} value
203
- * @returns {string}
204
- */
205
- function stringifyRegExp (value) {
206
- return `{"$regex":${JSON.stringify(value.source)},"$options":${JSON.stringify(value.flags)}}`
207
- }
208
-
209
- /**
210
- * @param {Record<string, unknown> | unknown[]} value
211
- * @param {object[]} ancestors
212
- * @returns {string | undefined}
213
- */
214
- function buildNone (value, ancestors) {
215
- // ArrayBuffer views (Buffer, every TypedArray, DataView) and Binary BSON
216
- // wrappers redact at the leaf; the walker neither recurses into the bytes
217
- // nor invokes any custom conversion.
218
- const bsontype = value._bsontype
219
- if (ArrayBuffer.isView(value) || bsontype === 'Binary' ||
220
- ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
221
- return REDACT_LEAF
222
- }
223
-
224
- if (isRegExp(value)) return stringifyRegExp(value)
225
-
226
- // Mirror JSON.stringify's contract: when `toJSON` is present, walk its
227
- // result (wrappers like Timestamp / Decimal128 expand to a small object,
228
- // ObjectId / Date flatten to a primitive).
229
- if (typeof value.toJSON === 'function') {
230
- const json = value.toJSON()
231
- if (json === value) return REDACT_LEAF
232
- // JSON.stringify keeps a null result as null (an invalid Date's toJSON
233
- // returns null); only function / symbol / undefined results drop the key.
234
- if (json === null) return 'null'
235
- if (typeof json !== 'object') return classifyLeafForNone(json)
236
- // A wrapper that exposes binary state through toJSON (Buffer-backed
237
- // class with WeakMap state, etc.) returns a TypedArray here. Re-screen
238
- // before the per-key walk would expand it element by element.
239
- if (ArrayBuffer.isView(json) || json._bsontype === 'Binary') return REDACT_LEAF
240
- value = json
241
- } else if (bsontype !== undefined) {
242
- return REDACT_LEAF
243
- }
244
-
245
- // The driver serializes a Map via its entries; mirror that as a document so
246
- // the tag matches the wire shape.
247
- if (isMap(value)) value = Object.fromEntries(value)
248
-
249
- ancestors.push(value)
250
-
251
- let result
252
- if (Array.isArray(value)) {
253
- result = '['
254
- let sep = ''
255
- for (let i = 0; i < value.length; i++) {
256
- // JSON.stringify renders unsupported leaves (function, symbol, undefined) as null in arrays.
257
- result += sep + (classifyForNone(value[i], ancestors) ?? 'null')
258
- sep = ','
9
+ static get plugins () {
10
+ return {
11
+ query: MongodbCoreQueryPlugin,
12
+ bulkWrite: MongodbCoreBulkWritePlugin,
259
13
  }
260
- result += ']'
261
- } else {
262
- result = '{'
263
- let sep = ''
264
- for (const key of Object.keys(value)) {
265
- const childResult = classifyForNone(value[key], ancestors)
266
- if (childResult === undefined) continue
267
- result += sep + JSON.stringify(key) + ':' + childResult
268
- sep = ','
269
- }
270
- result += '}'
271
14
  }
272
- ancestors.pop()
273
- return result
274
- }
275
-
276
- /**
277
- * @param {unknown} child
278
- * @param {object[]} ancestors
279
- * @returns {string | undefined}
280
- */
281
- function classifyForNone (child, ancestors) {
282
- if (typeof child !== 'object') return classifyLeafForNone(child)
283
- if (child === null) return 'null'
284
- return buildNone(child, ancestors)
285
- }
286
-
287
- /**
288
- * @param {unknown} leaf
289
- * @returns {string | undefined}
290
- */
291
- function classifyLeafForNone (leaf) {
292
- // Implicit `undefined` for function / symbol / undefined matches the
293
- // contract callers rely on: JSON.stringify drops those property values
294
- // inside objects and writes `null` in arrays.
295
- switch (typeof leaf) {
296
- case 'string': return JSON.stringify(leaf)
297
- case 'number': return Number.isFinite(leaf) ? String(leaf) : 'null'
298
- case 'boolean': return leaf ? 'true' : 'false'
299
- case 'bigint': return `"${String(leaf)}"`
300
- }
301
- }
302
-
303
- /**
304
- * @param {Record<string, unknown> | unknown[]} value
305
- * @param {object[]} ancestors
306
- */
307
- function buildRedact (value, ancestors) {
308
- const bsontype = value._bsontype
309
- if (ArrayBuffer.isView(value) || bsontype === 'Binary' || isRegExp(value) ||
310
- ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
311
- return REDACT_LEAF
312
- }
313
-
314
- // Mirror JSON.stringify: when `toJSON` is present, walk its result (which
315
- // wrappers like Timestamp / Decimal128 expand to `{$timestamp: "..."}` etc).
316
- // A primitive, null, or self-reference collapses to the sentinel — master's
317
- // `value === original` short-circuit.
318
- if (typeof value.toJSON === 'function') {
319
- const json = value.toJSON()
320
- if (typeof json !== 'object' || json === null || json === value) return REDACT_LEAF
321
- // Re-screen: toJSON can return a TypedArray or Binary BSON wrapper.
322
- if (ArrayBuffer.isView(json) || json._bsontype === 'Binary') return REDACT_LEAF
323
- value = json
324
- } else if (bsontype !== undefined) {
325
- return REDACT_LEAF
326
- }
327
-
328
- if (isMap(value)) value = Object.fromEntries(value)
329
-
330
- ancestors.push(value)
331
-
332
- let result
333
- if (Array.isArray(value)) {
334
- result = '['
335
- let sep = ''
336
- for (let i = 0; i < value.length; i++) {
337
- result += sep + classifyForRedact(value[i], ancestors)
338
- sep = ','
339
- }
340
- result += ']'
341
- } else {
342
- result = '{'
343
- let sep = ''
344
- for (const key of Object.keys(value)) {
345
- result += sep + JSON.stringify(key) + ':' + classifyForRedact(value[key], ancestors)
346
- sep = ','
347
- }
348
- result += '}'
349
- }
350
- ancestors.pop()
351
- return result
352
- }
353
-
354
- /**
355
- * @param {unknown} child
356
- * @param {object[]} ancestors
357
- */
358
- function classifyForRedact (child, ancestors) {
359
- if (typeof child !== 'object' || child === null) return REDACT_LEAF
360
- return buildRedact(child, ancestors)
361
- }
362
-
363
- const TYPE_OBJECT = '"object"'
364
- const TYPE_NULL = '"null"'
365
- const TYPE_BY_TYPEOF = {
366
- string: '"string"',
367
- number: '"number"',
368
- boolean: '"boolean"',
369
- bigint: '"bigint"',
370
- undefined: '"undefined"',
371
- }
372
-
373
- /**
374
- * @param {Record<string, unknown> | unknown[]} value
375
- * @param {object[]} ancestors
376
- */
377
- function buildTypes (value, ancestors) {
378
- const bsontype = value._bsontype
379
- if (ArrayBuffer.isView(value) || bsontype === 'Binary' || isRegExp(value) ||
380
- ancestors.length >= MAX_DEPTH || ancestors.includes(value)) {
381
- return TYPE_OBJECT
382
- }
383
-
384
- if (typeof value.toJSON === 'function') {
385
- const json = value.toJSON()
386
- if (typeof json !== 'object' ||
387
- json === null ||
388
- json === value ||
389
- ArrayBuffer.isView(json) ||
390
- json._bsontype === 'Binary') {
391
- return TYPE_OBJECT
392
- }
393
- value = json
394
- } else if (bsontype !== undefined) {
395
- return TYPE_OBJECT
396
- }
397
-
398
- if (isMap(value)) value = Object.fromEntries(value)
399
-
400
- ancestors.push(value)
401
-
402
- let result
403
- if (Array.isArray(value)) {
404
- result = '['
405
- let sep = ''
406
- for (let i = 0; i < value.length; i++) {
407
- // JSON.stringify renders unsupported leaves (function, symbol) as null in arrays.
408
- result += sep + (classifyForTypes(value[i], ancestors) ?? 'null')
409
- sep = ','
410
- }
411
- result += ']'
412
- } else {
413
- result = '{'
414
- let sep = ''
415
- for (const key of Object.keys(value)) {
416
- const childResult = classifyForTypes(value[key], ancestors)
417
- if (childResult === undefined) continue
418
- result += sep + JSON.stringify(key) + ':' + childResult
419
- sep = ','
420
- }
421
- result += '}'
422
- }
423
- ancestors.pop()
424
- return result
425
- }
426
-
427
- /**
428
- * @param {unknown} child
429
- * @param {object[]} ancestors
430
- */
431
- function classifyForTypes (child, ancestors) {
432
- if (typeof child !== 'object') return TYPE_BY_TYPEOF[typeof child]
433
- if (child === null) return TYPE_NULL
434
- return buildTypes(child, ancestors)
435
- }
436
-
437
- /** @param {unknown} value */
438
- function normaliseObfuscateQuery (value) {
439
- if (value === 'types' || value === 'redact') return value
440
- return 'none'
441
- }
442
-
443
- function isHeartbeat (ops, config) {
444
- // Check if it's a heartbeat command https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.md
445
- return (
446
- ops &&
447
- typeof ops === 'object' &&
448
- (ops.hello === 1 || ops.helloOk === true || ops.ismaster === 1 || ops.isMaster === 1)
449
- )
450
15
  }
451
16
 
452
17
  module.exports = MongodbCorePlugin