dd-trace 5.120.0 → 5.121.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 (59) hide show
  1. package/ci/vitest-no-worker-init-setup.mjs +112 -10
  2. package/index.d.ts +42 -0
  3. package/package.json +4 -3
  4. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  5. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +629 -0
  6. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
  7. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  10. package/packages/datadog-instrumentations/src/jest.js +1 -1
  11. package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -8
  13. package/packages/datadog-instrumentations/src/mysql.js +45 -15
  14. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  15. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  16. package/packages/datadog-instrumentations/src/pg.js +116 -92
  17. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
  18. package/packages/datadog-instrumentations/src/vitest-main.js +169 -17
  19. package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
  20. package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
  21. package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
  22. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  23. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -5
  24. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  25. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -1
  26. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  27. package/packages/datadog-plugin-jest/src/index.js +5 -4
  28. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  29. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  30. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
  31. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  32. package/packages/datadog-plugin-mocha/src/index.js +550 -39
  33. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  34. package/packages/datadog-plugin-next/src/index.js +1 -19
  35. package/packages/datadog-plugin-openai-agents/src/integration.js +278 -69
  36. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  37. package/packages/datadog-plugin-pg/src/index.js +1 -0
  38. package/packages/datadog-plugin-vitest/src/index.js +8 -20
  39. package/packages/datadog-plugin-ws/src/util.js +2 -1
  40. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  41. package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
  42. package/packages/dd-trace/src/carrier.js +356 -0
  43. package/packages/dd-trace/src/config/generated-config-types.d.ts +3 -0
  44. package/packages/dd-trace/src/config/supported-configurations.json +10 -0
  45. package/packages/dd-trace/src/datastreams/context.js +6 -1
  46. package/packages/dd-trace/src/datastreams/manager.js +5 -1
  47. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  48. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  49. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  50. package/packages/dd-trace/src/llmobs/index.js +34 -2
  51. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  52. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  53. package/packages/dd-trace/src/llmobs/util.js +78 -0
  54. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  55. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  56. package/packages/dd-trace/src/plugin_manager.js +2 -0
  57. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +1 -62
  58. package/packages/dd-trace/src/plugins/util/test.js +84 -5
  59. package/packages/dd-trace/src/plugins/util/web.js +53 -2
@@ -3,16 +3,19 @@ import { afterEach, beforeAll, beforeEach, inject } from 'vitest'
3
3
  // Instrumentation-less setup for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
4
4
  // It applies Test Optimization execution changes without initializing dd-trace and also supports Browser Mode.
5
5
  const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
6
+ const SERIALIZED_CONTEXT_PREFIX = '\u0000dd-vitest-context:'
6
7
  const providedContext = getProvidedContext()
7
8
  const isNoWorkerInitActive = providedContext.isActive ?? getIsNoWorkerInitActive()
8
9
  const attemptToFixTests = providedContext.attemptToFixTests || {}
9
10
  const attemptToFixRetries = providedContext.attemptToFixRetries || 0
10
11
  const disabledTests = providedContext.disabledTests || {}
12
+ const efdSuiteAdmissionBrowserCommand = providedContext.efdSuiteAdmissionBrowserCommand
11
13
  const earlyFlakeDetectionRetryPolicy = providedContext.earlyFlakeDetectionRetryPolicy || {
12
14
  durationRetryCounts: [],
13
15
  schedulingRetryCount: 0,
14
16
  }
15
17
  const earlyFlakeDetectionRetries = earlyFlakeDetectionRetryPolicy.schedulingRetryCount
18
+ const isEfdSuiteAdmissionEnabled = providedContext.isEfdSuiteAdmissionEnabled === true
16
19
  const isEarlyFlakeDetectionEnabled = providedContext.isEarlyFlakeDetectionEnabled === true
17
20
  const knownTests = providedContext.knownTests || {}
18
21
  const modifiedFiles = providedContext.modifiedFiles || {}
@@ -34,6 +37,7 @@ const earlyFlakeDetectionStartByTask = new WeakMap()
34
37
  const nextAttemptIndexByTask = new WeakMap()
35
38
  const retryAttemptIndexByTask = new WeakMap()
36
39
  const usedRumTestExecutionIds = new Set()
40
+ let browserCommands
37
41
  let now
38
42
  let timeOrigin
39
43
  // Use an unfaked monotonic clock in Node and Vitest's parent frame in Browser Mode.
@@ -48,18 +52,42 @@ if (typeof globalThis.process?.uptime === 'function') {
48
52
  timeOrigin = Number.isFinite(clock?.timeOrigin) ? clock.timeOrigin : Date.now() - now()
49
53
  }
50
54
 
55
+ /**
56
+ * Requests permission from the Vitest main process to schedule Browser Mode EFD retries for one suite.
57
+ *
58
+ * @param {string} testSuite
59
+ * @param {boolean} hasNewTest
60
+ * @returns {Promise<boolean>}
61
+ */
62
+ async function requestBrowserEfdSuiteAdmission (testSuite, hasNewTest) {
63
+ try {
64
+ if (!browserCommands) {
65
+ const vitestBrowser = await import('@vitest/browser/context')
66
+ browserCommands = vitestBrowser.commands
67
+ }
68
+ return await browserCommands[efdSuiteAdmissionBrowserCommand](testSuite, hasNewTest) === true
69
+ } catch (error) {
70
+ // Browser Mode setup runs without dd-trace, so the tracer logger is unavailable.
71
+ globalThis.console?.error('Datadog Test Optimization could not request Vitest EFD suite admission.', error)
72
+ return false
73
+ }
74
+ }
75
+
51
76
  if (isNoWorkerInitActive) {
52
77
  // eslint-disable-next-line no-empty-pattern
53
- beforeAll(function ({}, suite) {
78
+ beforeAll(async function ({}, suite) {
54
79
  suite ||= arguments[0]
55
- applyExecutionChanges(suite)
80
+ const efdSuiteCandidate = getEarlyFlakeDetectionSuiteCandidate(suite)
81
+ const isEfdSuiteAdmissionAllowed = !efdSuiteCandidate || !isEfdSuiteAdmissionEnabled ||
82
+ await requestBrowserEfdSuiteAdmission(efdSuiteCandidate.testSuite, efdSuiteCandidate.hasNewTest)
83
+ applyExecutionChanges(suite, isEfdSuiteAdmissionAllowed)
56
84
  })
57
85
 
58
86
  beforeEach(function ({ onTestFinished, task, skip }) {
59
87
  const testSuite = getTestSuite(task)
60
88
  const testName = getTestName(task)
61
89
  const isAttemptToFixTest = attemptToFixTests[testSuite]?.[testName]
62
- const isEarlyFlakeDetectionTestAttempt = isEarlyFlakeDetectionTest(testSuite, testName)
90
+ const isEarlyFlakeDetectionTestAttempt = isEarlyFlakeDetectionTask(task)
63
91
  const isQuarantinedTest = quarantinedTests[testSuite]?.[testName] && !isAttemptToFixTest
64
92
  const attemptIndex = getNextAttemptIndex(task)
65
93
  const attemptStart = now()
@@ -108,12 +136,12 @@ if (isNoWorkerInitActive) {
108
136
  })
109
137
  }
110
138
 
111
- function applyExecutionChanges (suite) {
139
+ function applyExecutionChanges (suite, isEfdSuiteAdmissionAllowed) {
112
140
  const tasks = suite?.tasks
113
141
  if (tasks) {
114
142
  for (const task of tasks) {
115
143
  if (task.type === 'suite') {
116
- applyExecutionChanges(task)
144
+ applyExecutionChanges(task, isEfdSuiteAdmissionAllowed)
117
145
  continue
118
146
  }
119
147
 
@@ -125,7 +153,7 @@ function applyExecutionChanges (suite) {
125
153
  task.meta.__ddTestOptAtfRetries = attemptToFixRetries
126
154
  } else if (disabledTests[testSuite]?.[testName]) {
127
155
  task.mode = 'skip'
128
- } else if (isEarlyFlakeDetectionTest(testSuite, testName)) {
156
+ } else if (isEfdSuiteAdmissionAllowed && isEarlyFlakeDetectionTest(testSuite, testName)) {
129
157
  task.retry = 0
130
158
  task.repeats = earlyFlakeDetectionRetries
131
159
  task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries
@@ -333,7 +361,7 @@ function recordTestOptimizationStatus (task, attemptIndex = task.result?.repeatC
333
361
 
334
362
  if (attemptToFixTests[testSuite]?.[testName]) {
335
363
  recordAttemptToFixStatus(task, attemptIndex, onlyIfNewErrors)
336
- } else if (isEarlyFlakeDetectionTest(testSuite, testName)) {
364
+ } else if (isEarlyFlakeDetectionTask(task)) {
337
365
  recordEarlyFlakeDetectionStatus(task, attemptIndex, onlyIfNewErrors)
338
366
  } else if (task.repeats > 0) {
339
367
  recordManualRepeatStatus(task, attemptIndex)
@@ -510,7 +538,7 @@ function getFinalAttemptIndex (task) {
510
538
  if (attemptToFixTests[testSuite]?.[testName]) {
511
539
  return getAttemptToFixRetryCount(task)
512
540
  }
513
- if (isEarlyFlakeDetectionTest(testSuite, testName)) {
541
+ if (isEarlyFlakeDetectionTask(task)) {
514
542
  return getEarlyFlakeDetectionRetryCountForTask(task)
515
543
  }
516
544
 
@@ -671,6 +699,64 @@ function isEarlyFlakeDetectionTest (testSuite, testName) {
671
699
  return !testsForSuite.includes(testName)
672
700
  }
673
701
 
702
+ /**
703
+ * Returns whether Datadog admitted this task for EFD retries.
704
+ *
705
+ * @param {object} task
706
+ * @returns {boolean}
707
+ */
708
+ function isEarlyFlakeDetectionTask (task) {
709
+ return task.meta.__ddTestOptEfdRetries !== undefined
710
+ }
711
+
712
+ /**
713
+ * Returns suite admission data when a collected suite contains an EFD candidate.
714
+ *
715
+ * @param {object} suite
716
+ * @returns {{ hasNewTest: boolean, testSuite: string }|undefined}
717
+ */
718
+ function getEarlyFlakeDetectionSuiteCandidate (suite) {
719
+ const tasks = suite?.tasks
720
+ if (!tasks) return
721
+
722
+ let candidate
723
+ for (const task of tasks) {
724
+ if (task.type === 'suite') {
725
+ const nestedCandidate = getEarlyFlakeDetectionSuiteCandidate(task)
726
+ if (nestedCandidate) {
727
+ candidate ||= nestedCandidate
728
+ candidate.hasNewTest ||= nestedCandidate.hasNewTest
729
+ }
730
+ continue
731
+ }
732
+
733
+ if (task.mode === 'skip' || task.mode === 'todo') continue
734
+
735
+ const testSuite = getTestSuite(task)
736
+ const testName = getTestName(task)
737
+ if (attemptToFixTests[testSuite]?.[testName] || disabledTests[testSuite]?.[testName]) continue
738
+ if (!isEarlyFlakeDetectionTest(testSuite, testName)) continue
739
+
740
+ candidate ||= {
741
+ hasNewTest: false,
742
+ testSuite,
743
+ }
744
+ candidate.hasNewTest ||= isNewTest(testSuite, testName)
745
+ }
746
+ return candidate
747
+ }
748
+
749
+ /**
750
+ * Returns whether a collected test is absent from the known-tests response.
751
+ *
752
+ * @param {string} testSuite
753
+ * @param {string} testName
754
+ * @returns {boolean}
755
+ */
756
+ function isNewTest (testSuite, testName) {
757
+ return !(knownTests[testSuite] || []).includes(testName)
758
+ }
759
+
674
760
  function isModifiedTest (testSuite) {
675
761
  return modifiedFiles[testSuite]?.length > 0
676
762
  }
@@ -712,12 +798,28 @@ function getIsNoWorkerInitActive () {
712
798
 
713
799
  function getProvidedContext () {
714
800
  try {
715
- return inject('_ddVitestWorkerSetup') || {}
801
+ return parseProvidedContextValue(inject('_ddVitestWorkerSetup'))
716
802
  } catch {
717
803
  try {
718
- return globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup || {}
804
+ return parseProvidedContextValue(globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup)
719
805
  } catch {
720
806
  return {}
721
807
  }
722
808
  }
723
809
  }
810
+
811
+ /**
812
+ * Restore context serialized to keep Vitest's inline browser bootstrap script valid.
813
+ *
814
+ * @param {object|string|undefined} value
815
+ * @returns {object}
816
+ */
817
+ function parseProvidedContextValue (value) {
818
+ if (typeof value !== 'string' || !value.startsWith(SERIALIZED_CONTEXT_PREFIX)) return value || {}
819
+
820
+ try {
821
+ return JSON.parse(value.slice(SERIALIZED_CONTEXT_PREFIX.length))
822
+ } catch {
823
+ return {}
824
+ }
825
+ }
package/index.d.ts CHANGED
@@ -4296,6 +4296,11 @@ declare namespace tracer {
4296
4296
  * Audio segments attached to the message (e.g. speech input/output)
4297
4297
  */
4298
4298
  audioParts?: AudioPart[],
4299
+
4300
+ /**
4301
+ * Images attached to the message (e.g. vision input, generated output)
4302
+ */
4303
+ imageParts?: ImagePart[],
4299
4304
  }
4300
4305
 
4301
4306
  /**
@@ -4313,6 +4318,43 @@ declare namespace tracer {
4313
4318
  content: string,
4314
4319
  }
4315
4320
 
4321
+ /**
4322
+ * Represents an image attached to an LLM chat model message, carrying exactly
4323
+ * one of inline `content` or an `attachmentKey`. Supplying neither or both is
4324
+ * rejected by the tagger, and the union keeps both shapes from type-checking.
4325
+ */
4326
+ type ImagePart = {
4327
+ /**
4328
+ * The MIME type of the image (e.g. "image/png", "image/jpeg")
4329
+ */
4330
+ mimeType: string,
4331
+
4332
+ /**
4333
+ * The image content as a base64-encoded string
4334
+ */
4335
+ content: string,
4336
+
4337
+ /**
4338
+ * Explicitly excluded when inline content is present to maintain type safety.
4339
+ */
4340
+ attachmentKey?: never,
4341
+ } | {
4342
+ /**
4343
+ * The MIME type of the image (e.g. "image/png", "image/jpeg")
4344
+ */
4345
+ mimeType: string,
4346
+
4347
+ /**
4348
+ * Key of an already-uploaded image, in place of inline content
4349
+ */
4350
+ attachmentKey: string,
4351
+
4352
+ /**
4353
+ * Explicitly excluded when an attachment key is present to maintain type safety.
4354
+ */
4355
+ content?: never,
4356
+ }
4357
+
4316
4358
  /**
4317
4359
  * Represents a single tool call for an LLM chat model message
4318
4360
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.120.0",
3
+ "version": "5.121.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -22,8 +22,9 @@
22
22
  "type:check": "tsc --noEmit -p tsconfig.dev.json",
23
23
  "type:doc:build": "cd docs && yarn && yarn build",
24
24
  "type:doc:test": "cd docs && yarn && yarn test",
25
- "lint": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && node scripts/check-docker-image-shas.js && eslint . --concurrency=auto --max-warnings 0 && npm run lint:codeowners:ci && npm run verify-exercised-tests",
25
+ "lint": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && node scripts/check-docker-image-shas.js && eslint . --concurrency=auto --max-warnings 0 && npm run verify:carrier-fields && npm run lint:codeowners:ci && npm run verify-exercised-tests",
26
26
  "lint:fix": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && node scripts/check-docker-image-shas.js && eslint . --concurrency=auto --max-warnings 0 --fix",
27
+ "verify:carrier-fields": "node scripts/verify-carrier-fields.mjs",
27
28
  "lint:inspect": "npx @eslint/config-inspector@latest",
28
29
  "lint:codeowners": "codeowners-audit",
29
30
  "lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js' --glob='benchmark/sirun/**' --glob='.agents/**' --glob='.claude/**' --glob='integration-tests/**' --glob='packages/dd-trace/src/guardrails/**'",
@@ -39,7 +40,7 @@
39
40
  "test:appsec:plugins:ci": "yarn services && node scripts/c8-ci.js test:appsec:plugins",
40
41
  "test:debugger": "mocha \"packages/dd-trace/test/debugger/**/*.spec.js\"",
41
42
  "test:debugger:ci": "node scripts/c8-ci.js test:debugger",
42
- "test:eslint-rules": "node eslint-rules/*.test.mjs",
43
+ "test:eslint-rules": "node scripts/run-eslint-rule-tests.mjs eslint-rules/*.test.mjs",
43
44
  "test:trace:core": "node scripts/mocha-parallel-files.js --expose-gc --timeout 30000 -- \"packages/dd-trace/test/*.spec.js\" \"packages/dd-trace/test/{agent,ci-visibility,config,crashtracking,datastreams,encode,exporters,msgpack,opentelemetry,opentracing,payload-tagging,plugins,remote_config,service-naming,standalone,telemetry,external-logger}/**/*.spec.js\"",
44
45
  "test:trace:core:ci": "node scripts/c8-ci.js test:trace:core",
45
46
  "test:trace:guardrails": "mocha \"packages/dd-trace/test/guardrails/**/*.spec.js\"",
@@ -29,6 +29,17 @@ const channels = {
29
29
 
30
30
  const disabledHeaderWeakSet = new WeakSet()
31
31
 
32
+ const EMPTY_CARRIER = Object.create(null)
33
+ /** @type {string[]} */
34
+ const NO_MERGEABLE_KEYS = []
35
+
36
+ /** @typedef {string | Buffer} NativeHeaderValue */
37
+ /** @typedef {Array<Record<string, NativeHeaderValue>>} NativeHeaders */
38
+ /** @typedef {Record<string, NativeHeaderValue | NativeHeaderValue[]>} NativeHeaderCarrier */
39
+
40
+ /** @type {NativeHeaders} */
41
+ const EMPTY_NATIVE_HEADERS = []
42
+
32
43
  // we need to store the offset per partition per topic for the consumer to track offsets for DSM
33
44
  const latestConsumerOffsets = new Map()
34
45
 
@@ -60,17 +71,47 @@ function instrumentBaseModule (module) {
60
71
  }
61
72
 
62
73
  const brokers = this.globalConfig?.['bootstrap.servers']
74
+ const nativeHeaders = Array.isArray(headers) ? headers : EMPTY_NATIVE_HEADERS
75
+
76
+ let carrier
77
+ let forwardCallerHeaders = false
78
+ let applicationValues = EMPTY_CARRIER
79
+ let headerKeys = NO_MERGEABLE_KEYS
80
+ if (nativeHeaders.length !== 0) {
81
+ // `__proto__` is a legal Kafka header key and propagation never
82
+ // writes it, so only the seeded carrier needs a null prototype.
83
+ const candidateCarrier = Object.create(null)
84
+ const candidateValues = Object.create(null)
85
+ const candidateKeys = seedNativeHeaderCarrier(nativeHeaders, candidateCarrier, candidateValues)
86
+ if (candidateKeys === undefined) {
87
+ forwardCallerHeaders = true
88
+ } else if (candidateKeys.length !== 0) {
89
+ carrier = candidateCarrier
90
+ applicationValues = candidateValues
91
+ headerKeys = candidateKeys
92
+ }
93
+ }
63
94
 
64
95
  const ctx = {
65
96
  topic,
66
- messages: [{ key, value: message }],
97
+ messages: [{
98
+ key,
99
+ value: message,
100
+ headers: carrier,
101
+ }],
67
102
  bootstrapServers: brokers,
68
103
  }
69
104
 
70
105
  return channels.producerStart.runStores(ctx, () => {
71
106
  try {
72
- const headers = convertHeaders(ctx.messages[0].headers)
73
- const result = produce.apply(this, [topic, partition, message, key, timestamp, opaque, headers])
107
+ const producedHeaders = forwardCallerHeaders
108
+ ? nativeHeaders
109
+ : mergeNativeHeaders(
110
+ nativeHeaders, headerKeys, applicationValues, ctx.messages[0].headers ?? EMPTY_CARRIER
111
+ )
112
+ const result = produce.apply(this, [
113
+ topic, partition, message, key, timestamp, opaque, producedHeaders,
114
+ ])
74
115
 
75
116
  ctx.result = result
76
117
  channels.producerCommit.publish(ctx)
@@ -432,7 +473,71 @@ function getLatestOffsets () {
432
473
  return [...latestConsumerOffsets.values()]
433
474
  }
434
475
 
435
- function convertHeaders (headers) {
436
- // convert headers from object to array of objects with 1 key and value per array entry
437
- return Object.entries(headers).map(([key, value]) => ({ [key.toString()]: value.toString() }))
476
+ /**
477
+ * `Producer::NodeProduce` reads the first *enumerable* own key of each entry and
478
+ * validates the value itself. A `null` or `undefined` entry aborts the process
479
+ * there, so those are the only entries we refuse to hand back to it.
480
+ *
481
+ * @param {NativeHeaders} headers Caller-owned.
482
+ * @param {NativeHeaderCarrier} carrier
483
+ * @param {NativeHeaderCarrier} applicationValues
484
+ * @returns {string[] | undefined} `undefined` when the binding has to reject the
485
+ * array itself; empty when it cannot consume the array at all.
486
+ * @see https://github.com/confluentinc/confluent-kafka-javascript/blob/v1.9.1/src/producer.cc
487
+ */
488
+ function seedNativeHeaderCarrier (headers, carrier, applicationValues) {
489
+ const keys = new Array(headers.length)
490
+ for (let i = 0; i < headers.length; i++) {
491
+ const header = headers[i]
492
+ if (header === null || header === undefined) return NO_MERGEABLE_KEYS
493
+
494
+ const key = Object.keys(header)[0]
495
+ if (key === undefined) return
496
+
497
+ keys[i] = key
498
+ const value = header[key]
499
+ if (typeof value !== 'string' && !Buffer.isBuffer(value)) return
500
+
501
+ if (i === 0) {
502
+ carrier[key] = value
503
+ applicationValues[key] = value
504
+ continue
505
+ }
506
+
507
+ const previousValue = carrier[key]
508
+ if (previousValue === undefined) {
509
+ carrier[key] = value
510
+ } else if (Array.isArray(previousValue)) {
511
+ previousValue.push(value)
512
+ } else {
513
+ carrier[key] = [previousValue, value]
514
+ }
515
+ applicationValues[key] = carrier[key]
516
+ }
517
+ return keys
518
+ }
519
+
520
+ /**
521
+ * @param {NativeHeaders} headers Caller-owned; surviving entries are forwarded
522
+ * by reference, never copied or mutated.
523
+ * @param {string[]} headerKeys Parallel to `headers`.
524
+ * @param {NativeHeaderCarrier} applicationValues Carrier contents before
525
+ * propagation ran.
526
+ * @param {NativeHeaderCarrier} carrier
527
+ */
528
+ function mergeNativeHeaders (headers, headerKeys, applicationValues, carrier) {
529
+ const merged = []
530
+ for (let i = 0; i < headerKeys.length; i++) {
531
+ const key = headerKeys[i]
532
+ if (carrier[key] === applicationValues[key]) {
533
+ merged.push(headers[i])
534
+ }
535
+ }
536
+
537
+ for (const key of Object.keys(carrier)) {
538
+ if (carrier[key] !== applicationValues[key]) {
539
+ merged.push({ [key]: carrier[key] })
540
+ }
541
+ }
542
+ return merged
438
543
  }